Example #1
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)
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)
Example #3
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))
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)
Example #5
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 start(self):
    self.__branches_info = get_branches_info(
        include_tracking_status=self.verbosity >= 1)
    roots = set()

    # A map of parents to a list of their children.
    for branch, branch_info in self.__branches_info.iteritems():
      if not branch_info:
        continue

      parent = branch_info.upstream
      if parent and not self.__branches_info[parent]:
        branch_upstream = upstream(branch)
        # If git can't find the upstream, mark the upstream as gone.
        if branch_upstream:
          parent = branch_upstream
        else:
          self.__gone_branches.add(parent)
        # A parent that isn't in the branches info is a root.
        roots.add(parent)

      self.__parent_map[parent].append(branch)

    self.__current_branch = current_branch()
    self.__current_hash = hash_one('HEAD', short=True)
    self.__tag_set = tags()

    if roots:
      for root in sorted(roots):
        self.__append_branch(root)
    else:
      no_branches = OutputLine()
      no_branches.append('No User Branches')
      self.output.append(no_branches)
Example #7
0
    def start(self):
        self.__branches_info = get_branches_info(
            include_tracking_status=self.verbosity >= 1)
        roots = set()

        # A map of parents to a list of their children.
        for branch, branch_info in self.__branches_info.iteritems():
            if not branch_info:
                continue

            parent = branch_info.upstream
            if not self.__branches_info[parent]:
                branch_upstream = upstream(branch)
                # If git can't find the upstream, mark the upstream as gone.
                if branch_upstream:
                    parent = branch_upstream
                else:
                    self.__gone_branches.add(parent)
                # A parent that isn't in the branches info is a root.
                roots.add(parent)

            self.__parent_map[parent].append(branch)

        self.__current_branch = current_branch()
        self.__current_hash = hash_one('HEAD', short=True)
        self.__tag_set = tags()

        if roots:
            for root in sorted(roots):
                self.__append_branch(root)
        else:
            no_branches = OutputLine()
            no_branches.append('No User Branches')
            self.output.append(no_branches)
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 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
Example #10
0
def main(argv):
    colorama.init()
    assert len(argv) == 1, "No arguments expected"
    branch_map = {}
    par_map = collections.defaultdict(list)
    for branch in branches():
        par = upstream(branch) or NO_UPSTREAM
        branch_map[branch] = par
        par_map[par].append(branch)

    current = current_branch()
    hashes = hash_multi(current, *branch_map.keys())
    current_hash = hashes[0]
    par_hashes = {
        k: hashes[i + 1]
        for i, k in enumerate(branch_map.iterkeys())
    }
    par_hashes[NO_UPSTREAM] = 0
    tag_set = tags()
    while par_map:
        for parent in par_map:
            if parent not in branch_map:
                if parent not in par_hashes:
                    par_hashes[parent] = hash_one(parent)
                print_branch(current, current_hash, parent, par_hashes,
                             par_map, branch_map, tag_set)
                break
Example #11
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 start(self):
        self.__branches_info = get_branches_info(
            include_tracking_status=self.verbosity >= 1)
        if (self.verbosity >= 2):
            # Avoid heavy import unless necessary.
            from git_cl import get_cl_statuses, color_for_status, Changelist

            change_cls = [
                Changelist(branchref='refs/heads/' + b)
                for b in self.__branches_info.keys() if b
            ]
            status_info = get_cl_statuses(change_cls,
                                          fine_grained=self.verbosity > 2,
                                          max_processes=self.maxjobs)

            # This is a blocking get which waits for the remote CL status to be
            # retrieved.
            for cl, status in status_info:
                self.__status_info[cl.GetBranch()] = (cl.GetIssueURL(),
                                                      color_for_status(status),
                                                      status)

        roots = set()

        # A map of parents to a list of their children.
        for branch, branch_info in self.__branches_info.items():
            if not branch_info:
                continue

            parent = branch_info.upstream
            if self.__check_cycle(branch):
                continue
            if not self.__branches_info[parent]:
                branch_upstream = upstream(branch)
                # If git can't find the upstream, mark the upstream as gone.
                if branch_upstream:
                    parent = branch_upstream
                else:
                    self.__gone_branches.add(parent)
                # A parent that isn't in the branches info is a root.
                roots.add(parent)

            self.__parent_map[parent].append(branch)

        self.__current_branch = current_branch()
        self.__current_hash = hash_one('HEAD', short=True)
        self.__tag_set = tags()

        if roots:
            for root in sorted(roots):
                self.__append_branch(root)
        else:
            no_branches = OutputLine()
            no_branches.append('No User Branches')
            self.output.append(no_branches)
  def start(self):
    self.__branches_info = get_branches_info(
        include_tracking_status=self.verbosity >= 1)
    if (self.verbosity >= 2):
      # Avoid heavy import unless necessary.
      from git_cl import get_cl_statuses, color_for_status, Changelist

      change_cls = [Changelist(branchref='refs/heads/'+b)
                    for b in self.__branches_info.keys() if b]
      status_info = get_cl_statuses(change_cls,
                                    fine_grained=self.verbosity > 2,
                                    max_processes=self.maxjobs)

      # This is a blocking get which waits for the remote CL status to be
      # retrieved.
      for cl, status in status_info:
        self.__status_info[cl.GetBranch()] = (cl.GetIssueURL(),
                                              color_for_status(status),
                                              status)

    roots = set()

    # A map of parents to a list of their children.
    for branch, branch_info in self.__branches_info.iteritems():
      if not branch_info:
        continue

      parent = branch_info.upstream
      if not self.__branches_info[parent]:
        branch_upstream = upstream(branch)
        # If git can't find the upstream, mark the upstream as gone.
        if branch_upstream:
          parent = branch_upstream
        else:
          self.__gone_branches.add(parent)
        # A parent that isn't in the branches info is a root.
        roots.add(parent)

      self.__parent_map[parent].append(branch)

    self.__current_branch = current_branch()
    self.__current_hash = hash_one('HEAD', short=True)
    self.__tag_set = tags()

    if roots:
      for root in sorted(roots):
        self.__append_branch(root)
    else:
      no_branches = OutputLine()
      no_branches.append('No User Branches')
      self.output.append(no_branches)
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, stdout=sys.stdout, stderr=sys.stderr):
  parser = argparse.ArgumentParser(
      prog='git hyper-blame',
      description='git blame with support for ignoring certain commits.')
  parser.add_argument('-i', metavar='REVISION', action='append', dest='ignored',
                      default=[], help='a revision to ignore')
  parser.add_argument('--ignore-file', metavar='FILE',
                      type=argparse.FileType('r'), dest='ignore_file',
                      help='a file containing a list of revisions to ignore')
  parser.add_argument('--no-default-ignores', dest='no_default_ignores',
                      action='store_true',
                      help='Do not ignore commits from .git-blame-ignore-revs.')
  parser.add_argument('revision', nargs='?', default='HEAD', metavar='REVISION',
                      help='revision to look at')
  parser.add_argument('filename', metavar='FILE', help='filename to blame')

  args = parser.parse_args(args)
  try:
    repo_root = git_common.repo_root()
  except subprocess2.CalledProcessError as e:
    stderr.write(e.stderr)
    return e.returncode

  # Make filename relative to the repository root, and cd to the root dir (so
  # all filenames throughout this script are relative to the root).
  filename = os.path.relpath(args.filename, repo_root)
  os.chdir(repo_root)

  # Normalize filename so we can compare it to other filenames git gives us.
  filename = os.path.normpath(filename)
  filename = os.path.normcase(filename)

  ignored_list = list(args.ignored)
  if not args.no_default_ignores and os.path.exists(DEFAULT_IGNORE_FILE_NAME):
    with open(DEFAULT_IGNORE_FILE_NAME) as ignore_file:
      ignored_list.extend(parse_ignore_file(ignore_file))

  if args.ignore_file:
    ignored_list.extend(parse_ignore_file(args.ignore_file))

  ignored = set()
  for c in ignored_list:
    try:
      ignored.add(git_common.hash_one(c))
    except subprocess2.CalledProcessError as e:
      # Custom warning string (the message from git-rev-parse is inappropriate).
      stderr.write('warning: unknown revision \'%s\'.\n' % c)

  return hyper_blame(ignored, filename, args.revision, out=stdout, err=stderr)
Example #16
0
def main(args, stdout=sys.stdout, stderr=sys.stderr):
  parser = argparse.ArgumentParser(
      prog='git hyper-blame',
      description='git blame with support for ignoring certain commits.')
  parser.add_argument('-i', metavar='REVISION', action='append', dest='ignored',
                      default=[], help='a revision to ignore')
  parser.add_argument('--ignore-file', metavar='FILE',
                      type=argparse.FileType('r'), dest='ignore_file',
                      help='a file containing a list of revisions to ignore')
  parser.add_argument('--no-default-ignores', dest='no_default_ignores',
                      action='store_true',
                      help='Do not ignore commits from .git-blame-ignore-revs.')
  parser.add_argument('revision', nargs='?', default='HEAD', metavar='REVISION',
                      help='revision to look at')
  parser.add_argument('filename', metavar='FILE', help='filename to blame')

  args = parser.parse_args(args)
  try:
    repo_root = git_common.repo_root()
  except subprocess2.CalledProcessError as e:
    stderr.write(e.stderr.decode())
    return e.returncode

  # Make filename relative to the repository root, and cd to the root dir (so
  # all filenames throughout this script are relative to the root).
  filename = os.path.relpath(args.filename, repo_root)
  os.chdir(repo_root)

  # Normalize filename so we can compare it to other filenames git gives us.
  filename = os.path.normpath(filename)
  filename = os.path.normcase(filename)

  ignored_list = list(args.ignored)
  if not args.no_default_ignores and os.path.exists(DEFAULT_IGNORE_FILE_NAME):
    with open(DEFAULT_IGNORE_FILE_NAME) as ignore_file:
      ignored_list.extend(parse_ignore_file(ignore_file))

  if args.ignore_file:
    ignored_list.extend(parse_ignore_file(args.ignore_file))

  ignored = set()
  for c in ignored_list:
    try:
      ignored.add(git_common.hash_one(c))
    except subprocess2.CalledProcessError as e:
      # Custom warning string (the message from git-rev-parse is inappropriate).
      stderr.write('warning: unknown revision \'%s\'.\n' % c)

  return hyper_blame(ignored, filename, args.revision, out=stdout, err=stderr)
Example #17
0
    def start(self):
        self.__branches_info = get_branches_info(
            include_tracking_status=self.verbosity >= 1)
        if (self.verbosity >= 2):
            # Avoid heavy import unless necessary.
            from git_cl import get_cl_statuses

            status_info = get_cl_statuses(self.__branches_info.keys(),
                                          fine_grained=self.verbosity > 2,
                                          max_processes=self.maxjobs)

            for _ in xrange(len(self.__branches_info)):
                # This is a blocking get which waits for the remote CL status to be
                # retrieved.
                (branch, url, color) = status_info.next()
                self.__status_info[branch] = (url, color)

        roots = set()

        # A map of parents to a list of their children.
        for branch, branch_info in self.__branches_info.iteritems():
            if not branch_info:
                continue

            parent = branch_info.upstream
            if not self.__branches_info[parent]:
                branch_upstream = upstream(branch)
                # If git can't find the upstream, mark the upstream as gone.
                if branch_upstream:
                    parent = branch_upstream
                else:
                    self.__gone_branches.add(parent)
                # A parent that isn't in the branches info is a root.
                roots.add(parent)

            self.__parent_map[parent].append(branch)

        self.__current_branch = current_branch()
        self.__current_hash = hash_one('HEAD', short=True)
        self.__tag_set = tags()

        if roots:
            for root in sorted(roots):
                self.__append_branch(root)
        else:
            no_branches = OutputLine()
            no_branches.append('No User Branches')
            self.output.append(no_branches)
Example #18
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:
        return "No downstream branches"
    elif len(downstreams) == 1:
        run_stream('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_stream('checkout',
                           downstreams[int(r)],
                           stdout=sys.stdout,
                           stderr=sys.stderr)
                break
Example #19
0
def main(argv):
    parser = argparse.ArgumentParser(
        description=__doc__.strip().splitlines()[0],
        epilog=' '.join(__doc__.strip().splitlines()[1:]))
    g = parser.add_mutually_exclusive_group()
    g.add_argument(
        'merge_base',
        nargs='?',
        help='The new hash to use as the merge base for the current branch')
    g.add_argument('--delete',
                   '-d',
                   action='store_true',
                   help='Remove the set mark.')
    opts = parser.parse_args(argv)

    cur = current_branch()

    if opts.delete:
        try:
            remove_merge_base(cur)
        except CalledProcessError:
            print "No merge base currently exists for %s." % cur
        return 0

    if opts.merge_base:
        try:
            opts.merge_base = hash_one(opts.merge_base)
        except CalledProcessError:
            print >> sys.stderr, ('fatal: could not resolve %s as a commit' %
                                  (opts.merge_base))
            return 1

        manual_merge_base(cur, opts.merge_base, upstream(cur))

    ret = 0
    actual = get_or_create_merge_base(cur)
    if opts.merge_base and opts.merge_base != actual:
        ret = 1
        print "Invalid merge_base %s" % opts.merge_base

    print "merge_base(%s): %s" % (cur, actual)

    return ret
def main(argv):
  parser = argparse.ArgumentParser(
    description=__doc__.strip().splitlines()[0],
    epilog=' '.join(__doc__.strip().splitlines()[1:]))
  g = parser.add_mutually_exclusive_group()
  g.add_argument(
    'merge_base', nargs='?',
    help='The new hash to use as the merge base for the current branch'
  )
  g.add_argument('--delete', '-d', action='store_true',
                 help='Remove the set mark.')
  opts = parser.parse_args(argv)

  cur = current_branch()

  if opts.delete:
    try:
      remove_merge_base(cur)
    except CalledProcessError:
      print "No merge base currently exists for %s." % cur
    return 0

  if opts.merge_base:
    try:
      opts.merge_base = hash_one(opts.merge_base)
    except CalledProcessError:
      print >> sys.stderr, (
          'fatal: could not resolve %s as a commit' % (opts.merge_base)
      )
      return 1

    manual_merge_base(cur, opts.merge_base)

  ret = 0
  actual = get_or_create_merge_base(cur)
  if opts.merge_base and opts.merge_base != actual:
    ret = 1
    print "Invalid merge_base %s" % opts.merge_base

  print "merge_base(%s): %s" % (cur, actual)

  return ret
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
Example #22
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
Example #23
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
Example #24
0
def main(args, stdout=sys.stdout, stderr=sys.stderr):
  parser = argparse.ArgumentParser(
      prog='git hyper-blame',
      description='git blame with support for ignoring certain commits.')
  parser.add_argument('-i', metavar='REVISION', action='append', dest='ignored',
                      default=[], help='a revision to ignore')
  parser.add_argument('revision', nargs='?', default='HEAD', metavar='REVISION',
                      help='revision to look at')
  parser.add_argument('filename', metavar='FILE', help='filename to blame')

  args = parser.parse_args(args)
  try:
    repo_root = git_common.repo_root()
  except subprocess2.CalledProcessError as e:
    stderr.write(e.stderr)
    return e.returncode

  # Make filename relative to the repository root, and cd to the root dir (so
  # all filenames throughout this script are relative to the root).
  filename = os.path.relpath(args.filename, repo_root)
  os.chdir(repo_root)

  # Normalize filename so we can compare it to other filenames git gives us.
  filename = os.path.normpath(filename)
  filename = os.path.normcase(filename)

  ignored = set()
  for c in args.ignored:
    try:
      ignored.add(git_common.hash_one(c))
    except subprocess2.CalledProcessError as e:
      # Custom error message (the message from git-rev-parse is inappropriate).
      stderr.write('fatal: unknown revision \'%s\'.\n' % c)
      return e.returncode

  return hyper_blame(ignored, filename, args.revision, out=stdout, err=stderr)
def main(argv):
  colorama.init()
  assert len(argv) == 1, "No arguments expected"
  branch_map = {}
  par_map = collections.defaultdict(list)
  for branch in branches():
    par = upstream(branch) or NO_UPSTREAM
    branch_map[branch] = par
    par_map[par].append(branch)

  current = current_branch()
  hashes = hash_multi(current, *branch_map.keys())
  current_hash = hashes[0]
  par_hashes = {k: hashes[i+1] for i, k in enumerate(branch_map.iterkeys())}
  par_hashes[NO_UPSTREAM] = 0
  tag_set = tags()
  while par_map:
    for parent in par_map:
      if parent not in branch_map:
        if parent not in par_hashes:
          par_hashes[parent] = hash_one(parent)
        print_branch(current, current_hash, parent, par_hashes, par_map,
                     branch_map, tag_set)
        break
Example #26
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)
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
Example #28
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:
            import git_cl  # avoid heavy import cost unless we need it
            none_text = '' if self.__is_invalid_parent(branch) else 'None'
            url = git_cl.Changelist(
                branchref=branch).GetIssueURL() if branch_hash else None
            line.append(url or none_text,
                        color=Fore.BLUE if url else Fore.WHITE)

        self.output.append(line)

        for child in sorted(self.__parent_map.pop(branch, ())):
            self.__append_branch(child, depth=depth + 1)
Example #29
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)
 def _upfn(b):
     parent = upstream(b)
     if parent:
         return hash_one(parent)
Example #31
0
def hyper_blame(ignored, filename, revision='HEAD', out=sys.stdout,
                err=sys.stderr):
  # Map from commit to parsed blame from that commit.
  blame_from = {}

  def cache_blame_from(filename, commithash):
    try:
      return blame_from[commithash]
    except KeyError:
      parsed = get_parsed_blame(filename, commithash)
      blame_from[commithash] = parsed
      return parsed

  try:
    parsed = cache_blame_from(filename, git_common.hash_one(revision))
  except subprocess2.CalledProcessError as e:
    err.write(e.stderr)
    return e.returncode

  new_parsed = []

  # We don't show filenames in blame output unless we have to.
  show_filenames = False

  for line in parsed:
    # If a line references an ignored commit, blame that commit's parent
    # repeatedly until we find a non-ignored commit.
    while line.commit.commithash in ignored:
      if line.commit.previous is None:
        # You can't ignore the commit that added this file.
        break

      previouscommit, previousfilename = line.commit.previous.split(' ', 1)
      parent_blame = cache_blame_from(previousfilename, previouscommit)

      if len(parent_blame) == 0:
        # The previous version of this file was empty, therefore, you can't
        # ignore this commit.
        break

      # line.lineno_then is the line number in question at line.commit. We need
      # to translate that line number so that it refers to the position of the
      # same line on previouscommit.
      lineno_previous = approx_lineno_across_revs(
          line.commit.filename, previousfilename, line.commit.commithash,
          previouscommit, line.lineno_then)
      logging.debug('ignore commit %s on line p%d/t%d/n%d',
                    line.commit.commithash, lineno_previous, line.lineno_then,
                    line.lineno_now)

      # Get the line at lineno_previous in the parent commit.
      assert 1 <= lineno_previous <= len(parent_blame)
      newline = parent_blame[lineno_previous - 1]

      # Replace the commit and lineno_then, but not the lineno_now or context.
      logging.debug('    replacing with %r', newline)
      line = BlameLine(newline.commit, line.context, lineno_previous,
                       line.lineno_now, True)

    # If any line has a different filename to the file's current name, turn on
    # filename display for the entire blame output.
    if line.commit.filename != filename:
      show_filenames = True

    new_parsed.append(line)

  pretty_print(new_parsed, show_filenames=show_filenames, out=out)

  return 0
 def _upfn(b):
   parent = upstream(b)
   if parent:
     return hash_one(parent)
Example #33
0
def hyper_blame(outbuf, ignored, filename, revision):
    # Map from commit to parsed blame from that commit.
    blame_from = {}
    filename = os.path.normpath(filename)

    def cache_blame_from(filename, commithash):
        try:
            return blame_from[commithash]
        except KeyError:
            parsed = get_parsed_blame(filename, commithash)
            blame_from[commithash] = parsed
            return parsed

    try:
        parsed = cache_blame_from(filename, git_common.hash_one(revision))
    except subprocess2.CalledProcessError as e:
        sys.stderr.write(e.stderr.decode())
        return e.returncode

    new_parsed = []

    # We don't show filenames in blame output unless we have to.
    show_filenames = False

    for line in parsed:
        # If a line references an ignored commit, blame that commit's parent
        # repeatedly until we find a non-ignored commit.
        while line.commit.commithash in ignored:
            if line.commit.previous is None:
                # You can't ignore the commit that added this file.
                break

            previouscommit, previousfilename = line.commit.previous.split(
                ' ', 1)
            parent_blame = cache_blame_from(previousfilename, previouscommit)

            if len(parent_blame) == 0:
                # The previous version of this file was empty, therefore, you can't
                # ignore this commit.
                break

            # line.lineno_then is the line number in question at line.commit. We need
            # to translate that line number so that it refers to the position of the
            # same line on previouscommit.
            lineno_previous = approx_lineno_across_revs(
                line.commit.filename, previousfilename, line.commit.commithash,
                previouscommit, line.lineno_then)
            logging.debug('ignore commit %s on line p%d/t%d/n%d',
                          line.commit.commithash, lineno_previous,
                          line.lineno_then, line.lineno_now)

            # Get the line at lineno_previous in the parent commit.
            assert 1 <= lineno_previous <= len(parent_blame)
            newline = parent_blame[lineno_previous - 1]

            # Replace the commit and lineno_then, but not the lineno_now or context.
            line = BlameLine(newline.commit, line.context, newline.lineno_then,
                             line.lineno_now, True)
            logging.debug('    replacing with %r', line)

        # If any line has a different filename to the file's current name, turn on
        # filename display for the entire blame output.
        # Use normpath to make variable consistent across platforms.
        if os.path.normpath(line.commit.filename) != filename:
            show_filenames = True

        new_parsed.append(line)

    pretty_print(outbuf, new_parsed, show_filenames=show_filenames)

    return 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 __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:
      branch_hash = hash_one(branch, short=True)

    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:
      import git_cl  # avoid heavy import cost unless we need it
      none_text = '' if self.__is_invalid_parent(branch) else 'None'
      url = git_cl.Changelist(branchref=branch).GetIssueURL()
      line.append(url or none_text, color=Fore.BLUE if url else Fore.WHITE)

    self.output.append(line)

    for child in sorted(self.__parent_map.pop(branch, ())):
      self.__append_branch(child, depth=depth + 1)