[PATCH] [RFC] Core support for modules (external repos)

Bryan O'Sullivan bos at serpentine.com
Mon Apr 14 17:00:47 CDT 2008


# HG changeset patch
# User Bryan O'Sullivan <bos at serpentine.com>
# Date 1208210415 25200
# Node ID fb937291c81e030d72734745b74da745e9cfc9be
# Parent  4de599e16c27964ad12c227b5dee1f41cb2ff671
modules: basic support in the working directory update machinery

diff -r 4de599e16c27 -r fb937291c81e mercurial/context.py
--- a/mercurial/context.py	Mon Apr 14 14:33:47 2008 -0700
+++ b/mercurial/context.py	Mon Apr 14 15:00:15 2008 -0700
@@ -510,6 +510,8 @@
     def unknown(self): return self._status[4]
     def clean(self): return self._status[5]
     def branch(self): return self._repo.dirstate.branch()
+    def modulenames(self): return self._repo.dirstate.modulenames()
+    def modules(self): return self._repo.dirstate.modules()
 
     def tags(self):
         t = []
diff -r 4de599e16c27 -r fb937291c81e mercurial/dirstate.py
--- a/mercurial/dirstate.py	Mon Apr 14 14:33:47 2008 -0700
+++ b/mercurial/dirstate.py	Mon Apr 14 15:00:15 2008 -0700
@@ -11,6 +11,7 @@
 from i18n import _
 import struct, os, bisect, stat, strutil, util, errno, ignore
 import cStringIO, osutil, sys
+import module
 
 _unknown = ('?', 0, 0, 0)
 _format = ">cllll"
@@ -635,3 +636,26 @@
 
         return (lookup, modified, added, removed, deleted, unknown, ignored,
                 clean)
+
+    def modulenames(self):
+        ms = []
+        for d in self._dirs.iterkeys():
+            if d.startswith('.hgmodules/'):
+                if not os.path.isdir(self._join(util.pconvert(d))):
+                    continue
+                f = d[11:]
+                if f in self._dirs or f in self._map:
+                    self._ui.warn(
+                        _('skipping module that conflicts with dirstate: %s\n'
+                          '(use "hg module remove" or "hg module '
+                          'rename" to remove module,\n'
+                          ' "hg remove" or "hg rename" to remove '
+                          'files or directories)\n') % f)
+                else:
+                    ms.append(f)
+        ms.sort()
+        return ms
+        
+    def modules(self):
+        for m in self.modulenames():
+            yield module.module(self._ui, self._root, m)
diff -r 4de599e16c27 -r fb937291c81e mercurial/hg.py
--- a/mercurial/hg.py	Mon Apr 14 14:33:47 2008 -0700
+++ b/mercurial/hg.py	Mon Apr 14 15:00:15 2008 -0700
@@ -243,10 +243,12 @@
             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:
-                    checkout = update
+                    checkout = dest_repo.lookup(update)
                 elif not checkout:
                     try:
                         checkout = dest_repo.lookup("default")
@@ -259,12 +261,19 @@
         del src_lock, dest_lock, dir_cleanup
 
 def _showstats(repo, stats):
+    unrefmodules = stats[4]
     stats = ((stats[0], _("updated")),
              (stats[1], _("merged")),
              (stats[2], _("removed")),
              (stats[3], _("unresolved")))
     note = ", ".join([_("%d files %s") % s for s in stats])
     repo.ui.status("%s\n" % note)
+    if unrefmodules:
+        repo.ui.status(_('these modules are not used by this '
+                         'revision, and can be deleted:\n'))
+        for m in unrefmodules:
+            repo.ui.status('  %s\n' % m)
+        repo.ui.status(_('(for more information, see "hg help module")\n'))
 
 def _update(repo, node): return update(repo, node)
 
@@ -275,12 +284,16 @@
     _showstats(repo, stats)
     if stats[3]:
         repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
+    for m in repo.workingctx().modules():
+        m.update()
     return stats[3] > 0
 
 def clean(repo, node, show_stats=True):
     """forcibly switch the working directory to node, clobbering changes"""
     stats = _merge.update(repo, node, False, True, None)
     if show_stats: _showstats(repo, stats)
+    for m in repo.workingctx().modules():
+        m.update(updatefn=clean)
     return stats[3] > 0
 
 def merge(repo, node, force=None, remind=True):
diff -r 4de599e16c27 -r fb937291c81e mercurial/merge.py
--- a/mercurial/merge.py	Mon Apr 14 14:33:47 2008 -0700
+++ b/mercurial/merge.py	Mon Apr 14 15:00:15 2008 -0700
@@ -410,6 +410,7 @@
     wlock = repo.wlock()
     try:
         wc = repo.workingctx()
+        oldmodules = dict.fromkeys(wc.modulenames())
         if node is None:
             # tip of current branch
             try:
@@ -479,7 +480,15 @@
             if not branchmerge and not fastforward:
                 repo.dirstate.setbranch(p2.branch())
             repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
-
-        return stats
     finally:
         del wlock
+
+    # don't update modules until after the dirstate has been
+    # invalidated
+    for m in repo.workingctx().modulenames():
+        oldmodules.pop(m, None)
+
+    oldmodules = oldmodules.keys()
+    oldmodules.sort()
+
+    return stats + (oldmodules,)
diff -r 4de599e16c27 -r fb937291c81e mercurial/module.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/module.py	Mon Apr 14 15:00:15 2008 -0700
@@ -0,0 +1,116 @@
+"""
+module.py - module tracking for mercurial
+
+Copyright 2008 Matt Mackall <mpm at selenic.com> and others
+
+This software may be used and distributed according to the terms
+of the GNU General Public License, incorporated herein by reference.
+"""
+
+from i18n import _
+from node import bin, nullid
+import hg, repo, util
+import os
+
+class module(object):
+    def __init__(self, ui, parent, relpath):
+        self._ui = ui
+        self._parent = parent
+        self.relpath = relpath
+        self._abspath = os.path.join(parent, '.hgmodules',
+                                     util.pconvert(relpath))
+        self._opener = util.opener(self._abspath)
+        self._repopath = os.path.join(parent, self.relpath)
+        self._repo = None
+
+    def _join(self, f):
+        return os.path.join(self._path, f)
+
+    def repo(self):
+        if self._repo is None:
+            self._repo = hg.repository(self._ui, self._repopath)
+        return self._repo
+
+    def _update(self, pull, updatefn):
+        myrepo = self.repo()
+        if self.related(myrepo):
+            rev = hg.parseurl(self.default())[1]
+            rev = rev and rev[0] or None
+            p1, p2 = myrepo.dirstate.parents()
+            if p2 != nullid:
+                self._ui.warn(_('not updating %s - in merge state\n') %
+                              self.relpath)
+                return
+            if rev:
+                try:
+                    if p1 == myrepo.lookup(rev):
+                        self._ui.status(_('no need to update %s\n') %
+                                        self.relpath)
+                        return
+                except repo.RepoError:
+                    if not pull:
+                        raise
+                self.pull(heads=[rev])
+            self._ui.status(_('updating %s\n') % self.relpath)
+            updatefn(myrepo, rev)
+        else:
+            self.ui.warn(_('FIXME repo IDs do not match\n'))
+
+    def clone(self):
+        def modulesource():
+            default = self.default()
+            url, rev = hg.parseurl(default)[:2]
+            rev = rev and rev[0] or None
+            if rev:
+                parentpath = self._ui.expandpath('default')
+                if parentpath != default:
+                    if self._ui.verbose:
+                        self._ui.status(
+                            _('checking %s for compatible %s repo\n') %
+                            (util.hidepassword(parentpath), self.relpath))
+                    try:
+                        parent = hg.repository(self._ui, parentpath)
+                        subrepo = hg.repository(self._ui,
+                                                parent.rjoin(self.relpath))
+                        if self.related(subrepo):
+                            subrepo.lookup(rev)
+                            return subrepo, default, rev
+                    except repo.RepoError, err:
+                        pass
+            return hg.repository(self._ui, url), default, rev
+
+        util.makedirs(os.path.dirname(self._repopath))
+        srcrepo, default, rev = modulesource()
+        self._ui.status(_('cloning %s from %s\n') %
+                        (self.relpath,
+                         util.hidepassword(hg.localpath(srcrepo.url()))))
+        if srcrepo.local():
+            self._repo = hg.clone(self._ui, srcrepo, self._repopath,
+                                  update=rev or True)[1]
+        else:
+            self._repo = hg.clone(self._ui, srcrepo, self._repopath,
+                                  rev=rev)[1]
+
+        
+    def update(self, pull=True, updatefn=hg.update):
+        if os.path.isdir(self._repopath):
+            self._update(pull, updatefn)
+        else:
+            self.clone()
+
+    def pull(self, remote=None, heads=None):
+        repo = self.repo()
+        source = repo.ui.expandpath(remote or 'default')
+        repo.ui.status(_('pulling into %s from %s\n') %
+                       (self.relpath, util.hidepassword(source)))
+        source = hg.repository(self._ui, source)
+        return repo.pull(source, heads=heads and map(source.lookup, heads))
+
+    def default(self):
+        return self._opener('default').read().rstrip()
+
+    def repoid(self):
+        return bin(self._opener('repoid').read().rstrip())
+
+    def related(self, repo):
+        return repo.repoid() == self.repoid()
diff -r 4de599e16c27 -r fb937291c81e mercurial/repo.py
--- a/mercurial/repo.py	Mon Apr 14 14:33:47 2008 -0700
+++ b/mercurial/repo.py	Mon Apr 14 15:00:15 2008 -0700
@@ -46,3 +46,6 @@
         if url.endswith('/'):
             return url + path
         return url + '/' + path
+
+    def repoid(self):
+        return self.lookup('0')


More information about the Mercurial-devel mailing list