##// END OF EJS Templates
Reduce the amount of stat traffic generated by a walk....
Bryan O'Sullivan -
r812:b65af904 default
parent child Browse files
Show More
@@ -1,1416 +1,1418 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 demandload(globals(), "os re sys signal shutil")
10 10 demandload(globals(), "fancyopts ui hg util")
11 11 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
12 12 demandload(globals(), "errno socket version struct atexit")
13 13
14 14 class UnknownCommand(Exception):
15 15 """Exception raised if command is not in the command table."""
16 16
17 17 class Abort(Exception):
18 18 """Raised if a command needs to print an error and exit."""
19 19
20 20 def filterfiles(filters, files):
21 21 l = [x for x in files if x in filters]
22 22
23 23 for t in filters:
24 24 if t and t[-1] != "/":
25 25 t += "/"
26 26 l += [x for x in files if x.startswith(t)]
27 27 return l
28 28
29 29 def relfilter(repo, files):
30 30 cwd = repo.getcwd()
31 31 if cwd:
32 32 return filterfiles([util.pconvert(cwd)], files)
33 33 return files
34 34
35 35 def relpath(repo, args):
36 36 cwd = repo.getcwd()
37 37 if cwd:
38 38 return [util.pconvert(os.path.normpath(os.path.join(cwd, x)))
39 39 for x in args]
40 40 return args
41 41
42 42 def matchpats(cwd, pats = [], opts = {}, head = ''):
43 43 return util.matcher(cwd, pats, opts.get('include'),
44 44 opts.get('exclude'), head)
45 45
46 46 def walk(repo, pats, opts, head = ''):
47 47 cwd = repo.getcwd()
48 48 c = 0
49 49 if cwd: c = len(cwd) + 1
50 for src, fn in repo.walk(match = matchpats(cwd, pats, opts, head)):
50 files, matchfn = matchpats(cwd, pats, opts, head)
51 for src, fn in repo.walk(files = files, match = matchfn):
51 52 yield src, fn, fn[c:]
52 53
53 54 revrangesep = ':'
54 55
55 56 def revrange(ui, repo, revs, revlog=None):
56 57 if revlog is None:
57 58 revlog = repo.changelog
58 59 revcount = revlog.count()
59 60 def fix(val, defval):
60 61 if not val:
61 62 return defval
62 63 try:
63 64 num = int(val)
64 65 if str(num) != val:
65 66 raise ValueError
66 67 if num < 0:
67 68 num += revcount
68 69 if not (0 <= num < revcount):
69 70 raise ValueError
70 71 except ValueError:
71 72 try:
72 73 num = repo.changelog.rev(repo.lookup(val))
73 74 except KeyError:
74 75 try:
75 76 num = revlog.rev(revlog.lookup(val))
76 77 except KeyError:
77 78 raise Abort('invalid revision identifier %s', val)
78 79 return num
79 80 for spec in revs:
80 81 if spec.find(revrangesep) >= 0:
81 82 start, end = spec.split(revrangesep, 1)
82 83 start = fix(start, 0)
83 84 end = fix(end, revcount - 1)
84 85 if end > start:
85 86 end += 1
86 87 step = 1
87 88 else:
88 89 end -= 1
89 90 step = -1
90 91 for rev in xrange(start, end, step):
91 92 yield str(rev)
92 93 else:
93 94 yield spec
94 95
95 96 def make_filename(repo, r, pat, node=None,
96 97 total=None, seqno=None, revwidth=None):
97 98 node_expander = {
98 99 'H': lambda: hg.hex(node),
99 100 'R': lambda: str(r.rev(node)),
100 101 'h': lambda: hg.short(node),
101 102 }
102 103 expander = {
103 104 '%': lambda: '%',
104 105 'b': lambda: os.path.basename(repo.root),
105 106 }
106 107
107 108 try:
108 109 if node:
109 110 expander.update(node_expander)
110 111 if node and revwidth is not None:
111 112 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
112 113 if total is not None:
113 114 expander['N'] = lambda: str(total)
114 115 if seqno is not None:
115 116 expander['n'] = lambda: str(seqno)
116 117 if total is not None and seqno is not None:
117 118 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
118 119
119 120 newname = []
120 121 patlen = len(pat)
121 122 i = 0
122 123 while i < patlen:
123 124 c = pat[i]
124 125 if c == '%':
125 126 i += 1
126 127 c = pat[i]
127 128 c = expander[c]()
128 129 newname.append(c)
129 130 i += 1
130 131 return ''.join(newname)
131 132 except KeyError, inst:
132 133 raise Abort("invalid format spec '%%%s' in output file name",
133 134 inst.args[0])
134 135
135 136 def make_file(repo, r, pat, node=None,
136 137 total=None, seqno=None, revwidth=None, mode='wb'):
137 138 if not pat or pat == '-':
138 139 if 'w' in mode: return sys.stdout
139 140 else: return sys.stdin
140 141 if hasattr(pat, 'write') and 'w' in mode:
141 142 return pat
142 143 if hasattr(pat, 'read') and 'r' in mode:
143 144 return pat
144 145 return open(make_filename(repo, r, pat, node, total, seqno, revwidth),
145 146 mode)
146 147
147 148 def dodiff(fp, ui, repo, files=None, node1=None, node2=None):
148 149 def date(c):
149 150 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
150 151
151 152 (c, a, d, u) = repo.changes(node1, node2, files)
152 153 if files:
153 154 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
154 155
155 156 if not c and not a and not d:
156 157 return
157 158
158 159 if node2:
159 160 change = repo.changelog.read(node2)
160 161 mmap2 = repo.manifest.read(change[0])
161 162 date2 = date(change)
162 163 def read(f):
163 164 return repo.file(f).read(mmap2[f])
164 165 else:
165 166 date2 = time.asctime()
166 167 if not node1:
167 168 node1 = repo.dirstate.parents()[0]
168 169 def read(f):
169 170 return repo.wfile(f).read()
170 171
171 172 if ui.quiet:
172 173 r = None
173 174 else:
174 175 hexfunc = ui.verbose and hg.hex or hg.short
175 176 r = [hexfunc(node) for node in [node1, node2] if node]
176 177
177 178 change = repo.changelog.read(node1)
178 179 mmap = repo.manifest.read(change[0])
179 180 date1 = date(change)
180 181
181 182 for f in c:
182 183 to = None
183 184 if f in mmap:
184 185 to = repo.file(f).read(mmap[f])
185 186 tn = read(f)
186 187 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
187 188 for f in a:
188 189 to = None
189 190 tn = read(f)
190 191 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
191 192 for f in d:
192 193 to = repo.file(f).read(mmap[f])
193 194 tn = None
194 195 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
195 196
196 197 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None):
197 198 """show a single changeset or file revision"""
198 199 changelog = repo.changelog
199 200 if filelog:
200 201 log = filelog
201 202 filerev = rev
202 203 node = filenode = filelog.node(filerev)
203 204 changerev = filelog.linkrev(filenode)
204 205 changenode = changenode or changelog.node(changerev)
205 206 else:
206 207 log = changelog
207 208 changerev = rev
208 209 if changenode is None:
209 210 changenode = changelog.node(changerev)
210 211 elif not changerev:
211 212 rev = changerev = changelog.rev(changenode)
212 213 node = changenode
213 214
214 215 if ui.quiet:
215 216 ui.write("%d:%s\n" % (rev, hg.hex(node)))
216 217 return
217 218
218 219 changes = changelog.read(changenode)
219 220
220 221 parents = [(log.rev(p), ui.verbose and hg.hex(p) or hg.short(p))
221 222 for p in log.parents(node)
222 223 if ui.debugflag or p != hg.nullid]
223 224 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
224 225 parents = []
225 226
226 227 if ui.verbose:
227 228 ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
228 229 else:
229 230 ui.write("changeset: %d:%s\n" % (changerev, hg.short(changenode)))
230 231
231 232 for tag in repo.nodetags(changenode):
232 233 ui.status("tag: %s\n" % tag)
233 234 for parent in parents:
234 235 ui.write("parent: %d:%s\n" % parent)
235 236 if filelog:
236 237 ui.debug("file rev: %d:%s\n" % (filerev, hg.hex(filenode)))
237 238
238 239 ui.debug("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
239 240 hg.hex(changes[0])))
240 241 ui.status("user: %s\n" % changes[1])
241 242 ui.status("date: %s\n" % time.asctime(
242 243 time.localtime(float(changes[2].split(' ')[0]))))
243 244
244 245 if ui.debugflag:
245 246 files = repo.changes(changelog.parents(changenode)[0], changenode)
246 247 for key, value in zip(["files:", "files+:", "files-:"], files):
247 248 if value:
248 249 ui.note("%-12s %s\n" % (key, " ".join(value)))
249 250 else:
250 251 ui.note("files: %s\n" % " ".join(changes[3]))
251 252
252 253 description = changes[4].strip()
253 254 if description:
254 255 if ui.verbose:
255 256 ui.status("description:\n")
256 257 ui.status(description)
257 258 ui.status("\n\n")
258 259 else:
259 260 ui.status("summary: %s\n" % description.splitlines()[0])
260 261 ui.status("\n")
261 262
262 263 def show_version(ui):
263 264 """output version and copyright information"""
264 265 ui.write("Mercurial version %s\n" % version.get_version())
265 266 ui.status(
266 267 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
267 268 "This is free software; see the source for copying conditions. "
268 269 "There is NO\nwarranty; "
269 270 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
270 271 )
271 272
272 273 def help_(ui, cmd=None):
273 274 """show help for a given command or all commands"""
274 275 if cmd:
275 276 try:
276 277 i = find(cmd)
277 278 ui.write("%s\n\n" % i[2])
278 279
279 280 if i[1]:
280 281 for s, l, d, c in i[1]:
281 282 opt = ' '
282 283 if s:
283 284 opt = opt + '-' + s + ' '
284 285 if l:
285 286 opt = opt + '--' + l + ' '
286 287 if d:
287 288 opt = opt + '(' + str(d) + ')'
288 289 ui.write(opt, "\n")
289 290 if c:
290 291 ui.write(' %s\n' % c)
291 292 ui.write("\n")
292 293
293 294 ui.write(i[0].__doc__, "\n")
294 295 except UnknownCommand:
295 296 ui.warn("hg: unknown command %s\n" % cmd)
296 297 sys.exit(0)
297 298 else:
298 299 if ui.verbose:
299 300 show_version(ui)
300 301 ui.write('\n')
301 302 if ui.verbose:
302 303 ui.write('hg commands:\n\n')
303 304 else:
304 305 ui.write('basic hg commands (use "hg help -v" for more):\n\n')
305 306
306 307 h = {}
307 308 for c, e in table.items():
308 309 f = c.split("|")[0]
309 310 if not ui.verbose and not f.startswith("^"):
310 311 continue
311 312 if not ui.debugflag and f.startswith("debug"):
312 313 continue
313 314 f = f.lstrip("^")
314 315 d = ""
315 316 if e[0].__doc__:
316 317 d = e[0].__doc__.splitlines(0)[0].rstrip()
317 318 h[f] = d
318 319
319 320 fns = h.keys()
320 321 fns.sort()
321 322 m = max(map(len, fns))
322 323 for f in fns:
323 324 ui.write(' %-*s %s\n' % (m, f, h[f]))
324 325
325 326 # Commands start here, listed alphabetically
326 327
327 328 def add(ui, repo, *pats, **opts):
328 329 '''add the specified files on the next commit'''
329 330 names = []
330 331 q = dict(zip(pats, pats))
331 332 for src, abs, rel in walk(repo, pats, opts):
332 333 if rel in q or abs in q:
333 334 names.append(abs)
334 335 elif repo.dirstate.state(abs) == '?':
335 336 ui.status('adding %s\n' % rel)
336 337 names.append(abs)
337 338 repo.add(names)
338 339
339 340 def addremove(ui, repo, *pats, **opts):
340 341 """add all new files, delete all missing files"""
341 342 q = dict(zip(pats, pats))
342 343 add, remove = [], []
343 344 for src, abs, rel in walk(repo, pats, opts):
344 345 if src == 'f':
345 346 if repo.dirstate.state(abs) == '?':
346 347 add.append(abs)
347 348 if rel not in q: ui.status('adding ', rel, '\n')
348 349 elif repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
349 350 remove.append(abs)
350 351 if rel not in q: ui.status('removing ', rel, '\n')
351 352 repo.add(add)
352 353 repo.remove(remove)
353 354
354 355 def annotate(ui, repo, *pats, **opts):
355 356 """show changeset information per file line"""
356 357 def getnode(rev):
357 358 return hg.short(repo.changelog.node(rev))
358 359
359 360 def getname(rev):
360 361 try:
361 362 return bcache[rev]
362 363 except KeyError:
363 364 cl = repo.changelog.read(repo.changelog.node(rev))
364 365 name = cl[1]
365 366 f = name.find('@')
366 367 if f >= 0:
367 368 name = name[:f]
368 369 f = name.find('<')
369 370 if f >= 0:
370 371 name = name[f+1:]
371 372 bcache[rev] = name
372 373 return name
373 374
374 375 if not pats:
375 376 raise Abort('at least one file name or pattern required')
376 377
377 378 bcache = {}
378 379 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
379 380 if not opts['user'] and not opts['changeset']:
380 381 opts['number'] = 1
381 382
382 383 if opts['rev']:
383 384 node = repo.changelog.lookup(opts['rev'])
384 385 else:
385 386 node = repo.dirstate.parents()[0]
386 387 change = repo.changelog.read(node)
387 388 mmap = repo.manifest.read(change[0])
388 389 for src, abs, rel in walk(repo, pats, opts):
389 390 if abs not in mmap:
390 391 ui.warn("warning: %s is not in the repository!\n" % rel)
391 392 continue
392 393
393 394 lines = repo.file(abs).annotate(mmap[abs])
394 395 pieces = []
395 396
396 397 for o, f in opmap:
397 398 if opts[o]:
398 399 l = [f(n) for n, dummy in lines]
399 400 if l:
400 401 m = max(map(len, l))
401 402 pieces.append(["%*s" % (m, x) for x in l])
402 403
403 404 if pieces:
404 405 for p, l in zip(zip(*pieces), lines):
405 406 ui.write("%s: %s" % (" ".join(p), l[1]))
406 407
407 408 def cat(ui, repo, file1, rev=None, **opts):
408 409 """output the latest or given revision of a file"""
409 410 r = repo.file(relpath(repo, [file1])[0])
410 411 if rev:
411 412 n = r.lookup(rev)
412 413 else:
413 414 n = r.tip()
414 415 fp = make_file(repo, r, opts['output'], node=n)
415 416 fp.write(r.read(n))
416 417
417 418 def clone(ui, source, dest=None, **opts):
418 419 """make a copy of an existing repository"""
419 420 if dest is None:
420 421 dest = os.path.basename(os.path.normpath(source))
421 422
422 423 if os.path.exists(dest):
423 424 ui.warn("abort: destination '%s' already exists\n" % dest)
424 425 return 1
425 426
426 427 class Dircleanup:
427 428 def __init__(self, dir_):
428 429 self.rmtree = shutil.rmtree
429 430 self.dir_ = dir_
430 431 os.mkdir(dir_)
431 432 def close(self):
432 433 self.dir_ = None
433 434 def __del__(self):
434 435 if self.dir_:
435 436 self.rmtree(self.dir_, True)
436 437
437 438 d = Dircleanup(dest)
438 439 abspath = source
439 440 source = ui.expandpath(source)
440 441 other = hg.repository(ui, source)
441 442
442 443 if other.dev() != -1:
443 444 abspath = os.path.abspath(source)
444 445 copyfile = (os.stat(dest).st_dev == other.dev()
445 446 and getattr(os, 'link', None) or shutil.copy2)
446 447 if copyfile is not shutil.copy2:
447 448 ui.note("cloning by hardlink\n")
448 449 util.copytree(os.path.join(source, ".hg"), os.path.join(dest, ".hg"),
449 450 copyfile)
450 451 try:
451 452 os.unlink(os.path.join(dest, ".hg", "dirstate"))
452 453 except OSError:
453 454 pass
454 455
455 456 repo = hg.repository(ui, dest)
456 457
457 458 else:
458 459 repo = hg.repository(ui, dest, create=1)
459 460 repo.pull(other)
460 461
461 462 f = repo.opener("hgrc", "w")
462 463 f.write("[paths]\n")
463 464 f.write("default = %s\n" % abspath)
464 465
465 466 if not opts['noupdate']:
466 467 update(ui, repo)
467 468
468 469 d.close()
469 470
470 471 def commit(ui, repo, *files, **opts):
471 472 """commit the specified files or all outstanding changes"""
472 473 if opts['text']:
473 474 ui.warn("Warning: -t and --text is deprecated,"
474 475 " please use -m or --message instead.\n")
475 476 message = opts['message'] or opts['text']
476 477 logfile = opts['logfile']
477 478 if not message and logfile:
478 479 try:
479 480 message = open(logfile).read()
480 481 except IOError, why:
481 482 ui.warn("Can't read commit message %s: %s\n" % (logfile, why))
482 483
483 484 if opts['addremove']:
484 485 addremove(ui, repo, *files)
485 486 repo.commit(relpath(repo, files), message, opts['user'], opts['date'])
486 487
487 488 def copy(ui, repo, source, dest):
488 489 """mark a file as copied or renamed for the next commit"""
489 490 return repo.copy(*relpath(repo, (source, dest)))
490 491
491 492 def debugcheckstate(ui, repo):
492 493 """validate the correctness of the current dirstate"""
493 494 parent1, parent2 = repo.dirstate.parents()
494 495 repo.dirstate.read()
495 496 dc = repo.dirstate.map
496 497 keys = dc.keys()
497 498 keys.sort()
498 499 m1n = repo.changelog.read(parent1)[0]
499 500 m2n = repo.changelog.read(parent2)[0]
500 501 m1 = repo.manifest.read(m1n)
501 502 m2 = repo.manifest.read(m2n)
502 503 errors = 0
503 504 for f in dc:
504 505 state = repo.dirstate.state(f)
505 506 if state in "nr" and f not in m1:
506 507 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
507 508 errors += 1
508 509 if state in "a" and f in m1:
509 510 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
510 511 errors += 1
511 512 if state in "m" and f not in m1 and f not in m2:
512 513 ui.warn("%s in state %s, but not in either manifest\n" %
513 514 (f, state))
514 515 errors += 1
515 516 for f in m1:
516 517 state = repo.dirstate.state(f)
517 518 if state not in "nrm":
518 519 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
519 520 errors += 1
520 521 if errors:
521 522 raise Abort(".hg/dirstate inconsistent with current parent's manifest")
522 523
523 524 def debugstate(ui, repo):
524 525 """show the contents of the current dirstate"""
525 526 repo.dirstate.read()
526 527 dc = repo.dirstate.map
527 528 keys = dc.keys()
528 529 keys.sort()
529 530 for file_ in keys:
530 531 ui.write("%c %s\n" % (dc[file_][0], file_))
531 532
532 533 def debugindex(ui, file_):
533 534 """dump the contents of an index file"""
534 535 r = hg.revlog(hg.opener(""), file_, "")
535 536 ui.write(" rev offset length base linkrev" +
536 537 " p1 p2 nodeid\n")
537 538 for i in range(r.count()):
538 539 e = r.index[i]
539 540 ui.write("% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s..\n" % (
540 541 i, e[0], e[1], e[2], e[3],
541 542 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5])))
542 543
543 544 def debugindexdot(ui, file_):
544 545 """dump an index DAG as a .dot file"""
545 546 r = hg.revlog(hg.opener(""), file_, "")
546 547 ui.write("digraph G {\n")
547 548 for i in range(r.count()):
548 549 e = r.index[i]
549 550 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
550 551 if e[5] != hg.nullid:
551 552 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
552 553 ui.write("}\n")
553 554
554 555 def diff(ui, repo, *pats, **opts):
555 556 """diff working directory (or selected files)"""
556 557 revs = []
557 558 if opts['rev']:
558 559 revs = map(lambda x: repo.lookup(x), opts['rev'])
559 560
560 561 if len(revs) > 2:
561 562 raise Abort("too many revisions to diff")
562 563
563 564 files = []
564 565 for src, abs, rel in walk(repo, pats, opts):
565 566 files.append(abs)
566 567 dodiff(sys.stdout, ui, repo, files, *revs)
567 568
568 569 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
569 570 node = repo.lookup(changeset)
570 571 prev, other = repo.changelog.parents(node)
571 572 change = repo.changelog.read(node)
572 573
573 574 fp = make_file(repo, repo.changelog, opts['output'],
574 575 node=node, total=total, seqno=seqno,
575 576 revwidth=revwidth)
576 577 if fp != sys.stdout:
577 578 ui.note("%s\n" % fp.name)
578 579
579 580 fp.write("# HG changeset patch\n")
580 581 fp.write("# User %s\n" % change[1])
581 582 fp.write("# Node ID %s\n" % hg.hex(node))
582 583 fp.write("# Parent %s\n" % hg.hex(prev))
583 584 if other != hg.nullid:
584 585 fp.write("# Parent %s\n" % hg.hex(other))
585 586 fp.write(change[4].rstrip())
586 587 fp.write("\n\n")
587 588
588 589 dodiff(fp, ui, repo, None, prev, node)
589 590 if fp != sys.stdout: fp.close()
590 591
591 592 def export(ui, repo, *changesets, **opts):
592 593 """dump the header and diffs for one or more changesets"""
593 594 if not changesets:
594 595 raise Abort("export requires at least one changeset")
595 596 seqno = 0
596 597 revs = list(revrange(ui, repo, changesets))
597 598 total = len(revs)
598 599 revwidth = max(len(revs[0]), len(revs[-1]))
599 600 ui.note(len(revs) > 1 and "Exporting patches:\n" or "Exporting patch:\n")
600 601 for cset in revs:
601 602 seqno += 1
602 603 doexport(ui, repo, cset, seqno, total, revwidth, opts)
603 604
604 605 def forget(ui, repo, *pats, **opts):
605 606 """don't add the specified files on the next commit"""
606 607 q = dict(zip(pats, pats))
607 608 forget = []
608 609 for src, abs, rel in walk(repo, pats, opts):
609 610 if repo.dirstate.state(abs) == 'a':
610 611 forget.append(abs)
611 612 if rel not in q: ui.status('forgetting ', rel, '\n')
612 613 repo.forget(forget)
613 614
614 615 def heads(ui, repo):
615 616 """show current repository heads"""
616 617 for n in repo.changelog.heads():
617 618 show_changeset(ui, repo, changenode=n)
618 619
619 620 def identify(ui, repo):
620 621 """print information about the working copy"""
621 622 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
622 623 if not parents:
623 624 ui.write("unknown\n")
624 625 return
625 626
626 627 hexfunc = ui.verbose and hg.hex or hg.short
627 628 (c, a, d, u) = repo.changes()
628 629 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
629 630 (c or a or d) and "+" or "")]
630 631
631 632 if not ui.quiet:
632 633 # multiple tags for a single parent separated by '/'
633 634 parenttags = ['/'.join(tags)
634 635 for tags in map(repo.nodetags, parents) if tags]
635 636 # tags for multiple parents separated by ' + '
636 637 if parenttags:
637 638 output.append(' + '.join(parenttags))
638 639
639 640 ui.write("%s\n" % ' '.join(output))
640 641
641 642 def import_(ui, repo, patch1, *patches, **opts):
642 643 """import an ordered set of patches"""
643 644 try:
644 645 import psyco
645 646 psyco.full()
646 647 except ImportError:
647 648 pass
648 649
649 650 patches = (patch1,) + patches
650 651
651 652 d = opts["base"]
652 653 strip = opts["strip"]
653 654
654 655 for patch in patches:
655 656 ui.status("applying %s\n" % patch)
656 657 pf = os.path.join(d, patch)
657 658
658 659 message = []
659 660 user = None
660 661 hgpatch = False
661 662 for line in file(pf):
662 663 line = line.rstrip()
663 664 if line.startswith("--- ") or line.startswith("diff -r"):
664 665 break
665 666 elif hgpatch:
666 667 # parse values when importing the result of an hg export
667 668 if line.startswith("# User "):
668 669 user = line[7:]
669 670 ui.debug('User: %s\n' % user)
670 671 elif not line.startswith("# ") and line:
671 672 message.append(line)
672 673 hgpatch = False
673 674 elif line == '# HG changeset patch':
674 675 hgpatch = True
675 676 else:
676 677 message.append(line)
677 678
678 679 # make sure message isn't empty
679 680 if not message:
680 681 message = "imported patch %s\n" % patch
681 682 else:
682 683 message = "%s\n" % '\n'.join(message)
683 684 ui.debug('message:\n%s\n' % message)
684 685
685 686 f = os.popen("patch -p%d < %s" % (strip, pf))
686 687 files = []
687 688 for l in f.read().splitlines():
688 689 l.rstrip('\r\n');
689 690 ui.status("%s\n" % l)
690 691 if l.startswith('patching file '):
691 692 pf = l[14:]
692 693 if pf not in files:
693 694 files.append(pf)
694 695 patcherr = f.close()
695 696 if patcherr:
696 697 raise Abort("patch failed")
697 698
698 699 if len(files) > 0:
699 700 addremove(ui, repo, *files)
700 701 repo.commit(files, message, user)
701 702
702 703 def init(ui, source=None):
703 704 """create a new repository in the current directory"""
704 705
705 706 if source:
706 707 raise Abort("no longer supported: use \"hg clone\" instead")
707 708 hg.repository(ui, ".", create=1)
708 709
709 710 def locate(ui, repo, *pats, **opts):
710 711 """locate files matching specific patterns"""
711 712 end = '\n'
712 713 if opts['print0']: end = '\0'
713 714
714 715 for src, abs, rel in walk(repo, pats, opts, '(?:.*/|)'):
715 716 if repo.dirstate.state(abs) == '?': continue
716 717 if opts['fullpath']:
717 718 ui.write(os.path.join(repo.root, abs), end)
718 719 else:
719 720 ui.write(rel, end)
720 721
721 722 def log(ui, repo, f=None, **opts):
722 723 """show the revision history of the repository or a single file"""
723 724 if f:
724 725 files = relpath(repo, [f])
725 726 filelog = repo.file(files[0])
726 727 log = filelog
727 728 lookup = filelog.lookup
728 729 else:
729 730 files = None
730 731 filelog = None
731 732 log = repo.changelog
732 733 lookup = repo.lookup
733 734 revlist = []
734 735 revs = [log.rev(lookup(rev)) for rev in opts['rev']]
735 736 while revs:
736 737 if len(revs) == 1:
737 738 revlist.append(revs.pop(0))
738 739 else:
739 740 a = revs.pop(0)
740 741 b = revs.pop(0)
741 742 off = a > b and -1 or 1
742 743 revlist.extend(range(a, b + off, off))
743 744
744 745 for i in revlist or range(log.count() - 1, -1, -1):
745 746 show_changeset(ui, repo, filelog=filelog, rev=i)
746 747 if opts['patch']:
747 748 if filelog:
748 749 filenode = filelog.node(i)
749 750 i = filelog.linkrev(filenode)
750 751 changenode = repo.changelog.node(i)
751 752 prev, other = repo.changelog.parents(changenode)
752 753 dodiff(sys.stdout, ui, repo, files, prev, changenode)
753 754 ui.write("\n\n")
754 755
755 756 def manifest(ui, repo, rev=None):
756 757 """output the latest or given revision of the project manifest"""
757 758 if rev:
758 759 try:
759 760 # assume all revision numbers are for changesets
760 761 n = repo.lookup(rev)
761 762 change = repo.changelog.read(n)
762 763 n = change[0]
763 764 except hg.RepoError:
764 765 n = repo.manifest.lookup(rev)
765 766 else:
766 767 n = repo.manifest.tip()
767 768 m = repo.manifest.read(n)
768 769 mf = repo.manifest.readflags(n)
769 770 files = m.keys()
770 771 files.sort()
771 772
772 773 for f in files:
773 774 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
774 775
775 776 def parents(ui, repo, rev=None):
776 777 """show the parents of the working dir or revision"""
777 778 if rev:
778 779 p = repo.changelog.parents(repo.lookup(rev))
779 780 else:
780 781 p = repo.dirstate.parents()
781 782
782 783 for n in p:
783 784 if n != hg.nullid:
784 785 show_changeset(ui, repo, changenode=n)
785 786
786 787 def paths(ui, repo, search = None):
787 788 """show path or list of available paths"""
788 789 if search:
789 790 for name, path in ui.configitems("paths"):
790 791 if name == search:
791 792 ui.write("%s\n" % path)
792 793 return
793 794 ui.warn("not found!\n")
794 795 return 1
795 796 else:
796 797 for name, path in ui.configitems("paths"):
797 798 ui.write("%s = %s\n" % (name, path))
798 799
799 800 def pull(ui, repo, source="default", **opts):
800 801 """pull changes from the specified source"""
801 802 source = ui.expandpath(source)
802 803 ui.status('pulling from %s\n' % (source))
803 804
804 805 other = hg.repository(ui, source)
805 806 r = repo.pull(other)
806 807 if not r:
807 808 if opts['update']:
808 809 return update(ui, repo)
809 810 else:
810 811 ui.status("(run 'hg update' to get a working copy)\n")
811 812
812 813 return r
813 814
814 815 def push(ui, repo, dest="default-push"):
815 816 """push changes to the specified destination"""
816 817 dest = ui.expandpath(dest)
817 818 ui.status('pushing to %s\n' % (dest))
818 819
819 820 other = hg.repository(ui, dest)
820 821 r = repo.push(other)
821 822 return r
822 823
823 824 def rawcommit(ui, repo, *flist, **rc):
824 825 "raw commit interface"
825 826 if rc['text']:
826 827 ui.warn("Warning: -t and --text is deprecated,"
827 828 " please use -m or --message instead.\n")
828 829 message = rc['message'] or rc['text']
829 830 if not message and rc['logfile']:
830 831 try:
831 832 message = open(rc['logfile']).read()
832 833 except IOError:
833 834 pass
834 835 if not message and not rc['logfile']:
835 836 ui.warn("abort: missing commit message\n")
836 837 return 1
837 838
838 839 files = relpath(repo, list(flist))
839 840 if rc['files']:
840 841 files += open(rc['files']).read().splitlines()
841 842
842 843 rc['parent'] = map(repo.lookup, rc['parent'])
843 844
844 845 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
845 846
846 847 def recover(ui, repo):
847 848 """roll back an interrupted transaction"""
848 849 repo.recover()
849 850
850 851 def remove(ui, repo, file1, *files):
851 852 """remove the specified files on the next commit"""
852 853 repo.remove(relpath(repo, (file1,) + files))
853 854
854 855 def revert(ui, repo, *names, **opts):
855 856 """revert modified files or dirs back to their unmodified states"""
856 857 node = opts['rev'] and repo.lookup(opts['rev']) or \
857 858 repo.dirstate.parents()[0]
858 859 root = os.path.realpath(repo.root)
859 860
860 861 def trimpath(p):
861 862 p = os.path.realpath(p)
862 863 if p.startswith(root):
863 864 rest = p[len(root):]
864 865 if not rest:
865 866 return rest
866 867 if p.startswith(os.sep):
867 868 return rest[1:]
868 869 return p
869 870
870 871 relnames = map(trimpath, names or [os.getcwd()])
871 872 chosen = {}
872 873
873 874 def choose(name):
874 875 def body(name):
875 876 for r in relnames:
876 877 if not name.startswith(r):
877 878 continue
878 879 rest = name[len(r):]
879 880 if not rest:
880 881 return r, True
881 882 depth = rest.count(os.sep)
882 883 if not r:
883 884 if depth == 0 or not opts['nonrecursive']:
884 885 return r, True
885 886 elif rest[0] == os.sep:
886 887 if depth == 1 or not opts['nonrecursive']:
887 888 return r, True
888 889 return None, False
889 890 relname, ret = body(name)
890 891 if ret:
891 892 chosen[relname] = 1
892 893 return ret
893 894
894 895 r = repo.update(node, False, True, choose, False)
895 896 for n in relnames:
896 897 if n not in chosen:
897 898 ui.warn('error: no matches for %s\n' % n)
898 899 r = 1
899 900 sys.stdout.flush()
900 901 return r
901 902
902 903 def root(ui, repo):
903 904 """print the root (top) of the current working dir"""
904 905 ui.write(repo.root + "\n")
905 906
906 907 def serve(ui, repo, **opts):
907 908 """export the repository via HTTP"""
908 909
909 910 if opts["stdio"]:
910 911 fin, fout = sys.stdin, sys.stdout
911 912 sys.stdout = sys.stderr
912 913
913 914 def getarg():
914 915 argline = fin.readline()[:-1]
915 916 arg, l = argline.split()
916 917 val = fin.read(int(l))
917 918 return arg, val
918 919 def respond(v):
919 920 fout.write("%d\n" % len(v))
920 921 fout.write(v)
921 922 fout.flush()
922 923
923 924 lock = None
924 925
925 926 while 1:
926 927 cmd = fin.readline()[:-1]
927 928 if cmd == '':
928 929 return
929 930 if cmd == "heads":
930 931 h = repo.heads()
931 932 respond(" ".join(map(hg.hex, h)) + "\n")
932 933 if cmd == "lock":
933 934 lock = repo.lock()
934 935 respond("")
935 936 if cmd == "unlock":
936 937 if lock:
937 938 lock.release()
938 939 lock = None
939 940 respond("")
940 941 elif cmd == "branches":
941 942 arg, nodes = getarg()
942 943 nodes = map(hg.bin, nodes.split(" "))
943 944 r = []
944 945 for b in repo.branches(nodes):
945 946 r.append(" ".join(map(hg.hex, b)) + "\n")
946 947 respond("".join(r))
947 948 elif cmd == "between":
948 949 arg, pairs = getarg()
949 950 pairs = [map(hg.bin, p.split("-")) for p in pairs.split(" ")]
950 951 r = []
951 952 for b in repo.between(pairs):
952 953 r.append(" ".join(map(hg.hex, b)) + "\n")
953 954 respond("".join(r))
954 955 elif cmd == "changegroup":
955 956 nodes = []
956 957 arg, roots = getarg()
957 958 nodes = map(hg.bin, roots.split(" "))
958 959
959 960 cg = repo.changegroup(nodes)
960 961 while 1:
961 962 d = cg.read(4096)
962 963 if not d:
963 964 break
964 965 fout.write(d)
965 966
966 967 fout.flush()
967 968
968 969 elif cmd == "addchangegroup":
969 970 if not lock:
970 971 respond("not locked")
971 972 continue
972 973 respond("")
973 974
974 975 r = repo.addchangegroup(fin)
975 976 respond("")
976 977
977 978 def openlog(opt, default):
978 979 if opts[opt] and opts[opt] != '-':
979 980 return open(opts[opt], 'w')
980 981 else:
981 982 return default
982 983
983 984 httpd = hgweb.create_server(repo.root, opts["name"], opts["templates"],
984 985 opts["address"], opts["port"],
985 986 openlog('accesslog', sys.stdout),
986 987 openlog('errorlog', sys.stderr))
987 988 if ui.verbose:
988 989 addr, port = httpd.socket.getsockname()
989 990 if addr == '0.0.0.0':
990 991 addr = socket.gethostname()
991 992 else:
992 993 try:
993 994 addr = socket.gethostbyaddr(addr)[0]
994 995 except socket.error:
995 996 pass
996 997 if port != 80:
997 998 ui.status('listening at http://%s:%d/\n' % (addr, port))
998 999 else:
999 1000 ui.status('listening at http://%s/\n' % addr)
1000 1001 httpd.serve_forever()
1001 1002
1002 1003 def status(ui, repo, *pats, **opts):
1003 1004 '''show changed files in the working directory
1004 1005
1005 1006 M = modified
1006 1007 A = added
1007 1008 R = removed
1008 1009 ? = not tracked'''
1009 1010
1010 (c, a, d, u) = repo.changes(match = matchpats(repo.getcwd(), pats, opts))
1011 files, matchfn = matchpats(repo.getcwd(), pats, opts)
1012 (c, a, d, u) = repo.changes(files = files, match = matchfn)
1011 1013 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
1012 1014
1013 1015 for f in c:
1014 1016 ui.write("M ", f, "\n")
1015 1017 for f in a:
1016 1018 ui.write("A ", f, "\n")
1017 1019 for f in d:
1018 1020 ui.write("R ", f, "\n")
1019 1021 for f in u:
1020 1022 ui.write("? ", f, "\n")
1021 1023
1022 1024 def tag(ui, repo, name, rev=None, **opts):
1023 1025 """add a tag for the current tip or a given revision"""
1024 1026 if opts['text']:
1025 1027 ui.warn("Warning: -t and --text is deprecated,"
1026 1028 " please use -m or --message instead.\n")
1027 1029 if name == "tip":
1028 1030 ui.warn("abort: 'tip' is a reserved name!\n")
1029 1031 return -1
1030 1032 if rev:
1031 1033 r = hg.hex(repo.lookup(rev))
1032 1034 else:
1033 1035 r = hg.hex(repo.changelog.tip())
1034 1036
1035 1037 if name.find(revrangesep) >= 0:
1036 1038 ui.warn("abort: '%s' cannot be used in a tag name\n" % revrangesep)
1037 1039 return -1
1038 1040
1039 1041 if opts['local']:
1040 1042 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
1041 1043 return
1042 1044
1043 1045 (c, a, d, u) = repo.changes()
1044 1046 for x in (c, a, d, u):
1045 1047 if ".hgtags" in x:
1046 1048 ui.warn("abort: working copy of .hgtags is changed!\n")
1047 1049 ui.status("(please commit .hgtags manually)\n")
1048 1050 return -1
1049 1051
1050 1052 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
1051 1053 if repo.dirstate.state(".hgtags") == '?':
1052 1054 repo.add([".hgtags"])
1053 1055
1054 1056 message = (opts['message'] or opts['text'] or
1055 1057 "Added tag %s for changeset %s" % (name, r))
1056 1058 repo.commit([".hgtags"], message, opts['user'], opts['date'])
1057 1059
1058 1060 def tags(ui, repo):
1059 1061 """list repository tags"""
1060 1062
1061 1063 l = repo.tagslist()
1062 1064 l.reverse()
1063 1065 for t, n in l:
1064 1066 try:
1065 1067 r = "%5d:%s" % (repo.changelog.rev(n), hg.hex(n))
1066 1068 except KeyError:
1067 1069 r = " ?:?"
1068 1070 ui.write("%-30s %s\n" % (t, r))
1069 1071
1070 1072 def tip(ui, repo):
1071 1073 """show the tip revision"""
1072 1074 n = repo.changelog.tip()
1073 1075 show_changeset(ui, repo, changenode=n)
1074 1076
1075 1077 def undo(ui, repo):
1076 1078 """undo the last commit or pull
1077 1079
1078 1080 Roll back the last pull or commit transaction on the
1079 1081 repository, restoring the project to its earlier state.
1080 1082
1081 1083 This command should be used with care. There is only one level of
1082 1084 undo and there is no redo.
1083 1085
1084 1086 This command is not intended for use on public repositories. Once
1085 1087 a change is visible for pull by other users, undoing it locally is
1086 1088 ineffective.
1087 1089 """
1088 1090 repo.undo()
1089 1091
1090 1092 def update(ui, repo, node=None, merge=False, clean=False):
1091 1093 '''update or merge working directory
1092 1094
1093 1095 If there are no outstanding changes in the working directory and
1094 1096 there is a linear relationship between the current version and the
1095 1097 requested version, the result is the requested version.
1096 1098
1097 1099 Otherwise the result is a merge between the contents of the
1098 1100 current working directory and the requested version. Files that
1099 1101 changed between either parent are marked as changed for the next
1100 1102 commit and a commit must be performed before any further updates
1101 1103 are allowed.
1102 1104 '''
1103 1105 node = node and repo.lookup(node) or repo.changelog.tip()
1104 1106 return repo.update(node, allow=merge, force=clean)
1105 1107
1106 1108 def verify(ui, repo):
1107 1109 """verify the integrity of the repository"""
1108 1110 return repo.verify()
1109 1111
1110 1112 # Command options and aliases are listed here, alphabetically
1111 1113
1112 1114 table = {
1113 1115 "^add": (add,
1114 1116 [('I', 'include', [], 'include path in search'),
1115 1117 ('X', 'exclude', [], 'exclude path from search')],
1116 1118 "hg add [FILE]..."),
1117 1119 "addremove": (addremove,
1118 1120 [('I', 'include', [], 'include path in search'),
1119 1121 ('X', 'exclude', [], 'exclude path from search')],
1120 1122 "hg addremove [OPTION]... [FILE]..."),
1121 1123 "^annotate":
1122 1124 (annotate,
1123 1125 [('r', 'rev', '', 'revision'),
1124 1126 ('u', 'user', None, 'show user'),
1125 1127 ('n', 'number', None, 'show revision number'),
1126 1128 ('c', 'changeset', None, 'show changeset'),
1127 1129 ('I', 'include', [], 'include path in search'),
1128 1130 ('X', 'exclude', [], 'exclude path from search')],
1129 1131 'hg annotate [-r REV] [-u] [-n] [-c] FILE...'),
1130 1132 "cat":
1131 1133 (cat,
1132 1134 [('o', 'output', "", 'output to file')],
1133 1135 'hg cat [-o OUTFILE] FILE [REV]'),
1134 1136 "^clone":
1135 1137 (clone,
1136 1138 [('U', 'noupdate', None, 'skip update after cloning')],
1137 1139 'hg clone [-U] SOURCE [DEST]'),
1138 1140 "^commit|ci":
1139 1141 (commit,
1140 1142 [('A', 'addremove', None, 'run add/remove during commit'),
1141 1143 ('m', 'message', "", 'commit message'),
1142 1144 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1143 1145 ('l', 'logfile', "", 'commit message file'),
1144 1146 ('d', 'date', "", 'date code'),
1145 1147 ('u', 'user', "", 'user')],
1146 1148 'hg commit [OPTION]... [FILE]...'),
1147 1149 "copy": (copy, [], 'hg copy SOURCE DEST'),
1148 1150 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
1149 1151 "debugstate": (debugstate, [], 'debugstate'),
1150 1152 "debugindex": (debugindex, [], 'debugindex FILE'),
1151 1153 "debugindexdot": (debugindexdot, [], 'debugindexdot FILE'),
1152 1154 "^diff":
1153 1155 (diff,
1154 1156 [('r', 'rev', [], 'revision'),
1155 1157 ('I', 'include', [], 'include path in search'),
1156 1158 ('X', 'exclude', [], 'exclude path from search')],
1157 1159 'hg diff [-r REV1 [-r REV2]] [FILE]...'),
1158 1160 "^export":
1159 1161 (export,
1160 1162 [('o', 'output', "", 'output to file')],
1161 1163 "hg export [-o OUTFILE] REV..."),
1162 1164 "forget": (forget,
1163 1165 [('I', 'include', [], 'include path in search'),
1164 1166 ('X', 'exclude', [], 'exclude path from search')],
1165 1167 "hg forget FILE..."),
1166 1168 "heads": (heads, [], 'hg heads'),
1167 1169 "help": (help_, [], 'hg help [COMMAND]'),
1168 1170 "identify|id": (identify, [], 'hg identify'),
1169 1171 "import|patch":
1170 1172 (import_,
1171 1173 [('p', 'strip', 1, 'path strip'),
1172 1174 ('b', 'base', "", 'base path')],
1173 1175 "hg import [-p NUM] [-b BASE] PATCH..."),
1174 1176 "^init": (init, [], 'hg init'),
1175 1177 "locate":
1176 1178 (locate,
1177 1179 [('r', 'rev', '', 'revision'),
1178 1180 ('0', 'print0', None, 'end records with NUL'),
1179 1181 ('f', 'fullpath', None, 'print complete paths'),
1180 1182 ('I', 'include', [], 'include path in search'),
1181 1183 ('X', 'exclude', [], 'exclude path from search')],
1182 1184 'hg locate [-r REV] [-f] [-0] [PATTERN]...'),
1183 1185 "^log|history":
1184 1186 (log,
1185 1187 [('r', 'rev', [], 'revision'),
1186 1188 ('p', 'patch', None, 'show patch')],
1187 1189 'hg log [-r REV1 [-r REV2]] [-p] [FILE]'),
1188 1190 "manifest": (manifest, [], 'hg manifest [REV]'),
1189 1191 "parents": (parents, [], 'hg parents [REV]'),
1190 1192 "paths": (paths, [], 'hg paths [name]'),
1191 1193 "^pull":
1192 1194 (pull,
1193 1195 [('u', 'update', None, 'update working directory')],
1194 1196 'hg pull [-u] [SOURCE]'),
1195 1197 "^push": (push, [], 'hg push [DEST]'),
1196 1198 "rawcommit":
1197 1199 (rawcommit,
1198 1200 [('p', 'parent', [], 'parent'),
1199 1201 ('d', 'date', "", 'date code'),
1200 1202 ('u', 'user', "", 'user'),
1201 1203 ('F', 'files', "", 'file list'),
1202 1204 ('m', 'message', "", 'commit message'),
1203 1205 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1204 1206 ('l', 'logfile', "", 'commit message file')],
1205 1207 'hg rawcommit [OPTION]... [FILE]...'),
1206 1208 "recover": (recover, [], "hg recover"),
1207 1209 "^remove|rm": (remove, [], "hg remove FILE..."),
1208 1210 "^revert":
1209 1211 (revert,
1210 1212 [("n", "nonrecursive", None, "don't recurse into subdirs"),
1211 1213 ("r", "rev", "", "revision")],
1212 1214 "hg revert [-n] [-r REV] [NAME]..."),
1213 1215 "root": (root, [], "hg root"),
1214 1216 "^serve":
1215 1217 (serve,
1216 1218 [('A', 'accesslog', '', 'access log file'),
1217 1219 ('E', 'errorlog', '', 'error log file'),
1218 1220 ('p', 'port', 8000, 'listen port'),
1219 1221 ('a', 'address', '', 'interface address'),
1220 1222 ('n', 'name', os.getcwd(), 'repository name'),
1221 1223 ('', 'stdio', None, 'for remote clients'),
1222 1224 ('t', 'templates', "", 'template map')],
1223 1225 "hg serve [OPTION]..."),
1224 1226 "^status": (status,
1225 1227 [('I', 'include', [], 'include path in search'),
1226 1228 ('X', 'exclude', [], 'exclude path from search')],
1227 1229 'hg status [FILE]...'),
1228 1230 "tag":
1229 1231 (tag,
1230 1232 [('l', 'local', None, 'make the tag local'),
1231 1233 ('m', 'message', "", 'commit message'),
1232 1234 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1233 1235 ('d', 'date', "", 'date code'),
1234 1236 ('u', 'user', "", 'user')],
1235 1237 'hg tag [OPTION]... NAME [REV]'),
1236 1238 "tags": (tags, [], 'hg tags'),
1237 1239 "tip": (tip, [], 'hg tip'),
1238 1240 "undo": (undo, [], 'hg undo'),
1239 1241 "^update|up|checkout|co":
1240 1242 (update,
1241 1243 [('m', 'merge', None, 'allow merging of conflicts'),
1242 1244 ('C', 'clean', None, 'overwrite locally modified files')],
1243 1245 'hg update [-m] [-C] [REV]'),
1244 1246 "verify": (verify, [], 'hg verify'),
1245 1247 "version": (show_version, [], 'hg version'),
1246 1248 }
1247 1249
1248 1250 globalopts = [('v', 'verbose', None, 'verbose'),
1249 1251 ('', 'debug', None, 'debug'),
1250 1252 ('q', 'quiet', None, 'quiet'),
1251 1253 ('', 'profile', None, 'profile'),
1252 1254 ('R', 'repository', "", 'repository root directory'),
1253 1255 ('', 'traceback', None, 'print traceback on exception'),
1254 1256 ('y', 'noninteractive', None, 'run non-interactively'),
1255 1257 ('', 'version', None, 'output version information and exit'),
1256 1258 ('', 'time', None, 'time how long the command takes'),
1257 1259 ]
1258 1260
1259 1261 norepo = "clone init version help debugindex debugindexdot"
1260 1262
1261 1263 def find(cmd):
1262 1264 for e in table.keys():
1263 1265 if re.match("(%s)$" % e, cmd):
1264 1266 return table[e]
1265 1267
1266 1268 raise UnknownCommand(cmd)
1267 1269
1268 1270 class SignalInterrupt(Exception):
1269 1271 """Exception raised on SIGTERM and SIGHUP."""
1270 1272
1271 1273 def catchterm(*args):
1272 1274 raise SignalInterrupt
1273 1275
1274 1276 def run():
1275 1277 sys.exit(dispatch(sys.argv[1:]))
1276 1278
1277 1279 class ParseError(Exception):
1278 1280 """Exception raised on errors in parsing the command line."""
1279 1281
1280 1282 def parse(args):
1281 1283 options = {}
1282 1284 cmdoptions = {}
1283 1285
1284 1286 try:
1285 1287 args = fancyopts.fancyopts(args, globalopts, options)
1286 1288 except fancyopts.getopt.GetoptError, inst:
1287 1289 raise ParseError(None, inst)
1288 1290
1289 1291 if options["version"]:
1290 1292 return ("version", show_version, [], options, cmdoptions)
1291 1293 elif not args:
1292 1294 return ("help", help_, [], options, cmdoptions)
1293 1295 else:
1294 1296 cmd, args = args[0], args[1:]
1295 1297
1296 1298 i = find(cmd)
1297 1299
1298 1300 # combine global options into local
1299 1301 c = list(i[1])
1300 1302 for o in globalopts:
1301 1303 c.append((o[0], o[1], options[o[1]], o[3]))
1302 1304
1303 1305 try:
1304 1306 args = fancyopts.fancyopts(args, c, cmdoptions)
1305 1307 except fancyopts.getopt.GetoptError, inst:
1306 1308 raise ParseError(cmd, inst)
1307 1309
1308 1310 # separate global options back out
1309 1311 for o in globalopts:
1310 1312 n = o[1]
1311 1313 options[n] = cmdoptions[n]
1312 1314 del cmdoptions[n]
1313 1315
1314 1316 return (cmd, i[0], args, options, cmdoptions)
1315 1317
1316 1318 def dispatch(args):
1317 1319 signal.signal(signal.SIGTERM, catchterm)
1318 1320 try:
1319 1321 signal.signal(signal.SIGHUP, catchterm)
1320 1322 except AttributeError:
1321 1323 pass
1322 1324
1323 1325 try:
1324 1326 cmd, func, args, options, cmdoptions = parse(args)
1325 1327 except ParseError, inst:
1326 1328 u = ui.ui()
1327 1329 if inst.args[0]:
1328 1330 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
1329 1331 help_(u, inst.args[0])
1330 1332 else:
1331 1333 u.warn("hg: %s\n" % inst.args[1])
1332 1334 help_(u)
1333 1335 sys.exit(-1)
1334 1336 except UnknownCommand, inst:
1335 1337 u = ui.ui()
1336 1338 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1337 1339 help_(u)
1338 1340 sys.exit(1)
1339 1341
1340 1342 if options["time"]:
1341 1343 def get_times():
1342 1344 t = os.times()
1343 1345 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
1344 1346 t = (t[0], t[1], t[2], t[3], time.clock())
1345 1347 return t
1346 1348 s = get_times()
1347 1349 def print_time():
1348 1350 t = get_times()
1349 1351 u = ui.ui()
1350 1352 u.warn("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n" %
1351 1353 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
1352 1354 atexit.register(print_time)
1353 1355
1354 1356 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
1355 1357 not options["noninteractive"])
1356 1358
1357 1359 try:
1358 1360 try:
1359 1361 if cmd not in norepo.split():
1360 1362 path = options["repository"] or ""
1361 1363 repo = hg.repository(ui=u, path=path)
1362 1364 d = lambda: func(u, repo, *args, **cmdoptions)
1363 1365 else:
1364 1366 d = lambda: func(u, *args, **cmdoptions)
1365 1367
1366 1368 if options['profile']:
1367 1369 import hotshot, hotshot.stats
1368 1370 prof = hotshot.Profile("hg.prof")
1369 1371 r = prof.runcall(d)
1370 1372 prof.close()
1371 1373 stats = hotshot.stats.load("hg.prof")
1372 1374 stats.strip_dirs()
1373 1375 stats.sort_stats('time', 'calls')
1374 1376 stats.print_stats(40)
1375 1377 return r
1376 1378 else:
1377 1379 return d()
1378 1380 except:
1379 1381 if options['traceback']:
1380 1382 traceback.print_exc()
1381 1383 raise
1382 1384 except util.CommandError, inst:
1383 1385 u.warn("abort: %s\n" % inst.args)
1384 1386 except hg.RepoError, inst:
1385 1387 u.warn("abort: ", inst, "!\n")
1386 1388 except SignalInterrupt:
1387 1389 u.warn("killed!\n")
1388 1390 except KeyboardInterrupt:
1389 1391 u.warn("interrupted!\n")
1390 1392 except IOError, inst:
1391 1393 if hasattr(inst, "code"):
1392 1394 u.warn("abort: %s\n" % inst)
1393 1395 elif hasattr(inst, "reason"):
1394 1396 u.warn("abort: error: %s\n" % inst.reason[1])
1395 1397 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
1396 1398 if u.debugflag: u.warn("broken pipe\n")
1397 1399 else:
1398 1400 raise
1399 1401 except OSError, inst:
1400 1402 if hasattr(inst, "filename"):
1401 1403 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
1402 1404 else:
1403 1405 u.warn("abort: %s\n" % inst.strerror)
1404 1406 except Abort, inst:
1405 1407 u.warn('abort: ', inst.args[0] % inst.args[1:], '\n')
1406 1408 sys.exit(1)
1407 1409 except TypeError, inst:
1408 1410 # was this an argument error?
1409 1411 tb = traceback.extract_tb(sys.exc_info()[2])
1410 1412 if len(tb) > 2: # no
1411 1413 raise
1412 1414 u.debug(inst, "\n")
1413 1415 u.warn("%s: invalid arguments\n" % cmd)
1414 1416 help_(u, cmd)
1415 1417
1416 1418 sys.exit(-1)
@@ -1,210 +1,229 b''
1 1 # util.py - utility functions and platform specfic implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.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 os, errno
9 9 from demandload import *
10 10 demandload(globals(), "re")
11 11
12 12 def unique(g):
13 13 seen = {}
14 14 for f in g:
15 15 if f not in seen:
16 16 seen[f] = 1
17 17 yield f
18 18
19 19 class CommandError(Exception): pass
20 20
21 21 def always(fn): return True
22 22 def never(fn): return False
23 23
24 24 def globre(pat, head = '^', tail = '$'):
25 25 "convert a glob pattern into a regexp"
26 26 i, n = 0, len(pat)
27 27 res = ''
28 28 group = False
29 29 def peek(): return i < n and pat[i]
30 30 while i < n:
31 31 c = pat[i]
32 32 i = i+1
33 33 if c == '*':
34 34 if peek() == '*':
35 35 i += 1
36 36 res += '.*'
37 37 else:
38 38 res += '[^/]*'
39 39 elif c == '?':
40 40 res += '.'
41 41 elif c == '[':
42 42 j = i
43 43 if j < n and pat[j] in '!]':
44 44 j += 1
45 45 while j < n and pat[j] != ']':
46 46 j += 1
47 47 if j >= n:
48 48 res += '\\['
49 49 else:
50 50 stuff = pat[i:j].replace('\\','\\\\')
51 51 i = j + 1
52 52 if stuff[0] == '!':
53 53 stuff = '^' + stuff[1:]
54 54 elif stuff[0] == '^':
55 55 stuff = '\\' + stuff
56 56 res = '%s[%s]' % (res, stuff)
57 57 elif c == '{':
58 58 group = True
59 59 res += '(?:'
60 60 elif c == '}' and group:
61 61 res += ')'
62 62 group = False
63 63 elif c == ',' and group:
64 64 res += '|'
65 65 else:
66 66 res += re.escape(c)
67 67 return head + res + tail
68 68
69 def matcher(cwd, pats, inc, exc, head = ''):
69 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
70
71 def matcher(cwd, names, inc, exc, head = ''):
72 def patlike(name):
73 for prefix in 're:', 'glob:', 'path:':
74 if name.startswith(prefix): return True
75 for c in name:
76 if c in _globchars: return True
77
70 78 def regex(name, tail):
71 79 '''convert a pattern into a regular expression'''
72 80 if name.startswith('re:'):
73 81 return name[3:]
74 82 elif name.startswith('path:'):
75 83 return '^' + re.escape(name[5:]) + '$'
76 84 elif name.startswith('glob:'):
77 85 return head + globre(name[5:], '', tail)
78 86 return head + globre(name, '', tail)
79 87
88 cwdsep = cwd + os.sep
89
80 90 def under(fn):
81 91 """check if fn is under our cwd"""
82 92 return not cwd or fn.startswith(cwdsep)
83 93
84 94 def matchfn(pats, tail):
85 95 """build a matching function from a set of patterns"""
86 96 if pats:
87 97 pat = '(?:%s)' % '|'.join([regex(p, tail) for p in pats])
88 98 if cwd:
89 pat = re.escape(cwd + os.sep) + pat
99 pat = re.escape(cwdsep) + pat
90 100 return re.compile(pat).match
91 101
92 cwdsep = cwd + os.sep
93 patmatch = matchfn(pats, '$') or (lambda fn: True)
102 pats = filter(patlike, names)
103 files = [n for n in names if not patlike(n)]
104 if pats: plain = []
105 elif cwd: plain = [cwdsep + f for f in files]
106 else: plain = files
107
108 patmatch = matchfn(pats, '$')
109 filematch = matchfn(files, '(?:/|$)')
94 110 incmatch = matchfn(inc, '(?:/|$)') or under
95 111 excmatch = matchfn(exc, '(?:/|$)') or (lambda fn: False)
96 112
97 return lambda fn: (incmatch(fn) and not excmatch(fn) and
98 (fn.endswith('/') or patmatch(fn)))
113 return plain, lambda fn: (incmatch(fn) and not excmatch(fn) and
114 (fn.endswith('/') or
115 (not pats and not files) or
116 (pats and patmatch(fn)) or
117 (files and filematch(fn))))
99 118
100 119 def system(cmd, errprefix=None):
101 120 """execute a shell command that must succeed"""
102 121 rc = os.system(cmd)
103 122 if rc:
104 123 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
105 124 explain_exit(rc)[0])
106 125 if errprefix:
107 126 errmsg = "%s: %s" % (errprefix, errmsg)
108 127 raise CommandError(errmsg)
109 128
110 129 def rename(src, dst):
111 130 try:
112 131 os.rename(src, dst)
113 132 except:
114 133 os.unlink(dst)
115 134 os.rename(src, dst)
116 135
117 136 def copytree(src, dst, copyfile):
118 137 """Copy a directory tree, files are copied using 'copyfile'."""
119 138 names = os.listdir(src)
120 139 os.mkdir(dst)
121 140
122 141 for name in names:
123 142 srcname = os.path.join(src, name)
124 143 dstname = os.path.join(dst, name)
125 144 if os.path.isdir(srcname):
126 145 copytree(srcname, dstname, copyfile)
127 146 elif os.path.isfile(srcname):
128 147 copyfile(srcname, dstname)
129 148 else:
130 149 raise IOError("Not a regular file: %r" % srcname)
131 150
132 151 def _makelock_file(info, pathname):
133 152 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
134 153 os.write(ld, info)
135 154 os.close(ld)
136 155
137 156 def _readlock_file(pathname):
138 157 return file(pathname).read()
139 158
140 159 # Platfor specific varients
141 160 if os.name == 'nt':
142 161 nulldev = 'NUL:'
143 162
144 163 def is_exec(f, last):
145 164 return last
146 165
147 166 def set_exec(f, mode):
148 167 pass
149 168
150 169 def pconvert(path):
151 170 return path.replace("\\", "/")
152 171
153 172 makelock = _makelock_file
154 173 readlock = _readlock_file
155 174
156 175 def explain_exit(code):
157 176 return "exited with status %d" % code, code
158 177
159 178 else:
160 179 nulldev = '/dev/null'
161 180
162 181 def is_exec(f, last):
163 182 return (os.stat(f).st_mode & 0100 != 0)
164 183
165 184 def set_exec(f, mode):
166 185 s = os.stat(f).st_mode
167 186 if (s & 0100 != 0) == mode:
168 187 return
169 188 if mode:
170 189 # Turn on +x for every +r bit when making a file executable
171 190 # and obey umask.
172 191 umask = os.umask(0)
173 192 os.umask(umask)
174 193 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
175 194 else:
176 195 os.chmod(f, s & 0666)
177 196
178 197 def pconvert(path):
179 198 return path
180 199
181 200 def makelock(info, pathname):
182 201 try:
183 202 os.symlink(info, pathname)
184 203 except OSError, why:
185 204 if why.errno == errno.EEXIST:
186 205 raise
187 206 else:
188 207 _makelock_file(info, pathname)
189 208
190 209 def readlock(pathname):
191 210 try:
192 211 return os.readlink(pathname)
193 212 except OSError, why:
194 213 if why.errno == errno.EINVAL:
195 214 return _readlock_file(pathname)
196 215 else:
197 216 raise
198 217
199 218 def explain_exit(code):
200 219 """return a 2-tuple (desc, code) describing a process's status"""
201 220 if os.WIFEXITED(code):
202 221 val = os.WEXITSTATUS(code)
203 222 return "exited with status %d" % val, val
204 223 elif os.WIFSIGNALED(code):
205 224 val = os.WTERMSIG(code)
206 225 return "killed by signal %d" % val, val
207 226 elif os.WIFSTOPPED(code):
208 227 val = os.STOPSIG(code)
209 228 return "stopped by signal %d" % val, val
210 229 raise ValueError("invalid exit code")
General Comments 0
You need to be logged in to leave comments. Login now