##// END OF EJS Templates
Make the purge extension use the statwalk walker from the dirstate object
Emanuele Aina -
r4147:691f9168 default
parent child Browse files
Show More
@@ -1,176 +1,178 b''
1 # Copyright (C) 2006 - Marco Barisione <marco@barisione.org>
1 # Copyright (C) 2006 - Marco Barisione <marco@barisione.org>
2 #
2 #
3 # This is a small extension for Mercurial (http://www.selenic.com/mercurial)
3 # This is a small extension for Mercurial (http://www.selenic.com/mercurial)
4 # that removes files not known to mercurial
4 # that removes files not known to mercurial
5 #
5 #
6 # This program is free software; you can redistribute it and/or modify
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
9 # (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
19
20 from mercurial import hg, util
20 from mercurial import hg, util
21 from mercurial.i18n import _
21 from mercurial.i18n import _
22 import os
22 import os
23
23
24 class Purge(object):
24 class Purge(object):
25 def __init__(self, act=True, abort_on_err=False, eol='\n'):
25 def __init__(self, act=True, abort_on_err=False, eol='\n'):
26 self._repo = None
26 self._repo = None
27 self._ui = None
27 self._ui = None
28 self._hg_root = None
28 self._hg_root = None
29 self._act = act
29 self._act = act
30 self._abort_on_err = abort_on_err
30 self._abort_on_err = abort_on_err
31 self._eol = eol
31 self._eol = eol
32
32
33 def purge(self, ui, repo, dirs=None):
33 def purge(self, ui, repo, dirs=None):
34 self._repo = repo
34 self._repo = repo
35 self._ui = ui
35 self._ui = ui
36 self._hg_root = self._split_path(repo.root)
36 self._hg_root = self._split_path(repo.root)
37
37
38 if not dirs:
38 directories = []
39 dirs = [repo.root]
39 files = []
40 for src, f, st in repo.dirstate.statwalk(files=dirs, ignored=True,
41 directories=True):
42 if src == 'd':
43 directories.append(f)
44 elif src == 'f' and f not in repo.dirstate:
45 files.append(f)
40
46
41 for path in dirs:
47 directories.sort()
42 path = os.path.abspath(path)
48
43 for root, dirs, files in os.walk(path, topdown=False):
49 for f in files:
44 if '.hg' in self._split_path(root):
50 self._remove_file(os.path.join(repo.root, f))
45 # Skip files in the .hg directory.
51
46 # Note that if the repository is in a directory
52 for f in directories[::-1]:
47 # called .hg this command does not work.
53 f = os.path.join(repo.root, f)
48 continue
54 if not os.listdir(f):
49 for name in files:
55 self._remove_dir(f)
50 self._remove_file(os.path.join(root, name))
51 if not os.listdir(root):
52 # Remove this directory if it is empty.
53 self._remove_dir(root)
54
56
55 self._repo = None
57 self._repo = None
56 self._ui = None
58 self._ui = None
57 self._hg_root = None
59 self._hg_root = None
58
60
59 def _error(self, msg):
61 def _error(self, msg):
60 if self._abort_on_err:
62 if self._abort_on_err:
61 raise util.Abort(msg)
63 raise util.Abort(msg)
62 else:
64 else:
63 self._ui.warn(_('warning: %s\n') % msg)
65 self._ui.warn(_('warning: %s\n') % msg)
64
66
65 def _remove_file(self, name):
67 def _remove_file(self, name):
66 relative_name = self._relative_name(name)
68 relative_name = self._relative_name(name)
67 # dirstate.state() requires a path relative to the root
69 # dirstate.state() requires a path relative to the root
68 # directory.
70 # directory.
69 if self._repo.dirstate.state(relative_name) != '?':
71 if self._repo.dirstate.state(relative_name) != '?':
70 return
72 return
71 self._ui.note(_('Removing file %s\n') % relative_name)
73 self._ui.note(_('Removing file %s\n') % relative_name)
72 if self._act:
74 if self._act:
73 try:
75 try:
74 os.remove(name)
76 os.remove(name)
75 except OSError, e:
77 except OSError, e:
76 self._error(_('%s cannot be removed') % relative_name)
78 self._error(_('%s cannot be removed') % relative_name)
77 else:
79 else:
78 self._ui.write('%s%s' % (relative_name, self._eol))
80 self._ui.write('%s%s' % (relative_name, self._eol))
79
81
80 def _remove_dir(self, name):
82 def _remove_dir(self, name):
81 relative_name = self._relative_name(name)
83 relative_name = self._relative_name(name)
82 self._ui.note(_('Removing directory %s\n') % relative_name)
84 self._ui.note(_('Removing directory %s\n') % relative_name)
83 if self._act:
85 if self._act:
84 try:
86 try:
85 os.rmdir(name)
87 os.rmdir(name)
86 except OSError, e:
88 except OSError, e:
87 self._error(_('%s cannot be removed') % relative_name)
89 self._error(_('%s cannot be removed') % relative_name)
88 else:
90 else:
89 self._ui.write('%s%s' % (relative_name, self._eol))
91 self._ui.write('%s%s' % (relative_name, self._eol))
90
92
91 def _relative_name(self, path):
93 def _relative_name(self, path):
92 '''
94 '''
93 Returns "path" but relative to the root directory of the
95 Returns "path" but relative to the root directory of the
94 repository and with '\\' replaced with '/'.
96 repository and with '\\' replaced with '/'.
95 This is needed because this is the format required by
97 This is needed because this is the format required by
96 self._repo.dirstate.state().
98 self._repo.dirstate.state().
97 '''
99 '''
98 splitted_path = self._split_path(path)[len(self._hg_root):]
100 splitted_path = self._split_path(path)[len(self._hg_root):]
99 # Even on Windows self._repo.dirstate.state() wants '/'.
101 # Even on Windows self._repo.dirstate.state() wants '/'.
100 return self._join_path(splitted_path).replace('\\', '/')
102 return self._join_path(splitted_path).replace('\\', '/')
101
103
102 def _split_path(self, path):
104 def _split_path(self, path):
103 '''
105 '''
104 Returns a list of the single files/directories in "path".
106 Returns a list of the single files/directories in "path".
105 For instance:
107 For instance:
106 '/home/user/test' -> ['/', 'home', 'user', 'test']
108 '/home/user/test' -> ['/', 'home', 'user', 'test']
107 'C:\\Mercurial' -> ['C:\\', 'Mercurial']
109 'C:\\Mercurial' -> ['C:\\', 'Mercurial']
108 '''
110 '''
109 ret = []
111 ret = []
110 while True:
112 while True:
111 head, tail = os.path.split(path)
113 head, tail = os.path.split(path)
112 if tail:
114 if tail:
113 ret.append(tail)
115 ret.append(tail)
114 if head == path:
116 if head == path:
115 ret.append(head)
117 ret.append(head)
116 break
118 break
117 path = head
119 path = head
118 ret.reverse()
120 ret.reverse()
119 return ret
121 return ret
120
122
121 def _join_path(self, splitted_path):
123 def _join_path(self, splitted_path):
122 '''
124 '''
123 Joins a list returned by _split_path().
125 Joins a list returned by _split_path().
124 '''
126 '''
125 ret = ''
127 ret = ''
126 for part in splitted_path:
128 for part in splitted_path:
127 if ret:
129 if ret:
128 ret = os.path.join(ret, part)
130 ret = os.path.join(ret, part)
129 else:
131 else:
130 ret = part
132 ret = part
131 return ret
133 return ret
132
134
133
135
134 def purge(ui, repo, *dirs, **opts):
136 def purge(ui, repo, *dirs, **opts):
135 '''removes files not tracked by mercurial
137 '''removes files not tracked by mercurial
136
138
137 Delete files not known to mercurial, this is useful to test local and
139 Delete files not known to mercurial, this is useful to test local and
138 uncommitted changes in the otherwise clean source tree.
140 uncommitted changes in the otherwise clean source tree.
139
141
140 This means that purge will delete:
142 This means that purge will delete:
141 - Unknown files: files marked with "?" by "hg status"
143 - Unknown files: files marked with "?" by "hg status"
142 - Ignored files: files usually ignored by Mercurial because they match
144 - Ignored files: files usually ignored by Mercurial because they match
143 a pattern in a ".hgignore" file
145 a pattern in a ".hgignore" file
144 - Empty directories: in fact Mercurial ignores directories unless they
146 - Empty directories: in fact Mercurial ignores directories unless they
145 contain files under source control managment
147 contain files under source control managment
146 But it will leave untouched:
148 But it will leave untouched:
147 - Unmodified tracked files
149 - Unmodified tracked files
148 - Modified tracked files
150 - Modified tracked files
149 - New files added to the repository (with "hg add")
151 - New files added to the repository (with "hg add")
150
152
151 If directories are given on the command line, only files in these
153 If directories are given on the command line, only files in these
152 directories are considered.
154 directories are considered.
153
155
154 Be careful with purge, you could irreversibly delete some files you
156 Be careful with purge, you could irreversibly delete some files you
155 forgot to add to the repository. If you only want to print the list of
157 forgot to add to the repository. If you only want to print the list of
156 files that this program would delete use the --print option.
158 files that this program would delete use the --print option.
157 '''
159 '''
158 act = not opts['print']
160 act = not opts['print']
159 abort_on_err = bool(opts['abort_on_err'])
161 abort_on_err = bool(opts['abort_on_err'])
160 eol = opts['print0'] and '\0' or '\n'
162 eol = opts['print0'] and '\0' or '\n'
161 if eol == '\0':
163 if eol == '\0':
162 # --print0 implies --print
164 # --print0 implies --print
163 act = False
165 act = False
164 p = Purge(act, abort_on_err, eol)
166 p = Purge(act, abort_on_err, eol)
165 p.purge(ui, repo, dirs)
167 p.purge(ui, repo, dirs)
166
168
167
169
168 cmdtable = {
170 cmdtable = {
169 'purge':
171 'purge':
170 (purge,
172 (purge,
171 [('a', 'abort-on-err', None, _('abort if an error occurs')),
173 [('a', 'abort-on-err', None, _('abort if an error occurs')),
172 ('p', 'print', None, _('print the file names instead of deleting them')),
174 ('p', 'print', None, _('print the file names instead of deleting them')),
173 ('0', 'print0', None, _('end filenames with NUL, for use with xargs'
175 ('0', 'print0', None, _('end filenames with NUL, for use with xargs'
174 ' (implies -p)'))],
176 ' (implies -p)'))],
175 _('hg purge [OPTION]... [DIR]...'))
177 _('hg purge [OPTION]... [DIR]...'))
176 }
178 }
General Comments 0
You need to be logged in to leave comments. Login now