[PATCH RFC] subrepo: Add the '--thin' option to pull a thin strand of changes
Alistair Bell
alistair.bell at netronome.com
Mon May 16 14:46:01 CDT 2011
# HG changeset patch
# User Alistair Bell <alistair.bell at netronome.com>
# Date 1305575046 14400
# Node ID 4d759579fb076f37cdf99d84ff59ceffeaefcf2a
# Parent 85c82ebc96a3611890b716f2ca5534cb92c6dbd5
subrepo: Add the '--thin' option to pull a thin strand of changes.
The idea of 'hg pull -r' or 'hg clone -r' is to pull a partial history of the
repo. An example may be to pull only those changes that are asserted to be
'good' from a 'dirty' repo to a 'clean' repo. Use of '--thin' extends this
functionality to subrepos. With 'hg update --thin' or 'hg pull -r <rev> -u
--thin' only the good data is pulled from subrepos, and other history is left
alone.
It could be argued that this should be the default behavior of 'hg pull -r' and
'hg clone -r' in the presence of subrepos and that the current behavior is a
bug, but for least surprise/backwards compatibility the current behavior is
retained as the default, with the good data only pulled with --thin.
diff -r 85c82ebc96a3 -r 4d759579fb07 mercurial/commands.py
--- a/mercurial/commands.py Mon May 16 13:06:48 2011 +0200
+++ b/mercurial/commands.py Mon May 16 15:44:06 2011 -0400
@@ -956,6 +956,7 @@
('r', 'rev', [], _('include the specified changeset'), _('REV')),
('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
('', 'pull', None, _('use pull protocol to copy metadata')),
+ ('', 'thin', None, _('only pull needed data from subrepos')),
('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
] + remoteopts,
_('[OPTION]... SOURCE [DEST]'))
@@ -1031,7 +1032,8 @@
stream=opts.get('uncompressed'),
rev=opts.get('rev'),
update=opts.get('updaterev') or not opts.get('noupdate'),
- branch=opts.get('branch'))
+ branch=opts.get('branch'),
+ thinsubrepos=opts.get('thin'))
return r is None
@@ -3421,7 +3423,8 @@
('t', 'tool', '', _('specify merge tool')),
('r', 'rev', '', _('revision to merge'), _('REV')),
('P', 'preview', None,
- _('review revisions to merge (no merge is performed)'))],
+ _('review revisions to merge (no merge is performed)')),
+ ('', 'thin', None, _('only pull needed data from subrepos'))],
_('[-P] [-f] [[-r] REV]'))
def merge(ui, repo, node=None, **opts):
"""merge working directory with another revision
@@ -3501,7 +3504,8 @@
try:
# ui.forcemerge is an internal variable, do not document
ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
- return hg.merge(repo, node, force=opts.get('force'))
+ return hg.merge(repo, node, force=opts.get('force'),
+ thinsubrepos=opts.get('thin'))
finally:
ui.setconfig('ui', 'forcemerge', '')
@@ -3628,12 +3632,12 @@
else:
ui.write("%s = %s\n" % (name, util.hidepassword(path)))
-def postincoming(ui, repo, modheads, optupdate, checkout):
+def postincoming(ui, repo, modheads, optupdate, checkout, thinsubrepos=False):
if modheads == 0:
return
if optupdate:
if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
- return hg.update(repo, checkout)
+ return hg.update(repo, checkout, thinsubrepos=thinsubrepos)
else:
ui.status(_("not updating, since new heads added\n"))
if modheads > 1:
@@ -3655,6 +3659,7 @@
('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
('b', 'branch', [], _('a specific branch you would like to pull'),
_('BRANCH')),
+ ('', 'thin', None, _('only pull needed data from subrepos')),
] + remoteopts,
_('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
def pull(ui, repo, source="default", **opts):
@@ -3705,7 +3710,8 @@
checkout = str(repo.changelog.rev(other.lookup(checkout)))
repo._subtoppath = source
try:
- ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
+ ret = postincoming(ui, repo, modheads, opts.get('update'), checkout,
+ opts.get('thin'))
finally:
del repo._subtoppath
@@ -4900,7 +4906,8 @@
@command('unbundle',
[('u', 'update', None,
- _('update to new branch head if changesets were unbundled'))],
+ _('update to new branch head if changesets were unbundled')),
+ ('', 'thin', None, _('only pull needed data from subrepos'))],
_('[-u] FILE...'))
def unbundle(ui, repo, fname1, *fnames, **opts):
"""apply one or more changegroup files
@@ -4923,16 +4930,19 @@
bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
finally:
lock.release()
- return postincoming(ui, repo, modheads, opts.get('update'), None)
+ return postincoming(ui, repo, modheads, opts.get('update'), None,
+ opts.get('thin'))
@command('^update|up|checkout|co',
[('C', 'clean', None, _('discard uncommitted changes (no backup)')),
('c', 'check', None,
_('update across branches if no uncommitted changes')),
('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
- ('r', 'rev', '', _('revision'), _('REV'))],
+ ('r', 'rev', '', _('revision'), _('REV')),
+ ('', 'thin', None, _('only pull needed data from subrepos'))],
_('[-c] [-C] [-d DATE] [[-r] REV]'))
-def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
+def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
+ thin=False):
"""update working directory (or switch revisions)
Update the repository's working directory to the specified
@@ -4998,9 +5008,9 @@
rev = cmdutil.finddate(ui, repo, date)
if clean or check:
- ret = hg.clean(repo, rev)
+ ret = hg.clean(repo, rev, thinsubrepos=thin)
else:
- ret = hg.update(repo, rev)
+ ret = hg.update(repo, rev, thinsubrepos=thin)
if brev in repo._bookmarks:
bookmarks.setcurrent(repo, brev)
diff -r 85c82ebc96a3 -r 4d759579fb07 mercurial/hg.py
--- a/mercurial/hg.py Mon May 16 13:06:48 2011 +0200
+++ b/mercurial/hg.py Mon May 16 15:44:06 2011 -0400
@@ -170,7 +170,7 @@
_update(r, uprev)
def clone(ui, source, dest=None, pull=False, rev=None, update=True,
- stream=False, branch=None):
+ stream=False, branch=None, thinsubrepos=False):
"""Make a copy of an existing repository.
Create a copy of an existing repository in a new directory. The
@@ -355,7 +355,7 @@
continue
bn = dest_repo[uprev].branch()
dest_repo.ui.status(_("updating to branch %s\n") % bn)
- _update(dest_repo, uprev)
+ _update(dest_repo, uprev, thinsubrepos)
# clone all bookmarks
if dest_repo.local() and src_repo.capable("pushkey"):
@@ -382,9 +382,10 @@
repo.ui.status(_("%d files updated, %d files merged, "
"%d files removed, %d files unresolved\n") % stats)
-def update(repo, node):
+def update(repo, node, thinsubrepos=False):
"""update the working directory to node, merging linear changes"""
- stats = mergemod.update(repo, node, False, False, None)
+ stats = mergemod.update(repo, node, False, False, None,
+ thinsubrepos=thinsubrepos)
_showstats(repo, stats)
if stats[3]:
repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
@@ -393,17 +394,19 @@
# naming conflict in clone()
_update = update
-def clean(repo, node, show_stats=True):
+def clean(repo, node, show_stats=True, thinsubrepos=False):
"""forcibly switch the working directory to node, clobbering changes"""
- stats = mergemod.update(repo, node, False, True, None)
+ stats = mergemod.update(repo, node, False, True, None,
+ thinsubrepos=thinsubrepos)
if show_stats:
_showstats(repo, stats)
return stats[3] > 0
-def merge(repo, node, force=None, remind=True):
+def merge(repo, node, force=None, remind=True, thinsubrepos=False):
"""Branch merge with node, resolving changes. Return true if any
unresolved conflicts."""
- stats = mergemod.update(repo, node, True, force, False)
+ stats = mergemod.update(repo, node, True, force, False,
+ thinsubrepos=thinsubrepos)
_showstats(repo, stats)
if stats[3]:
repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
diff -r 85c82ebc96a3 -r 4d759579fb07 mercurial/merge.py
--- a/mercurial/merge.py Mon May 16 13:06:48 2011 +0200
+++ b/mercurial/merge.py Mon May 16 15:44:06 2011 -0400
@@ -255,7 +255,7 @@
def actionkey(a):
return a[1] == 'r' and -1 or 0, a
-def applyupdates(repo, action, wctx, mctx, actx, overwrite):
+def applyupdates(repo, action, wctx, mctx, actx, overwrite, thinsubrepos=False):
"""apply the merge action list to the working directory
wctx is the working copy context
@@ -315,7 +315,8 @@
repo.ui.note(_("removing %s\n") % f)
audit_path(f)
if f == '.hgsubstate': # subrepo states need updating
- subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
+ subrepo.submerge(repo, wctx, mctx, wctx, overwrite,
+ thinsubrepos=thinsubrepos)
try:
util.unlinkpath(repo.wjoin(f))
except OSError, inst:
@@ -325,7 +326,8 @@
removed += 1
elif m == "m": # merge
if f == '.hgsubstate': # subrepo states need updating
- subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
+ subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
+ overwrite, thinsubrepos=thinsubrepos)
continue
f2, fd, flags, move = a[2:]
r = ms.resolve(fd, wctx, mctx)
@@ -349,7 +351,8 @@
t = None
updated += 1
if f == '.hgsubstate': # subrepo states need updating
- subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
+ subrepo.submerge(repo, wctx, mctx, wctx, overwrite,
+ thinsubrepos=thinsubrepos)
elif m == "d": # directory rename
f2, fd, flags = a[2:]
if f:
@@ -438,7 +441,8 @@
if f:
repo.dirstate.forget(f)
-def update(repo, node, branchmerge, force, partial, ancestor=None):
+def update(repo, node, branchmerge, force, partial, ancestor=None,
+ thinsubrepos=False):
"""
Perform a merge between the working directory and the given node
@@ -546,7 +550,8 @@
if not partial:
repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
- stats = applyupdates(repo, action, wc, p2, pa, overwrite)
+ stats = applyupdates(repo, action, wc, p2, pa, overwrite,
+ thinsubrepos=thinsubrepos)
if not partial:
repo.dirstate.setparents(fp1, fp2)
diff -r 85c82ebc96a3 -r 4d759579fb07 mercurial/subrepo.py
--- a/mercurial/subrepo.py Mon May 16 13:06:48 2011 +0200
+++ b/mercurial/subrepo.py Mon May 16 15:44:06 2011 -0400
@@ -83,7 +83,7 @@
''.join(['%s %s\n' % (state[s][1], s)
for s in sorted(state)]), '')
-def submerge(repo, wctx, mctx, actx, overwrite):
+def submerge(repo, wctx, mctx, actx, overwrite, thinsubrepos=False):
"""delegated from merge.applyupdates: merging of .hgsubstate file
in working context, merging context and ancestor context"""
if mctx == actx: # backwards?
@@ -93,7 +93,8 @@
sa = actx.substate
sm = {}
- repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
+ repo.ui.debug("subrepo merge %s %s %s %s\n" %
+ (wctx, mctx, actx, thinsubrepos))
def debug(s, msg, r=""):
if r:
@@ -115,7 +116,7 @@
continue
elif ld == a: # other side changed
debug(s, "other changed, get", r)
- wctx.sub(s).get(r, overwrite)
+ wctx.sub(s).get(r, overwrite, thinsubrepos)
sm[s] = r
elif ld[0] != r[0]: # sources differ
if repo.ui.promptchoice(
@@ -124,15 +125,15 @@
% (s, l[0], r[0]),
(_('&Local'), _('&Remote')), 0):
debug(s, "prompt changed, get", r)
- wctx.sub(s).get(r, overwrite)
+ wctx.sub(s).get(r, overwrite, thinsubrepos)
sm[s] = r
elif ld[1] == a[1]: # local side is unchanged
debug(s, "other side changed, get", r)
- wctx.sub(s).get(r, overwrite)
+ wctx.sub(s).get(r, overwrite, thinsubrepos)
sm[s] = r
else:
debug(s, "both sides changed, merge with", r)
- wctx.sub(s).merge(r)
+ wctx.sub(s).merge(r, thinsubrepos)
sm[s] = l
elif ld == a: # remote removed, local unchanged
debug(s, "remote removed, remove")
@@ -150,7 +151,7 @@
continue
elif s not in sa:
debug(s, "remote added, get", r)
- mctx.sub(s).get(r)
+ mctx.sub(s).get(r, False, thinsubrepos)
sm[s] = r
elif r != sa[s]:
if repo.ui.promptchoice(
@@ -158,7 +159,7 @@
'use (c)hanged version or (d)elete?') % s,
(_('&Changed'), _('&Delete')), 0) == 0:
debug(s, "prompt recreate", r)
- wctx.sub(s).get(r)
+ wctx.sub(s).get(r, False, thinsubrepos)
sm[s] = r
# record merged .hgsubstate
@@ -270,13 +271,13 @@
"""
raise NotImplementedError
- def get(self, state, overwrite=False):
+ def get(self, state, overwrite=False, thinsubrepos=False):
"""run whatever commands are needed to put the subrepo into
this state
"""
raise NotImplementedError
- def merge(self, state):
+ def merge(self, state, thinsubrepos=False):
"""merge currently-saved state with the new state."""
raise NotImplementedError
@@ -429,7 +430,7 @@
self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
hg.clean(self._repo, node.nullid, False)
- def _get(self, state):
+ def _get(self, state, thinsubrepos):
source, revision, kind = state
if revision not in self._repo:
self._repo._subsource = source
@@ -440,23 +441,31 @@
% (subrelpath(self), srcurl))
parentrepo = self._repo._subparent
shutil.rmtree(self._repo.root)
+ subrev = None
+ if thinsubrepos:
+ subrev = [revision]
other, self._repo = hg.clone(self._repo._subparent.ui, other,
- self._repo.root, update=False)
+ self._repo.root, rev=subrev,
+ update=False,
+ thinsubrepos=thinsubrepos)
self._initrepo(parentrepo, source, create=True)
else:
self._repo.ui.status(_('pulling subrepo %s from %s\n')
% (subrelpath(self), srcurl))
- self._repo.pull(other)
+ subrev = None
+ if thinsubrepos:
+ subrev = [other.lookup(revision)]
+ self._repo.pull(other, heads=subrev)
bookmarks.updatefromremote(self._repo.ui, self._repo, other)
- def get(self, state, overwrite=False):
- self._get(state)
+ def get(self, state, overwrite=False, thinsubrepos=False):
+ self._get(state, thinsubrepos)
source, revision, kind = state
self._repo.ui.debug("getting subrepo %s\n" % self._path)
- hg.clean(self._repo, revision, False)
+ hg.clean(self._repo, revision, False, thinsubrepos=thinsubrepos)
- def merge(self, state):
- self._get(state)
+ def merge(self, state, thinsubrepos):
+ self._get(state, thinsubrepos)
cur = self._repo['.']
dst = self._repo[state[1]]
anc = dst.ancestor(cur)
@@ -464,12 +473,13 @@
def mergefunc():
if anc == cur:
self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
- hg.update(self._repo, state[1])
+ hg.update(self._repo, state[1], thinsubrepos=thinsubrepos)
elif anc == dst:
self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
else:
self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
- hg.merge(self._repo, state[1], remind=False)
+ hg.merge(self._repo, state[1], remind=False,
+ thinsubrepos=thinsubrepos)
wctx = self._repo[None]
if self.dirty():
@@ -647,7 +657,7 @@
except OSError:
pass
- def get(self, state, overwrite=False):
+ def get(self, state, overwrite=False, thinsubrepos=False):
if overwrite:
self._svncommand(['revert', '--recursive'])
args = ['checkout']
@@ -659,7 +669,7 @@
raise util.Abort(status.splitlines()[-1])
self._ui.status(status)
- def merge(self, state):
+ def merge(self, state, thinsubrepos=False):
old = self._state[1]
new = state[1]
if new != self._wcrev():
@@ -826,7 +836,7 @@
out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
return code == 1
- def get(self, state, overwrite=False):
+ def get(self, state, overwrite=False, thinsubrepos=False):
source, revision, kind = state
self._fetch(source, revision)
# if the repo was set to be bare, unbare it
@@ -923,7 +933,7 @@
# circumstances
return self._gitstate()
- def merge(self, state):
+ def merge(self, state, thinsubrepos=False):
source, revision, kind = state
self._fetch(source, revision)
base = self._gitcommand(['merge-base', revision, self._state[1]])
diff -r 85c82ebc96a3 -r 4d759579fb07 tests/test-debugcomplete.t
--- a/tests/test-debugcomplete.t Mon May 16 13:06:48 2011 +0200
+++ b/tests/test-debugcomplete.t Mon May 16 15:44:06 2011 -0400
@@ -187,21 +187,21 @@
$ hg debugcommands
add: include, exclude, subrepos, dry-run
annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, include, exclude
- clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd, insecure
+ clone: noupdate, updaterev, rev, branch, pull, thin, uncompressed, ssh, remotecmd, insecure
commit: addremove, close-branch, include, exclude, message, logfile, date, user
diff: rev, change, text, git, nodates, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, include, exclude, subrepos
export: output, switch-parent, rev, text, git, nodates
forget: include, exclude
init: ssh, remotecmd, insecure
log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, style, template, include, exclude
- merge: force, tool, rev, preview
- pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
+ merge: force, tool, rev, preview, thin
+ pull: update, force, rev, bookmark, branch, thin, ssh, remotecmd, insecure
push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
remove: after, force, include, exclude
serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, templates, style, ipv6, certificate
status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos
summary: remote
- update: clean, check, date, rev
+ update: clean, check, date, rev, thin
addremove: similarity, include, exclude, dry-run
archive: no-decode, prefix, rev, type, subrepos, include, exclude
backout: merge, parent, tool, rev, include, exclude, message, logfile, date, user
@@ -260,6 +260,6 @@
tag: force, local, rev, remove, edit, message, date, user
tags:
tip: patch, git, style, template
- unbundle: update
+ unbundle: update, thin
verify:
version:
diff -r 85c82ebc96a3 -r 4d759579fb07 tests/test-subrepo.t
--- a/tests/test-subrepo.t Mon May 16 13:06:48 2011 +0200
+++ b/tests/test-subrepo.t Mon May 16 15:44:06 2011 -0400
@@ -177,7 +177,7 @@
ancestor 1f14a2e2d3ec local f0d2028bf86d+ remote 1831e14459c4
.hgsubstate: versions differ -> m
updating: .hgsubstate 1/1 files (100.00%)
- subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
+ subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec None
subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
getting subrepo t
resolving manifests
@@ -205,7 +205,7 @@
ancestor 1831e14459c4 local e45c8b14af55+ remote f94576341bcf
.hgsubstate: versions differ -> m
updating: .hgsubstate 1/1 files (100.00%)
- subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
+ subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4 None
subrepo t: both sides changed, merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
merging subrepo t
searching for copies back to rev 2
More information about the Mercurial-devel
mailing list