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,31 @@ 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 + path s + source ../gitroot + revision 32a343883b74769118bb1d3b4b1fbf9156f4dddc + $ 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 + path s + source ../gitroot + revision 32a343883b74769118bb1d3b4b1fbf9156f4dddc + update to a revision without the subrepo, keeping the local git repository $ cd ../t @@ -230,7 +255,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 +264,3 @@ archive subrepos g gg ggg -