##// END OF EJS Templates
Spelling fix: "commited" -> "committed"
mcmillen@cs.cmu.edu -
r1995:2da2d468 default
parent child Browse files
Show More
@@ -1,3309 +1,3309 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 templater bundlerepo")
13 13 demandload(globals(), "fnmatch hgweb mdiff random signal tempfile time")
14 14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
15 15 demandload(globals(), "changegroup")
16 16
17 17 class UnknownCommand(Exception):
18 18 """Exception raised if command is not in the command table."""
19 19 class AmbiguousCommand(Exception):
20 20 """Exception raised if command shortcut matches more than one command."""
21 21
22 22 def filterfiles(filters, files):
23 23 l = [x for x in files if x in filters]
24 24
25 25 for t in filters:
26 26 if t and t[-1] != "/":
27 27 t += "/"
28 28 l += [x for x in files if x.startswith(t)]
29 29 return l
30 30
31 31 def relpath(repo, args):
32 32 cwd = repo.getcwd()
33 33 if cwd:
34 34 return [util.normpath(os.path.join(cwd, x)) for x in args]
35 35 return args
36 36
37 37 def matchpats(repo, pats=[], opts={}, head=''):
38 38 cwd = repo.getcwd()
39 39 if not pats and cwd:
40 40 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
41 41 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
42 42 cwd = ''
43 43 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
44 44 opts.get('exclude'), head)
45 45
46 46 def makewalk(repo, pats, opts, node=None, head=''):
47 47 files, matchfn, anypats = matchpats(repo, pats, opts, head)
48 48 exact = dict(zip(files, files))
49 49 def walk():
50 50 for src, fn in repo.walk(node=node, files=files, match=matchfn):
51 51 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
52 52 return files, matchfn, walk()
53 53
54 54 def walk(repo, pats, opts, node=None, head=''):
55 55 files, matchfn, results = makewalk(repo, pats, opts, node, head)
56 56 for r in results:
57 57 yield r
58 58
59 59 def walkchangerevs(ui, repo, pats, opts):
60 60 '''Iterate over files and the revs they changed in.
61 61
62 62 Callers most commonly need to iterate backwards over the history
63 63 it is interested in. Doing so has awful (quadratic-looking)
64 64 performance, so we use iterators in a "windowed" way.
65 65
66 66 We walk a window of revisions in the desired order. Within the
67 67 window, we first walk forwards to gather data, then in the desired
68 68 order (usually backwards) to display it.
69 69
70 70 This function returns an (iterator, getchange, matchfn) tuple. The
71 71 getchange function returns the changelog entry for a numeric
72 72 revision. The iterator yields 3-tuples. They will be of one of
73 73 the following forms:
74 74
75 75 "window", incrementing, lastrev: stepping through a window,
76 76 positive if walking forwards through revs, last rev in the
77 77 sequence iterated over - use to reset state for the current window
78 78
79 79 "add", rev, fns: out-of-order traversal of the given file names
80 80 fns, which changed during revision rev - use to gather data for
81 81 possible display
82 82
83 83 "iter", rev, None: in-order traversal of the revs earlier iterated
84 84 over with "add" - use to display data'''
85 85
86 86 def increasing_windows(start, end, windowsize=8, sizelimit=512):
87 87 if start < end:
88 88 while start < end:
89 89 yield start, min(windowsize, end-start)
90 90 start += windowsize
91 91 if windowsize < sizelimit:
92 92 windowsize *= 2
93 93 else:
94 94 while start > end:
95 95 yield start, min(windowsize, start-end-1)
96 96 start -= windowsize
97 97 if windowsize < sizelimit:
98 98 windowsize *= 2
99 99
100 100
101 101 files, matchfn, anypats = matchpats(repo, pats, opts)
102 102
103 103 if repo.changelog.count() == 0:
104 104 return [], False, matchfn
105 105
106 106 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
107 107 wanted = {}
108 108 slowpath = anypats
109 109 fncache = {}
110 110
111 111 chcache = {}
112 112 def getchange(rev):
113 113 ch = chcache.get(rev)
114 114 if ch is None:
115 115 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
116 116 return ch
117 117
118 118 if not slowpath and not files:
119 119 # No files, no patterns. Display all revs.
120 120 wanted = dict(zip(revs, revs))
121 121 if not slowpath:
122 122 # Only files, no patterns. Check the history of each file.
123 123 def filerevgen(filelog):
124 124 for i, window in increasing_windows(filelog.count()-1, -1):
125 125 revs = []
126 126 for j in xrange(i - window, i + 1):
127 127 revs.append(filelog.linkrev(filelog.node(j)))
128 128 revs.reverse()
129 129 for rev in revs:
130 130 yield rev
131 131
132 132 minrev, maxrev = min(revs), max(revs)
133 133 for file_ in files:
134 134 filelog = repo.file(file_)
135 135 # A zero count may be a directory or deleted file, so
136 136 # try to find matching entries on the slow path.
137 137 if filelog.count() == 0:
138 138 slowpath = True
139 139 break
140 140 for rev in filerevgen(filelog):
141 141 if rev <= maxrev:
142 142 if rev < minrev:
143 143 break
144 144 fncache.setdefault(rev, [])
145 145 fncache[rev].append(file_)
146 146 wanted[rev] = 1
147 147 if slowpath:
148 148 # The slow path checks files modified in every changeset.
149 149 def changerevgen():
150 150 for i, window in increasing_windows(repo.changelog.count()-1, -1):
151 151 for j in xrange(i - window, i + 1):
152 152 yield j, getchange(j)[3]
153 153
154 154 for rev, changefiles in changerevgen():
155 155 matches = filter(matchfn, changefiles)
156 156 if matches:
157 157 fncache[rev] = matches
158 158 wanted[rev] = 1
159 159
160 160 def iterate():
161 161 for i, window in increasing_windows(0, len(revs)):
162 162 yield 'window', revs[0] < revs[-1], revs[-1]
163 163 nrevs = [rev for rev in revs[i:i+window]
164 164 if rev in wanted]
165 165 srevs = list(nrevs)
166 166 srevs.sort()
167 167 for rev in srevs:
168 168 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
169 169 yield 'add', rev, fns
170 170 for rev in nrevs:
171 171 yield 'iter', rev, None
172 172 return iterate(), getchange, matchfn
173 173
174 174 revrangesep = ':'
175 175
176 176 def revrange(ui, repo, revs, revlog=None):
177 177 """Yield revision as strings from a list of revision specifications."""
178 178 if revlog is None:
179 179 revlog = repo.changelog
180 180 revcount = revlog.count()
181 181 def fix(val, defval):
182 182 if not val:
183 183 return defval
184 184 try:
185 185 num = int(val)
186 186 if str(num) != val:
187 187 raise ValueError
188 188 if num < 0:
189 189 num += revcount
190 190 if num < 0:
191 191 num = 0
192 192 elif num >= revcount:
193 193 raise ValueError
194 194 except ValueError:
195 195 try:
196 196 num = repo.changelog.rev(repo.lookup(val))
197 197 except KeyError:
198 198 try:
199 199 num = revlog.rev(revlog.lookup(val))
200 200 except KeyError:
201 201 raise util.Abort(_('invalid revision identifier %s'), val)
202 202 return num
203 203 seen = {}
204 204 for spec in revs:
205 205 if spec.find(revrangesep) >= 0:
206 206 start, end = spec.split(revrangesep, 1)
207 207 start = fix(start, 0)
208 208 end = fix(end, revcount - 1)
209 209 step = start > end and -1 or 1
210 210 for rev in xrange(start, end+step, step):
211 211 if rev in seen:
212 212 continue
213 213 seen[rev] = 1
214 214 yield str(rev)
215 215 else:
216 216 rev = fix(spec, None)
217 217 if rev in seen:
218 218 continue
219 219 seen[rev] = 1
220 220 yield str(rev)
221 221
222 222 def make_filename(repo, r, pat, node=None,
223 223 total=None, seqno=None, revwidth=None, pathname=None):
224 224 node_expander = {
225 225 'H': lambda: hex(node),
226 226 'R': lambda: str(r.rev(node)),
227 227 'h': lambda: short(node),
228 228 }
229 229 expander = {
230 230 '%': lambda: '%',
231 231 'b': lambda: os.path.basename(repo.root),
232 232 }
233 233
234 234 try:
235 235 if node:
236 236 expander.update(node_expander)
237 237 if node and revwidth is not None:
238 238 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
239 239 if total is not None:
240 240 expander['N'] = lambda: str(total)
241 241 if seqno is not None:
242 242 expander['n'] = lambda: str(seqno)
243 243 if total is not None and seqno is not None:
244 244 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
245 245 if pathname is not None:
246 246 expander['s'] = lambda: os.path.basename(pathname)
247 247 expander['d'] = lambda: os.path.dirname(pathname) or '.'
248 248 expander['p'] = lambda: pathname
249 249
250 250 newname = []
251 251 patlen = len(pat)
252 252 i = 0
253 253 while i < patlen:
254 254 c = pat[i]
255 255 if c == '%':
256 256 i += 1
257 257 c = pat[i]
258 258 c = expander[c]()
259 259 newname.append(c)
260 260 i += 1
261 261 return ''.join(newname)
262 262 except KeyError, inst:
263 263 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
264 264 inst.args[0])
265 265
266 266 def make_file(repo, r, pat, node=None,
267 267 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
268 268 if not pat or pat == '-':
269 269 return 'w' in mode and sys.stdout or sys.stdin
270 270 if hasattr(pat, 'write') and 'w' in mode:
271 271 return pat
272 272 if hasattr(pat, 'read') and 'r' in mode:
273 273 return pat
274 274 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
275 275 pathname),
276 276 mode)
277 277
278 278 def write_bundle(cg, filename=None, compress=True):
279 279 """Write a bundle file and return its filename.
280 280
281 281 Existing files will not be overwritten.
282 282 If no filename is specified, a temporary file is created.
283 283 bz2 compression can be turned off.
284 284 The bundle file will be deleted in case of errors.
285 285 """
286 286 class nocompress(object):
287 287 def compress(self, x):
288 288 return x
289 289 def flush(self):
290 290 return ""
291 291
292 292 fh = None
293 293 cleanup = None
294 294 try:
295 295 if filename:
296 296 if os.path.exists(filename):
297 297 raise util.Abort(_("file '%s' already exists"), filename)
298 298 fh = open(filename, "wb")
299 299 else:
300 300 fd, filename = tempfile.mkstemp(suffix=".hg", prefix="hg-bundle-")
301 301 fh = os.fdopen(fd, "wb")
302 302 cleanup = filename
303 303
304 304 if compress:
305 305 fh.write("HG10")
306 306 z = bz2.BZ2Compressor(9)
307 307 else:
308 308 fh.write("HG10UN")
309 309 z = nocompress()
310 310 # parse the changegroup data, otherwise we will block
311 311 # in case of sshrepo because we don't know the end of the stream
312 312
313 313 # an empty chunkiter is the end of the changegroup
314 314 empty = False
315 315 while not empty:
316 316 empty = True
317 317 for chunk in changegroup.chunkiter(cg):
318 318 empty = False
319 319 fh.write(z.compress(changegroup.genchunk(chunk)))
320 320 fh.write(z.compress(changegroup.closechunk()))
321 321 fh.write(z.flush())
322 322 cleanup = None
323 323 return filename
324 324 finally:
325 325 if fh is not None:
326 326 fh.close()
327 327 if cleanup is not None:
328 328 os.unlink(cleanup)
329 329
330 330 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
331 331 changes=None, text=False, opts={}):
332 332 if not node1:
333 333 node1 = repo.dirstate.parents()[0]
334 334 # reading the data for node1 early allows it to play nicely
335 335 # with repo.changes and the revlog cache.
336 336 change = repo.changelog.read(node1)
337 337 mmap = repo.manifest.read(change[0])
338 338 date1 = util.datestr(change[2])
339 339
340 340 if not changes:
341 341 changes = repo.changes(node1, node2, files, match=match)
342 342 modified, added, removed, deleted, unknown = changes
343 343 if files:
344 344 modified, added, removed = map(lambda x: filterfiles(files, x),
345 345 (modified, added, removed))
346 346
347 347 if not modified and not added and not removed:
348 348 return
349 349
350 350 if node2:
351 351 change = repo.changelog.read(node2)
352 352 mmap2 = repo.manifest.read(change[0])
353 353 date2 = util.datestr(change[2])
354 354 def read(f):
355 355 return repo.file(f).read(mmap2[f])
356 356 else:
357 357 date2 = util.datestr()
358 358 def read(f):
359 359 return repo.wread(f)
360 360
361 361 if ui.quiet:
362 362 r = None
363 363 else:
364 364 hexfunc = ui.verbose and hex or short
365 365 r = [hexfunc(node) for node in [node1, node2] if node]
366 366
367 367 diffopts = ui.diffopts()
368 368 showfunc = opts.get('show_function') or diffopts['showfunc']
369 369 ignorews = opts.get('ignore_all_space') or diffopts['ignorews']
370 370 for f in modified:
371 371 to = None
372 372 if f in mmap:
373 373 to = repo.file(f).read(mmap[f])
374 374 tn = read(f)
375 375 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
376 376 showfunc=showfunc, ignorews=ignorews))
377 377 for f in added:
378 378 to = None
379 379 tn = read(f)
380 380 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
381 381 showfunc=showfunc, ignorews=ignorews))
382 382 for f in removed:
383 383 to = repo.file(f).read(mmap[f])
384 384 tn = None
385 385 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
386 386 showfunc=showfunc, ignorews=ignorews))
387 387
388 388 def trimuser(ui, name, rev, revcache):
389 389 """trim the name of the user who committed a change"""
390 390 user = revcache.get(rev)
391 391 if user is None:
392 392 user = revcache[rev] = ui.shortuser(name)
393 393 return user
394 394
395 395 class changeset_templater(object):
396 396 '''use templater module to format changeset information.'''
397 397
398 398 def __init__(self, ui, repo, mapfile):
399 399 self.t = templater.templater(mapfile, templater.common_filters,
400 400 cache={'parent': '{rev}:{node|short} ',
401 401 'manifest': '{rev}:{node|short}'})
402 402 self.ui = ui
403 403 self.repo = repo
404 404
405 405 def use_template(self, t):
406 406 '''set template string to use'''
407 407 self.t.cache['changeset'] = t
408 408
409 409 def write(self, thing):
410 410 '''write expanded template.
411 411 uses in-order recursive traverse of iterators.'''
412 412 for t in thing:
413 413 if hasattr(t, '__iter__'):
414 414 self.write(t)
415 415 else:
416 416 self.ui.write(t)
417 417
418 418 def show(self, rev=0, changenode=None, brinfo=None):
419 419 '''show a single changeset or file revision'''
420 420 log = self.repo.changelog
421 421 if changenode is None:
422 422 changenode = log.node(rev)
423 423 elif not rev:
424 424 rev = log.rev(changenode)
425 425
426 426 changes = log.read(changenode)
427 427
428 428 def showlist(name, values, plural=None, **args):
429 429 '''expand set of values.
430 430 name is name of key in template map.
431 431 values is list of strings or dicts.
432 432 plural is plural of name, if not simply name + 's'.
433 433
434 434 expansion works like this, given name 'foo'.
435 435
436 436 if values is empty, expand 'no_foos'.
437 437
438 438 if 'foo' not in template map, return values as a string,
439 439 joined by space.
440 440
441 441 expand 'start_foos'.
442 442
443 443 for each value, expand 'foo'. if 'last_foo' in template
444 444 map, expand it instead of 'foo' for last key.
445 445
446 446 expand 'end_foos'.
447 447 '''
448 448 if plural: names = plural
449 449 else: names = name + 's'
450 450 if not values:
451 451 noname = 'no_' + names
452 452 if noname in self.t:
453 453 yield self.t(noname, **args)
454 454 return
455 455 if name not in self.t:
456 456 if isinstance(values[0], str):
457 457 yield ' '.join(values)
458 458 else:
459 459 for v in values:
460 460 yield dict(v, **args)
461 461 return
462 462 startname = 'start_' + names
463 463 if startname in self.t:
464 464 yield self.t(startname, **args)
465 465 vargs = args.copy()
466 466 def one(v, tag=name):
467 467 try:
468 468 vargs.update(v)
469 469 except (AttributeError, ValueError):
470 470 try:
471 471 for a, b in v:
472 472 vargs[a] = b
473 473 except ValueError:
474 474 vargs[name] = v
475 475 return self.t(tag, **vargs)
476 476 lastname = 'last_' + name
477 477 if lastname in self.t:
478 478 last = values.pop()
479 479 else:
480 480 last = None
481 481 for v in values:
482 482 yield one(v)
483 483 if last is not None:
484 484 yield one(last, tag=lastname)
485 485 endname = 'end_' + names
486 486 if endname in self.t:
487 487 yield self.t(endname, **args)
488 488
489 489 if brinfo:
490 490 def showbranches(**args):
491 491 if changenode in brinfo:
492 492 for x in showlist('branch', brinfo[changenode],
493 493 plural='branches', **args):
494 494 yield x
495 495 else:
496 496 showbranches = ''
497 497
498 498 if self.ui.debugflag:
499 499 def showmanifest(**args):
500 500 args = args.copy()
501 501 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
502 502 node=hex(changes[0])))
503 503 yield self.t('manifest', **args)
504 504 else:
505 505 showmanifest = ''
506 506
507 507 def showparents(**args):
508 508 parents = [[('rev', log.rev(p)), ('node', hex(p))]
509 509 for p in log.parents(changenode)
510 510 if self.ui.debugflag or p != nullid]
511 511 if (not self.ui.debugflag and len(parents) == 1 and
512 512 parents[0][0][1] == rev - 1):
513 513 return
514 514 for x in showlist('parent', parents, **args):
515 515 yield x
516 516
517 517 def showtags(**args):
518 518 for x in showlist('tag', self.repo.nodetags(changenode), **args):
519 519 yield x
520 520
521 521 if self.ui.debugflag:
522 522 files = self.repo.changes(log.parents(changenode)[0], changenode)
523 523 def showfiles(**args):
524 524 for x in showlist('file', files[0], **args): yield x
525 525 def showadds(**args):
526 526 for x in showlist('file_add', files[1], **args): yield x
527 527 def showdels(**args):
528 528 for x in showlist('file_del', files[2], **args): yield x
529 529 else:
530 530 def showfiles(**args):
531 531 for x in showlist('file', changes[3], **args): yield x
532 532 showadds = ''
533 533 showdels = ''
534 534
535 535 props = {
536 536 'author': changes[1],
537 537 'branches': showbranches,
538 538 'date': changes[2],
539 539 'desc': changes[4],
540 540 'file_adds': showadds,
541 541 'file_dels': showdels,
542 542 'files': showfiles,
543 543 'manifest': showmanifest,
544 544 'node': hex(changenode),
545 545 'parents': showparents,
546 546 'rev': rev,
547 547 'tags': showtags,
548 548 }
549 549
550 550 try:
551 551 if self.ui.debugflag and 'changeset_debug' in self.t:
552 552 key = 'changeset_debug'
553 553 elif self.ui.quiet and 'changeset_quiet' in self.t:
554 554 key = 'changeset_quiet'
555 555 elif self.ui.verbose and 'changeset_verbose' in self.t:
556 556 key = 'changeset_verbose'
557 557 else:
558 558 key = 'changeset'
559 559 self.write(self.t(key, **props))
560 560 except KeyError, inst:
561 561 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
562 562 inst.args[0]))
563 563 except SyntaxError, inst:
564 564 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
565 565
566 566 class changeset_printer(object):
567 567 '''show changeset information when templating not requested.'''
568 568
569 569 def __init__(self, ui, repo):
570 570 self.ui = ui
571 571 self.repo = repo
572 572
573 573 def show(self, rev=0, changenode=None, brinfo=None):
574 574 '''show a single changeset or file revision'''
575 575 log = self.repo.changelog
576 576 if changenode is None:
577 577 changenode = log.node(rev)
578 578 elif not rev:
579 579 rev = log.rev(changenode)
580 580
581 581 if self.ui.quiet:
582 582 self.ui.write("%d:%s\n" % (rev, short(changenode)))
583 583 return
584 584
585 585 changes = log.read(changenode)
586 586 date = util.datestr(changes[2])
587 587
588 588 parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p))
589 589 for p in log.parents(changenode)
590 590 if self.ui.debugflag or p != nullid]
591 591 if (not self.ui.debugflag and len(parents) == 1 and
592 592 parents[0][0] == rev-1):
593 593 parents = []
594 594
595 595 if self.ui.verbose:
596 596 self.ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
597 597 else:
598 598 self.ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
599 599
600 600 for tag in self.repo.nodetags(changenode):
601 601 self.ui.status(_("tag: %s\n") % tag)
602 602 for parent in parents:
603 603 self.ui.write(_("parent: %d:%s\n") % parent)
604 604
605 605 if brinfo and changenode in brinfo:
606 606 br = brinfo[changenode]
607 607 self.ui.write(_("branch: %s\n") % " ".join(br))
608 608
609 609 self.ui.debug(_("manifest: %d:%s\n") %
610 610 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
611 611 self.ui.status(_("user: %s\n") % changes[1])
612 612 self.ui.status(_("date: %s\n") % date)
613 613
614 614 if self.ui.debugflag:
615 615 files = self.repo.changes(log.parents(changenode)[0], changenode)
616 616 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
617 617 files):
618 618 if value:
619 619 self.ui.note("%-12s %s\n" % (key, " ".join(value)))
620 620 else:
621 621 self.ui.note(_("files: %s\n") % " ".join(changes[3]))
622 622
623 623 description = changes[4].strip()
624 624 if description:
625 625 if self.ui.verbose:
626 626 self.ui.status(_("description:\n"))
627 627 self.ui.status(description)
628 628 self.ui.status("\n\n")
629 629 else:
630 630 self.ui.status(_("summary: %s\n") %
631 631 description.splitlines()[0])
632 632 self.ui.status("\n")
633 633
634 634 def show_changeset(ui, repo, opts):
635 635 '''show one changeset. uses template or regular display. caller
636 636 can pass in 'style' and 'template' options in opts.'''
637 637
638 638 tmpl = opts.get('template')
639 639 if tmpl:
640 640 tmpl = templater.parsestring(tmpl, quoted=False)
641 641 else:
642 642 tmpl = ui.config('ui', 'logtemplate')
643 643 if tmpl: tmpl = templater.parsestring(tmpl)
644 644 mapfile = opts.get('style') or ui.config('ui', 'style')
645 645 if tmpl or mapfile:
646 646 if mapfile:
647 647 if not os.path.isfile(mapfile):
648 648 mapname = templater.templatepath('map-cmdline.' + mapfile)
649 649 if not mapname: mapname = templater.templatepath(mapfile)
650 650 if mapname: mapfile = mapname
651 651 try:
652 652 t = changeset_templater(ui, repo, mapfile)
653 653 except SyntaxError, inst:
654 654 raise util.Abort(inst.args[0])
655 655 if tmpl: t.use_template(tmpl)
656 656 return t
657 657 return changeset_printer(ui, repo)
658 658
659 659 def show_version(ui):
660 660 """output version and copyright information"""
661 661 ui.write(_("Mercurial Distributed SCM (version %s)\n")
662 662 % version.get_version())
663 663 ui.status(_(
664 664 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
665 665 "This is free software; see the source for copying conditions. "
666 666 "There is NO\nwarranty; "
667 667 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
668 668 ))
669 669
670 670 def help_(ui, cmd=None, with_version=False):
671 671 """show help for a given command or all commands"""
672 672 option_lists = []
673 673 if cmd and cmd != 'shortlist':
674 674 if with_version:
675 675 show_version(ui)
676 676 ui.write('\n')
677 677 aliases, i = find(cmd)
678 678 # synopsis
679 679 ui.write("%s\n\n" % i[2])
680 680
681 681 # description
682 682 doc = i[0].__doc__
683 683 if not doc:
684 684 doc = _("(No help text available)")
685 685 if ui.quiet:
686 686 doc = doc.splitlines(0)[0]
687 687 ui.write("%s\n" % doc.rstrip())
688 688
689 689 if not ui.quiet:
690 690 # aliases
691 691 if len(aliases) > 1:
692 692 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
693 693
694 694 # options
695 695 if i[1]:
696 696 option_lists.append(("options", i[1]))
697 697
698 698 else:
699 699 # program name
700 700 if ui.verbose or with_version:
701 701 show_version(ui)
702 702 else:
703 703 ui.status(_("Mercurial Distributed SCM\n"))
704 704 ui.status('\n')
705 705
706 706 # list of commands
707 707 if cmd == "shortlist":
708 708 ui.status(_('basic commands (use "hg help" '
709 709 'for the full list or option "-v" for details):\n\n'))
710 710 elif ui.verbose:
711 711 ui.status(_('list of commands:\n\n'))
712 712 else:
713 713 ui.status(_('list of commands (use "hg help -v" '
714 714 'to show aliases and global options):\n\n'))
715 715
716 716 h = {}
717 717 cmds = {}
718 718 for c, e in table.items():
719 719 f = c.split("|")[0]
720 720 if cmd == "shortlist" and not f.startswith("^"):
721 721 continue
722 722 f = f.lstrip("^")
723 723 if not ui.debugflag and f.startswith("debug"):
724 724 continue
725 725 doc = e[0].__doc__
726 726 if not doc:
727 727 doc = _("(No help text available)")
728 728 h[f] = doc.splitlines(0)[0].rstrip()
729 729 cmds[f] = c.lstrip("^")
730 730
731 731 fns = h.keys()
732 732 fns.sort()
733 733 m = max(map(len, fns))
734 734 for f in fns:
735 735 if ui.verbose:
736 736 commands = cmds[f].replace("|",", ")
737 737 ui.write(" %s:\n %s\n"%(commands, h[f]))
738 738 else:
739 739 ui.write(' %-*s %s\n' % (m, f, h[f]))
740 740
741 741 # global options
742 742 if ui.verbose:
743 743 option_lists.append(("global options", globalopts))
744 744
745 745 # list all option lists
746 746 opt_output = []
747 747 for title, options in option_lists:
748 748 opt_output.append(("\n%s:\n" % title, None))
749 749 for shortopt, longopt, default, desc in options:
750 750 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
751 751 longopt and " --%s" % longopt),
752 752 "%s%s" % (desc,
753 753 default
754 754 and _(" (default: %s)") % default
755 755 or "")))
756 756
757 757 if opt_output:
758 758 opts_len = max([len(line[0]) for line in opt_output if line[1]])
759 759 for first, second in opt_output:
760 760 if second:
761 761 ui.write(" %-*s %s\n" % (opts_len, first, second))
762 762 else:
763 763 ui.write("%s\n" % first)
764 764
765 765 # Commands start here, listed alphabetically
766 766
767 767 def add(ui, repo, *pats, **opts):
768 768 """add the specified files on the next commit
769 769
770 770 Schedule files to be version controlled and added to the repository.
771 771
772 772 The files will be added to the repository at the next commit.
773 773
774 774 If no names are given, add all files in the repository.
775 775 """
776 776
777 777 names = []
778 778 for src, abs, rel, exact in walk(repo, pats, opts):
779 779 if exact:
780 780 if ui.verbose:
781 781 ui.status(_('adding %s\n') % rel)
782 782 names.append(abs)
783 783 elif repo.dirstate.state(abs) == '?':
784 784 ui.status(_('adding %s\n') % rel)
785 785 names.append(abs)
786 786 repo.add(names)
787 787
788 788 def addremove(ui, repo, *pats, **opts):
789 789 """add all new files, delete all missing files
790 790
791 791 Add all new files and remove all missing files from the repository.
792 792
793 793 New files are ignored if they match any of the patterns in .hgignore. As
794 794 with add, these changes take effect at the next commit.
795 795 """
796 796 return addremove_lock(ui, repo, pats, opts)
797 797
798 798 def addremove_lock(ui, repo, pats, opts, wlock=None):
799 799 add, remove = [], []
800 800 for src, abs, rel, exact in walk(repo, pats, opts):
801 801 if src == 'f' and repo.dirstate.state(abs) == '?':
802 802 add.append(abs)
803 803 if ui.verbose or not exact:
804 804 ui.status(_('adding %s\n') % ((pats and rel) or abs))
805 805 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
806 806 remove.append(abs)
807 807 if ui.verbose or not exact:
808 808 ui.status(_('removing %s\n') % ((pats and rel) or abs))
809 809 repo.add(add, wlock=wlock)
810 810 repo.remove(remove, wlock=wlock)
811 811
812 812 def annotate(ui, repo, *pats, **opts):
813 813 """show changeset information per file line
814 814
815 815 List changes in files, showing the revision id responsible for each line
816 816
817 817 This command is useful to discover who did a change or when a change took
818 818 place.
819 819
820 820 Without the -a option, annotate will avoid processing files it
821 821 detects as binary. With -a, annotate will generate an annotation
822 822 anyway, probably with undesirable results.
823 823 """
824 824 def getnode(rev):
825 825 return short(repo.changelog.node(rev))
826 826
827 827 ucache = {}
828 828 def getname(rev):
829 829 cl = repo.changelog.read(repo.changelog.node(rev))
830 830 return trimuser(ui, cl[1], rev, ucache)
831 831
832 832 dcache = {}
833 833 def getdate(rev):
834 834 datestr = dcache.get(rev)
835 835 if datestr is None:
836 836 cl = repo.changelog.read(repo.changelog.node(rev))
837 837 datestr = dcache[rev] = util.datestr(cl[2])
838 838 return datestr
839 839
840 840 if not pats:
841 841 raise util.Abort(_('at least one file name or pattern required'))
842 842
843 843 opmap = [['user', getname], ['number', str], ['changeset', getnode],
844 844 ['date', getdate]]
845 845 if not opts['user'] and not opts['changeset'] and not opts['date']:
846 846 opts['number'] = 1
847 847
848 848 if opts['rev']:
849 849 node = repo.changelog.lookup(opts['rev'])
850 850 else:
851 851 node = repo.dirstate.parents()[0]
852 852 change = repo.changelog.read(node)
853 853 mmap = repo.manifest.read(change[0])
854 854
855 855 for src, abs, rel, exact in walk(repo, pats, opts, node=node):
856 856 f = repo.file(abs)
857 857 if not opts['text'] and util.binary(f.read(mmap[abs])):
858 858 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
859 859 continue
860 860
861 861 lines = f.annotate(mmap[abs])
862 862 pieces = []
863 863
864 864 for o, f in opmap:
865 865 if opts[o]:
866 866 l = [f(n) for n, dummy in lines]
867 867 if l:
868 868 m = max(map(len, l))
869 869 pieces.append(["%*s" % (m, x) for x in l])
870 870
871 871 if pieces:
872 872 for p, l in zip(zip(*pieces), lines):
873 873 ui.write("%s: %s" % (" ".join(p), l[1]))
874 874
875 875 def bundle(ui, repo, fname, dest="default-push", **opts):
876 876 """create a changegroup file
877 877
878 878 Generate a compressed changegroup file collecting all changesets
879 879 not found in the other repository.
880 880
881 881 This file can then be transferred using conventional means and
882 882 applied to another repository with the unbundle command. This is
883 883 useful when native push and pull are not available or when
884 884 exporting an entire repository is undesirable. The standard file
885 885 extension is ".hg".
886 886
887 887 Unlike import/export, this exactly preserves all changeset
888 888 contents including permissions, rename data, and revision history.
889 889 """
890 890 dest = ui.expandpath(dest)
891 891 other = hg.repository(ui, dest)
892 892 o = repo.findoutgoing(other, force=opts['force'])
893 893 cg = repo.changegroup(o, 'bundle')
894 894 write_bundle(cg, fname)
895 895
896 896 def cat(ui, repo, file1, *pats, **opts):
897 897 """output the latest or given revisions of files
898 898
899 899 Print the specified files as they were at the given revision.
900 900 If no revision is given then the tip is used.
901 901
902 902 Output may be to a file, in which case the name of the file is
903 903 given using a format string. The formatting rules are the same as
904 904 for the export command, with the following additions:
905 905
906 906 %s basename of file being printed
907 907 %d dirname of file being printed, or '.' if in repo root
908 908 %p root-relative path name of file being printed
909 909 """
910 910 mf = {}
911 911 rev = opts['rev']
912 912 if rev:
913 913 node = repo.lookup(rev)
914 914 else:
915 915 node = repo.changelog.tip()
916 916 change = repo.changelog.read(node)
917 917 mf = repo.manifest.read(change[0])
918 918 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
919 919 r = repo.file(abs)
920 920 n = mf[abs]
921 921 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
922 922 fp.write(r.read(n))
923 923
924 924 def clone(ui, source, dest=None, **opts):
925 925 """make a copy of an existing repository
926 926
927 927 Create a copy of an existing repository in a new directory.
928 928
929 929 If no destination directory name is specified, it defaults to the
930 930 basename of the source.
931 931
932 932 The location of the source is added to the new repository's
933 933 .hg/hgrc file, as the default to be used for future pulls.
934 934
935 935 For efficiency, hardlinks are used for cloning whenever the source
936 936 and destination are on the same filesystem. Some filesystems,
937 937 such as AFS, implement hardlinking incorrectly, but do not report
938 938 errors. In these cases, use the --pull option to avoid
939 939 hardlinking.
940 940
941 941 See pull for valid source format details.
942 942 """
943 943 if dest is None:
944 944 dest = os.path.basename(os.path.normpath(source))
945 945
946 946 if os.path.exists(dest):
947 947 raise util.Abort(_("destination '%s' already exists"), dest)
948 948
949 949 dest = os.path.realpath(dest)
950 950
951 951 class Dircleanup(object):
952 952 def __init__(self, dir_):
953 953 self.rmtree = shutil.rmtree
954 954 self.dir_ = dir_
955 955 os.mkdir(dir_)
956 956 def close(self):
957 957 self.dir_ = None
958 958 def __del__(self):
959 959 if self.dir_:
960 960 self.rmtree(self.dir_, True)
961 961
962 962 if opts['ssh']:
963 963 ui.setconfig("ui", "ssh", opts['ssh'])
964 964 if opts['remotecmd']:
965 965 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
966 966
967 967 source = ui.expandpath(source)
968 968
969 969 d = Dircleanup(dest)
970 970 abspath = source
971 971 other = hg.repository(ui, source)
972 972
973 973 copy = False
974 974 if other.dev() != -1:
975 975 abspath = os.path.abspath(source)
976 976 if not opts['pull'] and not opts['rev']:
977 977 copy = True
978 978
979 979 if copy:
980 980 try:
981 981 # we use a lock here because if we race with commit, we
982 982 # can end up with extra data in the cloned revlogs that's
983 983 # not pointed to by changesets, thus causing verify to
984 984 # fail
985 985 l1 = other.lock()
986 986 except lock.LockException:
987 987 copy = False
988 988
989 989 if copy:
990 990 # we lock here to avoid premature writing to the target
991 991 os.mkdir(os.path.join(dest, ".hg"))
992 992 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
993 993
994 994 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
995 995 for f in files.split():
996 996 src = os.path.join(source, ".hg", f)
997 997 dst = os.path.join(dest, ".hg", f)
998 998 try:
999 999 util.copyfiles(src, dst)
1000 1000 except OSError, inst:
1001 1001 if inst.errno != errno.ENOENT:
1002 1002 raise
1003 1003
1004 1004 repo = hg.repository(ui, dest)
1005 1005
1006 1006 else:
1007 1007 revs = None
1008 1008 if opts['rev']:
1009 1009 if not other.local():
1010 1010 error = _("clone -r not supported yet for remote repositories.")
1011 1011 raise util.Abort(error)
1012 1012 else:
1013 1013 revs = [other.lookup(rev) for rev in opts['rev']]
1014 1014 repo = hg.repository(ui, dest, create=1)
1015 1015 repo.pull(other, heads = revs)
1016 1016
1017 1017 f = repo.opener("hgrc", "w", text=True)
1018 1018 f.write("[paths]\n")
1019 1019 f.write("default = %s\n" % abspath)
1020 1020 f.close()
1021 1021
1022 1022 if not opts['noupdate']:
1023 1023 update(repo.ui, repo)
1024 1024
1025 1025 d.close()
1026 1026
1027 1027 def commit(ui, repo, *pats, **opts):
1028 1028 """commit the specified files or all outstanding changes
1029 1029
1030 1030 Commit changes to the given files into the repository.
1031 1031
1032 1032 If a list of files is omitted, all changes reported by "hg status"
1033 will be commited.
1033 will be committed.
1034 1034
1035 1035 The HGEDITOR or EDITOR environment variables are used to start an
1036 1036 editor to add a commit comment.
1037 1037 """
1038 1038 message = opts['message']
1039 1039 logfile = opts['logfile']
1040 1040
1041 1041 if message and logfile:
1042 1042 raise util.Abort(_('options --message and --logfile are mutually '
1043 1043 'exclusive'))
1044 1044 if not message and logfile:
1045 1045 try:
1046 1046 if logfile == '-':
1047 1047 message = sys.stdin.read()
1048 1048 else:
1049 1049 message = open(logfile).read()
1050 1050 except IOError, inst:
1051 1051 raise util.Abort(_("can't read commit message '%s': %s") %
1052 1052 (logfile, inst.strerror))
1053 1053
1054 1054 if opts['addremove']:
1055 1055 addremove(ui, repo, *pats, **opts)
1056 1056 fns, match, anypats = matchpats(repo, pats, opts)
1057 1057 if pats:
1058 1058 modified, added, removed, deleted, unknown = (
1059 1059 repo.changes(files=fns, match=match))
1060 1060 files = modified + added + removed
1061 1061 else:
1062 1062 files = []
1063 1063 try:
1064 1064 repo.commit(files, message, opts['user'], opts['date'], match)
1065 1065 except ValueError, inst:
1066 1066 raise util.Abort(str(inst))
1067 1067
1068 1068 def docopy(ui, repo, pats, opts, wlock):
1069 1069 # called with the repo lock held
1070 1070 cwd = repo.getcwd()
1071 1071 errors = 0
1072 1072 copied = []
1073 1073 targets = {}
1074 1074
1075 1075 def okaytocopy(abs, rel, exact):
1076 1076 reasons = {'?': _('is not managed'),
1077 1077 'a': _('has been marked for add'),
1078 1078 'r': _('has been marked for remove')}
1079 1079 state = repo.dirstate.state(abs)
1080 1080 reason = reasons.get(state)
1081 1081 if reason:
1082 1082 if state == 'a':
1083 1083 origsrc = repo.dirstate.copied(abs)
1084 1084 if origsrc is not None:
1085 1085 return origsrc
1086 1086 if exact:
1087 1087 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
1088 1088 else:
1089 1089 return abs
1090 1090
1091 1091 def copy(origsrc, abssrc, relsrc, target, exact):
1092 1092 abstarget = util.canonpath(repo.root, cwd, target)
1093 1093 reltarget = util.pathto(cwd, abstarget)
1094 1094 prevsrc = targets.get(abstarget)
1095 1095 if prevsrc is not None:
1096 1096 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1097 1097 (reltarget, abssrc, prevsrc))
1098 1098 return
1099 1099 if (not opts['after'] and os.path.exists(reltarget) or
1100 1100 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
1101 1101 if not opts['force']:
1102 1102 ui.warn(_('%s: not overwriting - file exists\n') %
1103 1103 reltarget)
1104 1104 return
1105 1105 if not opts['after']:
1106 1106 os.unlink(reltarget)
1107 1107 if opts['after']:
1108 1108 if not os.path.exists(reltarget):
1109 1109 return
1110 1110 else:
1111 1111 targetdir = os.path.dirname(reltarget) or '.'
1112 1112 if not os.path.isdir(targetdir):
1113 1113 os.makedirs(targetdir)
1114 1114 try:
1115 1115 restore = repo.dirstate.state(abstarget) == 'r'
1116 1116 if restore:
1117 1117 repo.undelete([abstarget], wlock)
1118 1118 try:
1119 1119 shutil.copyfile(relsrc, reltarget)
1120 1120 shutil.copymode(relsrc, reltarget)
1121 1121 restore = False
1122 1122 finally:
1123 1123 if restore:
1124 1124 repo.remove([abstarget], wlock)
1125 1125 except shutil.Error, inst:
1126 1126 raise util.Abort(str(inst))
1127 1127 except IOError, inst:
1128 1128 if inst.errno == errno.ENOENT:
1129 1129 ui.warn(_('%s: deleted in working copy\n') % relsrc)
1130 1130 else:
1131 1131 ui.warn(_('%s: cannot copy - %s\n') %
1132 1132 (relsrc, inst.strerror))
1133 1133 errors += 1
1134 1134 return
1135 1135 if ui.verbose or not exact:
1136 1136 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1137 1137 targets[abstarget] = abssrc
1138 1138 if abstarget != origsrc:
1139 1139 repo.copy(origsrc, abstarget, wlock)
1140 1140 copied.append((abssrc, relsrc, exact))
1141 1141
1142 1142 def targetpathfn(pat, dest, srcs):
1143 1143 if os.path.isdir(pat):
1144 1144 abspfx = util.canonpath(repo.root, cwd, pat)
1145 1145 if destdirexists:
1146 1146 striplen = len(os.path.split(abspfx)[0])
1147 1147 else:
1148 1148 striplen = len(abspfx)
1149 1149 if striplen:
1150 1150 striplen += len(os.sep)
1151 1151 res = lambda p: os.path.join(dest, p[striplen:])
1152 1152 elif destdirexists:
1153 1153 res = lambda p: os.path.join(dest, os.path.basename(p))
1154 1154 else:
1155 1155 res = lambda p: dest
1156 1156 return res
1157 1157
1158 1158 def targetpathafterfn(pat, dest, srcs):
1159 1159 if util.patkind(pat, None)[0]:
1160 1160 # a mercurial pattern
1161 1161 res = lambda p: os.path.join(dest, os.path.basename(p))
1162 1162 else:
1163 1163 abspfx = util.canonpath(repo.root, cwd, pat)
1164 1164 if len(abspfx) < len(srcs[0][0]):
1165 1165 # A directory. Either the target path contains the last
1166 1166 # component of the source path or it does not.
1167 1167 def evalpath(striplen):
1168 1168 score = 0
1169 1169 for s in srcs:
1170 1170 t = os.path.join(dest, s[0][striplen:])
1171 1171 if os.path.exists(t):
1172 1172 score += 1
1173 1173 return score
1174 1174
1175 1175 striplen = len(abspfx)
1176 1176 if striplen:
1177 1177 striplen += len(os.sep)
1178 1178 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1179 1179 score = evalpath(striplen)
1180 1180 striplen1 = len(os.path.split(abspfx)[0])
1181 1181 if striplen1:
1182 1182 striplen1 += len(os.sep)
1183 1183 if evalpath(striplen1) > score:
1184 1184 striplen = striplen1
1185 1185 res = lambda p: os.path.join(dest, p[striplen:])
1186 1186 else:
1187 1187 # a file
1188 1188 if destdirexists:
1189 1189 res = lambda p: os.path.join(dest, os.path.basename(p))
1190 1190 else:
1191 1191 res = lambda p: dest
1192 1192 return res
1193 1193
1194 1194
1195 1195 pats = list(pats)
1196 1196 if not pats:
1197 1197 raise util.Abort(_('no source or destination specified'))
1198 1198 if len(pats) == 1:
1199 1199 raise util.Abort(_('no destination specified'))
1200 1200 dest = pats.pop()
1201 1201 destdirexists = os.path.isdir(dest)
1202 1202 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
1203 1203 raise util.Abort(_('with multiple sources, destination must be an '
1204 1204 'existing directory'))
1205 1205 if opts['after']:
1206 1206 tfn = targetpathafterfn
1207 1207 else:
1208 1208 tfn = targetpathfn
1209 1209 copylist = []
1210 1210 for pat in pats:
1211 1211 srcs = []
1212 1212 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
1213 1213 origsrc = okaytocopy(abssrc, relsrc, exact)
1214 1214 if origsrc:
1215 1215 srcs.append((origsrc, abssrc, relsrc, exact))
1216 1216 if not srcs:
1217 1217 continue
1218 1218 copylist.append((tfn(pat, dest, srcs), srcs))
1219 1219 if not copylist:
1220 1220 raise util.Abort(_('no files to copy'))
1221 1221
1222 1222 for targetpath, srcs in copylist:
1223 1223 for origsrc, abssrc, relsrc, exact in srcs:
1224 1224 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
1225 1225
1226 1226 if errors:
1227 1227 ui.warn(_('(consider using --after)\n'))
1228 1228 return errors, copied
1229 1229
1230 1230 def copy(ui, repo, *pats, **opts):
1231 1231 """mark files as copied for the next commit
1232 1232
1233 1233 Mark dest as having copies of source files. If dest is a
1234 1234 directory, copies are put in that directory. If dest is a file,
1235 1235 there can only be one source.
1236 1236
1237 1237 By default, this command copies the contents of files as they
1238 1238 stand in the working directory. If invoked with --after, the
1239 1239 operation is recorded, but no copying is performed.
1240 1240
1241 1241 This command takes effect in the next commit.
1242 1242
1243 1243 NOTE: This command should be treated as experimental. While it
1244 1244 should properly record copied files, this information is not yet
1245 1245 fully used by merge, nor fully reported by log.
1246 1246 """
1247 1247 try:
1248 1248 wlock = repo.wlock(0)
1249 1249 errs, copied = docopy(ui, repo, pats, opts, wlock)
1250 1250 except lock.LockHeld, inst:
1251 1251 ui.warn(_("repository lock held by %s\n") % inst.args[0])
1252 1252 errs = 1
1253 1253 return errs
1254 1254
1255 1255 def debugancestor(ui, index, rev1, rev2):
1256 1256 """find the ancestor revision of two revisions in a given index"""
1257 1257 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "")
1258 1258 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1259 1259 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1260 1260
1261 1261 def debugcomplete(ui, cmd):
1262 1262 """returns the completion list associated with the given command"""
1263 1263 clist = findpossible(cmd).keys()
1264 1264 clist.sort()
1265 1265 ui.write("%s\n" % " ".join(clist))
1266 1266
1267 1267 def debugrebuildstate(ui, repo, rev=None):
1268 1268 """rebuild the dirstate as it would look like for the given revision"""
1269 1269 if not rev:
1270 1270 rev = repo.changelog.tip()
1271 1271 else:
1272 1272 rev = repo.lookup(rev)
1273 1273 change = repo.changelog.read(rev)
1274 1274 n = change[0]
1275 1275 files = repo.manifest.readflags(n)
1276 1276 wlock = repo.wlock()
1277 1277 repo.dirstate.rebuild(rev, files.iteritems())
1278 1278
1279 1279 def debugcheckstate(ui, repo):
1280 1280 """validate the correctness of the current dirstate"""
1281 1281 parent1, parent2 = repo.dirstate.parents()
1282 1282 repo.dirstate.read()
1283 1283 dc = repo.dirstate.map
1284 1284 keys = dc.keys()
1285 1285 keys.sort()
1286 1286 m1n = repo.changelog.read(parent1)[0]
1287 1287 m2n = repo.changelog.read(parent2)[0]
1288 1288 m1 = repo.manifest.read(m1n)
1289 1289 m2 = repo.manifest.read(m2n)
1290 1290 errors = 0
1291 1291 for f in dc:
1292 1292 state = repo.dirstate.state(f)
1293 1293 if state in "nr" and f not in m1:
1294 1294 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1295 1295 errors += 1
1296 1296 if state in "a" and f in m1:
1297 1297 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1298 1298 errors += 1
1299 1299 if state in "m" and f not in m1 and f not in m2:
1300 1300 ui.warn(_("%s in state %s, but not in either manifest\n") %
1301 1301 (f, state))
1302 1302 errors += 1
1303 1303 for f in m1:
1304 1304 state = repo.dirstate.state(f)
1305 1305 if state not in "nrm":
1306 1306 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1307 1307 errors += 1
1308 1308 if errors:
1309 1309 error = _(".hg/dirstate inconsistent with current parent's manifest")
1310 1310 raise util.Abort(error)
1311 1311
1312 1312 def debugconfig(ui, repo):
1313 1313 """show combined config settings from all hgrc files"""
1314 1314 for section, name, value in ui.walkconfig():
1315 1315 ui.write('%s.%s=%s\n' % (section, name, value))
1316 1316
1317 1317 def debugsetparents(ui, repo, rev1, rev2=None):
1318 1318 """manually set the parents of the current working directory
1319 1319
1320 1320 This is useful for writing repository conversion tools, but should
1321 1321 be used with care.
1322 1322 """
1323 1323
1324 1324 if not rev2:
1325 1325 rev2 = hex(nullid)
1326 1326
1327 1327 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1328 1328
1329 1329 def debugstate(ui, repo):
1330 1330 """show the contents of the current dirstate"""
1331 1331 repo.dirstate.read()
1332 1332 dc = repo.dirstate.map
1333 1333 keys = dc.keys()
1334 1334 keys.sort()
1335 1335 for file_ in keys:
1336 1336 ui.write("%c %3o %10d %s %s\n"
1337 1337 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1338 1338 time.strftime("%x %X",
1339 1339 time.localtime(dc[file_][3])), file_))
1340 1340 for f in repo.dirstate.copies:
1341 1341 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1342 1342
1343 1343 def debugdata(ui, file_, rev):
1344 1344 """dump the contents of an data file revision"""
1345 1345 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1346 1346 file_[:-2] + ".i", file_)
1347 1347 try:
1348 1348 ui.write(r.revision(r.lookup(rev)))
1349 1349 except KeyError:
1350 1350 raise util.Abort(_('invalid revision identifier %s'), rev)
1351 1351
1352 1352 def debugindex(ui, file_):
1353 1353 """dump the contents of an index file"""
1354 1354 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "")
1355 1355 ui.write(" rev offset length base linkrev" +
1356 1356 " nodeid p1 p2\n")
1357 1357 for i in range(r.count()):
1358 1358 e = r.index[i]
1359 1359 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1360 1360 i, e[0], e[1], e[2], e[3],
1361 1361 short(e[6]), short(e[4]), short(e[5])))
1362 1362
1363 1363 def debugindexdot(ui, file_):
1364 1364 """dump an index DAG as a .dot file"""
1365 1365 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "")
1366 1366 ui.write("digraph G {\n")
1367 1367 for i in range(r.count()):
1368 1368 e = r.index[i]
1369 1369 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
1370 1370 if e[5] != nullid:
1371 1371 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
1372 1372 ui.write("}\n")
1373 1373
1374 1374 def debugrename(ui, repo, file, rev=None):
1375 1375 """dump rename information"""
1376 1376 r = repo.file(relpath(repo, [file])[0])
1377 1377 if rev:
1378 1378 try:
1379 1379 # assume all revision numbers are for changesets
1380 1380 n = repo.lookup(rev)
1381 1381 change = repo.changelog.read(n)
1382 1382 m = repo.manifest.read(change[0])
1383 1383 n = m[relpath(repo, [file])[0]]
1384 1384 except (hg.RepoError, KeyError):
1385 1385 n = r.lookup(rev)
1386 1386 else:
1387 1387 n = r.tip()
1388 1388 m = r.renamed(n)
1389 1389 if m:
1390 1390 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1391 1391 else:
1392 1392 ui.write(_("not renamed\n"))
1393 1393
1394 1394 def debugwalk(ui, repo, *pats, **opts):
1395 1395 """show how files match on given patterns"""
1396 1396 items = list(walk(repo, pats, opts))
1397 1397 if not items:
1398 1398 return
1399 1399 fmt = '%%s %%-%ds %%-%ds %%s' % (
1400 1400 max([len(abs) for (src, abs, rel, exact) in items]),
1401 1401 max([len(rel) for (src, abs, rel, exact) in items]))
1402 1402 for src, abs, rel, exact in items:
1403 1403 line = fmt % (src, abs, rel, exact and 'exact' or '')
1404 1404 ui.write("%s\n" % line.rstrip())
1405 1405
1406 1406 def diff(ui, repo, *pats, **opts):
1407 1407 """diff repository (or selected files)
1408 1408
1409 1409 Show differences between revisions for the specified files.
1410 1410
1411 1411 Differences between files are shown using the unified diff format.
1412 1412
1413 1413 When two revision arguments are given, then changes are shown
1414 1414 between those revisions. If only one revision is specified then
1415 1415 that revision is compared to the working directory, and, when no
1416 1416 revisions are specified, the working directory files are compared
1417 1417 to its parent.
1418 1418
1419 1419 Without the -a option, diff will avoid generating diffs of files
1420 1420 it detects as binary. With -a, diff will generate a diff anyway,
1421 1421 probably with undesirable results.
1422 1422 """
1423 1423 node1, node2 = None, None
1424 1424 revs = [repo.lookup(x) for x in opts['rev']]
1425 1425
1426 1426 if len(revs) > 0:
1427 1427 node1 = revs[0]
1428 1428 if len(revs) > 1:
1429 1429 node2 = revs[1]
1430 1430 if len(revs) > 2:
1431 1431 raise util.Abort(_("too many revisions to diff"))
1432 1432
1433 1433 fns, matchfn, anypats = matchpats(repo, pats, opts)
1434 1434
1435 1435 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1436 1436 text=opts['text'], opts=opts)
1437 1437
1438 1438 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1439 1439 node = repo.lookup(changeset)
1440 1440 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1441 1441 if opts['switch_parent']:
1442 1442 parents.reverse()
1443 1443 prev = (parents and parents[0]) or nullid
1444 1444 change = repo.changelog.read(node)
1445 1445
1446 1446 fp = make_file(repo, repo.changelog, opts['output'],
1447 1447 node=node, total=total, seqno=seqno,
1448 1448 revwidth=revwidth)
1449 1449 if fp != sys.stdout:
1450 1450 ui.note("%s\n" % fp.name)
1451 1451
1452 1452 fp.write("# HG changeset patch\n")
1453 1453 fp.write("# User %s\n" % change[1])
1454 1454 fp.write("# Node ID %s\n" % hex(node))
1455 1455 fp.write("# Parent %s\n" % hex(prev))
1456 1456 if len(parents) > 1:
1457 1457 fp.write("# Parent %s\n" % hex(parents[1]))
1458 1458 fp.write(change[4].rstrip())
1459 1459 fp.write("\n\n")
1460 1460
1461 1461 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1462 1462 if fp != sys.stdout:
1463 1463 fp.close()
1464 1464
1465 1465 def export(ui, repo, *changesets, **opts):
1466 1466 """dump the header and diffs for one or more changesets
1467 1467
1468 1468 Print the changeset header and diffs for one or more revisions.
1469 1469
1470 1470 The information shown in the changeset header is: author,
1471 1471 changeset hash, parent and commit comment.
1472 1472
1473 1473 Output may be to a file, in which case the name of the file is
1474 1474 given using a format string. The formatting rules are as follows:
1475 1475
1476 1476 %% literal "%" character
1477 1477 %H changeset hash (40 bytes of hexadecimal)
1478 1478 %N number of patches being generated
1479 1479 %R changeset revision number
1480 1480 %b basename of the exporting repository
1481 1481 %h short-form changeset hash (12 bytes of hexadecimal)
1482 1482 %n zero-padded sequence number, starting at 1
1483 1483 %r zero-padded changeset revision number
1484 1484
1485 1485 Without the -a option, export will avoid generating diffs of files
1486 1486 it detects as binary. With -a, export will generate a diff anyway,
1487 1487 probably with undesirable results.
1488 1488
1489 1489 With the --switch-parent option, the diff will be against the second
1490 1490 parent. It can be useful to review a merge.
1491 1491 """
1492 1492 if not changesets:
1493 1493 raise util.Abort(_("export requires at least one changeset"))
1494 1494 seqno = 0
1495 1495 revs = list(revrange(ui, repo, changesets))
1496 1496 total = len(revs)
1497 1497 revwidth = max(map(len, revs))
1498 1498 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1499 1499 ui.note(msg)
1500 1500 for cset in revs:
1501 1501 seqno += 1
1502 1502 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1503 1503
1504 1504 def forget(ui, repo, *pats, **opts):
1505 1505 """don't add the specified files on the next commit
1506 1506
1507 1507 Undo an 'hg add' scheduled for the next commit.
1508 1508 """
1509 1509 forget = []
1510 1510 for src, abs, rel, exact in walk(repo, pats, opts):
1511 1511 if repo.dirstate.state(abs) == 'a':
1512 1512 forget.append(abs)
1513 1513 if ui.verbose or not exact:
1514 1514 ui.status(_('forgetting %s\n') % ((pats and rel) or abs))
1515 1515 repo.forget(forget)
1516 1516
1517 1517 def grep(ui, repo, pattern, *pats, **opts):
1518 1518 """search for a pattern in specified files and revisions
1519 1519
1520 1520 Search revisions of files for a regular expression.
1521 1521
1522 1522 This command behaves differently than Unix grep. It only accepts
1523 1523 Python/Perl regexps. It searches repository history, not the
1524 1524 working directory. It always prints the revision number in which
1525 1525 a match appears.
1526 1526
1527 1527 By default, grep only prints output for the first revision of a
1528 1528 file in which it finds a match. To get it to print every revision
1529 1529 that contains a change in match status ("-" for a match that
1530 1530 becomes a non-match, or "+" for a non-match that becomes a match),
1531 1531 use the --all flag.
1532 1532 """
1533 1533 reflags = 0
1534 1534 if opts['ignore_case']:
1535 1535 reflags |= re.I
1536 1536 regexp = re.compile(pattern, reflags)
1537 1537 sep, eol = ':', '\n'
1538 1538 if opts['print0']:
1539 1539 sep = eol = '\0'
1540 1540
1541 1541 fcache = {}
1542 1542 def getfile(fn):
1543 1543 if fn not in fcache:
1544 1544 fcache[fn] = repo.file(fn)
1545 1545 return fcache[fn]
1546 1546
1547 1547 def matchlines(body):
1548 1548 begin = 0
1549 1549 linenum = 0
1550 1550 while True:
1551 1551 match = regexp.search(body, begin)
1552 1552 if not match:
1553 1553 break
1554 1554 mstart, mend = match.span()
1555 1555 linenum += body.count('\n', begin, mstart) + 1
1556 1556 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1557 1557 lend = body.find('\n', mend)
1558 1558 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1559 1559 begin = lend + 1
1560 1560
1561 1561 class linestate(object):
1562 1562 def __init__(self, line, linenum, colstart, colend):
1563 1563 self.line = line
1564 1564 self.linenum = linenum
1565 1565 self.colstart = colstart
1566 1566 self.colend = colend
1567 1567 def __eq__(self, other):
1568 1568 return self.line == other.line
1569 1569 def __hash__(self):
1570 1570 return hash(self.line)
1571 1571
1572 1572 matches = {}
1573 1573 def grepbody(fn, rev, body):
1574 1574 matches[rev].setdefault(fn, {})
1575 1575 m = matches[rev][fn]
1576 1576 for lnum, cstart, cend, line in matchlines(body):
1577 1577 s = linestate(line, lnum, cstart, cend)
1578 1578 m[s] = s
1579 1579
1580 1580 # FIXME: prev isn't used, why ?
1581 1581 prev = {}
1582 1582 ucache = {}
1583 1583 def display(fn, rev, states, prevstates):
1584 1584 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1585 1585 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1586 1586 counts = {'-': 0, '+': 0}
1587 1587 filerevmatches = {}
1588 1588 for l in diff:
1589 1589 if incrementing or not opts['all']:
1590 1590 change = ((l in prevstates) and '-') or '+'
1591 1591 r = rev
1592 1592 else:
1593 1593 change = ((l in states) and '-') or '+'
1594 1594 r = prev[fn]
1595 1595 cols = [fn, str(rev)]
1596 1596 if opts['line_number']:
1597 1597 cols.append(str(l.linenum))
1598 1598 if opts['all']:
1599 1599 cols.append(change)
1600 1600 if opts['user']:
1601 1601 cols.append(trimuser(ui, getchange(rev)[1], rev,
1602 1602 ucache))
1603 1603 if opts['files_with_matches']:
1604 1604 c = (fn, rev)
1605 1605 if c in filerevmatches:
1606 1606 continue
1607 1607 filerevmatches[c] = 1
1608 1608 else:
1609 1609 cols.append(l.line)
1610 1610 ui.write(sep.join(cols), eol)
1611 1611 counts[change] += 1
1612 1612 return counts['+'], counts['-']
1613 1613
1614 1614 fstate = {}
1615 1615 skip = {}
1616 1616 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1617 1617 count = 0
1618 1618 incrementing = False
1619 1619 for st, rev, fns in changeiter:
1620 1620 if st == 'window':
1621 1621 incrementing = rev
1622 1622 matches.clear()
1623 1623 elif st == 'add':
1624 1624 change = repo.changelog.read(repo.lookup(str(rev)))
1625 1625 mf = repo.manifest.read(change[0])
1626 1626 matches[rev] = {}
1627 1627 for fn in fns:
1628 1628 if fn in skip:
1629 1629 continue
1630 1630 fstate.setdefault(fn, {})
1631 1631 try:
1632 1632 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1633 1633 except KeyError:
1634 1634 pass
1635 1635 elif st == 'iter':
1636 1636 states = matches[rev].items()
1637 1637 states.sort()
1638 1638 for fn, m in states:
1639 1639 if fn in skip:
1640 1640 continue
1641 1641 if incrementing or not opts['all'] or fstate[fn]:
1642 1642 pos, neg = display(fn, rev, m, fstate[fn])
1643 1643 count += pos + neg
1644 1644 if pos and not opts['all']:
1645 1645 skip[fn] = True
1646 1646 fstate[fn] = m
1647 1647 prev[fn] = rev
1648 1648
1649 1649 if not incrementing:
1650 1650 fstate = fstate.items()
1651 1651 fstate.sort()
1652 1652 for fn, state in fstate:
1653 1653 if fn in skip:
1654 1654 continue
1655 1655 display(fn, rev, {}, state)
1656 1656 return (count == 0 and 1) or 0
1657 1657
1658 1658 def heads(ui, repo, **opts):
1659 1659 """show current repository heads
1660 1660
1661 1661 Show all repository head changesets.
1662 1662
1663 1663 Repository "heads" are changesets that don't have children
1664 1664 changesets. They are where development generally takes place and
1665 1665 are the usual targets for update and merge operations.
1666 1666 """
1667 1667 if opts['rev']:
1668 1668 heads = repo.heads(repo.lookup(opts['rev']))
1669 1669 else:
1670 1670 heads = repo.heads()
1671 1671 br = None
1672 1672 if opts['branches']:
1673 1673 br = repo.branchlookup(heads)
1674 1674 displayer = show_changeset(ui, repo, opts)
1675 1675 for n in heads:
1676 1676 displayer.show(changenode=n, brinfo=br)
1677 1677
1678 1678 def identify(ui, repo):
1679 1679 """print information about the working copy
1680 1680
1681 1681 Print a short summary of the current state of the repo.
1682 1682
1683 1683 This summary identifies the repository state using one or two parent
1684 1684 hash identifiers, followed by a "+" if there are uncommitted changes
1685 1685 in the working directory, followed by a list of tags for this revision.
1686 1686 """
1687 1687 parents = [p for p in repo.dirstate.parents() if p != nullid]
1688 1688 if not parents:
1689 1689 ui.write(_("unknown\n"))
1690 1690 return
1691 1691
1692 1692 hexfunc = ui.verbose and hex or short
1693 1693 modified, added, removed, deleted, unknown = repo.changes()
1694 1694 output = ["%s%s" %
1695 1695 ('+'.join([hexfunc(parent) for parent in parents]),
1696 1696 (modified or added or removed or deleted) and "+" or "")]
1697 1697
1698 1698 if not ui.quiet:
1699 1699 # multiple tags for a single parent separated by '/'
1700 1700 parenttags = ['/'.join(tags)
1701 1701 for tags in map(repo.nodetags, parents) if tags]
1702 1702 # tags for multiple parents separated by ' + '
1703 1703 if parenttags:
1704 1704 output.append(' + '.join(parenttags))
1705 1705
1706 1706 ui.write("%s\n" % ' '.join(output))
1707 1707
1708 1708 def import_(ui, repo, patch1, *patches, **opts):
1709 1709 """import an ordered set of patches
1710 1710
1711 1711 Import a list of patches and commit them individually.
1712 1712
1713 1713 If there are outstanding changes in the working directory, import
1714 1714 will abort unless given the -f flag.
1715 1715
1716 1716 If a patch looks like a mail message (its first line starts with
1717 1717 "From " or looks like an RFC822 header), it will not be applied
1718 1718 unless the -f option is used. The importer neither parses nor
1719 1719 discards mail headers, so use -f only to override the "mailness"
1720 1720 safety check, not to import a real mail message.
1721 1721 """
1722 1722 patches = (patch1,) + patches
1723 1723
1724 1724 if not opts['force']:
1725 1725 modified, added, removed, deleted, unknown = repo.changes()
1726 1726 if modified or added or removed or deleted:
1727 1727 raise util.Abort(_("outstanding uncommitted changes"))
1728 1728
1729 1729 d = opts["base"]
1730 1730 strip = opts["strip"]
1731 1731
1732 1732 mailre = re.compile(r'(?:From |[\w-]+:)')
1733 1733
1734 1734 # attempt to detect the start of a patch
1735 1735 # (this heuristic is borrowed from quilt)
1736 1736 diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1737 1737 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1738 1738 '(---|\*\*\*)[ \t])')
1739 1739
1740 1740 for patch in patches:
1741 1741 ui.status(_("applying %s\n") % patch)
1742 1742 pf = os.path.join(d, patch)
1743 1743
1744 1744 message = []
1745 1745 user = None
1746 1746 hgpatch = False
1747 1747 for line in file(pf):
1748 1748 line = line.rstrip()
1749 1749 if (not message and not hgpatch and
1750 1750 mailre.match(line) and not opts['force']):
1751 1751 if len(line) > 35:
1752 1752 line = line[:32] + '...'
1753 1753 raise util.Abort(_('first line looks like a '
1754 1754 'mail header: ') + line)
1755 1755 if diffre.match(line):
1756 1756 break
1757 1757 elif hgpatch:
1758 1758 # parse values when importing the result of an hg export
1759 1759 if line.startswith("# User "):
1760 1760 user = line[7:]
1761 1761 ui.debug(_('User: %s\n') % user)
1762 1762 elif not line.startswith("# ") and line:
1763 1763 message.append(line)
1764 1764 hgpatch = False
1765 1765 elif line == '# HG changeset patch':
1766 1766 hgpatch = True
1767 1767 message = [] # We may have collected garbage
1768 1768 else:
1769 1769 message.append(line)
1770 1770
1771 1771 # make sure message isn't empty
1772 1772 if not message:
1773 1773 message = _("imported patch %s\n") % patch
1774 1774 else:
1775 1775 message = "%s\n" % '\n'.join(message)
1776 1776 ui.debug(_('message:\n%s\n') % message)
1777 1777
1778 1778 files = util.patch(strip, pf, ui)
1779 1779
1780 1780 if len(files) > 0:
1781 1781 addremove(ui, repo, *files)
1782 1782 repo.commit(files, message, user)
1783 1783
1784 1784 def incoming(ui, repo, source="default", **opts):
1785 1785 """show new changesets found in source
1786 1786
1787 1787 Show new changesets found in the specified path/URL or the default
1788 1788 pull location. These are the changesets that would be pulled if a pull
1789 1789 was requested.
1790 1790
1791 1791 For remote repository, using --bundle avoids downloading the changesets
1792 1792 twice if the incoming is followed by a pull.
1793 1793
1794 1794 See pull for valid source format details.
1795 1795 """
1796 1796 source = ui.expandpath(source)
1797 1797 if opts['ssh']:
1798 1798 ui.setconfig("ui", "ssh", opts['ssh'])
1799 1799 if opts['remotecmd']:
1800 1800 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1801 1801
1802 1802 other = hg.repository(ui, source)
1803 1803 incoming = repo.findincoming(other, force=opts["force"])
1804 1804 if not incoming:
1805 1805 ui.status(_("no changes found\n"))
1806 1806 return
1807 1807
1808 1808 cleanup = None
1809 1809 try:
1810 1810 fname = opts["bundle"]
1811 1811 if fname or not other.local():
1812 1812 # create a bundle (uncompressed if other repo is not local)
1813 1813 cg = other.changegroup(incoming, "incoming")
1814 1814 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1815 1815 # keep written bundle?
1816 1816 if opts["bundle"]:
1817 1817 cleanup = None
1818 1818 if not other.local():
1819 1819 # use the created uncompressed bundlerepo
1820 1820 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1821 1821
1822 1822 o = other.changelog.nodesbetween(incoming)[0]
1823 1823 if opts['newest_first']:
1824 1824 o.reverse()
1825 1825 displayer = show_changeset(ui, other, opts)
1826 1826 for n in o:
1827 1827 parents = [p for p in other.changelog.parents(n) if p != nullid]
1828 1828 if opts['no_merges'] and len(parents) == 2:
1829 1829 continue
1830 1830 displayer.show(changenode=n)
1831 1831 if opts['patch']:
1832 1832 prev = (parents and parents[0]) or nullid
1833 1833 dodiff(ui, ui, other, prev, n)
1834 1834 ui.write("\n")
1835 1835 finally:
1836 1836 if hasattr(other, 'close'):
1837 1837 other.close()
1838 1838 if cleanup:
1839 1839 os.unlink(cleanup)
1840 1840
1841 1841 def init(ui, dest="."):
1842 1842 """create a new repository in the given directory
1843 1843
1844 1844 Initialize a new repository in the given directory. If the given
1845 1845 directory does not exist, it is created.
1846 1846
1847 1847 If no directory is given, the current directory is used.
1848 1848 """
1849 1849 if not os.path.exists(dest):
1850 1850 os.mkdir(dest)
1851 1851 hg.repository(ui, dest, create=1)
1852 1852
1853 1853 def locate(ui, repo, *pats, **opts):
1854 1854 """locate files matching specific patterns
1855 1855
1856 1856 Print all files under Mercurial control whose names match the
1857 1857 given patterns.
1858 1858
1859 1859 This command searches the current directory and its
1860 1860 subdirectories. To search an entire repository, move to the root
1861 1861 of the repository.
1862 1862
1863 1863 If no patterns are given to match, this command prints all file
1864 1864 names.
1865 1865
1866 1866 If you want to feed the output of this command into the "xargs"
1867 1867 command, use the "-0" option to both this command and "xargs".
1868 1868 This will avoid the problem of "xargs" treating single filenames
1869 1869 that contain white space as multiple filenames.
1870 1870 """
1871 1871 end = opts['print0'] and '\0' or '\n'
1872 1872 rev = opts['rev']
1873 1873 if rev:
1874 1874 node = repo.lookup(rev)
1875 1875 else:
1876 1876 node = None
1877 1877
1878 1878 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
1879 1879 head='(?:.*/|)'):
1880 1880 if not node and repo.dirstate.state(abs) == '?':
1881 1881 continue
1882 1882 if opts['fullpath']:
1883 1883 ui.write(os.path.join(repo.root, abs), end)
1884 1884 else:
1885 1885 ui.write(((pats and rel) or abs), end)
1886 1886
1887 1887 def log(ui, repo, *pats, **opts):
1888 1888 """show revision history of entire repository or files
1889 1889
1890 1890 Print the revision history of the specified files or the entire project.
1891 1891
1892 1892 By default this command outputs: changeset id and hash, tags,
1893 1893 non-trivial parents, user, date and time, and a summary for each
1894 1894 commit. When the -v/--verbose switch is used, the list of changed
1895 1895 files and full commit message is shown.
1896 1896 """
1897 1897 class dui(object):
1898 1898 # Implement and delegate some ui protocol. Save hunks of
1899 1899 # output for later display in the desired order.
1900 1900 def __init__(self, ui):
1901 1901 self.ui = ui
1902 1902 self.hunk = {}
1903 1903 def bump(self, rev):
1904 1904 self.rev = rev
1905 1905 self.hunk[rev] = []
1906 1906 def note(self, *args):
1907 1907 if self.verbose:
1908 1908 self.write(*args)
1909 1909 def status(self, *args):
1910 1910 if not self.quiet:
1911 1911 self.write(*args)
1912 1912 def write(self, *args):
1913 1913 self.hunk[self.rev].append(args)
1914 1914 def debug(self, *args):
1915 1915 if self.debugflag:
1916 1916 self.write(*args)
1917 1917 def __getattr__(self, key):
1918 1918 return getattr(self.ui, key)
1919 1919
1920 1920 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1921 1921
1922 1922 if opts['limit']:
1923 1923 try:
1924 1924 limit = int(opts['limit'])
1925 1925 except ValueError:
1926 1926 raise util.Abort(_('limit must be a positive integer'))
1927 1927 if limit <= 0: raise util.Abort(_('limit must be positive'))
1928 1928 else:
1929 1929 limit = sys.maxint
1930 1930 count = 0
1931 1931
1932 1932 displayer = show_changeset(ui, repo, opts)
1933 1933 for st, rev, fns in changeiter:
1934 1934 if st == 'window':
1935 1935 du = dui(ui)
1936 1936 displayer.ui = du
1937 1937 elif st == 'add':
1938 1938 du.bump(rev)
1939 1939 changenode = repo.changelog.node(rev)
1940 1940 parents = [p for p in repo.changelog.parents(changenode)
1941 1941 if p != nullid]
1942 1942 if opts['no_merges'] and len(parents) == 2:
1943 1943 continue
1944 1944 if opts['only_merges'] and len(parents) != 2:
1945 1945 continue
1946 1946
1947 1947 if opts['keyword']:
1948 1948 changes = getchange(rev)
1949 1949 miss = 0
1950 1950 for k in [kw.lower() for kw in opts['keyword']]:
1951 1951 if not (k in changes[1].lower() or
1952 1952 k in changes[4].lower() or
1953 1953 k in " ".join(changes[3][:20]).lower()):
1954 1954 miss = 1
1955 1955 break
1956 1956 if miss:
1957 1957 continue
1958 1958
1959 1959 br = None
1960 1960 if opts['branches']:
1961 1961 br = repo.branchlookup([repo.changelog.node(rev)])
1962 1962
1963 1963 displayer.show(rev, brinfo=br)
1964 1964 if opts['patch']:
1965 1965 prev = (parents and parents[0]) or nullid
1966 1966 dodiff(du, du, repo, prev, changenode, match=matchfn)
1967 1967 du.write("\n\n")
1968 1968 elif st == 'iter':
1969 1969 if count == limit: break
1970 1970 if du.hunk[rev]:
1971 1971 count += 1
1972 1972 for args in du.hunk[rev]:
1973 1973 ui.write(*args)
1974 1974
1975 1975 def manifest(ui, repo, rev=None):
1976 1976 """output the latest or given revision of the project manifest
1977 1977
1978 1978 Print a list of version controlled files for the given revision.
1979 1979
1980 1980 The manifest is the list of files being version controlled. If no revision
1981 1981 is given then the tip is used.
1982 1982 """
1983 1983 if rev:
1984 1984 try:
1985 1985 # assume all revision numbers are for changesets
1986 1986 n = repo.lookup(rev)
1987 1987 change = repo.changelog.read(n)
1988 1988 n = change[0]
1989 1989 except hg.RepoError:
1990 1990 n = repo.manifest.lookup(rev)
1991 1991 else:
1992 1992 n = repo.manifest.tip()
1993 1993 m = repo.manifest.read(n)
1994 1994 mf = repo.manifest.readflags(n)
1995 1995 files = m.keys()
1996 1996 files.sort()
1997 1997
1998 1998 for f in files:
1999 1999 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
2000 2000
2001 2001 def outgoing(ui, repo, dest="default-push", **opts):
2002 2002 """show changesets not found in destination
2003 2003
2004 2004 Show changesets not found in the specified destination repository or
2005 2005 the default push location. These are the changesets that would be pushed
2006 2006 if a push was requested.
2007 2007
2008 2008 See pull for valid destination format details.
2009 2009 """
2010 2010 dest = ui.expandpath(dest)
2011 2011 if opts['ssh']:
2012 2012 ui.setconfig("ui", "ssh", opts['ssh'])
2013 2013 if opts['remotecmd']:
2014 2014 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2015 2015
2016 2016 other = hg.repository(ui, dest)
2017 2017 o = repo.findoutgoing(other, force=opts['force'])
2018 2018 if not o:
2019 2019 ui.status(_("no changes found\n"))
2020 2020 return
2021 2021 o = repo.changelog.nodesbetween(o)[0]
2022 2022 if opts['newest_first']:
2023 2023 o.reverse()
2024 2024 displayer = show_changeset(ui, repo, opts)
2025 2025 for n in o:
2026 2026 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2027 2027 if opts['no_merges'] and len(parents) == 2:
2028 2028 continue
2029 2029 displayer.show(changenode=n)
2030 2030 if opts['patch']:
2031 2031 prev = (parents and parents[0]) or nullid
2032 2032 dodiff(ui, ui, repo, prev, n)
2033 2033 ui.write("\n")
2034 2034
2035 2035 def parents(ui, repo, rev=None, branches=None, **opts):
2036 2036 """show the parents of the working dir or revision
2037 2037
2038 2038 Print the working directory's parent revisions.
2039 2039 """
2040 2040 if rev:
2041 2041 p = repo.changelog.parents(repo.lookup(rev))
2042 2042 else:
2043 2043 p = repo.dirstate.parents()
2044 2044
2045 2045 br = None
2046 2046 if branches is not None:
2047 2047 br = repo.branchlookup(p)
2048 2048 displayer = show_changeset(ui, repo, opts)
2049 2049 for n in p:
2050 2050 if n != nullid:
2051 2051 displayer.show(changenode=n, brinfo=br)
2052 2052
2053 2053 def paths(ui, repo, search=None):
2054 2054 """show definition of symbolic path names
2055 2055
2056 2056 Show definition of symbolic path name NAME. If no name is given, show
2057 2057 definition of available names.
2058 2058
2059 2059 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2060 2060 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2061 2061 """
2062 2062 if search:
2063 2063 for name, path in ui.configitems("paths"):
2064 2064 if name == search:
2065 2065 ui.write("%s\n" % path)
2066 2066 return
2067 2067 ui.warn(_("not found!\n"))
2068 2068 return 1
2069 2069 else:
2070 2070 for name, path in ui.configitems("paths"):
2071 2071 ui.write("%s = %s\n" % (name, path))
2072 2072
2073 2073 def pull(ui, repo, source="default", **opts):
2074 2074 """pull changes from the specified source
2075 2075
2076 2076 Pull changes from a remote repository to a local one.
2077 2077
2078 2078 This finds all changes from the repository at the specified path
2079 2079 or URL and adds them to the local repository. By default, this
2080 2080 does not update the copy of the project in the working directory.
2081 2081
2082 2082 Valid URLs are of the form:
2083 2083
2084 2084 local/filesystem/path
2085 2085 http://[user@]host[:port][/path]
2086 2086 https://[user@]host[:port][/path]
2087 2087 ssh://[user@]host[:port][/path]
2088 2088
2089 2089 Some notes about using SSH with Mercurial:
2090 2090 - SSH requires an accessible shell account on the destination machine
2091 2091 and a copy of hg in the remote path or specified with as remotecmd.
2092 2092 - /path is relative to the remote user's home directory by default.
2093 2093 Use two slashes at the start of a path to specify an absolute path.
2094 2094 - Mercurial doesn't use its own compression via SSH; the right thing
2095 2095 to do is to configure it in your ~/.ssh/ssh_config, e.g.:
2096 2096 Host *.mylocalnetwork.example.com
2097 2097 Compression off
2098 2098 Host *
2099 2099 Compression on
2100 2100 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2101 2101 with the --ssh command line option.
2102 2102 """
2103 2103 source = ui.expandpath(source)
2104 2104 ui.status(_('pulling from %s\n') % (source))
2105 2105
2106 2106 if opts['ssh']:
2107 2107 ui.setconfig("ui", "ssh", opts['ssh'])
2108 2108 if opts['remotecmd']:
2109 2109 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2110 2110
2111 2111 other = hg.repository(ui, source)
2112 2112 revs = None
2113 2113 if opts['rev'] and not other.local():
2114 2114 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
2115 2115 elif opts['rev']:
2116 2116 revs = [other.lookup(rev) for rev in opts['rev']]
2117 2117 r = repo.pull(other, heads=revs, force=opts['force'])
2118 2118 if not r:
2119 2119 if opts['update']:
2120 2120 return update(ui, repo)
2121 2121 else:
2122 2122 ui.status(_("(run 'hg update' to get a working copy)\n"))
2123 2123
2124 2124 return r
2125 2125
2126 2126 def push(ui, repo, dest="default-push", **opts):
2127 2127 """push changes to the specified destination
2128 2128
2129 2129 Push changes from the local repository to the given destination.
2130 2130
2131 2131 This is the symmetrical operation for pull. It helps to move
2132 2132 changes from the current repository to a different one. If the
2133 2133 destination is local this is identical to a pull in that directory
2134 2134 from the current one.
2135 2135
2136 2136 By default, push will refuse to run if it detects the result would
2137 2137 increase the number of remote heads. This generally indicates the
2138 2138 the client has forgotten to sync and merge before pushing.
2139 2139
2140 2140 Valid URLs are of the form:
2141 2141
2142 2142 local/filesystem/path
2143 2143 ssh://[user@]host[:port][/path]
2144 2144
2145 2145 Look at the help text for the pull command for important details
2146 2146 about ssh:// URLs.
2147 2147 """
2148 2148 dest = ui.expandpath(dest)
2149 2149 ui.status('pushing to %s\n' % (dest))
2150 2150
2151 2151 if opts['ssh']:
2152 2152 ui.setconfig("ui", "ssh", opts['ssh'])
2153 2153 if opts['remotecmd']:
2154 2154 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2155 2155
2156 2156 other = hg.repository(ui, dest)
2157 2157 revs = None
2158 2158 if opts['rev']:
2159 2159 revs = [repo.lookup(rev) for rev in opts['rev']]
2160 2160 r = repo.push(other, opts['force'], revs=revs)
2161 2161 return r
2162 2162
2163 2163 def rawcommit(ui, repo, *flist, **rc):
2164 2164 """raw commit interface (DEPRECATED)
2165 2165
2166 2166 (DEPRECATED)
2167 2167 Lowlevel commit, for use in helper scripts.
2168 2168
2169 2169 This command is not intended to be used by normal users, as it is
2170 2170 primarily useful for importing from other SCMs.
2171 2171
2172 2172 This command is now deprecated and will be removed in a future
2173 2173 release, please use debugsetparents and commit instead.
2174 2174 """
2175 2175
2176 2176 ui.warn(_("(the rawcommit command is deprecated)\n"))
2177 2177
2178 2178 message = rc['message']
2179 2179 if not message and rc['logfile']:
2180 2180 try:
2181 2181 message = open(rc['logfile']).read()
2182 2182 except IOError:
2183 2183 pass
2184 2184 if not message and not rc['logfile']:
2185 2185 raise util.Abort(_("missing commit message"))
2186 2186
2187 2187 files = relpath(repo, list(flist))
2188 2188 if rc['files']:
2189 2189 files += open(rc['files']).read().splitlines()
2190 2190
2191 2191 rc['parent'] = map(repo.lookup, rc['parent'])
2192 2192
2193 2193 try:
2194 2194 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2195 2195 except ValueError, inst:
2196 2196 raise util.Abort(str(inst))
2197 2197
2198 2198 def recover(ui, repo):
2199 2199 """roll back an interrupted transaction
2200 2200
2201 2201 Recover from an interrupted commit or pull.
2202 2202
2203 2203 This command tries to fix the repository status after an interrupted
2204 2204 operation. It should only be necessary when Mercurial suggests it.
2205 2205 """
2206 2206 if repo.recover():
2207 2207 return repo.verify()
2208 2208 return False
2209 2209
2210 2210 def remove(ui, repo, pat, *pats, **opts):
2211 2211 """remove the specified files on the next commit
2212 2212
2213 2213 Schedule the indicated files for removal from the repository.
2214 2214
2215 2215 This command schedules the files to be removed at the next commit.
2216 2216 This only removes files from the current branch, not from the
2217 2217 entire project history. If the files still exist in the working
2218 2218 directory, they will be deleted from it.
2219 2219 """
2220 2220 names = []
2221 2221 def okaytoremove(abs, rel, exact):
2222 2222 modified, added, removed, deleted, unknown = repo.changes(files=[abs])
2223 2223 reason = None
2224 2224 if modified and not opts['force']:
2225 2225 reason = _('is modified')
2226 2226 elif added:
2227 2227 reason = _('has been marked for add')
2228 2228 elif unknown:
2229 2229 reason = _('is not managed')
2230 2230 if reason:
2231 2231 if exact:
2232 2232 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2233 2233 else:
2234 2234 return True
2235 2235 for src, abs, rel, exact in walk(repo, (pat,) + pats, opts):
2236 2236 if okaytoremove(abs, rel, exact):
2237 2237 if ui.verbose or not exact:
2238 2238 ui.status(_('removing %s\n') % rel)
2239 2239 names.append(abs)
2240 2240 repo.remove(names, unlink=True)
2241 2241
2242 2242 def rename(ui, repo, *pats, **opts):
2243 2243 """rename files; equivalent of copy + remove
2244 2244
2245 2245 Mark dest as copies of sources; mark sources for deletion. If
2246 2246 dest is a directory, copies are put in that directory. If dest is
2247 2247 a file, there can only be one source.
2248 2248
2249 2249 By default, this command copies the contents of files as they
2250 2250 stand in the working directory. If invoked with --after, the
2251 2251 operation is recorded, but no copying is performed.
2252 2252
2253 2253 This command takes effect in the next commit.
2254 2254
2255 2255 NOTE: This command should be treated as experimental. While it
2256 2256 should properly record rename files, this information is not yet
2257 2257 fully used by merge, nor fully reported by log.
2258 2258 """
2259 2259 try:
2260 2260 wlock = repo.wlock(0)
2261 2261 errs, copied = docopy(ui, repo, pats, opts, wlock)
2262 2262 names = []
2263 2263 for abs, rel, exact in copied:
2264 2264 if ui.verbose or not exact:
2265 2265 ui.status(_('removing %s\n') % rel)
2266 2266 names.append(abs)
2267 2267 repo.remove(names, True, wlock)
2268 2268 except lock.LockHeld, inst:
2269 2269 ui.warn(_("repository lock held by %s\n") % inst.args[0])
2270 2270 errs = 1
2271 2271 return errs
2272 2272
2273 2273 def revert(ui, repo, *pats, **opts):
2274 2274 """revert modified files or dirs back to their unmodified states
2275 2275
2276 2276 In its default mode, it reverts any uncommitted modifications made
2277 2277 to the named files or directories. This restores the contents of
2278 2278 the affected files to an unmodified state.
2279 2279
2280 2280 Using the -r option, it reverts the given files or directories to
2281 2281 their state as of an earlier revision. This can be helpful to "roll
2282 2282 back" some or all of a change that should not have been committed.
2283 2283
2284 2284 Revert modifies the working directory. It does not commit any
2285 2285 changes, or change the parent of the current working directory.
2286 2286
2287 2287 If a file has been deleted, it is recreated. If the executable
2288 2288 mode of a file was changed, it is reset.
2289 2289
2290 2290 If names are given, all files matching the names are reverted.
2291 2291
2292 2292 If no arguments are given, all files in the repository are reverted.
2293 2293 """
2294 2294 node = opts['rev'] and repo.lookup(opts['rev']) or \
2295 2295 repo.dirstate.parents()[0]
2296 2296
2297 2297 files, choose, anypats = matchpats(repo, pats, opts)
2298 2298 modified, added, removed, deleted, unknown = repo.changes(match=choose)
2299 2299 repo.forget(added)
2300 2300 repo.undelete(removed)
2301 2301
2302 2302 return repo.update(node, False, True, choose, False)
2303 2303
2304 2304 def root(ui, repo):
2305 2305 """print the root (top) of the current working dir
2306 2306
2307 2307 Print the root directory of the current repository.
2308 2308 """
2309 2309 ui.write(repo.root + "\n")
2310 2310
2311 2311 def serve(ui, repo, **opts):
2312 2312 """export the repository via HTTP
2313 2313
2314 2314 Start a local HTTP repository browser and pull server.
2315 2315
2316 2316 By default, the server logs accesses to stdout and errors to
2317 2317 stderr. Use the "-A" and "-E" options to log to files.
2318 2318 """
2319 2319
2320 2320 if opts["stdio"]:
2321 2321 fin, fout = sys.stdin, sys.stdout
2322 2322 sys.stdout = sys.stderr
2323 2323
2324 2324 # Prevent insertion/deletion of CRs
2325 2325 util.set_binary(fin)
2326 2326 util.set_binary(fout)
2327 2327
2328 2328 def getarg():
2329 2329 argline = fin.readline()[:-1]
2330 2330 arg, l = argline.split()
2331 2331 val = fin.read(int(l))
2332 2332 return arg, val
2333 2333 def respond(v):
2334 2334 fout.write("%d\n" % len(v))
2335 2335 fout.write(v)
2336 2336 fout.flush()
2337 2337
2338 2338 lock = None
2339 2339
2340 2340 while 1:
2341 2341 cmd = fin.readline()[:-1]
2342 2342 if cmd == '':
2343 2343 return
2344 2344 if cmd == "heads":
2345 2345 h = repo.heads()
2346 2346 respond(" ".join(map(hex, h)) + "\n")
2347 2347 if cmd == "lock":
2348 2348 lock = repo.lock()
2349 2349 respond("")
2350 2350 if cmd == "unlock":
2351 2351 if lock:
2352 2352 lock.release()
2353 2353 lock = None
2354 2354 respond("")
2355 2355 elif cmd == "branches":
2356 2356 arg, nodes = getarg()
2357 2357 nodes = map(bin, nodes.split(" "))
2358 2358 r = []
2359 2359 for b in repo.branches(nodes):
2360 2360 r.append(" ".join(map(hex, b)) + "\n")
2361 2361 respond("".join(r))
2362 2362 elif cmd == "between":
2363 2363 arg, pairs = getarg()
2364 2364 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
2365 2365 r = []
2366 2366 for b in repo.between(pairs):
2367 2367 r.append(" ".join(map(hex, b)) + "\n")
2368 2368 respond("".join(r))
2369 2369 elif cmd == "changegroup":
2370 2370 nodes = []
2371 2371 arg, roots = getarg()
2372 2372 nodes = map(bin, roots.split(" "))
2373 2373
2374 2374 cg = repo.changegroup(nodes, 'serve')
2375 2375 while 1:
2376 2376 d = cg.read(4096)
2377 2377 if not d:
2378 2378 break
2379 2379 fout.write(d)
2380 2380
2381 2381 fout.flush()
2382 2382
2383 2383 elif cmd == "addchangegroup":
2384 2384 if not lock:
2385 2385 respond("not locked")
2386 2386 continue
2387 2387 respond("")
2388 2388
2389 2389 r = repo.addchangegroup(fin)
2390 2390 respond("")
2391 2391
2392 2392 optlist = "name templates style address port ipv6 accesslog errorlog"
2393 2393 for o in optlist.split():
2394 2394 if opts[o]:
2395 2395 ui.setconfig("web", o, opts[o])
2396 2396
2397 2397 if opts['daemon'] and not opts['daemon_pipefds']:
2398 2398 rfd, wfd = os.pipe()
2399 2399 args = sys.argv[:]
2400 2400 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2401 2401 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2402 2402 args[0], args)
2403 2403 os.close(wfd)
2404 2404 os.read(rfd, 1)
2405 2405 os._exit(0)
2406 2406
2407 2407 try:
2408 2408 httpd = hgweb.create_server(repo)
2409 2409 except socket.error, inst:
2410 2410 raise util.Abort(_('cannot start server: ') + inst.args[1])
2411 2411
2412 2412 if ui.verbose:
2413 2413 addr, port = httpd.socket.getsockname()
2414 2414 if addr == '0.0.0.0':
2415 2415 addr = socket.gethostname()
2416 2416 else:
2417 2417 try:
2418 2418 addr = socket.gethostbyaddr(addr)[0]
2419 2419 except socket.error:
2420 2420 pass
2421 2421 if port != 80:
2422 2422 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2423 2423 else:
2424 2424 ui.status(_('listening at http://%s/\n') % addr)
2425 2425
2426 2426 if opts['pid_file']:
2427 2427 fp = open(opts['pid_file'], 'w')
2428 2428 fp.write(str(os.getpid()))
2429 2429 fp.close()
2430 2430
2431 2431 if opts['daemon_pipefds']:
2432 2432 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2433 2433 os.close(rfd)
2434 2434 os.write(wfd, 'y')
2435 2435 os.close(wfd)
2436 2436 sys.stdout.flush()
2437 2437 sys.stderr.flush()
2438 2438 fd = os.open(util.nulldev, os.O_RDWR)
2439 2439 if fd != 0: os.dup2(fd, 0)
2440 2440 if fd != 1: os.dup2(fd, 1)
2441 2441 if fd != 2: os.dup2(fd, 2)
2442 2442 if fd not in (0, 1, 2): os.close(fd)
2443 2443
2444 2444 httpd.serve_forever()
2445 2445
2446 2446 def status(ui, repo, *pats, **opts):
2447 2447 """show changed files in the working directory
2448 2448
2449 2449 Show changed files in the repository. If names are
2450 2450 given, only files that match are shown.
2451 2451
2452 2452 The codes used to show the status of files are:
2453 2453 M = modified
2454 2454 A = added
2455 2455 R = removed
2456 2456 ! = deleted, but still tracked
2457 2457 ? = not tracked
2458 2458 """
2459 2459
2460 2460 files, matchfn, anypats = matchpats(repo, pats, opts)
2461 2461 cwd = (pats and repo.getcwd()) or ''
2462 2462 modified, added, removed, deleted, unknown = [
2463 2463 [util.pathto(cwd, x) for x in n]
2464 2464 for n in repo.changes(files=files, match=matchfn)]
2465 2465
2466 2466 changetypes = [('modified', 'M', modified),
2467 2467 ('added', 'A', added),
2468 2468 ('removed', 'R', removed),
2469 2469 ('deleted', '!', deleted),
2470 2470 ('unknown', '?', unknown)]
2471 2471
2472 2472 end = opts['print0'] and '\0' or '\n'
2473 2473
2474 2474 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2475 2475 or changetypes):
2476 2476 if opts['no_status']:
2477 2477 format = "%%s%s" % end
2478 2478 else:
2479 2479 format = "%s %%s%s" % (char, end)
2480 2480
2481 2481 for f in changes:
2482 2482 ui.write(format % f)
2483 2483
2484 2484 def tag(ui, repo, name, rev_=None, **opts):
2485 2485 """add a tag for the current tip or a given revision
2486 2486
2487 2487 Name a particular revision using <name>.
2488 2488
2489 2489 Tags are used to name particular revisions of the repository and are
2490 2490 very useful to compare different revision, to go back to significant
2491 2491 earlier versions or to mark branch points as releases, etc.
2492 2492
2493 2493 If no revision is given, the tip is used.
2494 2494
2495 2495 To facilitate version control, distribution, and merging of tags,
2496 2496 they are stored as a file named ".hgtags" which is managed
2497 2497 similarly to other project files and can be hand-edited if
2498 2498 necessary. The file '.hg/localtags' is used for local tags (not
2499 2499 shared among repositories).
2500 2500 """
2501 2501 if name == "tip":
2502 2502 raise util.Abort(_("the name 'tip' is reserved"))
2503 2503 if rev_ is not None:
2504 2504 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2505 2505 "please use 'hg tag [-r REV] NAME' instead\n"))
2506 2506 if opts['rev']:
2507 2507 raise util.Abort(_("use only one form to specify the revision"))
2508 2508 if opts['rev']:
2509 2509 rev_ = opts['rev']
2510 2510 if rev_:
2511 2511 r = hex(repo.lookup(rev_))
2512 2512 else:
2513 2513 r = hex(repo.changelog.tip())
2514 2514
2515 2515 disallowed = (revrangesep, '\r', '\n')
2516 2516 for c in disallowed:
2517 2517 if name.find(c) >= 0:
2518 2518 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2519 2519
2520 2520 repo.hook('pretag', throw=True, node=r, tag=name,
2521 2521 local=int(not not opts['local']))
2522 2522
2523 2523 if opts['local']:
2524 2524 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2525 2525 repo.hook('tag', node=r, tag=name, local=1)
2526 2526 return
2527 2527
2528 2528 for x in repo.changes():
2529 2529 if ".hgtags" in x:
2530 2530 raise util.Abort(_("working copy of .hgtags is changed "
2531 2531 "(please commit .hgtags manually)"))
2532 2532
2533 2533 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2534 2534 if repo.dirstate.state(".hgtags") == '?':
2535 2535 repo.add([".hgtags"])
2536 2536
2537 2537 message = (opts['message'] or
2538 2538 _("Added tag %s for changeset %s") % (name, r))
2539 2539 try:
2540 2540 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2541 2541 repo.hook('tag', node=r, tag=name, local=0)
2542 2542 except ValueError, inst:
2543 2543 raise util.Abort(str(inst))
2544 2544
2545 2545 def tags(ui, repo):
2546 2546 """list repository tags
2547 2547
2548 2548 List the repository tags.
2549 2549
2550 2550 This lists both regular and local tags.
2551 2551 """
2552 2552
2553 2553 l = repo.tagslist()
2554 2554 l.reverse()
2555 2555 for t, n in l:
2556 2556 try:
2557 2557 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2558 2558 except KeyError:
2559 2559 r = " ?:?"
2560 2560 ui.write("%-30s %s\n" % (t, r))
2561 2561
2562 2562 def tip(ui, repo, **opts):
2563 2563 """show the tip revision
2564 2564
2565 2565 Show the tip revision.
2566 2566 """
2567 2567 n = repo.changelog.tip()
2568 2568 br = None
2569 2569 if opts['branches']:
2570 2570 br = repo.branchlookup([n])
2571 2571 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
2572 2572 if opts['patch']:
2573 2573 dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
2574 2574
2575 2575 def unbundle(ui, repo, fname, **opts):
2576 2576 """apply a changegroup file
2577 2577
2578 2578 Apply a compressed changegroup file generated by the bundle
2579 2579 command.
2580 2580 """
2581 2581 f = urllib.urlopen(fname)
2582 2582
2583 2583 header = f.read(6)
2584 2584 if not header.startswith("HG"):
2585 2585 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2586 2586 elif not header.startswith("HG10"):
2587 2587 raise util.Abort(_("%s: unknown bundle version") % fname)
2588 2588 elif header == "HG10BZ":
2589 2589 def generator(f):
2590 2590 zd = bz2.BZ2Decompressor()
2591 2591 zd.decompress("BZ")
2592 2592 for chunk in f:
2593 2593 yield zd.decompress(chunk)
2594 2594 elif header == "HG10UN":
2595 2595 def generator(f):
2596 2596 for chunk in f:
2597 2597 yield chunk
2598 2598 else:
2599 2599 raise util.Abort(_("%s: unknown bundle compression type")
2600 2600 % fname)
2601 2601 gen = generator(util.filechunkiter(f, 4096))
2602 2602 if repo.addchangegroup(util.chunkbuffer(gen)):
2603 2603 return 1
2604 2604
2605 2605 if opts['update']:
2606 2606 return update(ui, repo)
2607 2607 else:
2608 2608 ui.status(_("(run 'hg update' to get a working copy)\n"))
2609 2609
2610 2610 def undo(ui, repo):
2611 2611 """undo the last commit or pull
2612 2612
2613 2613 Roll back the last pull or commit transaction on the
2614 2614 repository, restoring the project to its earlier state.
2615 2615
2616 2616 This command should be used with care. There is only one level of
2617 2617 undo and there is no redo.
2618 2618
2619 2619 This command is not intended for use on public repositories. Once
2620 2620 a change is visible for pull by other users, undoing it locally is
2621 2621 ineffective.
2622 2622 """
2623 2623 repo.undo()
2624 2624
2625 2625 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2626 2626 branch=None, **opts):
2627 2627 """update or merge working directory
2628 2628
2629 2629 Update the working directory to the specified revision.
2630 2630
2631 2631 If there are no outstanding changes in the working directory and
2632 2632 there is a linear relationship between the current version and the
2633 2633 requested version, the result is the requested version.
2634 2634
2635 2635 Otherwise the result is a merge between the contents of the
2636 2636 current working directory and the requested version. Files that
2637 2637 changed between either parent are marked as changed for the next
2638 2638 commit and a commit must be performed before any further updates
2639 2639 are allowed.
2640 2640
2641 2641 By default, update will refuse to run if doing so would require
2642 2642 merging or discarding local changes.
2643 2643 """
2644 2644 if branch:
2645 2645 br = repo.branchlookup(branch=branch)
2646 2646 found = []
2647 2647 for x in br:
2648 2648 if branch in br[x]:
2649 2649 found.append(x)
2650 2650 if len(found) > 1:
2651 2651 ui.warn(_("Found multiple heads for %s\n") % branch)
2652 2652 for x in found:
2653 2653 show_changeset(ui, repo, opts).show(changenode=x, brinfo=br)
2654 2654 return 1
2655 2655 if len(found) == 1:
2656 2656 node = found[0]
2657 2657 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2658 2658 else:
2659 2659 ui.warn(_("branch %s not found\n") % (branch))
2660 2660 return 1
2661 2661 else:
2662 2662 node = node and repo.lookup(node) or repo.changelog.tip()
2663 2663 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2664 2664
2665 2665 def verify(ui, repo):
2666 2666 """verify the integrity of the repository
2667 2667
2668 2668 Verify the integrity of the current repository.
2669 2669
2670 2670 This will perform an extensive check of the repository's
2671 2671 integrity, validating the hashes and checksums of each entry in
2672 2672 the changelog, manifest, and tracked files, as well as the
2673 2673 integrity of their crosslinks and indices.
2674 2674 """
2675 2675 return repo.verify()
2676 2676
2677 2677 # Command options and aliases are listed here, alphabetically
2678 2678
2679 2679 table = {
2680 2680 "^add":
2681 2681 (add,
2682 2682 [('I', 'include', [], _('include names matching the given patterns')),
2683 2683 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2684 2684 _('hg add [OPTION]... [FILE]...')),
2685 2685 "addremove":
2686 2686 (addremove,
2687 2687 [('I', 'include', [], _('include names matching the given patterns')),
2688 2688 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2689 2689 _('hg addremove [OPTION]... [FILE]...')),
2690 2690 "^annotate":
2691 2691 (annotate,
2692 2692 [('r', 'rev', '', _('annotate the specified revision')),
2693 2693 ('a', 'text', None, _('treat all files as text')),
2694 2694 ('u', 'user', None, _('list the author')),
2695 2695 ('d', 'date', None, _('list the date')),
2696 2696 ('n', 'number', None, _('list the revision number (default)')),
2697 2697 ('c', 'changeset', None, _('list the changeset')),
2698 2698 ('I', 'include', [], _('include names matching the given patterns')),
2699 2699 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2700 2700 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2701 2701 "bundle":
2702 2702 (bundle,
2703 2703 [('f', 'force', None,
2704 2704 _('run even when remote repository is unrelated'))],
2705 2705 _('hg bundle FILE DEST')),
2706 2706 "cat":
2707 2707 (cat,
2708 2708 [('o', 'output', '', _('print output to file with formatted name')),
2709 2709 ('r', 'rev', '', _('print the given revision')),
2710 2710 ('I', 'include', [], _('include names matching the given patterns')),
2711 2711 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2712 2712 _('hg cat [OPTION]... FILE...')),
2713 2713 "^clone":
2714 2714 (clone,
2715 2715 [('U', 'noupdate', None, _('do not update the new working directory')),
2716 2716 ('r', 'rev', [],
2717 2717 _('a changeset you would like to have after cloning')),
2718 2718 ('', 'pull', None, _('use pull protocol to copy metadata')),
2719 2719 ('e', 'ssh', '', _('specify ssh command to use')),
2720 2720 ('', 'remotecmd', '',
2721 2721 _('specify hg command to run on the remote side'))],
2722 2722 _('hg clone [OPTION]... SOURCE [DEST]')),
2723 2723 "^commit|ci":
2724 2724 (commit,
2725 2725 [('A', 'addremove', None, _('run addremove during commit')),
2726 2726 ('m', 'message', '', _('use <text> as commit message')),
2727 2727 ('l', 'logfile', '', _('read the commit message from <file>')),
2728 2728 ('d', 'date', '', _('record datecode as commit date')),
2729 2729 ('u', 'user', '', _('record user as commiter')),
2730 2730 ('I', 'include', [], _('include names matching the given patterns')),
2731 2731 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2732 2732 _('hg commit [OPTION]... [FILE]...')),
2733 2733 "copy|cp":
2734 2734 (copy,
2735 2735 [('A', 'after', None, _('record a copy that has already occurred')),
2736 2736 ('f', 'force', None,
2737 2737 _('forcibly copy over an existing managed file')),
2738 2738 ('I', 'include', [], _('include names matching the given patterns')),
2739 2739 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2740 2740 _('hg copy [OPTION]... [SOURCE]... DEST')),
2741 2741 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2742 2742 "debugcomplete": (debugcomplete, [], _('debugcomplete CMD')),
2743 2743 "debugrebuildstate":
2744 2744 (debugrebuildstate,
2745 2745 [('r', 'rev', '', _('revision to rebuild to'))],
2746 2746 _('debugrebuildstate [-r REV] [REV]')),
2747 2747 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2748 2748 "debugconfig": (debugconfig, [], _('debugconfig')),
2749 2749 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2750 2750 "debugstate": (debugstate, [], _('debugstate')),
2751 2751 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2752 2752 "debugindex": (debugindex, [], _('debugindex FILE')),
2753 2753 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2754 2754 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2755 2755 "debugwalk":
2756 2756 (debugwalk,
2757 2757 [('I', 'include', [], _('include names matching the given patterns')),
2758 2758 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2759 2759 _('debugwalk [OPTION]... [FILE]...')),
2760 2760 "^diff":
2761 2761 (diff,
2762 2762 [('r', 'rev', [], _('revision')),
2763 2763 ('a', 'text', None, _('treat all files as text')),
2764 2764 ('p', 'show-function', None,
2765 2765 _('show which function each change is in')),
2766 2766 ('w', 'ignore-all-space', None,
2767 2767 _('ignore white space when comparing lines')),
2768 2768 ('I', 'include', [], _('include names matching the given patterns')),
2769 2769 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2770 2770 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2771 2771 "^export":
2772 2772 (export,
2773 2773 [('o', 'output', '', _('print output to file with formatted name')),
2774 2774 ('a', 'text', None, _('treat all files as text')),
2775 2775 ('', 'switch-parent', None, _('diff against the second parent'))],
2776 2776 _('hg export [-a] [-o OUTFILESPEC] REV...')),
2777 2777 "forget":
2778 2778 (forget,
2779 2779 [('I', 'include', [], _('include names matching the given patterns')),
2780 2780 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2781 2781 _('hg forget [OPTION]... FILE...')),
2782 2782 "grep":
2783 2783 (grep,
2784 2784 [('0', 'print0', None, _('end fields with NUL')),
2785 2785 ('', 'all', None, _('print all revisions that match')),
2786 2786 ('i', 'ignore-case', None, _('ignore case when matching')),
2787 2787 ('l', 'files-with-matches', None,
2788 2788 _('print only filenames and revs that match')),
2789 2789 ('n', 'line-number', None, _('print matching line numbers')),
2790 2790 ('r', 'rev', [], _('search in given revision range')),
2791 2791 ('u', 'user', None, _('print user who committed change')),
2792 2792 ('I', 'include', [], _('include names matching the given patterns')),
2793 2793 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2794 2794 _('hg grep [OPTION]... PATTERN [FILE]...')),
2795 2795 "heads":
2796 2796 (heads,
2797 2797 [('b', 'branches', None, _('show branches')),
2798 2798 ('', 'style', '', _('display using template map file')),
2799 2799 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2800 2800 ('', 'template', '', _('display with template'))],
2801 2801 _('hg heads [-b] [-r <rev>]')),
2802 2802 "help": (help_, [], _('hg help [COMMAND]')),
2803 2803 "identify|id": (identify, [], _('hg identify')),
2804 2804 "import|patch":
2805 2805 (import_,
2806 2806 [('p', 'strip', 1,
2807 2807 _('directory strip option for patch. This has the same\n') +
2808 2808 _('meaning as the corresponding patch option')),
2809 2809 ('b', 'base', '', _('base path')),
2810 2810 ('f', 'force', None,
2811 2811 _('skip check for outstanding uncommitted changes'))],
2812 2812 _('hg import [-p NUM] [-b BASE] [-f] PATCH...')),
2813 2813 "incoming|in": (incoming,
2814 2814 [('M', 'no-merges', None, _('do not show merges')),
2815 2815 ('f', 'force', None,
2816 2816 _('run even when remote repository is unrelated')),
2817 2817 ('', 'style', '', _('display using template map file')),
2818 2818 ('n', 'newest-first', None, _('show newest record first')),
2819 2819 ('', 'bundle', '', _('file to store the bundles into')),
2820 2820 ('p', 'patch', None, _('show patch')),
2821 2821 ('', 'template', '', _('display with template')),
2822 2822 ('e', 'ssh', '', _('specify ssh command to use')),
2823 2823 ('', 'remotecmd', '',
2824 2824 _('specify hg command to run on the remote side'))],
2825 2825 _('hg incoming [-p] [-n] [-M] [--bundle FILENAME] [SOURCE]')),
2826 2826 "^init": (init, [], _('hg init [DEST]')),
2827 2827 "locate":
2828 2828 (locate,
2829 2829 [('r', 'rev', '', _('search the repository as it stood at rev')),
2830 2830 ('0', 'print0', None,
2831 2831 _('end filenames with NUL, for use with xargs')),
2832 2832 ('f', 'fullpath', None,
2833 2833 _('print complete paths from the filesystem root')),
2834 2834 ('I', 'include', [], _('include names matching the given patterns')),
2835 2835 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2836 2836 _('hg locate [OPTION]... [PATTERN]...')),
2837 2837 "^log|history":
2838 2838 (log,
2839 2839 [('b', 'branches', None, _('show branches')),
2840 2840 ('k', 'keyword', [], _('search for a keyword')),
2841 2841 ('l', 'limit', '', _('limit number of changes displayed')),
2842 2842 ('r', 'rev', [], _('show the specified revision or range')),
2843 2843 ('M', 'no-merges', None, _('do not show merges')),
2844 2844 ('', 'style', '', _('display using template map file')),
2845 2845 ('m', 'only-merges', None, _('show only merges')),
2846 2846 ('p', 'patch', None, _('show patch')),
2847 2847 ('', 'template', '', _('display with template')),
2848 2848 ('I', 'include', [], _('include names matching the given patterns')),
2849 2849 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2850 2850 _('hg log [OPTION]... [FILE]')),
2851 2851 "manifest": (manifest, [], _('hg manifest [REV]')),
2852 2852 "outgoing|out": (outgoing,
2853 2853 [('M', 'no-merges', None, _('do not show merges')),
2854 2854 ('f', 'force', None,
2855 2855 _('run even when remote repository is unrelated')),
2856 2856 ('p', 'patch', None, _('show patch')),
2857 2857 ('', 'style', '', _('display using template map file')),
2858 2858 ('n', 'newest-first', None, _('show newest record first')),
2859 2859 ('', 'template', '', _('display with template')),
2860 2860 ('e', 'ssh', '', _('specify ssh command to use')),
2861 2861 ('', 'remotecmd', '',
2862 2862 _('specify hg command to run on the remote side'))],
2863 2863 _('hg outgoing [-M] [-p] [-n] [DEST]')),
2864 2864 "^parents":
2865 2865 (parents,
2866 2866 [('b', 'branches', None, _('show branches')),
2867 2867 ('', 'style', '', _('display using template map file')),
2868 2868 ('', 'template', '', _('display with template'))],
2869 2869 _('hg parents [-b] [REV]')),
2870 2870 "paths": (paths, [], _('hg paths [NAME]')),
2871 2871 "^pull":
2872 2872 (pull,
2873 2873 [('u', 'update', None,
2874 2874 _('update the working directory to tip after pull')),
2875 2875 ('e', 'ssh', '', _('specify ssh command to use')),
2876 2876 ('f', 'force', None,
2877 2877 _('run even when remote repository is unrelated')),
2878 2878 ('r', 'rev', [], _('a specific revision you would like to pull')),
2879 2879 ('', 'remotecmd', '',
2880 2880 _('specify hg command to run on the remote side'))],
2881 2881 _('hg pull [-u] [-e FILE] [-r REV]... [--remotecmd FILE] [SOURCE]')),
2882 2882 "^push":
2883 2883 (push,
2884 2884 [('f', 'force', None, _('force push')),
2885 2885 ('e', 'ssh', '', _('specify ssh command to use')),
2886 2886 ('r', 'rev', [], _('a specific revision you would like to push')),
2887 2887 ('', 'remotecmd', '',
2888 2888 _('specify hg command to run on the remote side'))],
2889 2889 _('hg push [-f] [-e FILE] [-r REV]... [--remotecmd FILE] [DEST]')),
2890 2890 "debugrawcommit|rawcommit":
2891 2891 (rawcommit,
2892 2892 [('p', 'parent', [], _('parent')),
2893 2893 ('d', 'date', '', _('date code')),
2894 2894 ('u', 'user', '', _('user')),
2895 2895 ('F', 'files', '', _('file list')),
2896 2896 ('m', 'message', '', _('commit message')),
2897 2897 ('l', 'logfile', '', _('commit message file'))],
2898 2898 _('hg debugrawcommit [OPTION]... [FILE]...')),
2899 2899 "recover": (recover, [], _('hg recover')),
2900 2900 "^remove|rm":
2901 2901 (remove,
2902 2902 [('f', 'force', None, _('remove file even if modified')),
2903 2903 ('I', 'include', [], _('include names matching the given patterns')),
2904 2904 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2905 2905 _('hg remove [OPTION]... FILE...')),
2906 2906 "rename|mv":
2907 2907 (rename,
2908 2908 [('A', 'after', None, _('record a rename that has already occurred')),
2909 2909 ('f', 'force', None,
2910 2910 _('forcibly copy over an existing managed file')),
2911 2911 ('I', 'include', [], _('include names matching the given patterns')),
2912 2912 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2913 2913 _('hg rename [OPTION]... SOURCE... DEST')),
2914 2914 "^revert":
2915 2915 (revert,
2916 2916 [('r', 'rev', '', _('revision to revert to')),
2917 2917 ('I', 'include', [], _('include names matching the given patterns')),
2918 2918 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2919 2919 _('hg revert [-r REV] [NAME]...')),
2920 2920 "root": (root, [], _('hg root')),
2921 2921 "^serve":
2922 2922 (serve,
2923 2923 [('A', 'accesslog', '', _('name of access log file to write to')),
2924 2924 ('d', 'daemon', None, _('run server in background')),
2925 2925 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
2926 2926 ('E', 'errorlog', '', _('name of error log file to write to')),
2927 2927 ('p', 'port', 0, _('port to use (default: 8000)')),
2928 2928 ('a', 'address', '', _('address to use')),
2929 2929 ('n', 'name', '',
2930 2930 _('name to show in web pages (default: working dir)')),
2931 2931 ('', 'pid-file', '', _('name of file to write process ID to')),
2932 2932 ('', 'stdio', None, _('for remote clients')),
2933 2933 ('t', 'templates', '', _('web templates to use')),
2934 2934 ('', 'style', '', _('template style to use')),
2935 2935 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
2936 2936 _('hg serve [OPTION]...')),
2937 2937 "^status|st":
2938 2938 (status,
2939 2939 [('m', 'modified', None, _('show only modified files')),
2940 2940 ('a', 'added', None, _('show only added files')),
2941 2941 ('r', 'removed', None, _('show only removed files')),
2942 2942 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
2943 2943 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
2944 2944 ('n', 'no-status', None, _('hide status prefix')),
2945 2945 ('0', 'print0', None,
2946 2946 _('end filenames with NUL, for use with xargs')),
2947 2947 ('I', 'include', [], _('include names matching the given patterns')),
2948 2948 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2949 2949 _('hg status [OPTION]... [FILE]...')),
2950 2950 "tag":
2951 2951 (tag,
2952 2952 [('l', 'local', None, _('make the tag local')),
2953 2953 ('m', 'message', '', _('message for tag commit log entry')),
2954 2954 ('d', 'date', '', _('record datecode as commit date')),
2955 2955 ('u', 'user', '', _('record user as commiter')),
2956 2956 ('r', 'rev', '', _('revision to tag'))],
2957 2957 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
2958 2958 "tags": (tags, [], _('hg tags')),
2959 2959 "tip":
2960 2960 (tip,
2961 2961 [('b', 'branches', None, _('show branches')),
2962 2962 ('', 'style', '', _('display using template map file')),
2963 2963 ('p', 'patch', None, _('show patch')),
2964 2964 ('', 'template', '', _('display with template'))],
2965 2965 _('hg tip [-b] [-p]')),
2966 2966 "unbundle":
2967 2967 (unbundle,
2968 2968 [('u', 'update', None,
2969 2969 _('update the working directory to tip after unbundle'))],
2970 2970 _('hg unbundle [-u] FILE')),
2971 2971 "undo": (undo, [], _('hg undo')),
2972 2972 "^update|up|checkout|co":
2973 2973 (update,
2974 2974 [('b', 'branch', '', _('checkout the head of a specific branch')),
2975 2975 ('', 'style', '', _('display using template map file')),
2976 2976 ('m', 'merge', None, _('allow merging of branches')),
2977 2977 ('C', 'clean', None, _('overwrite locally modified files')),
2978 2978 ('f', 'force', None, _('force a merge with outstanding changes')),
2979 2979 ('', 'template', '', _('display with template'))],
2980 2980 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
2981 2981 "verify": (verify, [], _('hg verify')),
2982 2982 "version": (show_version, [], _('hg version')),
2983 2983 }
2984 2984
2985 2985 globalopts = [
2986 2986 ('R', 'repository', '',
2987 2987 _('repository root directory or symbolic path name')),
2988 2988 ('', 'cwd', '', _('change working directory')),
2989 2989 ('y', 'noninteractive', None,
2990 2990 _('do not prompt, assume \'yes\' for any required answers')),
2991 2991 ('q', 'quiet', None, _('suppress output')),
2992 2992 ('v', 'verbose', None, _('enable additional output')),
2993 2993 ('', 'debug', None, _('enable debugging output')),
2994 2994 ('', 'debugger', None, _('start debugger')),
2995 2995 ('', 'traceback', None, _('print traceback on exception')),
2996 2996 ('', 'time', None, _('time how long the command takes')),
2997 2997 ('', 'profile', None, _('print command execution profile')),
2998 2998 ('', 'version', None, _('output version information and exit')),
2999 2999 ('h', 'help', None, _('display help and exit')),
3000 3000 ]
3001 3001
3002 3002 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3003 3003 " debugindex debugindexdot")
3004 3004 optionalrepo = ("paths debugconfig")
3005 3005
3006 3006 def findpossible(cmd):
3007 3007 """
3008 3008 Return cmd -> (aliases, command table entry)
3009 3009 for each matching command
3010 3010 """
3011 3011 choice = {}
3012 3012 debugchoice = {}
3013 3013 for e in table.keys():
3014 3014 aliases = e.lstrip("^").split("|")
3015 3015 if cmd in aliases:
3016 3016 choice[cmd] = (aliases, table[e])
3017 3017 continue
3018 3018 for a in aliases:
3019 3019 if a.startswith(cmd):
3020 3020 if aliases[0].startswith("debug"):
3021 3021 debugchoice[a] = (aliases, table[e])
3022 3022 else:
3023 3023 choice[a] = (aliases, table[e])
3024 3024 break
3025 3025
3026 3026 if not choice and debugchoice:
3027 3027 choice = debugchoice
3028 3028
3029 3029 return choice
3030 3030
3031 3031 def find(cmd):
3032 3032 """Return (aliases, command table entry) for command string."""
3033 3033 choice = findpossible(cmd)
3034 3034
3035 3035 if choice.has_key(cmd):
3036 3036 return choice[cmd]
3037 3037
3038 3038 if len(choice) > 1:
3039 3039 clist = choice.keys()
3040 3040 clist.sort()
3041 3041 raise AmbiguousCommand(cmd, clist)
3042 3042
3043 3043 if choice:
3044 3044 return choice.values()[0]
3045 3045
3046 3046 raise UnknownCommand(cmd)
3047 3047
3048 3048 class SignalInterrupt(Exception):
3049 3049 """Exception raised on SIGTERM and SIGHUP."""
3050 3050
3051 3051 def catchterm(*args):
3052 3052 raise SignalInterrupt
3053 3053
3054 3054 def run():
3055 3055 sys.exit(dispatch(sys.argv[1:]))
3056 3056
3057 3057 class ParseError(Exception):
3058 3058 """Exception raised on errors in parsing the command line."""
3059 3059
3060 3060 def parse(ui, args):
3061 3061 options = {}
3062 3062 cmdoptions = {}
3063 3063
3064 3064 try:
3065 3065 args = fancyopts.fancyopts(args, globalopts, options)
3066 3066 except fancyopts.getopt.GetoptError, inst:
3067 3067 raise ParseError(None, inst)
3068 3068
3069 3069 if args:
3070 3070 cmd, args = args[0], args[1:]
3071 3071 aliases, i = find(cmd)
3072 3072 cmd = aliases[0]
3073 3073 defaults = ui.config("defaults", cmd)
3074 3074 if defaults:
3075 3075 args = defaults.split() + args
3076 3076 c = list(i[1])
3077 3077 else:
3078 3078 cmd = None
3079 3079 c = []
3080 3080
3081 3081 # combine global options into local
3082 3082 for o in globalopts:
3083 3083 c.append((o[0], o[1], options[o[1]], o[3]))
3084 3084
3085 3085 try:
3086 3086 args = fancyopts.fancyopts(args, c, cmdoptions)
3087 3087 except fancyopts.getopt.GetoptError, inst:
3088 3088 raise ParseError(cmd, inst)
3089 3089
3090 3090 # separate global options back out
3091 3091 for o in globalopts:
3092 3092 n = o[1]
3093 3093 options[n] = cmdoptions[n]
3094 3094 del cmdoptions[n]
3095 3095
3096 3096 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3097 3097
3098 3098 def dispatch(args):
3099 3099 signal.signal(signal.SIGTERM, catchterm)
3100 3100 try:
3101 3101 signal.signal(signal.SIGHUP, catchterm)
3102 3102 except AttributeError:
3103 3103 pass
3104 3104
3105 3105 try:
3106 3106 u = ui.ui()
3107 3107 except util.Abort, inst:
3108 3108 sys.stderr.write(_("abort: %s\n") % inst)
3109 3109 sys.exit(1)
3110 3110
3111 3111 external = []
3112 3112 for x in u.extensions():
3113 3113 def on_exception(exc, inst):
3114 3114 u.warn(_("*** failed to import extension %s\n") % x[1])
3115 3115 u.warn("%s\n" % inst)
3116 3116 if "--traceback" in sys.argv[1:]:
3117 3117 traceback.print_exc()
3118 3118 if x[1]:
3119 3119 try:
3120 3120 mod = imp.load_source(x[0], x[1])
3121 3121 except Exception, inst:
3122 3122 on_exception(Exception, inst)
3123 3123 continue
3124 3124 else:
3125 3125 def importh(name):
3126 3126 mod = __import__(name)
3127 3127 components = name.split('.')
3128 3128 for comp in components[1:]:
3129 3129 mod = getattr(mod, comp)
3130 3130 return mod
3131 3131 try:
3132 3132 try:
3133 3133 mod = importh("hgext." + x[0])
3134 3134 except ImportError:
3135 3135 mod = importh(x[0])
3136 3136 except Exception, inst:
3137 3137 on_exception(Exception, inst)
3138 3138 continue
3139 3139
3140 3140 external.append(mod)
3141 3141 for x in external:
3142 3142 cmdtable = getattr(x, 'cmdtable', {})
3143 3143 for t in cmdtable:
3144 3144 if t in table:
3145 3145 u.warn(_("module %s overrides %s\n") % (x.__name__, t))
3146 3146 table.update(cmdtable)
3147 3147
3148 3148 try:
3149 3149 cmd, func, args, options, cmdoptions = parse(u, args)
3150 3150 if options["time"]:
3151 3151 def get_times():
3152 3152 t = os.times()
3153 3153 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3154 3154 t = (t[0], t[1], t[2], t[3], time.clock())
3155 3155 return t
3156 3156 s = get_times()
3157 3157 def print_time():
3158 3158 t = get_times()
3159 3159 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3160 3160 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3161 3161 atexit.register(print_time)
3162 3162
3163 3163 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3164 3164 not options["noninteractive"])
3165 3165
3166 3166 # enter the debugger before command execution
3167 3167 if options['debugger']:
3168 3168 pdb.set_trace()
3169 3169
3170 3170 try:
3171 3171 if options['cwd']:
3172 3172 try:
3173 3173 os.chdir(options['cwd'])
3174 3174 except OSError, inst:
3175 3175 raise util.Abort('%s: %s' %
3176 3176 (options['cwd'], inst.strerror))
3177 3177
3178 3178 path = u.expandpath(options["repository"]) or ""
3179 3179 repo = path and hg.repository(u, path=path) or None
3180 3180
3181 3181 if options['help']:
3182 3182 help_(u, cmd, options['version'])
3183 3183 sys.exit(0)
3184 3184 elif options['version']:
3185 3185 show_version(u)
3186 3186 sys.exit(0)
3187 3187 elif not cmd:
3188 3188 help_(u, 'shortlist')
3189 3189 sys.exit(0)
3190 3190
3191 3191 if cmd not in norepo.split():
3192 3192 try:
3193 3193 if not repo:
3194 3194 repo = hg.repository(u, path=path)
3195 3195 u = repo.ui
3196 3196 for x in external:
3197 3197 if hasattr(x, 'reposetup'):
3198 3198 x.reposetup(u, repo)
3199 3199 except hg.RepoError:
3200 3200 if cmd not in optionalrepo.split():
3201 3201 raise
3202 3202 d = lambda: func(u, repo, *args, **cmdoptions)
3203 3203 else:
3204 3204 d = lambda: func(u, *args, **cmdoptions)
3205 3205
3206 3206 try:
3207 3207 if options['profile']:
3208 3208 import hotshot, hotshot.stats
3209 3209 prof = hotshot.Profile("hg.prof")
3210 3210 try:
3211 3211 try:
3212 3212 return prof.runcall(d)
3213 3213 except:
3214 3214 try:
3215 3215 u.warn(_('exception raised - generating '
3216 3216 'profile anyway\n'))
3217 3217 except:
3218 3218 pass
3219 3219 raise
3220 3220 finally:
3221 3221 prof.close()
3222 3222 stats = hotshot.stats.load("hg.prof")
3223 3223 stats.strip_dirs()
3224 3224 stats.sort_stats('time', 'calls')
3225 3225 stats.print_stats(40)
3226 3226 else:
3227 3227 return d()
3228 3228 finally:
3229 3229 u.flush()
3230 3230 except:
3231 3231 # enter the debugger when we hit an exception
3232 3232 if options['debugger']:
3233 3233 pdb.post_mortem(sys.exc_info()[2])
3234 3234 if options['traceback']:
3235 3235 traceback.print_exc()
3236 3236 raise
3237 3237 except ParseError, inst:
3238 3238 if inst.args[0]:
3239 3239 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3240 3240 help_(u, inst.args[0])
3241 3241 else:
3242 3242 u.warn(_("hg: %s\n") % inst.args[1])
3243 3243 help_(u, 'shortlist')
3244 3244 sys.exit(-1)
3245 3245 except AmbiguousCommand, inst:
3246 3246 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3247 3247 (inst.args[0], " ".join(inst.args[1])))
3248 3248 sys.exit(1)
3249 3249 except UnknownCommand, inst:
3250 3250 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3251 3251 help_(u, 'shortlist')
3252 3252 sys.exit(1)
3253 3253 except hg.RepoError, inst:
3254 3254 u.warn(_("abort: "), inst, "!\n")
3255 3255 except revlog.RevlogError, inst:
3256 3256 u.warn(_("abort: "), inst, "!\n")
3257 3257 except SignalInterrupt:
3258 3258 u.warn(_("killed!\n"))
3259 3259 except KeyboardInterrupt:
3260 3260 try:
3261 3261 u.warn(_("interrupted!\n"))
3262 3262 except IOError, inst:
3263 3263 if inst.errno == errno.EPIPE:
3264 3264 if u.debugflag:
3265 3265 u.warn(_("\nbroken pipe\n"))
3266 3266 else:
3267 3267 raise
3268 3268 except IOError, inst:
3269 3269 if hasattr(inst, "code"):
3270 3270 u.warn(_("abort: %s\n") % inst)
3271 3271 elif hasattr(inst, "reason"):
3272 3272 u.warn(_("abort: error: %s\n") % inst.reason[1])
3273 3273 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3274 3274 if u.debugflag:
3275 3275 u.warn(_("broken pipe\n"))
3276 3276 elif getattr(inst, "strerror", None):
3277 3277 if getattr(inst, "filename", None):
3278 3278 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
3279 3279 else:
3280 3280 u.warn(_("abort: %s\n") % inst.strerror)
3281 3281 else:
3282 3282 raise
3283 3283 except OSError, inst:
3284 3284 if hasattr(inst, "filename"):
3285 3285 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3286 3286 else:
3287 3287 u.warn(_("abort: %s\n") % inst.strerror)
3288 3288 except util.Abort, inst:
3289 3289 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
3290 3290 sys.exit(1)
3291 3291 except TypeError, inst:
3292 3292 # was this an argument error?
3293 3293 tb = traceback.extract_tb(sys.exc_info()[2])
3294 3294 if len(tb) > 2: # no
3295 3295 raise
3296 3296 u.debug(inst, "\n")
3297 3297 u.warn(_("%s: invalid arguments\n") % cmd)
3298 3298 help_(u, cmd)
3299 3299 except SystemExit:
3300 3300 # don't catch this in the catch-all below
3301 3301 raise
3302 3302 except:
3303 3303 u.warn(_("** unknown exception encountered, details follow\n"))
3304 3304 u.warn(_("** report bug details to mercurial@selenic.com\n"))
3305 3305 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3306 3306 % version.get_version())
3307 3307 raise
3308 3308
3309 3309 sys.exit(-1)
@@ -1,1904 +1,1904 b''
1 1 # localrepo.py - read/write repository class 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, util
9 9 import filelog, manifest, changelog, dirstate, repo
10 10 from node import *
11 11 from i18n import gettext as _
12 12 from demandload import *
13 13 demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
14 14 demandload(globals(), "changegroup")
15 15
16 16 class localrepository(object):
17 17 def __del__(self):
18 18 self.transhandle = None
19 19 def __init__(self, parentui, path=None, create=0):
20 20 if not path:
21 21 p = os.getcwd()
22 22 while not os.path.isdir(os.path.join(p, ".hg")):
23 23 oldp = p
24 24 p = os.path.dirname(p)
25 25 if p == oldp:
26 26 raise repo.RepoError(_("no repo found"))
27 27 path = p
28 28 self.path = os.path.join(path, ".hg")
29 29
30 30 if not create and not os.path.isdir(self.path):
31 31 raise repo.RepoError(_("repository %s not found") % path)
32 32
33 33 self.root = os.path.abspath(path)
34 34 self.ui = ui.ui(parentui=parentui)
35 35 self.opener = util.opener(self.path)
36 36 self.wopener = util.opener(self.root)
37 37 self.manifest = manifest.manifest(self.opener)
38 38 self.changelog = changelog.changelog(self.opener)
39 39 self.tagscache = None
40 40 self.nodetagscache = None
41 41 self.encodepats = None
42 42 self.decodepats = None
43 43 self.transhandle = None
44 44
45 45 if create:
46 46 os.mkdir(self.path)
47 47 os.mkdir(self.join("data"))
48 48
49 49 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
50 50 try:
51 51 self.ui.readconfig(self.join("hgrc"), self.root)
52 52 except IOError:
53 53 pass
54 54
55 55 def hook(self, name, throw=False, **args):
56 56 def runhook(name, cmd):
57 57 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
58 58 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()] +
59 59 [(k.upper(), v) for k, v in args.iteritems()])
60 60 r = util.system(cmd, environ=env, cwd=self.root)
61 61 if r:
62 62 desc, r = util.explain_exit(r)
63 63 if throw:
64 64 raise util.Abort(_('%s hook %s') % (name, desc))
65 65 self.ui.warn(_('error: %s hook %s\n') % (name, desc))
66 66 return False
67 67 return True
68 68
69 69 r = True
70 70 hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks")
71 71 if hname.split(".", 1)[0] == name and cmd]
72 72 hooks.sort()
73 73 for hname, cmd in hooks:
74 74 r = runhook(hname, cmd) and r
75 75 return r
76 76
77 77 def tags(self):
78 78 '''return a mapping of tag to node'''
79 79 if not self.tagscache:
80 80 self.tagscache = {}
81 81
82 82 def parsetag(line, context):
83 83 if not line:
84 84 return
85 85 s = l.split(" ", 1)
86 86 if len(s) != 2:
87 87 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
88 88 return
89 89 node, key = s
90 90 try:
91 91 bin_n = bin(node)
92 92 except TypeError:
93 93 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
94 94 return
95 95 if bin_n not in self.changelog.nodemap:
96 96 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
97 97 return
98 98 self.tagscache[key.strip()] = bin_n
99 99
100 100 # read each head of the tags file, ending with the tip
101 101 # and add each tag found to the map, with "newer" ones
102 102 # taking precedence
103 103 fl = self.file(".hgtags")
104 104 h = fl.heads()
105 105 h.reverse()
106 106 for r in h:
107 107 count = 0
108 108 for l in fl.read(r).splitlines():
109 109 count += 1
110 110 parsetag(l, ".hgtags:%d" % count)
111 111
112 112 try:
113 113 f = self.opener("localtags")
114 114 count = 0
115 115 for l in f:
116 116 count += 1
117 117 parsetag(l, "localtags:%d" % count)
118 118 except IOError:
119 119 pass
120 120
121 121 self.tagscache['tip'] = self.changelog.tip()
122 122
123 123 return self.tagscache
124 124
125 125 def tagslist(self):
126 126 '''return a list of tags ordered by revision'''
127 127 l = []
128 128 for t, n in self.tags().items():
129 129 try:
130 130 r = self.changelog.rev(n)
131 131 except:
132 132 r = -2 # sort to the beginning of the list if unknown
133 133 l.append((r, t, n))
134 134 l.sort()
135 135 return [(t, n) for r, t, n in l]
136 136
137 137 def nodetags(self, node):
138 138 '''return the tags associated with a node'''
139 139 if not self.nodetagscache:
140 140 self.nodetagscache = {}
141 141 for t, n in self.tags().items():
142 142 self.nodetagscache.setdefault(n, []).append(t)
143 143 return self.nodetagscache.get(node, [])
144 144
145 145 def lookup(self, key):
146 146 try:
147 147 return self.tags()[key]
148 148 except KeyError:
149 149 try:
150 150 return self.changelog.lookup(key)
151 151 except:
152 152 raise repo.RepoError(_("unknown revision '%s'") % key)
153 153
154 154 def dev(self):
155 155 return os.stat(self.path).st_dev
156 156
157 157 def local(self):
158 158 return True
159 159
160 160 def join(self, f):
161 161 return os.path.join(self.path, f)
162 162
163 163 def wjoin(self, f):
164 164 return os.path.join(self.root, f)
165 165
166 166 def file(self, f):
167 167 if f[0] == '/':
168 168 f = f[1:]
169 169 return filelog.filelog(self.opener, f)
170 170
171 171 def getcwd(self):
172 172 return self.dirstate.getcwd()
173 173
174 174 def wfile(self, f, mode='r'):
175 175 return self.wopener(f, mode)
176 176
177 177 def wread(self, filename):
178 178 if self.encodepats == None:
179 179 l = []
180 180 for pat, cmd in self.ui.configitems("encode"):
181 181 mf = util.matcher(self.root, "", [pat], [], [])[1]
182 182 l.append((mf, cmd))
183 183 self.encodepats = l
184 184
185 185 data = self.wopener(filename, 'r').read()
186 186
187 187 for mf, cmd in self.encodepats:
188 188 if mf(filename):
189 189 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
190 190 data = util.filter(data, cmd)
191 191 break
192 192
193 193 return data
194 194
195 195 def wwrite(self, filename, data, fd=None):
196 196 if self.decodepats == None:
197 197 l = []
198 198 for pat, cmd in self.ui.configitems("decode"):
199 199 mf = util.matcher(self.root, "", [pat], [], [])[1]
200 200 l.append((mf, cmd))
201 201 self.decodepats = l
202 202
203 203 for mf, cmd in self.decodepats:
204 204 if mf(filename):
205 205 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
206 206 data = util.filter(data, cmd)
207 207 break
208 208
209 209 if fd:
210 210 return fd.write(data)
211 211 return self.wopener(filename, 'w').write(data)
212 212
213 213 def transaction(self):
214 214 tr = self.transhandle
215 215 if tr != None and tr.running():
216 216 return tr.nest()
217 217
218 218 # save dirstate for undo
219 219 try:
220 220 ds = self.opener("dirstate").read()
221 221 except IOError:
222 222 ds = ""
223 223 self.opener("journal.dirstate", "w").write(ds)
224 224
225 225 tr = transaction.transaction(self.ui.warn, self.opener,
226 226 self.join("journal"),
227 227 aftertrans(self.path))
228 228 self.transhandle = tr
229 229 return tr
230 230
231 231 def recover(self):
232 232 l = self.lock()
233 233 if os.path.exists(self.join("journal")):
234 234 self.ui.status(_("rolling back interrupted transaction\n"))
235 235 transaction.rollback(self.opener, self.join("journal"))
236 236 self.reload()
237 237 return True
238 238 else:
239 239 self.ui.warn(_("no interrupted transaction available\n"))
240 240 return False
241 241
242 242 def undo(self, wlock=None):
243 243 if not wlock:
244 244 wlock = self.wlock()
245 245 l = self.lock()
246 246 if os.path.exists(self.join("undo")):
247 247 self.ui.status(_("rolling back last transaction\n"))
248 248 transaction.rollback(self.opener, self.join("undo"))
249 249 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
250 250 self.reload()
251 251 self.wreload()
252 252 else:
253 253 self.ui.warn(_("no undo information available\n"))
254 254
255 255 def wreload(self):
256 256 self.dirstate.read()
257 257
258 258 def reload(self):
259 259 self.changelog.load()
260 260 self.manifest.load()
261 261 self.tagscache = None
262 262 self.nodetagscache = None
263 263
264 264 def do_lock(self, lockname, wait, releasefn=None, acquirefn=None):
265 265 try:
266 266 l = lock.lock(self.join(lockname), 0, releasefn)
267 267 except lock.LockHeld, inst:
268 268 if not wait:
269 269 raise inst
270 270 self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
271 271 try:
272 272 # default to 600 seconds timeout
273 273 l = lock.lock(self.join(lockname),
274 274 int(self.ui.config("ui", "timeout") or 600),
275 275 releasefn)
276 276 except lock.LockHeld, inst:
277 277 raise util.Abort(_("timeout while waiting for "
278 278 "lock held by %s") % inst.args[0])
279 279 if acquirefn:
280 280 acquirefn()
281 281 return l
282 282
283 283 def lock(self, wait=1):
284 284 return self.do_lock("lock", wait, acquirefn=self.reload)
285 285
286 286 def wlock(self, wait=1):
287 287 return self.do_lock("wlock", wait,
288 288 self.dirstate.write,
289 289 self.wreload)
290 290
291 291 def checkfilemerge(self, filename, text, filelog, manifest1, manifest2):
292 292 "determine whether a new filenode is needed"
293 293 fp1 = manifest1.get(filename, nullid)
294 294 fp2 = manifest2.get(filename, nullid)
295 295
296 296 if fp2 != nullid:
297 297 # is one parent an ancestor of the other?
298 298 fpa = filelog.ancestor(fp1, fp2)
299 299 if fpa == fp1:
300 300 fp1, fp2 = fp2, nullid
301 301 elif fpa == fp2:
302 302 fp2 = nullid
303 303
304 304 # is the file unmodified from the parent? report existing entry
305 305 if fp2 == nullid and text == filelog.read(fp1):
306 306 return (fp1, None, None)
307 307
308 308 return (None, fp1, fp2)
309 309
310 310 def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None):
311 311 orig_parent = self.dirstate.parents()[0] or nullid
312 312 p1 = p1 or self.dirstate.parents()[0] or nullid
313 313 p2 = p2 or self.dirstate.parents()[1] or nullid
314 314 c1 = self.changelog.read(p1)
315 315 c2 = self.changelog.read(p2)
316 316 m1 = self.manifest.read(c1[0])
317 317 mf1 = self.manifest.readflags(c1[0])
318 318 m2 = self.manifest.read(c2[0])
319 319 changed = []
320 320
321 321 if orig_parent == p1:
322 322 update_dirstate = 1
323 323 else:
324 324 update_dirstate = 0
325 325
326 326 if not wlock:
327 327 wlock = self.wlock()
328 328 l = self.lock()
329 329 tr = self.transaction()
330 330 mm = m1.copy()
331 331 mfm = mf1.copy()
332 332 linkrev = self.changelog.count()
333 333 for f in files:
334 334 try:
335 335 t = self.wread(f)
336 336 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
337 337 r = self.file(f)
338 338 mfm[f] = tm
339 339
340 340 (entry, fp1, fp2) = self.checkfilemerge(f, t, r, m1, m2)
341 341 if entry:
342 342 mm[f] = entry
343 343 continue
344 344
345 345 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
346 346 changed.append(f)
347 347 if update_dirstate:
348 348 self.dirstate.update([f], "n")
349 349 except IOError:
350 350 try:
351 351 del mm[f]
352 352 del mfm[f]
353 353 if update_dirstate:
354 354 self.dirstate.forget([f])
355 355 except:
356 356 # deleted from p2?
357 357 pass
358 358
359 359 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
360 360 user = user or self.ui.username()
361 361 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
362 362 tr.close()
363 363 if update_dirstate:
364 364 self.dirstate.setparents(n, nullid)
365 365
366 366 def commit(self, files=None, text="", user=None, date=None,
367 367 match=util.always, force=False, lock=None, wlock=None):
368 368 commit = []
369 369 remove = []
370 370 changed = []
371 371
372 372 if files:
373 373 for f in files:
374 374 s = self.dirstate.state(f)
375 375 if s in 'nmai':
376 376 commit.append(f)
377 377 elif s == 'r':
378 378 remove.append(f)
379 379 else:
380 380 self.ui.warn(_("%s not tracked!\n") % f)
381 381 else:
382 382 modified, added, removed, deleted, unknown = self.changes(match=match)
383 383 commit = modified + added
384 384 remove = removed
385 385
386 386 p1, p2 = self.dirstate.parents()
387 387 c1 = self.changelog.read(p1)
388 388 c2 = self.changelog.read(p2)
389 389 m1 = self.manifest.read(c1[0])
390 390 mf1 = self.manifest.readflags(c1[0])
391 391 m2 = self.manifest.read(c2[0])
392 392
393 393 if not commit and not remove and not force and p2 == nullid:
394 394 self.ui.status(_("nothing changed\n"))
395 395 return None
396 396
397 397 xp1 = hex(p1)
398 398 if p2 == nullid: xp2 = ''
399 399 else: xp2 = hex(p2)
400 400
401 401 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
402 402
403 403 if not wlock:
404 404 wlock = self.wlock()
405 405 if not lock:
406 406 lock = self.lock()
407 407 tr = self.transaction()
408 408
409 409 # check in files
410 410 new = {}
411 411 linkrev = self.changelog.count()
412 412 commit.sort()
413 413 for f in commit:
414 414 self.ui.note(f + "\n")
415 415 try:
416 416 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
417 417 t = self.wread(f)
418 418 except IOError:
419 419 self.ui.warn(_("trouble committing %s!\n") % f)
420 420 raise
421 421
422 422 r = self.file(f)
423 423
424 424 meta = {}
425 425 cp = self.dirstate.copied(f)
426 426 if cp:
427 427 meta["copy"] = cp
428 428 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
429 429 self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
430 430 fp1, fp2 = nullid, nullid
431 431 else:
432 432 entry, fp1, fp2 = self.checkfilemerge(f, t, r, m1, m2)
433 433 if entry:
434 434 new[f] = entry
435 435 continue
436 436
437 437 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
438 438 # remember what we've added so that we can later calculate
439 439 # the files to pull from a set of changesets
440 440 changed.append(f)
441 441
442 442 # update manifest
443 443 m1 = m1.copy()
444 444 m1.update(new)
445 445 for f in remove:
446 446 if f in m1:
447 447 del m1[f]
448 448 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
449 449 (new, remove))
450 450
451 451 # add changeset
452 452 new = new.keys()
453 453 new.sort()
454 454
455 455 user = user or self.ui.username()
456 456 if not text:
457 457 edittext = [""]
458 458 if p2 != nullid:
459 459 edittext.append("HG: branch merge")
460 460 edittext.extend(["HG: changed %s" % f for f in changed])
461 461 edittext.extend(["HG: removed %s" % f for f in remove])
462 462 if not changed and not remove:
463 463 edittext.append("HG: no files changed")
464 464 edittext.append("")
465 465 # run editor in the repository root
466 466 olddir = os.getcwd()
467 467 os.chdir(self.root)
468 468 edittext = self.ui.edit("\n".join(edittext), user)
469 469 os.chdir(olddir)
470 470 if not edittext.rstrip():
471 471 return None
472 472 text = edittext
473 473
474 474 n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date)
475 475 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
476 476 parent2=xp2)
477 477 tr.close()
478 478
479 479 self.dirstate.setparents(n)
480 480 self.dirstate.update(new, "n")
481 481 self.dirstate.forget(remove)
482 482
483 483 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
484 484 return n
485 485
486 486 def walk(self, node=None, files=[], match=util.always):
487 487 if node:
488 488 fdict = dict.fromkeys(files)
489 489 for fn in self.manifest.read(self.changelog.read(node)[0]):
490 490 fdict.pop(fn, None)
491 491 if match(fn):
492 492 yield 'm', fn
493 493 for fn in fdict:
494 494 self.ui.warn(_('%s: No such file in rev %s\n') % (
495 495 util.pathto(self.getcwd(), fn), short(node)))
496 496 else:
497 497 for src, fn in self.dirstate.walk(files, match):
498 498 yield src, fn
499 499
500 500 def changes(self, node1=None, node2=None, files=[], match=util.always,
501 501 wlock=None):
502 502 """return changes between two nodes or node and working directory
503 503
504 504 If node1 is None, use the first dirstate parent instead.
505 505 If node2 is None, compare node1 with working directory.
506 506 """
507 507
508 508 def fcmp(fn, mf):
509 509 t1 = self.wread(fn)
510 510 t2 = self.file(fn).read(mf.get(fn, nullid))
511 511 return cmp(t1, t2)
512 512
513 513 def mfmatches(node):
514 514 change = self.changelog.read(node)
515 515 mf = dict(self.manifest.read(change[0]))
516 516 for fn in mf.keys():
517 517 if not match(fn):
518 518 del mf[fn]
519 519 return mf
520 520
521 521 if node1:
522 522 # read the manifest from node1 before the manifest from node2,
523 523 # so that we'll hit the manifest cache if we're going through
524 524 # all the revisions in parent->child order.
525 525 mf1 = mfmatches(node1)
526 526
527 527 # are we comparing the working directory?
528 528 if not node2:
529 529 if not wlock:
530 530 try:
531 531 wlock = self.wlock(wait=0)
532 532 except lock.LockException:
533 533 wlock = None
534 534 lookup, modified, added, removed, deleted, unknown = (
535 535 self.dirstate.changes(files, match))
536 536
537 537 # are we comparing working dir against its parent?
538 538 if not node1:
539 539 if lookup:
540 540 # do a full compare of any files that might have changed
541 541 mf2 = mfmatches(self.dirstate.parents()[0])
542 542 for f in lookup:
543 543 if fcmp(f, mf2):
544 544 modified.append(f)
545 545 elif wlock is not None:
546 546 self.dirstate.update([f], "n")
547 547 else:
548 548 # we are comparing working dir against non-parent
549 549 # generate a pseudo-manifest for the working dir
550 550 mf2 = mfmatches(self.dirstate.parents()[0])
551 551 for f in lookup + modified + added:
552 552 mf2[f] = ""
553 553 for f in removed:
554 554 if f in mf2:
555 555 del mf2[f]
556 556 else:
557 557 # we are comparing two revisions
558 558 deleted, unknown = [], []
559 559 mf2 = mfmatches(node2)
560 560
561 561 if node1:
562 562 # flush lists from dirstate before comparing manifests
563 563 modified, added = [], []
564 564
565 565 for fn in mf2:
566 566 if mf1.has_key(fn):
567 567 if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)):
568 568 modified.append(fn)
569 569 del mf1[fn]
570 570 else:
571 571 added.append(fn)
572 572
573 573 removed = mf1.keys()
574 574
575 575 # sort and return results:
576 576 for l in modified, added, removed, deleted, unknown:
577 577 l.sort()
578 578 return (modified, added, removed, deleted, unknown)
579 579
580 580 def add(self, list, wlock=None):
581 581 if not wlock:
582 582 wlock = self.wlock()
583 583 for f in list:
584 584 p = self.wjoin(f)
585 585 if not os.path.exists(p):
586 586 self.ui.warn(_("%s does not exist!\n") % f)
587 587 elif not os.path.isfile(p):
588 588 self.ui.warn(_("%s not added: only files supported currently\n")
589 589 % f)
590 590 elif self.dirstate.state(f) in 'an':
591 591 self.ui.warn(_("%s already tracked!\n") % f)
592 592 else:
593 593 self.dirstate.update([f], "a")
594 594
595 595 def forget(self, list, wlock=None):
596 596 if not wlock:
597 597 wlock = self.wlock()
598 598 for f in list:
599 599 if self.dirstate.state(f) not in 'ai':
600 600 self.ui.warn(_("%s not added!\n") % f)
601 601 else:
602 602 self.dirstate.forget([f])
603 603
604 604 def remove(self, list, unlink=False, wlock=None):
605 605 if unlink:
606 606 for f in list:
607 607 try:
608 608 util.unlink(self.wjoin(f))
609 609 except OSError, inst:
610 610 if inst.errno != errno.ENOENT:
611 611 raise
612 612 if not wlock:
613 613 wlock = self.wlock()
614 614 for f in list:
615 615 p = self.wjoin(f)
616 616 if os.path.exists(p):
617 617 self.ui.warn(_("%s still exists!\n") % f)
618 618 elif self.dirstate.state(f) == 'a':
619 619 self.dirstate.forget([f])
620 620 elif f not in self.dirstate:
621 621 self.ui.warn(_("%s not tracked!\n") % f)
622 622 else:
623 623 self.dirstate.update([f], "r")
624 624
625 625 def undelete(self, list, wlock=None):
626 626 p = self.dirstate.parents()[0]
627 627 mn = self.changelog.read(p)[0]
628 628 mf = self.manifest.readflags(mn)
629 629 m = self.manifest.read(mn)
630 630 if not wlock:
631 631 wlock = self.wlock()
632 632 for f in list:
633 633 if self.dirstate.state(f) not in "r":
634 634 self.ui.warn("%s not removed!\n" % f)
635 635 else:
636 636 t = self.file(f).read(m[f])
637 637 self.wwrite(f, t)
638 638 util.set_exec(self.wjoin(f), mf[f])
639 639 self.dirstate.update([f], "n")
640 640
641 641 def copy(self, source, dest, wlock=None):
642 642 p = self.wjoin(dest)
643 643 if not os.path.exists(p):
644 644 self.ui.warn(_("%s does not exist!\n") % dest)
645 645 elif not os.path.isfile(p):
646 646 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
647 647 else:
648 648 if not wlock:
649 649 wlock = self.wlock()
650 650 if self.dirstate.state(dest) == '?':
651 651 self.dirstate.update([dest], "a")
652 652 self.dirstate.copy(source, dest)
653 653
654 654 def heads(self, start=None):
655 655 heads = self.changelog.heads(start)
656 656 # sort the output in rev descending order
657 657 heads = [(-self.changelog.rev(h), h) for h in heads]
658 658 heads.sort()
659 659 return [n for (r, n) in heads]
660 660
661 661 # branchlookup returns a dict giving a list of branches for
662 662 # each head. A branch is defined as the tag of a node or
663 663 # the branch of the node's parents. If a node has multiple
664 664 # branch tags, tags are eliminated if they are visible from other
665 665 # branch tags.
666 666 #
667 667 # So, for this graph: a->b->c->d->e
668 668 # \ /
669 669 # aa -----/
670 670 # a has tag 2.6.12
671 671 # d has tag 2.6.13
672 672 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
673 673 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
674 674 # from the list.
675 675 #
676 676 # It is possible that more than one head will have the same branch tag.
677 677 # callers need to check the result for multiple heads under the same
678 678 # branch tag if that is a problem for them (ie checkout of a specific
679 679 # branch).
680 680 #
681 681 # passing in a specific branch will limit the depth of the search
682 682 # through the parents. It won't limit the branches returned in the
683 683 # result though.
684 684 def branchlookup(self, heads=None, branch=None):
685 685 if not heads:
686 686 heads = self.heads()
687 687 headt = [ h for h in heads ]
688 688 chlog = self.changelog
689 689 branches = {}
690 690 merges = []
691 691 seenmerge = {}
692 692
693 693 # traverse the tree once for each head, recording in the branches
694 694 # dict which tags are visible from this head. The branches
695 695 # dict also records which tags are visible from each tag
696 696 # while we traverse.
697 697 while headt or merges:
698 698 if merges:
699 699 n, found = merges.pop()
700 700 visit = [n]
701 701 else:
702 702 h = headt.pop()
703 703 visit = [h]
704 704 found = [h]
705 705 seen = {}
706 706 while visit:
707 707 n = visit.pop()
708 708 if n in seen:
709 709 continue
710 710 pp = chlog.parents(n)
711 711 tags = self.nodetags(n)
712 712 if tags:
713 713 for x in tags:
714 714 if x == 'tip':
715 715 continue
716 716 for f in found:
717 717 branches.setdefault(f, {})[n] = 1
718 718 branches.setdefault(n, {})[n] = 1
719 719 break
720 720 if n not in found:
721 721 found.append(n)
722 722 if branch in tags:
723 723 continue
724 724 seen[n] = 1
725 725 if pp[1] != nullid and n not in seenmerge:
726 726 merges.append((pp[1], [x for x in found]))
727 727 seenmerge[n] = 1
728 728 if pp[0] != nullid:
729 729 visit.append(pp[0])
730 730 # traverse the branches dict, eliminating branch tags from each
731 731 # head that are visible from another branch tag for that head.
732 732 out = {}
733 733 viscache = {}
734 734 for h in heads:
735 735 def visible(node):
736 736 if node in viscache:
737 737 return viscache[node]
738 738 ret = {}
739 739 visit = [node]
740 740 while visit:
741 741 x = visit.pop()
742 742 if x in viscache:
743 743 ret.update(viscache[x])
744 744 elif x not in ret:
745 745 ret[x] = 1
746 746 if x in branches:
747 747 visit[len(visit):] = branches[x].keys()
748 748 viscache[node] = ret
749 749 return ret
750 750 if h not in branches:
751 751 continue
752 752 # O(n^2), but somewhat limited. This only searches the
753 753 # tags visible from a specific head, not all the tags in the
754 754 # whole repo.
755 755 for b in branches[h]:
756 756 vis = False
757 757 for bb in branches[h].keys():
758 758 if b != bb:
759 759 if b in visible(bb):
760 760 vis = True
761 761 break
762 762 if not vis:
763 763 l = out.setdefault(h, [])
764 764 l[len(l):] = self.nodetags(b)
765 765 return out
766 766
767 767 def branches(self, nodes):
768 768 if not nodes:
769 769 nodes = [self.changelog.tip()]
770 770 b = []
771 771 for n in nodes:
772 772 t = n
773 773 while n:
774 774 p = self.changelog.parents(n)
775 775 if p[1] != nullid or p[0] == nullid:
776 776 b.append((t, n, p[0], p[1]))
777 777 break
778 778 n = p[0]
779 779 return b
780 780
781 781 def between(self, pairs):
782 782 r = []
783 783
784 784 for top, bottom in pairs:
785 785 n, l, i = top, [], 0
786 786 f = 1
787 787
788 788 while n != bottom:
789 789 p = self.changelog.parents(n)[0]
790 790 if i == f:
791 791 l.append(n)
792 792 f = f * 2
793 793 n = p
794 794 i += 1
795 795
796 796 r.append(l)
797 797
798 798 return r
799 799
800 800 def findincoming(self, remote, base=None, heads=None, force=False):
801 801 m = self.changelog.nodemap
802 802 search = []
803 803 fetch = {}
804 804 seen = {}
805 805 seenbranch = {}
806 806 if base == None:
807 807 base = {}
808 808
809 809 # assume we're closer to the tip than the root
810 810 # and start by examining the heads
811 811 self.ui.status(_("searching for changes\n"))
812 812
813 813 if not heads:
814 814 heads = remote.heads()
815 815
816 816 unknown = []
817 817 for h in heads:
818 818 if h not in m:
819 819 unknown.append(h)
820 820 else:
821 821 base[h] = 1
822 822
823 823 if not unknown:
824 824 return []
825 825
826 826 rep = {}
827 827 reqcnt = 0
828 828
829 829 # search through remote branches
830 830 # a 'branch' here is a linear segment of history, with four parts:
831 831 # head, root, first parent, second parent
832 832 # (a branch always has two parents (or none) by definition)
833 833 unknown = remote.branches(unknown)
834 834 while unknown:
835 835 r = []
836 836 while unknown:
837 837 n = unknown.pop(0)
838 838 if n[0] in seen:
839 839 continue
840 840
841 841 self.ui.debug(_("examining %s:%s\n")
842 842 % (short(n[0]), short(n[1])))
843 843 if n[0] == nullid:
844 844 break
845 845 if n in seenbranch:
846 846 self.ui.debug(_("branch already found\n"))
847 847 continue
848 848 if n[1] and n[1] in m: # do we know the base?
849 849 self.ui.debug(_("found incomplete branch %s:%s\n")
850 850 % (short(n[0]), short(n[1])))
851 851 search.append(n) # schedule branch range for scanning
852 852 seenbranch[n] = 1
853 853 else:
854 854 if n[1] not in seen and n[1] not in fetch:
855 855 if n[2] in m and n[3] in m:
856 856 self.ui.debug(_("found new changeset %s\n") %
857 857 short(n[1]))
858 858 fetch[n[1]] = 1 # earliest unknown
859 859 base[n[2]] = 1 # latest known
860 860 continue
861 861
862 862 for a in n[2:4]:
863 863 if a not in rep:
864 864 r.append(a)
865 865 rep[a] = 1
866 866
867 867 seen[n[0]] = 1
868 868
869 869 if r:
870 870 reqcnt += 1
871 871 self.ui.debug(_("request %d: %s\n") %
872 872 (reqcnt, " ".join(map(short, r))))
873 873 for p in range(0, len(r), 10):
874 874 for b in remote.branches(r[p:p+10]):
875 875 self.ui.debug(_("received %s:%s\n") %
876 876 (short(b[0]), short(b[1])))
877 877 if b[0] in m:
878 878 self.ui.debug(_("found base node %s\n")
879 879 % short(b[0]))
880 880 base[b[0]] = 1
881 881 elif b[0] not in seen:
882 882 unknown.append(b)
883 883
884 884 # do binary search on the branches we found
885 885 while search:
886 886 n = search.pop(0)
887 887 reqcnt += 1
888 888 l = remote.between([(n[0], n[1])])[0]
889 889 l.append(n[1])
890 890 p = n[0]
891 891 f = 1
892 892 for i in l:
893 893 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
894 894 if i in m:
895 895 if f <= 2:
896 896 self.ui.debug(_("found new branch changeset %s\n") %
897 897 short(p))
898 898 fetch[p] = 1
899 899 base[i] = 1
900 900 else:
901 901 self.ui.debug(_("narrowed branch search to %s:%s\n")
902 902 % (short(p), short(i)))
903 903 search.append((p, i))
904 904 break
905 905 p, f = i, f * 2
906 906
907 907 # sanity check our fetch list
908 908 for f in fetch.keys():
909 909 if f in m:
910 910 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
911 911
912 912 if base.keys() == [nullid]:
913 913 if force:
914 914 self.ui.warn(_("warning: repository is unrelated\n"))
915 915 else:
916 916 raise util.Abort(_("repository is unrelated"))
917 917
918 918 self.ui.note(_("found new changesets starting at ") +
919 919 " ".join([short(f) for f in fetch]) + "\n")
920 920
921 921 self.ui.debug(_("%d total queries\n") % reqcnt)
922 922
923 923 return fetch.keys()
924 924
925 925 def findoutgoing(self, remote, base=None, heads=None, force=False):
926 926 if base == None:
927 927 base = {}
928 928 self.findincoming(remote, base, heads, force=force)
929 929
930 930 self.ui.debug(_("common changesets up to ")
931 931 + " ".join(map(short, base.keys())) + "\n")
932 932
933 933 remain = dict.fromkeys(self.changelog.nodemap)
934 934
935 935 # prune everything remote has from the tree
936 936 del remain[nullid]
937 937 remove = base.keys()
938 938 while remove:
939 939 n = remove.pop(0)
940 940 if n in remain:
941 941 del remain[n]
942 942 for p in self.changelog.parents(n):
943 943 remove.append(p)
944 944
945 945 # find every node whose parents have been pruned
946 946 subset = []
947 947 for n in remain:
948 948 p1, p2 = self.changelog.parents(n)
949 949 if p1 not in remain and p2 not in remain:
950 950 subset.append(n)
951 951
952 952 # this is the set of all roots we have to push
953 953 return subset
954 954
955 955 def pull(self, remote, heads=None, force=False):
956 956 l = self.lock()
957 957
958 958 # if we have an empty repo, fetch everything
959 959 if self.changelog.tip() == nullid:
960 960 self.ui.status(_("requesting all changes\n"))
961 961 fetch = [nullid]
962 962 else:
963 963 fetch = self.findincoming(remote, force=force)
964 964
965 965 if not fetch:
966 966 self.ui.status(_("no changes found\n"))
967 967 return 1
968 968
969 969 if heads is None:
970 970 cg = remote.changegroup(fetch, 'pull')
971 971 else:
972 972 cg = remote.changegroupsubset(fetch, heads, 'pull')
973 973 return self.addchangegroup(cg)
974 974
975 975 def push(self, remote, force=False, revs=None):
976 976 lock = remote.lock()
977 977
978 978 base = {}
979 979 heads = remote.heads()
980 980 inc = self.findincoming(remote, base, heads, force=force)
981 981 if not force and inc:
982 982 self.ui.warn(_("abort: unsynced remote changes!\n"))
983 983 self.ui.status(_("(did you forget to sync? use push -f to force)\n"))
984 984 return 1
985 985
986 986 update = self.findoutgoing(remote, base)
987 987 if revs is not None:
988 988 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
989 989 else:
990 990 bases, heads = update, self.changelog.heads()
991 991
992 992 if not bases:
993 993 self.ui.status(_("no changes found\n"))
994 994 return 1
995 995 elif not force:
996 996 if len(bases) < len(heads):
997 997 self.ui.warn(_("abort: push creates new remote branches!\n"))
998 998 self.ui.status(_("(did you forget to merge?"
999 999 " use push -f to force)\n"))
1000 1000 return 1
1001 1001
1002 1002 if revs is None:
1003 1003 cg = self.changegroup(update, 'push')
1004 1004 else:
1005 1005 cg = self.changegroupsubset(update, revs, 'push')
1006 1006 return remote.addchangegroup(cg)
1007 1007
1008 1008 def changegroupsubset(self, bases, heads, source):
1009 1009 """This function generates a changegroup consisting of all the nodes
1010 1010 that are descendents of any of the bases, and ancestors of any of
1011 1011 the heads.
1012 1012
1013 1013 It is fairly complex as determining which filenodes and which
1014 1014 manifest nodes need to be included for the changeset to be complete
1015 1015 is non-trivial.
1016 1016
1017 1017 Another wrinkle is doing the reverse, figuring out which changeset in
1018 1018 the changegroup a particular filenode or manifestnode belongs to."""
1019 1019
1020 1020 self.hook('preoutgoing', throw=True, source=source)
1021 1021
1022 1022 # Set up some initial variables
1023 1023 # Make it easy to refer to self.changelog
1024 1024 cl = self.changelog
1025 1025 # msng is short for missing - compute the list of changesets in this
1026 1026 # changegroup.
1027 1027 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1028 1028 # Some bases may turn out to be superfluous, and some heads may be
1029 1029 # too. nodesbetween will return the minimal set of bases and heads
1030 1030 # necessary to re-create the changegroup.
1031 1031
1032 1032 # Known heads are the list of heads that it is assumed the recipient
1033 1033 # of this changegroup will know about.
1034 1034 knownheads = {}
1035 1035 # We assume that all parents of bases are known heads.
1036 1036 for n in bases:
1037 1037 for p in cl.parents(n):
1038 1038 if p != nullid:
1039 1039 knownheads[p] = 1
1040 1040 knownheads = knownheads.keys()
1041 1041 if knownheads:
1042 1042 # Now that we know what heads are known, we can compute which
1043 1043 # changesets are known. The recipient must know about all
1044 1044 # changesets required to reach the known heads from the null
1045 1045 # changeset.
1046 1046 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1047 1047 junk = None
1048 1048 # Transform the list into an ersatz set.
1049 1049 has_cl_set = dict.fromkeys(has_cl_set)
1050 1050 else:
1051 1051 # If there were no known heads, the recipient cannot be assumed to
1052 1052 # know about any changesets.
1053 1053 has_cl_set = {}
1054 1054
1055 1055 # Make it easy to refer to self.manifest
1056 1056 mnfst = self.manifest
1057 1057 # We don't know which manifests are missing yet
1058 1058 msng_mnfst_set = {}
1059 1059 # Nor do we know which filenodes are missing.
1060 1060 msng_filenode_set = {}
1061 1061
1062 1062 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1063 1063 junk = None
1064 1064
1065 1065 # A changeset always belongs to itself, so the changenode lookup
1066 1066 # function for a changenode is identity.
1067 1067 def identity(x):
1068 1068 return x
1069 1069
1070 1070 # A function generating function. Sets up an environment for the
1071 1071 # inner function.
1072 1072 def cmp_by_rev_func(revlog):
1073 1073 # Compare two nodes by their revision number in the environment's
1074 1074 # revision history. Since the revision number both represents the
1075 1075 # most efficient order to read the nodes in, and represents a
1076 1076 # topological sorting of the nodes, this function is often useful.
1077 1077 def cmp_by_rev(a, b):
1078 1078 return cmp(revlog.rev(a), revlog.rev(b))
1079 1079 return cmp_by_rev
1080 1080
1081 1081 # If we determine that a particular file or manifest node must be a
1082 1082 # node that the recipient of the changegroup will already have, we can
1083 1083 # also assume the recipient will have all the parents. This function
1084 1084 # prunes them from the set of missing nodes.
1085 1085 def prune_parents(revlog, hasset, msngset):
1086 1086 haslst = hasset.keys()
1087 1087 haslst.sort(cmp_by_rev_func(revlog))
1088 1088 for node in haslst:
1089 1089 parentlst = [p for p in revlog.parents(node) if p != nullid]
1090 1090 while parentlst:
1091 1091 n = parentlst.pop()
1092 1092 if n not in hasset:
1093 1093 hasset[n] = 1
1094 1094 p = [p for p in revlog.parents(n) if p != nullid]
1095 1095 parentlst.extend(p)
1096 1096 for n in hasset:
1097 1097 msngset.pop(n, None)
1098 1098
1099 1099 # This is a function generating function used to set up an environment
1100 1100 # for the inner function to execute in.
1101 1101 def manifest_and_file_collector(changedfileset):
1102 1102 # This is an information gathering function that gathers
1103 1103 # information from each changeset node that goes out as part of
1104 1104 # the changegroup. The information gathered is a list of which
1105 1105 # manifest nodes are potentially required (the recipient may
1106 1106 # already have them) and total list of all files which were
1107 1107 # changed in any changeset in the changegroup.
1108 1108 #
1109 1109 # We also remember the first changenode we saw any manifest
1110 1110 # referenced by so we can later determine which changenode 'owns'
1111 1111 # the manifest.
1112 1112 def collect_manifests_and_files(clnode):
1113 1113 c = cl.read(clnode)
1114 1114 for f in c[3]:
1115 1115 # This is to make sure we only have one instance of each
1116 1116 # filename string for each filename.
1117 1117 changedfileset.setdefault(f, f)
1118 1118 msng_mnfst_set.setdefault(c[0], clnode)
1119 1119 return collect_manifests_and_files
1120 1120
1121 1121 # Figure out which manifest nodes (of the ones we think might be part
1122 1122 # of the changegroup) the recipient must know about and remove them
1123 1123 # from the changegroup.
1124 1124 def prune_manifests():
1125 1125 has_mnfst_set = {}
1126 1126 for n in msng_mnfst_set:
1127 1127 # If a 'missing' manifest thinks it belongs to a changenode
1128 1128 # the recipient is assumed to have, obviously the recipient
1129 1129 # must have that manifest.
1130 1130 linknode = cl.node(mnfst.linkrev(n))
1131 1131 if linknode in has_cl_set:
1132 1132 has_mnfst_set[n] = 1
1133 1133 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1134 1134
1135 1135 # Use the information collected in collect_manifests_and_files to say
1136 1136 # which changenode any manifestnode belongs to.
1137 1137 def lookup_manifest_link(mnfstnode):
1138 1138 return msng_mnfst_set[mnfstnode]
1139 1139
1140 1140 # A function generating function that sets up the initial environment
1141 1141 # the inner function.
1142 1142 def filenode_collector(changedfiles):
1143 1143 next_rev = [0]
1144 1144 # This gathers information from each manifestnode included in the
1145 1145 # changegroup about which filenodes the manifest node references
1146 1146 # so we can include those in the changegroup too.
1147 1147 #
1148 1148 # It also remembers which changenode each filenode belongs to. It
1149 1149 # does this by assuming the a filenode belongs to the changenode
1150 1150 # the first manifest that references it belongs to.
1151 1151 def collect_msng_filenodes(mnfstnode):
1152 1152 r = mnfst.rev(mnfstnode)
1153 1153 if r == next_rev[0]:
1154 1154 # If the last rev we looked at was the one just previous,
1155 1155 # we only need to see a diff.
1156 1156 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1157 1157 # For each line in the delta
1158 1158 for dline in delta.splitlines():
1159 1159 # get the filename and filenode for that line
1160 1160 f, fnode = dline.split('\0')
1161 1161 fnode = bin(fnode[:40])
1162 1162 f = changedfiles.get(f, None)
1163 1163 # And if the file is in the list of files we care
1164 1164 # about.
1165 1165 if f is not None:
1166 1166 # Get the changenode this manifest belongs to
1167 1167 clnode = msng_mnfst_set[mnfstnode]
1168 1168 # Create the set of filenodes for the file if
1169 1169 # there isn't one already.
1170 1170 ndset = msng_filenode_set.setdefault(f, {})
1171 1171 # And set the filenode's changelog node to the
1172 1172 # manifest's if it hasn't been set already.
1173 1173 ndset.setdefault(fnode, clnode)
1174 1174 else:
1175 1175 # Otherwise we need a full manifest.
1176 1176 m = mnfst.read(mnfstnode)
1177 1177 # For every file in we care about.
1178 1178 for f in changedfiles:
1179 1179 fnode = m.get(f, None)
1180 1180 # If it's in the manifest
1181 1181 if fnode is not None:
1182 1182 # See comments above.
1183 1183 clnode = msng_mnfst_set[mnfstnode]
1184 1184 ndset = msng_filenode_set.setdefault(f, {})
1185 1185 ndset.setdefault(fnode, clnode)
1186 1186 # Remember the revision we hope to see next.
1187 1187 next_rev[0] = r + 1
1188 1188 return collect_msng_filenodes
1189 1189
1190 1190 # We have a list of filenodes we think we need for a file, lets remove
1191 1191 # all those we now the recipient must have.
1192 1192 def prune_filenodes(f, filerevlog):
1193 1193 msngset = msng_filenode_set[f]
1194 1194 hasset = {}
1195 1195 # If a 'missing' filenode thinks it belongs to a changenode we
1196 1196 # assume the recipient must have, then the recipient must have
1197 1197 # that filenode.
1198 1198 for n in msngset:
1199 1199 clnode = cl.node(filerevlog.linkrev(n))
1200 1200 if clnode in has_cl_set:
1201 1201 hasset[n] = 1
1202 1202 prune_parents(filerevlog, hasset, msngset)
1203 1203
1204 1204 # A function generator function that sets up the a context for the
1205 1205 # inner function.
1206 1206 def lookup_filenode_link_func(fname):
1207 1207 msngset = msng_filenode_set[fname]
1208 1208 # Lookup the changenode the filenode belongs to.
1209 1209 def lookup_filenode_link(fnode):
1210 1210 return msngset[fnode]
1211 1211 return lookup_filenode_link
1212 1212
1213 1213 # Now that we have all theses utility functions to help out and
1214 1214 # logically divide up the task, generate the group.
1215 1215 def gengroup():
1216 1216 # The set of changed files starts empty.
1217 1217 changedfiles = {}
1218 1218 # Create a changenode group generator that will call our functions
1219 1219 # back to lookup the owning changenode and collect information.
1220 1220 group = cl.group(msng_cl_lst, identity,
1221 1221 manifest_and_file_collector(changedfiles))
1222 1222 for chnk in group:
1223 1223 yield chnk
1224 1224
1225 1225 # The list of manifests has been collected by the generator
1226 1226 # calling our functions back.
1227 1227 prune_manifests()
1228 1228 msng_mnfst_lst = msng_mnfst_set.keys()
1229 1229 # Sort the manifestnodes by revision number.
1230 1230 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1231 1231 # Create a generator for the manifestnodes that calls our lookup
1232 1232 # and data collection functions back.
1233 1233 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1234 1234 filenode_collector(changedfiles))
1235 1235 for chnk in group:
1236 1236 yield chnk
1237 1237
1238 1238 # These are no longer needed, dereference and toss the memory for
1239 1239 # them.
1240 1240 msng_mnfst_lst = None
1241 1241 msng_mnfst_set.clear()
1242 1242
1243 1243 changedfiles = changedfiles.keys()
1244 1244 changedfiles.sort()
1245 1245 # Go through all our files in order sorted by name.
1246 1246 for fname in changedfiles:
1247 1247 filerevlog = self.file(fname)
1248 1248 # Toss out the filenodes that the recipient isn't really
1249 1249 # missing.
1250 1250 if msng_filenode_set.has_key(fname):
1251 1251 prune_filenodes(fname, filerevlog)
1252 1252 msng_filenode_lst = msng_filenode_set[fname].keys()
1253 1253 else:
1254 1254 msng_filenode_lst = []
1255 1255 # If any filenodes are left, generate the group for them,
1256 1256 # otherwise don't bother.
1257 1257 if len(msng_filenode_lst) > 0:
1258 1258 yield changegroup.genchunk(fname)
1259 1259 # Sort the filenodes by their revision #
1260 1260 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1261 1261 # Create a group generator and only pass in a changenode
1262 1262 # lookup function as we need to collect no information
1263 1263 # from filenodes.
1264 1264 group = filerevlog.group(msng_filenode_lst,
1265 1265 lookup_filenode_link_func(fname))
1266 1266 for chnk in group:
1267 1267 yield chnk
1268 1268 if msng_filenode_set.has_key(fname):
1269 1269 # Don't need this anymore, toss it to free memory.
1270 1270 del msng_filenode_set[fname]
1271 1271 # Signal that no more groups are left.
1272 1272 yield changegroup.closechunk()
1273 1273
1274 1274 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1275 1275
1276 1276 return util.chunkbuffer(gengroup())
1277 1277
1278 1278 def changegroup(self, basenodes, source):
1279 1279 """Generate a changegroup of all nodes that we have that a recipient
1280 1280 doesn't.
1281 1281
1282 1282 This is much easier than the previous function as we can assume that
1283 1283 the recipient has any changenode we aren't sending them."""
1284 1284
1285 1285 self.hook('preoutgoing', throw=True, source=source)
1286 1286
1287 1287 cl = self.changelog
1288 1288 nodes = cl.nodesbetween(basenodes, None)[0]
1289 1289 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1290 1290
1291 1291 def identity(x):
1292 1292 return x
1293 1293
1294 1294 def gennodelst(revlog):
1295 1295 for r in xrange(0, revlog.count()):
1296 1296 n = revlog.node(r)
1297 1297 if revlog.linkrev(n) in revset:
1298 1298 yield n
1299 1299
1300 1300 def changed_file_collector(changedfileset):
1301 1301 def collect_changed_files(clnode):
1302 1302 c = cl.read(clnode)
1303 1303 for fname in c[3]:
1304 1304 changedfileset[fname] = 1
1305 1305 return collect_changed_files
1306 1306
1307 1307 def lookuprevlink_func(revlog):
1308 1308 def lookuprevlink(n):
1309 1309 return cl.node(revlog.linkrev(n))
1310 1310 return lookuprevlink
1311 1311
1312 1312 def gengroup():
1313 1313 # construct a list of all changed files
1314 1314 changedfiles = {}
1315 1315
1316 1316 for chnk in cl.group(nodes, identity,
1317 1317 changed_file_collector(changedfiles)):
1318 1318 yield chnk
1319 1319 changedfiles = changedfiles.keys()
1320 1320 changedfiles.sort()
1321 1321
1322 1322 mnfst = self.manifest
1323 1323 nodeiter = gennodelst(mnfst)
1324 1324 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1325 1325 yield chnk
1326 1326
1327 1327 for fname in changedfiles:
1328 1328 filerevlog = self.file(fname)
1329 1329 nodeiter = gennodelst(filerevlog)
1330 1330 nodeiter = list(nodeiter)
1331 1331 if nodeiter:
1332 1332 yield changegroup.genchunk(fname)
1333 1333 lookup = lookuprevlink_func(filerevlog)
1334 1334 for chnk in filerevlog.group(nodeiter, lookup):
1335 1335 yield chnk
1336 1336
1337 1337 yield changegroup.closechunk()
1338 1338 self.hook('outgoing', node=hex(nodes[0]), source=source)
1339 1339
1340 1340 return util.chunkbuffer(gengroup())
1341 1341
1342 1342 def addchangegroup(self, source):
1343 1343
1344 1344 def csmap(x):
1345 1345 self.ui.debug(_("add changeset %s\n") % short(x))
1346 1346 return self.changelog.count()
1347 1347
1348 1348 def revmap(x):
1349 1349 return self.changelog.rev(x)
1350 1350
1351 1351 if not source:
1352 1352 return
1353 1353
1354 1354 self.hook('prechangegroup', throw=True)
1355 1355
1356 1356 changesets = files = revisions = 0
1357 1357
1358 1358 tr = self.transaction()
1359 1359
1360 1360 oldheads = len(self.changelog.heads())
1361 1361
1362 1362 # pull off the changeset group
1363 1363 self.ui.status(_("adding changesets\n"))
1364 1364 co = self.changelog.tip()
1365 1365 chunkiter = changegroup.chunkiter(source)
1366 1366 cn = self.changelog.addgroup(chunkiter, csmap, tr, 1) # unique
1367 1367 cnr, cor = map(self.changelog.rev, (cn, co))
1368 1368 if cn == nullid:
1369 1369 cnr = cor
1370 1370 changesets = cnr - cor
1371 1371
1372 1372 # pull off the manifest group
1373 1373 self.ui.status(_("adding manifests\n"))
1374 1374 mm = self.manifest.tip()
1375 1375 chunkiter = changegroup.chunkiter(source)
1376 1376 mo = self.manifest.addgroup(chunkiter, revmap, tr)
1377 1377
1378 1378 # process the files
1379 1379 self.ui.status(_("adding file changes\n"))
1380 1380 while 1:
1381 1381 f = changegroup.getchunk(source)
1382 1382 if not f:
1383 1383 break
1384 1384 self.ui.debug(_("adding %s revisions\n") % f)
1385 1385 fl = self.file(f)
1386 1386 o = fl.count()
1387 1387 chunkiter = changegroup.chunkiter(source)
1388 1388 n = fl.addgroup(chunkiter, revmap, tr)
1389 1389 revisions += fl.count() - o
1390 1390 files += 1
1391 1391
1392 1392 newheads = len(self.changelog.heads())
1393 1393 heads = ""
1394 1394 if oldheads and newheads > oldheads:
1395 1395 heads = _(" (+%d heads)") % (newheads - oldheads)
1396 1396
1397 1397 self.ui.status(_("added %d changesets"
1398 1398 " with %d changes to %d files%s\n")
1399 1399 % (changesets, revisions, files, heads))
1400 1400
1401 1401 self.hook('pretxnchangegroup', throw=True,
1402 1402 node=hex(self.changelog.node(cor+1)))
1403 1403
1404 1404 tr.close()
1405 1405
1406 1406 if changesets > 0:
1407 1407 self.hook("changegroup", node=hex(self.changelog.node(cor+1)))
1408 1408
1409 1409 for i in range(cor + 1, cnr + 1):
1410 1410 self.hook("incoming", node=hex(self.changelog.node(i)))
1411 1411
1412 1412 def update(self, node, allow=False, force=False, choose=None,
1413 1413 moddirstate=True, forcemerge=False, wlock=None):
1414 1414 pl = self.dirstate.parents()
1415 1415 if not force and pl[1] != nullid:
1416 1416 self.ui.warn(_("aborting: outstanding uncommitted merges\n"))
1417 1417 return 1
1418 1418
1419 1419 err = False
1420 1420
1421 1421 p1, p2 = pl[0], node
1422 1422 pa = self.changelog.ancestor(p1, p2)
1423 1423 m1n = self.changelog.read(p1)[0]
1424 1424 m2n = self.changelog.read(p2)[0]
1425 1425 man = self.manifest.ancestor(m1n, m2n)
1426 1426 m1 = self.manifest.read(m1n)
1427 1427 mf1 = self.manifest.readflags(m1n)
1428 1428 m2 = self.manifest.read(m2n).copy()
1429 1429 mf2 = self.manifest.readflags(m2n)
1430 1430 ma = self.manifest.read(man)
1431 1431 mfa = self.manifest.readflags(man)
1432 1432
1433 1433 modified, added, removed, deleted, unknown = self.changes()
1434 1434
1435 1435 # is this a jump, or a merge? i.e. is there a linear path
1436 1436 # from p1 to p2?
1437 1437 linear_path = (pa == p1 or pa == p2)
1438 1438
1439 1439 if allow and linear_path:
1440 1440 raise util.Abort(_("there is nothing to merge, "
1441 1441 "just use 'hg update'"))
1442 1442 if allow and not forcemerge:
1443 1443 if modified or added or removed:
1444 raise util.Abort(_("outstanding uncommited changes"))
1444 raise util.Abort(_("outstanding uncommitted changes"))
1445 1445 if not forcemerge and not force:
1446 1446 for f in unknown:
1447 1447 if f in m2:
1448 1448 t1 = self.wread(f)
1449 1449 t2 = self.file(f).read(m2[f])
1450 1450 if cmp(t1, t2) != 0:
1451 1451 raise util.Abort(_("'%s' already exists in the working"
1452 1452 " dir and differs from remote") % f)
1453 1453
1454 1454 # resolve the manifest to determine which files
1455 1455 # we care about merging
1456 1456 self.ui.note(_("resolving manifests\n"))
1457 1457 self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
1458 1458 (force, allow, moddirstate, linear_path))
1459 1459 self.ui.debug(_(" ancestor %s local %s remote %s\n") %
1460 1460 (short(man), short(m1n), short(m2n)))
1461 1461
1462 1462 merge = {}
1463 1463 get = {}
1464 1464 remove = []
1465 1465
1466 1466 # construct a working dir manifest
1467 1467 mw = m1.copy()
1468 1468 mfw = mf1.copy()
1469 1469 umap = dict.fromkeys(unknown)
1470 1470
1471 1471 for f in added + modified + unknown:
1472 1472 mw[f] = ""
1473 1473 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1474 1474
1475 1475 if moddirstate and not wlock:
1476 1476 wlock = self.wlock()
1477 1477
1478 1478 for f in deleted + removed:
1479 1479 if f in mw:
1480 1480 del mw[f]
1481 1481
1482 1482 # If we're jumping between revisions (as opposed to merging),
1483 1483 # and if neither the working directory nor the target rev has
1484 1484 # the file, then we need to remove it from the dirstate, to
1485 1485 # prevent the dirstate from listing the file when it is no
1486 1486 # longer in the manifest.
1487 1487 if moddirstate and linear_path and f not in m2:
1488 1488 self.dirstate.forget((f,))
1489 1489
1490 1490 # Compare manifests
1491 1491 for f, n in mw.iteritems():
1492 1492 if choose and not choose(f):
1493 1493 continue
1494 1494 if f in m2:
1495 1495 s = 0
1496 1496
1497 1497 # is the wfile new since m1, and match m2?
1498 1498 if f not in m1:
1499 1499 t1 = self.wread(f)
1500 1500 t2 = self.file(f).read(m2[f])
1501 1501 if cmp(t1, t2) == 0:
1502 1502 n = m2[f]
1503 1503 del t1, t2
1504 1504
1505 1505 # are files different?
1506 1506 if n != m2[f]:
1507 1507 a = ma.get(f, nullid)
1508 1508 # are both different from the ancestor?
1509 1509 if n != a and m2[f] != a:
1510 1510 self.ui.debug(_(" %s versions differ, resolve\n") % f)
1511 1511 # merge executable bits
1512 1512 # "if we changed or they changed, change in merge"
1513 1513 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1514 1514 mode = ((a^b) | (a^c)) ^ a
1515 1515 merge[f] = (m1.get(f, nullid), m2[f], mode)
1516 1516 s = 1
1517 1517 # are we clobbering?
1518 1518 # is remote's version newer?
1519 1519 # or are we going back in time?
1520 1520 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1521 1521 self.ui.debug(_(" remote %s is newer, get\n") % f)
1522 1522 get[f] = m2[f]
1523 1523 s = 1
1524 1524 elif f in umap:
1525 1525 # this unknown file is the same as the checkout
1526 1526 get[f] = m2[f]
1527 1527
1528 1528 if not s and mfw[f] != mf2[f]:
1529 1529 if force:
1530 1530 self.ui.debug(_(" updating permissions for %s\n") % f)
1531 1531 util.set_exec(self.wjoin(f), mf2[f])
1532 1532 else:
1533 1533 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1534 1534 mode = ((a^b) | (a^c)) ^ a
1535 1535 if mode != b:
1536 1536 self.ui.debug(_(" updating permissions for %s\n")
1537 1537 % f)
1538 1538 util.set_exec(self.wjoin(f), mode)
1539 1539 del m2[f]
1540 1540 elif f in ma:
1541 1541 if n != ma[f]:
1542 1542 r = _("d")
1543 1543 if not force and (linear_path or allow):
1544 1544 r = self.ui.prompt(
1545 1545 (_(" local changed %s which remote deleted\n") % f) +
1546 1546 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1547 1547 if r == _("d"):
1548 1548 remove.append(f)
1549 1549 else:
1550 1550 self.ui.debug(_("other deleted %s\n") % f)
1551 1551 remove.append(f) # other deleted it
1552 1552 else:
1553 1553 # file is created on branch or in working directory
1554 1554 if force and f not in umap:
1555 1555 self.ui.debug(_("remote deleted %s, clobbering\n") % f)
1556 1556 remove.append(f)
1557 1557 elif n == m1.get(f, nullid): # same as parent
1558 1558 if p2 == pa: # going backwards?
1559 1559 self.ui.debug(_("remote deleted %s\n") % f)
1560 1560 remove.append(f)
1561 1561 else:
1562 1562 self.ui.debug(_("local modified %s, keeping\n") % f)
1563 1563 else:
1564 1564 self.ui.debug(_("working dir created %s, keeping\n") % f)
1565 1565
1566 1566 for f, n in m2.iteritems():
1567 1567 if choose and not choose(f):
1568 1568 continue
1569 1569 if f[0] == "/":
1570 1570 continue
1571 1571 if f in ma and n != ma[f]:
1572 1572 r = _("k")
1573 1573 if not force and (linear_path or allow):
1574 1574 r = self.ui.prompt(
1575 1575 (_("remote changed %s which local deleted\n") % f) +
1576 1576 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1577 1577 if r == _("k"):
1578 1578 get[f] = n
1579 1579 elif f not in ma:
1580 1580 self.ui.debug(_("remote created %s\n") % f)
1581 1581 get[f] = n
1582 1582 else:
1583 1583 if force or p2 == pa: # going backwards?
1584 1584 self.ui.debug(_("local deleted %s, recreating\n") % f)
1585 1585 get[f] = n
1586 1586 else:
1587 1587 self.ui.debug(_("local deleted %s\n") % f)
1588 1588
1589 1589 del mw, m1, m2, ma
1590 1590
1591 1591 if force:
1592 1592 for f in merge:
1593 1593 get[f] = merge[f][1]
1594 1594 merge = {}
1595 1595
1596 1596 if linear_path or force:
1597 1597 # we don't need to do any magic, just jump to the new rev
1598 1598 branch_merge = False
1599 1599 p1, p2 = p2, nullid
1600 1600 else:
1601 1601 if not allow:
1602 1602 self.ui.status(_("this update spans a branch"
1603 1603 " affecting the following files:\n"))
1604 1604 fl = merge.keys() + get.keys()
1605 1605 fl.sort()
1606 1606 for f in fl:
1607 1607 cf = ""
1608 1608 if f in merge:
1609 1609 cf = _(" (resolve)")
1610 1610 self.ui.status(" %s%s\n" % (f, cf))
1611 1611 self.ui.warn(_("aborting update spanning branches!\n"))
1612 1612 self.ui.status(_("(use update -m to merge across branches"
1613 1613 " or -C to lose changes)\n"))
1614 1614 return 1
1615 1615 branch_merge = True
1616 1616
1617 1617 # get the files we don't need to change
1618 1618 files = get.keys()
1619 1619 files.sort()
1620 1620 for f in files:
1621 1621 if f[0] == "/":
1622 1622 continue
1623 1623 self.ui.note(_("getting %s\n") % f)
1624 1624 t = self.file(f).read(get[f])
1625 1625 self.wwrite(f, t)
1626 1626 util.set_exec(self.wjoin(f), mf2[f])
1627 1627 if moddirstate:
1628 1628 if branch_merge:
1629 1629 self.dirstate.update([f], 'n', st_mtime=-1)
1630 1630 else:
1631 1631 self.dirstate.update([f], 'n')
1632 1632
1633 1633 # merge the tricky bits
1634 1634 failedmerge = []
1635 1635 files = merge.keys()
1636 1636 files.sort()
1637 1637 xp1 = hex(p1)
1638 1638 xp2 = hex(p2)
1639 1639 for f in files:
1640 1640 self.ui.status(_("merging %s\n") % f)
1641 1641 my, other, flag = merge[f]
1642 1642 ret = self.merge3(f, my, other, xp1, xp2)
1643 1643 if ret:
1644 1644 err = True
1645 1645 failedmerge.append(f)
1646 1646 util.set_exec(self.wjoin(f), flag)
1647 1647 if moddirstate:
1648 1648 if branch_merge:
1649 1649 # We've done a branch merge, mark this file as merged
1650 1650 # so that we properly record the merger later
1651 1651 self.dirstate.update([f], 'm')
1652 1652 else:
1653 1653 # We've update-merged a locally modified file, so
1654 1654 # we set the dirstate to emulate a normal checkout
1655 1655 # of that file some time in the past. Thus our
1656 1656 # merge will appear as a normal local file
1657 1657 # modification.
1658 1658 f_len = len(self.file(f).read(other))
1659 1659 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1660 1660
1661 1661 remove.sort()
1662 1662 for f in remove:
1663 1663 self.ui.note(_("removing %s\n") % f)
1664 1664 util.audit_path(f)
1665 1665 try:
1666 1666 util.unlink(self.wjoin(f))
1667 1667 except OSError, inst:
1668 1668 if inst.errno != errno.ENOENT:
1669 1669 self.ui.warn(_("update failed to remove %s: %s!\n") %
1670 1670 (f, inst.strerror))
1671 1671 if moddirstate:
1672 1672 if branch_merge:
1673 1673 self.dirstate.update(remove, 'r')
1674 1674 else:
1675 1675 self.dirstate.forget(remove)
1676 1676
1677 1677 if moddirstate:
1678 1678 self.dirstate.setparents(p1, p2)
1679 1679
1680 1680 stat = ((len(get), _("updated")),
1681 1681 (len(merge) - len(failedmerge), _("merged")),
1682 1682 (len(remove), _("removed")),
1683 1683 (len(failedmerge), _("unresolved")))
1684 1684 note = ", ".join([_("%d files %s") % s for s in stat])
1685 1685 self.ui.note("%s\n" % note)
1686 1686 if moddirstate and branch_merge:
1687 1687 self.ui.note(_("(branch merge, don't forget to commit)\n"))
1688 1688
1689 1689 return err
1690 1690
1691 1691 def merge3(self, fn, my, other, p1, p2):
1692 1692 """perform a 3-way merge in the working directory"""
1693 1693
1694 1694 def temp(prefix, node):
1695 1695 pre = "%s~%s." % (os.path.basename(fn), prefix)
1696 1696 (fd, name) = tempfile.mkstemp("", pre)
1697 1697 f = os.fdopen(fd, "wb")
1698 1698 self.wwrite(fn, fl.read(node), f)
1699 1699 f.close()
1700 1700 return name
1701 1701
1702 1702 fl = self.file(fn)
1703 1703 base = fl.ancestor(my, other)
1704 1704 a = self.wjoin(fn)
1705 1705 b = temp("base", base)
1706 1706 c = temp("other", other)
1707 1707
1708 1708 self.ui.note(_("resolving %s\n") % fn)
1709 1709 self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
1710 1710 (fn, short(my), short(other), short(base)))
1711 1711
1712 1712 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1713 1713 or "hgmerge")
1714 1714 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=self.root,
1715 1715 environ={'HG_FILE': fn,
1716 1716 'HG_MY_NODE': p1,
1717 1717 'HG_OTHER_NODE': p2,
1718 1718 'HG_FILE_MY_NODE': hex(my),
1719 1719 'HG_FILE_OTHER_NODE': hex(other),
1720 1720 'HG_FILE_BASE_NODE': hex(base)})
1721 1721 if r:
1722 1722 self.ui.warn(_("merging %s failed!\n") % fn)
1723 1723
1724 1724 os.unlink(b)
1725 1725 os.unlink(c)
1726 1726 return r
1727 1727
1728 1728 def verify(self):
1729 1729 filelinkrevs = {}
1730 1730 filenodes = {}
1731 1731 changesets = revisions = files = 0
1732 1732 errors = [0]
1733 1733 neededmanifests = {}
1734 1734
1735 1735 def err(msg):
1736 1736 self.ui.warn(msg + "\n")
1737 1737 errors[0] += 1
1738 1738
1739 1739 def checksize(obj, name):
1740 1740 d = obj.checksize()
1741 1741 if d[0]:
1742 1742 err(_("%s data length off by %d bytes") % (name, d[0]))
1743 1743 if d[1]:
1744 1744 err(_("%s index contains %d extra bytes") % (name, d[1]))
1745 1745
1746 1746 seen = {}
1747 1747 self.ui.status(_("checking changesets\n"))
1748 1748 checksize(self.changelog, "changelog")
1749 1749
1750 1750 for i in range(self.changelog.count()):
1751 1751 changesets += 1
1752 1752 n = self.changelog.node(i)
1753 1753 l = self.changelog.linkrev(n)
1754 1754 if l != i:
1755 1755 err(_("incorrect link (%d) for changeset revision %d") %(l, i))
1756 1756 if n in seen:
1757 1757 err(_("duplicate changeset at revision %d") % i)
1758 1758 seen[n] = 1
1759 1759
1760 1760 for p in self.changelog.parents(n):
1761 1761 if p not in self.changelog.nodemap:
1762 1762 err(_("changeset %s has unknown parent %s") %
1763 1763 (short(n), short(p)))
1764 1764 try:
1765 1765 changes = self.changelog.read(n)
1766 1766 except KeyboardInterrupt:
1767 1767 self.ui.warn(_("interrupted"))
1768 1768 raise
1769 1769 except Exception, inst:
1770 1770 err(_("unpacking changeset %s: %s") % (short(n), inst))
1771 1771 continue
1772 1772
1773 1773 neededmanifests[changes[0]] = n
1774 1774
1775 1775 for f in changes[3]:
1776 1776 filelinkrevs.setdefault(f, []).append(i)
1777 1777
1778 1778 seen = {}
1779 1779 self.ui.status(_("checking manifests\n"))
1780 1780 checksize(self.manifest, "manifest")
1781 1781
1782 1782 for i in range(self.manifest.count()):
1783 1783 n = self.manifest.node(i)
1784 1784 l = self.manifest.linkrev(n)
1785 1785
1786 1786 if l < 0 or l >= self.changelog.count():
1787 1787 err(_("bad manifest link (%d) at revision %d") % (l, i))
1788 1788
1789 1789 if n in neededmanifests:
1790 1790 del neededmanifests[n]
1791 1791
1792 1792 if n in seen:
1793 1793 err(_("duplicate manifest at revision %d") % i)
1794 1794
1795 1795 seen[n] = 1
1796 1796
1797 1797 for p in self.manifest.parents(n):
1798 1798 if p not in self.manifest.nodemap:
1799 1799 err(_("manifest %s has unknown parent %s") %
1800 1800 (short(n), short(p)))
1801 1801
1802 1802 try:
1803 1803 delta = mdiff.patchtext(self.manifest.delta(n))
1804 1804 except KeyboardInterrupt:
1805 1805 self.ui.warn(_("interrupted"))
1806 1806 raise
1807 1807 except Exception, inst:
1808 1808 err(_("unpacking manifest %s: %s") % (short(n), inst))
1809 1809 continue
1810 1810
1811 1811 try:
1812 1812 ff = [ l.split('\0') for l in delta.splitlines() ]
1813 1813 for f, fn in ff:
1814 1814 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1815 1815 except (ValueError, TypeError), inst:
1816 1816 err(_("broken delta in manifest %s: %s") % (short(n), inst))
1817 1817
1818 1818 self.ui.status(_("crosschecking files in changesets and manifests\n"))
1819 1819
1820 1820 for m, c in neededmanifests.items():
1821 1821 err(_("Changeset %s refers to unknown manifest %s") %
1822 1822 (short(m), short(c)))
1823 1823 del neededmanifests
1824 1824
1825 1825 for f in filenodes:
1826 1826 if f not in filelinkrevs:
1827 1827 err(_("file %s in manifest but not in changesets") % f)
1828 1828
1829 1829 for f in filelinkrevs:
1830 1830 if f not in filenodes:
1831 1831 err(_("file %s in changeset but not in manifest") % f)
1832 1832
1833 1833 self.ui.status(_("checking files\n"))
1834 1834 ff = filenodes.keys()
1835 1835 ff.sort()
1836 1836 for f in ff:
1837 1837 if f == "/dev/null":
1838 1838 continue
1839 1839 files += 1
1840 1840 if not f:
1841 1841 err(_("file without name in manifest %s") % short(n))
1842 1842 continue
1843 1843 fl = self.file(f)
1844 1844 checksize(fl, f)
1845 1845
1846 1846 nodes = {nullid: 1}
1847 1847 seen = {}
1848 1848 for i in range(fl.count()):
1849 1849 revisions += 1
1850 1850 n = fl.node(i)
1851 1851
1852 1852 if n in seen:
1853 1853 err(_("%s: duplicate revision %d") % (f, i))
1854 1854 if n not in filenodes[f]:
1855 1855 err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
1856 1856 else:
1857 1857 del filenodes[f][n]
1858 1858
1859 1859 flr = fl.linkrev(n)
1860 1860 if flr not in filelinkrevs.get(f, []):
1861 1861 err(_("%s:%s points to unexpected changeset %d")
1862 1862 % (f, short(n), flr))
1863 1863 else:
1864 1864 filelinkrevs[f].remove(flr)
1865 1865
1866 1866 # verify contents
1867 1867 try:
1868 1868 t = fl.read(n)
1869 1869 except KeyboardInterrupt:
1870 1870 self.ui.warn(_("interrupted"))
1871 1871 raise
1872 1872 except Exception, inst:
1873 1873 err(_("unpacking file %s %s: %s") % (f, short(n), inst))
1874 1874
1875 1875 # verify parents
1876 1876 (p1, p2) = fl.parents(n)
1877 1877 if p1 not in nodes:
1878 1878 err(_("file %s:%s unknown parent 1 %s") %
1879 1879 (f, short(n), short(p1)))
1880 1880 if p2 not in nodes:
1881 1881 err(_("file %s:%s unknown parent 2 %s") %
1882 1882 (f, short(n), short(p1)))
1883 1883 nodes[n] = 1
1884 1884
1885 1885 # cross-check
1886 1886 for node in filenodes[f]:
1887 1887 err(_("node %s in manifests not in %s") % (hex(node), f))
1888 1888
1889 1889 self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
1890 1890 (files, changesets, revisions))
1891 1891
1892 1892 if errors[0]:
1893 1893 self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
1894 1894 return 1
1895 1895
1896 1896 # used to avoid circular references so destructors work
1897 1897 def aftertrans(base):
1898 1898 p = base
1899 1899 def a():
1900 1900 util.rename(os.path.join(p, "journal"), os.path.join(p, "undo"))
1901 1901 util.rename(os.path.join(p, "journal.dirstate"),
1902 1902 os.path.join(p, "undo.dirstate"))
1903 1903 return a
1904 1904
@@ -1,19 +1,19 b''
1 1 %% no merges expected
2 2 %% merge should fail
3 3 abort: 'b' already exists in the working dir and differs from remote
4 4 %% merge of b expected
5 5 merging for b
6 6 merging b
7 7 %%
8 8 Contents of b should be "this is file b1"
9 9 This is file b1
10 10 %% merge fails
11 abort: outstanding uncommited changes
11 abort: outstanding uncommitted changes
12 12 %% merge expected!
13 13 merging for b
14 14 merging b
15 15 %% merge of b should fail
16 abort: outstanding uncommited changes
16 abort: outstanding uncommitted changes
17 17 %% merge of b expected
18 18 merging for b
19 19 merging b
@@ -1,138 +1,138 b''
1 1 adding a
2 2 diff -r 33aaa84a386b a
3 3 --- a/a
4 4 +++ b/a
5 5 @@ -1,1 +1,1 @@ a
6 6 -a
7 7 +abc
8 8 adding b
9 9 M a
10 10 changeset: 0:33aaa84a386b
11 11 user: test
12 12 date: Mon Jan 12 13:46:40 1970 +0000
13 13 summary: 1
14 14
15 15 resolving manifests
16 16 force None allow None moddirstate True linear True
17 17 ancestor a0c8bcbbb45c local a0c8bcbbb45c remote 1165e8bd193e
18 18 a versions differ, resolve
19 19 remote created b
20 20 getting b
21 21 merging a
22 22 resolving a
23 23 file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2
24 24 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
25 25 changeset: 1:802f095af299
26 26 tag: tip
27 27 user: test
28 28 date: Mon Jan 12 13:46:40 1970 +0000
29 29 summary: 2
30 30
31 31 resolving manifests
32 32 force None allow None moddirstate True linear True
33 33 ancestor a0c8bcbbb45c local 1165e8bd193e remote a0c8bcbbb45c
34 34 remote deleted b
35 35 removing b
36 36 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
37 37 changeset: 0:33aaa84a386b
38 38 user: test
39 39 date: Mon Jan 12 13:46:40 1970 +0000
40 40 summary: 1
41 41
42 42 abort: there is nothing to merge, just use 'hg update'
43 43 failed
44 44 changeset: 0:33aaa84a386b
45 45 user: test
46 46 date: Mon Jan 12 13:46:40 1970 +0000
47 47 summary: 1
48 48
49 49 resolving manifests
50 50 force None allow None moddirstate True linear True
51 51 ancestor a0c8bcbbb45c local a0c8bcbbb45c remote 1165e8bd193e
52 52 a versions differ, resolve
53 53 remote created b
54 54 getting b
55 55 merging a
56 56 resolving a
57 57 file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2
58 58 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
59 59 changeset: 1:802f095af299
60 60 tag: tip
61 61 user: test
62 62 date: Mon Jan 12 13:46:40 1970 +0000
63 63 summary: 2
64 64
65 65 changeset: 1:802f095af299cde27a85b2f056aef3829870956c
66 66 tag: tip
67 67 user: test
68 68 date: Mon Jan 12 13:46:40 1970 +0000
69 69 files: a b
70 70 description:
71 71 2
72 72
73 73
74 74 changeset: 0:33aaa84a386bd609094aeb21a97c09436c482ef1
75 75 user: test
76 76 date: Mon Jan 12 13:46:40 1970 +0000
77 77 files: a
78 78 description:
79 79 1
80 80
81 81
82 82 diff -r 802f095af299 a
83 83 --- a/a
84 84 +++ b/a
85 85 @@ -1,1 +1,1 @@ a2
86 86 -a2
87 87 +abc
88 88 adding b
89 89 M a
90 90 changeset: 1:802f095af299
91 91 user: test
92 92 date: Mon Jan 12 13:46:40 1970 +0000
93 93 summary: 2
94 94
95 95 resolving manifests
96 96 force None allow None moddirstate True linear False
97 97 ancestor a0c8bcbbb45c local 1165e8bd193e remote 4096f2872392
98 98 a versions differ, resolve
99 99 b versions differ, resolve
100 100 this update spans a branch affecting the following files:
101 101 a (resolve)
102 102 b (resolve)
103 103 aborting update spanning branches!
104 104 (use update -m to merge across branches or -C to lose changes)
105 105 failed
106 abort: outstanding uncommited changes
106 abort: outstanding uncommitted changes
107 107 failed
108 108 resolving manifests
109 109 force None allow 1 moddirstate True linear False
110 110 ancestor a0c8bcbbb45c local 1165e8bd193e remote 4096f2872392
111 111 a versions differ, resolve
112 112 b versions differ, resolve
113 113 merging a
114 114 resolving a
115 115 file a: my d730145abbf9 other 13e0d5f949fa ancestor b789fdd96dc2
116 116 merging b
117 117 resolving b
118 118 file b: my 1e88685f5dde other 61de8c7723ca ancestor 000000000000
119 119 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
120 120 (branch merge, don't forget to commit)
121 121 changeset: 1:802f095af299
122 122 user: test
123 123 date: Mon Jan 12 13:46:40 1970 +0000
124 124 summary: 2
125 125
126 126 changeset: 2:030602aee63d
127 127 tag: tip
128 128 parent: 0:33aaa84a386b
129 129 user: test
130 130 date: Mon Jan 12 13:46:40 1970 +0000
131 131 summary: 3
132 132
133 133 diff -r 802f095af299 a
134 134 --- a/a
135 135 +++ b/a
136 136 @@ -1,1 +1,1 @@ a2
137 137 -a2
138 138 +abc
General Comments 0
You need to be logged in to leave comments. Login now