##// END OF EJS Templates
patch._applydiff: resolve prefix with respect to the cwd...
Siddharth Agarwal -
r24390:72d7d390 default
parent child Browse files
Show More
@@ -1,6370 +1,6370 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 8 from node import hex, bin, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _
11 11 import os, re, difflib, time, tempfile, errno, shlex
12 12 import sys, socket
13 13 import hg, scmutil, util, revlog, copies, error, bookmarks
14 14 import patch, help, encoding, templatekw, discovery
15 15 import archival, changegroup, cmdutil, hbisect
16 16 import sshserver, hgweb, commandserver
17 17 import extensions
18 18 from hgweb import server as hgweb_server
19 19 import merge as mergemod
20 20 import minirst, revset, fileset
21 21 import dagparser, context, simplemerge, graphmod, copies
22 22 import random
23 23 import setdiscovery, treediscovery, dagutil, pvec, localrepo
24 24 import phases, obsolete, exchange, bundle2
25 25 import ui as uimod
26 26
27 27 table = {}
28 28
29 29 command = cmdutil.command(table)
30 30
31 31 # Space delimited list of commands that don't require local repositories.
32 32 # This should be populated by passing norepo=True into the @command decorator.
33 33 norepo = ''
34 34 # Space delimited list of commands that optionally require local repositories.
35 35 # This should be populated by passing optionalrepo=True into the @command
36 36 # decorator.
37 37 optionalrepo = ''
38 38 # Space delimited list of commands that will examine arguments looking for
39 39 # a repository. This should be populated by passing inferrepo=True into the
40 40 # @command decorator.
41 41 inferrepo = ''
42 42
43 43 # common command options
44 44
45 45 globalopts = [
46 46 ('R', 'repository', '',
47 47 _('repository root directory or name of overlay bundle file'),
48 48 _('REPO')),
49 49 ('', 'cwd', '',
50 50 _('change working directory'), _('DIR')),
51 51 ('y', 'noninteractive', None,
52 52 _('do not prompt, automatically pick the first choice for all prompts')),
53 53 ('q', 'quiet', None, _('suppress output')),
54 54 ('v', 'verbose', None, _('enable additional output')),
55 55 ('', 'config', [],
56 56 _('set/override config option (use \'section.name=value\')'),
57 57 _('CONFIG')),
58 58 ('', 'debug', None, _('enable debugging output')),
59 59 ('', 'debugger', None, _('start debugger')),
60 60 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
61 61 _('ENCODE')),
62 62 ('', 'encodingmode', encoding.encodingmode,
63 63 _('set the charset encoding mode'), _('MODE')),
64 64 ('', 'traceback', None, _('always print a traceback on exception')),
65 65 ('', 'time', None, _('time how long the command takes')),
66 66 ('', 'profile', None, _('print command execution profile')),
67 67 ('', 'version', None, _('output version information and exit')),
68 68 ('h', 'help', None, _('display help and exit')),
69 69 ('', 'hidden', False, _('consider hidden changesets')),
70 70 ]
71 71
72 72 dryrunopts = [('n', 'dry-run', None,
73 73 _('do not perform actions, just print output'))]
74 74
75 75 remoteopts = [
76 76 ('e', 'ssh', '',
77 77 _('specify ssh command to use'), _('CMD')),
78 78 ('', 'remotecmd', '',
79 79 _('specify hg command to run on the remote side'), _('CMD')),
80 80 ('', 'insecure', None,
81 81 _('do not verify server certificate (ignoring web.cacerts config)')),
82 82 ]
83 83
84 84 walkopts = [
85 85 ('I', 'include', [],
86 86 _('include names matching the given patterns'), _('PATTERN')),
87 87 ('X', 'exclude', [],
88 88 _('exclude names matching the given patterns'), _('PATTERN')),
89 89 ]
90 90
91 91 commitopts = [
92 92 ('m', 'message', '',
93 93 _('use text as commit message'), _('TEXT')),
94 94 ('l', 'logfile', '',
95 95 _('read commit message from file'), _('FILE')),
96 96 ]
97 97
98 98 commitopts2 = [
99 99 ('d', 'date', '',
100 100 _('record the specified date as commit date'), _('DATE')),
101 101 ('u', 'user', '',
102 102 _('record the specified user as committer'), _('USER')),
103 103 ]
104 104
105 105 # hidden for now
106 106 formatteropts = [
107 107 ('T', 'template', '',
108 108 _('display with template (DEPRECATED)'), _('TEMPLATE')),
109 109 ]
110 110
111 111 templateopts = [
112 112 ('', 'style', '',
113 113 _('display using template map file (DEPRECATED)'), _('STYLE')),
114 114 ('T', 'template', '',
115 115 _('display with template'), _('TEMPLATE')),
116 116 ]
117 117
118 118 logopts = [
119 119 ('p', 'patch', None, _('show patch')),
120 120 ('g', 'git', None, _('use git extended diff format')),
121 121 ('l', 'limit', '',
122 122 _('limit number of changes displayed'), _('NUM')),
123 123 ('M', 'no-merges', None, _('do not show merges')),
124 124 ('', 'stat', None, _('output diffstat-style summary of changes')),
125 125 ('G', 'graph', None, _("show the revision DAG")),
126 126 ] + templateopts
127 127
128 128 diffopts = [
129 129 ('a', 'text', None, _('treat all files as text')),
130 130 ('g', 'git', None, _('use git extended diff format')),
131 131 ('', 'nodates', None, _('omit dates from diff headers'))
132 132 ]
133 133
134 134 diffwsopts = [
135 135 ('w', 'ignore-all-space', None,
136 136 _('ignore white space when comparing lines')),
137 137 ('b', 'ignore-space-change', None,
138 138 _('ignore changes in the amount of white space')),
139 139 ('B', 'ignore-blank-lines', None,
140 140 _('ignore changes whose lines are all blank')),
141 141 ]
142 142
143 143 diffopts2 = [
144 144 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
145 145 ('p', 'show-function', None, _('show which function each change is in')),
146 146 ('', 'reverse', None, _('produce a diff that undoes the changes')),
147 147 ] + diffwsopts + [
148 148 ('U', 'unified', '',
149 149 _('number of lines of context to show'), _('NUM')),
150 150 ('', 'stat', None, _('output diffstat-style summary of changes')),
151 151 ]
152 152
153 153 mergetoolopts = [
154 154 ('t', 'tool', '', _('specify merge tool')),
155 155 ]
156 156
157 157 similarityopts = [
158 158 ('s', 'similarity', '',
159 159 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
160 160 ]
161 161
162 162 subrepoopts = [
163 163 ('S', 'subrepos', None,
164 164 _('recurse into subrepositories'))
165 165 ]
166 166
167 167 # Commands start here, listed alphabetically
168 168
169 169 @command('^add',
170 170 walkopts + subrepoopts + dryrunopts,
171 171 _('[OPTION]... [FILE]...'),
172 172 inferrepo=True)
173 173 def add(ui, repo, *pats, **opts):
174 174 """add the specified files on the next commit
175 175
176 176 Schedule files to be version controlled and added to the
177 177 repository.
178 178
179 179 The files will be added to the repository at the next commit. To
180 180 undo an add before that, see :hg:`forget`.
181 181
182 182 If no names are given, add all files to the repository.
183 183
184 184 .. container:: verbose
185 185
186 186 An example showing how new (unknown) files are added
187 187 automatically by :hg:`add`::
188 188
189 189 $ ls
190 190 foo.c
191 191 $ hg status
192 192 ? foo.c
193 193 $ hg add
194 194 adding foo.c
195 195 $ hg status
196 196 A foo.c
197 197
198 198 Returns 0 if all files are successfully added.
199 199 """
200 200
201 201 m = scmutil.match(repo[None], pats, opts)
202 202 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
203 203 return rejected and 1 or 0
204 204
205 205 @command('addremove',
206 206 similarityopts + subrepoopts + walkopts + dryrunopts,
207 207 _('[OPTION]... [FILE]...'),
208 208 inferrepo=True)
209 209 def addremove(ui, repo, *pats, **opts):
210 210 """add all new files, delete all missing files
211 211
212 212 Add all new files and remove all missing files from the
213 213 repository.
214 214
215 215 New files are ignored if they match any of the patterns in
216 216 ``.hgignore``. As with add, these changes take effect at the next
217 217 commit.
218 218
219 219 Use the -s/--similarity option to detect renamed files. This
220 220 option takes a percentage between 0 (disabled) and 100 (files must
221 221 be identical) as its parameter. With a parameter greater than 0,
222 222 this compares every removed file with every added file and records
223 223 those similar enough as renames. Detecting renamed files this way
224 224 can be expensive. After using this option, :hg:`status -C` can be
225 225 used to check which files were identified as moved or renamed. If
226 226 not specified, -s/--similarity defaults to 100 and only renames of
227 227 identical files are detected.
228 228
229 229 Returns 0 if all files are successfully added.
230 230 """
231 231 try:
232 232 sim = float(opts.get('similarity') or 100)
233 233 except ValueError:
234 234 raise util.Abort(_('similarity must be a number'))
235 235 if sim < 0 or sim > 100:
236 236 raise util.Abort(_('similarity must be between 0 and 100'))
237 237 matcher = scmutil.match(repo[None], pats, opts)
238 238 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
239 239
240 240 @command('^annotate|blame',
241 241 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
242 242 ('', 'follow', None,
243 243 _('follow copies/renames and list the filename (DEPRECATED)')),
244 244 ('', 'no-follow', None, _("don't follow copies and renames")),
245 245 ('a', 'text', None, _('treat all files as text')),
246 246 ('u', 'user', None, _('list the author (long with -v)')),
247 247 ('f', 'file', None, _('list the filename')),
248 248 ('d', 'date', None, _('list the date (short with -q)')),
249 249 ('n', 'number', None, _('list the revision number (default)')),
250 250 ('c', 'changeset', None, _('list the changeset')),
251 251 ('l', 'line-number', None, _('show line number at the first appearance'))
252 252 ] + diffwsopts + walkopts + formatteropts,
253 253 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
254 254 inferrepo=True)
255 255 def annotate(ui, repo, *pats, **opts):
256 256 """show changeset information by line for each file
257 257
258 258 List changes in files, showing the revision id responsible for
259 259 each line
260 260
261 261 This command is useful for discovering when a change was made and
262 262 by whom.
263 263
264 264 Without the -a/--text option, annotate will avoid processing files
265 265 it detects as binary. With -a, annotate will annotate the file
266 266 anyway, although the results will probably be neither useful
267 267 nor desirable.
268 268
269 269 Returns 0 on success.
270 270 """
271 271 if not pats:
272 272 raise util.Abort(_('at least one filename or pattern is required'))
273 273
274 274 if opts.get('follow'):
275 275 # --follow is deprecated and now just an alias for -f/--file
276 276 # to mimic the behavior of Mercurial before version 1.5
277 277 opts['file'] = True
278 278
279 279 fm = ui.formatter('annotate', opts)
280 280 if ui.quiet:
281 281 datefunc = util.shortdate
282 282 else:
283 283 datefunc = util.datestr
284 284 hexfn = fm.hexfunc
285 285
286 286 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
287 287 ('number', ' ', lambda x: x[0].rev(), str),
288 288 ('changeset', ' ', lambda x: hexfn(x[0].node()), str),
289 289 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
290 290 ('file', ' ', lambda x: x[0].path(), str),
291 291 ('line_number', ':', lambda x: x[1], str),
292 292 ]
293 293 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
294 294
295 295 if (not opts.get('user') and not opts.get('changeset')
296 296 and not opts.get('date') and not opts.get('file')):
297 297 opts['number'] = True
298 298
299 299 linenumber = opts.get('line_number') is not None
300 300 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
301 301 raise util.Abort(_('at least one of -n/-c is required for -l'))
302 302
303 303 if fm:
304 304 def makefunc(get, fmt):
305 305 return get
306 306 else:
307 307 def makefunc(get, fmt):
308 308 return lambda x: fmt(get(x))
309 309 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
310 310 if opts.get(op)]
311 311 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
312 312 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
313 313 if opts.get(op))
314 314
315 315 def bad(x, y):
316 316 raise util.Abort("%s: %s" % (x, y))
317 317
318 318 ctx = scmutil.revsingle(repo, opts.get('rev'))
319 319 m = scmutil.match(ctx, pats, opts)
320 320 m.bad = bad
321 321 follow = not opts.get('no_follow')
322 322 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
323 323 whitespace=True)
324 324 for abs in ctx.walk(m):
325 325 fctx = ctx[abs]
326 326 if not opts.get('text') and util.binary(fctx.data()):
327 327 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
328 328 continue
329 329
330 330 lines = fctx.annotate(follow=follow, linenumber=linenumber,
331 331 diffopts=diffopts)
332 332 formats = []
333 333 pieces = []
334 334
335 335 for f, sep in funcmap:
336 336 l = [f(n) for n, dummy in lines]
337 337 if l:
338 338 if fm:
339 339 formats.append(['%s' for x in l])
340 340 else:
341 341 sizes = [encoding.colwidth(x) for x in l]
342 342 ml = max(sizes)
343 343 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
344 344 pieces.append(l)
345 345
346 346 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
347 347 fm.startitem()
348 348 fm.write(fields, "".join(f), *p)
349 349 fm.write('line', ": %s", l[1])
350 350
351 351 if lines and not lines[-1][1].endswith('\n'):
352 352 fm.plain('\n')
353 353
354 354 fm.end()
355 355
356 356 @command('archive',
357 357 [('', 'no-decode', None, _('do not pass files through decoders')),
358 358 ('p', 'prefix', '', _('directory prefix for files in archive'),
359 359 _('PREFIX')),
360 360 ('r', 'rev', '', _('revision to distribute'), _('REV')),
361 361 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
362 362 ] + subrepoopts + walkopts,
363 363 _('[OPTION]... DEST'))
364 364 def archive(ui, repo, dest, **opts):
365 365 '''create an unversioned archive of a repository revision
366 366
367 367 By default, the revision used is the parent of the working
368 368 directory; use -r/--rev to specify a different revision.
369 369
370 370 The archive type is automatically detected based on file
371 371 extension (or override using -t/--type).
372 372
373 373 .. container:: verbose
374 374
375 375 Examples:
376 376
377 377 - create a zip file containing the 1.0 release::
378 378
379 379 hg archive -r 1.0 project-1.0.zip
380 380
381 381 - create a tarball excluding .hg files::
382 382
383 383 hg archive project.tar.gz -X ".hg*"
384 384
385 385 Valid types are:
386 386
387 387 :``files``: a directory full of files (default)
388 388 :``tar``: tar archive, uncompressed
389 389 :``tbz2``: tar archive, compressed using bzip2
390 390 :``tgz``: tar archive, compressed using gzip
391 391 :``uzip``: zip archive, uncompressed
392 392 :``zip``: zip archive, compressed using deflate
393 393
394 394 The exact name of the destination archive or directory is given
395 395 using a format string; see :hg:`help export` for details.
396 396
397 397 Each member added to an archive file has a directory prefix
398 398 prepended. Use -p/--prefix to specify a format string for the
399 399 prefix. The default is the basename of the archive, with suffixes
400 400 removed.
401 401
402 402 Returns 0 on success.
403 403 '''
404 404
405 405 ctx = scmutil.revsingle(repo, opts.get('rev'))
406 406 if not ctx:
407 407 raise util.Abort(_('no working directory: please specify a revision'))
408 408 node = ctx.node()
409 409 dest = cmdutil.makefilename(repo, dest, node)
410 410 if os.path.realpath(dest) == repo.root:
411 411 raise util.Abort(_('repository root cannot be destination'))
412 412
413 413 kind = opts.get('type') or archival.guesskind(dest) or 'files'
414 414 prefix = opts.get('prefix')
415 415
416 416 if dest == '-':
417 417 if kind == 'files':
418 418 raise util.Abort(_('cannot archive plain files to stdout'))
419 419 dest = cmdutil.makefileobj(repo, dest)
420 420 if not prefix:
421 421 prefix = os.path.basename(repo.root) + '-%h'
422 422
423 423 prefix = cmdutil.makefilename(repo, prefix, node)
424 424 matchfn = scmutil.match(ctx, [], opts)
425 425 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
426 426 matchfn, prefix, subrepos=opts.get('subrepos'))
427 427
428 428 @command('backout',
429 429 [('', 'merge', None, _('merge with old dirstate parent after backout')),
430 430 ('', 'commit', None, _('commit if no conflicts were encountered')),
431 431 ('', 'parent', '',
432 432 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
433 433 ('r', 'rev', '', _('revision to backout'), _('REV')),
434 434 ('e', 'edit', False, _('invoke editor on commit messages')),
435 435 ] + mergetoolopts + walkopts + commitopts + commitopts2,
436 436 _('[OPTION]... [-r] REV'))
437 437 def backout(ui, repo, node=None, rev=None, commit=False, **opts):
438 438 '''reverse effect of earlier changeset
439 439
440 440 Prepare a new changeset with the effect of REV undone in the
441 441 current working directory.
442 442
443 443 If REV is the parent of the working directory, then this new changeset
444 444 is committed automatically. Otherwise, hg needs to merge the
445 445 changes and the merged result is left uncommitted.
446 446
447 447 .. note::
448 448
449 449 backout cannot be used to fix either an unwanted or
450 450 incorrect merge.
451 451
452 452 .. container:: verbose
453 453
454 454 By default, the pending changeset will have one parent,
455 455 maintaining a linear history. With --merge, the pending
456 456 changeset will instead have two parents: the old parent of the
457 457 working directory and a new child of REV that simply undoes REV.
458 458
459 459 Before version 1.7, the behavior without --merge was equivalent
460 460 to specifying --merge followed by :hg:`update --clean .` to
461 461 cancel the merge and leave the child of REV as a head to be
462 462 merged separately.
463 463
464 464 See :hg:`help dates` for a list of formats valid for -d/--date.
465 465
466 466 Returns 0 on success, 1 if nothing to backout or there are unresolved
467 467 files.
468 468 '''
469 469 if rev and node:
470 470 raise util.Abort(_("please specify just one revision"))
471 471
472 472 if not rev:
473 473 rev = node
474 474
475 475 if not rev:
476 476 raise util.Abort(_("please specify a revision to backout"))
477 477
478 478 date = opts.get('date')
479 479 if date:
480 480 opts['date'] = util.parsedate(date)
481 481
482 482 cmdutil.checkunfinished(repo)
483 483 cmdutil.bailifchanged(repo)
484 484 node = scmutil.revsingle(repo, rev).node()
485 485
486 486 op1, op2 = repo.dirstate.parents()
487 487 if not repo.changelog.isancestor(node, op1):
488 488 raise util.Abort(_('cannot backout change that is not an ancestor'))
489 489
490 490 p1, p2 = repo.changelog.parents(node)
491 491 if p1 == nullid:
492 492 raise util.Abort(_('cannot backout a change with no parents'))
493 493 if p2 != nullid:
494 494 if not opts.get('parent'):
495 495 raise util.Abort(_('cannot backout a merge changeset'))
496 496 p = repo.lookup(opts['parent'])
497 497 if p not in (p1, p2):
498 498 raise util.Abort(_('%s is not a parent of %s') %
499 499 (short(p), short(node)))
500 500 parent = p
501 501 else:
502 502 if opts.get('parent'):
503 503 raise util.Abort(_('cannot use --parent on non-merge changeset'))
504 504 parent = p1
505 505
506 506 # the backout should appear on the same branch
507 507 wlock = repo.wlock()
508 508 try:
509 509 branch = repo.dirstate.branch()
510 510 bheads = repo.branchheads(branch)
511 511 rctx = scmutil.revsingle(repo, hex(parent))
512 512 if not opts.get('merge') and op1 != node:
513 513 try:
514 514 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
515 515 'backout')
516 516 repo.dirstate.beginparentchange()
517 517 stats = mergemod.update(repo, parent, True, True, False,
518 518 node, False)
519 519 repo.setparents(op1, op2)
520 520 repo.dirstate.endparentchange()
521 521 hg._showstats(repo, stats)
522 522 if stats[3]:
523 523 repo.ui.status(_("use 'hg resolve' to retry unresolved "
524 524 "file merges\n"))
525 525 return 1
526 526 elif not commit:
527 527 msg = _("changeset %s backed out, "
528 528 "don't forget to commit.\n")
529 529 ui.status(msg % short(node))
530 530 return 0
531 531 finally:
532 532 ui.setconfig('ui', 'forcemerge', '', '')
533 533 else:
534 534 hg.clean(repo, node, show_stats=False)
535 535 repo.dirstate.setbranch(branch)
536 536 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
537 537
538 538
539 539 def commitfunc(ui, repo, message, match, opts):
540 540 editform = 'backout'
541 541 e = cmdutil.getcommiteditor(editform=editform, **opts)
542 542 if not message:
543 543 # we don't translate commit messages
544 544 message = "Backed out changeset %s" % short(node)
545 545 e = cmdutil.getcommiteditor(edit=True, editform=editform)
546 546 return repo.commit(message, opts.get('user'), opts.get('date'),
547 547 match, editor=e)
548 548 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
549 549 if not newnode:
550 550 ui.status(_("nothing changed\n"))
551 551 return 1
552 552 cmdutil.commitstatus(repo, newnode, branch, bheads)
553 553
554 554 def nice(node):
555 555 return '%d:%s' % (repo.changelog.rev(node), short(node))
556 556 ui.status(_('changeset %s backs out changeset %s\n') %
557 557 (nice(repo.changelog.tip()), nice(node)))
558 558 if opts.get('merge') and op1 != node:
559 559 hg.clean(repo, op1, show_stats=False)
560 560 ui.status(_('merging with changeset %s\n')
561 561 % nice(repo.changelog.tip()))
562 562 try:
563 563 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
564 564 'backout')
565 565 return hg.merge(repo, hex(repo.changelog.tip()))
566 566 finally:
567 567 ui.setconfig('ui', 'forcemerge', '', '')
568 568 finally:
569 569 wlock.release()
570 570 return 0
571 571
572 572 @command('bisect',
573 573 [('r', 'reset', False, _('reset bisect state')),
574 574 ('g', 'good', False, _('mark changeset good')),
575 575 ('b', 'bad', False, _('mark changeset bad')),
576 576 ('s', 'skip', False, _('skip testing changeset')),
577 577 ('e', 'extend', False, _('extend the bisect range')),
578 578 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
579 579 ('U', 'noupdate', False, _('do not update to target'))],
580 580 _("[-gbsr] [-U] [-c CMD] [REV]"))
581 581 def bisect(ui, repo, rev=None, extra=None, command=None,
582 582 reset=None, good=None, bad=None, skip=None, extend=None,
583 583 noupdate=None):
584 584 """subdivision search of changesets
585 585
586 586 This command helps to find changesets which introduce problems. To
587 587 use, mark the earliest changeset you know exhibits the problem as
588 588 bad, then mark the latest changeset which is free from the problem
589 589 as good. Bisect will update your working directory to a revision
590 590 for testing (unless the -U/--noupdate option is specified). Once
591 591 you have performed tests, mark the working directory as good or
592 592 bad, and bisect will either update to another candidate changeset
593 593 or announce that it has found the bad revision.
594 594
595 595 As a shortcut, you can also use the revision argument to mark a
596 596 revision as good or bad without checking it out first.
597 597
598 598 If you supply a command, it will be used for automatic bisection.
599 599 The environment variable HG_NODE will contain the ID of the
600 600 changeset being tested. The exit status of the command will be
601 601 used to mark revisions as good or bad: status 0 means good, 125
602 602 means to skip the revision, 127 (command not found) will abort the
603 603 bisection, and any other non-zero exit status means the revision
604 604 is bad.
605 605
606 606 .. container:: verbose
607 607
608 608 Some examples:
609 609
610 610 - start a bisection with known bad revision 34, and good revision 12::
611 611
612 612 hg bisect --bad 34
613 613 hg bisect --good 12
614 614
615 615 - advance the current bisection by marking current revision as good or
616 616 bad::
617 617
618 618 hg bisect --good
619 619 hg bisect --bad
620 620
621 621 - mark the current revision, or a known revision, to be skipped (e.g. if
622 622 that revision is not usable because of another issue)::
623 623
624 624 hg bisect --skip
625 625 hg bisect --skip 23
626 626
627 627 - skip all revisions that do not touch directories ``foo`` or ``bar``::
628 628
629 629 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
630 630
631 631 - forget the current bisection::
632 632
633 633 hg bisect --reset
634 634
635 635 - use 'make && make tests' to automatically find the first broken
636 636 revision::
637 637
638 638 hg bisect --reset
639 639 hg bisect --bad 34
640 640 hg bisect --good 12
641 641 hg bisect --command "make && make tests"
642 642
643 643 - see all changesets whose states are already known in the current
644 644 bisection::
645 645
646 646 hg log -r "bisect(pruned)"
647 647
648 648 - see the changeset currently being bisected (especially useful
649 649 if running with -U/--noupdate)::
650 650
651 651 hg log -r "bisect(current)"
652 652
653 653 - see all changesets that took part in the current bisection::
654 654
655 655 hg log -r "bisect(range)"
656 656
657 657 - you can even get a nice graph::
658 658
659 659 hg log --graph -r "bisect(range)"
660 660
661 661 See :hg:`help revsets` for more about the `bisect()` keyword.
662 662
663 663 Returns 0 on success.
664 664 """
665 665 def extendbisectrange(nodes, good):
666 666 # bisect is incomplete when it ends on a merge node and
667 667 # one of the parent was not checked.
668 668 parents = repo[nodes[0]].parents()
669 669 if len(parents) > 1:
670 670 if good:
671 671 side = state['bad']
672 672 else:
673 673 side = state['good']
674 674 num = len(set(i.node() for i in parents) & set(side))
675 675 if num == 1:
676 676 return parents[0].ancestor(parents[1])
677 677 return None
678 678
679 679 def print_result(nodes, good):
680 680 displayer = cmdutil.show_changeset(ui, repo, {})
681 681 if len(nodes) == 1:
682 682 # narrowed it down to a single revision
683 683 if good:
684 684 ui.write(_("The first good revision is:\n"))
685 685 else:
686 686 ui.write(_("The first bad revision is:\n"))
687 687 displayer.show(repo[nodes[0]])
688 688 extendnode = extendbisectrange(nodes, good)
689 689 if extendnode is not None:
690 690 ui.write(_('Not all ancestors of this changeset have been'
691 691 ' checked.\nUse bisect --extend to continue the '
692 692 'bisection from\nthe common ancestor, %s.\n')
693 693 % extendnode)
694 694 else:
695 695 # multiple possible revisions
696 696 if good:
697 697 ui.write(_("Due to skipped revisions, the first "
698 698 "good revision could be any of:\n"))
699 699 else:
700 700 ui.write(_("Due to skipped revisions, the first "
701 701 "bad revision could be any of:\n"))
702 702 for n in nodes:
703 703 displayer.show(repo[n])
704 704 displayer.close()
705 705
706 706 def check_state(state, interactive=True):
707 707 if not state['good'] or not state['bad']:
708 708 if (good or bad or skip or reset) and interactive:
709 709 return
710 710 if not state['good']:
711 711 raise util.Abort(_('cannot bisect (no known good revisions)'))
712 712 else:
713 713 raise util.Abort(_('cannot bisect (no known bad revisions)'))
714 714 return True
715 715
716 716 # backward compatibility
717 717 if rev in "good bad reset init".split():
718 718 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
719 719 cmd, rev, extra = rev, extra, None
720 720 if cmd == "good":
721 721 good = True
722 722 elif cmd == "bad":
723 723 bad = True
724 724 else:
725 725 reset = True
726 726 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
727 727 raise util.Abort(_('incompatible arguments'))
728 728
729 729 cmdutil.checkunfinished(repo)
730 730
731 731 if reset:
732 732 p = repo.join("bisect.state")
733 733 if os.path.exists(p):
734 734 os.unlink(p)
735 735 return
736 736
737 737 state = hbisect.load_state(repo)
738 738
739 739 if command:
740 740 changesets = 1
741 741 if noupdate:
742 742 try:
743 743 node = state['current'][0]
744 744 except LookupError:
745 745 raise util.Abort(_('current bisect revision is unknown - '
746 746 'start a new bisect to fix'))
747 747 else:
748 748 node, p2 = repo.dirstate.parents()
749 749 if p2 != nullid:
750 750 raise util.Abort(_('current bisect revision is a merge'))
751 751 try:
752 752 while changesets:
753 753 # update state
754 754 state['current'] = [node]
755 755 hbisect.save_state(repo, state)
756 756 status = ui.system(command, environ={'HG_NODE': hex(node)})
757 757 if status == 125:
758 758 transition = "skip"
759 759 elif status == 0:
760 760 transition = "good"
761 761 # status < 0 means process was killed
762 762 elif status == 127:
763 763 raise util.Abort(_("failed to execute %s") % command)
764 764 elif status < 0:
765 765 raise util.Abort(_("%s killed") % command)
766 766 else:
767 767 transition = "bad"
768 768 ctx = scmutil.revsingle(repo, rev, node)
769 769 rev = None # clear for future iterations
770 770 state[transition].append(ctx.node())
771 771 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
772 772 check_state(state, interactive=False)
773 773 # bisect
774 774 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
775 775 # update to next check
776 776 node = nodes[0]
777 777 if not noupdate:
778 778 cmdutil.bailifchanged(repo)
779 779 hg.clean(repo, node, show_stats=False)
780 780 finally:
781 781 state['current'] = [node]
782 782 hbisect.save_state(repo, state)
783 783 print_result(nodes, bgood)
784 784 return
785 785
786 786 # update state
787 787
788 788 if rev:
789 789 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
790 790 else:
791 791 nodes = [repo.lookup('.')]
792 792
793 793 if good or bad or skip:
794 794 if good:
795 795 state['good'] += nodes
796 796 elif bad:
797 797 state['bad'] += nodes
798 798 elif skip:
799 799 state['skip'] += nodes
800 800 hbisect.save_state(repo, state)
801 801
802 802 if not check_state(state):
803 803 return
804 804
805 805 # actually bisect
806 806 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
807 807 if extend:
808 808 if not changesets:
809 809 extendnode = extendbisectrange(nodes, good)
810 810 if extendnode is not None:
811 811 ui.write(_("Extending search to changeset %d:%s\n")
812 812 % (extendnode.rev(), extendnode))
813 813 state['current'] = [extendnode.node()]
814 814 hbisect.save_state(repo, state)
815 815 if noupdate:
816 816 return
817 817 cmdutil.bailifchanged(repo)
818 818 return hg.clean(repo, extendnode.node())
819 819 raise util.Abort(_("nothing to extend"))
820 820
821 821 if changesets == 0:
822 822 print_result(nodes, good)
823 823 else:
824 824 assert len(nodes) == 1 # only a single node can be tested next
825 825 node = nodes[0]
826 826 # compute the approximate number of remaining tests
827 827 tests, size = 0, 2
828 828 while size <= changesets:
829 829 tests, size = tests + 1, size * 2
830 830 rev = repo.changelog.rev(node)
831 831 ui.write(_("Testing changeset %d:%s "
832 832 "(%d changesets remaining, ~%d tests)\n")
833 833 % (rev, short(node), changesets, tests))
834 834 state['current'] = [node]
835 835 hbisect.save_state(repo, state)
836 836 if not noupdate:
837 837 cmdutil.bailifchanged(repo)
838 838 return hg.clean(repo, node)
839 839
840 840 @command('bookmarks|bookmark',
841 841 [('f', 'force', False, _('force')),
842 842 ('r', 'rev', '', _('revision'), _('REV')),
843 843 ('d', 'delete', False, _('delete a given bookmark')),
844 844 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
845 845 ('i', 'inactive', False, _('mark a bookmark inactive')),
846 846 ] + formatteropts,
847 847 _('hg bookmarks [OPTIONS]... [NAME]...'))
848 848 def bookmark(ui, repo, *names, **opts):
849 849 '''create a new bookmark or list existing bookmarks
850 850
851 851 Bookmarks are labels on changesets to help track lines of development.
852 852 Bookmarks are unversioned and can be moved, renamed and deleted.
853 853 Deleting or moving a bookmark has no effect on the associated changesets.
854 854
855 855 Creating or updating to a bookmark causes it to be marked as 'active'.
856 856 The active bookmark is indicated with a '*'.
857 857 When a commit is made, the active bookmark will advance to the new commit.
858 858 A plain :hg:`update` will also advance an active bookmark, if possible.
859 859 Updating away from a bookmark will cause it to be deactivated.
860 860
861 861 Bookmarks can be pushed and pulled between repositories (see
862 862 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
863 863 diverged, a new 'divergent bookmark' of the form 'name@path' will
864 864 be created. Using :hg:`merge` will resolve the divergence.
865 865
866 866 A bookmark named '@' has the special property that :hg:`clone` will
867 867 check it out by default if it exists.
868 868
869 869 .. container:: verbose
870 870
871 871 Examples:
872 872
873 873 - create an active bookmark for a new line of development::
874 874
875 875 hg book new-feature
876 876
877 877 - create an inactive bookmark as a place marker::
878 878
879 879 hg book -i reviewed
880 880
881 881 - create an inactive bookmark on another changeset::
882 882
883 883 hg book -r .^ tested
884 884
885 885 - move the '@' bookmark from another branch::
886 886
887 887 hg book -f @
888 888 '''
889 889 force = opts.get('force')
890 890 rev = opts.get('rev')
891 891 delete = opts.get('delete')
892 892 rename = opts.get('rename')
893 893 inactive = opts.get('inactive')
894 894
895 895 def checkformat(mark):
896 896 mark = mark.strip()
897 897 if not mark:
898 898 raise util.Abort(_("bookmark names cannot consist entirely of "
899 899 "whitespace"))
900 900 scmutil.checknewlabel(repo, mark, 'bookmark')
901 901 return mark
902 902
903 903 def checkconflict(repo, mark, cur, force=False, target=None):
904 904 if mark in marks and not force:
905 905 if target:
906 906 if marks[mark] == target and target == cur:
907 907 # re-activating a bookmark
908 908 return
909 909 anc = repo.changelog.ancestors([repo[target].rev()])
910 910 bmctx = repo[marks[mark]]
911 911 divs = [repo[b].node() for b in marks
912 912 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
913 913
914 914 # allow resolving a single divergent bookmark even if moving
915 915 # the bookmark across branches when a revision is specified
916 916 # that contains a divergent bookmark
917 917 if bmctx.rev() not in anc and target in divs:
918 918 bookmarks.deletedivergent(repo, [target], mark)
919 919 return
920 920
921 921 deletefrom = [b for b in divs
922 922 if repo[b].rev() in anc or b == target]
923 923 bookmarks.deletedivergent(repo, deletefrom, mark)
924 924 if bookmarks.validdest(repo, bmctx, repo[target]):
925 925 ui.status(_("moving bookmark '%s' forward from %s\n") %
926 926 (mark, short(bmctx.node())))
927 927 return
928 928 raise util.Abort(_("bookmark '%s' already exists "
929 929 "(use -f to force)") % mark)
930 930 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
931 931 and not force):
932 932 raise util.Abort(
933 933 _("a bookmark cannot have the name of an existing branch"))
934 934
935 935 if delete and rename:
936 936 raise util.Abort(_("--delete and --rename are incompatible"))
937 937 if delete and rev:
938 938 raise util.Abort(_("--rev is incompatible with --delete"))
939 939 if rename and rev:
940 940 raise util.Abort(_("--rev is incompatible with --rename"))
941 941 if not names and (delete or rev):
942 942 raise util.Abort(_("bookmark name required"))
943 943
944 944 if delete or rename or names or inactive:
945 945 wlock = repo.wlock()
946 946 try:
947 947 cur = repo.changectx('.').node()
948 948 marks = repo._bookmarks
949 949 if delete:
950 950 for mark in names:
951 951 if mark not in marks:
952 952 raise util.Abort(_("bookmark '%s' does not exist") %
953 953 mark)
954 954 if mark == repo._bookmarkcurrent:
955 955 bookmarks.unsetcurrent(repo)
956 956 del marks[mark]
957 957 marks.write()
958 958
959 959 elif rename:
960 960 if not names:
961 961 raise util.Abort(_("new bookmark name required"))
962 962 elif len(names) > 1:
963 963 raise util.Abort(_("only one new bookmark name allowed"))
964 964 mark = checkformat(names[0])
965 965 if rename not in marks:
966 966 raise util.Abort(_("bookmark '%s' does not exist") % rename)
967 967 checkconflict(repo, mark, cur, force)
968 968 marks[mark] = marks[rename]
969 969 if repo._bookmarkcurrent == rename and not inactive:
970 970 bookmarks.setcurrent(repo, mark)
971 971 del marks[rename]
972 972 marks.write()
973 973
974 974 elif names:
975 975 newact = None
976 976 for mark in names:
977 977 mark = checkformat(mark)
978 978 if newact is None:
979 979 newact = mark
980 980 if inactive and mark == repo._bookmarkcurrent:
981 981 bookmarks.unsetcurrent(repo)
982 982 return
983 983 tgt = cur
984 984 if rev:
985 985 tgt = scmutil.revsingle(repo, rev).node()
986 986 checkconflict(repo, mark, cur, force, tgt)
987 987 marks[mark] = tgt
988 988 if not inactive and cur == marks[newact] and not rev:
989 989 bookmarks.setcurrent(repo, newact)
990 990 elif cur != tgt and newact == repo._bookmarkcurrent:
991 991 bookmarks.unsetcurrent(repo)
992 992 marks.write()
993 993
994 994 elif inactive:
995 995 if len(marks) == 0:
996 996 ui.status(_("no bookmarks set\n"))
997 997 elif not repo._bookmarkcurrent:
998 998 ui.status(_("no active bookmark\n"))
999 999 else:
1000 1000 bookmarks.unsetcurrent(repo)
1001 1001 finally:
1002 1002 wlock.release()
1003 1003 else: # show bookmarks
1004 1004 fm = ui.formatter('bookmarks', opts)
1005 1005 hexfn = fm.hexfunc
1006 1006 marks = repo._bookmarks
1007 1007 if len(marks) == 0 and not fm:
1008 1008 ui.status(_("no bookmarks set\n"))
1009 1009 for bmark, n in sorted(marks.iteritems()):
1010 1010 current = repo._bookmarkcurrent
1011 1011 if bmark == current:
1012 1012 prefix, label = '*', 'bookmarks.current'
1013 1013 else:
1014 1014 prefix, label = ' ', ''
1015 1015
1016 1016 fm.startitem()
1017 1017 if not ui.quiet:
1018 1018 fm.plain(' %s ' % prefix, label=label)
1019 1019 fm.write('bookmark', '%s', bmark, label=label)
1020 1020 pad = " " * (25 - encoding.colwidth(bmark))
1021 1021 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1022 1022 repo.changelog.rev(n), hexfn(n), label=label)
1023 1023 fm.data(active=(bmark == current))
1024 1024 fm.plain('\n')
1025 1025 fm.end()
1026 1026
1027 1027 @command('branch',
1028 1028 [('f', 'force', None,
1029 1029 _('set branch name even if it shadows an existing branch')),
1030 1030 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1031 1031 _('[-fC] [NAME]'))
1032 1032 def branch(ui, repo, label=None, **opts):
1033 1033 """set or show the current branch name
1034 1034
1035 1035 .. note::
1036 1036
1037 1037 Branch names are permanent and global. Use :hg:`bookmark` to create a
1038 1038 light-weight bookmark instead. See :hg:`help glossary` for more
1039 1039 information about named branches and bookmarks.
1040 1040
1041 1041 With no argument, show the current branch name. With one argument,
1042 1042 set the working directory branch name (the branch will not exist
1043 1043 in the repository until the next commit). Standard practice
1044 1044 recommends that primary development take place on the 'default'
1045 1045 branch.
1046 1046
1047 1047 Unless -f/--force is specified, branch will not let you set a
1048 1048 branch name that already exists.
1049 1049
1050 1050 Use -C/--clean to reset the working directory branch to that of
1051 1051 the parent of the working directory, negating a previous branch
1052 1052 change.
1053 1053
1054 1054 Use the command :hg:`update` to switch to an existing branch. Use
1055 1055 :hg:`commit --close-branch` to mark this branch as closed.
1056 1056
1057 1057 Returns 0 on success.
1058 1058 """
1059 1059 if label:
1060 1060 label = label.strip()
1061 1061
1062 1062 if not opts.get('clean') and not label:
1063 1063 ui.write("%s\n" % repo.dirstate.branch())
1064 1064 return
1065 1065
1066 1066 wlock = repo.wlock()
1067 1067 try:
1068 1068 if opts.get('clean'):
1069 1069 label = repo[None].p1().branch()
1070 1070 repo.dirstate.setbranch(label)
1071 1071 ui.status(_('reset working directory to branch %s\n') % label)
1072 1072 elif label:
1073 1073 if not opts.get('force') and label in repo.branchmap():
1074 1074 if label not in [p.branch() for p in repo.parents()]:
1075 1075 raise util.Abort(_('a branch of the same name already'
1076 1076 ' exists'),
1077 1077 # i18n: "it" refers to an existing branch
1078 1078 hint=_("use 'hg update' to switch to it"))
1079 1079 scmutil.checknewlabel(repo, label, 'branch')
1080 1080 repo.dirstate.setbranch(label)
1081 1081 ui.status(_('marked working directory as branch %s\n') % label)
1082 1082 ui.status(_('(branches are permanent and global, '
1083 1083 'did you want a bookmark?)\n'))
1084 1084 finally:
1085 1085 wlock.release()
1086 1086
1087 1087 @command('branches',
1088 1088 [('a', 'active', False,
1089 1089 _('show only branches that have unmerged heads (DEPRECATED)')),
1090 1090 ('c', 'closed', False, _('show normal and closed branches')),
1091 1091 ] + formatteropts,
1092 1092 _('[-ac]'))
1093 1093 def branches(ui, repo, active=False, closed=False, **opts):
1094 1094 """list repository named branches
1095 1095
1096 1096 List the repository's named branches, indicating which ones are
1097 1097 inactive. If -c/--closed is specified, also list branches which have
1098 1098 been marked closed (see :hg:`commit --close-branch`).
1099 1099
1100 1100 Use the command :hg:`update` to switch to an existing branch.
1101 1101
1102 1102 Returns 0.
1103 1103 """
1104 1104
1105 1105 fm = ui.formatter('branches', opts)
1106 1106 hexfunc = fm.hexfunc
1107 1107
1108 1108 allheads = set(repo.heads())
1109 1109 branches = []
1110 1110 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1111 1111 isactive = not isclosed and bool(set(heads) & allheads)
1112 1112 branches.append((tag, repo[tip], isactive, not isclosed))
1113 1113 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1114 1114 reverse=True)
1115 1115
1116 1116 for tag, ctx, isactive, isopen in branches:
1117 1117 if active and not isactive:
1118 1118 continue
1119 1119 if isactive:
1120 1120 label = 'branches.active'
1121 1121 notice = ''
1122 1122 elif not isopen:
1123 1123 if not closed:
1124 1124 continue
1125 1125 label = 'branches.closed'
1126 1126 notice = _(' (closed)')
1127 1127 else:
1128 1128 label = 'branches.inactive'
1129 1129 notice = _(' (inactive)')
1130 1130 current = (tag == repo.dirstate.branch())
1131 1131 if current:
1132 1132 label = 'branches.current'
1133 1133
1134 1134 fm.startitem()
1135 1135 fm.write('branch', '%s', tag, label=label)
1136 1136 rev = ctx.rev()
1137 1137 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1138 1138 fmt = ' ' * padsize + ' %d:%s'
1139 1139 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1140 1140 label='log.changeset changeset.%s' % ctx.phasestr())
1141 1141 fm.data(active=isactive, closed=not isopen, current=current)
1142 1142 if not ui.quiet:
1143 1143 fm.plain(notice)
1144 1144 fm.plain('\n')
1145 1145 fm.end()
1146 1146
1147 1147 @command('bundle',
1148 1148 [('f', 'force', None, _('run even when the destination is unrelated')),
1149 1149 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1150 1150 _('REV')),
1151 1151 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1152 1152 _('BRANCH')),
1153 1153 ('', 'base', [],
1154 1154 _('a base changeset assumed to be available at the destination'),
1155 1155 _('REV')),
1156 1156 ('a', 'all', None, _('bundle all changesets in the repository')),
1157 1157 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1158 1158 ] + remoteopts,
1159 1159 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1160 1160 def bundle(ui, repo, fname, dest=None, **opts):
1161 1161 """create a changegroup file
1162 1162
1163 1163 Generate a compressed changegroup file collecting changesets not
1164 1164 known to be in another repository.
1165 1165
1166 1166 If you omit the destination repository, then hg assumes the
1167 1167 destination will have all the nodes you specify with --base
1168 1168 parameters. To create a bundle containing all changesets, use
1169 1169 -a/--all (or --base null).
1170 1170
1171 1171 You can change compression method with the -t/--type option.
1172 1172 The available compression methods are: none, bzip2, and
1173 1173 gzip (by default, bundles are compressed using bzip2).
1174 1174
1175 1175 The bundle file can then be transferred using conventional means
1176 1176 and applied to another repository with the unbundle or pull
1177 1177 command. This is useful when direct push and pull are not
1178 1178 available or when exporting an entire repository is undesirable.
1179 1179
1180 1180 Applying bundles preserves all changeset contents including
1181 1181 permissions, copy/rename information, and revision history.
1182 1182
1183 1183 Returns 0 on success, 1 if no changes found.
1184 1184 """
1185 1185 revs = None
1186 1186 if 'rev' in opts:
1187 1187 revs = scmutil.revrange(repo, opts['rev'])
1188 1188
1189 1189 bundletype = opts.get('type', 'bzip2').lower()
1190 1190 btypes = {'none': 'HG10UN',
1191 1191 'bzip2': 'HG10BZ',
1192 1192 'gzip': 'HG10GZ',
1193 1193 'bundle2': 'HG2Y'}
1194 1194 bundletype = btypes.get(bundletype)
1195 1195 if bundletype not in changegroup.bundletypes:
1196 1196 raise util.Abort(_('unknown bundle type specified with --type'))
1197 1197
1198 1198 if opts.get('all'):
1199 1199 base = ['null']
1200 1200 else:
1201 1201 base = scmutil.revrange(repo, opts.get('base'))
1202 1202 # TODO: get desired bundlecaps from command line.
1203 1203 bundlecaps = None
1204 1204 if base:
1205 1205 if dest:
1206 1206 raise util.Abort(_("--base is incompatible with specifying "
1207 1207 "a destination"))
1208 1208 common = [repo.lookup(rev) for rev in base]
1209 1209 heads = revs and map(repo.lookup, revs) or revs
1210 1210 cg = changegroup.getchangegroup(repo, 'bundle', heads=heads,
1211 1211 common=common, bundlecaps=bundlecaps)
1212 1212 outgoing = None
1213 1213 else:
1214 1214 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1215 1215 dest, branches = hg.parseurl(dest, opts.get('branch'))
1216 1216 other = hg.peer(repo, opts, dest)
1217 1217 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1218 1218 heads = revs and map(repo.lookup, revs) or revs
1219 1219 outgoing = discovery.findcommonoutgoing(repo, other,
1220 1220 onlyheads=heads,
1221 1221 force=opts.get('force'),
1222 1222 portable=True)
1223 1223 cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
1224 1224 bundlecaps)
1225 1225 if not cg:
1226 1226 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1227 1227 return 1
1228 1228
1229 1229 changegroup.writebundle(ui, cg, fname, bundletype)
1230 1230
1231 1231 @command('cat',
1232 1232 [('o', 'output', '',
1233 1233 _('print output to file with formatted name'), _('FORMAT')),
1234 1234 ('r', 'rev', '', _('print the given revision'), _('REV')),
1235 1235 ('', 'decode', None, _('apply any matching decode filter')),
1236 1236 ] + walkopts,
1237 1237 _('[OPTION]... FILE...'),
1238 1238 inferrepo=True)
1239 1239 def cat(ui, repo, file1, *pats, **opts):
1240 1240 """output the current or given revision of files
1241 1241
1242 1242 Print the specified files as they were at the given revision. If
1243 1243 no revision is given, the parent of the working directory is used.
1244 1244
1245 1245 Output may be to a file, in which case the name of the file is
1246 1246 given using a format string. The formatting rules as follows:
1247 1247
1248 1248 :``%%``: literal "%" character
1249 1249 :``%s``: basename of file being printed
1250 1250 :``%d``: dirname of file being printed, or '.' if in repository root
1251 1251 :``%p``: root-relative path name of file being printed
1252 1252 :``%H``: changeset hash (40 hexadecimal digits)
1253 1253 :``%R``: changeset revision number
1254 1254 :``%h``: short-form changeset hash (12 hexadecimal digits)
1255 1255 :``%r``: zero-padded changeset revision number
1256 1256 :``%b``: basename of the exporting repository
1257 1257
1258 1258 Returns 0 on success.
1259 1259 """
1260 1260 ctx = scmutil.revsingle(repo, opts.get('rev'))
1261 1261 m = scmutil.match(ctx, (file1,) + pats, opts)
1262 1262
1263 1263 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1264 1264
1265 1265 @command('^clone',
1266 1266 [('U', 'noupdate', None, _('the clone will include an empty working '
1267 1267 'directory (only a repository)')),
1268 1268 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1269 1269 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1270 1270 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1271 1271 ('', 'pull', None, _('use pull protocol to copy metadata')),
1272 1272 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1273 1273 ] + remoteopts,
1274 1274 _('[OPTION]... SOURCE [DEST]'),
1275 1275 norepo=True)
1276 1276 def clone(ui, source, dest=None, **opts):
1277 1277 """make a copy of an existing repository
1278 1278
1279 1279 Create a copy of an existing repository in a new directory.
1280 1280
1281 1281 If no destination directory name is specified, it defaults to the
1282 1282 basename of the source.
1283 1283
1284 1284 The location of the source is added to the new repository's
1285 1285 ``.hg/hgrc`` file, as the default to be used for future pulls.
1286 1286
1287 1287 Only local paths and ``ssh://`` URLs are supported as
1288 1288 destinations. For ``ssh://`` destinations, no working directory or
1289 1289 ``.hg/hgrc`` will be created on the remote side.
1290 1290
1291 1291 To pull only a subset of changesets, specify one or more revisions
1292 1292 identifiers with -r/--rev or branches with -b/--branch. The
1293 1293 resulting clone will contain only the specified changesets and
1294 1294 their ancestors. These options (or 'clone src#rev dest') imply
1295 1295 --pull, even for local source repositories. Note that specifying a
1296 1296 tag will include the tagged changeset but not the changeset
1297 1297 containing the tag.
1298 1298
1299 1299 If the source repository has a bookmark called '@' set, that
1300 1300 revision will be checked out in the new repository by default.
1301 1301
1302 1302 To check out a particular version, use -u/--update, or
1303 1303 -U/--noupdate to create a clone with no working directory.
1304 1304
1305 1305 .. container:: verbose
1306 1306
1307 1307 For efficiency, hardlinks are used for cloning whenever the
1308 1308 source and destination are on the same filesystem (note this
1309 1309 applies only to the repository data, not to the working
1310 1310 directory). Some filesystems, such as AFS, implement hardlinking
1311 1311 incorrectly, but do not report errors. In these cases, use the
1312 1312 --pull option to avoid hardlinking.
1313 1313
1314 1314 In some cases, you can clone repositories and the working
1315 1315 directory using full hardlinks with ::
1316 1316
1317 1317 $ cp -al REPO REPOCLONE
1318 1318
1319 1319 This is the fastest way to clone, but it is not always safe. The
1320 1320 operation is not atomic (making sure REPO is not modified during
1321 1321 the operation is up to you) and you have to make sure your
1322 1322 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1323 1323 so). Also, this is not compatible with certain extensions that
1324 1324 place their metadata under the .hg directory, such as mq.
1325 1325
1326 1326 Mercurial will update the working directory to the first applicable
1327 1327 revision from this list:
1328 1328
1329 1329 a) null if -U or the source repository has no changesets
1330 1330 b) if -u . and the source repository is local, the first parent of
1331 1331 the source repository's working directory
1332 1332 c) the changeset specified with -u (if a branch name, this means the
1333 1333 latest head of that branch)
1334 1334 d) the changeset specified with -r
1335 1335 e) the tipmost head specified with -b
1336 1336 f) the tipmost head specified with the url#branch source syntax
1337 1337 g) the revision marked with the '@' bookmark, if present
1338 1338 h) the tipmost head of the default branch
1339 1339 i) tip
1340 1340
1341 1341 Examples:
1342 1342
1343 1343 - clone a remote repository to a new directory named hg/::
1344 1344
1345 1345 hg clone http://selenic.com/hg
1346 1346
1347 1347 - create a lightweight local clone::
1348 1348
1349 1349 hg clone project/ project-feature/
1350 1350
1351 1351 - clone from an absolute path on an ssh server (note double-slash)::
1352 1352
1353 1353 hg clone ssh://user@server//home/projects/alpha/
1354 1354
1355 1355 - do a high-speed clone over a LAN while checking out a
1356 1356 specified version::
1357 1357
1358 1358 hg clone --uncompressed http://server/repo -u 1.5
1359 1359
1360 1360 - create a repository without changesets after a particular revision::
1361 1361
1362 1362 hg clone -r 04e544 experimental/ good/
1363 1363
1364 1364 - clone (and track) a particular named branch::
1365 1365
1366 1366 hg clone http://selenic.com/hg#stable
1367 1367
1368 1368 See :hg:`help urls` for details on specifying URLs.
1369 1369
1370 1370 Returns 0 on success.
1371 1371 """
1372 1372 if opts.get('noupdate') and opts.get('updaterev'):
1373 1373 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1374 1374
1375 1375 r = hg.clone(ui, opts, source, dest,
1376 1376 pull=opts.get('pull'),
1377 1377 stream=opts.get('uncompressed'),
1378 1378 rev=opts.get('rev'),
1379 1379 update=opts.get('updaterev') or not opts.get('noupdate'),
1380 1380 branch=opts.get('branch'))
1381 1381
1382 1382 return r is None
1383 1383
1384 1384 @command('^commit|ci',
1385 1385 [('A', 'addremove', None,
1386 1386 _('mark new/missing files as added/removed before committing')),
1387 1387 ('', 'close-branch', None,
1388 1388 _('mark a branch as closed, hiding it from the branch list')),
1389 1389 ('', 'amend', None, _('amend the parent of the working directory')),
1390 1390 ('s', 'secret', None, _('use the secret phase for committing')),
1391 1391 ('e', 'edit', None, _('invoke editor on commit messages')),
1392 1392 ('i', 'interactive', None, _('use interactive mode')),
1393 1393 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1394 1394 _('[OPTION]... [FILE]...'),
1395 1395 inferrepo=True)
1396 1396 def commit(ui, repo, *pats, **opts):
1397 1397 """commit the specified files or all outstanding changes
1398 1398
1399 1399 Commit changes to the given files into the repository. Unlike a
1400 1400 centralized SCM, this operation is a local operation. See
1401 1401 :hg:`push` for a way to actively distribute your changes.
1402 1402
1403 1403 If a list of files is omitted, all changes reported by :hg:`status`
1404 1404 will be committed.
1405 1405
1406 1406 If you are committing the result of a merge, do not provide any
1407 1407 filenames or -I/-X filters.
1408 1408
1409 1409 If no commit message is specified, Mercurial starts your
1410 1410 configured editor where you can enter a message. In case your
1411 1411 commit fails, you will find a backup of your message in
1412 1412 ``.hg/last-message.txt``.
1413 1413
1414 1414 The --amend flag can be used to amend the parent of the
1415 1415 working directory with a new commit that contains the changes
1416 1416 in the parent in addition to those currently reported by :hg:`status`,
1417 1417 if there are any. The old commit is stored in a backup bundle in
1418 1418 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1419 1419 on how to restore it).
1420 1420
1421 1421 Message, user and date are taken from the amended commit unless
1422 1422 specified. When a message isn't specified on the command line,
1423 1423 the editor will open with the message of the amended commit.
1424 1424
1425 1425 It is not possible to amend public changesets (see :hg:`help phases`)
1426 1426 or changesets that have children.
1427 1427
1428 1428 See :hg:`help dates` for a list of formats valid for -d/--date.
1429 1429
1430 1430 Returns 0 on success, 1 if nothing changed.
1431 1431 """
1432 1432 if opts.get('interactive'):
1433 1433 opts.pop('interactive')
1434 1434 cmdutil.dorecord(ui, repo, commit, 'commit', False,
1435 1435 cmdutil.recordfilter, *pats, **opts)
1436 1436 return
1437 1437
1438 1438 if opts.get('subrepos'):
1439 1439 if opts.get('amend'):
1440 1440 raise util.Abort(_('cannot amend with --subrepos'))
1441 1441 # Let --subrepos on the command line override config setting.
1442 1442 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1443 1443
1444 1444 cmdutil.checkunfinished(repo, commit=True)
1445 1445
1446 1446 branch = repo[None].branch()
1447 1447 bheads = repo.branchheads(branch)
1448 1448
1449 1449 extra = {}
1450 1450 if opts.get('close_branch'):
1451 1451 extra['close'] = 1
1452 1452
1453 1453 if not bheads:
1454 1454 raise util.Abort(_('can only close branch heads'))
1455 1455 elif opts.get('amend'):
1456 1456 if repo.parents()[0].p1().branch() != branch and \
1457 1457 repo.parents()[0].p2().branch() != branch:
1458 1458 raise util.Abort(_('can only close branch heads'))
1459 1459
1460 1460 if opts.get('amend'):
1461 1461 if ui.configbool('ui', 'commitsubrepos'):
1462 1462 raise util.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1463 1463
1464 1464 old = repo['.']
1465 1465 if not old.mutable():
1466 1466 raise util.Abort(_('cannot amend public changesets'))
1467 1467 if len(repo[None].parents()) > 1:
1468 1468 raise util.Abort(_('cannot amend while merging'))
1469 1469 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1470 1470 if not allowunstable and old.children():
1471 1471 raise util.Abort(_('cannot amend changeset with children'))
1472 1472
1473 1473 # commitfunc is used only for temporary amend commit by cmdutil.amend
1474 1474 def commitfunc(ui, repo, message, match, opts):
1475 1475 return repo.commit(message,
1476 1476 opts.get('user') or old.user(),
1477 1477 opts.get('date') or old.date(),
1478 1478 match,
1479 1479 extra=extra)
1480 1480
1481 1481 current = repo._bookmarkcurrent
1482 1482 marks = old.bookmarks()
1483 1483 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1484 1484 if node == old.node():
1485 1485 ui.status(_("nothing changed\n"))
1486 1486 return 1
1487 1487 elif marks:
1488 1488 ui.debug('moving bookmarks %r from %s to %s\n' %
1489 1489 (marks, old.hex(), hex(node)))
1490 1490 newmarks = repo._bookmarks
1491 1491 for bm in marks:
1492 1492 newmarks[bm] = node
1493 1493 if bm == current:
1494 1494 bookmarks.setcurrent(repo, bm)
1495 1495 newmarks.write()
1496 1496 else:
1497 1497 def commitfunc(ui, repo, message, match, opts):
1498 1498 backup = ui.backupconfig('phases', 'new-commit')
1499 1499 baseui = repo.baseui
1500 1500 basebackup = baseui.backupconfig('phases', 'new-commit')
1501 1501 try:
1502 1502 if opts.get('secret'):
1503 1503 ui.setconfig('phases', 'new-commit', 'secret', 'commit')
1504 1504 # Propagate to subrepos
1505 1505 baseui.setconfig('phases', 'new-commit', 'secret', 'commit')
1506 1506
1507 1507 editform = cmdutil.mergeeditform(repo[None], 'commit.normal')
1508 1508 editor = cmdutil.getcommiteditor(editform=editform, **opts)
1509 1509 return repo.commit(message, opts.get('user'), opts.get('date'),
1510 1510 match,
1511 1511 editor=editor,
1512 1512 extra=extra)
1513 1513 finally:
1514 1514 ui.restoreconfig(backup)
1515 1515 repo.baseui.restoreconfig(basebackup)
1516 1516
1517 1517
1518 1518 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1519 1519
1520 1520 if not node:
1521 1521 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1522 1522 if stat[3]:
1523 1523 ui.status(_("nothing changed (%d missing files, see "
1524 1524 "'hg status')\n") % len(stat[3]))
1525 1525 else:
1526 1526 ui.status(_("nothing changed\n"))
1527 1527 return 1
1528 1528
1529 1529 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1530 1530
1531 1531 @command('config|showconfig|debugconfig',
1532 1532 [('u', 'untrusted', None, _('show untrusted configuration options')),
1533 1533 ('e', 'edit', None, _('edit user config')),
1534 1534 ('l', 'local', None, _('edit repository config')),
1535 1535 ('g', 'global', None, _('edit global config'))],
1536 1536 _('[-u] [NAME]...'),
1537 1537 optionalrepo=True)
1538 1538 def config(ui, repo, *values, **opts):
1539 1539 """show combined config settings from all hgrc files
1540 1540
1541 1541 With no arguments, print names and values of all config items.
1542 1542
1543 1543 With one argument of the form section.name, print just the value
1544 1544 of that config item.
1545 1545
1546 1546 With multiple arguments, print names and values of all config
1547 1547 items with matching section names.
1548 1548
1549 1549 With --edit, start an editor on the user-level config file. With
1550 1550 --global, edit the system-wide config file. With --local, edit the
1551 1551 repository-level config file.
1552 1552
1553 1553 With --debug, the source (filename and line number) is printed
1554 1554 for each config item.
1555 1555
1556 1556 See :hg:`help config` for more information about config files.
1557 1557
1558 1558 Returns 0 on success, 1 if NAME does not exist.
1559 1559
1560 1560 """
1561 1561
1562 1562 if opts.get('edit') or opts.get('local') or opts.get('global'):
1563 1563 if opts.get('local') and opts.get('global'):
1564 1564 raise util.Abort(_("can't use --local and --global together"))
1565 1565
1566 1566 if opts.get('local'):
1567 1567 if not repo:
1568 1568 raise util.Abort(_("can't use --local outside a repository"))
1569 1569 paths = [repo.join('hgrc')]
1570 1570 elif opts.get('global'):
1571 1571 paths = scmutil.systemrcpath()
1572 1572 else:
1573 1573 paths = scmutil.userrcpath()
1574 1574
1575 1575 for f in paths:
1576 1576 if os.path.exists(f):
1577 1577 break
1578 1578 else:
1579 1579 if opts.get('global'):
1580 1580 samplehgrc = uimod.samplehgrcs['global']
1581 1581 elif opts.get('local'):
1582 1582 samplehgrc = uimod.samplehgrcs['local']
1583 1583 else:
1584 1584 samplehgrc = uimod.samplehgrcs['user']
1585 1585
1586 1586 f = paths[0]
1587 1587 fp = open(f, "w")
1588 1588 fp.write(samplehgrc)
1589 1589 fp.close()
1590 1590
1591 1591 editor = ui.geteditor()
1592 1592 ui.system("%s \"%s\"" % (editor, f),
1593 1593 onerr=util.Abort, errprefix=_("edit failed"))
1594 1594 return
1595 1595
1596 1596 for f in scmutil.rcpath():
1597 1597 ui.debug('read config from: %s\n' % f)
1598 1598 untrusted = bool(opts.get('untrusted'))
1599 1599 if values:
1600 1600 sections = [v for v in values if '.' not in v]
1601 1601 items = [v for v in values if '.' in v]
1602 1602 if len(items) > 1 or items and sections:
1603 1603 raise util.Abort(_('only one config item permitted'))
1604 1604 matched = False
1605 1605 for section, name, value in ui.walkconfig(untrusted=untrusted):
1606 1606 value = str(value).replace('\n', '\\n')
1607 1607 sectname = section + '.' + name
1608 1608 if values:
1609 1609 for v in values:
1610 1610 if v == section:
1611 1611 ui.debug('%s: ' %
1612 1612 ui.configsource(section, name, untrusted))
1613 1613 ui.write('%s=%s\n' % (sectname, value))
1614 1614 matched = True
1615 1615 elif v == sectname:
1616 1616 ui.debug('%s: ' %
1617 1617 ui.configsource(section, name, untrusted))
1618 1618 ui.write(value, '\n')
1619 1619 matched = True
1620 1620 else:
1621 1621 ui.debug('%s: ' %
1622 1622 ui.configsource(section, name, untrusted))
1623 1623 ui.write('%s=%s\n' % (sectname, value))
1624 1624 matched = True
1625 1625 if matched:
1626 1626 return 0
1627 1627 return 1
1628 1628
1629 1629 @command('copy|cp',
1630 1630 [('A', 'after', None, _('record a copy that has already occurred')),
1631 1631 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1632 1632 ] + walkopts + dryrunopts,
1633 1633 _('[OPTION]... [SOURCE]... DEST'))
1634 1634 def copy(ui, repo, *pats, **opts):
1635 1635 """mark files as copied for the next commit
1636 1636
1637 1637 Mark dest as having copies of source files. If dest is a
1638 1638 directory, copies are put in that directory. If dest is a file,
1639 1639 the source must be a single file.
1640 1640
1641 1641 By default, this command copies the contents of files as they
1642 1642 exist in the working directory. If invoked with -A/--after, the
1643 1643 operation is recorded, but no copying is performed.
1644 1644
1645 1645 This command takes effect with the next commit. To undo a copy
1646 1646 before that, see :hg:`revert`.
1647 1647
1648 1648 Returns 0 on success, 1 if errors are encountered.
1649 1649 """
1650 1650 wlock = repo.wlock(False)
1651 1651 try:
1652 1652 return cmdutil.copy(ui, repo, pats, opts)
1653 1653 finally:
1654 1654 wlock.release()
1655 1655
1656 1656 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
1657 1657 def debugancestor(ui, repo, *args):
1658 1658 """find the ancestor revision of two revisions in a given index"""
1659 1659 if len(args) == 3:
1660 1660 index, rev1, rev2 = args
1661 1661 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1662 1662 lookup = r.lookup
1663 1663 elif len(args) == 2:
1664 1664 if not repo:
1665 1665 raise util.Abort(_("there is no Mercurial repository here "
1666 1666 "(.hg not found)"))
1667 1667 rev1, rev2 = args
1668 1668 r = repo.changelog
1669 1669 lookup = repo.lookup
1670 1670 else:
1671 1671 raise util.Abort(_('either two or three arguments required'))
1672 1672 a = r.ancestor(lookup(rev1), lookup(rev2))
1673 1673 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1674 1674
1675 1675 @command('debugbuilddag',
1676 1676 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1677 1677 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1678 1678 ('n', 'new-file', None, _('add new file at each rev'))],
1679 1679 _('[OPTION]... [TEXT]'))
1680 1680 def debugbuilddag(ui, repo, text=None,
1681 1681 mergeable_file=False,
1682 1682 overwritten_file=False,
1683 1683 new_file=False):
1684 1684 """builds a repo with a given DAG from scratch in the current empty repo
1685 1685
1686 1686 The description of the DAG is read from stdin if not given on the
1687 1687 command line.
1688 1688
1689 1689 Elements:
1690 1690
1691 1691 - "+n" is a linear run of n nodes based on the current default parent
1692 1692 - "." is a single node based on the current default parent
1693 1693 - "$" resets the default parent to null (implied at the start);
1694 1694 otherwise the default parent is always the last node created
1695 1695 - "<p" sets the default parent to the backref p
1696 1696 - "*p" is a fork at parent p, which is a backref
1697 1697 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1698 1698 - "/p2" is a merge of the preceding node and p2
1699 1699 - ":tag" defines a local tag for the preceding node
1700 1700 - "@branch" sets the named branch for subsequent nodes
1701 1701 - "#...\\n" is a comment up to the end of the line
1702 1702
1703 1703 Whitespace between the above elements is ignored.
1704 1704
1705 1705 A backref is either
1706 1706
1707 1707 - a number n, which references the node curr-n, where curr is the current
1708 1708 node, or
1709 1709 - the name of a local tag you placed earlier using ":tag", or
1710 1710 - empty to denote the default parent.
1711 1711
1712 1712 All string valued-elements are either strictly alphanumeric, or must
1713 1713 be enclosed in double quotes ("..."), with "\\" as escape character.
1714 1714 """
1715 1715
1716 1716 if text is None:
1717 1717 ui.status(_("reading DAG from stdin\n"))
1718 1718 text = ui.fin.read()
1719 1719
1720 1720 cl = repo.changelog
1721 1721 if len(cl) > 0:
1722 1722 raise util.Abort(_('repository is not empty'))
1723 1723
1724 1724 # determine number of revs in DAG
1725 1725 total = 0
1726 1726 for type, data in dagparser.parsedag(text):
1727 1727 if type == 'n':
1728 1728 total += 1
1729 1729
1730 1730 if mergeable_file:
1731 1731 linesperrev = 2
1732 1732 # make a file with k lines per rev
1733 1733 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1734 1734 initialmergedlines.append("")
1735 1735
1736 1736 tags = []
1737 1737
1738 1738 lock = tr = None
1739 1739 try:
1740 1740 lock = repo.lock()
1741 1741 tr = repo.transaction("builddag")
1742 1742
1743 1743 at = -1
1744 1744 atbranch = 'default'
1745 1745 nodeids = []
1746 1746 id = 0
1747 1747 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1748 1748 for type, data in dagparser.parsedag(text):
1749 1749 if type == 'n':
1750 1750 ui.note(('node %s\n' % str(data)))
1751 1751 id, ps = data
1752 1752
1753 1753 files = []
1754 1754 fctxs = {}
1755 1755
1756 1756 p2 = None
1757 1757 if mergeable_file:
1758 1758 fn = "mf"
1759 1759 p1 = repo[ps[0]]
1760 1760 if len(ps) > 1:
1761 1761 p2 = repo[ps[1]]
1762 1762 pa = p1.ancestor(p2)
1763 1763 base, local, other = [x[fn].data() for x in (pa, p1,
1764 1764 p2)]
1765 1765 m3 = simplemerge.Merge3Text(base, local, other)
1766 1766 ml = [l.strip() for l in m3.merge_lines()]
1767 1767 ml.append("")
1768 1768 elif at > 0:
1769 1769 ml = p1[fn].data().split("\n")
1770 1770 else:
1771 1771 ml = initialmergedlines
1772 1772 ml[id * linesperrev] += " r%i" % id
1773 1773 mergedtext = "\n".join(ml)
1774 1774 files.append(fn)
1775 1775 fctxs[fn] = context.memfilectx(repo, fn, mergedtext)
1776 1776
1777 1777 if overwritten_file:
1778 1778 fn = "of"
1779 1779 files.append(fn)
1780 1780 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
1781 1781
1782 1782 if new_file:
1783 1783 fn = "nf%i" % id
1784 1784 files.append(fn)
1785 1785 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
1786 1786 if len(ps) > 1:
1787 1787 if not p2:
1788 1788 p2 = repo[ps[1]]
1789 1789 for fn in p2:
1790 1790 if fn.startswith("nf"):
1791 1791 files.append(fn)
1792 1792 fctxs[fn] = p2[fn]
1793 1793
1794 1794 def fctxfn(repo, cx, path):
1795 1795 return fctxs.get(path)
1796 1796
1797 1797 if len(ps) == 0 or ps[0] < 0:
1798 1798 pars = [None, None]
1799 1799 elif len(ps) == 1:
1800 1800 pars = [nodeids[ps[0]], None]
1801 1801 else:
1802 1802 pars = [nodeids[p] for p in ps]
1803 1803 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1804 1804 date=(id, 0),
1805 1805 user="debugbuilddag",
1806 1806 extra={'branch': atbranch})
1807 1807 nodeid = repo.commitctx(cx)
1808 1808 nodeids.append(nodeid)
1809 1809 at = id
1810 1810 elif type == 'l':
1811 1811 id, name = data
1812 1812 ui.note(('tag %s\n' % name))
1813 1813 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1814 1814 elif type == 'a':
1815 1815 ui.note(('branch %s\n' % data))
1816 1816 atbranch = data
1817 1817 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1818 1818 tr.close()
1819 1819
1820 1820 if tags:
1821 1821 repo.vfs.write("localtags", "".join(tags))
1822 1822 finally:
1823 1823 ui.progress(_('building'), None)
1824 1824 release(tr, lock)
1825 1825
1826 1826 @command('debugbundle',
1827 1827 [('a', 'all', None, _('show all details'))],
1828 1828 _('FILE'),
1829 1829 norepo=True)
1830 1830 def debugbundle(ui, bundlepath, all=None, **opts):
1831 1831 """lists the contents of a bundle"""
1832 1832 f = hg.openpath(ui, bundlepath)
1833 1833 try:
1834 1834 gen = exchange.readbundle(ui, f, bundlepath)
1835 1835 if isinstance(gen, bundle2.unbundle20):
1836 1836 return _debugbundle2(ui, gen, all=all, **opts)
1837 1837 if all:
1838 1838 ui.write(("format: id, p1, p2, cset, delta base, len(delta)\n"))
1839 1839
1840 1840 def showchunks(named):
1841 1841 ui.write("\n%s\n" % named)
1842 1842 chain = None
1843 1843 while True:
1844 1844 chunkdata = gen.deltachunk(chain)
1845 1845 if not chunkdata:
1846 1846 break
1847 1847 node = chunkdata['node']
1848 1848 p1 = chunkdata['p1']
1849 1849 p2 = chunkdata['p2']
1850 1850 cs = chunkdata['cs']
1851 1851 deltabase = chunkdata['deltabase']
1852 1852 delta = chunkdata['delta']
1853 1853 ui.write("%s %s %s %s %s %s\n" %
1854 1854 (hex(node), hex(p1), hex(p2),
1855 1855 hex(cs), hex(deltabase), len(delta)))
1856 1856 chain = node
1857 1857
1858 1858 chunkdata = gen.changelogheader()
1859 1859 showchunks("changelog")
1860 1860 chunkdata = gen.manifestheader()
1861 1861 showchunks("manifest")
1862 1862 while True:
1863 1863 chunkdata = gen.filelogheader()
1864 1864 if not chunkdata:
1865 1865 break
1866 1866 fname = chunkdata['filename']
1867 1867 showchunks(fname)
1868 1868 else:
1869 1869 if isinstance(gen, bundle2.unbundle20):
1870 1870 raise util.Abort(_('use debugbundle2 for this file'))
1871 1871 chunkdata = gen.changelogheader()
1872 1872 chain = None
1873 1873 while True:
1874 1874 chunkdata = gen.deltachunk(chain)
1875 1875 if not chunkdata:
1876 1876 break
1877 1877 node = chunkdata['node']
1878 1878 ui.write("%s\n" % hex(node))
1879 1879 chain = node
1880 1880 finally:
1881 1881 f.close()
1882 1882
1883 1883 def _debugbundle2(ui, gen, **opts):
1884 1884 """lists the contents of a bundle2"""
1885 1885 if not isinstance(gen, bundle2.unbundle20):
1886 1886 raise util.Abort(_('not a bundle2 file'))
1887 1887 ui.write(('Stream params: %s\n' % repr(gen.params)))
1888 1888 for part in gen.iterparts():
1889 1889 ui.write('%s -- %r\n' % (part.type, repr(part.params)))
1890 1890 if part.type == 'b2x:changegroup':
1891 1891 version = part.params.get('version', '01')
1892 1892 cg = changegroup.packermap[version][1](part, 'UN')
1893 1893 chunkdata = cg.changelogheader()
1894 1894 chain = None
1895 1895 while True:
1896 1896 chunkdata = cg.deltachunk(chain)
1897 1897 if not chunkdata:
1898 1898 break
1899 1899 node = chunkdata['node']
1900 1900 ui.write(" %s\n" % hex(node))
1901 1901 chain = node
1902 1902
1903 1903 @command('debugcheckstate', [], '')
1904 1904 def debugcheckstate(ui, repo):
1905 1905 """validate the correctness of the current dirstate"""
1906 1906 parent1, parent2 = repo.dirstate.parents()
1907 1907 m1 = repo[parent1].manifest()
1908 1908 m2 = repo[parent2].manifest()
1909 1909 errors = 0
1910 1910 for f in repo.dirstate:
1911 1911 state = repo.dirstate[f]
1912 1912 if state in "nr" and f not in m1:
1913 1913 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1914 1914 errors += 1
1915 1915 if state in "a" and f in m1:
1916 1916 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1917 1917 errors += 1
1918 1918 if state in "m" and f not in m1 and f not in m2:
1919 1919 ui.warn(_("%s in state %s, but not in either manifest\n") %
1920 1920 (f, state))
1921 1921 errors += 1
1922 1922 for f in m1:
1923 1923 state = repo.dirstate[f]
1924 1924 if state not in "nrm":
1925 1925 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1926 1926 errors += 1
1927 1927 if errors:
1928 1928 error = _(".hg/dirstate inconsistent with current parent's manifest")
1929 1929 raise util.Abort(error)
1930 1930
1931 1931 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1932 1932 def debugcommands(ui, cmd='', *args):
1933 1933 """list all available commands and options"""
1934 1934 for cmd, vals in sorted(table.iteritems()):
1935 1935 cmd = cmd.split('|')[0].strip('^')
1936 1936 opts = ', '.join([i[1] for i in vals[1]])
1937 1937 ui.write('%s: %s\n' % (cmd, opts))
1938 1938
1939 1939 @command('debugcomplete',
1940 1940 [('o', 'options', None, _('show the command options'))],
1941 1941 _('[-o] CMD'),
1942 1942 norepo=True)
1943 1943 def debugcomplete(ui, cmd='', **opts):
1944 1944 """returns the completion list associated with the given command"""
1945 1945
1946 1946 if opts.get('options'):
1947 1947 options = []
1948 1948 otables = [globalopts]
1949 1949 if cmd:
1950 1950 aliases, entry = cmdutil.findcmd(cmd, table, False)
1951 1951 otables.append(entry[1])
1952 1952 for t in otables:
1953 1953 for o in t:
1954 1954 if "(DEPRECATED)" in o[3]:
1955 1955 continue
1956 1956 if o[0]:
1957 1957 options.append('-%s' % o[0])
1958 1958 options.append('--%s' % o[1])
1959 1959 ui.write("%s\n" % "\n".join(options))
1960 1960 return
1961 1961
1962 1962 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1963 1963 if ui.verbose:
1964 1964 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1965 1965 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1966 1966
1967 1967 @command('debugdag',
1968 1968 [('t', 'tags', None, _('use tags as labels')),
1969 1969 ('b', 'branches', None, _('annotate with branch names')),
1970 1970 ('', 'dots', None, _('use dots for runs')),
1971 1971 ('s', 'spaces', None, _('separate elements by spaces'))],
1972 1972 _('[OPTION]... [FILE [REV]...]'),
1973 1973 optionalrepo=True)
1974 1974 def debugdag(ui, repo, file_=None, *revs, **opts):
1975 1975 """format the changelog or an index DAG as a concise textual description
1976 1976
1977 1977 If you pass a revlog index, the revlog's DAG is emitted. If you list
1978 1978 revision numbers, they get labeled in the output as rN.
1979 1979
1980 1980 Otherwise, the changelog DAG of the current repo is emitted.
1981 1981 """
1982 1982 spaces = opts.get('spaces')
1983 1983 dots = opts.get('dots')
1984 1984 if file_:
1985 1985 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1986 1986 revs = set((int(r) for r in revs))
1987 1987 def events():
1988 1988 for r in rlog:
1989 1989 yield 'n', (r, list(p for p in rlog.parentrevs(r)
1990 1990 if p != -1))
1991 1991 if r in revs:
1992 1992 yield 'l', (r, "r%i" % r)
1993 1993 elif repo:
1994 1994 cl = repo.changelog
1995 1995 tags = opts.get('tags')
1996 1996 branches = opts.get('branches')
1997 1997 if tags:
1998 1998 labels = {}
1999 1999 for l, n in repo.tags().items():
2000 2000 labels.setdefault(cl.rev(n), []).append(l)
2001 2001 def events():
2002 2002 b = "default"
2003 2003 for r in cl:
2004 2004 if branches:
2005 2005 newb = cl.read(cl.node(r))[5]['branch']
2006 2006 if newb != b:
2007 2007 yield 'a', newb
2008 2008 b = newb
2009 2009 yield 'n', (r, list(p for p in cl.parentrevs(r)
2010 2010 if p != -1))
2011 2011 if tags:
2012 2012 ls = labels.get(r)
2013 2013 if ls:
2014 2014 for l in ls:
2015 2015 yield 'l', (r, l)
2016 2016 else:
2017 2017 raise util.Abort(_('need repo for changelog dag'))
2018 2018
2019 2019 for line in dagparser.dagtextlines(events(),
2020 2020 addspaces=spaces,
2021 2021 wraplabels=True,
2022 2022 wrapannotations=True,
2023 2023 wrapnonlinear=dots,
2024 2024 usedots=dots,
2025 2025 maxlinewidth=70):
2026 2026 ui.write(line)
2027 2027 ui.write("\n")
2028 2028
2029 2029 @command('debugdata',
2030 2030 [('c', 'changelog', False, _('open changelog')),
2031 2031 ('m', 'manifest', False, _('open manifest'))],
2032 2032 _('-c|-m|FILE REV'))
2033 2033 def debugdata(ui, repo, file_, rev=None, **opts):
2034 2034 """dump the contents of a data file revision"""
2035 2035 if opts.get('changelog') or opts.get('manifest'):
2036 2036 file_, rev = None, file_
2037 2037 elif rev is None:
2038 2038 raise error.CommandError('debugdata', _('invalid arguments'))
2039 2039 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
2040 2040 try:
2041 2041 ui.write(r.revision(r.lookup(rev)))
2042 2042 except KeyError:
2043 2043 raise util.Abort(_('invalid revision identifier %s') % rev)
2044 2044
2045 2045 @command('debugdate',
2046 2046 [('e', 'extended', None, _('try extended date formats'))],
2047 2047 _('[-e] DATE [RANGE]'),
2048 2048 norepo=True, optionalrepo=True)
2049 2049 def debugdate(ui, date, range=None, **opts):
2050 2050 """parse and display a date"""
2051 2051 if opts["extended"]:
2052 2052 d = util.parsedate(date, util.extendeddateformats)
2053 2053 else:
2054 2054 d = util.parsedate(date)
2055 2055 ui.write(("internal: %s %s\n") % d)
2056 2056 ui.write(("standard: %s\n") % util.datestr(d))
2057 2057 if range:
2058 2058 m = util.matchdate(range)
2059 2059 ui.write(("match: %s\n") % m(d[0]))
2060 2060
2061 2061 @command('debugdiscovery',
2062 2062 [('', 'old', None, _('use old-style discovery')),
2063 2063 ('', 'nonheads', None,
2064 2064 _('use old-style discovery with non-heads included')),
2065 2065 ] + remoteopts,
2066 2066 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
2067 2067 def debugdiscovery(ui, repo, remoteurl="default", **opts):
2068 2068 """runs the changeset discovery protocol in isolation"""
2069 2069 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
2070 2070 opts.get('branch'))
2071 2071 remote = hg.peer(repo, opts, remoteurl)
2072 2072 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
2073 2073
2074 2074 # make sure tests are repeatable
2075 2075 random.seed(12323)
2076 2076
2077 2077 def doit(localheads, remoteheads, remote=remote):
2078 2078 if opts.get('old'):
2079 2079 if localheads:
2080 2080 raise util.Abort('cannot use localheads with old style '
2081 2081 'discovery')
2082 2082 if not util.safehasattr(remote, 'branches'):
2083 2083 # enable in-client legacy support
2084 2084 remote = localrepo.locallegacypeer(remote.local())
2085 2085 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
2086 2086 force=True)
2087 2087 common = set(common)
2088 2088 if not opts.get('nonheads'):
2089 2089 ui.write(("unpruned common: %s\n") %
2090 2090 " ".join(sorted(short(n) for n in common)))
2091 2091 dag = dagutil.revlogdag(repo.changelog)
2092 2092 all = dag.ancestorset(dag.internalizeall(common))
2093 2093 common = dag.externalizeall(dag.headsetofconnecteds(all))
2094 2094 else:
2095 2095 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
2096 2096 common = set(common)
2097 2097 rheads = set(hds)
2098 2098 lheads = set(repo.heads())
2099 2099 ui.write(("common heads: %s\n") %
2100 2100 " ".join(sorted(short(n) for n in common)))
2101 2101 if lheads <= common:
2102 2102 ui.write(("local is subset\n"))
2103 2103 elif rheads <= common:
2104 2104 ui.write(("remote is subset\n"))
2105 2105
2106 2106 serverlogs = opts.get('serverlog')
2107 2107 if serverlogs:
2108 2108 for filename in serverlogs:
2109 2109 logfile = open(filename, 'r')
2110 2110 try:
2111 2111 line = logfile.readline()
2112 2112 while line:
2113 2113 parts = line.strip().split(';')
2114 2114 op = parts[1]
2115 2115 if op == 'cg':
2116 2116 pass
2117 2117 elif op == 'cgss':
2118 2118 doit(parts[2].split(' '), parts[3].split(' '))
2119 2119 elif op == 'unb':
2120 2120 doit(parts[3].split(' '), parts[2].split(' '))
2121 2121 line = logfile.readline()
2122 2122 finally:
2123 2123 logfile.close()
2124 2124
2125 2125 else:
2126 2126 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
2127 2127 opts.get('remote_head'))
2128 2128 localrevs = opts.get('local_head')
2129 2129 doit(localrevs, remoterevs)
2130 2130
2131 2131 @command('debugfileset',
2132 2132 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
2133 2133 _('[-r REV] FILESPEC'))
2134 2134 def debugfileset(ui, repo, expr, **opts):
2135 2135 '''parse and apply a fileset specification'''
2136 2136 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2137 2137 if ui.verbose:
2138 2138 tree = fileset.parse(expr)[0]
2139 2139 ui.note(tree, "\n")
2140 2140
2141 2141 for f in ctx.getfileset(expr):
2142 2142 ui.write("%s\n" % f)
2143 2143
2144 2144 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
2145 2145 def debugfsinfo(ui, path="."):
2146 2146 """show information detected about current filesystem"""
2147 2147 util.writefile('.debugfsinfo', '')
2148 2148 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
2149 2149 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
2150 2150 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
2151 2151 ui.write(('case-sensitive: %s\n') % (util.checkcase('.debugfsinfo')
2152 2152 and 'yes' or 'no'))
2153 2153 os.unlink('.debugfsinfo')
2154 2154
2155 2155 @command('debuggetbundle',
2156 2156 [('H', 'head', [], _('id of head node'), _('ID')),
2157 2157 ('C', 'common', [], _('id of common node'), _('ID')),
2158 2158 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
2159 2159 _('REPO FILE [-H|-C ID]...'),
2160 2160 norepo=True)
2161 2161 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
2162 2162 """retrieves a bundle from a repo
2163 2163
2164 2164 Every ID must be a full-length hex node id string. Saves the bundle to the
2165 2165 given file.
2166 2166 """
2167 2167 repo = hg.peer(ui, opts, repopath)
2168 2168 if not repo.capable('getbundle'):
2169 2169 raise util.Abort("getbundle() not supported by target repository")
2170 2170 args = {}
2171 2171 if common:
2172 2172 args['common'] = [bin(s) for s in common]
2173 2173 if head:
2174 2174 args['heads'] = [bin(s) for s in head]
2175 2175 # TODO: get desired bundlecaps from command line.
2176 2176 args['bundlecaps'] = None
2177 2177 bundle = repo.getbundle('debug', **args)
2178 2178
2179 2179 bundletype = opts.get('type', 'bzip2').lower()
2180 2180 btypes = {'none': 'HG10UN',
2181 2181 'bzip2': 'HG10BZ',
2182 2182 'gzip': 'HG10GZ',
2183 2183 'bundle2': 'HG2Y'}
2184 2184 bundletype = btypes.get(bundletype)
2185 2185 if bundletype not in changegroup.bundletypes:
2186 2186 raise util.Abort(_('unknown bundle type specified with --type'))
2187 2187 changegroup.writebundle(ui, bundle, bundlepath, bundletype)
2188 2188
2189 2189 @command('debugignore', [], '')
2190 2190 def debugignore(ui, repo, *values, **opts):
2191 2191 """display the combined ignore pattern"""
2192 2192 ignore = repo.dirstate._ignore
2193 2193 includepat = getattr(ignore, 'includepat', None)
2194 2194 if includepat is not None:
2195 2195 ui.write("%s\n" % includepat)
2196 2196 else:
2197 2197 raise util.Abort(_("no ignore patterns found"))
2198 2198
2199 2199 @command('debugindex',
2200 2200 [('c', 'changelog', False, _('open changelog')),
2201 2201 ('m', 'manifest', False, _('open manifest')),
2202 2202 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2203 2203 _('[-f FORMAT] -c|-m|FILE'),
2204 2204 optionalrepo=True)
2205 2205 def debugindex(ui, repo, file_=None, **opts):
2206 2206 """dump the contents of an index file"""
2207 2207 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
2208 2208 format = opts.get('format', 0)
2209 2209 if format not in (0, 1):
2210 2210 raise util.Abort(_("unknown format %d") % format)
2211 2211
2212 2212 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2213 2213 if generaldelta:
2214 2214 basehdr = ' delta'
2215 2215 else:
2216 2216 basehdr = ' base'
2217 2217
2218 2218 if ui.debugflag:
2219 2219 shortfn = hex
2220 2220 else:
2221 2221 shortfn = short
2222 2222
2223 2223 # There might not be anything in r, so have a sane default
2224 2224 idlen = 12
2225 2225 for i in r:
2226 2226 idlen = len(shortfn(r.node(i)))
2227 2227 break
2228 2228
2229 2229 if format == 0:
2230 2230 ui.write(" rev offset length " + basehdr + " linkrev"
2231 2231 " %s %s p2\n" % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
2232 2232 elif format == 1:
2233 2233 ui.write(" rev flag offset length"
2234 2234 " size " + basehdr + " link p1 p2"
2235 2235 " %s\n" % "nodeid".rjust(idlen))
2236 2236
2237 2237 for i in r:
2238 2238 node = r.node(i)
2239 2239 if generaldelta:
2240 2240 base = r.deltaparent(i)
2241 2241 else:
2242 2242 base = r.chainbase(i)
2243 2243 if format == 0:
2244 2244 try:
2245 2245 pp = r.parents(node)
2246 2246 except Exception:
2247 2247 pp = [nullid, nullid]
2248 2248 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
2249 2249 i, r.start(i), r.length(i), base, r.linkrev(i),
2250 2250 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
2251 2251 elif format == 1:
2252 2252 pr = r.parentrevs(i)
2253 2253 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
2254 2254 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2255 2255 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
2256 2256
2257 2257 @command('debugindexdot', [], _('FILE'), optionalrepo=True)
2258 2258 def debugindexdot(ui, repo, file_):
2259 2259 """dump an index DAG as a graphviz dot file"""
2260 2260 r = None
2261 2261 if repo:
2262 2262 filelog = repo.file(file_)
2263 2263 if len(filelog):
2264 2264 r = filelog
2265 2265 if not r:
2266 2266 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
2267 2267 ui.write(("digraph G {\n"))
2268 2268 for i in r:
2269 2269 node = r.node(i)
2270 2270 pp = r.parents(node)
2271 2271 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
2272 2272 if pp[1] != nullid:
2273 2273 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
2274 2274 ui.write("}\n")
2275 2275
2276 2276 @command('debuginstall', [], '', norepo=True)
2277 2277 def debuginstall(ui):
2278 2278 '''test Mercurial installation
2279 2279
2280 2280 Returns 0 on success.
2281 2281 '''
2282 2282
2283 2283 def writetemp(contents):
2284 2284 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
2285 2285 f = os.fdopen(fd, "wb")
2286 2286 f.write(contents)
2287 2287 f.close()
2288 2288 return name
2289 2289
2290 2290 problems = 0
2291 2291
2292 2292 # encoding
2293 2293 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
2294 2294 try:
2295 2295 encoding.fromlocal("test")
2296 2296 except util.Abort, inst:
2297 2297 ui.write(" %s\n" % inst)
2298 2298 ui.write(_(" (check that your locale is properly set)\n"))
2299 2299 problems += 1
2300 2300
2301 2301 # Python
2302 2302 ui.status(_("checking Python executable (%s)\n") % sys.executable)
2303 2303 ui.status(_("checking Python version (%s)\n")
2304 2304 % ("%s.%s.%s" % sys.version_info[:3]))
2305 2305 ui.status(_("checking Python lib (%s)...\n")
2306 2306 % os.path.dirname(os.__file__))
2307 2307
2308 2308 # compiled modules
2309 2309 ui.status(_("checking installed modules (%s)...\n")
2310 2310 % os.path.dirname(__file__))
2311 2311 try:
2312 2312 import bdiff, mpatch, base85, osutil
2313 2313 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2314 2314 except Exception, inst:
2315 2315 ui.write(" %s\n" % inst)
2316 2316 ui.write(_(" One or more extensions could not be found"))
2317 2317 ui.write(_(" (check that you compiled the extensions)\n"))
2318 2318 problems += 1
2319 2319
2320 2320 # templates
2321 2321 import templater
2322 2322 p = templater.templatepaths()
2323 2323 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2324 2324 if p:
2325 2325 m = templater.templatepath("map-cmdline.default")
2326 2326 if m:
2327 2327 # template found, check if it is working
2328 2328 try:
2329 2329 templater.templater(m)
2330 2330 except Exception, inst:
2331 2331 ui.write(" %s\n" % inst)
2332 2332 p = None
2333 2333 else:
2334 2334 ui.write(_(" template 'default' not found\n"))
2335 2335 p = None
2336 2336 else:
2337 2337 ui.write(_(" no template directories found\n"))
2338 2338 if not p:
2339 2339 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2340 2340 problems += 1
2341 2341
2342 2342 # editor
2343 2343 ui.status(_("checking commit editor...\n"))
2344 2344 editor = ui.geteditor()
2345 2345 cmdpath = util.findexe(shlex.split(editor)[0])
2346 2346 if not cmdpath:
2347 2347 if editor == 'vi':
2348 2348 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2349 2349 ui.write(_(" (specify a commit editor in your configuration"
2350 2350 " file)\n"))
2351 2351 else:
2352 2352 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2353 2353 ui.write(_(" (specify a commit editor in your configuration"
2354 2354 " file)\n"))
2355 2355 problems += 1
2356 2356
2357 2357 # check username
2358 2358 ui.status(_("checking username...\n"))
2359 2359 try:
2360 2360 ui.username()
2361 2361 except util.Abort, e:
2362 2362 ui.write(" %s\n" % e)
2363 2363 ui.write(_(" (specify a username in your configuration file)\n"))
2364 2364 problems += 1
2365 2365
2366 2366 if not problems:
2367 2367 ui.status(_("no problems detected\n"))
2368 2368 else:
2369 2369 ui.write(_("%s problems detected,"
2370 2370 " please check your install!\n") % problems)
2371 2371
2372 2372 return problems
2373 2373
2374 2374 @command('debugknown', [], _('REPO ID...'), norepo=True)
2375 2375 def debugknown(ui, repopath, *ids, **opts):
2376 2376 """test whether node ids are known to a repo
2377 2377
2378 2378 Every ID must be a full-length hex node id string. Returns a list of 0s
2379 2379 and 1s indicating unknown/known.
2380 2380 """
2381 2381 repo = hg.peer(ui, opts, repopath)
2382 2382 if not repo.capable('known'):
2383 2383 raise util.Abort("known() not supported by target repository")
2384 2384 flags = repo.known([bin(s) for s in ids])
2385 2385 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2386 2386
2387 2387 @command('debuglabelcomplete', [], _('LABEL...'))
2388 2388 def debuglabelcomplete(ui, repo, *args):
2389 2389 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
2390 2390 debugnamecomplete(ui, repo, *args)
2391 2391
2392 2392 @command('debugnamecomplete', [], _('NAME...'))
2393 2393 def debugnamecomplete(ui, repo, *args):
2394 2394 '''complete "names" - tags, open branch names, bookmark names'''
2395 2395
2396 2396 names = set()
2397 2397 # since we previously only listed open branches, we will handle that
2398 2398 # specially (after this for loop)
2399 2399 for name, ns in repo.names.iteritems():
2400 2400 if name != 'branches':
2401 2401 names.update(ns.listnames(repo))
2402 2402 names.update(tag for (tag, heads, tip, closed)
2403 2403 in repo.branchmap().iterbranches() if not closed)
2404 2404 completions = set()
2405 2405 if not args:
2406 2406 args = ['']
2407 2407 for a in args:
2408 2408 completions.update(n for n in names if n.startswith(a))
2409 2409 ui.write('\n'.join(sorted(completions)))
2410 2410 ui.write('\n')
2411 2411
2412 2412 @command('debuglocks',
2413 2413 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
2414 2414 ('W', 'force-wlock', None,
2415 2415 _('free the working state lock (DANGEROUS)'))],
2416 2416 _('[OPTION]...'))
2417 2417 def debuglocks(ui, repo, **opts):
2418 2418 """show or modify state of locks
2419 2419
2420 2420 By default, this command will show which locks are held. This
2421 2421 includes the user and process holding the lock, the amount of time
2422 2422 the lock has been held, and the machine name where the process is
2423 2423 running if it's not local.
2424 2424
2425 2425 Locks protect the integrity of Mercurial's data, so should be
2426 2426 treated with care. System crashes or other interruptions may cause
2427 2427 locks to not be properly released, though Mercurial will usually
2428 2428 detect and remove such stale locks automatically.
2429 2429
2430 2430 However, detecting stale locks may not always be possible (for
2431 2431 instance, on a shared filesystem). Removing locks may also be
2432 2432 blocked by filesystem permissions.
2433 2433
2434 2434 Returns 0 if no locks are held.
2435 2435
2436 2436 """
2437 2437
2438 2438 if opts.get('force_lock'):
2439 2439 repo.svfs.unlink('lock')
2440 2440 if opts.get('force_wlock'):
2441 2441 repo.vfs.unlink('wlock')
2442 2442 if opts.get('force_lock') or opts.get('force_lock'):
2443 2443 return 0
2444 2444
2445 2445 now = time.time()
2446 2446 held = 0
2447 2447
2448 2448 def report(vfs, name, method):
2449 2449 # this causes stale locks to get reaped for more accurate reporting
2450 2450 try:
2451 2451 l = method(False)
2452 2452 except error.LockHeld:
2453 2453 l = None
2454 2454
2455 2455 if l:
2456 2456 l.release()
2457 2457 else:
2458 2458 try:
2459 2459 stat = repo.svfs.lstat(name)
2460 2460 age = now - stat.st_mtime
2461 2461 user = util.username(stat.st_uid)
2462 2462 locker = vfs.readlock(name)
2463 2463 if ":" in locker:
2464 2464 host, pid = locker.split(':')
2465 2465 if host == socket.gethostname():
2466 2466 locker = 'user %s, process %s' % (user, pid)
2467 2467 else:
2468 2468 locker = 'user %s, process %s, host %s' \
2469 2469 % (user, pid, host)
2470 2470 ui.write("%-6s %s (%ds)\n" % (name + ":", locker, age))
2471 2471 return 1
2472 2472 except OSError, e:
2473 2473 if e.errno != errno.ENOENT:
2474 2474 raise
2475 2475
2476 2476 ui.write("%-6s free\n" % (name + ":"))
2477 2477 return 0
2478 2478
2479 2479 held += report(repo.svfs, "lock", repo.lock)
2480 2480 held += report(repo.vfs, "wlock", repo.wlock)
2481 2481
2482 2482 return held
2483 2483
2484 2484 @command('debugobsolete',
2485 2485 [('', 'flags', 0, _('markers flag')),
2486 2486 ('', 'record-parents', False,
2487 2487 _('record parent information for the precursor')),
2488 2488 ('r', 'rev', [], _('display markers relevant to REV')),
2489 2489 ] + commitopts2,
2490 2490 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2491 2491 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2492 2492 """create arbitrary obsolete marker
2493 2493
2494 2494 With no arguments, displays the list of obsolescence markers."""
2495 2495
2496 2496 def parsenodeid(s):
2497 2497 try:
2498 2498 # We do not use revsingle/revrange functions here to accept
2499 2499 # arbitrary node identifiers, possibly not present in the
2500 2500 # local repository.
2501 2501 n = bin(s)
2502 2502 if len(n) != len(nullid):
2503 2503 raise TypeError()
2504 2504 return n
2505 2505 except TypeError:
2506 2506 raise util.Abort('changeset references must be full hexadecimal '
2507 2507 'node identifiers')
2508 2508
2509 2509 if precursor is not None:
2510 2510 if opts['rev']:
2511 2511 raise util.Abort('cannot select revision when creating marker')
2512 2512 metadata = {}
2513 2513 metadata['user'] = opts['user'] or ui.username()
2514 2514 succs = tuple(parsenodeid(succ) for succ in successors)
2515 2515 l = repo.lock()
2516 2516 try:
2517 2517 tr = repo.transaction('debugobsolete')
2518 2518 try:
2519 2519 try:
2520 2520 date = opts.get('date')
2521 2521 if date:
2522 2522 date = util.parsedate(date)
2523 2523 else:
2524 2524 date = None
2525 2525 prec = parsenodeid(precursor)
2526 2526 parents = None
2527 2527 if opts['record_parents']:
2528 2528 if prec not in repo.unfiltered():
2529 2529 raise util.Abort('cannot used --record-parents on '
2530 2530 'unknown changesets')
2531 2531 parents = repo.unfiltered()[prec].parents()
2532 2532 parents = tuple(p.node() for p in parents)
2533 2533 repo.obsstore.create(tr, prec, succs, opts['flags'],
2534 2534 parents=parents, date=date,
2535 2535 metadata=metadata)
2536 2536 tr.close()
2537 2537 except ValueError, exc:
2538 2538 raise util.Abort(_('bad obsmarker input: %s') % exc)
2539 2539 finally:
2540 2540 tr.release()
2541 2541 finally:
2542 2542 l.release()
2543 2543 else:
2544 2544 if opts['rev']:
2545 2545 revs = scmutil.revrange(repo, opts['rev'])
2546 2546 nodes = [repo[r].node() for r in revs]
2547 2547 markers = list(obsolete.getmarkers(repo, nodes=nodes))
2548 2548 markers.sort(key=lambda x: x._data)
2549 2549 else:
2550 2550 markers = obsolete.getmarkers(repo)
2551 2551
2552 2552 for m in markers:
2553 2553 cmdutil.showmarker(ui, m)
2554 2554
2555 2555 @command('debugpathcomplete',
2556 2556 [('f', 'full', None, _('complete an entire path')),
2557 2557 ('n', 'normal', None, _('show only normal files')),
2558 2558 ('a', 'added', None, _('show only added files')),
2559 2559 ('r', 'removed', None, _('show only removed files'))],
2560 2560 _('FILESPEC...'))
2561 2561 def debugpathcomplete(ui, repo, *specs, **opts):
2562 2562 '''complete part or all of a tracked path
2563 2563
2564 2564 This command supports shells that offer path name completion. It
2565 2565 currently completes only files already known to the dirstate.
2566 2566
2567 2567 Completion extends only to the next path segment unless
2568 2568 --full is specified, in which case entire paths are used.'''
2569 2569
2570 2570 def complete(path, acceptable):
2571 2571 dirstate = repo.dirstate
2572 2572 spec = os.path.normpath(os.path.join(os.getcwd(), path))
2573 2573 rootdir = repo.root + os.sep
2574 2574 if spec != repo.root and not spec.startswith(rootdir):
2575 2575 return [], []
2576 2576 if os.path.isdir(spec):
2577 2577 spec += '/'
2578 2578 spec = spec[len(rootdir):]
2579 2579 fixpaths = os.sep != '/'
2580 2580 if fixpaths:
2581 2581 spec = spec.replace(os.sep, '/')
2582 2582 speclen = len(spec)
2583 2583 fullpaths = opts['full']
2584 2584 files, dirs = set(), set()
2585 2585 adddir, addfile = dirs.add, files.add
2586 2586 for f, st in dirstate.iteritems():
2587 2587 if f.startswith(spec) and st[0] in acceptable:
2588 2588 if fixpaths:
2589 2589 f = f.replace('/', os.sep)
2590 2590 if fullpaths:
2591 2591 addfile(f)
2592 2592 continue
2593 2593 s = f.find(os.sep, speclen)
2594 2594 if s >= 0:
2595 2595 adddir(f[:s])
2596 2596 else:
2597 2597 addfile(f)
2598 2598 return files, dirs
2599 2599
2600 2600 acceptable = ''
2601 2601 if opts['normal']:
2602 2602 acceptable += 'nm'
2603 2603 if opts['added']:
2604 2604 acceptable += 'a'
2605 2605 if opts['removed']:
2606 2606 acceptable += 'r'
2607 2607 cwd = repo.getcwd()
2608 2608 if not specs:
2609 2609 specs = ['.']
2610 2610
2611 2611 files, dirs = set(), set()
2612 2612 for spec in specs:
2613 2613 f, d = complete(spec, acceptable or 'nmar')
2614 2614 files.update(f)
2615 2615 dirs.update(d)
2616 2616 files.update(dirs)
2617 2617 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2618 2618 ui.write('\n')
2619 2619
2620 2620 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
2621 2621 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2622 2622 '''access the pushkey key/value protocol
2623 2623
2624 2624 With two args, list the keys in the given namespace.
2625 2625
2626 2626 With five args, set a key to new if it currently is set to old.
2627 2627 Reports success or failure.
2628 2628 '''
2629 2629
2630 2630 target = hg.peer(ui, {}, repopath)
2631 2631 if keyinfo:
2632 2632 key, old, new = keyinfo
2633 2633 r = target.pushkey(namespace, key, old, new)
2634 2634 ui.status(str(r) + '\n')
2635 2635 return not r
2636 2636 else:
2637 2637 for k, v in sorted(target.listkeys(namespace).iteritems()):
2638 2638 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2639 2639 v.encode('string-escape')))
2640 2640
2641 2641 @command('debugpvec', [], _('A B'))
2642 2642 def debugpvec(ui, repo, a, b=None):
2643 2643 ca = scmutil.revsingle(repo, a)
2644 2644 cb = scmutil.revsingle(repo, b)
2645 2645 pa = pvec.ctxpvec(ca)
2646 2646 pb = pvec.ctxpvec(cb)
2647 2647 if pa == pb:
2648 2648 rel = "="
2649 2649 elif pa > pb:
2650 2650 rel = ">"
2651 2651 elif pa < pb:
2652 2652 rel = "<"
2653 2653 elif pa | pb:
2654 2654 rel = "|"
2655 2655 ui.write(_("a: %s\n") % pa)
2656 2656 ui.write(_("b: %s\n") % pb)
2657 2657 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2658 2658 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2659 2659 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2660 2660 pa.distance(pb), rel))
2661 2661
2662 2662 @command('debugrebuilddirstate|debugrebuildstate',
2663 2663 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2664 2664 _('[-r REV]'))
2665 2665 def debugrebuilddirstate(ui, repo, rev):
2666 2666 """rebuild the dirstate as it would look like for the given revision
2667 2667
2668 2668 If no revision is specified the first current parent will be used.
2669 2669
2670 2670 The dirstate will be set to the files of the given revision.
2671 2671 The actual working directory content or existing dirstate
2672 2672 information such as adds or removes is not considered.
2673 2673
2674 2674 One use of this command is to make the next :hg:`status` invocation
2675 2675 check the actual file content.
2676 2676 """
2677 2677 ctx = scmutil.revsingle(repo, rev)
2678 2678 wlock = repo.wlock()
2679 2679 try:
2680 2680 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2681 2681 finally:
2682 2682 wlock.release()
2683 2683
2684 2684 @command('debugrename',
2685 2685 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2686 2686 _('[-r REV] FILE'))
2687 2687 def debugrename(ui, repo, file1, *pats, **opts):
2688 2688 """dump rename information"""
2689 2689
2690 2690 ctx = scmutil.revsingle(repo, opts.get('rev'))
2691 2691 m = scmutil.match(ctx, (file1,) + pats, opts)
2692 2692 for abs in ctx.walk(m):
2693 2693 fctx = ctx[abs]
2694 2694 o = fctx.filelog().renamed(fctx.filenode())
2695 2695 rel = m.rel(abs)
2696 2696 if o:
2697 2697 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2698 2698 else:
2699 2699 ui.write(_("%s not renamed\n") % rel)
2700 2700
2701 2701 @command('debugrevlog',
2702 2702 [('c', 'changelog', False, _('open changelog')),
2703 2703 ('m', 'manifest', False, _('open manifest')),
2704 2704 ('d', 'dump', False, _('dump index data'))],
2705 2705 _('-c|-m|FILE'),
2706 2706 optionalrepo=True)
2707 2707 def debugrevlog(ui, repo, file_=None, **opts):
2708 2708 """show data and statistics about a revlog"""
2709 2709 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2710 2710
2711 2711 if opts.get("dump"):
2712 2712 numrevs = len(r)
2713 2713 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2714 2714 " rawsize totalsize compression heads chainlen\n")
2715 2715 ts = 0
2716 2716 heads = set()
2717 2717
2718 2718 for rev in xrange(numrevs):
2719 2719 dbase = r.deltaparent(rev)
2720 2720 if dbase == -1:
2721 2721 dbase = rev
2722 2722 cbase = r.chainbase(rev)
2723 2723 clen = r.chainlen(rev)
2724 2724 p1, p2 = r.parentrevs(rev)
2725 2725 rs = r.rawsize(rev)
2726 2726 ts = ts + rs
2727 2727 heads -= set(r.parentrevs(rev))
2728 2728 heads.add(rev)
2729 2729 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2730 2730 "%11d %5d %8d\n" %
2731 2731 (rev, p1, p2, r.start(rev), r.end(rev),
2732 2732 r.start(dbase), r.start(cbase),
2733 2733 r.start(p1), r.start(p2),
2734 2734 rs, ts, ts / r.end(rev), len(heads), clen))
2735 2735 return 0
2736 2736
2737 2737 v = r.version
2738 2738 format = v & 0xFFFF
2739 2739 flags = []
2740 2740 gdelta = False
2741 2741 if v & revlog.REVLOGNGINLINEDATA:
2742 2742 flags.append('inline')
2743 2743 if v & revlog.REVLOGGENERALDELTA:
2744 2744 gdelta = True
2745 2745 flags.append('generaldelta')
2746 2746 if not flags:
2747 2747 flags = ['(none)']
2748 2748
2749 2749 nummerges = 0
2750 2750 numfull = 0
2751 2751 numprev = 0
2752 2752 nump1 = 0
2753 2753 nump2 = 0
2754 2754 numother = 0
2755 2755 nump1prev = 0
2756 2756 nump2prev = 0
2757 2757 chainlengths = []
2758 2758
2759 2759 datasize = [None, 0, 0L]
2760 2760 fullsize = [None, 0, 0L]
2761 2761 deltasize = [None, 0, 0L]
2762 2762
2763 2763 def addsize(size, l):
2764 2764 if l[0] is None or size < l[0]:
2765 2765 l[0] = size
2766 2766 if size > l[1]:
2767 2767 l[1] = size
2768 2768 l[2] += size
2769 2769
2770 2770 numrevs = len(r)
2771 2771 for rev in xrange(numrevs):
2772 2772 p1, p2 = r.parentrevs(rev)
2773 2773 delta = r.deltaparent(rev)
2774 2774 if format > 0:
2775 2775 addsize(r.rawsize(rev), datasize)
2776 2776 if p2 != nullrev:
2777 2777 nummerges += 1
2778 2778 size = r.length(rev)
2779 2779 if delta == nullrev:
2780 2780 chainlengths.append(0)
2781 2781 numfull += 1
2782 2782 addsize(size, fullsize)
2783 2783 else:
2784 2784 chainlengths.append(chainlengths[delta] + 1)
2785 2785 addsize(size, deltasize)
2786 2786 if delta == rev - 1:
2787 2787 numprev += 1
2788 2788 if delta == p1:
2789 2789 nump1prev += 1
2790 2790 elif delta == p2:
2791 2791 nump2prev += 1
2792 2792 elif delta == p1:
2793 2793 nump1 += 1
2794 2794 elif delta == p2:
2795 2795 nump2 += 1
2796 2796 elif delta != nullrev:
2797 2797 numother += 1
2798 2798
2799 2799 # Adjust size min value for empty cases
2800 2800 for size in (datasize, fullsize, deltasize):
2801 2801 if size[0] is None:
2802 2802 size[0] = 0
2803 2803
2804 2804 numdeltas = numrevs - numfull
2805 2805 numoprev = numprev - nump1prev - nump2prev
2806 2806 totalrawsize = datasize[2]
2807 2807 datasize[2] /= numrevs
2808 2808 fulltotal = fullsize[2]
2809 2809 fullsize[2] /= numfull
2810 2810 deltatotal = deltasize[2]
2811 2811 if numrevs - numfull > 0:
2812 2812 deltasize[2] /= numrevs - numfull
2813 2813 totalsize = fulltotal + deltatotal
2814 2814 avgchainlen = sum(chainlengths) / numrevs
2815 2815 compratio = totalrawsize / totalsize
2816 2816
2817 2817 basedfmtstr = '%%%dd\n'
2818 2818 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2819 2819
2820 2820 def dfmtstr(max):
2821 2821 return basedfmtstr % len(str(max))
2822 2822 def pcfmtstr(max, padding=0):
2823 2823 return basepcfmtstr % (len(str(max)), ' ' * padding)
2824 2824
2825 2825 def pcfmt(value, total):
2826 2826 return (value, 100 * float(value) / total)
2827 2827
2828 2828 ui.write(('format : %d\n') % format)
2829 2829 ui.write(('flags : %s\n') % ', '.join(flags))
2830 2830
2831 2831 ui.write('\n')
2832 2832 fmt = pcfmtstr(totalsize)
2833 2833 fmt2 = dfmtstr(totalsize)
2834 2834 ui.write(('revisions : ') + fmt2 % numrevs)
2835 2835 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2836 2836 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2837 2837 ui.write(('revisions : ') + fmt2 % numrevs)
2838 2838 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2839 2839 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2840 2840 ui.write(('revision size : ') + fmt2 % totalsize)
2841 2841 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2842 2842 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2843 2843
2844 2844 ui.write('\n')
2845 2845 fmt = dfmtstr(max(avgchainlen, compratio))
2846 2846 ui.write(('avg chain length : ') + fmt % avgchainlen)
2847 2847 ui.write(('compression ratio : ') + fmt % compratio)
2848 2848
2849 2849 if format > 0:
2850 2850 ui.write('\n')
2851 2851 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2852 2852 % tuple(datasize))
2853 2853 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2854 2854 % tuple(fullsize))
2855 2855 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2856 2856 % tuple(deltasize))
2857 2857
2858 2858 if numdeltas > 0:
2859 2859 ui.write('\n')
2860 2860 fmt = pcfmtstr(numdeltas)
2861 2861 fmt2 = pcfmtstr(numdeltas, 4)
2862 2862 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2863 2863 if numprev > 0:
2864 2864 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2865 2865 numprev))
2866 2866 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2867 2867 numprev))
2868 2868 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2869 2869 numprev))
2870 2870 if gdelta:
2871 2871 ui.write(('deltas against p1 : ')
2872 2872 + fmt % pcfmt(nump1, numdeltas))
2873 2873 ui.write(('deltas against p2 : ')
2874 2874 + fmt % pcfmt(nump2, numdeltas))
2875 2875 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2876 2876 numdeltas))
2877 2877
2878 2878 @command('debugrevspec',
2879 2879 [('', 'optimize', None, _('print parsed tree after optimizing'))],
2880 2880 ('REVSPEC'))
2881 2881 def debugrevspec(ui, repo, expr, **opts):
2882 2882 """parse and apply a revision specification
2883 2883
2884 2884 Use --verbose to print the parsed tree before and after aliases
2885 2885 expansion.
2886 2886 """
2887 2887 if ui.verbose:
2888 2888 tree = revset.parse(expr)[0]
2889 2889 ui.note(revset.prettyformat(tree), "\n")
2890 2890 newtree = revset.findaliases(ui, tree)
2891 2891 if newtree != tree:
2892 2892 ui.note(revset.prettyformat(newtree), "\n")
2893 2893 tree = newtree
2894 2894 newtree = revset.foldconcat(tree)
2895 2895 if newtree != tree:
2896 2896 ui.note(revset.prettyformat(newtree), "\n")
2897 2897 if opts["optimize"]:
2898 2898 weight, optimizedtree = revset.optimize(newtree, True)
2899 2899 ui.note("* optimized:\n", revset.prettyformat(optimizedtree), "\n")
2900 2900 func = revset.match(ui, expr)
2901 2901 for c in func(repo):
2902 2902 ui.write("%s\n" % c)
2903 2903
2904 2904 @command('debugsetparents', [], _('REV1 [REV2]'))
2905 2905 def debugsetparents(ui, repo, rev1, rev2=None):
2906 2906 """manually set the parents of the current working directory
2907 2907
2908 2908 This is useful for writing repository conversion tools, but should
2909 2909 be used with care. For example, neither the working directory nor the
2910 2910 dirstate is updated, so file status may be incorrect after running this
2911 2911 command.
2912 2912
2913 2913 Returns 0 on success.
2914 2914 """
2915 2915
2916 2916 r1 = scmutil.revsingle(repo, rev1).node()
2917 2917 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2918 2918
2919 2919 wlock = repo.wlock()
2920 2920 try:
2921 2921 repo.dirstate.beginparentchange()
2922 2922 repo.setparents(r1, r2)
2923 2923 repo.dirstate.endparentchange()
2924 2924 finally:
2925 2925 wlock.release()
2926 2926
2927 2927 @command('debugdirstate|debugstate',
2928 2928 [('', 'nodates', None, _('do not display the saved mtime')),
2929 2929 ('', 'datesort', None, _('sort by saved mtime'))],
2930 2930 _('[OPTION]...'))
2931 2931 def debugstate(ui, repo, nodates=None, datesort=None):
2932 2932 """show the contents of the current dirstate"""
2933 2933 timestr = ""
2934 2934 if datesort:
2935 2935 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2936 2936 else:
2937 2937 keyfunc = None # sort by filename
2938 2938 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2939 2939 if ent[3] == -1:
2940 2940 timestr = 'unset '
2941 2941 elif nodates:
2942 2942 timestr = 'set '
2943 2943 else:
2944 2944 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2945 2945 time.localtime(ent[3]))
2946 2946 if ent[1] & 020000:
2947 2947 mode = 'lnk'
2948 2948 else:
2949 2949 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2950 2950 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2951 2951 for f in repo.dirstate.copies():
2952 2952 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2953 2953
2954 2954 @command('debugsub',
2955 2955 [('r', 'rev', '',
2956 2956 _('revision to check'), _('REV'))],
2957 2957 _('[-r REV] [REV]'))
2958 2958 def debugsub(ui, repo, rev=None):
2959 2959 ctx = scmutil.revsingle(repo, rev, None)
2960 2960 for k, v in sorted(ctx.substate.items()):
2961 2961 ui.write(('path %s\n') % k)
2962 2962 ui.write((' source %s\n') % v[0])
2963 2963 ui.write((' revision %s\n') % v[1])
2964 2964
2965 2965 @command('debugsuccessorssets',
2966 2966 [],
2967 2967 _('[REV]'))
2968 2968 def debugsuccessorssets(ui, repo, *revs):
2969 2969 """show set of successors for revision
2970 2970
2971 2971 A successors set of changeset A is a consistent group of revisions that
2972 2972 succeed A. It contains non-obsolete changesets only.
2973 2973
2974 2974 In most cases a changeset A has a single successors set containing a single
2975 2975 successor (changeset A replaced by A').
2976 2976
2977 2977 A changeset that is made obsolete with no successors are called "pruned".
2978 2978 Such changesets have no successors sets at all.
2979 2979
2980 2980 A changeset that has been "split" will have a successors set containing
2981 2981 more than one successor.
2982 2982
2983 2983 A changeset that has been rewritten in multiple different ways is called
2984 2984 "divergent". Such changesets have multiple successor sets (each of which
2985 2985 may also be split, i.e. have multiple successors).
2986 2986
2987 2987 Results are displayed as follows::
2988 2988
2989 2989 <rev1>
2990 2990 <successors-1A>
2991 2991 <rev2>
2992 2992 <successors-2A>
2993 2993 <successors-2B1> <successors-2B2> <successors-2B3>
2994 2994
2995 2995 Here rev2 has two possible (i.e. divergent) successors sets. The first
2996 2996 holds one element, whereas the second holds three (i.e. the changeset has
2997 2997 been split).
2998 2998 """
2999 2999 # passed to successorssets caching computation from one call to another
3000 3000 cache = {}
3001 3001 ctx2str = str
3002 3002 node2str = short
3003 3003 if ui.debug():
3004 3004 def ctx2str(ctx):
3005 3005 return ctx.hex()
3006 3006 node2str = hex
3007 3007 for rev in scmutil.revrange(repo, revs):
3008 3008 ctx = repo[rev]
3009 3009 ui.write('%s\n'% ctx2str(ctx))
3010 3010 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
3011 3011 if succsset:
3012 3012 ui.write(' ')
3013 3013 ui.write(node2str(succsset[0]))
3014 3014 for node in succsset[1:]:
3015 3015 ui.write(' ')
3016 3016 ui.write(node2str(node))
3017 3017 ui.write('\n')
3018 3018
3019 3019 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'), inferrepo=True)
3020 3020 def debugwalk(ui, repo, *pats, **opts):
3021 3021 """show how files match on given patterns"""
3022 3022 m = scmutil.match(repo[None], pats, opts)
3023 3023 items = list(repo.walk(m))
3024 3024 if not items:
3025 3025 return
3026 3026 f = lambda fn: fn
3027 3027 if ui.configbool('ui', 'slash') and os.sep != '/':
3028 3028 f = lambda fn: util.normpath(fn)
3029 3029 fmt = 'f %%-%ds %%-%ds %%s' % (
3030 3030 max([len(abs) for abs in items]),
3031 3031 max([len(m.rel(abs)) for abs in items]))
3032 3032 for abs in items:
3033 3033 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
3034 3034 ui.write("%s\n" % line.rstrip())
3035 3035
3036 3036 @command('debugwireargs',
3037 3037 [('', 'three', '', 'three'),
3038 3038 ('', 'four', '', 'four'),
3039 3039 ('', 'five', '', 'five'),
3040 3040 ] + remoteopts,
3041 3041 _('REPO [OPTIONS]... [ONE [TWO]]'),
3042 3042 norepo=True)
3043 3043 def debugwireargs(ui, repopath, *vals, **opts):
3044 3044 repo = hg.peer(ui, opts, repopath)
3045 3045 for opt in remoteopts:
3046 3046 del opts[opt[1]]
3047 3047 args = {}
3048 3048 for k, v in opts.iteritems():
3049 3049 if v:
3050 3050 args[k] = v
3051 3051 # run twice to check that we don't mess up the stream for the next command
3052 3052 res1 = repo.debugwireargs(*vals, **args)
3053 3053 res2 = repo.debugwireargs(*vals, **args)
3054 3054 ui.write("%s\n" % res1)
3055 3055 if res1 != res2:
3056 3056 ui.warn("%s\n" % res2)
3057 3057
3058 3058 @command('^diff',
3059 3059 [('r', 'rev', [], _('revision'), _('REV')),
3060 3060 ('c', 'change', '', _('change made by revision'), _('REV'))
3061 3061 ] + diffopts + diffopts2 + walkopts + subrepoopts,
3062 3062 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
3063 3063 inferrepo=True)
3064 3064 def diff(ui, repo, *pats, **opts):
3065 3065 """diff repository (or selected files)
3066 3066
3067 3067 Show differences between revisions for the specified files.
3068 3068
3069 3069 Differences between files are shown using the unified diff format.
3070 3070
3071 3071 .. note::
3072 3072
3073 3073 diff may generate unexpected results for merges, as it will
3074 3074 default to comparing against the working directory's first
3075 3075 parent changeset if no revisions are specified.
3076 3076
3077 3077 When two revision arguments are given, then changes are shown
3078 3078 between those revisions. If only one revision is specified then
3079 3079 that revision is compared to the working directory, and, when no
3080 3080 revisions are specified, the working directory files are compared
3081 3081 to its parent.
3082 3082
3083 3083 Alternatively you can specify -c/--change with a revision to see
3084 3084 the changes in that changeset relative to its first parent.
3085 3085
3086 3086 Without the -a/--text option, diff will avoid generating diffs of
3087 3087 files it detects as binary. With -a, diff will generate a diff
3088 3088 anyway, probably with undesirable results.
3089 3089
3090 3090 Use the -g/--git option to generate diffs in the git extended diff
3091 3091 format. For more information, read :hg:`help diffs`.
3092 3092
3093 3093 .. container:: verbose
3094 3094
3095 3095 Examples:
3096 3096
3097 3097 - compare a file in the current working directory to its parent::
3098 3098
3099 3099 hg diff foo.c
3100 3100
3101 3101 - compare two historical versions of a directory, with rename info::
3102 3102
3103 3103 hg diff --git -r 1.0:1.2 lib/
3104 3104
3105 3105 - get change stats relative to the last change on some date::
3106 3106
3107 3107 hg diff --stat -r "date('may 2')"
3108 3108
3109 3109 - diff all newly-added files that contain a keyword::
3110 3110
3111 3111 hg diff "set:added() and grep(GNU)"
3112 3112
3113 3113 - compare a revision and its parents::
3114 3114
3115 3115 hg diff -c 9353 # compare against first parent
3116 3116 hg diff -r 9353^:9353 # same using revset syntax
3117 3117 hg diff -r 9353^2:9353 # compare against the second parent
3118 3118
3119 3119 Returns 0 on success.
3120 3120 """
3121 3121
3122 3122 revs = opts.get('rev')
3123 3123 change = opts.get('change')
3124 3124 stat = opts.get('stat')
3125 3125 reverse = opts.get('reverse')
3126 3126
3127 3127 if revs and change:
3128 3128 msg = _('cannot specify --rev and --change at the same time')
3129 3129 raise util.Abort(msg)
3130 3130 elif change:
3131 3131 node2 = scmutil.revsingle(repo, change, None).node()
3132 3132 node1 = repo[node2].p1().node()
3133 3133 else:
3134 3134 node1, node2 = scmutil.revpair(repo, revs)
3135 3135
3136 3136 if reverse:
3137 3137 node1, node2 = node2, node1
3138 3138
3139 3139 diffopts = patch.diffallopts(ui, opts)
3140 3140 m = scmutil.match(repo[node2], pats, opts)
3141 3141 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
3142 3142 listsubrepos=opts.get('subrepos'))
3143 3143
3144 3144 @command('^export',
3145 3145 [('o', 'output', '',
3146 3146 _('print output to file with formatted name'), _('FORMAT')),
3147 3147 ('', 'switch-parent', None, _('diff against the second parent')),
3148 3148 ('r', 'rev', [], _('revisions to export'), _('REV')),
3149 3149 ] + diffopts,
3150 3150 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
3151 3151 def export(ui, repo, *changesets, **opts):
3152 3152 """dump the header and diffs for one or more changesets
3153 3153
3154 3154 Print the changeset header and diffs for one or more revisions.
3155 3155 If no revision is given, the parent of the working directory is used.
3156 3156
3157 3157 The information shown in the changeset header is: author, date,
3158 3158 branch name (if non-default), changeset hash, parent(s) and commit
3159 3159 comment.
3160 3160
3161 3161 .. note::
3162 3162
3163 3163 export may generate unexpected diff output for merge
3164 3164 changesets, as it will compare the merge changeset against its
3165 3165 first parent only.
3166 3166
3167 3167 Output may be to a file, in which case the name of the file is
3168 3168 given using a format string. The formatting rules are as follows:
3169 3169
3170 3170 :``%%``: literal "%" character
3171 3171 :``%H``: changeset hash (40 hexadecimal digits)
3172 3172 :``%N``: number of patches being generated
3173 3173 :``%R``: changeset revision number
3174 3174 :``%b``: basename of the exporting repository
3175 3175 :``%h``: short-form changeset hash (12 hexadecimal digits)
3176 3176 :``%m``: first line of the commit message (only alphanumeric characters)
3177 3177 :``%n``: zero-padded sequence number, starting at 1
3178 3178 :``%r``: zero-padded changeset revision number
3179 3179
3180 3180 Without the -a/--text option, export will avoid generating diffs
3181 3181 of files it detects as binary. With -a, export will generate a
3182 3182 diff anyway, probably with undesirable results.
3183 3183
3184 3184 Use the -g/--git option to generate diffs in the git extended diff
3185 3185 format. See :hg:`help diffs` for more information.
3186 3186
3187 3187 With the --switch-parent option, the diff will be against the
3188 3188 second parent. It can be useful to review a merge.
3189 3189
3190 3190 .. container:: verbose
3191 3191
3192 3192 Examples:
3193 3193
3194 3194 - use export and import to transplant a bugfix to the current
3195 3195 branch::
3196 3196
3197 3197 hg export -r 9353 | hg import -
3198 3198
3199 3199 - export all the changesets between two revisions to a file with
3200 3200 rename information::
3201 3201
3202 3202 hg export --git -r 123:150 > changes.txt
3203 3203
3204 3204 - split outgoing changes into a series of patches with
3205 3205 descriptive names::
3206 3206
3207 3207 hg export -r "outgoing()" -o "%n-%m.patch"
3208 3208
3209 3209 Returns 0 on success.
3210 3210 """
3211 3211 changesets += tuple(opts.get('rev', []))
3212 3212 if not changesets:
3213 3213 changesets = ['.']
3214 3214 revs = scmutil.revrange(repo, changesets)
3215 3215 if not revs:
3216 3216 raise util.Abort(_("export requires at least one changeset"))
3217 3217 if len(revs) > 1:
3218 3218 ui.note(_('exporting patches:\n'))
3219 3219 else:
3220 3220 ui.note(_('exporting patch:\n'))
3221 3221 cmdutil.export(repo, revs, template=opts.get('output'),
3222 3222 switch_parent=opts.get('switch_parent'),
3223 3223 opts=patch.diffallopts(ui, opts))
3224 3224
3225 3225 @command('files',
3226 3226 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3227 3227 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3228 3228 ] + walkopts + formatteropts,
3229 3229 _('[OPTION]... [PATTERN]...'))
3230 3230 def files(ui, repo, *pats, **opts):
3231 3231 """list tracked files
3232 3232
3233 3233 Print files under Mercurial control in the working directory or
3234 3234 specified revision whose names match the given patterns (excluding
3235 3235 removed files).
3236 3236
3237 3237 If no patterns are given to match, this command prints the names
3238 3238 of all files under Mercurial control in the working directory.
3239 3239
3240 3240 .. container:: verbose
3241 3241
3242 3242 Examples:
3243 3243
3244 3244 - list all files under the current directory::
3245 3245
3246 3246 hg files .
3247 3247
3248 3248 - shows sizes and flags for current revision::
3249 3249
3250 3250 hg files -vr .
3251 3251
3252 3252 - list all files named README::
3253 3253
3254 3254 hg files -I "**/README"
3255 3255
3256 3256 - list all binary files::
3257 3257
3258 3258 hg files "set:binary()"
3259 3259
3260 3260 - find files containing a regular expression::
3261 3261
3262 3262 hg files "set:grep('bob')"
3263 3263
3264 3264 - search tracked file contents with xargs and grep::
3265 3265
3266 3266 hg files -0 | xargs -0 grep foo
3267 3267
3268 3268 See :hg:`help patterns` and :hg:`help filesets` for more information
3269 3269 on specifying file patterns.
3270 3270
3271 3271 Returns 0 if a match is found, 1 otherwise.
3272 3272
3273 3273 """
3274 3274 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3275 3275
3276 3276 end = '\n'
3277 3277 if opts.get('print0'):
3278 3278 end = '\0'
3279 3279 fm = ui.formatter('files', opts)
3280 3280 fmt = '%s' + end
3281 3281
3282 3282 m = scmutil.match(ctx, pats, opts)
3283 3283 ret = cmdutil.files(ui, ctx, m, fm, fmt)
3284 3284
3285 3285 fm.end()
3286 3286
3287 3287 return ret
3288 3288
3289 3289 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
3290 3290 def forget(ui, repo, *pats, **opts):
3291 3291 """forget the specified files on the next commit
3292 3292
3293 3293 Mark the specified files so they will no longer be tracked
3294 3294 after the next commit.
3295 3295
3296 3296 This only removes files from the current branch, not from the
3297 3297 entire project history, and it does not delete them from the
3298 3298 working directory.
3299 3299
3300 3300 To undo a forget before the next commit, see :hg:`add`.
3301 3301
3302 3302 .. container:: verbose
3303 3303
3304 3304 Examples:
3305 3305
3306 3306 - forget newly-added binary files::
3307 3307
3308 3308 hg forget "set:added() and binary()"
3309 3309
3310 3310 - forget files that would be excluded by .hgignore::
3311 3311
3312 3312 hg forget "set:hgignore()"
3313 3313
3314 3314 Returns 0 on success.
3315 3315 """
3316 3316
3317 3317 if not pats:
3318 3318 raise util.Abort(_('no files specified'))
3319 3319
3320 3320 m = scmutil.match(repo[None], pats, opts)
3321 3321 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
3322 3322 return rejected and 1 or 0
3323 3323
3324 3324 @command(
3325 3325 'graft',
3326 3326 [('r', 'rev', [], _('revisions to graft'), _('REV')),
3327 3327 ('c', 'continue', False, _('resume interrupted graft')),
3328 3328 ('e', 'edit', False, _('invoke editor on commit messages')),
3329 3329 ('', 'log', None, _('append graft info to log message')),
3330 3330 ('f', 'force', False, _('force graft')),
3331 3331 ('D', 'currentdate', False,
3332 3332 _('record the current date as commit date')),
3333 3333 ('U', 'currentuser', False,
3334 3334 _('record the current user as committer'), _('DATE'))]
3335 3335 + commitopts2 + mergetoolopts + dryrunopts,
3336 3336 _('[OPTION]... [-r] REV...'))
3337 3337 def graft(ui, repo, *revs, **opts):
3338 3338 '''copy changes from other branches onto the current branch
3339 3339
3340 3340 This command uses Mercurial's merge logic to copy individual
3341 3341 changes from other branches without merging branches in the
3342 3342 history graph. This is sometimes known as 'backporting' or
3343 3343 'cherry-picking'. By default, graft will copy user, date, and
3344 3344 description from the source changesets.
3345 3345
3346 3346 Changesets that are ancestors of the current revision, that have
3347 3347 already been grafted, or that are merges will be skipped.
3348 3348
3349 3349 If --log is specified, log messages will have a comment appended
3350 3350 of the form::
3351 3351
3352 3352 (grafted from CHANGESETHASH)
3353 3353
3354 3354 If --force is specified, revisions will be grafted even if they
3355 3355 are already ancestors of or have been grafted to the destination.
3356 3356 This is useful when the revisions have since been backed out.
3357 3357
3358 3358 If a graft merge results in conflicts, the graft process is
3359 3359 interrupted so that the current merge can be manually resolved.
3360 3360 Once all conflicts are addressed, the graft process can be
3361 3361 continued with the -c/--continue option.
3362 3362
3363 3363 .. note::
3364 3364
3365 3365 The -c/--continue option does not reapply earlier options, except
3366 3366 for --force.
3367 3367
3368 3368 .. container:: verbose
3369 3369
3370 3370 Examples:
3371 3371
3372 3372 - copy a single change to the stable branch and edit its description::
3373 3373
3374 3374 hg update stable
3375 3375 hg graft --edit 9393
3376 3376
3377 3377 - graft a range of changesets with one exception, updating dates::
3378 3378
3379 3379 hg graft -D "2085::2093 and not 2091"
3380 3380
3381 3381 - continue a graft after resolving conflicts::
3382 3382
3383 3383 hg graft -c
3384 3384
3385 3385 - show the source of a grafted changeset::
3386 3386
3387 3387 hg log --debug -r .
3388 3388
3389 3389 See :hg:`help revisions` and :hg:`help revsets` for more about
3390 3390 specifying revisions.
3391 3391
3392 3392 Returns 0 on successful completion.
3393 3393 '''
3394 3394
3395 3395 revs = list(revs)
3396 3396 revs.extend(opts['rev'])
3397 3397
3398 3398 if not opts.get('user') and opts.get('currentuser'):
3399 3399 opts['user'] = ui.username()
3400 3400 if not opts.get('date') and opts.get('currentdate'):
3401 3401 opts['date'] = "%d %d" % util.makedate()
3402 3402
3403 3403 editor = cmdutil.getcommiteditor(editform='graft', **opts)
3404 3404
3405 3405 cont = False
3406 3406 if opts['continue']:
3407 3407 cont = True
3408 3408 if revs:
3409 3409 raise util.Abort(_("can't specify --continue and revisions"))
3410 3410 # read in unfinished revisions
3411 3411 try:
3412 3412 nodes = repo.vfs.read('graftstate').splitlines()
3413 3413 revs = [repo[node].rev() for node in nodes]
3414 3414 except IOError, inst:
3415 3415 if inst.errno != errno.ENOENT:
3416 3416 raise
3417 3417 raise util.Abort(_("no graft state found, can't continue"))
3418 3418 else:
3419 3419 cmdutil.checkunfinished(repo)
3420 3420 cmdutil.bailifchanged(repo)
3421 3421 if not revs:
3422 3422 raise util.Abort(_('no revisions specified'))
3423 3423 revs = scmutil.revrange(repo, revs)
3424 3424
3425 3425 skipped = set()
3426 3426 # check for merges
3427 3427 for rev in repo.revs('%ld and merge()', revs):
3428 3428 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
3429 3429 skipped.add(rev)
3430 3430 revs = [r for r in revs if r not in skipped]
3431 3431 if not revs:
3432 3432 return -1
3433 3433
3434 3434 # Don't check in the --continue case, in effect retaining --force across
3435 3435 # --continues. That's because without --force, any revisions we decided to
3436 3436 # skip would have been filtered out here, so they wouldn't have made their
3437 3437 # way to the graftstate. With --force, any revisions we would have otherwise
3438 3438 # skipped would not have been filtered out, and if they hadn't been applied
3439 3439 # already, they'd have been in the graftstate.
3440 3440 if not (cont or opts.get('force')):
3441 3441 # check for ancestors of dest branch
3442 3442 crev = repo['.'].rev()
3443 3443 ancestors = repo.changelog.ancestors([crev], inclusive=True)
3444 3444 # Cannot use x.remove(y) on smart set, this has to be a list.
3445 3445 # XXX make this lazy in the future
3446 3446 revs = list(revs)
3447 3447 # don't mutate while iterating, create a copy
3448 3448 for rev in list(revs):
3449 3449 if rev in ancestors:
3450 3450 ui.warn(_('skipping ancestor revision %d:%s\n') %
3451 3451 (rev, repo[rev]))
3452 3452 # XXX remove on list is slow
3453 3453 revs.remove(rev)
3454 3454 if not revs:
3455 3455 return -1
3456 3456
3457 3457 # analyze revs for earlier grafts
3458 3458 ids = {}
3459 3459 for ctx in repo.set("%ld", revs):
3460 3460 ids[ctx.hex()] = ctx.rev()
3461 3461 n = ctx.extra().get('source')
3462 3462 if n:
3463 3463 ids[n] = ctx.rev()
3464 3464
3465 3465 # check ancestors for earlier grafts
3466 3466 ui.debug('scanning for duplicate grafts\n')
3467 3467
3468 3468 for rev in repo.changelog.findmissingrevs(revs, [crev]):
3469 3469 ctx = repo[rev]
3470 3470 n = ctx.extra().get('source')
3471 3471 if n in ids:
3472 3472 try:
3473 3473 r = repo[n].rev()
3474 3474 except error.RepoLookupError:
3475 3475 r = None
3476 3476 if r in revs:
3477 3477 ui.warn(_('skipping revision %d:%s '
3478 3478 '(already grafted to %d:%s)\n')
3479 3479 % (r, repo[r], rev, ctx))
3480 3480 revs.remove(r)
3481 3481 elif ids[n] in revs:
3482 3482 if r is None:
3483 3483 ui.warn(_('skipping already grafted revision %d:%s '
3484 3484 '(%d:%s also has unknown origin %s)\n')
3485 3485 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
3486 3486 else:
3487 3487 ui.warn(_('skipping already grafted revision %d:%s '
3488 3488 '(%d:%s also has origin %d:%s)\n')
3489 3489 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
3490 3490 revs.remove(ids[n])
3491 3491 elif ctx.hex() in ids:
3492 3492 r = ids[ctx.hex()]
3493 3493 ui.warn(_('skipping already grafted revision %d:%s '
3494 3494 '(was grafted from %d:%s)\n') %
3495 3495 (r, repo[r], rev, ctx))
3496 3496 revs.remove(r)
3497 3497 if not revs:
3498 3498 return -1
3499 3499
3500 3500 wlock = repo.wlock()
3501 3501 try:
3502 3502 for pos, ctx in enumerate(repo.set("%ld", revs)):
3503 3503 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
3504 3504 ctx.description().split('\n', 1)[0])
3505 3505 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3506 3506 if names:
3507 3507 desc += ' (%s)' % ' '.join(names)
3508 3508 ui.status(_('grafting %s\n') % desc)
3509 3509 if opts.get('dry_run'):
3510 3510 continue
3511 3511
3512 3512 source = ctx.extra().get('source')
3513 3513 if not source:
3514 3514 source = ctx.hex()
3515 3515 extra = {'source': source}
3516 3516 user = ctx.user()
3517 3517 if opts.get('user'):
3518 3518 user = opts['user']
3519 3519 date = ctx.date()
3520 3520 if opts.get('date'):
3521 3521 date = opts['date']
3522 3522 message = ctx.description()
3523 3523 if opts.get('log'):
3524 3524 message += '\n(grafted from %s)' % ctx.hex()
3525 3525
3526 3526 # we don't merge the first commit when continuing
3527 3527 if not cont:
3528 3528 # perform the graft merge with p1(rev) as 'ancestor'
3529 3529 try:
3530 3530 # ui.forcemerge is an internal variable, do not document
3531 3531 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
3532 3532 'graft')
3533 3533 stats = mergemod.graft(repo, ctx, ctx.p1(),
3534 3534 ['local', 'graft'])
3535 3535 finally:
3536 3536 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
3537 3537 # report any conflicts
3538 3538 if stats and stats[3] > 0:
3539 3539 # write out state for --continue
3540 3540 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
3541 3541 repo.vfs.write('graftstate', ''.join(nodelines))
3542 3542 raise util.Abort(
3543 3543 _("unresolved conflicts, can't continue"),
3544 3544 hint=_('use hg resolve and hg graft --continue'))
3545 3545 else:
3546 3546 cont = False
3547 3547
3548 3548 # commit
3549 3549 node = repo.commit(text=message, user=user,
3550 3550 date=date, extra=extra, editor=editor)
3551 3551 if node is None:
3552 3552 ui.warn(
3553 3553 _('note: graft of %d:%s created no changes to commit\n') %
3554 3554 (ctx.rev(), ctx))
3555 3555 finally:
3556 3556 wlock.release()
3557 3557
3558 3558 # remove state when we complete successfully
3559 3559 if not opts.get('dry_run'):
3560 3560 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
3561 3561
3562 3562 return 0
3563 3563
3564 3564 @command('grep',
3565 3565 [('0', 'print0', None, _('end fields with NUL')),
3566 3566 ('', 'all', None, _('print all revisions that match')),
3567 3567 ('a', 'text', None, _('treat all files as text')),
3568 3568 ('f', 'follow', None,
3569 3569 _('follow changeset history,'
3570 3570 ' or file history across copies and renames')),
3571 3571 ('i', 'ignore-case', None, _('ignore case when matching')),
3572 3572 ('l', 'files-with-matches', None,
3573 3573 _('print only filenames and revisions that match')),
3574 3574 ('n', 'line-number', None, _('print matching line numbers')),
3575 3575 ('r', 'rev', [],
3576 3576 _('only search files changed within revision range'), _('REV')),
3577 3577 ('u', 'user', None, _('list the author (long with -v)')),
3578 3578 ('d', 'date', None, _('list the date (short with -q)')),
3579 3579 ] + walkopts,
3580 3580 _('[OPTION]... PATTERN [FILE]...'),
3581 3581 inferrepo=True)
3582 3582 def grep(ui, repo, pattern, *pats, **opts):
3583 3583 """search for a pattern in specified files and revisions
3584 3584
3585 3585 Search revisions of files for a regular expression.
3586 3586
3587 3587 This command behaves differently than Unix grep. It only accepts
3588 3588 Python/Perl regexps. It searches repository history, not the
3589 3589 working directory. It always prints the revision number in which a
3590 3590 match appears.
3591 3591
3592 3592 By default, grep only prints output for the first revision of a
3593 3593 file in which it finds a match. To get it to print every revision
3594 3594 that contains a change in match status ("-" for a match that
3595 3595 becomes a non-match, or "+" for a non-match that becomes a match),
3596 3596 use the --all flag.
3597 3597
3598 3598 Returns 0 if a match is found, 1 otherwise.
3599 3599 """
3600 3600 reflags = re.M
3601 3601 if opts.get('ignore_case'):
3602 3602 reflags |= re.I
3603 3603 try:
3604 3604 regexp = util.re.compile(pattern, reflags)
3605 3605 except re.error, inst:
3606 3606 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
3607 3607 return 1
3608 3608 sep, eol = ':', '\n'
3609 3609 if opts.get('print0'):
3610 3610 sep = eol = '\0'
3611 3611
3612 3612 getfile = util.lrucachefunc(repo.file)
3613 3613
3614 3614 def matchlines(body):
3615 3615 begin = 0
3616 3616 linenum = 0
3617 3617 while begin < len(body):
3618 3618 match = regexp.search(body, begin)
3619 3619 if not match:
3620 3620 break
3621 3621 mstart, mend = match.span()
3622 3622 linenum += body.count('\n', begin, mstart) + 1
3623 3623 lstart = body.rfind('\n', begin, mstart) + 1 or begin
3624 3624 begin = body.find('\n', mend) + 1 or len(body) + 1
3625 3625 lend = begin - 1
3626 3626 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3627 3627
3628 3628 class linestate(object):
3629 3629 def __init__(self, line, linenum, colstart, colend):
3630 3630 self.line = line
3631 3631 self.linenum = linenum
3632 3632 self.colstart = colstart
3633 3633 self.colend = colend
3634 3634
3635 3635 def __hash__(self):
3636 3636 return hash((self.linenum, self.line))
3637 3637
3638 3638 def __eq__(self, other):
3639 3639 return self.line == other.line
3640 3640
3641 3641 def __iter__(self):
3642 3642 yield (self.line[:self.colstart], '')
3643 3643 yield (self.line[self.colstart:self.colend], 'grep.match')
3644 3644 rest = self.line[self.colend:]
3645 3645 while rest != '':
3646 3646 match = regexp.search(rest)
3647 3647 if not match:
3648 3648 yield (rest, '')
3649 3649 break
3650 3650 mstart, mend = match.span()
3651 3651 yield (rest[:mstart], '')
3652 3652 yield (rest[mstart:mend], 'grep.match')
3653 3653 rest = rest[mend:]
3654 3654
3655 3655 matches = {}
3656 3656 copies = {}
3657 3657 def grepbody(fn, rev, body):
3658 3658 matches[rev].setdefault(fn, [])
3659 3659 m = matches[rev][fn]
3660 3660 for lnum, cstart, cend, line in matchlines(body):
3661 3661 s = linestate(line, lnum, cstart, cend)
3662 3662 m.append(s)
3663 3663
3664 3664 def difflinestates(a, b):
3665 3665 sm = difflib.SequenceMatcher(None, a, b)
3666 3666 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3667 3667 if tag == 'insert':
3668 3668 for i in xrange(blo, bhi):
3669 3669 yield ('+', b[i])
3670 3670 elif tag == 'delete':
3671 3671 for i in xrange(alo, ahi):
3672 3672 yield ('-', a[i])
3673 3673 elif tag == 'replace':
3674 3674 for i in xrange(alo, ahi):
3675 3675 yield ('-', a[i])
3676 3676 for i in xrange(blo, bhi):
3677 3677 yield ('+', b[i])
3678 3678
3679 3679 def display(fn, ctx, pstates, states):
3680 3680 rev = ctx.rev()
3681 3681 if ui.quiet:
3682 3682 datefunc = util.shortdate
3683 3683 else:
3684 3684 datefunc = util.datestr
3685 3685 found = False
3686 3686 @util.cachefunc
3687 3687 def binary():
3688 3688 flog = getfile(fn)
3689 3689 return util.binary(flog.read(ctx.filenode(fn)))
3690 3690
3691 3691 if opts.get('all'):
3692 3692 iter = difflinestates(pstates, states)
3693 3693 else:
3694 3694 iter = [('', l) for l in states]
3695 3695 for change, l in iter:
3696 3696 cols = [(fn, 'grep.filename'), (str(rev), 'grep.rev')]
3697 3697
3698 3698 if opts.get('line_number'):
3699 3699 cols.append((str(l.linenum), 'grep.linenumber'))
3700 3700 if opts.get('all'):
3701 3701 cols.append((change, 'grep.change'))
3702 3702 if opts.get('user'):
3703 3703 cols.append((ui.shortuser(ctx.user()), 'grep.user'))
3704 3704 if opts.get('date'):
3705 3705 cols.append((datefunc(ctx.date()), 'grep.date'))
3706 3706 for col, label in cols[:-1]:
3707 3707 ui.write(col, label=label)
3708 3708 ui.write(sep, label='grep.sep')
3709 3709 ui.write(cols[-1][0], label=cols[-1][1])
3710 3710 if not opts.get('files_with_matches'):
3711 3711 ui.write(sep, label='grep.sep')
3712 3712 if not opts.get('text') and binary():
3713 3713 ui.write(" Binary file matches")
3714 3714 else:
3715 3715 for s, label in l:
3716 3716 ui.write(s, label=label)
3717 3717 ui.write(eol)
3718 3718 found = True
3719 3719 if opts.get('files_with_matches'):
3720 3720 break
3721 3721 return found
3722 3722
3723 3723 skip = {}
3724 3724 revfiles = {}
3725 3725 matchfn = scmutil.match(repo[None], pats, opts)
3726 3726 found = False
3727 3727 follow = opts.get('follow')
3728 3728
3729 3729 def prep(ctx, fns):
3730 3730 rev = ctx.rev()
3731 3731 pctx = ctx.p1()
3732 3732 parent = pctx.rev()
3733 3733 matches.setdefault(rev, {})
3734 3734 matches.setdefault(parent, {})
3735 3735 files = revfiles.setdefault(rev, [])
3736 3736 for fn in fns:
3737 3737 flog = getfile(fn)
3738 3738 try:
3739 3739 fnode = ctx.filenode(fn)
3740 3740 except error.LookupError:
3741 3741 continue
3742 3742
3743 3743 copied = flog.renamed(fnode)
3744 3744 copy = follow and copied and copied[0]
3745 3745 if copy:
3746 3746 copies.setdefault(rev, {})[fn] = copy
3747 3747 if fn in skip:
3748 3748 if copy:
3749 3749 skip[copy] = True
3750 3750 continue
3751 3751 files.append(fn)
3752 3752
3753 3753 if fn not in matches[rev]:
3754 3754 grepbody(fn, rev, flog.read(fnode))
3755 3755
3756 3756 pfn = copy or fn
3757 3757 if pfn not in matches[parent]:
3758 3758 try:
3759 3759 fnode = pctx.filenode(pfn)
3760 3760 grepbody(pfn, parent, flog.read(fnode))
3761 3761 except error.LookupError:
3762 3762 pass
3763 3763
3764 3764 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3765 3765 rev = ctx.rev()
3766 3766 parent = ctx.p1().rev()
3767 3767 for fn in sorted(revfiles.get(rev, [])):
3768 3768 states = matches[rev][fn]
3769 3769 copy = copies.get(rev, {}).get(fn)
3770 3770 if fn in skip:
3771 3771 if copy:
3772 3772 skip[copy] = True
3773 3773 continue
3774 3774 pstates = matches.get(parent, {}).get(copy or fn, [])
3775 3775 if pstates or states:
3776 3776 r = display(fn, ctx, pstates, states)
3777 3777 found = found or r
3778 3778 if r and not opts.get('all'):
3779 3779 skip[fn] = True
3780 3780 if copy:
3781 3781 skip[copy] = True
3782 3782 del matches[rev]
3783 3783 del revfiles[rev]
3784 3784
3785 3785 return not found
3786 3786
3787 3787 @command('heads',
3788 3788 [('r', 'rev', '',
3789 3789 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3790 3790 ('t', 'topo', False, _('show topological heads only')),
3791 3791 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3792 3792 ('c', 'closed', False, _('show normal and closed branch heads')),
3793 3793 ] + templateopts,
3794 3794 _('[-ct] [-r STARTREV] [REV]...'))
3795 3795 def heads(ui, repo, *branchrevs, **opts):
3796 3796 """show branch heads
3797 3797
3798 3798 With no arguments, show all open branch heads in the repository.
3799 3799 Branch heads are changesets that have no descendants on the
3800 3800 same branch. They are where development generally takes place and
3801 3801 are the usual targets for update and merge operations.
3802 3802
3803 3803 If one or more REVs are given, only open branch heads on the
3804 3804 branches associated with the specified changesets are shown. This
3805 3805 means that you can use :hg:`heads .` to see the heads on the
3806 3806 currently checked-out branch.
3807 3807
3808 3808 If -c/--closed is specified, also show branch heads marked closed
3809 3809 (see :hg:`commit --close-branch`).
3810 3810
3811 3811 If STARTREV is specified, only those heads that are descendants of
3812 3812 STARTREV will be displayed.
3813 3813
3814 3814 If -t/--topo is specified, named branch mechanics will be ignored and only
3815 3815 topological heads (changesets with no children) will be shown.
3816 3816
3817 3817 Returns 0 if matching heads are found, 1 if not.
3818 3818 """
3819 3819
3820 3820 start = None
3821 3821 if 'rev' in opts:
3822 3822 start = scmutil.revsingle(repo, opts['rev'], None).node()
3823 3823
3824 3824 if opts.get('topo'):
3825 3825 heads = [repo[h] for h in repo.heads(start)]
3826 3826 else:
3827 3827 heads = []
3828 3828 for branch in repo.branchmap():
3829 3829 heads += repo.branchheads(branch, start, opts.get('closed'))
3830 3830 heads = [repo[h] for h in heads]
3831 3831
3832 3832 if branchrevs:
3833 3833 branches = set(repo[br].branch() for br in branchrevs)
3834 3834 heads = [h for h in heads if h.branch() in branches]
3835 3835
3836 3836 if opts.get('active') and branchrevs:
3837 3837 dagheads = repo.heads(start)
3838 3838 heads = [h for h in heads if h.node() in dagheads]
3839 3839
3840 3840 if branchrevs:
3841 3841 haveheads = set(h.branch() for h in heads)
3842 3842 if branches - haveheads:
3843 3843 headless = ', '.join(b for b in branches - haveheads)
3844 3844 msg = _('no open branch heads found on branches %s')
3845 3845 if opts.get('rev'):
3846 3846 msg += _(' (started at %s)') % opts['rev']
3847 3847 ui.warn((msg + '\n') % headless)
3848 3848
3849 3849 if not heads:
3850 3850 return 1
3851 3851
3852 3852 heads = sorted(heads, key=lambda x: -x.rev())
3853 3853 displayer = cmdutil.show_changeset(ui, repo, opts)
3854 3854 for ctx in heads:
3855 3855 displayer.show(ctx)
3856 3856 displayer.close()
3857 3857
3858 3858 @command('help',
3859 3859 [('e', 'extension', None, _('show only help for extensions')),
3860 3860 ('c', 'command', None, _('show only help for commands')),
3861 3861 ('k', 'keyword', '', _('show topics matching keyword')),
3862 3862 ],
3863 3863 _('[-ec] [TOPIC]'),
3864 3864 norepo=True)
3865 3865 def help_(ui, name=None, **opts):
3866 3866 """show help for a given topic or a help overview
3867 3867
3868 3868 With no arguments, print a list of commands with short help messages.
3869 3869
3870 3870 Given a topic, extension, or command name, print help for that
3871 3871 topic.
3872 3872
3873 3873 Returns 0 if successful.
3874 3874 """
3875 3875
3876 3876 textwidth = min(ui.termwidth(), 80) - 2
3877 3877
3878 3878 keep = []
3879 3879 if ui.verbose:
3880 3880 keep.append('verbose')
3881 3881 if sys.platform.startswith('win'):
3882 3882 keep.append('windows')
3883 3883 elif sys.platform == 'OpenVMS':
3884 3884 keep.append('vms')
3885 3885 elif sys.platform == 'plan9':
3886 3886 keep.append('plan9')
3887 3887 else:
3888 3888 keep.append('unix')
3889 3889 keep.append(sys.platform.lower())
3890 3890
3891 3891 section = None
3892 3892 if name and '.' in name:
3893 3893 name, section = name.split('.', 1)
3894 3894
3895 3895 text = help.help_(ui, name, **opts)
3896 3896
3897 3897 formatted, pruned = minirst.format(text, textwidth, keep=keep,
3898 3898 section=section)
3899 3899 if section and not formatted:
3900 3900 raise util.Abort(_("help section not found"))
3901 3901
3902 3902 if 'verbose' in pruned:
3903 3903 keep.append('omitted')
3904 3904 else:
3905 3905 keep.append('notomitted')
3906 3906 formatted, pruned = minirst.format(text, textwidth, keep=keep,
3907 3907 section=section)
3908 3908 ui.write(formatted)
3909 3909
3910 3910
3911 3911 @command('identify|id',
3912 3912 [('r', 'rev', '',
3913 3913 _('identify the specified revision'), _('REV')),
3914 3914 ('n', 'num', None, _('show local revision number')),
3915 3915 ('i', 'id', None, _('show global revision id')),
3916 3916 ('b', 'branch', None, _('show branch')),
3917 3917 ('t', 'tags', None, _('show tags')),
3918 3918 ('B', 'bookmarks', None, _('show bookmarks')),
3919 3919 ] + remoteopts,
3920 3920 _('[-nibtB] [-r REV] [SOURCE]'),
3921 3921 optionalrepo=True)
3922 3922 def identify(ui, repo, source=None, rev=None,
3923 3923 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3924 3924 """identify the working directory or specified revision
3925 3925
3926 3926 Print a summary identifying the repository state at REV using one or
3927 3927 two parent hash identifiers, followed by a "+" if the working
3928 3928 directory has uncommitted changes, the branch name (if not default),
3929 3929 a list of tags, and a list of bookmarks.
3930 3930
3931 3931 When REV is not given, print a summary of the current state of the
3932 3932 repository.
3933 3933
3934 3934 Specifying a path to a repository root or Mercurial bundle will
3935 3935 cause lookup to operate on that repository/bundle.
3936 3936
3937 3937 .. container:: verbose
3938 3938
3939 3939 Examples:
3940 3940
3941 3941 - generate a build identifier for the working directory::
3942 3942
3943 3943 hg id --id > build-id.dat
3944 3944
3945 3945 - find the revision corresponding to a tag::
3946 3946
3947 3947 hg id -n -r 1.3
3948 3948
3949 3949 - check the most recent revision of a remote repository::
3950 3950
3951 3951 hg id -r tip http://selenic.com/hg/
3952 3952
3953 3953 Returns 0 if successful.
3954 3954 """
3955 3955
3956 3956 if not repo and not source:
3957 3957 raise util.Abort(_("there is no Mercurial repository here "
3958 3958 "(.hg not found)"))
3959 3959
3960 3960 if ui.debugflag:
3961 3961 hexfunc = hex
3962 3962 else:
3963 3963 hexfunc = short
3964 3964 default = not (num or id or branch or tags or bookmarks)
3965 3965 output = []
3966 3966 revs = []
3967 3967
3968 3968 if source:
3969 3969 source, branches = hg.parseurl(ui.expandpath(source))
3970 3970 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3971 3971 repo = peer.local()
3972 3972 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3973 3973
3974 3974 if not repo:
3975 3975 if num or branch or tags:
3976 3976 raise util.Abort(
3977 3977 _("can't query remote revision number, branch, or tags"))
3978 3978 if not rev and revs:
3979 3979 rev = revs[0]
3980 3980 if not rev:
3981 3981 rev = "tip"
3982 3982
3983 3983 remoterev = peer.lookup(rev)
3984 3984 if default or id:
3985 3985 output = [hexfunc(remoterev)]
3986 3986
3987 3987 def getbms():
3988 3988 bms = []
3989 3989
3990 3990 if 'bookmarks' in peer.listkeys('namespaces'):
3991 3991 hexremoterev = hex(remoterev)
3992 3992 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3993 3993 if bmr == hexremoterev]
3994 3994
3995 3995 return sorted(bms)
3996 3996
3997 3997 if bookmarks:
3998 3998 output.extend(getbms())
3999 3999 elif default and not ui.quiet:
4000 4000 # multiple bookmarks for a single parent separated by '/'
4001 4001 bm = '/'.join(getbms())
4002 4002 if bm:
4003 4003 output.append(bm)
4004 4004 else:
4005 4005 if not rev:
4006 4006 ctx = repo[None]
4007 4007 parents = ctx.parents()
4008 4008 changed = ""
4009 4009 if default or id or num:
4010 4010 if (util.any(repo.status())
4011 4011 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
4012 4012 changed = '+'
4013 4013 if default or id:
4014 4014 output = ["%s%s" %
4015 4015 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
4016 4016 if num:
4017 4017 output.append("%s%s" %
4018 4018 ('+'.join([str(p.rev()) for p in parents]), changed))
4019 4019 else:
4020 4020 ctx = scmutil.revsingle(repo, rev)
4021 4021 if default or id:
4022 4022 output = [hexfunc(ctx.node())]
4023 4023 if num:
4024 4024 output.append(str(ctx.rev()))
4025 4025
4026 4026 if default and not ui.quiet:
4027 4027 b = ctx.branch()
4028 4028 if b != 'default':
4029 4029 output.append("(%s)" % b)
4030 4030
4031 4031 # multiple tags for a single parent separated by '/'
4032 4032 t = '/'.join(ctx.tags())
4033 4033 if t:
4034 4034 output.append(t)
4035 4035
4036 4036 # multiple bookmarks for a single parent separated by '/'
4037 4037 bm = '/'.join(ctx.bookmarks())
4038 4038 if bm:
4039 4039 output.append(bm)
4040 4040 else:
4041 4041 if branch:
4042 4042 output.append(ctx.branch())
4043 4043
4044 4044 if tags:
4045 4045 output.extend(ctx.tags())
4046 4046
4047 4047 if bookmarks:
4048 4048 output.extend(ctx.bookmarks())
4049 4049
4050 4050 ui.write("%s\n" % ' '.join(output))
4051 4051
4052 4052 @command('import|patch',
4053 4053 [('p', 'strip', 1,
4054 4054 _('directory strip option for patch. This has the same '
4055 4055 'meaning as the corresponding patch option'), _('NUM')),
4056 4056 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
4057 4057 ('e', 'edit', False, _('invoke editor on commit messages')),
4058 4058 ('f', 'force', None,
4059 4059 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
4060 4060 ('', 'no-commit', None,
4061 4061 _("don't commit, just update the working directory")),
4062 4062 ('', 'bypass', None,
4063 4063 _("apply patch without touching the working directory")),
4064 4064 ('', 'partial', None,
4065 4065 _('commit even if some hunks fail')),
4066 4066 ('', 'exact', None,
4067 4067 _('apply patch to the nodes from which it was generated')),
4068 4068 ('', 'prefix', '',
4069 _('apply patch to directory relative to the root'), _('DIR')),
4069 _('apply patch to subdirectory'), _('DIR')),
4070 4070 ('', 'import-branch', None,
4071 4071 _('use any branch information in patch (implied by --exact)'))] +
4072 4072 commitopts + commitopts2 + similarityopts,
4073 4073 _('[OPTION]... PATCH...'))
4074 4074 def import_(ui, repo, patch1=None, *patches, **opts):
4075 4075 """import an ordered set of patches
4076 4076
4077 4077 Import a list of patches and commit them individually (unless
4078 4078 --no-commit is specified).
4079 4079
4080 4080 Because import first applies changes to the working directory,
4081 4081 import will abort if there are outstanding changes.
4082 4082
4083 4083 You can import a patch straight from a mail message. Even patches
4084 4084 as attachments work (to use the body part, it must have type
4085 4085 text/plain or text/x-patch). From and Subject headers of email
4086 4086 message are used as default committer and commit message. All
4087 4087 text/plain body parts before first diff are added to commit
4088 4088 message.
4089 4089
4090 4090 If the imported patch was generated by :hg:`export`, user and
4091 4091 description from patch override values from message headers and
4092 4092 body. Values given on command line with -m/--message and -u/--user
4093 4093 override these.
4094 4094
4095 4095 If --exact is specified, import will set the working directory to
4096 4096 the parent of each patch before applying it, and will abort if the
4097 4097 resulting changeset has a different ID than the one recorded in
4098 4098 the patch. This may happen due to character set problems or other
4099 4099 deficiencies in the text patch format.
4100 4100
4101 4101 Use --bypass to apply and commit patches directly to the
4102 4102 repository, not touching the working directory. Without --exact,
4103 4103 patches will be applied on top of the working directory parent
4104 4104 revision.
4105 4105
4106 4106 With -s/--similarity, hg will attempt to discover renames and
4107 4107 copies in the patch in the same way as :hg:`addremove`.
4108 4108
4109 4109 Use --partial to ensure a changeset will be created from the patch
4110 4110 even if some hunks fail to apply. Hunks that fail to apply will be
4111 4111 written to a <target-file>.rej file. Conflicts can then be resolved
4112 4112 by hand before :hg:`commit --amend` is run to update the created
4113 4113 changeset. This flag exists to let people import patches that
4114 4114 partially apply without losing the associated metadata (author,
4115 4115 date, description, ...). Note that when none of the hunk applies
4116 4116 cleanly, :hg:`import --partial` will create an empty changeset,
4117 4117 importing only the patch metadata.
4118 4118
4119 4119 To read a patch from standard input, use "-" as the patch name. If
4120 4120 a URL is specified, the patch will be downloaded from it.
4121 4121 See :hg:`help dates` for a list of formats valid for -d/--date.
4122 4122
4123 4123 .. container:: verbose
4124 4124
4125 4125 Examples:
4126 4126
4127 4127 - import a traditional patch from a website and detect renames::
4128 4128
4129 4129 hg import -s 80 http://example.com/bugfix.patch
4130 4130
4131 4131 - import a changeset from an hgweb server::
4132 4132
4133 4133 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
4134 4134
4135 4135 - import all the patches in an Unix-style mbox::
4136 4136
4137 4137 hg import incoming-patches.mbox
4138 4138
4139 4139 - attempt to exactly restore an exported changeset (not always
4140 4140 possible)::
4141 4141
4142 4142 hg import --exact proposed-fix.patch
4143 4143
4144 4144 Returns 0 on success, 1 on partial success (see --partial).
4145 4145 """
4146 4146
4147 4147 if not patch1:
4148 4148 raise util.Abort(_('need at least one patch to import'))
4149 4149
4150 4150 patches = (patch1,) + patches
4151 4151
4152 4152 date = opts.get('date')
4153 4153 if date:
4154 4154 opts['date'] = util.parsedate(date)
4155 4155
4156 4156 update = not opts.get('bypass')
4157 4157 if not update and opts.get('no_commit'):
4158 4158 raise util.Abort(_('cannot use --no-commit with --bypass'))
4159 4159 try:
4160 4160 sim = float(opts.get('similarity') or 0)
4161 4161 except ValueError:
4162 4162 raise util.Abort(_('similarity must be a number'))
4163 4163 if sim < 0 or sim > 100:
4164 4164 raise util.Abort(_('similarity must be between 0 and 100'))
4165 4165 if sim and not update:
4166 4166 raise util.Abort(_('cannot use --similarity with --bypass'))
4167 4167 if opts.get('exact') and opts.get('edit'):
4168 4168 raise util.Abort(_('cannot use --exact with --edit'))
4169 4169 if opts.get('exact') and opts.get('prefix'):
4170 4170 raise util.Abort(_('cannot use --exact with --prefix'))
4171 4171
4172 4172 if update:
4173 4173 cmdutil.checkunfinished(repo)
4174 4174 if (opts.get('exact') or not opts.get('force')) and update:
4175 4175 cmdutil.bailifchanged(repo)
4176 4176
4177 4177 base = opts["base"]
4178 4178 wlock = lock = tr = None
4179 4179 msgs = []
4180 4180 ret = 0
4181 4181
4182 4182
4183 4183 try:
4184 4184 try:
4185 4185 wlock = repo.wlock()
4186 4186 repo.dirstate.beginparentchange()
4187 4187 if not opts.get('no_commit'):
4188 4188 lock = repo.lock()
4189 4189 tr = repo.transaction('import')
4190 4190 parents = repo.parents()
4191 4191 for patchurl in patches:
4192 4192 if patchurl == '-':
4193 4193 ui.status(_('applying patch from stdin\n'))
4194 4194 patchfile = ui.fin
4195 4195 patchurl = 'stdin' # for error message
4196 4196 else:
4197 4197 patchurl = os.path.join(base, patchurl)
4198 4198 ui.status(_('applying %s\n') % patchurl)
4199 4199 patchfile = hg.openpath(ui, patchurl)
4200 4200
4201 4201 haspatch = False
4202 4202 for hunk in patch.split(patchfile):
4203 4203 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
4204 4204 parents, opts,
4205 4205 msgs, hg.clean)
4206 4206 if msg:
4207 4207 haspatch = True
4208 4208 ui.note(msg + '\n')
4209 4209 if update or opts.get('exact'):
4210 4210 parents = repo.parents()
4211 4211 else:
4212 4212 parents = [repo[node]]
4213 4213 if rej:
4214 4214 ui.write_err(_("patch applied partially\n"))
4215 4215 ui.write_err(_("(fix the .rej files and run "
4216 4216 "`hg commit --amend`)\n"))
4217 4217 ret = 1
4218 4218 break
4219 4219
4220 4220 if not haspatch:
4221 4221 raise util.Abort(_('%s: no diffs found') % patchurl)
4222 4222
4223 4223 if tr:
4224 4224 tr.close()
4225 4225 if msgs:
4226 4226 repo.savecommitmessage('\n* * *\n'.join(msgs))
4227 4227 repo.dirstate.endparentchange()
4228 4228 return ret
4229 4229 except: # re-raises
4230 4230 # wlock.release() indirectly calls dirstate.write(): since
4231 4231 # we're crashing, we do not want to change the working dir
4232 4232 # parent after all, so make sure it writes nothing
4233 4233 repo.dirstate.invalidate()
4234 4234 raise
4235 4235 finally:
4236 4236 if tr:
4237 4237 tr.release()
4238 4238 release(lock, wlock)
4239 4239
4240 4240 @command('incoming|in',
4241 4241 [('f', 'force', None,
4242 4242 _('run even if remote repository is unrelated')),
4243 4243 ('n', 'newest-first', None, _('show newest record first')),
4244 4244 ('', 'bundle', '',
4245 4245 _('file to store the bundles into'), _('FILE')),
4246 4246 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4247 4247 ('B', 'bookmarks', False, _("compare bookmarks")),
4248 4248 ('b', 'branch', [],
4249 4249 _('a specific branch you would like to pull'), _('BRANCH')),
4250 4250 ] + logopts + remoteopts + subrepoopts,
4251 4251 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
4252 4252 def incoming(ui, repo, source="default", **opts):
4253 4253 """show new changesets found in source
4254 4254
4255 4255 Show new changesets found in the specified path/URL or the default
4256 4256 pull location. These are the changesets that would have been pulled
4257 4257 if a pull at the time you issued this command.
4258 4258
4259 4259 See pull for valid source format details.
4260 4260
4261 4261 .. container:: verbose
4262 4262
4263 4263 For remote repository, using --bundle avoids downloading the
4264 4264 changesets twice if the incoming is followed by a pull.
4265 4265
4266 4266 Examples:
4267 4267
4268 4268 - show incoming changes with patches and full description::
4269 4269
4270 4270 hg incoming -vp
4271 4271
4272 4272 - show incoming changes excluding merges, store a bundle::
4273 4273
4274 4274 hg in -vpM --bundle incoming.hg
4275 4275 hg pull incoming.hg
4276 4276
4277 4277 - briefly list changes inside a bundle::
4278 4278
4279 4279 hg in changes.hg -T "{desc|firstline}\\n"
4280 4280
4281 4281 Returns 0 if there are incoming changes, 1 otherwise.
4282 4282 """
4283 4283 if opts.get('graph'):
4284 4284 cmdutil.checkunsupportedgraphflags([], opts)
4285 4285 def display(other, chlist, displayer):
4286 4286 revdag = cmdutil.graphrevs(other, chlist, opts)
4287 4287 showparents = [ctx.node() for ctx in repo[None].parents()]
4288 4288 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4289 4289 graphmod.asciiedges)
4290 4290
4291 4291 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4292 4292 return 0
4293 4293
4294 4294 if opts.get('bundle') and opts.get('subrepos'):
4295 4295 raise util.Abort(_('cannot combine --bundle and --subrepos'))
4296 4296
4297 4297 if opts.get('bookmarks'):
4298 4298 source, branches = hg.parseurl(ui.expandpath(source),
4299 4299 opts.get('branch'))
4300 4300 other = hg.peer(repo, opts, source)
4301 4301 if 'bookmarks' not in other.listkeys('namespaces'):
4302 4302 ui.warn(_("remote doesn't support bookmarks\n"))
4303 4303 return 0
4304 4304 ui.status(_('comparing with %s\n') % util.hidepassword(source))
4305 4305 return bookmarks.diff(ui, repo, other)
4306 4306
4307 4307 repo._subtoppath = ui.expandpath(source)
4308 4308 try:
4309 4309 return hg.incoming(ui, repo, source, opts)
4310 4310 finally:
4311 4311 del repo._subtoppath
4312 4312
4313 4313
4314 4314 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
4315 4315 norepo=True)
4316 4316 def init(ui, dest=".", **opts):
4317 4317 """create a new repository in the given directory
4318 4318
4319 4319 Initialize a new repository in the given directory. If the given
4320 4320 directory does not exist, it will be created.
4321 4321
4322 4322 If no directory is given, the current directory is used.
4323 4323
4324 4324 It is possible to specify an ``ssh://`` URL as the destination.
4325 4325 See :hg:`help urls` for more information.
4326 4326
4327 4327 Returns 0 on success.
4328 4328 """
4329 4329 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4330 4330
4331 4331 @command('locate',
4332 4332 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
4333 4333 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4334 4334 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
4335 4335 ] + walkopts,
4336 4336 _('[OPTION]... [PATTERN]...'))
4337 4337 def locate(ui, repo, *pats, **opts):
4338 4338 """locate files matching specific patterns (DEPRECATED)
4339 4339
4340 4340 Print files under Mercurial control in the working directory whose
4341 4341 names match the given patterns.
4342 4342
4343 4343 By default, this command searches all directories in the working
4344 4344 directory. To search just the current directory and its
4345 4345 subdirectories, use "--include .".
4346 4346
4347 4347 If no patterns are given to match, this command prints the names
4348 4348 of all files under Mercurial control in the working directory.
4349 4349
4350 4350 If you want to feed the output of this command into the "xargs"
4351 4351 command, use the -0 option to both this command and "xargs". This
4352 4352 will avoid the problem of "xargs" treating single filenames that
4353 4353 contain whitespace as multiple filenames.
4354 4354
4355 4355 See :hg:`help files` for a more versatile command.
4356 4356
4357 4357 Returns 0 if a match is found, 1 otherwise.
4358 4358 """
4359 4359 if opts.get('print0'):
4360 4360 end = '\0'
4361 4361 else:
4362 4362 end = '\n'
4363 4363 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
4364 4364
4365 4365 ret = 1
4366 4366 ctx = repo[rev]
4367 4367 m = scmutil.match(ctx, pats, opts, default='relglob')
4368 4368 m.bad = lambda x, y: False
4369 4369
4370 4370 for abs in ctx.matches(m):
4371 4371 if opts.get('fullpath'):
4372 4372 ui.write(repo.wjoin(abs), end)
4373 4373 else:
4374 4374 ui.write(((pats and m.rel(abs)) or abs), end)
4375 4375 ret = 0
4376 4376
4377 4377 return ret
4378 4378
4379 4379 @command('^log|history',
4380 4380 [('f', 'follow', None,
4381 4381 _('follow changeset history, or file history across copies and renames')),
4382 4382 ('', 'follow-first', None,
4383 4383 _('only follow the first parent of merge changesets (DEPRECATED)')),
4384 4384 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
4385 4385 ('C', 'copies', None, _('show copied files')),
4386 4386 ('k', 'keyword', [],
4387 4387 _('do case-insensitive search for a given text'), _('TEXT')),
4388 4388 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
4389 4389 ('', 'removed', None, _('include revisions where files were removed')),
4390 4390 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
4391 4391 ('u', 'user', [], _('revisions committed by user'), _('USER')),
4392 4392 ('', 'only-branch', [],
4393 4393 _('show only changesets within the given named branch (DEPRECATED)'),
4394 4394 _('BRANCH')),
4395 4395 ('b', 'branch', [],
4396 4396 _('show changesets within the given named branch'), _('BRANCH')),
4397 4397 ('P', 'prune', [],
4398 4398 _('do not display revision or any of its ancestors'), _('REV')),
4399 4399 ] + logopts + walkopts,
4400 4400 _('[OPTION]... [FILE]'),
4401 4401 inferrepo=True)
4402 4402 def log(ui, repo, *pats, **opts):
4403 4403 """show revision history of entire repository or files
4404 4404
4405 4405 Print the revision history of the specified files or the entire
4406 4406 project.
4407 4407
4408 4408 If no revision range is specified, the default is ``tip:0`` unless
4409 4409 --follow is set, in which case the working directory parent is
4410 4410 used as the starting revision.
4411 4411
4412 4412 File history is shown without following rename or copy history of
4413 4413 files. Use -f/--follow with a filename to follow history across
4414 4414 renames and copies. --follow without a filename will only show
4415 4415 ancestors or descendants of the starting revision.
4416 4416
4417 4417 By default this command prints revision number and changeset id,
4418 4418 tags, non-trivial parents, user, date and time, and a summary for
4419 4419 each commit. When the -v/--verbose switch is used, the list of
4420 4420 changed files and full commit message are shown.
4421 4421
4422 4422 With --graph the revisions are shown as an ASCII art DAG with the most
4423 4423 recent changeset at the top.
4424 4424 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
4425 4425 and '+' represents a fork where the changeset from the lines below is a
4426 4426 parent of the 'o' merge on the same line.
4427 4427
4428 4428 .. note::
4429 4429
4430 4430 log -p/--patch may generate unexpected diff output for merge
4431 4431 changesets, as it will only compare the merge changeset against
4432 4432 its first parent. Also, only files different from BOTH parents
4433 4433 will appear in files:.
4434 4434
4435 4435 .. note::
4436 4436
4437 4437 for performance reasons, log FILE may omit duplicate changes
4438 4438 made on branches and will not show removals or mode changes. To
4439 4439 see all such changes, use the --removed switch.
4440 4440
4441 4441 .. container:: verbose
4442 4442
4443 4443 Some examples:
4444 4444
4445 4445 - changesets with full descriptions and file lists::
4446 4446
4447 4447 hg log -v
4448 4448
4449 4449 - changesets ancestral to the working directory::
4450 4450
4451 4451 hg log -f
4452 4452
4453 4453 - last 10 commits on the current branch::
4454 4454
4455 4455 hg log -l 10 -b .
4456 4456
4457 4457 - changesets showing all modifications of a file, including removals::
4458 4458
4459 4459 hg log --removed file.c
4460 4460
4461 4461 - all changesets that touch a directory, with diffs, excluding merges::
4462 4462
4463 4463 hg log -Mp lib/
4464 4464
4465 4465 - all revision numbers that match a keyword::
4466 4466
4467 4467 hg log -k bug --template "{rev}\\n"
4468 4468
4469 4469 - list available log templates::
4470 4470
4471 4471 hg log -T list
4472 4472
4473 4473 - check if a given changeset is included in a tagged release::
4474 4474
4475 4475 hg log -r "a21ccf and ancestor(1.9)"
4476 4476
4477 4477 - find all changesets by some user in a date range::
4478 4478
4479 4479 hg log -k alice -d "may 2008 to jul 2008"
4480 4480
4481 4481 - summary of all changesets after the last tag::
4482 4482
4483 4483 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4484 4484
4485 4485 See :hg:`help dates` for a list of formats valid for -d/--date.
4486 4486
4487 4487 See :hg:`help revisions` and :hg:`help revsets` for more about
4488 4488 specifying revisions.
4489 4489
4490 4490 See :hg:`help templates` for more about pre-packaged styles and
4491 4491 specifying custom templates.
4492 4492
4493 4493 Returns 0 on success.
4494 4494
4495 4495 """
4496 4496 if opts.get('follow') and opts.get('rev'):
4497 4497 opts['rev'] = [revset.formatspec('reverse(::%lr)', opts.get('rev'))]
4498 4498 del opts['follow']
4499 4499
4500 4500 if opts.get('graph'):
4501 4501 return cmdutil.graphlog(ui, repo, *pats, **opts)
4502 4502
4503 4503 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
4504 4504 limit = cmdutil.loglimit(opts)
4505 4505 count = 0
4506 4506
4507 4507 getrenamed = None
4508 4508 if opts.get('copies'):
4509 4509 endrev = None
4510 4510 if opts.get('rev'):
4511 4511 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
4512 4512 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4513 4513
4514 4514 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4515 4515 for rev in revs:
4516 4516 if count == limit:
4517 4517 break
4518 4518 ctx = repo[rev]
4519 4519 copies = None
4520 4520 if getrenamed is not None and rev:
4521 4521 copies = []
4522 4522 for fn in ctx.files():
4523 4523 rename = getrenamed(fn, rev)
4524 4524 if rename:
4525 4525 copies.append((fn, rename[0]))
4526 4526 if filematcher:
4527 4527 revmatchfn = filematcher(ctx.rev())
4528 4528 else:
4529 4529 revmatchfn = None
4530 4530 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4531 4531 if displayer.flush(rev):
4532 4532 count += 1
4533 4533
4534 4534 displayer.close()
4535 4535
4536 4536 @command('manifest',
4537 4537 [('r', 'rev', '', _('revision to display'), _('REV')),
4538 4538 ('', 'all', False, _("list files from all revisions"))]
4539 4539 + formatteropts,
4540 4540 _('[-r REV]'))
4541 4541 def manifest(ui, repo, node=None, rev=None, **opts):
4542 4542 """output the current or given revision of the project manifest
4543 4543
4544 4544 Print a list of version controlled files for the given revision.
4545 4545 If no revision is given, the first parent of the working directory
4546 4546 is used, or the null revision if no revision is checked out.
4547 4547
4548 4548 With -v, print file permissions, symlink and executable bits.
4549 4549 With --debug, print file revision hashes.
4550 4550
4551 4551 If option --all is specified, the list of all files from all revisions
4552 4552 is printed. This includes deleted and renamed files.
4553 4553
4554 4554 Returns 0 on success.
4555 4555 """
4556 4556
4557 4557 fm = ui.formatter('manifest', opts)
4558 4558
4559 4559 if opts.get('all'):
4560 4560 if rev or node:
4561 4561 raise util.Abort(_("can't specify a revision with --all"))
4562 4562
4563 4563 res = []
4564 4564 prefix = "data/"
4565 4565 suffix = ".i"
4566 4566 plen = len(prefix)
4567 4567 slen = len(suffix)
4568 4568 lock = repo.lock()
4569 4569 try:
4570 4570 for fn, b, size in repo.store.datafiles():
4571 4571 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4572 4572 res.append(fn[plen:-slen])
4573 4573 finally:
4574 4574 lock.release()
4575 4575 for f in res:
4576 4576 fm.startitem()
4577 4577 fm.write("path", '%s\n', f)
4578 4578 fm.end()
4579 4579 return
4580 4580
4581 4581 if rev and node:
4582 4582 raise util.Abort(_("please specify just one revision"))
4583 4583
4584 4584 if not node:
4585 4585 node = rev
4586 4586
4587 4587 char = {'l': '@', 'x': '*', '': ''}
4588 4588 mode = {'l': '644', 'x': '755', '': '644'}
4589 4589 ctx = scmutil.revsingle(repo, node)
4590 4590 mf = ctx.manifest()
4591 4591 for f in ctx:
4592 4592 fm.startitem()
4593 4593 fl = ctx[f].flags()
4594 4594 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
4595 4595 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
4596 4596 fm.write('path', '%s\n', f)
4597 4597 fm.end()
4598 4598
4599 4599 @command('^merge',
4600 4600 [('f', 'force', None,
4601 4601 _('force a merge including outstanding changes (DEPRECATED)')),
4602 4602 ('r', 'rev', '', _('revision to merge'), _('REV')),
4603 4603 ('P', 'preview', None,
4604 4604 _('review revisions to merge (no merge is performed)'))
4605 4605 ] + mergetoolopts,
4606 4606 _('[-P] [-f] [[-r] REV]'))
4607 4607 def merge(ui, repo, node=None, **opts):
4608 4608 """merge another revision into working directory
4609 4609
4610 4610 The current working directory is updated with all changes made in
4611 4611 the requested revision since the last common predecessor revision.
4612 4612
4613 4613 Files that changed between either parent are marked as changed for
4614 4614 the next commit and a commit must be performed before any further
4615 4615 updates to the repository are allowed. The next commit will have
4616 4616 two parents.
4617 4617
4618 4618 ``--tool`` can be used to specify the merge tool used for file
4619 4619 merges. It overrides the HGMERGE environment variable and your
4620 4620 configuration files. See :hg:`help merge-tools` for options.
4621 4621
4622 4622 If no revision is specified, the working directory's parent is a
4623 4623 head revision, and the current branch contains exactly one other
4624 4624 head, the other head is merged with by default. Otherwise, an
4625 4625 explicit revision with which to merge with must be provided.
4626 4626
4627 4627 :hg:`resolve` must be used to resolve unresolved files.
4628 4628
4629 4629 To undo an uncommitted merge, use :hg:`update --clean .` which
4630 4630 will check out a clean copy of the original merge parent, losing
4631 4631 all changes.
4632 4632
4633 4633 Returns 0 on success, 1 if there are unresolved files.
4634 4634 """
4635 4635
4636 4636 if opts.get('rev') and node:
4637 4637 raise util.Abort(_("please specify just one revision"))
4638 4638 if not node:
4639 4639 node = opts.get('rev')
4640 4640
4641 4641 if node:
4642 4642 node = scmutil.revsingle(repo, node).node()
4643 4643
4644 4644 if not node and repo._bookmarkcurrent:
4645 4645 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4646 4646 curhead = repo[repo._bookmarkcurrent].node()
4647 4647 if len(bmheads) == 2:
4648 4648 if curhead == bmheads[0]:
4649 4649 node = bmheads[1]
4650 4650 else:
4651 4651 node = bmheads[0]
4652 4652 elif len(bmheads) > 2:
4653 4653 raise util.Abort(_("multiple matching bookmarks to merge - "
4654 4654 "please merge with an explicit rev or bookmark"),
4655 4655 hint=_("run 'hg heads' to see all heads"))
4656 4656 elif len(bmheads) <= 1:
4657 4657 raise util.Abort(_("no matching bookmark to merge - "
4658 4658 "please merge with an explicit rev or bookmark"),
4659 4659 hint=_("run 'hg heads' to see all heads"))
4660 4660
4661 4661 if not node and not repo._bookmarkcurrent:
4662 4662 branch = repo[None].branch()
4663 4663 bheads = repo.branchheads(branch)
4664 4664 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4665 4665
4666 4666 if len(nbhs) > 2:
4667 4667 raise util.Abort(_("branch '%s' has %d heads - "
4668 4668 "please merge with an explicit rev")
4669 4669 % (branch, len(bheads)),
4670 4670 hint=_("run 'hg heads .' to see heads"))
4671 4671
4672 4672 parent = repo.dirstate.p1()
4673 4673 if len(nbhs) <= 1:
4674 4674 if len(bheads) > 1:
4675 4675 raise util.Abort(_("heads are bookmarked - "
4676 4676 "please merge with an explicit rev"),
4677 4677 hint=_("run 'hg heads' to see all heads"))
4678 4678 if len(repo.heads()) > 1:
4679 4679 raise util.Abort(_("branch '%s' has one head - "
4680 4680 "please merge with an explicit rev")
4681 4681 % branch,
4682 4682 hint=_("run 'hg heads' to see all heads"))
4683 4683 msg, hint = _('nothing to merge'), None
4684 4684 if parent != repo.lookup(branch):
4685 4685 hint = _("use 'hg update' instead")
4686 4686 raise util.Abort(msg, hint=hint)
4687 4687
4688 4688 if parent not in bheads:
4689 4689 raise util.Abort(_('working directory not at a head revision'),
4690 4690 hint=_("use 'hg update' or merge with an "
4691 4691 "explicit revision"))
4692 4692 if parent == nbhs[0]:
4693 4693 node = nbhs[-1]
4694 4694 else:
4695 4695 node = nbhs[0]
4696 4696
4697 4697 if opts.get('preview'):
4698 4698 # find nodes that are ancestors of p2 but not of p1
4699 4699 p1 = repo.lookup('.')
4700 4700 p2 = repo.lookup(node)
4701 4701 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4702 4702
4703 4703 displayer = cmdutil.show_changeset(ui, repo, opts)
4704 4704 for node in nodes:
4705 4705 displayer.show(repo[node])
4706 4706 displayer.close()
4707 4707 return 0
4708 4708
4709 4709 try:
4710 4710 # ui.forcemerge is an internal variable, do not document
4711 4711 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
4712 4712 return hg.merge(repo, node, force=opts.get('force'))
4713 4713 finally:
4714 4714 ui.setconfig('ui', 'forcemerge', '', 'merge')
4715 4715
4716 4716 @command('outgoing|out',
4717 4717 [('f', 'force', None, _('run even when the destination is unrelated')),
4718 4718 ('r', 'rev', [],
4719 4719 _('a changeset intended to be included in the destination'), _('REV')),
4720 4720 ('n', 'newest-first', None, _('show newest record first')),
4721 4721 ('B', 'bookmarks', False, _('compare bookmarks')),
4722 4722 ('b', 'branch', [], _('a specific branch you would like to push'),
4723 4723 _('BRANCH')),
4724 4724 ] + logopts + remoteopts + subrepoopts,
4725 4725 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4726 4726 def outgoing(ui, repo, dest=None, **opts):
4727 4727 """show changesets not found in the destination
4728 4728
4729 4729 Show changesets not found in the specified destination repository
4730 4730 or the default push location. These are the changesets that would
4731 4731 be pushed if a push was requested.
4732 4732
4733 4733 See pull for details of valid destination formats.
4734 4734
4735 4735 Returns 0 if there are outgoing changes, 1 otherwise.
4736 4736 """
4737 4737 if opts.get('graph'):
4738 4738 cmdutil.checkunsupportedgraphflags([], opts)
4739 4739 o, other = hg._outgoing(ui, repo, dest, opts)
4740 4740 if not o:
4741 4741 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4742 4742 return
4743 4743
4744 4744 revdag = cmdutil.graphrevs(repo, o, opts)
4745 4745 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4746 4746 showparents = [ctx.node() for ctx in repo[None].parents()]
4747 4747 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4748 4748 graphmod.asciiedges)
4749 4749 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4750 4750 return 0
4751 4751
4752 4752 if opts.get('bookmarks'):
4753 4753 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4754 4754 dest, branches = hg.parseurl(dest, opts.get('branch'))
4755 4755 other = hg.peer(repo, opts, dest)
4756 4756 if 'bookmarks' not in other.listkeys('namespaces'):
4757 4757 ui.warn(_("remote doesn't support bookmarks\n"))
4758 4758 return 0
4759 4759 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4760 4760 return bookmarks.diff(ui, other, repo)
4761 4761
4762 4762 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4763 4763 try:
4764 4764 return hg.outgoing(ui, repo, dest, opts)
4765 4765 finally:
4766 4766 del repo._subtoppath
4767 4767
4768 4768 @command('parents',
4769 4769 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4770 4770 ] + templateopts,
4771 4771 _('[-r REV] [FILE]'),
4772 4772 inferrepo=True)
4773 4773 def parents(ui, repo, file_=None, **opts):
4774 4774 """show the parents of the working directory or revision (DEPRECATED)
4775 4775
4776 4776 Print the working directory's parent revisions. If a revision is
4777 4777 given via -r/--rev, the parent of that revision will be printed.
4778 4778 If a file argument is given, the revision in which the file was
4779 4779 last changed (before the working directory revision or the
4780 4780 argument to --rev if given) is printed.
4781 4781
4782 4782 See :hg:`summary` and :hg:`help revsets` for related information.
4783 4783
4784 4784 Returns 0 on success.
4785 4785 """
4786 4786
4787 4787 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4788 4788
4789 4789 if file_:
4790 4790 m = scmutil.match(ctx, (file_,), opts)
4791 4791 if m.anypats() or len(m.files()) != 1:
4792 4792 raise util.Abort(_('can only specify an explicit filename'))
4793 4793 file_ = m.files()[0]
4794 4794 filenodes = []
4795 4795 for cp in ctx.parents():
4796 4796 if not cp:
4797 4797 continue
4798 4798 try:
4799 4799 filenodes.append(cp.filenode(file_))
4800 4800 except error.LookupError:
4801 4801 pass
4802 4802 if not filenodes:
4803 4803 raise util.Abort(_("'%s' not found in manifest!") % file_)
4804 4804 p = []
4805 4805 for fn in filenodes:
4806 4806 fctx = repo.filectx(file_, fileid=fn)
4807 4807 p.append(fctx.node())
4808 4808 else:
4809 4809 p = [cp.node() for cp in ctx.parents()]
4810 4810
4811 4811 displayer = cmdutil.show_changeset(ui, repo, opts)
4812 4812 for n in p:
4813 4813 if n != nullid:
4814 4814 displayer.show(repo[n])
4815 4815 displayer.close()
4816 4816
4817 4817 @command('paths', [], _('[NAME]'), optionalrepo=True)
4818 4818 def paths(ui, repo, search=None):
4819 4819 """show aliases for remote repositories
4820 4820
4821 4821 Show definition of symbolic path name NAME. If no name is given,
4822 4822 show definition of all available names.
4823 4823
4824 4824 Option -q/--quiet suppresses all output when searching for NAME
4825 4825 and shows only the path names when listing all definitions.
4826 4826
4827 4827 Path names are defined in the [paths] section of your
4828 4828 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4829 4829 repository, ``.hg/hgrc`` is used, too.
4830 4830
4831 4831 The path names ``default`` and ``default-push`` have a special
4832 4832 meaning. When performing a push or pull operation, they are used
4833 4833 as fallbacks if no location is specified on the command-line.
4834 4834 When ``default-push`` is set, it will be used for push and
4835 4835 ``default`` will be used for pull; otherwise ``default`` is used
4836 4836 as the fallback for both. When cloning a repository, the clone
4837 4837 source is written as ``default`` in ``.hg/hgrc``. Note that
4838 4838 ``default`` and ``default-push`` apply to all inbound (e.g.
4839 4839 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4840 4840 :hg:`bundle`) operations.
4841 4841
4842 4842 See :hg:`help urls` for more information.
4843 4843
4844 4844 Returns 0 on success.
4845 4845 """
4846 4846 if search:
4847 4847 for name, path in sorted(ui.paths.iteritems()):
4848 4848 if name == search:
4849 4849 ui.status("%s\n" % util.hidepassword(path.loc))
4850 4850 return
4851 4851 if not ui.quiet:
4852 4852 ui.warn(_("not found!\n"))
4853 4853 return 1
4854 4854 else:
4855 4855 for name, path in sorted(ui.paths.iteritems()):
4856 4856 if ui.quiet:
4857 4857 ui.write("%s\n" % name)
4858 4858 else:
4859 4859 ui.write("%s = %s\n" % (name,
4860 4860 util.hidepassword(path.loc)))
4861 4861
4862 4862 @command('phase',
4863 4863 [('p', 'public', False, _('set changeset phase to public')),
4864 4864 ('d', 'draft', False, _('set changeset phase to draft')),
4865 4865 ('s', 'secret', False, _('set changeset phase to secret')),
4866 4866 ('f', 'force', False, _('allow to move boundary backward')),
4867 4867 ('r', 'rev', [], _('target revision'), _('REV')),
4868 4868 ],
4869 4869 _('[-p|-d|-s] [-f] [-r] REV...'))
4870 4870 def phase(ui, repo, *revs, **opts):
4871 4871 """set or show the current phase name
4872 4872
4873 4873 With no argument, show the phase name of specified revisions.
4874 4874
4875 4875 With one of -p/--public, -d/--draft or -s/--secret, change the
4876 4876 phase value of the specified revisions.
4877 4877
4878 4878 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4879 4879 lower phase to an higher phase. Phases are ordered as follows::
4880 4880
4881 4881 public < draft < secret
4882 4882
4883 4883 Returns 0 on success, 1 if no phases were changed or some could not
4884 4884 be changed.
4885 4885 """
4886 4886 # search for a unique phase argument
4887 4887 targetphase = None
4888 4888 for idx, name in enumerate(phases.phasenames):
4889 4889 if opts[name]:
4890 4890 if targetphase is not None:
4891 4891 raise util.Abort(_('only one phase can be specified'))
4892 4892 targetphase = idx
4893 4893
4894 4894 # look for specified revision
4895 4895 revs = list(revs)
4896 4896 revs.extend(opts['rev'])
4897 4897 if not revs:
4898 4898 raise util.Abort(_('no revisions specified'))
4899 4899
4900 4900 revs = scmutil.revrange(repo, revs)
4901 4901
4902 4902 lock = None
4903 4903 ret = 0
4904 4904 if targetphase is None:
4905 4905 # display
4906 4906 for r in revs:
4907 4907 ctx = repo[r]
4908 4908 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4909 4909 else:
4910 4910 tr = None
4911 4911 lock = repo.lock()
4912 4912 try:
4913 4913 tr = repo.transaction("phase")
4914 4914 # set phase
4915 4915 if not revs:
4916 4916 raise util.Abort(_('empty revision set'))
4917 4917 nodes = [repo[r].node() for r in revs]
4918 4918 # moving revision from public to draft may hide them
4919 4919 # We have to check result on an unfiltered repository
4920 4920 unfi = repo.unfiltered()
4921 4921 getphase = unfi._phasecache.phase
4922 4922 olddata = [getphase(unfi, r) for r in unfi]
4923 4923 phases.advanceboundary(repo, tr, targetphase, nodes)
4924 4924 if opts['force']:
4925 4925 phases.retractboundary(repo, tr, targetphase, nodes)
4926 4926 tr.close()
4927 4927 finally:
4928 4928 if tr is not None:
4929 4929 tr.release()
4930 4930 lock.release()
4931 4931 getphase = unfi._phasecache.phase
4932 4932 newdata = [getphase(unfi, r) for r in unfi]
4933 4933 changes = sum(newdata[r] != olddata[r] for r in unfi)
4934 4934 cl = unfi.changelog
4935 4935 rejected = [n for n in nodes
4936 4936 if newdata[cl.rev(n)] < targetphase]
4937 4937 if rejected:
4938 4938 ui.warn(_('cannot move %i changesets to a higher '
4939 4939 'phase, use --force\n') % len(rejected))
4940 4940 ret = 1
4941 4941 if changes:
4942 4942 msg = _('phase changed for %i changesets\n') % changes
4943 4943 if ret:
4944 4944 ui.status(msg)
4945 4945 else:
4946 4946 ui.note(msg)
4947 4947 else:
4948 4948 ui.warn(_('no phases changed\n'))
4949 4949 ret = 1
4950 4950 return ret
4951 4951
4952 4952 def postincoming(ui, repo, modheads, optupdate, checkout):
4953 4953 if modheads == 0:
4954 4954 return
4955 4955 if optupdate:
4956 4956 checkout, movemarkfrom = bookmarks.calculateupdate(ui, repo, checkout)
4957 4957 try:
4958 4958 ret = hg.update(repo, checkout)
4959 4959 except util.Abort, inst:
4960 4960 ui.warn(_("not updating: %s\n") % str(inst))
4961 4961 if inst.hint:
4962 4962 ui.warn(_("(%s)\n") % inst.hint)
4963 4963 return 0
4964 4964 if not ret and not checkout:
4965 4965 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4966 4966 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4967 4967 return ret
4968 4968 if modheads > 1:
4969 4969 currentbranchheads = len(repo.branchheads())
4970 4970 if currentbranchheads == modheads:
4971 4971 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4972 4972 elif currentbranchheads > 1:
4973 4973 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4974 4974 "merge)\n"))
4975 4975 else:
4976 4976 ui.status(_("(run 'hg heads' to see heads)\n"))
4977 4977 else:
4978 4978 ui.status(_("(run 'hg update' to get a working copy)\n"))
4979 4979
4980 4980 @command('^pull',
4981 4981 [('u', 'update', None,
4982 4982 _('update to new branch head if changesets were pulled')),
4983 4983 ('f', 'force', None, _('run even when remote repository is unrelated')),
4984 4984 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4985 4985 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4986 4986 ('b', 'branch', [], _('a specific branch you would like to pull'),
4987 4987 _('BRANCH')),
4988 4988 ] + remoteopts,
4989 4989 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4990 4990 def pull(ui, repo, source="default", **opts):
4991 4991 """pull changes from the specified source
4992 4992
4993 4993 Pull changes from a remote repository to a local one.
4994 4994
4995 4995 This finds all changes from the repository at the specified path
4996 4996 or URL and adds them to a local repository (the current one unless
4997 4997 -R is specified). By default, this does not update the copy of the
4998 4998 project in the working directory.
4999 4999
5000 5000 Use :hg:`incoming` if you want to see what would have been added
5001 5001 by a pull at the time you issued this command. If you then decide
5002 5002 to add those changes to the repository, you should use :hg:`pull
5003 5003 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5004 5004
5005 5005 If SOURCE is omitted, the 'default' path will be used.
5006 5006 See :hg:`help urls` for more information.
5007 5007
5008 5008 Returns 0 on success, 1 if an update had unresolved files.
5009 5009 """
5010 5010 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
5011 5011 ui.status(_('pulling from %s\n') % util.hidepassword(source))
5012 5012 other = hg.peer(repo, opts, source)
5013 5013 try:
5014 5014 revs, checkout = hg.addbranchrevs(repo, other, branches,
5015 5015 opts.get('rev'))
5016 5016
5017 5017 remotebookmarks = other.listkeys('bookmarks')
5018 5018
5019 5019 if opts.get('bookmark'):
5020 5020 if not revs:
5021 5021 revs = []
5022 5022 for b in opts['bookmark']:
5023 5023 if b not in remotebookmarks:
5024 5024 raise util.Abort(_('remote bookmark %s not found!') % b)
5025 5025 revs.append(remotebookmarks[b])
5026 5026
5027 5027 if revs:
5028 5028 try:
5029 5029 revs = [other.lookup(rev) for rev in revs]
5030 5030 except error.CapabilityError:
5031 5031 err = _("other repository doesn't support revision lookup, "
5032 5032 "so a rev cannot be specified.")
5033 5033 raise util.Abort(err)
5034 5034
5035 5035 modheads = exchange.pull(repo, other, heads=revs,
5036 5036 force=opts.get('force'),
5037 5037 bookmarks=opts.get('bookmark', ())).cgresult
5038 5038 if checkout:
5039 5039 checkout = str(repo.changelog.rev(other.lookup(checkout)))
5040 5040 repo._subtoppath = source
5041 5041 try:
5042 5042 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
5043 5043
5044 5044 finally:
5045 5045 del repo._subtoppath
5046 5046
5047 5047 finally:
5048 5048 other.close()
5049 5049 return ret
5050 5050
5051 5051 @command('^push',
5052 5052 [('f', 'force', None, _('force push')),
5053 5053 ('r', 'rev', [],
5054 5054 _('a changeset intended to be included in the destination'),
5055 5055 _('REV')),
5056 5056 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
5057 5057 ('b', 'branch', [],
5058 5058 _('a specific branch you would like to push'), _('BRANCH')),
5059 5059 ('', 'new-branch', False, _('allow pushing a new branch')),
5060 5060 ] + remoteopts,
5061 5061 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
5062 5062 def push(ui, repo, dest=None, **opts):
5063 5063 """push changes to the specified destination
5064 5064
5065 5065 Push changesets from the local repository to the specified
5066 5066 destination.
5067 5067
5068 5068 This operation is symmetrical to pull: it is identical to a pull
5069 5069 in the destination repository from the current one.
5070 5070
5071 5071 By default, push will not allow creation of new heads at the
5072 5072 destination, since multiple heads would make it unclear which head
5073 5073 to use. In this situation, it is recommended to pull and merge
5074 5074 before pushing.
5075 5075
5076 5076 Use --new-branch if you want to allow push to create a new named
5077 5077 branch that is not present at the destination. This allows you to
5078 5078 only create a new branch without forcing other changes.
5079 5079
5080 5080 .. note::
5081 5081
5082 5082 Extra care should be taken with the -f/--force option,
5083 5083 which will push all new heads on all branches, an action which will
5084 5084 almost always cause confusion for collaborators.
5085 5085
5086 5086 If -r/--rev is used, the specified revision and all its ancestors
5087 5087 will be pushed to the remote repository.
5088 5088
5089 5089 If -B/--bookmark is used, the specified bookmarked revision, its
5090 5090 ancestors, and the bookmark will be pushed to the remote
5091 5091 repository.
5092 5092
5093 5093 Please see :hg:`help urls` for important details about ``ssh://``
5094 5094 URLs. If DESTINATION is omitted, a default path will be used.
5095 5095
5096 5096 Returns 0 if push was successful, 1 if nothing to push.
5097 5097 """
5098 5098
5099 5099 if opts.get('bookmark'):
5100 5100 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
5101 5101 for b in opts['bookmark']:
5102 5102 # translate -B options to -r so changesets get pushed
5103 5103 if b in repo._bookmarks:
5104 5104 opts.setdefault('rev', []).append(b)
5105 5105 else:
5106 5106 # if we try to push a deleted bookmark, translate it to null
5107 5107 # this lets simultaneous -r, -b options continue working
5108 5108 opts.setdefault('rev', []).append("null")
5109 5109
5110 5110 dest = ui.expandpath(dest or 'default-push', dest or 'default')
5111 5111 dest, branches = hg.parseurl(dest, opts.get('branch'))
5112 5112 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
5113 5113 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
5114 5114 try:
5115 5115 other = hg.peer(repo, opts, dest)
5116 5116 except error.RepoError:
5117 5117 if dest == "default-push":
5118 5118 raise util.Abort(_("default repository not configured!"),
5119 5119 hint=_('see the "path" section in "hg help config"'))
5120 5120 else:
5121 5121 raise
5122 5122
5123 5123 if revs:
5124 5124 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
5125 5125
5126 5126 repo._subtoppath = dest
5127 5127 try:
5128 5128 # push subrepos depth-first for coherent ordering
5129 5129 c = repo['']
5130 5130 subs = c.substate # only repos that are committed
5131 5131 for s in sorted(subs):
5132 5132 result = c.sub(s).push(opts)
5133 5133 if result == 0:
5134 5134 return not result
5135 5135 finally:
5136 5136 del repo._subtoppath
5137 5137 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
5138 5138 newbranch=opts.get('new_branch'),
5139 5139 bookmarks=opts.get('bookmark', ()))
5140 5140
5141 5141 result = not pushop.cgresult
5142 5142
5143 5143 if pushop.bkresult is not None:
5144 5144 if pushop.bkresult == 2:
5145 5145 result = 2
5146 5146 elif not result and pushop.bkresult:
5147 5147 result = 2
5148 5148
5149 5149 return result
5150 5150
5151 5151 @command('recover', [])
5152 5152 def recover(ui, repo):
5153 5153 """roll back an interrupted transaction
5154 5154
5155 5155 Recover from an interrupted commit or pull.
5156 5156
5157 5157 This command tries to fix the repository status after an
5158 5158 interrupted operation. It should only be necessary when Mercurial
5159 5159 suggests it.
5160 5160
5161 5161 Returns 0 if successful, 1 if nothing to recover or verify fails.
5162 5162 """
5163 5163 if repo.recover():
5164 5164 return hg.verify(repo)
5165 5165 return 1
5166 5166
5167 5167 @command('^remove|rm',
5168 5168 [('A', 'after', None, _('record delete for missing files')),
5169 5169 ('f', 'force', None,
5170 5170 _('remove (and delete) file even if added or modified')),
5171 5171 ] + subrepoopts + walkopts,
5172 5172 _('[OPTION]... FILE...'),
5173 5173 inferrepo=True)
5174 5174 def remove(ui, repo, *pats, **opts):
5175 5175 """remove the specified files on the next commit
5176 5176
5177 5177 Schedule the indicated files for removal from the current branch.
5178 5178
5179 5179 This command schedules the files to be removed at the next commit.
5180 5180 To undo a remove before that, see :hg:`revert`. To undo added
5181 5181 files, see :hg:`forget`.
5182 5182
5183 5183 .. container:: verbose
5184 5184
5185 5185 -A/--after can be used to remove only files that have already
5186 5186 been deleted, -f/--force can be used to force deletion, and -Af
5187 5187 can be used to remove files from the next revision without
5188 5188 deleting them from the working directory.
5189 5189
5190 5190 The following table details the behavior of remove for different
5191 5191 file states (columns) and option combinations (rows). The file
5192 5192 states are Added [A], Clean [C], Modified [M] and Missing [!]
5193 5193 (as reported by :hg:`status`). The actions are Warn, Remove
5194 5194 (from branch) and Delete (from disk):
5195 5195
5196 5196 ========= == == == ==
5197 5197 opt/state A C M !
5198 5198 ========= == == == ==
5199 5199 none W RD W R
5200 5200 -f R RD RD R
5201 5201 -A W W W R
5202 5202 -Af R R R R
5203 5203 ========= == == == ==
5204 5204
5205 5205 Note that remove never deletes files in Added [A] state from the
5206 5206 working directory, not even if option --force is specified.
5207 5207
5208 5208 Returns 0 on success, 1 if any warnings encountered.
5209 5209 """
5210 5210
5211 5211 after, force = opts.get('after'), opts.get('force')
5212 5212 if not pats and not after:
5213 5213 raise util.Abort(_('no files specified'))
5214 5214
5215 5215 m = scmutil.match(repo[None], pats, opts)
5216 5216 subrepos = opts.get('subrepos')
5217 5217 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
5218 5218
5219 5219 @command('rename|move|mv',
5220 5220 [('A', 'after', None, _('record a rename that has already occurred')),
5221 5221 ('f', 'force', None, _('forcibly copy over an existing managed file')),
5222 5222 ] + walkopts + dryrunopts,
5223 5223 _('[OPTION]... SOURCE... DEST'))
5224 5224 def rename(ui, repo, *pats, **opts):
5225 5225 """rename files; equivalent of copy + remove
5226 5226
5227 5227 Mark dest as copies of sources; mark sources for deletion. If dest
5228 5228 is a directory, copies are put in that directory. If dest is a
5229 5229 file, there can only be one source.
5230 5230
5231 5231 By default, this command copies the contents of files as they
5232 5232 exist in the working directory. If invoked with -A/--after, the
5233 5233 operation is recorded, but no copying is performed.
5234 5234
5235 5235 This command takes effect at the next commit. To undo a rename
5236 5236 before that, see :hg:`revert`.
5237 5237
5238 5238 Returns 0 on success, 1 if errors are encountered.
5239 5239 """
5240 5240 wlock = repo.wlock(False)
5241 5241 try:
5242 5242 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5243 5243 finally:
5244 5244 wlock.release()
5245 5245
5246 5246 @command('resolve',
5247 5247 [('a', 'all', None, _('select all unresolved files')),
5248 5248 ('l', 'list', None, _('list state of files needing merge')),
5249 5249 ('m', 'mark', None, _('mark files as resolved')),
5250 5250 ('u', 'unmark', None, _('mark files as unresolved')),
5251 5251 ('n', 'no-status', None, _('hide status prefix'))]
5252 5252 + mergetoolopts + walkopts + formatteropts,
5253 5253 _('[OPTION]... [FILE]...'),
5254 5254 inferrepo=True)
5255 5255 def resolve(ui, repo, *pats, **opts):
5256 5256 """redo merges or set/view the merge status of files
5257 5257
5258 5258 Merges with unresolved conflicts are often the result of
5259 5259 non-interactive merging using the ``internal:merge`` configuration
5260 5260 setting, or a command-line merge tool like ``diff3``. The resolve
5261 5261 command is used to manage the files involved in a merge, after
5262 5262 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5263 5263 working directory must have two parents). See :hg:`help
5264 5264 merge-tools` for information on configuring merge tools.
5265 5265
5266 5266 The resolve command can be used in the following ways:
5267 5267
5268 5268 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
5269 5269 files, discarding any previous merge attempts. Re-merging is not
5270 5270 performed for files already marked as resolved. Use ``--all/-a``
5271 5271 to select all unresolved files. ``--tool`` can be used to specify
5272 5272 the merge tool used for the given files. It overrides the HGMERGE
5273 5273 environment variable and your configuration files. Previous file
5274 5274 contents are saved with a ``.orig`` suffix.
5275 5275
5276 5276 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5277 5277 (e.g. after having manually fixed-up the files). The default is
5278 5278 to mark all unresolved files.
5279 5279
5280 5280 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5281 5281 default is to mark all resolved files.
5282 5282
5283 5283 - :hg:`resolve -l`: list files which had or still have conflicts.
5284 5284 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5285 5285
5286 5286 Note that Mercurial will not let you commit files with unresolved
5287 5287 merge conflicts. You must use :hg:`resolve -m ...` before you can
5288 5288 commit after a conflicting merge.
5289 5289
5290 5290 Returns 0 on success, 1 if any files fail a resolve attempt.
5291 5291 """
5292 5292
5293 5293 all, mark, unmark, show, nostatus = \
5294 5294 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
5295 5295
5296 5296 if (show and (mark or unmark)) or (mark and unmark):
5297 5297 raise util.Abort(_("too many options specified"))
5298 5298 if pats and all:
5299 5299 raise util.Abort(_("can't specify --all and patterns"))
5300 5300 if not (all or pats or show or mark or unmark):
5301 5301 raise util.Abort(_('no files or directories specified'),
5302 5302 hint=('use --all to remerge all files'))
5303 5303
5304 5304 if show:
5305 5305 fm = ui.formatter('resolve', opts)
5306 5306 ms = mergemod.mergestate(repo)
5307 5307 m = scmutil.match(repo[None], pats, opts)
5308 5308 for f in ms:
5309 5309 if not m(f):
5310 5310 continue
5311 5311 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved'}[ms[f]]
5312 5312 fm.startitem()
5313 5313 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
5314 5314 fm.write('path', '%s\n', f, label=l)
5315 5315 fm.end()
5316 5316 return 0
5317 5317
5318 5318 wlock = repo.wlock()
5319 5319 try:
5320 5320 ms = mergemod.mergestate(repo)
5321 5321
5322 5322 if not (ms.active() or repo.dirstate.p2() != nullid):
5323 5323 raise util.Abort(
5324 5324 _('resolve command not applicable when not merging'))
5325 5325
5326 5326 m = scmutil.match(repo[None], pats, opts)
5327 5327 ret = 0
5328 5328 didwork = False
5329 5329
5330 5330 for f in ms:
5331 5331 if not m(f):
5332 5332 continue
5333 5333
5334 5334 didwork = True
5335 5335
5336 5336 if mark:
5337 5337 ms.mark(f, "r")
5338 5338 elif unmark:
5339 5339 ms.mark(f, "u")
5340 5340 else:
5341 5341 wctx = repo[None]
5342 5342
5343 5343 # backup pre-resolve (merge uses .orig for its own purposes)
5344 5344 a = repo.wjoin(f)
5345 5345 util.copyfile(a, a + ".resolve")
5346 5346
5347 5347 try:
5348 5348 # resolve file
5349 5349 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
5350 5350 'resolve')
5351 5351 if ms.resolve(f, wctx):
5352 5352 ret = 1
5353 5353 finally:
5354 5354 ui.setconfig('ui', 'forcemerge', '', 'resolve')
5355 5355 ms.commit()
5356 5356
5357 5357 # replace filemerge's .orig file with our resolve file
5358 5358 util.rename(a + ".resolve", a + ".orig")
5359 5359
5360 5360 ms.commit()
5361 5361
5362 5362 if not didwork and pats:
5363 5363 ui.warn(_("arguments do not match paths that need resolving\n"))
5364 5364
5365 5365 finally:
5366 5366 wlock.release()
5367 5367
5368 5368 # Nudge users into finishing an unfinished operation
5369 5369 if not list(ms.unresolved()):
5370 5370 ui.status(_('(no more unresolved files)\n'))
5371 5371
5372 5372 return ret
5373 5373
5374 5374 @command('revert',
5375 5375 [('a', 'all', None, _('revert all changes when no arguments given')),
5376 5376 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5377 5377 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5378 5378 ('C', 'no-backup', None, _('do not save backup copies of files')),
5379 5379 ('i', 'interactive', None, _('interactively select the changes')),
5380 5380 ] + walkopts + dryrunopts,
5381 5381 _('[OPTION]... [-r REV] [NAME]...'))
5382 5382 def revert(ui, repo, *pats, **opts):
5383 5383 """restore files to their checkout state
5384 5384
5385 5385 .. note::
5386 5386
5387 5387 To check out earlier revisions, you should use :hg:`update REV`.
5388 5388 To cancel an uncommitted merge (and lose your changes),
5389 5389 use :hg:`update --clean .`.
5390 5390
5391 5391 With no revision specified, revert the specified files or directories
5392 5392 to the contents they had in the parent of the working directory.
5393 5393 This restores the contents of files to an unmodified
5394 5394 state and unschedules adds, removes, copies, and renames. If the
5395 5395 working directory has two parents, you must explicitly specify a
5396 5396 revision.
5397 5397
5398 5398 Using the -r/--rev or -d/--date options, revert the given files or
5399 5399 directories to their states as of a specific revision. Because
5400 5400 revert does not change the working directory parents, this will
5401 5401 cause these files to appear modified. This can be helpful to "back
5402 5402 out" some or all of an earlier change. See :hg:`backout` for a
5403 5403 related method.
5404 5404
5405 5405 Modified files are saved with a .orig suffix before reverting.
5406 5406 To disable these backups, use --no-backup.
5407 5407
5408 5408 See :hg:`help dates` for a list of formats valid for -d/--date.
5409 5409
5410 5410 Returns 0 on success.
5411 5411 """
5412 5412
5413 5413 if opts.get("date"):
5414 5414 if opts.get("rev"):
5415 5415 raise util.Abort(_("you can't specify a revision and a date"))
5416 5416 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5417 5417
5418 5418 parent, p2 = repo.dirstate.parents()
5419 5419 if not opts.get('rev') and p2 != nullid:
5420 5420 # revert after merge is a trap for new users (issue2915)
5421 5421 raise util.Abort(_('uncommitted merge with no revision specified'),
5422 5422 hint=_('use "hg update" or see "hg help revert"'))
5423 5423
5424 5424 ctx = scmutil.revsingle(repo, opts.get('rev'))
5425 5425
5426 5426 if not pats and not opts.get('all'):
5427 5427 msg = _("no files or directories specified")
5428 5428 if p2 != nullid:
5429 5429 hint = _("uncommitted merge, use --all to discard all changes,"
5430 5430 " or 'hg update -C .' to abort the merge")
5431 5431 raise util.Abort(msg, hint=hint)
5432 5432 dirty = util.any(repo.status())
5433 5433 node = ctx.node()
5434 5434 if node != parent:
5435 5435 if dirty:
5436 5436 hint = _("uncommitted changes, use --all to discard all"
5437 5437 " changes, or 'hg update %s' to update") % ctx.rev()
5438 5438 else:
5439 5439 hint = _("use --all to revert all files,"
5440 5440 " or 'hg update %s' to update") % ctx.rev()
5441 5441 elif dirty:
5442 5442 hint = _("uncommitted changes, use --all to discard all changes")
5443 5443 else:
5444 5444 hint = _("use --all to revert all files")
5445 5445 raise util.Abort(msg, hint=hint)
5446 5446
5447 5447 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5448 5448
5449 5449 @command('rollback', dryrunopts +
5450 5450 [('f', 'force', False, _('ignore safety measures'))])
5451 5451 def rollback(ui, repo, **opts):
5452 5452 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5453 5453
5454 5454 Please use :hg:`commit --amend` instead of rollback to correct
5455 5455 mistakes in the last commit.
5456 5456
5457 5457 This command should be used with care. There is only one level of
5458 5458 rollback, and there is no way to undo a rollback. It will also
5459 5459 restore the dirstate at the time of the last transaction, losing
5460 5460 any dirstate changes since that time. This command does not alter
5461 5461 the working directory.
5462 5462
5463 5463 Transactions are used to encapsulate the effects of all commands
5464 5464 that create new changesets or propagate existing changesets into a
5465 5465 repository.
5466 5466
5467 5467 .. container:: verbose
5468 5468
5469 5469 For example, the following commands are transactional, and their
5470 5470 effects can be rolled back:
5471 5471
5472 5472 - commit
5473 5473 - import
5474 5474 - pull
5475 5475 - push (with this repository as the destination)
5476 5476 - unbundle
5477 5477
5478 5478 To avoid permanent data loss, rollback will refuse to rollback a
5479 5479 commit transaction if it isn't checked out. Use --force to
5480 5480 override this protection.
5481 5481
5482 5482 This command is not intended for use on public repositories. Once
5483 5483 changes are visible for pull by other users, rolling a transaction
5484 5484 back locally is ineffective (someone else may already have pulled
5485 5485 the changes). Furthermore, a race is possible with readers of the
5486 5486 repository; for example an in-progress pull from the repository
5487 5487 may fail if a rollback is performed.
5488 5488
5489 5489 Returns 0 on success, 1 if no rollback data is available.
5490 5490 """
5491 5491 return repo.rollback(dryrun=opts.get('dry_run'),
5492 5492 force=opts.get('force'))
5493 5493
5494 5494 @command('root', [])
5495 5495 def root(ui, repo):
5496 5496 """print the root (top) of the current working directory
5497 5497
5498 5498 Print the root directory of the current repository.
5499 5499
5500 5500 Returns 0 on success.
5501 5501 """
5502 5502 ui.write(repo.root + "\n")
5503 5503
5504 5504 @command('^serve',
5505 5505 [('A', 'accesslog', '', _('name of access log file to write to'),
5506 5506 _('FILE')),
5507 5507 ('d', 'daemon', None, _('run server in background')),
5508 5508 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('FILE')),
5509 5509 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5510 5510 # use string type, then we can check if something was passed
5511 5511 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5512 5512 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5513 5513 _('ADDR')),
5514 5514 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5515 5515 _('PREFIX')),
5516 5516 ('n', 'name', '',
5517 5517 _('name to show in web pages (default: working directory)'), _('NAME')),
5518 5518 ('', 'web-conf', '',
5519 5519 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5520 5520 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5521 5521 _('FILE')),
5522 5522 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5523 5523 ('', 'stdio', None, _('for remote clients')),
5524 5524 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5525 5525 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5526 5526 ('', 'style', '', _('template style to use'), _('STYLE')),
5527 5527 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5528 5528 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5529 5529 _('[OPTION]...'),
5530 5530 optionalrepo=True)
5531 5531 def serve(ui, repo, **opts):
5532 5532 """start stand-alone webserver
5533 5533
5534 5534 Start a local HTTP repository browser and pull server. You can use
5535 5535 this for ad-hoc sharing and browsing of repositories. It is
5536 5536 recommended to use a real web server to serve a repository for
5537 5537 longer periods of time.
5538 5538
5539 5539 Please note that the server does not implement access control.
5540 5540 This means that, by default, anybody can read from the server and
5541 5541 nobody can write to it by default. Set the ``web.allow_push``
5542 5542 option to ``*`` to allow everybody to push to the server. You
5543 5543 should use a real web server if you need to authenticate users.
5544 5544
5545 5545 By default, the server logs accesses to stdout and errors to
5546 5546 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5547 5547 files.
5548 5548
5549 5549 To have the server choose a free port number to listen on, specify
5550 5550 a port number of 0; in this case, the server will print the port
5551 5551 number it uses.
5552 5552
5553 5553 Returns 0 on success.
5554 5554 """
5555 5555
5556 5556 if opts["stdio"] and opts["cmdserver"]:
5557 5557 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5558 5558
5559 5559 if opts["stdio"]:
5560 5560 if repo is None:
5561 5561 raise error.RepoError(_("there is no Mercurial repository here"
5562 5562 " (.hg not found)"))
5563 5563 s = sshserver.sshserver(ui, repo)
5564 5564 s.serve_forever()
5565 5565
5566 5566 if opts["cmdserver"]:
5567 5567 service = commandserver.createservice(ui, repo, opts)
5568 5568 return cmdutil.service(opts, initfn=service.init, runfn=service.run)
5569 5569
5570 5570 # this way we can check if something was given in the command-line
5571 5571 if opts.get('port'):
5572 5572 opts['port'] = util.getport(opts.get('port'))
5573 5573
5574 5574 if repo:
5575 5575 baseui = repo.baseui
5576 5576 else:
5577 5577 baseui = ui
5578 5578 optlist = ("name templates style address port prefix ipv6"
5579 5579 " accesslog errorlog certificate encoding")
5580 5580 for o in optlist.split():
5581 5581 val = opts.get(o, '')
5582 5582 if val in (None, ''): # should check against default options instead
5583 5583 continue
5584 5584 baseui.setconfig("web", o, val, 'serve')
5585 5585 if repo and repo.ui != baseui:
5586 5586 repo.ui.setconfig("web", o, val, 'serve')
5587 5587
5588 5588 o = opts.get('web_conf') or opts.get('webdir_conf')
5589 5589 if not o:
5590 5590 if not repo:
5591 5591 raise error.RepoError(_("there is no Mercurial repository"
5592 5592 " here (.hg not found)"))
5593 5593 o = repo
5594 5594
5595 5595 app = hgweb.hgweb(o, baseui=baseui)
5596 5596 service = httpservice(ui, app, opts)
5597 5597 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5598 5598
5599 5599 class httpservice(object):
5600 5600 def __init__(self, ui, app, opts):
5601 5601 self.ui = ui
5602 5602 self.app = app
5603 5603 self.opts = opts
5604 5604
5605 5605 def init(self):
5606 5606 util.setsignalhandler()
5607 5607 self.httpd = hgweb_server.create_server(self.ui, self.app)
5608 5608
5609 5609 if self.opts['port'] and not self.ui.verbose:
5610 5610 return
5611 5611
5612 5612 if self.httpd.prefix:
5613 5613 prefix = self.httpd.prefix.strip('/') + '/'
5614 5614 else:
5615 5615 prefix = ''
5616 5616
5617 5617 port = ':%d' % self.httpd.port
5618 5618 if port == ':80':
5619 5619 port = ''
5620 5620
5621 5621 bindaddr = self.httpd.addr
5622 5622 if bindaddr == '0.0.0.0':
5623 5623 bindaddr = '*'
5624 5624 elif ':' in bindaddr: # IPv6
5625 5625 bindaddr = '[%s]' % bindaddr
5626 5626
5627 5627 fqaddr = self.httpd.fqaddr
5628 5628 if ':' in fqaddr:
5629 5629 fqaddr = '[%s]' % fqaddr
5630 5630 if self.opts['port']:
5631 5631 write = self.ui.status
5632 5632 else:
5633 5633 write = self.ui.write
5634 5634 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5635 5635 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5636 5636 self.ui.flush() # avoid buffering of status message
5637 5637
5638 5638 def run(self):
5639 5639 self.httpd.serve_forever()
5640 5640
5641 5641
5642 5642 @command('^status|st',
5643 5643 [('A', 'all', None, _('show status of all files')),
5644 5644 ('m', 'modified', None, _('show only modified files')),
5645 5645 ('a', 'added', None, _('show only added files')),
5646 5646 ('r', 'removed', None, _('show only removed files')),
5647 5647 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5648 5648 ('c', 'clean', None, _('show only files without changes')),
5649 5649 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5650 5650 ('i', 'ignored', None, _('show only ignored files')),
5651 5651 ('n', 'no-status', None, _('hide status prefix')),
5652 5652 ('C', 'copies', None, _('show source of copied files')),
5653 5653 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5654 5654 ('', 'rev', [], _('show difference from revision'), _('REV')),
5655 5655 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5656 5656 ] + walkopts + subrepoopts + formatteropts,
5657 5657 _('[OPTION]... [FILE]...'),
5658 5658 inferrepo=True)
5659 5659 def status(ui, repo, *pats, **opts):
5660 5660 """show changed files in the working directory
5661 5661
5662 5662 Show status of files in the repository. If names are given, only
5663 5663 files that match are shown. Files that are clean or ignored or
5664 5664 the source of a copy/move operation, are not listed unless
5665 5665 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5666 5666 Unless options described with "show only ..." are given, the
5667 5667 options -mardu are used.
5668 5668
5669 5669 Option -q/--quiet hides untracked (unknown and ignored) files
5670 5670 unless explicitly requested with -u/--unknown or -i/--ignored.
5671 5671
5672 5672 .. note::
5673 5673
5674 5674 status may appear to disagree with diff if permissions have
5675 5675 changed or a merge has occurred. The standard diff format does
5676 5676 not report permission changes and diff only reports changes
5677 5677 relative to one merge parent.
5678 5678
5679 5679 If one revision is given, it is used as the base revision.
5680 5680 If two revisions are given, the differences between them are
5681 5681 shown. The --change option can also be used as a shortcut to list
5682 5682 the changed files of a revision from its first parent.
5683 5683
5684 5684 The codes used to show the status of files are::
5685 5685
5686 5686 M = modified
5687 5687 A = added
5688 5688 R = removed
5689 5689 C = clean
5690 5690 ! = missing (deleted by non-hg command, but still tracked)
5691 5691 ? = not tracked
5692 5692 I = ignored
5693 5693 = origin of the previous file (with --copies)
5694 5694
5695 5695 .. container:: verbose
5696 5696
5697 5697 Examples:
5698 5698
5699 5699 - show changes in the working directory relative to a
5700 5700 changeset::
5701 5701
5702 5702 hg status --rev 9353
5703 5703
5704 5704 - show all changes including copies in an existing changeset::
5705 5705
5706 5706 hg status --copies --change 9353
5707 5707
5708 5708 - get a NUL separated list of added files, suitable for xargs::
5709 5709
5710 5710 hg status -an0
5711 5711
5712 5712 Returns 0 on success.
5713 5713 """
5714 5714
5715 5715 revs = opts.get('rev')
5716 5716 change = opts.get('change')
5717 5717
5718 5718 if revs and change:
5719 5719 msg = _('cannot specify --rev and --change at the same time')
5720 5720 raise util.Abort(msg)
5721 5721 elif change:
5722 5722 node2 = scmutil.revsingle(repo, change, None).node()
5723 5723 node1 = repo[node2].p1().node()
5724 5724 else:
5725 5725 node1, node2 = scmutil.revpair(repo, revs)
5726 5726
5727 5727 if pats:
5728 5728 cwd = repo.getcwd()
5729 5729 else:
5730 5730 cwd = ''
5731 5731
5732 5732 if opts.get('print0'):
5733 5733 end = '\0'
5734 5734 else:
5735 5735 end = '\n'
5736 5736 copy = {}
5737 5737 states = 'modified added removed deleted unknown ignored clean'.split()
5738 5738 show = [k for k in states if opts.get(k)]
5739 5739 if opts.get('all'):
5740 5740 show += ui.quiet and (states[:4] + ['clean']) or states
5741 5741 if not show:
5742 5742 if ui.quiet:
5743 5743 show = states[:4]
5744 5744 else:
5745 5745 show = states[:5]
5746 5746
5747 5747 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5748 5748 'ignored' in show, 'clean' in show, 'unknown' in show,
5749 5749 opts.get('subrepos'))
5750 5750 changestates = zip(states, 'MAR!?IC', stat)
5751 5751
5752 5752 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5753 5753 copy = copies.pathcopies(repo[node1], repo[node2])
5754 5754
5755 5755 fm = ui.formatter('status', opts)
5756 5756 fmt = '%s' + end
5757 5757 showchar = not opts.get('no_status')
5758 5758
5759 5759 for state, char, files in changestates:
5760 5760 if state in show:
5761 5761 label = 'status.' + state
5762 5762 for f in files:
5763 5763 fm.startitem()
5764 5764 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5765 5765 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
5766 5766 if f in copy:
5767 5767 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5768 5768 label='status.copied')
5769 5769 fm.end()
5770 5770
5771 5771 @command('^summary|sum',
5772 5772 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5773 5773 def summary(ui, repo, **opts):
5774 5774 """summarize working directory state
5775 5775
5776 5776 This generates a brief summary of the working directory state,
5777 5777 including parents, branch, commit status, and available updates.
5778 5778
5779 5779 With the --remote option, this will check the default paths for
5780 5780 incoming and outgoing changes. This can be time-consuming.
5781 5781
5782 5782 Returns 0 on success.
5783 5783 """
5784 5784
5785 5785 ctx = repo[None]
5786 5786 parents = ctx.parents()
5787 5787 pnode = parents[0].node()
5788 5788 marks = []
5789 5789
5790 5790 for p in parents:
5791 5791 # label with log.changeset (instead of log.parent) since this
5792 5792 # shows a working directory parent *changeset*:
5793 5793 # i18n: column positioning for "hg summary"
5794 5794 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5795 5795 label='log.changeset changeset.%s' % p.phasestr())
5796 5796 ui.write(' '.join(p.tags()), label='log.tag')
5797 5797 if p.bookmarks():
5798 5798 marks.extend(p.bookmarks())
5799 5799 if p.rev() == -1:
5800 5800 if not len(repo):
5801 5801 ui.write(_(' (empty repository)'))
5802 5802 else:
5803 5803 ui.write(_(' (no revision checked out)'))
5804 5804 ui.write('\n')
5805 5805 if p.description():
5806 5806 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5807 5807 label='log.summary')
5808 5808
5809 5809 branch = ctx.branch()
5810 5810 bheads = repo.branchheads(branch)
5811 5811 # i18n: column positioning for "hg summary"
5812 5812 m = _('branch: %s\n') % branch
5813 5813 if branch != 'default':
5814 5814 ui.write(m, label='log.branch')
5815 5815 else:
5816 5816 ui.status(m, label='log.branch')
5817 5817
5818 5818 if marks:
5819 5819 current = repo._bookmarkcurrent
5820 5820 # i18n: column positioning for "hg summary"
5821 5821 ui.write(_('bookmarks:'), label='log.bookmark')
5822 5822 if current is not None:
5823 5823 if current in marks:
5824 5824 ui.write(' *' + current, label='bookmarks.current')
5825 5825 marks.remove(current)
5826 5826 else:
5827 5827 ui.write(' [%s]' % current, label='bookmarks.current')
5828 5828 for m in marks:
5829 5829 ui.write(' ' + m, label='log.bookmark')
5830 5830 ui.write('\n', label='log.bookmark')
5831 5831
5832 5832 status = repo.status(unknown=True)
5833 5833
5834 5834 c = repo.dirstate.copies()
5835 5835 copied, renamed = [], []
5836 5836 for d, s in c.iteritems():
5837 5837 if s in status.removed:
5838 5838 status.removed.remove(s)
5839 5839 renamed.append(d)
5840 5840 else:
5841 5841 copied.append(d)
5842 5842 if d in status.added:
5843 5843 status.added.remove(d)
5844 5844
5845 5845 ms = mergemod.mergestate(repo)
5846 5846 unresolved = [f for f in ms if ms[f] == 'u']
5847 5847
5848 5848 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5849 5849
5850 5850 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5851 5851 (ui.label(_('%d added'), 'status.added'), status.added),
5852 5852 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5853 5853 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5854 5854 (ui.label(_('%d copied'), 'status.copied'), copied),
5855 5855 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5856 5856 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5857 5857 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5858 5858 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5859 5859 t = []
5860 5860 for l, s in labels:
5861 5861 if s:
5862 5862 t.append(l % len(s))
5863 5863
5864 5864 t = ', '.join(t)
5865 5865 cleanworkdir = False
5866 5866
5867 5867 if repo.vfs.exists('updatestate'):
5868 5868 t += _(' (interrupted update)')
5869 5869 elif len(parents) > 1:
5870 5870 t += _(' (merge)')
5871 5871 elif branch != parents[0].branch():
5872 5872 t += _(' (new branch)')
5873 5873 elif (parents[0].closesbranch() and
5874 5874 pnode in repo.branchheads(branch, closed=True)):
5875 5875 t += _(' (head closed)')
5876 5876 elif not (status.modified or status.added or status.removed or renamed or
5877 5877 copied or subs):
5878 5878 t += _(' (clean)')
5879 5879 cleanworkdir = True
5880 5880 elif pnode not in bheads:
5881 5881 t += _(' (new branch head)')
5882 5882
5883 5883 if cleanworkdir:
5884 5884 # i18n: column positioning for "hg summary"
5885 5885 ui.status(_('commit: %s\n') % t.strip())
5886 5886 else:
5887 5887 # i18n: column positioning for "hg summary"
5888 5888 ui.write(_('commit: %s\n') % t.strip())
5889 5889
5890 5890 # all ancestors of branch heads - all ancestors of parent = new csets
5891 5891 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5892 5892 bheads))
5893 5893
5894 5894 if new == 0:
5895 5895 # i18n: column positioning for "hg summary"
5896 5896 ui.status(_('update: (current)\n'))
5897 5897 elif pnode not in bheads:
5898 5898 # i18n: column positioning for "hg summary"
5899 5899 ui.write(_('update: %d new changesets (update)\n') % new)
5900 5900 else:
5901 5901 # i18n: column positioning for "hg summary"
5902 5902 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5903 5903 (new, len(bheads)))
5904 5904
5905 5905 cmdutil.summaryhooks(ui, repo)
5906 5906
5907 5907 if opts.get('remote'):
5908 5908 needsincoming, needsoutgoing = True, True
5909 5909 else:
5910 5910 needsincoming, needsoutgoing = False, False
5911 5911 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5912 5912 if i:
5913 5913 needsincoming = True
5914 5914 if o:
5915 5915 needsoutgoing = True
5916 5916 if not needsincoming and not needsoutgoing:
5917 5917 return
5918 5918
5919 5919 def getincoming():
5920 5920 source, branches = hg.parseurl(ui.expandpath('default'))
5921 5921 sbranch = branches[0]
5922 5922 try:
5923 5923 other = hg.peer(repo, {}, source)
5924 5924 except error.RepoError:
5925 5925 if opts.get('remote'):
5926 5926 raise
5927 5927 return source, sbranch, None, None, None
5928 5928 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5929 5929 if revs:
5930 5930 revs = [other.lookup(rev) for rev in revs]
5931 5931 ui.debug('comparing with %s\n' % util.hidepassword(source))
5932 5932 repo.ui.pushbuffer()
5933 5933 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5934 5934 repo.ui.popbuffer()
5935 5935 return source, sbranch, other, commoninc, commoninc[1]
5936 5936
5937 5937 if needsincoming:
5938 5938 source, sbranch, sother, commoninc, incoming = getincoming()
5939 5939 else:
5940 5940 source = sbranch = sother = commoninc = incoming = None
5941 5941
5942 5942 def getoutgoing():
5943 5943 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5944 5944 dbranch = branches[0]
5945 5945 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5946 5946 if source != dest:
5947 5947 try:
5948 5948 dother = hg.peer(repo, {}, dest)
5949 5949 except error.RepoError:
5950 5950 if opts.get('remote'):
5951 5951 raise
5952 5952 return dest, dbranch, None, None
5953 5953 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5954 5954 elif sother is None:
5955 5955 # there is no explicit destination peer, but source one is invalid
5956 5956 return dest, dbranch, None, None
5957 5957 else:
5958 5958 dother = sother
5959 5959 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5960 5960 common = None
5961 5961 else:
5962 5962 common = commoninc
5963 5963 if revs:
5964 5964 revs = [repo.lookup(rev) for rev in revs]
5965 5965 repo.ui.pushbuffer()
5966 5966 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5967 5967 commoninc=common)
5968 5968 repo.ui.popbuffer()
5969 5969 return dest, dbranch, dother, outgoing
5970 5970
5971 5971 if needsoutgoing:
5972 5972 dest, dbranch, dother, outgoing = getoutgoing()
5973 5973 else:
5974 5974 dest = dbranch = dother = outgoing = None
5975 5975
5976 5976 if opts.get('remote'):
5977 5977 t = []
5978 5978 if incoming:
5979 5979 t.append(_('1 or more incoming'))
5980 5980 o = outgoing.missing
5981 5981 if o:
5982 5982 t.append(_('%d outgoing') % len(o))
5983 5983 other = dother or sother
5984 5984 if 'bookmarks' in other.listkeys('namespaces'):
5985 5985 lmarks = repo.listkeys('bookmarks')
5986 5986 rmarks = other.listkeys('bookmarks')
5987 5987 diff = set(rmarks) - set(lmarks)
5988 5988 if len(diff) > 0:
5989 5989 t.append(_('%d incoming bookmarks') % len(diff))
5990 5990 diff = set(lmarks) - set(rmarks)
5991 5991 if len(diff) > 0:
5992 5992 t.append(_('%d outgoing bookmarks') % len(diff))
5993 5993
5994 5994 if t:
5995 5995 # i18n: column positioning for "hg summary"
5996 5996 ui.write(_('remote: %s\n') % (', '.join(t)))
5997 5997 else:
5998 5998 # i18n: column positioning for "hg summary"
5999 5999 ui.status(_('remote: (synced)\n'))
6000 6000
6001 6001 cmdutil.summaryremotehooks(ui, repo, opts,
6002 6002 ((source, sbranch, sother, commoninc),
6003 6003 (dest, dbranch, dother, outgoing)))
6004 6004
6005 6005 @command('tag',
6006 6006 [('f', 'force', None, _('force tag')),
6007 6007 ('l', 'local', None, _('make the tag local')),
6008 6008 ('r', 'rev', '', _('revision to tag'), _('REV')),
6009 6009 ('', 'remove', None, _('remove a tag')),
6010 6010 # -l/--local is already there, commitopts cannot be used
6011 6011 ('e', 'edit', None, _('invoke editor on commit messages')),
6012 6012 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
6013 6013 ] + commitopts2,
6014 6014 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
6015 6015 def tag(ui, repo, name1, *names, **opts):
6016 6016 """add one or more tags for the current or given revision
6017 6017
6018 6018 Name a particular revision using <name>.
6019 6019
6020 6020 Tags are used to name particular revisions of the repository and are
6021 6021 very useful to compare different revisions, to go back to significant
6022 6022 earlier versions or to mark branch points as releases, etc. Changing
6023 6023 an existing tag is normally disallowed; use -f/--force to override.
6024 6024
6025 6025 If no revision is given, the parent of the working directory is
6026 6026 used.
6027 6027
6028 6028 To facilitate version control, distribution, and merging of tags,
6029 6029 they are stored as a file named ".hgtags" which is managed similarly
6030 6030 to other project files and can be hand-edited if necessary. This
6031 6031 also means that tagging creates a new commit. The file
6032 6032 ".hg/localtags" is used for local tags (not shared among
6033 6033 repositories).
6034 6034
6035 6035 Tag commits are usually made at the head of a branch. If the parent
6036 6036 of the working directory is not a branch head, :hg:`tag` aborts; use
6037 6037 -f/--force to force the tag commit to be based on a non-head
6038 6038 changeset.
6039 6039
6040 6040 See :hg:`help dates` for a list of formats valid for -d/--date.
6041 6041
6042 6042 Since tag names have priority over branch names during revision
6043 6043 lookup, using an existing branch name as a tag name is discouraged.
6044 6044
6045 6045 Returns 0 on success.
6046 6046 """
6047 6047 wlock = lock = None
6048 6048 try:
6049 6049 wlock = repo.wlock()
6050 6050 lock = repo.lock()
6051 6051 rev_ = "."
6052 6052 names = [t.strip() for t in (name1,) + names]
6053 6053 if len(names) != len(set(names)):
6054 6054 raise util.Abort(_('tag names must be unique'))
6055 6055 for n in names:
6056 6056 scmutil.checknewlabel(repo, n, 'tag')
6057 6057 if not n:
6058 6058 raise util.Abort(_('tag names cannot consist entirely of '
6059 6059 'whitespace'))
6060 6060 if opts.get('rev') and opts.get('remove'):
6061 6061 raise util.Abort(_("--rev and --remove are incompatible"))
6062 6062 if opts.get('rev'):
6063 6063 rev_ = opts['rev']
6064 6064 message = opts.get('message')
6065 6065 if opts.get('remove'):
6066 6066 if opts.get('local'):
6067 6067 expectedtype = 'local'
6068 6068 else:
6069 6069 expectedtype = 'global'
6070 6070
6071 6071 for n in names:
6072 6072 if not repo.tagtype(n):
6073 6073 raise util.Abort(_("tag '%s' does not exist") % n)
6074 6074 if repo.tagtype(n) != expectedtype:
6075 6075 if expectedtype == 'global':
6076 6076 raise util.Abort(_("tag '%s' is not a global tag") % n)
6077 6077 else:
6078 6078 raise util.Abort(_("tag '%s' is not a local tag") % n)
6079 6079 rev_ = nullid
6080 6080 if not message:
6081 6081 # we don't translate commit messages
6082 6082 message = 'Removed tag %s' % ', '.join(names)
6083 6083 elif not opts.get('force'):
6084 6084 for n in names:
6085 6085 if n in repo.tags():
6086 6086 raise util.Abort(_("tag '%s' already exists "
6087 6087 "(use -f to force)") % n)
6088 6088 if not opts.get('local'):
6089 6089 p1, p2 = repo.dirstate.parents()
6090 6090 if p2 != nullid:
6091 6091 raise util.Abort(_('uncommitted merge'))
6092 6092 bheads = repo.branchheads()
6093 6093 if not opts.get('force') and bheads and p1 not in bheads:
6094 6094 raise util.Abort(_('not at a branch head (use -f to force)'))
6095 6095 r = scmutil.revsingle(repo, rev_).node()
6096 6096
6097 6097 if not message:
6098 6098 # we don't translate commit messages
6099 6099 message = ('Added tag %s for changeset %s' %
6100 6100 (', '.join(names), short(r)))
6101 6101
6102 6102 date = opts.get('date')
6103 6103 if date:
6104 6104 date = util.parsedate(date)
6105 6105
6106 6106 if opts.get('remove'):
6107 6107 editform = 'tag.remove'
6108 6108 else:
6109 6109 editform = 'tag.add'
6110 6110 editor = cmdutil.getcommiteditor(editform=editform, **opts)
6111 6111
6112 6112 # don't allow tagging the null rev
6113 6113 if (not opts.get('remove') and
6114 6114 scmutil.revsingle(repo, rev_).rev() == nullrev):
6115 6115 raise util.Abort(_("cannot tag null revision"))
6116 6116
6117 6117 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date,
6118 6118 editor=editor)
6119 6119 finally:
6120 6120 release(lock, wlock)
6121 6121
6122 6122 @command('tags', formatteropts, '')
6123 6123 def tags(ui, repo, **opts):
6124 6124 """list repository tags
6125 6125
6126 6126 This lists both regular and local tags. When the -v/--verbose
6127 6127 switch is used, a third column "local" is printed for local tags.
6128 6128
6129 6129 Returns 0 on success.
6130 6130 """
6131 6131
6132 6132 fm = ui.formatter('tags', opts)
6133 6133 hexfunc = fm.hexfunc
6134 6134 tagtype = ""
6135 6135
6136 6136 for t, n in reversed(repo.tagslist()):
6137 6137 hn = hexfunc(n)
6138 6138 label = 'tags.normal'
6139 6139 tagtype = ''
6140 6140 if repo.tagtype(t) == 'local':
6141 6141 label = 'tags.local'
6142 6142 tagtype = 'local'
6143 6143
6144 6144 fm.startitem()
6145 6145 fm.write('tag', '%s', t, label=label)
6146 6146 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
6147 6147 fm.condwrite(not ui.quiet, 'rev node', fmt,
6148 6148 repo.changelog.rev(n), hn, label=label)
6149 6149 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
6150 6150 tagtype, label=label)
6151 6151 fm.plain('\n')
6152 6152 fm.end()
6153 6153
6154 6154 @command('tip',
6155 6155 [('p', 'patch', None, _('show patch')),
6156 6156 ('g', 'git', None, _('use git extended diff format')),
6157 6157 ] + templateopts,
6158 6158 _('[-p] [-g]'))
6159 6159 def tip(ui, repo, **opts):
6160 6160 """show the tip revision (DEPRECATED)
6161 6161
6162 6162 The tip revision (usually just called the tip) is the changeset
6163 6163 most recently added to the repository (and therefore the most
6164 6164 recently changed head).
6165 6165
6166 6166 If you have just made a commit, that commit will be the tip. If
6167 6167 you have just pulled changes from another repository, the tip of
6168 6168 that repository becomes the current tip. The "tip" tag is special
6169 6169 and cannot be renamed or assigned to a different changeset.
6170 6170
6171 6171 This command is deprecated, please use :hg:`heads` instead.
6172 6172
6173 6173 Returns 0 on success.
6174 6174 """
6175 6175 displayer = cmdutil.show_changeset(ui, repo, opts)
6176 6176 displayer.show(repo['tip'])
6177 6177 displayer.close()
6178 6178
6179 6179 @command('unbundle',
6180 6180 [('u', 'update', None,
6181 6181 _('update to new branch head if changesets were unbundled'))],
6182 6182 _('[-u] FILE...'))
6183 6183 def unbundle(ui, repo, fname1, *fnames, **opts):
6184 6184 """apply one or more changegroup files
6185 6185
6186 6186 Apply one or more compressed changegroup files generated by the
6187 6187 bundle command.
6188 6188
6189 6189 Returns 0 on success, 1 if an update has unresolved files.
6190 6190 """
6191 6191 fnames = (fname1,) + fnames
6192 6192
6193 6193 lock = repo.lock()
6194 6194 try:
6195 6195 for fname in fnames:
6196 6196 f = hg.openpath(ui, fname)
6197 6197 gen = exchange.readbundle(ui, f, fname)
6198 6198 if isinstance(gen, bundle2.unbundle20):
6199 6199 tr = repo.transaction('unbundle')
6200 6200 try:
6201 6201 op = bundle2.processbundle(repo, gen, lambda: tr)
6202 6202 tr.close()
6203 6203 finally:
6204 6204 if tr:
6205 6205 tr.release()
6206 6206 changes = [r.get('result', 0)
6207 6207 for r in op.records['changegroup']]
6208 6208 modheads = changegroup.combineresults(changes)
6209 6209 else:
6210 6210 modheads = changegroup.addchangegroup(repo, gen, 'unbundle',
6211 6211 'bundle:' + fname)
6212 6212 finally:
6213 6213 lock.release()
6214 6214
6215 6215 return postincoming(ui, repo, modheads, opts.get('update'), None)
6216 6216
6217 6217 @command('^update|up|checkout|co',
6218 6218 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6219 6219 ('c', 'check', None,
6220 6220 _('update across branches if no uncommitted changes')),
6221 6221 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6222 6222 ('r', 'rev', '', _('revision'), _('REV'))
6223 6223 ] + mergetoolopts,
6224 6224 _('[-c] [-C] [-d DATE] [[-r] REV]'))
6225 6225 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
6226 6226 tool=None):
6227 6227 """update working directory (or switch revisions)
6228 6228
6229 6229 Update the repository's working directory to the specified
6230 6230 changeset. If no changeset is specified, update to the tip of the
6231 6231 current named branch and move the current bookmark (see :hg:`help
6232 6232 bookmarks`).
6233 6233
6234 6234 Update sets the working directory's parent revision to the specified
6235 6235 changeset (see :hg:`help parents`).
6236 6236
6237 6237 If the changeset is not a descendant or ancestor of the working
6238 6238 directory's parent, the update is aborted. With the -c/--check
6239 6239 option, the working directory is checked for uncommitted changes; if
6240 6240 none are found, the working directory is updated to the specified
6241 6241 changeset.
6242 6242
6243 6243 .. container:: verbose
6244 6244
6245 6245 The following rules apply when the working directory contains
6246 6246 uncommitted changes:
6247 6247
6248 6248 1. If neither -c/--check nor -C/--clean is specified, and if
6249 6249 the requested changeset is an ancestor or descendant of
6250 6250 the working directory's parent, the uncommitted changes
6251 6251 are merged into the requested changeset and the merged
6252 6252 result is left uncommitted. If the requested changeset is
6253 6253 not an ancestor or descendant (that is, it is on another
6254 6254 branch), the update is aborted and the uncommitted changes
6255 6255 are preserved.
6256 6256
6257 6257 2. With the -c/--check option, the update is aborted and the
6258 6258 uncommitted changes are preserved.
6259 6259
6260 6260 3. With the -C/--clean option, uncommitted changes are discarded and
6261 6261 the working directory is updated to the requested changeset.
6262 6262
6263 6263 To cancel an uncommitted merge (and lose your changes), use
6264 6264 :hg:`update --clean .`.
6265 6265
6266 6266 Use null as the changeset to remove the working directory (like
6267 6267 :hg:`clone -U`).
6268 6268
6269 6269 If you want to revert just one file to an older revision, use
6270 6270 :hg:`revert [-r REV] NAME`.
6271 6271
6272 6272 See :hg:`help dates` for a list of formats valid for -d/--date.
6273 6273
6274 6274 Returns 0 on success, 1 if there are unresolved files.
6275 6275 """
6276 6276 if rev and node:
6277 6277 raise util.Abort(_("please specify just one revision"))
6278 6278
6279 6279 if rev is None or rev == '':
6280 6280 rev = node
6281 6281
6282 6282 cmdutil.clearunfinished(repo)
6283 6283
6284 6284 # with no argument, we also move the current bookmark, if any
6285 6285 rev, movemarkfrom = bookmarks.calculateupdate(ui, repo, rev)
6286 6286
6287 6287 # if we defined a bookmark, we have to remember the original bookmark name
6288 6288 brev = rev
6289 6289 rev = scmutil.revsingle(repo, rev, rev).rev()
6290 6290
6291 6291 if check and clean:
6292 6292 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
6293 6293
6294 6294 if date:
6295 6295 if rev is not None:
6296 6296 raise util.Abort(_("you can't specify a revision and a date"))
6297 6297 rev = cmdutil.finddate(ui, repo, date)
6298 6298
6299 6299 if check:
6300 6300 c = repo[None]
6301 6301 if c.dirty(merge=False, branch=False, missing=True):
6302 6302 raise util.Abort(_("uncommitted changes"))
6303 6303 if rev is None:
6304 6304 rev = repo[repo[None].branch()].rev()
6305 6305
6306 6306 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
6307 6307
6308 6308 if clean:
6309 6309 ret = hg.clean(repo, rev)
6310 6310 else:
6311 6311 ret = hg.update(repo, rev)
6312 6312
6313 6313 if not ret and movemarkfrom:
6314 6314 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
6315 6315 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
6316 6316 elif brev in repo._bookmarks:
6317 6317 bookmarks.setcurrent(repo, brev)
6318 6318 ui.status(_("(activating bookmark %s)\n") % brev)
6319 6319 elif brev:
6320 6320 if repo._bookmarkcurrent:
6321 6321 ui.status(_("(leaving bookmark %s)\n") %
6322 6322 repo._bookmarkcurrent)
6323 6323 bookmarks.unsetcurrent(repo)
6324 6324
6325 6325 return ret
6326 6326
6327 6327 @command('verify', [])
6328 6328 def verify(ui, repo):
6329 6329 """verify the integrity of the repository
6330 6330
6331 6331 Verify the integrity of the current repository.
6332 6332
6333 6333 This will perform an extensive check of the repository's
6334 6334 integrity, validating the hashes and checksums of each entry in
6335 6335 the changelog, manifest, and tracked files, as well as the
6336 6336 integrity of their crosslinks and indices.
6337 6337
6338 6338 Please see http://mercurial.selenic.com/wiki/RepositoryCorruption
6339 6339 for more information about recovery from corruption of the
6340 6340 repository.
6341 6341
6342 6342 Returns 0 on success, 1 if errors are encountered.
6343 6343 """
6344 6344 return hg.verify(repo)
6345 6345
6346 6346 @command('version', [], norepo=True)
6347 6347 def version_(ui):
6348 6348 """output version and copyright information"""
6349 6349 ui.write(_("Mercurial Distributed SCM (version %s)\n")
6350 6350 % util.version())
6351 6351 ui.status(_(
6352 6352 "(see http://mercurial.selenic.com for more information)\n"
6353 6353 "\nCopyright (C) 2005-2015 Matt Mackall and others\n"
6354 6354 "This is free software; see the source for copying conditions. "
6355 6355 "There is NO\nwarranty; "
6356 6356 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6357 6357 ))
6358 6358
6359 6359 ui.note(_("\nEnabled extensions:\n\n"))
6360 6360 if ui.verbose:
6361 6361 # format names and versions into columns
6362 6362 names = []
6363 6363 vers = []
6364 6364 for name, module in extensions.extensions():
6365 6365 names.append(name)
6366 6366 vers.append(extensions.moduleversion(module))
6367 6367 if names:
6368 6368 maxnamelen = max(len(n) for n in names)
6369 6369 for i, name in enumerate(names):
6370 6370 ui.write(" %-*s %s\n" % (maxnamelen, name, vers[i]))
@@ -1,2424 +1,2427 b''
1 1 # patch.py - patch file parsing routines
2 2 #
3 3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import cStringIO, email, os, errno, re, posixpath, copy
10 10 import tempfile, zlib, shutil
11 11 # On python2.4 you have to import these by name or they fail to
12 12 # load. This was not a problem on Python 2.7.
13 13 import email.Generator
14 14 import email.Parser
15 15
16 16 from i18n import _
17 17 from node import hex, short
18 18 import cStringIO
19 19 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
20 import pathutil
20 21
21 22 gitre = re.compile('diff --git a/(.*) b/(.*)')
22 23 tabsplitter = re.compile(r'(\t+|[^\t]+)')
23 24
24 25 class PatchError(Exception):
25 26 pass
26 27
27 28
28 29 # public functions
29 30
30 31 def split(stream):
31 32 '''return an iterator of individual patches from a stream'''
32 33 def isheader(line, inheader):
33 34 if inheader and line[0] in (' ', '\t'):
34 35 # continuation
35 36 return True
36 37 if line[0] in (' ', '-', '+'):
37 38 # diff line - don't check for header pattern in there
38 39 return False
39 40 l = line.split(': ', 1)
40 41 return len(l) == 2 and ' ' not in l[0]
41 42
42 43 def chunk(lines):
43 44 return cStringIO.StringIO(''.join(lines))
44 45
45 46 def hgsplit(stream, cur):
46 47 inheader = True
47 48
48 49 for line in stream:
49 50 if not line.strip():
50 51 inheader = False
51 52 if not inheader and line.startswith('# HG changeset patch'):
52 53 yield chunk(cur)
53 54 cur = []
54 55 inheader = True
55 56
56 57 cur.append(line)
57 58
58 59 if cur:
59 60 yield chunk(cur)
60 61
61 62 def mboxsplit(stream, cur):
62 63 for line in stream:
63 64 if line.startswith('From '):
64 65 for c in split(chunk(cur[1:])):
65 66 yield c
66 67 cur = []
67 68
68 69 cur.append(line)
69 70
70 71 if cur:
71 72 for c in split(chunk(cur[1:])):
72 73 yield c
73 74
74 75 def mimesplit(stream, cur):
75 76 def msgfp(m):
76 77 fp = cStringIO.StringIO()
77 78 g = email.Generator.Generator(fp, mangle_from_=False)
78 79 g.flatten(m)
79 80 fp.seek(0)
80 81 return fp
81 82
82 83 for line in stream:
83 84 cur.append(line)
84 85 c = chunk(cur)
85 86
86 87 m = email.Parser.Parser().parse(c)
87 88 if not m.is_multipart():
88 89 yield msgfp(m)
89 90 else:
90 91 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
91 92 for part in m.walk():
92 93 ct = part.get_content_type()
93 94 if ct not in ok_types:
94 95 continue
95 96 yield msgfp(part)
96 97
97 98 def headersplit(stream, cur):
98 99 inheader = False
99 100
100 101 for line in stream:
101 102 if not inheader and isheader(line, inheader):
102 103 yield chunk(cur)
103 104 cur = []
104 105 inheader = True
105 106 if inheader and not isheader(line, inheader):
106 107 inheader = False
107 108
108 109 cur.append(line)
109 110
110 111 if cur:
111 112 yield chunk(cur)
112 113
113 114 def remainder(cur):
114 115 yield chunk(cur)
115 116
116 117 class fiter(object):
117 118 def __init__(self, fp):
118 119 self.fp = fp
119 120
120 121 def __iter__(self):
121 122 return self
122 123
123 124 def next(self):
124 125 l = self.fp.readline()
125 126 if not l:
126 127 raise StopIteration
127 128 return l
128 129
129 130 inheader = False
130 131 cur = []
131 132
132 133 mimeheaders = ['content-type']
133 134
134 135 if not util.safehasattr(stream, 'next'):
135 136 # http responses, for example, have readline but not next
136 137 stream = fiter(stream)
137 138
138 139 for line in stream:
139 140 cur.append(line)
140 141 if line.startswith('# HG changeset patch'):
141 142 return hgsplit(stream, cur)
142 143 elif line.startswith('From '):
143 144 return mboxsplit(stream, cur)
144 145 elif isheader(line, inheader):
145 146 inheader = True
146 147 if line.split(':', 1)[0].lower() in mimeheaders:
147 148 # let email parser handle this
148 149 return mimesplit(stream, cur)
149 150 elif line.startswith('--- ') and inheader:
150 151 # No evil headers seen by diff start, split by hand
151 152 return headersplit(stream, cur)
152 153 # Not enough info, keep reading
153 154
154 155 # if we are here, we have a very plain patch
155 156 return remainder(cur)
156 157
157 158 def extract(ui, fileobj):
158 159 '''extract patch from data read from fileobj.
159 160
160 161 patch can be a normal patch or contained in an email message.
161 162
162 163 return tuple (filename, message, user, date, branch, node, p1, p2).
163 164 Any item in the returned tuple can be None. If filename is None,
164 165 fileobj did not contain a patch. Caller must unlink filename when done.'''
165 166
166 167 # attempt to detect the start of a patch
167 168 # (this heuristic is borrowed from quilt)
168 169 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
169 170 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
170 171 r'---[ \t].*?^\+\+\+[ \t]|'
171 172 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
172 173
173 174 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
174 175 tmpfp = os.fdopen(fd, 'w')
175 176 try:
176 177 msg = email.Parser.Parser().parse(fileobj)
177 178
178 179 subject = msg['Subject']
179 180 user = msg['From']
180 181 if not subject and not user:
181 182 # Not an email, restore parsed headers if any
182 183 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
183 184
184 185 # should try to parse msg['Date']
185 186 date = None
186 187 nodeid = None
187 188 branch = None
188 189 parents = []
189 190
190 191 if subject:
191 192 if subject.startswith('[PATCH'):
192 193 pend = subject.find(']')
193 194 if pend >= 0:
194 195 subject = subject[pend + 1:].lstrip()
195 196 subject = re.sub(r'\n[ \t]+', ' ', subject)
196 197 ui.debug('Subject: %s\n' % subject)
197 198 if user:
198 199 ui.debug('From: %s\n' % user)
199 200 diffs_seen = 0
200 201 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
201 202 message = ''
202 203 for part in msg.walk():
203 204 content_type = part.get_content_type()
204 205 ui.debug('Content-Type: %s\n' % content_type)
205 206 if content_type not in ok_types:
206 207 continue
207 208 payload = part.get_payload(decode=True)
208 209 m = diffre.search(payload)
209 210 if m:
210 211 hgpatch = False
211 212 hgpatchheader = False
212 213 ignoretext = False
213 214
214 215 ui.debug('found patch at byte %d\n' % m.start(0))
215 216 diffs_seen += 1
216 217 cfp = cStringIO.StringIO()
217 218 for line in payload[:m.start(0)].splitlines():
218 219 if line.startswith('# HG changeset patch') and not hgpatch:
219 220 ui.debug('patch generated by hg export\n')
220 221 hgpatch = True
221 222 hgpatchheader = True
222 223 # drop earlier commit message content
223 224 cfp.seek(0)
224 225 cfp.truncate()
225 226 subject = None
226 227 elif hgpatchheader:
227 228 if line.startswith('# User '):
228 229 user = line[7:]
229 230 ui.debug('From: %s\n' % user)
230 231 elif line.startswith("# Date "):
231 232 date = line[7:]
232 233 elif line.startswith("# Branch "):
233 234 branch = line[9:]
234 235 elif line.startswith("# Node ID "):
235 236 nodeid = line[10:]
236 237 elif line.startswith("# Parent "):
237 238 parents.append(line[9:].lstrip())
238 239 elif not line.startswith("# "):
239 240 hgpatchheader = False
240 241 elif line == '---':
241 242 ignoretext = True
242 243 if not hgpatchheader and not ignoretext:
243 244 cfp.write(line)
244 245 cfp.write('\n')
245 246 message = cfp.getvalue()
246 247 if tmpfp:
247 248 tmpfp.write(payload)
248 249 if not payload.endswith('\n'):
249 250 tmpfp.write('\n')
250 251 elif not diffs_seen and message and content_type == 'text/plain':
251 252 message += '\n' + payload
252 253 except: # re-raises
253 254 tmpfp.close()
254 255 os.unlink(tmpname)
255 256 raise
256 257
257 258 if subject and not message.startswith(subject):
258 259 message = '%s\n%s' % (subject, message)
259 260 tmpfp.close()
260 261 if not diffs_seen:
261 262 os.unlink(tmpname)
262 263 return None, message, user, date, branch, None, None, None
263 264
264 265 if parents:
265 266 p1 = parents.pop(0)
266 267 else:
267 268 p1 = None
268 269
269 270 if parents:
270 271 p2 = parents.pop(0)
271 272 else:
272 273 p2 = None
273 274
274 275 return tmpname, message, user, date, branch, nodeid, p1, p2
275 276
276 277 class patchmeta(object):
277 278 """Patched file metadata
278 279
279 280 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
280 281 or COPY. 'path' is patched file path. 'oldpath' is set to the
281 282 origin file when 'op' is either COPY or RENAME, None otherwise. If
282 283 file mode is changed, 'mode' is a tuple (islink, isexec) where
283 284 'islink' is True if the file is a symlink and 'isexec' is True if
284 285 the file is executable. Otherwise, 'mode' is None.
285 286 """
286 287 def __init__(self, path):
287 288 self.path = path
288 289 self.oldpath = None
289 290 self.mode = None
290 291 self.op = 'MODIFY'
291 292 self.binary = False
292 293
293 294 def setmode(self, mode):
294 295 islink = mode & 020000
295 296 isexec = mode & 0100
296 297 self.mode = (islink, isexec)
297 298
298 299 def copy(self):
299 300 other = patchmeta(self.path)
300 301 other.oldpath = self.oldpath
301 302 other.mode = self.mode
302 303 other.op = self.op
303 304 other.binary = self.binary
304 305 return other
305 306
306 307 def _ispatchinga(self, afile):
307 308 if afile == '/dev/null':
308 309 return self.op == 'ADD'
309 310 return afile == 'a/' + (self.oldpath or self.path)
310 311
311 312 def _ispatchingb(self, bfile):
312 313 if bfile == '/dev/null':
313 314 return self.op == 'DELETE'
314 315 return bfile == 'b/' + self.path
315 316
316 317 def ispatching(self, afile, bfile):
317 318 return self._ispatchinga(afile) and self._ispatchingb(bfile)
318 319
319 320 def __repr__(self):
320 321 return "<patchmeta %s %r>" % (self.op, self.path)
321 322
322 323 def readgitpatch(lr):
323 324 """extract git-style metadata about patches from <patchname>"""
324 325
325 326 # Filter patch for git information
326 327 gp = None
327 328 gitpatches = []
328 329 for line in lr:
329 330 line = line.rstrip(' \r\n')
330 331 if line.startswith('diff --git a/'):
331 332 m = gitre.match(line)
332 333 if m:
333 334 if gp:
334 335 gitpatches.append(gp)
335 336 dst = m.group(2)
336 337 gp = patchmeta(dst)
337 338 elif gp:
338 339 if line.startswith('--- '):
339 340 gitpatches.append(gp)
340 341 gp = None
341 342 continue
342 343 if line.startswith('rename from '):
343 344 gp.op = 'RENAME'
344 345 gp.oldpath = line[12:]
345 346 elif line.startswith('rename to '):
346 347 gp.path = line[10:]
347 348 elif line.startswith('copy from '):
348 349 gp.op = 'COPY'
349 350 gp.oldpath = line[10:]
350 351 elif line.startswith('copy to '):
351 352 gp.path = line[8:]
352 353 elif line.startswith('deleted file'):
353 354 gp.op = 'DELETE'
354 355 elif line.startswith('new file mode '):
355 356 gp.op = 'ADD'
356 357 gp.setmode(int(line[-6:], 8))
357 358 elif line.startswith('new mode '):
358 359 gp.setmode(int(line[-6:], 8))
359 360 elif line.startswith('GIT binary patch'):
360 361 gp.binary = True
361 362 if gp:
362 363 gitpatches.append(gp)
363 364
364 365 return gitpatches
365 366
366 367 class linereader(object):
367 368 # simple class to allow pushing lines back into the input stream
368 369 def __init__(self, fp):
369 370 self.fp = fp
370 371 self.buf = []
371 372
372 373 def push(self, line):
373 374 if line is not None:
374 375 self.buf.append(line)
375 376
376 377 def readline(self):
377 378 if self.buf:
378 379 l = self.buf[0]
379 380 del self.buf[0]
380 381 return l
381 382 return self.fp.readline()
382 383
383 384 def __iter__(self):
384 385 while True:
385 386 l = self.readline()
386 387 if not l:
387 388 break
388 389 yield l
389 390
390 391 class abstractbackend(object):
391 392 def __init__(self, ui):
392 393 self.ui = ui
393 394
394 395 def getfile(self, fname):
395 396 """Return target file data and flags as a (data, (islink,
396 397 isexec)) tuple. Data is None if file is missing/deleted.
397 398 """
398 399 raise NotImplementedError
399 400
400 401 def setfile(self, fname, data, mode, copysource):
401 402 """Write data to target file fname and set its mode. mode is a
402 403 (islink, isexec) tuple. If data is None, the file content should
403 404 be left unchanged. If the file is modified after being copied,
404 405 copysource is set to the original file name.
405 406 """
406 407 raise NotImplementedError
407 408
408 409 def unlink(self, fname):
409 410 """Unlink target file."""
410 411 raise NotImplementedError
411 412
412 413 def writerej(self, fname, failed, total, lines):
413 414 """Write rejected lines for fname. total is the number of hunks
414 415 which failed to apply and total the total number of hunks for this
415 416 files.
416 417 """
417 418 pass
418 419
419 420 def exists(self, fname):
420 421 raise NotImplementedError
421 422
422 423 class fsbackend(abstractbackend):
423 424 def __init__(self, ui, basedir):
424 425 super(fsbackend, self).__init__(ui)
425 426 self.opener = scmutil.opener(basedir)
426 427
427 428 def _join(self, f):
428 429 return os.path.join(self.opener.base, f)
429 430
430 431 def getfile(self, fname):
431 432 if self.opener.islink(fname):
432 433 return (self.opener.readlink(fname), (True, False))
433 434
434 435 isexec = False
435 436 try:
436 437 isexec = self.opener.lstat(fname).st_mode & 0100 != 0
437 438 except OSError, e:
438 439 if e.errno != errno.ENOENT:
439 440 raise
440 441 try:
441 442 return (self.opener.read(fname), (False, isexec))
442 443 except IOError, e:
443 444 if e.errno != errno.ENOENT:
444 445 raise
445 446 return None, None
446 447
447 448 def setfile(self, fname, data, mode, copysource):
448 449 islink, isexec = mode
449 450 if data is None:
450 451 self.opener.setflags(fname, islink, isexec)
451 452 return
452 453 if islink:
453 454 self.opener.symlink(data, fname)
454 455 else:
455 456 self.opener.write(fname, data)
456 457 if isexec:
457 458 self.opener.setflags(fname, False, True)
458 459
459 460 def unlink(self, fname):
460 461 self.opener.unlinkpath(fname, ignoremissing=True)
461 462
462 463 def writerej(self, fname, failed, total, lines):
463 464 fname = fname + ".rej"
464 465 self.ui.warn(
465 466 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
466 467 (failed, total, fname))
467 468 fp = self.opener(fname, 'w')
468 469 fp.writelines(lines)
469 470 fp.close()
470 471
471 472 def exists(self, fname):
472 473 return self.opener.lexists(fname)
473 474
474 475 class workingbackend(fsbackend):
475 476 def __init__(self, ui, repo, similarity):
476 477 super(workingbackend, self).__init__(ui, repo.root)
477 478 self.repo = repo
478 479 self.similarity = similarity
479 480 self.removed = set()
480 481 self.changed = set()
481 482 self.copied = []
482 483
483 484 def _checkknown(self, fname):
484 485 if self.repo.dirstate[fname] == '?' and self.exists(fname):
485 486 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
486 487
487 488 def setfile(self, fname, data, mode, copysource):
488 489 self._checkknown(fname)
489 490 super(workingbackend, self).setfile(fname, data, mode, copysource)
490 491 if copysource is not None:
491 492 self.copied.append((copysource, fname))
492 493 self.changed.add(fname)
493 494
494 495 def unlink(self, fname):
495 496 self._checkknown(fname)
496 497 super(workingbackend, self).unlink(fname)
497 498 self.removed.add(fname)
498 499 self.changed.add(fname)
499 500
500 501 def close(self):
501 502 wctx = self.repo[None]
502 503 changed = set(self.changed)
503 504 for src, dst in self.copied:
504 505 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
505 506 if self.removed:
506 507 wctx.forget(sorted(self.removed))
507 508 for f in self.removed:
508 509 if f not in self.repo.dirstate:
509 510 # File was deleted and no longer belongs to the
510 511 # dirstate, it was probably marked added then
511 512 # deleted, and should not be considered by
512 513 # marktouched().
513 514 changed.discard(f)
514 515 if changed:
515 516 scmutil.marktouched(self.repo, changed, self.similarity)
516 517 return sorted(self.changed)
517 518
518 519 class filestore(object):
519 520 def __init__(self, maxsize=None):
520 521 self.opener = None
521 522 self.files = {}
522 523 self.created = 0
523 524 self.maxsize = maxsize
524 525 if self.maxsize is None:
525 526 self.maxsize = 4*(2**20)
526 527 self.size = 0
527 528 self.data = {}
528 529
529 530 def setfile(self, fname, data, mode, copied=None):
530 531 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
531 532 self.data[fname] = (data, mode, copied)
532 533 self.size += len(data)
533 534 else:
534 535 if self.opener is None:
535 536 root = tempfile.mkdtemp(prefix='hg-patch-')
536 537 self.opener = scmutil.opener(root)
537 538 # Avoid filename issues with these simple names
538 539 fn = str(self.created)
539 540 self.opener.write(fn, data)
540 541 self.created += 1
541 542 self.files[fname] = (fn, mode, copied)
542 543
543 544 def getfile(self, fname):
544 545 if fname in self.data:
545 546 return self.data[fname]
546 547 if not self.opener or fname not in self.files:
547 548 return None, None, None
548 549 fn, mode, copied = self.files[fname]
549 550 return self.opener.read(fn), mode, copied
550 551
551 552 def close(self):
552 553 if self.opener:
553 554 shutil.rmtree(self.opener.base)
554 555
555 556 class repobackend(abstractbackend):
556 557 def __init__(self, ui, repo, ctx, store):
557 558 super(repobackend, self).__init__(ui)
558 559 self.repo = repo
559 560 self.ctx = ctx
560 561 self.store = store
561 562 self.changed = set()
562 563 self.removed = set()
563 564 self.copied = {}
564 565
565 566 def _checkknown(self, fname):
566 567 if fname not in self.ctx:
567 568 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
568 569
569 570 def getfile(self, fname):
570 571 try:
571 572 fctx = self.ctx[fname]
572 573 except error.LookupError:
573 574 return None, None
574 575 flags = fctx.flags()
575 576 return fctx.data(), ('l' in flags, 'x' in flags)
576 577
577 578 def setfile(self, fname, data, mode, copysource):
578 579 if copysource:
579 580 self._checkknown(copysource)
580 581 if data is None:
581 582 data = self.ctx[fname].data()
582 583 self.store.setfile(fname, data, mode, copysource)
583 584 self.changed.add(fname)
584 585 if copysource:
585 586 self.copied[fname] = copysource
586 587
587 588 def unlink(self, fname):
588 589 self._checkknown(fname)
589 590 self.removed.add(fname)
590 591
591 592 def exists(self, fname):
592 593 return fname in self.ctx
593 594
594 595 def close(self):
595 596 return self.changed | self.removed
596 597
597 598 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
598 599 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
599 600 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
600 601 eolmodes = ['strict', 'crlf', 'lf', 'auto']
601 602
602 603 class patchfile(object):
603 604 def __init__(self, ui, gp, backend, store, eolmode='strict'):
604 605 self.fname = gp.path
605 606 self.eolmode = eolmode
606 607 self.eol = None
607 608 self.backend = backend
608 609 self.ui = ui
609 610 self.lines = []
610 611 self.exists = False
611 612 self.missing = True
612 613 self.mode = gp.mode
613 614 self.copysource = gp.oldpath
614 615 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
615 616 self.remove = gp.op == 'DELETE'
616 617 if self.copysource is None:
617 618 data, mode = backend.getfile(self.fname)
618 619 else:
619 620 data, mode = store.getfile(self.copysource)[:2]
620 621 if data is not None:
621 622 self.exists = self.copysource is None or backend.exists(self.fname)
622 623 self.missing = False
623 624 if data:
624 625 self.lines = mdiff.splitnewlines(data)
625 626 if self.mode is None:
626 627 self.mode = mode
627 628 if self.lines:
628 629 # Normalize line endings
629 630 if self.lines[0].endswith('\r\n'):
630 631 self.eol = '\r\n'
631 632 elif self.lines[0].endswith('\n'):
632 633 self.eol = '\n'
633 634 if eolmode != 'strict':
634 635 nlines = []
635 636 for l in self.lines:
636 637 if l.endswith('\r\n'):
637 638 l = l[:-2] + '\n'
638 639 nlines.append(l)
639 640 self.lines = nlines
640 641 else:
641 642 if self.create:
642 643 self.missing = False
643 644 if self.mode is None:
644 645 self.mode = (False, False)
645 646 if self.missing:
646 647 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
647 648
648 649 self.hash = {}
649 650 self.dirty = 0
650 651 self.offset = 0
651 652 self.skew = 0
652 653 self.rej = []
653 654 self.fileprinted = False
654 655 self.printfile(False)
655 656 self.hunks = 0
656 657
657 658 def writelines(self, fname, lines, mode):
658 659 if self.eolmode == 'auto':
659 660 eol = self.eol
660 661 elif self.eolmode == 'crlf':
661 662 eol = '\r\n'
662 663 else:
663 664 eol = '\n'
664 665
665 666 if self.eolmode != 'strict' and eol and eol != '\n':
666 667 rawlines = []
667 668 for l in lines:
668 669 if l and l[-1] == '\n':
669 670 l = l[:-1] + eol
670 671 rawlines.append(l)
671 672 lines = rawlines
672 673
673 674 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
674 675
675 676 def printfile(self, warn):
676 677 if self.fileprinted:
677 678 return
678 679 if warn or self.ui.verbose:
679 680 self.fileprinted = True
680 681 s = _("patching file %s\n") % self.fname
681 682 if warn:
682 683 self.ui.warn(s)
683 684 else:
684 685 self.ui.note(s)
685 686
686 687
687 688 def findlines(self, l, linenum):
688 689 # looks through the hash and finds candidate lines. The
689 690 # result is a list of line numbers sorted based on distance
690 691 # from linenum
691 692
692 693 cand = self.hash.get(l, [])
693 694 if len(cand) > 1:
694 695 # resort our list of potentials forward then back.
695 696 cand.sort(key=lambda x: abs(x - linenum))
696 697 return cand
697 698
698 699 def write_rej(self):
699 700 # our rejects are a little different from patch(1). This always
700 701 # creates rejects in the same form as the original patch. A file
701 702 # header is inserted so that you can run the reject through patch again
702 703 # without having to type the filename.
703 704 if not self.rej:
704 705 return
705 706 base = os.path.basename(self.fname)
706 707 lines = ["--- %s\n+++ %s\n" % (base, base)]
707 708 for x in self.rej:
708 709 for l in x.hunk:
709 710 lines.append(l)
710 711 if l[-1] != '\n':
711 712 lines.append("\n\ No newline at end of file\n")
712 713 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
713 714
714 715 def apply(self, h):
715 716 if not h.complete():
716 717 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
717 718 (h.number, h.desc, len(h.a), h.lena, len(h.b),
718 719 h.lenb))
719 720
720 721 self.hunks += 1
721 722
722 723 if self.missing:
723 724 self.rej.append(h)
724 725 return -1
725 726
726 727 if self.exists and self.create:
727 728 if self.copysource:
728 729 self.ui.warn(_("cannot create %s: destination already "
729 730 "exists\n") % self.fname)
730 731 else:
731 732 self.ui.warn(_("file %s already exists\n") % self.fname)
732 733 self.rej.append(h)
733 734 return -1
734 735
735 736 if isinstance(h, binhunk):
736 737 if self.remove:
737 738 self.backend.unlink(self.fname)
738 739 else:
739 740 l = h.new(self.lines)
740 741 self.lines[:] = l
741 742 self.offset += len(l)
742 743 self.dirty = True
743 744 return 0
744 745
745 746 horig = h
746 747 if (self.eolmode in ('crlf', 'lf')
747 748 or self.eolmode == 'auto' and self.eol):
748 749 # If new eols are going to be normalized, then normalize
749 750 # hunk data before patching. Otherwise, preserve input
750 751 # line-endings.
751 752 h = h.getnormalized()
752 753
753 754 # fast case first, no offsets, no fuzz
754 755 old, oldstart, new, newstart = h.fuzzit(0, False)
755 756 oldstart += self.offset
756 757 orig_start = oldstart
757 758 # if there's skew we want to emit the "(offset %d lines)" even
758 759 # when the hunk cleanly applies at start + skew, so skip the
759 760 # fast case code
760 761 if (self.skew == 0 and
761 762 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
762 763 if self.remove:
763 764 self.backend.unlink(self.fname)
764 765 else:
765 766 self.lines[oldstart:oldstart + len(old)] = new
766 767 self.offset += len(new) - len(old)
767 768 self.dirty = True
768 769 return 0
769 770
770 771 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
771 772 self.hash = {}
772 773 for x, s in enumerate(self.lines):
773 774 self.hash.setdefault(s, []).append(x)
774 775
775 776 for fuzzlen in xrange(3):
776 777 for toponly in [True, False]:
777 778 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
778 779 oldstart = oldstart + self.offset + self.skew
779 780 oldstart = min(oldstart, len(self.lines))
780 781 if old:
781 782 cand = self.findlines(old[0][1:], oldstart)
782 783 else:
783 784 # Only adding lines with no or fuzzed context, just
784 785 # take the skew in account
785 786 cand = [oldstart]
786 787
787 788 for l in cand:
788 789 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
789 790 self.lines[l : l + len(old)] = new
790 791 self.offset += len(new) - len(old)
791 792 self.skew = l - orig_start
792 793 self.dirty = True
793 794 offset = l - orig_start - fuzzlen
794 795 if fuzzlen:
795 796 msg = _("Hunk #%d succeeded at %d "
796 797 "with fuzz %d "
797 798 "(offset %d lines).\n")
798 799 self.printfile(True)
799 800 self.ui.warn(msg %
800 801 (h.number, l + 1, fuzzlen, offset))
801 802 else:
802 803 msg = _("Hunk #%d succeeded at %d "
803 804 "(offset %d lines).\n")
804 805 self.ui.note(msg % (h.number, l + 1, offset))
805 806 return fuzzlen
806 807 self.printfile(True)
807 808 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
808 809 self.rej.append(horig)
809 810 return -1
810 811
811 812 def close(self):
812 813 if self.dirty:
813 814 self.writelines(self.fname, self.lines, self.mode)
814 815 self.write_rej()
815 816 return len(self.rej)
816 817
817 818 class header(object):
818 819 """patch header
819 820 """
820 821 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
821 822 diff_re = re.compile('diff -r .* (.*)$')
822 823 allhunks_re = re.compile('(?:index|deleted file) ')
823 824 pretty_re = re.compile('(?:new file|deleted file) ')
824 825 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
825 826
826 827 def __init__(self, header):
827 828 self.header = header
828 829 self.hunks = []
829 830
830 831 def binary(self):
831 832 return util.any(h.startswith('index ') for h in self.header)
832 833
833 834 def pretty(self, fp):
834 835 for h in self.header:
835 836 if h.startswith('index '):
836 837 fp.write(_('this modifies a binary file (all or nothing)\n'))
837 838 break
838 839 if self.pretty_re.match(h):
839 840 fp.write(h)
840 841 if self.binary():
841 842 fp.write(_('this is a binary file\n'))
842 843 break
843 844 if h.startswith('---'):
844 845 fp.write(_('%d hunks, %d lines changed\n') %
845 846 (len(self.hunks),
846 847 sum([max(h.added, h.removed) for h in self.hunks])))
847 848 break
848 849 fp.write(h)
849 850
850 851 def write(self, fp):
851 852 fp.write(''.join(self.header))
852 853
853 854 def allhunks(self):
854 855 return util.any(self.allhunks_re.match(h) for h in self.header)
855 856
856 857 def files(self):
857 858 match = self.diffgit_re.match(self.header[0])
858 859 if match:
859 860 fromfile, tofile = match.groups()
860 861 if fromfile == tofile:
861 862 return [fromfile]
862 863 return [fromfile, tofile]
863 864 else:
864 865 return self.diff_re.match(self.header[0]).groups()
865 866
866 867 def filename(self):
867 868 return self.files()[-1]
868 869
869 870 def __repr__(self):
870 871 return '<header %s>' % (' '.join(map(repr, self.files())))
871 872
872 873 def special(self):
873 874 return util.any(self.special_re.match(h) for h in self.header)
874 875
875 876 class recordhunk(object):
876 877 """patch hunk
877 878
878 879 XXX shouldn't we merge this with the other hunk class?
879 880 """
880 881 maxcontext = 3
881 882
882 883 def __init__(self, header, fromline, toline, proc, before, hunk, after):
883 884 def trimcontext(number, lines):
884 885 delta = len(lines) - self.maxcontext
885 886 if False and delta > 0:
886 887 return number + delta, lines[:self.maxcontext]
887 888 return number, lines
888 889
889 890 self.header = header
890 891 self.fromline, self.before = trimcontext(fromline, before)
891 892 self.toline, self.after = trimcontext(toline, after)
892 893 self.proc = proc
893 894 self.hunk = hunk
894 895 self.added, self.removed = self.countchanges(self.hunk)
895 896
896 897 def __eq__(self, v):
897 898 if not isinstance(v, recordhunk):
898 899 return False
899 900
900 901 return ((v.hunk == self.hunk) and
901 902 (v.proc == self.proc) and
902 903 (self.fromline == v.fromline) and
903 904 (self.header.files() == v.header.files()))
904 905
905 906 def __hash__(self):
906 907 return hash((tuple(self.hunk),
907 908 tuple(self.header.files()),
908 909 self.fromline,
909 910 self.proc))
910 911
911 912 def countchanges(self, hunk):
912 913 """hunk -> (n+,n-)"""
913 914 add = len([h for h in hunk if h[0] == '+'])
914 915 rem = len([h for h in hunk if h[0] == '-'])
915 916 return add, rem
916 917
917 918 def write(self, fp):
918 919 delta = len(self.before) + len(self.after)
919 920 if self.after and self.after[-1] == '\\ No newline at end of file\n':
920 921 delta -= 1
921 922 fromlen = delta + self.removed
922 923 tolen = delta + self.added
923 924 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
924 925 (self.fromline, fromlen, self.toline, tolen,
925 926 self.proc and (' ' + self.proc)))
926 927 fp.write(''.join(self.before + self.hunk + self.after))
927 928
928 929 pretty = write
929 930
930 931 def filename(self):
931 932 return self.header.filename()
932 933
933 934 def __repr__(self):
934 935 return '<hunk %r@%d>' % (self.filename(), self.fromline)
935 936
936 937 def filterpatch(ui, headers):
937 938 """Interactively filter patch chunks into applied-only chunks"""
938 939
939 940 def prompt(skipfile, skipall, query, chunk):
940 941 """prompt query, and process base inputs
941 942
942 943 - y/n for the rest of file
943 944 - y/n for the rest
944 945 - ? (help)
945 946 - q (quit)
946 947
947 948 Return True/False and possibly updated skipfile and skipall.
948 949 """
949 950 newpatches = None
950 951 if skipall is not None:
951 952 return skipall, skipfile, skipall, newpatches
952 953 if skipfile is not None:
953 954 return skipfile, skipfile, skipall, newpatches
954 955 while True:
955 956 resps = _('[Ynesfdaq?]'
956 957 '$$ &Yes, record this change'
957 958 '$$ &No, skip this change'
958 959 '$$ &Edit this change manually'
959 960 '$$ &Skip remaining changes to this file'
960 961 '$$ Record remaining changes to this &file'
961 962 '$$ &Done, skip remaining changes and files'
962 963 '$$ Record &all changes to all remaining files'
963 964 '$$ &Quit, recording no changes'
964 965 '$$ &? (display help)')
965 966 r = ui.promptchoice("%s %s" % (query, resps))
966 967 ui.write("\n")
967 968 if r == 8: # ?
968 969 for c, t in ui.extractchoices(resps)[1]:
969 970 ui.write('%s - %s\n' % (c, t.lower()))
970 971 continue
971 972 elif r == 0: # yes
972 973 ret = True
973 974 elif r == 1: # no
974 975 ret = False
975 976 elif r == 2: # Edit patch
976 977 if chunk is None:
977 978 ui.write(_('cannot edit patch for whole file'))
978 979 ui.write("\n")
979 980 continue
980 981 if chunk.header.binary():
981 982 ui.write(_('cannot edit patch for binary file'))
982 983 ui.write("\n")
983 984 continue
984 985 # Patch comment based on the Git one (based on comment at end of
985 986 # http://mercurial.selenic.com/wiki/RecordExtension)
986 987 phelp = '---' + _("""
987 988 To remove '-' lines, make them ' ' lines (context).
988 989 To remove '+' lines, delete them.
989 990 Lines starting with # will be removed from the patch.
990 991
991 992 If the patch applies cleanly, the edited hunk will immediately be
992 993 added to the record list. If it does not apply cleanly, a rejects
993 994 file will be generated: you can use that when you try again. If
994 995 all lines of the hunk are removed, then the edit is aborted and
995 996 the hunk is left unchanged.
996 997 """)
997 998 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
998 999 suffix=".diff", text=True)
999 1000 ncpatchfp = None
1000 1001 try:
1001 1002 # Write the initial patch
1002 1003 f = os.fdopen(patchfd, "w")
1003 1004 chunk.header.write(f)
1004 1005 chunk.write(f)
1005 1006 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1006 1007 f.close()
1007 1008 # Start the editor and wait for it to complete
1008 1009 editor = ui.geteditor()
1009 1010 ui.system("%s \"%s\"" % (editor, patchfn),
1010 1011 environ={'HGUSER': ui.username()},
1011 1012 onerr=util.Abort, errprefix=_("edit failed"))
1012 1013 # Remove comment lines
1013 1014 patchfp = open(patchfn)
1014 1015 ncpatchfp = cStringIO.StringIO()
1015 1016 for line in patchfp:
1016 1017 if not line.startswith('#'):
1017 1018 ncpatchfp.write(line)
1018 1019 patchfp.close()
1019 1020 ncpatchfp.seek(0)
1020 1021 newpatches = parsepatch(ncpatchfp)
1021 1022 finally:
1022 1023 os.unlink(patchfn)
1023 1024 del ncpatchfp
1024 1025 # Signal that the chunk shouldn't be applied as-is, but
1025 1026 # provide the new patch to be used instead.
1026 1027 ret = False
1027 1028 elif r == 3: # Skip
1028 1029 ret = skipfile = False
1029 1030 elif r == 4: # file (Record remaining)
1030 1031 ret = skipfile = True
1031 1032 elif r == 5: # done, skip remaining
1032 1033 ret = skipall = False
1033 1034 elif r == 6: # all
1034 1035 ret = skipall = True
1035 1036 elif r == 7: # quit
1036 1037 raise util.Abort(_('user quit'))
1037 1038 return ret, skipfile, skipall, newpatches
1038 1039
1039 1040 seen = set()
1040 1041 applied = {} # 'filename' -> [] of chunks
1041 1042 skipfile, skipall = None, None
1042 1043 pos, total = 1, sum(len(h.hunks) for h in headers)
1043 1044 for h in headers:
1044 1045 pos += len(h.hunks)
1045 1046 skipfile = None
1046 1047 fixoffset = 0
1047 1048 hdr = ''.join(h.header)
1048 1049 if hdr in seen:
1049 1050 continue
1050 1051 seen.add(hdr)
1051 1052 if skipall is None:
1052 1053 h.pretty(ui)
1053 1054 msg = (_('examine changes to %s?') %
1054 1055 _(' and ').join("'%s'" % f for f in h.files()))
1055 1056 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1056 1057 if not r:
1057 1058 continue
1058 1059 applied[h.filename()] = [h]
1059 1060 if h.allhunks():
1060 1061 applied[h.filename()] += h.hunks
1061 1062 continue
1062 1063 for i, chunk in enumerate(h.hunks):
1063 1064 if skipfile is None and skipall is None:
1064 1065 chunk.pretty(ui)
1065 1066 if total == 1:
1066 1067 msg = _("record this change to '%s'?") % chunk.filename()
1067 1068 else:
1068 1069 idx = pos - len(h.hunks) + i
1069 1070 msg = _("record change %d/%d to '%s'?") % (idx, total,
1070 1071 chunk.filename())
1071 1072 r, skipfile, skipall, newpatches = prompt(skipfile,
1072 1073 skipall, msg, chunk)
1073 1074 if r:
1074 1075 if fixoffset:
1075 1076 chunk = copy.copy(chunk)
1076 1077 chunk.toline += fixoffset
1077 1078 applied[chunk.filename()].append(chunk)
1078 1079 elif newpatches is not None:
1079 1080 for newpatch in newpatches:
1080 1081 for newhunk in newpatch.hunks:
1081 1082 if fixoffset:
1082 1083 newhunk.toline += fixoffset
1083 1084 applied[newhunk.filename()].append(newhunk)
1084 1085 else:
1085 1086 fixoffset += chunk.removed - chunk.added
1086 1087 return sum([h for h in applied.itervalues()
1087 1088 if h[0].special() or len(h) > 1], [])
1088 1089 class hunk(object):
1089 1090 def __init__(self, desc, num, lr, context):
1090 1091 self.number = num
1091 1092 self.desc = desc
1092 1093 self.hunk = [desc]
1093 1094 self.a = []
1094 1095 self.b = []
1095 1096 self.starta = self.lena = None
1096 1097 self.startb = self.lenb = None
1097 1098 if lr is not None:
1098 1099 if context:
1099 1100 self.read_context_hunk(lr)
1100 1101 else:
1101 1102 self.read_unified_hunk(lr)
1102 1103
1103 1104 def getnormalized(self):
1104 1105 """Return a copy with line endings normalized to LF."""
1105 1106
1106 1107 def normalize(lines):
1107 1108 nlines = []
1108 1109 for line in lines:
1109 1110 if line.endswith('\r\n'):
1110 1111 line = line[:-2] + '\n'
1111 1112 nlines.append(line)
1112 1113 return nlines
1113 1114
1114 1115 # Dummy object, it is rebuilt manually
1115 1116 nh = hunk(self.desc, self.number, None, None)
1116 1117 nh.number = self.number
1117 1118 nh.desc = self.desc
1118 1119 nh.hunk = self.hunk
1119 1120 nh.a = normalize(self.a)
1120 1121 nh.b = normalize(self.b)
1121 1122 nh.starta = self.starta
1122 1123 nh.startb = self.startb
1123 1124 nh.lena = self.lena
1124 1125 nh.lenb = self.lenb
1125 1126 return nh
1126 1127
1127 1128 def read_unified_hunk(self, lr):
1128 1129 m = unidesc.match(self.desc)
1129 1130 if not m:
1130 1131 raise PatchError(_("bad hunk #%d") % self.number)
1131 1132 self.starta, self.lena, self.startb, self.lenb = m.groups()
1132 1133 if self.lena is None:
1133 1134 self.lena = 1
1134 1135 else:
1135 1136 self.lena = int(self.lena)
1136 1137 if self.lenb is None:
1137 1138 self.lenb = 1
1138 1139 else:
1139 1140 self.lenb = int(self.lenb)
1140 1141 self.starta = int(self.starta)
1141 1142 self.startb = int(self.startb)
1142 1143 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
1143 1144 self.b)
1144 1145 # if we hit eof before finishing out the hunk, the last line will
1145 1146 # be zero length. Lets try to fix it up.
1146 1147 while len(self.hunk[-1]) == 0:
1147 1148 del self.hunk[-1]
1148 1149 del self.a[-1]
1149 1150 del self.b[-1]
1150 1151 self.lena -= 1
1151 1152 self.lenb -= 1
1152 1153 self._fixnewline(lr)
1153 1154
1154 1155 def read_context_hunk(self, lr):
1155 1156 self.desc = lr.readline()
1156 1157 m = contextdesc.match(self.desc)
1157 1158 if not m:
1158 1159 raise PatchError(_("bad hunk #%d") % self.number)
1159 1160 self.starta, aend = m.groups()
1160 1161 self.starta = int(self.starta)
1161 1162 if aend is None:
1162 1163 aend = self.starta
1163 1164 self.lena = int(aend) - self.starta
1164 1165 if self.starta:
1165 1166 self.lena += 1
1166 1167 for x in xrange(self.lena):
1167 1168 l = lr.readline()
1168 1169 if l.startswith('---'):
1169 1170 # lines addition, old block is empty
1170 1171 lr.push(l)
1171 1172 break
1172 1173 s = l[2:]
1173 1174 if l.startswith('- ') or l.startswith('! '):
1174 1175 u = '-' + s
1175 1176 elif l.startswith(' '):
1176 1177 u = ' ' + s
1177 1178 else:
1178 1179 raise PatchError(_("bad hunk #%d old text line %d") %
1179 1180 (self.number, x))
1180 1181 self.a.append(u)
1181 1182 self.hunk.append(u)
1182 1183
1183 1184 l = lr.readline()
1184 1185 if l.startswith('\ '):
1185 1186 s = self.a[-1][:-1]
1186 1187 self.a[-1] = s
1187 1188 self.hunk[-1] = s
1188 1189 l = lr.readline()
1189 1190 m = contextdesc.match(l)
1190 1191 if not m:
1191 1192 raise PatchError(_("bad hunk #%d") % self.number)
1192 1193 self.startb, bend = m.groups()
1193 1194 self.startb = int(self.startb)
1194 1195 if bend is None:
1195 1196 bend = self.startb
1196 1197 self.lenb = int(bend) - self.startb
1197 1198 if self.startb:
1198 1199 self.lenb += 1
1199 1200 hunki = 1
1200 1201 for x in xrange(self.lenb):
1201 1202 l = lr.readline()
1202 1203 if l.startswith('\ '):
1203 1204 # XXX: the only way to hit this is with an invalid line range.
1204 1205 # The no-eol marker is not counted in the line range, but I
1205 1206 # guess there are diff(1) out there which behave differently.
1206 1207 s = self.b[-1][:-1]
1207 1208 self.b[-1] = s
1208 1209 self.hunk[hunki - 1] = s
1209 1210 continue
1210 1211 if not l:
1211 1212 # line deletions, new block is empty and we hit EOF
1212 1213 lr.push(l)
1213 1214 break
1214 1215 s = l[2:]
1215 1216 if l.startswith('+ ') or l.startswith('! '):
1216 1217 u = '+' + s
1217 1218 elif l.startswith(' '):
1218 1219 u = ' ' + s
1219 1220 elif len(self.b) == 0:
1220 1221 # line deletions, new block is empty
1221 1222 lr.push(l)
1222 1223 break
1223 1224 else:
1224 1225 raise PatchError(_("bad hunk #%d old text line %d") %
1225 1226 (self.number, x))
1226 1227 self.b.append(s)
1227 1228 while True:
1228 1229 if hunki >= len(self.hunk):
1229 1230 h = ""
1230 1231 else:
1231 1232 h = self.hunk[hunki]
1232 1233 hunki += 1
1233 1234 if h == u:
1234 1235 break
1235 1236 elif h.startswith('-'):
1236 1237 continue
1237 1238 else:
1238 1239 self.hunk.insert(hunki - 1, u)
1239 1240 break
1240 1241
1241 1242 if not self.a:
1242 1243 # this happens when lines were only added to the hunk
1243 1244 for x in self.hunk:
1244 1245 if x.startswith('-') or x.startswith(' '):
1245 1246 self.a.append(x)
1246 1247 if not self.b:
1247 1248 # this happens when lines were only deleted from the hunk
1248 1249 for x in self.hunk:
1249 1250 if x.startswith('+') or x.startswith(' '):
1250 1251 self.b.append(x[1:])
1251 1252 # @@ -start,len +start,len @@
1252 1253 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
1253 1254 self.startb, self.lenb)
1254 1255 self.hunk[0] = self.desc
1255 1256 self._fixnewline(lr)
1256 1257
1257 1258 def _fixnewline(self, lr):
1258 1259 l = lr.readline()
1259 1260 if l.startswith('\ '):
1260 1261 diffhelpers.fix_newline(self.hunk, self.a, self.b)
1261 1262 else:
1262 1263 lr.push(l)
1263 1264
1264 1265 def complete(self):
1265 1266 return len(self.a) == self.lena and len(self.b) == self.lenb
1266 1267
1267 1268 def _fuzzit(self, old, new, fuzz, toponly):
1268 1269 # this removes context lines from the top and bottom of list 'l'. It
1269 1270 # checks the hunk to make sure only context lines are removed, and then
1270 1271 # returns a new shortened list of lines.
1271 1272 fuzz = min(fuzz, len(old))
1272 1273 if fuzz:
1273 1274 top = 0
1274 1275 bot = 0
1275 1276 hlen = len(self.hunk)
1276 1277 for x in xrange(hlen - 1):
1277 1278 # the hunk starts with the @@ line, so use x+1
1278 1279 if self.hunk[x + 1][0] == ' ':
1279 1280 top += 1
1280 1281 else:
1281 1282 break
1282 1283 if not toponly:
1283 1284 for x in xrange(hlen - 1):
1284 1285 if self.hunk[hlen - bot - 1][0] == ' ':
1285 1286 bot += 1
1286 1287 else:
1287 1288 break
1288 1289
1289 1290 bot = min(fuzz, bot)
1290 1291 top = min(fuzz, top)
1291 1292 return old[top:len(old) - bot], new[top:len(new) - bot], top
1292 1293 return old, new, 0
1293 1294
1294 1295 def fuzzit(self, fuzz, toponly):
1295 1296 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1296 1297 oldstart = self.starta + top
1297 1298 newstart = self.startb + top
1298 1299 # zero length hunk ranges already have their start decremented
1299 1300 if self.lena and oldstart > 0:
1300 1301 oldstart -= 1
1301 1302 if self.lenb and newstart > 0:
1302 1303 newstart -= 1
1303 1304 return old, oldstart, new, newstart
1304 1305
1305 1306 class binhunk(object):
1306 1307 'A binary patch file.'
1307 1308 def __init__(self, lr, fname):
1308 1309 self.text = None
1309 1310 self.delta = False
1310 1311 self.hunk = ['GIT binary patch\n']
1311 1312 self._fname = fname
1312 1313 self._read(lr)
1313 1314
1314 1315 def complete(self):
1315 1316 return self.text is not None
1316 1317
1317 1318 def new(self, lines):
1318 1319 if self.delta:
1319 1320 return [applybindelta(self.text, ''.join(lines))]
1320 1321 return [self.text]
1321 1322
1322 1323 def _read(self, lr):
1323 1324 def getline(lr, hunk):
1324 1325 l = lr.readline()
1325 1326 hunk.append(l)
1326 1327 return l.rstrip('\r\n')
1327 1328
1328 1329 size = 0
1329 1330 while True:
1330 1331 line = getline(lr, self.hunk)
1331 1332 if not line:
1332 1333 raise PatchError(_('could not extract "%s" binary data')
1333 1334 % self._fname)
1334 1335 if line.startswith('literal '):
1335 1336 size = int(line[8:].rstrip())
1336 1337 break
1337 1338 if line.startswith('delta '):
1338 1339 size = int(line[6:].rstrip())
1339 1340 self.delta = True
1340 1341 break
1341 1342 dec = []
1342 1343 line = getline(lr, self.hunk)
1343 1344 while len(line) > 1:
1344 1345 l = line[0]
1345 1346 if l <= 'Z' and l >= 'A':
1346 1347 l = ord(l) - ord('A') + 1
1347 1348 else:
1348 1349 l = ord(l) - ord('a') + 27
1349 1350 try:
1350 1351 dec.append(base85.b85decode(line[1:])[:l])
1351 1352 except ValueError, e:
1352 1353 raise PatchError(_('could not decode "%s" binary patch: %s')
1353 1354 % (self._fname, str(e)))
1354 1355 line = getline(lr, self.hunk)
1355 1356 text = zlib.decompress(''.join(dec))
1356 1357 if len(text) != size:
1357 1358 raise PatchError(_('"%s" length is %d bytes, should be %d')
1358 1359 % (self._fname, len(text), size))
1359 1360 self.text = text
1360 1361
1361 1362 def parsefilename(str):
1362 1363 # --- filename \t|space stuff
1363 1364 s = str[4:].rstrip('\r\n')
1364 1365 i = s.find('\t')
1365 1366 if i < 0:
1366 1367 i = s.find(' ')
1367 1368 if i < 0:
1368 1369 return s
1369 1370 return s[:i]
1370 1371
1371 1372 def parsepatch(originalchunks):
1372 1373 """patch -> [] of headers -> [] of hunks """
1373 1374 class parser(object):
1374 1375 """patch parsing state machine"""
1375 1376 def __init__(self):
1376 1377 self.fromline = 0
1377 1378 self.toline = 0
1378 1379 self.proc = ''
1379 1380 self.header = None
1380 1381 self.context = []
1381 1382 self.before = []
1382 1383 self.hunk = []
1383 1384 self.headers = []
1384 1385
1385 1386 def addrange(self, limits):
1386 1387 fromstart, fromend, tostart, toend, proc = limits
1387 1388 self.fromline = int(fromstart)
1388 1389 self.toline = int(tostart)
1389 1390 self.proc = proc
1390 1391
1391 1392 def addcontext(self, context):
1392 1393 if self.hunk:
1393 1394 h = recordhunk(self.header, self.fromline, self.toline,
1394 1395 self.proc, self.before, self.hunk, context)
1395 1396 self.header.hunks.append(h)
1396 1397 self.fromline += len(self.before) + h.removed
1397 1398 self.toline += len(self.before) + h.added
1398 1399 self.before = []
1399 1400 self.hunk = []
1400 1401 self.proc = ''
1401 1402 self.context = context
1402 1403
1403 1404 def addhunk(self, hunk):
1404 1405 if self.context:
1405 1406 self.before = self.context
1406 1407 self.context = []
1407 1408 self.hunk = hunk
1408 1409
1409 1410 def newfile(self, hdr):
1410 1411 self.addcontext([])
1411 1412 h = header(hdr)
1412 1413 self.headers.append(h)
1413 1414 self.header = h
1414 1415
1415 1416 def addother(self, line):
1416 1417 pass # 'other' lines are ignored
1417 1418
1418 1419 def finished(self):
1419 1420 self.addcontext([])
1420 1421 return self.headers
1421 1422
1422 1423 transitions = {
1423 1424 'file': {'context': addcontext,
1424 1425 'file': newfile,
1425 1426 'hunk': addhunk,
1426 1427 'range': addrange},
1427 1428 'context': {'file': newfile,
1428 1429 'hunk': addhunk,
1429 1430 'range': addrange,
1430 1431 'other': addother},
1431 1432 'hunk': {'context': addcontext,
1432 1433 'file': newfile,
1433 1434 'range': addrange},
1434 1435 'range': {'context': addcontext,
1435 1436 'hunk': addhunk},
1436 1437 'other': {'other': addother},
1437 1438 }
1438 1439
1439 1440 p = parser()
1440 1441 fp = cStringIO.StringIO()
1441 1442 fp.write(''.join(originalchunks))
1442 1443 fp.seek(0)
1443 1444
1444 1445 state = 'context'
1445 1446 for newstate, data in scanpatch(fp):
1446 1447 try:
1447 1448 p.transitions[state][newstate](p, data)
1448 1449 except KeyError:
1449 1450 raise PatchError('unhandled transition: %s -> %s' %
1450 1451 (state, newstate))
1451 1452 state = newstate
1452 1453 del fp
1453 1454 return p.finished()
1454 1455
1455 1456 def pathtransform(path, strip, prefix):
1456 1457 '''turn a path from a patch into a path suitable for the repository
1457 1458
1458 1459 prefix, if not empty, is expected to be normalized with a / at the end.
1459 1460
1460 1461 Returns (stripped components, path in repository).
1461 1462
1462 1463 >>> pathtransform('a/b/c', 0, '')
1463 1464 ('', 'a/b/c')
1464 1465 >>> pathtransform(' a/b/c ', 0, '')
1465 1466 ('', ' a/b/c')
1466 1467 >>> pathtransform(' a/b/c ', 2, '')
1467 1468 ('a/b/', 'c')
1468 1469 >>> pathtransform('a/b/c', 0, 'd/e/')
1469 1470 ('', 'd/e/a/b/c')
1470 1471 >>> pathtransform(' a//b/c ', 2, 'd/e/')
1471 1472 ('a//b/', 'd/e/c')
1472 1473 >>> pathtransform('a/b/c', 3, '')
1473 1474 Traceback (most recent call last):
1474 1475 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1475 1476 '''
1476 1477 pathlen = len(path)
1477 1478 i = 0
1478 1479 if strip == 0:
1479 1480 return '', prefix + path.rstrip()
1480 1481 count = strip
1481 1482 while count > 0:
1482 1483 i = path.find('/', i)
1483 1484 if i == -1:
1484 1485 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1485 1486 (count, strip, path))
1486 1487 i += 1
1487 1488 # consume '//' in the path
1488 1489 while i < pathlen - 1 and path[i] == '/':
1489 1490 i += 1
1490 1491 count -= 1
1491 1492 return path[:i].lstrip(), prefix + path[i:].rstrip()
1492 1493
1493 1494 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1494 1495 nulla = afile_orig == "/dev/null"
1495 1496 nullb = bfile_orig == "/dev/null"
1496 1497 create = nulla and hunk.starta == 0 and hunk.lena == 0
1497 1498 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1498 1499 abase, afile = pathtransform(afile_orig, strip, prefix)
1499 1500 gooda = not nulla and backend.exists(afile)
1500 1501 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1501 1502 if afile == bfile:
1502 1503 goodb = gooda
1503 1504 else:
1504 1505 goodb = not nullb and backend.exists(bfile)
1505 1506 missing = not goodb and not gooda and not create
1506 1507
1507 1508 # some diff programs apparently produce patches where the afile is
1508 1509 # not /dev/null, but afile starts with bfile
1509 1510 abasedir = afile[:afile.rfind('/') + 1]
1510 1511 bbasedir = bfile[:bfile.rfind('/') + 1]
1511 1512 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1512 1513 and hunk.starta == 0 and hunk.lena == 0):
1513 1514 create = True
1514 1515 missing = False
1515 1516
1516 1517 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1517 1518 # diff is between a file and its backup. In this case, the original
1518 1519 # file should be patched (see original mpatch code).
1519 1520 isbackup = (abase == bbase and bfile.startswith(afile))
1520 1521 fname = None
1521 1522 if not missing:
1522 1523 if gooda and goodb:
1523 1524 if isbackup:
1524 1525 fname = afile
1525 1526 else:
1526 1527 fname = bfile
1527 1528 elif gooda:
1528 1529 fname = afile
1529 1530
1530 1531 if not fname:
1531 1532 if not nullb:
1532 1533 if isbackup:
1533 1534 fname = afile
1534 1535 else:
1535 1536 fname = bfile
1536 1537 elif not nulla:
1537 1538 fname = afile
1538 1539 else:
1539 1540 raise PatchError(_("undefined source and destination files"))
1540 1541
1541 1542 gp = patchmeta(fname)
1542 1543 if create:
1543 1544 gp.op = 'ADD'
1544 1545 elif remove:
1545 1546 gp.op = 'DELETE'
1546 1547 return gp
1547 1548
1548 1549 def scanpatch(fp):
1549 1550 """like patch.iterhunks, but yield different events
1550 1551
1551 1552 - ('file', [header_lines + fromfile + tofile])
1552 1553 - ('context', [context_lines])
1553 1554 - ('hunk', [hunk_lines])
1554 1555 - ('range', (-start,len, +start,len, proc))
1555 1556 """
1556 1557 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
1557 1558 lr = linereader(fp)
1558 1559
1559 1560 def scanwhile(first, p):
1560 1561 """scan lr while predicate holds"""
1561 1562 lines = [first]
1562 1563 while True:
1563 1564 line = lr.readline()
1564 1565 if not line:
1565 1566 break
1566 1567 if p(line):
1567 1568 lines.append(line)
1568 1569 else:
1569 1570 lr.push(line)
1570 1571 break
1571 1572 return lines
1572 1573
1573 1574 while True:
1574 1575 line = lr.readline()
1575 1576 if not line:
1576 1577 break
1577 1578 if line.startswith('diff --git a/') or line.startswith('diff -r '):
1578 1579 def notheader(line):
1579 1580 s = line.split(None, 1)
1580 1581 return not s or s[0] not in ('---', 'diff')
1581 1582 header = scanwhile(line, notheader)
1582 1583 fromfile = lr.readline()
1583 1584 if fromfile.startswith('---'):
1584 1585 tofile = lr.readline()
1585 1586 header += [fromfile, tofile]
1586 1587 else:
1587 1588 lr.push(fromfile)
1588 1589 yield 'file', header
1589 1590 elif line[0] == ' ':
1590 1591 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
1591 1592 elif line[0] in '-+':
1592 1593 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
1593 1594 else:
1594 1595 m = lines_re.match(line)
1595 1596 if m:
1596 1597 yield 'range', m.groups()
1597 1598 else:
1598 1599 yield 'other', line
1599 1600
1600 1601 def scangitpatch(lr, firstline):
1601 1602 """
1602 1603 Git patches can emit:
1603 1604 - rename a to b
1604 1605 - change b
1605 1606 - copy a to c
1606 1607 - change c
1607 1608
1608 1609 We cannot apply this sequence as-is, the renamed 'a' could not be
1609 1610 found for it would have been renamed already. And we cannot copy
1610 1611 from 'b' instead because 'b' would have been changed already. So
1611 1612 we scan the git patch for copy and rename commands so we can
1612 1613 perform the copies ahead of time.
1613 1614 """
1614 1615 pos = 0
1615 1616 try:
1616 1617 pos = lr.fp.tell()
1617 1618 fp = lr.fp
1618 1619 except IOError:
1619 1620 fp = cStringIO.StringIO(lr.fp.read())
1620 1621 gitlr = linereader(fp)
1621 1622 gitlr.push(firstline)
1622 1623 gitpatches = readgitpatch(gitlr)
1623 1624 fp.seek(pos)
1624 1625 return gitpatches
1625 1626
1626 1627 def iterhunks(fp):
1627 1628 """Read a patch and yield the following events:
1628 1629 - ("file", afile, bfile, firsthunk): select a new target file.
1629 1630 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1630 1631 "file" event.
1631 1632 - ("git", gitchanges): current diff is in git format, gitchanges
1632 1633 maps filenames to gitpatch records. Unique event.
1633 1634 """
1634 1635 afile = ""
1635 1636 bfile = ""
1636 1637 state = None
1637 1638 hunknum = 0
1638 1639 emitfile = newfile = False
1639 1640 gitpatches = None
1640 1641
1641 1642 # our states
1642 1643 BFILE = 1
1643 1644 context = None
1644 1645 lr = linereader(fp)
1645 1646
1646 1647 while True:
1647 1648 x = lr.readline()
1648 1649 if not x:
1649 1650 break
1650 1651 if state == BFILE and (
1651 1652 (not context and x[0] == '@')
1652 1653 or (context is not False and x.startswith('***************'))
1653 1654 or x.startswith('GIT binary patch')):
1654 1655 gp = None
1655 1656 if (gitpatches and
1656 1657 gitpatches[-1].ispatching(afile, bfile)):
1657 1658 gp = gitpatches.pop()
1658 1659 if x.startswith('GIT binary patch'):
1659 1660 h = binhunk(lr, gp.path)
1660 1661 else:
1661 1662 if context is None and x.startswith('***************'):
1662 1663 context = True
1663 1664 h = hunk(x, hunknum + 1, lr, context)
1664 1665 hunknum += 1
1665 1666 if emitfile:
1666 1667 emitfile = False
1667 1668 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1668 1669 yield 'hunk', h
1669 1670 elif x.startswith('diff --git a/'):
1670 1671 m = gitre.match(x.rstrip(' \r\n'))
1671 1672 if not m:
1672 1673 continue
1673 1674 if gitpatches is None:
1674 1675 # scan whole input for git metadata
1675 1676 gitpatches = scangitpatch(lr, x)
1676 1677 yield 'git', [g.copy() for g in gitpatches
1677 1678 if g.op in ('COPY', 'RENAME')]
1678 1679 gitpatches.reverse()
1679 1680 afile = 'a/' + m.group(1)
1680 1681 bfile = 'b/' + m.group(2)
1681 1682 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1682 1683 gp = gitpatches.pop()
1683 1684 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1684 1685 if not gitpatches:
1685 1686 raise PatchError(_('failed to synchronize metadata for "%s"')
1686 1687 % afile[2:])
1687 1688 gp = gitpatches[-1]
1688 1689 newfile = True
1689 1690 elif x.startswith('---'):
1690 1691 # check for a unified diff
1691 1692 l2 = lr.readline()
1692 1693 if not l2.startswith('+++'):
1693 1694 lr.push(l2)
1694 1695 continue
1695 1696 newfile = True
1696 1697 context = False
1697 1698 afile = parsefilename(x)
1698 1699 bfile = parsefilename(l2)
1699 1700 elif x.startswith('***'):
1700 1701 # check for a context diff
1701 1702 l2 = lr.readline()
1702 1703 if not l2.startswith('---'):
1703 1704 lr.push(l2)
1704 1705 continue
1705 1706 l3 = lr.readline()
1706 1707 lr.push(l3)
1707 1708 if not l3.startswith("***************"):
1708 1709 lr.push(l2)
1709 1710 continue
1710 1711 newfile = True
1711 1712 context = True
1712 1713 afile = parsefilename(x)
1713 1714 bfile = parsefilename(l2)
1714 1715
1715 1716 if newfile:
1716 1717 newfile = False
1717 1718 emitfile = True
1718 1719 state = BFILE
1719 1720 hunknum = 0
1720 1721
1721 1722 while gitpatches:
1722 1723 gp = gitpatches.pop()
1723 1724 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1724 1725
1725 1726 def applybindelta(binchunk, data):
1726 1727 """Apply a binary delta hunk
1727 1728 The algorithm used is the algorithm from git's patch-delta.c
1728 1729 """
1729 1730 def deltahead(binchunk):
1730 1731 i = 0
1731 1732 for c in binchunk:
1732 1733 i += 1
1733 1734 if not (ord(c) & 0x80):
1734 1735 return i
1735 1736 return i
1736 1737 out = ""
1737 1738 s = deltahead(binchunk)
1738 1739 binchunk = binchunk[s:]
1739 1740 s = deltahead(binchunk)
1740 1741 binchunk = binchunk[s:]
1741 1742 i = 0
1742 1743 while i < len(binchunk):
1743 1744 cmd = ord(binchunk[i])
1744 1745 i += 1
1745 1746 if (cmd & 0x80):
1746 1747 offset = 0
1747 1748 size = 0
1748 1749 if (cmd & 0x01):
1749 1750 offset = ord(binchunk[i])
1750 1751 i += 1
1751 1752 if (cmd & 0x02):
1752 1753 offset |= ord(binchunk[i]) << 8
1753 1754 i += 1
1754 1755 if (cmd & 0x04):
1755 1756 offset |= ord(binchunk[i]) << 16
1756 1757 i += 1
1757 1758 if (cmd & 0x08):
1758 1759 offset |= ord(binchunk[i]) << 24
1759 1760 i += 1
1760 1761 if (cmd & 0x10):
1761 1762 size = ord(binchunk[i])
1762 1763 i += 1
1763 1764 if (cmd & 0x20):
1764 1765 size |= ord(binchunk[i]) << 8
1765 1766 i += 1
1766 1767 if (cmd & 0x40):
1767 1768 size |= ord(binchunk[i]) << 16
1768 1769 i += 1
1769 1770 if size == 0:
1770 1771 size = 0x10000
1771 1772 offset_end = offset + size
1772 1773 out += data[offset:offset_end]
1773 1774 elif cmd != 0:
1774 1775 offset_end = i + cmd
1775 1776 out += binchunk[i:offset_end]
1776 1777 i += cmd
1777 1778 else:
1778 1779 raise PatchError(_('unexpected delta opcode 0'))
1779 1780 return out
1780 1781
1781 1782 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1782 1783 """Reads a patch from fp and tries to apply it.
1783 1784
1784 1785 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1785 1786 there was any fuzz.
1786 1787
1787 1788 If 'eolmode' is 'strict', the patch content and patched file are
1788 1789 read in binary mode. Otherwise, line endings are ignored when
1789 1790 patching then normalized according to 'eolmode'.
1790 1791 """
1791 1792 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1792 1793 prefix=prefix, eolmode=eolmode)
1793 1794
1794 1795 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
1795 1796 eolmode='strict'):
1796 1797
1797 1798 if prefix:
1798 # clean up double slashes, lack of trailing slashes, etc
1799 prefix = util.normpath(prefix) + '/'
1799 prefix = pathutil.canonpath(backend.repo.root, backend.repo.getcwd(),
1800 prefix)
1801 if prefix != '':
1802 prefix += '/'
1800 1803 def pstrip(p):
1801 1804 return pathtransform(p, strip - 1, prefix)[1]
1802 1805
1803 1806 rejects = 0
1804 1807 err = 0
1805 1808 current_file = None
1806 1809
1807 1810 for state, values in iterhunks(fp):
1808 1811 if state == 'hunk':
1809 1812 if not current_file:
1810 1813 continue
1811 1814 ret = current_file.apply(values)
1812 1815 if ret > 0:
1813 1816 err = 1
1814 1817 elif state == 'file':
1815 1818 if current_file:
1816 1819 rejects += current_file.close()
1817 1820 current_file = None
1818 1821 afile, bfile, first_hunk, gp = values
1819 1822 if gp:
1820 1823 gp.path = pstrip(gp.path)
1821 1824 if gp.oldpath:
1822 1825 gp.oldpath = pstrip(gp.oldpath)
1823 1826 else:
1824 1827 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1825 1828 prefix)
1826 1829 if gp.op == 'RENAME':
1827 1830 backend.unlink(gp.oldpath)
1828 1831 if not first_hunk:
1829 1832 if gp.op == 'DELETE':
1830 1833 backend.unlink(gp.path)
1831 1834 continue
1832 1835 data, mode = None, None
1833 1836 if gp.op in ('RENAME', 'COPY'):
1834 1837 data, mode = store.getfile(gp.oldpath)[:2]
1835 1838 # FIXME: failing getfile has never been handled here
1836 1839 assert data is not None
1837 1840 if gp.mode:
1838 1841 mode = gp.mode
1839 1842 if gp.op == 'ADD':
1840 1843 # Added files without content have no hunk and
1841 1844 # must be created
1842 1845 data = ''
1843 1846 if data or mode:
1844 1847 if (gp.op in ('ADD', 'RENAME', 'COPY')
1845 1848 and backend.exists(gp.path)):
1846 1849 raise PatchError(_("cannot create %s: destination "
1847 1850 "already exists") % gp.path)
1848 1851 backend.setfile(gp.path, data, mode, gp.oldpath)
1849 1852 continue
1850 1853 try:
1851 1854 current_file = patcher(ui, gp, backend, store,
1852 1855 eolmode=eolmode)
1853 1856 except PatchError, inst:
1854 1857 ui.warn(str(inst) + '\n')
1855 1858 current_file = None
1856 1859 rejects += 1
1857 1860 continue
1858 1861 elif state == 'git':
1859 1862 for gp in values:
1860 1863 path = pstrip(gp.oldpath)
1861 1864 data, mode = backend.getfile(path)
1862 1865 if data is None:
1863 1866 # The error ignored here will trigger a getfile()
1864 1867 # error in a place more appropriate for error
1865 1868 # handling, and will not interrupt the patching
1866 1869 # process.
1867 1870 pass
1868 1871 else:
1869 1872 store.setfile(path, data, mode)
1870 1873 else:
1871 1874 raise util.Abort(_('unsupported parser state: %s') % state)
1872 1875
1873 1876 if current_file:
1874 1877 rejects += current_file.close()
1875 1878
1876 1879 if rejects:
1877 1880 return -1
1878 1881 return err
1879 1882
1880 1883 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1881 1884 similarity):
1882 1885 """use <patcher> to apply <patchname> to the working directory.
1883 1886 returns whether patch was applied with fuzz factor."""
1884 1887
1885 1888 fuzz = False
1886 1889 args = []
1887 1890 cwd = repo.root
1888 1891 if cwd:
1889 1892 args.append('-d %s' % util.shellquote(cwd))
1890 1893 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1891 1894 util.shellquote(patchname)))
1892 1895 try:
1893 1896 for line in fp:
1894 1897 line = line.rstrip()
1895 1898 ui.note(line + '\n')
1896 1899 if line.startswith('patching file '):
1897 1900 pf = util.parsepatchoutput(line)
1898 1901 printed_file = False
1899 1902 files.add(pf)
1900 1903 elif line.find('with fuzz') >= 0:
1901 1904 fuzz = True
1902 1905 if not printed_file:
1903 1906 ui.warn(pf + '\n')
1904 1907 printed_file = True
1905 1908 ui.warn(line + '\n')
1906 1909 elif line.find('saving rejects to file') >= 0:
1907 1910 ui.warn(line + '\n')
1908 1911 elif line.find('FAILED') >= 0:
1909 1912 if not printed_file:
1910 1913 ui.warn(pf + '\n')
1911 1914 printed_file = True
1912 1915 ui.warn(line + '\n')
1913 1916 finally:
1914 1917 if files:
1915 1918 scmutil.marktouched(repo, files, similarity)
1916 1919 code = fp.close()
1917 1920 if code:
1918 1921 raise PatchError(_("patch command failed: %s") %
1919 1922 util.explainexit(code)[0])
1920 1923 return fuzz
1921 1924
1922 1925 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
1923 1926 eolmode='strict'):
1924 1927 if files is None:
1925 1928 files = set()
1926 1929 if eolmode is None:
1927 1930 eolmode = ui.config('patch', 'eol', 'strict')
1928 1931 if eolmode.lower() not in eolmodes:
1929 1932 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1930 1933 eolmode = eolmode.lower()
1931 1934
1932 1935 store = filestore()
1933 1936 try:
1934 1937 fp = open(patchobj, 'rb')
1935 1938 except TypeError:
1936 1939 fp = patchobj
1937 1940 try:
1938 1941 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
1939 1942 eolmode=eolmode)
1940 1943 finally:
1941 1944 if fp != patchobj:
1942 1945 fp.close()
1943 1946 files.update(backend.close())
1944 1947 store.close()
1945 1948 if ret < 0:
1946 1949 raise PatchError(_('patch failed to apply'))
1947 1950 return ret > 0
1948 1951
1949 1952 def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
1950 1953 eolmode='strict', similarity=0):
1951 1954 """use builtin patch to apply <patchobj> to the working directory.
1952 1955 returns whether patch was applied with fuzz factor."""
1953 1956 backend = workingbackend(ui, repo, similarity)
1954 1957 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1955 1958
1956 1959 def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
1957 1960 eolmode='strict'):
1958 1961 backend = repobackend(ui, repo, ctx, store)
1959 1962 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1960 1963
1961 1964 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
1962 1965 similarity=0):
1963 1966 """Apply <patchname> to the working directory.
1964 1967
1965 1968 'eolmode' specifies how end of lines should be handled. It can be:
1966 1969 - 'strict': inputs are read in binary mode, EOLs are preserved
1967 1970 - 'crlf': EOLs are ignored when patching and reset to CRLF
1968 1971 - 'lf': EOLs are ignored when patching and reset to LF
1969 1972 - None: get it from user settings, default to 'strict'
1970 1973 'eolmode' is ignored when using an external patcher program.
1971 1974
1972 1975 Returns whether patch was applied with fuzz factor.
1973 1976 """
1974 1977 patcher = ui.config('ui', 'patch')
1975 1978 if files is None:
1976 1979 files = set()
1977 1980 if patcher:
1978 1981 return _externalpatch(ui, repo, patcher, patchname, strip,
1979 1982 files, similarity)
1980 1983 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
1981 1984 similarity)
1982 1985
1983 1986 def changedfiles(ui, repo, patchpath, strip=1):
1984 1987 backend = fsbackend(ui, repo.root)
1985 1988 fp = open(patchpath, 'rb')
1986 1989 try:
1987 1990 changed = set()
1988 1991 for state, values in iterhunks(fp):
1989 1992 if state == 'file':
1990 1993 afile, bfile, first_hunk, gp = values
1991 1994 if gp:
1992 1995 gp.path = pathtransform(gp.path, strip - 1, '')[1]
1993 1996 if gp.oldpath:
1994 1997 gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
1995 1998 else:
1996 1999 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1997 2000 '')
1998 2001 changed.add(gp.path)
1999 2002 if gp.op == 'RENAME':
2000 2003 changed.add(gp.oldpath)
2001 2004 elif state not in ('hunk', 'git'):
2002 2005 raise util.Abort(_('unsupported parser state: %s') % state)
2003 2006 return changed
2004 2007 finally:
2005 2008 fp.close()
2006 2009
2007 2010 class GitDiffRequired(Exception):
2008 2011 pass
2009 2012
2010 2013 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
2011 2014 '''return diffopts with all features supported and parsed'''
2012 2015 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
2013 2016 git=True, whitespace=True, formatchanging=True)
2014 2017
2015 2018 diffopts = diffallopts
2016 2019
2017 2020 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
2018 2021 whitespace=False, formatchanging=False):
2019 2022 '''return diffopts with only opted-in features parsed
2020 2023
2021 2024 Features:
2022 2025 - git: git-style diffs
2023 2026 - whitespace: whitespace options like ignoreblanklines and ignorews
2024 2027 - formatchanging: options that will likely break or cause correctness issues
2025 2028 with most diff parsers
2026 2029 '''
2027 2030 def get(key, name=None, getter=ui.configbool, forceplain=None):
2028 2031 if opts:
2029 2032 v = opts.get(key)
2030 2033 if v:
2031 2034 return v
2032 2035 if forceplain is not None and ui.plain():
2033 2036 return forceplain
2034 2037 return getter(section, name or key, None, untrusted=untrusted)
2035 2038
2036 2039 # core options, expected to be understood by every diff parser
2037 2040 buildopts = {
2038 2041 'nodates': get('nodates'),
2039 2042 'showfunc': get('show_function', 'showfunc'),
2040 2043 'context': get('unified', getter=ui.config),
2041 2044 }
2042 2045
2043 2046 if git:
2044 2047 buildopts['git'] = get('git')
2045 2048 if whitespace:
2046 2049 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
2047 2050 buildopts['ignorewsamount'] = get('ignore_space_change',
2048 2051 'ignorewsamount')
2049 2052 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
2050 2053 'ignoreblanklines')
2051 2054 if formatchanging:
2052 2055 buildopts['text'] = opts and opts.get('text')
2053 2056 buildopts['nobinary'] = get('nobinary')
2054 2057 buildopts['noprefix'] = get('noprefix', forceplain=False)
2055 2058
2056 2059 return mdiff.diffopts(**buildopts)
2057 2060
2058 2061 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
2059 2062 losedatafn=None, prefix=''):
2060 2063 '''yields diff of changes to files between two nodes, or node and
2061 2064 working directory.
2062 2065
2063 2066 if node1 is None, use first dirstate parent instead.
2064 2067 if node2 is None, compare node1 with working directory.
2065 2068
2066 2069 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
2067 2070 every time some change cannot be represented with the current
2068 2071 patch format. Return False to upgrade to git patch format, True to
2069 2072 accept the loss or raise an exception to abort the diff. It is
2070 2073 called with the name of current file being diffed as 'fn'. If set
2071 2074 to None, patches will always be upgraded to git format when
2072 2075 necessary.
2073 2076
2074 2077 prefix is a filename prefix that is prepended to all filenames on
2075 2078 display (used for subrepos).
2076 2079 '''
2077 2080
2078 2081 if opts is None:
2079 2082 opts = mdiff.defaultopts
2080 2083
2081 2084 if not node1 and not node2:
2082 2085 node1 = repo.dirstate.p1()
2083 2086
2084 2087 def lrugetfilectx():
2085 2088 cache = {}
2086 2089 order = util.deque()
2087 2090 def getfilectx(f, ctx):
2088 2091 fctx = ctx.filectx(f, filelog=cache.get(f))
2089 2092 if f not in cache:
2090 2093 if len(cache) > 20:
2091 2094 del cache[order.popleft()]
2092 2095 cache[f] = fctx.filelog()
2093 2096 else:
2094 2097 order.remove(f)
2095 2098 order.append(f)
2096 2099 return fctx
2097 2100 return getfilectx
2098 2101 getfilectx = lrugetfilectx()
2099 2102
2100 2103 ctx1 = repo[node1]
2101 2104 ctx2 = repo[node2]
2102 2105
2103 2106 if not changes:
2104 2107 changes = repo.status(ctx1, ctx2, match=match)
2105 2108 modified, added, removed = changes[:3]
2106 2109
2107 2110 if not modified and not added and not removed:
2108 2111 return []
2109 2112
2110 2113 if repo.ui.debugflag:
2111 2114 hexfunc = hex
2112 2115 else:
2113 2116 hexfunc = short
2114 2117 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2115 2118
2116 2119 copy = {}
2117 2120 if opts.git or opts.upgrade:
2118 2121 copy = copies.pathcopies(ctx1, ctx2)
2119 2122
2120 2123 def difffn(opts, losedata):
2121 2124 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2122 2125 copy, getfilectx, opts, losedata, prefix)
2123 2126 if opts.upgrade and not opts.git:
2124 2127 try:
2125 2128 def losedata(fn):
2126 2129 if not losedatafn or not losedatafn(fn=fn):
2127 2130 raise GitDiffRequired
2128 2131 # Buffer the whole output until we are sure it can be generated
2129 2132 return list(difffn(opts.copy(git=False), losedata))
2130 2133 except GitDiffRequired:
2131 2134 return difffn(opts.copy(git=True), None)
2132 2135 else:
2133 2136 return difffn(opts, None)
2134 2137
2135 2138 def difflabel(func, *args, **kw):
2136 2139 '''yields 2-tuples of (output, label) based on the output of func()'''
2137 2140 headprefixes = [('diff', 'diff.diffline'),
2138 2141 ('copy', 'diff.extended'),
2139 2142 ('rename', 'diff.extended'),
2140 2143 ('old', 'diff.extended'),
2141 2144 ('new', 'diff.extended'),
2142 2145 ('deleted', 'diff.extended'),
2143 2146 ('---', 'diff.file_a'),
2144 2147 ('+++', 'diff.file_b')]
2145 2148 textprefixes = [('@', 'diff.hunk'),
2146 2149 ('-', 'diff.deleted'),
2147 2150 ('+', 'diff.inserted')]
2148 2151 head = False
2149 2152 for chunk in func(*args, **kw):
2150 2153 lines = chunk.split('\n')
2151 2154 for i, line in enumerate(lines):
2152 2155 if i != 0:
2153 2156 yield ('\n', '')
2154 2157 if head:
2155 2158 if line.startswith('@'):
2156 2159 head = False
2157 2160 else:
2158 2161 if line and line[0] not in ' +-@\\':
2159 2162 head = True
2160 2163 stripline = line
2161 2164 diffline = False
2162 2165 if not head and line and line[0] in '+-':
2163 2166 # highlight tabs and trailing whitespace, but only in
2164 2167 # changed lines
2165 2168 stripline = line.rstrip()
2166 2169 diffline = True
2167 2170
2168 2171 prefixes = textprefixes
2169 2172 if head:
2170 2173 prefixes = headprefixes
2171 2174 for prefix, label in prefixes:
2172 2175 if stripline.startswith(prefix):
2173 2176 if diffline:
2174 2177 for token in tabsplitter.findall(stripline):
2175 2178 if '\t' == token[0]:
2176 2179 yield (token, 'diff.tab')
2177 2180 else:
2178 2181 yield (token, label)
2179 2182 else:
2180 2183 yield (stripline, label)
2181 2184 break
2182 2185 else:
2183 2186 yield (line, '')
2184 2187 if line != stripline:
2185 2188 yield (line[len(stripline):], 'diff.trailingwhitespace')
2186 2189
2187 2190 def diffui(*args, **kw):
2188 2191 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2189 2192 return difflabel(diff, *args, **kw)
2190 2193
2191 2194 def _filepairs(ctx1, modified, added, removed, copy, opts):
2192 2195 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
2193 2196 before and f2 is the the name after. For added files, f1 will be None,
2194 2197 and for removed files, f2 will be None. copyop may be set to None, 'copy'
2195 2198 or 'rename' (the latter two only if opts.git is set).'''
2196 2199 gone = set()
2197 2200
2198 2201 copyto = dict([(v, k) for k, v in copy.items()])
2199 2202
2200 2203 addedset, removedset = set(added), set(removed)
2201 2204 # Fix up added, since merged-in additions appear as
2202 2205 # modifications during merges
2203 2206 for f in modified:
2204 2207 if f not in ctx1:
2205 2208 addedset.add(f)
2206 2209
2207 2210 for f in sorted(modified + added + removed):
2208 2211 copyop = None
2209 2212 f1, f2 = f, f
2210 2213 if f in addedset:
2211 2214 f1 = None
2212 2215 if f in copy:
2213 2216 if opts.git:
2214 2217 f1 = copy[f]
2215 2218 if f1 in removedset and f1 not in gone:
2216 2219 copyop = 'rename'
2217 2220 gone.add(f1)
2218 2221 else:
2219 2222 copyop = 'copy'
2220 2223 elif f in removedset:
2221 2224 f2 = None
2222 2225 if opts.git:
2223 2226 # have we already reported a copy above?
2224 2227 if (f in copyto and copyto[f] in addedset
2225 2228 and copy[copyto[f]] == f):
2226 2229 continue
2227 2230 yield f1, f2, copyop
2228 2231
2229 2232 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2230 2233 copy, getfilectx, opts, losedatafn, prefix):
2231 2234 '''given input data, generate a diff and yield it in blocks
2232 2235
2233 2236 If generating a diff would lose data like flags or binary data and
2234 2237 losedatafn is not None, it will be called.
2235 2238
2236 2239 prefix is added to every path in the diff output.'''
2237 2240
2238 2241 def gitindex(text):
2239 2242 if not text:
2240 2243 text = ""
2241 2244 l = len(text)
2242 2245 s = util.sha1('blob %d\0' % l)
2243 2246 s.update(text)
2244 2247 return s.hexdigest()
2245 2248
2246 2249 if opts.noprefix:
2247 2250 aprefix = bprefix = ''
2248 2251 else:
2249 2252 aprefix = 'a/'
2250 2253 bprefix = 'b/'
2251 2254
2252 2255 def diffline(f, revs):
2253 2256 revinfo = ' '.join(["-r %s" % rev for rev in revs])
2254 2257 return 'diff %s %s' % (revinfo, f)
2255 2258
2256 2259 date1 = util.datestr(ctx1.date())
2257 2260 date2 = util.datestr(ctx2.date())
2258 2261
2259 2262 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
2260 2263
2261 2264 for f1, f2, copyop in _filepairs(
2262 2265 ctx1, modified, added, removed, copy, opts):
2263 2266 content1 = None
2264 2267 content2 = None
2265 2268 flag1 = None
2266 2269 flag2 = None
2267 2270 if f1:
2268 2271 content1 = getfilectx(f1, ctx1).data()
2269 2272 if opts.git or losedatafn:
2270 2273 flag1 = ctx1.flags(f1)
2271 2274 if f2:
2272 2275 content2 = getfilectx(f2, ctx2).data()
2273 2276 if opts.git or losedatafn:
2274 2277 flag2 = ctx2.flags(f2)
2275 2278 binary = False
2276 2279 if opts.git or losedatafn:
2277 2280 binary = util.binary(content1) or util.binary(content2)
2278 2281
2279 2282 if losedatafn and not opts.git:
2280 2283 if (binary or
2281 2284 # copy/rename
2282 2285 f2 in copy or
2283 2286 # empty file creation
2284 2287 (not f1 and not content2) or
2285 2288 # empty file deletion
2286 2289 (not content1 and not f2) or
2287 2290 # create with flags
2288 2291 (not f1 and flag2) or
2289 2292 # change flags
2290 2293 (f1 and f2 and flag1 != flag2)):
2291 2294 losedatafn(f2 or f1)
2292 2295
2293 2296 path1 = posixpath.join(prefix, f1 or f2)
2294 2297 path2 = posixpath.join(prefix, f2 or f1)
2295 2298 header = []
2296 2299 if opts.git:
2297 2300 header.append('diff --git %s%s %s%s' %
2298 2301 (aprefix, path1, bprefix, path2))
2299 2302 if not f1: # added
2300 2303 header.append('new file mode %s' % gitmode[flag2])
2301 2304 elif not f2: # removed
2302 2305 header.append('deleted file mode %s' % gitmode[flag1])
2303 2306 else: # modified/copied/renamed
2304 2307 mode1, mode2 = gitmode[flag1], gitmode[flag2]
2305 2308 if mode1 != mode2:
2306 2309 header.append('old mode %s' % mode1)
2307 2310 header.append('new mode %s' % mode2)
2308 2311 if copyop is not None:
2309 2312 header.append('%s from %s' % (copyop, path1))
2310 2313 header.append('%s to %s' % (copyop, path2))
2311 2314 elif revs and not repo.ui.quiet:
2312 2315 header.append(diffline(path1, revs))
2313 2316
2314 2317 if binary and opts.git and not opts.nobinary:
2315 2318 text = mdiff.b85diff(content1, content2)
2316 2319 if text:
2317 2320 header.append('index %s..%s' %
2318 2321 (gitindex(content1), gitindex(content2)))
2319 2322 else:
2320 2323 text = mdiff.unidiff(content1, date1,
2321 2324 content2, date2,
2322 2325 path1, path2, opts=opts)
2323 2326 if header and (text or len(header) > 1):
2324 2327 yield '\n'.join(header) + '\n'
2325 2328 if text:
2326 2329 yield text
2327 2330
2328 2331 def diffstatsum(stats):
2329 2332 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
2330 2333 for f, a, r, b in stats:
2331 2334 maxfile = max(maxfile, encoding.colwidth(f))
2332 2335 maxtotal = max(maxtotal, a + r)
2333 2336 addtotal += a
2334 2337 removetotal += r
2335 2338 binary = binary or b
2336 2339
2337 2340 return maxfile, maxtotal, addtotal, removetotal, binary
2338 2341
2339 2342 def diffstatdata(lines):
2340 2343 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
2341 2344
2342 2345 results = []
2343 2346 filename, adds, removes, isbinary = None, 0, 0, False
2344 2347
2345 2348 def addresult():
2346 2349 if filename:
2347 2350 results.append((filename, adds, removes, isbinary))
2348 2351
2349 2352 for line in lines:
2350 2353 if line.startswith('diff'):
2351 2354 addresult()
2352 2355 # set numbers to 0 anyway when starting new file
2353 2356 adds, removes, isbinary = 0, 0, False
2354 2357 if line.startswith('diff --git a/'):
2355 2358 filename = gitre.search(line).group(2)
2356 2359 elif line.startswith('diff -r'):
2357 2360 # format: "diff -r ... -r ... filename"
2358 2361 filename = diffre.search(line).group(1)
2359 2362 elif line.startswith('+') and not line.startswith('+++ '):
2360 2363 adds += 1
2361 2364 elif line.startswith('-') and not line.startswith('--- '):
2362 2365 removes += 1
2363 2366 elif (line.startswith('GIT binary patch') or
2364 2367 line.startswith('Binary file')):
2365 2368 isbinary = True
2366 2369 addresult()
2367 2370 return results
2368 2371
2369 2372 def diffstat(lines, width=80, git=False):
2370 2373 output = []
2371 2374 stats = diffstatdata(lines)
2372 2375 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
2373 2376
2374 2377 countwidth = len(str(maxtotal))
2375 2378 if hasbinary and countwidth < 3:
2376 2379 countwidth = 3
2377 2380 graphwidth = width - countwidth - maxname - 6
2378 2381 if graphwidth < 10:
2379 2382 graphwidth = 10
2380 2383
2381 2384 def scale(i):
2382 2385 if maxtotal <= graphwidth:
2383 2386 return i
2384 2387 # If diffstat runs out of room it doesn't print anything,
2385 2388 # which isn't very useful, so always print at least one + or -
2386 2389 # if there were at least some changes.
2387 2390 return max(i * graphwidth // maxtotal, int(bool(i)))
2388 2391
2389 2392 for filename, adds, removes, isbinary in stats:
2390 2393 if isbinary:
2391 2394 count = 'Bin'
2392 2395 else:
2393 2396 count = adds + removes
2394 2397 pluses = '+' * scale(adds)
2395 2398 minuses = '-' * scale(removes)
2396 2399 output.append(' %s%s | %*s %s%s\n' %
2397 2400 (filename, ' ' * (maxname - encoding.colwidth(filename)),
2398 2401 countwidth, count, pluses, minuses))
2399 2402
2400 2403 if stats:
2401 2404 output.append(_(' %d files changed, %d insertions(+), '
2402 2405 '%d deletions(-)\n')
2403 2406 % (len(stats), totaladds, totalremoves))
2404 2407
2405 2408 return ''.join(output)
2406 2409
2407 2410 def diffstatui(*args, **kw):
2408 2411 '''like diffstat(), but yields 2-tuples of (output, label) for
2409 2412 ui.write()
2410 2413 '''
2411 2414
2412 2415 for line in diffstat(*args, **kw).splitlines():
2413 2416 if line and line[-1] in '+-':
2414 2417 name, graph = line.rsplit(' ', 1)
2415 2418 yield (name + ' ', '')
2416 2419 m = re.search(r'\++', graph)
2417 2420 if m:
2418 2421 yield (m.group(0), 'diffstat.inserted')
2419 2422 m = re.search(r'-+', graph)
2420 2423 if m:
2421 2424 yield (m.group(0), 'diffstat.deleted')
2422 2425 else:
2423 2426 yield (line, '')
2424 2427 yield ('\n', '')
@@ -1,798 +1,825 b''
1 1 $ hg init repo
2 2 $ cd repo
3 3
4 4 New file:
5 5
6 6 $ hg import -d "1000000 0" -mnew - <<EOF
7 7 > diff --git a/new b/new
8 8 > new file mode 100644
9 9 > index 0000000..7898192
10 10 > --- /dev/null
11 11 > +++ b/new
12 12 > @@ -0,0 +1 @@
13 13 > +a
14 14 > EOF
15 15 applying patch from stdin
16 16
17 17 $ hg tip -q
18 18 0:ae3ee40d2079
19 19
20 20 New empty file:
21 21
22 22 $ hg import -d "1000000 0" -mempty - <<EOF
23 23 > diff --git a/empty b/empty
24 24 > new file mode 100644
25 25 > EOF
26 26 applying patch from stdin
27 27
28 28 $ hg tip -q
29 29 1:ab199dc869b5
30 30
31 31 $ hg locate empty
32 32 empty
33 33
34 34 chmod +x:
35 35
36 36 $ hg import -d "1000000 0" -msetx - <<EOF
37 37 > diff --git a/new b/new
38 38 > old mode 100644
39 39 > new mode 100755
40 40 > EOF
41 41 applying patch from stdin
42 42
43 43 #if execbit
44 44 $ hg tip -q
45 45 2:3a34410f282e
46 46 $ test -x new
47 47 $ hg rollback -q
48 48 #else
49 49 $ hg tip -q
50 50 1:ab199dc869b5
51 51 #endif
52 52
53 53 Copy and removing x bit:
54 54
55 55 $ hg import -f -d "1000000 0" -mcopy - <<EOF
56 56 > diff --git a/new b/copy
57 57 > old mode 100755
58 58 > new mode 100644
59 59 > similarity index 100%
60 60 > copy from new
61 61 > copy to copy
62 62 > diff --git a/new b/copyx
63 63 > similarity index 100%
64 64 > copy from new
65 65 > copy to copyx
66 66 > EOF
67 67 applying patch from stdin
68 68
69 69 $ test -f copy
70 70 #if execbit
71 71 $ test ! -x copy
72 72 $ test -x copyx
73 73 $ hg tip -q
74 74 2:21dfaae65c71
75 75 #else
76 76 $ hg tip -q
77 77 2:0efdaa8e3bf3
78 78 #endif
79 79
80 80 $ hg up -qCr1
81 81 $ hg rollback -q
82 82
83 83 Copy (like above but independent of execbit):
84 84
85 85 $ hg import -d "1000000 0" -mcopy - <<EOF
86 86 > diff --git a/new b/copy
87 87 > similarity index 100%
88 88 > copy from new
89 89 > copy to copy
90 90 > diff --git a/new b/copyx
91 91 > similarity index 100%
92 92 > copy from new
93 93 > copy to copyx
94 94 > EOF
95 95 applying patch from stdin
96 96
97 97 $ hg tip -q
98 98 2:0efdaa8e3bf3
99 99 $ test -f copy
100 100
101 101 $ cat copy
102 102 a
103 103
104 104 $ hg cat copy
105 105 a
106 106
107 107 Rename:
108 108
109 109 $ hg import -d "1000000 0" -mrename - <<EOF
110 110 > diff --git a/copy b/rename
111 111 > similarity index 100%
112 112 > rename from copy
113 113 > rename to rename
114 114 > EOF
115 115 applying patch from stdin
116 116
117 117 $ hg tip -q
118 118 3:b1f57753fad2
119 119
120 120 $ hg locate
121 121 copyx
122 122 empty
123 123 new
124 124 rename
125 125
126 126 Delete:
127 127
128 128 $ hg import -d "1000000 0" -mdelete - <<EOF
129 129 > diff --git a/copyx b/copyx
130 130 > deleted file mode 100755
131 131 > index 7898192..0000000
132 132 > --- a/copyx
133 133 > +++ /dev/null
134 134 > @@ -1 +0,0 @@
135 135 > -a
136 136 > EOF
137 137 applying patch from stdin
138 138
139 139 $ hg tip -q
140 140 4:1bd1da94b9b2
141 141
142 142 $ hg locate
143 143 empty
144 144 new
145 145 rename
146 146
147 147 $ test -f copyx
148 148 [1]
149 149
150 150 Regular diff:
151 151
152 152 $ hg import -d "1000000 0" -mregular - <<EOF
153 153 > diff --git a/rename b/rename
154 154 > index 7898192..72e1fe3 100644
155 155 > --- a/rename
156 156 > +++ b/rename
157 157 > @@ -1 +1,5 @@
158 158 > a
159 159 > +a
160 160 > +a
161 161 > +a
162 162 > +a
163 163 > EOF
164 164 applying patch from stdin
165 165
166 166 $ hg tip -q
167 167 5:46fe99cb3035
168 168
169 169 Copy and modify:
170 170
171 171 $ hg import -d "1000000 0" -mcopymod - <<EOF
172 172 > diff --git a/rename b/copy2
173 173 > similarity index 80%
174 174 > copy from rename
175 175 > copy to copy2
176 176 > index 72e1fe3..b53c148 100644
177 177 > --- a/rename
178 178 > +++ b/copy2
179 179 > @@ -1,5 +1,5 @@
180 180 > a
181 181 > a
182 182 > -a
183 183 > +b
184 184 > a
185 185 > a
186 186 > EOF
187 187 applying patch from stdin
188 188
189 189 $ hg tip -q
190 190 6:ffeb3197c12d
191 191
192 192 $ hg cat copy2
193 193 a
194 194 a
195 195 b
196 196 a
197 197 a
198 198
199 199 Rename and modify:
200 200
201 201 $ hg import -d "1000000 0" -mrenamemod - <<EOF
202 202 > diff --git a/copy2 b/rename2
203 203 > similarity index 80%
204 204 > rename from copy2
205 205 > rename to rename2
206 206 > index b53c148..8f81e29 100644
207 207 > --- a/copy2
208 208 > +++ b/rename2
209 209 > @@ -1,5 +1,5 @@
210 210 > a
211 211 > a
212 212 > b
213 213 > -a
214 214 > +c
215 215 > a
216 216 > EOF
217 217 applying patch from stdin
218 218
219 219 $ hg tip -q
220 220 7:401aede9e6bb
221 221
222 222 $ hg locate copy2
223 223 [1]
224 224 $ hg cat rename2
225 225 a
226 226 a
227 227 b
228 228 c
229 229 a
230 230
231 231 One file renamed multiple times:
232 232
233 233 $ hg import -d "1000000 0" -mmultirenames - <<EOF
234 234 > diff --git a/rename2 b/rename3
235 235 > rename from rename2
236 236 > rename to rename3
237 237 > diff --git a/rename2 b/rename3-2
238 238 > rename from rename2
239 239 > rename to rename3-2
240 240 > EOF
241 241 applying patch from stdin
242 242
243 243 $ hg tip -q
244 244 8:2ef727e684e8
245 245
246 246 $ hg log -vr. --template '{rev} {files} / {file_copies}\n'
247 247 8 rename2 rename3 rename3-2 / rename3 (rename2)rename3-2 (rename2)
248 248
249 249 $ hg locate rename2 rename3 rename3-2
250 250 rename3
251 251 rename3-2
252 252
253 253 $ hg cat rename3
254 254 a
255 255 a
256 256 b
257 257 c
258 258 a
259 259
260 260 $ hg cat rename3-2
261 261 a
262 262 a
263 263 b
264 264 c
265 265 a
266 266
267 267 $ echo foo > foo
268 268 $ hg add foo
269 269 $ hg ci -m 'add foo'
270 270
271 271 Binary files and regular patch hunks:
272 272
273 273 $ hg import -d "1000000 0" -m binaryregular - <<EOF
274 274 > diff --git a/binary b/binary
275 275 > new file mode 100644
276 276 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
277 277 > GIT binary patch
278 278 > literal 4
279 279 > Lc\${NkU|;|M00aO5
280 280 >
281 281 > diff --git a/foo b/foo2
282 282 > rename from foo
283 283 > rename to foo2
284 284 > EOF
285 285 applying patch from stdin
286 286
287 287 $ hg tip -q
288 288 10:27377172366e
289 289
290 290 $ cat foo2
291 291 foo
292 292
293 293 $ hg manifest --debug | grep binary
294 294 045c85ba38952325e126c70962cc0f9d9077bc67 644 binary
295 295
296 296 Multiple binary files:
297 297
298 298 $ hg import -d "1000000 0" -m multibinary - <<EOF
299 299 > diff --git a/mbinary1 b/mbinary1
300 300 > new file mode 100644
301 301 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
302 302 > GIT binary patch
303 303 > literal 4
304 304 > Lc\${NkU|;|M00aO5
305 305 >
306 306 > diff --git a/mbinary2 b/mbinary2
307 307 > new file mode 100644
308 308 > index 0000000000000000000000000000000000000000..112363ac1917b417ffbd7f376ca786a1e5fa7490
309 309 > GIT binary patch
310 310 > literal 5
311 311 > Mc\${NkU|\`?^000jF3jhEB
312 312 >
313 313 > EOF
314 314 applying patch from stdin
315 315
316 316 $ hg tip -q
317 317 11:18b73a84b4ab
318 318
319 319 $ hg manifest --debug | grep mbinary
320 320 045c85ba38952325e126c70962cc0f9d9077bc67 644 mbinary1
321 321 a874b471193996e7cb034bb301cac7bdaf3e3f46 644 mbinary2
322 322
323 323 Binary file and delta hunk (we build the patch using this sed hack to
324 324 avoid an unquoted ^, which check-code says breaks sh on Solaris):
325 325
326 326 $ sed 's/ caret /^/g;s/ dollarparen /$(/g' > quote-hack.patch <<'EOF'
327 327 > diff --git a/delta b/delta
328 328 > new file mode 100644
329 329 > index 0000000000000000000000000000000000000000..8c9b7831b231c2600843e303e66b521353a200b3
330 330 > GIT binary patch
331 331 > literal 3749
332 332 > zcmV;W4qEYvP)<h;3K|Lk000e1NJLTq006iE002D*0ssI2kt{U(0000PbVXQnQ*UN;
333 333 > zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU=M@d9MRCwC#oC!>o#}>x{(W-y~UN*tK
334 334 > z%A%sxiUy2Ys)0Vm#ueArYKoYqX;GuiqZpgirM6nCVoYk?YNAz3G~z;BZ~@~&OQEe4
335 335 > zmGvS5isFJI;Pd_7J+EKxyHZeu` caret t4r2>F;h-+VK3{_{WoGv8dSpFDYDrA%3UX03pt
336 336 > zOaVoi0*W#P6lDr1$`nwPDWE7*rhuYM0Y#YtiZTThWeO<D6i}2YpqR<%$s>bRRaI42
337 337 > zS3iFIxJ8Q=EnBv1Z7?pBw_bLjJb3V+tgP(Tty_2R-mR#p04x78n2n7MSOFyt4i1iv
338 338 > zjxH`PPEJmgD7U?IK&h;(EGQ@_DJc<@01=4fiNXHcKZ8LhZQ8T}E3U4tUS3}OrcgQW
339 339 > zWdX{K8#l7Ev&#$ysR)G#0*rC+<WGZ3?CtG4bm-ve>Dj$|_qJ`@D*stNP_AFUe&x!Q
340 340 > zJ9q9B7Z=ym)MyZ?Tg1ROunUYr81nV?B@!tYS~5_|%gfW#(_s<4UN1!Q?Dv8d>g#m6
341 341 > z%*@R2@bI2JdnzxQ!EDU`$eQY!tgI~Zn$prz;gaXNod5*5p(1Bz=P$qfvZ$y?dC@X~
342 342 > zlAD+NAKhB{=;6bMwzjqn>9mavvKOGd`s%A+fBiL>Q;xJWpa72C+}u{JTHUX>{~}Qj
343 343 > zUb%hyHgN~c?cBLjInvUALMD9g-aXt54ZL8AOCvXL-V6!~ijR*kEG$&Mv?!pE61OlI
344 344 > z8nzMSPE8F7bH|Py*RNl1VUCggq<V)>@_6gkEeiz7{rmTeuNTW6+KVS#0FG%IHf-3L
345 345 > zGiS21vn>WCCr+GLx caret !uNetzB6u3o(w6&1C2?_LW8ij$+$sZ*zZ`|US3H@8N~%&V%Z
346 346 > zAeA0HdhFS=$6|nzn3%YH`SN<>DQRO;Qc caret )dfdvA caret 5u`Xf;Zzu<ZQHgG?28V-#s<;T
347 347 > zzkh#LA)v7gpoE5ou3o*GoUUF%b#iht&kl9d0)><$FE1}ACr68;uCA`6DrGmz_U+rp
348 348 > zL>Rx;X_yhk$fP_yJrTCQ|NgsW0A<985g&c@k-NKly<>mgU8n||ZPPV<`SN8#%$+-T
349 349 > zfP$T!ou8jypFVwnzqhxyUvIxXd-wF~*U!ht=hCH1wzjqn9x#)IrhDa;S0JbK caret z_$W
350 350 > zd(8rX@;7|t*;GJ5h$SZ{v(}+UBEs$4w~?{@9%`_Z<P<kox5bMWuUWH(sF9hONgd$Q
351 351 > zunCgwT@1|CU9+;X caret 4z&|M~@yw23Ay50NFWn=FqF%yLZEUty;AT2??1oV@B)Nt))J7
352 352 > zh>{5j2@f7T=-an%L_`E)h;mZ4D_5>?7tjQtVPRo2XU-&;mX(!l-MSTJP4XWY82JAC
353 353 > z@57+y&!1=P{Mn{W8)-HzEsgAtd63}Cazc>O6vGb>51%@9DzbyI3?4j~$ijmT95_IS
354 354 > zS#r!LCDW%*4-O7CGnkr$xXR1RQ&UrA<CQt} caret 73NL%zk`)Jk!yxUAt-1r}ggLn-Zq}
355 355 > z*s){8pw68;i+kiG%CpBKYSJLLFyq&*U8}qDp+kpe&6<Vp(Z58%l#~>ZK?&s7y?b}i
356 356 > zuwcOgO%x-27A;y785zknl_{sU;E6v$8{pWmVS{KaJPpu`i;HP$#flY@u~Ua~K3%tN
357 357 > z-LhrNh{9SoHgDd%WXTc$$~Dq{?AWou3!H&?V8K{ caret {P9Ot5vecD?%1&-E-ntBFj87(
358 358 > zy5`QE%QRX7qcHC%1{Ua}M~}L6=`wQUNEQ=I;qc+ZMMXtK2T+0os;jEco;}OV9z1w3
359 359 > zARqv caret bm-85xnRCng3OT|MyVSmR3ND7 caret ?KaQGG! caret (aTbo1N;Nz;X3Q9FJbwK6`0?Yp
360 360 > zj*X2ac;Pw3!I2|JShDaF>-gJmzm1NLj){rk&o|$E caret WAsfrK=x&@B!`w7Hik81sPz4
361 361 > zuJTaiCppM>-+c!wPzcUw)5@?J4U-u|pJ~xbWUe-C+60k caret 7>9!)56DbjmA~`OJJ40v
362 362 > zu3hCA7eJXZWeN|1iJLu87$;+fS8+Kq6O`aT)*_x@sY#t7LxwoEcVw*)cWhhQW@l%!
363 363 > z{#Z=y+qcK@%z{p*D=8_Fcg278AnH3fI5;~yGu?9TscxXaaP*4$f<LIv! caret 5Lfr%vKg
364 364 > zpxmunH#%=+ICMvZA~wyNH%~eMl!-g caret R!cYJ#WmLq5N8viz#J%%LPtkO?V)tZ81cp>
365 365 > z{ALK?fNPePmd;289&M8Q3>YwgZX5GcGY&n>K1<x)!`;Qjg&}bb!Lrnl@xH#kS~VYE
366 366 > zpJmIJO`A3iy+Y3X`k>cY-@}Iw2Onq`=!ba3eATgs3yg3Wej=+P-Z8WF#w=RXvS@J3
367 367 > zEyhVTj-gO?kfDu1g9afo<RkPrYzG#_yF41IFxF%Ylg>9lx6<clPweR-b7Hn+r)e1l
368 368 > zO6c6FbNt@;;*w$z;N|H>h{czme)_4V6UC4hv**kX2@L caret Bgds dollarparen &P7M4dhfmWe)!=B
369 369 > zR3X=Y{P9N}p@-##@1ZNW1YbVaiP~D@8m&<dzEP&cO|87Ju#j*=;wH~Exr>i*Hpp&@
370 370 > z`9!Sj+O;byD~s8qZ>6QB8uv7Bpn&&?xe;;e<M4F8KEID&pT7QmqoSgq&06adp5T=U
371 371 > z6DH*4=AB7C1D9Amu?ia-wtxSAlmTEO96XHx)-+rKP;ip$pukuSJGW3P1aUmc2yo%)
372 372 > z&<t3F>d1X+1qzaag-%x+eKHx{?Afz3GBQSw9u0lw<mB+I#v11TKRpKWQS+lvVL7=u
373 373 > zHr6)1ynEF<i3kO6A8&ppPMo-F=PnWfXkSj@i*7J6C<F}wR?s(O0niC?t+6;+k}pPq
374 374 > zrok&TPU40rL0ZYDwenNrrmPZ`gjo@DEF`7 caret cKP||pUr;+r)hyn9O37=xA`3%Bj-ih
375 375 > z+1usk<%5G-y+R?tA`qY=)6&vNjL{P?QzHg%P%>`ZxP=QB%DHY6L26?36V_p caret {}n$q
376 376 > z3@9W=KmGI*Ng_Q#AzA%-z|Z caret |#oW(hkfgpuS$RKRhlrarX%efMMCs}GLChec5+y{6
377 377 > z1Qnxim_C-fmQuaAK_NUHUBV&;1c0V)wji<RcdZ*aAWTwyt>hVnlt caret asFCe0&a@tqp
378 378 > zEEy;$L}D$X6)wfQNl8gu6Z>oB3_RrP=gTyK2@@w#LbQfLNHj>Q&z(C5wUFhK+}0aV
379 379 > zSohlc=7K+spN<ctf}5KgKqNyJDNP9;LZd)nTE=9|6Xdr9%Hzk63-tL2c9FD*rsyYY
380 380 > z!}t+Yljq7-p$X;4_YL?6d;mdY3R##o1e%rlPxrsMh8|;sKTr~ caret QD#sw3&vS$FwlTk
381 381 > zp1#Gw!Qo-$LtvpXt#ApV0g) caret F=qFB`VB!W297x=$mr<$>rco3v$QKih_xN!k6;M=@
382 382 > zCr?gDNQj7tm@;JwD;Ty&NlBSCYZk(b3dZeN8D4h2{r20dSFc7;(>E&r`s=TVtzpB4
383 383 > zk+ caret N&zCAiRns(?p6iBlk9v&h{1ve(FNtc)td51M>)TkXhc6{>5C)`fS$&)A1*CP1%
384 384 > zld+peue4aYbg3C0!+4mu+}vE caret j_feX+ZijvffBI7Ofh#RZ*U3<3J5(+nfRCzexqQ5
385 385 > zgM&##Y4Dd{e%ZKjqrbm@|Ni}l4jo!AqtFynj3Xsd$o caret ?yV4$|UQ(j&UWCH>M=o_&N
386 386 > zmclXc3i|Q#<;#EoG>~V}4unTHbUK}u=y4;rA3S&vzC3 caret aJP!&D4RvvGfoyo(>C>la
387 387 > zijP<=v>X{3Ne&2BXo}DV8l0V-jdv`$am0ubG{Wuh%CTd|l9Q7m;G&|U@#Dvbhlj(d
388 388 > zg6W{3ATxYt#T?)3;SmIgOP4M|Dki~I_TX7SxP0x}wI~DQI7Lhm2BI7gph(aPIFAd;
389 389 > zQ&UsF`Q{rOz+z=87c5v%@5u~d6dWV5OlX`oH3cAH&UlvsZUEo(Q(P|lKs17rXvaiU
390 390 > zQcj}IEufi1+Bnh6&(EhF{7O3vLHp`jjlp0J<M1kh$+$2xGm~Zk7OY7(q=&Rdhq*RG
391 391 > zwrmcd5MnP}xByB_)P@{J>DR9x6;`cUwPM8z){yooNiXPOc9_{W-gtwxE5TUg0vJk6
392 392 > zO#JGruV&1cL6VGK2?+_YQr4`+EY8;Sm$9U$uuGRN=uj3k7?O9b+R~J7t_y*K64ZnI
393 393 > zM+{aE<b(v?vSmw;9zFP!aE266zHIhlmdI@ caret xa6o2jwdRk54a$>pcRbC29ZyG!Cfdp
394 394 > zutFf`Q`vljgo!(wHf=)F#m2_MIuj;L(2ja2YsQRX+rswV{d<H`Ar;(@%aNa9VPU8Z
395 395 > z;tq*`y}dm#NDJHKlV}uTIm!_vAq5E7!X-p{P=Z=Sh668>PuVS1*6e}OwOiMc;u3OQ
396 396 > z@Bs)w3=lzfKoufH$SFuPG@uZ4NOnM#+=8LnQ2Q4zUd+nM+OT26;lqbN{P07dhH{jH
397 397 > zManE8 caret dLms-Q2;1kB<*Q1a3f8kZr;xX=!Qro@`~@xN*Qj>gx;i;0Z24!~i2uLb`}v
398 398 > zA?R$|wvC+m caret Ups=*(4lDh*=UN8{5h(A?p#D caret 2N$8u4Z55!q?ZAh(iEEng9_Zi>IgO
399 399 > z#~**JC8hE4@n{hO&8btT5F*?nC_%LhA3i)PDhh-pB_&1wGrDIl caret *=8x3n&;akBf caret -
400 400 > zJd&86kq$%%907v caret tgWoQdwI`|oNK%VvU~S#C<o caret F?6c48?Cjj#-4P<>HFD%&|Ni~t
401 401 > zKJ(|#H`$<5W+6ZkBb213rXonKZLB+X> caret L}J@W6osP3piLD_5?R!`S}*{xLBzFiL4@
402 402 > zX+}l{`A%?f@T5tT%ztu60p;)be`fWC`tP@WpO=?cpf8Xuf1OSj6d3f@Ki(ovDYq%0
403 403 > z{4ZSe`kOay5@=lAT!}vFzxyemC{sXDrhuYM0Y#ZI1r%ipD9W11{w=@&xgJ}t2x;ep
404 404 > P00000NkvXXu0mjfZ5|Er
405 405 >
406 406 > literal 0
407 407 > HcmV?d00001
408 408 >
409 409 > EOF
410 410 $ hg import -d "1000000 0" -m delta quote-hack.patch
411 411 applying quote-hack.patch
412 412 $ rm quote-hack.patch
413 413
414 414 $ hg manifest --debug | grep delta
415 415 9600f98bb60ce732634d126aaa4ac1ec959c573e 644 delta
416 416
417 417 $ hg import -d "1000000 0" -m delta - <<'EOF'
418 418 > diff --git a/delta b/delta
419 419 > index 8c9b7831b231c2600843e303e66b521353a200b3..0021dd95bc0dba53c39ce81377126d43731d68df 100644
420 420 > GIT binary patch
421 421 > delta 49
422 422 > zcmZ1~yHs|=21Z8J$r~9bFdA-lVv=EEw4WT$qRf2QSa5SIOAHI6(&k4T8H|kLo4vWB
423 423 > FSO9ZT4bA`n
424 424 >
425 425 > delta 49
426 426 > zcmV-10M7rV9i<(xumJ(}ld%Di0Xefm0vrMXpOaq%BLm9I%d>?9Tm%6Vv*HM70RcC&
427 427 > HOA1;9yU-AD
428 428 >
429 429 > EOF
430 430 applying patch from stdin
431 431
432 432 $ hg manifest --debug | grep delta
433 433 56094bbea136dcf8dbd4088f6af469bde1a98b75 644 delta
434 434
435 435 Filenames with spaces:
436 436
437 437 $ sed 's,EOL$,,g' <<EOF | hg import -d "1000000 0" -m spaces -
438 438 > diff --git a/foo bar b/foo bar
439 439 > new file mode 100644
440 440 > index 0000000..257cc56
441 441 > --- /dev/null
442 442 > +++ b/foo bar EOL
443 443 > @@ -0,0 +1 @@
444 444 > +foo
445 445 > EOF
446 446 applying patch from stdin
447 447
448 448 $ hg tip -q
449 449 14:4b79479c9a6d
450 450
451 451 $ cat "foo bar"
452 452 foo
453 453
454 454 Copy then modify the original file:
455 455
456 456 $ hg import -d "1000000 0" -m copy-mod-orig - <<EOF
457 457 > diff --git a/foo2 b/foo2
458 458 > index 257cc56..fe08ec6 100644
459 459 > --- a/foo2
460 460 > +++ b/foo2
461 461 > @@ -1 +1,2 @@
462 462 > foo
463 463 > +new line
464 464 > diff --git a/foo2 b/foo3
465 465 > similarity index 100%
466 466 > copy from foo2
467 467 > copy to foo3
468 468 > EOF
469 469 applying patch from stdin
470 470
471 471 $ hg tip -q
472 472 15:9cbe44af4ae9
473 473
474 474 $ cat foo3
475 475 foo
476 476
477 477 Move text file and patch as binary
478 478
479 479 $ echo a > text2
480 480 $ hg ci -Am0
481 481 adding text2
482 482 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
483 483 > diff --git a/text2 b/binary2
484 484 > rename from text2
485 485 > rename to binary2
486 486 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
487 487 > GIT binary patch
488 488 > literal 5
489 489 > Mc$`b*O5$Pw00T?_*Z=?k
490 490 >
491 491 > EOF
492 492 applying patch from stdin
493 493
494 494 $ cat binary2
495 495 a
496 496 b
497 497 \x00 (no-eol) (esc)
498 498
499 499 $ hg st --copies --change .
500 500 A binary2
501 501 text2
502 502 R text2
503 503
504 504 Invalid base85 content
505 505
506 506 $ hg rollback
507 507 repository tip rolled back to revision 16 (undo import)
508 508 working directory now based on revision 16
509 509 $ hg revert -aq
510 510 $ hg import -d "1000000 0" -m invalid-binary - <<"EOF"
511 511 > diff --git a/text2 b/binary2
512 512 > rename from text2
513 513 > rename to binary2
514 514 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
515 515 > GIT binary patch
516 516 > literal 5
517 517 > Mc$`b*O.$Pw00T?_*Z=?k
518 518 >
519 519 > EOF
520 520 applying patch from stdin
521 521 abort: could not decode "binary2" binary patch: bad base85 character at position 6
522 522 [255]
523 523
524 524 $ hg revert -aq
525 525 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
526 526 > diff --git a/text2 b/binary2
527 527 > rename from text2
528 528 > rename to binary2
529 529 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
530 530 > GIT binary patch
531 531 > literal 6
532 532 > Mc$`b*O5$Pw00T?_*Z=?k
533 533 >
534 534 > EOF
535 535 applying patch from stdin
536 536 abort: "binary2" length is 5 bytes, should be 6
537 537 [255]
538 538
539 539 $ hg revert -aq
540 540 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
541 541 > diff --git a/text2 b/binary2
542 542 > rename from text2
543 543 > rename to binary2
544 544 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
545 545 > GIT binary patch
546 546 > Mc$`b*O5$Pw00T?_*Z=?k
547 547 >
548 548 > EOF
549 549 applying patch from stdin
550 550 abort: could not extract "binary2" binary data
551 551 [255]
552 552
553 553 Simulate a copy/paste turning LF into CRLF (issue2870)
554 554
555 555 $ hg revert -aq
556 556 $ cat > binary.diff <<"EOF"
557 557 > diff --git a/text2 b/binary2
558 558 > rename from text2
559 559 > rename to binary2
560 560 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
561 561 > GIT binary patch
562 562 > literal 5
563 563 > Mc$`b*O5$Pw00T?_*Z=?k
564 564 >
565 565 > EOF
566 566 >>> fp = file('binary.diff', 'rb')
567 567 >>> data = fp.read()
568 568 >>> fp.close()
569 569 >>> file('binary.diff', 'wb').write(data.replace('\n', '\r\n'))
570 570 $ rm binary2
571 571 $ hg import --no-commit binary.diff
572 572 applying binary.diff
573 573
574 574 $ cd ..
575 575
576 576 Consecutive import with renames (issue2459)
577 577
578 578 $ hg init issue2459
579 579 $ cd issue2459
580 580 $ hg import --no-commit --force - <<EOF
581 581 > diff --git a/a b/a
582 582 > new file mode 100644
583 583 > EOF
584 584 applying patch from stdin
585 585 $ hg import --no-commit --force - <<EOF
586 586 > diff --git a/a b/b
587 587 > rename from a
588 588 > rename to b
589 589 > EOF
590 590 applying patch from stdin
591 591 a has not been committed yet, so no copy data will be stored for b.
592 592 $ hg debugstate
593 593 a 0 -1 unset b
594 594 $ hg ci -m done
595 595 $ cd ..
596 596
597 597 Renames and strip
598 598
599 599 $ hg init renameandstrip
600 600 $ cd renameandstrip
601 601 $ echo a > a
602 602 $ hg ci -Am adda
603 603 adding a
604 604 $ hg import --no-commit -p2 - <<EOF
605 605 > diff --git a/foo/a b/foo/b
606 606 > rename from foo/a
607 607 > rename to foo/b
608 608 > EOF
609 609 applying patch from stdin
610 610 $ hg st --copies
611 611 A b
612 612 a
613 613 R a
614 614
615 615 Prefix with strip, renames, creates etc
616 616
617 617 $ hg revert -aC
618 618 undeleting a
619 619 forgetting b
620 620 $ rm b
621 621 $ mkdir -p dir/dir2
622 622 $ echo b > dir/dir2/b
623 623 $ echo c > dir/dir2/c
624 624 $ echo d > dir/d
625 625 $ hg ci -Am addbcd
626 626 adding dir/d
627 627 adding dir/dir2/b
628 628 adding dir/dir2/c
629
630 prefix '.' is the same as no prefix
631 $ hg import --no-commit --prefix . - <<EOF
632 > diff --git a/dir/a b/dir/a
633 > --- /dev/null
634 > +++ b/dir/a
635 > @@ -0,0 +1 @@
636 > +aaaa
637 > diff --git a/dir/d b/dir/d
638 > --- a/dir/d
639 > +++ b/dir/d
640 > @@ -1,1 +1,2 @@
641 > d
642 > +dddd
643 > EOF
644 applying patch from stdin
645 $ cat dir/a
646 aaaa
647 $ cat dir/d
648 d
649 dddd
650 $ hg revert -aC
651 forgetting dir/a (glob)
652 reverting dir/d (glob)
653 $ rm dir/a
654
655 prefix with default strip
629 656 $ hg import --no-commit --prefix dir/ - <<EOF
630 657 > diff --git a/a b/a
631 658 > --- /dev/null
632 659 > +++ b/a
633 660 > @@ -0,0 +1 @@
634 661 > +aaa
635 662 > diff --git a/d b/d
636 663 > --- a/d
637 664 > +++ b/d
638 665 > @@ -1,1 +1,2 @@
639 666 > d
640 667 > +dd
641 668 > EOF
642 669 applying patch from stdin
643 670 $ cat dir/a
644 671 aaa
645 672 $ cat dir/d
646 673 d
647 674 dd
648 675 $ hg revert -aC
649 676 forgetting dir/a (glob)
650 677 reverting dir/d (glob)
651 678 $ rm dir/a
652 (test that prefixes are relative to the root)
679 (test that prefixes are relative to the cwd)
653 680 $ mkdir tmpdir
654 681 $ cd tmpdir
655 $ hg import --no-commit -p2 --prefix dir/ - <<EOF
682 $ hg import --no-commit -p2 --prefix ../dir/ - <<EOF
656 683 > diff --git a/foo/a b/foo/a
657 684 > new file mode 100644
658 685 > --- /dev/null
659 686 > +++ b/foo/a
660 687 > @@ -0,0 +1 @@
661 688 > +a
662 689 > diff --git a/foo/dir2/b b/foo/dir2/b2
663 690 > rename from foo/dir2/b
664 691 > rename to foo/dir2/b2
665 692 > diff --git a/foo/dir2/c b/foo/dir2/c
666 693 > --- a/foo/dir2/c
667 694 > +++ b/foo/dir2/c
668 695 > @@ -0,0 +1 @@
669 696 > +cc
670 697 > diff --git a/foo/d b/foo/d
671 698 > deleted file mode 100644
672 699 > --- a/foo/d
673 700 > +++ /dev/null
674 701 > @@ -1,1 +0,0 @@
675 702 > -d
676 703 > EOF
677 704 applying patch from stdin
678 705 $ hg st --copies
679 706 M dir/dir2/c
680 707 A dir/a
681 708 A dir/dir2/b2
682 709 dir/dir2/b
683 710 R dir/d
684 711 R dir/dir2/b
685 712 $ cd ..
686 713
687 714 Renames, similarity and git diff
688 715
689 716 $ hg revert -aC
690 717 forgetting dir/a (glob)
691 718 undeleting dir/d (glob)
692 719 undeleting dir/dir2/b (glob)
693 720 forgetting dir/dir2/b2 (glob)
694 721 reverting dir/dir2/c (glob)
695 722 $ rm dir/a dir/dir2/b2
696 723 $ hg import --similarity 90 --no-commit - <<EOF
697 724 > diff --git a/a b/b
698 725 > rename from a
699 726 > rename to b
700 727 > EOF
701 728 applying patch from stdin
702 729 $ hg st --copies
703 730 A b
704 731 a
705 732 R a
706 733 $ cd ..
707 734
708 735 Pure copy with existing destination
709 736
710 737 $ hg init copytoexisting
711 738 $ cd copytoexisting
712 739 $ echo a > a
713 740 $ echo b > b
714 741 $ hg ci -Am add
715 742 adding a
716 743 adding b
717 744 $ hg import --no-commit - <<EOF
718 745 > diff --git a/a b/b
719 746 > copy from a
720 747 > copy to b
721 748 > EOF
722 749 applying patch from stdin
723 750 abort: cannot create b: destination already exists
724 751 [255]
725 752 $ cat b
726 753 b
727 754
728 755 Copy and changes with existing destination
729 756
730 757 $ hg import --no-commit - <<EOF
731 758 > diff --git a/a b/b
732 759 > copy from a
733 760 > copy to b
734 761 > --- a/a
735 762 > +++ b/b
736 763 > @@ -1,1 +1,2 @@
737 764 > a
738 765 > +b
739 766 > EOF
740 767 applying patch from stdin
741 768 cannot create b: destination already exists
742 769 1 out of 1 hunks FAILED -- saving rejects to file b.rej
743 770 abort: patch failed to apply
744 771 [255]
745 772 $ cat b
746 773 b
747 774
748 775 #if symlink
749 776
750 777 $ ln -s b linkb
751 778 $ hg add linkb
752 779 $ hg ci -m addlinkb
753 780 $ hg import --no-commit - <<EOF
754 781 > diff --git a/linkb b/linkb
755 782 > deleted file mode 120000
756 783 > --- a/linkb
757 784 > +++ /dev/null
758 785 > @@ -1,1 +0,0 @@
759 786 > -badhunk
760 787 > \ No newline at end of file
761 788 > EOF
762 789 applying patch from stdin
763 790 patching file linkb
764 791 Hunk #1 FAILED at 0
765 792 1 out of 1 hunks FAILED -- saving rejects to file linkb.rej
766 793 abort: patch failed to apply
767 794 [255]
768 795 $ hg st
769 796 ? b.rej
770 797 ? linkb.rej
771 798
772 799 #endif
773 800
774 801 Test corner case involving copies and multiple hunks (issue3384)
775 802
776 803 $ hg revert -qa
777 804 $ hg import --no-commit - <<EOF
778 805 > diff --git a/a b/c
779 806 > copy from a
780 807 > copy to c
781 808 > --- a/a
782 809 > +++ b/c
783 810 > @@ -1,1 +1,2 @@
784 811 > a
785 812 > +a
786 813 > @@ -2,1 +2,2 @@
787 814 > a
788 815 > +a
789 816 > diff --git a/a b/a
790 817 > --- a/a
791 818 > +++ b/a
792 819 > @@ -1,1 +1,2 @@
793 820 > a
794 821 > +b
795 822 > EOF
796 823 applying patch from stdin
797 824
798 825 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now