예제 #1
0
def garbage_collect():
    """Removes temporary change branches which are fully merged."""
    current_branch = git.get_current_branch()
    if current_branch.startswith('change-I'):
        exit_error('`git-change gc` cannot be run from a change branch.')

    unmerged_branches = []
    deleted = False
    for branch in get_change_branches():
        try:
            # Note: git branch -d prints 'Deleted branch ...' to stdout.
            git.run_command('git branch -d %s' % branch, trap_stderr=True, output_on_error=False)
        except git.CalledProcessError:
            unmerged_branches.append(branch)
        else:
            deleted = True

    if unmerged_branches:
        if deleted:
            print  # Blank line between deleted branches and the message below.
        print ('The following change branches could not be deleted, probably because they\n'
               'are not fully merged into the current branch. You might try first running\n'
               'git-pull or git-change rebase in order to sync with remote.\n')
        for branch in unmerged_branches:
            print branch
예제 #2
0
def rebase():
    """Rebases the target and temporary change branches.

    Rebases the target branch (the branch from which the temporary
    change branch was created) and then rebases the temporary change
    branch. This can be used to pull upstream changes down to both
    branches to resolve a failed Gerrit submission due to a path
    conflict.

    If there are conflicts with either rebase operation, the process
    terminates and it is up to the user to resolve the conflicts.
    """
    check_for_change_branch()
    target_branch = get_target_branch()
    change_branch = git.get_current_branch()

    git.run_command_or_die('git checkout %s' % target_branch)
    try:
        git.run_command('git pull --rebase', output_on_error=False)
    except git.CalledProcessError, e:
        print ('Rebase failed for branch %s. After resolving merge failure(s),\n'
               'check out the change branch (%s) and run "git change rebase" again.\n'
               'See "git help rebase" for help on resolving merge conflicts.' %
               (target_branch, change_branch))
        sys.exit(e.returncode)
예제 #3
0
def update_change():
    """Updates an existing change with Gerrit.

    Runs a git push command to update an existing change. The change
    ID is taken from the current branch, which should be a temporary
    change branch created by a previous run of git-change.
    """
    if FLAGS.message is not None:
        exit_error('--message cannot be used with the update subcommand.')

    change_id = check_for_change_branch()
    change = get_change(change_id)
    if not change['open']:
        exit_error('Change %s is no longer open.' % change_id)

    # Amend the HEAD commit if there are staged changes or if at least
    # one of the --reviewers, --cc or --bug flags was passed. Amending
    # the HEAD commit changes its SHA1 hash, signaling to Gerrit that
    # we have a new patch set.
    if (FLAGS.reviewers or FLAGS.cc or FLAGS.bug is not None or
        git.run_command('git diff --cached --name-status', trap_stdout=True)):
        commit_change(['--amend'])

    command = build_push_command(change['branch'])
    try:
        git.run_command(command)
    except git.CalledProcessError, e:
        # Run command prints an error message prior to raising.
        sys.exit(e.returncode)
예제 #4
0
def rebase():
    """Rebases the target and temporary change branches.

    Rebases the target branch (the branch from which the temporary
    change branch was created) and then rebases the temporary change
    branch. This can be used to pull upstream changes down to both
    branches to resolve a failed Gerrit submission due to a path
    conflict.

    If there are conflicts with either rebase operation, the process
    terminates and it is up to the user to resolve the conflicts.
    """
    change_id = check_for_change_branch()
    change = get_change(change_id)
    target_branch = change["branch"]
    change_branch = git.get_branch()

    git.run_command_or_die("git checkout %s" % target_branch)
    try:
        git.run_command("git pull --rebase", output_on_error=False)
    except git.CalledProcessError, e:
        print (
            "Rebase failed for branch %s. After resolving merge failure(s),\n"
            'check out the change branch (%s) and run "git change rebase" again.\n'
            'See "git help rebase" for help on resolving merge conflicts.' % (target_branch, change_branch)
        )
        sys.exit(e.returncode)
예제 #5
0
def update_change():
    """Updates an existing change with Gerrit.

    Runs a git push command to update an existing change. The change
    ID is taken from the current branch, which should be a temporary
    change branch created by a previous run of git-change.
    """
    change_id = check_for_change_branch()
    change = get_change(change_id)
    if not change["open"]:
        exit_error("Change %s is no longer open." % change_id)

    # Amend the HEAD commit if there are staged changes or if at least
    # one of the --reviewers, --cc or --bug flags was passed. Amending
    # the HEAD commit changes its SHA1 hash, signaling to Gerrit that
    # we have a new patch set.
    if (
        FLAGS.reviewers
        or FLAGS.cc
        or FLAGS.bug is not None
        or git.run_command("git diff --cached --name-status", trap_stdout=True)
    ):

        commit_change(["--amend"])

    command = build_push_command(change["branch"], draft=change["status"] == "DRAFT")
    try:
        git.run_command(command)
    except git.CalledProcessError, e:
        # Run command prints an error message prior to raising.
        sys.exit(e.returncode)
예제 #6
0
def check_for_pending_changes():
    """Checks the working tree and index for changed files.

    If there are any uncommitted changes, exits with an
    error. Untracked files are okay.
    """
    output = git.run_command("git status --porcelain --untracked-files=no", trap_stdout=True)
    if output:
        git.run_command("git status")
        exit_error("You have uncommitted changes in your working tree/index. " "Please stash them and try again.")
예제 #7
0
def check_for_pending_changes():
    """Checks the working tree and index for changed files.

    If there are any uncommitted changes, exits with an
    error. Untracked files are okay.
    """
    output = git.run_command('git status --porcelain --untracked-files=no', trap_stdout=True)
    if output:
        git.run_command('git status')
        exit_error('You have uncommitted changes in your working tree/index. '
                   'Please stash them and try again.')
예제 #8
0
def list_change_branches():
    """Lists all temporary change branches.

    Lists the branches and prompts user with a menu to check one of
    them out.
    """
    branches = get_change_branches()
    if not branches:
        print 'You have no change branches to list'
        return

    not_merged_branches = git.run_command('git branch --no-merged',
                                          trap_stdout=True).strip().split('\n')
    not_merged_branches = [line.strip()[BRANCH_SHORT_LENGTH:] for line in not_merged_branches]

    print 'Change branches:\n'
    i = 0
    for branch in branches:
        i += 1

        output = git.run_command('git log --oneline -1 %s --' % branch, trap_stdout=True)
        change_id = output.split(' ')[0]
        description = ' '.join(output.split(' ')[1:])
        short_branch = branch[0:16]
        change_branch = branch.split('-')[1]

        # handle colors here
        use_color = git.get_config_option('git-change.color')
        use_color = (use_color != 'false')  # auto or yes or anything else count as True

        cid_url = git.get_config_option('git-change.cid-url') or ''

        if use_color and change_branch not in not_merged_branches:  # not not == is merged
            sys.stdout.write(COLOR_OBSOLETE)

        if use_color and change_branch == get_change_id_from_branch():
            sys.stdout.write(COLOR_CURRENT)
        sys.stdout.write('{i:>2}: {branch_id} {href}{cid} {name}'.format(
            i=i, branch_id=short_branch, href=cid_url, cid=change_id, name=description))
        if use_color:
            sys.stdout.write(COLOR_CLEAR)
    try:
        selection = raw_input('\nSelect a branch number to check out, '
                              'or hit enter to exit: ')
    except (EOFError, KeyboardInterrupt):
        # User pressed or Ctrl-D or Ctrl-C.
        return
    if selection.isdigit() and int(selection) <= len(branches):
        git.run_command_or_die('git checkout %s' % branches[int(selection) - 1])
    elif selection:
        print 'Not a valid selection'
    else:
        pass  # User hit enter; just exit.
예제 #9
0
def commit_staged_changes(original_branch, tmp_branch):
    """Commits staged changes.

    A change ID will be generated by the commit-msg hook as a
    side-effect.

    If the git-commit command fails or is interrupted by the user
    (e.g., with Control-C) the original branch is restored and the
    process exits with a non-zero status.

    Args:
        original_branch: A string representing the name of the branch
            the user started with. Used for rolling back on error.
        tmp_branch: A string representing the name of the temporary
            change branch. Used for rolling back on error.
    """
    try:
        commit_change()
    except KeyboardInterrupt:
        # The user bailed with Control-C.
        git.run_command('git checkout %s' % original_branch)
        git.run_command('git branch -d %s' % tmp_branch)
        sys.exit(1)
    except git.CalledProcessError, e:
        # git-commit returned non-zero status. Maybe the user provided
        # an empty commit message.
        git.run_command('git checkout %s' % original_branch)
        git.run_command('git branch -d %s' % tmp_branch)
        sys.exit(e.returncode)
예제 #10
0
def commit_staged_changes(original_branch, tmp_branch):
    """Commits staged changes.

    A change ID will be generated by the commit-msg hook as a
    side-effect. This function exits the process on errors.

    Args:
        original_branch: A string representing the name of the branch
            the user started with. Used for rolling back on error.
        tmp_branch: A string representing the name of the temporary
            change branch. Used for rolling back on error.
    """
    try:
        commit_change()
    except KeyboardInterrupt:
        # The user bailed with Control-C.
        git.run_command("git checkout %s" % original_branch)
        git.run_command("git branch -d %s" % tmp_branch)
        sys.exit(1)
    except git.CalledProcessError, e:
        # git-commit returned non-zero status. Maybe the user provided
        # an empty commit message.
        git.run_command("git checkout %s" % original_branch)
        git.run_command("git branch -d %s" % tmp_branch)
        sys.exit(e.returncode)
예제 #11
0
def check_unmerged_commits(branch):
    """Checks whether the given branch has unmerged commits.

    Specifically, checks whether the given branch has unmerged commits
    relative to its remote branch. For example, assuming the branch is
    'master' and the remote is 'origin', checks whether 'master' has
    commits that have not been merged into 'origin/master'.

    Args:
        branch: A string representing the branch to check.

    Returns:
        True if the given branch has commits not yet merged in its
        remote branch. False if not, or if the user elected to proceed
        anyway.
    """
    output = git.run_command("git log --oneline %s ^%s/%s --" % (branch, FLAGS.remote, branch), trap_stdout=True)
    if not output or FLAGS["dry-run"].value:
        return False

    print "Your branch %s is ahead of its remote by the following %s commit(s):\n" % (branch, len(output.split("\n")))
    sys.stdout.write(output)
    user_input = raw_input(
        "\nIf we continue, each of the commits above may result in a new code\n"
        "review and a submit dependency in Gerrit. You might try syncing the\n"
        "remote branch by passing the --fetch flag.\n"
        "Continue? "
    )
    if user_input.lower().startswith("y"):
        return False

    print "\nAborted"
    return True
예제 #12
0
def list_change_branches():
    """Lists all temporary change branches.

    Lists the branches and prompts user with a menu to check one of
    them out.
    """
    branches = get_change_branches()
    if not branches:
        print "You have no change branches to list"
        return
    print "Change branches:\n"
    i = 0
    for branch in branches:
        i += 1
        output = git.run_command("git log --oneline -1 %s --" % branch, trap_stdout=True)
        sys.stdout.write("{0:>2}. {1} {2}".format(i, branch, output))
    try:
        selection = raw_input("\nSelect a branch number to check out, " "or hit enter to exit: ")
    except (EOFError, KeyboardInterrupt):
        # User pressed or Ctrl-D or Ctrl-C.
        return
    if selection.isdigit() and int(selection) <= len(branches):
        git.run_command_or_die("git checkout %s" % branches[int(selection) - 1])
    elif selection:
        print "Not a valid selection"
    else:
        pass  # User hit enter; just exit.
예제 #13
0
def download_review(review):
    """Download existing review from Gerrit.
    
    Almost literally copied from git-review.
    """
    hostname, _, port = FLAGS["gerrit-ssh-host"].value.partition(":")

    if port is not None:
        port = "-p %s" % port
    else:
        port = ""

    review_info = None
    try:
        (output, errput) = git.run_command(
            " ".join(
                ["ssh", "-x", port, hostname, "gerrit", "query", "--format=JSON --current-patch-set change:%s" % review]
            ),
            trap_stderr=True,
            trap_stdout=True,
        )
    except git.CalledProcessError, e:
        print ("Could not fetch review information from gerrit")
        print e.stderr
        sys.exit(1)
예제 #14
0
def submit_change():
    """Submits the existing change to Gerrit."""
    change_id = check_for_change_branch()
    change = get_change(change_id)
    if not change['open']:
        exit_error('Change %s is no longer open.' % change_id)

    commit = git.run_command('git rev-parse --verify HEAD', trap_stdout=True)
    project = change['project']
    git.run_command_or_die('ssh %s gerrit review --project %s --submit %s' %
                           (FLAGS['gerrit-ssh-host'].value, project, commit))
예제 #15
0
def check_unmerged_commits(branch):
    """Checks whether the given branch has unmerged commits.

    Specifically, checks whether the given branch has unmerged commits
    relative to its remote branch. For example, assuming the branch is
    'master' and the remote is 'origin', checks whether 'master' has
    commits that have not been merged into 'origin/master'.

    Args:
        branch: A string representing the branch to check.

    Returns:
        True if the given branch has commits not yet merged in its
        remote branch. False if not, or if the user elected to proceed
        anyway.
    """
    # Gather output of git log.
    output = git.run_command('git log --oneline %s ^%s/%s --' % (
        branch, FLAGS.remote, branch), trap_stdout=True).strip()

    # Count how many commits ahead we are vs. origin:
    commits_ahead = int(git.run_command('git rev-list %s ^%s/%s --count' % (
        branch, FLAGS.remote, branch), trap_stdout=True).strip())

    if not output or FLAGS['dry-run'].value:
        return False

    print 'Your branch %s is ahead of its remote by the following %i commit%s:\n' % (
        branch, commits_ahead, 's' if commits_ahead > 1 else '')

    sys.stdout.write(output)
    user_input = raw_input(
        '\n\nIf we continue, each of the commits above may result in a new code\n'
        'review and a submit dependency in Gerrit. You might try syncing the\n'
        'remote branch by passing the --fetch flag.\n'
        'Continue? ')
    if user_input.lower().startswith('y'):
        return False

    print '\nAborted'
    return True
예제 #16
0
def submit_change():
    """Submits the existing change to Gerrit."""
    change_id = check_for_change_branch()
    change = get_change(change_id)
    if not change["open"]:
        exit_error("Change %s is no longer open." % change_id)

    commit = git.run_command("git rev-parse --verify HEAD", trap_stdout=True)
    project = change["project"]
    ssh_host, _, ssh_port = FLAGS["gerrit-ssh-host"].value.partition(":")
    ssh_port = (ssh_port and "-p %s " % ssh_port) or ""
    git.run_command_or_die("ssh %s%s gerrit review --project %s --submit %s" % (ssh_port, ssh_host, project, commit))
예제 #17
0
def get_directories_with_changes():
    """Gets the absolute paths to the parent directories of changed files.

    Returns:
        A list of strings representing the absolute paths to directories of
        files changed in the last commit.
    """
    # Get a list of changed files in the HEAD commit.
    changed_files = git.run_command(
        'git diff --name-only HEAD^ HEAD', trap_stdout=True).split('\n')[:-1]

    # Return absolute paths to the parent dirs of changed files.
    repo_root = _get_repo_root()
    dir_paths = [os.path.dirname(os.path.join(repo_root, path)) for path in changed_files]
    return list(set(dir_paths))
예제 #18
0
def get_change_branches():
    """Returns temporary change branches.

    Temporary change branch names match the pattern 'change-*'.

    Returns:
        A sequence of strings each representing a branch names, sorted
        in chronological order based on the author date of each
        branch's HEAD commit.
    """
    output = git.run_command(
        'git for-each-ref --format="%(refname:short)" --sort=authordate refs/heads/change-*', trap_stdout=True
    )
    if output:
        return output.strip().split("\n")
    else:
        return []
예제 #19
0
def get_change_branches():
    """Returns temporary change branches.

    Temporary change branch names match the pattern 'change-*'.

    Returns:
        A sequence of strings each representing a branch names, sorted
        in chronological order based on the author date of each
        branch's HEAD commit.
    """
    output = git.run_command(
        'git for-each-ref --format="%(refname:short)" --sort=authordate refs/heads/change-*',
        trap_stdout=True)
    if output:
        return output.strip().split('\n')
    else:
        return []
예제 #20
0
def get_change_id_from_head():
    """Returns the change ID of the last commit.

    If the change ID is available as the value of the Change-Id
    attribute in the HEAD commit message, that value is returned. If
    there is no Change-Id attribute, returns None.

    Returns:
        A string representing the HEAD commit's change ID if it is
        available, or None if not.
    """
    output = git.run_command("git cat-file -p HEAD", trap_stdout=True)
    lines = output.split("\n")
    for line in lines:
        if line.startswith("Change-Id:"):
            _, change_id = line.split(":")
            return change_id.strip()
    return None
예제 #21
0
    def check_grammar(self, filename):
        # https://stackoverflow.com/questions/4284313/how-can-i-check-the-syntax-of-python-script-without-executing-it
        # some file names may contain space

        python_cmd_list = [self.python_path, '-m', 'py_compile', filename]

        msgo, msge = git.run_command(python_cmd_list)

        # as run_command() is happening within a separated subprocess, try except may not catch the exception from the file
        if 'FileNotFoundError' in msge:
            print('check_grammar() : unable to find file =', filename)
            print('check_grammar() : os.getcwd() =', os.getcwd())
            print('check_grammar() : python_cmd =', python_cmd_list)

        result = {'grammar pass': not (msgo or msge)}

        # free memory after use
        del msgo, msge

        return result
예제 #22
0
    def run_script_input(self, python_cmd):

        # adaptive input string based on list of available files
        # to prevent excessive file not found error
        input_list = list(str(i) for i in range(10))

        # frequent filename
        if 'test.txt' in os.listdir():
            input_list.insert(0, 'test.txt')
        elif 'ex15_sample.txt' in os.listdir():
            input_list.insert(0, 'ex15_sample.txt')

        input_txt = '\n'.join(input_list)

        # subprocess.Popen() needs bytes as input
        msgo, msge = git.run_command(
            python_cmd,
            in_txt=input_txt,
            b_verbose=False,
        )
        return msgo, msge
예제 #23
0
def sanity_check_merge_commit():
    """Checks whether the HEAD commit looks like a merge.

    If the HEAD commit does not look like a merge, prompts the user to
    see if we should continue, and exits if not.
    """
    num_parents = 0
    merge_message_seen = False
    output = git.run_command("git cat-file -p HEAD", trap_stdout=True)
    lines = output.split("\n")
    for line in lines:
        if line.startswith("parent "):
            num_parents += 1
        elif line.startswith("Merge branch "):
            merge_message_seen = True
    if num_parents < 2 or not merge_message_seen:
        user_input = raw_input("The HEAD commit does not look like a merge. Continue? ")
        if user_input.lower().startswith("y"):
            return
        else:
            print "Aborted"
            sys.exit(1)
예제 #24
0
def get_change_id_from_commit(commit):
    """Returns the change ID of the given commit.

    If the change ID is available as the value of the Change-Id header
    in the given commit's message, that value is returned. If there is
    no Change-Id header, returns None.

    Args:
        commit: A string representing the commit from which to read
            the change ID.

    Returns:
        A string representing the given commit's change ID if it is
        available, or None if not.
    """
    output = git.run_command('git cat-file -p %s' % commit, trap_stdout=True)
    lines = output.split('\n')
    for line in lines:
        if line.startswith('Change-Id:'):
            _, change_id = line.split(':')
            return change_id.strip()
    return None
예제 #25
0
def sanity_check_merge_commit():
    """Checks whether the HEAD commit looks like a merge.

    If the HEAD commit does not look like a merge, prompts the user to
    see if we should continue, and exits if not.
    """
    num_parents = 0
    merge_message_seen = False
    output = git.run_command('git cat-file -p HEAD', trap_stdout=True)
    lines = output.split('\n')
    for line in lines:
        if line.startswith('parent '):
            num_parents += 1
        elif line.startswith('Merge branch '):
            merge_message_seen = True
    if num_parents < 2 or not merge_message_seen:
        user_input = raw_input('The HEAD commit does not look like a merge. Continue? ')
        if user_input.lower().startswith('y'):
            return
        else:
            print 'Aborted'
            sys.exit(1)
예제 #26
0
def _get_repo_root():
    """Returns the absolute path to the root of the git repo."""
    return git.run_command('git rev-parse --show-toplevel', trap_stdout=True).strip()
예제 #27
0
def create_change():
    """Creates a Gerrit code review change."""
    if not FLAGS["use-head-commit"].value:
        if not git.run_command("git diff --cached --name-status", trap_stdout=True):
            exit_error(
                "You have no staged changes; exiting.\n" "(You may want to specify --use-head-commit.)", prefix=""
            )

    if FLAGS["merge-commit"].value:
        check_for_pending_changes()
        sanity_check_merge_commit()

    original_branch, target_branch = determine_branches()

    # Fetch from origin so that we can see how many commits ahead our
    # local branch is.
    if FLAGS.fetch:
        git.run_command("git fetch %s" % FLAGS.remote)

    # Make sure the original branch does not have any unmerged
    # commits relative to its remote. This check only makes sense if
    # original_branch is a tracking branch (i.e. if --chain is false).
    # The check is skipped in the case of a merge commit change, which
    # will likely have many (expected) unmerged commits.
    if not FLAGS.chain and not FLAGS["merge-commit"].value:
        if check_unmerged_commits(original_branch):
            sys.exit(1)

    # Create and switch to a temporary branch. Once we have a change
    # ID, it will be renamed to include the ID.
    tmp_branch = "tmp-change-%s" % time.time()
    git.run_command("git checkout -b %s" % tmp_branch, trap_stdout=True)

    if not FLAGS["use-head-commit"].value:
        commit_staged_changes(original_branch, tmp_branch)

    # Now rename the branch according to the change ID.
    change_id = get_change_id_from_head()
    if FLAGS["use-head-commit"].value and change_id is None:
        # Amend the HEAD commit in order to force running the
        # commit-msg hook, which should insert a Change-Id header.
        commit_change(["--amend"])
        change_id = get_change_id_from_head()
    if change_id is None:
        print (
            "\nWARNING: Reading change ID from the HEAD commit failed. (You may need to\n"
            "install the Gerrit commit-msg hook.) Before continuing, you need to add\n"
            "the change ID header to the HEAD commit message (git commit --amend) and\n"
            "rename the branch %s to change-<change-ID> manaully." % tmp_branch
        )
        new_branch = tmp_branch
    else:
        new_branch = "change-%s" % change_id
        git.run_command("git branch -m %s %s" % (tmp_branch, new_branch))
    print "\nCreated branch: %s\n" % new_branch

    command = build_push_command(target_branch, draft=FLAGS.draft)
    try:
        git.run_command(command)
    except git.CalledProcessError, e:
        # Roll back the commit and remove the change branch.
        git.run_command("git reset --soft HEAD^")
        git.run_command("git checkout %s" % original_branch)
        git.run_command("git branch -d %s" % new_branch)
        sys.exit(e.returncode)
예제 #28
0
        git.run_command(command)
    except git.CalledProcessError, e:
        # Roll back the commit and remove the change branch.
        git.run_command('git reset --soft HEAD^')
        git.run_command('git checkout %s' % original_branch)
        git.run_command('git branch -d %s' % new_branch)
        sys.exit(e.returncode)

    if FLAGS['merge-commit'].value:
        # Remove the merge commit from the original branch to avoid
        # duplicating the commit in case the version of that commit in
        # the change branch is amended (i.e., its SHA1 hash changed).
        # The call to check_for_pending_changes above ensures that the
        # working tree and index are clean and thus 'git reset --hard'
        # is safe to run.
        git.run_command('git checkout %s' % original_branch)
        git.run_command('git reset --hard HEAD^')
        print 'Removed HEAD commit from branch %s' % original_branch
        if FLAGS.switch or FLAGS.chain:
            git.run_command('git checkout %s' % new_branch)
        return

    # Switch back to the original branch, but not if --chain is true
    # as the user may be want to make multiple commits in the
    # temporary change branch.
    if FLAGS.switch or FLAGS.chain:
        pass  # switch to (stay on) temporary change branch
    else:
        git.run_command_or_die('git checkout %s' % original_branch)

예제 #29
0
        git.run_command(command)
    except git.CalledProcessError, e:
        # Roll back the commit and remove the change branch.
        git.run_command("git reset --soft HEAD^")
        git.run_command("git checkout %s" % original_branch)
        git.run_command("git branch -d %s" % new_branch)
        sys.exit(e.returncode)

    if FLAGS["merge-commit"].value:
        # Remove the merge commit from the original branch to avoid
        # duplicating the commit in case the version of that commit in
        # the change branch is amended (i.e., its SHA1 hash changed).
        # The call to check_for_pending_changes above ensures that the
        # working tree and index are clean and thus 'git reset --hard'
        # is safe to run.
        git.run_command("git checkout %s" % original_branch)
        git.run_command("git reset --hard HEAD^")
        print "Removed HEAD commit from branch %s" % original_branch
        if FLAGS.switch or FLAGS.chain:
            git.run_command("git checkout %s" % new_branch)
        return

    # Switch back to the original branch, but not if --chain is true
    # as the user may be want to make multiple commits in the
    # temporary change branch.
    if FLAGS.switch or FLAGS.chain:
        pass  # switch to (stay on) temporary change branch
    else:
        git.run_command_or_die("git checkout %s" % original_branch)

예제 #30
0
def create_change():
    """Creates a Gerrit code review change."""
    if not FLAGS['use-head-commit'].value:
        if not git.run_command('git diff --cached --name-status', trap_stdout=True):
            exit_error('You have no staged changes; exiting.\n'
                       '(You may want to specify --use-head-commit.)', prefix='')

    if FLAGS['merge-commit'].value:
        check_for_pending_changes()
        sanity_check_merge_commit()

    original_branch, target_branch = determine_branches()

    # Fetch from origin so that we can see how many commits ahead our
    # local branch is.
    if FLAGS.fetch:
        git.run_command('git fetch %s' % FLAGS.remote)

    # Make sure the original branch does not have any unmerged
    # commits relative to its remote. This check only makes sense if
    # original_branch is a tracking branch (i.e. if --chain is false).
    # The check is skipped in the case of a merge commit change, which
    # will likely have many (expected) unmerged commits.
    if not FLAGS.chain and not FLAGS['merge-commit'].value:
        if check_unmerged_commits(original_branch):
            sys.exit(1)

    # Create and switch to a temporary branch. Once we have a change
    # ID, it will be renamed to include the ID.
    tmp_branch = 'tmp-change-%s' % time.time()
    git.run_command('git checkout -b %s' % tmp_branch, trap_stdout=True)

    if not FLAGS['use-head-commit'].value:
        commit_staged_changes(original_branch, tmp_branch)

    # Now rename the branch according to the change ID.
    change_id = get_change_id_from_head()
    if FLAGS['use-head-commit'].value and change_id is None:
        # Amend the HEAD commit in order to force running the
        # commit-msg hook, which should insert a Change-Id header.
        commit_change(['--amend'])
        change_id = get_change_id_from_head()
    if change_id is None:
        print ('\nWARNING: Reading change ID from the HEAD commit failed. (You may need to\n'
               'install the Gerrit commit-msg hook.) Before continuing, you need to add\n'
               'the change ID header to the HEAD commit message (git commit --amend) and\n'
               'rename the branch %s to change-<change-ID> manually.' % tmp_branch)
        new_branch = tmp_branch
    else:
        new_branch = 'change-%s' % change_id
        git.run_command('git branch -m %s %s' % (tmp_branch, new_branch))
    print '\nCreated branch: %s\n' % new_branch

    # Cache change meta-data in a note. With --chain, Parent-Branch is
    # the temporary change branch that is the base of the
    # chain. Without --chain, Parent-Branch and Taget-Branch are the
    # same.
    note = {
        'Change-Id': change_id,
        'Target-Branch': target_branch,
        'Parent-Branch': original_branch,
        }
    git.write_note(note)

    command = build_push_command(target_branch)
    try:
        git.run_command(command)
    except git.CalledProcessError, e:
        # Roll back the commit and remove the change branch.
        git.run_command('git reset --soft HEAD^')
        git.run_command('git checkout %s' % original_branch)
        git.run_command('git branch -d %s' % new_branch)
        sys.exit(e.returncode)