예제 #1
0
def GetNameForCommit(sha1):
  name = re.sub(r'~.*$', '', git.run('name-rev', '--tags', '--name-only', sha1))
  if name == 'undefined':
    name = git.run(
        'name-rev', '--refs', 'remotes/branch-heads/*', '--name-only',
        sha1) + ' [untagged]'
  return name
예제 #2
0
def remove_empty_branches(branch_tree):
    tag_set = git.tags()
    ensure_root_checkout = git.once(lambda: git.run('checkout', git.root()))

    deletions = set()
    downstreams = collections.defaultdict(list)
    for branch, parent in git.topo_iter(branch_tree, top_down=False):
        downstreams[parent].append(branch)

        if git.hash_one(branch) == git.hash_one(parent):
            ensure_root_checkout()

            logging.debug('branch %s merged to %s', branch, parent)

            for down in downstreams[branch]:
                if down in deletions:
                    continue

                if parent in tag_set:
                    git.set_branch_config(down, 'remote', '.')
                    git.set_branch_config(down, 'merge',
                                          'refs/tags/%s' % parent)
                    print('Reparented %s to track %s [tag] (was tracking %s)' %
                          (down, parent, branch))
                else:
                    git.run('branch', '--set-upstream-to', parent, down)
                    print('Reparented %s to track %s (was tracking %s)' %
                          (down, parent, branch))

            deletions.add(branch)
            print git.run('branch', '-d', branch)
예제 #3
0
def main(argv):
  assert len(argv) == 1, "No arguments expected"
  upfn = upstream
  cur = current_branch()
  if cur == 'HEAD':
    def _upfn(b):
      parent = upstream(b)
      if parent:
        return hash_one(parent)
    upfn = _upfn
    cur = hash_one(cur)
  downstreams = [b for b in branches() if upfn(b) == cur]
  if not downstreams:
    return "No downstream branches"
  elif len(downstreams) == 1:
    run('checkout', downstreams[0])
  else:
    high = len(downstreams) - 1
    print
    while True:
      print "Please select a downstream branch"
      for i, b in enumerate(downstreams):
        print "  %d. %s" % (i, b)
      r = raw_input("Selection (0-%d)[0]: " % high).strip() or '0'
      if not r.isdigit() or (0 > int(r) > high):
        print "Invalid choice."
      else:
        run('checkout', downstreams[int(r)])
        break
예제 #4
0
def remove_empty_branches(branch_tree):
    tag_set = git.tags()
    ensure_root_checkout = git.once(lambda: git.run("checkout", git.root()))

    deletions = set()
    downstreams = collections.defaultdict(list)
    for branch, parent in git.topo_iter(branch_tree, top_down=False):
        downstreams[parent].append(branch)

        if git.hash_one(branch) == git.hash_one(parent):
            ensure_root_checkout()

            logging.debug("branch %s merged to %s", branch, parent)

            for down in downstreams[branch]:
                if down in deletions:
                    continue

                if parent in tag_set:
                    git.set_branch_config(down, "remote", ".")
                    git.set_branch_config(down, "merge", "refs/tags/%s" % parent)
                    print ("Reparented %s to track %s [tag] (was tracking %s)" % (down, parent, branch))
                else:
                    git.run("branch", "--set-upstream-to", parent, down)
                    print ("Reparented %s to track %s (was tracking %s)" % (down, parent, branch))

            deletions.add(branch)
            print git.run("branch", "-d", branch)
예제 #5
0
def main(args):
  current = current_branch()
  if current == 'HEAD':
    current = None
  old_name_help = 'The old branch to rename.'
  if current:
    old_name_help += ' (default %(default)r)'

  parser = argparse.ArgumentParser()
  parser.add_argument('old_name', nargs=('?' if current else 1),
                      help=old_name_help, default=current)
  parser.add_argument('new_name', help='The new branch name.')

  opts = parser.parse_args(args)

  # when nargs=1, we get a list :(
  if isinstance(opts.old_name, list):
    opts.old_name = opts.old_name[0]

  try:
    run('branch', '-m', opts.old_name, opts.new_name)

    # update the downstreams
    for branch, merge in branch_config_map('merge').iteritems():
      if merge == 'refs/heads/' + opts.old_name:
        # Only care about local branches
        if branch_config(branch, 'remote') == '.':
          set_branch_config(branch, 'merge', 'refs/heads/' + opts.new_name)
  except subprocess2.CalledProcessError as cpe:
    sys.stderr.write(cpe.stderr)
    return 1
예제 #6
0
def main(args):
  current = current_branch()
  if current == 'HEAD':
    current = None
  old_name_help = 'The old branch to rename.'
  if current:
    old_name_help += ' (default %(default)r)'

  parser = argparse.ArgumentParser()
  parser.add_argument('old_name', nargs=('?' if current else 1),
                      help=old_name_help, default=current)
  parser.add_argument('new_name', help='The new branch name.')

  opts = parser.parse_args(args)

  # when nargs=1, we get a list :(
  if isinstance(opts.old_name, list):
    opts.old_name = opts.old_name[0]

  try:
    run('branch', '-m', opts.old_name, opts.new_name)

    # update the downstreams
    for branch, merge in branch_config_map('merge').items():
      if merge == 'refs/heads/' + opts.old_name:
        # Only care about local branches
        if branch_config(branch, 'remote') == '.':
          set_branch_config(branch, 'merge', 'refs/heads/' + opts.new_name)
  except subprocess2.CalledProcessError as cpe:
    sys.stderr.write(cpe.stderr)
    return 1
  return 0
예제 #7
0
def GetNameForCommit(sha1):
  name = re.sub(r'~.*$', '', git.run('name-rev', '--tags', '--name-only', sha1))
  if name == 'undefined':
    name = git.run(
        'name-rev', '--refs', 'remotes/branch-heads/*', '--name-only',
        sha1) + ' [untagged]'
  return name
예제 #8
0
def main(argv):
    assert len(argv) == 1, "No arguments expected"
    upfn = upstream
    cur = current_branch()
    if cur == 'HEAD':

        def _upfn(b):
            parent = upstream(b)
            if parent:
                return hash_one(parent)

        upfn = _upfn
        cur = hash_one(cur)
    downstreams = [b for b in branches() if upfn(b) == cur]
    if not downstreams:
        return "No downstream branches"
    elif len(downstreams) == 1:
        run('checkout', downstreams[0])
    else:
        high = len(downstreams) - 1
        print
        while True:
            print "Please select a downstream branch"
            for i, b in enumerate(downstreams):
                print "  %d. %s" % (i, b)
            r = raw_input("Selection (0-%d)[0]: " % high).strip() or '0'
            if not r.isdigit() or (0 > int(r) > high):
                print "Invalid choice."
            else:
                run('checkout', downstreams[int(r)])
                break
예제 #9
0
def SplitCl(description_file, comment_file, changelist, cmd_upload):
    """"Splits a branch into smaller branches and uploads CLs.

  Args:
    description_file: File containing the description of uploaded CLs.
    comment_file: File containing the comment of uploaded CLs.
    changelist: The Changelist class.
    cmd_upload: The function associated with the git cl upload command.

  Returns:
    0 in case of success. 1 in case of error.
  """
    description = AddUploadedByGitClSplitToDescription(
        ReadFile(description_file))
    comment = ReadFile(comment_file) if comment_file else None

    try:
        EnsureInGitRepository()

        cl = changelist()
        change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None)
        files = change.AffectedFiles()

        if not files:
            print 'Cannot split an empty CL.'
            return 1

        author = git.run('config', 'user.email').strip() or None
        refactor_branch = git.current_branch()
        assert refactor_branch, "Can't run from detached branch."
        refactor_branch_upstream = git.upstream(refactor_branch)
        assert refactor_branch_upstream, \
            "Branch %s must have an upstream." % refactor_branch

        owners_database = owners.Database(change.RepositoryRoot(), file,
                                          os.path)
        owners_database.load_data_needed_for([f.LocalPath() for f in files])

        files_split_by_owners = GetFilesSplitByOwners(owners_database, files)

        print('Will split current branch (' + refactor_branch + ') in ' +
              str(len(files_split_by_owners)) + ' CLs.\n')

        for directory, files in files_split_by_owners.iteritems():
            # Use '/' as a path separator in the branch name and the CL description
            # and comment.
            directory = directory.replace(os.path.sep, '/')
            # Upload the CL.
            UploadCl(refactor_branch, refactor_branch_upstream, directory,
                     files, author, description, comment, owners_database,
                     changelist, cmd_upload)

        # Go back to the original branch.
        git.run('checkout', refactor_branch)

    except subprocess2.CalledProcessError as cpe:
        sys.stderr.write(cpe.stderr)
        return 1
    return 0
예제 #10
0
def main(args):
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description=__doc__,
    )
    parser.add_argument('branch_name')
    g = parser.add_mutually_exclusive_group()
    g.add_argument('--upstream-current',
                   '--upstream_current',
                   action='store_true',
                   help='set upstream branch to current branch.')
    g.add_argument('--upstream',
                   metavar='REF',
                   default=root(),
                   help='upstream branch (or tag) to track.')
    g.add_argument('--inject-current',
                   '--inject_current',
                   action='store_true',
                   help='new branch adopts current branch\'s upstream,' +
                   ' and new branch becomes current branch\'s upstream.')
    g.add_argument('--lkgr',
                   action='store_const',
                   const='lkgr',
                   dest='upstream',
                   help='set basis ref for new branch to lkgr.')

    opts = parser.parse_args(args)

    try:
        if opts.inject_current:
            below = current_branch()
            if below is None:
                raise Exception('no current branch')
            above = upstream(below)
            if above is None:
                raise Exception('branch %s has no upstream' % (below))
            run('checkout', '--track', above, '-b', opts.branch_name)
            run('branch', '--set-upstream-to', opts.branch_name, below)
        elif opts.upstream_current:
            run('checkout', '--track', '-b', opts.branch_name)
        else:
            if opts.upstream in tags():
                # TODO(iannucci): ensure that basis_ref is an ancestor of HEAD?
                run('checkout', '--no-track', '-b', opts.branch_name,
                    hash_one(opts.upstream))
                set_config('branch.%s.remote' % opts.branch_name, '.')
                set_config('branch.%s.merge' % opts.branch_name, opts.upstream)
            else:
                # TODO(iannucci): Detect unclean workdir then stash+pop if we need to
                # teleport to a conflicting portion of history?
                run('checkout', '--track', opts.upstream, '-b',
                    opts.branch_name)
        get_or_create_merge_base(opts.branch_name)
    except subprocess2.CalledProcessError as cpe:
        sys.stdout.write(cpe.stdout)
        sys.stderr.write(cpe.stderr)
        return 1
    sys.stderr.write('Switched to branch %s.\n' % opts.branch_name)
    return 0
예제 #11
0
def main(args):
    root_ref = root()

    parser = argparse.ArgumentParser()
    g = parser.add_mutually_exclusive_group()
    g.add_argument('new_parent',
                   nargs='?',
                   help='New parent branch (or tag) to reparent to.')
    g.add_argument('--root',
                   action='store_true',
                   help='Reparent to the configured root branch (%s).' %
                   root_ref)
    g.add_argument('--lkgr',
                   action='store_true',
                   help='Reparent to the lkgr tag.')
    opts = parser.parse_args(args)

    # TODO(iannucci): Allow specification of the branch-to-reparent

    branch = current_branch()
    if opts.root:
        new_parent = root_ref
    elif opts.lkgr:
        new_parent = 'lkgr'
    else:
        new_parent = opts.new_parent
    cur_parent = upstream(branch)

    if branch == 'HEAD' or not branch:
        parser.error('Must be on the branch you want to reparent')
    if new_parent == cur_parent:
        parser.error('Cannot reparent a branch to its existing parent')

    mbase = get_or_create_merge_base(branch, cur_parent)

    all_tags = tags()
    if cur_parent in all_tags:
        cur_parent += ' [tag]'

    try:
        run('show-ref', new_parent)
    except subprocess2.CalledProcessError:
        print >> sys.stderr, 'fatal: invalid reference: %s' % new_parent
        return 1

    if new_parent in all_tags:
        print("Reparenting %s to track %s [tag] (was %s)" %
              (branch, new_parent, cur_parent))
        set_branch_config(branch, 'remote', '.')
        set_branch_config(branch, 'merge', new_parent)
    else:
        print("Reparenting %s to track %s (was %s)" %
              (branch, new_parent, cur_parent))
        run('branch', '--set-upstream-to', new_parent, branch)

    manual_merge_base(branch, mbase, new_parent)

    # TODO(iannucci): ONLY rebase-update the branch which moved (and dependants)
    return git_rebase_update.main(['--no-fetch'])
예제 #12
0
def load_generation_numbers(targets):
  """Populates the caches of get_num and get_number_tree so they contain
  the results for |targets|.

  Loads cached numbers from disk, and calculates missing numbers if one or
  more of |targets| is newer than the cached calculations.

  Args:
    targets - An iterable of binary-encoded full git commit hashes.
  """
  # In case they pass us a generator, listify targets.
  targets = list(targets)

  if all(get_num(t) is not None for t in targets):
    return

  if git.tree(REF) is None:
    empty = git.mktree({})
    commit_hash = git.run(
        # Git user.name and/or user.email may not be configured, so specifying
        # them explicitly. They are not used, but requried by Git.
        '-c', 'user.name=%s' % AUTHOR_NAME,
        '-c', 'user.email=%s' % AUTHOR_EMAIL,
        'commit-tree',
        '-m', 'Initial commit from git-number',
        empty)
    git.run('update-ref', REF, commit_hash)

  with git.ScopedPool(kind=POOL_KIND) as pool:
    preload_iter = pool.imap_unordered(preload_tree, all_prefixes())

    rev_list = []

    with git.ProgressPrinter('Loading commits: %(count)d') as inc:
      # Curiously, buffering the list into memory seems to be the fastest
      # approach in python (as opposed to iterating over the lines in the
      # stdout as they're produced). GIL strikes again :/
      cmd = [
        'rev-list', '--topo-order', '--parents', '--reverse', '^' + REF,
      ] + [binascii.hexlify(target).decode() for target in targets]
      for line in git.run(*cmd).splitlines():
        tokens = [binascii.unhexlify(token) for token in line.split()]
        rev_list.append((tokens[0], tokens[1:]))
        inc()

    get_number_tree.update(preload_iter)

  with git.ProgressPrinter('Counting: %%(count)d/%d' % len(rev_list)) as inc:
    for commit_hash, pars in rev_list:
      num = max(map(get_num, pars)) + 1 if pars else 0

      prefix = commit_hash[:PREFIX_LEN]
      get_number_tree(prefix)[commit_hash] = num
      DIRTY_TREES[prefix] += 1
      get_num.set(commit_hash, num)

      inc()
예제 #13
0
def main(args):
  root_ref = root()

  parser = argparse.ArgumentParser()
  g = parser.add_mutually_exclusive_group()
  g.add_argument('new_parent', nargs='?',
                 help='New parent branch (or tag) to reparent to.')
  g.add_argument('--root', action='store_true',
                 help='Reparent to the configured root branch (%s).' % root_ref)
  g.add_argument('--lkgr', action='store_true',
                 help='Reparent to the lkgr tag.')
  opts = parser.parse_args(args)

  # TODO(iannucci): Allow specification of the branch-to-reparent

  branch = current_branch()
  if opts.root:
    new_parent = root_ref
  elif opts.lkgr:
    new_parent = 'lkgr'
  else:
    if not opts.new_parent:
      parser.error('Must specify new parent somehow')
    new_parent = opts.new_parent
  cur_parent = upstream(branch)

  if branch == 'HEAD' or not branch:
    parser.error('Must be on the branch you want to reparent')
  if new_parent == cur_parent:
    parser.error('Cannot reparent a branch to its existing parent')

  mbase = get_or_create_merge_base(branch, cur_parent)

  all_tags = tags()
  if cur_parent in all_tags:
    cur_parent += ' [tag]'

  try:
    run('show-ref', new_parent)
  except subprocess2.CalledProcessError:
    print >> sys.stderr, 'fatal: invalid reference: %s' % new_parent
    return 1

  if new_parent in all_tags:
    print ("Reparenting %s to track %s [tag] (was %s)"
           % (branch, new_parent, cur_parent))
    set_branch_config(branch, 'remote', '.')
    set_branch_config(branch, 'merge', new_parent)
  else:
    print ("Reparenting %s to track %s (was %s)"
           % (branch, new_parent, cur_parent))
    run('branch', '--set-upstream-to', new_parent, branch)

  manual_merge_base(branch, mbase, new_parent)

  # TODO(iannucci): ONLY rebase-update the branch which moved (and dependants)
  return git_rebase_update.main(['--no-fetch'])
예제 #14
0
def load_generation_numbers(targets):
  """Populates the caches of get_num and get_number_tree so they contain
  the results for |targets|.

  Loads cached numbers from disk, and calculates missing numbers if one or
  more of |targets| is newer than the cached calculations.

  Args:
    targets - An iterable of binary-encoded full git commit hashes.
  """
  # In case they pass us a generator, listify targets.
  targets = list(targets)

  if all(get_num(t) is not None for t in targets):
    return

  if git.tree(REF) is None:
    empty = git.mktree({})
    commit_hash = git.run(
        # Git user.name and/or user.email may not be configured, so specifying
        # them explicitly. They are not used, but requried by Git.
        '-c', 'user.name=%s' % AUTHOR_NAME,
        '-c', 'user.email=%s' % AUTHOR_EMAIL,
        'commit-tree',
        '-m', 'Initial commit from git-number',
        empty)
    git.run('update-ref', REF, commit_hash)

  with git.ScopedPool(kind=POOL_KIND) as pool:
    preload_iter = pool.imap_unordered(preload_tree, all_prefixes())

    rev_list = []

    with git.ProgressPrinter('Loading commits: %(count)d') as inc:
      # Curiously, buffering the list into memory seems to be the fastest
      # approach in python (as opposed to iterating over the lines in the
      # stdout as they're produced). GIL strikes again :/
      cmd = [
        'rev-list', '--topo-order', '--parents', '--reverse', '^' + REF,
      ] + map(binascii.hexlify, targets)
      for line in git.run(*cmd).splitlines():
        tokens = map(binascii.unhexlify, line.split())
        rev_list.append((tokens[0], tokens[1:]))
        inc()

    get_number_tree.update(preload_iter)

  with git.ProgressPrinter('Counting: %%(count)d/%d' % len(rev_list)) as inc:
    for commit_hash, pars in rev_list:
      num = max(map(get_num, pars)) + 1 if pars else 0

      prefix = commit_hash[:PREFIX_LEN]
      get_number_tree(prefix)[commit_hash] = num
      DIRTY_TREES[prefix] += 1
      get_num.set(commit_hash, num)

      inc()
예제 #15
0
def remove_empty_branches(branch_tree):
    tag_set = git.tags()
    ensure_root_checkout = git.once(lambda: git.run('checkout', git.root()))

    deletions = {}
    reparents = {}
    downstreams = collections.defaultdict(list)
    for branch, parent in git.topo_iter(branch_tree, top_down=False):
        if git.is_dormant(branch):
            continue

        downstreams[parent].append(branch)

        # If branch and parent have the same tree, then branch has to be marked
        # for deletion and its children and grand-children reparented to parent.
        if git.hash_one(branch + ":") == git.hash_one(parent + ":"):
            ensure_root_checkout()

            logging.debug('branch %s merged to %s', branch, parent)

            # Mark branch for deletion while remembering the ordering, then add all
            # its children as grand-children of its parent and record reparenting
            # information if necessary.
            deletions[branch] = len(deletions)

            for down in downstreams[branch]:
                if down in deletions:
                    continue

                # Record the new and old parent for down, or update such a record
                # if it already exists. Keep track of the ordering so that reparenting
                # happen in topological order.
                downstreams[parent].append(down)
                if down not in reparents:
                    reparents[down] = (len(reparents), parent, branch)
                else:
                    order, _, old_parent = reparents[down]
                    reparents[down] = (order, parent, old_parent)

    # Apply all reparenting recorded, in order.
    for branch, value in sorted(reparents.items(), key=lambda x: x[1][0]):
        _, parent, old_parent = value
        if parent in tag_set:
            git.set_branch_config(branch, 'remote', '.')
            git.set_branch_config(branch, 'merge', 'refs/tags/%s' % parent)
            print('Reparented %s to track %s [tag] (was tracking %s)' %
                  (branch, parent, old_parent))
        else:
            git.run('branch', '--set-upstream-to', parent, branch)
            print('Reparented %s to track %s (was tracking %s)' %
                  (branch, parent, old_parent))

    # Apply all deletions recorded, in order.
    for branch, _ in sorted(deletions.items(), key=lambda x: x[1]):
        print(git.run('branch', '-d', branch))
예제 #16
0
def CreateBranchForDirectory(prefix, directory, upstream):
    """Creates a branch named |prefix| + "_" + |directory| + "_split".

  Return false if the branch already exists. |upstream| is used as upstream for
  the created branch.
  """
    existing_branches = set(git.branches(use_limit=False))
    branch_name = prefix + '_' + directory + '_split'
    if branch_name in existing_branches:
        return False
    git.run('checkout', '-t', upstream, '-b', branch_name)
    return True
예제 #17
0
def CreateBranchForDirectory(prefix, directory, upstream):
  """Creates a branch named |prefix| + "_" + |directory| + "_split".

  Return false if the branch already exists. |upstream| is used as upstream for
  the created branch.
  """
  existing_branches = set(git.branches(use_limit = False))
  branch_name = prefix + '_' + directory + '_split'
  if branch_name in existing_branches:
    return False
  git.run('checkout', '-t', upstream, '-b', branch_name)
  return True
예제 #18
0
def remove_empty_branches(branch_tree):
  tag_set = git.tags()
  ensure_root_checkout = git.once(lambda: git.run('checkout', git.root()))

  deletions = {}
  reparents = {}
  downstreams = collections.defaultdict(list)
  for branch, parent in git.topo_iter(branch_tree, top_down=False):
    downstreams[parent].append(branch)

    # If branch and parent have the same tree, then branch has to be marked
    # for deletion and its children and grand-children reparented to parent.
    if git.hash_one(branch+":") == git.hash_one(parent+":"):
      ensure_root_checkout()

      logging.debug('branch %s merged to %s', branch, parent)

      # Mark branch for deletion while remembering the ordering, then add all
      # its children as grand-children of its parent and record reparenting
      # information if necessary.
      deletions[branch] = len(deletions)

      for down in downstreams[branch]:
        if down in deletions:
          continue

        # Record the new and old parent for down, or update such a record
        # if it already exists. Keep track of the ordering so that reparenting
        # happen in topological order.
        downstreams[parent].append(down)
        if down not in reparents:
          reparents[down] = (len(reparents), parent, branch)
        else:
          order, _, old_parent = reparents[down]
          reparents[down] = (order, parent, old_parent)

  # Apply all reparenting recorded, in order.
  for branch, value in sorted(reparents.iteritems(), key=lambda x:x[1][0]):
    _, parent, old_parent = value
    if parent in tag_set:
      git.set_branch_config(branch, 'remote', '.')
      git.set_branch_config(branch, 'merge', 'refs/tags/%s' % parent)
      print ('Reparented %s to track %s [tag] (was tracking %s)'
             % (branch, parent, old_parent))
    else:
      git.run('branch', '--set-upstream-to', parent, branch)
      print ('Reparented %s to track %s (was tracking %s)'
             % (branch, parent, old_parent))

  # Apply all deletions recorded, in order.
  for branch, _ in sorted(deletions.iteritems(), key=lambda x: x[1]):
    print git.run('branch', '-d', branch)
예제 #19
0
def main(args):
  parser = argparse.ArgumentParser(
    formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    description=__doc__,
  )
  parser.add_argument('branch_name')
  g = parser.add_mutually_exclusive_group()
  g.add_argument('--upstream-current', '--upstream_current',
                 action='store_true',
                 help='set upstream branch to current branch.')
  g.add_argument('--upstream', metavar='REF', default=root(),
                 help='upstream branch (or tag) to track.')
  g.add_argument('--inject-current', '--inject_current',
                 action='store_true',
                 help='new branch adopts current branch\'s upstream,' +
                 ' and new branch becomes current branch\'s upstream.')
  g.add_argument('--lkgr', action='store_const', const='lkgr', dest='upstream',
                 help='set basis ref for new branch to lkgr.')

  opts = parser.parse_args(args)

  try:
    if opts.inject_current:
      below = current_branch()
      if below is None:
        raise Exception('no current branch')
      above = upstream(below)
      if above is None:
        raise Exception('branch %s has no upstream' % (below))
      run('checkout', '--track', above, '-b', opts.branch_name)
      run('branch', '--set-upstream-to', opts.branch_name, below)
    elif opts.upstream_current:
      run('checkout', '--track', '-b', opts.branch_name)
    else:
      if opts.upstream in tags():
        # TODO(iannucci): ensure that basis_ref is an ancestor of HEAD?
        run('checkout', '--no-track', '-b', opts.branch_name,
            hash_one(opts.upstream))
        set_config('branch.%s.remote' % opts.branch_name, '.')
        set_config('branch.%s.merge' % opts.branch_name, opts.upstream)
      else:
        # TODO(iannucci): Detect unclean workdir then stash+pop if we need to
        # teleport to a conflicting portion of history?
        run('checkout', '--track', opts.upstream, '-b', opts.branch_name)
    get_or_create_merge_base(opts.branch_name)
  except subprocess2.CalledProcessError as cpe:
    sys.stdout.write(cpe.stdout)
    sys.stderr.write(cpe.stderr)
    return 1
  sys.stderr.write('Switched to branch %s.\n' % opts.branch_name)
  return 0
예제 #20
0
def GetMergesForCommit(sha1):
    return [
        c.split()[0]
        for c in git.run('log', '--oneline', '-F', '--all', '--no-abbrev',
                         '--grep', 'cherry picked from commit %s' %
                         sha1).splitlines()
    ]
예제 #21
0
def main(args):
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument('ref')

    g = parser.add_mutually_exclusive_group()
    g.add_argument('--key',
                   metavar='KEY',
                   help='Get all values for the given footer name, one per '
                   'line (case insensitive)')
    g.add_argument('--position', action='store_true')
    g.add_argument('--position-ref', action='store_true')
    g.add_argument('--position-num', action='store_true')

    opts = parser.parse_args(args)

    message = git.run('log', '-1', '--format=%B', opts.ref)
    footers = parse_footers(message)

    if opts.key:
        for v in footers.get(normalize_name(opts.key), []):
            print v
    elif opts.position:
        pos = get_position(footers)
        print '%s@{#%s}' % (pos[0], pos[1] or '?')
    elif opts.position_ref:
        print get_position(footers)[0]
    elif opts.position_num:
        pos = get_position(footers)
        assert pos[1], 'No valid position for commit'
        print pos[1]
    else:
        for k in footers.keys():
            for v in footers[k]:
                print '%s: %s' % (k, v)
예제 #22
0
def fetch_remotes(branch_tree):
  """Fetches all remotes which are needed to update |branch_tree|."""
  fetch_tags = False
  remotes = set()
  tag_set = git.tags()
  for parent in branch_tree.itervalues():
    if parent in tag_set:
      fetch_tags = True
    else:
      full_ref = git.run('rev-parse', '--symbolic-full-name', parent)
      if full_ref.startswith('refs/remotes'):
        parts = full_ref.split('/')
        remote_name = parts[2]
        remotes.add(remote_name)

  fetch_args = []
  if fetch_tags:
    # Need to fetch all because we don't know what remote the tag comes from :(
    # TODO(iannucci): assert that the tags are in the remote fetch refspec
    fetch_args = ['--all']
  else:
    fetch_args.append('--multiple')
    fetch_args.extend(remotes)
  # TODO(iannucci): Should we fetch git-svn?

  if not fetch_args:  # pragma: no cover
    print 'Nothing to fetch.'
  else:
    git.run_with_stderr('fetch', *fetch_args, stdout=sys.stdout,
                        stderr=sys.stderr)
예제 #23
0
def fetch_remotes(branch_tree):
    """Fetches all remotes which are needed to update |branch_tree|."""
    fetch_tags = False
    remotes = set()
    tag_set = git.tags()
    for parent in branch_tree.itervalues():
        if parent in tag_set:
            fetch_tags = True
        else:
            full_ref = git.run('rev-parse', '--symbolic-full-name', parent)
            if full_ref.startswith('refs/remotes'):
                parts = full_ref.split('/')
                remote_name = parts[2]
                remotes.add(remote_name)

    fetch_args = []
    if fetch_tags:
        # Need to fetch all because we don't know what remote the tag comes from :(
        # TODO(iannucci): assert that the tags are in the remote fetch refspec
        fetch_args = ['--all']
    else:
        fetch_args.append('--multiple')
        fetch_args.extend(remotes)
    # TODO(iannucci): Should we fetch git-svn?

    if not fetch_args:  # pragma: no cover
        print 'Nothing to fetch.'
    else:
        git.run_with_stderr('fetch',
                            *fetch_args,
                            stdout=sys.stdout,
                            stderr=sys.stderr)
예제 #24
0
def main(args):
    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument("ref")

    g = parser.add_mutually_exclusive_group()
    g.add_argument(
        "--key", metavar="KEY", help="Get all values for the given footer name, one per " "line (case insensitive)"
    )
    g.add_argument("--position", action="store_true")
    g.add_argument("--position-ref", action="store_true")
    g.add_argument("--position-num", action="store_true")

    opts = parser.parse_args(args)

    message = git.run("log", "-1", "--format=%B", opts.ref)
    footers = parse_footers(message)

    if opts.key:
        for v in footers.get(normalize_name(opts.key), []):
            print v
    elif opts.position:
        pos = get_position(footers)
        print "%s@{#%s}" % (pos[0], pos[1] or "?")
    elif opts.position_ref:
        print get_position(footers)[0]
    elif opts.position_num:
        pos = get_position(footers)
        assert pos[1], "No valid position for commit"
        print pos[1]
    else:
        for k in footers.keys():
            for v in footers[k]:
                print "%s: %s" % (k, v)
    return 0
예제 #25
0
def main(args):
    parser = argparse.ArgumentParser()
    parser.add_argument('--pick',
                        help=('The number to pick if this command would '
                              'prompt'))
    opts = parser.parse_args(args)

    upfn = upstream
    cur = current_branch()
    if cur == 'HEAD':

        def _upfn(b):
            parent = upstream(b)
            if parent:
                return hash_one(parent)

        upfn = _upfn
        cur = hash_one(cur)
    downstreams = [b for b in branches() if upfn(b) == cur]
    if not downstreams:
        print "No downstream branches"
        return 1
    elif len(downstreams) == 1:
        run('checkout', downstreams[0], stdout=sys.stdout, stderr=sys.stderr)
    else:
        high = len(downstreams) - 1
        while True:
            print "Please select a downstream branch"
            for i, b in enumerate(downstreams):
                print "  %d. %s" % (i, b)
            prompt = "Selection (0-%d)[0]: " % high
            r = opts.pick
            if r:
                print prompt + r
            else:
                r = raw_input(prompt).strip() or '0'
            if not r.isdigit() or (0 > int(r) > high):
                print "Invalid choice."
            else:
                run('checkout',
                    downstreams[int(r)],
                    stdout=sys.stdout,
                    stderr=sys.stderr)
                break
    return 0
예제 #26
0
def main(args):
  parser = argparse.ArgumentParser()
  parser.add_argument('--pick',
                      help=(
                          'The number to pick if this command would '
                          'prompt'))
  opts = parser.parse_args(args)

  upfn = upstream
  cur = current_branch()
  if cur == 'HEAD':
    def _upfn(b):
      parent = upstream(b)
      if parent:
        return hash_one(parent)
    upfn = _upfn
    cur = hash_one(cur)
  downstreams = [b for b in branches() if upfn(b) == cur]
  if not downstreams:
    print "No downstream branches"
    return 1
  elif len(downstreams) == 1:
    run('checkout', downstreams[0], stdout=sys.stdout, stderr=sys.stderr)
  else:
    high = len(downstreams) - 1
    while True:
      print "Please select a downstream branch"
      for i, b in enumerate(downstreams):
        print "  %d. %s" % (i, b)
      prompt = "Selection (0-%d)[0]: " % high
      r = opts.pick
      if r:
        print prompt + r
      else:
        r = raw_input(prompt).strip() or '0'
      if not r.isdigit() or (0 > int(r) > high):
        print "Invalid choice."
      else:
        run('checkout', downstreams[int(r)], stdout=sys.stdout,
            stderr=sys.stderr)
        break
  return 0
예제 #27
0
def get_footer_svn_id(branch=None):
    if not branch:
        branch = git.root()
    svn_id = None
    message = git.run('log', '-1', '--format=%B', branch)
    footers = parse_footers(message)
    git_svn_id = get_unique(footers, 'git-svn-id')
    if git_svn_id:
        match = GIT_SVN_ID_PATTERN.match(git_svn_id)
        if match:
            svn_id = match.group(1)
    return svn_id
예제 #28
0
def get_footer_svn_id(branch=None):
  if not branch:
    branch = git.root()
  svn_id = None
  message = git.run('log', '-1', '--format=%B', branch)
  footers = parse_footers(message)
  git_svn_id = get_unique(footers, 'git-svn-id')
  if git_svn_id:
    match = GIT_SVN_ID_PATTERN.match(git_svn_id)
    if match:
      svn_id = match.group(1)
  return svn_id
예제 #29
0
def main(args):
  parser = argparse.ArgumentParser(
    formatter_class=argparse.ArgumentDefaultsHelpFormatter
  )
  parser.add_argument('branch_name')
  g = parser.add_mutually_exclusive_group()
  g.add_argument('--upstream_current', action='store_true',
                 help='set upstream branch to current branch.')
  g.add_argument('--upstream', metavar='REF', default=root(),
                 help='upstream branch (or tag) to track.')
  g.add_argument('--lkgr', action='store_const', const='lkgr', dest='upstream',
                 help='set basis ref for new branch to lkgr.')

  opts = parser.parse_args(args)

  try:
    if opts.upstream_current:
      run('checkout', '--track', '-b', opts.branch_name)
    else:
      if opts.upstream in tags():
        # TODO(iannucci): ensure that basis_ref is an ancestor of HEAD?
        run('checkout', '--no-track', '-b', opts.branch_name,
            hash_one(opts.upstream))
        set_config('branch.%s.remote' % opts.branch_name, '.')
        set_config('branch.%s.merge' % opts.branch_name, opts.upstream)
      else:
        # TODO(iannucci): Detect unclean workdir then stash+pop if we need to
        # teleport to a conflicting portion of history?
        run('checkout', '--track', opts.upstream, '-b', opts.branch_name)

    get_or_create_merge_base(opts.branch_name)
  except subprocess2.CalledProcessError as cpe:
    sys.stdout.write(cpe.stdout)
    sys.stderr.write(cpe.stderr)
    return 1
예제 #30
0
def finalize(targets):
    """Saves all cache data to the git repository.

  After calculating the generation number for |targets|, call finalize() to
  save all the work to the git repository.

  This in particular saves the trees referred to by DIRTY_TREES.
  """
    if not DIRTY_TREES:
        return

    msg = 'git-number Added %s numbers' % sum(DIRTY_TREES.itervalues())

    idx = os.path.join(git.run('rev-parse', '--git-dir'), 'number.idx')
    env = os.environ.copy()
    env['GIT_INDEX_FILE'] = idx

    progress_message = 'Finalizing: (%%(count)d/%d)' % len(DIRTY_TREES)
    with git.ProgressPrinter(progress_message) as inc:
        git.run('read-tree', REF, env=env)

        prefixes_trees = ((p, get_number_tree(p)) for p in sorted(DIRTY_TREES))
        updater = subprocess2.Popen(
            ['git', 'update-index', '-z', '--index-info'],
            stdin=subprocess2.PIPE,
            env=env)

        with git.ScopedPool(kind=POOL_KIND) as leaf_pool:
            for item in leaf_pool.imap(leaf_map_fn, prefixes_trees):
                updater.stdin.write(item)
                inc()

        updater.stdin.close()
        updater.wait()
        assert updater.returncode == 0

        tree_id = git.run('write-tree', env=env)
        commit_cmd = [
            # Git user.name and/or user.email may not be configured, so specifying
            # them explicitly. They are not used, but requried by Git.
            '-c',
            'user.name=%s' % AUTHOR_NAME,
            '-c',
            'user.email=%s' % AUTHOR_EMAIL,
            'commit-tree',
            '-m',
            msg,
            '-p'
        ] + git.hash_multi(REF)
        for t in targets:
            commit_cmd.extend(['-p', binascii.hexlify(t)])
        commit_cmd.append(tree_id)
        commit_hash = git.run(*commit_cmd)
        git.run('update-ref', REF, commit_hash)
    DIRTY_TREES.clear()
예제 #31
0
def fetch_remotes(branch_tree):
    """Fetches all remotes which are needed to update |branch_tree|."""
    fetch_tags = False
    remotes = set()
    tag_set = git.tags()
    fetchspec_map = {}
    all_fetchspec_configs = git.run('config', '--get-regexp',
                                    r'^remote\..*\.fetch').strip()
    for fetchspec_config in all_fetchspec_configs.splitlines():
        key, _, fetchspec = fetchspec_config.partition(' ')
        dest_spec = fetchspec.partition(':')[2]
        remote_name = key.split('.')[1]
        fetchspec_map[dest_spec] = remote_name
    for parent in branch_tree.itervalues():
        if parent in tag_set:
            fetch_tags = True
        else:
            full_ref = git.run('rev-parse', '--symbolic-full-name', parent)
            for dest_spec, remote_name in fetchspec_map.iteritems():
                if fnmatch(full_ref, dest_spec):
                    remotes.add(remote_name)
                    break

    fetch_args = []
    if fetch_tags:
        # Need to fetch all because we don't know what remote the tag comes from :(
        # TODO(iannucci): assert that the tags are in the remote fetch refspec
        fetch_args = ['--all']
    else:
        fetch_args.append('--multiple')
        fetch_args.extend(remotes)
    # TODO(iannucci): Should we fetch git-svn?

    if not fetch_args:  # pragma: no cover
        print 'Nothing to fetch.'
    else:
        git.run_with_stderr('fetch',
                            *fetch_args,
                            stdout=sys.stdout,
                            stderr=sys.stderr)
def main():
    remote = git_common.run('remote')
    # Use first remote as source of truth
    remote = remote.split("\n")[0]
    if not remote:
        raise RuntimeError('Could not find any remote')
    url = scm.GIT.GetConfig(git_common.repo_root(), 'remote.%s.url' % remote)
    host = urllib.parse.urlparse(url).netloc
    if not host:
        raise RuntimeError('Could not find remote host')

    project_head = gerrit_util.GetProjectHead(GetGerritHost(host),
                                              GetGerritProject(url))
    if project_head != 'refs/heads/main':
        raise RuntimeError("The repository is not migrated yet.")

    logging.info("Running fetch...")
    git_common.run('fetch', remote)
    logging.info("Updating remote HEAD...")
    git_common.run('remote', 'set-head', '-a', remote)

    branches = git_common.get_branches_info(True)

    if 'master' in branches:
        logging.info("Migrating master branch...")
        if 'main' in branches:
            logging.info(
                'You already have master and main branch, consider removing '
                'master manually:\n'
                ' $ git branch -d master\n')
        else:
            git_common.run('branch', '-m', 'master', 'main')
        branches = git_common.get_branches_info(True)

    for name in branches:
        branch = branches[name]
        if not branch:
            continue

        if 'master' in branch.upstream:
            logging.info("Migrating %s branch..." % name)
            new_upstream = branch.upstream.replace('master', 'main')
            git_common.run('branch', '--set-upstream-to', new_upstream, name)
            git_common.remove_merge_base(name)
def UploadCl(refactor_branch, refactor_branch_upstream, directory, files,
             description, comment, reviewers, changelist, cmd_upload,
             cq_dry_run, enable_auto_submit):
    """Uploads a CL with all changes to |files| in |refactor_branch|.

  Args:
    refactor_branch: Name of the branch that contains the changes to upload.
    refactor_branch_upstream: Name of the upstream of |refactor_branch|.
    directory: Path to the directory that contains the OWNERS file for which
        to upload a CL.
    files: List of AffectedFile instances to include in the uploaded CL.
    description: Description of the uploaded CL.
    comment: Comment to post on the uploaded CL.
    reviewers: A set of reviewers for the CL.
    changelist: The Changelist class.
    cmd_upload: The function associated with the git cl upload command.
    cq_dry_run: If CL uploads should also do a cq dry run.
    enable_auto_submit: If CL uploads should also enable auto submit.
  """
    # Create a branch.
    if not CreateBranchForDirectory(refactor_branch, directory,
                                    refactor_branch_upstream):
        print('Skipping ' + directory + ' for which a branch already exists.')
        return

    # Checkout all changes to files in |files|.
    deleted_files = [f.AbsoluteLocalPath() for f in files if f.Action() == 'D']
    if deleted_files:
        git.run(*['rm'] + deleted_files)
    modified_files = [
        f.AbsoluteLocalPath() for f in files if f.Action() != 'D'
    ]
    if modified_files:
        git.run(*['checkout', refactor_branch, '--'] + modified_files)

    # Commit changes. The temporary file is created with delete=False so that it
    # can be deleted manually after git has read it rather than automatically
    # when it is closed.
    with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
        tmp_file.write(FormatDescriptionOrComment(description, directory))
        # Close the file to let git open it at the next line.
        tmp_file.close()
        git.run('commit', '-F', tmp_file.name)
        os.remove(tmp_file.name)

    # Upload a CL.
    upload_args = ['-f', '-r', ','.join(reviewers)]
    if cq_dry_run:
        upload_args.append('--cq-dry-run')
    if not comment:
        upload_args.append('--send-mail')
    if enable_auto_submit:
        upload_args.append('--enable-auto-submit')
    print('Uploading CL for ' + directory + '.')
    cmd_upload(upload_args)
    if comment:
        changelist().AddComment(FormatDescriptionOrComment(comment, directory),
                                publish=True)
예제 #34
0
def fetch_remotes(branch_tree):
  """Fetches all remotes which are needed to update |branch_tree|."""
  fetch_tags = False
  remotes = set()
  tag_set = git.tags()
  fetchspec_map = {}
  all_fetchspec_configs = git.run(
      'config', '--get-regexp', r'^remote\..*\.fetch').strip()
  for fetchspec_config in all_fetchspec_configs.splitlines():
    key, _, fetchspec = fetchspec_config.partition(' ')
    dest_spec = fetchspec.partition(':')[2]
    remote_name = key.split('.')[1]
    fetchspec_map[dest_spec] = remote_name
  for parent in branch_tree.itervalues():
    if parent in tag_set:
      fetch_tags = True
    else:
      full_ref = git.run('rev-parse', '--symbolic-full-name', parent)
      for dest_spec, remote_name in fetchspec_map.iteritems():
        if fnmatch(full_ref, dest_spec):
          remotes.add(remote_name)
          break

  fetch_args = []
  if fetch_tags:
    # Need to fetch all because we don't know what remote the tag comes from :(
    # TODO(iannucci): assert that the tags are in the remote fetch refspec
    fetch_args = ['--all']
  else:
    fetch_args.append('--multiple')
    fetch_args.extend(remotes)
  # TODO(iannucci): Should we fetch git-svn?

  if not fetch_args:  # pragma: no cover
    print 'Nothing to fetch.'
  else:
    git.run_with_stderr('fetch', *fetch_args, stdout=sys.stdout,
                        stderr=sys.stderr)
예제 #35
0
def create_new_branch(branch_name,
                      upstream_current=False,
                      upstream=None,
                      inject_current=False):
    upstream = upstream or git_common.root()
    try:
        if inject_current:
            below = git_common.current_branch()
            if below is None:
                raise Exception('no current branch')
            above = git_common.upstream(below)
            if above is None:
                raise Exception('branch %s has no upstream' % (below))
            git_common.run('checkout', '--track', above, '-b', branch_name)
            git_common.run('branch', '--set-upstream-to', branch_name, below)
        elif upstream_current:
            git_common.run('checkout', '--track', '-b', branch_name)
        else:
            if upstream in git_common.tags():
                # TODO(iannucci): ensure that basis_ref is an ancestor of HEAD?
                git_common.run('checkout', '--no-track', '-b', branch_name,
                               git_common.hash_one(upstream))
                git_common.set_config('branch.%s.remote' % branch_name, '.')
                git_common.set_config('branch.%s.merge' % branch_name,
                                      upstream)
            else:
                # TODO(iannucci): Detect unclean workdir then stash+pop if we need to
                # teleport to a conflicting portion of history?
                git_common.run('checkout', '--track', upstream, '-b',
                               branch_name)
        git_common.get_or_create_merge_base(branch_name)
    except subprocess2.CalledProcessError as cpe:
        sys.stdout.write(cpe.stdout.decode('utf-8', 'replace'))
        sys.stderr.write(cpe.stderr.decode('utf-8', 'replace'))
        return 1
    sys.stderr.write('Switched to branch %s.\n' % branch_name)
    return 0
예제 #36
0
def UploadCl(refactor_branch, refactor_branch_upstream, directory, files,
             author, description, comment, owners_database, changelist,
             cmd_upload):
    """Uploads a CL with all changes to |files| in |refactor_branch|.

  Args:
    refactor_branch: Name of the branch that contains the changes to upload.
    refactor_branch_upstream: Name of the upstream of |refactor_branch|.
    directory: Path to the directory that contains the OWNERS file for which
        to upload a CL.
    files: List of AffectedFile instances to include in the uploaded CL.
    author: Email address of the user running this script.
    description: Description of the uploaded CL.
    comment: Comment to post on the uploaded CL.
    owners_database: An owners.Database instance.
    changelist: The Changelist class.
    cmd_upload: The function associated with the git cl upload command.
  """

    # Create a branch.
    if not CreateBranchForDirectory(refactor_branch, directory,
                                    refactor_branch_upstream):
        print 'Skipping ' + directory + ' for which a branch already exists.'
        return

    # Checkout all changes to files in |files|.
    deleted_files = [f.AbsoluteLocalPath() for f in files if f.Action() == 'D']
    if deleted_files:
        git.run(*['rm'] + deleted_files)
    modified_files = [
        f.AbsoluteLocalPath() for f in files if f.Action() != 'D'
    ]
    if modified_files:
        git.run(*['checkout', refactor_branch, '--'] + modified_files)

    # Commit changes. The temporary file is created with delete=False so that it
    # can be deleted manually after git has read it rather than automatically
    # when it is closed.
    with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
        tmp_file.write(FormatDescriptionOrComment(description, directory))
        # Close the file to let git open it at the next line.
        tmp_file.close()
        git.run('commit', '-F', tmp_file.name)
        os.remove(tmp_file.name)

    # Upload a CL.
    reviewers = owners_database.reviewers_for([f.LocalPath() for f in files],
                                              author)
    upload_args = ['-f', '--cq-dry-run', '-r', ','.join(reviewers)]
    if not comment:
        upload_args.append('--send-mail')
    print 'Uploading CL for ' + directory + '.'
    cmd_upload(upload_args)
    if comment:
        changelist().AddComment(FormatDescriptionOrComment(comment, directory),
                                publish=True)
예제 #37
0
def main(args):
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument('ref',
                        nargs='?',
                        help='Git ref to retrieve footers from.'
                        ' Omit to parse stdin.')

    g = parser.add_mutually_exclusive_group()
    g.add_argument('--key',
                   metavar='KEY',
                   help='Get all values for the given footer name, one per '
                   'line (case insensitive)')
    g.add_argument('--position', action='store_true')
    g.add_argument('--position-ref', action='store_true')
    g.add_argument('--position-num', action='store_true')
    g.add_argument('--json',
                   help='filename to dump JSON serialized footers to.')

    opts = parser.parse_args(args)

    if opts.ref:
        message = git.run('log', '-1', '--format=%B', opts.ref)
    else:
        message = sys.stdin.read()

    footers = parse_footers(message)

    if opts.key:
        for v in footers.get(normalize_name(opts.key), []):
            print(v)
    elif opts.position:
        pos = get_position(footers)
        print('%s@{#%s}' % (pos[0], pos[1] or '?'))
    elif opts.position_ref:
        print(get_position(footers)[0])
    elif opts.position_num:
        pos = get_position(footers)
        assert pos[1], 'No valid position for commit'
        print(pos[1])
    elif opts.json:
        with open(opts.json, 'w') as f:
            json.dump(footers, f)
    else:
        for k in footers.keys():
            for v in footers[k]:
                print('%s: %s' % (k, v))
    return 0
예제 #38
0
def finalize(targets):
  """Saves all cache data to the git repository.

  After calculating the generation number for |targets|, call finalize() to
  save all the work to the git repository.

  This in particular saves the trees referred to by DIRTY_TREES.
  """
  if not DIRTY_TREES:
    return

  msg = 'git-number Added %s numbers' % sum(DIRTY_TREES.itervalues())

  idx = os.path.join(git.run('rev-parse', '--git-dir'), 'number.idx')
  env = os.environ.copy()
  env['GIT_INDEX_FILE'] = idx

  progress_message = 'Finalizing: (%%(count)d/%d)' % len(DIRTY_TREES)
  with git.ProgressPrinter(progress_message) as inc:
    git.run('read-tree', REF, env=env)

    prefixes_trees = ((p, get_number_tree(p)) for p in sorted(DIRTY_TREES))
    updater = subprocess2.Popen(['git', 'update-index', '-z', '--index-info'],
                                stdin=subprocess2.PIPE, env=env)

    with git.ScopedPool(kind=POOL_KIND) as leaf_pool:
      for item in leaf_pool.imap(leaf_map_fn, prefixes_trees):
        updater.stdin.write(item)
        inc()

    updater.stdin.close()
    updater.wait()
    assert updater.returncode == 0

    tree_id = git.run('write-tree', env=env)
    commit_cmd = [
        # Git user.name and/or user.email may not be configured, so specifying
        # them explicitly. They are not used, but requried by Git.
        '-c', 'user.name=%s' % AUTHOR_NAME,
        '-c', 'user.email=%s' % AUTHOR_EMAIL,
        'commit-tree',
        '-m', msg,
        '-p'] + git.hash_multi(REF)
    for t in targets:
      commit_cmd.extend(['-p', binascii.hexlify(t)])
    commit_cmd.append(tree_id)
    commit_hash = git.run(*commit_cmd)
    git.run('update-ref', REF, commit_hash)
  DIRTY_TREES.clear()
예제 #39
0
def get_number_tree(prefix_bytes):
  """Returns a dictionary of the git-number registry specified by
  |prefix_bytes|.

  This is in the form of {<full binary ref>: <gen num> ...}

  >>> get_number_tree('\x83\xb4')
  {'\x83\xb4\xe3\xe4W\xf9J*\x8f/c\x16\xecD\xd1\x04\x8b\xa9qz': 169, ...}
  """
  ref = '%s:%s' % (REF, pathlify(prefix_bytes))

  try:
    raw = git.run('cat-file', 'blob', ref, autostrip=False, decode=False)
    return dict(struct.unpack_from(CHUNK_FMT, raw, i * CHUNK_SIZE)
                for i in range(len(raw) // CHUNK_SIZE))
  except subprocess2.CalledProcessError:
    return {}
예제 #40
0
def main(args):
  parser = argparse.ArgumentParser(
    formatter_class=argparse.ArgumentDefaultsHelpFormatter
  )
  parser.add_argument('ref', nargs='?', help="Git ref to retrieve footers from."
                      " Omit to parse stdin.")

  g = parser.add_mutually_exclusive_group()
  g.add_argument('--key', metavar='KEY',
                 help='Get all values for the given footer name, one per '
                 'line (case insensitive)')
  g.add_argument('--position', action='store_true')
  g.add_argument('--position-ref', action='store_true')
  g.add_argument('--position-num', action='store_true')
  g.add_argument('--json', help="filename to dump JSON serialized headers to.")


  opts = parser.parse_args(args)

  if opts.ref:
    message = git.run('log', '-1', '--format=%B', opts.ref)
  else:
    message = '\n'.join(l for l in sys.stdin)

  footers = parse_footers(message)

  if opts.key:
    for v in footers.get(normalize_name(opts.key), []):
      print v
  elif opts.position:
    pos = get_position(footers)
    print '%s@{#%s}' % (pos[0], pos[1] or '?')
  elif opts.position_ref:
    print get_position(footers)[0]
  elif opts.position_num:
    pos = get_position(footers)
    assert pos[1], 'No valid position for commit'
    print pos[1]
  elif opts.json:
    with open(opts.json, 'w') as f:
      json.dump(footers, f)
  else:
    for k in footers.keys():
      for v in footers[k]:
        print '%s: %s' % (k, v)
  return 0
예제 #41
0
def get_number_tree(prefix_bytes):
  """Returns a dictionary of the git-number registry specified by
  |prefix_bytes|.

  This is in the form of {<full binary ref>: <gen num> ...}

  >>> get_number_tree('\x83\xb4')
  {'\x83\xb4\xe3\xe4W\xf9J*\x8f/c\x16\xecD\xd1\x04\x8b\xa9qz': 169, ...}
  """
  ref = '%s:%s' % (REF, pathlify(prefix_bytes))

  try:
    raw = buffer(git.run('cat-file', 'blob', ref, autostrip=False))
    return dict(struct.unpack_from(CHUNK_FMT, raw, i * CHUNK_SIZE)
                for i in xrange(len(raw) / CHUNK_SIZE))
  except subprocess2.CalledProcessError:
    return {}
예제 #42
0
def UploadCl(refactor_branch, refactor_branch_upstream, directory, files,
             description, comment, reviewers, changelist, cmd_upload,
             cq_dry_run):
  """Uploads a CL with all changes to |files| in |refactor_branch|.

  Args:
    refactor_branch: Name of the branch that contains the changes to upload.
    refactor_branch_upstream: Name of the upstream of |refactor_branch|.
    directory: Path to the directory that contains the OWNERS file for which
        to upload a CL.
    files: List of AffectedFile instances to include in the uploaded CL.
    description: Description of the uploaded CL.
    comment: Comment to post on the uploaded CL.
    reviewers: A set of reviewers for the CL.
    changelist: The Changelist class.
    cmd_upload: The function associated with the git cl upload command.
    cq_dry_run: If CL uploads should also do a cq dry run.
  """
  # Create a branch.
  if not CreateBranchForDirectory(
      refactor_branch, directory, refactor_branch_upstream):
    print 'Skipping ' + directory + ' for which a branch already exists.'
    return

  # Checkout all changes to files in |files|.
  deleted_files = [f.AbsoluteLocalPath() for f in files if f.Action() == 'D']
  if deleted_files:
    git.run(*['rm'] + deleted_files)
  modified_files = [f.AbsoluteLocalPath() for f in files if f.Action() != 'D']
  if modified_files:
    git.run(*['checkout', refactor_branch, '--'] + modified_files)

  # Commit changes. The temporary file is created with delete=False so that it
  # can be deleted manually after git has read it rather than automatically
  # when it is closed.
  with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
    tmp_file.write(FormatDescriptionOrComment(description, directory))
    # Close the file to let git open it at the next line.
    tmp_file.close()
    git.run('commit', '-F', tmp_file.name)
    os.remove(tmp_file.name)

  # Upload a CL.
  upload_args = ['-f', '-r', ','.join(reviewers)]
  if cq_dry_run:
    upload_args.append('--cq-dry-run')
  if not comment:
    upload_args.append('--send-mail')
  print 'Uploading CL for ' + directory + '.'
  cmd_upload(upload_args)
  if comment:
    changelist().AddComment(FormatDescriptionOrComment(comment, directory),
                            publish=True)
예제 #43
0
def HasFormatErrors():
  # For more options, see vendor/depot_tools/git_cl.py
  cmd = ['cl', 'format', '--diff']
  diff = git_cl.RunGit(cmd).encode('utf-8')
  if diff:
    # Verify that git cl format generates a diff
    if git_common.is_dirty_git_tree('git cl format'):
      # Skip verification if there are uncommitted changes
      print(diff)
      print('Format errors detected. Run npm format locally to fix.')
      return True
    git_cl.RunGit(['cl', 'format'])
    git_diff = git_common.run('diff').encode('utf-8')
    if git_diff:
      print(git_diff)
      print('Format errors have been auto-fixed. Please review and commit these changes if lint was run locally. Otherwise run npm format to fix.')
      return True
  return False
예제 #44
0
def main(args):
  parser = argparse.ArgumentParser(
    formatter_class=argparse.ArgumentDefaultsHelpFormatter
  )
  parser.add_argument('ref')

  g = parser.add_mutually_exclusive_group()
  g.add_argument('--key', metavar='KEY',
                 help='Get all values for the given footer name, one per '
                 'line (case insensitive)')
  g.add_argument('--position', action='store_true')
  g.add_argument('--position-ref', action='store_true')
  g.add_argument('--position-num', action='store_true')


  opts = parser.parse_args(args)

  message = git.run('log', '-1', '--format=%B', opts.ref)
  footers = parse_footers(message)

  if opts.key:
    for v in footers.get(normalize_name(opts.key), []):
      print v
  elif opts.position:
    pos = get_position(footers)
    print '%s@{#%s}' % (pos[0], pos[1] or '?')
  elif opts.position_ref:
    print get_position(footers)[0]
  elif opts.position_num:
    pos = get_position(footers)
    assert pos[1], 'No valid position for commit'
    print pos[1]
  else:
    for k in footers.keys():
      for v in footers[k]:
        print '%s: %s' % (k, v)
  return 0
예제 #45
0
def main(args):
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument('branch_name')
    g = parser.add_mutually_exclusive_group()
    g.add_argument('--upstream_current',
                   action='store_true',
                   help='set upstream branch to current branch.')
    g.add_argument('--upstream',
                   metavar='REF',
                   default=root(),
                   help='upstream branch (or tag) to track.')
    g.add_argument('--lkgr',
                   action='store_const',
                   const='lkgr',
                   dest='upstream',
                   help='set basis ref for new branch to lkgr.')

    opts = parser.parse_args(args)

    try:
        if opts.upstream_current:
            run('checkout', '--track', '-b', opts.branch_name)
        else:
            if opts.upstream in tags():
                # TODO(iannucci): ensure that basis_ref is an ancestor of HEAD?
                run('checkout', '--no-track', '-b', opts.branch_name,
                    hash_one(opts.upstream))
                set_config('branch.%s.remote' % opts.branch_name, '.')
                set_config('branch.%s.merge' % opts.branch_name, opts.upstream)
            else:
                # TODO(iannucci): Detect unclean workdir then stash+pop if we need to
                # teleport to a conflicting portion of history?
                run('checkout', '--track', opts.upstream, '-b',
                    opts.branch_name)

        get_or_create_merge_base(opts.branch_name)
    except subprocess2.CalledProcessError as cpe:
        sys.stdout.write(cpe.stdout)
        sys.stderr.write(cpe.stderr)
        return 1
예제 #46
0
  def __append_branch(self, branch, depth=0):
    """Recurses through the tree structure and appends an OutputLine to the
    OutputManager for each branch."""
    branch_info = self.__branches_info[branch]
    if branch_info:
      branch_hash = branch_info.hash
    else:
      try:
        branch_hash = hash_one(branch, short=True)
      except subprocess2.CalledProcessError:
        branch_hash = None

    line = OutputLine()

    # The branch name with appropriate indentation.
    suffix = ''
    if branch == self.__current_branch or (
        self.__current_branch == 'HEAD' and branch == self.__current_hash):
      suffix = ' *'
    branch_string = branch
    if branch in self.__gone_branches:
      branch_string = '{%s:GONE}' % branch
    if not branch:
      branch_string = '{NO_UPSTREAM}'
    main_string = '  ' * depth + branch_string + suffix
    line.append(
        main_string,
        color=self.__color_for_branch(branch, branch_hash))

    # The branch hash.
    if self.verbosity >= 2:
      line.append(branch_hash or '', separator=' ', color=Fore.RED)

    # The branch tracking status.
    if self.verbosity >= 1:
      ahead_string = ''
      behind_string = ''
      front_separator = ''
      center_separator = ''
      back_separator = ''
      if branch_info and not self.__is_invalid_parent(branch_info.upstream):
        ahead = branch_info.ahead
        behind = branch_info.behind

        if ahead:
          ahead_string = 'ahead %d' % ahead
        if behind:
          behind_string = 'behind %d' % behind

        if ahead or behind:
          front_separator = '['
          back_separator = ']'

        if ahead and behind:
          center_separator = '|'

      line.append(front_separator, separator=' ')
      line.append(ahead_string, separator=' ', color=Fore.MAGENTA)
      line.append(center_separator, separator=' ')
      line.append(behind_string, separator=' ', color=Fore.MAGENTA)
      line.append(back_separator)

    # The Rietveld issue associated with the branch.
    if self.verbosity >= 2:
      (url, color, status) = ('', '', '') if self.__is_invalid_parent(branch) \
          else self.__status_info[branch]
      if self.verbosity > 2:
        line.append('{} ({})'.format(url, status) if url else '', color=color)
      else:
        line.append(url or '', color=color)

    # The subject of the most recent commit on the branch.
    if self.show_subject:
      line.append(run('log', '-n1', '--format=%s', branch, '--'))

    self.output.append(line)

    for child in sorted(self.__parent_map.pop(branch, ())):
      self.__append_branch(child, depth=depth + 1)
예제 #47
0
 def run_git(self, *cmd, **kwargs):
   print 'Running: git %s' % (' '.join(pipes.quote(x) for x in cmd))
   if self.options.dry_run:
     return ''
   return git_common.run(*cmd, **kwargs)
예제 #48
0
def main(args=None):
  parser = argparse.ArgumentParser()
  parser.add_argument('--verbose', '-v', action='store_true')
  parser.add_argument('--keep-going', '-k', action='store_true',
                      help='Keep processing past failed rebases.')
  parser.add_argument('--no_fetch', '--no-fetch', '-n',
                      action='store_true',
                      help='Skip fetching remotes.')
  opts = parser.parse_args(args)

  if opts.verbose:  # pragma: no cover
    logging.getLogger().setLevel(logging.DEBUG)

  # TODO(iannucci): snapshot all branches somehow, so we can implement
  #                 `git rebase-update --undo`.
  #   * Perhaps just copy packed-refs + refs/ + logs/ to the side?
  #     * commit them to a secret ref?
  #       * Then we could view a summary of each run as a
  #         `diff --stat` on that secret ref.

  if git.in_rebase():
    # TODO(iannucci): Be able to resume rebase with flags like --continue,
    # etc.
    print (
      'Rebase in progress. Please complete the rebase before running '
      '`git rebase-update`.'
    )
    return 1

  return_branch, return_workdir = find_return_branch_workdir()
  os.chdir(git.run('rev-parse', '--show-toplevel'))

  if git.current_branch() == 'HEAD':
    if git.run('status', '--porcelain'):
      print 'Cannot rebase-update with detached head + uncommitted changes.'
      return 1
  else:
    git.freeze()  # just in case there are any local changes.

  skipped, branch_tree = git.get_branch_tree()
  for branch in skipped:
    print 'Skipping %s: No upstream specified' % branch

  if not opts.no_fetch:
    fetch_remotes(branch_tree)

  merge_base = {}
  for branch, parent in branch_tree.iteritems():
    merge_base[branch] = git.get_or_create_merge_base(branch, parent)

  logging.debug('branch_tree: %s' % pformat(branch_tree))
  logging.debug('merge_base: %s' % pformat(merge_base))

  retcode = 0
  unrebased_branches = []
  # Rebase each branch starting with the root-most branches and working
  # towards the leaves.
  for branch, parent in git.topo_iter(branch_tree):
    if git.is_dormant(branch):
      print 'Skipping dormant branch', branch
    else:
      ret = rebase_branch(branch, parent, merge_base[branch])
      if not ret:
        retcode = 1

        if opts.keep_going:
          print '--keep-going set, continuing with next branch.'
          unrebased_branches.append(branch)
          if git.in_rebase():
            git.run_with_retcode('rebase', '--abort')
          if git.in_rebase():  # pragma: no cover
            print 'Failed to abort rebase. Something is really wrong.'
            break
        else:
          break

  if unrebased_branches:
    print
    print 'The following branches could not be cleanly rebased:'
    for branch in unrebased_branches:
      print '  %s' % branch

  if not retcode:
    remove_empty_branches(branch_tree)

    # return_branch may not be there any more.
    if return_branch in git.branches():
      git.run('checkout', return_branch)
      git.thaw()
    else:
      root_branch = git.root()
      if return_branch != 'HEAD':
        print (
          "%r was merged with its parent, checking out %r instead."
          % (return_branch, root_branch)
        )
      git.run('checkout', root_branch)
    if return_workdir:
      os.chdir(return_workdir)
    git.set_config(STARTING_BRANCH_KEY, '')
    git.set_config(STARTING_WORKDIR_KEY, '')

  return retcode
예제 #49
0
def rebase_branch(branch, parent, start_hash):
  logging.debug('considering %s(%s) -> %s(%s) : %s',
                branch, git.hash_one(branch), parent, git.hash_one(parent),
                start_hash)

  # If parent has FROZEN commits, don't base branch on top of them. Instead,
  # base branch on top of whatever commit is before them.
  back_ups = 0
  orig_parent = parent
  while git.run('log', '-n1', '--format=%s',
                parent, '--').startswith(git.FREEZE):
    back_ups += 1
    parent = git.run('rev-parse', parent+'~')

  if back_ups:
    logging.debug('Backed parent up by %d from %s to %s',
                  back_ups, orig_parent, parent)

  if git.hash_one(parent) != start_hash:
    # Try a plain rebase first
    print 'Rebasing:', branch
    rebase_ret = git.rebase(parent, start_hash, branch, abort=True)
    if not rebase_ret.success:
      # TODO(iannucci): Find collapsible branches in a smarter way?
      print "Failed! Attempting to squash", branch, "...",
      squash_branch = branch+"_squash_attempt"
      git.run('checkout', '-b', squash_branch)
      git.squash_current_branch(merge_base=start_hash)

      # Try to rebase the branch_squash_attempt branch to see if it's empty.
      squash_ret = git.rebase(parent, start_hash, squash_branch, abort=True)
      empty_rebase = git.hash_one(squash_branch) == git.hash_one(parent)
      git.run('checkout', branch)
      git.run('branch', '-D', squash_branch)
      if squash_ret.success and empty_rebase:
        print 'Success!'
        git.squash_current_branch(merge_base=start_hash)
        git.rebase(parent, start_hash, branch)
      else:
        print "Failed!"
        print

        # rebase and leave in mid-rebase state.
        # This second rebase attempt should always fail in the same
        # way that the first one does.  If it magically succeeds then
        # something very strange has happened.
        second_rebase_ret = git.rebase(parent, start_hash, branch)
        if second_rebase_ret.success: # pragma: no cover
          print "Second rebase succeeded unexpectedly!"
          print "Please see: http://crbug.com/425696"
          print "First rebased failed with:"
          print rebase_ret.stderr
        else:
          print "Here's what git-rebase (squashed) had to say:"
          print
          print squash_ret.stdout
          print squash_ret.stderr
          print textwrap.dedent(
          """\
          Squashing failed. You probably have a real merge conflict.

          Your working copy is in mid-rebase. Either:
           * completely resolve like a normal git-rebase; OR
           * abort the rebase and mark this branch as dormant:
                 git config branch.%s.dormant true

          And then run `git rebase-update` again to resume.
          """ % branch)
          return False
  else:
    print '%s up-to-date' % branch

  git.remove_merge_base(branch)
  git.get_or_create_merge_base(branch)

  return True
def GetNameForCommit(sha1):
  return re.sub(r'~.*$', '', git.run('name-rev', '--tags', '--name-only', sha1))
def GetMergesForCommit(sha1):
  return [c.split()[0] for c in
          git.run('log', '--oneline', '-F', '--all', '--no-abbrev', '--grep',
                  'cherry picked from commit %s' % sha1).splitlines()]
예제 #52
0
  def __append_branch(self, branch, depth=0):
    """Recurses through the tree structure and appends an OutputLine to the
    OutputManager for each branch."""
    branch_info = self.__branches_info[branch]
    if branch_info:
      branch_hash = branch_info.hash
    else:
      try:
        branch_hash = hash_one(branch, short=True)
      except subprocess2.CalledProcessError:
        branch_hash = None

    line = OutputLine()

    # The branch name with appropriate indentation.
    suffix = ''
    if branch == self.__current_branch or (
        self.__current_branch == 'HEAD' and branch == self.__current_hash):
      suffix = ' *'
    branch_string = branch
    if branch in self.__gone_branches:
      branch_string = '{%s:GONE}' % branch
    if not branch:
      branch_string = '{NO_UPSTREAM}'
    main_string = '  ' * depth + branch_string + suffix
    line.append(
        main_string,
        color=self.__color_for_branch(branch, branch_hash))

    # The branch hash.
    if self.verbosity >= 2:
      line.append(branch_hash or '', separator=' ', color=Fore.RED)

    # The branch tracking status.
    if self.verbosity >= 1:
      ahead_string = ''
      behind_string = ''
      front_separator = ''
      center_separator = ''
      back_separator = ''
      if branch_info and not self.__is_invalid_parent(branch_info.upstream):
        ahead = branch_info.ahead
        behind = branch_info.behind

        if ahead:
          ahead_string = 'ahead %d' % ahead
        if behind:
          behind_string = 'behind %d' % behind

        if ahead or behind:
          front_separator = '['
          back_separator = ']'

        if ahead and behind:
          center_separator = '|'

      line.append(front_separator, separator=' ')
      line.append(ahead_string, separator=' ', color=Fore.MAGENTA)
      line.append(center_separator, separator=' ')
      line.append(behind_string, separator=' ', color=Fore.MAGENTA)
      line.append(back_separator)

    # The Rietveld issue associated with the branch.
    if self.verbosity >= 2:
      none_text = '' if self.__is_invalid_parent(branch) else 'None'
      (url, color) = self.__status_info[branch]
      line.append(url or none_text, color=color)

    # The subject of the most recent commit on the branch.
    if self.show_subject:
      line.append(run('log', '-n1', '--format=%s', branch))

    self.output.append(line)

    for child in sorted(self.__parent_map.pop(branch, ())):
      self.__append_branch(child, depth=depth + 1)
예제 #53
0
def cherry_pick(target_branch, commit, auth_config):
  """Attempt to upload a cherry pick CL to rietveld.

  Args:
    target_branch: The branch to cherry pick onto.
    commit: The git hash of the commit to cherry pick.
    auth_config: auth.AuthConfig object with authentication configuration.
  """
  author = config('user.email')

  description = '%s\n\n(cherry picked from commit %s)\n' % (
      run('show', '--pretty=%B', '--quiet', commit), commit)

  parent = run('show', '--pretty=%P', '--quiet', commit)
  print 'Found parent revision:', parent

  class Options(object):
    def __init__(self):
      self.emulate_svn_auto_props = False

  content_type, payload = EncodeMultipartFormData([
      ('base', '%s@%s' % (Changelist().GetRemoteUrl(), target_branch)),
      ('cc', config('rietveld.cc')),
      ('content_upload', '1'),
      ('description', description),
      ('project', '%s@%s' % (config('rietveld.project'), target_branch)),
      ('subject', description.splitlines()[0]),
      ('user', author),
  ], [
      ('data', 'data.diff', GitVCS(Options()).PostProcessDiff(
          run('diff', parent, commit))),
  ])

  rietveld = Rietveld(config('rietveld.server'), auth_config, author)
  # pylint: disable=W0212
  output = rietveld._send(
    '/upload',
    payload=payload,
    content_type=content_type,
  ).splitlines()

  # If successful, output will look like:
  # Issue created. URL: https://codereview.chromium.org/1234567890
  # 1
  # 10001 some/path/first.file
  # 10002 some/path/second.file
  # 10003 some/path/third.file

  if output[0].startswith('Issue created. URL: '):
    print output[0]
    issue = output[0].rsplit('/', 1)[-1]
    patchset = output[1]
    files = output[2:]

    for f in files:
      file_id, filename = f.split()
      mode = 'M'

      try:
        content = run('show', '%s:%s' % (parent, filename))
      except subprocess2.CalledProcessError:
        # File didn't exist in the parent revision.
        content = ''
        mode = 'A'

      content_type, payload = EncodeMultipartFormData([
        ('checksum', md5.md5(content).hexdigest()),
        ('filename', filename),
        ('is_current', 'False'),
        ('status', mode),
      ], [
        ('data', filename, content),
      ])

      # pylint: disable=W0212
      print '  Uploading base file for %s:' % filename, rietveld._send(
        '/%s/upload_content/%s/%s' % (issue, patchset, file_id),
        payload=payload,
        content_type=content_type,
      )

      try:
        content = run('show', '%s:%s' % (commit, filename))
      except subprocess2.CalledProcessError:
        # File no longer exists in the new commit.
        content = ''
        mode = 'D'

      content_type, payload = EncodeMultipartFormData([
        ('checksum', md5.md5(content).hexdigest()),
        ('filename', filename),
        ('is_current', 'True'),
        ('status', mode),
      ], [
        ('data', filename, content),
      ])

      # pylint: disable=W0212
      print '  Uploading %s:' % filename, rietveld._send(
        '/%s/upload_content/%s/%s' % (issue, patchset, file_id),
        payload=payload,
        content_type=content_type,
      )

    # pylint: disable=W0212
    print 'Finalizing upload:', rietveld._send('/%s/upload_complete/1' % issue)
예제 #54
0
def EnsureInGitRepository():
  """Throws an exception if the current directory is not a git repository."""
  git.run('rev-parse')
예제 #55
0
def main(args):
  root_ref = root()

  parser = argparse.ArgumentParser()
  g = parser.add_mutually_exclusive_group()
  g.add_argument('new_parent', nargs='?',
                 help='New parent branch (or tag) to reparent to.')
  g.add_argument('--root', action='store_true',
                 help='Reparent to the configured root branch (%s).' % root_ref)
  g.add_argument('--lkgr', action='store_true',
                 help='Reparent to the lkgr tag.')
  opts = parser.parse_args(args)

  # TODO(iannucci): Allow specification of the branch-to-reparent

  branch = current_branch()

  if opts.root:
    new_parent = root_ref
  elif opts.lkgr:
    new_parent = 'lkgr'
  else:
    if not opts.new_parent:
      parser.error('Must specify new parent somehow')
    new_parent = opts.new_parent
  cur_parent = upstream(branch)

  if branch == 'HEAD' or not branch:
    parser.error('Must be on the branch you want to reparent')
  if new_parent == cur_parent:
    parser.error('Cannot reparent a branch to its existing parent')

  if not cur_parent:
    msg = (
      "Unable to determine %s@{upstream}.\n\nThis can happen if you didn't use "
      "`git new-branch` to create the branch and haven't used "
      "`git branch --set-upstream-to` to assign it one.\n\nPlease assign an "
      "upstream branch and then run this command again."
    )
    print(msg % branch, file=sys.stderr)
    return 1

  mbase = get_or_create_merge_base(branch, cur_parent)

  all_tags = tags()
  if cur_parent in all_tags:
    cur_parent += ' [tag]'

  try:
    run('show-ref', new_parent)
  except subprocess2.CalledProcessError:
    print('fatal: invalid reference: %s' % new_parent, file=sys.stderr)
    return 1

  if new_parent in all_tags:
    print("Reparenting %s to track %s [tag] (was %s)" % (branch, new_parent,
                                                         cur_parent))
    set_branch_config(branch, 'remote', '.')
    set_branch_config(branch, 'merge', new_parent)
  else:
    print("Reparenting %s to track %s (was %s)" % (branch, new_parent,
                                                   cur_parent))
    run('branch', '--set-upstream-to', new_parent, branch)

  manual_merge_base(branch, mbase, new_parent)

  # ONLY rebase-update the branch which moved (and dependants)
  _, branch_tree = get_branch_tree()
  branches = [branch]
  for branch, parent in topo_iter(branch_tree):
    if parent in branches:
      branches.append(branch)
  return git_rebase_update.main(['--no-fetch'] + branches)
예제 #56
0
def clear_caches(on_disk=False):
  """Clears in-process caches for e.g. unit testing."""
  get_number_tree.clear()
  get_num.clear()
  if on_disk:
    git.run('update-ref', '-d', REF)
예제 #57
0
def rebase_branch(branch, parent, start_hash):
    logging.debug('considering %s(%s) -> %s(%s) : %s', branch,
                  git.hash_one(branch), parent, git.hash_one(parent),
                  start_hash)

    # If parent has FROZEN commits, don't base branch on top of them. Instead,
    # base branch on top of whatever commit is before them.
    back_ups = 0
    orig_parent = parent
    while git.run('log', '-n1', '--format=%s', parent,
                  '--').startswith(git.FREEZE):
        back_ups += 1
        parent = git.run('rev-parse', parent + '~')

    if back_ups:
        logging.debug('Backed parent up by %d from %s to %s', back_ups,
                      orig_parent, parent)

    if git.hash_one(parent) != start_hash:
        # Try a plain rebase first
        print 'Rebasing:', branch
        rebase_ret = git.rebase(parent, start_hash, branch, abort=True)
        if not rebase_ret.success:
            # TODO(iannucci): Find collapsible branches in a smarter way?
            print "Failed! Attempting to squash", branch, "...",
            squash_branch = branch + "_squash_attempt"
            git.run('checkout', '-b', squash_branch)
            git.squash_current_branch(merge_base=start_hash)

            # Try to rebase the branch_squash_attempt branch to see if it's empty.
            squash_ret = git.rebase(parent,
                                    start_hash,
                                    squash_branch,
                                    abort=True)
            empty_rebase = git.hash_one(squash_branch) == git.hash_one(parent)
            git.run('checkout', branch)
            git.run('branch', '-D', squash_branch)
            if squash_ret.success and empty_rebase:
                print 'Success!'
                git.squash_current_branch(merge_base=start_hash)
                git.rebase(parent, start_hash, branch)
            else:
                print "Failed!"
                print

                # rebase and leave in mid-rebase state.
                # This second rebase attempt should always fail in the same
                # way that the first one does.  If it magically succeeds then
                # something very strange has happened.
                second_rebase_ret = git.rebase(parent, start_hash, branch)
                if second_rebase_ret.success:  # pragma: no cover
                    print "Second rebase succeeded unexpectedly!"
                    print "Please see: http://crbug.com/425696"
                    print "First rebased failed with:"
                    print rebase_ret.stderr
                else:
                    print "Here's what git-rebase (squashed) had to say:"
                    print
                    print squash_ret.stdout
                    print squash_ret.stderr
                    print textwrap.dedent("""\
          Squashing failed. You probably have a real merge conflict.

          Your working copy is in mid-rebase. Either:
           * completely resolve like a normal git-rebase; OR
           * abort the rebase and mark this branch as dormant:
                 git config branch.%s.dormant true

          And then run `git rebase-update` again to resume.
          """ % branch)
                    return False
    else:
        print '%s up-to-date' % branch

    git.remove_merge_base(branch)
    git.get_or_create_merge_base(branch)

    return True
예제 #58
0
def main(args=None):
    parser = argparse.ArgumentParser()
    parser.add_argument('--verbose', '-v', action='store_true')
    parser.add_argument('--no_fetch',
                        '--no-fetch',
                        '-n',
                        action='store_true',
                        help='Skip fetching remotes.')
    opts = parser.parse_args(args)

    if opts.verbose:  # pragma: no cover
        logging.getLogger().setLevel(logging.DEBUG)

    # TODO(iannucci): snapshot all branches somehow, so we can implement
    #                 `git rebase-update --undo`.
    #   * Perhaps just copy packed-refs + refs/ + logs/ to the side?
    #     * commit them to a secret ref?
    #       * Then we could view a summary of each run as a
    #         `diff --stat` on that secret ref.

    if git.in_rebase():
        # TODO(iannucci): Be able to resume rebase with flags like --continue,
        # etc.
        print(
            'Rebase in progress. Please complete the rebase before running '
            '`git rebase-update`.')
        return 1

    return_branch, return_workdir = find_return_branch_workdir()
    os.chdir(git.run('rev-parse', '--show-toplevel'))

    if git.current_branch() == 'HEAD':
        if git.run('status', '--porcelain'):
            print 'Cannot rebase-update with detached head + uncommitted changes.'
            return 1
    else:
        git.freeze()  # just in case there are any local changes.

    skipped, branch_tree = git.get_branch_tree()
    for branch in skipped:
        print 'Skipping %s: No upstream specified' % branch

    if not opts.no_fetch:
        fetch_remotes(branch_tree)

    merge_base = {}
    for branch, parent in branch_tree.iteritems():
        merge_base[branch] = git.get_or_create_merge_base(branch, parent)

    logging.debug('branch_tree: %s' % pformat(branch_tree))
    logging.debug('merge_base: %s' % pformat(merge_base))

    retcode = 0
    # Rebase each branch starting with the root-most branches and working
    # towards the leaves.
    for branch, parent in git.topo_iter(branch_tree):
        if git.is_dormant(branch):
            print 'Skipping dormant branch', branch
        else:
            ret = rebase_branch(branch, parent, merge_base[branch])
            if not ret:
                retcode = 1
                break

    if not retcode:
        remove_empty_branches(branch_tree)

        # return_branch may not be there any more.
        if return_branch in git.branches():
            git.run('checkout', return_branch)
            git.thaw()
        else:
            root_branch = git.root()
            if return_branch != 'HEAD':
                print(
                    "%r was merged with its parent, checking out %r instead." %
                    (return_branch, root_branch))
            git.run('checkout', root_branch)
        if return_workdir:
            os.chdir(return_workdir)
        git.set_config(STARTING_BRANCH_KEY, '')
        git.set_config(STARTING_WORKDIR_KEY, '')

    return retcode
예제 #59
0
def clear_caches(on_disk=False):
    """Clears in-process caches for e.g. unit testing."""
    get_number_tree.clear()
    get_num.clear()
    if on_disk:
        git.run('update-ref', '-d', REF)
예제 #60
0
def SplitCl(description_file, comment_file, changelist, cmd_upload, dry_run,
            cq_dry_run):
  """"Splits a branch into smaller branches and uploads CLs.

  Args:
    description_file: File containing the description of uploaded CLs.
    comment_file: File containing the comment of uploaded CLs.
    changelist: The Changelist class.
    cmd_upload: The function associated with the git cl upload command.
    dry_run: Whether this is a dry run (no branches or CLs created).
    cq_dry_run: If CL uploads should also do a cq dry run.

  Returns:
    0 in case of success. 1 in case of error.
  """
  description = AddUploadedByGitClSplitToDescription(ReadFile(description_file))
  comment = ReadFile(comment_file) if comment_file else None

  try:
    EnsureInGitRepository()

    cl = changelist()
    change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None)
    files = change.AffectedFiles()

    if not files:
      print 'Cannot split an empty CL.'
      return 1

    author = git.run('config', 'user.email').strip() or None
    refactor_branch = git.current_branch()
    assert refactor_branch, "Can't run from detached branch."
    refactor_branch_upstream = git.upstream(refactor_branch)
    assert refactor_branch_upstream, \
        "Branch %s must have an upstream." % refactor_branch

    owners_database = owners.Database(change.RepositoryRoot(), file, os.path)
    owners_database.load_data_needed_for([f.LocalPath() for f in files])

    files_split_by_owners = GetFilesSplitByOwners(owners_database, files)

    num_cls = len(files_split_by_owners)
    print('Will split current branch (' + refactor_branch + ') into ' +
          str(num_cls) + ' CLs.\n')
    if cq_dry_run and num_cls > CL_SPLIT_FORCE_LIMIT:
      print (
        'This will generate "%r" CLs. This many CLs can potentially generate'
        ' too much load on the build infrastructure. Please email'
        ' [email protected] to ensure that this won\'t  break anything.'
        ' The infra team reserves the right to cancel your jobs if they are'
        ' overloading the CQ.') % num_cls
      answer = raw_input('Proceed? (y/n):')
      if answer.lower() != 'y':
        return 0

    for cl_index, (directory, files) in \
        enumerate(files_split_by_owners.iteritems(), 1):
      # Use '/' as a path separator in the branch name and the CL description
      # and comment.
      directory = directory.replace(os.path.sep, '/')
      file_paths = [f.LocalPath() for f in files]
      reviewers = owners_database.reviewers_for(file_paths, author)

      if dry_run:
        PrintClInfo(cl_index, num_cls, directory, file_paths, description,
                    reviewers)
      else:
        UploadCl(refactor_branch, refactor_branch_upstream, directory, files,
                 description, comment, reviewers, changelist, cmd_upload,
                 cq_dry_run)

    # Go back to the original branch.
    git.run('checkout', refactor_branch)

  except subprocess2.CalledProcessError as cpe:
    sys.stderr.write(cpe.stderr)
    return 1
  return 0