1#!/usr/bin/env python
2# Copyright 2014 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Change the upstream of the current branch."""
7
8from __future__ import print_function
9
10import argparse
11import sys
12
13import subprocess2
14
15from git_common import upstream, current_branch, run, tags, set_branch_config
16from git_common import get_or_create_merge_base, root, manual_merge_base
17from git_common import get_branch_tree, topo_iter
18
19import git_rebase_update
20
21def main(args):
22  root_ref = root()
23
24  parser = argparse.ArgumentParser()
25  g = parser.add_mutually_exclusive_group()
26  g.add_argument('new_parent', nargs='?',
27                 help='New parent branch (or tag) to reparent to.')
28  g.add_argument('--root', action='store_true',
29                 help='Reparent to the configured root branch (%s).' % root_ref)
30  g.add_argument('--lkgr', action='store_true',
31                 help='Reparent to the lkgr tag.')
32  opts = parser.parse_args(args)
33
34  # TODO(iannucci): Allow specification of the branch-to-reparent
35
36  branch = current_branch()
37
38  if opts.root:
39    new_parent = root_ref
40  elif opts.lkgr:
41    new_parent = 'lkgr'
42  else:
43    if not opts.new_parent:
44      parser.error('Must specify new parent somehow')
45    new_parent = opts.new_parent
46  cur_parent = upstream(branch)
47
48  if branch == 'HEAD' or not branch:
49    parser.error('Must be on the branch you want to reparent')
50  if new_parent == cur_parent:
51    parser.error('Cannot reparent a branch to its existing parent')
52
53  if not cur_parent:
54    msg = (
55      "Unable to determine %s@{upstream}.\n\nThis can happen if you didn't use "
56      "`git new-branch` to create the branch and haven't used "
57      "`git branch --set-upstream-to` to assign it one.\n\nPlease assign an "
58      "upstream branch and then run this command again."
59    )
60    print(msg % branch, file=sys.stderr)
61    return 1
62
63  mbase = get_or_create_merge_base(branch, cur_parent)
64
65  all_tags = tags()
66  if cur_parent in all_tags:
67    cur_parent += ' [tag]'
68
69  try:
70    run('show-ref', new_parent)
71  except subprocess2.CalledProcessError:
72    print('fatal: invalid reference: %s' % new_parent, file=sys.stderr)
73    return 1
74
75  if new_parent in all_tags:
76    print("Reparenting %s to track %s [tag] (was %s)" % (branch, new_parent,
77                                                         cur_parent))
78    set_branch_config(branch, 'remote', '.')
79    set_branch_config(branch, 'merge', new_parent)
80  else:
81    print("Reparenting %s to track %s (was %s)" % (branch, new_parent,
82                                                   cur_parent))
83    run('branch', '--set-upstream-to', new_parent, branch)
84
85  manual_merge_base(branch, mbase, new_parent)
86
87  # ONLY rebase-update the branch which moved (and dependants)
88  _, branch_tree = get_branch_tree()
89  branches = [branch]
90  for branch, parent in topo_iter(branch_tree):
91    if parent in branches:
92      branches.append(branch)
93  return git_rebase_update.main(['--no-fetch'] + branches)
94
95
96if __name__ == '__main__':  # pragma: no cover
97  try:
98    sys.exit(main(sys.argv[1:]))
99  except KeyboardInterrupt:
100    sys.stderr.write('interrupted\n')
101    sys.exit(1)
102