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