qpush asserts that patches do not affect unknown files
* * *
diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
-599,6 +599,31 @@ class queue:
else:
raise util.Abort(_("local changes found"))
return m, a, r, d
+
+ def get_unknown(self, repo, affectedfiles):
+ unknownfiles = set([])
+ for f in affectedfiles:
+ if util.lexists(repo.wjoin(f)):
+ if f not in repo.dirstate:
+ unknownfiles.add(f)
+ return unknownfiles
+
+ def check_unknown(self, repo, patchname, patchdir=None):
+ if not patchdir:
+ patchdir = self.path
+
+ patchpath = os.path.join(patchdir, patchname)
+
+ affectedfiles = patch.getaffected(patchpath, self.ui, strip=1)
+ if len(affectedfiles) == 0:
+ if os.stat(patchpath).st_size > 0:
+ self.ui.warn(_('Could not detect files that will be affected by %s. Can not assure that files are added and committed.\n') % patchname)
+ # or could force check of all files unless Force is true or something like that...
+
+ unknownfiles = self.get_unknown(repo, affectedfiles)
+ if len(unknownfiles) > 0:
+ raise util.Abort(_('Patch %s can not be applied. Patch effects unknown files %s') % (patchname, unknownfiles))
+
def new(self, repo, patch, *pats, **opts):
msg = opts.get('msg')
-781,6 +806,11 @@ class queue:
else:
end = self.series.index(patch, start) + 1
s = self.series[start:end]
+
+ if not force:
+ for patchname in s:
+ self.check_unknown(repo, patchname);
+
all_files = {}
try:
if mergeq:
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
-496,6 +496,50 @@ class patchfile:
self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
self.rej.append(h)
return -1
+
+def getaffected(patchfilepath, ui, strip=1):
+ lsdiff = ui.config('ui', 'lsdiff')
+ if lsdiff:
+ return externalgetaffected(lsdiff, patchfilepath, strip);
+ else:
+ return internalgetaffected(patchfilepath, strip);
+
+
+def externalgetaffected(lsdiff, patchfilepath, strip=1):
+ fp = os.popen('%s %s' % (lsdiff, util.shellquote(patchfilepath)))
+ files = []
+
+ for line in fp:
+ affectedfile = line.strip()
+ files.append(pathstrip(affectedfile, strip))
+ code = fp.close()
+ if code:
+ raise PatchError(_("lsdiff command failed: %s") %
+ util.explain_exit(code)[0])
+ return files;
+
+
+def internalgetaffected(patchfilepath, strip=1):
+ files = set()
+ difftag = re.compile("^diff( --?[^\\s]*)* a/.* (b/.*)$") # doesn't handle c-style quotes
+ orig_pattern = re.compile("^((\\*{3})|(\\-{3}))\\s") # *** or +++
+ new_pattern = re.compile("^((\\-{3})|(\\+{3}))\\s([^\\t]*)")
+ found_orig=False;
+ for line in file(patchfilepath):
+ diffmatcher = difftag.match(line)
+ if diffmatcher != None:
+ files.add(pathstrip(diffmatcher.group(2), strip))
+
+ if not found_orig:
+ matcher = orig_pattern.match(line);
+ if matcher != None:
+ found_orig=True
+ else:
+ found_orig=False
+ matcher = new_pattern.match(line)
+ if matcher != None:
+ files.add(pathstrip(matcher.group(4), strip))
+ return files
class hunk:
def __init__(self, desc, num, lr, context):
-774,24 +818,24 @@ def parsefilename(str):
return s
return s[:i]
+def pathstrip(path, count=1):
+ pathlen = len(path)
+ i = 0
+ if count == 0:
+ return path.rstrip()
+ while count > 0:
+ i = path.find('/', i)
+ if i == -1:
+ raise PatchError(_("unable to strip away %d dirs from %s") %
+ (count, path))
+ i += 1
+ # consume '//' in the path
+ while i < pathlen - 1 and path[i] == '/':
+ i += 1
+ count -= 1
+ return path[i:].rstrip()
+
def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
- def pathstrip(path, count=1):
- pathlen = len(path)
- i = 0
- if count == 0:
- return path.rstrip()
- while count > 0:
- i = path.find('/', i)
- if i == -1:
- raise PatchError(_("unable to strip away %d dirs from %s") %
- (count, path))
- i += 1
- # consume '//' in the path
- while i < pathlen - 1 and path[i] == '/':
- i += 1
- count -= 1
- return path[i:].rstrip()
-
nulla = afile_orig == "/dev/null"
nullb = bfile_orig == "/dev/null"
afile = pathstrip(afile_orig, strip)
diff --git a/tests/test-mq-qpush-error b/tests/test-mq-qpush-error
new file mode 100755
--- /dev/null
+++ b/tests/test-mq-qpush-error
-0,0 +1,21 @@
+#!/bin/bash
+
+echo "[extensions]" >> $HGRCPATH
+echo "mq=" >> $HGRCPATH
+
+mkdir tmp1
+mkdir tmp2
+
+echo hi >> tmp1/foo
+echo h2 >> tmp2/foo
+
+diff -uNr tmp1 tmp2 > unified.patch
+
+cd tmp1
+hg init
+hg qinit
+hg qimport ../unified.patch
+hg qpush
+hg qpop
+#ls: foo: No such file or directory
+ls -1 foo
diff --git a/tests/test-mq-qpush-error.out b/tests/test-mq-qpush-error.out
new file mode 100644
--- /dev/null
+++ b/tests/test-mq-qpush-error.out
-0,0 +1,4 @@
+adding unified.patch to series file
+abort: Patch unified.patch can not be applied. Patch effects unknown files set(['foo'])
+no patches applied
+foo
diff --git a/tests/test-mq-qpush-unknown b/tests/test-mq-qpush-unknown
new file mode 100755
--- /dev/null
+++ b/tests/test-mq-qpush-unknown
-0,0 +1,229 @@
+#!/bin/bash
+
+echo "[extensions]" >> $HGRCPATH
+echo "mq=" >> $HGRCPATH
+
+mkdir tmp1
+mkdir tmp1/dir
+mkdir tmp2
+mkdir tmp2/dir
+
+echo hi >> tmp1/foo
+echo hi >> tmp1/foo
+echo hi >> tmp1/foo
+echo hi >> tmp1/foo
+
+echo hi >> tmp1/dir/bar
+echo hi >> tmp1/dir/bar
+echo hi >> tmp1/dir/bar
+echo hi >> tmp1/dir/bar
+
+echo hi >> tmp2/foo
+echo h2 >> tmp2/foo
+echo hi >> tmp2/foo
+echo h4 >> tmp2/foo
+
+echo hi >> tmp2/dir/bar
+echo hi >> tmp2/dir/bar
+echo h3 >> tmp2/dir/bar
+echo hi >> tmp2/dir/bar
+
+diff -uNr tmp1 tmp2 > unified.patch
+diff -eNr tmp1 tmp2 > ed.patch
+diff -nNr tmp1 tmp2 > normal.patch
+diff -cNr tmp1 tmp2 > context.patch
+
+# don't want to require git just to support. Pregenerated.
+cat > git.patch <<EOF
+diff --git a/dir/bar b/dir/bar
+index af8b2b3..49a5f2f 100644
+--- a/dir/bar
++++ b/dir/bar
+@@ -1,4 +1,4 @@
+ hi
+ hi
+-hi
++h3
+ hi
+diff --git a/foo b/foo
+index af8b2b3..8953a92 100644
+--- a/foo
++++ b/foo
+@@ -1,4 +1,4 @@
+ hi
++h2
+ hi
+-hi
+-hi
++h4
+EOF
+
+echo % qpush unknown correct
+cp -r tmp1 tmp3
+cd tmp3
+hg init
+hg add
+hg commit -m "test"
+hg qinit
+hg qimport ../unified.patch
+hg qpush
+cat foo | grep h2
+cat dir/bar | grep h3
+hg qpop
+cat foo | grep hi | wc -l
+cat dir/bar | grep hi | wc -l
+cd ..
+rm -rf tmp3
+
+echo % qpush unknown correct 2
+cp -r tmp1 tmp3
+cd tmp3
+hg init
+hg add
+hg commit -m "test"
+hg qinit
+hg qimport ../context.patch
+hg qpush
+cat foo | grep h2
+cat dir/bar | grep h3
+hg qpop
+cat foo | grep hi | wc -l
+cat dir/bar | grep hi | wc -l
+cd ..
+rm -rf tmp3
+
+echo % qpush unknown correct 3
+cp -r tmp1 tmp3
+cd tmp3
+hg init
+hg add
+hg commit -m "test"
+hg qinit
+hg qimport ../git.patch
+hg qpush
+cat foo | grep h2
+cat dir/bar | grep h3
+hg qpop
+cat foo | grep hi | wc -l
+cat dir/bar | grep hi | wc -l
+cd ..
+rm -rf tmp3
+
+echo % qpush unknown correct 4 - qpush from sub directory
+cp -r tmp1 tmp3
+cd tmp3
+hg init
+hg add
+hg commit -m "test"
+hg qinit
+hg qimport ../git.patch
+cd dir
+hg qpush
+cd ..
+cat foo | grep h2
+cat dir/bar | grep h3
+hg qpop
+cat foo | grep hi | wc -l
+cat dir/bar | grep hi | wc -l
+cd ..
+rm -rf tmp3
+
+echo % qpush unknown warn
+cp -r tmp1 tmp3
+cd tmp3
+hg init
+hg add
+hg commit -m "test"
+hg qinit
+hg qimport ../ed.patch
+hg qpush
+cd ..
+rm -rf tmp3
+
+echo % qpush unknown warn 2
+cp -r tmp1 tmp3
+cd tmp3
+hg init
+hg add
+hg commit -m "test"
+hg qinit
+hg qimport ../normal.patch
+hg qpush
+cd ..
+rm -rf tmp3
+
+echo % qpush unknown error
+cp -r tmp1 tmp3
+cd tmp3
+hg init
+hg add foo # but not bar
+hg qinit
+hg qimport ../unified.patch
+hg qpush
+cd ..
+rm -rf tmp3
+
+echo % qpush unknown error 2
+cp -r tmp1 tmp3
+cd tmp3
+hg init
+hg add dir/bar # but not foo
+hg qinit
+hg qimport ../context.patch
+hg qpush
+cd ..
+rm -rf tmp3
+
+echo % qpush unknown error 3
+cp -r tmp1 tmp3
+cd tmp3
+hg init
+# add nothing
+hg qinit
+hg qimport ../git.patch
+hg qpush
+cd ..
+rm -rf tmp3
+
+
+echo % create bgit.patch
+
+cp -rp tmp1 tmp3
+cd tmp3
+hg init
+hg qinit
+hg qnew binary
+
+cat > writebin.py <<EOF
+import sys
+open('binaryfile', 'wb').write('BIN\x00ARY')
+EOF
+python writebin.py
+hg add binaryfile
+hg qref --git
+cp .hg/patches/binary ../bgit.patch
+cd ..
+rm -r tmp3
+
+echo % bgit.patch works
+
+cp -rp tmp1 tmp3
+cd tmp3
+hg init
+hg qinit
+hg qimport ../bgit.patch
+hg qpush
+cd ..
+rm -rf tmp3
+
+echo % bgit.patch unknown
+
+cp -rp tmp1 tmp3
+cd tmp3
+hg init
+hg qinit
+echo hi > binaryfile
+hg qimport ../bgit.patch
+hg qpush
+cd ..
+rm -rf tmp3
diff --git a/tests/test-mq-qpush-unknown.out b/tests/test-mq-qpush-unknown.out
new file mode 100644
--- /dev/null
+++ b/tests/test-mq-qpush-unknown.out
-0,0 +1,80 @@
+% qpush unknown correct
+adding dir/bar
+adding foo
+adding unified.patch to series file
+applying unified.patch
+Now at: unified.patch
+h2
+h3
+Patch queue now empty
+4
+4
+% qpush unknown correct 2
+adding dir/bar
+adding foo
+adding context.patch to series file
+applying context.patch
+Now at: context.patch
+h2
+h3
+Patch queue now empty
+4
+4
+% qpush unknown correct 3
+adding dir/bar
+adding foo
+adding git.patch to series file
+applying git.patch
+Now at: git.patch
+h2
+h3
+Patch queue now empty
+4
+4
+% qpush unknown correct 4 - qpush from sub directory
+adding dir/bar
+adding foo
+adding git.patch to series file
+applying git.patch
+Now at: git.patch
+h2
+h3
+Patch queue now empty
+4
+4
+% qpush unknown warn
+adding dir/bar
+adding foo
+adding ed.patch to series file
+Could not detect files that will be affected by ed.patch. Can not assure that files are added and committed.
+applying ed.patch
+patch failed, unable to continue (try -v)
+patch ed.patch is empty
+Now at: ed.patch
+% qpush unknown warn 2
+adding dir/bar
+adding foo
+adding normal.patch to series file
+Could not detect files that will be affected by normal.patch. Can not assure that files are added and committed.
+applying normal.patch
+/usr/bin/patch: **** Only garbage was found in the patch input.
+patch failed, unable to continue (try -v)
+patch normal.patch is empty
+Now at: normal.patch
+% qpush unknown error
+adding unified.patch to series file
+abort: local changes found, refresh first
+% qpush unknown error 2
+adding context.patch to series file
+abort: local changes found, refresh first
+% qpush unknown error 3
+adding git.patch to series file
+abort: Patch git.patch can not be applied. Patch effects unknown files set(['dir/bar', 'foo'])
+% create bgit.patch
+% bgit.patch works
+adding bgit.patch to series file
+applying bgit.patch
+Now at: bgit.patch
+% bgit.patch unknown
+adding bgit.patch to series file
+abort: Patch bgit.patch can not be applied. Patch effects unknown files set(['binaryfile'])
diff --git a/tests/test-mq.out b/tests/test-mq.out
--- a/tests/test-mq.out
+++ b/tests/test-mq.out
-260,19 +260,10 @@ M a
M a
% qpush failure
Patch queue now empty
-applying foo
-applying bar
-file foo already exists
-1 out of 1 hunk FAILED -- saving rejects to file foo.rej
-patch failed, unable to continue (try -v)
-patch failed, rejects left in working dir
-Errors during apply, please fix and refresh bar
+abort: Patch bar can not be applied. Patch effects unknown files set(['foo'])
? foo
-? foo.rej
% mq tags
-0 qparent
-1 qbase foo
-2 qtip bar tip
+abort: unknown revision 'qparent'!
new file
diff --git a/new b/new
diff --git a/tests/test-patch-affected b/tests/test-patch-affected
new file mode 100755
--- /dev/null
+++ b/tests/test-patch-affected
-0,0 +1,81 @@
+#!/bin/bash
+
+mkdir tmp1
+mkdir tmp1/dir
+mkdir tmp2
+mkdir tmp2/dir
+
+echo hi >> tmp1/foo
+echo hi >> tmp1/foo
+echo hi >> tmp1/foo
+echo hi >> tmp1/foo
+
+echo hi >> tmp1/dir/bar
+echo hi >> tmp1/dir/bar
+echo hi >> tmp1/dir/bar
+echo hi >> tmp1/dir/bar
+
+echo hi >> tmp2/foo
+echo h2 >> tmp2/foo
+echo hi >> tmp2/foo
+echo h4 >> tmp2/foo
+
+echo hi >> tmp2/dir/bar
+echo hi >> tmp2/dir/bar
+echo h3 >> tmp2/dir/bar
+echo hi >> tmp2/dir/bar
+
+diff -uNr tmp1 tmp2 > unified.patch
+diff -eNr tmp1 tmp2 > ed.patch
+diff -nNr tmp1 tmp2 > normal.patch
+diff -cNr tmp1 tmp2 > context.patch
+
+# don't want to require git just to support. Pregenerated.
+cat > git.patch <<EOF
+diff --git a/dir/bar b/dir/bar
+index af8b2b3..49a5f2f 100644
+--- a/dir/bar
++++ b/dir/bar
+@@ -1,4 +1,4 @@
+ hi
+ hi
+-hi
++h3
+ hi
+diff --git a/foo b/foo
+index af8b2b3..8953a92 100644
+--- a/foo
++++ b/foo
+@@ -1,4 +1,4 @@
+ hi
++h2
+ hi
+-hi
+-hi
++h4
+EOF
+
+cat > patch.py <<EOF
+from mercurial import commands, cmdutil, hg, patch, revlog, util
+def printaffected(patchfilepath):
+ files = patch.internalgetaffected(patchfilepath);
+ for f in files:
+ print f;
+
+print("% testing getaffected: unified");
+printaffected("unified.patch");
+
+print("% testing getaffected: ed");
+printaffected("ed.patch");
+
+print("% testing getaffected: normal");
+printaffected("normal.patch");
+
+print("% testing getaffected: context");
+printaffected("context.patch");
+
+print("% testing getaffected: git");
+printaffected("git.patch");
+EOF
+
+python patch.py
diff --git a/tests/test-patch-affected-external b/tests/test-patch-affected-external
new file mode 100755
--- /dev/null
+++ b/tests/test-patch-affected-external
-0,0 +1,47 @@
+#!/bin/bash
+
+echo "[ui]" >> $HGRCPATH
+echo "lsdiff=lsdiff" >> $HGRCPATH
+
+mkdir tmp1
+mkdir tmp1/dir
+mkdir tmp2
+mkdir tmp2/dir
+
+echo hi >> tmp1/foo
+echo hi >> tmp1/foo
+echo hi >> tmp1/foo
+echo hi >> tmp1/foo
+
+echo hi >> tmp1/dir/bar
+echo hi >> tmp1/dir/bar
+echo hi >> tmp1/dir/bar
+echo hi >> tmp1/dir/bar
+
+echo hi >> tmp2/foo
+echo h2 >> tmp2/foo
+echo hi >> tmp2/foo
+echo h4 >> tmp2/foo
+
+echo hi >> tmp2/dir/bar
+echo hi >> tmp2/dir/bar
+echo h3 >> tmp2/dir/bar
+echo hi >> tmp2/dir/bar
+
+diff -cNr tmp1 tmp2 > context.patch
+
+cat > patch.py <<EOF
+from mercurial import commands, cmdutil, hg, patch, revlog, util
+from mercurial import ui as _ui
+
+def printaffected(patchfilepath):
+ u = _ui.ui()
+ files = patch.getaffected(patchfilepath, u)
+ for f in files:
+ print f;
+
+print("% testing getaffected: context");
+printaffected("context.patch");
+EOF
+
+python patch.py
diff --git a/tests/test-patch-affected-external.out b/tests/test-patch-affected-external.out
new file mode 100644
--- /dev/null
+++ b/tests/test-patch-affected-external.out
-0,0 +1,3 @@
+% testing getaffected: context
+dir/bar
+foo
diff --git a/tests/test-patch-affected.out b/tests/test-patch-affected.out
new file mode 100644
--- /dev/null
+++ b/tests/test-patch-affected.out
-0,0 +1,11 @@
+% testing getaffected: unified
+dir/bar
+foo
+% testing getaffected: ed
+% testing getaffected: normal
+% testing getaffected: context
+dir/bar
+foo
+% testing getaffected: git
+dir/bar
+foo