##// END OF EJS Templates
purge: simplify safety net for case mangling filesystems...
Alexis S. L. Carvalho -
r5517:98d5f9b9 default
parent child Browse files
Show More
@@ -1,167 +1,152 b''
1 1 # Copyright (C) 2006 - Marco Barisione <marco@barisione.org>
2 2 #
3 3 # This is a small extension for Mercurial (http://www.selenic.com/mercurial)
4 4 # that removes files not known to mercurial
5 5 #
6 6 # This program was inspired by the "cvspurge" script contained in CVS utilities
7 7 # (http://www.red-bean.com/cvsutils/).
8 8 #
9 9 # To enable the "purge" extension put these lines in your ~/.hgrc:
10 10 # [extensions]
11 11 # hgext.purge =
12 12 #
13 13 # For help on the usage of "hg purge" use:
14 14 # hg help purge
15 15 #
16 16 # This program is free software; you can redistribute it and/or modify
17 17 # it under the terms of the GNU General Public License as published by
18 18 # the Free Software Foundation; either version 2 of the License, or
19 19 # (at your option) any later version.
20 20 #
21 21 # This program is distributed in the hope that it will be useful,
22 22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 24 # GNU General Public License for more details.
25 25 #
26 26 # You should have received a copy of the GNU General Public License
27 27 # along with this program; if not, write to the Free Software
28 28 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 29
30 30 from mercurial import hg, util, commands
31 31 from mercurial.i18n import _
32 32 import os
33 33
34 34 def dopurge(ui, repo, dirs=None, act=True, ignored=False,
35 35 abort_on_err=False, eol='\n',
36 36 force=False, include=None, exclude=None):
37 37 def error(msg):
38 38 if abort_on_err:
39 39 raise util.Abort(msg)
40 40 else:
41 41 ui.warn(_('warning: %s\n') % msg)
42 42
43 43 def remove(remove_func, name):
44 44 if act:
45 45 try:
46 46 remove_func(os.path.join(repo.root, name))
47 47 except OSError, e:
48 48 error(_('%s cannot be removed') % name)
49 49 else:
50 50 ui.write('%s%s' % (name, eol))
51 51
52 if not force:
53 _check_fs(ui, repo)
54
52 55 directories = []
53 56 files = []
54 57 missing = []
55 58 roots, match, anypats = util.cmdmatcher(repo.root, repo.getcwd(), dirs,
56 59 include, exclude)
57 60 for src, f, st in repo.dirstate.statwalk(files=roots, match=match,
58 61 ignored=ignored, directories=True):
59 62 if src == 'd':
60 63 directories.append(f)
61 64 elif src == 'm':
62 65 missing.append(f)
63 66 elif src == 'f' and f not in repo.dirstate:
64 67 files.append(f)
65 68
66 _check_missing(ui, repo, missing, force)
67
68 69 directories.sort()
69 70
70 71 for f in files:
71 72 if f not in repo.dirstate:
72 73 ui.note(_('Removing file %s\n') % f)
73 74 remove(os.remove, f)
74 75
75 76 for f in directories[::-1]:
76 77 if match(f) and not os.listdir(repo.wjoin(f)):
77 78 ui.note(_('Removing directory %s\n') % f)
78 79 remove(os.rmdir, f)
79 80
80 def _check_missing(ui, repo, missing, force=False):
81 def _check_fs(ui, repo):
81 82 """Abort if there is the chance of having problems with name-mangling fs
82 83
83 84 In a name mangling filesystem (e.g. a case insensitive one)
84 85 dirstate.walk() can yield filenames different from the ones
85 86 stored in the dirstate. This already confuses the status and
86 87 add commands, but with purge this may cause data loss.
87 88
88 To prevent this, _check_missing will abort if there are missing
89 files. The force option will let the user skip the check if he
90 knows it is safe.
91
92 Even with the force option this function will check if any of the
93 missing files is still available in the working dir: if so there
94 may be some problem with the underlying filesystem, so it
95 aborts unconditionally."""
96
97 found = [f for f in missing if util.lexists(repo.wjoin(f))]
89 To prevent this, this function will abort if there are uncommitted
90 changes.
91 """
98 92
99 if found:
100 if not ui.quiet:
101 ui.warn(_("The following tracked files weren't listed by the "
102 "filesystem, but could still be found:\n"))
103 for f in found:
104 ui.warn("%s\n" % f)
105 if util.checkfolding(repo.path):
106 ui.warn(_("This is probably due to a case-insensitive "
107 "filesystem\n"))
108 raise util.Abort(_("purging on name mangling filesystems is not "
109 "yet fully supported"))
110
111 if missing and not force:
112 raise util.Abort(_("there are missing files in the working dir and "
113 "purge still has problems with them due to name "
114 "mangling filesystems. "
115 "Use --force if you know what you are doing"))
93 # We can't use (files, match) to do a partial walk here - we wouldn't
94 # notice a modified README file if the user ran "hg purge readme"
95 modified, added, removed, deleted = repo.status()[:4]
96 if modified or added or removed or deleted:
97 if not util.checkfolding(repo.path) and not ui.quiet:
98 ui.warn(_("Purging on name mangling filesystems is not "
99 "fully supported.\n"))
100 raise util.Abort(_("outstanding uncommitted changes"))
116 101
117 102
118 103 def purge(ui, repo, *dirs, **opts):
119 104 '''removes files not tracked by mercurial
120 105
121 106 Delete files not known to mercurial, this is useful to test local and
122 107 uncommitted changes in the otherwise clean source tree.
123 108
124 109 This means that purge will delete:
125 110 - Unknown files: files marked with "?" by "hg status"
126 111 - Ignored files: files usually ignored by Mercurial because they match
127 112 a pattern in a ".hgignore" file
128 113 - Empty directories: in fact Mercurial ignores directories unless they
129 114 contain files under source control managment
130 115 But it will leave untouched:
131 116 - Unmodified tracked files
132 117 - Modified tracked files
133 118 - New files added to the repository (with "hg add")
134 119
135 120 If directories are given on the command line, only files in these
136 121 directories are considered.
137 122
138 123 Be careful with purge, you could irreversibly delete some files you
139 124 forgot to add to the repository. If you only want to print the list of
140 125 files that this program would delete use the --print option.
141 126 '''
142 127 act = not opts['print']
143 128 ignored = bool(opts['all'])
144 129 abort_on_err = bool(opts['abort_on_err'])
145 130 eol = opts['print0'] and '\0' or '\n'
146 131 if eol == '\0':
147 132 # --print0 implies --print
148 133 act = False
149 134 force = bool(opts['force'])
150 135 include = opts['include']
151 136 exclude = opts['exclude']
152 137 dopurge(ui, repo, dirs, act, ignored, abort_on_err,
153 138 eol, force, include, exclude)
154 139
155 140
156 141 cmdtable = {
157 142 'purge|clean':
158 143 (purge,
159 144 [('a', 'abort-on-err', None, _('abort if an error occurs')),
160 145 ('', 'all', None, _('purge ignored files too')),
161 ('f', 'force', None, _('purge even when missing files are detected')),
146 ('f', 'force', None, _('purge even when there are uncommitted changes')),
162 147 ('p', 'print', None, _('print the file names instead of deleting them')),
163 148 ('0', 'print0', None, _('end filenames with NUL, for use with xargs'
164 149 ' (implies -p)')),
165 150 ] + commands.walkopts,
166 151 _('hg purge [OPTION]... [DIR]...'))
167 152 }
@@ -1,133 +1,140 b''
1 1 #!/bin/sh
2 2
3 3 cat <<EOF >> $HGRCPATH
4 4 [extensions]
5 5 hgext.purge=
6 6 EOF
7 7
8 8 echo % init
9 9 hg init t
10 10 cd t
11 11
12 12 echo % setup
13 13 echo r1 > r1
14 14 hg ci -qAmr1 -d'0 0'
15 15 mkdir directory
16 16 echo r2 > directory/r2
17 17 hg ci -qAmr2 -d'1 0'
18 18 echo 'ignored' > .hgignore
19 19 hg ci -qAmr3 -d'2 0'
20 20
21 21 echo % delete an empty directory
22 22 mkdir empty_dir
23 23 hg purge -p
24 24 hg purge -v
25 25 ls
26 26
27 27 echo % delete an untracked directory
28 28 mkdir untracked_dir
29 29 touch untracked_dir/untracked_file1
30 30 touch untracked_dir/untracked_file2
31 31 hg purge -p
32 32 hg purge -v
33 33 ls
34 34
35 35 echo % delete an untracked file
36 36 touch untracked_file
37 37 hg purge -p
38 38 hg purge -v
39 39 ls
40 40
41 41 echo % delete an untracked file in a tracked directory
42 42 touch directory/untracked_file
43 43 hg purge -p
44 44 hg purge -v
45 45 ls
46 46
47 47 echo % delete nested directories
48 48 mkdir -p untracked_directory/nested_directory
49 49 hg purge -p
50 50 hg purge -v
51 51 ls
52 52
53 53 echo % delete nested directories from a subdir
54 54 mkdir -p untracked_directory/nested_directory
55 55 cd directory
56 56 hg purge -p
57 57 hg purge -v
58 58 cd ..
59 59 ls
60 60
61 61 echo % delete only part of the tree
62 62 mkdir -p untracked_directory/nested_directory
63 63 touch directory/untracked_file
64 64 cd directory
65 65 hg purge -p ../untracked_directory
66 66 hg purge -v ../untracked_directory
67 67 cd ..
68 68 ls
69 69 ls directory/untracked_file
70 70 rm directory/untracked_file
71 71
72 72 echo % skip ignored files if --all not specified
73 73 touch ignored
74 74 hg purge -p
75 75 hg purge -v
76 76 ls
77 77 hg purge -p --all
78 78 hg purge -v --all
79 79 ls
80 80
81 81 echo % abort with missing files until we support name mangling filesystems
82 82 touch untracked_file
83 83 rm r1
84 84 # hide error messages to avoid changing the output when the text changes
85 85 hg purge -p 2> /dev/null
86 86 if [ $? -ne 0 ]; then
87 87 echo "refused to run"
88 88 fi
89 89 if [ -f untracked_file ]; then
90 90 echo "untracked_file still around"
91 91 fi
92 92 hg purge -p --force
93 93 hg purge -v 2> /dev/null
94 94 if [ $? -ne 0 ]; then
95 95 echo "refused to run"
96 96 fi
97 97 if [ -f untracked_file ]; then
98 98 echo "untracked_file still around"
99 99 fi
100 100 hg purge -v --force
101 101 hg revert --all --quiet
102 102 ls
103 103
104 echo '% tracked file in ignored directory (issue621)'
105 echo directory >> .hgignore
106 hg ci -m 'ignore directory'
107 touch untracked_file
108 hg purge -p
109 hg purge -v
110
104 111 echo % skip excluded files
105 112 touch excluded_file
106 113 hg purge -p -X excluded_file
107 114 hg purge -v -X excluded_file
108 115 ls
109 116 rm excluded_file
110 117
111 118 echo % skip files in excluded dirs
112 119 mkdir excluded_dir
113 120 touch excluded_dir/file
114 121 hg purge -p -X excluded_dir
115 122 hg purge -v -X excluded_dir
116 123 ls
117 124 ls excluded_dir
118 125 rm -R excluded_dir
119 126
120 127 echo % skip excluded empty dirs
121 128 mkdir excluded_dir
122 129 hg purge -p -X excluded_dir
123 130 hg purge -v -X excluded_dir
124 131 ls
125 132 rmdir excluded_dir
126 133
127 134 echo % skip patterns
128 135 mkdir .svn
129 136 touch .svn/foo
130 137 mkdir directory/.svn
131 138 touch directory/.svn/foo
132 139 hg purge -p -X .svn -X '*/.svn'
133 140 hg purge -p -X re:.*.svn
@@ -1,75 +1,78 b''
1 1 % init
2 2 % setup
3 3 % delete an empty directory
4 4 empty_dir
5 5 Removing directory empty_dir
6 6 directory
7 7 r1
8 8 % delete an untracked directory
9 9 untracked_dir/untracked_file1
10 10 untracked_dir/untracked_file2
11 11 Removing file untracked_dir/untracked_file1
12 12 Removing file untracked_dir/untracked_file2
13 13 Removing directory untracked_dir
14 14 directory
15 15 r1
16 16 % delete an untracked file
17 17 untracked_file
18 18 Removing file untracked_file
19 19 directory
20 20 r1
21 21 % delete an untracked file in a tracked directory
22 22 directory/untracked_file
23 23 Removing file directory/untracked_file
24 24 directory
25 25 r1
26 26 % delete nested directories
27 27 untracked_directory/nested_directory
28 28 Removing directory untracked_directory/nested_directory
29 29 Removing directory untracked_directory
30 30 directory
31 31 r1
32 32 % delete nested directories from a subdir
33 33 untracked_directory/nested_directory
34 34 Removing directory untracked_directory/nested_directory
35 35 Removing directory untracked_directory
36 36 directory
37 37 r1
38 38 % delete only part of the tree
39 39 untracked_directory/nested_directory
40 40 Removing directory untracked_directory/nested_directory
41 41 Removing directory untracked_directory
42 42 directory
43 43 r1
44 44 directory/untracked_file
45 45 % skip ignored files if --all not specified
46 46 directory
47 47 ignored
48 48 r1
49 49 ignored
50 50 Removing file ignored
51 51 directory
52 52 r1
53 53 % abort with missing files until we support name mangling filesystems
54 54 refused to run
55 55 untracked_file still around
56 56 untracked_file
57 57 refused to run
58 58 untracked_file still around
59 59 Removing file untracked_file
60 60 directory
61 61 r1
62 % tracked file in ignored directory (issue621)
63 untracked_file
64 Removing file untracked_file
62 65 % skip excluded files
63 66 directory
64 67 excluded_file
65 68 r1
66 69 % skip files in excluded dirs
67 70 directory
68 71 excluded_dir
69 72 r1
70 73 file
71 74 % skip excluded empty dirs
72 75 directory
73 76 excluded_dir
74 77 r1
75 78 % skip patterns
General Comments 0
You need to be logged in to leave comments. Login now