[PATCH 3 of 3] import: add --no-update option

Matt Mackall mpm at selenic.com
Sat Jun 11 14:23:30 CDT 2011


On Sat, 2011-06-11 at 15:20 +0200, Patrick Mezard wrote:
> # HG changeset patch
> # User Patrick Mezard <pmezard at gmail.com>
> # Date 1307796661 -7200
> # Node ID 984577891b4661199808f923e184a4101fc7334e
> # Parent  231c722c1d8db6a9181a22e41272320114f87093
> import: add --no-update option

The first two patches look fine, but I can't say I'm happy with this
flag.

> This feature is more a way to test patching without a working directory than
> something people ask about. Adding a --rev option to specify the parent patch
> revision would make it a little more useful.

If we -know- we're going to introduce a --rev -like option eventually,
let's just introduce one option that combines both. ie:

--parent REV  apply patch at REV, bypassing working copy

> What this change introduces is patch.repobackend class which let patches be
> applied against repository revisions. The caller must supply a filestore object
> to receive patched content, which can be turned into a memctx with
> patch.makememctx() helper.
> 
> diff --git a/mercurial/commands.py b/mercurial/commands.py
> --- a/mercurial/commands.py
> +++ b/mercurial/commands.py
> @@ -3001,6 +3001,8 @@
>      ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
>      ('', 'no-commit', None,
>       _("don't commit, just update the working directory")),
> +    ('', 'no-update', None,
> +     _("do not update the working directory")),
>      ('', 'exact', None,
>       _('apply patch to the nodes from which it was generated')),
>      ('', 'import-branch', None,
> @@ -3049,14 +3051,19 @@
>      if date:
>          opts['date'] = util.parsedate(date)
>  
> +    update = not opts.get('no_update')
> +    if not update and opts.get('no_commit'):
> +        raise util.Abort(_('cannot use --no-commit with --no-update'))
>      try:
>          sim = float(opts.get('similarity') or 0)
>      except ValueError:
>          raise util.Abort(_('similarity must be a number'))
>      if sim < 0 or sim > 100:
>          raise util.Abort(_('similarity must be between 0 and 100'))
> -
> -    if opts.get('exact') or not opts.get('force'):
> +    if sim and not update:
> +        raise util.Abort(_('cannot use --similarity with --no-update'))
> +
> +    if (opts.get('exact') or not opts.get('force')) and update:
>          cmdutil.bailifchanged(repo)
>  
>      d = opts["base"]
> @@ -3064,6 +3071,11 @@
>      wlock = lock = None
>      msgs = []
>  
> +    def checkexact(repo, n, nodeid):
> +        if opts.get('exact') and hex(n) != nodeid:
> +            repo.rollback()
> +            raise util.Abort(_('patch is damaged or loses information'))
> +
>      def tryone(ui, hunk):
>          tmpname, message, user, date, branch, nodeid, p1, p2 = \
>              patch.extract(ui, hunk)
> @@ -3102,40 +3114,60 @@
>              else:
>                  p1, p2 = wp
>  
> -            if opts.get('exact') and p1 != wp[0]:
> -                hg.clean(repo, p1.node())
> -            if p1 != wp[0] and p2 != wp[1]:
> -                repo.dirstate.setparents(p1.node(), p2.node())
> -
> -            if opts.get('exact') or opts.get('import_branch'):
> -                repo.dirstate.setbranch(branch or 'default')
> -
> -            files = set()
> -            patch.patch(ui, repo, tmpname, strip=strip, files=files,
> -                        eolmode=None, similarity=sim / 100.0)
> -            files = list(files)
> -            if opts.get('no_commit'):
> -                if message:
> -                    msgs.append(message)
> +            n = None
> +            if update:
> +                if opts.get('exact') and p1 != wp[0]:
> +                    hg.clean(repo, p1.node())
> +                if p1 != wp[0] and p2 != wp[1]:
> +                    repo.dirstate.setparents(p1.node(), p2.node())
> +
> +                if opts.get('exact') or opts.get('import_branch'):
> +                    repo.dirstate.setbranch(branch or 'default')
> +
> +                files = set()
> +                patch.patch(ui, repo, tmpname, strip=strip, files=files,
> +                            eolmode=None, similarity=sim / 100.0)
> +                files = list(files)
> +                if opts.get('no_commit'):
> +                    if message:
> +                        msgs.append(message)
> +                else:
> +                    if opts.get('exact'):
> +                        m = None
> +                    else:
> +                        m = scmutil.matchfiles(repo, files or [])
> +                    n = repo.commit(message, opts.get('user') or user,
> +                                    opts.get('date') or date, match=m,
> +                                    editor=cmdutil.commiteditor)
> +                    checkexact(repo, n, nodeid)
> +                    # Force a dirstate write so that the next transaction
> +                    # backups an up-to-date file.
> +                    repo.dirstate.write()
>              else:
> -                if opts.get('exact'):
> -                    m = None
> +                if opts.get('exact') or opts.get('import_branch'):
> +                    branch = branch or 'default'
>                  else:
> -                    m = scmutil.matchfiles(repo, files or [])
> -                n = repo.commit(message, opts.get('user') or user,
> -                                opts.get('date') or date, match=m,
> -                                editor=cmdutil.commiteditor)
> -                if opts.get('exact'):
> -                    if hex(n) != nodeid:
> -                        repo.rollback()
> -                        raise util.Abort(_('patch is damaged'
> -                                           ' or loses information'))
> -                # Force a dirstate write so that the next transaction
> -                # backups an up-do-date file.
> -                repo.dirstate.write()
> -                if n:
> -                    commitid = short(n)
> -
> +                    branch = p1.branch()
> +                store = patch.filestore()
> +                try:
> +                    files = set()
> +                    try:
> +                        patch.patchrepo(ui, repo, p1, store, tmpname, strip, files,
> +                                        eolmode=None)
> +                    except patch.PatchError, e:
> +                        raise util.Abort(str(e))
> +                    memctx = patch.makememctx(repo, (p1.node(), p2.node()), message,
> +                                              opts.get('user') or user,
> +                                              opts.get('date') or date,
> +                                              branch, files, store,
> +                                              editor=cmdutil.commiteditor)
> +                    repo.savecommitmessage(memctx.description())
> +                    n = memctx.commit()
> +                    checkexact(repo, n, nodeid)
> +                finally:
> +                    store.close()
> +            if n:
> +                commitid = short(n)
>              return commitid
>          finally:
>              os.unlink(tmpname)
> diff --git a/mercurial/patch.py b/mercurial/patch.py
> --- a/mercurial/patch.py
> +++ b/mercurial/patch.py
> @@ -11,7 +11,8 @@
>  
>  from i18n import _
>  from node import hex, nullid, short
> -import base85, mdiff, scmutil, util, diffhelpers, copies, encoding
> +import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
> +import context
>  
>  gitre = re.compile('diff --git a/(.*) b/(.*)')
>  
> @@ -511,6 +512,48 @@
>          if self.opener:
>              shutil.rmtree(self.opener.base)
>  
> +class repobackend(abstractbackend):
> +    def __init__(self, ui, repo, ctx, store):
> +        super(repobackend, self).__init__(ui)
> +        self.repo = repo
> +        self.ctx = ctx
> +        self.store = store
> +        self.changed = set()
> +        self.removed = set()
> +        self.copied = {}
> +
> +    def _checkknown(self, fname):
> +        if fname not in self.ctx:
> +            raise PatchError(_('cannot patch %s: file is not tracked') % fname)
> +
> +    def getfile(self, fname):
> +        try:
> +            fctx = self.ctx[fname]
> +        except error.LookupError:
> +            raise IOError()
> +        flags = fctx.flags()
> +        return fctx.data(), ('l' in flags, 'x' in flags)
> +
> +    def setfile(self, fname, data, mode, copysource):
> +        if copysource:
> +            self._checkknown(copysource)
> +        if data is None:
> +            data = self.ctx[fname].data()
> +        self.store.setfile(fname, data, mode, copysource)
> +        self.changed.add(fname)
> +        if copysource:
> +            self.copied[fname] = copysource
> +
> +    def unlink(self, fname):
> +        self._checkknown(fname)
> +        self.removed.add(fname)
> +
> +    def exists(self, fname):
> +        return fname in self.ctx
> +
> +    def close(self):
> +        return self.changed | self.removed
> +
>  # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
>  unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
>  contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
> @@ -1332,11 +1375,7 @@
>                           util.explainexit(code)[0])
>      return fuzz
>  
> -def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
> -                  similarity=0):
> -    """use builtin patch to apply <patchobj> to the working directory.
> -    returns whether patch was applied with fuzz factor."""
> -
> +def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
>      if files is None:
>          files = set()
>      if eolmode is None:
> @@ -1346,7 +1385,6 @@
>      eolmode = eolmode.lower()
>  
>      store = filestore()
> -    backend = workingbackend(ui, repo, similarity)
>      try:
>          fp = open(patchobj, 'rb')
>      except TypeError:
> @@ -1363,6 +1401,33 @@
>          raise PatchError(_('patch failed to apply'))
>      return ret > 0
>  
> +def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
> +                  similarity=0):
> +    """use builtin patch to apply <patchobj> to the working directory.
> +    returns whether patch was applied with fuzz factor."""
> +    backend = workingbackend(ui, repo, similarity)
> +    return patchbackend(ui, backend, patchobj, strip, files, eolmode)
> +
> +def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
> +              eolmode='strict'):
> +    backend = repobackend(ui, repo, ctx, store)
> +    return patchbackend(ui, backend, patchobj, strip, files, eolmode)
> +
> +def makememctx(repo, parents, text, user, date, branch, files, store,
> +               editor=None):
> +    def getfilectx(repo, memctx, path):
> +        data, (islink, isexec), copied = store.getfile(path)
> +        return context.memfilectx(path, data, islink=islink, isexec=isexec,
> +                                  copied=copied)
> +    extra = {}
> +    if branch:
> +        extra['branch'] = encoding.fromlocal(branch)
> +    ctx =  context.memctx(repo, parents, text, files, getfilectx, user,
> +                          date, extra)
> +    if editor:
> +        ctx._text = editor(repo, ctx, [])
> +    return ctx
> +
>  def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
>            similarity=0):
>      """Apply <patchname> to the working directory.
> diff --git a/tests/test-debugcomplete.t b/tests/test-debugcomplete.t
> --- a/tests/test-debugcomplete.t
> +++ b/tests/test-debugcomplete.t
> @@ -245,7 +245,7 @@
>    heads: rev, topo, active, closed, style, template
>    help: extension, command
>    identify: rev, num, id, branch, tags, bookmarks
> -  import: strip, base, force, no-commit, exact, import-branch, message, logfile, date, user, similarity
> +  import: strip, base, force, no-commit, no-update, exact, import-branch, message, logfile, date, user, similarity
>    incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, style, template, ssh, remotecmd, insecure, subrepos
>    locate: rev, print0, fullpath, include, exclude
>    manifest: rev, all
> diff --git a/tests/test-import-noupdate.t b/tests/test-import-noupdate.t
> new file mode 100644
> --- /dev/null
> +++ b/tests/test-import-noupdate.t
> @@ -0,0 +1,182 @@
> +  $ echo "[extensions]" >> $HGRCPATH
> +  $ echo "purge=" >> $HGRCPATH
> +
> +  $ shortlog() {
> +  >     hg log --template '{rev}:{node|short} {author} {date|hgdate} - {branch} - {desc|firstline}\n'
> +  > }
> +
> +Test --noupdate with other options
> +
> +  $ hg init repo-options
> +  $ cd repo-options
> +  $ echo a > a
> +  $ hg ci -Am adda
> +  adding a
> +  $ echo a >> a
> +  $ hg branch foo
> +  marked working directory as branch foo
> +  $ hg ci -Am changea
> +  $ hg export . > ../test.diff
> +  $ hg up null
> +  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
> +
> +Test importing an existing revision
> +
> +  $ hg import --no-update --exact ../test.diff
> +  applying ../test.diff
> +  $ shortlog
> +  1:4e322f7ce8e3 test 0 0 - foo - changea
> +  0:07f494440405 test 0 0 - default - adda
> +
> +Test failure without --exact
> +
> +  $ hg import --no-update ../test.diff
> +  applying ../test.diff
> +  unable to find 'a' for patching
> +  abort: patch failed to apply
> +  [255]
> +  $ hg st
> +  $ shortlog
> +  1:4e322f7ce8e3 test 0 0 - foo - changea
> +  0:07f494440405 test 0 0 - default - adda
> +
> +Test --user, --date and --message
> +
> +  $ hg up 0
> +  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
> +  $ hg import --no-update --u test2 -d '1 0' -m patch2 ../test.diff
> +  applying ../test.diff
> +  $ cat .hg/last-message.txt
> +  patch2 (no-eol)
> +  $ shortlog
> +  2:2e127d1da504 test2 1 0 - default - patch2
> +  1:4e322f7ce8e3 test 0 0 - foo - changea
> +  0:07f494440405 test 0 0 - default - adda
> +  $ hg rollback
> +  repository tip rolled back to revision 1 (undo commit)
> +  working directory now based on revision 0
> +
> +Test --import-branch
> +
> +  $ hg import --no-update --import-branch ../test.diff
> +  applying ../test.diff
> +  $ shortlog
> +  1:4e322f7ce8e3 test 0 0 - foo - changea
> +  0:07f494440405 test 0 0 - default - adda
> +  $ hg rollback
> +  repository tip rolled back to revision 1 (undo commit)
> +  working directory now based on revision 0
> +
> +Test --strip
> +
> +  $ hg import --no-update --strip 0 - <<EOF
> +  > # HG changeset patch
> +  > # User test
> +  > # Date 0 0
> +  > # Branch foo
> +  > # Node ID 4e322f7ce8e3e4203950eac9ece27bf7e45ffa6c
> +  > # Parent  07f4944404050f47db2e5c5071e0e84e7a27bba9
> +  > changea
> +  > 
> +  > diff -r 07f494440405 -r 4e322f7ce8e3 a
> +  > --- a	Thu Jan 01 00:00:00 1970 +0000
> +  > +++ a	Thu Jan 01 00:00:00 1970 +0000
> +  > @@ -1,1 +1,2 @@
> +  >  a
> +  > +a
> +  > EOF
> +  applying patch from stdin
> +  $ hg rollback
> +  repository tip rolled back to revision 1 (undo commit)
> +  working directory now based on revision 0
> +
> +Test unsupported combinations
> +
> +  $ hg import --no-update --no-commit ../test.diff
> +  abort: cannot use --no-commit with --no-update
> +  [255]
> +  $ hg import --no-update --similarity 50 ../test.diff
> +  abort: cannot use --similarity with --no-update
> +  [255]
> +
> +Test commit editor
> +
> +  $ hg diff -c 1 > ../test.diff
> +  $ HGEDITOR=cat hg import --no-update ../test.diff
> +  applying ../test.diff
> +  
> +  
> +  HG: Enter commit message.  Lines beginning with 'HG:' are removed.
> +  HG: Leave message empty to abort commit.
> +  HG: --
> +  HG: user: test
> +  HG: branch 'default'
> +  HG: changed a
> +  abort: empty commit message
> +  [255]
> +
> +Test patch.eol is handled
> +
> +  $ python -c 'file("a", "wb").write("a\r\n")'
> +  $ hg ci -m makeacrlf
> +  $ hg import -m 'should fail because of eol' --no-update ../test.diff
> +  applying ../test.diff
> +  patching file a
> +  Hunk #1 FAILED at 0
> +  abort: patch failed to apply
> +  [255]
> +  $ hg --config patch.eol=auto import -d '0 0' -m 'test patch.eol' --no-update ../test.diff
> +  applying ../test.diff
> +  $ shortlog
> +  3:d7805b4d2cb3 test 0 0 - default - test patch.eol
> +  2:872023de769d test 0 0 - default - makeacrlf
> +  1:4e322f7ce8e3 test 0 0 - foo - changea
> +  0:07f494440405 test 0 0 - default - adda
> +
> +  $ cd ..
> +
> +Test complicated patch with --exact
> +
> +  $ hg init repo-exact
> +  $ cd repo-exact
> +  $ echo a > a
> +  $ echo c > c
> +  $ echo d > d
> +  $ echo e > e
> +  $ echo f > f
> +  $ chmod +x f
> +  $ ln -s c linkc
> +  $ hg ci -Am t
> +  adding a
> +  adding c
> +  adding d
> +  adding e
> +  adding f
> +  adding linkc
> +  $ hg cp a aa1
> +  $ echo b >> a
> +  $ echo b > b
> +  $ hg add b
> +  $ hg cp a aa2
> +  $ echo aa >> aa2
> +  $ chmod +x e
> +  $ chmod -x f
> +  $ ln -s a linka
> +  $ hg rm d
> +  $ hg rm linkc
> +  $ hg mv c cc
> +  $ hg ci -m patch
> +  $ hg export --git . > ../test.diff
> +  $ hg up -C null
> +  0 files updated, 0 files merged, 7 files removed, 0 files unresolved
> +  $ hg purge
> +  $ hg st
> +  $ hg import --no-update --exact ../test.diff
> +  applying ../test.diff
> +
> +The patch should have matched the exported revision and generated no additional
> +data. If not, diff both heads to debug it.
> +
> +  $ shortlog
> +  1:2978fd5c8aa4 test 0 0 - default - patch
> +  0:a0e19e636a43 test 0 0 - default - t
> _______________________________________________
> 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