subrepo: add update/merge logic
authorMatt Mackall <mpm@selenic.com>
Mon, 15 Jun 2009 02:45:38 -0500
changeset 8814 ab668c92a036
parent 8813 db3c1ab0e632
child 8815 e87b0fc4750b
subrepo: add update/merge logic
mercurial/hg.py
mercurial/merge.py
mercurial/subrepo.py
--- a/mercurial/hg.py	Mon Jun 15 02:45:38 2009 -0500
+++ b/mercurial/hg.py	Mon Jun 15 02:45:38 2009 -0500
@@ -297,6 +297,8 @@
             fp.write("default = %s\n" % abspath)
             fp.close()
 
+            dest_repo.ui.setconfig('paths', 'default', abspath)
+
             if update:
                 dest_repo.ui.status(_("updating working directory\n"))
                 if update is not True:
--- a/mercurial/merge.py	Mon Jun 15 02:45:38 2009 -0500
+++ b/mercurial/merge.py	Mon Jun 15 02:45:38 2009 -0500
@@ -7,7 +7,7 @@
 
 from node import nullid, nullrev, hex, bin
 from i18n import _
-import util, filemerge, copies
+import util, filemerge, copies, subrepo
 import errno, os, shutil
 
 class mergestate(object):
@@ -241,12 +241,15 @@
     ms.reset(wctx.parents()[0].node())
     moves = []
     action.sort(key=actionkey)
+    substate = wctx.substate # prime
 
     # prescan for merges
     for a in action:
         f, m = a[:2]
         if m == 'm': # merge
             f2, fd, flags, move = a[2:]
+            if f == '.hgsubstate': # merged internally
+                continue
             repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd))
             fcl = wctx[f]
             fco = mctx[f2]
@@ -270,6 +273,8 @@
         if m == "r": # remove
             repo.ui.note(_("removing %s\n") % f)
             audit_path(f)
+            if f == '.hgsubstate': # subrepo states need updating
+                subrepo.submerge(repo, wctx, mctx, wctx)
             try:
                 util.unlink(repo.wjoin(f))
             except OSError, inst:
@@ -278,6 +283,9 @@
                                  (f, inst.strerror))
             removed += 1
         elif m == "m": # merge
+            if f == '.hgsubstate': # subrepo states need updating
+                subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
+                continue
             f2, fd, flags, move = a[2:]
             r = ms.resolve(fd, wctx, mctx)
             if r > 0:
@@ -297,6 +305,8 @@
             t = mctx.filectx(f).data()
             repo.wwrite(f, t, flags)
             updated += 1
+            if f == '.hgsubstate': # subrepo states need updating
+                subrepo.submerge(repo, wctx, mctx, wctx)
         elif m == "d": # directory rename
             f2, fd, flags = a[2:]
             if f:
--- a/mercurial/subrepo.py	Mon Jun 15 02:45:38 2009 -0500
+++ b/mercurial/subrepo.py	Mon Jun 15 02:45:38 2009 -0500
@@ -6,8 +6,9 @@
 # GNU General Public License version 2, incorporated herein by reference.
 
 import errno, os
+from i18n import _
 import config, util, node, error
-localrepo = None
+localrepo = hg = None
 
 nullstate = ('', '')
 
@@ -43,14 +44,81 @@
                 ''.join(['%s %s\n' % (state[s][1], s)
                          for s in sorted(state)]), '')
 
+def submerge(repo, wctx, mctx, actx):
+    if mctx == actx: # backwards?
+        actx = wctx.p1()
+    s1 = wctx.substate
+    s2 = mctx.substate
+    sa = actx.substate
+    sm = {}
+
+    for s, l in s1.items():
+        a = sa.get(s, nullstate)
+        if s in s2:
+            r = s2[s]
+            if l == r or r == a: # no change or local is newer
+                sm[s] = l
+                continue
+            elif l == a: # other side changed
+                wctx.sub(s).get(r)
+                sm[s] = r
+            elif l[0] != r[0]: # sources differ
+                if repo.ui.prompt(
+                    _(' subrepository sources for %s differ\n'
+                      'use (l)ocal source (%s) or (r)emote source (%s)?'
+                      % (s, l[0], r[0]),
+                      (_('&Local'), _('&Remote')), _('l'))) == _('r'):
+                    wctx.sub(s).get(r)
+                    sm[s] = r
+            elif l[1] == a[1]: # local side is unchanged
+                wctx.sub(s).get(r)
+                sm[s] = r
+            else:
+                wctx.sub(s).merge(r)
+                sm[s] = l
+        elif l == a: # remote removed, local unchanged
+            wctx.sub(s).remove()
+        else:
+            if repo.ui.prompt(
+                _(' local changed subrepository %s which remote removed\n'
+                  'use (c)hanged version or (d)elete?' % s,
+                  (_('&Changed'), _('&Delete')), _('c'))) == _('d'):
+                wctx.sub(s).remove()
+
+    for s, r in s2.items():
+        if s in s1:
+            continue
+        elif s not in sa:
+            wctx.sub(s).get(r)
+            sm[s] = r
+        elif r != sa[s]:
+            if repo.ui.prompt(
+                _(' remote changed subrepository %s which local removed\n'
+                  'use (c)hanged version or (d)elete?' % s,
+                  (_('&Changed'), _('&Delete')), _('c'))) == _('c'):
+                wctx.sub(s).get(r)
+                sm[s] = r
+
+    # record merged .hgsubstate
+    writestate(repo, sm)
+
+def _abssource(repo):
+    if hasattr(repo, '_subparent'):
+        source = repo._subsource
+        if source.startswith('/') or '://' in source:
+            return source
+        return os.path.join(_abssource(repo._subparent), repo._subsource)
+    return repo.ui.config('paths', 'default', repo.root)
+
 def subrepo(ctx, path):
     # subrepo inherently violates our import layering rules
     # because it wants to make repo objects from deep inside the stack
     # so we manually delay the circular imports to not break
     # scripts that don't use our demand-loading
-    global localrepo
-    import localrepo as l
+    global localrepo, hg
+    import localrepo as l, hg as h
     localrepo = l
+    hg = h
 
     state = ctx.substate.get(path, nullstate)
     if state[0].startswith('['): # future expansion
@@ -64,7 +132,13 @@
         self._state = state
         r = ctx._repo
         root = r.wjoin(path)
-        self._repo = localrepo.localrepository(r.ui, root)
+        if os.path.exists(os.path.join(root, '.hg')):
+            self._repo = localrepo.localrepository(r.ui, root)
+        else:
+            util.makedirs(root)
+            self._repo = localrepo.localrepository(r.ui, root, create=True)
+        self._repo._subparent = r
+        self._repo._subsource = state[0]
 
     def dirty(self):
         r = self._state[1]
@@ -80,3 +154,25 @@
         if not n:
             return self._repo['.'].hex() # different version checked out
         return node.hex(n)
+
+    def remove(self):
+        # we can't fully delete the repository as it may contain
+        # local-only history
+        self._repo.ui.note(_('removing subrepo %s\n') % self._path)
+        hg.clean(self._repo, node.nullid, False)
+
+    def get(self, state):
+        source, revision = state
+        try:
+            self._repo.lookup(revision)
+        except error.RepoError:
+            self._repo._subsource = source
+            self._repo.ui.status(_('pulling subrepo %s\n') % self._path)
+            srcurl = _abssource(self._repo)
+            other = hg.repository(self._repo.ui, srcurl)
+            self._repo.pull(other)
+
+        hg.clean(self._repo, revision, False)
+
+    def merge(self, state):
+        hg.merge(self._repo, state[1], remind=False)