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