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