##// END OF EJS Templates
check export options for changeset before running...
shaleh@speakeasy.net -
r610:4c02464c default
parent child Browse files
Show More
@@ -1,1170 +1,1173 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
9 9 import fancyopts, ui, hg, util
10 10 from demandload import *
11 11 demandload(globals(), "mdiff time hgweb traceback random signal")
12 12 demandload(globals(), "socket errno version")
13 13
14 14 class UnknownCommand(Exception): pass
15 15
16 16 def filterfiles(filters, files):
17 17 l = [ x for x in files if x in filters ]
18 18
19 19 for t in filters:
20 20 if t and t[-1] != "/": t += "/"
21 21 l += [ x for x in files if x.startswith(t) ]
22 22 return l
23 23
24 24 def relfilter(repo, files):
25 25 if os.getcwd() != repo.root:
26 26 p = os.getcwd()[len(repo.root) + 1: ]
27 27 return filterfiles([util.pconvert(p)], files)
28 28 return files
29 29
30 30 def relpath(repo, args):
31 31 if os.getcwd() != repo.root:
32 32 p = os.getcwd()[len(repo.root) + 1: ]
33 33 return [ util.pconvert(os.path.normpath(os.path.join(p, x)))
34 34 for x in args ]
35 35 return args
36 36
37 37 revrangesep = ':'
38 38
39 39 def revrange(ui, repo, revs = [], revlog = None):
40 40 if revlog is None:
41 41 revlog = repo.changelog
42 42 revcount = revlog.count()
43 43 def fix(val, defval):
44 44 if not val: return defval
45 45 try:
46 46 num = int(val)
47 47 if str(num) != val: raise ValueError
48 48 if num < 0: num += revcount
49 49 if not (0 <= num < revcount):
50 50 raise ValueError
51 51 except ValueError:
52 52 try:
53 53 num = repo.changelog.rev(repo.lookup(val))
54 54 except KeyError:
55 55 try:
56 56 num = revlog.rev(revlog.lookup(val))
57 57 except KeyError:
58 58 ui.warn('abort: invalid revision identifier %s\n' % val)
59 59 sys.exit(1)
60 60 return num
61 61 for spec in revs:
62 62 if spec.find(revrangesep) >= 0:
63 63 start, end = spec.split(revrangesep, 1)
64 64 start = fix(start, 0)
65 65 end = fix(end, revcount - 1)
66 66 if end > start:
67 67 end += 1
68 68 step = 1
69 69 else:
70 70 end -= 1
71 71 step = -1
72 72 for rev in xrange(start, end, step):
73 73 yield str(rev)
74 74 else:
75 75 yield spec
76 76
77 77 def dodiff(fp, ui, repo, files = None, node1 = None, node2 = None):
78 78 def date(c):
79 79 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
80 80
81 81 (c, a, d, u) = repo.changes(node1, node2, files)
82 82 if files:
83 83 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
84 84
85 85 if not c and not a and not d:
86 86 return
87 87
88 88 if node2:
89 89 change = repo.changelog.read(node2)
90 90 mmap2 = repo.manifest.read(change[0])
91 91 def read(f): return repo.file(f).read(mmap2[f])
92 92 date2 = date(change)
93 93 else:
94 94 date2 = time.asctime()
95 95 if not node1:
96 96 node1 = repo.dirstate.parents()[0]
97 97 def read(f): return repo.wfile(f).read()
98 98
99 99 if ui.quiet:
100 100 r = None
101 101 else:
102 102 hexfunc = ui.verbose and hg.hex or hg.short
103 103 r = [hexfunc(node) for node in [node1, node2] if node]
104 104
105 105 change = repo.changelog.read(node1)
106 106 mmap = repo.manifest.read(change[0])
107 107 date1 = date(change)
108 108
109 109 for f in c:
110 110 to = None
111 111 if f in mmap:
112 112 to = repo.file(f).read(mmap[f])
113 113 tn = read(f)
114 114 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
115 115 for f in a:
116 116 to = None
117 117 tn = read(f)
118 118 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
119 119 for f in d:
120 120 to = repo.file(f).read(mmap[f])
121 121 tn = None
122 122 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
123 123
124 124 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None):
125 125 """show a single changeset or file revision"""
126 126 changelog = repo.changelog
127 127 if filelog:
128 128 log = filelog
129 129 filerev = rev
130 130 node = filenode = filelog.node(filerev)
131 131 changerev = filelog.linkrev(filenode)
132 132 changenode = changenode or changelog.node(changerev)
133 133 else:
134 134 log = changelog
135 135 changerev = rev
136 136 if changenode is None:
137 137 changenode = changelog.node(changerev)
138 138 elif not changerev:
139 139 rev = changerev = changelog.rev(changenode)
140 140 node = changenode
141 141
142 142 if ui.quiet:
143 143 ui.write("%d:%s\n" % (rev, hg.hex(node)))
144 144 return
145 145
146 146 changes = changelog.read(changenode)
147 147
148 148 parents = [(log.rev(parent), hg.hex(parent))
149 149 for parent in log.parents(node)
150 150 if ui.debugflag or parent != hg.nullid]
151 151 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
152 152 parents = []
153 153
154 154 if filelog:
155 155 ui.write("revision: %d:%s\n" % (filerev, hg.hex(filenode)))
156 156 for parent in parents:
157 157 ui.write("parent: %d:%s\n" % parent)
158 158 ui.status("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
159 159 else:
160 160 ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
161 161 for tag in repo.nodetags(changenode):
162 162 ui.status("tag: %s\n" % tag)
163 163 for parent in parents:
164 164 ui.write("parent: %d:%s\n" % parent)
165 165 ui.note("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
166 166 hg.hex(changes[0])))
167 167 ui.status("user: %s\n" % changes[1])
168 168 ui.status("date: %s\n" % time.asctime(
169 169 time.localtime(float(changes[2].split(' ')[0]))))
170 170 if ui.debugflag:
171 171 files = repo.changes(changelog.parents(changenode)[0], changenode)
172 172 for key, value in zip(["files:", "files+:", "files-:"], files):
173 173 if value:
174 174 ui.note("%-12s %s\n" % (key, " ".join(value)))
175 175 else:
176 176 ui.note("files: %s\n" % " ".join(changes[3]))
177 177 description = changes[4].strip()
178 178 if description:
179 179 if ui.verbose:
180 180 ui.status("description:\n")
181 181 ui.status(description)
182 182 ui.status("\n\n")
183 183 else:
184 184 ui.status("summary: %s\n" % description.splitlines()[0])
185 185 ui.status("\n")
186 186
187 187 def show_version(ui):
188 188 """output version and copyright information"""
189 189 ui.write("Mercurial version %s\n" % version.get_version())
190 190 ui.status(
191 191 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
192 192 "This is free software; see the source for copying conditions. "
193 193 "There is NO\nwarranty; "
194 194 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
195 195 )
196 196
197 197 def help(ui, cmd=None):
198 198 '''show help for a given command or all commands'''
199 199 if cmd:
200 200 try:
201 201 i = find(cmd)
202 202 ui.write("%s\n\n" % i[2])
203 203
204 204 if i[1]:
205 205 for s, l, d, c in i[1]:
206 206 opt=' '
207 207 if s: opt = opt + '-' + s + ' '
208 208 if l: opt = opt + '--' + l + ' '
209 209 if d: opt = opt + '(' + str(d) + ')'
210 210 ui.write(opt, "\n")
211 211 if c: ui.write(' %s\n' % c)
212 212 ui.write("\n")
213 213
214 214 ui.write(i[0].__doc__, "\n")
215 215 except UnknownCommand:
216 216 ui.warn("hg: unknown command %s\n" % cmd)
217 217 sys.exit(0)
218 218 else:
219 219 if ui.verbose:
220 220 show_version(ui)
221 221 ui.write('\n')
222 222 if ui.verbose:
223 223 ui.write('hg commands:\n\n')
224 224 else:
225 225 ui.write('basic hg commands (use "hg help -v" for more):\n\n')
226 226
227 227 h = {}
228 228 for c, e in table.items():
229 229 f = c.split("|")[0]
230 230 if not ui.verbose and not f.startswith("^"):
231 231 continue
232 232 if not ui.debugflag and f.startswith("debug"):
233 233 continue
234 234 f = f.lstrip("^")
235 235 d = ""
236 236 if e[0].__doc__:
237 237 d = e[0].__doc__.splitlines(0)[0].rstrip()
238 238 h[f] = d
239 239
240 240 fns = h.keys()
241 241 fns.sort()
242 242 m = max(map(len, fns))
243 243 for f in fns:
244 244 ui.write(' %-*s %s\n' % (m, f, h[f]))
245 245
246 246 # Commands start here, listed alphabetically
247 247
248 248 def add(ui, repo, file, *files):
249 249 '''add the specified files on the next commit'''
250 250 repo.add(relpath(repo, (file,) + files))
251 251
252 252 def addremove(ui, repo, *files):
253 253 """add all new files, delete all missing files"""
254 254 if files:
255 255 files = relpath(repo, files)
256 256 d = []
257 257 u = []
258 258 for f in files:
259 259 p = repo.wjoin(f)
260 260 s = repo.dirstate.state(f)
261 261 isfile = os.path.isfile(p)
262 262 if s != 'r' and not isfile:
263 263 d.append(f)
264 264 elif s not in 'nmai' and isfile:
265 265 u.append(f)
266 266 else:
267 267 (c, a, d, u) = repo.changes(None, None)
268 268 repo.add(u)
269 269 repo.remove(d)
270 270
271 271 def annotate(u, repo, file, *files, **ops):
272 272 """show changeset information per file line"""
273 273 def getnode(rev):
274 274 return hg.short(repo.changelog.node(rev))
275 275
276 276 def getname(rev):
277 277 try:
278 278 return bcache[rev]
279 279 except KeyError:
280 280 cl = repo.changelog.read(repo.changelog.node(rev))
281 281 name = cl[1]
282 282 f = name.find('@')
283 283 if f >= 0:
284 284 name = name[:f]
285 285 f = name.find('<')
286 286 if f >= 0:
287 287 name = name[f+1:]
288 288 bcache[rev] = name
289 289 return name
290 290
291 291 bcache = {}
292 292 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
293 293 if not ops['user'] and not ops['changeset']:
294 294 ops['number'] = 1
295 295
296 296 node = repo.dirstate.parents()[0]
297 297 if ops['revision']:
298 298 node = repo.changelog.lookup(ops['revision'])
299 299 change = repo.changelog.read(node)
300 300 mmap = repo.manifest.read(change[0])
301 301 for f in relpath(repo, (file,) + files):
302 302 lines = repo.file(f).annotate(mmap[f])
303 303 pieces = []
304 304
305 305 for o, f in opmap:
306 306 if ops[o]:
307 307 l = [ f(n) for n,t in lines ]
308 308 m = max(map(len, l))
309 309 pieces.append([ "%*s" % (m, x) for x in l])
310 310
311 311 for p,l in zip(zip(*pieces), lines):
312 312 u.write(" ".join(p) + ": " + l[1])
313 313
314 314 def cat(ui, repo, file, rev = []):
315 315 """output the latest or given revision of a file"""
316 316 r = repo.file(relpath(repo, [file])[0])
317 317 n = r.tip()
318 318 if rev: n = r.lookup(rev)
319 319 sys.stdout.write(r.read(n))
320 320
321 321 def clone(ui, source, dest = None, **opts):
322 322 """make a copy of an existing repository"""
323 323 source = ui.expandpath(source)
324 324
325 325 if dest is None:
326 326 dest = os.path.basename(os.path.normpath(source))
327 327
328 328 if os.path.exists(dest):
329 329 ui.warn("abort: destination '%s' already exists\n" % dest)
330 330 return 1
331 331
332 332 class dircleanup:
333 333 def __init__(self, dir):
334 334 self.dir = dir
335 335 os.mkdir(dir)
336 336 def close(self):
337 337 self.dir = None
338 338 def __del__(self):
339 339 if self.dir:
340 340 import shutil
341 341 shutil.rmtree(self.dir, True)
342 342
343 343 d = dircleanup(dest)
344 344
345 345 link = 0
346 346 abspath = source
347 347 if not (source.startswith("http://") or
348 348 source.startswith("hg://") or
349 349 source.startswith("old-http://")):
350 350 abspath = os.path.abspath(source)
351 351 d1 = os.stat(dest).st_dev
352 352 d2 = os.stat(source).st_dev
353 353 if d1 == d2: link = 1
354 354
355 355 if link:
356 356 ui.note("copying by hardlink\n")
357 357 util.system("cp -al '%s'/.hg '%s'/.hg" % (source, dest))
358 358 try:
359 359 os.remove(os.path.join(dest, ".hg", "dirstate"))
360 360 except: pass
361 361
362 362 repo = hg.repository(ui, dest)
363 363
364 364 else:
365 365 repo = hg.repository(ui, dest, create=1)
366 366 other = hg.repository(ui, source)
367 367 fetch = repo.findincoming(other)
368 368 if fetch:
369 369 cg = other.changegroup(fetch)
370 370 repo.addchangegroup(cg)
371 371
372 372 f = repo.opener("hgrc", "w")
373 373 f.write("[paths]\n")
374 374 f.write("default = %s\n" % abspath)
375 375
376 376 if not opts['noupdate']:
377 377 update(ui, repo)
378 378
379 379 d.close()
380 380
381 381 def commit(ui, repo, *files, **opts):
382 382 """commit the specified files or all outstanding changes"""
383 383 text = opts['text']
384 384 if not text and opts['logfile']:
385 385 try: text = open(opts['logfile']).read()
386 386 except IOError: pass
387 387
388 388 if opts['addremove']:
389 389 addremove(ui, repo, *files)
390 390 repo.commit(relpath(repo, files), text, opts['user'], opts['date'])
391 391
392 392 def copy(ui, repo, source, dest):
393 393 """mark a file as copied or renamed for the next commit"""
394 394 return repo.copy(*relpath(repo, (source, dest)))
395 395
396 396 def debugcheckstate(ui, repo):
397 397 """validate the correctness of the current dirstate"""
398 398 parent1, parent2 = repo.dirstate.parents()
399 399 repo.dirstate.read()
400 400 dc = repo.dirstate.map
401 401 keys = dc.keys()
402 402 keys.sort()
403 403 m1n = repo.changelog.read(parent1)[0]
404 404 m2n = repo.changelog.read(parent2)[0]
405 405 m1 = repo.manifest.read(m1n)
406 406 m2 = repo.manifest.read(m2n)
407 407 errors = 0
408 408 for f in dc:
409 409 state = repo.dirstate.state(f)
410 410 if state in "nr" and f not in m1:
411 411 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
412 412 errors += 1
413 413 if state in "a" and f in m1:
414 414 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
415 415 errors += 1
416 416 if state in "m" and f not in m1 and f not in m2:
417 417 ui.warn("%s in state %s, but not in either manifest\n" %
418 418 (f, state))
419 419 errors += 1
420 420 for f in m1:
421 421 state = repo.dirstate.state(f)
422 422 if state not in "nrm":
423 423 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
424 424 errors += 1
425 425 if errors:
426 426 ui.warn(".hg/dirstate inconsistent with current parent's manifest\n")
427 427 sys.exit(1)
428 428
429 429 def debugstate(ui, repo):
430 430 """show the contents of the current dirstate"""
431 431 repo.dirstate.read()
432 432 dc = repo.dirstate.map
433 433 keys = dc.keys()
434 434 keys.sort()
435 435 for file in keys:
436 436 ui.write("%c %s\n" % (dc[file][0], file))
437 437
438 438 def debugindex(ui, file):
439 439 """dump the contents of an index file"""
440 440 r = hg.revlog(hg.opener(""), file, "")
441 441 ui.write(" rev offset length base linkrev" +
442 442 " p1 p2 nodeid\n")
443 443 for i in range(r.count()):
444 444 e = r.index[i]
445 445 ui.write("% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s..\n" % (
446 446 i, e[0], e[1], e[2], e[3],
447 447 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5])))
448 448
449 449 def debugindexdot(ui, file):
450 450 """dump an index DAG as a .dot file"""
451 451 r = hg.revlog(hg.opener(""), file, "")
452 452 ui.write("digraph G {\n")
453 453 for i in range(r.count()):
454 454 e = r.index[i]
455 455 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
456 456 if e[5] != hg.nullid:
457 457 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
458 458 ui.write("}\n")
459 459
460 460 def diff(ui, repo, *files, **opts):
461 461 """diff working directory (or selected files)"""
462 462 revs = []
463 463 if opts['rev']:
464 464 revs = map(lambda x: repo.lookup(x), opts['rev'])
465 465
466 466 if len(revs) > 2:
467 467 ui.warn("too many revisions to diff\n")
468 468 sys.exit(1)
469 469
470 470 if files:
471 471 files = relpath(repo, files)
472 472 else:
473 473 files = relpath(repo, [""])
474 474
475 475 dodiff(sys.stdout, ui, repo, files, *revs)
476 476
477 477 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
478 478 node = repo.lookup(changeset)
479 479 prev, other = repo.changelog.parents(node)
480 480 change = repo.changelog.read(node)
481 481
482 482 def expand(name):
483 483 expansions = {
484 484 '%': lambda: '%',
485 485 'H': lambda: hg.hex(node),
486 486 'N': lambda: str(total),
487 487 'R': lambda: str(repo.changelog.rev(node)),
488 488 'b': lambda: os.path.basename(repo.root),
489 489 'h': lambda: hg.short(node),
490 490 'n': lambda: str(seqno).zfill(len(str(total))),
491 491 'r': lambda: str(repo.changelog.rev(node)).zfill(revwidth),
492 492 }
493 493 newname = []
494 494 namelen = len(name)
495 495 i = 0
496 496 while i < namelen:
497 497 c = name[i]
498 498 if c == '%':
499 499 i += 1
500 500 c = name[i]
501 501 c = expansions[c]()
502 502 newname.append(c)
503 503 i += 1
504 504 return ''.join(newname)
505 505
506 506 if opts['output'] and opts['output'] != '-':
507 507 try:
508 508 fp = open(expand(opts['output']), 'w')
509 509 except KeyError, inst:
510 510 ui.warn("error: invalid format spec '%%%s' in output file name\n" %
511 511 inst.args[0])
512 512 sys.exit(1)
513 513 else:
514 514 fp = sys.stdout
515 515
516 516 fp.write("# HG changeset patch\n")
517 517 fp.write("# User %s\n" % change[1])
518 518 fp.write("# Node ID %s\n" % hg.hex(node))
519 519 fp.write("# Parent %s\n" % hg.hex(prev))
520 520 if other != hg.nullid:
521 521 fp.write("# Parent %s\n" % hg.hex(other))
522 522 fp.write(change[4].rstrip())
523 523 fp.write("\n\n")
524 524
525 525 dodiff(fp, ui, repo, None, prev, node)
526 526
527 527 def export(ui, repo, *changesets, **opts):
528 528 """dump the header and diffs for one or more changesets"""
529 if not changesets:
530 ui.warn("error: export requires at least one changeset\n")
531 sys.exit(1)
529 532 seqno = 0
530 533 revs = list(revrange(ui, repo, changesets))
531 534 total = len(revs)
532 535 revwidth = max(len(revs[0]), len(revs[-1]))
533 536 for cset in revs:
534 537 seqno += 1
535 538 doexport(ui, repo, cset, seqno, total, revwidth, opts)
536 539
537 540 def forget(ui, repo, file, *files):
538 541 """don't add the specified files on the next commit"""
539 542 repo.forget(relpath(repo, (file,) + files))
540 543
541 544 def heads(ui, repo):
542 545 """show current repository heads"""
543 546 for n in repo.changelog.heads():
544 547 show_changeset(ui, repo, changenode=n)
545 548
546 549 def identify(ui, repo):
547 550 """print information about the working copy"""
548 551 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
549 552 if not parents:
550 553 ui.write("unknown\n")
551 554 return
552 555
553 556 hexfunc = ui.verbose and hg.hex or hg.short
554 557 (c, a, d, u) = repo.changes(None, None)
555 558 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
556 559 (c or a or d) and "+" or "")]
557 560
558 561 if not ui.quiet:
559 562 # multiple tags for a single parent separated by '/'
560 563 parenttags = ['/'.join(tags)
561 564 for tags in map(repo.nodetags, parents) if tags]
562 565 # tags for multiple parents separated by ' + '
563 566 output.append(' + '.join(parenttags))
564 567
565 568 ui.write("%s\n" % ' '.join(output))
566 569
567 570 def import_(ui, repo, patch1, *patches, **opts):
568 571 """import an ordered set of patches"""
569 572 try:
570 573 import psyco
571 574 psyco.full()
572 575 except:
573 576 pass
574 577
575 578 patches = (patch1,) + patches
576 579
577 580 d = opts["base"]
578 581 strip = opts["strip"]
579 582
580 583 for patch in patches:
581 584 ui.status("applying %s\n" % patch)
582 585 pf = os.path.join(d, patch)
583 586
584 587 text = ""
585 588 for l in file(pf):
586 589 if l[:4] == "--- ": break
587 590 text += l
588 591
589 592 # parse values that exist when importing the result of an hg export
590 593 hgpatch = user = snippet = None
591 594 ui.debug('text:\n')
592 595 for t in text.splitlines():
593 596 ui.debug(t,'\n')
594 597 if t == '# HG changeset patch' or hgpatch == True:
595 598 hgpatch = True
596 599 if t[:7] == "# User ":
597 600 user = t[7:]
598 601 ui.debug('User: %s\n' % user)
599 602 if t[:2] <> "# " and t.strip() and not snippet: snippet = t
600 603 if snippet: text = snippet + '\n' + text
601 604 ui.debug('text:\n%s\n' % text)
602 605
603 606 # make sure text isn't empty
604 607 if not text: text = "imported patch %s\n" % patch
605 608
606 609 f = os.popen("patch -p%d < %s" % (strip, pf))
607 610 files = []
608 611 for l in f.read().splitlines():
609 612 l.rstrip('\r\n');
610 613 ui.status("%s\n" % l)
611 614 if l[:14] == 'patching file ':
612 615 pf = l[14:]
613 616 if pf not in files:
614 617 files.append(pf)
615 618 patcherr = f.close()
616 619 if patcherr:
617 620 sys.stderr.write("patch failed")
618 621 sys.exit(1)
619 622
620 623 if len(files) > 0:
621 624 addremove(ui, repo, *files)
622 625 repo.commit(files, text, user)
623 626
624 627 def init(ui, source=None):
625 628 """create a new repository in the current directory"""
626 629
627 630 if source:
628 631 ui.warn("no longer supported: use \"hg clone\" instead\n")
629 632 sys.exit(1)
630 633 repo = hg.repository(ui, ".", create=1)
631 634
632 635 def log(ui, repo, f=None, **opts):
633 636 """show the revision history of the repository or a single file"""
634 637 if f:
635 638 filelog = repo.file(relpath(repo, [f])[0])
636 639 log = filelog
637 640 lookup = filelog.lookup
638 641 else:
639 642 filelog = None
640 643 log = repo.changelog
641 644 lookup = repo.lookup
642 645 revlist = []
643 646 revs = [log.rev(lookup(rev)) for rev in opts['rev']]
644 647 while revs:
645 648 if len(revs) == 1:
646 649 revlist.append(revs.pop(0))
647 650 else:
648 651 a = revs.pop(0)
649 652 b = revs.pop(0)
650 653 off = a > b and -1 or 1
651 654 revlist.extend(range(a, b + off, off))
652 655
653 656 for i in revlist or range(log.count() - 1, -1, -1):
654 657 show_changeset(ui, repo, filelog=filelog, rev=i)
655 658
656 659 def manifest(ui, repo, rev = []):
657 660 """output the latest or given revision of the project manifest"""
658 661 n = repo.manifest.tip()
659 662 if rev:
660 663 n = repo.manifest.lookup(rev)
661 664 m = repo.manifest.read(n)
662 665 mf = repo.manifest.readflags(n)
663 666 files = m.keys()
664 667 files.sort()
665 668
666 669 for f in files:
667 670 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
668 671
669 672 def parents(ui, repo, node = None):
670 673 '''show the parents of the current working dir'''
671 674 if node:
672 675 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
673 676 else:
674 677 p = repo.dirstate.parents()
675 678
676 679 for n in p:
677 680 if n != hg.nullid:
678 681 show_changeset(ui, repo, changenode=n)
679 682
680 683 def pull(ui, repo, source="default", **opts):
681 684 """pull changes from the specified source"""
682 685 source = ui.expandpath(source)
683 686
684 687 ui.status('pulling from %s\n' % (source))
685 688
686 689 other = hg.repository(ui, source)
687 690 fetch = repo.findincoming(other)
688 691 if not fetch:
689 692 ui.status("no changes found\n")
690 693 return
691 694
692 695 cg = other.changegroup(fetch)
693 696 r = repo.addchangegroup(cg)
694 697 if cg and not r:
695 698 if opts['update']:
696 699 return update(ui, repo)
697 700 else:
698 701 ui.status("(run 'hg update' to get a working copy)\n")
699 702
700 703 return r
701 704
702 705 def push(ui, repo, dest="default-push"):
703 706 """push changes to the specified destination"""
704 707 dest = ui.expandpath(dest)
705 708
706 709 if not dest.startswith("ssh://"):
707 710 ui.warn("abort: can only push to ssh:// destinations currently\n")
708 711 return 1
709 712
710 713 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', dest)
711 714 if not m:
712 715 ui.warn("abort: couldn't parse destination %s\n" % dest)
713 716 return 1
714 717
715 718 user, host, port, path = map(m.group, (2, 3, 5, 7))
716 719 uhost = user and ("%s@%s" % (user, host)) or host
717 720 port = port and (" -p %s") % port or ""
718 721 path = path or ""
719 722
720 723 sport = random.randrange(30000, 60000)
721 724 cmd = "ssh %s%s -R %d:localhost:%d 'cd %s; hg pull http://localhost:%d/'"
722 725 cmd = cmd % (uhost, port, sport+1, sport, path, sport+1)
723 726
724 727 child = os.fork()
725 728 if not child:
726 729 sys.stdout = file("/dev/null", "w")
727 730 sys.stderr = sys.stdout
728 731 hgweb.server(repo.root, "pull", "", "localhost", sport)
729 732 else:
730 733 ui.status("connecting to %s\n" % host)
731 734 r = os.system(cmd)
732 735 os.kill(child, signal.SIGTERM)
733 736 return r
734 737
735 738 def rawcommit(ui, repo, *flist, **rc):
736 739 "raw commit interface"
737 740
738 741 text = rc['text']
739 742 if not text and rc['logfile']:
740 743 try: text = open(rc['logfile']).read()
741 744 except IOError: pass
742 745 if not text and not rc['logfile']:
743 746 ui.warn("abort: missing commit text\n")
744 747 return 1
745 748
746 749 files = relpath(repo, list(flist))
747 750 if rc['files']:
748 751 files += open(rc['files']).read().splitlines()
749 752
750 753 rc['parent'] = map(repo.lookup, rc['parent'])
751 754
752 755 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
753 756
754 757 def recover(ui, repo):
755 758 """roll back an interrupted transaction"""
756 759 repo.recover()
757 760
758 761 def remove(ui, repo, file, *files):
759 762 """remove the specified files on the next commit"""
760 763 repo.remove(relpath(repo, (file,) + files))
761 764
762 765 def revert(ui, repo, *names, **opts):
763 766 """revert modified files or dirs back to their unmodified states"""
764 767 node = opts['rev'] and repo.lookup(opts['rev']) or \
765 768 repo.dirstate.parents()[0]
766 769 root = os.path.realpath(repo.root)
767 770
768 771 def trimpath(p):
769 772 p = os.path.realpath(p)
770 773 if p.startswith(root):
771 774 rest = p[len(root):]
772 775 if not rest:
773 776 return rest
774 777 if p.startswith(os.sep):
775 778 return rest[1:]
776 779 return p
777 780
778 781 relnames = map(trimpath, names or [os.getcwd()])
779 782 chosen = {}
780 783
781 784 def choose(name):
782 785 def body(name):
783 786 for r in relnames:
784 787 if not name.startswith(r): continue
785 788 rest = name[len(r):]
786 789 if not rest: return r, True
787 790 depth = rest.count(os.sep)
788 791 if not r:
789 792 if depth == 0 or not opts['nonrecursive']: return r, True
790 793 elif rest[0] == os.sep:
791 794 if depth == 1 or not opts['nonrecursive']: return r, True
792 795 return None, False
793 796 relname, ret = body(name)
794 797 if ret:
795 798 chosen[relname] = 1
796 799 return ret
797 800
798 801 r = repo.update(node, False, True, choose, False)
799 802 for n in relnames:
800 803 if n not in chosen:
801 804 ui.warn('error: no matches for %s\n' % n)
802 805 r = 1
803 806 sys.stdout.flush()
804 807 return r
805 808
806 809 def root(ui, repo):
807 810 """print the root (top) of the current working dir"""
808 811 ui.write(repo.root + "\n")
809 812
810 813 def serve(ui, repo, **opts):
811 814 """export the repository via HTTP"""
812 815 def openlog(opt, default):
813 816 if opts[opt] and opts[opt] != '-': return open(opts[opt], 'w')
814 817 else: return default
815 818 httpd = hgweb.create_server(repo.root, opts["name"], opts["templates"],
816 819 opts["address"], opts["port"],
817 820 openlog('accesslog', sys.stdout),
818 821 openlog('errorlog', sys.stderr))
819 822 if ui.verbose:
820 823 addr, port = httpd.socket.getsockname()
821 824 if addr == '0.0.0.0':
822 825 addr = socket.gethostname()
823 826 else:
824 827 try:
825 828 addr = socket.gethostbyaddr(addr)[0]
826 829 except: pass
827 830 if port != 80:
828 831 ui.status('listening at http://%s:%d/\n' % (addr, port))
829 832 else:
830 833 ui.status('listening at http://%s/\n' % addr)
831 834 httpd.serve_forever()
832 835
833 836 def status(ui, repo):
834 837 '''show changed files in the working directory
835 838
836 839 C = changed
837 840 A = added
838 841 R = removed
839 842 ? = not tracked'''
840 843
841 844 (c, a, d, u) = repo.changes(None, None)
842 845 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
843 846
844 847 for f in c: ui.write("C ", f, "\n")
845 848 for f in a: ui.write("A ", f, "\n")
846 849 for f in d: ui.write("R ", f, "\n")
847 850 for f in u: ui.write("? ", f, "\n")
848 851
849 852 def tag(ui, repo, name, rev = None, **opts):
850 853 """add a tag for the current tip or a given revision"""
851 854
852 855 if name == "tip":
853 856 ui.warn("abort: 'tip' is a reserved name!\n")
854 857 return -1
855 858 if rev:
856 859 r = hg.hex(repo.lookup(rev))
857 860 else:
858 861 r = hg.hex(repo.changelog.tip())
859 862
860 863 if name.find(revrangesep) >= 0:
861 864 ui.warn("abort: '%s' cannot be used in a tag name\n" % revrangesep)
862 865 return -1
863 866
864 867 if opts['local']:
865 868 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
866 869 return
867 870
868 871 (c, a, d, u) = repo.changes(None, None)
869 872 for x in (c, a, d, u):
870 873 if ".hgtags" in x:
871 874 ui.warn("abort: working copy of .hgtags is changed!\n")
872 875 ui.status("(please commit .hgtags manually)\n")
873 876 return -1
874 877
875 878 add = 0
876 879 if not os.path.exists(repo.wjoin(".hgtags")): add = 1
877 880 repo.wfile(".hgtags", "a").write("%s %s\n" % (r, name))
878 881 if add: repo.add([".hgtags"])
879 882
880 883 if not opts['text']:
881 884 opts['text'] = "Added tag %s for changeset %s" % (name, r)
882 885
883 886 repo.commit([".hgtags"], opts['text'], opts['user'], opts['date'])
884 887
885 888 def tags(ui, repo):
886 889 """list repository tags"""
887 890
888 891 l = repo.tagslist()
889 892 l.reverse()
890 893 for t, n in l:
891 894 try:
892 895 r = "%5d:%s" % (repo.changelog.rev(n), hg.hex(n))
893 896 except KeyError:
894 897 r = " ?:?"
895 898 ui.write("%-30s %s\n" % (t, r))
896 899
897 900 def tip(ui, repo):
898 901 """show the tip revision"""
899 902 n = repo.changelog.tip()
900 903 show_changeset(ui, repo, changenode=n)
901 904
902 905 def undo(ui, repo):
903 906 """undo the last commit or pull
904 907
905 908 Roll back the last pull or commit transaction on the
906 909 repository, restoring the project to its earlier state.
907 910
908 911 This command should be used with care. There is only one level of
909 912 undo and there is no redo.
910 913
911 914 This command is not intended for use on public repositories. Once
912 915 a change is visible for pull by other users, undoing it locally is
913 916 ineffective.
914 917 """
915 918 repo.undo()
916 919
917 920 def update(ui, repo, node=None, merge=False, clean=False):
918 921 '''update or merge working directory
919 922
920 923 If there are no outstanding changes in the working directory and
921 924 there is a linear relationship between the current version and the
922 925 requested version, the result is the requested version.
923 926
924 927 Otherwise the result is a merge between the contents of the
925 928 current working directory and the requested version. Files that
926 929 changed between either parent are marked as changed for the next
927 930 commit and a commit must be performed before any further updates
928 931 are allowed.
929 932 '''
930 933 node = node and repo.lookup(node) or repo.changelog.tip()
931 934 return repo.update(node, allow=merge, force=clean)
932 935
933 936 def verify(ui, repo):
934 937 """verify the integrity of the repository"""
935 938 return repo.verify()
936 939
937 940 # Command options and aliases are listed here, alphabetically
938 941
939 942 table = {
940 943 "^add": (add, [], "hg add [files]"),
941 944 "addremove": (addremove, [], "hg addremove [files]"),
942 945 "^annotate": (annotate,
943 946 [('r', 'revision', '', 'revision'),
944 947 ('u', 'user', None, 'show user'),
945 948 ('n', 'number', None, 'show revision number'),
946 949 ('c', 'changeset', None, 'show changeset')],
947 950 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
948 951 "cat": (cat, [], 'hg cat <file> [rev]'),
949 952 "^clone": (clone, [('U', 'noupdate', None, 'skip update after cloning')],
950 953 'hg clone [options] <source> [dest]'),
951 954 "^commit|ci": (commit,
952 955 [('t', 'text', "", 'commit text'),
953 956 ('A', 'addremove', None, 'run add/remove during commit'),
954 957 ('l', 'logfile', "", 'commit text file'),
955 958 ('d', 'date', "", 'date code'),
956 959 ('u', 'user', "", 'user')],
957 960 'hg commit [files]'),
958 961 "copy": (copy, [], 'hg copy <source> <dest>'),
959 962 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
960 963 "debugstate": (debugstate, [], 'debugstate'),
961 964 "debugindex": (debugindex, [], 'debugindex <file>'),
962 965 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
963 966 "^diff": (diff, [('r', 'rev', [], 'revision')],
964 967 'hg diff [-r A] [-r B] [files]'),
965 968 "^export": (export, [('o', 'output', "", 'output to file')],
966 969 "hg export [-o file] <changeset> ..."),
967 970 "forget": (forget, [], "hg forget [files]"),
968 971 "heads": (heads, [], 'hg heads'),
969 972 "help": (help, [], 'hg help [command]'),
970 973 "identify|id": (identify, [], 'hg identify'),
971 974 "import|patch": (import_,
972 975 [('p', 'strip', 1, 'path strip'),
973 976 ('b', 'base', "", 'base path')],
974 977 "hg import [options] <patches>"),
975 978 "^init": (init, [], 'hg init'),
976 979 "^log|history": (log,
977 980 [('r', 'rev', [], 'revision')],
978 981 'hg log [-r A] [-r B] [file]'),
979 982 "manifest": (manifest, [], 'hg manifest [rev]'),
980 983 "parents": (parents, [], 'hg parents [node]'),
981 984 "^pull": (pull,
982 985 [('u', 'update', None, 'update working directory')],
983 986 'hg pull [options] [source]'),
984 987 "^push": (push, [], 'hg push <destination>'),
985 988 "rawcommit": (rawcommit,
986 989 [('p', 'parent', [], 'parent'),
987 990 ('d', 'date', "", 'date code'),
988 991 ('u', 'user', "", 'user'),
989 992 ('F', 'files', "", 'file list'),
990 993 ('t', 'text', "", 'commit text'),
991 994 ('l', 'logfile', "", 'commit text file')],
992 995 'hg rawcommit [options] [files]'),
993 996 "recover": (recover, [], "hg recover"),
994 997 "^remove|rm": (remove, [], "hg remove [files]"),
995 998 "^revert": (revert,
996 999 [("n", "nonrecursive", None, "don't recurse into subdirs"),
997 1000 ("r", "rev", "", "revision")],
998 1001 "hg revert [files|dirs]"),
999 1002 "root": (root, [], "hg root"),
1000 1003 "^serve": (serve, [('A', 'accesslog', '', 'access log file'),
1001 1004 ('E', 'errorlog', '', 'error log file'),
1002 1005 ('p', 'port', 8000, 'listen port'),
1003 1006 ('a', 'address', '', 'interface address'),
1004 1007 ('n', 'name', os.getcwd(), 'repository name'),
1005 1008 ('t', 'templates', "", 'template map')],
1006 1009 "hg serve [options]"),
1007 1010 "^status": (status, [], 'hg status'),
1008 1011 "tag": (tag, [('l', 'local', None, 'make the tag local'),
1009 1012 ('t', 'text', "", 'commit text'),
1010 1013 ('d', 'date', "", 'date code'),
1011 1014 ('u', 'user', "", 'user')],
1012 1015 'hg tag [options] <name> [rev]'),
1013 1016 "tags": (tags, [], 'hg tags'),
1014 1017 "tip": (tip, [], 'hg tip'),
1015 1018 "undo": (undo, [], 'hg undo'),
1016 1019 "^update|up|checkout|co":
1017 1020 (update,
1018 1021 [('m', 'merge', None, 'allow merging of conflicts'),
1019 1022 ('C', 'clean', None, 'overwrite locally modified files')],
1020 1023 'hg update [options] [node]'),
1021 1024 "verify": (verify, [], 'hg verify'),
1022 1025 "version": (show_version, [], 'hg version'),
1023 1026 }
1024 1027
1025 1028 globalopts = [('v', 'verbose', None, 'verbose'),
1026 1029 ('', 'debug', None, 'debug'),
1027 1030 ('q', 'quiet', None, 'quiet'),
1028 1031 ('', 'profile', None, 'profile'),
1029 1032 ('R', 'repository', "", 'repository root directory'),
1030 1033 ('', 'traceback', None, 'print traceback on exception'),
1031 1034 ('y', 'noninteractive', None, 'run non-interactively'),
1032 1035 ('', 'version', None, 'output version information and exit'),
1033 1036 ]
1034 1037
1035 1038 norepo = "clone init version help debugindex debugindexdot"
1036 1039
1037 1040 def find(cmd):
1038 1041 for e in table.keys():
1039 1042 if re.match("(%s)$" % e, cmd):
1040 1043 return table[e]
1041 1044
1042 1045 raise UnknownCommand(cmd)
1043 1046
1044 1047 class SignalInterrupt(Exception): pass
1045 1048
1046 1049 def catchterm(*args):
1047 1050 raise SignalInterrupt
1048 1051
1049 1052 def run():
1050 1053 sys.exit(dispatch(sys.argv[1:]))
1051 1054
1052 1055 class ParseError(Exception): pass
1053 1056
1054 1057 def parse(args):
1055 1058 options = {}
1056 1059 cmdoptions = {}
1057 1060
1058 1061 try:
1059 1062 args = fancyopts.fancyopts(args, globalopts, options)
1060 1063 except fancyopts.getopt.GetoptError, inst:
1061 1064 raise ParseError(cmd, inst)
1062 1065
1063 1066 if options["version"]:
1064 1067 return ("version", show_version, [], options, cmdoptions)
1065 1068 elif not args:
1066 1069 return ("help", help, [], options, cmdoptions)
1067 1070 else:
1068 1071 cmd, args = args[0], args[1:]
1069 1072
1070 1073 i = find(cmd)
1071 1074
1072 1075 # combine global options into local
1073 1076 c = list(i[1])
1074 1077 l = len(c)
1075 1078 for o in globalopts:
1076 1079 c.append((o[0], o[1], options[o[1]], o[3]))
1077 1080
1078 1081 try:
1079 1082 args = fancyopts.fancyopts(args, c, cmdoptions)
1080 1083 except fancyopts.getopt.GetoptError, inst:
1081 1084 raise ParseError(cmd, inst)
1082 1085
1083 1086 # separate global options back out
1084 1087 for o in globalopts:
1085 1088 n = o[1]
1086 1089 options[n] = cmdoptions[n]
1087 1090 del cmdoptions[n]
1088 1091
1089 1092 return (cmd, i[0], args, options, cmdoptions)
1090 1093
1091 1094 def dispatch(args):
1092 1095 signal.signal(signal.SIGTERM, catchterm)
1093 1096
1094 1097 try:
1095 1098 cmd, func, args, options, cmdoptions = parse(args)
1096 1099 except ParseError, inst:
1097 1100 u = ui.ui()
1098 1101 if inst.args[0]:
1099 1102 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
1100 1103 help(u, inst.args[0])
1101 1104 else:
1102 1105 u.warn("hg: %s\n" % inst.args[1])
1103 1106 help(u)
1104 1107 sys.exit(-1)
1105 1108 except UnknownCommand, inst:
1106 1109 u = ui.ui()
1107 1110 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1108 1111 help(u)
1109 1112 sys.exit(1)
1110 1113
1111 1114 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
1112 1115 not options["noninteractive"])
1113 1116
1114 1117 try:
1115 1118 try:
1116 1119 if cmd not in norepo.split():
1117 1120 path = options["repository"] or ""
1118 1121 repo = hg.repository(ui=u, path=path)
1119 1122 d = lambda: func(u, repo, *args, **cmdoptions)
1120 1123 else:
1121 1124 d = lambda: func(u, *args, **cmdoptions)
1122 1125
1123 1126 if options['profile']:
1124 1127 import hotshot, hotshot.stats
1125 1128 prof = hotshot.Profile("hg.prof")
1126 1129 r = prof.runcall(d)
1127 1130 prof.close()
1128 1131 stats = hotshot.stats.load("hg.prof")
1129 1132 stats.strip_dirs()
1130 1133 stats.sort_stats('time', 'calls')
1131 1134 stats.print_stats(40)
1132 1135 return r
1133 1136 else:
1134 1137 return d()
1135 1138 except:
1136 1139 if options['traceback']:
1137 1140 traceback.print_exc()
1138 1141 raise
1139 1142 except util.CommandError, inst:
1140 1143 u.warn("abort: %s\n" % inst.args)
1141 1144 except hg.RepoError, inst:
1142 1145 u.warn("abort: ", inst, "!\n")
1143 1146 except SignalInterrupt:
1144 1147 u.warn("killed!\n")
1145 1148 except KeyboardInterrupt:
1146 1149 u.warn("interrupted!\n")
1147 1150 except IOError, inst:
1148 1151 if hasattr(inst, "code"):
1149 1152 u.warn("abort: %s\n" % inst)
1150 1153 elif hasattr(inst, "reason"):
1151 1154 u.warn("abort: error %d: %s\n" % (inst.reason[0], inst.reason[1]))
1152 1155 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
1153 1156 u.warn("broken pipe\n")
1154 1157 else:
1155 1158 raise
1156 1159 except OSError, inst:
1157 1160 if hasattr(inst, "filename"):
1158 1161 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
1159 1162 else:
1160 1163 u.warn("abort: %s\n" % inst.strerror)
1161 1164 except TypeError, inst:
1162 1165 # was this an argument error?
1163 1166 tb = traceback.extract_tb(sys.exc_info()[2])
1164 1167 if len(tb) > 2: # no
1165 1168 raise
1166 1169 u.debug(inst, "\n")
1167 1170 u.warn("%s: invalid arguments\n" % cmd)
1168 1171 help(u, cmd)
1169 1172
1170 1173 sys.exit(-1)
General Comments 0
You need to be logged in to leave comments. Login now