rename from contrib/purge/purge.py
rename to hgext/purge.py
--- a/contrib/purge/purge.py
+++ b/hgext/purge.py
@@ -8,7 +8,7 @@
#
# To enable the "purge" extension put these lines in your ~/.hgrc:
# [extensions]
-# purge = /path/to/purge.py
+# hgext.purge =
#
# For help on the usage of "hg purge" use:
# hg help purge
@@ -31,7 +31,8 @@ from mercurial import hg, util
from mercurial.i18n import _
import os
-def dopurge(ui, repo, dirs=None, act=True, abort_on_err=False, eol='\n'):
+def dopurge(ui, repo, dirs=None, act=True, abort_on_err=False, eol='\n',
+ force=False):
def error(msg):
if abort_on_err:
raise util.Abort(msg)
@@ -49,14 +50,19 @@ def dopurge(ui, repo, dirs=None, act=Tru
directories = []
files = []
+ missing = []
roots, match, anypats = util.cmdmatcher(repo.root, repo.getcwd(), dirs)
for src, f, st in repo.dirstate.statwalk(files=roots, match=match,
ignored=True, directories=True):
if src == 'd':
directories.append(f)
+ elif src == 'm':
+ missing.append(f)
elif src == 'f' and f not in repo.dirstate:
files.append(f)
+ _check_missing(ui, repo, missing, force)
+
directories.sort()
for f in files:
@@ -69,6 +75,43 @@ def dopurge(ui, repo, dirs=None, act=Tru
ui.note(_('Removing directory %s\n') % f)
remove(os.rmdir, f)
+def _check_missing(ui, repo, missing, force=False):
+ """Abort if there is the chance of having problems with name-mangling fs
+
+ In a name mangling filesystem (e.g. a case insensitive one)
+ dirstate.walk() can yield filenames different from the ones
+ stored in the dirstate. This already confuses the status and
+ add commands, but with purge this may cause data loss.
+
+ To prevent this, _check_missing will abort if there are missing
+ files. The force option will let the user skip the check if he
+ knows it is safe.
+
+ Even with the force option this function will check if any of the
+ missing files is still available in the working dir: if so there
+ may be some problem with the underlying filesystem, so it
+ aborts unconditionally."""
+
+ found = [f for f in missing if util.lexists(repo.wjoin(f))]
+
+ if found:
+ if not ui.quiet:
+ ui.warn(_("The following tracked files weren't listed by the "
+ "filesystem, but could still be found:\n"))
+ for f in found:
+ ui.warn("%s\n" % f)
+ if util.checkfolding(repo.path):
+ ui.warn(_("This is probably due to a case-insensitive "
+ "filesystem\n"))
+ raise util.Abort(_("purging on name mangling filesystems is not "
+ "yet fully supported"))
+
+ if missing and not force:
+ raise util.Abort(_("there are missing files in the working dir and "
+ "purge still has problems with them due to name "
+ "mangling filesystems. "
+ "Use --force if you know what you are doing"))
+
def purge(ui, repo, *dirs, **opts):
'''removes files not tracked by mercurial
@@ -100,13 +143,15 @@ def purge(ui, repo, *dirs, **opts):
if eol == '\0':
# --print0 implies --print
act = False
- dopurge(ui, repo, dirs, act, abort_on_err, eol)
+ force = bool(opts['force'])
+ dopurge(ui, repo, dirs, act, abort_on_err, eol, force)
cmdtable = {
'purge':
(purge,
[('a', 'abort-on-err', None, _('abort if an error occurs')),
+ ('f', 'force', None, _('purge even when missing files are detected')),
('p', 'print', None, _('print the file names instead of deleting them')),
('0', 'print0', None, _('end filenames with NUL, for use with xargs'
' (implies -p)'))],
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1658,7 +1658,10 @@ def locate(ui, repo, *pats, **opts):
ret = 1
for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
+ badmatch=util.always,
default='relglob'):
+ if src == 'b':
+ continue
if not node and repo.dirstate.state(abs) == '?':
continue
if opts['fullpath']:
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -447,7 +447,7 @@ def _matcher(canonroot, cwd, names, inc,
if c in _globchars: return True
return False
- def regex(kind, name):
+ def regex(kind, name, tail):
'''convert a pattern into a regular expression'''
if not name:
return ''
@@ -456,23 +456,23 @@ def _matcher(canonroot, cwd, names, inc,
elif kind == 'path':
return '^' + re.escape(name) + '(?:/|$)'
elif kind == 'relglob':
- return globre(name, '(?:|.*/)', '(?:/|$)')
+ return globre(name, '(?:|.*/)', tail)
elif kind == 'relpath':
return re.escape(name) + '(?:/|$)'
elif kind == 'relre':
if name.startswith('^'):
return name
return '.*' + name
- return globre(name, '', '(?:/|$)')
+ return globre(name, '', tail)
- def matchfn(pats):
+ def matchfn(pats, tail):
"""build a matching function from a set of patterns"""
if not pats:
return
matches = []
for k, p in pats:
try:
- pat = '(?:%s)' % regex(k, p)
+ pat = '(?:%s)' % regex(k, p, tail)
matches.append(re.compile(pat).match)
except re.error:
if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
@@ -520,15 +520,15 @@ def _matcher(canonroot, cwd, names, inc,
roots, pats, anypats = normalizepats(names, dflt_pat)
- patmatch = matchfn(pats) or always
+ patmatch = matchfn(pats, '$') or always
incmatch = always
if inc:
dummy, inckinds, dummy = normalizepats(inc, 'glob')
- incmatch = matchfn(inckinds)
+ incmatch = matchfn(inckinds, '(?:/|$)')
excmatch = lambda fn: False
if exc:
dummy, exckinds, dummy = normalizepats(exc, 'glob')
- excmatch = matchfn(exckinds)
+ excmatch = matchfn(exckinds, '(?:/|$)')
if not names and inc and not exc:
# common case: hgignore patterns
--- a/tests/test-locate
+++ b/tests/test-locate
@@ -19,6 +19,8 @@ mkdir t
echo 0 > t/x
echo 0 > t/b
echo 0 > t/e.h
+mkdir dir.h
+echo 0 > dir.h/foo
hg ci -A -m m -d "1000000 0"
touch nottracked
hglocate a && echo locate succeeded || echo locate failed
@@ -28,9 +30,11 @@ hg rm a
hg ci -m m -d "1000000 0"
hglocate a
hglocate NONEXISTENT
+hglocate relpath:NONEXISTENT
hglocate
hglocate -r 0 a
hglocate -r 0 NONEXISTENT
+hglocate -r 0 relpath:NONEXISTENT
hglocate -r 0
echo % -I/-X with relative path should work
cd t
@@ -39,14 +43,14 @@ hglocate -I ../t
# test issue294
cd ..
rm -r t
-hglocate t
+hglocate 't/**'
mkdir otherdir
cd otherdir
hglocate b
hglocate '*.h'
hglocate path:t/x
-hglocate 're:.*\.h'
+hglocate 're:.*\.h$'
hglocate -r 0 b
hglocate -r 0 '*.h'
hglocate -r 0 path:t/x
-hglocate -r 0 're:.*\.h'
+hglocate -r 0 're:.*\.h$'
--- a/tests/test-locate.out
+++ b/tests/test-locate.out
@@ -1,5 +1,6 @@
adding a
adding b
+adding dir.h/foo
adding t.h
adding t/b
adding t/e.h
@@ -14,6 +15,7 @@ locate failed
hg locate
a
b
+dir.h/foo
t.h
t/b
t/e.h
@@ -23,8 +25,11 @@ hg locate a
hg locate NONEXISTENT
+hg locate relpath:NONEXISTENT
+
hg locate
b
+dir.h/foo
t.h
t/b
t/e.h
@@ -35,9 +40,12 @@ a
hg locate -r 0 NONEXISTENT
+hg locate -r 0 relpath:NONEXISTENT
+
hg locate -r 0
a
b
+dir.h/foo
t.h
t/b
t/e.h
@@ -46,6 +54,7 @@ t/x
% -I/-X with relative path should work
hg locate
b
+dir.h/foo
t.h
t/b
t/e.h
@@ -56,7 +65,7 @@ t/b
t/e.h
t/x
-hg locate t
+hg locate t/**
t/b
t/e.h
t/x
@@ -72,7 +81,7 @@ hg locate *.h
hg locate path:t/x
../t/x
-hg locate re:.*\.h
+hg locate re:.*\.h$
../t.h
../t/e.h
@@ -87,7 +96,7 @@ hg locate -r 0 *.h
hg locate -r 0 path:t/x
../t/x
-hg locate -r 0 re:.*\.h
+hg locate -r 0 re:.*\.h$
../t.h
../t/e.h
--- a/tests/test-purge
+++ b/tests/test-purge
@@ -2,7 +2,7 @@
cat <<EOF >> $HGRCPATH
[extensions]
-purge=${TESTDIR}/../contrib/purge/purge.py
+hgext.purge=
EOF
echo % init
@@ -74,3 +74,26 @@ touch ignored
hg purge -p
hg purge -v
ls
+
+echo % abort with missing files until we support name mangling filesystems
+touch untracked_file
+rm r1
+# hide error messages to avoid changing the output when the text changes
+hg purge -p 2> /dev/null
+if [ $? -ne 0 ]; then
+ echo "refused to run"
+fi
+if [ -f untracked_file ]; then
+ echo "untracked_file still around"
+fi
+hg purge -p --force
+hg purge -v 2> /dev/null
+if [ $? -ne 0 ]; then
+ echo "refused to run"
+fi
+if [ -f untracked_file ]; then
+ echo "untracked_file still around"
+fi
+hg purge -v --force
+hg revert --all --quiet
+ls
--- a/tests/test-purge.out
+++ b/tests/test-purge.out
@@ -47,3 +47,12 @@ ignored
Removing file ignored
directory
r1
+% abort with missing files until we support name mangling filesystems
+refused to run
+untracked_file still around
+untracked_file
+refused to run
+untracked_file still around
+Removing file untracked_file
+directory
+r1
--- a/tests/test-walk
+++ b/tests/test-walk
@@ -72,11 +72,13 @@ debugwalk beans/.hg
#debugwalk `pwd`/beans
#debugwalk `pwd`/..
debugwalk glob:\*
+debugwalk 'glob:**e'
debugwalk 're:.*[kb]$'
debugwalk path:beans/black
debugwalk path:beans//black
debugwalk relglob:Procyonidae
-debugwalk relglob:Procyonidae/ fennel
+debugwalk 'relglob:Procyonidae/**'
+debugwalk 'relglob:Procyonidae/**' fennel
debugwalk beans 'glob:beans/*'
debugwalk 'glob:mamm**'
debugwalk 'glob:mamm**' fennel
--- a/tests/test-walk.out
+++ b/tests/test-walk.out
@@ -210,20 +210,14 @@ hg debugwalk beans/.hg
beans/.hg: No such file or directory
hg debugwalk glob:*
-f beans/black beans/black
-f beans/borlotti beans/borlotti
-f beans/kidney beans/kidney
-f beans/navy beans/navy
-f beans/pinto beans/pinto
+f fennel fennel
+f fenugreek fenugreek
+f fiddlehead fiddlehead
+f glob:glob glob:glob
+
+hg debugwalk glob:**e
f beans/turtle beans/turtle
-f fennel fennel
-f fenugreek fenugreek
-f fiddlehead fiddlehead
-f glob:glob glob:glob
f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
-f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
-f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
-f mammals/skunk mammals/skunk
hg debugwalk re:.*[kb]$
f beans/black beans/black
@@ -238,11 +232,13 @@ hg debugwalk path:beans//black
f beans/black beans/black exact
hg debugwalk relglob:Procyonidae
+
+hg debugwalk relglob:Procyonidae/**
f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
-hg debugwalk relglob:Procyonidae/ fennel
+hg debugwalk relglob:Procyonidae/** fennel
f fennel fennel exact
f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi