[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