Make marked merge base invalid when the upstream changes.

This should let the base marker transparently work with plain-old-git tools
which was the idea in the first place. Specifically `git branch -u` without a
corresponding rebase.

R=agable@chromium.org
BUG=373977

Review URL: https://codereview.chromium.org/288323002

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@271112 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/git_common.py b/git_common.py
index 6952c1e..298ecc4 100644
--- a/git_common.py
+++ b/git_common.py
@@ -352,9 +352,16 @@
   If parent is supplied, it's used instead of calling upstream(branch).
   """
   base = branch_config(branch, 'base')
+  base_upstream = branch_config(branch, 'base-upstream')
   parent = parent or upstream(branch)
+  if not parent:
+    return None
   actual_merge_base = run('merge-base', parent, branch)
 
+  if base_upstream != parent:
+    base = None
+    base_upstream = None
+
   def is_ancestor(a, b):
     return run_with_retcode('merge-base', '--is-ancestor', a, b) == 0
 
@@ -370,7 +377,7 @@
 
   if not base:
     base = actual_merge_base
-    manual_merge_base(branch, base)
+    manual_merge_base(branch, base, parent)
 
   return base
 
@@ -409,8 +416,9 @@
   return branch_config(branch, 'dormant', 'false') != 'false'
 
 
-def manual_merge_base(branch, base):
+def manual_merge_base(branch, base, parent):
   set_branch_config(branch, 'base', base)
+  set_branch_config(branch, 'base-upstream', parent)
 
 
 def mktree(treedict):
@@ -475,6 +483,7 @@
 
 def remove_merge_base(branch):
   del_branch_config(branch, 'base')
+  del_branch_config(branch, 'base-upstream')
 
 
 def root():
diff --git a/git_map.py b/git_map.py
index 6fec89d..65814b9 100755
--- a/git_map.py
+++ b/git_map.py
@@ -50,6 +50,7 @@
   current = current_branch()
   all_branches = set(branches())
   merge_base_map = {b: get_or_create_merge_base(b) for b in all_branches}
+  merge_base_map = {b: v for b, v in merge_base_map.iteritems() if v}
   if current in all_branches:
     all_branches.remove(current)
   all_tags = set(tags())
diff --git a/git_mark_merge_base.py b/git_mark_merge_base.py
index cefb6df..673e2b4 100755
--- a/git_mark_merge_base.py
+++ b/git_mark_merge_base.py
@@ -17,7 +17,7 @@
 from subprocess2 import CalledProcessError
 
 from git_common import remove_merge_base, manual_merge_base, current_branch
-from git_common import get_or_create_merge_base, hash_one
+from git_common import get_or_create_merge_base, hash_one, upstream
 
 
 def main(argv):
@@ -51,7 +51,7 @@
       )
       return 1
 
-    manual_merge_base(cur, opts.merge_base)
+    manual_merge_base(cur, opts.merge_base, upstream(cur))
 
   ret = 0
   actual = get_or_create_merge_base(cur)
diff --git a/git_reparent_branch.py b/git_reparent_branch.py
index 9b7dace..f9bf9c5 100755
--- a/git_reparent_branch.py
+++ b/git_reparent_branch.py
@@ -11,7 +11,7 @@
 import subprocess2
 
 from git_common import upstream, current_branch, run, tags, set_branch_config
-from git_common import get_or_create_merge_base, root
+from git_common import get_or_create_merge_base, root, manual_merge_base
 
 import git_rebase_update
 
@@ -44,7 +44,7 @@
   if new_parent == cur_parent:
     parser.error('Cannot reparent a branch to its existing parent')
 
-  get_or_create_merge_base(branch, cur_parent)
+  mbase = get_or_create_merge_base(branch, cur_parent)
 
   all_tags = tags()
   if cur_parent in all_tags:
@@ -66,6 +66,8 @@
            % (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'])
 
diff --git a/tests/git_common_test.py b/tests/git_common_test.py
index 5695559..bba5b43 100755
--- a/tests/git_common_test.py
+++ b/tests/git_common_test.py
@@ -375,6 +375,8 @@
           J L
 
   X Y Z
+
+  CAT DOG
   """
 
   COMMIT_B = {'file': {'data': 'B'}}
@@ -396,18 +398,18 @@
     for i in xrange(30):
       self.repo.git('branch', 'a'*i)
 
-    with self.assertRaises(SystemExit):
-      self.repo.run(list, self.gc.branches())
+    _, rslt = self.repo.capture_stdio(list, self.gc.branches())
+    self.assertIn('too many branches (39/20)', rslt)
 
     self.repo.git('config', 'depot-tools.branch-limit', 'cat')
 
-    with self.assertRaises(SystemExit):
-      self.repo.run(list, self.gc.branches())
+    _, rslt = self.repo.capture_stdio(list, self.gc.branches())
+    self.assertIn('too many branches (39/20)', rslt)
 
     self.repo.git('config', 'depot-tools.branch-limit', '100')
 
     # should not raise
-    self.assertEqual(36, len(self.repo.run(list, self.gc.branches())))
+    self.assertEqual(38, len(self.repo.run(list, self.gc.branches())))
 
   def testMergeBase(self):
     self.repo.git('checkout', 'branch_K')
@@ -425,9 +427,12 @@
     self.assertEqual(
       self.repo['B'], self.repo.run(self.gc.config, 'branch.branch_K.base')
     )
+    self.assertEqual(
+      'branch_G', self.repo.run(self.gc.config, 'branch.branch_K.base-upstream')
+    )
 
     # deadbeef is a bad hash, so this will result in repo['B']
-    self.repo.run(self.gc.manual_merge_base, 'branch_K', 'deadbeef')
+    self.repo.run(self.gc.manual_merge_base, 'branch_K', 'deadbeef', 'branch_G')
 
     self.assertEqual(
       self.repo['B'],
@@ -435,7 +440,8 @@
     )
 
     # but if we pick a real ancestor, then it'll work
-    self.repo.run(self.gc.manual_merge_base, 'branch_K', self.repo['I'])
+    self.repo.run(self.gc.manual_merge_base, 'branch_K', self.repo['I'],
+                  'branch_G')
 
     self.assertEqual(
       self.repo['I'],
@@ -454,16 +460,28 @@
     self.assertEqual({}, self.repo.run(self.gc.branch_config_map, 'base'))
 
     # if it's too old, then it caps at merge-base
-    self.repo.run(self.gc.manual_merge_base, 'branch_K', self.repo['A'])
+    self.repo.run(self.gc.manual_merge_base, 'branch_K', self.repo['A'],
+                  'branch_G')
 
     self.assertEqual(
       self.repo['B'],
       self.repo.run(self.gc.get_or_create_merge_base, 'branch_K', 'branch_G')
     )
 
+    # If the user does --set-upstream-to something else, then we discard the
+    # base and recompute it.
+    self.repo.run(self.gc.run, 'branch', '-u', 'root_A')
+    self.assertEqual(
+      self.repo['A'],
+      self.repo.run(self.gc.get_or_create_merge_base, 'branch_K')
+    )
+
+    self.assertIsNone(
+      self.repo.run(self.gc.get_or_create_merge_base, 'branch_DOG'))
+
   def testGetBranchTree(self):
     skipped, tree = self.repo.run(self.gc.get_branch_tree)
-    self.assertEqual(skipped, {'master', 'root_X'})
+    self.assertEqual(skipped, {'master', 'root_X', 'branch_DOG', 'root_CAT'})
     self.assertEqual(tree, {
       'branch_G': 'root_A',
       'root_A': 'root_X',
@@ -517,6 +535,8 @@
             J L
 
     X Y Z
+
+    CAT DOG
     """)
 
     rslt = self.repo.run(
@@ -528,6 +548,8 @@
       B H I J L
 
     X Y Z
+
+    CAT DOG
     """)
 
     rslt = self.repo.run(
@@ -551,6 +573,8 @@
     A B C D E F G H I J K L
 
     X Y Z
+
+    CAT DOG
     """)