diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index b746c3c99..310e5f3c1 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,8 @@ ## New in git-machete 3.12.0 +- added: subcommand `git machete list childless` + ## New in git-machete 3.11.6 - added: package for Ubuntu 22.04 LTS diff --git a/completion/git-machete.completion.bash b/completion/git-machete.completion.bash index c0fa8258b..8b89dc5f8 100644 --- a/completion/git-machete.completion.bash +++ b/completion/git-machete.completion.bash @@ -4,7 +4,7 @@ _git_machete() { local cmds="add advance anno clean d delete-unmanaged diff discover e edit file fork-point g github go help is-managed l list log reapply s show slide-out squash status traverse update version" local help_topics="$cmds config format hooks" - local categories="addable managed slidable slidable-after unmanaged with-overridden-fork-point" + local categories="addable childless managed slidable slidable-after unmanaged with-overridden-fork-point" local directions="down first last next prev root up" local github_subcommands="anno-prs checkout-prs create-pr retarget-pr sync" local locations="current $directions" diff --git a/completion/git-machete.completion.zsh b/completion/git-machete.completion.zsh index 85f32c9e7..b6f234bcf 100644 --- a/completion/git-machete.completion.zsh +++ b/completion/git-machete.completion.zsh @@ -274,6 +274,7 @@ __git_machete_categories() { # TODO (#115): complete slidable-after's argument categories=( 'addable:all branches (local or remote) than can be added to the definition file' + 'childless:all branches that do not possess child branches' 'managed:all branches that appear in the definition file' 'slidable:all managed branches that have exactly one upstream and one downstream (i.e. the ones that can be slid out with slide-out command)' 'slidable-after:the downstream branch of the given branch, if it exists and is its only downstream (i.e. the one that can be slid out immediately following )' diff --git a/completion/git-machete.fish b/completion/git-machete.fish index aca0265a8..b0948d780 100644 --- a/completion/git-machete.fish +++ b/completion/git-machete.fish @@ -113,13 +113,14 @@ complete -c git -n "__fish_git_using_command machete; and __fish_seen_subcommand # git machete list complete -c git -n "__fish_git_using_command machete; and not __fish_seen_subcommand_from $__mcht_commands" -f -a list -d 'List all branches that fall into one of pre-defined categories (mostly for internal use)' -complete -c git -n "__fish_git_using_command machete; and __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from addable managed slidable slidable-after unmanaged with-overridden-fork-point" -f -a addable -d 'all branches (local or remote) than can be added to the definition file' -complete -c git -n "__fish_git_using_command machete; and __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from addable managed slidable slidable-after unmanaged with-overridden-fork-point" -f -a managed -d 'all branches that appear in the definition file' -complete -c git -n "__fish_git_using_command machete; and __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from addable managed slidable slidable-after unmanaged with-overridden-fork-point" -f -a slidable -d 'all managed branches that have an upstream and can be slid out with slide-out command' -complete -c git -n "__fish_git_using_command machete; and __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from addable managed slidable slidable-after unmanaged with-overridden-fork-point" -f -a slidable-after -d 'the downstream branch of the , if it exists and is the only downstream of (i.e. the one that can be slid out immediately following )' +complete -c git -n "__fish_git_using_command machete; and __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from addable childless managed slidable slidable-after unmanaged with-overridden-fork-point" -f -a addable +complete -c git -n "__fish_git_using_command machete; and __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from addable childless managed slidable slidable-after unmanaged with-overridden-fork-point" -f -a childless -d 'all branches (local or remote) than can be added to the definition file' +complete -c git -n "__fish_git_using_command machete; and __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from addable childless managed slidable slidable-after unmanaged with-overridden-fork-point" -f -a managed -d 'all branches that appear in the definition file' +complete -c git -n "__fish_git_using_command machete; and __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from addable childless managed slidable slidable-after unmanaged with-overridden-fork-point" -f -a slidable -d 'all managed branches that have an upstream and can be slid out with slide-out command' +complete -c git -n "__fish_git_using_command machete; and __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from addable childless managed slidable slidable-after unmanaged with-overridden-fork-point" -f -a slidable-after -d 'the downstream branch of the , if it exists and is the only downstream of (i.e. the one that can be slid out immediately following )' complete -c git -n "__fish_git_using_command machete; and __fish_seen_subcommand_from list; and __fish_seen_subcommand_from slidable-after" -f -a '(__fish_git_local_branches)' -complete -c git -n "__fish_git_using_command machete; and __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from addable managed slidable slidable-after unmanaged with-overridden-fork-point" -f -a unmanaged -d 'all local branches that don\'t appear in the definition file' -complete -c git -n "__fish_git_using_command machete; and __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from addable managed slidable slidable-after unmanaged with-overridden-fork-point" -f -a with-overridden-fork-point -d 'all local branches that have a fork point override set up (even if this override does not affect the location of their fork point anymore).' +complete -c git -n "__fish_git_using_command machete; and __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from addable childless managed slidable slidable-after unmanaged with-overridden-fork-point" -f -a unmanaged -d 'all local branches that don\'t appear in the definition file' +complete -c git -n "__fish_git_using_command machete; and __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from addable childless managed slidable slidable-after unmanaged with-overridden-fork-point" -f -a with-overridden-fork-point -d 'all local branches that have a fork point override set up (even if this override does not affect the location of their fork point anymore).' # git machete log complete -c git -n "__fish_git_using_command machete; and not __fish_seen_subcommand_from $__mcht_commands" -f -a log -d 'Log the part of history specific to the given branch' diff --git a/docs/source/cli_help/list.rst b/docs/source/cli_help/list.rst index 5e84c101e..64022875d 100644 --- a/docs/source/cli_help/list.rst +++ b/docs/source/cli_help/list.rst @@ -8,11 +8,12 @@ list git machete list -where is one of: ``addable``, ``managed``, ``slidable``, ``slidable-after ``, ``unmanaged``, ``with-overridden-fork-point``. +where is one of: ``addable``, ``childless``, ``managed``, ``slidable``, ``slidable-after ``, ``unmanaged``, ``with-overridden-fork-point``. Lists all branches that fall into one of the specified categories: * ``addable``: all branches (local or remote) than can be added to the definition file, + * ``childless``: all branches that do not possess child branches, * ``managed``: all branches that appear in the definition file, * ``slidable``: all managed branches that have an upstream and can be slid out with :ref:`slide-out` command * ``slidable-after ``: the downstream branch of the , if it exists and is the only downstream of (i.e. the one that can be slid out immediately following ), diff --git a/git_machete/cli.py b/git_machete/cli.py index 7a1aa2b6a..d03790731 100644 --- a/git_machete/cli.py +++ b/git_machete/cli.py @@ -253,7 +253,7 @@ def create_cli_parser() -> argparse.ArgumentParser: list_parser.add_argument( 'category', choices=[ - 'addable', 'managed', 'slidable', 'slidable-after', 'unmanaged', + 'addable', 'childless', 'managed', 'slidable', 'slidable-after', 'unmanaged', 'with-overridden-fork-point'] ) list_parser.add_argument('branch', nargs='?', default=argparse.SUPPRESS) @@ -682,6 +682,8 @@ def strip_remote_name(remote_branch: RemoteBranchShortName) -> LocalBranchShortN remote_counterparts_of_local_branches}) res = excluding(git.get_local_branches(), machete_client.managed_branches) + list( map(strip_remote_name, qualifying_remote_branches)) + elif category == "childless": + res = machete_client.get_childless_branches() elif category == "managed": res = machete_client.managed_branches elif category == "slidable": diff --git a/git_machete/client.py b/git_machete/client.py index fc4913d60..64845aaad 100644 --- a/git_machete/client.py +++ b/git_machete/client.py @@ -68,6 +68,10 @@ def up_branch(self) -> Dict[LocalBranchShortName, Optional[LocalBranchShortName] def up_branch(self, val: Dict[LocalBranchShortName, Optional[LocalBranchShortName]]) -> None: self._up_branch = val + def get_childless_branches(self) -> List[LocalBranchShortName]: + parent_branches = [parent_branch for parent_branch, child_branches in self.__down_branches.items() if len(child_branches) > 0] + return excluding(self.managed_branches, parent_branches) + def expect_in_managed_branches(self, branch: LocalBranchShortName) -> None: if branch not in self.managed_branches: raise MacheteException( diff --git a/git_machete/docs.py b/git_machete/docs.py index e7c8f7aea..fa30f7ec1 100644 --- a/git_machete/docs.py +++ b/git_machete/docs.py @@ -554,6 +554,7 @@ Lists all branches that fall into one of the specified categories: * `addable`: all branches (local or remote) than can be added to the definition file, + * `childless`: all branches that do not possess child branches, * `managed`: all branches that appear in the definition file, * `slidable`: all managed branches that have an upstream and can be slid out with `slide-out` command * `slidable-after `: the downstream branch of the , if it exists and is the only downstream of (i.e. the one that can be slid out immediately following ), diff --git a/tests/test_list.py b/tests/test_list.py new file mode 100644 index 000000000..69a6c07be --- /dev/null +++ b/tests/test_list.py @@ -0,0 +1,143 @@ +from textwrap import dedent +from typing import Any + +from .mockers import (GitRepositorySandbox, assert_command, launch_command, mock_run_cmd, rewrite_definition_file) + + +class TestList: + + def setup_method(self) -> None: + self.repo_sandbox = GitRepositorySandbox() + + ( + self.repo_sandbox + # Create the remote and sandbox repos, chdir into sandbox repo + .new_repo(self.repo_sandbox.remote_path, "--bare") + .new_repo(self.repo_sandbox.local_path) + .execute(f"git remote add origin {self.repo_sandbox.remote_path}") + .execute('git config user.email "tester@test.com"') + .execute('git config user.name "Tester Test"') + ) + + def test_list(self, mocker: Any) -> None: + """ + Verify behaviour of a 'git machete list' command. + """ + mocker.patch('git_machete.utils.run_cmd', mock_run_cmd) # to hide git outputs in tests + + ( + self.repo_sandbox.new_branch("master") + .commit("master commit.") + .new_branch("develop") + .commit("develop commit.") + .new_branch("feature_0") + .commit("feature_0 commit.") + .new_branch("feature_0_0") + .commit("feature_0_0 commit.") + .new_branch("feature_0_0_0") + .commit("feature_0_0_0 commit.") + .check_out("feature_0") + .new_branch("feature_0_1") + .commit("feature_0_1 commit.") + .check_out("develop") + .new_branch("feature_1") + .commit("feature_1 commit.") + ) + + body: str = \ + """ + master + develop + feature_0 + feature_0_0 + feature_0_0_0 + feature_0_1 + feature_1 + """ + body = dedent(body) + rewrite_definition_file(body) + + ( + self.repo_sandbox.check_out("develop") + .new_branch("feature_2") + .commit("feature_2 commit.") + ) + + expected_output = """ + master + develop + feature_0 + feature_0_0 + feature_0_0_0 + feature_0_1 + feature_1 + """ + assert_command( + ['list', 'managed'], + expected_output, + indent='' + ) + + expected_output = """ + feature_2 + """ + assert_command( + ['list', 'addable'], + expected_output, + indent='' + ) + + expected_output = """ + master + feature_0_0_0 + feature_0_1 + feature_1 + """ + assert_command( + ['list', 'childless'], + expected_output, + indent='' + ) + + expected_output = """ + feature_0 + feature_0_0 + feature_0_0_0 + feature_0_1 + feature_1 + """ + assert_command( + ['list', 'slidable'], + expected_output, + indent='' + ) + + expected_output = """ + feature_0_0_0 + """ + assert_command( + ['list', 'slidable-after', 'feature_0_0'], + expected_output, + indent='' + ) + + expected_output = """ + feature_2 + """ + assert_command( + ['list', 'unmanaged'], + expected_output, + indent='' + ) + + self.repo_sandbox.check_out("feature_1") + launch_command('fork-point', '--override-to-inferred') + + expected_output = """ + feature_1 + """ + assert_command( + ['list', 'with-overridden-fork-point'], + expected_output, + indent='' + )