##// END OF EJS Templates
Handle unknown commands nicely...
mpm@selenic.com -
r252:5eda6c54 default
parent child Browse files
Show More
@@ -1,587 +1,591 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 import os, re, sys, signal, time, mdiff
9 9 from mercurial import fancyopts, ui, hg
10 10
11 11 class UnknownCommand(Exception): pass
12 12
13 13 def filterfiles(filters, files):
14 14 l = [ x for x in files if x in filters ]
15 15
16 16 for t in filters:
17 17 if t and t[-1] != os.sep: t += os.sep
18 18 l += [ x for x in files if x.startswith(t) ]
19 19 return l
20 20
21 21 def relfilter(repo, files):
22 22 if os.getcwd() != repo.root:
23 23 p = os.getcwd()[len(repo.root) + 1: ]
24 24 return filterfiles(p, files)
25 25 return files
26 26
27 27 def relpath(repo, args):
28 28 if os.getcwd() != repo.root:
29 29 p = os.getcwd()[len(repo.root) + 1: ]
30 30 return [ os.path.normpath(os.path.join(p, x)) for x in args ]
31 31 return args
32 32
33 33 def dodiff(repo, files = None, node1 = None, node2 = None):
34 34 def date(c):
35 35 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
36 36
37 37 if node2:
38 38 change = repo.changelog.read(node2)
39 39 mmap2 = repo.manifest.read(change[0])
40 40 (c, a, d) = repo.diffrevs(node1, node2)
41 41 def read(f): return repo.file(f).read(mmap2[f])
42 42 date2 = date(change)
43 43 else:
44 44 date2 = time.asctime()
45 45 (c, a, d, u) = repo.diffdir(repo.root, node1)
46 46 if not node1:
47 47 node1 = repo.dirstate.parents()[0]
48 48 def read(f): return file(os.path.join(repo.root, f)).read()
49 49
50 50 change = repo.changelog.read(node1)
51 51 mmap = repo.manifest.read(change[0])
52 52 date1 = date(change)
53 53
54 54 if files:
55 55 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
56 56
57 57 for f in c:
58 58 to = repo.file(f).read(mmap[f])
59 59 tn = read(f)
60 60 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
61 61 for f in a:
62 62 to = ""
63 63 tn = read(f)
64 64 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
65 65 for f in d:
66 66 to = repo.file(f).read(mmap[f])
67 67 tn = ""
68 68 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
69 69
70 70 def help(ui, cmd=None):
71 71 '''show help'''
72 72 if cmd:
73 73 try:
74 74 i = find(cmd)
75 75 ui.write("%s\n\n" % i[2])
76 76 ui.write(i[0].__doc__, "\n")
77 77 except UnknownCommand:
78 ui.warn("unknown command %s", cmd)
78 ui.warn("unknown command %s" % cmd)
79 79 sys.exit(0)
80 80
81 81 ui.status("""\
82 82 hg commands:
83 83
84 84 add [files...] add the given files in the next commit
85 85 addremove add all new files, delete all missing files
86 86 annotate [files...] show changeset number per file line
87 87 branch <path> create a branch of <path> in this directory
88 88 checkout [changeset] checkout the latest or given changeset
89 89 commit commit all changes to the repository
90 90 diff [files...] diff working directory (or selected files)
91 91 dump <file> [rev] dump the latest or given revision of a file
92 92 dumpmanifest [rev] dump the latest or given revision of the manifest
93 93 export <rev> dump the changeset header and diffs for a revision
94 94 history show changeset history
95 95 init create a new repository in this directory
96 96 log <file> show revision history of a single file
97 97 merge <path> merge changes from <path> into local repository
98 98 recover rollback an interrupted transaction
99 99 remove [files...] remove the given files in the next commit
100 100 serve export the repository via HTTP
101 101 status show new, missing, and changed files in working dir
102 102 tags show current changeset tags
103 103 undo undo the last transaction
104 104 """)
105 105
106 106 def add(ui, repo, file, *files):
107 107 '''add the specified files on the next commit'''
108 108 repo.add(relpath(repo, (file,) + files))
109 109
110 110 def addremove(ui, repo):
111 111 (c, a, d, u) = repo.diffdir(repo.root)
112 112 repo.add(a)
113 113 repo.remove(d)
114 114
115 115 def annotate(u, repo, file, *files, **ops):
116 116 def getnode(rev):
117 117 return hg.short(repo.changelog.node(rev))
118 118
119 119 def getname(rev):
120 120 try:
121 121 return bcache[rev]
122 122 except KeyError:
123 123 cl = repo.changelog.read(repo.changelog.node(rev))
124 124 name = cl[1]
125 125 f = name.find('@')
126 126 if f >= 0:
127 127 name = name[:f]
128 128 bcache[rev] = name
129 129 return name
130 130
131 131 bcache = {}
132 132 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
133 133 if not ops['user'] and not ops['changeset']:
134 134 ops['number'] = 1
135 135
136 136 node = repo.dirstate.parents()[0]
137 137 if ops['revision']:
138 138 node = repo.changelog.lookup(ops['revision'])
139 139 change = repo.changelog.read(node)
140 140 mmap = repo.manifest.read(change[0])
141 141 maxuserlen = 0
142 142 maxchangelen = 0
143 143 for f in relpath(repo, (file,) + files):
144 144 lines = repo.file(f).annotate(mmap[f])
145 145 pieces = []
146 146
147 147 for o, f in opmap:
148 148 if ops[o]:
149 149 l = [ f(n) for n,t in lines ]
150 150 m = max(map(len, l))
151 151 pieces.append([ "%*s" % (m, x) for x in l])
152 152
153 153 for p,l in zip(zip(*pieces), lines):
154 154 u.write(" ".join(p) + ": " + l[1])
155 155
156 156 def branch(ui, path):
157 157 '''branch from a local repository'''
158 158 # this should eventually support remote repos
159 159 os.system("cp -al %s/.hg .hg" % path)
160 160
161 161 def cat(ui, repo, file, rev = []):
162 162 r = repo.file(file)
163 163 n = r.tip()
164 164 if rev: n = r.lookup(rev)
165 165 sys.stdout.write(r.read(n))
166 166
167 167 def checkout(ui, repo, changeset=None):
168 168 '''checkout a given changeset or the current tip'''
169 169 (c, a, d, u) = repo.diffdir(repo.root)
170 170 if c or a or d:
171 171 ui.warn("aborting (outstanding changes in working directory)\n")
172 172 sys.exit(1)
173 173
174 174 node = repo.changelog.tip()
175 175 if changeset:
176 176 node = repo.lookup(changeset)
177 177 repo.checkout(node)
178 178
179 179 def commit(ui, repo, *files):
180 180 """commit the specified files or all outstanding changes"""
181 181 repo.commit(relpath(repo, files))
182 182
183 183 def debugaddchangegroup(ui, repo):
184 184 data = sys.stdin.read()
185 185 repo.addchangegroup(data)
186 186
187 187 def debugchangegroup(ui, repo, roots):
188 188 newer = repo.newer(map(repo.lookup, roots))
189 189 for chunk in repo.changegroup(newer):
190 190 sys.stdout.write(chunk)
191 191
192 192 def debugindex(ui, file):
193 193 r = hg.revlog(open, file, "")
194 194 print " rev offset length base linkrev"+\
195 195 " p1 p2 nodeid"
196 196 for i in range(r.count()):
197 197 e = r.index[i]
198 198 print "% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s.." % (
199 199 i, e[0], e[1], e[2], e[3],
200 200 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5]))
201 201
202 202 def debugindexdot(ui, file):
203 203 r = hg.revlog(open, file, "")
204 204 print "digraph G {"
205 205 for i in range(r.count()):
206 206 e = r.index[i]
207 207 print "\t%d -> %d" % (r.rev(e[4]), i)
208 208 if e[5] != hg.nullid:
209 209 print "\t%d -> %d" % (r.rev(e[5]), i)
210 210 print "}"
211 211
212 212 def diff(ui, repo, *files, **opts):
213 213 revs = []
214 214 if opts['rev']:
215 215 revs = map(lambda x: repo.lookup(x), opts['rev'])
216 216
217 217 if len(revs) > 2:
218 218 self.ui.warn("too many revisions to diff\n")
219 219 sys.exit(1)
220 220
221 221 if files:
222 222 files = relpath(repo, files)
223 223 else:
224 224 files = relpath(repo, [""])
225 225
226 226 dodiff(repo, files, *revs)
227 227
228 228 def export(ui, repo, changeset):
229 229 node = repo.lookup(changeset)
230 230 prev, other = repo.changelog.parents(node)
231 231 change = repo.changelog.read(node)
232 232 print "# HG changeset patch"
233 233 print "# User %s" % change[1]
234 234 print "# Node ID %s" % hg.hex(node)
235 235 print "# Parent %s" % hg.hex(prev)
236 236 print
237 237 if other != hg.nullid:
238 238 print "# Parent %s" % hg.hex(other)
239 239 print change[4].rstrip()
240 240 print
241 241
242 242 dodiff(repo, None, prev, node)
243 243
244 244 def forget(ui, repo, file, *files):
245 245 """don't add the specified files on the next commit"""
246 246 repo.forget(relpath(repo, (file,) + files))
247 247
248 248 def heads(ui, repo):
249 249 '''show current repository heads'''
250 250 for n in repo.changelog.heads():
251 251 i = repo.changelog.rev(n)
252 252 changes = repo.changelog.read(n)
253 253 (p1, p2) = repo.changelog.parents(n)
254 254 (h, h1, h2) = map(hg.hex, (n, p1, p2))
255 255 (i1, i2) = map(repo.changelog.rev, (p1, p2))
256 256 print "rev: %4d:%s" % (i, h)
257 257 print "parents: %4d:%s" % (i1, h1)
258 258 if i2: print " %4d:%s" % (i2, h2)
259 259 print "manifest: %4d:%s" % (repo.manifest.rev(changes[0]),
260 260 hg.hex(changes[0]))
261 261 print "user:", changes[1]
262 262 print "date:", time.asctime(
263 263 time.localtime(float(changes[2].split(' ')[0])))
264 264 if ui.verbose: print "files:", " ".join(changes[3])
265 265 print "description:"
266 266 print changes[4]
267 267
268 268 def history(ui, repo):
269 269 """show the changelog history"""
270 270 for i in range(repo.changelog.count()):
271 271 n = repo.changelog.node(i)
272 272 changes = repo.changelog.read(n)
273 273 (p1, p2) = repo.changelog.parents(n)
274 274 (h, h1, h2) = map(hg.hex, (n, p1, p2))
275 275 (i1, i2) = map(repo.changelog.rev, (p1, p2))
276 276 print "rev: %4d:%s" % (i, h)
277 277 print "parents: %4d:%s" % (i1, h1)
278 278 if i2: print " %4d:%s" % (i2, h2)
279 279 print "manifest: %4d:%s" % (repo.manifest.rev(changes[0]),
280 280 hg.hex(changes[0]))
281 281 print "user:", changes[1]
282 282 print "date:", time.asctime(
283 283 time.localtime(float(changes[2].split(' ')[0])))
284 284 if ui.verbose: print "files:", " ".join(changes[3])
285 285 print "description:"
286 286 print changes[4]
287 287
288 288 def patch(ui, repo, patches, opts):
289 289 """import an ordered set of patches"""
290 290 try:
291 291 import psyco
292 292 psyco.full()
293 293 except:
294 294 pass
295 295
296 296 d = opts["base"]
297 297 strip = opts["strip"]
298 298 quiet = opts["quiet"] and "> /dev/null" or ""
299 299
300 300 for patch in patches:
301 301 ui.status("applying %s\n" % patch)
302 302 pf = os.path.join(d, patch)
303 303
304 304 text = ""
305 305 for l in file(pf):
306 306 if l[:4] == "--- ": break
307 307 text += l
308 308
309 309 f = os.popen("lsdiff --strip %d %s" % (strip, pf))
310 310 files = filter(None, map(lambda x: x.rstrip(), f.read().splitlines()))
311 311 f.close()
312 312
313 313 if files:
314 314 if os.system("patch -p%d < %s %s" % (strip, pf, quiet)):
315 315 raise "patch failed!"
316 316 repo.commit(files, text)
317 317
318 318 def init(ui):
319 319 """create a repository"""
320 320 hg.repository(ui, ".", create=1)
321 321
322 322 def log(ui, repo, f):
323 323 f = relpath(repo, [f])[0]
324 324
325 325 r = repo.file(f)
326 326 for i in range(r.count()):
327 327 n = r.node(i)
328 328 (p1, p2) = r.parents(n)
329 329 (h, h1, h2) = map(hg.hex, (n, p1, p2))
330 330 (i1, i2) = map(r.rev, (p1, p2))
331 331 cr = r.linkrev(n)
332 332 cn = hg.hex(repo.changelog.node(cr))
333 333 print "rev: %4d:%s" % (i, h)
334 334 print "changeset: %4d:%s" % (cr, cn)
335 335 print "parents: %4d:%s" % (i1, h1)
336 336 if i2: print " %4d:%s" % (i2, h2)
337 337 changes = repo.changelog.read(repo.changelog.node(cr))
338 338 print "user: %s" % changes[1]
339 339 print "date: %s" % time.asctime(
340 340 time.localtime(float(changes[2].split(' ')[0])))
341 341 print "description:"
342 342 print changes[4].rstrip()
343 343 print
344 344
345 345 def manifest(ui, repo, rev = []):
346 346 n = repo.manifest.tip()
347 347 if rev:
348 348 n = repo.manifest.lookup(rev)
349 349 m = repo.manifest.read(n)
350 350 files = m.keys()
351 351 files.sort()
352 352
353 353 for f in files:
354 354 print hg.hex(m[f]), f
355 355
356 356 def parents(ui, repo, node = None):
357 357 '''show the parents of the current working dir'''
358 358 if node:
359 359 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
360 360 else:
361 361 p = repo.dirstate.parents()
362 362
363 363 for n in p:
364 364 if n != hg.nullid:
365 365 ui.write("%d:%s\n" % (repo.changelog.rev(n), hg.hex(n)))
366 366
367 367 def pull(ui, repo, source):
368 368 """pull changes from the specified source"""
369 369 paths = {}
370 370 try:
371 371 pf = os.path.expanduser("~/.hgpaths")
372 372 for l in file(pf):
373 373 name, path = l.split()
374 374 paths[name] = path
375 375 except IOError:
376 376 pass
377 377
378 378 if source in paths: source = paths[source]
379 379
380 380 other = hg.repository(ui, source)
381 381 cg = repo.getchangegroup(other)
382 382 repo.addchangegroup(cg)
383 383
384 384 def rawcommit(ui, repo, files, rc):
385 385 "raw commit interface"
386 386
387 387 text = rc['text']
388 388 if not text and rc['logfile']:
389 389 try: text = open(rc['logfile']).read()
390 390 except IOError: pass
391 391 if not text and not rc['logfile']:
392 392 print "missing commit text"
393 393 return 1
394 394
395 395 files = relpath(repo, files)
396 396 if rc['files']:
397 397 files += open(rc['files']).read().splitlines()
398 398
399 399 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
400 400
401 401 def recover(ui, repo):
402 402 repo.recover()
403 403
404 404 def remove(ui, repo, file, *files):
405 405 """remove the specified files on the next commit"""
406 406 repo.remove(relpath(repo, (file,) + files))
407 407
408 408 def resolve(ui, repo, node=None):
409 409 '''merge a given node or the current tip into the working dir'''
410 410 if not node:
411 411 node = repo.changelog.tip()
412 412 else:
413 413 node = repo.lookup(node)
414 414 repo.resolve(node)
415 415
416 416 def serve(ui, repo, **opts):
417 417 from mercurial import hgweb
418 418 hgweb.server(repo.root, opts["name"], opts["templates"],
419 419 opts["address"], opts["port"])
420 420
421 421 def status(ui, repo):
422 422 '''show changed files in the working directory
423 423
424 424 C = changed
425 425 A = added
426 426 R = removed
427 427 ? = not tracked'''
428 428
429 429 (c, a, d, u) = repo.diffdir(repo.root)
430 430 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
431 431
432 432 for f in c: print "C", f
433 433 for f in a: print "A", f
434 434 for f in d: print "R", f
435 435 for f in u: print "?", f
436 436
437 437 def tags(ui, repo):
438 438 repo.lookup(0) # prime the cache
439 439 i = repo.tags.items()
440 440 i.sort()
441 441 for k, n in i:
442 442 try:
443 443 r = repo.changelog.rev(n)
444 444 except KeyError:
445 445 r = "?"
446 446 print "%-30s %5d:%s" % (k, repo.changelog.rev(n), hg.hex(n))
447 447
448 448 def tip(ui, repo):
449 449 n = repo.changelog.tip()
450 450 t = repo.changelog.rev(n)
451 451 ui.status("%d:%s\n" % (t, hg.hex(n)))
452 452
453 453 def undo(ui, repo):
454 454 repo.undo()
455 455
456 456 def verify(ui, repo):
457 457 """verify the integrity of the repository"""
458 458 return repo.verify()
459 459
460 460 table = {
461 461 "add": (add, [], "hg add [files]"),
462 462 "addremove": (addremove, [], "hg addremove"),
463 463 "ann|annotate": (annotate,
464 464 [('r', 'revision', '', 'revision'),
465 465 ('u', 'user', None, 'show user'),
466 466 ('n', 'number', None, 'show revision number'),
467 467 ('c', 'changeset', None, 'show changeset')],
468 468 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
469 469 "branch|clone": (branch, [], 'hg branch [path]'),
470 470 "cat|dump": (cat, [], 'hg cat <file> [rev]'),
471 471 "checkout|co": (checkout, [], 'hg checkout [changeset]'),
472 472 "commit|ci": (commit, [], 'hg commit [files]'),
473 473 "debugaddchangegroup": (debugaddchangegroup, [], 'debugaddchangegroup'),
474 474 "debugchangegroup": (debugchangegroup, [], 'debugchangegroup [roots]'),
475 475 "debugindex": (debugindex, [], 'debugindex <file>'),
476 476 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
477 477 "diff": (diff, [('r', 'rev', [], 'revision')],
478 478 'hg diff [-r A] [-r B] [files]'),
479 479 "export": (export, [], "hg export <changeset>"),
480 480 "forget": (forget, [], "hg forget [files]"),
481 481 "heads": (heads, [], 'hg heads'),
482 482 "history": (history, [], 'hg history'),
483 483 "help": (help, [], 'hg help [command]'),
484 484 "init": (init, [], 'hg init'),
485 485 "log": (log, [], 'hg log <file>'),
486 486 "manifest|dumpmanifest": (manifest, [], 'hg manifest [rev]'),
487 487 "parents": (parents, [], 'hg parents [node]'),
488 488 "patch|import": (patch,
489 489 [('p', 'strip', 1, 'path strip'),
490 490 ('b', 'base', "", 'base path'),
491 491 ('q', 'quiet', "", 'silence diff')],
492 492 "hg import [options] patches"),
493 493 "pull|merge": (pull, [], 'hg pull [source]'),
494 494 "rawcommit": (rawcommit,
495 495 [('p', 'parent', [], 'parent'),
496 496 ('d', 'date', "", 'data'),
497 497 ('u', 'user', "", 'user'),
498 498 ('F', 'files', "", 'file list'),
499 499 ('t', 'text', "", 'commit text'),
500 500 ('l', 'logfile', "", 'commit text file')],
501 501 'hg rawcommit [options] [files]'),
502 502 "recover": (recover, [], "hg recover"),
503 503 "remove": (remove, [], "hg remove [files]"),
504 504 "resolve": (resolve, [], 'hg resolve [node]'),
505 505 "serve": (serve, [('p', 'port', 8000, 'listen port'),
506 506 ('a', 'address', '', 'interface address'),
507 507 ('n', 'name', os.getcwd(), 'repository name'),
508 508 ('t', 'templates', "", 'template map')],
509 509 "hg serve [options]"),
510 510 "status": (status, [], 'hg status'),
511 511 "tags": (tags, [], 'hg tags'),
512 512 "tip": (tip, [], 'hg tip'),
513 513 "undo": (undo, [], 'hg undo'),
514 514 "verify": (verify, [], 'hg verify'),
515 515 }
516 516
517 517 norepo = "init branch help debugindex debugindexdot"
518 518
519 519 def find(cmd):
520 520 i = None
521 521 for e in table.keys():
522 522 if re.match(e + "$", cmd):
523 523 return table[e]
524 524
525 525 raise UnknownCommand(cmd)
526 526
527 527 class SignalInterrupt(Exception): pass
528 528
529 529 def catchterm(*args):
530 530 raise SignalInterrupt
531 531
532 532 def run():
533 533 sys.exit(dispatch(sys.argv[1:]))
534 534
535 535 def dispatch(args):
536 536 options = {}
537 537 opts = [('v', 'verbose', None, 'verbose'),
538 538 ('d', 'debug', None, 'debug'),
539 539 ('q', 'quiet', None, 'quiet'),
540 540 ('y', 'noninteractive', None, 'run non-interactively'),
541 541 ]
542 542
543 543 args = fancyopts.fancyopts(args, opts, options,
544 544 'hg [options] <command> [options] [files]')
545 545
546 546 if not args:
547 547 cmd = "help"
548 548 else:
549 549 cmd, args = args[0], args[1:]
550 550
551 551 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
552 552 not options["noninteractive"])
553 553
554 # deal with unfound commands later
555 i = find(cmd)
554 try:
555 i = find(cmd)
556 except UnknownCommand:
557 u.warn("unknown command '%s'\n" % cmd)
558 help(u)
559 sys.exit(1)
556 560
557 561 signal.signal(signal.SIGTERM, catchterm)
558 562
559 563 cmdoptions = {}
560 564 args = fancyopts.fancyopts(args, i[1], cmdoptions, i[2])
561 565
562 566 if cmd not in norepo.split():
563 567 repo = hg.repository(ui = u)
564 568 d = lambda: i[0](u, repo, *args, **cmdoptions)
565 569 else:
566 570 d = lambda: i[0](u, *args, **cmdoptions)
567 571
568 572 try:
569 573 return d()
570 574 except SignalInterrupt:
571 575 u.warn("killed!\n")
572 576 except KeyboardInterrupt:
573 577 u.warn("interrupted!\n")
574 578 except IOError, inst:
575 579 if inst.errno == 32:
576 580 u.warn("broken pipe\n")
577 581 else:
578 582 raise
579 583 except TypeError, inst:
580 584 import traceback
581 585 # was this an argument error?
582 586 tb = traceback.extract_tb(sys.exc_info()[2])
583 587 if len(tb) > 2: # no
584 588 raise
585 589 u.warn("%s: invalid arguments\n" % i[0].__name__)
586 590 u.warn("syntax: %s\n" % i[2])
587 591 sys.exit(-1)
General Comments 0
You need to be logged in to leave comments. Login now