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