diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 2b57b9264..95879e84b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,8 +1,8 @@ # Release notes -## New in git-machete 3.15.3 +## New in git-machete 3.16.0 -- improved: stability of releases to Homebrew +- deprecated: `machete.overrideForkPoint..whileDescendantOf` is no longer taken into account; it's still written, however, for compatibility reasons ## New in git-machete 3.15.2 diff --git a/docs/source/cli_help/config.rst b/docs/source/cli_help/config.rst index 6c37eca0d..d6ea155ee 100644 --- a/docs/source/cli_help/config.rst +++ b/docs/source/cli_help/config.rst @@ -14,13 +14,13 @@ Note: ``config`` is not a command as such, just a help topic (there is no ``git .. include:: github_config_keys.rst :start-line: 2 -``machete.overrideForkPoint..{to,whileDescendantOf}``: - Executing ``git machete fork-point --override-to= []`` sets up a fork point override for . +``machete.overrideForkPoint..to``: + Executing ``git machete fork-point --override-to[-parent|-inferred|=] []`` sets up a fork point override for . - The override data is stored under ``machete.overrideForkPoint..to`` and - ``machete.overrideForkPoint..whileDescendantOf`` git config keys. + The override data is stored under ``machete.overrideForkPoint..to`` git config key. - There should be **no** need for the user to interact with these keys directly. + There should be **no** need for the user to interact with this key directly, + ``git machete fork-point`` with flags should be used instead. ``machete.status.extraSpaceBeforeBranchName``: .. include:: status_config_key.rst diff --git a/docs/source/cli_help/fork-point.rst b/docs/source/cli_help/fork-point.rst index e4fcf7b06..208965b12 100644 --- a/docs/source/cli_help/fork-point.rst +++ b/docs/source/cli_help/fork-point.rst @@ -19,21 +19,21 @@ fork-point git machete fork-point --override-to=|--override-to-inferred|--override-to-parent [] git machete fork-point --unset-override [] -Note: in all three forms, if no is specified, the currently checked out branch is assumed. +Note: in all three forms, if no ```` is specified, the currently checked out branch is assumed. The branch in question does not need to occur in the definition file. -Without any option, displays full hash of the fork point commit for the . -Fork point of the given is the commit at which the history of the diverges from history of any other branch. +Without any option, displays full hash of the fork point commit for the ````. +Fork point of the given ```` is the commit at which the history of the ```` diverges from history of any other branch. -Fork point is assumed by many ``git machete`` commands as the place where the unique history of the starts. +Fork point is assumed by many ``git machete`` commands as the place where the unique history of the ```` starts. The range of commits between the fork point and the tip of the given branch is, for instance: * listed for each branch by ``git machete status --list-commits`` * passed to ``git rebase`` by ``git machete`` ``reapply``/``slide-out``/``traverse``/``update`` * provided to ``git diff``/``log`` by ``git machete`` ``diff``/``log``. -``git machete`` assumes fork point of is the most recent commit in the log of that has NOT been introduced on that very branch, +``git machete`` assumes fork point of ```` is the most recent commit in the log of ```` that has NOT been introduced on that very branch, but instead occurs on a reflog (see help for ``git reflog``) of some other, usually chronologically earlier, branch. This yields a correct result in typical cases, but there are some situations (esp. when some local branches have been deleted) where the fork point might not be determined correctly. @@ -47,26 +47,25 @@ while the former scans reflogs of all local branches and their remote tracking b This makes git machete's ``fork-point`` more resilient to modifications of ``.git/machete`` :ref:`file` where certain branches are re-attached under new parents (upstreams). -With ``--override-to=``, sets up a fork point override for . -Fork point for will be overridden to the provided (commit) as long as the still points to (or is descendant of) the commit X -that pointed to at the moment the override is set up. +With ``--override-to=``, sets up a fork point override for ````. +Fork point for ```` will be overridden to the provided (commit) as long as the ```` still points to (or is descendant of) that commit. Even if revision is a symbolic name (e.g. other branch name or ``HEAD~3)`` and not explicit commit hash (like ``a1b2c3ff``), it's still resolved to a specific commit hash at the moment the override is set up (and not later when the override is actually used). -The override data is stored under ``machete.overrideForkPoint..to`` and ``machete.overrideForkPoint..whileDescendantOf`` git config keys. -Note: the provided fork point must be an ancestor of the current commit X. +The override data is stored under ``machete.overrideForkPoint..to`` git config key. +Note: the provided fork point must be an ancestor of the current ```` commit. -With ``--override-to-parent``, overrides fork point of the to the commit currently pointed by 's parent in the branch dependency tree. -Note: this will only work if has a parent at all (i.e. is not a root) and parent of is an ancestor of current commit X. +With ``--override-to-parent``, overrides fork point of the ```` to the commit currently pointed by ````'s parent in the branch dependency tree. +Note: this will only work if ```` has a parent at all (i.e. is not a root) and parent of ```` is an ancestor of current ```` commit. -With ``--inferred``, displays the commit that ``git machete fork-point`` infers to be the fork point of . -If there is NO fork point override for , this is identical to the output of ``git machete fork-point``. -If there is a fork point override for , this is identical to the what the output of ``git machete fork-point`` would be if the override was NOT present. +With ``--inferred``, displays the commit that ``git machete fork-point`` infers to be the fork point of ````. +If there is NO fork point override for ````, this is identical to the output of ``git machete fork-point``. +If there is a fork point override for ````, this is identical to the what the output of ``git machete fork-point`` would be if the override was NOT present. -With ``--override-to-inferred`` option, overrides fork point of the to the commit that ``git machete fork-point`` infers to be the fork point of . +With ``--override-to-inferred`` option, overrides fork point of the ```` to the commit that ``git machete fork-point`` infers to be the fork point of ````. Note: this piece of information is also displayed by ``git machete status --list-commits`` in case a :yellow:`yellow` edge occurs. -With ``--unset-override``, the fork point override for is unset. -This is simply done by removing the corresponding ``machete.overrideForkPoint..*`` config entries. +With ``--unset-override``, the fork point override for ```` is unset. +This is simply done by removing the corresponding ``machete.overrideForkPoint..to`` config entry. Note: if an overridden fork point applies to a branch ``B``, then it's considered to be connected with a :green:`green` edge to its upstream (parent) ``U``, diff --git a/git_machete/__init__.py b/git_machete/__init__.py index 1394f3dcd..0c9d57a9e 100644 --- a/git_machete/__init__.py +++ b/git_machete/__init__.py @@ -1 +1 @@ -__version__ = '3.15.3' +__version__ = '3.16.0' diff --git a/git_machete/client.py b/git_machete/client.py index 7bcaf09c6..c270dbc4a 100644 --- a/git_machete/client.py +++ b/git_machete/client.py @@ -1631,71 +1631,46 @@ def __infer_upstream(self, debug(f"upstream candidate {candidate} rejected ({reject_reason_message})") return None - # Also includes config that is incomplete (only one entry out of two) or otherwise invalid. + # Also includes config that is invalid (corresponding to a non-existent/GCed commit etc.). def has_any_fork_point_override_config(self, branch: LocalBranchShortName) -> bool: return (self.__git.get_config_attr_or_none(git_config_keys.override_fork_point_to(branch)) or + # Note that we still include the now-deprecated `whileDescendantOf` key for this purpose. self.__git.get_config_attr_or_none(git_config_keys.override_fork_point_while_descendant_of(branch))) is not None def __get_fork_point_override_data(self, branch: LocalBranchShortName) -> Optional[ForkPointOverrideData]: - to, while_descendant_of = None, None + # Note that here we ignore the now-deprecated `whileDescendantOf`. to_key = git_config_keys.override_fork_point_to(branch) if FullCommitHash.is_valid(value=self.__git.get_config_attr_or_none(to_key), git_context=self.__git): to = FullCommitHash.of(self.__git.get_config_attr_or_none(to_key)) - while_descendant_of_key = git_config_keys.override_fork_point_while_descendant_of(branch) - if FullCommitHash.is_valid(value=self.__git.get_config_attr_or_none(while_descendant_of_key), git_context=self.__git): - while_descendant_of = FullCommitHash.of(self.__git.get_config_attr_or_none(while_descendant_of_key)) - if not to and not while_descendant_of: - return None - if to and not while_descendant_of: - warn(f"`{to_key}` config is set but `{while_descendant_of_key}` config is missing or invalid") - return None - if not to and while_descendant_of: - warn(f"`{while_descendant_of_key}` config is set but `{to_key}` config is missing or invalid") + else: return None to_hash: Optional[FullCommitHash] = self.__git.get_commit_hash_by_revision(to) - while_descendant_of_hash: Optional[FullCommitHash] = self.__git.get_commit_hash_by_revision(while_descendant_of) - if not to_hash or not while_descendant_of_hash: - if not to_hash: - warn(f"`{to_key}` config value {bold(to)} does not point to a valid commit") - if not while_descendant_of_hash: - warn(f"`{while_descendant_of_key}` config value {bold(while_descendant_of)} does not point to a valid commit") - return None - # This check needs to be performed every time the config is retrieved. - # We can't rely on the values being validated in set_fork_point_override(), - # since the config could have been modified outside of git-machete. - if not self.__git.is_ancestor_or_equal(to_hash.full_name(), while_descendant_of_hash.full_name()): - warn( - f"commit {bold(self.__git.get_short_commit_hash_by_revision(to))} pointed by `{to_key}` config " - f"is not an ancestor of commit {bold(self.__git.get_short_commit_hash_by_revision(while_descendant_of))} " - f"pointed by `{while_descendant_of_key}` config") + if not to_hash: + warn(f"`{to_key}` config value {bold(to)} does not point to a valid commit") return None - return ForkPointOverrideData(to_hash, while_descendant_of_hash) + + return ForkPointOverrideData(to_hash) def __get_overridden_fork_point(self, branch: LocalBranchShortName) -> Optional[FullCommitHash]: override_data: ForkPointOverrideData = self.__get_fork_point_override_data(branch) if not override_data: return None - to, while_descendant_of = override_data.to_hash, override_data.while_descendant_of_hash - # Note that this check is distinct from the is_ancestor check performed in - # get_fork_point_override_data. - # While the latter checks the sanity of fork point override configuration, - # the former checks if the override still applies to wherever the given - # branch currently points. - if not self.__git.is_ancestor_or_equal(while_descendant_of.full_name(), branch.full_name()): + to = override_data.to_hash + # Checks if the override still applies to wherever the given branch currently points. + if not self.__git.is_ancestor_or_equal(to.full_name(), branch.full_name()): warn(fmt( - f"since branch {bold(branch)} is no longer a descendant of commit " - f"{bold(while_descendant_of)}, ", - f"the fork point override to commit {bold(to)} no longer applies.\n", + f"since branch {bold(branch)} is no longer a descendant of commit {bold(to)}, ", + "the fork point override to this commit no longer applies.\n", f"Consider running:\n `git machete fork-point --unset-override {branch}`\n")) return None - debug(f"since branch {branch} is descendant of while_descendant_of={while_descendant_of}, " - f"fork point of {branch} is overridden to {to}") + debug(f"since branch {branch} is descendant of {to}, fork point of {branch} is overridden to {to}") return to def unset_fork_point_override(self, branch: LocalBranchShortName) -> None: self.__git.unset_config_attr(git_config_keys.override_fork_point_to(branch)) + # Note that we still unset the now-deprecated `whileDescendantOf` key. self.__git.unset_config_attr(git_config_keys.override_fork_point_while_descendant_of(branch)) def set_fork_point_override(self, branch: LocalBranchShortName, to_revision: AnyRevision) -> None: @@ -1711,20 +1686,15 @@ def set_fork_point_override(self, branch: LocalBranchShortName, to_revision: Any to_key = git_config_keys.override_fork_point_to(branch) self.__git.set_config_attr(to_key, to_hash) + # Let's still set the now-deprecated `whileDescendantOf` key to maintain compatibility with older git-machete clients + # (esp. IntelliJ plugin) that still require that key for an override to apply. while_descendant_of_key = git_config_keys.override_fork_point_while_descendant_of(branch) - branch_hash = self.__git.get_commit_hash_by_revision(branch) - self.__git.set_config_attr(while_descendant_of_key, branch_hash) - - print(fmt(f"Fork point for {branch} is overridden to " - f"{self.__git.get_revision_repr(to_revision)}.\n", - f"This applies as long as {branch} points to (or is descendant of)" - " its current head (commit " - f"{self.__git.get_short_commit_hash_by_revision(branch_hash)}).\n\n", - f"This information is stored under git config keys:\n" - f" * `{to_key}`\n" - f" * `{while_descendant_of_key}`\n\n", - "To unset this override, use:\n `git machete fork-point " - f"--unset-override {branch}`")) + self.__git.set_config_attr(while_descendant_of_key, to_hash) + + print(fmt(f"Fork point for {branch} is overridden to {self.__git.get_revision_repr(to_revision)}.\n", + f"This applies as long as {branch} is a descendant of commit {to_hash}.\n\n" + f"This information is stored under `{to_key}` git config key.\n\n" + f"To unset this override, use:\n `git machete fork-point --unset-override {branch}`")) def __pick_remote( self, diff --git a/git_machete/generated_docs.py b/git_machete/generated_docs.py index 17f432c8b..20466d5b8 100644 --- a/git_machete/generated_docs.py +++ b/git_machete/generated_docs.py @@ -272,14 +272,14 @@ Note that you do not need to set all four keys at once. For example, in a typical usage of GitHub Enterprise, it should be enough to just set `machete.github.domain`. - `machete.overrideForkPoint..{to,whileDescendantOf}`: + `machete.overrideForkPoint..to`: - Executing `git machete fork-point --override-to= []` sets up a fork point override for . + Executing `git machete fork-point --override-to[-parent|-inferred|=] []` sets up a fork point override for . - The override data is stored under `machete.overrideForkPoint..to` and - `machete.overrideForkPoint..whileDescendantOf` git config keys. + The override data is stored under `machete.overrideForkPoint..to` git config key. - There should be no need for the user to interact with these keys directly. + There should be no need for the user to interact with this key directly, + `git machete fork-point` with flags should be used instead. `machete.status.extraSpaceBeforeBranchName`: @@ -438,19 +438,19 @@ git machete fork-point --override-to=|--override-to-inferred|--override-to-parent [] git machete fork-point --unset-override [] - Note: in all three forms, if no is specified, the currently checked out branch is assumed. + Note: in all three forms, if no `` is specified, the currently checked out branch is assumed. The branch in question does not need to occur in the definition file. - Without any option, displays full hash of the fork point commit for the . - Fork point of the given is the commit at which the history of the diverges from history of any other branch. + Without any option, displays full hash of the fork point commit for the ``. + Fork point of the given `` is the commit at which the history of the `` diverges from history of any other branch. - Fork point is assumed by many `git machete` commands as the place where the unique history of the starts. + Fork point is assumed by many `git machete` commands as the place where the unique history of the `` starts. The range of commits between the fork point and the tip of the given branch is, for instance: * listed for each branch by `git machete status --list-commits` * passed to `git rebase` by `git machete` `reapply`/`slide-out`/`traverse`/`update` * provided to `git diff`/`log` by `git machete` `diff`/`log`. - `git machete` assumes fork point of is the most recent commit in the log of that has NOT been introduced on that very branch, + `git machete` assumes fork point of `` is the most recent commit in the log of `` that has NOT been introduced on that very branch, but instead occurs on a reflog (see help for `git reflog`) of some other, usually chronologically earlier, branch. This yields a correct result in typical cases, but there are some situations (esp. when some local branches have been deleted) where the fork point might not be determined correctly. @@ -463,26 +463,25 @@ while the former scans reflogs of all local branches and their remote tracking branches. This makes git machete's `fork-point` more resilient to modifications of `.git/machete` `file` where certain branches are re-attached under new parents (upstreams). - With `--override-to=`, sets up a fork point override for . - Fork point for will be overridden to the provided (commit) as long as the still points to (or is descendant of) the commit X - that pointed to at the moment the override is set up. + With `--override-to=`, sets up a fork point override for ``. + Fork point for `` will be overridden to the provided (commit) as long as the `` still points to (or is descendant of) that commit. Even if revision is a symbolic name (e.g. other branch name or `HEAD~3)` and not explicit commit hash (like `a1b2c3ff`), it's still resolved to a specific commit hash at the moment the override is set up (and not later when the override is actually used). - The override data is stored under `machete.overrideForkPoint..to` and `machete.overrideForkPoint..whileDescendantOf` git config keys. - Note: the provided fork point must be an ancestor of the current commit X. + The override data is stored under `machete.overrideForkPoint..to` git config key. + Note: the provided fork point must be an ancestor of the current `` commit. - With `--override-to-parent`, overrides fork point of the to the commit currently pointed by 's parent in the branch dependency tree. - Note: this will only work if has a parent at all (i.e. is not a root) and parent of is an ancestor of current commit X. + With `--override-to-parent`, overrides fork point of the `` to the commit currently pointed by ``'s parent in the branch dependency tree. + Note: this will only work if `` has a parent at all (i.e. is not a root) and parent of `` is an ancestor of current `` commit. - With `--inferred`, displays the commit that `git machete fork-point` infers to be the fork point of . - If there is NO fork point override for , this is identical to the output of `git machete fork-point`. - If there is a fork point override for , this is identical to the what the output of `git machete fork-point` would be if the override was NOT present. + With `--inferred`, displays the commit that `git machete fork-point` infers to be the fork point of ``. + If there is NO fork point override for ``, this is identical to the output of `git machete fork-point`. + If there is a fork point override for ``, this is identical to the what the output of `git machete fork-point` would be if the override was NOT present. - With `--override-to-inferred` option, overrides fork point of the to the commit that `git machete fork-point` infers to be the fork point of . + With `--override-to-inferred` option, overrides fork point of the `` to the commit that `git machete fork-point` infers to be the fork point of ``. Note: this piece of information is also displayed by `git machete status --list-commits` in case a yellow edge occurs. - With `--unset-override`, the fork point override for is unset. - This is simply done by removing the corresponding `machete.overrideForkPoint..*` config entries. + With `--unset-override`, the fork point override for `` is unset. + This is simply done by removing the corresponding `machete.overrideForkPoint..to` config entry. Note: if an overridden fork point applies to a branch `B`, then it's considered to be connected with a green edge to its upstream (parent) `U`, even if the overridden fork point of `B` is NOT equal to the commit pointed by `U`. diff --git a/git_machete/git_operations.py b/git_machete/git_operations.py index 078a71930..ace674d95 100644 --- a/git_machete/git_operations.py +++ b/git_machete/git_operations.py @@ -127,9 +127,7 @@ def of(value: str) -> Optional["FullCommitHash"]: @staticmethod def is_valid(value: str, git_context: "GitContext") -> bool: - revision = AnyRevision(value) - return value is not None and len(value) == 40 and all(c in string.hexdigits for c in value) \ - and git_context.is_commit_present_in_repository(revision) + return value is not None and len(value) == 40 and all(c in string.hexdigits for c in value) def full_name(self) -> "FullCommitHash": return self @@ -157,9 +155,8 @@ def of(value: str) -> Optional["FullTreeHash"]: class ForkPointOverrideData: - def __init__(self, to_hash: FullCommitHash, while_descendant_of_hash: FullCommitHash): + def __init__(self, to_hash: FullCommitHash): self.to_hash: FullCommitHash = to_hash - self.while_descendant_of_hash: FullCommitHash = while_descendant_of_hash class GitLogEntry(NamedTuple): @@ -193,7 +190,7 @@ def __init__(self) -> None: self.__remotes_cached: Optional[List[str]] = None self.__counterparts_for_fetching_cached: Optional[ Dict[LocalBranchShortName, Optional[RemoteBranchShortName]]] = None # TODO (#110): default dict with None - self.__short_commit_hash_by_revision_cached: Dict[AnyRevision, ShortCommitHash] = {} + self.__short_commit_hash_by_revision_cached: Dict[AnyRevision, Optional[ShortCommitHash]] = {} self.__tree_hash_by_commit_hash_cached: Optional[ Dict[FullCommitHash, Optional[FullTreeHash]]] = None # TODO (#110): default dict with None self.__commit_hash_by_revision_cached: Optional[ @@ -424,9 +421,12 @@ def pull_ff_only(self, remote: str, remote_branch: RemoteBranchShortName) -> Non def __find_short_commit_hash_by_revision(self, revision: AnyRevision) -> ShortCommitHash: return ShortCommitHash.of(self._popen_git("rev-parse", "--short", revision + "^{commit}").stdout.rstrip()) # noqa: FS003 - def get_short_commit_hash_by_revision(self, revision: AnyRevision) -> ShortCommitHash: + def get_short_commit_hash_by_revision_or_none(self, revision: AnyRevision) -> Optional[ShortCommitHash]: if revision not in self.__short_commit_hash_by_revision_cached: - self.__short_commit_hash_by_revision_cached[revision] = self.__find_short_commit_hash_by_revision(revision) + try: + self.__short_commit_hash_by_revision_cached[revision] = self.__find_short_commit_hash_by_revision(revision) + except MacheteException: + self.__short_commit_hash_by_revision_cached[revision] = None return self.__short_commit_hash_by_revision_cached[revision] def __find_commit_hash_by_revision(self, revision: AnyRevision) -> Optional[FullCommitHash]: @@ -682,11 +682,11 @@ def flush_caches(self) -> None: self.__reflogs_cached = None def get_revision_repr(self, revision: AnyRevision) -> str: - short_hash = self.get_short_commit_hash_by_revision(revision) - if self.is_full_hash(revision.full_name()) or revision == short_hash: - return f"commit {revision}" + short_hash = self.get_short_commit_hash_by_revision_or_none(revision) + if not short_hash or self.is_full_hash(revision.full_name()) or revision == short_hash: + return f"commit {revision}" else: - return f"{revision} (commit {self.get_short_commit_hash_by_revision(revision)})" + return f"{revision} (commit {short_hash})" # Note: while rebase is ongoing, the repository is always in a detached HEAD state, # so we need to extract the name of the currently rebased branch from the rebase-specific internals diff --git a/tests/test_fork_point.py b/tests/test_fork_point.py index 788be800e..dfde52b12 100644 --- a/tests/test_fork_point.py +++ b/tests/test_fork_point.py @@ -71,23 +71,23 @@ def test_fork_point_override(self) -> None: # invalid fork point with length not equal to 40 self.repo_sandbox.add_git_config_key('machete.overrideForkPoint.develop.to', 39 * 'a') - self.repo_sandbox.add_git_config_key('machete.overrideForkPoint.develop.whileDescendantOf', 39 * 'b') assert launch_command('fork-point').strip() == develop_branch_fork_point # invalid, non-hexadecimal alphanumeric characters present in the fork point self.repo_sandbox.add_git_config_key('machete.overrideForkPoint.develop.to', 20 * 'g1') - self.repo_sandbox.add_git_config_key('machete.overrideForkPoint.develop.whileDescendantOf', 20 * 'g1') assert launch_command('fork-point').strip() == develop_branch_fork_point # invalid, non-hexadecimal special characters present in the fork point self.repo_sandbox.add_git_config_key('machete.overrideForkPoint.develop.to', 40 * '#') - self.repo_sandbox.add_git_config_key('machete.overrideForkPoint.develop.whileDescendantOf', 40 * '!') assert launch_command('fork-point').strip() == develop_branch_fork_point # valid commit hash but not present in the repository self.repo_sandbox.add_git_config_key('machete.overrideForkPoint.develop.to', 40 * 'a') - self.repo_sandbox.add_git_config_key('machete.overrideForkPoint.develop.whileDescendantOf', 40 * 'a') - assert launch_command('fork-point').strip() == develop_branch_fork_point + assert launch_command('fork-point').strip() == ( + "Warn: since branch develop is no longer a descendant of commit aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, the fork point override to this commit no longer applies.\n" # noqa: E501 + "Consider running:\n" + " git machete fork-point --unset-override develop\n\n" + + develop_branch_fork_point) # valid fork-point override commit hash launch_command('fork-point', f'--override-to={master_branch_first_commit_hash}')