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)
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)
예제 #3
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 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 do_rebase(tracker, parent, child):
    # type: (BranchTracker, str, str) -> None
    bases = tracker.bases_for_branch(child)

    if len(bases) == 2:
        first_base, second_base = bases
        has_first_base = does_branch_contain_commit(child, first_base)
        has_second_base = does_branch_contain_commit(child, second_base)
        # Should have at least one of the two bases
        assert has_first_base or has_second_base
        if has_first_base and has_second_base:
            # Choose the newer one. The older one will be the merge base of the two
            older_base = git("merge-base %s %s" % (first_base, second_base))
            first_is_newer = older_base == second_base
            base = first_base if first_is_newer else second_base
        else:
            # Only has one, choose the one that it does have
            base = bases[0] if has_first_base else bases[1]
        tracker.finish_rebase(child, base)
    else:
        base = bases[0]

    parent_rev = hash_for(parent)

    tracker.start_rebase(child, parent_rev)
    git("rebase --onto %s %s %s" % (parent, base, child))
    tracker.finish_rebase(child, parent_rev)
def do_rebase(tracker, parent, child):
    # type: (BranchTracker, str, str) -> None
    bases = tracker.bases_for_branch(child)

    if len(bases) == 2:
        first_base, second_base = bases
        has_first_base = does_branch_contain_commit(child, first_base)
        has_second_base = does_branch_contain_commit(child, second_base)
        # Should have at least one of the two bases
        assert has_first_base or has_second_base
        if has_first_base and has_second_base:
            # Choose the newer one. The older one will be the merge base of the two
            older_base = git("merge-base {} {}".format(first_base,
                                                       second_base))
            first_is_newer = older_base == second_base
            base = first_base if first_is_newer else second_base
        else:
            # Only has one, choose the one that it does have
            base = bases[0] if has_first_base else bases[1]
        tracker.finish_rebase(child, base)
    else:
        base = bases[0]

    parent_rev = hash_for(parent)

    tracker.start_rebase(child, parent_rev)
    git("rebase --onto {} {} {}".format(parent, base, child))
    tracker.finish_rebase(child, parent_rev)
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 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)
예제 #9
0
def do_rebase(tracker, parent, child):
    # type: (BranchTracker, Text, Text) -> None
    base = tracker.base_for_branch(child)
    parent_rev = hash_for(parent)
    if base == parent_rev:
        return

    tracker.start_rebase(child, parent_rev)
    git("rebase --onto {} {} {}".format(parent, base, child))
    tracker.finish_rebase(child, parent_rev)
예제 #10
0
def clean_invalid_branches(dry_run, archive, upstream):
    # type: (bool, bool, bool) -> None
    if upstream:
        # Make sure we have latest remote info
        git("fetch --prune")

    with get_branch_tracker() as tracker:
        for branch in tracker.linearized_branches():
            if _is_branch_invalid(tracker, branch, upstream):
                if archive:
                    _archive_invalid_branch(dry_run, tracker, branch)
                else:
                    _delete_invalid_branch_if_possible(dry_run, tracker,
                                                       branch, upstream)
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)
예제 #12
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")
예제 #13
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 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)
예제 #17
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"
예제 #18
0
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
예제 #19
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"
예제 #20
0
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