Exemple #1
0
def _test_delete_archived_branches(target_directory):
    # type: (Text) -> None
    with _run_test(target_directory):
        _initialize_repo()

        # Create all the branches
        make_child_branch("first_branch")
        assert get_current_branch() == "first_branch"

        git("checkout master")

        make_child_branch("second_branch")
        assert get_current_branch() == "second_branch"

        make_child_branch("second_branch_child_one")
        assert get_current_branch() == "second_branch_child_one"

        make_child_branch("second_branch_child_two")
        assert get_current_branch() == "second_branch_child_two"

        git("checkout master")

        with get_branch_tracker() as tracker:
            tracker.set_is_archived("first_branch", True)
            tracker.set_is_archived("second_branch_child_two", True)

        # Test
        delete_archived_branches()

        with get_branch_tracker() as tracker:
            assert not tracker.is_branch_tracked("first_branch")
            assert tracker.is_branch_tracked("second_branch")
            assert tracker.is_branch_tracked("second_branch_child_one")
            assert not tracker.is_branch_tracked("second_branch_child_two")
def rename_current_branch(new_branch_name):
    # type: (Text) -> None
    current_branch = get_current_branch()
    with get_branch_tracker() as tracker:
        git("checkout -b {}".format(new_branch_name))
        git("branch -d {}".format(current_branch))
        tracker.rename_branch(current_branch, new_branch_name)
def rebase_children(is_recursive, extra_git_rebase_args=()):
    # type: (bool, Sequence[Text]) -> None
    current_branch = get_current_branch()
    if extra_git_rebase_args:
        # If the first extra arg starts with "-", "--" must also have been passed, and
        # argparse doesn't remove it for us
        if extra_git_rebase_args[0] == "--":
            extra_git_rebase_args = extra_git_rebase_args[1:]
        extra_args = " " + " ".join(extra_git_rebase_args)
    else:
        extra_args = ""

    with get_branch_tracker() as tracker:
        do_rebase(tracker, tracker.parent_for_child(current_branch),
                  current_branch, extra_args)

        if is_recursive:
            to_rebase_onto = [current_branch]
            while to_rebase_onto:
                parent = to_rebase_onto.pop()
                children = tracker.children_for_parent(parent)
                for child in children:
                    do_rebase(tracker, parent, child, extra_args)
                    to_rebase_onto.append(child)

        # Go back to where we started.
        git("checkout {}".format(current_branch))
def rename_current_branch(new_branch_name):
    # type: (str) -> None
    current_branch = get_current_branch()
    with get_branch_tracker() as tracker:
        git("checkout -b %s" % new_branch_name)
        git("branch -d %s" % current_branch)
        tracker.rename_branch(current_branch, new_branch_name)
Exemple #5
0
    def run_command(self, args):
        # type: (Namespace) -> None
        extra_arc_land_options = args.arc_land_args
        if extra_arc_land_options:
            # If the first extra arg starts with "-", "--" must also have been passed, and
            # argparse doesn't remove it for us
            if extra_arc_land_options[0] == "--":
                del extra_arc_land_options[0]
            extra_args = " " + " ".join(extra_arc_land_options)
        else:
            extra_args = ""

        current_branch = get_current_branch()
        with get_branch_tracker() as tracker:
            parent = tracker.parent_for_child(current_branch)
            fail_if_not_rebased(current_branch, parent, tracker)

            if parent != "master":
                should_land = raw_input(
                    "Are you sure you want to land onto non-master branch "
                    "'{}'? [y/N] ".format(parent))
                should_land = should_land.lower()
                if should_land not in ("y", "yes"):
                    print "Aborting land"
                    exit()

            arc("land --onto {}{}".format(parent, extra_args))

            # Successfully landed, replace ourselves with our parent
            tracker.collapse_and_remove_parent(current_branch)
Exemple #6
0
def _test_clean_branches(target_directory):
    # type: (Text) -> None
    with _run_test(target_directory):
        _initialize_repo()

        # Create all the branches
        make_child_branch("valid_branch")
        assert get_current_branch() == "valid_branch"

        git("checkout master")

        make_child_branch("ghost_branch_childless")
        assert get_current_branch() == "ghost_branch_childless"

        git("checkout master")

        make_child_branch("ghost_branch_with_children")
        assert get_current_branch() == "ghost_branch_with_children"

        make_child_branch("child_of_ghost_branch")
        assert get_current_branch() == "child_of_ghost_branch"

        git("checkout master")

        print("Deleting branches from git")
        git("branch -D ghost_branch_childless")
        git("branch -D ghost_branch_with_children")

        print("Test cleaning by archiving")
        clean_invalid_branches(dry_run=False, archive=True, upstream=False)

        with get_branch_tracker() as tracker:
            assert not tracker.is_archived("valid_branch")
            assert tracker.is_archived("ghost_branch_childless")
            assert tracker.is_archived("ghost_branch_with_children")

            print("Clear archived flags for next test")
            tracker.set_is_archived("ghost_branch_childless", False)
            tracker.set_is_archived("ghost_branch_with_children", False)

        print("Test cleaning by deleting")
        clean_invalid_branches(dry_run=False, archive=False, upstream=False)

        with get_branch_tracker() as tracker:
            assert tracker.is_branch_tracked("valid_branch")
            assert not tracker.is_branch_tracked("ghost_branch_childless")
            assert tracker.is_branch_tracked("ghost_branch_with_children")
def rename_current_branch(new_branch_name, force):
    # type: (Text, bool) -> None
    current_branch = get_current_branch()
    with get_branch_tracker() as tracker:
        git("checkout -b {}".format(new_branch_name))
        flag = "-D" if force else "-d"
        git("branch {} {}".format(flag, current_branch))
        tracker.rename_branch(current_branch, new_branch_name)
 def run_command(self, args):
     # type: (Namespace) -> None
     new_parent = args.new_parent
     current_branch = get_current_branch()
     with get_branch_tracker() as tracker:
         tracker.set_parent(current_branch, new_parent)
     print "You may want to rebase on top of the new parent to make sure its changes are " \
           "visible in this branch."
def make_child_branch(new_branch_name):
    # type: (str) -> None
    current_branch = get_current_branch()
    current_rev = hash_for(current_branch)
    with get_branch_tracker() as tracker:
        # Add the child using the current branch as the parent.
        tracker.add_child_for_parent(current_branch, new_branch_name, current_rev)
        # Make the new branch, also where the current branch is
        git("checkout -b %s" % new_branch_name)
def main(extra_arc_diff_options):
    # type: (List[str]) -> None
    if extra_arc_diff_options:
        extra_args = " " + " ".join(extra_arc_diff_options)
    else:
        extra_args = ""

    current_branch = get_current_branch()
    with get_branch_tracker() as tracker:
        parent = tracker.parent_for_child(current_branch)
        fail_if_not_rebased(current_branch, parent, tracker)
        arc("diff %s%s" % (parent, extra_args))
Exemple #11
0
def _initialize_repo():
    # type: () -> None
    """
        Initialize a repo and add a first commit so we can tell what branch we're on.
    """
    print("Initializing repo")
    git("init")
    open("hello.txt", "w").close()
    git("add .")
    git("commit -am initial_commit")

    assert get_current_branch() == "master"
def main(force_remove):
    # type: (bool) -> None
    current_branch = get_current_branch()
    with get_branch_tracker() as tracker:
        parent = tracker.parent_for_child(current_branch)
        children = tracker.children_for_parent(current_branch)
        assert not children, "Child branch should not have any children, found %s child(ren)" % len(children)

        git("checkout %s" % parent)
        # This will fail if we're not forcing the remove and the branch isn't merged in.
        delete_flag = "-D" if force_remove else "-d"
        git("branch %s %s" % (delete_flag, current_branch))

        tracker.remove_child_leaf(current_branch)
def get_branch_info(branch, use_null_delimiter):
    # type: (Optional[Text], bool) -> Text
    if branch is None:
        branch = get_current_branch()

    with get_branch_tracker() as tracker:
        if not tracker.has_parent(branch):
            sys.exit("Branch does not have a parent: {}".format(branch))
        parent = tracker.parent_for_child(branch)
        base = tracker.base_for_branch(branch)

    if use_null_delimiter:
        return "{}\0{}".format(parent, base)
    else:
        return "Parent branch: {}; Base revision: {}".format(parent, base)
def print_branch_structure():
    # type: () -> None
    current_branch = get_current_branch()
    with get_branch_tracker() as tracker:
        roots = []
        for parent in tracker.get_all_parents():
            if not tracker.has_parent(parent):
                roots.append(parent)

    first = True
    for root in roots:
        if not first:
            print ""
        first = False
        print format_node(current_branch, root)
        print_tree(tracker, current_branch, root, "")
    def run_command(self, args):
        # type: (Namespace) -> None
        extra_arc_diff_options = args.arc_diff_args
        if extra_arc_diff_options:
            # If the first extra arg starts with "-", "--" must also have been passed, and
            # argparse doesn't remove it for us
            if extra_arc_diff_options[0] == "--":
                del extra_arc_diff_options[0]
            extra_args = " " + " ".join(extra_arc_diff_options)
        else:
            extra_args = ""

        current_branch = get_current_branch()
        with get_branch_tracker() as tracker:
            base = tracker.base_for_branch(current_branch)
            arc("diff {}{}".format(base, extra_args))
Exemple #16
0
def rebase_children(is_recursive):
    # type: (bool) -> None
    current_branch = get_current_branch()
    with get_branch_tracker() as tracker:
        do_rebase(tracker, tracker.parent_for_child(current_branch), current_branch)

        if is_recursive:
            to_rebase_onto = [current_branch]
            while to_rebase_onto:
                parent = to_rebase_onto.pop()
                children = tracker.children_for_parent(parent)
                for child in children:
                    do_rebase(tracker, parent, child)
                    to_rebase_onto.append(child)

        # Go back to where we started.
        git("checkout {}".format(current_branch))
def rebase_children(is_recursive):
    # type: (bool) -> None
    current_branch = get_current_branch()
    with get_branch_tracker() as tracker:
        do_rebase(tracker, tracker.parent_for_child(current_branch), current_branch)

        if is_recursive:
            to_rebase_onto = [current_branch]
            while to_rebase_onto:
                parent = to_rebase_onto.pop()
                children = tracker.children_for_parent(parent)
                for child in children:
                    do_rebase(tracker, parent, child)
                    to_rebase_onto.append(child)

        # Go back to where we started.
        git("checkout %s" % current_branch)
def get_branch_structure_string(show_all):
    # type: (bool) -> str
    current_branch = get_current_branch()
    structure_parts = []  # type: List[str]
    with get_branch_tracker() as tracker:
        roots = []
        for parent in tracker.get_all_parents():
            if not tracker.has_parent(parent):
                roots.append(parent)

        skipped_archived = _get_branch_structure_parts_internal(
            tracker, current_branch, roots, structure_parts, show_all)

        if skipped_archived:
            structure_parts.append("(not displaying archived branches, run with --all to see them)")

    return "\n".join(structure_parts)
def main():
    # type: () -> None
    current_branch = get_current_branch()
    with get_branch_tracker() as tracker:
        parent = tracker.parent_for_child(current_branch)
        fail_if_not_rebased(current_branch, parent, tracker)

        if parent != "master":
            should_land = raw_input("Are you sure you want to land onto non-master branch '%s'? [y/N] " % parent)
            should_land = should_land.lower()
            if should_land not in ("y", "yes"):
                print "Aborting land"
                exit()

        arc("land --onto %s" % parent)

        # Successfully landed, replace ourselves with our parent
        tracker.collapse_and_remove_parent(current_branch)
def get_branch_structure_string(show_all):
    # type: (bool) -> Text
    current_branch = get_current_branch()
    structure_parts = []  # type: List[Text]
    with get_branch_tracker() as tracker:
        roots = []
        for parent in tracker.get_all_parents():
            if not tracker.has_parent(parent):
                roots.append(parent)
        roots = sorted(roots)

        skipped_archived = _get_branch_structure_parts_internal(
            tracker, current_branch, roots, structure_parts, show_all)

        if skipped_archived:
            structure_parts.append("(not displaying archived branches, run with --all to see them)")

    return "\n".join(structure_parts)
def remove_branch(force_remove):
    # type: (bool) -> None
    current_branch = get_current_branch()
    current_commit = hash_for(current_branch)
    with get_branch_tracker() as tracker:
        parent = tracker.parent_for_child(current_branch)
        children = tracker.children_for_parent(current_branch)
        assert not children, \
            "Child branch should not have any children, found {} child(ren)".format(len(children))

        merged_into_parent = [
            line[2:]
            for line in git("branch --merged {}".format(parent)).split('\n')
            if line
        ]
        if current_branch in merged_into_parent:
            print("Removing merged branch {!r} (was at commit {})".format(
                current_branch, current_commit))
        elif force_remove:
            print("Force removing unmerged branch {!r} (was at commit {})".
                  format(
                      current_branch,
                      current_commit,
                  ))
        else:
            print("")
            print("!!!!!!!!")
            print("!!! Trying to remove branch {!r} not merged into its parent. Re-run with" \
                  "".format(current_branch))
            print(
                "!!! '--force' if you want to force the deletion of this branch."
            )
            print("!!!")
            print("!!! WARNING: Running with '--force' may cause data loss")
            print("!!!!!!!!")
            print("")
            exit(1)

        git("checkout {}".format(parent))
        # This will fail if we're not forcing the remove and the branch isn't merged in.
        delete_flag = "-D" if force_remove else "-d"
        git("branch {} {}".format(delete_flag, current_branch))

        tracker.remove_child_leaf(current_branch)
def make_child_branch(new_branch_name, revision=None):
    # type: (Text, Optional[Text]) -> None
    parent = get_current_branch()
    if revision is None:
        # Use the current revision as the base
        base_rev = hash_for("HEAD")
    else:
        # Use the merge-base of the given revision and the parent branch as the base
        base_rev = git("merge-base {} {}".format(parent, revision)).strip()

    with get_branch_tracker() as tracker:
        # Add the child using the current branch as the parent.
        tracker.add_child_for_parent(parent, new_branch_name, base_rev)
        # Make the new branch, either where the current branch is or at the specified revision
        if revision is None:
            command = "checkout -b {}".format(new_branch_name)
        else:
            command = "checkout -b {} {}".format(new_branch_name, revision)
        git(command)
def main(new_parent):
    # type: (str) -> None
    current_branch = get_current_branch()
    with get_branch_tracker() as tracker:
        tracker.set_parent(current_branch, new_parent)
    print "You may want to rebase on top of the new parent to make sure its changes are visible in this branch."
def set_archived(archived, branch_name=None):
    # type: (bool, Optional[str]) -> None
    if branch_name is None:
        branch_name = get_current_branch()
    with get_branch_tracker() as tracker:
        tracker.set_is_archived(branch_name, archived)
def set_archived(archived, branch_name=None):
    # type: (bool, Optional[str]) -> None
    if branch_name is None:
        branch_name = get_current_branch()
    with get_branch_tracker() as tracker:
        tracker.set_is_archived(branch_name, archived)
def _integration_test(target_directory):
    # type: (str) -> None
    target_directory = os.path.expanduser(target_directory)
    target_container = os.path.dirname(target_directory)
    assert not os.path.exists(target_directory)
    assert os.path.isdir(target_container)

    with run_test(target_directory):
        # Initialize a repo and add a first commit so we can tell what branch we're on.
        print "Initializing repo"
        git("init")
        open("hello.txt", "w").close()
        git("add .")
        git("commit -am initial_commit")

        assert get_current_branch() == "master"
        original_commit = hash_for("HEAD")

        # Create all the branches
        make_child_branch("first_branch")
        assert get_current_branch() == "first_branch"

        make_child_branch("second_branch")
        assert get_current_branch() == "second_branch"

        make_child_branch("third_branch")
        assert get_current_branch() == "third_branch"

        # Rename a branch
        rename_current_branch("third_branch_renamed")
        assert get_current_branch() == "third_branch_renamed"

        # Rename it back
        rename_current_branch("third_branch")
        assert get_current_branch() == "third_branch"

        # This should be sibling to second_branch, on top of first_branch
        git("checkout first_branch")
        make_child_branch("sibling_branch")
        assert get_current_branch() == "sibling_branch"

        # Make the first "real" commit, in master
        print "First commit"
        git("checkout master")
        with open(os.path.join(target_directory, "hello.txt"), "w") as f:
            f.write("Hello!")
        # Avoiding spaces because of how we break up args in the `git` function
        git("commit -am first_commit_message")
        first_commit = hash_for("HEAD")
        assert original_commit != first_commit

        # Do the recursive rebase
        print "Rebase first_branch and its children on top of master."
        git("checkout first_branch")
        rebase_children(True)
        assert first_commit == hash_for("first_branch")
        assert first_commit == hash_for("second_branch")
        assert first_commit == hash_for("third_branch")
        assert first_commit == hash_for("sibling_branch")

        # Make a second commit, this time in first_branch
        print "Make a second commit in first_branch"
        git("checkout first_branch")
        with open(os.path.join(target_directory, "hello.txt"), "w") as f:
            f.write("Hello there!")
        git("commit -am second_commit_message")
        second_commit = hash_for("HEAD")
        assert original_commit != second_commit and first_commit != second_commit

        # Rebase just second_branch. This should update third_branch but shouldn't touch
        # sibling_branch.
        print "Doing second rebase"
        git("checkout second_branch")
        rebase_children(True)
        assert second_commit == hash_for("first_branch")
        assert second_commit == hash_for("second_branch")
        assert second_commit == hash_for("third_branch")
        assert first_commit == hash_for("sibling_branch")

        print "Make a merge conflict in sibling_branch"
        # Add a new commit to the sibling branch, delete the branch, and re-create it at the
        # revision it was at.
        git("checkout sibling_branch")
        # Make a conflicting change on sibling so that we can test rebasing it later.
        with open(os.path.join(target_directory, "hello.txt"), "w") as f:
            f.write("Hello conflict")
        git("commit -am conflicting_change_message")
        sibling_conflicting_commit = hash_for('HEAD')

        print "Test deleting branch"
        # See that removing fails since it's not merged
        _assert_fails(lambda: remove_branch(force_remove=False))
        assert get_current_branch() == 'sibling_branch'
        assert sibling_conflicting_commit == hash_for('HEAD')
        remove_branch(force_remove=True)
        assert get_current_branch() == 'first_branch'
        assert second_commit == hash_for('HEAD')

        print "Test creating branch at specific revision"
        make_child_branch('sibling_branch', sibling_conflicting_commit)
        assert get_current_branch() == 'sibling_branch'
        assert sibling_conflicting_commit == hash_for('HEAD')

        # This should throw since the rebase has conflicts
        print "Testing merge conflicts"
        _assert_fails(lambda: rebase_children(True))

        # Abort the rebase and try again
        git("rebase --abort")
        # It should fail for the same reason
        print "Testing merge conflicts again"
        _assert_fails(lambda: rebase_children(True))

        print "Resolving the merge conflict"
        with open(os.path.join(target_directory, "hello.txt"), "w") as f:
            f.write("Hello merge")
        git("add hello.txt")
        git("rebase --continue")

        # This should effectively no-op
        print "Doing no-op rebase"
        current_commit = hash_for("HEAD")
        rebase_children(True)
        assert current_commit == hash_for("HEAD")

        assert get_branch_structure_string(False) == UNARCHIVED_PRINT_STRUCTURE

        set_archived(True, "second_branch")
        assert get_branch_structure_string(False) == ARCHIVED_PRINT_STRUCTURE
        assert get_branch_structure_string(True) == UNARCHIVED_PRINT_STRUCTURE

        git("checkout second_branch")
        set_archived(False)
        assert get_branch_structure_string(False) == UNARCHIVED_PRINT_STRUCTURE
Exemple #27
0
def _integration_test(target_directory):
    # type: (Text) -> None
    with _run_test(target_directory):
        _initialize_repo()
        original_commit = hash_for("HEAD")

        # Create all the branches
        make_child_branch("first_branch")
        assert get_current_branch() == "first_branch"

        make_child_branch("second_branch")
        assert get_current_branch() == "second_branch"

        make_child_branch("third_branch")
        assert get_current_branch() == "third_branch"

        # Rename a branch
        rename_current_branch("third_branch_renamed", force=False)
        assert get_current_branch() == "third_branch_renamed"

        # Rename it back
        rename_current_branch("third_branch", force=False)
        assert get_current_branch() == "third_branch"

        # This should be sibling to second_branch, on top of first_branch
        git("checkout first_branch")
        make_child_branch("sibling_branch")
        assert get_current_branch() == "sibling_branch"

        # Make the first "real" commit, in master
        print("First commit")
        git("checkout master")
        with open(os.path.join(target_directory, "hello.txt"), "wb") as f:
            f.write(b"Hello!")
        # Avoiding spaces because of how we break up args in the `git` function
        git("commit -am first_commit_message")
        first_commit = hash_for("HEAD")
        assert original_commit != first_commit

        # Do the recursive rebase
        print("Rebase first_branch and its children on top of master.")
        git("checkout first_branch")
        rebase_children(True)
        assert first_commit == hash_for("first_branch")
        assert first_commit == hash_for("second_branch")
        assert first_commit == hash_for("third_branch")
        assert first_commit == hash_for("sibling_branch")

        # Make a second commit, this time in first_branch
        print("Make a second commit in first_branch")
        git("checkout first_branch")
        with open(os.path.join(target_directory, "hello.txt"), "wb") as f:
            f.write(b"Hello there!")
        git("commit -am second_commit_message")
        second_commit = hash_for("HEAD")
        assert original_commit != second_commit and first_commit != second_commit

        # Rebase just second_branch. This should update third_branch but shouldn't touch
        # sibling_branch.
        print("Doing second rebase")
        git("checkout second_branch")
        rebase_children(True)
        assert second_commit == hash_for("first_branch")
        assert second_commit == hash_for("second_branch")
        assert second_commit == hash_for("third_branch")
        assert first_commit == hash_for("sibling_branch")

        print("Make a merge conflict in sibling_branch")
        # Add a new commit to the sibling branch, delete the branch, and re-create it at the
        # revision it was at.
        git("checkout sibling_branch")
        # Make a conflicting change on sibling so that we can test rebasing it later.
        with open(os.path.join(target_directory, "hello.txt"), "wb") as f:
            f.write(b"Hello conflict")
        git("commit -am conflicting_change_message")
        sibling_conflicting_commit = hash_for("HEAD")

        print("Test deleting branch")
        # See that removing fails since it's not merged
        _assert_fails(lambda: _command_with_args(GitRemoveLeafBranch, []))
        assert get_current_branch() == "sibling_branch"
        assert sibling_conflicting_commit == hash_for("HEAD")
        _command_with_args(GitRemoveLeafBranch, ["--force"])
        assert get_current_branch() == "first_branch"
        assert second_commit == hash_for("HEAD")

        print("Test creating branch at specific revision")
        make_child_branch("sibling_branch", sibling_conflicting_commit)
        assert get_current_branch() == "sibling_branch"
        assert sibling_conflicting_commit == hash_for("HEAD")

        # This should throw since the rebase has conflicts
        print("Testing merge conflicts")
        _assert_fails(lambda: rebase_children(True))

        # Abort the rebase and try again
        git("rebase --abort")
        # It should fail for the same reason
        print("Testing merge conflicts again")
        _assert_fails(lambda: rebase_children(True))

        print("Resolving the merge conflict")
        with open(os.path.join(target_directory, "hello.txt"), "wb") as f:
            f.write(b"Hello merge")
        git("add hello.txt")
        git("rebase --continue")

        # This should effectively no-op
        print("Doing no-op rebase")
        current_commit = hash_for("HEAD")
        rebase_children(True)
        assert current_commit == hash_for("HEAD")

        unarchived_print_structure_sibling_branch = UNARCHIVED_PRINT_STRUCTURE.replace(
            "sibling_branch", make_green("sibling_branch")
        )
        archived_print_structure_sibling_branch = ARCHIVED_PRINT_STRUCTURE.replace(
            "sibling_branch", make_green("sibling_branch")
        )
        assert get_branch_structure_string(False) == unarchived_print_structure_sibling_branch

        set_archived(True, "second_branch")
        assert get_branch_structure_string(False) == archived_print_structure_sibling_branch
        assert get_branch_structure_string(True) == unarchived_print_structure_sibling_branch

        git("checkout second_branch")
        set_archived(False)
        assert get_branch_structure_string(False) == UNARCHIVED_PRINT_STRUCTURE.replace(
            "second_branch", make_green("second_branch")
        )

        current_commit = hash_for("HEAD")
        assert get_branch_info(
            branch=None, use_null_delimiter=False
        ) == "Parent branch: first_branch; Base revision: {}".format(current_commit)
        assert get_branch_info(branch=None, use_null_delimiter=True) == "first_branch\0{}".format(current_commit)
        try:
            get_branch_info(branch="master", use_null_delimiter=False)
            assert False, "Should not get here"
        except SystemExit as e:
            assert str(e) == "Branch does not have a parent: master"
def _unit_tests(target_directory):
    # type: (str) -> None
    target_directory = os.path.expanduser(target_directory)
    target_container = os.path.dirname(target_directory)
    assert not os.path.exists(target_directory)
    assert os.path.isdir(target_container)

    with run_test(target_directory):
        # Initialize a repo and add a first commit so we can tell what branch we're on.
        print "Initializing repo"
        git("init")
        open("hello.txt", "w").close()
        git("add .")
        git("commit -am initial_commit")

        assert get_current_branch() == "master"
        original_commit = hash_for("HEAD")

        # Create all the branches
        make_child_branch("first_branch")
        assert get_current_branch() == "first_branch"

        make_child_branch("second_branch")
        assert get_current_branch() == "second_branch"

        make_child_branch("third_branch")
        assert get_current_branch() == "third_branch"

        # Rename a branch
        rename_current_branch("third_branch_renamed")
        assert get_current_branch() == "third_branch_renamed"

        # Rename it back
        rename_current_branch("third_branch")
        assert get_current_branch() == "third_branch"

        # This should be sibling to second_branch, on top of first_branch
        git("checkout first_branch")
        make_child_branch("sibling_branch")
        assert get_current_branch() == "sibling_branch"

        # Make the first "real" commit, in master
        print "First commit"
        git("checkout master")
        with open(os.path.join(target_directory, "hello.txt"), "w") as f:
            f.write("Hello!")
        # Avoiding spaces because of how we break up args in the `git` function
        git("commit -am first_commit_message")
        first_commit = hash_for("HEAD")
        assert original_commit != first_commit

        # Do the recursive rebase
        print "Rebase first_branch and its children on top of master."
        git("checkout first_branch")
        rebase_children(True)
        assert first_commit == hash_for("first_branch")
        assert first_commit == hash_for("second_branch")
        assert first_commit == hash_for("third_branch")
        assert first_commit == hash_for("sibling_branch")

        # Make a second commit, this time in first_branch
        print "Make a second commit in first_branch"
        git("checkout first_branch")
        with open(os.path.join(target_directory, "hello.txt"), "w") as f:
            f.write("Hello there!")
        git("commit -am second_commit_message")
        second_commit = hash_for("HEAD")
        assert original_commit != second_commit and first_commit != second_commit

        # Rebase just second_branch. This should update third_branch but shouldn't touch sibling_branch.
        print "Doing second rebase"
        git("checkout second_branch")
        rebase_children(True)
        assert second_commit == hash_for("first_branch")
        assert second_commit == hash_for("second_branch")
        assert second_commit == hash_for("third_branch")
        assert first_commit == hash_for("sibling_branch")

        print "Make a merge conflict in sibling_branch"
        git("checkout sibling_branch")
        with open(os.path.join(target_directory, "hello.txt"), "w") as f:
            f.write("Hello conflict")
        git("commit -am conflicting_change_message")

        # This should throw since the rebase has conflicts
        print "Testing merge conflicts"
        _assert_fails(lambda: rebase_children(True))

        # Abort the rebase and try again
        git("rebase --abort")
        # It should fail for the same reason
        print "Testing merge conflicts again"
        _assert_fails(lambda: rebase_children(True))

        print "Resolving the merge conflict"
        with open(os.path.join(target_directory, "hello.txt"), "w") as f:
            f.write("Hello merge")
        git("add hello.txt")
        git("rebase --continue")

        # This should effectively no-op
        print "Doing no-op rebase"
        current_commit = hash_for("HEAD")
        rebase_children(True)
        assert current_commit == hash_for("HEAD")

        assert get_branch_structure_string(False) == UNARCHIVED_PRINT_STRUCTURE

        set_archived(True, "second_branch")
        assert get_branch_structure_string(False) == ARCHIVED_PRINT_STRUCTURE
        assert get_branch_structure_string(True) == UNARCHIVED_PRINT_STRUCTURE

        git("checkout second_branch")
        set_archived(False)
        assert get_branch_structure_string(False) == UNARCHIVED_PRINT_STRUCTURE
 def run_command(self, args):
     # type: (Namespace) -> None
     with get_branch_tracker() as tracker:
         remove_branch(tracker,
                       branch_name=args.branch_name or get_current_branch(),
                       force_remove=args.force)