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