##// END OF EJS Templates
docopy: deal with globs on windows in a better way
Alexis S. L. Carvalho -
r4055:e37786b2 default
parent child Browse files
Show More
@@ -1,773 +1,775 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from node import *
10 10 from i18n import gettext as _
11 11 demandload(globals(), 'os sys')
12 12 demandload(globals(), 'mdiff util templater patch')
13 13
14 14 revrangesep = ':'
15 15
16 16 def revpair(repo, revs):
17 17 '''return pair of nodes, given list of revisions. second item can
18 18 be None, meaning use working dir.'''
19 19
20 20 def revfix(repo, val, defval):
21 21 if not val and val != 0 and defval is not None:
22 22 val = defval
23 23 return repo.lookup(val)
24 24
25 25 if not revs:
26 26 return repo.dirstate.parents()[0], None
27 27 end = None
28 28 if len(revs) == 1:
29 29 if revrangesep in revs[0]:
30 30 start, end = revs[0].split(revrangesep, 1)
31 31 start = revfix(repo, start, 0)
32 32 end = revfix(repo, end, repo.changelog.count() - 1)
33 33 else:
34 34 start = revfix(repo, revs[0], None)
35 35 elif len(revs) == 2:
36 36 if revrangesep in revs[0] or revrangesep in revs[1]:
37 37 raise util.Abort(_('too many revisions specified'))
38 38 start = revfix(repo, revs[0], None)
39 39 end = revfix(repo, revs[1], None)
40 40 else:
41 41 raise util.Abort(_('too many revisions specified'))
42 42 return start, end
43 43
44 44 def revrange(repo, revs):
45 45 """Yield revision as strings from a list of revision specifications."""
46 46
47 47 def revfix(repo, val, defval):
48 48 if not val and val != 0 and defval is not None:
49 49 return defval
50 50 return repo.changelog.rev(repo.lookup(val))
51 51
52 52 seen, l = {}, []
53 53 for spec in revs:
54 54 if revrangesep in spec:
55 55 start, end = spec.split(revrangesep, 1)
56 56 start = revfix(repo, start, 0)
57 57 end = revfix(repo, end, repo.changelog.count() - 1)
58 58 step = start > end and -1 or 1
59 59 for rev in xrange(start, end+step, step):
60 60 if rev in seen:
61 61 continue
62 62 seen[rev] = 1
63 63 l.append(rev)
64 64 else:
65 65 rev = revfix(repo, spec, None)
66 66 if rev in seen:
67 67 continue
68 68 seen[rev] = 1
69 69 l.append(rev)
70 70
71 71 return l
72 72
73 73 def make_filename(repo, pat, node,
74 74 total=None, seqno=None, revwidth=None, pathname=None):
75 75 node_expander = {
76 76 'H': lambda: hex(node),
77 77 'R': lambda: str(repo.changelog.rev(node)),
78 78 'h': lambda: short(node),
79 79 }
80 80 expander = {
81 81 '%': lambda: '%',
82 82 'b': lambda: os.path.basename(repo.root),
83 83 }
84 84
85 85 try:
86 86 if node:
87 87 expander.update(node_expander)
88 88 if node and revwidth is not None:
89 89 expander['r'] = (lambda:
90 90 str(repo.changelog.rev(node)).zfill(revwidth))
91 91 if total is not None:
92 92 expander['N'] = lambda: str(total)
93 93 if seqno is not None:
94 94 expander['n'] = lambda: str(seqno)
95 95 if total is not None and seqno is not None:
96 96 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
97 97 if pathname is not None:
98 98 expander['s'] = lambda: os.path.basename(pathname)
99 99 expander['d'] = lambda: os.path.dirname(pathname) or '.'
100 100 expander['p'] = lambda: pathname
101 101
102 102 newname = []
103 103 patlen = len(pat)
104 104 i = 0
105 105 while i < patlen:
106 106 c = pat[i]
107 107 if c == '%':
108 108 i += 1
109 109 c = pat[i]
110 110 c = expander[c]()
111 111 newname.append(c)
112 112 i += 1
113 113 return ''.join(newname)
114 114 except KeyError, inst:
115 115 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
116 116 inst.args[0])
117 117
118 118 def make_file(repo, pat, node=None,
119 119 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
120 120 if not pat or pat == '-':
121 121 return 'w' in mode and sys.stdout or sys.stdin
122 122 if hasattr(pat, 'write') and 'w' in mode:
123 123 return pat
124 124 if hasattr(pat, 'read') and 'r' in mode:
125 125 return pat
126 126 return open(make_filename(repo, pat, node, total, seqno, revwidth,
127 127 pathname),
128 128 mode)
129 129
130 def matchpats(repo, pats=[], opts={}, head=''):
130 def matchpats(repo, pats=[], opts={}, head='', globbed=False):
131 131 cwd = repo.getcwd()
132 132 if not pats and cwd:
133 133 opts['include'] = [os.path.join(cwd, i)
134 134 for i in opts.get('include', [])]
135 135 opts['exclude'] = [os.path.join(cwd, x)
136 136 for x in opts.get('exclude', [])]
137 137 cwd = ''
138 138 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
139 opts.get('exclude'), head)
139 opts.get('exclude'), head, globbed=globbed)
140 140
141 def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None):
142 files, matchfn, anypats = matchpats(repo, pats, opts, head)
141 def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None,
142 globbed=False):
143 files, matchfn, anypats = matchpats(repo, pats, opts, head,
144 globbed=globbed)
143 145 exact = dict.fromkeys(files)
144 146 for src, fn in repo.walk(node=node, files=files, match=matchfn,
145 147 badmatch=badmatch):
146 148 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
147 149
148 150 def findrenames(repo, added=None, removed=None, threshold=0.5):
149 151 if added is None or removed is None:
150 152 added, removed = repo.status()[1:3]
151 153 changes = repo.changelog.read(repo.dirstate.parents()[0])
152 154 mf = repo.manifest.read(changes[0])
153 155 for a in added:
154 156 aa = repo.wread(a)
155 157 bestscore, bestname = None, None
156 158 for r in removed:
157 159 rr = repo.file(r).read(mf[r])
158 160 delta = mdiff.textdiff(aa, rr)
159 161 if len(delta) < len(aa):
160 162 myscore = 1.0 - (float(len(delta)) / len(aa))
161 163 if bestscore is None or myscore > bestscore:
162 164 bestscore, bestname = myscore, r
163 165 if bestname and bestscore >= threshold:
164 166 yield bestname, a, bestscore
165 167
166 168 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
167 169 similarity=None):
168 170 if dry_run is None:
169 171 dry_run = opts.get('dry_run')
170 172 if similarity is None:
171 173 similarity = float(opts.get('similarity') or 0)
172 174 add, remove = [], []
173 175 mapping = {}
174 176 for src, abs, rel, exact in walk(repo, pats, opts):
175 177 if src == 'f' and repo.dirstate.state(abs) == '?':
176 178 add.append(abs)
177 179 mapping[abs] = rel, exact
178 180 if repo.ui.verbose or not exact:
179 181 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
180 182 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
181 183 remove.append(abs)
182 184 mapping[abs] = rel, exact
183 185 if repo.ui.verbose or not exact:
184 186 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
185 187 if not dry_run:
186 188 repo.add(add, wlock=wlock)
187 189 repo.remove(remove, wlock=wlock)
188 190 if similarity > 0:
189 191 for old, new, score in findrenames(repo, add, remove, similarity):
190 192 oldrel, oldexact = mapping[old]
191 193 newrel, newexact = mapping[new]
192 194 if repo.ui.verbose or not oldexact or not newexact:
193 195 repo.ui.status(_('recording removal of %s as rename to %s '
194 196 '(%d%% similar)\n') %
195 197 (oldrel, newrel, score * 100))
196 198 if not dry_run:
197 199 repo.copy(old, new, wlock=wlock)
198 200
199 201 class changeset_printer(object):
200 202 '''show changeset information when templating not requested.'''
201 203
202 204 def __init__(self, ui, repo, patch, brinfo, buffered):
203 205 self.ui = ui
204 206 self.repo = repo
205 207 self.buffered = buffered
206 208 self.patch = patch
207 209 self.brinfo = brinfo
208 210 self.header = {}
209 211 self.hunk = {}
210 212 self.lastheader = None
211 213
212 214 def flush(self, rev):
213 215 if rev in self.header:
214 216 h = self.header[rev]
215 217 if h != self.lastheader:
216 218 self.lastheader = h
217 219 self.ui.write(h)
218 220 del self.header[rev]
219 221 if rev in self.hunk:
220 222 self.ui.write(self.hunk[rev])
221 223 del self.hunk[rev]
222 224 return 1
223 225 return 0
224 226
225 227 def show(self, rev=0, changenode=None, copies=None, **props):
226 228 if self.buffered:
227 229 self.ui.pushbuffer()
228 230 self._show(rev, changenode, copies, props)
229 231 self.hunk[rev] = self.ui.popbuffer()
230 232 else:
231 233 self._show(rev, changenode, copies, props)
232 234
233 235 def _show(self, rev, changenode, copies, props):
234 236 '''show a single changeset or file revision'''
235 237 log = self.repo.changelog
236 238 if changenode is None:
237 239 changenode = log.node(rev)
238 240 elif not rev:
239 241 rev = log.rev(changenode)
240 242
241 243 if self.ui.quiet:
242 244 self.ui.write("%d:%s\n" % (rev, short(changenode)))
243 245 return
244 246
245 247 changes = log.read(changenode)
246 248 date = util.datestr(changes[2])
247 249 extra = changes[5]
248 250 branch = extra.get("branch")
249 251
250 252 hexfunc = self.ui.debugflag and hex or short
251 253
252 254 parents = log.parentrevs(rev)
253 255 if not self.ui.debugflag:
254 256 if parents[1] == nullrev:
255 257 if parents[0] >= rev - 1:
256 258 parents = []
257 259 else:
258 260 parents = [parents[0]]
259 261 parents = [(p, hexfunc(log.node(p))) for p in parents]
260 262
261 263 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
262 264
263 265 if branch:
264 266 branch = util.tolocal(branch)
265 267 self.ui.write(_("branch: %s\n") % branch)
266 268 for tag in self.repo.nodetags(changenode):
267 269 self.ui.write(_("tag: %s\n") % tag)
268 270 for parent in parents:
269 271 self.ui.write(_("parent: %d:%s\n") % parent)
270 272
271 273 if self.brinfo:
272 274 br = self.repo.branchlookup([changenode])
273 275 if br:
274 276 self.ui.write(_("branch: %s\n") % " ".join(br[changenode]))
275 277
276 278 if self.ui.debugflag:
277 279 self.ui.write(_("manifest: %d:%s\n") %
278 280 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
279 281 self.ui.write(_("user: %s\n") % changes[1])
280 282 self.ui.write(_("date: %s\n") % date)
281 283
282 284 if self.ui.debugflag:
283 285 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
284 286 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
285 287 files):
286 288 if value:
287 289 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
288 290 elif changes[3] and self.ui.verbose:
289 291 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
290 292 if copies and self.ui.verbose:
291 293 copies = ['%s (%s)' % c for c in copies]
292 294 self.ui.write(_("copies: %s\n") % ' '.join(copies))
293 295
294 296 if extra and self.ui.debugflag:
295 297 extraitems = extra.items()
296 298 extraitems.sort()
297 299 for key, value in extraitems:
298 300 self.ui.write(_("extra: %s=%s\n")
299 301 % (key, value.encode('string_escape')))
300 302
301 303 description = changes[4].strip()
302 304 if description:
303 305 if self.ui.verbose:
304 306 self.ui.write(_("description:\n"))
305 307 self.ui.write(description)
306 308 self.ui.write("\n\n")
307 309 else:
308 310 self.ui.write(_("summary: %s\n") %
309 311 description.splitlines()[0])
310 312 self.ui.write("\n")
311 313
312 314 self.showpatch(changenode)
313 315
314 316 def showpatch(self, node):
315 317 if self.patch:
316 318 prev = self.repo.changelog.parents(node)[0]
317 319 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui)
318 320 self.ui.write("\n")
319 321
320 322 class changeset_templater(changeset_printer):
321 323 '''format changeset information.'''
322 324
323 325 def __init__(self, ui, repo, patch, brinfo, mapfile, buffered):
324 326 changeset_printer.__init__(self, ui, repo, patch, brinfo, buffered)
325 327 self.t = templater.templater(mapfile, templater.common_filters,
326 328 cache={'parent': '{rev}:{node|short} ',
327 329 'manifest': '{rev}:{node|short}',
328 330 'filecopy': '{name} ({source})'})
329 331
330 332 def use_template(self, t):
331 333 '''set template string to use'''
332 334 self.t.cache['changeset'] = t
333 335
334 336 def _show(self, rev, changenode, copies, props):
335 337 '''show a single changeset or file revision'''
336 338 log = self.repo.changelog
337 339 if changenode is None:
338 340 changenode = log.node(rev)
339 341 elif not rev:
340 342 rev = log.rev(changenode)
341 343
342 344 changes = log.read(changenode)
343 345
344 346 def showlist(name, values, plural=None, **args):
345 347 '''expand set of values.
346 348 name is name of key in template map.
347 349 values is list of strings or dicts.
348 350 plural is plural of name, if not simply name + 's'.
349 351
350 352 expansion works like this, given name 'foo'.
351 353
352 354 if values is empty, expand 'no_foos'.
353 355
354 356 if 'foo' not in template map, return values as a string,
355 357 joined by space.
356 358
357 359 expand 'start_foos'.
358 360
359 361 for each value, expand 'foo'. if 'last_foo' in template
360 362 map, expand it instead of 'foo' for last key.
361 363
362 364 expand 'end_foos'.
363 365 '''
364 366 if plural: names = plural
365 367 else: names = name + 's'
366 368 if not values:
367 369 noname = 'no_' + names
368 370 if noname in self.t:
369 371 yield self.t(noname, **args)
370 372 return
371 373 if name not in self.t:
372 374 if isinstance(values[0], str):
373 375 yield ' '.join(values)
374 376 else:
375 377 for v in values:
376 378 yield dict(v, **args)
377 379 return
378 380 startname = 'start_' + names
379 381 if startname in self.t:
380 382 yield self.t(startname, **args)
381 383 vargs = args.copy()
382 384 def one(v, tag=name):
383 385 try:
384 386 vargs.update(v)
385 387 except (AttributeError, ValueError):
386 388 try:
387 389 for a, b in v:
388 390 vargs[a] = b
389 391 except ValueError:
390 392 vargs[name] = v
391 393 return self.t(tag, **vargs)
392 394 lastname = 'last_' + name
393 395 if lastname in self.t:
394 396 last = values.pop()
395 397 else:
396 398 last = None
397 399 for v in values:
398 400 yield one(v)
399 401 if last is not None:
400 402 yield one(last, tag=lastname)
401 403 endname = 'end_' + names
402 404 if endname in self.t:
403 405 yield self.t(endname, **args)
404 406
405 407 def showbranches(**args):
406 408 branch = changes[5].get("branch")
407 409 if branch:
408 410 branch = util.tolocal(branch)
409 411 return showlist('branch', [branch], plural='branches', **args)
410 412 # add old style branches if requested
411 413 if self.brinfo:
412 414 br = self.repo.branchlookup([changenode])
413 415 if changenode in br:
414 416 return showlist('branch', br[changenode],
415 417 plural='branches', **args)
416 418
417 419 def showparents(**args):
418 420 parents = [[('rev', log.rev(p)), ('node', hex(p))]
419 421 for p in log.parents(changenode)
420 422 if self.ui.debugflag or p != nullid]
421 423 if (not self.ui.debugflag and len(parents) == 1 and
422 424 parents[0][0][1] == rev - 1):
423 425 return
424 426 return showlist('parent', parents, **args)
425 427
426 428 def showtags(**args):
427 429 return showlist('tag', self.repo.nodetags(changenode), **args)
428 430
429 431 def showextras(**args):
430 432 extras = changes[5].items()
431 433 extras.sort()
432 434 for key, value in extras:
433 435 args = args.copy()
434 436 args.update(dict(key=key, value=value))
435 437 yield self.t('extra', **args)
436 438
437 439 def showcopies(**args):
438 440 c = [{'name': x[0], 'source': x[1]} for x in copies]
439 441 return showlist('file_copy', c, plural='file_copies', **args)
440 442
441 443 if self.ui.debugflag:
442 444 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
443 445 def showfiles(**args):
444 446 return showlist('file', files[0], **args)
445 447 def showadds(**args):
446 448 return showlist('file_add', files[1], **args)
447 449 def showdels(**args):
448 450 return showlist('file_del', files[2], **args)
449 451 def showmanifest(**args):
450 452 args = args.copy()
451 453 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
452 454 node=hex(changes[0])))
453 455 return self.t('manifest', **args)
454 456 else:
455 457 def showfiles(**args):
456 458 return showlist('file', changes[3], **args)
457 459 showadds = ''
458 460 showdels = ''
459 461 showmanifest = ''
460 462
461 463 defprops = {
462 464 'author': changes[1],
463 465 'branches': showbranches,
464 466 'date': changes[2],
465 467 'desc': changes[4],
466 468 'file_adds': showadds,
467 469 'file_dels': showdels,
468 470 'files': showfiles,
469 471 'file_copies': showcopies,
470 472 'manifest': showmanifest,
471 473 'node': hex(changenode),
472 474 'parents': showparents,
473 475 'rev': rev,
474 476 'tags': showtags,
475 477 'extras': showextras,
476 478 }
477 479 props = props.copy()
478 480 props.update(defprops)
479 481
480 482 try:
481 483 if self.ui.debugflag and 'header_debug' in self.t:
482 484 key = 'header_debug'
483 485 elif self.ui.quiet and 'header_quiet' in self.t:
484 486 key = 'header_quiet'
485 487 elif self.ui.verbose and 'header_verbose' in self.t:
486 488 key = 'header_verbose'
487 489 elif 'header' in self.t:
488 490 key = 'header'
489 491 else:
490 492 key = ''
491 493 if key:
492 494 h = templater.stringify(self.t(key, **props))
493 495 if self.buffered:
494 496 self.header[rev] = h
495 497 else:
496 498 self.ui.write(h)
497 499 if self.ui.debugflag and 'changeset_debug' in self.t:
498 500 key = 'changeset_debug'
499 501 elif self.ui.quiet and 'changeset_quiet' in self.t:
500 502 key = 'changeset_quiet'
501 503 elif self.ui.verbose and 'changeset_verbose' in self.t:
502 504 key = 'changeset_verbose'
503 505 else:
504 506 key = 'changeset'
505 507 self.ui.write(templater.stringify(self.t(key, **props)))
506 508 self.showpatch(changenode)
507 509 except KeyError, inst:
508 510 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
509 511 inst.args[0]))
510 512 except SyntaxError, inst:
511 513 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
512 514
513 515 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
514 516 """show one changeset using template or regular display.
515 517
516 518 Display format will be the first non-empty hit of:
517 519 1. option 'template'
518 520 2. option 'style'
519 521 3. [ui] setting 'logtemplate'
520 522 4. [ui] setting 'style'
521 523 If all of these values are either the unset or the empty string,
522 524 regular display via changeset_printer() is done.
523 525 """
524 526 # options
525 527 patch = False
526 528 if opts.get('patch'):
527 529 patch = matchfn or util.always
528 530
529 531 br = None
530 532 if opts.get('branches'):
531 533 ui.warn(_("the --branches option is deprecated, "
532 534 "please use 'hg branches' instead\n"))
533 535 br = True
534 536 tmpl = opts.get('template')
535 537 mapfile = None
536 538 if tmpl:
537 539 tmpl = templater.parsestring(tmpl, quoted=False)
538 540 else:
539 541 mapfile = opts.get('style')
540 542 # ui settings
541 543 if not mapfile:
542 544 tmpl = ui.config('ui', 'logtemplate')
543 545 if tmpl:
544 546 tmpl = templater.parsestring(tmpl)
545 547 else:
546 548 mapfile = ui.config('ui', 'style')
547 549
548 550 if tmpl or mapfile:
549 551 if mapfile:
550 552 if not os.path.split(mapfile)[0]:
551 553 mapname = (templater.templatepath('map-cmdline.' + mapfile)
552 554 or templater.templatepath(mapfile))
553 555 if mapname: mapfile = mapname
554 556 try:
555 557 t = changeset_templater(ui, repo, patch, br, mapfile, buffered)
556 558 except SyntaxError, inst:
557 559 raise util.Abort(inst.args[0])
558 560 if tmpl: t.use_template(tmpl)
559 561 return t
560 562 return changeset_printer(ui, repo, patch, br, buffered)
561 563
562 564 def finddate(ui, repo, date):
563 565 """Find the tipmost changeset that matches the given date spec"""
564 566 df = util.matchdate(date + " to " + date)
565 567 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
566 568 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
567 569 results = {}
568 570 for st, rev, fns in changeiter:
569 571 if st == 'add':
570 572 d = get(rev)[2]
571 573 if df(d[0]):
572 574 results[rev] = d
573 575 elif st == 'iter':
574 576 if rev in results:
575 577 ui.status("Found revision %s from %s\n" %
576 578 (rev, util.datestr(results[rev])))
577 579 return str(rev)
578 580
579 581 raise util.Abort(_("revision matching date not found"))
580 582
581 583 def walkchangerevs(ui, repo, pats, change, opts):
582 584 '''Iterate over files and the revs they changed in.
583 585
584 586 Callers most commonly need to iterate backwards over the history
585 587 it is interested in. Doing so has awful (quadratic-looking)
586 588 performance, so we use iterators in a "windowed" way.
587 589
588 590 We walk a window of revisions in the desired order. Within the
589 591 window, we first walk forwards to gather data, then in the desired
590 592 order (usually backwards) to display it.
591 593
592 594 This function returns an (iterator, matchfn) tuple. The iterator
593 595 yields 3-tuples. They will be of one of the following forms:
594 596
595 597 "window", incrementing, lastrev: stepping through a window,
596 598 positive if walking forwards through revs, last rev in the
597 599 sequence iterated over - use to reset state for the current window
598 600
599 601 "add", rev, fns: out-of-order traversal of the given file names
600 602 fns, which changed during revision rev - use to gather data for
601 603 possible display
602 604
603 605 "iter", rev, None: in-order traversal of the revs earlier iterated
604 606 over with "add" - use to display data'''
605 607
606 608 def increasing_windows(start, end, windowsize=8, sizelimit=512):
607 609 if start < end:
608 610 while start < end:
609 611 yield start, min(windowsize, end-start)
610 612 start += windowsize
611 613 if windowsize < sizelimit:
612 614 windowsize *= 2
613 615 else:
614 616 while start > end:
615 617 yield start, min(windowsize, start-end-1)
616 618 start -= windowsize
617 619 if windowsize < sizelimit:
618 620 windowsize *= 2
619 621
620 622 files, matchfn, anypats = matchpats(repo, pats, opts)
621 623 follow = opts.get('follow') or opts.get('follow_first')
622 624
623 625 if repo.changelog.count() == 0:
624 626 return [], matchfn
625 627
626 628 if follow:
627 629 defrange = '%s:0' % repo.changectx().rev()
628 630 else:
629 631 defrange = 'tip:0'
630 632 revs = revrange(repo, opts['rev'] or [defrange])
631 633 wanted = {}
632 634 slowpath = anypats or opts.get('removed')
633 635 fncache = {}
634 636
635 637 if not slowpath and not files:
636 638 # No files, no patterns. Display all revs.
637 639 wanted = dict.fromkeys(revs)
638 640 copies = []
639 641 if not slowpath:
640 642 # Only files, no patterns. Check the history of each file.
641 643 def filerevgen(filelog, node):
642 644 cl_count = repo.changelog.count()
643 645 if node is None:
644 646 last = filelog.count() - 1
645 647 else:
646 648 last = filelog.rev(node)
647 649 for i, window in increasing_windows(last, nullrev):
648 650 revs = []
649 651 for j in xrange(i - window, i + 1):
650 652 n = filelog.node(j)
651 653 revs.append((filelog.linkrev(n),
652 654 follow and filelog.renamed(n)))
653 655 revs.reverse()
654 656 for rev in revs:
655 657 # only yield rev for which we have the changelog, it can
656 658 # happen while doing "hg log" during a pull or commit
657 659 if rev[0] < cl_count:
658 660 yield rev
659 661 def iterfiles():
660 662 for filename in files:
661 663 yield filename, None
662 664 for filename_node in copies:
663 665 yield filename_node
664 666 minrev, maxrev = min(revs), max(revs)
665 667 for file_, node in iterfiles():
666 668 filelog = repo.file(file_)
667 669 # A zero count may be a directory or deleted file, so
668 670 # try to find matching entries on the slow path.
669 671 if filelog.count() == 0:
670 672 slowpath = True
671 673 break
672 674 for rev, copied in filerevgen(filelog, node):
673 675 if rev <= maxrev:
674 676 if rev < minrev:
675 677 break
676 678 fncache.setdefault(rev, [])
677 679 fncache[rev].append(file_)
678 680 wanted[rev] = 1
679 681 if follow and copied:
680 682 copies.append(copied)
681 683 if slowpath:
682 684 if follow:
683 685 raise util.Abort(_('can only follow copies/renames for explicit '
684 686 'file names'))
685 687
686 688 # The slow path checks files modified in every changeset.
687 689 def changerevgen():
688 690 for i, window in increasing_windows(repo.changelog.count()-1,
689 691 nullrev):
690 692 for j in xrange(i - window, i + 1):
691 693 yield j, change(j)[3]
692 694
693 695 for rev, changefiles in changerevgen():
694 696 matches = filter(matchfn, changefiles)
695 697 if matches:
696 698 fncache[rev] = matches
697 699 wanted[rev] = 1
698 700
699 701 class followfilter:
700 702 def __init__(self, onlyfirst=False):
701 703 self.startrev = nullrev
702 704 self.roots = []
703 705 self.onlyfirst = onlyfirst
704 706
705 707 def match(self, rev):
706 708 def realparents(rev):
707 709 if self.onlyfirst:
708 710 return repo.changelog.parentrevs(rev)[0:1]
709 711 else:
710 712 return filter(lambda x: x != nullrev,
711 713 repo.changelog.parentrevs(rev))
712 714
713 715 if self.startrev == nullrev:
714 716 self.startrev = rev
715 717 return True
716 718
717 719 if rev > self.startrev:
718 720 # forward: all descendants
719 721 if not self.roots:
720 722 self.roots.append(self.startrev)
721 723 for parent in realparents(rev):
722 724 if parent in self.roots:
723 725 self.roots.append(rev)
724 726 return True
725 727 else:
726 728 # backwards: all parents
727 729 if not self.roots:
728 730 self.roots.extend(realparents(self.startrev))
729 731 if rev in self.roots:
730 732 self.roots.remove(rev)
731 733 self.roots.extend(realparents(rev))
732 734 return True
733 735
734 736 return False
735 737
736 738 # it might be worthwhile to do this in the iterator if the rev range
737 739 # is descending and the prune args are all within that range
738 740 for rev in opts.get('prune', ()):
739 741 rev = repo.changelog.rev(repo.lookup(rev))
740 742 ff = followfilter()
741 743 stop = min(revs[0], revs[-1])
742 744 for x in xrange(rev, stop-1, -1):
743 745 if ff.match(x) and x in wanted:
744 746 del wanted[x]
745 747
746 748 def iterate():
747 749 if follow and not files:
748 750 ff = followfilter(onlyfirst=opts.get('follow_first'))
749 751 def want(rev):
750 752 if ff.match(rev) and rev in wanted:
751 753 return True
752 754 return False
753 755 else:
754 756 def want(rev):
755 757 return rev in wanted
756 758
757 759 for i, window in increasing_windows(0, len(revs)):
758 760 yield 'window', revs[0] < revs[-1], revs[-1]
759 761 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
760 762 srevs = list(nrevs)
761 763 srevs.sort()
762 764 for rev in srevs:
763 765 fns = fncache.get(rev)
764 766 if not fns:
765 767 def fns_generator():
766 768 for f in change(rev)[3]:
767 769 if matchfn(f):
768 770 yield f
769 771 fns = fns_generator()
770 772 yield 'add', rev, fns
771 773 for rev in nrevs:
772 774 yield 'iter', rev, None
773 775 return iterate(), matchfn
@@ -1,3338 +1,3339 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from node import *
10 10 from i18n import gettext as _
11 11 demandload(globals(), "bisect os re sys signal imp urllib pdb shlex stat")
12 12 demandload(globals(), "fancyopts ui hg util lock revlog bundlerepo")
13 13 demandload(globals(), "difflib patch time help mdiff tempfile")
14 14 demandload(globals(), "traceback errno version atexit socket")
15 15 demandload(globals(), "archival changegroup cmdutil hgweb.server sshserver")
16 16
17 17 class UnknownCommand(Exception):
18 18 """Exception raised if command is not in the command table."""
19 19 class AmbiguousCommand(Exception):
20 20 """Exception raised if command shortcut matches more than one command."""
21 21
22 22 def bail_if_changed(repo):
23 23 modified, added, removed, deleted = repo.status()[:4]
24 24 if modified or added or removed or deleted:
25 25 raise util.Abort(_("outstanding uncommitted changes"))
26 26
27 27 def logmessage(opts):
28 28 """ get the log message according to -m and -l option """
29 29 message = opts['message']
30 30 logfile = opts['logfile']
31 31
32 32 if message and logfile:
33 33 raise util.Abort(_('options --message and --logfile are mutually '
34 34 'exclusive'))
35 35 if not message and logfile:
36 36 try:
37 37 if logfile == '-':
38 38 message = sys.stdin.read()
39 39 else:
40 40 message = open(logfile).read()
41 41 except IOError, inst:
42 42 raise util.Abort(_("can't read commit message '%s': %s") %
43 43 (logfile, inst.strerror))
44 44 return message
45 45
46 46 def setremoteconfig(ui, opts):
47 47 "copy remote options to ui tree"
48 48 if opts.get('ssh'):
49 49 ui.setconfig("ui", "ssh", opts['ssh'])
50 50 if opts.get('remotecmd'):
51 51 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
52 52
53 53 # Commands start here, listed alphabetically
54 54
55 55 def add(ui, repo, *pats, **opts):
56 56 """add the specified files on the next commit
57 57
58 58 Schedule files to be version controlled and added to the repository.
59 59
60 60 The files will be added to the repository at the next commit. To
61 61 undo an add before that, see hg revert.
62 62
63 63 If no names are given, add all files in the repository.
64 64 """
65 65
66 66 names = []
67 67 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
68 68 if exact:
69 69 if ui.verbose:
70 70 ui.status(_('adding %s\n') % rel)
71 71 names.append(abs)
72 72 elif repo.dirstate.state(abs) == '?':
73 73 ui.status(_('adding %s\n') % rel)
74 74 names.append(abs)
75 75 if not opts.get('dry_run'):
76 76 repo.add(names)
77 77
78 78 def addremove(ui, repo, *pats, **opts):
79 79 """add all new files, delete all missing files
80 80
81 81 Add all new files and remove all missing files from the repository.
82 82
83 83 New files are ignored if they match any of the patterns in .hgignore. As
84 84 with add, these changes take effect at the next commit.
85 85
86 86 Use the -s option to detect renamed files. With a parameter > 0,
87 87 this compares every removed file with every added file and records
88 88 those similar enough as renames. This option takes a percentage
89 89 between 0 (disabled) and 100 (files must be identical) as its
90 90 parameter. Detecting renamed files this way can be expensive.
91 91 """
92 92 sim = float(opts.get('similarity') or 0)
93 93 if sim < 0 or sim > 100:
94 94 raise util.Abort(_('similarity must be between 0 and 100'))
95 95 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
96 96
97 97 def annotate(ui, repo, *pats, **opts):
98 98 """show changeset information per file line
99 99
100 100 List changes in files, showing the revision id responsible for each line
101 101
102 102 This command is useful to discover who did a change or when a change took
103 103 place.
104 104
105 105 Without the -a option, annotate will avoid processing files it
106 106 detects as binary. With -a, annotate will generate an annotation
107 107 anyway, probably with undesirable results.
108 108 """
109 109 getdate = util.cachefunc(lambda x: util.datestr(x.date()))
110 110
111 111 if not pats:
112 112 raise util.Abort(_('at least one file name or pattern required'))
113 113
114 114 opmap = [['user', lambda x: ui.shortuser(x.user())],
115 115 ['number', lambda x: str(x.rev())],
116 116 ['changeset', lambda x: short(x.node())],
117 117 ['date', getdate], ['follow', lambda x: x.path()]]
118 118 if (not opts['user'] and not opts['changeset'] and not opts['date']
119 119 and not opts['follow']):
120 120 opts['number'] = 1
121 121
122 122 ctx = repo.changectx(opts['rev'])
123 123
124 124 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
125 125 node=ctx.node()):
126 126 fctx = ctx.filectx(abs)
127 127 if not opts['text'] and util.binary(fctx.data()):
128 128 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
129 129 continue
130 130
131 131 lines = fctx.annotate(follow=opts.get('follow'))
132 132 pieces = []
133 133
134 134 for o, f in opmap:
135 135 if opts[o]:
136 136 l = [f(n) for n, dummy in lines]
137 137 if l:
138 138 m = max(map(len, l))
139 139 pieces.append(["%*s" % (m, x) for x in l])
140 140
141 141 if pieces:
142 142 for p, l in zip(zip(*pieces), lines):
143 143 ui.write("%s: %s" % (" ".join(p), l[1]))
144 144
145 145 def archive(ui, repo, dest, **opts):
146 146 '''create unversioned archive of a repository revision
147 147
148 148 By default, the revision used is the parent of the working
149 149 directory; use "-r" to specify a different revision.
150 150
151 151 To specify the type of archive to create, use "-t". Valid
152 152 types are:
153 153
154 154 "files" (default): a directory full of files
155 155 "tar": tar archive, uncompressed
156 156 "tbz2": tar archive, compressed using bzip2
157 157 "tgz": tar archive, compressed using gzip
158 158 "uzip": zip archive, uncompressed
159 159 "zip": zip archive, compressed using deflate
160 160
161 161 The exact name of the destination archive or directory is given
162 162 using a format string; see "hg help export" for details.
163 163
164 164 Each member added to an archive file has a directory prefix
165 165 prepended. Use "-p" to specify a format string for the prefix.
166 166 The default is the basename of the archive, with suffixes removed.
167 167 '''
168 168
169 169 node = repo.changectx(opts['rev']).node()
170 170 dest = cmdutil.make_filename(repo, dest, node)
171 171 if os.path.realpath(dest) == repo.root:
172 172 raise util.Abort(_('repository root cannot be destination'))
173 173 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
174 174 kind = opts.get('type') or 'files'
175 175 prefix = opts['prefix']
176 176 if dest == '-':
177 177 if kind == 'files':
178 178 raise util.Abort(_('cannot archive plain files to stdout'))
179 179 dest = sys.stdout
180 180 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
181 181 prefix = cmdutil.make_filename(repo, prefix, node)
182 182 archival.archive(repo, dest, node, kind, not opts['no_decode'],
183 183 matchfn, prefix)
184 184
185 185 def backout(ui, repo, rev, **opts):
186 186 '''reverse effect of earlier changeset
187 187
188 188 Commit the backed out changes as a new changeset. The new
189 189 changeset is a child of the backed out changeset.
190 190
191 191 If you back out a changeset other than the tip, a new head is
192 192 created. This head is the parent of the working directory. If
193 193 you back out an old changeset, your working directory will appear
194 194 old after the backout. You should merge the backout changeset
195 195 with another head.
196 196
197 197 The --merge option remembers the parent of the working directory
198 198 before starting the backout, then merges the new head with that
199 199 changeset afterwards. This saves you from doing the merge by
200 200 hand. The result of this merge is not committed, as for a normal
201 201 merge.'''
202 202
203 203 bail_if_changed(repo)
204 204 op1, op2 = repo.dirstate.parents()
205 205 if op2 != nullid:
206 206 raise util.Abort(_('outstanding uncommitted merge'))
207 207 node = repo.lookup(rev)
208 208 p1, p2 = repo.changelog.parents(node)
209 209 if p1 == nullid:
210 210 raise util.Abort(_('cannot back out a change with no parents'))
211 211 if p2 != nullid:
212 212 if not opts['parent']:
213 213 raise util.Abort(_('cannot back out a merge changeset without '
214 214 '--parent'))
215 215 p = repo.lookup(opts['parent'])
216 216 if p not in (p1, p2):
217 217 raise util.Abort(_('%s is not a parent of %s') %
218 218 (short(p), short(node)))
219 219 parent = p
220 220 else:
221 221 if opts['parent']:
222 222 raise util.Abort(_('cannot use --parent on non-merge changeset'))
223 223 parent = p1
224 224 hg.clean(repo, node, show_stats=False)
225 225 revert_opts = opts.copy()
226 226 revert_opts['date'] = None
227 227 revert_opts['all'] = True
228 228 revert_opts['rev'] = hex(parent)
229 229 revert(ui, repo, **revert_opts)
230 230 commit_opts = opts.copy()
231 231 commit_opts['addremove'] = False
232 232 if not commit_opts['message'] and not commit_opts['logfile']:
233 233 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
234 234 commit_opts['force_editor'] = True
235 235 commit(ui, repo, **commit_opts)
236 236 def nice(node):
237 237 return '%d:%s' % (repo.changelog.rev(node), short(node))
238 238 ui.status(_('changeset %s backs out changeset %s\n') %
239 239 (nice(repo.changelog.tip()), nice(node)))
240 240 if op1 != node:
241 241 if opts['merge']:
242 242 ui.status(_('merging with changeset %s\n') % nice(op1))
243 243 n = _lookup(repo, hex(op1))
244 244 hg.merge(repo, n)
245 245 else:
246 246 ui.status(_('the backout changeset is a new head - '
247 247 'do not forget to merge\n'))
248 248 ui.status(_('(use "backout --merge" '
249 249 'if you want to auto-merge)\n'))
250 250
251 251 def branch(ui, repo, label=None):
252 252 """set or show the current branch name
253 253
254 254 With <name>, set the current branch name. Otherwise, show the
255 255 current branch name.
256 256 """
257 257
258 258 if label is not None:
259 259 repo.opener("branch", "w").write(util.fromlocal(label) + '\n')
260 260 else:
261 261 b = util.tolocal(repo.workingctx().branch())
262 262 if b:
263 263 ui.write("%s\n" % b)
264 264
265 265 def branches(ui, repo):
266 266 """list repository named branches
267 267
268 268 List the repository's named branches.
269 269 """
270 270 b = repo.branchtags()
271 271 l = [(-repo.changelog.rev(n), n, t) for t, n in b.items()]
272 272 l.sort()
273 273 for r, n, t in l:
274 274 hexfunc = ui.debugflag and hex or short
275 275 if ui.quiet:
276 276 ui.write("%s\n" % t)
277 277 else:
278 278 t = util.localsub(t, 30)
279 279 t += " " * (30 - util.locallen(t))
280 280 ui.write("%s %s:%s\n" % (t, -r, hexfunc(n)))
281 281
282 282 def bundle(ui, repo, fname, dest=None, **opts):
283 283 """create a changegroup file
284 284
285 285 Generate a compressed changegroup file collecting changesets not
286 286 found in the other repository.
287 287
288 288 If no destination repository is specified the destination is assumed
289 289 to have all the nodes specified by one or more --base parameters.
290 290
291 291 The bundle file can then be transferred using conventional means and
292 292 applied to another repository with the unbundle or pull command.
293 293 This is useful when direct push and pull are not available or when
294 294 exporting an entire repository is undesirable.
295 295
296 296 Applying bundles preserves all changeset contents including
297 297 permissions, copy/rename information, and revision history.
298 298 """
299 299 revs = opts.get('rev') or None
300 300 if revs:
301 301 revs = [repo.lookup(rev) for rev in revs]
302 302 base = opts.get('base')
303 303 if base:
304 304 if dest:
305 305 raise util.Abort(_("--base is incompatible with specifiying "
306 306 "a destination"))
307 307 base = [repo.lookup(rev) for rev in base]
308 308 # create the right base
309 309 # XXX: nodesbetween / changegroup* should be "fixed" instead
310 310 o = []
311 311 has = {nullid: None}
312 312 for n in base:
313 313 has.update(repo.changelog.reachable(n))
314 314 if revs:
315 315 visit = list(revs)
316 316 else:
317 317 visit = repo.changelog.heads()
318 318 seen = {}
319 319 while visit:
320 320 n = visit.pop(0)
321 321 parents = [p for p in repo.changelog.parents(n) if p not in has]
322 322 if len(parents) == 0:
323 323 o.insert(0, n)
324 324 else:
325 325 for p in parents:
326 326 if p not in seen:
327 327 seen[p] = 1
328 328 visit.append(p)
329 329 else:
330 330 setremoteconfig(ui, opts)
331 331 dest = ui.expandpath(dest or 'default-push', dest or 'default')
332 332 other = hg.repository(ui, dest)
333 333 o = repo.findoutgoing(other, force=opts['force'])
334 334
335 335 if revs:
336 336 cg = repo.changegroupsubset(o, revs, 'bundle')
337 337 else:
338 338 cg = repo.changegroup(o, 'bundle')
339 339 changegroup.writebundle(cg, fname, "HG10BZ")
340 340
341 341 def cat(ui, repo, file1, *pats, **opts):
342 342 """output the current or given revision of files
343 343
344 344 Print the specified files as they were at the given revision.
345 345 If no revision is given, the parent of the working directory is used,
346 346 or tip if no revision is checked out.
347 347
348 348 Output may be to a file, in which case the name of the file is
349 349 given using a format string. The formatting rules are the same as
350 350 for the export command, with the following additions:
351 351
352 352 %s basename of file being printed
353 353 %d dirname of file being printed, or '.' if in repo root
354 354 %p root-relative path name of file being printed
355 355 """
356 356 ctx = repo.changectx(opts['rev'])
357 357 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
358 358 ctx.node()):
359 359 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
360 360 fp.write(ctx.filectx(abs).data())
361 361
362 362 def clone(ui, source, dest=None, **opts):
363 363 """make a copy of an existing repository
364 364
365 365 Create a copy of an existing repository in a new directory.
366 366
367 367 If no destination directory name is specified, it defaults to the
368 368 basename of the source.
369 369
370 370 The location of the source is added to the new repository's
371 371 .hg/hgrc file, as the default to be used for future pulls.
372 372
373 373 For efficiency, hardlinks are used for cloning whenever the source
374 374 and destination are on the same filesystem (note this applies only
375 375 to the repository data, not to the checked out files). Some
376 376 filesystems, such as AFS, implement hardlinking incorrectly, but
377 377 do not report errors. In these cases, use the --pull option to
378 378 avoid hardlinking.
379 379
380 380 You can safely clone repositories and checked out files using full
381 381 hardlinks with
382 382
383 383 $ cp -al REPO REPOCLONE
384 384
385 385 which is the fastest way to clone. However, the operation is not
386 386 atomic (making sure REPO is not modified during the operation is
387 387 up to you) and you have to make sure your editor breaks hardlinks
388 388 (Emacs and most Linux Kernel tools do so).
389 389
390 390 If you use the -r option to clone up to a specific revision, no
391 391 subsequent revisions will be present in the cloned repository.
392 392 This option implies --pull, even on local repositories.
393 393
394 394 See pull for valid source format details.
395 395
396 396 It is possible to specify an ssh:// URL as the destination, but no
397 397 .hg/hgrc and working directory will be created on the remote side.
398 398 Look at the help text for the pull command for important details
399 399 about ssh:// URLs.
400 400 """
401 401 setremoteconfig(ui, opts)
402 402 hg.clone(ui, ui.expandpath(source), dest,
403 403 pull=opts['pull'],
404 404 stream=opts['uncompressed'],
405 405 rev=opts['rev'],
406 406 update=not opts['noupdate'])
407 407
408 408 def commit(ui, repo, *pats, **opts):
409 409 """commit the specified files or all outstanding changes
410 410
411 411 Commit changes to the given files into the repository.
412 412
413 413 If a list of files is omitted, all changes reported by "hg status"
414 414 will be committed.
415 415
416 416 If no commit message is specified, the editor configured in your hgrc
417 417 or in the EDITOR environment variable is started to enter a message.
418 418 """
419 419 message = logmessage(opts)
420 420
421 421 if opts['addremove']:
422 422 cmdutil.addremove(repo, pats, opts)
423 423 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
424 424 if pats:
425 425 status = repo.status(files=fns, match=match)
426 426 modified, added, removed, deleted, unknown = status[:5]
427 427 files = modified + added + removed
428 428 slist = None
429 429 for f in fns:
430 430 if f not in files:
431 431 rf = repo.wjoin(f)
432 432 if f in unknown:
433 433 raise util.Abort(_("file %s not tracked!") % rf)
434 434 try:
435 435 mode = os.lstat(rf)[stat.ST_MODE]
436 436 except OSError:
437 437 raise util.Abort(_("file %s not found!") % rf)
438 438 if stat.S_ISDIR(mode):
439 439 name = f + '/'
440 440 if slist is None:
441 441 slist = list(files)
442 442 slist.sort()
443 443 i = bisect.bisect(slist, name)
444 444 if i >= len(slist) or not slist[i].startswith(name):
445 445 raise util.Abort(_("no match under directory %s!")
446 446 % rf)
447 447 elif not stat.S_ISREG(mode):
448 448 raise util.Abort(_("can't commit %s: "
449 449 "unsupported file type!") % rf)
450 450 else:
451 451 files = []
452 452 try:
453 453 repo.commit(files, message, opts['user'], opts['date'], match,
454 454 force_editor=opts.get('force_editor'))
455 455 except ValueError, inst:
456 456 raise util.Abort(str(inst))
457 457
458 458 def docopy(ui, repo, pats, opts, wlock):
459 459 # called with the repo lock held
460 460 #
461 461 # hgsep => pathname that uses "/" to separate directories
462 462 # ossep => pathname that uses os.sep to separate directories
463 463 cwd = repo.getcwd()
464 464 errors = 0
465 465 copied = []
466 466 targets = {}
467 467
468 468 # abs: hgsep
469 469 # rel: ossep
470 470 # return: hgsep
471 471 def okaytocopy(abs, rel, exact):
472 472 reasons = {'?': _('is not managed'),
473 473 'a': _('has been marked for add'),
474 474 'r': _('has been marked for remove')}
475 475 state = repo.dirstate.state(abs)
476 476 reason = reasons.get(state)
477 477 if reason:
478 478 if state == 'a':
479 479 origsrc = repo.dirstate.copied(abs)
480 480 if origsrc is not None:
481 481 return origsrc
482 482 if exact:
483 483 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
484 484 else:
485 485 return abs
486 486
487 487 # origsrc: hgsep
488 488 # abssrc: hgsep
489 489 # relsrc: ossep
490 490 # target: ossep
491 491 def copy(origsrc, abssrc, relsrc, target, exact):
492 492 abstarget = util.canonpath(repo.root, cwd, target)
493 493 reltarget = util.pathto(cwd, abstarget)
494 494 prevsrc = targets.get(abstarget)
495 495 if prevsrc is not None:
496 496 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
497 497 (reltarget, util.localpath(abssrc),
498 498 util.localpath(prevsrc)))
499 499 return
500 500 if (not opts['after'] and os.path.exists(reltarget) or
501 501 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
502 502 if not opts['force']:
503 503 ui.warn(_('%s: not overwriting - file exists\n') %
504 504 reltarget)
505 505 return
506 506 if not opts['after'] and not opts.get('dry_run'):
507 507 os.unlink(reltarget)
508 508 if opts['after']:
509 509 if not os.path.exists(reltarget):
510 510 return
511 511 else:
512 512 targetdir = os.path.dirname(reltarget) or '.'
513 513 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
514 514 os.makedirs(targetdir)
515 515 try:
516 516 restore = repo.dirstate.state(abstarget) == 'r'
517 517 if restore and not opts.get('dry_run'):
518 518 repo.undelete([abstarget], wlock)
519 519 try:
520 520 if not opts.get('dry_run'):
521 521 util.copyfile(relsrc, reltarget)
522 522 restore = False
523 523 finally:
524 524 if restore:
525 525 repo.remove([abstarget], wlock)
526 526 except IOError, inst:
527 527 if inst.errno == errno.ENOENT:
528 528 ui.warn(_('%s: deleted in working copy\n') % relsrc)
529 529 else:
530 530 ui.warn(_('%s: cannot copy - %s\n') %
531 531 (relsrc, inst.strerror))
532 532 errors += 1
533 533 return
534 534 if ui.verbose or not exact:
535 535 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
536 536 targets[abstarget] = abssrc
537 537 if abstarget != origsrc and not opts.get('dry_run'):
538 538 repo.copy(origsrc, abstarget, wlock)
539 539 copied.append((abssrc, relsrc, exact))
540 540
541 541 # pat: ossep
542 542 # dest ossep
543 543 # srcs: list of (hgsep, hgsep, ossep, bool)
544 544 # return: function that takes hgsep and returns ossep
545 545 def targetpathfn(pat, dest, srcs):
546 546 if os.path.isdir(pat):
547 547 abspfx = util.canonpath(repo.root, cwd, pat)
548 548 abspfx = util.localpath(abspfx)
549 549 if destdirexists:
550 550 striplen = len(os.path.split(abspfx)[0])
551 551 else:
552 552 striplen = len(abspfx)
553 553 if striplen:
554 554 striplen += len(os.sep)
555 555 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
556 556 elif destdirexists:
557 557 res = lambda p: os.path.join(dest,
558 558 os.path.basename(util.localpath(p)))
559 559 else:
560 560 res = lambda p: dest
561 561 return res
562 562
563 563 # pat: ossep
564 564 # dest ossep
565 565 # srcs: list of (hgsep, hgsep, ossep, bool)
566 566 # return: function that takes hgsep and returns ossep
567 567 def targetpathafterfn(pat, dest, srcs):
568 568 if util.patkind(pat, None)[0]:
569 569 # a mercurial pattern
570 570 res = lambda p: os.path.join(dest,
571 571 os.path.basename(util.localpath(p)))
572 572 else:
573 573 abspfx = util.canonpath(repo.root, cwd, pat)
574 574 if len(abspfx) < len(srcs[0][0]):
575 575 # A directory. Either the target path contains the last
576 576 # component of the source path or it does not.
577 577 def evalpath(striplen):
578 578 score = 0
579 579 for s in srcs:
580 580 t = os.path.join(dest, util.localpath(s[0])[striplen:])
581 581 if os.path.exists(t):
582 582 score += 1
583 583 return score
584 584
585 585 abspfx = util.localpath(abspfx)
586 586 striplen = len(abspfx)
587 587 if striplen:
588 588 striplen += len(os.sep)
589 589 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
590 590 score = evalpath(striplen)
591 591 striplen1 = len(os.path.split(abspfx)[0])
592 592 if striplen1:
593 593 striplen1 += len(os.sep)
594 594 if evalpath(striplen1) > score:
595 595 striplen = striplen1
596 596 res = lambda p: os.path.join(dest,
597 597 util.localpath(p)[striplen:])
598 598 else:
599 599 # a file
600 600 if destdirexists:
601 601 res = lambda p: os.path.join(dest,
602 602 os.path.basename(util.localpath(p)))
603 603 else:
604 604 res = lambda p: dest
605 605 return res
606 606
607 607
608 pats = list(pats)
608 pats = util.expand_glob(pats)
609 609 if not pats:
610 610 raise util.Abort(_('no source or destination specified'))
611 611 if len(pats) == 1:
612 612 raise util.Abort(_('no destination specified'))
613 613 dest = pats.pop()
614 614 destdirexists = os.path.isdir(dest)
615 615 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
616 616 raise util.Abort(_('with multiple sources, destination must be an '
617 617 'existing directory'))
618 618 if opts['after']:
619 619 tfn = targetpathafterfn
620 620 else:
621 621 tfn = targetpathfn
622 622 copylist = []
623 623 for pat in pats:
624 624 srcs = []
625 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts):
625 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts,
626 globbed=True):
626 627 origsrc = okaytocopy(abssrc, relsrc, exact)
627 628 if origsrc:
628 629 srcs.append((origsrc, abssrc, relsrc, exact))
629 630 if not srcs:
630 631 continue
631 632 copylist.append((tfn(pat, dest, srcs), srcs))
632 633 if not copylist:
633 634 raise util.Abort(_('no files to copy'))
634 635
635 636 for targetpath, srcs in copylist:
636 637 for origsrc, abssrc, relsrc, exact in srcs:
637 638 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
638 639
639 640 if errors:
640 641 ui.warn(_('(consider using --after)\n'))
641 642 return errors, copied
642 643
643 644 def copy(ui, repo, *pats, **opts):
644 645 """mark files as copied for the next commit
645 646
646 647 Mark dest as having copies of source files. If dest is a
647 648 directory, copies are put in that directory. If dest is a file,
648 649 there can only be one source.
649 650
650 651 By default, this command copies the contents of files as they
651 652 stand in the working directory. If invoked with --after, the
652 653 operation is recorded, but no copying is performed.
653 654
654 655 This command takes effect in the next commit. To undo a copy
655 656 before that, see hg revert.
656 657 """
657 658 wlock = repo.wlock(0)
658 659 errs, copied = docopy(ui, repo, pats, opts, wlock)
659 660 return errs
660 661
661 662 def debugancestor(ui, index, rev1, rev2):
662 663 """find the ancestor revision of two revisions in a given index"""
663 664 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
664 665 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
665 666 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
666 667
667 668 def debugcomplete(ui, cmd='', **opts):
668 669 """returns the completion list associated with the given command"""
669 670
670 671 if opts['options']:
671 672 options = []
672 673 otables = [globalopts]
673 674 if cmd:
674 675 aliases, entry = findcmd(ui, cmd)
675 676 otables.append(entry[1])
676 677 for t in otables:
677 678 for o in t:
678 679 if o[0]:
679 680 options.append('-%s' % o[0])
680 681 options.append('--%s' % o[1])
681 682 ui.write("%s\n" % "\n".join(options))
682 683 return
683 684
684 685 clist = findpossible(ui, cmd).keys()
685 686 clist.sort()
686 687 ui.write("%s\n" % "\n".join(clist))
687 688
688 689 def debugrebuildstate(ui, repo, rev=None):
689 690 """rebuild the dirstate as it would look like for the given revision"""
690 691 if not rev:
691 692 rev = repo.changelog.tip()
692 693 else:
693 694 rev = repo.lookup(rev)
694 695 change = repo.changelog.read(rev)
695 696 n = change[0]
696 697 files = repo.manifest.read(n)
697 698 wlock = repo.wlock()
698 699 repo.dirstate.rebuild(rev, files)
699 700
700 701 def debugcheckstate(ui, repo):
701 702 """validate the correctness of the current dirstate"""
702 703 parent1, parent2 = repo.dirstate.parents()
703 704 repo.dirstate.read()
704 705 dc = repo.dirstate.map
705 706 keys = dc.keys()
706 707 keys.sort()
707 708 m1n = repo.changelog.read(parent1)[0]
708 709 m2n = repo.changelog.read(parent2)[0]
709 710 m1 = repo.manifest.read(m1n)
710 711 m2 = repo.manifest.read(m2n)
711 712 errors = 0
712 713 for f in dc:
713 714 state = repo.dirstate.state(f)
714 715 if state in "nr" and f not in m1:
715 716 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
716 717 errors += 1
717 718 if state in "a" and f in m1:
718 719 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
719 720 errors += 1
720 721 if state in "m" and f not in m1 and f not in m2:
721 722 ui.warn(_("%s in state %s, but not in either manifest\n") %
722 723 (f, state))
723 724 errors += 1
724 725 for f in m1:
725 726 state = repo.dirstate.state(f)
726 727 if state not in "nrm":
727 728 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
728 729 errors += 1
729 730 if errors:
730 731 error = _(".hg/dirstate inconsistent with current parent's manifest")
731 732 raise util.Abort(error)
732 733
733 734 def showconfig(ui, repo, *values, **opts):
734 735 """show combined config settings from all hgrc files
735 736
736 737 With no args, print names and values of all config items.
737 738
738 739 With one arg of the form section.name, print just the value of
739 740 that config item.
740 741
741 742 With multiple args, print names and values of all config items
742 743 with matching section names."""
743 744
744 745 untrusted = bool(opts.get('untrusted'))
745 746 if values:
746 747 if len([v for v in values if '.' in v]) > 1:
747 748 raise util.Abort(_('only one config item permitted'))
748 749 for section, name, value in ui.walkconfig(untrusted=untrusted):
749 750 sectname = section + '.' + name
750 751 if values:
751 752 for v in values:
752 753 if v == section:
753 754 ui.write('%s=%s\n' % (sectname, value))
754 755 elif v == sectname:
755 756 ui.write(value, '\n')
756 757 else:
757 758 ui.write('%s=%s\n' % (sectname, value))
758 759
759 760 def debugsetparents(ui, repo, rev1, rev2=None):
760 761 """manually set the parents of the current working directory
761 762
762 763 This is useful for writing repository conversion tools, but should
763 764 be used with care.
764 765 """
765 766
766 767 if not rev2:
767 768 rev2 = hex(nullid)
768 769
769 770 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
770 771
771 772 def debugstate(ui, repo):
772 773 """show the contents of the current dirstate"""
773 774 repo.dirstate.read()
774 775 dc = repo.dirstate.map
775 776 keys = dc.keys()
776 777 keys.sort()
777 778 for file_ in keys:
778 779 if dc[file_][3] == -1:
779 780 # Pad or slice to locale representation
780 781 locale_len = len(time.strftime("%x %X", time.localtime(0)))
781 782 timestr = 'unset'
782 783 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
783 784 else:
784 785 timestr = time.strftime("%x %X", time.localtime(dc[file_][3]))
785 786 ui.write("%c %3o %10d %s %s\n"
786 787 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
787 788 timestr, file_))
788 789 for f in repo.dirstate.copies():
789 790 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
790 791
791 792 def debugdata(ui, file_, rev):
792 793 """dump the contents of an data file revision"""
793 794 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
794 795 file_[:-2] + ".i", file_, 0)
795 796 try:
796 797 ui.write(r.revision(r.lookup(rev)))
797 798 except KeyError:
798 799 raise util.Abort(_('invalid revision identifier %s') % rev)
799 800
800 801 def debugdate(ui, date, range=None, **opts):
801 802 """parse and display a date"""
802 803 if opts["extended"]:
803 804 d = util.parsedate(date, util.extendeddateformats)
804 805 else:
805 806 d = util.parsedate(date)
806 807 ui.write("internal: %s %s\n" % d)
807 808 ui.write("standard: %s\n" % util.datestr(d))
808 809 if range:
809 810 m = util.matchdate(range)
810 811 ui.write("match: %s\n" % m(d[0]))
811 812
812 813 def debugindex(ui, file_):
813 814 """dump the contents of an index file"""
814 815 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
815 816 ui.write(" rev offset length base linkrev" +
816 817 " nodeid p1 p2\n")
817 818 for i in xrange(r.count()):
818 819 node = r.node(i)
819 820 pp = r.parents(node)
820 821 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
821 822 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
822 823 short(node), short(pp[0]), short(pp[1])))
823 824
824 825 def debugindexdot(ui, file_):
825 826 """dump an index DAG as a .dot file"""
826 827 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
827 828 ui.write("digraph G {\n")
828 829 for i in xrange(r.count()):
829 830 node = r.node(i)
830 831 pp = r.parents(node)
831 832 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
832 833 if pp[1] != nullid:
833 834 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
834 835 ui.write("}\n")
835 836
836 837 def debuginstall(ui):
837 838 '''test Mercurial installation'''
838 839
839 840 def writetemp(contents):
840 841 (fd, name) = tempfile.mkstemp()
841 842 f = os.fdopen(fd, "wb")
842 843 f.write(contents)
843 844 f.close()
844 845 return name
845 846
846 847 problems = 0
847 848
848 849 # encoding
849 850 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
850 851 try:
851 852 util.fromlocal("test")
852 853 except util.Abort, inst:
853 854 ui.write(" %s\n" % inst)
854 855 ui.write(_(" (check that your locale is properly set)\n"))
855 856 problems += 1
856 857
857 858 # compiled modules
858 859 ui.status(_("Checking extensions...\n"))
859 860 try:
860 861 import bdiff, mpatch, base85
861 862 except Exception, inst:
862 863 ui.write(" %s\n" % inst)
863 864 ui.write(_(" One or more extensions could not be found"))
864 865 ui.write(_(" (check that you compiled the extensions)\n"))
865 866 problems += 1
866 867
867 868 # templates
868 869 ui.status(_("Checking templates...\n"))
869 870 try:
870 871 import templater
871 872 t = templater.templater(templater.templatepath("map-cmdline.default"))
872 873 except Exception, inst:
873 874 ui.write(" %s\n" % inst)
874 875 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
875 876 problems += 1
876 877
877 878 # patch
878 879 ui.status(_("Checking patch...\n"))
879 880 path = os.environ.get('PATH', '')
880 881 patcher = util.find_in_path('gpatch', path,
881 882 util.find_in_path('patch', path, None))
882 883 if not patcher:
883 884 ui.write(_(" Can't find patch or gpatch in PATH\n"))
884 885 ui.write(_(" (specify a patch utility in your .hgrc file)\n"))
885 886 problems += 1
886 887 else:
887 888 # actually attempt a patch here
888 889 a = "1\n2\n3\n4\n"
889 890 b = "1\n2\n3\ninsert\n4\n"
890 891 d = mdiff.unidiff(a, None, b, None, "a")
891 892 fa = writetemp(a)
892 893 fd = writetemp(d)
893 894 fp = os.popen('%s %s %s' % (patcher, fa, fd))
894 895 files = []
895 896 output = ""
896 897 for line in fp:
897 898 output += line
898 899 if line.startswith('patching file '):
899 900 pf = util.parse_patch_output(line.rstrip())
900 901 files.append(pf)
901 902 if files != [fa]:
902 903 ui.write(_(" unexpected patch output!"))
903 904 ui.write(_(" (you may have an incompatible version of patch)\n"))
904 905 ui.write(output)
905 906 problems += 1
906 907 a = file(fa).read()
907 908 if a != b:
908 909 ui.write(_(" patch test failed!"))
909 910 ui.write(_(" (you may have an incompatible version of patch)\n"))
910 911 problems += 1
911 912 os.unlink(fa)
912 913 os.unlink(fd)
913 914
914 915 # merge helper
915 916 ui.status(_("Checking merge helper...\n"))
916 917 cmd = (os.environ.get("HGMERGE") or ui.config("ui", "merge")
917 918 or "hgmerge")
918 919 cmdpath = util.find_in_path(cmd, path)
919 920 if not cmdpath:
920 921 cmdpath = util.find_in_path(cmd.split()[0], path)
921 922 if not cmdpath:
922 923 if cmd == 'hgmerge':
923 924 ui.write(_(" No merge helper set and can't find default"
924 925 " hgmerge script in PATH\n"))
925 926 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
926 927 else:
927 928 ui.write(_(" Can't find merge helper '%s' in PATH\n") % cmd)
928 929 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
929 930 problems += 1
930 931 else:
931 932 # actually attempt a patch here
932 933 fa = writetemp("1\n2\n3\n4\n")
933 934 fl = writetemp("1\n2\n3\ninsert\n4\n")
934 935 fr = writetemp("begin\n1\n2\n3\n4\n")
935 936 r = os.system('%s %s %s %s' % (cmd, fl, fa, fr))
936 937 if r:
937 938 ui.write(_(" got unexpected merge error %d!") % r)
938 939 problems += 1
939 940 m = file(fl).read()
940 941 if m != "begin\n1\n2\n3\ninsert\n4\n":
941 942 ui.write(_(" got unexpected merge results!") % r)
942 943 ui.write(_(" (your merge helper may have the"
943 944 " wrong argument order)\n"))
944 945 ui.write(m)
945 946 os.unlink(fa)
946 947 os.unlink(fl)
947 948 os.unlink(fr)
948 949
949 950 # editor
950 951 ui.status(_("Checking commit editor...\n"))
951 952 editor = (os.environ.get("HGEDITOR") or
952 953 ui.config("ui", "editor") or
953 954 os.environ.get("EDITOR", "vi"))
954 955 cmdpath = util.find_in_path(editor, path)
955 956 if not cmdpath:
956 957 cmdpath = util.find_in_path(editor.split()[0], path)
957 958 if not cmdpath:
958 959 if editor == 'vi':
959 960 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
960 961 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
961 962 else:
962 963 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
963 964 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
964 965 problems += 1
965 966
966 967 # check username
967 968 ui.status(_("Checking username...\n"))
968 969 user = os.environ.get("HGUSER")
969 970 if user is None:
970 971 user = ui.config("ui", "username")
971 972 if user is None:
972 973 user = os.environ.get("EMAIL")
973 974 if not user:
974 975 ui.warn(" ")
975 976 ui.username()
976 977 ui.write(_(" (specify a username in your .hgrc file)\n"))
977 978
978 979 if not problems:
979 980 ui.status(_("No problems detected\n"))
980 981 else:
981 982 ui.write(_("%s problems detected,"
982 983 " please check your install!\n") % problems)
983 984
984 985 return problems
985 986
986 987 def debugrename(ui, repo, file1, *pats, **opts):
987 988 """dump rename information"""
988 989
989 990 ctx = repo.changectx(opts.get('rev', 'tip'))
990 991 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
991 992 ctx.node()):
992 993 m = ctx.filectx(abs).renamed()
993 994 if m:
994 995 ui.write(_("%s renamed from %s:%s\n") % (rel, m[0], hex(m[1])))
995 996 else:
996 997 ui.write(_("%s not renamed\n") % rel)
997 998
998 999 def debugwalk(ui, repo, *pats, **opts):
999 1000 """show how files match on given patterns"""
1000 1001 items = list(cmdutil.walk(repo, pats, opts))
1001 1002 if not items:
1002 1003 return
1003 1004 fmt = '%%s %%-%ds %%-%ds %%s' % (
1004 1005 max([len(abs) for (src, abs, rel, exact) in items]),
1005 1006 max([len(rel) for (src, abs, rel, exact) in items]))
1006 1007 for src, abs, rel, exact in items:
1007 1008 line = fmt % (src, abs, rel, exact and 'exact' or '')
1008 1009 ui.write("%s\n" % line.rstrip())
1009 1010
1010 1011 def diff(ui, repo, *pats, **opts):
1011 1012 """diff repository (or selected files)
1012 1013
1013 1014 Show differences between revisions for the specified files.
1014 1015
1015 1016 Differences between files are shown using the unified diff format.
1016 1017
1017 1018 NOTE: diff may generate unexpected results for merges, as it will
1018 1019 default to comparing against the working directory's first parent
1019 1020 changeset if no revisions are specified.
1020 1021
1021 1022 When two revision arguments are given, then changes are shown
1022 1023 between those revisions. If only one revision is specified then
1023 1024 that revision is compared to the working directory, and, when no
1024 1025 revisions are specified, the working directory files are compared
1025 1026 to its parent.
1026 1027
1027 1028 Without the -a option, diff will avoid generating diffs of files
1028 1029 it detects as binary. With -a, diff will generate a diff anyway,
1029 1030 probably with undesirable results.
1030 1031 """
1031 1032 node1, node2 = cmdutil.revpair(repo, opts['rev'])
1032 1033
1033 1034 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1034 1035
1035 1036 patch.diff(repo, node1, node2, fns, match=matchfn,
1036 1037 opts=patch.diffopts(ui, opts))
1037 1038
1038 1039 def export(ui, repo, *changesets, **opts):
1039 1040 """dump the header and diffs for one or more changesets
1040 1041
1041 1042 Print the changeset header and diffs for one or more revisions.
1042 1043
1043 1044 The information shown in the changeset header is: author,
1044 1045 changeset hash, parent(s) and commit comment.
1045 1046
1046 1047 NOTE: export may generate unexpected diff output for merge changesets,
1047 1048 as it will compare the merge changeset against its first parent only.
1048 1049
1049 1050 Output may be to a file, in which case the name of the file is
1050 1051 given using a format string. The formatting rules are as follows:
1051 1052
1052 1053 %% literal "%" character
1053 1054 %H changeset hash (40 bytes of hexadecimal)
1054 1055 %N number of patches being generated
1055 1056 %R changeset revision number
1056 1057 %b basename of the exporting repository
1057 1058 %h short-form changeset hash (12 bytes of hexadecimal)
1058 1059 %n zero-padded sequence number, starting at 1
1059 1060 %r zero-padded changeset revision number
1060 1061
1061 1062 Without the -a option, export will avoid generating diffs of files
1062 1063 it detects as binary. With -a, export will generate a diff anyway,
1063 1064 probably with undesirable results.
1064 1065
1065 1066 With the --switch-parent option, the diff will be against the second
1066 1067 parent. It can be useful to review a merge.
1067 1068 """
1068 1069 if not changesets:
1069 1070 raise util.Abort(_("export requires at least one changeset"))
1070 1071 revs = cmdutil.revrange(repo, changesets)
1071 1072 if len(revs) > 1:
1072 1073 ui.note(_('exporting patches:\n'))
1073 1074 else:
1074 1075 ui.note(_('exporting patch:\n'))
1075 1076 patch.export(repo, revs, template=opts['output'],
1076 1077 switch_parent=opts['switch_parent'],
1077 1078 opts=patch.diffopts(ui, opts))
1078 1079
1079 1080 def grep(ui, repo, pattern, *pats, **opts):
1080 1081 """search for a pattern in specified files and revisions
1081 1082
1082 1083 Search revisions of files for a regular expression.
1083 1084
1084 1085 This command behaves differently than Unix grep. It only accepts
1085 1086 Python/Perl regexps. It searches repository history, not the
1086 1087 working directory. It always prints the revision number in which
1087 1088 a match appears.
1088 1089
1089 1090 By default, grep only prints output for the first revision of a
1090 1091 file in which it finds a match. To get it to print every revision
1091 1092 that contains a change in match status ("-" for a match that
1092 1093 becomes a non-match, or "+" for a non-match that becomes a match),
1093 1094 use the --all flag.
1094 1095 """
1095 1096 reflags = 0
1096 1097 if opts['ignore_case']:
1097 1098 reflags |= re.I
1098 1099 regexp = re.compile(pattern, reflags)
1099 1100 sep, eol = ':', '\n'
1100 1101 if opts['print0']:
1101 1102 sep = eol = '\0'
1102 1103
1103 1104 fcache = {}
1104 1105 def getfile(fn):
1105 1106 if fn not in fcache:
1106 1107 fcache[fn] = repo.file(fn)
1107 1108 return fcache[fn]
1108 1109
1109 1110 def matchlines(body):
1110 1111 begin = 0
1111 1112 linenum = 0
1112 1113 while True:
1113 1114 match = regexp.search(body, begin)
1114 1115 if not match:
1115 1116 break
1116 1117 mstart, mend = match.span()
1117 1118 linenum += body.count('\n', begin, mstart) + 1
1118 1119 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1119 1120 lend = body.find('\n', mend)
1120 1121 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1121 1122 begin = lend + 1
1122 1123
1123 1124 class linestate(object):
1124 1125 def __init__(self, line, linenum, colstart, colend):
1125 1126 self.line = line
1126 1127 self.linenum = linenum
1127 1128 self.colstart = colstart
1128 1129 self.colend = colend
1129 1130
1130 1131 def __eq__(self, other):
1131 1132 return self.line == other.line
1132 1133
1133 1134 matches = {}
1134 1135 copies = {}
1135 1136 def grepbody(fn, rev, body):
1136 1137 matches[rev].setdefault(fn, [])
1137 1138 m = matches[rev][fn]
1138 1139 for lnum, cstart, cend, line in matchlines(body):
1139 1140 s = linestate(line, lnum, cstart, cend)
1140 1141 m.append(s)
1141 1142
1142 1143 def difflinestates(a, b):
1143 1144 sm = difflib.SequenceMatcher(None, a, b)
1144 1145 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1145 1146 if tag == 'insert':
1146 1147 for i in xrange(blo, bhi):
1147 1148 yield ('+', b[i])
1148 1149 elif tag == 'delete':
1149 1150 for i in xrange(alo, ahi):
1150 1151 yield ('-', a[i])
1151 1152 elif tag == 'replace':
1152 1153 for i in xrange(alo, ahi):
1153 1154 yield ('-', a[i])
1154 1155 for i in xrange(blo, bhi):
1155 1156 yield ('+', b[i])
1156 1157
1157 1158 prev = {}
1158 1159 def display(fn, rev, states, prevstates):
1159 1160 counts = {'-': 0, '+': 0}
1160 1161 filerevmatches = {}
1161 1162 if incrementing or not opts['all']:
1162 1163 a, b, r = prevstates, states, rev
1163 1164 else:
1164 1165 a, b, r = states, prevstates, prev.get(fn, -1)
1165 1166 for change, l in difflinestates(a, b):
1166 1167 cols = [fn, str(r)]
1167 1168 if opts['line_number']:
1168 1169 cols.append(str(l.linenum))
1169 1170 if opts['all']:
1170 1171 cols.append(change)
1171 1172 if opts['user']:
1172 1173 cols.append(ui.shortuser(get(r)[1]))
1173 1174 if opts['files_with_matches']:
1174 1175 c = (fn, r)
1175 1176 if c in filerevmatches:
1176 1177 continue
1177 1178 filerevmatches[c] = 1
1178 1179 else:
1179 1180 cols.append(l.line)
1180 1181 ui.write(sep.join(cols), eol)
1181 1182 counts[change] += 1
1182 1183 return counts['+'], counts['-']
1183 1184
1184 1185 fstate = {}
1185 1186 skip = {}
1186 1187 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1187 1188 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1188 1189 count = 0
1189 1190 incrementing = False
1190 1191 follow = opts.get('follow')
1191 1192 for st, rev, fns in changeiter:
1192 1193 if st == 'window':
1193 1194 incrementing = rev
1194 1195 matches.clear()
1195 1196 elif st == 'add':
1196 1197 mf = repo.changectx(rev).manifest()
1197 1198 matches[rev] = {}
1198 1199 for fn in fns:
1199 1200 if fn in skip:
1200 1201 continue
1201 1202 fstate.setdefault(fn, {})
1202 1203 try:
1203 1204 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1204 1205 if follow:
1205 1206 copied = getfile(fn).renamed(mf[fn])
1206 1207 if copied:
1207 1208 copies.setdefault(rev, {})[fn] = copied[0]
1208 1209 except KeyError:
1209 1210 pass
1210 1211 elif st == 'iter':
1211 1212 states = matches[rev].items()
1212 1213 states.sort()
1213 1214 for fn, m in states:
1214 1215 copy = copies.get(rev, {}).get(fn)
1215 1216 if fn in skip:
1216 1217 if copy:
1217 1218 skip[copy] = True
1218 1219 continue
1219 1220 if incrementing or not opts['all'] or fstate[fn]:
1220 1221 pos, neg = display(fn, rev, m, fstate[fn])
1221 1222 count += pos + neg
1222 1223 if pos and not opts['all']:
1223 1224 skip[fn] = True
1224 1225 if copy:
1225 1226 skip[copy] = True
1226 1227 fstate[fn] = m
1227 1228 if copy:
1228 1229 fstate[copy] = m
1229 1230 prev[fn] = rev
1230 1231
1231 1232 if not incrementing:
1232 1233 fstate = fstate.items()
1233 1234 fstate.sort()
1234 1235 for fn, state in fstate:
1235 1236 if fn in skip:
1236 1237 continue
1237 1238 if fn not in copies.get(prev[fn], {}):
1238 1239 display(fn, rev, {}, state)
1239 1240 return (count == 0 and 1) or 0
1240 1241
1241 1242 def heads(ui, repo, **opts):
1242 1243 """show current repository heads
1243 1244
1244 1245 Show all repository head changesets.
1245 1246
1246 1247 Repository "heads" are changesets that don't have children
1247 1248 changesets. They are where development generally takes place and
1248 1249 are the usual targets for update and merge operations.
1249 1250 """
1250 1251 if opts['rev']:
1251 1252 heads = repo.heads(repo.lookup(opts['rev']))
1252 1253 else:
1253 1254 heads = repo.heads()
1254 1255 displayer = cmdutil.show_changeset(ui, repo, opts)
1255 1256 for n in heads:
1256 1257 displayer.show(changenode=n)
1257 1258
1258 1259 def help_(ui, name=None, with_version=False):
1259 1260 """show help for a command, extension, or list of commands
1260 1261
1261 1262 With no arguments, print a list of commands and short help.
1262 1263
1263 1264 Given a command name, print help for that command.
1264 1265
1265 1266 Given an extension name, print help for that extension, and the
1266 1267 commands it provides."""
1267 1268 option_lists = []
1268 1269
1269 1270 def helpcmd(name):
1270 1271 if with_version:
1271 1272 version_(ui)
1272 1273 ui.write('\n')
1273 1274 aliases, i = findcmd(ui, name)
1274 1275 # synopsis
1275 1276 ui.write("%s\n\n" % i[2])
1276 1277
1277 1278 # description
1278 1279 doc = i[0].__doc__
1279 1280 if not doc:
1280 1281 doc = _("(No help text available)")
1281 1282 if ui.quiet:
1282 1283 doc = doc.splitlines(0)[0]
1283 1284 ui.write("%s\n" % doc.rstrip())
1284 1285
1285 1286 if not ui.quiet:
1286 1287 # aliases
1287 1288 if len(aliases) > 1:
1288 1289 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1289 1290
1290 1291 # options
1291 1292 if i[1]:
1292 1293 option_lists.append(("options", i[1]))
1293 1294
1294 1295 def helplist(select=None):
1295 1296 h = {}
1296 1297 cmds = {}
1297 1298 for c, e in table.items():
1298 1299 f = c.split("|", 1)[0]
1299 1300 if select and not select(f):
1300 1301 continue
1301 1302 if name == "shortlist" and not f.startswith("^"):
1302 1303 continue
1303 1304 f = f.lstrip("^")
1304 1305 if not ui.debugflag and f.startswith("debug"):
1305 1306 continue
1306 1307 doc = e[0].__doc__
1307 1308 if not doc:
1308 1309 doc = _("(No help text available)")
1309 1310 h[f] = doc.splitlines(0)[0].rstrip()
1310 1311 cmds[f] = c.lstrip("^")
1311 1312
1312 1313 fns = h.keys()
1313 1314 fns.sort()
1314 1315 m = max(map(len, fns))
1315 1316 for f in fns:
1316 1317 if ui.verbose:
1317 1318 commands = cmds[f].replace("|",", ")
1318 1319 ui.write(" %s:\n %s\n"%(commands, h[f]))
1319 1320 else:
1320 1321 ui.write(' %-*s %s\n' % (m, f, h[f]))
1321 1322
1322 1323 def helptopic(name):
1323 1324 v = None
1324 1325 for i in help.helptable:
1325 1326 l = i.split('|')
1326 1327 if name in l:
1327 1328 v = i
1328 1329 header = l[-1]
1329 1330 if not v:
1330 1331 raise UnknownCommand(name)
1331 1332
1332 1333 # description
1333 1334 doc = help.helptable[v]
1334 1335 if not doc:
1335 1336 doc = _("(No help text available)")
1336 1337 if callable(doc):
1337 1338 doc = doc()
1338 1339
1339 1340 ui.write("%s\n" % header)
1340 1341 ui.write("%s\n" % doc.rstrip())
1341 1342
1342 1343 def helpext(name):
1343 1344 try:
1344 1345 mod = findext(name)
1345 1346 except KeyError:
1346 1347 raise UnknownCommand(name)
1347 1348
1348 1349 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1349 1350 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1350 1351 for d in doc[1:]:
1351 1352 ui.write(d, '\n')
1352 1353
1353 1354 ui.status('\n')
1354 1355
1355 1356 try:
1356 1357 ct = mod.cmdtable
1357 1358 except AttributeError:
1358 1359 ui.status(_('no commands defined\n'))
1359 1360 return
1360 1361
1361 1362 if ui.verbose:
1362 1363 ui.status(_('list of commands:\n\n'))
1363 1364 else:
1364 1365 ui.status(_('list of commands (use "hg help -v %s" '
1365 1366 'to show aliases and global options):\n\n') % name)
1366 1367
1367 1368 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1368 1369 helplist(modcmds.has_key)
1369 1370
1370 1371 if name and name != 'shortlist':
1371 1372 i = None
1372 1373 for f in (helpcmd, helptopic, helpext):
1373 1374 try:
1374 1375 f(name)
1375 1376 i = None
1376 1377 break
1377 1378 except UnknownCommand, inst:
1378 1379 i = inst
1379 1380 if i:
1380 1381 raise i
1381 1382
1382 1383 else:
1383 1384 # program name
1384 1385 if ui.verbose or with_version:
1385 1386 version_(ui)
1386 1387 else:
1387 1388 ui.status(_("Mercurial Distributed SCM\n"))
1388 1389 ui.status('\n')
1389 1390
1390 1391 # list of commands
1391 1392 if name == "shortlist":
1392 1393 ui.status(_('basic commands (use "hg help" '
1393 1394 'for the full list or option "-v" for details):\n\n'))
1394 1395 elif ui.verbose:
1395 1396 ui.status(_('list of commands:\n\n'))
1396 1397 else:
1397 1398 ui.status(_('list of commands (use "hg help -v" '
1398 1399 'to show aliases and global options):\n\n'))
1399 1400
1400 1401 helplist()
1401 1402
1402 1403 # global options
1403 1404 if ui.verbose:
1404 1405 option_lists.append(("global options", globalopts))
1405 1406
1406 1407 # list all option lists
1407 1408 opt_output = []
1408 1409 for title, options in option_lists:
1409 1410 opt_output.append(("\n%s:\n" % title, None))
1410 1411 for shortopt, longopt, default, desc in options:
1411 1412 if "DEPRECATED" in desc and not ui.verbose: continue
1412 1413 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1413 1414 longopt and " --%s" % longopt),
1414 1415 "%s%s" % (desc,
1415 1416 default
1416 1417 and _(" (default: %s)") % default
1417 1418 or "")))
1418 1419
1419 1420 if opt_output:
1420 1421 opts_len = max([len(line[0]) for line in opt_output if line[1]])
1421 1422 for first, second in opt_output:
1422 1423 if second:
1423 1424 ui.write(" %-*s %s\n" % (opts_len, first, second))
1424 1425 else:
1425 1426 ui.write("%s\n" % first)
1426 1427
1427 1428 def identify(ui, repo):
1428 1429 """print information about the working copy
1429 1430
1430 1431 Print a short summary of the current state of the repo.
1431 1432
1432 1433 This summary identifies the repository state using one or two parent
1433 1434 hash identifiers, followed by a "+" if there are uncommitted changes
1434 1435 in the working directory, followed by a list of tags for this revision.
1435 1436 """
1436 1437 parents = [p for p in repo.dirstate.parents() if p != nullid]
1437 1438 if not parents:
1438 1439 ui.write(_("unknown\n"))
1439 1440 return
1440 1441
1441 1442 hexfunc = ui.debugflag and hex or short
1442 1443 modified, added, removed, deleted = repo.status()[:4]
1443 1444 output = ["%s%s" %
1444 1445 ('+'.join([hexfunc(parent) for parent in parents]),
1445 1446 (modified or added or removed or deleted) and "+" or "")]
1446 1447
1447 1448 if not ui.quiet:
1448 1449
1449 1450 branch = util.tolocal(repo.workingctx().branch())
1450 1451 if branch:
1451 1452 output.append("(%s)" % branch)
1452 1453
1453 1454 # multiple tags for a single parent separated by '/'
1454 1455 parenttags = ['/'.join(tags)
1455 1456 for tags in map(repo.nodetags, parents) if tags]
1456 1457 # tags for multiple parents separated by ' + '
1457 1458 if parenttags:
1458 1459 output.append(' + '.join(parenttags))
1459 1460
1460 1461 ui.write("%s\n" % ' '.join(output))
1461 1462
1462 1463 def import_(ui, repo, patch1, *patches, **opts):
1463 1464 """import an ordered set of patches
1464 1465
1465 1466 Import a list of patches and commit them individually.
1466 1467
1467 1468 If there are outstanding changes in the working directory, import
1468 1469 will abort unless given the -f flag.
1469 1470
1470 1471 You can import a patch straight from a mail message. Even patches
1471 1472 as attachments work (body part must be type text/plain or
1472 1473 text/x-patch to be used). From and Subject headers of email
1473 1474 message are used as default committer and commit message. All
1474 1475 text/plain body parts before first diff are added to commit
1475 1476 message.
1476 1477
1477 1478 If imported patch was generated by hg export, user and description
1478 1479 from patch override values from message headers and body. Values
1479 1480 given on command line with -m and -u override these.
1480 1481
1481 1482 To read a patch from standard input, use patch name "-".
1482 1483 """
1483 1484 patches = (patch1,) + patches
1484 1485
1485 1486 if not opts['force']:
1486 1487 bail_if_changed(repo)
1487 1488
1488 1489 d = opts["base"]
1489 1490 strip = opts["strip"]
1490 1491
1491 1492 wlock = repo.wlock()
1492 1493 lock = repo.lock()
1493 1494
1494 1495 for p in patches:
1495 1496 pf = os.path.join(d, p)
1496 1497
1497 1498 if pf == '-':
1498 1499 ui.status(_("applying patch from stdin\n"))
1499 1500 tmpname, message, user, date = patch.extract(ui, sys.stdin)
1500 1501 else:
1501 1502 ui.status(_("applying %s\n") % p)
1502 1503 tmpname, message, user, date = patch.extract(ui, file(pf))
1503 1504
1504 1505 if tmpname is None:
1505 1506 raise util.Abort(_('no diffs found'))
1506 1507
1507 1508 try:
1508 1509 cmdline_message = logmessage(opts)
1509 1510 if cmdline_message:
1510 1511 # pickup the cmdline msg
1511 1512 message = cmdline_message
1512 1513 elif message:
1513 1514 # pickup the patch msg
1514 1515 message = message.strip()
1515 1516 else:
1516 1517 # launch the editor
1517 1518 message = None
1518 1519 ui.debug(_('message:\n%s\n') % message)
1519 1520
1520 1521 files = {}
1521 1522 try:
1522 1523 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1523 1524 files=files)
1524 1525 finally:
1525 1526 files = patch.updatedir(ui, repo, files, wlock=wlock)
1526 1527 repo.commit(files, message, user, date, wlock=wlock, lock=lock)
1527 1528 finally:
1528 1529 os.unlink(tmpname)
1529 1530
1530 1531 def incoming(ui, repo, source="default", **opts):
1531 1532 """show new changesets found in source
1532 1533
1533 1534 Show new changesets found in the specified path/URL or the default
1534 1535 pull location. These are the changesets that would be pulled if a pull
1535 1536 was requested.
1536 1537
1537 1538 For remote repository, using --bundle avoids downloading the changesets
1538 1539 twice if the incoming is followed by a pull.
1539 1540
1540 1541 See pull for valid source format details.
1541 1542 """
1542 1543 source = ui.expandpath(source)
1543 1544 setremoteconfig(ui, opts)
1544 1545
1545 1546 other = hg.repository(ui, source)
1546 1547 incoming = repo.findincoming(other, force=opts["force"])
1547 1548 if not incoming:
1548 1549 ui.status(_("no changes found\n"))
1549 1550 return
1550 1551
1551 1552 cleanup = None
1552 1553 try:
1553 1554 fname = opts["bundle"]
1554 1555 if fname or not other.local():
1555 1556 # create a bundle (uncompressed if other repo is not local)
1556 1557 cg = other.changegroup(incoming, "incoming")
1557 1558 bundletype = other.local() and "HG10BZ" or "HG10UN"
1558 1559 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1559 1560 # keep written bundle?
1560 1561 if opts["bundle"]:
1561 1562 cleanup = None
1562 1563 if not other.local():
1563 1564 # use the created uncompressed bundlerepo
1564 1565 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1565 1566
1566 1567 revs = None
1567 1568 if opts['rev']:
1568 1569 revs = [other.lookup(rev) for rev in opts['rev']]
1569 1570 o = other.changelog.nodesbetween(incoming, revs)[0]
1570 1571 if opts['newest_first']:
1571 1572 o.reverse()
1572 1573 displayer = cmdutil.show_changeset(ui, other, opts)
1573 1574 for n in o:
1574 1575 parents = [p for p in other.changelog.parents(n) if p != nullid]
1575 1576 if opts['no_merges'] and len(parents) == 2:
1576 1577 continue
1577 1578 displayer.show(changenode=n)
1578 1579 finally:
1579 1580 if hasattr(other, 'close'):
1580 1581 other.close()
1581 1582 if cleanup:
1582 1583 os.unlink(cleanup)
1583 1584
1584 1585 def init(ui, dest=".", **opts):
1585 1586 """create a new repository in the given directory
1586 1587
1587 1588 Initialize a new repository in the given directory. If the given
1588 1589 directory does not exist, it is created.
1589 1590
1590 1591 If no directory is given, the current directory is used.
1591 1592
1592 1593 It is possible to specify an ssh:// URL as the destination.
1593 1594 Look at the help text for the pull command for important details
1594 1595 about ssh:// URLs.
1595 1596 """
1596 1597 setremoteconfig(ui, opts)
1597 1598 hg.repository(ui, dest, create=1)
1598 1599
1599 1600 def locate(ui, repo, *pats, **opts):
1600 1601 """locate files matching specific patterns
1601 1602
1602 1603 Print all files under Mercurial control whose names match the
1603 1604 given patterns.
1604 1605
1605 1606 This command searches the current directory and its
1606 1607 subdirectories. To search an entire repository, move to the root
1607 1608 of the repository.
1608 1609
1609 1610 If no patterns are given to match, this command prints all file
1610 1611 names.
1611 1612
1612 1613 If you want to feed the output of this command into the "xargs"
1613 1614 command, use the "-0" option to both this command and "xargs".
1614 1615 This will avoid the problem of "xargs" treating single filenames
1615 1616 that contain white space as multiple filenames.
1616 1617 """
1617 1618 end = opts['print0'] and '\0' or '\n'
1618 1619 rev = opts['rev']
1619 1620 if rev:
1620 1621 node = repo.lookup(rev)
1621 1622 else:
1622 1623 node = None
1623 1624
1624 1625 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1625 1626 head='(?:.*/|)'):
1626 1627 if not node and repo.dirstate.state(abs) == '?':
1627 1628 continue
1628 1629 if opts['fullpath']:
1629 1630 ui.write(os.path.join(repo.root, abs), end)
1630 1631 else:
1631 1632 ui.write(((pats and rel) or abs), end)
1632 1633
1633 1634 def log(ui, repo, *pats, **opts):
1634 1635 """show revision history of entire repository or files
1635 1636
1636 1637 Print the revision history of the specified files or the entire
1637 1638 project.
1638 1639
1639 1640 File history is shown without following rename or copy history of
1640 1641 files. Use -f/--follow with a file name to follow history across
1641 1642 renames and copies. --follow without a file name will only show
1642 1643 ancestors or descendants of the starting revision. --follow-first
1643 1644 only follows the first parent of merge revisions.
1644 1645
1645 1646 If no revision range is specified, the default is tip:0 unless
1646 1647 --follow is set, in which case the working directory parent is
1647 1648 used as the starting revision.
1648 1649
1649 1650 By default this command outputs: changeset id and hash, tags,
1650 1651 non-trivial parents, user, date and time, and a summary for each
1651 1652 commit. When the -v/--verbose switch is used, the list of changed
1652 1653 files and full commit message is shown.
1653 1654
1654 1655 NOTE: log -p may generate unexpected diff output for merge
1655 1656 changesets, as it will compare the merge changeset against its
1656 1657 first parent only. Also, the files: list will only reflect files
1657 1658 that are different from BOTH parents.
1658 1659
1659 1660 """
1660 1661
1661 1662 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1662 1663 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1663 1664
1664 1665 if opts['limit']:
1665 1666 try:
1666 1667 limit = int(opts['limit'])
1667 1668 except ValueError:
1668 1669 raise util.Abort(_('limit must be a positive integer'))
1669 1670 if limit <= 0: raise util.Abort(_('limit must be positive'))
1670 1671 else:
1671 1672 limit = sys.maxint
1672 1673 count = 0
1673 1674
1674 1675 if opts['copies'] and opts['rev']:
1675 1676 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1676 1677 else:
1677 1678 endrev = repo.changelog.count()
1678 1679 rcache = {}
1679 1680 ncache = {}
1680 1681 dcache = []
1681 1682 def getrenamed(fn, rev, man):
1682 1683 '''looks up all renames for a file (up to endrev) the first
1683 1684 time the file is given. It indexes on the changerev and only
1684 1685 parses the manifest if linkrev != changerev.
1685 1686 Returns rename info for fn at changerev rev.'''
1686 1687 if fn not in rcache:
1687 1688 rcache[fn] = {}
1688 1689 ncache[fn] = {}
1689 1690 fl = repo.file(fn)
1690 1691 for i in xrange(fl.count()):
1691 1692 node = fl.node(i)
1692 1693 lr = fl.linkrev(node)
1693 1694 renamed = fl.renamed(node)
1694 1695 rcache[fn][lr] = renamed
1695 1696 if renamed:
1696 1697 ncache[fn][node] = renamed
1697 1698 if lr >= endrev:
1698 1699 break
1699 1700 if rev in rcache[fn]:
1700 1701 return rcache[fn][rev]
1701 1702 mr = repo.manifest.rev(man)
1702 1703 if repo.manifest.parentrevs(mr) != (mr - 1, nullrev):
1703 1704 return ncache[fn].get(repo.manifest.find(man, fn)[0])
1704 1705 if not dcache or dcache[0] != man:
1705 1706 dcache[:] = [man, repo.manifest.readdelta(man)]
1706 1707 if fn in dcache[1]:
1707 1708 return ncache[fn].get(dcache[1][fn])
1708 1709 return None
1709 1710
1710 1711 df = False
1711 1712 if opts["date"]:
1712 1713 df = util.matchdate(opts["date"])
1713 1714
1714 1715
1715 1716 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1716 1717 for st, rev, fns in changeiter:
1717 1718 if st == 'add':
1718 1719 changenode = repo.changelog.node(rev)
1719 1720 parents = [p for p in repo.changelog.parentrevs(rev)
1720 1721 if p != nullrev]
1721 1722 if opts['no_merges'] and len(parents) == 2:
1722 1723 continue
1723 1724 if opts['only_merges'] and len(parents) != 2:
1724 1725 continue
1725 1726
1726 1727 if df:
1727 1728 changes = get(rev)
1728 1729 if not df(changes[2][0]):
1729 1730 continue
1730 1731
1731 1732 if opts['keyword']:
1732 1733 changes = get(rev)
1733 1734 miss = 0
1734 1735 for k in [kw.lower() for kw in opts['keyword']]:
1735 1736 if not (k in changes[1].lower() or
1736 1737 k in changes[4].lower() or
1737 1738 k in " ".join(changes[3][:20]).lower()):
1738 1739 miss = 1
1739 1740 break
1740 1741 if miss:
1741 1742 continue
1742 1743
1743 1744 copies = []
1744 1745 if opts.get('copies') and rev:
1745 1746 mf = get(rev)[0]
1746 1747 for fn in get(rev)[3]:
1747 1748 rename = getrenamed(fn, rev, mf)
1748 1749 if rename:
1749 1750 copies.append((fn, rename[0]))
1750 1751 displayer.show(rev, changenode, copies=copies)
1751 1752 elif st == 'iter':
1752 1753 if count == limit: break
1753 1754 if displayer.flush(rev):
1754 1755 count += 1
1755 1756
1756 1757 def manifest(ui, repo, rev=None):
1757 1758 """output the current or given revision of the project manifest
1758 1759
1759 1760 Print a list of version controlled files for the given revision.
1760 1761 If no revision is given, the parent of the working directory is used,
1761 1762 or tip if no revision is checked out.
1762 1763
1763 1764 The manifest is the list of files being version controlled. If no revision
1764 1765 is given then the first parent of the working directory is used.
1765 1766
1766 1767 With -v flag, print file permissions. With --debug flag, print
1767 1768 file revision hashes.
1768 1769 """
1769 1770
1770 1771 m = repo.changectx(rev).manifest()
1771 1772 files = m.keys()
1772 1773 files.sort()
1773 1774
1774 1775 for f in files:
1775 1776 if ui.debugflag:
1776 1777 ui.write("%40s " % hex(m[f]))
1777 1778 if ui.verbose:
1778 1779 ui.write("%3s " % (m.execf(f) and "755" or "644"))
1779 1780 ui.write("%s\n" % f)
1780 1781
1781 1782 def merge(ui, repo, node=None, force=None, branch=None):
1782 1783 """merge working directory with another revision
1783 1784
1784 1785 Merge the contents of the current working directory and the
1785 1786 requested revision. Files that changed between either parent are
1786 1787 marked as changed for the next commit and a commit must be
1787 1788 performed before any further updates are allowed.
1788 1789
1789 1790 If no revision is specified, the working directory's parent is a
1790 1791 head revision, and the repository contains exactly one other head,
1791 1792 the other head is merged with by default. Otherwise, an explicit
1792 1793 revision to merge with must be provided.
1793 1794 """
1794 1795
1795 1796 if node or branch:
1796 1797 node = _lookup(repo, node, branch)
1797 1798 else:
1798 1799 heads = repo.heads()
1799 1800 if len(heads) > 2:
1800 1801 raise util.Abort(_('repo has %d heads - '
1801 1802 'please merge with an explicit rev') %
1802 1803 len(heads))
1803 1804 if len(heads) == 1:
1804 1805 raise util.Abort(_('there is nothing to merge - '
1805 1806 'use "hg update" instead'))
1806 1807 parent = repo.dirstate.parents()[0]
1807 1808 if parent not in heads:
1808 1809 raise util.Abort(_('working dir not at a head rev - '
1809 1810 'use "hg update" or merge with an explicit rev'))
1810 1811 node = parent == heads[0] and heads[-1] or heads[0]
1811 1812 return hg.merge(repo, node, force=force)
1812 1813
1813 1814 def outgoing(ui, repo, dest=None, **opts):
1814 1815 """show changesets not found in destination
1815 1816
1816 1817 Show changesets not found in the specified destination repository or
1817 1818 the default push location. These are the changesets that would be pushed
1818 1819 if a push was requested.
1819 1820
1820 1821 See pull for valid destination format details.
1821 1822 """
1822 1823 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1823 1824 setremoteconfig(ui, opts)
1824 1825 revs = None
1825 1826 if opts['rev']:
1826 1827 revs = [repo.lookup(rev) for rev in opts['rev']]
1827 1828
1828 1829 other = hg.repository(ui, dest)
1829 1830 o = repo.findoutgoing(other, force=opts['force'])
1830 1831 if not o:
1831 1832 ui.status(_("no changes found\n"))
1832 1833 return
1833 1834 o = repo.changelog.nodesbetween(o, revs)[0]
1834 1835 if opts['newest_first']:
1835 1836 o.reverse()
1836 1837 displayer = cmdutil.show_changeset(ui, repo, opts)
1837 1838 for n in o:
1838 1839 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1839 1840 if opts['no_merges'] and len(parents) == 2:
1840 1841 continue
1841 1842 displayer.show(changenode=n)
1842 1843
1843 1844 def parents(ui, repo, file_=None, **opts):
1844 1845 """show the parents of the working dir or revision
1845 1846
1846 1847 Print the working directory's parent revisions.
1847 1848 """
1848 1849 rev = opts.get('rev')
1849 1850 if rev:
1850 1851 if file_:
1851 1852 ctx = repo.filectx(file_, changeid=rev)
1852 1853 else:
1853 1854 ctx = repo.changectx(rev)
1854 1855 p = [cp.node() for cp in ctx.parents()]
1855 1856 else:
1856 1857 p = repo.dirstate.parents()
1857 1858
1858 1859 displayer = cmdutil.show_changeset(ui, repo, opts)
1859 1860 for n in p:
1860 1861 if n != nullid:
1861 1862 displayer.show(changenode=n)
1862 1863
1863 1864 def paths(ui, repo, search=None):
1864 1865 """show definition of symbolic path names
1865 1866
1866 1867 Show definition of symbolic path name NAME. If no name is given, show
1867 1868 definition of available names.
1868 1869
1869 1870 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1870 1871 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1871 1872 """
1872 1873 if search:
1873 1874 for name, path in ui.configitems("paths"):
1874 1875 if name == search:
1875 1876 ui.write("%s\n" % path)
1876 1877 return
1877 1878 ui.warn(_("not found!\n"))
1878 1879 return 1
1879 1880 else:
1880 1881 for name, path in ui.configitems("paths"):
1881 1882 ui.write("%s = %s\n" % (name, path))
1882 1883
1883 1884 def postincoming(ui, repo, modheads, optupdate):
1884 1885 if modheads == 0:
1885 1886 return
1886 1887 if optupdate:
1887 1888 if modheads == 1:
1888 1889 return hg.update(repo, repo.changelog.tip()) # update
1889 1890 else:
1890 1891 ui.status(_("not updating, since new heads added\n"))
1891 1892 if modheads > 1:
1892 1893 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
1893 1894 else:
1894 1895 ui.status(_("(run 'hg update' to get a working copy)\n"))
1895 1896
1896 1897 def pull(ui, repo, source="default", **opts):
1897 1898 """pull changes from the specified source
1898 1899
1899 1900 Pull changes from a remote repository to a local one.
1900 1901
1901 1902 This finds all changes from the repository at the specified path
1902 1903 or URL and adds them to the local repository. By default, this
1903 1904 does not update the copy of the project in the working directory.
1904 1905
1905 1906 Valid URLs are of the form:
1906 1907
1907 1908 local/filesystem/path (or file://local/filesystem/path)
1908 1909 http://[user@]host[:port]/[path]
1909 1910 https://[user@]host[:port]/[path]
1910 1911 ssh://[user@]host[:port]/[path]
1911 1912 static-http://host[:port]/[path]
1912 1913
1913 1914 Paths in the local filesystem can either point to Mercurial
1914 1915 repositories or to bundle files (as created by 'hg bundle' or
1915 1916 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
1916 1917 allows access to a Mercurial repository where you simply use a web
1917 1918 server to publish the .hg directory as static content.
1918 1919
1919 1920 Some notes about using SSH with Mercurial:
1920 1921 - SSH requires an accessible shell account on the destination machine
1921 1922 and a copy of hg in the remote path or specified with as remotecmd.
1922 1923 - path is relative to the remote user's home directory by default.
1923 1924 Use an extra slash at the start of a path to specify an absolute path:
1924 1925 ssh://example.com//tmp/repository
1925 1926 - Mercurial doesn't use its own compression via SSH; the right thing
1926 1927 to do is to configure it in your ~/.ssh/config, e.g.:
1927 1928 Host *.mylocalnetwork.example.com
1928 1929 Compression no
1929 1930 Host *
1930 1931 Compression yes
1931 1932 Alternatively specify "ssh -C" as your ssh command in your hgrc or
1932 1933 with the --ssh command line option.
1933 1934 """
1934 1935 source = ui.expandpath(source)
1935 1936 setremoteconfig(ui, opts)
1936 1937
1937 1938 other = hg.repository(ui, source)
1938 1939 ui.status(_('pulling from %s\n') % (source))
1939 1940 revs = None
1940 1941 if opts['rev']:
1941 1942 if 'lookup' in other.capabilities:
1942 1943 revs = [other.lookup(rev) for rev in opts['rev']]
1943 1944 else:
1944 1945 error = _("Other repository doesn't support revision lookup, so a rev cannot be specified.")
1945 1946 raise util.Abort(error)
1946 1947 modheads = repo.pull(other, heads=revs, force=opts['force'])
1947 1948 return postincoming(ui, repo, modheads, opts['update'])
1948 1949
1949 1950 def push(ui, repo, dest=None, **opts):
1950 1951 """push changes to the specified destination
1951 1952
1952 1953 Push changes from the local repository to the given destination.
1953 1954
1954 1955 This is the symmetrical operation for pull. It helps to move
1955 1956 changes from the current repository to a different one. If the
1956 1957 destination is local this is identical to a pull in that directory
1957 1958 from the current one.
1958 1959
1959 1960 By default, push will refuse to run if it detects the result would
1960 1961 increase the number of remote heads. This generally indicates the
1961 1962 the client has forgotten to sync and merge before pushing.
1962 1963
1963 1964 Valid URLs are of the form:
1964 1965
1965 1966 local/filesystem/path (or file://local/filesystem/path)
1966 1967 ssh://[user@]host[:port]/[path]
1967 1968 http://[user@]host[:port]/[path]
1968 1969 https://[user@]host[:port]/[path]
1969 1970
1970 1971 Look at the help text for the pull command for important details
1971 1972 about ssh:// URLs.
1972 1973
1973 1974 Pushing to http:// and https:// URLs is only possible, if this
1974 1975 feature is explicitly enabled on the remote Mercurial server.
1975 1976 """
1976 1977 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1977 1978 setremoteconfig(ui, opts)
1978 1979
1979 1980 other = hg.repository(ui, dest)
1980 1981 ui.status('pushing to %s\n' % (dest))
1981 1982 revs = None
1982 1983 if opts['rev']:
1983 1984 revs = [repo.lookup(rev) for rev in opts['rev']]
1984 1985 r = repo.push(other, opts['force'], revs=revs)
1985 1986 return r == 0
1986 1987
1987 1988 def rawcommit(ui, repo, *pats, **opts):
1988 1989 """raw commit interface (DEPRECATED)
1989 1990
1990 1991 (DEPRECATED)
1991 1992 Lowlevel commit, for use in helper scripts.
1992 1993
1993 1994 This command is not intended to be used by normal users, as it is
1994 1995 primarily useful for importing from other SCMs.
1995 1996
1996 1997 This command is now deprecated and will be removed in a future
1997 1998 release, please use debugsetparents and commit instead.
1998 1999 """
1999 2000
2000 2001 ui.warn(_("(the rawcommit command is deprecated)\n"))
2001 2002
2002 2003 message = logmessage(opts)
2003 2004
2004 2005 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
2005 2006 if opts['files']:
2006 2007 files += open(opts['files']).read().splitlines()
2007 2008
2008 2009 parents = [repo.lookup(p) for p in opts['parent']]
2009 2010
2010 2011 try:
2011 2012 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2012 2013 except ValueError, inst:
2013 2014 raise util.Abort(str(inst))
2014 2015
2015 2016 def recover(ui, repo):
2016 2017 """roll back an interrupted transaction
2017 2018
2018 2019 Recover from an interrupted commit or pull.
2019 2020
2020 2021 This command tries to fix the repository status after an interrupted
2021 2022 operation. It should only be necessary when Mercurial suggests it.
2022 2023 """
2023 2024 if repo.recover():
2024 2025 return hg.verify(repo)
2025 2026 return 1
2026 2027
2027 2028 def remove(ui, repo, *pats, **opts):
2028 2029 """remove the specified files on the next commit
2029 2030
2030 2031 Schedule the indicated files for removal from the repository.
2031 2032
2032 2033 This only removes files from the current branch, not from the
2033 2034 entire project history. If the files still exist in the working
2034 2035 directory, they will be deleted from it. If invoked with --after,
2035 2036 files that have been manually deleted are marked as removed.
2036 2037
2037 2038 This command schedules the files to be removed at the next commit.
2038 2039 To undo a remove before that, see hg revert.
2039 2040
2040 2041 Modified files and added files are not removed by default. To
2041 2042 remove them, use the -f/--force option.
2042 2043 """
2043 2044 names = []
2044 2045 if not opts['after'] and not pats:
2045 2046 raise util.Abort(_('no files specified'))
2046 2047 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2047 2048 exact = dict.fromkeys(files)
2048 2049 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2049 2050 modified, added, removed, deleted, unknown = mardu
2050 2051 remove, forget = [], []
2051 2052 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2052 2053 reason = None
2053 2054 if abs not in deleted and opts['after']:
2054 2055 reason = _('is still present')
2055 2056 elif abs in modified and not opts['force']:
2056 2057 reason = _('is modified (use -f to force removal)')
2057 2058 elif abs in added:
2058 2059 if opts['force']:
2059 2060 forget.append(abs)
2060 2061 continue
2061 2062 reason = _('has been marked for add (use -f to force removal)')
2062 2063 elif abs in unknown:
2063 2064 reason = _('is not managed')
2064 2065 elif abs in removed:
2065 2066 continue
2066 2067 if reason:
2067 2068 if exact:
2068 2069 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2069 2070 else:
2070 2071 if ui.verbose or not exact:
2071 2072 ui.status(_('removing %s\n') % rel)
2072 2073 remove.append(abs)
2073 2074 repo.forget(forget)
2074 2075 repo.remove(remove, unlink=not opts['after'])
2075 2076
2076 2077 def rename(ui, repo, *pats, **opts):
2077 2078 """rename files; equivalent of copy + remove
2078 2079
2079 2080 Mark dest as copies of sources; mark sources for deletion. If
2080 2081 dest is a directory, copies are put in that directory. If dest is
2081 2082 a file, there can only be one source.
2082 2083
2083 2084 By default, this command copies the contents of files as they
2084 2085 stand in the working directory. If invoked with --after, the
2085 2086 operation is recorded, but no copying is performed.
2086 2087
2087 2088 This command takes effect in the next commit. To undo a rename
2088 2089 before that, see hg revert.
2089 2090 """
2090 2091 wlock = repo.wlock(0)
2091 2092 errs, copied = docopy(ui, repo, pats, opts, wlock)
2092 2093 names = []
2093 2094 for abs, rel, exact in copied:
2094 2095 if ui.verbose or not exact:
2095 2096 ui.status(_('removing %s\n') % rel)
2096 2097 names.append(abs)
2097 2098 if not opts.get('dry_run'):
2098 2099 repo.remove(names, True, wlock)
2099 2100 return errs
2100 2101
2101 2102 def revert(ui, repo, *pats, **opts):
2102 2103 """revert files or dirs to their states as of some revision
2103 2104
2104 2105 With no revision specified, revert the named files or directories
2105 2106 to the contents they had in the parent of the working directory.
2106 2107 This restores the contents of the affected files to an unmodified
2107 2108 state and unschedules adds, removes, copies, and renames. If the
2108 2109 working directory has two parents, you must explicitly specify the
2109 2110 revision to revert to.
2110 2111
2111 2112 Modified files are saved with a .orig suffix before reverting.
2112 2113 To disable these backups, use --no-backup.
2113 2114
2114 2115 Using the -r option, revert the given files or directories to their
2115 2116 contents as of a specific revision. This can be helpful to "roll
2116 2117 back" some or all of a change that should not have been committed.
2117 2118
2118 2119 Revert modifies the working directory. It does not commit any
2119 2120 changes, or change the parent of the working directory. If you
2120 2121 revert to a revision other than the parent of the working
2121 2122 directory, the reverted files will thus appear modified
2122 2123 afterwards.
2123 2124
2124 2125 If a file has been deleted, it is recreated. If the executable
2125 2126 mode of a file was changed, it is reset.
2126 2127
2127 2128 If names are given, all files matching the names are reverted.
2128 2129
2129 2130 If no arguments are given, no files are reverted.
2130 2131 """
2131 2132
2132 2133 if opts["date"]:
2133 2134 if opts["rev"]:
2134 2135 raise util.Abort(_("you can't specify a revision and a date"))
2135 2136 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2136 2137
2137 2138 if not pats and not opts['all']:
2138 2139 raise util.Abort(_('no files or directories specified; '
2139 2140 'use --all to revert the whole repo'))
2140 2141
2141 2142 parent, p2 = repo.dirstate.parents()
2142 2143 if not opts['rev'] and p2 != nullid:
2143 2144 raise util.Abort(_('uncommitted merge - please provide a '
2144 2145 'specific revision'))
2145 2146 node = repo.changectx(opts['rev']).node()
2146 2147 mf = repo.manifest.read(repo.changelog.read(node)[0])
2147 2148 if node == parent:
2148 2149 pmf = mf
2149 2150 else:
2150 2151 pmf = None
2151 2152
2152 2153 wlock = repo.wlock()
2153 2154
2154 2155 # need all matching names in dirstate and manifest of target rev,
2155 2156 # so have to walk both. do not print errors if files exist in one
2156 2157 # but not other.
2157 2158
2158 2159 names = {}
2159 2160 target_only = {}
2160 2161
2161 2162 # walk dirstate.
2162 2163
2163 2164 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2164 2165 badmatch=mf.has_key):
2165 2166 names[abs] = (rel, exact)
2166 2167 if src == 'b':
2167 2168 target_only[abs] = True
2168 2169
2169 2170 # walk target manifest.
2170 2171
2171 2172 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2172 2173 badmatch=names.has_key):
2173 2174 if abs in names: continue
2174 2175 names[abs] = (rel, exact)
2175 2176 target_only[abs] = True
2176 2177
2177 2178 changes = repo.status(match=names.has_key, wlock=wlock)[:5]
2178 2179 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2179 2180
2180 2181 revert = ([], _('reverting %s\n'))
2181 2182 add = ([], _('adding %s\n'))
2182 2183 remove = ([], _('removing %s\n'))
2183 2184 forget = ([], _('forgetting %s\n'))
2184 2185 undelete = ([], _('undeleting %s\n'))
2185 2186 update = {}
2186 2187
2187 2188 disptable = (
2188 2189 # dispatch table:
2189 2190 # file state
2190 2191 # action if in target manifest
2191 2192 # action if not in target manifest
2192 2193 # make backup if in target manifest
2193 2194 # make backup if not in target manifest
2194 2195 (modified, revert, remove, True, True),
2195 2196 (added, revert, forget, True, False),
2196 2197 (removed, undelete, None, False, False),
2197 2198 (deleted, revert, remove, False, False),
2198 2199 (unknown, add, None, True, False),
2199 2200 (target_only, add, None, False, False),
2200 2201 )
2201 2202
2202 2203 entries = names.items()
2203 2204 entries.sort()
2204 2205
2205 2206 for abs, (rel, exact) in entries:
2206 2207 mfentry = mf.get(abs)
2207 2208 def handle(xlist, dobackup):
2208 2209 xlist[0].append(abs)
2209 2210 update[abs] = 1
2210 2211 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2211 2212 bakname = "%s.orig" % rel
2212 2213 ui.note(_('saving current version of %s as %s\n') %
2213 2214 (rel, bakname))
2214 2215 if not opts.get('dry_run'):
2215 2216 util.copyfile(rel, bakname)
2216 2217 if ui.verbose or not exact:
2217 2218 ui.status(xlist[1] % rel)
2218 2219 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2219 2220 if abs not in table: continue
2220 2221 # file has changed in dirstate
2221 2222 if mfentry:
2222 2223 handle(hitlist, backuphit)
2223 2224 elif misslist is not None:
2224 2225 handle(misslist, backupmiss)
2225 2226 else:
2226 2227 if exact: ui.warn(_('file not managed: %s\n') % rel)
2227 2228 break
2228 2229 else:
2229 2230 # file has not changed in dirstate
2230 2231 if node == parent:
2231 2232 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2232 2233 continue
2233 2234 if pmf is None:
2234 2235 # only need parent manifest in this unlikely case,
2235 2236 # so do not read by default
2236 2237 pmf = repo.manifest.read(repo.changelog.read(parent)[0])
2237 2238 if abs in pmf:
2238 2239 if mfentry:
2239 2240 # if version of file is same in parent and target
2240 2241 # manifests, do nothing
2241 2242 if pmf[abs] != mfentry:
2242 2243 handle(revert, False)
2243 2244 else:
2244 2245 handle(remove, False)
2245 2246
2246 2247 if not opts.get('dry_run'):
2247 2248 repo.dirstate.forget(forget[0])
2248 2249 r = hg.revert(repo, node, update.has_key, wlock)
2249 2250 repo.dirstate.update(add[0], 'a')
2250 2251 repo.dirstate.update(undelete[0], 'n')
2251 2252 repo.dirstate.update(remove[0], 'r')
2252 2253 return r
2253 2254
2254 2255 def rollback(ui, repo):
2255 2256 """roll back the last transaction in this repository
2256 2257
2257 2258 Roll back the last transaction in this repository, restoring the
2258 2259 project to its state prior to the transaction.
2259 2260
2260 2261 Transactions are used to encapsulate the effects of all commands
2261 2262 that create new changesets or propagate existing changesets into a
2262 2263 repository. For example, the following commands are transactional,
2263 2264 and their effects can be rolled back:
2264 2265
2265 2266 commit
2266 2267 import
2267 2268 pull
2268 2269 push (with this repository as destination)
2269 2270 unbundle
2270 2271
2271 2272 This command should be used with care. There is only one level of
2272 2273 rollback, and there is no way to undo a rollback.
2273 2274
2274 2275 This command is not intended for use on public repositories. Once
2275 2276 changes are visible for pull by other users, rolling a transaction
2276 2277 back locally is ineffective (someone else may already have pulled
2277 2278 the changes). Furthermore, a race is possible with readers of the
2278 2279 repository; for example an in-progress pull from the repository
2279 2280 may fail if a rollback is performed.
2280 2281 """
2281 2282 repo.rollback()
2282 2283
2283 2284 def root(ui, repo):
2284 2285 """print the root (top) of the current working dir
2285 2286
2286 2287 Print the root directory of the current repository.
2287 2288 """
2288 2289 ui.write(repo.root + "\n")
2289 2290
2290 2291 def serve(ui, repo, **opts):
2291 2292 """export the repository via HTTP
2292 2293
2293 2294 Start a local HTTP repository browser and pull server.
2294 2295
2295 2296 By default, the server logs accesses to stdout and errors to
2296 2297 stderr. Use the "-A" and "-E" options to log to files.
2297 2298 """
2298 2299
2299 2300 if opts["stdio"]:
2300 2301 if repo is None:
2301 2302 raise hg.RepoError(_("There is no Mercurial repository here"
2302 2303 " (.hg not found)"))
2303 2304 s = sshserver.sshserver(ui, repo)
2304 2305 s.serve_forever()
2305 2306
2306 2307 optlist = ("name templates style address port ipv6"
2307 2308 " accesslog errorlog webdir_conf")
2308 2309 for o in optlist.split():
2309 2310 if opts[o]:
2310 2311 ui.setconfig("web", o, str(opts[o]))
2311 2312
2312 2313 if repo is None and not ui.config("web", "webdir_conf"):
2313 2314 raise hg.RepoError(_("There is no Mercurial repository here"
2314 2315 " (.hg not found)"))
2315 2316
2316 2317 if opts['daemon'] and not opts['daemon_pipefds']:
2317 2318 rfd, wfd = os.pipe()
2318 2319 args = sys.argv[:]
2319 2320 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2320 2321 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2321 2322 args[0], args)
2322 2323 os.close(wfd)
2323 2324 os.read(rfd, 1)
2324 2325 os._exit(0)
2325 2326
2326 2327 httpd = hgweb.server.create_server(ui, repo)
2327 2328
2328 2329 if ui.verbose:
2329 2330 if httpd.port != 80:
2330 2331 ui.status(_('listening at http://%s:%d/\n') %
2331 2332 (httpd.addr, httpd.port))
2332 2333 else:
2333 2334 ui.status(_('listening at http://%s/\n') % httpd.addr)
2334 2335
2335 2336 if opts['pid_file']:
2336 2337 fp = open(opts['pid_file'], 'w')
2337 2338 fp.write(str(os.getpid()) + '\n')
2338 2339 fp.close()
2339 2340
2340 2341 if opts['daemon_pipefds']:
2341 2342 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2342 2343 os.close(rfd)
2343 2344 os.write(wfd, 'y')
2344 2345 os.close(wfd)
2345 2346 sys.stdout.flush()
2346 2347 sys.stderr.flush()
2347 2348 fd = os.open(util.nulldev, os.O_RDWR)
2348 2349 if fd != 0: os.dup2(fd, 0)
2349 2350 if fd != 1: os.dup2(fd, 1)
2350 2351 if fd != 2: os.dup2(fd, 2)
2351 2352 if fd not in (0, 1, 2): os.close(fd)
2352 2353
2353 2354 httpd.serve_forever()
2354 2355
2355 2356 def status(ui, repo, *pats, **opts):
2356 2357 """show changed files in the working directory
2357 2358
2358 2359 Show status of files in the repository. If names are given, only
2359 2360 files that match are shown. Files that are clean or ignored, are
2360 2361 not listed unless -c (clean), -i (ignored) or -A is given.
2361 2362
2362 2363 NOTE: status may appear to disagree with diff if permissions have
2363 2364 changed or a merge has occurred. The standard diff format does not
2364 2365 report permission changes and diff only reports changes relative
2365 2366 to one merge parent.
2366 2367
2367 2368 If one revision is given, it is used as the base revision.
2368 2369 If two revisions are given, the difference between them is shown.
2369 2370
2370 2371 The codes used to show the status of files are:
2371 2372 M = modified
2372 2373 A = added
2373 2374 R = removed
2374 2375 C = clean
2375 2376 ! = deleted, but still tracked
2376 2377 ? = not tracked
2377 2378 I = ignored (not shown by default)
2378 2379 = the previous added file was copied from here
2379 2380 """
2380 2381
2381 2382 all = opts['all']
2382 2383 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2383 2384
2384 2385 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2385 2386 cwd = (pats and repo.getcwd()) or ''
2386 2387 modified, added, removed, deleted, unknown, ignored, clean = [
2387 2388 n for n in repo.status(node1=node1, node2=node2, files=files,
2388 2389 match=matchfn,
2389 2390 list_ignored=all or opts['ignored'],
2390 2391 list_clean=all or opts['clean'])]
2391 2392
2392 2393 changetypes = (('modified', 'M', modified),
2393 2394 ('added', 'A', added),
2394 2395 ('removed', 'R', removed),
2395 2396 ('deleted', '!', deleted),
2396 2397 ('unknown', '?', unknown),
2397 2398 ('ignored', 'I', ignored))
2398 2399
2399 2400 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2400 2401
2401 2402 end = opts['print0'] and '\0' or '\n'
2402 2403
2403 2404 for opt, char, changes in ([ct for ct in explicit_changetypes
2404 2405 if all or opts[ct[0]]]
2405 2406 or changetypes):
2406 2407 if opts['no_status']:
2407 2408 format = "%%s%s" % end
2408 2409 else:
2409 2410 format = "%s %%s%s" % (char, end)
2410 2411
2411 2412 for f in changes:
2412 2413 ui.write(format % util.pathto(cwd, f))
2413 2414 if ((all or opts.get('copies')) and not opts.get('no_status')):
2414 2415 copied = repo.dirstate.copied(f)
2415 2416 if copied:
2416 2417 ui.write(' %s%s' % (util.pathto(cwd, copied), end))
2417 2418
2418 2419 def tag(ui, repo, name, rev_=None, **opts):
2419 2420 """add a tag for the current or given revision
2420 2421
2421 2422 Name a particular revision using <name>.
2422 2423
2423 2424 Tags are used to name particular revisions of the repository and are
2424 2425 very useful to compare different revision, to go back to significant
2425 2426 earlier versions or to mark branch points as releases, etc.
2426 2427
2427 2428 If no revision is given, the parent of the working directory is used,
2428 2429 or tip if no revision is checked out.
2429 2430
2430 2431 To facilitate version control, distribution, and merging of tags,
2431 2432 they are stored as a file named ".hgtags" which is managed
2432 2433 similarly to other project files and can be hand-edited if
2433 2434 necessary. The file '.hg/localtags' is used for local tags (not
2434 2435 shared among repositories).
2435 2436 """
2436 2437 if name in ['tip', '.', 'null']:
2437 2438 raise util.Abort(_("the name '%s' is reserved") % name)
2438 2439 if rev_ is not None:
2439 2440 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2440 2441 "please use 'hg tag [-r REV] NAME' instead\n"))
2441 2442 if opts['rev']:
2442 2443 raise util.Abort(_("use only one form to specify the revision"))
2443 2444 if opts['rev']:
2444 2445 rev_ = opts['rev']
2445 2446 if not rev_ and repo.dirstate.parents()[1] != nullid:
2446 2447 raise util.Abort(_('uncommitted merge - please provide a '
2447 2448 'specific revision'))
2448 2449 r = repo.changectx(rev_).node()
2449 2450
2450 2451 message = opts['message']
2451 2452 if not message:
2452 2453 message = _('Added tag %s for changeset %s') % (name, short(r))
2453 2454
2454 2455 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2455 2456
2456 2457 def tags(ui, repo):
2457 2458 """list repository tags
2458 2459
2459 2460 List the repository tags.
2460 2461
2461 2462 This lists both regular and local tags.
2462 2463 """
2463 2464
2464 2465 l = repo.tagslist()
2465 2466 l.reverse()
2466 2467 hexfunc = ui.debugflag and hex or short
2467 2468 for t, n in l:
2468 2469 try:
2469 2470 r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
2470 2471 except KeyError:
2471 2472 r = " ?:?"
2472 2473 if ui.quiet:
2473 2474 ui.write("%s\n" % t)
2474 2475 else:
2475 2476 t = util.localsub(t, 30)
2476 2477 t += " " * (30 - util.locallen(t))
2477 2478 ui.write("%s %s\n" % (t, r))
2478 2479
2479 2480 def tip(ui, repo, **opts):
2480 2481 """show the tip revision
2481 2482
2482 2483 Show the tip revision.
2483 2484 """
2484 2485 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2485 2486
2486 2487 def unbundle(ui, repo, fname, **opts):
2487 2488 """apply a changegroup file
2488 2489
2489 2490 Apply a compressed changegroup file generated by the bundle
2490 2491 command.
2491 2492 """
2492 2493 gen = changegroup.readbundle(urllib.urlopen(fname), fname)
2493 2494 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2494 2495 return postincoming(ui, repo, modheads, opts['update'])
2495 2496
2496 2497 def update(ui, repo, node=None, clean=False, branch=None, date=None):
2497 2498 """update working directory
2498 2499
2499 2500 Update the working directory to the specified revision.
2500 2501
2501 2502 If there are no outstanding changes in the working directory and
2502 2503 there is a linear relationship between the current version and the
2503 2504 requested version, the result is the requested version.
2504 2505
2505 2506 To merge the working directory with another revision, use the
2506 2507 merge command.
2507 2508
2508 2509 By default, update will refuse to run if doing so would require
2509 2510 discarding local changes.
2510 2511 """
2511 2512 if date:
2512 2513 if node:
2513 2514 raise util.Abort(_("you can't specify a revision and a date"))
2514 2515 node = cmdutil.finddate(ui, repo, date)
2515 2516
2516 2517 node = _lookup(repo, node, branch)
2517 2518 if clean:
2518 2519 return hg.clean(repo, node)
2519 2520 else:
2520 2521 return hg.update(repo, node)
2521 2522
2522 2523 def _lookup(repo, node, branch=None):
2523 2524 if branch:
2524 2525 repo.ui.warn(_("the --branch option is deprecated, "
2525 2526 "please use 'hg branch' instead\n"))
2526 2527 br = repo.branchlookup(branch=branch)
2527 2528 found = []
2528 2529 for x in br:
2529 2530 if branch in br[x]:
2530 2531 found.append(x)
2531 2532 if len(found) > 1:
2532 2533 repo.ui.warn(_("Found multiple heads for %s\n") % branch)
2533 2534 for x in found:
2534 2535 cmdutil.show_changeset(ui, repo, {}).show(changenode=x)
2535 2536 raise util.Abort("")
2536 2537 if len(found) == 1:
2537 2538 node = found[0]
2538 2539 repo.ui.warn(_("Using head %s for branch %s\n")
2539 2540 % (short(node), branch))
2540 2541 else:
2541 2542 raise util.Abort(_("branch %s not found") % branch)
2542 2543 else:
2543 2544 node = node and repo.lookup(node) or repo.changelog.tip()
2544 2545 return node
2545 2546
2546 2547 def verify(ui, repo):
2547 2548 """verify the integrity of the repository
2548 2549
2549 2550 Verify the integrity of the current repository.
2550 2551
2551 2552 This will perform an extensive check of the repository's
2552 2553 integrity, validating the hashes and checksums of each entry in
2553 2554 the changelog, manifest, and tracked files, as well as the
2554 2555 integrity of their crosslinks and indices.
2555 2556 """
2556 2557 return hg.verify(repo)
2557 2558
2558 2559 def version_(ui):
2559 2560 """output version and copyright information"""
2560 2561 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2561 2562 % version.get_version())
2562 2563 ui.status(_(
2563 2564 "\nCopyright (C) 2005, 2006 Matt Mackall <mpm@selenic.com>\n"
2564 2565 "This is free software; see the source for copying conditions. "
2565 2566 "There is NO\nwarranty; "
2566 2567 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2567 2568 ))
2568 2569
2569 2570 # Command options and aliases are listed here, alphabetically
2570 2571
2571 2572 globalopts = [
2572 2573 ('R', 'repository', '',
2573 2574 _('repository root directory or symbolic path name')),
2574 2575 ('', 'cwd', '', _('change working directory')),
2575 2576 ('y', 'noninteractive', None,
2576 2577 _('do not prompt, assume \'yes\' for any required answers')),
2577 2578 ('q', 'quiet', None, _('suppress output')),
2578 2579 ('v', 'verbose', None, _('enable additional output')),
2579 2580 ('', 'config', [], _('set/override config option')),
2580 2581 ('', 'debug', None, _('enable debugging output')),
2581 2582 ('', 'debugger', None, _('start debugger')),
2582 2583 ('', 'encoding', util._encoding, _('set the charset encoding')),
2583 2584 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2584 2585 ('', 'lsprof', None, _('print improved command execution profile')),
2585 2586 ('', 'traceback', None, _('print traceback on exception')),
2586 2587 ('', 'time', None, _('time how long the command takes')),
2587 2588 ('', 'profile', None, _('print command execution profile')),
2588 2589 ('', 'version', None, _('output version information and exit')),
2589 2590 ('h', 'help', None, _('display help and exit')),
2590 2591 ]
2591 2592
2592 2593 dryrunopts = [('n', 'dry-run', None,
2593 2594 _('do not perform actions, just print output'))]
2594 2595
2595 2596 remoteopts = [
2596 2597 ('e', 'ssh', '', _('specify ssh command to use')),
2597 2598 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2598 2599 ]
2599 2600
2600 2601 walkopts = [
2601 2602 ('I', 'include', [], _('include names matching the given patterns')),
2602 2603 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2603 2604 ]
2604 2605
2605 2606 commitopts = [
2606 2607 ('m', 'message', '', _('use <text> as commit message')),
2607 2608 ('l', 'logfile', '', _('read commit message from <file>')),
2608 2609 ]
2609 2610
2610 2611 table = {
2611 2612 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2612 2613 "addremove":
2613 2614 (addremove,
2614 2615 [('s', 'similarity', '',
2615 2616 _('guess renamed files by similarity (0<=s<=100)')),
2616 2617 ] + walkopts + dryrunopts,
2617 2618 _('hg addremove [OPTION]... [FILE]...')),
2618 2619 "^annotate":
2619 2620 (annotate,
2620 2621 [('r', 'rev', '', _('annotate the specified revision')),
2621 2622 ('f', 'follow', None, _('follow file copies and renames')),
2622 2623 ('a', 'text', None, _('treat all files as text')),
2623 2624 ('u', 'user', None, _('list the author')),
2624 2625 ('d', 'date', None, _('list the date')),
2625 2626 ('n', 'number', None, _('list the revision number (default)')),
2626 2627 ('c', 'changeset', None, _('list the changeset')),
2627 2628 ] + walkopts,
2628 2629 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] FILE...')),
2629 2630 "archive":
2630 2631 (archive,
2631 2632 [('', 'no-decode', None, _('do not pass files through decoders')),
2632 2633 ('p', 'prefix', '', _('directory prefix for files in archive')),
2633 2634 ('r', 'rev', '', _('revision to distribute')),
2634 2635 ('t', 'type', '', _('type of distribution to create')),
2635 2636 ] + walkopts,
2636 2637 _('hg archive [OPTION]... DEST')),
2637 2638 "backout":
2638 2639 (backout,
2639 2640 [('', 'merge', None,
2640 2641 _('merge with old dirstate parent after backout')),
2641 2642 ('d', 'date', '', _('record datecode as commit date')),
2642 2643 ('', 'parent', '', _('parent to choose when backing out merge')),
2643 2644 ('u', 'user', '', _('record user as committer')),
2644 2645 ] + walkopts + commitopts,
2645 2646 _('hg backout [OPTION]... REV')),
2646 2647 "branch": (branch, [], _('hg branch [NAME]')),
2647 2648 "branches": (branches, [], _('hg branches')),
2648 2649 "bundle":
2649 2650 (bundle,
2650 2651 [('f', 'force', None,
2651 2652 _('run even when remote repository is unrelated')),
2652 2653 ('r', 'rev', [],
2653 2654 _('a changeset you would like to bundle')),
2654 2655 ('', 'base', [],
2655 2656 _('a base changeset to specify instead of a destination')),
2656 2657 ] + remoteopts,
2657 2658 _('hg bundle [-f] [-r REV]... [--base REV]... FILE [DEST]')),
2658 2659 "cat":
2659 2660 (cat,
2660 2661 [('o', 'output', '', _('print output to file with formatted name')),
2661 2662 ('r', 'rev', '', _('print the given revision')),
2662 2663 ] + walkopts,
2663 2664 _('hg cat [OPTION]... FILE...')),
2664 2665 "^clone":
2665 2666 (clone,
2666 2667 [('U', 'noupdate', None, _('do not update the new working directory')),
2667 2668 ('r', 'rev', [],
2668 2669 _('a changeset you would like to have after cloning')),
2669 2670 ('', 'pull', None, _('use pull protocol to copy metadata')),
2670 2671 ('', 'uncompressed', None,
2671 2672 _('use uncompressed transfer (fast over LAN)')),
2672 2673 ] + remoteopts,
2673 2674 _('hg clone [OPTION]... SOURCE [DEST]')),
2674 2675 "^commit|ci":
2675 2676 (commit,
2676 2677 [('A', 'addremove', None,
2677 2678 _('mark new/missing files as added/removed before committing')),
2678 2679 ('d', 'date', '', _('record datecode as commit date')),
2679 2680 ('u', 'user', '', _('record user as commiter')),
2680 2681 ] + walkopts + commitopts,
2681 2682 _('hg commit [OPTION]... [FILE]...')),
2682 2683 "copy|cp":
2683 2684 (copy,
2684 2685 [('A', 'after', None, _('record a copy that has already occurred')),
2685 2686 ('f', 'force', None,
2686 2687 _('forcibly copy over an existing managed file')),
2687 2688 ] + walkopts + dryrunopts,
2688 2689 _('hg copy [OPTION]... [SOURCE]... DEST')),
2689 2690 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2690 2691 "debugcomplete":
2691 2692 (debugcomplete,
2692 2693 [('o', 'options', None, _('show the command options'))],
2693 2694 _('debugcomplete [-o] CMD')),
2694 2695 "debuginstall": (debuginstall, [], _('debuginstall')),
2695 2696 "debugrebuildstate":
2696 2697 (debugrebuildstate,
2697 2698 [('r', 'rev', '', _('revision to rebuild to'))],
2698 2699 _('debugrebuildstate [-r REV] [REV]')),
2699 2700 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2700 2701 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2701 2702 "debugstate": (debugstate, [], _('debugstate')),
2702 2703 "debugdate":
2703 2704 (debugdate,
2704 2705 [('e', 'extended', None, _('try extended date formats'))],
2705 2706 _('debugdate [-e] DATE [RANGE]')),
2706 2707 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2707 2708 "debugindex": (debugindex, [], _('debugindex FILE')),
2708 2709 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2709 2710 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2710 2711 "debugwalk": (debugwalk, walkopts, _('debugwalk [OPTION]... [FILE]...')),
2711 2712 "^diff":
2712 2713 (diff,
2713 2714 [('r', 'rev', [], _('revision')),
2714 2715 ('a', 'text', None, _('treat all files as text')),
2715 2716 ('p', 'show-function', None,
2716 2717 _('show which function each change is in')),
2717 2718 ('g', 'git', None, _('use git extended diff format')),
2718 2719 ('', 'nodates', None, _("don't include dates in diff headers")),
2719 2720 ('w', 'ignore-all-space', None,
2720 2721 _('ignore white space when comparing lines')),
2721 2722 ('b', 'ignore-space-change', None,
2722 2723 _('ignore changes in the amount of white space')),
2723 2724 ('B', 'ignore-blank-lines', None,
2724 2725 _('ignore changes whose lines are all blank')),
2725 2726 ] + walkopts,
2726 2727 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
2727 2728 "^export":
2728 2729 (export,
2729 2730 [('o', 'output', '', _('print output to file with formatted name')),
2730 2731 ('a', 'text', None, _('treat all files as text')),
2731 2732 ('g', 'git', None, _('use git extended diff format')),
2732 2733 ('', 'nodates', None, _("don't include dates in diff headers")),
2733 2734 ('', 'switch-parent', None, _('diff against the second parent'))],
2734 2735 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
2735 2736 "grep":
2736 2737 (grep,
2737 2738 [('0', 'print0', None, _('end fields with NUL')),
2738 2739 ('', 'all', None, _('print all revisions that match')),
2739 2740 ('f', 'follow', None,
2740 2741 _('follow changeset history, or file history across copies and renames')),
2741 2742 ('i', 'ignore-case', None, _('ignore case when matching')),
2742 2743 ('l', 'files-with-matches', None,
2743 2744 _('print only filenames and revs that match')),
2744 2745 ('n', 'line-number', None, _('print matching line numbers')),
2745 2746 ('r', 'rev', [], _('search in given revision range')),
2746 2747 ('u', 'user', None, _('print user who committed change')),
2747 2748 ] + walkopts,
2748 2749 _('hg grep [OPTION]... PATTERN [FILE]...')),
2749 2750 "heads":
2750 2751 (heads,
2751 2752 [('b', 'branches', None, _('show branches (DEPRECATED)')),
2752 2753 ('', 'style', '', _('display using template map file')),
2753 2754 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2754 2755 ('', 'template', '', _('display with template'))],
2755 2756 _('hg heads [-r REV]')),
2756 2757 "help": (help_, [], _('hg help [COMMAND]')),
2757 2758 "identify|id": (identify, [], _('hg identify')),
2758 2759 "import|patch":
2759 2760 (import_,
2760 2761 [('p', 'strip', 1,
2761 2762 _('directory strip option for patch. This has the same\n'
2762 2763 'meaning as the corresponding patch option')),
2763 2764 ('b', 'base', '', _('base path (DEPRECATED)')),
2764 2765 ('f', 'force', None,
2765 2766 _('skip check for outstanding uncommitted changes'))] + commitopts,
2766 2767 _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
2767 2768 "incoming|in": (incoming,
2768 2769 [('M', 'no-merges', None, _('do not show merges')),
2769 2770 ('f', 'force', None,
2770 2771 _('run even when remote repository is unrelated')),
2771 2772 ('', 'style', '', _('display using template map file')),
2772 2773 ('n', 'newest-first', None, _('show newest record first')),
2773 2774 ('', 'bundle', '', _('file to store the bundles into')),
2774 2775 ('p', 'patch', None, _('show patch')),
2775 2776 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2776 2777 ('', 'template', '', _('display with template')),
2777 2778 ] + remoteopts,
2778 2779 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
2779 2780 ' [--bundle FILENAME] [SOURCE]')),
2780 2781 "^init":
2781 2782 (init,
2782 2783 remoteopts,
2783 2784 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
2784 2785 "locate":
2785 2786 (locate,
2786 2787 [('r', 'rev', '', _('search the repository as it stood at rev')),
2787 2788 ('0', 'print0', None,
2788 2789 _('end filenames with NUL, for use with xargs')),
2789 2790 ('f', 'fullpath', None,
2790 2791 _('print complete paths from the filesystem root')),
2791 2792 ] + walkopts,
2792 2793 _('hg locate [OPTION]... [PATTERN]...')),
2793 2794 "^log|history":
2794 2795 (log,
2795 2796 [('b', 'branches', None, _('show branches (DEPRECATED)')),
2796 2797 ('f', 'follow', None,
2797 2798 _('follow changeset history, or file history across copies and renames')),
2798 2799 ('', 'follow-first', None,
2799 2800 _('only follow the first parent of merge changesets')),
2800 2801 ('d', 'date', '', _('show revs matching date spec')),
2801 2802 ('C', 'copies', None, _('show copied files')),
2802 2803 ('k', 'keyword', [], _('search for a keyword')),
2803 2804 ('l', 'limit', '', _('limit number of changes displayed')),
2804 2805 ('r', 'rev', [], _('show the specified revision or range')),
2805 2806 ('', 'removed', None, _('include revs where files were removed')),
2806 2807 ('M', 'no-merges', None, _('do not show merges')),
2807 2808 ('', 'style', '', _('display using template map file')),
2808 2809 ('m', 'only-merges', None, _('show only merges')),
2809 2810 ('p', 'patch', None, _('show patch')),
2810 2811 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
2811 2812 ('', 'template', '', _('display with template')),
2812 2813 ] + walkopts,
2813 2814 _('hg log [OPTION]... [FILE]')),
2814 2815 "manifest": (manifest, [], _('hg manifest [REV]')),
2815 2816 "^merge":
2816 2817 (merge,
2817 2818 [('b', 'branch', '', _('merge with head of a specific branch (DEPRECATED)')),
2818 2819 ('f', 'force', None, _('force a merge with outstanding changes'))],
2819 2820 _('hg merge [-f] [REV]')),
2820 2821 "outgoing|out": (outgoing,
2821 2822 [('M', 'no-merges', None, _('do not show merges')),
2822 2823 ('f', 'force', None,
2823 2824 _('run even when remote repository is unrelated')),
2824 2825 ('p', 'patch', None, _('show patch')),
2825 2826 ('', 'style', '', _('display using template map file')),
2826 2827 ('r', 'rev', [], _('a specific revision you would like to push')),
2827 2828 ('n', 'newest-first', None, _('show newest record first')),
2828 2829 ('', 'template', '', _('display with template')),
2829 2830 ] + remoteopts,
2830 2831 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
2831 2832 "^parents":
2832 2833 (parents,
2833 2834 [('b', 'branches', None, _('show branches (DEPRECATED)')),
2834 2835 ('r', 'rev', '', _('show parents from the specified rev')),
2835 2836 ('', 'style', '', _('display using template map file')),
2836 2837 ('', 'template', '', _('display with template'))],
2837 2838 _('hg parents [-r REV] [FILE]')),
2838 2839 "paths": (paths, [], _('hg paths [NAME]')),
2839 2840 "^pull":
2840 2841 (pull,
2841 2842 [('u', 'update', None,
2842 2843 _('update to new tip if changesets were pulled')),
2843 2844 ('f', 'force', None,
2844 2845 _('run even when remote repository is unrelated')),
2845 2846 ('r', 'rev', [],
2846 2847 _('a specific revision up to which you would like to pull')),
2847 2848 ] + remoteopts,
2848 2849 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
2849 2850 "^push":
2850 2851 (push,
2851 2852 [('f', 'force', None, _('force push')),
2852 2853 ('r', 'rev', [], _('a specific revision you would like to push')),
2853 2854 ] + remoteopts,
2854 2855 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
2855 2856 "debugrawcommit|rawcommit":
2856 2857 (rawcommit,
2857 2858 [('p', 'parent', [], _('parent')),
2858 2859 ('d', 'date', '', _('date code')),
2859 2860 ('u', 'user', '', _('user')),
2860 2861 ('F', 'files', '', _('file list'))
2861 2862 ] + commitopts,
2862 2863 _('hg debugrawcommit [OPTION]... [FILE]...')),
2863 2864 "recover": (recover, [], _('hg recover')),
2864 2865 "^remove|rm":
2865 2866 (remove,
2866 2867 [('A', 'after', None, _('record remove that has already occurred')),
2867 2868 ('f', 'force', None, _('remove file even if modified')),
2868 2869 ] + walkopts,
2869 2870 _('hg remove [OPTION]... FILE...')),
2870 2871 "rename|mv":
2871 2872 (rename,
2872 2873 [('A', 'after', None, _('record a rename that has already occurred')),
2873 2874 ('f', 'force', None,
2874 2875 _('forcibly copy over an existing managed file')),
2875 2876 ] + walkopts + dryrunopts,
2876 2877 _('hg rename [OPTION]... SOURCE... DEST')),
2877 2878 "^revert":
2878 2879 (revert,
2879 2880 [('a', 'all', None, _('revert all changes when no arguments given')),
2880 2881 ('d', 'date', '', _('tipmost revision matching date')),
2881 2882 ('r', 'rev', '', _('revision to revert to')),
2882 2883 ('', 'no-backup', None, _('do not save backup copies of files')),
2883 2884 ] + walkopts + dryrunopts,
2884 2885 _('hg revert [OPTION]... [-r REV] [NAME]...')),
2885 2886 "rollback": (rollback, [], _('hg rollback')),
2886 2887 "root": (root, [], _('hg root')),
2887 2888 "showconfig|debugconfig":
2888 2889 (showconfig,
2889 2890 [('u', 'untrusted', None, _('show untrusted configuration options'))],
2890 2891 _('showconfig [-u] [NAME]...')),
2891 2892 "^serve":
2892 2893 (serve,
2893 2894 [('A', 'accesslog', '', _('name of access log file to write to')),
2894 2895 ('d', 'daemon', None, _('run server in background')),
2895 2896 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
2896 2897 ('E', 'errorlog', '', _('name of error log file to write to')),
2897 2898 ('p', 'port', 0, _('port to use (default: 8000)')),
2898 2899 ('a', 'address', '', _('address to use')),
2899 2900 ('n', 'name', '',
2900 2901 _('name to show in web pages (default: working dir)')),
2901 2902 ('', 'webdir-conf', '', _('name of the webdir config file'
2902 2903 ' (serve more than one repo)')),
2903 2904 ('', 'pid-file', '', _('name of file to write process ID to')),
2904 2905 ('', 'stdio', None, _('for remote clients')),
2905 2906 ('t', 'templates', '', _('web templates to use')),
2906 2907 ('', 'style', '', _('template style to use')),
2907 2908 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
2908 2909 _('hg serve [OPTION]...')),
2909 2910 "^status|st":
2910 2911 (status,
2911 2912 [('A', 'all', None, _('show status of all files')),
2912 2913 ('m', 'modified', None, _('show only modified files')),
2913 2914 ('a', 'added', None, _('show only added files')),
2914 2915 ('r', 'removed', None, _('show only removed files')),
2915 2916 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
2916 2917 ('c', 'clean', None, _('show only files without changes')),
2917 2918 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
2918 2919 ('i', 'ignored', None, _('show ignored files')),
2919 2920 ('n', 'no-status', None, _('hide status prefix')),
2920 2921 ('C', 'copies', None, _('show source of copied files')),
2921 2922 ('0', 'print0', None,
2922 2923 _('end filenames with NUL, for use with xargs')),
2923 2924 ('', 'rev', [], _('show difference from revision')),
2924 2925 ] + walkopts,
2925 2926 _('hg status [OPTION]... [FILE]...')),
2926 2927 "tag":
2927 2928 (tag,
2928 2929 [('l', 'local', None, _('make the tag local')),
2929 2930 ('m', 'message', '', _('message for tag commit log entry')),
2930 2931 ('d', 'date', '', _('record datecode as commit date')),
2931 2932 ('u', 'user', '', _('record user as commiter')),
2932 2933 ('r', 'rev', '', _('revision to tag'))],
2933 2934 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
2934 2935 "tags": (tags, [], _('hg tags')),
2935 2936 "tip":
2936 2937 (tip,
2937 2938 [('b', 'branches', None, _('show branches (DEPRECATED)')),
2938 2939 ('', 'style', '', _('display using template map file')),
2939 2940 ('p', 'patch', None, _('show patch')),
2940 2941 ('', 'template', '', _('display with template'))],
2941 2942 _('hg tip [-p]')),
2942 2943 "unbundle":
2943 2944 (unbundle,
2944 2945 [('u', 'update', None,
2945 2946 _('update to new tip if changesets were unbundled'))],
2946 2947 _('hg unbundle [-u] FILE')),
2947 2948 "^update|up|checkout|co":
2948 2949 (update,
2949 2950 [('b', 'branch', '',
2950 2951 _('checkout the head of a specific branch (DEPRECATED)')),
2951 2952 ('C', 'clean', None, _('overwrite locally modified files')),
2952 2953 ('d', 'date', '', _('tipmost revision matching date'))],
2953 2954 _('hg update [-C] [-d DATE] [REV]')),
2954 2955 "verify": (verify, [], _('hg verify')),
2955 2956 "version": (version_, [], _('hg version')),
2956 2957 }
2957 2958
2958 2959 norepo = ("clone init version help debugancestor debugcomplete debugdata"
2959 2960 " debugindex debugindexdot debugdate debuginstall")
2960 2961 optionalrepo = ("paths serve showconfig")
2961 2962
2962 2963 def findpossible(ui, cmd):
2963 2964 """
2964 2965 Return cmd -> (aliases, command table entry)
2965 2966 for each matching command.
2966 2967 Return debug commands (or their aliases) only if no normal command matches.
2967 2968 """
2968 2969 choice = {}
2969 2970 debugchoice = {}
2970 2971 for e in table.keys():
2971 2972 aliases = e.lstrip("^").split("|")
2972 2973 found = None
2973 2974 if cmd in aliases:
2974 2975 found = cmd
2975 2976 elif not ui.config("ui", "strict"):
2976 2977 for a in aliases:
2977 2978 if a.startswith(cmd):
2978 2979 found = a
2979 2980 break
2980 2981 if found is not None:
2981 2982 if aliases[0].startswith("debug") or found.startswith("debug"):
2982 2983 debugchoice[found] = (aliases, table[e])
2983 2984 else:
2984 2985 choice[found] = (aliases, table[e])
2985 2986
2986 2987 if not choice and debugchoice:
2987 2988 choice = debugchoice
2988 2989
2989 2990 return choice
2990 2991
2991 2992 def findcmd(ui, cmd):
2992 2993 """Return (aliases, command table entry) for command string."""
2993 2994 choice = findpossible(ui, cmd)
2994 2995
2995 2996 if choice.has_key(cmd):
2996 2997 return choice[cmd]
2997 2998
2998 2999 if len(choice) > 1:
2999 3000 clist = choice.keys()
3000 3001 clist.sort()
3001 3002 raise AmbiguousCommand(cmd, clist)
3002 3003
3003 3004 if choice:
3004 3005 return choice.values()[0]
3005 3006
3006 3007 raise UnknownCommand(cmd)
3007 3008
3008 3009 def catchterm(*args):
3009 3010 raise util.SignalInterrupt
3010 3011
3011 3012 def run():
3012 3013 sys.exit(dispatch(sys.argv[1:]))
3013 3014
3014 3015 class ParseError(Exception):
3015 3016 """Exception raised on errors in parsing the command line."""
3016 3017
3017 3018 def parse(ui, args):
3018 3019 options = {}
3019 3020 cmdoptions = {}
3020 3021
3021 3022 try:
3022 3023 args = fancyopts.fancyopts(args, globalopts, options)
3023 3024 except fancyopts.getopt.GetoptError, inst:
3024 3025 raise ParseError(None, inst)
3025 3026
3026 3027 if args:
3027 3028 cmd, args = args[0], args[1:]
3028 3029 aliases, i = findcmd(ui, cmd)
3029 3030 cmd = aliases[0]
3030 3031 defaults = ui.config("defaults", cmd)
3031 3032 if defaults:
3032 3033 args = shlex.split(defaults) + args
3033 3034 c = list(i[1])
3034 3035 else:
3035 3036 cmd = None
3036 3037 c = []
3037 3038
3038 3039 # combine global options into local
3039 3040 for o in globalopts:
3040 3041 c.append((o[0], o[1], options[o[1]], o[3]))
3041 3042
3042 3043 try:
3043 3044 args = fancyopts.fancyopts(args, c, cmdoptions)
3044 3045 except fancyopts.getopt.GetoptError, inst:
3045 3046 raise ParseError(cmd, inst)
3046 3047
3047 3048 # separate global options back out
3048 3049 for o in globalopts:
3049 3050 n = o[1]
3050 3051 options[n] = cmdoptions[n]
3051 3052 del cmdoptions[n]
3052 3053
3053 3054 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3054 3055
3055 3056 external = {}
3056 3057
3057 3058 def findext(name):
3058 3059 '''return module with given extension name'''
3059 3060 try:
3060 3061 return sys.modules[external[name]]
3061 3062 except KeyError:
3062 3063 for k, v in external.iteritems():
3063 3064 if k.endswith('.' + name) or k.endswith('/' + name) or v == name:
3064 3065 return sys.modules[v]
3065 3066 raise KeyError(name)
3066 3067
3067 3068 def load_extensions(ui):
3068 3069 added = []
3069 3070 for ext_name, load_from_name in ui.extensions():
3070 3071 if ext_name in external:
3071 3072 continue
3072 3073 try:
3073 3074 if load_from_name:
3074 3075 # the module will be loaded in sys.modules
3075 3076 # choose an unique name so that it doesn't
3076 3077 # conflicts with other modules
3077 3078 module_name = "hgext_%s" % ext_name.replace('.', '_')
3078 3079 mod = imp.load_source(module_name, load_from_name)
3079 3080 else:
3080 3081 def importh(name):
3081 3082 mod = __import__(name)
3082 3083 components = name.split('.')
3083 3084 for comp in components[1:]:
3084 3085 mod = getattr(mod, comp)
3085 3086 return mod
3086 3087 try:
3087 3088 mod = importh("hgext.%s" % ext_name)
3088 3089 except ImportError:
3089 3090 mod = importh(ext_name)
3090 3091 external[ext_name] = mod.__name__
3091 3092 added.append((mod, ext_name))
3092 3093 except (util.SignalInterrupt, KeyboardInterrupt):
3093 3094 raise
3094 3095 except Exception, inst:
3095 3096 ui.warn(_("*** failed to import extension %s: %s\n") %
3096 3097 (ext_name, inst))
3097 3098 if ui.print_exc():
3098 3099 return 1
3099 3100
3100 3101 for mod, name in added:
3101 3102 uisetup = getattr(mod, 'uisetup', None)
3102 3103 if uisetup:
3103 3104 uisetup(ui)
3104 3105 cmdtable = getattr(mod, 'cmdtable', {})
3105 3106 for t in cmdtable:
3106 3107 if t in table:
3107 3108 ui.warn(_("module %s overrides %s\n") % (name, t))
3108 3109 table.update(cmdtable)
3109 3110
3110 3111 def parseconfig(config):
3111 3112 """parse the --config options from the command line"""
3112 3113 parsed = []
3113 3114 for cfg in config:
3114 3115 try:
3115 3116 name, value = cfg.split('=', 1)
3116 3117 section, name = name.split('.', 1)
3117 3118 if not section or not name:
3118 3119 raise IndexError
3119 3120 parsed.append((section, name, value))
3120 3121 except (IndexError, ValueError):
3121 3122 raise util.Abort(_('malformed --config option: %s') % cfg)
3122 3123 return parsed
3123 3124
3124 3125 def dispatch(args):
3125 3126 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3126 3127 num = getattr(signal, name, None)
3127 3128 if num: signal.signal(num, catchterm)
3128 3129
3129 3130 try:
3130 3131 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3131 3132 except util.Abort, inst:
3132 3133 sys.stderr.write(_("abort: %s\n") % inst)
3133 3134 return -1
3134 3135
3135 3136 load_extensions(u)
3136 3137 u.addreadhook(load_extensions)
3137 3138
3138 3139 try:
3139 3140 cmd, func, args, options, cmdoptions = parse(u, args)
3140 3141 if options["encoding"]:
3141 3142 util._encoding = options["encoding"]
3142 3143 if options["encodingmode"]:
3143 3144 util._encodingmode = options["encodingmode"]
3144 3145 if options["time"]:
3145 3146 def get_times():
3146 3147 t = os.times()
3147 3148 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3148 3149 t = (t[0], t[1], t[2], t[3], time.clock())
3149 3150 return t
3150 3151 s = get_times()
3151 3152 def print_time():
3152 3153 t = get_times()
3153 3154 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3154 3155 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3155 3156 atexit.register(print_time)
3156 3157
3157 3158 # enter the debugger before command execution
3158 3159 if options['debugger']:
3159 3160 pdb.set_trace()
3160 3161
3161 3162 try:
3162 3163 if options['cwd']:
3163 3164 os.chdir(options['cwd'])
3164 3165
3165 3166 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3166 3167 not options["noninteractive"], options["traceback"],
3167 3168 parseconfig(options["config"]))
3168 3169
3169 3170 path = u.expandpath(options["repository"]) or ""
3170 3171 repo = path and hg.repository(u, path=path) or None
3171 3172 if repo and not repo.local():
3172 3173 raise util.Abort(_("repository '%s' is not local") % path)
3173 3174
3174 3175 if options['help']:
3175 3176 return help_(u, cmd, options['version'])
3176 3177 elif options['version']:
3177 3178 return version_(u)
3178 3179 elif not cmd:
3179 3180 return help_(u, 'shortlist')
3180 3181
3181 3182 if cmd not in norepo.split():
3182 3183 try:
3183 3184 if not repo:
3184 3185 repo = hg.repository(u, path=path)
3185 3186 u = repo.ui
3186 3187 for name in external.itervalues():
3187 3188 mod = sys.modules[name]
3188 3189 if hasattr(mod, 'reposetup'):
3189 3190 mod.reposetup(u, repo)
3190 3191 hg.repo_setup_hooks.append(mod.reposetup)
3191 3192 except hg.RepoError:
3192 3193 if cmd not in optionalrepo.split():
3193 3194 raise
3194 3195 d = lambda: func(u, repo, *args, **cmdoptions)
3195 3196 else:
3196 3197 d = lambda: func(u, *args, **cmdoptions)
3197 3198
3198 3199 try:
3199 3200 if options['profile']:
3200 3201 import hotshot, hotshot.stats
3201 3202 prof = hotshot.Profile("hg.prof")
3202 3203 try:
3203 3204 try:
3204 3205 return prof.runcall(d)
3205 3206 except:
3206 3207 try:
3207 3208 u.warn(_('exception raised - generating '
3208 3209 'profile anyway\n'))
3209 3210 except:
3210 3211 pass
3211 3212 raise
3212 3213 finally:
3213 3214 prof.close()
3214 3215 stats = hotshot.stats.load("hg.prof")
3215 3216 stats.strip_dirs()
3216 3217 stats.sort_stats('time', 'calls')
3217 3218 stats.print_stats(40)
3218 3219 elif options['lsprof']:
3219 3220 try:
3220 3221 from mercurial import lsprof
3221 3222 except ImportError:
3222 3223 raise util.Abort(_(
3223 3224 'lsprof not available - install from '
3224 3225 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
3225 3226 p = lsprof.Profiler()
3226 3227 p.enable(subcalls=True)
3227 3228 try:
3228 3229 return d()
3229 3230 finally:
3230 3231 p.disable()
3231 3232 stats = lsprof.Stats(p.getstats())
3232 3233 stats.sort()
3233 3234 stats.pprint(top=10, file=sys.stderr, climit=5)
3234 3235 else:
3235 3236 return d()
3236 3237 finally:
3237 3238 u.flush()
3238 3239 except:
3239 3240 # enter the debugger when we hit an exception
3240 3241 if options['debugger']:
3241 3242 pdb.post_mortem(sys.exc_info()[2])
3242 3243 u.print_exc()
3243 3244 raise
3244 3245 except ParseError, inst:
3245 3246 if inst.args[0]:
3246 3247 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3247 3248 help_(u, inst.args[0])
3248 3249 else:
3249 3250 u.warn(_("hg: %s\n") % inst.args[1])
3250 3251 help_(u, 'shortlist')
3251 3252 except AmbiguousCommand, inst:
3252 3253 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3253 3254 (inst.args[0], " ".join(inst.args[1])))
3254 3255 except UnknownCommand, inst:
3255 3256 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3256 3257 help_(u, 'shortlist')
3257 3258 except hg.RepoError, inst:
3258 3259 u.warn(_("abort: %s!\n") % inst)
3259 3260 except lock.LockHeld, inst:
3260 3261 if inst.errno == errno.ETIMEDOUT:
3261 3262 reason = _('timed out waiting for lock held by %s') % inst.locker
3262 3263 else:
3263 3264 reason = _('lock held by %s') % inst.locker
3264 3265 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3265 3266 except lock.LockUnavailable, inst:
3266 3267 u.warn(_("abort: could not lock %s: %s\n") %
3267 3268 (inst.desc or inst.filename, inst.strerror))
3268 3269 except revlog.RevlogError, inst:
3269 3270 u.warn(_("abort: %s!\n") % inst)
3270 3271 except util.SignalInterrupt:
3271 3272 u.warn(_("killed!\n"))
3272 3273 except KeyboardInterrupt:
3273 3274 try:
3274 3275 u.warn(_("interrupted!\n"))
3275 3276 except IOError, inst:
3276 3277 if inst.errno == errno.EPIPE:
3277 3278 if u.debugflag:
3278 3279 u.warn(_("\nbroken pipe\n"))
3279 3280 else:
3280 3281 raise
3281 3282 except socket.error, inst:
3282 3283 u.warn(_("abort: %s\n") % inst[1])
3283 3284 except IOError, inst:
3284 3285 if hasattr(inst, "code"):
3285 3286 u.warn(_("abort: %s\n") % inst)
3286 3287 elif hasattr(inst, "reason"):
3287 3288 try: # usually it is in the form (errno, strerror)
3288 3289 reason = inst.reason.args[1]
3289 3290 except: # it might be anything, for example a string
3290 3291 reason = inst.reason
3291 3292 u.warn(_("abort: error: %s\n") % reason)
3292 3293 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3293 3294 if u.debugflag:
3294 3295 u.warn(_("broken pipe\n"))
3295 3296 elif getattr(inst, "strerror", None):
3296 3297 if getattr(inst, "filename", None):
3297 3298 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3298 3299 else:
3299 3300 u.warn(_("abort: %s\n") % inst.strerror)
3300 3301 else:
3301 3302 raise
3302 3303 except OSError, inst:
3303 3304 if getattr(inst, "filename", None):
3304 3305 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3305 3306 else:
3306 3307 u.warn(_("abort: %s\n") % inst.strerror)
3307 3308 except util.UnexpectedOutput, inst:
3308 3309 u.warn(_("abort: %s") % inst[0])
3309 3310 if not isinstance(inst[1], basestring):
3310 3311 u.warn(" %r\n" % (inst[1],))
3311 3312 elif not inst[1]:
3312 3313 u.warn(_(" empty string\n"))
3313 3314 else:
3314 3315 u.warn("\n%r\n" % util.ellipsis(inst[1]))
3315 3316 except util.Abort, inst:
3316 3317 u.warn(_("abort: %s\n") % inst)
3317 3318 except TypeError, inst:
3318 3319 # was this an argument error?
3319 3320 tb = traceback.extract_tb(sys.exc_info()[2])
3320 3321 if len(tb) > 2: # no
3321 3322 raise
3322 3323 u.debug(inst, "\n")
3323 3324 u.warn(_("%s: invalid arguments\n") % cmd)
3324 3325 help_(u, cmd)
3325 3326 except SystemExit, inst:
3326 3327 # Commands shouldn't sys.exit directly, but give a return code.
3327 3328 # Just in case catch this and and pass exit code to caller.
3328 3329 return inst.code
3329 3330 except:
3330 3331 u.warn(_("** unknown exception encountered, details follow\n"))
3331 3332 u.warn(_("** report bug details to "
3332 3333 "http://www.selenic.com/mercurial/bts\n"))
3333 3334 u.warn(_("** or mercurial@selenic.com\n"))
3334 3335 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3335 3336 % version.get_version())
3336 3337 raise
3337 3338
3338 3339 return -1
@@ -1,1338 +1,1340 b''
1 1 """
2 2 util.py - Mercurial utility functions and platform specfic implementations
3 3
4 4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
6 6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7 7
8 8 This software may be used and distributed according to the terms
9 9 of the GNU General Public License, incorporated herein by reference.
10 10
11 11 This contains helper routines that are independent of the SCM core and hide
12 12 platform-specific details from the core.
13 13 """
14 14
15 15 from i18n import gettext as _
16 16 from demandload import *
17 17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
18 18 demandload(globals(), "os threading time calendar ConfigParser locale glob")
19 19
20 20 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
21 21 or "ascii"
22 22 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
23 23 _fallbackencoding = 'ISO-8859-1'
24 24
25 25 def tolocal(s):
26 26 """
27 27 Convert a string from internal UTF-8 to local encoding
28 28
29 29 All internal strings should be UTF-8 but some repos before the
30 30 implementation of locale support may contain latin1 or possibly
31 31 other character sets. We attempt to decode everything strictly
32 32 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
33 33 replace unknown characters.
34 34 """
35 35 for e in ('UTF-8', _fallbackencoding):
36 36 try:
37 37 u = s.decode(e) # attempt strict decoding
38 38 return u.encode(_encoding, "replace")
39 39 except LookupError, k:
40 40 raise Abort(_("%s, please check your locale settings") % k)
41 41 except UnicodeDecodeError:
42 42 pass
43 43 u = s.decode("utf-8", "replace") # last ditch
44 44 return u.encode(_encoding, "replace")
45 45
46 46 def fromlocal(s):
47 47 """
48 48 Convert a string from the local character encoding to UTF-8
49 49
50 50 We attempt to decode strings using the encoding mode set by
51 51 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
52 52 characters will cause an error message. Other modes include
53 53 'replace', which replaces unknown characters with a special
54 54 Unicode character, and 'ignore', which drops the character.
55 55 """
56 56 try:
57 57 return s.decode(_encoding, _encodingmode).encode("utf-8")
58 58 except UnicodeDecodeError, inst:
59 59 sub = s[max(0, inst.start-10):inst.start+10]
60 60 raise Abort("decoding near '%s': %s!" % (sub, inst))
61 61 except LookupError, k:
62 62 raise Abort(_("%s, please check your locale settings") % k)
63 63
64 64 def locallen(s):
65 65 """Find the length in characters of a local string"""
66 66 return len(s.decode(_encoding, "replace"))
67 67
68 68 def localsub(s, a, b=None):
69 69 try:
70 70 u = s.decode(_encoding, _encodingmode)
71 71 if b is not None:
72 72 u = u[a:b]
73 73 else:
74 74 u = u[:a]
75 75 return u.encode(_encoding, _encodingmode)
76 76 except UnicodeDecodeError, inst:
77 77 sub = s[max(0, inst.start-10), inst.start+10]
78 78 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
79 79
80 80 # used by parsedate
81 81 defaultdateformats = (
82 82 '%Y-%m-%d %H:%M:%S',
83 83 '%Y-%m-%d %I:%M:%S%p',
84 84 '%Y-%m-%d %H:%M',
85 85 '%Y-%m-%d %I:%M%p',
86 86 '%Y-%m-%d',
87 87 '%m-%d',
88 88 '%m/%d',
89 89 '%m/%d/%y',
90 90 '%m/%d/%Y',
91 91 '%a %b %d %H:%M:%S %Y',
92 92 '%a %b %d %I:%M:%S%p %Y',
93 93 '%b %d %H:%M:%S %Y',
94 94 '%b %d %I:%M:%S%p %Y',
95 95 '%b %d %H:%M:%S',
96 96 '%b %d %I:%M:%S%p',
97 97 '%b %d %H:%M',
98 98 '%b %d %I:%M%p',
99 99 '%b %d %Y',
100 100 '%b %d',
101 101 '%H:%M:%S',
102 102 '%I:%M:%SP',
103 103 '%H:%M',
104 104 '%I:%M%p',
105 105 )
106 106
107 107 extendeddateformats = defaultdateformats + (
108 108 "%Y",
109 109 "%Y-%m",
110 110 "%b",
111 111 "%b %Y",
112 112 )
113 113
114 114 class SignalInterrupt(Exception):
115 115 """Exception raised on SIGTERM and SIGHUP."""
116 116
117 117 # like SafeConfigParser but with case-sensitive keys
118 118 class configparser(ConfigParser.SafeConfigParser):
119 119 def optionxform(self, optionstr):
120 120 return optionstr
121 121
122 122 def cachefunc(func):
123 123 '''cache the result of function calls'''
124 124 # XXX doesn't handle keywords args
125 125 cache = {}
126 126 if func.func_code.co_argcount == 1:
127 127 # we gain a small amount of time because
128 128 # we don't need to pack/unpack the list
129 129 def f(arg):
130 130 if arg not in cache:
131 131 cache[arg] = func(arg)
132 132 return cache[arg]
133 133 else:
134 134 def f(*args):
135 135 if args not in cache:
136 136 cache[args] = func(*args)
137 137 return cache[args]
138 138
139 139 return f
140 140
141 141 def pipefilter(s, cmd):
142 142 '''filter string S through command CMD, returning its output'''
143 143 (pout, pin) = popen2.popen2(cmd, -1, 'b')
144 144 def writer():
145 145 try:
146 146 pin.write(s)
147 147 pin.close()
148 148 except IOError, inst:
149 149 if inst.errno != errno.EPIPE:
150 150 raise
151 151
152 152 # we should use select instead on UNIX, but this will work on most
153 153 # systems, including Windows
154 154 w = threading.Thread(target=writer)
155 155 w.start()
156 156 f = pout.read()
157 157 pout.close()
158 158 w.join()
159 159 return f
160 160
161 161 def tempfilter(s, cmd):
162 162 '''filter string S through a pair of temporary files with CMD.
163 163 CMD is used as a template to create the real command to be run,
164 164 with the strings INFILE and OUTFILE replaced by the real names of
165 165 the temporary files generated.'''
166 166 inname, outname = None, None
167 167 try:
168 168 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
169 169 fp = os.fdopen(infd, 'wb')
170 170 fp.write(s)
171 171 fp.close()
172 172 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
173 173 os.close(outfd)
174 174 cmd = cmd.replace('INFILE', inname)
175 175 cmd = cmd.replace('OUTFILE', outname)
176 176 code = os.system(cmd)
177 177 if code: raise Abort(_("command '%s' failed: %s") %
178 178 (cmd, explain_exit(code)))
179 179 return open(outname, 'rb').read()
180 180 finally:
181 181 try:
182 182 if inname: os.unlink(inname)
183 183 except: pass
184 184 try:
185 185 if outname: os.unlink(outname)
186 186 except: pass
187 187
188 188 filtertable = {
189 189 'tempfile:': tempfilter,
190 190 'pipe:': pipefilter,
191 191 }
192 192
193 193 def filter(s, cmd):
194 194 "filter a string through a command that transforms its input to its output"
195 195 for name, fn in filtertable.iteritems():
196 196 if cmd.startswith(name):
197 197 return fn(s, cmd[len(name):].lstrip())
198 198 return pipefilter(s, cmd)
199 199
200 200 def find_in_path(name, path, default=None):
201 201 '''find name in search path. path can be string (will be split
202 202 with os.pathsep), or iterable thing that returns strings. if name
203 203 found, return path to name. else return default.'''
204 204 if isinstance(path, str):
205 205 path = path.split(os.pathsep)
206 206 for p in path:
207 207 p_name = os.path.join(p, name)
208 208 if os.path.exists(p_name):
209 209 return p_name
210 210 return default
211 211
212 212 def binary(s):
213 213 """return true if a string is binary data using diff's heuristic"""
214 214 if s and '\0' in s[:4096]:
215 215 return True
216 216 return False
217 217
218 218 def unique(g):
219 219 """return the uniq elements of iterable g"""
220 220 seen = {}
221 221 l = []
222 222 for f in g:
223 223 if f not in seen:
224 224 seen[f] = 1
225 225 l.append(f)
226 226 return l
227 227
228 228 class Abort(Exception):
229 229 """Raised if a command needs to print an error and exit."""
230 230
231 231 class UnexpectedOutput(Abort):
232 232 """Raised to print an error with part of output and exit."""
233 233
234 234 def always(fn): return True
235 235 def never(fn): return False
236 236
237 237 def expand_glob(pats):
238 238 '''On Windows, expand the implicit globs in a list of patterns'''
239 239 if os.name != 'nt':
240 240 return list(pats)
241 241 ret = []
242 242 for p in pats:
243 243 kind, name = patkind(p, None)
244 244 if kind is None:
245 245 globbed = glob.glob(name)
246 246 if globbed:
247 247 ret.extend(globbed)
248 248 continue
249 249 # if we couldn't expand the glob, just keep it around
250 250 ret.append(p)
251 251 return ret
252 252
253 253 def patkind(name, dflt_pat='glob'):
254 254 """Split a string into an optional pattern kind prefix and the
255 255 actual pattern."""
256 256 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
257 257 if name.startswith(prefix + ':'): return name.split(':', 1)
258 258 return dflt_pat, name
259 259
260 260 def globre(pat, head='^', tail='$'):
261 261 "convert a glob pattern into a regexp"
262 262 i, n = 0, len(pat)
263 263 res = ''
264 264 group = False
265 265 def peek(): return i < n and pat[i]
266 266 while i < n:
267 267 c = pat[i]
268 268 i = i+1
269 269 if c == '*':
270 270 if peek() == '*':
271 271 i += 1
272 272 res += '.*'
273 273 else:
274 274 res += '[^/]*'
275 275 elif c == '?':
276 276 res += '.'
277 277 elif c == '[':
278 278 j = i
279 279 if j < n and pat[j] in '!]':
280 280 j += 1
281 281 while j < n and pat[j] != ']':
282 282 j += 1
283 283 if j >= n:
284 284 res += '\\['
285 285 else:
286 286 stuff = pat[i:j].replace('\\','\\\\')
287 287 i = j + 1
288 288 if stuff[0] == '!':
289 289 stuff = '^' + stuff[1:]
290 290 elif stuff[0] == '^':
291 291 stuff = '\\' + stuff
292 292 res = '%s[%s]' % (res, stuff)
293 293 elif c == '{':
294 294 group = True
295 295 res += '(?:'
296 296 elif c == '}' and group:
297 297 res += ')'
298 298 group = False
299 299 elif c == ',' and group:
300 300 res += '|'
301 301 elif c == '\\':
302 302 p = peek()
303 303 if p:
304 304 i += 1
305 305 res += re.escape(p)
306 306 else:
307 307 res += re.escape(c)
308 308 else:
309 309 res += re.escape(c)
310 310 return head + res + tail
311 311
312 312 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
313 313
314 314 def pathto(n1, n2):
315 315 '''return the relative path from one place to another.
316 316 n1 should use os.sep to separate directories
317 317 n2 should use "/" to separate directories
318 318 returns an os.sep-separated path.
319 319 '''
320 320 if not n1: return localpath(n2)
321 321 a, b = n1.split(os.sep), n2.split('/')
322 322 a.reverse()
323 323 b.reverse()
324 324 while a and b and a[-1] == b[-1]:
325 325 a.pop()
326 326 b.pop()
327 327 b.reverse()
328 328 return os.sep.join((['..'] * len(a)) + b)
329 329
330 330 def canonpath(root, cwd, myname):
331 331 """return the canonical path of myname, given cwd and root"""
332 332 if root == os.sep:
333 333 rootsep = os.sep
334 334 elif root.endswith(os.sep):
335 335 rootsep = root
336 336 else:
337 337 rootsep = root + os.sep
338 338 name = myname
339 339 if not os.path.isabs(name):
340 340 name = os.path.join(root, cwd, name)
341 341 name = os.path.normpath(name)
342 342 if name != rootsep and name.startswith(rootsep):
343 343 name = name[len(rootsep):]
344 344 audit_path(name)
345 345 return pconvert(name)
346 346 elif name == root:
347 347 return ''
348 348 else:
349 349 # Determine whether `name' is in the hierarchy at or beneath `root',
350 350 # by iterating name=dirname(name) until that causes no change (can't
351 351 # check name == '/', because that doesn't work on windows). For each
352 352 # `name', compare dev/inode numbers. If they match, the list `rel'
353 353 # holds the reversed list of components making up the relative file
354 354 # name we want.
355 355 root_st = os.stat(root)
356 356 rel = []
357 357 while True:
358 358 try:
359 359 name_st = os.stat(name)
360 360 except OSError:
361 361 break
362 362 if samestat(name_st, root_st):
363 363 rel.reverse()
364 364 name = os.path.join(*rel)
365 365 audit_path(name)
366 366 return pconvert(name)
367 367 dirname, basename = os.path.split(name)
368 368 rel.append(basename)
369 369 if dirname == name:
370 370 break
371 371 name = dirname
372 372
373 373 raise Abort('%s not under root' % myname)
374 374
375 375 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
376 376 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
377 377
378 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
379 names = expand_glob(names)
378 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='',
379 src=None, globbed=False):
380 if not globbed:
381 names = expand_glob(names)
380 382 return _matcher(canonroot, cwd, names, inc, exc, head, 'relpath', src)
381 383
382 384 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
383 385 """build a function to match a set of file patterns
384 386
385 387 arguments:
386 388 canonroot - the canonical root of the tree you're matching against
387 389 cwd - the current working directory, if relevant
388 390 names - patterns to find
389 391 inc - patterns to include
390 392 exc - patterns to exclude
391 393 head - a regex to prepend to patterns to control whether a match is rooted
392 394
393 395 a pattern is one of:
394 396 'glob:<rooted glob>'
395 397 're:<rooted regexp>'
396 398 'path:<rooted path>'
397 399 'relglob:<relative glob>'
398 400 'relpath:<relative path>'
399 401 'relre:<relative regexp>'
400 402 '<rooted path or regexp>'
401 403
402 404 returns:
403 405 a 3-tuple containing
404 406 - list of explicit non-pattern names passed in
405 407 - a bool match(filename) function
406 408 - a bool indicating if any patterns were passed in
407 409
408 410 todo:
409 411 make head regex a rooted bool
410 412 """
411 413
412 414 def contains_glob(name):
413 415 for c in name:
414 416 if c in _globchars: return True
415 417 return False
416 418
417 419 def regex(kind, name, tail):
418 420 '''convert a pattern into a regular expression'''
419 421 if kind == 're':
420 422 return name
421 423 elif kind == 'path':
422 424 return '^' + re.escape(name) + '(?:/|$)'
423 425 elif kind == 'relglob':
424 426 return head + globre(name, '(?:|.*/)', tail)
425 427 elif kind == 'relpath':
426 428 return head + re.escape(name) + tail
427 429 elif kind == 'relre':
428 430 if name.startswith('^'):
429 431 return name
430 432 return '.*' + name
431 433 return head + globre(name, '', tail)
432 434
433 435 def matchfn(pats, tail):
434 436 """build a matching function from a set of patterns"""
435 437 if not pats:
436 438 return
437 439 matches = []
438 440 for k, p in pats:
439 441 try:
440 442 pat = '(?:%s)' % regex(k, p, tail)
441 443 matches.append(re.compile(pat).match)
442 444 except re.error:
443 445 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
444 446 else: raise Abort("invalid pattern (%s): %s" % (k, p))
445 447
446 448 def buildfn(text):
447 449 for m in matches:
448 450 r = m(text)
449 451 if r:
450 452 return r
451 453
452 454 return buildfn
453 455
454 456 def globprefix(pat):
455 457 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
456 458 root = []
457 459 for p in pat.split(os.sep):
458 460 if contains_glob(p): break
459 461 root.append(p)
460 462 return '/'.join(root)
461 463
462 464 pats = []
463 465 files = []
464 466 roots = []
465 467 for kind, name in [patkind(p, dflt_pat) for p in names]:
466 468 if kind in ('glob', 'relpath'):
467 469 name = canonpath(canonroot, cwd, name)
468 470 if name == '':
469 471 kind, name = 'glob', '**'
470 472 if kind in ('glob', 'path', 're'):
471 473 pats.append((kind, name))
472 474 if kind == 'glob':
473 475 root = globprefix(name)
474 476 if root: roots.append(root)
475 477 elif kind == 'relpath':
476 478 files.append((kind, name))
477 479 roots.append(name)
478 480
479 481 patmatch = matchfn(pats, '$') or always
480 482 filematch = matchfn(files, '(?:/|$)') or always
481 483 incmatch = always
482 484 if inc:
483 485 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
484 486 incmatch = matchfn(inckinds, '(?:/|$)')
485 487 excmatch = lambda fn: False
486 488 if exc:
487 489 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
488 490 excmatch = matchfn(exckinds, '(?:/|$)')
489 491
490 492 return (roots,
491 493 lambda fn: (incmatch(fn) and not excmatch(fn) and
492 494 (fn.endswith('/') or
493 495 (not pats and not files) or
494 496 (pats and patmatch(fn)) or
495 497 (files and filematch(fn)))),
496 498 (inc or exc or (pats and pats != [('glob', '**')])) and True)
497 499
498 500 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
499 501 '''enhanced shell command execution.
500 502 run with environment maybe modified, maybe in different dir.
501 503
502 504 if command fails and onerr is None, return status. if ui object,
503 505 print error message and return status, else raise onerr object as
504 506 exception.'''
505 507 def py2shell(val):
506 508 'convert python object into string that is useful to shell'
507 509 if val in (None, False):
508 510 return '0'
509 511 if val == True:
510 512 return '1'
511 513 return str(val)
512 514 oldenv = {}
513 515 for k in environ:
514 516 oldenv[k] = os.environ.get(k)
515 517 if cwd is not None:
516 518 oldcwd = os.getcwd()
517 519 origcmd = cmd
518 520 if os.name == 'nt':
519 521 cmd = '"%s"' % cmd
520 522 try:
521 523 for k, v in environ.iteritems():
522 524 os.environ[k] = py2shell(v)
523 525 if cwd is not None and oldcwd != cwd:
524 526 os.chdir(cwd)
525 527 rc = os.system(cmd)
526 528 if rc and onerr:
527 529 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
528 530 explain_exit(rc)[0])
529 531 if errprefix:
530 532 errmsg = '%s: %s' % (errprefix, errmsg)
531 533 try:
532 534 onerr.warn(errmsg + '\n')
533 535 except AttributeError:
534 536 raise onerr(errmsg)
535 537 return rc
536 538 finally:
537 539 for k, v in oldenv.iteritems():
538 540 if v is None:
539 541 del os.environ[k]
540 542 else:
541 543 os.environ[k] = v
542 544 if cwd is not None and oldcwd != cwd:
543 545 os.chdir(oldcwd)
544 546
545 547 def rename(src, dst):
546 548 """forcibly rename a file"""
547 549 try:
548 550 os.rename(src, dst)
549 551 except OSError, err:
550 552 # on windows, rename to existing file is not allowed, so we
551 553 # must delete destination first. but if file is open, unlink
552 554 # schedules it for delete but does not delete it. rename
553 555 # happens immediately even for open files, so we create
554 556 # temporary file, delete it, rename destination to that name,
555 557 # then delete that. then rename is safe to do.
556 558 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
557 559 os.close(fd)
558 560 os.unlink(temp)
559 561 os.rename(dst, temp)
560 562 os.unlink(temp)
561 563 os.rename(src, dst)
562 564
563 565 def unlink(f):
564 566 """unlink and remove the directory if it is empty"""
565 567 os.unlink(f)
566 568 # try removing directories that might now be empty
567 569 try:
568 570 os.removedirs(os.path.dirname(f))
569 571 except OSError:
570 572 pass
571 573
572 574 def copyfile(src, dest):
573 575 "copy a file, preserving mode"
574 576 try:
575 577 shutil.copyfile(src, dest)
576 578 shutil.copymode(src, dest)
577 579 except shutil.Error, inst:
578 580 raise util.Abort(str(inst))
579 581
580 582 def copyfiles(src, dst, hardlink=None):
581 583 """Copy a directory tree using hardlinks if possible"""
582 584
583 585 if hardlink is None:
584 586 hardlink = (os.stat(src).st_dev ==
585 587 os.stat(os.path.dirname(dst)).st_dev)
586 588
587 589 if os.path.isdir(src):
588 590 os.mkdir(dst)
589 591 for name in os.listdir(src):
590 592 srcname = os.path.join(src, name)
591 593 dstname = os.path.join(dst, name)
592 594 copyfiles(srcname, dstname, hardlink)
593 595 else:
594 596 if hardlink:
595 597 try:
596 598 os_link(src, dst)
597 599 except (IOError, OSError):
598 600 hardlink = False
599 601 shutil.copy(src, dst)
600 602 else:
601 603 shutil.copy(src, dst)
602 604
603 605 def audit_path(path):
604 606 """Abort if path contains dangerous components"""
605 607 parts = os.path.normcase(path).split(os.sep)
606 608 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
607 609 or os.pardir in parts):
608 610 raise Abort(_("path contains illegal component: %s\n") % path)
609 611
610 612 def _makelock_file(info, pathname):
611 613 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
612 614 os.write(ld, info)
613 615 os.close(ld)
614 616
615 617 def _readlock_file(pathname):
616 618 return posixfile(pathname).read()
617 619
618 620 def nlinks(pathname):
619 621 """Return number of hardlinks for the given file."""
620 622 return os.lstat(pathname).st_nlink
621 623
622 624 if hasattr(os, 'link'):
623 625 os_link = os.link
624 626 else:
625 627 def os_link(src, dst):
626 628 raise OSError(0, _("Hardlinks not supported"))
627 629
628 630 def fstat(fp):
629 631 '''stat file object that may not have fileno method.'''
630 632 try:
631 633 return os.fstat(fp.fileno())
632 634 except AttributeError:
633 635 return os.stat(fp.name)
634 636
635 637 posixfile = file
636 638
637 639 def is_win_9x():
638 640 '''return true if run on windows 95, 98 or me.'''
639 641 try:
640 642 return sys.getwindowsversion()[3] == 1
641 643 except AttributeError:
642 644 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
643 645
644 646 getuser_fallback = None
645 647
646 648 def getuser():
647 649 '''return name of current user'''
648 650 try:
649 651 return getpass.getuser()
650 652 except ImportError:
651 653 # import of pwd will fail on windows - try fallback
652 654 if getuser_fallback:
653 655 return getuser_fallback()
654 656 # raised if win32api not available
655 657 raise Abort(_('user name not available - set USERNAME '
656 658 'environment variable'))
657 659
658 660 def username(uid=None):
659 661 """Return the name of the user with the given uid.
660 662
661 663 If uid is None, return the name of the current user."""
662 664 try:
663 665 import pwd
664 666 if uid is None:
665 667 uid = os.getuid()
666 668 try:
667 669 return pwd.getpwuid(uid)[0]
668 670 except KeyError:
669 671 return str(uid)
670 672 except ImportError:
671 673 return None
672 674
673 675 def groupname(gid=None):
674 676 """Return the name of the group with the given gid.
675 677
676 678 If gid is None, return the name of the current group."""
677 679 try:
678 680 import grp
679 681 if gid is None:
680 682 gid = os.getgid()
681 683 try:
682 684 return grp.getgrgid(gid)[0]
683 685 except KeyError:
684 686 return str(gid)
685 687 except ImportError:
686 688 return None
687 689
688 690 # File system features
689 691
690 692 def checkfolding(path):
691 693 """
692 694 Check whether the given path is on a case-sensitive filesystem
693 695
694 696 Requires a path (like /foo/.hg) ending with a foldable final
695 697 directory component.
696 698 """
697 699 s1 = os.stat(path)
698 700 d, b = os.path.split(path)
699 701 p2 = os.path.join(d, b.upper())
700 702 if path == p2:
701 703 p2 = os.path.join(d, b.lower())
702 704 try:
703 705 s2 = os.stat(p2)
704 706 if s2 == s1:
705 707 return False
706 708 return True
707 709 except:
708 710 return True
709 711
710 712 # Platform specific variants
711 713 if os.name == 'nt':
712 714 demandload(globals(), "msvcrt")
713 715 nulldev = 'NUL:'
714 716
715 717 class winstdout:
716 718 '''stdout on windows misbehaves if sent through a pipe'''
717 719
718 720 def __init__(self, fp):
719 721 self.fp = fp
720 722
721 723 def __getattr__(self, key):
722 724 return getattr(self.fp, key)
723 725
724 726 def close(self):
725 727 try:
726 728 self.fp.close()
727 729 except: pass
728 730
729 731 def write(self, s):
730 732 try:
731 733 return self.fp.write(s)
732 734 except IOError, inst:
733 735 if inst.errno != 0: raise
734 736 self.close()
735 737 raise IOError(errno.EPIPE, 'Broken pipe')
736 738
737 739 sys.stdout = winstdout(sys.stdout)
738 740
739 741 def system_rcpath():
740 742 try:
741 743 return system_rcpath_win32()
742 744 except:
743 745 return [r'c:\mercurial\mercurial.ini']
744 746
745 747 def os_rcpath():
746 748 '''return default os-specific hgrc search path'''
747 749 path = system_rcpath()
748 750 path.append(user_rcpath())
749 751 userprofile = os.environ.get('USERPROFILE')
750 752 if userprofile:
751 753 path.append(os.path.join(userprofile, 'mercurial.ini'))
752 754 return path
753 755
754 756 def user_rcpath():
755 757 '''return os-specific hgrc search path to the user dir'''
756 758 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
757 759
758 760 def parse_patch_output(output_line):
759 761 """parses the output produced by patch and returns the file name"""
760 762 pf = output_line[14:]
761 763 if pf[0] == '`':
762 764 pf = pf[1:-1] # Remove the quotes
763 765 return pf
764 766
765 767 def testpid(pid):
766 768 '''return False if pid dead, True if running or not known'''
767 769 return True
768 770
769 771 def is_exec(f, last):
770 772 return last
771 773
772 774 def set_exec(f, mode):
773 775 pass
774 776
775 777 def set_binary(fd):
776 778 msvcrt.setmode(fd.fileno(), os.O_BINARY)
777 779
778 780 def pconvert(path):
779 781 return path.replace("\\", "/")
780 782
781 783 def localpath(path):
782 784 return path.replace('/', '\\')
783 785
784 786 def normpath(path):
785 787 return pconvert(os.path.normpath(path))
786 788
787 789 makelock = _makelock_file
788 790 readlock = _readlock_file
789 791
790 792 def samestat(s1, s2):
791 793 return False
792 794
793 795 def shellquote(s):
794 796 return '"%s"' % s.replace('"', '\\"')
795 797
796 798 def explain_exit(code):
797 799 return _("exited with status %d") % code, code
798 800
799 801 # if you change this stub into a real check, please try to implement the
800 802 # username and groupname functions above, too.
801 803 def isowner(fp, st=None):
802 804 return True
803 805
804 806 try:
805 807 # override functions with win32 versions if possible
806 808 from util_win32 import *
807 809 if not is_win_9x():
808 810 posixfile = posixfile_nt
809 811 except ImportError:
810 812 pass
811 813
812 814 else:
813 815 nulldev = '/dev/null'
814 816
815 817 def rcfiles(path):
816 818 rcs = [os.path.join(path, 'hgrc')]
817 819 rcdir = os.path.join(path, 'hgrc.d')
818 820 try:
819 821 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
820 822 if f.endswith(".rc")])
821 823 except OSError:
822 824 pass
823 825 return rcs
824 826
825 827 def os_rcpath():
826 828 '''return default os-specific hgrc search path'''
827 829 path = []
828 830 # old mod_python does not set sys.argv
829 831 if len(getattr(sys, 'argv', [])) > 0:
830 832 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
831 833 '/../etc/mercurial'))
832 834 path.extend(rcfiles('/etc/mercurial'))
833 835 path.append(os.path.expanduser('~/.hgrc'))
834 836 path = [os.path.normpath(f) for f in path]
835 837 return path
836 838
837 839 def parse_patch_output(output_line):
838 840 """parses the output produced by patch and returns the file name"""
839 841 pf = output_line[14:]
840 842 if pf.startswith("'") and pf.endswith("'") and " " in pf:
841 843 pf = pf[1:-1] # Remove the quotes
842 844 return pf
843 845
844 846 def is_exec(f, last):
845 847 """check whether a file is executable"""
846 848 return (os.lstat(f).st_mode & 0100 != 0)
847 849
848 850 def set_exec(f, mode):
849 851 s = os.lstat(f).st_mode
850 852 if (s & 0100 != 0) == mode:
851 853 return
852 854 if mode:
853 855 # Turn on +x for every +r bit when making a file executable
854 856 # and obey umask.
855 857 umask = os.umask(0)
856 858 os.umask(umask)
857 859 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
858 860 else:
859 861 os.chmod(f, s & 0666)
860 862
861 863 def set_binary(fd):
862 864 pass
863 865
864 866 def pconvert(path):
865 867 return path
866 868
867 869 def localpath(path):
868 870 return path
869 871
870 872 normpath = os.path.normpath
871 873 samestat = os.path.samestat
872 874
873 875 def makelock(info, pathname):
874 876 try:
875 877 os.symlink(info, pathname)
876 878 except OSError, why:
877 879 if why.errno == errno.EEXIST:
878 880 raise
879 881 else:
880 882 _makelock_file(info, pathname)
881 883
882 884 def readlock(pathname):
883 885 try:
884 886 return os.readlink(pathname)
885 887 except OSError, why:
886 888 if why.errno == errno.EINVAL:
887 889 return _readlock_file(pathname)
888 890 else:
889 891 raise
890 892
891 893 def shellquote(s):
892 894 return "'%s'" % s.replace("'", "'\\''")
893 895
894 896 def testpid(pid):
895 897 '''return False if pid dead, True if running or not sure'''
896 898 try:
897 899 os.kill(pid, 0)
898 900 return True
899 901 except OSError, inst:
900 902 return inst.errno != errno.ESRCH
901 903
902 904 def explain_exit(code):
903 905 """return a 2-tuple (desc, code) describing a process's status"""
904 906 if os.WIFEXITED(code):
905 907 val = os.WEXITSTATUS(code)
906 908 return _("exited with status %d") % val, val
907 909 elif os.WIFSIGNALED(code):
908 910 val = os.WTERMSIG(code)
909 911 return _("killed by signal %d") % val, val
910 912 elif os.WIFSTOPPED(code):
911 913 val = os.WSTOPSIG(code)
912 914 return _("stopped by signal %d") % val, val
913 915 raise ValueError(_("invalid exit code"))
914 916
915 917 def isowner(fp, st=None):
916 918 """Return True if the file object f belongs to the current user.
917 919
918 920 The return value of a util.fstat(f) may be passed as the st argument.
919 921 """
920 922 if st is None:
921 923 st = fstat(fp)
922 924 return st.st_uid == os.getuid()
923 925
924 926 def _buildencodefun():
925 927 e = '_'
926 928 win_reserved = [ord(x) for x in '\\:*?"<>|']
927 929 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
928 930 for x in (range(32) + range(126, 256) + win_reserved):
929 931 cmap[chr(x)] = "~%02x" % x
930 932 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
931 933 cmap[chr(x)] = e + chr(x).lower()
932 934 dmap = {}
933 935 for k, v in cmap.iteritems():
934 936 dmap[v] = k
935 937 def decode(s):
936 938 i = 0
937 939 while i < len(s):
938 940 for l in xrange(1, 4):
939 941 try:
940 942 yield dmap[s[i:i+l]]
941 943 i += l
942 944 break
943 945 except KeyError:
944 946 pass
945 947 else:
946 948 raise KeyError
947 949 return (lambda s: "".join([cmap[c] for c in s]),
948 950 lambda s: "".join(list(decode(s))))
949 951
950 952 encodefilename, decodefilename = _buildencodefun()
951 953
952 954 def encodedopener(openerfn, fn):
953 955 def o(path, *args, **kw):
954 956 return openerfn(fn(path), *args, **kw)
955 957 return o
956 958
957 959 def opener(base, audit=True):
958 960 """
959 961 return a function that opens files relative to base
960 962
961 963 this function is used to hide the details of COW semantics and
962 964 remote file access from higher level code.
963 965 """
964 966 p = base
965 967 audit_p = audit
966 968
967 969 def mktempcopy(name):
968 970 d, fn = os.path.split(name)
969 971 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
970 972 os.close(fd)
971 973 ofp = posixfile(temp, "wb")
972 974 try:
973 975 try:
974 976 ifp = posixfile(name, "rb")
975 977 except IOError, inst:
976 978 if not getattr(inst, 'filename', None):
977 979 inst.filename = name
978 980 raise
979 981 for chunk in filechunkiter(ifp):
980 982 ofp.write(chunk)
981 983 ifp.close()
982 984 ofp.close()
983 985 except:
984 986 try: os.unlink(temp)
985 987 except: pass
986 988 raise
987 989 st = os.lstat(name)
988 990 os.chmod(temp, st.st_mode)
989 991 return temp
990 992
991 993 class atomictempfile(posixfile):
992 994 """the file will only be copied when rename is called"""
993 995 def __init__(self, name, mode):
994 996 self.__name = name
995 997 self.temp = mktempcopy(name)
996 998 posixfile.__init__(self, self.temp, mode)
997 999 def rename(self):
998 1000 if not self.closed:
999 1001 posixfile.close(self)
1000 1002 rename(self.temp, localpath(self.__name))
1001 1003 def __del__(self):
1002 1004 if not self.closed:
1003 1005 try:
1004 1006 os.unlink(self.temp)
1005 1007 except: pass
1006 1008 posixfile.close(self)
1007 1009
1008 1010 class atomicfile(atomictempfile):
1009 1011 """the file will only be copied on close"""
1010 1012 def __init__(self, name, mode):
1011 1013 atomictempfile.__init__(self, name, mode)
1012 1014 def close(self):
1013 1015 self.rename()
1014 1016 def __del__(self):
1015 1017 self.rename()
1016 1018
1017 1019 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1018 1020 if audit_p:
1019 1021 audit_path(path)
1020 1022 f = os.path.join(p, path)
1021 1023
1022 1024 if not text:
1023 1025 mode += "b" # for that other OS
1024 1026
1025 1027 if mode[0] != "r":
1026 1028 try:
1027 1029 nlink = nlinks(f)
1028 1030 except OSError:
1029 1031 d = os.path.dirname(f)
1030 1032 if not os.path.isdir(d):
1031 1033 os.makedirs(d)
1032 1034 else:
1033 1035 if atomic:
1034 1036 return atomicfile(f, mode)
1035 1037 elif atomictemp:
1036 1038 return atomictempfile(f, mode)
1037 1039 if nlink > 1:
1038 1040 rename(mktempcopy(f), f)
1039 1041 return posixfile(f, mode)
1040 1042
1041 1043 return o
1042 1044
1043 1045 class chunkbuffer(object):
1044 1046 """Allow arbitrary sized chunks of data to be efficiently read from an
1045 1047 iterator over chunks of arbitrary size."""
1046 1048
1047 1049 def __init__(self, in_iter, targetsize = 2**16):
1048 1050 """in_iter is the iterator that's iterating over the input chunks.
1049 1051 targetsize is how big a buffer to try to maintain."""
1050 1052 self.in_iter = iter(in_iter)
1051 1053 self.buf = ''
1052 1054 self.targetsize = int(targetsize)
1053 1055 if self.targetsize <= 0:
1054 1056 raise ValueError(_("targetsize must be greater than 0, was %d") %
1055 1057 targetsize)
1056 1058 self.iterempty = False
1057 1059
1058 1060 def fillbuf(self):
1059 1061 """Ignore target size; read every chunk from iterator until empty."""
1060 1062 if not self.iterempty:
1061 1063 collector = cStringIO.StringIO()
1062 1064 collector.write(self.buf)
1063 1065 for ch in self.in_iter:
1064 1066 collector.write(ch)
1065 1067 self.buf = collector.getvalue()
1066 1068 self.iterempty = True
1067 1069
1068 1070 def read(self, l):
1069 1071 """Read L bytes of data from the iterator of chunks of data.
1070 1072 Returns less than L bytes if the iterator runs dry."""
1071 1073 if l > len(self.buf) and not self.iterempty:
1072 1074 # Clamp to a multiple of self.targetsize
1073 1075 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1074 1076 collector = cStringIO.StringIO()
1075 1077 collector.write(self.buf)
1076 1078 collected = len(self.buf)
1077 1079 for chunk in self.in_iter:
1078 1080 collector.write(chunk)
1079 1081 collected += len(chunk)
1080 1082 if collected >= targetsize:
1081 1083 break
1082 1084 if collected < targetsize:
1083 1085 self.iterempty = True
1084 1086 self.buf = collector.getvalue()
1085 1087 s, self.buf = self.buf[:l], buffer(self.buf, l)
1086 1088 return s
1087 1089
1088 1090 def filechunkiter(f, size=65536, limit=None):
1089 1091 """Create a generator that produces the data in the file size
1090 1092 (default 65536) bytes at a time, up to optional limit (default is
1091 1093 to read all data). Chunks may be less than size bytes if the
1092 1094 chunk is the last chunk in the file, or the file is a socket or
1093 1095 some other type of file that sometimes reads less data than is
1094 1096 requested."""
1095 1097 assert size >= 0
1096 1098 assert limit is None or limit >= 0
1097 1099 while True:
1098 1100 if limit is None: nbytes = size
1099 1101 else: nbytes = min(limit, size)
1100 1102 s = nbytes and f.read(nbytes)
1101 1103 if not s: break
1102 1104 if limit: limit -= len(s)
1103 1105 yield s
1104 1106
1105 1107 def makedate():
1106 1108 lt = time.localtime()
1107 1109 if lt[8] == 1 and time.daylight:
1108 1110 tz = time.altzone
1109 1111 else:
1110 1112 tz = time.timezone
1111 1113 return time.mktime(lt), tz
1112 1114
1113 1115 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1114 1116 """represent a (unixtime, offset) tuple as a localized time.
1115 1117 unixtime is seconds since the epoch, and offset is the time zone's
1116 1118 number of seconds away from UTC. if timezone is false, do not
1117 1119 append time zone to string."""
1118 1120 t, tz = date or makedate()
1119 1121 s = time.strftime(format, time.gmtime(float(t) - tz))
1120 1122 if timezone:
1121 1123 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1122 1124 return s
1123 1125
1124 1126 def strdate(string, format, defaults):
1125 1127 """parse a localized time string and return a (unixtime, offset) tuple.
1126 1128 if the string cannot be parsed, ValueError is raised."""
1127 1129 def timezone(string):
1128 1130 tz = string.split()[-1]
1129 1131 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1130 1132 tz = int(tz)
1131 1133 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1132 1134 return offset
1133 1135 if tz == "GMT" or tz == "UTC":
1134 1136 return 0
1135 1137 return None
1136 1138
1137 1139 # NOTE: unixtime = localunixtime + offset
1138 1140 offset, date = timezone(string), string
1139 1141 if offset != None:
1140 1142 date = " ".join(string.split()[:-1])
1141 1143
1142 1144 # add missing elements from defaults
1143 1145 for part in defaults:
1144 1146 found = [True for p in part if ("%"+p) in format]
1145 1147 if not found:
1146 1148 date += "@" + defaults[part]
1147 1149 format += "@%" + part[0]
1148 1150
1149 1151 timetuple = time.strptime(date, format)
1150 1152 localunixtime = int(calendar.timegm(timetuple))
1151 1153 if offset is None:
1152 1154 # local timezone
1153 1155 unixtime = int(time.mktime(timetuple))
1154 1156 offset = unixtime - localunixtime
1155 1157 else:
1156 1158 unixtime = localunixtime + offset
1157 1159 return unixtime, offset
1158 1160
1159 1161 def parsedate(string, formats=None, defaults=None):
1160 1162 """parse a localized time string and return a (unixtime, offset) tuple.
1161 1163 The date may be a "unixtime offset" string or in one of the specified
1162 1164 formats."""
1163 1165 if not string:
1164 1166 return 0, 0
1165 1167 if not formats:
1166 1168 formats = defaultdateformats
1167 1169 string = string.strip()
1168 1170 try:
1169 1171 when, offset = map(int, string.split(' '))
1170 1172 except ValueError:
1171 1173 # fill out defaults
1172 1174 if not defaults:
1173 1175 defaults = {}
1174 1176 now = makedate()
1175 1177 for part in "d mb yY HI M S".split():
1176 1178 if part not in defaults:
1177 1179 if part[0] in "HMS":
1178 1180 defaults[part] = "00"
1179 1181 elif part[0] in "dm":
1180 1182 defaults[part] = "1"
1181 1183 else:
1182 1184 defaults[part] = datestr(now, "%" + part[0], False)
1183 1185
1184 1186 for format in formats:
1185 1187 try:
1186 1188 when, offset = strdate(string, format, defaults)
1187 1189 except ValueError:
1188 1190 pass
1189 1191 else:
1190 1192 break
1191 1193 else:
1192 1194 raise Abort(_('invalid date: %r ') % string)
1193 1195 # validate explicit (probably user-specified) date and
1194 1196 # time zone offset. values must fit in signed 32 bits for
1195 1197 # current 32-bit linux runtimes. timezones go from UTC-12
1196 1198 # to UTC+14
1197 1199 if abs(when) > 0x7fffffff:
1198 1200 raise Abort(_('date exceeds 32 bits: %d') % when)
1199 1201 if offset < -50400 or offset > 43200:
1200 1202 raise Abort(_('impossible time zone offset: %d') % offset)
1201 1203 return when, offset
1202 1204
1203 1205 def matchdate(date):
1204 1206 """Return a function that matches a given date match specifier
1205 1207
1206 1208 Formats include:
1207 1209
1208 1210 '{date}' match a given date to the accuracy provided
1209 1211
1210 1212 '<{date}' on or before a given date
1211 1213
1212 1214 '>{date}' on or after a given date
1213 1215
1214 1216 """
1215 1217
1216 1218 def lower(date):
1217 1219 return parsedate(date, extendeddateformats)[0]
1218 1220
1219 1221 def upper(date):
1220 1222 d = dict(mb="12", HI="23", M="59", S="59")
1221 1223 for days in "31 30 29".split():
1222 1224 try:
1223 1225 d["d"] = days
1224 1226 return parsedate(date, extendeddateformats, d)[0]
1225 1227 except:
1226 1228 pass
1227 1229 d["d"] = "28"
1228 1230 return parsedate(date, extendeddateformats, d)[0]
1229 1231
1230 1232 if date[0] == "<":
1231 1233 when = upper(date[1:])
1232 1234 return lambda x: x <= when
1233 1235 elif date[0] == ">":
1234 1236 when = lower(date[1:])
1235 1237 return lambda x: x >= when
1236 1238 elif date[0] == "-":
1237 1239 try:
1238 1240 days = int(date[1:])
1239 1241 except ValueError:
1240 1242 raise Abort(_("invalid day spec: %s") % date[1:])
1241 1243 when = makedate()[0] - days * 3600 * 24
1242 1244 return lambda x: x >= when
1243 1245 elif " to " in date:
1244 1246 a, b = date.split(" to ")
1245 1247 start, stop = lower(a), upper(b)
1246 1248 return lambda x: x >= start and x <= stop
1247 1249 else:
1248 1250 start, stop = lower(date), upper(date)
1249 1251 return lambda x: x >= start and x <= stop
1250 1252
1251 1253 def shortuser(user):
1252 1254 """Return a short representation of a user name or email address."""
1253 1255 f = user.find('@')
1254 1256 if f >= 0:
1255 1257 user = user[:f]
1256 1258 f = user.find('<')
1257 1259 if f >= 0:
1258 1260 user = user[f+1:]
1259 1261 f = user.find(' ')
1260 1262 if f >= 0:
1261 1263 user = user[:f]
1262 1264 f = user.find('.')
1263 1265 if f >= 0:
1264 1266 user = user[:f]
1265 1267 return user
1266 1268
1267 1269 def ellipsis(text, maxlength=400):
1268 1270 """Trim string to at most maxlength (default: 400) characters."""
1269 1271 if len(text) <= maxlength:
1270 1272 return text
1271 1273 else:
1272 1274 return "%s..." % (text[:maxlength-3])
1273 1275
1274 1276 def walkrepos(path):
1275 1277 '''yield every hg repository under path, recursively.'''
1276 1278 def errhandler(err):
1277 1279 if err.filename == path:
1278 1280 raise err
1279 1281
1280 1282 for root, dirs, files in os.walk(path, onerror=errhandler):
1281 1283 for d in dirs:
1282 1284 if d == '.hg':
1283 1285 yield root
1284 1286 dirs[:] = []
1285 1287 break
1286 1288
1287 1289 _rcpath = None
1288 1290
1289 1291 def rcpath():
1290 1292 '''return hgrc search path. if env var HGRCPATH is set, use it.
1291 1293 for each item in path, if directory, use files ending in .rc,
1292 1294 else use item.
1293 1295 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1294 1296 if no HGRCPATH, use default os-specific path.'''
1295 1297 global _rcpath
1296 1298 if _rcpath is None:
1297 1299 if 'HGRCPATH' in os.environ:
1298 1300 _rcpath = []
1299 1301 for p in os.environ['HGRCPATH'].split(os.pathsep):
1300 1302 if not p: continue
1301 1303 if os.path.isdir(p):
1302 1304 for f in os.listdir(p):
1303 1305 if f.endswith('.rc'):
1304 1306 _rcpath.append(os.path.join(p, f))
1305 1307 else:
1306 1308 _rcpath.append(p)
1307 1309 else:
1308 1310 _rcpath = os_rcpath()
1309 1311 return _rcpath
1310 1312
1311 1313 def bytecount(nbytes):
1312 1314 '''return byte count formatted as readable string, with units'''
1313 1315
1314 1316 units = (
1315 1317 (100, 1<<30, _('%.0f GB')),
1316 1318 (10, 1<<30, _('%.1f GB')),
1317 1319 (1, 1<<30, _('%.2f GB')),
1318 1320 (100, 1<<20, _('%.0f MB')),
1319 1321 (10, 1<<20, _('%.1f MB')),
1320 1322 (1, 1<<20, _('%.2f MB')),
1321 1323 (100, 1<<10, _('%.0f KB')),
1322 1324 (10, 1<<10, _('%.1f KB')),
1323 1325 (1, 1<<10, _('%.2f KB')),
1324 1326 (1, 1, _('%.0f bytes')),
1325 1327 )
1326 1328
1327 1329 for multiplier, divisor, format in units:
1328 1330 if nbytes >= divisor * multiplier:
1329 1331 return format % (nbytes / float(divisor))
1330 1332 return units[-1][2] % nbytes
1331 1333
1332 1334 def drop_scheme(scheme, path):
1333 1335 sc = scheme + ':'
1334 1336 if path.startswith(sc):
1335 1337 path = path[len(sc):]
1336 1338 if path.startswith('//'):
1337 1339 path = path[2:]
1338 1340 return path
General Comments 0
You need to be logged in to leave comments. Login now