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