changeset 16642:5cf18921bb7b

merge with stable
author Matt Mackall <mpm@selenic.com>
date Sat, 12 May 2012 00:06:11 +0200
parents 1435866c1937 (current diff) e6dfbc5df76f (diff)
children 24dbef11f477
files hgext/largefiles/overrides.py mercurial/cmdutil.py mercurial/commands.py mercurial/parsers.c
diffstat 12 files changed, 276 insertions(+), 67 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/largefiles/overrides.py	Wed May 09 09:58:50 2012 +0200
+++ b/hgext/largefiles/overrides.py	Sat May 12 00:06:11 2012 +0200
@@ -552,7 +552,8 @@
         for lfile in modified:
             lfutil.updatestandin(repo, lfutil.standin(lfile))
         for lfile in missing:
-            os.unlink(repo.wjoin(lfutil.standin(lfile)))
+            if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
+                os.unlink(repo.wjoin(lfutil.standin(lfile)))
 
         try:
             ctx = repo[opts.get('rev')]
@@ -953,6 +954,8 @@
             ui.status(_('largefiles: %d to upload\n') % len(toupload))
 
 def overrideaddremove(orig, ui, repo, *pats, **opts):
+    if not lfutil.islfilesrepo(repo):
+        return orig(ui, repo, *pats, **opts)
     # Get the list of missing largefiles so we can remove them
     lfdirstate = lfutil.openlfdirstate(ui, repo)
     s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
--- a/hgext/mq.py	Wed May 09 09:58:50 2012 +0200
+++ b/hgext/mq.py	Sat May 12 00:06:11 2012 +0200
@@ -554,6 +554,18 @@
         except OSError, inst:
             self.ui.warn(_('error removing undo: %s\n') % str(inst))
 
+    def backup(self, repo, files, copy=False):
+        # backup local changes in --force case
+        for f in sorted(files):
+            absf = repo.wjoin(f)
+            if os.path.lexists(absf):
+                self.ui.note(_('saving current version of %s as %s\n') %
+                             (f, f + '.orig'))
+                if copy:
+                    util.copyfile(absf, absf + '.orig')
+                else:
+                    util.rename(absf, absf + '.orig')
+
     def printdiff(self, repo, diffopts, node1, node2=None, files=None,
                   fp=None, changes=None, opts={}):
         stat = opts.get('stat')
@@ -668,7 +680,8 @@
             return (False, list(files), False)
 
     def apply(self, repo, series, list=False, update_status=True,
-              strict=False, patchdir=None, merge=None, all_files=None):
+              strict=False, patchdir=None, merge=None, all_files=None,
+              tobackup=None):
         wlock = lock = tr = None
         try:
             wlock = repo.wlock()
@@ -676,7 +689,8 @@
             tr = repo.transaction("qpush")
             try:
                 ret = self._apply(repo, series, list, update_status,
-                                  strict, patchdir, merge, all_files=all_files)
+                                  strict, patchdir, merge, all_files=all_files,
+                                  tobackup=tobackup)
                 tr.close()
                 self.savedirty()
                 return ret
@@ -693,9 +707,14 @@
             self.removeundo(repo)
 
     def _apply(self, repo, series, list=False, update_status=True,
-               strict=False, patchdir=None, merge=None, all_files=None):
-        '''returns (error, hash)
-        error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
+               strict=False, patchdir=None, merge=None, all_files=None,
+               tobackup=None):
+        """returns (error, hash)
+
+        error = 1 for unable to read, 2 for patch failed, 3 for patch
+        fuzz. tobackup is None or a set of files to backup before they
+        are modified by a patch.
+        """
         # TODO unify with commands.py
         if not patchdir:
             patchdir = self.path
@@ -727,6 +746,11 @@
                 message = '\n'.join(message)
 
             if ph.haspatch:
+                if tobackup:
+                    touched = patchmod.changedfiles(self.ui, repo, pf)
+                    touched = set(touched) & tobackup
+                    self.backup(repo, touched, copy=True)
+                    tobackup = tobackup - touched
                 (patcherr, files, fuzz) = self.patch(repo, pf)
                 if all_files is not None:
                     all_files.update(files)
@@ -1133,7 +1157,7 @@
         raise util.Abort(_("patch %s not in series") % patch)
 
     def push(self, repo, patch=None, force=False, list=False,
-             mergeq=None, all=False, move=False, exact=False):
+             mergeq=None, all=False, move=False, exact=False, nobackup=False):
         diffopts = self.diffopts()
         wlock = repo.wlock()
         try:
@@ -1232,13 +1256,19 @@
             else:
                 end = self.series.index(patch, start) + 1
 
+            tobackup = set()
+            if not nobackup and force:
+                m, a, r, d = self.checklocalchanges(repo, force=True)
+                tobackup.update(m + a)
+
             s = self.series[start:end]
             all_files = set()
             try:
                 if mergeq:
                     ret = self.mergepatch(repo, mergeq, s, diffopts)
                 else:
-                    ret = self.apply(repo, s, list, all_files=all_files)
+                    ret = self.apply(repo, s, list, all_files=all_files,
+                                     tobackup=tobackup)
             except:
                 self.ui.warn(_('cleaning up working directory...'))
                 node = repo.dirstate.p1()
@@ -1268,7 +1298,8 @@
         finally:
             wlock.release()
 
-    def pop(self, repo, patch=None, force=False, update=True, all=False):
+    def pop(self, repo, patch=None, force=False, update=True, all=False,
+            nobackup=False):
         wlock = repo.wlock()
         try:
             if patch:
@@ -1313,8 +1344,11 @@
                         break
                 update = needupdate
 
-            if not force and update:
-                self.checklocalchanges(repo)
+            tobackup = set()
+            if update:
+                m, a, r, d = self.checklocalchanges(repo, force=force)
+                if not nobackup and force:
+                    tobackup.update(m + a)
 
             self.applieddirty = True
             end = len(self.applied)
@@ -1344,6 +1378,10 @@
                 m, a, r, d = repo.status(qp, top)[:4]
                 if d:
                     raise util.Abort(_("deletions found between repo revs"))
+
+                # backup local changes in --force case
+                self.backup(repo, set(a + m + r) & tobackup)
+
                 for f in a:
                     try:
                         util.unlinkpath(repo.wjoin(f))
@@ -2460,7 +2498,8 @@
         wlock.release()
 
 @command("qgoto",
-         [('f', 'force', None, _('overwrite any local changes'))],
+         [('f', 'force', None, _('overwrite any local changes')),
+          ('', 'no-backup', None, _('do not save backup copies of files'))],
          _('hg qgoto [OPTION]... PATCH'))
 def goto(ui, repo, patch, **opts):
     '''push or pop patches until named patch is at top of stack
@@ -2468,10 +2507,11 @@
     Returns 0 on success.'''
     q = repo.mq
     patch = q.lookup(patch)
+    nobackup = opts.get('no_backup')
     if q.isapplied(patch):
-        ret = q.pop(repo, patch, force=opts.get('force'))
+        ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup)
     else:
-        ret = q.push(repo, patch, force=opts.get('force'))
+        ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup)
     q.savedirty()
     return ret
 
@@ -2598,7 +2638,9 @@
           ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
           ('n', 'name', '',
            _('merge queue name (DEPRECATED)'), _('NAME')),
-          ('', 'move', None, _('reorder patch series and apply only the patch'))],
+          ('', 'move', None,
+           _('reorder patch series and apply only the patch')),
+          ('', 'no-backup', None, _('do not save backup copies of files'))],
          _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
 def push(ui, repo, patch=None, **opts):
     """push the next patch onto the stack
@@ -2623,14 +2665,15 @@
         ui.warn(_("merging with queue at: %s\n") % mergeq.path)
     ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
                  mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
-                 exact=opts.get('exact'))
+                 exact=opts.get('exact'), nobackup=opts.get('no_backup'))
     return ret
 
 @command("^qpop",
          [('a', 'all', None, _('pop all patches')),
           ('n', 'name', '',
            _('queue name to pop (DEPRECATED)'), _('NAME')),
-          ('f', 'force', None, _('forget any local changes to patched files'))],
+          ('f', 'force', None, _('forget any local changes to patched files')),
+          ('', 'no-backup', None, _('do not save backup copies of files'))],
          _('hg qpop [-a] [-f] [PATCH | INDEX]'))
 def pop(ui, repo, patch=None, **opts):
     """pop the current patch off the stack
@@ -2649,7 +2692,7 @@
     else:
         q = repo.mq
     ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
-                all=opts.get('all'))
+                all=opts.get('all'), nobackup=opts.get('no_backup'))
     q.savedirty()
     return ret
 
--- a/hgext/pager.py	Wed May 09 09:58:50 2012 +0200
+++ b/hgext/pager.py	Sat May 12 00:06:11 2012 +0200
@@ -22,12 +22,6 @@
 If no pager is set, the pager extensions uses the environment variable
 $PAGER. If neither pager.pager, nor $PAGER is set, no pager is used.
 
-If you notice "BROKEN PIPE" error messages, you can disable them by
-setting::
-
-  [pager]
-  quiet = True
-
 You can disable the pager for certain commands by adding them to the
 pager.ignore list::
 
@@ -53,37 +47,27 @@
 normal behavior.
 '''
 
-import sys, os, signal, shlex, errno
+import atexit, sys, os, signal, subprocess
 from mercurial import commands, dispatch, util, extensions
 from mercurial.i18n import _
 
 def _runpager(p):
-    if not util.safehasattr(os, 'fork'):
-        sys.stdout = util.popen(p, 'wb')
-        if util.isatty(sys.stderr):
-            sys.stderr = sys.stdout
-        return
-    fdin, fdout = os.pipe()
-    pid = os.fork()
-    if pid == 0:
-        os.close(fdin)
-        os.dup2(fdout, sys.stdout.fileno())
-        if util.isatty(sys.stderr):
-            os.dup2(fdout, sys.stderr.fileno())
-        os.close(fdout)
-        return
-    os.dup2(fdin, sys.stdin.fileno())
-    os.close(fdin)
-    os.close(fdout)
-    try:
-        os.execvp('/bin/sh', ['/bin/sh', '-c', p])
-    except OSError, e:
-        if e.errno == errno.ENOENT:
-            # no /bin/sh, try executing the pager directly
-            args = shlex.split(p)
-            os.execvp(args[0], args)
-        else:
-            raise
+    pager = subprocess.Popen(p, shell=True, bufsize=-1,
+                             close_fds=util.closefds, stdin=subprocess.PIPE,
+                             stdout=sys.stdout, stderr=sys.stderr)
+
+    stdout = os.dup(sys.stdout.fileno())
+    stderr = os.dup(sys.stderr.fileno())
+    os.dup2(pager.stdin.fileno(), sys.stdout.fileno())
+    if util.isatty(sys.stderr):
+        os.dup2(pager.stdin.fileno(), sys.stderr.fileno())
+
+    @atexit.register
+    def killpager():
+        pager.stdin.close()
+        os.dup2(stdout, sys.stdout.fileno())
+        os.dup2(stderr, sys.stderr.fileno())
+        pager.wait()
 
 def uisetup(ui):
     if ui.plain() or '--debugger' in sys.argv or not util.isatty(sys.stdout):
@@ -101,9 +85,11 @@
                  (cmd not in ui.configlist('pager', 'ignore') and not attend))):
                 ui.setconfig('ui', 'formatted', ui.formatted())
                 ui.setconfig('ui', 'interactive', False)
+                try:
+                    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+                except ValueError:
+                    pass
                 _runpager(p)
-                if ui.configbool('pager', 'quiet'):
-                    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
         return orig(ui, options, cmd, cmdfunc)
 
     extensions.wrapfunction(dispatch, '_runcommand', pagecmd)
--- a/mercurial/cmdutil.py	Wed May 09 09:58:50 2012 +0200
+++ b/mercurial/cmdutil.py	Sat May 12 00:06:11 2012 +0200
@@ -1311,6 +1311,12 @@
         #          |
         # base     o - parent of amending changeset
 
+        # Update extra dict from amended commit (e.g. to preserve graft source)
+        extra.update(old.extra())
+
+        # Also update it from the intermediate commit or from the wctx
+        extra.update(ctx.extra())
+
         files = set(old.files())
 
         # Second, we use either the commit we just did, or if there were no
@@ -1322,7 +1328,6 @@
             user = ctx.user()
             date = ctx.date()
             message = ctx.description()
-            extra = ctx.extra()
             # Recompute copies (avoid recording a -> b -> a)
             copied = copies.pathcopies(base, ctx)
 
--- a/mercurial/commands.py	Wed May 09 09:58:50 2012 +0200
+++ b/mercurial/commands.py	Sat May 12 00:06:11 2012 +0200
@@ -196,6 +196,8 @@
     be identical) as its parameter. Detecting renamed files this way
     can be expensive. After using this option, :hg:`status -C` can be
     used to check which files were identified as moved or renamed.
+    If this option is not specified, only renames of identical files
+    are detected.
 
     Returns 0 if all files are successfully added.
     """
--- a/mercurial/parsers.c	Wed May 09 09:58:50 2012 +0200
+++ b/mercurial/parsers.c	Sat May 12 00:06:11 2012 +0200
@@ -555,7 +555,7 @@
  */
 static int nt_find(indexObject *self, const char *node, Py_ssize_t nodelen)
 {
-	int level, off;
+	int level, maxlevel, off;
 
 	if (nodelen == 20 && node[0] == '\0' && memcmp(node, nullid, 20) == 0)
 		return -1;
@@ -563,7 +563,9 @@
 	if (self->nt == NULL)
 		return -2;
 
-	for (level = off = 0; level < nodelen; level++) {
+	maxlevel = nodelen > 20 ? 40 : ((int)nodelen * 2);
+
+	for (level = off = 0; level < maxlevel; level++) {
 		int k = nt_level(node, level);
 		nodetree *n = &self->nt[off];
 		int v = n->children[k];
@@ -606,7 +608,7 @@
 	int level = 0;
 	int off = 0;
 
-	while (level < 20) {
+	while (level < 40) {
 		int k = nt_level(node, level);
 		nodetree *n;
 		int v;
--- a/mercurial/revset.py	Wed May 09 09:58:50 2012 +0200
+++ b/mercurial/revset.py	Sat May 12 00:06:11 2012 +0200
@@ -926,7 +926,7 @@
 
     Special fields are ``summary`` and ``metadata``:
     ``summary`` matches the first line of the description.
-    ``metatadata`` is equivalent to matching ``description user date``
+    ``metadata`` is equivalent to matching ``description user date``
     (i.e. it matches the main metadata fields).
 
     ``metadata`` is the default field which is used when no fields are
@@ -996,7 +996,7 @@
     # is only one field to match)
     getinfo = lambda r: [f(r) for f in getfieldfuncs]
 
-    matches = []
+    matches = set()
     for rev in revs:
         target = getinfo(rev)
         for r in subset:
@@ -1006,10 +1006,8 @@
                     match = False
                     break
             if match:
-                matches.append(r)
-    if len(revs) > 1:
-        matches = sorted(set(matches))
-    return matches
+                matches.add(r)
+    return [r for r in subset if r in matches]
 
 def reverse(repo, subset, x):
     """``reverse(set)``
--- a/tests/test-check-code-hg.t	Wed May 09 09:58:50 2012 +0200
+++ b/tests/test-check-code-hg.t	Sat May 12 00:06:11 2012 +0200
@@ -220,9 +220,6 @@
    >             raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
    warning: line over 80 characters
   hgext/mq.py:0:
-   >           ('', 'move', None, _('reorder patch series and apply only the patch'))],
-   warning: line over 80 characters
-  hgext/mq.py:0:
    >           ('U', 'noupdate', None, _('do not update the new working directories')),
    warning: line over 80 characters
   hgext/mq.py:0:
--- a/tests/test-commit-amend.t	Wed May 09 09:58:50 2012 +0200
+++ b/tests/test-commit-amend.t	Sat May 12 00:06:11 2012 +0200
@@ -316,3 +316,37 @@
   $ hg rollback
   no rollback information available
   [1]
+
+Preserve extra dict (issue3430):
+
+  $ hg branch a
+  marked working directory as branch a
+  (branches are permanent and global, did you want a bookmark?)
+  $ echo a >> a
+  $ hg ci -ma
+  $ hg ci --amend -m "a'"
+  saved backup bundle to $TESTTMP/.hg/strip-backup/167f8e3031df-amend-backup.hg
+  $ hg log -r . --template "{branch}\n"
+  a
+  $ hg ci --amend -m "a''"
+  saved backup bundle to $TESTTMP/.hg/strip-backup/ceac1a44c806-amend-backup.hg
+  $ hg log -r . --template "{branch}\n"
+  a
+
+Also preserve other entries in the dict that are in the old commit,
+first graft something so there's an additional entry:
+
+  $ hg up 0 -q
+  $ echo z > z
+  $ hg ci -Am 'fork'
+  adding z
+  created new head
+  $ hg up 11
+  5 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg graft 12
+  grafting revision 12
+  $ hg ci --amend -m 'graft amend'
+  saved backup bundle to $TESTTMP/.hg/strip-backup/18a5124daf7a-amend-backup.hg
+  $ hg log -r . --debug | grep extra
+  extra:       branch=a
+  extra:       source=2647734878ef0236dda712fae9c1651cf694ea8a
--- a/tests/test-mq-qpush-fail.t	Wed May 09 09:58:50 2012 +0200
+++ b/tests/test-mq-qpush-fail.t	Sat May 12 00:06:11 2012 +0200
@@ -150,3 +150,134 @@
   abort: cannot push to a previous patch: a
   [255]
 
+test qpop --force and backup files
+
+  $ hg qpop -a
+  popping b
+  patch queue now empty
+  $ hg qq --create force
+  $ echo a > a
+  $ echo b > b
+  $ echo c > c
+  $ hg ci -Am add a b c
+  $ echo a >> a
+  $ hg rm b
+  $ hg rm c
+  $ hg qnew p1
+  $ echo a >> a
+  $ echo bb > b
+  $ hg add b
+  $ echo cc > c
+  $ hg add c
+  $ hg qpop --force --verbose
+  saving current version of a as a.orig
+  saving current version of b as b.orig
+  saving current version of c as c.orig
+  popping p1
+  patch queue now empty
+  $ hg st
+  ? a.orig
+  ? b.orig
+  ? c.orig
+  ? untracked-file
+  $ cat a.orig
+  a
+  a
+  a
+  $ cat b.orig
+  bb
+  $ cat c.orig
+  cc
+
+test qpop --force --no-backup
+
+  $ hg qpush
+  applying p1
+  now at: p1
+  $ rm a.orig
+  $ echo a >> a
+  $ hg qpop --force --no-backup --verbose
+  popping p1
+  patch queue now empty
+  $ test -f a.orig && echo 'error: backup with --no-backup'
+  [1]
+
+test qpush --force and backup files
+
+  $ echo a >> a
+  $ hg qnew p2
+  $ echo b >> b
+  $ echo d > d
+  $ echo e > e
+  $ hg add d e
+  $ hg rm c
+  $ hg qnew p3
+  $ hg qpop -a
+  popping p3
+  popping p2
+  patch queue now empty
+  $ echo a >> a
+  $ echo b1 >> b
+  $ echo d1 > d
+  $ hg add d
+  $ echo e1 > e
+  $ hg qpush -a --force --verbose
+  applying p2
+  saving current version of a as a.orig
+  patching file a
+  a
+  applying p3
+  saving current version of b as b.orig
+  saving current version of d as d.orig
+  patching file b
+  patching file c
+  patching file d
+  file d already exists
+  1 out of 1 hunks FAILED -- saving rejects to file d.rej
+  patching file e
+  file e already exists
+  1 out of 1 hunks FAILED -- saving rejects to file e.rej
+  patch failed to apply
+  b
+  patch failed, rejects left in working dir
+  errors during apply, please fix and refresh p3
+  [2]
+  $ cat a.orig
+  a
+  a
+  $ cat b.orig
+  b
+  b1
+  $ cat d.orig
+  d1
+
+test qpush --force --no-backup
+
+  $ hg revert -qa
+  $ hg qpop -a
+  popping p3
+  popping p2
+  patch queue now empty
+  $ echo a >> a
+  $ rm a.orig
+  $ hg qpush --force --no-backup --verbose
+  applying p2
+  patching file a
+  a
+  now at: p2
+  $ test -f a.orig && echo 'error: backup with --no-backup'
+  [1]
+
+test qgoto --force --no-backup
+
+  $ hg qpop
+  popping p2
+  patch queue now empty
+  $ echo a >> a
+  $ hg qgoto --force --no-backup p2 --verbose
+  applying p2
+  patching file a
+  a
+  now at: p2
+  $ test -f a.orig && echo 'error: backup with --no-backup'
+  [1]
--- a/tests/test-mq.t	Wed May 09 09:58:50 2012 +0200
+++ b/tests/test-mq.t	Sat May 12 00:06:11 2012 +0200
@@ -1356,11 +1356,15 @@
 
 apply force, should discard changes in hello, but not bye
 
-  $ hg qpush -f
+  $ hg qpush -f --verbose
   applying empty
+  saving current version of hello.txt as hello.txt.orig
+  patching file hello.txt
+  hello.txt
   now at: empty
   $ hg st
   M bye.txt
+  ? hello.txt.orig
   $ hg diff --config diff.nodates=True
   diff -r ba252371dbc1 bye.txt
   --- a/bye.txt
--- a/tests/test-revset.t	Wed May 09 09:58:50 2012 +0200
+++ b/tests/test-revset.t	Sat May 12 00:06:11 2012 +0200
@@ -410,6 +410,10 @@
   0
   $ log '4::8 - 8'
   4
+  $ log 'matching(1 or 2 or 3) and (2 or 3 or 1)'
+  2
+  3
+  1
 
 issue2437