##// END OF EJS Templates
wireproto: add known([id]) function...
Peter Arrenbrecht -
r13723:e615765f default
parent child Browse files
Show More
@@ -0,0 +1,37 b''
1
2 = Test the known() protocol function =
3
4 Create a test repository:
5
6 $ hg init repo
7 $ cd repo
8 $ touch a ; hg add a ; hg ci -ma
9 $ touch b ; hg add b ; hg ci -mb
10 $ touch c ; hg add c ; hg ci -mc
11 $ hg log --template '{node}\n'
12 991a3460af53952d10ec8a295d3d2cc2e5fa9690
13 0e067c57feba1a5694ca4844f05588bb1bf82342
14 3903775176ed42b1458a6281db4a0ccf4d9f287a
15 $ cd ..
16
17 Test locally:
18
19 $ hg debugknown repo 991a3460af53952d10ec8a295d3d2cc2e5fa9690 0e067c57feba1a5694ca4844f05588bb1bf82342 3903775176ed42b1458a6281db4a0ccf4d9f287a
20 111
21 $ hg debugknown repo 000a3460af53952d10ec8a295d3d2cc2e5fa9690 0e067c57feba1a5694ca4844f05588bb1bf82342 0003775176ed42b1458a6281db4a0ccf4d9f287a
22 010
23 $ hg debugknown repo
24
25
26 Test via HTTP:
27
28 $ hg serve -R repo -p $HGPORT -d --pid-file=hg.pid -E error.log -A access.log
29 $ cat hg.pid >> $DAEMON_PIDS
30 $ hg debugknown http://localhost:$HGPORT/ 991a3460af53952d10ec8a295d3d2cc2e5fa9690 0e067c57feba1a5694ca4844f05588bb1bf82342 3903775176ed42b1458a6281db4a0ccf4d9f287a
31 111
32 $ hg debugknown http://localhost:$HGPORT/ 000a3460af53952d10ec8a295d3d2cc2e5fa9690 0e067c57feba1a5694ca4844f05588bb1bf82342 0003775176ed42b1458a6281db4a0ccf4d9f287a
33 010
34 $ hg debugknown http://localhost:$HGPORT/
35
36 $ cat error.log
37
@@ -1,4815 +1,4829 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 of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 from node import hex, nullid, nullrev, short
8 from node import hex, bin, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _, gettext
11 11 import os, re, sys, difflib, time, tempfile
12 12 import hg, util, revlog, extensions, copies, error, bookmarks
13 13 import patch, help, mdiff, url, encoding, templatekw, discovery
14 14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
15 15 import merge as mergemod
16 16 import minirst, revset, templatefilters
17 17 import dagparser
18 18
19 19 # Commands start here, listed alphabetically
20 20
21 21 def add(ui, repo, *pats, **opts):
22 22 """add the specified files on the next commit
23 23
24 24 Schedule files to be version controlled and added to the
25 25 repository.
26 26
27 27 The files will be added to the repository at the next commit. To
28 28 undo an add before that, see :hg:`forget`.
29 29
30 30 If no names are given, add all files to the repository.
31 31
32 32 .. container:: verbose
33 33
34 34 An example showing how new (unknown) files are added
35 35 automatically by :hg:`add`::
36 36
37 37 $ ls
38 38 foo.c
39 39 $ hg status
40 40 ? foo.c
41 41 $ hg add
42 42 adding foo.c
43 43 $ hg status
44 44 A foo.c
45 45
46 46 Returns 0 if all files are successfully added.
47 47 """
48 48
49 49 m = cmdutil.match(repo, pats, opts)
50 50 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
51 51 opts.get('subrepos'), prefix="")
52 52 return rejected and 1 or 0
53 53
54 54 def addremove(ui, repo, *pats, **opts):
55 55 """add all new files, delete all missing files
56 56
57 57 Add all new files and remove all missing files from the
58 58 repository.
59 59
60 60 New files are ignored if they match any of the patterns in
61 61 ``.hgignore``. As with add, these changes take effect at the next
62 62 commit.
63 63
64 64 Use the -s/--similarity option to detect renamed files. With a
65 65 parameter greater than 0, this compares every removed file with
66 66 every added file and records those similar enough as renames. This
67 67 option takes a percentage between 0 (disabled) and 100 (files must
68 68 be identical) as its parameter. Detecting renamed files this way
69 69 can be expensive. After using this option, :hg:`status -C` can be
70 70 used to check which files were identified as moved or renamed.
71 71
72 72 Returns 0 if all files are successfully added.
73 73 """
74 74 try:
75 75 sim = float(opts.get('similarity') or 100)
76 76 except ValueError:
77 77 raise util.Abort(_('similarity must be a number'))
78 78 if sim < 0 or sim > 100:
79 79 raise util.Abort(_('similarity must be between 0 and 100'))
80 80 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
81 81
82 82 def annotate(ui, repo, *pats, **opts):
83 83 """show changeset information by line for each file
84 84
85 85 List changes in files, showing the revision id responsible for
86 86 each line
87 87
88 88 This command is useful for discovering when a change was made and
89 89 by whom.
90 90
91 91 Without the -a/--text option, annotate will avoid processing files
92 92 it detects as binary. With -a, annotate will annotate the file
93 93 anyway, although the results will probably be neither useful
94 94 nor desirable.
95 95
96 96 Returns 0 on success.
97 97 """
98 98 if opts.get('follow'):
99 99 # --follow is deprecated and now just an alias for -f/--file
100 100 # to mimic the behavior of Mercurial before version 1.5
101 101 opts['file'] = 1
102 102
103 103 datefunc = ui.quiet and util.shortdate or util.datestr
104 104 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
105 105
106 106 if not pats:
107 107 raise util.Abort(_('at least one filename or pattern is required'))
108 108
109 109 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
110 110 ('number', lambda x: str(x[0].rev())),
111 111 ('changeset', lambda x: short(x[0].node())),
112 112 ('date', getdate),
113 113 ('file', lambda x: x[0].path()),
114 114 ]
115 115
116 116 if (not opts.get('user') and not opts.get('changeset')
117 117 and not opts.get('date') and not opts.get('file')):
118 118 opts['number'] = 1
119 119
120 120 linenumber = opts.get('line_number') is not None
121 121 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
122 122 raise util.Abort(_('at least one of -n/-c is required for -l'))
123 123
124 124 funcmap = [func for op, func in opmap if opts.get(op)]
125 125 if linenumber:
126 126 lastfunc = funcmap[-1]
127 127 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
128 128
129 129 def bad(x, y):
130 130 raise util.Abort("%s: %s" % (x, y))
131 131
132 132 ctx = cmdutil.revsingle(repo, opts.get('rev'))
133 133 m = cmdutil.match(repo, pats, opts)
134 134 m.bad = bad
135 135 follow = not opts.get('no_follow')
136 136 for abs in ctx.walk(m):
137 137 fctx = ctx[abs]
138 138 if not opts.get('text') and util.binary(fctx.data()):
139 139 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
140 140 continue
141 141
142 142 lines = fctx.annotate(follow=follow, linenumber=linenumber)
143 143 pieces = []
144 144
145 145 for f in funcmap:
146 146 l = [f(n) for n, dummy in lines]
147 147 if l:
148 148 sized = [(x, encoding.colwidth(x)) for x in l]
149 149 ml = max([w for x, w in sized])
150 150 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
151 151
152 152 if pieces:
153 153 for p, l in zip(zip(*pieces), lines):
154 154 ui.write("%s: %s" % (" ".join(p), l[1]))
155 155
156 156 def archive(ui, repo, dest, **opts):
157 157 '''create an unversioned archive of a repository revision
158 158
159 159 By default, the revision used is the parent of the working
160 160 directory; use -r/--rev to specify a different revision.
161 161
162 162 The archive type is automatically detected based on file
163 163 extension (or override using -t/--type).
164 164
165 165 Valid types are:
166 166
167 167 :``files``: a directory full of files (default)
168 168 :``tar``: tar archive, uncompressed
169 169 :``tbz2``: tar archive, compressed using bzip2
170 170 :``tgz``: tar archive, compressed using gzip
171 171 :``uzip``: zip archive, uncompressed
172 172 :``zip``: zip archive, compressed using deflate
173 173
174 174 The exact name of the destination archive or directory is given
175 175 using a format string; see :hg:`help export` for details.
176 176
177 177 Each member added to an archive file has a directory prefix
178 178 prepended. Use -p/--prefix to specify a format string for the
179 179 prefix. The default is the basename of the archive, with suffixes
180 180 removed.
181 181
182 182 Returns 0 on success.
183 183 '''
184 184
185 185 ctx = cmdutil.revsingle(repo, opts.get('rev'))
186 186 if not ctx:
187 187 raise util.Abort(_('no working directory: please specify a revision'))
188 188 node = ctx.node()
189 189 dest = cmdutil.make_filename(repo, dest, node)
190 190 if os.path.realpath(dest) == repo.root:
191 191 raise util.Abort(_('repository root cannot be destination'))
192 192
193 193 kind = opts.get('type') or archival.guesskind(dest) or 'files'
194 194 prefix = opts.get('prefix')
195 195
196 196 if dest == '-':
197 197 if kind == 'files':
198 198 raise util.Abort(_('cannot archive plain files to stdout'))
199 199 dest = sys.stdout
200 200 if not prefix:
201 201 prefix = os.path.basename(repo.root) + '-%h'
202 202
203 203 prefix = cmdutil.make_filename(repo, prefix, node)
204 204 matchfn = cmdutil.match(repo, [], opts)
205 205 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
206 206 matchfn, prefix, subrepos=opts.get('subrepos'))
207 207
208 208 def backout(ui, repo, node=None, rev=None, **opts):
209 209 '''reverse effect of earlier changeset
210 210
211 211 Prepare a new changeset with the effect of REV undone in the
212 212 current working directory.
213 213
214 214 If REV is the parent of the working directory, then this new changeset
215 215 is committed automatically. Otherwise, hg needs to merge the
216 216 changes and the merged result is left uncommitted.
217 217
218 218 By default, the pending changeset will have one parent,
219 219 maintaining a linear history. With --merge, the pending changeset
220 220 will instead have two parents: the old parent of the working
221 221 directory and a new child of REV that simply undoes REV.
222 222
223 223 Before version 1.7, the behavior without --merge was equivalent to
224 224 specifying --merge followed by :hg:`update --clean .` to cancel
225 225 the merge and leave the child of REV as a head to be merged
226 226 separately.
227 227
228 228 See :hg:`help dates` for a list of formats valid for -d/--date.
229 229
230 230 Returns 0 on success.
231 231 '''
232 232 if rev and node:
233 233 raise util.Abort(_("please specify just one revision"))
234 234
235 235 if not rev:
236 236 rev = node
237 237
238 238 if not rev:
239 239 raise util.Abort(_("please specify a revision to backout"))
240 240
241 241 date = opts.get('date')
242 242 if date:
243 243 opts['date'] = util.parsedate(date)
244 244
245 245 cmdutil.bail_if_changed(repo)
246 246 node = cmdutil.revsingle(repo, rev).node()
247 247
248 248 op1, op2 = repo.dirstate.parents()
249 249 a = repo.changelog.ancestor(op1, node)
250 250 if a != node:
251 251 raise util.Abort(_('cannot backout change on a different branch'))
252 252
253 253 p1, p2 = repo.changelog.parents(node)
254 254 if p1 == nullid:
255 255 raise util.Abort(_('cannot backout a change with no parents'))
256 256 if p2 != nullid:
257 257 if not opts.get('parent'):
258 258 raise util.Abort(_('cannot backout a merge changeset without '
259 259 '--parent'))
260 260 p = repo.lookup(opts['parent'])
261 261 if p not in (p1, p2):
262 262 raise util.Abort(_('%s is not a parent of %s') %
263 263 (short(p), short(node)))
264 264 parent = p
265 265 else:
266 266 if opts.get('parent'):
267 267 raise util.Abort(_('cannot use --parent on non-merge changeset'))
268 268 parent = p1
269 269
270 270 # the backout should appear on the same branch
271 271 branch = repo.dirstate.branch()
272 272 hg.clean(repo, node, show_stats=False)
273 273 repo.dirstate.setbranch(branch)
274 274 revert_opts = opts.copy()
275 275 revert_opts['date'] = None
276 276 revert_opts['all'] = True
277 277 revert_opts['rev'] = hex(parent)
278 278 revert_opts['no_backup'] = None
279 279 revert(ui, repo, **revert_opts)
280 280 if not opts.get('merge') and op1 != node:
281 281 try:
282 282 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
283 283 return hg.update(repo, op1)
284 284 finally:
285 285 ui.setconfig('ui', 'forcemerge', '')
286 286
287 287 commit_opts = opts.copy()
288 288 commit_opts['addremove'] = False
289 289 if not commit_opts['message'] and not commit_opts['logfile']:
290 290 # we don't translate commit messages
291 291 commit_opts['message'] = "Backed out changeset %s" % short(node)
292 292 commit_opts['force_editor'] = True
293 293 commit(ui, repo, **commit_opts)
294 294 def nice(node):
295 295 return '%d:%s' % (repo.changelog.rev(node), short(node))
296 296 ui.status(_('changeset %s backs out changeset %s\n') %
297 297 (nice(repo.changelog.tip()), nice(node)))
298 298 if opts.get('merge') and op1 != node:
299 299 hg.clean(repo, op1, show_stats=False)
300 300 ui.status(_('merging with changeset %s\n')
301 301 % nice(repo.changelog.tip()))
302 302 try:
303 303 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
304 304 return hg.merge(repo, hex(repo.changelog.tip()))
305 305 finally:
306 306 ui.setconfig('ui', 'forcemerge', '')
307 307 return 0
308 308
309 309 def bisect(ui, repo, rev=None, extra=None, command=None,
310 310 reset=None, good=None, bad=None, skip=None, extend=None,
311 311 noupdate=None):
312 312 """subdivision search of changesets
313 313
314 314 This command helps to find changesets which introduce problems. To
315 315 use, mark the earliest changeset you know exhibits the problem as
316 316 bad, then mark the latest changeset which is free from the problem
317 317 as good. Bisect will update your working directory to a revision
318 318 for testing (unless the -U/--noupdate option is specified). Once
319 319 you have performed tests, mark the working directory as good or
320 320 bad, and bisect will either update to another candidate changeset
321 321 or announce that it has found the bad revision.
322 322
323 323 As a shortcut, you can also use the revision argument to mark a
324 324 revision as good or bad without checking it out first.
325 325
326 326 If you supply a command, it will be used for automatic bisection.
327 327 Its exit status will be used to mark revisions as good or bad:
328 328 status 0 means good, 125 means to skip the revision, 127
329 329 (command not found) will abort the bisection, and any other
330 330 non-zero exit status means the revision is bad.
331 331
332 332 Returns 0 on success.
333 333 """
334 334 def extendbisectrange(nodes, good):
335 335 # bisect is incomplete when it ends on a merge node and
336 336 # one of the parent was not checked.
337 337 parents = repo[nodes[0]].parents()
338 338 if len(parents) > 1:
339 339 side = good and state['bad'] or state['good']
340 340 num = len(set(i.node() for i in parents) & set(side))
341 341 if num == 1:
342 342 return parents[0].ancestor(parents[1])
343 343 return None
344 344
345 345 def print_result(nodes, good):
346 346 displayer = cmdutil.show_changeset(ui, repo, {})
347 347 if len(nodes) == 1:
348 348 # narrowed it down to a single revision
349 349 if good:
350 350 ui.write(_("The first good revision is:\n"))
351 351 else:
352 352 ui.write(_("The first bad revision is:\n"))
353 353 displayer.show(repo[nodes[0]])
354 354 parents = repo[nodes[0]].parents()
355 355 extendnode = extendbisectrange(nodes, good)
356 356 if extendnode is not None:
357 357 ui.write(_('Not all ancestors of this changeset have been'
358 358 ' checked.\nUse bisect --extend to continue the '
359 359 'bisection from\nthe common ancestor, %s.\n')
360 360 % short(extendnode.node()))
361 361 else:
362 362 # multiple possible revisions
363 363 if good:
364 364 ui.write(_("Due to skipped revisions, the first "
365 365 "good revision could be any of:\n"))
366 366 else:
367 367 ui.write(_("Due to skipped revisions, the first "
368 368 "bad revision could be any of:\n"))
369 369 for n in nodes:
370 370 displayer.show(repo[n])
371 371 displayer.close()
372 372
373 373 def check_state(state, interactive=True):
374 374 if not state['good'] or not state['bad']:
375 375 if (good or bad or skip or reset) and interactive:
376 376 return
377 377 if not state['good']:
378 378 raise util.Abort(_('cannot bisect (no known good revisions)'))
379 379 else:
380 380 raise util.Abort(_('cannot bisect (no known bad revisions)'))
381 381 return True
382 382
383 383 # backward compatibility
384 384 if rev in "good bad reset init".split():
385 385 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
386 386 cmd, rev, extra = rev, extra, None
387 387 if cmd == "good":
388 388 good = True
389 389 elif cmd == "bad":
390 390 bad = True
391 391 else:
392 392 reset = True
393 393 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
394 394 raise util.Abort(_('incompatible arguments'))
395 395
396 396 if reset:
397 397 p = repo.join("bisect.state")
398 398 if os.path.exists(p):
399 399 os.unlink(p)
400 400 return
401 401
402 402 state = hbisect.load_state(repo)
403 403
404 404 if command:
405 405 changesets = 1
406 406 try:
407 407 while changesets:
408 408 # update state
409 409 status = util.system(command)
410 410 if status == 125:
411 411 transition = "skip"
412 412 elif status == 0:
413 413 transition = "good"
414 414 # status < 0 means process was killed
415 415 elif status == 127:
416 416 raise util.Abort(_("failed to execute %s") % command)
417 417 elif status < 0:
418 418 raise util.Abort(_("%s killed") % command)
419 419 else:
420 420 transition = "bad"
421 421 ctx = cmdutil.revsingle(repo, rev)
422 422 rev = None # clear for future iterations
423 423 state[transition].append(ctx.node())
424 424 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
425 425 check_state(state, interactive=False)
426 426 # bisect
427 427 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
428 428 # update to next check
429 429 cmdutil.bail_if_changed(repo)
430 430 hg.clean(repo, nodes[0], show_stats=False)
431 431 finally:
432 432 hbisect.save_state(repo, state)
433 433 print_result(nodes, good)
434 434 return
435 435
436 436 # update state
437 437
438 438 if rev:
439 439 nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])]
440 440 else:
441 441 nodes = [repo.lookup('.')]
442 442
443 443 if good or bad or skip:
444 444 if good:
445 445 state['good'] += nodes
446 446 elif bad:
447 447 state['bad'] += nodes
448 448 elif skip:
449 449 state['skip'] += nodes
450 450 hbisect.save_state(repo, state)
451 451
452 452 if not check_state(state):
453 453 return
454 454
455 455 # actually bisect
456 456 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
457 457 if extend:
458 458 if not changesets:
459 459 extendnode = extendbisectrange(nodes, good)
460 460 if extendnode is not None:
461 461 ui.write(_("Extending search to changeset %d:%s\n"
462 462 % (extendnode.rev(), short(extendnode.node()))))
463 463 if noupdate:
464 464 return
465 465 cmdutil.bail_if_changed(repo)
466 466 return hg.clean(repo, extendnode.node())
467 467 raise util.Abort(_("nothing to extend"))
468 468
469 469 if changesets == 0:
470 470 print_result(nodes, good)
471 471 else:
472 472 assert len(nodes) == 1 # only a single node can be tested next
473 473 node = nodes[0]
474 474 # compute the approximate number of remaining tests
475 475 tests, size = 0, 2
476 476 while size <= changesets:
477 477 tests, size = tests + 1, size * 2
478 478 rev = repo.changelog.rev(node)
479 479 ui.write(_("Testing changeset %d:%s "
480 480 "(%d changesets remaining, ~%d tests)\n")
481 481 % (rev, short(node), changesets, tests))
482 482 if not noupdate:
483 483 cmdutil.bail_if_changed(repo)
484 484 return hg.clean(repo, node)
485 485
486 486 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
487 487 '''track a line of development with movable markers
488 488
489 489 Bookmarks are pointers to certain commits that move when
490 490 committing. Bookmarks are local. They can be renamed, copied and
491 491 deleted. It is possible to use bookmark names in :hg:`merge` and
492 492 :hg:`update` to merge and update respectively to a given bookmark.
493 493
494 494 You can use :hg:`bookmark NAME` to set a bookmark on the working
495 495 directory's parent revision with the given name. If you specify
496 496 a revision using -r REV (where REV may be an existing bookmark),
497 497 the bookmark is assigned to that revision.
498 498
499 499 Bookmarks can be pushed and pulled between repositories (see :hg:`help
500 500 push` and :hg:`help pull`). This requires both the local and remote
501 501 repositories to support bookmarks. For versions prior to 1.8, this means
502 502 the bookmarks extension must be enabled.
503 503 '''
504 504 hexfn = ui.debugflag and hex or short
505 505 marks = repo._bookmarks
506 506 cur = repo.changectx('.').node()
507 507
508 508 if rename:
509 509 if rename not in marks:
510 510 raise util.Abort(_("a bookmark of this name does not exist"))
511 511 if mark in marks and not force:
512 512 raise util.Abort(_("a bookmark of the same name already exists"))
513 513 if mark is None:
514 514 raise util.Abort(_("new bookmark name required"))
515 515 marks[mark] = marks[rename]
516 516 if repo._bookmarkcurrent == rename:
517 517 bookmarks.setcurrent(repo, mark)
518 518 del marks[rename]
519 519 bookmarks.write(repo)
520 520 return
521 521
522 522 if delete:
523 523 if mark is None:
524 524 raise util.Abort(_("bookmark name required"))
525 525 if mark not in marks:
526 526 raise util.Abort(_("a bookmark of this name does not exist"))
527 527 if mark == repo._bookmarkcurrent:
528 528 bookmarks.setcurrent(repo, None)
529 529 del marks[mark]
530 530 bookmarks.write(repo)
531 531 return
532 532
533 533 if mark is not None:
534 534 if "\n" in mark:
535 535 raise util.Abort(_("bookmark name cannot contain newlines"))
536 536 mark = mark.strip()
537 537 if not mark:
538 538 raise util.Abort(_("bookmark names cannot consist entirely of "
539 539 "whitespace"))
540 540 if mark in marks and not force:
541 541 raise util.Abort(_("a bookmark of the same name already exists"))
542 542 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
543 543 and not force):
544 544 raise util.Abort(
545 545 _("a bookmark cannot have the name of an existing branch"))
546 546 if rev:
547 547 marks[mark] = repo.lookup(rev)
548 548 else:
549 549 marks[mark] = repo.changectx('.').node()
550 550 if repo.changectx('.').node() == marks[mark]:
551 551 bookmarks.setcurrent(repo, mark)
552 552 bookmarks.write(repo)
553 553 return
554 554
555 555 if mark is None:
556 556 if rev:
557 557 raise util.Abort(_("bookmark name required"))
558 558 if len(marks) == 0:
559 559 ui.status(_("no bookmarks set\n"))
560 560 else:
561 561 for bmark, n in sorted(marks.iteritems()):
562 562 current = repo._bookmarkcurrent
563 563 if bmark == current and n == cur:
564 564 prefix, label = '*', 'bookmarks.current'
565 565 else:
566 566 prefix, label = ' ', ''
567 567
568 568 if ui.quiet:
569 569 ui.write("%s\n" % bmark, label=label)
570 570 else:
571 571 ui.write(" %s %-25s %d:%s\n" % (
572 572 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
573 573 label=label)
574 574 return
575 575
576 576 def branch(ui, repo, label=None, **opts):
577 577 """set or show the current branch name
578 578
579 579 With no argument, show the current branch name. With one argument,
580 580 set the working directory branch name (the branch will not exist
581 581 in the repository until the next commit). Standard practice
582 582 recommends that primary development take place on the 'default'
583 583 branch.
584 584
585 585 Unless -f/--force is specified, branch will not let you set a
586 586 branch name that already exists, even if it's inactive.
587 587
588 588 Use -C/--clean to reset the working directory branch to that of
589 589 the parent of the working directory, negating a previous branch
590 590 change.
591 591
592 592 Use the command :hg:`update` to switch to an existing branch. Use
593 593 :hg:`commit --close-branch` to mark this branch as closed.
594 594
595 595 Returns 0 on success.
596 596 """
597 597
598 598 if opts.get('clean'):
599 599 label = repo[None].parents()[0].branch()
600 600 repo.dirstate.setbranch(label)
601 601 ui.status(_('reset working directory to branch %s\n') % label)
602 602 elif label:
603 603 if not opts.get('force') and label in repo.branchtags():
604 604 if label not in [p.branch() for p in repo.parents()]:
605 605 raise util.Abort(_('a branch of the same name already exists'
606 606 " (use 'hg update' to switch to it)"))
607 607 repo.dirstate.setbranch(label)
608 608 ui.status(_('marked working directory as branch %s\n') % label)
609 609 else:
610 610 ui.write("%s\n" % repo.dirstate.branch())
611 611
612 612 def branches(ui, repo, active=False, closed=False):
613 613 """list repository named branches
614 614
615 615 List the repository's named branches, indicating which ones are
616 616 inactive. If -c/--closed is specified, also list branches which have
617 617 been marked closed (see :hg:`commit --close-branch`).
618 618
619 619 If -a/--active is specified, only show active branches. A branch
620 620 is considered active if it contains repository heads.
621 621
622 622 Use the command :hg:`update` to switch to an existing branch.
623 623
624 624 Returns 0.
625 625 """
626 626
627 627 hexfunc = ui.debugflag and hex or short
628 628 activebranches = [repo[n].branch() for n in repo.heads()]
629 629 def testactive(tag, node):
630 630 realhead = tag in activebranches
631 631 open = node in repo.branchheads(tag, closed=False)
632 632 return realhead and open
633 633 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
634 634 for tag, node in repo.branchtags().items()],
635 635 reverse=True)
636 636
637 637 for isactive, node, tag in branches:
638 638 if (not active) or isactive:
639 639 if ui.quiet:
640 640 ui.write("%s\n" % tag)
641 641 else:
642 642 hn = repo.lookup(node)
643 643 if isactive:
644 644 label = 'branches.active'
645 645 notice = ''
646 646 elif hn not in repo.branchheads(tag, closed=False):
647 647 if not closed:
648 648 continue
649 649 label = 'branches.closed'
650 650 notice = _(' (closed)')
651 651 else:
652 652 label = 'branches.inactive'
653 653 notice = _(' (inactive)')
654 654 if tag == repo.dirstate.branch():
655 655 label = 'branches.current'
656 656 rev = str(node).rjust(31 - encoding.colwidth(tag))
657 657 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
658 658 tag = ui.label(tag, label)
659 659 ui.write("%s %s%s\n" % (tag, rev, notice))
660 660
661 661 def bundle(ui, repo, fname, dest=None, **opts):
662 662 """create a changegroup file
663 663
664 664 Generate a compressed changegroup file collecting changesets not
665 665 known to be in another repository.
666 666
667 667 If you omit the destination repository, then hg assumes the
668 668 destination will have all the nodes you specify with --base
669 669 parameters. To create a bundle containing all changesets, use
670 670 -a/--all (or --base null).
671 671
672 672 You can change compression method with the -t/--type option.
673 673 The available compression methods are: none, bzip2, and
674 674 gzip (by default, bundles are compressed using bzip2).
675 675
676 676 The bundle file can then be transferred using conventional means
677 677 and applied to another repository with the unbundle or pull
678 678 command. This is useful when direct push and pull are not
679 679 available or when exporting an entire repository is undesirable.
680 680
681 681 Applying bundles preserves all changeset contents including
682 682 permissions, copy/rename information, and revision history.
683 683
684 684 Returns 0 on success, 1 if no changes found.
685 685 """
686 686 revs = None
687 687 if 'rev' in opts:
688 688 revs = cmdutil.revrange(repo, opts['rev'])
689 689
690 690 if opts.get('all'):
691 691 base = ['null']
692 692 else:
693 693 base = cmdutil.revrange(repo, opts.get('base'))
694 694 if base:
695 695 if dest:
696 696 raise util.Abort(_("--base is incompatible with specifying "
697 697 "a destination"))
698 698 base = [repo.lookup(rev) for rev in base]
699 699 # create the right base
700 700 # XXX: nodesbetween / changegroup* should be "fixed" instead
701 701 o = []
702 702 has = set((nullid,))
703 703 for n in base:
704 704 has.update(repo.changelog.reachable(n))
705 705 if revs:
706 706 revs = [repo.lookup(rev) for rev in revs]
707 707 visit = revs[:]
708 708 has.difference_update(visit)
709 709 else:
710 710 visit = repo.changelog.heads()
711 711 seen = {}
712 712 while visit:
713 713 n = visit.pop(0)
714 714 parents = [p for p in repo.changelog.parents(n) if p not in has]
715 715 if len(parents) == 0:
716 716 if n not in has:
717 717 o.append(n)
718 718 else:
719 719 for p in parents:
720 720 if p not in seen:
721 721 seen[p] = 1
722 722 visit.append(p)
723 723 else:
724 724 dest = ui.expandpath(dest or 'default-push', dest or 'default')
725 725 dest, branches = hg.parseurl(dest, opts.get('branch'))
726 726 other = hg.repository(hg.remoteui(repo, opts), dest)
727 727 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
728 728 if revs:
729 729 revs = [repo.lookup(rev) for rev in revs]
730 730 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
731 731
732 732 if not o:
733 733 ui.status(_("no changes found\n"))
734 734 return 1
735 735
736 736 if revs:
737 737 cg = repo.changegroupsubset(o, revs, 'bundle')
738 738 else:
739 739 cg = repo.changegroup(o, 'bundle')
740 740
741 741 bundletype = opts.get('type', 'bzip2').lower()
742 742 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
743 743 bundletype = btypes.get(bundletype)
744 744 if bundletype not in changegroup.bundletypes:
745 745 raise util.Abort(_('unknown bundle type specified with --type'))
746 746
747 747 changegroup.writebundle(cg, fname, bundletype)
748 748
749 749 def cat(ui, repo, file1, *pats, **opts):
750 750 """output the current or given revision of files
751 751
752 752 Print the specified files as they were at the given revision. If
753 753 no revision is given, the parent of the working directory is used,
754 754 or tip if no revision is checked out.
755 755
756 756 Output may be to a file, in which case the name of the file is
757 757 given using a format string. The formatting rules are the same as
758 758 for the export command, with the following additions:
759 759
760 760 :``%s``: basename of file being printed
761 761 :``%d``: dirname of file being printed, or '.' if in repository root
762 762 :``%p``: root-relative path name of file being printed
763 763
764 764 Returns 0 on success.
765 765 """
766 766 ctx = cmdutil.revsingle(repo, opts.get('rev'))
767 767 err = 1
768 768 m = cmdutil.match(repo, (file1,) + pats, opts)
769 769 for abs in ctx.walk(m):
770 770 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
771 771 data = ctx[abs].data()
772 772 if opts.get('decode'):
773 773 data = repo.wwritedata(abs, data)
774 774 fp.write(data)
775 775 fp.close()
776 776 err = 0
777 777 return err
778 778
779 779 def clone(ui, source, dest=None, **opts):
780 780 """make a copy of an existing repository
781 781
782 782 Create a copy of an existing repository in a new directory.
783 783
784 784 If no destination directory name is specified, it defaults to the
785 785 basename of the source.
786 786
787 787 The location of the source is added to the new repository's
788 788 ``.hg/hgrc`` file, as the default to be used for future pulls.
789 789
790 790 See :hg:`help urls` for valid source format details.
791 791
792 792 It is possible to specify an ``ssh://`` URL as the destination, but no
793 793 ``.hg/hgrc`` and working directory will be created on the remote side.
794 794 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
795 795
796 796 A set of changesets (tags, or branch names) to pull may be specified
797 797 by listing each changeset (tag, or branch name) with -r/--rev.
798 798 If -r/--rev is used, the cloned repository will contain only a subset
799 799 of the changesets of the source repository. Only the set of changesets
800 800 defined by all -r/--rev options (including all their ancestors)
801 801 will be pulled into the destination repository.
802 802 No subsequent changesets (including subsequent tags) will be present
803 803 in the destination.
804 804
805 805 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
806 806 local source repositories.
807 807
808 808 For efficiency, hardlinks are used for cloning whenever the source
809 809 and destination are on the same filesystem (note this applies only
810 810 to the repository data, not to the working directory). Some
811 811 filesystems, such as AFS, implement hardlinking incorrectly, but
812 812 do not report errors. In these cases, use the --pull option to
813 813 avoid hardlinking.
814 814
815 815 In some cases, you can clone repositories and the working directory
816 816 using full hardlinks with ::
817 817
818 818 $ cp -al REPO REPOCLONE
819 819
820 820 This is the fastest way to clone, but it is not always safe. The
821 821 operation is not atomic (making sure REPO is not modified during
822 822 the operation is up to you) and you have to make sure your editor
823 823 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
824 824 this is not compatible with certain extensions that place their
825 825 metadata under the .hg directory, such as mq.
826 826
827 827 Mercurial will update the working directory to the first applicable
828 828 revision from this list:
829 829
830 830 a) null if -U or the source repository has no changesets
831 831 b) if -u . and the source repository is local, the first parent of
832 832 the source repository's working directory
833 833 c) the changeset specified with -u (if a branch name, this means the
834 834 latest head of that branch)
835 835 d) the changeset specified with -r
836 836 e) the tipmost head specified with -b
837 837 f) the tipmost head specified with the url#branch source syntax
838 838 g) the tipmost head of the default branch
839 839 h) tip
840 840
841 841 Returns 0 on success.
842 842 """
843 843 if opts.get('noupdate') and opts.get('updaterev'):
844 844 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
845 845
846 846 r = hg.clone(hg.remoteui(ui, opts), source, dest,
847 847 pull=opts.get('pull'),
848 848 stream=opts.get('uncompressed'),
849 849 rev=opts.get('rev'),
850 850 update=opts.get('updaterev') or not opts.get('noupdate'),
851 851 branch=opts.get('branch'))
852 852
853 853 return r is None
854 854
855 855 def commit(ui, repo, *pats, **opts):
856 856 """commit the specified files or all outstanding changes
857 857
858 858 Commit changes to the given files into the repository. Unlike a
859 859 centralized SCM, this operation is a local operation. See
860 860 :hg:`push` for a way to actively distribute your changes.
861 861
862 862 If a list of files is omitted, all changes reported by :hg:`status`
863 863 will be committed.
864 864
865 865 If you are committing the result of a merge, do not provide any
866 866 filenames or -I/-X filters.
867 867
868 868 If no commit message is specified, Mercurial starts your
869 869 configured editor where you can enter a message. In case your
870 870 commit fails, you will find a backup of your message in
871 871 ``.hg/last-message.txt``.
872 872
873 873 See :hg:`help dates` for a list of formats valid for -d/--date.
874 874
875 875 Returns 0 on success, 1 if nothing changed.
876 876 """
877 877 extra = {}
878 878 if opts.get('close_branch'):
879 879 if repo['.'].node() not in repo.branchheads():
880 880 # The topo heads set is included in the branch heads set of the
881 881 # current branch, so it's sufficient to test branchheads
882 882 raise util.Abort(_('can only close branch heads'))
883 883 extra['close'] = 1
884 884 e = cmdutil.commiteditor
885 885 if opts.get('force_editor'):
886 886 e = cmdutil.commitforceeditor
887 887
888 888 def commitfunc(ui, repo, message, match, opts):
889 889 return repo.commit(message, opts.get('user'), opts.get('date'), match,
890 890 editor=e, extra=extra)
891 891
892 892 branch = repo[None].branch()
893 893 bheads = repo.branchheads(branch)
894 894
895 895 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
896 896 if not node:
897 897 ui.status(_("nothing changed\n"))
898 898 return 1
899 899
900 900 ctx = repo[node]
901 901 parents = ctx.parents()
902 902
903 903 if bheads and not [x for x in parents
904 904 if x.node() in bheads and x.branch() == branch]:
905 905 ui.status(_('created new head\n'))
906 906 # The message is not printed for initial roots. For the other
907 907 # changesets, it is printed in the following situations:
908 908 #
909 909 # Par column: for the 2 parents with ...
910 910 # N: null or no parent
911 911 # B: parent is on another named branch
912 912 # C: parent is a regular non head changeset
913 913 # H: parent was a branch head of the current branch
914 914 # Msg column: whether we print "created new head" message
915 915 # In the following, it is assumed that there already exists some
916 916 # initial branch heads of the current branch, otherwise nothing is
917 917 # printed anyway.
918 918 #
919 919 # Par Msg Comment
920 920 # NN y additional topo root
921 921 #
922 922 # BN y additional branch root
923 923 # CN y additional topo head
924 924 # HN n usual case
925 925 #
926 926 # BB y weird additional branch root
927 927 # CB y branch merge
928 928 # HB n merge with named branch
929 929 #
930 930 # CC y additional head from merge
931 931 # CH n merge with a head
932 932 #
933 933 # HH n head merge: head count decreases
934 934
935 935 if not opts.get('close_branch'):
936 936 for r in parents:
937 937 if r.extra().get('close') and r.branch() == branch:
938 938 ui.status(_('reopening closed branch head %d\n') % r)
939 939
940 940 if ui.debugflag:
941 941 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
942 942 elif ui.verbose:
943 943 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
944 944
945 945 def copy(ui, repo, *pats, **opts):
946 946 """mark files as copied for the next commit
947 947
948 948 Mark dest as having copies of source files. If dest is a
949 949 directory, copies are put in that directory. If dest is a file,
950 950 the source must be a single file.
951 951
952 952 By default, this command copies the contents of files as they
953 953 exist in the working directory. If invoked with -A/--after, the
954 954 operation is recorded, but no copying is performed.
955 955
956 956 This command takes effect with the next commit. To undo a copy
957 957 before that, see :hg:`revert`.
958 958
959 959 Returns 0 on success, 1 if errors are encountered.
960 960 """
961 961 wlock = repo.wlock(False)
962 962 try:
963 963 return cmdutil.copy(ui, repo, pats, opts)
964 964 finally:
965 965 wlock.release()
966 966
967 967 def debugancestor(ui, repo, *args):
968 968 """find the ancestor revision of two revisions in a given index"""
969 969 if len(args) == 3:
970 970 index, rev1, rev2 = args
971 971 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
972 972 lookup = r.lookup
973 973 elif len(args) == 2:
974 974 if not repo:
975 975 raise util.Abort(_("there is no Mercurial repository here "
976 976 "(.hg not found)"))
977 977 rev1, rev2 = args
978 978 r = repo.changelog
979 979 lookup = repo.lookup
980 980 else:
981 981 raise util.Abort(_('either two or three arguments required'))
982 982 a = r.ancestor(lookup(rev1), lookup(rev2))
983 983 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
984 984
985 985 def debugbuilddag(ui, repo, text,
986 986 mergeable_file=False,
987 987 appended_file=False,
988 988 overwritten_file=False,
989 989 new_file=False):
990 990 """builds a repo with a given dag from scratch in the current empty repo
991 991
992 992 Elements:
993 993
994 994 - "+n" is a linear run of n nodes based on the current default parent
995 995 - "." is a single node based on the current default parent
996 996 - "$" resets the default parent to null (implied at the start);
997 997 otherwise the default parent is always the last node created
998 998 - "<p" sets the default parent to the backref p
999 999 - "*p" is a fork at parent p, which is a backref
1000 1000 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1001 1001 - "/p2" is a merge of the preceding node and p2
1002 1002 - ":tag" defines a local tag for the preceding node
1003 1003 - "@branch" sets the named branch for subsequent nodes
1004 1004 - "!command" runs the command using your shell
1005 1005 - "!!my command\\n" is like "!", but to the end of the line
1006 1006 - "#...\\n" is a comment up to the end of the line
1007 1007
1008 1008 Whitespace between the above elements is ignored.
1009 1009
1010 1010 A backref is either
1011 1011
1012 1012 - a number n, which references the node curr-n, where curr is the current
1013 1013 node, or
1014 1014 - the name of a local tag you placed earlier using ":tag", or
1015 1015 - empty to denote the default parent.
1016 1016
1017 1017 All string valued-elements are either strictly alphanumeric, or must
1018 1018 be enclosed in double quotes ("..."), with "\\" as escape character.
1019 1019
1020 1020 Note that the --overwritten-file and --appended-file options imply the
1021 1021 use of "HGMERGE=internal:local" during DAG buildup.
1022 1022 """
1023 1023
1024 1024 if not (mergeable_file or appended_file or overwritten_file or new_file):
1025 1025 raise util.Abort(_('need at least one of -m, -a, -o, -n'))
1026 1026
1027 1027 if len(repo.changelog) > 0:
1028 1028 raise util.Abort(_('repository is not empty'))
1029 1029
1030 1030 if overwritten_file or appended_file:
1031 1031 # we don't want to fail in merges during buildup
1032 1032 os.environ['HGMERGE'] = 'internal:local'
1033 1033
1034 1034 def writefile(fname, text, fmode="wb"):
1035 1035 f = open(fname, fmode)
1036 1036 try:
1037 1037 f.write(text)
1038 1038 finally:
1039 1039 f.close()
1040 1040
1041 1041 if mergeable_file:
1042 1042 linesperrev = 2
1043 1043 # determine number of revs in DAG
1044 1044 n = 0
1045 1045 for type, data in dagparser.parsedag(text):
1046 1046 if type == 'n':
1047 1047 n += 1
1048 1048 # make a file with k lines per rev
1049 1049 writefile("mf", "\n".join(str(i) for i in xrange(0, n * linesperrev))
1050 1050 + "\n")
1051 1051
1052 1052 at = -1
1053 1053 atbranch = 'default'
1054 1054 for type, data in dagparser.parsedag(text):
1055 1055 if type == 'n':
1056 1056 ui.status('node %s\n' % str(data))
1057 1057 id, ps = data
1058 1058 p1 = ps[0]
1059 1059 if p1 != at:
1060 1060 update(ui, repo, node=str(p1), clean=True)
1061 1061 at = p1
1062 1062 if repo.dirstate.branch() != atbranch:
1063 1063 branch(ui, repo, atbranch, force=True)
1064 1064 if len(ps) > 1:
1065 1065 p2 = ps[1]
1066 1066 merge(ui, repo, node=p2)
1067 1067
1068 1068 if mergeable_file:
1069 1069 f = open("mf", "rb+")
1070 1070 try:
1071 1071 lines = f.read().split("\n")
1072 1072 lines[id * linesperrev] += " r%i" % id
1073 1073 f.seek(0)
1074 1074 f.write("\n".join(lines))
1075 1075 finally:
1076 1076 f.close()
1077 1077
1078 1078 if appended_file:
1079 1079 writefile("af", "r%i\n" % id, "ab")
1080 1080
1081 1081 if overwritten_file:
1082 1082 writefile("of", "r%i\n" % id)
1083 1083
1084 1084 if new_file:
1085 1085 writefile("nf%i" % id, "r%i\n" % id)
1086 1086
1087 1087 commit(ui, repo, addremove=True, message="r%i" % id, date=(id, 0))
1088 1088 at = id
1089 1089 elif type == 'l':
1090 1090 id, name = data
1091 1091 ui.status('tag %s\n' % name)
1092 1092 tag(ui, repo, name, local=True)
1093 1093 elif type == 'a':
1094 1094 ui.status('branch %s\n' % data)
1095 1095 atbranch = data
1096 1096 elif type in 'cC':
1097 1097 r = util.system(data, cwd=repo.root)
1098 1098 if r:
1099 1099 desc, r = util.explain_exit(r)
1100 1100 raise util.Abort(_('%s command %s') % (data, desc))
1101 1101
1102 1102 def debugcommands(ui, cmd='', *args):
1103 1103 """list all available commands and options"""
1104 1104 for cmd, vals in sorted(table.iteritems()):
1105 1105 cmd = cmd.split('|')[0].strip('^')
1106 1106 opts = ', '.join([i[1] for i in vals[1]])
1107 1107 ui.write('%s: %s\n' % (cmd, opts))
1108 1108
1109 1109 def debugcomplete(ui, cmd='', **opts):
1110 1110 """returns the completion list associated with the given command"""
1111 1111
1112 1112 if opts.get('options'):
1113 1113 options = []
1114 1114 otables = [globalopts]
1115 1115 if cmd:
1116 1116 aliases, entry = cmdutil.findcmd(cmd, table, False)
1117 1117 otables.append(entry[1])
1118 1118 for t in otables:
1119 1119 for o in t:
1120 1120 if "(DEPRECATED)" in o[3]:
1121 1121 continue
1122 1122 if o[0]:
1123 1123 options.append('-%s' % o[0])
1124 1124 options.append('--%s' % o[1])
1125 1125 ui.write("%s\n" % "\n".join(options))
1126 1126 return
1127 1127
1128 1128 cmdlist = cmdutil.findpossible(cmd, table)
1129 1129 if ui.verbose:
1130 1130 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1131 1131 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1132 1132
1133 1133 def debugfsinfo(ui, path = "."):
1134 1134 """show information detected about current filesystem"""
1135 1135 open('.debugfsinfo', 'w').write('')
1136 1136 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1137 1137 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1138 1138 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1139 1139 and 'yes' or 'no'))
1140 1140 os.unlink('.debugfsinfo')
1141 1141
1142 1142 def debugrebuildstate(ui, repo, rev="tip"):
1143 1143 """rebuild the dirstate as it would look like for the given revision"""
1144 1144 ctx = cmdutil.revsingle(repo, rev)
1145 1145 wlock = repo.wlock()
1146 1146 try:
1147 1147 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1148 1148 finally:
1149 1149 wlock.release()
1150 1150
1151 1151 def debugcheckstate(ui, repo):
1152 1152 """validate the correctness of the current dirstate"""
1153 1153 parent1, parent2 = repo.dirstate.parents()
1154 1154 m1 = repo[parent1].manifest()
1155 1155 m2 = repo[parent2].manifest()
1156 1156 errors = 0
1157 1157 for f in repo.dirstate:
1158 1158 state = repo.dirstate[f]
1159 1159 if state in "nr" and f not in m1:
1160 1160 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1161 1161 errors += 1
1162 1162 if state in "a" and f in m1:
1163 1163 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1164 1164 errors += 1
1165 1165 if state in "m" and f not in m1 and f not in m2:
1166 1166 ui.warn(_("%s in state %s, but not in either manifest\n") %
1167 1167 (f, state))
1168 1168 errors += 1
1169 1169 for f in m1:
1170 1170 state = repo.dirstate[f]
1171 1171 if state not in "nrm":
1172 1172 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1173 1173 errors += 1
1174 1174 if errors:
1175 1175 error = _(".hg/dirstate inconsistent with current parent's manifest")
1176 1176 raise util.Abort(error)
1177 1177
1178 1178 def showconfig(ui, repo, *values, **opts):
1179 1179 """show combined config settings from all hgrc files
1180 1180
1181 1181 With no arguments, print names and values of all config items.
1182 1182
1183 1183 With one argument of the form section.name, print just the value
1184 1184 of that config item.
1185 1185
1186 1186 With multiple arguments, print names and values of all config
1187 1187 items with matching section names.
1188 1188
1189 1189 With --debug, the source (filename and line number) is printed
1190 1190 for each config item.
1191 1191
1192 1192 Returns 0 on success.
1193 1193 """
1194 1194
1195 1195 for f in util.rcpath():
1196 1196 ui.debug(_('read config from: %s\n') % f)
1197 1197 untrusted = bool(opts.get('untrusted'))
1198 1198 if values:
1199 1199 sections = [v for v in values if '.' not in v]
1200 1200 items = [v for v in values if '.' in v]
1201 1201 if len(items) > 1 or items and sections:
1202 1202 raise util.Abort(_('only one config item permitted'))
1203 1203 for section, name, value in ui.walkconfig(untrusted=untrusted):
1204 1204 value = str(value).replace('\n', '\\n')
1205 1205 sectname = section + '.' + name
1206 1206 if values:
1207 1207 for v in values:
1208 1208 if v == section:
1209 1209 ui.debug('%s: ' %
1210 1210 ui.configsource(section, name, untrusted))
1211 1211 ui.write('%s=%s\n' % (sectname, value))
1212 1212 elif v == sectname:
1213 1213 ui.debug('%s: ' %
1214 1214 ui.configsource(section, name, untrusted))
1215 1215 ui.write(value, '\n')
1216 1216 else:
1217 1217 ui.debug('%s: ' %
1218 1218 ui.configsource(section, name, untrusted))
1219 1219 ui.write('%s=%s\n' % (sectname, value))
1220 1220
1221 def debugknown(ui, repopath, *ids, **opts):
1222 """test whether node ids are known to a repo
1223
1224 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
1225 indicating unknown/known.
1226 """
1227 repo = hg.repository(ui, repopath)
1228 if not repo.capable('known'):
1229 raise util.Abort("known() not supported by target repository")
1230 flags = repo.known([bin(s) for s in ids])
1231 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1232
1221 1233 def debugpushkey(ui, repopath, namespace, *keyinfo):
1222 1234 '''access the pushkey key/value protocol
1223 1235
1224 1236 With two args, list the keys in the given namespace.
1225 1237
1226 1238 With five args, set a key to new if it currently is set to old.
1227 1239 Reports success or failure.
1228 1240 '''
1229 1241
1230 1242 target = hg.repository(ui, repopath)
1231 1243 if keyinfo:
1232 1244 key, old, new = keyinfo
1233 1245 r = target.pushkey(namespace, key, old, new)
1234 1246 ui.status(str(r) + '\n')
1235 1247 return not r
1236 1248 else:
1237 1249 for k, v in target.listkeys(namespace).iteritems():
1238 1250 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1239 1251 v.encode('string-escape')))
1240 1252
1241 1253 def debugrevspec(ui, repo, expr):
1242 1254 '''parse and apply a revision specification'''
1243 1255 if ui.verbose:
1244 1256 tree = revset.parse(expr)[0]
1245 1257 ui.note(tree, "\n")
1246 1258 func = revset.match(expr)
1247 1259 for c in func(repo, range(len(repo))):
1248 1260 ui.write("%s\n" % c)
1249 1261
1250 1262 def debugsetparents(ui, repo, rev1, rev2=None):
1251 1263 """manually set the parents of the current working directory
1252 1264
1253 1265 This is useful for writing repository conversion tools, but should
1254 1266 be used with care.
1255 1267
1256 1268 Returns 0 on success.
1257 1269 """
1258 1270
1259 1271 r1 = cmdutil.revsingle(repo, rev1).node()
1260 1272 r2 = cmdutil.revsingle(repo, rev2, 'null').node()
1261 1273
1262 1274 wlock = repo.wlock()
1263 1275 try:
1264 1276 repo.dirstate.setparents(r1, r2)
1265 1277 finally:
1266 1278 wlock.release()
1267 1279
1268 1280 def debugstate(ui, repo, nodates=None):
1269 1281 """show the contents of the current dirstate"""
1270 1282 timestr = ""
1271 1283 showdate = not nodates
1272 1284 for file_, ent in sorted(repo.dirstate._map.iteritems()):
1273 1285 if showdate:
1274 1286 if ent[3] == -1:
1275 1287 # Pad or slice to locale representation
1276 1288 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1277 1289 time.localtime(0)))
1278 1290 timestr = 'unset'
1279 1291 timestr = (timestr[:locale_len] +
1280 1292 ' ' * (locale_len - len(timestr)))
1281 1293 else:
1282 1294 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1283 1295 time.localtime(ent[3]))
1284 1296 if ent[1] & 020000:
1285 1297 mode = 'lnk'
1286 1298 else:
1287 1299 mode = '%3o' % (ent[1] & 0777)
1288 1300 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1289 1301 for f in repo.dirstate.copies():
1290 1302 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1291 1303
1292 1304 def debugsub(ui, repo, rev=None):
1293 1305 ctx = cmdutil.revsingle(repo, rev, None)
1294 1306 for k, v in sorted(ctx.substate.items()):
1295 1307 ui.write('path %s\n' % k)
1296 1308 ui.write(' source %s\n' % v[0])
1297 1309 ui.write(' revision %s\n' % v[1])
1298 1310
1299 1311 def debugdag(ui, repo, file_=None, *revs, **opts):
1300 1312 """format the changelog or an index DAG as a concise textual description
1301 1313
1302 1314 If you pass a revlog index, the revlog's DAG is emitted. If you list
1303 1315 revision numbers, they get labelled in the output as rN.
1304 1316
1305 1317 Otherwise, the changelog DAG of the current repo is emitted.
1306 1318 """
1307 1319 spaces = opts.get('spaces')
1308 1320 dots = opts.get('dots')
1309 1321 if file_:
1310 1322 rlog = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1311 1323 revs = set((int(r) for r in revs))
1312 1324 def events():
1313 1325 for r in rlog:
1314 1326 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1315 1327 if r in revs:
1316 1328 yield 'l', (r, "r%i" % r)
1317 1329 elif repo:
1318 1330 cl = repo.changelog
1319 1331 tags = opts.get('tags')
1320 1332 branches = opts.get('branches')
1321 1333 if tags:
1322 1334 labels = {}
1323 1335 for l, n in repo.tags().items():
1324 1336 labels.setdefault(cl.rev(n), []).append(l)
1325 1337 def events():
1326 1338 b = "default"
1327 1339 for r in cl:
1328 1340 if branches:
1329 1341 newb = cl.read(cl.node(r))[5]['branch']
1330 1342 if newb != b:
1331 1343 yield 'a', newb
1332 1344 b = newb
1333 1345 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1334 1346 if tags:
1335 1347 ls = labels.get(r)
1336 1348 if ls:
1337 1349 for l in ls:
1338 1350 yield 'l', (r, l)
1339 1351 else:
1340 1352 raise util.Abort(_('need repo for changelog dag'))
1341 1353
1342 1354 for line in dagparser.dagtextlines(events(),
1343 1355 addspaces=spaces,
1344 1356 wraplabels=True,
1345 1357 wrapannotations=True,
1346 1358 wrapnonlinear=dots,
1347 1359 usedots=dots,
1348 1360 maxlinewidth=70):
1349 1361 ui.write(line)
1350 1362 ui.write("\n")
1351 1363
1352 1364 def debugdata(ui, repo, file_, rev):
1353 1365 """dump the contents of a data file revision"""
1354 1366 r = None
1355 1367 if repo:
1356 1368 filelog = repo.file(file_)
1357 1369 if len(filelog):
1358 1370 r = filelog
1359 1371 if not r:
1360 1372 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
1361 1373 try:
1362 1374 ui.write(r.revision(r.lookup(rev)))
1363 1375 except KeyError:
1364 1376 raise util.Abort(_('invalid revision identifier %s') % rev)
1365 1377
1366 1378 def debugdate(ui, date, range=None, **opts):
1367 1379 """parse and display a date"""
1368 1380 if opts["extended"]:
1369 1381 d = util.parsedate(date, util.extendeddateformats)
1370 1382 else:
1371 1383 d = util.parsedate(date)
1372 1384 ui.write("internal: %s %s\n" % d)
1373 1385 ui.write("standard: %s\n" % util.datestr(d))
1374 1386 if range:
1375 1387 m = util.matchdate(range)
1376 1388 ui.write("match: %s\n" % m(d[0]))
1377 1389
1378 1390 def debugignore(ui, repo, *values, **opts):
1379 1391 """display the combined ignore pattern"""
1380 1392 ignore = repo.dirstate._ignore
1381 1393 if hasattr(ignore, 'includepat'):
1382 1394 ui.write("%s\n" % ignore.includepat)
1383 1395 else:
1384 1396 raise util.Abort(_("no ignore patterns found"))
1385 1397
1386 1398 def debugindex(ui, repo, file_, **opts):
1387 1399 """dump the contents of an index file"""
1388 1400 r = None
1389 1401 if repo:
1390 1402 filelog = repo.file(file_)
1391 1403 if len(filelog):
1392 1404 r = filelog
1393 1405
1394 1406 format = opts.get('format', 0)
1395 1407 if format not in (0, 1):
1396 1408 raise util.Abort(_("unknown format %d") % format)
1397 1409
1398 1410 if not r:
1399 1411 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1400 1412
1401 1413 if format == 0:
1402 1414 ui.write(" rev offset length base linkrev"
1403 1415 " nodeid p1 p2\n")
1404 1416 elif format == 1:
1405 1417 ui.write(" rev flag offset length"
1406 1418 " size base link p1 p2 nodeid\n")
1407 1419
1408 1420 for i in r:
1409 1421 node = r.node(i)
1410 1422 if format == 0:
1411 1423 try:
1412 1424 pp = r.parents(node)
1413 1425 except:
1414 1426 pp = [nullid, nullid]
1415 1427 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1416 1428 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1417 1429 short(node), short(pp[0]), short(pp[1])))
1418 1430 elif format == 1:
1419 1431 pr = r.parentrevs(i)
1420 1432 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1421 1433 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1422 1434 r.base(i), r.linkrev(i), pr[0], pr[1], short(node)))
1423 1435
1424 1436 def debugindexdot(ui, repo, file_):
1425 1437 """dump an index DAG as a graphviz dot file"""
1426 1438 r = None
1427 1439 if repo:
1428 1440 filelog = repo.file(file_)
1429 1441 if len(filelog):
1430 1442 r = filelog
1431 1443 if not r:
1432 1444 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1433 1445 ui.write("digraph G {\n")
1434 1446 for i in r:
1435 1447 node = r.node(i)
1436 1448 pp = r.parents(node)
1437 1449 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1438 1450 if pp[1] != nullid:
1439 1451 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1440 1452 ui.write("}\n")
1441 1453
1442 1454 def debuginstall(ui):
1443 1455 '''test Mercurial installation
1444 1456
1445 1457 Returns 0 on success.
1446 1458 '''
1447 1459
1448 1460 def writetemp(contents):
1449 1461 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1450 1462 f = os.fdopen(fd, "wb")
1451 1463 f.write(contents)
1452 1464 f.close()
1453 1465 return name
1454 1466
1455 1467 problems = 0
1456 1468
1457 1469 # encoding
1458 1470 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1459 1471 try:
1460 1472 encoding.fromlocal("test")
1461 1473 except util.Abort, inst:
1462 1474 ui.write(" %s\n" % inst)
1463 1475 ui.write(_(" (check that your locale is properly set)\n"))
1464 1476 problems += 1
1465 1477
1466 1478 # compiled modules
1467 1479 ui.status(_("Checking installed modules (%s)...\n")
1468 1480 % os.path.dirname(__file__))
1469 1481 try:
1470 1482 import bdiff, mpatch, base85, osutil
1471 1483 except Exception, inst:
1472 1484 ui.write(" %s\n" % inst)
1473 1485 ui.write(_(" One or more extensions could not be found"))
1474 1486 ui.write(_(" (check that you compiled the extensions)\n"))
1475 1487 problems += 1
1476 1488
1477 1489 # templates
1478 1490 ui.status(_("Checking templates...\n"))
1479 1491 try:
1480 1492 import templater
1481 1493 templater.templater(templater.templatepath("map-cmdline.default"))
1482 1494 except Exception, inst:
1483 1495 ui.write(" %s\n" % inst)
1484 1496 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1485 1497 problems += 1
1486 1498
1487 1499 # patch
1488 1500 ui.status(_("Checking patch...\n"))
1489 1501 patchproblems = 0
1490 1502 a = "1\n2\n3\n4\n"
1491 1503 b = "1\n2\n3\ninsert\n4\n"
1492 1504 fa = writetemp(a)
1493 1505 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
1494 1506 os.path.basename(fa))
1495 1507 fd = writetemp(d)
1496 1508
1497 1509 files = {}
1498 1510 try:
1499 1511 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1500 1512 except util.Abort, e:
1501 1513 ui.write(_(" patch call failed:\n"))
1502 1514 ui.write(" " + str(e) + "\n")
1503 1515 patchproblems += 1
1504 1516 else:
1505 1517 if list(files) != [os.path.basename(fa)]:
1506 1518 ui.write(_(" unexpected patch output!\n"))
1507 1519 patchproblems += 1
1508 1520 a = open(fa).read()
1509 1521 if a != b:
1510 1522 ui.write(_(" patch test failed!\n"))
1511 1523 patchproblems += 1
1512 1524
1513 1525 if patchproblems:
1514 1526 if ui.config('ui', 'patch'):
1515 1527 ui.write(_(" (Current patch tool may be incompatible with patch,"
1516 1528 " or misconfigured. Please check your configuration"
1517 1529 " file)\n"))
1518 1530 else:
1519 1531 ui.write(_(" Internal patcher failure, please report this error"
1520 1532 " to http://mercurial.selenic.com/wiki/BugTracker\n"))
1521 1533 problems += patchproblems
1522 1534
1523 1535 os.unlink(fa)
1524 1536 os.unlink(fd)
1525 1537
1526 1538 # editor
1527 1539 ui.status(_("Checking commit editor...\n"))
1528 1540 editor = ui.geteditor()
1529 1541 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1530 1542 if not cmdpath:
1531 1543 if editor == 'vi':
1532 1544 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1533 1545 ui.write(_(" (specify a commit editor in your configuration"
1534 1546 " file)\n"))
1535 1547 else:
1536 1548 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1537 1549 ui.write(_(" (specify a commit editor in your configuration"
1538 1550 " file)\n"))
1539 1551 problems += 1
1540 1552
1541 1553 # check username
1542 1554 ui.status(_("Checking username...\n"))
1543 1555 try:
1544 1556 ui.username()
1545 1557 except util.Abort, e:
1546 1558 ui.write(" %s\n" % e)
1547 1559 ui.write(_(" (specify a username in your configuration file)\n"))
1548 1560 problems += 1
1549 1561
1550 1562 if not problems:
1551 1563 ui.status(_("No problems detected\n"))
1552 1564 else:
1553 1565 ui.write(_("%s problems detected,"
1554 1566 " please check your install!\n") % problems)
1555 1567
1556 1568 return problems
1557 1569
1558 1570 def debugrename(ui, repo, file1, *pats, **opts):
1559 1571 """dump rename information"""
1560 1572
1561 1573 ctx = cmdutil.revsingle(repo, opts.get('rev'))
1562 1574 m = cmdutil.match(repo, (file1,) + pats, opts)
1563 1575 for abs in ctx.walk(m):
1564 1576 fctx = ctx[abs]
1565 1577 o = fctx.filelog().renamed(fctx.filenode())
1566 1578 rel = m.rel(abs)
1567 1579 if o:
1568 1580 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1569 1581 else:
1570 1582 ui.write(_("%s not renamed\n") % rel)
1571 1583
1572 1584 def debugwalk(ui, repo, *pats, **opts):
1573 1585 """show how files match on given patterns"""
1574 1586 m = cmdutil.match(repo, pats, opts)
1575 1587 items = list(repo.walk(m))
1576 1588 if not items:
1577 1589 return
1578 1590 fmt = 'f %%-%ds %%-%ds %%s' % (
1579 1591 max([len(abs) for abs in items]),
1580 1592 max([len(m.rel(abs)) for abs in items]))
1581 1593 for abs in items:
1582 1594 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1583 1595 ui.write("%s\n" % line.rstrip())
1584 1596
1585 1597 def debugwireargs(ui, repopath, *vals, **opts):
1586 1598 repo = hg.repository(hg.remoteui(ui, opts), repopath)
1587 1599 for opt in remoteopts:
1588 1600 del opts[opt[1]]
1589 1601 args = {}
1590 1602 for k, v in opts.iteritems():
1591 1603 if v:
1592 1604 args[k] = v
1593 1605 # run twice to check that we don't mess up the stream for the next command
1594 1606 res1 = repo.debugwireargs(*vals, **args)
1595 1607 res2 = repo.debugwireargs(*vals, **args)
1596 1608 ui.write("%s\n" % res1)
1597 1609 if res1 != res2:
1598 1610 ui.warn("%s\n" % res2)
1599 1611
1600 1612 def diff(ui, repo, *pats, **opts):
1601 1613 """diff repository (or selected files)
1602 1614
1603 1615 Show differences between revisions for the specified files.
1604 1616
1605 1617 Differences between files are shown using the unified diff format.
1606 1618
1607 1619 .. note::
1608 1620 diff may generate unexpected results for merges, as it will
1609 1621 default to comparing against the working directory's first
1610 1622 parent changeset if no revisions are specified.
1611 1623
1612 1624 When two revision arguments are given, then changes are shown
1613 1625 between those revisions. If only one revision is specified then
1614 1626 that revision is compared to the working directory, and, when no
1615 1627 revisions are specified, the working directory files are compared
1616 1628 to its parent.
1617 1629
1618 1630 Alternatively you can specify -c/--change with a revision to see
1619 1631 the changes in that changeset relative to its first parent.
1620 1632
1621 1633 Without the -a/--text option, diff will avoid generating diffs of
1622 1634 files it detects as binary. With -a, diff will generate a diff
1623 1635 anyway, probably with undesirable results.
1624 1636
1625 1637 Use the -g/--git option to generate diffs in the git extended diff
1626 1638 format. For more information, read :hg:`help diffs`.
1627 1639
1628 1640 Returns 0 on success.
1629 1641 """
1630 1642
1631 1643 revs = opts.get('rev')
1632 1644 change = opts.get('change')
1633 1645 stat = opts.get('stat')
1634 1646 reverse = opts.get('reverse')
1635 1647
1636 1648 if revs and change:
1637 1649 msg = _('cannot specify --rev and --change at the same time')
1638 1650 raise util.Abort(msg)
1639 1651 elif change:
1640 1652 node2 = cmdutil.revsingle(repo, change, None).node()
1641 1653 node1 = repo[node2].parents()[0].node()
1642 1654 else:
1643 1655 node1, node2 = cmdutil.revpair(repo, revs)
1644 1656
1645 1657 if reverse:
1646 1658 node1, node2 = node2, node1
1647 1659
1648 1660 diffopts = patch.diffopts(ui, opts)
1649 1661 m = cmdutil.match(repo, pats, opts)
1650 1662 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1651 1663 listsubrepos=opts.get('subrepos'))
1652 1664
1653 1665 def export(ui, repo, *changesets, **opts):
1654 1666 """dump the header and diffs for one or more changesets
1655 1667
1656 1668 Print the changeset header and diffs for one or more revisions.
1657 1669
1658 1670 The information shown in the changeset header is: author, date,
1659 1671 branch name (if non-default), changeset hash, parent(s) and commit
1660 1672 comment.
1661 1673
1662 1674 .. note::
1663 1675 export may generate unexpected diff output for merge
1664 1676 changesets, as it will compare the merge changeset against its
1665 1677 first parent only.
1666 1678
1667 1679 Output may be to a file, in which case the name of the file is
1668 1680 given using a format string. The formatting rules are as follows:
1669 1681
1670 1682 :``%%``: literal "%" character
1671 1683 :``%H``: changeset hash (40 hexadecimal digits)
1672 1684 :``%N``: number of patches being generated
1673 1685 :``%R``: changeset revision number
1674 1686 :``%b``: basename of the exporting repository
1675 1687 :``%h``: short-form changeset hash (12 hexadecimal digits)
1676 1688 :``%n``: zero-padded sequence number, starting at 1
1677 1689 :``%r``: zero-padded changeset revision number
1678 1690
1679 1691 Without the -a/--text option, export will avoid generating diffs
1680 1692 of files it detects as binary. With -a, export will generate a
1681 1693 diff anyway, probably with undesirable results.
1682 1694
1683 1695 Use the -g/--git option to generate diffs in the git extended diff
1684 1696 format. See :hg:`help diffs` for more information.
1685 1697
1686 1698 With the --switch-parent option, the diff will be against the
1687 1699 second parent. It can be useful to review a merge.
1688 1700
1689 1701 Returns 0 on success.
1690 1702 """
1691 1703 changesets += tuple(opts.get('rev', []))
1692 1704 if not changesets:
1693 1705 raise util.Abort(_("export requires at least one changeset"))
1694 1706 revs = cmdutil.revrange(repo, changesets)
1695 1707 if len(revs) > 1:
1696 1708 ui.note(_('exporting patches:\n'))
1697 1709 else:
1698 1710 ui.note(_('exporting patch:\n'))
1699 1711 cmdutil.export(repo, revs, template=opts.get('output'),
1700 1712 switch_parent=opts.get('switch_parent'),
1701 1713 opts=patch.diffopts(ui, opts))
1702 1714
1703 1715 def forget(ui, repo, *pats, **opts):
1704 1716 """forget the specified files on the next commit
1705 1717
1706 1718 Mark the specified files so they will no longer be tracked
1707 1719 after the next commit.
1708 1720
1709 1721 This only removes files from the current branch, not from the
1710 1722 entire project history, and it does not delete them from the
1711 1723 working directory.
1712 1724
1713 1725 To undo a forget before the next commit, see :hg:`add`.
1714 1726
1715 1727 Returns 0 on success.
1716 1728 """
1717 1729
1718 1730 if not pats:
1719 1731 raise util.Abort(_('no files specified'))
1720 1732
1721 1733 m = cmdutil.match(repo, pats, opts)
1722 1734 s = repo.status(match=m, clean=True)
1723 1735 forget = sorted(s[0] + s[1] + s[3] + s[6])
1724 1736 errs = 0
1725 1737
1726 1738 for f in m.files():
1727 1739 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1728 1740 ui.warn(_('not removing %s: file is already untracked\n')
1729 1741 % m.rel(f))
1730 1742 errs = 1
1731 1743
1732 1744 for f in forget:
1733 1745 if ui.verbose or not m.exact(f):
1734 1746 ui.status(_('removing %s\n') % m.rel(f))
1735 1747
1736 1748 repo[None].remove(forget, unlink=False)
1737 1749 return errs
1738 1750
1739 1751 def grep(ui, repo, pattern, *pats, **opts):
1740 1752 """search for a pattern in specified files and revisions
1741 1753
1742 1754 Search revisions of files for a regular expression.
1743 1755
1744 1756 This command behaves differently than Unix grep. It only accepts
1745 1757 Python/Perl regexps. It searches repository history, not the
1746 1758 working directory. It always prints the revision number in which a
1747 1759 match appears.
1748 1760
1749 1761 By default, grep only prints output for the first revision of a
1750 1762 file in which it finds a match. To get it to print every revision
1751 1763 that contains a change in match status ("-" for a match that
1752 1764 becomes a non-match, or "+" for a non-match that becomes a match),
1753 1765 use the --all flag.
1754 1766
1755 1767 Returns 0 if a match is found, 1 otherwise.
1756 1768 """
1757 1769 reflags = 0
1758 1770 if opts.get('ignore_case'):
1759 1771 reflags |= re.I
1760 1772 try:
1761 1773 regexp = re.compile(pattern, reflags)
1762 1774 except re.error, inst:
1763 1775 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1764 1776 return 1
1765 1777 sep, eol = ':', '\n'
1766 1778 if opts.get('print0'):
1767 1779 sep = eol = '\0'
1768 1780
1769 1781 getfile = util.lrucachefunc(repo.file)
1770 1782
1771 1783 def matchlines(body):
1772 1784 begin = 0
1773 1785 linenum = 0
1774 1786 while True:
1775 1787 match = regexp.search(body, begin)
1776 1788 if not match:
1777 1789 break
1778 1790 mstart, mend = match.span()
1779 1791 linenum += body.count('\n', begin, mstart) + 1
1780 1792 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1781 1793 begin = body.find('\n', mend) + 1 or len(body)
1782 1794 lend = begin - 1
1783 1795 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1784 1796
1785 1797 class linestate(object):
1786 1798 def __init__(self, line, linenum, colstart, colend):
1787 1799 self.line = line
1788 1800 self.linenum = linenum
1789 1801 self.colstart = colstart
1790 1802 self.colend = colend
1791 1803
1792 1804 def __hash__(self):
1793 1805 return hash((self.linenum, self.line))
1794 1806
1795 1807 def __eq__(self, other):
1796 1808 return self.line == other.line
1797 1809
1798 1810 matches = {}
1799 1811 copies = {}
1800 1812 def grepbody(fn, rev, body):
1801 1813 matches[rev].setdefault(fn, [])
1802 1814 m = matches[rev][fn]
1803 1815 for lnum, cstart, cend, line in matchlines(body):
1804 1816 s = linestate(line, lnum, cstart, cend)
1805 1817 m.append(s)
1806 1818
1807 1819 def difflinestates(a, b):
1808 1820 sm = difflib.SequenceMatcher(None, a, b)
1809 1821 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1810 1822 if tag == 'insert':
1811 1823 for i in xrange(blo, bhi):
1812 1824 yield ('+', b[i])
1813 1825 elif tag == 'delete':
1814 1826 for i in xrange(alo, ahi):
1815 1827 yield ('-', a[i])
1816 1828 elif tag == 'replace':
1817 1829 for i in xrange(alo, ahi):
1818 1830 yield ('-', a[i])
1819 1831 for i in xrange(blo, bhi):
1820 1832 yield ('+', b[i])
1821 1833
1822 1834 def display(fn, ctx, pstates, states):
1823 1835 rev = ctx.rev()
1824 1836 datefunc = ui.quiet and util.shortdate or util.datestr
1825 1837 found = False
1826 1838 filerevmatches = {}
1827 1839 if opts.get('all'):
1828 1840 iter = difflinestates(pstates, states)
1829 1841 else:
1830 1842 iter = [('', l) for l in states]
1831 1843 for change, l in iter:
1832 1844 cols = [fn, str(rev)]
1833 1845 before, match, after = None, None, None
1834 1846 if opts.get('line_number'):
1835 1847 cols.append(str(l.linenum))
1836 1848 if opts.get('all'):
1837 1849 cols.append(change)
1838 1850 if opts.get('user'):
1839 1851 cols.append(ui.shortuser(ctx.user()))
1840 1852 if opts.get('date'):
1841 1853 cols.append(datefunc(ctx.date()))
1842 1854 if opts.get('files_with_matches'):
1843 1855 c = (fn, rev)
1844 1856 if c in filerevmatches:
1845 1857 continue
1846 1858 filerevmatches[c] = 1
1847 1859 else:
1848 1860 before = l.line[:l.colstart]
1849 1861 match = l.line[l.colstart:l.colend]
1850 1862 after = l.line[l.colend:]
1851 1863 ui.write(sep.join(cols))
1852 1864 if before is not None:
1853 1865 ui.write(sep + before)
1854 1866 ui.write(match, label='grep.match')
1855 1867 ui.write(after)
1856 1868 ui.write(eol)
1857 1869 found = True
1858 1870 return found
1859 1871
1860 1872 skip = {}
1861 1873 revfiles = {}
1862 1874 matchfn = cmdutil.match(repo, pats, opts)
1863 1875 found = False
1864 1876 follow = opts.get('follow')
1865 1877
1866 1878 def prep(ctx, fns):
1867 1879 rev = ctx.rev()
1868 1880 pctx = ctx.parents()[0]
1869 1881 parent = pctx.rev()
1870 1882 matches.setdefault(rev, {})
1871 1883 matches.setdefault(parent, {})
1872 1884 files = revfiles.setdefault(rev, [])
1873 1885 for fn in fns:
1874 1886 flog = getfile(fn)
1875 1887 try:
1876 1888 fnode = ctx.filenode(fn)
1877 1889 except error.LookupError:
1878 1890 continue
1879 1891
1880 1892 copied = flog.renamed(fnode)
1881 1893 copy = follow and copied and copied[0]
1882 1894 if copy:
1883 1895 copies.setdefault(rev, {})[fn] = copy
1884 1896 if fn in skip:
1885 1897 if copy:
1886 1898 skip[copy] = True
1887 1899 continue
1888 1900 files.append(fn)
1889 1901
1890 1902 if fn not in matches[rev]:
1891 1903 grepbody(fn, rev, flog.read(fnode))
1892 1904
1893 1905 pfn = copy or fn
1894 1906 if pfn not in matches[parent]:
1895 1907 try:
1896 1908 fnode = pctx.filenode(pfn)
1897 1909 grepbody(pfn, parent, flog.read(fnode))
1898 1910 except error.LookupError:
1899 1911 pass
1900 1912
1901 1913 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1902 1914 rev = ctx.rev()
1903 1915 parent = ctx.parents()[0].rev()
1904 1916 for fn in sorted(revfiles.get(rev, [])):
1905 1917 states = matches[rev][fn]
1906 1918 copy = copies.get(rev, {}).get(fn)
1907 1919 if fn in skip:
1908 1920 if copy:
1909 1921 skip[copy] = True
1910 1922 continue
1911 1923 pstates = matches.get(parent, {}).get(copy or fn, [])
1912 1924 if pstates or states:
1913 1925 r = display(fn, ctx, pstates, states)
1914 1926 found = found or r
1915 1927 if r and not opts.get('all'):
1916 1928 skip[fn] = True
1917 1929 if copy:
1918 1930 skip[copy] = True
1919 1931 del matches[rev]
1920 1932 del revfiles[rev]
1921 1933
1922 1934 return not found
1923 1935
1924 1936 def heads(ui, repo, *branchrevs, **opts):
1925 1937 """show current repository heads or show branch heads
1926 1938
1927 1939 With no arguments, show all repository branch heads.
1928 1940
1929 1941 Repository "heads" are changesets with no child changesets. They are
1930 1942 where development generally takes place and are the usual targets
1931 1943 for update and merge operations. Branch heads are changesets that have
1932 1944 no child changeset on the same branch.
1933 1945
1934 1946 If one or more REVs are given, only branch heads on the branches
1935 1947 associated with the specified changesets are shown.
1936 1948
1937 1949 If -c/--closed is specified, also show branch heads marked closed
1938 1950 (see :hg:`commit --close-branch`).
1939 1951
1940 1952 If STARTREV is specified, only those heads that are descendants of
1941 1953 STARTREV will be displayed.
1942 1954
1943 1955 If -t/--topo is specified, named branch mechanics will be ignored and only
1944 1956 changesets without children will be shown.
1945 1957
1946 1958 Returns 0 if matching heads are found, 1 if not.
1947 1959 """
1948 1960
1949 1961 start = None
1950 1962 if 'rev' in opts:
1951 1963 start = cmdutil.revsingle(repo, opts['rev'], None).node()
1952 1964
1953 1965 if opts.get('topo'):
1954 1966 heads = [repo[h] for h in repo.heads(start)]
1955 1967 else:
1956 1968 heads = []
1957 1969 for b, ls in repo.branchmap().iteritems():
1958 1970 if start is None:
1959 1971 heads += [repo[h] for h in ls]
1960 1972 continue
1961 1973 startrev = repo.changelog.rev(start)
1962 1974 descendants = set(repo.changelog.descendants(startrev))
1963 1975 descendants.add(startrev)
1964 1976 rev = repo.changelog.rev
1965 1977 heads += [repo[h] for h in ls if rev(h) in descendants]
1966 1978
1967 1979 if branchrevs:
1968 1980 branches = set(repo[br].branch() for br in branchrevs)
1969 1981 heads = [h for h in heads if h.branch() in branches]
1970 1982
1971 1983 if not opts.get('closed'):
1972 1984 heads = [h for h in heads if not h.extra().get('close')]
1973 1985
1974 1986 if opts.get('active') and branchrevs:
1975 1987 dagheads = repo.heads(start)
1976 1988 heads = [h for h in heads if h.node() in dagheads]
1977 1989
1978 1990 if branchrevs:
1979 1991 haveheads = set(h.branch() for h in heads)
1980 1992 if branches - haveheads:
1981 1993 headless = ', '.join(b for b in branches - haveheads)
1982 1994 msg = _('no open branch heads found on branches %s')
1983 1995 if opts.get('rev'):
1984 1996 msg += _(' (started at %s)' % opts['rev'])
1985 1997 ui.warn((msg + '\n') % headless)
1986 1998
1987 1999 if not heads:
1988 2000 return 1
1989 2001
1990 2002 heads = sorted(heads, key=lambda x: -x.rev())
1991 2003 displayer = cmdutil.show_changeset(ui, repo, opts)
1992 2004 for ctx in heads:
1993 2005 displayer.show(ctx)
1994 2006 displayer.close()
1995 2007
1996 2008 def help_(ui, name=None, with_version=False, unknowncmd=False):
1997 2009 """show help for a given topic or a help overview
1998 2010
1999 2011 With no arguments, print a list of commands with short help messages.
2000 2012
2001 2013 Given a topic, extension, or command name, print help for that
2002 2014 topic.
2003 2015
2004 2016 Returns 0 if successful.
2005 2017 """
2006 2018 option_lists = []
2007 2019 textwidth = min(ui.termwidth(), 80) - 2
2008 2020
2009 2021 def addglobalopts(aliases):
2010 2022 if ui.verbose:
2011 2023 option_lists.append((_("global options:"), globalopts))
2012 2024 if name == 'shortlist':
2013 2025 option_lists.append((_('use "hg help" for the full list '
2014 2026 'of commands'), ()))
2015 2027 else:
2016 2028 if name == 'shortlist':
2017 2029 msg = _('use "hg help" for the full list of commands '
2018 2030 'or "hg -v" for details')
2019 2031 elif aliases:
2020 2032 msg = _('use "hg -v help%s" to show builtin aliases and '
2021 2033 'global options') % (name and " " + name or "")
2022 2034 else:
2023 2035 msg = _('use "hg -v help %s" to show global options') % name
2024 2036 option_lists.append((msg, ()))
2025 2037
2026 2038 def helpcmd(name):
2027 2039 if with_version:
2028 2040 version_(ui)
2029 2041 ui.write('\n')
2030 2042
2031 2043 try:
2032 2044 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
2033 2045 except error.AmbiguousCommand, inst:
2034 2046 # py3k fix: except vars can't be used outside the scope of the
2035 2047 # except block, nor can be used inside a lambda. python issue4617
2036 2048 prefix = inst.args[0]
2037 2049 select = lambda c: c.lstrip('^').startswith(prefix)
2038 2050 helplist(_('list of commands:\n\n'), select)
2039 2051 return
2040 2052
2041 2053 # check if it's an invalid alias and display its error if it is
2042 2054 if getattr(entry[0], 'badalias', False):
2043 2055 if not unknowncmd:
2044 2056 entry[0](ui)
2045 2057 return
2046 2058
2047 2059 # synopsis
2048 2060 if len(entry) > 2:
2049 2061 if entry[2].startswith('hg'):
2050 2062 ui.write("%s\n" % entry[2])
2051 2063 else:
2052 2064 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2053 2065 else:
2054 2066 ui.write('hg %s\n' % aliases[0])
2055 2067
2056 2068 # aliases
2057 2069 if not ui.quiet and len(aliases) > 1:
2058 2070 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2059 2071
2060 2072 # description
2061 2073 doc = gettext(entry[0].__doc__)
2062 2074 if not doc:
2063 2075 doc = _("(no help text available)")
2064 2076 if hasattr(entry[0], 'definition'): # aliased command
2065 2077 if entry[0].definition.startswith('!'): # shell alias
2066 2078 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2067 2079 else:
2068 2080 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2069 2081 if ui.quiet:
2070 2082 doc = doc.splitlines()[0]
2071 2083 keep = ui.verbose and ['verbose'] or []
2072 2084 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2073 2085 ui.write("\n%s\n" % formatted)
2074 2086 if pruned:
2075 2087 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2076 2088
2077 2089 if not ui.quiet:
2078 2090 # options
2079 2091 if entry[1]:
2080 2092 option_lists.append((_("options:\n"), entry[1]))
2081 2093
2082 2094 addglobalopts(False)
2083 2095
2084 2096 def helplist(header, select=None):
2085 2097 h = {}
2086 2098 cmds = {}
2087 2099 for c, e in table.iteritems():
2088 2100 f = c.split("|", 1)[0]
2089 2101 if select and not select(f):
2090 2102 continue
2091 2103 if (not select and name != 'shortlist' and
2092 2104 e[0].__module__ != __name__):
2093 2105 continue
2094 2106 if name == "shortlist" and not f.startswith("^"):
2095 2107 continue
2096 2108 f = f.lstrip("^")
2097 2109 if not ui.debugflag and f.startswith("debug"):
2098 2110 continue
2099 2111 doc = e[0].__doc__
2100 2112 if doc and 'DEPRECATED' in doc and not ui.verbose:
2101 2113 continue
2102 2114 doc = gettext(doc)
2103 2115 if not doc:
2104 2116 doc = _("(no help text available)")
2105 2117 h[f] = doc.splitlines()[0].rstrip()
2106 2118 cmds[f] = c.lstrip("^")
2107 2119
2108 2120 if not h:
2109 2121 ui.status(_('no commands defined\n'))
2110 2122 return
2111 2123
2112 2124 ui.status(header)
2113 2125 fns = sorted(h)
2114 2126 m = max(map(len, fns))
2115 2127 for f in fns:
2116 2128 if ui.verbose:
2117 2129 commands = cmds[f].replace("|",", ")
2118 2130 ui.write(" %s:\n %s\n"%(commands, h[f]))
2119 2131 else:
2120 2132 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2121 2133 initindent=' %-*s ' % (m, f),
2122 2134 hangindent=' ' * (m + 4))))
2123 2135
2124 2136 if not ui.quiet:
2125 2137 addglobalopts(True)
2126 2138
2127 2139 def helptopic(name):
2128 2140 for names, header, doc in help.helptable:
2129 2141 if name in names:
2130 2142 break
2131 2143 else:
2132 2144 raise error.UnknownCommand(name)
2133 2145
2134 2146 # description
2135 2147 if not doc:
2136 2148 doc = _("(no help text available)")
2137 2149 if hasattr(doc, '__call__'):
2138 2150 doc = doc()
2139 2151
2140 2152 ui.write("%s\n\n" % header)
2141 2153 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2142 2154
2143 2155 def helpext(name):
2144 2156 try:
2145 2157 mod = extensions.find(name)
2146 2158 doc = gettext(mod.__doc__) or _('no help text available')
2147 2159 except KeyError:
2148 2160 mod = None
2149 2161 doc = extensions.disabledext(name)
2150 2162 if not doc:
2151 2163 raise error.UnknownCommand(name)
2152 2164
2153 2165 if '\n' not in doc:
2154 2166 head, tail = doc, ""
2155 2167 else:
2156 2168 head, tail = doc.split('\n', 1)
2157 2169 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2158 2170 if tail:
2159 2171 ui.write(minirst.format(tail, textwidth))
2160 2172 ui.status('\n\n')
2161 2173
2162 2174 if mod:
2163 2175 try:
2164 2176 ct = mod.cmdtable
2165 2177 except AttributeError:
2166 2178 ct = {}
2167 2179 modcmds = set([c.split('|', 1)[0] for c in ct])
2168 2180 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2169 2181 else:
2170 2182 ui.write(_('use "hg help extensions" for information on enabling '
2171 2183 'extensions\n'))
2172 2184
2173 2185 def helpextcmd(name):
2174 2186 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2175 2187 doc = gettext(mod.__doc__).splitlines()[0]
2176 2188
2177 2189 msg = help.listexts(_("'%s' is provided by the following "
2178 2190 "extension:") % cmd, {ext: doc}, len(ext),
2179 2191 indent=4)
2180 2192 ui.write(minirst.format(msg, textwidth))
2181 2193 ui.write('\n\n')
2182 2194 ui.write(_('use "hg help extensions" for information on enabling '
2183 2195 'extensions\n'))
2184 2196
2185 2197 help.addtopichook('revsets', revset.makedoc)
2186 2198 help.addtopichook('templates', templatekw.makedoc)
2187 2199 help.addtopichook('templates', templatefilters.makedoc)
2188 2200
2189 2201 if name and name != 'shortlist':
2190 2202 i = None
2191 2203 if unknowncmd:
2192 2204 queries = (helpextcmd,)
2193 2205 else:
2194 2206 queries = (helptopic, helpcmd, helpext, helpextcmd)
2195 2207 for f in queries:
2196 2208 try:
2197 2209 f(name)
2198 2210 i = None
2199 2211 break
2200 2212 except error.UnknownCommand, inst:
2201 2213 i = inst
2202 2214 if i:
2203 2215 raise i
2204 2216
2205 2217 else:
2206 2218 # program name
2207 2219 if ui.verbose or with_version:
2208 2220 version_(ui)
2209 2221 else:
2210 2222 ui.status(_("Mercurial Distributed SCM\n"))
2211 2223 ui.status('\n')
2212 2224
2213 2225 # list of commands
2214 2226 if name == "shortlist":
2215 2227 header = _('basic commands:\n\n')
2216 2228 else:
2217 2229 header = _('list of commands:\n\n')
2218 2230
2219 2231 helplist(header)
2220 2232 if name != 'shortlist':
2221 2233 exts, maxlength = extensions.enabled()
2222 2234 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2223 2235 if text:
2224 2236 ui.write("\n%s\n" % minirst.format(text, textwidth))
2225 2237
2226 2238 # list all option lists
2227 2239 opt_output = []
2228 2240 multioccur = False
2229 2241 for title, options in option_lists:
2230 2242 opt_output.append(("\n%s" % title, None))
2231 2243 for option in options:
2232 2244 if len(option) == 5:
2233 2245 shortopt, longopt, default, desc, optlabel = option
2234 2246 else:
2235 2247 shortopt, longopt, default, desc = option
2236 2248 optlabel = _("VALUE") # default label
2237 2249
2238 2250 if _("DEPRECATED") in desc and not ui.verbose:
2239 2251 continue
2240 2252 if isinstance(default, list):
2241 2253 numqualifier = " %s [+]" % optlabel
2242 2254 multioccur = True
2243 2255 elif (default is not None) and not isinstance(default, bool):
2244 2256 numqualifier = " %s" % optlabel
2245 2257 else:
2246 2258 numqualifier = ""
2247 2259 opt_output.append(("%2s%s" %
2248 2260 (shortopt and "-%s" % shortopt,
2249 2261 longopt and " --%s%s" %
2250 2262 (longopt, numqualifier)),
2251 2263 "%s%s" % (desc,
2252 2264 default
2253 2265 and _(" (default: %s)") % default
2254 2266 or "")))
2255 2267 if multioccur:
2256 2268 msg = _("\n[+] marked option can be specified multiple times")
2257 2269 if ui.verbose and name != 'shortlist':
2258 2270 opt_output.append((msg, None))
2259 2271 else:
2260 2272 opt_output.insert(-1, (msg, None))
2261 2273
2262 2274 if not name:
2263 2275 ui.write(_("\nadditional help topics:\n\n"))
2264 2276 topics = []
2265 2277 for names, header, doc in help.helptable:
2266 2278 topics.append((sorted(names, key=len, reverse=True)[0], header))
2267 2279 topics_len = max([len(s[0]) for s in topics])
2268 2280 for t, desc in topics:
2269 2281 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2270 2282
2271 2283 if opt_output:
2272 2284 colwidth = encoding.colwidth
2273 2285 # normalize: (opt or message, desc or None, width of opt)
2274 2286 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2275 2287 for opt, desc in opt_output]
2276 2288 hanging = max([e[2] for e in entries])
2277 2289 for opt, desc, width in entries:
2278 2290 if desc:
2279 2291 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2280 2292 hangindent = ' ' * (hanging + 3)
2281 2293 ui.write('%s\n' % (util.wrap(desc, textwidth,
2282 2294 initindent=initindent,
2283 2295 hangindent=hangindent)))
2284 2296 else:
2285 2297 ui.write("%s\n" % opt)
2286 2298
2287 2299 def identify(ui, repo, source=None, rev=None,
2288 2300 num=None, id=None, branch=None, tags=None, bookmarks=None):
2289 2301 """identify the working copy or specified revision
2290 2302
2291 2303 With no revision, print a summary of the current state of the
2292 2304 repository.
2293 2305
2294 2306 Specifying a path to a repository root or Mercurial bundle will
2295 2307 cause lookup to operate on that repository/bundle.
2296 2308
2297 2309 This summary identifies the repository state using one or two
2298 2310 parent hash identifiers, followed by a "+" if there are
2299 2311 uncommitted changes in the working directory, a list of tags for
2300 2312 this revision and a branch name for non-default branches.
2301 2313
2302 2314 Returns 0 if successful.
2303 2315 """
2304 2316
2305 2317 if not repo and not source:
2306 2318 raise util.Abort(_("there is no Mercurial repository here "
2307 2319 "(.hg not found)"))
2308 2320
2309 2321 hexfunc = ui.debugflag and hex or short
2310 2322 default = not (num or id or branch or tags or bookmarks)
2311 2323 output = []
2312 2324
2313 2325 revs = []
2314 2326 bms = []
2315 2327 if source:
2316 2328 source, branches = hg.parseurl(ui.expandpath(source))
2317 2329 repo = hg.repository(ui, source)
2318 2330 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2319 2331
2320 2332 if not repo.local():
2321 2333 if not rev and revs:
2322 2334 rev = revs[0]
2323 2335 if not rev:
2324 2336 rev = "tip"
2325 2337 if num or branch or tags:
2326 2338 raise util.Abort(
2327 2339 _("can't query remote revision number, branch, or tags"))
2328 2340
2329 2341 remoterev = repo.lookup(rev)
2330 2342 if default or id:
2331 2343 output = [hexfunc(remoterev)]
2332 2344
2333 2345 if 'bookmarks' in repo.listkeys('namespaces'):
2334 2346 hexremoterev = hex(remoterev)
2335 2347 bms = [bm for bm, bmrev in repo.listkeys('bookmarks').iteritems()
2336 2348 if bmrev == hexremoterev]
2337 2349
2338 2350 elif not rev:
2339 2351 ctx = repo[None]
2340 2352 parents = ctx.parents()
2341 2353 changed = False
2342 2354 if default or id or num:
2343 2355 changed = util.any(repo.status())
2344 2356 if default or id:
2345 2357 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
2346 2358 (changed) and "+" or "")]
2347 2359 if num:
2348 2360 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
2349 2361 (changed) and "+" or ""))
2350 2362 else:
2351 2363 ctx = cmdutil.revsingle(repo, rev)
2352 2364 if default or id:
2353 2365 output = [hexfunc(ctx.node())]
2354 2366 if num:
2355 2367 output.append(str(ctx.rev()))
2356 2368
2357 2369 if repo.local():
2358 2370 bms = ctx.bookmarks()
2359 2371
2360 2372 if repo.local() and default and not ui.quiet:
2361 2373 b = ctx.branch()
2362 2374 if b != 'default':
2363 2375 output.append("(%s)" % b)
2364 2376
2365 2377 # multiple tags for a single parent separated by '/'
2366 2378 t = "/".join(ctx.tags())
2367 2379 if t:
2368 2380 output.append(t)
2369 2381
2370 2382 if default and not ui.quiet:
2371 2383 # multiple bookmarks for a single parent separated by '/'
2372 2384 bm = '/'.join(bms)
2373 2385 if bm:
2374 2386 output.append(bm)
2375 2387
2376 2388 if branch:
2377 2389 output.append(ctx.branch())
2378 2390
2379 2391 if tags:
2380 2392 output.extend(ctx.tags())
2381 2393
2382 2394 if bookmarks:
2383 2395 output.extend(bms)
2384 2396
2385 2397 ui.write("%s\n" % ' '.join(output))
2386 2398
2387 2399 def import_(ui, repo, patch1, *patches, **opts):
2388 2400 """import an ordered set of patches
2389 2401
2390 2402 Import a list of patches and commit them individually (unless
2391 2403 --no-commit is specified).
2392 2404
2393 2405 If there are outstanding changes in the working directory, import
2394 2406 will abort unless given the -f/--force flag.
2395 2407
2396 2408 You can import a patch straight from a mail message. Even patches
2397 2409 as attachments work (to use the body part, it must have type
2398 2410 text/plain or text/x-patch). From and Subject headers of email
2399 2411 message are used as default committer and commit message. All
2400 2412 text/plain body parts before first diff are added to commit
2401 2413 message.
2402 2414
2403 2415 If the imported patch was generated by :hg:`export`, user and
2404 2416 description from patch override values from message headers and
2405 2417 body. Values given on command line with -m/--message and -u/--user
2406 2418 override these.
2407 2419
2408 2420 If --exact is specified, import will set the working directory to
2409 2421 the parent of each patch before applying it, and will abort if the
2410 2422 resulting changeset has a different ID than the one recorded in
2411 2423 the patch. This may happen due to character set problems or other
2412 2424 deficiencies in the text patch format.
2413 2425
2414 2426 With -s/--similarity, hg will attempt to discover renames and
2415 2427 copies in the patch in the same way as 'addremove'.
2416 2428
2417 2429 To read a patch from standard input, use "-" as the patch name. If
2418 2430 a URL is specified, the patch will be downloaded from it.
2419 2431 See :hg:`help dates` for a list of formats valid for -d/--date.
2420 2432
2421 2433 Returns 0 on success.
2422 2434 """
2423 2435 patches = (patch1,) + patches
2424 2436
2425 2437 date = opts.get('date')
2426 2438 if date:
2427 2439 opts['date'] = util.parsedate(date)
2428 2440
2429 2441 try:
2430 2442 sim = float(opts.get('similarity') or 0)
2431 2443 except ValueError:
2432 2444 raise util.Abort(_('similarity must be a number'))
2433 2445 if sim < 0 or sim > 100:
2434 2446 raise util.Abort(_('similarity must be between 0 and 100'))
2435 2447
2436 2448 if opts.get('exact') or not opts.get('force'):
2437 2449 cmdutil.bail_if_changed(repo)
2438 2450
2439 2451 d = opts["base"]
2440 2452 strip = opts["strip"]
2441 2453 wlock = lock = None
2442 2454 msgs = []
2443 2455
2444 2456 def tryone(ui, hunk):
2445 2457 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2446 2458 patch.extract(ui, hunk)
2447 2459
2448 2460 if not tmpname:
2449 2461 return None
2450 2462 commitid = _('to working directory')
2451 2463
2452 2464 try:
2453 2465 cmdline_message = cmdutil.logmessage(opts)
2454 2466 if cmdline_message:
2455 2467 # pickup the cmdline msg
2456 2468 message = cmdline_message
2457 2469 elif message:
2458 2470 # pickup the patch msg
2459 2471 message = message.strip()
2460 2472 else:
2461 2473 # launch the editor
2462 2474 message = None
2463 2475 ui.debug('message:\n%s\n' % message)
2464 2476
2465 2477 wp = repo.parents()
2466 2478 if opts.get('exact'):
2467 2479 if not nodeid or not p1:
2468 2480 raise util.Abort(_('not a Mercurial patch'))
2469 2481 p1 = repo.lookup(p1)
2470 2482 p2 = repo.lookup(p2 or hex(nullid))
2471 2483
2472 2484 if p1 != wp[0].node():
2473 2485 hg.clean(repo, p1)
2474 2486 repo.dirstate.setparents(p1, p2)
2475 2487 elif p2:
2476 2488 try:
2477 2489 p1 = repo.lookup(p1)
2478 2490 p2 = repo.lookup(p2)
2479 2491 if p1 == wp[0].node():
2480 2492 repo.dirstate.setparents(p1, p2)
2481 2493 except error.RepoError:
2482 2494 pass
2483 2495 if opts.get('exact') or opts.get('import_branch'):
2484 2496 repo.dirstate.setbranch(branch or 'default')
2485 2497
2486 2498 files = {}
2487 2499 try:
2488 2500 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2489 2501 files=files, eolmode=None)
2490 2502 finally:
2491 2503 files = cmdutil.updatedir(ui, repo, files,
2492 2504 similarity=sim / 100.0)
2493 2505 if opts.get('no_commit'):
2494 2506 if message:
2495 2507 msgs.append(message)
2496 2508 else:
2497 2509 if opts.get('exact'):
2498 2510 m = None
2499 2511 else:
2500 2512 m = cmdutil.matchfiles(repo, files or [])
2501 2513 n = repo.commit(message, opts.get('user') or user,
2502 2514 opts.get('date') or date, match=m,
2503 2515 editor=cmdutil.commiteditor)
2504 2516 if opts.get('exact'):
2505 2517 if hex(n) != nodeid:
2506 2518 repo.rollback()
2507 2519 raise util.Abort(_('patch is damaged'
2508 2520 ' or loses information'))
2509 2521 # Force a dirstate write so that the next transaction
2510 2522 # backups an up-do-date file.
2511 2523 repo.dirstate.write()
2512 2524 if n:
2513 2525 commitid = short(n)
2514 2526
2515 2527 return commitid
2516 2528 finally:
2517 2529 os.unlink(tmpname)
2518 2530
2519 2531 try:
2520 2532 wlock = repo.wlock()
2521 2533 lock = repo.lock()
2522 2534 lastcommit = None
2523 2535 for p in patches:
2524 2536 pf = os.path.join(d, p)
2525 2537
2526 2538 if pf == '-':
2527 2539 ui.status(_("applying patch from stdin\n"))
2528 2540 pf = sys.stdin
2529 2541 else:
2530 2542 ui.status(_("applying %s\n") % p)
2531 2543 pf = url.open(ui, pf)
2532 2544
2533 2545 haspatch = False
2534 2546 for hunk in patch.split(pf):
2535 2547 commitid = tryone(ui, hunk)
2536 2548 if commitid:
2537 2549 haspatch = True
2538 2550 if lastcommit:
2539 2551 ui.status(_('applied %s\n') % lastcommit)
2540 2552 lastcommit = commitid
2541 2553
2542 2554 if not haspatch:
2543 2555 raise util.Abort(_('no diffs found'))
2544 2556
2545 2557 if msgs:
2546 2558 repo.opener('last-message.txt', 'wb').write('\n* * *\n'.join(msgs))
2547 2559 finally:
2548 2560 release(lock, wlock)
2549 2561
2550 2562 def incoming(ui, repo, source="default", **opts):
2551 2563 """show new changesets found in source
2552 2564
2553 2565 Show new changesets found in the specified path/URL or the default
2554 2566 pull location. These are the changesets that would have been pulled
2555 2567 if a pull at the time you issued this command.
2556 2568
2557 2569 For remote repository, using --bundle avoids downloading the
2558 2570 changesets twice if the incoming is followed by a pull.
2559 2571
2560 2572 See pull for valid source format details.
2561 2573
2562 2574 Returns 0 if there are incoming changes, 1 otherwise.
2563 2575 """
2564 2576 if opts.get('bundle') and opts.get('subrepos'):
2565 2577 raise util.Abort(_('cannot combine --bundle and --subrepos'))
2566 2578
2567 2579 if opts.get('bookmarks'):
2568 2580 source, branches = hg.parseurl(ui.expandpath(source),
2569 2581 opts.get('branch'))
2570 2582 other = hg.repository(hg.remoteui(repo, opts), source)
2571 2583 if 'bookmarks' not in other.listkeys('namespaces'):
2572 2584 ui.warn(_("remote doesn't support bookmarks\n"))
2573 2585 return 0
2574 2586 ui.status(_('comparing with %s\n') % url.hidepassword(source))
2575 2587 return bookmarks.diff(ui, repo, other)
2576 2588
2577 2589 ret = hg.incoming(ui, repo, source, opts)
2578 2590 return ret
2579 2591
2580 2592 def init(ui, dest=".", **opts):
2581 2593 """create a new repository in the given directory
2582 2594
2583 2595 Initialize a new repository in the given directory. If the given
2584 2596 directory does not exist, it will be created.
2585 2597
2586 2598 If no directory is given, the current directory is used.
2587 2599
2588 2600 It is possible to specify an ``ssh://`` URL as the destination.
2589 2601 See :hg:`help urls` for more information.
2590 2602
2591 2603 Returns 0 on success.
2592 2604 """
2593 2605 hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=1)
2594 2606
2595 2607 def locate(ui, repo, *pats, **opts):
2596 2608 """locate files matching specific patterns
2597 2609
2598 2610 Print files under Mercurial control in the working directory whose
2599 2611 names match the given patterns.
2600 2612
2601 2613 By default, this command searches all directories in the working
2602 2614 directory. To search just the current directory and its
2603 2615 subdirectories, use "--include .".
2604 2616
2605 2617 If no patterns are given to match, this command prints the names
2606 2618 of all files under Mercurial control in the working directory.
2607 2619
2608 2620 If you want to feed the output of this command into the "xargs"
2609 2621 command, use the -0 option to both this command and "xargs". This
2610 2622 will avoid the problem of "xargs" treating single filenames that
2611 2623 contain whitespace as multiple filenames.
2612 2624
2613 2625 Returns 0 if a match is found, 1 otherwise.
2614 2626 """
2615 2627 end = opts.get('print0') and '\0' or '\n'
2616 2628 rev = cmdutil.revsingle(repo, opts.get('rev'), None).node()
2617 2629
2618 2630 ret = 1
2619 2631 m = cmdutil.match(repo, pats, opts, default='relglob')
2620 2632 m.bad = lambda x, y: False
2621 2633 for abs in repo[rev].walk(m):
2622 2634 if not rev and abs not in repo.dirstate:
2623 2635 continue
2624 2636 if opts.get('fullpath'):
2625 2637 ui.write(repo.wjoin(abs), end)
2626 2638 else:
2627 2639 ui.write(((pats and m.rel(abs)) or abs), end)
2628 2640 ret = 0
2629 2641
2630 2642 return ret
2631 2643
2632 2644 def log(ui, repo, *pats, **opts):
2633 2645 """show revision history of entire repository or files
2634 2646
2635 2647 Print the revision history of the specified files or the entire
2636 2648 project.
2637 2649
2638 2650 File history is shown without following rename or copy history of
2639 2651 files. Use -f/--follow with a filename to follow history across
2640 2652 renames and copies. --follow without a filename will only show
2641 2653 ancestors or descendants of the starting revision. --follow-first
2642 2654 only follows the first parent of merge revisions.
2643 2655
2644 2656 If no revision range is specified, the default is ``tip:0`` unless
2645 2657 --follow is set, in which case the working directory parent is
2646 2658 used as the starting revision. You can specify a revision set for
2647 2659 log, see :hg:`help revsets` for more information.
2648 2660
2649 2661 See :hg:`help dates` for a list of formats valid for -d/--date.
2650 2662
2651 2663 By default this command prints revision number and changeset id,
2652 2664 tags, non-trivial parents, user, date and time, and a summary for
2653 2665 each commit. When the -v/--verbose switch is used, the list of
2654 2666 changed files and full commit message are shown.
2655 2667
2656 2668 .. note::
2657 2669 log -p/--patch may generate unexpected diff output for merge
2658 2670 changesets, as it will only compare the merge changeset against
2659 2671 its first parent. Also, only files different from BOTH parents
2660 2672 will appear in files:.
2661 2673
2662 2674 Returns 0 on success.
2663 2675 """
2664 2676
2665 2677 matchfn = cmdutil.match(repo, pats, opts)
2666 2678 limit = cmdutil.loglimit(opts)
2667 2679 count = 0
2668 2680
2669 2681 endrev = None
2670 2682 if opts.get('copies') and opts.get('rev'):
2671 2683 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2672 2684
2673 2685 df = False
2674 2686 if opts["date"]:
2675 2687 df = util.matchdate(opts["date"])
2676 2688
2677 2689 branches = opts.get('branch', []) + opts.get('only_branch', [])
2678 2690 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2679 2691
2680 2692 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2681 2693 def prep(ctx, fns):
2682 2694 rev = ctx.rev()
2683 2695 parents = [p for p in repo.changelog.parentrevs(rev)
2684 2696 if p != nullrev]
2685 2697 if opts.get('no_merges') and len(parents) == 2:
2686 2698 return
2687 2699 if opts.get('only_merges') and len(parents) != 2:
2688 2700 return
2689 2701 if opts.get('branch') and ctx.branch() not in opts['branch']:
2690 2702 return
2691 2703 if df and not df(ctx.date()[0]):
2692 2704 return
2693 2705 if opts['user'] and not [k for k in opts['user']
2694 2706 if k.lower() in ctx.user().lower()]:
2695 2707 return
2696 2708 if opts.get('keyword'):
2697 2709 for k in [kw.lower() for kw in opts['keyword']]:
2698 2710 if (k in ctx.user().lower() or
2699 2711 k in ctx.description().lower() or
2700 2712 k in " ".join(ctx.files()).lower()):
2701 2713 break
2702 2714 else:
2703 2715 return
2704 2716
2705 2717 copies = None
2706 2718 if opts.get('copies') and rev:
2707 2719 copies = []
2708 2720 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2709 2721 for fn in ctx.files():
2710 2722 rename = getrenamed(fn, rev)
2711 2723 if rename:
2712 2724 copies.append((fn, rename[0]))
2713 2725
2714 2726 revmatchfn = None
2715 2727 if opts.get('patch') or opts.get('stat'):
2716 2728 if opts.get('follow') or opts.get('follow_first'):
2717 2729 # note: this might be wrong when following through merges
2718 2730 revmatchfn = cmdutil.match(repo, fns, default='path')
2719 2731 else:
2720 2732 revmatchfn = matchfn
2721 2733
2722 2734 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2723 2735
2724 2736 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2725 2737 if count == limit:
2726 2738 break
2727 2739 if displayer.flush(ctx.rev()):
2728 2740 count += 1
2729 2741 displayer.close()
2730 2742
2731 2743 def manifest(ui, repo, node=None, rev=None):
2732 2744 """output the current or given revision of the project manifest
2733 2745
2734 2746 Print a list of version controlled files for the given revision.
2735 2747 If no revision is given, the first parent of the working directory
2736 2748 is used, or the null revision if no revision is checked out.
2737 2749
2738 2750 With -v, print file permissions, symlink and executable bits.
2739 2751 With --debug, print file revision hashes.
2740 2752
2741 2753 Returns 0 on success.
2742 2754 """
2743 2755
2744 2756 if rev and node:
2745 2757 raise util.Abort(_("please specify just one revision"))
2746 2758
2747 2759 if not node:
2748 2760 node = rev
2749 2761
2750 2762 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2751 2763 ctx = cmdutil.revsingle(repo, node)
2752 2764 for f in ctx:
2753 2765 if ui.debugflag:
2754 2766 ui.write("%40s " % hex(ctx.manifest()[f]))
2755 2767 if ui.verbose:
2756 2768 ui.write(decor[ctx.flags(f)])
2757 2769 ui.write("%s\n" % f)
2758 2770
2759 2771 def merge(ui, repo, node=None, **opts):
2760 2772 """merge working directory with another revision
2761 2773
2762 2774 The current working directory is updated with all changes made in
2763 2775 the requested revision since the last common predecessor revision.
2764 2776
2765 2777 Files that changed between either parent are marked as changed for
2766 2778 the next commit and a commit must be performed before any further
2767 2779 updates to the repository are allowed. The next commit will have
2768 2780 two parents.
2769 2781
2770 2782 ``--tool`` can be used to specify the merge tool used for file
2771 2783 merges. It overrides the HGMERGE environment variable and your
2772 2784 configuration files.
2773 2785
2774 2786 If no revision is specified, the working directory's parent is a
2775 2787 head revision, and the current branch contains exactly one other
2776 2788 head, the other head is merged with by default. Otherwise, an
2777 2789 explicit revision with which to merge with must be provided.
2778 2790
2779 2791 :hg:`resolve` must be used to resolve unresolved files.
2780 2792
2781 2793 To undo an uncommitted merge, use :hg:`update --clean .` which
2782 2794 will check out a clean copy of the original merge parent, losing
2783 2795 all changes.
2784 2796
2785 2797 Returns 0 on success, 1 if there are unresolved files.
2786 2798 """
2787 2799
2788 2800 if opts.get('rev') and node:
2789 2801 raise util.Abort(_("please specify just one revision"))
2790 2802 if not node:
2791 2803 node = opts.get('rev')
2792 2804
2793 2805 if not node:
2794 2806 branch = repo[None].branch()
2795 2807 bheads = repo.branchheads(branch)
2796 2808 if len(bheads) > 2:
2797 2809 raise util.Abort(_(
2798 2810 'branch \'%s\' has %d heads - '
2799 2811 'please merge with an explicit rev\n'
2800 2812 '(run \'hg heads .\' to see heads)')
2801 2813 % (branch, len(bheads)))
2802 2814
2803 2815 parent = repo.dirstate.parents()[0]
2804 2816 if len(bheads) == 1:
2805 2817 if len(repo.heads()) > 1:
2806 2818 raise util.Abort(_(
2807 2819 'branch \'%s\' has one head - '
2808 2820 'please merge with an explicit rev\n'
2809 2821 '(run \'hg heads\' to see all heads)')
2810 2822 % branch)
2811 2823 msg = _('there is nothing to merge')
2812 2824 if parent != repo.lookup(repo[None].branch()):
2813 2825 msg = _('%s - use "hg update" instead') % msg
2814 2826 raise util.Abort(msg)
2815 2827
2816 2828 if parent not in bheads:
2817 2829 raise util.Abort(_('working dir not at a head rev - '
2818 2830 'use "hg update" or merge with an explicit rev'))
2819 2831 node = parent == bheads[0] and bheads[-1] or bheads[0]
2820 2832 else:
2821 2833 node = cmdutil.revsingle(repo, node).node()
2822 2834
2823 2835 if opts.get('preview'):
2824 2836 # find nodes that are ancestors of p2 but not of p1
2825 2837 p1 = repo.lookup('.')
2826 2838 p2 = repo.lookup(node)
2827 2839 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2828 2840
2829 2841 displayer = cmdutil.show_changeset(ui, repo, opts)
2830 2842 for node in nodes:
2831 2843 displayer.show(repo[node])
2832 2844 displayer.close()
2833 2845 return 0
2834 2846
2835 2847 try:
2836 2848 # ui.forcemerge is an internal variable, do not document
2837 2849 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2838 2850 return hg.merge(repo, node, force=opts.get('force'))
2839 2851 finally:
2840 2852 ui.setconfig('ui', 'forcemerge', '')
2841 2853
2842 2854 def outgoing(ui, repo, dest=None, **opts):
2843 2855 """show changesets not found in the destination
2844 2856
2845 2857 Show changesets not found in the specified destination repository
2846 2858 or the default push location. These are the changesets that would
2847 2859 be pushed if a push was requested.
2848 2860
2849 2861 See pull for details of valid destination formats.
2850 2862
2851 2863 Returns 0 if there are outgoing changes, 1 otherwise.
2852 2864 """
2853 2865
2854 2866 if opts.get('bookmarks'):
2855 2867 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2856 2868 dest, branches = hg.parseurl(dest, opts.get('branch'))
2857 2869 other = hg.repository(hg.remoteui(repo, opts), dest)
2858 2870 if 'bookmarks' not in other.listkeys('namespaces'):
2859 2871 ui.warn(_("remote doesn't support bookmarks\n"))
2860 2872 return 0
2861 2873 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2862 2874 return bookmarks.diff(ui, other, repo)
2863 2875
2864 2876 ret = hg.outgoing(ui, repo, dest, opts)
2865 2877 return ret
2866 2878
2867 2879 def parents(ui, repo, file_=None, **opts):
2868 2880 """show the parents of the working directory or revision
2869 2881
2870 2882 Print the working directory's parent revisions. If a revision is
2871 2883 given via -r/--rev, the parent of that revision will be printed.
2872 2884 If a file argument is given, the revision in which the file was
2873 2885 last changed (before the working directory revision or the
2874 2886 argument to --rev if given) is printed.
2875 2887
2876 2888 Returns 0 on success.
2877 2889 """
2878 2890
2879 2891 ctx = cmdutil.revsingle(repo, opts.get('rev'), None)
2880 2892
2881 2893 if file_:
2882 2894 m = cmdutil.match(repo, (file_,), opts)
2883 2895 if m.anypats() or len(m.files()) != 1:
2884 2896 raise util.Abort(_('can only specify an explicit filename'))
2885 2897 file_ = m.files()[0]
2886 2898 filenodes = []
2887 2899 for cp in ctx.parents():
2888 2900 if not cp:
2889 2901 continue
2890 2902 try:
2891 2903 filenodes.append(cp.filenode(file_))
2892 2904 except error.LookupError:
2893 2905 pass
2894 2906 if not filenodes:
2895 2907 raise util.Abort(_("'%s' not found in manifest!") % file_)
2896 2908 fl = repo.file(file_)
2897 2909 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2898 2910 else:
2899 2911 p = [cp.node() for cp in ctx.parents()]
2900 2912
2901 2913 displayer = cmdutil.show_changeset(ui, repo, opts)
2902 2914 for n in p:
2903 2915 if n != nullid:
2904 2916 displayer.show(repo[n])
2905 2917 displayer.close()
2906 2918
2907 2919 def paths(ui, repo, search=None):
2908 2920 """show aliases for remote repositories
2909 2921
2910 2922 Show definition of symbolic path name NAME. If no name is given,
2911 2923 show definition of all available names.
2912 2924
2913 2925 Path names are defined in the [paths] section of your
2914 2926 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
2915 2927 repository, ``.hg/hgrc`` is used, too.
2916 2928
2917 2929 The path names ``default`` and ``default-push`` have a special
2918 2930 meaning. When performing a push or pull operation, they are used
2919 2931 as fallbacks if no location is specified on the command-line.
2920 2932 When ``default-push`` is set, it will be used for push and
2921 2933 ``default`` will be used for pull; otherwise ``default`` is used
2922 2934 as the fallback for both. When cloning a repository, the clone
2923 2935 source is written as ``default`` in ``.hg/hgrc``. Note that
2924 2936 ``default`` and ``default-push`` apply to all inbound (e.g.
2925 2937 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2926 2938 :hg:`bundle`) operations.
2927 2939
2928 2940 See :hg:`help urls` for more information.
2929 2941
2930 2942 Returns 0 on success.
2931 2943 """
2932 2944 if search:
2933 2945 for name, path in ui.configitems("paths"):
2934 2946 if name == search:
2935 2947 ui.write("%s\n" % url.hidepassword(path))
2936 2948 return
2937 2949 ui.warn(_("not found!\n"))
2938 2950 return 1
2939 2951 else:
2940 2952 for name, path in ui.configitems("paths"):
2941 2953 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2942 2954
2943 2955 def postincoming(ui, repo, modheads, optupdate, checkout):
2944 2956 if modheads == 0:
2945 2957 return
2946 2958 if optupdate:
2947 2959 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2948 2960 return hg.update(repo, checkout)
2949 2961 else:
2950 2962 ui.status(_("not updating, since new heads added\n"))
2951 2963 if modheads > 1:
2952 2964 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2953 2965 else:
2954 2966 ui.status(_("(run 'hg update' to get a working copy)\n"))
2955 2967
2956 2968 def pull(ui, repo, source="default", **opts):
2957 2969 """pull changes from the specified source
2958 2970
2959 2971 Pull changes from a remote repository to a local one.
2960 2972
2961 2973 This finds all changes from the repository at the specified path
2962 2974 or URL and adds them to a local repository (the current one unless
2963 2975 -R is specified). By default, this does not update the copy of the
2964 2976 project in the working directory.
2965 2977
2966 2978 Use :hg:`incoming` if you want to see what would have been added
2967 2979 by a pull at the time you issued this command. If you then decide
2968 2980 to add those changes to the repository, you should use :hg:`pull
2969 2981 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
2970 2982
2971 2983 If SOURCE is omitted, the 'default' path will be used.
2972 2984 See :hg:`help urls` for more information.
2973 2985
2974 2986 Returns 0 on success, 1 if an update had unresolved files.
2975 2987 """
2976 2988 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2977 2989 other = hg.repository(hg.remoteui(repo, opts), source)
2978 2990 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2979 2991 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2980 2992
2981 2993 if opts.get('bookmark'):
2982 2994 if not revs:
2983 2995 revs = []
2984 2996 rb = other.listkeys('bookmarks')
2985 2997 for b in opts['bookmark']:
2986 2998 if b not in rb:
2987 2999 raise util.Abort(_('remote bookmark %s not found!') % b)
2988 3000 revs.append(rb[b])
2989 3001
2990 3002 if revs:
2991 3003 try:
2992 3004 revs = [other.lookup(rev) for rev in revs]
2993 3005 except error.CapabilityError:
2994 3006 err = _("other repository doesn't support revision lookup, "
2995 3007 "so a rev cannot be specified.")
2996 3008 raise util.Abort(err)
2997 3009
2998 3010 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2999 3011 bookmarks.updatefromremote(ui, repo, other)
3000 3012 if checkout:
3001 3013 checkout = str(repo.changelog.rev(other.lookup(checkout)))
3002 3014 repo._subtoppath = source
3003 3015 try:
3004 3016 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
3005 3017
3006 3018 finally:
3007 3019 del repo._subtoppath
3008 3020
3009 3021 # update specified bookmarks
3010 3022 if opts.get('bookmark'):
3011 3023 for b in opts['bookmark']:
3012 3024 # explicit pull overrides local bookmark if any
3013 3025 ui.status(_("importing bookmark %s\n") % b)
3014 3026 repo._bookmarks[b] = repo[rb[b]].node()
3015 3027 bookmarks.write(repo)
3016 3028
3017 3029 return ret
3018 3030
3019 3031 def push(ui, repo, dest=None, **opts):
3020 3032 """push changes to the specified destination
3021 3033
3022 3034 Push changesets from the local repository to the specified
3023 3035 destination.
3024 3036
3025 3037 This operation is symmetrical to pull: it is identical to a pull
3026 3038 in the destination repository from the current one.
3027 3039
3028 3040 By default, push will not allow creation of new heads at the
3029 3041 destination, since multiple heads would make it unclear which head
3030 3042 to use. In this situation, it is recommended to pull and merge
3031 3043 before pushing.
3032 3044
3033 3045 Use --new-branch if you want to allow push to create a new named
3034 3046 branch that is not present at the destination. This allows you to
3035 3047 only create a new branch without forcing other changes.
3036 3048
3037 3049 Use -f/--force to override the default behavior and push all
3038 3050 changesets on all branches.
3039 3051
3040 3052 If -r/--rev is used, the specified revision and all its ancestors
3041 3053 will be pushed to the remote repository.
3042 3054
3043 3055 Please see :hg:`help urls` for important details about ``ssh://``
3044 3056 URLs. If DESTINATION is omitted, a default path will be used.
3045 3057
3046 3058 Returns 0 if push was successful, 1 if nothing to push.
3047 3059 """
3048 3060
3049 3061 if opts.get('bookmark'):
3050 3062 for b in opts['bookmark']:
3051 3063 # translate -B options to -r so changesets get pushed
3052 3064 if b in repo._bookmarks:
3053 3065 opts.setdefault('rev', []).append(b)
3054 3066 else:
3055 3067 # if we try to push a deleted bookmark, translate it to null
3056 3068 # this lets simultaneous -r, -b options continue working
3057 3069 opts.setdefault('rev', []).append("null")
3058 3070
3059 3071 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3060 3072 dest, branches = hg.parseurl(dest, opts.get('branch'))
3061 3073 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
3062 3074 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
3063 3075 other = hg.repository(hg.remoteui(repo, opts), dest)
3064 3076 if revs:
3065 3077 revs = [repo.lookup(rev) for rev in revs]
3066 3078
3067 3079 repo._subtoppath = dest
3068 3080 try:
3069 3081 # push subrepos depth-first for coherent ordering
3070 3082 c = repo['']
3071 3083 subs = c.substate # only repos that are committed
3072 3084 for s in sorted(subs):
3073 3085 if not c.sub(s).push(opts.get('force')):
3074 3086 return False
3075 3087 finally:
3076 3088 del repo._subtoppath
3077 3089 result = repo.push(other, opts.get('force'), revs=revs,
3078 3090 newbranch=opts.get('new_branch'))
3079 3091
3080 3092 result = (result == 0)
3081 3093
3082 3094 if opts.get('bookmark'):
3083 3095 rb = other.listkeys('bookmarks')
3084 3096 for b in opts['bookmark']:
3085 3097 # explicit push overrides remote bookmark if any
3086 3098 if b in repo._bookmarks:
3087 3099 ui.status(_("exporting bookmark %s\n") % b)
3088 3100 new = repo[b].hex()
3089 3101 elif b in rb:
3090 3102 ui.status(_("deleting remote bookmark %s\n") % b)
3091 3103 new = '' # delete
3092 3104 else:
3093 3105 ui.warn(_('bookmark %s does not exist on the local '
3094 3106 'or remote repository!\n') % b)
3095 3107 return 2
3096 3108 old = rb.get(b, '')
3097 3109 r = other.pushkey('bookmarks', b, old, new)
3098 3110 if not r:
3099 3111 ui.warn(_('updating bookmark %s failed!\n') % b)
3100 3112 if not result:
3101 3113 result = 2
3102 3114
3103 3115 return result
3104 3116
3105 3117 def recover(ui, repo):
3106 3118 """roll back an interrupted transaction
3107 3119
3108 3120 Recover from an interrupted commit or pull.
3109 3121
3110 3122 This command tries to fix the repository status after an
3111 3123 interrupted operation. It should only be necessary when Mercurial
3112 3124 suggests it.
3113 3125
3114 3126 Returns 0 if successful, 1 if nothing to recover or verify fails.
3115 3127 """
3116 3128 if repo.recover():
3117 3129 return hg.verify(repo)
3118 3130 return 1
3119 3131
3120 3132 def remove(ui, repo, *pats, **opts):
3121 3133 """remove the specified files on the next commit
3122 3134
3123 3135 Schedule the indicated files for removal from the repository.
3124 3136
3125 3137 This only removes files from the current branch, not from the
3126 3138 entire project history. -A/--after can be used to remove only
3127 3139 files that have already been deleted, -f/--force can be used to
3128 3140 force deletion, and -Af can be used to remove files from the next
3129 3141 revision without deleting them from the working directory.
3130 3142
3131 3143 The following table details the behavior of remove for different
3132 3144 file states (columns) and option combinations (rows). The file
3133 3145 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3134 3146 reported by :hg:`status`). The actions are Warn, Remove (from
3135 3147 branch) and Delete (from disk)::
3136 3148
3137 3149 A C M !
3138 3150 none W RD W R
3139 3151 -f R RD RD R
3140 3152 -A W W W R
3141 3153 -Af R R R R
3142 3154
3143 3155 This command schedules the files to be removed at the next commit.
3144 3156 To undo a remove before that, see :hg:`revert`.
3145 3157
3146 3158 Returns 0 on success, 1 if any warnings encountered.
3147 3159 """
3148 3160
3149 3161 ret = 0
3150 3162 after, force = opts.get('after'), opts.get('force')
3151 3163 if not pats and not after:
3152 3164 raise util.Abort(_('no files specified'))
3153 3165
3154 3166 m = cmdutil.match(repo, pats, opts)
3155 3167 s = repo.status(match=m, clean=True)
3156 3168 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3157 3169
3158 3170 for f in m.files():
3159 3171 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3160 3172 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3161 3173 ret = 1
3162 3174
3163 3175 if force:
3164 3176 remove, forget = modified + deleted + clean, added
3165 3177 elif after:
3166 3178 remove, forget = deleted, []
3167 3179 for f in modified + added + clean:
3168 3180 ui.warn(_('not removing %s: file still exists (use -f'
3169 3181 ' to force removal)\n') % m.rel(f))
3170 3182 ret = 1
3171 3183 else:
3172 3184 remove, forget = deleted + clean, []
3173 3185 for f in modified:
3174 3186 ui.warn(_('not removing %s: file is modified (use -f'
3175 3187 ' to force removal)\n') % m.rel(f))
3176 3188 ret = 1
3177 3189 for f in added:
3178 3190 ui.warn(_('not removing %s: file has been marked for add (use -f'
3179 3191 ' to force removal)\n') % m.rel(f))
3180 3192 ret = 1
3181 3193
3182 3194 for f in sorted(remove + forget):
3183 3195 if ui.verbose or not m.exact(f):
3184 3196 ui.status(_('removing %s\n') % m.rel(f))
3185 3197
3186 3198 repo[None].forget(forget)
3187 3199 repo[None].remove(remove, unlink=not after)
3188 3200 return ret
3189 3201
3190 3202 def rename(ui, repo, *pats, **opts):
3191 3203 """rename files; equivalent of copy + remove
3192 3204
3193 3205 Mark dest as copies of sources; mark sources for deletion. If dest
3194 3206 is a directory, copies are put in that directory. If dest is a
3195 3207 file, there can only be one source.
3196 3208
3197 3209 By default, this command copies the contents of files as they
3198 3210 exist in the working directory. If invoked with -A/--after, the
3199 3211 operation is recorded, but no copying is performed.
3200 3212
3201 3213 This command takes effect at the next commit. To undo a rename
3202 3214 before that, see :hg:`revert`.
3203 3215
3204 3216 Returns 0 on success, 1 if errors are encountered.
3205 3217 """
3206 3218 wlock = repo.wlock(False)
3207 3219 try:
3208 3220 return cmdutil.copy(ui, repo, pats, opts, rename=True)
3209 3221 finally:
3210 3222 wlock.release()
3211 3223
3212 3224 def resolve(ui, repo, *pats, **opts):
3213 3225 """redo merges or set/view the merge status of files
3214 3226
3215 3227 Merges with unresolved conflicts are often the result of
3216 3228 non-interactive merging using the ``internal:merge`` configuration
3217 3229 setting, or a command-line merge tool like ``diff3``. The resolve
3218 3230 command is used to manage the files involved in a merge, after
3219 3231 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3220 3232 working directory must have two parents).
3221 3233
3222 3234 The resolve command can be used in the following ways:
3223 3235
3224 3236 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
3225 3237 files, discarding any previous merge attempts. Re-merging is not
3226 3238 performed for files already marked as resolved. Use ``--all/-a``
3227 3239 to selects all unresolved files. ``--tool`` can be used to specify
3228 3240 the merge tool used for the given files. It overrides the HGMERGE
3229 3241 environment variable and your configuration files.
3230 3242
3231 3243 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3232 3244 (e.g. after having manually fixed-up the files). The default is
3233 3245 to mark all unresolved files.
3234 3246
3235 3247 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3236 3248 default is to mark all resolved files.
3237 3249
3238 3250 - :hg:`resolve -l`: list files which had or still have conflicts.
3239 3251 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3240 3252
3241 3253 Note that Mercurial will not let you commit files with unresolved
3242 3254 merge conflicts. You must use :hg:`resolve -m ...` before you can
3243 3255 commit after a conflicting merge.
3244 3256
3245 3257 Returns 0 on success, 1 if any files fail a resolve attempt.
3246 3258 """
3247 3259
3248 3260 all, mark, unmark, show, nostatus = \
3249 3261 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3250 3262
3251 3263 if (show and (mark or unmark)) or (mark and unmark):
3252 3264 raise util.Abort(_("too many options specified"))
3253 3265 if pats and all:
3254 3266 raise util.Abort(_("can't specify --all and patterns"))
3255 3267 if not (all or pats or show or mark or unmark):
3256 3268 raise util.Abort(_('no files or directories specified; '
3257 3269 'use --all to remerge all files'))
3258 3270
3259 3271 ms = mergemod.mergestate(repo)
3260 3272 m = cmdutil.match(repo, pats, opts)
3261 3273 ret = 0
3262 3274
3263 3275 for f in ms:
3264 3276 if m(f):
3265 3277 if show:
3266 3278 if nostatus:
3267 3279 ui.write("%s\n" % f)
3268 3280 else:
3269 3281 ui.write("%s %s\n" % (ms[f].upper(), f),
3270 3282 label='resolve.' +
3271 3283 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3272 3284 elif mark:
3273 3285 ms.mark(f, "r")
3274 3286 elif unmark:
3275 3287 ms.mark(f, "u")
3276 3288 else:
3277 3289 wctx = repo[None]
3278 3290 mctx = wctx.parents()[-1]
3279 3291
3280 3292 # backup pre-resolve (merge uses .orig for its own purposes)
3281 3293 a = repo.wjoin(f)
3282 3294 util.copyfile(a, a + ".resolve")
3283 3295
3284 3296 try:
3285 3297 # resolve file
3286 3298 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3287 3299 if ms.resolve(f, wctx, mctx):
3288 3300 ret = 1
3289 3301 finally:
3290 3302 ui.setconfig('ui', 'forcemerge', '')
3291 3303
3292 3304 # replace filemerge's .orig file with our resolve file
3293 3305 util.rename(a + ".resolve", a + ".orig")
3294 3306
3295 3307 ms.commit()
3296 3308 return ret
3297 3309
3298 3310 def revert(ui, repo, *pats, **opts):
3299 3311 """restore individual files or directories to an earlier state
3300 3312
3301 3313 .. note::
3302 3314 This command is most likely not what you are looking for.
3303 3315 Revert will partially overwrite content in the working
3304 3316 directory without changing the working directory parents. Use
3305 3317 :hg:`update -r rev` to check out earlier revisions, or
3306 3318 :hg:`update --clean .` to undo a merge which has added another
3307 3319 parent.
3308 3320
3309 3321 With no revision specified, revert the named files or directories
3310 3322 to the contents they had in the parent of the working directory.
3311 3323 This restores the contents of the affected files to an unmodified
3312 3324 state and unschedules adds, removes, copies, and renames. If the
3313 3325 working directory has two parents, you must explicitly specify a
3314 3326 revision.
3315 3327
3316 3328 Using the -r/--rev option, revert the given files or directories
3317 3329 to their contents as of a specific revision. This can be helpful
3318 3330 to "roll back" some or all of an earlier change. See :hg:`help
3319 3331 dates` for a list of formats valid for -d/--date.
3320 3332
3321 3333 Revert modifies the working directory. It does not commit any
3322 3334 changes, or change the parent of the working directory. If you
3323 3335 revert to a revision other than the parent of the working
3324 3336 directory, the reverted files will thus appear modified
3325 3337 afterwards.
3326 3338
3327 3339 If a file has been deleted, it is restored. If the executable mode
3328 3340 of a file was changed, it is reset.
3329 3341
3330 3342 If names are given, all files matching the names are reverted.
3331 3343 If no arguments are given, no files are reverted.
3332 3344
3333 3345 Modified files are saved with a .orig suffix before reverting.
3334 3346 To disable these backups, use --no-backup.
3335 3347
3336 3348 Returns 0 on success.
3337 3349 """
3338 3350
3339 3351 if opts.get("date"):
3340 3352 if opts.get("rev"):
3341 3353 raise util.Abort(_("you can't specify a revision and a date"))
3342 3354 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3343 3355
3344 3356 parent, p2 = repo.dirstate.parents()
3345 3357 if not opts.get('rev') and p2 != nullid:
3346 3358 raise util.Abort(_('uncommitted merge - '
3347 3359 'use "hg update", see "hg help revert"'))
3348 3360
3349 3361 if not pats and not opts.get('all'):
3350 3362 raise util.Abort(_('no files or directories specified; '
3351 3363 'use --all to revert the whole repo'))
3352 3364
3353 3365 ctx = cmdutil.revsingle(repo, opts.get('rev'))
3354 3366 node = ctx.node()
3355 3367 mf = ctx.manifest()
3356 3368 if node == parent:
3357 3369 pmf = mf
3358 3370 else:
3359 3371 pmf = None
3360 3372
3361 3373 # need all matching names in dirstate and manifest of target rev,
3362 3374 # so have to walk both. do not print errors if files exist in one
3363 3375 # but not other.
3364 3376
3365 3377 names = {}
3366 3378
3367 3379 wlock = repo.wlock()
3368 3380 try:
3369 3381 # walk dirstate.
3370 3382
3371 3383 m = cmdutil.match(repo, pats, opts)
3372 3384 m.bad = lambda x, y: False
3373 3385 for abs in repo.walk(m):
3374 3386 names[abs] = m.rel(abs), m.exact(abs)
3375 3387
3376 3388 # walk target manifest.
3377 3389
3378 3390 def badfn(path, msg):
3379 3391 if path in names:
3380 3392 return
3381 3393 path_ = path + '/'
3382 3394 for f in names:
3383 3395 if f.startswith(path_):
3384 3396 return
3385 3397 ui.warn("%s: %s\n" % (m.rel(path), msg))
3386 3398
3387 3399 m = cmdutil.match(repo, pats, opts)
3388 3400 m.bad = badfn
3389 3401 for abs in repo[node].walk(m):
3390 3402 if abs not in names:
3391 3403 names[abs] = m.rel(abs), m.exact(abs)
3392 3404
3393 3405 m = cmdutil.matchfiles(repo, names)
3394 3406 changes = repo.status(match=m)[:4]
3395 3407 modified, added, removed, deleted = map(set, changes)
3396 3408
3397 3409 # if f is a rename, also revert the source
3398 3410 cwd = repo.getcwd()
3399 3411 for f in added:
3400 3412 src = repo.dirstate.copied(f)
3401 3413 if src and src not in names and repo.dirstate[src] == 'r':
3402 3414 removed.add(src)
3403 3415 names[src] = (repo.pathto(src, cwd), True)
3404 3416
3405 3417 def removeforget(abs):
3406 3418 if repo.dirstate[abs] == 'a':
3407 3419 return _('forgetting %s\n')
3408 3420 return _('removing %s\n')
3409 3421
3410 3422 revert = ([], _('reverting %s\n'))
3411 3423 add = ([], _('adding %s\n'))
3412 3424 remove = ([], removeforget)
3413 3425 undelete = ([], _('undeleting %s\n'))
3414 3426
3415 3427 disptable = (
3416 3428 # dispatch table:
3417 3429 # file state
3418 3430 # action if in target manifest
3419 3431 # action if not in target manifest
3420 3432 # make backup if in target manifest
3421 3433 # make backup if not in target manifest
3422 3434 (modified, revert, remove, True, True),
3423 3435 (added, revert, remove, True, False),
3424 3436 (removed, undelete, None, False, False),
3425 3437 (deleted, revert, remove, False, False),
3426 3438 )
3427 3439
3428 3440 for abs, (rel, exact) in sorted(names.items()):
3429 3441 mfentry = mf.get(abs)
3430 3442 target = repo.wjoin(abs)
3431 3443 def handle(xlist, dobackup):
3432 3444 xlist[0].append(abs)
3433 3445 if (dobackup and not opts.get('no_backup') and
3434 3446 os.path.lexists(target)):
3435 3447 bakname = "%s.orig" % rel
3436 3448 ui.note(_('saving current version of %s as %s\n') %
3437 3449 (rel, bakname))
3438 3450 if not opts.get('dry_run'):
3439 3451 util.rename(target, bakname)
3440 3452 if ui.verbose or not exact:
3441 3453 msg = xlist[1]
3442 3454 if not isinstance(msg, basestring):
3443 3455 msg = msg(abs)
3444 3456 ui.status(msg % rel)
3445 3457 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3446 3458 if abs not in table:
3447 3459 continue
3448 3460 # file has changed in dirstate
3449 3461 if mfentry:
3450 3462 handle(hitlist, backuphit)
3451 3463 elif misslist is not None:
3452 3464 handle(misslist, backupmiss)
3453 3465 break
3454 3466 else:
3455 3467 if abs not in repo.dirstate:
3456 3468 if mfentry:
3457 3469 handle(add, True)
3458 3470 elif exact:
3459 3471 ui.warn(_('file not managed: %s\n') % rel)
3460 3472 continue
3461 3473 # file has not changed in dirstate
3462 3474 if node == parent:
3463 3475 if exact:
3464 3476 ui.warn(_('no changes needed to %s\n') % rel)
3465 3477 continue
3466 3478 if pmf is None:
3467 3479 # only need parent manifest in this unlikely case,
3468 3480 # so do not read by default
3469 3481 pmf = repo[parent].manifest()
3470 3482 if abs in pmf:
3471 3483 if mfentry:
3472 3484 # if version of file is same in parent and target
3473 3485 # manifests, do nothing
3474 3486 if (pmf[abs] != mfentry or
3475 3487 pmf.flags(abs) != mf.flags(abs)):
3476 3488 handle(revert, False)
3477 3489 else:
3478 3490 handle(remove, False)
3479 3491
3480 3492 if not opts.get('dry_run'):
3481 3493 def checkout(f):
3482 3494 fc = ctx[f]
3483 3495 repo.wwrite(f, fc.data(), fc.flags())
3484 3496
3485 3497 audit_path = util.path_auditor(repo.root)
3486 3498 for f in remove[0]:
3487 3499 if repo.dirstate[f] == 'a':
3488 3500 repo.dirstate.forget(f)
3489 3501 continue
3490 3502 audit_path(f)
3491 3503 try:
3492 3504 util.unlinkpath(repo.wjoin(f))
3493 3505 except OSError:
3494 3506 pass
3495 3507 repo.dirstate.remove(f)
3496 3508
3497 3509 normal = None
3498 3510 if node == parent:
3499 3511 # We're reverting to our parent. If possible, we'd like status
3500 3512 # to report the file as clean. We have to use normallookup for
3501 3513 # merges to avoid losing information about merged/dirty files.
3502 3514 if p2 != nullid:
3503 3515 normal = repo.dirstate.normallookup
3504 3516 else:
3505 3517 normal = repo.dirstate.normal
3506 3518 for f in revert[0]:
3507 3519 checkout(f)
3508 3520 if normal:
3509 3521 normal(f)
3510 3522
3511 3523 for f in add[0]:
3512 3524 checkout(f)
3513 3525 repo.dirstate.add(f)
3514 3526
3515 3527 normal = repo.dirstate.normallookup
3516 3528 if node == parent and p2 == nullid:
3517 3529 normal = repo.dirstate.normal
3518 3530 for f in undelete[0]:
3519 3531 checkout(f)
3520 3532 normal(f)
3521 3533
3522 3534 finally:
3523 3535 wlock.release()
3524 3536
3525 3537 def rollback(ui, repo, **opts):
3526 3538 """roll back the last transaction (dangerous)
3527 3539
3528 3540 This command should be used with care. There is only one level of
3529 3541 rollback, and there is no way to undo a rollback. It will also
3530 3542 restore the dirstate at the time of the last transaction, losing
3531 3543 any dirstate changes since that time. This command does not alter
3532 3544 the working directory.
3533 3545
3534 3546 Transactions are used to encapsulate the effects of all commands
3535 3547 that create new changesets or propagate existing changesets into a
3536 3548 repository. For example, the following commands are transactional,
3537 3549 and their effects can be rolled back:
3538 3550
3539 3551 - commit
3540 3552 - import
3541 3553 - pull
3542 3554 - push (with this repository as the destination)
3543 3555 - unbundle
3544 3556
3545 3557 This command is not intended for use on public repositories. Once
3546 3558 changes are visible for pull by other users, rolling a transaction
3547 3559 back locally is ineffective (someone else may already have pulled
3548 3560 the changes). Furthermore, a race is possible with readers of the
3549 3561 repository; for example an in-progress pull from the repository
3550 3562 may fail if a rollback is performed.
3551 3563
3552 3564 Returns 0 on success, 1 if no rollback data is available.
3553 3565 """
3554 3566 return repo.rollback(opts.get('dry_run'))
3555 3567
3556 3568 def root(ui, repo):
3557 3569 """print the root (top) of the current working directory
3558 3570
3559 3571 Print the root directory of the current repository.
3560 3572
3561 3573 Returns 0 on success.
3562 3574 """
3563 3575 ui.write(repo.root + "\n")
3564 3576
3565 3577 def serve(ui, repo, **opts):
3566 3578 """start stand-alone webserver
3567 3579
3568 3580 Start a local HTTP repository browser and pull server. You can use
3569 3581 this for ad-hoc sharing and browsing of repositories. It is
3570 3582 recommended to use a real web server to serve a repository for
3571 3583 longer periods of time.
3572 3584
3573 3585 Please note that the server does not implement access control.
3574 3586 This means that, by default, anybody can read from the server and
3575 3587 nobody can write to it by default. Set the ``web.allow_push``
3576 3588 option to ``*`` to allow everybody to push to the server. You
3577 3589 should use a real web server if you need to authenticate users.
3578 3590
3579 3591 By default, the server logs accesses to stdout and errors to
3580 3592 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3581 3593 files.
3582 3594
3583 3595 To have the server choose a free port number to listen on, specify
3584 3596 a port number of 0; in this case, the server will print the port
3585 3597 number it uses.
3586 3598
3587 3599 Returns 0 on success.
3588 3600 """
3589 3601
3590 3602 if opts["stdio"]:
3591 3603 if repo is None:
3592 3604 raise error.RepoError(_("There is no Mercurial repository here"
3593 3605 " (.hg not found)"))
3594 3606 s = sshserver.sshserver(ui, repo)
3595 3607 s.serve_forever()
3596 3608
3597 3609 # this way we can check if something was given in the command-line
3598 3610 if opts.get('port'):
3599 3611 opts['port'] = util.getport(opts.get('port'))
3600 3612
3601 3613 baseui = repo and repo.baseui or ui
3602 3614 optlist = ("name templates style address port prefix ipv6"
3603 3615 " accesslog errorlog certificate encoding")
3604 3616 for o in optlist.split():
3605 3617 val = opts.get(o, '')
3606 3618 if val in (None, ''): # should check against default options instead
3607 3619 continue
3608 3620 baseui.setconfig("web", o, val)
3609 3621 if repo and repo.ui != baseui:
3610 3622 repo.ui.setconfig("web", o, val)
3611 3623
3612 3624 o = opts.get('web_conf') or opts.get('webdir_conf')
3613 3625 if not o:
3614 3626 if not repo:
3615 3627 raise error.RepoError(_("There is no Mercurial repository"
3616 3628 " here (.hg not found)"))
3617 3629 o = repo.root
3618 3630
3619 3631 app = hgweb.hgweb(o, baseui=ui)
3620 3632
3621 3633 class service(object):
3622 3634 def init(self):
3623 3635 util.set_signal_handler()
3624 3636 self.httpd = hgweb.server.create_server(ui, app)
3625 3637
3626 3638 if opts['port'] and not ui.verbose:
3627 3639 return
3628 3640
3629 3641 if self.httpd.prefix:
3630 3642 prefix = self.httpd.prefix.strip('/') + '/'
3631 3643 else:
3632 3644 prefix = ''
3633 3645
3634 3646 port = ':%d' % self.httpd.port
3635 3647 if port == ':80':
3636 3648 port = ''
3637 3649
3638 3650 bindaddr = self.httpd.addr
3639 3651 if bindaddr == '0.0.0.0':
3640 3652 bindaddr = '*'
3641 3653 elif ':' in bindaddr: # IPv6
3642 3654 bindaddr = '[%s]' % bindaddr
3643 3655
3644 3656 fqaddr = self.httpd.fqaddr
3645 3657 if ':' in fqaddr:
3646 3658 fqaddr = '[%s]' % fqaddr
3647 3659 if opts['port']:
3648 3660 write = ui.status
3649 3661 else:
3650 3662 write = ui.write
3651 3663 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3652 3664 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3653 3665
3654 3666 def run(self):
3655 3667 self.httpd.serve_forever()
3656 3668
3657 3669 service = service()
3658 3670
3659 3671 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3660 3672
3661 3673 def status(ui, repo, *pats, **opts):
3662 3674 """show changed files in the working directory
3663 3675
3664 3676 Show status of files in the repository. If names are given, only
3665 3677 files that match are shown. Files that are clean or ignored or
3666 3678 the source of a copy/move operation, are not listed unless
3667 3679 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3668 3680 Unless options described with "show only ..." are given, the
3669 3681 options -mardu are used.
3670 3682
3671 3683 Option -q/--quiet hides untracked (unknown and ignored) files
3672 3684 unless explicitly requested with -u/--unknown or -i/--ignored.
3673 3685
3674 3686 .. note::
3675 3687 status may appear to disagree with diff if permissions have
3676 3688 changed or a merge has occurred. The standard diff format does
3677 3689 not report permission changes and diff only reports changes
3678 3690 relative to one merge parent.
3679 3691
3680 3692 If one revision is given, it is used as the base revision.
3681 3693 If two revisions are given, the differences between them are
3682 3694 shown. The --change option can also be used as a shortcut to list
3683 3695 the changed files of a revision from its first parent.
3684 3696
3685 3697 The codes used to show the status of files are::
3686 3698
3687 3699 M = modified
3688 3700 A = added
3689 3701 R = removed
3690 3702 C = clean
3691 3703 ! = missing (deleted by non-hg command, but still tracked)
3692 3704 ? = not tracked
3693 3705 I = ignored
3694 3706 = origin of the previous file listed as A (added)
3695 3707
3696 3708 Returns 0 on success.
3697 3709 """
3698 3710
3699 3711 revs = opts.get('rev')
3700 3712 change = opts.get('change')
3701 3713
3702 3714 if revs and change:
3703 3715 msg = _('cannot specify --rev and --change at the same time')
3704 3716 raise util.Abort(msg)
3705 3717 elif change:
3706 3718 node2 = repo.lookup(change)
3707 3719 node1 = repo[node2].parents()[0].node()
3708 3720 else:
3709 3721 node1, node2 = cmdutil.revpair(repo, revs)
3710 3722
3711 3723 cwd = (pats and repo.getcwd()) or ''
3712 3724 end = opts.get('print0') and '\0' or '\n'
3713 3725 copy = {}
3714 3726 states = 'modified added removed deleted unknown ignored clean'.split()
3715 3727 show = [k for k in states if opts.get(k)]
3716 3728 if opts.get('all'):
3717 3729 show += ui.quiet and (states[:4] + ['clean']) or states
3718 3730 if not show:
3719 3731 show = ui.quiet and states[:4] or states[:5]
3720 3732
3721 3733 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3722 3734 'ignored' in show, 'clean' in show, 'unknown' in show,
3723 3735 opts.get('subrepos'))
3724 3736 changestates = zip(states, 'MAR!?IC', stat)
3725 3737
3726 3738 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3727 3739 ctxn = repo[nullid]
3728 3740 ctx1 = repo[node1]
3729 3741 ctx2 = repo[node2]
3730 3742 added = stat[1]
3731 3743 if node2 is None:
3732 3744 added = stat[0] + stat[1] # merged?
3733 3745
3734 3746 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3735 3747 if k in added:
3736 3748 copy[k] = v
3737 3749 elif v in added:
3738 3750 copy[v] = k
3739 3751
3740 3752 for state, char, files in changestates:
3741 3753 if state in show:
3742 3754 format = "%s %%s%s" % (char, end)
3743 3755 if opts.get('no_status'):
3744 3756 format = "%%s%s" % end
3745 3757
3746 3758 for f in files:
3747 3759 ui.write(format % repo.pathto(f, cwd),
3748 3760 label='status.' + state)
3749 3761 if f in copy:
3750 3762 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3751 3763 label='status.copied')
3752 3764
3753 3765 def summary(ui, repo, **opts):
3754 3766 """summarize working directory state
3755 3767
3756 3768 This generates a brief summary of the working directory state,
3757 3769 including parents, branch, commit status, and available updates.
3758 3770
3759 3771 With the --remote option, this will check the default paths for
3760 3772 incoming and outgoing changes. This can be time-consuming.
3761 3773
3762 3774 Returns 0 on success.
3763 3775 """
3764 3776
3765 3777 ctx = repo[None]
3766 3778 parents = ctx.parents()
3767 3779 pnode = parents[0].node()
3768 3780
3769 3781 for p in parents:
3770 3782 # label with log.changeset (instead of log.parent) since this
3771 3783 # shows a working directory parent *changeset*:
3772 3784 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3773 3785 label='log.changeset')
3774 3786 ui.write(' '.join(p.tags()), label='log.tag')
3775 3787 if p.bookmarks():
3776 3788 ui.write(' ' + ' '.join(p.bookmarks()), label='log.bookmark')
3777 3789 if p.rev() == -1:
3778 3790 if not len(repo):
3779 3791 ui.write(_(' (empty repository)'))
3780 3792 else:
3781 3793 ui.write(_(' (no revision checked out)'))
3782 3794 ui.write('\n')
3783 3795 if p.description():
3784 3796 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3785 3797 label='log.summary')
3786 3798
3787 3799 branch = ctx.branch()
3788 3800 bheads = repo.branchheads(branch)
3789 3801 m = _('branch: %s\n') % branch
3790 3802 if branch != 'default':
3791 3803 ui.write(m, label='log.branch')
3792 3804 else:
3793 3805 ui.status(m, label='log.branch')
3794 3806
3795 3807 st = list(repo.status(unknown=True))[:6]
3796 3808
3797 3809 c = repo.dirstate.copies()
3798 3810 copied, renamed = [], []
3799 3811 for d, s in c.iteritems():
3800 3812 if s in st[2]:
3801 3813 st[2].remove(s)
3802 3814 renamed.append(d)
3803 3815 else:
3804 3816 copied.append(d)
3805 3817 if d in st[1]:
3806 3818 st[1].remove(d)
3807 3819 st.insert(3, renamed)
3808 3820 st.insert(4, copied)
3809 3821
3810 3822 ms = mergemod.mergestate(repo)
3811 3823 st.append([f for f in ms if ms[f] == 'u'])
3812 3824
3813 3825 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3814 3826 st.append(subs)
3815 3827
3816 3828 labels = [ui.label(_('%d modified'), 'status.modified'),
3817 3829 ui.label(_('%d added'), 'status.added'),
3818 3830 ui.label(_('%d removed'), 'status.removed'),
3819 3831 ui.label(_('%d renamed'), 'status.copied'),
3820 3832 ui.label(_('%d copied'), 'status.copied'),
3821 3833 ui.label(_('%d deleted'), 'status.deleted'),
3822 3834 ui.label(_('%d unknown'), 'status.unknown'),
3823 3835 ui.label(_('%d ignored'), 'status.ignored'),
3824 3836 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3825 3837 ui.label(_('%d subrepos'), 'status.modified')]
3826 3838 t = []
3827 3839 for s, l in zip(st, labels):
3828 3840 if s:
3829 3841 t.append(l % len(s))
3830 3842
3831 3843 t = ', '.join(t)
3832 3844 cleanworkdir = False
3833 3845
3834 3846 if len(parents) > 1:
3835 3847 t += _(' (merge)')
3836 3848 elif branch != parents[0].branch():
3837 3849 t += _(' (new branch)')
3838 3850 elif (parents[0].extra().get('close') and
3839 3851 pnode in repo.branchheads(branch, closed=True)):
3840 3852 t += _(' (head closed)')
3841 3853 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3842 3854 t += _(' (clean)')
3843 3855 cleanworkdir = True
3844 3856 elif pnode not in bheads:
3845 3857 t += _(' (new branch head)')
3846 3858
3847 3859 if cleanworkdir:
3848 3860 ui.status(_('commit: %s\n') % t.strip())
3849 3861 else:
3850 3862 ui.write(_('commit: %s\n') % t.strip())
3851 3863
3852 3864 # all ancestors of branch heads - all ancestors of parent = new csets
3853 3865 new = [0] * len(repo)
3854 3866 cl = repo.changelog
3855 3867 for a in [cl.rev(n) for n in bheads]:
3856 3868 new[a] = 1
3857 3869 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3858 3870 new[a] = 1
3859 3871 for a in [p.rev() for p in parents]:
3860 3872 if a >= 0:
3861 3873 new[a] = 0
3862 3874 for a in cl.ancestors(*[p.rev() for p in parents]):
3863 3875 new[a] = 0
3864 3876 new = sum(new)
3865 3877
3866 3878 if new == 0:
3867 3879 ui.status(_('update: (current)\n'))
3868 3880 elif pnode not in bheads:
3869 3881 ui.write(_('update: %d new changesets (update)\n') % new)
3870 3882 else:
3871 3883 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3872 3884 (new, len(bheads)))
3873 3885
3874 3886 if opts.get('remote'):
3875 3887 t = []
3876 3888 source, branches = hg.parseurl(ui.expandpath('default'))
3877 3889 other = hg.repository(hg.remoteui(repo, {}), source)
3878 3890 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3879 3891 ui.debug('comparing with %s\n' % url.hidepassword(source))
3880 3892 repo.ui.pushbuffer()
3881 3893 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3882 3894 repo.ui.popbuffer()
3883 3895 if incoming:
3884 3896 t.append(_('1 or more incoming'))
3885 3897
3886 3898 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3887 3899 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3888 3900 other = hg.repository(hg.remoteui(repo, {}), dest)
3889 3901 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3890 3902 repo.ui.pushbuffer()
3891 3903 o = discovery.findoutgoing(repo, other)
3892 3904 repo.ui.popbuffer()
3893 3905 o = repo.changelog.nodesbetween(o, None)[0]
3894 3906 if o:
3895 3907 t.append(_('%d outgoing') % len(o))
3896 3908 if 'bookmarks' in other.listkeys('namespaces'):
3897 3909 lmarks = repo.listkeys('bookmarks')
3898 3910 rmarks = other.listkeys('bookmarks')
3899 3911 diff = set(rmarks) - set(lmarks)
3900 3912 if len(diff) > 0:
3901 3913 t.append(_('%d incoming bookmarks') % len(diff))
3902 3914 diff = set(lmarks) - set(rmarks)
3903 3915 if len(diff) > 0:
3904 3916 t.append(_('%d outgoing bookmarks') % len(diff))
3905 3917
3906 3918 if t:
3907 3919 ui.write(_('remote: %s\n') % (', '.join(t)))
3908 3920 else:
3909 3921 ui.status(_('remote: (synced)\n'))
3910 3922
3911 3923 def tag(ui, repo, name1, *names, **opts):
3912 3924 """add one or more tags for the current or given revision
3913 3925
3914 3926 Name a particular revision using <name>.
3915 3927
3916 3928 Tags are used to name particular revisions of the repository and are
3917 3929 very useful to compare different revisions, to go back to significant
3918 3930 earlier versions or to mark branch points as releases, etc. Changing
3919 3931 an existing tag is normally disallowed; use -f/--force to override.
3920 3932
3921 3933 If no revision is given, the parent of the working directory is
3922 3934 used, or tip if no revision is checked out.
3923 3935
3924 3936 To facilitate version control, distribution, and merging of tags,
3925 3937 they are stored as a file named ".hgtags" which is managed similarly
3926 3938 to other project files and can be hand-edited if necessary. This
3927 3939 also means that tagging creates a new commit. The file
3928 3940 ".hg/localtags" is used for local tags (not shared among
3929 3941 repositories).
3930 3942
3931 3943 Tag commits are usually made at the head of a branch. If the parent
3932 3944 of the working directory is not a branch head, :hg:`tag` aborts; use
3933 3945 -f/--force to force the tag commit to be based on a non-head
3934 3946 changeset.
3935 3947
3936 3948 See :hg:`help dates` for a list of formats valid for -d/--date.
3937 3949
3938 3950 Since tag names have priority over branch names during revision
3939 3951 lookup, using an existing branch name as a tag name is discouraged.
3940 3952
3941 3953 Returns 0 on success.
3942 3954 """
3943 3955
3944 3956 rev_ = "."
3945 3957 names = [t.strip() for t in (name1,) + names]
3946 3958 if len(names) != len(set(names)):
3947 3959 raise util.Abort(_('tag names must be unique'))
3948 3960 for n in names:
3949 3961 if n in ['tip', '.', 'null']:
3950 3962 raise util.Abort(_('the name \'%s\' is reserved') % n)
3951 3963 if not n:
3952 3964 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
3953 3965 if opts.get('rev') and opts.get('remove'):
3954 3966 raise util.Abort(_("--rev and --remove are incompatible"))
3955 3967 if opts.get('rev'):
3956 3968 rev_ = opts['rev']
3957 3969 message = opts.get('message')
3958 3970 if opts.get('remove'):
3959 3971 expectedtype = opts.get('local') and 'local' or 'global'
3960 3972 for n in names:
3961 3973 if not repo.tagtype(n):
3962 3974 raise util.Abort(_('tag \'%s\' does not exist') % n)
3963 3975 if repo.tagtype(n) != expectedtype:
3964 3976 if expectedtype == 'global':
3965 3977 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3966 3978 else:
3967 3979 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3968 3980 rev_ = nullid
3969 3981 if not message:
3970 3982 # we don't translate commit messages
3971 3983 message = 'Removed tag %s' % ', '.join(names)
3972 3984 elif not opts.get('force'):
3973 3985 for n in names:
3974 3986 if n in repo.tags():
3975 3987 raise util.Abort(_('tag \'%s\' already exists '
3976 3988 '(use -f to force)') % n)
3977 3989 if not opts.get('local'):
3978 3990 p1, p2 = repo.dirstate.parents()
3979 3991 if p2 != nullid:
3980 3992 raise util.Abort(_('uncommitted merge'))
3981 3993 bheads = repo.branchheads()
3982 3994 if not opts.get('force') and bheads and p1 not in bheads:
3983 3995 raise util.Abort(_('not at a branch head (use -f to force)'))
3984 3996 r = cmdutil.revsingle(repo, rev_).node()
3985 3997
3986 3998 if not message:
3987 3999 # we don't translate commit messages
3988 4000 message = ('Added tag %s for changeset %s' %
3989 4001 (', '.join(names), short(r)))
3990 4002
3991 4003 date = opts.get('date')
3992 4004 if date:
3993 4005 date = util.parsedate(date)
3994 4006
3995 4007 if opts.get('edit'):
3996 4008 message = ui.edit(message, ui.username())
3997 4009
3998 4010 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3999 4011
4000 4012 def tags(ui, repo):
4001 4013 """list repository tags
4002 4014
4003 4015 This lists both regular and local tags. When the -v/--verbose
4004 4016 switch is used, a third column "local" is printed for local tags.
4005 4017
4006 4018 Returns 0 on success.
4007 4019 """
4008 4020
4009 4021 hexfunc = ui.debugflag and hex or short
4010 4022 tagtype = ""
4011 4023
4012 4024 for t, n in reversed(repo.tagslist()):
4013 4025 if ui.quiet:
4014 4026 ui.write("%s\n" % t)
4015 4027 continue
4016 4028
4017 4029 try:
4018 4030 hn = hexfunc(n)
4019 4031 r = "%5d:%s" % (repo.changelog.rev(n), hn)
4020 4032 except error.LookupError:
4021 4033 r = " ?:%s" % hn
4022 4034 else:
4023 4035 spaces = " " * (30 - encoding.colwidth(t))
4024 4036 if ui.verbose:
4025 4037 if repo.tagtype(t) == 'local':
4026 4038 tagtype = " local"
4027 4039 else:
4028 4040 tagtype = ""
4029 4041 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
4030 4042
4031 4043 def tip(ui, repo, **opts):
4032 4044 """show the tip revision
4033 4045
4034 4046 The tip revision (usually just called the tip) is the changeset
4035 4047 most recently added to the repository (and therefore the most
4036 4048 recently changed head).
4037 4049
4038 4050 If you have just made a commit, that commit will be the tip. If
4039 4051 you have just pulled changes from another repository, the tip of
4040 4052 that repository becomes the current tip. The "tip" tag is special
4041 4053 and cannot be renamed or assigned to a different changeset.
4042 4054
4043 4055 Returns 0 on success.
4044 4056 """
4045 4057 displayer = cmdutil.show_changeset(ui, repo, opts)
4046 4058 displayer.show(repo[len(repo) - 1])
4047 4059 displayer.close()
4048 4060
4049 4061 def unbundle(ui, repo, fname1, *fnames, **opts):
4050 4062 """apply one or more changegroup files
4051 4063
4052 4064 Apply one or more compressed changegroup files generated by the
4053 4065 bundle command.
4054 4066
4055 4067 Returns 0 on success, 1 if an update has unresolved files.
4056 4068 """
4057 4069 fnames = (fname1,) + fnames
4058 4070
4059 4071 lock = repo.lock()
4060 4072 wc = repo['.']
4061 4073 try:
4062 4074 for fname in fnames:
4063 4075 f = url.open(ui, fname)
4064 4076 gen = changegroup.readbundle(f, fname)
4065 4077 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
4066 4078 lock=lock)
4067 4079 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
4068 4080 finally:
4069 4081 lock.release()
4070 4082 return postincoming(ui, repo, modheads, opts.get('update'), None)
4071 4083
4072 4084 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
4073 4085 """update working directory (or switch revisions)
4074 4086
4075 4087 Update the repository's working directory to the specified
4076 4088 changeset. If no changeset is specified, update to the tip of the
4077 4089 current named branch.
4078 4090
4079 4091 If the changeset is not a descendant of the working directory's
4080 4092 parent, the update is aborted. With the -c/--check option, the
4081 4093 working directory is checked for uncommitted changes; if none are
4082 4094 found, the working directory is updated to the specified
4083 4095 changeset.
4084 4096
4085 4097 The following rules apply when the working directory contains
4086 4098 uncommitted changes:
4087 4099
4088 4100 1. If neither -c/--check nor -C/--clean is specified, and if
4089 4101 the requested changeset is an ancestor or descendant of
4090 4102 the working directory's parent, the uncommitted changes
4091 4103 are merged into the requested changeset and the merged
4092 4104 result is left uncommitted. If the requested changeset is
4093 4105 not an ancestor or descendant (that is, it is on another
4094 4106 branch), the update is aborted and the uncommitted changes
4095 4107 are preserved.
4096 4108
4097 4109 2. With the -c/--check option, the update is aborted and the
4098 4110 uncommitted changes are preserved.
4099 4111
4100 4112 3. With the -C/--clean option, uncommitted changes are discarded and
4101 4113 the working directory is updated to the requested changeset.
4102 4114
4103 4115 Use null as the changeset to remove the working directory (like
4104 4116 :hg:`clone -U`).
4105 4117
4106 4118 If you want to update just one file to an older changeset, use
4107 4119 :hg:`revert`.
4108 4120
4109 4121 See :hg:`help dates` for a list of formats valid for -d/--date.
4110 4122
4111 4123 Returns 0 on success, 1 if there are unresolved files.
4112 4124 """
4113 4125 if rev and node:
4114 4126 raise util.Abort(_("please specify just one revision"))
4115 4127
4116 4128 if rev is None or rev == '':
4117 4129 rev = node
4118 4130
4119 4131 # if we defined a bookmark, we have to remember the original bookmark name
4120 4132 brev = rev
4121 4133 rev = cmdutil.revsingle(repo, rev, rev).rev()
4122 4134
4123 4135 if check and clean:
4124 4136 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
4125 4137
4126 4138 if check:
4127 4139 # we could use dirty() but we can ignore merge and branch trivia
4128 4140 c = repo[None]
4129 4141 if c.modified() or c.added() or c.removed():
4130 4142 raise util.Abort(_("uncommitted local changes"))
4131 4143
4132 4144 if date:
4133 4145 if rev:
4134 4146 raise util.Abort(_("you can't specify a revision and a date"))
4135 4147 rev = cmdutil.finddate(ui, repo, date)
4136 4148
4137 4149 if clean or check:
4138 4150 ret = hg.clean(repo, rev)
4139 4151 else:
4140 4152 ret = hg.update(repo, rev)
4141 4153
4142 4154 if brev in repo._bookmarks:
4143 4155 bookmarks.setcurrent(repo, brev)
4144 4156
4145 4157 return ret
4146 4158
4147 4159 def verify(ui, repo):
4148 4160 """verify the integrity of the repository
4149 4161
4150 4162 Verify the integrity of the current repository.
4151 4163
4152 4164 This will perform an extensive check of the repository's
4153 4165 integrity, validating the hashes and checksums of each entry in
4154 4166 the changelog, manifest, and tracked files, as well as the
4155 4167 integrity of their crosslinks and indices.
4156 4168
4157 4169 Returns 0 on success, 1 if errors are encountered.
4158 4170 """
4159 4171 return hg.verify(repo)
4160 4172
4161 4173 def version_(ui):
4162 4174 """output version and copyright information"""
4163 4175 ui.write(_("Mercurial Distributed SCM (version %s)\n")
4164 4176 % util.version())
4165 4177 ui.status(_(
4166 4178 "(see http://mercurial.selenic.com for more information)\n"
4167 4179 "\nCopyright (C) 2005-2011 Matt Mackall and others\n"
4168 4180 "This is free software; see the source for copying conditions. "
4169 4181 "There is NO\nwarranty; "
4170 4182 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
4171 4183 ))
4172 4184
4173 4185 # Command options and aliases are listed here, alphabetically
4174 4186
4175 4187 globalopts = [
4176 4188 ('R', 'repository', '',
4177 4189 _('repository root directory or name of overlay bundle file'),
4178 4190 _('REPO')),
4179 4191 ('', 'cwd', '',
4180 4192 _('change working directory'), _('DIR')),
4181 4193 ('y', 'noninteractive', None,
4182 4194 _('do not prompt, assume \'yes\' for any required answers')),
4183 4195 ('q', 'quiet', None, _('suppress output')),
4184 4196 ('v', 'verbose', None, _('enable additional output')),
4185 4197 ('', 'config', [],
4186 4198 _('set/override config option (use \'section.name=value\')'),
4187 4199 _('CONFIG')),
4188 4200 ('', 'debug', None, _('enable debugging output')),
4189 4201 ('', 'debugger', None, _('start debugger')),
4190 4202 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
4191 4203 _('ENCODE')),
4192 4204 ('', 'encodingmode', encoding.encodingmode,
4193 4205 _('set the charset encoding mode'), _('MODE')),
4194 4206 ('', 'traceback', None, _('always print a traceback on exception')),
4195 4207 ('', 'time', None, _('time how long the command takes')),
4196 4208 ('', 'profile', None, _('print command execution profile')),
4197 4209 ('', 'version', None, _('output version information and exit')),
4198 4210 ('h', 'help', None, _('display help and exit')),
4199 4211 ]
4200 4212
4201 4213 dryrunopts = [('n', 'dry-run', None,
4202 4214 _('do not perform actions, just print output'))]
4203 4215
4204 4216 remoteopts = [
4205 4217 ('e', 'ssh', '',
4206 4218 _('specify ssh command to use'), _('CMD')),
4207 4219 ('', 'remotecmd', '',
4208 4220 _('specify hg command to run on the remote side'), _('CMD')),
4209 4221 ('', 'insecure', None,
4210 4222 _('do not verify server certificate (ignoring web.cacerts config)')),
4211 4223 ]
4212 4224
4213 4225 walkopts = [
4214 4226 ('I', 'include', [],
4215 4227 _('include names matching the given patterns'), _('PATTERN')),
4216 4228 ('X', 'exclude', [],
4217 4229 _('exclude names matching the given patterns'), _('PATTERN')),
4218 4230 ]
4219 4231
4220 4232 commitopts = [
4221 4233 ('m', 'message', '',
4222 4234 _('use text as commit message'), _('TEXT')),
4223 4235 ('l', 'logfile', '',
4224 4236 _('read commit message from file'), _('FILE')),
4225 4237 ]
4226 4238
4227 4239 commitopts2 = [
4228 4240 ('d', 'date', '',
4229 4241 _('record datecode as commit date'), _('DATE')),
4230 4242 ('u', 'user', '',
4231 4243 _('record the specified user as committer'), _('USER')),
4232 4244 ]
4233 4245
4234 4246 templateopts = [
4235 4247 ('', 'style', '',
4236 4248 _('display using template map file'), _('STYLE')),
4237 4249 ('', 'template', '',
4238 4250 _('display with template'), _('TEMPLATE')),
4239 4251 ]
4240 4252
4241 4253 logopts = [
4242 4254 ('p', 'patch', None, _('show patch')),
4243 4255 ('g', 'git', None, _('use git extended diff format')),
4244 4256 ('l', 'limit', '',
4245 4257 _('limit number of changes displayed'), _('NUM')),
4246 4258 ('M', 'no-merges', None, _('do not show merges')),
4247 4259 ('', 'stat', None, _('output diffstat-style summary of changes')),
4248 4260 ] + templateopts
4249 4261
4250 4262 diffopts = [
4251 4263 ('a', 'text', None, _('treat all files as text')),
4252 4264 ('g', 'git', None, _('use git extended diff format')),
4253 4265 ('', 'nodates', None, _('omit dates from diff headers'))
4254 4266 ]
4255 4267
4256 4268 diffopts2 = [
4257 4269 ('p', 'show-function', None, _('show which function each change is in')),
4258 4270 ('', 'reverse', None, _('produce a diff that undoes the changes')),
4259 4271 ('w', 'ignore-all-space', None,
4260 4272 _('ignore white space when comparing lines')),
4261 4273 ('b', 'ignore-space-change', None,
4262 4274 _('ignore changes in the amount of white space')),
4263 4275 ('B', 'ignore-blank-lines', None,
4264 4276 _('ignore changes whose lines are all blank')),
4265 4277 ('U', 'unified', '',
4266 4278 _('number of lines of context to show'), _('NUM')),
4267 4279 ('', 'stat', None, _('output diffstat-style summary of changes')),
4268 4280 ]
4269 4281
4270 4282 similarityopts = [
4271 4283 ('s', 'similarity', '',
4272 4284 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
4273 4285 ]
4274 4286
4275 4287 subrepoopts = [
4276 4288 ('S', 'subrepos', None,
4277 4289 _('recurse into subrepositories'))
4278 4290 ]
4279 4291
4280 4292 table = {
4281 4293 "^add": (add, walkopts + subrepoopts + dryrunopts,
4282 4294 _('[OPTION]... [FILE]...')),
4283 4295 "addremove":
4284 4296 (addremove, similarityopts + walkopts + dryrunopts,
4285 4297 _('[OPTION]... [FILE]...')),
4286 4298 "^annotate|blame":
4287 4299 (annotate,
4288 4300 [('r', 'rev', '',
4289 4301 _('annotate the specified revision'), _('REV')),
4290 4302 ('', 'follow', None,
4291 4303 _('follow copies/renames and list the filename (DEPRECATED)')),
4292 4304 ('', 'no-follow', None, _("don't follow copies and renames")),
4293 4305 ('a', 'text', None, _('treat all files as text')),
4294 4306 ('u', 'user', None, _('list the author (long with -v)')),
4295 4307 ('f', 'file', None, _('list the filename')),
4296 4308 ('d', 'date', None, _('list the date (short with -q)')),
4297 4309 ('n', 'number', None, _('list the revision number (default)')),
4298 4310 ('c', 'changeset', None, _('list the changeset')),
4299 4311 ('l', 'line-number', None,
4300 4312 _('show line number at the first appearance'))
4301 4313 ] + walkopts,
4302 4314 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
4303 4315 "archive":
4304 4316 (archive,
4305 4317 [('', 'no-decode', None, _('do not pass files through decoders')),
4306 4318 ('p', 'prefix', '',
4307 4319 _('directory prefix for files in archive'), _('PREFIX')),
4308 4320 ('r', 'rev', '',
4309 4321 _('revision to distribute'), _('REV')),
4310 4322 ('t', 'type', '',
4311 4323 _('type of distribution to create'), _('TYPE')),
4312 4324 ] + subrepoopts + walkopts,
4313 4325 _('[OPTION]... DEST')),
4314 4326 "backout":
4315 4327 (backout,
4316 4328 [('', 'merge', None,
4317 4329 _('merge with old dirstate parent after backout')),
4318 4330 ('', 'parent', '',
4319 4331 _('parent to choose when backing out merge'), _('REV')),
4320 4332 ('t', 'tool', '',
4321 4333 _('specify merge tool')),
4322 4334 ('r', 'rev', '',
4323 4335 _('revision to backout'), _('REV')),
4324 4336 ] + walkopts + commitopts + commitopts2,
4325 4337 _('[OPTION]... [-r] REV')),
4326 4338 "bisect":
4327 4339 (bisect,
4328 4340 [('r', 'reset', False, _('reset bisect state')),
4329 4341 ('g', 'good', False, _('mark changeset good')),
4330 4342 ('b', 'bad', False, _('mark changeset bad')),
4331 4343 ('s', 'skip', False, _('skip testing changeset')),
4332 4344 ('e', 'extend', False, _('extend the bisect range')),
4333 4345 ('c', 'command', '',
4334 4346 _('use command to check changeset state'), _('CMD')),
4335 4347 ('U', 'noupdate', False, _('do not update to target'))],
4336 4348 _("[-gbsr] [-U] [-c CMD] [REV]")),
4337 4349 "bookmarks":
4338 4350 (bookmark,
4339 4351 [('f', 'force', False, _('force')),
4340 4352 ('r', 'rev', '', _('revision'), _('REV')),
4341 4353 ('d', 'delete', False, _('delete a given bookmark')),
4342 4354 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
4343 4355 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
4344 4356 "branch":
4345 4357 (branch,
4346 4358 [('f', 'force', None,
4347 4359 _('set branch name even if it shadows an existing branch')),
4348 4360 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4349 4361 _('[-fC] [NAME]')),
4350 4362 "branches":
4351 4363 (branches,
4352 4364 [('a', 'active', False,
4353 4365 _('show only branches that have unmerged heads')),
4354 4366 ('c', 'closed', False,
4355 4367 _('show normal and closed branches'))],
4356 4368 _('[-ac]')),
4357 4369 "bundle":
4358 4370 (bundle,
4359 4371 [('f', 'force', None,
4360 4372 _('run even when the destination is unrelated')),
4361 4373 ('r', 'rev', [],
4362 4374 _('a changeset intended to be added to the destination'),
4363 4375 _('REV')),
4364 4376 ('b', 'branch', [],
4365 4377 _('a specific branch you would like to bundle'),
4366 4378 _('BRANCH')),
4367 4379 ('', 'base', [],
4368 4380 _('a base changeset assumed to be available at the destination'),
4369 4381 _('REV')),
4370 4382 ('a', 'all', None, _('bundle all changesets in the repository')),
4371 4383 ('t', 'type', 'bzip2',
4372 4384 _('bundle compression type to use'), _('TYPE')),
4373 4385 ] + remoteopts,
4374 4386 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4375 4387 "cat":
4376 4388 (cat,
4377 4389 [('o', 'output', '',
4378 4390 _('print output to file with formatted name'), _('FORMAT')),
4379 4391 ('r', 'rev', '',
4380 4392 _('print the given revision'), _('REV')),
4381 4393 ('', 'decode', None, _('apply any matching decode filter')),
4382 4394 ] + walkopts,
4383 4395 _('[OPTION]... FILE...')),
4384 4396 "^clone":
4385 4397 (clone,
4386 4398 [('U', 'noupdate', None,
4387 4399 _('the clone will include an empty working copy (only a repository)')),
4388 4400 ('u', 'updaterev', '',
4389 4401 _('revision, tag or branch to check out'), _('REV')),
4390 4402 ('r', 'rev', [],
4391 4403 _('include the specified changeset'), _('REV')),
4392 4404 ('b', 'branch', [],
4393 4405 _('clone only the specified branch'), _('BRANCH')),
4394 4406 ('', 'pull', None, _('use pull protocol to copy metadata')),
4395 4407 ('', 'uncompressed', None,
4396 4408 _('use uncompressed transfer (fast over LAN)')),
4397 4409 ] + remoteopts,
4398 4410 _('[OPTION]... SOURCE [DEST]')),
4399 4411 "^commit|ci":
4400 4412 (commit,
4401 4413 [('A', 'addremove', None,
4402 4414 _('mark new/missing files as added/removed before committing')),
4403 4415 ('', 'close-branch', None,
4404 4416 _('mark a branch as closed, hiding it from the branch list')),
4405 4417 ] + walkopts + commitopts + commitopts2,
4406 4418 _('[OPTION]... [FILE]...')),
4407 4419 "copy|cp":
4408 4420 (copy,
4409 4421 [('A', 'after', None, _('record a copy that has already occurred')),
4410 4422 ('f', 'force', None,
4411 4423 _('forcibly copy over an existing managed file')),
4412 4424 ] + walkopts + dryrunopts,
4413 4425 _('[OPTION]... [SOURCE]... DEST')),
4414 4426 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4415 4427 "debugbuilddag":
4416 4428 (debugbuilddag,
4417 4429 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4418 4430 ('a', 'appended-file', None, _('add single file all revs append to')),
4419 4431 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4420 4432 ('n', 'new-file', None, _('add new file at each rev')),
4421 4433 ],
4422 4434 _('[OPTION]... TEXT')),
4423 4435 "debugcheckstate": (debugcheckstate, [], ''),
4424 4436 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4425 4437 "debugcomplete":
4426 4438 (debugcomplete,
4427 4439 [('o', 'options', None, _('show the command options'))],
4428 4440 _('[-o] CMD')),
4429 4441 "debugdag":
4430 4442 (debugdag,
4431 4443 [('t', 'tags', None, _('use tags as labels')),
4432 4444 ('b', 'branches', None, _('annotate with branch names')),
4433 4445 ('', 'dots', None, _('use dots for runs')),
4434 4446 ('s', 'spaces', None, _('separate elements by spaces')),
4435 4447 ],
4436 4448 _('[OPTION]... [FILE [REV]...]')),
4437 4449 "debugdate":
4438 4450 (debugdate,
4439 4451 [('e', 'extended', None, _('try extended date formats'))],
4440 4452 _('[-e] DATE [RANGE]')),
4441 4453 "debugdata": (debugdata, [], _('FILE REV')),
4442 4454 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4443 4455 "debugignore": (debugignore, [], ''),
4444 4456 "debugindex": (debugindex,
4445 4457 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
4446 4458 _('FILE')),
4447 4459 "debugindexdot": (debugindexdot, [], _('FILE')),
4448 4460 "debuginstall": (debuginstall, [], ''),
4461 "debugknown": (debugknown, [], _('REPO ID...')),
4449 4462 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4450 4463 "debugrebuildstate":
4451 4464 (debugrebuildstate,
4452 4465 [('r', 'rev', '',
4453 4466 _('revision to rebuild to'), _('REV'))],
4454 4467 _('[-r REV] [REV]')),
4455 4468 "debugrename":
4456 4469 (debugrename,
4457 4470 [('r', 'rev', '',
4458 4471 _('revision to debug'), _('REV'))],
4459 4472 _('[-r REV] FILE')),
4460 4473 "debugrevspec":
4461 4474 (debugrevspec, [], ('REVSPEC')),
4462 4475 "debugsetparents":
4463 4476 (debugsetparents, [], _('REV1 [REV2]')),
4464 4477 "debugstate":
4465 4478 (debugstate,
4466 4479 [('', 'nodates', None, _('do not display the saved mtime'))],
4467 4480 _('[OPTION]...')),
4468 4481 "debugsub":
4469 4482 (debugsub,
4470 4483 [('r', 'rev', '',
4471 4484 _('revision to check'), _('REV'))],
4472 4485 _('[-r REV] [REV]')),
4473 4486 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4474 4487 "debugwireargs":
4475 4488 (debugwireargs,
4476 4489 [('', 'three', '', 'three'),
4477 4490 ('', 'four', '', 'four'),
4478 4491 ] + remoteopts,
4479 4492 _('REPO [OPTIONS]... [ONE [TWO]]')),
4480 4493 "^diff":
4481 4494 (diff,
4482 4495 [('r', 'rev', [],
4483 4496 _('revision'), _('REV')),
4484 4497 ('c', 'change', '',
4485 4498 _('change made by revision'), _('REV'))
4486 4499 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4487 4500 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4488 4501 "^export":
4489 4502 (export,
4490 4503 [('o', 'output', '',
4491 4504 _('print output to file with formatted name'), _('FORMAT')),
4492 4505 ('', 'switch-parent', None, _('diff against the second parent')),
4493 4506 ('r', 'rev', [],
4494 4507 _('revisions to export'), _('REV')),
4495 4508 ] + diffopts,
4496 4509 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4497 4510 "^forget":
4498 4511 (forget,
4499 4512 [] + walkopts,
4500 4513 _('[OPTION]... FILE...')),
4501 4514 "grep":
4502 4515 (grep,
4503 4516 [('0', 'print0', None, _('end fields with NUL')),
4504 4517 ('', 'all', None, _('print all revisions that match')),
4505 4518 ('f', 'follow', None,
4506 4519 _('follow changeset history,'
4507 4520 ' or file history across copies and renames')),
4508 4521 ('i', 'ignore-case', None, _('ignore case when matching')),
4509 4522 ('l', 'files-with-matches', None,
4510 4523 _('print only filenames and revisions that match')),
4511 4524 ('n', 'line-number', None, _('print matching line numbers')),
4512 4525 ('r', 'rev', [],
4513 4526 _('only search files changed within revision range'), _('REV')),
4514 4527 ('u', 'user', None, _('list the author (long with -v)')),
4515 4528 ('d', 'date', None, _('list the date (short with -q)')),
4516 4529 ] + walkopts,
4517 4530 _('[OPTION]... PATTERN [FILE]...')),
4518 4531 "heads":
4519 4532 (heads,
4520 4533 [('r', 'rev', '',
4521 4534 _('show only heads which are descendants of STARTREV'),
4522 4535 _('STARTREV')),
4523 4536 ('t', 'topo', False, _('show topological heads only')),
4524 4537 ('a', 'active', False,
4525 4538 _('show active branchheads only (DEPRECATED)')),
4526 4539 ('c', 'closed', False,
4527 4540 _('show normal and closed branch heads')),
4528 4541 ] + templateopts,
4529 4542 _('[-ac] [-r STARTREV] [REV]...')),
4530 4543 "help": (help_, [], _('[TOPIC]')),
4531 4544 "identify|id":
4532 4545 (identify,
4533 4546 [('r', 'rev', '',
4534 4547 _('identify the specified revision'), _('REV')),
4535 4548 ('n', 'num', None, _('show local revision number')),
4536 4549 ('i', 'id', None, _('show global revision id')),
4537 4550 ('b', 'branch', None, _('show branch')),
4538 4551 ('t', 'tags', None, _('show tags')),
4539 4552 ('B', 'bookmarks', None, _('show bookmarks'))],
4540 4553 _('[-nibtB] [-r REV] [SOURCE]')),
4541 4554 "import|patch":
4542 4555 (import_,
4543 4556 [('p', 'strip', 1,
4544 4557 _('directory strip option for patch. This has the same '
4545 4558 'meaning as the corresponding patch option'),
4546 4559 _('NUM')),
4547 4560 ('b', 'base', '',
4548 4561 _('base path'), _('PATH')),
4549 4562 ('f', 'force', None,
4550 4563 _('skip check for outstanding uncommitted changes')),
4551 4564 ('', 'no-commit', None,
4552 4565 _("don't commit, just update the working directory")),
4553 4566 ('', 'exact', None,
4554 4567 _('apply patch to the nodes from which it was generated')),
4555 4568 ('', 'import-branch', None,
4556 4569 _('use any branch information in patch (implied by --exact)'))] +
4557 4570 commitopts + commitopts2 + similarityopts,
4558 4571 _('[OPTION]... PATCH...')),
4559 4572 "incoming|in":
4560 4573 (incoming,
4561 4574 [('f', 'force', None,
4562 4575 _('run even if remote repository is unrelated')),
4563 4576 ('n', 'newest-first', None, _('show newest record first')),
4564 4577 ('', 'bundle', '',
4565 4578 _('file to store the bundles into'), _('FILE')),
4566 4579 ('r', 'rev', [],
4567 4580 _('a remote changeset intended to be added'), _('REV')),
4568 4581 ('B', 'bookmarks', False, _("compare bookmarks")),
4569 4582 ('b', 'branch', [],
4570 4583 _('a specific branch you would like to pull'), _('BRANCH')),
4571 4584 ] + logopts + remoteopts + subrepoopts,
4572 4585 _('[-p] [-n] [-M] [-f] [-r REV]...'
4573 4586 ' [--bundle FILENAME] [SOURCE]')),
4574 4587 "^init":
4575 4588 (init,
4576 4589 remoteopts,
4577 4590 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4578 4591 "locate":
4579 4592 (locate,
4580 4593 [('r', 'rev', '',
4581 4594 _('search the repository as it is in REV'), _('REV')),
4582 4595 ('0', 'print0', None,
4583 4596 _('end filenames with NUL, for use with xargs')),
4584 4597 ('f', 'fullpath', None,
4585 4598 _('print complete paths from the filesystem root')),
4586 4599 ] + walkopts,
4587 4600 _('[OPTION]... [PATTERN]...')),
4588 4601 "^log|history":
4589 4602 (log,
4590 4603 [('f', 'follow', None,
4591 4604 _('follow changeset history,'
4592 4605 ' or file history across copies and renames')),
4593 4606 ('', 'follow-first', None,
4594 4607 _('only follow the first parent of merge changesets')),
4595 4608 ('d', 'date', '',
4596 4609 _('show revisions matching date spec'), _('DATE')),
4597 4610 ('C', 'copies', None, _('show copied files')),
4598 4611 ('k', 'keyword', [],
4599 4612 _('do case-insensitive search for a given text'), _('TEXT')),
4600 4613 ('r', 'rev', [],
4601 4614 _('show the specified revision or range'), _('REV')),
4602 4615 ('', 'removed', None, _('include revisions where files were removed')),
4603 4616 ('m', 'only-merges', None, _('show only merges')),
4604 4617 ('u', 'user', [],
4605 4618 _('revisions committed by user'), _('USER')),
4606 4619 ('', 'only-branch', [],
4607 4620 _('show only changesets within the given named branch (DEPRECATED)'),
4608 4621 _('BRANCH')),
4609 4622 ('b', 'branch', [],
4610 4623 _('show changesets within the given named branch'), _('BRANCH')),
4611 4624 ('P', 'prune', [],
4612 4625 _('do not display revision or any of its ancestors'), _('REV')),
4613 4626 ] + logopts + walkopts,
4614 4627 _('[OPTION]... [FILE]')),
4615 4628 "manifest":
4616 4629 (manifest,
4617 4630 [('r', 'rev', '',
4618 4631 _('revision to display'), _('REV'))],
4619 4632 _('[-r REV]')),
4620 4633 "^merge":
4621 4634 (merge,
4622 4635 [('f', 'force', None, _('force a merge with outstanding changes')),
4623 4636 ('t', 'tool', '', _('specify merge tool')),
4624 4637 ('r', 'rev', '',
4625 4638 _('revision to merge'), _('REV')),
4626 4639 ('P', 'preview', None,
4627 4640 _('review revisions to merge (no merge is performed)'))],
4628 4641 _('[-P] [-f] [[-r] REV]')),
4629 4642 "outgoing|out":
4630 4643 (outgoing,
4631 4644 [('f', 'force', None,
4632 4645 _('run even when the destination is unrelated')),
4633 4646 ('r', 'rev', [],
4634 4647 _('a changeset intended to be included in the destination'),
4635 4648 _('REV')),
4636 4649 ('n', 'newest-first', None, _('show newest record first')),
4637 4650 ('B', 'bookmarks', False, _("compare bookmarks")),
4638 4651 ('b', 'branch', [],
4639 4652 _('a specific branch you would like to push'), _('BRANCH')),
4640 4653 ] + logopts + remoteopts + subrepoopts,
4641 4654 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4642 4655 "parents":
4643 4656 (parents,
4644 4657 [('r', 'rev', '',
4645 4658 _('show parents of the specified revision'), _('REV')),
4646 4659 ] + templateopts,
4647 4660 _('[-r REV] [FILE]')),
4648 4661 "paths": (paths, [], _('[NAME]')),
4649 4662 "^pull":
4650 4663 (pull,
4651 4664 [('u', 'update', None,
4652 4665 _('update to new branch head if changesets were pulled')),
4653 4666 ('f', 'force', None,
4654 4667 _('run even when remote repository is unrelated')),
4655 4668 ('r', 'rev', [],
4656 4669 _('a remote changeset intended to be added'), _('REV')),
4657 4670 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4658 4671 ('b', 'branch', [],
4659 4672 _('a specific branch you would like to pull'), _('BRANCH')),
4660 4673 ] + remoteopts,
4661 4674 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4662 4675 "^push":
4663 4676 (push,
4664 4677 [('f', 'force', None, _('force push')),
4665 4678 ('r', 'rev', [],
4666 4679 _('a changeset intended to be included in the destination'),
4667 4680 _('REV')),
4668 4681 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4669 4682 ('b', 'branch', [],
4670 4683 _('a specific branch you would like to push'), _('BRANCH')),
4671 4684 ('', 'new-branch', False, _('allow pushing a new branch')),
4672 4685 ] + remoteopts,
4673 4686 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4674 4687 "recover": (recover, []),
4675 4688 "^remove|rm":
4676 4689 (remove,
4677 4690 [('A', 'after', None, _('record delete for missing files')),
4678 4691 ('f', 'force', None,
4679 4692 _('remove (and delete) file even if added or modified')),
4680 4693 ] + walkopts,
4681 4694 _('[OPTION]... FILE...')),
4682 4695 "rename|move|mv":
4683 4696 (rename,
4684 4697 [('A', 'after', None, _('record a rename that has already occurred')),
4685 4698 ('f', 'force', None,
4686 4699 _('forcibly copy over an existing managed file')),
4687 4700 ] + walkopts + dryrunopts,
4688 4701 _('[OPTION]... SOURCE... DEST')),
4689 4702 "resolve":
4690 4703 (resolve,
4691 4704 [('a', 'all', None, _('select all unresolved files')),
4692 4705 ('l', 'list', None, _('list state of files needing merge')),
4693 4706 ('m', 'mark', None, _('mark files as resolved')),
4694 4707 ('u', 'unmark', None, _('mark files as unresolved')),
4695 4708 ('t', 'tool', '', _('specify merge tool')),
4696 4709 ('n', 'no-status', None, _('hide status prefix'))]
4697 4710 + walkopts,
4698 4711 _('[OPTION]... [FILE]...')),
4699 4712 "revert":
4700 4713 (revert,
4701 4714 [('a', 'all', None, _('revert all changes when no arguments given')),
4702 4715 ('d', 'date', '',
4703 4716 _('tipmost revision matching date'), _('DATE')),
4704 4717 ('r', 'rev', '',
4705 4718 _('revert to the specified revision'), _('REV')),
4706 4719 ('', 'no-backup', None, _('do not save backup copies of files')),
4707 4720 ] + walkopts + dryrunopts,
4708 4721 _('[OPTION]... [-r REV] [NAME]...')),
4709 4722 "rollback": (rollback, dryrunopts),
4710 4723 "root": (root, []),
4711 4724 "^serve":
4712 4725 (serve,
4713 4726 [('A', 'accesslog', '',
4714 4727 _('name of access log file to write to'), _('FILE')),
4715 4728 ('d', 'daemon', None, _('run server in background')),
4716 4729 ('', 'daemon-pipefds', '',
4717 4730 _('used internally by daemon mode'), _('NUM')),
4718 4731 ('E', 'errorlog', '',
4719 4732 _('name of error log file to write to'), _('FILE')),
4720 4733 # use string type, then we can check if something was passed
4721 4734 ('p', 'port', '',
4722 4735 _('port to listen on (default: 8000)'), _('PORT')),
4723 4736 ('a', 'address', '',
4724 4737 _('address to listen on (default: all interfaces)'), _('ADDR')),
4725 4738 ('', 'prefix', '',
4726 4739 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4727 4740 ('n', 'name', '',
4728 4741 _('name to show in web pages (default: working directory)'),
4729 4742 _('NAME')),
4730 4743 ('', 'web-conf', '',
4731 4744 _('name of the hgweb config file (see "hg help hgweb")'),
4732 4745 _('FILE')),
4733 4746 ('', 'webdir-conf', '',
4734 4747 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4735 4748 ('', 'pid-file', '',
4736 4749 _('name of file to write process ID to'), _('FILE')),
4737 4750 ('', 'stdio', None, _('for remote clients')),
4738 4751 ('t', 'templates', '',
4739 4752 _('web templates to use'), _('TEMPLATE')),
4740 4753 ('', 'style', '',
4741 4754 _('template style to use'), _('STYLE')),
4742 4755 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4743 4756 ('', 'certificate', '',
4744 4757 _('SSL certificate file'), _('FILE'))],
4745 4758 _('[OPTION]...')),
4746 4759 "showconfig|debugconfig":
4747 4760 (showconfig,
4748 4761 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4749 4762 _('[-u] [NAME]...')),
4750 4763 "^summary|sum":
4751 4764 (summary,
4752 4765 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4753 4766 "^status|st":
4754 4767 (status,
4755 4768 [('A', 'all', None, _('show status of all files')),
4756 4769 ('m', 'modified', None, _('show only modified files')),
4757 4770 ('a', 'added', None, _('show only added files')),
4758 4771 ('r', 'removed', None, _('show only removed files')),
4759 4772 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4760 4773 ('c', 'clean', None, _('show only files without changes')),
4761 4774 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4762 4775 ('i', 'ignored', None, _('show only ignored files')),
4763 4776 ('n', 'no-status', None, _('hide status prefix')),
4764 4777 ('C', 'copies', None, _('show source of copied files')),
4765 4778 ('0', 'print0', None,
4766 4779 _('end filenames with NUL, for use with xargs')),
4767 4780 ('', 'rev', [],
4768 4781 _('show difference from revision'), _('REV')),
4769 4782 ('', 'change', '',
4770 4783 _('list the changed files of a revision'), _('REV')),
4771 4784 ] + walkopts + subrepoopts,
4772 4785 _('[OPTION]... [FILE]...')),
4773 4786 "tag":
4774 4787 (tag,
4775 4788 [('f', 'force', None, _('force tag')),
4776 4789 ('l', 'local', None, _('make the tag local')),
4777 4790 ('r', 'rev', '',
4778 4791 _('revision to tag'), _('REV')),
4779 4792 ('', 'remove', None, _('remove a tag')),
4780 4793 # -l/--local is already there, commitopts cannot be used
4781 4794 ('e', 'edit', None, _('edit commit message')),
4782 4795 ('m', 'message', '',
4783 4796 _('use <text> as commit message'), _('TEXT')),
4784 4797 ] + commitopts2,
4785 4798 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4786 4799 "tags": (tags, [], ''),
4787 4800 "tip":
4788 4801 (tip,
4789 4802 [('p', 'patch', None, _('show patch')),
4790 4803 ('g', 'git', None, _('use git extended diff format')),
4791 4804 ] + templateopts,
4792 4805 _('[-p] [-g]')),
4793 4806 "unbundle":
4794 4807 (unbundle,
4795 4808 [('u', 'update', None,
4796 4809 _('update to new branch head if changesets were unbundled'))],
4797 4810 _('[-u] FILE...')),
4798 4811 "^update|up|checkout|co":
4799 4812 (update,
4800 4813 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4801 4814 ('c', 'check', None,
4802 4815 _('update across branches if no uncommitted changes')),
4803 4816 ('d', 'date', '',
4804 4817 _('tipmost revision matching date'), _('DATE')),
4805 4818 ('r', 'rev', '',
4806 4819 _('revision'), _('REV'))],
4807 4820 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4808 4821 "verify": (verify, []),
4809 4822 "version": (version_, []),
4810 4823 }
4811 4824
4812 4825 norepo = ("clone init version help debugcommands debugcomplete"
4813 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs")
4826 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
4827 " debugknown")
4814 4828 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4815 4829 " debugdata debugindex debugindexdot")
@@ -1,1924 +1,1929 b''
1 1 # localrepo.py - read/write repository class 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 of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import bin, hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import repo, changegroup, subrepo, discovery, pushkey
11 11 import changelog, dirstate, filelog, manifest, context, bookmarks
12 12 import lock, transaction, store, encoding
13 13 import util, extensions, hook, error
14 14 import match as matchmod
15 15 import merge as mergemod
16 16 import tags as tagsmod
17 17 import url as urlmod
18 18 from lock import release
19 19 import weakref, errno, os, time, inspect
20 20 propertycache = util.propertycache
21 21
22 22 class localrepository(repo.repository):
23 capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey'))
23 capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey',
24 'known'))
24 25 supportedformats = set(('revlogv1', 'parentdelta'))
25 26 supported = supportedformats | set(('store', 'fncache', 'shared',
26 27 'dotencode'))
27 28
28 29 def __init__(self, baseui, path=None, create=0):
29 30 repo.repository.__init__(self)
30 31 self.root = os.path.realpath(util.expandpath(path))
31 32 self.path = os.path.join(self.root, ".hg")
32 33 self.origroot = path
33 34 self.auditor = util.path_auditor(self.root, self._checknested)
34 35 self.opener = util.opener(self.path)
35 36 self.wopener = util.opener(self.root)
36 37 self.baseui = baseui
37 38 self.ui = baseui.copy()
38 39
39 40 try:
40 41 self.ui.readconfig(self.join("hgrc"), self.root)
41 42 extensions.loadall(self.ui)
42 43 except IOError:
43 44 pass
44 45
45 46 if not os.path.isdir(self.path):
46 47 if create:
47 48 if not os.path.exists(path):
48 49 util.makedirs(path)
49 50 os.mkdir(self.path)
50 51 requirements = ["revlogv1"]
51 52 if self.ui.configbool('format', 'usestore', True):
52 53 os.mkdir(os.path.join(self.path, "store"))
53 54 requirements.append("store")
54 55 if self.ui.configbool('format', 'usefncache', True):
55 56 requirements.append("fncache")
56 57 if self.ui.configbool('format', 'dotencode', True):
57 58 requirements.append('dotencode')
58 59 # create an invalid changelog
59 60 self.opener("00changelog.i", "a").write(
60 61 '\0\0\0\2' # represents revlogv2
61 62 ' dummy changelog to prevent using the old repo layout'
62 63 )
63 64 if self.ui.configbool('format', 'parentdelta', False):
64 65 requirements.append("parentdelta")
65 66 else:
66 67 raise error.RepoError(_("repository %s not found") % path)
67 68 elif create:
68 69 raise error.RepoError(_("repository %s already exists") % path)
69 70 else:
70 71 # find requirements
71 72 requirements = set()
72 73 try:
73 74 requirements = set(self.opener("requires").read().splitlines())
74 75 except IOError, inst:
75 76 if inst.errno != errno.ENOENT:
76 77 raise
77 78 for r in requirements - self.supported:
78 79 raise error.RequirementError(
79 80 _("requirement '%s' not supported") % r)
80 81
81 82 self.sharedpath = self.path
82 83 try:
83 84 s = os.path.realpath(self.opener("sharedpath").read())
84 85 if not os.path.exists(s):
85 86 raise error.RepoError(
86 87 _('.hg/sharedpath points to nonexistent directory %s') % s)
87 88 self.sharedpath = s
88 89 except IOError, inst:
89 90 if inst.errno != errno.ENOENT:
90 91 raise
91 92
92 93 self.store = store.store(requirements, self.sharedpath, util.opener)
93 94 self.spath = self.store.path
94 95 self.sopener = self.store.opener
95 96 self.sjoin = self.store.join
96 97 self.opener.createmode = self.store.createmode
97 98 self._applyrequirements(requirements)
98 99 if create:
99 100 self._writerequirements()
100 101
101 102 # These two define the set of tags for this repository. _tags
102 103 # maps tag name to node; _tagtypes maps tag name to 'global' or
103 104 # 'local'. (Global tags are defined by .hgtags across all
104 105 # heads, and local tags are defined in .hg/localtags.) They
105 106 # constitute the in-memory cache of tags.
106 107 self._tags = None
107 108 self._tagtypes = None
108 109
109 110 self._branchcache = None
110 111 self._branchcachetip = None
111 112 self.nodetagscache = None
112 113 self.filterpats = {}
113 114 self._datafilters = {}
114 115 self._transref = self._lockref = self._wlockref = None
115 116
116 117 def _applyrequirements(self, requirements):
117 118 self.requirements = requirements
118 119 self.sopener.options = {}
119 120 if 'parentdelta' in requirements:
120 121 self.sopener.options['parentdelta'] = 1
121 122
122 123 def _writerequirements(self):
123 124 reqfile = self.opener("requires", "w")
124 125 for r in self.requirements:
125 126 reqfile.write("%s\n" % r)
126 127 reqfile.close()
127 128
128 129 def _checknested(self, path):
129 130 """Determine if path is a legal nested repository."""
130 131 if not path.startswith(self.root):
131 132 return False
132 133 subpath = path[len(self.root) + 1:]
133 134
134 135 # XXX: Checking against the current working copy is wrong in
135 136 # the sense that it can reject things like
136 137 #
137 138 # $ hg cat -r 10 sub/x.txt
138 139 #
139 140 # if sub/ is no longer a subrepository in the working copy
140 141 # parent revision.
141 142 #
142 143 # However, it can of course also allow things that would have
143 144 # been rejected before, such as the above cat command if sub/
144 145 # is a subrepository now, but was a normal directory before.
145 146 # The old path auditor would have rejected by mistake since it
146 147 # panics when it sees sub/.hg/.
147 148 #
148 149 # All in all, checking against the working copy seems sensible
149 150 # since we want to prevent access to nested repositories on
150 151 # the filesystem *now*.
151 152 ctx = self[None]
152 153 parts = util.splitpath(subpath)
153 154 while parts:
154 155 prefix = os.sep.join(parts)
155 156 if prefix in ctx.substate:
156 157 if prefix == subpath:
157 158 return True
158 159 else:
159 160 sub = ctx.sub(prefix)
160 161 return sub.checknested(subpath[len(prefix) + 1:])
161 162 else:
162 163 parts.pop()
163 164 return False
164 165
165 166 @util.propertycache
166 167 def _bookmarks(self):
167 168 return bookmarks.read(self)
168 169
169 170 @util.propertycache
170 171 def _bookmarkcurrent(self):
171 172 return bookmarks.readcurrent(self)
172 173
173 174 @propertycache
174 175 def changelog(self):
175 176 c = changelog.changelog(self.sopener)
176 177 if 'HG_PENDING' in os.environ:
177 178 p = os.environ['HG_PENDING']
178 179 if p.startswith(self.root):
179 180 c.readpending('00changelog.i.a')
180 181 self.sopener.options['defversion'] = c.version
181 182 return c
182 183
183 184 @propertycache
184 185 def manifest(self):
185 186 return manifest.manifest(self.sopener)
186 187
187 188 @propertycache
188 189 def dirstate(self):
189 190 warned = [0]
190 191 def validate(node):
191 192 try:
192 193 r = self.changelog.rev(node)
193 194 return node
194 195 except error.LookupError:
195 196 if not warned[0]:
196 197 warned[0] = True
197 198 self.ui.warn(_("warning: ignoring unknown"
198 199 " working parent %s!\n") % short(node))
199 200 return nullid
200 201
201 202 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
202 203
203 204 def __getitem__(self, changeid):
204 205 if changeid is None:
205 206 return context.workingctx(self)
206 207 return context.changectx(self, changeid)
207 208
208 209 def __contains__(self, changeid):
209 210 try:
210 211 return bool(self.lookup(changeid))
211 212 except error.RepoLookupError:
212 213 return False
213 214
214 215 def __nonzero__(self):
215 216 return True
216 217
217 218 def __len__(self):
218 219 return len(self.changelog)
219 220
220 221 def __iter__(self):
221 222 for i in xrange(len(self)):
222 223 yield i
223 224
224 225 def url(self):
225 226 return 'file:' + self.root
226 227
227 228 def hook(self, name, throw=False, **args):
228 229 return hook.hook(self.ui, self, name, throw, **args)
229 230
230 231 tag_disallowed = ':\r\n'
231 232
232 233 def _tag(self, names, node, message, local, user, date, extra={}):
233 234 if isinstance(names, str):
234 235 allchars = names
235 236 names = (names,)
236 237 else:
237 238 allchars = ''.join(names)
238 239 for c in self.tag_disallowed:
239 240 if c in allchars:
240 241 raise util.Abort(_('%r cannot be used in a tag name') % c)
241 242
242 243 branches = self.branchmap()
243 244 for name in names:
244 245 self.hook('pretag', throw=True, node=hex(node), tag=name,
245 246 local=local)
246 247 if name in branches:
247 248 self.ui.warn(_("warning: tag %s conflicts with existing"
248 249 " branch name\n") % name)
249 250
250 251 def writetags(fp, names, munge, prevtags):
251 252 fp.seek(0, 2)
252 253 if prevtags and prevtags[-1] != '\n':
253 254 fp.write('\n')
254 255 for name in names:
255 256 m = munge and munge(name) or name
256 257 if self._tagtypes and name in self._tagtypes:
257 258 old = self._tags.get(name, nullid)
258 259 fp.write('%s %s\n' % (hex(old), m))
259 260 fp.write('%s %s\n' % (hex(node), m))
260 261 fp.close()
261 262
262 263 prevtags = ''
263 264 if local:
264 265 try:
265 266 fp = self.opener('localtags', 'r+')
266 267 except IOError:
267 268 fp = self.opener('localtags', 'a')
268 269 else:
269 270 prevtags = fp.read()
270 271
271 272 # local tags are stored in the current charset
272 273 writetags(fp, names, None, prevtags)
273 274 for name in names:
274 275 self.hook('tag', node=hex(node), tag=name, local=local)
275 276 return
276 277
277 278 try:
278 279 fp = self.wfile('.hgtags', 'rb+')
279 280 except IOError:
280 281 fp = self.wfile('.hgtags', 'ab')
281 282 else:
282 283 prevtags = fp.read()
283 284
284 285 # committed tags are stored in UTF-8
285 286 writetags(fp, names, encoding.fromlocal, prevtags)
286 287
287 288 fp.close()
288 289
289 290 if '.hgtags' not in self.dirstate:
290 291 self[None].add(['.hgtags'])
291 292
292 293 m = matchmod.exact(self.root, '', ['.hgtags'])
293 294 tagnode = self.commit(message, user, date, extra=extra, match=m)
294 295
295 296 for name in names:
296 297 self.hook('tag', node=hex(node), tag=name, local=local)
297 298
298 299 return tagnode
299 300
300 301 def tag(self, names, node, message, local, user, date):
301 302 '''tag a revision with one or more symbolic names.
302 303
303 304 names is a list of strings or, when adding a single tag, names may be a
304 305 string.
305 306
306 307 if local is True, the tags are stored in a per-repository file.
307 308 otherwise, they are stored in the .hgtags file, and a new
308 309 changeset is committed with the change.
309 310
310 311 keyword arguments:
311 312
312 313 local: whether to store tags in non-version-controlled file
313 314 (default False)
314 315
315 316 message: commit message to use if committing
316 317
317 318 user: name of user to use if committing
318 319
319 320 date: date tuple to use if committing'''
320 321
321 322 if not local:
322 323 for x in self.status()[:5]:
323 324 if '.hgtags' in x:
324 325 raise util.Abort(_('working copy of .hgtags is changed '
325 326 '(please commit .hgtags manually)'))
326 327
327 328 self.tags() # instantiate the cache
328 329 self._tag(names, node, message, local, user, date)
329 330
330 331 def tags(self):
331 332 '''return a mapping of tag to node'''
332 333 if self._tags is None:
333 334 (self._tags, self._tagtypes) = self._findtags()
334 335
335 336 return self._tags
336 337
337 338 def _findtags(self):
338 339 '''Do the hard work of finding tags. Return a pair of dicts
339 340 (tags, tagtypes) where tags maps tag name to node, and tagtypes
340 341 maps tag name to a string like \'global\' or \'local\'.
341 342 Subclasses or extensions are free to add their own tags, but
342 343 should be aware that the returned dicts will be retained for the
343 344 duration of the localrepo object.'''
344 345
345 346 # XXX what tagtype should subclasses/extensions use? Currently
346 347 # mq and bookmarks add tags, but do not set the tagtype at all.
347 348 # Should each extension invent its own tag type? Should there
348 349 # be one tagtype for all such "virtual" tags? Or is the status
349 350 # quo fine?
350 351
351 352 alltags = {} # map tag name to (node, hist)
352 353 tagtypes = {}
353 354
354 355 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
355 356 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
356 357
357 358 # Build the return dicts. Have to re-encode tag names because
358 359 # the tags module always uses UTF-8 (in order not to lose info
359 360 # writing to the cache), but the rest of Mercurial wants them in
360 361 # local encoding.
361 362 tags = {}
362 363 for (name, (node, hist)) in alltags.iteritems():
363 364 if node != nullid:
364 365 tags[encoding.tolocal(name)] = node
365 366 tags['tip'] = self.changelog.tip()
366 367 tagtypes = dict([(encoding.tolocal(name), value)
367 368 for (name, value) in tagtypes.iteritems()])
368 369 return (tags, tagtypes)
369 370
370 371 def tagtype(self, tagname):
371 372 '''
372 373 return the type of the given tag. result can be:
373 374
374 375 'local' : a local tag
375 376 'global' : a global tag
376 377 None : tag does not exist
377 378 '''
378 379
379 380 self.tags()
380 381
381 382 return self._tagtypes.get(tagname)
382 383
383 384 def tagslist(self):
384 385 '''return a list of tags ordered by revision'''
385 386 l = []
386 387 for t, n in self.tags().iteritems():
387 388 try:
388 389 r = self.changelog.rev(n)
389 390 except:
390 391 r = -2 # sort to the beginning of the list if unknown
391 392 l.append((r, t, n))
392 393 return [(t, n) for r, t, n in sorted(l)]
393 394
394 395 def nodetags(self, node):
395 396 '''return the tags associated with a node'''
396 397 if not self.nodetagscache:
397 398 self.nodetagscache = {}
398 399 for t, n in self.tags().iteritems():
399 400 self.nodetagscache.setdefault(n, []).append(t)
400 401 for tags in self.nodetagscache.itervalues():
401 402 tags.sort()
402 403 return self.nodetagscache.get(node, [])
403 404
404 405 def nodebookmarks(self, node):
405 406 marks = []
406 407 for bookmark, n in self._bookmarks.iteritems():
407 408 if n == node:
408 409 marks.append(bookmark)
409 410 return sorted(marks)
410 411
411 412 def _branchtags(self, partial, lrev):
412 413 # TODO: rename this function?
413 414 tiprev = len(self) - 1
414 415 if lrev != tiprev:
415 416 ctxgen = (self[r] for r in xrange(lrev + 1, tiprev + 1))
416 417 self._updatebranchcache(partial, ctxgen)
417 418 self._writebranchcache(partial, self.changelog.tip(), tiprev)
418 419
419 420 return partial
420 421
421 422 def updatebranchcache(self):
422 423 tip = self.changelog.tip()
423 424 if self._branchcache is not None and self._branchcachetip == tip:
424 425 return self._branchcache
425 426
426 427 oldtip = self._branchcachetip
427 428 self._branchcachetip = tip
428 429 if oldtip is None or oldtip not in self.changelog.nodemap:
429 430 partial, last, lrev = self._readbranchcache()
430 431 else:
431 432 lrev = self.changelog.rev(oldtip)
432 433 partial = self._branchcache
433 434
434 435 self._branchtags(partial, lrev)
435 436 # this private cache holds all heads (not just tips)
436 437 self._branchcache = partial
437 438
438 439 def branchmap(self):
439 440 '''returns a dictionary {branch: [branchheads]}'''
440 441 self.updatebranchcache()
441 442 return self._branchcache
442 443
443 444 def branchtags(self):
444 445 '''return a dict where branch names map to the tipmost head of
445 446 the branch, open heads come before closed'''
446 447 bt = {}
447 448 for bn, heads in self.branchmap().iteritems():
448 449 tip = heads[-1]
449 450 for h in reversed(heads):
450 451 if 'close' not in self.changelog.read(h)[5]:
451 452 tip = h
452 453 break
453 454 bt[bn] = tip
454 455 return bt
455 456
456 457 def _readbranchcache(self):
457 458 partial = {}
458 459 try:
459 460 f = self.opener("cache/branchheads")
460 461 lines = f.read().split('\n')
461 462 f.close()
462 463 except (IOError, OSError):
463 464 return {}, nullid, nullrev
464 465
465 466 try:
466 467 last, lrev = lines.pop(0).split(" ", 1)
467 468 last, lrev = bin(last), int(lrev)
468 469 if lrev >= len(self) or self[lrev].node() != last:
469 470 # invalidate the cache
470 471 raise ValueError('invalidating branch cache (tip differs)')
471 472 for l in lines:
472 473 if not l:
473 474 continue
474 475 node, label = l.split(" ", 1)
475 476 label = encoding.tolocal(label.strip())
476 477 partial.setdefault(label, []).append(bin(node))
477 478 except KeyboardInterrupt:
478 479 raise
479 480 except Exception, inst:
480 481 if self.ui.debugflag:
481 482 self.ui.warn(str(inst), '\n')
482 483 partial, last, lrev = {}, nullid, nullrev
483 484 return partial, last, lrev
484 485
485 486 def _writebranchcache(self, branches, tip, tiprev):
486 487 try:
487 488 f = self.opener("cache/branchheads", "w", atomictemp=True)
488 489 f.write("%s %s\n" % (hex(tip), tiprev))
489 490 for label, nodes in branches.iteritems():
490 491 for node in nodes:
491 492 f.write("%s %s\n" % (hex(node), encoding.fromlocal(label)))
492 493 f.rename()
493 494 except (IOError, OSError):
494 495 pass
495 496
496 497 def _updatebranchcache(self, partial, ctxgen):
497 498 # collect new branch entries
498 499 newbranches = {}
499 500 for c in ctxgen:
500 501 newbranches.setdefault(c.branch(), []).append(c.node())
501 502 # if older branchheads are reachable from new ones, they aren't
502 503 # really branchheads. Note checking parents is insufficient:
503 504 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
504 505 for branch, newnodes in newbranches.iteritems():
505 506 bheads = partial.setdefault(branch, [])
506 507 bheads.extend(newnodes)
507 508 if len(bheads) <= 1:
508 509 continue
509 510 # starting from tip means fewer passes over reachable
510 511 while newnodes:
511 512 latest = newnodes.pop()
512 513 if latest not in bheads:
513 514 continue
514 515 minbhrev = self[min([self[bh].rev() for bh in bheads])].node()
515 516 reachable = self.changelog.reachable(latest, minbhrev)
516 517 reachable.remove(latest)
517 518 bheads = [b for b in bheads if b not in reachable]
518 519 partial[branch] = bheads
519 520
520 521 def lookup(self, key):
521 522 if isinstance(key, int):
522 523 return self.changelog.node(key)
523 524 elif key == '.':
524 525 return self.dirstate.parents()[0]
525 526 elif key == 'null':
526 527 return nullid
527 528 elif key == 'tip':
528 529 return self.changelog.tip()
529 530 n = self.changelog._match(key)
530 531 if n:
531 532 return n
532 533 if key in self._bookmarks:
533 534 return self._bookmarks[key]
534 535 if key in self.tags():
535 536 return self.tags()[key]
536 537 if key in self.branchtags():
537 538 return self.branchtags()[key]
538 539 n = self.changelog._partialmatch(key)
539 540 if n:
540 541 return n
541 542
542 543 # can't find key, check if it might have come from damaged dirstate
543 544 if key in self.dirstate.parents():
544 545 raise error.Abort(_("working directory has unknown parent '%s'!")
545 546 % short(key))
546 547 try:
547 548 if len(key) == 20:
548 549 key = hex(key)
549 550 except:
550 551 pass
551 552 raise error.RepoLookupError(_("unknown revision '%s'") % key)
552 553
553 554 def lookupbranch(self, key, remote=None):
554 555 repo = remote or self
555 556 if key in repo.branchmap():
556 557 return key
557 558
558 559 repo = (remote and remote.local()) and remote or self
559 560 return repo[key].branch()
560 561
562 def known(self, nodes):
563 nm = self.changelog.nodemap
564 return [(n in nm) for n in nodes]
565
561 566 def local(self):
562 567 return True
563 568
564 569 def join(self, f):
565 570 return os.path.join(self.path, f)
566 571
567 572 def wjoin(self, f):
568 573 return os.path.join(self.root, f)
569 574
570 575 def file(self, f):
571 576 if f[0] == '/':
572 577 f = f[1:]
573 578 return filelog.filelog(self.sopener, f)
574 579
575 580 def changectx(self, changeid):
576 581 return self[changeid]
577 582
578 583 def parents(self, changeid=None):
579 584 '''get list of changectxs for parents of changeid'''
580 585 return self[changeid].parents()
581 586
582 587 def filectx(self, path, changeid=None, fileid=None):
583 588 """changeid can be a changeset revision, node, or tag.
584 589 fileid can be a file revision or node."""
585 590 return context.filectx(self, path, changeid, fileid)
586 591
587 592 def getcwd(self):
588 593 return self.dirstate.getcwd()
589 594
590 595 def pathto(self, f, cwd=None):
591 596 return self.dirstate.pathto(f, cwd)
592 597
593 598 def wfile(self, f, mode='r'):
594 599 return self.wopener(f, mode)
595 600
596 601 def _link(self, f):
597 602 return os.path.islink(self.wjoin(f))
598 603
599 604 def _loadfilter(self, filter):
600 605 if filter not in self.filterpats:
601 606 l = []
602 607 for pat, cmd in self.ui.configitems(filter):
603 608 if cmd == '!':
604 609 continue
605 610 mf = matchmod.match(self.root, '', [pat])
606 611 fn = None
607 612 params = cmd
608 613 for name, filterfn in self._datafilters.iteritems():
609 614 if cmd.startswith(name):
610 615 fn = filterfn
611 616 params = cmd[len(name):].lstrip()
612 617 break
613 618 if not fn:
614 619 fn = lambda s, c, **kwargs: util.filter(s, c)
615 620 # Wrap old filters not supporting keyword arguments
616 621 if not inspect.getargspec(fn)[2]:
617 622 oldfn = fn
618 623 fn = lambda s, c, **kwargs: oldfn(s, c)
619 624 l.append((mf, fn, params))
620 625 self.filterpats[filter] = l
621 626 return self.filterpats[filter]
622 627
623 628 def _filter(self, filterpats, filename, data):
624 629 for mf, fn, cmd in filterpats:
625 630 if mf(filename):
626 631 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
627 632 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
628 633 break
629 634
630 635 return data
631 636
632 637 @propertycache
633 638 def _encodefilterpats(self):
634 639 return self._loadfilter('encode')
635 640
636 641 @propertycache
637 642 def _decodefilterpats(self):
638 643 return self._loadfilter('decode')
639 644
640 645 def adddatafilter(self, name, filter):
641 646 self._datafilters[name] = filter
642 647
643 648 def wread(self, filename):
644 649 if self._link(filename):
645 650 data = os.readlink(self.wjoin(filename))
646 651 else:
647 652 data = self.wopener(filename, 'r').read()
648 653 return self._filter(self._encodefilterpats, filename, data)
649 654
650 655 def wwrite(self, filename, data, flags):
651 656 data = self._filter(self._decodefilterpats, filename, data)
652 657 if 'l' in flags:
653 658 self.wopener.symlink(data, filename)
654 659 else:
655 660 self.wopener(filename, 'w').write(data)
656 661 if 'x' in flags:
657 662 util.set_flags(self.wjoin(filename), False, True)
658 663
659 664 def wwritedata(self, filename, data):
660 665 return self._filter(self._decodefilterpats, filename, data)
661 666
662 667 def transaction(self, desc):
663 668 tr = self._transref and self._transref() or None
664 669 if tr and tr.running():
665 670 return tr.nest()
666 671
667 672 # abort here if the journal already exists
668 673 if os.path.exists(self.sjoin("journal")):
669 674 raise error.RepoError(
670 675 _("abandoned transaction found - run hg recover"))
671 676
672 677 # save dirstate for rollback
673 678 try:
674 679 ds = self.opener("dirstate").read()
675 680 except IOError:
676 681 ds = ""
677 682 self.opener("journal.dirstate", "w").write(ds)
678 683 self.opener("journal.branch", "w").write(
679 684 encoding.fromlocal(self.dirstate.branch()))
680 685 self.opener("journal.desc", "w").write("%d\n%s\n" % (len(self), desc))
681 686
682 687 renames = [(self.sjoin("journal"), self.sjoin("undo")),
683 688 (self.join("journal.dirstate"), self.join("undo.dirstate")),
684 689 (self.join("journal.branch"), self.join("undo.branch")),
685 690 (self.join("journal.desc"), self.join("undo.desc"))]
686 691 tr = transaction.transaction(self.ui.warn, self.sopener,
687 692 self.sjoin("journal"),
688 693 aftertrans(renames),
689 694 self.store.createmode)
690 695 self._transref = weakref.ref(tr)
691 696 return tr
692 697
693 698 def recover(self):
694 699 lock = self.lock()
695 700 try:
696 701 if os.path.exists(self.sjoin("journal")):
697 702 self.ui.status(_("rolling back interrupted transaction\n"))
698 703 transaction.rollback(self.sopener, self.sjoin("journal"),
699 704 self.ui.warn)
700 705 self.invalidate()
701 706 return True
702 707 else:
703 708 self.ui.warn(_("no interrupted transaction available\n"))
704 709 return False
705 710 finally:
706 711 lock.release()
707 712
708 713 def rollback(self, dryrun=False):
709 714 wlock = lock = None
710 715 try:
711 716 wlock = self.wlock()
712 717 lock = self.lock()
713 718 if os.path.exists(self.sjoin("undo")):
714 719 try:
715 720 args = self.opener("undo.desc", "r").read().splitlines()
716 721 if len(args) >= 3 and self.ui.verbose:
717 722 desc = _("repository tip rolled back to revision %s"
718 723 " (undo %s: %s)\n") % (
719 724 int(args[0]) - 1, args[1], args[2])
720 725 elif len(args) >= 2:
721 726 desc = _("repository tip rolled back to revision %s"
722 727 " (undo %s)\n") % (
723 728 int(args[0]) - 1, args[1])
724 729 except IOError:
725 730 desc = _("rolling back unknown transaction\n")
726 731 self.ui.status(desc)
727 732 if dryrun:
728 733 return
729 734 transaction.rollback(self.sopener, self.sjoin("undo"),
730 735 self.ui.warn)
731 736 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
732 737 if os.path.exists(self.join('undo.bookmarks')):
733 738 util.rename(self.join('undo.bookmarks'),
734 739 self.join('bookmarks'))
735 740 try:
736 741 branch = self.opener("undo.branch").read()
737 742 self.dirstate.setbranch(branch)
738 743 except IOError:
739 744 self.ui.warn(_("Named branch could not be reset, "
740 745 "current branch still is: %s\n")
741 746 % self.dirstate.branch())
742 747 self.invalidate()
743 748 self.dirstate.invalidate()
744 749 self.destroyed()
745 750 parents = tuple([p.rev() for p in self.parents()])
746 751 if len(parents) > 1:
747 752 self.ui.status(_("working directory now based on "
748 753 "revisions %d and %d\n") % parents)
749 754 else:
750 755 self.ui.status(_("working directory now based on "
751 756 "revision %d\n") % parents)
752 757 else:
753 758 self.ui.warn(_("no rollback information available\n"))
754 759 return 1
755 760 finally:
756 761 release(lock, wlock)
757 762
758 763 def invalidatecaches(self):
759 764 self._tags = None
760 765 self._tagtypes = None
761 766 self.nodetagscache = None
762 767 self._branchcache = None # in UTF-8
763 768 self._branchcachetip = None
764 769
765 770 def invalidate(self):
766 771 for a in ("changelog", "manifest", "_bookmarks", "_bookmarkcurrent"):
767 772 if a in self.__dict__:
768 773 delattr(self, a)
769 774 self.invalidatecaches()
770 775
771 776 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
772 777 try:
773 778 l = lock.lock(lockname, 0, releasefn, desc=desc)
774 779 except error.LockHeld, inst:
775 780 if not wait:
776 781 raise
777 782 self.ui.warn(_("waiting for lock on %s held by %r\n") %
778 783 (desc, inst.locker))
779 784 # default to 600 seconds timeout
780 785 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
781 786 releasefn, desc=desc)
782 787 if acquirefn:
783 788 acquirefn()
784 789 return l
785 790
786 791 def lock(self, wait=True):
787 792 '''Lock the repository store (.hg/store) and return a weak reference
788 793 to the lock. Use this before modifying the store (e.g. committing or
789 794 stripping). If you are opening a transaction, get a lock as well.)'''
790 795 l = self._lockref and self._lockref()
791 796 if l is not None and l.held:
792 797 l.lock()
793 798 return l
794 799
795 800 l = self._lock(self.sjoin("lock"), wait, self.store.write,
796 801 self.invalidate, _('repository %s') % self.origroot)
797 802 self._lockref = weakref.ref(l)
798 803 return l
799 804
800 805 def wlock(self, wait=True):
801 806 '''Lock the non-store parts of the repository (everything under
802 807 .hg except .hg/store) and return a weak reference to the lock.
803 808 Use this before modifying files in .hg.'''
804 809 l = self._wlockref and self._wlockref()
805 810 if l is not None and l.held:
806 811 l.lock()
807 812 return l
808 813
809 814 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
810 815 self.dirstate.invalidate, _('working directory of %s') %
811 816 self.origroot)
812 817 self._wlockref = weakref.ref(l)
813 818 return l
814 819
815 820 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
816 821 """
817 822 commit an individual file as part of a larger transaction
818 823 """
819 824
820 825 fname = fctx.path()
821 826 text = fctx.data()
822 827 flog = self.file(fname)
823 828 fparent1 = manifest1.get(fname, nullid)
824 829 fparent2 = fparent2o = manifest2.get(fname, nullid)
825 830
826 831 meta = {}
827 832 copy = fctx.renamed()
828 833 if copy and copy[0] != fname:
829 834 # Mark the new revision of this file as a copy of another
830 835 # file. This copy data will effectively act as a parent
831 836 # of this new revision. If this is a merge, the first
832 837 # parent will be the nullid (meaning "look up the copy data")
833 838 # and the second one will be the other parent. For example:
834 839 #
835 840 # 0 --- 1 --- 3 rev1 changes file foo
836 841 # \ / rev2 renames foo to bar and changes it
837 842 # \- 2 -/ rev3 should have bar with all changes and
838 843 # should record that bar descends from
839 844 # bar in rev2 and foo in rev1
840 845 #
841 846 # this allows this merge to succeed:
842 847 #
843 848 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
844 849 # \ / merging rev3 and rev4 should use bar@rev2
845 850 # \- 2 --- 4 as the merge base
846 851 #
847 852
848 853 cfname = copy[0]
849 854 crev = manifest1.get(cfname)
850 855 newfparent = fparent2
851 856
852 857 if manifest2: # branch merge
853 858 if fparent2 == nullid or crev is None: # copied on remote side
854 859 if cfname in manifest2:
855 860 crev = manifest2[cfname]
856 861 newfparent = fparent1
857 862
858 863 # find source in nearest ancestor if we've lost track
859 864 if not crev:
860 865 self.ui.debug(" %s: searching for copy revision for %s\n" %
861 866 (fname, cfname))
862 867 for ancestor in self[None].ancestors():
863 868 if cfname in ancestor:
864 869 crev = ancestor[cfname].filenode()
865 870 break
866 871
867 872 if crev:
868 873 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
869 874 meta["copy"] = cfname
870 875 meta["copyrev"] = hex(crev)
871 876 fparent1, fparent2 = nullid, newfparent
872 877 else:
873 878 self.ui.warn(_("warning: can't find ancestor for '%s' "
874 879 "copied from '%s'!\n") % (fname, cfname))
875 880
876 881 elif fparent2 != nullid:
877 882 # is one parent an ancestor of the other?
878 883 fparentancestor = flog.ancestor(fparent1, fparent2)
879 884 if fparentancestor == fparent1:
880 885 fparent1, fparent2 = fparent2, nullid
881 886 elif fparentancestor == fparent2:
882 887 fparent2 = nullid
883 888
884 889 # is the file changed?
885 890 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
886 891 changelist.append(fname)
887 892 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
888 893
889 894 # are just the flags changed during merge?
890 895 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
891 896 changelist.append(fname)
892 897
893 898 return fparent1
894 899
895 900 def commit(self, text="", user=None, date=None, match=None, force=False,
896 901 editor=False, extra={}):
897 902 """Add a new revision to current repository.
898 903
899 904 Revision information is gathered from the working directory,
900 905 match can be used to filter the committed files. If editor is
901 906 supplied, it is called to get a commit message.
902 907 """
903 908
904 909 def fail(f, msg):
905 910 raise util.Abort('%s: %s' % (f, msg))
906 911
907 912 if not match:
908 913 match = matchmod.always(self.root, '')
909 914
910 915 if not force:
911 916 vdirs = []
912 917 match.dir = vdirs.append
913 918 match.bad = fail
914 919
915 920 wlock = self.wlock()
916 921 try:
917 922 wctx = self[None]
918 923 merge = len(wctx.parents()) > 1
919 924
920 925 if (not force and merge and match and
921 926 (match.files() or match.anypats())):
922 927 raise util.Abort(_('cannot partially commit a merge '
923 928 '(do not specify files or patterns)'))
924 929
925 930 changes = self.status(match=match, clean=force)
926 931 if force:
927 932 changes[0].extend(changes[6]) # mq may commit unchanged files
928 933
929 934 # check subrepos
930 935 subs = []
931 936 removedsubs = set()
932 937 for p in wctx.parents():
933 938 removedsubs.update(s for s in p.substate if match(s))
934 939 for s in wctx.substate:
935 940 removedsubs.discard(s)
936 941 if match(s) and wctx.sub(s).dirty():
937 942 subs.append(s)
938 943 if (subs or removedsubs):
939 944 if (not match('.hgsub') and
940 945 '.hgsub' in (wctx.modified() + wctx.added())):
941 946 raise util.Abort(_("can't commit subrepos without .hgsub"))
942 947 if '.hgsubstate' not in changes[0]:
943 948 changes[0].insert(0, '.hgsubstate')
944 949
945 950 if subs and not self.ui.configbool('ui', 'commitsubrepos', True):
946 951 changedsubs = [s for s in subs if wctx.sub(s).dirty(True)]
947 952 if changedsubs:
948 953 raise util.Abort(_("uncommitted changes in subrepo %s")
949 954 % changedsubs[0])
950 955
951 956 # make sure all explicit patterns are matched
952 957 if not force and match.files():
953 958 matched = set(changes[0] + changes[1] + changes[2])
954 959
955 960 for f in match.files():
956 961 if f == '.' or f in matched or f in wctx.substate:
957 962 continue
958 963 if f in changes[3]: # missing
959 964 fail(f, _('file not found!'))
960 965 if f in vdirs: # visited directory
961 966 d = f + '/'
962 967 for mf in matched:
963 968 if mf.startswith(d):
964 969 break
965 970 else:
966 971 fail(f, _("no match under directory!"))
967 972 elif f not in self.dirstate:
968 973 fail(f, _("file not tracked!"))
969 974
970 975 if (not force and not extra.get("close") and not merge
971 976 and not (changes[0] or changes[1] or changes[2])
972 977 and wctx.branch() == wctx.p1().branch()):
973 978 return None
974 979
975 980 ms = mergemod.mergestate(self)
976 981 for f in changes[0]:
977 982 if f in ms and ms[f] == 'u':
978 983 raise util.Abort(_("unresolved merge conflicts "
979 984 "(see hg help resolve)"))
980 985
981 986 cctx = context.workingctx(self, text, user, date, extra, changes)
982 987 if editor:
983 988 cctx._text = editor(self, cctx, subs)
984 989 edited = (text != cctx._text)
985 990
986 991 # commit subs
987 992 if subs or removedsubs:
988 993 state = wctx.substate.copy()
989 994 for s in sorted(subs):
990 995 sub = wctx.sub(s)
991 996 self.ui.status(_('committing subrepository %s\n') %
992 997 subrepo.subrelpath(sub))
993 998 sr = sub.commit(cctx._text, user, date)
994 999 state[s] = (state[s][0], sr)
995 1000 subrepo.writestate(self, state)
996 1001
997 1002 # Save commit message in case this transaction gets rolled back
998 1003 # (e.g. by a pretxncommit hook). Leave the content alone on
999 1004 # the assumption that the user will use the same editor again.
1000 1005 msgfile = self.opener('last-message.txt', 'wb')
1001 1006 msgfile.write(cctx._text)
1002 1007 msgfile.close()
1003 1008
1004 1009 p1, p2 = self.dirstate.parents()
1005 1010 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1006 1011 try:
1007 1012 self.hook("precommit", throw=True, parent1=hookp1, parent2=hookp2)
1008 1013 ret = self.commitctx(cctx, True)
1009 1014 except:
1010 1015 if edited:
1011 1016 msgfn = self.pathto(msgfile.name[len(self.root)+1:])
1012 1017 self.ui.write(
1013 1018 _('note: commit message saved in %s\n') % msgfn)
1014 1019 raise
1015 1020
1016 1021 # update bookmarks, dirstate and mergestate
1017 1022 parents = (p1, p2)
1018 1023 if p2 == nullid:
1019 1024 parents = (p1,)
1020 1025 bookmarks.update(self, parents, ret)
1021 1026 for f in changes[0] + changes[1]:
1022 1027 self.dirstate.normal(f)
1023 1028 for f in changes[2]:
1024 1029 self.dirstate.forget(f)
1025 1030 self.dirstate.setparents(ret)
1026 1031 ms.reset()
1027 1032 finally:
1028 1033 wlock.release()
1029 1034
1030 1035 self.hook("commit", node=hex(ret), parent1=hookp1, parent2=hookp2)
1031 1036 return ret
1032 1037
1033 1038 def commitctx(self, ctx, error=False):
1034 1039 """Add a new revision to current repository.
1035 1040 Revision information is passed via the context argument.
1036 1041 """
1037 1042
1038 1043 tr = lock = None
1039 1044 removed = list(ctx.removed())
1040 1045 p1, p2 = ctx.p1(), ctx.p2()
1041 1046 m1 = p1.manifest().copy()
1042 1047 m2 = p2.manifest()
1043 1048 user = ctx.user()
1044 1049
1045 1050 lock = self.lock()
1046 1051 try:
1047 1052 tr = self.transaction("commit")
1048 1053 trp = weakref.proxy(tr)
1049 1054
1050 1055 # check in files
1051 1056 new = {}
1052 1057 changed = []
1053 1058 linkrev = len(self)
1054 1059 for f in sorted(ctx.modified() + ctx.added()):
1055 1060 self.ui.note(f + "\n")
1056 1061 try:
1057 1062 fctx = ctx[f]
1058 1063 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1059 1064 changed)
1060 1065 m1.set(f, fctx.flags())
1061 1066 except OSError, inst:
1062 1067 self.ui.warn(_("trouble committing %s!\n") % f)
1063 1068 raise
1064 1069 except IOError, inst:
1065 1070 errcode = getattr(inst, 'errno', errno.ENOENT)
1066 1071 if error or errcode and errcode != errno.ENOENT:
1067 1072 self.ui.warn(_("trouble committing %s!\n") % f)
1068 1073 raise
1069 1074 else:
1070 1075 removed.append(f)
1071 1076
1072 1077 # update manifest
1073 1078 m1.update(new)
1074 1079 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1075 1080 drop = [f for f in removed if f in m1]
1076 1081 for f in drop:
1077 1082 del m1[f]
1078 1083 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1079 1084 p2.manifestnode(), (new, drop))
1080 1085
1081 1086 # update changelog
1082 1087 self.changelog.delayupdate()
1083 1088 n = self.changelog.add(mn, changed + removed, ctx.description(),
1084 1089 trp, p1.node(), p2.node(),
1085 1090 user, ctx.date(), ctx.extra().copy())
1086 1091 p = lambda: self.changelog.writepending() and self.root or ""
1087 1092 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1088 1093 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1089 1094 parent2=xp2, pending=p)
1090 1095 self.changelog.finalize(trp)
1091 1096 tr.close()
1092 1097
1093 1098 if self._branchcache:
1094 1099 self.updatebranchcache()
1095 1100 return n
1096 1101 finally:
1097 1102 if tr:
1098 1103 tr.release()
1099 1104 lock.release()
1100 1105
1101 1106 def destroyed(self):
1102 1107 '''Inform the repository that nodes have been destroyed.
1103 1108 Intended for use by strip and rollback, so there's a common
1104 1109 place for anything that has to be done after destroying history.'''
1105 1110 # XXX it might be nice if we could take the list of destroyed
1106 1111 # nodes, but I don't see an easy way for rollback() to do that
1107 1112
1108 1113 # Ensure the persistent tag cache is updated. Doing it now
1109 1114 # means that the tag cache only has to worry about destroyed
1110 1115 # heads immediately after a strip/rollback. That in turn
1111 1116 # guarantees that "cachetip == currenttip" (comparing both rev
1112 1117 # and node) always means no nodes have been added or destroyed.
1113 1118
1114 1119 # XXX this is suboptimal when qrefresh'ing: we strip the current
1115 1120 # head, refresh the tag cache, then immediately add a new head.
1116 1121 # But I think doing it this way is necessary for the "instant
1117 1122 # tag cache retrieval" case to work.
1118 1123 self.invalidatecaches()
1119 1124
1120 1125 def walk(self, match, node=None):
1121 1126 '''
1122 1127 walk recursively through the directory tree or a given
1123 1128 changeset, finding all files matched by the match
1124 1129 function
1125 1130 '''
1126 1131 return self[node].walk(match)
1127 1132
1128 1133 def status(self, node1='.', node2=None, match=None,
1129 1134 ignored=False, clean=False, unknown=False,
1130 1135 listsubrepos=False):
1131 1136 """return status of files between two nodes or node and working directory
1132 1137
1133 1138 If node1 is None, use the first dirstate parent instead.
1134 1139 If node2 is None, compare node1 with working directory.
1135 1140 """
1136 1141
1137 1142 def mfmatches(ctx):
1138 1143 mf = ctx.manifest().copy()
1139 1144 for fn in mf.keys():
1140 1145 if not match(fn):
1141 1146 del mf[fn]
1142 1147 return mf
1143 1148
1144 1149 if isinstance(node1, context.changectx):
1145 1150 ctx1 = node1
1146 1151 else:
1147 1152 ctx1 = self[node1]
1148 1153 if isinstance(node2, context.changectx):
1149 1154 ctx2 = node2
1150 1155 else:
1151 1156 ctx2 = self[node2]
1152 1157
1153 1158 working = ctx2.rev() is None
1154 1159 parentworking = working and ctx1 == self['.']
1155 1160 match = match or matchmod.always(self.root, self.getcwd())
1156 1161 listignored, listclean, listunknown = ignored, clean, unknown
1157 1162
1158 1163 # load earliest manifest first for caching reasons
1159 1164 if not working and ctx2.rev() < ctx1.rev():
1160 1165 ctx2.manifest()
1161 1166
1162 1167 if not parentworking:
1163 1168 def bad(f, msg):
1164 1169 if f not in ctx1:
1165 1170 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1166 1171 match.bad = bad
1167 1172
1168 1173 if working: # we need to scan the working dir
1169 1174 subrepos = []
1170 1175 if '.hgsub' in self.dirstate:
1171 1176 subrepos = ctx1.substate.keys()
1172 1177 s = self.dirstate.status(match, subrepos, listignored,
1173 1178 listclean, listunknown)
1174 1179 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1175 1180
1176 1181 # check for any possibly clean files
1177 1182 if parentworking and cmp:
1178 1183 fixup = []
1179 1184 # do a full compare of any files that might have changed
1180 1185 for f in sorted(cmp):
1181 1186 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1182 1187 or ctx1[f].cmp(ctx2[f])):
1183 1188 modified.append(f)
1184 1189 else:
1185 1190 fixup.append(f)
1186 1191
1187 1192 # update dirstate for files that are actually clean
1188 1193 if fixup:
1189 1194 if listclean:
1190 1195 clean += fixup
1191 1196
1192 1197 try:
1193 1198 # updating the dirstate is optional
1194 1199 # so we don't wait on the lock
1195 1200 wlock = self.wlock(False)
1196 1201 try:
1197 1202 for f in fixup:
1198 1203 self.dirstate.normal(f)
1199 1204 finally:
1200 1205 wlock.release()
1201 1206 except error.LockError:
1202 1207 pass
1203 1208
1204 1209 if not parentworking:
1205 1210 mf1 = mfmatches(ctx1)
1206 1211 if working:
1207 1212 # we are comparing working dir against non-parent
1208 1213 # generate a pseudo-manifest for the working dir
1209 1214 mf2 = mfmatches(self['.'])
1210 1215 for f in cmp + modified + added:
1211 1216 mf2[f] = None
1212 1217 mf2.set(f, ctx2.flags(f))
1213 1218 for f in removed:
1214 1219 if f in mf2:
1215 1220 del mf2[f]
1216 1221 else:
1217 1222 # we are comparing two revisions
1218 1223 deleted, unknown, ignored = [], [], []
1219 1224 mf2 = mfmatches(ctx2)
1220 1225
1221 1226 modified, added, clean = [], [], []
1222 1227 for fn in mf2:
1223 1228 if fn in mf1:
1224 1229 if (mf1.flags(fn) != mf2.flags(fn) or
1225 1230 (mf1[fn] != mf2[fn] and
1226 1231 (mf2[fn] or ctx1[fn].cmp(ctx2[fn])))):
1227 1232 modified.append(fn)
1228 1233 elif listclean:
1229 1234 clean.append(fn)
1230 1235 del mf1[fn]
1231 1236 else:
1232 1237 added.append(fn)
1233 1238 removed = mf1.keys()
1234 1239
1235 1240 r = modified, added, removed, deleted, unknown, ignored, clean
1236 1241
1237 1242 if listsubrepos:
1238 1243 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1239 1244 if working:
1240 1245 rev2 = None
1241 1246 else:
1242 1247 rev2 = ctx2.substate[subpath][1]
1243 1248 try:
1244 1249 submatch = matchmod.narrowmatcher(subpath, match)
1245 1250 s = sub.status(rev2, match=submatch, ignored=listignored,
1246 1251 clean=listclean, unknown=listunknown,
1247 1252 listsubrepos=True)
1248 1253 for rfiles, sfiles in zip(r, s):
1249 1254 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1250 1255 except error.LookupError:
1251 1256 self.ui.status(_("skipping missing subrepository: %s\n")
1252 1257 % subpath)
1253 1258
1254 1259 for l in r:
1255 1260 l.sort()
1256 1261 return r
1257 1262
1258 1263 def heads(self, start=None):
1259 1264 heads = self.changelog.heads(start)
1260 1265 # sort the output in rev descending order
1261 1266 return sorted(heads, key=self.changelog.rev, reverse=True)
1262 1267
1263 1268 def branchheads(self, branch=None, start=None, closed=False):
1264 1269 '''return a (possibly filtered) list of heads for the given branch
1265 1270
1266 1271 Heads are returned in topological order, from newest to oldest.
1267 1272 If branch is None, use the dirstate branch.
1268 1273 If start is not None, return only heads reachable from start.
1269 1274 If closed is True, return heads that are marked as closed as well.
1270 1275 '''
1271 1276 if branch is None:
1272 1277 branch = self[None].branch()
1273 1278 branches = self.branchmap()
1274 1279 if branch not in branches:
1275 1280 return []
1276 1281 # the cache returns heads ordered lowest to highest
1277 1282 bheads = list(reversed(branches[branch]))
1278 1283 if start is not None:
1279 1284 # filter out the heads that cannot be reached from startrev
1280 1285 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1281 1286 bheads = [h for h in bheads if h in fbheads]
1282 1287 if not closed:
1283 1288 bheads = [h for h in bheads if
1284 1289 ('close' not in self.changelog.read(h)[5])]
1285 1290 return bheads
1286 1291
1287 1292 def branches(self, nodes):
1288 1293 if not nodes:
1289 1294 nodes = [self.changelog.tip()]
1290 1295 b = []
1291 1296 for n in nodes:
1292 1297 t = n
1293 1298 while 1:
1294 1299 p = self.changelog.parents(n)
1295 1300 if p[1] != nullid or p[0] == nullid:
1296 1301 b.append((t, n, p[0], p[1]))
1297 1302 break
1298 1303 n = p[0]
1299 1304 return b
1300 1305
1301 1306 def between(self, pairs):
1302 1307 r = []
1303 1308
1304 1309 for top, bottom in pairs:
1305 1310 n, l, i = top, [], 0
1306 1311 f = 1
1307 1312
1308 1313 while n != bottom and n != nullid:
1309 1314 p = self.changelog.parents(n)[0]
1310 1315 if i == f:
1311 1316 l.append(n)
1312 1317 f = f * 2
1313 1318 n = p
1314 1319 i += 1
1315 1320
1316 1321 r.append(l)
1317 1322
1318 1323 return r
1319 1324
1320 1325 def pull(self, remote, heads=None, force=False):
1321 1326 lock = self.lock()
1322 1327 try:
1323 1328 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1324 1329 force=force)
1325 1330 common, fetch, rheads = tmp
1326 1331 if not fetch:
1327 1332 self.ui.status(_("no changes found\n"))
1328 1333 result = 0
1329 1334 else:
1330 1335 if heads is None and fetch == [nullid]:
1331 1336 self.ui.status(_("requesting all changes\n"))
1332 1337 elif heads is None and remote.capable('changegroupsubset'):
1333 1338 # issue1320, avoid a race if remote changed after discovery
1334 1339 heads = rheads
1335 1340
1336 1341 if heads is None:
1337 1342 cg = remote.changegroup(fetch, 'pull')
1338 1343 elif not remote.capable('changegroupsubset'):
1339 1344 raise util.Abort(_("partial pull cannot be done because "
1340 1345 "other repository doesn't support "
1341 1346 "changegroupsubset."))
1342 1347 else:
1343 1348 cg = remote.changegroupsubset(fetch, heads, 'pull')
1344 1349 result = self.addchangegroup(cg, 'pull', remote.url(),
1345 1350 lock=lock)
1346 1351 finally:
1347 1352 lock.release()
1348 1353
1349 1354 return result
1350 1355
1351 1356 def checkpush(self, force, revs):
1352 1357 """Extensions can override this function if additional checks have
1353 1358 to be performed before pushing, or call it if they override push
1354 1359 command.
1355 1360 """
1356 1361 pass
1357 1362
1358 1363 def push(self, remote, force=False, revs=None, newbranch=False):
1359 1364 '''Push outgoing changesets (limited by revs) from the current
1360 1365 repository to remote. Return an integer:
1361 1366 - 0 means HTTP error *or* nothing to push
1362 1367 - 1 means we pushed and remote head count is unchanged *or*
1363 1368 we have outgoing changesets but refused to push
1364 1369 - other values as described by addchangegroup()
1365 1370 '''
1366 1371 # there are two ways to push to remote repo:
1367 1372 #
1368 1373 # addchangegroup assumes local user can lock remote
1369 1374 # repo (local filesystem, old ssh servers).
1370 1375 #
1371 1376 # unbundle assumes local user cannot lock remote repo (new ssh
1372 1377 # servers, http servers).
1373 1378
1374 1379 self.checkpush(force, revs)
1375 1380 lock = None
1376 1381 unbundle = remote.capable('unbundle')
1377 1382 if not unbundle:
1378 1383 lock = remote.lock()
1379 1384 try:
1380 1385 cg, remote_heads = discovery.prepush(self, remote, force, revs,
1381 1386 newbranch)
1382 1387 ret = remote_heads
1383 1388 if cg is not None:
1384 1389 if unbundle:
1385 1390 # local repo finds heads on server, finds out what
1386 1391 # revs it must push. once revs transferred, if server
1387 1392 # finds it has different heads (someone else won
1388 1393 # commit/push race), server aborts.
1389 1394 if force:
1390 1395 remote_heads = ['force']
1391 1396 # ssh: return remote's addchangegroup()
1392 1397 # http: return remote's addchangegroup() or 0 for error
1393 1398 ret = remote.unbundle(cg, remote_heads, 'push')
1394 1399 else:
1395 1400 # we return an integer indicating remote head count change
1396 1401 ret = remote.addchangegroup(cg, 'push', self.url(),
1397 1402 lock=lock)
1398 1403 finally:
1399 1404 if lock is not None:
1400 1405 lock.release()
1401 1406
1402 1407 self.ui.debug("checking for updated bookmarks\n")
1403 1408 rb = remote.listkeys('bookmarks')
1404 1409 for k in rb.keys():
1405 1410 if k in self._bookmarks:
1406 1411 nr, nl = rb[k], hex(self._bookmarks[k])
1407 1412 if nr in self:
1408 1413 cr = self[nr]
1409 1414 cl = self[nl]
1410 1415 if cl in cr.descendants():
1411 1416 r = remote.pushkey('bookmarks', k, nr, nl)
1412 1417 if r:
1413 1418 self.ui.status(_("updating bookmark %s\n") % k)
1414 1419 else:
1415 1420 self.ui.warn(_('updating bookmark %s'
1416 1421 ' failed!\n') % k)
1417 1422
1418 1423 return ret
1419 1424
1420 1425 def changegroupinfo(self, nodes, source):
1421 1426 if self.ui.verbose or source == 'bundle':
1422 1427 self.ui.status(_("%d changesets found\n") % len(nodes))
1423 1428 if self.ui.debugflag:
1424 1429 self.ui.debug("list of changesets:\n")
1425 1430 for node in nodes:
1426 1431 self.ui.debug("%s\n" % hex(node))
1427 1432
1428 1433 def changegroupsubset(self, bases, heads, source):
1429 1434 """Compute a changegroup consisting of all the nodes that are
1430 1435 descendents of any of the bases and ancestors of any of the heads.
1431 1436 Return a chunkbuffer object whose read() method will return
1432 1437 successive changegroup chunks.
1433 1438
1434 1439 It is fairly complex as determining which filenodes and which
1435 1440 manifest nodes need to be included for the changeset to be complete
1436 1441 is non-trivial.
1437 1442
1438 1443 Another wrinkle is doing the reverse, figuring out which changeset in
1439 1444 the changegroup a particular filenode or manifestnode belongs to.
1440 1445 """
1441 1446
1442 1447 cl = self.changelog
1443 1448 mf = self.manifest
1444 1449 mfs = {} # needed manifests
1445 1450 fnodes = {} # needed file nodes
1446 1451
1447 1452 if not bases:
1448 1453 bases = [nullid]
1449 1454 csets, bases, heads = cl.nodesbetween(bases, heads)
1450 1455
1451 1456 # can we go through the fast path ?
1452 1457 heads.sort()
1453 1458 if heads == sorted(self.heads()):
1454 1459 return self._changegroup(csets, source)
1455 1460
1456 1461 # slow path
1457 1462 self.hook('preoutgoing', throw=True, source=source)
1458 1463 self.changegroupinfo(csets, source)
1459 1464
1460 1465 # We assume that all ancestors of bases are known
1461 1466 commonrevs = set(cl.ancestors(*[cl.rev(n) for n in bases]))
1462 1467
1463 1468 # A function generating function that sets up the initial environment
1464 1469 # the inner function.
1465 1470 def filenode_collector(changedfiles):
1466 1471 # This gathers information from each manifestnode included in the
1467 1472 # changegroup about which filenodes the manifest node references
1468 1473 # so we can include those in the changegroup too.
1469 1474 #
1470 1475 # It also remembers which changenode each filenode belongs to. It
1471 1476 # does this by assuming the a filenode belongs to the changenode
1472 1477 # the first manifest that references it belongs to.
1473 1478 def collect(mnode):
1474 1479 r = mf.rev(mnode)
1475 1480 clnode = mfs[mnode]
1476 1481 mdata = mf.readfast(mnode)
1477 1482 for f in changedfiles:
1478 1483 if f in mdata:
1479 1484 fnodes.setdefault(f, {}).setdefault(mdata[f], clnode)
1480 1485
1481 1486 return collect
1482 1487
1483 1488 # If we determine that a particular file or manifest node must be a
1484 1489 # node that the recipient of the changegroup will already have, we can
1485 1490 # also assume the recipient will have all the parents. This function
1486 1491 # prunes them from the set of missing nodes.
1487 1492 def prune(revlog, missingnodes):
1488 1493 # drop any nodes that claim to be part of a cset in commonrevs
1489 1494 drop = set()
1490 1495 for n in missingnodes:
1491 1496 if revlog.linkrev(revlog.rev(n)) in commonrevs:
1492 1497 drop.add(n)
1493 1498 for n in drop:
1494 1499 missingnodes.pop(n, None)
1495 1500
1496 1501 # Now that we have all theses utility functions to help out and
1497 1502 # logically divide up the task, generate the group.
1498 1503 def gengroup():
1499 1504 # The set of changed files starts empty.
1500 1505 changedfiles = set()
1501 1506 collect = changegroup.collector(cl, mfs, changedfiles)
1502 1507
1503 1508 # Create a changenode group generator that will call our functions
1504 1509 # back to lookup the owning changenode and collect information.
1505 1510 group = cl.group(csets, lambda x: x, collect)
1506 1511 for count, chunk in enumerate(group):
1507 1512 yield chunk
1508 1513 # revlog.group yields three entries per node, so
1509 1514 # dividing by 3 gives an approximation of how many
1510 1515 # nodes have been processed.
1511 1516 self.ui.progress(_('bundling'), count / 3,
1512 1517 unit=_('changesets'))
1513 1518 changecount = count / 3
1514 1519 efiles = len(changedfiles)
1515 1520 self.ui.progress(_('bundling'), None)
1516 1521
1517 1522 prune(mf, mfs)
1518 1523 # Create a generator for the manifestnodes that calls our lookup
1519 1524 # and data collection functions back.
1520 1525 group = mf.group(sorted(mfs, key=mf.rev),
1521 1526 lambda mnode: mfs[mnode],
1522 1527 filenode_collector(changedfiles))
1523 1528 for count, chunk in enumerate(group):
1524 1529 yield chunk
1525 1530 # see above comment for why we divide by 3
1526 1531 self.ui.progress(_('bundling'), count / 3,
1527 1532 unit=_('manifests'), total=changecount)
1528 1533 self.ui.progress(_('bundling'), None)
1529 1534
1530 1535 mfs.clear()
1531 1536
1532 1537 # Go through all our files in order sorted by name.
1533 1538 for idx, fname in enumerate(sorted(changedfiles)):
1534 1539 filerevlog = self.file(fname)
1535 1540 if not len(filerevlog):
1536 1541 raise util.Abort(_("empty or missing revlog for %s") % fname)
1537 1542 # Toss out the filenodes that the recipient isn't really
1538 1543 # missing.
1539 1544 missingfnodes = fnodes.pop(fname, {})
1540 1545 prune(filerevlog, missingfnodes)
1541 1546 # If any filenodes are left, generate the group for them,
1542 1547 # otherwise don't bother.
1543 1548 if missingfnodes:
1544 1549 yield changegroup.chunkheader(len(fname))
1545 1550 yield fname
1546 1551 # Create a group generator and only pass in a changenode
1547 1552 # lookup function as we need to collect no information
1548 1553 # from filenodes.
1549 1554 group = filerevlog.group(
1550 1555 sorted(missingfnodes, key=filerevlog.rev),
1551 1556 lambda fnode: missingfnodes[fnode])
1552 1557 for chunk in group:
1553 1558 # even though we print the same progress on
1554 1559 # most loop iterations, put the progress call
1555 1560 # here so that time estimates (if any) can be updated
1556 1561 self.ui.progress(
1557 1562 _('bundling'), idx, item=fname,
1558 1563 unit=_('files'), total=efiles)
1559 1564 yield chunk
1560 1565 # Signal that no more groups are left.
1561 1566 yield changegroup.closechunk()
1562 1567 self.ui.progress(_('bundling'), None)
1563 1568
1564 1569 if csets:
1565 1570 self.hook('outgoing', node=hex(csets[0]), source=source)
1566 1571
1567 1572 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1568 1573
1569 1574 def changegroup(self, basenodes, source):
1570 1575 # to avoid a race we use changegroupsubset() (issue1320)
1571 1576 return self.changegroupsubset(basenodes, self.heads(), source)
1572 1577
1573 1578 def _changegroup(self, nodes, source):
1574 1579 """Compute the changegroup of all nodes that we have that a recipient
1575 1580 doesn't. Return a chunkbuffer object whose read() method will return
1576 1581 successive changegroup chunks.
1577 1582
1578 1583 This is much easier than the previous function as we can assume that
1579 1584 the recipient has any changenode we aren't sending them.
1580 1585
1581 1586 nodes is the set of nodes to send"""
1582 1587
1583 1588 self.hook('preoutgoing', throw=True, source=source)
1584 1589
1585 1590 cl = self.changelog
1586 1591 revset = set([cl.rev(n) for n in nodes])
1587 1592 self.changegroupinfo(nodes, source)
1588 1593
1589 1594 def gennodelst(log):
1590 1595 for r in log:
1591 1596 if log.linkrev(r) in revset:
1592 1597 yield log.node(r)
1593 1598
1594 1599 def lookuplinkrev_func(revlog):
1595 1600 def lookuplinkrev(n):
1596 1601 return cl.node(revlog.linkrev(revlog.rev(n)))
1597 1602 return lookuplinkrev
1598 1603
1599 1604 def gengroup():
1600 1605 '''yield a sequence of changegroup chunks (strings)'''
1601 1606 # construct a list of all changed files
1602 1607 changedfiles = set()
1603 1608 mmfs = {}
1604 1609 collect = changegroup.collector(cl, mmfs, changedfiles)
1605 1610
1606 1611 for count, chunk in enumerate(cl.group(nodes, lambda x: x, collect)):
1607 1612 # revlog.group yields three entries per node, so
1608 1613 # dividing by 3 gives an approximation of how many
1609 1614 # nodes have been processed.
1610 1615 self.ui.progress(_('bundling'), count / 3, unit=_('changesets'))
1611 1616 yield chunk
1612 1617 efiles = len(changedfiles)
1613 1618 changecount = count / 3
1614 1619 self.ui.progress(_('bundling'), None)
1615 1620
1616 1621 mnfst = self.manifest
1617 1622 nodeiter = gennodelst(mnfst)
1618 1623 for count, chunk in enumerate(mnfst.group(nodeiter,
1619 1624 lookuplinkrev_func(mnfst))):
1620 1625 # see above comment for why we divide by 3
1621 1626 self.ui.progress(_('bundling'), count / 3,
1622 1627 unit=_('manifests'), total=changecount)
1623 1628 yield chunk
1624 1629 self.ui.progress(_('bundling'), None)
1625 1630
1626 1631 for idx, fname in enumerate(sorted(changedfiles)):
1627 1632 filerevlog = self.file(fname)
1628 1633 if not len(filerevlog):
1629 1634 raise util.Abort(_("empty or missing revlog for %s") % fname)
1630 1635 nodeiter = gennodelst(filerevlog)
1631 1636 nodeiter = list(nodeiter)
1632 1637 if nodeiter:
1633 1638 yield changegroup.chunkheader(len(fname))
1634 1639 yield fname
1635 1640 lookup = lookuplinkrev_func(filerevlog)
1636 1641 for chunk in filerevlog.group(nodeiter, lookup):
1637 1642 self.ui.progress(
1638 1643 _('bundling'), idx, item=fname,
1639 1644 total=efiles, unit=_('files'))
1640 1645 yield chunk
1641 1646 self.ui.progress(_('bundling'), None)
1642 1647
1643 1648 yield changegroup.closechunk()
1644 1649
1645 1650 if nodes:
1646 1651 self.hook('outgoing', node=hex(nodes[0]), source=source)
1647 1652
1648 1653 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1649 1654
1650 1655 def addchangegroup(self, source, srctype, url, emptyok=False, lock=None):
1651 1656 """Add the changegroup returned by source.read() to this repo.
1652 1657 srctype is a string like 'push', 'pull', or 'unbundle'. url is
1653 1658 the URL of the repo where this changegroup is coming from.
1654 1659 If lock is not None, the function takes ownership of the lock
1655 1660 and releases it after the changegroup is added.
1656 1661
1657 1662 Return an integer summarizing the change to this repo:
1658 1663 - nothing changed or no source: 0
1659 1664 - more heads than before: 1+added heads (2..n)
1660 1665 - fewer heads than before: -1-removed heads (-2..-n)
1661 1666 - number of heads stays the same: 1
1662 1667 """
1663 1668 def csmap(x):
1664 1669 self.ui.debug("add changeset %s\n" % short(x))
1665 1670 return len(cl)
1666 1671
1667 1672 def revmap(x):
1668 1673 return cl.rev(x)
1669 1674
1670 1675 if not source:
1671 1676 return 0
1672 1677
1673 1678 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1674 1679
1675 1680 changesets = files = revisions = 0
1676 1681 efiles = set()
1677 1682
1678 1683 # write changelog data to temp files so concurrent readers will not see
1679 1684 # inconsistent view
1680 1685 cl = self.changelog
1681 1686 cl.delayupdate()
1682 1687 oldheads = len(cl.heads())
1683 1688
1684 1689 tr = self.transaction("\n".join([srctype, urlmod.hidepassword(url)]))
1685 1690 try:
1686 1691 trp = weakref.proxy(tr)
1687 1692 # pull off the changeset group
1688 1693 self.ui.status(_("adding changesets\n"))
1689 1694 clstart = len(cl)
1690 1695 class prog(object):
1691 1696 step = _('changesets')
1692 1697 count = 1
1693 1698 ui = self.ui
1694 1699 total = None
1695 1700 def __call__(self):
1696 1701 self.ui.progress(self.step, self.count, unit=_('chunks'),
1697 1702 total=self.total)
1698 1703 self.count += 1
1699 1704 pr = prog()
1700 1705 source.callback = pr
1701 1706
1702 1707 if (cl.addgroup(source, csmap, trp) is None
1703 1708 and not emptyok):
1704 1709 raise util.Abort(_("received changelog group is empty"))
1705 1710 clend = len(cl)
1706 1711 changesets = clend - clstart
1707 1712 for c in xrange(clstart, clend):
1708 1713 efiles.update(self[c].files())
1709 1714 efiles = len(efiles)
1710 1715 self.ui.progress(_('changesets'), None)
1711 1716
1712 1717 # pull off the manifest group
1713 1718 self.ui.status(_("adding manifests\n"))
1714 1719 pr.step = _('manifests')
1715 1720 pr.count = 1
1716 1721 pr.total = changesets # manifests <= changesets
1717 1722 # no need to check for empty manifest group here:
1718 1723 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1719 1724 # no new manifest will be created and the manifest group will
1720 1725 # be empty during the pull
1721 1726 self.manifest.addgroup(source, revmap, trp)
1722 1727 self.ui.progress(_('manifests'), None)
1723 1728
1724 1729 needfiles = {}
1725 1730 if self.ui.configbool('server', 'validate', default=False):
1726 1731 # validate incoming csets have their manifests
1727 1732 for cset in xrange(clstart, clend):
1728 1733 mfest = self.changelog.read(self.changelog.node(cset))[0]
1729 1734 mfest = self.manifest.readdelta(mfest)
1730 1735 # store file nodes we must see
1731 1736 for f, n in mfest.iteritems():
1732 1737 needfiles.setdefault(f, set()).add(n)
1733 1738
1734 1739 # process the files
1735 1740 self.ui.status(_("adding file changes\n"))
1736 1741 pr.step = 'files'
1737 1742 pr.count = 1
1738 1743 pr.total = efiles
1739 1744 source.callback = None
1740 1745
1741 1746 while 1:
1742 1747 f = source.chunk()
1743 1748 if not f:
1744 1749 break
1745 1750 self.ui.debug("adding %s revisions\n" % f)
1746 1751 pr()
1747 1752 fl = self.file(f)
1748 1753 o = len(fl)
1749 1754 if fl.addgroup(source, revmap, trp) is None:
1750 1755 raise util.Abort(_("received file revlog group is empty"))
1751 1756 revisions += len(fl) - o
1752 1757 files += 1
1753 1758 if f in needfiles:
1754 1759 needs = needfiles[f]
1755 1760 for new in xrange(o, len(fl)):
1756 1761 n = fl.node(new)
1757 1762 if n in needs:
1758 1763 needs.remove(n)
1759 1764 if not needs:
1760 1765 del needfiles[f]
1761 1766 self.ui.progress(_('files'), None)
1762 1767
1763 1768 for f, needs in needfiles.iteritems():
1764 1769 fl = self.file(f)
1765 1770 for n in needs:
1766 1771 try:
1767 1772 fl.rev(n)
1768 1773 except error.LookupError:
1769 1774 raise util.Abort(
1770 1775 _('missing file data for %s:%s - run hg verify') %
1771 1776 (f, hex(n)))
1772 1777
1773 1778 newheads = len(cl.heads())
1774 1779 heads = ""
1775 1780 if oldheads and newheads != oldheads:
1776 1781 heads = _(" (%+d heads)") % (newheads - oldheads)
1777 1782
1778 1783 self.ui.status(_("added %d changesets"
1779 1784 " with %d changes to %d files%s\n")
1780 1785 % (changesets, revisions, files, heads))
1781 1786
1782 1787 if changesets > 0:
1783 1788 p = lambda: cl.writepending() and self.root or ""
1784 1789 self.hook('pretxnchangegroup', throw=True,
1785 1790 node=hex(cl.node(clstart)), source=srctype,
1786 1791 url=url, pending=p)
1787 1792
1788 1793 # make changelog see real files again
1789 1794 cl.finalize(trp)
1790 1795
1791 1796 tr.close()
1792 1797 finally:
1793 1798 tr.release()
1794 1799 if lock:
1795 1800 lock.release()
1796 1801
1797 1802 if changesets > 0:
1798 1803 # forcefully update the on-disk branch cache
1799 1804 self.ui.debug("updating the branch cache\n")
1800 1805 self.updatebranchcache()
1801 1806 self.hook("changegroup", node=hex(cl.node(clstart)),
1802 1807 source=srctype, url=url)
1803 1808
1804 1809 for i in xrange(clstart, clend):
1805 1810 self.hook("incoming", node=hex(cl.node(i)),
1806 1811 source=srctype, url=url)
1807 1812
1808 1813 # never return 0 here:
1809 1814 if newheads < oldheads:
1810 1815 return newheads - oldheads - 1
1811 1816 else:
1812 1817 return newheads - oldheads + 1
1813 1818
1814 1819
1815 1820 def stream_in(self, remote, requirements):
1816 1821 lock = self.lock()
1817 1822 try:
1818 1823 fp = remote.stream_out()
1819 1824 l = fp.readline()
1820 1825 try:
1821 1826 resp = int(l)
1822 1827 except ValueError:
1823 1828 raise error.ResponseError(
1824 1829 _('Unexpected response from remote server:'), l)
1825 1830 if resp == 1:
1826 1831 raise util.Abort(_('operation forbidden by server'))
1827 1832 elif resp == 2:
1828 1833 raise util.Abort(_('locking the remote repository failed'))
1829 1834 elif resp != 0:
1830 1835 raise util.Abort(_('the server sent an unknown error code'))
1831 1836 self.ui.status(_('streaming all changes\n'))
1832 1837 l = fp.readline()
1833 1838 try:
1834 1839 total_files, total_bytes = map(int, l.split(' ', 1))
1835 1840 except (ValueError, TypeError):
1836 1841 raise error.ResponseError(
1837 1842 _('Unexpected response from remote server:'), l)
1838 1843 self.ui.status(_('%d files to transfer, %s of data\n') %
1839 1844 (total_files, util.bytecount(total_bytes)))
1840 1845 start = time.time()
1841 1846 for i in xrange(total_files):
1842 1847 # XXX doesn't support '\n' or '\r' in filenames
1843 1848 l = fp.readline()
1844 1849 try:
1845 1850 name, size = l.split('\0', 1)
1846 1851 size = int(size)
1847 1852 except (ValueError, TypeError):
1848 1853 raise error.ResponseError(
1849 1854 _('Unexpected response from remote server:'), l)
1850 1855 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
1851 1856 # for backwards compat, name was partially encoded
1852 1857 ofp = self.sopener(store.decodedir(name), 'w')
1853 1858 for chunk in util.filechunkiter(fp, limit=size):
1854 1859 ofp.write(chunk)
1855 1860 ofp.close()
1856 1861 elapsed = time.time() - start
1857 1862 if elapsed <= 0:
1858 1863 elapsed = 0.001
1859 1864 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1860 1865 (util.bytecount(total_bytes), elapsed,
1861 1866 util.bytecount(total_bytes / elapsed)))
1862 1867
1863 1868 # new requirements = old non-format requirements + new format-related
1864 1869 # requirements from the streamed-in repository
1865 1870 requirements.update(set(self.requirements) - self.supportedformats)
1866 1871 self._applyrequirements(requirements)
1867 1872 self._writerequirements()
1868 1873
1869 1874 self.invalidate()
1870 1875 return len(self.heads()) + 1
1871 1876 finally:
1872 1877 lock.release()
1873 1878
1874 1879 def clone(self, remote, heads=[], stream=False):
1875 1880 '''clone remote repository.
1876 1881
1877 1882 keyword arguments:
1878 1883 heads: list of revs to clone (forces use of pull)
1879 1884 stream: use streaming clone if possible'''
1880 1885
1881 1886 # now, all clients that can request uncompressed clones can
1882 1887 # read repo formats supported by all servers that can serve
1883 1888 # them.
1884 1889
1885 1890 # if revlog format changes, client will have to check version
1886 1891 # and format flags on "stream" capability, and use
1887 1892 # uncompressed only if compatible.
1888 1893
1889 1894 if stream and not heads:
1890 1895 # 'stream' means remote revlog format is revlogv1 only
1891 1896 if remote.capable('stream'):
1892 1897 return self.stream_in(remote, set(('revlogv1',)))
1893 1898 # otherwise, 'streamreqs' contains the remote revlog format
1894 1899 streamreqs = remote.capable('streamreqs')
1895 1900 if streamreqs:
1896 1901 streamreqs = set(streamreqs.split(','))
1897 1902 # if we support it, stream in and adjust our requirements
1898 1903 if not streamreqs - self.supportedformats:
1899 1904 return self.stream_in(remote, streamreqs)
1900 1905 return self.pull(remote, heads)
1901 1906
1902 1907 def pushkey(self, namespace, key, old, new):
1903 1908 return pushkey.push(self, namespace, key, old, new)
1904 1909
1905 1910 def listkeys(self, namespace):
1906 1911 return pushkey.list(self, namespace)
1907 1912
1908 1913 def debugwireargs(self, one, two, three=None, four=None):
1909 1914 '''used to test argument passing over the wire'''
1910 1915 return "%s %s %s %s" % (one, two, three, four)
1911 1916
1912 1917 # used to avoid circular references so destructors work
1913 1918 def aftertrans(files):
1914 1919 renamefiles = [tuple(t) for t in files]
1915 1920 def a():
1916 1921 for src, dest in renamefiles:
1917 1922 util.rename(src, dest)
1918 1923 return a
1919 1924
1920 1925 def instance(ui, path, create):
1921 1926 return localrepository(ui, util.drop_scheme('file', path), create)
1922 1927
1923 1928 def islocal(path):
1924 1929 return True
@@ -1,381 +1,393 b''
1 1 # wireproto.py - generic wire protocol support functions
2 2 #
3 3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import urllib, tempfile, os, sys
9 9 from i18n import _
10 10 from node import bin, hex
11 11 import changegroup as changegroupmod
12 12 import repo, error, encoding, util, store
13 13 import pushkey as pushkeymod
14 14
15 15 # list of nodes encoding / decoding
16 16
17 17 def decodelist(l, sep=' '):
18 18 if l:
19 19 return map(bin, l.split(sep))
20 20 return []
21 21
22 22 def encodelist(l, sep=' '):
23 23 return sep.join(map(hex, l))
24 24
25 25 # client side
26 26
27 27 class wirerepository(repo.repository):
28 28 def lookup(self, key):
29 29 self.requirecap('lookup', _('look up remote revision'))
30 30 d = self._call("lookup", key=encoding.fromlocal(key))
31 31 success, data = d[:-1].split(" ", 1)
32 32 if int(success):
33 33 return bin(data)
34 34 self._abort(error.RepoError(data))
35 35
36 36 def heads(self):
37 37 d = self._call("heads")
38 38 try:
39 39 return decodelist(d[:-1])
40 40 except:
41 41 self._abort(error.ResponseError(_("unexpected response:"), d))
42 42
43 def known(self, nodes):
44 n = encodelist(nodes)
45 d = self._call("known", nodes=n)
46 try:
47 return [bool(int(f)) for f in d]
48 except:
49 self._abort(error.ResponseError(_("unexpected response:"), d))
50
43 51 def branchmap(self):
44 52 d = self._call("branchmap")
45 53 try:
46 54 branchmap = {}
47 55 for branchpart in d.splitlines():
48 56 branchname, branchheads = branchpart.split(' ', 1)
49 57 branchname = encoding.tolocal(urllib.unquote(branchname))
50 58 branchheads = decodelist(branchheads)
51 59 branchmap[branchname] = branchheads
52 60 return branchmap
53 61 except TypeError:
54 62 self._abort(error.ResponseError(_("unexpected response:"), d))
55 63
56 64 def branches(self, nodes):
57 65 n = encodelist(nodes)
58 66 d = self._call("branches", nodes=n)
59 67 try:
60 68 br = [tuple(decodelist(b)) for b in d.splitlines()]
61 69 return br
62 70 except:
63 71 self._abort(error.ResponseError(_("unexpected response:"), d))
64 72
65 73 def between(self, pairs):
66 74 batch = 8 # avoid giant requests
67 75 r = []
68 76 for i in xrange(0, len(pairs), batch):
69 77 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
70 78 d = self._call("between", pairs=n)
71 79 try:
72 80 r.extend(l and decodelist(l) or [] for l in d.splitlines())
73 81 except:
74 82 self._abort(error.ResponseError(_("unexpected response:"), d))
75 83 return r
76 84
77 85 def pushkey(self, namespace, key, old, new):
78 86 if not self.capable('pushkey'):
79 87 return False
80 88 d = self._call("pushkey",
81 89 namespace=encoding.fromlocal(namespace),
82 90 key=encoding.fromlocal(key),
83 91 old=encoding.fromlocal(old),
84 92 new=encoding.fromlocal(new))
85 93 try:
86 94 d = bool(int(d))
87 95 except ValueError:
88 96 raise error.ResponseError(
89 97 _('push failed (unexpected response):'), d)
90 98 return d
91 99
92 100 def listkeys(self, namespace):
93 101 if not self.capable('pushkey'):
94 102 return {}
95 103 d = self._call("listkeys", namespace=encoding.fromlocal(namespace))
96 104 r = {}
97 105 for l in d.splitlines():
98 106 k, v = l.split('\t')
99 107 r[encoding.tolocal(k)] = encoding.tolocal(v)
100 108 return r
101 109
102 110 def stream_out(self):
103 111 return self._callstream('stream_out')
104 112
105 113 def changegroup(self, nodes, kind):
106 114 n = encodelist(nodes)
107 115 f = self._callstream("changegroup", roots=n)
108 116 return changegroupmod.unbundle10(self._decompress(f), 'UN')
109 117
110 118 def changegroupsubset(self, bases, heads, kind):
111 119 self.requirecap('changegroupsubset', _('look up remote changes'))
112 120 bases = encodelist(bases)
113 121 heads = encodelist(heads)
114 122 f = self._callstream("changegroupsubset",
115 123 bases=bases, heads=heads)
116 124 return changegroupmod.unbundle10(self._decompress(f), 'UN')
117 125
118 126 def unbundle(self, cg, heads, source):
119 127 '''Send cg (a readable file-like object representing the
120 128 changegroup to push, typically a chunkbuffer object) to the
121 129 remote server as a bundle. Return an integer indicating the
122 130 result of the push (see localrepository.addchangegroup()).'''
123 131
124 132 ret, output = self._callpush("unbundle", cg, heads=encodelist(heads))
125 133 if ret == "":
126 134 raise error.ResponseError(
127 135 _('push failed:'), output)
128 136 try:
129 137 ret = int(ret)
130 138 except ValueError:
131 139 raise error.ResponseError(
132 140 _('push failed (unexpected response):'), ret)
133 141
134 142 for l in output.splitlines(True):
135 143 self.ui.status(_('remote: '), l)
136 144 return ret
137 145
138 146 def debugwireargs(self, one, two, three=None, four=None):
139 147 # don't pass optional arguments left at their default value
140 148 opts = {}
141 149 if three is not None:
142 150 opts['three'] = three
143 151 if four is not None:
144 152 opts['four'] = four
145 153 return self._call('debugwireargs', one=one, two=two, **opts)
146 154
147 155 # server side
148 156
149 157 class streamres(object):
150 158 def __init__(self, gen):
151 159 self.gen = gen
152 160
153 161 class pushres(object):
154 162 def __init__(self, res):
155 163 self.res = res
156 164
157 165 class pusherr(object):
158 166 def __init__(self, res):
159 167 self.res = res
160 168
161 169 def dispatch(repo, proto, command):
162 170 func, spec = commands[command]
163 171 args = proto.getargs(spec)
164 172 return func(repo, proto, *args)
165 173
166 174 def options(cmd, keys, others):
167 175 opts = {}
168 176 for k in keys:
169 177 if k in others:
170 178 opts[k] = others[k]
171 179 del others[k]
172 180 if others:
173 181 sys.stderr.write("abort: %s got unexpected arguments %s\n"
174 182 % (cmd, ",".join(others)))
175 183 return opts
176 184
177 185 def between(repo, proto, pairs):
178 186 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
179 187 r = []
180 188 for b in repo.between(pairs):
181 189 r.append(encodelist(b) + "\n")
182 190 return "".join(r)
183 191
184 192 def branchmap(repo, proto):
185 193 branchmap = repo.branchmap()
186 194 heads = []
187 195 for branch, nodes in branchmap.iteritems():
188 196 branchname = urllib.quote(encoding.fromlocal(branch))
189 197 branchnodes = encodelist(nodes)
190 198 heads.append('%s %s' % (branchname, branchnodes))
191 199 return '\n'.join(heads)
192 200
193 201 def branches(repo, proto, nodes):
194 202 nodes = decodelist(nodes)
195 203 r = []
196 204 for b in repo.branches(nodes):
197 205 r.append(encodelist(b) + "\n")
198 206 return "".join(r)
199 207
200 208 def capabilities(repo, proto):
201 caps = 'lookup changegroupsubset branchmap pushkey'.split()
209 caps = 'lookup changegroupsubset branchmap pushkey known'.split()
202 210 if _allowstream(repo.ui):
203 211 requiredformats = repo.requirements & repo.supportedformats
204 212 # if our local revlogs are just revlogv1, add 'stream' cap
205 213 if not requiredformats - set(('revlogv1',)):
206 214 caps.append('stream')
207 215 # otherwise, add 'streamreqs' detailing our local revlog format
208 216 else:
209 217 caps.append('streamreqs=%s' % ','.join(requiredformats))
210 218 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
211 219 return ' '.join(caps)
212 220
213 221 def changegroup(repo, proto, roots):
214 222 nodes = decodelist(roots)
215 223 cg = repo.changegroup(nodes, 'serve')
216 224 return streamres(proto.groupchunks(cg))
217 225
218 226 def changegroupsubset(repo, proto, bases, heads):
219 227 bases = decodelist(bases)
220 228 heads = decodelist(heads)
221 229 cg = repo.changegroupsubset(bases, heads, 'serve')
222 230 return streamres(proto.groupchunks(cg))
223 231
224 232 def debugwireargs(repo, proto, one, two, others):
225 233 # only accept optional args from the known set
226 234 opts = options('debugwireargs', ['three', 'four'], others)
227 235 return repo.debugwireargs(one, two, **opts)
228 236
229 237 def heads(repo, proto):
230 238 h = repo.heads()
231 239 return encodelist(h) + "\n"
232 240
233 241 def hello(repo, proto):
234 242 '''the hello command returns a set of lines describing various
235 243 interesting things about the server, in an RFC822-like format.
236 244 Currently the only one defined is "capabilities", which
237 245 consists of a line in the form:
238 246
239 247 capabilities: space separated list of tokens
240 248 '''
241 249 return "capabilities: %s\n" % (capabilities(repo, proto))
242 250
243 251 def listkeys(repo, proto, namespace):
244 252 d = pushkeymod.list(repo, encoding.tolocal(namespace)).items()
245 253 t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
246 254 for k, v in d])
247 255 return t
248 256
249 257 def lookup(repo, proto, key):
250 258 try:
251 259 r = hex(repo.lookup(encoding.tolocal(key)))
252 260 success = 1
253 261 except Exception, inst:
254 262 r = str(inst)
255 263 success = 0
256 264 return "%s %s\n" % (success, r)
257 265
266 def known(repo, proto, nodes):
267 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
268
258 269 def pushkey(repo, proto, namespace, key, old, new):
259 270 # compatibility with pre-1.8 clients which were accidentally
260 271 # sending raw binary nodes rather than utf-8-encoded hex
261 272 if len(new) == 20 and new.encode('string-escape') != new:
262 273 # looks like it could be a binary node
263 274 try:
264 275 u = new.decode('utf-8')
265 276 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
266 277 except UnicodeDecodeError:
267 278 pass # binary, leave unmodified
268 279 else:
269 280 new = encoding.tolocal(new) # normal path
270 281
271 282 r = pushkeymod.push(repo,
272 283 encoding.tolocal(namespace), encoding.tolocal(key),
273 284 encoding.tolocal(old), new)
274 285 return '%s\n' % int(r)
275 286
276 287 def _allowstream(ui):
277 288 return ui.configbool('server', 'uncompressed', True, untrusted=True)
278 289
279 290 def stream(repo, proto):
280 291 '''If the server supports streaming clone, it advertises the "stream"
281 292 capability with a value representing the version and flags of the repo
282 293 it is serving. Client checks to see if it understands the format.
283 294
284 295 The format is simple: the server writes out a line with the amount
285 296 of files, then the total amount of bytes to be transfered (separated
286 297 by a space). Then, for each file, the server first writes the filename
287 298 and filesize (separated by the null character), then the file contents.
288 299 '''
289 300
290 301 if not _allowstream(repo.ui):
291 302 return '1\n'
292 303
293 304 entries = []
294 305 total_bytes = 0
295 306 try:
296 307 # get consistent snapshot of repo, lock during scan
297 308 lock = repo.lock()
298 309 try:
299 310 repo.ui.debug('scanning\n')
300 311 for name, ename, size in repo.store.walk():
301 312 entries.append((name, size))
302 313 total_bytes += size
303 314 finally:
304 315 lock.release()
305 316 except error.LockError:
306 317 return '2\n' # error: 2
307 318
308 319 def streamer(repo, entries, total):
309 320 '''stream out all metadata files in repository.'''
310 321 yield '0\n' # success
311 322 repo.ui.debug('%d files, %d bytes to transfer\n' %
312 323 (len(entries), total_bytes))
313 324 yield '%d %d\n' % (len(entries), total_bytes)
314 325 for name, size in entries:
315 326 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
316 327 # partially encode name over the wire for backwards compat
317 328 yield '%s\0%d\n' % (store.encodedir(name), size)
318 329 for chunk in util.filechunkiter(repo.sopener(name), limit=size):
319 330 yield chunk
320 331
321 332 return streamres(streamer(repo, entries, total_bytes))
322 333
323 334 def unbundle(repo, proto, heads):
324 335 their_heads = decodelist(heads)
325 336
326 337 def check_heads():
327 338 heads = repo.heads()
328 339 return their_heads == ['force'] or their_heads == heads
329 340
330 341 proto.redirect()
331 342
332 343 # fail early if possible
333 344 if not check_heads():
334 345 return pusherr('unsynced changes')
335 346
336 347 # write bundle data to temporary file because it can be big
337 348 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
338 349 fp = os.fdopen(fd, 'wb+')
339 350 r = 0
340 351 try:
341 352 proto.getfile(fp)
342 353 lock = repo.lock()
343 354 try:
344 355 if not check_heads():
345 356 # someone else committed/pushed/unbundled while we
346 357 # were transferring data
347 358 return pusherr('unsynced changes')
348 359
349 360 # push can proceed
350 361 fp.seek(0)
351 362 gen = changegroupmod.readbundle(fp, None)
352 363
353 364 try:
354 365 r = repo.addchangegroup(gen, 'serve', proto._client(),
355 366 lock=lock)
356 367 except util.Abort, inst:
357 368 sys.stderr.write("abort: %s\n" % inst)
358 369 finally:
359 370 lock.release()
360 371 return pushres(r)
361 372
362 373 finally:
363 374 fp.close()
364 375 os.unlink(tempname)
365 376
366 377 commands = {
367 378 'between': (between, 'pairs'),
368 379 'branchmap': (branchmap, ''),
369 380 'branches': (branches, 'nodes'),
370 381 'capabilities': (capabilities, ''),
371 382 'changegroup': (changegroup, 'roots'),
372 383 'changegroupsubset': (changegroupsubset, 'bases heads'),
373 384 'debugwireargs': (debugwireargs, 'one two *'),
374 385 'heads': (heads, ''),
375 386 'hello': (hello, ''),
387 'known': (known, 'nodes'),
376 388 'listkeys': (listkeys, 'namespace'),
377 389 'lookup': (lookup, 'key'),
378 390 'pushkey': (pushkey, 'namespace key old new'),
379 391 'stream_out': (stream, ''),
380 392 'unbundle': (unbundle, 'heads'),
381 393 }
@@ -1,255 +1,257 b''
1 1 Show all commands except debug commands
2 2 $ hg debugcomplete
3 3 add
4 4 addremove
5 5 annotate
6 6 archive
7 7 backout
8 8 bisect
9 9 bookmarks
10 10 branch
11 11 branches
12 12 bundle
13 13 cat
14 14 clone
15 15 commit
16 16 copy
17 17 diff
18 18 export
19 19 forget
20 20 grep
21 21 heads
22 22 help
23 23 identify
24 24 import
25 25 incoming
26 26 init
27 27 locate
28 28 log
29 29 manifest
30 30 merge
31 31 outgoing
32 32 parents
33 33 paths
34 34 pull
35 35 push
36 36 recover
37 37 remove
38 38 rename
39 39 resolve
40 40 revert
41 41 rollback
42 42 root
43 43 serve
44 44 showconfig
45 45 status
46 46 summary
47 47 tag
48 48 tags
49 49 tip
50 50 unbundle
51 51 update
52 52 verify
53 53 version
54 54
55 55 Show all commands that start with "a"
56 56 $ hg debugcomplete a
57 57 add
58 58 addremove
59 59 annotate
60 60 archive
61 61
62 62 Do not show debug commands if there are other candidates
63 63 $ hg debugcomplete d
64 64 diff
65 65
66 66 Show debug commands if there are no other candidates
67 67 $ hg debugcomplete debug
68 68 debugancestor
69 69 debugbuilddag
70 70 debugcheckstate
71 71 debugcommands
72 72 debugcomplete
73 73 debugconfig
74 74 debugdag
75 75 debugdata
76 76 debugdate
77 77 debugfsinfo
78 78 debugignore
79 79 debugindex
80 80 debugindexdot
81 81 debuginstall
82 debugknown
82 83 debugpushkey
83 84 debugrebuildstate
84 85 debugrename
85 86 debugrevspec
86 87 debugsetparents
87 88 debugstate
88 89 debugsub
89 90 debugwalk
90 91 debugwireargs
91 92
92 93 Do not show the alias of a debug command if there are other candidates
93 94 (this should hide rawcommit)
94 95 $ hg debugcomplete r
95 96 recover
96 97 remove
97 98 rename
98 99 resolve
99 100 revert
100 101 rollback
101 102 root
102 103 Show the alias of a debug command if there are no other candidates
103 104 $ hg debugcomplete rawc
104 105
105 106
106 107 Show the global options
107 108 $ hg debugcomplete --options | sort
108 109 --config
109 110 --cwd
110 111 --debug
111 112 --debugger
112 113 --encoding
113 114 --encodingmode
114 115 --help
115 116 --noninteractive
116 117 --profile
117 118 --quiet
118 119 --repository
119 120 --time
120 121 --traceback
121 122 --verbose
122 123 --version
123 124 -R
124 125 -h
125 126 -q
126 127 -v
127 128 -y
128 129
129 130 Show the options for the "serve" command
130 131 $ hg debugcomplete --options serve | sort
131 132 --accesslog
132 133 --address
133 134 --certificate
134 135 --config
135 136 --cwd
136 137 --daemon
137 138 --daemon-pipefds
138 139 --debug
139 140 --debugger
140 141 --encoding
141 142 --encodingmode
142 143 --errorlog
143 144 --help
144 145 --ipv6
145 146 --name
146 147 --noninteractive
147 148 --pid-file
148 149 --port
149 150 --prefix
150 151 --profile
151 152 --quiet
152 153 --repository
153 154 --stdio
154 155 --style
155 156 --templates
156 157 --time
157 158 --traceback
158 159 --verbose
159 160 --version
160 161 --web-conf
161 162 -6
162 163 -A
163 164 -E
164 165 -R
165 166 -a
166 167 -d
167 168 -h
168 169 -n
169 170 -p
170 171 -q
171 172 -t
172 173 -v
173 174 -y
174 175
175 176 Show an error if we use --options with an ambiguous abbreviation
176 177 $ hg debugcomplete --options s
177 178 hg: command 's' is ambiguous:
178 179 serve showconfig status summary
179 180 [255]
180 181
181 182 Show all commands + options
182 183 $ hg debugcommands
183 184 add: include, exclude, subrepos, dry-run
184 185 annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, include, exclude
185 186 clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd, insecure
186 187 commit: addremove, close-branch, include, exclude, message, logfile, date, user
187 188 diff: rev, change, text, git, nodates, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, include, exclude, subrepos
188 189 export: output, switch-parent, rev, text, git, nodates
189 190 forget: include, exclude
190 191 init: ssh, remotecmd, insecure
191 192 log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, style, template, include, exclude
192 193 merge: force, tool, rev, preview
193 194 pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
194 195 push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
195 196 remove: after, force, include, exclude
196 197 serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, templates, style, ipv6, certificate
197 198 status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos
198 199 summary: remote
199 200 update: clean, check, date, rev
200 201 addremove: similarity, include, exclude, dry-run
201 202 archive: no-decode, prefix, rev, type, subrepos, include, exclude
202 203 backout: merge, parent, tool, rev, include, exclude, message, logfile, date, user
203 204 bisect: reset, good, bad, skip, extend, command, noupdate
204 205 bookmarks: force, rev, delete, rename
205 206 branch: force, clean
206 207 branches: active, closed
207 208 bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure
208 209 cat: output, rev, decode, include, exclude
209 210 copy: after, force, include, exclude, dry-run
210 211 debugancestor:
211 212 debugbuilddag: mergeable-file, appended-file, overwritten-file, new-file
212 213 debugcheckstate:
213 214 debugcommands:
214 215 debugcomplete: options
215 216 debugdag: tags, branches, dots, spaces
216 217 debugdata:
217 218 debugdate: extended
218 219 debugfsinfo:
219 220 debugignore:
220 221 debugindex: format
221 222 debugindexdot:
222 223 debuginstall:
224 debugknown:
223 225 debugpushkey:
224 226 debugrebuildstate: rev
225 227 debugrename: rev
226 228 debugrevspec:
227 229 debugsetparents:
228 230 debugstate: nodates
229 231 debugsub: rev
230 232 debugwalk: include, exclude
231 233 debugwireargs: three, four, ssh, remotecmd, insecure
232 234 grep: print0, all, follow, ignore-case, files-with-matches, line-number, rev, user, date, include, exclude
233 235 heads: rev, topo, active, closed, style, template
234 236 help:
235 237 identify: rev, num, id, branch, tags, bookmarks
236 238 import: strip, base, force, no-commit, exact, import-branch, message, logfile, date, user, similarity
237 239 incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, style, template, ssh, remotecmd, insecure, subrepos
238 240 locate: rev, print0, fullpath, include, exclude
239 241 manifest: rev
240 242 outgoing: force, rev, newest-first, bookmarks, branch, patch, git, limit, no-merges, stat, style, template, ssh, remotecmd, insecure, subrepos
241 243 parents: rev, style, template
242 244 paths:
243 245 recover:
244 246 rename: after, force, include, exclude, dry-run
245 247 resolve: all, list, mark, unmark, tool, no-status, include, exclude
246 248 revert: all, date, rev, no-backup, include, exclude, dry-run
247 249 rollback: dry-run
248 250 root:
249 251 showconfig: untrusted
250 252 tag: force, local, rev, remove, edit, message, date, user
251 253 tags:
252 254 tip: patch, git, style, template
253 255 unbundle: update
254 256 verify:
255 257 version:
@@ -1,1081 +1,1081 b''
1 1 An attempt at more fully testing the hgweb web interface.
2 2 The following things are tested elsewhere and are therefore omitted:
3 3 - archive, tested in test-archive
4 4 - unbundle, tested in test-push-http
5 5 - changegroupsubset, tested in test-pull
6 6
7 7 Set up the repo
8 8
9 9 $ hg init test
10 10 $ cd test
11 11 $ mkdir da
12 12 $ echo foo > da/foo
13 13 $ echo foo > foo
14 14 $ hg ci -Ambase
15 15 adding da/foo
16 16 adding foo
17 17 $ hg tag 1.0
18 18 $ hg bookmark something
19 19 $ echo another > foo
20 20 $ hg branch stable
21 21 marked working directory as branch stable
22 22 $ hg ci -Ambranch
23 23 $ hg serve --config server.uncompressed=False -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
24 24 $ cat hg.pid >> $DAEMON_PIDS
25 25
26 26 Logs and changes
27 27
28 28 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/?style=atom'
29 29 200 Script output follows
30 30
31 31 <?xml version="1.0" encoding="ascii"?>
32 32 <feed xmlns="http://www.w3.org/2005/Atom">
33 33 <!-- Changelog -->
34 34 <id>http://*:$HGPORT/</id> (glob)
35 35 <link rel="self" href="http://*:$HGPORT/atom-log"/> (glob)
36 36 <link rel="alternate" href="http://*:$HGPORT/"/> (glob)
37 37 <title>test Changelog</title>
38 38 <updated>1970-01-01T00:00:00+00:00</updated>
39 39
40 40 <entry>
41 41 <title>branch</title>
42 42 <id>http://*:$HGPORT/#changeset-1d22e65f027e5a0609357e7d8e7508cd2ba5d2fe</id> (glob)
43 43 <link href="http://*:$HGPORT/rev/1d22e65f027e"/> (glob)
44 44 <author>
45 45 <name>test</name>
46 46 <email>&#116;&#101;&#115;&#116;</email>
47 47 </author>
48 48 <updated>1970-01-01T00:00:00+00:00</updated>
49 49 <published>1970-01-01T00:00:00+00:00</published>
50 50 <content type="xhtml">
51 51 <div xmlns="http://www.w3.org/1999/xhtml">
52 52 <pre xml:space="preserve">branch</pre>
53 53 </div>
54 54 </content>
55 55 </entry>
56 56 <entry>
57 57 <title>Added tag 1.0 for changeset 2ef0ac749a14</title>
58 58 <id>http://*:$HGPORT/#changeset-a4f92ed23982be056b9852de5dfe873eaac7f0de</id> (glob)
59 59 <link href="http://*:$HGPORT/rev/a4f92ed23982"/> (glob)
60 60 <author>
61 61 <name>test</name>
62 62 <email>&#116;&#101;&#115;&#116;</email>
63 63 </author>
64 64 <updated>1970-01-01T00:00:00+00:00</updated>
65 65 <published>1970-01-01T00:00:00+00:00</published>
66 66 <content type="xhtml">
67 67 <div xmlns="http://www.w3.org/1999/xhtml">
68 68 <pre xml:space="preserve">Added tag 1.0 for changeset 2ef0ac749a14</pre>
69 69 </div>
70 70 </content>
71 71 </entry>
72 72 <entry>
73 73 <title>base</title>
74 74 <id>http://*:$HGPORT/#changeset-2ef0ac749a14e4f57a5a822464a0902c6f7f448f</id> (glob)
75 75 <link href="http://*:$HGPORT/rev/2ef0ac749a14"/> (glob)
76 76 <author>
77 77 <name>test</name>
78 78 <email>&#116;&#101;&#115;&#116;</email>
79 79 </author>
80 80 <updated>1970-01-01T00:00:00+00:00</updated>
81 81 <published>1970-01-01T00:00:00+00:00</published>
82 82 <content type="xhtml">
83 83 <div xmlns="http://www.w3.org/1999/xhtml">
84 84 <pre xml:space="preserve">base</pre>
85 85 </div>
86 86 </content>
87 87 </entry>
88 88
89 89 </feed>
90 90 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/?style=atom'
91 91 200 Script output follows
92 92
93 93 <?xml version="1.0" encoding="ascii"?>
94 94 <feed xmlns="http://www.w3.org/2005/Atom">
95 95 <!-- Changelog -->
96 96 <id>http://*:$HGPORT/</id> (glob)
97 97 <link rel="self" href="http://*:$HGPORT/atom-log"/> (glob)
98 98 <link rel="alternate" href="http://*:$HGPORT/"/> (glob)
99 99 <title>test Changelog</title>
100 100 <updated>1970-01-01T00:00:00+00:00</updated>
101 101
102 102 <entry>
103 103 <title>branch</title>
104 104 <id>http://*:$HGPORT/#changeset-1d22e65f027e5a0609357e7d8e7508cd2ba5d2fe</id> (glob)
105 105 <link href="http://*:$HGPORT/rev/1d22e65f027e"/> (glob)
106 106 <author>
107 107 <name>test</name>
108 108 <email>&#116;&#101;&#115;&#116;</email>
109 109 </author>
110 110 <updated>1970-01-01T00:00:00+00:00</updated>
111 111 <published>1970-01-01T00:00:00+00:00</published>
112 112 <content type="xhtml">
113 113 <div xmlns="http://www.w3.org/1999/xhtml">
114 114 <pre xml:space="preserve">branch</pre>
115 115 </div>
116 116 </content>
117 117 </entry>
118 118 <entry>
119 119 <title>Added tag 1.0 for changeset 2ef0ac749a14</title>
120 120 <id>http://*:$HGPORT/#changeset-a4f92ed23982be056b9852de5dfe873eaac7f0de</id> (glob)
121 121 <link href="http://*:$HGPORT/rev/a4f92ed23982"/> (glob)
122 122 <author>
123 123 <name>test</name>
124 124 <email>&#116;&#101;&#115;&#116;</email>
125 125 </author>
126 126 <updated>1970-01-01T00:00:00+00:00</updated>
127 127 <published>1970-01-01T00:00:00+00:00</published>
128 128 <content type="xhtml">
129 129 <div xmlns="http://www.w3.org/1999/xhtml">
130 130 <pre xml:space="preserve">Added tag 1.0 for changeset 2ef0ac749a14</pre>
131 131 </div>
132 132 </content>
133 133 </entry>
134 134 <entry>
135 135 <title>base</title>
136 136 <id>http://*:$HGPORT/#changeset-2ef0ac749a14e4f57a5a822464a0902c6f7f448f</id> (glob)
137 137 <link href="http://*:$HGPORT/rev/2ef0ac749a14"/> (glob)
138 138 <author>
139 139 <name>test</name>
140 140 <email>&#116;&#101;&#115;&#116;</email>
141 141 </author>
142 142 <updated>1970-01-01T00:00:00+00:00</updated>
143 143 <published>1970-01-01T00:00:00+00:00</published>
144 144 <content type="xhtml">
145 145 <div xmlns="http://www.w3.org/1999/xhtml">
146 146 <pre xml:space="preserve">base</pre>
147 147 </div>
148 148 </content>
149 149 </entry>
150 150
151 151 </feed>
152 152 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/foo/?style=atom'
153 153 200 Script output follows
154 154
155 155 <?xml version="1.0" encoding="ascii"?>
156 156 <feed xmlns="http://www.w3.org/2005/Atom">
157 157 <id>http://*:$HGPORT/atom-log/tip/foo</id> (glob)
158 158 <link rel="self" href="http://*:$HGPORT/atom-log/tip/foo"/> (glob)
159 159 <title>test: foo history</title>
160 160 <updated>1970-01-01T00:00:00+00:00</updated>
161 161
162 162 <entry>
163 163 <title>base</title>
164 164 <id>http://*:$HGPORT/#changeset-2ef0ac749a14e4f57a5a822464a0902c6f7f448f</id> (glob)
165 165 <link href="http://*:$HGPORT/rev/2ef0ac749a14"/> (glob)
166 166 <author>
167 167 <name>test</name>
168 168 <email>&#116;&#101;&#115;&#116;</email>
169 169 </author>
170 170 <updated>1970-01-01T00:00:00+00:00</updated>
171 171 <published>1970-01-01T00:00:00+00:00</published>
172 172 <content type="xhtml">
173 173 <div xmlns="http://www.w3.org/1999/xhtml">
174 174 <pre xml:space="preserve">base</pre>
175 175 </div>
176 176 </content>
177 177 </entry>
178 178
179 179 </feed>
180 180 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/shortlog/'
181 181 200 Script output follows
182 182
183 183 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
184 184 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
185 185 <head>
186 186 <link rel="icon" href="/static/hgicon.png" type="image/png" />
187 187 <meta name="robots" content="index, nofollow" />
188 188 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
189 189
190 190 <title>test: log</title>
191 191 <link rel="alternate" type="application/atom+xml"
192 192 href="/atom-log" title="Atom feed for test" />
193 193 <link rel="alternate" type="application/rss+xml"
194 194 href="/rss-log" title="RSS feed for test" />
195 195 </head>
196 196 <body>
197 197
198 198 <div class="container">
199 199 <div class="menu">
200 200 <div class="logo">
201 201 <a href="http://mercurial.selenic.com/">
202 202 <img src="/static/hglogo.png" alt="mercurial" /></a>
203 203 </div>
204 204 <ul>
205 205 <li class="active">log</li>
206 206 <li><a href="/graph/1d22e65f027e">graph</a></li>
207 207 <li><a href="/tags">tags</a></li>
208 208 <li><a href="/bookmarks">bookmarks</a></li>
209 209 <li><a href="/branches">branches</a></li>
210 210 </ul>
211 211 <ul>
212 212 <li><a href="/rev/1d22e65f027e">changeset</a></li>
213 213 <li><a href="/file/1d22e65f027e">browse</a></li>
214 214 </ul>
215 215 <ul>
216 216
217 217 </ul>
218 218 <ul>
219 219 <li><a href="/help">help</a></li>
220 220 </ul>
221 221 </div>
222 222
223 223 <div class="main">
224 224 <h2><a href="/">test</a></h2>
225 225 <h3>log</h3>
226 226
227 227 <form class="search" action="/log">
228 228
229 229 <p><input name="rev" id="search1" type="text" size="30" /></p>
230 230 <div id="hint">find changesets by author, revision,
231 231 files, or words in the commit message</div>
232 232 </form>
233 233
234 234 <div class="navigate">
235 235 <a href="/shortlog/2?revcount=30">less</a>
236 236 <a href="/shortlog/2?revcount=120">more</a>
237 237 | rev 2: <a href="/shortlog/2ef0ac749a14">(0)</a> <a href="/shortlog/tip">tip</a>
238 238 </div>
239 239
240 240 <table class="bigtable">
241 241 <tr>
242 242 <th class="age">age</th>
243 243 <th class="author">author</th>
244 244 <th class="description">description</th>
245 245 </tr>
246 246 <tr class="parity0">
247 247 <td class="age">1970-01-01</td>
248 248 <td class="author">test</td>
249 249 <td class="description"><a href="/rev/1d22e65f027e">branch</a><span class="branchhead">stable</span> <span class="tag">tip</span> <span class="tag">something</span> </td>
250 250 </tr>
251 251 <tr class="parity1">
252 252 <td class="age">1970-01-01</td>
253 253 <td class="author">test</td>
254 254 <td class="description"><a href="/rev/a4f92ed23982">Added tag 1.0 for changeset 2ef0ac749a14</a><span class="branchhead">default</span> </td>
255 255 </tr>
256 256 <tr class="parity0">
257 257 <td class="age">1970-01-01</td>
258 258 <td class="author">test</td>
259 259 <td class="description"><a href="/rev/2ef0ac749a14">base</a><span class="tag">1.0</span> </td>
260 260 </tr>
261 261
262 262 </table>
263 263
264 264 <div class="navigate">
265 265 <a href="/shortlog/2?revcount=30">less</a>
266 266 <a href="/shortlog/2?revcount=120">more</a>
267 267 | rev 2: <a href="/shortlog/2ef0ac749a14">(0)</a> <a href="/shortlog/tip">tip</a>
268 268 </div>
269 269
270 270 </div>
271 271 </div>
272 272
273 273
274 274
275 275 </body>
276 276 </html>
277 277
278 278 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/rev/0/'
279 279 200 Script output follows
280 280
281 281 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
282 282 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
283 283 <head>
284 284 <link rel="icon" href="/static/hgicon.png" type="image/png" />
285 285 <meta name="robots" content="index, nofollow" />
286 286 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
287 287
288 288 <title>test: 2ef0ac749a14</title>
289 289 </head>
290 290 <body>
291 291 <div class="container">
292 292 <div class="menu">
293 293 <div class="logo">
294 294 <a href="http://mercurial.selenic.com/">
295 295 <img src="/static/hglogo.png" alt="mercurial" /></a>
296 296 </div>
297 297 <ul>
298 298 <li><a href="/shortlog/2ef0ac749a14">log</a></li>
299 299 <li><a href="/graph/2ef0ac749a14">graph</a></li>
300 300 <li><a href="/tags">tags</a></li>
301 301 <li><a href="/bookmarks">bookmarks</a></li>
302 302 <li><a href="/branches">branches</a></li>
303 303 </ul>
304 304 <ul>
305 305 <li class="active">changeset</li>
306 306 <li><a href="/raw-rev/2ef0ac749a14">raw</a></li>
307 307 <li><a href="/file/2ef0ac749a14">browse</a></li>
308 308 </ul>
309 309 <ul>
310 310
311 311 </ul>
312 312 <ul>
313 313 <li><a href="/help">help</a></li>
314 314 </ul>
315 315 </div>
316 316
317 317 <div class="main">
318 318
319 319 <h2><a href="/">test</a></h2>
320 320 <h3>changeset 0:2ef0ac749a14 <span class="tag">1.0</span> </h3>
321 321
322 322 <form class="search" action="/log">
323 323
324 324 <p><input name="rev" id="search1" type="text" size="30" /></p>
325 325 <div id="hint">find changesets by author, revision,
326 326 files, or words in the commit message</div>
327 327 </form>
328 328
329 329 <div class="description">base</div>
330 330
331 331 <table id="changesetEntry">
332 332 <tr>
333 333 <th class="author">author</th>
334 334 <td class="author">&#116;&#101;&#115;&#116;</td>
335 335 </tr>
336 336 <tr>
337 337 <th class="date">date</th>
338 338 <td class="date">Thu Jan 01 00:00:00 1970 +0000 (1970-01-01)</td></tr>
339 339 <tr>
340 340 <th class="author">parents</th>
341 341 <td class="author"></td>
342 342 </tr>
343 343 <tr>
344 344 <th class="author">children</th>
345 345 <td class="author"> <a href="/rev/a4f92ed23982">a4f92ed23982</a></td>
346 346 </tr>
347 347 <tr>
348 348 <th class="files">files</th>
349 349 <td class="files"><a href="/file/2ef0ac749a14/da/foo">da/foo</a> <a href="/file/2ef0ac749a14/foo">foo</a> </td>
350 350 </tr>
351 351 </table>
352 352
353 353 <div class="overflow">
354 354 <div class="sourcefirst"> line diff</div>
355 355
356 356 <div class="source bottomline parity0"><pre><a href="#l1.1" id="l1.1"> 1.1</a> <span class="minusline">--- /dev/null Thu Jan 01 00:00:00 1970 +0000
357 357 </span><a href="#l1.2" id="l1.2"> 1.2</a> <span class="plusline">+++ b/da/foo Thu Jan 01 00:00:00 1970 +0000
358 358 </span><a href="#l1.3" id="l1.3"> 1.3</a> <span class="atline">@@ -0,0 +1,1 @@
359 359 </span><a href="#l1.4" id="l1.4"> 1.4</a> <span class="plusline">+foo
360 360 </span></pre></div><div class="source bottomline parity1"><pre><a href="#l2.1" id="l2.1"> 2.1</a> <span class="minusline">--- /dev/null Thu Jan 01 00:00:00 1970 +0000
361 361 </span><a href="#l2.2" id="l2.2"> 2.2</a> <span class="plusline">+++ b/foo Thu Jan 01 00:00:00 1970 +0000
362 362 </span><a href="#l2.3" id="l2.3"> 2.3</a> <span class="atline">@@ -0,0 +1,1 @@
363 363 </span><a href="#l2.4" id="l2.4"> 2.4</a> <span class="plusline">+foo
364 364 </span></pre></div>
365 365 </div>
366 366
367 367 </div>
368 368 </div>
369 369
370 370
371 371 </body>
372 372 </html>
373 373
374 374 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/rev/1/?style=raw'
375 375 200 Script output follows
376 376
377 377
378 378 # HG changeset patch
379 379 # User test
380 380 # Date 0 0
381 381 # Node ID a4f92ed23982be056b9852de5dfe873eaac7f0de
382 382 # Parent 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
383 383 Added tag 1.0 for changeset 2ef0ac749a14
384 384
385 385 diff -r 2ef0ac749a14 -r a4f92ed23982 .hgtags
386 386 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
387 387 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
388 388 @@ -0,0 +1,1 @@
389 389 +2ef0ac749a14e4f57a5a822464a0902c6f7f448f 1.0
390 390
391 391 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log?rev=base'
392 392 200 Script output follows
393 393
394 394 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
395 395 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
396 396 <head>
397 397 <link rel="icon" href="/static/hgicon.png" type="image/png" />
398 398 <meta name="robots" content="index, nofollow" />
399 399 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
400 400
401 401 <title>test: searching for base</title>
402 402 </head>
403 403 <body>
404 404
405 405 <div class="container">
406 406 <div class="menu">
407 407 <div class="logo">
408 408 <a href="http://mercurial.selenic.com/">
409 409 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
410 410 </div>
411 411 <ul>
412 412 <li><a href="/shortlog">log</a></li>
413 413 <li><a href="/graph">graph</a></li>
414 414 <li><a href="/tags">tags</a></li>
415 415 <li><a href="/bookmarks">bookmarks</a></li>
416 416 <li><a href="/branches">branches</a></li>
417 417 <li><a href="/help">help</a></li>
418 418 </ul>
419 419 </div>
420 420
421 421 <div class="main">
422 422 <h2><a href="/">test</a></h2>
423 423 <h3>searching for 'base'</h3>
424 424
425 425 <form class="search" action="/log">
426 426
427 427 <p><input name="rev" id="search1" type="text" size="30"></p>
428 428 <div id="hint">find changesets by author, revision,
429 429 files, or words in the commit message</div>
430 430 </form>
431 431
432 432 <div class="navigate">
433 433 <a href="/search/?rev=base&revcount=5">less</a>
434 434 <a href="/search/?rev=base&revcount=20">more</a>
435 435 </div>
436 436
437 437 <table class="bigtable">
438 438 <tr>
439 439 <th class="age">age</th>
440 440 <th class="author">author</th>
441 441 <th class="description">description</th>
442 442 </tr>
443 443 <tr class="parity0">
444 444 <td class="age">1970-01-01</td>
445 445 <td class="author">test</td>
446 446 <td class="description"><a href="/rev/2ef0ac749a14">base</a><span class="tag">1.0</span> </td>
447 447 </tr>
448 448
449 449 </table>
450 450
451 451 <div class="navigate">
452 452 <a href="/search/?rev=base&revcount=5">less</a>
453 453 <a href="/search/?rev=base&revcount=20">more</a>
454 454 </div>
455 455
456 456 </div>
457 457 </div>
458 458
459 459
460 460
461 461 </body>
462 462 </html>
463 463
464 464
465 465 File-related
466 466
467 467 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo/?style=raw'
468 468 200 Script output follows
469 469
470 470 foo
471 471 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/annotate/1/foo/?style=raw'
472 472 200 Script output follows
473 473
474 474
475 475 test@0: foo
476 476
477 477
478 478
479 479
480 480 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/?style=raw'
481 481 200 Script output follows
482 482
483 483
484 484 drwxr-xr-x da
485 485 -rw-r--r-- 45 .hgtags
486 486 -rw-r--r-- 4 foo
487 487
488 488
489 489 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo'
490 490 200 Script output follows
491 491
492 492 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
493 493 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
494 494 <head>
495 495 <link rel="icon" href="/static/hgicon.png" type="image/png" />
496 496 <meta name="robots" content="index, nofollow" />
497 497 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
498 498
499 499 <title>test: a4f92ed23982 foo</title>
500 500 </head>
501 501 <body>
502 502
503 503 <div class="container">
504 504 <div class="menu">
505 505 <div class="logo">
506 506 <a href="http://mercurial.selenic.com/">
507 507 <img src="/static/hglogo.png" alt="mercurial" /></a>
508 508 </div>
509 509 <ul>
510 510 <li><a href="/shortlog/a4f92ed23982">log</a></li>
511 511 <li><a href="/graph/a4f92ed23982">graph</a></li>
512 512 <li><a href="/tags">tags</a></li>
513 513 <li><a href="/branches">branches</a></li>
514 514 </ul>
515 515 <ul>
516 516 <li><a href="/rev/a4f92ed23982">changeset</a></li>
517 517 <li><a href="/file/a4f92ed23982/">browse</a></li>
518 518 </ul>
519 519 <ul>
520 520 <li class="active">file</li>
521 521 <li><a href="/file/tip/foo">latest</a></li>
522 522 <li><a href="/diff/a4f92ed23982/foo">diff</a></li>
523 523 <li><a href="/annotate/a4f92ed23982/foo">annotate</a></li>
524 524 <li><a href="/log/a4f92ed23982/foo">file log</a></li>
525 525 <li><a href="/raw-file/a4f92ed23982/foo">raw</a></li>
526 526 </ul>
527 527 <ul>
528 528 <li><a href="/help">help</a></li>
529 529 </ul>
530 530 </div>
531 531
532 532 <div class="main">
533 533 <h2><a href="/">test</a></h2>
534 534 <h3>view foo @ 1:a4f92ed23982</h3>
535 535
536 536 <form class="search" action="/log">
537 537
538 538 <p><input name="rev" id="search1" type="text" size="30" /></p>
539 539 <div id="hint">find changesets by author, revision,
540 540 files, or words in the commit message</div>
541 541 </form>
542 542
543 543 <div class="description">Added tag 1.0 for changeset 2ef0ac749a14</div>
544 544
545 545 <table id="changesetEntry">
546 546 <tr>
547 547 <th class="author">author</th>
548 548 <td class="author">&#116;&#101;&#115;&#116;</td>
549 549 </tr>
550 550 <tr>
551 551 <th class="date">date</th>
552 552 <td class="date">Thu Jan 01 00:00:00 1970 +0000 (1970-01-01)</td>
553 553 </tr>
554 554 <tr>
555 555 <th class="author">parents</th>
556 556 <td class="author"></td>
557 557 </tr>
558 558 <tr>
559 559 <th class="author">children</th>
560 560 <td class="author"><a href="/file/1d22e65f027e/foo">1d22e65f027e</a> </td>
561 561 </tr>
562 562
563 563 </table>
564 564
565 565 <div class="overflow">
566 566 <div class="sourcefirst"> line source</div>
567 567
568 568 <div class="parity0 source"><a href="#l1" id="l1"> 1</a> foo
569 569 </div>
570 570 <div class="sourcelast"></div>
571 571 </div>
572 572 </div>
573 573 </div>
574 574
575 575
576 576
577 577 </body>
578 578 </html>
579 579
580 580 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/filediff/1/foo/?style=raw'
581 581 200 Script output follows
582 582
583 583
584 584 diff -r 000000000000 -r a4f92ed23982 foo
585 585 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
586 586 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
587 587 @@ -0,0 +1,1 @@
588 588 +foo
589 589
590 590
591 591
592 592
593 593
594 594 Overviews
595 595
596 596 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/raw-tags'
597 597 200 Script output follows
598 598
599 599 tip 1d22e65f027e5a0609357e7d8e7508cd2ba5d2fe
600 600 1.0 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
601 601 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/raw-branches'
602 602 200 Script output follows
603 603
604 604 stable 1d22e65f027e5a0609357e7d8e7508cd2ba5d2fe open
605 605 default a4f92ed23982be056b9852de5dfe873eaac7f0de inactive
606 606 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/summary/?style=gitweb'
607 607 200 Script output follows
608 608
609 609 <?xml version="1.0" encoding="ascii"?>
610 610 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
611 611 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
612 612 <head>
613 613 <link rel="icon" href="/static/hgicon.png" type="image/png" />
614 614 <meta name="robots" content="index, nofollow"/>
615 615 <link rel="stylesheet" href="/static/style-gitweb.css" type="text/css" />
616 616
617 617
618 618 <title>test: Summary</title>
619 619 <link rel="alternate" type="application/atom+xml"
620 620 href="/atom-log" title="Atom feed for test"/>
621 621 <link rel="alternate" type="application/rss+xml"
622 622 href="/rss-log" title="RSS feed for test"/>
623 623 </head>
624 624 <body>
625 625
626 626 <div class="page_header">
627 627 <a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="/summary?style=gitweb">test</a> / summary
628 628
629 629 <form action="/log">
630 630 <input type="hidden" name="style" value="gitweb" />
631 631 <div class="search">
632 632 <input type="text" name="rev" />
633 633 </div>
634 634 </form>
635 635 </div>
636 636
637 637 <div class="page_nav">
638 638 summary |
639 639 <a href="/shortlog?style=gitweb">shortlog</a> |
640 640 <a href="/log?style=gitweb">changelog</a> |
641 641 <a href="/graph?style=gitweb">graph</a> |
642 642 <a href="/tags?style=gitweb">tags</a> |
643 643 <a href="/branches?style=gitweb">branches</a> |
644 644 <a href="/file/1d22e65f027e?style=gitweb">files</a> |
645 645 <a href="/help?style=gitweb">help</a>
646 646 <br/>
647 647 </div>
648 648
649 649 <div class="title">&nbsp;</div>
650 650 <table cellspacing="0">
651 651 <tr><td>description</td><td>unknown</td></tr>
652 652 <tr><td>owner</td><td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td></tr>
653 653 <tr><td>last change</td><td>Thu, 01 Jan 1970 00:00:00 +0000</td></tr>
654 654 </table>
655 655
656 656 <div><a class="title" href="/shortlog?style=gitweb">changes</a></div>
657 657 <table cellspacing="0">
658 658
659 659 <tr class="parity0">
660 660 <td class="age"><i>1970-01-01</i></td>
661 661 <td><i>test</i></td>
662 662 <td>
663 663 <a class="list" href="/rev/1d22e65f027e?style=gitweb">
664 664 <b>branch</b>
665 665 <span class="logtags"><span class="branchtag" title="stable">stable</span> <span class="tagtag" title="tip">tip</span> </span>
666 666 </a>
667 667 </td>
668 668 <td class="link" nowrap>
669 669 <a href="/rev/1d22e65f027e?style=gitweb">changeset</a> |
670 670 <a href="/file/1d22e65f027e?style=gitweb">files</a>
671 671 </td>
672 672 </tr>
673 673 <tr class="parity1">
674 674 <td class="age"><i>1970-01-01</i></td>
675 675 <td><i>test</i></td>
676 676 <td>
677 677 <a class="list" href="/rev/a4f92ed23982?style=gitweb">
678 678 <b>Added tag 1.0 for changeset 2ef0ac749a14</b>
679 679 <span class="logtags"><span class="branchtag" title="default">default</span> </span>
680 680 </a>
681 681 </td>
682 682 <td class="link" nowrap>
683 683 <a href="/rev/a4f92ed23982?style=gitweb">changeset</a> |
684 684 <a href="/file/a4f92ed23982?style=gitweb">files</a>
685 685 </td>
686 686 </tr>
687 687 <tr class="parity0">
688 688 <td class="age"><i>1970-01-01</i></td>
689 689 <td><i>test</i></td>
690 690 <td>
691 691 <a class="list" href="/rev/2ef0ac749a14?style=gitweb">
692 692 <b>base</b>
693 693 <span class="logtags"><span class="tagtag" title="1.0">1.0</span> </span>
694 694 </a>
695 695 </td>
696 696 <td class="link" nowrap>
697 697 <a href="/rev/2ef0ac749a14?style=gitweb">changeset</a> |
698 698 <a href="/file/2ef0ac749a14?style=gitweb">files</a>
699 699 </td>
700 700 </tr>
701 701 <tr class="light"><td colspan="4"><a class="list" href="/shortlog?style=gitweb">...</a></td></tr>
702 702 </table>
703 703
704 704 <div><a class="title" href="/tags?style=gitweb">tags</a></div>
705 705 <table cellspacing="0">
706 706
707 707 <tr class="parity0">
708 708 <td class="age"><i>1970-01-01</i></td>
709 709 <td><a class="list" href="/rev/2ef0ac749a14?style=gitweb"><b>1.0</b></a></td>
710 710 <td class="link">
711 711 <a href="/rev/2ef0ac749a14?style=gitweb">changeset</a> |
712 712 <a href="/log/2ef0ac749a14?style=gitweb">changelog</a> |
713 713 <a href="/file/2ef0ac749a14?style=gitweb">files</a>
714 714 </td>
715 715 </tr>
716 716 <tr class="light"><td colspan="3"><a class="list" href="/tags?style=gitweb">...</a></td></tr>
717 717 </table>
718 718
719 719 <div><a class="title" href="#">branches</a></div>
720 720 <table cellspacing="0">
721 721
722 722 <tr class="parity0">
723 723 <td class="age"><i>1970-01-01</i></td>
724 724 <td><a class="list" href="/shortlog/1d22e65f027e?style=gitweb"><b>1d22e65f027e</b></a></td>
725 725 <td class="">stable</td>
726 726 <td class="link">
727 727 <a href="/changeset/1d22e65f027e?style=gitweb">changeset</a> |
728 728 <a href="/log/1d22e65f027e?style=gitweb">changelog</a> |
729 729 <a href="/file/1d22e65f027e?style=gitweb">files</a>
730 730 </td>
731 731 </tr>
732 732 <tr class="parity1">
733 733 <td class="age"><i>1970-01-01</i></td>
734 734 <td><a class="list" href="/shortlog/a4f92ed23982?style=gitweb"><b>a4f92ed23982</b></a></td>
735 735 <td class="">default</td>
736 736 <td class="link">
737 737 <a href="/changeset/a4f92ed23982?style=gitweb">changeset</a> |
738 738 <a href="/log/a4f92ed23982?style=gitweb">changelog</a> |
739 739 <a href="/file/a4f92ed23982?style=gitweb">files</a>
740 740 </td>
741 741 </tr>
742 742 <tr class="light">
743 743 <td colspan="4"><a class="list" href="#">...</a></td>
744 744 </tr>
745 745 </table>
746 746 <div class="page_footer">
747 747 <div class="page_footer_text">test</div>
748 748 <div class="rss_logo">
749 749 <a href="/rss-log">RSS</a>
750 750 <a href="/atom-log">Atom</a>
751 751 </div>
752 752 <br />
753 753
754 754 </div>
755 755 </body>
756 756 </html>
757 757
758 758 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/graph/?style=gitweb'
759 759 200 Script output follows
760 760
761 761 <?xml version="1.0" encoding="ascii"?>
762 762 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
763 763 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
764 764 <head>
765 765 <link rel="icon" href="/static/hgicon.png" type="image/png" />
766 766 <meta name="robots" content="index, nofollow"/>
767 767 <link rel="stylesheet" href="/static/style-gitweb.css" type="text/css" />
768 768
769 769
770 770 <title>test: Graph</title>
771 771 <link rel="alternate" type="application/atom+xml"
772 772 href="/atom-log" title="Atom feed for test"/>
773 773 <link rel="alternate" type="application/rss+xml"
774 774 href="/rss-log" title="RSS feed for test"/>
775 775 <!--[if IE]><script type="text/javascript" src="/static/excanvas.js"></script><![endif]-->
776 776 </head>
777 777 <body>
778 778
779 779 <div class="page_header">
780 780 <a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="/summary?style=gitweb">test</a> / graph
781 781 </div>
782 782
783 783 <form action="/log">
784 784 <input type="hidden" name="style" value="gitweb" />
785 785 <div class="search">
786 786 <input type="text" name="rev" />
787 787 </div>
788 788 </form>
789 789 <div class="page_nav">
790 790 <a href="/summary?style=gitweb">summary</a> |
791 791 <a href="/shortlog?style=gitweb">shortlog</a> |
792 792 <a href="/log/2?style=gitweb">changelog</a> |
793 793 graph |
794 794 <a href="/tags?style=gitweb">tags</a> |
795 795 <a href="/branches?style=gitweb">branches</a> |
796 796 <a href="/file/1d22e65f027e?style=gitweb">files</a> |
797 797 <a href="/help?style=gitweb">help</a>
798 798 <br/>
799 799 <a href="/graph/2?style=gitweb&revcount=30">less</a>
800 800 <a href="/graph/2?style=gitweb&revcount=120">more</a>
801 801 | <a href="/graph/2ef0ac749a14?style=gitweb">(0)</a> <a href="/graph/2ef0ac749a14?style=gitweb">-2</a> <a href="/graph/tip?style=gitweb">tip</a> <br/>
802 802 </div>
803 803
804 804 <div class="title">&nbsp;</div>
805 805
806 806 <noscript>The revision graph only works with JavaScript-enabled browsers.</noscript>
807 807
808 808 <div id="wrapper">
809 809 <ul id="nodebgs"></ul>
810 810 <canvas id="graph" width="480" height="129"></canvas>
811 811 <ul id="graphnodes"></ul>
812 812 </div>
813 813
814 814 <script type="text/javascript" src="/static/graph.js"></script>
815 815 <script>
816 816 <!-- hide script content
817 817
818 818 var data = [["1d22e65f027e", [0, 1], [[0, 0, 1]], "branch", "test", "1970-01-01", ["stable", true], ["tip"], ["something"]], ["a4f92ed23982", [0, 1], [[0, 0, 1]], "Added tag 1.0 for changeset 2ef0ac749a14", "test", "1970-01-01", ["default", true], [], []], ["2ef0ac749a14", [0, 1], [], "base", "test", "1970-01-01", ["default", false], ["1.0"], []]];
819 819 var graph = new Graph();
820 820 graph.scale(39);
821 821
822 822 graph.edge = function(x0, y0, x1, y1, color) {
823 823
824 824 this.setColor(color, 0.0, 0.65);
825 825 this.ctx.beginPath();
826 826 this.ctx.moveTo(x0, y0);
827 827 this.ctx.lineTo(x1, y1);
828 828 this.ctx.stroke();
829 829
830 830 }
831 831
832 832 var revlink = '<li style="_STYLE"><span class="desc">';
833 833 revlink += '<a class="list" href="/rev/_NODEID?style=gitweb" title="_NODEID"><b>_DESC</b></a>';
834 834 revlink += '</span> _TAGS';
835 835 revlink += '<span class="info">_DATE, by _USER</span></li>';
836 836
837 837 graph.vertex = function(x, y, color, parity, cur) {
838 838
839 839 this.ctx.beginPath();
840 840 color = this.setColor(color, 0.25, 0.75);
841 841 this.ctx.arc(x, y, radius, 0, Math.PI * 2, true);
842 842 this.ctx.fill();
843 843
844 844 var bg = '<li class="bg parity' + parity + '"></li>';
845 845 var left = (this.columns + 1) * this.bg_height;
846 846 var nstyle = 'padding-left: ' + left + 'px;';
847 847 var item = revlink.replace(/_STYLE/, nstyle);
848 848 item = item.replace(/_PARITY/, 'parity' + parity);
849 849 item = item.replace(/_NODEID/, cur[0]);
850 850 item = item.replace(/_NODEID/, cur[0]);
851 851 item = item.replace(/_DESC/, cur[3]);
852 852 item = item.replace(/_USER/, cur[4]);
853 853 item = item.replace(/_DATE/, cur[5]);
854 854
855 855 var tagspan = '';
856 856 if (cur[7].length || (cur[6][0] != 'default' || cur[6][1])) {
857 857 tagspan = '<span class="logtags">';
858 858 if (cur[6][1]) {
859 859 tagspan += '<span class="branchtag" title="' + cur[6][0] + '">';
860 860 tagspan += cur[6][0] + '</span> ';
861 861 } else if (!cur[6][1] && cur[6][0] != 'default') {
862 862 tagspan += '<span class="inbranchtag" title="' + cur[6][0] + '">';
863 863 tagspan += cur[6][0] + '</span> ';
864 864 }
865 865 if (cur[7].length) {
866 866 for (var t in cur[7]) {
867 867 var tag = cur[7][t];
868 868 tagspan += '<span class="tagtag">' + tag + '</span> ';
869 869 }
870 870 }
871 871 tagspan += '</span>';
872 872 }
873 873
874 874 item = item.replace(/_TAGS/, tagspan);
875 875 return [bg, item];
876 876
877 877 }
878 878
879 879 graph.render(data);
880 880
881 881 // stop hiding script -->
882 882 </script>
883 883
884 884 <div class="page_nav">
885 885 <a href="/graph/2?style=gitweb&revcount=30">less</a>
886 886 <a href="/graph/2?style=gitweb&revcount=120">more</a>
887 887 | <a href="/graph/2ef0ac749a14?style=gitweb">(0)</a> <a href="/graph/2ef0ac749a14?style=gitweb">-2</a> <a href="/graph/tip?style=gitweb">tip</a>
888 888 </div>
889 889
890 890 <div class="page_footer">
891 891 <div class="page_footer_text">test</div>
892 892 <div class="rss_logo">
893 893 <a href="/rss-log">RSS</a>
894 894 <a href="/atom-log">Atom</a>
895 895 </div>
896 896 <br />
897 897
898 898 </div>
899 899 </body>
900 900 </html>
901 901
902 902
903 903 capabilities
904 904
905 905 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=capabilities'; echo
906 906 200 Script output follows
907 907
908 lookup changegroupsubset branchmap pushkey unbundle=HG10GZ,HG10BZ,HG10UN
908 lookup changegroupsubset branchmap pushkey known unbundle=HG10GZ,HG10BZ,HG10UN
909 909
910 910 heads
911 911
912 912 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=heads'
913 913 200 Script output follows
914 914
915 915 1d22e65f027e5a0609357e7d8e7508cd2ba5d2fe
916 916
917 917 branches
918 918
919 919 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=branches&nodes=0000000000000000000000000000000000000000'
920 920 200 Script output follows
921 921
922 922 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
923 923
924 924 changegroup
925 925
926 926 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=changegroup&roots=0000000000000000000000000000000000000000'
927 927 200 Script output follows
928 928
929 929 x\x9c\xbdTMHUA\x14\xbe\xa8\xf9\xec\xda&\x10\x11*\xb8\x88\x81\x99\xbef\xe6\xce\xbdw\xc6\xf2a\x16E\x1b\x11[%\x98\xcc\xaf\x8f\x8c\xf7\xc0\xf7\x82 (esc)
930 930 4\x11KP2m\x95\xad*\xabE\x05AP\xd0\xc22Z\x14\xf9\x03\xb9j\xa3\x9b$\xa4MJ\xb4\x90\xc0\x9a\x9bO0\x10\xdf\x13\xa2\x81\x0f\x869g\xe6|\xe7\x9c\xef\x8ceY\xf7\xa2KO\xd2\xb7K\x16~\\n\xe9\xad\x90w\x86\xab\x93W\x8e\xdf\xb0r\\Y\xee6(\xa2)\xf6\x95\xc6\x01\xe4\x1az\x80R\xe8kN\x98\xe7R\xa4\xa9K@\xe0!A\xb4k\xa7U*m\x03\x07\xd8\x92\x1d\xd2\xc9\xa4\x1d\xc2\xe6,\xa5\xcc+\x1f\xef\xafDgi\xef\xab\x1d\x1d\xb7\x9a\xe7[W\xfbc\x8f\xde-\xcd\xe7\xcaz\xb3\xbb\x19\xd3\x81\x10>c>\x08\x00"X\x11\xc2\x84@\xd2\xe7B*L\x00\x01P\x04R\xc3@\xbaB0\xdb8#\x83:\x83\xa2h\xbc=\xcd\xdaS\xe1Y,L\xd3\xa0\xf2\xa8\x94J:\xe6\xd8\x81Q\xe0\xe8d\xa7#\xe2,\xd1\xaeR*\xed \xa5\x01\x13\x01\xa6\x0cb\xe3;\xbe\xaf\xfcK[^wK\xe1N\xaf\xbbk\xe8B\xd1\xf4\xc1\x07\xb3\xab[\x10\xfdkmvwcB\xa6\xa4\xd4G\xc4D\xc2\x141\xad\x91\x10\x00\x08J\x81\xcb}\xee \xee+W\xba\x8a\x80\x90|\xd4\xa0\xd6\xa0\xd4T\xde\xe1\x9d,!\xe2\xb5\xa94\xe3\xe7\xd5\x9f\x06\x18\xcba\x03aP\xb8f\xcd\x04\x1a_\\9\xf1\xed\xe4\x9e\xe5\xa6\xd1\xd2\x9f\x03\xa7o\xae\x90H\xf3\xfb\xef\xffH3\xadk (esc)
931 931 \xb0\x90\x92\x88\xb9\x14"\x068\xc2\x1e@\x00\xbb\x8a)\xd3'\x859 (esc)
932 932 \xa8\x80\x84S \xa5\xbd-g\x13`\xe4\xdc\xc3H^\xdf\xe2\xc0TM\xc7\xf4BO\xcf\xde\xae\xe5\xae#\x1frM(K\x97`F\x19\x16s\x05GD\xb9\x01\xc1\x00+\x8c|\x9fp\xc11\xf0\x14\x00\x9cJ\x82<\xe0\x12\x9f\xc1\x90\xd0\xf5\xc8\x19>Pr\xaa\xeaW\xf5\xc4\xae\xd1\xfc\x17\xcf'\x13u\xb1\x9e\xcdHnC\x0e\xcc`\xc8\xa0&\xac\x0e\xf1|\x8c\x10$\xc4\x8c\xa2p\x05`\xdc\x08 \x80\xc4\xd7Rr-\x94\x10\x102\xedi;\xf3f\xf1z\x16\x86\xdb\xd8d\xe5\xe7\x8b\xf5\x8d\rzp\xb2\xfe\xac\xf5\xf2\xd3\xfe\xfckws\xedt\x96b\xd5l\x1c\x0b\x85\xb5\x170\x8f\x11\x84\xb0\x8f\x19\xa0\x00 _\x07\x1ac\xa2\xc3\x89Z\xe7\x96\xf9 \xccNFg\xc7F\xaa\x8a+\x9a\x9cc_\x17\x1b\x17\x9e]z38<\x97+\xb5,",\xc8\xc8?\\\x91\xff\x17.~U\x96\x97\xf5%\xdeN<\x8e\xf5\x97%\xe7^\xcfL\xed~\xda\x96k\xdc->\x86\x02\x83"\x96H\xa6\xe3\xaas=-\xeb7\xe5\xda\x8f\xbc (no-eol) (esc)
933 933
934 934 stream_out
935 935
936 936 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=stream_out'
937 937 200 Script output follows
938 938
939 939 1
940 940
941 941 failing unbundle, requires POST request
942 942
943 943 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=unbundle'
944 944 405 push requires POST request
945 945
946 946 0
947 947 push requires POST request
948 948 [1]
949 949
950 950 Static files
951 951
952 952 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/static/style.css'
953 953 200 Script output follows
954 954
955 955 a { text-decoration:none; }
956 956 .age { white-space:nowrap; }
957 957 .date { white-space:nowrap; }
958 958 .indexlinks { white-space:nowrap; }
959 959 .parity0 { background-color: #ddd; }
960 960 .parity1 { background-color: #eee; }
961 961 .lineno { width: 60px; color: #aaa; font-size: smaller;
962 962 text-align: right; }
963 963 .plusline { color: green; }
964 964 .minusline { color: red; }
965 965 .atline { color: purple; }
966 966 .annotate { font-size: smaller; text-align: right; padding-right: 1em; }
967 967 .buttons a {
968 968 background-color: #666;
969 969 padding: 2pt;
970 970 color: white;
971 971 font-family: sans;
972 972 font-weight: bold;
973 973 }
974 974 .navigate a {
975 975 background-color: #ccc;
976 976 padding: 2pt;
977 977 font-family: sans;
978 978 color: black;
979 979 }
980 980
981 981 .metatag {
982 982 background-color: #888;
983 983 color: white;
984 984 text-align: right;
985 985 }
986 986
987 987 /* Common */
988 988 pre { margin: 0; }
989 989
990 990 .logo {
991 991 float: right;
992 992 clear: right;
993 993 }
994 994
995 995 /* Changelog/Filelog entries */
996 996 .logEntry { width: 100%; }
997 997 .logEntry .age { width: 15%; }
998 998 .logEntry th { font-weight: normal; text-align: right; vertical-align: top; }
999 999 .logEntry th.age, .logEntry th.firstline { font-weight: bold; }
1000 1000 .logEntry th.firstline { text-align: left; width: inherit; }
1001 1001
1002 1002 /* Shortlog entries */
1003 1003 .slogEntry { width: 100%; }
1004 1004 .slogEntry .age { width: 8em; }
1005 1005 .slogEntry td { font-weight: normal; text-align: left; vertical-align: top; }
1006 1006 .slogEntry td.author { width: 15em; }
1007 1007
1008 1008 /* Tag entries */
1009 1009 #tagEntries { list-style: none; margin: 0; padding: 0; }
1010 1010 #tagEntries .tagEntry { list-style: none; margin: 0; padding: 0; }
1011 1011
1012 1012 /* Changeset entry */
1013 1013 #changesetEntry { }
1014 1014 #changesetEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; }
1015 1015 #changesetEntry th.files, #changesetEntry th.description { vertical-align: top; }
1016 1016
1017 1017 /* File diff view */
1018 1018 #filediffEntry { }
1019 1019 #filediffEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; }
1020 1020
1021 1021 /* Graph */
1022 1022 div#wrapper {
1023 1023 position: relative;
1024 1024 margin: 0;
1025 1025 padding: 0;
1026 1026 }
1027 1027
1028 1028 canvas {
1029 1029 position: absolute;
1030 1030 z-index: 5;
1031 1031 top: -0.6em;
1032 1032 margin: 0;
1033 1033 }
1034 1034
1035 1035 ul#nodebgs {
1036 1036 list-style: none inside none;
1037 1037 padding: 0;
1038 1038 margin: 0;
1039 1039 top: -0.7em;
1040 1040 }
1041 1041
1042 1042 ul#graphnodes li, ul#nodebgs li {
1043 1043 height: 39px;
1044 1044 }
1045 1045
1046 1046 ul#graphnodes {
1047 1047 position: absolute;
1048 1048 z-index: 10;
1049 1049 top: -0.85em;
1050 1050 list-style: none inside none;
1051 1051 padding: 0;
1052 1052 }
1053 1053
1054 1054 ul#graphnodes li .info {
1055 1055 display: block;
1056 1056 font-size: 70%;
1057 1057 position: relative;
1058 1058 top: -1px;
1059 1059 }
1060 1060
1061 1061 Stop and restart with HGENCODING=cp932
1062 1062
1063 1063 $ "$TESTDIR/killdaemons.py"
1064 1064 $ HGENCODING=cp932 hg serve --config server.uncompressed=False -n test \
1065 1065 > -p $HGPORT -d --pid-file=hg.pid -E errors.log
1066 1066 $ cat hg.pid >> $DAEMON_PIDS
1067 1067
1068 1068 commit message with Japanese Kanji 'Noh', which ends with '\x5c'
1069 1069
1070 1070 $ echo foo >> foo
1071 1071 $ HGENCODING=cp932 hg ci -m `python -c 'print("\x94\x5c")'`
1072 1072
1073 1073 Graph json escape of multibyte character
1074 1074
1075 1075 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/graph/' \
1076 1076 > | grep '^var data ='
1077 1077 var data = [["40b4d6888e92", [0, 1], [[0, 0, 1]], "\u80fd", "test", "1970-01-01", ["stable", true], ["tip"], ["something"]], ["1d22e65f027e", [0, 1], [[0, 0, 1]], "branch", "test", "1970-01-01", ["stable", false], [], []], ["a4f92ed23982", [0, 1], [[0, 0, 1]], "Added tag 1.0 for changeset 2ef0ac749a14", "test", "1970-01-01", ["default", true], [], []], ["2ef0ac749a14", [0, 1], [], "base", "test", "1970-01-01", ["default", false], ["1.0"], []]];
1078 1078
1079 1079 ERRORS ENCOUNTERED
1080 1080
1081 1081 $ cat errors.log
General Comments 0
You need to be logged in to leave comments. Login now