diff mercurial/subrepo.py @ 13087:cca0779b4832

subrepo: lazily update git's local tracking branches This continues the strategy of separation between hg pull and hg update in git subrepos by only dealing with git's branches on an update. This behavior tries to cover the bare essentials of the semantics of git pull in the subrepo when the parent repo does hg pull and hg update.
author Eric Eisner <ede@mit.edu>
date Sun, 28 Nov 2010 17:19:23 -0500
parents 8db85e39d59c
children d0cbddfe3f4c
line wrap: on
line diff
--- a/mercurial/subrepo.py	Sun Nov 28 15:21:23 2010 -0500
+++ b/mercurial/subrepo.py	Sun Nov 28 17:19:23 2010 -0500
@@ -691,6 +691,19 @@
             rev2branch.setdefault(revision, []).append(branch)
         return current, branch2rev, rev2branch
 
+    def _gittracking(self, branches):
+        'return map of remote branch to local tracking branch'
+        # assumes no more than one local tracking branch for each remote
+        tracking = {}
+        for b in branches:
+            if b.startswith('remotes/'):
+                continue
+            remote = self._gitcommand(['config', 'branch.%s.remote' % b])
+            if remote:
+                ref = self._gitcommand(['config', 'branch.%s.merge' % b])
+                tracking['remotes/%s/%s' % (remote, ref.split('/')[-1])] = b
+        return tracking
+
     def _fetch(self, source, revision):
         if not os.path.exists('%s/.git' % self._path):
             self._ui.status(_('cloning subrepo %s\n') % self._relpath)
@@ -724,13 +737,17 @@
         elif self._gitstate() == revision:
             return
         current, branch2rev, rev2branch = self._gitbranchmap()
-        if revision not in rev2branch:
+
+        def rawcheckout():
             # no branch to checkout, check it out with no branch
             self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
                           self._relpath)
             self._ui.warn(_('check out a git branch if you intend '
                             'to make changes\n'))
             self._gitcommand(['checkout', '-q', revision])
+
+        if revision not in rev2branch:
+            rawcheckout()
             return
         branches = rev2branch[revision]
         firstlocalbranch = None
@@ -743,10 +760,34 @@
                 firstlocalbranch = b
         if firstlocalbranch:
             self._gitcommand(['checkout', firstlocalbranch])
-        else:
-            remote = branches[0]
+            return
+
+        tracking = self._gittracking(branch2rev.keys())
+        # choose a remote branch already tracked if possible
+        remote = branches[0]
+        if remote not in tracking:
+            for b in branches:
+                if b in tracking:
+                    remote = b
+                    break
+
+        if remote not in tracking:
+            # create a new local tracking branch
             local = remote.split('/')[-1]
             self._gitcommand(['checkout', '-b', local, remote])
+        elif self._gitisancestor(branch2rev[tracking[remote]], remote):
+            # When updating to a tracked remote branch,
+            # if the local tracking branch is downstream of it,
+            # a normal `git pull` would have performed a "fast-forward merge"
+            # which is equivalent to updating the local branch to the remote.
+            # Since we are only looking at branching at update, we need to
+            # detect this situation and perform this action lazily.
+            if tracking[remote] != current:
+                self._gitcommand(['checkout', tracking[remote]])
+            self._gitcommand(['merge', '--ff', remote])
+        else:
+            # a real merge would be required, just checkout the revision
+            rawcheckout()
 
     def commit(self, text, user, date):
         cmd = ['commit', '-a', '-m', text]