##// END OF EJS Templates
fix coding style (reported by pylint)
Benoit Boissinot -
r10394:4612cded default
parent child Browse files
Show More
@@ -1,260 +1,260 b''
1 1 # bzr.py - bzr support for the convert extension
2 2 #
3 3 # Copyright 2008, 2009 Marek Kubica <marek@xivilization.net> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 # This module is for handling 'bzr', that was formerly known as Bazaar-NG;
9 9 # it cannot access 'bar' repositories, but they were never used very much
10 10
11 11 import os
12 12 from mercurial import demandimport
13 13 # these do not work with demandimport, blacklist
14 14 demandimport.ignore.extend([
15 15 'bzrlib.transactions',
16 16 'bzrlib.urlutils',
17 17 ])
18 18
19 19 from mercurial.i18n import _
20 20 from mercurial import util
21 21 from common import NoRepo, commit, converter_source
22 22
23 23 try:
24 24 # bazaar imports
25 25 from bzrlib import branch, revision, errors
26 26 from bzrlib.revisionspec import RevisionSpec
27 27 except ImportError:
28 28 pass
29 29
30 30 supportedkinds = ('file', 'symlink')
31 31
32 32 class bzr_source(converter_source):
33 33 """Reads Bazaar repositories by using the Bazaar Python libraries"""
34 34
35 35 def __init__(self, ui, path, rev=None):
36 36 super(bzr_source, self).__init__(ui, path, rev=rev)
37 37
38 38 if not os.path.exists(os.path.join(path, '.bzr')):
39 39 raise NoRepo('%s does not look like a Bazaar repo' % path)
40 40
41 41 try:
42 42 # access bzrlib stuff
43 43 branch
44 44 except NameError:
45 45 raise NoRepo('Bazaar modules could not be loaded')
46 46
47 47 path = os.path.abspath(path)
48 48 self._checkrepotype(path)
49 49 self.branch = branch.Branch.open(path)
50 50 self.sourcerepo = self.branch.repository
51 51 self._parentids = {}
52 52
53 53 def _checkrepotype(self, path):
54 54 # Lightweight checkouts detection is informational but probably
55 55 # fragile at API level. It should not terminate the conversion.
56 56 try:
57 57 from bzrlib import bzrdir
58 58 dir = bzrdir.BzrDir.open_containing(path)[0]
59 59 try:
60 60 tree = dir.open_workingtree(recommend_upgrade=False)
61 61 branch = tree.branch
62 62 except (errors.NoWorkingTree, errors.NotLocalUrl), e:
63 63 tree = None
64 64 branch = dir.open_branch()
65 65 if (tree is not None and tree.bzrdir.root_transport.base !=
66 66 branch.bzrdir.root_transport.base):
67 67 self.ui.warn(_('warning: lightweight checkouts may cause '
68 68 'conversion failures, try with a regular '
69 69 'branch instead.\n'))
70 70 except:
71 71 self.ui.note(_('bzr source type could not be determined\n'))
72 72
73 73 def before(self):
74 74 """Before the conversion begins, acquire a read lock
75 75 for all the operations that might need it. Fortunately
76 76 read locks don't block other reads or writes to the
77 77 repository, so this shouldn't have any impact on the usage of
78 78 the source repository.
79 79
80 80 The alternative would be locking on every operation that
81 81 needs locks (there are currently two: getting the file and
82 82 getting the parent map) and releasing immediately after,
83 83 but this approach can take even 40% longer."""
84 84 self.sourcerepo.lock_read()
85 85
86 86 def after(self):
87 87 self.sourcerepo.unlock()
88 88
89 89 def getheads(self):
90 90 if not self.rev:
91 91 return [self.branch.last_revision()]
92 92 try:
93 93 r = RevisionSpec.from_string(self.rev)
94 94 info = r.in_history(self.branch)
95 95 except errors.BzrError:
96 96 raise util.Abort(_('%s is not a valid revision in current branch')
97 97 % self.rev)
98 98 return [info.rev_id]
99 99
100 100 def getfile(self, name, rev):
101 101 revtree = self.sourcerepo.revision_tree(rev)
102 102 fileid = revtree.path2id(name.decode(self.encoding or 'utf-8'))
103 103 kind = None
104 104 if fileid is not None:
105 105 kind = revtree.kind(fileid)
106 106 if kind not in supportedkinds:
107 107 # the file is not available anymore - was deleted
108 108 raise IOError(_('%s is not available in %s anymore') %
109 109 (name, rev))
110 110 if kind == 'symlink':
111 111 target = revtree.get_symlink_target(fileid)
112 112 if target is None:
113 113 raise util.Abort(_('%s.%s symlink has no target')
114 114 % (name, rev))
115 115 return target
116 116 else:
117 117 sio = revtree.get_file(fileid)
118 118 return sio.read()
119 119
120 120 def getmode(self, name, rev):
121 121 return self._modecache[(name, rev)]
122 122
123 123 def getchanges(self, version):
124 124 # set up caches: modecache and revtree
125 125 self._modecache = {}
126 126 self._revtree = self.sourcerepo.revision_tree(version)
127 127 # get the parentids from the cache
128 128 parentids = self._parentids.pop(version)
129 129 # only diff against first parent id
130 130 prevtree = self.sourcerepo.revision_tree(parentids[0])
131 131 return self._gettreechanges(self._revtree, prevtree)
132 132
133 133 def getcommit(self, version):
134 134 rev = self.sourcerepo.get_revision(version)
135 135 # populate parent id cache
136 136 if not rev.parent_ids:
137 137 parents = []
138 138 self._parentids[version] = (revision.NULL_REVISION,)
139 139 else:
140 140 parents = self._filterghosts(rev.parent_ids)
141 141 self._parentids[version] = parents
142 142
143 143 return commit(parents=parents,
144 144 date='%d %d' % (rev.timestamp, -rev.timezone),
145 145 author=self.recode(rev.committer),
146 146 # bzr returns bytestrings or unicode, depending on the content
147 147 desc=self.recode(rev.message),
148 148 rev=version)
149 149
150 150 def gettags(self):
151 151 if not self.branch.supports_tags():
152 152 return {}
153 153 tagdict = self.branch.tags.get_tag_dict()
154 154 bytetags = {}
155 155 for name, rev in tagdict.iteritems():
156 156 bytetags[self.recode(name)] = rev
157 157 return bytetags
158 158
159 159 def getchangedfiles(self, rev, i):
160 160 self._modecache = {}
161 161 curtree = self.sourcerepo.revision_tree(rev)
162 162 if i is not None:
163 163 parentid = self._parentids[rev][i]
164 164 else:
165 165 # no parent id, get the empty revision
166 166 parentid = revision.NULL_REVISION
167 167
168 168 prevtree = self.sourcerepo.revision_tree(parentid)
169 169 changes = [e[0] for e in self._gettreechanges(curtree, prevtree)[0]]
170 170 return changes
171 171
172 172 def _gettreechanges(self, current, origin):
173 revid = current._revision_id;
173 revid = current._revision_id
174 174 changes = []
175 175 renames = {}
176 176 for (fileid, paths, changed_content, versioned, parent, name,
177 177 kind, executable) in current.iter_changes(origin):
178 178
179 179 if paths[0] == u'' or paths[1] == u'':
180 180 # ignore changes to tree root
181 181 continue
182 182
183 183 # bazaar tracks directories, mercurial does not, so
184 184 # we have to rename the directory contents
185 185 if kind[1] == 'directory':
186 186 if kind[0] not in (None, 'directory'):
187 187 # Replacing 'something' with a directory, record it
188 188 # so it can be removed.
189 189 changes.append((self.recode(paths[0]), revid))
190 190
191 191 if None not in paths and paths[0] != paths[1]:
192 192 # neither an add nor an delete - a move
193 193 # rename all directory contents manually
194 194 subdir = origin.inventory.path2id(paths[0])
195 195 # get all child-entries of the directory
196 196 for name, entry in origin.inventory.iter_entries(subdir):
197 197 # hg does not track directory renames
198 198 if entry.kind == 'directory':
199 199 continue
200 200 frompath = self.recode(paths[0] + '/' + name)
201 201 topath = self.recode(paths[1] + '/' + name)
202 202 # register the files as changed
203 203 changes.append((frompath, revid))
204 204 changes.append((topath, revid))
205 205 # add to mode cache
206 206 mode = ((entry.executable and 'x')
207 207 or (entry.kind == 'symlink' and 's')
208 208 or '')
209 209 self._modecache[(topath, revid)] = mode
210 210 # register the change as move
211 211 renames[topath] = frompath
212 212
213 213 # no futher changes, go to the next change
214 214 continue
215 215
216 216 # we got unicode paths, need to convert them
217 217 path, topath = [self.recode(part) for part in paths]
218 218
219 219 if topath is None:
220 220 # file deleted
221 221 changes.append((path, revid))
222 222 continue
223 223
224 224 # renamed
225 225 if path and path != topath:
226 226 renames[topath] = path
227 227 changes.append((path, revid))
228 228
229 229 # populate the mode cache
230 230 kind, executable = [e[1] for e in (kind, executable)]
231 231 mode = ((executable and 'x') or (kind == 'symlink' and 'l')
232 232 or '')
233 233 self._modecache[(topath, revid)] = mode
234 234 changes.append((topath, revid))
235 235
236 236 return changes, renames
237 237
238 238 def _filterghosts(self, ids):
239 239 """Filters out ghost revisions which hg does not support, see
240 240 <http://bazaar-vcs.org/GhostRevision>
241 241 """
242 242 parentmap = self.sourcerepo.get_parent_map(ids)
243 243 parents = tuple([parent for parent in ids if parent in parentmap])
244 244 return parents
245 245
246 246 def recode(self, s, encoding=None):
247 247 """This version of recode tries to encode unicode to bytecode,
248 248 and preferably using the UTF-8 codec.
249 249 Other types than Unicode are silently returned, this is by
250 250 intention, e.g. the None-type is not going to be encoded but instead
251 251 just passed through
252 252 """
253 253 if not encoding:
254 254 encoding = self.encoding or 'utf-8'
255 255
256 256 if isinstance(s, unicode):
257 257 return s.encode(encoding)
258 258 else:
259 259 # leave it alone
260 260 return s
@@ -1,345 +1,345 b''
1 1 # gnuarch.py - GNU Arch support for the convert extension
2 2 #
3 3 # Copyright 2008, 2009 Aleix Conchillo Flaque <aleix@member.fsf.org>
4 4 # and others
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from common import NoRepo, commandline, commit, converter_source
10 10 from mercurial.i18n import _
11 11 from mercurial import util
12 12 import os, shutil, tempfile, stat, locale
13 13 from email.Parser import Parser
14 14
15 15 class gnuarch_source(converter_source, commandline):
16 16
17 17 class gnuarch_rev(object):
18 18 def __init__(self, rev):
19 19 self.rev = rev
20 20 self.summary = ''
21 21 self.date = None
22 22 self.author = ''
23 23 self.continuationof = None
24 24 self.add_files = []
25 25 self.mod_files = []
26 26 self.del_files = []
27 27 self.ren_files = {}
28 28 self.ren_dirs = {}
29 29
30 30 def __init__(self, ui, path, rev=None):
31 31 super(gnuarch_source, self).__init__(ui, path, rev=rev)
32 32
33 33 if not os.path.exists(os.path.join(path, '{arch}')):
34 34 raise NoRepo(_("%s does not look like a GNU Arch repo") % path)
35 35
36 36 # Could use checktool, but we want to check for baz or tla.
37 37 self.execmd = None
38 38 if util.find_exe('baz'):
39 39 self.execmd = 'baz'
40 40 else:
41 41 if util.find_exe('tla'):
42 42 self.execmd = 'tla'
43 43 else:
44 44 raise util.Abort(_('cannot find a GNU Arch tool'))
45 45
46 46 commandline.__init__(self, ui, self.execmd)
47 47
48 48 self.path = os.path.realpath(path)
49 49 self.tmppath = None
50 50
51 51 self.treeversion = None
52 52 self.lastrev = None
53 53 self.changes = {}
54 54 self.parents = {}
55 55 self.tags = {}
56 56 self.modecache = {}
57 57 self.catlogparser = Parser()
58 58 self.locale = locale.getpreferredencoding()
59 59 self.archives = []
60 60
61 61 def before(self):
62 62 # Get registered archives
63 63 self.archives = [i.rstrip('\n')
64 64 for i in self.runlines0('archives', '-n')]
65 65
66 66 if self.execmd == 'tla':
67 67 output = self.run0('tree-version', self.path)
68 68 else:
69 69 output = self.run0('tree-version', '-d', self.path)
70 70 self.treeversion = output.strip()
71 71
72 72 # Get name of temporary directory
73 73 version = self.treeversion.split('/')
74 74 self.tmppath = os.path.join(tempfile.gettempdir(),
75 75 'hg-%s' % version[1])
76 76
77 77 # Generate parents dictionary
78 78 self.parents[None] = []
79 79 treeversion = self.treeversion
80 80 child = None
81 81 while treeversion:
82 82 self.ui.status(_('analyzing tree version %s...\n') % treeversion)
83 83
84 84 archive = treeversion.split('/')[0]
85 85 if archive not in self.archives:
86 86 self.ui.status(_('tree analysis stopped because it points to '
87 87 'an unregistered archive %s...\n') % archive)
88 88 break
89 89
90 90 # Get the complete list of revisions for that tree version
91 91 output, status = self.runlines('revisions', '-r', '-f', treeversion)
92 92 self.checkexit(status, 'failed retrieveing revisions for %s'
93 93 % treeversion)
94 94
95 95 # No new iteration unless a revision has a continuation-of header
96 96 treeversion = None
97 97
98 98 for l in output:
99 99 rev = l.strip()
100 100 self.changes[rev] = self.gnuarch_rev(rev)
101 101 self.parents[rev] = []
102 102
103 103 # Read author, date and summary
104 104 catlog, status = self.run('cat-log', '-d', self.path, rev)
105 105 if status:
106 106 catlog = self.run0('cat-archive-log', rev)
107 107 self._parsecatlog(catlog, rev)
108 108
109 109 # Populate the parents map
110 110 self.parents[child].append(rev)
111 111
112 112 # Keep track of the current revision as the child of the next
113 113 # revision scanned
114 114 child = rev
115 115
116 116 # Check if we have to follow the usual incremental history
117 117 # or if we have to 'jump' to a different treeversion given
118 118 # by the continuation-of header.
119 119 if self.changes[rev].continuationof:
120 120 treeversion = '--'.join(
121 121 self.changes[rev].continuationof.split('--')[:-1])
122 122 break
123 123
124 124 # If we reached a base-0 revision w/o any continuation-of
125 125 # header, it means the tree history ends here.
126 126 if rev[-6:] == 'base-0':
127 127 break
128 128
129 129 def after(self):
130 130 self.ui.debug('cleaning up %s\n' % self.tmppath)
131 131 shutil.rmtree(self.tmppath, ignore_errors=True)
132 132
133 133 def getheads(self):
134 134 return self.parents[None]
135 135
136 136 def getfile(self, name, rev):
137 137 if rev != self.lastrev:
138 138 raise util.Abort(_('internal calling inconsistency'))
139 139
140 140 # Raise IOError if necessary (i.e. deleted files).
141 141 if not os.path.exists(os.path.join(self.tmppath, name)):
142 142 raise IOError
143 143
144 144 data, mode = self._getfile(name, rev)
145 145 self.modecache[(name, rev)] = mode
146 146
147 147 return data
148 148
149 149 def getmode(self, name, rev):
150 150 return self.modecache[(name, rev)]
151 151
152 152 def getchanges(self, rev):
153 153 self.modecache = {}
154 154 self._update(rev)
155 155 changes = []
156 156 copies = {}
157 157
158 158 for f in self.changes[rev].add_files:
159 159 changes.append((f, rev))
160 160
161 161 for f in self.changes[rev].mod_files:
162 162 changes.append((f, rev))
163 163
164 164 for f in self.changes[rev].del_files:
165 165 changes.append((f, rev))
166 166
167 167 for src in self.changes[rev].ren_files:
168 168 to = self.changes[rev].ren_files[src]
169 169 changes.append((src, rev))
170 170 changes.append((to, rev))
171 171 copies[to] = src
172 172
173 173 for src in self.changes[rev].ren_dirs:
174 174 to = self.changes[rev].ren_dirs[src]
175 chgs, cps = self._rendirchanges(src, to);
175 chgs, cps = self._rendirchanges(src, to)
176 176 changes += [(f, rev) for f in chgs]
177 177 copies.update(cps)
178 178
179 179 self.lastrev = rev
180 180 return sorted(set(changes)), copies
181 181
182 182 def getcommit(self, rev):
183 183 changes = self.changes[rev]
184 184 return commit(author=changes.author, date=changes.date,
185 185 desc=changes.summary, parents=self.parents[rev], rev=rev)
186 186
187 187 def gettags(self):
188 188 return self.tags
189 189
190 190 def _execute(self, cmd, *args, **kwargs):
191 191 cmdline = [self.execmd, cmd]
192 192 cmdline += args
193 193 cmdline = [util.shellquote(arg) for arg in cmdline]
194 194 cmdline += ['>', util.nulldev, '2>', util.nulldev]
195 195 cmdline = util.quotecommand(' '.join(cmdline))
196 196 self.ui.debug(cmdline, '\n')
197 197 return os.system(cmdline)
198 198
199 199 def _update(self, rev):
200 200 self.ui.debug('applying revision %s...\n' % rev)
201 201 changeset, status = self.runlines('replay', '-d', self.tmppath,
202 202 rev)
203 203 if status:
204 204 # Something went wrong while merging (baz or tla
205 205 # issue?), get latest revision and try from there
206 206 shutil.rmtree(self.tmppath, ignore_errors=True)
207 207 self._obtainrevision(rev)
208 208 else:
209 209 old_rev = self.parents[rev][0]
210 210 self.ui.debug('computing changeset between %s and %s...\n'
211 211 % (old_rev, rev))
212 212 self._parsechangeset(changeset, rev)
213 213
214 214 def _getfile(self, name, rev):
215 215 mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
216 216 if stat.S_ISLNK(mode):
217 217 data = os.readlink(os.path.join(self.tmppath, name))
218 218 mode = mode and 'l' or ''
219 219 else:
220 220 data = open(os.path.join(self.tmppath, name), 'rb').read()
221 221 mode = (mode & 0111) and 'x' or ''
222 222 return data, mode
223 223
224 224 def _exclude(self, name):
225 225 exclude = ['{arch}', '.arch-ids', '.arch-inventory']
226 226 for exc in exclude:
227 227 if name.find(exc) != -1:
228 228 return True
229 229 return False
230 230
231 231 def _readcontents(self, path):
232 232 files = []
233 233 contents = os.listdir(path)
234 234 while len(contents) > 0:
235 235 c = contents.pop()
236 236 p = os.path.join(path, c)
237 237 # os.walk could be used, but here we avoid internal GNU
238 238 # Arch files and directories, thus saving a lot time.
239 239 if not self._exclude(p):
240 240 if os.path.isdir(p):
241 241 contents += [os.path.join(c, f) for f in os.listdir(p)]
242 242 else:
243 243 files.append(c)
244 244 return files
245 245
246 246 def _rendirchanges(self, src, dest):
247 247 changes = []
248 248 copies = {}
249 249 files = self._readcontents(os.path.join(self.tmppath, dest))
250 250 for f in files:
251 251 s = os.path.join(src, f)
252 252 d = os.path.join(dest, f)
253 253 changes.append(s)
254 254 changes.append(d)
255 255 copies[d] = s
256 256 return changes, copies
257 257
258 258 def _obtainrevision(self, rev):
259 259 self.ui.debug('obtaining revision %s...\n' % rev)
260 260 output = self._execute('get', rev, self.tmppath)
261 261 self.checkexit(output)
262 262 self.ui.debug('analyzing revision %s...\n' % rev)
263 263 files = self._readcontents(self.tmppath)
264 264 self.changes[rev].add_files += files
265 265
266 266 def _stripbasepath(self, path):
267 267 if path.startswith('./'):
268 268 return path[2:]
269 269 return path
270 270
271 271 def _parsecatlog(self, data, rev):
272 272 try:
273 273 catlog = self.catlogparser.parsestr(data)
274 274
275 275 # Commit date
276 276 self.changes[rev].date = util.datestr(
277 277 util.strdate(catlog['Standard-date'],
278 278 '%Y-%m-%d %H:%M:%S'))
279 279
280 280 # Commit author
281 281 self.changes[rev].author = self.recode(catlog['Creator'])
282 282
283 283 # Commit description
284 284 self.changes[rev].summary = '\n\n'.join((catlog['Summary'],
285 285 catlog.get_payload()))
286 286 self.changes[rev].summary = self.recode(self.changes[rev].summary)
287 287
288 288 # Commit revision origin when dealing with a branch or tag
289 289 if 'Continuation-of' in catlog:
290 290 self.changes[rev].continuationof = self.recode(
291 291 catlog['Continuation-of'])
292 292 except Exception:
293 293 raise util.Abort(_('could not parse cat-log of %s') % rev)
294 294
295 295 def _parsechangeset(self, data, rev):
296 296 for l in data:
297 297 l = l.strip()
298 298 # Added file (ignore added directory)
299 299 if l.startswith('A') and not l.startswith('A/'):
300 300 file = self._stripbasepath(l[1:].strip())
301 301 if not self._exclude(file):
302 302 self.changes[rev].add_files.append(file)
303 303 # Deleted file (ignore deleted directory)
304 304 elif l.startswith('D') and not l.startswith('D/'):
305 305 file = self._stripbasepath(l[1:].strip())
306 306 if not self._exclude(file):
307 307 self.changes[rev].del_files.append(file)
308 308 # Modified binary file
309 309 elif l.startswith('Mb'):
310 310 file = self._stripbasepath(l[2:].strip())
311 311 if not self._exclude(file):
312 312 self.changes[rev].mod_files.append(file)
313 313 # Modified link
314 314 elif l.startswith('M->'):
315 315 file = self._stripbasepath(l[3:].strip())
316 316 if not self._exclude(file):
317 317 self.changes[rev].mod_files.append(file)
318 318 # Modified file
319 319 elif l.startswith('M'):
320 320 file = self._stripbasepath(l[1:].strip())
321 321 if not self._exclude(file):
322 322 self.changes[rev].mod_files.append(file)
323 323 # Renamed file (or link)
324 324 elif l.startswith('=>'):
325 325 files = l[2:].strip().split(' ')
326 326 if len(files) == 1:
327 327 files = l[2:].strip().split('\t')
328 328 src = self._stripbasepath(files[0])
329 329 dst = self._stripbasepath(files[1])
330 330 if not self._exclude(src) and not self._exclude(dst):
331 331 self.changes[rev].ren_files[src] = dst
332 332 # Conversion from file to link or from link to file (modified)
333 333 elif l.startswith('ch'):
334 334 file = self._stripbasepath(l[2:].strip())
335 335 if not self._exclude(file):
336 336 self.changes[rev].mod_files.append(file)
337 337 # Renamed directory
338 338 elif l.startswith('/>'):
339 339 dirs = l[2:].strip().split(' ')
340 340 if len(dirs) == 1:
341 341 dirs = l[2:].strip().split('\t')
342 342 src = self._stripbasepath(dirs[0])
343 343 dst = self._stripbasepath(dirs[1])
344 344 if not self._exclude(src) and not self._exclude(dst):
345 345 self.changes[rev].ren_dirs[src] = dst
@@ -1,347 +1,347 b''
1 1 # Minimal support for git commands on an hg repository
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''browse the repository in a graphical way
9 9
10 10 The hgk extension allows browsing the history of a repository in a
11 11 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not
12 12 distributed with Mercurial.)
13 13
14 14 hgk consists of two parts: a Tcl script that does the displaying and
15 15 querying of information, and an extension to Mercurial named hgk.py,
16 16 which provides hooks for hgk to get information. hgk can be found in
17 17 the contrib directory, and the extension is shipped in the hgext
18 18 repository, and needs to be enabled.
19 19
20 20 The hg view command will launch the hgk Tcl script. For this command
21 21 to work, hgk must be in your search path. Alternately, you can specify
22 22 the path to hgk in your .hgrc file::
23 23
24 24 [hgk]
25 25 path=/location/of/hgk
26 26
27 27 hgk can make use of the extdiff extension to visualize revisions.
28 28 Assuming you had already configured extdiff vdiff command, just add::
29 29
30 30 [hgk]
31 31 vdiff=vdiff
32 32
33 33 Revisions context menu will now display additional entries to fire
34 34 vdiff on hovered and selected revisions.
35 35 '''
36 36
37 37 import os
38 38 from mercurial import commands, util, patch, revlog, cmdutil
39 39 from mercurial.node import nullid, nullrev, short
40 40 from mercurial.i18n import _
41 41
42 42 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
43 43 """diff trees from two commits"""
44 44 def __difftree(repo, node1, node2, files=[]):
45 45 assert node2 is not None
46 46 mmap = repo[node1].manifest()
47 47 mmap2 = repo[node2].manifest()
48 48 m = cmdutil.match(repo, files)
49 49 modified, added, removed = repo.status(node1, node2, m)[:3]
50 50 empty = short(nullid)
51 51
52 52 for f in modified:
53 53 # TODO get file permissions
54 54 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
55 55 (short(mmap[f]), short(mmap2[f]), f, f))
56 56 for f in added:
57 57 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
58 58 (empty, short(mmap2[f]), f, f))
59 59 for f in removed:
60 60 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
61 61 (short(mmap[f]), empty, f, f))
62 62 ##
63 63
64 64 while True:
65 65 if opts['stdin']:
66 66 try:
67 67 line = raw_input().split(' ')
68 68 node1 = line[0]
69 69 if len(line) > 1:
70 70 node2 = line[1]
71 71 else:
72 72 node2 = None
73 73 except EOFError:
74 74 break
75 75 node1 = repo.lookup(node1)
76 76 if node2:
77 77 node2 = repo.lookup(node2)
78 78 else:
79 79 node2 = node1
80 80 node1 = repo.changelog.parents(node1)[0]
81 81 if opts['patch']:
82 82 if opts['pretty']:
83 83 catcommit(ui, repo, node2, "")
84 84 m = cmdutil.match(repo, files)
85 85 chunks = patch.diff(repo, node1, node2, match=m,
86 86 opts=patch.diffopts(ui, {'git': True}))
87 87 for chunk in chunks:
88 88 ui.write(chunk)
89 89 else:
90 90 __difftree(repo, node1, node2, files=files)
91 91 if not opts['stdin']:
92 92 break
93 93
94 94 def catcommit(ui, repo, n, prefix, ctx=None):
95 nlprefix = '\n' + prefix;
95 nlprefix = '\n' + prefix
96 96 if ctx is None:
97 97 ctx = repo[n]
98 98 ui.write("tree %s\n" % short(ctx.changeset()[0])) # use ctx.node() instead ??
99 99 for p in ctx.parents():
100 100 ui.write("parent %s\n" % p)
101 101
102 102 date = ctx.date()
103 103 description = ctx.description().replace("\0", "")
104 104 lines = description.splitlines()
105 105 if lines and lines[-1].startswith('committer:'):
106 106 committer = lines[-1].split(': ')[1].rstrip()
107 107 else:
108 108 committer = ctx.user()
109 109
110 110 ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
111 111 ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
112 112 ui.write("revision %d\n" % ctx.rev())
113 113 ui.write("branch %s\n\n" % ctx.branch())
114 114
115 115 if prefix != "":
116 116 ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
117 117 else:
118 118 ui.write(description + "\n")
119 119 if prefix:
120 120 ui.write('\0')
121 121
122 122 def base(ui, repo, node1, node2):
123 123 """output common ancestor information"""
124 124 node1 = repo.lookup(node1)
125 125 node2 = repo.lookup(node2)
126 126 n = repo.changelog.ancestor(node1, node2)
127 127 ui.write(short(n) + "\n")
128 128
129 129 def catfile(ui, repo, type=None, r=None, **opts):
130 130 """cat a specific revision"""
131 131 # in stdin mode, every line except the commit is prefixed with two
132 132 # spaces. This way the our caller can find the commit without magic
133 133 # strings
134 134 #
135 135 prefix = ""
136 136 if opts['stdin']:
137 137 try:
138 (type, r) = raw_input().split(' ');
138 (type, r) = raw_input().split(' ')
139 139 prefix = " "
140 140 except EOFError:
141 141 return
142 142
143 143 else:
144 144 if not type or not r:
145 145 ui.warn(_("cat-file: type or revision not supplied\n"))
146 146 commands.help_(ui, 'cat-file')
147 147
148 148 while r:
149 149 if type != "commit":
150 150 ui.warn(_("aborting hg cat-file only understands commits\n"))
151 return 1;
151 return 1
152 152 n = repo.lookup(r)
153 153 catcommit(ui, repo, n, prefix)
154 154 if opts['stdin']:
155 155 try:
156 (type, r) = raw_input().split(' ');
156 (type, r) = raw_input().split(' ')
157 157 except EOFError:
158 158 break
159 159 else:
160 160 break
161 161
162 162 # git rev-tree is a confusing thing. You can supply a number of
163 163 # commit sha1s on the command line, and it walks the commit history
164 164 # telling you which commits are reachable from the supplied ones via
165 165 # a bitmask based on arg position.
166 166 # you can specify a commit to stop at by starting the sha1 with ^
167 167 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
168 168 def chlogwalk():
169 169 count = len(repo)
170 170 i = count
171 171 l = [0] * 100
172 172 chunk = 100
173 173 while True:
174 174 if chunk > i:
175 175 chunk = i
176 176 i = 0
177 177 else:
178 178 i -= chunk
179 179
180 180 for x in xrange(chunk):
181 181 if i + x >= count:
182 182 l[chunk - x:] = [0] * (chunk - x)
183 183 break
184 184 if full != None:
185 185 l[x] = repo[i + x]
186 186 l[x].changeset() # force reading
187 187 else:
188 188 l[x] = 1
189 189 for x in xrange(chunk - 1, -1, -1):
190 190 if l[x] != 0:
191 191 yield (i + x, full != None and l[x] or None)
192 192 if i == 0:
193 193 break
194 194
195 195 # calculate and return the reachability bitmask for sha
196 196 def is_reachable(ar, reachable, sha):
197 197 if len(ar) == 0:
198 198 return 1
199 199 mask = 0
200 200 for i in xrange(len(ar)):
201 201 if sha in reachable[i]:
202 202 mask |= 1 << i
203 203
204 204 return mask
205 205
206 206 reachable = []
207 207 stop_sha1 = []
208 208 want_sha1 = []
209 209 count = 0
210 210
211 211 # figure out which commits they are asking for and which ones they
212 212 # want us to stop on
213 213 for i, arg in enumerate(args):
214 214 if arg.startswith('^'):
215 215 s = repo.lookup(arg[1:])
216 216 stop_sha1.append(s)
217 217 want_sha1.append(s)
218 218 elif arg != 'HEAD':
219 219 want_sha1.append(repo.lookup(arg))
220 220
221 221 # calculate the graph for the supplied commits
222 222 for i, n in enumerate(want_sha1):
223 reachable.append(set());
224 visit = [n];
223 reachable.append(set())
224 visit = [n]
225 225 reachable[i].add(n)
226 226 while visit:
227 227 n = visit.pop(0)
228 228 if n in stop_sha1:
229 229 continue
230 230 for p in repo.changelog.parents(n):
231 231 if p not in reachable[i]:
232 232 reachable[i].add(p)
233 233 visit.append(p)
234 234 if p in stop_sha1:
235 235 continue
236 236
237 237 # walk the repository looking for commits that are in our
238 238 # reachability graph
239 239 for i, ctx in chlogwalk():
240 240 n = repo.changelog.node(i)
241 241 mask = is_reachable(want_sha1, reachable, n)
242 242 if mask:
243 243 parentstr = ""
244 244 if parents:
245 245 pp = repo.changelog.parents(n)
246 246 if pp[0] != nullid:
247 247 parentstr += " " + short(pp[0])
248 248 if pp[1] != nullid:
249 249 parentstr += " " + short(pp[1])
250 250 if not full:
251 251 ui.write("%s%s\n" % (short(n), parentstr))
252 252 elif full == "commit":
253 253 ui.write("%s%s\n" % (short(n), parentstr))
254 254 catcommit(ui, repo, n, ' ', ctx)
255 255 else:
256 256 (p1, p2) = repo.changelog.parents(n)
257 257 (h, h1, h2) = map(short, (n, p1, p2))
258 258 (i1, i2) = map(repo.changelog.rev, (p1, p2))
259 259
260 260 date = ctx.date()[0]
261 261 ui.write("%s %s:%s" % (date, h, mask))
262 262 mask = is_reachable(want_sha1, reachable, p1)
263 263 if i1 != nullrev and mask > 0:
264 264 ui.write("%s:%s " % (h1, mask)),
265 265 mask = is_reachable(want_sha1, reachable, p2)
266 266 if i2 != nullrev and mask > 0:
267 267 ui.write("%s:%s " % (h2, mask))
268 268 ui.write("\n")
269 269 if maxnr and count >= maxnr:
270 270 break
271 271 count += 1
272 272
273 273 def revparse(ui, repo, *revs, **opts):
274 274 """parse given revisions"""
275 275 def revstr(rev):
276 276 if rev == 'HEAD':
277 277 rev = 'tip'
278 278 return revlog.hex(repo.lookup(rev))
279 279
280 280 for r in revs:
281 281 revrange = r.split(':', 1)
282 282 ui.write('%s\n' % revstr(revrange[0]))
283 283 if len(revrange) == 2:
284 284 ui.write('^%s\n' % revstr(revrange[1]))
285 285
286 286 # git rev-list tries to order things by date, and has the ability to stop
287 287 # at a given commit without walking the whole repo. TODO add the stop
288 288 # parameter
289 289 def revlist(ui, repo, *revs, **opts):
290 290 """print revisions"""
291 291 if opts['header']:
292 292 full = "commit"
293 293 else:
294 294 full = None
295 295 copy = [x for x in revs]
296 296 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
297 297
298 298 def config(ui, repo, **opts):
299 299 """print extension options"""
300 300 def writeopt(name, value):
301 301 ui.write('k=%s\nv=%s\n' % (name, value))
302 302
303 303 writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
304 304
305 305
306 306 def view(ui, repo, *etc, **opts):
307 307 "start interactive history viewer"
308 308 os.chdir(repo.root)
309 309 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
310 310 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
311 311 ui.debug("running %s\n" % cmd)
312 312 util.system(cmd)
313 313
314 314 cmdtable = {
315 315 "^view":
316 316 (view,
317 317 [('l', 'limit', '', _('limit number of changes displayed'))],
318 318 _('hg view [-l LIMIT] [REVRANGE]')),
319 319 "debug-diff-tree":
320 320 (difftree,
321 321 [('p', 'patch', None, _('generate patch')),
322 322 ('r', 'recursive', None, _('recursive')),
323 323 ('P', 'pretty', None, _('pretty')),
324 324 ('s', 'stdin', None, _('stdin')),
325 325 ('C', 'copy', None, _('detect copies')),
326 326 ('S', 'search', "", _('search'))],
327 327 _('hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...')),
328 328 "debug-cat-file":
329 329 (catfile,
330 330 [('s', 'stdin', None, _('stdin'))],
331 331 _('hg debug-cat-file [OPTION]... TYPE FILE')),
332 332 "debug-config":
333 333 (config, [], _('hg debug-config')),
334 334 "debug-merge-base":
335 335 (base, [], _('hg debug-merge-base REV REV')),
336 336 "debug-rev-parse":
337 337 (revparse,
338 338 [('', 'default', '', _('ignored'))],
339 339 _('hg debug-rev-parse REV')),
340 340 "debug-rev-list":
341 341 (revlist,
342 342 [('H', 'header', None, _('header')),
343 343 ('t', 'topo-order', None, _('topo-order')),
344 344 ('p', 'parents', None, _('parents')),
345 345 ('n', 'max-count', 0, _('max-count'))],
346 346 _('hg debug-rev-list [OPTION]... REV...')),
347 347 }
@@ -1,61 +1,61 b''
1 1 # highlight.py - highlight extension implementation file
2 2 #
3 3 # Copyright 2007-2009 Adam Hupp <adam@hupp.org> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 #
8 8 # The original module was split in an interface and an implementation
9 9 # file to defer pygments loading and speedup extension setup.
10 10
11 11 from mercurial import demandimport
12 demandimport.ignore.extend(['pkgutil', 'pkg_resources', '__main__',])
12 demandimport.ignore.extend(['pkgutil', 'pkg_resources', '__main__'])
13 13 from mercurial import util, encoding
14 14
15 15 from pygments import highlight
16 16 from pygments.util import ClassNotFound
17 17 from pygments.lexers import guess_lexer, guess_lexer_for_filename, TextLexer
18 18 from pygments.formatters import HtmlFormatter
19 19
20 20 SYNTAX_CSS = ('\n<link rel="stylesheet" href="{url}highlightcss" '
21 21 'type="text/css" />')
22 22
23 23 def pygmentize(field, fctx, style, tmpl):
24 24
25 25 # append a <link ...> to the syntax highlighting css
26 26 old_header = ''.join(tmpl('header'))
27 27 if SYNTAX_CSS not in old_header:
28 28 new_header = old_header + SYNTAX_CSS
29 29 tmpl.cache['header'] = new_header
30 30
31 31 text = fctx.data()
32 32 if util.binary(text):
33 33 return
34 34
35 35 # Pygments is best used with Unicode strings:
36 36 # <http://pygments.org/docs/unicode/>
37 37 text = text.decode(encoding.encoding, 'replace')
38 38
39 39 # To get multi-line strings right, we can't format line-by-line
40 40 try:
41 41 lexer = guess_lexer_for_filename(fctx.path(), text[:1024])
42 42 except (ClassNotFound, ValueError):
43 43 try:
44 44 lexer = guess_lexer(text[:1024])
45 45 except (ClassNotFound, ValueError):
46 46 lexer = TextLexer()
47 47
48 48 formatter = HtmlFormatter(style=style)
49 49
50 50 colorized = highlight(text, lexer, formatter)
51 51 # strip wrapping div
52 52 colorized = colorized[:colorized.find('\n</pre>')]
53 53 colorized = colorized[colorized.find('<pre>')+5:]
54 54 coloriter = (s.encode(encoding.encoding, 'replace')
55 55 for s in colorized.splitlines())
56 56
57 57 tmpl.filters['colorize'] = lambda x: coloriter.next()
58 58
59 59 oldl = tmpl.cache[field]
60 60 newl = oldl.replace('line|escape', 'line|colorize')
61 61 tmpl.cache[field] = newl
@@ -1,3808 +1,3808 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import hex, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _, gettext
11 11 import os, re, sys, difflib, time, tempfile
12 12 import hg, util, revlog, bundlerepo, extensions, copies, error
13 13 import patch, help, mdiff, url, encoding, templatekw
14 14 import archival, changegroup, cmdutil, sshserver, hbisect
15 15 from hgweb import server
16 16 import merge as merge_
17 17 import minirst
18 18
19 19 # Commands start here, listed alphabetically
20 20
21 21 def add(ui, repo, *pats, **opts):
22 22 """add the specified files on the next commit
23 23
24 24 Schedule files to be version controlled and added to the
25 25 repository.
26 26
27 27 The files will be added to the repository at the next commit. To
28 28 undo an add before that, see hg forget.
29 29
30 30 If no names are given, add all files to the repository.
31 31 """
32 32
33 33 bad = []
34 34 names = []
35 35 m = cmdutil.match(repo, pats, opts)
36 36 oldbad = m.bad
37 37 m.bad = lambda x, y: bad.append(x) or oldbad(x, y)
38 38
39 39 for f in repo.walk(m):
40 40 exact = m.exact(f)
41 41 if exact or f not in repo.dirstate:
42 42 names.append(f)
43 43 if ui.verbose or not exact:
44 44 ui.status(_('adding %s\n') % m.rel(f))
45 45 if not opts.get('dry_run'):
46 46 bad += [f for f in repo.add(names) if f in m.files()]
47 47 return bad and 1 or 0
48 48
49 49 def addremove(ui, repo, *pats, **opts):
50 50 """add all new files, delete all missing files
51 51
52 52 Add all new files and remove all missing files from the
53 53 repository.
54 54
55 55 New files are ignored if they match any of the patterns in
56 56 .hgignore. As with add, these changes take effect at the next
57 57 commit.
58 58
59 59 Use the -s/--similarity option to detect renamed files. With a
60 60 parameter greater than 0, this compares every removed file with
61 61 every added file and records those similar enough as renames. This
62 62 option takes a percentage between 0 (disabled) and 100 (files must
63 63 be identical) as its parameter. Detecting renamed files this way
64 64 can be expensive.
65 65 """
66 66 try:
67 67 sim = float(opts.get('similarity') or 0)
68 68 except ValueError:
69 69 raise util.Abort(_('similarity must be a number'))
70 70 if sim < 0 or sim > 100:
71 71 raise util.Abort(_('similarity must be between 0 and 100'))
72 72 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
73 73
74 74 def annotate(ui, repo, *pats, **opts):
75 75 """show changeset information by line for each file
76 76
77 77 List changes in files, showing the revision id responsible for
78 78 each line
79 79
80 80 This command is useful for discovering when a change was made and
81 81 by whom.
82 82
83 83 Without the -a/--text option, annotate will avoid processing files
84 84 it detects as binary. With -a, annotate will annotate the file
85 85 anyway, although the results will probably be neither useful
86 86 nor desirable.
87 87 """
88 88 datefunc = ui.quiet and util.shortdate or util.datestr
89 89 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
90 90
91 91 if not pats:
92 92 raise util.Abort(_('at least one filename or pattern is required'))
93 93
94 94 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
95 95 ('number', lambda x: str(x[0].rev())),
96 96 ('changeset', lambda x: short(x[0].node())),
97 97 ('date', getdate),
98 98 ('file', lambda x: x[0].path()),
99 99 ]
100 100
101 101 if (not opts.get('user') and not opts.get('changeset')
102 102 and not opts.get('date') and not opts.get('file')):
103 103 opts['number'] = 1
104 104
105 105 linenumber = opts.get('line_number') is not None
106 if (linenumber and (not opts.get('changeset')) and (not opts.get('number'))):
106 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
107 107 raise util.Abort(_('at least one of -n/-c is required for -l'))
108 108
109 109 funcmap = [func for op, func in opmap if opts.get(op)]
110 110 if linenumber:
111 111 lastfunc = funcmap[-1]
112 112 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
113 113
114 114 ctx = repo[opts.get('rev')]
115 115 m = cmdutil.match(repo, pats, opts)
116 116 follow = not opts.get('no_follow')
117 117 for abs in ctx.walk(m):
118 118 fctx = ctx[abs]
119 119 if not opts.get('text') and util.binary(fctx.data()):
120 120 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
121 121 continue
122 122
123 123 lines = fctx.annotate(follow=follow, linenumber=linenumber)
124 124 pieces = []
125 125
126 126 for f in funcmap:
127 127 l = [f(n) for n, dummy in lines]
128 128 if l:
129 129 ml = max(map(len, l))
130 130 pieces.append(["%*s" % (ml, x) for x in l])
131 131
132 132 if pieces:
133 133 for p, l in zip(zip(*pieces), lines):
134 134 ui.write("%s: %s" % (" ".join(p), l[1]))
135 135
136 136 def archive(ui, repo, dest, **opts):
137 137 '''create an unversioned archive of a repository revision
138 138
139 139 By default, the revision used is the parent of the working
140 140 directory; use -r/--rev to specify a different revision.
141 141
142 142 To specify the type of archive to create, use -t/--type. Valid
143 143 types are:
144 144
145 145 :``files``: a directory full of files (default)
146 146 :``tar``: tar archive, uncompressed
147 147 :``tbz2``: tar archive, compressed using bzip2
148 148 :``tgz``: tar archive, compressed using gzip
149 149 :``uzip``: zip archive, uncompressed
150 150 :``zip``: zip archive, compressed using deflate
151 151
152 152 The exact name of the destination archive or directory is given
153 153 using a format string; see 'hg help export' for details.
154 154
155 155 Each member added to an archive file has a directory prefix
156 156 prepended. Use -p/--prefix to specify a format string for the
157 157 prefix. The default is the basename of the archive, with suffixes
158 158 removed.
159 159 '''
160 160
161 161 ctx = repo[opts.get('rev')]
162 162 if not ctx:
163 163 raise util.Abort(_('no working directory: please specify a revision'))
164 164 node = ctx.node()
165 165 dest = cmdutil.make_filename(repo, dest, node)
166 166 if os.path.realpath(dest) == repo.root:
167 167 raise util.Abort(_('repository root cannot be destination'))
168 168 matchfn = cmdutil.match(repo, [], opts)
169 169 kind = opts.get('type') or 'files'
170 170 prefix = opts.get('prefix')
171 171 if dest == '-':
172 172 if kind == 'files':
173 173 raise util.Abort(_('cannot archive plain files to stdout'))
174 174 dest = sys.stdout
175 175 if not prefix:
176 176 prefix = os.path.basename(repo.root) + '-%h'
177 177 prefix = cmdutil.make_filename(repo, prefix, node)
178 178 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
179 179 matchfn, prefix)
180 180
181 181 def backout(ui, repo, node=None, rev=None, **opts):
182 182 '''reverse effect of earlier changeset
183 183
184 184 Commit the backed out changes as a new changeset. The new
185 185 changeset is a child of the backed out changeset.
186 186
187 187 If you backout a changeset other than the tip, a new head is
188 188 created. This head will be the new tip and you should merge this
189 189 backout changeset with another head.
190 190
191 191 The --merge option remembers the parent of the working directory
192 192 before starting the backout, then merges the new head with that
193 193 changeset afterwards. This saves you from doing the merge by hand.
194 194 The result of this merge is not committed, as with a normal merge.
195 195
196 196 See 'hg help dates' for a list of formats valid for -d/--date.
197 197 '''
198 198 if rev and node:
199 199 raise util.Abort(_("please specify just one revision"))
200 200
201 201 if not rev:
202 202 rev = node
203 203
204 204 if not rev:
205 205 raise util.Abort(_("please specify a revision to backout"))
206 206
207 207 date = opts.get('date')
208 208 if date:
209 209 opts['date'] = util.parsedate(date)
210 210
211 211 cmdutil.bail_if_changed(repo)
212 212 node = repo.lookup(rev)
213 213
214 214 op1, op2 = repo.dirstate.parents()
215 215 a = repo.changelog.ancestor(op1, node)
216 216 if a != node:
217 217 raise util.Abort(_('cannot backout change on a different branch'))
218 218
219 219 p1, p2 = repo.changelog.parents(node)
220 220 if p1 == nullid:
221 221 raise util.Abort(_('cannot backout a change with no parents'))
222 222 if p2 != nullid:
223 223 if not opts.get('parent'):
224 224 raise util.Abort(_('cannot backout a merge changeset without '
225 225 '--parent'))
226 226 p = repo.lookup(opts['parent'])
227 227 if p not in (p1, p2):
228 228 raise util.Abort(_('%s is not a parent of %s') %
229 229 (short(p), short(node)))
230 230 parent = p
231 231 else:
232 232 if opts.get('parent'):
233 233 raise util.Abort(_('cannot use --parent on non-merge changeset'))
234 234 parent = p1
235 235
236 236 # the backout should appear on the same branch
237 237 branch = repo.dirstate.branch()
238 238 hg.clean(repo, node, show_stats=False)
239 239 repo.dirstate.setbranch(branch)
240 240 revert_opts = opts.copy()
241 241 revert_opts['date'] = None
242 242 revert_opts['all'] = True
243 243 revert_opts['rev'] = hex(parent)
244 244 revert_opts['no_backup'] = None
245 245 revert(ui, repo, **revert_opts)
246 246 commit_opts = opts.copy()
247 247 commit_opts['addremove'] = False
248 248 if not commit_opts['message'] and not commit_opts['logfile']:
249 249 # we don't translate commit messages
250 250 commit_opts['message'] = "Backed out changeset %s" % short(node)
251 251 commit_opts['force_editor'] = True
252 252 commit(ui, repo, **commit_opts)
253 253 def nice(node):
254 254 return '%d:%s' % (repo.changelog.rev(node), short(node))
255 255 ui.status(_('changeset %s backs out changeset %s\n') %
256 256 (nice(repo.changelog.tip()), nice(node)))
257 257 if op1 != node:
258 258 hg.clean(repo, op1, show_stats=False)
259 259 if opts.get('merge'):
260 260 ui.status(_('merging with changeset %s\n')
261 261 % nice(repo.changelog.tip()))
262 262 hg.merge(repo, hex(repo.changelog.tip()))
263 263 else:
264 264 ui.status(_('the backout changeset is a new head - '
265 265 'do not forget to merge\n'))
266 266 ui.status(_('(use "backout --merge" '
267 267 'if you want to auto-merge)\n'))
268 268
269 269 def bisect(ui, repo, rev=None, extra=None, command=None,
270 270 reset=None, good=None, bad=None, skip=None, noupdate=None):
271 271 """subdivision search of changesets
272 272
273 273 This command helps to find changesets which introduce problems. To
274 274 use, mark the earliest changeset you know exhibits the problem as
275 275 bad, then mark the latest changeset which is free from the problem
276 276 as good. Bisect will update your working directory to a revision
277 277 for testing (unless the -U/--noupdate option is specified). Once
278 278 you have performed tests, mark the working directory as good or
279 279 bad, and bisect will either update to another candidate changeset
280 280 or announce that it has found the bad revision.
281 281
282 282 As a shortcut, you can also use the revision argument to mark a
283 283 revision as good or bad without checking it out first.
284 284
285 285 If you supply a command, it will be used for automatic bisection.
286 286 Its exit status will be used to mark revisions as good or bad:
287 287 status 0 means good, 125 means to skip the revision, 127
288 288 (command not found) will abort the bisection, and any other
289 289 non-zero exit status means the revision is bad.
290 290 """
291 291 def print_result(nodes, good):
292 292 displayer = cmdutil.show_changeset(ui, repo, {})
293 293 if len(nodes) == 1:
294 294 # narrowed it down to a single revision
295 295 if good:
296 296 ui.write(_("The first good revision is:\n"))
297 297 else:
298 298 ui.write(_("The first bad revision is:\n"))
299 299 displayer.show(repo[nodes[0]])
300 300 else:
301 301 # multiple possible revisions
302 302 if good:
303 303 ui.write(_("Due to skipped revisions, the first "
304 304 "good revision could be any of:\n"))
305 305 else:
306 306 ui.write(_("Due to skipped revisions, the first "
307 307 "bad revision could be any of:\n"))
308 308 for n in nodes:
309 309 displayer.show(repo[n])
310 310 displayer.close()
311 311
312 312 def check_state(state, interactive=True):
313 313 if not state['good'] or not state['bad']:
314 314 if (good or bad or skip or reset) and interactive:
315 315 return
316 316 if not state['good']:
317 317 raise util.Abort(_('cannot bisect (no known good revisions)'))
318 318 else:
319 319 raise util.Abort(_('cannot bisect (no known bad revisions)'))
320 320 return True
321 321
322 322 # backward compatibility
323 323 if rev in "good bad reset init".split():
324 324 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
325 325 cmd, rev, extra = rev, extra, None
326 326 if cmd == "good":
327 327 good = True
328 328 elif cmd == "bad":
329 329 bad = True
330 330 else:
331 331 reset = True
332 332 elif extra or good + bad + skip + reset + bool(command) > 1:
333 333 raise util.Abort(_('incompatible arguments'))
334 334
335 335 if reset:
336 336 p = repo.join("bisect.state")
337 337 if os.path.exists(p):
338 338 os.unlink(p)
339 339 return
340 340
341 341 state = hbisect.load_state(repo)
342 342
343 343 if command:
344 344 changesets = 1
345 345 try:
346 346 while changesets:
347 347 # update state
348 348 status = util.system(command)
349 349 if status == 125:
350 350 transition = "skip"
351 351 elif status == 0:
352 352 transition = "good"
353 353 # status < 0 means process was killed
354 354 elif status == 127:
355 355 raise util.Abort(_("failed to execute %s") % command)
356 356 elif status < 0:
357 357 raise util.Abort(_("%s killed") % command)
358 358 else:
359 359 transition = "bad"
360 360 ctx = repo[rev or '.']
361 361 state[transition].append(ctx.node())
362 362 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
363 363 check_state(state, interactive=False)
364 364 # bisect
365 365 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
366 366 # update to next check
367 367 cmdutil.bail_if_changed(repo)
368 368 hg.clean(repo, nodes[0], show_stats=False)
369 369 finally:
370 370 hbisect.save_state(repo, state)
371 371 return print_result(nodes, good)
372 372
373 373 # update state
374 374 node = repo.lookup(rev or '.')
375 375 if good or bad or skip:
376 376 if good:
377 377 state['good'].append(node)
378 378 elif bad:
379 379 state['bad'].append(node)
380 380 elif skip:
381 381 state['skip'].append(node)
382 382 hbisect.save_state(repo, state)
383 383
384 384 if not check_state(state):
385 385 return
386 386
387 387 # actually bisect
388 388 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
389 389 if changesets == 0:
390 390 print_result(nodes, good)
391 391 else:
392 392 assert len(nodes) == 1 # only a single node can be tested next
393 393 node = nodes[0]
394 394 # compute the approximate number of remaining tests
395 395 tests, size = 0, 2
396 396 while size <= changesets:
397 397 tests, size = tests + 1, size * 2
398 398 rev = repo.changelog.rev(node)
399 399 ui.write(_("Testing changeset %d:%s "
400 400 "(%d changesets remaining, ~%d tests)\n")
401 401 % (rev, short(node), changesets, tests))
402 402 if not noupdate:
403 403 cmdutil.bail_if_changed(repo)
404 404 return hg.clean(repo, node)
405 405
406 406 def branch(ui, repo, label=None, **opts):
407 407 """set or show the current branch name
408 408
409 409 With no argument, show the current branch name. With one argument,
410 410 set the working directory branch name (the branch will not exist
411 411 in the repository until the next commit). Standard practice
412 412 recommends that primary development take place on the 'default'
413 413 branch.
414 414
415 415 Unless -f/--force is specified, branch will not let you set a
416 416 branch name that already exists, even if it's inactive.
417 417
418 418 Use -C/--clean to reset the working directory branch to that of
419 419 the parent of the working directory, negating a previous branch
420 420 change.
421 421
422 422 Use the command 'hg update' to switch to an existing branch. Use
423 423 'hg commit --close-branch' to mark this branch as closed.
424 424 """
425 425
426 426 if opts.get('clean'):
427 427 label = repo[None].parents()[0].branch()
428 428 repo.dirstate.setbranch(label)
429 429 ui.status(_('reset working directory to branch %s\n') % label)
430 430 elif label:
431 431 utflabel = encoding.fromlocal(label)
432 432 if not opts.get('force') and utflabel in repo.branchtags():
433 433 if label not in [p.branch() for p in repo.parents()]:
434 434 raise util.Abort(_('a branch of the same name already exists'
435 435 ' (use --force to override)'))
436 436 repo.dirstate.setbranch(utflabel)
437 437 ui.status(_('marked working directory as branch %s\n') % label)
438 438 else:
439 439 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
440 440
441 441 def branches(ui, repo, active=False, closed=False):
442 442 """list repository named branches
443 443
444 444 List the repository's named branches, indicating which ones are
445 445 inactive. If -c/--closed is specified, also list branches which have
446 446 been marked closed (see hg commit --close-branch).
447 447
448 448 If -a/--active is specified, only show active branches. A branch
449 449 is considered active if it contains repository heads.
450 450
451 451 Use the command 'hg update' to switch to an existing branch.
452 452 """
453 453
454 454 hexfunc = ui.debugflag and hex or short
455 455 activebranches = [repo[n].branch() for n in repo.heads()]
456 456 def testactive(tag, node):
457 457 realhead = tag in activebranches
458 458 open = node in repo.branchheads(tag, closed=False)
459 459 return realhead and open
460 460 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
461 461 for tag, node in repo.branchtags().items()],
462 462 reverse=True)
463 463
464 464 for isactive, node, tag in branches:
465 465 if (not active) or isactive:
466 466 encodedtag = encoding.tolocal(tag)
467 467 if ui.quiet:
468 468 ui.write("%s\n" % encodedtag)
469 469 else:
470 470 hn = repo.lookup(node)
471 471 if isactive:
472 472 notice = ''
473 473 elif hn not in repo.branchheads(tag, closed=False):
474 474 if not closed:
475 475 continue
476 476 notice = _(' (closed)')
477 477 else:
478 478 notice = _(' (inactive)')
479 479 rev = str(node).rjust(31 - encoding.colwidth(encodedtag))
480 480 data = encodedtag, rev, hexfunc(hn), notice
481 481 ui.write("%s %s:%s%s\n" % data)
482 482
483 483 def bundle(ui, repo, fname, dest=None, **opts):
484 484 """create a changegroup file
485 485
486 486 Generate a compressed changegroup file collecting changesets not
487 487 known to be in another repository.
488 488
489 489 If you omit the destination repository, then hg assumes the
490 490 destination will have all the nodes you specify with --base
491 491 parameters. To create a bundle containing all changesets, use
492 492 -a/--all (or --base null).
493 493
494 494 You can change compression method with the -t/--type option.
495 495 The available compression methods are: none, bzip2, and
496 496 gzip (by default, bundles are compressed using bzip2).
497 497
498 498 The bundle file can then be transferred using conventional means
499 499 and applied to another repository with the unbundle or pull
500 500 command. This is useful when direct push and pull are not
501 501 available or when exporting an entire repository is undesirable.
502 502
503 503 Applying bundles preserves all changeset contents including
504 504 permissions, copy/rename information, and revision history.
505 505 """
506 506 revs = opts.get('rev') or None
507 507 if revs:
508 508 revs = [repo.lookup(rev) for rev in revs]
509 509 if opts.get('all'):
510 510 base = ['null']
511 511 else:
512 512 base = opts.get('base')
513 513 if base:
514 514 if dest:
515 515 raise util.Abort(_("--base is incompatible with specifying "
516 516 "a destination"))
517 517 base = [repo.lookup(rev) for rev in base]
518 518 # create the right base
519 519 # XXX: nodesbetween / changegroup* should be "fixed" instead
520 520 o = []
521 521 has = set((nullid,))
522 522 for n in base:
523 523 has.update(repo.changelog.reachable(n))
524 524 if revs:
525 525 visit = list(revs)
526 526 else:
527 527 visit = repo.changelog.heads()
528 528 seen = {}
529 529 while visit:
530 530 n = visit.pop(0)
531 531 parents = [p for p in repo.changelog.parents(n) if p not in has]
532 532 if len(parents) == 0:
533 533 o.insert(0, n)
534 534 else:
535 535 for p in parents:
536 536 if p not in seen:
537 537 seen[p] = 1
538 538 visit.append(p)
539 539 else:
540 540 dest = ui.expandpath(dest or 'default-push', dest or 'default')
541 541 dest, branches = hg.parseurl(dest, opts.get('branch'))
542 542 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
543 543 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
544 544 o = repo.findoutgoing(other, force=opts.get('force'))
545 545
546 546 if revs:
547 547 cg = repo.changegroupsubset(o, revs, 'bundle')
548 548 else:
549 549 cg = repo.changegroup(o, 'bundle')
550 550
551 551 bundletype = opts.get('type', 'bzip2').lower()
552 552 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
553 553 bundletype = btypes.get(bundletype)
554 554 if bundletype not in changegroup.bundletypes:
555 555 raise util.Abort(_('unknown bundle type specified with --type'))
556 556
557 557 changegroup.writebundle(cg, fname, bundletype)
558 558
559 559 def cat(ui, repo, file1, *pats, **opts):
560 560 """output the current or given revision of files
561 561
562 562 Print the specified files as they were at the given revision. If
563 563 no revision is given, the parent of the working directory is used,
564 564 or tip if no revision is checked out.
565 565
566 566 Output may be to a file, in which case the name of the file is
567 567 given using a format string. The formatting rules are the same as
568 568 for the export command, with the following additions:
569 569
570 570 :``%s``: basename of file being printed
571 571 :``%d``: dirname of file being printed, or '.' if in repository root
572 572 :``%p``: root-relative path name of file being printed
573 573 """
574 574 ctx = repo[opts.get('rev')]
575 575 err = 1
576 576 m = cmdutil.match(repo, (file1,) + pats, opts)
577 577 for abs in ctx.walk(m):
578 578 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
579 579 data = ctx[abs].data()
580 580 if opts.get('decode'):
581 581 data = repo.wwritedata(abs, data)
582 582 fp.write(data)
583 583 err = 0
584 584 return err
585 585
586 586 def clone(ui, source, dest=None, **opts):
587 587 """make a copy of an existing repository
588 588
589 589 Create a copy of an existing repository in a new directory.
590 590
591 591 If no destination directory name is specified, it defaults to the
592 592 basename of the source.
593 593
594 594 The location of the source is added to the new repository's
595 595 .hg/hgrc file, as the default to be used for future pulls.
596 596
597 597 See 'hg help urls' for valid source format details.
598 598
599 599 It is possible to specify an ``ssh://`` URL as the destination, but no
600 600 .hg/hgrc and working directory will be created on the remote side.
601 601 Please see 'hg help urls' for important details about ``ssh://`` URLs.
602 602
603 603 If the -U/--noupdate option is specified, the new clone will contain
604 604 only a repository (.hg) and no working copy (the working copy parent
605 605 will be the null changeset). Otherwise, clone will initially check
606 606 out (in order of precedence):
607 607
608 608 a) the changeset, tag or branch specified with -u/--updaterev
609 609 b) the changeset, tag or branch given with the first -r/--rev
610 610 c) the branch given with the first -b/--branch
611 611 d) the branch given with the url#branch source syntax
612 612 e) the head of the default branch
613 613
614 614 Use 'hg clone -u . src dst' to checkout the source repository's
615 615 parent changeset (applicable for local source repositories only).
616 616
617 617 A set of changesets (tags, or branch names) to pull may be specified
618 618 by listing each changeset (tag, or branch name) with -r/--rev.
619 619 If -r/--rev is used, the cloned repository will contain only a subset
620 620 of the changesets of the source repository. Only the set of changesets
621 621 defined by all -r/--rev options (including all their ancestors)
622 622 will be pulled into the destination repository.
623 623 No subsequent changesets (including subsequent tags) will be present
624 624 in the destination.
625 625
626 626 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
627 627 local source repositories.
628 628
629 629 For efficiency, hardlinks are used for cloning whenever the source
630 630 and destination are on the same filesystem (note this applies only
631 631 to the repository data, not to the checked out files). Some
632 632 filesystems, such as AFS, implement hardlinking incorrectly, but
633 633 do not report errors. In these cases, use the --pull option to
634 634 avoid hardlinking.
635 635
636 636 In some cases, you can clone repositories and checked out files
637 637 using full hardlinks with ::
638 638
639 639 $ cp -al REPO REPOCLONE
640 640
641 641 This is the fastest way to clone, but it is not always safe. The
642 642 operation is not atomic (making sure REPO is not modified during
643 643 the operation is up to you) and you have to make sure your editor
644 644 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
645 645 this is not compatible with certain extensions that place their
646 646 metadata under the .hg directory, such as mq.
647 647 """
648 648 if opts.get('noupdate') and opts.get('updaterev'):
649 649 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
650 650
651 651 hg.clone(cmdutil.remoteui(ui, opts), source, dest,
652 652 pull=opts.get('pull'),
653 653 stream=opts.get('uncompressed'),
654 654 rev=opts.get('rev'),
655 655 update=opts.get('updaterev') or not opts.get('noupdate'),
656 656 branch=opts.get('branch'))
657 657
658 658 def commit(ui, repo, *pats, **opts):
659 659 """commit the specified files or all outstanding changes
660 660
661 661 Commit changes to the given files into the repository. Unlike a
662 662 centralized RCS, this operation is a local operation. See hg push
663 663 for a way to actively distribute your changes.
664 664
665 665 If a list of files is omitted, all changes reported by "hg status"
666 666 will be committed.
667 667
668 668 If you are committing the result of a merge, do not provide any
669 669 filenames or -I/-X filters.
670 670
671 671 If no commit message is specified, the configured editor is
672 672 started to prompt you for a message.
673 673
674 674 See 'hg help dates' for a list of formats valid for -d/--date.
675 675 """
676 676 extra = {}
677 677 if opts.get('close_branch'):
678 678 extra['close'] = 1
679 679 e = cmdutil.commiteditor
680 680 if opts.get('force_editor'):
681 681 e = cmdutil.commitforceeditor
682 682
683 683 def commitfunc(ui, repo, message, match, opts):
684 684 return repo.commit(message, opts.get('user'), opts.get('date'), match,
685 685 editor=e, extra=extra)
686 686
687 687 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
688 688 if not node:
689 689 ui.status(_("nothing changed\n"))
690 690 return
691 691 cl = repo.changelog
692 692 rev = cl.rev(node)
693 693 parents = cl.parentrevs(rev)
694 694 if rev - 1 in parents:
695 695 # one of the parents was the old tip
696 696 pass
697 697 elif (parents == (nullrev, nullrev) or
698 698 len(cl.heads(cl.node(parents[0]))) > 1 and
699 699 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
700 700 ui.status(_('created new head\n'))
701 701
702 702 if ui.debugflag:
703 703 ui.write(_('committed changeset %d:%s\n') % (rev, hex(node)))
704 704 elif ui.verbose:
705 705 ui.write(_('committed changeset %d:%s\n') % (rev, short(node)))
706 706
707 707 def copy(ui, repo, *pats, **opts):
708 708 """mark files as copied for the next commit
709 709
710 710 Mark dest as having copies of source files. If dest is a
711 711 directory, copies are put in that directory. If dest is a file,
712 712 the source must be a single file.
713 713
714 714 By default, this command copies the contents of files as they
715 715 exist in the working directory. If invoked with -A/--after, the
716 716 operation is recorded, but no copying is performed.
717 717
718 718 This command takes effect with the next commit. To undo a copy
719 719 before that, see hg revert.
720 720 """
721 721 wlock = repo.wlock(False)
722 722 try:
723 723 return cmdutil.copy(ui, repo, pats, opts)
724 724 finally:
725 725 wlock.release()
726 726
727 727 def debugancestor(ui, repo, *args):
728 728 """find the ancestor revision of two revisions in a given index"""
729 729 if len(args) == 3:
730 730 index, rev1, rev2 = args
731 731 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
732 732 lookup = r.lookup
733 733 elif len(args) == 2:
734 734 if not repo:
735 735 raise util.Abort(_("There is no Mercurial repository here "
736 736 "(.hg not found)"))
737 737 rev1, rev2 = args
738 738 r = repo.changelog
739 739 lookup = repo.lookup
740 740 else:
741 741 raise util.Abort(_('either two or three arguments required'))
742 742 a = r.ancestor(lookup(rev1), lookup(rev2))
743 743 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
744 744
745 745 def debugcommands(ui, cmd='', *args):
746 746 for cmd, vals in sorted(table.iteritems()):
747 747 cmd = cmd.split('|')[0].strip('^')
748 748 opts = ', '.join([i[1] for i in vals[1]])
749 749 ui.write('%s: %s\n' % (cmd, opts))
750 750
751 751 def debugcomplete(ui, cmd='', **opts):
752 752 """returns the completion list associated with the given command"""
753 753
754 754 if opts.get('options'):
755 755 options = []
756 756 otables = [globalopts]
757 757 if cmd:
758 758 aliases, entry = cmdutil.findcmd(cmd, table, False)
759 759 otables.append(entry[1])
760 760 for t in otables:
761 761 for o in t:
762 762 if o[0]:
763 763 options.append('-%s' % o[0])
764 764 options.append('--%s' % o[1])
765 765 ui.write("%s\n" % "\n".join(options))
766 766 return
767 767
768 768 cmdlist = cmdutil.findpossible(cmd, table)
769 769 if ui.verbose:
770 770 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
771 771 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
772 772
773 773 def debugfsinfo(ui, path = "."):
774 774 open('.debugfsinfo', 'w').write('')
775 775 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
776 776 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
777 777 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
778 778 and 'yes' or 'no'))
779 779 os.unlink('.debugfsinfo')
780 780
781 781 def debugrebuildstate(ui, repo, rev="tip"):
782 782 """rebuild the dirstate as it would look like for the given revision"""
783 783 ctx = repo[rev]
784 784 wlock = repo.wlock()
785 785 try:
786 786 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
787 787 finally:
788 788 wlock.release()
789 789
790 790 def debugcheckstate(ui, repo):
791 791 """validate the correctness of the current dirstate"""
792 792 parent1, parent2 = repo.dirstate.parents()
793 793 m1 = repo[parent1].manifest()
794 794 m2 = repo[parent2].manifest()
795 795 errors = 0
796 796 for f in repo.dirstate:
797 797 state = repo.dirstate[f]
798 798 if state in "nr" and f not in m1:
799 799 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
800 800 errors += 1
801 801 if state in "a" and f in m1:
802 802 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
803 803 errors += 1
804 804 if state in "m" and f not in m1 and f not in m2:
805 805 ui.warn(_("%s in state %s, but not in either manifest\n") %
806 806 (f, state))
807 807 errors += 1
808 808 for f in m1:
809 809 state = repo.dirstate[f]
810 810 if state not in "nrm":
811 811 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
812 812 errors += 1
813 813 if errors:
814 814 error = _(".hg/dirstate inconsistent with current parent's manifest")
815 815 raise util.Abort(error)
816 816
817 817 def showconfig(ui, repo, *values, **opts):
818 818 """show combined config settings from all hgrc files
819 819
820 820 With no arguments, print names and values of all config items.
821 821
822 822 With one argument of the form section.name, print just the value
823 823 of that config item.
824 824
825 825 With multiple arguments, print names and values of all config
826 826 items with matching section names.
827 827
828 828 With --debug, the source (filename and line number) is printed
829 829 for each config item.
830 830 """
831 831
832 832 untrusted = bool(opts.get('untrusted'))
833 833 if values:
834 834 if len([v for v in values if '.' in v]) > 1:
835 835 raise util.Abort(_('only one config item permitted'))
836 836 for section, name, value in ui.walkconfig(untrusted=untrusted):
837 837 sectname = section + '.' + name
838 838 if values:
839 839 for v in values:
840 840 if v == section:
841 841 ui.debug('%s: ' %
842 842 ui.configsource(section, name, untrusted))
843 843 ui.write('%s=%s\n' % (sectname, value))
844 844 elif v == sectname:
845 845 ui.debug('%s: ' %
846 846 ui.configsource(section, name, untrusted))
847 847 ui.write(value, '\n')
848 848 else:
849 849 ui.debug('%s: ' %
850 850 ui.configsource(section, name, untrusted))
851 851 ui.write('%s=%s\n' % (sectname, value))
852 852
853 853 def debugsetparents(ui, repo, rev1, rev2=None):
854 854 """manually set the parents of the current working directory
855 855
856 856 This is useful for writing repository conversion tools, but should
857 857 be used with care.
858 858 """
859 859
860 860 if not rev2:
861 861 rev2 = hex(nullid)
862 862
863 863 wlock = repo.wlock()
864 864 try:
865 865 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
866 866 finally:
867 867 wlock.release()
868 868
869 869 def debugstate(ui, repo, nodates=None):
870 870 """show the contents of the current dirstate"""
871 871 timestr = ""
872 872 showdate = not nodates
873 873 for file_, ent in sorted(repo.dirstate._map.iteritems()):
874 874 if showdate:
875 875 if ent[3] == -1:
876 876 # Pad or slice to locale representation
877 877 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
878 878 time.localtime(0)))
879 879 timestr = 'unset'
880 880 timestr = (timestr[:locale_len] +
881 881 ' ' * (locale_len - len(timestr)))
882 882 else:
883 883 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
884 884 time.localtime(ent[3]))
885 885 if ent[1] & 020000:
886 886 mode = 'lnk'
887 887 else:
888 888 mode = '%3o' % (ent[1] & 0777)
889 889 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
890 890 for f in repo.dirstate.copies():
891 891 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
892 892
893 893 def debugsub(ui, repo, rev=None):
894 894 if rev == '':
895 895 rev = None
896 896 for k, v in sorted(repo[rev].substate.items()):
897 897 ui.write('path %s\n' % k)
898 898 ui.write(' source %s\n' % v[0])
899 899 ui.write(' revision %s\n' % v[1])
900 900
901 901 def debugdata(ui, file_, rev):
902 902 """dump the contents of a data file revision"""
903 903 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
904 904 try:
905 905 ui.write(r.revision(r.lookup(rev)))
906 906 except KeyError:
907 907 raise util.Abort(_('invalid revision identifier %s') % rev)
908 908
909 909 def debugdate(ui, date, range=None, **opts):
910 910 """parse and display a date"""
911 911 if opts["extended"]:
912 912 d = util.parsedate(date, util.extendeddateformats)
913 913 else:
914 914 d = util.parsedate(date)
915 915 ui.write("internal: %s %s\n" % d)
916 916 ui.write("standard: %s\n" % util.datestr(d))
917 917 if range:
918 918 m = util.matchdate(range)
919 919 ui.write("match: %s\n" % m(d[0]))
920 920
921 921 def debugindex(ui, file_):
922 922 """dump the contents of an index file"""
923 923 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
924 924 ui.write(" rev offset length base linkrev"
925 925 " nodeid p1 p2\n")
926 926 for i in r:
927 927 node = r.node(i)
928 928 try:
929 929 pp = r.parents(node)
930 930 except:
931 931 pp = [nullid, nullid]
932 932 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
933 933 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
934 934 short(node), short(pp[0]), short(pp[1])))
935 935
936 936 def debugindexdot(ui, file_):
937 937 """dump an index DAG as a graphviz dot file"""
938 938 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
939 939 ui.write("digraph G {\n")
940 940 for i in r:
941 941 node = r.node(i)
942 942 pp = r.parents(node)
943 943 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
944 944 if pp[1] != nullid:
945 945 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
946 946 ui.write("}\n")
947 947
948 948 def debuginstall(ui):
949 949 '''test Mercurial installation'''
950 950
951 951 def writetemp(contents):
952 952 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
953 953 f = os.fdopen(fd, "wb")
954 954 f.write(contents)
955 955 f.close()
956 956 return name
957 957
958 958 problems = 0
959 959
960 960 # encoding
961 961 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
962 962 try:
963 963 encoding.fromlocal("test")
964 964 except util.Abort, inst:
965 965 ui.write(" %s\n" % inst)
966 966 ui.write(_(" (check that your locale is properly set)\n"))
967 967 problems += 1
968 968
969 969 # compiled modules
970 970 ui.status(_("Checking extensions...\n"))
971 971 try:
972 972 import bdiff, mpatch, base85
973 973 except Exception, inst:
974 974 ui.write(" %s\n" % inst)
975 975 ui.write(_(" One or more extensions could not be found"))
976 976 ui.write(_(" (check that you compiled the extensions)\n"))
977 977 problems += 1
978 978
979 979 # templates
980 980 ui.status(_("Checking templates...\n"))
981 981 try:
982 982 import templater
983 983 templater.templater(templater.templatepath("map-cmdline.default"))
984 984 except Exception, inst:
985 985 ui.write(" %s\n" % inst)
986 986 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
987 987 problems += 1
988 988
989 989 # patch
990 990 ui.status(_("Checking patch...\n"))
991 991 patchproblems = 0
992 992 a = "1\n2\n3\n4\n"
993 993 b = "1\n2\n3\ninsert\n4\n"
994 994 fa = writetemp(a)
995 995 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
996 996 os.path.basename(fa))
997 997 fd = writetemp(d)
998 998
999 999 files = {}
1000 1000 try:
1001 1001 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1002 1002 except util.Abort, e:
1003 1003 ui.write(_(" patch call failed:\n"))
1004 1004 ui.write(" " + str(e) + "\n")
1005 1005 patchproblems += 1
1006 1006 else:
1007 1007 if list(files) != [os.path.basename(fa)]:
1008 1008 ui.write(_(" unexpected patch output!\n"))
1009 1009 patchproblems += 1
1010 1010 a = open(fa).read()
1011 1011 if a != b:
1012 1012 ui.write(_(" patch test failed!\n"))
1013 1013 patchproblems += 1
1014 1014
1015 1015 if patchproblems:
1016 1016 if ui.config('ui', 'patch'):
1017 1017 ui.write(_(" (Current patch tool may be incompatible with patch,"
1018 1018 " or misconfigured. Please check your .hgrc file)\n"))
1019 1019 else:
1020 1020 ui.write(_(" Internal patcher failure, please report this error"
1021 1021 " to http://mercurial.selenic.com/bts/\n"))
1022 1022 problems += patchproblems
1023 1023
1024 1024 os.unlink(fa)
1025 1025 os.unlink(fd)
1026 1026
1027 1027 # editor
1028 1028 ui.status(_("Checking commit editor...\n"))
1029 1029 editor = ui.geteditor()
1030 1030 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1031 1031 if not cmdpath:
1032 1032 if editor == 'vi':
1033 1033 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1034 1034 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1035 1035 else:
1036 1036 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1037 1037 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1038 1038 problems += 1
1039 1039
1040 1040 # check username
1041 1041 ui.status(_("Checking username...\n"))
1042 1042 try:
1043 1043 user = ui.username()
1044 1044 except util.Abort, e:
1045 1045 ui.write(" %s\n" % e)
1046 1046 ui.write(_(" (specify a username in your .hgrc file)\n"))
1047 1047 problems += 1
1048 1048
1049 1049 if not problems:
1050 1050 ui.status(_("No problems detected\n"))
1051 1051 else:
1052 1052 ui.write(_("%s problems detected,"
1053 1053 " please check your install!\n") % problems)
1054 1054
1055 1055 return problems
1056 1056
1057 1057 def debugrename(ui, repo, file1, *pats, **opts):
1058 1058 """dump rename information"""
1059 1059
1060 1060 ctx = repo[opts.get('rev')]
1061 1061 m = cmdutil.match(repo, (file1,) + pats, opts)
1062 1062 for abs in ctx.walk(m):
1063 1063 fctx = ctx[abs]
1064 1064 o = fctx.filelog().renamed(fctx.filenode())
1065 1065 rel = m.rel(abs)
1066 1066 if o:
1067 1067 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1068 1068 else:
1069 1069 ui.write(_("%s not renamed\n") % rel)
1070 1070
1071 1071 def debugwalk(ui, repo, *pats, **opts):
1072 1072 """show how files match on given patterns"""
1073 1073 m = cmdutil.match(repo, pats, opts)
1074 1074 items = list(repo.walk(m))
1075 1075 if not items:
1076 1076 return
1077 1077 fmt = 'f %%-%ds %%-%ds %%s' % (
1078 1078 max([len(abs) for abs in items]),
1079 1079 max([len(m.rel(abs)) for abs in items]))
1080 1080 for abs in items:
1081 1081 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1082 1082 ui.write("%s\n" % line.rstrip())
1083 1083
1084 1084 def diff(ui, repo, *pats, **opts):
1085 1085 """diff repository (or selected files)
1086 1086
1087 1087 Show differences between revisions for the specified files.
1088 1088
1089 1089 Differences between files are shown using the unified diff format.
1090 1090
1091 1091 NOTE: diff may generate unexpected results for merges, as it will
1092 1092 default to comparing against the working directory's first parent
1093 1093 changeset if no revisions are specified.
1094 1094
1095 1095 When two revision arguments are given, then changes are shown
1096 1096 between those revisions. If only one revision is specified then
1097 1097 that revision is compared to the working directory, and, when no
1098 1098 revisions are specified, the working directory files are compared
1099 1099 to its parent.
1100 1100
1101 1101 Without the -a/--text option, diff will avoid generating diffs of
1102 1102 files it detects as binary. With -a, diff will generate a diff
1103 1103 anyway, probably with undesirable results.
1104 1104
1105 1105 Use the -g/--git option to generate diffs in the git extended diff
1106 1106 format. For more information, read 'hg help diffs'.
1107 1107 """
1108 1108
1109 1109 revs = opts.get('rev')
1110 1110 change = opts.get('change')
1111 1111 stat = opts.get('stat')
1112 1112 reverse = opts.get('reverse')
1113 1113
1114 1114 if revs and change:
1115 1115 msg = _('cannot specify --rev and --change at the same time')
1116 1116 raise util.Abort(msg)
1117 1117 elif change:
1118 1118 node2 = repo.lookup(change)
1119 1119 node1 = repo[node2].parents()[0].node()
1120 1120 else:
1121 1121 node1, node2 = cmdutil.revpair(repo, revs)
1122 1122
1123 1123 if reverse:
1124 1124 node1, node2 = node2, node1
1125 1125
1126 1126 if stat:
1127 1127 opts['unified'] = '0'
1128 1128 diffopts = patch.diffopts(ui, opts)
1129 1129
1130 1130 m = cmdutil.match(repo, pats, opts)
1131 1131 it = patch.diff(repo, node1, node2, match=m, opts=diffopts)
1132 1132 if stat:
1133 1133 width = ui.interactive() and util.termwidth() or 80
1134 1134 ui.write(patch.diffstat(util.iterlines(it), width=width,
1135 1135 git=diffopts.git))
1136 1136 else:
1137 1137 for chunk in it:
1138 1138 ui.write(chunk)
1139 1139
1140 1140 def export(ui, repo, *changesets, **opts):
1141 1141 """dump the header and diffs for one or more changesets
1142 1142
1143 1143 Print the changeset header and diffs for one or more revisions.
1144 1144
1145 1145 The information shown in the changeset header is: author, date,
1146 1146 branch name (if non-default), changeset hash, parent(s) and commit
1147 1147 comment.
1148 1148
1149 1149 NOTE: export may generate unexpected diff output for merge
1150 1150 changesets, as it will compare the merge changeset against its
1151 1151 first parent only.
1152 1152
1153 1153 Output may be to a file, in which case the name of the file is
1154 1154 given using a format string. The formatting rules are as follows:
1155 1155
1156 1156 :``%%``: literal "%" character
1157 1157 :``%H``: changeset hash (40 bytes of hexadecimal)
1158 1158 :``%N``: number of patches being generated
1159 1159 :``%R``: changeset revision number
1160 1160 :``%b``: basename of the exporting repository
1161 1161 :``%h``: short-form changeset hash (12 bytes of hexadecimal)
1162 1162 :``%n``: zero-padded sequence number, starting at 1
1163 1163 :``%r``: zero-padded changeset revision number
1164 1164
1165 1165 Without the -a/--text option, export will avoid generating diffs
1166 1166 of files it detects as binary. With -a, export will generate a
1167 1167 diff anyway, probably with undesirable results.
1168 1168
1169 1169 Use the -g/--git option to generate diffs in the git extended diff
1170 1170 format. See 'hg help diffs' for more information.
1171 1171
1172 1172 With the --switch-parent option, the diff will be against the
1173 1173 second parent. It can be useful to review a merge.
1174 1174 """
1175 1175 changesets += tuple(opts.get('rev', []))
1176 1176 if not changesets:
1177 1177 raise util.Abort(_("export requires at least one changeset"))
1178 1178 revs = cmdutil.revrange(repo, changesets)
1179 1179 if len(revs) > 1:
1180 1180 ui.note(_('exporting patches:\n'))
1181 1181 else:
1182 1182 ui.note(_('exporting patch:\n'))
1183 1183 patch.export(repo, revs, template=opts.get('output'),
1184 1184 switch_parent=opts.get('switch_parent'),
1185 1185 opts=patch.diffopts(ui, opts))
1186 1186
1187 1187 def forget(ui, repo, *pats, **opts):
1188 1188 """forget the specified files on the next commit
1189 1189
1190 1190 Mark the specified files so they will no longer be tracked
1191 1191 after the next commit.
1192 1192
1193 1193 This only removes files from the current branch, not from the
1194 1194 entire project history, and it does not delete them from the
1195 1195 working directory.
1196 1196
1197 1197 To undo a forget before the next commit, see hg add.
1198 1198 """
1199 1199
1200 1200 if not pats:
1201 1201 raise util.Abort(_('no files specified'))
1202 1202
1203 1203 m = cmdutil.match(repo, pats, opts)
1204 1204 s = repo.status(match=m, clean=True)
1205 1205 forget = sorted(s[0] + s[1] + s[3] + s[6])
1206 1206
1207 1207 for f in m.files():
1208 1208 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1209 1209 ui.warn(_('not removing %s: file is already untracked\n')
1210 1210 % m.rel(f))
1211 1211
1212 1212 for f in forget:
1213 1213 if ui.verbose or not m.exact(f):
1214 1214 ui.status(_('removing %s\n') % m.rel(f))
1215 1215
1216 1216 repo.remove(forget, unlink=False)
1217 1217
1218 1218 def grep(ui, repo, pattern, *pats, **opts):
1219 1219 """search for a pattern in specified files and revisions
1220 1220
1221 1221 Search revisions of files for a regular expression.
1222 1222
1223 1223 This command behaves differently than Unix grep. It only accepts
1224 1224 Python/Perl regexps. It searches repository history, not the
1225 1225 working directory. It always prints the revision number in which a
1226 1226 match appears.
1227 1227
1228 1228 By default, grep only prints output for the first revision of a
1229 1229 file in which it finds a match. To get it to print every revision
1230 1230 that contains a change in match status ("-" for a match that
1231 1231 becomes a non-match, or "+" for a non-match that becomes a match),
1232 1232 use the --all flag.
1233 1233 """
1234 1234 reflags = 0
1235 1235 if opts.get('ignore_case'):
1236 1236 reflags |= re.I
1237 1237 try:
1238 1238 regexp = re.compile(pattern, reflags)
1239 1239 except Exception, inst:
1240 1240 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1241 1241 return None
1242 1242 sep, eol = ':', '\n'
1243 1243 if opts.get('print0'):
1244 1244 sep = eol = '\0'
1245 1245
1246 1246 getfile = util.lrucachefunc(repo.file)
1247 1247
1248 1248 def matchlines(body):
1249 1249 begin = 0
1250 1250 linenum = 0
1251 1251 while True:
1252 1252 match = regexp.search(body, begin)
1253 1253 if not match:
1254 1254 break
1255 1255 mstart, mend = match.span()
1256 1256 linenum += body.count('\n', begin, mstart) + 1
1257 1257 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1258 1258 begin = body.find('\n', mend) + 1 or len(body)
1259 1259 lend = begin - 1
1260 1260 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1261 1261
1262 1262 class linestate(object):
1263 1263 def __init__(self, line, linenum, colstart, colend):
1264 1264 self.line = line
1265 1265 self.linenum = linenum
1266 1266 self.colstart = colstart
1267 1267 self.colend = colend
1268 1268
1269 1269 def __hash__(self):
1270 1270 return hash((self.linenum, self.line))
1271 1271
1272 1272 def __eq__(self, other):
1273 1273 return self.line == other.line
1274 1274
1275 1275 matches = {}
1276 1276 copies = {}
1277 1277 def grepbody(fn, rev, body):
1278 1278 matches[rev].setdefault(fn, [])
1279 1279 m = matches[rev][fn]
1280 1280 for lnum, cstart, cend, line in matchlines(body):
1281 1281 s = linestate(line, lnum, cstart, cend)
1282 1282 m.append(s)
1283 1283
1284 1284 def difflinestates(a, b):
1285 1285 sm = difflib.SequenceMatcher(None, a, b)
1286 1286 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1287 1287 if tag == 'insert':
1288 1288 for i in xrange(blo, bhi):
1289 1289 yield ('+', b[i])
1290 1290 elif tag == 'delete':
1291 1291 for i in xrange(alo, ahi):
1292 1292 yield ('-', a[i])
1293 1293 elif tag == 'replace':
1294 1294 for i in xrange(alo, ahi):
1295 1295 yield ('-', a[i])
1296 1296 for i in xrange(blo, bhi):
1297 1297 yield ('+', b[i])
1298 1298
1299 1299 def display(fn, ctx, pstates, states):
1300 1300 rev = ctx.rev()
1301 1301 datefunc = ui.quiet and util.shortdate or util.datestr
1302 1302 found = False
1303 1303 filerevmatches = {}
1304 1304 if opts.get('all'):
1305 1305 iter = difflinestates(pstates, states)
1306 1306 else:
1307 1307 iter = [('', l) for l in states]
1308 1308 for change, l in iter:
1309 1309 cols = [fn, str(rev)]
1310 1310 if opts.get('line_number'):
1311 1311 cols.append(str(l.linenum))
1312 1312 if opts.get('all'):
1313 1313 cols.append(change)
1314 1314 if opts.get('user'):
1315 1315 cols.append(ui.shortuser(ctx.user()))
1316 1316 if opts.get('date'):
1317 1317 cols.append(datefunc(ctx.date()))
1318 1318 if opts.get('files_with_matches'):
1319 1319 c = (fn, rev)
1320 1320 if c in filerevmatches:
1321 1321 continue
1322 1322 filerevmatches[c] = 1
1323 1323 else:
1324 1324 cols.append(l.line)
1325 1325 ui.write(sep.join(cols), eol)
1326 1326 found = True
1327 1327 return found
1328 1328
1329 1329 skip = {}
1330 1330 revfiles = {}
1331 1331 matchfn = cmdutil.match(repo, pats, opts)
1332 1332 found = False
1333 1333 follow = opts.get('follow')
1334 1334
1335 1335 def prep(ctx, fns):
1336 1336 rev = ctx.rev()
1337 1337 pctx = ctx.parents()[0]
1338 1338 parent = pctx.rev()
1339 1339 matches.setdefault(rev, {})
1340 1340 matches.setdefault(parent, {})
1341 1341 files = revfiles.setdefault(rev, [])
1342 1342 for fn in fns:
1343 1343 flog = getfile(fn)
1344 1344 try:
1345 1345 fnode = ctx.filenode(fn)
1346 1346 except error.LookupError:
1347 1347 continue
1348 1348
1349 1349 copied = flog.renamed(fnode)
1350 1350 copy = follow and copied and copied[0]
1351 1351 if copy:
1352 1352 copies.setdefault(rev, {})[fn] = copy
1353 1353 if fn in skip:
1354 1354 if copy:
1355 1355 skip[copy] = True
1356 1356 continue
1357 1357 files.append(fn)
1358 1358
1359 1359 if fn not in matches[rev]:
1360 1360 grepbody(fn, rev, flog.read(fnode))
1361 1361
1362 1362 pfn = copy or fn
1363 1363 if pfn not in matches[parent]:
1364 1364 try:
1365 1365 fnode = pctx.filenode(pfn)
1366 1366 grepbody(pfn, parent, flog.read(fnode))
1367 1367 except error.LookupError:
1368 1368 pass
1369 1369
1370 1370 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1371 1371 rev = ctx.rev()
1372 1372 parent = ctx.parents()[0].rev()
1373 1373 for fn in sorted(revfiles.get(rev, [])):
1374 1374 states = matches[rev][fn]
1375 1375 copy = copies.get(rev, {}).get(fn)
1376 1376 if fn in skip:
1377 1377 if copy:
1378 1378 skip[copy] = True
1379 1379 continue
1380 1380 pstates = matches.get(parent, {}).get(copy or fn, [])
1381 1381 if pstates or states:
1382 1382 r = display(fn, ctx, pstates, states)
1383 1383 found = found or r
1384 1384 if r and not opts.get('all'):
1385 1385 skip[fn] = True
1386 1386 if copy:
1387 1387 skip[copy] = True
1388 1388 del matches[rev]
1389 1389 del revfiles[rev]
1390 1390
1391 1391 def heads(ui, repo, *branchrevs, **opts):
1392 1392 """show current repository heads or show branch heads
1393 1393
1394 1394 With no arguments, show all repository branch heads.
1395 1395
1396 1396 Repository "heads" are changesets with no child changesets. They are
1397 1397 where development generally takes place and are the usual targets
1398 1398 for update and merge operations. Branch heads are changesets that have
1399 1399 no child changeset on the same branch.
1400 1400
1401 1401 If one or more REVs are given, only branch heads on the branches
1402 1402 associated with the specified changesets are shown.
1403 1403
1404 1404 If -c/--closed is specified, also show branch heads marked closed
1405 1405 (see hg commit --close-branch).
1406 1406
1407 1407 If STARTREV is specified, only those heads that are descendants of
1408 1408 STARTREV will be displayed.
1409 1409
1410 1410 If -t/--topo is specified, named branch mechanics will be ignored and only
1411 1411 changesets without children will be shown.
1412 1412 """
1413 1413
1414 1414 if opts.get('rev'):
1415 1415 start = repo.lookup(opts['rev'])
1416 1416 else:
1417 1417 start = None
1418 1418
1419 1419 if opts.get('topo'):
1420 1420 heads = [repo[h] for h in repo.heads(start)]
1421 1421 else:
1422 1422 heads = []
1423 1423 for b, ls in repo.branchmap().iteritems():
1424 1424 if start is None:
1425 1425 heads += [repo[h] for h in ls]
1426 1426 continue
1427 1427 startrev = repo.changelog.rev(start)
1428 1428 descendants = set(repo.changelog.descendants(startrev))
1429 1429 descendants.add(startrev)
1430 1430 rev = repo.changelog.rev
1431 1431 heads += [repo[h] for h in ls if rev(h) in descendants]
1432 1432
1433 1433 if branchrevs:
1434 1434 decode, encode = encoding.fromlocal, encoding.tolocal
1435 1435 branches = set(repo[decode(br)].branch() for br in branchrevs)
1436 1436 heads = [h for h in heads if h.branch() in branches]
1437 1437
1438 1438 if not opts.get('closed'):
1439 1439 heads = [h for h in heads if not h.extra().get('close')]
1440 1440
1441 1441 if opts.get('active') and branchrevs:
1442 1442 dagheads = repo.heads(start)
1443 1443 heads = [h for h in heads if h.node() in dagheads]
1444 1444
1445 1445 if branchrevs:
1446 1446 haveheads = set(h.branch() for h in heads)
1447 1447 if branches - haveheads:
1448 1448 headless = ', '.join(encode(b) for b in branches - haveheads)
1449 1449 msg = _('no open branch heads found on branches %s')
1450 1450 if opts.get('rev'):
1451 1451 msg += _(' (started at %s)' % opts['rev'])
1452 1452 ui.warn((msg + '\n') % headless)
1453 1453
1454 1454 if not heads:
1455 1455 return 1
1456 1456
1457 1457 heads = sorted(heads, key=lambda x: -x.rev())
1458 1458 displayer = cmdutil.show_changeset(ui, repo, opts)
1459 1459 for ctx in heads:
1460 1460 displayer.show(ctx)
1461 1461 displayer.close()
1462 1462
1463 1463 def help_(ui, name=None, with_version=False, unknowncmd=False):
1464 1464 """show help for a given topic or a help overview
1465 1465
1466 1466 With no arguments, print a list of commands with short help messages.
1467 1467
1468 1468 Given a topic, extension, or command name, print help for that
1469 1469 topic."""
1470 1470 option_lists = []
1471 1471 textwidth = util.termwidth() - 2
1472 1472
1473 1473 def addglobalopts(aliases):
1474 1474 if ui.verbose:
1475 1475 option_lists.append((_("global options:"), globalopts))
1476 1476 if name == 'shortlist':
1477 1477 option_lists.append((_('use "hg help" for the full list '
1478 1478 'of commands'), ()))
1479 1479 else:
1480 1480 if name == 'shortlist':
1481 1481 msg = _('use "hg help" for the full list of commands '
1482 1482 'or "hg -v" for details')
1483 1483 elif aliases:
1484 1484 msg = _('use "hg -v help%s" to show aliases and '
1485 1485 'global options') % (name and " " + name or "")
1486 1486 else:
1487 1487 msg = _('use "hg -v help %s" to show global options') % name
1488 1488 option_lists.append((msg, ()))
1489 1489
1490 1490 def helpcmd(name):
1491 1491 if with_version:
1492 1492 version_(ui)
1493 1493 ui.write('\n')
1494 1494
1495 1495 try:
1496 1496 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1497 1497 except error.AmbiguousCommand, inst:
1498 1498 # py3k fix: except vars can't be used outside the scope of the
1499 1499 # except block, nor can be used inside a lambda. python issue4617
1500 1500 prefix = inst.args[0]
1501 1501 select = lambda c: c.lstrip('^').startswith(prefix)
1502 1502 helplist(_('list of commands:\n\n'), select)
1503 1503 return
1504 1504
1505 1505 # check if it's an invalid alias and display its error if it is
1506 1506 if getattr(entry[0], 'badalias', False):
1507 1507 if not unknowncmd:
1508 1508 entry[0](ui)
1509 1509 return
1510 1510
1511 1511 # synopsis
1512 1512 if len(entry) > 2:
1513 1513 if entry[2].startswith('hg'):
1514 1514 ui.write("%s\n" % entry[2])
1515 1515 else:
1516 1516 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
1517 1517 else:
1518 1518 ui.write('hg %s\n' % aliases[0])
1519 1519
1520 1520 # aliases
1521 1521 if not ui.quiet and len(aliases) > 1:
1522 1522 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1523 1523
1524 1524 # description
1525 1525 doc = gettext(entry[0].__doc__)
1526 1526 if not doc:
1527 1527 doc = _("(no help text available)")
1528 1528 if ui.quiet:
1529 1529 doc = doc.splitlines()[0]
1530 1530 ui.write("\n%s\n" % minirst.format(doc, textwidth))
1531 1531
1532 1532 if not ui.quiet:
1533 1533 # options
1534 1534 if entry[1]:
1535 1535 option_lists.append((_("options:\n"), entry[1]))
1536 1536
1537 1537 addglobalopts(False)
1538 1538
1539 1539 def helplist(header, select=None):
1540 1540 h = {}
1541 1541 cmds = {}
1542 1542 for c, e in table.iteritems():
1543 1543 f = c.split("|", 1)[0]
1544 1544 if select and not select(f):
1545 1545 continue
1546 1546 if (not select and name != 'shortlist' and
1547 1547 e[0].__module__ != __name__):
1548 1548 continue
1549 1549 if name == "shortlist" and not f.startswith("^"):
1550 1550 continue
1551 1551 f = f.lstrip("^")
1552 1552 if not ui.debugflag and f.startswith("debug"):
1553 1553 continue
1554 1554 doc = e[0].__doc__
1555 1555 if doc and 'DEPRECATED' in doc and not ui.verbose:
1556 1556 continue
1557 1557 doc = gettext(doc)
1558 1558 if not doc:
1559 1559 doc = _("(no help text available)")
1560 1560 h[f] = doc.splitlines()[0].rstrip()
1561 1561 cmds[f] = c.lstrip("^")
1562 1562
1563 1563 if not h:
1564 1564 ui.status(_('no commands defined\n'))
1565 1565 return
1566 1566
1567 1567 ui.status(header)
1568 1568 fns = sorted(h)
1569 1569 m = max(map(len, fns))
1570 1570 for f in fns:
1571 1571 if ui.verbose:
1572 1572 commands = cmds[f].replace("|",", ")
1573 1573 ui.write(" %s:\n %s\n"%(commands, h[f]))
1574 1574 else:
1575 1575 ui.write(' %-*s %s\n' % (m, f, util.wrap(h[f], m + 4)))
1576 1576
1577 1577 if not ui.quiet:
1578 1578 addglobalopts(True)
1579 1579
1580 1580 def helptopic(name):
1581 1581 for names, header, doc in help.helptable:
1582 1582 if name in names:
1583 1583 break
1584 1584 else:
1585 1585 raise error.UnknownCommand(name)
1586 1586
1587 1587 # description
1588 1588 if not doc:
1589 1589 doc = _("(no help text available)")
1590 1590 if hasattr(doc, '__call__'):
1591 1591 doc = doc()
1592 1592
1593 1593 ui.write("%s\n\n" % header)
1594 1594 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
1595 1595
1596 1596 def helpext(name):
1597 1597 try:
1598 1598 mod = extensions.find(name)
1599 1599 doc = gettext(mod.__doc__) or _('no help text available')
1600 1600 except KeyError:
1601 1601 mod = None
1602 1602 doc = extensions.disabledext(name)
1603 1603 if not doc:
1604 1604 raise error.UnknownCommand(name)
1605 1605
1606 1606 if '\n' not in doc:
1607 1607 head, tail = doc, ""
1608 1608 else:
1609 1609 head, tail = doc.split('\n', 1)
1610 1610 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
1611 1611 if tail:
1612 1612 ui.write(minirst.format(tail, textwidth))
1613 1613 ui.status('\n\n')
1614 1614
1615 1615 if mod:
1616 1616 try:
1617 1617 ct = mod.cmdtable
1618 1618 except AttributeError:
1619 1619 ct = {}
1620 1620 modcmds = set([c.split('|', 1)[0] for c in ct])
1621 1621 helplist(_('list of commands:\n\n'), modcmds.__contains__)
1622 1622 else:
1623 1623 ui.write(_('use "hg help extensions" for information on enabling '
1624 1624 'extensions\n'))
1625 1625
1626 1626 def helpextcmd(name):
1627 1627 cmd, ext, mod = extensions.disabledcmd(name, ui.config('ui', 'strict'))
1628 1628 doc = gettext(mod.__doc__).splitlines()[0]
1629 1629
1630 1630 msg = help.listexts(_("'%s' is provided by the following "
1631 1631 "extension:") % cmd, {ext: doc}, len(ext),
1632 1632 indent=4)
1633 1633 ui.write(minirst.format(msg, textwidth))
1634 1634 ui.write('\n\n')
1635 1635 ui.write(_('use "hg help extensions" for information on enabling '
1636 1636 'extensions\n'))
1637 1637
1638 1638 if name and name != 'shortlist':
1639 1639 i = None
1640 1640 if unknowncmd:
1641 1641 queries = (helpextcmd,)
1642 1642 else:
1643 1643 queries = (helptopic, helpcmd, helpext, helpextcmd)
1644 1644 for f in queries:
1645 1645 try:
1646 1646 f(name)
1647 1647 i = None
1648 1648 break
1649 1649 except error.UnknownCommand, inst:
1650 1650 i = inst
1651 1651 if i:
1652 1652 raise i
1653 1653
1654 1654 else:
1655 1655 # program name
1656 1656 if ui.verbose or with_version:
1657 1657 version_(ui)
1658 1658 else:
1659 1659 ui.status(_("Mercurial Distributed SCM\n"))
1660 1660 ui.status('\n')
1661 1661
1662 1662 # list of commands
1663 1663 if name == "shortlist":
1664 1664 header = _('basic commands:\n\n')
1665 1665 else:
1666 1666 header = _('list of commands:\n\n')
1667 1667
1668 1668 helplist(header)
1669 1669 if name != 'shortlist':
1670 1670 exts, maxlength = extensions.enabled()
1671 1671 text = help.listexts(_('enabled extensions:'), exts, maxlength)
1672 1672 if text:
1673 1673 ui.write("\n%s\n" % minirst.format(text, textwidth))
1674 1674
1675 1675 # list all option lists
1676 1676 opt_output = []
1677 1677 for title, options in option_lists:
1678 1678 opt_output.append(("\n%s" % title, None))
1679 1679 for shortopt, longopt, default, desc in options:
1680 1680 if _("DEPRECATED") in desc and not ui.verbose:
1681 1681 continue
1682 1682 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1683 1683 longopt and " --%s" % longopt),
1684 1684 "%s%s" % (desc,
1685 1685 default
1686 1686 and _(" (default: %s)") % default
1687 1687 or "")))
1688 1688
1689 1689 if not name:
1690 1690 ui.write(_("\nadditional help topics:\n\n"))
1691 1691 topics = []
1692 1692 for names, header, doc in help.helptable:
1693 1693 topics.append((sorted(names, key=len, reverse=True)[0], header))
1694 1694 topics_len = max([len(s[0]) for s in topics])
1695 1695 for t, desc in topics:
1696 1696 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1697 1697
1698 1698 if opt_output:
1699 1699 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1700 1700 for first, second in opt_output:
1701 1701 if second:
1702 1702 second = util.wrap(second, opts_len + 3)
1703 1703 ui.write(" %-*s %s\n" % (opts_len, first, second))
1704 1704 else:
1705 1705 ui.write("%s\n" % first)
1706 1706
1707 1707 def identify(ui, repo, source=None,
1708 1708 rev=None, num=None, id=None, branch=None, tags=None):
1709 1709 """identify the working copy or specified revision
1710 1710
1711 1711 With no revision, print a summary of the current state of the
1712 1712 repository.
1713 1713
1714 1714 Specifying a path to a repository root or Mercurial bundle will
1715 1715 cause lookup to operate on that repository/bundle.
1716 1716
1717 1717 This summary identifies the repository state using one or two
1718 1718 parent hash identifiers, followed by a "+" if there are
1719 1719 uncommitted changes in the working directory, a list of tags for
1720 1720 this revision and a branch name for non-default branches.
1721 1721 """
1722 1722
1723 1723 if not repo and not source:
1724 1724 raise util.Abort(_("There is no Mercurial repository here "
1725 1725 "(.hg not found)"))
1726 1726
1727 1727 hexfunc = ui.debugflag and hex or short
1728 1728 default = not (num or id or branch or tags)
1729 1729 output = []
1730 1730
1731 1731 revs = []
1732 1732 if source:
1733 1733 source, branches = hg.parseurl(ui.expandpath(source))
1734 1734 repo = hg.repository(ui, source)
1735 1735 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
1736 1736
1737 1737 if not repo.local():
1738 1738 if not rev and revs:
1739 1739 rev = revs[0]
1740 1740 if not rev:
1741 1741 rev = "tip"
1742 1742 if num or branch or tags:
1743 1743 raise util.Abort(
1744 1744 "can't query remote revision number, branch, or tags")
1745 1745 output = [hexfunc(repo.lookup(rev))]
1746 1746 elif not rev:
1747 1747 ctx = repo[None]
1748 1748 parents = ctx.parents()
1749 1749 changed = False
1750 1750 if default or id or num:
1751 1751 changed = ctx.files() + ctx.deleted()
1752 1752 if default or id:
1753 1753 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1754 1754 (changed) and "+" or "")]
1755 1755 if num:
1756 1756 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1757 1757 (changed) and "+" or ""))
1758 1758 else:
1759 1759 ctx = repo[rev]
1760 1760 if default or id:
1761 1761 output = [hexfunc(ctx.node())]
1762 1762 if num:
1763 1763 output.append(str(ctx.rev()))
1764 1764
1765 1765 if repo.local() and default and not ui.quiet:
1766 1766 b = encoding.tolocal(ctx.branch())
1767 1767 if b != 'default':
1768 1768 output.append("(%s)" % b)
1769 1769
1770 1770 # multiple tags for a single parent separated by '/'
1771 1771 t = "/".join(ctx.tags())
1772 1772 if t:
1773 1773 output.append(t)
1774 1774
1775 1775 if branch:
1776 1776 output.append(encoding.tolocal(ctx.branch()))
1777 1777
1778 1778 if tags:
1779 1779 output.extend(ctx.tags())
1780 1780
1781 1781 ui.write("%s\n" % ' '.join(output))
1782 1782
1783 1783 def import_(ui, repo, patch1, *patches, **opts):
1784 1784 """import an ordered set of patches
1785 1785
1786 1786 Import a list of patches and commit them individually (unless
1787 1787 --no-commit is specified).
1788 1788
1789 1789 If there are outstanding changes in the working directory, import
1790 1790 will abort unless given the -f/--force flag.
1791 1791
1792 1792 You can import a patch straight from a mail message. Even patches
1793 1793 as attachments work (to use the body part, it must have type
1794 1794 text/plain or text/x-patch). From and Subject headers of email
1795 1795 message are used as default committer and commit message. All
1796 1796 text/plain body parts before first diff are added to commit
1797 1797 message.
1798 1798
1799 1799 If the imported patch was generated by hg export, user and
1800 1800 description from patch override values from message headers and
1801 1801 body. Values given on command line with -m/--message and -u/--user
1802 1802 override these.
1803 1803
1804 1804 If --exact is specified, import will set the working directory to
1805 1805 the parent of each patch before applying it, and will abort if the
1806 1806 resulting changeset has a different ID than the one recorded in
1807 1807 the patch. This may happen due to character set problems or other
1808 1808 deficiencies in the text patch format.
1809 1809
1810 1810 With -s/--similarity, hg will attempt to discover renames and
1811 1811 copies in the patch in the same way as 'addremove'.
1812 1812
1813 1813 To read a patch from standard input, use "-" as the patch name. If
1814 1814 a URL is specified, the patch will be downloaded from it.
1815 1815 See 'hg help dates' for a list of formats valid for -d/--date.
1816 1816 """
1817 1817 patches = (patch1,) + patches
1818 1818
1819 1819 date = opts.get('date')
1820 1820 if date:
1821 1821 opts['date'] = util.parsedate(date)
1822 1822
1823 1823 try:
1824 1824 sim = float(opts.get('similarity') or 0)
1825 1825 except ValueError:
1826 1826 raise util.Abort(_('similarity must be a number'))
1827 1827 if sim < 0 or sim > 100:
1828 1828 raise util.Abort(_('similarity must be between 0 and 100'))
1829 1829
1830 1830 if opts.get('exact') or not opts.get('force'):
1831 1831 cmdutil.bail_if_changed(repo)
1832 1832
1833 1833 d = opts["base"]
1834 1834 strip = opts["strip"]
1835 1835 wlock = lock = None
1836 1836
1837 1837 def tryone(ui, hunk):
1838 1838 tmpname, message, user, date, branch, nodeid, p1, p2 = patch.extract(ui, hunk)
1839 1839
1840 1840 if not tmpname:
1841 1841 return None
1842 1842 commitid = _('to working directory')
1843 1843
1844 1844 try:
1845 1845 cmdline_message = cmdutil.logmessage(opts)
1846 1846 if cmdline_message:
1847 1847 # pickup the cmdline msg
1848 1848 message = cmdline_message
1849 1849 elif message:
1850 1850 # pickup the patch msg
1851 1851 message = message.strip()
1852 1852 else:
1853 1853 # launch the editor
1854 1854 message = None
1855 1855 ui.debug('message:\n%s\n' % message)
1856 1856
1857 1857 wp = repo.parents()
1858 1858 if opts.get('exact'):
1859 1859 if not nodeid or not p1:
1860 1860 raise util.Abort(_('not a Mercurial patch'))
1861 1861 p1 = repo.lookup(p1)
1862 1862 p2 = repo.lookup(p2 or hex(nullid))
1863 1863
1864 1864 if p1 != wp[0].node():
1865 1865 hg.clean(repo, p1)
1866 1866 repo.dirstate.setparents(p1, p2)
1867 1867 elif p2:
1868 1868 try:
1869 1869 p1 = repo.lookup(p1)
1870 1870 p2 = repo.lookup(p2)
1871 1871 if p1 == wp[0].node():
1872 1872 repo.dirstate.setparents(p1, p2)
1873 1873 except error.RepoError:
1874 1874 pass
1875 1875 if opts.get('exact') or opts.get('import_branch'):
1876 1876 repo.dirstate.setbranch(branch or 'default')
1877 1877
1878 1878 files = {}
1879 1879 try:
1880 1880 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1881 1881 files=files, eolmode=None)
1882 1882 finally:
1883 1883 files = patch.updatedir(ui, repo, files,
1884 1884 similarity=sim / 100.0)
1885 1885 if not opts.get('no_commit'):
1886 1886 if opts.get('exact'):
1887 1887 m = None
1888 1888 else:
1889 1889 m = cmdutil.matchfiles(repo, files or [])
1890 1890 n = repo.commit(message, opts.get('user') or user,
1891 1891 opts.get('date') or date, match=m,
1892 1892 editor=cmdutil.commiteditor)
1893 1893 if opts.get('exact'):
1894 1894 if hex(n) != nodeid:
1895 1895 repo.rollback()
1896 1896 raise util.Abort(_('patch is damaged'
1897 1897 ' or loses information'))
1898 1898 # Force a dirstate write so that the next transaction
1899 1899 # backups an up-do-date file.
1900 1900 repo.dirstate.write()
1901 1901 if n:
1902 1902 commitid = short(n)
1903 1903
1904 1904 return commitid
1905 1905 finally:
1906 1906 os.unlink(tmpname)
1907 1907
1908 1908 try:
1909 1909 wlock = repo.wlock()
1910 1910 lock = repo.lock()
1911 1911 lastcommit = None
1912 1912 for p in patches:
1913 1913 pf = os.path.join(d, p)
1914 1914
1915 1915 if pf == '-':
1916 1916 ui.status(_("applying patch from stdin\n"))
1917 1917 pf = sys.stdin
1918 1918 else:
1919 1919 ui.status(_("applying %s\n") % p)
1920 1920 pf = url.open(ui, pf)
1921 1921
1922 1922 haspatch = False
1923 1923 for hunk in patch.split(pf):
1924 1924 commitid = tryone(ui, hunk)
1925 1925 if commitid:
1926 1926 haspatch = True
1927 1927 if lastcommit:
1928 1928 ui.status(_('applied %s\n') % lastcommit)
1929 1929 lastcommit = commitid
1930 1930
1931 1931 if not haspatch:
1932 1932 raise util.Abort(_('no diffs found'))
1933 1933
1934 1934 finally:
1935 1935 release(lock, wlock)
1936 1936
1937 1937 def incoming(ui, repo, source="default", **opts):
1938 1938 """show new changesets found in source
1939 1939
1940 1940 Show new changesets found in the specified path/URL or the default
1941 1941 pull location. These are the changesets that would have been pulled
1942 1942 if a pull at the time you issued this command.
1943 1943
1944 1944 For remote repository, using --bundle avoids downloading the
1945 1945 changesets twice if the incoming is followed by a pull.
1946 1946
1947 1947 See pull for valid source format details.
1948 1948 """
1949 1949 limit = cmdutil.loglimit(opts)
1950 1950 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
1951 1951 other = hg.repository(cmdutil.remoteui(repo, opts), source)
1952 1952 ui.status(_('comparing with %s\n') % url.hidepassword(source))
1953 1953 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
1954 1954 if revs:
1955 1955 revs = [other.lookup(rev) for rev in revs]
1956 1956 common, incoming, rheads = repo.findcommonincoming(other, heads=revs,
1957 1957 force=opts["force"])
1958 1958 if not incoming:
1959 1959 try:
1960 1960 os.unlink(opts["bundle"])
1961 1961 except:
1962 1962 pass
1963 1963 ui.status(_("no changes found\n"))
1964 1964 return 1
1965 1965
1966 1966 cleanup = None
1967 1967 try:
1968 1968 fname = opts["bundle"]
1969 1969 if fname or not other.local():
1970 1970 # create a bundle (uncompressed if other repo is not local)
1971 1971
1972 1972 if revs is None and other.capable('changegroupsubset'):
1973 1973 revs = rheads
1974 1974
1975 1975 if revs is None:
1976 1976 cg = other.changegroup(incoming, "incoming")
1977 1977 else:
1978 1978 cg = other.changegroupsubset(incoming, revs, 'incoming')
1979 1979 bundletype = other.local() and "HG10BZ" or "HG10UN"
1980 1980 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1981 1981 # keep written bundle?
1982 1982 if opts["bundle"]:
1983 1983 cleanup = None
1984 1984 if not other.local():
1985 1985 # use the created uncompressed bundlerepo
1986 1986 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1987 1987
1988 1988 o = other.changelog.nodesbetween(incoming, revs)[0]
1989 1989 if opts.get('newest_first'):
1990 1990 o.reverse()
1991 1991 displayer = cmdutil.show_changeset(ui, other, opts)
1992 1992 count = 0
1993 1993 for n in o:
1994 1994 if limit is not None and count >= limit:
1995 1995 break
1996 1996 parents = [p for p in other.changelog.parents(n) if p != nullid]
1997 1997 if opts.get('no_merges') and len(parents) == 2:
1998 1998 continue
1999 1999 count += 1
2000 2000 displayer.show(other[n])
2001 2001 displayer.close()
2002 2002 finally:
2003 2003 if hasattr(other, 'close'):
2004 2004 other.close()
2005 2005 if cleanup:
2006 2006 os.unlink(cleanup)
2007 2007
2008 2008 def init(ui, dest=".", **opts):
2009 2009 """create a new repository in the given directory
2010 2010
2011 2011 Initialize a new repository in the given directory. If the given
2012 2012 directory does not exist, it will be created.
2013 2013
2014 2014 If no directory is given, the current directory is used.
2015 2015
2016 2016 It is possible to specify an ``ssh://`` URL as the destination.
2017 2017 See 'hg help urls' for more information.
2018 2018 """
2019 2019 hg.repository(cmdutil.remoteui(ui, opts), dest, create=1)
2020 2020
2021 2021 def locate(ui, repo, *pats, **opts):
2022 2022 """locate files matching specific patterns
2023 2023
2024 2024 Print files under Mercurial control in the working directory whose
2025 2025 names match the given patterns.
2026 2026
2027 2027 By default, this command searches all directories in the working
2028 2028 directory. To search just the current directory and its
2029 2029 subdirectories, use "--include .".
2030 2030
2031 2031 If no patterns are given to match, this command prints the names
2032 2032 of all files under Mercurial control in the working directory.
2033 2033
2034 2034 If you want to feed the output of this command into the "xargs"
2035 2035 command, use the -0 option to both this command and "xargs". This
2036 2036 will avoid the problem of "xargs" treating single filenames that
2037 2037 contain whitespace as multiple filenames.
2038 2038 """
2039 2039 end = opts.get('print0') and '\0' or '\n'
2040 2040 rev = opts.get('rev') or None
2041 2041
2042 2042 ret = 1
2043 2043 m = cmdutil.match(repo, pats, opts, default='relglob')
2044 2044 m.bad = lambda x, y: False
2045 2045 for abs in repo[rev].walk(m):
2046 2046 if not rev and abs not in repo.dirstate:
2047 2047 continue
2048 2048 if opts.get('fullpath'):
2049 2049 ui.write(repo.wjoin(abs), end)
2050 2050 else:
2051 2051 ui.write(((pats and m.rel(abs)) or abs), end)
2052 2052 ret = 0
2053 2053
2054 2054 return ret
2055 2055
2056 2056 def log(ui, repo, *pats, **opts):
2057 2057 """show revision history of entire repository or files
2058 2058
2059 2059 Print the revision history of the specified files or the entire
2060 2060 project.
2061 2061
2062 2062 File history is shown without following rename or copy history of
2063 2063 files. Use -f/--follow with a filename to follow history across
2064 2064 renames and copies. --follow without a filename will only show
2065 2065 ancestors or descendants of the starting revision. --follow-first
2066 2066 only follows the first parent of merge revisions.
2067 2067
2068 2068 If no revision range is specified, the default is tip:0 unless
2069 2069 --follow is set, in which case the working directory parent is
2070 2070 used as the starting revision.
2071 2071
2072 2072 See 'hg help dates' for a list of formats valid for -d/--date.
2073 2073
2074 2074 By default this command prints revision number and changeset id,
2075 2075 tags, non-trivial parents, user, date and time, and a summary for
2076 2076 each commit. When the -v/--verbose switch is used, the list of
2077 2077 changed files and full commit message are shown.
2078 2078
2079 2079 NOTE: log -p/--patch may generate unexpected diff output for merge
2080 2080 changesets, as it will only compare the merge changeset against
2081 2081 its first parent. Also, only files different from BOTH parents
2082 2082 will appear in files:.
2083 2083 """
2084 2084
2085 2085 matchfn = cmdutil.match(repo, pats, opts)
2086 2086 limit = cmdutil.loglimit(opts)
2087 2087 count = 0
2088 2088
2089 2089 endrev = None
2090 2090 if opts.get('copies') and opts.get('rev'):
2091 2091 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2092 2092
2093 2093 df = False
2094 2094 if opts["date"]:
2095 2095 df = util.matchdate(opts["date"])
2096 2096
2097 2097 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
2098 2098 def prep(ctx, fns):
2099 2099 rev = ctx.rev()
2100 2100 parents = [p for p in repo.changelog.parentrevs(rev)
2101 2101 if p != nullrev]
2102 2102 if opts.get('no_merges') and len(parents) == 2:
2103 2103 return
2104 2104 if opts.get('only_merges') and len(parents) != 2:
2105 2105 return
2106 2106 if opts.get('only_branch') and ctx.branch() not in opts['only_branch']:
2107 2107 return
2108 2108 if df and not df(ctx.date()[0]):
2109 2109 return
2110 2110 if opts['user'] and not [k for k in opts['user'] if k in ctx.user()]:
2111 2111 return
2112 2112 if opts.get('keyword'):
2113 2113 for k in [kw.lower() for kw in opts['keyword']]:
2114 2114 if (k in ctx.user().lower() or
2115 2115 k in ctx.description().lower() or
2116 2116 k in " ".join(ctx.files()).lower()):
2117 2117 break
2118 2118 else:
2119 2119 return
2120 2120
2121 2121 copies = None
2122 2122 if opts.get('copies') and rev:
2123 2123 copies = []
2124 2124 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2125 2125 for fn in ctx.files():
2126 2126 rename = getrenamed(fn, rev)
2127 2127 if rename:
2128 2128 copies.append((fn, rename[0]))
2129 2129
2130 2130 displayer.show(ctx, copies=copies)
2131 2131
2132 2132 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2133 2133 if count == limit:
2134 2134 break
2135 2135 if displayer.flush(ctx.rev()):
2136 2136 count += 1
2137 2137 displayer.close()
2138 2138
2139 2139 def manifest(ui, repo, node=None, rev=None):
2140 2140 """output the current or given revision of the project manifest
2141 2141
2142 2142 Print a list of version controlled files for the given revision.
2143 2143 If no revision is given, the first parent of the working directory
2144 2144 is used, or the null revision if no revision is checked out.
2145 2145
2146 2146 With -v, print file permissions, symlink and executable bits.
2147 2147 With --debug, print file revision hashes.
2148 2148 """
2149 2149
2150 2150 if rev and node:
2151 2151 raise util.Abort(_("please specify just one revision"))
2152 2152
2153 2153 if not node:
2154 2154 node = rev
2155 2155
2156 2156 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2157 2157 ctx = repo[node]
2158 2158 for f in ctx:
2159 2159 if ui.debugflag:
2160 2160 ui.write("%40s " % hex(ctx.manifest()[f]))
2161 2161 if ui.verbose:
2162 2162 ui.write(decor[ctx.flags(f)])
2163 2163 ui.write("%s\n" % f)
2164 2164
2165 2165 def merge(ui, repo, node=None, **opts):
2166 2166 """merge working directory with another revision
2167 2167
2168 2168 The current working directory is updated with all changes made in
2169 2169 the requested revision since the last common predecessor revision.
2170 2170
2171 2171 Files that changed between either parent are marked as changed for
2172 2172 the next commit and a commit must be performed before any further
2173 2173 updates to the repository are allowed. The next commit will have
2174 2174 two parents.
2175 2175
2176 2176 If no revision is specified, the working directory's parent is a
2177 2177 head revision, and the current branch contains exactly one other
2178 2178 head, the other head is merged with by default. Otherwise, an
2179 2179 explicit revision with which to merge with must be provided.
2180 2180 """
2181 2181
2182 2182 if opts.get('rev') and node:
2183 2183 raise util.Abort(_("please specify just one revision"))
2184 2184 if not node:
2185 2185 node = opts.get('rev')
2186 2186
2187 2187 if not node:
2188 2188 branch = repo.changectx(None).branch()
2189 2189 bheads = repo.branchheads(branch)
2190 2190 if len(bheads) > 2:
2191 2191 ui.warn(_("abort: branch '%s' has %d heads - "
2192 2192 "please merge with an explicit rev\n")
2193 2193 % (branch, len(bheads)))
2194 2194 ui.status(_("(run 'hg heads .' to see heads)\n"))
2195 2195 return False
2196 2196
2197 2197 parent = repo.dirstate.parents()[0]
2198 2198 if len(bheads) == 1:
2199 2199 if len(repo.heads()) > 1:
2200 2200 ui.warn(_("abort: branch '%s' has one head - "
2201 2201 "please merge with an explicit rev\n" % branch))
2202 2202 ui.status(_("(run 'hg heads' to see all heads)\n"))
2203 2203 return False
2204 2204 msg = _('there is nothing to merge')
2205 2205 if parent != repo.lookup(repo[None].branch()):
2206 2206 msg = _('%s - use "hg update" instead') % msg
2207 2207 raise util.Abort(msg)
2208 2208
2209 2209 if parent not in bheads:
2210 2210 raise util.Abort(_('working dir not at a head rev - '
2211 2211 'use "hg update" or merge with an explicit rev'))
2212 2212 node = parent == bheads[0] and bheads[-1] or bheads[0]
2213 2213
2214 2214 if opts.get('preview'):
2215 2215 p1 = repo['.']
2216 2216 p2 = repo[node]
2217 2217 common = p1.ancestor(p2)
2218 2218 roots, heads = [common.node()], [p2.node()]
2219 2219 displayer = cmdutil.show_changeset(ui, repo, opts)
2220 2220 for node in repo.changelog.nodesbetween(roots=roots, heads=heads)[0]:
2221 2221 if node not in roots:
2222 2222 displayer.show(repo[node])
2223 2223 displayer.close()
2224 2224 return 0
2225 2225
2226 2226 return hg.merge(repo, node, force=opts.get('force'))
2227 2227
2228 2228 def outgoing(ui, repo, dest=None, **opts):
2229 2229 """show changesets not found in the destination
2230 2230
2231 2231 Show changesets not found in the specified destination repository
2232 2232 or the default push location. These are the changesets that would
2233 2233 be pushed if a push was requested.
2234 2234
2235 2235 See pull for details of valid destination formats.
2236 2236 """
2237 2237 limit = cmdutil.loglimit(opts)
2238 2238 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2239 2239 dest, branches = hg.parseurl(dest, opts.get('branch'))
2240 2240 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2241 2241 if revs:
2242 2242 revs = [repo.lookup(rev) for rev in revs]
2243 2243
2244 2244 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2245 2245 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2246 2246 o = repo.findoutgoing(other, force=opts.get('force'))
2247 2247 if not o:
2248 2248 ui.status(_("no changes found\n"))
2249 2249 return 1
2250 2250 o = repo.changelog.nodesbetween(o, revs)[0]
2251 2251 if opts.get('newest_first'):
2252 2252 o.reverse()
2253 2253 displayer = cmdutil.show_changeset(ui, repo, opts)
2254 2254 count = 0
2255 2255 for n in o:
2256 2256 if limit is not None and count >= limit:
2257 2257 break
2258 2258 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2259 2259 if opts.get('no_merges') and len(parents) == 2:
2260 2260 continue
2261 2261 count += 1
2262 2262 displayer.show(repo[n])
2263 2263 displayer.close()
2264 2264
2265 2265 def parents(ui, repo, file_=None, **opts):
2266 2266 """show the parents of the working directory or revision
2267 2267
2268 2268 Print the working directory's parent revisions. If a revision is
2269 2269 given via -r/--rev, the parent of that revision will be printed.
2270 2270 If a file argument is given, the revision in which the file was
2271 2271 last changed (before the working directory revision or the
2272 2272 argument to --rev if given) is printed.
2273 2273 """
2274 2274 rev = opts.get('rev')
2275 2275 if rev:
2276 2276 ctx = repo[rev]
2277 2277 else:
2278 2278 ctx = repo[None]
2279 2279
2280 2280 if file_:
2281 2281 m = cmdutil.match(repo, (file_,), opts)
2282 2282 if m.anypats() or len(m.files()) != 1:
2283 2283 raise util.Abort(_('can only specify an explicit filename'))
2284 2284 file_ = m.files()[0]
2285 2285 filenodes = []
2286 2286 for cp in ctx.parents():
2287 2287 if not cp:
2288 2288 continue
2289 2289 try:
2290 2290 filenodes.append(cp.filenode(file_))
2291 2291 except error.LookupError:
2292 2292 pass
2293 2293 if not filenodes:
2294 2294 raise util.Abort(_("'%s' not found in manifest!") % file_)
2295 2295 fl = repo.file(file_)
2296 2296 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2297 2297 else:
2298 2298 p = [cp.node() for cp in ctx.parents()]
2299 2299
2300 2300 displayer = cmdutil.show_changeset(ui, repo, opts)
2301 2301 for n in p:
2302 2302 if n != nullid:
2303 2303 displayer.show(repo[n])
2304 2304 displayer.close()
2305 2305
2306 2306 def paths(ui, repo, search=None):
2307 2307 """show aliases for remote repositories
2308 2308
2309 2309 Show definition of symbolic path name NAME. If no name is given,
2310 2310 show definition of all available names.
2311 2311
2312 2312 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2313 2313 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2314 2314
2315 2315 See 'hg help urls' for more information.
2316 2316 """
2317 2317 if search:
2318 2318 for name, path in ui.configitems("paths"):
2319 2319 if name == search:
2320 2320 ui.write("%s\n" % url.hidepassword(path))
2321 2321 return
2322 2322 ui.warn(_("not found!\n"))
2323 2323 return 1
2324 2324 else:
2325 2325 for name, path in ui.configitems("paths"):
2326 2326 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2327 2327
2328 2328 def postincoming(ui, repo, modheads, optupdate, checkout):
2329 2329 if modheads == 0:
2330 2330 return
2331 2331 if optupdate:
2332 2332 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2333 2333 return hg.update(repo, checkout)
2334 2334 else:
2335 2335 ui.status(_("not updating, since new heads added\n"))
2336 2336 if modheads > 1:
2337 2337 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2338 2338 else:
2339 2339 ui.status(_("(run 'hg update' to get a working copy)\n"))
2340 2340
2341 2341 def pull(ui, repo, source="default", **opts):
2342 2342 """pull changes from the specified source
2343 2343
2344 2344 Pull changes from a remote repository to a local one.
2345 2345
2346 2346 This finds all changes from the repository at the specified path
2347 2347 or URL and adds them to a local repository (the current one unless
2348 2348 -R is specified). By default, this does not update the copy of the
2349 2349 project in the working directory.
2350 2350
2351 2351 Use hg incoming if you want to see what would have been added by a
2352 2352 pull at the time you issued this command. If you then decide to
2353 2353 added those changes to the repository, you should use pull -r X
2354 2354 where X is the last changeset listed by hg incoming.
2355 2355
2356 2356 If SOURCE is omitted, the 'default' path will be used.
2357 2357 See 'hg help urls' for more information.
2358 2358 """
2359 2359 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2360 2360 other = hg.repository(cmdutil.remoteui(repo, opts), source)
2361 2361 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2362 2362 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2363 2363 if revs:
2364 2364 try:
2365 2365 revs = [other.lookup(rev) for rev in revs]
2366 2366 except error.CapabilityError:
2367 2367 err = _("Other repository doesn't support revision lookup, "
2368 2368 "so a rev cannot be specified.")
2369 2369 raise util.Abort(err)
2370 2370
2371 2371 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2372 2372 if checkout:
2373 2373 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2374 2374 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2375 2375
2376 2376 def push(ui, repo, dest=None, **opts):
2377 2377 """push changes to the specified destination
2378 2378
2379 2379 Push changes from the local repository to the specified destination.
2380 2380
2381 2381 This is the symmetrical operation for pull. It moves changes from
2382 2382 the current repository to a different one. If the destination is
2383 2383 local this is identical to a pull in that directory from the
2384 2384 current one.
2385 2385
2386 2386 By default, push will refuse to run if it detects the result would
2387 2387 increase the number of remote heads. This generally indicates the
2388 2388 user forgot to pull and merge before pushing.
2389 2389
2390 2390 If -r/--rev is used, the named revision and all its ancestors will
2391 2391 be pushed to the remote repository.
2392 2392
2393 2393 Please see 'hg help urls' for important details about ``ssh://``
2394 2394 URLs. If DESTINATION is omitted, a default path will be used.
2395 2395 """
2396 2396 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2397 2397 dest, branches = hg.parseurl(dest, opts.get('branch'))
2398 2398 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2399 2399 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2400 2400 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2401 2401 if revs:
2402 2402 revs = [repo.lookup(rev) for rev in revs]
2403 2403
2404 2404 # push subrepos depth-first for coherent ordering
2405 2405 c = repo['']
2406 2406 subs = c.substate # only repos that are committed
2407 2407 for s in sorted(subs):
2408 2408 c.sub(s).push(opts.get('force'))
2409 2409
2410 2410 r = repo.push(other, opts.get('force'), revs=revs)
2411 2411 return r == 0
2412 2412
2413 2413 def recover(ui, repo):
2414 2414 """roll back an interrupted transaction
2415 2415
2416 2416 Recover from an interrupted commit or pull.
2417 2417
2418 2418 This command tries to fix the repository status after an
2419 2419 interrupted operation. It should only be necessary when Mercurial
2420 2420 suggests it.
2421 2421 """
2422 2422 if repo.recover():
2423 2423 return hg.verify(repo)
2424 2424 return 1
2425 2425
2426 2426 def remove(ui, repo, *pats, **opts):
2427 2427 """remove the specified files on the next commit
2428 2428
2429 2429 Schedule the indicated files for removal from the repository.
2430 2430
2431 2431 This only removes files from the current branch, not from the
2432 2432 entire project history. -A/--after can be used to remove only
2433 2433 files that have already been deleted, -f/--force can be used to
2434 2434 force deletion, and -Af can be used to remove files from the next
2435 2435 revision without deleting them from the working directory.
2436 2436
2437 2437 The following table details the behavior of remove for different
2438 2438 file states (columns) and option combinations (rows). The file
2439 2439 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
2440 2440 reported by hg status). The actions are Warn, Remove (from branch)
2441 2441 and Delete (from disk)::
2442 2442
2443 2443 A C M !
2444 2444 none W RD W R
2445 2445 -f R RD RD R
2446 2446 -A W W W R
2447 2447 -Af R R R R
2448 2448
2449 2449 This command schedules the files to be removed at the next commit.
2450 2450 To undo a remove before that, see hg revert.
2451 2451 """
2452 2452
2453 2453 after, force = opts.get('after'), opts.get('force')
2454 2454 if not pats and not after:
2455 2455 raise util.Abort(_('no files specified'))
2456 2456
2457 2457 m = cmdutil.match(repo, pats, opts)
2458 2458 s = repo.status(match=m, clean=True)
2459 2459 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2460 2460
2461 2461 for f in m.files():
2462 2462 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2463 2463 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2464 2464
2465 2465 def warn(files, reason):
2466 2466 for f in files:
2467 2467 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
2468 2468 % (m.rel(f), reason))
2469 2469
2470 2470 if force:
2471 2471 remove, forget = modified + deleted + clean, added
2472 2472 elif after:
2473 2473 remove, forget = deleted, []
2474 2474 warn(modified + added + clean, _('still exists'))
2475 2475 else:
2476 2476 remove, forget = deleted + clean, []
2477 2477 warn(modified, _('is modified'))
2478 2478 warn(added, _('has been marked for add'))
2479 2479
2480 2480 for f in sorted(remove + forget):
2481 2481 if ui.verbose or not m.exact(f):
2482 2482 ui.status(_('removing %s\n') % m.rel(f))
2483 2483
2484 2484 repo.forget(forget)
2485 2485 repo.remove(remove, unlink=not after)
2486 2486
2487 2487 def rename(ui, repo, *pats, **opts):
2488 2488 """rename files; equivalent of copy + remove
2489 2489
2490 2490 Mark dest as copies of sources; mark sources for deletion. If dest
2491 2491 is a directory, copies are put in that directory. If dest is a
2492 2492 file, there can only be one source.
2493 2493
2494 2494 By default, this command copies the contents of files as they
2495 2495 exist in the working directory. If invoked with -A/--after, the
2496 2496 operation is recorded, but no copying is performed.
2497 2497
2498 2498 This command takes effect at the next commit. To undo a rename
2499 2499 before that, see hg revert.
2500 2500 """
2501 2501 wlock = repo.wlock(False)
2502 2502 try:
2503 2503 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2504 2504 finally:
2505 2505 wlock.release()
2506 2506
2507 2507 def resolve(ui, repo, *pats, **opts):
2508 2508 """retry file merges from a merge or update
2509 2509
2510 2510 This command can cleanly retry unresolved file merges using file
2511 2511 revisions preserved from the last update or merge.
2512 2512
2513 2513 If a conflict is resolved manually, please note that the changes
2514 2514 will be overwritten if the merge is retried with resolve. The
2515 2515 -m/--mark switch should be used to mark the file as resolved.
2516 2516
2517 2517 You can specify a set of files to operate on, or use the -a/--all
2518 2518 switch to select all unresolved files.
2519 2519
2520 2520 This command also allows listing resolved files and manually
2521 2521 indicating whether or not files are resolved. All files must be
2522 2522 marked as resolved before a commit is permitted.
2523 2523
2524 2524 The codes used to show the status of files are::
2525 2525
2526 2526 U = unresolved
2527 2527 R = resolved
2528 2528 """
2529 2529
2530 2530 all, mark, unmark, show, nostatus = \
2531 2531 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
2532 2532
2533 2533 if (show and (mark or unmark)) or (mark and unmark):
2534 2534 raise util.Abort(_("too many options specified"))
2535 2535 if pats and all:
2536 2536 raise util.Abort(_("can't specify --all and patterns"))
2537 2537 if not (all or pats or show or mark or unmark):
2538 2538 raise util.Abort(_('no files or directories specified; '
2539 2539 'use --all to remerge all files'))
2540 2540
2541 2541 ms = merge_.mergestate(repo)
2542 2542 m = cmdutil.match(repo, pats, opts)
2543 2543
2544 2544 for f in ms:
2545 2545 if m(f):
2546 2546 if show:
2547 2547 if nostatus:
2548 2548 ui.write("%s\n" % f)
2549 2549 else:
2550 2550 ui.write("%s %s\n" % (ms[f].upper(), f))
2551 2551 elif mark:
2552 2552 ms.mark(f, "r")
2553 2553 elif unmark:
2554 2554 ms.mark(f, "u")
2555 2555 else:
2556 2556 wctx = repo[None]
2557 2557 mctx = wctx.parents()[-1]
2558 2558
2559 2559 # backup pre-resolve (merge uses .orig for its own purposes)
2560 2560 a = repo.wjoin(f)
2561 2561 util.copyfile(a, a + ".resolve")
2562 2562
2563 2563 # resolve file
2564 2564 ms.resolve(f, wctx, mctx)
2565 2565
2566 2566 # replace filemerge's .orig file with our resolve file
2567 2567 util.rename(a + ".resolve", a + ".orig")
2568 2568
2569 2569 def revert(ui, repo, *pats, **opts):
2570 2570 """restore individual files or directories to an earlier state
2571 2571
2572 2572 (Use update -r to check out earlier revisions, revert does not
2573 2573 change the working directory parents.)
2574 2574
2575 2575 With no revision specified, revert the named files or directories
2576 2576 to the contents they had in the parent of the working directory.
2577 2577 This restores the contents of the affected files to an unmodified
2578 2578 state and unschedules adds, removes, copies, and renames. If the
2579 2579 working directory has two parents, you must explicitly specify a
2580 2580 revision.
2581 2581
2582 2582 Using the -r/--rev option, revert the given files or directories
2583 2583 to their contents as of a specific revision. This can be helpful
2584 2584 to "roll back" some or all of an earlier change. See 'hg help
2585 2585 dates' for a list of formats valid for -d/--date.
2586 2586
2587 2587 Revert modifies the working directory. It does not commit any
2588 2588 changes, or change the parent of the working directory. If you
2589 2589 revert to a revision other than the parent of the working
2590 2590 directory, the reverted files will thus appear modified
2591 2591 afterwards.
2592 2592
2593 2593 If a file has been deleted, it is restored. If the executable mode
2594 2594 of a file was changed, it is reset.
2595 2595
2596 2596 If names are given, all files matching the names are reverted.
2597 2597 If no arguments are given, no files are reverted.
2598 2598
2599 2599 Modified files are saved with a .orig suffix before reverting.
2600 2600 To disable these backups, use --no-backup.
2601 2601 """
2602 2602
2603 2603 if opts["date"]:
2604 2604 if opts["rev"]:
2605 2605 raise util.Abort(_("you can't specify a revision and a date"))
2606 2606 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2607 2607
2608 2608 if not pats and not opts.get('all'):
2609 2609 raise util.Abort(_('no files or directories specified; '
2610 2610 'use --all to revert the whole repo'))
2611 2611
2612 2612 parent, p2 = repo.dirstate.parents()
2613 2613 if not opts.get('rev') and p2 != nullid:
2614 2614 raise util.Abort(_('uncommitted merge - please provide a '
2615 2615 'specific revision'))
2616 2616 ctx = repo[opts.get('rev')]
2617 2617 node = ctx.node()
2618 2618 mf = ctx.manifest()
2619 2619 if node == parent:
2620 2620 pmf = mf
2621 2621 else:
2622 2622 pmf = None
2623 2623
2624 2624 # need all matching names in dirstate and manifest of target rev,
2625 2625 # so have to walk both. do not print errors if files exist in one
2626 2626 # but not other.
2627 2627
2628 2628 names = {}
2629 2629
2630 2630 wlock = repo.wlock()
2631 2631 try:
2632 2632 # walk dirstate.
2633 2633
2634 2634 m = cmdutil.match(repo, pats, opts)
2635 2635 m.bad = lambda x, y: False
2636 2636 for abs in repo.walk(m):
2637 2637 names[abs] = m.rel(abs), m.exact(abs)
2638 2638
2639 2639 # walk target manifest.
2640 2640
2641 2641 def badfn(path, msg):
2642 2642 if path in names:
2643 2643 return
2644 2644 path_ = path + '/'
2645 2645 for f in names:
2646 2646 if f.startswith(path_):
2647 2647 return
2648 2648 ui.warn("%s: %s\n" % (m.rel(path), msg))
2649 2649
2650 2650 m = cmdutil.match(repo, pats, opts)
2651 2651 m.bad = badfn
2652 2652 for abs in repo[node].walk(m):
2653 2653 if abs not in names:
2654 2654 names[abs] = m.rel(abs), m.exact(abs)
2655 2655
2656 2656 m = cmdutil.matchfiles(repo, names)
2657 2657 changes = repo.status(match=m)[:4]
2658 2658 modified, added, removed, deleted = map(set, changes)
2659 2659
2660 2660 # if f is a rename, also revert the source
2661 2661 cwd = repo.getcwd()
2662 2662 for f in added:
2663 2663 src = repo.dirstate.copied(f)
2664 2664 if src and src not in names and repo.dirstate[src] == 'r':
2665 2665 removed.add(src)
2666 2666 names[src] = (repo.pathto(src, cwd), True)
2667 2667
2668 2668 def removeforget(abs):
2669 2669 if repo.dirstate[abs] == 'a':
2670 2670 return _('forgetting %s\n')
2671 2671 return _('removing %s\n')
2672 2672
2673 2673 revert = ([], _('reverting %s\n'))
2674 2674 add = ([], _('adding %s\n'))
2675 2675 remove = ([], removeforget)
2676 2676 undelete = ([], _('undeleting %s\n'))
2677 2677
2678 2678 disptable = (
2679 2679 # dispatch table:
2680 2680 # file state
2681 2681 # action if in target manifest
2682 2682 # action if not in target manifest
2683 2683 # make backup if in target manifest
2684 2684 # make backup if not in target manifest
2685 2685 (modified, revert, remove, True, True),
2686 2686 (added, revert, remove, True, False),
2687 2687 (removed, undelete, None, False, False),
2688 2688 (deleted, revert, remove, False, False),
2689 2689 )
2690 2690
2691 2691 for abs, (rel, exact) in sorted(names.items()):
2692 2692 mfentry = mf.get(abs)
2693 2693 target = repo.wjoin(abs)
2694 2694 def handle(xlist, dobackup):
2695 2695 xlist[0].append(abs)
2696 2696 if dobackup and not opts.get('no_backup') and util.lexists(target):
2697 2697 bakname = "%s.orig" % rel
2698 2698 ui.note(_('saving current version of %s as %s\n') %
2699 2699 (rel, bakname))
2700 2700 if not opts.get('dry_run'):
2701 2701 util.copyfile(target, bakname)
2702 2702 if ui.verbose or not exact:
2703 2703 msg = xlist[1]
2704 2704 if not isinstance(msg, basestring):
2705 2705 msg = msg(abs)
2706 2706 ui.status(msg % rel)
2707 2707 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2708 2708 if abs not in table:
2709 2709 continue
2710 2710 # file has changed in dirstate
2711 2711 if mfentry:
2712 2712 handle(hitlist, backuphit)
2713 2713 elif misslist is not None:
2714 2714 handle(misslist, backupmiss)
2715 2715 break
2716 2716 else:
2717 2717 if abs not in repo.dirstate:
2718 2718 if mfentry:
2719 2719 handle(add, True)
2720 2720 elif exact:
2721 2721 ui.warn(_('file not managed: %s\n') % rel)
2722 2722 continue
2723 2723 # file has not changed in dirstate
2724 2724 if node == parent:
2725 2725 if exact:
2726 2726 ui.warn(_('no changes needed to %s\n') % rel)
2727 2727 continue
2728 2728 if pmf is None:
2729 2729 # only need parent manifest in this unlikely case,
2730 2730 # so do not read by default
2731 2731 pmf = repo[parent].manifest()
2732 2732 if abs in pmf:
2733 2733 if mfentry:
2734 2734 # if version of file is same in parent and target
2735 2735 # manifests, do nothing
2736 2736 if (pmf[abs] != mfentry or
2737 2737 pmf.flags(abs) != mf.flags(abs)):
2738 2738 handle(revert, False)
2739 2739 else:
2740 2740 handle(remove, False)
2741 2741
2742 2742 if not opts.get('dry_run'):
2743 2743 def checkout(f):
2744 2744 fc = ctx[f]
2745 2745 repo.wwrite(f, fc.data(), fc.flags())
2746 2746
2747 2747 audit_path = util.path_auditor(repo.root)
2748 2748 for f in remove[0]:
2749 2749 if repo.dirstate[f] == 'a':
2750 2750 repo.dirstate.forget(f)
2751 2751 continue
2752 2752 audit_path(f)
2753 2753 try:
2754 2754 util.unlink(repo.wjoin(f))
2755 2755 except OSError:
2756 2756 pass
2757 2757 repo.dirstate.remove(f)
2758 2758
2759 2759 normal = None
2760 2760 if node == parent:
2761 2761 # We're reverting to our parent. If possible, we'd like status
2762 2762 # to report the file as clean. We have to use normallookup for
2763 2763 # merges to avoid losing information about merged/dirty files.
2764 2764 if p2 != nullid:
2765 2765 normal = repo.dirstate.normallookup
2766 2766 else:
2767 2767 normal = repo.dirstate.normal
2768 2768 for f in revert[0]:
2769 2769 checkout(f)
2770 2770 if normal:
2771 2771 normal(f)
2772 2772
2773 2773 for f in add[0]:
2774 2774 checkout(f)
2775 2775 repo.dirstate.add(f)
2776 2776
2777 2777 normal = repo.dirstate.normallookup
2778 2778 if node == parent and p2 == nullid:
2779 2779 normal = repo.dirstate.normal
2780 2780 for f in undelete[0]:
2781 2781 checkout(f)
2782 2782 normal(f)
2783 2783
2784 2784 finally:
2785 2785 wlock.release()
2786 2786
2787 2787 def rollback(ui, repo):
2788 2788 """roll back the last transaction
2789 2789
2790 2790 This command should be used with care. There is only one level of
2791 2791 rollback, and there is no way to undo a rollback. It will also
2792 2792 restore the dirstate at the time of the last transaction, losing
2793 2793 any dirstate changes since that time. This command does not alter
2794 2794 the working directory.
2795 2795
2796 2796 Transactions are used to encapsulate the effects of all commands
2797 2797 that create new changesets or propagate existing changesets into a
2798 2798 repository. For example, the following commands are transactional,
2799 2799 and their effects can be rolled back:
2800 2800
2801 2801 - commit
2802 2802 - import
2803 2803 - pull
2804 2804 - push (with this repository as the destination)
2805 2805 - unbundle
2806 2806
2807 2807 This command is not intended for use on public repositories. Once
2808 2808 changes are visible for pull by other users, rolling a transaction
2809 2809 back locally is ineffective (someone else may already have pulled
2810 2810 the changes). Furthermore, a race is possible with readers of the
2811 2811 repository; for example an in-progress pull from the repository
2812 2812 may fail if a rollback is performed.
2813 2813 """
2814 2814 repo.rollback()
2815 2815
2816 2816 def root(ui, repo):
2817 2817 """print the root (top) of the current working directory
2818 2818
2819 2819 Print the root directory of the current repository.
2820 2820 """
2821 2821 ui.write(repo.root + "\n")
2822 2822
2823 2823 def serve(ui, repo, **opts):
2824 2824 """export the repository via HTTP
2825 2825
2826 2826 Start a local HTTP repository browser and pull server.
2827 2827
2828 2828 By default, the server logs accesses to stdout and errors to
2829 2829 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
2830 2830 files.
2831 2831 """
2832 2832
2833 2833 if opts["stdio"]:
2834 2834 if repo is None:
2835 2835 raise error.RepoError(_("There is no Mercurial repository here"
2836 2836 " (.hg not found)"))
2837 2837 s = sshserver.sshserver(ui, repo)
2838 2838 s.serve_forever()
2839 2839
2840 2840 baseui = repo and repo.baseui or ui
2841 2841 optlist = ("name templates style address port prefix ipv6"
2842 2842 " accesslog errorlog webdir_conf certificate encoding")
2843 2843 for o in optlist.split():
2844 2844 if opts.get(o, None):
2845 2845 baseui.setconfig("web", o, str(opts[o]))
2846 2846 if (repo is not None) and (repo.ui != baseui):
2847 2847 repo.ui.setconfig("web", o, str(opts[o]))
2848 2848
2849 2849 if repo is None and not ui.config("web", "webdir_conf"):
2850 2850 raise error.RepoError(_("There is no Mercurial repository here"
2851 2851 " (.hg not found)"))
2852 2852
2853 2853 class service(object):
2854 2854 def init(self):
2855 2855 util.set_signal_handler()
2856 2856 self.httpd = server.create_server(baseui, repo)
2857 2857
2858 2858 if not ui.verbose:
2859 2859 return
2860 2860
2861 2861 if self.httpd.prefix:
2862 2862 prefix = self.httpd.prefix.strip('/') + '/'
2863 2863 else:
2864 2864 prefix = ''
2865 2865
2866 2866 port = ':%d' % self.httpd.port
2867 2867 if port == ':80':
2868 2868 port = ''
2869 2869
2870 2870 bindaddr = self.httpd.addr
2871 2871 if bindaddr == '0.0.0.0':
2872 2872 bindaddr = '*'
2873 2873 elif ':' in bindaddr: # IPv6
2874 2874 bindaddr = '[%s]' % bindaddr
2875 2875
2876 2876 fqaddr = self.httpd.fqaddr
2877 2877 if ':' in fqaddr:
2878 2878 fqaddr = '[%s]' % fqaddr
2879 2879 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2880 2880 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2881 2881
2882 2882 def run(self):
2883 2883 self.httpd.serve_forever()
2884 2884
2885 2885 service = service()
2886 2886
2887 2887 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2888 2888
2889 2889 def status(ui, repo, *pats, **opts):
2890 2890 """show changed files in the working directory
2891 2891
2892 2892 Show status of files in the repository. If names are given, only
2893 2893 files that match are shown. Files that are clean or ignored or
2894 2894 the source of a copy/move operation, are not listed unless
2895 2895 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
2896 2896 Unless options described with "show only ..." are given, the
2897 2897 options -mardu are used.
2898 2898
2899 2899 Option -q/--quiet hides untracked (unknown and ignored) files
2900 2900 unless explicitly requested with -u/--unknown or -i/--ignored.
2901 2901
2902 2902 NOTE: status may appear to disagree with diff if permissions have
2903 2903 changed or a merge has occurred. The standard diff format does not
2904 2904 report permission changes and diff only reports changes relative
2905 2905 to one merge parent.
2906 2906
2907 2907 If one revision is given, it is used as the base revision.
2908 2908 If two revisions are given, the differences between them are
2909 2909 shown. The --change option can also be used as a shortcut to list
2910 2910 the changed files of a revision from its first parent.
2911 2911
2912 2912 The codes used to show the status of files are::
2913 2913
2914 2914 M = modified
2915 2915 A = added
2916 2916 R = removed
2917 2917 C = clean
2918 2918 ! = missing (deleted by non-hg command, but still tracked)
2919 2919 ? = not tracked
2920 2920 I = ignored
2921 2921 = origin of the previous file listed as A (added)
2922 2922 """
2923 2923
2924 2924 revs = opts.get('rev')
2925 2925 change = opts.get('change')
2926 2926
2927 2927 if revs and change:
2928 2928 msg = _('cannot specify --rev and --change at the same time')
2929 2929 raise util.Abort(msg)
2930 2930 elif change:
2931 2931 node2 = repo.lookup(change)
2932 2932 node1 = repo[node2].parents()[0].node()
2933 2933 else:
2934 2934 node1, node2 = cmdutil.revpair(repo, revs)
2935 2935
2936 2936 cwd = (pats and repo.getcwd()) or ''
2937 2937 end = opts.get('print0') and '\0' or '\n'
2938 2938 copy = {}
2939 2939 states = 'modified added removed deleted unknown ignored clean'.split()
2940 2940 show = [k for k in states if opts.get(k)]
2941 2941 if opts.get('all'):
2942 2942 show += ui.quiet and (states[:4] + ['clean']) or states
2943 2943 if not show:
2944 2944 show = ui.quiet and states[:4] or states[:5]
2945 2945
2946 2946 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
2947 2947 'ignored' in show, 'clean' in show, 'unknown' in show)
2948 2948 changestates = zip(states, 'MAR!?IC', stat)
2949 2949
2950 2950 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
2951 2951 ctxn = repo[nullid]
2952 2952 ctx1 = repo[node1]
2953 2953 ctx2 = repo[node2]
2954 2954 added = stat[1]
2955 2955 if node2 is None:
2956 2956 added = stat[0] + stat[1] # merged?
2957 2957
2958 2958 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
2959 2959 if k in added:
2960 2960 copy[k] = v
2961 2961 elif v in added:
2962 2962 copy[v] = k
2963 2963
2964 2964 for state, char, files in changestates:
2965 2965 if state in show:
2966 2966 format = "%s %%s%s" % (char, end)
2967 2967 if opts.get('no_status'):
2968 2968 format = "%%s%s" % end
2969 2969
2970 2970 for f in files:
2971 2971 ui.write(format % repo.pathto(f, cwd))
2972 2972 if f in copy:
2973 2973 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2974 2974
2975 2975 def summary(ui, repo, **opts):
2976 2976 """summarize working directory state
2977 2977
2978 2978 This generates a brief summary of the working directory state,
2979 2979 including parents, branch, commit status, and available updates.
2980 2980
2981 2981 With the --remote option, this will check the default paths for
2982 2982 incoming and outgoing changes. This can be time-consuming.
2983 2983 """
2984 2984
2985 2985 ctx = repo[None]
2986 2986 parents = ctx.parents()
2987 2987 pnode = parents[0].node()
2988 2988 tags = repo.tags()
2989 2989
2990 2990 for p in parents:
2991 2991 t = ' '.join([t for t in tags if tags[t] == p.node()])
2992 2992 if p.rev() == -1:
2993 2993 if not len(repo):
2994 2994 t += _(' (empty repository)')
2995 2995 else:
2996 2996 t += _(' (no revision checked out)')
2997 2997 ui.write(_('parent: %d:%s %s\n') % (p.rev(), str(p), t))
2998 2998 if p.description():
2999 2999 ui.status(' ' + p.description().splitlines()[0].strip() + '\n')
3000 3000
3001 3001 branch = ctx.branch()
3002 3002 bheads = repo.branchheads(branch)
3003 3003 m = _('branch: %s\n') % branch
3004 3004 if branch != 'default':
3005 3005 ui.write(m)
3006 3006 else:
3007 3007 ui.status(m)
3008 3008
3009 3009 st = list(repo.status(unknown=True))[:6]
3010 3010 ms = merge_.mergestate(repo)
3011 3011 st.append([f for f in ms if ms[f] == 'u'])
3012 3012 labels = [_('%d modified'), _('%d added'), _('%d removed'),
3013 3013 _('%d deleted'), _('%d unknown'), _('%d ignored'),
3014 3014 _('%d unresolved')]
3015 3015 t = []
3016 3016 for s, l in zip(st, labels):
3017 3017 if s:
3018 3018 t.append(l % len(s))
3019 3019
3020 3020 t = ', '.join(t)
3021 3021 cleanworkdir = False
3022 3022
3023 3023 if len(parents) > 1:
3024 3024 t += _(' (merge)')
3025 3025 elif branch != parents[0].branch():
3026 3026 t += _(' (new branch)')
3027 3027 elif (not st[0] and not st[1] and not st[2]):
3028 3028 t += _(' (clean)')
3029 3029 cleanworkdir = True
3030 3030 elif pnode not in bheads:
3031 3031 t += _(' (new branch head)')
3032 3032
3033 3033 if cleanworkdir:
3034 3034 ui.status(_('commit: %s\n') % t.strip())
3035 3035 else:
3036 3036 ui.write(_('commit: %s\n') % t.strip())
3037 3037
3038 3038 # all ancestors of branch heads - all ancestors of parent = new csets
3039 3039 new = [0] * len(repo)
3040 3040 cl = repo.changelog
3041 3041 for a in [cl.rev(n) for n in bheads]:
3042 3042 new[a] = 1
3043 3043 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3044 3044 new[a] = 1
3045 3045 for a in [p.rev() for p in parents]:
3046 3046 if a >= 0:
3047 3047 new[a] = 0
3048 3048 for a in cl.ancestors(*[p.rev() for p in parents]):
3049 3049 new[a] = 0
3050 3050 new = sum(new)
3051 3051
3052 3052 if new == 0:
3053 3053 ui.status(_('update: (current)\n'))
3054 3054 elif pnode not in bheads:
3055 3055 ui.write(_('update: %d new changesets (update)\n') % new)
3056 3056 else:
3057 3057 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3058 3058 (new, len(bheads)))
3059 3059
3060 3060 if opts.get('remote'):
3061 3061 t = []
3062 3062 source, branches = hg.parseurl(ui.expandpath('default'))
3063 3063 other = hg.repository(cmdutil.remoteui(repo, {}), source)
3064 3064 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3065 3065 ui.debug('comparing with %s\n' % url.hidepassword(source))
3066 3066 repo.ui.pushbuffer()
3067 3067 common, incoming, rheads = repo.findcommonincoming(other)
3068 3068 repo.ui.popbuffer()
3069 3069 if incoming:
3070 3070 t.append(_('1 or more incoming'))
3071 3071
3072 3072 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3073 3073 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3074 3074 other = hg.repository(cmdutil.remoteui(repo, {}), dest)
3075 3075 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3076 3076 repo.ui.pushbuffer()
3077 3077 o = repo.findoutgoing(other)
3078 3078 repo.ui.popbuffer()
3079 3079 o = repo.changelog.nodesbetween(o, None)[0]
3080 3080 if o:
3081 3081 t.append(_('%d outgoing') % len(o))
3082 3082
3083 3083 if t:
3084 3084 ui.write(_('remote: %s\n') % (', '.join(t)))
3085 3085 else:
3086 3086 ui.status(_('remote: (synced)\n'))
3087 3087
3088 3088 def tag(ui, repo, name1, *names, **opts):
3089 3089 """add one or more tags for the current or given revision
3090 3090
3091 3091 Name a particular revision using <name>.
3092 3092
3093 3093 Tags are used to name particular revisions of the repository and are
3094 3094 very useful to compare different revisions, to go back to significant
3095 3095 earlier versions or to mark branch points as releases, etc.
3096 3096
3097 3097 If no revision is given, the parent of the working directory is
3098 3098 used, or tip if no revision is checked out.
3099 3099
3100 3100 To facilitate version control, distribution, and merging of tags,
3101 3101 they are stored as a file named ".hgtags" which is managed
3102 3102 similarly to other project files and can be hand-edited if
3103 3103 necessary. The file '.hg/localtags' is used for local tags (not
3104 3104 shared among repositories).
3105 3105
3106 3106 See 'hg help dates' for a list of formats valid for -d/--date.
3107 3107 """
3108 3108
3109 3109 rev_ = "."
3110 3110 names = (name1,) + names
3111 3111 if len(names) != len(set(names)):
3112 3112 raise util.Abort(_('tag names must be unique'))
3113 3113 for n in names:
3114 3114 if n in ['tip', '.', 'null']:
3115 3115 raise util.Abort(_('the name \'%s\' is reserved') % n)
3116 3116 if opts.get('rev') and opts.get('remove'):
3117 3117 raise util.Abort(_("--rev and --remove are incompatible"))
3118 3118 if opts.get('rev'):
3119 3119 rev_ = opts['rev']
3120 3120 message = opts.get('message')
3121 3121 if opts.get('remove'):
3122 3122 expectedtype = opts.get('local') and 'local' or 'global'
3123 3123 for n in names:
3124 3124 if not repo.tagtype(n):
3125 3125 raise util.Abort(_('tag \'%s\' does not exist') % n)
3126 3126 if repo.tagtype(n) != expectedtype:
3127 3127 if expectedtype == 'global':
3128 3128 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3129 3129 else:
3130 3130 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3131 3131 rev_ = nullid
3132 3132 if not message:
3133 3133 # we don't translate commit messages
3134 3134 message = 'Removed tag %s' % ', '.join(names)
3135 3135 elif not opts.get('force'):
3136 3136 for n in names:
3137 3137 if n in repo.tags():
3138 3138 raise util.Abort(_('tag \'%s\' already exists '
3139 3139 '(use -f to force)') % n)
3140 3140 if not rev_ and repo.dirstate.parents()[1] != nullid:
3141 3141 raise util.Abort(_('uncommitted merge - please provide a '
3142 3142 'specific revision'))
3143 3143 r = repo[rev_].node()
3144 3144
3145 3145 if not message:
3146 3146 # we don't translate commit messages
3147 3147 message = ('Added tag %s for changeset %s' %
3148 3148 (', '.join(names), short(r)))
3149 3149
3150 3150 date = opts.get('date')
3151 3151 if date:
3152 3152 date = util.parsedate(date)
3153 3153
3154 3154 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3155 3155
3156 3156 def tags(ui, repo):
3157 3157 """list repository tags
3158 3158
3159 3159 This lists both regular and local tags. When the -v/--verbose
3160 3160 switch is used, a third column "local" is printed for local tags.
3161 3161 """
3162 3162
3163 3163 hexfunc = ui.debugflag and hex or short
3164 3164 tagtype = ""
3165 3165
3166 3166 for t, n in reversed(repo.tagslist()):
3167 3167 if ui.quiet:
3168 3168 ui.write("%s\n" % t)
3169 3169 continue
3170 3170
3171 3171 try:
3172 3172 hn = hexfunc(n)
3173 3173 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3174 3174 except error.LookupError:
3175 3175 r = " ?:%s" % hn
3176 3176 else:
3177 3177 spaces = " " * (30 - encoding.colwidth(t))
3178 3178 if ui.verbose:
3179 3179 if repo.tagtype(t) == 'local':
3180 3180 tagtype = " local"
3181 3181 else:
3182 3182 tagtype = ""
3183 3183 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3184 3184
3185 3185 def tip(ui, repo, **opts):
3186 3186 """show the tip revision
3187 3187
3188 3188 The tip revision (usually just called the tip) is the changeset
3189 3189 most recently added to the repository (and therefore the most
3190 3190 recently changed head).
3191 3191
3192 3192 If you have just made a commit, that commit will be the tip. If
3193 3193 you have just pulled changes from another repository, the tip of
3194 3194 that repository becomes the current tip. The "tip" tag is special
3195 3195 and cannot be renamed or assigned to a different changeset.
3196 3196 """
3197 3197 displayer = cmdutil.show_changeset(ui, repo, opts)
3198 3198 displayer.show(repo[len(repo) - 1])
3199 3199 displayer.close()
3200 3200
3201 3201 def unbundle(ui, repo, fname1, *fnames, **opts):
3202 3202 """apply one or more changegroup files
3203 3203
3204 3204 Apply one or more compressed changegroup files generated by the
3205 3205 bundle command.
3206 3206 """
3207 3207 fnames = (fname1,) + fnames
3208 3208
3209 3209 lock = repo.lock()
3210 3210 try:
3211 3211 for fname in fnames:
3212 3212 f = url.open(ui, fname)
3213 3213 gen = changegroup.readbundle(f, fname)
3214 3214 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
3215 3215 finally:
3216 3216 lock.release()
3217 3217
3218 3218 return postincoming(ui, repo, modheads, opts.get('update'), None)
3219 3219
3220 3220 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3221 3221 """update working directory
3222 3222
3223 3223 Update the repository's working directory to the specified
3224 3224 changeset.
3225 3225
3226 3226 If no changeset is specified, attempt to update to the head of the
3227 3227 current branch. If this head is a descendant of the working
3228 3228 directory's parent, update to it, otherwise abort.
3229 3229
3230 3230 The following rules apply when the working directory contains
3231 3231 uncommitted changes:
3232 3232
3233 3233 1. If neither -c/--check nor -C/--clean is specified, and if
3234 3234 the requested changeset is an ancestor or descendant of
3235 3235 the working directory's parent, the uncommitted changes
3236 3236 are merged into the requested changeset and the merged
3237 3237 result is left uncommitted. If the requested changeset is
3238 3238 not an ancestor or descendant (that is, it is on another
3239 3239 branch), the update is aborted and the uncommitted changes
3240 3240 are preserved.
3241 3241
3242 3242 2. With the -c/--check option, the update is aborted and the
3243 3243 uncommitted changes are preserved.
3244 3244
3245 3245 3. With the -C/--clean option, uncommitted changes are discarded and
3246 3246 the working directory is updated to the requested changeset.
3247 3247
3248 3248 Use null as the changeset to remove the working directory (like 'hg
3249 3249 clone -U').
3250 3250
3251 3251 If you want to update just one file to an older changeset, use 'hg revert'.
3252 3252
3253 3253 See 'hg help dates' for a list of formats valid for -d/--date.
3254 3254 """
3255 3255 if rev and node:
3256 3256 raise util.Abort(_("please specify just one revision"))
3257 3257
3258 3258 if not rev:
3259 3259 rev = node
3260 3260
3261 3261 if check and clean:
3262 3262 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
3263 3263
3264 3264 if check:
3265 3265 # we could use dirty() but we can ignore merge and branch trivia
3266 3266 c = repo[None]
3267 3267 if c.modified() or c.added() or c.removed():
3268 3268 raise util.Abort(_("uncommitted local changes"))
3269 3269
3270 3270 if date:
3271 3271 if rev:
3272 3272 raise util.Abort(_("you can't specify a revision and a date"))
3273 3273 rev = cmdutil.finddate(ui, repo, date)
3274 3274
3275 3275 if clean or check:
3276 3276 return hg.clean(repo, rev)
3277 3277 else:
3278 3278 return hg.update(repo, rev)
3279 3279
3280 3280 def verify(ui, repo):
3281 3281 """verify the integrity of the repository
3282 3282
3283 3283 Verify the integrity of the current repository.
3284 3284
3285 3285 This will perform an extensive check of the repository's
3286 3286 integrity, validating the hashes and checksums of each entry in
3287 3287 the changelog, manifest, and tracked files, as well as the
3288 3288 integrity of their crosslinks and indices.
3289 3289 """
3290 3290 return hg.verify(repo)
3291 3291
3292 3292 def version_(ui):
3293 3293 """output version and copyright information"""
3294 3294 ui.write(_("Mercurial Distributed SCM (version %s)\n")
3295 3295 % util.version())
3296 3296 ui.status(_(
3297 3297 "\nCopyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others\n"
3298 3298 "This is free software; see the source for copying conditions. "
3299 3299 "There is NO\nwarranty; "
3300 3300 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
3301 3301 ))
3302 3302
3303 3303 # Command options and aliases are listed here, alphabetically
3304 3304
3305 3305 globalopts = [
3306 3306 ('R', 'repository', '',
3307 3307 _('repository root directory or name of overlay bundle file')),
3308 3308 ('', 'cwd', '', _('change working directory')),
3309 3309 ('y', 'noninteractive', None,
3310 3310 _('do not prompt, assume \'yes\' for any required answers')),
3311 3311 ('q', 'quiet', None, _('suppress output')),
3312 3312 ('v', 'verbose', None, _('enable additional output')),
3313 3313 ('', 'config', [], _('set/override config option')),
3314 3314 ('', 'debug', None, _('enable debugging output')),
3315 3315 ('', 'debugger', None, _('start debugger')),
3316 3316 ('', 'encoding', encoding.encoding, _('set the charset encoding')),
3317 3317 ('', 'encodingmode', encoding.encodingmode,
3318 3318 _('set the charset encoding mode')),
3319 3319 ('', 'traceback', None, _('always print a traceback on exception')),
3320 3320 ('', 'time', None, _('time how long the command takes')),
3321 3321 ('', 'profile', None, _('print command execution profile')),
3322 3322 ('', 'version', None, _('output version information and exit')),
3323 3323 ('h', 'help', None, _('display help and exit')),
3324 3324 ]
3325 3325
3326 3326 dryrunopts = [('n', 'dry-run', None,
3327 3327 _('do not perform actions, just print output'))]
3328 3328
3329 3329 remoteopts = [
3330 3330 ('e', 'ssh', '', _('specify ssh command to use')),
3331 3331 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
3332 3332 ]
3333 3333
3334 3334 walkopts = [
3335 3335 ('I', 'include', [], _('include names matching the given patterns')),
3336 3336 ('X', 'exclude', [], _('exclude names matching the given patterns')),
3337 3337 ]
3338 3338
3339 3339 commitopts = [
3340 3340 ('m', 'message', '', _('use <text> as commit message')),
3341 3341 ('l', 'logfile', '', _('read commit message from <file>')),
3342 3342 ]
3343 3343
3344 3344 commitopts2 = [
3345 3345 ('d', 'date', '', _('record datecode as commit date')),
3346 3346 ('u', 'user', '', _('record the specified user as committer')),
3347 3347 ]
3348 3348
3349 3349 templateopts = [
3350 3350 ('', 'style', '', _('display using template map file')),
3351 3351 ('', 'template', '', _('display with template')),
3352 3352 ]
3353 3353
3354 3354 logopts = [
3355 3355 ('p', 'patch', None, _('show patch')),
3356 3356 ('g', 'git', None, _('use git extended diff format')),
3357 3357 ('l', 'limit', '', _('limit number of changes displayed')),
3358 3358 ('M', 'no-merges', None, _('do not show merges')),
3359 3359 ] + templateopts
3360 3360
3361 3361 diffopts = [
3362 3362 ('a', 'text', None, _('treat all files as text')),
3363 3363 ('g', 'git', None, _('use git extended diff format')),
3364 3364 ('', 'nodates', None, _('omit dates from diff headers'))
3365 3365 ]
3366 3366
3367 3367 diffopts2 = [
3368 3368 ('p', 'show-function', None, _('show which function each change is in')),
3369 3369 ('', 'reverse', None, _('produce a diff that undoes the changes')),
3370 3370 ('w', 'ignore-all-space', None,
3371 3371 _('ignore white space when comparing lines')),
3372 3372 ('b', 'ignore-space-change', None,
3373 3373 _('ignore changes in the amount of white space')),
3374 3374 ('B', 'ignore-blank-lines', None,
3375 3375 _('ignore changes whose lines are all blank')),
3376 3376 ('U', 'unified', '', _('number of lines of context to show')),
3377 3377 ('', 'stat', None, _('output diffstat-style summary of changes')),
3378 3378 ]
3379 3379
3380 3380 similarityopts = [
3381 3381 ('s', 'similarity', '',
3382 3382 _('guess renamed files by similarity (0<=s<=100)'))
3383 3383 ]
3384 3384
3385 3385 table = {
3386 3386 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
3387 3387 "addremove":
3388 3388 (addremove, similarityopts + walkopts + dryrunopts,
3389 3389 _('[OPTION]... [FILE]...')),
3390 3390 "^annotate|blame":
3391 3391 (annotate,
3392 3392 [('r', 'rev', '', _('annotate the specified revision')),
3393 3393 ('', 'follow', None, _('follow copies and renames (DEPRECATED)')),
3394 3394 ('', 'no-follow', None, _("don't follow copies and renames")),
3395 3395 ('a', 'text', None, _('treat all files as text')),
3396 3396 ('u', 'user', None, _('list the author (long with -v)')),
3397 3397 ('f', 'file', None, _('list the filename')),
3398 3398 ('d', 'date', None, _('list the date (short with -q)')),
3399 3399 ('n', 'number', None, _('list the revision number (default)')),
3400 3400 ('c', 'changeset', None, _('list the changeset')),
3401 3401 ('l', 'line-number', None,
3402 3402 _('show line number at the first appearance'))
3403 3403 ] + walkopts,
3404 3404 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
3405 3405 "archive":
3406 3406 (archive,
3407 3407 [('', 'no-decode', None, _('do not pass files through decoders')),
3408 3408 ('p', 'prefix', '', _('directory prefix for files in archive')),
3409 3409 ('r', 'rev', '', _('revision to distribute')),
3410 3410 ('t', 'type', '', _('type of distribution to create')),
3411 3411 ] + walkopts,
3412 3412 _('[OPTION]... DEST')),
3413 3413 "backout":
3414 3414 (backout,
3415 3415 [('', 'merge', None,
3416 3416 _('merge with old dirstate parent after backout')),
3417 3417 ('', 'parent', '', _('parent to choose when backing out merge')),
3418 3418 ('r', 'rev', '', _('revision to backout')),
3419 3419 ] + walkopts + commitopts + commitopts2,
3420 3420 _('[OPTION]... [-r] REV')),
3421 3421 "bisect":
3422 3422 (bisect,
3423 3423 [('r', 'reset', False, _('reset bisect state')),
3424 3424 ('g', 'good', False, _('mark changeset good')),
3425 3425 ('b', 'bad', False, _('mark changeset bad')),
3426 3426 ('s', 'skip', False, _('skip testing changeset')),
3427 3427 ('c', 'command', '', _('use command to check changeset state')),
3428 3428 ('U', 'noupdate', False, _('do not update to target'))],
3429 3429 _("[-gbsr] [-U] [-c CMD] [REV]")),
3430 3430 "branch":
3431 3431 (branch,
3432 3432 [('f', 'force', None,
3433 3433 _('set branch name even if it shadows an existing branch')),
3434 3434 ('C', 'clean', None, _('reset branch name to parent branch name'))],
3435 3435 _('[-fC] [NAME]')),
3436 3436 "branches":
3437 3437 (branches,
3438 3438 [('a', 'active', False,
3439 3439 _('show only branches that have unmerged heads')),
3440 3440 ('c', 'closed', False,
3441 3441 _('show normal and closed branches'))],
3442 3442 _('[-ac]')),
3443 3443 "bundle":
3444 3444 (bundle,
3445 3445 [('f', 'force', None,
3446 3446 _('run even when the destination is unrelated')),
3447 3447 ('r', 'rev', [],
3448 3448 _('a changeset intended to be added to the destination')),
3449 3449 ('b', 'branch', [],
3450 3450 _('a specific branch you would like to bundle')),
3451 3451 ('', 'base', [],
3452 3452 _('a base changeset assumed to be available at the destination')),
3453 3453 ('a', 'all', None, _('bundle all changesets in the repository')),
3454 3454 ('t', 'type', 'bzip2', _('bundle compression type to use')),
3455 3455 ] + remoteopts,
3456 3456 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3457 3457 "cat":
3458 3458 (cat,
3459 3459 [('o', 'output', '', _('print output to file with formatted name')),
3460 3460 ('r', 'rev', '', _('print the given revision')),
3461 3461 ('', 'decode', None, _('apply any matching decode filter')),
3462 3462 ] + walkopts,
3463 3463 _('[OPTION]... FILE...')),
3464 3464 "^clone":
3465 3465 (clone,
3466 3466 [('U', 'noupdate', None,
3467 3467 _('the clone will include an empty working copy (only a repository)')),
3468 3468 ('u', 'updaterev', '',
3469 3469 _('revision, tag or branch to check out')),
3470 3470 ('r', 'rev', [],
3471 3471 _('include the specified changeset')),
3472 3472 ('b', 'branch', [],
3473 3473 _('clone only the specified branch')),
3474 3474 ('', 'pull', None, _('use pull protocol to copy metadata')),
3475 3475 ('', 'uncompressed', None,
3476 3476 _('use uncompressed transfer (fast over LAN)')),
3477 3477 ] + remoteopts,
3478 3478 _('[OPTION]... SOURCE [DEST]')),
3479 3479 "^commit|ci":
3480 3480 (commit,
3481 3481 [('A', 'addremove', None,
3482 3482 _('mark new/missing files as added/removed before committing')),
3483 3483 ('', 'close-branch', None,
3484 3484 _('mark a branch as closed, hiding it from the branch list')),
3485 3485 ] + walkopts + commitopts + commitopts2,
3486 3486 _('[OPTION]... [FILE]...')),
3487 3487 "copy|cp":
3488 3488 (copy,
3489 3489 [('A', 'after', None, _('record a copy that has already occurred')),
3490 3490 ('f', 'force', None,
3491 3491 _('forcibly copy over an existing managed file')),
3492 3492 ] + walkopts + dryrunopts,
3493 3493 _('[OPTION]... [SOURCE]... DEST')),
3494 3494 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
3495 3495 "debugcheckstate": (debugcheckstate, [], ''),
3496 3496 "debugcommands": (debugcommands, [], _('[COMMAND]')),
3497 3497 "debugcomplete":
3498 3498 (debugcomplete,
3499 3499 [('o', 'options', None, _('show the command options'))],
3500 3500 _('[-o] CMD')),
3501 3501 "debugdate":
3502 3502 (debugdate,
3503 3503 [('e', 'extended', None, _('try extended date formats'))],
3504 3504 _('[-e] DATE [RANGE]')),
3505 3505 "debugdata": (debugdata, [], _('FILE REV')),
3506 3506 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
3507 3507 "debugindex": (debugindex, [], _('FILE')),
3508 3508 "debugindexdot": (debugindexdot, [], _('FILE')),
3509 3509 "debuginstall": (debuginstall, [], ''),
3510 3510 "debugrebuildstate":
3511 3511 (debugrebuildstate,
3512 3512 [('r', 'rev', '', _('revision to rebuild to'))],
3513 3513 _('[-r REV] [REV]')),
3514 3514 "debugrename":
3515 3515 (debugrename,
3516 3516 [('r', 'rev', '', _('revision to debug'))],
3517 3517 _('[-r REV] FILE')),
3518 3518 "debugsetparents":
3519 3519 (debugsetparents, [], _('REV1 [REV2]')),
3520 3520 "debugstate":
3521 3521 (debugstate,
3522 3522 [('', 'nodates', None, _('do not display the saved mtime'))],
3523 3523 _('[OPTION]...')),
3524 3524 "debugsub":
3525 3525 (debugsub,
3526 3526 [('r', 'rev', '', _('revision to check'))],
3527 3527 _('[-r REV] [REV]')),
3528 3528 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
3529 3529 "^diff":
3530 3530 (diff,
3531 3531 [('r', 'rev', [], _('revision')),
3532 3532 ('c', 'change', '', _('change made by revision'))
3533 3533 ] + diffopts + diffopts2 + walkopts,
3534 3534 _('[OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
3535 3535 "^export":
3536 3536 (export,
3537 3537 [('o', 'output', '', _('print output to file with formatted name')),
3538 3538 ('', 'switch-parent', None, _('diff against the second parent')),
3539 3539 ('r', 'rev', [], _('revisions to export')),
3540 3540 ] + diffopts,
3541 3541 _('[OPTION]... [-o OUTFILESPEC] REV...')),
3542 3542 "^forget":
3543 3543 (forget,
3544 3544 [] + walkopts,
3545 3545 _('[OPTION]... FILE...')),
3546 3546 "grep":
3547 3547 (grep,
3548 3548 [('0', 'print0', None, _('end fields with NUL')),
3549 3549 ('', 'all', None, _('print all revisions that match')),
3550 3550 ('f', 'follow', None,
3551 3551 _('follow changeset history,'
3552 3552 ' or file history across copies and renames')),
3553 3553 ('i', 'ignore-case', None, _('ignore case when matching')),
3554 3554 ('l', 'files-with-matches', None,
3555 3555 _('print only filenames and revisions that match')),
3556 3556 ('n', 'line-number', None, _('print matching line numbers')),
3557 3557 ('r', 'rev', [], _('search in given revision range')),
3558 3558 ('u', 'user', None, _('list the author (long with -v)')),
3559 3559 ('d', 'date', None, _('list the date (short with -q)')),
3560 3560 ] + walkopts,
3561 3561 _('[OPTION]... PATTERN [FILE]...')),
3562 3562 "heads":
3563 3563 (heads,
3564 3564 [('r', 'rev', '', _('show only heads which are descendants of REV')),
3565 3565 ('t', 'topo', False, _('show topological heads only')),
3566 3566 ('a', 'active', False,
3567 3567 _('show active branchheads only [DEPRECATED]')),
3568 3568 ('c', 'closed', False,
3569 3569 _('show normal and closed branch heads')),
3570 3570 ] + templateopts,
3571 3571 _('[-ac] [-r STARTREV] [REV]...')),
3572 3572 "help": (help_, [], _('[TOPIC]')),
3573 3573 "identify|id":
3574 3574 (identify,
3575 3575 [('r', 'rev', '', _('identify the specified revision')),
3576 3576 ('n', 'num', None, _('show local revision number')),
3577 3577 ('i', 'id', None, _('show global revision id')),
3578 3578 ('b', 'branch', None, _('show branch')),
3579 3579 ('t', 'tags', None, _('show tags'))],
3580 3580 _('[-nibt] [-r REV] [SOURCE]')),
3581 3581 "import|patch":
3582 3582 (import_,
3583 3583 [('p', 'strip', 1,
3584 3584 _('directory strip option for patch. This has the same '
3585 3585 'meaning as the corresponding patch option')),
3586 3586 ('b', 'base', '', _('base path')),
3587 3587 ('f', 'force', None,
3588 3588 _('skip check for outstanding uncommitted changes')),
3589 3589 ('', 'no-commit', None,
3590 3590 _("don't commit, just update the working directory")),
3591 3591 ('', 'exact', None,
3592 3592 _('apply patch to the nodes from which it was generated')),
3593 3593 ('', 'import-branch', None,
3594 3594 _('use any branch information in patch (implied by --exact)'))] +
3595 3595 commitopts + commitopts2 + similarityopts,
3596 3596 _('[OPTION]... PATCH...')),
3597 3597 "incoming|in":
3598 3598 (incoming,
3599 3599 [('f', 'force', None,
3600 3600 _('run even if remote repository is unrelated')),
3601 3601 ('n', 'newest-first', None, _('show newest record first')),
3602 3602 ('', 'bundle', '', _('file to store the bundles into')),
3603 3603 ('r', 'rev', [],
3604 3604 _('a remote changeset intended to be added')),
3605 3605 ('b', 'branch', [],
3606 3606 _('a specific branch you would like to pull')),
3607 3607 ] + logopts + remoteopts,
3608 3608 _('[-p] [-n] [-M] [-f] [-r REV]...'
3609 3609 ' [--bundle FILENAME] [SOURCE]')),
3610 3610 "^init":
3611 3611 (init,
3612 3612 remoteopts,
3613 3613 _('[-e CMD] [--remotecmd CMD] [DEST]')),
3614 3614 "locate":
3615 3615 (locate,
3616 3616 [('r', 'rev', '', _('search the repository as it is in REV')),
3617 3617 ('0', 'print0', None,
3618 3618 _('end filenames with NUL, for use with xargs')),
3619 3619 ('f', 'fullpath', None,
3620 3620 _('print complete paths from the filesystem root')),
3621 3621 ] + walkopts,
3622 3622 _('[OPTION]... [PATTERN]...')),
3623 3623 "^log|history":
3624 3624 (log,
3625 3625 [('f', 'follow', None,
3626 3626 _('follow changeset history,'
3627 3627 ' or file history across copies and renames')),
3628 3628 ('', 'follow-first', None,
3629 3629 _('only follow the first parent of merge changesets')),
3630 3630 ('d', 'date', '', _('show revisions matching date spec')),
3631 3631 ('C', 'copies', None, _('show copied files')),
3632 3632 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3633 3633 ('r', 'rev', [], _('show the specified revision or range')),
3634 3634 ('', 'removed', None, _('include revisions where files were removed')),
3635 3635 ('m', 'only-merges', None, _('show only merges')),
3636 3636 ('u', 'user', [], _('revisions committed by user')),
3637 3637 ('b', 'only-branch', [],
3638 3638 _('show only changesets within the given named branch')),
3639 3639 ('P', 'prune', [],
3640 3640 _('do not display revision or any of its ancestors')),
3641 3641 ] + logopts + walkopts,
3642 3642 _('[OPTION]... [FILE]')),
3643 3643 "manifest":
3644 3644 (manifest,
3645 3645 [('r', 'rev', '', _('revision to display'))],
3646 3646 _('[-r REV]')),
3647 3647 "^merge":
3648 3648 (merge,
3649 3649 [('f', 'force', None, _('force a merge with outstanding changes')),
3650 3650 ('r', 'rev', '', _('revision to merge')),
3651 3651 ('P', 'preview', None,
3652 3652 _('review revisions to merge (no merge is performed)'))],
3653 3653 _('[-P] [-f] [[-r] REV]')),
3654 3654 "outgoing|out":
3655 3655 (outgoing,
3656 3656 [('f', 'force', None,
3657 3657 _('run even when the destination is unrelated')),
3658 3658 ('r', 'rev', [],
3659 3659 _('a changeset intended to be included in the destination')),
3660 3660 ('n', 'newest-first', None, _('show newest record first')),
3661 3661 ('b', 'branch', [],
3662 3662 _('a specific branch you would like to push')),
3663 3663 ] + logopts + remoteopts,
3664 3664 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3665 3665 "parents":
3666 3666 (parents,
3667 3667 [('r', 'rev', '', _('show parents of the specified revision')),
3668 3668 ] + templateopts,
3669 3669 _('[-r REV] [FILE]')),
3670 3670 "paths": (paths, [], _('[NAME]')),
3671 3671 "^pull":
3672 3672 (pull,
3673 3673 [('u', 'update', None,
3674 3674 _('update to new branch head if changesets were pulled')),
3675 3675 ('f', 'force', None,
3676 3676 _('run even when remote repository is unrelated')),
3677 3677 ('r', 'rev', [],
3678 3678 _('a remote changeset intended to be added')),
3679 3679 ('b', 'branch', [],
3680 3680 _('a specific branch you would like to pull')),
3681 3681 ] + remoteopts,
3682 3682 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3683 3683 "^push":
3684 3684 (push,
3685 3685 [('f', 'force', None, _('force push')),
3686 3686 ('r', 'rev', [],
3687 3687 _('a changeset intended to be included in the destination')),
3688 3688 ('b', 'branch', [],
3689 3689 _('a specific branch you would like to push')),
3690 3690 ] + remoteopts,
3691 3691 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3692 3692 "recover": (recover, []),
3693 3693 "^remove|rm":
3694 3694 (remove,
3695 3695 [('A', 'after', None, _('record delete for missing files')),
3696 3696 ('f', 'force', None,
3697 3697 _('remove (and delete) file even if added or modified')),
3698 3698 ] + walkopts,
3699 3699 _('[OPTION]... FILE...')),
3700 3700 "rename|mv":
3701 3701 (rename,
3702 3702 [('A', 'after', None, _('record a rename that has already occurred')),
3703 3703 ('f', 'force', None,
3704 3704 _('forcibly copy over an existing managed file')),
3705 3705 ] + walkopts + dryrunopts,
3706 3706 _('[OPTION]... SOURCE... DEST')),
3707 3707 "resolve":
3708 3708 (resolve,
3709 3709 [('a', 'all', None, _('select all unresolved files')),
3710 3710 ('l', 'list', None, _('list state of files needing merge')),
3711 3711 ('m', 'mark', None, _('mark files as resolved')),
3712 3712 ('u', 'unmark', None, _('unmark files as resolved')),
3713 3713 ('n', 'no-status', None, _('hide status prefix'))]
3714 3714 + walkopts,
3715 3715 _('[OPTION]... [FILE]...')),
3716 3716 "revert":
3717 3717 (revert,
3718 3718 [('a', 'all', None, _('revert all changes when no arguments given')),
3719 3719 ('d', 'date', '', _('tipmost revision matching date')),
3720 3720 ('r', 'rev', '', _('revert to the specified revision')),
3721 3721 ('', 'no-backup', None, _('do not save backup copies of files')),
3722 3722 ] + walkopts + dryrunopts,
3723 3723 _('[OPTION]... [-r REV] [NAME]...')),
3724 3724 "rollback": (rollback, []),
3725 3725 "root": (root, []),
3726 3726 "^serve":
3727 3727 (serve,
3728 3728 [('A', 'accesslog', '', _('name of access log file to write to')),
3729 3729 ('d', 'daemon', None, _('run server in background')),
3730 3730 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3731 3731 ('E', 'errorlog', '', _('name of error log file to write to')),
3732 3732 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3733 3733 ('a', 'address', '',
3734 3734 _('address to listen on (default: all interfaces)')),
3735 3735 ('', 'prefix', '',
3736 3736 _('prefix path to serve from (default: server root)')),
3737 3737 ('n', 'name', '',
3738 3738 _('name to show in web pages (default: working directory)')),
3739 3739 ('', 'webdir-conf', '', _('name of the webdir config file'
3740 3740 ' (serve more than one repository)')),
3741 3741 ('', 'pid-file', '', _('name of file to write process ID to')),
3742 3742 ('', 'stdio', None, _('for remote clients')),
3743 3743 ('t', 'templates', '', _('web templates to use')),
3744 3744 ('', 'style', '', _('template style to use')),
3745 3745 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3746 3746 ('', 'certificate', '', _('SSL certificate file'))],
3747 3747 _('[OPTION]...')),
3748 3748 "showconfig|debugconfig":
3749 3749 (showconfig,
3750 3750 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3751 3751 _('[-u] [NAME]...')),
3752 3752 "^summary|sum":
3753 3753 (summary,
3754 3754 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
3755 3755 "^status|st":
3756 3756 (status,
3757 3757 [('A', 'all', None, _('show status of all files')),
3758 3758 ('m', 'modified', None, _('show only modified files')),
3759 3759 ('a', 'added', None, _('show only added files')),
3760 3760 ('r', 'removed', None, _('show only removed files')),
3761 3761 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3762 3762 ('c', 'clean', None, _('show only files without changes')),
3763 3763 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3764 3764 ('i', 'ignored', None, _('show only ignored files')),
3765 3765 ('n', 'no-status', None, _('hide status prefix')),
3766 3766 ('C', 'copies', None, _('show source of copied files')),
3767 3767 ('0', 'print0', None,
3768 3768 _('end filenames with NUL, for use with xargs')),
3769 3769 ('', 'rev', [], _('show difference from revision')),
3770 3770 ('', 'change', '', _('list the changed files of a revision')),
3771 3771 ] + walkopts,
3772 3772 _('[OPTION]... [FILE]...')),
3773 3773 "tag":
3774 3774 (tag,
3775 3775 [('f', 'force', None, _('replace existing tag')),
3776 3776 ('l', 'local', None, _('make the tag local')),
3777 3777 ('r', 'rev', '', _('revision to tag')),
3778 3778 ('', 'remove', None, _('remove a tag')),
3779 3779 # -l/--local is already there, commitopts cannot be used
3780 3780 ('m', 'message', '', _('use <text> as commit message')),
3781 3781 ] + commitopts2,
3782 3782 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3783 3783 "tags": (tags, [], ''),
3784 3784 "tip":
3785 3785 (tip,
3786 3786 [('p', 'patch', None, _('show patch')),
3787 3787 ('g', 'git', None, _('use git extended diff format')),
3788 3788 ] + templateopts,
3789 3789 _('[-p] [-g]')),
3790 3790 "unbundle":
3791 3791 (unbundle,
3792 3792 [('u', 'update', None,
3793 3793 _('update to new branch head if changesets were unbundled'))],
3794 3794 _('[-u] FILE...')),
3795 3795 "^update|up|checkout|co":
3796 3796 (update,
3797 3797 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
3798 3798 ('c', 'check', None, _('check for uncommitted changes')),
3799 3799 ('d', 'date', '', _('tipmost revision matching date')),
3800 3800 ('r', 'rev', '', _('revision'))],
3801 3801 _('[-c] [-C] [-d DATE] [[-r] REV]')),
3802 3802 "verify": (verify, []),
3803 3803 "version": (version_, []),
3804 3804 }
3805 3805
3806 3806 norepo = ("clone init version help debugcommands debugcomplete debugdata"
3807 3807 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3808 3808 optionalrepo = ("identify paths serve showconfig debugancestor")
@@ -1,655 +1,655 b''
1 1 # dirstate.py - working directory tracking for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid
9 9 from i18n import _
10 10 import util, ignore, osutil, parsers
11 11 import struct, os, stat, errno
12 12 import cStringIO
13 13
14 14 _unknown = ('?', 0, 0, 0)
15 15 _format = ">cllll"
16 16 propertycache = util.propertycache
17 17
18 18 def _finddirs(path):
19 19 pos = path.rfind('/')
20 20 while pos != -1:
21 21 yield path[:pos]
22 22 pos = path.rfind('/', 0, pos)
23 23
24 24 def _incdirs(dirs, path):
25 25 for base in _finddirs(path):
26 26 if base in dirs:
27 27 dirs[base] += 1
28 28 return
29 29 dirs[base] = 1
30 30
31 31 def _decdirs(dirs, path):
32 32 for base in _finddirs(path):
33 33 if dirs[base] > 1:
34 34 dirs[base] -= 1
35 35 return
36 36 del dirs[base]
37 37
38 38 class dirstate(object):
39 39
40 40 def __init__(self, opener, ui, root):
41 41 '''Create a new dirstate object.
42 42
43 43 opener is an open()-like callable that can be used to open the
44 44 dirstate file; root is the root of the directory tracked by
45 45 the dirstate.
46 46 '''
47 47 self._opener = opener
48 48 self._root = root
49 49 self._rootdir = os.path.join(root, '')
50 50 self._dirty = False
51 51 self._dirtypl = False
52 52 self._ui = ui
53 53
54 54 @propertycache
55 55 def _map(self):
56 56 '''Return the dirstate contents as a map from filename to
57 57 (state, mode, size, time).'''
58 58 self._read()
59 59 return self._map
60 60
61 61 @propertycache
62 62 def _copymap(self):
63 63 self._read()
64 64 return self._copymap
65 65
66 66 @propertycache
67 67 def _foldmap(self):
68 68 f = {}
69 69 for name in self._map:
70 70 f[os.path.normcase(name)] = name
71 71 return f
72 72
73 73 @propertycache
74 74 def _branch(self):
75 75 try:
76 76 return self._opener("branch").read().strip() or "default"
77 77 except IOError:
78 78 return "default"
79 79
80 80 @propertycache
81 81 def _pl(self):
82 82 try:
83 83 st = self._opener("dirstate").read(40)
84 84 l = len(st)
85 85 if l == 40:
86 86 return st[:20], st[20:40]
87 87 elif l > 0 and l < 40:
88 88 raise util.Abort(_('working directory state appears damaged!'))
89 89 except IOError, err:
90 90 if err.errno != errno.ENOENT:
91 91 raise
92 92 return [nullid, nullid]
93 93
94 94 @propertycache
95 95 def _dirs(self):
96 96 dirs = {}
97 97 for f, s in self._map.iteritems():
98 98 if s[0] != 'r':
99 99 _incdirs(dirs, f)
100 100 return dirs
101 101
102 102 @propertycache
103 103 def _ignore(self):
104 104 files = [self._join('.hgignore')]
105 105 for name, path in self._ui.configitems("ui"):
106 106 if name == 'ignore' or name.startswith('ignore.'):
107 107 files.append(util.expandpath(path))
108 108 return ignore.ignore(self._root, files, self._ui.warn)
109 109
110 110 @propertycache
111 111 def _slash(self):
112 112 return self._ui.configbool('ui', 'slash') and os.sep != '/'
113 113
114 114 @propertycache
115 115 def _checklink(self):
116 116 return util.checklink(self._root)
117 117
118 118 @propertycache
119 119 def _checkexec(self):
120 120 return util.checkexec(self._root)
121 121
122 122 @propertycache
123 123 def _checkcase(self):
124 124 return not util.checkcase(self._join('.hg'))
125 125
126 126 def _join(self, f):
127 127 # much faster than os.path.join()
128 128 # it's safe because f is always a relative path
129 129 return self._rootdir + f
130 130
131 131 def flagfunc(self, fallback):
132 132 if self._checklink:
133 133 if self._checkexec:
134 134 def f(x):
135 135 p = self._join(x)
136 136 if os.path.islink(p):
137 137 return 'l'
138 138 if util.is_exec(p):
139 139 return 'x'
140 140 return ''
141 141 return f
142 142 def f(x):
143 143 if os.path.islink(self._join(x)):
144 144 return 'l'
145 145 if 'x' in fallback(x):
146 146 return 'x'
147 147 return ''
148 148 return f
149 149 if self._checkexec:
150 150 def f(x):
151 151 if 'l' in fallback(x):
152 152 return 'l'
153 153 if util.is_exec(self._join(x)):
154 154 return 'x'
155 155 return ''
156 156 return f
157 157 return fallback
158 158
159 159 def getcwd(self):
160 160 cwd = os.getcwd()
161 161 if cwd == self._root:
162 162 return ''
163 163 # self._root ends with a path separator if self._root is '/' or 'C:\'
164 164 rootsep = self._root
165 165 if not util.endswithsep(rootsep):
166 166 rootsep += os.sep
167 167 if cwd.startswith(rootsep):
168 168 return cwd[len(rootsep):]
169 169 else:
170 170 # we're outside the repo. return an absolute path.
171 171 return cwd
172 172
173 173 def pathto(self, f, cwd=None):
174 174 if cwd is None:
175 175 cwd = self.getcwd()
176 176 path = util.pathto(self._root, cwd, f)
177 177 if self._slash:
178 178 return util.normpath(path)
179 179 return path
180 180
181 181 def __getitem__(self, key):
182 182 '''Return the current state of key (a filename) in the dirstate.
183 183
184 184 States are:
185 185 n normal
186 186 m needs merging
187 187 r marked for removal
188 188 a marked for addition
189 189 ? not tracked
190 190 '''
191 191 return self._map.get(key, ("?",))[0]
192 192
193 193 def __contains__(self, key):
194 194 return key in self._map
195 195
196 196 def __iter__(self):
197 197 for x in sorted(self._map):
198 198 yield x
199 199
200 200 def parents(self):
201 201 return self._pl
202 202
203 203 def branch(self):
204 204 return self._branch
205 205
206 206 def setparents(self, p1, p2=nullid):
207 207 self._dirty = self._dirtypl = True
208 208 self._pl = p1, p2
209 209
210 210 def setbranch(self, branch):
211 211 self._branch = branch
212 212 self._opener("branch", "w").write(branch + '\n')
213 213
214 214 def _read(self):
215 215 self._map = {}
216 216 self._copymap = {}
217 217 try:
218 218 st = self._opener("dirstate").read()
219 219 except IOError, err:
220 220 if err.errno != errno.ENOENT:
221 221 raise
222 222 return
223 223 if not st:
224 224 return
225 225
226 226 p = parsers.parse_dirstate(self._map, self._copymap, st)
227 227 if not self._dirtypl:
228 228 self._pl = p
229 229
230 230 def invalidate(self):
231 231 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
232 232 if a in self.__dict__:
233 233 delattr(self, a)
234 234 self._dirty = False
235 235
236 236 def copy(self, source, dest):
237 237 """Mark dest as a copy of source. Unmark dest if source is None."""
238 238 if source == dest:
239 239 return
240 240 self._dirty = True
241 241 if source is not None:
242 242 self._copymap[dest] = source
243 243 elif dest in self._copymap:
244 244 del self._copymap[dest]
245 245
246 246 def copied(self, file):
247 247 return self._copymap.get(file, None)
248 248
249 249 def copies(self):
250 250 return self._copymap
251 251
252 252 def _droppath(self, f):
253 253 if self[f] not in "?r" and "_dirs" in self.__dict__:
254 254 _decdirs(self._dirs, f)
255 255
256 256 def _addpath(self, f, check=False):
257 257 oldstate = self[f]
258 258 if check or oldstate == "r":
259 259 if '\r' in f or '\n' in f:
260 260 raise util.Abort(
261 261 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
262 262 if f in self._dirs:
263 263 raise util.Abort(_('directory %r already in dirstate') % f)
264 264 # shadows
265 265 for d in _finddirs(f):
266 266 if d in self._dirs:
267 267 break
268 268 if d in self._map and self[d] != 'r':
269 269 raise util.Abort(
270 270 _('file %r in dirstate clashes with %r') % (d, f))
271 271 if oldstate in "?r" and "_dirs" in self.__dict__:
272 272 _incdirs(self._dirs, f)
273 273
274 274 def normal(self, f):
275 275 '''Mark a file normal and clean.'''
276 276 self._dirty = True
277 277 self._addpath(f)
278 278 s = os.lstat(self._join(f))
279 279 self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
280 280 if f in self._copymap:
281 281 del self._copymap[f]
282 282
283 283 def normallookup(self, f):
284 284 '''Mark a file normal, but possibly dirty.'''
285 285 if self._pl[1] != nullid and f in self._map:
286 286 # if there is a merge going on and the file was either
287 287 # in state 'm' or dirty before being removed, restore that state.
288 288 entry = self._map[f]
289 289 if entry[0] == 'r' and entry[2] in (-1, -2):
290 290 source = self._copymap.get(f)
291 291 if entry[2] == -1:
292 292 self.merge(f)
293 293 elif entry[2] == -2:
294 294 self.normaldirty(f)
295 295 if source:
296 296 self.copy(source, f)
297 297 return
298 298 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
299 299 return
300 300 self._dirty = True
301 301 self._addpath(f)
302 302 self._map[f] = ('n', 0, -1, -1)
303 303 if f in self._copymap:
304 304 del self._copymap[f]
305 305
306 306 def normaldirty(self, f):
307 307 '''Mark a file normal, but dirty.'''
308 308 self._dirty = True
309 309 self._addpath(f)
310 310 self._map[f] = ('n', 0, -2, -1)
311 311 if f in self._copymap:
312 312 del self._copymap[f]
313 313
314 314 def add(self, f):
315 315 '''Mark a file added.'''
316 316 self._dirty = True
317 317 self._addpath(f, True)
318 318 self._map[f] = ('a', 0, -1, -1)
319 319 if f in self._copymap:
320 320 del self._copymap[f]
321 321
322 322 def remove(self, f):
323 323 '''Mark a file removed.'''
324 324 self._dirty = True
325 325 self._droppath(f)
326 326 size = 0
327 327 if self._pl[1] != nullid and f in self._map:
328 328 entry = self._map[f]
329 329 if entry[0] == 'm':
330 330 size = -1
331 331 elif entry[0] == 'n' and entry[2] == -2:
332 332 size = -2
333 333 self._map[f] = ('r', 0, size, 0)
334 334 if size == 0 and f in self._copymap:
335 335 del self._copymap[f]
336 336
337 337 def merge(self, f):
338 338 '''Mark a file merged.'''
339 339 self._dirty = True
340 340 s = os.lstat(self._join(f))
341 341 self._addpath(f)
342 342 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
343 343 if f in self._copymap:
344 344 del self._copymap[f]
345 345
346 346 def forget(self, f):
347 347 '''Forget a file.'''
348 348 self._dirty = True
349 349 try:
350 350 self._droppath(f)
351 351 del self._map[f]
352 352 except KeyError:
353 353 self._ui.warn(_("not in dirstate: %s\n") % f)
354 354
355 355 def _normalize(self, path, knownpath):
356 356 norm_path = os.path.normcase(path)
357 357 fold_path = self._foldmap.get(norm_path, None)
358 358 if fold_path is None:
359 359 if knownpath or not os.path.exists(os.path.join(self._root, path)):
360 360 fold_path = path
361 361 else:
362 362 fold_path = self._foldmap.setdefault(norm_path,
363 363 util.fspath(path, self._root))
364 364 return fold_path
365 365
366 366 def clear(self):
367 367 self._map = {}
368 368 if "_dirs" in self.__dict__:
369 delattr(self, "_dirs");
369 delattr(self, "_dirs")
370 370 self._copymap = {}
371 371 self._pl = [nullid, nullid]
372 372 self._dirty = True
373 373
374 374 def rebuild(self, parent, files):
375 375 self.clear()
376 376 for f in files:
377 377 if 'x' in files.flags(f):
378 378 self._map[f] = ('n', 0777, -1, 0)
379 379 else:
380 380 self._map[f] = ('n', 0666, -1, 0)
381 381 self._pl = (parent, nullid)
382 382 self._dirty = True
383 383
384 384 def write(self):
385 385 if not self._dirty:
386 386 return
387 387 st = self._opener("dirstate", "w", atomictemp=True)
388 388
389 389 # use the modification time of the newly created temporary file as the
390 390 # filesystem's notion of 'now'
391 391 now = int(util.fstat(st).st_mtime)
392 392
393 393 cs = cStringIO.StringIO()
394 394 copymap = self._copymap
395 395 pack = struct.pack
396 396 write = cs.write
397 397 write("".join(self._pl))
398 398 for f, e in self._map.iteritems():
399 399 if f in copymap:
400 400 f = "%s\0%s" % (f, copymap[f])
401 401
402 402 if e[0] == 'n' and e[3] == now:
403 403 # The file was last modified "simultaneously" with the current
404 404 # write to dirstate (i.e. within the same second for file-
405 405 # systems with a granularity of 1 sec). This commonly happens
406 406 # for at least a couple of files on 'update'.
407 407 # The user could change the file without changing its size
408 408 # within the same second. Invalidate the file's stat data in
409 409 # dirstate, forcing future 'status' calls to compare the
410 410 # contents of the file. This prevents mistakenly treating such
411 411 # files as clean.
412 412 e = (e[0], 0, -1, -1) # mark entry as 'unset'
413 413
414 414 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
415 415 write(e)
416 416 write(f)
417 417 st.write(cs.getvalue())
418 418 st.rename()
419 419 self._dirty = self._dirtypl = False
420 420
421 421 def _dirignore(self, f):
422 422 if f == '.':
423 423 return False
424 424 if self._ignore(f):
425 425 return True
426 426 for p in _finddirs(f):
427 427 if self._ignore(p):
428 428 return True
429 429 return False
430 430
431 431 def walk(self, match, subrepos, unknown, ignored):
432 432 '''
433 433 Walk recursively through the directory tree, finding all files
434 434 matched by match.
435 435
436 436 Return a dict mapping filename to stat-like object (either
437 437 mercurial.osutil.stat instance or return value of os.stat()).
438 438 '''
439 439
440 440 def fwarn(f, msg):
441 441 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
442 442 return False
443 443
444 444 def badtype(mode):
445 445 kind = _('unknown')
446 446 if stat.S_ISCHR(mode):
447 447 kind = _('character device')
448 448 elif stat.S_ISBLK(mode):
449 449 kind = _('block device')
450 450 elif stat.S_ISFIFO(mode):
451 451 kind = _('fifo')
452 452 elif stat.S_ISSOCK(mode):
453 453 kind = _('socket')
454 454 elif stat.S_ISDIR(mode):
455 455 kind = _('directory')
456 456 return _('unsupported file type (type is %s)') % kind
457 457
458 458 ignore = self._ignore
459 459 dirignore = self._dirignore
460 460 if ignored:
461 461 ignore = util.never
462 462 dirignore = util.never
463 463 elif not unknown:
464 464 # if unknown and ignored are False, skip step 2
465 465 ignore = util.always
466 466 dirignore = util.always
467 467
468 468 matchfn = match.matchfn
469 469 badfn = match.bad
470 470 dmap = self._map
471 471 normpath = util.normpath
472 472 listdir = osutil.listdir
473 473 lstat = os.lstat
474 474 getkind = stat.S_IFMT
475 475 dirkind = stat.S_IFDIR
476 476 regkind = stat.S_IFREG
477 477 lnkkind = stat.S_IFLNK
478 478 join = self._join
479 479 work = []
480 480 wadd = work.append
481 481
482 482 if self._checkcase:
483 483 normalize = self._normalize
484 484 else:
485 485 normalize = lambda x, y: x
486 486
487 487 exact = skipstep3 = False
488 488 if matchfn == match.exact: # match.exact
489 489 exact = True
490 490 dirignore = util.always # skip step 2
491 491 elif match.files() and not match.anypats(): # match.match, no patterns
492 492 skipstep3 = True
493 493
494 494 files = set(match.files())
495 495 if not files or '.' in files:
496 496 files = ['']
497 497 results = dict.fromkeys(subrepos)
498 498 results['.hg'] = None
499 499
500 500 # step 1: find all explicit files
501 501 for ff in sorted(files):
502 502 nf = normalize(normpath(ff), False)
503 503 if nf in results:
504 504 continue
505 505
506 506 try:
507 507 st = lstat(join(nf))
508 508 kind = getkind(st.st_mode)
509 509 if kind == dirkind:
510 510 skipstep3 = False
511 511 if nf in dmap:
512 512 #file deleted on disk but still in dirstate
513 513 results[nf] = None
514 514 match.dir(nf)
515 515 if not dirignore(nf):
516 516 wadd(nf)
517 517 elif kind == regkind or kind == lnkkind:
518 518 results[nf] = st
519 519 else:
520 520 badfn(ff, badtype(kind))
521 521 if nf in dmap:
522 522 results[nf] = None
523 523 except OSError, inst:
524 524 if nf in dmap: # does it exactly match a file?
525 525 results[nf] = None
526 526 else: # does it match a directory?
527 527 prefix = nf + "/"
528 528 for fn in dmap:
529 529 if fn.startswith(prefix):
530 530 match.dir(nf)
531 531 skipstep3 = False
532 532 break
533 533 else:
534 534 badfn(ff, inst.strerror)
535 535
536 536 # step 2: visit subdirectories
537 537 while work:
538 538 nd = work.pop()
539 539 skip = None
540 540 if nd == '.':
541 541 nd = ''
542 542 else:
543 543 skip = '.hg'
544 544 try:
545 545 entries = listdir(join(nd), stat=True, skip=skip)
546 546 except OSError, inst:
547 547 if inst.errno == errno.EACCES:
548 548 fwarn(nd, inst.strerror)
549 549 continue
550 550 raise
551 551 for f, kind, st in entries:
552 552 nf = normalize(nd and (nd + "/" + f) or f, True)
553 553 if nf not in results:
554 554 if kind == dirkind:
555 555 if not ignore(nf):
556 556 match.dir(nf)
557 557 wadd(nf)
558 558 if nf in dmap and matchfn(nf):
559 559 results[nf] = None
560 560 elif kind == regkind or kind == lnkkind:
561 561 if nf in dmap:
562 562 if matchfn(nf):
563 563 results[nf] = st
564 564 elif matchfn(nf) and not ignore(nf):
565 565 results[nf] = st
566 566 elif nf in dmap and matchfn(nf):
567 567 results[nf] = None
568 568
569 569 # step 3: report unseen items in the dmap hash
570 570 if not skipstep3 and not exact:
571 571 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
572 572 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
573 573 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
574 574 st = None
575 575 results[nf] = st
576 576 for s in subrepos:
577 577 del results[s]
578 578 del results['.hg']
579 579 return results
580 580
581 581 def status(self, match, subrepos, ignored, clean, unknown):
582 582 '''Determine the status of the working copy relative to the
583 583 dirstate and return a tuple of lists (unsure, modified, added,
584 584 removed, deleted, unknown, ignored, clean), where:
585 585
586 586 unsure:
587 587 files that might have been modified since the dirstate was
588 588 written, but need to be read to be sure (size is the same
589 589 but mtime differs)
590 590 modified:
591 591 files that have definitely been modified since the dirstate
592 592 was written (different size or mode)
593 593 added:
594 594 files that have been explicitly added with hg add
595 595 removed:
596 596 files that have been explicitly removed with hg remove
597 597 deleted:
598 598 files that have been deleted through other means ("missing")
599 599 unknown:
600 600 files not in the dirstate that are not ignored
601 601 ignored:
602 602 files not in the dirstate that are ignored
603 603 (by _dirignore())
604 604 clean:
605 605 files that have definitely not been modified since the
606 606 dirstate was written
607 607 '''
608 608 listignored, listclean, listunknown = ignored, clean, unknown
609 609 lookup, modified, added, unknown, ignored = [], [], [], [], []
610 610 removed, deleted, clean = [], [], []
611 611
612 612 dmap = self._map
613 613 ladd = lookup.append # aka "unsure"
614 614 madd = modified.append
615 615 aadd = added.append
616 616 uadd = unknown.append
617 617 iadd = ignored.append
618 618 radd = removed.append
619 619 dadd = deleted.append
620 620 cadd = clean.append
621 621
622 622 for fn, st in self.walk(match, subrepos, listunknown,
623 623 listignored).iteritems():
624 624 if fn not in dmap:
625 625 if (listignored or match.exact(fn)) and self._dirignore(fn):
626 626 if listignored:
627 627 iadd(fn)
628 628 elif listunknown:
629 629 uadd(fn)
630 630 continue
631 631
632 632 state, mode, size, time = dmap[fn]
633 633
634 634 if not st and state in "nma":
635 635 dadd(fn)
636 636 elif state == 'n':
637 637 if (size >= 0 and
638 638 (size != st.st_size
639 639 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
640 640 or size == -2
641 641 or fn in self._copymap):
642 642 madd(fn)
643 643 elif time != int(st.st_mtime):
644 644 ladd(fn)
645 645 elif listclean:
646 646 cadd(fn)
647 647 elif state == 'm':
648 648 madd(fn)
649 649 elif state == 'a':
650 650 aadd(fn)
651 651 elif state == 'r':
652 652 radd(fn)
653 653
654 654 return (lookup, modified, added, removed, deleted, unknown, ignored,
655 655 clean)
@@ -1,727 +1,727 b''
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import os, mimetypes, re, cgi, copy
9 9 import webutil
10 10 from mercurial import error, archival, templater, templatefilters
11 11 from mercurial.node import short, hex
12 12 from mercurial.util import binary
13 13 from common import paritygen, staticfile, get_contact, ErrorResponse
14 14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
15 15 from mercurial import graphmod
16 16
17 17 # __all__ is populated with the allowed commands. Be sure to add to it if
18 18 # you're adding a new command, or the new command won't work.
19 19
20 20 __all__ = [
21 21 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
22 22 'manifest', 'tags', 'branches', 'summary', 'filediff', 'diff', 'annotate',
23 23 'filelog', 'archive', 'static', 'graph',
24 24 ]
25 25
26 26 def log(web, req, tmpl):
27 27 if 'file' in req.form and req.form['file'][0]:
28 28 return filelog(web, req, tmpl)
29 29 else:
30 30 return changelog(web, req, tmpl)
31 31
32 32 def rawfile(web, req, tmpl):
33 33 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
34 34 if not path:
35 35 content = manifest(web, req, tmpl)
36 36 req.respond(HTTP_OK, web.ctype)
37 37 return content
38 38
39 39 try:
40 40 fctx = webutil.filectx(web.repo, req)
41 41 except error.LookupError, inst:
42 42 try:
43 43 content = manifest(web, req, tmpl)
44 44 req.respond(HTTP_OK, web.ctype)
45 45 return content
46 46 except ErrorResponse:
47 47 raise inst
48 48
49 49 path = fctx.path()
50 50 text = fctx.data()
51 51 mt = mimetypes.guess_type(path)[0]
52 52 if mt is None:
53 53 mt = binary(text) and 'application/octet-stream' or 'text/plain'
54 54
55 55 req.respond(HTTP_OK, mt, path, len(text))
56 56 return [text]
57 57
58 58 def _filerevision(web, tmpl, fctx):
59 59 f = fctx.path()
60 60 text = fctx.data()
61 61 parity = paritygen(web.stripecount)
62 62
63 63 if binary(text):
64 64 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
65 65 text = '(binary:%s)' % mt
66 66
67 67 def lines():
68 68 for lineno, t in enumerate(text.splitlines(True)):
69 69 yield {"line": t,
70 70 "lineid": "l%d" % (lineno + 1),
71 71 "linenumber": "% 6d" % (lineno + 1),
72 72 "parity": parity.next()}
73 73
74 74 return tmpl("filerevision",
75 75 file=f,
76 76 path=webutil.up(f),
77 77 text=lines(),
78 78 rev=fctx.rev(),
79 79 node=hex(fctx.node()),
80 80 author=fctx.user(),
81 81 date=fctx.date(),
82 82 desc=fctx.description(),
83 83 branch=webutil.nodebranchnodefault(fctx),
84 84 parent=webutil.parents(fctx),
85 85 child=webutil.children(fctx),
86 86 rename=webutil.renamelink(fctx),
87 87 permissions=fctx.manifest().flags(f))
88 88
89 89 def file(web, req, tmpl):
90 90 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
91 91 if not path:
92 92 return manifest(web, req, tmpl)
93 93 try:
94 94 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
95 95 except error.LookupError, inst:
96 96 try:
97 97 return manifest(web, req, tmpl)
98 98 except ErrorResponse:
99 99 raise inst
100 100
101 101 def _search(web, req, tmpl):
102 102
103 103 query = req.form['rev'][0]
104 104 revcount = web.maxchanges
105 105 if 'revcount' in req.form:
106 106 revcount = int(req.form.get('revcount', [revcount])[0])
107 107 tmpl.defaults['sessionvars']['revcount'] = revcount
108 108
109 109 lessvars = copy.copy(tmpl.defaults['sessionvars'])
110 110 lessvars['revcount'] = revcount / 2
111 111 lessvars['rev'] = query
112 112 morevars = copy.copy(tmpl.defaults['sessionvars'])
113 113 morevars['revcount'] = revcount * 2
114 114 morevars['rev'] = query
115 115
116 116 def changelist(**map):
117 117 cl = web.repo.changelog
118 118 count = 0
119 119 qw = query.lower().split()
120 120
121 121 def revgen():
122 122 for i in xrange(len(cl) - 1, 0, -100):
123 123 l = []
124 124 for j in xrange(max(0, i - 100), i + 1):
125 125 ctx = web.repo[j]
126 126 l.append(ctx)
127 127 l.reverse()
128 128 for e in l:
129 129 yield e
130 130
131 131 for ctx in revgen():
132 132 miss = 0
133 133 for q in qw:
134 134 if not (q in ctx.user().lower() or
135 135 q in ctx.description().lower() or
136 136 q in " ".join(ctx.files()).lower()):
137 137 miss = 1
138 138 break
139 139 if miss:
140 140 continue
141 141
142 142 count += 1
143 143 n = ctx.node()
144 144 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
145 145 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
146 146
147 147 yield tmpl('searchentry',
148 148 parity=parity.next(),
149 149 author=ctx.user(),
150 150 parent=webutil.parents(ctx),
151 151 child=webutil.children(ctx),
152 152 changelogtag=showtags,
153 153 desc=ctx.description(),
154 154 date=ctx.date(),
155 155 files=files,
156 156 rev=ctx.rev(),
157 157 node=hex(n),
158 158 tags=webutil.nodetagsdict(web.repo, n),
159 159 inbranch=webutil.nodeinbranch(web.repo, ctx),
160 160 branches=webutil.nodebranchdict(web.repo, ctx))
161 161
162 162 if count >= revcount:
163 163 break
164 164
165 165 cl = web.repo.changelog
166 166 parity = paritygen(web.stripecount)
167 167
168 168 return tmpl('search', query=query, node=hex(cl.tip()),
169 169 entries=changelist, archives=web.archivelist("tip"),
170 170 morevars=morevars, lessvars=lessvars)
171 171
172 172 def changelog(web, req, tmpl, shortlog=False):
173 173
174 174 if 'node' in req.form:
175 175 ctx = webutil.changectx(web.repo, req)
176 176 else:
177 177 if 'rev' in req.form:
178 178 hi = req.form['rev'][0]
179 179 else:
180 180 hi = len(web.repo) - 1
181 181 try:
182 182 ctx = web.repo[hi]
183 183 except error.RepoError:
184 184 return _search(web, req, tmpl) # XXX redirect to 404 page?
185 185
186 186 def changelist(limit=0, **map):
187 187 l = [] # build a list in forward order for efficiency
188 188 for i in xrange(start, end):
189 189 ctx = web.repo[i]
190 190 n = ctx.node()
191 191 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
192 192 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
193 193
194 194 l.insert(0, {"parity": parity.next(),
195 195 "author": ctx.user(),
196 196 "parent": webutil.parents(ctx, i - 1),
197 197 "child": webutil.children(ctx, i + 1),
198 198 "changelogtag": showtags,
199 199 "desc": ctx.description(),
200 200 "date": ctx.date(),
201 201 "files": files,
202 202 "rev": i,
203 203 "node": hex(n),
204 204 "tags": webutil.nodetagsdict(web.repo, n),
205 205 "inbranch": webutil.nodeinbranch(web.repo, ctx),
206 206 "branches": webutil.nodebranchdict(web.repo, ctx)
207 207 })
208 208
209 209 if limit > 0:
210 210 l = l[:limit]
211 211
212 212 for e in l:
213 213 yield e
214 214
215 215 revcount = shortlog and web.maxshortchanges or web.maxchanges
216 216 if 'revcount' in req.form:
217 217 revcount = int(req.form.get('revcount', [revcount])[0])
218 218 tmpl.defaults['sessionvars']['revcount'] = revcount
219 219
220 220 lessvars = copy.copy(tmpl.defaults['sessionvars'])
221 221 lessvars['revcount'] = revcount / 2
222 222 morevars = copy.copy(tmpl.defaults['sessionvars'])
223 223 morevars['revcount'] = revcount * 2
224 224
225 225 cl = web.repo.changelog
226 226 count = len(cl)
227 227 pos = ctx.rev()
228 228 start = max(0, pos - revcount + 1)
229 229 end = min(count, start + revcount)
230 230 pos = end - 1
231 231 parity = paritygen(web.stripecount, offset=start - end)
232 232
233 233 changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx)
234 234
235 235 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
236 236 node=hex(ctx.node()), rev=pos, changesets=count,
237 237 entries=lambda **x: changelist(limit=0,**x),
238 238 latestentry=lambda **x: changelist(limit=1,**x),
239 239 archives=web.archivelist("tip"), revcount=revcount,
240 240 morevars=morevars, lessvars=lessvars)
241 241
242 242 def shortlog(web, req, tmpl):
243 243 return changelog(web, req, tmpl, shortlog = True)
244 244
245 245 def changeset(web, req, tmpl):
246 246 ctx = webutil.changectx(web.repo, req)
247 247 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
248 248 showbranch = webutil.nodebranchnodefault(ctx)
249 249
250 250 files = []
251 251 parity = paritygen(web.stripecount)
252 252 for f in ctx.files():
253 253 template = f in ctx and 'filenodelink' or 'filenolink'
254 254 files.append(tmpl(template,
255 255 node=ctx.hex(), file=f,
256 256 parity=parity.next()))
257 257
258 258 parity = paritygen(web.stripecount)
259 259 style = web.config('web', 'style', 'paper')
260 260 if 'style' in req.form:
261 261 style = req.form['style'][0]
262 262
263 263 diffs = webutil.diffs(web.repo, tmpl, ctx, None, parity, style)
264 264 return tmpl('changeset',
265 265 diff=diffs,
266 266 rev=ctx.rev(),
267 267 node=ctx.hex(),
268 268 parent=webutil.parents(ctx),
269 269 child=webutil.children(ctx),
270 270 changesettag=showtags,
271 271 changesetbranch=showbranch,
272 272 author=ctx.user(),
273 273 desc=ctx.description(),
274 274 date=ctx.date(),
275 275 files=files,
276 276 archives=web.archivelist(ctx.hex()),
277 277 tags=webutil.nodetagsdict(web.repo, ctx.node()),
278 278 branch=webutil.nodebranchnodefault(ctx),
279 279 inbranch=webutil.nodeinbranch(web.repo, ctx),
280 280 branches=webutil.nodebranchdict(web.repo, ctx))
281 281
282 282 rev = changeset
283 283
284 284 def manifest(web, req, tmpl):
285 285 ctx = webutil.changectx(web.repo, req)
286 286 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
287 287 mf = ctx.manifest()
288 288 node = ctx.node()
289 289
290 290 files = {}
291 291 dirs = {}
292 292 parity = paritygen(web.stripecount)
293 293
294 294 if path and path[-1] != "/":
295 295 path += "/"
296 296 l = len(path)
297 297 abspath = "/" + path
298 298
299 299 for f, n in mf.iteritems():
300 300 if f[:l] != path:
301 301 continue
302 302 remain = f[l:]
303 303 elements = remain.split('/')
304 304 if len(elements) == 1:
305 305 files[remain] = f
306 306 else:
307 307 h = dirs # need to retain ref to dirs (root)
308 308 for elem in elements[0:-1]:
309 309 if elem not in h:
310 310 h[elem] = {}
311 311 h = h[elem]
312 312 if len(h) > 1:
313 313 break
314 314 h[None] = None # denotes files present
315 315
316 316 if mf and not files and not dirs:
317 317 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
318 318
319 319 def filelist(**map):
320 320 for f in sorted(files):
321 321 full = files[f]
322 322
323 323 fctx = ctx.filectx(full)
324 324 yield {"file": full,
325 325 "parity": parity.next(),
326 326 "basename": f,
327 327 "date": fctx.date(),
328 328 "size": fctx.size(),
329 329 "permissions": mf.flags(full)}
330 330
331 331 def dirlist(**map):
332 332 for d in sorted(dirs):
333 333
334 334 emptydirs = []
335 335 h = dirs[d]
336 336 while isinstance(h, dict) and len(h) == 1:
337 337 k, v = h.items()[0]
338 338 if v:
339 339 emptydirs.append(k)
340 340 h = v
341 341
342 342 path = "%s%s" % (abspath, d)
343 343 yield {"parity": parity.next(),
344 344 "path": path,
345 345 "emptydirs": "/".join(emptydirs),
346 346 "basename": d}
347 347
348 348 return tmpl("manifest",
349 349 rev=ctx.rev(),
350 350 node=hex(node),
351 351 path=abspath,
352 352 up=webutil.up(abspath),
353 353 upparity=parity.next(),
354 354 fentries=filelist,
355 355 dentries=dirlist,
356 356 archives=web.archivelist(hex(node)),
357 357 tags=webutil.nodetagsdict(web.repo, node),
358 358 inbranch=webutil.nodeinbranch(web.repo, ctx),
359 359 branches=webutil.nodebranchdict(web.repo, ctx))
360 360
361 361 def tags(web, req, tmpl):
362 362 i = web.repo.tagslist()
363 363 i.reverse()
364 364 parity = paritygen(web.stripecount)
365 365
366 366 def entries(notip=False, limit=0, **map):
367 367 count = 0
368 368 for k, n in i:
369 369 if notip and k == "tip":
370 370 continue
371 371 if limit > 0 and count >= limit:
372 372 continue
373 373 count = count + 1
374 374 yield {"parity": parity.next(),
375 375 "tag": k,
376 376 "date": web.repo[n].date(),
377 377 "node": hex(n)}
378 378
379 379 return tmpl("tags",
380 380 node=hex(web.repo.changelog.tip()),
381 381 entries=lambda **x: entries(False, 0, **x),
382 382 entriesnotip=lambda **x: entries(True, 0, **x),
383 383 latestentry=lambda **x: entries(True, 1, **x))
384 384
385 385 def branches(web, req, tmpl):
386 386 b = web.repo.branchtags()
387 387 tips = (web.repo[n] for t, n in web.repo.branchtags().iteritems())
388 388 heads = web.repo.heads()
389 389 parity = paritygen(web.stripecount)
390 390 sortkey = lambda ctx: ('close' not in ctx.extra(), ctx.rev())
391 391
392 392 def entries(limit, **map):
393 393 count = 0
394 394 for ctx in sorted(tips, key=sortkey, reverse=True):
395 395 if limit > 0 and count >= limit:
396 396 return
397 397 count += 1
398 398 if ctx.node() not in heads:
399 399 status = 'inactive'
400 400 elif not web.repo.branchheads(ctx.branch()):
401 401 status = 'closed'
402 402 else:
403 403 status = 'open'
404 404 yield {'parity': parity.next(),
405 405 'branch': ctx.branch(),
406 406 'status': status,
407 407 'node': ctx.hex(),
408 408 'date': ctx.date()}
409 409
410 410 return tmpl('branches', node=hex(web.repo.changelog.tip()),
411 411 entries=lambda **x: entries(0, **x),
412 412 latestentry=lambda **x: entries(1, **x))
413 413
414 414 def summary(web, req, tmpl):
415 415 i = web.repo.tagslist()
416 416 i.reverse()
417 417
418 418 def tagentries(**map):
419 419 parity = paritygen(web.stripecount)
420 420 count = 0
421 421 for k, n in i:
422 422 if k == "tip": # skip tip
423 423 continue
424 424
425 425 count += 1
426 426 if count > 10: # limit to 10 tags
427 427 break
428 428
429 429 yield tmpl("tagentry",
430 430 parity=parity.next(),
431 431 tag=k,
432 432 node=hex(n),
433 433 date=web.repo[n].date())
434 434
435 435 def branches(**map):
436 436 parity = paritygen(web.stripecount)
437 437
438 438 b = web.repo.branchtags()
439 439 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()]
440 440 for r, n, t in sorted(l):
441 441 yield {'parity': parity.next(),
442 442 'branch': t,
443 443 'node': hex(n),
444 444 'date': web.repo[n].date()}
445 445
446 446 def changelist(**map):
447 447 parity = paritygen(web.stripecount, offset=start - end)
448 448 l = [] # build a list in forward order for efficiency
449 449 for i in xrange(start, end):
450 450 ctx = web.repo[i]
451 451 n = ctx.node()
452 452 hn = hex(n)
453 453
454 454 l.insert(0, tmpl(
455 455 'shortlogentry',
456 456 parity=parity.next(),
457 457 author=ctx.user(),
458 458 desc=ctx.description(),
459 459 date=ctx.date(),
460 460 rev=i,
461 461 node=hn,
462 462 tags=webutil.nodetagsdict(web.repo, n),
463 463 inbranch=webutil.nodeinbranch(web.repo, ctx),
464 464 branches=webutil.nodebranchdict(web.repo, ctx)))
465 465
466 466 yield l
467 467
468 468 cl = web.repo.changelog
469 469 count = len(cl)
470 470 start = max(0, count - web.maxchanges)
471 471 end = min(count, start + web.maxchanges)
472 472
473 473 return tmpl("summary",
474 474 desc=web.config("web", "description", "unknown"),
475 475 owner=get_contact(web.config) or "unknown",
476 476 lastchange=cl.read(cl.tip())[2],
477 477 tags=tagentries,
478 478 branches=branches,
479 479 shortlog=changelist,
480 480 node=hex(cl.tip()),
481 481 archives=web.archivelist("tip"))
482 482
483 483 def filediff(web, req, tmpl):
484 484 fctx, ctx = None, None
485 485 try:
486 486 fctx = webutil.filectx(web.repo, req)
487 487 except LookupError:
488 488 ctx = webutil.changectx(web.repo, req)
489 489 path = webutil.cleanpath(web.repo, req.form['file'][0])
490 490 if path not in ctx.files():
491 491 raise
492 492
493 493 if fctx is not None:
494 494 n = fctx.node()
495 495 path = fctx.path()
496 496 else:
497 497 n = ctx.node()
498 498 # path already defined in except clause
499 499
500 500 parity = paritygen(web.stripecount)
501 501 style = web.config('web', 'style', 'paper')
502 502 if 'style' in req.form:
503 503 style = req.form['style'][0]
504 504
505 505 diffs = webutil.diffs(web.repo, tmpl, fctx or ctx, [path], parity, style)
506 506 rename = fctx and webutil.renamelink(fctx) or []
507 507 ctx = fctx and fctx or ctx
508 508 return tmpl("filediff",
509 509 file=path,
510 510 node=hex(n),
511 511 rev=ctx.rev(),
512 512 date=ctx.date(),
513 513 desc=ctx.description(),
514 514 author=ctx.user(),
515 515 rename=rename,
516 516 branch=webutil.nodebranchnodefault(ctx),
517 517 parent=webutil.parents(ctx),
518 518 child=webutil.children(ctx),
519 519 diff=diffs)
520 520
521 521 diff = filediff
522 522
523 523 def annotate(web, req, tmpl):
524 524 fctx = webutil.filectx(web.repo, req)
525 525 f = fctx.path()
526 526 parity = paritygen(web.stripecount)
527 527
528 528 def annotate(**map):
529 529 last = None
530 530 if binary(fctx.data()):
531 531 mt = (mimetypes.guess_type(fctx.path())[0]
532 532 or 'application/octet-stream')
533 533 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
534 534 '(binary:%s)' % mt)])
535 535 else:
536 536 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
537 537 for lineno, ((f, targetline), l) in lines:
538 538 fnode = f.filenode()
539 539
540 540 if last != fnode:
541 541 last = fnode
542 542
543 543 yield {"parity": parity.next(),
544 544 "node": hex(f.node()),
545 545 "rev": f.rev(),
546 546 "author": f.user(),
547 547 "desc": f.description(),
548 548 "file": f.path(),
549 549 "targetline": targetline,
550 550 "line": l,
551 551 "lineid": "l%d" % (lineno + 1),
552 552 "linenumber": "% 6d" % (lineno + 1)}
553 553
554 554 return tmpl("fileannotate",
555 555 file=f,
556 556 annotate=annotate,
557 557 path=webutil.up(f),
558 558 rev=fctx.rev(),
559 559 node=hex(fctx.node()),
560 560 author=fctx.user(),
561 561 date=fctx.date(),
562 562 desc=fctx.description(),
563 563 rename=webutil.renamelink(fctx),
564 564 branch=webutil.nodebranchnodefault(fctx),
565 565 parent=webutil.parents(fctx),
566 566 child=webutil.children(fctx),
567 567 permissions=fctx.manifest().flags(f))
568 568
569 569 def filelog(web, req, tmpl):
570 570
571 571 try:
572 572 fctx = webutil.filectx(web.repo, req)
573 573 f = fctx.path()
574 574 fl = fctx.filelog()
575 575 except error.LookupError:
576 576 f = webutil.cleanpath(web.repo, req.form['file'][0])
577 577 fl = web.repo.file(f)
578 578 numrevs = len(fl)
579 579 if not numrevs: # file doesn't exist at all
580 580 raise
581 581 rev = webutil.changectx(web.repo, req).rev()
582 582 first = fl.linkrev(0)
583 583 if rev < first: # current rev is from before file existed
584 584 raise
585 585 frev = numrevs - 1
586 586 while fl.linkrev(frev) > rev:
587 587 frev -= 1
588 588 fctx = web.repo.filectx(f, fl.linkrev(frev))
589 589
590 590 revcount = web.maxshortchanges
591 591 if 'revcount' in req.form:
592 592 revcount = int(req.form.get('revcount', [revcount])[0])
593 593 tmpl.defaults['sessionvars']['revcount'] = revcount
594 594
595 595 lessvars = copy.copy(tmpl.defaults['sessionvars'])
596 596 lessvars['revcount'] = revcount / 2
597 597 morevars = copy.copy(tmpl.defaults['sessionvars'])
598 598 morevars['revcount'] = revcount * 2
599 599
600 600 count = fctx.filerev() + 1
601 601 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
602 602 end = min(count, start + revcount) # last rev on this page
603 603 parity = paritygen(web.stripecount, offset=start - end)
604 604
605 605 def entries(limit=0, **map):
606 606 l = []
607 607
608 608 repo = web.repo
609 609 for i in xrange(start, end):
610 610 iterfctx = fctx.filectx(i)
611 611
612 612 l.insert(0, {"parity": parity.next(),
613 613 "filerev": i,
614 614 "file": f,
615 615 "node": hex(iterfctx.node()),
616 616 "author": iterfctx.user(),
617 617 "date": iterfctx.date(),
618 618 "rename": webutil.renamelink(iterfctx),
619 619 "parent": webutil.parents(iterfctx),
620 620 "child": webutil.children(iterfctx),
621 621 "desc": iterfctx.description(),
622 622 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
623 623 "branch": webutil.nodebranchnodefault(iterfctx),
624 624 "inbranch": webutil.nodeinbranch(repo, iterfctx),
625 625 "branches": webutil.nodebranchdict(repo, iterfctx)})
626 626
627 627 if limit > 0:
628 628 l = l[:limit]
629 629
630 630 for e in l:
631 631 yield e
632 632
633 633 nodefunc = lambda x: fctx.filectx(fileid=x)
634 634 nav = webutil.revnavgen(end - 1, revcount, count, nodefunc)
635 635 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
636 636 entries=lambda **x: entries(limit=0, **x),
637 637 latestentry=lambda **x: entries(limit=1, **x),
638 638 revcount=revcount, morevars=morevars, lessvars=lessvars)
639 639
640 640 def archive(web, req, tmpl):
641 641 type_ = req.form.get('type', [None])[0]
642 642 allowed = web.configlist("web", "allow_archive")
643 643 key = req.form['node'][0]
644 644
645 645 if type_ not in web.archives:
646 646 msg = 'Unsupported archive type: %s' % type_
647 647 raise ErrorResponse(HTTP_NOT_FOUND, msg)
648 648
649 649 if not ((type_ in allowed or
650 650 web.configbool("web", "allow" + type_, False))):
651 651 msg = 'Archive type not allowed: %s' % type_
652 652 raise ErrorResponse(HTTP_FORBIDDEN, msg)
653 653
654 654 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
655 655 cnode = web.repo.lookup(key)
656 656 arch_version = key
657 657 if cnode == key or key == 'tip':
658 658 arch_version = short(cnode)
659 659 name = "%s-%s" % (reponame, arch_version)
660 660 mimetype, artype, extension, encoding = web.archive_specs[type_]
661 661 headers = [
662 662 ('Content-Type', mimetype),
663 663 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
664 664 ]
665 665 if encoding:
666 666 headers.append(('Content-Encoding', encoding))
667 667 req.header(headers)
668 668 req.respond(HTTP_OK)
669 669 archival.archive(web.repo, req, cnode, artype, prefix=name)
670 670 return []
671 671
672 672
673 673 def static(web, req, tmpl):
674 674 fname = req.form['file'][0]
675 675 # a repo owner may set web.static in .hg/hgrc to get any file
676 676 # readable by the user running the CGI script
677 677 static = web.config("web", "static", None, untrusted=False)
678 678 if not static:
679 679 tp = web.templatepath or templater.templatepath()
680 680 if isinstance(tp, str):
681 681 tp = [tp]
682 682 static = [os.path.join(p, 'static') for p in tp]
683 683 return [staticfile(static, fname, req)]
684 684
685 685 def graph(web, req, tmpl):
686 686
687 687 rev = webutil.changectx(web.repo, req).rev()
688 688 bg_height = 39
689 689 revcount = web.maxshortchanges
690 690 if 'revcount' in req.form:
691 691 revcount = int(req.form.get('revcount', [revcount])[0])
692 692 tmpl.defaults['sessionvars']['revcount'] = revcount
693 693
694 694 lessvars = copy.copy(tmpl.defaults['sessionvars'])
695 695 lessvars['revcount'] = revcount / 2
696 696 morevars = copy.copy(tmpl.defaults['sessionvars'])
697 697 morevars['revcount'] = revcount * 2
698 698
699 699 max_rev = len(web.repo) - 1
700 700 revcount = min(max_rev, revcount)
701 701 revnode = web.repo.changelog.node(rev)
702 702 revnode_hex = hex(revnode)
703 703 uprev = min(max_rev, rev + revcount)
704 704 downrev = max(0, rev - revcount)
705 705 count = len(web.repo)
706 706 changenav = webutil.revnavgen(rev, revcount, count, web.repo.changectx)
707 707
708 708 dag = graphmod.revisions(web.repo, rev, downrev)
709 709 tree = list(graphmod.colored(dag))
710 canvasheight = (len(tree) + 1) * bg_height - 27;
710 canvasheight = (len(tree) + 1) * bg_height - 27
711 711 data = []
712 712 for (id, type, ctx, vtx, edges) in tree:
713 713 if type != graphmod.CHANGESET:
714 714 continue
715 715 node = short(ctx.node())
716 716 age = templatefilters.age(ctx.date())
717 717 desc = templatefilters.firstline(ctx.description())
718 718 desc = cgi.escape(templatefilters.nonempty(desc))
719 719 user = cgi.escape(templatefilters.person(ctx.user()))
720 720 branch = ctx.branch()
721 721 branch = branch, web.repo.branchtags().get(branch) == ctx.node()
722 722 data.append((node, vtx, edges, desc, user, age, branch, ctx.tags()))
723 723
724 724 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
725 725 lessvars=lessvars, morevars=morevars, downrev=downrev,
726 726 canvasheight=canvasheight, jsdata=data, bg_height=bg_height,
727 727 node=revnode_hex, changenav=changenav)
@@ -1,366 +1,365 b''
1 1 # windows.py - Windows utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import osutil, error
10 10 import errno, msvcrt, os, re, sys, random, subprocess
11 11
12 12 nulldev = 'NUL:'
13 13 umask = 002
14 14
15 15 # wrap osutil.posixfile to provide friendlier exceptions
16 16 def posixfile(name, mode='r', buffering=-1):
17 17 try:
18 18 return osutil.posixfile(name, mode, buffering)
19 19 except WindowsError, err:
20 20 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
21 21 posixfile.__doc__ = osutil.posixfile.__doc__
22 22
23 23 class winstdout(object):
24 24 '''stdout on windows misbehaves if sent through a pipe'''
25 25
26 26 def __init__(self, fp):
27 27 self.fp = fp
28 28
29 29 def __getattr__(self, key):
30 30 return getattr(self.fp, key)
31 31
32 32 def close(self):
33 33 try:
34 34 self.fp.close()
35 35 except: pass
36 36
37 37 def write(self, s):
38 38 try:
39 39 # This is workaround for "Not enough space" error on
40 40 # writing large size of data to console.
41 41 limit = 16000
42 42 l = len(s)
43 43 start = 0
44 self.softspace = 0;
44 self.softspace = 0
45 45 while start < l:
46 46 end = start + limit
47 47 self.fp.write(s[start:end])
48 48 start = end
49 49 except IOError, inst:
50 50 if inst.errno != 0:
51 51 raise
52 52 self.close()
53 53 raise IOError(errno.EPIPE, 'Broken pipe')
54 54
55 55 def flush(self):
56 56 try:
57 57 return self.fp.flush()
58 58 except IOError, inst:
59 59 if inst.errno != errno.EINVAL:
60 60 raise
61 61 self.close()
62 62 raise IOError(errno.EPIPE, 'Broken pipe')
63 63
64 64 sys.stdout = winstdout(sys.stdout)
65 65
66 66 def _is_win_9x():
67 67 '''return true if run on windows 95, 98 or me.'''
68 68 try:
69 69 return sys.getwindowsversion()[3] == 1
70 70 except AttributeError:
71 71 return 'command' in os.environ.get('comspec', '')
72 72
73 73 def openhardlinks():
74 74 return not _is_win_9x() and "win32api" in globals()
75 75
76 76 def system_rcpath():
77 77 try:
78 78 return system_rcpath_win32()
79 79 except:
80 80 return [r'c:\mercurial\mercurial.ini']
81 81
82 82 def user_rcpath():
83 83 '''return os-specific hgrc search path to the user dir'''
84 84 try:
85 85 path = user_rcpath_win32()
86 86 except:
87 87 home = os.path.expanduser('~')
88 88 path = [os.path.join(home, 'mercurial.ini'),
89 89 os.path.join(home, '.hgrc')]
90 90 userprofile = os.environ.get('USERPROFILE')
91 91 if userprofile:
92 92 path.append(os.path.join(userprofile, 'mercurial.ini'))
93 93 path.append(os.path.join(userprofile, '.hgrc'))
94 94 return path
95 95
96 96 def parse_patch_output(output_line):
97 97 """parses the output produced by patch and returns the filename"""
98 98 pf = output_line[14:]
99 99 if pf[0] == '`':
100 100 pf = pf[1:-1] # Remove the quotes
101 101 return pf
102 102
103 103 def sshargs(sshcmd, host, user, port):
104 104 '''Build argument list for ssh or Plink'''
105 105 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
106 106 args = user and ("%s@%s" % (user, host)) or host
107 107 return port and ("%s %s %s" % (args, pflag, port)) or args
108 108
109 109 def testpid(pid):
110 110 '''return False if pid dead, True if running or not known'''
111 111 return True
112 112
113 113 def set_flags(f, l, x):
114 114 pass
115 115
116 116 def set_binary(fd):
117 117 # When run without console, pipes may expose invalid
118 118 # fileno(), usually set to -1.
119 119 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
120 120 msvcrt.setmode(fd.fileno(), os.O_BINARY)
121 121
122 122 def pconvert(path):
123 123 return '/'.join(path.split(os.sep))
124 124
125 125 def localpath(path):
126 126 return path.replace('/', '\\')
127 127
128 128 def normpath(path):
129 129 return pconvert(os.path.normpath(path))
130 130
131 131 def realpath(path):
132 132 '''
133 133 Returns the true, canonical file system path equivalent to the given
134 134 path.
135 135 '''
136 136 # TODO: There may be a more clever way to do this that also handles other,
137 137 # less common file systems.
138 138 return os.path.normpath(os.path.normcase(os.path.realpath(path)))
139 139
140 140 def samestat(s1, s2):
141 141 return False
142 142
143 143 # A sequence of backslashes is special iff it precedes a double quote:
144 144 # - if there's an even number of backslashes, the double quote is not
145 145 # quoted (i.e. it ends the quoted region)
146 146 # - if there's an odd number of backslashes, the double quote is quoted
147 147 # - in both cases, every pair of backslashes is unquoted into a single
148 148 # backslash
149 149 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
150 150 # So, to quote a string, we must surround it in double quotes, double
151 151 # the number of backslashes that preceed double quotes and add another
152 152 # backslash before every double quote (being careful with the double
153 153 # quote we've appended to the end)
154 154 _quotere = None
155 155 def shellquote(s):
156 156 global _quotere
157 157 if _quotere is None:
158 158 _quotere = re.compile(r'(\\*)("|\\$)')
159 159 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
160 160
161 161 def quotecommand(cmd):
162 162 """Build a command string suitable for os.popen* calls."""
163 163 # The extra quotes are needed because popen* runs the command
164 164 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
165 165 return '"' + cmd + '"'
166 166
167 167 def popen(command, mode='r'):
168 168 # Work around "popen spawned process may not write to stdout
169 169 # under windows"
170 170 # http://bugs.python.org/issue1366
171 171 command += " 2> %s" % nulldev
172 172 return os.popen(quotecommand(command), mode)
173 173
174 174 def explain_exit(code):
175 175 return _("exited with status %d") % code, code
176 176
177 177 # if you change this stub into a real check, please try to implement the
178 178 # username and groupname functions above, too.
179 179 def isowner(st):
180 180 return True
181 181
182 182 def find_exe(command):
183 183 '''Find executable for command searching like cmd.exe does.
184 184 If command is a basename then PATH is searched for command.
185 185 PATH isn't searched if command is an absolute or relative path.
186 186 An extension from PATHEXT is found and added if not present.
187 187 If command isn't found None is returned.'''
188 188 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
189 189 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
190 190 if os.path.splitext(command)[1].lower() in pathexts:
191 191 pathexts = ['']
192 192
193 193 def findexisting(pathcommand):
194 194 'Will append extension (if needed) and return existing file'
195 195 for ext in pathexts:
196 196 executable = pathcommand + ext
197 197 if os.path.exists(executable):
198 198 return executable
199 199 return None
200 200
201 201 if os.sep in command:
202 202 return findexisting(command)
203 203
204 204 for path in os.environ.get('PATH', '').split(os.pathsep):
205 205 executable = findexisting(os.path.join(path, command))
206 206 if executable is not None:
207 207 return executable
208 208 return findexisting(os.path.expanduser(os.path.expandvars(command)))
209 209
210 210 def set_signal_handler():
211 211 try:
212 212 set_signal_handler_win32()
213 213 except NameError:
214 214 pass
215 215
216 216 def statfiles(files):
217 217 '''Stat each file in files and yield stat or None if file does not exist.
218 218 Cluster and cache stat per directory to minimize number of OS stat calls.'''
219 219 ncase = os.path.normcase
220 sep = os.sep
221 220 dircache = {} # dirname -> filename -> status | None if file does not exist
222 221 for nf in files:
223 222 nf = ncase(nf)
224 223 dir, base = os.path.split(nf)
225 224 if not dir:
226 225 dir = '.'
227 226 cache = dircache.get(dir, None)
228 227 if cache is None:
229 228 try:
230 229 dmap = dict([(ncase(n), s)
231 230 for n, k, s in osutil.listdir(dir, True)])
232 231 except OSError, err:
233 232 # handle directory not found in Python version prior to 2.5
234 233 # Python <= 2.4 returns native Windows code 3 in errno
235 234 # Python >= 2.5 returns ENOENT and adds winerror field
236 235 # EINVAL is raised if dir is not a directory.
237 236 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
238 237 errno.ENOTDIR):
239 238 raise
240 239 dmap = {}
241 240 cache = dircache.setdefault(dir, dmap)
242 241 yield cache.get(base, None)
243 242
244 243 def getuser():
245 244 '''return name of current user'''
246 245 raise error.Abort(_('user name not available - set USERNAME '
247 246 'environment variable'))
248 247
249 248 def username(uid=None):
250 249 """Return the name of the user with the given uid.
251 250
252 251 If uid is None, return the name of the current user."""
253 252 return None
254 253
255 254 def groupname(gid=None):
256 255 """Return the name of the group with the given gid.
257 256
258 257 If gid is None, return the name of the current group."""
259 258 return None
260 259
261 260 def _removedirs(name):
262 261 """special version of os.removedirs that does not remove symlinked
263 262 directories or junction points if they actually contain files"""
264 263 if osutil.listdir(name):
265 264 return
266 265 os.rmdir(name)
267 266 head, tail = os.path.split(name)
268 267 if not tail:
269 268 head, tail = os.path.split(head)
270 269 while head and tail:
271 270 try:
272 271 if osutil.listdir(head):
273 272 return
274 273 os.rmdir(head)
275 274 except:
276 275 break
277 276 head, tail = os.path.split(head)
278 277
279 278 def unlink(f):
280 279 """unlink and remove the directory if it is empty"""
281 280 os.unlink(f)
282 281 # try removing directories that might now be empty
283 282 try:
284 283 _removedirs(os.path.dirname(f))
285 284 except OSError:
286 285 pass
287 286
288 287 def rename(src, dst):
289 288 '''atomically rename file src to dst, replacing dst if it exists'''
290 289 try:
291 290 os.rename(src, dst)
292 291 except OSError, err: # FIXME: check err (EEXIST ?)
293 292
294 293 # On windows, rename to existing file is not allowed, so we
295 294 # must delete destination first. But if a file is open, unlink
296 295 # schedules it for delete but does not delete it. Rename
297 296 # happens immediately even for open files, so we rename
298 297 # destination to a temporary name, then delete that. Then
299 298 # rename is safe to do.
300 299 # The temporary name is chosen at random to avoid the situation
301 300 # where a file is left lying around from a previous aborted run.
302 301 # The usual race condition this introduces can't be avoided as
303 302 # we need the name to rename into, and not the file itself. Due
304 303 # to the nature of the operation however, any races will at worst
305 304 # lead to the rename failing and the current operation aborting.
306 305
307 306 def tempname(prefix):
308 307 for tries in xrange(10):
309 308 temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
310 309 if not os.path.exists(temp):
311 310 return temp
312 311 raise IOError, (errno.EEXIST, "No usable temporary filename found")
313 312
314 313 temp = tempname(dst)
315 314 os.rename(dst, temp)
316 315 try:
317 316 os.unlink(temp)
318 317 except:
319 318 # Some rude AV-scanners on Windows may cause the unlink to
320 319 # fail. Not aborting here just leaks the temp file, whereas
321 320 # aborting at this point may leave serious inconsistencies.
322 321 # Ideally, we would notify the user here.
323 322 pass
324 323 os.rename(src, dst)
325 324
326 325 def spawndetached(args):
327 326 # No standard library function really spawns a fully detached
328 327 # process under win32 because they allocate pipes or other objects
329 328 # to handle standard streams communications. Passing these objects
330 329 # to the child process requires handle inheritance to be enabled
331 330 # which makes really detached processes impossible.
332 331 class STARTUPINFO:
333 332 dwFlags = subprocess.STARTF_USESHOWWINDOW
334 333 hStdInput = None
335 334 hStdOutput = None
336 335 hStdError = None
337 336 wShowWindow = subprocess.SW_HIDE
338 337
339 338 args = subprocess.list2cmdline(args)
340 339 # Not running the command in shell mode makes python26 hang when
341 340 # writing to hgweb output socket.
342 341 comspec = os.environ.get("COMSPEC", "cmd.exe")
343 342 args = comspec + " /c " + args
344 343 hp, ht, pid, tid = subprocess.CreateProcess(
345 344 None, args,
346 345 # no special security
347 346 None, None,
348 347 # Do not inherit handles
349 348 0,
350 349 # DETACHED_PROCESS
351 350 0x00000008,
352 351 os.environ,
353 352 os.getcwd(),
354 353 STARTUPINFO())
355 354 return pid
356 355
357 356 def gethgcmd():
358 357 return [sys.executable] + sys.argv[:1]
359 358
360 359 try:
361 360 # override functions with win32 versions if possible
362 361 from win32 import *
363 362 except ImportError:
364 363 pass
365 364
366 365 expandglobs = True
General Comments 0
You need to be logged in to leave comments. Login now