[PATCH] Subrepository support
Alexander Solovyov
piranha at piranha.org.ua
Tue Feb 10 15:29:50 CST 2009
# HG changeset patch
# User Alexander Solovyov <piranha at piranha.org.ua>
# Date 1234301285 -7200
# Node ID 33ecfa1f6da2bc4e0826bd6f69acaa5d818e4a5b
# Parent 9294c0158c42ce8f8dd884e214386a29d91d6ca4
Subrepository support
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -2750,6 +2750,11 @@
if f in copy:
ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
+def sublist(ui, repo, **opts):
+ '''List all subrepositories with their desired revisions'''
+ for path, info in repo.subrepos().items():
+ ui.write(" %-25s %s\n" % (path, info[1]))
+
def tag(ui, repo, name1, *names, **opts):
"""add one or more tags for the current or given revision
@@ -3374,6 +3379,7 @@
('', 'rev', [], _('show difference from revision')),
] + walkopts,
_('[OPTION]... [FILE]...')),
+ "sublist": (sublist, []),
"tag":
(tag,
[('f', 'force', None, _('replace existing tag')),
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -10,7 +10,7 @@
from node import nullid
from i18n import _
import struct, os, stat, util, errno, ignore
-import cStringIO, osutil, sys, parsers
+import cStringIO, osutil, sys, parsers, error
_unknown = ('?', 0, 0, 0)
_format = ">cllll"
@@ -37,13 +37,14 @@
class dirstate(object):
- def __init__(self, opener, ui, root):
+ def __init__(self, opener, ui, root, repo):
self._opener = opener
self._root = root
self._rootdir = os.path.join(root, '')
self._dirty = False
self._dirtypl = False
self._ui = ui
+ self._repo = repo
def __getattr__(self, name):
if name == '_map':
@@ -333,7 +334,8 @@
self._droppath(f)
del self._map[f]
except KeyError:
- self._ui.warn(_("not in dirstate: %s\n") % f)
+ if not f in self._repo.subrepos():
+ self._ui.warn(_("not in dirstate: %s\n") % f)
def _normalize(self, path, knownpath=False):
norm_path = os.path.normcase(path)
@@ -562,7 +564,8 @@
state, mode, size, time = dmap[fn]
if not st and state in "nma":
- dadd(fn)
+ if fn not in self._repo.subrepos():
+ dadd(fn)
elif state == 'n':
if (size >= 0 and
(size != st.st_size
@@ -581,5 +584,13 @@
elif state == 'r':
radd(fn)
+ for path in self._repo.subrepos():
+ try:
+ r = self._repo.subrepo(path)
+ if not r['.'].hex() == self._repo.subrepos()[path][1]:
+ madd(path)
+ except error.RepoError:
+ radd(path)
+
return (lookup, modified, added, removed, deleted, unknown, ignored,
clean)
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -9,7 +9,7 @@
from i18n import _
import repo, changegroup
import changelog, dirstate, filelog, manifest, context, weakref
-import lock, transaction, stat, errno, ui, store
+import lock, transaction, stat, errno, ui, store, hg
import os, time, util, extensions, hook, inspect, error
import match as match_
import merge as merge_
@@ -84,6 +84,7 @@
self.filterpats = {}
self._datafilters = {}
self._transref = self._lockref = self._wlockref = None
+ self._subrepocache = {}
def __getattr__(self, name):
if name == 'changelog':
@@ -95,7 +96,7 @@
self.manifest = manifest.manifest(self.sopener)
return self.manifest
if name == 'dirstate':
- self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
+ self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root, self)
return self.dirstate
else:
raise AttributeError(name)
@@ -565,7 +566,9 @@
self._datafilters[name] = filter
def wread(self, filename):
- if self._link(filename):
+ if filename in self.subrepos():
+ data = self.subrepo(filename)['.'].hex() + '\n'
+ elif self._link(filename):
data = os.readlink(self.wjoin(filename))
else:
data = self.wopener(filename, 'r').read()
@@ -2139,6 +2142,63 @@
return self.stream_in(remote)
return self.pull(remote, heads)
+ def subrepos(self, rev=None, usecache=True):
+ '''Parse .hgsubrepos and return a dictionary
+
+ Subrepos are stored in the format:
+
+ # comment
+ path/to/workdir http://path.to/repo/ [optional modes]
+ # comment
+ another/workdir http://path.to/another/ [optional modes]
+
+ Revisions of subrepos are stored inside a parent repository
+ store as a text file containing revision in a hex format. This
+ file is placed exactly at subrepo path:
+
+ .hg/store/data/path/to/workdir.i
+
+ The parsed dictionary is cached until a write() operation is done.
+ '''
+ ctx = self[rev]
+ rev = ctx.rev()
+ if rev is None:
+ # use parent revision for working ctx
+ rev = str(ctx.parents()[0].rev()) + '+'
+ try:
+ if self._subrepocache.get(rev) is not None and usecache:
+ return self._subrepocache[rev]
+ self._subrepocache[rev] = {}
+ for line in ctx['.hgsubrepos'].data().splitlines():
+ if '#' in line:
+ line = line.split('#')[0]
+ line = line.strip().split()
+ if not line:
+ continue
+ path = line[0]
+ source = line[1]
+ modes = line[2:]
+ try:
+ node = ctx[path].data().strip()
+ except IOError:
+ node = ctx.parents()[0][path].data().strip()
+ except error.LookupError:
+ node = None
+ # path: source, rev, modes
+ self._subrepocache[rev][path] = (source, node, modes)
+ except:
+ pass
+ try:
+ return self._subrepocache[rev]
+ except KeyError:
+ return None
+
+ def subrepo(self, path, rev=None):
+ if not path in self.subrepos(rev):
+ raise error.RepoError('Subrepository %s unavailable' % path)
+ newui = ui.ui(parentui=self.ui, prepend='%s: ' % path)
+ return hg.repository(newui, self.wjoin(path))
+
# used to avoid circular references so destructors work
def aftertrans(files):
renamefiles = [tuple(t) for t in files]
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -304,7 +304,15 @@
f, m = a[:2]
if f and f[0] == "/":
continue
- if m == "r": # remove
+ if f and f in repo.subrepos():
+ import subrepo
+ if m == "r":
+ subrepo.subremove(repo, f)
+ elif m in "fg":
+ subrepo.subupdate(repo, f)
+ else:
+ util.Abort("Something new with %s: %s\n" % (f, m))
+ elif m == "r": # remove
repo.ui.note(_("removing %s\n") % f)
audit_path(f)
try:
diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
new file mode 100644
--- /dev/null
+++ b/mercurial/subrepo.py
@@ -0,0 +1,50 @@
+import os
+from i18n import _
+import hg, error, commands, util
+
+def subretrieve(repo, source, path, node):
+ '''Retrieve (clone/checkout) subrepository from a source.
+
+ Possibly use a cache (saved store) of subrepositories.'''
+ dest = repo.wjoin(path)
+ cache = repo.join('subcache/%s' % path)
+ if os.path.isdir(cache):
+ os.renames(cache, os.path.join(dest, '.hg'))
+ else:
+ repo.ui.status(_('%s: cloning from %s\n') % (path, source))
+ opts = dict(source=source, dest=dest, noupdate=True)
+ if node:
+ opts['rev'] = [node]
+ commands.clone(repo.ui, **opts)
+
+def subupdate(repo, path, rev=None):
+ '''Update subrepository to necessary revision.
+ '''
+ source, node, modes = repo.subrepos(rev)[path]
+
+ # clone if repo does not exist
+ try:
+ sub = repo.subrepo(path)
+ except error.RepoError:
+ subretrieve(repo, source, path, node)
+ sub = repo.subrepo(path, rev)
+
+ # pull if repo does not have necessary revision
+ try:
+ if node:
+ sub[node]
+ except error.RepoError:
+ src = hg.repository(sub.ui, source)
+ sub.pull(src, heads=[node])
+
+ hg.update(sub, node)
+
+def subremove(repo, path):
+ '''Remove subrepository, saving its store in a cache.
+ '''
+ try:
+ sub = repo.subrepo(path)
+ except error.RepoError:
+ return
+ hg.update(sub, 'null')
+ os.renames(sub.path, repo.join('subcache/%s' % path))
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -28,9 +28,10 @@
def __init__(self, verbose=False, debug=False, quiet=False,
interactive=True, traceback=False, report_untrusted=True,
- parentui=None):
+ parentui=None, prepend=''):
self.overlay = None
self.buffers = []
+ self.prepend = prepend
if parentui is None:
# this is the parent of all ui children
self.parentui = None
@@ -65,6 +66,10 @@
self.overlay = util.configparser()
updateconfig(parentui.overlay, self.overlay)
self.buffers = parentui.buffers
+ if parentui.prepend:
+ self.prepend = parentui.prepend
+ if prepend and prepend != self.prepend:
+ self.prepend += prepend
def __getattr__(self, key):
return getattr(self.parentui, key)
@@ -376,16 +381,16 @@
def write(self, *args):
if self.buffers:
- self.buffers[-1].extend([str(a) for a in args])
+ self.buffers[-1].extend([self.prepend+str(a) for a in args])
else:
for a in args:
- sys.stdout.write(str(a))
+ sys.stdout.write(self.prepend+str(a))
def write_err(self, *args):
try:
if not sys.stdout.closed: sys.stdout.flush()
for a in args:
- sys.stderr.write(str(a))
+ sys.stderr.write(self.prepend+str(a))
# stderr may be buffered under win32 when redirected to files,
# including stdout.
if not sys.stderr.closed: sys.stderr.flush()
More information about the Mercurial-devel
mailing list