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
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)
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
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)
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
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
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
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
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'])
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()
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'])
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()
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))
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
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
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)
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() ]
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)
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)
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
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
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
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
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
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()
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)
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 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
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)
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
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()
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 {}
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
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 {}
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)
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
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
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
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)
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)
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
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()]
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)
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)
def EnsureInGitRepository(): """Throws an exception if the current directory is not a git repository.""" git.run('rev-parse')
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)
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)
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 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
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