[PATCH 3 of 3] subrepo: lazily update git's local tracking branches

Eric Eisner ede at MIT.EDU
Mon Nov 29 08:32:51 CST 2010


# HG changeset patch
# User Eric Eisner <ede at mit.edu>
# Date 1290982763 18000
# Node ID 4ed9da7ea5fbccfa7075237fdfddfb69bf6736b9
# Parent  e312f62cc053efb87b0ebc9c25a27e75da4759ba
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.

diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -691,6 +691,19 @@ class gitsubrepo(object):
             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 @@ class gitsubrepo(object):
         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 @@ class gitsubrepo(object):
                 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]
diff --git a/tests/test-subrepo-git.t b/tests/test-subrepo-git.t
--- a/tests/test-subrepo-git.t
+++ b/tests/test-subrepo-git.t
@@ -209,6 +209,25 @@ make and push changes to hg without upda
   adding file changes
   added 1 changesets with 1 changes to 1 files
 
+sync to upstream git, distribute changes
+
+  $ cd ../ta
+  $ hg pull -u -q
+  $ cd s
+  $ git pull -q
+  $ cd ..
+  $ hg commit -m 'git upstream sync'
+  committing subrepository $TESTTMP/ta/s
+  $ hg debugsub
+  $ hg push -q
+
+  $ cd ../tb
+  $ hg pull -q
+  $ hg update
+  pulling subrepo s
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg debugsub
+
 update to a revision without the subrepo, keeping the local git repository
 
   $ cd ../t
@@ -230,7 +249,7 @@ update to a revision without the subrepo
 archive subrepos
 
   $ cd ../t
-  $ hg archive --subrepos -r tip ../archive
+  $ hg archive --subrepos -r 5 ../archive
   pulling subrepo s
   $ cd ../archive
   $ cat s/f
@@ -239,4 +258,3 @@ archive subrepos
   g
   gg
   ggg
-


More information about the Mercurial-devel mailing list