[PATCH RFC] subrepo: Add the '--thin' option to pull a thin strand of changes
Matt Mackall
mpm at selenic.com
Mon May 16 16:42:49 CDT 2011
On Mon, 2011-05-16 at 15:46 -0400, Alistair Bell wrote:
> # 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.
This might be better as a config setting. Then you can set it globally
and forget about it.
> 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.
I'd like to have that argument. The downside -I- see is that -r can be
significantly more CPU intensive for the server if it ends up getting
the same csets, but when it's getting significantly fewer, it's a big
win.
> 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
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel
--
Mathematics is the supreme nostalgia of our time.
More information about the Mercurial-devel
mailing list