##// END OF EJS Templates
merge with crew-stable
Alexis S. L. Carvalho -
r4782:37e11c76 merge default
parent child Browse files
Show More
@@ -1,3118 +1,3124 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 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, urllib, shlex, stat
12 12 import ui, hg, util, revlog, bundlerepo, extensions
13 13 import difflib, patch, time, help, mdiff, tempfile
14 14 import errno, version, socket
15 15 import archival, changegroup, cmdutil, hgweb.server, sshserver
16 16
17 17 # Commands start here, listed alphabetically
18 18
19 19 def add(ui, repo, *pats, **opts):
20 20 """add the specified files on the next commit
21 21
22 22 Schedule files to be version controlled and added to the repository.
23 23
24 24 The files will be added to the repository at the next commit. To
25 25 undo an add before that, see hg revert.
26 26
27 27 If no names are given, add all files in the repository.
28 28 """
29 29
30 30 names = []
31 31 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
32 32 if exact:
33 33 if ui.verbose:
34 34 ui.status(_('adding %s\n') % rel)
35 35 names.append(abs)
36 36 elif repo.dirstate.state(abs) == '?':
37 37 ui.status(_('adding %s\n') % rel)
38 38 names.append(abs)
39 39 if not opts.get('dry_run'):
40 40 repo.add(names)
41 41
42 42 def addremove(ui, repo, *pats, **opts):
43 43 """add all new files, delete all missing files
44 44
45 45 Add all new files and remove all missing files from the repository.
46 46
47 47 New files are ignored if they match any of the patterns in .hgignore. As
48 48 with add, these changes take effect at the next commit.
49 49
50 50 Use the -s option to detect renamed files. With a parameter > 0,
51 51 this compares every removed file with every added file and records
52 52 those similar enough as renames. This option takes a percentage
53 53 between 0 (disabled) and 100 (files must be identical) as its
54 54 parameter. Detecting renamed files this way can be expensive.
55 55 """
56 56 sim = float(opts.get('similarity') or 0)
57 57 if sim < 0 or sim > 100:
58 58 raise util.Abort(_('similarity must be between 0 and 100'))
59 59 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
60 60
61 61 def annotate(ui, repo, *pats, **opts):
62 62 """show changeset information per file line
63 63
64 64 List changes in files, showing the revision id responsible for each line
65 65
66 66 This command is useful to discover who did a change or when a change took
67 67 place.
68 68
69 69 Without the -a option, annotate will avoid processing files it
70 70 detects as binary. With -a, annotate will generate an annotation
71 71 anyway, probably with undesirable results.
72 72 """
73 73 getdate = util.cachefunc(lambda x: util.datestr(x.date()))
74 74
75 75 if not pats:
76 76 raise util.Abort(_('at least one file name or pattern required'))
77 77
78 78 opmap = [['user', lambda x: ui.shortuser(x.user())],
79 79 ['number', lambda x: str(x.rev())],
80 80 ['changeset', lambda x: short(x.node())],
81 81 ['date', getdate], ['follow', lambda x: x.path()]]
82 82 if (not opts['user'] and not opts['changeset'] and not opts['date']
83 83 and not opts['follow']):
84 84 opts['number'] = 1
85 85
86 86 ctx = repo.changectx(opts['rev'])
87 87
88 88 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
89 89 node=ctx.node()):
90 90 fctx = ctx.filectx(abs)
91 91 if not opts['text'] and util.binary(fctx.data()):
92 92 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
93 93 continue
94 94
95 95 lines = fctx.annotate(follow=opts.get('follow'))
96 96 pieces = []
97 97
98 98 for o, f in opmap:
99 99 if opts[o]:
100 100 l = [f(n) for n, dummy in lines]
101 101 if l:
102 102 m = max(map(len, l))
103 103 pieces.append(["%*s" % (m, x) for x in l])
104 104
105 105 if pieces:
106 106 for p, l in zip(zip(*pieces), lines):
107 107 ui.write("%s: %s" % (" ".join(p), l[1]))
108 108
109 109 def archive(ui, repo, dest, **opts):
110 110 '''create unversioned archive of a repository revision
111 111
112 112 By default, the revision used is the parent of the working
113 113 directory; use "-r" to specify a different revision.
114 114
115 115 To specify the type of archive to create, use "-t". Valid
116 116 types are:
117 117
118 118 "files" (default): a directory full of files
119 119 "tar": tar archive, uncompressed
120 120 "tbz2": tar archive, compressed using bzip2
121 121 "tgz": tar archive, compressed using gzip
122 122 "uzip": zip archive, uncompressed
123 123 "zip": zip archive, compressed using deflate
124 124
125 125 The exact name of the destination archive or directory is given
126 126 using a format string; see "hg help export" for details.
127 127
128 128 Each member added to an archive file has a directory prefix
129 129 prepended. Use "-p" to specify a format string for the prefix.
130 130 The default is the basename of the archive, with suffixes removed.
131 131 '''
132 132
133 133 node = repo.changectx(opts['rev']).node()
134 134 dest = cmdutil.make_filename(repo, dest, node)
135 135 if os.path.realpath(dest) == repo.root:
136 136 raise util.Abort(_('repository root cannot be destination'))
137 137 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
138 138 kind = opts.get('type') or 'files'
139 139 prefix = opts['prefix']
140 140 if dest == '-':
141 141 if kind == 'files':
142 142 raise util.Abort(_('cannot archive plain files to stdout'))
143 143 dest = sys.stdout
144 144 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
145 145 prefix = cmdutil.make_filename(repo, prefix, node)
146 146 archival.archive(repo, dest, node, kind, not opts['no_decode'],
147 147 matchfn, prefix)
148 148
149 149 def backout(ui, repo, node=None, rev=None, **opts):
150 150 '''reverse effect of earlier changeset
151 151
152 152 Commit the backed out changes as a new changeset. The new
153 153 changeset is a child of the backed out changeset.
154 154
155 155 If you back out a changeset other than the tip, a new head is
156 156 created. This head is the parent of the working directory. If
157 157 you back out an old changeset, your working directory will appear
158 158 old after the backout. You should merge the backout changeset
159 159 with another head.
160 160
161 161 The --merge option remembers the parent of the working directory
162 162 before starting the backout, then merges the new head with that
163 163 changeset afterwards. This saves you from doing the merge by
164 164 hand. The result of this merge is not committed, as for a normal
165 165 merge.'''
166 166 if rev and node:
167 167 raise util.Abort(_("please specify just one revision"))
168 168
169 169 if not rev:
170 170 rev = node
171 171
172 172 if not rev:
173 173 raise util.Abort(_("please specify a revision to backout"))
174 174
175 175 cmdutil.bail_if_changed(repo)
176 176 op1, op2 = repo.dirstate.parents()
177 177 if op2 != nullid:
178 178 raise util.Abort(_('outstanding uncommitted merge'))
179 179 node = repo.lookup(rev)
180 180 p1, p2 = repo.changelog.parents(node)
181 181 if p1 == nullid:
182 182 raise util.Abort(_('cannot back out a change with no parents'))
183 183 if p2 != nullid:
184 184 if not opts['parent']:
185 185 raise util.Abort(_('cannot back out a merge changeset without '
186 186 '--parent'))
187 187 p = repo.lookup(opts['parent'])
188 188 if p not in (p1, p2):
189 189 raise util.Abort(_('%s is not a parent of %s') %
190 190 (short(p), short(node)))
191 191 parent = p
192 192 else:
193 193 if opts['parent']:
194 194 raise util.Abort(_('cannot use --parent on non-merge changeset'))
195 195 parent = p1
196 196 hg.clean(repo, node, show_stats=False)
197 197 revert_opts = opts.copy()
198 198 revert_opts['date'] = None
199 199 revert_opts['all'] = True
200 200 revert_opts['rev'] = hex(parent)
201 201 revert(ui, repo, **revert_opts)
202 202 commit_opts = opts.copy()
203 203 commit_opts['addremove'] = False
204 204 if not commit_opts['message'] and not commit_opts['logfile']:
205 205 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
206 206 commit_opts['force_editor'] = True
207 207 commit(ui, repo, **commit_opts)
208 208 def nice(node):
209 209 return '%d:%s' % (repo.changelog.rev(node), short(node))
210 210 ui.status(_('changeset %s backs out changeset %s\n') %
211 211 (nice(repo.changelog.tip()), nice(node)))
212 212 if op1 != node:
213 213 if opts['merge']:
214 214 ui.status(_('merging with changeset %s\n') % nice(op1))
215 215 hg.merge(repo, hex(op1))
216 216 else:
217 217 ui.status(_('the backout changeset is a new head - '
218 218 'do not forget to merge\n'))
219 219 ui.status(_('(use "backout --merge" '
220 220 'if you want to auto-merge)\n'))
221 221
222 222 def branch(ui, repo, label=None, **opts):
223 223 """set or show the current branch name
224 224
225 225 With no argument, show the current branch name. With one argument,
226 226 set the working directory branch name (the branch does not exist in
227 227 the repository until the next commit).
228 228
229 229 Unless --force is specified, branch will not let you set a
230 230 branch name that shadows an existing branch.
231 231 """
232 232
233 233 if label:
234 234 if not opts.get('force') and label in repo.branchtags():
235 235 if label not in [p.branch() for p in repo.workingctx().parents()]:
236 236 raise util.Abort(_('a branch of the same name already exists'
237 237 ' (use --force to override)'))
238 238 repo.dirstate.setbranch(util.fromlocal(label))
239 239 ui.status(_('marked working directory as branch %s\n') % label)
240 240 else:
241 241 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
242 242
243 243 def branches(ui, repo, active=False):
244 244 """list repository named branches
245 245
246 246 List the repository's named branches, indicating which ones are
247 247 inactive. If active is specified, only show active branches.
248 248
249 249 A branch is considered active if it contains unmerged heads.
250 250 """
251 251 b = repo.branchtags()
252 252 heads = dict.fromkeys(repo.heads(), 1)
253 253 l = [((n in heads), repo.changelog.rev(n), n, t) for t, n in b.items()]
254 254 l.sort()
255 255 l.reverse()
256 256 for ishead, r, n, t in l:
257 257 if active and not ishead:
258 258 # If we're only displaying active branches, abort the loop on
259 259 # encountering the first inactive head
260 260 break
261 261 else:
262 262 hexfunc = ui.debugflag and hex or short
263 263 if ui.quiet:
264 264 ui.write("%s\n" % t)
265 265 else:
266 266 spaces = " " * (30 - util.locallen(t))
267 267 # The code only gets here if inactive branches are being
268 268 # displayed or the branch is active.
269 269 isinactive = ((not ishead) and " (inactive)") or ''
270 270 ui.write("%s%s %s:%s%s\n" % (t, spaces, r, hexfunc(n), isinactive))
271 271
272 272 def bundle(ui, repo, fname, dest=None, **opts):
273 273 """create a changegroup file
274 274
275 275 Generate a compressed changegroup file collecting changesets not
276 276 found in the other repository.
277 277
278 278 If no destination repository is specified the destination is assumed
279 279 to have all the nodes specified by one or more --base parameters.
280 280
281 281 The bundle file can then be transferred using conventional means and
282 282 applied to another repository with the unbundle or pull command.
283 283 This is useful when direct push and pull are not available or when
284 284 exporting an entire repository is undesirable.
285 285
286 286 Applying bundles preserves all changeset contents including
287 287 permissions, copy/rename information, and revision history.
288 288 """
289 289 revs = opts.get('rev') or None
290 290 if revs:
291 291 revs = [repo.lookup(rev) for rev in revs]
292 292 base = opts.get('base')
293 293 if base:
294 294 if dest:
295 295 raise util.Abort(_("--base is incompatible with specifiying "
296 296 "a destination"))
297 297 base = [repo.lookup(rev) for rev in base]
298 298 # create the right base
299 299 # XXX: nodesbetween / changegroup* should be "fixed" instead
300 300 o = []
301 301 has = {nullid: None}
302 302 for n in base:
303 303 has.update(repo.changelog.reachable(n))
304 304 if revs:
305 305 visit = list(revs)
306 306 else:
307 307 visit = repo.changelog.heads()
308 308 seen = {}
309 309 while visit:
310 310 n = visit.pop(0)
311 311 parents = [p for p in repo.changelog.parents(n) if p not in has]
312 312 if len(parents) == 0:
313 313 o.insert(0, n)
314 314 else:
315 315 for p in parents:
316 316 if p not in seen:
317 317 seen[p] = 1
318 318 visit.append(p)
319 319 else:
320 320 cmdutil.setremoteconfig(ui, opts)
321 321 dest, revs = cmdutil.parseurl(
322 322 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
323 323 other = hg.repository(ui, dest)
324 324 o = repo.findoutgoing(other, force=opts['force'])
325 325
326 326 if revs:
327 327 cg = repo.changegroupsubset(o, revs, 'bundle')
328 328 else:
329 329 cg = repo.changegroup(o, 'bundle')
330 330 changegroup.writebundle(cg, fname, "HG10BZ")
331 331
332 332 def cat(ui, repo, file1, *pats, **opts):
333 333 """output the current or given revision of files
334 334
335 335 Print the specified files as they were at the given revision.
336 336 If no revision is given, the parent of the working directory is used,
337 337 or tip if no revision is checked out.
338 338
339 339 Output may be to a file, in which case the name of the file is
340 340 given using a format string. The formatting rules are the same as
341 341 for the export command, with the following additions:
342 342
343 343 %s basename of file being printed
344 344 %d dirname of file being printed, or '.' if in repo root
345 345 %p root-relative path name of file being printed
346 346 """
347 347 ctx = repo.changectx(opts['rev'])
348 348 err = 1
349 349 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
350 350 ctx.node()):
351 351 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
352 352 fp.write(ctx.filectx(abs).data())
353 353 err = 0
354 354 return err
355 355
356 356 def clone(ui, source, dest=None, **opts):
357 357 """make a copy of an existing repository
358 358
359 359 Create a copy of an existing repository in a new directory.
360 360
361 361 If no destination directory name is specified, it defaults to the
362 362 basename of the source.
363 363
364 364 The location of the source is added to the new repository's
365 365 .hg/hgrc file, as the default to be used for future pulls.
366 366
367 367 For efficiency, hardlinks are used for cloning whenever the source
368 368 and destination are on the same filesystem (note this applies only
369 369 to the repository data, not to the checked out files). Some
370 370 filesystems, such as AFS, implement hardlinking incorrectly, but
371 371 do not report errors. In these cases, use the --pull option to
372 372 avoid hardlinking.
373 373
374 374 You can safely clone repositories and checked out files using full
375 375 hardlinks with
376 376
377 377 $ cp -al REPO REPOCLONE
378 378
379 379 which is the fastest way to clone. However, the operation is not
380 380 atomic (making sure REPO is not modified during the operation is
381 381 up to you) and you have to make sure your editor breaks hardlinks
382 382 (Emacs and most Linux Kernel tools do so).
383 383
384 384 If you use the -r option to clone up to a specific revision, no
385 385 subsequent revisions will be present in the cloned repository.
386 386 This option implies --pull, even on local repositories.
387 387
388 388 See pull for valid source format details.
389 389
390 390 It is possible to specify an ssh:// URL as the destination, but no
391 391 .hg/hgrc and working directory will be created on the remote side.
392 392 Look at the help text for the pull command for important details
393 393 about ssh:// URLs.
394 394 """
395 395 cmdutil.setremoteconfig(ui, opts)
396 396 hg.clone(ui, source, dest,
397 397 pull=opts['pull'],
398 398 stream=opts['uncompressed'],
399 399 rev=opts['rev'],
400 400 update=not opts['noupdate'])
401 401
402 402 def commit(ui, repo, *pats, **opts):
403 403 """commit the specified files or all outstanding changes
404 404
405 405 Commit changes to the given files into the repository.
406 406
407 407 If a list of files is omitted, all changes reported by "hg status"
408 408 will be committed.
409 409
410 410 If no commit message is specified, the editor configured in your hgrc
411 411 or in the EDITOR environment variable is started to enter a message.
412 412 """
413 413 message = cmdutil.logmessage(opts)
414 414
415 415 if opts['addremove']:
416 416 cmdutil.addremove(repo, pats, opts)
417 417 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
418 418 if pats:
419 419 status = repo.status(files=fns, match=match)
420 420 modified, added, removed, deleted, unknown = status[:5]
421 421 files = modified + added + removed
422 422 slist = None
423 423 for f in fns:
424 424 if f == '.':
425 425 continue
426 426 if f not in files:
427 427 rf = repo.wjoin(f)
428 428 try:
429 429 mode = os.lstat(rf)[stat.ST_MODE]
430 430 except OSError:
431 431 raise util.Abort(_("file %s not found!") % rf)
432 432 if stat.S_ISDIR(mode):
433 433 name = f + '/'
434 434 if slist is None:
435 435 slist = list(files)
436 436 slist.sort()
437 437 i = bisect.bisect(slist, name)
438 438 if i >= len(slist) or not slist[i].startswith(name):
439 439 raise util.Abort(_("no match under directory %s!")
440 440 % rf)
441 441 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
442 442 raise util.Abort(_("can't commit %s: "
443 443 "unsupported file type!") % rf)
444 444 elif repo.dirstate.state(f) == '?':
445 445 raise util.Abort(_("file %s not tracked!") % rf)
446 446 else:
447 447 files = []
448 448 try:
449 449 repo.commit(files, message, opts['user'], opts['date'], match,
450 450 force_editor=opts.get('force_editor'))
451 451 except ValueError, inst:
452 452 raise util.Abort(str(inst))
453 453
454 454 def docopy(ui, repo, pats, opts, wlock):
455 455 # called with the repo lock held
456 456 #
457 457 # hgsep => pathname that uses "/" to separate directories
458 458 # ossep => pathname that uses os.sep to separate directories
459 459 cwd = repo.getcwd()
460 460 errors = 0
461 461 copied = []
462 462 targets = {}
463 463
464 464 # abs: hgsep
465 465 # rel: ossep
466 466 # return: hgsep
467 467 def okaytocopy(abs, rel, exact):
468 468 reasons = {'?': _('is not managed'),
469 'a': _('has been marked for add'),
470 469 'r': _('has been marked for remove')}
471 470 state = repo.dirstate.state(abs)
472 471 reason = reasons.get(state)
473 472 if reason:
473 if exact:
474 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
475 else:
474 476 if state == 'a':
475 477 origsrc = repo.dirstate.copied(abs)
476 478 if origsrc is not None:
477 479 return origsrc
478 if exact:
479 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
480 else:
481 480 return abs
482 481
483 482 # origsrc: hgsep
484 483 # abssrc: hgsep
485 484 # relsrc: ossep
486 485 # otarget: ossep
487 486 def copy(origsrc, abssrc, relsrc, otarget, exact):
488 487 abstarget = util.canonpath(repo.root, cwd, otarget)
489 488 reltarget = repo.pathto(abstarget, cwd)
490 489 prevsrc = targets.get(abstarget)
491 490 src = repo.wjoin(abssrc)
492 491 target = repo.wjoin(abstarget)
493 492 if prevsrc is not None:
494 493 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
495 494 (reltarget, repo.pathto(abssrc, cwd),
496 495 repo.pathto(prevsrc, cwd)))
497 496 return
498 497 if (not opts['after'] and os.path.exists(target) or
499 498 opts['after'] and repo.dirstate.state(abstarget) not in '?ar'):
500 499 if not opts['force']:
501 500 ui.warn(_('%s: not overwriting - file exists\n') %
502 501 reltarget)
503 502 return
504 503 if not opts['after'] and not opts.get('dry_run'):
505 504 os.unlink(target)
506 505 if opts['after']:
507 506 if not os.path.exists(target):
508 507 return
509 508 else:
510 509 targetdir = os.path.dirname(target) or '.'
511 510 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
512 511 os.makedirs(targetdir)
513 512 try:
514 513 restore = repo.dirstate.state(abstarget) == 'r'
515 514 if restore and not opts.get('dry_run'):
516 515 repo.undelete([abstarget], wlock)
517 516 try:
518 517 if not opts.get('dry_run'):
519 518 util.copyfile(src, target)
520 519 restore = False
521 520 finally:
522 521 if restore:
523 522 repo.remove([abstarget], wlock=wlock)
524 523 except IOError, inst:
525 524 if inst.errno == errno.ENOENT:
526 525 ui.warn(_('%s: deleted in working copy\n') % relsrc)
527 526 else:
528 527 ui.warn(_('%s: cannot copy - %s\n') %
529 528 (relsrc, inst.strerror))
530 529 errors += 1
531 530 return
532 531 if ui.verbose or not exact:
533 532 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
534 533 targets[abstarget] = abssrc
535 if abstarget != origsrc and not opts.get('dry_run'):
536 repo.copy(origsrc, abstarget, wlock)
534 if abstarget != origsrc:
535 if repo.dirstate.state(origsrc) == 'a':
536 ui.warn(_("%s was marked for addition. "
537 "%s will not be committed as a copy.\n")
538 % (repo.pathto(origsrc, cwd), reltarget))
539 if abstarget not in repo.dirstate and not opts.get('dry_run'):
540 repo.add([abstarget], wlock)
541 elif not opts.get('dry_run'):
542 repo.copy(origsrc, abstarget, wlock)
537 543 copied.append((abssrc, relsrc, exact))
538 544
539 545 # pat: ossep
540 546 # dest ossep
541 547 # srcs: list of (hgsep, hgsep, ossep, bool)
542 548 # return: function that takes hgsep and returns ossep
543 549 def targetpathfn(pat, dest, srcs):
544 550 if os.path.isdir(pat):
545 551 abspfx = util.canonpath(repo.root, cwd, pat)
546 552 abspfx = util.localpath(abspfx)
547 553 if destdirexists:
548 554 striplen = len(os.path.split(abspfx)[0])
549 555 else:
550 556 striplen = len(abspfx)
551 557 if striplen:
552 558 striplen += len(os.sep)
553 559 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
554 560 elif destdirexists:
555 561 res = lambda p: os.path.join(dest,
556 562 os.path.basename(util.localpath(p)))
557 563 else:
558 564 res = lambda p: dest
559 565 return res
560 566
561 567 # pat: ossep
562 568 # dest ossep
563 569 # srcs: list of (hgsep, hgsep, ossep, bool)
564 570 # return: function that takes hgsep and returns ossep
565 571 def targetpathafterfn(pat, dest, srcs):
566 572 if util.patkind(pat, None)[0]:
567 573 # a mercurial pattern
568 574 res = lambda p: os.path.join(dest,
569 575 os.path.basename(util.localpath(p)))
570 576 else:
571 577 abspfx = util.canonpath(repo.root, cwd, pat)
572 578 if len(abspfx) < len(srcs[0][0]):
573 579 # A directory. Either the target path contains the last
574 580 # component of the source path or it does not.
575 581 def evalpath(striplen):
576 582 score = 0
577 583 for s in srcs:
578 584 t = os.path.join(dest, util.localpath(s[0])[striplen:])
579 585 if os.path.exists(t):
580 586 score += 1
581 587 return score
582 588
583 589 abspfx = util.localpath(abspfx)
584 590 striplen = len(abspfx)
585 591 if striplen:
586 592 striplen += len(os.sep)
587 593 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
588 594 score = evalpath(striplen)
589 595 striplen1 = len(os.path.split(abspfx)[0])
590 596 if striplen1:
591 597 striplen1 += len(os.sep)
592 598 if evalpath(striplen1) > score:
593 599 striplen = striplen1
594 600 res = lambda p: os.path.join(dest,
595 601 util.localpath(p)[striplen:])
596 602 else:
597 603 # a file
598 604 if destdirexists:
599 605 res = lambda p: os.path.join(dest,
600 606 os.path.basename(util.localpath(p)))
601 607 else:
602 608 res = lambda p: dest
603 609 return res
604 610
605 611
606 612 pats = util.expand_glob(pats)
607 613 if not pats:
608 614 raise util.Abort(_('no source or destination specified'))
609 615 if len(pats) == 1:
610 616 raise util.Abort(_('no destination specified'))
611 617 dest = pats.pop()
612 618 destdirexists = os.path.isdir(dest)
613 619 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
614 620 raise util.Abort(_('with multiple sources, destination must be an '
615 621 'existing directory'))
616 622 if opts['after']:
617 623 tfn = targetpathafterfn
618 624 else:
619 625 tfn = targetpathfn
620 626 copylist = []
621 627 for pat in pats:
622 628 srcs = []
623 629 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts,
624 630 globbed=True):
625 631 origsrc = okaytocopy(abssrc, relsrc, exact)
626 632 if origsrc:
627 633 srcs.append((origsrc, abssrc, relsrc, exact))
628 634 if not srcs:
629 635 continue
630 636 copylist.append((tfn(pat, dest, srcs), srcs))
631 637 if not copylist:
632 638 raise util.Abort(_('no files to copy'))
633 639
634 640 for targetpath, srcs in copylist:
635 641 for origsrc, abssrc, relsrc, exact in srcs:
636 642 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
637 643
638 644 if errors:
639 645 ui.warn(_('(consider using --after)\n'))
640 646 return errors, copied
641 647
642 648 def copy(ui, repo, *pats, **opts):
643 649 """mark files as copied for the next commit
644 650
645 651 Mark dest as having copies of source files. If dest is a
646 652 directory, copies are put in that directory. If dest is a file,
647 653 there can only be one source.
648 654
649 655 By default, this command copies the contents of files as they
650 656 stand in the working directory. If invoked with --after, the
651 657 operation is recorded, but no copying is performed.
652 658
653 659 This command takes effect in the next commit. To undo a copy
654 660 before that, see hg revert.
655 661 """
656 662 wlock = repo.wlock(0)
657 663 errs, copied = docopy(ui, repo, pats, opts, wlock)
658 664 return errs
659 665
660 666 def debugancestor(ui, index, rev1, rev2):
661 667 """find the ancestor revision of two revisions in a given index"""
662 668 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
663 669 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
664 670 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
665 671
666 672 def debugcomplete(ui, cmd='', **opts):
667 673 """returns the completion list associated with the given command"""
668 674
669 675 if opts['options']:
670 676 options = []
671 677 otables = [globalopts]
672 678 if cmd:
673 679 aliases, entry = cmdutil.findcmd(ui, cmd)
674 680 otables.append(entry[1])
675 681 for t in otables:
676 682 for o in t:
677 683 if o[0]:
678 684 options.append('-%s' % o[0])
679 685 options.append('--%s' % o[1])
680 686 ui.write("%s\n" % "\n".join(options))
681 687 return
682 688
683 689 clist = cmdutil.findpossible(ui, cmd).keys()
684 690 clist.sort()
685 691 ui.write("%s\n" % "\n".join(clist))
686 692
687 693 def debugrebuildstate(ui, repo, rev=""):
688 694 """rebuild the dirstate as it would look like for the given revision"""
689 695 if rev == "":
690 696 rev = repo.changelog.tip()
691 697 ctx = repo.changectx(rev)
692 698 files = ctx.manifest()
693 699 wlock = repo.wlock()
694 700 repo.dirstate.rebuild(rev, files)
695 701
696 702 def debugcheckstate(ui, repo):
697 703 """validate the correctness of the current dirstate"""
698 704 parent1, parent2 = repo.dirstate.parents()
699 705 dc = repo.dirstate
700 706 m1 = repo.changectx(parent1).manifest()
701 707 m2 = repo.changectx(parent2).manifest()
702 708 errors = 0
703 709 for f in dc:
704 710 state = repo.dirstate.state(f)
705 711 if state in "nr" and f not in m1:
706 712 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
707 713 errors += 1
708 714 if state in "a" and f in m1:
709 715 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
710 716 errors += 1
711 717 if state in "m" and f not in m1 and f not in m2:
712 718 ui.warn(_("%s in state %s, but not in either manifest\n") %
713 719 (f, state))
714 720 errors += 1
715 721 for f in m1:
716 722 state = repo.dirstate.state(f)
717 723 if state not in "nrm":
718 724 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
719 725 errors += 1
720 726 if errors:
721 727 error = _(".hg/dirstate inconsistent with current parent's manifest")
722 728 raise util.Abort(error)
723 729
724 730 def showconfig(ui, repo, *values, **opts):
725 731 """show combined config settings from all hgrc files
726 732
727 733 With no args, print names and values of all config items.
728 734
729 735 With one arg of the form section.name, print just the value of
730 736 that config item.
731 737
732 738 With multiple args, print names and values of all config items
733 739 with matching section names."""
734 740
735 741 untrusted = bool(opts.get('untrusted'))
736 742 if values:
737 743 if len([v for v in values if '.' in v]) > 1:
738 744 raise util.Abort(_('only one config item permitted'))
739 745 for section, name, value in ui.walkconfig(untrusted=untrusted):
740 746 sectname = section + '.' + name
741 747 if values:
742 748 for v in values:
743 749 if v == section:
744 750 ui.write('%s=%s\n' % (sectname, value))
745 751 elif v == sectname:
746 752 ui.write(value, '\n')
747 753 else:
748 754 ui.write('%s=%s\n' % (sectname, value))
749 755
750 756 def debugsetparents(ui, repo, rev1, rev2=None):
751 757 """manually set the parents of the current working directory
752 758
753 759 This is useful for writing repository conversion tools, but should
754 760 be used with care.
755 761 """
756 762
757 763 if not rev2:
758 764 rev2 = hex(nullid)
759 765
760 766 wlock = repo.wlock()
761 767 try:
762 768 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
763 769 finally:
764 770 wlock.release()
765 771
766 772 def debugstate(ui, repo):
767 773 """show the contents of the current dirstate"""
768 774 dc = repo.dirstate
769 775 for file_ in dc:
770 776 if dc[file_][3] == -1:
771 777 # Pad or slice to locale representation
772 778 locale_len = len(time.strftime("%x %X", time.localtime(0)))
773 779 timestr = 'unset'
774 780 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
775 781 else:
776 782 timestr = time.strftime("%x %X", time.localtime(dc[file_][3]))
777 783 ui.write("%c %3o %10d %s %s\n"
778 784 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
779 785 timestr, file_))
780 786 for f in repo.dirstate.copies():
781 787 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
782 788
783 789 def debugdata(ui, file_, rev):
784 790 """dump the contents of a data file revision"""
785 791 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
786 792 try:
787 793 ui.write(r.revision(r.lookup(rev)))
788 794 except KeyError:
789 795 raise util.Abort(_('invalid revision identifier %s') % rev)
790 796
791 797 def debugdate(ui, date, range=None, **opts):
792 798 """parse and display a date"""
793 799 if opts["extended"]:
794 800 d = util.parsedate(date, util.extendeddateformats)
795 801 else:
796 802 d = util.parsedate(date)
797 803 ui.write("internal: %s %s\n" % d)
798 804 ui.write("standard: %s\n" % util.datestr(d))
799 805 if range:
800 806 m = util.matchdate(range)
801 807 ui.write("match: %s\n" % m(d[0]))
802 808
803 809 def debugindex(ui, file_):
804 810 """dump the contents of an index file"""
805 811 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
806 812 ui.write(" rev offset length base linkrev" +
807 813 " nodeid p1 p2\n")
808 814 for i in xrange(r.count()):
809 815 node = r.node(i)
810 816 pp = r.parents(node)
811 817 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
812 818 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
813 819 short(node), short(pp[0]), short(pp[1])))
814 820
815 821 def debugindexdot(ui, file_):
816 822 """dump an index DAG as a .dot file"""
817 823 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
818 824 ui.write("digraph G {\n")
819 825 for i in xrange(r.count()):
820 826 node = r.node(i)
821 827 pp = r.parents(node)
822 828 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
823 829 if pp[1] != nullid:
824 830 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
825 831 ui.write("}\n")
826 832
827 833 def debuginstall(ui):
828 834 '''test Mercurial installation'''
829 835
830 836 def writetemp(contents):
831 837 (fd, name) = tempfile.mkstemp()
832 838 f = os.fdopen(fd, "wb")
833 839 f.write(contents)
834 840 f.close()
835 841 return name
836 842
837 843 problems = 0
838 844
839 845 # encoding
840 846 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
841 847 try:
842 848 util.fromlocal("test")
843 849 except util.Abort, inst:
844 850 ui.write(" %s\n" % inst)
845 851 ui.write(_(" (check that your locale is properly set)\n"))
846 852 problems += 1
847 853
848 854 # compiled modules
849 855 ui.status(_("Checking extensions...\n"))
850 856 try:
851 857 import bdiff, mpatch, base85
852 858 except Exception, inst:
853 859 ui.write(" %s\n" % inst)
854 860 ui.write(_(" One or more extensions could not be found"))
855 861 ui.write(_(" (check that you compiled the extensions)\n"))
856 862 problems += 1
857 863
858 864 # templates
859 865 ui.status(_("Checking templates...\n"))
860 866 try:
861 867 import templater
862 868 t = templater.templater(templater.templatepath("map-cmdline.default"))
863 869 except Exception, inst:
864 870 ui.write(" %s\n" % inst)
865 871 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
866 872 problems += 1
867 873
868 874 # patch
869 875 ui.status(_("Checking patch...\n"))
870 876 patcher = ui.config('ui', 'patch')
871 877 patcher = ((patcher and util.find_exe(patcher)) or
872 878 util.find_exe('gpatch') or
873 879 util.find_exe('patch'))
874 880 if not patcher:
875 881 ui.write(_(" Can't find patch or gpatch in PATH\n"))
876 882 ui.write(_(" (specify a patch utility in your .hgrc file)\n"))
877 883 problems += 1
878 884 else:
879 885 # actually attempt a patch here
880 886 a = "1\n2\n3\n4\n"
881 887 b = "1\n2\n3\ninsert\n4\n"
882 888 fa = writetemp(a)
883 889 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa))
884 890 fd = writetemp(d)
885 891
886 892 files = {}
887 893 try:
888 894 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
889 895 except util.Abort, e:
890 896 ui.write(_(" patch call failed:\n"))
891 897 ui.write(" " + str(e) + "\n")
892 898 problems += 1
893 899 else:
894 900 if list(files) != [os.path.basename(fa)]:
895 901 ui.write(_(" unexpected patch output!"))
896 902 ui.write(_(" (you may have an incompatible version of patch)\n"))
897 903 problems += 1
898 904 a = file(fa).read()
899 905 if a != b:
900 906 ui.write(_(" patch test failed!"))
901 907 ui.write(_(" (you may have an incompatible version of patch)\n"))
902 908 problems += 1
903 909
904 910 os.unlink(fa)
905 911 os.unlink(fd)
906 912
907 913 # merge helper
908 914 ui.status(_("Checking merge helper...\n"))
909 915 cmd = (os.environ.get("HGMERGE") or ui.config("ui", "merge")
910 916 or "hgmerge")
911 917 cmdpath = util.find_exe(cmd) or util.find_exe(cmd.split()[0])
912 918 if not cmdpath:
913 919 if cmd == 'hgmerge':
914 920 ui.write(_(" No merge helper set and can't find default"
915 921 " hgmerge script in PATH\n"))
916 922 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
917 923 else:
918 924 ui.write(_(" Can't find merge helper '%s' in PATH\n") % cmd)
919 925 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
920 926 problems += 1
921 927 else:
922 928 # actually attempt a patch here
923 929 fa = writetemp("1\n2\n3\n4\n")
924 930 fl = writetemp("1\n2\n3\ninsert\n4\n")
925 931 fr = writetemp("begin\n1\n2\n3\n4\n")
926 932 r = os.system('%s %s %s %s' % (cmd, fl, fa, fr))
927 933 if r:
928 934 ui.write(_(" got unexpected merge error %d!") % r)
929 935 problems += 1
930 936 m = file(fl).read()
931 937 if m != "begin\n1\n2\n3\ninsert\n4\n":
932 938 ui.write(_(" got unexpected merge results!") % r)
933 939 ui.write(_(" (your merge helper may have the"
934 940 " wrong argument order)\n"))
935 941 ui.write(m)
936 942 os.unlink(fa)
937 943 os.unlink(fl)
938 944 os.unlink(fr)
939 945
940 946 # editor
941 947 ui.status(_("Checking commit editor...\n"))
942 948 editor = (os.environ.get("HGEDITOR") or
943 949 ui.config("ui", "editor") or
944 950 os.environ.get("EDITOR", "vi"))
945 951 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
946 952 if not cmdpath:
947 953 if editor == 'vi':
948 954 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
949 955 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
950 956 else:
951 957 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
952 958 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
953 959 problems += 1
954 960
955 961 # check username
956 962 ui.status(_("Checking username...\n"))
957 963 user = os.environ.get("HGUSER")
958 964 if user is None:
959 965 user = ui.config("ui", "username")
960 966 if user is None:
961 967 user = os.environ.get("EMAIL")
962 968 if not user:
963 969 ui.warn(" ")
964 970 ui.username()
965 971 ui.write(_(" (specify a username in your .hgrc file)\n"))
966 972
967 973 if not problems:
968 974 ui.status(_("No problems detected\n"))
969 975 else:
970 976 ui.write(_("%s problems detected,"
971 977 " please check your install!\n") % problems)
972 978
973 979 return problems
974 980
975 981 def debugrename(ui, repo, file1, *pats, **opts):
976 982 """dump rename information"""
977 983
978 984 ctx = repo.changectx(opts.get('rev', 'tip'))
979 985 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
980 986 ctx.node()):
981 987 m = ctx.filectx(abs).renamed()
982 988 if m:
983 989 ui.write(_("%s renamed from %s:%s\n") % (rel, m[0], hex(m[1])))
984 990 else:
985 991 ui.write(_("%s not renamed\n") % rel)
986 992
987 993 def debugwalk(ui, repo, *pats, **opts):
988 994 """show how files match on given patterns"""
989 995 items = list(cmdutil.walk(repo, pats, opts))
990 996 if not items:
991 997 return
992 998 fmt = '%%s %%-%ds %%-%ds %%s' % (
993 999 max([len(abs) for (src, abs, rel, exact) in items]),
994 1000 max([len(rel) for (src, abs, rel, exact) in items]))
995 1001 for src, abs, rel, exact in items:
996 1002 line = fmt % (src, abs, rel, exact and 'exact' or '')
997 1003 ui.write("%s\n" % line.rstrip())
998 1004
999 1005 def diff(ui, repo, *pats, **opts):
1000 1006 """diff repository (or selected files)
1001 1007
1002 1008 Show differences between revisions for the specified files.
1003 1009
1004 1010 Differences between files are shown using the unified diff format.
1005 1011
1006 1012 NOTE: diff may generate unexpected results for merges, as it will
1007 1013 default to comparing against the working directory's first parent
1008 1014 changeset if no revisions are specified.
1009 1015
1010 1016 When two revision arguments are given, then changes are shown
1011 1017 between those revisions. If only one revision is specified then
1012 1018 that revision is compared to the working directory, and, when no
1013 1019 revisions are specified, the working directory files are compared
1014 1020 to its parent.
1015 1021
1016 1022 Without the -a option, diff will avoid generating diffs of files
1017 1023 it detects as binary. With -a, diff will generate a diff anyway,
1018 1024 probably with undesirable results.
1019 1025 """
1020 1026 node1, node2 = cmdutil.revpair(repo, opts['rev'])
1021 1027
1022 1028 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1023 1029
1024 1030 patch.diff(repo, node1, node2, fns, match=matchfn,
1025 1031 opts=patch.diffopts(ui, opts))
1026 1032
1027 1033 def export(ui, repo, *changesets, **opts):
1028 1034 """dump the header and diffs for one or more changesets
1029 1035
1030 1036 Print the changeset header and diffs for one or more revisions.
1031 1037
1032 1038 The information shown in the changeset header is: author,
1033 1039 changeset hash, parent(s) and commit comment.
1034 1040
1035 1041 NOTE: export may generate unexpected diff output for merge changesets,
1036 1042 as it will compare the merge changeset against its first parent only.
1037 1043
1038 1044 Output may be to a file, in which case the name of the file is
1039 1045 given using a format string. The formatting rules are as follows:
1040 1046
1041 1047 %% literal "%" character
1042 1048 %H changeset hash (40 bytes of hexadecimal)
1043 1049 %N number of patches being generated
1044 1050 %R changeset revision number
1045 1051 %b basename of the exporting repository
1046 1052 %h short-form changeset hash (12 bytes of hexadecimal)
1047 1053 %n zero-padded sequence number, starting at 1
1048 1054 %r zero-padded changeset revision number
1049 1055
1050 1056 Without the -a option, export will avoid generating diffs of files
1051 1057 it detects as binary. With -a, export will generate a diff anyway,
1052 1058 probably with undesirable results.
1053 1059
1054 1060 With the --switch-parent option, the diff will be against the second
1055 1061 parent. It can be useful to review a merge.
1056 1062 """
1057 1063 if not changesets:
1058 1064 raise util.Abort(_("export requires at least one changeset"))
1059 1065 revs = cmdutil.revrange(repo, changesets)
1060 1066 if len(revs) > 1:
1061 1067 ui.note(_('exporting patches:\n'))
1062 1068 else:
1063 1069 ui.note(_('exporting patch:\n'))
1064 1070 patch.export(repo, revs, template=opts['output'],
1065 1071 switch_parent=opts['switch_parent'],
1066 1072 opts=patch.diffopts(ui, opts))
1067 1073
1068 1074 def grep(ui, repo, pattern, *pats, **opts):
1069 1075 """search for a pattern in specified files and revisions
1070 1076
1071 1077 Search revisions of files for a regular expression.
1072 1078
1073 1079 This command behaves differently than Unix grep. It only accepts
1074 1080 Python/Perl regexps. It searches repository history, not the
1075 1081 working directory. It always prints the revision number in which
1076 1082 a match appears.
1077 1083
1078 1084 By default, grep only prints output for the first revision of a
1079 1085 file in which it finds a match. To get it to print every revision
1080 1086 that contains a change in match status ("-" for a match that
1081 1087 becomes a non-match, or "+" for a non-match that becomes a match),
1082 1088 use the --all flag.
1083 1089 """
1084 1090 reflags = 0
1085 1091 if opts['ignore_case']:
1086 1092 reflags |= re.I
1087 1093 regexp = re.compile(pattern, reflags)
1088 1094 sep, eol = ':', '\n'
1089 1095 if opts['print0']:
1090 1096 sep = eol = '\0'
1091 1097
1092 1098 fcache = {}
1093 1099 def getfile(fn):
1094 1100 if fn not in fcache:
1095 1101 fcache[fn] = repo.file(fn)
1096 1102 return fcache[fn]
1097 1103
1098 1104 def matchlines(body):
1099 1105 begin = 0
1100 1106 linenum = 0
1101 1107 while True:
1102 1108 match = regexp.search(body, begin)
1103 1109 if not match:
1104 1110 break
1105 1111 mstart, mend = match.span()
1106 1112 linenum += body.count('\n', begin, mstart) + 1
1107 1113 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1108 1114 lend = body.find('\n', mend)
1109 1115 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1110 1116 begin = lend + 1
1111 1117
1112 1118 class linestate(object):
1113 1119 def __init__(self, line, linenum, colstart, colend):
1114 1120 self.line = line
1115 1121 self.linenum = linenum
1116 1122 self.colstart = colstart
1117 1123 self.colend = colend
1118 1124
1119 1125 def __eq__(self, other):
1120 1126 return self.line == other.line
1121 1127
1122 1128 matches = {}
1123 1129 copies = {}
1124 1130 def grepbody(fn, rev, body):
1125 1131 matches[rev].setdefault(fn, [])
1126 1132 m = matches[rev][fn]
1127 1133 for lnum, cstart, cend, line in matchlines(body):
1128 1134 s = linestate(line, lnum, cstart, cend)
1129 1135 m.append(s)
1130 1136
1131 1137 def difflinestates(a, b):
1132 1138 sm = difflib.SequenceMatcher(None, a, b)
1133 1139 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1134 1140 if tag == 'insert':
1135 1141 for i in xrange(blo, bhi):
1136 1142 yield ('+', b[i])
1137 1143 elif tag == 'delete':
1138 1144 for i in xrange(alo, ahi):
1139 1145 yield ('-', a[i])
1140 1146 elif tag == 'replace':
1141 1147 for i in xrange(alo, ahi):
1142 1148 yield ('-', a[i])
1143 1149 for i in xrange(blo, bhi):
1144 1150 yield ('+', b[i])
1145 1151
1146 1152 prev = {}
1147 1153 def display(fn, rev, states, prevstates):
1148 1154 found = False
1149 1155 filerevmatches = {}
1150 1156 r = prev.get(fn, -1)
1151 1157 if opts['all']:
1152 1158 iter = difflinestates(states, prevstates)
1153 1159 else:
1154 1160 iter = [('', l) for l in prevstates]
1155 1161 for change, l in iter:
1156 1162 cols = [fn, str(r)]
1157 1163 if opts['line_number']:
1158 1164 cols.append(str(l.linenum))
1159 1165 if opts['all']:
1160 1166 cols.append(change)
1161 1167 if opts['user']:
1162 1168 cols.append(ui.shortuser(get(r)[1]))
1163 1169 if opts['files_with_matches']:
1164 1170 c = (fn, r)
1165 1171 if c in filerevmatches:
1166 1172 continue
1167 1173 filerevmatches[c] = 1
1168 1174 else:
1169 1175 cols.append(l.line)
1170 1176 ui.write(sep.join(cols), eol)
1171 1177 found = True
1172 1178 return found
1173 1179
1174 1180 fstate = {}
1175 1181 skip = {}
1176 1182 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1177 1183 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1178 1184 found = False
1179 1185 follow = opts.get('follow')
1180 1186 for st, rev, fns in changeiter:
1181 1187 if st == 'window':
1182 1188 matches.clear()
1183 1189 elif st == 'add':
1184 1190 mf = repo.changectx(rev).manifest()
1185 1191 matches[rev] = {}
1186 1192 for fn in fns:
1187 1193 if fn in skip:
1188 1194 continue
1189 1195 fstate.setdefault(fn, {})
1190 1196 try:
1191 1197 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1192 1198 if follow:
1193 1199 copied = getfile(fn).renamed(mf[fn])
1194 1200 if copied:
1195 1201 copies.setdefault(rev, {})[fn] = copied[0]
1196 1202 except KeyError:
1197 1203 pass
1198 1204 elif st == 'iter':
1199 1205 states = matches[rev].items()
1200 1206 states.sort()
1201 1207 for fn, m in states:
1202 1208 copy = copies.get(rev, {}).get(fn)
1203 1209 if fn in skip:
1204 1210 if copy:
1205 1211 skip[copy] = True
1206 1212 continue
1207 1213 if fn in prev or fstate[fn]:
1208 1214 r = display(fn, rev, m, fstate[fn])
1209 1215 found = found or r
1210 1216 if r and not opts['all']:
1211 1217 skip[fn] = True
1212 1218 if copy:
1213 1219 skip[copy] = True
1214 1220 fstate[fn] = m
1215 1221 if copy:
1216 1222 fstate[copy] = m
1217 1223 prev[fn] = rev
1218 1224
1219 1225 fstate = fstate.items()
1220 1226 fstate.sort()
1221 1227 for fn, state in fstate:
1222 1228 if fn in skip:
1223 1229 continue
1224 1230 if fn not in copies.get(prev[fn], {}):
1225 1231 found = display(fn, rev, {}, state) or found
1226 1232 return (not found and 1) or 0
1227 1233
1228 1234 def heads(ui, repo, *branchrevs, **opts):
1229 1235 """show current repository heads or show branch heads
1230 1236
1231 1237 With no arguments, show all repository head changesets.
1232 1238
1233 1239 If branch or revisions names are given this will show the heads of
1234 1240 the specified branches or the branches those revisions are tagged
1235 1241 with.
1236 1242
1237 1243 Repository "heads" are changesets that don't have child
1238 1244 changesets. They are where development generally takes place and
1239 1245 are the usual targets for update and merge operations.
1240 1246
1241 1247 Branch heads are changesets that have a given branch tag, but have
1242 1248 no child changesets with that tag. They are usually where
1243 1249 development on the given branch takes place.
1244 1250 """
1245 1251 if opts['rev']:
1246 1252 start = repo.lookup(opts['rev'])
1247 1253 else:
1248 1254 start = None
1249 1255 if not branchrevs:
1250 1256 # Assume we're looking repo-wide heads if no revs were specified.
1251 1257 heads = repo.heads(start)
1252 1258 else:
1253 1259 heads = []
1254 1260 visitedset = util.set()
1255 1261 for branchrev in branchrevs:
1256 1262 branch = repo.changectx(branchrev).branch()
1257 1263 if branch in visitedset:
1258 1264 continue
1259 1265 visitedset.add(branch)
1260 1266 bheads = repo.branchheads(branch, start)
1261 1267 if not bheads:
1262 1268 if branch != branchrev:
1263 1269 ui.warn(_("no changes on branch %s containing %s are "
1264 1270 "reachable from %s\n")
1265 1271 % (branch, branchrev, opts['rev']))
1266 1272 else:
1267 1273 ui.warn(_("no changes on branch %s are reachable from %s\n")
1268 1274 % (branch, opts['rev']))
1269 1275 heads.extend(bheads)
1270 1276 if not heads:
1271 1277 return 1
1272 1278 displayer = cmdutil.show_changeset(ui, repo, opts)
1273 1279 for n in heads:
1274 1280 displayer.show(changenode=n)
1275 1281
1276 1282 def help_(ui, name=None, with_version=False):
1277 1283 """show help for a command, extension, or list of commands
1278 1284
1279 1285 With no arguments, print a list of commands and short help.
1280 1286
1281 1287 Given a command name, print help for that command.
1282 1288
1283 1289 Given an extension name, print help for that extension, and the
1284 1290 commands it provides."""
1285 1291 option_lists = []
1286 1292
1287 1293 def addglobalopts(aliases):
1288 1294 if ui.verbose:
1289 1295 option_lists.append((_("global options:"), globalopts))
1290 1296 if name == 'shortlist':
1291 1297 option_lists.append((_('use "hg help" for the full list '
1292 1298 'of commands'), ()))
1293 1299 else:
1294 1300 if name == 'shortlist':
1295 1301 msg = _('use "hg help" for the full list of commands '
1296 1302 'or "hg -v" for details')
1297 1303 elif aliases:
1298 1304 msg = _('use "hg -v help%s" to show aliases and '
1299 1305 'global options') % (name and " " + name or "")
1300 1306 else:
1301 1307 msg = _('use "hg -v help %s" to show global options') % name
1302 1308 option_lists.append((msg, ()))
1303 1309
1304 1310 def helpcmd(name):
1305 1311 if with_version:
1306 1312 version_(ui)
1307 1313 ui.write('\n')
1308 1314 aliases, i = cmdutil.findcmd(ui, name)
1309 1315 # synopsis
1310 1316 ui.write("%s\n\n" % i[2])
1311 1317
1312 1318 # description
1313 1319 doc = i[0].__doc__
1314 1320 if not doc:
1315 1321 doc = _("(No help text available)")
1316 1322 if ui.quiet:
1317 1323 doc = doc.splitlines(0)[0]
1318 1324 ui.write("%s\n" % doc.rstrip())
1319 1325
1320 1326 if not ui.quiet:
1321 1327 # aliases
1322 1328 if len(aliases) > 1:
1323 1329 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1324 1330
1325 1331 # options
1326 1332 if i[1]:
1327 1333 option_lists.append((_("options:\n"), i[1]))
1328 1334
1329 1335 addglobalopts(False)
1330 1336
1331 1337 def helplist(select=None):
1332 1338 h = {}
1333 1339 cmds = {}
1334 1340 for c, e in table.items():
1335 1341 f = c.split("|", 1)[0]
1336 1342 if select and not select(f):
1337 1343 continue
1338 1344 if name == "shortlist" and not f.startswith("^"):
1339 1345 continue
1340 1346 f = f.lstrip("^")
1341 1347 if not ui.debugflag and f.startswith("debug"):
1342 1348 continue
1343 1349 doc = e[0].__doc__
1344 1350 if not doc:
1345 1351 doc = _("(No help text available)")
1346 1352 h[f] = doc.splitlines(0)[0].rstrip()
1347 1353 cmds[f] = c.lstrip("^")
1348 1354
1349 1355 fns = h.keys()
1350 1356 fns.sort()
1351 1357 m = max(map(len, fns))
1352 1358 for f in fns:
1353 1359 if ui.verbose:
1354 1360 commands = cmds[f].replace("|",", ")
1355 1361 ui.write(" %s:\n %s\n"%(commands, h[f]))
1356 1362 else:
1357 1363 ui.write(' %-*s %s\n' % (m, f, h[f]))
1358 1364
1359 1365 if not ui.quiet:
1360 1366 addglobalopts(True)
1361 1367
1362 1368 def helptopic(name):
1363 1369 v = None
1364 1370 for i in help.helptable:
1365 1371 l = i.split('|')
1366 1372 if name in l:
1367 1373 v = i
1368 1374 header = l[-1]
1369 1375 if not v:
1370 1376 raise cmdutil.UnknownCommand(name)
1371 1377
1372 1378 # description
1373 1379 doc = help.helptable[v]
1374 1380 if not doc:
1375 1381 doc = _("(No help text available)")
1376 1382 if callable(doc):
1377 1383 doc = doc()
1378 1384
1379 1385 ui.write("%s\n" % header)
1380 1386 ui.write("%s\n" % doc.rstrip())
1381 1387
1382 1388 def helpext(name):
1383 1389 try:
1384 1390 mod = extensions.find(name)
1385 1391 except KeyError:
1386 1392 raise cmdutil.UnknownCommand(name)
1387 1393
1388 1394 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1389 1395 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1390 1396 for d in doc[1:]:
1391 1397 ui.write(d, '\n')
1392 1398
1393 1399 ui.status('\n')
1394 1400
1395 1401 try:
1396 1402 ct = mod.cmdtable
1397 1403 except AttributeError:
1398 1404 ct = None
1399 1405 if not ct:
1400 1406 ui.status(_('no commands defined\n'))
1401 1407 return
1402 1408
1403 1409 ui.status(_('list of commands:\n\n'))
1404 1410 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1405 1411 helplist(modcmds.has_key)
1406 1412
1407 1413 if name and name != 'shortlist':
1408 1414 i = None
1409 1415 for f in (helpcmd, helptopic, helpext):
1410 1416 try:
1411 1417 f(name)
1412 1418 i = None
1413 1419 break
1414 1420 except cmdutil.UnknownCommand, inst:
1415 1421 i = inst
1416 1422 if i:
1417 1423 raise i
1418 1424
1419 1425 else:
1420 1426 # program name
1421 1427 if ui.verbose or with_version:
1422 1428 version_(ui)
1423 1429 else:
1424 1430 ui.status(_("Mercurial Distributed SCM\n"))
1425 1431 ui.status('\n')
1426 1432
1427 1433 # list of commands
1428 1434 if name == "shortlist":
1429 1435 ui.status(_('basic commands:\n\n'))
1430 1436 else:
1431 1437 ui.status(_('list of commands:\n\n'))
1432 1438
1433 1439 helplist()
1434 1440
1435 1441 # list all option lists
1436 1442 opt_output = []
1437 1443 for title, options in option_lists:
1438 1444 opt_output.append(("\n%s" % title, None))
1439 1445 for shortopt, longopt, default, desc in options:
1440 1446 if "DEPRECATED" in desc and not ui.verbose: continue
1441 1447 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1442 1448 longopt and " --%s" % longopt),
1443 1449 "%s%s" % (desc,
1444 1450 default
1445 1451 and _(" (default: %s)") % default
1446 1452 or "")))
1447 1453
1448 1454 if opt_output:
1449 1455 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1450 1456 for first, second in opt_output:
1451 1457 if second:
1452 1458 ui.write(" %-*s %s\n" % (opts_len, first, second))
1453 1459 else:
1454 1460 ui.write("%s\n" % first)
1455 1461
1456 1462 def identify(ui, repo, source=None,
1457 1463 rev=None, num=None, id=None, branch=None, tags=None):
1458 1464 """identify the working copy or specified revision
1459 1465
1460 1466 With no revision, print a summary of the current state of the repo.
1461 1467
1462 1468 With a path, do a lookup in another repository.
1463 1469
1464 1470 This summary identifies the repository state using one or two parent
1465 1471 hash identifiers, followed by a "+" if there are uncommitted changes
1466 1472 in the working directory, a list of tags for this revision and a branch
1467 1473 name for non-default branches.
1468 1474 """
1469 1475
1470 1476 hexfunc = ui.debugflag and hex or short
1471 1477 default = not (num or id or branch or tags)
1472 1478 output = []
1473 1479
1474 1480 if source:
1475 1481 source, revs = cmdutil.parseurl(ui.expandpath(source), [])
1476 1482 srepo = hg.repository(ui, source)
1477 1483 if not rev and revs:
1478 1484 rev = revs[0]
1479 1485 if not rev:
1480 1486 rev = "tip"
1481 1487 if num or branch or tags:
1482 1488 raise util.Abort(
1483 1489 "can't query remote revision number, branch, or tags")
1484 1490 output = [hexfunc(srepo.lookup(rev))]
1485 1491 elif not rev:
1486 1492 ctx = repo.workingctx()
1487 1493 parents = ctx.parents()
1488 1494 changed = False
1489 1495 if default or id or num:
1490 1496 changed = ctx.files() + ctx.deleted()
1491 1497 if default or id:
1492 1498 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1493 1499 (changed) and "+" or "")]
1494 1500 if num:
1495 1501 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1496 1502 (changed) and "+" or ""))
1497 1503 else:
1498 1504 ctx = repo.changectx(rev)
1499 1505 if default or id:
1500 1506 output = [hexfunc(ctx.node())]
1501 1507 if num:
1502 1508 output.append(str(ctx.rev()))
1503 1509
1504 1510 if not source and default and not ui.quiet:
1505 1511 b = util.tolocal(ctx.branch())
1506 1512 if b != 'default':
1507 1513 output.append("(%s)" % b)
1508 1514
1509 1515 # multiple tags for a single parent separated by '/'
1510 1516 t = "/".join(ctx.tags())
1511 1517 if t:
1512 1518 output.append(t)
1513 1519
1514 1520 if branch:
1515 1521 output.append(util.tolocal(ctx.branch()))
1516 1522
1517 1523 if tags:
1518 1524 output.extend(ctx.tags())
1519 1525
1520 1526 ui.write("%s\n" % ' '.join(output))
1521 1527
1522 1528 def import_(ui, repo, patch1, *patches, **opts):
1523 1529 """import an ordered set of patches
1524 1530
1525 1531 Import a list of patches and commit them individually.
1526 1532
1527 1533 If there are outstanding changes in the working directory, import
1528 1534 will abort unless given the -f flag.
1529 1535
1530 1536 You can import a patch straight from a mail message. Even patches
1531 1537 as attachments work (body part must be type text/plain or
1532 1538 text/x-patch to be used). From and Subject headers of email
1533 1539 message are used as default committer and commit message. All
1534 1540 text/plain body parts before first diff are added to commit
1535 1541 message.
1536 1542
1537 1543 If the imported patch was generated by hg export, user and description
1538 1544 from patch override values from message headers and body. Values
1539 1545 given on command line with -m and -u override these.
1540 1546
1541 1547 If --exact is specified, import will set the working directory
1542 1548 to the parent of each patch before applying it, and will abort
1543 1549 if the resulting changeset has a different ID than the one
1544 1550 recorded in the patch. This may happen due to character set
1545 1551 problems or other deficiencies in the text patch format.
1546 1552
1547 1553 To read a patch from standard input, use patch name "-".
1548 1554 """
1549 1555 patches = (patch1,) + patches
1550 1556
1551 1557 if opts.get('exact') or not opts['force']:
1552 1558 cmdutil.bail_if_changed(repo)
1553 1559
1554 1560 d = opts["base"]
1555 1561 strip = opts["strip"]
1556 1562
1557 1563 wlock = repo.wlock()
1558 1564 lock = repo.lock()
1559 1565
1560 1566 for p in patches:
1561 1567 pf = os.path.join(d, p)
1562 1568
1563 1569 if pf == '-':
1564 1570 ui.status(_("applying patch from stdin\n"))
1565 1571 tmpname, message, user, date, branch, nodeid, p1, p2 = patch.extract(ui, sys.stdin)
1566 1572 else:
1567 1573 ui.status(_("applying %s\n") % p)
1568 1574 tmpname, message, user, date, branch, nodeid, p1, p2 = patch.extract(ui, file(pf, 'rb'))
1569 1575
1570 1576 if tmpname is None:
1571 1577 raise util.Abort(_('no diffs found'))
1572 1578
1573 1579 try:
1574 1580 cmdline_message = cmdutil.logmessage(opts)
1575 1581 if cmdline_message:
1576 1582 # pickup the cmdline msg
1577 1583 message = cmdline_message
1578 1584 elif message:
1579 1585 # pickup the patch msg
1580 1586 message = message.strip()
1581 1587 else:
1582 1588 # launch the editor
1583 1589 message = None
1584 1590 ui.debug(_('message:\n%s\n') % message)
1585 1591
1586 1592 wp = repo.workingctx().parents()
1587 1593 if opts.get('exact'):
1588 1594 if not nodeid or not p1:
1589 1595 raise util.Abort(_('not a mercurial patch'))
1590 1596 p1 = repo.lookup(p1)
1591 1597 p2 = repo.lookup(p2 or hex(nullid))
1592 1598
1593 1599 if p1 != wp[0].node():
1594 1600 hg.clean(repo, p1, wlock=wlock)
1595 1601 repo.dirstate.setparents(p1, p2)
1596 1602 elif p2:
1597 1603 try:
1598 1604 p1 = repo.lookup(p1)
1599 1605 p2 = repo.lookup(p2)
1600 1606 if p1 == wp[0].node():
1601 1607 repo.dirstate.setparents(p1, p2)
1602 1608 except hg.RepoError:
1603 1609 pass
1604 1610 if opts.get('exact') or opts.get('import_branch'):
1605 1611 repo.dirstate.setbranch(branch or 'default')
1606 1612
1607 1613 files = {}
1608 1614 try:
1609 1615 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1610 1616 files=files)
1611 1617 finally:
1612 1618 files = patch.updatedir(ui, repo, files, wlock=wlock)
1613 1619 n = repo.commit(files, message, user, date, wlock=wlock, lock=lock)
1614 1620 if opts.get('exact'):
1615 1621 if hex(n) != nodeid:
1616 1622 repo.rollback(wlock=wlock, lock=lock)
1617 1623 raise util.Abort(_('patch is damaged or loses information'))
1618 1624 finally:
1619 1625 os.unlink(tmpname)
1620 1626
1621 1627 def incoming(ui, repo, source="default", **opts):
1622 1628 """show new changesets found in source
1623 1629
1624 1630 Show new changesets found in the specified path/URL or the default
1625 1631 pull location. These are the changesets that would be pulled if a pull
1626 1632 was requested.
1627 1633
1628 1634 For remote repository, using --bundle avoids downloading the changesets
1629 1635 twice if the incoming is followed by a pull.
1630 1636
1631 1637 See pull for valid source format details.
1632 1638 """
1633 1639 source, revs = cmdutil.parseurl(ui.expandpath(source), opts['rev'])
1634 1640 cmdutil.setremoteconfig(ui, opts)
1635 1641
1636 1642 other = hg.repository(ui, source)
1637 1643 ui.status(_('comparing with %s\n') % source)
1638 1644 if revs:
1639 1645 if 'lookup' in other.capabilities:
1640 1646 revs = [other.lookup(rev) for rev in revs]
1641 1647 else:
1642 1648 error = _("Other repository doesn't support revision lookup, so a rev cannot be specified.")
1643 1649 raise util.Abort(error)
1644 1650 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1645 1651 if not incoming:
1646 1652 try:
1647 1653 os.unlink(opts["bundle"])
1648 1654 except:
1649 1655 pass
1650 1656 ui.status(_("no changes found\n"))
1651 1657 return 1
1652 1658
1653 1659 cleanup = None
1654 1660 try:
1655 1661 fname = opts["bundle"]
1656 1662 if fname or not other.local():
1657 1663 # create a bundle (uncompressed if other repo is not local)
1658 1664 if revs is None:
1659 1665 cg = other.changegroup(incoming, "incoming")
1660 1666 else:
1661 1667 if 'changegroupsubset' not in other.capabilities:
1662 1668 raise util.Abort(_("Partial incoming cannot be done because other repository doesn't support changegroupsubset."))
1663 1669 cg = other.changegroupsubset(incoming, revs, 'incoming')
1664 1670 bundletype = other.local() and "HG10BZ" or "HG10UN"
1665 1671 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1666 1672 # keep written bundle?
1667 1673 if opts["bundle"]:
1668 1674 cleanup = None
1669 1675 if not other.local():
1670 1676 # use the created uncompressed bundlerepo
1671 1677 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1672 1678
1673 1679 o = other.changelog.nodesbetween(incoming, revs)[0]
1674 1680 if opts['newest_first']:
1675 1681 o.reverse()
1676 1682 displayer = cmdutil.show_changeset(ui, other, opts)
1677 1683 for n in o:
1678 1684 parents = [p for p in other.changelog.parents(n) if p != nullid]
1679 1685 if opts['no_merges'] and len(parents) == 2:
1680 1686 continue
1681 1687 displayer.show(changenode=n)
1682 1688 finally:
1683 1689 if hasattr(other, 'close'):
1684 1690 other.close()
1685 1691 if cleanup:
1686 1692 os.unlink(cleanup)
1687 1693
1688 1694 def init(ui, dest=".", **opts):
1689 1695 """create a new repository in the given directory
1690 1696
1691 1697 Initialize a new repository in the given directory. If the given
1692 1698 directory does not exist, it is created.
1693 1699
1694 1700 If no directory is given, the current directory is used.
1695 1701
1696 1702 It is possible to specify an ssh:// URL as the destination.
1697 1703 Look at the help text for the pull command for important details
1698 1704 about ssh:// URLs.
1699 1705 """
1700 1706 cmdutil.setremoteconfig(ui, opts)
1701 1707 hg.repository(ui, dest, create=1)
1702 1708
1703 1709 def locate(ui, repo, *pats, **opts):
1704 1710 """locate files matching specific patterns
1705 1711
1706 1712 Print all files under Mercurial control whose names match the
1707 1713 given patterns.
1708 1714
1709 1715 This command searches the entire repository by default. To search
1710 1716 just the current directory and its subdirectories, use
1711 1717 "--include .".
1712 1718
1713 1719 If no patterns are given to match, this command prints all file
1714 1720 names.
1715 1721
1716 1722 If you want to feed the output of this command into the "xargs"
1717 1723 command, use the "-0" option to both this command and "xargs".
1718 1724 This will avoid the problem of "xargs" treating single filenames
1719 1725 that contain white space as multiple filenames.
1720 1726 """
1721 1727 end = opts['print0'] and '\0' or '\n'
1722 1728 rev = opts['rev']
1723 1729 if rev:
1724 1730 node = repo.lookup(rev)
1725 1731 else:
1726 1732 node = None
1727 1733
1728 1734 ret = 1
1729 1735 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1730 1736 badmatch=util.always,
1731 1737 default='relglob'):
1732 1738 if src == 'b':
1733 1739 continue
1734 1740 if not node and repo.dirstate.state(abs) == '?':
1735 1741 continue
1736 1742 if opts['fullpath']:
1737 1743 ui.write(os.path.join(repo.root, abs), end)
1738 1744 else:
1739 1745 ui.write(((pats and rel) or abs), end)
1740 1746 ret = 0
1741 1747
1742 1748 return ret
1743 1749
1744 1750 def log(ui, repo, *pats, **opts):
1745 1751 """show revision history of entire repository or files
1746 1752
1747 1753 Print the revision history of the specified files or the entire
1748 1754 project.
1749 1755
1750 1756 File history is shown without following rename or copy history of
1751 1757 files. Use -f/--follow with a file name to follow history across
1752 1758 renames and copies. --follow without a file name will only show
1753 1759 ancestors or descendants of the starting revision. --follow-first
1754 1760 only follows the first parent of merge revisions.
1755 1761
1756 1762 If no revision range is specified, the default is tip:0 unless
1757 1763 --follow is set, in which case the working directory parent is
1758 1764 used as the starting revision.
1759 1765
1760 1766 By default this command outputs: changeset id and hash, tags,
1761 1767 non-trivial parents, user, date and time, and a summary for each
1762 1768 commit. When the -v/--verbose switch is used, the list of changed
1763 1769 files and full commit message is shown.
1764 1770
1765 1771 NOTE: log -p may generate unexpected diff output for merge
1766 1772 changesets, as it will compare the merge changeset against its
1767 1773 first parent only. Also, the files: list will only reflect files
1768 1774 that are different from BOTH parents.
1769 1775
1770 1776 """
1771 1777
1772 1778 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1773 1779 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1774 1780
1775 1781 if opts['limit']:
1776 1782 try:
1777 1783 limit = int(opts['limit'])
1778 1784 except ValueError:
1779 1785 raise util.Abort(_('limit must be a positive integer'))
1780 1786 if limit <= 0: raise util.Abort(_('limit must be positive'))
1781 1787 else:
1782 1788 limit = sys.maxint
1783 1789 count = 0
1784 1790
1785 1791 if opts['copies'] and opts['rev']:
1786 1792 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1787 1793 else:
1788 1794 endrev = repo.changelog.count()
1789 1795 rcache = {}
1790 1796 ncache = {}
1791 1797 dcache = []
1792 1798 def getrenamed(fn, rev, man):
1793 1799 '''looks up all renames for a file (up to endrev) the first
1794 1800 time the file is given. It indexes on the changerev and only
1795 1801 parses the manifest if linkrev != changerev.
1796 1802 Returns rename info for fn at changerev rev.'''
1797 1803 if fn not in rcache:
1798 1804 rcache[fn] = {}
1799 1805 ncache[fn] = {}
1800 1806 fl = repo.file(fn)
1801 1807 for i in xrange(fl.count()):
1802 1808 node = fl.node(i)
1803 1809 lr = fl.linkrev(node)
1804 1810 renamed = fl.renamed(node)
1805 1811 rcache[fn][lr] = renamed
1806 1812 if renamed:
1807 1813 ncache[fn][node] = renamed
1808 1814 if lr >= endrev:
1809 1815 break
1810 1816 if rev in rcache[fn]:
1811 1817 return rcache[fn][rev]
1812 1818 mr = repo.manifest.rev(man)
1813 1819 if repo.manifest.parentrevs(mr) != (mr - 1, nullrev):
1814 1820 return ncache[fn].get(repo.manifest.find(man, fn)[0])
1815 1821 if not dcache or dcache[0] != man:
1816 1822 dcache[:] = [man, repo.manifest.readdelta(man)]
1817 1823 if fn in dcache[1]:
1818 1824 return ncache[fn].get(dcache[1][fn])
1819 1825 return None
1820 1826
1821 1827 df = False
1822 1828 if opts["date"]:
1823 1829 df = util.matchdate(opts["date"])
1824 1830
1825 1831 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1826 1832 for st, rev, fns in changeiter:
1827 1833 if st == 'add':
1828 1834 changenode = repo.changelog.node(rev)
1829 1835 parents = [p for p in repo.changelog.parentrevs(rev)
1830 1836 if p != nullrev]
1831 1837 if opts['no_merges'] and len(parents) == 2:
1832 1838 continue
1833 1839 if opts['only_merges'] and len(parents) != 2:
1834 1840 continue
1835 1841
1836 1842 if df:
1837 1843 changes = get(rev)
1838 1844 if not df(changes[2][0]):
1839 1845 continue
1840 1846
1841 1847 if opts['keyword']:
1842 1848 changes = get(rev)
1843 1849 miss = 0
1844 1850 for k in [kw.lower() for kw in opts['keyword']]:
1845 1851 if not (k in changes[1].lower() or
1846 1852 k in changes[4].lower() or
1847 1853 k in " ".join(changes[3]).lower()):
1848 1854 miss = 1
1849 1855 break
1850 1856 if miss:
1851 1857 continue
1852 1858
1853 1859 copies = []
1854 1860 if opts.get('copies') and rev:
1855 1861 mf = get(rev)[0]
1856 1862 for fn in get(rev)[3]:
1857 1863 rename = getrenamed(fn, rev, mf)
1858 1864 if rename:
1859 1865 copies.append((fn, rename[0]))
1860 1866 displayer.show(rev, changenode, copies=copies)
1861 1867 elif st == 'iter':
1862 1868 if count == limit: break
1863 1869 if displayer.flush(rev):
1864 1870 count += 1
1865 1871
1866 1872 def manifest(ui, repo, rev=None):
1867 1873 """output the current or given revision of the project manifest
1868 1874
1869 1875 Print a list of version controlled files for the given revision.
1870 1876 If no revision is given, the parent of the working directory is used,
1871 1877 or tip if no revision is checked out.
1872 1878
1873 1879 The manifest is the list of files being version controlled. If no revision
1874 1880 is given then the first parent of the working directory is used.
1875 1881
1876 1882 With -v flag, print file permissions. With --debug flag, print
1877 1883 file revision hashes.
1878 1884 """
1879 1885
1880 1886 m = repo.changectx(rev).manifest()
1881 1887 files = m.keys()
1882 1888 files.sort()
1883 1889
1884 1890 for f in files:
1885 1891 if ui.debugflag:
1886 1892 ui.write("%40s " % hex(m[f]))
1887 1893 if ui.verbose:
1888 1894 ui.write("%3s " % (m.execf(f) and "755" or "644"))
1889 1895 ui.write("%s\n" % f)
1890 1896
1891 1897 def merge(ui, repo, node=None, force=None, rev=None):
1892 1898 """merge working directory with another revision
1893 1899
1894 1900 Merge the contents of the current working directory and the
1895 1901 requested revision. Files that changed between either parent are
1896 1902 marked as changed for the next commit and a commit must be
1897 1903 performed before any further updates are allowed.
1898 1904
1899 1905 If no revision is specified, the working directory's parent is a
1900 1906 head revision, and the repository contains exactly one other head,
1901 1907 the other head is merged with by default. Otherwise, an explicit
1902 1908 revision to merge with must be provided.
1903 1909 """
1904 1910
1905 1911 if rev and node:
1906 1912 raise util.Abort(_("please specify just one revision"))
1907 1913
1908 1914 if not node:
1909 1915 node = rev
1910 1916
1911 1917 if not node:
1912 1918 heads = repo.heads()
1913 1919 if len(heads) > 2:
1914 1920 raise util.Abort(_('repo has %d heads - '
1915 1921 'please merge with an explicit rev') %
1916 1922 len(heads))
1917 1923 if len(heads) == 1:
1918 1924 raise util.Abort(_('there is nothing to merge - '
1919 1925 'use "hg update" instead'))
1920 1926 parent = repo.dirstate.parents()[0]
1921 1927 if parent not in heads:
1922 1928 raise util.Abort(_('working dir not at a head rev - '
1923 1929 'use "hg update" or merge with an explicit rev'))
1924 1930 node = parent == heads[0] and heads[-1] or heads[0]
1925 1931 return hg.merge(repo, node, force=force)
1926 1932
1927 1933 def outgoing(ui, repo, dest=None, **opts):
1928 1934 """show changesets not found in destination
1929 1935
1930 1936 Show changesets not found in the specified destination repository or
1931 1937 the default push location. These are the changesets that would be pushed
1932 1938 if a push was requested.
1933 1939
1934 1940 See pull for valid destination format details.
1935 1941 """
1936 1942 dest, revs = cmdutil.parseurl(
1937 1943 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1938 1944 cmdutil.setremoteconfig(ui, opts)
1939 1945 if revs:
1940 1946 revs = [repo.lookup(rev) for rev in revs]
1941 1947
1942 1948 other = hg.repository(ui, dest)
1943 1949 ui.status(_('comparing with %s\n') % dest)
1944 1950 o = repo.findoutgoing(other, force=opts['force'])
1945 1951 if not o:
1946 1952 ui.status(_("no changes found\n"))
1947 1953 return 1
1948 1954 o = repo.changelog.nodesbetween(o, revs)[0]
1949 1955 if opts['newest_first']:
1950 1956 o.reverse()
1951 1957 displayer = cmdutil.show_changeset(ui, repo, opts)
1952 1958 for n in o:
1953 1959 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1954 1960 if opts['no_merges'] and len(parents) == 2:
1955 1961 continue
1956 1962 displayer.show(changenode=n)
1957 1963
1958 1964 def parents(ui, repo, file_=None, **opts):
1959 1965 """show the parents of the working dir or revision
1960 1966
1961 1967 Print the working directory's parent revisions. If a
1962 1968 revision is given via --rev, the parent of that revision
1963 1969 will be printed. If a file argument is given, revision in
1964 1970 which the file was last changed (before the working directory
1965 1971 revision or the argument to --rev if given) is printed.
1966 1972 """
1967 1973 rev = opts.get('rev')
1968 1974 if file_:
1969 1975 ctx = repo.filectx(file_, changeid=rev)
1970 1976 elif rev:
1971 1977 ctx = repo.changectx(rev)
1972 1978 else:
1973 1979 ctx = repo.workingctx()
1974 1980 p = [cp.node() for cp in ctx.parents()]
1975 1981
1976 1982 displayer = cmdutil.show_changeset(ui, repo, opts)
1977 1983 for n in p:
1978 1984 if n != nullid:
1979 1985 displayer.show(changenode=n)
1980 1986
1981 1987 def paths(ui, repo, search=None):
1982 1988 """show definition of symbolic path names
1983 1989
1984 1990 Show definition of symbolic path name NAME. If no name is given, show
1985 1991 definition of available names.
1986 1992
1987 1993 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1988 1994 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1989 1995 """
1990 1996 if search:
1991 1997 for name, path in ui.configitems("paths"):
1992 1998 if name == search:
1993 1999 ui.write("%s\n" % path)
1994 2000 return
1995 2001 ui.warn(_("not found!\n"))
1996 2002 return 1
1997 2003 else:
1998 2004 for name, path in ui.configitems("paths"):
1999 2005 ui.write("%s = %s\n" % (name, path))
2000 2006
2001 2007 def postincoming(ui, repo, modheads, optupdate):
2002 2008 if modheads == 0:
2003 2009 return
2004 2010 if optupdate:
2005 2011 if modheads == 1:
2006 2012 return hg.update(repo, repo.changelog.tip()) # update
2007 2013 else:
2008 2014 ui.status(_("not updating, since new heads added\n"))
2009 2015 if modheads > 1:
2010 2016 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2011 2017 else:
2012 2018 ui.status(_("(run 'hg update' to get a working copy)\n"))
2013 2019
2014 2020 def pull(ui, repo, source="default", **opts):
2015 2021 """pull changes from the specified source
2016 2022
2017 2023 Pull changes from a remote repository to a local one.
2018 2024
2019 2025 This finds all changes from the repository at the specified path
2020 2026 or URL and adds them to the local repository. By default, this
2021 2027 does not update the copy of the project in the working directory.
2022 2028
2023 2029 Valid URLs are of the form:
2024 2030
2025 2031 local/filesystem/path (or file://local/filesystem/path)
2026 2032 http://[user@]host[:port]/[path]
2027 2033 https://[user@]host[:port]/[path]
2028 2034 ssh://[user@]host[:port]/[path]
2029 2035 static-http://host[:port]/[path]
2030 2036
2031 2037 Paths in the local filesystem can either point to Mercurial
2032 2038 repositories or to bundle files (as created by 'hg bundle' or
2033 2039 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
2034 2040 allows access to a Mercurial repository where you simply use a web
2035 2041 server to publish the .hg directory as static content.
2036 2042
2037 2043 An optional identifier after # indicates a particular branch, tag,
2038 2044 or changeset to pull.
2039 2045
2040 2046 Some notes about using SSH with Mercurial:
2041 2047 - SSH requires an accessible shell account on the destination machine
2042 2048 and a copy of hg in the remote path or specified with as remotecmd.
2043 2049 - path is relative to the remote user's home directory by default.
2044 2050 Use an extra slash at the start of a path to specify an absolute path:
2045 2051 ssh://example.com//tmp/repository
2046 2052 - Mercurial doesn't use its own compression via SSH; the right thing
2047 2053 to do is to configure it in your ~/.ssh/config, e.g.:
2048 2054 Host *.mylocalnetwork.example.com
2049 2055 Compression no
2050 2056 Host *
2051 2057 Compression yes
2052 2058 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2053 2059 with the --ssh command line option.
2054 2060 """
2055 2061 source, revs = cmdutil.parseurl(ui.expandpath(source), opts['rev'])
2056 2062 cmdutil.setremoteconfig(ui, opts)
2057 2063
2058 2064 other = hg.repository(ui, source)
2059 2065 ui.status(_('pulling from %s\n') % (source))
2060 2066 if revs:
2061 2067 if 'lookup' in other.capabilities:
2062 2068 revs = [other.lookup(rev) for rev in revs]
2063 2069 else:
2064 2070 error = _("Other repository doesn't support revision lookup, so a rev cannot be specified.")
2065 2071 raise util.Abort(error)
2066 2072
2067 2073 modheads = repo.pull(other, heads=revs, force=opts['force'])
2068 2074 return postincoming(ui, repo, modheads, opts['update'])
2069 2075
2070 2076 def push(ui, repo, dest=None, **opts):
2071 2077 """push changes to the specified destination
2072 2078
2073 2079 Push changes from the local repository to the given destination.
2074 2080
2075 2081 This is the symmetrical operation for pull. It helps to move
2076 2082 changes from the current repository to a different one. If the
2077 2083 destination is local this is identical to a pull in that directory
2078 2084 from the current one.
2079 2085
2080 2086 By default, push will refuse to run if it detects the result would
2081 2087 increase the number of remote heads. This generally indicates the
2082 2088 the client has forgotten to sync and merge before pushing.
2083 2089
2084 2090 Valid URLs are of the form:
2085 2091
2086 2092 local/filesystem/path (or file://local/filesystem/path)
2087 2093 ssh://[user@]host[:port]/[path]
2088 2094 http://[user@]host[:port]/[path]
2089 2095 https://[user@]host[:port]/[path]
2090 2096
2091 2097 An optional identifier after # indicates a particular branch, tag,
2092 2098 or changeset to push.
2093 2099
2094 2100 Look at the help text for the pull command for important details
2095 2101 about ssh:// URLs.
2096 2102
2097 2103 Pushing to http:// and https:// URLs is only possible, if this
2098 2104 feature is explicitly enabled on the remote Mercurial server.
2099 2105 """
2100 2106 dest, revs = cmdutil.parseurl(
2101 2107 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2102 2108 cmdutil.setremoteconfig(ui, opts)
2103 2109
2104 2110 other = hg.repository(ui, dest)
2105 2111 ui.status('pushing to %s\n' % (dest))
2106 2112 if revs:
2107 2113 revs = [repo.lookup(rev) for rev in revs]
2108 2114 r = repo.push(other, opts['force'], revs=revs)
2109 2115 return r == 0
2110 2116
2111 2117 def rawcommit(ui, repo, *pats, **opts):
2112 2118 """raw commit interface (DEPRECATED)
2113 2119
2114 2120 (DEPRECATED)
2115 2121 Lowlevel commit, for use in helper scripts.
2116 2122
2117 2123 This command is not intended to be used by normal users, as it is
2118 2124 primarily useful for importing from other SCMs.
2119 2125
2120 2126 This command is now deprecated and will be removed in a future
2121 2127 release, please use debugsetparents and commit instead.
2122 2128 """
2123 2129
2124 2130 ui.warn(_("(the rawcommit command is deprecated)\n"))
2125 2131
2126 2132 message = cmdutil.logmessage(opts)
2127 2133
2128 2134 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
2129 2135 if opts['files']:
2130 2136 files += open(opts['files']).read().splitlines()
2131 2137
2132 2138 parents = [repo.lookup(p) for p in opts['parent']]
2133 2139
2134 2140 try:
2135 2141 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2136 2142 except ValueError, inst:
2137 2143 raise util.Abort(str(inst))
2138 2144
2139 2145 def recover(ui, repo):
2140 2146 """roll back an interrupted transaction
2141 2147
2142 2148 Recover from an interrupted commit or pull.
2143 2149
2144 2150 This command tries to fix the repository status after an interrupted
2145 2151 operation. It should only be necessary when Mercurial suggests it.
2146 2152 """
2147 2153 if repo.recover():
2148 2154 return hg.verify(repo)
2149 2155 return 1
2150 2156
2151 2157 def remove(ui, repo, *pats, **opts):
2152 2158 """remove the specified files on the next commit
2153 2159
2154 2160 Schedule the indicated files for removal from the repository.
2155 2161
2156 2162 This only removes files from the current branch, not from the
2157 2163 entire project history. If the files still exist in the working
2158 2164 directory, they will be deleted from it. If invoked with --after,
2159 2165 files are marked as removed, but not actually unlinked unless --force
2160 2166 is also given. Without exact file names, --after will only mark
2161 2167 files as removed if they are no longer in the working directory.
2162 2168
2163 2169 This command schedules the files to be removed at the next commit.
2164 2170 To undo a remove before that, see hg revert.
2165 2171
2166 2172 Modified files and added files are not removed by default. To
2167 2173 remove them, use the -f/--force option.
2168 2174 """
2169 2175 names = []
2170 2176 if not opts['after'] and not pats:
2171 2177 raise util.Abort(_('no files specified'))
2172 2178 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2173 2179 exact = dict.fromkeys(files)
2174 2180 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2175 2181 modified, added, removed, deleted, unknown = mardu
2176 2182 remove, forget = [], []
2177 2183 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2178 2184 reason = None
2179 2185 if abs in modified and not opts['force']:
2180 2186 reason = _('is modified (use -f to force removal)')
2181 2187 elif abs in added:
2182 2188 if opts['force']:
2183 2189 forget.append(abs)
2184 2190 continue
2185 2191 reason = _('has been marked for add (use -f to force removal)')
2186 2192 elif repo.dirstate.state(abs) == '?':
2187 2193 reason = _('is not managed')
2188 2194 elif opts['after'] and not exact and abs not in deleted:
2189 2195 continue
2190 2196 elif abs in removed:
2191 2197 continue
2192 2198 if reason:
2193 2199 if exact:
2194 2200 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2195 2201 else:
2196 2202 if ui.verbose or not exact:
2197 2203 ui.status(_('removing %s\n') % rel)
2198 2204 remove.append(abs)
2199 2205 repo.forget(forget)
2200 2206 repo.remove(remove, unlink=opts['force'] or not opts['after'])
2201 2207
2202 2208 def rename(ui, repo, *pats, **opts):
2203 2209 """rename files; equivalent of copy + remove
2204 2210
2205 2211 Mark dest as copies of sources; mark sources for deletion. If
2206 2212 dest is a directory, copies are put in that directory. If dest is
2207 2213 a file, there can only be one source.
2208 2214
2209 2215 By default, this command copies the contents of files as they
2210 2216 stand in the working directory. If invoked with --after, the
2211 2217 operation is recorded, but no copying is performed.
2212 2218
2213 2219 This command takes effect in the next commit. To undo a rename
2214 2220 before that, see hg revert.
2215 2221 """
2216 2222 wlock = repo.wlock(0)
2217 2223 errs, copied = docopy(ui, repo, pats, opts, wlock)
2218 2224 names = []
2219 2225 for abs, rel, exact in copied:
2220 2226 if ui.verbose or not exact:
2221 2227 ui.status(_('removing %s\n') % rel)
2222 2228 names.append(abs)
2223 2229 if not opts.get('dry_run'):
2224 2230 repo.remove(names, True, wlock=wlock)
2225 2231 return errs
2226 2232
2227 2233 def revert(ui, repo, *pats, **opts):
2228 2234 """revert files or dirs to their states as of some revision
2229 2235
2230 2236 With no revision specified, revert the named files or directories
2231 2237 to the contents they had in the parent of the working directory.
2232 2238 This restores the contents of the affected files to an unmodified
2233 2239 state and unschedules adds, removes, copies, and renames. If the
2234 2240 working directory has two parents, you must explicitly specify the
2235 2241 revision to revert to.
2236 2242
2237 2243 Modified files are saved with a .orig suffix before reverting.
2238 2244 To disable these backups, use --no-backup.
2239 2245
2240 2246 Using the -r option, revert the given files or directories to their
2241 2247 contents as of a specific revision. This can be helpful to "roll
2242 2248 back" some or all of a change that should not have been committed.
2243 2249
2244 2250 Revert modifies the working directory. It does not commit any
2245 2251 changes, or change the parent of the working directory. If you
2246 2252 revert to a revision other than the parent of the working
2247 2253 directory, the reverted files will thus appear modified
2248 2254 afterwards.
2249 2255
2250 2256 If a file has been deleted, it is recreated. If the executable
2251 2257 mode of a file was changed, it is reset.
2252 2258
2253 2259 If names are given, all files matching the names are reverted.
2254 2260
2255 2261 If no arguments are given, no files are reverted.
2256 2262 """
2257 2263
2258 2264 if opts["date"]:
2259 2265 if opts["rev"]:
2260 2266 raise util.Abort(_("you can't specify a revision and a date"))
2261 2267 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2262 2268
2263 2269 if not pats and not opts['all']:
2264 2270 raise util.Abort(_('no files or directories specified; '
2265 2271 'use --all to revert the whole repo'))
2266 2272
2267 2273 parent, p2 = repo.dirstate.parents()
2268 2274 if not opts['rev'] and p2 != nullid:
2269 2275 raise util.Abort(_('uncommitted merge - please provide a '
2270 2276 'specific revision'))
2271 2277 ctx = repo.changectx(opts['rev'])
2272 2278 node = ctx.node()
2273 2279 mf = ctx.manifest()
2274 2280 if node == parent:
2275 2281 pmf = mf
2276 2282 else:
2277 2283 pmf = None
2278 2284
2279 2285 wlock = repo.wlock()
2280 2286
2281 2287 # need all matching names in dirstate and manifest of target rev,
2282 2288 # so have to walk both. do not print errors if files exist in one
2283 2289 # but not other.
2284 2290
2285 2291 names = {}
2286 2292 target_only = {}
2287 2293
2288 2294 # walk dirstate.
2289 2295
2290 2296 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2291 2297 badmatch=mf.has_key):
2292 2298 names[abs] = (rel, exact)
2293 2299 if src == 'b':
2294 2300 target_only[abs] = True
2295 2301
2296 2302 # walk target manifest.
2297 2303
2298 2304 def badmatch(path):
2299 2305 if path in names:
2300 2306 return True
2301 2307 path_ = path + '/'
2302 2308 for f in names:
2303 2309 if f.startswith(path_):
2304 2310 return True
2305 2311 return False
2306 2312
2307 2313 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2308 2314 badmatch=badmatch):
2309 2315 if abs in names or src == 'b':
2310 2316 continue
2311 2317 names[abs] = (rel, exact)
2312 2318 target_only[abs] = True
2313 2319
2314 2320 changes = repo.status(match=names.has_key, wlock=wlock)[:5]
2315 2321 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2316 2322
2317 2323 revert = ([], _('reverting %s\n'))
2318 2324 add = ([], _('adding %s\n'))
2319 2325 remove = ([], _('removing %s\n'))
2320 2326 forget = ([], _('forgetting %s\n'))
2321 2327 undelete = ([], _('undeleting %s\n'))
2322 2328 update = {}
2323 2329
2324 2330 disptable = (
2325 2331 # dispatch table:
2326 2332 # file state
2327 2333 # action if in target manifest
2328 2334 # action if not in target manifest
2329 2335 # make backup if in target manifest
2330 2336 # make backup if not in target manifest
2331 2337 (modified, revert, remove, True, True),
2332 2338 (added, revert, forget, True, False),
2333 2339 (removed, undelete, None, False, False),
2334 2340 (deleted, revert, remove, False, False),
2335 2341 (unknown, add, None, True, False),
2336 2342 (target_only, add, None, False, False),
2337 2343 )
2338 2344
2339 2345 entries = names.items()
2340 2346 entries.sort()
2341 2347
2342 2348 for abs, (rel, exact) in entries:
2343 2349 mfentry = mf.get(abs)
2344 2350 target = repo.wjoin(abs)
2345 2351 def handle(xlist, dobackup):
2346 2352 xlist[0].append(abs)
2347 2353 update[abs] = 1
2348 2354 if dobackup and not opts['no_backup'] and util.lexists(target):
2349 2355 bakname = "%s.orig" % rel
2350 2356 ui.note(_('saving current version of %s as %s\n') %
2351 2357 (rel, bakname))
2352 2358 if not opts.get('dry_run'):
2353 2359 util.copyfile(target, bakname)
2354 2360 if ui.verbose or not exact:
2355 2361 ui.status(xlist[1] % rel)
2356 2362 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2357 2363 if abs not in table: continue
2358 2364 # file has changed in dirstate
2359 2365 if mfentry:
2360 2366 handle(hitlist, backuphit)
2361 2367 elif misslist is not None:
2362 2368 handle(misslist, backupmiss)
2363 2369 else:
2364 2370 if exact: ui.warn(_('file not managed: %s\n') % rel)
2365 2371 break
2366 2372 else:
2367 2373 # file has not changed in dirstate
2368 2374 if node == parent:
2369 2375 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2370 2376 continue
2371 2377 if pmf is None:
2372 2378 # only need parent manifest in this unlikely case,
2373 2379 # so do not read by default
2374 2380 pmf = repo.changectx(parent).manifest()
2375 2381 if abs in pmf:
2376 2382 if mfentry:
2377 2383 # if version of file is same in parent and target
2378 2384 # manifests, do nothing
2379 2385 if pmf[abs] != mfentry:
2380 2386 handle(revert, False)
2381 2387 else:
2382 2388 handle(remove, False)
2383 2389
2384 2390 if not opts.get('dry_run'):
2385 2391 repo.dirstate.forget(forget[0])
2386 2392 r = hg.revert(repo, node, update.has_key, wlock)
2387 2393 repo.dirstate.update(add[0], 'a')
2388 2394 repo.dirstate.update(undelete[0], 'n')
2389 2395 repo.dirstate.update(remove[0], 'r')
2390 2396 return r
2391 2397
2392 2398 def rollback(ui, repo):
2393 2399 """roll back the last transaction in this repository
2394 2400
2395 2401 Roll back the last transaction in this repository, restoring the
2396 2402 project to its state prior to the transaction.
2397 2403
2398 2404 Transactions are used to encapsulate the effects of all commands
2399 2405 that create new changesets or propagate existing changesets into a
2400 2406 repository. For example, the following commands are transactional,
2401 2407 and their effects can be rolled back:
2402 2408
2403 2409 commit
2404 2410 import
2405 2411 pull
2406 2412 push (with this repository as destination)
2407 2413 unbundle
2408 2414
2409 2415 This command should be used with care. There is only one level of
2410 2416 rollback, and there is no way to undo a rollback. It will also
2411 2417 restore the dirstate at the time of the last transaction, which
2412 2418 may lose subsequent dirstate changes.
2413 2419
2414 2420 This command is not intended for use on public repositories. Once
2415 2421 changes are visible for pull by other users, rolling a transaction
2416 2422 back locally is ineffective (someone else may already have pulled
2417 2423 the changes). Furthermore, a race is possible with readers of the
2418 2424 repository; for example an in-progress pull from the repository
2419 2425 may fail if a rollback is performed.
2420 2426 """
2421 2427 repo.rollback()
2422 2428
2423 2429 def root(ui, repo):
2424 2430 """print the root (top) of the current working dir
2425 2431
2426 2432 Print the root directory of the current repository.
2427 2433 """
2428 2434 ui.write(repo.root + "\n")
2429 2435
2430 2436 def serve(ui, repo, **opts):
2431 2437 """export the repository via HTTP
2432 2438
2433 2439 Start a local HTTP repository browser and pull server.
2434 2440
2435 2441 By default, the server logs accesses to stdout and errors to
2436 2442 stderr. Use the "-A" and "-E" options to log to files.
2437 2443 """
2438 2444
2439 2445 if opts["stdio"]:
2440 2446 if repo is None:
2441 2447 raise hg.RepoError(_("There is no Mercurial repository here"
2442 2448 " (.hg not found)"))
2443 2449 s = sshserver.sshserver(ui, repo)
2444 2450 s.serve_forever()
2445 2451
2446 2452 parentui = ui.parentui or ui
2447 2453 optlist = ("name templates style address port ipv6"
2448 2454 " accesslog errorlog webdir_conf")
2449 2455 for o in optlist.split():
2450 2456 if opts[o]:
2451 2457 parentui.setconfig("web", o, str(opts[o]))
2452 2458
2453 2459 if repo is None and not ui.config("web", "webdir_conf"):
2454 2460 raise hg.RepoError(_("There is no Mercurial repository here"
2455 2461 " (.hg not found)"))
2456 2462
2457 2463 class service:
2458 2464 def init(self):
2459 2465 util.set_signal_handler()
2460 2466 try:
2461 2467 self.httpd = hgweb.server.create_server(parentui, repo)
2462 2468 except socket.error, inst:
2463 2469 raise util.Abort(_('cannot start server: ') + inst.args[1])
2464 2470
2465 2471 if not ui.verbose: return
2466 2472
2467 2473 if self.httpd.port != 80:
2468 2474 ui.status(_('listening at http://%s:%d/\n') %
2469 2475 (self.httpd.addr, self.httpd.port))
2470 2476 else:
2471 2477 ui.status(_('listening at http://%s/\n') % self.httpd.addr)
2472 2478
2473 2479 def run(self):
2474 2480 self.httpd.serve_forever()
2475 2481
2476 2482 service = service()
2477 2483
2478 2484 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2479 2485
2480 2486 def status(ui, repo, *pats, **opts):
2481 2487 """show changed files in the working directory
2482 2488
2483 2489 Show status of files in the repository. If names are given, only
2484 2490 files that match are shown. Files that are clean or ignored, are
2485 2491 not listed unless -c (clean), -i (ignored) or -A is given.
2486 2492
2487 2493 NOTE: status may appear to disagree with diff if permissions have
2488 2494 changed or a merge has occurred. The standard diff format does not
2489 2495 report permission changes and diff only reports changes relative
2490 2496 to one merge parent.
2491 2497
2492 2498 If one revision is given, it is used as the base revision.
2493 2499 If two revisions are given, the difference between them is shown.
2494 2500
2495 2501 The codes used to show the status of files are:
2496 2502 M = modified
2497 2503 A = added
2498 2504 R = removed
2499 2505 C = clean
2500 2506 ! = deleted, but still tracked
2501 2507 ? = not tracked
2502 2508 I = ignored (not shown by default)
2503 2509 = the previous added file was copied from here
2504 2510 """
2505 2511
2506 2512 all = opts['all']
2507 2513 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2508 2514
2509 2515 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2510 2516 cwd = (pats and repo.getcwd()) or ''
2511 2517 modified, added, removed, deleted, unknown, ignored, clean = [
2512 2518 n for n in repo.status(node1=node1, node2=node2, files=files,
2513 2519 match=matchfn,
2514 2520 list_ignored=all or opts['ignored'],
2515 2521 list_clean=all or opts['clean'])]
2516 2522
2517 2523 changetypes = (('modified', 'M', modified),
2518 2524 ('added', 'A', added),
2519 2525 ('removed', 'R', removed),
2520 2526 ('deleted', '!', deleted),
2521 2527 ('unknown', '?', unknown),
2522 2528 ('ignored', 'I', ignored))
2523 2529
2524 2530 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2525 2531
2526 2532 end = opts['print0'] and '\0' or '\n'
2527 2533
2528 2534 for opt, char, changes in ([ct for ct in explicit_changetypes
2529 2535 if all or opts[ct[0]]]
2530 2536 or changetypes):
2531 2537 if opts['no_status']:
2532 2538 format = "%%s%s" % end
2533 2539 else:
2534 2540 format = "%s %%s%s" % (char, end)
2535 2541
2536 2542 for f in changes:
2537 2543 ui.write(format % repo.pathto(f, cwd))
2538 2544 if ((all or opts.get('copies')) and not opts.get('no_status')):
2539 2545 copied = repo.dirstate.copied(f)
2540 2546 if copied:
2541 2547 ui.write(' %s%s' % (repo.pathto(copied, cwd), end))
2542 2548
2543 2549 def tag(ui, repo, name, rev_=None, **opts):
2544 2550 """add a tag for the current or given revision
2545 2551
2546 2552 Name a particular revision using <name>.
2547 2553
2548 2554 Tags are used to name particular revisions of the repository and are
2549 2555 very useful to compare different revision, to go back to significant
2550 2556 earlier versions or to mark branch points as releases, etc.
2551 2557
2552 2558 If no revision is given, the parent of the working directory is used,
2553 2559 or tip if no revision is checked out.
2554 2560
2555 2561 To facilitate version control, distribution, and merging of tags,
2556 2562 they are stored as a file named ".hgtags" which is managed
2557 2563 similarly to other project files and can be hand-edited if
2558 2564 necessary. The file '.hg/localtags' is used for local tags (not
2559 2565 shared among repositories).
2560 2566 """
2561 2567 if name in ['tip', '.', 'null']:
2562 2568 raise util.Abort(_("the name '%s' is reserved") % name)
2563 2569 if rev_ is not None:
2564 2570 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2565 2571 "please use 'hg tag [-r REV] NAME' instead\n"))
2566 2572 if opts['rev']:
2567 2573 raise util.Abort(_("use only one form to specify the revision"))
2568 2574 if opts['rev'] and opts['remove']:
2569 2575 raise util.Abort(_("--rev and --remove are incompatible"))
2570 2576 if opts['rev']:
2571 2577 rev_ = opts['rev']
2572 2578 message = opts['message']
2573 2579 if opts['remove']:
2574 2580 if not name in repo.tags():
2575 2581 raise util.Abort(_('tag %s does not exist') % name)
2576 2582 rev_ = nullid
2577 2583 if not message:
2578 2584 message = _('Removed tag %s') % name
2579 2585 elif name in repo.tags() and not opts['force']:
2580 2586 raise util.Abort(_('a tag named %s already exists (use -f to force)')
2581 2587 % name)
2582 2588 if not rev_ and repo.dirstate.parents()[1] != nullid:
2583 2589 raise util.Abort(_('uncommitted merge - please provide a '
2584 2590 'specific revision'))
2585 2591 r = repo.changectx(rev_).node()
2586 2592
2587 2593 if not message:
2588 2594 message = _('Added tag %s for changeset %s') % (name, short(r))
2589 2595
2590 2596 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2591 2597
2592 2598 def tags(ui, repo):
2593 2599 """list repository tags
2594 2600
2595 2601 List the repository tags.
2596 2602
2597 2603 This lists both regular and local tags.
2598 2604 """
2599 2605
2600 2606 l = repo.tagslist()
2601 2607 l.reverse()
2602 2608 hexfunc = ui.debugflag and hex or short
2603 2609 for t, n in l:
2604 2610 try:
2605 2611 hn = hexfunc(n)
2606 2612 r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
2607 2613 except revlog.LookupError:
2608 2614 r = " ?:%s" % hn
2609 2615 if ui.quiet:
2610 2616 ui.write("%s\n" % t)
2611 2617 else:
2612 2618 spaces = " " * (30 - util.locallen(t))
2613 2619 ui.write("%s%s %s\n" % (t, spaces, r))
2614 2620
2615 2621 def tip(ui, repo, **opts):
2616 2622 """show the tip revision
2617 2623
2618 2624 Show the tip revision.
2619 2625 """
2620 2626 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2621 2627
2622 2628 def unbundle(ui, repo, fname1, *fnames, **opts):
2623 2629 """apply one or more changegroup files
2624 2630
2625 2631 Apply one or more compressed changegroup files generated by the
2626 2632 bundle command.
2627 2633 """
2628 2634 fnames = (fname1,) + fnames
2629 2635 result = None
2630 2636 for fname in fnames:
2631 2637 if os.path.exists(fname):
2632 2638 f = open(fname, "rb")
2633 2639 else:
2634 2640 f = urllib.urlopen(fname)
2635 2641 gen = changegroup.readbundle(f, fname)
2636 2642 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2637 2643
2638 2644 return postincoming(ui, repo, modheads, opts['update'])
2639 2645
2640 2646 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2641 2647 """update working directory
2642 2648
2643 2649 Update the working directory to the specified revision, or the
2644 2650 tip of the current branch if none is specified.
2645 2651
2646 2652 If there are no outstanding changes in the working directory and
2647 2653 there is a linear relationship between the current version and the
2648 2654 requested version, the result is the requested version.
2649 2655
2650 2656 To merge the working directory with another revision, use the
2651 2657 merge command.
2652 2658
2653 2659 By default, update will refuse to run if doing so would require
2654 2660 discarding local changes.
2655 2661 """
2656 2662 if rev and node:
2657 2663 raise util.Abort(_("please specify just one revision"))
2658 2664
2659 2665 if not rev:
2660 2666 rev = node
2661 2667
2662 2668 if date:
2663 2669 if rev:
2664 2670 raise util.Abort(_("you can't specify a revision and a date"))
2665 2671 rev = cmdutil.finddate(ui, repo, date)
2666 2672
2667 2673 if clean:
2668 2674 return hg.clean(repo, rev)
2669 2675 else:
2670 2676 return hg.update(repo, rev)
2671 2677
2672 2678 def verify(ui, repo):
2673 2679 """verify the integrity of the repository
2674 2680
2675 2681 Verify the integrity of the current repository.
2676 2682
2677 2683 This will perform an extensive check of the repository's
2678 2684 integrity, validating the hashes and checksums of each entry in
2679 2685 the changelog, manifest, and tracked files, as well as the
2680 2686 integrity of their crosslinks and indices.
2681 2687 """
2682 2688 return hg.verify(repo)
2683 2689
2684 2690 def version_(ui):
2685 2691 """output version and copyright information"""
2686 2692 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2687 2693 % version.get_version())
2688 2694 ui.status(_(
2689 2695 "\nCopyright (C) 2005-2007 Matt Mackall <mpm@selenic.com> and others\n"
2690 2696 "This is free software; see the source for copying conditions. "
2691 2697 "There is NO\nwarranty; "
2692 2698 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2693 2699 ))
2694 2700
2695 2701 # Command options and aliases are listed here, alphabetically
2696 2702
2697 2703 globalopts = [
2698 2704 ('R', 'repository', '',
2699 2705 _('repository root directory or symbolic path name')),
2700 2706 ('', 'cwd', '', _('change working directory')),
2701 2707 ('y', 'noninteractive', None,
2702 2708 _('do not prompt, assume \'yes\' for any required answers')),
2703 2709 ('q', 'quiet', None, _('suppress output')),
2704 2710 ('v', 'verbose', None, _('enable additional output')),
2705 2711 ('', 'config', [], _('set/override config option')),
2706 2712 ('', 'debug', None, _('enable debugging output')),
2707 2713 ('', 'debugger', None, _('start debugger')),
2708 2714 ('', 'encoding', util._encoding, _('set the charset encoding')),
2709 2715 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2710 2716 ('', 'lsprof', None, _('print improved command execution profile')),
2711 2717 ('', 'traceback', None, _('print traceback on exception')),
2712 2718 ('', 'time', None, _('time how long the command takes')),
2713 2719 ('', 'profile', None, _('print command execution profile')),
2714 2720 ('', 'version', None, _('output version information and exit')),
2715 2721 ('h', 'help', None, _('display help and exit')),
2716 2722 ]
2717 2723
2718 2724 dryrunopts = [('n', 'dry-run', None,
2719 2725 _('do not perform actions, just print output'))]
2720 2726
2721 2727 remoteopts = [
2722 2728 ('e', 'ssh', '', _('specify ssh command to use')),
2723 2729 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2724 2730 ]
2725 2731
2726 2732 walkopts = [
2727 2733 ('I', 'include', [], _('include names matching the given patterns')),
2728 2734 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2729 2735 ]
2730 2736
2731 2737 commitopts = [
2732 2738 ('m', 'message', '', _('use <text> as commit message')),
2733 2739 ('l', 'logfile', '', _('read commit message from <file>')),
2734 2740 ]
2735 2741
2736 2742 table = {
2737 2743 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2738 2744 "addremove":
2739 2745 (addremove,
2740 2746 [('s', 'similarity', '',
2741 2747 _('guess renamed files by similarity (0<=s<=100)')),
2742 2748 ] + walkopts + dryrunopts,
2743 2749 _('hg addremove [OPTION]... [FILE]...')),
2744 2750 "^annotate":
2745 2751 (annotate,
2746 2752 [('r', 'rev', '', _('annotate the specified revision')),
2747 2753 ('f', 'follow', None, _('follow file copies and renames')),
2748 2754 ('a', 'text', None, _('treat all files as text')),
2749 2755 ('u', 'user', None, _('list the author')),
2750 2756 ('d', 'date', None, _('list the date')),
2751 2757 ('n', 'number', None, _('list the revision number (default)')),
2752 2758 ('c', 'changeset', None, _('list the changeset')),
2753 2759 ] + walkopts,
2754 2760 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] FILE...')),
2755 2761 "archive":
2756 2762 (archive,
2757 2763 [('', 'no-decode', None, _('do not pass files through decoders')),
2758 2764 ('p', 'prefix', '', _('directory prefix for files in archive')),
2759 2765 ('r', 'rev', '', _('revision to distribute')),
2760 2766 ('t', 'type', '', _('type of distribution to create')),
2761 2767 ] + walkopts,
2762 2768 _('hg archive [OPTION]... DEST')),
2763 2769 "backout":
2764 2770 (backout,
2765 2771 [('', 'merge', None,
2766 2772 _('merge with old dirstate parent after backout')),
2767 2773 ('d', 'date', '', _('record datecode as commit date')),
2768 2774 ('', 'parent', '', _('parent to choose when backing out merge')),
2769 2775 ('u', 'user', '', _('record user as committer')),
2770 2776 ('r', 'rev', '', _('revision to backout')),
2771 2777 ] + walkopts + commitopts,
2772 2778 _('hg backout [OPTION]... [-r] REV')),
2773 2779 "branch":
2774 2780 (branch,
2775 2781 [('f', 'force', None,
2776 2782 _('set branch name even if it shadows an existing branch'))],
2777 2783 _('hg branch [NAME]')),
2778 2784 "branches":
2779 2785 (branches,
2780 2786 [('a', 'active', False,
2781 2787 _('show only branches that have unmerged heads'))],
2782 2788 _('hg branches [-a]')),
2783 2789 "bundle":
2784 2790 (bundle,
2785 2791 [('f', 'force', None,
2786 2792 _('run even when remote repository is unrelated')),
2787 2793 ('r', 'rev', [],
2788 2794 _('a changeset you would like to bundle')),
2789 2795 ('', 'base', [],
2790 2796 _('a base changeset to specify instead of a destination')),
2791 2797 ] + remoteopts,
2792 2798 _('hg bundle [-f] [-r REV]... [--base REV]... FILE [DEST]')),
2793 2799 "cat":
2794 2800 (cat,
2795 2801 [('o', 'output', '', _('print output to file with formatted name')),
2796 2802 ('r', 'rev', '', _('print the given revision')),
2797 2803 ] + walkopts,
2798 2804 _('hg cat [OPTION]... FILE...')),
2799 2805 "^clone":
2800 2806 (clone,
2801 2807 [('U', 'noupdate', None, _('do not update the new working directory')),
2802 2808 ('r', 'rev', [],
2803 2809 _('a changeset you would like to have after cloning')),
2804 2810 ('', 'pull', None, _('use pull protocol to copy metadata')),
2805 2811 ('', 'uncompressed', None,
2806 2812 _('use uncompressed transfer (fast over LAN)')),
2807 2813 ] + remoteopts,
2808 2814 _('hg clone [OPTION]... SOURCE [DEST]')),
2809 2815 "^commit|ci":
2810 2816 (commit,
2811 2817 [('A', 'addremove', None,
2812 2818 _('mark new/missing files as added/removed before committing')),
2813 2819 ('d', 'date', '', _('record datecode as commit date')),
2814 2820 ('u', 'user', '', _('record user as commiter')),
2815 2821 ] + walkopts + commitopts,
2816 2822 _('hg commit [OPTION]... [FILE]...')),
2817 2823 "copy|cp":
2818 2824 (copy,
2819 2825 [('A', 'after', None, _('record a copy that has already occurred')),
2820 2826 ('f', 'force', None,
2821 2827 _('forcibly copy over an existing managed file')),
2822 2828 ] + walkopts + dryrunopts,
2823 2829 _('hg copy [OPTION]... [SOURCE]... DEST')),
2824 2830 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2825 2831 "debugcomplete":
2826 2832 (debugcomplete,
2827 2833 [('o', 'options', None, _('show the command options'))],
2828 2834 _('debugcomplete [-o] CMD')),
2829 2835 "debuginstall": (debuginstall, [], _('debuginstall')),
2830 2836 "debugrebuildstate":
2831 2837 (debugrebuildstate,
2832 2838 [('r', 'rev', '', _('revision to rebuild to'))],
2833 2839 _('debugrebuildstate [-r REV] [REV]')),
2834 2840 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2835 2841 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2836 2842 "debugstate": (debugstate, [], _('debugstate')),
2837 2843 "debugdate":
2838 2844 (debugdate,
2839 2845 [('e', 'extended', None, _('try extended date formats'))],
2840 2846 _('debugdate [-e] DATE [RANGE]')),
2841 2847 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2842 2848 "debugindex": (debugindex, [], _('debugindex FILE')),
2843 2849 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2844 2850 "debugrename":
2845 2851 (debugrename,
2846 2852 [('r', 'rev', '', _('revision to debug'))],
2847 2853 _('debugrename [-r REV] FILE')),
2848 2854 "debugwalk": (debugwalk, walkopts, _('debugwalk [OPTION]... [FILE]...')),
2849 2855 "^diff":
2850 2856 (diff,
2851 2857 [('r', 'rev', [], _('revision')),
2852 2858 ('a', 'text', None, _('treat all files as text')),
2853 2859 ('p', 'show-function', None,
2854 2860 _('show which function each change is in')),
2855 2861 ('g', 'git', None, _('use git extended diff format')),
2856 2862 ('', 'nodates', None, _("don't include dates in diff headers")),
2857 2863 ('w', 'ignore-all-space', None,
2858 2864 _('ignore white space when comparing lines')),
2859 2865 ('b', 'ignore-space-change', None,
2860 2866 _('ignore changes in the amount of white space')),
2861 2867 ('B', 'ignore-blank-lines', None,
2862 2868 _('ignore changes whose lines are all blank')),
2863 2869 ] + walkopts,
2864 2870 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
2865 2871 "^export":
2866 2872 (export,
2867 2873 [('o', 'output', '', _('print output to file with formatted name')),
2868 2874 ('a', 'text', None, _('treat all files as text')),
2869 2875 ('g', 'git', None, _('use git extended diff format')),
2870 2876 ('', 'nodates', None, _("don't include dates in diff headers")),
2871 2877 ('', 'switch-parent', None, _('diff against the second parent'))],
2872 2878 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
2873 2879 "grep":
2874 2880 (grep,
2875 2881 [('0', 'print0', None, _('end fields with NUL')),
2876 2882 ('', 'all', None, _('print all revisions that match')),
2877 2883 ('f', 'follow', None,
2878 2884 _('follow changeset history, or file history across copies and renames')),
2879 2885 ('i', 'ignore-case', None, _('ignore case when matching')),
2880 2886 ('l', 'files-with-matches', None,
2881 2887 _('print only filenames and revs that match')),
2882 2888 ('n', 'line-number', None, _('print matching line numbers')),
2883 2889 ('r', 'rev', [], _('search in given revision range')),
2884 2890 ('u', 'user', None, _('print user who committed change')),
2885 2891 ] + walkopts,
2886 2892 _('hg grep [OPTION]... PATTERN [FILE]...')),
2887 2893 "heads":
2888 2894 (heads,
2889 2895 [('', 'style', '', _('display using template map file')),
2890 2896 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2891 2897 ('', 'template', '', _('display with template'))],
2892 2898 _('hg heads [-r REV] [REV]...')),
2893 2899 "help": (help_, [], _('hg help [COMMAND]')),
2894 2900 "identify|id":
2895 2901 (identify,
2896 2902 [('r', 'rev', '', _('identify the specified rev')),
2897 2903 ('n', 'num', None, _('show local revision number')),
2898 2904 ('i', 'id', None, _('show global revision id')),
2899 2905 ('b', 'branch', None, _('show branch')),
2900 2906 ('t', 'tags', None, _('show tags'))],
2901 2907 _('hg identify [-nibt] [-r REV] [SOURCE]')),
2902 2908 "import|patch":
2903 2909 (import_,
2904 2910 [('p', 'strip', 1,
2905 2911 _('directory strip option for patch. This has the same\n'
2906 2912 'meaning as the corresponding patch option')),
2907 2913 ('b', 'base', '', _('base path')),
2908 2914 ('f', 'force', None,
2909 2915 _('skip check for outstanding uncommitted changes')),
2910 2916 ('', 'exact', None,
2911 2917 _('apply patch to the nodes from which it was generated')),
2912 2918 ('', 'import-branch', None,
2913 2919 _('Use any branch information in patch (implied by --exact)'))] + commitopts,
2914 2920 _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
2915 2921 "incoming|in": (incoming,
2916 2922 [('M', 'no-merges', None, _('do not show merges')),
2917 2923 ('f', 'force', None,
2918 2924 _('run even when remote repository is unrelated')),
2919 2925 ('', 'style', '', _('display using template map file')),
2920 2926 ('n', 'newest-first', None, _('show newest record first')),
2921 2927 ('', 'bundle', '', _('file to store the bundles into')),
2922 2928 ('p', 'patch', None, _('show patch')),
2923 2929 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2924 2930 ('', 'template', '', _('display with template')),
2925 2931 ] + remoteopts,
2926 2932 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
2927 2933 ' [--bundle FILENAME] [SOURCE]')),
2928 2934 "^init":
2929 2935 (init,
2930 2936 remoteopts,
2931 2937 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
2932 2938 "locate":
2933 2939 (locate,
2934 2940 [('r', 'rev', '', _('search the repository as it stood at rev')),
2935 2941 ('0', 'print0', None,
2936 2942 _('end filenames with NUL, for use with xargs')),
2937 2943 ('f', 'fullpath', None,
2938 2944 _('print complete paths from the filesystem root')),
2939 2945 ] + walkopts,
2940 2946 _('hg locate [OPTION]... [PATTERN]...')),
2941 2947 "^log|history":
2942 2948 (log,
2943 2949 [('f', 'follow', None,
2944 2950 _('follow changeset history, or file history across copies and renames')),
2945 2951 ('', 'follow-first', None,
2946 2952 _('only follow the first parent of merge changesets')),
2947 2953 ('d', 'date', '', _('show revs matching date spec')),
2948 2954 ('C', 'copies', None, _('show copied files')),
2949 2955 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
2950 2956 ('l', 'limit', '', _('limit number of changes displayed')),
2951 2957 ('r', 'rev', [], _('show the specified revision or range')),
2952 2958 ('', 'removed', None, _('include revs where files were removed')),
2953 2959 ('M', 'no-merges', None, _('do not show merges')),
2954 2960 ('', 'style', '', _('display using template map file')),
2955 2961 ('m', 'only-merges', None, _('show only merges')),
2956 2962 ('p', 'patch', None, _('show patch')),
2957 2963 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
2958 2964 ('', 'template', '', _('display with template')),
2959 2965 ] + walkopts,
2960 2966 _('hg log [OPTION]... [FILE]')),
2961 2967 "manifest": (manifest, [], _('hg manifest [REV]')),
2962 2968 "^merge":
2963 2969 (merge,
2964 2970 [('f', 'force', None, _('force a merge with outstanding changes')),
2965 2971 ('r', 'rev', '', _('revision to merge')),
2966 2972 ],
2967 2973 _('hg merge [-f] [[-r] REV]')),
2968 2974 "outgoing|out": (outgoing,
2969 2975 [('M', 'no-merges', None, _('do not show merges')),
2970 2976 ('f', 'force', None,
2971 2977 _('run even when remote repository is unrelated')),
2972 2978 ('p', 'patch', None, _('show patch')),
2973 2979 ('', 'style', '', _('display using template map file')),
2974 2980 ('r', 'rev', [], _('a specific revision you would like to push')),
2975 2981 ('n', 'newest-first', None, _('show newest record first')),
2976 2982 ('', 'template', '', _('display with template')),
2977 2983 ] + remoteopts,
2978 2984 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
2979 2985 "^parents":
2980 2986 (parents,
2981 2987 [('r', 'rev', '', _('show parents from the specified rev')),
2982 2988 ('', 'style', '', _('display using template map file')),
2983 2989 ('', 'template', '', _('display with template'))],
2984 2990 _('hg parents [-r REV] [FILE]')),
2985 2991 "paths": (paths, [], _('hg paths [NAME]')),
2986 2992 "^pull":
2987 2993 (pull,
2988 2994 [('u', 'update', None,
2989 2995 _('update to new tip if changesets were pulled')),
2990 2996 ('f', 'force', None,
2991 2997 _('run even when remote repository is unrelated')),
2992 2998 ('r', 'rev', [],
2993 2999 _('a specific revision up to which you would like to pull')),
2994 3000 ] + remoteopts,
2995 3001 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
2996 3002 "^push":
2997 3003 (push,
2998 3004 [('f', 'force', None, _('force push')),
2999 3005 ('r', 'rev', [], _('a specific revision you would like to push')),
3000 3006 ] + remoteopts,
3001 3007 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3002 3008 "debugrawcommit|rawcommit":
3003 3009 (rawcommit,
3004 3010 [('p', 'parent', [], _('parent')),
3005 3011 ('d', 'date', '', _('date code')),
3006 3012 ('u', 'user', '', _('user')),
3007 3013 ('F', 'files', '', _('file list'))
3008 3014 ] + commitopts,
3009 3015 _('hg debugrawcommit [OPTION]... [FILE]...')),
3010 3016 "recover": (recover, [], _('hg recover')),
3011 3017 "^remove|rm":
3012 3018 (remove,
3013 3019 [('A', 'after', None, _('record remove that has already occurred')),
3014 3020 ('f', 'force', None, _('remove file even if modified')),
3015 3021 ] + walkopts,
3016 3022 _('hg remove [OPTION]... FILE...')),
3017 3023 "rename|mv":
3018 3024 (rename,
3019 3025 [('A', 'after', None, _('record a rename that has already occurred')),
3020 3026 ('f', 'force', None,
3021 3027 _('forcibly copy over an existing managed file')),
3022 3028 ] + walkopts + dryrunopts,
3023 3029 _('hg rename [OPTION]... SOURCE... DEST')),
3024 3030 "^revert":
3025 3031 (revert,
3026 3032 [('a', 'all', None, _('revert all changes when no arguments given')),
3027 3033 ('d', 'date', '', _('tipmost revision matching date')),
3028 3034 ('r', 'rev', '', _('revision to revert to')),
3029 3035 ('', 'no-backup', None, _('do not save backup copies of files')),
3030 3036 ] + walkopts + dryrunopts,
3031 3037 _('hg revert [OPTION]... [-r REV] [NAME]...')),
3032 3038 "rollback": (rollback, [], _('hg rollback')),
3033 3039 "root": (root, [], _('hg root')),
3034 3040 "showconfig|debugconfig":
3035 3041 (showconfig,
3036 3042 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3037 3043 _('showconfig [-u] [NAME]...')),
3038 3044 "^serve":
3039 3045 (serve,
3040 3046 [('A', 'accesslog', '', _('name of access log file to write to')),
3041 3047 ('d', 'daemon', None, _('run server in background')),
3042 3048 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3043 3049 ('E', 'errorlog', '', _('name of error log file to write to')),
3044 3050 ('p', 'port', 0, _('port to use (default: 8000)')),
3045 3051 ('a', 'address', '', _('address to use')),
3046 3052 ('n', 'name', '',
3047 3053 _('name to show in web pages (default: working dir)')),
3048 3054 ('', 'webdir-conf', '', _('name of the webdir config file'
3049 3055 ' (serve more than one repo)')),
3050 3056 ('', 'pid-file', '', _('name of file to write process ID to')),
3051 3057 ('', 'stdio', None, _('for remote clients')),
3052 3058 ('t', 'templates', '', _('web templates to use')),
3053 3059 ('', 'style', '', _('template style to use')),
3054 3060 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
3055 3061 _('hg serve [OPTION]...')),
3056 3062 "^status|st":
3057 3063 (status,
3058 3064 [('A', 'all', None, _('show status of all files')),
3059 3065 ('m', 'modified', None, _('show only modified files')),
3060 3066 ('a', 'added', None, _('show only added files')),
3061 3067 ('r', 'removed', None, _('show only removed files')),
3062 3068 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3063 3069 ('c', 'clean', None, _('show only files without changes')),
3064 3070 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3065 3071 ('i', 'ignored', None, _('show only ignored files')),
3066 3072 ('n', 'no-status', None, _('hide status prefix')),
3067 3073 ('C', 'copies', None, _('show source of copied files')),
3068 3074 ('0', 'print0', None,
3069 3075 _('end filenames with NUL, for use with xargs')),
3070 3076 ('', 'rev', [], _('show difference from revision')),
3071 3077 ] + walkopts,
3072 3078 _('hg status [OPTION]... [FILE]...')),
3073 3079 "tag":
3074 3080 (tag,
3075 3081 [('f', 'force', None, _('replace existing tag')),
3076 3082 ('l', 'local', None, _('make the tag local')),
3077 3083 ('m', 'message', '', _('message for tag commit log entry')),
3078 3084 ('d', 'date', '', _('record datecode as commit date')),
3079 3085 ('u', 'user', '', _('record user as commiter')),
3080 3086 ('r', 'rev', '', _('revision to tag')),
3081 3087 ('', 'remove', None, _('remove a tag'))],
3082 3088 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3083 3089 "tags": (tags, [], _('hg tags')),
3084 3090 "tip":
3085 3091 (tip,
3086 3092 [('', 'style', '', _('display using template map file')),
3087 3093 ('p', 'patch', None, _('show patch')),
3088 3094 ('', 'template', '', _('display with template'))],
3089 3095 _('hg tip [-p]')),
3090 3096 "unbundle":
3091 3097 (unbundle,
3092 3098 [('u', 'update', None,
3093 3099 _('update to new tip if changesets were unbundled'))],
3094 3100 _('hg unbundle [-u] FILE...')),
3095 3101 "^update|up|checkout|co":
3096 3102 (update,
3097 3103 [('C', 'clean', None, _('overwrite locally modified files')),
3098 3104 ('d', 'date', '', _('tipmost revision matching date')),
3099 3105 ('r', 'rev', '', _('revision'))],
3100 3106 _('hg update [-C] [-d DATE] [[-r] REV]')),
3101 3107 "verify": (verify, [], _('hg verify')),
3102 3108 "version": (version_, [], _('hg version')),
3103 3109 }
3104 3110
3105 3111 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3106 3112 " debugindex debugindexdot debugdate debuginstall")
3107 3113 optionalrepo = ("paths serve showconfig")
3108 3114
3109 3115 def dispatch(args, argv0=None):
3110 3116 try:
3111 3117 u = ui.ui(traceback='--traceback' in args)
3112 3118 except util.Abort, inst:
3113 3119 sys.stderr.write(_("abort: %s\n") % inst)
3114 3120 return -1
3115 3121 return cmdutil.runcatch(u, args, argv0=argv0)
3116 3122
3117 3123 def run():
3118 3124 sys.exit(dispatch(sys.argv[1:], argv0=sys.argv[0]))
@@ -1,1180 +1,1180 b''
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os, mimetypes, re, zlib, mimetools, cStringIO, sys
10 10 import tempfile, urllib, bz2
11 11 from mercurial.node import *
12 12 from mercurial.i18n import gettext as _
13 13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
14 14 from mercurial import revlog, templater
15 15 from common import get_mtime, staticfile, style_map, paritygen
16 16
17 17 def _up(p):
18 18 if p[0] != "/":
19 19 p = "/" + p
20 20 if p[-1] == "/":
21 21 p = p[:-1]
22 22 up = os.path.dirname(p)
23 23 if up == "/":
24 24 return "/"
25 25 return up + "/"
26 26
27 27 def revnavgen(pos, pagelen, limit, nodefunc):
28 28 def seq(factor, limit=None):
29 29 if limit:
30 30 yield limit
31 31 if limit >= 20 and limit <= 40:
32 32 yield 50
33 33 else:
34 34 yield 1 * factor
35 35 yield 3 * factor
36 36 for f in seq(factor * 10):
37 37 yield f
38 38
39 39 def nav(**map):
40 40 l = []
41 41 last = 0
42 42 for f in seq(1, pagelen):
43 43 if f < pagelen or f <= last:
44 44 continue
45 45 if f > limit:
46 46 break
47 47 last = f
48 48 if pos + f < limit:
49 49 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
50 50 if pos - f >= 0:
51 51 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
52 52
53 53 try:
54 54 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
55 55
56 56 for label, node in l:
57 57 yield {"label": label, "node": node}
58 58
59 59 yield {"label": "tip", "node": "tip"}
60 60 except hg.RepoError:
61 61 pass
62 62
63 63 return nav
64 64
65 65 class hgweb(object):
66 66 def __init__(self, repo, name=None):
67 67 if type(repo) == type(""):
68 68 self.repo = hg.repository(ui.ui(report_untrusted=False), repo)
69 69 else:
70 70 self.repo = repo
71 71
72 72 self.mtime = -1
73 73 self.reponame = name
74 74 self.archives = 'zip', 'gz', 'bz2'
75 75 self.stripecount = 1
76 76 # a repo owner may set web.templates in .hg/hgrc to get any file
77 77 # readable by the user running the CGI script
78 78 self.templatepath = self.config("web", "templates",
79 79 templater.templatepath(),
80 80 untrusted=False)
81 81
82 82 # The CGI scripts are often run by a user different from the repo owner.
83 83 # Trust the settings from the .hg/hgrc files by default.
84 84 def config(self, section, name, default=None, untrusted=True):
85 85 return self.repo.ui.config(section, name, default,
86 86 untrusted=untrusted)
87 87
88 88 def configbool(self, section, name, default=False, untrusted=True):
89 89 return self.repo.ui.configbool(section, name, default,
90 90 untrusted=untrusted)
91 91
92 92 def configlist(self, section, name, default=None, untrusted=True):
93 93 return self.repo.ui.configlist(section, name, default,
94 94 untrusted=untrusted)
95 95
96 96 def refresh(self):
97 97 mtime = get_mtime(self.repo.root)
98 98 if mtime != self.mtime:
99 99 self.mtime = mtime
100 100 self.repo = hg.repository(self.repo.ui, self.repo.root)
101 101 self.maxchanges = int(self.config("web", "maxchanges", 10))
102 102 self.stripecount = int(self.config("web", "stripes", 1))
103 103 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
104 104 self.maxfiles = int(self.config("web", "maxfiles", 10))
105 105 self.allowpull = self.configbool("web", "allowpull", True)
106 106 self.encoding = self.config("web", "encoding", util._encoding)
107 107
108 108 def archivelist(self, nodeid):
109 109 allowed = self.configlist("web", "allow_archive")
110 110 for i, spec in self.archive_specs.iteritems():
111 111 if i in allowed or self.configbool("web", "allow" + i):
112 112 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
113 113
114 114 def listfilediffs(self, files, changeset):
115 115 for f in files[:self.maxfiles]:
116 116 yield self.t("filedifflink", node=hex(changeset), file=f)
117 117 if len(files) > self.maxfiles:
118 118 yield self.t("fileellipses")
119 119
120 120 def siblings(self, siblings=[], hiderev=None, **args):
121 121 siblings = [s for s in siblings if s.node() != nullid]
122 122 if len(siblings) == 1 and siblings[0].rev() == hiderev:
123 123 return
124 124 for s in siblings:
125 125 d = {'node': hex(s.node()), 'rev': s.rev()}
126 126 if hasattr(s, 'path'):
127 127 d['file'] = s.path()
128 128 d.update(args)
129 129 yield d
130 130
131 131 def renamelink(self, fl, node):
132 132 r = fl.renamed(node)
133 133 if r:
134 134 return [dict(file=r[0], node=hex(r[1]))]
135 135 return []
136 136
137 137 def nodetagsdict(self, node):
138 138 return [{"name": i} for i in self.repo.nodetags(node)]
139 139
140 140 def nodebranchdict(self, ctx):
141 141 branches = []
142 142 branch = ctx.branch()
143 143 if self.repo.branchtags()[branch] == ctx.node():
144 144 branches.append({"name": branch})
145 145 return branches
146 146
147 147 def showtag(self, t1, node=nullid, **args):
148 148 for t in self.repo.nodetags(node):
149 149 yield self.t(t1, tag=t, **args)
150 150
151 151 def diff(self, node1, node2, files):
152 152 def filterfiles(filters, files):
153 153 l = [x for x in files if x in filters]
154 154
155 155 for t in filters:
156 156 if t and t[-1] != os.sep:
157 157 t += os.sep
158 158 l += [x for x in files if x.startswith(t)]
159 159 return l
160 160
161 161 parity = paritygen(self.stripecount)
162 162 def diffblock(diff, f, fn):
163 163 yield self.t("diffblock",
164 164 lines=prettyprintlines(diff),
165 165 parity=parity.next(),
166 166 file=f,
167 167 filenode=hex(fn or nullid))
168 168
169 169 def prettyprintlines(diff):
170 170 for l in diff.splitlines(1):
171 171 if l.startswith('+'):
172 172 yield self.t("difflineplus", line=l)
173 173 elif l.startswith('-'):
174 174 yield self.t("difflineminus", line=l)
175 175 elif l.startswith('@'):
176 176 yield self.t("difflineat", line=l)
177 177 else:
178 178 yield self.t("diffline", line=l)
179 179
180 180 r = self.repo
181 181 c1 = r.changectx(node1)
182 182 c2 = r.changectx(node2)
183 183 date1 = util.datestr(c1.date())
184 184 date2 = util.datestr(c2.date())
185 185
186 186 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
187 187 if files:
188 188 modified, added, removed = map(lambda x: filterfiles(files, x),
189 189 (modified, added, removed))
190 190
191 191 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
192 192 for f in modified:
193 193 to = c1.filectx(f).data()
194 194 tn = c2.filectx(f).data()
195 195 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
196 196 opts=diffopts), f, tn)
197 197 for f in added:
198 198 to = None
199 199 tn = c2.filectx(f).data()
200 200 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
201 201 opts=diffopts), f, tn)
202 202 for f in removed:
203 203 to = c1.filectx(f).data()
204 204 tn = None
205 205 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
206 206 opts=diffopts), f, tn)
207 207
208 208 def changelog(self, ctx, shortlog=False):
209 209 def changelist(**map):
210 210 cl = self.repo.changelog
211 211 l = [] # build a list in forward order for efficiency
212 212 for i in xrange(start, end):
213 213 ctx = self.repo.changectx(i)
214 214 n = ctx.node()
215 215
216 216 l.insert(0, {"parity": parity.next(),
217 217 "author": ctx.user(),
218 218 "parent": self.siblings(ctx.parents(), i - 1),
219 219 "child": self.siblings(ctx.children(), i + 1),
220 220 "changelogtag": self.showtag("changelogtag",n),
221 221 "desc": ctx.description(),
222 222 "date": ctx.date(),
223 223 "files": self.listfilediffs(ctx.files(), n),
224 224 "rev": i,
225 225 "node": hex(n),
226 226 "tags": self.nodetagsdict(n),
227 227 "branches": self.nodebranchdict(ctx)})
228 228
229 229 for e in l:
230 230 yield e
231 231
232 232 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
233 233 cl = self.repo.changelog
234 234 count = cl.count()
235 235 pos = ctx.rev()
236 236 start = max(0, pos - maxchanges + 1)
237 237 end = min(count, start + maxchanges)
238 238 pos = end - 1
239 239 parity = paritygen(self.stripecount, offset=start-end)
240 240
241 241 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
242 242
243 243 yield self.t(shortlog and 'shortlog' or 'changelog',
244 244 changenav=changenav,
245 245 node=hex(cl.tip()),
246 246 rev=pos, changesets=count, entries=changelist,
247 247 archives=self.archivelist("tip"))
248 248
249 249 def search(self, query):
250 250
251 251 def changelist(**map):
252 252 cl = self.repo.changelog
253 253 count = 0
254 254 qw = query.lower().split()
255 255
256 256 def revgen():
257 257 for i in xrange(cl.count() - 1, 0, -100):
258 258 l = []
259 259 for j in xrange(max(0, i - 100), i):
260 260 ctx = self.repo.changectx(j)
261 261 l.append(ctx)
262 262 l.reverse()
263 263 for e in l:
264 264 yield e
265 265
266 266 for ctx in revgen():
267 267 miss = 0
268 268 for q in qw:
269 269 if not (q in ctx.user().lower() or
270 270 q in ctx.description().lower() or
271 271 q in " ".join(ctx.files()).lower()):
272 272 miss = 1
273 273 break
274 274 if miss:
275 275 continue
276 276
277 277 count += 1
278 278 n = ctx.node()
279 279
280 280 yield self.t('searchentry',
281 281 parity=parity.next(),
282 282 author=ctx.user(),
283 283 parent=self.siblings(ctx.parents()),
284 284 child=self.siblings(ctx.children()),
285 285 changelogtag=self.showtag("changelogtag",n),
286 286 desc=ctx.description(),
287 287 date=ctx.date(),
288 288 files=self.listfilediffs(ctx.files(), n),
289 289 rev=ctx.rev(),
290 290 node=hex(n),
291 291 tags=self.nodetagsdict(n),
292 292 branches=self.nodebranchdict(ctx))
293 293
294 294 if count >= self.maxchanges:
295 295 break
296 296
297 297 cl = self.repo.changelog
298 298 parity = paritygen(self.stripecount)
299 299
300 300 yield self.t('search',
301 301 query=query,
302 302 node=hex(cl.tip()),
303 303 entries=changelist,
304 304 archives=self.archivelist("tip"))
305 305
306 306 def changeset(self, ctx):
307 307 n = ctx.node()
308 308 parents = ctx.parents()
309 309 p1 = parents[0].node()
310 310
311 311 files = []
312 312 parity = paritygen(self.stripecount)
313 313 for f in ctx.files():
314 314 files.append(self.t("filenodelink",
315 315 node=hex(n), file=f,
316 316 parity=parity.next()))
317 317
318 318 def diff(**map):
319 319 yield self.diff(p1, n, None)
320 320
321 321 yield self.t('changeset',
322 322 diff=diff,
323 323 rev=ctx.rev(),
324 324 node=hex(n),
325 325 parent=self.siblings(parents),
326 326 child=self.siblings(ctx.children()),
327 327 changesettag=self.showtag("changesettag",n),
328 328 author=ctx.user(),
329 329 desc=ctx.description(),
330 330 date=ctx.date(),
331 331 files=files,
332 332 archives=self.archivelist(hex(n)),
333 333 tags=self.nodetagsdict(n),
334 334 branches=self.nodebranchdict(ctx))
335 335
336 336 def filelog(self, fctx):
337 337 f = fctx.path()
338 338 fl = fctx.filelog()
339 339 count = fl.count()
340 340 pagelen = self.maxshortchanges
341 341 pos = fctx.filerev()
342 342 start = max(0, pos - pagelen + 1)
343 343 end = min(count, start + pagelen)
344 344 pos = end - 1
345 345 parity = paritygen(self.stripecount, offset=start-end)
346 346
347 347 def entries(**map):
348 348 l = []
349 349
350 350 for i in xrange(start, end):
351 351 ctx = fctx.filectx(i)
352 352 n = fl.node(i)
353 353
354 354 l.insert(0, {"parity": parity.next(),
355 355 "filerev": i,
356 356 "file": f,
357 357 "node": hex(ctx.node()),
358 358 "author": ctx.user(),
359 359 "date": ctx.date(),
360 360 "rename": self.renamelink(fl, n),
361 361 "parent": self.siblings(fctx.parents()),
362 362 "child": self.siblings(fctx.children()),
363 363 "desc": ctx.description()})
364 364
365 365 for e in l:
366 366 yield e
367 367
368 368 nodefunc = lambda x: fctx.filectx(fileid=x)
369 369 nav = revnavgen(pos, pagelen, count, nodefunc)
370 370 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
371 371 entries=entries)
372 372
373 373 def filerevision(self, fctx):
374 374 f = fctx.path()
375 375 text = fctx.data()
376 376 fl = fctx.filelog()
377 377 n = fctx.filenode()
378 378 parity = paritygen(self.stripecount)
379 379
380 380 mt = mimetypes.guess_type(f)[0]
381 381 rawtext = text
382 382 if util.binary(text):
383 383 mt = mt or 'application/octet-stream'
384 384 text = "(binary:%s)" % mt
385 385 mt = mt or 'text/plain'
386 386
387 387 def lines():
388 388 for l, t in enumerate(text.splitlines(1)):
389 389 yield {"line": t,
390 390 "linenumber": "% 6d" % (l + 1),
391 391 "parity": parity.next()}
392 392
393 393 yield self.t("filerevision",
394 394 file=f,
395 395 path=_up(f),
396 396 text=lines(),
397 397 raw=rawtext,
398 398 mimetype=mt,
399 399 rev=fctx.rev(),
400 400 node=hex(fctx.node()),
401 401 author=fctx.user(),
402 402 date=fctx.date(),
403 403 desc=fctx.description(),
404 404 parent=self.siblings(fctx.parents()),
405 405 child=self.siblings(fctx.children()),
406 406 rename=self.renamelink(fl, n),
407 permissions=fctx.manifest().execf(f))
407 permissions=fctx.manifest().flags(f))
408 408
409 409 def fileannotate(self, fctx):
410 410 f = fctx.path()
411 411 n = fctx.filenode()
412 412 fl = fctx.filelog()
413 413 parity = paritygen(self.stripecount)
414 414
415 415 def annotate(**map):
416 416 last = None
417 417 for f, l in fctx.annotate(follow=True):
418 418 fnode = f.filenode()
419 419 name = self.repo.ui.shortuser(f.user())
420 420
421 421 if last != fnode:
422 422 last = fnode
423 423
424 424 yield {"parity": parity.next(),
425 425 "node": hex(f.node()),
426 426 "rev": f.rev(),
427 427 "author": name,
428 428 "file": f.path(),
429 429 "line": l}
430 430
431 431 yield self.t("fileannotate",
432 432 file=f,
433 433 annotate=annotate,
434 434 path=_up(f),
435 435 rev=fctx.rev(),
436 436 node=hex(fctx.node()),
437 437 author=fctx.user(),
438 438 date=fctx.date(),
439 439 desc=fctx.description(),
440 440 rename=self.renamelink(fl, n),
441 441 parent=self.siblings(fctx.parents()),
442 442 child=self.siblings(fctx.children()),
443 permissions=fctx.manifest().execf(f))
443 permissions=fctx.manifest().flags(f))
444 444
445 445 def manifest(self, ctx, path):
446 446 mf = ctx.manifest()
447 447 node = ctx.node()
448 448
449 449 files = {}
450 450 parity = paritygen(self.stripecount)
451 451
452 452 if path and path[-1] != "/":
453 453 path += "/"
454 454 l = len(path)
455 455 abspath = "/" + path
456 456
457 457 for f, n in mf.items():
458 458 if f[:l] != path:
459 459 continue
460 460 remain = f[l:]
461 461 if "/" in remain:
462 462 short = remain[:remain.index("/") + 1] # bleah
463 463 files[short] = (f, None)
464 464 else:
465 465 short = os.path.basename(remain)
466 466 files[short] = (f, n)
467 467
468 468 def filelist(**map):
469 469 fl = files.keys()
470 470 fl.sort()
471 471 for f in fl:
472 472 full, fnode = files[f]
473 473 if not fnode:
474 474 continue
475 475
476 476 yield {"file": full,
477 477 "parity": parity.next(),
478 478 "basename": f,
479 479 "size": ctx.filectx(full).size(),
480 "permissions": mf.execf(full)}
480 "permissions": mf.flags(full)}
481 481
482 482 def dirlist(**map):
483 483 fl = files.keys()
484 484 fl.sort()
485 485 for f in fl:
486 486 full, fnode = files[f]
487 487 if fnode:
488 488 continue
489 489
490 490 yield {"parity": parity.next(),
491 491 "path": os.path.join(abspath, f),
492 492 "basename": f[:-1]}
493 493
494 494 yield self.t("manifest",
495 495 rev=ctx.rev(),
496 496 node=hex(node),
497 497 path=abspath,
498 498 up=_up(abspath),
499 499 upparity=parity.next(),
500 500 fentries=filelist,
501 501 dentries=dirlist,
502 502 archives=self.archivelist(hex(node)),
503 503 tags=self.nodetagsdict(node),
504 504 branches=self.nodebranchdict(ctx))
505 505
506 506 def tags(self):
507 507 i = self.repo.tagslist()
508 508 i.reverse()
509 509 parity = paritygen(self.stripecount)
510 510
511 511 def entries(notip=False, **map):
512 512 for k, n in i:
513 513 if notip and k == "tip":
514 514 continue
515 515 yield {"parity": parity.next(),
516 516 "tag": k,
517 517 "date": self.repo.changectx(n).date(),
518 518 "node": hex(n)}
519 519
520 520 yield self.t("tags",
521 521 node=hex(self.repo.changelog.tip()),
522 522 entries=lambda **x: entries(False, **x),
523 523 entriesnotip=lambda **x: entries(True, **x))
524 524
525 525 def summary(self):
526 526 i = self.repo.tagslist()
527 527 i.reverse()
528 528
529 529 def tagentries(**map):
530 530 parity = paritygen(self.stripecount)
531 531 count = 0
532 532 for k, n in i:
533 533 if k == "tip": # skip tip
534 534 continue;
535 535
536 536 count += 1
537 537 if count > 10: # limit to 10 tags
538 538 break;
539 539
540 540 yield self.t("tagentry",
541 541 parity=parity.next(),
542 542 tag=k,
543 543 node=hex(n),
544 544 date=self.repo.changectx(n).date())
545 545
546 546
547 547 def branches(**map):
548 548 parity = paritygen(self.stripecount)
549 549
550 550 b = self.repo.branchtags()
551 551 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
552 552 l.sort()
553 553
554 554 for r,n,t in l:
555 555 ctx = self.repo.changectx(n)
556 556
557 557 yield {'parity': parity.next(),
558 558 'branch': t,
559 559 'node': hex(n),
560 560 'date': ctx.date()}
561 561
562 562 def changelist(**map):
563 563 parity = paritygen(self.stripecount, offset=start-end)
564 564 l = [] # build a list in forward order for efficiency
565 565 for i in xrange(start, end):
566 566 ctx = self.repo.changectx(i)
567 567 n = ctx.node()
568 568 hn = hex(n)
569 569
570 570 l.insert(0, self.t(
571 571 'shortlogentry',
572 572 parity=parity.next(),
573 573 author=ctx.user(),
574 574 desc=ctx.description(),
575 575 date=ctx.date(),
576 576 rev=i,
577 577 node=hn,
578 578 tags=self.nodetagsdict(n),
579 579 branches=self.nodebranchdict(ctx)))
580 580
581 581 yield l
582 582
583 583 cl = self.repo.changelog
584 584 count = cl.count()
585 585 start = max(0, count - self.maxchanges)
586 586 end = min(count, start + self.maxchanges)
587 587
588 588 yield self.t("summary",
589 589 desc=self.config("web", "description", "unknown"),
590 590 owner=(self.config("ui", "username") or # preferred
591 591 self.config("web", "contact") or # deprecated
592 592 self.config("web", "author", "unknown")), # also
593 593 lastchange=cl.read(cl.tip())[2],
594 594 tags=tagentries,
595 595 branches=branches,
596 596 shortlog=changelist,
597 597 node=hex(cl.tip()),
598 598 archives=self.archivelist("tip"))
599 599
600 600 def filediff(self, fctx):
601 601 n = fctx.node()
602 602 path = fctx.path()
603 603 parents = fctx.parents()
604 604 p1 = parents and parents[0].node() or nullid
605 605
606 606 def diff(**map):
607 607 yield self.diff(p1, n, [path])
608 608
609 609 yield self.t("filediff",
610 610 file=path,
611 611 node=hex(n),
612 612 rev=fctx.rev(),
613 613 parent=self.siblings(parents),
614 614 child=self.siblings(fctx.children()),
615 615 diff=diff)
616 616
617 617 archive_specs = {
618 618 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
619 619 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
620 620 'zip': ('application/zip', 'zip', '.zip', None),
621 621 }
622 622
623 623 def archive(self, req, key, type_):
624 624 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
625 625 cnode = self.repo.lookup(key)
626 626 arch_version = key
627 627 if cnode == key or key == 'tip':
628 628 arch_version = short(cnode)
629 629 name = "%s-%s" % (reponame, arch_version)
630 630 mimetype, artype, extension, encoding = self.archive_specs[type_]
631 631 headers = [('Content-type', mimetype),
632 632 ('Content-disposition', 'attachment; filename=%s%s' %
633 633 (name, extension))]
634 634 if encoding:
635 635 headers.append(('Content-encoding', encoding))
636 636 req.header(headers)
637 637 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
638 638
639 639 # add tags to things
640 640 # tags -> list of changesets corresponding to tags
641 641 # find tag, changeset, file
642 642
643 643 def cleanpath(self, path):
644 644 path = path.lstrip('/')
645 645 return util.canonpath(self.repo.root, '', path)
646 646
647 647 def run(self):
648 648 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
649 649 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
650 650 import mercurial.hgweb.wsgicgi as wsgicgi
651 651 from request import wsgiapplication
652 652 def make_web_app():
653 653 return self
654 654 wsgicgi.launch(wsgiapplication(make_web_app))
655 655
656 656 def run_wsgi(self, req):
657 657 def header(**map):
658 658 header_file = cStringIO.StringIO(
659 659 ''.join(self.t("header", encoding=self.encoding, **map)))
660 660 msg = mimetools.Message(header_file, 0)
661 661 req.header(msg.items())
662 662 yield header_file.read()
663 663
664 664 def rawfileheader(**map):
665 665 req.header([('Content-type', map['mimetype']),
666 666 ('Content-disposition', 'filename=%s' % map['file']),
667 667 ('Content-length', str(len(map['raw'])))])
668 668 yield ''
669 669
670 670 def footer(**map):
671 671 yield self.t("footer", **map)
672 672
673 673 def motd(**map):
674 674 yield self.config("web", "motd", "")
675 675
676 676 def expand_form(form):
677 677 shortcuts = {
678 678 'cl': [('cmd', ['changelog']), ('rev', None)],
679 679 'sl': [('cmd', ['shortlog']), ('rev', None)],
680 680 'cs': [('cmd', ['changeset']), ('node', None)],
681 681 'f': [('cmd', ['file']), ('filenode', None)],
682 682 'fl': [('cmd', ['filelog']), ('filenode', None)],
683 683 'fd': [('cmd', ['filediff']), ('node', None)],
684 684 'fa': [('cmd', ['annotate']), ('filenode', None)],
685 685 'mf': [('cmd', ['manifest']), ('manifest', None)],
686 686 'ca': [('cmd', ['archive']), ('node', None)],
687 687 'tags': [('cmd', ['tags'])],
688 688 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
689 689 'static': [('cmd', ['static']), ('file', None)]
690 690 }
691 691
692 692 for k in shortcuts.iterkeys():
693 693 if form.has_key(k):
694 694 for name, value in shortcuts[k]:
695 695 if value is None:
696 696 value = form[k]
697 697 form[name] = value
698 698 del form[k]
699 699
700 700 def rewrite_request(req):
701 701 '''translate new web interface to traditional format'''
702 702
703 703 def spliturl(req):
704 704 def firstitem(query):
705 705 return query.split('&', 1)[0].split(';', 1)[0]
706 706
707 707 def normurl(url):
708 708 inner = '/'.join([x for x in url.split('/') if x])
709 709 tl = len(url) > 1 and url.endswith('/') and '/' or ''
710 710
711 711 return '%s%s%s' % (url.startswith('/') and '/' or '',
712 712 inner, tl)
713 713
714 714 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
715 715 pi = normurl(req.env.get('PATH_INFO', ''))
716 716 if pi:
717 717 # strip leading /
718 718 pi = pi[1:]
719 719 if pi:
720 720 root = root[:root.rfind(pi)]
721 721 if req.env.has_key('REPO_NAME'):
722 722 rn = req.env['REPO_NAME'] + '/'
723 723 root += rn
724 724 query = pi[len(rn):]
725 725 else:
726 726 query = pi
727 727 else:
728 728 root += '?'
729 729 query = firstitem(req.env['QUERY_STRING'])
730 730
731 731 return (root, query)
732 732
733 733 req.url, query = spliturl(req)
734 734
735 735 if req.form.has_key('cmd'):
736 736 # old style
737 737 return
738 738
739 739 args = query.split('/', 2)
740 740 if not args or not args[0]:
741 741 return
742 742
743 743 cmd = args.pop(0)
744 744 style = cmd.rfind('-')
745 745 if style != -1:
746 746 req.form['style'] = [cmd[:style]]
747 747 cmd = cmd[style+1:]
748 748 # avoid accepting e.g. style parameter as command
749 749 if hasattr(self, 'do_' + cmd):
750 750 req.form['cmd'] = [cmd]
751 751
752 752 if args and args[0]:
753 753 node = args.pop(0)
754 754 req.form['node'] = [node]
755 755 if args:
756 756 req.form['file'] = args
757 757
758 758 if cmd == 'static':
759 759 req.form['file'] = req.form['node']
760 760 elif cmd == 'archive':
761 761 fn = req.form['node'][0]
762 762 for type_, spec in self.archive_specs.iteritems():
763 763 ext = spec[2]
764 764 if fn.endswith(ext):
765 765 req.form['node'] = [fn[:-len(ext)]]
766 766 req.form['type'] = [type_]
767 767
768 768 def sessionvars(**map):
769 769 fields = []
770 770 if req.form.has_key('style'):
771 771 style = req.form['style'][0]
772 772 if style != self.config('web', 'style', ''):
773 773 fields.append(('style', style))
774 774
775 775 separator = req.url[-1] == '?' and ';' or '?'
776 776 for name, value in fields:
777 777 yield dict(name=name, value=value, separator=separator)
778 778 separator = ';'
779 779
780 780 self.refresh()
781 781
782 782 expand_form(req.form)
783 783 rewrite_request(req)
784 784
785 785 style = self.config("web", "style", "")
786 786 if req.form.has_key('style'):
787 787 style = req.form['style'][0]
788 788 mapfile = style_map(self.templatepath, style)
789 789
790 790 port = req.env["SERVER_PORT"]
791 791 port = port != "80" and (":" + port) or ""
792 792 urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
793 793 staticurl = self.config("web", "staticurl") or req.url + 'static/'
794 794 if not staticurl.endswith('/'):
795 795 staticurl += '/'
796 796
797 797 if not self.reponame:
798 798 self.reponame = (self.config("web", "name")
799 799 or req.env.get('REPO_NAME')
800 800 or req.url.strip('/') or self.repo.root)
801 801
802 802 self.t = templater.templater(mapfile, templater.common_filters,
803 803 defaults={"url": req.url,
804 804 "staticurl": staticurl,
805 805 "urlbase": urlbase,
806 806 "repo": self.reponame,
807 807 "header": header,
808 808 "footer": footer,
809 809 "motd": motd,
810 810 "rawfileheader": rawfileheader,
811 811 "sessionvars": sessionvars
812 812 })
813 813
814 814 try:
815 815 if not req.form.has_key('cmd'):
816 816 req.form['cmd'] = [self.t.cache['default']]
817 817
818 818 cmd = req.form['cmd'][0]
819 819
820 820 method = getattr(self, 'do_' + cmd, None)
821 821 if method:
822 822 try:
823 823 method(req)
824 824 except (hg.RepoError, revlog.RevlogError), inst:
825 825 req.write(self.t("error", error=str(inst)))
826 826 else:
827 827 req.write(self.t("error", error='No such method: ' + cmd))
828 828 finally:
829 829 self.t = None
830 830
831 831 def changectx(self, req):
832 832 if req.form.has_key('node'):
833 833 changeid = req.form['node'][0]
834 834 elif req.form.has_key('manifest'):
835 835 changeid = req.form['manifest'][0]
836 836 else:
837 837 changeid = self.repo.changelog.count() - 1
838 838
839 839 try:
840 840 ctx = self.repo.changectx(changeid)
841 841 except hg.RepoError:
842 842 man = self.repo.manifest
843 843 mn = man.lookup(changeid)
844 844 ctx = self.repo.changectx(man.linkrev(mn))
845 845
846 846 return ctx
847 847
848 848 def filectx(self, req):
849 849 path = self.cleanpath(req.form['file'][0])
850 850 if req.form.has_key('node'):
851 851 changeid = req.form['node'][0]
852 852 else:
853 853 changeid = req.form['filenode'][0]
854 854 try:
855 855 ctx = self.repo.changectx(changeid)
856 856 fctx = ctx.filectx(path)
857 857 except hg.RepoError:
858 858 fctx = self.repo.filectx(path, fileid=changeid)
859 859
860 860 return fctx
861 861
862 862 def do_log(self, req):
863 863 if req.form.has_key('file') and req.form['file'][0]:
864 864 self.do_filelog(req)
865 865 else:
866 866 self.do_changelog(req)
867 867
868 868 def do_rev(self, req):
869 869 self.do_changeset(req)
870 870
871 871 def do_file(self, req):
872 872 path = self.cleanpath(req.form.get('file', [''])[0])
873 873 if path:
874 874 try:
875 875 req.write(self.filerevision(self.filectx(req)))
876 876 return
877 877 except revlog.LookupError:
878 878 pass
879 879
880 880 req.write(self.manifest(self.changectx(req), path))
881 881
882 882 def do_diff(self, req):
883 883 self.do_filediff(req)
884 884
885 885 def do_changelog(self, req, shortlog = False):
886 886 if req.form.has_key('node'):
887 887 ctx = self.changectx(req)
888 888 else:
889 889 if req.form.has_key('rev'):
890 890 hi = req.form['rev'][0]
891 891 else:
892 892 hi = self.repo.changelog.count() - 1
893 893 try:
894 894 ctx = self.repo.changectx(hi)
895 895 except hg.RepoError:
896 896 req.write(self.search(hi)) # XXX redirect to 404 page?
897 897 return
898 898
899 899 req.write(self.changelog(ctx, shortlog = shortlog))
900 900
901 901 def do_shortlog(self, req):
902 902 self.do_changelog(req, shortlog = True)
903 903
904 904 def do_changeset(self, req):
905 905 req.write(self.changeset(self.changectx(req)))
906 906
907 907 def do_manifest(self, req):
908 908 req.write(self.manifest(self.changectx(req),
909 909 self.cleanpath(req.form['path'][0])))
910 910
911 911 def do_tags(self, req):
912 912 req.write(self.tags())
913 913
914 914 def do_summary(self, req):
915 915 req.write(self.summary())
916 916
917 917 def do_filediff(self, req):
918 918 req.write(self.filediff(self.filectx(req)))
919 919
920 920 def do_annotate(self, req):
921 921 req.write(self.fileannotate(self.filectx(req)))
922 922
923 923 def do_filelog(self, req):
924 924 req.write(self.filelog(self.filectx(req)))
925 925
926 926 def do_lookup(self, req):
927 927 try:
928 928 r = hex(self.repo.lookup(req.form['key'][0]))
929 929 success = 1
930 930 except Exception,inst:
931 931 r = str(inst)
932 932 success = 0
933 933 resp = "%s %s\n" % (success, r)
934 934 req.httphdr("application/mercurial-0.1", length=len(resp))
935 935 req.write(resp)
936 936
937 937 def do_heads(self, req):
938 938 resp = " ".join(map(hex, self.repo.heads())) + "\n"
939 939 req.httphdr("application/mercurial-0.1", length=len(resp))
940 940 req.write(resp)
941 941
942 942 def do_branches(self, req):
943 943 nodes = []
944 944 if req.form.has_key('nodes'):
945 945 nodes = map(bin, req.form['nodes'][0].split(" "))
946 946 resp = cStringIO.StringIO()
947 947 for b in self.repo.branches(nodes):
948 948 resp.write(" ".join(map(hex, b)) + "\n")
949 949 resp = resp.getvalue()
950 950 req.httphdr("application/mercurial-0.1", length=len(resp))
951 951 req.write(resp)
952 952
953 953 def do_between(self, req):
954 954 if req.form.has_key('pairs'):
955 955 pairs = [map(bin, p.split("-"))
956 956 for p in req.form['pairs'][0].split(" ")]
957 957 resp = cStringIO.StringIO()
958 958 for b in self.repo.between(pairs):
959 959 resp.write(" ".join(map(hex, b)) + "\n")
960 960 resp = resp.getvalue()
961 961 req.httphdr("application/mercurial-0.1", length=len(resp))
962 962 req.write(resp)
963 963
964 964 def do_changegroup(self, req):
965 965 req.httphdr("application/mercurial-0.1")
966 966 nodes = []
967 967 if not self.allowpull:
968 968 return
969 969
970 970 if req.form.has_key('roots'):
971 971 nodes = map(bin, req.form['roots'][0].split(" "))
972 972
973 973 z = zlib.compressobj()
974 974 f = self.repo.changegroup(nodes, 'serve')
975 975 while 1:
976 976 chunk = f.read(4096)
977 977 if not chunk:
978 978 break
979 979 req.write(z.compress(chunk))
980 980
981 981 req.write(z.flush())
982 982
983 983 def do_changegroupsubset(self, req):
984 984 req.httphdr("application/mercurial-0.1")
985 985 bases = []
986 986 heads = []
987 987 if not self.allowpull:
988 988 return
989 989
990 990 if req.form.has_key('bases'):
991 991 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
992 992 if req.form.has_key('heads'):
993 993 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
994 994
995 995 z = zlib.compressobj()
996 996 f = self.repo.changegroupsubset(bases, heads, 'serve')
997 997 while 1:
998 998 chunk = f.read(4096)
999 999 if not chunk:
1000 1000 break
1001 1001 req.write(z.compress(chunk))
1002 1002
1003 1003 req.write(z.flush())
1004 1004
1005 1005 def do_archive(self, req):
1006 1006 type_ = req.form['type'][0]
1007 1007 allowed = self.configlist("web", "allow_archive")
1008 1008 if (type_ in self.archives and (type_ in allowed or
1009 1009 self.configbool("web", "allow" + type_, False))):
1010 1010 self.archive(req, req.form['node'][0], type_)
1011 1011 return
1012 1012
1013 1013 req.write(self.t("error"))
1014 1014
1015 1015 def do_static(self, req):
1016 1016 fname = req.form['file'][0]
1017 1017 # a repo owner may set web.static in .hg/hgrc to get any file
1018 1018 # readable by the user running the CGI script
1019 1019 static = self.config("web", "static",
1020 1020 os.path.join(self.templatepath, "static"),
1021 1021 untrusted=False)
1022 1022 req.write(staticfile(static, fname, req)
1023 1023 or self.t("error", error="%r not found" % fname))
1024 1024
1025 1025 def do_capabilities(self, req):
1026 1026 caps = ['lookup', 'changegroupsubset']
1027 1027 if self.configbool('server', 'uncompressed'):
1028 1028 caps.append('stream=%d' % self.repo.changelog.version)
1029 1029 # XXX: make configurable and/or share code with do_unbundle:
1030 1030 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1031 1031 if unbundleversions:
1032 1032 caps.append('unbundle=%s' % ','.join(unbundleversions))
1033 1033 resp = ' '.join(caps)
1034 1034 req.httphdr("application/mercurial-0.1", length=len(resp))
1035 1035 req.write(resp)
1036 1036
1037 1037 def check_perm(self, req, op, default):
1038 1038 '''check permission for operation based on user auth.
1039 1039 return true if op allowed, else false.
1040 1040 default is policy to use if no config given.'''
1041 1041
1042 1042 user = req.env.get('REMOTE_USER')
1043 1043
1044 1044 deny = self.configlist('web', 'deny_' + op)
1045 1045 if deny and (not user or deny == ['*'] or user in deny):
1046 1046 return False
1047 1047
1048 1048 allow = self.configlist('web', 'allow_' + op)
1049 1049 return (allow and (allow == ['*'] or user in allow)) or default
1050 1050
1051 1051 def do_unbundle(self, req):
1052 1052 def bail(response, headers={}):
1053 1053 length = int(req.env['CONTENT_LENGTH'])
1054 1054 for s in util.filechunkiter(req, limit=length):
1055 1055 # drain incoming bundle, else client will not see
1056 1056 # response when run outside cgi script
1057 1057 pass
1058 1058 req.httphdr("application/mercurial-0.1", headers=headers)
1059 1059 req.write('0\n')
1060 1060 req.write(response)
1061 1061
1062 1062 # require ssl by default, auth info cannot be sniffed and
1063 1063 # replayed
1064 1064 ssl_req = self.configbool('web', 'push_ssl', True)
1065 1065 if ssl_req:
1066 1066 if not req.env.get('HTTPS'):
1067 1067 bail(_('ssl required\n'))
1068 1068 return
1069 1069 proto = 'https'
1070 1070 else:
1071 1071 proto = 'http'
1072 1072
1073 1073 # do not allow push unless explicitly allowed
1074 1074 if not self.check_perm(req, 'push', False):
1075 1075 bail(_('push not authorized\n'),
1076 1076 headers={'status': '401 Unauthorized'})
1077 1077 return
1078 1078
1079 1079 their_heads = req.form['heads'][0].split(' ')
1080 1080
1081 1081 def check_heads():
1082 1082 heads = map(hex, self.repo.heads())
1083 1083 return their_heads == [hex('force')] or their_heads == heads
1084 1084
1085 1085 # fail early if possible
1086 1086 if not check_heads():
1087 1087 bail(_('unsynced changes\n'))
1088 1088 return
1089 1089
1090 1090 req.httphdr("application/mercurial-0.1")
1091 1091
1092 1092 # do not lock repo until all changegroup data is
1093 1093 # streamed. save to temporary file.
1094 1094
1095 1095 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1096 1096 fp = os.fdopen(fd, 'wb+')
1097 1097 try:
1098 1098 length = int(req.env['CONTENT_LENGTH'])
1099 1099 for s in util.filechunkiter(req, limit=length):
1100 1100 fp.write(s)
1101 1101
1102 1102 try:
1103 1103 lock = self.repo.lock()
1104 1104 try:
1105 1105 if not check_heads():
1106 1106 req.write('0\n')
1107 1107 req.write(_('unsynced changes\n'))
1108 1108 return
1109 1109
1110 1110 fp.seek(0)
1111 1111 header = fp.read(6)
1112 1112 if not header.startswith("HG"):
1113 1113 # old client with uncompressed bundle
1114 1114 def generator(f):
1115 1115 yield header
1116 1116 for chunk in f:
1117 1117 yield chunk
1118 1118 elif not header.startswith("HG10"):
1119 1119 req.write("0\n")
1120 1120 req.write(_("unknown bundle version\n"))
1121 1121 return
1122 1122 elif header == "HG10GZ":
1123 1123 def generator(f):
1124 1124 zd = zlib.decompressobj()
1125 1125 for chunk in f:
1126 1126 yield zd.decompress(chunk)
1127 1127 elif header == "HG10BZ":
1128 1128 def generator(f):
1129 1129 zd = bz2.BZ2Decompressor()
1130 1130 zd.decompress("BZ")
1131 1131 for chunk in f:
1132 1132 yield zd.decompress(chunk)
1133 1133 elif header == "HG10UN":
1134 1134 def generator(f):
1135 1135 for chunk in f:
1136 1136 yield chunk
1137 1137 else:
1138 1138 req.write("0\n")
1139 1139 req.write(_("unknown bundle compression type\n"))
1140 1140 return
1141 1141 gen = generator(util.filechunkiter(fp, 4096))
1142 1142
1143 1143 # send addchangegroup output to client
1144 1144
1145 1145 old_stdout = sys.stdout
1146 1146 sys.stdout = cStringIO.StringIO()
1147 1147
1148 1148 try:
1149 1149 url = 'remote:%s:%s' % (proto,
1150 1150 req.env.get('REMOTE_HOST', ''))
1151 1151 try:
1152 1152 ret = self.repo.addchangegroup(
1153 1153 util.chunkbuffer(gen), 'serve', url)
1154 1154 except util.Abort, inst:
1155 1155 sys.stdout.write("abort: %s\n" % inst)
1156 1156 ret = 0
1157 1157 finally:
1158 1158 val = sys.stdout.getvalue()
1159 1159 sys.stdout = old_stdout
1160 1160 req.write('%d\n' % ret)
1161 1161 req.write(val)
1162 1162 finally:
1163 1163 lock.release()
1164 1164 except (OSError, IOError), inst:
1165 1165 req.write('0\n')
1166 1166 filename = getattr(inst, 'filename', '')
1167 1167 # Don't send our filesystem layout to the client
1168 1168 if filename.startswith(self.repo.root):
1169 1169 filename = filename[len(self.repo.root)+1:]
1170 1170 else:
1171 1171 filename = ''
1172 1172 error = getattr(inst, 'strerror', 'Unknown error')
1173 1173 req.write('%s: %s\n' % (error, filename))
1174 1174 finally:
1175 1175 fp.close()
1176 1176 os.unlink(tempname)
1177 1177
1178 1178 def do_stream_out(self, req):
1179 1179 req.httphdr("application/mercurial-0.1")
1180 1180 streamclone.stream_out(self.repo, req)
@@ -1,290 +1,297 b''
1 1 # templater.py - template expansion for output
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 i18n import _
9 9 from node import *
10 10 import cgi, re, sys, os, time, urllib, util, textwrap
11 11
12 12 def parsestring(s, quoted=True):
13 13 '''parse a string using simple c-like syntax.
14 14 string must be in quotes if quoted is True.'''
15 15 if quoted:
16 16 if len(s) < 2 or s[0] != s[-1]:
17 17 raise SyntaxError(_('unmatched quotes'))
18 18 return s[1:-1].decode('string_escape')
19 19
20 20 return s.decode('string_escape')
21 21
22 22 class templater(object):
23 23 '''template expansion engine.
24 24
25 25 template expansion works like this. a map file contains key=value
26 26 pairs. if value is quoted, it is treated as string. otherwise, it
27 27 is treated as name of template file.
28 28
29 29 templater is asked to expand a key in map. it looks up key, and
30 30 looks for strings like this: {foo}. it expands {foo} by looking up
31 31 foo in map, and substituting it. expansion is recursive: it stops
32 32 when there is no more {foo} to replace.
33 33
34 34 expansion also allows formatting and filtering.
35 35
36 36 format uses key to expand each item in list. syntax is
37 37 {key%format}.
38 38
39 39 filter uses function to transform value. syntax is
40 40 {key|filter1|filter2|...}.'''
41 41
42 42 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))"
43 43 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]")
44 44
45 45 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
46 46 '''set up template engine.
47 47 mapfile is name of file to read map definitions from.
48 48 filters is dict of functions. each transforms a value into another.
49 49 defaults is dict of default map definitions.'''
50 50 self.mapfile = mapfile or 'template'
51 51 self.cache = cache.copy()
52 52 self.map = {}
53 53 self.base = (mapfile and os.path.dirname(mapfile)) or ''
54 54 self.filters = filters
55 55 self.defaults = defaults
56 56
57 57 if not mapfile:
58 58 return
59 59 i = 0
60 60 for l in file(mapfile):
61 61 l = l.strip()
62 62 i += 1
63 63 if not l or l[0] in '#;': continue
64 64 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
65 65 if m:
66 66 key, val = m.groups()
67 67 if val[0] in "'\"":
68 68 try:
69 69 self.cache[key] = parsestring(val)
70 70 except SyntaxError, inst:
71 71 raise SyntaxError('%s:%s: %s' %
72 72 (mapfile, i, inst.args[0]))
73 73 else:
74 74 self.map[key] = os.path.join(self.base, val)
75 75 else:
76 76 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
77 77
78 78 def __contains__(self, key):
79 79 return key in self.cache or key in self.map
80 80
81 81 def __call__(self, t, **map):
82 82 '''perform expansion.
83 83 t is name of map element to expand.
84 84 map is added elements to use during expansion.'''
85 85 if not self.cache.has_key(t):
86 86 try:
87 87 self.cache[t] = file(self.map[t]).read()
88 88 except IOError, inst:
89 89 raise IOError(inst.args[0], _('template file %s: %s') %
90 90 (self.map[t], inst.args[1]))
91 91 tmpl = self.cache[t]
92 92
93 93 while tmpl:
94 94 m = self.template_re.search(tmpl)
95 95 if not m:
96 96 yield tmpl
97 97 break
98 98
99 99 start, end = m.span(0)
100 100 key, format, fl = m.groups()
101 101
102 102 if start:
103 103 yield tmpl[:start]
104 104 tmpl = tmpl[end:]
105 105
106 106 if key in map:
107 107 v = map[key]
108 108 else:
109 109 v = self.defaults.get(key, "")
110 110 if callable(v):
111 111 v = v(**map)
112 112 if format:
113 113 if not hasattr(v, '__iter__'):
114 114 raise SyntaxError(_("Error expanding '%s%s'")
115 115 % (key, format))
116 116 lm = map.copy()
117 117 for i in v:
118 118 lm.update(i)
119 119 yield self(format, **lm)
120 120 else:
121 121 if fl:
122 122 for f in fl.split("|")[1:]:
123 123 v = self.filters[f](v)
124 124 yield v
125 125
126 126 agescales = [("second", 1),
127 127 ("minute", 60),
128 128 ("hour", 3600),
129 129 ("day", 3600 * 24),
130 130 ("week", 3600 * 24 * 7),
131 131 ("month", 3600 * 24 * 30),
132 132 ("year", 3600 * 24 * 365)]
133 133
134 134 agescales.reverse()
135 135
136 136 def age(date):
137 137 '''turn a (timestamp, tzoff) tuple into an age string.'''
138 138
139 139 def plural(t, c):
140 140 if c == 1:
141 141 return t
142 142 return t + "s"
143 143 def fmt(t, c):
144 144 return "%d %s" % (c, plural(t, c))
145 145
146 146 now = time.time()
147 147 then = date[0]
148 148 delta = max(1, int(now - then))
149 149
150 150 for t, s in agescales:
151 151 n = delta / s
152 152 if n >= 2 or s == 1:
153 153 return fmt(t, n)
154 154
155 155 def stringify(thing):
156 156 '''turn nested template iterator into string.'''
157 157 if hasattr(thing, '__iter__'):
158 158 return "".join([stringify(t) for t in thing if t is not None])
159 159 return str(thing)
160 160
161 161 para_re = None
162 162 space_re = None
163 163
164 164 def fill(text, width):
165 165 '''fill many paragraphs.'''
166 166 global para_re, space_re
167 167 if para_re is None:
168 168 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
169 169 space_re = re.compile(r' +')
170 170
171 171 def findparas():
172 172 start = 0
173 173 while True:
174 174 m = para_re.search(text, start)
175 175 if not m:
176 176 w = len(text)
177 177 while w > start and text[w-1].isspace(): w -= 1
178 178 yield text[start:w], text[w:]
179 179 break
180 180 yield text[start:m.start(0)], m.group(1)
181 181 start = m.end(1)
182 182
183 183 return "".join([space_re.sub(' ', textwrap.fill(para, width)) + rest
184 184 for para, rest in findparas()])
185 185
186 186 def firstline(text):
187 187 '''return the first line of text'''
188 188 try:
189 189 return text.splitlines(1)[0].rstrip('\r\n')
190 190 except IndexError:
191 191 return ''
192 192
193 193 def isodate(date):
194 194 '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.'''
195 195 return util.datestr(date, format='%Y-%m-%d %H:%M')
196 196
197 197 def hgdate(date):
198 198 '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.'''
199 199 return "%d %d" % date
200 200
201 201 def nl2br(text):
202 202 '''replace raw newlines with xhtml line breaks.'''
203 203 return text.replace('\n', '<br/>\n')
204 204
205 205 def obfuscate(text):
206 206 text = unicode(text, util._encoding, 'replace')
207 207 return ''.join(['&#%d;' % ord(c) for c in text])
208 208
209 209 def domain(author):
210 210 '''get domain of author, or empty string if none.'''
211 211 f = author.find('@')
212 212 if f == -1: return ''
213 213 author = author[f+1:]
214 214 f = author.find('>')
215 215 if f >= 0: author = author[:f]
216 216 return author
217 217
218 218 def email(author):
219 219 '''get email of author.'''
220 220 r = author.find('>')
221 221 if r == -1: r = None
222 222 return author[author.find('<')+1:r]
223 223
224 224 def person(author):
225 225 '''get name of author, or else username.'''
226 226 f = author.find('<')
227 227 if f == -1: return util.shortuser(author)
228 228 return author[:f].rstrip()
229 229
230 230 def shortdate(date):
231 231 '''turn (timestamp, tzoff) tuple into iso 8631 date.'''
232 232 return util.datestr(date, format='%Y-%m-%d', timezone=False)
233 233
234 234 def indent(text, prefix):
235 235 '''indent each non-empty line of text after first with prefix.'''
236 236 lines = text.splitlines()
237 237 num_lines = len(lines)
238 238 def indenter():
239 239 for i in xrange(num_lines):
240 240 l = lines[i]
241 241 if i and l.strip():
242 242 yield prefix
243 243 yield l
244 244 if i < num_lines - 1 or text.endswith('\n'):
245 245 yield '\n'
246 246 return "".join(indenter())
247 247
248 def permissions(flags):
249 if "l" in flags:
250 return "lrwxrwxrwx"
251 if "x" in flags:
252 return "-rwxr-xr-x"
253 return "-rw-r--r--"
254
248 255 common_filters = {
249 256 "addbreaks": nl2br,
250 257 "basename": os.path.basename,
251 258 "age": age,
252 259 "date": lambda x: util.datestr(x),
253 260 "domain": domain,
254 261 "email": email,
255 262 "escape": lambda x: cgi.escape(x, True),
256 263 "fill68": lambda x: fill(x, width=68),
257 264 "fill76": lambda x: fill(x, width=76),
258 265 "firstline": firstline,
259 266 "tabindent": lambda x: indent(x, '\t'),
260 267 "hgdate": hgdate,
261 268 "isodate": isodate,
262 269 "obfuscate": obfuscate,
263 "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
270 "permissions": permissions,
264 271 "person": person,
265 272 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
266 273 "short": lambda x: x[:12],
267 274 "shortdate": shortdate,
268 275 "stringify": stringify,
269 276 "strip": lambda x: x.strip(),
270 277 "urlescape": lambda x: urllib.quote(x),
271 278 "user": lambda x: util.shortuser(x),
272 279 "stringescape": lambda x: x.encode('string_escape'),
273 280 }
274 281
275 282 def templatepath(name=None):
276 283 '''return location of template file or directory (if no name).
277 284 returns None if not found.'''
278 285
279 286 # executable version (py2exe) doesn't support __file__
280 287 if hasattr(sys, 'frozen'):
281 288 module = sys.executable
282 289 else:
283 290 module = __file__
284 291 for f in 'templates', '../templates':
285 292 fl = f.split('/')
286 293 if name: fl.append(name)
287 294 p = os.path.join(os.path.dirname(module), *fl)
288 295 if (name and os.path.exists(p)) or os.path.isdir(p):
289 296 return os.path.normpath(p)
290 297
@@ -1,53 +1,70 b''
1 1 #!/bin/sh
2 2
3 3 hg init
4 4 echo foo > foo
5 echo "# should fail - foo is not managed"
6 hg mv foo bar
7 hg st -A
5 8 hg add foo
9 echo "# dry-run; print a warning that this is not a real copy; foo is added"
10 hg mv --dry-run foo bar
11 hg st -A
12 echo "# should print a warning that this is not a real copy; bar is added"
13 hg mv foo bar
14 hg st -A
15 echo "# should print a warning that this is not a real copy; foo is added"
16 hg cp bar foo
17 hg rm -f bar
18 rm bar
19 hg st -A
6 20 hg commit -m1 -d"0 0"
7 21
22 echo "# dry-run; should show that foo is clean"
23 hg copy --dry-run foo bar
24 hg st -A
8 25 echo "# should show copy"
9 26 hg copy foo bar
10 hg debugstate|grep '^copy'
27 hg st -C
11 28
12 29 echo "# shouldn't show copy"
13 30 hg commit -m2 -d"0 0"
14 hg debugstate|grep '^copy'
31 hg st -C
15 32
16 33 echo "# should match"
17 34 hg debugindex .hg/store/data/foo.i
18 35 hg debugrename bar
19 36
20 37 echo bleah > foo
21 38 echo quux > bar
22 39 hg commit -m3 -d"0 0"
23 40
24 41 echo "# should not be renamed"
25 42 hg debugrename bar
26 43
27 44 hg copy -f foo bar
28 45 echo "# should show copy"
29 hg debugstate|grep '^copy'
46 hg st -C
30 47 hg commit -m3 -d"0 0"
31 48
32 49 echo "# should show no parents for tip"
33 50 hg debugindex .hg/store/data/bar.i
34 51 echo "# should match"
35 52 hg debugindex .hg/store/data/foo.i
36 53 hg debugrename bar
37 54
38 55 echo "# should show no copies"
39 hg debugstate|grep '^copy'
56 hg st -C
40 57
41 58 echo "# copy --after on an added file"
42 59 cp bar baz
43 60 hg add baz
44 61 hg cp -A bar baz
45 62 hg st -C
46 63
47 64 echo "# foo was clean:"
48 65 hg st -AC foo
49 66 echo "# but it's considered modified after a copy --after --force"
50 67 hg copy -Af bar foo
51 68 hg st -AC foo
52 69
53 70 exit 0
@@ -1,30 +1,47 b''
1 # should fail - foo is not managed
2 foo: not copying - file is not managed
3 abort: no files to copy
4 ? foo
5 # dry-run; print a warning that this is not a real copy; foo is added
6 foo was marked for addition. bar will not be committed as a copy.
7 A foo
8 # should print a warning that this is not a real copy; bar is added
9 foo was marked for addition. bar will not be committed as a copy.
10 A bar
11 # should print a warning that this is not a real copy; foo is added
12 bar was marked for addition. foo will not be committed as a copy.
13 A foo
14 # dry-run; should show that foo is clean
15 C foo
1 16 # should show copy
2 copy: foo -> bar
17 A bar
18 foo
3 19 # shouldn't show copy
4 20 # should match
5 21 rev offset length base linkrev nodeid p1 p2
6 22 0 0 5 0 0 2ed2a3912a0b 000000000000 000000000000
7 23 bar renamed from foo:2ed2a3912a0b24502043eae84ee4b279c18b90dd
8 24 # should not be renamed
9 25 bar not renamed
10 26 # should show copy
11 copy: foo -> bar
27 M bar
28 foo
12 29 # should show no parents for tip
13 30 rev offset length base linkrev nodeid p1 p2
14 31 0 0 69 0 1 6ca237634e1f 000000000000 000000000000
15 32 1 69 6 1 2 7a1ff8e75f5b 6ca237634e1f 000000000000
16 33 2 75 82 1 3 243dfe60f3d9 000000000000 000000000000
17 34 # should match
18 35 rev offset length base linkrev nodeid p1 p2
19 36 0 0 5 0 0 2ed2a3912a0b 000000000000 000000000000
20 37 1 5 7 1 2 dd12c926cf16 2ed2a3912a0b 000000000000
21 38 bar renamed from foo:dd12c926cf165e3eb4cf87b084955cb617221c17
22 39 # should show no copies
23 40 # copy --after on an added file
24 41 A baz
25 42 bar
26 43 # foo was clean:
27 44 C foo
28 45 # but it's considered modified after a copy --after --force
29 46 M foo
30 47 bar
@@ -1,211 +1,210 b''
1 1 #!/bin/sh
2 2
3 3 hg init
4 4 mkdir d1 d1/d11 d2
5 5 echo d1/a > d1/a
6 6 echo d1/ba > d1/ba
7 7 echo d1/a1 > d1/d11/a1
8 8 echo d1/b > d1/b
9 9 echo d2/b > d2/b
10 10 hg add d1/a d1/b d1/ba d1/d11/a1 d2/b
11 11 hg commit -m "1" -d "1000000 0"
12 12
13 13 echo "# rename a single file"
14 14 hg rename d1/d11/a1 d2/c
15 hg status
15 hg status -C
16 16 hg update -C
17 17
18 18 echo "# rename --after a single file"
19 19 mv d1/d11/a1 d2/c
20 20 hg rename --after d1/d11/a1 d2/c
21 hg status
21 hg status -C
22 22 hg update -C
23 23
24 24 echo "# move a single file to an existing directory"
25 25 hg rename d1/d11/a1 d2
26 hg status
26 hg status -C
27 27 hg update -C
28 28
29 29 echo "# move --after a single file to an existing directory"
30 30 mv d1/d11/a1 d2
31 31 hg rename --after d1/d11/a1 d2
32 hg status
32 hg status -C
33 33 hg update -C
34 34
35 35 echo "# rename a file using a relative path"
36 36 (cd d1/d11; hg rename ../../d2/b e)
37 hg status
37 hg status -C
38 38 hg update -C
39 39
40 40 echo "# rename --after a file using a relative path"
41 41 (cd d1/d11; mv ../../d2/b e; hg rename --after ../../d2/b e)
42 hg status
42 hg status -C
43 43 hg update -C
44 44
45 45 echo "# rename directory d1 as d3"
46 46 hg rename d1/ d3
47 hg status
47 hg status -C
48 48 hg update -C
49 49
50 50 echo "# rename --after directory d1 as d3"
51 51 mv d1 d3
52 52 hg rename --after d1 d3
53 hg status
53 hg status -C
54 54 hg update -C
55 55
56 56 echo "# move a directory using a relative path"
57 57 (cd d2; mkdir d3; hg rename ../d1/d11 d3)
58 hg status
58 hg status -C
59 59 hg update -C
60 60
61 61 echo "# move --after a directory using a relative path"
62 62 (cd d2; mkdir d3; mv ../d1/d11 d3; hg rename --after ../d1/d11 d3)
63 hg status
63 hg status -C
64 64 hg update -C
65 65
66 66 echo "# move directory d1/d11 to an existing directory d2 (removes empty d1)"
67 67 hg rename d1/d11/ d2
68 hg status
68 hg status -C
69 69 hg update -C
70 70
71 71 echo "# move directories d1 and d2 to a new directory d3"
72 72 mkdir d3
73 73 hg rename d1 d2 d3
74 hg status
74 hg status -C
75 75 hg update -C
76 76
77 77 echo "# move --after directories d1 and d2 to a new directory d3"
78 78 mkdir d3
79 79 mv d1 d2 d3
80 80 hg rename --after d1 d2 d3
81 hg status
81 hg status -C
82 82 hg update -C
83 83
84 84 echo "# move everything under directory d1 to existing directory d2, do not"
85 85 echo "# overwrite existing files (d2/b)"
86 86 hg rename d1/* d2
87 hg status
87 hg status -C
88 88 diff d1/b d2/b
89 89 hg update -C
90 90
91 91 echo "# attempt to move potentially more than one file into a non-existent"
92 92 echo "# directory"
93 93 hg rename 'glob:d1/**' dx
94 94
95 95 echo "# move every file under d1 to d2/d21 (glob)"
96 96 mkdir d2/d21
97 97 hg rename 'glob:d1/**' d2/d21
98 hg status
98 hg status -C
99 99 hg update -C
100 100
101 101 echo "# move --after some files under d1 to d2/d21 (glob)"
102 102 mkdir d2/d21
103 103 mv d1/a d1/d11/a1 d2/d21
104 104 hg rename --after 'glob:d1/**' d2/d21
105 hg status
105 hg status -C
106 106 hg update -C
107 107
108 108 echo "# move every file under d1 starting with an 'a' to d2/d21 (regexp)"
109 109 mkdir d2/d21
110 110 hg rename 're:d1/([^a][^/]*/)*a.*' d2/d21
111 hg status
111 hg status -C
112 112 hg update -C
113 113
114 114 echo "# attempt to overwrite an existing file"
115 115 echo "ca" > d1/ca
116 116 hg rename d1/ba d1/ca
117 hg status
117 hg status -C
118 118 hg update -C
119 119
120 120 echo "# forced overwrite of an existing file"
121 121 echo "ca" > d1/ca
122 122 hg rename --force d1/ba d1/ca
123 hg status
123 hg status -C
124 124 hg update -C
125 125
126 126 echo "# replace a symlink with a file"
127 127 ln -s ba d1/ca
128 128 hg rename --force d1/ba d1/ca
129 hg status
129 hg status -C
130 130 hg update -C
131 131
132 132 echo "# do not copy more than one source file to the same destination file"
133 133 mkdir d3
134 134 hg rename d1/* d2/* d3
135 hg status
135 hg status -C
136 136 hg update -C
137 137
138 138 echo "# move a whole subtree with \"hg rename .\""
139 139 mkdir d3
140 140 (cd d1; hg rename . ../d3)
141 hg status
141 hg status -C
142 142 hg update -C
143 143
144 144 echo "# move a whole subtree with \"hg rename --after .\""
145 145 mkdir d3
146 146 mv d1/* d3
147 147 (cd d1; hg rename --after . ../d3)
148 hg status
148 hg status -C
149 149 hg update -C
150 150
151 151 echo "# move the parent tree with \"hg rename ..\""
152 152 (cd d1/d11; hg rename .. ../../d3)
153 hg status
153 hg status -C
154 154 hg update -C
155 155
156 156 echo "# skip removed files"
157 157 hg remove d1/b
158 158 hg rename d1 d3
159 hg status
159 hg status -C
160 160 hg update -C
161 161
162 162 echo "# transitive rename"
163 163 hg rename d1/b d1/bb
164 164 hg rename d1/bb d1/bc
165 hg status
165 hg status -C
166 166 hg update -C
167 167
168 168 echo "# transitive rename --after"
169 169 hg rename d1/b d1/bb
170 170 mv d1/bb d1/bc
171 171 hg rename --after d1/bb d1/bc
172 hg status
172 hg status -C
173 173 hg update -C
174 174
175 175 echo "# idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)"
176 176 hg rename d1/b d1/bb
177 177 echo "some stuff added to d1/bb" >> d1/bb
178 178 hg rename d1/bb d1/b
179 hg status
180 hg debugstate | grep copy
179 hg status -C
181 180 hg update -C
182 181
183 182 echo "# check illegal path components"
184 183
185 184 hg rename d1/d11/a1 .hg/foo
186 hg status
185 hg status -C
187 186 hg rename d1/d11/a1 ../foo
188 hg status
187 hg status -C
189 188
190 189 mv d1/d11/a1 .hg/foo
191 190 hg rename --after d1/d11/a1 .hg/foo
192 hg status
191 hg status -C
193 192 hg update -C
194 193 rm .hg/foo
195 194
196 195 hg rename d1/d11/a1 .hg
197 hg status
196 hg status -C
198 197 hg rename d1/d11/a1 ..
199 hg status
198 hg status -C
200 199
201 200 mv d1/d11/a1 .hg
202 201 hg rename --after d1/d11/a1 .hg
203 hg status
202 hg status -C
204 203 hg update -C
205 204 rm .hg/a1
206 205
207 206 (cd d1/d11; hg rename ../../d2/b ../../.hg/foo)
208 hg status
207 hg status -C
209 208 (cd d1/d11; hg rename ../../d2/b ../../../foo)
210 hg status
209 hg status -C
211 210
@@ -1,297 +1,358 b''
1 1 # rename a single file
2 2 A d2/c
3 d1/d11/a1
3 4 R d1/d11/a1
4 5 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
5 6 # rename --after a single file
6 7 A d2/c
8 d1/d11/a1
7 9 R d1/d11/a1
8 10 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
9 11 # move a single file to an existing directory
10 12 A d2/a1
13 d1/d11/a1
11 14 R d1/d11/a1
12 15 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
13 16 # move --after a single file to an existing directory
14 17 A d2/a1
18 d1/d11/a1
15 19 R d1/d11/a1
16 20 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
17 21 # rename a file using a relative path
18 22 A d1/d11/e
23 d2/b
19 24 R d2/b
20 25 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
21 26 # rename --after a file using a relative path
22 27 A d1/d11/e
28 d2/b
23 29 R d2/b
24 30 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
25 31 # rename directory d1 as d3
26 32 copying d1/a to d3/a
27 33 copying d1/b to d3/b
28 34 copying d1/ba to d3/ba
29 35 copying d1/d11/a1 to d3/d11/a1
30 36 removing d1/a
31 37 removing d1/b
32 38 removing d1/ba
33 39 removing d1/d11/a1
34 40 A d3/a
41 d1/a
35 42 A d3/b
43 d1/b
36 44 A d3/ba
45 d1/ba
37 46 A d3/d11/a1
47 d1/d11/a1
38 48 R d1/a
39 49 R d1/b
40 50 R d1/ba
41 51 R d1/d11/a1
42 52 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
43 53 # rename --after directory d1 as d3
44 54 copying d1/a to d3/a
45 55 copying d1/b to d3/b
46 56 copying d1/ba to d3/ba
47 57 copying d1/d11/a1 to d3/d11/a1
48 58 removing d1/a
49 59 removing d1/b
50 60 removing d1/ba
51 61 removing d1/d11/a1
52 62 A d3/a
63 d1/a
53 64 A d3/b
65 d1/b
54 66 A d3/ba
67 d1/ba
55 68 A d3/d11/a1
69 d1/d11/a1
56 70 R d1/a
57 71 R d1/b
58 72 R d1/ba
59 73 R d1/d11/a1
60 74 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
61 75 # move a directory using a relative path
62 76 copying ../d1/d11/a1 to d3/d11/a1
63 77 removing ../d1/d11/a1
64 78 A d2/d3/d11/a1
79 d1/d11/a1
65 80 R d1/d11/a1
66 81 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
67 82 # move --after a directory using a relative path
68 83 copying ../d1/d11/a1 to d3/d11/a1
69 84 removing ../d1/d11/a1
70 85 A d2/d3/d11/a1
86 d1/d11/a1
71 87 R d1/d11/a1
72 88 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
73 89 # move directory d1/d11 to an existing directory d2 (removes empty d1)
74 90 copying d1/d11/a1 to d2/d11/a1
75 91 removing d1/d11/a1
76 92 A d2/d11/a1
93 d1/d11/a1
77 94 R d1/d11/a1
78 95 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
79 96 # move directories d1 and d2 to a new directory d3
80 97 copying d1/a to d3/d1/a
81 98 copying d1/b to d3/d1/b
82 99 copying d1/ba to d3/d1/ba
83 100 copying d1/d11/a1 to d3/d1/d11/a1
84 101 copying d2/b to d3/d2/b
85 102 removing d1/a
86 103 removing d1/b
87 104 removing d1/ba
88 105 removing d1/d11/a1
89 106 removing d2/b
90 107 A d3/d1/a
108 d1/a
91 109 A d3/d1/b
110 d1/b
92 111 A d3/d1/ba
112 d1/ba
93 113 A d3/d1/d11/a1
114 d1/d11/a1
94 115 A d3/d2/b
116 d2/b
95 117 R d1/a
96 118 R d1/b
97 119 R d1/ba
98 120 R d1/d11/a1
99 121 R d2/b
100 122 5 files updated, 0 files merged, 5 files removed, 0 files unresolved
101 123 # move --after directories d1 and d2 to a new directory d3
102 124 copying d1/a to d3/d1/a
103 125 copying d1/b to d3/d1/b
104 126 copying d1/ba to d3/d1/ba
105 127 copying d1/d11/a1 to d3/d1/d11/a1
106 128 copying d2/b to d3/d2/b
107 129 removing d1/a
108 130 removing d1/b
109 131 removing d1/ba
110 132 removing d1/d11/a1
111 133 removing d2/b
112 134 A d3/d1/a
135 d1/a
113 136 A d3/d1/b
137 d1/b
114 138 A d3/d1/ba
139 d1/ba
115 140 A d3/d1/d11/a1
141 d1/d11/a1
116 142 A d3/d2/b
143 d2/b
117 144 R d1/a
118 145 R d1/b
119 146 R d1/ba
120 147 R d1/d11/a1
121 148 R d2/b
122 149 5 files updated, 0 files merged, 5 files removed, 0 files unresolved
123 150 # move everything under directory d1 to existing directory d2, do not
124 151 # overwrite existing files (d2/b)
125 152 d2/b: not overwriting - file exists
126 153 copying d1/d11/a1 to d2/d11/a1
127 154 removing d1/d11/a1
128 155 A d2/a
156 d1/a
129 157 A d2/ba
158 d1/ba
130 159 A d2/d11/a1
160 d1/d11/a1
131 161 R d1/a
132 162 R d1/ba
133 163 R d1/d11/a1
134 164 1c1
135 165 < d1/b
136 166 ---
137 167 > d2/b
138 168 3 files updated, 0 files merged, 3 files removed, 0 files unresolved
139 169 # attempt to move potentially more than one file into a non-existent
140 170 # directory
141 171 abort: with multiple sources, destination must be an existing directory
142 172 # move every file under d1 to d2/d21 (glob)
143 173 copying d1/a to d2/d21/a
144 174 copying d1/b to d2/d21/b
145 175 copying d1/ba to d2/d21/ba
146 176 copying d1/d11/a1 to d2/d21/a1
147 177 removing d1/a
148 178 removing d1/b
149 179 removing d1/ba
150 180 removing d1/d11/a1
151 181 A d2/d21/a
182 d1/a
152 183 A d2/d21/a1
184 d1/d11/a1
153 185 A d2/d21/b
186 d1/b
154 187 A d2/d21/ba
188 d1/ba
155 189 R d1/a
156 190 R d1/b
157 191 R d1/ba
158 192 R d1/d11/a1
159 193 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
160 194 # move --after some files under d1 to d2/d21 (glob)
161 195 copying d1/a to d2/d21/a
162 196 copying d1/d11/a1 to d2/d21/a1
163 197 removing d1/a
164 198 removing d1/d11/a1
165 199 A d2/d21/a
200 d1/a
166 201 A d2/d21/a1
202 d1/d11/a1
167 203 R d1/a
168 204 R d1/d11/a1
169 205 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
170 206 # move every file under d1 starting with an 'a' to d2/d21 (regexp)
171 207 copying d1/a to d2/d21/a
172 208 copying d1/d11/a1 to d2/d21/a1
173 209 removing d1/a
174 210 removing d1/d11/a1
175 211 A d2/d21/a
212 d1/a
176 213 A d2/d21/a1
214 d1/d11/a1
177 215 R d1/a
178 216 R d1/d11/a1
179 217 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
180 218 # attempt to overwrite an existing file
181 219 d1/ca: not overwriting - file exists
182 220 ? d1/ca
183 221 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
184 222 # forced overwrite of an existing file
185 223 A d1/ca
224 d1/ba
186 225 R d1/ba
187 226 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
188 227 # replace a symlink with a file
189 228 A d1/ca
229 d1/ba
190 230 R d1/ba
191 231 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
192 232 # do not copy more than one source file to the same destination file
193 233 copying d1/d11/a1 to d3/d11/a1
194 234 d3/b: not overwriting - d2/b collides with d1/b
195 235 removing d1/d11/a1
196 236 A d3/a
237 d1/a
197 238 A d3/b
239 d1/b
198 240 A d3/ba
241 d1/ba
199 242 A d3/d11/a1
243 d1/d11/a1
200 244 R d1/a
201 245 R d1/b
202 246 R d1/ba
203 247 R d1/d11/a1
204 248 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
205 249 # move a whole subtree with "hg rename ."
206 250 copying a to ../d3/d1/a
207 251 copying b to ../d3/d1/b
208 252 copying ba to ../d3/d1/ba
209 253 copying d11/a1 to ../d3/d1/d11/a1
210 254 removing a
211 255 removing b
212 256 removing ba
213 257 removing d11/a1
214 258 A d3/d1/a
259 d1/a
215 260 A d3/d1/b
261 d1/b
216 262 A d3/d1/ba
263 d1/ba
217 264 A d3/d1/d11/a1
265 d1/d11/a1
218 266 R d1/a
219 267 R d1/b
220 268 R d1/ba
221 269 R d1/d11/a1
222 270 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
223 271 # move a whole subtree with "hg rename --after ."
224 272 copying a to ../d3/a
225 273 copying b to ../d3/b
226 274 copying ba to ../d3/ba
227 275 copying d11/a1 to ../d3/d11/a1
228 276 removing a
229 277 removing b
230 278 removing ba
231 279 removing d11/a1
232 280 A d3/a
281 d1/a
233 282 A d3/b
283 d1/b
234 284 A d3/ba
285 d1/ba
235 286 A d3/d11/a1
287 d1/d11/a1
236 288 R d1/a
237 289 R d1/b
238 290 R d1/ba
239 291 R d1/d11/a1
240 292 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
241 293 # move the parent tree with "hg rename .."
242 294 copying ../a to ../../d3/a
243 295 copying ../b to ../../d3/b
244 296 copying ../ba to ../../d3/ba
245 297 copying a1 to ../../d3/d11/a1
246 298 removing ../a
247 299 removing ../b
248 300 removing ../ba
249 301 removing a1
250 302 A d3/a
303 d1/a
251 304 A d3/b
305 d1/b
252 306 A d3/ba
307 d1/ba
253 308 A d3/d11/a1
309 d1/d11/a1
254 310 R d1/a
255 311 R d1/b
256 312 R d1/ba
257 313 R d1/d11/a1
258 314 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
259 315 # skip removed files
260 316 copying d1/a to d3/a
261 317 copying d1/ba to d3/ba
262 318 copying d1/d11/a1 to d3/d11/a1
263 319 removing d1/a
264 320 removing d1/ba
265 321 removing d1/d11/a1
266 322 A d3/a
323 d1/a
267 324 A d3/ba
325 d1/ba
268 326 A d3/d11/a1
327 d1/d11/a1
269 328 R d1/a
270 329 R d1/b
271 330 R d1/ba
272 331 R d1/d11/a1
273 332 4 files updated, 0 files merged, 3 files removed, 0 files unresolved
274 333 # transitive rename
275 334 A d1/bc
335 d1/b
276 336 R d1/b
277 337 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
278 338 # transitive rename --after
279 339 A d1/bc
340 d1/b
280 341 R d1/b
281 342 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
282 343 # idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)
283 344 M d1/b
284 345 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
285 346 # check illegal path components
286 347 abort: path contains illegal component: .hg/foo
287 348 abort: ../foo not under root
288 349 abort: path contains illegal component: .hg/foo
289 350 ! d1/d11/a1
290 351 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
291 352 abort: path contains illegal component: .hg/a1
292 353 abort: ../a1 not under root
293 354 abort: path contains illegal component: .hg/a1
294 355 ! d1/d11/a1
295 356 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
296 357 abort: path contains illegal component: .hg/foo
297 358 abort: ../../../foo not under root
General Comments 0
You need to be logged in to leave comments. Login now