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