##// END OF EJS Templates
diff: recurse into subrepositories with --subrepos/-S flag
Martin Geisler -
r12167:d2c5b092 default
parent child Browse files
Show More
@@ -1,575 +1,575 b''
1 1 # keyword.py - $Keyword$ expansion for Mercurial
2 2 #
3 3 # Copyright 2007-2010 Christian Ebert <blacktrash@gmx.net>
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 # $Id$
9 9 #
10 10 # Keyword expansion hack against the grain of a DSCM
11 11 #
12 12 # There are many good reasons why this is not needed in a distributed
13 13 # SCM, still it may be useful in very small projects based on single
14 14 # files (like LaTeX packages), that are mostly addressed to an
15 15 # audience not running a version control system.
16 16 #
17 17 # For in-depth discussion refer to
18 18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
19 19 #
20 20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 21 #
22 22 # Binary files are not touched.
23 23 #
24 24 # Files to act upon/ignore are specified in the [keyword] section.
25 25 # Customized keyword template mappings in the [keywordmaps] section.
26 26 #
27 27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
28 28
29 29 '''expand keywords in tracked files
30 30
31 31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
32 32 tracked text files selected by your configuration.
33 33
34 34 Keywords are only expanded in local repositories and not stored in the
35 35 change history. The mechanism can be regarded as a convenience for the
36 36 current user or for archive distribution.
37 37
38 38 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
39 39 sections of hgrc files.
40 40
41 41 Example::
42 42
43 43 [keyword]
44 44 # expand keywords in every python file except those matching "x*"
45 45 **.py =
46 46 x* = ignore
47 47
48 48 [keywordset]
49 49 # prefer svn- over cvs-like default keywordmaps
50 50 svn = True
51 51
52 52 NOTE: the more specific you are in your filename patterns the less you
53 53 lose speed in huge repositories.
54 54
55 55 For [keywordmaps] template mapping and expansion demonstration and
56 56 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
57 57 available templates and filters.
58 58
59 59 Three additional date template filters are provided::
60 60
61 61 utcdate "2006/09/18 15:13:13"
62 62 svnutcdate "2006-09-18 15:13:13Z"
63 63 svnisodate "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
64 64
65 65 The default template mappings (view with :hg:`kwdemo -d`) can be
66 66 replaced with customized keywords and templates. Again, run
67 67 :hg:`kwdemo` to control the results of your config changes.
68 68
69 69 Before changing/disabling active keywords, run :hg:`kwshrink` to avoid
70 70 the risk of inadvertently storing expanded keywords in the change
71 71 history.
72 72
73 73 To force expansion after enabling it, or a configuration change, run
74 74 :hg:`kwexpand`.
75 75
76 76 Expansions spanning more than one line and incremental expansions,
77 77 like CVS' $Log$, are not supported. A keyword template map "Log =
78 78 {desc}" expands to the first line of the changeset description.
79 79 '''
80 80
81 81 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
82 82 from mercurial import patch, localrepo, templater, templatefilters, util, match
83 83 from mercurial.hgweb import webcommands
84 84 from mercurial.i18n import _
85 85 import re, shutil, tempfile
86 86
87 87 commands.optionalrepo += ' kwdemo'
88 88
89 89 # hg commands that do not act on keywords
90 90 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
91 91 ' log outgoing push rename rollback tip verify'
92 92 ' convert email glog')
93 93
94 94 # hg commands that trigger expansion only when writing to working dir,
95 95 # not when reading filelog, and unexpand when reading from working dir
96 96 restricted = 'merge record qrecord resolve transplant'
97 97
98 98 # commands using dorecord
99 99 recordcommands = 'record qrecord'
100 100 # names of extensions using dorecord
101 101 recordextensions = 'record'
102 102
103 103 # date like in cvs' $Date
104 104 utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S')
105 105 # date like in svn's $Date
106 106 svnisodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
107 107 # date like in svn's $Id
108 108 svnutcdate = lambda x: util.datestr((x[0], 0), '%Y-%m-%d %H:%M:%SZ')
109 109
110 110 # make keyword tools accessible
111 111 kwtools = {'templater': None, 'hgcmd': ''}
112 112
113 113
114 114 def _defaultkwmaps(ui):
115 115 '''Returns default keywordmaps according to keywordset configuration.'''
116 116 templates = {
117 117 'Revision': '{node|short}',
118 118 'Author': '{author|user}',
119 119 }
120 120 kwsets = ({
121 121 'Date': '{date|utcdate}',
122 122 'RCSfile': '{file|basename},v',
123 123 'RCSFile': '{file|basename},v', # kept for backwards compatibility
124 124 # with hg-keyword
125 125 'Source': '{root}/{file},v',
126 126 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
127 127 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
128 128 }, {
129 129 'Date': '{date|svnisodate}',
130 130 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
131 131 'LastChangedRevision': '{node|short}',
132 132 'LastChangedBy': '{author|user}',
133 133 'LastChangedDate': '{date|svnisodate}',
134 134 })
135 135 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
136 136 return templates
137 137
138 138 class kwtemplater(object):
139 139 '''
140 140 Sets up keyword templates, corresponding keyword regex, and
141 141 provides keyword substitution functions.
142 142 '''
143 143
144 144 def __init__(self, ui, repo, inc, exc):
145 145 self.ui = ui
146 146 self.repo = repo
147 147 self.match = match.match(repo.root, '', [], inc, exc)
148 148 self.restrict = kwtools['hgcmd'] in restricted.split()
149 149 self.record = kwtools['hgcmd'] in recordcommands.split()
150 150
151 151 kwmaps = self.ui.configitems('keywordmaps')
152 152 if kwmaps: # override default templates
153 153 self.templates = dict((k, templater.parsestring(v, False))
154 154 for k, v in kwmaps)
155 155 else:
156 156 self.templates = _defaultkwmaps(self.ui)
157 157 escaped = map(re.escape, self.templates.keys())
158 158 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
159 159 self.re_kw = re.compile(kwpat)
160 160
161 161 templatefilters.filters.update({'utcdate': utcdate,
162 162 'svnisodate': svnisodate,
163 163 'svnutcdate': svnutcdate})
164 164
165 165 def substitute(self, data, path, ctx, subfunc):
166 166 '''Replaces keywords in data with expanded template.'''
167 167 def kwsub(mobj):
168 168 kw = mobj.group(1)
169 169 ct = cmdutil.changeset_templater(self.ui, self.repo,
170 170 False, None, '', False)
171 171 ct.use_template(self.templates[kw])
172 172 self.ui.pushbuffer()
173 173 ct.show(ctx, root=self.repo.root, file=path)
174 174 ekw = templatefilters.firstline(self.ui.popbuffer())
175 175 return '$%s: %s $' % (kw, ekw)
176 176 return subfunc(kwsub, data)
177 177
178 178 def expand(self, path, node, data):
179 179 '''Returns data with keywords expanded.'''
180 180 if not self.restrict and self.match(path) and not util.binary(data):
181 181 ctx = self.repo.filectx(path, fileid=node).changectx()
182 182 return self.substitute(data, path, ctx, self.re_kw.sub)
183 183 return data
184 184
185 185 def iskwfile(self, path, flagfunc):
186 186 '''Returns true if path matches [keyword] pattern
187 187 and is not a symbolic link.
188 188 Caveat: localrepository._link fails on Windows.'''
189 189 return self.match(path) and not 'l' in flagfunc(path)
190 190
191 191 def overwrite(self, ctx, candidates, iswctx, expand):
192 192 '''Overwrites selected files expanding/shrinking keywords.'''
193 193 if self.record:
194 194 candidates = [f for f in ctx.files() if f in ctx]
195 195 candidates = [f for f in candidates if self.iskwfile(f, ctx.flags)]
196 196 if candidates:
197 197 self.restrict = True # do not expand when reading
198 198 mf = ctx.manifest()
199 199 msg = (expand and _('overwriting %s expanding keywords\n')
200 200 or _('overwriting %s shrinking keywords\n'))
201 201 for f in candidates:
202 202 if not self.record:
203 203 data = self.repo.file(f).read(mf[f])
204 204 else:
205 205 data = self.repo.wread(f)
206 206 if util.binary(data):
207 207 continue
208 208 if expand:
209 209 if iswctx:
210 210 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
211 211 data, found = self.substitute(data, f, ctx,
212 212 self.re_kw.subn)
213 213 else:
214 214 found = self.re_kw.search(data)
215 215 if found:
216 216 self.ui.note(msg % f)
217 217 self.repo.wwrite(f, data, mf.flags(f))
218 218 if iswctx:
219 219 self.repo.dirstate.normal(f)
220 220 elif self.record:
221 221 self.repo.dirstate.normallookup(f)
222 222 self.restrict = False
223 223
224 224 def shrinktext(self, text):
225 225 '''Unconditionally removes all keyword substitutions from text.'''
226 226 return self.re_kw.sub(r'$\1$', text)
227 227
228 228 def shrink(self, fname, text):
229 229 '''Returns text with all keyword substitutions removed.'''
230 230 if self.match(fname) and not util.binary(text):
231 231 return self.shrinktext(text)
232 232 return text
233 233
234 234 def shrinklines(self, fname, lines):
235 235 '''Returns lines with keyword substitutions removed.'''
236 236 if self.match(fname):
237 237 text = ''.join(lines)
238 238 if not util.binary(text):
239 239 return self.shrinktext(text).splitlines(True)
240 240 return lines
241 241
242 242 def wread(self, fname, data):
243 243 '''If in restricted mode returns data read from wdir with
244 244 keyword substitutions removed.'''
245 245 return self.restrict and self.shrink(fname, data) or data
246 246
247 247 class kwfilelog(filelog.filelog):
248 248 '''
249 249 Subclass of filelog to hook into its read, add, cmp methods.
250 250 Keywords are "stored" unexpanded, and processed on reading.
251 251 '''
252 252 def __init__(self, opener, kwt, path):
253 253 super(kwfilelog, self).__init__(opener, path)
254 254 self.kwt = kwt
255 255 self.path = path
256 256
257 257 def read(self, node):
258 258 '''Expands keywords when reading filelog.'''
259 259 data = super(kwfilelog, self).read(node)
260 260 return self.kwt.expand(self.path, node, data)
261 261
262 262 def add(self, text, meta, tr, link, p1=None, p2=None):
263 263 '''Removes keyword substitutions when adding to filelog.'''
264 264 text = self.kwt.shrink(self.path, text)
265 265 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
266 266
267 267 def cmp(self, node, text):
268 268 '''Removes keyword substitutions for comparison.'''
269 269 text = self.kwt.shrink(self.path, text)
270 270 if self.renamed(node):
271 271 t2 = super(kwfilelog, self).read(node)
272 272 return t2 != text
273 273 return revlog.revlog.cmp(self, node, text)
274 274
275 275 def _status(ui, repo, kwt, *pats, **opts):
276 276 '''Bails out if [keyword] configuration is not active.
277 277 Returns status of working directory.'''
278 278 if kwt:
279 279 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
280 280 unknown=opts.get('unknown') or opts.get('all'))
281 281 if ui.configitems('keyword'):
282 282 raise util.Abort(_('[keyword] patterns cannot match'))
283 283 raise util.Abort(_('no [keyword] patterns configured'))
284 284
285 285 def _kwfwrite(ui, repo, expand, *pats, **opts):
286 286 '''Selects files and passes them to kwtemplater.overwrite.'''
287 287 wctx = repo[None]
288 288 if len(wctx.parents()) > 1:
289 289 raise util.Abort(_('outstanding uncommitted merge'))
290 290 kwt = kwtools['templater']
291 291 wlock = repo.wlock()
292 292 try:
293 293 status = _status(ui, repo, kwt, *pats, **opts)
294 294 modified, added, removed, deleted, unknown, ignored, clean = status
295 295 if modified or added or removed or deleted:
296 296 raise util.Abort(_('outstanding uncommitted changes'))
297 297 kwt.overwrite(wctx, clean, True, expand)
298 298 finally:
299 299 wlock.release()
300 300
301 301 def demo(ui, repo, *args, **opts):
302 302 '''print [keywordmaps] configuration and an expansion example
303 303
304 304 Show current, custom, or default keyword template maps and their
305 305 expansions.
306 306
307 307 Extend the current configuration by specifying maps as arguments
308 308 and using -f/--rcfile to source an external hgrc file.
309 309
310 310 Use -d/--default to disable current configuration.
311 311
312 312 See :hg:`help templates` for information on templates and filters.
313 313 '''
314 314 def demoitems(section, items):
315 315 ui.write('[%s]\n' % section)
316 316 for k, v in sorted(items):
317 317 ui.write('%s = %s\n' % (k, v))
318 318
319 319 fn = 'demo.txt'
320 320 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
321 321 ui.note(_('creating temporary repository at %s\n') % tmpdir)
322 322 repo = localrepo.localrepository(ui, tmpdir, True)
323 323 ui.setconfig('keyword', fn, '')
324 324
325 325 uikwmaps = ui.configitems('keywordmaps')
326 326 if args or opts.get('rcfile'):
327 327 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
328 328 if uikwmaps:
329 329 ui.status(_('\textending current template maps\n'))
330 330 if opts.get('default') or not uikwmaps:
331 331 ui.status(_('\toverriding default template maps\n'))
332 332 if opts.get('rcfile'):
333 333 ui.readconfig(opts.get('rcfile'))
334 334 if args:
335 335 # simulate hgrc parsing
336 336 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
337 337 fp = repo.opener('hgrc', 'w')
338 338 fp.writelines(rcmaps)
339 339 fp.close()
340 340 ui.readconfig(repo.join('hgrc'))
341 341 kwmaps = dict(ui.configitems('keywordmaps'))
342 342 elif opts.get('default'):
343 343 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
344 344 kwmaps = _defaultkwmaps(ui)
345 345 if uikwmaps:
346 346 ui.status(_('\tdisabling current template maps\n'))
347 347 for k, v in kwmaps.iteritems():
348 348 ui.setconfig('keywordmaps', k, v)
349 349 else:
350 350 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
351 351 kwmaps = dict(uikwmaps) or _defaultkwmaps(ui)
352 352
353 353 uisetup(ui)
354 354 reposetup(ui, repo)
355 355 ui.write('[extensions]\nkeyword =\n')
356 356 demoitems('keyword', ui.configitems('keyword'))
357 357 demoitems('keywordmaps', kwmaps.iteritems())
358 358 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
359 359 repo.wopener(fn, 'w').write(keywords)
360 360 repo[None].add([fn])
361 361 ui.note(_('\nkeywords written to %s:\n') % fn)
362 362 ui.note(keywords)
363 363 repo.dirstate.setbranch('demobranch')
364 364 for name, cmd in ui.configitems('hooks'):
365 365 if name.split('.', 1)[0].find('commit') > -1:
366 366 repo.ui.setconfig('hooks', name, '')
367 367 msg = _('hg keyword configuration and expansion example')
368 368 ui.note("hg ci -m '%s'\n" % msg)
369 369 repo.commit(text=msg)
370 370 ui.status(_('\n\tkeywords expanded\n'))
371 371 ui.write(repo.wread(fn))
372 372 shutil.rmtree(tmpdir, ignore_errors=True)
373 373
374 374 def expand(ui, repo, *pats, **opts):
375 375 '''expand keywords in the working directory
376 376
377 377 Run after (re)enabling keyword expansion.
378 378
379 379 kwexpand refuses to run if given files contain local changes.
380 380 '''
381 381 # 3rd argument sets expansion to True
382 382 _kwfwrite(ui, repo, True, *pats, **opts)
383 383
384 384 def files(ui, repo, *pats, **opts):
385 385 '''show files configured for keyword expansion
386 386
387 387 List which files in the working directory are matched by the
388 388 [keyword] configuration patterns.
389 389
390 390 Useful to prevent inadvertent keyword expansion and to speed up
391 391 execution by including only files that are actual candidates for
392 392 expansion.
393 393
394 394 See :hg:`help keyword` on how to construct patterns both for
395 395 inclusion and exclusion of files.
396 396
397 397 With -A/--all and -v/--verbose the codes used to show the status
398 398 of files are::
399 399
400 400 K = keyword expansion candidate
401 401 k = keyword expansion candidate (not tracked)
402 402 I = ignored
403 403 i = ignored (not tracked)
404 404 '''
405 405 kwt = kwtools['templater']
406 406 status = _status(ui, repo, kwt, *pats, **opts)
407 407 cwd = pats and repo.getcwd() or ''
408 408 modified, added, removed, deleted, unknown, ignored, clean = status
409 409 files = []
410 410 if not opts.get('unknown') or opts.get('all'):
411 411 files = sorted(modified + added + clean)
412 412 wctx = repo[None]
413 413 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
414 414 kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
415 415 if not opts.get('ignore') or opts.get('all'):
416 416 showfiles = kwfiles, kwunknown
417 417 else:
418 418 showfiles = [], []
419 419 if opts.get('all') or opts.get('ignore'):
420 420 showfiles += ([f for f in files if f not in kwfiles],
421 421 [f for f in unknown if f not in kwunknown])
422 422 for char, filenames in zip('KkIi', showfiles):
423 423 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
424 424 for f in filenames:
425 425 ui.write(fmt % repo.pathto(f, cwd))
426 426
427 427 def shrink(ui, repo, *pats, **opts):
428 428 '''revert expanded keywords in the working directory
429 429
430 430 Run before changing/disabling active keywords or if you experience
431 431 problems with :hg:`import` or :hg:`merge`.
432 432
433 433 kwshrink refuses to run if given files contain local changes.
434 434 '''
435 435 # 3rd argument sets expansion to False
436 436 _kwfwrite(ui, repo, False, *pats, **opts)
437 437
438 438
439 439 def uisetup(ui):
440 440 ''' Monkeypatches dispatch._parse to retrieve user command.'''
441 441
442 442 def kwdispatch_parse(orig, ui, args):
443 443 '''Monkeypatch dispatch._parse to obtain running hg command.'''
444 444 cmd, func, args, options, cmdoptions = orig(ui, args)
445 445 kwtools['hgcmd'] = cmd
446 446 return cmd, func, args, options, cmdoptions
447 447
448 448 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
449 449
450 450 def reposetup(ui, repo):
451 451 '''Sets up repo as kwrepo for keyword substitution.
452 452 Overrides file method to return kwfilelog instead of filelog
453 453 if file matches user configuration.
454 454 Wraps commit to overwrite configured files with updated
455 455 keyword substitutions.
456 456 Monkeypatches patch and webcommands.'''
457 457
458 458 try:
459 459 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
460 460 or '.hg' in util.splitpath(repo.root)
461 461 or repo._url.startswith('bundle:')):
462 462 return
463 463 except AttributeError:
464 464 pass
465 465
466 466 inc, exc = [], ['.hg*']
467 467 for pat, opt in ui.configitems('keyword'):
468 468 if opt != 'ignore':
469 469 inc.append(pat)
470 470 else:
471 471 exc.append(pat)
472 472 if not inc:
473 473 return
474 474
475 475 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
476 476
477 477 class kwrepo(repo.__class__):
478 478 def file(self, f):
479 479 if f[0] == '/':
480 480 f = f[1:]
481 481 return kwfilelog(self.sopener, kwt, f)
482 482
483 483 def wread(self, filename):
484 484 data = super(kwrepo, self).wread(filename)
485 485 return kwt.wread(filename, data)
486 486
487 487 def commit(self, *args, **opts):
488 488 # use custom commitctx for user commands
489 489 # other extensions can still wrap repo.commitctx directly
490 490 self.commitctx = self.kwcommitctx
491 491 try:
492 492 return super(kwrepo, self).commit(*args, **opts)
493 493 finally:
494 494 del self.commitctx
495 495
496 496 def kwcommitctx(self, ctx, error=False):
497 497 n = super(kwrepo, self).commitctx(ctx, error)
498 498 # no lock needed, only called from repo.commit() which already locks
499 499 if not kwt.record:
500 500 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
501 501 False, True)
502 502 return n
503 503
504 504 # monkeypatches
505 505 def kwpatchfile_init(orig, self, ui, fname, opener,
506 506 missing=False, eolmode=None):
507 507 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
508 508 rejects or conflicts due to expanded keywords in working dir.'''
509 509 orig(self, ui, fname, opener, missing, eolmode)
510 510 # shrink keywords read from working dir
511 511 self.lines = kwt.shrinklines(self.fname, self.lines)
512 512
513 513 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
514 opts=None):
514 opts=None, prefix=''):
515 515 '''Monkeypatch patch.diff to avoid expansion except when
516 516 comparing against working dir.'''
517 517 if node2 is not None:
518 518 kwt.match = util.never
519 519 elif node1 is not None and node1 != repo['.'].node():
520 520 kwt.restrict = True
521 return orig(repo, node1, node2, match, changes, opts)
521 return orig(repo, node1, node2, match, changes, opts, prefix)
522 522
523 523 def kwweb_skip(orig, web, req, tmpl):
524 524 '''Wraps webcommands.x turning off keyword expansion.'''
525 525 kwt.match = util.never
526 526 return orig(web, req, tmpl)
527 527
528 528 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
529 529 '''Wraps record.dorecord expanding keywords after recording.'''
530 530 wlock = repo.wlock()
531 531 try:
532 532 # record returns 0 even when nothing has changed
533 533 # therefore compare nodes before and after
534 534 ctx = repo['.']
535 535 ret = orig(ui, repo, commitfunc, *pats, **opts)
536 536 recordctx = repo['.']
537 537 if ctx != recordctx:
538 538 kwt.overwrite(recordctx, None, False, True)
539 539 return ret
540 540 finally:
541 541 wlock.release()
542 542
543 543 repo.__class__ = kwrepo
544 544
545 545 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
546 546 if not kwt.restrict:
547 547 extensions.wrapfunction(patch, 'diff', kw_diff)
548 548 for c in 'annotate changeset rev filediff diff'.split():
549 549 extensions.wrapfunction(webcommands, c, kwweb_skip)
550 550 for name in recordextensions.split():
551 551 try:
552 552 record = extensions.find(name)
553 553 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
554 554 except KeyError:
555 555 pass
556 556
557 557 cmdtable = {
558 558 'kwdemo':
559 559 (demo,
560 560 [('d', 'default', None, _('show default keyword template maps')),
561 561 ('f', 'rcfile', '',
562 562 _('read maps from rcfile'), _('FILE'))],
563 563 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
564 564 'kwexpand': (expand, commands.walkopts,
565 565 _('hg kwexpand [OPTION]... [FILE]...')),
566 566 'kwfiles':
567 567 (files,
568 568 [('A', 'all', None, _('show keyword status flags of all files')),
569 569 ('i', 'ignore', None, _('show files excluded from expansion')),
570 570 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
571 571 ] + commands.walkopts,
572 572 _('hg kwfiles [OPTION]... [FILE]...')),
573 573 'kwshrink': (shrink, commands.walkopts,
574 574 _('hg kwshrink [OPTION]... [FILE]...')),
575 575 }
@@ -1,1281 +1,1293 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 from node import hex, nullid, nullrev, short
8 from node import hex, bin, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, errno, re, glob, tempfile
11 11 import util, templater, patch, error, encoding, templatekw
12 12 import match as matchmod
13 13 import similar, revset
14 14
15 15 revrangesep = ':'
16 16
17 17 def parsealiases(cmd):
18 18 return cmd.lstrip("^").split("|")
19 19
20 20 def findpossible(cmd, table, strict=False):
21 21 """
22 22 Return cmd -> (aliases, command table entry)
23 23 for each matching command.
24 24 Return debug commands (or their aliases) only if no normal command matches.
25 25 """
26 26 choice = {}
27 27 debugchoice = {}
28 28 for e in table.keys():
29 29 aliases = parsealiases(e)
30 30 found = None
31 31 if cmd in aliases:
32 32 found = cmd
33 33 elif not strict:
34 34 for a in aliases:
35 35 if a.startswith(cmd):
36 36 found = a
37 37 break
38 38 if found is not None:
39 39 if aliases[0].startswith("debug") or found.startswith("debug"):
40 40 debugchoice[found] = (aliases, table[e])
41 41 else:
42 42 choice[found] = (aliases, table[e])
43 43
44 44 if not choice and debugchoice:
45 45 choice = debugchoice
46 46
47 47 return choice
48 48
49 49 def findcmd(cmd, table, strict=True):
50 50 """Return (aliases, command table entry) for command string."""
51 51 choice = findpossible(cmd, table, strict)
52 52
53 53 if cmd in choice:
54 54 return choice[cmd]
55 55
56 56 if len(choice) > 1:
57 57 clist = choice.keys()
58 58 clist.sort()
59 59 raise error.AmbiguousCommand(cmd, clist)
60 60
61 61 if choice:
62 62 return choice.values()[0]
63 63
64 64 raise error.UnknownCommand(cmd)
65 65
66 66 def findrepo(p):
67 67 while not os.path.isdir(os.path.join(p, ".hg")):
68 68 oldp, p = p, os.path.dirname(p)
69 69 if p == oldp:
70 70 return None
71 71
72 72 return p
73 73
74 74 def bail_if_changed(repo):
75 75 if repo.dirstate.parents()[1] != nullid:
76 76 raise util.Abort(_('outstanding uncommitted merge'))
77 77 modified, added, removed, deleted = repo.status()[:4]
78 78 if modified or added or removed or deleted:
79 79 raise util.Abort(_("outstanding uncommitted changes"))
80 80
81 81 def logmessage(opts):
82 82 """ get the log message according to -m and -l option """
83 83 message = opts.get('message')
84 84 logfile = opts.get('logfile')
85 85
86 86 if message and logfile:
87 87 raise util.Abort(_('options --message and --logfile are mutually '
88 88 'exclusive'))
89 89 if not message and logfile:
90 90 try:
91 91 if logfile == '-':
92 92 message = sys.stdin.read()
93 93 else:
94 94 message = open(logfile).read()
95 95 except IOError, inst:
96 96 raise util.Abort(_("can't read commit message '%s': %s") %
97 97 (logfile, inst.strerror))
98 98 return message
99 99
100 100 def loglimit(opts):
101 101 """get the log limit according to option -l/--limit"""
102 102 limit = opts.get('limit')
103 103 if limit:
104 104 try:
105 105 limit = int(limit)
106 106 except ValueError:
107 107 raise util.Abort(_('limit must be a positive integer'))
108 108 if limit <= 0:
109 109 raise util.Abort(_('limit must be positive'))
110 110 else:
111 111 limit = None
112 112 return limit
113 113
114 114 def revpair(repo, revs):
115 115 '''return pair of nodes, given list of revisions. second item can
116 116 be None, meaning use working dir.'''
117 117
118 118 def revfix(repo, val, defval):
119 119 if not val and val != 0 and defval is not None:
120 120 val = defval
121 121 return repo.lookup(val)
122 122
123 123 if not revs:
124 124 return repo.dirstate.parents()[0], None
125 125 end = None
126 126 if len(revs) == 1:
127 127 if revrangesep in revs[0]:
128 128 start, end = revs[0].split(revrangesep, 1)
129 129 start = revfix(repo, start, 0)
130 130 end = revfix(repo, end, len(repo) - 1)
131 131 else:
132 132 start = revfix(repo, revs[0], None)
133 133 elif len(revs) == 2:
134 134 if revrangesep in revs[0] or revrangesep in revs[1]:
135 135 raise util.Abort(_('too many revisions specified'))
136 136 start = revfix(repo, revs[0], None)
137 137 end = revfix(repo, revs[1], None)
138 138 else:
139 139 raise util.Abort(_('too many revisions specified'))
140 140 return start, end
141 141
142 142 def revrange(repo, revs):
143 143 """Yield revision as strings from a list of revision specifications."""
144 144
145 145 def revfix(repo, val, defval):
146 146 if not val and val != 0 and defval is not None:
147 147 return defval
148 148 return repo.changelog.rev(repo.lookup(val))
149 149
150 150 seen, l = set(), []
151 151 for spec in revs:
152 152 # attempt to parse old-style ranges first to deal with
153 153 # things like old-tag which contain query metacharacters
154 154 try:
155 155 if revrangesep in spec:
156 156 start, end = spec.split(revrangesep, 1)
157 157 start = revfix(repo, start, 0)
158 158 end = revfix(repo, end, len(repo) - 1)
159 159 step = start > end and -1 or 1
160 160 for rev in xrange(start, end + step, step):
161 161 if rev in seen:
162 162 continue
163 163 seen.add(rev)
164 164 l.append(rev)
165 165 continue
166 166 elif spec and spec in repo: # single unquoted rev
167 167 rev = revfix(repo, spec, None)
168 168 if rev in seen:
169 169 continue
170 170 seen.add(rev)
171 171 l.append(rev)
172 172 continue
173 173 except error.RepoLookupError:
174 174 pass
175 175
176 176 # fall through to new-style queries if old-style fails
177 177 m = revset.match(spec)
178 178 for r in m(repo, range(len(repo))):
179 179 if r not in seen:
180 180 l.append(r)
181 181 seen.update(l)
182 182
183 183 return l
184 184
185 185 def make_filename(repo, pat, node,
186 186 total=None, seqno=None, revwidth=None, pathname=None):
187 187 node_expander = {
188 188 'H': lambda: hex(node),
189 189 'R': lambda: str(repo.changelog.rev(node)),
190 190 'h': lambda: short(node),
191 191 }
192 192 expander = {
193 193 '%': lambda: '%',
194 194 'b': lambda: os.path.basename(repo.root),
195 195 }
196 196
197 197 try:
198 198 if node:
199 199 expander.update(node_expander)
200 200 if node:
201 201 expander['r'] = (lambda:
202 202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
203 203 if total is not None:
204 204 expander['N'] = lambda: str(total)
205 205 if seqno is not None:
206 206 expander['n'] = lambda: str(seqno)
207 207 if total is not None and seqno is not None:
208 208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
209 209 if pathname is not None:
210 210 expander['s'] = lambda: os.path.basename(pathname)
211 211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
212 212 expander['p'] = lambda: pathname
213 213
214 214 newname = []
215 215 patlen = len(pat)
216 216 i = 0
217 217 while i < patlen:
218 218 c = pat[i]
219 219 if c == '%':
220 220 i += 1
221 221 c = pat[i]
222 222 c = expander[c]()
223 223 newname.append(c)
224 224 i += 1
225 225 return ''.join(newname)
226 226 except KeyError, inst:
227 227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
228 228 inst.args[0])
229 229
230 230 def make_file(repo, pat, node=None,
231 231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
232 232
233 233 writable = 'w' in mode or 'a' in mode
234 234
235 235 if not pat or pat == '-':
236 236 return writable and sys.stdout or sys.stdin
237 237 if hasattr(pat, 'write') and writable:
238 238 return pat
239 239 if hasattr(pat, 'read') and 'r' in mode:
240 240 return pat
241 241 return open(make_filename(repo, pat, node, total, seqno, revwidth,
242 242 pathname),
243 243 mode)
244 244
245 245 def expandpats(pats):
246 246 if not util.expandglobs:
247 247 return list(pats)
248 248 ret = []
249 249 for p in pats:
250 250 kind, name = matchmod._patsplit(p, None)
251 251 if kind is None:
252 252 try:
253 253 globbed = glob.glob(name)
254 254 except re.error:
255 255 globbed = [name]
256 256 if globbed:
257 257 ret.extend(globbed)
258 258 continue
259 259 ret.append(p)
260 260 return ret
261 261
262 262 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
263 263 if not globbed and default == 'relpath':
264 264 pats = expandpats(pats or [])
265 265 m = matchmod.match(repo.root, repo.getcwd(), pats,
266 266 opts.get('include'), opts.get('exclude'), default,
267 267 auditor=repo.auditor)
268 268 def badfn(f, msg):
269 269 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
270 270 m.bad = badfn
271 271 return m
272 272
273 273 def matchall(repo):
274 274 return matchmod.always(repo.root, repo.getcwd())
275 275
276 276 def matchfiles(repo, files):
277 277 return matchmod.exact(repo.root, repo.getcwd(), files)
278 278
279 279 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
280 280 if dry_run is None:
281 281 dry_run = opts.get('dry_run')
282 282 if similarity is None:
283 283 similarity = float(opts.get('similarity') or 0)
284 284 # we'd use status here, except handling of symlinks and ignore is tricky
285 285 added, unknown, deleted, removed = [], [], [], []
286 286 audit_path = util.path_auditor(repo.root)
287 287 m = match(repo, pats, opts)
288 288 for abs in repo.walk(m):
289 289 target = repo.wjoin(abs)
290 290 good = True
291 291 try:
292 292 audit_path(abs)
293 293 except:
294 294 good = False
295 295 rel = m.rel(abs)
296 296 exact = m.exact(abs)
297 297 if good and abs not in repo.dirstate:
298 298 unknown.append(abs)
299 299 if repo.ui.verbose or not exact:
300 300 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
301 301 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
302 302 or (os.path.isdir(target) and not os.path.islink(target))):
303 303 deleted.append(abs)
304 304 if repo.ui.verbose or not exact:
305 305 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
306 306 # for finding renames
307 307 elif repo.dirstate[abs] == 'r':
308 308 removed.append(abs)
309 309 elif repo.dirstate[abs] == 'a':
310 310 added.append(abs)
311 311 copies = {}
312 312 if similarity > 0:
313 313 for old, new, score in similar.findrenames(repo,
314 314 added + unknown, removed + deleted, similarity):
315 315 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
316 316 repo.ui.status(_('recording removal of %s as rename to %s '
317 317 '(%d%% similar)\n') %
318 318 (m.rel(old), m.rel(new), score * 100))
319 319 copies[new] = old
320 320
321 321 if not dry_run:
322 322 wctx = repo[None]
323 323 wlock = repo.wlock()
324 324 try:
325 325 wctx.remove(deleted)
326 326 wctx.add(unknown)
327 327 for new, old in copies.iteritems():
328 328 wctx.copy(old, new)
329 329 finally:
330 330 wlock.release()
331 331
332 332 def copy(ui, repo, pats, opts, rename=False):
333 333 # called with the repo lock held
334 334 #
335 335 # hgsep => pathname that uses "/" to separate directories
336 336 # ossep => pathname that uses os.sep to separate directories
337 337 cwd = repo.getcwd()
338 338 targets = {}
339 339 after = opts.get("after")
340 340 dryrun = opts.get("dry_run")
341 341 wctx = repo[None]
342 342
343 343 def walkpat(pat):
344 344 srcs = []
345 345 badstates = after and '?' or '?r'
346 346 m = match(repo, [pat], opts, globbed=True)
347 347 for abs in repo.walk(m):
348 348 state = repo.dirstate[abs]
349 349 rel = m.rel(abs)
350 350 exact = m.exact(abs)
351 351 if state in badstates:
352 352 if exact and state == '?':
353 353 ui.warn(_('%s: not copying - file is not managed\n') % rel)
354 354 if exact and state == 'r':
355 355 ui.warn(_('%s: not copying - file has been marked for'
356 356 ' remove\n') % rel)
357 357 continue
358 358 # abs: hgsep
359 359 # rel: ossep
360 360 srcs.append((abs, rel, exact))
361 361 return srcs
362 362
363 363 # abssrc: hgsep
364 364 # relsrc: ossep
365 365 # otarget: ossep
366 366 def copyfile(abssrc, relsrc, otarget, exact):
367 367 abstarget = util.canonpath(repo.root, cwd, otarget)
368 368 reltarget = repo.pathto(abstarget, cwd)
369 369 target = repo.wjoin(abstarget)
370 370 src = repo.wjoin(abssrc)
371 371 state = repo.dirstate[abstarget]
372 372
373 373 # check for collisions
374 374 prevsrc = targets.get(abstarget)
375 375 if prevsrc is not None:
376 376 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
377 377 (reltarget, repo.pathto(abssrc, cwd),
378 378 repo.pathto(prevsrc, cwd)))
379 379 return
380 380
381 381 # check for overwrites
382 382 exists = os.path.exists(target)
383 383 if not after and exists or after and state in 'mn':
384 384 if not opts['force']:
385 385 ui.warn(_('%s: not overwriting - file exists\n') %
386 386 reltarget)
387 387 return
388 388
389 389 if after:
390 390 if not exists:
391 391 if rename:
392 392 ui.warn(_('%s: not recording move - %s does not exist\n') %
393 393 (relsrc, reltarget))
394 394 else:
395 395 ui.warn(_('%s: not recording copy - %s does not exist\n') %
396 396 (relsrc, reltarget))
397 397 return
398 398 elif not dryrun:
399 399 try:
400 400 if exists:
401 401 os.unlink(target)
402 402 targetdir = os.path.dirname(target) or '.'
403 403 if not os.path.isdir(targetdir):
404 404 os.makedirs(targetdir)
405 405 util.copyfile(src, target)
406 406 except IOError, inst:
407 407 if inst.errno == errno.ENOENT:
408 408 ui.warn(_('%s: deleted in working copy\n') % relsrc)
409 409 else:
410 410 ui.warn(_('%s: cannot copy - %s\n') %
411 411 (relsrc, inst.strerror))
412 412 return True # report a failure
413 413
414 414 if ui.verbose or not exact:
415 415 if rename:
416 416 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
417 417 else:
418 418 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
419 419
420 420 targets[abstarget] = abssrc
421 421
422 422 # fix up dirstate
423 423 origsrc = repo.dirstate.copied(abssrc) or abssrc
424 424 if abstarget == origsrc: # copying back a copy?
425 425 if state not in 'mn' and not dryrun:
426 426 repo.dirstate.normallookup(abstarget)
427 427 else:
428 428 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
429 429 if not ui.quiet:
430 430 ui.warn(_("%s has not been committed yet, so no copy "
431 431 "data will be stored for %s.\n")
432 432 % (repo.pathto(origsrc, cwd), reltarget))
433 433 if repo.dirstate[abstarget] in '?r' and not dryrun:
434 434 wctx.add([abstarget])
435 435 elif not dryrun:
436 436 wctx.copy(origsrc, abstarget)
437 437
438 438 if rename and not dryrun:
439 439 wctx.remove([abssrc], not after)
440 440
441 441 # pat: ossep
442 442 # dest ossep
443 443 # srcs: list of (hgsep, hgsep, ossep, bool)
444 444 # return: function that takes hgsep and returns ossep
445 445 def targetpathfn(pat, dest, srcs):
446 446 if os.path.isdir(pat):
447 447 abspfx = util.canonpath(repo.root, cwd, pat)
448 448 abspfx = util.localpath(abspfx)
449 449 if destdirexists:
450 450 striplen = len(os.path.split(abspfx)[0])
451 451 else:
452 452 striplen = len(abspfx)
453 453 if striplen:
454 454 striplen += len(os.sep)
455 455 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
456 456 elif destdirexists:
457 457 res = lambda p: os.path.join(dest,
458 458 os.path.basename(util.localpath(p)))
459 459 else:
460 460 res = lambda p: dest
461 461 return res
462 462
463 463 # pat: ossep
464 464 # dest ossep
465 465 # srcs: list of (hgsep, hgsep, ossep, bool)
466 466 # return: function that takes hgsep and returns ossep
467 467 def targetpathafterfn(pat, dest, srcs):
468 468 if matchmod.patkind(pat):
469 469 # a mercurial pattern
470 470 res = lambda p: os.path.join(dest,
471 471 os.path.basename(util.localpath(p)))
472 472 else:
473 473 abspfx = util.canonpath(repo.root, cwd, pat)
474 474 if len(abspfx) < len(srcs[0][0]):
475 475 # A directory. Either the target path contains the last
476 476 # component of the source path or it does not.
477 477 def evalpath(striplen):
478 478 score = 0
479 479 for s in srcs:
480 480 t = os.path.join(dest, util.localpath(s[0])[striplen:])
481 481 if os.path.exists(t):
482 482 score += 1
483 483 return score
484 484
485 485 abspfx = util.localpath(abspfx)
486 486 striplen = len(abspfx)
487 487 if striplen:
488 488 striplen += len(os.sep)
489 489 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
490 490 score = evalpath(striplen)
491 491 striplen1 = len(os.path.split(abspfx)[0])
492 492 if striplen1:
493 493 striplen1 += len(os.sep)
494 494 if evalpath(striplen1) > score:
495 495 striplen = striplen1
496 496 res = lambda p: os.path.join(dest,
497 497 util.localpath(p)[striplen:])
498 498 else:
499 499 # a file
500 500 if destdirexists:
501 501 res = lambda p: os.path.join(dest,
502 502 os.path.basename(util.localpath(p)))
503 503 else:
504 504 res = lambda p: dest
505 505 return res
506 506
507 507
508 508 pats = expandpats(pats)
509 509 if not pats:
510 510 raise util.Abort(_('no source or destination specified'))
511 511 if len(pats) == 1:
512 512 raise util.Abort(_('no destination specified'))
513 513 dest = pats.pop()
514 514 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
515 515 if not destdirexists:
516 516 if len(pats) > 1 or matchmod.patkind(pats[0]):
517 517 raise util.Abort(_('with multiple sources, destination must be an '
518 518 'existing directory'))
519 519 if util.endswithsep(dest):
520 520 raise util.Abort(_('destination %s is not a directory') % dest)
521 521
522 522 tfn = targetpathfn
523 523 if after:
524 524 tfn = targetpathafterfn
525 525 copylist = []
526 526 for pat in pats:
527 527 srcs = walkpat(pat)
528 528 if not srcs:
529 529 continue
530 530 copylist.append((tfn(pat, dest, srcs), srcs))
531 531 if not copylist:
532 532 raise util.Abort(_('no files to copy'))
533 533
534 534 errors = 0
535 535 for targetpath, srcs in copylist:
536 536 for abssrc, relsrc, exact in srcs:
537 537 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
538 538 errors += 1
539 539
540 540 if errors:
541 541 ui.warn(_('(consider using --after)\n'))
542 542
543 543 return errors != 0
544 544
545 545 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
546 546 runargs=None, appendpid=False):
547 547 '''Run a command as a service.'''
548 548
549 549 if opts['daemon'] and not opts['daemon_pipefds']:
550 550 # Signal child process startup with file removal
551 551 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
552 552 os.close(lockfd)
553 553 try:
554 554 if not runargs:
555 555 runargs = util.hgcmd() + sys.argv[1:]
556 556 runargs.append('--daemon-pipefds=%s' % lockpath)
557 557 # Don't pass --cwd to the child process, because we've already
558 558 # changed directory.
559 559 for i in xrange(1, len(runargs)):
560 560 if runargs[i].startswith('--cwd='):
561 561 del runargs[i]
562 562 break
563 563 elif runargs[i].startswith('--cwd'):
564 564 del runargs[i:i + 2]
565 565 break
566 566 def condfn():
567 567 return not os.path.exists(lockpath)
568 568 pid = util.rundetached(runargs, condfn)
569 569 if pid < 0:
570 570 raise util.Abort(_('child process failed to start'))
571 571 finally:
572 572 try:
573 573 os.unlink(lockpath)
574 574 except OSError, e:
575 575 if e.errno != errno.ENOENT:
576 576 raise
577 577 if parentfn:
578 578 return parentfn(pid)
579 579 else:
580 580 return
581 581
582 582 if initfn:
583 583 initfn()
584 584
585 585 if opts['pid_file']:
586 586 mode = appendpid and 'a' or 'w'
587 587 fp = open(opts['pid_file'], mode)
588 588 fp.write(str(os.getpid()) + '\n')
589 589 fp.close()
590 590
591 591 if opts['daemon_pipefds']:
592 592 lockpath = opts['daemon_pipefds']
593 593 try:
594 594 os.setsid()
595 595 except AttributeError:
596 596 pass
597 597 os.unlink(lockpath)
598 598 util.hidewindow()
599 599 sys.stdout.flush()
600 600 sys.stderr.flush()
601 601
602 602 nullfd = os.open(util.nulldev, os.O_RDWR)
603 603 logfilefd = nullfd
604 604 if logfile:
605 605 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
606 606 os.dup2(nullfd, 0)
607 607 os.dup2(logfilefd, 1)
608 608 os.dup2(logfilefd, 2)
609 609 if nullfd not in (0, 1, 2):
610 610 os.close(nullfd)
611 611 if logfile and logfilefd not in (0, 1, 2):
612 612 os.close(logfilefd)
613 613
614 614 if runfn:
615 615 return runfn()
616 616
617 617 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
618 618 opts=None):
619 619 '''export changesets as hg patches.'''
620 620
621 621 total = len(revs)
622 622 revwidth = max([len(str(rev)) for rev in revs])
623 623
624 624 def single(rev, seqno, fp):
625 625 ctx = repo[rev]
626 626 node = ctx.node()
627 627 parents = [p.node() for p in ctx.parents() if p]
628 628 branch = ctx.branch()
629 629 if switch_parent:
630 630 parents.reverse()
631 631 prev = (parents and parents[0]) or nullid
632 632
633 633 if not fp:
634 634 fp = make_file(repo, template, node, total=total, seqno=seqno,
635 635 revwidth=revwidth, mode='ab')
636 636 if fp != sys.stdout and hasattr(fp, 'name'):
637 637 repo.ui.note("%s\n" % fp.name)
638 638
639 639 fp.write("# HG changeset patch\n")
640 640 fp.write("# User %s\n" % ctx.user())
641 641 fp.write("# Date %d %d\n" % ctx.date())
642 642 if branch and branch != 'default':
643 643 fp.write("# Branch %s\n" % branch)
644 644 fp.write("# Node ID %s\n" % hex(node))
645 645 fp.write("# Parent %s\n" % hex(prev))
646 646 if len(parents) > 1:
647 647 fp.write("# Parent %s\n" % hex(parents[1]))
648 648 fp.write(ctx.description().rstrip())
649 649 fp.write("\n\n")
650 650
651 651 for chunk in patch.diff(repo, prev, node, opts=opts):
652 652 fp.write(chunk)
653 653
654 654 for seqno, rev in enumerate(revs):
655 655 single(rev, seqno + 1, fp)
656 656
657 657 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
658 changes=None, stat=False, fp=None):
658 changes=None, stat=False, fp=None, prefix='',
659 listsubrepos=False):
659 660 '''show diff or diffstat.'''
660 661 if fp is None:
661 662 write = ui.write
662 663 else:
663 664 def write(s, **kw):
664 665 fp.write(s)
665 666
666 667 if stat:
667 668 diffopts = diffopts.copy(context=0)
668 669 width = 80
669 670 if not ui.plain():
670 671 width = util.termwidth()
671 chunks = patch.diff(repo, node1, node2, match, changes, diffopts)
672 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
673 prefix=prefix)
672 674 for chunk, label in patch.diffstatui(util.iterlines(chunks),
673 675 width=width,
674 676 git=diffopts.git):
675 677 write(chunk, label=label)
676 678 else:
677 679 for chunk, label in patch.diffui(repo, node1, node2, match,
678 changes, diffopts):
680 changes, diffopts, prefix=prefix):
679 681 write(chunk, label=label)
680 682
683 if listsubrepos:
684 ctx1 = repo[node1]
685 for subpath in ctx1.substate:
686 sub = ctx1.sub(subpath)
687 if node2 is not None:
688 node2 = bin(repo[node2].substate[subpath][1])
689 submatch = matchmod.narrowmatcher(subpath, match)
690 sub.diff(diffopts, node2, submatch, changes=changes,
691 stat=stat, fp=fp, prefix=prefix)
692
681 693 class changeset_printer(object):
682 694 '''show changeset information when templating not requested.'''
683 695
684 696 def __init__(self, ui, repo, patch, diffopts, buffered):
685 697 self.ui = ui
686 698 self.repo = repo
687 699 self.buffered = buffered
688 700 self.patch = patch
689 701 self.diffopts = diffopts
690 702 self.header = {}
691 703 self.hunk = {}
692 704 self.lastheader = None
693 705 self.footer = None
694 706
695 707 def flush(self, rev):
696 708 if rev in self.header:
697 709 h = self.header[rev]
698 710 if h != self.lastheader:
699 711 self.lastheader = h
700 712 self.ui.write(h)
701 713 del self.header[rev]
702 714 if rev in self.hunk:
703 715 self.ui.write(self.hunk[rev])
704 716 del self.hunk[rev]
705 717 return 1
706 718 return 0
707 719
708 720 def close(self):
709 721 if self.footer:
710 722 self.ui.write(self.footer)
711 723
712 724 def show(self, ctx, copies=None, matchfn=None, **props):
713 725 if self.buffered:
714 726 self.ui.pushbuffer()
715 727 self._show(ctx, copies, matchfn, props)
716 728 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
717 729 else:
718 730 self._show(ctx, copies, matchfn, props)
719 731
720 732 def _show(self, ctx, copies, matchfn, props):
721 733 '''show a single changeset or file revision'''
722 734 changenode = ctx.node()
723 735 rev = ctx.rev()
724 736
725 737 if self.ui.quiet:
726 738 self.ui.write("%d:%s\n" % (rev, short(changenode)),
727 739 label='log.node')
728 740 return
729 741
730 742 log = self.repo.changelog
731 743 date = util.datestr(ctx.date())
732 744
733 745 hexfunc = self.ui.debugflag and hex or short
734 746
735 747 parents = [(p, hexfunc(log.node(p)))
736 748 for p in self._meaningful_parentrevs(log, rev)]
737 749
738 750 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
739 751 label='log.changeset')
740 752
741 753 branch = ctx.branch()
742 754 # don't show the default branch name
743 755 if branch != 'default':
744 756 branch = encoding.tolocal(branch)
745 757 self.ui.write(_("branch: %s\n") % branch,
746 758 label='log.branch')
747 759 for tag in self.repo.nodetags(changenode):
748 760 self.ui.write(_("tag: %s\n") % tag,
749 761 label='log.tag')
750 762 for parent in parents:
751 763 self.ui.write(_("parent: %d:%s\n") % parent,
752 764 label='log.parent')
753 765
754 766 if self.ui.debugflag:
755 767 mnode = ctx.manifestnode()
756 768 self.ui.write(_("manifest: %d:%s\n") %
757 769 (self.repo.manifest.rev(mnode), hex(mnode)),
758 770 label='ui.debug log.manifest')
759 771 self.ui.write(_("user: %s\n") % ctx.user(),
760 772 label='log.user')
761 773 self.ui.write(_("date: %s\n") % date,
762 774 label='log.date')
763 775
764 776 if self.ui.debugflag:
765 777 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
766 778 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
767 779 files):
768 780 if value:
769 781 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
770 782 label='ui.debug log.files')
771 783 elif ctx.files() and self.ui.verbose:
772 784 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
773 785 label='ui.note log.files')
774 786 if copies and self.ui.verbose:
775 787 copies = ['%s (%s)' % c for c in copies]
776 788 self.ui.write(_("copies: %s\n") % ' '.join(copies),
777 789 label='ui.note log.copies')
778 790
779 791 extra = ctx.extra()
780 792 if extra and self.ui.debugflag:
781 793 for key, value in sorted(extra.items()):
782 794 self.ui.write(_("extra: %s=%s\n")
783 795 % (key, value.encode('string_escape')),
784 796 label='ui.debug log.extra')
785 797
786 798 description = ctx.description().strip()
787 799 if description:
788 800 if self.ui.verbose:
789 801 self.ui.write(_("description:\n"),
790 802 label='ui.note log.description')
791 803 self.ui.write(description,
792 804 label='ui.note log.description')
793 805 self.ui.write("\n\n")
794 806 else:
795 807 self.ui.write(_("summary: %s\n") %
796 808 description.splitlines()[0],
797 809 label='log.summary')
798 810 self.ui.write("\n")
799 811
800 812 self.showpatch(changenode, matchfn)
801 813
802 814 def showpatch(self, node, matchfn):
803 815 if not matchfn:
804 816 matchfn = self.patch
805 817 if matchfn:
806 818 stat = self.diffopts.get('stat')
807 819 diff = self.diffopts.get('patch')
808 820 diffopts = patch.diffopts(self.ui, self.diffopts)
809 821 prev = self.repo.changelog.parents(node)[0]
810 822 if stat:
811 823 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
812 824 match=matchfn, stat=True)
813 825 if diff:
814 826 if stat:
815 827 self.ui.write("\n")
816 828 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
817 829 match=matchfn, stat=False)
818 830 self.ui.write("\n")
819 831
820 832 def _meaningful_parentrevs(self, log, rev):
821 833 """Return list of meaningful (or all if debug) parentrevs for rev.
822 834
823 835 For merges (two non-nullrev revisions) both parents are meaningful.
824 836 Otherwise the first parent revision is considered meaningful if it
825 837 is not the preceding revision.
826 838 """
827 839 parents = log.parentrevs(rev)
828 840 if not self.ui.debugflag and parents[1] == nullrev:
829 841 if parents[0] >= rev - 1:
830 842 parents = []
831 843 else:
832 844 parents = [parents[0]]
833 845 return parents
834 846
835 847
836 848 class changeset_templater(changeset_printer):
837 849 '''format changeset information.'''
838 850
839 851 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
840 852 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
841 853 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
842 854 defaulttempl = {
843 855 'parent': '{rev}:{node|formatnode} ',
844 856 'manifest': '{rev}:{node|formatnode}',
845 857 'file_copy': '{name} ({source})',
846 858 'extra': '{key}={value|stringescape}'
847 859 }
848 860 # filecopy is preserved for compatibility reasons
849 861 defaulttempl['filecopy'] = defaulttempl['file_copy']
850 862 self.t = templater.templater(mapfile, {'formatnode': formatnode},
851 863 cache=defaulttempl)
852 864 self.cache = {}
853 865
854 866 def use_template(self, t):
855 867 '''set template string to use'''
856 868 self.t.cache['changeset'] = t
857 869
858 870 def _meaningful_parentrevs(self, ctx):
859 871 """Return list of meaningful (or all if debug) parentrevs for rev.
860 872 """
861 873 parents = ctx.parents()
862 874 if len(parents) > 1:
863 875 return parents
864 876 if self.ui.debugflag:
865 877 return [parents[0], self.repo['null']]
866 878 if parents[0].rev() >= ctx.rev() - 1:
867 879 return []
868 880 return parents
869 881
870 882 def _show(self, ctx, copies, matchfn, props):
871 883 '''show a single changeset or file revision'''
872 884
873 885 showlist = templatekw.showlist
874 886
875 887 # showparents() behaviour depends on ui trace level which
876 888 # causes unexpected behaviours at templating level and makes
877 889 # it harder to extract it in a standalone function. Its
878 890 # behaviour cannot be changed so leave it here for now.
879 891 def showparents(**args):
880 892 ctx = args['ctx']
881 893 parents = [[('rev', p.rev()), ('node', p.hex())]
882 894 for p in self._meaningful_parentrevs(ctx)]
883 895 return showlist('parent', parents, **args)
884 896
885 897 props = props.copy()
886 898 props.update(templatekw.keywords)
887 899 props['parents'] = showparents
888 900 props['templ'] = self.t
889 901 props['ctx'] = ctx
890 902 props['repo'] = self.repo
891 903 props['revcache'] = {'copies': copies}
892 904 props['cache'] = self.cache
893 905
894 906 # find correct templates for current mode
895 907
896 908 tmplmodes = [
897 909 (True, None),
898 910 (self.ui.verbose, 'verbose'),
899 911 (self.ui.quiet, 'quiet'),
900 912 (self.ui.debugflag, 'debug'),
901 913 ]
902 914
903 915 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
904 916 for mode, postfix in tmplmodes:
905 917 for type in types:
906 918 cur = postfix and ('%s_%s' % (type, postfix)) or type
907 919 if mode and cur in self.t:
908 920 types[type] = cur
909 921
910 922 try:
911 923
912 924 # write header
913 925 if types['header']:
914 926 h = templater.stringify(self.t(types['header'], **props))
915 927 if self.buffered:
916 928 self.header[ctx.rev()] = h
917 929 else:
918 930 if self.lastheader != h:
919 931 self.lastheader = h
920 932 self.ui.write(h)
921 933
922 934 # write changeset metadata, then patch if requested
923 935 key = types['changeset']
924 936 self.ui.write(templater.stringify(self.t(key, **props)))
925 937 self.showpatch(ctx.node(), matchfn)
926 938
927 939 if types['footer']:
928 940 if not self.footer:
929 941 self.footer = templater.stringify(self.t(types['footer'],
930 942 **props))
931 943
932 944 except KeyError, inst:
933 945 msg = _("%s: no key named '%s'")
934 946 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
935 947 except SyntaxError, inst:
936 948 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
937 949
938 950 def show_changeset(ui, repo, opts, buffered=False):
939 951 """show one changeset using template or regular display.
940 952
941 953 Display format will be the first non-empty hit of:
942 954 1. option 'template'
943 955 2. option 'style'
944 956 3. [ui] setting 'logtemplate'
945 957 4. [ui] setting 'style'
946 958 If all of these values are either the unset or the empty string,
947 959 regular display via changeset_printer() is done.
948 960 """
949 961 # options
950 962 patch = False
951 963 if opts.get('patch') or opts.get('stat'):
952 964 patch = matchall(repo)
953 965
954 966 tmpl = opts.get('template')
955 967 style = None
956 968 if tmpl:
957 969 tmpl = templater.parsestring(tmpl, quoted=False)
958 970 else:
959 971 style = opts.get('style')
960 972
961 973 # ui settings
962 974 if not (tmpl or style):
963 975 tmpl = ui.config('ui', 'logtemplate')
964 976 if tmpl:
965 977 tmpl = templater.parsestring(tmpl)
966 978 else:
967 979 style = util.expandpath(ui.config('ui', 'style', ''))
968 980
969 981 if not (tmpl or style):
970 982 return changeset_printer(ui, repo, patch, opts, buffered)
971 983
972 984 mapfile = None
973 985 if style and not tmpl:
974 986 mapfile = style
975 987 if not os.path.split(mapfile)[0]:
976 988 mapname = (templater.templatepath('map-cmdline.' + mapfile)
977 989 or templater.templatepath(mapfile))
978 990 if mapname:
979 991 mapfile = mapname
980 992
981 993 try:
982 994 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
983 995 except SyntaxError, inst:
984 996 raise util.Abort(inst.args[0])
985 997 if tmpl:
986 998 t.use_template(tmpl)
987 999 return t
988 1000
989 1001 def finddate(ui, repo, date):
990 1002 """Find the tipmost changeset that matches the given date spec"""
991 1003
992 1004 df = util.matchdate(date)
993 1005 m = matchall(repo)
994 1006 results = {}
995 1007
996 1008 def prep(ctx, fns):
997 1009 d = ctx.date()
998 1010 if df(d[0]):
999 1011 results[ctx.rev()] = d
1000 1012
1001 1013 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1002 1014 rev = ctx.rev()
1003 1015 if rev in results:
1004 1016 ui.status(_("Found revision %s from %s\n") %
1005 1017 (rev, util.datestr(results[rev])))
1006 1018 return str(rev)
1007 1019
1008 1020 raise util.Abort(_("revision matching date not found"))
1009 1021
1010 1022 def walkchangerevs(repo, match, opts, prepare):
1011 1023 '''Iterate over files and the revs in which they changed.
1012 1024
1013 1025 Callers most commonly need to iterate backwards over the history
1014 1026 in which they are interested. Doing so has awful (quadratic-looking)
1015 1027 performance, so we use iterators in a "windowed" way.
1016 1028
1017 1029 We walk a window of revisions in the desired order. Within the
1018 1030 window, we first walk forwards to gather data, then in the desired
1019 1031 order (usually backwards) to display it.
1020 1032
1021 1033 This function returns an iterator yielding contexts. Before
1022 1034 yielding each context, the iterator will first call the prepare
1023 1035 function on each context in the window in forward order.'''
1024 1036
1025 1037 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1026 1038 if start < end:
1027 1039 while start < end:
1028 1040 yield start, min(windowsize, end - start)
1029 1041 start += windowsize
1030 1042 if windowsize < sizelimit:
1031 1043 windowsize *= 2
1032 1044 else:
1033 1045 while start > end:
1034 1046 yield start, min(windowsize, start - end - 1)
1035 1047 start -= windowsize
1036 1048 if windowsize < sizelimit:
1037 1049 windowsize *= 2
1038 1050
1039 1051 follow = opts.get('follow') or opts.get('follow_first')
1040 1052
1041 1053 if not len(repo):
1042 1054 return []
1043 1055
1044 1056 if follow:
1045 1057 defrange = '%s:0' % repo['.'].rev()
1046 1058 else:
1047 1059 defrange = '-1:0'
1048 1060 revs = revrange(repo, opts['rev'] or [defrange])
1049 1061 if not revs:
1050 1062 return []
1051 1063 wanted = set()
1052 1064 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1053 1065 fncache = {}
1054 1066 change = util.cachefunc(repo.changectx)
1055 1067
1056 1068 # First step is to fill wanted, the set of revisions that we want to yield.
1057 1069 # When it does not induce extra cost, we also fill fncache for revisions in
1058 1070 # wanted: a cache of filenames that were changed (ctx.files()) and that
1059 1071 # match the file filtering conditions.
1060 1072
1061 1073 if not slowpath and not match.files():
1062 1074 # No files, no patterns. Display all revs.
1063 1075 wanted = set(revs)
1064 1076 copies = []
1065 1077
1066 1078 if not slowpath:
1067 1079 # We only have to read through the filelog to find wanted revisions
1068 1080
1069 1081 minrev, maxrev = min(revs), max(revs)
1070 1082 def filerevgen(filelog, last):
1071 1083 """
1072 1084 Only files, no patterns. Check the history of each file.
1073 1085
1074 1086 Examines filelog entries within minrev, maxrev linkrev range
1075 1087 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1076 1088 tuples in backwards order
1077 1089 """
1078 1090 cl_count = len(repo)
1079 1091 revs = []
1080 1092 for j in xrange(0, last + 1):
1081 1093 linkrev = filelog.linkrev(j)
1082 1094 if linkrev < minrev:
1083 1095 continue
1084 1096 # only yield rev for which we have the changelog, it can
1085 1097 # happen while doing "hg log" during a pull or commit
1086 1098 if linkrev > maxrev or linkrev >= cl_count:
1087 1099 break
1088 1100
1089 1101 parentlinkrevs = []
1090 1102 for p in filelog.parentrevs(j):
1091 1103 if p != nullrev:
1092 1104 parentlinkrevs.append(filelog.linkrev(p))
1093 1105 n = filelog.node(j)
1094 1106 revs.append((linkrev, parentlinkrevs,
1095 1107 follow and filelog.renamed(n)))
1096 1108
1097 1109 return reversed(revs)
1098 1110 def iterfiles():
1099 1111 for filename in match.files():
1100 1112 yield filename, None
1101 1113 for filename_node in copies:
1102 1114 yield filename_node
1103 1115 for file_, node in iterfiles():
1104 1116 filelog = repo.file(file_)
1105 1117 if not len(filelog):
1106 1118 if node is None:
1107 1119 # A zero count may be a directory or deleted file, so
1108 1120 # try to find matching entries on the slow path.
1109 1121 if follow:
1110 1122 raise util.Abort(
1111 1123 _('cannot follow nonexistent file: "%s"') % file_)
1112 1124 slowpath = True
1113 1125 break
1114 1126 else:
1115 1127 continue
1116 1128
1117 1129 if node is None:
1118 1130 last = len(filelog) - 1
1119 1131 else:
1120 1132 last = filelog.rev(node)
1121 1133
1122 1134
1123 1135 # keep track of all ancestors of the file
1124 1136 ancestors = set([filelog.linkrev(last)])
1125 1137
1126 1138 # iterate from latest to oldest revision
1127 1139 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1128 1140 if rev not in ancestors:
1129 1141 continue
1130 1142 # XXX insert 1327 fix here
1131 1143 if flparentlinkrevs:
1132 1144 ancestors.update(flparentlinkrevs)
1133 1145
1134 1146 fncache.setdefault(rev, []).append(file_)
1135 1147 wanted.add(rev)
1136 1148 if copied:
1137 1149 copies.append(copied)
1138 1150 if slowpath:
1139 1151 # We have to read the changelog to match filenames against
1140 1152 # changed files
1141 1153
1142 1154 if follow:
1143 1155 raise util.Abort(_('can only follow copies/renames for explicit '
1144 1156 'filenames'))
1145 1157
1146 1158 # The slow path checks files modified in every changeset.
1147 1159 for i in sorted(revs):
1148 1160 ctx = change(i)
1149 1161 matches = filter(match, ctx.files())
1150 1162 if matches:
1151 1163 fncache[i] = matches
1152 1164 wanted.add(i)
1153 1165
1154 1166 class followfilter(object):
1155 1167 def __init__(self, onlyfirst=False):
1156 1168 self.startrev = nullrev
1157 1169 self.roots = set()
1158 1170 self.onlyfirst = onlyfirst
1159 1171
1160 1172 def match(self, rev):
1161 1173 def realparents(rev):
1162 1174 if self.onlyfirst:
1163 1175 return repo.changelog.parentrevs(rev)[0:1]
1164 1176 else:
1165 1177 return filter(lambda x: x != nullrev,
1166 1178 repo.changelog.parentrevs(rev))
1167 1179
1168 1180 if self.startrev == nullrev:
1169 1181 self.startrev = rev
1170 1182 return True
1171 1183
1172 1184 if rev > self.startrev:
1173 1185 # forward: all descendants
1174 1186 if not self.roots:
1175 1187 self.roots.add(self.startrev)
1176 1188 for parent in realparents(rev):
1177 1189 if parent in self.roots:
1178 1190 self.roots.add(rev)
1179 1191 return True
1180 1192 else:
1181 1193 # backwards: all parents
1182 1194 if not self.roots:
1183 1195 self.roots.update(realparents(self.startrev))
1184 1196 if rev in self.roots:
1185 1197 self.roots.remove(rev)
1186 1198 self.roots.update(realparents(rev))
1187 1199 return True
1188 1200
1189 1201 return False
1190 1202
1191 1203 # it might be worthwhile to do this in the iterator if the rev range
1192 1204 # is descending and the prune args are all within that range
1193 1205 for rev in opts.get('prune', ()):
1194 1206 rev = repo.changelog.rev(repo.lookup(rev))
1195 1207 ff = followfilter()
1196 1208 stop = min(revs[0], revs[-1])
1197 1209 for x in xrange(rev, stop - 1, -1):
1198 1210 if ff.match(x):
1199 1211 wanted.discard(x)
1200 1212
1201 1213 # Now that wanted is correctly initialized, we can iterate over the
1202 1214 # revision range, yielding only revisions in wanted.
1203 1215 def iterate():
1204 1216 if follow and not match.files():
1205 1217 ff = followfilter(onlyfirst=opts.get('follow_first'))
1206 1218 def want(rev):
1207 1219 return ff.match(rev) and rev in wanted
1208 1220 else:
1209 1221 def want(rev):
1210 1222 return rev in wanted
1211 1223
1212 1224 for i, window in increasing_windows(0, len(revs)):
1213 1225 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1214 1226 for rev in sorted(nrevs):
1215 1227 fns = fncache.get(rev)
1216 1228 ctx = change(rev)
1217 1229 if not fns:
1218 1230 def fns_generator():
1219 1231 for f in ctx.files():
1220 1232 if match(f):
1221 1233 yield f
1222 1234 fns = fns_generator()
1223 1235 prepare(ctx, fns)
1224 1236 for rev in nrevs:
1225 1237 yield change(rev)
1226 1238 return iterate()
1227 1239
1228 1240 def commit(ui, repo, commitfunc, pats, opts):
1229 1241 '''commit the specified files or all outstanding changes'''
1230 1242 date = opts.get('date')
1231 1243 if date:
1232 1244 opts['date'] = util.parsedate(date)
1233 1245 message = logmessage(opts)
1234 1246
1235 1247 # extract addremove carefully -- this function can be called from a command
1236 1248 # that doesn't support addremove
1237 1249 if opts.get('addremove'):
1238 1250 addremove(repo, pats, opts)
1239 1251
1240 1252 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1241 1253
1242 1254 def commiteditor(repo, ctx, subs):
1243 1255 if ctx.description():
1244 1256 return ctx.description()
1245 1257 return commitforceeditor(repo, ctx, subs)
1246 1258
1247 1259 def commitforceeditor(repo, ctx, subs):
1248 1260 edittext = []
1249 1261 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1250 1262 if ctx.description():
1251 1263 edittext.append(ctx.description())
1252 1264 edittext.append("")
1253 1265 edittext.append("") # Empty line between message and comments.
1254 1266 edittext.append(_("HG: Enter commit message."
1255 1267 " Lines beginning with 'HG:' are removed."))
1256 1268 edittext.append(_("HG: Leave message empty to abort commit."))
1257 1269 edittext.append("HG: --")
1258 1270 edittext.append(_("HG: user: %s") % ctx.user())
1259 1271 if ctx.p2():
1260 1272 edittext.append(_("HG: branch merge"))
1261 1273 if ctx.branch():
1262 1274 edittext.append(_("HG: branch '%s'")
1263 1275 % encoding.tolocal(ctx.branch()))
1264 1276 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1265 1277 edittext.extend([_("HG: added %s") % f for f in added])
1266 1278 edittext.extend([_("HG: changed %s") % f for f in modified])
1267 1279 edittext.extend([_("HG: removed %s") % f for f in removed])
1268 1280 if not added and not modified and not removed:
1269 1281 edittext.append(_("HG: no files changed"))
1270 1282 edittext.append("")
1271 1283 # run editor in the repository root
1272 1284 olddir = os.getcwd()
1273 1285 os.chdir(repo.root)
1274 1286 text = repo.ui.edit("\n".join(edittext), ctx.user())
1275 1287 text = re.sub("(?m)^HG:.*\n", "", text)
1276 1288 os.chdir(olddir)
1277 1289
1278 1290 if not text.strip():
1279 1291 raise util.Abort(_("empty commit message"))
1280 1292
1281 1293 return text
@@ -1,4506 +1,4507 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, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _, gettext
11 11 import os, re, sys, difflib, time, tempfile
12 12 import hg, util, revlog, bundlerepo, extensions, copies, error
13 13 import patch, help, mdiff, url, encoding, templatekw, discovery
14 14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
15 15 import merge as mergemod
16 16 import minirst, revset
17 17 import dagparser
18 18
19 19 # Commands start here, listed alphabetically
20 20
21 21 def add(ui, repo, *pats, **opts):
22 22 """add the specified files on the next commit
23 23
24 24 Schedule files to be version controlled and added to the
25 25 repository.
26 26
27 27 The files will be added to the repository at the next commit. To
28 28 undo an add before that, see :hg:`forget`.
29 29
30 30 If no names are given, add all files to the repository.
31 31
32 32 .. container:: verbose
33 33
34 34 An example showing how new (unknown) files are added
35 35 automatically by :hg:`add`::
36 36
37 37 $ ls
38 38 foo.c
39 39 $ hg status
40 40 ? foo.c
41 41 $ hg add
42 42 adding foo.c
43 43 $ hg status
44 44 A foo.c
45 45
46 46 Returns 0 if all files are successfully added.
47 47 """
48 48
49 49 bad = []
50 50 names = []
51 51 m = cmdutil.match(repo, pats, opts)
52 52 oldbad = m.bad
53 53 m.bad = lambda x, y: bad.append(x) or oldbad(x, y)
54 54
55 55 for f in repo.walk(m):
56 56 exact = m.exact(f)
57 57 if exact or f not in repo.dirstate:
58 58 names.append(f)
59 59 if ui.verbose or not exact:
60 60 ui.status(_('adding %s\n') % m.rel(f))
61 61 if not opts.get('dry_run'):
62 62 bad += [f for f in repo[None].add(names) if f in m.files()]
63 63 return bad and 1 or 0
64 64
65 65 def addremove(ui, repo, *pats, **opts):
66 66 """add all new files, delete all missing files
67 67
68 68 Add all new files and remove all missing files from the
69 69 repository.
70 70
71 71 New files are ignored if they match any of the patterns in
72 72 .hgignore. As with add, these changes take effect at the next
73 73 commit.
74 74
75 75 Use the -s/--similarity option to detect renamed files. With a
76 76 parameter greater than 0, this compares every removed file with
77 77 every added file and records those similar enough as renames. This
78 78 option takes a percentage between 0 (disabled) and 100 (files must
79 79 be identical) as its parameter. Detecting renamed files this way
80 80 can be expensive. After using this option, :hg:`status -C` can be
81 81 used to check which files were identified as moved or renamed.
82 82
83 83 Returns 0 if all files are successfully added.
84 84 """
85 85 try:
86 86 sim = float(opts.get('similarity') or 100)
87 87 except ValueError:
88 88 raise util.Abort(_('similarity must be a number'))
89 89 if sim < 0 or sim > 100:
90 90 raise util.Abort(_('similarity must be between 0 and 100'))
91 91 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
92 92
93 93 def annotate(ui, repo, *pats, **opts):
94 94 """show changeset information by line for each file
95 95
96 96 List changes in files, showing the revision id responsible for
97 97 each line
98 98
99 99 This command is useful for discovering when a change was made and
100 100 by whom.
101 101
102 102 Without the -a/--text option, annotate will avoid processing files
103 103 it detects as binary. With -a, annotate will annotate the file
104 104 anyway, although the results will probably be neither useful
105 105 nor desirable.
106 106
107 107 Returns 0 on success.
108 108 """
109 109 if opts.get('follow'):
110 110 # --follow is deprecated and now just an alias for -f/--file
111 111 # to mimic the behavior of Mercurial before version 1.5
112 112 opts['file'] = 1
113 113
114 114 datefunc = ui.quiet and util.shortdate or util.datestr
115 115 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
116 116
117 117 if not pats:
118 118 raise util.Abort(_('at least one filename or pattern is required'))
119 119
120 120 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
121 121 ('number', lambda x: str(x[0].rev())),
122 122 ('changeset', lambda x: short(x[0].node())),
123 123 ('date', getdate),
124 124 ('file', lambda x: x[0].path()),
125 125 ]
126 126
127 127 if (not opts.get('user') and not opts.get('changeset')
128 128 and not opts.get('date') and not opts.get('file')):
129 129 opts['number'] = 1
130 130
131 131 linenumber = opts.get('line_number') is not None
132 132 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
133 133 raise util.Abort(_('at least one of -n/-c is required for -l'))
134 134
135 135 funcmap = [func for op, func in opmap if opts.get(op)]
136 136 if linenumber:
137 137 lastfunc = funcmap[-1]
138 138 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
139 139
140 140 ctx = repo[opts.get('rev')]
141 141 m = cmdutil.match(repo, pats, opts)
142 142 follow = not opts.get('no_follow')
143 143 for abs in ctx.walk(m):
144 144 fctx = ctx[abs]
145 145 if not opts.get('text') and util.binary(fctx.data()):
146 146 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
147 147 continue
148 148
149 149 lines = fctx.annotate(follow=follow, linenumber=linenumber)
150 150 pieces = []
151 151
152 152 for f in funcmap:
153 153 l = [f(n) for n, dummy in lines]
154 154 if l:
155 155 sized = [(x, encoding.colwidth(x)) for x in l]
156 156 ml = max([w for x, w in sized])
157 157 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
158 158
159 159 if pieces:
160 160 for p, l in zip(zip(*pieces), lines):
161 161 ui.write("%s: %s" % (" ".join(p), l[1]))
162 162
163 163 def archive(ui, repo, dest, **opts):
164 164 '''create an unversioned archive of a repository revision
165 165
166 166 By default, the revision used is the parent of the working
167 167 directory; use -r/--rev to specify a different revision.
168 168
169 169 The archive type is automatically detected based on file
170 170 extension (or override using -t/--type).
171 171
172 172 Valid types are:
173 173
174 174 :``files``: a directory full of files (default)
175 175 :``tar``: tar archive, uncompressed
176 176 :``tbz2``: tar archive, compressed using bzip2
177 177 :``tgz``: tar archive, compressed using gzip
178 178 :``uzip``: zip archive, uncompressed
179 179 :``zip``: zip archive, compressed using deflate
180 180
181 181 The exact name of the destination archive or directory is given
182 182 using a format string; see :hg:`help export` for details.
183 183
184 184 Each member added to an archive file has a directory prefix
185 185 prepended. Use -p/--prefix to specify a format string for the
186 186 prefix. The default is the basename of the archive, with suffixes
187 187 removed.
188 188
189 189 Returns 0 on success.
190 190 '''
191 191
192 192 ctx = repo[opts.get('rev')]
193 193 if not ctx:
194 194 raise util.Abort(_('no working directory: please specify a revision'))
195 195 node = ctx.node()
196 196 dest = cmdutil.make_filename(repo, dest, node)
197 197 if os.path.realpath(dest) == repo.root:
198 198 raise util.Abort(_('repository root cannot be destination'))
199 199
200 200 kind = opts.get('type') or archival.guesskind(dest) or 'files'
201 201 prefix = opts.get('prefix')
202 202
203 203 if dest == '-':
204 204 if kind == 'files':
205 205 raise util.Abort(_('cannot archive plain files to stdout'))
206 206 dest = sys.stdout
207 207 if not prefix:
208 208 prefix = os.path.basename(repo.root) + '-%h'
209 209
210 210 prefix = cmdutil.make_filename(repo, prefix, node)
211 211 matchfn = cmdutil.match(repo, [], opts)
212 212 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
213 213 matchfn, prefix)
214 214
215 215 def backout(ui, repo, node=None, rev=None, **opts):
216 216 '''reverse effect of earlier changeset
217 217
218 218 Commit the backed out changes as a new changeset. The new
219 219 changeset is a child of the backed out changeset.
220 220
221 221 If you backout a changeset other than the tip, a new head is
222 222 created. This head will be the new tip and you should merge this
223 223 backout changeset with another head.
224 224
225 225 The --merge option remembers the parent of the working directory
226 226 before starting the backout, then merges the new head with that
227 227 changeset afterwards. This saves you from doing the merge by hand.
228 228 The result of this merge is not committed, as with a normal merge.
229 229
230 230 See :hg:`help dates` for a list of formats valid for -d/--date.
231 231
232 232 Returns 0 on success.
233 233 '''
234 234 if rev and node:
235 235 raise util.Abort(_("please specify just one revision"))
236 236
237 237 if not rev:
238 238 rev = node
239 239
240 240 if not rev:
241 241 raise util.Abort(_("please specify a revision to backout"))
242 242
243 243 date = opts.get('date')
244 244 if date:
245 245 opts['date'] = util.parsedate(date)
246 246
247 247 cmdutil.bail_if_changed(repo)
248 248 node = repo.lookup(rev)
249 249
250 250 op1, op2 = repo.dirstate.parents()
251 251 a = repo.changelog.ancestor(op1, node)
252 252 if a != node:
253 253 raise util.Abort(_('cannot backout change on a different branch'))
254 254
255 255 p1, p2 = repo.changelog.parents(node)
256 256 if p1 == nullid:
257 257 raise util.Abort(_('cannot backout a change with no parents'))
258 258 if p2 != nullid:
259 259 if not opts.get('parent'):
260 260 raise util.Abort(_('cannot backout a merge changeset without '
261 261 '--parent'))
262 262 p = repo.lookup(opts['parent'])
263 263 if p not in (p1, p2):
264 264 raise util.Abort(_('%s is not a parent of %s') %
265 265 (short(p), short(node)))
266 266 parent = p
267 267 else:
268 268 if opts.get('parent'):
269 269 raise util.Abort(_('cannot use --parent on non-merge changeset'))
270 270 parent = p1
271 271
272 272 # the backout should appear on the same branch
273 273 branch = repo.dirstate.branch()
274 274 hg.clean(repo, node, show_stats=False)
275 275 repo.dirstate.setbranch(branch)
276 276 revert_opts = opts.copy()
277 277 revert_opts['date'] = None
278 278 revert_opts['all'] = True
279 279 revert_opts['rev'] = hex(parent)
280 280 revert_opts['no_backup'] = None
281 281 revert(ui, repo, **revert_opts)
282 282 commit_opts = opts.copy()
283 283 commit_opts['addremove'] = False
284 284 if not commit_opts['message'] and not commit_opts['logfile']:
285 285 # we don't translate commit messages
286 286 commit_opts['message'] = "Backed out changeset %s" % short(node)
287 287 commit_opts['force_editor'] = True
288 288 commit(ui, repo, **commit_opts)
289 289 def nice(node):
290 290 return '%d:%s' % (repo.changelog.rev(node), short(node))
291 291 ui.status(_('changeset %s backs out changeset %s\n') %
292 292 (nice(repo.changelog.tip()), nice(node)))
293 293 if op1 != node:
294 294 hg.clean(repo, op1, show_stats=False)
295 295 if opts.get('merge'):
296 296 ui.status(_('merging with changeset %s\n')
297 297 % nice(repo.changelog.tip()))
298 298 hg.merge(repo, hex(repo.changelog.tip()))
299 299 else:
300 300 ui.status(_('the backout changeset is a new head - '
301 301 'do not forget to merge\n'))
302 302 ui.status(_('(use "backout --merge" '
303 303 'if you want to auto-merge)\n'))
304 304
305 305 def bisect(ui, repo, rev=None, extra=None, command=None,
306 306 reset=None, good=None, bad=None, skip=None, noupdate=None):
307 307 """subdivision search of changesets
308 308
309 309 This command helps to find changesets which introduce problems. To
310 310 use, mark the earliest changeset you know exhibits the problem as
311 311 bad, then mark the latest changeset which is free from the problem
312 312 as good. Bisect will update your working directory to a revision
313 313 for testing (unless the -U/--noupdate option is specified). Once
314 314 you have performed tests, mark the working directory as good or
315 315 bad, and bisect will either update to another candidate changeset
316 316 or announce that it has found the bad revision.
317 317
318 318 As a shortcut, you can also use the revision argument to mark a
319 319 revision as good or bad without checking it out first.
320 320
321 321 If you supply a command, it will be used for automatic bisection.
322 322 Its exit status will be used to mark revisions as good or bad:
323 323 status 0 means good, 125 means to skip the revision, 127
324 324 (command not found) will abort the bisection, and any other
325 325 non-zero exit status means the revision is bad.
326 326
327 327 Returns 0 on success.
328 328 """
329 329 def print_result(nodes, good):
330 330 displayer = cmdutil.show_changeset(ui, repo, {})
331 331 if len(nodes) == 1:
332 332 # narrowed it down to a single revision
333 333 if good:
334 334 ui.write(_("The first good revision is:\n"))
335 335 else:
336 336 ui.write(_("The first bad revision is:\n"))
337 337 displayer.show(repo[nodes[0]])
338 338 else:
339 339 # multiple possible revisions
340 340 if good:
341 341 ui.write(_("Due to skipped revisions, the first "
342 342 "good revision could be any of:\n"))
343 343 else:
344 344 ui.write(_("Due to skipped revisions, the first "
345 345 "bad revision could be any of:\n"))
346 346 for n in nodes:
347 347 displayer.show(repo[n])
348 348 displayer.close()
349 349
350 350 def check_state(state, interactive=True):
351 351 if not state['good'] or not state['bad']:
352 352 if (good or bad or skip or reset) and interactive:
353 353 return
354 354 if not state['good']:
355 355 raise util.Abort(_('cannot bisect (no known good revisions)'))
356 356 else:
357 357 raise util.Abort(_('cannot bisect (no known bad revisions)'))
358 358 return True
359 359
360 360 # backward compatibility
361 361 if rev in "good bad reset init".split():
362 362 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
363 363 cmd, rev, extra = rev, extra, None
364 364 if cmd == "good":
365 365 good = True
366 366 elif cmd == "bad":
367 367 bad = True
368 368 else:
369 369 reset = True
370 370 elif extra or good + bad + skip + reset + bool(command) > 1:
371 371 raise util.Abort(_('incompatible arguments'))
372 372
373 373 if reset:
374 374 p = repo.join("bisect.state")
375 375 if os.path.exists(p):
376 376 os.unlink(p)
377 377 return
378 378
379 379 state = hbisect.load_state(repo)
380 380
381 381 if command:
382 382 changesets = 1
383 383 try:
384 384 while changesets:
385 385 # update state
386 386 status = util.system(command)
387 387 if status == 125:
388 388 transition = "skip"
389 389 elif status == 0:
390 390 transition = "good"
391 391 # status < 0 means process was killed
392 392 elif status == 127:
393 393 raise util.Abort(_("failed to execute %s") % command)
394 394 elif status < 0:
395 395 raise util.Abort(_("%s killed") % command)
396 396 else:
397 397 transition = "bad"
398 398 ctx = repo[rev or '.']
399 399 state[transition].append(ctx.node())
400 400 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
401 401 check_state(state, interactive=False)
402 402 # bisect
403 403 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
404 404 # update to next check
405 405 cmdutil.bail_if_changed(repo)
406 406 hg.clean(repo, nodes[0], show_stats=False)
407 407 finally:
408 408 hbisect.save_state(repo, state)
409 409 print_result(nodes, good)
410 410 return
411 411
412 412 # update state
413 413 node = repo.lookup(rev or '.')
414 414 if good or bad or skip:
415 415 if good:
416 416 state['good'].append(node)
417 417 elif bad:
418 418 state['bad'].append(node)
419 419 elif skip:
420 420 state['skip'].append(node)
421 421 hbisect.save_state(repo, state)
422 422
423 423 if not check_state(state):
424 424 return
425 425
426 426 # actually bisect
427 427 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
428 428 if changesets == 0:
429 429 print_result(nodes, good)
430 430 else:
431 431 assert len(nodes) == 1 # only a single node can be tested next
432 432 node = nodes[0]
433 433 # compute the approximate number of remaining tests
434 434 tests, size = 0, 2
435 435 while size <= changesets:
436 436 tests, size = tests + 1, size * 2
437 437 rev = repo.changelog.rev(node)
438 438 ui.write(_("Testing changeset %d:%s "
439 439 "(%d changesets remaining, ~%d tests)\n")
440 440 % (rev, short(node), changesets, tests))
441 441 if not noupdate:
442 442 cmdutil.bail_if_changed(repo)
443 443 return hg.clean(repo, node)
444 444
445 445 def branch(ui, repo, label=None, **opts):
446 446 """set or show the current branch name
447 447
448 448 With no argument, show the current branch name. With one argument,
449 449 set the working directory branch name (the branch will not exist
450 450 in the repository until the next commit). Standard practice
451 451 recommends that primary development take place on the 'default'
452 452 branch.
453 453
454 454 Unless -f/--force is specified, branch will not let you set a
455 455 branch name that already exists, even if it's inactive.
456 456
457 457 Use -C/--clean to reset the working directory branch to that of
458 458 the parent of the working directory, negating a previous branch
459 459 change.
460 460
461 461 Use the command :hg:`update` to switch to an existing branch. Use
462 462 :hg:`commit --close-branch` to mark this branch as closed.
463 463
464 464 Returns 0 on success.
465 465 """
466 466
467 467 if opts.get('clean'):
468 468 label = repo[None].parents()[0].branch()
469 469 repo.dirstate.setbranch(label)
470 470 ui.status(_('reset working directory to branch %s\n') % label)
471 471 elif label:
472 472 utflabel = encoding.fromlocal(label)
473 473 if not opts.get('force') and utflabel in repo.branchtags():
474 474 if label not in [p.branch() for p in repo.parents()]:
475 475 raise util.Abort(_('a branch of the same name already exists'
476 476 " (use 'hg update' to switch to it)"))
477 477 repo.dirstate.setbranch(utflabel)
478 478 ui.status(_('marked working directory as branch %s\n') % label)
479 479 else:
480 480 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
481 481
482 482 def branches(ui, repo, active=False, closed=False):
483 483 """list repository named branches
484 484
485 485 List the repository's named branches, indicating which ones are
486 486 inactive. If -c/--closed is specified, also list branches which have
487 487 been marked closed (see :hg:`commit --close-branch`).
488 488
489 489 If -a/--active is specified, only show active branches. A branch
490 490 is considered active if it contains repository heads.
491 491
492 492 Use the command :hg:`update` to switch to an existing branch.
493 493
494 494 Returns 0.
495 495 """
496 496
497 497 hexfunc = ui.debugflag and hex or short
498 498 activebranches = [repo[n].branch() for n in repo.heads()]
499 499 def testactive(tag, node):
500 500 realhead = tag in activebranches
501 501 open = node in repo.branchheads(tag, closed=False)
502 502 return realhead and open
503 503 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
504 504 for tag, node in repo.branchtags().items()],
505 505 reverse=True)
506 506
507 507 for isactive, node, tag in branches:
508 508 if (not active) or isactive:
509 509 encodedtag = encoding.tolocal(tag)
510 510 if ui.quiet:
511 511 ui.write("%s\n" % encodedtag)
512 512 else:
513 513 hn = repo.lookup(node)
514 514 if isactive:
515 515 label = 'branches.active'
516 516 notice = ''
517 517 elif hn not in repo.branchheads(tag, closed=False):
518 518 if not closed:
519 519 continue
520 520 label = 'branches.closed'
521 521 notice = _(' (closed)')
522 522 else:
523 523 label = 'branches.inactive'
524 524 notice = _(' (inactive)')
525 525 if tag == repo.dirstate.branch():
526 526 label = 'branches.current'
527 527 rev = str(node).rjust(31 - encoding.colwidth(encodedtag))
528 528 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
529 529 encodedtag = ui.label(encodedtag, label)
530 530 ui.write("%s %s%s\n" % (encodedtag, rev, notice))
531 531
532 532 def bundle(ui, repo, fname, dest=None, **opts):
533 533 """create a changegroup file
534 534
535 535 Generate a compressed changegroup file collecting changesets not
536 536 known to be in another repository.
537 537
538 538 If you omit the destination repository, then hg assumes the
539 539 destination will have all the nodes you specify with --base
540 540 parameters. To create a bundle containing all changesets, use
541 541 -a/--all (or --base null).
542 542
543 543 You can change compression method with the -t/--type option.
544 544 The available compression methods are: none, bzip2, and
545 545 gzip (by default, bundles are compressed using bzip2).
546 546
547 547 The bundle file can then be transferred using conventional means
548 548 and applied to another repository with the unbundle or pull
549 549 command. This is useful when direct push and pull are not
550 550 available or when exporting an entire repository is undesirable.
551 551
552 552 Applying bundles preserves all changeset contents including
553 553 permissions, copy/rename information, and revision history.
554 554
555 555 Returns 0 on success, 1 if no changes found.
556 556 """
557 557 revs = opts.get('rev') or None
558 558 if opts.get('all'):
559 559 base = ['null']
560 560 else:
561 561 base = opts.get('base')
562 562 if base:
563 563 if dest:
564 564 raise util.Abort(_("--base is incompatible with specifying "
565 565 "a destination"))
566 566 base = [repo.lookup(rev) for rev in base]
567 567 # create the right base
568 568 # XXX: nodesbetween / changegroup* should be "fixed" instead
569 569 o = []
570 570 has = set((nullid,))
571 571 for n in base:
572 572 has.update(repo.changelog.reachable(n))
573 573 if revs:
574 574 revs = [repo.lookup(rev) for rev in revs]
575 575 visit = revs[:]
576 576 has.difference_update(visit)
577 577 else:
578 578 visit = repo.changelog.heads()
579 579 seen = {}
580 580 while visit:
581 581 n = visit.pop(0)
582 582 parents = [p for p in repo.changelog.parents(n) if p not in has]
583 583 if len(parents) == 0:
584 584 if n not in has:
585 585 o.append(n)
586 586 else:
587 587 for p in parents:
588 588 if p not in seen:
589 589 seen[p] = 1
590 590 visit.append(p)
591 591 else:
592 592 dest = ui.expandpath(dest or 'default-push', dest or 'default')
593 593 dest, branches = hg.parseurl(dest, opts.get('branch'))
594 594 other = hg.repository(hg.remoteui(repo, opts), dest)
595 595 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
596 596 if revs:
597 597 revs = [repo.lookup(rev) for rev in revs]
598 598 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
599 599
600 600 if not o:
601 601 ui.status(_("no changes found\n"))
602 602 return 1
603 603
604 604 if revs:
605 605 cg = repo.changegroupsubset(o, revs, 'bundle')
606 606 else:
607 607 cg = repo.changegroup(o, 'bundle')
608 608
609 609 bundletype = opts.get('type', 'bzip2').lower()
610 610 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
611 611 bundletype = btypes.get(bundletype)
612 612 if bundletype not in changegroup.bundletypes:
613 613 raise util.Abort(_('unknown bundle type specified with --type'))
614 614
615 615 changegroup.writebundle(cg, fname, bundletype)
616 616
617 617 def cat(ui, repo, file1, *pats, **opts):
618 618 """output the current or given revision of files
619 619
620 620 Print the specified files as they were at the given revision. If
621 621 no revision is given, the parent of the working directory is used,
622 622 or tip if no revision is checked out.
623 623
624 624 Output may be to a file, in which case the name of the file is
625 625 given using a format string. The formatting rules are the same as
626 626 for the export command, with the following additions:
627 627
628 628 :``%s``: basename of file being printed
629 629 :``%d``: dirname of file being printed, or '.' if in repository root
630 630 :``%p``: root-relative path name of file being printed
631 631
632 632 Returns 0 on success.
633 633 """
634 634 ctx = repo[opts.get('rev')]
635 635 err = 1
636 636 m = cmdutil.match(repo, (file1,) + pats, opts)
637 637 for abs in ctx.walk(m):
638 638 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
639 639 data = ctx[abs].data()
640 640 if opts.get('decode'):
641 641 data = repo.wwritedata(abs, data)
642 642 fp.write(data)
643 643 err = 0
644 644 return err
645 645
646 646 def clone(ui, source, dest=None, **opts):
647 647 """make a copy of an existing repository
648 648
649 649 Create a copy of an existing repository in a new directory.
650 650
651 651 If no destination directory name is specified, it defaults to the
652 652 basename of the source.
653 653
654 654 The location of the source is added to the new repository's
655 655 .hg/hgrc file, as the default to be used for future pulls.
656 656
657 657 See :hg:`help urls` for valid source format details.
658 658
659 659 It is possible to specify an ``ssh://`` URL as the destination, but no
660 660 .hg/hgrc and working directory will be created on the remote side.
661 661 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
662 662
663 663 A set of changesets (tags, or branch names) to pull may be specified
664 664 by listing each changeset (tag, or branch name) with -r/--rev.
665 665 If -r/--rev is used, the cloned repository will contain only a subset
666 666 of the changesets of the source repository. Only the set of changesets
667 667 defined by all -r/--rev options (including all their ancestors)
668 668 will be pulled into the destination repository.
669 669 No subsequent changesets (including subsequent tags) will be present
670 670 in the destination.
671 671
672 672 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
673 673 local source repositories.
674 674
675 675 For efficiency, hardlinks are used for cloning whenever the source
676 676 and destination are on the same filesystem (note this applies only
677 677 to the repository data, not to the working directory). Some
678 678 filesystems, such as AFS, implement hardlinking incorrectly, but
679 679 do not report errors. In these cases, use the --pull option to
680 680 avoid hardlinking.
681 681
682 682 In some cases, you can clone repositories and the working directory
683 683 using full hardlinks with ::
684 684
685 685 $ cp -al REPO REPOCLONE
686 686
687 687 This is the fastest way to clone, but it is not always safe. The
688 688 operation is not atomic (making sure REPO is not modified during
689 689 the operation is up to you) and you have to make sure your editor
690 690 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
691 691 this is not compatible with certain extensions that place their
692 692 metadata under the .hg directory, such as mq.
693 693
694 694 Mercurial will update the working directory to the first applicable
695 695 revision from this list:
696 696
697 697 a) null if -U or the source repository has no changesets
698 698 b) if -u . and the source repository is local, the first parent of
699 699 the source repository's working directory
700 700 c) the changeset specified with -u (if a branch name, this means the
701 701 latest head of that branch)
702 702 d) the changeset specified with -r
703 703 e) the tipmost head specified with -b
704 704 f) the tipmost head specified with the url#branch source syntax
705 705 g) the tipmost head of the default branch
706 706 h) tip
707 707
708 708 Returns 0 on success.
709 709 """
710 710 if opts.get('noupdate') and opts.get('updaterev'):
711 711 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
712 712
713 713 r = hg.clone(hg.remoteui(ui, opts), source, dest,
714 714 pull=opts.get('pull'),
715 715 stream=opts.get('uncompressed'),
716 716 rev=opts.get('rev'),
717 717 update=opts.get('updaterev') or not opts.get('noupdate'),
718 718 branch=opts.get('branch'))
719 719
720 720 return r is None
721 721
722 722 def commit(ui, repo, *pats, **opts):
723 723 """commit the specified files or all outstanding changes
724 724
725 725 Commit changes to the given files into the repository. Unlike a
726 726 centralized RCS, this operation is a local operation. See
727 727 :hg:`push` for a way to actively distribute your changes.
728 728
729 729 If a list of files is omitted, all changes reported by :hg:`status`
730 730 will be committed.
731 731
732 732 If you are committing the result of a merge, do not provide any
733 733 filenames or -I/-X filters.
734 734
735 735 If no commit message is specified, Mercurial starts your
736 736 configured editor where you can enter a message. In case your
737 737 commit fails, you will find a backup of your message in
738 738 ``.hg/last-message.txt``.
739 739
740 740 See :hg:`help dates` for a list of formats valid for -d/--date.
741 741
742 742 Returns 0 on success, 1 if nothing changed.
743 743 """
744 744 extra = {}
745 745 if opts.get('close_branch'):
746 746 if repo['.'].node() not in repo.branchheads():
747 747 # The topo heads set is included in the branch heads set of the
748 748 # current branch, so it's sufficient to test branchheads
749 749 raise util.Abort(_('can only close branch heads'))
750 750 extra['close'] = 1
751 751 e = cmdutil.commiteditor
752 752 if opts.get('force_editor'):
753 753 e = cmdutil.commitforceeditor
754 754
755 755 def commitfunc(ui, repo, message, match, opts):
756 756 return repo.commit(message, opts.get('user'), opts.get('date'), match,
757 757 editor=e, extra=extra)
758 758
759 759 branch = repo[None].branch()
760 760 bheads = repo.branchheads(branch)
761 761
762 762 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
763 763 if not node:
764 764 ui.status(_("nothing changed\n"))
765 765 return 1
766 766
767 767 ctx = repo[node]
768 768 parents = ctx.parents()
769 769
770 770 if bheads and not [x for x in parents
771 771 if x.node() in bheads and x.branch() == branch]:
772 772 ui.status(_('created new head\n'))
773 773 # The message is not printed for initial roots. For the other
774 774 # changesets, it is printed in the following situations:
775 775 #
776 776 # Par column: for the 2 parents with ...
777 777 # N: null or no parent
778 778 # B: parent is on another named branch
779 779 # C: parent is a regular non head changeset
780 780 # H: parent was a branch head of the current branch
781 781 # Msg column: whether we print "created new head" message
782 782 # In the following, it is assumed that there already exists some
783 783 # initial branch heads of the current branch, otherwise nothing is
784 784 # printed anyway.
785 785 #
786 786 # Par Msg Comment
787 787 # NN y additional topo root
788 788 #
789 789 # BN y additional branch root
790 790 # CN y additional topo head
791 791 # HN n usual case
792 792 #
793 793 # BB y weird additional branch root
794 794 # CB y branch merge
795 795 # HB n merge with named branch
796 796 #
797 797 # CC y additional head from merge
798 798 # CH n merge with a head
799 799 #
800 800 # HH n head merge: head count decreases
801 801
802 802 if not opts.get('close_branch'):
803 803 for r in parents:
804 804 if r.extra().get('close') and r.branch() == branch:
805 805 ui.status(_('reopening closed branch head %d\n') % r)
806 806
807 807 if ui.debugflag:
808 808 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
809 809 elif ui.verbose:
810 810 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
811 811
812 812 def copy(ui, repo, *pats, **opts):
813 813 """mark files as copied for the next commit
814 814
815 815 Mark dest as having copies of source files. If dest is a
816 816 directory, copies are put in that directory. If dest is a file,
817 817 the source must be a single file.
818 818
819 819 By default, this command copies the contents of files as they
820 820 exist in the working directory. If invoked with -A/--after, the
821 821 operation is recorded, but no copying is performed.
822 822
823 823 This command takes effect with the next commit. To undo a copy
824 824 before that, see :hg:`revert`.
825 825
826 826 Returns 0 on success, 1 if errors are encountered.
827 827 """
828 828 wlock = repo.wlock(False)
829 829 try:
830 830 return cmdutil.copy(ui, repo, pats, opts)
831 831 finally:
832 832 wlock.release()
833 833
834 834 def debugancestor(ui, repo, *args):
835 835 """find the ancestor revision of two revisions in a given index"""
836 836 if len(args) == 3:
837 837 index, rev1, rev2 = args
838 838 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
839 839 lookup = r.lookup
840 840 elif len(args) == 2:
841 841 if not repo:
842 842 raise util.Abort(_("there is no Mercurial repository here "
843 843 "(.hg not found)"))
844 844 rev1, rev2 = args
845 845 r = repo.changelog
846 846 lookup = repo.lookup
847 847 else:
848 848 raise util.Abort(_('either two or three arguments required'))
849 849 a = r.ancestor(lookup(rev1), lookup(rev2))
850 850 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
851 851
852 852 def debugbuilddag(ui, repo, text,
853 853 mergeable_file=False,
854 854 appended_file=False,
855 855 overwritten_file=False,
856 856 new_file=False):
857 857 """builds a repo with a given dag from scratch in the current empty repo
858 858
859 859 Elements:
860 860
861 861 - "+n" is a linear run of n nodes based on the current default parent
862 862 - "." is a single node based on the current default parent
863 863 - "$" resets the default parent to null (implied at the start);
864 864 otherwise the default parent is always the last node created
865 865 - "<p" sets the default parent to the backref p
866 866 - "*p" is a fork at parent p, which is a backref
867 867 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
868 868 - "/p2" is a merge of the preceding node and p2
869 869 - ":tag" defines a local tag for the preceding node
870 870 - "@branch" sets the named branch for subsequent nodes
871 871 - "!command" runs the command using your shell
872 872 - "!!my command\\n" is like "!", but to the end of the line
873 873 - "#...\\n" is a comment up to the end of the line
874 874
875 875 Whitespace between the above elements is ignored.
876 876
877 877 A backref is either
878 878
879 879 - a number n, which references the node curr-n, where curr is the current
880 880 node, or
881 881 - the name of a local tag you placed earlier using ":tag", or
882 882 - empty to denote the default parent.
883 883
884 884 All string valued-elements are either strictly alphanumeric, or must
885 885 be enclosed in double quotes ("..."), with "\\" as escape character.
886 886
887 887 Note that the --overwritten-file and --appended-file options imply the
888 888 use of "HGMERGE=internal:local" during DAG buildup.
889 889 """
890 890
891 891 if not (mergeable_file or appended_file or overwritten_file or new_file):
892 892 raise util.Abort(_('need at least one of -m, -a, -o, -n'))
893 893
894 894 if len(repo.changelog) > 0:
895 895 raise util.Abort(_('repository is not empty'))
896 896
897 897 if overwritten_file or appended_file:
898 898 # we don't want to fail in merges during buildup
899 899 os.environ['HGMERGE'] = 'internal:local'
900 900
901 901 def writefile(fname, text, fmode="wb"):
902 902 f = open(fname, fmode)
903 903 try:
904 904 f.write(text)
905 905 finally:
906 906 f.close()
907 907
908 908 if mergeable_file:
909 909 linesperrev = 2
910 910 # determine number of revs in DAG
911 911 n = 0
912 912 for type, data in dagparser.parsedag(text):
913 913 if type == 'n':
914 914 n += 1
915 915 # make a file with k lines per rev
916 916 writefile("mf", "\n".join(str(i) for i in xrange(0, n * linesperrev))
917 917 + "\n")
918 918
919 919 at = -1
920 920 atbranch = 'default'
921 921 for type, data in dagparser.parsedag(text):
922 922 if type == 'n':
923 923 ui.status('node %s\n' % str(data))
924 924 id, ps = data
925 925 p1 = ps[0]
926 926 if p1 != at:
927 927 update(ui, repo, node=p1, clean=True)
928 928 at = p1
929 929 if repo.dirstate.branch() != atbranch:
930 930 branch(ui, repo, atbranch, force=True)
931 931 if len(ps) > 1:
932 932 p2 = ps[1]
933 933 merge(ui, repo, node=p2)
934 934
935 935 if mergeable_file:
936 936 f = open("mf", "rb+")
937 937 try:
938 938 lines = f.read().split("\n")
939 939 lines[id * linesperrev] += " r%i" % id
940 940 f.seek(0)
941 941 f.write("\n".join(lines))
942 942 finally:
943 943 f.close()
944 944
945 945 if appended_file:
946 946 writefile("af", "r%i\n" % id, "ab")
947 947
948 948 if overwritten_file:
949 949 writefile("of", "r%i\n" % id)
950 950
951 951 if new_file:
952 952 writefile("nf%i" % id, "r%i\n" % id)
953 953
954 954 commit(ui, repo, addremove=True, message="r%i" % id, date=(id, 0))
955 955 at = id
956 956 elif type == 'l':
957 957 id, name = data
958 958 ui.status('tag %s\n' % name)
959 959 tag(ui, repo, name, local=True)
960 960 elif type == 'a':
961 961 ui.status('branch %s\n' % data)
962 962 atbranch = data
963 963 elif type in 'cC':
964 964 r = util.system(data, cwd=repo.root)
965 965 if r:
966 966 desc, r = util.explain_exit(r)
967 967 raise util.Abort(_('%s command %s') % (data, desc))
968 968
969 969 def debugcommands(ui, cmd='', *args):
970 970 """list all available commands and options"""
971 971 for cmd, vals in sorted(table.iteritems()):
972 972 cmd = cmd.split('|')[0].strip('^')
973 973 opts = ', '.join([i[1] for i in vals[1]])
974 974 ui.write('%s: %s\n' % (cmd, opts))
975 975
976 976 def debugcomplete(ui, cmd='', **opts):
977 977 """returns the completion list associated with the given command"""
978 978
979 979 if opts.get('options'):
980 980 options = []
981 981 otables = [globalopts]
982 982 if cmd:
983 983 aliases, entry = cmdutil.findcmd(cmd, table, False)
984 984 otables.append(entry[1])
985 985 for t in otables:
986 986 for o in t:
987 987 if "(DEPRECATED)" in o[3]:
988 988 continue
989 989 if o[0]:
990 990 options.append('-%s' % o[0])
991 991 options.append('--%s' % o[1])
992 992 ui.write("%s\n" % "\n".join(options))
993 993 return
994 994
995 995 cmdlist = cmdutil.findpossible(cmd, table)
996 996 if ui.verbose:
997 997 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
998 998 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
999 999
1000 1000 def debugfsinfo(ui, path = "."):
1001 1001 """show information detected about current filesystem"""
1002 1002 open('.debugfsinfo', 'w').write('')
1003 1003 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1004 1004 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1005 1005 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1006 1006 and 'yes' or 'no'))
1007 1007 os.unlink('.debugfsinfo')
1008 1008
1009 1009 def debugrebuildstate(ui, repo, rev="tip"):
1010 1010 """rebuild the dirstate as it would look like for the given revision"""
1011 1011 ctx = repo[rev]
1012 1012 wlock = repo.wlock()
1013 1013 try:
1014 1014 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1015 1015 finally:
1016 1016 wlock.release()
1017 1017
1018 1018 def debugcheckstate(ui, repo):
1019 1019 """validate the correctness of the current dirstate"""
1020 1020 parent1, parent2 = repo.dirstate.parents()
1021 1021 m1 = repo[parent1].manifest()
1022 1022 m2 = repo[parent2].manifest()
1023 1023 errors = 0
1024 1024 for f in repo.dirstate:
1025 1025 state = repo.dirstate[f]
1026 1026 if state in "nr" and f not in m1:
1027 1027 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1028 1028 errors += 1
1029 1029 if state in "a" and f in m1:
1030 1030 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1031 1031 errors += 1
1032 1032 if state in "m" and f not in m1 and f not in m2:
1033 1033 ui.warn(_("%s in state %s, but not in either manifest\n") %
1034 1034 (f, state))
1035 1035 errors += 1
1036 1036 for f in m1:
1037 1037 state = repo.dirstate[f]
1038 1038 if state not in "nrm":
1039 1039 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1040 1040 errors += 1
1041 1041 if errors:
1042 1042 error = _(".hg/dirstate inconsistent with current parent's manifest")
1043 1043 raise util.Abort(error)
1044 1044
1045 1045 def showconfig(ui, repo, *values, **opts):
1046 1046 """show combined config settings from all hgrc files
1047 1047
1048 1048 With no arguments, print names and values of all config items.
1049 1049
1050 1050 With one argument of the form section.name, print just the value
1051 1051 of that config item.
1052 1052
1053 1053 With multiple arguments, print names and values of all config
1054 1054 items with matching section names.
1055 1055
1056 1056 With --debug, the source (filename and line number) is printed
1057 1057 for each config item.
1058 1058
1059 1059 Returns 0 on success.
1060 1060 """
1061 1061
1062 1062 for f in util.rcpath():
1063 1063 ui.debug(_('read config from: %s\n') % f)
1064 1064 untrusted = bool(opts.get('untrusted'))
1065 1065 if values:
1066 1066 if len([v for v in values if '.' in v]) > 1:
1067 1067 raise util.Abort(_('only one config item permitted'))
1068 1068 for section, name, value in ui.walkconfig(untrusted=untrusted):
1069 1069 sectname = section + '.' + name
1070 1070 if values:
1071 1071 for v in values:
1072 1072 if v == section:
1073 1073 ui.debug('%s: ' %
1074 1074 ui.configsource(section, name, untrusted))
1075 1075 ui.write('%s=%s\n' % (sectname, value))
1076 1076 elif v == sectname:
1077 1077 ui.debug('%s: ' %
1078 1078 ui.configsource(section, name, untrusted))
1079 1079 ui.write(value, '\n')
1080 1080 else:
1081 1081 ui.debug('%s: ' %
1082 1082 ui.configsource(section, name, untrusted))
1083 1083 ui.write('%s=%s\n' % (sectname, value))
1084 1084
1085 1085 def debugpushkey(ui, repopath, namespace, *keyinfo):
1086 1086 '''access the pushkey key/value protocol
1087 1087
1088 1088 With two args, list the keys in the given namespace.
1089 1089
1090 1090 With five args, set a key to new if it currently is set to old.
1091 1091 Reports success or failure.
1092 1092 '''
1093 1093
1094 1094 target = hg.repository(ui, repopath)
1095 1095 if keyinfo:
1096 1096 key, old, new = keyinfo
1097 1097 r = target.pushkey(namespace, key, old, new)
1098 1098 ui.status(str(r) + '\n')
1099 1099 return not(r)
1100 1100 else:
1101 1101 for k, v in target.listkeys(namespace).iteritems():
1102 1102 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1103 1103 v.encode('string-escape')))
1104 1104
1105 1105 def debugrevspec(ui, repo, expr):
1106 1106 '''parse and apply a revision specification'''
1107 1107 if ui.verbose:
1108 1108 tree = revset.parse(expr)
1109 1109 ui.note(tree, "\n")
1110 1110 func = revset.match(expr)
1111 1111 for c in func(repo, range(len(repo))):
1112 1112 ui.write("%s\n" % c)
1113 1113
1114 1114 def debugsetparents(ui, repo, rev1, rev2=None):
1115 1115 """manually set the parents of the current working directory
1116 1116
1117 1117 This is useful for writing repository conversion tools, but should
1118 1118 be used with care.
1119 1119
1120 1120 Returns 0 on success.
1121 1121 """
1122 1122
1123 1123 if not rev2:
1124 1124 rev2 = hex(nullid)
1125 1125
1126 1126 wlock = repo.wlock()
1127 1127 try:
1128 1128 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1129 1129 finally:
1130 1130 wlock.release()
1131 1131
1132 1132 def debugstate(ui, repo, nodates=None):
1133 1133 """show the contents of the current dirstate"""
1134 1134 timestr = ""
1135 1135 showdate = not nodates
1136 1136 for file_, ent in sorted(repo.dirstate._map.iteritems()):
1137 1137 if showdate:
1138 1138 if ent[3] == -1:
1139 1139 # Pad or slice to locale representation
1140 1140 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1141 1141 time.localtime(0)))
1142 1142 timestr = 'unset'
1143 1143 timestr = (timestr[:locale_len] +
1144 1144 ' ' * (locale_len - len(timestr)))
1145 1145 else:
1146 1146 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1147 1147 time.localtime(ent[3]))
1148 1148 if ent[1] & 020000:
1149 1149 mode = 'lnk'
1150 1150 else:
1151 1151 mode = '%3o' % (ent[1] & 0777)
1152 1152 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1153 1153 for f in repo.dirstate.copies():
1154 1154 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1155 1155
1156 1156 def debugsub(ui, repo, rev=None):
1157 1157 if rev == '':
1158 1158 rev = None
1159 1159 for k, v in sorted(repo[rev].substate.items()):
1160 1160 ui.write('path %s\n' % k)
1161 1161 ui.write(' source %s\n' % v[0])
1162 1162 ui.write(' revision %s\n' % v[1])
1163 1163
1164 1164 def debugdag(ui, repo, file_=None, *revs, **opts):
1165 1165 """format the changelog or an index DAG as a concise textual description
1166 1166
1167 1167 If you pass a revlog index, the revlog's DAG is emitted. If you list
1168 1168 revision numbers, they get labelled in the output as rN.
1169 1169
1170 1170 Otherwise, the changelog DAG of the current repo is emitted.
1171 1171 """
1172 1172 spaces = opts.get('spaces')
1173 1173 dots = opts.get('dots')
1174 1174 if file_:
1175 1175 rlog = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1176 1176 revs = set((int(r) for r in revs))
1177 1177 def events():
1178 1178 for r in rlog:
1179 1179 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1180 1180 if r in revs:
1181 1181 yield 'l', (r, "r%i" % r)
1182 1182 elif repo:
1183 1183 cl = repo.changelog
1184 1184 tags = opts.get('tags')
1185 1185 branches = opts.get('branches')
1186 1186 if tags:
1187 1187 labels = {}
1188 1188 for l, n in repo.tags().items():
1189 1189 labels.setdefault(cl.rev(n), []).append(l)
1190 1190 def events():
1191 1191 b = "default"
1192 1192 for r in cl:
1193 1193 if branches:
1194 1194 newb = cl.read(cl.node(r))[5]['branch']
1195 1195 if newb != b:
1196 1196 yield 'a', newb
1197 1197 b = newb
1198 1198 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1199 1199 if tags:
1200 1200 ls = labels.get(r)
1201 1201 if ls:
1202 1202 for l in ls:
1203 1203 yield 'l', (r, l)
1204 1204 else:
1205 1205 raise util.Abort(_('need repo for changelog dag'))
1206 1206
1207 1207 for line in dagparser.dagtextlines(events(),
1208 1208 addspaces=spaces,
1209 1209 wraplabels=True,
1210 1210 wrapannotations=True,
1211 1211 wrapnonlinear=dots,
1212 1212 usedots=dots,
1213 1213 maxlinewidth=70):
1214 1214 ui.write(line)
1215 1215 ui.write("\n")
1216 1216
1217 1217 def debugdata(ui, repo, file_, rev):
1218 1218 """dump the contents of a data file revision"""
1219 1219 r = None
1220 1220 if repo:
1221 1221 filelog = repo.file(file_)
1222 1222 if len(filelog):
1223 1223 r = filelog
1224 1224 if not r:
1225 1225 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
1226 1226 try:
1227 1227 ui.write(r.revision(r.lookup(rev)))
1228 1228 except KeyError:
1229 1229 raise util.Abort(_('invalid revision identifier %s') % rev)
1230 1230
1231 1231 def debugdate(ui, date, range=None, **opts):
1232 1232 """parse and display a date"""
1233 1233 if opts["extended"]:
1234 1234 d = util.parsedate(date, util.extendeddateformats)
1235 1235 else:
1236 1236 d = util.parsedate(date)
1237 1237 ui.write("internal: %s %s\n" % d)
1238 1238 ui.write("standard: %s\n" % util.datestr(d))
1239 1239 if range:
1240 1240 m = util.matchdate(range)
1241 1241 ui.write("match: %s\n" % m(d[0]))
1242 1242
1243 1243 def debugindex(ui, repo, file_):
1244 1244 """dump the contents of an index file"""
1245 1245 r = None
1246 1246 if repo:
1247 1247 filelog = repo.file(file_)
1248 1248 if len(filelog):
1249 1249 r = filelog
1250 1250 if not r:
1251 1251 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1252 1252 ui.write(" rev offset length base linkrev"
1253 1253 " nodeid p1 p2\n")
1254 1254 for i in r:
1255 1255 node = r.node(i)
1256 1256 try:
1257 1257 pp = r.parents(node)
1258 1258 except:
1259 1259 pp = [nullid, nullid]
1260 1260 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1261 1261 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1262 1262 short(node), short(pp[0]), short(pp[1])))
1263 1263
1264 1264 def debugindexdot(ui, repo, file_):
1265 1265 """dump an index DAG as a graphviz dot file"""
1266 1266 r = None
1267 1267 if repo:
1268 1268 filelog = repo.file(file_)
1269 1269 if len(filelog):
1270 1270 r = filelog
1271 1271 if not r:
1272 1272 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1273 1273 ui.write("digraph G {\n")
1274 1274 for i in r:
1275 1275 node = r.node(i)
1276 1276 pp = r.parents(node)
1277 1277 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1278 1278 if pp[1] != nullid:
1279 1279 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1280 1280 ui.write("}\n")
1281 1281
1282 1282 def debuginstall(ui):
1283 1283 '''test Mercurial installation
1284 1284
1285 1285 Returns 0 on success.
1286 1286 '''
1287 1287
1288 1288 def writetemp(contents):
1289 1289 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1290 1290 f = os.fdopen(fd, "wb")
1291 1291 f.write(contents)
1292 1292 f.close()
1293 1293 return name
1294 1294
1295 1295 problems = 0
1296 1296
1297 1297 # encoding
1298 1298 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1299 1299 try:
1300 1300 encoding.fromlocal("test")
1301 1301 except util.Abort, inst:
1302 1302 ui.write(" %s\n" % inst)
1303 1303 ui.write(_(" (check that your locale is properly set)\n"))
1304 1304 problems += 1
1305 1305
1306 1306 # compiled modules
1307 1307 ui.status(_("Checking installed modules (%s)...\n")
1308 1308 % os.path.dirname(__file__))
1309 1309 try:
1310 1310 import bdiff, mpatch, base85, osutil
1311 1311 except Exception, inst:
1312 1312 ui.write(" %s\n" % inst)
1313 1313 ui.write(_(" One or more extensions could not be found"))
1314 1314 ui.write(_(" (check that you compiled the extensions)\n"))
1315 1315 problems += 1
1316 1316
1317 1317 # templates
1318 1318 ui.status(_("Checking templates...\n"))
1319 1319 try:
1320 1320 import templater
1321 1321 templater.templater(templater.templatepath("map-cmdline.default"))
1322 1322 except Exception, inst:
1323 1323 ui.write(" %s\n" % inst)
1324 1324 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1325 1325 problems += 1
1326 1326
1327 1327 # patch
1328 1328 ui.status(_("Checking patch...\n"))
1329 1329 patchproblems = 0
1330 1330 a = "1\n2\n3\n4\n"
1331 1331 b = "1\n2\n3\ninsert\n4\n"
1332 1332 fa = writetemp(a)
1333 1333 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
1334 1334 os.path.basename(fa))
1335 1335 fd = writetemp(d)
1336 1336
1337 1337 files = {}
1338 1338 try:
1339 1339 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1340 1340 except util.Abort, e:
1341 1341 ui.write(_(" patch call failed:\n"))
1342 1342 ui.write(" " + str(e) + "\n")
1343 1343 patchproblems += 1
1344 1344 else:
1345 1345 if list(files) != [os.path.basename(fa)]:
1346 1346 ui.write(_(" unexpected patch output!\n"))
1347 1347 patchproblems += 1
1348 1348 a = open(fa).read()
1349 1349 if a != b:
1350 1350 ui.write(_(" patch test failed!\n"))
1351 1351 patchproblems += 1
1352 1352
1353 1353 if patchproblems:
1354 1354 if ui.config('ui', 'patch'):
1355 1355 ui.write(_(" (Current patch tool may be incompatible with patch,"
1356 1356 " or misconfigured. Please check your configuration"
1357 1357 " file)\n"))
1358 1358 else:
1359 1359 ui.write(_(" Internal patcher failure, please report this error"
1360 1360 " to http://mercurial.selenic.com/bts/\n"))
1361 1361 problems += patchproblems
1362 1362
1363 1363 os.unlink(fa)
1364 1364 os.unlink(fd)
1365 1365
1366 1366 # editor
1367 1367 ui.status(_("Checking commit editor...\n"))
1368 1368 editor = ui.geteditor()
1369 1369 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1370 1370 if not cmdpath:
1371 1371 if editor == 'vi':
1372 1372 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1373 1373 ui.write(_(" (specify a commit editor in your configuration"
1374 1374 " file)\n"))
1375 1375 else:
1376 1376 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1377 1377 ui.write(_(" (specify a commit editor in your configuration"
1378 1378 " file)\n"))
1379 1379 problems += 1
1380 1380
1381 1381 # check username
1382 1382 ui.status(_("Checking username...\n"))
1383 1383 try:
1384 1384 ui.username()
1385 1385 except util.Abort, e:
1386 1386 ui.write(" %s\n" % e)
1387 1387 ui.write(_(" (specify a username in your configuration file)\n"))
1388 1388 problems += 1
1389 1389
1390 1390 if not problems:
1391 1391 ui.status(_("No problems detected\n"))
1392 1392 else:
1393 1393 ui.write(_("%s problems detected,"
1394 1394 " please check your install!\n") % problems)
1395 1395
1396 1396 return problems
1397 1397
1398 1398 def debugrename(ui, repo, file1, *pats, **opts):
1399 1399 """dump rename information"""
1400 1400
1401 1401 ctx = repo[opts.get('rev')]
1402 1402 m = cmdutil.match(repo, (file1,) + pats, opts)
1403 1403 for abs in ctx.walk(m):
1404 1404 fctx = ctx[abs]
1405 1405 o = fctx.filelog().renamed(fctx.filenode())
1406 1406 rel = m.rel(abs)
1407 1407 if o:
1408 1408 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1409 1409 else:
1410 1410 ui.write(_("%s not renamed\n") % rel)
1411 1411
1412 1412 def debugwalk(ui, repo, *pats, **opts):
1413 1413 """show how files match on given patterns"""
1414 1414 m = cmdutil.match(repo, pats, opts)
1415 1415 items = list(repo.walk(m))
1416 1416 if not items:
1417 1417 return
1418 1418 fmt = 'f %%-%ds %%-%ds %%s' % (
1419 1419 max([len(abs) for abs in items]),
1420 1420 max([len(m.rel(abs)) for abs in items]))
1421 1421 for abs in items:
1422 1422 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1423 1423 ui.write("%s\n" % line.rstrip())
1424 1424
1425 1425 def diff(ui, repo, *pats, **opts):
1426 1426 """diff repository (or selected files)
1427 1427
1428 1428 Show differences between revisions for the specified files.
1429 1429
1430 1430 Differences between files are shown using the unified diff format.
1431 1431
1432 1432 NOTE: diff may generate unexpected results for merges, as it will
1433 1433 default to comparing against the working directory's first parent
1434 1434 changeset if no revisions are specified.
1435 1435
1436 1436 When two revision arguments are given, then changes are shown
1437 1437 between those revisions. If only one revision is specified then
1438 1438 that revision is compared to the working directory, and, when no
1439 1439 revisions are specified, the working directory files are compared
1440 1440 to its parent.
1441 1441
1442 1442 Alternatively you can specify -c/--change with a revision to see
1443 1443 the changes in that changeset relative to its first parent.
1444 1444
1445 1445 Without the -a/--text option, diff will avoid generating diffs of
1446 1446 files it detects as binary. With -a, diff will generate a diff
1447 1447 anyway, probably with undesirable results.
1448 1448
1449 1449 Use the -g/--git option to generate diffs in the git extended diff
1450 1450 format. For more information, read :hg:`help diffs`.
1451 1451
1452 1452 Returns 0 on success.
1453 1453 """
1454 1454
1455 1455 revs = opts.get('rev')
1456 1456 change = opts.get('change')
1457 1457 stat = opts.get('stat')
1458 1458 reverse = opts.get('reverse')
1459 1459
1460 1460 if revs and change:
1461 1461 msg = _('cannot specify --rev and --change at the same time')
1462 1462 raise util.Abort(msg)
1463 1463 elif change:
1464 1464 node2 = repo.lookup(change)
1465 1465 node1 = repo[node2].parents()[0].node()
1466 1466 else:
1467 1467 node1, node2 = cmdutil.revpair(repo, revs)
1468 1468
1469 1469 if reverse:
1470 1470 node1, node2 = node2, node1
1471 1471
1472 1472 diffopts = patch.diffopts(ui, opts)
1473 1473 m = cmdutil.match(repo, pats, opts)
1474 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat)
1474 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1475 listsubrepos=opts.get('subrepos'))
1475 1476
1476 1477 def export(ui, repo, *changesets, **opts):
1477 1478 """dump the header and diffs for one or more changesets
1478 1479
1479 1480 Print the changeset header and diffs for one or more revisions.
1480 1481
1481 1482 The information shown in the changeset header is: author, date,
1482 1483 branch name (if non-default), changeset hash, parent(s) and commit
1483 1484 comment.
1484 1485
1485 1486 NOTE: export may generate unexpected diff output for merge
1486 1487 changesets, as it will compare the merge changeset against its
1487 1488 first parent only.
1488 1489
1489 1490 Output may be to a file, in which case the name of the file is
1490 1491 given using a format string. The formatting rules are as follows:
1491 1492
1492 1493 :``%%``: literal "%" character
1493 1494 :``%H``: changeset hash (40 hexadecimal digits)
1494 1495 :``%N``: number of patches being generated
1495 1496 :``%R``: changeset revision number
1496 1497 :``%b``: basename of the exporting repository
1497 1498 :``%h``: short-form changeset hash (12 hexadecimal digits)
1498 1499 :``%n``: zero-padded sequence number, starting at 1
1499 1500 :``%r``: zero-padded changeset revision number
1500 1501
1501 1502 Without the -a/--text option, export will avoid generating diffs
1502 1503 of files it detects as binary. With -a, export will generate a
1503 1504 diff anyway, probably with undesirable results.
1504 1505
1505 1506 Use the -g/--git option to generate diffs in the git extended diff
1506 1507 format. See :hg:`help diffs` for more information.
1507 1508
1508 1509 With the --switch-parent option, the diff will be against the
1509 1510 second parent. It can be useful to review a merge.
1510 1511
1511 1512 Returns 0 on success.
1512 1513 """
1513 1514 changesets += tuple(opts.get('rev', []))
1514 1515 if not changesets:
1515 1516 raise util.Abort(_("export requires at least one changeset"))
1516 1517 revs = cmdutil.revrange(repo, changesets)
1517 1518 if len(revs) > 1:
1518 1519 ui.note(_('exporting patches:\n'))
1519 1520 else:
1520 1521 ui.note(_('exporting patch:\n'))
1521 1522 cmdutil.export(repo, revs, template=opts.get('output'),
1522 1523 switch_parent=opts.get('switch_parent'),
1523 1524 opts=patch.diffopts(ui, opts))
1524 1525
1525 1526 def forget(ui, repo, *pats, **opts):
1526 1527 """forget the specified files on the next commit
1527 1528
1528 1529 Mark the specified files so they will no longer be tracked
1529 1530 after the next commit.
1530 1531
1531 1532 This only removes files from the current branch, not from the
1532 1533 entire project history, and it does not delete them from the
1533 1534 working directory.
1534 1535
1535 1536 To undo a forget before the next commit, see :hg:`add`.
1536 1537
1537 1538 Returns 0 on success.
1538 1539 """
1539 1540
1540 1541 if not pats:
1541 1542 raise util.Abort(_('no files specified'))
1542 1543
1543 1544 m = cmdutil.match(repo, pats, opts)
1544 1545 s = repo.status(match=m, clean=True)
1545 1546 forget = sorted(s[0] + s[1] + s[3] + s[6])
1546 1547 errs = 0
1547 1548
1548 1549 for f in m.files():
1549 1550 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1550 1551 ui.warn(_('not removing %s: file is already untracked\n')
1551 1552 % m.rel(f))
1552 1553 errs = 1
1553 1554
1554 1555 for f in forget:
1555 1556 if ui.verbose or not m.exact(f):
1556 1557 ui.status(_('removing %s\n') % m.rel(f))
1557 1558
1558 1559 repo[None].remove(forget, unlink=False)
1559 1560 return errs
1560 1561
1561 1562 def grep(ui, repo, pattern, *pats, **opts):
1562 1563 """search for a pattern in specified files and revisions
1563 1564
1564 1565 Search revisions of files for a regular expression.
1565 1566
1566 1567 This command behaves differently than Unix grep. It only accepts
1567 1568 Python/Perl regexps. It searches repository history, not the
1568 1569 working directory. It always prints the revision number in which a
1569 1570 match appears.
1570 1571
1571 1572 By default, grep only prints output for the first revision of a
1572 1573 file in which it finds a match. To get it to print every revision
1573 1574 that contains a change in match status ("-" for a match that
1574 1575 becomes a non-match, or "+" for a non-match that becomes a match),
1575 1576 use the --all flag.
1576 1577
1577 1578 Returns 0 if a match is found, 1 otherwise.
1578 1579 """
1579 1580 reflags = 0
1580 1581 if opts.get('ignore_case'):
1581 1582 reflags |= re.I
1582 1583 try:
1583 1584 regexp = re.compile(pattern, reflags)
1584 1585 except Exception, inst:
1585 1586 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1586 1587 return 1
1587 1588 sep, eol = ':', '\n'
1588 1589 if opts.get('print0'):
1589 1590 sep = eol = '\0'
1590 1591
1591 1592 getfile = util.lrucachefunc(repo.file)
1592 1593
1593 1594 def matchlines(body):
1594 1595 begin = 0
1595 1596 linenum = 0
1596 1597 while True:
1597 1598 match = regexp.search(body, begin)
1598 1599 if not match:
1599 1600 break
1600 1601 mstart, mend = match.span()
1601 1602 linenum += body.count('\n', begin, mstart) + 1
1602 1603 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1603 1604 begin = body.find('\n', mend) + 1 or len(body)
1604 1605 lend = begin - 1
1605 1606 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1606 1607
1607 1608 class linestate(object):
1608 1609 def __init__(self, line, linenum, colstart, colend):
1609 1610 self.line = line
1610 1611 self.linenum = linenum
1611 1612 self.colstart = colstart
1612 1613 self.colend = colend
1613 1614
1614 1615 def __hash__(self):
1615 1616 return hash((self.linenum, self.line))
1616 1617
1617 1618 def __eq__(self, other):
1618 1619 return self.line == other.line
1619 1620
1620 1621 matches = {}
1621 1622 copies = {}
1622 1623 def grepbody(fn, rev, body):
1623 1624 matches[rev].setdefault(fn, [])
1624 1625 m = matches[rev][fn]
1625 1626 for lnum, cstart, cend, line in matchlines(body):
1626 1627 s = linestate(line, lnum, cstart, cend)
1627 1628 m.append(s)
1628 1629
1629 1630 def difflinestates(a, b):
1630 1631 sm = difflib.SequenceMatcher(None, a, b)
1631 1632 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1632 1633 if tag == 'insert':
1633 1634 for i in xrange(blo, bhi):
1634 1635 yield ('+', b[i])
1635 1636 elif tag == 'delete':
1636 1637 for i in xrange(alo, ahi):
1637 1638 yield ('-', a[i])
1638 1639 elif tag == 'replace':
1639 1640 for i in xrange(alo, ahi):
1640 1641 yield ('-', a[i])
1641 1642 for i in xrange(blo, bhi):
1642 1643 yield ('+', b[i])
1643 1644
1644 1645 def display(fn, ctx, pstates, states):
1645 1646 rev = ctx.rev()
1646 1647 datefunc = ui.quiet and util.shortdate or util.datestr
1647 1648 found = False
1648 1649 filerevmatches = {}
1649 1650 if opts.get('all'):
1650 1651 iter = difflinestates(pstates, states)
1651 1652 else:
1652 1653 iter = [('', l) for l in states]
1653 1654 for change, l in iter:
1654 1655 cols = [fn, str(rev)]
1655 1656 before, match, after = None, None, None
1656 1657 if opts.get('line_number'):
1657 1658 cols.append(str(l.linenum))
1658 1659 if opts.get('all'):
1659 1660 cols.append(change)
1660 1661 if opts.get('user'):
1661 1662 cols.append(ui.shortuser(ctx.user()))
1662 1663 if opts.get('date'):
1663 1664 cols.append(datefunc(ctx.date()))
1664 1665 if opts.get('files_with_matches'):
1665 1666 c = (fn, rev)
1666 1667 if c in filerevmatches:
1667 1668 continue
1668 1669 filerevmatches[c] = 1
1669 1670 else:
1670 1671 before = l.line[:l.colstart]
1671 1672 match = l.line[l.colstart:l.colend]
1672 1673 after = l.line[l.colend:]
1673 1674 ui.write(sep.join(cols))
1674 1675 if before is not None:
1675 1676 ui.write(sep + before)
1676 1677 ui.write(match, label='grep.match')
1677 1678 ui.write(after)
1678 1679 ui.write(eol)
1679 1680 found = True
1680 1681 return found
1681 1682
1682 1683 skip = {}
1683 1684 revfiles = {}
1684 1685 matchfn = cmdutil.match(repo, pats, opts)
1685 1686 found = False
1686 1687 follow = opts.get('follow')
1687 1688
1688 1689 def prep(ctx, fns):
1689 1690 rev = ctx.rev()
1690 1691 pctx = ctx.parents()[0]
1691 1692 parent = pctx.rev()
1692 1693 matches.setdefault(rev, {})
1693 1694 matches.setdefault(parent, {})
1694 1695 files = revfiles.setdefault(rev, [])
1695 1696 for fn in fns:
1696 1697 flog = getfile(fn)
1697 1698 try:
1698 1699 fnode = ctx.filenode(fn)
1699 1700 except error.LookupError:
1700 1701 continue
1701 1702
1702 1703 copied = flog.renamed(fnode)
1703 1704 copy = follow and copied and copied[0]
1704 1705 if copy:
1705 1706 copies.setdefault(rev, {})[fn] = copy
1706 1707 if fn in skip:
1707 1708 if copy:
1708 1709 skip[copy] = True
1709 1710 continue
1710 1711 files.append(fn)
1711 1712
1712 1713 if fn not in matches[rev]:
1713 1714 grepbody(fn, rev, flog.read(fnode))
1714 1715
1715 1716 pfn = copy or fn
1716 1717 if pfn not in matches[parent]:
1717 1718 try:
1718 1719 fnode = pctx.filenode(pfn)
1719 1720 grepbody(pfn, parent, flog.read(fnode))
1720 1721 except error.LookupError:
1721 1722 pass
1722 1723
1723 1724 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1724 1725 rev = ctx.rev()
1725 1726 parent = ctx.parents()[0].rev()
1726 1727 for fn in sorted(revfiles.get(rev, [])):
1727 1728 states = matches[rev][fn]
1728 1729 copy = copies.get(rev, {}).get(fn)
1729 1730 if fn in skip:
1730 1731 if copy:
1731 1732 skip[copy] = True
1732 1733 continue
1733 1734 pstates = matches.get(parent, {}).get(copy or fn, [])
1734 1735 if pstates or states:
1735 1736 r = display(fn, ctx, pstates, states)
1736 1737 found = found or r
1737 1738 if r and not opts.get('all'):
1738 1739 skip[fn] = True
1739 1740 if copy:
1740 1741 skip[copy] = True
1741 1742 del matches[rev]
1742 1743 del revfiles[rev]
1743 1744
1744 1745 return not found
1745 1746
1746 1747 def heads(ui, repo, *branchrevs, **opts):
1747 1748 """show current repository heads or show branch heads
1748 1749
1749 1750 With no arguments, show all repository branch heads.
1750 1751
1751 1752 Repository "heads" are changesets with no child changesets. They are
1752 1753 where development generally takes place and are the usual targets
1753 1754 for update and merge operations. Branch heads are changesets that have
1754 1755 no child changeset on the same branch.
1755 1756
1756 1757 If one or more REVs are given, only branch heads on the branches
1757 1758 associated with the specified changesets are shown.
1758 1759
1759 1760 If -c/--closed is specified, also show branch heads marked closed
1760 1761 (see :hg:`commit --close-branch`).
1761 1762
1762 1763 If STARTREV is specified, only those heads that are descendants of
1763 1764 STARTREV will be displayed.
1764 1765
1765 1766 If -t/--topo is specified, named branch mechanics will be ignored and only
1766 1767 changesets without children will be shown.
1767 1768
1768 1769 Returns 0 if matching heads are found, 1 if not.
1769 1770 """
1770 1771
1771 1772 if opts.get('rev'):
1772 1773 start = repo.lookup(opts['rev'])
1773 1774 else:
1774 1775 start = None
1775 1776
1776 1777 if opts.get('topo'):
1777 1778 heads = [repo[h] for h in repo.heads(start)]
1778 1779 else:
1779 1780 heads = []
1780 1781 for b, ls in repo.branchmap().iteritems():
1781 1782 if start is None:
1782 1783 heads += [repo[h] for h in ls]
1783 1784 continue
1784 1785 startrev = repo.changelog.rev(start)
1785 1786 descendants = set(repo.changelog.descendants(startrev))
1786 1787 descendants.add(startrev)
1787 1788 rev = repo.changelog.rev
1788 1789 heads += [repo[h] for h in ls if rev(h) in descendants]
1789 1790
1790 1791 if branchrevs:
1791 1792 decode, encode = encoding.fromlocal, encoding.tolocal
1792 1793 branches = set(repo[decode(br)].branch() for br in branchrevs)
1793 1794 heads = [h for h in heads if h.branch() in branches]
1794 1795
1795 1796 if not opts.get('closed'):
1796 1797 heads = [h for h in heads if not h.extra().get('close')]
1797 1798
1798 1799 if opts.get('active') and branchrevs:
1799 1800 dagheads = repo.heads(start)
1800 1801 heads = [h for h in heads if h.node() in dagheads]
1801 1802
1802 1803 if branchrevs:
1803 1804 haveheads = set(h.branch() for h in heads)
1804 1805 if branches - haveheads:
1805 1806 headless = ', '.join(encode(b) for b in branches - haveheads)
1806 1807 msg = _('no open branch heads found on branches %s')
1807 1808 if opts.get('rev'):
1808 1809 msg += _(' (started at %s)' % opts['rev'])
1809 1810 ui.warn((msg + '\n') % headless)
1810 1811
1811 1812 if not heads:
1812 1813 return 1
1813 1814
1814 1815 heads = sorted(heads, key=lambda x: -x.rev())
1815 1816 displayer = cmdutil.show_changeset(ui, repo, opts)
1816 1817 for ctx in heads:
1817 1818 displayer.show(ctx)
1818 1819 displayer.close()
1819 1820
1820 1821 def help_(ui, name=None, with_version=False, unknowncmd=False):
1821 1822 """show help for a given topic or a help overview
1822 1823
1823 1824 With no arguments, print a list of commands with short help messages.
1824 1825
1825 1826 Given a topic, extension, or command name, print help for that
1826 1827 topic.
1827 1828
1828 1829 Returns 0 if successful.
1829 1830 """
1830 1831 option_lists = []
1831 1832 textwidth = util.termwidth() - 2
1832 1833
1833 1834 def addglobalopts(aliases):
1834 1835 if ui.verbose:
1835 1836 option_lists.append((_("global options:"), globalopts))
1836 1837 if name == 'shortlist':
1837 1838 option_lists.append((_('use "hg help" for the full list '
1838 1839 'of commands'), ()))
1839 1840 else:
1840 1841 if name == 'shortlist':
1841 1842 msg = _('use "hg help" for the full list of commands '
1842 1843 'or "hg -v" for details')
1843 1844 elif aliases:
1844 1845 msg = _('use "hg -v help%s" to show aliases and '
1845 1846 'global options') % (name and " " + name or "")
1846 1847 else:
1847 1848 msg = _('use "hg -v help %s" to show global options') % name
1848 1849 option_lists.append((msg, ()))
1849 1850
1850 1851 def helpcmd(name):
1851 1852 if with_version:
1852 1853 version_(ui)
1853 1854 ui.write('\n')
1854 1855
1855 1856 try:
1856 1857 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1857 1858 except error.AmbiguousCommand, inst:
1858 1859 # py3k fix: except vars can't be used outside the scope of the
1859 1860 # except block, nor can be used inside a lambda. python issue4617
1860 1861 prefix = inst.args[0]
1861 1862 select = lambda c: c.lstrip('^').startswith(prefix)
1862 1863 helplist(_('list of commands:\n\n'), select)
1863 1864 return
1864 1865
1865 1866 # check if it's an invalid alias and display its error if it is
1866 1867 if getattr(entry[0], 'badalias', False):
1867 1868 if not unknowncmd:
1868 1869 entry[0](ui)
1869 1870 return
1870 1871
1871 1872 # synopsis
1872 1873 if len(entry) > 2:
1873 1874 if entry[2].startswith('hg'):
1874 1875 ui.write("%s\n" % entry[2])
1875 1876 else:
1876 1877 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
1877 1878 else:
1878 1879 ui.write('hg %s\n' % aliases[0])
1879 1880
1880 1881 # aliases
1881 1882 if not ui.quiet and len(aliases) > 1:
1882 1883 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1883 1884
1884 1885 # description
1885 1886 doc = gettext(entry[0].__doc__)
1886 1887 if not doc:
1887 1888 doc = _("(no help text available)")
1888 1889 if hasattr(entry[0], 'definition'): # aliased command
1889 1890 if entry[0].definition.startswith('!'): # shell alias
1890 1891 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
1891 1892 else:
1892 1893 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
1893 1894 if ui.quiet:
1894 1895 doc = doc.splitlines()[0]
1895 1896 keep = ui.verbose and ['verbose'] or []
1896 1897 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
1897 1898 ui.write("\n%s\n" % formatted)
1898 1899 if pruned:
1899 1900 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
1900 1901
1901 1902 if not ui.quiet:
1902 1903 # options
1903 1904 if entry[1]:
1904 1905 option_lists.append((_("options:\n"), entry[1]))
1905 1906
1906 1907 addglobalopts(False)
1907 1908
1908 1909 def helplist(header, select=None):
1909 1910 h = {}
1910 1911 cmds = {}
1911 1912 for c, e in table.iteritems():
1912 1913 f = c.split("|", 1)[0]
1913 1914 if select and not select(f):
1914 1915 continue
1915 1916 if (not select and name != 'shortlist' and
1916 1917 e[0].__module__ != __name__):
1917 1918 continue
1918 1919 if name == "shortlist" and not f.startswith("^"):
1919 1920 continue
1920 1921 f = f.lstrip("^")
1921 1922 if not ui.debugflag and f.startswith("debug"):
1922 1923 continue
1923 1924 doc = e[0].__doc__
1924 1925 if doc and 'DEPRECATED' in doc and not ui.verbose:
1925 1926 continue
1926 1927 doc = gettext(doc)
1927 1928 if not doc:
1928 1929 doc = _("(no help text available)")
1929 1930 h[f] = doc.splitlines()[0].rstrip()
1930 1931 cmds[f] = c.lstrip("^")
1931 1932
1932 1933 if not h:
1933 1934 ui.status(_('no commands defined\n'))
1934 1935 return
1935 1936
1936 1937 ui.status(header)
1937 1938 fns = sorted(h)
1938 1939 m = max(map(len, fns))
1939 1940 for f in fns:
1940 1941 if ui.verbose:
1941 1942 commands = cmds[f].replace("|",", ")
1942 1943 ui.write(" %s:\n %s\n"%(commands, h[f]))
1943 1944 else:
1944 1945 ui.write('%s\n' % (util.wrap(h[f],
1945 1946 initindent=' %-*s ' % (m, f),
1946 1947 hangindent=' ' * (m + 4))))
1947 1948
1948 1949 if not ui.quiet:
1949 1950 addglobalopts(True)
1950 1951
1951 1952 def helptopic(name):
1952 1953 for names, header, doc in help.helptable:
1953 1954 if name in names:
1954 1955 break
1955 1956 else:
1956 1957 raise error.UnknownCommand(name)
1957 1958
1958 1959 # description
1959 1960 if not doc:
1960 1961 doc = _("(no help text available)")
1961 1962 if hasattr(doc, '__call__'):
1962 1963 doc = doc()
1963 1964
1964 1965 ui.write("%s\n\n" % header)
1965 1966 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
1966 1967
1967 1968 def helpext(name):
1968 1969 try:
1969 1970 mod = extensions.find(name)
1970 1971 doc = gettext(mod.__doc__) or _('no help text available')
1971 1972 except KeyError:
1972 1973 mod = None
1973 1974 doc = extensions.disabledext(name)
1974 1975 if not doc:
1975 1976 raise error.UnknownCommand(name)
1976 1977
1977 1978 if '\n' not in doc:
1978 1979 head, tail = doc, ""
1979 1980 else:
1980 1981 head, tail = doc.split('\n', 1)
1981 1982 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
1982 1983 if tail:
1983 1984 ui.write(minirst.format(tail, textwidth))
1984 1985 ui.status('\n\n')
1985 1986
1986 1987 if mod:
1987 1988 try:
1988 1989 ct = mod.cmdtable
1989 1990 except AttributeError:
1990 1991 ct = {}
1991 1992 modcmds = set([c.split('|', 1)[0] for c in ct])
1992 1993 helplist(_('list of commands:\n\n'), modcmds.__contains__)
1993 1994 else:
1994 1995 ui.write(_('use "hg help extensions" for information on enabling '
1995 1996 'extensions\n'))
1996 1997
1997 1998 def helpextcmd(name):
1998 1999 cmd, ext, mod = extensions.disabledcmd(name, ui.config('ui', 'strict'))
1999 2000 doc = gettext(mod.__doc__).splitlines()[0]
2000 2001
2001 2002 msg = help.listexts(_("'%s' is provided by the following "
2002 2003 "extension:") % cmd, {ext: doc}, len(ext),
2003 2004 indent=4)
2004 2005 ui.write(minirst.format(msg, textwidth))
2005 2006 ui.write('\n\n')
2006 2007 ui.write(_('use "hg help extensions" for information on enabling '
2007 2008 'extensions\n'))
2008 2009
2009 2010 if name and name != 'shortlist':
2010 2011 i = None
2011 2012 if unknowncmd:
2012 2013 queries = (helpextcmd,)
2013 2014 else:
2014 2015 queries = (helptopic, helpcmd, helpext, helpextcmd)
2015 2016 for f in queries:
2016 2017 try:
2017 2018 f(name)
2018 2019 i = None
2019 2020 break
2020 2021 except error.UnknownCommand, inst:
2021 2022 i = inst
2022 2023 if i:
2023 2024 raise i
2024 2025
2025 2026 else:
2026 2027 # program name
2027 2028 if ui.verbose or with_version:
2028 2029 version_(ui)
2029 2030 else:
2030 2031 ui.status(_("Mercurial Distributed SCM\n"))
2031 2032 ui.status('\n')
2032 2033
2033 2034 # list of commands
2034 2035 if name == "shortlist":
2035 2036 header = _('basic commands:\n\n')
2036 2037 else:
2037 2038 header = _('list of commands:\n\n')
2038 2039
2039 2040 helplist(header)
2040 2041 if name != 'shortlist':
2041 2042 exts, maxlength = extensions.enabled()
2042 2043 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2043 2044 if text:
2044 2045 ui.write("\n%s\n" % minirst.format(text, textwidth))
2045 2046
2046 2047 # list all option lists
2047 2048 opt_output = []
2048 2049 multioccur = False
2049 2050 for title, options in option_lists:
2050 2051 opt_output.append(("\n%s" % title, None))
2051 2052 for option in options:
2052 2053 if len(option) == 5:
2053 2054 shortopt, longopt, default, desc, optlabel = option
2054 2055 else:
2055 2056 shortopt, longopt, default, desc = option
2056 2057 optlabel = _("VALUE") # default label
2057 2058
2058 2059 if _("DEPRECATED") in desc and not ui.verbose:
2059 2060 continue
2060 2061 if isinstance(default, list):
2061 2062 numqualifier = " %s [+]" % optlabel
2062 2063 multioccur = True
2063 2064 elif (default is not None) and not isinstance(default, bool):
2064 2065 numqualifier = " %s" % optlabel
2065 2066 else:
2066 2067 numqualifier = ""
2067 2068 opt_output.append(("%2s%s" %
2068 2069 (shortopt and "-%s" % shortopt,
2069 2070 longopt and " --%s%s" %
2070 2071 (longopt, numqualifier)),
2071 2072 "%s%s" % (desc,
2072 2073 default
2073 2074 and _(" (default: %s)") % default
2074 2075 or "")))
2075 2076 if multioccur:
2076 2077 msg = _("\n[+] marked option can be specified multiple times")
2077 2078 if ui.verbose and name != 'shortlist':
2078 2079 opt_output.append((msg, None))
2079 2080 else:
2080 2081 opt_output.insert(-1, (msg, None))
2081 2082
2082 2083 if not name:
2083 2084 ui.write(_("\nadditional help topics:\n\n"))
2084 2085 topics = []
2085 2086 for names, header, doc in help.helptable:
2086 2087 topics.append((sorted(names, key=len, reverse=True)[0], header))
2087 2088 topics_len = max([len(s[0]) for s in topics])
2088 2089 for t, desc in topics:
2089 2090 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2090 2091
2091 2092 if opt_output:
2092 2093 colwidth = encoding.colwidth
2093 2094 # normalize: (opt or message, desc or None, width of opt)
2094 2095 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2095 2096 for opt, desc in opt_output]
2096 2097 hanging = max([e[2] for e in entries])
2097 2098 for opt, desc, width in entries:
2098 2099 if desc:
2099 2100 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2100 2101 hangindent = ' ' * (hanging + 3)
2101 2102 ui.write('%s\n' % (util.wrap(desc,
2102 2103 initindent=initindent,
2103 2104 hangindent=hangindent)))
2104 2105 else:
2105 2106 ui.write("%s\n" % opt)
2106 2107
2107 2108 def identify(ui, repo, source=None,
2108 2109 rev=None, num=None, id=None, branch=None, tags=None):
2109 2110 """identify the working copy or specified revision
2110 2111
2111 2112 With no revision, print a summary of the current state of the
2112 2113 repository.
2113 2114
2114 2115 Specifying a path to a repository root or Mercurial bundle will
2115 2116 cause lookup to operate on that repository/bundle.
2116 2117
2117 2118 This summary identifies the repository state using one or two
2118 2119 parent hash identifiers, followed by a "+" if there are
2119 2120 uncommitted changes in the working directory, a list of tags for
2120 2121 this revision and a branch name for non-default branches.
2121 2122
2122 2123 Returns 0 if successful.
2123 2124 """
2124 2125
2125 2126 if not repo and not source:
2126 2127 raise util.Abort(_("there is no Mercurial repository here "
2127 2128 "(.hg not found)"))
2128 2129
2129 2130 hexfunc = ui.debugflag and hex or short
2130 2131 default = not (num or id or branch or tags)
2131 2132 output = []
2132 2133
2133 2134 revs = []
2134 2135 if source:
2135 2136 source, branches = hg.parseurl(ui.expandpath(source))
2136 2137 repo = hg.repository(ui, source)
2137 2138 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2138 2139
2139 2140 if not repo.local():
2140 2141 if not rev and revs:
2141 2142 rev = revs[0]
2142 2143 if not rev:
2143 2144 rev = "tip"
2144 2145 if num or branch or tags:
2145 2146 raise util.Abort(
2146 2147 "can't query remote revision number, branch, or tags")
2147 2148 output = [hexfunc(repo.lookup(rev))]
2148 2149 elif not rev:
2149 2150 ctx = repo[None]
2150 2151 parents = ctx.parents()
2151 2152 changed = False
2152 2153 if default or id or num:
2153 2154 changed = util.any(repo.status())
2154 2155 if default or id:
2155 2156 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
2156 2157 (changed) and "+" or "")]
2157 2158 if num:
2158 2159 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
2159 2160 (changed) and "+" or ""))
2160 2161 else:
2161 2162 ctx = repo[rev]
2162 2163 if default or id:
2163 2164 output = [hexfunc(ctx.node())]
2164 2165 if num:
2165 2166 output.append(str(ctx.rev()))
2166 2167
2167 2168 if repo.local() and default and not ui.quiet:
2168 2169 b = encoding.tolocal(ctx.branch())
2169 2170 if b != 'default':
2170 2171 output.append("(%s)" % b)
2171 2172
2172 2173 # multiple tags for a single parent separated by '/'
2173 2174 t = "/".join(ctx.tags())
2174 2175 if t:
2175 2176 output.append(t)
2176 2177
2177 2178 if branch:
2178 2179 output.append(encoding.tolocal(ctx.branch()))
2179 2180
2180 2181 if tags:
2181 2182 output.extend(ctx.tags())
2182 2183
2183 2184 ui.write("%s\n" % ' '.join(output))
2184 2185
2185 2186 def import_(ui, repo, patch1, *patches, **opts):
2186 2187 """import an ordered set of patches
2187 2188
2188 2189 Import a list of patches and commit them individually (unless
2189 2190 --no-commit is specified).
2190 2191
2191 2192 If there are outstanding changes in the working directory, import
2192 2193 will abort unless given the -f/--force flag.
2193 2194
2194 2195 You can import a patch straight from a mail message. Even patches
2195 2196 as attachments work (to use the body part, it must have type
2196 2197 text/plain or text/x-patch). From and Subject headers of email
2197 2198 message are used as default committer and commit message. All
2198 2199 text/plain body parts before first diff are added to commit
2199 2200 message.
2200 2201
2201 2202 If the imported patch was generated by :hg:`export`, user and
2202 2203 description from patch override values from message headers and
2203 2204 body. Values given on command line with -m/--message and -u/--user
2204 2205 override these.
2205 2206
2206 2207 If --exact is specified, import will set the working directory to
2207 2208 the parent of each patch before applying it, and will abort if the
2208 2209 resulting changeset has a different ID than the one recorded in
2209 2210 the patch. This may happen due to character set problems or other
2210 2211 deficiencies in the text patch format.
2211 2212
2212 2213 With -s/--similarity, hg will attempt to discover renames and
2213 2214 copies in the patch in the same way as 'addremove'.
2214 2215
2215 2216 To read a patch from standard input, use "-" as the patch name. If
2216 2217 a URL is specified, the patch will be downloaded from it.
2217 2218 See :hg:`help dates` for a list of formats valid for -d/--date.
2218 2219
2219 2220 Returns 0 on success.
2220 2221 """
2221 2222 patches = (patch1,) + patches
2222 2223
2223 2224 date = opts.get('date')
2224 2225 if date:
2225 2226 opts['date'] = util.parsedate(date)
2226 2227
2227 2228 try:
2228 2229 sim = float(opts.get('similarity') or 0)
2229 2230 except ValueError:
2230 2231 raise util.Abort(_('similarity must be a number'))
2231 2232 if sim < 0 or sim > 100:
2232 2233 raise util.Abort(_('similarity must be between 0 and 100'))
2233 2234
2234 2235 if opts.get('exact') or not opts.get('force'):
2235 2236 cmdutil.bail_if_changed(repo)
2236 2237
2237 2238 d = opts["base"]
2238 2239 strip = opts["strip"]
2239 2240 wlock = lock = None
2240 2241
2241 2242 def tryone(ui, hunk):
2242 2243 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2243 2244 patch.extract(ui, hunk)
2244 2245
2245 2246 if not tmpname:
2246 2247 return None
2247 2248 commitid = _('to working directory')
2248 2249
2249 2250 try:
2250 2251 cmdline_message = cmdutil.logmessage(opts)
2251 2252 if cmdline_message:
2252 2253 # pickup the cmdline msg
2253 2254 message = cmdline_message
2254 2255 elif message:
2255 2256 # pickup the patch msg
2256 2257 message = message.strip()
2257 2258 else:
2258 2259 # launch the editor
2259 2260 message = None
2260 2261 ui.debug('message:\n%s\n' % message)
2261 2262
2262 2263 wp = repo.parents()
2263 2264 if opts.get('exact'):
2264 2265 if not nodeid or not p1:
2265 2266 raise util.Abort(_('not a Mercurial patch'))
2266 2267 p1 = repo.lookup(p1)
2267 2268 p2 = repo.lookup(p2 or hex(nullid))
2268 2269
2269 2270 if p1 != wp[0].node():
2270 2271 hg.clean(repo, p1)
2271 2272 repo.dirstate.setparents(p1, p2)
2272 2273 elif p2:
2273 2274 try:
2274 2275 p1 = repo.lookup(p1)
2275 2276 p2 = repo.lookup(p2)
2276 2277 if p1 == wp[0].node():
2277 2278 repo.dirstate.setparents(p1, p2)
2278 2279 except error.RepoError:
2279 2280 pass
2280 2281 if opts.get('exact') or opts.get('import_branch'):
2281 2282 repo.dirstate.setbranch(branch or 'default')
2282 2283
2283 2284 files = {}
2284 2285 try:
2285 2286 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2286 2287 files=files, eolmode=None)
2287 2288 finally:
2288 2289 files = patch.updatedir(ui, repo, files,
2289 2290 similarity=sim / 100.0)
2290 2291 if not opts.get('no_commit'):
2291 2292 if opts.get('exact'):
2292 2293 m = None
2293 2294 else:
2294 2295 m = cmdutil.matchfiles(repo, files or [])
2295 2296 n = repo.commit(message, opts.get('user') or user,
2296 2297 opts.get('date') or date, match=m,
2297 2298 editor=cmdutil.commiteditor)
2298 2299 if opts.get('exact'):
2299 2300 if hex(n) != nodeid:
2300 2301 repo.rollback()
2301 2302 raise util.Abort(_('patch is damaged'
2302 2303 ' or loses information'))
2303 2304 # Force a dirstate write so that the next transaction
2304 2305 # backups an up-do-date file.
2305 2306 repo.dirstate.write()
2306 2307 if n:
2307 2308 commitid = short(n)
2308 2309
2309 2310 return commitid
2310 2311 finally:
2311 2312 os.unlink(tmpname)
2312 2313
2313 2314 try:
2314 2315 wlock = repo.wlock()
2315 2316 lock = repo.lock()
2316 2317 lastcommit = None
2317 2318 for p in patches:
2318 2319 pf = os.path.join(d, p)
2319 2320
2320 2321 if pf == '-':
2321 2322 ui.status(_("applying patch from stdin\n"))
2322 2323 pf = sys.stdin
2323 2324 else:
2324 2325 ui.status(_("applying %s\n") % p)
2325 2326 pf = url.open(ui, pf)
2326 2327
2327 2328 haspatch = False
2328 2329 for hunk in patch.split(pf):
2329 2330 commitid = tryone(ui, hunk)
2330 2331 if commitid:
2331 2332 haspatch = True
2332 2333 if lastcommit:
2333 2334 ui.status(_('applied %s\n') % lastcommit)
2334 2335 lastcommit = commitid
2335 2336
2336 2337 if not haspatch:
2337 2338 raise util.Abort(_('no diffs found'))
2338 2339
2339 2340 finally:
2340 2341 release(lock, wlock)
2341 2342
2342 2343 def incoming(ui, repo, source="default", **opts):
2343 2344 """show new changesets found in source
2344 2345
2345 2346 Show new changesets found in the specified path/URL or the default
2346 2347 pull location. These are the changesets that would have been pulled
2347 2348 if a pull at the time you issued this command.
2348 2349
2349 2350 For remote repository, using --bundle avoids downloading the
2350 2351 changesets twice if the incoming is followed by a pull.
2351 2352
2352 2353 See pull for valid source format details.
2353 2354
2354 2355 Returns 0 if there are incoming changes, 1 otherwise.
2355 2356 """
2356 2357 limit = cmdutil.loglimit(opts)
2357 2358 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2358 2359 other = hg.repository(hg.remoteui(repo, opts), source)
2359 2360 ui.status(_('comparing with %s\n') % url.hidepassword(source))
2360 2361 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2361 2362 if revs:
2362 2363 revs = [other.lookup(rev) for rev in revs]
2363 2364
2364 2365 tmp = discovery.findcommonincoming(repo, other, heads=revs,
2365 2366 force=opts.get('force'))
2366 2367 common, incoming, rheads = tmp
2367 2368 if not incoming:
2368 2369 try:
2369 2370 os.unlink(opts["bundle"])
2370 2371 except:
2371 2372 pass
2372 2373 ui.status(_("no changes found\n"))
2373 2374 return 1
2374 2375
2375 2376 cleanup = None
2376 2377 try:
2377 2378 fname = opts["bundle"]
2378 2379 if fname or not other.local():
2379 2380 # create a bundle (uncompressed if other repo is not local)
2380 2381
2381 2382 if revs is None and other.capable('changegroupsubset'):
2382 2383 revs = rheads
2383 2384
2384 2385 if revs is None:
2385 2386 cg = other.changegroup(incoming, "incoming")
2386 2387 else:
2387 2388 cg = other.changegroupsubset(incoming, revs, 'incoming')
2388 2389 bundletype = other.local() and "HG10BZ" or "HG10UN"
2389 2390 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
2390 2391 # keep written bundle?
2391 2392 if opts["bundle"]:
2392 2393 cleanup = None
2393 2394 if not other.local():
2394 2395 # use the created uncompressed bundlerepo
2395 2396 other = bundlerepo.bundlerepository(ui, repo.root, fname)
2396 2397
2397 2398 o = other.changelog.nodesbetween(incoming, revs)[0]
2398 2399 if opts.get('newest_first'):
2399 2400 o.reverse()
2400 2401 displayer = cmdutil.show_changeset(ui, other, opts)
2401 2402 count = 0
2402 2403 for n in o:
2403 2404 if limit is not None and count >= limit:
2404 2405 break
2405 2406 parents = [p for p in other.changelog.parents(n) if p != nullid]
2406 2407 if opts.get('no_merges') and len(parents) == 2:
2407 2408 continue
2408 2409 count += 1
2409 2410 displayer.show(other[n])
2410 2411 displayer.close()
2411 2412 finally:
2412 2413 if hasattr(other, 'close'):
2413 2414 other.close()
2414 2415 if cleanup:
2415 2416 os.unlink(cleanup)
2416 2417
2417 2418 def init(ui, dest=".", **opts):
2418 2419 """create a new repository in the given directory
2419 2420
2420 2421 Initialize a new repository in the given directory. If the given
2421 2422 directory does not exist, it will be created.
2422 2423
2423 2424 If no directory is given, the current directory is used.
2424 2425
2425 2426 It is possible to specify an ``ssh://`` URL as the destination.
2426 2427 See :hg:`help urls` for more information.
2427 2428
2428 2429 Returns 0 on success.
2429 2430 """
2430 2431 hg.repository(hg.remoteui(ui, opts), dest, create=1)
2431 2432
2432 2433 def locate(ui, repo, *pats, **opts):
2433 2434 """locate files matching specific patterns
2434 2435
2435 2436 Print files under Mercurial control in the working directory whose
2436 2437 names match the given patterns.
2437 2438
2438 2439 By default, this command searches all directories in the working
2439 2440 directory. To search just the current directory and its
2440 2441 subdirectories, use "--include .".
2441 2442
2442 2443 If no patterns are given to match, this command prints the names
2443 2444 of all files under Mercurial control in the working directory.
2444 2445
2445 2446 If you want to feed the output of this command into the "xargs"
2446 2447 command, use the -0 option to both this command and "xargs". This
2447 2448 will avoid the problem of "xargs" treating single filenames that
2448 2449 contain whitespace as multiple filenames.
2449 2450
2450 2451 Returns 0 if a match is found, 1 otherwise.
2451 2452 """
2452 2453 end = opts.get('print0') and '\0' or '\n'
2453 2454 rev = opts.get('rev') or None
2454 2455
2455 2456 ret = 1
2456 2457 m = cmdutil.match(repo, pats, opts, default='relglob')
2457 2458 m.bad = lambda x, y: False
2458 2459 for abs in repo[rev].walk(m):
2459 2460 if not rev and abs not in repo.dirstate:
2460 2461 continue
2461 2462 if opts.get('fullpath'):
2462 2463 ui.write(repo.wjoin(abs), end)
2463 2464 else:
2464 2465 ui.write(((pats and m.rel(abs)) or abs), end)
2465 2466 ret = 0
2466 2467
2467 2468 return ret
2468 2469
2469 2470 def log(ui, repo, *pats, **opts):
2470 2471 """show revision history of entire repository or files
2471 2472
2472 2473 Print the revision history of the specified files or the entire
2473 2474 project.
2474 2475
2475 2476 File history is shown without following rename or copy history of
2476 2477 files. Use -f/--follow with a filename to follow history across
2477 2478 renames and copies. --follow without a filename will only show
2478 2479 ancestors or descendants of the starting revision. --follow-first
2479 2480 only follows the first parent of merge revisions.
2480 2481
2481 2482 If no revision range is specified, the default is tip:0 unless
2482 2483 --follow is set, in which case the working directory parent is
2483 2484 used as the starting revision. You can specify a revision set for
2484 2485 log, see :hg:`help revsets` for more information.
2485 2486
2486 2487 See :hg:`help dates` for a list of formats valid for -d/--date.
2487 2488
2488 2489 By default this command prints revision number and changeset id,
2489 2490 tags, non-trivial parents, user, date and time, and a summary for
2490 2491 each commit. When the -v/--verbose switch is used, the list of
2491 2492 changed files and full commit message are shown.
2492 2493
2493 2494 NOTE: log -p/--patch may generate unexpected diff output for merge
2494 2495 changesets, as it will only compare the merge changeset against
2495 2496 its first parent. Also, only files different from BOTH parents
2496 2497 will appear in files:.
2497 2498
2498 2499 Returns 0 on success.
2499 2500 """
2500 2501
2501 2502 matchfn = cmdutil.match(repo, pats, opts)
2502 2503 limit = cmdutil.loglimit(opts)
2503 2504 count = 0
2504 2505
2505 2506 endrev = None
2506 2507 if opts.get('copies') and opts.get('rev'):
2507 2508 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2508 2509
2509 2510 df = False
2510 2511 if opts["date"]:
2511 2512 df = util.matchdate(opts["date"])
2512 2513
2513 2514 branches = opts.get('branch', []) + opts.get('only_branch', [])
2514 2515 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2515 2516
2516 2517 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2517 2518 def prep(ctx, fns):
2518 2519 rev = ctx.rev()
2519 2520 parents = [p for p in repo.changelog.parentrevs(rev)
2520 2521 if p != nullrev]
2521 2522 if opts.get('no_merges') and len(parents) == 2:
2522 2523 return
2523 2524 if opts.get('only_merges') and len(parents) != 2:
2524 2525 return
2525 2526 if opts.get('branch') and ctx.branch() not in opts['branch']:
2526 2527 return
2527 2528 if df and not df(ctx.date()[0]):
2528 2529 return
2529 2530 if opts['user'] and not [k for k in opts['user'] if k in ctx.user()]:
2530 2531 return
2531 2532 if opts.get('keyword'):
2532 2533 for k in [kw.lower() for kw in opts['keyword']]:
2533 2534 if (k in ctx.user().lower() or
2534 2535 k in ctx.description().lower() or
2535 2536 k in " ".join(ctx.files()).lower()):
2536 2537 break
2537 2538 else:
2538 2539 return
2539 2540
2540 2541 copies = None
2541 2542 if opts.get('copies') and rev:
2542 2543 copies = []
2543 2544 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2544 2545 for fn in ctx.files():
2545 2546 rename = getrenamed(fn, rev)
2546 2547 if rename:
2547 2548 copies.append((fn, rename[0]))
2548 2549
2549 2550 revmatchfn = None
2550 2551 if opts.get('patch') or opts.get('stat'):
2551 2552 revmatchfn = cmdutil.match(repo, fns, default='path')
2552 2553
2553 2554 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2554 2555
2555 2556 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2556 2557 if count == limit:
2557 2558 break
2558 2559 if displayer.flush(ctx.rev()):
2559 2560 count += 1
2560 2561 displayer.close()
2561 2562
2562 2563 def manifest(ui, repo, node=None, rev=None):
2563 2564 """output the current or given revision of the project manifest
2564 2565
2565 2566 Print a list of version controlled files for the given revision.
2566 2567 If no revision is given, the first parent of the working directory
2567 2568 is used, or the null revision if no revision is checked out.
2568 2569
2569 2570 With -v, print file permissions, symlink and executable bits.
2570 2571 With --debug, print file revision hashes.
2571 2572
2572 2573 Returns 0 on success.
2573 2574 """
2574 2575
2575 2576 if rev and node:
2576 2577 raise util.Abort(_("please specify just one revision"))
2577 2578
2578 2579 if not node:
2579 2580 node = rev
2580 2581
2581 2582 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2582 2583 ctx = repo[node]
2583 2584 for f in ctx:
2584 2585 if ui.debugflag:
2585 2586 ui.write("%40s " % hex(ctx.manifest()[f]))
2586 2587 if ui.verbose:
2587 2588 ui.write(decor[ctx.flags(f)])
2588 2589 ui.write("%s\n" % f)
2589 2590
2590 2591 def merge(ui, repo, node=None, **opts):
2591 2592 """merge working directory with another revision
2592 2593
2593 2594 The current working directory is updated with all changes made in
2594 2595 the requested revision since the last common predecessor revision.
2595 2596
2596 2597 Files that changed between either parent are marked as changed for
2597 2598 the next commit and a commit must be performed before any further
2598 2599 updates to the repository are allowed. The next commit will have
2599 2600 two parents.
2600 2601
2601 2602 If no revision is specified, the working directory's parent is a
2602 2603 head revision, and the current branch contains exactly one other
2603 2604 head, the other head is merged with by default. Otherwise, an
2604 2605 explicit revision with which to merge with must be provided.
2605 2606
2606 2607 To undo an uncommitted merge, use :hg:`update --clean .` which
2607 2608 will check out a clean copy of the original merge parent, losing
2608 2609 all changes.
2609 2610
2610 2611 Returns 0 on success, 1 if there are unresolved files.
2611 2612 """
2612 2613
2613 2614 if opts.get('rev') and node:
2614 2615 raise util.Abort(_("please specify just one revision"))
2615 2616 if not node:
2616 2617 node = opts.get('rev')
2617 2618
2618 2619 if not node:
2619 2620 branch = repo.changectx(None).branch()
2620 2621 bheads = repo.branchheads(branch)
2621 2622 if len(bheads) > 2:
2622 2623 raise util.Abort(_(
2623 2624 'branch \'%s\' has %d heads - '
2624 2625 'please merge with an explicit rev\n'
2625 2626 '(run \'hg heads .\' to see heads)')
2626 2627 % (branch, len(bheads)))
2627 2628
2628 2629 parent = repo.dirstate.parents()[0]
2629 2630 if len(bheads) == 1:
2630 2631 if len(repo.heads()) > 1:
2631 2632 raise util.Abort(_(
2632 2633 'branch \'%s\' has one head - '
2633 2634 'please merge with an explicit rev\n'
2634 2635 '(run \'hg heads\' to see all heads)')
2635 2636 % branch)
2636 2637 msg = _('there is nothing to merge')
2637 2638 if parent != repo.lookup(repo[None].branch()):
2638 2639 msg = _('%s - use "hg update" instead') % msg
2639 2640 raise util.Abort(msg)
2640 2641
2641 2642 if parent not in bheads:
2642 2643 raise util.Abort(_('working dir not at a head rev - '
2643 2644 'use "hg update" or merge with an explicit rev'))
2644 2645 node = parent == bheads[0] and bheads[-1] or bheads[0]
2645 2646
2646 2647 if opts.get('preview'):
2647 2648 # find nodes that are ancestors of p2 but not of p1
2648 2649 p1 = repo.lookup('.')
2649 2650 p2 = repo.lookup(node)
2650 2651 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2651 2652
2652 2653 displayer = cmdutil.show_changeset(ui, repo, opts)
2653 2654 for node in nodes:
2654 2655 displayer.show(repo[node])
2655 2656 displayer.close()
2656 2657 return 0
2657 2658
2658 2659 return hg.merge(repo, node, force=opts.get('force'))
2659 2660
2660 2661 def outgoing(ui, repo, dest=None, **opts):
2661 2662 """show changesets not found in the destination
2662 2663
2663 2664 Show changesets not found in the specified destination repository
2664 2665 or the default push location. These are the changesets that would
2665 2666 be pushed if a push was requested.
2666 2667
2667 2668 See pull for details of valid destination formats.
2668 2669
2669 2670 Returns 0 if there are outgoing changes, 1 otherwise.
2670 2671 """
2671 2672 limit = cmdutil.loglimit(opts)
2672 2673 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2673 2674 dest, branches = hg.parseurl(dest, opts.get('branch'))
2674 2675 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2675 2676 if revs:
2676 2677 revs = [repo.lookup(rev) for rev in revs]
2677 2678
2678 2679 other = hg.repository(hg.remoteui(repo, opts), dest)
2679 2680 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2680 2681 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
2681 2682 if not o:
2682 2683 ui.status(_("no changes found\n"))
2683 2684 return 1
2684 2685 o = repo.changelog.nodesbetween(o, revs)[0]
2685 2686 if opts.get('newest_first'):
2686 2687 o.reverse()
2687 2688 displayer = cmdutil.show_changeset(ui, repo, opts)
2688 2689 count = 0
2689 2690 for n in o:
2690 2691 if limit is not None and count >= limit:
2691 2692 break
2692 2693 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2693 2694 if opts.get('no_merges') and len(parents) == 2:
2694 2695 continue
2695 2696 count += 1
2696 2697 displayer.show(repo[n])
2697 2698 displayer.close()
2698 2699
2699 2700 def parents(ui, repo, file_=None, **opts):
2700 2701 """show the parents of the working directory or revision
2701 2702
2702 2703 Print the working directory's parent revisions. If a revision is
2703 2704 given via -r/--rev, the parent of that revision will be printed.
2704 2705 If a file argument is given, the revision in which the file was
2705 2706 last changed (before the working directory revision or the
2706 2707 argument to --rev if given) is printed.
2707 2708
2708 2709 Returns 0 on success.
2709 2710 """
2710 2711 rev = opts.get('rev')
2711 2712 if rev:
2712 2713 ctx = repo[rev]
2713 2714 else:
2714 2715 ctx = repo[None]
2715 2716
2716 2717 if file_:
2717 2718 m = cmdutil.match(repo, (file_,), opts)
2718 2719 if m.anypats() or len(m.files()) != 1:
2719 2720 raise util.Abort(_('can only specify an explicit filename'))
2720 2721 file_ = m.files()[0]
2721 2722 filenodes = []
2722 2723 for cp in ctx.parents():
2723 2724 if not cp:
2724 2725 continue
2725 2726 try:
2726 2727 filenodes.append(cp.filenode(file_))
2727 2728 except error.LookupError:
2728 2729 pass
2729 2730 if not filenodes:
2730 2731 raise util.Abort(_("'%s' not found in manifest!") % file_)
2731 2732 fl = repo.file(file_)
2732 2733 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2733 2734 else:
2734 2735 p = [cp.node() for cp in ctx.parents()]
2735 2736
2736 2737 displayer = cmdutil.show_changeset(ui, repo, opts)
2737 2738 for n in p:
2738 2739 if n != nullid:
2739 2740 displayer.show(repo[n])
2740 2741 displayer.close()
2741 2742
2742 2743 def paths(ui, repo, search=None):
2743 2744 """show aliases for remote repositories
2744 2745
2745 2746 Show definition of symbolic path name NAME. If no name is given,
2746 2747 show definition of all available names.
2747 2748
2748 2749 Path names are defined in the [paths] section of your
2749 2750 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
2750 2751 repository, ``.hg/hgrc`` is used, too.
2751 2752
2752 2753 The path names ``default`` and ``default-push`` have a special
2753 2754 meaning. When performing a push or pull operation, they are used
2754 2755 as fallbacks if no location is specified on the command-line.
2755 2756 When ``default-push`` is set, it will be used for push and
2756 2757 ``default`` will be used for pull; otherwise ``default`` is used
2757 2758 as the fallback for both. When cloning a repository, the clone
2758 2759 source is written as ``default`` in ``.hg/hgrc``. Note that
2759 2760 ``default`` and ``default-push`` apply to all inbound (e.g.
2760 2761 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2761 2762 :hg:`bundle`) operations.
2762 2763
2763 2764 See :hg:`help urls` for more information.
2764 2765
2765 2766 Returns 0 on success.
2766 2767 """
2767 2768 if search:
2768 2769 for name, path in ui.configitems("paths"):
2769 2770 if name == search:
2770 2771 ui.write("%s\n" % url.hidepassword(path))
2771 2772 return
2772 2773 ui.warn(_("not found!\n"))
2773 2774 return 1
2774 2775 else:
2775 2776 for name, path in ui.configitems("paths"):
2776 2777 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2777 2778
2778 2779 def postincoming(ui, repo, modheads, optupdate, checkout):
2779 2780 if modheads == 0:
2780 2781 return
2781 2782 if optupdate:
2782 2783 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2783 2784 return hg.update(repo, checkout)
2784 2785 else:
2785 2786 ui.status(_("not updating, since new heads added\n"))
2786 2787 if modheads > 1:
2787 2788 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2788 2789 else:
2789 2790 ui.status(_("(run 'hg update' to get a working copy)\n"))
2790 2791
2791 2792 def pull(ui, repo, source="default", **opts):
2792 2793 """pull changes from the specified source
2793 2794
2794 2795 Pull changes from a remote repository to a local one.
2795 2796
2796 2797 This finds all changes from the repository at the specified path
2797 2798 or URL and adds them to a local repository (the current one unless
2798 2799 -R is specified). By default, this does not update the copy of the
2799 2800 project in the working directory.
2800 2801
2801 2802 Use :hg:`incoming` if you want to see what would have been added
2802 2803 by a pull at the time you issued this command. If you then decide
2803 2804 to add those changes to the repository, you should use :hg:`pull
2804 2805 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
2805 2806
2806 2807 If SOURCE is omitted, the 'default' path will be used.
2807 2808 See :hg:`help urls` for more information.
2808 2809
2809 2810 Returns 0 on success, 1 if an update had unresolved files.
2810 2811 """
2811 2812 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2812 2813 other = hg.repository(hg.remoteui(repo, opts), source)
2813 2814 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2814 2815 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2815 2816 if revs:
2816 2817 try:
2817 2818 revs = [other.lookup(rev) for rev in revs]
2818 2819 except error.CapabilityError:
2819 2820 err = _("other repository doesn't support revision lookup, "
2820 2821 "so a rev cannot be specified.")
2821 2822 raise util.Abort(err)
2822 2823
2823 2824 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2824 2825 if checkout:
2825 2826 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2826 2827 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2827 2828
2828 2829 def push(ui, repo, dest=None, **opts):
2829 2830 """push changes to the specified destination
2830 2831
2831 2832 Push changesets from the local repository to the specified
2832 2833 destination.
2833 2834
2834 2835 This operation is symmetrical to pull: it is identical to a pull
2835 2836 in the destination repository from the current one.
2836 2837
2837 2838 By default, push will not allow creation of new heads at the
2838 2839 destination, since multiple heads would make it unclear which head
2839 2840 to use. In this situation, it is recommended to pull and merge
2840 2841 before pushing.
2841 2842
2842 2843 Use --new-branch if you want to allow push to create a new named
2843 2844 branch that is not present at the destination. This allows you to
2844 2845 only create a new branch without forcing other changes.
2845 2846
2846 2847 Use -f/--force to override the default behavior and push all
2847 2848 changesets on all branches.
2848 2849
2849 2850 If -r/--rev is used, the specified revision and all its ancestors
2850 2851 will be pushed to the remote repository.
2851 2852
2852 2853 Please see :hg:`help urls` for important details about ``ssh://``
2853 2854 URLs. If DESTINATION is omitted, a default path will be used.
2854 2855
2855 2856 Returns 0 if push was successful, 1 if nothing to push.
2856 2857 """
2857 2858 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2858 2859 dest, branches = hg.parseurl(dest, opts.get('branch'))
2859 2860 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2860 2861 other = hg.repository(hg.remoteui(repo, opts), dest)
2861 2862 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2862 2863 if revs:
2863 2864 revs = [repo.lookup(rev) for rev in revs]
2864 2865
2865 2866 # push subrepos depth-first for coherent ordering
2866 2867 c = repo['']
2867 2868 subs = c.substate # only repos that are committed
2868 2869 for s in sorted(subs):
2869 2870 if not c.sub(s).push(opts.get('force')):
2870 2871 return False
2871 2872
2872 2873 r = repo.push(other, opts.get('force'), revs=revs,
2873 2874 newbranch=opts.get('new_branch'))
2874 2875 return r == 0
2875 2876
2876 2877 def recover(ui, repo):
2877 2878 """roll back an interrupted transaction
2878 2879
2879 2880 Recover from an interrupted commit or pull.
2880 2881
2881 2882 This command tries to fix the repository status after an
2882 2883 interrupted operation. It should only be necessary when Mercurial
2883 2884 suggests it.
2884 2885
2885 2886 Returns 0 if successful, 1 if nothing to recover or verify fails.
2886 2887 """
2887 2888 if repo.recover():
2888 2889 return hg.verify(repo)
2889 2890 return 1
2890 2891
2891 2892 def remove(ui, repo, *pats, **opts):
2892 2893 """remove the specified files on the next commit
2893 2894
2894 2895 Schedule the indicated files for removal from the repository.
2895 2896
2896 2897 This only removes files from the current branch, not from the
2897 2898 entire project history. -A/--after can be used to remove only
2898 2899 files that have already been deleted, -f/--force can be used to
2899 2900 force deletion, and -Af can be used to remove files from the next
2900 2901 revision without deleting them from the working directory.
2901 2902
2902 2903 The following table details the behavior of remove for different
2903 2904 file states (columns) and option combinations (rows). The file
2904 2905 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
2905 2906 reported by :hg:`status`). The actions are Warn, Remove (from
2906 2907 branch) and Delete (from disk)::
2907 2908
2908 2909 A C M !
2909 2910 none W RD W R
2910 2911 -f R RD RD R
2911 2912 -A W W W R
2912 2913 -Af R R R R
2913 2914
2914 2915 This command schedules the files to be removed at the next commit.
2915 2916 To undo a remove before that, see :hg:`revert`.
2916 2917
2917 2918 Returns 0 on success, 1 if any warnings encountered.
2918 2919 """
2919 2920
2920 2921 ret = 0
2921 2922 after, force = opts.get('after'), opts.get('force')
2922 2923 if not pats and not after:
2923 2924 raise util.Abort(_('no files specified'))
2924 2925
2925 2926 m = cmdutil.match(repo, pats, opts)
2926 2927 s = repo.status(match=m, clean=True)
2927 2928 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2928 2929
2929 2930 for f in m.files():
2930 2931 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2931 2932 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2932 2933 ret = 1
2933 2934
2934 2935 if force:
2935 2936 remove, forget = modified + deleted + clean, added
2936 2937 elif after:
2937 2938 remove, forget = deleted, []
2938 2939 for f in modified + added + clean:
2939 2940 ui.warn(_('not removing %s: file still exists (use -f'
2940 2941 ' to force removal)\n') % m.rel(f))
2941 2942 ret = 1
2942 2943 else:
2943 2944 remove, forget = deleted + clean, []
2944 2945 for f in modified:
2945 2946 ui.warn(_('not removing %s: file is modified (use -f'
2946 2947 ' to force removal)\n') % m.rel(f))
2947 2948 ret = 1
2948 2949 for f in added:
2949 2950 ui.warn(_('not removing %s: file has been marked for add (use -f'
2950 2951 ' to force removal)\n') % m.rel(f))
2951 2952 ret = 1
2952 2953
2953 2954 for f in sorted(remove + forget):
2954 2955 if ui.verbose or not m.exact(f):
2955 2956 ui.status(_('removing %s\n') % m.rel(f))
2956 2957
2957 2958 repo[None].forget(forget)
2958 2959 repo[None].remove(remove, unlink=not after)
2959 2960 return ret
2960 2961
2961 2962 def rename(ui, repo, *pats, **opts):
2962 2963 """rename files; equivalent of copy + remove
2963 2964
2964 2965 Mark dest as copies of sources; mark sources for deletion. If dest
2965 2966 is a directory, copies are put in that directory. If dest is a
2966 2967 file, there can only be one source.
2967 2968
2968 2969 By default, this command copies the contents of files as they
2969 2970 exist in the working directory. If invoked with -A/--after, the
2970 2971 operation is recorded, but no copying is performed.
2971 2972
2972 2973 This command takes effect at the next commit. To undo a rename
2973 2974 before that, see :hg:`revert`.
2974 2975
2975 2976 Returns 0 on success, 1 if errors are encountered.
2976 2977 """
2977 2978 wlock = repo.wlock(False)
2978 2979 try:
2979 2980 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2980 2981 finally:
2981 2982 wlock.release()
2982 2983
2983 2984 def resolve(ui, repo, *pats, **opts):
2984 2985 """redo merges or set/view the merge status of files
2985 2986
2986 2987 Merges with unresolved conflicts are often the result of
2987 2988 non-interactive merging using the ``internal:merge`` configuration
2988 2989 setting, or a command-line merge tool like ``diff3``. The resolve
2989 2990 command is used to manage the files involved in a merge, after
2990 2991 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
2991 2992 working directory must have two parents).
2992 2993
2993 2994 The resolve command can be used in the following ways:
2994 2995
2995 2996 - :hg:`resolve FILE...`: attempt to re-merge the specified files,
2996 2997 discarding any previous merge attempts. Re-merging is not
2997 2998 performed for files already marked as resolved. Use ``--all/-a``
2998 2999 to selects all unresolved files.
2999 3000
3000 3001 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3001 3002 (e.g. after having manually fixed-up the files). The default is
3002 3003 to mark all unresolved files.
3003 3004
3004 3005 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3005 3006 default is to mark all resolved files.
3006 3007
3007 3008 - :hg:`resolve -l`: list files which had or still have conflicts.
3008 3009 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3009 3010
3010 3011 Note that Mercurial will not let you commit files with unresolved
3011 3012 merge conflicts. You must use :hg:`resolve -m ...` before you can
3012 3013 commit after a conflicting merge.
3013 3014
3014 3015 Returns 0 on success, 1 if any files fail a resolve attempt.
3015 3016 """
3016 3017
3017 3018 all, mark, unmark, show, nostatus = \
3018 3019 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3019 3020
3020 3021 if (show and (mark or unmark)) or (mark and unmark):
3021 3022 raise util.Abort(_("too many options specified"))
3022 3023 if pats and all:
3023 3024 raise util.Abort(_("can't specify --all and patterns"))
3024 3025 if not (all or pats or show or mark or unmark):
3025 3026 raise util.Abort(_('no files or directories specified; '
3026 3027 'use --all to remerge all files'))
3027 3028
3028 3029 ms = mergemod.mergestate(repo)
3029 3030 m = cmdutil.match(repo, pats, opts)
3030 3031 ret = 0
3031 3032
3032 3033 for f in ms:
3033 3034 if m(f):
3034 3035 if show:
3035 3036 if nostatus:
3036 3037 ui.write("%s\n" % f)
3037 3038 else:
3038 3039 ui.write("%s %s\n" % (ms[f].upper(), f),
3039 3040 label='resolve.' +
3040 3041 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3041 3042 elif mark:
3042 3043 ms.mark(f, "r")
3043 3044 elif unmark:
3044 3045 ms.mark(f, "u")
3045 3046 else:
3046 3047 wctx = repo[None]
3047 3048 mctx = wctx.parents()[-1]
3048 3049
3049 3050 # backup pre-resolve (merge uses .orig for its own purposes)
3050 3051 a = repo.wjoin(f)
3051 3052 util.copyfile(a, a + ".resolve")
3052 3053
3053 3054 # resolve file
3054 3055 if ms.resolve(f, wctx, mctx):
3055 3056 ret = 1
3056 3057
3057 3058 # replace filemerge's .orig file with our resolve file
3058 3059 util.rename(a + ".resolve", a + ".orig")
3059 3060 return ret
3060 3061
3061 3062 def revert(ui, repo, *pats, **opts):
3062 3063 """restore individual files or directories to an earlier state
3063 3064
3064 3065 NOTE: This command is most likely not what you are looking for. revert
3065 3066 will partially overwrite content in the working directory without changing
3066 3067 the working directory parents. Use :hg:`update -r rev` to check out earlier
3067 3068 revisions, or :hg:`update --clean .` to undo a merge which has added
3068 3069 another parent.
3069 3070
3070 3071 With no revision specified, revert the named files or directories
3071 3072 to the contents they had in the parent of the working directory.
3072 3073 This restores the contents of the affected files to an unmodified
3073 3074 state and unschedules adds, removes, copies, and renames. If the
3074 3075 working directory has two parents, you must explicitly specify a
3075 3076 revision.
3076 3077
3077 3078 Using the -r/--rev option, revert the given files or directories
3078 3079 to their contents as of a specific revision. This can be helpful
3079 3080 to "roll back" some or all of an earlier change. See :hg:`help
3080 3081 dates` for a list of formats valid for -d/--date.
3081 3082
3082 3083 Revert modifies the working directory. It does not commit any
3083 3084 changes, or change the parent of the working directory. If you
3084 3085 revert to a revision other than the parent of the working
3085 3086 directory, the reverted files will thus appear modified
3086 3087 afterwards.
3087 3088
3088 3089 If a file has been deleted, it is restored. If the executable mode
3089 3090 of a file was changed, it is reset.
3090 3091
3091 3092 If names are given, all files matching the names are reverted.
3092 3093 If no arguments are given, no files are reverted.
3093 3094
3094 3095 Modified files are saved with a .orig suffix before reverting.
3095 3096 To disable these backups, use --no-backup.
3096 3097
3097 3098 Returns 0 on success.
3098 3099 """
3099 3100
3100 3101 if opts.get("date"):
3101 3102 if opts.get("rev"):
3102 3103 raise util.Abort(_("you can't specify a revision and a date"))
3103 3104 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3104 3105
3105 3106 if not pats and not opts.get('all'):
3106 3107 raise util.Abort(_('no files or directories specified; '
3107 3108 'use --all to revert the whole repo'))
3108 3109
3109 3110 parent, p2 = repo.dirstate.parents()
3110 3111 if not opts.get('rev') and p2 != nullid:
3111 3112 raise util.Abort(_('uncommitted merge - please provide a '
3112 3113 'specific revision'))
3113 3114 ctx = repo[opts.get('rev')]
3114 3115 node = ctx.node()
3115 3116 mf = ctx.manifest()
3116 3117 if node == parent:
3117 3118 pmf = mf
3118 3119 else:
3119 3120 pmf = None
3120 3121
3121 3122 # need all matching names in dirstate and manifest of target rev,
3122 3123 # so have to walk both. do not print errors if files exist in one
3123 3124 # but not other.
3124 3125
3125 3126 names = {}
3126 3127
3127 3128 wlock = repo.wlock()
3128 3129 try:
3129 3130 # walk dirstate.
3130 3131
3131 3132 m = cmdutil.match(repo, pats, opts)
3132 3133 m.bad = lambda x, y: False
3133 3134 for abs in repo.walk(m):
3134 3135 names[abs] = m.rel(abs), m.exact(abs)
3135 3136
3136 3137 # walk target manifest.
3137 3138
3138 3139 def badfn(path, msg):
3139 3140 if path in names:
3140 3141 return
3141 3142 path_ = path + '/'
3142 3143 for f in names:
3143 3144 if f.startswith(path_):
3144 3145 return
3145 3146 ui.warn("%s: %s\n" % (m.rel(path), msg))
3146 3147
3147 3148 m = cmdutil.match(repo, pats, opts)
3148 3149 m.bad = badfn
3149 3150 for abs in repo[node].walk(m):
3150 3151 if abs not in names:
3151 3152 names[abs] = m.rel(abs), m.exact(abs)
3152 3153
3153 3154 m = cmdutil.matchfiles(repo, names)
3154 3155 changes = repo.status(match=m)[:4]
3155 3156 modified, added, removed, deleted = map(set, changes)
3156 3157
3157 3158 # if f is a rename, also revert the source
3158 3159 cwd = repo.getcwd()
3159 3160 for f in added:
3160 3161 src = repo.dirstate.copied(f)
3161 3162 if src and src not in names and repo.dirstate[src] == 'r':
3162 3163 removed.add(src)
3163 3164 names[src] = (repo.pathto(src, cwd), True)
3164 3165
3165 3166 def removeforget(abs):
3166 3167 if repo.dirstate[abs] == 'a':
3167 3168 return _('forgetting %s\n')
3168 3169 return _('removing %s\n')
3169 3170
3170 3171 revert = ([], _('reverting %s\n'))
3171 3172 add = ([], _('adding %s\n'))
3172 3173 remove = ([], removeforget)
3173 3174 undelete = ([], _('undeleting %s\n'))
3174 3175
3175 3176 disptable = (
3176 3177 # dispatch table:
3177 3178 # file state
3178 3179 # action if in target manifest
3179 3180 # action if not in target manifest
3180 3181 # make backup if in target manifest
3181 3182 # make backup if not in target manifest
3182 3183 (modified, revert, remove, True, True),
3183 3184 (added, revert, remove, True, False),
3184 3185 (removed, undelete, None, False, False),
3185 3186 (deleted, revert, remove, False, False),
3186 3187 )
3187 3188
3188 3189 for abs, (rel, exact) in sorted(names.items()):
3189 3190 mfentry = mf.get(abs)
3190 3191 target = repo.wjoin(abs)
3191 3192 def handle(xlist, dobackup):
3192 3193 xlist[0].append(abs)
3193 3194 if (dobackup and not opts.get('no_backup') and
3194 3195 os.path.lexists(target)):
3195 3196 bakname = "%s.orig" % rel
3196 3197 ui.note(_('saving current version of %s as %s\n') %
3197 3198 (rel, bakname))
3198 3199 if not opts.get('dry_run'):
3199 3200 util.rename(target, bakname)
3200 3201 if ui.verbose or not exact:
3201 3202 msg = xlist[1]
3202 3203 if not isinstance(msg, basestring):
3203 3204 msg = msg(abs)
3204 3205 ui.status(msg % rel)
3205 3206 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3206 3207 if abs not in table:
3207 3208 continue
3208 3209 # file has changed in dirstate
3209 3210 if mfentry:
3210 3211 handle(hitlist, backuphit)
3211 3212 elif misslist is not None:
3212 3213 handle(misslist, backupmiss)
3213 3214 break
3214 3215 else:
3215 3216 if abs not in repo.dirstate:
3216 3217 if mfentry:
3217 3218 handle(add, True)
3218 3219 elif exact:
3219 3220 ui.warn(_('file not managed: %s\n') % rel)
3220 3221 continue
3221 3222 # file has not changed in dirstate
3222 3223 if node == parent:
3223 3224 if exact:
3224 3225 ui.warn(_('no changes needed to %s\n') % rel)
3225 3226 continue
3226 3227 if pmf is None:
3227 3228 # only need parent manifest in this unlikely case,
3228 3229 # so do not read by default
3229 3230 pmf = repo[parent].manifest()
3230 3231 if abs in pmf:
3231 3232 if mfentry:
3232 3233 # if version of file is same in parent and target
3233 3234 # manifests, do nothing
3234 3235 if (pmf[abs] != mfentry or
3235 3236 pmf.flags(abs) != mf.flags(abs)):
3236 3237 handle(revert, False)
3237 3238 else:
3238 3239 handle(remove, False)
3239 3240
3240 3241 if not opts.get('dry_run'):
3241 3242 def checkout(f):
3242 3243 fc = ctx[f]
3243 3244 repo.wwrite(f, fc.data(), fc.flags())
3244 3245
3245 3246 audit_path = util.path_auditor(repo.root)
3246 3247 for f in remove[0]:
3247 3248 if repo.dirstate[f] == 'a':
3248 3249 repo.dirstate.forget(f)
3249 3250 continue
3250 3251 audit_path(f)
3251 3252 try:
3252 3253 util.unlink(repo.wjoin(f))
3253 3254 except OSError:
3254 3255 pass
3255 3256 repo.dirstate.remove(f)
3256 3257
3257 3258 normal = None
3258 3259 if node == parent:
3259 3260 # We're reverting to our parent. If possible, we'd like status
3260 3261 # to report the file as clean. We have to use normallookup for
3261 3262 # merges to avoid losing information about merged/dirty files.
3262 3263 if p2 != nullid:
3263 3264 normal = repo.dirstate.normallookup
3264 3265 else:
3265 3266 normal = repo.dirstate.normal
3266 3267 for f in revert[0]:
3267 3268 checkout(f)
3268 3269 if normal:
3269 3270 normal(f)
3270 3271
3271 3272 for f in add[0]:
3272 3273 checkout(f)
3273 3274 repo.dirstate.add(f)
3274 3275
3275 3276 normal = repo.dirstate.normallookup
3276 3277 if node == parent and p2 == nullid:
3277 3278 normal = repo.dirstate.normal
3278 3279 for f in undelete[0]:
3279 3280 checkout(f)
3280 3281 normal(f)
3281 3282
3282 3283 finally:
3283 3284 wlock.release()
3284 3285
3285 3286 def rollback(ui, repo, **opts):
3286 3287 """roll back the last transaction (dangerous)
3287 3288
3288 3289 This command should be used with care. There is only one level of
3289 3290 rollback, and there is no way to undo a rollback. It will also
3290 3291 restore the dirstate at the time of the last transaction, losing
3291 3292 any dirstate changes since that time. This command does not alter
3292 3293 the working directory.
3293 3294
3294 3295 Transactions are used to encapsulate the effects of all commands
3295 3296 that create new changesets or propagate existing changesets into a
3296 3297 repository. For example, the following commands are transactional,
3297 3298 and their effects can be rolled back:
3298 3299
3299 3300 - commit
3300 3301 - import
3301 3302 - pull
3302 3303 - push (with this repository as the destination)
3303 3304 - unbundle
3304 3305
3305 3306 This command is not intended for use on public repositories. Once
3306 3307 changes are visible for pull by other users, rolling a transaction
3307 3308 back locally is ineffective (someone else may already have pulled
3308 3309 the changes). Furthermore, a race is possible with readers of the
3309 3310 repository; for example an in-progress pull from the repository
3310 3311 may fail if a rollback is performed.
3311 3312
3312 3313 Returns 0 on success, 1 if no rollback data is available.
3313 3314 """
3314 3315 return repo.rollback(opts.get('dry_run'))
3315 3316
3316 3317 def root(ui, repo):
3317 3318 """print the root (top) of the current working directory
3318 3319
3319 3320 Print the root directory of the current repository.
3320 3321
3321 3322 Returns 0 on success.
3322 3323 """
3323 3324 ui.write(repo.root + "\n")
3324 3325
3325 3326 def serve(ui, repo, **opts):
3326 3327 """start stand-alone webserver
3327 3328
3328 3329 Start a local HTTP repository browser and pull server. You can use
3329 3330 this for ad-hoc sharing and browing of repositories. It is
3330 3331 recommended to use a real web server to serve a repository for
3331 3332 longer periods of time.
3332 3333
3333 3334 Please note that the server does not implement access control.
3334 3335 This means that, by default, anybody can read from the server and
3335 3336 nobody can write to it by default. Set the ``web.allow_push``
3336 3337 option to ``*`` to allow everybody to push to the server. You
3337 3338 should use a real web server if you need to authenticate users.
3338 3339
3339 3340 By default, the server logs accesses to stdout and errors to
3340 3341 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3341 3342 files.
3342 3343
3343 3344 To have the server choose a free port number to listen on, specify
3344 3345 a port number of 0; in this case, the server will print the port
3345 3346 number it uses.
3346 3347
3347 3348 Returns 0 on success.
3348 3349 """
3349 3350
3350 3351 if opts["stdio"]:
3351 3352 if repo is None:
3352 3353 raise error.RepoError(_("There is no Mercurial repository here"
3353 3354 " (.hg not found)"))
3354 3355 s = sshserver.sshserver(ui, repo)
3355 3356 s.serve_forever()
3356 3357
3357 3358 # this way we can check if something was given in the command-line
3358 3359 if opts.get('port'):
3359 3360 opts['port'] = util.getport(opts.get('port'))
3360 3361
3361 3362 baseui = repo and repo.baseui or ui
3362 3363 optlist = ("name templates style address port prefix ipv6"
3363 3364 " accesslog errorlog certificate encoding")
3364 3365 for o in optlist.split():
3365 3366 val = opts.get(o, '')
3366 3367 if val in (None, ''): # should check against default options instead
3367 3368 continue
3368 3369 baseui.setconfig("web", o, val)
3369 3370 if repo and repo.ui != baseui:
3370 3371 repo.ui.setconfig("web", o, val)
3371 3372
3372 3373 o = opts.get('web_conf') or opts.get('webdir_conf')
3373 3374 if not o:
3374 3375 if not repo:
3375 3376 raise error.RepoError(_("There is no Mercurial repository"
3376 3377 " here (.hg not found)"))
3377 3378 o = repo.root
3378 3379
3379 3380 app = hgweb.hgweb(o, baseui=ui)
3380 3381
3381 3382 class service(object):
3382 3383 def init(self):
3383 3384 util.set_signal_handler()
3384 3385 self.httpd = hgweb.server.create_server(ui, app)
3385 3386
3386 3387 if opts['port'] and not ui.verbose:
3387 3388 return
3388 3389
3389 3390 if self.httpd.prefix:
3390 3391 prefix = self.httpd.prefix.strip('/') + '/'
3391 3392 else:
3392 3393 prefix = ''
3393 3394
3394 3395 port = ':%d' % self.httpd.port
3395 3396 if port == ':80':
3396 3397 port = ''
3397 3398
3398 3399 bindaddr = self.httpd.addr
3399 3400 if bindaddr == '0.0.0.0':
3400 3401 bindaddr = '*'
3401 3402 elif ':' in bindaddr: # IPv6
3402 3403 bindaddr = '[%s]' % bindaddr
3403 3404
3404 3405 fqaddr = self.httpd.fqaddr
3405 3406 if ':' in fqaddr:
3406 3407 fqaddr = '[%s]' % fqaddr
3407 3408 if opts['port']:
3408 3409 write = ui.status
3409 3410 else:
3410 3411 write = ui.write
3411 3412 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3412 3413 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3413 3414
3414 3415 def run(self):
3415 3416 self.httpd.serve_forever()
3416 3417
3417 3418 service = service()
3418 3419
3419 3420 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3420 3421
3421 3422 def status(ui, repo, *pats, **opts):
3422 3423 """show changed files in the working directory
3423 3424
3424 3425 Show status of files in the repository. If names are given, only
3425 3426 files that match are shown. Files that are clean or ignored or
3426 3427 the source of a copy/move operation, are not listed unless
3427 3428 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3428 3429 Unless options described with "show only ..." are given, the
3429 3430 options -mardu are used.
3430 3431
3431 3432 Option -q/--quiet hides untracked (unknown and ignored) files
3432 3433 unless explicitly requested with -u/--unknown or -i/--ignored.
3433 3434
3434 3435 NOTE: status may appear to disagree with diff if permissions have
3435 3436 changed or a merge has occurred. The standard diff format does not
3436 3437 report permission changes and diff only reports changes relative
3437 3438 to one merge parent.
3438 3439
3439 3440 If one revision is given, it is used as the base revision.
3440 3441 If two revisions are given, the differences between them are
3441 3442 shown. The --change option can also be used as a shortcut to list
3442 3443 the changed files of a revision from its first parent.
3443 3444
3444 3445 The codes used to show the status of files are::
3445 3446
3446 3447 M = modified
3447 3448 A = added
3448 3449 R = removed
3449 3450 C = clean
3450 3451 ! = missing (deleted by non-hg command, but still tracked)
3451 3452 ? = not tracked
3452 3453 I = ignored
3453 3454 = origin of the previous file listed as A (added)
3454 3455
3455 3456 Returns 0 on success.
3456 3457 """
3457 3458
3458 3459 revs = opts.get('rev')
3459 3460 change = opts.get('change')
3460 3461
3461 3462 if revs and change:
3462 3463 msg = _('cannot specify --rev and --change at the same time')
3463 3464 raise util.Abort(msg)
3464 3465 elif change:
3465 3466 node2 = repo.lookup(change)
3466 3467 node1 = repo[node2].parents()[0].node()
3467 3468 else:
3468 3469 node1, node2 = cmdutil.revpair(repo, revs)
3469 3470
3470 3471 cwd = (pats and repo.getcwd()) or ''
3471 3472 end = opts.get('print0') and '\0' or '\n'
3472 3473 copy = {}
3473 3474 states = 'modified added removed deleted unknown ignored clean'.split()
3474 3475 show = [k for k in states if opts.get(k)]
3475 3476 if opts.get('all'):
3476 3477 show += ui.quiet and (states[:4] + ['clean']) or states
3477 3478 if not show:
3478 3479 show = ui.quiet and states[:4] or states[:5]
3479 3480
3480 3481 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3481 3482 'ignored' in show, 'clean' in show, 'unknown' in show,
3482 3483 opts.get('subrepos'))
3483 3484 changestates = zip(states, 'MAR!?IC', stat)
3484 3485
3485 3486 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3486 3487 ctxn = repo[nullid]
3487 3488 ctx1 = repo[node1]
3488 3489 ctx2 = repo[node2]
3489 3490 added = stat[1]
3490 3491 if node2 is None:
3491 3492 added = stat[0] + stat[1] # merged?
3492 3493
3493 3494 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3494 3495 if k in added:
3495 3496 copy[k] = v
3496 3497 elif v in added:
3497 3498 copy[v] = k
3498 3499
3499 3500 for state, char, files in changestates:
3500 3501 if state in show:
3501 3502 format = "%s %%s%s" % (char, end)
3502 3503 if opts.get('no_status'):
3503 3504 format = "%%s%s" % end
3504 3505
3505 3506 for f in files:
3506 3507 ui.write(format % repo.pathto(f, cwd),
3507 3508 label='status.' + state)
3508 3509 if f in copy:
3509 3510 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3510 3511 label='status.copied')
3511 3512
3512 3513 def summary(ui, repo, **opts):
3513 3514 """summarize working directory state
3514 3515
3515 3516 This generates a brief summary of the working directory state,
3516 3517 including parents, branch, commit status, and available updates.
3517 3518
3518 3519 With the --remote option, this will check the default paths for
3519 3520 incoming and outgoing changes. This can be time-consuming.
3520 3521
3521 3522 Returns 0 on success.
3522 3523 """
3523 3524
3524 3525 ctx = repo[None]
3525 3526 parents = ctx.parents()
3526 3527 pnode = parents[0].node()
3527 3528
3528 3529 for p in parents:
3529 3530 # label with log.changeset (instead of log.parent) since this
3530 3531 # shows a working directory parent *changeset*:
3531 3532 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3532 3533 label='log.changeset')
3533 3534 ui.write(' '.join(p.tags()), label='log.tag')
3534 3535 if p.rev() == -1:
3535 3536 if not len(repo):
3536 3537 ui.write(_(' (empty repository)'))
3537 3538 else:
3538 3539 ui.write(_(' (no revision checked out)'))
3539 3540 ui.write('\n')
3540 3541 if p.description():
3541 3542 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3542 3543 label='log.summary')
3543 3544
3544 3545 branch = ctx.branch()
3545 3546 bheads = repo.branchheads(branch)
3546 3547 m = _('branch: %s\n') % branch
3547 3548 if branch != 'default':
3548 3549 ui.write(m, label='log.branch')
3549 3550 else:
3550 3551 ui.status(m, label='log.branch')
3551 3552
3552 3553 st = list(repo.status(unknown=True))[:6]
3553 3554
3554 3555 c = repo.dirstate.copies()
3555 3556 copied, renamed = [], []
3556 3557 for d, s in c.iteritems():
3557 3558 if s in st[2]:
3558 3559 st[2].remove(s)
3559 3560 renamed.append(d)
3560 3561 else:
3561 3562 copied.append(d)
3562 3563 if d in st[1]:
3563 3564 st[1].remove(d)
3564 3565 st.insert(3, renamed)
3565 3566 st.insert(4, copied)
3566 3567
3567 3568 ms = mergemod.mergestate(repo)
3568 3569 st.append([f for f in ms if ms[f] == 'u'])
3569 3570
3570 3571 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3571 3572 st.append(subs)
3572 3573
3573 3574 labels = [ui.label(_('%d modified'), 'status.modified'),
3574 3575 ui.label(_('%d added'), 'status.added'),
3575 3576 ui.label(_('%d removed'), 'status.removed'),
3576 3577 ui.label(_('%d renamed'), 'status.copied'),
3577 3578 ui.label(_('%d copied'), 'status.copied'),
3578 3579 ui.label(_('%d deleted'), 'status.deleted'),
3579 3580 ui.label(_('%d unknown'), 'status.unknown'),
3580 3581 ui.label(_('%d ignored'), 'status.ignored'),
3581 3582 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3582 3583 ui.label(_('%d subrepos'), 'status.modified')]
3583 3584 t = []
3584 3585 for s, l in zip(st, labels):
3585 3586 if s:
3586 3587 t.append(l % len(s))
3587 3588
3588 3589 t = ', '.join(t)
3589 3590 cleanworkdir = False
3590 3591
3591 3592 if len(parents) > 1:
3592 3593 t += _(' (merge)')
3593 3594 elif branch != parents[0].branch():
3594 3595 t += _(' (new branch)')
3595 3596 elif (parents[0].extra().get('close') and
3596 3597 pnode in repo.branchheads(branch, closed=True)):
3597 3598 t += _(' (head closed)')
3598 3599 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3599 3600 t += _(' (clean)')
3600 3601 cleanworkdir = True
3601 3602 elif pnode not in bheads:
3602 3603 t += _(' (new branch head)')
3603 3604
3604 3605 if cleanworkdir:
3605 3606 ui.status(_('commit: %s\n') % t.strip())
3606 3607 else:
3607 3608 ui.write(_('commit: %s\n') % t.strip())
3608 3609
3609 3610 # all ancestors of branch heads - all ancestors of parent = new csets
3610 3611 new = [0] * len(repo)
3611 3612 cl = repo.changelog
3612 3613 for a in [cl.rev(n) for n in bheads]:
3613 3614 new[a] = 1
3614 3615 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3615 3616 new[a] = 1
3616 3617 for a in [p.rev() for p in parents]:
3617 3618 if a >= 0:
3618 3619 new[a] = 0
3619 3620 for a in cl.ancestors(*[p.rev() for p in parents]):
3620 3621 new[a] = 0
3621 3622 new = sum(new)
3622 3623
3623 3624 if new == 0:
3624 3625 ui.status(_('update: (current)\n'))
3625 3626 elif pnode not in bheads:
3626 3627 ui.write(_('update: %d new changesets (update)\n') % new)
3627 3628 else:
3628 3629 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3629 3630 (new, len(bheads)))
3630 3631
3631 3632 if opts.get('remote'):
3632 3633 t = []
3633 3634 source, branches = hg.parseurl(ui.expandpath('default'))
3634 3635 other = hg.repository(hg.remoteui(repo, {}), source)
3635 3636 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3636 3637 ui.debug('comparing with %s\n' % url.hidepassword(source))
3637 3638 repo.ui.pushbuffer()
3638 3639 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3639 3640 repo.ui.popbuffer()
3640 3641 if incoming:
3641 3642 t.append(_('1 or more incoming'))
3642 3643
3643 3644 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3644 3645 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3645 3646 other = hg.repository(hg.remoteui(repo, {}), dest)
3646 3647 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3647 3648 repo.ui.pushbuffer()
3648 3649 o = discovery.findoutgoing(repo, other)
3649 3650 repo.ui.popbuffer()
3650 3651 o = repo.changelog.nodesbetween(o, None)[0]
3651 3652 if o:
3652 3653 t.append(_('%d outgoing') % len(o))
3653 3654
3654 3655 if t:
3655 3656 ui.write(_('remote: %s\n') % (', '.join(t)))
3656 3657 else:
3657 3658 ui.status(_('remote: (synced)\n'))
3658 3659
3659 3660 def tag(ui, repo, name1, *names, **opts):
3660 3661 """add one or more tags for the current or given revision
3661 3662
3662 3663 Name a particular revision using <name>.
3663 3664
3664 3665 Tags are used to name particular revisions of the repository and are
3665 3666 very useful to compare different revisions, to go back to significant
3666 3667 earlier versions or to mark branch points as releases, etc.
3667 3668
3668 3669 If no revision is given, the parent of the working directory is
3669 3670 used, or tip if no revision is checked out.
3670 3671
3671 3672 To facilitate version control, distribution, and merging of tags,
3672 3673 they are stored as a file named ".hgtags" which is managed
3673 3674 similarly to other project files and can be hand-edited if
3674 3675 necessary. The file '.hg/localtags' is used for local tags (not
3675 3676 shared among repositories).
3676 3677
3677 3678 See :hg:`help dates` for a list of formats valid for -d/--date.
3678 3679
3679 3680 Since tag names have priority over branch names during revision
3680 3681 lookup, using an existing branch name as a tag name is discouraged.
3681 3682
3682 3683 Returns 0 on success.
3683 3684 """
3684 3685
3685 3686 rev_ = "."
3686 3687 names = [t.strip() for t in (name1,) + names]
3687 3688 if len(names) != len(set(names)):
3688 3689 raise util.Abort(_('tag names must be unique'))
3689 3690 for n in names:
3690 3691 if n in ['tip', '.', 'null']:
3691 3692 raise util.Abort(_('the name \'%s\' is reserved') % n)
3692 3693 if not n:
3693 3694 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
3694 3695 if opts.get('rev') and opts.get('remove'):
3695 3696 raise util.Abort(_("--rev and --remove are incompatible"))
3696 3697 if opts.get('rev'):
3697 3698 rev_ = opts['rev']
3698 3699 message = opts.get('message')
3699 3700 if opts.get('remove'):
3700 3701 expectedtype = opts.get('local') and 'local' or 'global'
3701 3702 for n in names:
3702 3703 if not repo.tagtype(n):
3703 3704 raise util.Abort(_('tag \'%s\' does not exist') % n)
3704 3705 if repo.tagtype(n) != expectedtype:
3705 3706 if expectedtype == 'global':
3706 3707 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3707 3708 else:
3708 3709 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3709 3710 rev_ = nullid
3710 3711 if not message:
3711 3712 # we don't translate commit messages
3712 3713 message = 'Removed tag %s' % ', '.join(names)
3713 3714 elif not opts.get('force'):
3714 3715 for n in names:
3715 3716 if n in repo.tags():
3716 3717 raise util.Abort(_('tag \'%s\' already exists '
3717 3718 '(use -f to force)') % n)
3718 3719 if not rev_ and repo.dirstate.parents()[1] != nullid:
3719 3720 raise util.Abort(_('uncommitted merge - please provide a '
3720 3721 'specific revision'))
3721 3722 r = repo[rev_].node()
3722 3723
3723 3724 if not message:
3724 3725 # we don't translate commit messages
3725 3726 message = ('Added tag %s for changeset %s' %
3726 3727 (', '.join(names), short(r)))
3727 3728
3728 3729 date = opts.get('date')
3729 3730 if date:
3730 3731 date = util.parsedate(date)
3731 3732
3732 3733 if opts.get('edit'):
3733 3734 message = ui.edit(message, ui.username())
3734 3735
3735 3736 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3736 3737
3737 3738 def tags(ui, repo):
3738 3739 """list repository tags
3739 3740
3740 3741 This lists both regular and local tags. When the -v/--verbose
3741 3742 switch is used, a third column "local" is printed for local tags.
3742 3743
3743 3744 Returns 0 on success.
3744 3745 """
3745 3746
3746 3747 hexfunc = ui.debugflag and hex or short
3747 3748 tagtype = ""
3748 3749
3749 3750 for t, n in reversed(repo.tagslist()):
3750 3751 if ui.quiet:
3751 3752 ui.write("%s\n" % t)
3752 3753 continue
3753 3754
3754 3755 try:
3755 3756 hn = hexfunc(n)
3756 3757 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3757 3758 except error.LookupError:
3758 3759 r = " ?:%s" % hn
3759 3760 else:
3760 3761 spaces = " " * (30 - encoding.colwidth(t))
3761 3762 if ui.verbose:
3762 3763 if repo.tagtype(t) == 'local':
3763 3764 tagtype = " local"
3764 3765 else:
3765 3766 tagtype = ""
3766 3767 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3767 3768
3768 3769 def tip(ui, repo, **opts):
3769 3770 """show the tip revision
3770 3771
3771 3772 The tip revision (usually just called the tip) is the changeset
3772 3773 most recently added to the repository (and therefore the most
3773 3774 recently changed head).
3774 3775
3775 3776 If you have just made a commit, that commit will be the tip. If
3776 3777 you have just pulled changes from another repository, the tip of
3777 3778 that repository becomes the current tip. The "tip" tag is special
3778 3779 and cannot be renamed or assigned to a different changeset.
3779 3780
3780 3781 Returns 0 on success.
3781 3782 """
3782 3783 displayer = cmdutil.show_changeset(ui, repo, opts)
3783 3784 displayer.show(repo[len(repo) - 1])
3784 3785 displayer.close()
3785 3786
3786 3787 def unbundle(ui, repo, fname1, *fnames, **opts):
3787 3788 """apply one or more changegroup files
3788 3789
3789 3790 Apply one or more compressed changegroup files generated by the
3790 3791 bundle command.
3791 3792
3792 3793 Returns 0 on success, 1 if an update has unresolved files.
3793 3794 """
3794 3795 fnames = (fname1,) + fnames
3795 3796
3796 3797 lock = repo.lock()
3797 3798 try:
3798 3799 for fname in fnames:
3799 3800 f = url.open(ui, fname)
3800 3801 gen = changegroup.readbundle(f, fname)
3801 3802 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
3802 3803 lock=lock)
3803 3804 finally:
3804 3805 lock.release()
3805 3806
3806 3807 return postincoming(ui, repo, modheads, opts.get('update'), None)
3807 3808
3808 3809 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3809 3810 """update working directory (or switch revisions)
3810 3811
3811 3812 Update the repository's working directory to the specified
3812 3813 changeset.
3813 3814
3814 3815 If no changeset is specified, attempt to update to the tip of the
3815 3816 current branch. If this changeset is a descendant of the working
3816 3817 directory's parent, update to it, otherwise abort.
3817 3818
3818 3819 The following rules apply when the working directory contains
3819 3820 uncommitted changes:
3820 3821
3821 3822 1. If neither -c/--check nor -C/--clean is specified, and if
3822 3823 the requested changeset is an ancestor or descendant of
3823 3824 the working directory's parent, the uncommitted changes
3824 3825 are merged into the requested changeset and the merged
3825 3826 result is left uncommitted. If the requested changeset is
3826 3827 not an ancestor or descendant (that is, it is on another
3827 3828 branch), the update is aborted and the uncommitted changes
3828 3829 are preserved.
3829 3830
3830 3831 2. With the -c/--check option, the update is aborted and the
3831 3832 uncommitted changes are preserved.
3832 3833
3833 3834 3. With the -C/--clean option, uncommitted changes are discarded and
3834 3835 the working directory is updated to the requested changeset.
3835 3836
3836 3837 Use null as the changeset to remove the working directory (like
3837 3838 :hg:`clone -U`).
3838 3839
3839 3840 If you want to update just one file to an older changeset, use :hg:`revert`.
3840 3841
3841 3842 See :hg:`help dates` for a list of formats valid for -d/--date.
3842 3843
3843 3844 Returns 0 on success, 1 if there are unresolved files.
3844 3845 """
3845 3846 if rev and node:
3846 3847 raise util.Abort(_("please specify just one revision"))
3847 3848
3848 3849 if not rev:
3849 3850 rev = node
3850 3851
3851 3852 if check and clean:
3852 3853 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
3853 3854
3854 3855 if check:
3855 3856 # we could use dirty() but we can ignore merge and branch trivia
3856 3857 c = repo[None]
3857 3858 if c.modified() or c.added() or c.removed():
3858 3859 raise util.Abort(_("uncommitted local changes"))
3859 3860
3860 3861 if date:
3861 3862 if rev:
3862 3863 raise util.Abort(_("you can't specify a revision and a date"))
3863 3864 rev = cmdutil.finddate(ui, repo, date)
3864 3865
3865 3866 if clean or check:
3866 3867 return hg.clean(repo, rev)
3867 3868 else:
3868 3869 return hg.update(repo, rev)
3869 3870
3870 3871 def verify(ui, repo):
3871 3872 """verify the integrity of the repository
3872 3873
3873 3874 Verify the integrity of the current repository.
3874 3875
3875 3876 This will perform an extensive check of the repository's
3876 3877 integrity, validating the hashes and checksums of each entry in
3877 3878 the changelog, manifest, and tracked files, as well as the
3878 3879 integrity of their crosslinks and indices.
3879 3880
3880 3881 Returns 0 on success, 1 if errors are encountered.
3881 3882 """
3882 3883 return hg.verify(repo)
3883 3884
3884 3885 def version_(ui):
3885 3886 """output version and copyright information"""
3886 3887 ui.write(_("Mercurial Distributed SCM (version %s)\n")
3887 3888 % util.version())
3888 3889 ui.status(_(
3889 3890 "\nCopyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others\n"
3890 3891 "This is free software; see the source for copying conditions. "
3891 3892 "There is NO\nwarranty; "
3892 3893 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
3893 3894 ))
3894 3895
3895 3896 # Command options and aliases are listed here, alphabetically
3896 3897
3897 3898 globalopts = [
3898 3899 ('R', 'repository', '',
3899 3900 _('repository root directory or name of overlay bundle file'),
3900 3901 _('REPO')),
3901 3902 ('', 'cwd', '',
3902 3903 _('change working directory'), _('DIR')),
3903 3904 ('y', 'noninteractive', None,
3904 3905 _('do not prompt, assume \'yes\' for any required answers')),
3905 3906 ('q', 'quiet', None, _('suppress output')),
3906 3907 ('v', 'verbose', None, _('enable additional output')),
3907 3908 ('', 'config', [],
3908 3909 _('set/override config option (use \'section.name=value\')'),
3909 3910 _('CONFIG')),
3910 3911 ('', 'debug', None, _('enable debugging output')),
3911 3912 ('', 'debugger', None, _('start debugger')),
3912 3913 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
3913 3914 _('ENCODE')),
3914 3915 ('', 'encodingmode', encoding.encodingmode,
3915 3916 _('set the charset encoding mode'), _('MODE')),
3916 3917 ('', 'traceback', None, _('always print a traceback on exception')),
3917 3918 ('', 'time', None, _('time how long the command takes')),
3918 3919 ('', 'profile', None, _('print command execution profile')),
3919 3920 ('', 'version', None, _('output version information and exit')),
3920 3921 ('h', 'help', None, _('display help and exit')),
3921 3922 ]
3922 3923
3923 3924 dryrunopts = [('n', 'dry-run', None,
3924 3925 _('do not perform actions, just print output'))]
3925 3926
3926 3927 remoteopts = [
3927 3928 ('e', 'ssh', '',
3928 3929 _('specify ssh command to use'), _('CMD')),
3929 3930 ('', 'remotecmd', '',
3930 3931 _('specify hg command to run on the remote side'), _('CMD')),
3931 3932 ]
3932 3933
3933 3934 walkopts = [
3934 3935 ('I', 'include', [],
3935 3936 _('include names matching the given patterns'), _('PATTERN')),
3936 3937 ('X', 'exclude', [],
3937 3938 _('exclude names matching the given patterns'), _('PATTERN')),
3938 3939 ]
3939 3940
3940 3941 commitopts = [
3941 3942 ('m', 'message', '',
3942 3943 _('use text as commit message'), _('TEXT')),
3943 3944 ('l', 'logfile', '',
3944 3945 _('read commit message from file'), _('FILE')),
3945 3946 ]
3946 3947
3947 3948 commitopts2 = [
3948 3949 ('d', 'date', '',
3949 3950 _('record datecode as commit date'), _('DATE')),
3950 3951 ('u', 'user', '',
3951 3952 _('record the specified user as committer'), _('USER')),
3952 3953 ]
3953 3954
3954 3955 templateopts = [
3955 3956 ('', 'style', '',
3956 3957 _('display using template map file'), _('STYLE')),
3957 3958 ('', 'template', '',
3958 3959 _('display with template'), _('TEMPLATE')),
3959 3960 ]
3960 3961
3961 3962 logopts = [
3962 3963 ('p', 'patch', None, _('show patch')),
3963 3964 ('g', 'git', None, _('use git extended diff format')),
3964 3965 ('l', 'limit', '',
3965 3966 _('limit number of changes displayed'), _('NUM')),
3966 3967 ('M', 'no-merges', None, _('do not show merges')),
3967 3968 ('', 'stat', None, _('output diffstat-style summary of changes')),
3968 3969 ] + templateopts
3969 3970
3970 3971 diffopts = [
3971 3972 ('a', 'text', None, _('treat all files as text')),
3972 3973 ('g', 'git', None, _('use git extended diff format')),
3973 3974 ('', 'nodates', None, _('omit dates from diff headers'))
3974 3975 ]
3975 3976
3976 3977 diffopts2 = [
3977 3978 ('p', 'show-function', None, _('show which function each change is in')),
3978 3979 ('', 'reverse', None, _('produce a diff that undoes the changes')),
3979 3980 ('w', 'ignore-all-space', None,
3980 3981 _('ignore white space when comparing lines')),
3981 3982 ('b', 'ignore-space-change', None,
3982 3983 _('ignore changes in the amount of white space')),
3983 3984 ('B', 'ignore-blank-lines', None,
3984 3985 _('ignore changes whose lines are all blank')),
3985 3986 ('U', 'unified', '',
3986 3987 _('number of lines of context to show'), _('NUM')),
3987 3988 ('', 'stat', None, _('output diffstat-style summary of changes')),
3988 3989 ]
3989 3990
3990 3991 similarityopts = [
3991 3992 ('s', 'similarity', '',
3992 3993 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
3993 3994 ]
3994 3995
3995 3996 subrepoopts = [
3996 3997 ('S', 'subrepos', None,
3997 3998 _('recurse into subrepositories'))
3998 3999 ]
3999 4000
4000 4001 table = {
4001 4002 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
4002 4003 "addremove":
4003 4004 (addremove, similarityopts + walkopts + dryrunopts,
4004 4005 _('[OPTION]... [FILE]...')),
4005 4006 "^annotate|blame":
4006 4007 (annotate,
4007 4008 [('r', 'rev', '',
4008 4009 _('annotate the specified revision'), _('REV')),
4009 4010 ('', 'follow', None,
4010 4011 _('follow copies/renames and list the filename (DEPRECATED)')),
4011 4012 ('', 'no-follow', None, _("don't follow copies and renames")),
4012 4013 ('a', 'text', None, _('treat all files as text')),
4013 4014 ('u', 'user', None, _('list the author (long with -v)')),
4014 4015 ('f', 'file', None, _('list the filename')),
4015 4016 ('d', 'date', None, _('list the date (short with -q)')),
4016 4017 ('n', 'number', None, _('list the revision number (default)')),
4017 4018 ('c', 'changeset', None, _('list the changeset')),
4018 4019 ('l', 'line-number', None,
4019 4020 _('show line number at the first appearance'))
4020 4021 ] + walkopts,
4021 4022 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
4022 4023 "archive":
4023 4024 (archive,
4024 4025 [('', 'no-decode', None, _('do not pass files through decoders')),
4025 4026 ('p', 'prefix', '',
4026 4027 _('directory prefix for files in archive'), _('PREFIX')),
4027 4028 ('r', 'rev', '',
4028 4029 _('revision to distribute'), _('REV')),
4029 4030 ('t', 'type', '',
4030 4031 _('type of distribution to create'), _('TYPE')),
4031 4032 ] + walkopts,
4032 4033 _('[OPTION]... DEST')),
4033 4034 "backout":
4034 4035 (backout,
4035 4036 [('', 'merge', None,
4036 4037 _('merge with old dirstate parent after backout')),
4037 4038 ('', 'parent', '',
4038 4039 _('parent to choose when backing out merge'), _('REV')),
4039 4040 ('r', 'rev', '',
4040 4041 _('revision to backout'), _('REV')),
4041 4042 ] + walkopts + commitopts + commitopts2,
4042 4043 _('[OPTION]... [-r] REV')),
4043 4044 "bisect":
4044 4045 (bisect,
4045 4046 [('r', 'reset', False, _('reset bisect state')),
4046 4047 ('g', 'good', False, _('mark changeset good')),
4047 4048 ('b', 'bad', False, _('mark changeset bad')),
4048 4049 ('s', 'skip', False, _('skip testing changeset')),
4049 4050 ('c', 'command', '',
4050 4051 _('use command to check changeset state'), _('CMD')),
4051 4052 ('U', 'noupdate', False, _('do not update to target'))],
4052 4053 _("[-gbsr] [-U] [-c CMD] [REV]")),
4053 4054 "branch":
4054 4055 (branch,
4055 4056 [('f', 'force', None,
4056 4057 _('set branch name even if it shadows an existing branch')),
4057 4058 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4058 4059 _('[-fC] [NAME]')),
4059 4060 "branches":
4060 4061 (branches,
4061 4062 [('a', 'active', False,
4062 4063 _('show only branches that have unmerged heads')),
4063 4064 ('c', 'closed', False,
4064 4065 _('show normal and closed branches'))],
4065 4066 _('[-ac]')),
4066 4067 "bundle":
4067 4068 (bundle,
4068 4069 [('f', 'force', None,
4069 4070 _('run even when the destination is unrelated')),
4070 4071 ('r', 'rev', [],
4071 4072 _('a changeset intended to be added to the destination'),
4072 4073 _('REV')),
4073 4074 ('b', 'branch', [],
4074 4075 _('a specific branch you would like to bundle'),
4075 4076 _('BRANCH')),
4076 4077 ('', 'base', [],
4077 4078 _('a base changeset assumed to be available at the destination'),
4078 4079 _('REV')),
4079 4080 ('a', 'all', None, _('bundle all changesets in the repository')),
4080 4081 ('t', 'type', 'bzip2',
4081 4082 _('bundle compression type to use'), _('TYPE')),
4082 4083 ] + remoteopts,
4083 4084 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4084 4085 "cat":
4085 4086 (cat,
4086 4087 [('o', 'output', '',
4087 4088 _('print output to file with formatted name'), _('FORMAT')),
4088 4089 ('r', 'rev', '',
4089 4090 _('print the given revision'), _('REV')),
4090 4091 ('', 'decode', None, _('apply any matching decode filter')),
4091 4092 ] + walkopts,
4092 4093 _('[OPTION]... FILE...')),
4093 4094 "^clone":
4094 4095 (clone,
4095 4096 [('U', 'noupdate', None,
4096 4097 _('the clone will include an empty working copy (only a repository)')),
4097 4098 ('u', 'updaterev', '',
4098 4099 _('revision, tag or branch to check out'), _('REV')),
4099 4100 ('r', 'rev', [],
4100 4101 _('include the specified changeset'), _('REV')),
4101 4102 ('b', 'branch', [],
4102 4103 _('clone only the specified branch'), _('BRANCH')),
4103 4104 ('', 'pull', None, _('use pull protocol to copy metadata')),
4104 4105 ('', 'uncompressed', None,
4105 4106 _('use uncompressed transfer (fast over LAN)')),
4106 4107 ] + remoteopts,
4107 4108 _('[OPTION]... SOURCE [DEST]')),
4108 4109 "^commit|ci":
4109 4110 (commit,
4110 4111 [('A', 'addremove', None,
4111 4112 _('mark new/missing files as added/removed before committing')),
4112 4113 ('', 'close-branch', None,
4113 4114 _('mark a branch as closed, hiding it from the branch list')),
4114 4115 ] + walkopts + commitopts + commitopts2,
4115 4116 _('[OPTION]... [FILE]...')),
4116 4117 "copy|cp":
4117 4118 (copy,
4118 4119 [('A', 'after', None, _('record a copy that has already occurred')),
4119 4120 ('f', 'force', None,
4120 4121 _('forcibly copy over an existing managed file')),
4121 4122 ] + walkopts + dryrunopts,
4122 4123 _('[OPTION]... [SOURCE]... DEST')),
4123 4124 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4124 4125 "debugbuilddag":
4125 4126 (debugbuilddag,
4126 4127 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4127 4128 ('a', 'appended-file', None, _('add single file all revs append to')),
4128 4129 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4129 4130 ('n', 'new-file', None, _('add new file at each rev')),
4130 4131 ],
4131 4132 _('[OPTION]... TEXT')),
4132 4133 "debugcheckstate": (debugcheckstate, [], ''),
4133 4134 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4134 4135 "debugcomplete":
4135 4136 (debugcomplete,
4136 4137 [('o', 'options', None, _('show the command options'))],
4137 4138 _('[-o] CMD')),
4138 4139 "debugdag":
4139 4140 (debugdag,
4140 4141 [('t', 'tags', None, _('use tags as labels')),
4141 4142 ('b', 'branches', None, _('annotate with branch names')),
4142 4143 ('', 'dots', None, _('use dots for runs')),
4143 4144 ('s', 'spaces', None, _('separate elements by spaces')),
4144 4145 ],
4145 4146 _('[OPTION]... [FILE [REV]...]')),
4146 4147 "debugdate":
4147 4148 (debugdate,
4148 4149 [('e', 'extended', None, _('try extended date formats'))],
4149 4150 _('[-e] DATE [RANGE]')),
4150 4151 "debugdata": (debugdata, [], _('FILE REV')),
4151 4152 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4152 4153 "debugindex": (debugindex, [], _('FILE')),
4153 4154 "debugindexdot": (debugindexdot, [], _('FILE')),
4154 4155 "debuginstall": (debuginstall, [], ''),
4155 4156 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4156 4157 "debugrebuildstate":
4157 4158 (debugrebuildstate,
4158 4159 [('r', 'rev', '',
4159 4160 _('revision to rebuild to'), _('REV'))],
4160 4161 _('[-r REV] [REV]')),
4161 4162 "debugrename":
4162 4163 (debugrename,
4163 4164 [('r', 'rev', '',
4164 4165 _('revision to debug'), _('REV'))],
4165 4166 _('[-r REV] FILE')),
4166 4167 "debugrevspec":
4167 4168 (debugrevspec, [], ('REVSPEC')),
4168 4169 "debugsetparents":
4169 4170 (debugsetparents, [], _('REV1 [REV2]')),
4170 4171 "debugstate":
4171 4172 (debugstate,
4172 4173 [('', 'nodates', None, _('do not display the saved mtime'))],
4173 4174 _('[OPTION]...')),
4174 4175 "debugsub":
4175 4176 (debugsub,
4176 4177 [('r', 'rev', '',
4177 4178 _('revision to check'), _('REV'))],
4178 4179 _('[-r REV] [REV]')),
4179 4180 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4180 4181 "^diff":
4181 4182 (diff,
4182 4183 [('r', 'rev', [],
4183 4184 _('revision'), _('REV')),
4184 4185 ('c', 'change', '',
4185 4186 _('change made by revision'), _('REV'))
4186 ] + diffopts + diffopts2 + walkopts,
4187 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4187 4188 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4188 4189 "^export":
4189 4190 (export,
4190 4191 [('o', 'output', '',
4191 4192 _('print output to file with formatted name'), _('FORMAT')),
4192 4193 ('', 'switch-parent', None, _('diff against the second parent')),
4193 4194 ('r', 'rev', [],
4194 4195 _('revisions to export'), _('REV')),
4195 4196 ] + diffopts,
4196 4197 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4197 4198 "^forget":
4198 4199 (forget,
4199 4200 [] + walkopts,
4200 4201 _('[OPTION]... FILE...')),
4201 4202 "grep":
4202 4203 (grep,
4203 4204 [('0', 'print0', None, _('end fields with NUL')),
4204 4205 ('', 'all', None, _('print all revisions that match')),
4205 4206 ('f', 'follow', None,
4206 4207 _('follow changeset history,'
4207 4208 ' or file history across copies and renames')),
4208 4209 ('i', 'ignore-case', None, _('ignore case when matching')),
4209 4210 ('l', 'files-with-matches', None,
4210 4211 _('print only filenames and revisions that match')),
4211 4212 ('n', 'line-number', None, _('print matching line numbers')),
4212 4213 ('r', 'rev', [],
4213 4214 _('only search files changed within revision range'), _('REV')),
4214 4215 ('u', 'user', None, _('list the author (long with -v)')),
4215 4216 ('d', 'date', None, _('list the date (short with -q)')),
4216 4217 ] + walkopts,
4217 4218 _('[OPTION]... PATTERN [FILE]...')),
4218 4219 "heads":
4219 4220 (heads,
4220 4221 [('r', 'rev', '',
4221 4222 _('show only heads which are descendants of REV'), _('REV')),
4222 4223 ('t', 'topo', False, _('show topological heads only')),
4223 4224 ('a', 'active', False,
4224 4225 _('show active branchheads only (DEPRECATED)')),
4225 4226 ('c', 'closed', False,
4226 4227 _('show normal and closed branch heads')),
4227 4228 ] + templateopts,
4228 4229 _('[-ac] [-r REV] [REV]...')),
4229 4230 "help": (help_, [], _('[TOPIC]')),
4230 4231 "identify|id":
4231 4232 (identify,
4232 4233 [('r', 'rev', '',
4233 4234 _('identify the specified revision'), _('REV')),
4234 4235 ('n', 'num', None, _('show local revision number')),
4235 4236 ('i', 'id', None, _('show global revision id')),
4236 4237 ('b', 'branch', None, _('show branch')),
4237 4238 ('t', 'tags', None, _('show tags'))],
4238 4239 _('[-nibt] [-r REV] [SOURCE]')),
4239 4240 "import|patch":
4240 4241 (import_,
4241 4242 [('p', 'strip', 1,
4242 4243 _('directory strip option for patch. This has the same '
4243 4244 'meaning as the corresponding patch option'),
4244 4245 _('NUM')),
4245 4246 ('b', 'base', '',
4246 4247 _('base path'), _('PATH')),
4247 4248 ('f', 'force', None,
4248 4249 _('skip check for outstanding uncommitted changes')),
4249 4250 ('', 'no-commit', None,
4250 4251 _("don't commit, just update the working directory")),
4251 4252 ('', 'exact', None,
4252 4253 _('apply patch to the nodes from which it was generated')),
4253 4254 ('', 'import-branch', None,
4254 4255 _('use any branch information in patch (implied by --exact)'))] +
4255 4256 commitopts + commitopts2 + similarityopts,
4256 4257 _('[OPTION]... PATCH...')),
4257 4258 "incoming|in":
4258 4259 (incoming,
4259 4260 [('f', 'force', None,
4260 4261 _('run even if remote repository is unrelated')),
4261 4262 ('n', 'newest-first', None, _('show newest record first')),
4262 4263 ('', 'bundle', '',
4263 4264 _('file to store the bundles into'), _('FILE')),
4264 4265 ('r', 'rev', [],
4265 4266 _('a remote changeset intended to be added'), _('REV')),
4266 4267 ('b', 'branch', [],
4267 4268 _('a specific branch you would like to pull'), _('BRANCH')),
4268 4269 ] + logopts + remoteopts,
4269 4270 _('[-p] [-n] [-M] [-f] [-r REV]...'
4270 4271 ' [--bundle FILENAME] [SOURCE]')),
4271 4272 "^init":
4272 4273 (init,
4273 4274 remoteopts,
4274 4275 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4275 4276 "locate":
4276 4277 (locate,
4277 4278 [('r', 'rev', '',
4278 4279 _('search the repository as it is in REV'), _('REV')),
4279 4280 ('0', 'print0', None,
4280 4281 _('end filenames with NUL, for use with xargs')),
4281 4282 ('f', 'fullpath', None,
4282 4283 _('print complete paths from the filesystem root')),
4283 4284 ] + walkopts,
4284 4285 _('[OPTION]... [PATTERN]...')),
4285 4286 "^log|history":
4286 4287 (log,
4287 4288 [('f', 'follow', None,
4288 4289 _('follow changeset history,'
4289 4290 ' or file history across copies and renames')),
4290 4291 ('', 'follow-first', None,
4291 4292 _('only follow the first parent of merge changesets')),
4292 4293 ('d', 'date', '',
4293 4294 _('show revisions matching date spec'), _('DATE')),
4294 4295 ('C', 'copies', None, _('show copied files')),
4295 4296 ('k', 'keyword', [],
4296 4297 _('do case-insensitive search for a given text'), _('TEXT')),
4297 4298 ('r', 'rev', [],
4298 4299 _('show the specified revision or range'), _('REV')),
4299 4300 ('', 'removed', None, _('include revisions where files were removed')),
4300 4301 ('m', 'only-merges', None, _('show only merges')),
4301 4302 ('u', 'user', [],
4302 4303 _('revisions committed by user'), _('USER')),
4303 4304 ('', 'only-branch', [],
4304 4305 _('show only changesets within the given named branch (DEPRECATED)'),
4305 4306 _('BRANCH')),
4306 4307 ('b', 'branch', [],
4307 4308 _('show changesets within the given named branch'), _('BRANCH')),
4308 4309 ('P', 'prune', [],
4309 4310 _('do not display revision or any of its ancestors'), _('REV')),
4310 4311 ] + logopts + walkopts,
4311 4312 _('[OPTION]... [FILE]')),
4312 4313 "manifest":
4313 4314 (manifest,
4314 4315 [('r', 'rev', '',
4315 4316 _('revision to display'), _('REV'))],
4316 4317 _('[-r REV]')),
4317 4318 "^merge":
4318 4319 (merge,
4319 4320 [('f', 'force', None, _('force a merge with outstanding changes')),
4320 4321 ('r', 'rev', '',
4321 4322 _('revision to merge'), _('REV')),
4322 4323 ('P', 'preview', None,
4323 4324 _('review revisions to merge (no merge is performed)'))],
4324 4325 _('[-P] [-f] [[-r] REV]')),
4325 4326 "outgoing|out":
4326 4327 (outgoing,
4327 4328 [('f', 'force', None,
4328 4329 _('run even when the destination is unrelated')),
4329 4330 ('r', 'rev', [],
4330 4331 _('a changeset intended to be included in the destination'),
4331 4332 _('REV')),
4332 4333 ('n', 'newest-first', None, _('show newest record first')),
4333 4334 ('b', 'branch', [],
4334 4335 _('a specific branch you would like to push'), _('BRANCH')),
4335 4336 ] + logopts + remoteopts,
4336 4337 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4337 4338 "parents":
4338 4339 (parents,
4339 4340 [('r', 'rev', '',
4340 4341 _('show parents of the specified revision'), _('REV')),
4341 4342 ] + templateopts,
4342 4343 _('[-r REV] [FILE]')),
4343 4344 "paths": (paths, [], _('[NAME]')),
4344 4345 "^pull":
4345 4346 (pull,
4346 4347 [('u', 'update', None,
4347 4348 _('update to new branch head if changesets were pulled')),
4348 4349 ('f', 'force', None,
4349 4350 _('run even when remote repository is unrelated')),
4350 4351 ('r', 'rev', [],
4351 4352 _('a remote changeset intended to be added'), _('REV')),
4352 4353 ('b', 'branch', [],
4353 4354 _('a specific branch you would like to pull'), _('BRANCH')),
4354 4355 ] + remoteopts,
4355 4356 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4356 4357 "^push":
4357 4358 (push,
4358 4359 [('f', 'force', None, _('force push')),
4359 4360 ('r', 'rev', [],
4360 4361 _('a changeset intended to be included in the destination'),
4361 4362 _('REV')),
4362 4363 ('b', 'branch', [],
4363 4364 _('a specific branch you would like to push'), _('BRANCH')),
4364 4365 ('', 'new-branch', False, _('allow pushing a new branch')),
4365 4366 ] + remoteopts,
4366 4367 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4367 4368 "recover": (recover, []),
4368 4369 "^remove|rm":
4369 4370 (remove,
4370 4371 [('A', 'after', None, _('record delete for missing files')),
4371 4372 ('f', 'force', None,
4372 4373 _('remove (and delete) file even if added or modified')),
4373 4374 ] + walkopts,
4374 4375 _('[OPTION]... FILE...')),
4375 4376 "rename|mv":
4376 4377 (rename,
4377 4378 [('A', 'after', None, _('record a rename that has already occurred')),
4378 4379 ('f', 'force', None,
4379 4380 _('forcibly copy over an existing managed file')),
4380 4381 ] + walkopts + dryrunopts,
4381 4382 _('[OPTION]... SOURCE... DEST')),
4382 4383 "resolve":
4383 4384 (resolve,
4384 4385 [('a', 'all', None, _('select all unresolved files')),
4385 4386 ('l', 'list', None, _('list state of files needing merge')),
4386 4387 ('m', 'mark', None, _('mark files as resolved')),
4387 4388 ('u', 'unmark', None, _('mark files as unresolved')),
4388 4389 ('n', 'no-status', None, _('hide status prefix'))]
4389 4390 + walkopts,
4390 4391 _('[OPTION]... [FILE]...')),
4391 4392 "revert":
4392 4393 (revert,
4393 4394 [('a', 'all', None, _('revert all changes when no arguments given')),
4394 4395 ('d', 'date', '',
4395 4396 _('tipmost revision matching date'), _('DATE')),
4396 4397 ('r', 'rev', '',
4397 4398 _('revert to the specified revision'), _('REV')),
4398 4399 ('', 'no-backup', None, _('do not save backup copies of files')),
4399 4400 ] + walkopts + dryrunopts,
4400 4401 _('[OPTION]... [-r REV] [NAME]...')),
4401 4402 "rollback": (rollback, dryrunopts),
4402 4403 "root": (root, []),
4403 4404 "^serve":
4404 4405 (serve,
4405 4406 [('A', 'accesslog', '',
4406 4407 _('name of access log file to write to'), _('FILE')),
4407 4408 ('d', 'daemon', None, _('run server in background')),
4408 4409 ('', 'daemon-pipefds', '',
4409 4410 _('used internally by daemon mode'), _('NUM')),
4410 4411 ('E', 'errorlog', '',
4411 4412 _('name of error log file to write to'), _('FILE')),
4412 4413 # use string type, then we can check if something was passed
4413 4414 ('p', 'port', '',
4414 4415 _('port to listen on (default: 8000)'), _('PORT')),
4415 4416 ('a', 'address', '',
4416 4417 _('address to listen on (default: all interfaces)'), _('ADDR')),
4417 4418 ('', 'prefix', '',
4418 4419 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4419 4420 ('n', 'name', '',
4420 4421 _('name to show in web pages (default: working directory)'),
4421 4422 _('NAME')),
4422 4423 ('', 'web-conf', '',
4423 4424 _('name of the hgweb config file (serve more than one repository)'),
4424 4425 _('FILE')),
4425 4426 ('', 'webdir-conf', '',
4426 4427 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4427 4428 ('', 'pid-file', '',
4428 4429 _('name of file to write process ID to'), _('FILE')),
4429 4430 ('', 'stdio', None, _('for remote clients')),
4430 4431 ('t', 'templates', '',
4431 4432 _('web templates to use'), _('TEMPLATE')),
4432 4433 ('', 'style', '',
4433 4434 _('template style to use'), _('STYLE')),
4434 4435 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4435 4436 ('', 'certificate', '',
4436 4437 _('SSL certificate file'), _('FILE'))],
4437 4438 _('[OPTION]...')),
4438 4439 "showconfig|debugconfig":
4439 4440 (showconfig,
4440 4441 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4441 4442 _('[-u] [NAME]...')),
4442 4443 "^summary|sum":
4443 4444 (summary,
4444 4445 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4445 4446 "^status|st":
4446 4447 (status,
4447 4448 [('A', 'all', None, _('show status of all files')),
4448 4449 ('m', 'modified', None, _('show only modified files')),
4449 4450 ('a', 'added', None, _('show only added files')),
4450 4451 ('r', 'removed', None, _('show only removed files')),
4451 4452 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4452 4453 ('c', 'clean', None, _('show only files without changes')),
4453 4454 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4454 4455 ('i', 'ignored', None, _('show only ignored files')),
4455 4456 ('n', 'no-status', None, _('hide status prefix')),
4456 4457 ('C', 'copies', None, _('show source of copied files')),
4457 4458 ('0', 'print0', None,
4458 4459 _('end filenames with NUL, for use with xargs')),
4459 4460 ('', 'rev', [],
4460 4461 _('show difference from revision'), _('REV')),
4461 4462 ('', 'change', '',
4462 4463 _('list the changed files of a revision'), _('REV')),
4463 4464 ] + walkopts + subrepoopts,
4464 4465 _('[OPTION]... [FILE]...')),
4465 4466 "tag":
4466 4467 (tag,
4467 4468 [('f', 'force', None, _('replace existing tag')),
4468 4469 ('l', 'local', None, _('make the tag local')),
4469 4470 ('r', 'rev', '',
4470 4471 _('revision to tag'), _('REV')),
4471 4472 ('', 'remove', None, _('remove a tag')),
4472 4473 # -l/--local is already there, commitopts cannot be used
4473 4474 ('e', 'edit', None, _('edit commit message')),
4474 4475 ('m', 'message', '',
4475 4476 _('use <text> as commit message'), _('TEXT')),
4476 4477 ] + commitopts2,
4477 4478 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4478 4479 "tags": (tags, [], ''),
4479 4480 "tip":
4480 4481 (tip,
4481 4482 [('p', 'patch', None, _('show patch')),
4482 4483 ('g', 'git', None, _('use git extended diff format')),
4483 4484 ] + templateopts,
4484 4485 _('[-p] [-g]')),
4485 4486 "unbundle":
4486 4487 (unbundle,
4487 4488 [('u', 'update', None,
4488 4489 _('update to new branch head if changesets were unbundled'))],
4489 4490 _('[-u] FILE...')),
4490 4491 "^update|up|checkout|co":
4491 4492 (update,
4492 4493 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4493 4494 ('c', 'check', None, _('check for uncommitted changes')),
4494 4495 ('d', 'date', '',
4495 4496 _('tipmost revision matching date'), _('DATE')),
4496 4497 ('r', 'rev', '',
4497 4498 _('revision'), _('REV'))],
4498 4499 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4499 4500 "verify": (verify, []),
4500 4501 "version": (version_, []),
4501 4502 }
4502 4503
4503 4504 norepo = ("clone init version help debugcommands debugcomplete"
4504 4505 " debugdate debuginstall debugfsinfo debugpushkey")
4505 4506 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4506 4507 " debugdata debugindex debugindexdot")
@@ -1,1709 +1,1715 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.Parser, os, re
10 10 import tempfile, zlib
11 11
12 12 from i18n import _
13 13 from node import hex, nullid, short
14 14 import base85, cmdutil, mdiff, util, diffhelpers, copies, encoding
15 15
16 16 gitre = re.compile('diff --git a/(.*) b/(.*)')
17 17
18 18 class PatchError(Exception):
19 19 pass
20 20
21 21 class NoHunks(PatchError):
22 22 pass
23 23
24 24 # helper functions
25 25
26 26 def copyfile(src, dst, basedir):
27 27 abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]]
28 28 if os.path.exists(absdst):
29 29 raise util.Abort(_("cannot create %s: destination already exists") %
30 30 dst)
31 31
32 32 dstdir = os.path.dirname(absdst)
33 33 if dstdir and not os.path.isdir(dstdir):
34 34 try:
35 35 os.makedirs(dstdir)
36 36 except IOError:
37 37 raise util.Abort(
38 38 _("cannot create %s: unable to create destination directory")
39 39 % dst)
40 40
41 41 util.copyfile(abssrc, absdst)
42 42
43 43 # public functions
44 44
45 45 def split(stream):
46 46 '''return an iterator of individual patches from a stream'''
47 47 def isheader(line, inheader):
48 48 if inheader and line[0] in (' ', '\t'):
49 49 # continuation
50 50 return True
51 51 if line[0] in (' ', '-', '+'):
52 52 # diff line - don't check for header pattern in there
53 53 return False
54 54 l = line.split(': ', 1)
55 55 return len(l) == 2 and ' ' not in l[0]
56 56
57 57 def chunk(lines):
58 58 return cStringIO.StringIO(''.join(lines))
59 59
60 60 def hgsplit(stream, cur):
61 61 inheader = True
62 62
63 63 for line in stream:
64 64 if not line.strip():
65 65 inheader = False
66 66 if not inheader and line.startswith('# HG changeset patch'):
67 67 yield chunk(cur)
68 68 cur = []
69 69 inheader = True
70 70
71 71 cur.append(line)
72 72
73 73 if cur:
74 74 yield chunk(cur)
75 75
76 76 def mboxsplit(stream, cur):
77 77 for line in stream:
78 78 if line.startswith('From '):
79 79 for c in split(chunk(cur[1:])):
80 80 yield c
81 81 cur = []
82 82
83 83 cur.append(line)
84 84
85 85 if cur:
86 86 for c in split(chunk(cur[1:])):
87 87 yield c
88 88
89 89 def mimesplit(stream, cur):
90 90 def msgfp(m):
91 91 fp = cStringIO.StringIO()
92 92 g = email.Generator.Generator(fp, mangle_from_=False)
93 93 g.flatten(m)
94 94 fp.seek(0)
95 95 return fp
96 96
97 97 for line in stream:
98 98 cur.append(line)
99 99 c = chunk(cur)
100 100
101 101 m = email.Parser.Parser().parse(c)
102 102 if not m.is_multipart():
103 103 yield msgfp(m)
104 104 else:
105 105 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
106 106 for part in m.walk():
107 107 ct = part.get_content_type()
108 108 if ct not in ok_types:
109 109 continue
110 110 yield msgfp(part)
111 111
112 112 def headersplit(stream, cur):
113 113 inheader = False
114 114
115 115 for line in stream:
116 116 if not inheader and isheader(line, inheader):
117 117 yield chunk(cur)
118 118 cur = []
119 119 inheader = True
120 120 if inheader and not isheader(line, inheader):
121 121 inheader = False
122 122
123 123 cur.append(line)
124 124
125 125 if cur:
126 126 yield chunk(cur)
127 127
128 128 def remainder(cur):
129 129 yield chunk(cur)
130 130
131 131 class fiter(object):
132 132 def __init__(self, fp):
133 133 self.fp = fp
134 134
135 135 def __iter__(self):
136 136 return self
137 137
138 138 def next(self):
139 139 l = self.fp.readline()
140 140 if not l:
141 141 raise StopIteration
142 142 return l
143 143
144 144 inheader = False
145 145 cur = []
146 146
147 147 mimeheaders = ['content-type']
148 148
149 149 if not hasattr(stream, 'next'):
150 150 # http responses, for example, have readline but not next
151 151 stream = fiter(stream)
152 152
153 153 for line in stream:
154 154 cur.append(line)
155 155 if line.startswith('# HG changeset patch'):
156 156 return hgsplit(stream, cur)
157 157 elif line.startswith('From '):
158 158 return mboxsplit(stream, cur)
159 159 elif isheader(line, inheader):
160 160 inheader = True
161 161 if line.split(':', 1)[0].lower() in mimeheaders:
162 162 # let email parser handle this
163 163 return mimesplit(stream, cur)
164 164 elif line.startswith('--- ') and inheader:
165 165 # No evil headers seen by diff start, split by hand
166 166 return headersplit(stream, cur)
167 167 # Not enough info, keep reading
168 168
169 169 # if we are here, we have a very plain patch
170 170 return remainder(cur)
171 171
172 172 def extract(ui, fileobj):
173 173 '''extract patch from data read from fileobj.
174 174
175 175 patch can be a normal patch or contained in an email message.
176 176
177 177 return tuple (filename, message, user, date, branch, node, p1, p2).
178 178 Any item in the returned tuple can be None. If filename is None,
179 179 fileobj did not contain a patch. Caller must unlink filename when done.'''
180 180
181 181 # attempt to detect the start of a patch
182 182 # (this heuristic is borrowed from quilt)
183 183 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
184 184 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
185 185 r'---[ \t].*?^\+\+\+[ \t]|'
186 186 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
187 187
188 188 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
189 189 tmpfp = os.fdopen(fd, 'w')
190 190 try:
191 191 msg = email.Parser.Parser().parse(fileobj)
192 192
193 193 subject = msg['Subject']
194 194 user = msg['From']
195 195 if not subject and not user:
196 196 # Not an email, restore parsed headers if any
197 197 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
198 198
199 199 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
200 200 # should try to parse msg['Date']
201 201 date = None
202 202 nodeid = None
203 203 branch = None
204 204 parents = []
205 205
206 206 if subject:
207 207 if subject.startswith('[PATCH'):
208 208 pend = subject.find(']')
209 209 if pend >= 0:
210 210 subject = subject[pend + 1:].lstrip()
211 211 subject = subject.replace('\n\t', ' ')
212 212 ui.debug('Subject: %s\n' % subject)
213 213 if user:
214 214 ui.debug('From: %s\n' % user)
215 215 diffs_seen = 0
216 216 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
217 217 message = ''
218 218 for part in msg.walk():
219 219 content_type = part.get_content_type()
220 220 ui.debug('Content-Type: %s\n' % content_type)
221 221 if content_type not in ok_types:
222 222 continue
223 223 payload = part.get_payload(decode=True)
224 224 m = diffre.search(payload)
225 225 if m:
226 226 hgpatch = False
227 227 ignoretext = False
228 228
229 229 ui.debug('found patch at byte %d\n' % m.start(0))
230 230 diffs_seen += 1
231 231 cfp = cStringIO.StringIO()
232 232 for line in payload[:m.start(0)].splitlines():
233 233 if line.startswith('# HG changeset patch'):
234 234 ui.debug('patch generated by hg export\n')
235 235 hgpatch = True
236 236 # drop earlier commit message content
237 237 cfp.seek(0)
238 238 cfp.truncate()
239 239 subject = None
240 240 elif hgpatch:
241 241 if line.startswith('# User '):
242 242 user = line[7:]
243 243 ui.debug('From: %s\n' % user)
244 244 elif line.startswith("# Date "):
245 245 date = line[7:]
246 246 elif line.startswith("# Branch "):
247 247 branch = line[9:]
248 248 elif line.startswith("# Node ID "):
249 249 nodeid = line[10:]
250 250 elif line.startswith("# Parent "):
251 251 parents.append(line[10:])
252 252 elif line == '---' and gitsendmail:
253 253 ignoretext = True
254 254 if not line.startswith('# ') and not ignoretext:
255 255 cfp.write(line)
256 256 cfp.write('\n')
257 257 message = cfp.getvalue()
258 258 if tmpfp:
259 259 tmpfp.write(payload)
260 260 if not payload.endswith('\n'):
261 261 tmpfp.write('\n')
262 262 elif not diffs_seen and message and content_type == 'text/plain':
263 263 message += '\n' + payload
264 264 except:
265 265 tmpfp.close()
266 266 os.unlink(tmpname)
267 267 raise
268 268
269 269 if subject and not message.startswith(subject):
270 270 message = '%s\n%s' % (subject, message)
271 271 tmpfp.close()
272 272 if not diffs_seen:
273 273 os.unlink(tmpname)
274 274 return None, message, user, date, branch, None, None, None
275 275 p1 = parents and parents.pop(0) or None
276 276 p2 = parents and parents.pop(0) or None
277 277 return tmpname, message, user, date, branch, nodeid, p1, p2
278 278
279 279 GP_PATCH = 1 << 0 # we have to run patch
280 280 GP_FILTER = 1 << 1 # there's some copy/rename operation
281 281 GP_BINARY = 1 << 2 # there's a binary patch
282 282
283 283 class patchmeta(object):
284 284 """Patched file metadata
285 285
286 286 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
287 287 or COPY. 'path' is patched file path. 'oldpath' is set to the
288 288 origin file when 'op' is either COPY or RENAME, None otherwise. If
289 289 file mode is changed, 'mode' is a tuple (islink, isexec) where
290 290 'islink' is True if the file is a symlink and 'isexec' is True if
291 291 the file is executable. Otherwise, 'mode' is None.
292 292 """
293 293 def __init__(self, path):
294 294 self.path = path
295 295 self.oldpath = None
296 296 self.mode = None
297 297 self.op = 'MODIFY'
298 298 self.lineno = 0
299 299 self.binary = False
300 300
301 301 def setmode(self, mode):
302 302 islink = mode & 020000
303 303 isexec = mode & 0100
304 304 self.mode = (islink, isexec)
305 305
306 306 def __repr__(self):
307 307 return "<patchmeta %s %r>" % (self.op, self.path)
308 308
309 309 def readgitpatch(lr):
310 310 """extract git-style metadata about patches from <patchname>"""
311 311
312 312 # Filter patch for git information
313 313 gp = None
314 314 gitpatches = []
315 315 # Can have a git patch with only metadata, causing patch to complain
316 316 dopatch = 0
317 317
318 318 lineno = 0
319 319 for line in lr:
320 320 lineno += 1
321 321 line = line.rstrip(' \r\n')
322 322 if line.startswith('diff --git'):
323 323 m = gitre.match(line)
324 324 if m:
325 325 if gp:
326 326 gitpatches.append(gp)
327 327 dst = m.group(2)
328 328 gp = patchmeta(dst)
329 329 gp.lineno = lineno
330 330 elif gp:
331 331 if line.startswith('--- '):
332 332 if gp.op in ('COPY', 'RENAME'):
333 333 dopatch |= GP_FILTER
334 334 gitpatches.append(gp)
335 335 gp = None
336 336 dopatch |= GP_PATCH
337 337 continue
338 338 if line.startswith('rename from '):
339 339 gp.op = 'RENAME'
340 340 gp.oldpath = line[12:]
341 341 elif line.startswith('rename to '):
342 342 gp.path = line[10:]
343 343 elif line.startswith('copy from '):
344 344 gp.op = 'COPY'
345 345 gp.oldpath = line[10:]
346 346 elif line.startswith('copy to '):
347 347 gp.path = line[8:]
348 348 elif line.startswith('deleted file'):
349 349 gp.op = 'DELETE'
350 350 elif line.startswith('new file mode '):
351 351 gp.op = 'ADD'
352 352 gp.setmode(int(line[-6:], 8))
353 353 elif line.startswith('new mode '):
354 354 gp.setmode(int(line[-6:], 8))
355 355 elif line.startswith('GIT binary patch'):
356 356 dopatch |= GP_BINARY
357 357 gp.binary = True
358 358 if gp:
359 359 gitpatches.append(gp)
360 360
361 361 if not gitpatches:
362 362 dopatch = GP_PATCH
363 363
364 364 return (dopatch, gitpatches)
365 365
366 366 class linereader(object):
367 367 # simple class to allow pushing lines back into the input stream
368 368 def __init__(self, fp, textmode=False):
369 369 self.fp = fp
370 370 self.buf = []
371 371 self.textmode = textmode
372 372 self.eol = None
373 373
374 374 def push(self, line):
375 375 if line is not None:
376 376 self.buf.append(line)
377 377
378 378 def readline(self):
379 379 if self.buf:
380 380 l = self.buf[0]
381 381 del self.buf[0]
382 382 return l
383 383 l = self.fp.readline()
384 384 if not self.eol:
385 385 if l.endswith('\r\n'):
386 386 self.eol = '\r\n'
387 387 elif l.endswith('\n'):
388 388 self.eol = '\n'
389 389 if self.textmode and l.endswith('\r\n'):
390 390 l = l[:-2] + '\n'
391 391 return l
392 392
393 393 def __iter__(self):
394 394 while 1:
395 395 l = self.readline()
396 396 if not l:
397 397 break
398 398 yield l
399 399
400 400 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
401 401 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
402 402 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
403 403 eolmodes = ['strict', 'crlf', 'lf', 'auto']
404 404
405 405 class patchfile(object):
406 406 def __init__(self, ui, fname, opener, missing=False, eolmode='strict'):
407 407 self.fname = fname
408 408 self.eolmode = eolmode
409 409 self.eol = None
410 410 self.opener = opener
411 411 self.ui = ui
412 412 self.lines = []
413 413 self.exists = False
414 414 self.missing = missing
415 415 if not missing:
416 416 try:
417 417 self.lines = self.readlines(fname)
418 418 self.exists = True
419 419 except IOError:
420 420 pass
421 421 else:
422 422 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
423 423
424 424 self.hash = {}
425 425 self.dirty = 0
426 426 self.offset = 0
427 427 self.skew = 0
428 428 self.rej = []
429 429 self.fileprinted = False
430 430 self.printfile(False)
431 431 self.hunks = 0
432 432
433 433 def readlines(self, fname):
434 434 if os.path.islink(fname):
435 435 return [os.readlink(fname)]
436 436 fp = self.opener(fname, 'r')
437 437 try:
438 438 lr = linereader(fp, self.eolmode != 'strict')
439 439 lines = list(lr)
440 440 self.eol = lr.eol
441 441 return lines
442 442 finally:
443 443 fp.close()
444 444
445 445 def writelines(self, fname, lines):
446 446 # Ensure supplied data ends in fname, being a regular file or
447 447 # a symlink. updatedir() will -too magically- take care of
448 448 # setting it to the proper type afterwards.
449 449 islink = os.path.islink(fname)
450 450 if islink:
451 451 fp = cStringIO.StringIO()
452 452 else:
453 453 fp = self.opener(fname, 'w')
454 454 try:
455 455 if self.eolmode == 'auto':
456 456 eol = self.eol
457 457 elif self.eolmode == 'crlf':
458 458 eol = '\r\n'
459 459 else:
460 460 eol = '\n'
461 461
462 462 if self.eolmode != 'strict' and eol and eol != '\n':
463 463 for l in lines:
464 464 if l and l[-1] == '\n':
465 465 l = l[:-1] + eol
466 466 fp.write(l)
467 467 else:
468 468 fp.writelines(lines)
469 469 if islink:
470 470 self.opener.symlink(fp.getvalue(), fname)
471 471 finally:
472 472 fp.close()
473 473
474 474 def unlink(self, fname):
475 475 os.unlink(fname)
476 476
477 477 def printfile(self, warn):
478 478 if self.fileprinted:
479 479 return
480 480 if warn or self.ui.verbose:
481 481 self.fileprinted = True
482 482 s = _("patching file %s\n") % self.fname
483 483 if warn:
484 484 self.ui.warn(s)
485 485 else:
486 486 self.ui.note(s)
487 487
488 488
489 489 def findlines(self, l, linenum):
490 490 # looks through the hash and finds candidate lines. The
491 491 # result is a list of line numbers sorted based on distance
492 492 # from linenum
493 493
494 494 cand = self.hash.get(l, [])
495 495 if len(cand) > 1:
496 496 # resort our list of potentials forward then back.
497 497 cand.sort(key=lambda x: abs(x - linenum))
498 498 return cand
499 499
500 500 def hashlines(self):
501 501 self.hash = {}
502 502 for x, s in enumerate(self.lines):
503 503 self.hash.setdefault(s, []).append(x)
504 504
505 505 def write_rej(self):
506 506 # our rejects are a little different from patch(1). This always
507 507 # creates rejects in the same form as the original patch. A file
508 508 # header is inserted so that you can run the reject through patch again
509 509 # without having to type the filename.
510 510
511 511 if not self.rej:
512 512 return
513 513
514 514 fname = self.fname + ".rej"
515 515 self.ui.warn(
516 516 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
517 517 (len(self.rej), self.hunks, fname))
518 518
519 519 def rejlines():
520 520 base = os.path.basename(self.fname)
521 521 yield "--- %s\n+++ %s\n" % (base, base)
522 522 for x in self.rej:
523 523 for l in x.hunk:
524 524 yield l
525 525 if l[-1] != '\n':
526 526 yield "\n\ No newline at end of file\n"
527 527
528 528 self.writelines(fname, rejlines())
529 529
530 530 def apply(self, h):
531 531 if not h.complete():
532 532 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
533 533 (h.number, h.desc, len(h.a), h.lena, len(h.b),
534 534 h.lenb))
535 535
536 536 self.hunks += 1
537 537
538 538 if self.missing:
539 539 self.rej.append(h)
540 540 return -1
541 541
542 542 if self.exists and h.createfile():
543 543 self.ui.warn(_("file %s already exists\n") % self.fname)
544 544 self.rej.append(h)
545 545 return -1
546 546
547 547 if isinstance(h, binhunk):
548 548 if h.rmfile():
549 549 self.unlink(self.fname)
550 550 else:
551 551 self.lines[:] = h.new()
552 552 self.offset += len(h.new())
553 553 self.dirty = 1
554 554 return 0
555 555
556 556 horig = h
557 557 if (self.eolmode in ('crlf', 'lf')
558 558 or self.eolmode == 'auto' and self.eol):
559 559 # If new eols are going to be normalized, then normalize
560 560 # hunk data before patching. Otherwise, preserve input
561 561 # line-endings.
562 562 h = h.getnormalized()
563 563
564 564 # fast case first, no offsets, no fuzz
565 565 old = h.old()
566 566 # patch starts counting at 1 unless we are adding the file
567 567 if h.starta == 0:
568 568 start = 0
569 569 else:
570 570 start = h.starta + self.offset - 1
571 571 orig_start = start
572 572 # if there's skew we want to emit the "(offset %d lines)" even
573 573 # when the hunk cleanly applies at start + skew, so skip the
574 574 # fast case code
575 575 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
576 576 if h.rmfile():
577 577 self.unlink(self.fname)
578 578 else:
579 579 self.lines[start : start + h.lena] = h.new()
580 580 self.offset += h.lenb - h.lena
581 581 self.dirty = 1
582 582 return 0
583 583
584 584 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
585 585 self.hashlines()
586 586 if h.hunk[-1][0] != ' ':
587 587 # if the hunk tried to put something at the bottom of the file
588 588 # override the start line and use eof here
589 589 search_start = len(self.lines)
590 590 else:
591 591 search_start = orig_start + self.skew
592 592
593 593 for fuzzlen in xrange(3):
594 594 for toponly in [True, False]:
595 595 old = h.old(fuzzlen, toponly)
596 596
597 597 cand = self.findlines(old[0][1:], search_start)
598 598 for l in cand:
599 599 if diffhelpers.testhunk(old, self.lines, l) == 0:
600 600 newlines = h.new(fuzzlen, toponly)
601 601 self.lines[l : l + len(old)] = newlines
602 602 self.offset += len(newlines) - len(old)
603 603 self.skew = l - orig_start
604 604 self.dirty = 1
605 605 offset = l - orig_start - fuzzlen
606 606 if fuzzlen:
607 607 msg = _("Hunk #%d succeeded at %d "
608 608 "with fuzz %d "
609 609 "(offset %d lines).\n")
610 610 self.printfile(True)
611 611 self.ui.warn(msg %
612 612 (h.number, l + 1, fuzzlen, offset))
613 613 else:
614 614 msg = _("Hunk #%d succeeded at %d "
615 615 "(offset %d lines).\n")
616 616 self.ui.note(msg % (h.number, l + 1, offset))
617 617 return fuzzlen
618 618 self.printfile(True)
619 619 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
620 620 self.rej.append(horig)
621 621 return -1
622 622
623 623 class hunk(object):
624 624 def __init__(self, desc, num, lr, context, create=False, remove=False):
625 625 self.number = num
626 626 self.desc = desc
627 627 self.hunk = [desc]
628 628 self.a = []
629 629 self.b = []
630 630 self.starta = self.lena = None
631 631 self.startb = self.lenb = None
632 632 if lr is not None:
633 633 if context:
634 634 self.read_context_hunk(lr)
635 635 else:
636 636 self.read_unified_hunk(lr)
637 637 self.create = create
638 638 self.remove = remove and not create
639 639
640 640 def getnormalized(self):
641 641 """Return a copy with line endings normalized to LF."""
642 642
643 643 def normalize(lines):
644 644 nlines = []
645 645 for line in lines:
646 646 if line.endswith('\r\n'):
647 647 line = line[:-2] + '\n'
648 648 nlines.append(line)
649 649 return nlines
650 650
651 651 # Dummy object, it is rebuilt manually
652 652 nh = hunk(self.desc, self.number, None, None, False, False)
653 653 nh.number = self.number
654 654 nh.desc = self.desc
655 655 nh.hunk = self.hunk
656 656 nh.a = normalize(self.a)
657 657 nh.b = normalize(self.b)
658 658 nh.starta = self.starta
659 659 nh.startb = self.startb
660 660 nh.lena = self.lena
661 661 nh.lenb = self.lenb
662 662 nh.create = self.create
663 663 nh.remove = self.remove
664 664 return nh
665 665
666 666 def read_unified_hunk(self, lr):
667 667 m = unidesc.match(self.desc)
668 668 if not m:
669 669 raise PatchError(_("bad hunk #%d") % self.number)
670 670 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
671 671 if self.lena is None:
672 672 self.lena = 1
673 673 else:
674 674 self.lena = int(self.lena)
675 675 if self.lenb is None:
676 676 self.lenb = 1
677 677 else:
678 678 self.lenb = int(self.lenb)
679 679 self.starta = int(self.starta)
680 680 self.startb = int(self.startb)
681 681 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
682 682 # if we hit eof before finishing out the hunk, the last line will
683 683 # be zero length. Lets try to fix it up.
684 684 while len(self.hunk[-1]) == 0:
685 685 del self.hunk[-1]
686 686 del self.a[-1]
687 687 del self.b[-1]
688 688 self.lena -= 1
689 689 self.lenb -= 1
690 690
691 691 def read_context_hunk(self, lr):
692 692 self.desc = lr.readline()
693 693 m = contextdesc.match(self.desc)
694 694 if not m:
695 695 raise PatchError(_("bad hunk #%d") % self.number)
696 696 foo, self.starta, foo2, aend, foo3 = m.groups()
697 697 self.starta = int(self.starta)
698 698 if aend is None:
699 699 aend = self.starta
700 700 self.lena = int(aend) - self.starta
701 701 if self.starta:
702 702 self.lena += 1
703 703 for x in xrange(self.lena):
704 704 l = lr.readline()
705 705 if l.startswith('---'):
706 706 lr.push(l)
707 707 break
708 708 s = l[2:]
709 709 if l.startswith('- ') or l.startswith('! '):
710 710 u = '-' + s
711 711 elif l.startswith(' '):
712 712 u = ' ' + s
713 713 else:
714 714 raise PatchError(_("bad hunk #%d old text line %d") %
715 715 (self.number, x))
716 716 self.a.append(u)
717 717 self.hunk.append(u)
718 718
719 719 l = lr.readline()
720 720 if l.startswith('\ '):
721 721 s = self.a[-1][:-1]
722 722 self.a[-1] = s
723 723 self.hunk[-1] = s
724 724 l = lr.readline()
725 725 m = contextdesc.match(l)
726 726 if not m:
727 727 raise PatchError(_("bad hunk #%d") % self.number)
728 728 foo, self.startb, foo2, bend, foo3 = m.groups()
729 729 self.startb = int(self.startb)
730 730 if bend is None:
731 731 bend = self.startb
732 732 self.lenb = int(bend) - self.startb
733 733 if self.startb:
734 734 self.lenb += 1
735 735 hunki = 1
736 736 for x in xrange(self.lenb):
737 737 l = lr.readline()
738 738 if l.startswith('\ '):
739 739 s = self.b[-1][:-1]
740 740 self.b[-1] = s
741 741 self.hunk[hunki - 1] = s
742 742 continue
743 743 if not l:
744 744 lr.push(l)
745 745 break
746 746 s = l[2:]
747 747 if l.startswith('+ ') or l.startswith('! '):
748 748 u = '+' + s
749 749 elif l.startswith(' '):
750 750 u = ' ' + s
751 751 elif len(self.b) == 0:
752 752 # this can happen when the hunk does not add any lines
753 753 lr.push(l)
754 754 break
755 755 else:
756 756 raise PatchError(_("bad hunk #%d old text line %d") %
757 757 (self.number, x))
758 758 self.b.append(s)
759 759 while True:
760 760 if hunki >= len(self.hunk):
761 761 h = ""
762 762 else:
763 763 h = self.hunk[hunki]
764 764 hunki += 1
765 765 if h == u:
766 766 break
767 767 elif h.startswith('-'):
768 768 continue
769 769 else:
770 770 self.hunk.insert(hunki - 1, u)
771 771 break
772 772
773 773 if not self.a:
774 774 # this happens when lines were only added to the hunk
775 775 for x in self.hunk:
776 776 if x.startswith('-') or x.startswith(' '):
777 777 self.a.append(x)
778 778 if not self.b:
779 779 # this happens when lines were only deleted from the hunk
780 780 for x in self.hunk:
781 781 if x.startswith('+') or x.startswith(' '):
782 782 self.b.append(x[1:])
783 783 # @@ -start,len +start,len @@
784 784 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
785 785 self.startb, self.lenb)
786 786 self.hunk[0] = self.desc
787 787
788 788 def fix_newline(self):
789 789 diffhelpers.fix_newline(self.hunk, self.a, self.b)
790 790
791 791 def complete(self):
792 792 return len(self.a) == self.lena and len(self.b) == self.lenb
793 793
794 794 def createfile(self):
795 795 return self.starta == 0 and self.lena == 0 and self.create
796 796
797 797 def rmfile(self):
798 798 return self.startb == 0 and self.lenb == 0 and self.remove
799 799
800 800 def fuzzit(self, l, fuzz, toponly):
801 801 # this removes context lines from the top and bottom of list 'l'. It
802 802 # checks the hunk to make sure only context lines are removed, and then
803 803 # returns a new shortened list of lines.
804 804 fuzz = min(fuzz, len(l)-1)
805 805 if fuzz:
806 806 top = 0
807 807 bot = 0
808 808 hlen = len(self.hunk)
809 809 for x in xrange(hlen - 1):
810 810 # the hunk starts with the @@ line, so use x+1
811 811 if self.hunk[x + 1][0] == ' ':
812 812 top += 1
813 813 else:
814 814 break
815 815 if not toponly:
816 816 for x in xrange(hlen - 1):
817 817 if self.hunk[hlen - bot - 1][0] == ' ':
818 818 bot += 1
819 819 else:
820 820 break
821 821
822 822 # top and bot now count context in the hunk
823 823 # adjust them if either one is short
824 824 context = max(top, bot, 3)
825 825 if bot < context:
826 826 bot = max(0, fuzz - (context - bot))
827 827 else:
828 828 bot = min(fuzz, bot)
829 829 if top < context:
830 830 top = max(0, fuzz - (context - top))
831 831 else:
832 832 top = min(fuzz, top)
833 833
834 834 return l[top:len(l)-bot]
835 835 return l
836 836
837 837 def old(self, fuzz=0, toponly=False):
838 838 return self.fuzzit(self.a, fuzz, toponly)
839 839
840 840 def new(self, fuzz=0, toponly=False):
841 841 return self.fuzzit(self.b, fuzz, toponly)
842 842
843 843 class binhunk:
844 844 'A binary patch file. Only understands literals so far.'
845 845 def __init__(self, gitpatch):
846 846 self.gitpatch = gitpatch
847 847 self.text = None
848 848 self.hunk = ['GIT binary patch\n']
849 849
850 850 def createfile(self):
851 851 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
852 852
853 853 def rmfile(self):
854 854 return self.gitpatch.op == 'DELETE'
855 855
856 856 def complete(self):
857 857 return self.text is not None
858 858
859 859 def new(self):
860 860 return [self.text]
861 861
862 862 def extract(self, lr):
863 863 line = lr.readline()
864 864 self.hunk.append(line)
865 865 while line and not line.startswith('literal '):
866 866 line = lr.readline()
867 867 self.hunk.append(line)
868 868 if not line:
869 869 raise PatchError(_('could not extract binary patch'))
870 870 size = int(line[8:].rstrip())
871 871 dec = []
872 872 line = lr.readline()
873 873 self.hunk.append(line)
874 874 while len(line) > 1:
875 875 l = line[0]
876 876 if l <= 'Z' and l >= 'A':
877 877 l = ord(l) - ord('A') + 1
878 878 else:
879 879 l = ord(l) - ord('a') + 27
880 880 dec.append(base85.b85decode(line[1:-1])[:l])
881 881 line = lr.readline()
882 882 self.hunk.append(line)
883 883 text = zlib.decompress(''.join(dec))
884 884 if len(text) != size:
885 885 raise PatchError(_('binary patch is %d bytes, not %d') %
886 886 len(text), size)
887 887 self.text = text
888 888
889 889 def parsefilename(str):
890 890 # --- filename \t|space stuff
891 891 s = str[4:].rstrip('\r\n')
892 892 i = s.find('\t')
893 893 if i < 0:
894 894 i = s.find(' ')
895 895 if i < 0:
896 896 return s
897 897 return s[:i]
898 898
899 899 def pathstrip(path, strip):
900 900 pathlen = len(path)
901 901 i = 0
902 902 if strip == 0:
903 903 return '', path.rstrip()
904 904 count = strip
905 905 while count > 0:
906 906 i = path.find('/', i)
907 907 if i == -1:
908 908 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
909 909 (count, strip, path))
910 910 i += 1
911 911 # consume '//' in the path
912 912 while i < pathlen - 1 and path[i] == '/':
913 913 i += 1
914 914 count -= 1
915 915 return path[:i].lstrip(), path[i:].rstrip()
916 916
917 917 def selectfile(afile_orig, bfile_orig, hunk, strip):
918 918 nulla = afile_orig == "/dev/null"
919 919 nullb = bfile_orig == "/dev/null"
920 920 abase, afile = pathstrip(afile_orig, strip)
921 921 gooda = not nulla and os.path.lexists(afile)
922 922 bbase, bfile = pathstrip(bfile_orig, strip)
923 923 if afile == bfile:
924 924 goodb = gooda
925 925 else:
926 926 goodb = not nullb and os.path.exists(bfile)
927 927 createfunc = hunk.createfile
928 928 missing = not goodb and not gooda and not createfunc()
929 929
930 930 # some diff programs apparently produce patches where the afile is
931 931 # not /dev/null, but afile starts with bfile
932 932 abasedir = afile[:afile.rfind('/') + 1]
933 933 bbasedir = bfile[:bfile.rfind('/') + 1]
934 934 if missing and abasedir == bbasedir and afile.startswith(bfile):
935 935 # this isn't very pretty
936 936 hunk.create = True
937 937 if createfunc():
938 938 missing = False
939 939 else:
940 940 hunk.create = False
941 941
942 942 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
943 943 # diff is between a file and its backup. In this case, the original
944 944 # file should be patched (see original mpatch code).
945 945 isbackup = (abase == bbase and bfile.startswith(afile))
946 946 fname = None
947 947 if not missing:
948 948 if gooda and goodb:
949 949 fname = isbackup and afile or bfile
950 950 elif gooda:
951 951 fname = afile
952 952
953 953 if not fname:
954 954 if not nullb:
955 955 fname = isbackup and afile or bfile
956 956 elif not nulla:
957 957 fname = afile
958 958 else:
959 959 raise PatchError(_("undefined source and destination files"))
960 960
961 961 return fname, missing
962 962
963 963 def scangitpatch(lr, firstline):
964 964 """
965 965 Git patches can emit:
966 966 - rename a to b
967 967 - change b
968 968 - copy a to c
969 969 - change c
970 970
971 971 We cannot apply this sequence as-is, the renamed 'a' could not be
972 972 found for it would have been renamed already. And we cannot copy
973 973 from 'b' instead because 'b' would have been changed already. So
974 974 we scan the git patch for copy and rename commands so we can
975 975 perform the copies ahead of time.
976 976 """
977 977 pos = 0
978 978 try:
979 979 pos = lr.fp.tell()
980 980 fp = lr.fp
981 981 except IOError:
982 982 fp = cStringIO.StringIO(lr.fp.read())
983 983 gitlr = linereader(fp, lr.textmode)
984 984 gitlr.push(firstline)
985 985 (dopatch, gitpatches) = readgitpatch(gitlr)
986 986 fp.seek(pos)
987 987 return dopatch, gitpatches
988 988
989 989 def iterhunks(ui, fp, sourcefile=None):
990 990 """Read a patch and yield the following events:
991 991 - ("file", afile, bfile, firsthunk): select a new target file.
992 992 - ("hunk", hunk): a new hunk is ready to be applied, follows a
993 993 "file" event.
994 994 - ("git", gitchanges): current diff is in git format, gitchanges
995 995 maps filenames to gitpatch records. Unique event.
996 996 """
997 997 changed = {}
998 998 current_hunk = None
999 999 afile = ""
1000 1000 bfile = ""
1001 1001 state = None
1002 1002 hunknum = 0
1003 1003 emitfile = False
1004 1004 git = False
1005 1005
1006 1006 # our states
1007 1007 BFILE = 1
1008 1008 context = None
1009 1009 lr = linereader(fp)
1010 1010 # gitworkdone is True if a git operation (copy, rename, ...) was
1011 1011 # performed already for the current file. Useful when the file
1012 1012 # section may have no hunk.
1013 1013 gitworkdone = False
1014 1014 empty = None
1015 1015
1016 1016 while True:
1017 1017 newfile = newgitfile = False
1018 1018 x = lr.readline()
1019 1019 if not x:
1020 1020 break
1021 1021 if current_hunk:
1022 1022 if x.startswith('\ '):
1023 1023 current_hunk.fix_newline()
1024 1024 yield 'hunk', current_hunk
1025 1025 current_hunk = None
1026 1026 empty = False
1027 1027 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
1028 1028 ((context is not False) and x.startswith('***************')))):
1029 1029 try:
1030 1030 if context is None and x.startswith('***************'):
1031 1031 context = True
1032 1032 gpatch = changed.get(bfile)
1033 1033 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
1034 1034 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
1035 1035 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
1036 1036 except PatchError, err:
1037 1037 ui.debug(err)
1038 1038 current_hunk = None
1039 1039 continue
1040 1040 hunknum += 1
1041 1041 if emitfile:
1042 1042 emitfile = False
1043 1043 yield 'file', (afile, bfile, current_hunk)
1044 1044 empty = False
1045 1045 elif state == BFILE and x.startswith('GIT binary patch'):
1046 1046 current_hunk = binhunk(changed[bfile])
1047 1047 hunknum += 1
1048 1048 if emitfile:
1049 1049 emitfile = False
1050 1050 yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
1051 1051 empty = False
1052 1052 current_hunk.extract(lr)
1053 1053 elif x.startswith('diff --git'):
1054 1054 # check for git diff, scanning the whole patch file if needed
1055 1055 m = gitre.match(x)
1056 1056 gitworkdone = False
1057 1057 if m:
1058 1058 afile, bfile = m.group(1, 2)
1059 1059 if not git:
1060 1060 git = True
1061 1061 gitpatches = scangitpatch(lr, x)[1]
1062 1062 yield 'git', gitpatches
1063 1063 for gp in gitpatches:
1064 1064 changed[gp.path] = gp
1065 1065 # else error?
1066 1066 # copy/rename + modify should modify target, not source
1067 1067 gp = changed.get(bfile)
1068 1068 if gp and (gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD')
1069 1069 or gp.mode):
1070 1070 afile = bfile
1071 1071 gitworkdone = True
1072 1072 newgitfile = True
1073 1073 elif x.startswith('---'):
1074 1074 # check for a unified diff
1075 1075 l2 = lr.readline()
1076 1076 if not l2.startswith('+++'):
1077 1077 lr.push(l2)
1078 1078 continue
1079 1079 newfile = True
1080 1080 context = False
1081 1081 afile = parsefilename(x)
1082 1082 bfile = parsefilename(l2)
1083 1083 elif x.startswith('***'):
1084 1084 # check for a context diff
1085 1085 l2 = lr.readline()
1086 1086 if not l2.startswith('---'):
1087 1087 lr.push(l2)
1088 1088 continue
1089 1089 l3 = lr.readline()
1090 1090 lr.push(l3)
1091 1091 if not l3.startswith("***************"):
1092 1092 lr.push(l2)
1093 1093 continue
1094 1094 newfile = True
1095 1095 context = True
1096 1096 afile = parsefilename(x)
1097 1097 bfile = parsefilename(l2)
1098 1098
1099 1099 if newfile:
1100 1100 if empty:
1101 1101 raise NoHunks
1102 1102 empty = not gitworkdone
1103 1103 gitworkdone = False
1104 1104
1105 1105 if newgitfile or newfile:
1106 1106 emitfile = True
1107 1107 state = BFILE
1108 1108 hunknum = 0
1109 1109 if current_hunk:
1110 1110 if current_hunk.complete():
1111 1111 yield 'hunk', current_hunk
1112 1112 empty = False
1113 1113 else:
1114 1114 raise PatchError(_("malformed patch %s %s") % (afile,
1115 1115 current_hunk.desc))
1116 1116
1117 1117 if (empty is None and not gitworkdone) or empty:
1118 1118 raise NoHunks
1119 1119
1120 1120
1121 1121 def applydiff(ui, fp, changed, strip=1, sourcefile=None, eolmode='strict'):
1122 1122 """Reads a patch from fp and tries to apply it.
1123 1123
1124 1124 The dict 'changed' is filled in with all of the filenames changed
1125 1125 by the patch. Returns 0 for a clean patch, -1 if any rejects were
1126 1126 found and 1 if there was any fuzz.
1127 1127
1128 1128 If 'eolmode' is 'strict', the patch content and patched file are
1129 1129 read in binary mode. Otherwise, line endings are ignored when
1130 1130 patching then normalized according to 'eolmode'.
1131 1131
1132 1132 Callers probably want to call 'updatedir' after this to apply
1133 1133 certain categories of changes not done by this function.
1134 1134 """
1135 1135 return _applydiff(
1136 1136 ui, fp, patchfile, copyfile,
1137 1137 changed, strip=strip, sourcefile=sourcefile, eolmode=eolmode)
1138 1138
1139 1139
1140 1140 def _applydiff(ui, fp, patcher, copyfn, changed, strip=1,
1141 1141 sourcefile=None, eolmode='strict'):
1142 1142 rejects = 0
1143 1143 err = 0
1144 1144 current_file = None
1145 1145 cwd = os.getcwd()
1146 1146 opener = util.opener(cwd)
1147 1147
1148 1148 def closefile():
1149 1149 if not current_file:
1150 1150 return 0
1151 1151 if current_file.dirty:
1152 1152 current_file.writelines(current_file.fname, current_file.lines)
1153 1153 current_file.write_rej()
1154 1154 return len(current_file.rej)
1155 1155
1156 1156 for state, values in iterhunks(ui, fp, sourcefile):
1157 1157 if state == 'hunk':
1158 1158 if not current_file:
1159 1159 continue
1160 1160 ret = current_file.apply(values)
1161 1161 if ret >= 0:
1162 1162 changed.setdefault(current_file.fname, None)
1163 1163 if ret > 0:
1164 1164 err = 1
1165 1165 elif state == 'file':
1166 1166 rejects += closefile()
1167 1167 afile, bfile, first_hunk = values
1168 1168 try:
1169 1169 if sourcefile:
1170 1170 current_file = patcher(ui, sourcefile, opener,
1171 1171 eolmode=eolmode)
1172 1172 else:
1173 1173 current_file, missing = selectfile(afile, bfile,
1174 1174 first_hunk, strip)
1175 1175 current_file = patcher(ui, current_file, opener,
1176 1176 missing=missing, eolmode=eolmode)
1177 1177 except PatchError, err:
1178 1178 ui.warn(str(err) + '\n')
1179 1179 current_file = None
1180 1180 rejects += 1
1181 1181 continue
1182 1182 elif state == 'git':
1183 1183 for gp in values:
1184 1184 gp.path = pathstrip(gp.path, strip - 1)[1]
1185 1185 if gp.oldpath:
1186 1186 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1187 1187 if gp.op in ('COPY', 'RENAME'):
1188 1188 copyfn(gp.oldpath, gp.path, cwd)
1189 1189 changed[gp.path] = gp
1190 1190 else:
1191 1191 raise util.Abort(_('unsupported parser state: %s') % state)
1192 1192
1193 1193 rejects += closefile()
1194 1194
1195 1195 if rejects:
1196 1196 return -1
1197 1197 return err
1198 1198
1199 1199 def updatedir(ui, repo, patches, similarity=0):
1200 1200 '''Update dirstate after patch application according to metadata'''
1201 1201 if not patches:
1202 1202 return
1203 1203 copies = []
1204 1204 removes = set()
1205 1205 cfiles = patches.keys()
1206 1206 cwd = repo.getcwd()
1207 1207 if cwd:
1208 1208 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1209 1209 for f in patches:
1210 1210 gp = patches[f]
1211 1211 if not gp:
1212 1212 continue
1213 1213 if gp.op == 'RENAME':
1214 1214 copies.append((gp.oldpath, gp.path))
1215 1215 removes.add(gp.oldpath)
1216 1216 elif gp.op == 'COPY':
1217 1217 copies.append((gp.oldpath, gp.path))
1218 1218 elif gp.op == 'DELETE':
1219 1219 removes.add(gp.path)
1220 1220
1221 1221 wctx = repo[None]
1222 1222 for src, dst in copies:
1223 1223 wctx.copy(src, dst)
1224 1224 if (not similarity) and removes:
1225 1225 wctx.remove(sorted(removes), True)
1226 1226
1227 1227 for f in patches:
1228 1228 gp = patches[f]
1229 1229 if gp and gp.mode:
1230 1230 islink, isexec = gp.mode
1231 1231 dst = repo.wjoin(gp.path)
1232 1232 # patch won't create empty files
1233 1233 if gp.op == 'ADD' and not os.path.exists(dst):
1234 1234 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1235 1235 repo.wwrite(gp.path, '', flags)
1236 1236 util.set_flags(dst, islink, isexec)
1237 1237 cmdutil.addremove(repo, cfiles, similarity=similarity)
1238 1238 files = patches.keys()
1239 1239 files.extend([r for r in removes if r not in files])
1240 1240 return sorted(files)
1241 1241
1242 1242 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1243 1243 """use <patcher> to apply <patchname> to the working directory.
1244 1244 returns whether patch was applied with fuzz factor."""
1245 1245
1246 1246 fuzz = False
1247 1247 if cwd:
1248 1248 args.append('-d %s' % util.shellquote(cwd))
1249 1249 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1250 1250 util.shellquote(patchname)))
1251 1251
1252 1252 for line in fp:
1253 1253 line = line.rstrip()
1254 1254 ui.note(line + '\n')
1255 1255 if line.startswith('patching file '):
1256 1256 pf = util.parse_patch_output(line)
1257 1257 printed_file = False
1258 1258 files.setdefault(pf, None)
1259 1259 elif line.find('with fuzz') >= 0:
1260 1260 fuzz = True
1261 1261 if not printed_file:
1262 1262 ui.warn(pf + '\n')
1263 1263 printed_file = True
1264 1264 ui.warn(line + '\n')
1265 1265 elif line.find('saving rejects to file') >= 0:
1266 1266 ui.warn(line + '\n')
1267 1267 elif line.find('FAILED') >= 0:
1268 1268 if not printed_file:
1269 1269 ui.warn(pf + '\n')
1270 1270 printed_file = True
1271 1271 ui.warn(line + '\n')
1272 1272 code = fp.close()
1273 1273 if code:
1274 1274 raise PatchError(_("patch command failed: %s") %
1275 1275 util.explain_exit(code)[0])
1276 1276 return fuzz
1277 1277
1278 1278 def internalpatch(patchobj, ui, strip, cwd, files=None, eolmode='strict'):
1279 1279 """use builtin patch to apply <patchobj> to the working directory.
1280 1280 returns whether patch was applied with fuzz factor."""
1281 1281
1282 1282 if files is None:
1283 1283 files = {}
1284 1284 if eolmode is None:
1285 1285 eolmode = ui.config('patch', 'eol', 'strict')
1286 1286 if eolmode.lower() not in eolmodes:
1287 1287 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1288 1288 eolmode = eolmode.lower()
1289 1289
1290 1290 try:
1291 1291 fp = open(patchobj, 'rb')
1292 1292 except TypeError:
1293 1293 fp = patchobj
1294 1294 if cwd:
1295 1295 curdir = os.getcwd()
1296 1296 os.chdir(cwd)
1297 1297 try:
1298 1298 ret = applydiff(ui, fp, files, strip=strip, eolmode=eolmode)
1299 1299 finally:
1300 1300 if cwd:
1301 1301 os.chdir(curdir)
1302 1302 if fp != patchobj:
1303 1303 fp.close()
1304 1304 if ret < 0:
1305 1305 raise PatchError
1306 1306 return ret > 0
1307 1307
1308 1308 def patch(patchname, ui, strip=1, cwd=None, files=None, eolmode='strict'):
1309 1309 """Apply <patchname> to the working directory.
1310 1310
1311 1311 'eolmode' specifies how end of lines should be handled. It can be:
1312 1312 - 'strict': inputs are read in binary mode, EOLs are preserved
1313 1313 - 'crlf': EOLs are ignored when patching and reset to CRLF
1314 1314 - 'lf': EOLs are ignored when patching and reset to LF
1315 1315 - None: get it from user settings, default to 'strict'
1316 1316 'eolmode' is ignored when using an external patcher program.
1317 1317
1318 1318 Returns whether patch was applied with fuzz factor.
1319 1319 """
1320 1320 patcher = ui.config('ui', 'patch')
1321 1321 args = []
1322 1322 if files is None:
1323 1323 files = {}
1324 1324 try:
1325 1325 if patcher:
1326 1326 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1327 1327 files)
1328 1328 else:
1329 1329 try:
1330 1330 return internalpatch(patchname, ui, strip, cwd, files, eolmode)
1331 1331 except NoHunks:
1332 1332 ui.warn(_('internal patcher failed\n'
1333 1333 'please report details to '
1334 1334 'http://mercurial.selenic.com/bts/\n'
1335 1335 'or mercurial@selenic.com\n'))
1336 1336 patcher = (util.find_exe('gpatch') or util.find_exe('patch')
1337 1337 or 'patch')
1338 1338 ui.debug('no valid hunks found; trying with %r instead\n' %
1339 1339 patcher)
1340 1340 if util.needbinarypatch():
1341 1341 args.append('--binary')
1342 1342 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1343 1343 files)
1344 1344 except PatchError, err:
1345 1345 s = str(err)
1346 1346 if s:
1347 1347 raise util.Abort(s)
1348 1348 else:
1349 1349 raise util.Abort(_('patch failed to apply'))
1350 1350
1351 1351 def b85diff(to, tn):
1352 1352 '''print base85-encoded binary diff'''
1353 1353 def gitindex(text):
1354 1354 if not text:
1355 1355 return hex(nullid)
1356 1356 l = len(text)
1357 1357 s = util.sha1('blob %d\0' % l)
1358 1358 s.update(text)
1359 1359 return s.hexdigest()
1360 1360
1361 1361 def fmtline(line):
1362 1362 l = len(line)
1363 1363 if l <= 26:
1364 1364 l = chr(ord('A') + l - 1)
1365 1365 else:
1366 1366 l = chr(l - 26 + ord('a') - 1)
1367 1367 return '%c%s\n' % (l, base85.b85encode(line, True))
1368 1368
1369 1369 def chunk(text, csize=52):
1370 1370 l = len(text)
1371 1371 i = 0
1372 1372 while i < l:
1373 1373 yield text[i:i + csize]
1374 1374 i += csize
1375 1375
1376 1376 tohash = gitindex(to)
1377 1377 tnhash = gitindex(tn)
1378 1378 if tohash == tnhash:
1379 1379 return ""
1380 1380
1381 1381 # TODO: deltas
1382 1382 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1383 1383 (tohash, tnhash, len(tn))]
1384 1384 for l in chunk(zlib.compress(tn)):
1385 1385 ret.append(fmtline(l))
1386 1386 ret.append('\n')
1387 1387 return ''.join(ret)
1388 1388
1389 1389 class GitDiffRequired(Exception):
1390 1390 pass
1391 1391
1392 1392 def diffopts(ui, opts=None, untrusted=False):
1393 1393 def get(key, name=None, getter=ui.configbool):
1394 1394 return ((opts and opts.get(key)) or
1395 1395 getter('diff', name or key, None, untrusted=untrusted))
1396 1396 return mdiff.diffopts(
1397 1397 text=opts and opts.get('text'),
1398 1398 git=get('git'),
1399 1399 nodates=get('nodates'),
1400 1400 showfunc=get('show_function', 'showfunc'),
1401 1401 ignorews=get('ignore_all_space', 'ignorews'),
1402 1402 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1403 1403 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1404 1404 context=get('unified', getter=ui.config))
1405 1405
1406 1406 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1407 losedatafn=None):
1407 losedatafn=None, prefix=''):
1408 1408 '''yields diff of changes to files between two nodes, or node and
1409 1409 working directory.
1410 1410
1411 1411 if node1 is None, use first dirstate parent instead.
1412 1412 if node2 is None, compare node1 with working directory.
1413 1413
1414 1414 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1415 1415 every time some change cannot be represented with the current
1416 1416 patch format. Return False to upgrade to git patch format, True to
1417 1417 accept the loss or raise an exception to abort the diff. It is
1418 1418 called with the name of current file being diffed as 'fn'. If set
1419 1419 to None, patches will always be upgraded to git format when
1420 1420 necessary.
1421
1422 prefix is a filename prefix that is prepended to all filenames on
1423 display (used for subrepos).
1421 1424 '''
1422 1425
1423 1426 if opts is None:
1424 1427 opts = mdiff.defaultopts
1425 1428
1426 1429 if not node1 and not node2:
1427 1430 node1 = repo.dirstate.parents()[0]
1428 1431
1429 1432 def lrugetfilectx():
1430 1433 cache = {}
1431 1434 order = []
1432 1435 def getfilectx(f, ctx):
1433 1436 fctx = ctx.filectx(f, filelog=cache.get(f))
1434 1437 if f not in cache:
1435 1438 if len(cache) > 20:
1436 1439 del cache[order.pop(0)]
1437 1440 cache[f] = fctx.filelog()
1438 1441 else:
1439 1442 order.remove(f)
1440 1443 order.append(f)
1441 1444 return fctx
1442 1445 return getfilectx
1443 1446 getfilectx = lrugetfilectx()
1444 1447
1445 1448 ctx1 = repo[node1]
1446 1449 ctx2 = repo[node2]
1447 1450
1448 1451 if not changes:
1449 1452 changes = repo.status(ctx1, ctx2, match=match)
1450 1453 modified, added, removed = changes[:3]
1451 1454
1452 1455 if not modified and not added and not removed:
1453 1456 return []
1454 1457
1455 1458 revs = None
1456 1459 if not repo.ui.quiet:
1457 1460 hexfunc = repo.ui.debugflag and hex or short
1458 1461 revs = [hexfunc(node) for node in [node1, node2] if node]
1459 1462
1460 1463 copy = {}
1461 1464 if opts.git or opts.upgrade:
1462 1465 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1463 1466
1464 1467 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1465 modified, added, removed, copy, getfilectx, opts, losedata)
1468 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1466 1469 if opts.upgrade and not opts.git:
1467 1470 try:
1468 1471 def losedata(fn):
1469 1472 if not losedatafn or not losedatafn(fn=fn):
1470 1473 raise GitDiffRequired()
1471 1474 # Buffer the whole output until we are sure it can be generated
1472 1475 return list(difffn(opts.copy(git=False), losedata))
1473 1476 except GitDiffRequired:
1474 1477 return difffn(opts.copy(git=True), None)
1475 1478 else:
1476 1479 return difffn(opts, None)
1477 1480
1478 1481 def difflabel(func, *args, **kw):
1479 1482 '''yields 2-tuples of (output, label) based on the output of func()'''
1480 1483 prefixes = [('diff', 'diff.diffline'),
1481 1484 ('copy', 'diff.extended'),
1482 1485 ('rename', 'diff.extended'),
1483 1486 ('old', 'diff.extended'),
1484 1487 ('new', 'diff.extended'),
1485 1488 ('deleted', 'diff.extended'),
1486 1489 ('---', 'diff.file_a'),
1487 1490 ('+++', 'diff.file_b'),
1488 1491 ('@@', 'diff.hunk'),
1489 1492 ('-', 'diff.deleted'),
1490 1493 ('+', 'diff.inserted')]
1491 1494
1492 1495 for chunk in func(*args, **kw):
1493 1496 lines = chunk.split('\n')
1494 1497 for i, line in enumerate(lines):
1495 1498 if i != 0:
1496 1499 yield ('\n', '')
1497 1500 stripline = line
1498 1501 if line and line[0] in '+-':
1499 1502 # highlight trailing whitespace, but only in changed lines
1500 1503 stripline = line.rstrip()
1501 1504 for prefix, label in prefixes:
1502 1505 if stripline.startswith(prefix):
1503 1506 yield (stripline, label)
1504 1507 break
1505 1508 else:
1506 1509 yield (line, '')
1507 1510 if line != stripline:
1508 1511 yield (line[len(stripline):], 'diff.trailingwhitespace')
1509 1512
1510 1513 def diffui(*args, **kw):
1511 1514 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1512 1515 return difflabel(diff, *args, **kw)
1513 1516
1514 1517
1515 1518 def _addmodehdr(header, omode, nmode):
1516 1519 if omode != nmode:
1517 1520 header.append('old mode %s\n' % omode)
1518 1521 header.append('new mode %s\n' % nmode)
1519 1522
1520 1523 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1521 copy, getfilectx, opts, losedatafn):
1524 copy, getfilectx, opts, losedatafn, prefix):
1525
1526 def join(f):
1527 return os.path.join(prefix, f)
1522 1528
1523 1529 date1 = util.datestr(ctx1.date())
1524 1530 man1 = ctx1.manifest()
1525 1531
1526 1532 gone = set()
1527 1533 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1528 1534
1529 1535 copyto = dict([(v, k) for k, v in copy.items()])
1530 1536
1531 1537 if opts.git:
1532 1538 revs = None
1533 1539
1534 1540 for f in sorted(modified + added + removed):
1535 1541 to = None
1536 1542 tn = None
1537 1543 dodiff = True
1538 1544 header = []
1539 1545 if f in man1:
1540 1546 to = getfilectx(f, ctx1).data()
1541 1547 if f not in removed:
1542 1548 tn = getfilectx(f, ctx2).data()
1543 1549 a, b = f, f
1544 1550 if opts.git or losedatafn:
1545 1551 if f in added:
1546 1552 mode = gitmode[ctx2.flags(f)]
1547 1553 if f in copy or f in copyto:
1548 1554 if opts.git:
1549 1555 if f in copy:
1550 1556 a = copy[f]
1551 1557 else:
1552 1558 a = copyto[f]
1553 1559 omode = gitmode[man1.flags(a)]
1554 1560 _addmodehdr(header, omode, mode)
1555 1561 if a in removed and a not in gone:
1556 1562 op = 'rename'
1557 1563 gone.add(a)
1558 1564 else:
1559 1565 op = 'copy'
1560 header.append('%s from %s\n' % (op, a))
1561 header.append('%s to %s\n' % (op, f))
1566 header.append('%s from %s\n' % (op, join(a)))
1567 header.append('%s to %s\n' % (op, join(f)))
1562 1568 to = getfilectx(a, ctx1).data()
1563 1569 else:
1564 1570 losedatafn(f)
1565 1571 else:
1566 1572 if opts.git:
1567 1573 header.append('new file mode %s\n' % mode)
1568 1574 elif ctx2.flags(f):
1569 1575 losedatafn(f)
1570 1576 if util.binary(tn):
1571 1577 if opts.git:
1572 1578 dodiff = 'binary'
1573 1579 else:
1574 1580 losedatafn(f)
1575 1581 if not opts.git and not tn:
1576 1582 # regular diffs cannot represent new empty file
1577 1583 losedatafn(f)
1578 1584 elif f in removed:
1579 1585 if opts.git:
1580 1586 # have we already reported a copy above?
1581 1587 if ((f in copy and copy[f] in added
1582 1588 and copyto[copy[f]] == f) or
1583 1589 (f in copyto and copyto[f] in added
1584 1590 and copy[copyto[f]] == f)):
1585 1591 dodiff = False
1586 1592 else:
1587 1593 header.append('deleted file mode %s\n' %
1588 1594 gitmode[man1.flags(f)])
1589 1595 elif not to:
1590 1596 # regular diffs cannot represent empty file deletion
1591 1597 losedatafn(f)
1592 1598 else:
1593 1599 oflag = man1.flags(f)
1594 1600 nflag = ctx2.flags(f)
1595 1601 binary = util.binary(to) or util.binary(tn)
1596 1602 if opts.git:
1597 1603 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1598 1604 if binary:
1599 1605 dodiff = 'binary'
1600 1606 elif binary or nflag != oflag:
1601 1607 losedatafn(f)
1602 1608 if opts.git:
1603 header.insert(0, mdiff.diffline(revs, a, b, opts))
1609 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1604 1610
1605 1611 if dodiff:
1606 1612 if dodiff == 'binary':
1607 1613 text = b85diff(to, tn)
1608 1614 else:
1609 1615 text = mdiff.unidiff(to, date1,
1610 1616 # ctx2 date may be dynamic
1611 1617 tn, util.datestr(ctx2.date()),
1612 a, b, revs, opts=opts)
1618 join(a), join(b), revs, opts=opts)
1613 1619 if header and (text or len(header) > 1):
1614 1620 yield ''.join(header)
1615 1621 if text:
1616 1622 yield text
1617 1623
1618 1624 def diffstatdata(lines):
1619 1625 filename, adds, removes = None, 0, 0
1620 1626 for line in lines:
1621 1627 if line.startswith('diff'):
1622 1628 if filename:
1623 1629 isbinary = adds == 0 and removes == 0
1624 1630 yield (filename, adds, removes, isbinary)
1625 1631 # set numbers to 0 anyway when starting new file
1626 1632 adds, removes = 0, 0
1627 1633 if line.startswith('diff --git'):
1628 1634 filename = gitre.search(line).group(1)
1629 1635 else:
1630 1636 # format: "diff -r ... -r ... filename"
1631 1637 filename = line.split(None, 5)[-1]
1632 1638 elif line.startswith('+') and not line.startswith('+++'):
1633 1639 adds += 1
1634 1640 elif line.startswith('-') and not line.startswith('---'):
1635 1641 removes += 1
1636 1642 if filename:
1637 1643 isbinary = adds == 0 and removes == 0
1638 1644 yield (filename, adds, removes, isbinary)
1639 1645
1640 1646 def diffstat(lines, width=80, git=False):
1641 1647 output = []
1642 1648 stats = list(diffstatdata(lines))
1643 1649
1644 1650 maxtotal, maxname = 0, 0
1645 1651 totaladds, totalremoves = 0, 0
1646 1652 hasbinary = False
1647 1653
1648 1654 sized = [(filename, adds, removes, isbinary, encoding.colwidth(filename))
1649 1655 for filename, adds, removes, isbinary in stats]
1650 1656
1651 1657 for filename, adds, removes, isbinary, namewidth in sized:
1652 1658 totaladds += adds
1653 1659 totalremoves += removes
1654 1660 maxname = max(maxname, namewidth)
1655 1661 maxtotal = max(maxtotal, adds + removes)
1656 1662 if isbinary:
1657 1663 hasbinary = True
1658 1664
1659 1665 countwidth = len(str(maxtotal))
1660 1666 if hasbinary and countwidth < 3:
1661 1667 countwidth = 3
1662 1668 graphwidth = width - countwidth - maxname - 6
1663 1669 if graphwidth < 10:
1664 1670 graphwidth = 10
1665 1671
1666 1672 def scale(i):
1667 1673 if maxtotal <= graphwidth:
1668 1674 return i
1669 1675 # If diffstat runs out of room it doesn't print anything,
1670 1676 # which isn't very useful, so always print at least one + or -
1671 1677 # if there were at least some changes.
1672 1678 return max(i * graphwidth // maxtotal, int(bool(i)))
1673 1679
1674 1680 for filename, adds, removes, isbinary, namewidth in sized:
1675 1681 if git and isbinary:
1676 1682 count = 'Bin'
1677 1683 else:
1678 1684 count = adds + removes
1679 1685 pluses = '+' * scale(adds)
1680 1686 minuses = '-' * scale(removes)
1681 1687 output.append(' %s%s | %*s %s%s\n' %
1682 1688 (filename, ' ' * (maxname - namewidth),
1683 1689 countwidth, count,
1684 1690 pluses, minuses))
1685 1691
1686 1692 if stats:
1687 1693 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1688 1694 % (len(stats), totaladds, totalremoves))
1689 1695
1690 1696 return ''.join(output)
1691 1697
1692 1698 def diffstatui(*args, **kw):
1693 1699 '''like diffstat(), but yields 2-tuples of (output, label) for
1694 1700 ui.write()
1695 1701 '''
1696 1702
1697 1703 for line in diffstat(*args, **kw).splitlines():
1698 1704 if line and line[-1] in '+-':
1699 1705 name, graph = line.rsplit(' ', 1)
1700 1706 yield (name + ' ', '')
1701 1707 m = re.search(r'\++', graph)
1702 1708 if m:
1703 1709 yield (m.group(0), 'diffstat.inserted')
1704 1710 m = re.search(r'-+', graph)
1705 1711 if m:
1706 1712 yield (m.group(0), 'diffstat.deleted')
1707 1713 else:
1708 1714 yield (line, '')
1709 1715 yield ('\n', '')
@@ -1,468 +1,482 b''
1 1 # subrepo.py - sub-repository handling for Mercurial
2 2 #
3 3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import errno, os, re, xml.dom.minidom, shutil, urlparse, posixpath
9 9 from i18n import _
10 import config, util, node, error
10 import config, util, node, error, cmdutil
11 11 hg = None
12 12
13 13 nullstate = ('', '', 'empty')
14 14
15 15 def state(ctx, ui):
16 16 """return a state dict, mapping subrepo paths configured in .hgsub
17 17 to tuple: (source from .hgsub, revision from .hgsubstate, kind
18 18 (key in types dict))
19 19 """
20 20 p = config.config()
21 21 def read(f, sections=None, remap=None):
22 22 if f in ctx:
23 23 p.parse(f, ctx[f].data(), sections, remap, read)
24 24 else:
25 25 raise util.Abort(_("subrepo spec file %s not found") % f)
26 26
27 27 if '.hgsub' in ctx:
28 28 read('.hgsub')
29 29
30 30 for path, src in ui.configitems('subpaths'):
31 31 p.set('subpaths', path, src, ui.configsource('subpaths', path))
32 32
33 33 rev = {}
34 34 if '.hgsubstate' in ctx:
35 35 try:
36 36 for l in ctx['.hgsubstate'].data().splitlines():
37 37 revision, path = l.split(" ", 1)
38 38 rev[path] = revision
39 39 except IOError, err:
40 40 if err.errno != errno.ENOENT:
41 41 raise
42 42
43 43 state = {}
44 44 for path, src in p[''].items():
45 45 kind = 'hg'
46 46 if src.startswith('['):
47 47 if ']' not in src:
48 48 raise util.Abort(_('missing ] in subrepo source'))
49 49 kind, src = src.split(']', 1)
50 50 kind = kind[1:]
51 51
52 52 for pattern, repl in p.items('subpaths'):
53 53 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
54 54 # does a string decode.
55 55 repl = repl.encode('string-escape')
56 56 # However, we still want to allow back references to go
57 57 # through unharmed, so we turn r'\\1' into r'\1'. Again,
58 58 # extra escapes are needed because re.sub string decodes.
59 59 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
60 60 try:
61 61 src = re.sub(pattern, repl, src, 1)
62 62 except re.error, e:
63 63 raise util.Abort(_("bad subrepository pattern in %s: %s")
64 64 % (p.source('subpaths', pattern), e))
65 65
66 66 state[path] = (src.strip(), rev.get(path, ''), kind)
67 67
68 68 return state
69 69
70 70 def writestate(repo, state):
71 71 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
72 72 repo.wwrite('.hgsubstate',
73 73 ''.join(['%s %s\n' % (state[s][1], s)
74 74 for s in sorted(state)]), '')
75 75
76 76 def submerge(repo, wctx, mctx, actx):
77 77 """delegated from merge.applyupdates: merging of .hgsubstate file
78 78 in working context, merging context and ancestor context"""
79 79 if mctx == actx: # backwards?
80 80 actx = wctx.p1()
81 81 s1 = wctx.substate
82 82 s2 = mctx.substate
83 83 sa = actx.substate
84 84 sm = {}
85 85
86 86 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
87 87
88 88 def debug(s, msg, r=""):
89 89 if r:
90 90 r = "%s:%s:%s" % r
91 91 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
92 92
93 93 for s, l in s1.items():
94 94 a = sa.get(s, nullstate)
95 95 ld = l # local state with possible dirty flag for compares
96 96 if wctx.sub(s).dirty():
97 97 ld = (l[0], l[1] + "+")
98 98 if wctx == actx: # overwrite
99 99 a = ld
100 100
101 101 if s in s2:
102 102 r = s2[s]
103 103 if ld == r or r == a: # no change or local is newer
104 104 sm[s] = l
105 105 continue
106 106 elif ld == a: # other side changed
107 107 debug(s, "other changed, get", r)
108 108 wctx.sub(s).get(r)
109 109 sm[s] = r
110 110 elif ld[0] != r[0]: # sources differ
111 111 if repo.ui.promptchoice(
112 112 _(' subrepository sources for %s differ\n'
113 113 'use (l)ocal source (%s) or (r)emote source (%s)?')
114 114 % (s, l[0], r[0]),
115 115 (_('&Local'), _('&Remote')), 0):
116 116 debug(s, "prompt changed, get", r)
117 117 wctx.sub(s).get(r)
118 118 sm[s] = r
119 119 elif ld[1] == a[1]: # local side is unchanged
120 120 debug(s, "other side changed, get", r)
121 121 wctx.sub(s).get(r)
122 122 sm[s] = r
123 123 else:
124 124 debug(s, "both sides changed, merge with", r)
125 125 wctx.sub(s).merge(r)
126 126 sm[s] = l
127 127 elif ld == a: # remote removed, local unchanged
128 128 debug(s, "remote removed, remove")
129 129 wctx.sub(s).remove()
130 130 else:
131 131 if repo.ui.promptchoice(
132 132 _(' local changed subrepository %s which remote removed\n'
133 133 'use (c)hanged version or (d)elete?') % s,
134 134 (_('&Changed'), _('&Delete')), 0):
135 135 debug(s, "prompt remove")
136 136 wctx.sub(s).remove()
137 137
138 138 for s, r in s2.items():
139 139 if s in s1:
140 140 continue
141 141 elif s not in sa:
142 142 debug(s, "remote added, get", r)
143 143 mctx.sub(s).get(r)
144 144 sm[s] = r
145 145 elif r != sa[s]:
146 146 if repo.ui.promptchoice(
147 147 _(' remote changed subrepository %s which local removed\n'
148 148 'use (c)hanged version or (d)elete?') % s,
149 149 (_('&Changed'), _('&Delete')), 0) == 0:
150 150 debug(s, "prompt recreate", r)
151 151 wctx.sub(s).get(r)
152 152 sm[s] = r
153 153
154 154 # record merged .hgsubstate
155 155 writestate(repo, sm)
156 156
157 157 def relpath(sub):
158 158 """return path to this subrepo as seen from outermost repo"""
159 159 if not hasattr(sub, '_repo'):
160 160 return sub._path
161 161 parent = sub._repo
162 162 while hasattr(parent, '_subparent'):
163 163 parent = parent._subparent
164 164 return sub._repo.root[len(parent.root)+1:]
165 165
166 166 def _abssource(repo, push=False):
167 167 """return pull/push path of repo - either based on parent repo
168 168 .hgsub info or on the subrepos own config"""
169 169 if hasattr(repo, '_subparent'):
170 170 source = repo._subsource
171 171 if source.startswith('/') or '://' in source:
172 172 return source
173 173 parent = _abssource(repo._subparent, push)
174 174 if '://' in parent:
175 175 if parent[-1] == '/':
176 176 parent = parent[:-1]
177 177 r = urlparse.urlparse(parent + '/' + source)
178 178 r = urlparse.urlunparse((r[0], r[1],
179 179 posixpath.normpath(r[2]),
180 180 r[3], r[4], r[5]))
181 181 return r
182 182 return posixpath.normpath(os.path.join(parent, repo._subsource))
183 183 if push and repo.ui.config('paths', 'default-push'):
184 184 return repo.ui.config('paths', 'default-push', repo.root)
185 185 return repo.ui.config('paths', 'default', repo.root)
186 186
187 187 def subrepo(ctx, path):
188 188 """return instance of the right subrepo class for subrepo in path"""
189 189 # subrepo inherently violates our import layering rules
190 190 # because it wants to make repo objects from deep inside the stack
191 191 # so we manually delay the circular imports to not break
192 192 # scripts that don't use our demand-loading
193 193 global hg
194 194 import hg as h
195 195 hg = h
196 196
197 197 util.path_auditor(ctx._repo.root)(path)
198 198 state = ctx.substate.get(path, nullstate)
199 199 if state[2] not in types:
200 200 raise util.Abort(_('unknown subrepo type %s') % state[2])
201 201 return types[state[2]](ctx, path, state[:2])
202 202
203 203 # subrepo classes need to implement the following abstract class:
204 204
205 205 class abstractsubrepo(object):
206 206
207 207 def dirty(self):
208 208 """returns true if the dirstate of the subrepo does not match
209 209 current stored state
210 210 """
211 211 raise NotImplementedError
212 212
213 213 def checknested(path):
214 214 """check if path is a subrepository within this repository"""
215 215 return False
216 216
217 217 def commit(self, text, user, date):
218 218 """commit the current changes to the subrepo with the given
219 219 log message. Use given user and date if possible. Return the
220 220 new state of the subrepo.
221 221 """
222 222 raise NotImplementedError
223 223
224 224 def remove(self):
225 225 """remove the subrepo
226 226
227 227 (should verify the dirstate is not dirty first)
228 228 """
229 229 raise NotImplementedError
230 230
231 231 def get(self, state):
232 232 """run whatever commands are needed to put the subrepo into
233 233 this state
234 234 """
235 235 raise NotImplementedError
236 236
237 237 def merge(self, state):
238 238 """merge currently-saved state with the new state."""
239 239 raise NotImplementedError
240 240
241 241 def push(self, force):
242 242 """perform whatever action is analogous to 'hg push'
243 243
244 244 This may be a no-op on some systems.
245 245 """
246 246 raise NotImplementedError
247 247
248 248
249 249 def status(self, rev2, **opts):
250 250 return [], [], [], [], [], [], []
251 251
252 def diff(self, diffopts, node2, match, prefix, **opts):
253 pass
254
252 255 class hgsubrepo(abstractsubrepo):
253 256 def __init__(self, ctx, path, state):
254 257 self._path = path
255 258 self._state = state
256 259 r = ctx._repo
257 260 root = r.wjoin(path)
258 261 create = False
259 262 if not os.path.exists(os.path.join(root, '.hg')):
260 263 create = True
261 264 util.makedirs(root)
262 265 self._repo = hg.repository(r.ui, root, create=create)
263 266 self._repo._subparent = r
264 267 self._repo._subsource = state[0]
265 268
266 269 if create:
267 270 fp = self._repo.opener("hgrc", "w", text=True)
268 271 fp.write('[paths]\n')
269 272
270 273 def addpathconfig(key, value):
271 274 fp.write('%s = %s\n' % (key, value))
272 275 self._repo.ui.setconfig('paths', key, value)
273 276
274 277 defpath = _abssource(self._repo)
275 278 defpushpath = _abssource(self._repo, True)
276 279 addpathconfig('default', defpath)
277 280 if defpath != defpushpath:
278 281 addpathconfig('default-push', defpushpath)
279 282 fp.close()
280 283
281 284 def status(self, rev2, **opts):
282 285 try:
283 286 rev1 = self._state[1]
284 287 ctx1 = self._repo[rev1]
285 288 ctx2 = self._repo[rev2]
286 289 return self._repo.status(ctx1, ctx2, **opts)
287 290 except error.RepoLookupError, inst:
288 291 self._repo.ui.warn(_("warning: %s in %s\n")
289 292 % (inst, relpath(self)))
290 293 return [], [], [], [], [], [], []
291 294
295 def diff(self, diffopts, node2, match, prefix, **opts):
296 try:
297 node1 = node.bin(self._state[1])
298 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
299 node1, node2, match,
300 prefix=os.path.join(prefix, self._path),
301 listsubrepos=True, **opts)
302 except error.RepoLookupError, inst:
303 self._repo.ui.warn(_("warning: %s in %s\n")
304 % (inst, relpath(self)))
305
292 306 def dirty(self):
293 307 r = self._state[1]
294 308 if r == '':
295 309 return True
296 310 w = self._repo[None]
297 311 if w.p1() != self._repo[r]: # version checked out change
298 312 return True
299 313 return w.dirty() # working directory changed
300 314
301 315 def checknested(self, path):
302 316 return self._repo._checknested(self._repo.wjoin(path))
303 317
304 318 def commit(self, text, user, date):
305 319 self._repo.ui.debug("committing subrepo %s\n" % relpath(self))
306 320 n = self._repo.commit(text, user, date)
307 321 if not n:
308 322 return self._repo['.'].hex() # different version checked out
309 323 return node.hex(n)
310 324
311 325 def remove(self):
312 326 # we can't fully delete the repository as it may contain
313 327 # local-only history
314 328 self._repo.ui.note(_('removing subrepo %s\n') % relpath(self))
315 329 hg.clean(self._repo, node.nullid, False)
316 330
317 331 def _get(self, state):
318 332 source, revision, kind = state
319 333 try:
320 334 self._repo.lookup(revision)
321 335 except error.RepoError:
322 336 self._repo._subsource = source
323 337 srcurl = _abssource(self._repo)
324 338 self._repo.ui.status(_('pulling subrepo %s from %s\n')
325 339 % (relpath(self), srcurl))
326 340 other = hg.repository(self._repo.ui, srcurl)
327 341 self._repo.pull(other)
328 342
329 343 def get(self, state):
330 344 self._get(state)
331 345 source, revision, kind = state
332 346 self._repo.ui.debug("getting subrepo %s\n" % self._path)
333 347 hg.clean(self._repo, revision, False)
334 348
335 349 def merge(self, state):
336 350 self._get(state)
337 351 cur = self._repo['.']
338 352 dst = self._repo[state[1]]
339 353 anc = dst.ancestor(cur)
340 354 if anc == cur:
341 355 self._repo.ui.debug("updating subrepo %s\n" % relpath(self))
342 356 hg.update(self._repo, state[1])
343 357 elif anc == dst:
344 358 self._repo.ui.debug("skipping subrepo %s\n" % relpath(self))
345 359 else:
346 360 self._repo.ui.debug("merging subrepo %s\n" % relpath(self))
347 361 hg.merge(self._repo, state[1], remind=False)
348 362
349 363 def push(self, force):
350 364 # push subrepos depth-first for coherent ordering
351 365 c = self._repo['']
352 366 subs = c.substate # only repos that are committed
353 367 for s in sorted(subs):
354 368 if not c.sub(s).push(force):
355 369 return False
356 370
357 371 dsturl = _abssource(self._repo, True)
358 372 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
359 373 (relpath(self), dsturl))
360 374 other = hg.repository(self._repo.ui, dsturl)
361 375 return self._repo.push(other, force)
362 376
363 377 class svnsubrepo(abstractsubrepo):
364 378 def __init__(self, ctx, path, state):
365 379 self._path = path
366 380 self._state = state
367 381 self._ctx = ctx
368 382 self._ui = ctx._repo.ui
369 383
370 384 def _svncommand(self, commands, filename=''):
371 385 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
372 386 cmd = ['svn'] + commands + [path]
373 387 cmd = [util.shellquote(arg) for arg in cmd]
374 388 cmd = util.quotecommand(' '.join(cmd))
375 389 env = dict(os.environ)
376 390 # Avoid localized output, preserve current locale for everything else.
377 391 env['LC_MESSAGES'] = 'C'
378 392 write, read, err = util.popen3(cmd, env=env, newlines=True)
379 393 retdata = read.read()
380 394 err = err.read().strip()
381 395 if err:
382 396 raise util.Abort(err)
383 397 return retdata
384 398
385 399 def _wcrev(self):
386 400 output = self._svncommand(['info', '--xml'])
387 401 doc = xml.dom.minidom.parseString(output)
388 402 entries = doc.getElementsByTagName('entry')
389 403 if not entries:
390 404 return 0
391 405 return int(entries[0].getAttribute('revision') or 0)
392 406
393 407 def _wcchanged(self):
394 408 """Return (changes, extchanges) where changes is True
395 409 if the working directory was changed, and extchanges is
396 410 True if any of these changes concern an external entry.
397 411 """
398 412 output = self._svncommand(['status', '--xml'])
399 413 externals, changes = [], []
400 414 doc = xml.dom.minidom.parseString(output)
401 415 for e in doc.getElementsByTagName('entry'):
402 416 s = e.getElementsByTagName('wc-status')
403 417 if not s:
404 418 continue
405 419 item = s[0].getAttribute('item')
406 420 props = s[0].getAttribute('props')
407 421 path = e.getAttribute('path')
408 422 if item == 'external':
409 423 externals.append(path)
410 424 if (item not in ('', 'normal', 'unversioned', 'external')
411 425 or props not in ('', 'none')):
412 426 changes.append(path)
413 427 for path in changes:
414 428 for ext in externals:
415 429 if path == ext or path.startswith(ext + os.sep):
416 430 return True, True
417 431 return bool(changes), False
418 432
419 433 def dirty(self):
420 434 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
421 435 return False
422 436 return True
423 437
424 438 def commit(self, text, user, date):
425 439 # user and date are out of our hands since svn is centralized
426 440 changed, extchanged = self._wcchanged()
427 441 if not changed:
428 442 return self._wcrev()
429 443 if extchanged:
430 444 # Do not try to commit externals
431 445 raise util.Abort(_('cannot commit svn externals'))
432 446 commitinfo = self._svncommand(['commit', '-m', text])
433 447 self._ui.status(commitinfo)
434 448 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
435 449 if not newrev:
436 450 raise util.Abort(commitinfo.splitlines()[-1])
437 451 newrev = newrev.groups()[0]
438 452 self._ui.status(self._svncommand(['update', '-r', newrev]))
439 453 return newrev
440 454
441 455 def remove(self):
442 456 if self.dirty():
443 457 self._ui.warn(_('not removing repo %s because '
444 458 'it has changes.\n' % self._path))
445 459 return
446 460 self._ui.note(_('removing subrepo %s\n') % self._path)
447 461 shutil.rmtree(self._ctx.repo.join(self._path))
448 462
449 463 def get(self, state):
450 464 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
451 465 if not re.search('Checked out revision [0-9]+.', status):
452 466 raise util.Abort(status.splitlines()[-1])
453 467 self._ui.status(status)
454 468
455 469 def merge(self, state):
456 470 old = int(self._state[1])
457 471 new = int(state[1])
458 472 if new > old:
459 473 self.get(state)
460 474
461 475 def push(self, force):
462 476 # push is a no-op for SVN
463 477 return True
464 478
465 479 types = {
466 480 'hg': hgsubrepo,
467 481 'svn': svnsubrepo,
468 482 }
@@ -1,250 +1,250 b''
1 1 Show all commands except debug commands
2 2 $ hg debugcomplete
3 3 add
4 4 addremove
5 5 annotate
6 6 archive
7 7 backout
8 8 bisect
9 9 branch
10 10 branches
11 11 bundle
12 12 cat
13 13 clone
14 14 commit
15 15 copy
16 16 diff
17 17 export
18 18 forget
19 19 grep
20 20 heads
21 21 help
22 22 identify
23 23 import
24 24 incoming
25 25 init
26 26 locate
27 27 log
28 28 manifest
29 29 merge
30 30 outgoing
31 31 parents
32 32 paths
33 33 pull
34 34 push
35 35 recover
36 36 remove
37 37 rename
38 38 resolve
39 39 revert
40 40 rollback
41 41 root
42 42 serve
43 43 showconfig
44 44 status
45 45 summary
46 46 tag
47 47 tags
48 48 tip
49 49 unbundle
50 50 update
51 51 verify
52 52 version
53 53
54 54 Show all commands that start with "a"
55 55 $ hg debugcomplete a
56 56 add
57 57 addremove
58 58 annotate
59 59 archive
60 60
61 61 Do not show debug commands if there are other candidates
62 62 $ hg debugcomplete d
63 63 diff
64 64
65 65 Show debug commands if there are no other candidates
66 66 $ hg debugcomplete debug
67 67 debugancestor
68 68 debugbuilddag
69 69 debugcheckstate
70 70 debugcommands
71 71 debugcomplete
72 72 debugconfig
73 73 debugdag
74 74 debugdata
75 75 debugdate
76 76 debugfsinfo
77 77 debugindex
78 78 debugindexdot
79 79 debuginstall
80 80 debugpushkey
81 81 debugrebuildstate
82 82 debugrename
83 83 debugrevspec
84 84 debugsetparents
85 85 debugstate
86 86 debugsub
87 87 debugwalk
88 88
89 89 Do not show the alias of a debug command if there are other candidates
90 90 (this should hide rawcommit)
91 91 $ hg debugcomplete r
92 92 recover
93 93 remove
94 94 rename
95 95 resolve
96 96 revert
97 97 rollback
98 98 root
99 99 Show the alias of a debug command if there are no other candidates
100 100 $ hg debugcomplete rawc
101 101
102 102
103 103 Show the global options
104 104 $ hg debugcomplete --options | sort
105 105 --config
106 106 --cwd
107 107 --debug
108 108 --debugger
109 109 --encoding
110 110 --encodingmode
111 111 --help
112 112 --noninteractive
113 113 --profile
114 114 --quiet
115 115 --repository
116 116 --time
117 117 --traceback
118 118 --verbose
119 119 --version
120 120 -R
121 121 -h
122 122 -q
123 123 -v
124 124 -y
125 125
126 126 Show the options for the "serve" command
127 127 $ hg debugcomplete --options serve | sort
128 128 --accesslog
129 129 --address
130 130 --certificate
131 131 --config
132 132 --cwd
133 133 --daemon
134 134 --daemon-pipefds
135 135 --debug
136 136 --debugger
137 137 --encoding
138 138 --encodingmode
139 139 --errorlog
140 140 --help
141 141 --ipv6
142 142 --name
143 143 --noninteractive
144 144 --pid-file
145 145 --port
146 146 --prefix
147 147 --profile
148 148 --quiet
149 149 --repository
150 150 --stdio
151 151 --style
152 152 --templates
153 153 --time
154 154 --traceback
155 155 --verbose
156 156 --version
157 157 --web-conf
158 158 -6
159 159 -A
160 160 -E
161 161 -R
162 162 -a
163 163 -d
164 164 -h
165 165 -n
166 166 -p
167 167 -q
168 168 -t
169 169 -v
170 170 -y
171 171
172 172 Show an error if we use --options with an ambiguous abbreviation
173 173 $ hg debugcomplete --options s
174 174 hg: command 's' is ambiguous:
175 175 serve showconfig status summary
176 176
177 177 Show all commands + options
178 178 $ hg debugcommands
179 179 add: include, exclude, dry-run
180 180 annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, include, exclude
181 181 clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd
182 182 commit: addremove, close-branch, include, exclude, message, logfile, date, user
183 diff: rev, change, text, git, nodates, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, include, exclude
183 diff: rev, change, text, git, nodates, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, include, exclude, subrepos
184 184 export: output, switch-parent, rev, text, git, nodates
185 185 forget: include, exclude
186 186 init: ssh, remotecmd
187 187 log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, style, template, include, exclude
188 188 merge: force, rev, preview
189 189 pull: update, force, rev, branch, ssh, remotecmd
190 190 push: force, rev, branch, new-branch, ssh, remotecmd
191 191 remove: after, force, include, exclude
192 192 serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, templates, style, ipv6, certificate
193 193 status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos
194 194 summary: remote
195 195 update: clean, check, date, rev
196 196 addremove: similarity, include, exclude, dry-run
197 197 archive: no-decode, prefix, rev, type, include, exclude
198 198 backout: merge, parent, rev, include, exclude, message, logfile, date, user
199 199 bisect: reset, good, bad, skip, command, noupdate
200 200 branch: force, clean
201 201 branches: active, closed
202 202 bundle: force, rev, branch, base, all, type, ssh, remotecmd
203 203 cat: output, rev, decode, include, exclude
204 204 copy: after, force, include, exclude, dry-run
205 205 debugancestor:
206 206 debugbuilddag: mergeable-file, appended-file, overwritten-file, new-file
207 207 debugcheckstate:
208 208 debugcommands:
209 209 debugcomplete: options
210 210 debugdag: tags, branches, dots, spaces
211 211 debugdata:
212 212 debugdate: extended
213 213 debugfsinfo:
214 214 debugindex:
215 215 debugindexdot:
216 216 debuginstall:
217 217 debugpushkey:
218 218 debugrebuildstate: rev
219 219 debugrename: rev
220 220 debugrevspec:
221 221 debugsetparents:
222 222 debugstate: nodates
223 223 debugsub: rev
224 224 debugwalk: include, exclude
225 225 grep: print0, all, follow, ignore-case, files-with-matches, line-number, rev, user, date, include, exclude
226 226 heads: rev, topo, active, closed, style, template
227 227 help:
228 228 identify: rev, num, id, branch, tags
229 229 import: strip, base, force, no-commit, exact, import-branch, message, logfile, date, user, similarity
230 230 incoming: force, newest-first, bundle, rev, branch, patch, git, limit, no-merges, stat, style, template, ssh, remotecmd
231 231 locate: rev, print0, fullpath, include, exclude
232 232 manifest: rev
233 233 outgoing: force, rev, newest-first, branch, patch, git, limit, no-merges, stat, style, template, ssh, remotecmd
234 234 parents: rev, style, template
235 235 paths:
236 236 recover:
237 237 rename: after, force, include, exclude, dry-run
238 238 resolve: all, list, mark, unmark, no-status, include, exclude
239 239 revert: all, date, rev, no-backup, include, exclude, dry-run
240 240 rollback: dry-run
241 241 root:
242 242 showconfig: untrusted
243 243 tag: force, local, rev, remove, edit, message, date, user
244 244 tags:
245 245 tip: patch, git, style, template
246 246 unbundle: update
247 247 verify:
248 248 version:
249 249
250 250 $ exit 0
@@ -1,745 +1,746 b''
1 1 #!/bin/sh
2 2
3 3 $ hg
4 4 Mercurial Distributed SCM
5 5
6 6 basic commands:
7 7
8 8 add add the specified files on the next commit
9 9 annotate show changeset information by line for each file
10 10 clone make a copy of an existing repository
11 11 commit commit the specified files or all outstanding changes
12 12 diff diff repository (or selected files)
13 13 export dump the header and diffs for one or more changesets
14 14 forget forget the specified files on the next commit
15 15 init create a new repository in the given directory
16 16 log show revision history of entire repository or files
17 17 merge merge working directory with another revision
18 18 pull pull changes from the specified source
19 19 push push changes to the specified destination
20 20 remove remove the specified files on the next commit
21 21 serve start stand-alone webserver
22 22 status show changed files in the working directory
23 23 summary summarize working directory state
24 24 update update working directory (or switch revisions)
25 25
26 26 use "hg help" for the full list of commands or "hg -v" for details
27 27
28 28 $ hg -q
29 29 add add the specified files on the next commit
30 30 annotate show changeset information by line for each file
31 31 clone make a copy of an existing repository
32 32 commit commit the specified files or all outstanding changes
33 33 diff diff repository (or selected files)
34 34 export dump the header and diffs for one or more changesets
35 35 forget forget the specified files on the next commit
36 36 init create a new repository in the given directory
37 37 log show revision history of entire repository or files
38 38 merge merge working directory with another revision
39 39 pull pull changes from the specified source
40 40 push push changes to the specified destination
41 41 remove remove the specified files on the next commit
42 42 serve start stand-alone webserver
43 43 status show changed files in the working directory
44 44 summary summarize working directory state
45 45 update update working directory (or switch revisions)
46 46
47 47 $ hg help
48 48 Mercurial Distributed SCM
49 49
50 50 list of commands:
51 51
52 52 add add the specified files on the next commit
53 53 addremove add all new files, delete all missing files
54 54 annotate show changeset information by line for each file
55 55 archive create an unversioned archive of a repository revision
56 56 backout reverse effect of earlier changeset
57 57 bisect subdivision search of changesets
58 58 branch set or show the current branch name
59 59 branches list repository named branches
60 60 bundle create a changegroup file
61 61 cat output the current or given revision of files
62 62 clone make a copy of an existing repository
63 63 commit commit the specified files or all outstanding changes
64 64 copy mark files as copied for the next commit
65 65 diff diff repository (or selected files)
66 66 export dump the header and diffs for one or more changesets
67 67 forget forget the specified files on the next commit
68 68 grep search for a pattern in specified files and revisions
69 69 heads show current repository heads or show branch heads
70 70 help show help for a given topic or a help overview
71 71 identify identify the working copy or specified revision
72 72 import import an ordered set of patches
73 73 incoming show new changesets found in source
74 74 init create a new repository in the given directory
75 75 locate locate files matching specific patterns
76 76 log show revision history of entire repository or files
77 77 manifest output the current or given revision of the project manifest
78 78 merge merge working directory with another revision
79 79 outgoing show changesets not found in the destination
80 80 parents show the parents of the working directory or revision
81 81 paths show aliases for remote repositories
82 82 pull pull changes from the specified source
83 83 push push changes to the specified destination
84 84 recover roll back an interrupted transaction
85 85 remove remove the specified files on the next commit
86 86 rename rename files; equivalent of copy + remove
87 87 resolve redo merges or set/view the merge status of files
88 88 revert restore individual files or directories to an earlier state
89 89 rollback roll back the last transaction (dangerous)
90 90 root print the root (top) of the current working directory
91 91 serve start stand-alone webserver
92 92 showconfig show combined config settings from all hgrc files
93 93 status show changed files in the working directory
94 94 summary summarize working directory state
95 95 tag add one or more tags for the current or given revision
96 96 tags list repository tags
97 97 tip show the tip revision
98 98 unbundle apply one or more changegroup files
99 99 update update working directory (or switch revisions)
100 100 verify verify the integrity of the repository
101 101 version output version and copyright information
102 102
103 103 additional help topics:
104 104
105 105 config Configuration Files
106 106 dates Date Formats
107 107 patterns File Name Patterns
108 108 environment Environment Variables
109 109 revisions Specifying Single Revisions
110 110 multirevs Specifying Multiple Revisions
111 111 revsets Specifying Revision Sets
112 112 diffs Diff Formats
113 113 templating Template Usage
114 114 urls URL Paths
115 115 extensions Using additional features
116 116 hgweb Configuring hgweb
117 117 glossary Glossary
118 118
119 119 use "hg -v help" to show aliases and global options
120 120
121 121 $ hg -q help
122 122 add add the specified files on the next commit
123 123 addremove add all new files, delete all missing files
124 124 annotate show changeset information by line for each file
125 125 archive create an unversioned archive of a repository revision
126 126 backout reverse effect of earlier changeset
127 127 bisect subdivision search of changesets
128 128 branch set or show the current branch name
129 129 branches list repository named branches
130 130 bundle create a changegroup file
131 131 cat output the current or given revision of files
132 132 clone make a copy of an existing repository
133 133 commit commit the specified files or all outstanding changes
134 134 copy mark files as copied for the next commit
135 135 diff diff repository (or selected files)
136 136 export dump the header and diffs for one or more changesets
137 137 forget forget the specified files on the next commit
138 138 grep search for a pattern in specified files and revisions
139 139 heads show current repository heads or show branch heads
140 140 help show help for a given topic or a help overview
141 141 identify identify the working copy or specified revision
142 142 import import an ordered set of patches
143 143 incoming show new changesets found in source
144 144 init create a new repository in the given directory
145 145 locate locate files matching specific patterns
146 146 log show revision history of entire repository or files
147 147 manifest output the current or given revision of the project manifest
148 148 merge merge working directory with another revision
149 149 outgoing show changesets not found in the destination
150 150 parents show the parents of the working directory or revision
151 151 paths show aliases for remote repositories
152 152 pull pull changes from the specified source
153 153 push push changes to the specified destination
154 154 recover roll back an interrupted transaction
155 155 remove remove the specified files on the next commit
156 156 rename rename files; equivalent of copy + remove
157 157 resolve redo merges or set/view the merge status of files
158 158 revert restore individual files or directories to an earlier state
159 159 rollback roll back the last transaction (dangerous)
160 160 root print the root (top) of the current working directory
161 161 serve start stand-alone webserver
162 162 showconfig show combined config settings from all hgrc files
163 163 status show changed files in the working directory
164 164 summary summarize working directory state
165 165 tag add one or more tags for the current or given revision
166 166 tags list repository tags
167 167 tip show the tip revision
168 168 unbundle apply one or more changegroup files
169 169 update update working directory (or switch revisions)
170 170 verify verify the integrity of the repository
171 171 version output version and copyright information
172 172
173 173 additional help topics:
174 174
175 175 config Configuration Files
176 176 dates Date Formats
177 177 patterns File Name Patterns
178 178 environment Environment Variables
179 179 revisions Specifying Single Revisions
180 180 multirevs Specifying Multiple Revisions
181 181 revsets Specifying Revision Sets
182 182 diffs Diff Formats
183 183 templating Template Usage
184 184 urls URL Paths
185 185 extensions Using additional features
186 186 hgweb Configuring hgweb
187 187 glossary Glossary
188 188
189 189 Test short command list with verbose option
190 190
191 191 $ hg -v help shortlist
192 192 Mercurial Distributed SCM \(version .*?\)
193 193
194 194 Copyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others
195 195 This is free software; see the source for copying conditions. There is NO
196 196 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
197 197
198 198 basic commands:
199 199
200 200 add:
201 201 add the specified files on the next commit
202 202 annotate, blame:
203 203 show changeset information by line for each file
204 204 clone:
205 205 make a copy of an existing repository
206 206 commit, ci:
207 207 commit the specified files or all outstanding changes
208 208 diff:
209 209 diff repository (or selected files)
210 210 export:
211 211 dump the header and diffs for one or more changesets
212 212 forget:
213 213 forget the specified files on the next commit
214 214 init:
215 215 create a new repository in the given directory
216 216 log, history:
217 217 show revision history of entire repository or files
218 218 merge:
219 219 merge working directory with another revision
220 220 pull:
221 221 pull changes from the specified source
222 222 push:
223 223 push changes to the specified destination
224 224 remove, rm:
225 225 remove the specified files on the next commit
226 226 serve:
227 227 start stand-alone webserver
228 228 status, st:
229 229 show changed files in the working directory
230 230 summary, sum:
231 231 summarize working directory state
232 232 update, up, checkout, co:
233 233 update working directory (or switch revisions)
234 234
235 235 global options:
236 236 -R --repository REPO repository root directory or name of overlay bundle
237 237 file
238 238 --cwd DIR change working directory
239 239 -y --noninteractive do not prompt, assume 'yes' for any required answers
240 240 -q --quiet suppress output
241 241 -v --verbose enable additional output
242 242 --config CONFIG [+] set/override config option (use 'section.name=value')
243 243 --debug enable debugging output
244 244 --debugger start debugger
245 245 --encoding ENCODE set the charset encoding (default: ascii)
246 246 --encodingmode MODE set the charset encoding mode (default: strict)
247 247 --traceback always print a traceback on exception
248 248 --time time how long the command takes
249 249 --profile print command execution profile
250 250 --version output version information and exit
251 251 -h --help display help and exit
252 252
253 253 [+] marked option can be specified multiple times
254 254
255 255 use "hg help" for the full list of commands
256 256
257 257 $ hg add -h
258 258 hg add [OPTION]... [FILE]...
259 259
260 260 add the specified files on the next commit
261 261
262 262 Schedule files to be version controlled and added to the repository.
263 263
264 264 The files will be added to the repository at the next commit. To undo an
265 265 add before that, see "hg forget".
266 266
267 267 If no names are given, add all files to the repository.
268 268
269 269 Returns 0 if all files are successfully added.
270 270
271 271 use "hg -v help add" to show verbose help
272 272
273 273 options:
274 274
275 275 -I --include PATTERN [+] include names matching the given patterns
276 276 -X --exclude PATTERN [+] exclude names matching the given patterns
277 277 -n --dry-run do not perform actions, just print output
278 278
279 279 [+] marked option can be specified multiple times
280 280
281 281 use "hg -v help add" to show global options
282 282
283 283 Verbose help for add
284 284
285 285 $ hg add -hv
286 286 hg add [OPTION]... [FILE]...
287 287
288 288 add the specified files on the next commit
289 289
290 290 Schedule files to be version controlled and added to the repository.
291 291
292 292 The files will be added to the repository at the next commit. To undo an
293 293 add before that, see "hg forget".
294 294
295 295 If no names are given, add all files to the repository.
296 296
297 297 An example showing how new (unknown) files are added automatically by "hg
298 298 add":
299 299
300 300 $ ls
301 301 foo.c
302 302 $ hg status
303 303 ? foo.c
304 304 $ hg add
305 305 adding foo.c
306 306 $ hg status
307 307 A foo.c
308 308
309 309 Returns 0 if all files are successfully added.
310 310
311 311 options:
312 312
313 313 -I --include PATTERN [+] include names matching the given patterns
314 314 -X --exclude PATTERN [+] exclude names matching the given patterns
315 315 -n --dry-run do not perform actions, just print output
316 316
317 317 global options:
318 318 -R --repository REPO repository root directory or name of overlay bundle
319 319 file
320 320 --cwd DIR change working directory
321 321 -y --noninteractive do not prompt, assume 'yes' for any required
322 322 answers
323 323 -q --quiet suppress output
324 324 -v --verbose enable additional output
325 325 --config CONFIG [+] set/override config option (use
326 326 'section.name=value')
327 327 --debug enable debugging output
328 328 --debugger start debugger
329 329 --encoding ENCODE set the charset encoding (default: ascii)
330 330 --encodingmode MODE set the charset encoding mode (default: strict)
331 331 --traceback always print a traceback on exception
332 332 --time time how long the command takes
333 333 --profile print command execution profile
334 334 --version output version information and exit
335 335 -h --help display help and exit
336 336
337 337 [+] marked option can be specified multiple times
338 338
339 339 Test help option with version option
340 340
341 341 $ hg add -h --version
342 342 Mercurial Distributed SCM \(version .+?\)
343 343
344 344 Copyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others
345 345 This is free software; see the source for copying conditions. There is NO
346 346 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
347 347
348 348 hg add [OPTION]... [FILE]...
349 349
350 350 add the specified files on the next commit
351 351
352 352 Schedule files to be version controlled and added to the repository.
353 353
354 354 The files will be added to the repository at the next commit. To undo an
355 355 add before that, see "hg forget".
356 356
357 357 If no names are given, add all files to the repository.
358 358
359 359 Returns 0 if all files are successfully added.
360 360
361 361 use "hg -v help add" to show verbose help
362 362
363 363 options:
364 364
365 365 -I --include PATTERN [+] include names matching the given patterns
366 366 -X --exclude PATTERN [+] exclude names matching the given patterns
367 367 -n --dry-run do not perform actions, just print output
368 368
369 369 [+] marked option can be specified multiple times
370 370
371 371 use "hg -v help add" to show global options
372 372
373 373 $ hg add --skjdfks
374 374 hg add: option --skjdfks not recognized
375 375 hg add [OPTION]... [FILE]...
376 376
377 377 add the specified files on the next commit
378 378
379 379 Schedule files to be version controlled and added to the repository.
380 380
381 381 The files will be added to the repository at the next commit. To undo an
382 382 add before that, see "hg forget".
383 383
384 384 If no names are given, add all files to the repository.
385 385
386 386 Returns 0 if all files are successfully added.
387 387
388 388 use "hg -v help add" to show verbose help
389 389
390 390 options:
391 391
392 392 -I --include PATTERN [+] include names matching the given patterns
393 393 -X --exclude PATTERN [+] exclude names matching the given patterns
394 394 -n --dry-run do not perform actions, just print output
395 395
396 396 [+] marked option can be specified multiple times
397 397
398 398 use "hg -v help add" to show global options
399 399
400 400 Test ambiguous command help
401 401
402 402 $ hg help ad
403 403 list of commands:
404 404
405 405 add add the specified files on the next commit
406 406 addremove add all new files, delete all missing files
407 407
408 408 use "hg -v help ad" to show aliases and global options
409 409
410 410 Test command without options
411 411
412 412 $ hg help verify
413 413 hg verify
414 414
415 415 verify the integrity of the repository
416 416
417 417 Verify the integrity of the current repository.
418 418
419 419 This will perform an extensive check of the repository's integrity,
420 420 validating the hashes and checksums of each entry in the changelog,
421 421 manifest, and tracked files, as well as the integrity of their crosslinks
422 422 and indices.
423 423
424 424 Returns 0 on success, 1 if errors are encountered.
425 425
426 426 use "hg -v help verify" to show global options
427 427
428 428 $ hg help diff
429 429 hg diff [OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...
430 430
431 431 diff repository (or selected files)
432 432
433 433 Show differences between revisions for the specified files.
434 434
435 435 Differences between files are shown using the unified diff format.
436 436
437 437 NOTE: diff may generate unexpected results for merges, as it will default
438 438 to comparing against the working directory's first parent changeset if no
439 439 revisions are specified.
440 440
441 441 When two revision arguments are given, then changes are shown between
442 442 those revisions. If only one revision is specified then that revision is
443 443 compared to the working directory, and, when no revisions are specified,
444 444 the working directory files are compared to its parent.
445 445
446 446 Alternatively you can specify -c/--change with a revision to see the
447 447 changes in that changeset relative to its first parent.
448 448
449 449 Without the -a/--text option, diff will avoid generating diffs of files it
450 450 detects as binary. With -a, diff will generate a diff anyway, probably
451 451 with undesirable results.
452 452
453 453 Use the -g/--git option to generate diffs in the git extended diff format.
454 454 For more information, read "hg help diffs".
455 455
456 456 Returns 0 on success.
457 457
458 458 options:
459 459
460 460 -r --rev REV [+] revision
461 461 -c --change REV change made by revision
462 462 -a --text treat all files as text
463 463 -g --git use git extended diff format
464 464 --nodates omit dates from diff headers
465 465 -p --show-function show which function each change is in
466 466 --reverse produce a diff that undoes the changes
467 467 -w --ignore-all-space ignore white space when comparing lines
468 468 -b --ignore-space-change ignore changes in the amount of white space
469 469 -B --ignore-blank-lines ignore changes whose lines are all blank
470 470 -U --unified NUM number of lines of context to show
471 471 --stat output diffstat-style summary of changes
472 472 -I --include PATTERN [+] include names matching the given patterns
473 473 -X --exclude PATTERN [+] exclude names matching the given patterns
474 -S --subrepos recurse into subrepositories
474 475
475 476 [+] marked option can be specified multiple times
476 477
477 478 use "hg -v help diff" to show global options
478 479
479 480 $ hg help status
480 481 hg status [OPTION]... [FILE]...
481 482
482 483 aliases: st
483 484
484 485 show changed files in the working directory
485 486
486 487 Show status of files in the repository. If names are given, only files
487 488 that match are shown. Files that are clean or ignored or the source of a
488 489 copy/move operation, are not listed unless -c/--clean, -i/--ignored,
489 490 -C/--copies or -A/--all are given. Unless options described with "show
490 491 only ..." are given, the options -mardu are used.
491 492
492 493 Option -q/--quiet hides untracked (unknown and ignored) files unless
493 494 explicitly requested with -u/--unknown or -i/--ignored.
494 495
495 496 NOTE: status may appear to disagree with diff if permissions have changed
496 497 or a merge has occurred. The standard diff format does not report
497 498 permission changes and diff only reports changes relative to one merge
498 499 parent.
499 500
500 501 If one revision is given, it is used as the base revision. If two
501 502 revisions are given, the differences between them are shown. The --change
502 503 option can also be used as a shortcut to list the changed files of a
503 504 revision from its first parent.
504 505
505 506 The codes used to show the status of files are:
506 507
507 508 M = modified
508 509 A = added
509 510 R = removed
510 511 C = clean
511 512 ! = missing (deleted by non-hg command, but still tracked)
512 513 ? = not tracked
513 514 I = ignored
514 515 = origin of the previous file listed as A (added)
515 516
516 517 Returns 0 on success.
517 518
518 519 options:
519 520
520 521 -A --all show status of all files
521 522 -m --modified show only modified files
522 523 -a --added show only added files
523 524 -r --removed show only removed files
524 525 -d --deleted show only deleted (but tracked) files
525 526 -c --clean show only files without changes
526 527 -u --unknown show only unknown (not tracked) files
527 528 -i --ignored show only ignored files
528 529 -n --no-status hide status prefix
529 530 -C --copies show source of copied files
530 531 -0 --print0 end filenames with NUL, for use with xargs
531 532 --rev REV [+] show difference from revision
532 533 --change REV list the changed files of a revision
533 534 -I --include PATTERN [+] include names matching the given patterns
534 535 -X --exclude PATTERN [+] exclude names matching the given patterns
535 536 -S --subrepos recurse into subrepositories
536 537
537 538 [+] marked option can be specified multiple times
538 539
539 540 use "hg -v help status" to show global options
540 541
541 542 $ hg -q help status
542 543 hg status [OPTION]... [FILE]...
543 544
544 545 show changed files in the working directory
545 546
546 547 $ hg help foo
547 548 hg: unknown command 'foo'
548 549 Mercurial Distributed SCM
549 550
550 551 basic commands:
551 552
552 553 add add the specified files on the next commit
553 554 annotate show changeset information by line for each file
554 555 clone make a copy of an existing repository
555 556 commit commit the specified files or all outstanding changes
556 557 diff diff repository (or selected files)
557 558 export dump the header and diffs for one or more changesets
558 559 forget forget the specified files on the next commit
559 560 init create a new repository in the given directory
560 561 log show revision history of entire repository or files
561 562 merge merge working directory with another revision
562 563 pull pull changes from the specified source
563 564 push push changes to the specified destination
564 565 remove remove the specified files on the next commit
565 566 serve start stand-alone webserver
566 567 status show changed files in the working directory
567 568 summary summarize working directory state
568 569 update update working directory (or switch revisions)
569 570
570 571 use "hg help" for the full list of commands or "hg -v" for details
571 572
572 573 $ hg skjdfks
573 574 hg: unknown command 'skjdfks'
574 575 Mercurial Distributed SCM
575 576
576 577 basic commands:
577 578
578 579 add add the specified files on the next commit
579 580 annotate show changeset information by line for each file
580 581 clone make a copy of an existing repository
581 582 commit commit the specified files or all outstanding changes
582 583 diff diff repository (or selected files)
583 584 export dump the header and diffs for one or more changesets
584 585 forget forget the specified files on the next commit
585 586 init create a new repository in the given directory
586 587 log show revision history of entire repository or files
587 588 merge merge working directory with another revision
588 589 pull pull changes from the specified source
589 590 push push changes to the specified destination
590 591 remove remove the specified files on the next commit
591 592 serve start stand-alone webserver
592 593 status show changed files in the working directory
593 594 summary summarize working directory state
594 595 update update working directory (or switch revisions)
595 596
596 597 use "hg help" for the full list of commands or "hg -v" for details
597 598
598 599 $ cat > helpext.py <<EOF
599 600 > import os
600 601 > from mercurial import commands
601 602 >
602 603 > def nohelp(ui, *args, **kwargs):
603 604 > pass
604 605 >
605 606 > cmdtable = {
606 607 > "nohelp": (nohelp, [], "hg nohelp"),
607 608 > }
608 609 >
609 610 > commands.norepo += ' nohelp'
610 611 > EOF
611 612 $ echo '[extensions]' >> $HGRCPATH
612 613 $ echo "helpext = `pwd`/helpext.py" >> $HGRCPATH
613 614
614 615 Test command with no help text
615 616
616 617 $ hg help nohelp
617 618 hg nohelp
618 619
619 620 (no help text available)
620 621
621 622 use "hg -v help nohelp" to show global options
622 623
623 624 Test that default list of commands omits extension commands
624 625
625 626 $ hg help
626 627 Mercurial Distributed SCM
627 628
628 629 list of commands:
629 630
630 631 add add the specified files on the next commit
631 632 addremove add all new files, delete all missing files
632 633 annotate show changeset information by line for each file
633 634 archive create an unversioned archive of a repository revision
634 635 backout reverse effect of earlier changeset
635 636 bisect subdivision search of changesets
636 637 branch set or show the current branch name
637 638 branches list repository named branches
638 639 bundle create a changegroup file
639 640 cat output the current or given revision of files
640 641 clone make a copy of an existing repository
641 642 commit commit the specified files or all outstanding changes
642 643 copy mark files as copied for the next commit
643 644 diff diff repository (or selected files)
644 645 export dump the header and diffs for one or more changesets
645 646 forget forget the specified files on the next commit
646 647 grep search for a pattern in specified files and revisions
647 648 heads show current repository heads or show branch heads
648 649 help show help for a given topic or a help overview
649 650 identify identify the working copy or specified revision
650 651 import import an ordered set of patches
651 652 incoming show new changesets found in source
652 653 init create a new repository in the given directory
653 654 locate locate files matching specific patterns
654 655 log show revision history of entire repository or files
655 656 manifest output the current or given revision of the project manifest
656 657 merge merge working directory with another revision
657 658 outgoing show changesets not found in the destination
658 659 parents show the parents of the working directory or revision
659 660 paths show aliases for remote repositories
660 661 pull pull changes from the specified source
661 662 push push changes to the specified destination
662 663 recover roll back an interrupted transaction
663 664 remove remove the specified files on the next commit
664 665 rename rename files; equivalent of copy + remove
665 666 resolve redo merges or set/view the merge status of files
666 667 revert restore individual files or directories to an earlier state
667 668 rollback roll back the last transaction (dangerous)
668 669 root print the root (top) of the current working directory
669 670 serve start stand-alone webserver
670 671 showconfig show combined config settings from all hgrc files
671 672 status show changed files in the working directory
672 673 summary summarize working directory state
673 674 tag add one or more tags for the current or given revision
674 675 tags list repository tags
675 676 tip show the tip revision
676 677 unbundle apply one or more changegroup files
677 678 update update working directory (or switch revisions)
678 679 verify verify the integrity of the repository
679 680 version output version and copyright information
680 681
681 682 enabled extensions:
682 683
683 684 helpext (no help text available)
684 685
685 686 additional help topics:
686 687
687 688 config Configuration Files
688 689 dates Date Formats
689 690 patterns File Name Patterns
690 691 environment Environment Variables
691 692 revisions Specifying Single Revisions
692 693 multirevs Specifying Multiple Revisions
693 694 revsets Specifying Revision Sets
694 695 diffs Diff Formats
695 696 templating Template Usage
696 697 urls URL Paths
697 698 extensions Using additional features
698 699 hgweb Configuring hgweb
699 700 glossary Glossary
700 701
701 702 use "hg -v help" to show aliases and global options
702 703
703 704 Test list of commands with command with no help text
704 705
705 706 $ hg help helpext
706 707 helpext extension - no help text available
707 708
708 709 list of commands:
709 710
710 711 nohelp (no help text available)
711 712
712 713 use "hg -v help helpext" to show aliases and global options
713 714
714 715 Test a help topic
715 716
716 717 $ hg help revs
717 718 Specifying Single Revisions
718 719
719 720 Mercurial supports several ways to specify individual revisions.
720 721
721 722 A plain integer is treated as a revision number. Negative integers are
722 723 treated as sequential offsets from the tip, with -1 denoting the tip, -2
723 724 denoting the revision prior to the tip, and so forth.
724 725
725 726 A 40-digit hexadecimal string is treated as a unique revision identifier.
726 727
727 728 A hexadecimal string less than 40 characters long is treated as a unique
728 729 revision identifier and is referred to as a short-form identifier. A
729 730 short-form identifier is only valid if it is the prefix of exactly one
730 731 full-length identifier.
731 732
732 733 Any other string is treated as a tag or branch name. A tag name is a
733 734 symbolic name associated with a revision identifier. A branch name denotes
734 735 the tipmost revision of that branch. Tag and branch names must not contain
735 736 the ":" character.
736 737
737 738 The reserved name "tip" is a special tag that always identifies the most
738 739 recent revision.
739 740
740 741 The reserved name "null" indicates the null revision. This is the revision
741 742 of an empty repository, and the parent of revision 0.
742 743
743 744 The reserved name "." indicates the working directory parent. If no
744 745 working directory is checked out, it is equivalent to null. If an
745 746 uncommitted merge is in progress, "." is the revision of the first parent.
@@ -1,97 +1,192 b''
1 1 Make status look into subrepositories by default:
2 2
3 3 $ echo '[defaults]' >> $HGRCPATH
4 4 $ echo 'status = -S' >> $HGRCPATH
5 $ echo 'diff = --nodates -S' >> $HGRCPATH
5 6
6 7 Create test repository:
7 8
8 9 $ hg init
9 10 $ echo x1 > x.txt
10 11 $ hg add x.txt
11 12
12 13 $ hg init foo
13 14 $ cd foo
14 15 $ echo y1 > y.txt
15 16 $ hg add y.txt
16 17
17 18 $ hg init bar
18 19 $ cd bar
19 20 $ echo z1 > z.txt
20 21 $ hg add z.txt
21 22
22 23 $ cd ..
23 24 $ echo 'bar = bar' > .hgsub
24 25 $ hg add .hgsub
25 26
26 27 $ cd ..
27 28 $ echo 'foo = foo' > .hgsub
28 29 $ hg add .hgsub
29 30
30 31 $ hg commit -m 0-0-0
31 32 committing subrepository foo
32 33 committing subrepository foo/bar
33 34
34 35 $ cd foo
35 36 $ echo y2 >> y.txt
36 37 $ hg commit -m 0-1-0
37 38
38 39 $ cd bar
39 40 $ echo z2 >> z.txt
40 41 $ hg commit -m 0-1-1
41 42
42 43 $ cd ..
43 44 $ hg commit -m 0-2-1
44 45 committing subrepository bar
45 46
46 47 $ cd ..
47 48 $ hg commit -m 1-2-1
48 49 committing subrepository foo
49 50
50 51 Change working directory:
51 52
52 53 $ echo y3 >> foo/y.txt
53 54 $ echo z3 >> foo/bar/z.txt
54 55 $ hg status
55 56 M foo/bar/z.txt
56 57 M foo/y.txt
58 $ hg diff
59 diff -r d254738c5f5e foo/y.txt
60 --- a/foo/y.txt
61 +++ b/foo/y.txt
62 @@ -1,2 +1,3 @@
63 y1
64 y2
65 +y3
66 diff -r 9647f22de499 foo/bar/z.txt
67 --- a/foo/bar/z.txt
68 +++ b/foo/bar/z.txt
69 @@ -1,2 +1,3 @@
70 z1
71 z2
72 +z3
57 73
58 74 Status call crossing repository boundaries:
59 75
60 76 $ hg status foo/bar/z.txt
61 77 M foo/bar/z.txt
62 78 $ hg status -I 'foo/?.txt'
63 79 M foo/y.txt
64 80 $ hg status -I '**/?.txt'
65 81 M foo/bar/z.txt
66 82 M foo/y.txt
83 $ hg diff -I '**/?.txt'
84 diff -r d254738c5f5e foo/y.txt
85 --- a/foo/y.txt
86 +++ b/foo/y.txt
87 @@ -1,2 +1,3 @@
88 y1
89 y2
90 +y3
91 diff -r 9647f22de499 foo/bar/z.txt
92 --- a/foo/bar/z.txt
93 +++ b/foo/bar/z.txt
94 @@ -1,2 +1,3 @@
95 z1
96 z2
97 +z3
67 98
68 99 Status from within a subdirectory:
69 100
70 101 $ mkdir dir
71 102 $ cd dir
72 103 $ echo a1 > a.txt
73 104 $ hg status
74 105 M foo/bar/z.txt
75 106 M foo/y.txt
76 107 ? dir/a.txt
108 $ hg diff
109 diff -r d254738c5f5e foo/y.txt
110 --- a/foo/y.txt
111 +++ b/foo/y.txt
112 @@ -1,2 +1,3 @@
113 y1
114 y2
115 +y3
116 diff -r 9647f22de499 foo/bar/z.txt
117 --- a/foo/bar/z.txt
118 +++ b/foo/bar/z.txt
119 @@ -1,2 +1,3 @@
120 z1
121 z2
122 +z3
77 123
78 124 Status with relative path:
79 125
80 126 $ hg status ..
81 127 M ../foo/bar/z.txt
82 128 M ../foo/y.txt
83 129 ? a.txt
130 $ hg diff ..
131 diff -r d254738c5f5e foo/y.txt
132 --- a/foo/y.txt
133 +++ b/foo/y.txt
134 @@ -1,2 +1,3 @@
135 y1
136 y2
137 +y3
138 diff -r 9647f22de499 foo/bar/z.txt
139 --- a/foo/bar/z.txt
140 +++ b/foo/bar/z.txt
141 @@ -1,2 +1,3 @@
142 z1
143 z2
144 +z3
84 145 $ cd ..
85 146
147 Cleanup and final commit:
148
149 $ rm -r dir
150 $ hg commit -m 2-3-2
151 committing subrepository foo
152 committing subrepository foo/bar
153
154 Log with the relationships between repo and its subrepo:
155
156 $ hg log --template '{rev}:{node|short} {desc}\n'
157 2:1326fa26d0c0 2-3-2
158 1:4b3c9ff4f66b 1-2-1
159 0:23376cbba0d8 0-0-0
160
161 $ hg -R foo log --template '{rev}:{node|short} {desc}\n'
162 3:65903cebad86 2-3-2
163 2:d254738c5f5e 0-2-1
164 1:8629ce7dcc39 0-1-0
165 0:af048e97ade2 0-0-0
166
167 $ hg -R foo/bar log --template '{rev}:{node|short} {desc}\n'
168 2:31ecbdafd357 2-3-2
169 1:9647f22de499 0-1-1
170 0:4904098473f9 0-0-0
171
86 172 Status between revisions:
87 173
88 $ rm -r dir
89 $ hg commit -m 2-2-1
90 committing subrepository foo
91 committing subrepository foo/bar
92 174 $ hg status
93 175 $ hg status --rev 0:1
94 176 M .hgsubstate
95 177 M foo/.hgsubstate
96 178 M foo/bar/z.txt
97 179 M foo/y.txt
180 $ hg diff -I '**/?.txt' --rev 0:1
181 diff -r af048e97ade2 -r d254738c5f5e foo/y.txt
182 --- a/foo/y.txt
183 +++ b/foo/y.txt
184 @@ -1,1 +1,2 @@
185 y1
186 +y2
187 diff -r 4904098473f9 -r 9647f22de499 foo/bar/z.txt
188 --- a/foo/bar/z.txt
189 +++ b/foo/bar/z.txt
190 @@ -1,1 +1,2 @@
191 z1
192 +z2
General Comments 0
You need to be logged in to leave comments. Login now