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