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