##// END OF EJS Templates
Add new bdiff based unidiff generation.
mason@suse.com -
r1637:3b1b44b9 default
parent child Browse files
Show More
@@ -1,2770 +1,2776 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from node import *
10 10 from i18n import gettext as _
11 11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
12 12 demandload(globals(), "fancyopts ui hg util lock revlog")
13 13 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
14 14 demandload(globals(), "errno socket version struct atexit sets bz2")
15 15
16 16 class UnknownCommand(Exception):
17 17 """Exception raised if command is not in the command table."""
18 18 class AmbiguousCommand(Exception):
19 19 """Exception raised if command shortcut matches more than one command."""
20 20
21 21 def filterfiles(filters, files):
22 22 l = [x for x in files if x in filters]
23 23
24 24 for t in filters:
25 25 if t and t[-1] != "/":
26 26 t += "/"
27 27 l += [x for x in files if x.startswith(t)]
28 28 return l
29 29
30 30 def relpath(repo, args):
31 31 cwd = repo.getcwd()
32 32 if cwd:
33 33 return [util.normpath(os.path.join(cwd, x)) for x in args]
34 34 return args
35 35
36 36 def matchpats(repo, pats=[], opts={}, head=''):
37 37 cwd = repo.getcwd()
38 38 if not pats and cwd:
39 39 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
40 40 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
41 41 cwd = ''
42 42 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
43 43 opts.get('exclude'), head)
44 44
45 45 def makewalk(repo, pats, opts, node=None, head=''):
46 46 files, matchfn, anypats = matchpats(repo, pats, opts, head)
47 47 exact = dict(zip(files, files))
48 48 def walk():
49 49 for src, fn in repo.walk(node=node, files=files, match=matchfn):
50 50 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
51 51 return files, matchfn, walk()
52 52
53 53 def walk(repo, pats, opts, node=None, head=''):
54 54 files, matchfn, results = makewalk(repo, pats, opts, node, head)
55 55 for r in results:
56 56 yield r
57 57
58 58 def walkchangerevs(ui, repo, pats, opts):
59 59 '''Iterate over files and the revs they changed in.
60 60
61 61 Callers most commonly need to iterate backwards over the history
62 62 it is interested in. Doing so has awful (quadratic-looking)
63 63 performance, so we use iterators in a "windowed" way.
64 64
65 65 We walk a window of revisions in the desired order. Within the
66 66 window, we first walk forwards to gather data, then in the desired
67 67 order (usually backwards) to display it.
68 68
69 69 This function returns an (iterator, getchange, matchfn) tuple. The
70 70 getchange function returns the changelog entry for a numeric
71 71 revision. The iterator yields 3-tuples. They will be of one of
72 72 the following forms:
73 73
74 74 "window", incrementing, lastrev: stepping through a window,
75 75 positive if walking forwards through revs, last rev in the
76 76 sequence iterated over - use to reset state for the current window
77 77
78 78 "add", rev, fns: out-of-order traversal of the given file names
79 79 fns, which changed during revision rev - use to gather data for
80 80 possible display
81 81
82 82 "iter", rev, None: in-order traversal of the revs earlier iterated
83 83 over with "add" - use to display data'''
84 84
85 85 files, matchfn, anypats = matchpats(repo, pats, opts)
86 86
87 87 if repo.changelog.count() == 0:
88 88 return [], False, matchfn
89 89
90 90 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
91 91 wanted = {}
92 92 slowpath = anypats
93 93 window = 300
94 94 fncache = {}
95 95
96 96 chcache = {}
97 97 def getchange(rev):
98 98 ch = chcache.get(rev)
99 99 if ch is None:
100 100 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
101 101 return ch
102 102
103 103 if not slowpath and not files:
104 104 # No files, no patterns. Display all revs.
105 105 wanted = dict(zip(revs, revs))
106 106 if not slowpath:
107 107 # Only files, no patterns. Check the history of each file.
108 108 def filerevgen(filelog):
109 109 for i in xrange(filelog.count() - 1, -1, -window):
110 110 revs = []
111 111 for j in xrange(max(0, i - window), i + 1):
112 112 revs.append(filelog.linkrev(filelog.node(j)))
113 113 revs.reverse()
114 114 for rev in revs:
115 115 yield rev
116 116
117 117 minrev, maxrev = min(revs), max(revs)
118 118 for file in files:
119 119 filelog = repo.file(file)
120 120 # A zero count may be a directory or deleted file, so
121 121 # try to find matching entries on the slow path.
122 122 if filelog.count() == 0:
123 123 slowpath = True
124 124 break
125 125 for rev in filerevgen(filelog):
126 126 if rev <= maxrev:
127 127 if rev < minrev:
128 128 break
129 129 fncache.setdefault(rev, [])
130 130 fncache[rev].append(file)
131 131 wanted[rev] = 1
132 132 if slowpath:
133 133 # The slow path checks files modified in every changeset.
134 134 def changerevgen():
135 135 for i in xrange(repo.changelog.count() - 1, -1, -window):
136 136 for j in xrange(max(0, i - window), i + 1):
137 137 yield j, getchange(j)[3]
138 138
139 139 for rev, changefiles in changerevgen():
140 140 matches = filter(matchfn, changefiles)
141 141 if matches:
142 142 fncache[rev] = matches
143 143 wanted[rev] = 1
144 144
145 145 def iterate():
146 146 for i in xrange(0, len(revs), window):
147 147 yield 'window', revs[0] < revs[-1], revs[-1]
148 148 nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
149 149 if rev in wanted]
150 150 srevs = list(nrevs)
151 151 srevs.sort()
152 152 for rev in srevs:
153 153 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
154 154 yield 'add', rev, fns
155 155 for rev in nrevs:
156 156 yield 'iter', rev, None
157 157 return iterate(), getchange, matchfn
158 158
159 159 revrangesep = ':'
160 160
161 161 def revrange(ui, repo, revs, revlog=None):
162 162 """Yield revision as strings from a list of revision specifications."""
163 163 if revlog is None:
164 164 revlog = repo.changelog
165 165 revcount = revlog.count()
166 166 def fix(val, defval):
167 167 if not val:
168 168 return defval
169 169 try:
170 170 num = int(val)
171 171 if str(num) != val:
172 172 raise ValueError
173 173 if num < 0:
174 174 num += revcount
175 175 if num < 0:
176 176 num = 0
177 177 elif num >= revcount:
178 178 raise ValueError
179 179 except ValueError:
180 180 try:
181 181 num = repo.changelog.rev(repo.lookup(val))
182 182 except KeyError:
183 183 try:
184 184 num = revlog.rev(revlog.lookup(val))
185 185 except KeyError:
186 186 raise util.Abort(_('invalid revision identifier %s'), val)
187 187 return num
188 188 seen = {}
189 189 for spec in revs:
190 190 if spec.find(revrangesep) >= 0:
191 191 start, end = spec.split(revrangesep, 1)
192 192 start = fix(start, 0)
193 193 end = fix(end, revcount - 1)
194 194 step = start > end and -1 or 1
195 195 for rev in xrange(start, end+step, step):
196 196 if rev in seen:
197 197 continue
198 198 seen[rev] = 1
199 199 yield str(rev)
200 200 else:
201 201 rev = fix(spec, None)
202 202 if rev in seen:
203 203 continue
204 204 seen[rev] = 1
205 205 yield str(rev)
206 206
207 207 def make_filename(repo, r, pat, node=None,
208 208 total=None, seqno=None, revwidth=None, pathname=None):
209 209 node_expander = {
210 210 'H': lambda: hex(node),
211 211 'R': lambda: str(r.rev(node)),
212 212 'h': lambda: short(node),
213 213 }
214 214 expander = {
215 215 '%': lambda: '%',
216 216 'b': lambda: os.path.basename(repo.root),
217 217 }
218 218
219 219 try:
220 220 if node:
221 221 expander.update(node_expander)
222 222 if node and revwidth is not None:
223 223 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
224 224 if total is not None:
225 225 expander['N'] = lambda: str(total)
226 226 if seqno is not None:
227 227 expander['n'] = lambda: str(seqno)
228 228 if total is not None and seqno is not None:
229 229 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
230 230 if pathname is not None:
231 231 expander['s'] = lambda: os.path.basename(pathname)
232 232 expander['d'] = lambda: os.path.dirname(pathname) or '.'
233 233 expander['p'] = lambda: pathname
234 234
235 235 newname = []
236 236 patlen = len(pat)
237 237 i = 0
238 238 while i < patlen:
239 239 c = pat[i]
240 240 if c == '%':
241 241 i += 1
242 242 c = pat[i]
243 243 c = expander[c]()
244 244 newname.append(c)
245 245 i += 1
246 246 return ''.join(newname)
247 247 except KeyError, inst:
248 248 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
249 249 inst.args[0])
250 250
251 251 def make_file(repo, r, pat, node=None,
252 252 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
253 253 if not pat or pat == '-':
254 254 return 'w' in mode and sys.stdout or sys.stdin
255 255 if hasattr(pat, 'write') and 'w' in mode:
256 256 return pat
257 257 if hasattr(pat, 'read') and 'r' in mode:
258 258 return pat
259 259 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
260 260 pathname),
261 261 mode)
262 262
263 263 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
264 264 changes=None, text=False):
265 265 if not changes:
266 266 changes = repo.changes(node1, node2, files, match=match)
267 267 modified, added, removed, deleted, unknown = changes
268 268 if files:
269 269 modified, added, removed = map(lambda x: filterfiles(files, x),
270 270 (modified, added, removed))
271 271
272 272 if not modified and not added and not removed:
273 273 return
274 274
275 275 if node2:
276 276 change = repo.changelog.read(node2)
277 277 mmap2 = repo.manifest.read(change[0])
278 278 date2 = util.datestr(change[2])
279 279 def read(f):
280 280 return repo.file(f).read(mmap2[f])
281 281 else:
282 282 date2 = util.datestr()
283 283 if not node1:
284 284 node1 = repo.dirstate.parents()[0]
285 285 def read(f):
286 286 return repo.wfile(f).read()
287 287
288 288 if ui.quiet:
289 289 r = None
290 290 else:
291 291 hexfunc = ui.verbose and hex or short
292 292 r = [hexfunc(node) for node in [node1, node2] if node]
293 293
294 294 change = repo.changelog.read(node1)
295 295 mmap = repo.manifest.read(change[0])
296 296 date1 = util.datestr(change[2])
297 297
298 diffopts = ui.diffopts()
299 showfunc = diffopts['showfunc']
300 ignorews = diffopts['ignorews']
298 301 for f in modified:
299 302 to = None
300 303 if f in mmap:
301 304 to = repo.file(f).read(mmap[f])
302 305 tn = read(f)
303 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
306 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
307 showfunc=showfunc, ignorews=ignorews))
304 308 for f in added:
305 309 to = None
306 310 tn = read(f)
307 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
311 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
312 showfunc=showfunc, ignorews=ignorews))
308 313 for f in removed:
309 314 to = repo.file(f).read(mmap[f])
310 315 tn = None
311 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
316 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
317 showfunc=showfunc, ignorews=ignorews))
312 318
313 319 def trimuser(ui, name, rev, revcache):
314 320 """trim the name of the user who committed a change"""
315 321 user = revcache.get(rev)
316 322 if user is None:
317 323 user = revcache[rev] = ui.shortuser(name)
318 324 return user
319 325
320 326 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
321 327 """show a single changeset or file revision"""
322 328 log = repo.changelog
323 329 if changenode is None:
324 330 changenode = log.node(rev)
325 331 elif not rev:
326 332 rev = log.rev(changenode)
327 333
328 334 if ui.quiet:
329 335 ui.write("%d:%s\n" % (rev, short(changenode)))
330 336 return
331 337
332 338 changes = log.read(changenode)
333 339 date = util.datestr(changes[2])
334 340
335 341 parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
336 342 for p in log.parents(changenode)
337 343 if ui.debugflag or p != nullid]
338 344 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
339 345 parents = []
340 346
341 347 if ui.verbose:
342 348 ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
343 349 else:
344 350 ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
345 351
346 352 for tag in repo.nodetags(changenode):
347 353 ui.status(_("tag: %s\n") % tag)
348 354 for parent in parents:
349 355 ui.write(_("parent: %d:%s\n") % parent)
350 356
351 357 if brinfo and changenode in brinfo:
352 358 br = brinfo[changenode]
353 359 ui.write(_("branch: %s\n") % " ".join(br))
354 360
355 361 ui.debug(_("manifest: %d:%s\n") % (repo.manifest.rev(changes[0]),
356 362 hex(changes[0])))
357 363 ui.status(_("user: %s\n") % changes[1])
358 364 ui.status(_("date: %s\n") % date)
359 365
360 366 if ui.debugflag:
361 367 files = repo.changes(log.parents(changenode)[0], changenode)
362 368 for key, value in zip([_("files:"), _("files+:"), _("files-:")], files):
363 369 if value:
364 370 ui.note("%-12s %s\n" % (key, " ".join(value)))
365 371 else:
366 372 ui.note(_("files: %s\n") % " ".join(changes[3]))
367 373
368 374 description = changes[4].strip()
369 375 if description:
370 376 if ui.verbose:
371 377 ui.status(_("description:\n"))
372 378 ui.status(description)
373 379 ui.status("\n\n")
374 380 else:
375 381 ui.status(_("summary: %s\n") % description.splitlines()[0])
376 382 ui.status("\n")
377 383
378 384 def show_version(ui):
379 385 """output version and copyright information"""
380 386 ui.write(_("Mercurial Distributed SCM (version %s)\n")
381 387 % version.get_version())
382 388 ui.status(_(
383 389 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
384 390 "This is free software; see the source for copying conditions. "
385 391 "There is NO\nwarranty; "
386 392 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
387 393 ))
388 394
389 395 def help_(ui, cmd=None, with_version=False):
390 396 """show help for a given command or all commands"""
391 397 option_lists = []
392 398 if cmd and cmd != 'shortlist':
393 399 if with_version:
394 400 show_version(ui)
395 401 ui.write('\n')
396 402 aliases, i = find(cmd)
397 403 # synopsis
398 404 ui.write("%s\n\n" % i[2])
399 405
400 406 # description
401 407 doc = i[0].__doc__
402 408 if ui.quiet:
403 409 doc = doc.splitlines(0)[0]
404 410 ui.write("%s\n" % doc.rstrip())
405 411
406 412 if not ui.quiet:
407 413 # aliases
408 414 if len(aliases) > 1:
409 415 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
410 416
411 417 # options
412 418 if i[1]:
413 419 option_lists.append(("options", i[1]))
414 420
415 421 else:
416 422 # program name
417 423 if ui.verbose or with_version:
418 424 show_version(ui)
419 425 else:
420 426 ui.status(_("Mercurial Distributed SCM\n"))
421 427 ui.status('\n')
422 428
423 429 # list of commands
424 430 if cmd == "shortlist":
425 431 ui.status(_('basic commands (use "hg help" '
426 432 'for the full list or option "-v" for details):\n\n'))
427 433 elif ui.verbose:
428 434 ui.status(_('list of commands:\n\n'))
429 435 else:
430 436 ui.status(_('list of commands (use "hg help -v" '
431 437 'to show aliases and global options):\n\n'))
432 438
433 439 h = {}
434 440 cmds = {}
435 441 for c, e in table.items():
436 442 f = c.split("|")[0]
437 443 if cmd == "shortlist" and not f.startswith("^"):
438 444 continue
439 445 f = f.lstrip("^")
440 446 if not ui.debugflag and f.startswith("debug"):
441 447 continue
442 448 d = ""
443 449 if e[0].__doc__:
444 450 d = e[0].__doc__.splitlines(0)[0].rstrip()
445 451 h[f] = d
446 452 cmds[f] = c.lstrip("^")
447 453
448 454 fns = h.keys()
449 455 fns.sort()
450 456 m = max(map(len, fns))
451 457 for f in fns:
452 458 if ui.verbose:
453 459 commands = cmds[f].replace("|",", ")
454 460 ui.write(" %s:\n %s\n"%(commands, h[f]))
455 461 else:
456 462 ui.write(' %-*s %s\n' % (m, f, h[f]))
457 463
458 464 # global options
459 465 if ui.verbose:
460 466 option_lists.append(("global options", globalopts))
461 467
462 468 # list all option lists
463 469 opt_output = []
464 470 for title, options in option_lists:
465 471 opt_output.append(("\n%s:\n" % title, None))
466 472 for shortopt, longopt, default, desc in options:
467 473 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
468 474 longopt and " --%s" % longopt),
469 475 "%s%s" % (desc,
470 476 default
471 477 and _(" (default: %s)") % default
472 478 or "")))
473 479
474 480 if opt_output:
475 481 opts_len = max([len(line[0]) for line in opt_output if line[1]])
476 482 for first, second in opt_output:
477 483 if second:
478 484 ui.write(" %-*s %s\n" % (opts_len, first, second))
479 485 else:
480 486 ui.write("%s\n" % first)
481 487
482 488 # Commands start here, listed alphabetically
483 489
484 490 def add(ui, repo, *pats, **opts):
485 491 """add the specified files on the next commit
486 492
487 493 Schedule files to be version controlled and added to the repository.
488 494
489 495 The files will be added to the repository at the next commit.
490 496
491 497 If no names are given, add all files in the repository.
492 498 """
493 499
494 500 names = []
495 501 for src, abs, rel, exact in walk(repo, pats, opts):
496 502 if exact:
497 503 if ui.verbose:
498 504 ui.status(_('adding %s\n') % rel)
499 505 names.append(abs)
500 506 elif repo.dirstate.state(abs) == '?':
501 507 ui.status(_('adding %s\n') % rel)
502 508 names.append(abs)
503 509 repo.add(names)
504 510
505 511 def addremove(ui, repo, *pats, **opts):
506 512 """add all new files, delete all missing files
507 513
508 514 Add all new files and remove all missing files from the repository.
509 515
510 516 New files are ignored if they match any of the patterns in .hgignore. As
511 517 with add, these changes take effect at the next commit.
512 518 """
513 519 add, remove = [], []
514 520 for src, abs, rel, exact in walk(repo, pats, opts):
515 521 if src == 'f' and repo.dirstate.state(abs) == '?':
516 522 add.append(abs)
517 523 if ui.verbose or not exact:
518 524 ui.status(_('adding %s\n') % ((pats and rel) or abs))
519 525 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
520 526 remove.append(abs)
521 527 if ui.verbose or not exact:
522 528 ui.status(_('removing %s\n') % ((pats and rel) or abs))
523 529 repo.add(add)
524 530 repo.remove(remove)
525 531
526 532 def annotate(ui, repo, *pats, **opts):
527 533 """show changeset information per file line
528 534
529 535 List changes in files, showing the revision id responsible for each line
530 536
531 537 This command is useful to discover who did a change or when a change took
532 538 place.
533 539
534 540 Without the -a option, annotate will avoid processing files it
535 541 detects as binary. With -a, annotate will generate an annotation
536 542 anyway, probably with undesirable results.
537 543 """
538 544 def getnode(rev):
539 545 return short(repo.changelog.node(rev))
540 546
541 547 ucache = {}
542 548 def getname(rev):
543 549 cl = repo.changelog.read(repo.changelog.node(rev))
544 550 return trimuser(ui, cl[1], rev, ucache)
545 551
546 552 dcache = {}
547 553 def getdate(rev):
548 554 datestr = dcache.get(rev)
549 555 if datestr is None:
550 556 cl = repo.changelog.read(repo.changelog.node(rev))
551 557 datestr = dcache[rev] = util.datestr(cl[2])
552 558 return datestr
553 559
554 560 if not pats:
555 561 raise util.Abort(_('at least one file name or pattern required'))
556 562
557 563 opmap = [['user', getname], ['number', str], ['changeset', getnode],
558 564 ['date', getdate]]
559 565 if not opts['user'] and not opts['changeset'] and not opts['date']:
560 566 opts['number'] = 1
561 567
562 568 if opts['rev']:
563 569 node = repo.changelog.lookup(opts['rev'])
564 570 else:
565 571 node = repo.dirstate.parents()[0]
566 572 change = repo.changelog.read(node)
567 573 mmap = repo.manifest.read(change[0])
568 574
569 575 for src, abs, rel, exact in walk(repo, pats, opts):
570 576 if abs not in mmap:
571 577 ui.warn(_("warning: %s is not in the repository!\n") %
572 578 ((pats and rel) or abs))
573 579 continue
574 580
575 581 f = repo.file(abs)
576 582 if not opts['text'] and util.binary(f.read(mmap[abs])):
577 583 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
578 584 continue
579 585
580 586 lines = f.annotate(mmap[abs])
581 587 pieces = []
582 588
583 589 for o, f in opmap:
584 590 if opts[o]:
585 591 l = [f(n) for n, dummy in lines]
586 592 if l:
587 593 m = max(map(len, l))
588 594 pieces.append(["%*s" % (m, x) for x in l])
589 595
590 596 if pieces:
591 597 for p, l in zip(zip(*pieces), lines):
592 598 ui.write("%s: %s" % (" ".join(p), l[1]))
593 599
594 600 def bundle(ui, repo, fname, dest="default-push", **opts):
595 601 """create a changegroup file
596 602
597 603 Generate a compressed changegroup file collecting all changesets
598 604 not found in the other repository.
599 605
600 606 This file can then be transferred using conventional means and
601 607 applied to another repository with the unbundle command. This is
602 608 useful when native push and pull are not available or when
603 609 exporting an entire repository is undesirable. The standard file
604 610 extension is ".hg".
605 611
606 612 Unlike import/export, this exactly preserves all changeset
607 613 contents including permissions, rename data, and revision history.
608 614 """
609 615 f = open(fname, "wb")
610 616 dest = ui.expandpath(dest, repo.root)
611 617 other = hg.repository(ui, dest)
612 618 o = repo.findoutgoing(other)
613 619 cg = repo.changegroup(o)
614 620
615 621 try:
616 622 f.write("HG10")
617 623 z = bz2.BZ2Compressor(9)
618 624 while 1:
619 625 chunk = cg.read(4096)
620 626 if not chunk:
621 627 break
622 628 f.write(z.compress(chunk))
623 629 f.write(z.flush())
624 630 except:
625 631 os.unlink(fname)
626 632 raise
627 633
628 634 def cat(ui, repo, file1, *pats, **opts):
629 635 """output the latest or given revisions of files
630 636
631 637 Print the specified files as they were at the given revision.
632 638 If no revision is given then the tip is used.
633 639
634 640 Output may be to a file, in which case the name of the file is
635 641 given using a format string. The formatting rules are the same as
636 642 for the export command, with the following additions:
637 643
638 644 %s basename of file being printed
639 645 %d dirname of file being printed, or '.' if in repo root
640 646 %p root-relative path name of file being printed
641 647 """
642 648 mf = {}
643 649 rev = opts['rev']
644 650 if rev:
645 651 node = repo.lookup(rev)
646 652 else:
647 653 node = repo.changelog.tip()
648 654 change = repo.changelog.read(node)
649 655 mf = repo.manifest.read(change[0])
650 656 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
651 657 r = repo.file(abs)
652 658 n = mf[abs]
653 659 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
654 660 fp.write(r.read(n))
655 661
656 662 def clone(ui, source, dest=None, **opts):
657 663 """make a copy of an existing repository
658 664
659 665 Create a copy of an existing repository in a new directory.
660 666
661 667 If no destination directory name is specified, it defaults to the
662 668 basename of the source.
663 669
664 670 The location of the source is added to the new repository's
665 671 .hg/hgrc file, as the default to be used for future pulls.
666 672
667 673 For efficiency, hardlinks are used for cloning whenever the source
668 674 and destination are on the same filesystem. Some filesystems,
669 675 such as AFS, implement hardlinking incorrectly, but do not report
670 676 errors. In these cases, use the --pull option to avoid
671 677 hardlinking.
672 678 """
673 679 if dest is None:
674 680 dest = os.path.basename(os.path.normpath(source))
675 681
676 682 if os.path.exists(dest):
677 683 raise util.Abort(_("destination '%s' already exists"), dest)
678 684
679 685 dest = os.path.realpath(dest)
680 686
681 687 class Dircleanup(object):
682 688 def __init__(self, dir_):
683 689 self.rmtree = shutil.rmtree
684 690 self.dir_ = dir_
685 691 os.mkdir(dir_)
686 692 def close(self):
687 693 self.dir_ = None
688 694 def __del__(self):
689 695 if self.dir_:
690 696 self.rmtree(self.dir_, True)
691 697
692 698 if opts['ssh']:
693 699 ui.setconfig("ui", "ssh", opts['ssh'])
694 700 if opts['remotecmd']:
695 701 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
696 702
697 703 if not os.path.exists(source):
698 704 source = ui.expandpath(source)
699 705
700 706 d = Dircleanup(dest)
701 707 abspath = source
702 708 other = hg.repository(ui, source)
703 709
704 710 copy = False
705 711 if other.dev() != -1:
706 712 abspath = os.path.abspath(source)
707 713 if not opts['pull'] and not opts['rev']:
708 714 copy = True
709 715
710 716 if copy:
711 717 try:
712 718 # we use a lock here because if we race with commit, we
713 719 # can end up with extra data in the cloned revlogs that's
714 720 # not pointed to by changesets, thus causing verify to
715 721 # fail
716 722 l1 = lock.lock(os.path.join(source, ".hg", "lock"))
717 723 except OSError:
718 724 copy = False
719 725
720 726 if copy:
721 727 # we lock here to avoid premature writing to the target
722 728 os.mkdir(os.path.join(dest, ".hg"))
723 729 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
724 730
725 731 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
726 732 for f in files.split():
727 733 src = os.path.join(source, ".hg", f)
728 734 dst = os.path.join(dest, ".hg", f)
729 735 try:
730 736 util.copyfiles(src, dst)
731 737 except OSError, inst:
732 738 if inst.errno != errno.ENOENT:
733 739 raise
734 740
735 741 repo = hg.repository(ui, dest)
736 742
737 743 else:
738 744 revs = None
739 745 if opts['rev']:
740 746 if not other.local():
741 747 error = _("clone -r not supported yet for remote repositories.")
742 748 raise util.Abort(error)
743 749 else:
744 750 revs = [other.lookup(rev) for rev in opts['rev']]
745 751 repo = hg.repository(ui, dest, create=1)
746 752 repo.pull(other, heads = revs)
747 753
748 754 f = repo.opener("hgrc", "w", text=True)
749 755 f.write("[paths]\n")
750 756 f.write("default = %s\n" % abspath)
751 757 f.close()
752 758
753 759 if not opts['noupdate']:
754 760 update(ui, repo)
755 761
756 762 d.close()
757 763
758 764 def commit(ui, repo, *pats, **opts):
759 765 """commit the specified files or all outstanding changes
760 766
761 767 Commit changes to the given files into the repository.
762 768
763 769 If a list of files is omitted, all changes reported by "hg status"
764 770 will be commited.
765 771
766 772 The HGEDITOR or EDITOR environment variables are used to start an
767 773 editor to add a commit comment.
768 774 """
769 775 message = opts['message']
770 776 logfile = opts['logfile']
771 777
772 778 if message and logfile:
773 779 raise util.Abort(_('options --message and --logfile are mutually '
774 780 'exclusive'))
775 781 if not message and logfile:
776 782 try:
777 783 if logfile == '-':
778 784 message = sys.stdin.read()
779 785 else:
780 786 message = open(logfile).read()
781 787 except IOError, inst:
782 788 raise util.Abort(_("can't read commit message '%s': %s") %
783 789 (logfile, inst.strerror))
784 790
785 791 if opts['addremove']:
786 792 addremove(ui, repo, *pats, **opts)
787 793 fns, match, anypats = matchpats(repo, pats, opts)
788 794 if pats:
789 795 modified, added, removed, deleted, unknown = (
790 796 repo.changes(files=fns, match=match))
791 797 files = modified + added + removed
792 798 else:
793 799 files = []
794 800 try:
795 801 repo.commit(files, message, opts['user'], opts['date'], match)
796 802 except ValueError, inst:
797 803 raise util.Abort(str(inst))
798 804
799 805 def docopy(ui, repo, pats, opts):
800 806 cwd = repo.getcwd()
801 807 errors = 0
802 808 copied = []
803 809 targets = {}
804 810
805 811 def okaytocopy(abs, rel, exact):
806 812 reasons = {'?': _('is not managed'),
807 813 'a': _('has been marked for add'),
808 814 'r': _('has been marked for remove')}
809 815 reason = reasons.get(repo.dirstate.state(abs))
810 816 if reason:
811 817 if exact:
812 818 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
813 819 else:
814 820 return True
815 821
816 822 def copy(abssrc, relsrc, target, exact):
817 823 abstarget = util.canonpath(repo.root, cwd, target)
818 824 reltarget = util.pathto(cwd, abstarget)
819 825 prevsrc = targets.get(abstarget)
820 826 if prevsrc is not None:
821 827 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
822 828 (reltarget, abssrc, prevsrc))
823 829 return
824 830 if (not opts['after'] and os.path.exists(reltarget) or
825 831 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
826 832 if not opts['force']:
827 833 ui.warn(_('%s: not overwriting - file exists\n') %
828 834 reltarget)
829 835 return
830 836 if not opts['after']:
831 837 os.unlink(reltarget)
832 838 if opts['after']:
833 839 if not os.path.exists(reltarget):
834 840 return
835 841 else:
836 842 targetdir = os.path.dirname(reltarget) or '.'
837 843 if not os.path.isdir(targetdir):
838 844 os.makedirs(targetdir)
839 845 try:
840 846 shutil.copyfile(relsrc, reltarget)
841 847 shutil.copymode(relsrc, reltarget)
842 848 except shutil.Error, inst:
843 849 raise util.Abort(str(inst))
844 850 except IOError, inst:
845 851 if inst.errno == errno.ENOENT:
846 852 ui.warn(_('%s: deleted in working copy\n') % relsrc)
847 853 else:
848 854 ui.warn(_('%s: cannot copy - %s\n') %
849 855 (relsrc, inst.strerror))
850 856 errors += 1
851 857 return
852 858 if ui.verbose or not exact:
853 859 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
854 860 targets[abstarget] = abssrc
855 861 repo.copy(abssrc, abstarget)
856 862 copied.append((abssrc, relsrc, exact))
857 863
858 864 def targetpathfn(pat, dest, srcs):
859 865 if os.path.isdir(pat):
860 866 abspfx = util.canonpath(repo.root, cwd, pat)
861 867 if destdirexists:
862 868 striplen = len(os.path.split(abspfx)[0])
863 869 else:
864 870 striplen = len(abspfx)
865 871 if striplen:
866 872 striplen += len(os.sep)
867 873 res = lambda p: os.path.join(dest, p[striplen:])
868 874 elif destdirexists:
869 875 res = lambda p: os.path.join(dest, os.path.basename(p))
870 876 else:
871 877 res = lambda p: dest
872 878 return res
873 879
874 880 def targetpathafterfn(pat, dest, srcs):
875 881 if util.patkind(pat, None)[0]:
876 882 # a mercurial pattern
877 883 res = lambda p: os.path.join(dest, os.path.basename(p))
878 884 else:
879 885 abspfx = util.canonpath(repo.root, cwd, pat)
880 886 if len(abspfx) < len(srcs[0][0]):
881 887 # A directory. Either the target path contains the last
882 888 # component of the source path or it does not.
883 889 def evalpath(striplen):
884 890 score = 0
885 891 for s in srcs:
886 892 t = os.path.join(dest, s[0][striplen:])
887 893 if os.path.exists(t):
888 894 score += 1
889 895 return score
890 896
891 897 striplen = len(abspfx)
892 898 if striplen:
893 899 striplen += len(os.sep)
894 900 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
895 901 score = evalpath(striplen)
896 902 striplen1 = len(os.path.split(abspfx)[0])
897 903 if striplen1:
898 904 striplen1 += len(os.sep)
899 905 if evalpath(striplen1) > score:
900 906 striplen = striplen1
901 907 res = lambda p: os.path.join(dest, p[striplen:])
902 908 else:
903 909 # a file
904 910 if destdirexists:
905 911 res = lambda p: os.path.join(dest, os.path.basename(p))
906 912 else:
907 913 res = lambda p: dest
908 914 return res
909 915
910 916
911 917 pats = list(pats)
912 918 if not pats:
913 919 raise util.Abort(_('no source or destination specified'))
914 920 if len(pats) == 1:
915 921 raise util.Abort(_('no destination specified'))
916 922 dest = pats.pop()
917 923 destdirexists = os.path.isdir(dest)
918 924 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
919 925 raise util.Abort(_('with multiple sources, destination must be an '
920 926 'existing directory'))
921 927 if opts['after']:
922 928 tfn = targetpathafterfn
923 929 else:
924 930 tfn = targetpathfn
925 931 copylist = []
926 932 for pat in pats:
927 933 srcs = []
928 934 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
929 935 if okaytocopy(abssrc, relsrc, exact):
930 936 srcs.append((abssrc, relsrc, exact))
931 937 if not srcs:
932 938 continue
933 939 copylist.append((tfn(pat, dest, srcs), srcs))
934 940 if not copylist:
935 941 raise util.Abort(_('no files to copy'))
936 942
937 943 for targetpath, srcs in copylist:
938 944 for abssrc, relsrc, exact in srcs:
939 945 copy(abssrc, relsrc, targetpath(abssrc), exact)
940 946
941 947 if errors:
942 948 ui.warn(_('(consider using --after)\n'))
943 949 return errors, copied
944 950
945 951 def copy(ui, repo, *pats, **opts):
946 952 """mark files as copied for the next commit
947 953
948 954 Mark dest as having copies of source files. If dest is a
949 955 directory, copies are put in that directory. If dest is a file,
950 956 there can only be one source.
951 957
952 958 By default, this command copies the contents of files as they
953 959 stand in the working directory. If invoked with --after, the
954 960 operation is recorded, but no copying is performed.
955 961
956 962 This command takes effect in the next commit.
957 963
958 964 NOTE: This command should be treated as experimental. While it
959 965 should properly record copied files, this information is not yet
960 966 fully used by merge, nor fully reported by log.
961 967 """
962 968 errs, copied = docopy(ui, repo, pats, opts)
963 969 return errs
964 970
965 971 def debugancestor(ui, index, rev1, rev2):
966 972 """find the ancestor revision of two revisions in a given index"""
967 973 r = revlog.revlog(util.opener(os.getcwd()), index, "")
968 974 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
969 975 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
970 976
971 977 def debugcheckstate(ui, repo):
972 978 """validate the correctness of the current dirstate"""
973 979 parent1, parent2 = repo.dirstate.parents()
974 980 repo.dirstate.read()
975 981 dc = repo.dirstate.map
976 982 keys = dc.keys()
977 983 keys.sort()
978 984 m1n = repo.changelog.read(parent1)[0]
979 985 m2n = repo.changelog.read(parent2)[0]
980 986 m1 = repo.manifest.read(m1n)
981 987 m2 = repo.manifest.read(m2n)
982 988 errors = 0
983 989 for f in dc:
984 990 state = repo.dirstate.state(f)
985 991 if state in "nr" and f not in m1:
986 992 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
987 993 errors += 1
988 994 if state in "a" and f in m1:
989 995 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
990 996 errors += 1
991 997 if state in "m" and f not in m1 and f not in m2:
992 998 ui.warn(_("%s in state %s, but not in either manifest\n") %
993 999 (f, state))
994 1000 errors += 1
995 1001 for f in m1:
996 1002 state = repo.dirstate.state(f)
997 1003 if state not in "nrm":
998 1004 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
999 1005 errors += 1
1000 1006 if errors:
1001 1007 error = _(".hg/dirstate inconsistent with current parent's manifest")
1002 1008 raise util.Abort(error)
1003 1009
1004 1010 def debugconfig(ui):
1005 1011 """show combined config settings from all hgrc files"""
1006 1012 try:
1007 1013 repo = hg.repository(ui)
1008 1014 except hg.RepoError:
1009 1015 pass
1010 1016 for section, name, value in ui.walkconfig():
1011 1017 ui.write('%s.%s=%s\n' % (section, name, value))
1012 1018
1013 1019 def debugsetparents(ui, repo, rev1, rev2=None):
1014 1020 """manually set the parents of the current working directory
1015 1021
1016 1022 This is useful for writing repository conversion tools, but should
1017 1023 be used with care.
1018 1024 """
1019 1025
1020 1026 if not rev2:
1021 1027 rev2 = hex(nullid)
1022 1028
1023 1029 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1024 1030
1025 1031 def debugstate(ui, repo):
1026 1032 """show the contents of the current dirstate"""
1027 1033 repo.dirstate.read()
1028 1034 dc = repo.dirstate.map
1029 1035 keys = dc.keys()
1030 1036 keys.sort()
1031 1037 for file_ in keys:
1032 1038 ui.write("%c %3o %10d %s %s\n"
1033 1039 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1034 1040 time.strftime("%x %X",
1035 1041 time.localtime(dc[file_][3])), file_))
1036 1042 for f in repo.dirstate.copies:
1037 1043 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1038 1044
1039 1045 def debugdata(ui, file_, rev):
1040 1046 """dump the contents of an data file revision"""
1041 1047 r = revlog.revlog(util.opener(os.getcwd()), file_[:-2] + ".i", file_)
1042 1048 try:
1043 1049 ui.write(r.revision(r.lookup(rev)))
1044 1050 except KeyError:
1045 1051 raise util.Abort(_('invalid revision identifier %s'), rev)
1046 1052
1047 1053 def debugindex(ui, file_):
1048 1054 """dump the contents of an index file"""
1049 1055 r = revlog.revlog(util.opener(os.getcwd()), file_, "")
1050 1056 ui.write(" rev offset length base linkrev" +
1051 1057 " nodeid p1 p2\n")
1052 1058 for i in range(r.count()):
1053 1059 e = r.index[i]
1054 1060 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1055 1061 i, e[0], e[1], e[2], e[3],
1056 1062 short(e[6]), short(e[4]), short(e[5])))
1057 1063
1058 1064 def debugindexdot(ui, file_):
1059 1065 """dump an index DAG as a .dot file"""
1060 1066 r = revlog.revlog(util.opener(os.getcwd()), file_, "")
1061 1067 ui.write("digraph G {\n")
1062 1068 for i in range(r.count()):
1063 1069 e = r.index[i]
1064 1070 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
1065 1071 if e[5] != nullid:
1066 1072 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
1067 1073 ui.write("}\n")
1068 1074
1069 1075 def debugrename(ui, repo, file, rev=None):
1070 1076 """dump rename information"""
1071 1077 r = repo.file(relpath(repo, [file])[0])
1072 1078 if rev:
1073 1079 try:
1074 1080 # assume all revision numbers are for changesets
1075 1081 n = repo.lookup(rev)
1076 1082 change = repo.changelog.read(n)
1077 1083 m = repo.manifest.read(change[0])
1078 1084 n = m[relpath(repo, [file])[0]]
1079 1085 except (hg.RepoError, KeyError):
1080 1086 n = r.lookup(rev)
1081 1087 else:
1082 1088 n = r.tip()
1083 1089 m = r.renamed(n)
1084 1090 if m:
1085 1091 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1086 1092 else:
1087 1093 ui.write(_("not renamed\n"))
1088 1094
1089 1095 def debugwalk(ui, repo, *pats, **opts):
1090 1096 """show how files match on given patterns"""
1091 1097 items = list(walk(repo, pats, opts))
1092 1098 if not items:
1093 1099 return
1094 1100 fmt = '%%s %%-%ds %%-%ds %%s' % (
1095 1101 max([len(abs) for (src, abs, rel, exact) in items]),
1096 1102 max([len(rel) for (src, abs, rel, exact) in items]))
1097 1103 for src, abs, rel, exact in items:
1098 1104 line = fmt % (src, abs, rel, exact and 'exact' or '')
1099 1105 ui.write("%s\n" % line.rstrip())
1100 1106
1101 1107 def diff(ui, repo, *pats, **opts):
1102 1108 """diff repository (or selected files)
1103 1109
1104 1110 Show differences between revisions for the specified files.
1105 1111
1106 1112 Differences between files are shown using the unified diff format.
1107 1113
1108 1114 When two revision arguments are given, then changes are shown
1109 1115 between those revisions. If only one revision is specified then
1110 1116 that revision is compared to the working directory, and, when no
1111 1117 revisions are specified, the working directory files are compared
1112 1118 to its parent.
1113 1119
1114 1120 Without the -a option, diff will avoid generating diffs of files
1115 1121 it detects as binary. With -a, diff will generate a diff anyway,
1116 1122 probably with undesirable results.
1117 1123 """
1118 1124 node1, node2 = None, None
1119 1125 revs = [repo.lookup(x) for x in opts['rev']]
1120 1126
1121 1127 if len(revs) > 0:
1122 1128 node1 = revs[0]
1123 1129 if len(revs) > 1:
1124 1130 node2 = revs[1]
1125 1131 if len(revs) > 2:
1126 1132 raise util.Abort(_("too many revisions to diff"))
1127 1133
1128 1134 fns, matchfn, anypats = matchpats(repo, pats, opts)
1129 1135
1130 1136 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1131 1137 text=opts['text'])
1132 1138
1133 1139 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1134 1140 node = repo.lookup(changeset)
1135 1141 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1136 1142 if opts['switch_parent']:
1137 1143 parents.reverse()
1138 1144 prev = (parents and parents[0]) or nullid
1139 1145 change = repo.changelog.read(node)
1140 1146
1141 1147 fp = make_file(repo, repo.changelog, opts['output'],
1142 1148 node=node, total=total, seqno=seqno,
1143 1149 revwidth=revwidth)
1144 1150 if fp != sys.stdout:
1145 1151 ui.note("%s\n" % fp.name)
1146 1152
1147 1153 fp.write("# HG changeset patch\n")
1148 1154 fp.write("# User %s\n" % change[1])
1149 1155 fp.write("# Node ID %s\n" % hex(node))
1150 1156 fp.write("# Parent %s\n" % hex(prev))
1151 1157 if len(parents) > 1:
1152 1158 fp.write("# Parent %s\n" % hex(parents[1]))
1153 1159 fp.write(change[4].rstrip())
1154 1160 fp.write("\n\n")
1155 1161
1156 1162 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1157 1163 if fp != sys.stdout:
1158 1164 fp.close()
1159 1165
1160 1166 def export(ui, repo, *changesets, **opts):
1161 1167 """dump the header and diffs for one or more changesets
1162 1168
1163 1169 Print the changeset header and diffs for one or more revisions.
1164 1170
1165 1171 The information shown in the changeset header is: author,
1166 1172 changeset hash, parent and commit comment.
1167 1173
1168 1174 Output may be to a file, in which case the name of the file is
1169 1175 given using a format string. The formatting rules are as follows:
1170 1176
1171 1177 %% literal "%" character
1172 1178 %H changeset hash (40 bytes of hexadecimal)
1173 1179 %N number of patches being generated
1174 1180 %R changeset revision number
1175 1181 %b basename of the exporting repository
1176 1182 %h short-form changeset hash (12 bytes of hexadecimal)
1177 1183 %n zero-padded sequence number, starting at 1
1178 1184 %r zero-padded changeset revision number
1179 1185
1180 1186 Without the -a option, export will avoid generating diffs of files
1181 1187 it detects as binary. With -a, export will generate a diff anyway,
1182 1188 probably with undesirable results.
1183 1189
1184 1190 With the --switch-parent option, the diff will be against the second
1185 1191 parent. It can be useful to review a merge.
1186 1192 """
1187 1193 if not changesets:
1188 1194 raise util.Abort(_("export requires at least one changeset"))
1189 1195 seqno = 0
1190 1196 revs = list(revrange(ui, repo, changesets))
1191 1197 total = len(revs)
1192 1198 revwidth = max(map(len, revs))
1193 1199 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1194 1200 ui.note(msg)
1195 1201 for cset in revs:
1196 1202 seqno += 1
1197 1203 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1198 1204
1199 1205 def forget(ui, repo, *pats, **opts):
1200 1206 """don't add the specified files on the next commit
1201 1207
1202 1208 Undo an 'hg add' scheduled for the next commit.
1203 1209 """
1204 1210 forget = []
1205 1211 for src, abs, rel, exact in walk(repo, pats, opts):
1206 1212 if repo.dirstate.state(abs) == 'a':
1207 1213 forget.append(abs)
1208 1214 if ui.verbose or not exact:
1209 1215 ui.status(_('forgetting %s\n') % ((pats and rel) or abs))
1210 1216 repo.forget(forget)
1211 1217
1212 1218 def grep(ui, repo, pattern, *pats, **opts):
1213 1219 """search for a pattern in specified files and revisions
1214 1220
1215 1221 Search revisions of files for a regular expression.
1216 1222
1217 1223 This command behaves differently than Unix grep. It only accepts
1218 1224 Python/Perl regexps. It searches repository history, not the
1219 1225 working directory. It always prints the revision number in which
1220 1226 a match appears.
1221 1227
1222 1228 By default, grep only prints output for the first revision of a
1223 1229 file in which it finds a match. To get it to print every revision
1224 1230 that contains a change in match status ("-" for a match that
1225 1231 becomes a non-match, or "+" for a non-match that becomes a match),
1226 1232 use the --all flag.
1227 1233 """
1228 1234 reflags = 0
1229 1235 if opts['ignore_case']:
1230 1236 reflags |= re.I
1231 1237 regexp = re.compile(pattern, reflags)
1232 1238 sep, eol = ':', '\n'
1233 1239 if opts['print0']:
1234 1240 sep = eol = '\0'
1235 1241
1236 1242 fcache = {}
1237 1243 def getfile(fn):
1238 1244 if fn not in fcache:
1239 1245 fcache[fn] = repo.file(fn)
1240 1246 return fcache[fn]
1241 1247
1242 1248 def matchlines(body):
1243 1249 begin = 0
1244 1250 linenum = 0
1245 1251 while True:
1246 1252 match = regexp.search(body, begin)
1247 1253 if not match:
1248 1254 break
1249 1255 mstart, mend = match.span()
1250 1256 linenum += body.count('\n', begin, mstart) + 1
1251 1257 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1252 1258 lend = body.find('\n', mend)
1253 1259 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1254 1260 begin = lend + 1
1255 1261
1256 1262 class linestate(object):
1257 1263 def __init__(self, line, linenum, colstart, colend):
1258 1264 self.line = line
1259 1265 self.linenum = linenum
1260 1266 self.colstart = colstart
1261 1267 self.colend = colend
1262 1268 def __eq__(self, other):
1263 1269 return self.line == other.line
1264 1270 def __hash__(self):
1265 1271 return hash(self.line)
1266 1272
1267 1273 matches = {}
1268 1274 def grepbody(fn, rev, body):
1269 1275 matches[rev].setdefault(fn, {})
1270 1276 m = matches[rev][fn]
1271 1277 for lnum, cstart, cend, line in matchlines(body):
1272 1278 s = linestate(line, lnum, cstart, cend)
1273 1279 m[s] = s
1274 1280
1275 1281 prev = {}
1276 1282 ucache = {}
1277 1283 def display(fn, rev, states, prevstates):
1278 1284 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1279 1285 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1280 1286 counts = {'-': 0, '+': 0}
1281 1287 filerevmatches = {}
1282 1288 for l in diff:
1283 1289 if incrementing or not opts['all']:
1284 1290 change = ((l in prevstates) and '-') or '+'
1285 1291 r = rev
1286 1292 else:
1287 1293 change = ((l in states) and '-') or '+'
1288 1294 r = prev[fn]
1289 1295 cols = [fn, str(rev)]
1290 1296 if opts['line_number']:
1291 1297 cols.append(str(l.linenum))
1292 1298 if opts['all']:
1293 1299 cols.append(change)
1294 1300 if opts['user']:
1295 1301 cols.append(trimuser(ui, getchange(rev)[1], rev,
1296 1302 ucache))
1297 1303 if opts['files_with_matches']:
1298 1304 c = (fn, rev)
1299 1305 if c in filerevmatches:
1300 1306 continue
1301 1307 filerevmatches[c] = 1
1302 1308 else:
1303 1309 cols.append(l.line)
1304 1310 ui.write(sep.join(cols), eol)
1305 1311 counts[change] += 1
1306 1312 return counts['+'], counts['-']
1307 1313
1308 1314 fstate = {}
1309 1315 skip = {}
1310 1316 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1311 1317 count = 0
1312 1318 incrementing = False
1313 1319 for st, rev, fns in changeiter:
1314 1320 if st == 'window':
1315 1321 incrementing = rev
1316 1322 matches.clear()
1317 1323 elif st == 'add':
1318 1324 change = repo.changelog.read(repo.lookup(str(rev)))
1319 1325 mf = repo.manifest.read(change[0])
1320 1326 matches[rev] = {}
1321 1327 for fn in fns:
1322 1328 if fn in skip:
1323 1329 continue
1324 1330 fstate.setdefault(fn, {})
1325 1331 try:
1326 1332 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1327 1333 except KeyError:
1328 1334 pass
1329 1335 elif st == 'iter':
1330 1336 states = matches[rev].items()
1331 1337 states.sort()
1332 1338 for fn, m in states:
1333 1339 if fn in skip:
1334 1340 continue
1335 1341 if incrementing or not opts['all'] or fstate[fn]:
1336 1342 pos, neg = display(fn, rev, m, fstate[fn])
1337 1343 count += pos + neg
1338 1344 if pos and not opts['all']:
1339 1345 skip[fn] = True
1340 1346 fstate[fn] = m
1341 1347 prev[fn] = rev
1342 1348
1343 1349 if not incrementing:
1344 1350 fstate = fstate.items()
1345 1351 fstate.sort()
1346 1352 for fn, state in fstate:
1347 1353 if fn in skip:
1348 1354 continue
1349 1355 display(fn, rev, {}, state)
1350 1356 return (count == 0 and 1) or 0
1351 1357
1352 1358 def heads(ui, repo, **opts):
1353 1359 """show current repository heads
1354 1360
1355 1361 Show all repository head changesets.
1356 1362
1357 1363 Repository "heads" are changesets that don't have children
1358 1364 changesets. They are where development generally takes place and
1359 1365 are the usual targets for update and merge operations.
1360 1366 """
1361 1367 if opts['rev']:
1362 1368 heads = repo.heads(repo.lookup(opts['rev']))
1363 1369 else:
1364 1370 heads = repo.heads()
1365 1371 br = None
1366 1372 if opts['branches']:
1367 1373 br = repo.branchlookup(heads)
1368 1374 for n in heads:
1369 1375 show_changeset(ui, repo, changenode=n, brinfo=br)
1370 1376
1371 1377 def identify(ui, repo):
1372 1378 """print information about the working copy
1373 1379
1374 1380 Print a short summary of the current state of the repo.
1375 1381
1376 1382 This summary identifies the repository state using one or two parent
1377 1383 hash identifiers, followed by a "+" if there are uncommitted changes
1378 1384 in the working directory, followed by a list of tags for this revision.
1379 1385 """
1380 1386 parents = [p for p in repo.dirstate.parents() if p != nullid]
1381 1387 if not parents:
1382 1388 ui.write(_("unknown\n"))
1383 1389 return
1384 1390
1385 1391 hexfunc = ui.verbose and hex or short
1386 1392 modified, added, removed, deleted, unknown = repo.changes()
1387 1393 output = ["%s%s" %
1388 1394 ('+'.join([hexfunc(parent) for parent in parents]),
1389 1395 (modified or added or removed or deleted) and "+" or "")]
1390 1396
1391 1397 if not ui.quiet:
1392 1398 # multiple tags for a single parent separated by '/'
1393 1399 parenttags = ['/'.join(tags)
1394 1400 for tags in map(repo.nodetags, parents) if tags]
1395 1401 # tags for multiple parents separated by ' + '
1396 1402 if parenttags:
1397 1403 output.append(' + '.join(parenttags))
1398 1404
1399 1405 ui.write("%s\n" % ' '.join(output))
1400 1406
1401 1407 def import_(ui, repo, patch1, *patches, **opts):
1402 1408 """import an ordered set of patches
1403 1409
1404 1410 Import a list of patches and commit them individually.
1405 1411
1406 1412 If there are outstanding changes in the working directory, import
1407 1413 will abort unless given the -f flag.
1408 1414
1409 1415 If a patch looks like a mail message (its first line starts with
1410 1416 "From " or looks like an RFC822 header), it will not be applied
1411 1417 unless the -f option is used. The importer neither parses nor
1412 1418 discards mail headers, so use -f only to override the "mailness"
1413 1419 safety check, not to import a real mail message.
1414 1420 """
1415 1421 patches = (patch1,) + patches
1416 1422
1417 1423 if not opts['force']:
1418 1424 modified, added, removed, deleted, unknown = repo.changes()
1419 1425 if modified or added or removed or deleted:
1420 1426 raise util.Abort(_("outstanding uncommitted changes"))
1421 1427
1422 1428 d = opts["base"]
1423 1429 strip = opts["strip"]
1424 1430
1425 1431 mailre = re.compile(r'(?:From |[\w-]+:)')
1426 1432
1427 1433 # attempt to detect the start of a patch
1428 1434 # (this heuristic is borrowed from quilt)
1429 1435 diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1430 1436 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1431 1437 '(---|\*\*\*)[ \t])')
1432 1438
1433 1439 for patch in patches:
1434 1440 ui.status(_("applying %s\n") % patch)
1435 1441 pf = os.path.join(d, patch)
1436 1442
1437 1443 message = []
1438 1444 user = None
1439 1445 hgpatch = False
1440 1446 for line in file(pf):
1441 1447 line = line.rstrip()
1442 1448 if (not message and not hgpatch and
1443 1449 mailre.match(line) and not opts['force']):
1444 1450 if len(line) > 35:
1445 1451 line = line[:32] + '...'
1446 1452 raise util.Abort(_('first line looks like a '
1447 1453 'mail header: ') + line)
1448 1454 if diffre.match(line):
1449 1455 break
1450 1456 elif hgpatch:
1451 1457 # parse values when importing the result of an hg export
1452 1458 if line.startswith("# User "):
1453 1459 user = line[7:]
1454 1460 ui.debug(_('User: %s\n') % user)
1455 1461 elif not line.startswith("# ") and line:
1456 1462 message.append(line)
1457 1463 hgpatch = False
1458 1464 elif line == '# HG changeset patch':
1459 1465 hgpatch = True
1460 1466 message = [] # We may have collected garbage
1461 1467 else:
1462 1468 message.append(line)
1463 1469
1464 1470 # make sure message isn't empty
1465 1471 if not message:
1466 1472 message = _("imported patch %s\n") % patch
1467 1473 else:
1468 1474 message = "%s\n" % '\n'.join(message)
1469 1475 ui.debug(_('message:\n%s\n') % message)
1470 1476
1471 1477 files = util.patch(strip, pf, ui)
1472 1478
1473 1479 if len(files) > 0:
1474 1480 addremove(ui, repo, *files)
1475 1481 repo.commit(files, message, user)
1476 1482
1477 1483 def incoming(ui, repo, source="default", **opts):
1478 1484 """show new changesets found in source
1479 1485
1480 1486 Show new changesets found in the specified repo or the default
1481 1487 pull repo. These are the changesets that would be pulled if a pull
1482 1488 was requested.
1483 1489
1484 1490 Currently only local repositories are supported.
1485 1491 """
1486 1492 source = ui.expandpath(source, repo.root)
1487 1493 other = hg.repository(ui, source)
1488 1494 if not other.local():
1489 1495 raise util.Abort(_("incoming doesn't work for remote repositories yet"))
1490 1496 o = repo.findincoming(other)
1491 1497 if not o:
1492 1498 return
1493 1499 o = other.changelog.nodesbetween(o)[0]
1494 1500 if opts['newest_first']:
1495 1501 o.reverse()
1496 1502 for n in o:
1497 1503 parents = [p for p in other.changelog.parents(n) if p != nullid]
1498 1504 if opts['no_merges'] and len(parents) == 2:
1499 1505 continue
1500 1506 show_changeset(ui, other, changenode=n)
1501 1507 if opts['patch']:
1502 1508 prev = (parents and parents[0]) or nullid
1503 1509 dodiff(ui, ui, other, prev, n)
1504 1510 ui.write("\n")
1505 1511
1506 1512 def init(ui, dest="."):
1507 1513 """create a new repository in the given directory
1508 1514
1509 1515 Initialize a new repository in the given directory. If the given
1510 1516 directory does not exist, it is created.
1511 1517
1512 1518 If no directory is given, the current directory is used.
1513 1519 """
1514 1520 if not os.path.exists(dest):
1515 1521 os.mkdir(dest)
1516 1522 hg.repository(ui, dest, create=1)
1517 1523
1518 1524 def locate(ui, repo, *pats, **opts):
1519 1525 """locate files matching specific patterns
1520 1526
1521 1527 Print all files under Mercurial control whose names match the
1522 1528 given patterns.
1523 1529
1524 1530 This command searches the current directory and its
1525 1531 subdirectories. To search an entire repository, move to the root
1526 1532 of the repository.
1527 1533
1528 1534 If no patterns are given to match, this command prints all file
1529 1535 names.
1530 1536
1531 1537 If you want to feed the output of this command into the "xargs"
1532 1538 command, use the "-0" option to both this command and "xargs".
1533 1539 This will avoid the problem of "xargs" treating single filenames
1534 1540 that contain white space as multiple filenames.
1535 1541 """
1536 1542 end = opts['print0'] and '\0' or '\n'
1537 1543 rev = opts['rev']
1538 1544 if rev:
1539 1545 node = repo.lookup(rev)
1540 1546 else:
1541 1547 node = None
1542 1548
1543 1549 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
1544 1550 head='(?:.*/|)'):
1545 1551 if not node and repo.dirstate.state(abs) == '?':
1546 1552 continue
1547 1553 if opts['fullpath']:
1548 1554 ui.write(os.path.join(repo.root, abs), end)
1549 1555 else:
1550 1556 ui.write(((pats and rel) or abs), end)
1551 1557
1552 1558 def log(ui, repo, *pats, **opts):
1553 1559 """show revision history of entire repository or files
1554 1560
1555 1561 Print the revision history of the specified files or the entire project.
1556 1562
1557 1563 By default this command outputs: changeset id and hash, tags,
1558 1564 non-trivial parents, user, date and time, and a summary for each
1559 1565 commit. When the -v/--verbose switch is used, the list of changed
1560 1566 files and full commit message is shown.
1561 1567 """
1562 1568 class dui(object):
1563 1569 # Implement and delegate some ui protocol. Save hunks of
1564 1570 # output for later display in the desired order.
1565 1571 def __init__(self, ui):
1566 1572 self.ui = ui
1567 1573 self.hunk = {}
1568 1574 def bump(self, rev):
1569 1575 self.rev = rev
1570 1576 self.hunk[rev] = []
1571 1577 def note(self, *args):
1572 1578 if self.verbose:
1573 1579 self.write(*args)
1574 1580 def status(self, *args):
1575 1581 if not self.quiet:
1576 1582 self.write(*args)
1577 1583 def write(self, *args):
1578 1584 self.hunk[self.rev].append(args)
1579 1585 def debug(self, *args):
1580 1586 if self.debugflag:
1581 1587 self.write(*args)
1582 1588 def __getattr__(self, key):
1583 1589 return getattr(self.ui, key)
1584 1590 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1585 1591 for st, rev, fns in changeiter:
1586 1592 if st == 'window':
1587 1593 du = dui(ui)
1588 1594 elif st == 'add':
1589 1595 du.bump(rev)
1590 1596 changenode = repo.changelog.node(rev)
1591 1597 parents = [p for p in repo.changelog.parents(changenode)
1592 1598 if p != nullid]
1593 1599 if opts['no_merges'] and len(parents) == 2:
1594 1600 continue
1595 1601 if opts['only_merges'] and len(parents) != 2:
1596 1602 continue
1597 1603
1598 1604 br = None
1599 1605 if opts['keyword']:
1600 1606 changes = getchange(rev)
1601 1607 miss = 0
1602 1608 for k in [kw.lower() for kw in opts['keyword']]:
1603 1609 if not (k in changes[1].lower() or
1604 1610 k in changes[4].lower() or
1605 1611 k in " ".join(changes[3][:20]).lower()):
1606 1612 miss = 1
1607 1613 break
1608 1614 if miss:
1609 1615 continue
1610 1616
1611 1617 if opts['branch']:
1612 1618 br = repo.branchlookup([repo.changelog.node(rev)])
1613 1619
1614 1620 show_changeset(du, repo, rev, brinfo=br)
1615 1621 if opts['patch']:
1616 1622 prev = (parents and parents[0]) or nullid
1617 1623 dodiff(du, du, repo, prev, changenode, match=matchfn)
1618 1624 du.write("\n\n")
1619 1625 elif st == 'iter':
1620 1626 for args in du.hunk[rev]:
1621 1627 ui.write(*args)
1622 1628
1623 1629 def manifest(ui, repo, rev=None):
1624 1630 """output the latest or given revision of the project manifest
1625 1631
1626 1632 Print a list of version controlled files for the given revision.
1627 1633
1628 1634 The manifest is the list of files being version controlled. If no revision
1629 1635 is given then the tip is used.
1630 1636 """
1631 1637 if rev:
1632 1638 try:
1633 1639 # assume all revision numbers are for changesets
1634 1640 n = repo.lookup(rev)
1635 1641 change = repo.changelog.read(n)
1636 1642 n = change[0]
1637 1643 except hg.RepoError:
1638 1644 n = repo.manifest.lookup(rev)
1639 1645 else:
1640 1646 n = repo.manifest.tip()
1641 1647 m = repo.manifest.read(n)
1642 1648 mf = repo.manifest.readflags(n)
1643 1649 files = m.keys()
1644 1650 files.sort()
1645 1651
1646 1652 for f in files:
1647 1653 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1648 1654
1649 1655 def outgoing(ui, repo, dest="default-push", **opts):
1650 1656 """show changesets not found in destination
1651 1657
1652 1658 Show changesets not found in the specified destination repo or the
1653 1659 default push repo. These are the changesets that would be pushed
1654 1660 if a push was requested.
1655 1661 """
1656 1662 dest = ui.expandpath(dest, repo.root)
1657 1663 other = hg.repository(ui, dest)
1658 1664 o = repo.findoutgoing(other)
1659 1665 o = repo.changelog.nodesbetween(o)[0]
1660 1666 if opts['newest_first']:
1661 1667 o.reverse()
1662 1668 for n in o:
1663 1669 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1664 1670 if opts['no_merges'] and len(parents) == 2:
1665 1671 continue
1666 1672 show_changeset(ui, repo, changenode=n)
1667 1673 if opts['patch']:
1668 1674 prev = (parents and parents[0]) or nullid
1669 1675 dodiff(ui, ui, repo, prev, n)
1670 1676 ui.write("\n")
1671 1677
1672 1678 def parents(ui, repo, rev=None):
1673 1679 """show the parents of the working dir or revision
1674 1680
1675 1681 Print the working directory's parent revisions.
1676 1682 """
1677 1683 if rev:
1678 1684 p = repo.changelog.parents(repo.lookup(rev))
1679 1685 else:
1680 1686 p = repo.dirstate.parents()
1681 1687
1682 1688 for n in p:
1683 1689 if n != nullid:
1684 1690 show_changeset(ui, repo, changenode=n)
1685 1691
1686 1692 def paths(ui, search=None):
1687 1693 """show definition of symbolic path names
1688 1694
1689 1695 Show definition of symbolic path name NAME. If no name is given, show
1690 1696 definition of available names.
1691 1697
1692 1698 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1693 1699 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1694 1700 """
1695 1701 try:
1696 1702 repo = hg.repository(ui=ui)
1697 1703 except hg.RepoError:
1698 1704 pass
1699 1705
1700 1706 if search:
1701 1707 for name, path in ui.configitems("paths"):
1702 1708 if name == search:
1703 1709 ui.write("%s\n" % path)
1704 1710 return
1705 1711 ui.warn(_("not found!\n"))
1706 1712 return 1
1707 1713 else:
1708 1714 for name, path in ui.configitems("paths"):
1709 1715 ui.write("%s = %s\n" % (name, path))
1710 1716
1711 1717 def pull(ui, repo, source="default", **opts):
1712 1718 """pull changes from the specified source
1713 1719
1714 1720 Pull changes from a remote repository to a local one.
1715 1721
1716 1722 This finds all changes from the repository at the specified path
1717 1723 or URL and adds them to the local repository. By default, this
1718 1724 does not update the copy of the project in the working directory.
1719 1725
1720 1726 Valid URLs are of the form:
1721 1727
1722 1728 local/filesystem/path
1723 1729 http://[user@]host[:port][/path]
1724 1730 https://[user@]host[:port][/path]
1725 1731 ssh://[user@]host[:port][/path]
1726 1732
1727 1733 SSH requires an accessible shell account on the destination machine
1728 1734 and a copy of hg in the remote path. With SSH, paths are relative
1729 1735 to the remote user's home directory by default; use two slashes at
1730 1736 the start of a path to specify it as relative to the filesystem root.
1731 1737 """
1732 1738 source = ui.expandpath(source, repo.root)
1733 1739 ui.status(_('pulling from %s\n') % (source))
1734 1740
1735 1741 if opts['ssh']:
1736 1742 ui.setconfig("ui", "ssh", opts['ssh'])
1737 1743 if opts['remotecmd']:
1738 1744 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1739 1745
1740 1746 other = hg.repository(ui, source)
1741 1747 revs = None
1742 1748 if opts['rev'] and not other.local():
1743 1749 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
1744 1750 elif opts['rev']:
1745 1751 revs = [other.lookup(rev) for rev in opts['rev']]
1746 1752 r = repo.pull(other, heads=revs)
1747 1753 if not r:
1748 1754 if opts['update']:
1749 1755 return update(ui, repo)
1750 1756 else:
1751 1757 ui.status(_("(run 'hg update' to get a working copy)\n"))
1752 1758
1753 1759 return r
1754 1760
1755 1761 def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
1756 1762 """push changes to the specified destination
1757 1763
1758 1764 Push changes from the local repository to the given destination.
1759 1765
1760 1766 This is the symmetrical operation for pull. It helps to move
1761 1767 changes from the current repository to a different one. If the
1762 1768 destination is local this is identical to a pull in that directory
1763 1769 from the current one.
1764 1770
1765 1771 By default, push will refuse to run if it detects the result would
1766 1772 increase the number of remote heads. This generally indicates the
1767 1773 the client has forgotten to sync and merge before pushing.
1768 1774
1769 1775 Valid URLs are of the form:
1770 1776
1771 1777 local/filesystem/path
1772 1778 ssh://[user@]host[:port][/path]
1773 1779
1774 1780 SSH requires an accessible shell account on the destination
1775 1781 machine and a copy of hg in the remote path.
1776 1782 """
1777 1783 dest = ui.expandpath(dest, repo.root)
1778 1784 ui.status('pushing to %s\n' % (dest))
1779 1785
1780 1786 if ssh:
1781 1787 ui.setconfig("ui", "ssh", ssh)
1782 1788 if remotecmd:
1783 1789 ui.setconfig("ui", "remotecmd", remotecmd)
1784 1790
1785 1791 other = hg.repository(ui, dest)
1786 1792 r = repo.push(other, force)
1787 1793 return r
1788 1794
1789 1795 def rawcommit(ui, repo, *flist, **rc):
1790 1796 """raw commit interface
1791 1797
1792 1798 Lowlevel commit, for use in helper scripts.
1793 1799
1794 1800 This command is not intended to be used by normal users, as it is
1795 1801 primarily useful for importing from other SCMs.
1796 1802 """
1797 1803 message = rc['message']
1798 1804 if not message and rc['logfile']:
1799 1805 try:
1800 1806 message = open(rc['logfile']).read()
1801 1807 except IOError:
1802 1808 pass
1803 1809 if not message and not rc['logfile']:
1804 1810 raise util.Abort(_("missing commit message"))
1805 1811
1806 1812 files = relpath(repo, list(flist))
1807 1813 if rc['files']:
1808 1814 files += open(rc['files']).read().splitlines()
1809 1815
1810 1816 rc['parent'] = map(repo.lookup, rc['parent'])
1811 1817
1812 1818 try:
1813 1819 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
1814 1820 except ValueError, inst:
1815 1821 raise util.Abort(str(inst))
1816 1822
1817 1823 def recover(ui, repo):
1818 1824 """roll back an interrupted transaction
1819 1825
1820 1826 Recover from an interrupted commit or pull.
1821 1827
1822 1828 This command tries to fix the repository status after an interrupted
1823 1829 operation. It should only be necessary when Mercurial suggests it.
1824 1830 """
1825 1831 if repo.recover():
1826 1832 return repo.verify()
1827 1833 return False
1828 1834
1829 1835 def remove(ui, repo, pat, *pats, **opts):
1830 1836 """remove the specified files on the next commit
1831 1837
1832 1838 Schedule the indicated files for removal from the repository.
1833 1839
1834 1840 This command schedules the files to be removed at the next commit.
1835 1841 This only removes files from the current branch, not from the
1836 1842 entire project history. If the files still exist in the working
1837 1843 directory, they will be deleted from it.
1838 1844 """
1839 1845 names = []
1840 1846 def okaytoremove(abs, rel, exact):
1841 1847 modified, added, removed, deleted, unknown = repo.changes(files=[abs])
1842 1848 reason = None
1843 1849 if modified:
1844 1850 reason = _('is modified')
1845 1851 elif added:
1846 1852 reason = _('has been marked for add')
1847 1853 elif unknown:
1848 1854 reason = _('is not managed')
1849 1855 if reason:
1850 1856 if exact:
1851 1857 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
1852 1858 else:
1853 1859 return True
1854 1860 for src, abs, rel, exact in walk(repo, (pat,) + pats, opts):
1855 1861 if okaytoremove(abs, rel, exact):
1856 1862 if ui.verbose or not exact:
1857 1863 ui.status(_('removing %s\n') % rel)
1858 1864 names.append(abs)
1859 1865 repo.remove(names, unlink=True)
1860 1866
1861 1867 def rename(ui, repo, *pats, **opts):
1862 1868 """rename files; equivalent of copy + remove
1863 1869
1864 1870 Mark dest as copies of sources; mark sources for deletion. If
1865 1871 dest is a directory, copies are put in that directory. If dest is
1866 1872 a file, there can only be one source.
1867 1873
1868 1874 By default, this command copies the contents of files as they
1869 1875 stand in the working directory. If invoked with --after, the
1870 1876 operation is recorded, but no copying is performed.
1871 1877
1872 1878 This command takes effect in the next commit.
1873 1879
1874 1880 NOTE: This command should be treated as experimental. While it
1875 1881 should properly record rename files, this information is not yet
1876 1882 fully used by merge, nor fully reported by log.
1877 1883 """
1878 1884 errs, copied = docopy(ui, repo, pats, opts)
1879 1885 names = []
1880 1886 for abs, rel, exact in copied:
1881 1887 if ui.verbose or not exact:
1882 1888 ui.status(_('removing %s\n') % rel)
1883 1889 names.append(abs)
1884 1890 repo.remove(names, unlink=True)
1885 1891 return errs
1886 1892
1887 1893 def revert(ui, repo, *pats, **opts):
1888 1894 """revert modified files or dirs back to their unmodified states
1889 1895
1890 1896 Revert any uncommitted modifications made to the named files or
1891 1897 directories. This restores the contents of the affected files to
1892 1898 an unmodified state.
1893 1899
1894 1900 If a file has been deleted, it is recreated. If the executable
1895 1901 mode of a file was changed, it is reset.
1896 1902
1897 1903 If names are given, all files matching the names are reverted.
1898 1904
1899 1905 If no arguments are given, all files in the repository are reverted.
1900 1906 """
1901 1907 node = opts['rev'] and repo.lookup(opts['rev']) or \
1902 1908 repo.dirstate.parents()[0]
1903 1909
1904 1910 files, choose, anypats = matchpats(repo, pats, opts)
1905 1911 modified, added, removed, deleted, unknown = repo.changes(match=choose)
1906 1912 repo.forget(added)
1907 1913 repo.undelete(removed + deleted)
1908 1914
1909 1915 return repo.update(node, False, True, choose, False)
1910 1916
1911 1917 def root(ui, repo):
1912 1918 """print the root (top) of the current working dir
1913 1919
1914 1920 Print the root directory of the current repository.
1915 1921 """
1916 1922 ui.write(repo.root + "\n")
1917 1923
1918 1924 def serve(ui, repo, **opts):
1919 1925 """export the repository via HTTP
1920 1926
1921 1927 Start a local HTTP repository browser and pull server.
1922 1928
1923 1929 By default, the server logs accesses to stdout and errors to
1924 1930 stderr. Use the "-A" and "-E" options to log to files.
1925 1931 """
1926 1932
1927 1933 if opts["stdio"]:
1928 1934 fin, fout = sys.stdin, sys.stdout
1929 1935 sys.stdout = sys.stderr
1930 1936
1931 1937 # Prevent insertion/deletion of CRs
1932 1938 util.set_binary(fin)
1933 1939 util.set_binary(fout)
1934 1940
1935 1941 def getarg():
1936 1942 argline = fin.readline()[:-1]
1937 1943 arg, l = argline.split()
1938 1944 val = fin.read(int(l))
1939 1945 return arg, val
1940 1946 def respond(v):
1941 1947 fout.write("%d\n" % len(v))
1942 1948 fout.write(v)
1943 1949 fout.flush()
1944 1950
1945 1951 lock = None
1946 1952
1947 1953 while 1:
1948 1954 cmd = fin.readline()[:-1]
1949 1955 if cmd == '':
1950 1956 return
1951 1957 if cmd == "heads":
1952 1958 h = repo.heads()
1953 1959 respond(" ".join(map(hex, h)) + "\n")
1954 1960 if cmd == "lock":
1955 1961 lock = repo.lock()
1956 1962 respond("")
1957 1963 if cmd == "unlock":
1958 1964 if lock:
1959 1965 lock.release()
1960 1966 lock = None
1961 1967 respond("")
1962 1968 elif cmd == "branches":
1963 1969 arg, nodes = getarg()
1964 1970 nodes = map(bin, nodes.split(" "))
1965 1971 r = []
1966 1972 for b in repo.branches(nodes):
1967 1973 r.append(" ".join(map(hex, b)) + "\n")
1968 1974 respond("".join(r))
1969 1975 elif cmd == "between":
1970 1976 arg, pairs = getarg()
1971 1977 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
1972 1978 r = []
1973 1979 for b in repo.between(pairs):
1974 1980 r.append(" ".join(map(hex, b)) + "\n")
1975 1981 respond("".join(r))
1976 1982 elif cmd == "changegroup":
1977 1983 nodes = []
1978 1984 arg, roots = getarg()
1979 1985 nodes = map(bin, roots.split(" "))
1980 1986
1981 1987 cg = repo.changegroup(nodes)
1982 1988 while 1:
1983 1989 d = cg.read(4096)
1984 1990 if not d:
1985 1991 break
1986 1992 fout.write(d)
1987 1993
1988 1994 fout.flush()
1989 1995
1990 1996 elif cmd == "addchangegroup":
1991 1997 if not lock:
1992 1998 respond("not locked")
1993 1999 continue
1994 2000 respond("")
1995 2001
1996 2002 r = repo.addchangegroup(fin)
1997 2003 respond("")
1998 2004
1999 2005 optlist = "name templates style address port ipv6 accesslog errorlog"
2000 2006 for o in optlist.split():
2001 2007 if opts[o]:
2002 2008 ui.setconfig("web", o, opts[o])
2003 2009
2004 2010 try:
2005 2011 httpd = hgweb.create_server(repo)
2006 2012 except socket.error, inst:
2007 2013 raise util.Abort(_('cannot start server: ') + inst.args[1])
2008 2014
2009 2015 if ui.verbose:
2010 2016 addr, port = httpd.socket.getsockname()
2011 2017 if addr == '0.0.0.0':
2012 2018 addr = socket.gethostname()
2013 2019 else:
2014 2020 try:
2015 2021 addr = socket.gethostbyaddr(addr)[0]
2016 2022 except socket.error:
2017 2023 pass
2018 2024 if port != 80:
2019 2025 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2020 2026 else:
2021 2027 ui.status(_('listening at http://%s/\n') % addr)
2022 2028 httpd.serve_forever()
2023 2029
2024 2030 def status(ui, repo, *pats, **opts):
2025 2031 """show changed files in the working directory
2026 2032
2027 2033 Show changed files in the repository. If names are
2028 2034 given, only files that match are shown.
2029 2035
2030 2036 The codes used to show the status of files are:
2031 2037 M = modified
2032 2038 A = added
2033 2039 R = removed
2034 2040 ! = deleted, but still tracked
2035 2041 ? = not tracked
2036 2042 """
2037 2043
2038 2044 files, matchfn, anypats = matchpats(repo, pats, opts)
2039 2045 cwd = (pats and repo.getcwd()) or ''
2040 2046 modified, added, removed, deleted, unknown = [
2041 2047 [util.pathto(cwd, x) for x in n]
2042 2048 for n in repo.changes(files=files, match=matchfn)]
2043 2049
2044 2050 changetypes = [(_('modified'), 'M', modified),
2045 2051 (_('added'), 'A', added),
2046 2052 (_('removed'), 'R', removed),
2047 2053 (_('deleted'), '!', deleted),
2048 2054 (_('unknown'), '?', unknown)]
2049 2055
2050 2056 end = opts['print0'] and '\0' or '\n'
2051 2057
2052 2058 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2053 2059 or changetypes):
2054 2060 if opts['no_status']:
2055 2061 format = "%%s%s" % end
2056 2062 else:
2057 2063 format = "%s %%s%s" % (char, end);
2058 2064
2059 2065 for f in changes:
2060 2066 ui.write(format % f)
2061 2067
2062 2068 def tag(ui, repo, name, rev_=None, **opts):
2063 2069 """add a tag for the current tip or a given revision
2064 2070
2065 2071 Name a particular revision using <name>.
2066 2072
2067 2073 Tags are used to name particular revisions of the repository and are
2068 2074 very useful to compare different revision, to go back to significant
2069 2075 earlier versions or to mark branch points as releases, etc.
2070 2076
2071 2077 If no revision is given, the tip is used.
2072 2078
2073 2079 To facilitate version control, distribution, and merging of tags,
2074 2080 they are stored as a file named ".hgtags" which is managed
2075 2081 similarly to other project files and can be hand-edited if
2076 2082 necessary.
2077 2083 """
2078 2084 if name == "tip":
2079 2085 raise util.Abort(_("the name 'tip' is reserved"))
2080 2086 if opts['rev']:
2081 2087 rev_ = opts['rev']
2082 2088 if rev_:
2083 2089 r = hex(repo.lookup(rev_))
2084 2090 else:
2085 2091 r = hex(repo.changelog.tip())
2086 2092
2087 2093 disallowed = (revrangesep, '\r', '\n')
2088 2094 for c in disallowed:
2089 2095 if name.find(c) >= 0:
2090 2096 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2091 2097
2092 2098 if opts['local']:
2093 2099 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2094 2100 return
2095 2101
2096 2102 for x in repo.changes():
2097 2103 if ".hgtags" in x:
2098 2104 raise util.Abort(_("working copy of .hgtags is changed "
2099 2105 "(please commit .hgtags manually)"))
2100 2106
2101 2107 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2102 2108 if repo.dirstate.state(".hgtags") == '?':
2103 2109 repo.add([".hgtags"])
2104 2110
2105 2111 message = (opts['message'] or
2106 2112 _("Added tag %s for changeset %s") % (name, r))
2107 2113 try:
2108 2114 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2109 2115 except ValueError, inst:
2110 2116 raise util.Abort(str(inst))
2111 2117
2112 2118 def tags(ui, repo):
2113 2119 """list repository tags
2114 2120
2115 2121 List the repository tags.
2116 2122
2117 2123 This lists both regular and local tags.
2118 2124 """
2119 2125
2120 2126 l = repo.tagslist()
2121 2127 l.reverse()
2122 2128 for t, n in l:
2123 2129 try:
2124 2130 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2125 2131 except KeyError:
2126 2132 r = " ?:?"
2127 2133 ui.write("%-30s %s\n" % (t, r))
2128 2134
2129 2135 def tip(ui, repo):
2130 2136 """show the tip revision
2131 2137
2132 2138 Show the tip revision.
2133 2139 """
2134 2140 n = repo.changelog.tip()
2135 2141 show_changeset(ui, repo, changenode=n)
2136 2142
2137 2143 def unbundle(ui, repo, fname, **opts):
2138 2144 """apply a changegroup file
2139 2145
2140 2146 Apply a compressed changegroup file generated by the bundle
2141 2147 command.
2142 2148 """
2143 2149 f = urllib.urlopen(fname)
2144 2150
2145 2151 if f.read(4) != "HG10":
2146 2152 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2147 2153
2148 2154 def bzgenerator(f):
2149 2155 zd = bz2.BZ2Decompressor()
2150 2156 for chunk in f:
2151 2157 yield zd.decompress(chunk)
2152 2158
2153 2159 bzgen = bzgenerator(util.filechunkiter(f, 4096))
2154 2160 if repo.addchangegroup(util.chunkbuffer(bzgen)):
2155 2161 return 1
2156 2162
2157 2163 if opts['update']:
2158 2164 return update(ui, repo)
2159 2165 else:
2160 2166 ui.status(_("(run 'hg update' to get a working copy)\n"))
2161 2167
2162 2168 def undo(ui, repo):
2163 2169 """undo the last commit or pull
2164 2170
2165 2171 Roll back the last pull or commit transaction on the
2166 2172 repository, restoring the project to its earlier state.
2167 2173
2168 2174 This command should be used with care. There is only one level of
2169 2175 undo and there is no redo.
2170 2176
2171 2177 This command is not intended for use on public repositories. Once
2172 2178 a change is visible for pull by other users, undoing it locally is
2173 2179 ineffective.
2174 2180 """
2175 2181 repo.undo()
2176 2182
2177 2183 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2178 2184 branch=None):
2179 2185 """update or merge working directory
2180 2186
2181 2187 Update the working directory to the specified revision.
2182 2188
2183 2189 If there are no outstanding changes in the working directory and
2184 2190 there is a linear relationship between the current version and the
2185 2191 requested version, the result is the requested version.
2186 2192
2187 2193 Otherwise the result is a merge between the contents of the
2188 2194 current working directory and the requested version. Files that
2189 2195 changed between either parent are marked as changed for the next
2190 2196 commit and a commit must be performed before any further updates
2191 2197 are allowed.
2192 2198
2193 2199 By default, update will refuse to run if doing so would require
2194 2200 merging or discarding local changes.
2195 2201 """
2196 2202 if branch:
2197 2203 br = repo.branchlookup(branch=branch)
2198 2204 found = []
2199 2205 for x in br:
2200 2206 if branch in br[x]:
2201 2207 found.append(x)
2202 2208 if len(found) > 1:
2203 2209 ui.warn(_("Found multiple heads for %s\n") % branch)
2204 2210 for x in found:
2205 2211 show_changeset(ui, repo, changenode=x, brinfo=br)
2206 2212 return 1
2207 2213 if len(found) == 1:
2208 2214 node = found[0]
2209 2215 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2210 2216 else:
2211 2217 ui.warn(_("branch %s not found\n") % (branch))
2212 2218 return 1
2213 2219 else:
2214 2220 node = node and repo.lookup(node) or repo.changelog.tip()
2215 2221 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2216 2222
2217 2223 def verify(ui, repo):
2218 2224 """verify the integrity of the repository
2219 2225
2220 2226 Verify the integrity of the current repository.
2221 2227
2222 2228 This will perform an extensive check of the repository's
2223 2229 integrity, validating the hashes and checksums of each entry in
2224 2230 the changelog, manifest, and tracked files, as well as the
2225 2231 integrity of their crosslinks and indices.
2226 2232 """
2227 2233 return repo.verify()
2228 2234
2229 2235 # Command options and aliases are listed here, alphabetically
2230 2236
2231 2237 table = {
2232 2238 "^add":
2233 2239 (add,
2234 2240 [('I', 'include', [], _('include names matching the given patterns')),
2235 2241 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2236 2242 _('hg add [OPTION]... [FILE]...')),
2237 2243 "addremove":
2238 2244 (addremove,
2239 2245 [('I', 'include', [], _('include names matching the given patterns')),
2240 2246 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2241 2247 _('hg addremove [OPTION]... [FILE]...')),
2242 2248 "^annotate":
2243 2249 (annotate,
2244 2250 [('r', 'rev', '', _('annotate the specified revision')),
2245 2251 ('a', 'text', None, _('treat all files as text')),
2246 2252 ('u', 'user', None, _('list the author')),
2247 2253 ('d', 'date', None, _('list the date')),
2248 2254 ('n', 'number', None, _('list the revision number (default)')),
2249 2255 ('c', 'changeset', None, _('list the changeset')),
2250 2256 ('I', 'include', [], _('include names matching the given patterns')),
2251 2257 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2252 2258 _('hg annotate [OPTION]... FILE...')),
2253 2259 "bundle":
2254 2260 (bundle,
2255 2261 [],
2256 2262 _('hg bundle FILE DEST')),
2257 2263 "cat":
2258 2264 (cat,
2259 2265 [('I', 'include', [], _('include names matching the given patterns')),
2260 2266 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2261 2267 ('o', 'output', '', _('print output to file with formatted name')),
2262 2268 ('r', 'rev', '', _('print the given revision'))],
2263 2269 _('hg cat [OPTION]... FILE...')),
2264 2270 "^clone":
2265 2271 (clone,
2266 2272 [('U', 'noupdate', None, _('do not update the new working directory')),
2267 2273 ('e', 'ssh', '', _('specify ssh command to use')),
2268 2274 ('', 'pull', None, _('use pull protocol to copy metadata')),
2269 2275 ('r', 'rev', [],
2270 2276 _('a changeset you would like to have after cloning')),
2271 2277 ('', 'remotecmd', '',
2272 2278 _('specify hg command to run on the remote side'))],
2273 2279 _('hg clone [OPTION]... SOURCE [DEST]')),
2274 2280 "^commit|ci":
2275 2281 (commit,
2276 2282 [('A', 'addremove', None, _('run addremove during commit')),
2277 2283 ('I', 'include', [], _('include names matching the given patterns')),
2278 2284 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2279 2285 ('m', 'message', '', _('use <text> as commit message')),
2280 2286 ('l', 'logfile', '', _('read the commit message from <file>')),
2281 2287 ('d', 'date', '', _('record datecode as commit date')),
2282 2288 ('u', 'user', '', _('record user as commiter'))],
2283 2289 _('hg commit [OPTION]... [FILE]...')),
2284 2290 "copy|cp":
2285 2291 (copy,
2286 2292 [('I', 'include', [], _('include names matching the given patterns')),
2287 2293 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2288 2294 ('A', 'after', None, _('record a copy that has already occurred')),
2289 2295 ('f', 'force', None,
2290 2296 _('forcibly copy over an existing managed file'))],
2291 2297 _('hg copy [OPTION]... [SOURCE]... DEST')),
2292 2298 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2293 2299 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2294 2300 "debugconfig": (debugconfig, [], _('debugconfig')),
2295 2301 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2296 2302 "debugstate": (debugstate, [], _('debugstate')),
2297 2303 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2298 2304 "debugindex": (debugindex, [], _('debugindex FILE')),
2299 2305 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2300 2306 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2301 2307 "debugwalk":
2302 2308 (debugwalk,
2303 2309 [('I', 'include', [], _('include names matching the given patterns')),
2304 2310 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2305 2311 _('debugwalk [OPTION]... [FILE]...')),
2306 2312 "^diff":
2307 2313 (diff,
2308 2314 [('r', 'rev', [], _('revision')),
2309 2315 ('a', 'text', None, _('treat all files as text')),
2310 2316 ('I', 'include', [], _('include names matching the given patterns')),
2311 2317 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2312 2318 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2313 2319 "^export":
2314 2320 (export,
2315 2321 [('o', 'output', '', _('print output to file with formatted name')),
2316 2322 ('a', 'text', None, _('treat all files as text')),
2317 2323 ('', 'switch-parent', None, _('diff against the second parent'))],
2318 2324 _('hg export [-a] [-o OUTFILE] REV...')),
2319 2325 "forget":
2320 2326 (forget,
2321 2327 [('I', 'include', [], _('include names matching the given patterns')),
2322 2328 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2323 2329 _('hg forget [OPTION]... FILE...')),
2324 2330 "grep":
2325 2331 (grep,
2326 2332 [('0', 'print0', None, _('end fields with NUL')),
2327 2333 ('I', 'include', [], _('include names matching the given patterns')),
2328 2334 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2329 2335 ('', 'all', None, _('print all revisions that match')),
2330 2336 ('i', 'ignore-case', None, _('ignore case when matching')),
2331 2337 ('l', 'files-with-matches', None,
2332 2338 _('print only filenames and revs that match')),
2333 2339 ('n', 'line-number', None, _('print matching line numbers')),
2334 2340 ('r', 'rev', [], _('search in given revision range')),
2335 2341 ('u', 'user', None, _('print user who committed change'))],
2336 2342 _('hg grep [OPTION]... PATTERN [FILE]...')),
2337 2343 "heads":
2338 2344 (heads,
2339 2345 [('b', 'branches', None, _('find branch info')),
2340 2346 ('r', 'rev', '', _('show only heads which are descendants of rev'))],
2341 2347 _('hg heads [-b] [-r <rev>]')),
2342 2348 "help": (help_, [], _('hg help [COMMAND]')),
2343 2349 "identify|id": (identify, [], _('hg identify')),
2344 2350 "import|patch":
2345 2351 (import_,
2346 2352 [('p', 'strip', 1,
2347 2353 _('directory strip option for patch. This has the same\n') +
2348 2354 _('meaning as the corresponding patch option')),
2349 2355 ('f', 'force', None,
2350 2356 _('skip check for outstanding uncommitted changes')),
2351 2357 ('b', 'base', '', _('base path'))],
2352 2358 _('hg import [-f] [-p NUM] [-b BASE] PATCH...')),
2353 2359 "incoming|in": (incoming,
2354 2360 [('M', 'no-merges', None, _('do not show merges')),
2355 2361 ('p', 'patch', None, _('show patch')),
2356 2362 ('n', 'newest-first', None, _('show newest record first'))],
2357 2363 _('hg incoming [-p] [-n] [-M] [SOURCE]')),
2358 2364 "^init": (init, [], _('hg init [DEST]')),
2359 2365 "locate":
2360 2366 (locate,
2361 2367 [('r', 'rev', '', _('search the repository as it stood at rev')),
2362 2368 ('0', 'print0', None,
2363 2369 _('end filenames with NUL, for use with xargs')),
2364 2370 ('f', 'fullpath', None,
2365 2371 _('print complete paths from the filesystem root')),
2366 2372 ('I', 'include', [], _('include names matching the given patterns')),
2367 2373 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2368 2374 _('hg locate [OPTION]... [PATTERN]...')),
2369 2375 "^log|history":
2370 2376 (log,
2371 2377 [('I', 'include', [], _('include names matching the given patterns')),
2372 2378 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2373 2379 ('b', 'branch', None, _('show branches')),
2374 2380 ('k', 'keyword', [], _('search for a keyword')),
2375 2381 ('r', 'rev', [], _('show the specified revision or range')),
2376 2382 ('M', 'no-merges', None, _('do not show merges')),
2377 2383 ('m', 'only-merges', None, _('show only merges')),
2378 2384 ('p', 'patch', None, _('show patch'))],
2379 2385 _('hg log [-I] [-X] [-r REV]... [-p] [FILE]')),
2380 2386 "manifest": (manifest, [], _('hg manifest [REV]')),
2381 2387 "outgoing|out": (outgoing,
2382 2388 [('M', 'no-merges', None, _('do not show merges')),
2383 2389 ('p', 'patch', None, _('show patch')),
2384 2390 ('n', 'newest-first', None, _('show newest record first'))],
2385 2391 _('hg outgoing [-p] [-n] [-M] [DEST]')),
2386 2392 "^parents": (parents, [], _('hg parents [REV]')),
2387 2393 "paths": (paths, [], _('hg paths [NAME]')),
2388 2394 "^pull":
2389 2395 (pull,
2390 2396 [('u', 'update', None,
2391 2397 _('update the working directory to tip after pull')),
2392 2398 ('e', 'ssh', '', _('specify ssh command to use')),
2393 2399 ('r', 'rev', [], _('a specific revision you would like to pull')),
2394 2400 ('', 'remotecmd', '',
2395 2401 _('specify hg command to run on the remote side'))],
2396 2402 _('hg pull [-u] [-e FILE] [-r rev] [--remotecmd FILE] [SOURCE]')),
2397 2403 "^push":
2398 2404 (push,
2399 2405 [('f', 'force', None, _('force push')),
2400 2406 ('e', 'ssh', '', _('specify ssh command to use')),
2401 2407 ('', 'remotecmd', '',
2402 2408 _('specify hg command to run on the remote side'))],
2403 2409 _('hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]')),
2404 2410 "rawcommit":
2405 2411 (rawcommit,
2406 2412 [('p', 'parent', [], _('parent')),
2407 2413 ('d', 'date', '', _('date code')),
2408 2414 ('u', 'user', '', _('user')),
2409 2415 ('F', 'files', '', _('file list')),
2410 2416 ('m', 'message', '', _('commit message')),
2411 2417 ('l', 'logfile', '', _('commit message file'))],
2412 2418 _('hg rawcommit [OPTION]... [FILE]...')),
2413 2419 "recover": (recover, [], _('hg recover')),
2414 2420 "^remove|rm":
2415 2421 (remove,
2416 2422 [('I', 'include', [], _('include names matching the given patterns')),
2417 2423 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2418 2424 _('hg remove [OPTION]... FILE...')),
2419 2425 "rename|mv":
2420 2426 (rename,
2421 2427 [('I', 'include', [], _('include names matching the given patterns')),
2422 2428 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2423 2429 ('A', 'after', None, _('record a rename that has already occurred')),
2424 2430 ('f', 'force', None,
2425 2431 _('forcibly copy over an existing managed file'))],
2426 2432 _('hg rename [OPTION]... [SOURCE]... DEST')),
2427 2433 "^revert":
2428 2434 (revert,
2429 2435 [('I', 'include', [], _('include names matching the given patterns')),
2430 2436 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2431 2437 ('r', 'rev', '', _('revision to revert to'))],
2432 2438 _('hg revert [-n] [-r REV] [NAME]...')),
2433 2439 "root": (root, [], _('hg root')),
2434 2440 "^serve":
2435 2441 (serve,
2436 2442 [('A', 'accesslog', '', _('name of access log file to write to')),
2437 2443 ('E', 'errorlog', '', _('name of error log file to write to')),
2438 2444 ('p', 'port', 0, _('port to use (default: 8000)')),
2439 2445 ('a', 'address', '', _('address to use')),
2440 2446 ('n', 'name', '',
2441 2447 _('name to show in web pages (default: working dir)')),
2442 2448 ('', 'stdio', None, _('for remote clients')),
2443 2449 ('t', 'templates', '', _('web templates to use')),
2444 2450 ('', 'style', '', _('template style to use')),
2445 2451 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
2446 2452 _('hg serve [OPTION]...')),
2447 2453 "^status|st":
2448 2454 (status,
2449 2455 [('m', 'modified', None, _('show only modified files')),
2450 2456 ('a', 'added', None, _('show only added files')),
2451 2457 ('r', 'removed', None, _('show only removed files')),
2452 2458 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
2453 2459 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
2454 2460 ('n', 'no-status', None, _('hide status prefix')),
2455 2461 ('0', 'print0', None,
2456 2462 _('end filenames with NUL, for use with xargs')),
2457 2463 ('I', 'include', [], _('include names matching the given patterns')),
2458 2464 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2459 2465 _('hg status [OPTION]... [FILE]...')),
2460 2466 "tag":
2461 2467 (tag,
2462 2468 [('l', 'local', None, _('make the tag local')),
2463 2469 ('m', 'message', '', _('message for tag commit log entry')),
2464 2470 ('d', 'date', '', _('record datecode as commit date')),
2465 2471 ('u', 'user', '', _('record user as commiter')),
2466 2472 ('r', 'rev', '', _('revision to tag'))],
2467 2473 _('hg tag [OPTION]... NAME [REV]')),
2468 2474 "tags": (tags, [], _('hg tags')),
2469 2475 "tip": (tip, [], _('hg tip')),
2470 2476 "unbundle":
2471 2477 (unbundle,
2472 2478 [('u', 'update', None,
2473 2479 _('update the working directory to tip after unbundle'))],
2474 2480 _('hg unbundle [-u] FILE')),
2475 2481 "undo": (undo, [], _('hg undo')),
2476 2482 "^update|up|checkout|co":
2477 2483 (update,
2478 2484 [('b', 'branch', '', _('checkout the head of a specific branch')),
2479 2485 ('m', 'merge', None, _('allow merging of branches')),
2480 2486 ('C', 'clean', None, _('overwrite locally modified files')),
2481 2487 ('f', 'force', None, _('force a merge with outstanding changes'))],
2482 2488 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
2483 2489 "verify": (verify, [], _('hg verify')),
2484 2490 "version": (show_version, [], _('hg version')),
2485 2491 }
2486 2492
2487 2493 globalopts = [
2488 2494 ('R', 'repository', '', _('repository root directory')),
2489 2495 ('', 'cwd', '', _('change working directory')),
2490 2496 ('y', 'noninteractive', None,
2491 2497 _('do not prompt, assume \'yes\' for any required answers')),
2492 2498 ('q', 'quiet', None, _('suppress output')),
2493 2499 ('v', 'verbose', None, _('enable additional output')),
2494 2500 ('', 'debug', None, _('enable debugging output')),
2495 2501 ('', 'debugger', None, _('start debugger')),
2496 2502 ('', 'traceback', None, _('print traceback on exception')),
2497 2503 ('', 'time', None, _('time how long the command takes')),
2498 2504 ('', 'profile', None, _('print command execution profile')),
2499 2505 ('', 'version', None, _('output version information and exit')),
2500 2506 ('h', 'help', None, _('display help and exit')),
2501 2507 ]
2502 2508
2503 2509 norepo = ("clone init version help debugancestor debugconfig debugdata"
2504 2510 " debugindex debugindexdot paths")
2505 2511
2506 2512 def find(cmd):
2507 2513 """Return (aliases, command table entry) for command string."""
2508 2514 choice = None
2509 2515 for e in table.keys():
2510 2516 aliases = e.lstrip("^").split("|")
2511 2517 if cmd in aliases:
2512 2518 return aliases, table[e]
2513 2519 for a in aliases:
2514 2520 if a.startswith(cmd):
2515 2521 if choice:
2516 2522 raise AmbiguousCommand(cmd)
2517 2523 else:
2518 2524 choice = aliases, table[e]
2519 2525 break
2520 2526 if choice:
2521 2527 return choice
2522 2528
2523 2529 raise UnknownCommand(cmd)
2524 2530
2525 2531 class SignalInterrupt(Exception):
2526 2532 """Exception raised on SIGTERM and SIGHUP."""
2527 2533
2528 2534 def catchterm(*args):
2529 2535 raise SignalInterrupt
2530 2536
2531 2537 def run():
2532 2538 sys.exit(dispatch(sys.argv[1:]))
2533 2539
2534 2540 class ParseError(Exception):
2535 2541 """Exception raised on errors in parsing the command line."""
2536 2542
2537 2543 def parse(ui, args):
2538 2544 options = {}
2539 2545 cmdoptions = {}
2540 2546
2541 2547 try:
2542 2548 args = fancyopts.fancyopts(args, globalopts, options)
2543 2549 except fancyopts.getopt.GetoptError, inst:
2544 2550 raise ParseError(None, inst)
2545 2551
2546 2552 if args:
2547 2553 cmd, args = args[0], args[1:]
2548 2554 aliases, i = find(cmd)
2549 2555 cmd = aliases[0]
2550 2556 defaults = ui.config("defaults", cmd)
2551 2557 if defaults:
2552 2558 args = defaults.split() + args
2553 2559 c = list(i[1])
2554 2560 else:
2555 2561 cmd = None
2556 2562 c = []
2557 2563
2558 2564 # combine global options into local
2559 2565 for o in globalopts:
2560 2566 c.append((o[0], o[1], options[o[1]], o[3]))
2561 2567
2562 2568 try:
2563 2569 args = fancyopts.fancyopts(args, c, cmdoptions)
2564 2570 except fancyopts.getopt.GetoptError, inst:
2565 2571 raise ParseError(cmd, inst)
2566 2572
2567 2573 # separate global options back out
2568 2574 for o in globalopts:
2569 2575 n = o[1]
2570 2576 options[n] = cmdoptions[n]
2571 2577 del cmdoptions[n]
2572 2578
2573 2579 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
2574 2580
2575 2581 def dispatch(args):
2576 2582 signal.signal(signal.SIGTERM, catchterm)
2577 2583 try:
2578 2584 signal.signal(signal.SIGHUP, catchterm)
2579 2585 except AttributeError:
2580 2586 pass
2581 2587
2582 2588 try:
2583 2589 u = ui.ui()
2584 2590 except util.Abort, inst:
2585 2591 sys.stderr.write(_("abort: %s\n") % inst)
2586 2592 sys.exit(1)
2587 2593
2588 2594 external = []
2589 2595 for x in u.extensions():
2590 2596 def on_exception(exc, inst):
2591 2597 u.warn(_("*** failed to import extension %s\n") % x[1])
2592 2598 u.warn("%s\n" % inst)
2593 2599 if "--traceback" in sys.argv[1:]:
2594 2600 traceback.print_exc()
2595 2601 if x[1]:
2596 2602 try:
2597 2603 mod = imp.load_source(x[0], x[1])
2598 2604 except Exception, inst:
2599 2605 on_exception(Exception, inst)
2600 2606 continue
2601 2607 else:
2602 2608 def importh(name):
2603 2609 mod = __import__(name)
2604 2610 components = name.split('.')
2605 2611 for comp in components[1:]:
2606 2612 mod = getattr(mod, comp)
2607 2613 return mod
2608 2614 try:
2609 2615 mod = importh(x[0])
2610 2616 except Exception, inst:
2611 2617 on_exception(Exception, inst)
2612 2618 continue
2613 2619
2614 2620 external.append(mod)
2615 2621 for x in external:
2616 2622 cmdtable = getattr(x, 'cmdtable', {})
2617 2623 for t in cmdtable:
2618 2624 if t in table:
2619 2625 u.warn(_("module %s overrides %s\n") % (x.__name__, t))
2620 2626 table.update(cmdtable)
2621 2627
2622 2628 try:
2623 2629 cmd, func, args, options, cmdoptions = parse(u, args)
2624 2630 except ParseError, inst:
2625 2631 if inst.args[0]:
2626 2632 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
2627 2633 help_(u, inst.args[0])
2628 2634 else:
2629 2635 u.warn(_("hg: %s\n") % inst.args[1])
2630 2636 help_(u, 'shortlist')
2631 2637 sys.exit(-1)
2632 2638 except AmbiguousCommand, inst:
2633 2639 u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
2634 2640 sys.exit(1)
2635 2641 except UnknownCommand, inst:
2636 2642 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
2637 2643 help_(u, 'shortlist')
2638 2644 sys.exit(1)
2639 2645
2640 2646 if options["time"]:
2641 2647 def get_times():
2642 2648 t = os.times()
2643 2649 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
2644 2650 t = (t[0], t[1], t[2], t[3], time.clock())
2645 2651 return t
2646 2652 s = get_times()
2647 2653 def print_time():
2648 2654 t = get_times()
2649 2655 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
2650 2656 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
2651 2657 atexit.register(print_time)
2652 2658
2653 2659 u.updateopts(options["verbose"], options["debug"], options["quiet"],
2654 2660 not options["noninteractive"])
2655 2661
2656 2662 # enter the debugger before command execution
2657 2663 if options['debugger']:
2658 2664 pdb.set_trace()
2659 2665
2660 2666 try:
2661 2667 try:
2662 2668 if options['help']:
2663 2669 help_(u, cmd, options['version'])
2664 2670 sys.exit(0)
2665 2671 elif options['version']:
2666 2672 show_version(u)
2667 2673 sys.exit(0)
2668 2674 elif not cmd:
2669 2675 help_(u, 'shortlist')
2670 2676 sys.exit(0)
2671 2677
2672 2678 if options['cwd']:
2673 2679 try:
2674 2680 os.chdir(options['cwd'])
2675 2681 except OSError, inst:
2676 2682 raise util.Abort('%s: %s' %
2677 2683 (options['cwd'], inst.strerror))
2678 2684
2679 2685 if cmd not in norepo.split():
2680 2686 path = options["repository"] or ""
2681 2687 repo = hg.repository(ui=u, path=path)
2682 2688 for x in external:
2683 2689 if hasattr(x, 'reposetup'):
2684 2690 x.reposetup(u, repo)
2685 2691 d = lambda: func(u, repo, *args, **cmdoptions)
2686 2692 else:
2687 2693 d = lambda: func(u, *args, **cmdoptions)
2688 2694
2689 2695 if options['profile']:
2690 2696 import hotshot, hotshot.stats
2691 2697 prof = hotshot.Profile("hg.prof")
2692 2698 r = prof.runcall(d)
2693 2699 prof.close()
2694 2700 stats = hotshot.stats.load("hg.prof")
2695 2701 stats.strip_dirs()
2696 2702 stats.sort_stats('time', 'calls')
2697 2703 stats.print_stats(40)
2698 2704 return r
2699 2705 else:
2700 2706 return d()
2701 2707 except:
2702 2708 # enter the debugger when we hit an exception
2703 2709 if options['debugger']:
2704 2710 pdb.post_mortem(sys.exc_info()[2])
2705 2711 if options['traceback']:
2706 2712 traceback.print_exc()
2707 2713 raise
2708 2714 except hg.RepoError, inst:
2709 2715 u.warn(_("abort: "), inst, "!\n")
2710 2716 except revlog.RevlogError, inst:
2711 2717 u.warn(_("abort: "), inst, "!\n")
2712 2718 except SignalInterrupt:
2713 2719 u.warn(_("killed!\n"))
2714 2720 except KeyboardInterrupt:
2715 2721 try:
2716 2722 u.warn(_("interrupted!\n"))
2717 2723 except IOError, inst:
2718 2724 if inst.errno == errno.EPIPE:
2719 2725 if u.debugflag:
2720 2726 u.warn(_("\nbroken pipe\n"))
2721 2727 else:
2722 2728 raise
2723 2729 except IOError, inst:
2724 2730 if hasattr(inst, "code"):
2725 2731 u.warn(_("abort: %s\n") % inst)
2726 2732 elif hasattr(inst, "reason"):
2727 2733 u.warn(_("abort: error: %s\n") % inst.reason[1])
2728 2734 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
2729 2735 if u.debugflag:
2730 2736 u.warn(_("broken pipe\n"))
2731 2737 elif getattr(inst, "strerror", None):
2732 2738 if getattr(inst, "filename", None):
2733 2739 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
2734 2740 else:
2735 2741 u.warn(_("abort: %s\n") % inst.strerror)
2736 2742 else:
2737 2743 raise
2738 2744 except OSError, inst:
2739 2745 if hasattr(inst, "filename"):
2740 2746 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
2741 2747 else:
2742 2748 u.warn(_("abort: %s\n") % inst.strerror)
2743 2749 except util.Abort, inst:
2744 2750 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
2745 2751 sys.exit(1)
2746 2752 except TypeError, inst:
2747 2753 # was this an argument error?
2748 2754 tb = traceback.extract_tb(sys.exc_info()[2])
2749 2755 if len(tb) > 2: # no
2750 2756 raise
2751 2757 u.debug(inst, "\n")
2752 2758 u.warn(_("%s: invalid arguments\n") % cmd)
2753 2759 help_(u, cmd)
2754 2760 except AmbiguousCommand, inst:
2755 2761 u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
2756 2762 help_(u, 'shortlist')
2757 2763 except UnknownCommand, inst:
2758 2764 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
2759 2765 help_(u, 'shortlist')
2760 2766 except SystemExit:
2761 2767 # don't catch this in the catch-all below
2762 2768 raise
2763 2769 except:
2764 2770 u.warn(_("** unknown exception encountered, details follow\n"))
2765 2771 u.warn(_("** report bug details to mercurial@selenic.com\n"))
2766 2772 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
2767 2773 % version.get_version())
2768 2774 raise
2769 2775
2770 2776 sys.exit(-1)
@@ -1,1110 +1,1116 b''
1 1 # hgweb.py - web interface to a mercurial repository
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005 Matt Mackall <mpm@selenic.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 import os, cgi, sys
10 10 from demandload import demandload
11 11 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
12 12 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
13 13 demandload(globals(), "mimetypes")
14 14 from node import *
15 15 from i18n import gettext as _
16 16
17 17 def templatepath():
18 18 for f in "templates", "../templates":
19 19 p = os.path.join(os.path.dirname(__file__), f)
20 20 if os.path.isdir(p):
21 21 return p
22 22
23 23 def age(x):
24 24 def plural(t, c):
25 25 if c == 1:
26 26 return t
27 27 return t + "s"
28 28 def fmt(t, c):
29 29 return "%d %s" % (c, plural(t, c))
30 30
31 31 now = time.time()
32 32 then = x[0]
33 33 delta = max(1, int(now - then))
34 34
35 35 scales = [["second", 1],
36 36 ["minute", 60],
37 37 ["hour", 3600],
38 38 ["day", 3600 * 24],
39 39 ["week", 3600 * 24 * 7],
40 40 ["month", 3600 * 24 * 30],
41 41 ["year", 3600 * 24 * 365]]
42 42
43 43 scales.reverse()
44 44
45 45 for t, s in scales:
46 46 n = delta / s
47 47 if n >= 2 or s == 1:
48 48 return fmt(t, n)
49 49
50 50 def nl2br(text):
51 51 return text.replace('\n', '<br/>\n')
52 52
53 53 def obfuscate(text):
54 54 return ''.join(['&#%d;' % ord(c) for c in text])
55 55
56 56 def up(p):
57 57 if p[0] != "/":
58 58 p = "/" + p
59 59 if p[-1] == "/":
60 60 p = p[:-1]
61 61 up = os.path.dirname(p)
62 62 if up == "/":
63 63 return "/"
64 64 return up + "/"
65 65
66 66 def get_mtime(repo_path):
67 67 hg_path = os.path.join(repo_path, ".hg")
68 68 cl_path = os.path.join(hg_path, "00changelog.i")
69 69 if os.path.exists(os.path.join(cl_path)):
70 70 return os.stat(cl_path).st_mtime
71 71 else:
72 72 return os.stat(hg_path).st_mtime
73 73
74 74 class hgrequest(object):
75 75 def __init__(self, inp=None, out=None, env=None):
76 76 self.inp = inp or sys.stdin
77 77 self.out = out or sys.stdout
78 78 self.env = env or os.environ
79 79 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
80 80
81 81 def write(self, *things):
82 82 for thing in things:
83 83 if hasattr(thing, "__iter__"):
84 84 for part in thing:
85 85 self.write(part)
86 86 else:
87 87 try:
88 88 self.out.write(str(thing))
89 89 except socket.error, inst:
90 90 if inst[0] != errno.ECONNRESET:
91 91 raise
92 92
93 93 def header(self, headers=[('Content-type','text/html')]):
94 94 for header in headers:
95 95 self.out.write("%s: %s\r\n" % header)
96 96 self.out.write("\r\n")
97 97
98 98 def httphdr(self, type, file="", size=0):
99 99
100 100 headers = [('Content-type', type)]
101 101 if file:
102 102 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
103 103 if size > 0:
104 104 headers.append(('Content-length', str(size)))
105 105 self.header(headers)
106 106
107 107 class templater(object):
108 108 def __init__(self, mapfile, filters={}, defaults={}):
109 109 self.cache = {}
110 110 self.map = {}
111 111 self.base = os.path.dirname(mapfile)
112 112 self.filters = filters
113 113 self.defaults = defaults
114 114
115 115 for l in file(mapfile):
116 116 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
117 117 if m:
118 118 self.cache[m.group(1)] = m.group(2)
119 119 else:
120 120 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
121 121 if m:
122 122 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
123 123 else:
124 124 raise LookupError(_("unknown map entry '%s'") % l)
125 125
126 126 def __call__(self, t, **map):
127 127 m = self.defaults.copy()
128 128 m.update(map)
129 129 try:
130 130 tmpl = self.cache[t]
131 131 except KeyError:
132 132 tmpl = self.cache[t] = file(self.map[t]).read()
133 133 return self.template(tmpl, self.filters, **m)
134 134
135 135 def template(self, tmpl, filters={}, **map):
136 136 while tmpl:
137 137 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
138 138 if m:
139 139 yield tmpl[:m.start(0)]
140 140 v = map.get(m.group(1), "")
141 141 v = callable(v) and v(**map) or v
142 142
143 143 format = m.group(2)
144 144 fl = m.group(4)
145 145
146 146 if format:
147 147 q = v.__iter__
148 148 for i in q():
149 149 lm = map.copy()
150 150 lm.update(i)
151 151 yield self(format[1:], **lm)
152 152
153 153 v = ""
154 154
155 155 elif fl:
156 156 for f in fl.split("|")[1:]:
157 157 v = filters[f](v)
158 158
159 159 yield v
160 160 tmpl = tmpl[m.end(0):]
161 161 else:
162 162 yield tmpl
163 163 return
164 164
165 165 common_filters = {
166 166 "escape": cgi.escape,
167 167 "strip": lambda x: x.strip(),
168 168 "age": age,
169 169 "date": lambda x: util.datestr(x),
170 170 "addbreaks": nl2br,
171 171 "obfuscate": obfuscate,
172 172 "short": (lambda x: x[:12]),
173 173 "firstline": (lambda x: x.splitlines(1)[0]),
174 174 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
175 175 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
176 176 }
177 177
178 178 class hgweb(object):
179 179 def __init__(self, repo, name=None):
180 180 if type(repo) == type(""):
181 181 self.repo = hg.repository(ui.ui(), repo)
182 182 else:
183 183 self.repo = repo
184 184
185 185 self.mtime = -1
186 186 self.reponame = name
187 187 self.archives = 'zip', 'gz', 'bz2'
188 188
189 189 def refresh(self):
190 190 mtime = get_mtime(self.repo.root)
191 191 if mtime != self.mtime:
192 192 self.mtime = mtime
193 193 self.repo = hg.repository(self.repo.ui, self.repo.root)
194 194 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
195 195 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
196 196 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
197 197
198 198 def archivelist(self, nodeid):
199 199 for i in self.archives:
200 200 if self.repo.ui.configbool("web", "allow" + i, False):
201 201 yield {"type" : i, "node" : nodeid}
202 202
203 203 def listfiles(self, files, mf):
204 204 for f in files[:self.maxfiles]:
205 205 yield self.t("filenodelink", node=hex(mf[f]), file=f)
206 206 if len(files) > self.maxfiles:
207 207 yield self.t("fileellipses")
208 208
209 209 def listfilediffs(self, files, changeset):
210 210 for f in files[:self.maxfiles]:
211 211 yield self.t("filedifflink", node=hex(changeset), file=f)
212 212 if len(files) > self.maxfiles:
213 213 yield self.t("fileellipses")
214 214
215 215 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
216 216 if not rev:
217 217 rev = lambda x: ""
218 218 siblings = [s for s in siblings if s != nullid]
219 219 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
220 220 return
221 221 for s in siblings:
222 222 yield dict(node=hex(s), rev=rev(s), **args)
223 223
224 224 def showtag(self, t1, node=nullid, **args):
225 225 for t in self.repo.nodetags(node):
226 226 yield self.t(t1, tag=t, **args)
227 227
228 228 def diff(self, node1, node2, files):
229 229 def filterfiles(filters, files):
230 230 l = [x for x in files if x in filters]
231 231
232 232 for t in filters:
233 233 if t and t[-1] != os.sep:
234 234 t += os.sep
235 235 l += [x for x in files if x.startswith(t)]
236 236 return l
237 237
238 238 parity = [0]
239 239 def diffblock(diff, f, fn):
240 240 yield self.t("diffblock",
241 241 lines=prettyprintlines(diff),
242 242 parity=parity[0],
243 243 file=f,
244 244 filenode=hex(fn or nullid))
245 245 parity[0] = 1 - parity[0]
246 246
247 247 def prettyprintlines(diff):
248 248 for l in diff.splitlines(1):
249 249 if l.startswith('+'):
250 250 yield self.t("difflineplus", line=l)
251 251 elif l.startswith('-'):
252 252 yield self.t("difflineminus", line=l)
253 253 elif l.startswith('@'):
254 254 yield self.t("difflineat", line=l)
255 255 else:
256 256 yield self.t("diffline", line=l)
257 257
258 258 r = self.repo
259 259 cl = r.changelog
260 260 mf = r.manifest
261 261 change1 = cl.read(node1)
262 262 change2 = cl.read(node2)
263 263 mmap1 = mf.read(change1[0])
264 264 mmap2 = mf.read(change2[0])
265 265 date1 = util.datestr(change1[2])
266 266 date2 = util.datestr(change2[2])
267 267
268 268 modified, added, removed, deleted, unknown = r.changes(node1, node2)
269 269 if files:
270 270 modified, added, removed = map(lambda x: filterfiles(files, x),
271 271 (modified, added, removed))
272 272
273 diffopts = self.repo.ui.diffopts()
274 showfunc = diffopts['showfunc']
275 ignorews = diffopts['ignorews']
273 276 for f in modified:
274 277 to = r.file(f).read(mmap1[f])
275 278 tn = r.file(f).read(mmap2[f])
276 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
279 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
280 showfunc=showfunc, ignorews=ignorews), f, tn)
277 281 for f in added:
278 282 to = None
279 283 tn = r.file(f).read(mmap2[f])
280 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
284 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
285 showfunc=showfunc, ignorews=ignorews), f, tn)
281 286 for f in removed:
282 287 to = r.file(f).read(mmap1[f])
283 288 tn = None
284 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
289 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
290 showfunc=showfunc, ignorews=ignorews), f, tn)
285 291
286 292 def changelog(self, pos):
287 293 def changenav(**map):
288 294 def seq(factor=1):
289 295 yield 1 * factor
290 296 yield 3 * factor
291 297 #yield 5 * factor
292 298 for f in seq(factor * 10):
293 299 yield f
294 300
295 301 l = []
296 302 for f in seq():
297 303 if f < self.maxchanges / 2:
298 304 continue
299 305 if f > count:
300 306 break
301 307 r = "%d" % f
302 308 if pos + f < count:
303 309 l.append(("+" + r, pos + f))
304 310 if pos - f >= 0:
305 311 l.insert(0, ("-" + r, pos - f))
306 312
307 313 yield {"rev": 0, "label": "(0)"}
308 314
309 315 for label, rev in l:
310 316 yield {"label": label, "rev": rev}
311 317
312 318 yield {"label": "tip", "rev": "tip"}
313 319
314 320 def changelist(**map):
315 321 parity = (start - end) & 1
316 322 cl = self.repo.changelog
317 323 l = [] # build a list in forward order for efficiency
318 324 for i in range(start, end):
319 325 n = cl.node(i)
320 326 changes = cl.read(n)
321 327 hn = hex(n)
322 328
323 329 l.insert(0, {"parity": parity,
324 330 "author": changes[1],
325 331 "parent": self.siblings(cl.parents(n), cl.rev,
326 332 cl.rev(n) - 1),
327 333 "child": self.siblings(cl.children(n), cl.rev,
328 334 cl.rev(n) + 1),
329 335 "changelogtag": self.showtag("changelogtag",n),
330 336 "manifest": hex(changes[0]),
331 337 "desc": changes[4],
332 338 "date": changes[2],
333 339 "files": self.listfilediffs(changes[3], n),
334 340 "rev": i,
335 341 "node": hn})
336 342 parity = 1 - parity
337 343
338 344 for e in l:
339 345 yield e
340 346
341 347 cl = self.repo.changelog
342 348 mf = cl.read(cl.tip())[0]
343 349 count = cl.count()
344 350 start = max(0, pos - self.maxchanges + 1)
345 351 end = min(count, start + self.maxchanges)
346 352 pos = end - 1
347 353
348 354 yield self.t('changelog',
349 355 changenav=changenav,
350 356 manifest=hex(mf),
351 357 rev=pos, changesets=count, entries=changelist)
352 358
353 359 def search(self, query):
354 360
355 361 def changelist(**map):
356 362 cl = self.repo.changelog
357 363 count = 0
358 364 qw = query.lower().split()
359 365
360 366 def revgen():
361 367 for i in range(cl.count() - 1, 0, -100):
362 368 l = []
363 369 for j in range(max(0, i - 100), i):
364 370 n = cl.node(j)
365 371 changes = cl.read(n)
366 372 l.append((n, j, changes))
367 373 l.reverse()
368 374 for e in l:
369 375 yield e
370 376
371 377 for n, i, changes in revgen():
372 378 miss = 0
373 379 for q in qw:
374 380 if not (q in changes[1].lower() or
375 381 q in changes[4].lower() or
376 382 q in " ".join(changes[3][:20]).lower()):
377 383 miss = 1
378 384 break
379 385 if miss:
380 386 continue
381 387
382 388 count += 1
383 389 hn = hex(n)
384 390
385 391 yield self.t('searchentry',
386 392 parity=count & 1,
387 393 author=changes[1],
388 394 parent=self.siblings(cl.parents(n), cl.rev),
389 395 child=self.siblings(cl.children(n), cl.rev),
390 396 changelogtag=self.showtag("changelogtag",n),
391 397 manifest=hex(changes[0]),
392 398 desc=changes[4],
393 399 date=changes[2],
394 400 files=self.listfilediffs(changes[3], n),
395 401 rev=i,
396 402 node=hn)
397 403
398 404 if count >= self.maxchanges:
399 405 break
400 406
401 407 cl = self.repo.changelog
402 408 mf = cl.read(cl.tip())[0]
403 409
404 410 yield self.t('search',
405 411 query=query,
406 412 manifest=hex(mf),
407 413 entries=changelist)
408 414
409 415 def changeset(self, nodeid):
410 416 cl = self.repo.changelog
411 417 n = self.repo.lookup(nodeid)
412 418 nodeid = hex(n)
413 419 changes = cl.read(n)
414 420 p1 = cl.parents(n)[0]
415 421
416 422 files = []
417 423 mf = self.repo.manifest.read(changes[0])
418 424 for f in changes[3]:
419 425 files.append(self.t("filenodelink",
420 426 filenode=hex(mf.get(f, nullid)), file=f))
421 427
422 428 def diff(**map):
423 429 yield self.diff(p1, n, None)
424 430
425 431 yield self.t('changeset',
426 432 diff=diff,
427 433 rev=cl.rev(n),
428 434 node=nodeid,
429 435 parent=self.siblings(cl.parents(n), cl.rev),
430 436 child=self.siblings(cl.children(n), cl.rev),
431 437 changesettag=self.showtag("changesettag",n),
432 438 manifest=hex(changes[0]),
433 439 author=changes[1],
434 440 desc=changes[4],
435 441 date=changes[2],
436 442 files=files,
437 443 archives=self.archivelist(nodeid))
438 444
439 445 def filelog(self, f, filenode):
440 446 cl = self.repo.changelog
441 447 fl = self.repo.file(f)
442 448 filenode = hex(fl.lookup(filenode))
443 449 count = fl.count()
444 450
445 451 def entries(**map):
446 452 l = []
447 453 parity = (count - 1) & 1
448 454
449 455 for i in range(count):
450 456 n = fl.node(i)
451 457 lr = fl.linkrev(n)
452 458 cn = cl.node(lr)
453 459 cs = cl.read(cl.node(lr))
454 460
455 461 l.insert(0, {"parity": parity,
456 462 "filenode": hex(n),
457 463 "filerev": i,
458 464 "file": f,
459 465 "node": hex(cn),
460 466 "author": cs[1],
461 467 "date": cs[2],
462 468 "parent": self.siblings(fl.parents(n),
463 469 fl.rev, file=f),
464 470 "child": self.siblings(fl.children(n),
465 471 fl.rev, file=f),
466 472 "desc": cs[4]})
467 473 parity = 1 - parity
468 474
469 475 for e in l:
470 476 yield e
471 477
472 478 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
473 479
474 480 def filerevision(self, f, node):
475 481 fl = self.repo.file(f)
476 482 n = fl.lookup(node)
477 483 node = hex(n)
478 484 text = fl.read(n)
479 485 changerev = fl.linkrev(n)
480 486 cl = self.repo.changelog
481 487 cn = cl.node(changerev)
482 488 cs = cl.read(cn)
483 489 mfn = cs[0]
484 490
485 491 mt = mimetypes.guess_type(f)[0]
486 492 rawtext = text
487 493 if util.binary(text):
488 494 text = "(binary:%s)" % mt
489 495
490 496 def lines():
491 497 for l, t in enumerate(text.splitlines(1)):
492 498 yield {"line": t,
493 499 "linenumber": "% 6d" % (l + 1),
494 500 "parity": l & 1}
495 501
496 502 yield self.t("filerevision",
497 503 file=f,
498 504 filenode=node,
499 505 path=up(f),
500 506 text=lines(),
501 507 raw=rawtext,
502 508 mimetype=mt,
503 509 rev=changerev,
504 510 node=hex(cn),
505 511 manifest=hex(mfn),
506 512 author=cs[1],
507 513 date=cs[2],
508 514 parent=self.siblings(fl.parents(n), fl.rev, file=f),
509 515 child=self.siblings(fl.children(n), fl.rev, file=f),
510 516 permissions=self.repo.manifest.readflags(mfn)[f])
511 517
512 518 def fileannotate(self, f, node):
513 519 bcache = {}
514 520 ncache = {}
515 521 fl = self.repo.file(f)
516 522 n = fl.lookup(node)
517 523 node = hex(n)
518 524 changerev = fl.linkrev(n)
519 525
520 526 cl = self.repo.changelog
521 527 cn = cl.node(changerev)
522 528 cs = cl.read(cn)
523 529 mfn = cs[0]
524 530
525 531 def annotate(**map):
526 532 parity = 1
527 533 last = None
528 534 for r, l in fl.annotate(n):
529 535 try:
530 536 cnode = ncache[r]
531 537 except KeyError:
532 538 cnode = ncache[r] = self.repo.changelog.node(r)
533 539
534 540 try:
535 541 name = bcache[r]
536 542 except KeyError:
537 543 cl = self.repo.changelog.read(cnode)
538 544 bcache[r] = name = self.repo.ui.shortuser(cl[1])
539 545
540 546 if last != cnode:
541 547 parity = 1 - parity
542 548 last = cnode
543 549
544 550 yield {"parity": parity,
545 551 "node": hex(cnode),
546 552 "rev": r,
547 553 "author": name,
548 554 "file": f,
549 555 "line": l}
550 556
551 557 yield self.t("fileannotate",
552 558 file=f,
553 559 filenode=node,
554 560 annotate=annotate,
555 561 path=up(f),
556 562 rev=changerev,
557 563 node=hex(cn),
558 564 manifest=hex(mfn),
559 565 author=cs[1],
560 566 date=cs[2],
561 567 parent=self.siblings(fl.parents(n), fl.rev, file=f),
562 568 child=self.siblings(fl.children(n), fl.rev, file=f),
563 569 permissions=self.repo.manifest.readflags(mfn)[f])
564 570
565 571 def manifest(self, mnode, path):
566 572 man = self.repo.manifest
567 573 mn = man.lookup(mnode)
568 574 mnode = hex(mn)
569 575 mf = man.read(mn)
570 576 rev = man.rev(mn)
571 577 node = self.repo.changelog.node(rev)
572 578 mff = man.readflags(mn)
573 579
574 580 files = {}
575 581
576 582 p = path[1:]
577 583 l = len(p)
578 584
579 585 for f,n in mf.items():
580 586 if f[:l] != p:
581 587 continue
582 588 remain = f[l:]
583 589 if "/" in remain:
584 590 short = remain[:remain.find("/") + 1] # bleah
585 591 files[short] = (f, None)
586 592 else:
587 593 short = os.path.basename(remain)
588 594 files[short] = (f, n)
589 595
590 596 def filelist(**map):
591 597 parity = 0
592 598 fl = files.keys()
593 599 fl.sort()
594 600 for f in fl:
595 601 full, fnode = files[f]
596 602 if not fnode:
597 603 continue
598 604
599 605 yield {"file": full,
600 606 "manifest": mnode,
601 607 "filenode": hex(fnode),
602 608 "parity": parity,
603 609 "basename": f,
604 610 "permissions": mff[full]}
605 611 parity = 1 - parity
606 612
607 613 def dirlist(**map):
608 614 parity = 0
609 615 fl = files.keys()
610 616 fl.sort()
611 617 for f in fl:
612 618 full, fnode = files[f]
613 619 if fnode:
614 620 continue
615 621
616 622 yield {"parity": parity,
617 623 "path": os.path.join(path, f),
618 624 "manifest": mnode,
619 625 "basename": f[:-1]}
620 626 parity = 1 - parity
621 627
622 628 yield self.t("manifest",
623 629 manifest=mnode,
624 630 rev=rev,
625 631 node=hex(node),
626 632 path=path,
627 633 up=up(path),
628 634 fentries=filelist,
629 635 dentries=dirlist,
630 636 archives=self.archivelist(hex(node)))
631 637
632 638 def tags(self):
633 639 cl = self.repo.changelog
634 640 mf = cl.read(cl.tip())[0]
635 641
636 642 i = self.repo.tagslist()
637 643 i.reverse()
638 644
639 645 def entries(**map):
640 646 parity = 0
641 647 for k,n in i:
642 648 yield {"parity": parity,
643 649 "tag": k,
644 650 "tagmanifest": hex(cl.read(n)[0]),
645 651 "date": cl.read(n)[2],
646 652 "node": hex(n)}
647 653 parity = 1 - parity
648 654
649 655 yield self.t("tags",
650 656 manifest=hex(mf),
651 657 entries=entries)
652 658
653 659 def summary(self):
654 660 cl = self.repo.changelog
655 661 mf = cl.read(cl.tip())[0]
656 662
657 663 i = self.repo.tagslist()
658 664 i.reverse()
659 665
660 666 def tagentries(**map):
661 667 parity = 0
662 668 count = 0
663 669 for k,n in i:
664 670 if k == "tip": # skip tip
665 671 continue;
666 672
667 673 count += 1
668 674 if count > 10: # limit to 10 tags
669 675 break;
670 676
671 677 c = cl.read(n)
672 678 m = c[0]
673 679 t = c[2]
674 680
675 681 yield self.t("tagentry",
676 682 parity = parity,
677 683 tag = k,
678 684 node = hex(n),
679 685 date = t,
680 686 tagmanifest = hex(m))
681 687 parity = 1 - parity
682 688
683 689 def changelist(**map):
684 690 parity = 0
685 691 cl = self.repo.changelog
686 692 l = [] # build a list in forward order for efficiency
687 693 for i in range(start, end):
688 694 n = cl.node(i)
689 695 changes = cl.read(n)
690 696 hn = hex(n)
691 697 t = changes[2]
692 698
693 699 l.insert(0, self.t(
694 700 'shortlogentry',
695 701 parity = parity,
696 702 author = changes[1],
697 703 manifest = hex(changes[0]),
698 704 desc = changes[4],
699 705 date = t,
700 706 rev = i,
701 707 node = hn))
702 708 parity = 1 - parity
703 709
704 710 yield l
705 711
706 712 cl = self.repo.changelog
707 713 mf = cl.read(cl.tip())[0]
708 714 count = cl.count()
709 715 start = max(0, count - self.maxchanges)
710 716 end = min(count, start + self.maxchanges)
711 717 pos = end - 1
712 718
713 719 yield self.t("summary",
714 720 desc = self.repo.ui.config("web", "description", "unknown"),
715 721 owner = (self.repo.ui.config("ui", "username") or # preferred
716 722 self.repo.ui.config("web", "contact") or # deprecated
717 723 self.repo.ui.config("web", "author", "unknown")), # also
718 724 lastchange = (0, 0), # FIXME
719 725 manifest = hex(mf),
720 726 tags = tagentries,
721 727 shortlog = changelist)
722 728
723 729 def filediff(self, file, changeset):
724 730 cl = self.repo.changelog
725 731 n = self.repo.lookup(changeset)
726 732 changeset = hex(n)
727 733 p1 = cl.parents(n)[0]
728 734 cs = cl.read(n)
729 735 mf = self.repo.manifest.read(cs[0])
730 736
731 737 def diff(**map):
732 738 yield self.diff(p1, n, file)
733 739
734 740 yield self.t("filediff",
735 741 file=file,
736 742 filenode=hex(mf.get(file, nullid)),
737 743 node=changeset,
738 744 rev=self.repo.changelog.rev(n),
739 745 parent=self.siblings(cl.parents(n), cl.rev),
740 746 child=self.siblings(cl.children(n), cl.rev),
741 747 diff=diff)
742 748
743 749 def archive(self, req, cnode, type):
744 750 cs = self.repo.changelog.read(cnode)
745 751 mnode = cs[0]
746 752 mf = self.repo.manifest.read(mnode)
747 753 rev = self.repo.manifest.rev(mnode)
748 754 reponame = re.sub(r"\W+", "-", self.reponame)
749 755 name = "%s-%s/" % (reponame, short(cnode))
750 756
751 757 files = mf.keys()
752 758 files.sort()
753 759
754 760 if type == 'zip':
755 761 tmp = tempfile.mkstemp()[1]
756 762 try:
757 763 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
758 764
759 765 for f in files:
760 766 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
761 767 zf.close()
762 768
763 769 f = open(tmp, 'r')
764 770 req.httphdr('application/zip', name[:-1] + '.zip',
765 771 os.path.getsize(tmp))
766 772 req.write(f.read())
767 773 f.close()
768 774 finally:
769 775 os.unlink(tmp)
770 776
771 777 else:
772 778 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
773 779 mff = self.repo.manifest.readflags(mnode)
774 780 mtime = int(time.time())
775 781
776 782 if type == "gz":
777 783 encoding = "gzip"
778 784 else:
779 785 encoding = "x-bzip2"
780 786 req.header([('Content-type', 'application/x-tar'),
781 787 ('Content-disposition', 'attachment; filename=%s%s%s' %
782 788 (name[:-1], '.tar.', type)),
783 789 ('Content-encoding', encoding)])
784 790 for fname in files:
785 791 rcont = self.repo.file(fname).read(mf[fname])
786 792 finfo = tarfile.TarInfo(name + fname)
787 793 finfo.mtime = mtime
788 794 finfo.size = len(rcont)
789 795 finfo.mode = mff[fname] and 0755 or 0644
790 796 tf.addfile(finfo, StringIO.StringIO(rcont))
791 797 tf.close()
792 798
793 799 # add tags to things
794 800 # tags -> list of changesets corresponding to tags
795 801 # find tag, changeset, file
796 802
797 803 def run(self, req=hgrequest()):
798 804 def header(**map):
799 805 yield self.t("header", **map)
800 806
801 807 def footer(**map):
802 808 yield self.t("footer", **map)
803 809
804 810 def expand_form(form):
805 811 shortcuts = {
806 812 'cl': [('cmd', ['changelog']), ('rev', None)],
807 813 'cs': [('cmd', ['changeset']), ('node', None)],
808 814 'f': [('cmd', ['file']), ('filenode', None)],
809 815 'fl': [('cmd', ['filelog']), ('filenode', None)],
810 816 'fd': [('cmd', ['filediff']), ('node', None)],
811 817 'fa': [('cmd', ['annotate']), ('filenode', None)],
812 818 'mf': [('cmd', ['manifest']), ('manifest', None)],
813 819 'ca': [('cmd', ['archive']), ('node', None)],
814 820 'tags': [('cmd', ['tags'])],
815 821 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
816 822 }
817 823
818 824 for k in shortcuts.iterkeys():
819 825 if form.has_key(k):
820 826 for name, value in shortcuts[k]:
821 827 if value is None:
822 828 value = form[k]
823 829 form[name] = value
824 830 del form[k]
825 831
826 832 self.refresh()
827 833
828 834 expand_form(req.form)
829 835
830 836 t = self.repo.ui.config("web", "templates", templatepath())
831 837 m = os.path.join(t, "map")
832 838 style = self.repo.ui.config("web", "style", "")
833 839 if req.form.has_key('style'):
834 840 style = req.form['style'][0]
835 841 if style:
836 842 b = os.path.basename("map-" + style)
837 843 p = os.path.join(t, b)
838 844 if os.path.isfile(p):
839 845 m = p
840 846
841 847 port = req.env["SERVER_PORT"]
842 848 port = port != "80" and (":" + port) or ""
843 849 uri = req.env["REQUEST_URI"]
844 850 if "?" in uri:
845 851 uri = uri.split("?")[0]
846 852 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
847 853 if not self.reponame:
848 854 self.reponame = (self.repo.ui.config("web", "name")
849 855 or uri.strip('/') or self.repo.root)
850 856
851 857 self.t = templater(m, common_filters,
852 858 {"url": url,
853 859 "repo": self.reponame,
854 860 "header": header,
855 861 "footer": footer,
856 862 })
857 863
858 864 if not req.form.has_key('cmd'):
859 865 req.form['cmd'] = [self.t.cache['default'],]
860 866
861 867 if req.form['cmd'][0] == 'changelog':
862 868 c = self.repo.changelog.count() - 1
863 869 hi = c
864 870 if req.form.has_key('rev'):
865 871 hi = req.form['rev'][0]
866 872 try:
867 873 hi = self.repo.changelog.rev(self.repo.lookup(hi))
868 874 except hg.RepoError:
869 875 req.write(self.search(hi))
870 876 return
871 877
872 878 req.write(self.changelog(hi))
873 879
874 880 elif req.form['cmd'][0] == 'changeset':
875 881 req.write(self.changeset(req.form['node'][0]))
876 882
877 883 elif req.form['cmd'][0] == 'manifest':
878 884 req.write(self.manifest(req.form['manifest'][0], req.form['path'][0]))
879 885
880 886 elif req.form['cmd'][0] == 'tags':
881 887 req.write(self.tags())
882 888
883 889 elif req.form['cmd'][0] == 'summary':
884 890 req.write(self.summary())
885 891
886 892 elif req.form['cmd'][0] == 'filediff':
887 893 req.write(self.filediff(req.form['file'][0], req.form['node'][0]))
888 894
889 895 elif req.form['cmd'][0] == 'file':
890 896 req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0]))
891 897
892 898 elif req.form['cmd'][0] == 'annotate':
893 899 req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0]))
894 900
895 901 elif req.form['cmd'][0] == 'filelog':
896 902 req.write(self.filelog(req.form['file'][0], req.form['filenode'][0]))
897 903
898 904 elif req.form['cmd'][0] == 'heads':
899 905 req.httphdr("application/mercurial-0.1")
900 906 h = self.repo.heads()
901 907 req.write(" ".join(map(hex, h)) + "\n")
902 908
903 909 elif req.form['cmd'][0] == 'branches':
904 910 req.httphdr("application/mercurial-0.1")
905 911 nodes = []
906 912 if req.form.has_key('nodes'):
907 913 nodes = map(bin, req.form['nodes'][0].split(" "))
908 914 for b in self.repo.branches(nodes):
909 915 req.write(" ".join(map(hex, b)) + "\n")
910 916
911 917 elif req.form['cmd'][0] == 'between':
912 918 req.httphdr("application/mercurial-0.1")
913 919 nodes = []
914 920 if req.form.has_key('pairs'):
915 921 pairs = [map(bin, p.split("-"))
916 922 for p in req.form['pairs'][0].split(" ")]
917 923 for b in self.repo.between(pairs):
918 924 req.write(" ".join(map(hex, b)) + "\n")
919 925
920 926 elif req.form['cmd'][0] == 'changegroup':
921 927 req.httphdr("application/mercurial-0.1")
922 928 nodes = []
923 929 if not self.allowpull:
924 930 return
925 931
926 932 if req.form.has_key('roots'):
927 933 nodes = map(bin, req.form['roots'][0].split(" "))
928 934
929 935 z = zlib.compressobj()
930 936 f = self.repo.changegroup(nodes)
931 937 while 1:
932 938 chunk = f.read(4096)
933 939 if not chunk:
934 940 break
935 941 req.write(z.compress(chunk))
936 942
937 943 req.write(z.flush())
938 944
939 945 elif req.form['cmd'][0] == 'archive':
940 946 changeset = self.repo.lookup(req.form['node'][0])
941 947 type = req.form['type'][0]
942 948 if (type in self.archives and
943 949 self.repo.ui.configbool("web", "allow" + type, False)):
944 950 self.archive(req, changeset, type)
945 951 return
946 952
947 953 req.write(self.t("error"))
948 954
949 955 else:
950 956 req.write(self.t("error"))
951 957
952 958 def create_server(repo):
953 959
954 960 def openlog(opt, default):
955 961 if opt and opt != '-':
956 962 return open(opt, 'w')
957 963 return default
958 964
959 965 address = repo.ui.config("web", "address", "")
960 966 port = int(repo.ui.config("web", "port", 8000))
961 967 use_ipv6 = repo.ui.configbool("web", "ipv6")
962 968 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
963 969 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
964 970
965 971 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
966 972 address_family = getattr(socket, 'AF_INET6', None)
967 973
968 974 def __init__(self, *args, **kwargs):
969 975 if self.address_family is None:
970 976 raise hg.RepoError(_('IPv6 not available on this system'))
971 977 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
972 978
973 979 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
974 980 def log_error(self, format, *args):
975 981 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
976 982 self.log_date_time_string(),
977 983 format % args))
978 984
979 985 def log_message(self, format, *args):
980 986 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
981 987 self.log_date_time_string(),
982 988 format % args))
983 989
984 990 def do_POST(self):
985 991 try:
986 992 self.do_hgweb()
987 993 except socket.error, inst:
988 994 if inst[0] != errno.EPIPE:
989 995 raise
990 996
991 997 def do_GET(self):
992 998 self.do_POST()
993 999
994 1000 def do_hgweb(self):
995 1001 query = ""
996 1002 p = self.path.find("?")
997 1003 if p:
998 1004 query = self.path[p + 1:]
999 1005 query = query.replace('+', ' ')
1000 1006
1001 1007 env = {}
1002 1008 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
1003 1009 env['REQUEST_METHOD'] = self.command
1004 1010 env['SERVER_NAME'] = self.server.server_name
1005 1011 env['SERVER_PORT'] = str(self.server.server_port)
1006 1012 env['REQUEST_URI'] = "/"
1007 1013 if query:
1008 1014 env['QUERY_STRING'] = query
1009 1015 host = self.address_string()
1010 1016 if host != self.client_address[0]:
1011 1017 env['REMOTE_HOST'] = host
1012 1018 env['REMOTE_ADDR'] = self.client_address[0]
1013 1019
1014 1020 if self.headers.typeheader is None:
1015 1021 env['CONTENT_TYPE'] = self.headers.type
1016 1022 else:
1017 1023 env['CONTENT_TYPE'] = self.headers.typeheader
1018 1024 length = self.headers.getheader('content-length')
1019 1025 if length:
1020 1026 env['CONTENT_LENGTH'] = length
1021 1027 accept = []
1022 1028 for line in self.headers.getallmatchingheaders('accept'):
1023 1029 if line[:1] in "\t\n\r ":
1024 1030 accept.append(line.strip())
1025 1031 else:
1026 1032 accept = accept + line[7:].split(',')
1027 1033 env['HTTP_ACCEPT'] = ','.join(accept)
1028 1034
1029 1035 req = hgrequest(self.rfile, self.wfile, env)
1030 1036 self.send_response(200, "Script output follows")
1031 1037 hg.run(req)
1032 1038
1033 1039 hg = hgweb(repo)
1034 1040 if use_ipv6:
1035 1041 return IPv6HTTPServer((address, port), hgwebhandler)
1036 1042 else:
1037 1043 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
1038 1044
1039 1045 # This is a stopgap
1040 1046 class hgwebdir(object):
1041 1047 def __init__(self, config):
1042 1048 def cleannames(items):
1043 1049 return [(name.strip('/'), path) for name, path in items]
1044 1050
1045 1051 if type(config) == type([]):
1046 1052 self.repos = cleannames(config)
1047 1053 elif type(config) == type({}):
1048 1054 self.repos = cleannames(config.items())
1049 1055 self.repos.sort()
1050 1056 else:
1051 1057 cp = ConfigParser.SafeConfigParser()
1052 1058 cp.read(config)
1053 1059 self.repos = cleannames(cp.items("paths"))
1054 1060 self.repos.sort()
1055 1061
1056 1062 def run(self, req=hgrequest()):
1057 1063 def header(**map):
1058 1064 yield tmpl("header", **map)
1059 1065
1060 1066 def footer(**map):
1061 1067 yield tmpl("footer", **map)
1062 1068
1063 1069 m = os.path.join(templatepath(), "map")
1064 1070 tmpl = templater(m, common_filters,
1065 1071 {"header": header, "footer": footer})
1066 1072
1067 1073 def entries(**map):
1068 1074 parity = 0
1069 1075 for name, path in self.repos:
1070 1076 u = ui.ui()
1071 1077 try:
1072 1078 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1073 1079 except IOError:
1074 1080 pass
1075 1081 get = u.config
1076 1082
1077 1083 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1078 1084 .replace("//", "/"))
1079 1085
1080 1086 # update time with local timezone
1081 1087 try:
1082 1088 d = (get_mtime(path), util.makedate()[1])
1083 1089 except OSError:
1084 1090 continue
1085 1091
1086 1092 yield dict(contact=(get("ui", "username") or # preferred
1087 1093 get("web", "contact") or # deprecated
1088 1094 get("web", "author", "unknown")), # also
1089 1095 name=get("web", "name", name),
1090 1096 url=url,
1091 1097 parity=parity,
1092 1098 shortdesc=get("web", "description", "unknown"),
1093 1099 lastupdate=d)
1094 1100
1095 1101 parity = 1 - parity
1096 1102
1097 1103 virtual = req.env.get("PATH_INFO", "").strip('/')
1098 1104 if virtual:
1099 1105 real = dict(self.repos).get(virtual)
1100 1106 if real:
1101 1107 try:
1102 1108 hgweb(real).run(req)
1103 1109 except IOError, inst:
1104 1110 req.write(tmpl("error", error=inst.strerror))
1105 1111 except hg.RepoError, inst:
1106 1112 req.write(tmpl("error", error=str(inst)))
1107 1113 else:
1108 1114 req.write(tmpl("notfound", repo=virtual))
1109 1115 else:
1110 1116 req.write(tmpl("index", entries=entries))
@@ -1,62 +1,189 b''
1 1 # mdiff.py - diff and patch routines 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 import difflib, struct, bdiff, util, mpatch
8 from demandload import demandload
9 import struct, bdiff, util, mpatch
10 demandload(globals(), "re")
9 11
10 def unidiff(a, ad, b, bd, fn, r=None, text=False):
12
13 def unidiff(a, ad, b, bd, fn, r=None, text=False,
14 showfunc=False, ignorews=False):
11 15
12 16 if not a and not b: return ""
13 17 epoch = util.datestr((0, 0))
14 18
15 19 if not text and (util.binary(a) or util.binary(b)):
16 20 l = ['Binary file %s has changed\n' % fn]
17 21 elif a == None:
18 22 b = b.splitlines(1)
19 23 l1 = "--- %s\t%s\n" % ("/dev/null", epoch)
20 24 l2 = "+++ %s\t%s\n" % ("b/" + fn, bd)
21 25 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
22 26 l = [l1, l2, l3] + ["+" + e for e in b]
23 27 elif b == None:
24 28 a = a.splitlines(1)
25 29 l1 = "--- %s\t%s\n" % ("a/" + fn, ad)
26 30 l2 = "+++ %s\t%s\n" % ("/dev/null", epoch)
27 31 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
28 32 l = [l1, l2, l3] + ["-" + e for e in a]
29 33 else:
30 a = a.splitlines(1)
31 b = b.splitlines(1)
32 l = list(difflib.unified_diff(a, b, "a/" + fn, "b/" + fn))
34 al = a.splitlines(1)
35 bl = b.splitlines(1)
36 l = list(bunidiff(a, b, al, bl, "a/" + fn, "b/" + fn,
37 showfunc=showfunc, ignorews=ignorews))
33 38 if not l: return ""
34 39 # difflib uses a space, rather than a tab
35 40 l[0] = "%s\t%s\n" % (l[0][:-2], ad)
36 41 l[1] = "%s\t%s\n" % (l[1][:-2], bd)
37 42
38 43 for ln in xrange(len(l)):
39 44 if l[ln][-1] != '\n':
40 45 l[ln] += "\n\ No newline at end of file\n"
41 46
42 47 if r:
43 48 l.insert(0, "diff %s %s\n" %
44 49 (' '.join(["-r %s" % rev for rev in r]), fn))
45 50
46 51 return "".join(l)
47 52
53 # somewhat self contained replacement for difflib.unified_diff
54 # t1 and t2 are the text to be diffed
55 # l1 and l2 are the text broken up into lines
56 # header1 and header2 are the filenames for the diff output
57 # context is the number of context lines
58 # showfunc enables diff -p output
59 # ignorews ignores all whitespace changes in the diff
60 def bunidiff(t1, t2, l1, l2, header1, header2, context=3, showfunc=False,
61 ignorews=False):
62 def contextend(l, len):
63 ret = l + context
64 if ret > len:
65 ret = len
66 return ret
67
68 def contextstart(l):
69 ret = l - context
70 if ret < 0:
71 return 0
72 return ret
73
74 def yieldhunk(hunk, header):
75 if header:
76 for x in header:
77 yield x
78 (astart, a2, bstart, b2, delta) = hunk
79 aend = contextend(a2, len(l1))
80 alen = aend - astart
81 blen = b2 - bstart + aend - a2
82
83 func = ""
84 if showfunc:
85 # walk backwards from the start of the context
86 # to find a line starting with an alphanumeric char.
87 for x in xrange(astart, -1, -1):
88 t = l1[x].rstrip()
89 if funcre.match(t):
90 func = ' ' + t[:40]
91 break
92
93 yield "@@ -%d,%d +%d,%d @@%s\n" % (astart + 1, alen,
94 bstart + 1, blen, func)
95 for x in delta:
96 yield x
97 for x in xrange(a2, aend):
98 yield ' ' + l1[x]
99
100 header = [ "--- %s\t\n" % header1, "+++ %s\t\n" % header2 ]
101
102 if showfunc:
103 funcre = re.compile('\w')
104 if ignorews:
105 wsre = re.compile('[ \t]')
106
107 # bdiff.blocks gives us the matching sequences in the files. The loop
108 # below finds the spaces between those matching sequences and translates
109 # them into diff output.
110 #
111 diff = bdiff.blocks(t1, t2)
112 hunk = None
113 for i in xrange(len(diff)):
114 # The first match is special.
115 # we've either found a match starting at line 0 or a match later
116 # in the file. If it starts later, old and new below will both be
117 # empty and we'll continue to the next match.
118 if i > 0:
119 s = diff[i-1]
120 else:
121 s = [0, 0, 0, 0]
122 delta = []
123 s1 = diff[i]
124 a1 = s[1]
125 a2 = s1[0]
126 b1 = s[3]
127 b2 = s1[2]
128
129 old = l1[a1:a2]
130 new = l2[b1:b2]
131
132 # bdiff sometimes gives huge matches past eof, this check eats them,
133 # and deals with the special first match case described above
134 if not old and not new:
135 continue
136
137 if ignorews:
138 wsold = wsre.sub('', "".join(old))
139 wsnew = wsre.sub('', "".join(new))
140 if wsold == wsnew:
141 continue
142
143 astart = contextstart(a1)
144 bstart = contextstart(b1)
145 prev = None
146 if hunk:
147 # join with the previous hunk if it falls inside the context
148 if astart < hunk[1] + context + 1:
149 prev = hunk
150 astart = hunk[1]
151 bstart = hunk[3]
152 else:
153 for x in yieldhunk(hunk, header):
154 yield x
155 # we only want to yield the header if the files differ, and
156 # we only want to yield it once.
157 header = None
158 if prev:
159 # we've joined the previous hunk, record the new ending points.
160 hunk[1] = a2
161 hunk[3] = b2
162 delta = hunk[4]
163 else:
164 # create a new hunk
165 hunk = [ astart, a2, bstart, b2, delta ]
166
167 delta[len(delta):] = [ ' ' + x for x in l1[astart:a1] ]
168 delta[len(delta):] = [ '-' + x for x in old ]
169 delta[len(delta):] = [ '+' + x for x in new ]
170
171 if hunk:
172 for x in yieldhunk(hunk, header):
173 yield x
174
48 175 def patchtext(bin):
49 176 pos = 0
50 177 t = []
51 178 while pos < len(bin):
52 179 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
53 180 pos += 12
54 181 t.append(bin[pos:pos + l])
55 182 pos += l
56 183 return "".join(t)
57 184
58 185 def patch(a, bin):
59 186 return mpatch.patches(a, [bin])
60 187
61 188 patches = mpatch.patches
62 189 textdiff = bdiff.bdiff
@@ -1,155 +1,173 b''
1 1 # ui.py - user interface bits 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, ConfigParser
9 9 from i18n import gettext as _
10 10 from demandload import *
11 11 demandload(globals(), "re socket sys util")
12 12
13 13 class ui(object):
14 14 def __init__(self, verbose=False, debug=False, quiet=False,
15 15 interactive=True):
16 16 self.overlay = {}
17 17 self.cdata = ConfigParser.SafeConfigParser()
18 18 self.readconfig(util.rcpath)
19 19
20 20 self.quiet = self.configbool("ui", "quiet")
21 21 self.verbose = self.configbool("ui", "verbose")
22 22 self.debugflag = self.configbool("ui", "debug")
23 23 self.interactive = self.configbool("ui", "interactive", True)
24 24
25 25 self.updateopts(verbose, debug, quiet, interactive)
26 self.diffcache = None
26 27
27 28 def updateopts(self, verbose=False, debug=False, quiet=False,
28 29 interactive=True):
29 30 self.quiet = (self.quiet or quiet) and not verbose and not debug
30 31 self.verbose = (self.verbose or verbose) or debug
31 32 self.debugflag = (self.debugflag or debug)
32 33 self.interactive = (self.interactive and interactive)
33 34
34 35 def readconfig(self, fn):
35 36 if isinstance(fn, basestring):
36 37 fn = [fn]
37 38 for f in fn:
38 39 try:
39 40 self.cdata.read(f)
40 41 except ConfigParser.ParsingError, inst:
41 42 raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
42 43
43 44 def setconfig(self, section, name, val):
44 45 self.overlay[(section, name)] = val
45 46
46 47 def config(self, section, name, default=None):
47 48 if self.overlay.has_key((section, name)):
48 49 return self.overlay[(section, name)]
49 50 if self.cdata.has_option(section, name):
50 51 return self.cdata.get(section, name)
51 52 return default
52 53
53 54 def configbool(self, section, name, default=False):
54 55 if self.overlay.has_key((section, name)):
55 56 return self.overlay[(section, name)]
56 57 if self.cdata.has_option(section, name):
57 58 return self.cdata.getboolean(section, name)
58 59 return default
59 60
60 61 def configitems(self, section):
61 62 if self.cdata.has_section(section):
62 63 return self.cdata.items(section)
63 64 return []
64 65
65 66 def walkconfig(self):
66 67 seen = {}
67 68 for (section, name), value in self.overlay.iteritems():
68 69 yield section, name, value
69 70 seen[section, name] = 1
70 71 for section in self.cdata.sections():
71 72 for name, value in self.cdata.items(section):
72 73 if (section, name) in seen: continue
73 74 yield section, name, value.replace('\n', '\\n')
74 75 seen[section, name] = 1
75 76
76 77 def extensions(self):
77 78 return self.configitems("extensions")
78 79
80 def diffopts(self):
81 if self.diffcache:
82 return self.diffcache
83 ret = { 'showfunc' : True, 'ignorews' : False}
84 for x in self.configitems("diff"):
85 k = x[0].lower()
86 v = x[1]
87 if v:
88 v = v.lower()
89 if v == 'true':
90 value = True
91 else:
92 value = False
93 ret[k] = value
94 self.diffcache = ret
95 return ret
96
79 97 def username(self):
80 98 return (os.environ.get("HGUSER") or
81 99 self.config("ui", "username") or
82 100 os.environ.get("EMAIL") or
83 101 (os.environ.get("LOGNAME",
84 102 os.environ.get("USERNAME", "unknown"))
85 103 + '@' + socket.getfqdn()))
86 104
87 105 def shortuser(self, user):
88 106 """Return a short representation of a user name or email address."""
89 107 if not self.verbose:
90 108 f = user.find('@')
91 109 if f >= 0:
92 110 user = user[:f]
93 111 f = user.find('<')
94 112 if f >= 0:
95 113 user = user[f+1:]
96 114 return user
97 115
98 116 def expandpath(self, loc, root=""):
99 117 paths = {}
100 118 for name, path in self.configitems("paths"):
101 119 m = path.find("://")
102 120 if m == -1:
103 121 path = os.path.join(root, path)
104 122 paths[name] = path
105 123
106 124 return paths.get(loc, loc)
107 125
108 126 def write(self, *args):
109 127 for a in args:
110 128 sys.stdout.write(str(a))
111 129
112 130 def write_err(self, *args):
113 131 if not sys.stdout.closed: sys.stdout.flush()
114 132 for a in args:
115 133 sys.stderr.write(str(a))
116 134
117 135 def readline(self):
118 136 return sys.stdin.readline()[:-1]
119 137 def prompt(self, msg, pat, default="y"):
120 138 if not self.interactive: return default
121 139 while 1:
122 140 self.write(msg, " ")
123 141 r = self.readline()
124 142 if re.match(pat, r):
125 143 return r
126 144 else:
127 145 self.write(_("unrecognized response\n"))
128 146 def status(self, *msg):
129 147 if not self.quiet: self.write(*msg)
130 148 def warn(self, *msg):
131 149 self.write_err(*msg)
132 150 def note(self, *msg):
133 151 if self.verbose: self.write(*msg)
134 152 def debug(self, *msg):
135 153 if self.debugflag: self.write(*msg)
136 154 def edit(self, text):
137 155 import tempfile
138 156 (fd, name) = tempfile.mkstemp("hg")
139 157 f = os.fdopen(fd, "w")
140 158 f.write(text)
141 159 f.close()
142 160
143 161 editor = (os.environ.get("HGEDITOR") or
144 162 self.config("ui", "editor") or
145 163 os.environ.get("EDITOR", "vi"))
146 164
147 165 os.environ["HGUSER"] = self.username()
148 166 util.system("%s \"%s\"" % (editor, name), errprefix=_("edit failed"))
149 167
150 168 t = open(name).read()
151 169 t = re.sub("(?m)^HG:.*\n", "", t)
152 170
153 171 os.unlink(name)
154 172
155 173 return t
@@ -1,26 +1,26 b''
1 1 1:f4d7a8c73d23
2 2 0:232e179b3f29
3 3 f4d7a8c73d23 tip
4 4 232e179b3f29
5 5 232e179b3f29+
6 6 232e179b3f29
7 7 f4d7a8c73d23 tip
8 8 merge: warning: conflicts during merge
9 9 merging file1
10 10 merging file1 failed!
11 11 diff -r f4d7a8c73d23 file1
12 12 --- a/file1
13 13 +++ b/file1
14 @@ -1,3 +1,7 @@
14 @@ -1,3 +1,7 @@ added file1
15 15 added file1
16 16 another line of text
17 17 +<<<<<<<
18 18 +changed file1 different
19 19 +=======
20 20 changed file1
21 21 +>>>>>>>
22 22 M file1
23 23 f4d7a8c73d23+ tip
24 24 f4d7a8c73d23 tip
25 25 f4d7a8c73d23 tip
26 26 f4d7a8c73d23 tip
@@ -1,52 +1,52 b''
1 1 adding a
2 2 diff -r c19d34741b0a a
3 3 --- a/a
4 4 +++ b/a
5 @@ -1,1 +1,1 @@
5 @@ -1,1 +1,1 @@ a
6 6 -a
7 7 +abc
8 8 adding b
9 9 M a
10 10 resolving manifests
11 11 force None allow None moddirstate True linear True
12 12 ancestor a0c8bcbbb45c local a0c8bcbbb45c remote 1165e8bd193e
13 13 a versions differ, resolve
14 14 remote created b
15 15 getting b
16 16 merging a
17 17 resolving a
18 18 file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2
19 19 abort: outstanding uncommited changes
20 20 failed
21 21 resolving manifests
22 22 force None allow 1 moddirstate True linear True
23 23 ancestor 1165e8bd193e local 1165e8bd193e remote 1165e8bd193e
24 24 changeset: 1:1e71731e6fbb
25 25 tag: tip
26 26 user: test
27 27 date: Thu Jan 1 00:00:00 1970 +0000
28 28 summary: 2
29 29
30 30 changeset: 1:1e71731e6fbb5b35fae293120dea6964371c13c6
31 31 tag: tip
32 32 user: test
33 33 date: Thu Jan 1 00:00:00 1970 +0000
34 34 files: a b
35 35 description:
36 36 2
37 37
38 38
39 39 changeset: 0:c19d34741b0a4ced8e4ba74bb834597d5193851e
40 40 user: test
41 41 date: Thu Jan 1 00:00:00 1970 +0000
42 42 files: a
43 43 description:
44 44 1
45 45
46 46
47 47 diff -r 1e71731e6fbb a
48 48 --- a/a
49 49 +++ b/a
50 @@ -1,1 +1,1 @@
50 @@ -1,1 +1,1 @@ a2
51 51 -a2
52 52 +abc
General Comments 0
You need to be logged in to leave comments. Login now