##// END OF EJS Templates
forget: add --confirm option...
Sushil khanchi -
r37774:e7bf5a73 default
parent child Browse files
Show More
@@ -1,1495 +1,1496
1 1 # Copyright 2009-2010 Gregory P. Ward
2 2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 3 # Copyright 2010-2011 Fog Creek Software
4 4 # Copyright 2010-2011 Unity Technologies
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 '''Overridden Mercurial commands and functions for the largefiles extension'''
10 10 from __future__ import absolute_import
11 11
12 12 import copy
13 13 import os
14 14
15 15 from mercurial.i18n import _
16 16
17 17 from mercurial import (
18 18 archival,
19 19 cmdutil,
20 20 error,
21 21 hg,
22 22 logcmdutil,
23 23 match as matchmod,
24 24 pathutil,
25 25 pycompat,
26 26 registrar,
27 27 scmutil,
28 28 smartset,
29 29 util,
30 30 )
31 31
32 32 from . import (
33 33 lfcommands,
34 34 lfutil,
35 35 storefactory,
36 36 )
37 37
38 38 # -- Utility functions: commonly/repeatedly needed functionality ---------------
39 39
40 40 def composelargefilematcher(match, manifest):
41 41 '''create a matcher that matches only the largefiles in the original
42 42 matcher'''
43 43 m = copy.copy(match)
44 44 lfile = lambda f: lfutil.standin(f) in manifest
45 45 m._files = [lf for lf in m._files if lfile(lf)]
46 46 m._fileset = set(m._files)
47 47 m.always = lambda: False
48 48 origmatchfn = m.matchfn
49 49 m.matchfn = lambda f: lfile(f) and origmatchfn(f)
50 50 return m
51 51
52 52 def composenormalfilematcher(match, manifest, exclude=None):
53 53 excluded = set()
54 54 if exclude is not None:
55 55 excluded.update(exclude)
56 56
57 57 m = copy.copy(match)
58 58 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
59 59 manifest or f in excluded)
60 60 m._files = [lf for lf in m._files if notlfile(lf)]
61 61 m._fileset = set(m._files)
62 62 m.always = lambda: False
63 63 origmatchfn = m.matchfn
64 64 m.matchfn = lambda f: notlfile(f) and origmatchfn(f)
65 65 return m
66 66
67 67 def installnormalfilesmatchfn(manifest):
68 68 '''installmatchfn with a matchfn that ignores all largefiles'''
69 69 def overridematch(ctx, pats=(), opts=None, globbed=False,
70 70 default='relpath', badfn=None):
71 71 if opts is None:
72 72 opts = {}
73 73 match = oldmatch(ctx, pats, opts, globbed, default, badfn=badfn)
74 74 return composenormalfilematcher(match, manifest)
75 75 oldmatch = installmatchfn(overridematch)
76 76
77 77 def installmatchfn(f):
78 78 '''monkey patch the scmutil module with a custom match function.
79 79 Warning: it is monkey patching the _module_ on runtime! Not thread safe!'''
80 80 oldmatch = scmutil.match
81 81 setattr(f, 'oldmatch', oldmatch)
82 82 scmutil.match = f
83 83 return oldmatch
84 84
85 85 def restorematchfn():
86 86 '''restores scmutil.match to what it was before installmatchfn
87 87 was called. no-op if scmutil.match is its original function.
88 88
89 89 Note that n calls to installmatchfn will require n calls to
90 90 restore the original matchfn.'''
91 91 scmutil.match = getattr(scmutil.match, 'oldmatch')
92 92
93 93 def installmatchandpatsfn(f):
94 94 oldmatchandpats = scmutil.matchandpats
95 95 setattr(f, 'oldmatchandpats', oldmatchandpats)
96 96 scmutil.matchandpats = f
97 97 return oldmatchandpats
98 98
99 99 def restorematchandpatsfn():
100 100 '''restores scmutil.matchandpats to what it was before
101 101 installmatchandpatsfn was called. No-op if scmutil.matchandpats
102 102 is its original function.
103 103
104 104 Note that n calls to installmatchandpatsfn will require n calls
105 105 to restore the original matchfn.'''
106 106 scmutil.matchandpats = getattr(scmutil.matchandpats, 'oldmatchandpats',
107 107 scmutil.matchandpats)
108 108
109 109 def addlargefiles(ui, repo, isaddremove, matcher, **opts):
110 110 large = opts.get(r'large')
111 111 lfsize = lfutil.getminsize(
112 112 ui, lfutil.islfilesrepo(repo), opts.get(r'lfsize'))
113 113
114 114 lfmatcher = None
115 115 if lfutil.islfilesrepo(repo):
116 116 lfpats = ui.configlist(lfutil.longname, 'patterns')
117 117 if lfpats:
118 118 lfmatcher = matchmod.match(repo.root, '', list(lfpats))
119 119
120 120 lfnames = []
121 121 m = matcher
122 122
123 123 wctx = repo[None]
124 124 for f in wctx.walk(matchmod.badmatch(m, lambda x, y: None)):
125 125 exact = m.exact(f)
126 126 lfile = lfutil.standin(f) in wctx
127 127 nfile = f in wctx
128 128 exists = lfile or nfile
129 129
130 130 # addremove in core gets fancy with the name, add doesn't
131 131 if isaddremove:
132 132 name = m.uipath(f)
133 133 else:
134 134 name = m.rel(f)
135 135
136 136 # Don't warn the user when they attempt to add a normal tracked file.
137 137 # The normal add code will do that for us.
138 138 if exact and exists:
139 139 if lfile:
140 140 ui.warn(_('%s already a largefile\n') % name)
141 141 continue
142 142
143 143 if (exact or not exists) and not lfutil.isstandin(f):
144 144 # In case the file was removed previously, but not committed
145 145 # (issue3507)
146 146 if not repo.wvfs.exists(f):
147 147 continue
148 148
149 149 abovemin = (lfsize and
150 150 repo.wvfs.lstat(f).st_size >= lfsize * 1024 * 1024)
151 151 if large or abovemin or (lfmatcher and lfmatcher(f)):
152 152 lfnames.append(f)
153 153 if ui.verbose or not exact:
154 154 ui.status(_('adding %s as a largefile\n') % name)
155 155
156 156 bad = []
157 157
158 158 # Need to lock, otherwise there could be a race condition between
159 159 # when standins are created and added to the repo.
160 160 with repo.wlock():
161 161 if not opts.get(r'dry_run'):
162 162 standins = []
163 163 lfdirstate = lfutil.openlfdirstate(ui, repo)
164 164 for f in lfnames:
165 165 standinname = lfutil.standin(f)
166 166 lfutil.writestandin(repo, standinname, hash='',
167 167 executable=lfutil.getexecutable(repo.wjoin(f)))
168 168 standins.append(standinname)
169 169 if lfdirstate[f] == 'r':
170 170 lfdirstate.normallookup(f)
171 171 else:
172 172 lfdirstate.add(f)
173 173 lfdirstate.write()
174 174 bad += [lfutil.splitstandin(f)
175 175 for f in repo[None].add(standins)
176 176 if f in m.files()]
177 177
178 178 added = [f for f in lfnames if f not in bad]
179 179 return added, bad
180 180
181 181 def removelargefiles(ui, repo, isaddremove, matcher, dryrun, **opts):
182 182 after = opts.get(r'after')
183 183 m = composelargefilematcher(matcher, repo[None].manifest())
184 184 try:
185 185 repo.lfstatus = True
186 186 s = repo.status(match=m, clean=not isaddremove)
187 187 finally:
188 188 repo.lfstatus = False
189 189 manifest = repo[None].manifest()
190 190 modified, added, deleted, clean = [[f for f in list
191 191 if lfutil.standin(f) in manifest]
192 192 for list in (s.modified, s.added,
193 193 s.deleted, s.clean)]
194 194
195 195 def warn(files, msg):
196 196 for f in files:
197 197 ui.warn(msg % m.rel(f))
198 198 return int(len(files) > 0)
199 199
200 200 result = 0
201 201
202 202 if after:
203 203 remove = deleted
204 204 result = warn(modified + added + clean,
205 205 _('not removing %s: file still exists\n'))
206 206 else:
207 207 remove = deleted + clean
208 208 result = warn(modified, _('not removing %s: file is modified (use -f'
209 209 ' to force removal)\n'))
210 210 result = warn(added, _('not removing %s: file has been marked for add'
211 211 ' (use forget to undo)\n')) or result
212 212
213 213 # Need to lock because standin files are deleted then removed from the
214 214 # repository and we could race in-between.
215 215 with repo.wlock():
216 216 lfdirstate = lfutil.openlfdirstate(ui, repo)
217 217 for f in sorted(remove):
218 218 if ui.verbose or not m.exact(f):
219 219 # addremove in core gets fancy with the name, remove doesn't
220 220 if isaddremove:
221 221 name = m.uipath(f)
222 222 else:
223 223 name = m.rel(f)
224 224 ui.status(_('removing %s\n') % name)
225 225
226 226 if not dryrun:
227 227 if not after:
228 228 repo.wvfs.unlinkpath(f, ignoremissing=True)
229 229
230 230 if dryrun:
231 231 return result
232 232
233 233 remove = [lfutil.standin(f) for f in remove]
234 234 # If this is being called by addremove, let the original addremove
235 235 # function handle this.
236 236 if not isaddremove:
237 237 for f in remove:
238 238 repo.wvfs.unlinkpath(f, ignoremissing=True)
239 239 repo[None].forget(remove)
240 240
241 241 for f in remove:
242 242 lfutil.synclfdirstate(repo, lfdirstate, lfutil.splitstandin(f),
243 243 False)
244 244
245 245 lfdirstate.write()
246 246
247 247 return result
248 248
249 249 # For overriding mercurial.hgweb.webcommands so that largefiles will
250 250 # appear at their right place in the manifests.
251 251 def decodepath(orig, path):
252 252 return lfutil.splitstandin(path) or path
253 253
254 254 # -- Wrappers: modify existing commands --------------------------------
255 255
256 256 def overrideadd(orig, ui, repo, *pats, **opts):
257 257 if opts.get(r'normal') and opts.get(r'large'):
258 258 raise error.Abort(_('--normal cannot be used with --large'))
259 259 return orig(ui, repo, *pats, **opts)
260 260
261 261 def cmdutiladd(orig, ui, repo, matcher, prefix, explicitonly, **opts):
262 262 # The --normal flag short circuits this override
263 263 if opts.get(r'normal'):
264 264 return orig(ui, repo, matcher, prefix, explicitonly, **opts)
265 265
266 266 ladded, lbad = addlargefiles(ui, repo, False, matcher, **opts)
267 267 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest(),
268 268 ladded)
269 269 bad = orig(ui, repo, normalmatcher, prefix, explicitonly, **opts)
270 270
271 271 bad.extend(f for f in lbad)
272 272 return bad
273 273
274 274 def cmdutilremove(orig, ui, repo, matcher, prefix, after, force, subrepos,
275 275 dryrun):
276 276 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest())
277 277 result = orig(ui, repo, normalmatcher, prefix, after, force, subrepos,
278 278 dryrun)
279 279 return removelargefiles(ui, repo, False, matcher, dryrun, after=after,
280 280 force=force) or result
281 281
282 282 def overridestatusfn(orig, repo, rev2, **opts):
283 283 try:
284 284 repo._repo.lfstatus = True
285 285 return orig(repo, rev2, **opts)
286 286 finally:
287 287 repo._repo.lfstatus = False
288 288
289 289 def overridestatus(orig, ui, repo, *pats, **opts):
290 290 try:
291 291 repo.lfstatus = True
292 292 return orig(ui, repo, *pats, **opts)
293 293 finally:
294 294 repo.lfstatus = False
295 295
296 296 def overridedirty(orig, repo, ignoreupdate=False, missing=False):
297 297 try:
298 298 repo._repo.lfstatus = True
299 299 return orig(repo, ignoreupdate=ignoreupdate, missing=missing)
300 300 finally:
301 301 repo._repo.lfstatus = False
302 302
303 303 def overridelog(orig, ui, repo, *pats, **opts):
304 304 def overridematchandpats(ctx, pats=(), opts=None, globbed=False,
305 305 default='relpath', badfn=None):
306 306 """Matcher that merges root directory with .hglf, suitable for log.
307 307 It is still possible to match .hglf directly.
308 308 For any listed files run log on the standin too.
309 309 matchfn tries both the given filename and with .hglf stripped.
310 310 """
311 311 if opts is None:
312 312 opts = {}
313 313 matchandpats = oldmatchandpats(ctx, pats, opts, globbed, default,
314 314 badfn=badfn)
315 315 m, p = copy.copy(matchandpats)
316 316
317 317 if m.always():
318 318 # We want to match everything anyway, so there's no benefit trying
319 319 # to add standins.
320 320 return matchandpats
321 321
322 322 pats = set(p)
323 323
324 324 def fixpats(pat, tostandin=lfutil.standin):
325 325 if pat.startswith('set:'):
326 326 return pat
327 327
328 328 kindpat = matchmod._patsplit(pat, None)
329 329
330 330 if kindpat[0] is not None:
331 331 return kindpat[0] + ':' + tostandin(kindpat[1])
332 332 return tostandin(kindpat[1])
333 333
334 334 if m._cwd:
335 335 hglf = lfutil.shortname
336 336 back = util.pconvert(m.rel(hglf)[:-len(hglf)])
337 337
338 338 def tostandin(f):
339 339 # The file may already be a standin, so truncate the back
340 340 # prefix and test before mangling it. This avoids turning
341 341 # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'.
342 342 if f.startswith(back) and lfutil.splitstandin(f[len(back):]):
343 343 return f
344 344
345 345 # An absolute path is from outside the repo, so truncate the
346 346 # path to the root before building the standin. Otherwise cwd
347 347 # is somewhere in the repo, relative to root, and needs to be
348 348 # prepended before building the standin.
349 349 if os.path.isabs(m._cwd):
350 350 f = f[len(back):]
351 351 else:
352 352 f = m._cwd + '/' + f
353 353 return back + lfutil.standin(f)
354 354 else:
355 355 def tostandin(f):
356 356 if lfutil.isstandin(f):
357 357 return f
358 358 return lfutil.standin(f)
359 359 pats.update(fixpats(f, tostandin) for f in p)
360 360
361 361 for i in range(0, len(m._files)):
362 362 # Don't add '.hglf' to m.files, since that is already covered by '.'
363 363 if m._files[i] == '.':
364 364 continue
365 365 standin = lfutil.standin(m._files[i])
366 366 # If the "standin" is a directory, append instead of replace to
367 367 # support naming a directory on the command line with only
368 368 # largefiles. The original directory is kept to support normal
369 369 # files.
370 370 if standin in ctx:
371 371 m._files[i] = standin
372 372 elif m._files[i] not in ctx and repo.wvfs.isdir(standin):
373 373 m._files.append(standin)
374 374
375 375 m._fileset = set(m._files)
376 376 m.always = lambda: False
377 377 origmatchfn = m.matchfn
378 378 def lfmatchfn(f):
379 379 lf = lfutil.splitstandin(f)
380 380 if lf is not None and origmatchfn(lf):
381 381 return True
382 382 r = origmatchfn(f)
383 383 return r
384 384 m.matchfn = lfmatchfn
385 385
386 386 ui.debug('updated patterns: %s\n' % ', '.join(sorted(pats)))
387 387 return m, pats
388 388
389 389 # For hg log --patch, the match object is used in two different senses:
390 390 # (1) to determine what revisions should be printed out, and
391 391 # (2) to determine what files to print out diffs for.
392 392 # The magic matchandpats override should be used for case (1) but not for
393 393 # case (2).
394 394 def overridemakefilematcher(repo, pats, opts, badfn=None):
395 395 wctx = repo[None]
396 396 match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn)
397 397 return lambda ctx: match
398 398
399 399 oldmatchandpats = installmatchandpatsfn(overridematchandpats)
400 400 oldmakefilematcher = logcmdutil._makenofollowfilematcher
401 401 setattr(logcmdutil, '_makenofollowfilematcher', overridemakefilematcher)
402 402
403 403 try:
404 404 return orig(ui, repo, *pats, **opts)
405 405 finally:
406 406 restorematchandpatsfn()
407 407 setattr(logcmdutil, '_makenofollowfilematcher', oldmakefilematcher)
408 408
409 409 def overrideverify(orig, ui, repo, *pats, **opts):
410 410 large = opts.pop(r'large', False)
411 411 all = opts.pop(r'lfa', False)
412 412 contents = opts.pop(r'lfc', False)
413 413
414 414 result = orig(ui, repo, *pats, **opts)
415 415 if large or all or contents:
416 416 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
417 417 return result
418 418
419 419 def overridedebugstate(orig, ui, repo, *pats, **opts):
420 420 large = opts.pop(r'large', False)
421 421 if large:
422 422 class fakerepo(object):
423 423 dirstate = lfutil.openlfdirstate(ui, repo)
424 424 orig(ui, fakerepo, *pats, **opts)
425 425 else:
426 426 orig(ui, repo, *pats, **opts)
427 427
428 428 # Before starting the manifest merge, merge.updates will call
429 429 # _checkunknownfile to check if there are any files in the merged-in
430 430 # changeset that collide with unknown files in the working copy.
431 431 #
432 432 # The largefiles are seen as unknown, so this prevents us from merging
433 433 # in a file 'foo' if we already have a largefile with the same name.
434 434 #
435 435 # The overridden function filters the unknown files by removing any
436 436 # largefiles. This makes the merge proceed and we can then handle this
437 437 # case further in the overridden calculateupdates function below.
438 438 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None):
439 439 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
440 440 return False
441 441 return origfn(repo, wctx, mctx, f, f2)
442 442
443 443 # The manifest merge handles conflicts on the manifest level. We want
444 444 # to handle changes in largefile-ness of files at this level too.
445 445 #
446 446 # The strategy is to run the original calculateupdates and then process
447 447 # the action list it outputs. There are two cases we need to deal with:
448 448 #
449 449 # 1. Normal file in p1, largefile in p2. Here the largefile is
450 450 # detected via its standin file, which will enter the working copy
451 451 # with a "get" action. It is not "merge" since the standin is all
452 452 # Mercurial is concerned with at this level -- the link to the
453 453 # existing normal file is not relevant here.
454 454 #
455 455 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
456 456 # since the largefile will be present in the working copy and
457 457 # different from the normal file in p2. Mercurial therefore
458 458 # triggers a merge action.
459 459 #
460 460 # In both cases, we prompt the user and emit new actions to either
461 461 # remove the standin (if the normal file was kept) or to remove the
462 462 # normal file and get the standin (if the largefile was kept). The
463 463 # default prompt answer is to use the largefile version since it was
464 464 # presumably changed on purpose.
465 465 #
466 466 # Finally, the merge.applyupdates function will then take care of
467 467 # writing the files into the working copy and lfcommands.updatelfiles
468 468 # will update the largefiles.
469 469 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
470 470 acceptremote, *args, **kwargs):
471 471 overwrite = force and not branchmerge
472 472 actions, diverge, renamedelete = origfn(
473 473 repo, p1, p2, pas, branchmerge, force, acceptremote, *args, **kwargs)
474 474
475 475 if overwrite:
476 476 return actions, diverge, renamedelete
477 477
478 478 # Convert to dictionary with filename as key and action as value.
479 479 lfiles = set()
480 480 for f in actions:
481 481 splitstandin = lfutil.splitstandin(f)
482 482 if splitstandin in p1:
483 483 lfiles.add(splitstandin)
484 484 elif lfutil.standin(f) in p1:
485 485 lfiles.add(f)
486 486
487 487 for lfile in sorted(lfiles):
488 488 standin = lfutil.standin(lfile)
489 489 (lm, largs, lmsg) = actions.get(lfile, (None, None, None))
490 490 (sm, sargs, smsg) = actions.get(standin, (None, None, None))
491 491 if sm in ('g', 'dc') and lm != 'r':
492 492 if sm == 'dc':
493 493 f1, f2, fa, move, anc = sargs
494 494 sargs = (p2[f2].flags(), False)
495 495 # Case 1: normal file in the working copy, largefile in
496 496 # the second parent
497 497 usermsg = _('remote turned local normal file %s into a largefile\n'
498 498 'use (l)argefile or keep (n)ormal file?'
499 499 '$$ &Largefile $$ &Normal file') % lfile
500 500 if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile
501 501 actions[lfile] = ('r', None, 'replaced by standin')
502 502 actions[standin] = ('g', sargs, 'replaces standin')
503 503 else: # keep local normal file
504 504 actions[lfile] = ('k', None, 'replaces standin')
505 505 if branchmerge:
506 506 actions[standin] = ('k', None, 'replaced by non-standin')
507 507 else:
508 508 actions[standin] = ('r', None, 'replaced by non-standin')
509 509 elif lm in ('g', 'dc') and sm != 'r':
510 510 if lm == 'dc':
511 511 f1, f2, fa, move, anc = largs
512 512 largs = (p2[f2].flags(), False)
513 513 # Case 2: largefile in the working copy, normal file in
514 514 # the second parent
515 515 usermsg = _('remote turned local largefile %s into a normal file\n'
516 516 'keep (l)argefile or use (n)ormal file?'
517 517 '$$ &Largefile $$ &Normal file') % lfile
518 518 if repo.ui.promptchoice(usermsg, 0) == 0: # keep local largefile
519 519 if branchmerge:
520 520 # largefile can be restored from standin safely
521 521 actions[lfile] = ('k', None, 'replaced by standin')
522 522 actions[standin] = ('k', None, 'replaces standin')
523 523 else:
524 524 # "lfile" should be marked as "removed" without
525 525 # removal of itself
526 526 actions[lfile] = ('lfmr', None,
527 527 'forget non-standin largefile')
528 528
529 529 # linear-merge should treat this largefile as 're-added'
530 530 actions[standin] = ('a', None, 'keep standin')
531 531 else: # pick remote normal file
532 532 actions[lfile] = ('g', largs, 'replaces standin')
533 533 actions[standin] = ('r', None, 'replaced by non-standin')
534 534
535 535 return actions, diverge, renamedelete
536 536
537 537 def mergerecordupdates(orig, repo, actions, branchmerge):
538 538 if 'lfmr' in actions:
539 539 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
540 540 for lfile, args, msg in actions['lfmr']:
541 541 # this should be executed before 'orig', to execute 'remove'
542 542 # before all other actions
543 543 repo.dirstate.remove(lfile)
544 544 # make sure lfile doesn't get synclfdirstate'd as normal
545 545 lfdirstate.add(lfile)
546 546 lfdirstate.write()
547 547
548 548 return orig(repo, actions, branchmerge)
549 549
550 550 # Override filemerge to prompt the user about how they wish to merge
551 551 # largefiles. This will handle identical edits without prompting the user.
552 552 def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca,
553 553 labels=None):
554 554 if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
555 555 return origfn(premerge, repo, wctx, mynode, orig, fcd, fco, fca,
556 556 labels=labels)
557 557
558 558 ahash = lfutil.readasstandin(fca).lower()
559 559 dhash = lfutil.readasstandin(fcd).lower()
560 560 ohash = lfutil.readasstandin(fco).lower()
561 561 if (ohash != ahash and
562 562 ohash != dhash and
563 563 (dhash == ahash or
564 564 repo.ui.promptchoice(
565 565 _('largefile %s has a merge conflict\nancestor was %s\n'
566 566 'keep (l)ocal %s or\ntake (o)ther %s?'
567 567 '$$ &Local $$ &Other') %
568 568 (lfutil.splitstandin(orig), ahash, dhash, ohash),
569 569 0) == 1)):
570 570 repo.wwrite(fcd.path(), fco.data(), fco.flags())
571 571 return True, 0, False
572 572
573 573 def copiespathcopies(orig, ctx1, ctx2, match=None):
574 574 copies = orig(ctx1, ctx2, match=match)
575 575 updated = {}
576 576
577 577 for k, v in copies.iteritems():
578 578 updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
579 579
580 580 return updated
581 581
582 582 # Copy first changes the matchers to match standins instead of
583 583 # largefiles. Then it overrides util.copyfile in that function it
584 584 # checks if the destination largefile already exists. It also keeps a
585 585 # list of copied files so that the largefiles can be copied and the
586 586 # dirstate updated.
587 587 def overridecopy(orig, ui, repo, pats, opts, rename=False):
588 588 # doesn't remove largefile on rename
589 589 if len(pats) < 2:
590 590 # this isn't legal, let the original function deal with it
591 591 return orig(ui, repo, pats, opts, rename)
592 592
593 593 # This could copy both lfiles and normal files in one command,
594 594 # but we don't want to do that. First replace their matcher to
595 595 # only match normal files and run it, then replace it to just
596 596 # match largefiles and run it again.
597 597 nonormalfiles = False
598 598 nolfiles = False
599 599 installnormalfilesmatchfn(repo[None].manifest())
600 600 try:
601 601 result = orig(ui, repo, pats, opts, rename)
602 602 except error.Abort as e:
603 603 if pycompat.bytestr(e) != _('no files to copy'):
604 604 raise e
605 605 else:
606 606 nonormalfiles = True
607 607 result = 0
608 608 finally:
609 609 restorematchfn()
610 610
611 611 # The first rename can cause our current working directory to be removed.
612 612 # In that case there is nothing left to copy/rename so just quit.
613 613 try:
614 614 repo.getcwd()
615 615 except OSError:
616 616 return result
617 617
618 618 def makestandin(relpath):
619 619 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
620 620 return repo.wvfs.join(lfutil.standin(path))
621 621
622 622 fullpats = scmutil.expandpats(pats)
623 623 dest = fullpats[-1]
624 624
625 625 if os.path.isdir(dest):
626 626 if not os.path.isdir(makestandin(dest)):
627 627 os.makedirs(makestandin(dest))
628 628
629 629 try:
630 630 # When we call orig below it creates the standins but we don't add
631 631 # them to the dir state until later so lock during that time.
632 632 wlock = repo.wlock()
633 633
634 634 manifest = repo[None].manifest()
635 635 def overridematch(ctx, pats=(), opts=None, globbed=False,
636 636 default='relpath', badfn=None):
637 637 if opts is None:
638 638 opts = {}
639 639 newpats = []
640 640 # The patterns were previously mangled to add the standin
641 641 # directory; we need to remove that now
642 642 for pat in pats:
643 643 if matchmod.patkind(pat) is None and lfutil.shortname in pat:
644 644 newpats.append(pat.replace(lfutil.shortname, ''))
645 645 else:
646 646 newpats.append(pat)
647 647 match = oldmatch(ctx, newpats, opts, globbed, default, badfn=badfn)
648 648 m = copy.copy(match)
649 649 lfile = lambda f: lfutil.standin(f) in manifest
650 650 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
651 651 m._fileset = set(m._files)
652 652 origmatchfn = m.matchfn
653 653 def matchfn(f):
654 654 lfile = lfutil.splitstandin(f)
655 655 return (lfile is not None and
656 656 (f in manifest) and
657 657 origmatchfn(lfile) or
658 658 None)
659 659 m.matchfn = matchfn
660 660 return m
661 661 oldmatch = installmatchfn(overridematch)
662 662 listpats = []
663 663 for pat in pats:
664 664 if matchmod.patkind(pat) is not None:
665 665 listpats.append(pat)
666 666 else:
667 667 listpats.append(makestandin(pat))
668 668
669 669 try:
670 670 origcopyfile = util.copyfile
671 671 copiedfiles = []
672 672 def overridecopyfile(src, dest, *args, **kwargs):
673 673 if (lfutil.shortname in src and
674 674 dest.startswith(repo.wjoin(lfutil.shortname))):
675 675 destlfile = dest.replace(lfutil.shortname, '')
676 676 if not opts['force'] and os.path.exists(destlfile):
677 677 raise IOError('',
678 678 _('destination largefile already exists'))
679 679 copiedfiles.append((src, dest))
680 680 origcopyfile(src, dest, *args, **kwargs)
681 681
682 682 util.copyfile = overridecopyfile
683 683 result += orig(ui, repo, listpats, opts, rename)
684 684 finally:
685 685 util.copyfile = origcopyfile
686 686
687 687 lfdirstate = lfutil.openlfdirstate(ui, repo)
688 688 for (src, dest) in copiedfiles:
689 689 if (lfutil.shortname in src and
690 690 dest.startswith(repo.wjoin(lfutil.shortname))):
691 691 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
692 692 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
693 693 destlfiledir = repo.wvfs.dirname(repo.wjoin(destlfile)) or '.'
694 694 if not os.path.isdir(destlfiledir):
695 695 os.makedirs(destlfiledir)
696 696 if rename:
697 697 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
698 698
699 699 # The file is gone, but this deletes any empty parent
700 700 # directories as a side-effect.
701 701 repo.wvfs.unlinkpath(srclfile, ignoremissing=True)
702 702 lfdirstate.remove(srclfile)
703 703 else:
704 704 util.copyfile(repo.wjoin(srclfile),
705 705 repo.wjoin(destlfile))
706 706
707 707 lfdirstate.add(destlfile)
708 708 lfdirstate.write()
709 709 except error.Abort as e:
710 710 if pycompat.bytestr(e) != _('no files to copy'):
711 711 raise e
712 712 else:
713 713 nolfiles = True
714 714 finally:
715 715 restorematchfn()
716 716 wlock.release()
717 717
718 718 if nolfiles and nonormalfiles:
719 719 raise error.Abort(_('no files to copy'))
720 720
721 721 return result
722 722
723 723 # When the user calls revert, we have to be careful to not revert any
724 724 # changes to other largefiles accidentally. This means we have to keep
725 725 # track of the largefiles that are being reverted so we only pull down
726 726 # the necessary largefiles.
727 727 #
728 728 # Standins are only updated (to match the hash of largefiles) before
729 729 # commits. Update the standins then run the original revert, changing
730 730 # the matcher to hit standins instead of largefiles. Based on the
731 731 # resulting standins update the largefiles.
732 732 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
733 733 # Because we put the standins in a bad state (by updating them)
734 734 # and then return them to a correct state we need to lock to
735 735 # prevent others from changing them in their incorrect state.
736 736 with repo.wlock():
737 737 lfdirstate = lfutil.openlfdirstate(ui, repo)
738 738 s = lfutil.lfdirstatestatus(lfdirstate, repo)
739 739 lfdirstate.write()
740 740 for lfile in s.modified:
741 741 lfutil.updatestandin(repo, lfile, lfutil.standin(lfile))
742 742 for lfile in s.deleted:
743 743 fstandin = lfutil.standin(lfile)
744 744 if (repo.wvfs.exists(fstandin)):
745 745 repo.wvfs.unlink(fstandin)
746 746
747 747 oldstandins = lfutil.getstandinsstate(repo)
748 748
749 749 def overridematch(mctx, pats=(), opts=None, globbed=False,
750 750 default='relpath', badfn=None):
751 751 if opts is None:
752 752 opts = {}
753 753 match = oldmatch(mctx, pats, opts, globbed, default, badfn=badfn)
754 754 m = copy.copy(match)
755 755
756 756 # revert supports recursing into subrepos, and though largefiles
757 757 # currently doesn't work correctly in that case, this match is
758 758 # called, so the lfdirstate above may not be the correct one for
759 759 # this invocation of match.
760 760 lfdirstate = lfutil.openlfdirstate(mctx.repo().ui, mctx.repo(),
761 761 False)
762 762
763 763 wctx = repo[None]
764 764 matchfiles = []
765 765 for f in m._files:
766 766 standin = lfutil.standin(f)
767 767 if standin in ctx or standin in mctx:
768 768 matchfiles.append(standin)
769 769 elif standin in wctx or lfdirstate[f] == 'r':
770 770 continue
771 771 else:
772 772 matchfiles.append(f)
773 773 m._files = matchfiles
774 774 m._fileset = set(m._files)
775 775 origmatchfn = m.matchfn
776 776 def matchfn(f):
777 777 lfile = lfutil.splitstandin(f)
778 778 if lfile is not None:
779 779 return (origmatchfn(lfile) and
780 780 (f in ctx or f in mctx))
781 781 return origmatchfn(f)
782 782 m.matchfn = matchfn
783 783 return m
784 784 oldmatch = installmatchfn(overridematch)
785 785 try:
786 786 orig(ui, repo, ctx, parents, *pats, **opts)
787 787 finally:
788 788 restorematchfn()
789 789
790 790 newstandins = lfutil.getstandinsstate(repo)
791 791 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
792 792 # lfdirstate should be 'normallookup'-ed for updated files,
793 793 # because reverting doesn't touch dirstate for 'normal' files
794 794 # when target revision is explicitly specified: in such case,
795 795 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
796 796 # of target (standin) file.
797 797 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
798 798 normallookup=True)
799 799
800 800 # after pulling changesets, we need to take some extra care to get
801 801 # largefiles updated remotely
802 802 def overridepull(orig, ui, repo, source=None, **opts):
803 803 revsprepull = len(repo)
804 804 if not source:
805 805 source = 'default'
806 806 repo.lfpullsource = source
807 807 result = orig(ui, repo, source, **opts)
808 808 revspostpull = len(repo)
809 809 lfrevs = opts.get(r'lfrev', [])
810 810 if opts.get(r'all_largefiles'):
811 811 lfrevs.append('pulled()')
812 812 if lfrevs and revspostpull > revsprepull:
813 813 numcached = 0
814 814 repo.firstpulled = revsprepull # for pulled() revset expression
815 815 try:
816 816 for rev in scmutil.revrange(repo, lfrevs):
817 817 ui.note(_('pulling largefiles for revision %d\n') % rev)
818 818 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
819 819 numcached += len(cached)
820 820 finally:
821 821 del repo.firstpulled
822 822 ui.status(_("%d largefiles cached\n") % numcached)
823 823 return result
824 824
825 825 def overridepush(orig, ui, repo, *args, **kwargs):
826 826 """Override push command and store --lfrev parameters in opargs"""
827 827 lfrevs = kwargs.pop(r'lfrev', None)
828 828 if lfrevs:
829 829 opargs = kwargs.setdefault(r'opargs', {})
830 830 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
831 831 return orig(ui, repo, *args, **kwargs)
832 832
833 833 def exchangepushoperation(orig, *args, **kwargs):
834 834 """Override pushoperation constructor and store lfrevs parameter"""
835 835 lfrevs = kwargs.pop(r'lfrevs', None)
836 836 pushop = orig(*args, **kwargs)
837 837 pushop.lfrevs = lfrevs
838 838 return pushop
839 839
840 840 revsetpredicate = registrar.revsetpredicate()
841 841
842 842 @revsetpredicate('pulled()')
843 843 def pulledrevsetsymbol(repo, subset, x):
844 844 """Changesets that just has been pulled.
845 845
846 846 Only available with largefiles from pull --lfrev expressions.
847 847
848 848 .. container:: verbose
849 849
850 850 Some examples:
851 851
852 852 - pull largefiles for all new changesets::
853 853
854 854 hg pull -lfrev "pulled()"
855 855
856 856 - pull largefiles for all new branch heads::
857 857
858 858 hg pull -lfrev "head(pulled()) and not closed()"
859 859
860 860 """
861 861
862 862 try:
863 863 firstpulled = repo.firstpulled
864 864 except AttributeError:
865 865 raise error.Abort(_("pulled() only available in --lfrev"))
866 866 return smartset.baseset([r for r in subset if r >= firstpulled])
867 867
868 868 def overrideclone(orig, ui, source, dest=None, **opts):
869 869 d = dest
870 870 if d is None:
871 871 d = hg.defaultdest(source)
872 872 if opts.get(r'all_largefiles') and not hg.islocal(d):
873 873 raise error.Abort(_(
874 874 '--all-largefiles is incompatible with non-local destination %s') %
875 875 d)
876 876
877 877 return orig(ui, source, dest, **opts)
878 878
879 879 def hgclone(orig, ui, opts, *args, **kwargs):
880 880 result = orig(ui, opts, *args, **kwargs)
881 881
882 882 if result is not None:
883 883 sourcerepo, destrepo = result
884 884 repo = destrepo.local()
885 885
886 886 # When cloning to a remote repo (like through SSH), no repo is available
887 887 # from the peer. Therefore the largefiles can't be downloaded and the
888 888 # hgrc can't be updated.
889 889 if not repo:
890 890 return result
891 891
892 892 # If largefiles is required for this repo, permanently enable it locally
893 893 if 'largefiles' in repo.requirements:
894 894 repo.vfs.append('hgrc',
895 895 util.tonativeeol('\n[extensions]\nlargefiles=\n'))
896 896
897 897 # Caching is implicitly limited to 'rev' option, since the dest repo was
898 898 # truncated at that point. The user may expect a download count with
899 899 # this option, so attempt whether or not this is a largefile repo.
900 900 if opts.get('all_largefiles'):
901 901 success, missing = lfcommands.downloadlfiles(ui, repo, None)
902 902
903 903 if missing != 0:
904 904 return None
905 905
906 906 return result
907 907
908 908 def hgpostshare(orig, sourcerepo, destrepo, bookmarks=True, defaultpath=None):
909 909 orig(sourcerepo, destrepo, bookmarks, defaultpath)
910 910
911 911 # If largefiles is required for this repo, permanently enable it locally
912 912 if 'largefiles' in destrepo.requirements:
913 913 destrepo.vfs.append('hgrc',
914 914 util.tonativeeol('\n[extensions]\nlargefiles=\n'))
915 915
916 916 def overriderebase(orig, ui, repo, **opts):
917 917 if not util.safehasattr(repo, '_largefilesenabled'):
918 918 return orig(ui, repo, **opts)
919 919
920 920 resuming = opts.get(r'continue')
921 921 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
922 922 repo._lfstatuswriters.append(lambda *msg, **opts: None)
923 923 try:
924 924 return orig(ui, repo, **opts)
925 925 finally:
926 926 repo._lfstatuswriters.pop()
927 927 repo._lfcommithooks.pop()
928 928
929 929 def overridearchivecmd(orig, ui, repo, dest, **opts):
930 930 repo.unfiltered().lfstatus = True
931 931
932 932 try:
933 933 return orig(ui, repo.unfiltered(), dest, **opts)
934 934 finally:
935 935 repo.unfiltered().lfstatus = False
936 936
937 937 def hgwebarchive(orig, web):
938 938 web.repo.lfstatus = True
939 939
940 940 try:
941 941 return orig(web)
942 942 finally:
943 943 web.repo.lfstatus = False
944 944
945 945 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
946 946 prefix='', mtime=None, subrepos=None):
947 947 # For some reason setting repo.lfstatus in hgwebarchive only changes the
948 948 # unfiltered repo's attr, so check that as well.
949 949 if not repo.lfstatus and not repo.unfiltered().lfstatus:
950 950 return orig(repo, dest, node, kind, decode, matchfn, prefix, mtime,
951 951 subrepos)
952 952
953 953 # No need to lock because we are only reading history and
954 954 # largefile caches, neither of which are modified.
955 955 if node is not None:
956 956 lfcommands.cachelfiles(repo.ui, repo, node)
957 957
958 958 if kind not in archival.archivers:
959 959 raise error.Abort(_("unknown archive type '%s'") % kind)
960 960
961 961 ctx = repo[node]
962 962
963 963 if kind == 'files':
964 964 if prefix:
965 965 raise error.Abort(
966 966 _('cannot give prefix when archiving to files'))
967 967 else:
968 968 prefix = archival.tidyprefix(dest, kind, prefix)
969 969
970 970 def write(name, mode, islink, getdata):
971 971 if matchfn and not matchfn(name):
972 972 return
973 973 data = getdata()
974 974 if decode:
975 975 data = repo.wwritedata(name, data)
976 976 archiver.addfile(prefix + name, mode, islink, data)
977 977
978 978 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
979 979
980 980 if repo.ui.configbool("ui", "archivemeta"):
981 981 write('.hg_archival.txt', 0o644, False,
982 982 lambda: archival.buildmetadata(ctx))
983 983
984 984 for f in ctx:
985 985 ff = ctx.flags(f)
986 986 getdata = ctx[f].data
987 987 lfile = lfutil.splitstandin(f)
988 988 if lfile is not None:
989 989 if node is not None:
990 990 path = lfutil.findfile(repo, getdata().strip())
991 991
992 992 if path is None:
993 993 raise error.Abort(
994 994 _('largefile %s not found in repo store or system cache')
995 995 % lfile)
996 996 else:
997 997 path = lfile
998 998
999 999 f = lfile
1000 1000
1001 1001 getdata = lambda: util.readfile(path)
1002 1002 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1003 1003
1004 1004 if subrepos:
1005 1005 for subpath in sorted(ctx.substate):
1006 1006 sub = ctx.workingsub(subpath)
1007 1007 submatch = matchmod.subdirmatcher(subpath, matchfn)
1008 1008 sub._repo.lfstatus = True
1009 1009 sub.archive(archiver, prefix, submatch)
1010 1010
1011 1011 archiver.done()
1012 1012
1013 1013 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
1014 1014 lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
1015 1015 if not lfenabled or not repo._repo.lfstatus:
1016 1016 return orig(repo, archiver, prefix, match, decode)
1017 1017
1018 1018 repo._get(repo._state + ('hg',))
1019 1019 rev = repo._state[1]
1020 1020 ctx = repo._repo[rev]
1021 1021
1022 1022 if ctx.node() is not None:
1023 1023 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
1024 1024
1025 1025 def write(name, mode, islink, getdata):
1026 1026 # At this point, the standin has been replaced with the largefile name,
1027 1027 # so the normal matcher works here without the lfutil variants.
1028 1028 if match and not match(f):
1029 1029 return
1030 1030 data = getdata()
1031 1031 if decode:
1032 1032 data = repo._repo.wwritedata(name, data)
1033 1033
1034 1034 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
1035 1035
1036 1036 for f in ctx:
1037 1037 ff = ctx.flags(f)
1038 1038 getdata = ctx[f].data
1039 1039 lfile = lfutil.splitstandin(f)
1040 1040 if lfile is not None:
1041 1041 if ctx.node() is not None:
1042 1042 path = lfutil.findfile(repo._repo, getdata().strip())
1043 1043
1044 1044 if path is None:
1045 1045 raise error.Abort(
1046 1046 _('largefile %s not found in repo store or system cache')
1047 1047 % lfile)
1048 1048 else:
1049 1049 path = lfile
1050 1050
1051 1051 f = lfile
1052 1052
1053 1053 getdata = lambda: util.readfile(os.path.join(prefix, path))
1054 1054
1055 1055 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1056 1056
1057 1057 for subpath in sorted(ctx.substate):
1058 1058 sub = ctx.workingsub(subpath)
1059 1059 submatch = matchmod.subdirmatcher(subpath, match)
1060 1060 sub._repo.lfstatus = True
1061 1061 sub.archive(archiver, prefix + repo._path + '/', submatch, decode)
1062 1062
1063 1063 # If a largefile is modified, the change is not reflected in its
1064 1064 # standin until a commit. cmdutil.bailifchanged() raises an exception
1065 1065 # if the repo has uncommitted changes. Wrap it to also check if
1066 1066 # largefiles were changed. This is used by bisect, backout and fetch.
1067 1067 def overridebailifchanged(orig, repo, *args, **kwargs):
1068 1068 orig(repo, *args, **kwargs)
1069 1069 repo.lfstatus = True
1070 1070 s = repo.status()
1071 1071 repo.lfstatus = False
1072 1072 if s.modified or s.added or s.removed or s.deleted:
1073 1073 raise error.Abort(_('uncommitted changes'))
1074 1074
1075 1075 def postcommitstatus(orig, repo, *args, **kwargs):
1076 1076 repo.lfstatus = True
1077 1077 try:
1078 1078 return orig(repo, *args, **kwargs)
1079 1079 finally:
1080 1080 repo.lfstatus = False
1081 1081
1082 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly, dryrun):
1082 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly, dryrun, confirm):
1083 1083 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1084 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly, dryrun)
1084 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly, dryrun,
1085 confirm)
1085 1086 m = composelargefilematcher(match, repo[None].manifest())
1086 1087
1087 1088 try:
1088 1089 repo.lfstatus = True
1089 1090 s = repo.status(match=m, clean=True)
1090 1091 finally:
1091 1092 repo.lfstatus = False
1092 1093 manifest = repo[None].manifest()
1093 1094 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1094 1095 forget = [f for f in forget if lfutil.standin(f) in manifest]
1095 1096
1096 1097 for f in forget:
1097 1098 fstandin = lfutil.standin(f)
1098 1099 if fstandin not in repo.dirstate and not repo.wvfs.isdir(fstandin):
1099 1100 ui.warn(_('not removing %s: file is already untracked\n')
1100 1101 % m.rel(f))
1101 1102 bad.append(f)
1102 1103
1103 1104 for f in forget:
1104 1105 if ui.verbose or not m.exact(f):
1105 1106 ui.status(_('removing %s\n') % m.rel(f))
1106 1107
1107 1108 # Need to lock because standin files are deleted then removed from the
1108 1109 # repository and we could race in-between.
1109 1110 with repo.wlock():
1110 1111 lfdirstate = lfutil.openlfdirstate(ui, repo)
1111 1112 for f in forget:
1112 1113 if lfdirstate[f] == 'a':
1113 1114 lfdirstate.drop(f)
1114 1115 else:
1115 1116 lfdirstate.remove(f)
1116 1117 lfdirstate.write()
1117 1118 standins = [lfutil.standin(f) for f in forget]
1118 1119 for f in standins:
1119 1120 repo.wvfs.unlinkpath(f, ignoremissing=True)
1120 1121 rejected = repo[None].forget(standins)
1121 1122
1122 1123 bad.extend(f for f in rejected if f in m.files())
1123 1124 forgot.extend(f for f in forget if f not in rejected)
1124 1125 return bad, forgot
1125 1126
1126 1127 def _getoutgoings(repo, other, missing, addfunc):
1127 1128 """get pairs of filename and largefile hash in outgoing revisions
1128 1129 in 'missing'.
1129 1130
1130 1131 largefiles already existing on 'other' repository are ignored.
1131 1132
1132 1133 'addfunc' is invoked with each unique pairs of filename and
1133 1134 largefile hash value.
1134 1135 """
1135 1136 knowns = set()
1136 1137 lfhashes = set()
1137 1138 def dedup(fn, lfhash):
1138 1139 k = (fn, lfhash)
1139 1140 if k not in knowns:
1140 1141 knowns.add(k)
1141 1142 lfhashes.add(lfhash)
1142 1143 lfutil.getlfilestoupload(repo, missing, dedup)
1143 1144 if lfhashes:
1144 1145 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1145 1146 for fn, lfhash in knowns:
1146 1147 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1147 1148 addfunc(fn, lfhash)
1148 1149
1149 1150 def outgoinghook(ui, repo, other, opts, missing):
1150 1151 if opts.pop('large', None):
1151 1152 lfhashes = set()
1152 1153 if ui.debugflag:
1153 1154 toupload = {}
1154 1155 def addfunc(fn, lfhash):
1155 1156 if fn not in toupload:
1156 1157 toupload[fn] = []
1157 1158 toupload[fn].append(lfhash)
1158 1159 lfhashes.add(lfhash)
1159 1160 def showhashes(fn):
1160 1161 for lfhash in sorted(toupload[fn]):
1161 1162 ui.debug(' %s\n' % (lfhash))
1162 1163 else:
1163 1164 toupload = set()
1164 1165 def addfunc(fn, lfhash):
1165 1166 toupload.add(fn)
1166 1167 lfhashes.add(lfhash)
1167 1168 def showhashes(fn):
1168 1169 pass
1169 1170 _getoutgoings(repo, other, missing, addfunc)
1170 1171
1171 1172 if not toupload:
1172 1173 ui.status(_('largefiles: no files to upload\n'))
1173 1174 else:
1174 1175 ui.status(_('largefiles to upload (%d entities):\n')
1175 1176 % (len(lfhashes)))
1176 1177 for file in sorted(toupload):
1177 1178 ui.status(lfutil.splitstandin(file) + '\n')
1178 1179 showhashes(file)
1179 1180 ui.status('\n')
1180 1181
1181 1182 def summaryremotehook(ui, repo, opts, changes):
1182 1183 largeopt = opts.get('large', False)
1183 1184 if changes is None:
1184 1185 if largeopt:
1185 1186 return (False, True) # only outgoing check is needed
1186 1187 else:
1187 1188 return (False, False)
1188 1189 elif largeopt:
1189 1190 url, branch, peer, outgoing = changes[1]
1190 1191 if peer is None:
1191 1192 # i18n: column positioning for "hg summary"
1192 1193 ui.status(_('largefiles: (no remote repo)\n'))
1193 1194 return
1194 1195
1195 1196 toupload = set()
1196 1197 lfhashes = set()
1197 1198 def addfunc(fn, lfhash):
1198 1199 toupload.add(fn)
1199 1200 lfhashes.add(lfhash)
1200 1201 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1201 1202
1202 1203 if not toupload:
1203 1204 # i18n: column positioning for "hg summary"
1204 1205 ui.status(_('largefiles: (no files to upload)\n'))
1205 1206 else:
1206 1207 # i18n: column positioning for "hg summary"
1207 1208 ui.status(_('largefiles: %d entities for %d files to upload\n')
1208 1209 % (len(lfhashes), len(toupload)))
1209 1210
1210 1211 def overridesummary(orig, ui, repo, *pats, **opts):
1211 1212 try:
1212 1213 repo.lfstatus = True
1213 1214 orig(ui, repo, *pats, **opts)
1214 1215 finally:
1215 1216 repo.lfstatus = False
1216 1217
1217 1218 def scmutiladdremove(orig, repo, matcher, prefix, opts=None):
1218 1219 if opts is None:
1219 1220 opts = {}
1220 1221 if not lfutil.islfilesrepo(repo):
1221 1222 return orig(repo, matcher, prefix, opts)
1222 1223 # Get the list of missing largefiles so we can remove them
1223 1224 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1224 1225 unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()),
1225 1226 subrepos=[], ignored=False, clean=False,
1226 1227 unknown=False)
1227 1228
1228 1229 # Call into the normal remove code, but the removing of the standin, we want
1229 1230 # to have handled by original addremove. Monkey patching here makes sure
1230 1231 # we don't remove the standin in the largefiles code, preventing a very
1231 1232 # confused state later.
1232 1233 if s.deleted:
1233 1234 m = copy.copy(matcher)
1234 1235
1235 1236 # The m._files and m._map attributes are not changed to the deleted list
1236 1237 # because that affects the m.exact() test, which in turn governs whether
1237 1238 # or not the file name is printed, and how. Simply limit the original
1238 1239 # matches to those in the deleted status list.
1239 1240 matchfn = m.matchfn
1240 1241 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1241 1242
1242 1243 removelargefiles(repo.ui, repo, True, m, opts.get('dry_run'),
1243 1244 **pycompat.strkwargs(opts))
1244 1245 # Call into the normal add code, and any files that *should* be added as
1245 1246 # largefiles will be
1246 1247 added, bad = addlargefiles(repo.ui, repo, True, matcher,
1247 1248 **pycompat.strkwargs(opts))
1248 1249 # Now that we've handled largefiles, hand off to the original addremove
1249 1250 # function to take care of the rest. Make sure it doesn't do anything with
1250 1251 # largefiles by passing a matcher that will ignore them.
1251 1252 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1252 1253 return orig(repo, matcher, prefix, opts)
1253 1254
1254 1255 # Calling purge with --all will cause the largefiles to be deleted.
1255 1256 # Override repo.status to prevent this from happening.
1256 1257 def overridepurge(orig, ui, repo, *dirs, **opts):
1257 1258 # XXX Monkey patching a repoview will not work. The assigned attribute will
1258 1259 # be set on the unfiltered repo, but we will only lookup attributes in the
1259 1260 # unfiltered repo if the lookup in the repoview object itself fails. As the
1260 1261 # monkey patched method exists on the repoview class the lookup will not
1261 1262 # fail. As a result, the original version will shadow the monkey patched
1262 1263 # one, defeating the monkey patch.
1263 1264 #
1264 1265 # As a work around we use an unfiltered repo here. We should do something
1265 1266 # cleaner instead.
1266 1267 repo = repo.unfiltered()
1267 1268 oldstatus = repo.status
1268 1269 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1269 1270 clean=False, unknown=False, listsubrepos=False):
1270 1271 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1271 1272 listsubrepos)
1272 1273 lfdirstate = lfutil.openlfdirstate(ui, repo)
1273 1274 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1274 1275 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1275 1276 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1276 1277 unknown, ignored, r.clean)
1277 1278 repo.status = overridestatus
1278 1279 orig(ui, repo, *dirs, **opts)
1279 1280 repo.status = oldstatus
1280 1281
1281 1282 def overriderollback(orig, ui, repo, **opts):
1282 1283 with repo.wlock():
1283 1284 before = repo.dirstate.parents()
1284 1285 orphans = set(f for f in repo.dirstate
1285 1286 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1286 1287 result = orig(ui, repo, **opts)
1287 1288 after = repo.dirstate.parents()
1288 1289 if before == after:
1289 1290 return result # no need to restore standins
1290 1291
1291 1292 pctx = repo['.']
1292 1293 for f in repo.dirstate:
1293 1294 if lfutil.isstandin(f):
1294 1295 orphans.discard(f)
1295 1296 if repo.dirstate[f] == 'r':
1296 1297 repo.wvfs.unlinkpath(f, ignoremissing=True)
1297 1298 elif f in pctx:
1298 1299 fctx = pctx[f]
1299 1300 repo.wwrite(f, fctx.data(), fctx.flags())
1300 1301 else:
1301 1302 # content of standin is not so important in 'a',
1302 1303 # 'm' or 'n' (coming from the 2nd parent) cases
1303 1304 lfutil.writestandin(repo, f, '', False)
1304 1305 for standin in orphans:
1305 1306 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1306 1307
1307 1308 lfdirstate = lfutil.openlfdirstate(ui, repo)
1308 1309 orphans = set(lfdirstate)
1309 1310 lfiles = lfutil.listlfiles(repo)
1310 1311 for file in lfiles:
1311 1312 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1312 1313 orphans.discard(file)
1313 1314 for lfile in orphans:
1314 1315 lfdirstate.drop(lfile)
1315 1316 lfdirstate.write()
1316 1317 return result
1317 1318
1318 1319 def overridetransplant(orig, ui, repo, *revs, **opts):
1319 1320 resuming = opts.get(r'continue')
1320 1321 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1321 1322 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1322 1323 try:
1323 1324 result = orig(ui, repo, *revs, **opts)
1324 1325 finally:
1325 1326 repo._lfstatuswriters.pop()
1326 1327 repo._lfcommithooks.pop()
1327 1328 return result
1328 1329
1329 1330 def overridecat(orig, ui, repo, file1, *pats, **opts):
1330 1331 opts = pycompat.byteskwargs(opts)
1331 1332 ctx = scmutil.revsingle(repo, opts.get('rev'))
1332 1333 err = 1
1333 1334 notbad = set()
1334 1335 m = scmutil.match(ctx, (file1,) + pats, opts)
1335 1336 origmatchfn = m.matchfn
1336 1337 def lfmatchfn(f):
1337 1338 if origmatchfn(f):
1338 1339 return True
1339 1340 lf = lfutil.splitstandin(f)
1340 1341 if lf is None:
1341 1342 return False
1342 1343 notbad.add(lf)
1343 1344 return origmatchfn(lf)
1344 1345 m.matchfn = lfmatchfn
1345 1346 origbadfn = m.bad
1346 1347 def lfbadfn(f, msg):
1347 1348 if not f in notbad:
1348 1349 origbadfn(f, msg)
1349 1350 m.bad = lfbadfn
1350 1351
1351 1352 origvisitdirfn = m.visitdir
1352 1353 def lfvisitdirfn(dir):
1353 1354 if dir == lfutil.shortname:
1354 1355 return True
1355 1356 ret = origvisitdirfn(dir)
1356 1357 if ret:
1357 1358 return ret
1358 1359 lf = lfutil.splitstandin(dir)
1359 1360 if lf is None:
1360 1361 return False
1361 1362 return origvisitdirfn(lf)
1362 1363 m.visitdir = lfvisitdirfn
1363 1364
1364 1365 for f in ctx.walk(m):
1365 1366 with cmdutil.makefileobj(ctx, opts.get('output'), pathname=f) as fp:
1366 1367 lf = lfutil.splitstandin(f)
1367 1368 if lf is None or origmatchfn(f):
1368 1369 # duplicating unreachable code from commands.cat
1369 1370 data = ctx[f].data()
1370 1371 if opts.get('decode'):
1371 1372 data = repo.wwritedata(f, data)
1372 1373 fp.write(data)
1373 1374 else:
1374 1375 hash = lfutil.readasstandin(ctx[f])
1375 1376 if not lfutil.inusercache(repo.ui, hash):
1376 1377 store = storefactory.openstore(repo)
1377 1378 success, missing = store.get([(lf, hash)])
1378 1379 if len(success) != 1:
1379 1380 raise error.Abort(
1380 1381 _('largefile %s is not in cache and could not be '
1381 1382 'downloaded') % lf)
1382 1383 path = lfutil.usercachepath(repo.ui, hash)
1383 1384 with open(path, "rb") as fpin:
1384 1385 for chunk in util.filechunkiter(fpin):
1385 1386 fp.write(chunk)
1386 1387 err = 0
1387 1388 return err
1388 1389
1389 1390 def mergeupdate(orig, repo, node, branchmerge, force,
1390 1391 *args, **kwargs):
1391 1392 matcher = kwargs.get(r'matcher', None)
1392 1393 # note if this is a partial update
1393 1394 partial = matcher and not matcher.always()
1394 1395 with repo.wlock():
1395 1396 # branch | | |
1396 1397 # merge | force | partial | action
1397 1398 # -------+-------+---------+--------------
1398 1399 # x | x | x | linear-merge
1399 1400 # o | x | x | branch-merge
1400 1401 # x | o | x | overwrite (as clean update)
1401 1402 # o | o | x | force-branch-merge (*1)
1402 1403 # x | x | o | (*)
1403 1404 # o | x | o | (*)
1404 1405 # x | o | o | overwrite (as revert)
1405 1406 # o | o | o | (*)
1406 1407 #
1407 1408 # (*) don't care
1408 1409 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1409 1410
1410 1411 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1411 1412 unsure, s = lfdirstate.status(matchmod.always(repo.root,
1412 1413 repo.getcwd()),
1413 1414 subrepos=[], ignored=False,
1414 1415 clean=True, unknown=False)
1415 1416 oldclean = set(s.clean)
1416 1417 pctx = repo['.']
1417 1418 dctx = repo[node]
1418 1419 for lfile in unsure + s.modified:
1419 1420 lfileabs = repo.wvfs.join(lfile)
1420 1421 if not repo.wvfs.exists(lfileabs):
1421 1422 continue
1422 1423 lfhash = lfutil.hashfile(lfileabs)
1423 1424 standin = lfutil.standin(lfile)
1424 1425 lfutil.writestandin(repo, standin, lfhash,
1425 1426 lfutil.getexecutable(lfileabs))
1426 1427 if (standin in pctx and
1427 1428 lfhash == lfutil.readasstandin(pctx[standin])):
1428 1429 oldclean.add(lfile)
1429 1430 for lfile in s.added:
1430 1431 fstandin = lfutil.standin(lfile)
1431 1432 if fstandin not in dctx:
1432 1433 # in this case, content of standin file is meaningless
1433 1434 # (in dctx, lfile is unknown, or normal file)
1434 1435 continue
1435 1436 lfutil.updatestandin(repo, lfile, fstandin)
1436 1437 # mark all clean largefiles as dirty, just in case the update gets
1437 1438 # interrupted before largefiles and lfdirstate are synchronized
1438 1439 for lfile in oldclean:
1439 1440 lfdirstate.normallookup(lfile)
1440 1441 lfdirstate.write()
1441 1442
1442 1443 oldstandins = lfutil.getstandinsstate(repo)
1443 1444 # Make sure the merge runs on disk, not in-memory. largefiles is not a
1444 1445 # good candidate for in-memory merge (large files, custom dirstate,
1445 1446 # matcher usage).
1446 1447 kwargs[r'wc'] = repo[None]
1447 1448 result = orig(repo, node, branchmerge, force, *args, **kwargs)
1448 1449
1449 1450 newstandins = lfutil.getstandinsstate(repo)
1450 1451 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1451 1452
1452 1453 # to avoid leaving all largefiles as dirty and thus rehash them, mark
1453 1454 # all the ones that didn't change as clean
1454 1455 for lfile in oldclean.difference(filelist):
1455 1456 lfdirstate.normal(lfile)
1456 1457 lfdirstate.write()
1457 1458
1458 1459 if branchmerge or force or partial:
1459 1460 filelist.extend(s.deleted + s.removed)
1460 1461
1461 1462 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1462 1463 normallookup=partial)
1463 1464
1464 1465 return result
1465 1466
1466 1467 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1467 1468 result = orig(repo, files, *args, **kwargs)
1468 1469
1469 1470 filelist = []
1470 1471 for f in files:
1471 1472 lf = lfutil.splitstandin(f)
1472 1473 if lf is not None:
1473 1474 filelist.append(lf)
1474 1475 if filelist:
1475 1476 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1476 1477 printmessage=False, normallookup=True)
1477 1478
1478 1479 return result
1479 1480
1480 1481 def upgraderequirements(orig, repo):
1481 1482 reqs = orig(repo)
1482 1483 if 'largefiles' in repo.requirements:
1483 1484 reqs.add('largefiles')
1484 1485 return reqs
1485 1486
1486 1487 _lfscheme = 'largefile://'
1487 1488 def openlargefile(orig, ui, url_, data=None):
1488 1489 if url_.startswith(_lfscheme):
1489 1490 if data:
1490 1491 msg = "cannot use data on a 'largefile://' url"
1491 1492 raise error.ProgrammingError(msg)
1492 1493 lfid = url_[len(_lfscheme):]
1493 1494 return storefactory.getlfile(ui, lfid)
1494 1495 else:
1495 1496 return orig(ui, url_, data=data)
@@ -1,3232 +1,3266
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 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import os
12 12 import re
13 13 import tempfile
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 hex,
18 18 nullid,
19 19 nullrev,
20 20 short,
21 21 )
22 22
23 23 from . import (
24 24 bookmarks,
25 25 changelog,
26 26 copies,
27 27 crecord as crecordmod,
28 28 dirstateguard,
29 29 encoding,
30 30 error,
31 31 formatter,
32 32 logcmdutil,
33 33 match as matchmod,
34 34 merge as mergemod,
35 35 mergeutil,
36 36 obsolete,
37 37 patch,
38 38 pathutil,
39 39 pycompat,
40 40 registrar,
41 41 revlog,
42 42 rewriteutil,
43 43 scmutil,
44 44 smartset,
45 45 subrepoutil,
46 46 templatekw,
47 47 templater,
48 48 util,
49 49 vfs as vfsmod,
50 50 )
51 51
52 52 from .utils import (
53 53 dateutil,
54 54 stringutil,
55 55 )
56 56
57 57 stringio = util.stringio
58 58
59 59 # templates of common command options
60 60
61 61 dryrunopts = [
62 62 ('n', 'dry-run', None,
63 63 _('do not perform actions, just print output')),
64 64 ]
65 65
66 confirmopts = [
67 ('', 'confirm', None,
68 _('ask before applying actions')),
69 ]
70
66 71 remoteopts = [
67 72 ('e', 'ssh', '',
68 73 _('specify ssh command to use'), _('CMD')),
69 74 ('', 'remotecmd', '',
70 75 _('specify hg command to run on the remote side'), _('CMD')),
71 76 ('', 'insecure', None,
72 77 _('do not verify server certificate (ignoring web.cacerts config)')),
73 78 ]
74 79
75 80 walkopts = [
76 81 ('I', 'include', [],
77 82 _('include names matching the given patterns'), _('PATTERN')),
78 83 ('X', 'exclude', [],
79 84 _('exclude names matching the given patterns'), _('PATTERN')),
80 85 ]
81 86
82 87 commitopts = [
83 88 ('m', 'message', '',
84 89 _('use text as commit message'), _('TEXT')),
85 90 ('l', 'logfile', '',
86 91 _('read commit message from file'), _('FILE')),
87 92 ]
88 93
89 94 commitopts2 = [
90 95 ('d', 'date', '',
91 96 _('record the specified date as commit date'), _('DATE')),
92 97 ('u', 'user', '',
93 98 _('record the specified user as committer'), _('USER')),
94 99 ]
95 100
96 101 # hidden for now
97 102 formatteropts = [
98 103 ('T', 'template', '',
99 104 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
100 105 ]
101 106
102 107 templateopts = [
103 108 ('', 'style', '',
104 109 _('display using template map file (DEPRECATED)'), _('STYLE')),
105 110 ('T', 'template', '',
106 111 _('display with template'), _('TEMPLATE')),
107 112 ]
108 113
109 114 logopts = [
110 115 ('p', 'patch', None, _('show patch')),
111 116 ('g', 'git', None, _('use git extended diff format')),
112 117 ('l', 'limit', '',
113 118 _('limit number of changes displayed'), _('NUM')),
114 119 ('M', 'no-merges', None, _('do not show merges')),
115 120 ('', 'stat', None, _('output diffstat-style summary of changes')),
116 121 ('G', 'graph', None, _("show the revision DAG")),
117 122 ] + templateopts
118 123
119 124 diffopts = [
120 125 ('a', 'text', None, _('treat all files as text')),
121 126 ('g', 'git', None, _('use git extended diff format')),
122 127 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
123 128 ('', 'nodates', None, _('omit dates from diff headers'))
124 129 ]
125 130
126 131 diffwsopts = [
127 132 ('w', 'ignore-all-space', None,
128 133 _('ignore white space when comparing lines')),
129 134 ('b', 'ignore-space-change', None,
130 135 _('ignore changes in the amount of white space')),
131 136 ('B', 'ignore-blank-lines', None,
132 137 _('ignore changes whose lines are all blank')),
133 138 ('Z', 'ignore-space-at-eol', None,
134 139 _('ignore changes in whitespace at EOL')),
135 140 ]
136 141
137 142 diffopts2 = [
138 143 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
139 144 ('p', 'show-function', None, _('show which function each change is in')),
140 145 ('', 'reverse', None, _('produce a diff that undoes the changes')),
141 146 ] + diffwsopts + [
142 147 ('U', 'unified', '',
143 148 _('number of lines of context to show'), _('NUM')),
144 149 ('', 'stat', None, _('output diffstat-style summary of changes')),
145 150 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
146 151 ]
147 152
148 153 mergetoolopts = [
149 154 ('t', 'tool', '', _('specify merge tool')),
150 155 ]
151 156
152 157 similarityopts = [
153 158 ('s', 'similarity', '',
154 159 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
155 160 ]
156 161
157 162 subrepoopts = [
158 163 ('S', 'subrepos', None,
159 164 _('recurse into subrepositories'))
160 165 ]
161 166
162 167 debugrevlogopts = [
163 168 ('c', 'changelog', False, _('open changelog')),
164 169 ('m', 'manifest', False, _('open manifest')),
165 170 ('', 'dir', '', _('open directory manifest')),
166 171 ]
167 172
168 173 # special string such that everything below this line will be ingored in the
169 174 # editor text
170 175 _linebelow = "^HG: ------------------------ >8 ------------------------$"
171 176
172 177 def ishunk(x):
173 178 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
174 179 return isinstance(x, hunkclasses)
175 180
176 181 def newandmodified(chunks, originalchunks):
177 182 newlyaddedandmodifiedfiles = set()
178 183 for chunk in chunks:
179 184 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
180 185 originalchunks:
181 186 newlyaddedandmodifiedfiles.add(chunk.header.filename())
182 187 return newlyaddedandmodifiedfiles
183 188
184 189 def parsealiases(cmd):
185 190 return cmd.lstrip("^").split("|")
186 191
187 192 def setupwrapcolorwrite(ui):
188 193 # wrap ui.write so diff output can be labeled/colorized
189 194 def wrapwrite(orig, *args, **kw):
190 195 label = kw.pop(r'label', '')
191 196 for chunk, l in patch.difflabel(lambda: args):
192 197 orig(chunk, label=label + l)
193 198
194 199 oldwrite = ui.write
195 200 def wrap(*args, **kwargs):
196 201 return wrapwrite(oldwrite, *args, **kwargs)
197 202 setattr(ui, 'write', wrap)
198 203 return oldwrite
199 204
200 205 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
201 206 if usecurses:
202 207 if testfile:
203 208 recordfn = crecordmod.testdecorator(testfile,
204 209 crecordmod.testchunkselector)
205 210 else:
206 211 recordfn = crecordmod.chunkselector
207 212
208 213 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
209 214
210 215 else:
211 216 return patch.filterpatch(ui, originalhunks, operation)
212 217
213 218 def recordfilter(ui, originalhunks, operation=None):
214 219 """ Prompts the user to filter the originalhunks and return a list of
215 220 selected hunks.
216 221 *operation* is used for to build ui messages to indicate the user what
217 222 kind of filtering they are doing: reverting, committing, shelving, etc.
218 223 (see patch.filterpatch).
219 224 """
220 225 usecurses = crecordmod.checkcurses(ui)
221 226 testfile = ui.config('experimental', 'crecordtest')
222 227 oldwrite = setupwrapcolorwrite(ui)
223 228 try:
224 229 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
225 230 testfile, operation)
226 231 finally:
227 232 ui.write = oldwrite
228 233 return newchunks, newopts
229 234
230 235 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
231 236 filterfn, *pats, **opts):
232 237 opts = pycompat.byteskwargs(opts)
233 238 if not ui.interactive():
234 239 if cmdsuggest:
235 240 msg = _('running non-interactively, use %s instead') % cmdsuggest
236 241 else:
237 242 msg = _('running non-interactively')
238 243 raise error.Abort(msg)
239 244
240 245 # make sure username is set before going interactive
241 246 if not opts.get('user'):
242 247 ui.username() # raise exception, username not provided
243 248
244 249 def recordfunc(ui, repo, message, match, opts):
245 250 """This is generic record driver.
246 251
247 252 Its job is to interactively filter local changes, and
248 253 accordingly prepare working directory into a state in which the
249 254 job can be delegated to a non-interactive commit command such as
250 255 'commit' or 'qrefresh'.
251 256
252 257 After the actual job is done by non-interactive command, the
253 258 working directory is restored to its original state.
254 259
255 260 In the end we'll record interesting changes, and everything else
256 261 will be left in place, so the user can continue working.
257 262 """
258 263
259 264 checkunfinished(repo, commit=True)
260 265 wctx = repo[None]
261 266 merge = len(wctx.parents()) > 1
262 267 if merge:
263 268 raise error.Abort(_('cannot partially commit a merge '
264 269 '(use "hg commit" instead)'))
265 270
266 271 def fail(f, msg):
267 272 raise error.Abort('%s: %s' % (f, msg))
268 273
269 274 force = opts.get('force')
270 275 if not force:
271 276 vdirs = []
272 277 match.explicitdir = vdirs.append
273 278 match.bad = fail
274 279
275 280 status = repo.status(match=match)
276 281 if not force:
277 282 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
278 283 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
279 284 diffopts.nodates = True
280 285 diffopts.git = True
281 286 diffopts.showfunc = True
282 287 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
283 288 originalchunks = patch.parsepatch(originaldiff)
284 289
285 290 # 1. filter patch, since we are intending to apply subset of it
286 291 try:
287 292 chunks, newopts = filterfn(ui, originalchunks)
288 293 except error.PatchError as err:
289 294 raise error.Abort(_('error parsing patch: %s') % err)
290 295 opts.update(newopts)
291 296
292 297 # We need to keep a backup of files that have been newly added and
293 298 # modified during the recording process because there is a previous
294 299 # version without the edit in the workdir
295 300 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
296 301 contenders = set()
297 302 for h in chunks:
298 303 try:
299 304 contenders.update(set(h.files()))
300 305 except AttributeError:
301 306 pass
302 307
303 308 changed = status.modified + status.added + status.removed
304 309 newfiles = [f for f in changed if f in contenders]
305 310 if not newfiles:
306 311 ui.status(_('no changes to record\n'))
307 312 return 0
308 313
309 314 modified = set(status.modified)
310 315
311 316 # 2. backup changed files, so we can restore them in the end
312 317
313 318 if backupall:
314 319 tobackup = changed
315 320 else:
316 321 tobackup = [f for f in newfiles if f in modified or f in \
317 322 newlyaddedandmodifiedfiles]
318 323 backups = {}
319 324 if tobackup:
320 325 backupdir = repo.vfs.join('record-backups')
321 326 try:
322 327 os.mkdir(backupdir)
323 328 except OSError as err:
324 329 if err.errno != errno.EEXIST:
325 330 raise
326 331 try:
327 332 # backup continues
328 333 for f in tobackup:
329 334 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
330 335 dir=backupdir)
331 336 os.close(fd)
332 337 ui.debug('backup %r as %r\n' % (f, tmpname))
333 338 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
334 339 backups[f] = tmpname
335 340
336 341 fp = stringio()
337 342 for c in chunks:
338 343 fname = c.filename()
339 344 if fname in backups:
340 345 c.write(fp)
341 346 dopatch = fp.tell()
342 347 fp.seek(0)
343 348
344 349 # 2.5 optionally review / modify patch in text editor
345 350 if opts.get('review', False):
346 351 patchtext = (crecordmod.diffhelptext
347 352 + crecordmod.patchhelptext
348 353 + fp.read())
349 354 reviewedpatch = ui.edit(patchtext, "",
350 355 action="diff",
351 356 repopath=repo.path)
352 357 fp.truncate(0)
353 358 fp.write(reviewedpatch)
354 359 fp.seek(0)
355 360
356 361 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
357 362 # 3a. apply filtered patch to clean repo (clean)
358 363 if backups:
359 364 # Equivalent to hg.revert
360 365 m = scmutil.matchfiles(repo, backups.keys())
361 366 mergemod.update(repo, repo.dirstate.p1(),
362 367 False, True, matcher=m)
363 368
364 369 # 3b. (apply)
365 370 if dopatch:
366 371 try:
367 372 ui.debug('applying patch\n')
368 373 ui.debug(fp.getvalue())
369 374 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
370 375 except error.PatchError as err:
371 376 raise error.Abort(pycompat.bytestr(err))
372 377 del fp
373 378
374 379 # 4. We prepared working directory according to filtered
375 380 # patch. Now is the time to delegate the job to
376 381 # commit/qrefresh or the like!
377 382
378 383 # Make all of the pathnames absolute.
379 384 newfiles = [repo.wjoin(nf) for nf in newfiles]
380 385 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
381 386 finally:
382 387 # 5. finally restore backed-up files
383 388 try:
384 389 dirstate = repo.dirstate
385 390 for realname, tmpname in backups.iteritems():
386 391 ui.debug('restoring %r to %r\n' % (tmpname, realname))
387 392
388 393 if dirstate[realname] == 'n':
389 394 # without normallookup, restoring timestamp
390 395 # may cause partially committed files
391 396 # to be treated as unmodified
392 397 dirstate.normallookup(realname)
393 398
394 399 # copystat=True here and above are a hack to trick any
395 400 # editors that have f open that we haven't modified them.
396 401 #
397 402 # Also note that this racy as an editor could notice the
398 403 # file's mtime before we've finished writing it.
399 404 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
400 405 os.unlink(tmpname)
401 406 if tobackup:
402 407 os.rmdir(backupdir)
403 408 except OSError:
404 409 pass
405 410
406 411 def recordinwlock(ui, repo, message, match, opts):
407 412 with repo.wlock():
408 413 return recordfunc(ui, repo, message, match, opts)
409 414
410 415 return commit(ui, repo, recordinwlock, pats, opts)
411 416
412 417 class dirnode(object):
413 418 """
414 419 Represent a directory in user working copy with information required for
415 420 the purpose of tersing its status.
416 421
417 422 path is the path to the directory
418 423
419 424 statuses is a set of statuses of all files in this directory (this includes
420 425 all the files in all the subdirectories too)
421 426
422 427 files is a list of files which are direct child of this directory
423 428
424 429 subdirs is a dictionary of sub-directory name as the key and it's own
425 430 dirnode object as the value
426 431 """
427 432
428 433 def __init__(self, dirpath):
429 434 self.path = dirpath
430 435 self.statuses = set([])
431 436 self.files = []
432 437 self.subdirs = {}
433 438
434 439 def _addfileindir(self, filename, status):
435 440 """Add a file in this directory as a direct child."""
436 441 self.files.append((filename, status))
437 442
438 443 def addfile(self, filename, status):
439 444 """
440 445 Add a file to this directory or to its direct parent directory.
441 446
442 447 If the file is not direct child of this directory, we traverse to the
443 448 directory of which this file is a direct child of and add the file
444 449 there.
445 450 """
446 451
447 452 # the filename contains a path separator, it means it's not the direct
448 453 # child of this directory
449 454 if '/' in filename:
450 455 subdir, filep = filename.split('/', 1)
451 456
452 457 # does the dirnode object for subdir exists
453 458 if subdir not in self.subdirs:
454 459 subdirpath = os.path.join(self.path, subdir)
455 460 self.subdirs[subdir] = dirnode(subdirpath)
456 461
457 462 # try adding the file in subdir
458 463 self.subdirs[subdir].addfile(filep, status)
459 464
460 465 else:
461 466 self._addfileindir(filename, status)
462 467
463 468 if status not in self.statuses:
464 469 self.statuses.add(status)
465 470
466 471 def iterfilepaths(self):
467 472 """Yield (status, path) for files directly under this directory."""
468 473 for f, st in self.files:
469 474 yield st, os.path.join(self.path, f)
470 475
471 476 def tersewalk(self, terseargs):
472 477 """
473 478 Yield (status, path) obtained by processing the status of this
474 479 dirnode.
475 480
476 481 terseargs is the string of arguments passed by the user with `--terse`
477 482 flag.
478 483
479 484 Following are the cases which can happen:
480 485
481 486 1) All the files in the directory (including all the files in its
482 487 subdirectories) share the same status and the user has asked us to terse
483 488 that status. -> yield (status, dirpath)
484 489
485 490 2) Otherwise, we do following:
486 491
487 492 a) Yield (status, filepath) for all the files which are in this
488 493 directory (only the ones in this directory, not the subdirs)
489 494
490 495 b) Recurse the function on all the subdirectories of this
491 496 directory
492 497 """
493 498
494 499 if len(self.statuses) == 1:
495 500 onlyst = self.statuses.pop()
496 501
497 502 # Making sure we terse only when the status abbreviation is
498 503 # passed as terse argument
499 504 if onlyst in terseargs:
500 505 yield onlyst, self.path + pycompat.ossep
501 506 return
502 507
503 508 # add the files to status list
504 509 for st, fpath in self.iterfilepaths():
505 510 yield st, fpath
506 511
507 512 #recurse on the subdirs
508 513 for dirobj in self.subdirs.values():
509 514 for st, fpath in dirobj.tersewalk(terseargs):
510 515 yield st, fpath
511 516
512 517 def tersedir(statuslist, terseargs):
513 518 """
514 519 Terse the status if all the files in a directory shares the same status.
515 520
516 521 statuslist is scmutil.status() object which contains a list of files for
517 522 each status.
518 523 terseargs is string which is passed by the user as the argument to `--terse`
519 524 flag.
520 525
521 526 The function makes a tree of objects of dirnode class, and at each node it
522 527 stores the information required to know whether we can terse a certain
523 528 directory or not.
524 529 """
525 530 # the order matters here as that is used to produce final list
526 531 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
527 532
528 533 # checking the argument validity
529 534 for s in pycompat.bytestr(terseargs):
530 535 if s not in allst:
531 536 raise error.Abort(_("'%s' not recognized") % s)
532 537
533 538 # creating a dirnode object for the root of the repo
534 539 rootobj = dirnode('')
535 540 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
536 541 'ignored', 'removed')
537 542
538 543 tersedict = {}
539 544 for attrname in pstatus:
540 545 statuschar = attrname[0:1]
541 546 for f in getattr(statuslist, attrname):
542 547 rootobj.addfile(f, statuschar)
543 548 tersedict[statuschar] = []
544 549
545 550 # we won't be tersing the root dir, so add files in it
546 551 for st, fpath in rootobj.iterfilepaths():
547 552 tersedict[st].append(fpath)
548 553
549 554 # process each sub-directory and build tersedict
550 555 for subdir in rootobj.subdirs.values():
551 556 for st, f in subdir.tersewalk(terseargs):
552 557 tersedict[st].append(f)
553 558
554 559 tersedlist = []
555 560 for st in allst:
556 561 tersedict[st].sort()
557 562 tersedlist.append(tersedict[st])
558 563
559 564 return tersedlist
560 565
561 566 def _commentlines(raw):
562 567 '''Surround lineswith a comment char and a new line'''
563 568 lines = raw.splitlines()
564 569 commentedlines = ['# %s' % line for line in lines]
565 570 return '\n'.join(commentedlines) + '\n'
566 571
567 572 def _conflictsmsg(repo):
568 573 mergestate = mergemod.mergestate.read(repo)
569 574 if not mergestate.active():
570 575 return
571 576
572 577 m = scmutil.match(repo[None])
573 578 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
574 579 if unresolvedlist:
575 580 mergeliststr = '\n'.join(
576 581 [' %s' % util.pathto(repo.root, pycompat.getcwd(), path)
577 582 for path in unresolvedlist])
578 583 msg = _('''Unresolved merge conflicts:
579 584
580 585 %s
581 586
582 587 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
583 588 else:
584 589 msg = _('No unresolved merge conflicts.')
585 590
586 591 return _commentlines(msg)
587 592
588 593 def _helpmessage(continuecmd, abortcmd):
589 594 msg = _('To continue: %s\n'
590 595 'To abort: %s') % (continuecmd, abortcmd)
591 596 return _commentlines(msg)
592 597
593 598 def _rebasemsg():
594 599 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
595 600
596 601 def _histeditmsg():
597 602 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
598 603
599 604 def _unshelvemsg():
600 605 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
601 606
602 607 def _updatecleanmsg(dest=None):
603 608 warning = _('warning: this will discard uncommitted changes')
604 609 return 'hg update --clean %s (%s)' % (dest or '.', warning)
605 610
606 611 def _graftmsg():
607 612 # tweakdefaults requires `update` to have a rev hence the `.`
608 613 return _helpmessage('hg graft --continue', _updatecleanmsg())
609 614
610 615 def _mergemsg():
611 616 # tweakdefaults requires `update` to have a rev hence the `.`
612 617 return _helpmessage('hg commit', _updatecleanmsg())
613 618
614 619 def _bisectmsg():
615 620 msg = _('To mark the changeset good: hg bisect --good\n'
616 621 'To mark the changeset bad: hg bisect --bad\n'
617 622 'To abort: hg bisect --reset\n')
618 623 return _commentlines(msg)
619 624
620 625 def fileexistspredicate(filename):
621 626 return lambda repo: repo.vfs.exists(filename)
622 627
623 628 def _mergepredicate(repo):
624 629 return len(repo[None].parents()) > 1
625 630
626 631 STATES = (
627 632 # (state, predicate to detect states, helpful message function)
628 633 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
629 634 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
630 635 ('graft', fileexistspredicate('graftstate'), _graftmsg),
631 636 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
632 637 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
633 638 # The merge state is part of a list that will be iterated over.
634 639 # They need to be last because some of the other unfinished states may also
635 640 # be in a merge or update state (eg. rebase, histedit, graft, etc).
636 641 # We want those to have priority.
637 642 ('merge', _mergepredicate, _mergemsg),
638 643 )
639 644
640 645 def _getrepostate(repo):
641 646 # experimental config: commands.status.skipstates
642 647 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
643 648 for state, statedetectionpredicate, msgfn in STATES:
644 649 if state in skip:
645 650 continue
646 651 if statedetectionpredicate(repo):
647 652 return (state, statedetectionpredicate, msgfn)
648 653
649 654 def morestatus(repo, fm):
650 655 statetuple = _getrepostate(repo)
651 656 label = 'status.morestatus'
652 657 if statetuple:
653 658 fm.startitem()
654 659 state, statedetectionpredicate, helpfulmsg = statetuple
655 660 statemsg = _('The repository is in an unfinished *%s* state.') % state
656 661 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
657 662 conmsg = _conflictsmsg(repo)
658 663 if conmsg:
659 664 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
660 665 if helpfulmsg:
661 666 helpmsg = helpfulmsg()
662 667 fm.write('helpmsg', '%s\n', helpmsg, label=label)
663 668
664 669 def findpossible(cmd, table, strict=False):
665 670 """
666 671 Return cmd -> (aliases, command table entry)
667 672 for each matching command.
668 673 Return debug commands (or their aliases) only if no normal command matches.
669 674 """
670 675 choice = {}
671 676 debugchoice = {}
672 677
673 678 if cmd in table:
674 679 # short-circuit exact matches, "log" alias beats "^log|history"
675 680 keys = [cmd]
676 681 else:
677 682 keys = table.keys()
678 683
679 684 allcmds = []
680 685 for e in keys:
681 686 aliases = parsealiases(e)
682 687 allcmds.extend(aliases)
683 688 found = None
684 689 if cmd in aliases:
685 690 found = cmd
686 691 elif not strict:
687 692 for a in aliases:
688 693 if a.startswith(cmd):
689 694 found = a
690 695 break
691 696 if found is not None:
692 697 if aliases[0].startswith("debug") or found.startswith("debug"):
693 698 debugchoice[found] = (aliases, table[e])
694 699 else:
695 700 choice[found] = (aliases, table[e])
696 701
697 702 if not choice and debugchoice:
698 703 choice = debugchoice
699 704
700 705 return choice, allcmds
701 706
702 707 def findcmd(cmd, table, strict=True):
703 708 """Return (aliases, command table entry) for command string."""
704 709 choice, allcmds = findpossible(cmd, table, strict)
705 710
706 711 if cmd in choice:
707 712 return choice[cmd]
708 713
709 714 if len(choice) > 1:
710 715 clist = sorted(choice)
711 716 raise error.AmbiguousCommand(cmd, clist)
712 717
713 718 if choice:
714 719 return list(choice.values())[0]
715 720
716 721 raise error.UnknownCommand(cmd, allcmds)
717 722
718 723 def changebranch(ui, repo, revs, label):
719 724 """ Change the branch name of given revs to label """
720 725
721 726 with repo.wlock(), repo.lock(), repo.transaction('branches'):
722 727 # abort in case of uncommitted merge or dirty wdir
723 728 bailifchanged(repo)
724 729 revs = scmutil.revrange(repo, revs)
725 730 if not revs:
726 731 raise error.Abort("empty revision set")
727 732 roots = repo.revs('roots(%ld)', revs)
728 733 if len(roots) > 1:
729 734 raise error.Abort(_("cannot change branch of non-linear revisions"))
730 735 rewriteutil.precheck(repo, revs, 'change branch of')
731 736
732 737 root = repo[roots.first()]
733 738 if not root.p1().branch() == label and label in repo.branchmap():
734 739 raise error.Abort(_("a branch of the same name already exists"))
735 740
736 741 if repo.revs('merge() and %ld', revs):
737 742 raise error.Abort(_("cannot change branch of a merge commit"))
738 743 if repo.revs('obsolete() and %ld', revs):
739 744 raise error.Abort(_("cannot change branch of a obsolete changeset"))
740 745
741 746 # make sure only topological heads
742 747 if repo.revs('heads(%ld) - head()', revs):
743 748 raise error.Abort(_("cannot change branch in middle of a stack"))
744 749
745 750 replacements = {}
746 751 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
747 752 # mercurial.subrepo -> mercurial.cmdutil
748 753 from . import context
749 754 for rev in revs:
750 755 ctx = repo[rev]
751 756 oldbranch = ctx.branch()
752 757 # check if ctx has same branch
753 758 if oldbranch == label:
754 759 continue
755 760
756 761 def filectxfn(repo, newctx, path):
757 762 try:
758 763 return ctx[path]
759 764 except error.ManifestLookupError:
760 765 return None
761 766
762 767 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
763 768 % (hex(ctx.node()), oldbranch, label))
764 769 extra = ctx.extra()
765 770 extra['branch_change'] = hex(ctx.node())
766 771 # While changing branch of set of linear commits, make sure that
767 772 # we base our commits on new parent rather than old parent which
768 773 # was obsoleted while changing the branch
769 774 p1 = ctx.p1().node()
770 775 p2 = ctx.p2().node()
771 776 if p1 in replacements:
772 777 p1 = replacements[p1][0]
773 778 if p2 in replacements:
774 779 p2 = replacements[p2][0]
775 780
776 781 mc = context.memctx(repo, (p1, p2),
777 782 ctx.description(),
778 783 ctx.files(),
779 784 filectxfn,
780 785 user=ctx.user(),
781 786 date=ctx.date(),
782 787 extra=extra,
783 788 branch=label)
784 789
785 790 commitphase = ctx.phase()
786 791 overrides = {('phases', 'new-commit'): commitphase}
787 792 with repo.ui.configoverride(overrides, 'branch-change'):
788 793 newnode = repo.commitctx(mc)
789 794
790 795 replacements[ctx.node()] = (newnode,)
791 796 ui.debug('new node id is %s\n' % hex(newnode))
792 797
793 798 # create obsmarkers and move bookmarks
794 799 scmutil.cleanupnodes(repo, replacements, 'branch-change')
795 800
796 801 # move the working copy too
797 802 wctx = repo[None]
798 803 # in-progress merge is a bit too complex for now.
799 804 if len(wctx.parents()) == 1:
800 805 newid = replacements.get(wctx.p1().node())
801 806 if newid is not None:
802 807 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
803 808 # mercurial.cmdutil
804 809 from . import hg
805 810 hg.update(repo, newid[0], quietempty=True)
806 811
807 812 ui.status(_("changed branch on %d changesets\n") % len(replacements))
808 813
809 814 def findrepo(p):
810 815 while not os.path.isdir(os.path.join(p, ".hg")):
811 816 oldp, p = p, os.path.dirname(p)
812 817 if p == oldp:
813 818 return None
814 819
815 820 return p
816 821
817 822 def bailifchanged(repo, merge=True, hint=None):
818 823 """ enforce the precondition that working directory must be clean.
819 824
820 825 'merge' can be set to false if a pending uncommitted merge should be
821 826 ignored (such as when 'update --check' runs).
822 827
823 828 'hint' is the usual hint given to Abort exception.
824 829 """
825 830
826 831 if merge and repo.dirstate.p2() != nullid:
827 832 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
828 833 modified, added, removed, deleted = repo.status()[:4]
829 834 if modified or added or removed or deleted:
830 835 raise error.Abort(_('uncommitted changes'), hint=hint)
831 836 ctx = repo[None]
832 837 for s in sorted(ctx.substate):
833 838 ctx.sub(s).bailifchanged(hint=hint)
834 839
835 840 def logmessage(ui, opts):
836 841 """ get the log message according to -m and -l option """
837 842 message = opts.get('message')
838 843 logfile = opts.get('logfile')
839 844
840 845 if message and logfile:
841 846 raise error.Abort(_('options --message and --logfile are mutually '
842 847 'exclusive'))
843 848 if not message and logfile:
844 849 try:
845 850 if isstdiofilename(logfile):
846 851 message = ui.fin.read()
847 852 else:
848 853 message = '\n'.join(util.readfile(logfile).splitlines())
849 854 except IOError as inst:
850 855 raise error.Abort(_("can't read commit message '%s': %s") %
851 856 (logfile, encoding.strtolocal(inst.strerror)))
852 857 return message
853 858
854 859 def mergeeditform(ctxorbool, baseformname):
855 860 """return appropriate editform name (referencing a committemplate)
856 861
857 862 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
858 863 merging is committed.
859 864
860 865 This returns baseformname with '.merge' appended if it is a merge,
861 866 otherwise '.normal' is appended.
862 867 """
863 868 if isinstance(ctxorbool, bool):
864 869 if ctxorbool:
865 870 return baseformname + ".merge"
866 871 elif 1 < len(ctxorbool.parents()):
867 872 return baseformname + ".merge"
868 873
869 874 return baseformname + ".normal"
870 875
871 876 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
872 877 editform='', **opts):
873 878 """get appropriate commit message editor according to '--edit' option
874 879
875 880 'finishdesc' is a function to be called with edited commit message
876 881 (= 'description' of the new changeset) just after editing, but
877 882 before checking empty-ness. It should return actual text to be
878 883 stored into history. This allows to change description before
879 884 storing.
880 885
881 886 'extramsg' is a extra message to be shown in the editor instead of
882 887 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
883 888 is automatically added.
884 889
885 890 'editform' is a dot-separated list of names, to distinguish
886 891 the purpose of commit text editing.
887 892
888 893 'getcommiteditor' returns 'commitforceeditor' regardless of
889 894 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
890 895 they are specific for usage in MQ.
891 896 """
892 897 if edit or finishdesc or extramsg:
893 898 return lambda r, c, s: commitforceeditor(r, c, s,
894 899 finishdesc=finishdesc,
895 900 extramsg=extramsg,
896 901 editform=editform)
897 902 elif editform:
898 903 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
899 904 else:
900 905 return commiteditor
901 906
902 907 def rendertemplate(ctx, tmpl, props=None):
903 908 """Expand a literal template 'tmpl' byte-string against one changeset
904 909
905 910 Each props item must be a stringify-able value or a callable returning
906 911 such value, i.e. no bare list nor dict should be passed.
907 912 """
908 913 repo = ctx.repo()
909 914 tres = formatter.templateresources(repo.ui, repo)
910 915 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
911 916 resources=tres)
912 917 mapping = {'ctx': ctx}
913 918 if props:
914 919 mapping.update(props)
915 920 return t.renderdefault(mapping)
916 921
917 922 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
918 923 r"""Convert old-style filename format string to template string
919 924
920 925 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
921 926 'foo-{reporoot|basename}-{seqno}.patch'
922 927 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
923 928 '{rev}{tags % "{tag}"}{node}'
924 929
925 930 '\' in outermost strings has to be escaped because it is a directory
926 931 separator on Windows:
927 932
928 933 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
929 934 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
930 935 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
931 936 '\\\\\\\\foo\\\\bar.patch'
932 937 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
933 938 '\\\\{tags % "{tag}"}'
934 939
935 940 but inner strings follow the template rules (i.e. '\' is taken as an
936 941 escape character):
937 942
938 943 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
939 944 '{"c:\\tmp"}'
940 945 """
941 946 expander = {
942 947 b'H': b'{node}',
943 948 b'R': b'{rev}',
944 949 b'h': b'{node|short}',
945 950 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
946 951 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
947 952 b'%': b'%',
948 953 b'b': b'{reporoot|basename}',
949 954 }
950 955 if total is not None:
951 956 expander[b'N'] = b'{total}'
952 957 if seqno is not None:
953 958 expander[b'n'] = b'{seqno}'
954 959 if total is not None and seqno is not None:
955 960 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
956 961 if pathname is not None:
957 962 expander[b's'] = b'{pathname|basename}'
958 963 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
959 964 expander[b'p'] = b'{pathname}'
960 965
961 966 newname = []
962 967 for typ, start, end in templater.scantemplate(pat, raw=True):
963 968 if typ != b'string':
964 969 newname.append(pat[start:end])
965 970 continue
966 971 i = start
967 972 while i < end:
968 973 n = pat.find(b'%', i, end)
969 974 if n < 0:
970 975 newname.append(stringutil.escapestr(pat[i:end]))
971 976 break
972 977 newname.append(stringutil.escapestr(pat[i:n]))
973 978 if n + 2 > end:
974 979 raise error.Abort(_("incomplete format spec in output "
975 980 "filename"))
976 981 c = pat[n + 1:n + 2]
977 982 i = n + 2
978 983 try:
979 984 newname.append(expander[c])
980 985 except KeyError:
981 986 raise error.Abort(_("invalid format spec '%%%s' in output "
982 987 "filename") % c)
983 988 return ''.join(newname)
984 989
985 990 def makefilename(ctx, pat, **props):
986 991 if not pat:
987 992 return pat
988 993 tmpl = _buildfntemplate(pat, **props)
989 994 # BUG: alias expansion shouldn't be made against template fragments
990 995 # rewritten from %-format strings, but we have no easy way to partially
991 996 # disable the expansion.
992 997 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
993 998
994 999 def isstdiofilename(pat):
995 1000 """True if the given pat looks like a filename denoting stdin/stdout"""
996 1001 return not pat or pat == '-'
997 1002
998 1003 class _unclosablefile(object):
999 1004 def __init__(self, fp):
1000 1005 self._fp = fp
1001 1006
1002 1007 def close(self):
1003 1008 pass
1004 1009
1005 1010 def __iter__(self):
1006 1011 return iter(self._fp)
1007 1012
1008 1013 def __getattr__(self, attr):
1009 1014 return getattr(self._fp, attr)
1010 1015
1011 1016 def __enter__(self):
1012 1017 return self
1013 1018
1014 1019 def __exit__(self, exc_type, exc_value, exc_tb):
1015 1020 pass
1016 1021
1017 1022 def makefileobj(ctx, pat, mode='wb', **props):
1018 1023 writable = mode not in ('r', 'rb')
1019 1024
1020 1025 if isstdiofilename(pat):
1021 1026 repo = ctx.repo()
1022 1027 if writable:
1023 1028 fp = repo.ui.fout
1024 1029 else:
1025 1030 fp = repo.ui.fin
1026 1031 return _unclosablefile(fp)
1027 1032 fn = makefilename(ctx, pat, **props)
1028 1033 return open(fn, mode)
1029 1034
1030 1035 def openrevlog(repo, cmd, file_, opts):
1031 1036 """opens the changelog, manifest, a filelog or a given revlog"""
1032 1037 cl = opts['changelog']
1033 1038 mf = opts['manifest']
1034 1039 dir = opts['dir']
1035 1040 msg = None
1036 1041 if cl and mf:
1037 1042 msg = _('cannot specify --changelog and --manifest at the same time')
1038 1043 elif cl and dir:
1039 1044 msg = _('cannot specify --changelog and --dir at the same time')
1040 1045 elif cl or mf or dir:
1041 1046 if file_:
1042 1047 msg = _('cannot specify filename with --changelog or --manifest')
1043 1048 elif not repo:
1044 1049 msg = _('cannot specify --changelog or --manifest or --dir '
1045 1050 'without a repository')
1046 1051 if msg:
1047 1052 raise error.Abort(msg)
1048 1053
1049 1054 r = None
1050 1055 if repo:
1051 1056 if cl:
1052 1057 r = repo.unfiltered().changelog
1053 1058 elif dir:
1054 1059 if 'treemanifest' not in repo.requirements:
1055 1060 raise error.Abort(_("--dir can only be used on repos with "
1056 1061 "treemanifest enabled"))
1057 1062 if not dir.endswith('/'):
1058 1063 dir = dir + '/'
1059 1064 dirlog = repo.manifestlog._revlog.dirlog(dir)
1060 1065 if len(dirlog):
1061 1066 r = dirlog
1062 1067 elif mf:
1063 1068 r = repo.manifestlog._revlog
1064 1069 elif file_:
1065 1070 filelog = repo.file(file_)
1066 1071 if len(filelog):
1067 1072 r = filelog
1068 1073 if not r:
1069 1074 if not file_:
1070 1075 raise error.CommandError(cmd, _('invalid arguments'))
1071 1076 if not os.path.isfile(file_):
1072 1077 raise error.Abort(_("revlog '%s' not found") % file_)
1073 1078 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
1074 1079 file_[:-2] + ".i")
1075 1080 return r
1076 1081
1077 1082 def copy(ui, repo, pats, opts, rename=False):
1078 1083 # called with the repo lock held
1079 1084 #
1080 1085 # hgsep => pathname that uses "/" to separate directories
1081 1086 # ossep => pathname that uses os.sep to separate directories
1082 1087 cwd = repo.getcwd()
1083 1088 targets = {}
1084 1089 after = opts.get("after")
1085 1090 dryrun = opts.get("dry_run")
1086 1091 wctx = repo[None]
1087 1092
1088 1093 def walkpat(pat):
1089 1094 srcs = []
1090 1095 if after:
1091 1096 badstates = '?'
1092 1097 else:
1093 1098 badstates = '?r'
1094 1099 m = scmutil.match(wctx, [pat], opts, globbed=True)
1095 1100 for abs in wctx.walk(m):
1096 1101 state = repo.dirstate[abs]
1097 1102 rel = m.rel(abs)
1098 1103 exact = m.exact(abs)
1099 1104 if state in badstates:
1100 1105 if exact and state == '?':
1101 1106 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1102 1107 if exact and state == 'r':
1103 1108 ui.warn(_('%s: not copying - file has been marked for'
1104 1109 ' remove\n') % rel)
1105 1110 continue
1106 1111 # abs: hgsep
1107 1112 # rel: ossep
1108 1113 srcs.append((abs, rel, exact))
1109 1114 return srcs
1110 1115
1111 1116 # abssrc: hgsep
1112 1117 # relsrc: ossep
1113 1118 # otarget: ossep
1114 1119 def copyfile(abssrc, relsrc, otarget, exact):
1115 1120 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1116 1121 if '/' in abstarget:
1117 1122 # We cannot normalize abstarget itself, this would prevent
1118 1123 # case only renames, like a => A.
1119 1124 abspath, absname = abstarget.rsplit('/', 1)
1120 1125 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1121 1126 reltarget = repo.pathto(abstarget, cwd)
1122 1127 target = repo.wjoin(abstarget)
1123 1128 src = repo.wjoin(abssrc)
1124 1129 state = repo.dirstate[abstarget]
1125 1130
1126 1131 scmutil.checkportable(ui, abstarget)
1127 1132
1128 1133 # check for collisions
1129 1134 prevsrc = targets.get(abstarget)
1130 1135 if prevsrc is not None:
1131 1136 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1132 1137 (reltarget, repo.pathto(abssrc, cwd),
1133 1138 repo.pathto(prevsrc, cwd)))
1134 1139 return
1135 1140
1136 1141 # check for overwrites
1137 1142 exists = os.path.lexists(target)
1138 1143 samefile = False
1139 1144 if exists and abssrc != abstarget:
1140 1145 if (repo.dirstate.normalize(abssrc) ==
1141 1146 repo.dirstate.normalize(abstarget)):
1142 1147 if not rename:
1143 1148 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1144 1149 return
1145 1150 exists = False
1146 1151 samefile = True
1147 1152
1148 1153 if not after and exists or after and state in 'mn':
1149 1154 if not opts['force']:
1150 1155 if state in 'mn':
1151 1156 msg = _('%s: not overwriting - file already committed\n')
1152 1157 if after:
1153 1158 flags = '--after --force'
1154 1159 else:
1155 1160 flags = '--force'
1156 1161 if rename:
1157 1162 hint = _('(hg rename %s to replace the file by '
1158 1163 'recording a rename)\n') % flags
1159 1164 else:
1160 1165 hint = _('(hg copy %s to replace the file by '
1161 1166 'recording a copy)\n') % flags
1162 1167 else:
1163 1168 msg = _('%s: not overwriting - file exists\n')
1164 1169 if rename:
1165 1170 hint = _('(hg rename --after to record the rename)\n')
1166 1171 else:
1167 1172 hint = _('(hg copy --after to record the copy)\n')
1168 1173 ui.warn(msg % reltarget)
1169 1174 ui.warn(hint)
1170 1175 return
1171 1176
1172 1177 if after:
1173 1178 if not exists:
1174 1179 if rename:
1175 1180 ui.warn(_('%s: not recording move - %s does not exist\n') %
1176 1181 (relsrc, reltarget))
1177 1182 else:
1178 1183 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1179 1184 (relsrc, reltarget))
1180 1185 return
1181 1186 elif not dryrun:
1182 1187 try:
1183 1188 if exists:
1184 1189 os.unlink(target)
1185 1190 targetdir = os.path.dirname(target) or '.'
1186 1191 if not os.path.isdir(targetdir):
1187 1192 os.makedirs(targetdir)
1188 1193 if samefile:
1189 1194 tmp = target + "~hgrename"
1190 1195 os.rename(src, tmp)
1191 1196 os.rename(tmp, target)
1192 1197 else:
1193 1198 # Preserve stat info on renames, not on copies; this matches
1194 1199 # Linux CLI behavior.
1195 1200 util.copyfile(src, target, copystat=rename)
1196 1201 srcexists = True
1197 1202 except IOError as inst:
1198 1203 if inst.errno == errno.ENOENT:
1199 1204 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1200 1205 srcexists = False
1201 1206 else:
1202 1207 ui.warn(_('%s: cannot copy - %s\n') %
1203 1208 (relsrc, encoding.strtolocal(inst.strerror)))
1204 1209 return True # report a failure
1205 1210
1206 1211 if ui.verbose or not exact:
1207 1212 if rename:
1208 1213 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1209 1214 else:
1210 1215 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1211 1216
1212 1217 targets[abstarget] = abssrc
1213 1218
1214 1219 # fix up dirstate
1215 1220 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1216 1221 dryrun=dryrun, cwd=cwd)
1217 1222 if rename and not dryrun:
1218 1223 if not after and srcexists and not samefile:
1219 1224 repo.wvfs.unlinkpath(abssrc)
1220 1225 wctx.forget([abssrc])
1221 1226
1222 1227 # pat: ossep
1223 1228 # dest ossep
1224 1229 # srcs: list of (hgsep, hgsep, ossep, bool)
1225 1230 # return: function that takes hgsep and returns ossep
1226 1231 def targetpathfn(pat, dest, srcs):
1227 1232 if os.path.isdir(pat):
1228 1233 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1229 1234 abspfx = util.localpath(abspfx)
1230 1235 if destdirexists:
1231 1236 striplen = len(os.path.split(abspfx)[0])
1232 1237 else:
1233 1238 striplen = len(abspfx)
1234 1239 if striplen:
1235 1240 striplen += len(pycompat.ossep)
1236 1241 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1237 1242 elif destdirexists:
1238 1243 res = lambda p: os.path.join(dest,
1239 1244 os.path.basename(util.localpath(p)))
1240 1245 else:
1241 1246 res = lambda p: dest
1242 1247 return res
1243 1248
1244 1249 # pat: ossep
1245 1250 # dest ossep
1246 1251 # srcs: list of (hgsep, hgsep, ossep, bool)
1247 1252 # return: function that takes hgsep and returns ossep
1248 1253 def targetpathafterfn(pat, dest, srcs):
1249 1254 if matchmod.patkind(pat):
1250 1255 # a mercurial pattern
1251 1256 res = lambda p: os.path.join(dest,
1252 1257 os.path.basename(util.localpath(p)))
1253 1258 else:
1254 1259 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1255 1260 if len(abspfx) < len(srcs[0][0]):
1256 1261 # A directory. Either the target path contains the last
1257 1262 # component of the source path or it does not.
1258 1263 def evalpath(striplen):
1259 1264 score = 0
1260 1265 for s in srcs:
1261 1266 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1262 1267 if os.path.lexists(t):
1263 1268 score += 1
1264 1269 return score
1265 1270
1266 1271 abspfx = util.localpath(abspfx)
1267 1272 striplen = len(abspfx)
1268 1273 if striplen:
1269 1274 striplen += len(pycompat.ossep)
1270 1275 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1271 1276 score = evalpath(striplen)
1272 1277 striplen1 = len(os.path.split(abspfx)[0])
1273 1278 if striplen1:
1274 1279 striplen1 += len(pycompat.ossep)
1275 1280 if evalpath(striplen1) > score:
1276 1281 striplen = striplen1
1277 1282 res = lambda p: os.path.join(dest,
1278 1283 util.localpath(p)[striplen:])
1279 1284 else:
1280 1285 # a file
1281 1286 if destdirexists:
1282 1287 res = lambda p: os.path.join(dest,
1283 1288 os.path.basename(util.localpath(p)))
1284 1289 else:
1285 1290 res = lambda p: dest
1286 1291 return res
1287 1292
1288 1293 pats = scmutil.expandpats(pats)
1289 1294 if not pats:
1290 1295 raise error.Abort(_('no source or destination specified'))
1291 1296 if len(pats) == 1:
1292 1297 raise error.Abort(_('no destination specified'))
1293 1298 dest = pats.pop()
1294 1299 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1295 1300 if not destdirexists:
1296 1301 if len(pats) > 1 or matchmod.patkind(pats[0]):
1297 1302 raise error.Abort(_('with multiple sources, destination must be an '
1298 1303 'existing directory'))
1299 1304 if util.endswithsep(dest):
1300 1305 raise error.Abort(_('destination %s is not a directory') % dest)
1301 1306
1302 1307 tfn = targetpathfn
1303 1308 if after:
1304 1309 tfn = targetpathafterfn
1305 1310 copylist = []
1306 1311 for pat in pats:
1307 1312 srcs = walkpat(pat)
1308 1313 if not srcs:
1309 1314 continue
1310 1315 copylist.append((tfn(pat, dest, srcs), srcs))
1311 1316 if not copylist:
1312 1317 raise error.Abort(_('no files to copy'))
1313 1318
1314 1319 errors = 0
1315 1320 for targetpath, srcs in copylist:
1316 1321 for abssrc, relsrc, exact in srcs:
1317 1322 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1318 1323 errors += 1
1319 1324
1320 1325 if errors:
1321 1326 ui.warn(_('(consider using --after)\n'))
1322 1327
1323 1328 return errors != 0
1324 1329
1325 1330 ## facility to let extension process additional data into an import patch
1326 1331 # list of identifier to be executed in order
1327 1332 extrapreimport = [] # run before commit
1328 1333 extrapostimport = [] # run after commit
1329 1334 # mapping from identifier to actual import function
1330 1335 #
1331 1336 # 'preimport' are run before the commit is made and are provided the following
1332 1337 # arguments:
1333 1338 # - repo: the localrepository instance,
1334 1339 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1335 1340 # - extra: the future extra dictionary of the changeset, please mutate it,
1336 1341 # - opts: the import options.
1337 1342 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1338 1343 # mutation of in memory commit and more. Feel free to rework the code to get
1339 1344 # there.
1340 1345 extrapreimportmap = {}
1341 1346 # 'postimport' are run after the commit is made and are provided the following
1342 1347 # argument:
1343 1348 # - ctx: the changectx created by import.
1344 1349 extrapostimportmap = {}
1345 1350
1346 1351 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1347 1352 """Utility function used by commands.import to import a single patch
1348 1353
1349 1354 This function is explicitly defined here to help the evolve extension to
1350 1355 wrap this part of the import logic.
1351 1356
1352 1357 The API is currently a bit ugly because it a simple code translation from
1353 1358 the import command. Feel free to make it better.
1354 1359
1355 1360 :patchdata: a dictionary containing parsed patch data (such as from
1356 1361 ``patch.extract()``)
1357 1362 :parents: nodes that will be parent of the created commit
1358 1363 :opts: the full dict of option passed to the import command
1359 1364 :msgs: list to save commit message to.
1360 1365 (used in case we need to save it when failing)
1361 1366 :updatefunc: a function that update a repo to a given node
1362 1367 updatefunc(<repo>, <node>)
1363 1368 """
1364 1369 # avoid cycle context -> subrepo -> cmdutil
1365 1370 from . import context
1366 1371
1367 1372 tmpname = patchdata.get('filename')
1368 1373 message = patchdata.get('message')
1369 1374 user = opts.get('user') or patchdata.get('user')
1370 1375 date = opts.get('date') or patchdata.get('date')
1371 1376 branch = patchdata.get('branch')
1372 1377 nodeid = patchdata.get('nodeid')
1373 1378 p1 = patchdata.get('p1')
1374 1379 p2 = patchdata.get('p2')
1375 1380
1376 1381 nocommit = opts.get('no_commit')
1377 1382 importbranch = opts.get('import_branch')
1378 1383 update = not opts.get('bypass')
1379 1384 strip = opts["strip"]
1380 1385 prefix = opts["prefix"]
1381 1386 sim = float(opts.get('similarity') or 0)
1382 1387
1383 1388 if not tmpname:
1384 1389 return None, None, False
1385 1390
1386 1391 rejects = False
1387 1392
1388 1393 cmdline_message = logmessage(ui, opts)
1389 1394 if cmdline_message:
1390 1395 # pickup the cmdline msg
1391 1396 message = cmdline_message
1392 1397 elif message:
1393 1398 # pickup the patch msg
1394 1399 message = message.strip()
1395 1400 else:
1396 1401 # launch the editor
1397 1402 message = None
1398 1403 ui.debug('message:\n%s\n' % (message or ''))
1399 1404
1400 1405 if len(parents) == 1:
1401 1406 parents.append(repo[nullid])
1402 1407 if opts.get('exact'):
1403 1408 if not nodeid or not p1:
1404 1409 raise error.Abort(_('not a Mercurial patch'))
1405 1410 p1 = repo[p1]
1406 1411 p2 = repo[p2 or nullid]
1407 1412 elif p2:
1408 1413 try:
1409 1414 p1 = repo[p1]
1410 1415 p2 = repo[p2]
1411 1416 # Without any options, consider p2 only if the
1412 1417 # patch is being applied on top of the recorded
1413 1418 # first parent.
1414 1419 if p1 != parents[0]:
1415 1420 p1 = parents[0]
1416 1421 p2 = repo[nullid]
1417 1422 except error.RepoError:
1418 1423 p1, p2 = parents
1419 1424 if p2.node() == nullid:
1420 1425 ui.warn(_("warning: import the patch as a normal revision\n"
1421 1426 "(use --exact to import the patch as a merge)\n"))
1422 1427 else:
1423 1428 p1, p2 = parents
1424 1429
1425 1430 n = None
1426 1431 if update:
1427 1432 if p1 != parents[0]:
1428 1433 updatefunc(repo, p1.node())
1429 1434 if p2 != parents[1]:
1430 1435 repo.setparents(p1.node(), p2.node())
1431 1436
1432 1437 if opts.get('exact') or importbranch:
1433 1438 repo.dirstate.setbranch(branch or 'default')
1434 1439
1435 1440 partial = opts.get('partial', False)
1436 1441 files = set()
1437 1442 try:
1438 1443 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1439 1444 files=files, eolmode=None, similarity=sim / 100.0)
1440 1445 except error.PatchError as e:
1441 1446 if not partial:
1442 1447 raise error.Abort(pycompat.bytestr(e))
1443 1448 if partial:
1444 1449 rejects = True
1445 1450
1446 1451 files = list(files)
1447 1452 if nocommit:
1448 1453 if message:
1449 1454 msgs.append(message)
1450 1455 else:
1451 1456 if opts.get('exact') or p2:
1452 1457 # If you got here, you either use --force and know what
1453 1458 # you are doing or used --exact or a merge patch while
1454 1459 # being updated to its first parent.
1455 1460 m = None
1456 1461 else:
1457 1462 m = scmutil.matchfiles(repo, files or [])
1458 1463 editform = mergeeditform(repo[None], 'import.normal')
1459 1464 if opts.get('exact'):
1460 1465 editor = None
1461 1466 else:
1462 1467 editor = getcommiteditor(editform=editform,
1463 1468 **pycompat.strkwargs(opts))
1464 1469 extra = {}
1465 1470 for idfunc in extrapreimport:
1466 1471 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1467 1472 overrides = {}
1468 1473 if partial:
1469 1474 overrides[('ui', 'allowemptycommit')] = True
1470 1475 with repo.ui.configoverride(overrides, 'import'):
1471 1476 n = repo.commit(message, user,
1472 1477 date, match=m,
1473 1478 editor=editor, extra=extra)
1474 1479 for idfunc in extrapostimport:
1475 1480 extrapostimportmap[idfunc](repo[n])
1476 1481 else:
1477 1482 if opts.get('exact') or importbranch:
1478 1483 branch = branch or 'default'
1479 1484 else:
1480 1485 branch = p1.branch()
1481 1486 store = patch.filestore()
1482 1487 try:
1483 1488 files = set()
1484 1489 try:
1485 1490 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1486 1491 files, eolmode=None)
1487 1492 except error.PatchError as e:
1488 1493 raise error.Abort(stringutil.forcebytestr(e))
1489 1494 if opts.get('exact'):
1490 1495 editor = None
1491 1496 else:
1492 1497 editor = getcommiteditor(editform='import.bypass')
1493 1498 memctx = context.memctx(repo, (p1.node(), p2.node()),
1494 1499 message,
1495 1500 files=files,
1496 1501 filectxfn=store,
1497 1502 user=user,
1498 1503 date=date,
1499 1504 branch=branch,
1500 1505 editor=editor)
1501 1506 n = memctx.commit()
1502 1507 finally:
1503 1508 store.close()
1504 1509 if opts.get('exact') and nocommit:
1505 1510 # --exact with --no-commit is still useful in that it does merge
1506 1511 # and branch bits
1507 1512 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1508 1513 elif opts.get('exact') and hex(n) != nodeid:
1509 1514 raise error.Abort(_('patch is damaged or loses information'))
1510 1515 msg = _('applied to working directory')
1511 1516 if n:
1512 1517 # i18n: refers to a short changeset id
1513 1518 msg = _('created %s') % short(n)
1514 1519 return msg, n, rejects
1515 1520
1516 1521 # facility to let extensions include additional data in an exported patch
1517 1522 # list of identifiers to be executed in order
1518 1523 extraexport = []
1519 1524 # mapping from identifier to actual export function
1520 1525 # function as to return a string to be added to the header or None
1521 1526 # it is given two arguments (sequencenumber, changectx)
1522 1527 extraexportmap = {}
1523 1528
1524 1529 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1525 1530 node = scmutil.binnode(ctx)
1526 1531 parents = [p.node() for p in ctx.parents() if p]
1527 1532 branch = ctx.branch()
1528 1533 if switch_parent:
1529 1534 parents.reverse()
1530 1535
1531 1536 if parents:
1532 1537 prev = parents[0]
1533 1538 else:
1534 1539 prev = nullid
1535 1540
1536 1541 fm.context(ctx=ctx)
1537 1542 fm.plain('# HG changeset patch\n')
1538 1543 fm.write('user', '# User %s\n', ctx.user())
1539 1544 fm.plain('# Date %d %d\n' % ctx.date())
1540 1545 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1541 1546 fm.condwrite(branch and branch != 'default',
1542 1547 'branch', '# Branch %s\n', branch)
1543 1548 fm.write('node', '# Node ID %s\n', hex(node))
1544 1549 fm.plain('# Parent %s\n' % hex(prev))
1545 1550 if len(parents) > 1:
1546 1551 fm.plain('# Parent %s\n' % hex(parents[1]))
1547 1552 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1548 1553
1549 1554 # TODO: redesign extraexportmap function to support formatter
1550 1555 for headerid in extraexport:
1551 1556 header = extraexportmap[headerid](seqno, ctx)
1552 1557 if header is not None:
1553 1558 fm.plain('# %s\n' % header)
1554 1559
1555 1560 fm.write('desc', '%s\n', ctx.description().rstrip())
1556 1561 fm.plain('\n')
1557 1562
1558 1563 if fm.isplain():
1559 1564 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1560 1565 for chunk, label in chunkiter:
1561 1566 fm.plain(chunk, label=label)
1562 1567 else:
1563 1568 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1564 1569 # TODO: make it structured?
1565 1570 fm.data(diff=b''.join(chunkiter))
1566 1571
1567 1572 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1568 1573 """Export changesets to stdout or a single file"""
1569 1574 for seqno, rev in enumerate(revs, 1):
1570 1575 ctx = repo[rev]
1571 1576 if not dest.startswith('<'):
1572 1577 repo.ui.note("%s\n" % dest)
1573 1578 fm.startitem()
1574 1579 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1575 1580
1576 1581 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1577 1582 match):
1578 1583 """Export changesets to possibly multiple files"""
1579 1584 total = len(revs)
1580 1585 revwidth = max(len(str(rev)) for rev in revs)
1581 1586 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1582 1587
1583 1588 for seqno, rev in enumerate(revs, 1):
1584 1589 ctx = repo[rev]
1585 1590 dest = makefilename(ctx, fntemplate,
1586 1591 total=total, seqno=seqno, revwidth=revwidth)
1587 1592 filemap.setdefault(dest, []).append((seqno, rev))
1588 1593
1589 1594 for dest in filemap:
1590 1595 with formatter.maybereopen(basefm, dest) as fm:
1591 1596 repo.ui.note("%s\n" % dest)
1592 1597 for seqno, rev in filemap[dest]:
1593 1598 fm.startitem()
1594 1599 ctx = repo[rev]
1595 1600 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1596 1601 diffopts)
1597 1602
1598 1603 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1599 1604 opts=None, match=None):
1600 1605 '''export changesets as hg patches
1601 1606
1602 1607 Args:
1603 1608 repo: The repository from which we're exporting revisions.
1604 1609 revs: A list of revisions to export as revision numbers.
1605 1610 basefm: A formatter to which patches should be written.
1606 1611 fntemplate: An optional string to use for generating patch file names.
1607 1612 switch_parent: If True, show diffs against second parent when not nullid.
1608 1613 Default is false, which always shows diff against p1.
1609 1614 opts: diff options to use for generating the patch.
1610 1615 match: If specified, only export changes to files matching this matcher.
1611 1616
1612 1617 Returns:
1613 1618 Nothing.
1614 1619
1615 1620 Side Effect:
1616 1621 "HG Changeset Patch" data is emitted to one of the following
1617 1622 destinations:
1618 1623 fntemplate specified: Each rev is written to a unique file named using
1619 1624 the given template.
1620 1625 Otherwise: All revs will be written to basefm.
1621 1626 '''
1622 1627 if not fntemplate:
1623 1628 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1624 1629 else:
1625 1630 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1626 1631 match)
1627 1632
1628 1633 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1629 1634 """Export changesets to the given file stream"""
1630 1635 dest = getattr(fp, 'name', '<unnamed>')
1631 1636 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1632 1637 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1633 1638
1634 1639 def showmarker(fm, marker, index=None):
1635 1640 """utility function to display obsolescence marker in a readable way
1636 1641
1637 1642 To be used by debug function."""
1638 1643 if index is not None:
1639 1644 fm.write('index', '%i ', index)
1640 1645 fm.write('prednode', '%s ', hex(marker.prednode()))
1641 1646 succs = marker.succnodes()
1642 1647 fm.condwrite(succs, 'succnodes', '%s ',
1643 1648 fm.formatlist(map(hex, succs), name='node'))
1644 1649 fm.write('flag', '%X ', marker.flags())
1645 1650 parents = marker.parentnodes()
1646 1651 if parents is not None:
1647 1652 fm.write('parentnodes', '{%s} ',
1648 1653 fm.formatlist(map(hex, parents), name='node', sep=', '))
1649 1654 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1650 1655 meta = marker.metadata().copy()
1651 1656 meta.pop('date', None)
1652 1657 smeta = util.rapply(pycompat.maybebytestr, meta)
1653 1658 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1654 1659 fm.plain('\n')
1655 1660
1656 1661 def finddate(ui, repo, date):
1657 1662 """Find the tipmost changeset that matches the given date spec"""
1658 1663
1659 1664 df = dateutil.matchdate(date)
1660 1665 m = scmutil.matchall(repo)
1661 1666 results = {}
1662 1667
1663 1668 def prep(ctx, fns):
1664 1669 d = ctx.date()
1665 1670 if df(d[0]):
1666 1671 results[ctx.rev()] = d
1667 1672
1668 1673 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1669 1674 rev = ctx.rev()
1670 1675 if rev in results:
1671 1676 ui.status(_("found revision %s from %s\n") %
1672 1677 (rev, dateutil.datestr(results[rev])))
1673 1678 return '%d' % rev
1674 1679
1675 1680 raise error.Abort(_("revision matching date not found"))
1676 1681
1677 1682 def increasingwindows(windowsize=8, sizelimit=512):
1678 1683 while True:
1679 1684 yield windowsize
1680 1685 if windowsize < sizelimit:
1681 1686 windowsize *= 2
1682 1687
1683 1688 def _walkrevs(repo, opts):
1684 1689 # Default --rev value depends on --follow but --follow behavior
1685 1690 # depends on revisions resolved from --rev...
1686 1691 follow = opts.get('follow') or opts.get('follow_first')
1687 1692 if opts.get('rev'):
1688 1693 revs = scmutil.revrange(repo, opts['rev'])
1689 1694 elif follow and repo.dirstate.p1() == nullid:
1690 1695 revs = smartset.baseset()
1691 1696 elif follow:
1692 1697 revs = repo.revs('reverse(:.)')
1693 1698 else:
1694 1699 revs = smartset.spanset(repo)
1695 1700 revs.reverse()
1696 1701 return revs
1697 1702
1698 1703 class FileWalkError(Exception):
1699 1704 pass
1700 1705
1701 1706 def walkfilerevs(repo, match, follow, revs, fncache):
1702 1707 '''Walks the file history for the matched files.
1703 1708
1704 1709 Returns the changeset revs that are involved in the file history.
1705 1710
1706 1711 Throws FileWalkError if the file history can't be walked using
1707 1712 filelogs alone.
1708 1713 '''
1709 1714 wanted = set()
1710 1715 copies = []
1711 1716 minrev, maxrev = min(revs), max(revs)
1712 1717 def filerevgen(filelog, last):
1713 1718 """
1714 1719 Only files, no patterns. Check the history of each file.
1715 1720
1716 1721 Examines filelog entries within minrev, maxrev linkrev range
1717 1722 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1718 1723 tuples in backwards order
1719 1724 """
1720 1725 cl_count = len(repo)
1721 1726 revs = []
1722 1727 for j in xrange(0, last + 1):
1723 1728 linkrev = filelog.linkrev(j)
1724 1729 if linkrev < minrev:
1725 1730 continue
1726 1731 # only yield rev for which we have the changelog, it can
1727 1732 # happen while doing "hg log" during a pull or commit
1728 1733 if linkrev >= cl_count:
1729 1734 break
1730 1735
1731 1736 parentlinkrevs = []
1732 1737 for p in filelog.parentrevs(j):
1733 1738 if p != nullrev:
1734 1739 parentlinkrevs.append(filelog.linkrev(p))
1735 1740 n = filelog.node(j)
1736 1741 revs.append((linkrev, parentlinkrevs,
1737 1742 follow and filelog.renamed(n)))
1738 1743
1739 1744 return reversed(revs)
1740 1745 def iterfiles():
1741 1746 pctx = repo['.']
1742 1747 for filename in match.files():
1743 1748 if follow:
1744 1749 if filename not in pctx:
1745 1750 raise error.Abort(_('cannot follow file not in parent '
1746 1751 'revision: "%s"') % filename)
1747 1752 yield filename, pctx[filename].filenode()
1748 1753 else:
1749 1754 yield filename, None
1750 1755 for filename_node in copies:
1751 1756 yield filename_node
1752 1757
1753 1758 for file_, node in iterfiles():
1754 1759 filelog = repo.file(file_)
1755 1760 if not len(filelog):
1756 1761 if node is None:
1757 1762 # A zero count may be a directory or deleted file, so
1758 1763 # try to find matching entries on the slow path.
1759 1764 if follow:
1760 1765 raise error.Abort(
1761 1766 _('cannot follow nonexistent file: "%s"') % file_)
1762 1767 raise FileWalkError("Cannot walk via filelog")
1763 1768 else:
1764 1769 continue
1765 1770
1766 1771 if node is None:
1767 1772 last = len(filelog) - 1
1768 1773 else:
1769 1774 last = filelog.rev(node)
1770 1775
1771 1776 # keep track of all ancestors of the file
1772 1777 ancestors = {filelog.linkrev(last)}
1773 1778
1774 1779 # iterate from latest to oldest revision
1775 1780 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1776 1781 if not follow:
1777 1782 if rev > maxrev:
1778 1783 continue
1779 1784 else:
1780 1785 # Note that last might not be the first interesting
1781 1786 # rev to us:
1782 1787 # if the file has been changed after maxrev, we'll
1783 1788 # have linkrev(last) > maxrev, and we still need
1784 1789 # to explore the file graph
1785 1790 if rev not in ancestors:
1786 1791 continue
1787 1792 # XXX insert 1327 fix here
1788 1793 if flparentlinkrevs:
1789 1794 ancestors.update(flparentlinkrevs)
1790 1795
1791 1796 fncache.setdefault(rev, []).append(file_)
1792 1797 wanted.add(rev)
1793 1798 if copied:
1794 1799 copies.append(copied)
1795 1800
1796 1801 return wanted
1797 1802
1798 1803 class _followfilter(object):
1799 1804 def __init__(self, repo, onlyfirst=False):
1800 1805 self.repo = repo
1801 1806 self.startrev = nullrev
1802 1807 self.roots = set()
1803 1808 self.onlyfirst = onlyfirst
1804 1809
1805 1810 def match(self, rev):
1806 1811 def realparents(rev):
1807 1812 if self.onlyfirst:
1808 1813 return self.repo.changelog.parentrevs(rev)[0:1]
1809 1814 else:
1810 1815 return filter(lambda x: x != nullrev,
1811 1816 self.repo.changelog.parentrevs(rev))
1812 1817
1813 1818 if self.startrev == nullrev:
1814 1819 self.startrev = rev
1815 1820 return True
1816 1821
1817 1822 if rev > self.startrev:
1818 1823 # forward: all descendants
1819 1824 if not self.roots:
1820 1825 self.roots.add(self.startrev)
1821 1826 for parent in realparents(rev):
1822 1827 if parent in self.roots:
1823 1828 self.roots.add(rev)
1824 1829 return True
1825 1830 else:
1826 1831 # backwards: all parents
1827 1832 if not self.roots:
1828 1833 self.roots.update(realparents(self.startrev))
1829 1834 if rev in self.roots:
1830 1835 self.roots.remove(rev)
1831 1836 self.roots.update(realparents(rev))
1832 1837 return True
1833 1838
1834 1839 return False
1835 1840
1836 1841 def walkchangerevs(repo, match, opts, prepare):
1837 1842 '''Iterate over files and the revs in which they changed.
1838 1843
1839 1844 Callers most commonly need to iterate backwards over the history
1840 1845 in which they are interested. Doing so has awful (quadratic-looking)
1841 1846 performance, so we use iterators in a "windowed" way.
1842 1847
1843 1848 We walk a window of revisions in the desired order. Within the
1844 1849 window, we first walk forwards to gather data, then in the desired
1845 1850 order (usually backwards) to display it.
1846 1851
1847 1852 This function returns an iterator yielding contexts. Before
1848 1853 yielding each context, the iterator will first call the prepare
1849 1854 function on each context in the window in forward order.'''
1850 1855
1851 1856 follow = opts.get('follow') or opts.get('follow_first')
1852 1857 revs = _walkrevs(repo, opts)
1853 1858 if not revs:
1854 1859 return []
1855 1860 wanted = set()
1856 1861 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1857 1862 fncache = {}
1858 1863 change = repo.__getitem__
1859 1864
1860 1865 # First step is to fill wanted, the set of revisions that we want to yield.
1861 1866 # When it does not induce extra cost, we also fill fncache for revisions in
1862 1867 # wanted: a cache of filenames that were changed (ctx.files()) and that
1863 1868 # match the file filtering conditions.
1864 1869
1865 1870 if match.always():
1866 1871 # No files, no patterns. Display all revs.
1867 1872 wanted = revs
1868 1873 elif not slowpath:
1869 1874 # We only have to read through the filelog to find wanted revisions
1870 1875
1871 1876 try:
1872 1877 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1873 1878 except FileWalkError:
1874 1879 slowpath = True
1875 1880
1876 1881 # We decided to fall back to the slowpath because at least one
1877 1882 # of the paths was not a file. Check to see if at least one of them
1878 1883 # existed in history, otherwise simply return
1879 1884 for path in match.files():
1880 1885 if path == '.' or path in repo.store:
1881 1886 break
1882 1887 else:
1883 1888 return []
1884 1889
1885 1890 if slowpath:
1886 1891 # We have to read the changelog to match filenames against
1887 1892 # changed files
1888 1893
1889 1894 if follow:
1890 1895 raise error.Abort(_('can only follow copies/renames for explicit '
1891 1896 'filenames'))
1892 1897
1893 1898 # The slow path checks files modified in every changeset.
1894 1899 # This is really slow on large repos, so compute the set lazily.
1895 1900 class lazywantedset(object):
1896 1901 def __init__(self):
1897 1902 self.set = set()
1898 1903 self.revs = set(revs)
1899 1904
1900 1905 # No need to worry about locality here because it will be accessed
1901 1906 # in the same order as the increasing window below.
1902 1907 def __contains__(self, value):
1903 1908 if value in self.set:
1904 1909 return True
1905 1910 elif not value in self.revs:
1906 1911 return False
1907 1912 else:
1908 1913 self.revs.discard(value)
1909 1914 ctx = change(value)
1910 1915 matches = [f for f in ctx.files() if match(f)]
1911 1916 if matches:
1912 1917 fncache[value] = matches
1913 1918 self.set.add(value)
1914 1919 return True
1915 1920 return False
1916 1921
1917 1922 def discard(self, value):
1918 1923 self.revs.discard(value)
1919 1924 self.set.discard(value)
1920 1925
1921 1926 wanted = lazywantedset()
1922 1927
1923 1928 # it might be worthwhile to do this in the iterator if the rev range
1924 1929 # is descending and the prune args are all within that range
1925 1930 for rev in opts.get('prune', ()):
1926 1931 rev = repo[rev].rev()
1927 1932 ff = _followfilter(repo)
1928 1933 stop = min(revs[0], revs[-1])
1929 1934 for x in xrange(rev, stop - 1, -1):
1930 1935 if ff.match(x):
1931 1936 wanted = wanted - [x]
1932 1937
1933 1938 # Now that wanted is correctly initialized, we can iterate over the
1934 1939 # revision range, yielding only revisions in wanted.
1935 1940 def iterate():
1936 1941 if follow and match.always():
1937 1942 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1938 1943 def want(rev):
1939 1944 return ff.match(rev) and rev in wanted
1940 1945 else:
1941 1946 def want(rev):
1942 1947 return rev in wanted
1943 1948
1944 1949 it = iter(revs)
1945 1950 stopiteration = False
1946 1951 for windowsize in increasingwindows():
1947 1952 nrevs = []
1948 1953 for i in xrange(windowsize):
1949 1954 rev = next(it, None)
1950 1955 if rev is None:
1951 1956 stopiteration = True
1952 1957 break
1953 1958 elif want(rev):
1954 1959 nrevs.append(rev)
1955 1960 for rev in sorted(nrevs):
1956 1961 fns = fncache.get(rev)
1957 1962 ctx = change(rev)
1958 1963 if not fns:
1959 1964 def fns_generator():
1960 1965 for f in ctx.files():
1961 1966 if match(f):
1962 1967 yield f
1963 1968 fns = fns_generator()
1964 1969 prepare(ctx, fns)
1965 1970 for rev in nrevs:
1966 1971 yield change(rev)
1967 1972
1968 1973 if stopiteration:
1969 1974 break
1970 1975
1971 1976 return iterate()
1972 1977
1973 1978 def add(ui, repo, match, prefix, explicitonly, **opts):
1974 1979 join = lambda f: os.path.join(prefix, f)
1975 1980 bad = []
1976 1981
1977 1982 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1978 1983 names = []
1979 1984 wctx = repo[None]
1980 1985 cca = None
1981 1986 abort, warn = scmutil.checkportabilityalert(ui)
1982 1987 if abort or warn:
1983 1988 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1984 1989
1985 1990 badmatch = matchmod.badmatch(match, badfn)
1986 1991 dirstate = repo.dirstate
1987 1992 # We don't want to just call wctx.walk here, since it would return a lot of
1988 1993 # clean files, which we aren't interested in and takes time.
1989 1994 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
1990 1995 unknown=True, ignored=False, full=False)):
1991 1996 exact = match.exact(f)
1992 1997 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1993 1998 if cca:
1994 1999 cca(f)
1995 2000 names.append(f)
1996 2001 if ui.verbose or not exact:
1997 2002 ui.status(_('adding %s\n') % match.rel(f))
1998 2003
1999 2004 for subpath in sorted(wctx.substate):
2000 2005 sub = wctx.sub(subpath)
2001 2006 try:
2002 2007 submatch = matchmod.subdirmatcher(subpath, match)
2003 2008 if opts.get(r'subrepos'):
2004 2009 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2005 2010 else:
2006 2011 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2007 2012 except error.LookupError:
2008 2013 ui.status(_("skipping missing subrepository: %s\n")
2009 2014 % join(subpath))
2010 2015
2011 2016 if not opts.get(r'dry_run'):
2012 2017 rejected = wctx.add(names, prefix)
2013 2018 bad.extend(f for f in rejected if f in match.files())
2014 2019 return bad
2015 2020
2016 2021 def addwebdirpath(repo, serverpath, webconf):
2017 2022 webconf[serverpath] = repo.root
2018 2023 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2019 2024
2020 2025 for r in repo.revs('filelog("path:.hgsub")'):
2021 2026 ctx = repo[r]
2022 2027 for subpath in ctx.substate:
2023 2028 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2024 2029
2025 def forget(ui, repo, match, prefix, explicitonly, dryrun):
2030 def forget(ui, repo, match, prefix, explicitonly, dryrun, confirm):
2031 if dryrun and confirm:
2032 raise error.Abort(_("cannot specify both --dry-run and --confirm"))
2026 2033 join = lambda f: os.path.join(prefix, f)
2027 2034 bad = []
2028 2035 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2029 2036 wctx = repo[None]
2030 2037 forgot = []
2031 2038
2032 2039 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2033 2040 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2034 2041 if explicitonly:
2035 2042 forget = [f for f in forget if match.exact(f)]
2036 2043
2037 2044 for subpath in sorted(wctx.substate):
2038 2045 sub = wctx.sub(subpath)
2039 2046 try:
2040 2047 submatch = matchmod.subdirmatcher(subpath, match)
2041 subbad, subforgot = sub.forget(submatch, prefix, dryrun=dryrun)
2048 subbad, subforgot = sub.forget(submatch, prefix,
2049 dryrun=dryrun, confirm=confirm)
2042 2050 bad.extend([subpath + '/' + f for f in subbad])
2043 2051 forgot.extend([subpath + '/' + f for f in subforgot])
2044 2052 except error.LookupError:
2045 2053 ui.status(_("skipping missing subrepository: %s\n")
2046 2054 % join(subpath))
2047 2055
2048 2056 if not explicitonly:
2049 2057 for f in match.files():
2050 2058 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2051 2059 if f not in forgot:
2052 2060 if repo.wvfs.exists(f):
2053 2061 # Don't complain if the exact case match wasn't given.
2054 2062 # But don't do this until after checking 'forgot', so
2055 2063 # that subrepo files aren't normalized, and this op is
2056 2064 # purely from data cached by the status walk above.
2057 2065 if repo.dirstate.normalize(f) in repo.dirstate:
2058 2066 continue
2059 2067 ui.warn(_('not removing %s: '
2060 2068 'file is already untracked\n')
2061 2069 % match.rel(f))
2062 2070 bad.append(f)
2063 2071
2072 if confirm:
2073 responses = _('[Ynsa?]'
2074 '$$ &Yes, forget this file'
2075 '$$ &No, skip this file'
2076 '$$ &Skip remaining files'
2077 '$$ Include &all remaining files'
2078 '$$ &? (display help)')
2079 for filename in forget[:]:
2080 r = ui.promptchoice(_('forget %s %s') % (filename, responses))
2081 if r == 4: # ?
2082 while r == 4:
2083 for c, t in ui.extractchoices(responses)[1]:
2084 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2085 r = ui.promptchoice(_('forget %s %s') % (filename,
2086 responses))
2087 if r == 0: # yes
2088 continue
2089 elif r == 1: # no
2090 forget.remove(filename)
2091 elif r == 2: # Skip
2092 fnindex = forget.index(filename)
2093 del forget[fnindex:]
2094 break
2095 elif r == 3: # All
2096 break
2097
2064 2098 for f in forget:
2065 if ui.verbose or not match.exact(f):
2099 if ui.verbose or not match.exact(f) or confirm:
2066 2100 ui.status(_('removing %s\n') % match.rel(f))
2067 2101
2068 2102 if not dryrun:
2069 2103 rejected = wctx.forget(forget, prefix)
2070 2104 bad.extend(f for f in rejected if f in match.files())
2071 2105 forgot.extend(f for f in forget if f not in rejected)
2072 2106 return bad, forgot
2073 2107
2074 2108 def files(ui, ctx, m, fm, fmt, subrepos):
2075 2109 rev = ctx.rev()
2076 2110 ret = 1
2077 2111 ds = ctx.repo().dirstate
2078 2112
2079 2113 for f in ctx.matches(m):
2080 2114 if rev is None and ds[f] == 'r':
2081 2115 continue
2082 2116 fm.startitem()
2083 2117 if ui.verbose:
2084 2118 fc = ctx[f]
2085 2119 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2086 2120 fm.data(abspath=f)
2087 2121 fm.write('path', fmt, m.rel(f))
2088 2122 ret = 0
2089 2123
2090 2124 for subpath in sorted(ctx.substate):
2091 2125 submatch = matchmod.subdirmatcher(subpath, m)
2092 2126 if (subrepos or m.exact(subpath) or any(submatch.files())):
2093 2127 sub = ctx.sub(subpath)
2094 2128 try:
2095 2129 recurse = m.exact(subpath) or subrepos
2096 2130 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2097 2131 ret = 0
2098 2132 except error.LookupError:
2099 2133 ui.status(_("skipping missing subrepository: %s\n")
2100 2134 % m.abs(subpath))
2101 2135
2102 2136 return ret
2103 2137
2104 2138 def remove(ui, repo, m, prefix, after, force, subrepos, dryrun, warnings=None):
2105 2139 join = lambda f: os.path.join(prefix, f)
2106 2140 ret = 0
2107 2141 s = repo.status(match=m, clean=True)
2108 2142 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2109 2143
2110 2144 wctx = repo[None]
2111 2145
2112 2146 if warnings is None:
2113 2147 warnings = []
2114 2148 warn = True
2115 2149 else:
2116 2150 warn = False
2117 2151
2118 2152 subs = sorted(wctx.substate)
2119 2153 total = len(subs)
2120 2154 count = 0
2121 2155 for subpath in subs:
2122 2156 count += 1
2123 2157 submatch = matchmod.subdirmatcher(subpath, m)
2124 2158 if subrepos or m.exact(subpath) or any(submatch.files()):
2125 2159 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2126 2160 sub = wctx.sub(subpath)
2127 2161 try:
2128 2162 if sub.removefiles(submatch, prefix, after, force, subrepos,
2129 2163 dryrun, warnings):
2130 2164 ret = 1
2131 2165 except error.LookupError:
2132 2166 warnings.append(_("skipping missing subrepository: %s\n")
2133 2167 % join(subpath))
2134 2168 ui.progress(_('searching'), None)
2135 2169
2136 2170 # warn about failure to delete explicit files/dirs
2137 2171 deleteddirs = util.dirs(deleted)
2138 2172 files = m.files()
2139 2173 total = len(files)
2140 2174 count = 0
2141 2175 for f in files:
2142 2176 def insubrepo():
2143 2177 for subpath in wctx.substate:
2144 2178 if f.startswith(subpath + '/'):
2145 2179 return True
2146 2180 return False
2147 2181
2148 2182 count += 1
2149 2183 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2150 2184 isdir = f in deleteddirs or wctx.hasdir(f)
2151 2185 if (f in repo.dirstate or isdir or f == '.'
2152 2186 or insubrepo() or f in subs):
2153 2187 continue
2154 2188
2155 2189 if repo.wvfs.exists(f):
2156 2190 if repo.wvfs.isdir(f):
2157 2191 warnings.append(_('not removing %s: no tracked files\n')
2158 2192 % m.rel(f))
2159 2193 else:
2160 2194 warnings.append(_('not removing %s: file is untracked\n')
2161 2195 % m.rel(f))
2162 2196 # missing files will generate a warning elsewhere
2163 2197 ret = 1
2164 2198 ui.progress(_('deleting'), None)
2165 2199
2166 2200 if force:
2167 2201 list = modified + deleted + clean + added
2168 2202 elif after:
2169 2203 list = deleted
2170 2204 remaining = modified + added + clean
2171 2205 total = len(remaining)
2172 2206 count = 0
2173 2207 for f in remaining:
2174 2208 count += 1
2175 2209 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2176 2210 if ui.verbose or (f in files):
2177 2211 warnings.append(_('not removing %s: file still exists\n')
2178 2212 % m.rel(f))
2179 2213 ret = 1
2180 2214 ui.progress(_('skipping'), None)
2181 2215 else:
2182 2216 list = deleted + clean
2183 2217 total = len(modified) + len(added)
2184 2218 count = 0
2185 2219 for f in modified:
2186 2220 count += 1
2187 2221 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2188 2222 warnings.append(_('not removing %s: file is modified (use -f'
2189 2223 ' to force removal)\n') % m.rel(f))
2190 2224 ret = 1
2191 2225 for f in added:
2192 2226 count += 1
2193 2227 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2194 2228 warnings.append(_("not removing %s: file has been marked for add"
2195 2229 " (use 'hg forget' to undo add)\n") % m.rel(f))
2196 2230 ret = 1
2197 2231 ui.progress(_('skipping'), None)
2198 2232
2199 2233 list = sorted(list)
2200 2234 total = len(list)
2201 2235 count = 0
2202 2236 for f in list:
2203 2237 count += 1
2204 2238 if ui.verbose or not m.exact(f):
2205 2239 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2206 2240 ui.status(_('removing %s\n') % m.rel(f))
2207 2241 ui.progress(_('deleting'), None)
2208 2242
2209 2243 if not dryrun:
2210 2244 with repo.wlock():
2211 2245 if not after:
2212 2246 for f in list:
2213 2247 if f in added:
2214 2248 continue # we never unlink added files on remove
2215 2249 repo.wvfs.unlinkpath(f, ignoremissing=True)
2216 2250 repo[None].forget(list)
2217 2251
2218 2252 if warn:
2219 2253 for warning in warnings:
2220 2254 ui.warn(warning)
2221 2255
2222 2256 return ret
2223 2257
2224 2258 def _updatecatformatter(fm, ctx, matcher, path, decode):
2225 2259 """Hook for adding data to the formatter used by ``hg cat``.
2226 2260
2227 2261 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2228 2262 this method first."""
2229 2263 data = ctx[path].data()
2230 2264 if decode:
2231 2265 data = ctx.repo().wwritedata(path, data)
2232 2266 fm.startitem()
2233 2267 fm.write('data', '%s', data)
2234 2268 fm.data(abspath=path, path=matcher.rel(path))
2235 2269
2236 2270 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2237 2271 err = 1
2238 2272 opts = pycompat.byteskwargs(opts)
2239 2273
2240 2274 def write(path):
2241 2275 filename = None
2242 2276 if fntemplate:
2243 2277 filename = makefilename(ctx, fntemplate,
2244 2278 pathname=os.path.join(prefix, path))
2245 2279 # attempt to create the directory if it does not already exist
2246 2280 try:
2247 2281 os.makedirs(os.path.dirname(filename))
2248 2282 except OSError:
2249 2283 pass
2250 2284 with formatter.maybereopen(basefm, filename) as fm:
2251 2285 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2252 2286
2253 2287 # Automation often uses hg cat on single files, so special case it
2254 2288 # for performance to avoid the cost of parsing the manifest.
2255 2289 if len(matcher.files()) == 1 and not matcher.anypats():
2256 2290 file = matcher.files()[0]
2257 2291 mfl = repo.manifestlog
2258 2292 mfnode = ctx.manifestnode()
2259 2293 try:
2260 2294 if mfnode and mfl[mfnode].find(file)[0]:
2261 2295 scmutil.fileprefetchhooks(repo, ctx, [file])
2262 2296 write(file)
2263 2297 return 0
2264 2298 except KeyError:
2265 2299 pass
2266 2300
2267 2301 files = [f for f in ctx.walk(matcher)]
2268 2302 scmutil.fileprefetchhooks(repo, ctx, files)
2269 2303
2270 2304 for abs in files:
2271 2305 write(abs)
2272 2306 err = 0
2273 2307
2274 2308 for subpath in sorted(ctx.substate):
2275 2309 sub = ctx.sub(subpath)
2276 2310 try:
2277 2311 submatch = matchmod.subdirmatcher(subpath, matcher)
2278 2312
2279 2313 if not sub.cat(submatch, basefm, fntemplate,
2280 2314 os.path.join(prefix, sub._path),
2281 2315 **pycompat.strkwargs(opts)):
2282 2316 err = 0
2283 2317 except error.RepoLookupError:
2284 2318 ui.status(_("skipping missing subrepository: %s\n")
2285 2319 % os.path.join(prefix, subpath))
2286 2320
2287 2321 return err
2288 2322
2289 2323 def commit(ui, repo, commitfunc, pats, opts):
2290 2324 '''commit the specified files or all outstanding changes'''
2291 2325 date = opts.get('date')
2292 2326 if date:
2293 2327 opts['date'] = dateutil.parsedate(date)
2294 2328 message = logmessage(ui, opts)
2295 2329 matcher = scmutil.match(repo[None], pats, opts)
2296 2330
2297 2331 dsguard = None
2298 2332 # extract addremove carefully -- this function can be called from a command
2299 2333 # that doesn't support addremove
2300 2334 if opts.get('addremove'):
2301 2335 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2302 2336 with dsguard or util.nullcontextmanager():
2303 2337 if dsguard:
2304 2338 if scmutil.addremove(repo, matcher, "", opts) != 0:
2305 2339 raise error.Abort(
2306 2340 _("failed to mark all new/missing files as added/removed"))
2307 2341
2308 2342 return commitfunc(ui, repo, message, matcher, opts)
2309 2343
2310 2344 def samefile(f, ctx1, ctx2):
2311 2345 if f in ctx1.manifest():
2312 2346 a = ctx1.filectx(f)
2313 2347 if f in ctx2.manifest():
2314 2348 b = ctx2.filectx(f)
2315 2349 return (not a.cmp(b)
2316 2350 and a.flags() == b.flags())
2317 2351 else:
2318 2352 return False
2319 2353 else:
2320 2354 return f not in ctx2.manifest()
2321 2355
2322 2356 def amend(ui, repo, old, extra, pats, opts):
2323 2357 # avoid cycle context -> subrepo -> cmdutil
2324 2358 from . import context
2325 2359
2326 2360 # amend will reuse the existing user if not specified, but the obsolete
2327 2361 # marker creation requires that the current user's name is specified.
2328 2362 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2329 2363 ui.username() # raise exception if username not set
2330 2364
2331 2365 ui.note(_('amending changeset %s\n') % old)
2332 2366 base = old.p1()
2333 2367
2334 2368 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2335 2369 # Participating changesets:
2336 2370 #
2337 2371 # wctx o - workingctx that contains changes from working copy
2338 2372 # | to go into amending commit
2339 2373 # |
2340 2374 # old o - changeset to amend
2341 2375 # |
2342 2376 # base o - first parent of the changeset to amend
2343 2377 wctx = repo[None]
2344 2378
2345 2379 # Copy to avoid mutating input
2346 2380 extra = extra.copy()
2347 2381 # Update extra dict from amended commit (e.g. to preserve graft
2348 2382 # source)
2349 2383 extra.update(old.extra())
2350 2384
2351 2385 # Also update it from the from the wctx
2352 2386 extra.update(wctx.extra())
2353 2387
2354 2388 user = opts.get('user') or old.user()
2355 2389 date = opts.get('date') or old.date()
2356 2390
2357 2391 # Parse the date to allow comparison between date and old.date()
2358 2392 date = dateutil.parsedate(date)
2359 2393
2360 2394 if len(old.parents()) > 1:
2361 2395 # ctx.files() isn't reliable for merges, so fall back to the
2362 2396 # slower repo.status() method
2363 2397 files = set([fn for st in repo.status(base, old)[:3]
2364 2398 for fn in st])
2365 2399 else:
2366 2400 files = set(old.files())
2367 2401
2368 2402 # add/remove the files to the working copy if the "addremove" option
2369 2403 # was specified.
2370 2404 matcher = scmutil.match(wctx, pats, opts)
2371 2405 if (opts.get('addremove')
2372 2406 and scmutil.addremove(repo, matcher, "", opts)):
2373 2407 raise error.Abort(
2374 2408 _("failed to mark all new/missing files as added/removed"))
2375 2409
2376 2410 # Check subrepos. This depends on in-place wctx._status update in
2377 2411 # subrepo.precommit(). To minimize the risk of this hack, we do
2378 2412 # nothing if .hgsub does not exist.
2379 2413 if '.hgsub' in wctx or '.hgsub' in old:
2380 2414 subs, commitsubs, newsubstate = subrepoutil.precommit(
2381 2415 ui, wctx, wctx._status, matcher)
2382 2416 # amend should abort if commitsubrepos is enabled
2383 2417 assert not commitsubs
2384 2418 if subs:
2385 2419 subrepoutil.writestate(repo, newsubstate)
2386 2420
2387 2421 ms = mergemod.mergestate.read(repo)
2388 2422 mergeutil.checkunresolved(ms)
2389 2423
2390 2424 filestoamend = set(f for f in wctx.files() if matcher(f))
2391 2425
2392 2426 changes = (len(filestoamend) > 0)
2393 2427 if changes:
2394 2428 # Recompute copies (avoid recording a -> b -> a)
2395 2429 copied = copies.pathcopies(base, wctx, matcher)
2396 2430 if old.p2:
2397 2431 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2398 2432
2399 2433 # Prune files which were reverted by the updates: if old
2400 2434 # introduced file X and the file was renamed in the working
2401 2435 # copy, then those two files are the same and
2402 2436 # we can discard X from our list of files. Likewise if X
2403 2437 # was removed, it's no longer relevant. If X is missing (aka
2404 2438 # deleted), old X must be preserved.
2405 2439 files.update(filestoamend)
2406 2440 files = [f for f in files if (not samefile(f, wctx, base)
2407 2441 or f in wctx.deleted())]
2408 2442
2409 2443 def filectxfn(repo, ctx_, path):
2410 2444 try:
2411 2445 # If the file being considered is not amongst the files
2412 2446 # to be amended, we should return the file context from the
2413 2447 # old changeset. This avoids issues when only some files in
2414 2448 # the working copy are being amended but there are also
2415 2449 # changes to other files from the old changeset.
2416 2450 if path not in filestoamend:
2417 2451 return old.filectx(path)
2418 2452
2419 2453 # Return None for removed files.
2420 2454 if path in wctx.removed():
2421 2455 return None
2422 2456
2423 2457 fctx = wctx[path]
2424 2458 flags = fctx.flags()
2425 2459 mctx = context.memfilectx(repo, ctx_,
2426 2460 fctx.path(), fctx.data(),
2427 2461 islink='l' in flags,
2428 2462 isexec='x' in flags,
2429 2463 copied=copied.get(path))
2430 2464 return mctx
2431 2465 except KeyError:
2432 2466 return None
2433 2467 else:
2434 2468 ui.note(_('copying changeset %s to %s\n') % (old, base))
2435 2469
2436 2470 # Use version of files as in the old cset
2437 2471 def filectxfn(repo, ctx_, path):
2438 2472 try:
2439 2473 return old.filectx(path)
2440 2474 except KeyError:
2441 2475 return None
2442 2476
2443 2477 # See if we got a message from -m or -l, if not, open the editor with
2444 2478 # the message of the changeset to amend.
2445 2479 message = logmessage(ui, opts)
2446 2480
2447 2481 editform = mergeeditform(old, 'commit.amend')
2448 2482 editor = getcommiteditor(editform=editform,
2449 2483 **pycompat.strkwargs(opts))
2450 2484
2451 2485 if not message:
2452 2486 editor = getcommiteditor(edit=True, editform=editform)
2453 2487 message = old.description()
2454 2488
2455 2489 pureextra = extra.copy()
2456 2490 extra['amend_source'] = old.hex()
2457 2491
2458 2492 new = context.memctx(repo,
2459 2493 parents=[base.node(), old.p2().node()],
2460 2494 text=message,
2461 2495 files=files,
2462 2496 filectxfn=filectxfn,
2463 2497 user=user,
2464 2498 date=date,
2465 2499 extra=extra,
2466 2500 editor=editor)
2467 2501
2468 2502 newdesc = changelog.stripdesc(new.description())
2469 2503 if ((not changes)
2470 2504 and newdesc == old.description()
2471 2505 and user == old.user()
2472 2506 and date == old.date()
2473 2507 and pureextra == old.extra()):
2474 2508 # nothing changed. continuing here would create a new node
2475 2509 # anyway because of the amend_source noise.
2476 2510 #
2477 2511 # This not what we expect from amend.
2478 2512 return old.node()
2479 2513
2480 2514 if opts.get('secret'):
2481 2515 commitphase = 'secret'
2482 2516 else:
2483 2517 commitphase = old.phase()
2484 2518 overrides = {('phases', 'new-commit'): commitphase}
2485 2519 with ui.configoverride(overrides, 'amend'):
2486 2520 newid = repo.commitctx(new)
2487 2521
2488 2522 # Reroute the working copy parent to the new changeset
2489 2523 repo.setparents(newid, nullid)
2490 2524 mapping = {old.node(): (newid,)}
2491 2525 obsmetadata = None
2492 2526 if opts.get('note'):
2493 2527 obsmetadata = {'note': opts['note']}
2494 2528 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
2495 2529
2496 2530 # Fixing the dirstate because localrepo.commitctx does not update
2497 2531 # it. This is rather convenient because we did not need to update
2498 2532 # the dirstate for all the files in the new commit which commitctx
2499 2533 # could have done if it updated the dirstate. Now, we can
2500 2534 # selectively update the dirstate only for the amended files.
2501 2535 dirstate = repo.dirstate
2502 2536
2503 2537 # Update the state of the files which were added and
2504 2538 # and modified in the amend to "normal" in the dirstate.
2505 2539 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2506 2540 for f in normalfiles:
2507 2541 dirstate.normal(f)
2508 2542
2509 2543 # Update the state of files which were removed in the amend
2510 2544 # to "removed" in the dirstate.
2511 2545 removedfiles = set(wctx.removed()) & filestoamend
2512 2546 for f in removedfiles:
2513 2547 dirstate.drop(f)
2514 2548
2515 2549 return newid
2516 2550
2517 2551 def commiteditor(repo, ctx, subs, editform=''):
2518 2552 if ctx.description():
2519 2553 return ctx.description()
2520 2554 return commitforceeditor(repo, ctx, subs, editform=editform,
2521 2555 unchangedmessagedetection=True)
2522 2556
2523 2557 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2524 2558 editform='', unchangedmessagedetection=False):
2525 2559 if not extramsg:
2526 2560 extramsg = _("Leave message empty to abort commit.")
2527 2561
2528 2562 forms = [e for e in editform.split('.') if e]
2529 2563 forms.insert(0, 'changeset')
2530 2564 templatetext = None
2531 2565 while forms:
2532 2566 ref = '.'.join(forms)
2533 2567 if repo.ui.config('committemplate', ref):
2534 2568 templatetext = committext = buildcommittemplate(
2535 2569 repo, ctx, subs, extramsg, ref)
2536 2570 break
2537 2571 forms.pop()
2538 2572 else:
2539 2573 committext = buildcommittext(repo, ctx, subs, extramsg)
2540 2574
2541 2575 # run editor in the repository root
2542 2576 olddir = pycompat.getcwd()
2543 2577 os.chdir(repo.root)
2544 2578
2545 2579 # make in-memory changes visible to external process
2546 2580 tr = repo.currenttransaction()
2547 2581 repo.dirstate.write(tr)
2548 2582 pending = tr and tr.writepending() and repo.root
2549 2583
2550 2584 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2551 2585 editform=editform, pending=pending,
2552 2586 repopath=repo.path, action='commit')
2553 2587 text = editortext
2554 2588
2555 2589 # strip away anything below this special string (used for editors that want
2556 2590 # to display the diff)
2557 2591 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2558 2592 if stripbelow:
2559 2593 text = text[:stripbelow.start()]
2560 2594
2561 2595 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2562 2596 os.chdir(olddir)
2563 2597
2564 2598 if finishdesc:
2565 2599 text = finishdesc(text)
2566 2600 if not text.strip():
2567 2601 raise error.Abort(_("empty commit message"))
2568 2602 if unchangedmessagedetection and editortext == templatetext:
2569 2603 raise error.Abort(_("commit message unchanged"))
2570 2604
2571 2605 return text
2572 2606
2573 2607 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2574 2608 ui = repo.ui
2575 2609 spec = formatter.templatespec(ref, None, None)
2576 2610 t = logcmdutil.changesettemplater(ui, repo, spec)
2577 2611 t.t.cache.update((k, templater.unquotestring(v))
2578 2612 for k, v in repo.ui.configitems('committemplate'))
2579 2613
2580 2614 if not extramsg:
2581 2615 extramsg = '' # ensure that extramsg is string
2582 2616
2583 2617 ui.pushbuffer()
2584 2618 t.show(ctx, extramsg=extramsg)
2585 2619 return ui.popbuffer()
2586 2620
2587 2621 def hgprefix(msg):
2588 2622 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2589 2623
2590 2624 def buildcommittext(repo, ctx, subs, extramsg):
2591 2625 edittext = []
2592 2626 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2593 2627 if ctx.description():
2594 2628 edittext.append(ctx.description())
2595 2629 edittext.append("")
2596 2630 edittext.append("") # Empty line between message and comments.
2597 2631 edittext.append(hgprefix(_("Enter commit message."
2598 2632 " Lines beginning with 'HG:' are removed.")))
2599 2633 edittext.append(hgprefix(extramsg))
2600 2634 edittext.append("HG: --")
2601 2635 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2602 2636 if ctx.p2():
2603 2637 edittext.append(hgprefix(_("branch merge")))
2604 2638 if ctx.branch():
2605 2639 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2606 2640 if bookmarks.isactivewdirparent(repo):
2607 2641 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2608 2642 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2609 2643 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2610 2644 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2611 2645 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2612 2646 if not added and not modified and not removed:
2613 2647 edittext.append(hgprefix(_("no files changed")))
2614 2648 edittext.append("")
2615 2649
2616 2650 return "\n".join(edittext)
2617 2651
2618 2652 def commitstatus(repo, node, branch, bheads=None, opts=None):
2619 2653 if opts is None:
2620 2654 opts = {}
2621 2655 ctx = repo[node]
2622 2656 parents = ctx.parents()
2623 2657
2624 2658 if (not opts.get('amend') and bheads and node not in bheads and not
2625 2659 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2626 2660 repo.ui.status(_('created new head\n'))
2627 2661 # The message is not printed for initial roots. For the other
2628 2662 # changesets, it is printed in the following situations:
2629 2663 #
2630 2664 # Par column: for the 2 parents with ...
2631 2665 # N: null or no parent
2632 2666 # B: parent is on another named branch
2633 2667 # C: parent is a regular non head changeset
2634 2668 # H: parent was a branch head of the current branch
2635 2669 # Msg column: whether we print "created new head" message
2636 2670 # In the following, it is assumed that there already exists some
2637 2671 # initial branch heads of the current branch, otherwise nothing is
2638 2672 # printed anyway.
2639 2673 #
2640 2674 # Par Msg Comment
2641 2675 # N N y additional topo root
2642 2676 #
2643 2677 # B N y additional branch root
2644 2678 # C N y additional topo head
2645 2679 # H N n usual case
2646 2680 #
2647 2681 # B B y weird additional branch root
2648 2682 # C B y branch merge
2649 2683 # H B n merge with named branch
2650 2684 #
2651 2685 # C C y additional head from merge
2652 2686 # C H n merge with a head
2653 2687 #
2654 2688 # H H n head merge: head count decreases
2655 2689
2656 2690 if not opts.get('close_branch'):
2657 2691 for r in parents:
2658 2692 if r.closesbranch() and r.branch() == branch:
2659 2693 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2660 2694
2661 2695 if repo.ui.debugflag:
2662 2696 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2663 2697 elif repo.ui.verbose:
2664 2698 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2665 2699
2666 2700 def postcommitstatus(repo, pats, opts):
2667 2701 return repo.status(match=scmutil.match(repo[None], pats, opts))
2668 2702
2669 2703 def revert(ui, repo, ctx, parents, *pats, **opts):
2670 2704 opts = pycompat.byteskwargs(opts)
2671 2705 parent, p2 = parents
2672 2706 node = ctx.node()
2673 2707
2674 2708 mf = ctx.manifest()
2675 2709 if node == p2:
2676 2710 parent = p2
2677 2711
2678 2712 # need all matching names in dirstate and manifest of target rev,
2679 2713 # so have to walk both. do not print errors if files exist in one
2680 2714 # but not other. in both cases, filesets should be evaluated against
2681 2715 # workingctx to get consistent result (issue4497). this means 'set:**'
2682 2716 # cannot be used to select missing files from target rev.
2683 2717
2684 2718 # `names` is a mapping for all elements in working copy and target revision
2685 2719 # The mapping is in the form:
2686 2720 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2687 2721 names = {}
2688 2722
2689 2723 with repo.wlock():
2690 2724 ## filling of the `names` mapping
2691 2725 # walk dirstate to fill `names`
2692 2726
2693 2727 interactive = opts.get('interactive', False)
2694 2728 wctx = repo[None]
2695 2729 m = scmutil.match(wctx, pats, opts)
2696 2730
2697 2731 # we'll need this later
2698 2732 targetsubs = sorted(s for s in wctx.substate if m(s))
2699 2733
2700 2734 if not m.always():
2701 2735 matcher = matchmod.badmatch(m, lambda x, y: False)
2702 2736 for abs in wctx.walk(matcher):
2703 2737 names[abs] = m.rel(abs), m.exact(abs)
2704 2738
2705 2739 # walk target manifest to fill `names`
2706 2740
2707 2741 def badfn(path, msg):
2708 2742 if path in names:
2709 2743 return
2710 2744 if path in ctx.substate:
2711 2745 return
2712 2746 path_ = path + '/'
2713 2747 for f in names:
2714 2748 if f.startswith(path_):
2715 2749 return
2716 2750 ui.warn("%s: %s\n" % (m.rel(path), msg))
2717 2751
2718 2752 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2719 2753 if abs not in names:
2720 2754 names[abs] = m.rel(abs), m.exact(abs)
2721 2755
2722 2756 # Find status of all file in `names`.
2723 2757 m = scmutil.matchfiles(repo, names)
2724 2758
2725 2759 changes = repo.status(node1=node, match=m,
2726 2760 unknown=True, ignored=True, clean=True)
2727 2761 else:
2728 2762 changes = repo.status(node1=node, match=m)
2729 2763 for kind in changes:
2730 2764 for abs in kind:
2731 2765 names[abs] = m.rel(abs), m.exact(abs)
2732 2766
2733 2767 m = scmutil.matchfiles(repo, names)
2734 2768
2735 2769 modified = set(changes.modified)
2736 2770 added = set(changes.added)
2737 2771 removed = set(changes.removed)
2738 2772 _deleted = set(changes.deleted)
2739 2773 unknown = set(changes.unknown)
2740 2774 unknown.update(changes.ignored)
2741 2775 clean = set(changes.clean)
2742 2776 modadded = set()
2743 2777
2744 2778 # We need to account for the state of the file in the dirstate,
2745 2779 # even when we revert against something else than parent. This will
2746 2780 # slightly alter the behavior of revert (doing back up or not, delete
2747 2781 # or just forget etc).
2748 2782 if parent == node:
2749 2783 dsmodified = modified
2750 2784 dsadded = added
2751 2785 dsremoved = removed
2752 2786 # store all local modifications, useful later for rename detection
2753 2787 localchanges = dsmodified | dsadded
2754 2788 modified, added, removed = set(), set(), set()
2755 2789 else:
2756 2790 changes = repo.status(node1=parent, match=m)
2757 2791 dsmodified = set(changes.modified)
2758 2792 dsadded = set(changes.added)
2759 2793 dsremoved = set(changes.removed)
2760 2794 # store all local modifications, useful later for rename detection
2761 2795 localchanges = dsmodified | dsadded
2762 2796
2763 2797 # only take into account for removes between wc and target
2764 2798 clean |= dsremoved - removed
2765 2799 dsremoved &= removed
2766 2800 # distinct between dirstate remove and other
2767 2801 removed -= dsremoved
2768 2802
2769 2803 modadded = added & dsmodified
2770 2804 added -= modadded
2771 2805
2772 2806 # tell newly modified apart.
2773 2807 dsmodified &= modified
2774 2808 dsmodified |= modified & dsadded # dirstate added may need backup
2775 2809 modified -= dsmodified
2776 2810
2777 2811 # We need to wait for some post-processing to update this set
2778 2812 # before making the distinction. The dirstate will be used for
2779 2813 # that purpose.
2780 2814 dsadded = added
2781 2815
2782 2816 # in case of merge, files that are actually added can be reported as
2783 2817 # modified, we need to post process the result
2784 2818 if p2 != nullid:
2785 2819 mergeadd = set(dsmodified)
2786 2820 for path in dsmodified:
2787 2821 if path in mf:
2788 2822 mergeadd.remove(path)
2789 2823 dsadded |= mergeadd
2790 2824 dsmodified -= mergeadd
2791 2825
2792 2826 # if f is a rename, update `names` to also revert the source
2793 2827 cwd = repo.getcwd()
2794 2828 for f in localchanges:
2795 2829 src = repo.dirstate.copied(f)
2796 2830 # XXX should we check for rename down to target node?
2797 2831 if src and src not in names and repo.dirstate[src] == 'r':
2798 2832 dsremoved.add(src)
2799 2833 names[src] = (repo.pathto(src, cwd), True)
2800 2834
2801 2835 # determine the exact nature of the deleted changesets
2802 2836 deladded = set(_deleted)
2803 2837 for path in _deleted:
2804 2838 if path in mf:
2805 2839 deladded.remove(path)
2806 2840 deleted = _deleted - deladded
2807 2841
2808 2842 # distinguish between file to forget and the other
2809 2843 added = set()
2810 2844 for abs in dsadded:
2811 2845 if repo.dirstate[abs] != 'a':
2812 2846 added.add(abs)
2813 2847 dsadded -= added
2814 2848
2815 2849 for abs in deladded:
2816 2850 if repo.dirstate[abs] == 'a':
2817 2851 dsadded.add(abs)
2818 2852 deladded -= dsadded
2819 2853
2820 2854 # For files marked as removed, we check if an unknown file is present at
2821 2855 # the same path. If a such file exists it may need to be backed up.
2822 2856 # Making the distinction at this stage helps have simpler backup
2823 2857 # logic.
2824 2858 removunk = set()
2825 2859 for abs in removed:
2826 2860 target = repo.wjoin(abs)
2827 2861 if os.path.lexists(target):
2828 2862 removunk.add(abs)
2829 2863 removed -= removunk
2830 2864
2831 2865 dsremovunk = set()
2832 2866 for abs in dsremoved:
2833 2867 target = repo.wjoin(abs)
2834 2868 if os.path.lexists(target):
2835 2869 dsremovunk.add(abs)
2836 2870 dsremoved -= dsremovunk
2837 2871
2838 2872 # action to be actually performed by revert
2839 2873 # (<list of file>, message>) tuple
2840 2874 actions = {'revert': ([], _('reverting %s\n')),
2841 2875 'add': ([], _('adding %s\n')),
2842 2876 'remove': ([], _('removing %s\n')),
2843 2877 'drop': ([], _('removing %s\n')),
2844 2878 'forget': ([], _('forgetting %s\n')),
2845 2879 'undelete': ([], _('undeleting %s\n')),
2846 2880 'noop': (None, _('no changes needed to %s\n')),
2847 2881 'unknown': (None, _('file not managed: %s\n')),
2848 2882 }
2849 2883
2850 2884 # "constant" that convey the backup strategy.
2851 2885 # All set to `discard` if `no-backup` is set do avoid checking
2852 2886 # no_backup lower in the code.
2853 2887 # These values are ordered for comparison purposes
2854 2888 backupinteractive = 3 # do backup if interactively modified
2855 2889 backup = 2 # unconditionally do backup
2856 2890 check = 1 # check if the existing file differs from target
2857 2891 discard = 0 # never do backup
2858 2892 if opts.get('no_backup'):
2859 2893 backupinteractive = backup = check = discard
2860 2894 if interactive:
2861 2895 dsmodifiedbackup = backupinteractive
2862 2896 else:
2863 2897 dsmodifiedbackup = backup
2864 2898 tobackup = set()
2865 2899
2866 2900 backupanddel = actions['remove']
2867 2901 if not opts.get('no_backup'):
2868 2902 backupanddel = actions['drop']
2869 2903
2870 2904 disptable = (
2871 2905 # dispatch table:
2872 2906 # file state
2873 2907 # action
2874 2908 # make backup
2875 2909
2876 2910 ## Sets that results that will change file on disk
2877 2911 # Modified compared to target, no local change
2878 2912 (modified, actions['revert'], discard),
2879 2913 # Modified compared to target, but local file is deleted
2880 2914 (deleted, actions['revert'], discard),
2881 2915 # Modified compared to target, local change
2882 2916 (dsmodified, actions['revert'], dsmodifiedbackup),
2883 2917 # Added since target
2884 2918 (added, actions['remove'], discard),
2885 2919 # Added in working directory
2886 2920 (dsadded, actions['forget'], discard),
2887 2921 # Added since target, have local modification
2888 2922 (modadded, backupanddel, backup),
2889 2923 # Added since target but file is missing in working directory
2890 2924 (deladded, actions['drop'], discard),
2891 2925 # Removed since target, before working copy parent
2892 2926 (removed, actions['add'], discard),
2893 2927 # Same as `removed` but an unknown file exists at the same path
2894 2928 (removunk, actions['add'], check),
2895 2929 # Removed since targe, marked as such in working copy parent
2896 2930 (dsremoved, actions['undelete'], discard),
2897 2931 # Same as `dsremoved` but an unknown file exists at the same path
2898 2932 (dsremovunk, actions['undelete'], check),
2899 2933 ## the following sets does not result in any file changes
2900 2934 # File with no modification
2901 2935 (clean, actions['noop'], discard),
2902 2936 # Existing file, not tracked anywhere
2903 2937 (unknown, actions['unknown'], discard),
2904 2938 )
2905 2939
2906 2940 for abs, (rel, exact) in sorted(names.items()):
2907 2941 # target file to be touch on disk (relative to cwd)
2908 2942 target = repo.wjoin(abs)
2909 2943 # search the entry in the dispatch table.
2910 2944 # if the file is in any of these sets, it was touched in the working
2911 2945 # directory parent and we are sure it needs to be reverted.
2912 2946 for table, (xlist, msg), dobackup in disptable:
2913 2947 if abs not in table:
2914 2948 continue
2915 2949 if xlist is not None:
2916 2950 xlist.append(abs)
2917 2951 if dobackup:
2918 2952 # If in interactive mode, don't automatically create
2919 2953 # .orig files (issue4793)
2920 2954 if dobackup == backupinteractive:
2921 2955 tobackup.add(abs)
2922 2956 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
2923 2957 bakname = scmutil.origpath(ui, repo, rel)
2924 2958 ui.note(_('saving current version of %s as %s\n') %
2925 2959 (rel, bakname))
2926 2960 if not opts.get('dry_run'):
2927 2961 if interactive:
2928 2962 util.copyfile(target, bakname)
2929 2963 else:
2930 2964 util.rename(target, bakname)
2931 2965 if ui.verbose or not exact:
2932 2966 if not isinstance(msg, bytes):
2933 2967 msg = msg(abs)
2934 2968 ui.status(msg % rel)
2935 2969 elif exact:
2936 2970 ui.warn(msg % rel)
2937 2971 break
2938 2972
2939 2973 if not opts.get('dry_run'):
2940 2974 needdata = ('revert', 'add', 'undelete')
2941 2975 if _revertprefetch is not _revertprefetchstub:
2942 2976 ui.deprecwarn("'cmdutil._revertprefetch' is deprecated, "
2943 2977 "add a callback to 'scmutil.fileprefetchhooks'",
2944 2978 '4.6', stacklevel=1)
2945 2979 _revertprefetch(repo, ctx,
2946 2980 *[actions[name][0] for name in needdata])
2947 2981 oplist = [actions[name][0] for name in needdata]
2948 2982 prefetch = scmutil.fileprefetchhooks
2949 2983 prefetch(repo, ctx, [f for sublist in oplist for f in sublist])
2950 2984 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
2951 2985
2952 2986 if targetsubs:
2953 2987 # Revert the subrepos on the revert list
2954 2988 for sub in targetsubs:
2955 2989 try:
2956 2990 wctx.sub(sub).revert(ctx.substate[sub], *pats,
2957 2991 **pycompat.strkwargs(opts))
2958 2992 except KeyError:
2959 2993 raise error.Abort("subrepository '%s' does not exist in %s!"
2960 2994 % (sub, short(ctx.node())))
2961 2995
2962 2996 def _revertprefetchstub(repo, ctx, *files):
2963 2997 """Stub method for detecting extension wrapping of _revertprefetch(), to
2964 2998 issue a deprecation warning."""
2965 2999
2966 3000 _revertprefetch = _revertprefetchstub
2967 3001
2968 3002 def _performrevert(repo, parents, ctx, actions, interactive=False,
2969 3003 tobackup=None):
2970 3004 """function that actually perform all the actions computed for revert
2971 3005
2972 3006 This is an independent function to let extension to plug in and react to
2973 3007 the imminent revert.
2974 3008
2975 3009 Make sure you have the working directory locked when calling this function.
2976 3010 """
2977 3011 parent, p2 = parents
2978 3012 node = ctx.node()
2979 3013 excluded_files = []
2980 3014
2981 3015 def checkout(f):
2982 3016 fc = ctx[f]
2983 3017 repo.wwrite(f, fc.data(), fc.flags())
2984 3018
2985 3019 def doremove(f):
2986 3020 try:
2987 3021 repo.wvfs.unlinkpath(f)
2988 3022 except OSError:
2989 3023 pass
2990 3024 repo.dirstate.remove(f)
2991 3025
2992 3026 audit_path = pathutil.pathauditor(repo.root, cached=True)
2993 3027 for f in actions['forget'][0]:
2994 3028 if interactive:
2995 3029 choice = repo.ui.promptchoice(
2996 3030 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
2997 3031 if choice == 0:
2998 3032 repo.dirstate.drop(f)
2999 3033 else:
3000 3034 excluded_files.append(f)
3001 3035 else:
3002 3036 repo.dirstate.drop(f)
3003 3037 for f in actions['remove'][0]:
3004 3038 audit_path(f)
3005 3039 if interactive:
3006 3040 choice = repo.ui.promptchoice(
3007 3041 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3008 3042 if choice == 0:
3009 3043 doremove(f)
3010 3044 else:
3011 3045 excluded_files.append(f)
3012 3046 else:
3013 3047 doremove(f)
3014 3048 for f in actions['drop'][0]:
3015 3049 audit_path(f)
3016 3050 repo.dirstate.remove(f)
3017 3051
3018 3052 normal = None
3019 3053 if node == parent:
3020 3054 # We're reverting to our parent. If possible, we'd like status
3021 3055 # to report the file as clean. We have to use normallookup for
3022 3056 # merges to avoid losing information about merged/dirty files.
3023 3057 if p2 != nullid:
3024 3058 normal = repo.dirstate.normallookup
3025 3059 else:
3026 3060 normal = repo.dirstate.normal
3027 3061
3028 3062 newlyaddedandmodifiedfiles = set()
3029 3063 if interactive:
3030 3064 # Prompt the user for changes to revert
3031 3065 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3032 3066 m = scmutil.matchfiles(repo, torevert)
3033 3067 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3034 3068 diffopts.nodates = True
3035 3069 diffopts.git = True
3036 3070 operation = 'discard'
3037 3071 reversehunks = True
3038 3072 if node != parent:
3039 3073 operation = 'apply'
3040 3074 reversehunks = False
3041 3075 if reversehunks:
3042 3076 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3043 3077 else:
3044 3078 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3045 3079 originalchunks = patch.parsepatch(diff)
3046 3080
3047 3081 try:
3048 3082
3049 3083 chunks, opts = recordfilter(repo.ui, originalchunks,
3050 3084 operation=operation)
3051 3085 if reversehunks:
3052 3086 chunks = patch.reversehunks(chunks)
3053 3087
3054 3088 except error.PatchError as err:
3055 3089 raise error.Abort(_('error parsing patch: %s') % err)
3056 3090
3057 3091 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3058 3092 if tobackup is None:
3059 3093 tobackup = set()
3060 3094 # Apply changes
3061 3095 fp = stringio()
3062 3096 for c in chunks:
3063 3097 # Create a backup file only if this hunk should be backed up
3064 3098 if ishunk(c) and c.header.filename() in tobackup:
3065 3099 abs = c.header.filename()
3066 3100 target = repo.wjoin(abs)
3067 3101 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3068 3102 util.copyfile(target, bakname)
3069 3103 tobackup.remove(abs)
3070 3104 c.write(fp)
3071 3105 dopatch = fp.tell()
3072 3106 fp.seek(0)
3073 3107 if dopatch:
3074 3108 try:
3075 3109 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3076 3110 except error.PatchError as err:
3077 3111 raise error.Abort(pycompat.bytestr(err))
3078 3112 del fp
3079 3113 else:
3080 3114 for f in actions['revert'][0]:
3081 3115 checkout(f)
3082 3116 if normal:
3083 3117 normal(f)
3084 3118
3085 3119 for f in actions['add'][0]:
3086 3120 # Don't checkout modified files, they are already created by the diff
3087 3121 if f not in newlyaddedandmodifiedfiles:
3088 3122 checkout(f)
3089 3123 repo.dirstate.add(f)
3090 3124
3091 3125 normal = repo.dirstate.normallookup
3092 3126 if node == parent and p2 == nullid:
3093 3127 normal = repo.dirstate.normal
3094 3128 for f in actions['undelete'][0]:
3095 3129 checkout(f)
3096 3130 normal(f)
3097 3131
3098 3132 copied = copies.pathcopies(repo[parent], ctx)
3099 3133
3100 3134 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3101 3135 if f in copied:
3102 3136 repo.dirstate.copy(copied[f], f)
3103 3137
3104 3138 class command(registrar.command):
3105 3139 """deprecated: used registrar.command instead"""
3106 3140 def _doregister(self, func, name, *args, **kwargs):
3107 3141 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3108 3142 return super(command, self)._doregister(func, name, *args, **kwargs)
3109 3143
3110 3144 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3111 3145 # commands.outgoing. "missing" is "missing" of the result of
3112 3146 # "findcommonoutgoing()"
3113 3147 outgoinghooks = util.hooks()
3114 3148
3115 3149 # a list of (ui, repo) functions called by commands.summary
3116 3150 summaryhooks = util.hooks()
3117 3151
3118 3152 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3119 3153 #
3120 3154 # functions should return tuple of booleans below, if 'changes' is None:
3121 3155 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3122 3156 #
3123 3157 # otherwise, 'changes' is a tuple of tuples below:
3124 3158 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3125 3159 # - (desturl, destbranch, destpeer, outgoing)
3126 3160 summaryremotehooks = util.hooks()
3127 3161
3128 3162 # A list of state files kept by multistep operations like graft.
3129 3163 # Since graft cannot be aborted, it is considered 'clearable' by update.
3130 3164 # note: bisect is intentionally excluded
3131 3165 # (state file, clearable, allowcommit, error, hint)
3132 3166 unfinishedstates = [
3133 3167 ('graftstate', True, False, _('graft in progress'),
3134 3168 _("use 'hg graft --continue' or 'hg update' to abort")),
3135 3169 ('updatestate', True, False, _('last update was interrupted'),
3136 3170 _("use 'hg update' to get a consistent checkout"))
3137 3171 ]
3138 3172
3139 3173 def checkunfinished(repo, commit=False):
3140 3174 '''Look for an unfinished multistep operation, like graft, and abort
3141 3175 if found. It's probably good to check this right before
3142 3176 bailifchanged().
3143 3177 '''
3144 3178 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3145 3179 if commit and allowcommit:
3146 3180 continue
3147 3181 if repo.vfs.exists(f):
3148 3182 raise error.Abort(msg, hint=hint)
3149 3183
3150 3184 def clearunfinished(repo):
3151 3185 '''Check for unfinished operations (as above), and clear the ones
3152 3186 that are clearable.
3153 3187 '''
3154 3188 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3155 3189 if not clearable and repo.vfs.exists(f):
3156 3190 raise error.Abort(msg, hint=hint)
3157 3191 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3158 3192 if clearable and repo.vfs.exists(f):
3159 3193 util.unlink(repo.vfs.join(f))
3160 3194
3161 3195 afterresolvedstates = [
3162 3196 ('graftstate',
3163 3197 _('hg graft --continue')),
3164 3198 ]
3165 3199
3166 3200 def howtocontinue(repo):
3167 3201 '''Check for an unfinished operation and return the command to finish
3168 3202 it.
3169 3203
3170 3204 afterresolvedstates tuples define a .hg/{file} and the corresponding
3171 3205 command needed to finish it.
3172 3206
3173 3207 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3174 3208 a boolean.
3175 3209 '''
3176 3210 contmsg = _("continue: %s")
3177 3211 for f, msg in afterresolvedstates:
3178 3212 if repo.vfs.exists(f):
3179 3213 return contmsg % msg, True
3180 3214 if repo[None].dirty(missing=True, merge=False, branch=False):
3181 3215 return contmsg % _("hg commit"), False
3182 3216 return None, None
3183 3217
3184 3218 def checkafterresolved(repo):
3185 3219 '''Inform the user about the next action after completing hg resolve
3186 3220
3187 3221 If there's a matching afterresolvedstates, howtocontinue will yield
3188 3222 repo.ui.warn as the reporter.
3189 3223
3190 3224 Otherwise, it will yield repo.ui.note.
3191 3225 '''
3192 3226 msg, warning = howtocontinue(repo)
3193 3227 if msg is not None:
3194 3228 if warning:
3195 3229 repo.ui.warn("%s\n" % msg)
3196 3230 else:
3197 3231 repo.ui.note("%s\n" % msg)
3198 3232
3199 3233 def wrongtooltocontinue(repo, task):
3200 3234 '''Raise an abort suggesting how to properly continue if there is an
3201 3235 active task.
3202 3236
3203 3237 Uses howtocontinue() to find the active task.
3204 3238
3205 3239 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3206 3240 a hint.
3207 3241 '''
3208 3242 after = howtocontinue(repo)
3209 3243 hint = None
3210 3244 if after[1]:
3211 3245 hint = after[0]
3212 3246 raise error.Abort(_('no %s in progress') % task, hint=hint)
3213 3247
3214 3248 class changeset_printer(logcmdutil.changesetprinter):
3215 3249
3216 3250 def __init__(self, ui, *args, **kwargs):
3217 3251 msg = ("'cmdutil.changeset_printer' is deprecated, "
3218 3252 "use 'logcmdutil.logcmdutil'")
3219 3253 ui.deprecwarn(msg, "4.6")
3220 3254 super(changeset_printer, self).__init__(ui, *args, **kwargs)
3221 3255
3222 3256 def displaygraph(ui, *args, **kwargs):
3223 3257 msg = ("'cmdutil.displaygraph' is deprecated, "
3224 3258 "use 'logcmdutil.displaygraph'")
3225 3259 ui.deprecwarn(msg, "4.6")
3226 3260 return logcmdutil.displaygraph(ui, *args, **kwargs)
3227 3261
3228 3262 def show_changeset(ui, *args, **kwargs):
3229 3263 msg = ("'cmdutil.show_changeset' is deprecated, "
3230 3264 "use 'logcmdutil.changesetdisplayer'")
3231 3265 ui.deprecwarn(msg, "4.6")
3232 3266 return logcmdutil.changesetdisplayer(ui, *args, **kwargs)
@@ -1,5667 +1,5669
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 __future__ import absolute_import
9 9
10 10 import difflib
11 11 import errno
12 12 import os
13 13 import re
14 14 import sys
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 hex,
19 19 nullid,
20 20 nullrev,
21 21 short,
22 22 )
23 23 from . import (
24 24 archival,
25 25 bookmarks,
26 26 bundle2,
27 27 changegroup,
28 28 cmdutil,
29 29 copies,
30 30 debugcommands as debugcommandsmod,
31 31 destutil,
32 32 dirstateguard,
33 33 discovery,
34 34 encoding,
35 35 error,
36 36 exchange,
37 37 extensions,
38 38 formatter,
39 39 graphmod,
40 40 hbisect,
41 41 help,
42 42 hg,
43 43 lock as lockmod,
44 44 logcmdutil,
45 45 merge as mergemod,
46 46 obsolete,
47 47 obsutil,
48 48 patch,
49 49 phases,
50 50 pycompat,
51 51 rcutil,
52 52 registrar,
53 53 revsetlang,
54 54 rewriteutil,
55 55 scmutil,
56 56 server,
57 57 streamclone,
58 58 tags as tagsmod,
59 59 templatekw,
60 60 ui as uimod,
61 61 util,
62 62 wireprotoserver,
63 63 )
64 64 from .utils import (
65 65 dateutil,
66 66 procutil,
67 67 stringutil,
68 68 )
69 69
70 70 release = lockmod.release
71 71
72 72 table = {}
73 73 table.update(debugcommandsmod.command._table)
74 74
75 75 command = registrar.command(table)
76 76 INTENT_READONLY = registrar.INTENT_READONLY
77 77
78 78 # common command options
79 79
80 80 globalopts = [
81 81 ('R', 'repository', '',
82 82 _('repository root directory or name of overlay bundle file'),
83 83 _('REPO')),
84 84 ('', 'cwd', '',
85 85 _('change working directory'), _('DIR')),
86 86 ('y', 'noninteractive', None,
87 87 _('do not prompt, automatically pick the first choice for all prompts')),
88 88 ('q', 'quiet', None, _('suppress output')),
89 89 ('v', 'verbose', None, _('enable additional output')),
90 90 ('', 'color', '',
91 91 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
92 92 # and should not be translated
93 93 _("when to colorize (boolean, always, auto, never, or debug)"),
94 94 _('TYPE')),
95 95 ('', 'config', [],
96 96 _('set/override config option (use \'section.name=value\')'),
97 97 _('CONFIG')),
98 98 ('', 'debug', None, _('enable debugging output')),
99 99 ('', 'debugger', None, _('start debugger')),
100 100 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
101 101 _('ENCODE')),
102 102 ('', 'encodingmode', encoding.encodingmode,
103 103 _('set the charset encoding mode'), _('MODE')),
104 104 ('', 'traceback', None, _('always print a traceback on exception')),
105 105 ('', 'time', None, _('time how long the command takes')),
106 106 ('', 'profile', None, _('print command execution profile')),
107 107 ('', 'version', None, _('output version information and exit')),
108 108 ('h', 'help', None, _('display help and exit')),
109 109 ('', 'hidden', False, _('consider hidden changesets')),
110 110 ('', 'pager', 'auto',
111 111 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
112 112 ]
113 113
114 114 dryrunopts = cmdutil.dryrunopts
115 confirmopts = cmdutil.confirmopts
115 116 remoteopts = cmdutil.remoteopts
116 117 walkopts = cmdutil.walkopts
117 118 commitopts = cmdutil.commitopts
118 119 commitopts2 = cmdutil.commitopts2
119 120 formatteropts = cmdutil.formatteropts
120 121 templateopts = cmdutil.templateopts
121 122 logopts = cmdutil.logopts
122 123 diffopts = cmdutil.diffopts
123 124 diffwsopts = cmdutil.diffwsopts
124 125 diffopts2 = cmdutil.diffopts2
125 126 mergetoolopts = cmdutil.mergetoolopts
126 127 similarityopts = cmdutil.similarityopts
127 128 subrepoopts = cmdutil.subrepoopts
128 129 debugrevlogopts = cmdutil.debugrevlogopts
129 130
130 131 # Commands start here, listed alphabetically
131 132
132 133 @command('^add',
133 134 walkopts + subrepoopts + dryrunopts,
134 135 _('[OPTION]... [FILE]...'),
135 136 inferrepo=True)
136 137 def add(ui, repo, *pats, **opts):
137 138 """add the specified files on the next commit
138 139
139 140 Schedule files to be version controlled and added to the
140 141 repository.
141 142
142 143 The files will be added to the repository at the next commit. To
143 144 undo an add before that, see :hg:`forget`.
144 145
145 146 If no names are given, add all files to the repository (except
146 147 files matching ``.hgignore``).
147 148
148 149 .. container:: verbose
149 150
150 151 Examples:
151 152
152 153 - New (unknown) files are added
153 154 automatically by :hg:`add`::
154 155
155 156 $ ls
156 157 foo.c
157 158 $ hg status
158 159 ? foo.c
159 160 $ hg add
160 161 adding foo.c
161 162 $ hg status
162 163 A foo.c
163 164
164 165 - Specific files to be added can be specified::
165 166
166 167 $ ls
167 168 bar.c foo.c
168 169 $ hg status
169 170 ? bar.c
170 171 ? foo.c
171 172 $ hg add bar.c
172 173 $ hg status
173 174 A bar.c
174 175 ? foo.c
175 176
176 177 Returns 0 if all files are successfully added.
177 178 """
178 179
179 180 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
180 181 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
181 182 return rejected and 1 or 0
182 183
183 184 @command('addremove',
184 185 similarityopts + subrepoopts + walkopts + dryrunopts,
185 186 _('[OPTION]... [FILE]...'),
186 187 inferrepo=True)
187 188 def addremove(ui, repo, *pats, **opts):
188 189 """add all new files, delete all missing files
189 190
190 191 Add all new files and remove all missing files from the
191 192 repository.
192 193
193 194 Unless names are given, new files are ignored if they match any of
194 195 the patterns in ``.hgignore``. As with add, these changes take
195 196 effect at the next commit.
196 197
197 198 Use the -s/--similarity option to detect renamed files. This
198 199 option takes a percentage between 0 (disabled) and 100 (files must
199 200 be identical) as its parameter. With a parameter greater than 0,
200 201 this compares every removed file with every added file and records
201 202 those similar enough as renames. Detecting renamed files this way
202 203 can be expensive. After using this option, :hg:`status -C` can be
203 204 used to check which files were identified as moved or renamed. If
204 205 not specified, -s/--similarity defaults to 100 and only renames of
205 206 identical files are detected.
206 207
207 208 .. container:: verbose
208 209
209 210 Examples:
210 211
211 212 - A number of files (bar.c and foo.c) are new,
212 213 while foobar.c has been removed (without using :hg:`remove`)
213 214 from the repository::
214 215
215 216 $ ls
216 217 bar.c foo.c
217 218 $ hg status
218 219 ! foobar.c
219 220 ? bar.c
220 221 ? foo.c
221 222 $ hg addremove
222 223 adding bar.c
223 224 adding foo.c
224 225 removing foobar.c
225 226 $ hg status
226 227 A bar.c
227 228 A foo.c
228 229 R foobar.c
229 230
230 231 - A file foobar.c was moved to foo.c without using :hg:`rename`.
231 232 Afterwards, it was edited slightly::
232 233
233 234 $ ls
234 235 foo.c
235 236 $ hg status
236 237 ! foobar.c
237 238 ? foo.c
238 239 $ hg addremove --similarity 90
239 240 removing foobar.c
240 241 adding foo.c
241 242 recording removal of foobar.c as rename to foo.c (94% similar)
242 243 $ hg status -C
243 244 A foo.c
244 245 foobar.c
245 246 R foobar.c
246 247
247 248 Returns 0 if all files are successfully added.
248 249 """
249 250 opts = pycompat.byteskwargs(opts)
250 251 if not opts.get('similarity'):
251 252 opts['similarity'] = '100'
252 253 matcher = scmutil.match(repo[None], pats, opts)
253 254 return scmutil.addremove(repo, matcher, "", opts)
254 255
255 256 @command('^annotate|blame',
256 257 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
257 258 ('', 'follow', None,
258 259 _('follow copies/renames and list the filename (DEPRECATED)')),
259 260 ('', 'no-follow', None, _("don't follow copies and renames")),
260 261 ('a', 'text', None, _('treat all files as text')),
261 262 ('u', 'user', None, _('list the author (long with -v)')),
262 263 ('f', 'file', None, _('list the filename')),
263 264 ('d', 'date', None, _('list the date (short with -q)')),
264 265 ('n', 'number', None, _('list the revision number (default)')),
265 266 ('c', 'changeset', None, _('list the changeset')),
266 267 ('l', 'line-number', None, _('show line number at the first appearance')),
267 268 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
268 269 ] + diffwsopts + walkopts + formatteropts,
269 270 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
270 271 inferrepo=True)
271 272 def annotate(ui, repo, *pats, **opts):
272 273 """show changeset information by line for each file
273 274
274 275 List changes in files, showing the revision id responsible for
275 276 each line.
276 277
277 278 This command is useful for discovering when a change was made and
278 279 by whom.
279 280
280 281 If you include --file, --user, or --date, the revision number is
281 282 suppressed unless you also include --number.
282 283
283 284 Without the -a/--text option, annotate will avoid processing files
284 285 it detects as binary. With -a, annotate will annotate the file
285 286 anyway, although the results will probably be neither useful
286 287 nor desirable.
287 288
288 289 Returns 0 on success.
289 290 """
290 291 opts = pycompat.byteskwargs(opts)
291 292 if not pats:
292 293 raise error.Abort(_('at least one filename or pattern is required'))
293 294
294 295 if opts.get('follow'):
295 296 # --follow is deprecated and now just an alias for -f/--file
296 297 # to mimic the behavior of Mercurial before version 1.5
297 298 opts['file'] = True
298 299
299 300 rev = opts.get('rev')
300 301 if rev:
301 302 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
302 303 ctx = scmutil.revsingle(repo, rev)
303 304
304 305 rootfm = ui.formatter('annotate', opts)
305 306 if ui.quiet:
306 307 datefunc = dateutil.shortdate
307 308 else:
308 309 datefunc = dateutil.datestr
309 310 if ctx.rev() is None:
310 311 def hexfn(node):
311 312 if node is None:
312 313 return None
313 314 else:
314 315 return rootfm.hexfunc(node)
315 316 if opts.get('changeset'):
316 317 # omit "+" suffix which is appended to node hex
317 318 def formatrev(rev):
318 319 if rev is None:
319 320 return '%d' % ctx.p1().rev()
320 321 else:
321 322 return '%d' % rev
322 323 else:
323 324 def formatrev(rev):
324 325 if rev is None:
325 326 return '%d+' % ctx.p1().rev()
326 327 else:
327 328 return '%d ' % rev
328 329 def formathex(hex):
329 330 if hex is None:
330 331 return '%s+' % rootfm.hexfunc(ctx.p1().node())
331 332 else:
332 333 return '%s ' % hex
333 334 else:
334 335 hexfn = rootfm.hexfunc
335 336 formatrev = formathex = pycompat.bytestr
336 337
337 338 opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
338 339 ('number', ' ', lambda x: x.fctx.rev(), formatrev),
339 340 ('changeset', ' ', lambda x: hexfn(x.fctx.node()), formathex),
340 341 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
341 342 ('file', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
342 343 ('line_number', ':', lambda x: x.lineno, pycompat.bytestr),
343 344 ]
344 345 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
345 346
346 347 if (not opts.get('user') and not opts.get('changeset')
347 348 and not opts.get('date') and not opts.get('file')):
348 349 opts['number'] = True
349 350
350 351 linenumber = opts.get('line_number') is not None
351 352 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
352 353 raise error.Abort(_('at least one of -n/-c is required for -l'))
353 354
354 355 ui.pager('annotate')
355 356
356 357 if rootfm.isplain():
357 358 def makefunc(get, fmt):
358 359 return lambda x: fmt(get(x))
359 360 else:
360 361 def makefunc(get, fmt):
361 362 return get
362 363 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
363 364 if opts.get(op)]
364 365 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
365 366 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
366 367 if opts.get(op))
367 368
368 369 def bad(x, y):
369 370 raise error.Abort("%s: %s" % (x, y))
370 371
371 372 m = scmutil.match(ctx, pats, opts, badfn=bad)
372 373
373 374 follow = not opts.get('no_follow')
374 375 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
375 376 whitespace=True)
376 377 skiprevs = opts.get('skip')
377 378 if skiprevs:
378 379 skiprevs = scmutil.revrange(repo, skiprevs)
379 380
380 381 for abs in ctx.walk(m):
381 382 fctx = ctx[abs]
382 383 rootfm.startitem()
383 384 rootfm.data(abspath=abs, path=m.rel(abs))
384 385 if not opts.get('text') and fctx.isbinary():
385 386 rootfm.plain(_("%s: binary file\n")
386 387 % ((pats and m.rel(abs)) or abs))
387 388 continue
388 389
389 390 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
390 391 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
391 392 diffopts=diffopts)
392 393 if not lines:
393 394 fm.end()
394 395 continue
395 396 formats = []
396 397 pieces = []
397 398
398 399 for f, sep in funcmap:
399 400 l = [f(n) for n in lines]
400 401 if fm.isplain():
401 402 sizes = [encoding.colwidth(x) for x in l]
402 403 ml = max(sizes)
403 404 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
404 405 else:
405 406 formats.append(['%s' for x in l])
406 407 pieces.append(l)
407 408
408 409 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
409 410 fm.startitem()
410 411 fm.context(fctx=n.fctx)
411 412 fm.write(fields, "".join(f), *p)
412 413 if n.skip:
413 414 fmt = "* %s"
414 415 else:
415 416 fmt = ": %s"
416 417 fm.write('line', fmt, n.text)
417 418
418 419 if not lines[-1].text.endswith('\n'):
419 420 fm.plain('\n')
420 421 fm.end()
421 422
422 423 rootfm.end()
423 424
424 425 @command('archive',
425 426 [('', 'no-decode', None, _('do not pass files through decoders')),
426 427 ('p', 'prefix', '', _('directory prefix for files in archive'),
427 428 _('PREFIX')),
428 429 ('r', 'rev', '', _('revision to distribute'), _('REV')),
429 430 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
430 431 ] + subrepoopts + walkopts,
431 432 _('[OPTION]... DEST'))
432 433 def archive(ui, repo, dest, **opts):
433 434 '''create an unversioned archive of a repository revision
434 435
435 436 By default, the revision used is the parent of the working
436 437 directory; use -r/--rev to specify a different revision.
437 438
438 439 The archive type is automatically detected based on file
439 440 extension (to override, use -t/--type).
440 441
441 442 .. container:: verbose
442 443
443 444 Examples:
444 445
445 446 - create a zip file containing the 1.0 release::
446 447
447 448 hg archive -r 1.0 project-1.0.zip
448 449
449 450 - create a tarball excluding .hg files::
450 451
451 452 hg archive project.tar.gz -X ".hg*"
452 453
453 454 Valid types are:
454 455
455 456 :``files``: a directory full of files (default)
456 457 :``tar``: tar archive, uncompressed
457 458 :``tbz2``: tar archive, compressed using bzip2
458 459 :``tgz``: tar archive, compressed using gzip
459 460 :``uzip``: zip archive, uncompressed
460 461 :``zip``: zip archive, compressed using deflate
461 462
462 463 The exact name of the destination archive or directory is given
463 464 using a format string; see :hg:`help export` for details.
464 465
465 466 Each member added to an archive file has a directory prefix
466 467 prepended. Use -p/--prefix to specify a format string for the
467 468 prefix. The default is the basename of the archive, with suffixes
468 469 removed.
469 470
470 471 Returns 0 on success.
471 472 '''
472 473
473 474 opts = pycompat.byteskwargs(opts)
474 475 rev = opts.get('rev')
475 476 if rev:
476 477 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
477 478 ctx = scmutil.revsingle(repo, rev)
478 479 if not ctx:
479 480 raise error.Abort(_('no working directory: please specify a revision'))
480 481 node = ctx.node()
481 482 dest = cmdutil.makefilename(ctx, dest)
482 483 if os.path.realpath(dest) == repo.root:
483 484 raise error.Abort(_('repository root cannot be destination'))
484 485
485 486 kind = opts.get('type') or archival.guesskind(dest) or 'files'
486 487 prefix = opts.get('prefix')
487 488
488 489 if dest == '-':
489 490 if kind == 'files':
490 491 raise error.Abort(_('cannot archive plain files to stdout'))
491 492 dest = cmdutil.makefileobj(ctx, dest)
492 493 if not prefix:
493 494 prefix = os.path.basename(repo.root) + '-%h'
494 495
495 496 prefix = cmdutil.makefilename(ctx, prefix)
496 497 match = scmutil.match(ctx, [], opts)
497 498 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
498 499 match, prefix, subrepos=opts.get('subrepos'))
499 500
500 501 @command('backout',
501 502 [('', 'merge', None, _('merge with old dirstate parent after backout')),
502 503 ('', 'commit', None,
503 504 _('commit if no conflicts were encountered (DEPRECATED)')),
504 505 ('', 'no-commit', None, _('do not commit')),
505 506 ('', 'parent', '',
506 507 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
507 508 ('r', 'rev', '', _('revision to backout'), _('REV')),
508 509 ('e', 'edit', False, _('invoke editor on commit messages')),
509 510 ] + mergetoolopts + walkopts + commitopts + commitopts2,
510 511 _('[OPTION]... [-r] REV'))
511 512 def backout(ui, repo, node=None, rev=None, **opts):
512 513 '''reverse effect of earlier changeset
513 514
514 515 Prepare a new changeset with the effect of REV undone in the
515 516 current working directory. If no conflicts were encountered,
516 517 it will be committed immediately.
517 518
518 519 If REV is the parent of the working directory, then this new changeset
519 520 is committed automatically (unless --no-commit is specified).
520 521
521 522 .. note::
522 523
523 524 :hg:`backout` cannot be used to fix either an unwanted or
524 525 incorrect merge.
525 526
526 527 .. container:: verbose
527 528
528 529 Examples:
529 530
530 531 - Reverse the effect of the parent of the working directory.
531 532 This backout will be committed immediately::
532 533
533 534 hg backout -r .
534 535
535 536 - Reverse the effect of previous bad revision 23::
536 537
537 538 hg backout -r 23
538 539
539 540 - Reverse the effect of previous bad revision 23 and
540 541 leave changes uncommitted::
541 542
542 543 hg backout -r 23 --no-commit
543 544 hg commit -m "Backout revision 23"
544 545
545 546 By default, the pending changeset will have one parent,
546 547 maintaining a linear history. With --merge, the pending
547 548 changeset will instead have two parents: the old parent of the
548 549 working directory and a new child of REV that simply undoes REV.
549 550
550 551 Before version 1.7, the behavior without --merge was equivalent
551 552 to specifying --merge followed by :hg:`update --clean .` to
552 553 cancel the merge and leave the child of REV as a head to be
553 554 merged separately.
554 555
555 556 See :hg:`help dates` for a list of formats valid for -d/--date.
556 557
557 558 See :hg:`help revert` for a way to restore files to the state
558 559 of another revision.
559 560
560 561 Returns 0 on success, 1 if nothing to backout or there are unresolved
561 562 files.
562 563 '''
563 564 wlock = lock = None
564 565 try:
565 566 wlock = repo.wlock()
566 567 lock = repo.lock()
567 568 return _dobackout(ui, repo, node, rev, **opts)
568 569 finally:
569 570 release(lock, wlock)
570 571
571 572 def _dobackout(ui, repo, node=None, rev=None, **opts):
572 573 opts = pycompat.byteskwargs(opts)
573 574 if opts.get('commit') and opts.get('no_commit'):
574 575 raise error.Abort(_("cannot use --commit with --no-commit"))
575 576 if opts.get('merge') and opts.get('no_commit'):
576 577 raise error.Abort(_("cannot use --merge with --no-commit"))
577 578
578 579 if rev and node:
579 580 raise error.Abort(_("please specify just one revision"))
580 581
581 582 if not rev:
582 583 rev = node
583 584
584 585 if not rev:
585 586 raise error.Abort(_("please specify a revision to backout"))
586 587
587 588 date = opts.get('date')
588 589 if date:
589 590 opts['date'] = dateutil.parsedate(date)
590 591
591 592 cmdutil.checkunfinished(repo)
592 593 cmdutil.bailifchanged(repo)
593 594 node = scmutil.revsingle(repo, rev).node()
594 595
595 596 op1, op2 = repo.dirstate.parents()
596 597 if not repo.changelog.isancestor(node, op1):
597 598 raise error.Abort(_('cannot backout change that is not an ancestor'))
598 599
599 600 p1, p2 = repo.changelog.parents(node)
600 601 if p1 == nullid:
601 602 raise error.Abort(_('cannot backout a change with no parents'))
602 603 if p2 != nullid:
603 604 if not opts.get('parent'):
604 605 raise error.Abort(_('cannot backout a merge changeset'))
605 606 p = repo.lookup(opts['parent'])
606 607 if p not in (p1, p2):
607 608 raise error.Abort(_('%s is not a parent of %s') %
608 609 (short(p), short(node)))
609 610 parent = p
610 611 else:
611 612 if opts.get('parent'):
612 613 raise error.Abort(_('cannot use --parent on non-merge changeset'))
613 614 parent = p1
614 615
615 616 # the backout should appear on the same branch
616 617 branch = repo.dirstate.branch()
617 618 bheads = repo.branchheads(branch)
618 619 rctx = scmutil.revsingle(repo, hex(parent))
619 620 if not opts.get('merge') and op1 != node:
620 621 dsguard = dirstateguard.dirstateguard(repo, 'backout')
621 622 try:
622 623 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
623 624 'backout')
624 625 stats = mergemod.update(repo, parent, True, True, node, False)
625 626 repo.setparents(op1, op2)
626 627 dsguard.close()
627 628 hg._showstats(repo, stats)
628 629 if stats.unresolvedcount:
629 630 repo.ui.status(_("use 'hg resolve' to retry unresolved "
630 631 "file merges\n"))
631 632 return 1
632 633 finally:
633 634 ui.setconfig('ui', 'forcemerge', '', '')
634 635 lockmod.release(dsguard)
635 636 else:
636 637 hg.clean(repo, node, show_stats=False)
637 638 repo.dirstate.setbranch(branch)
638 639 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
639 640
640 641 if opts.get('no_commit'):
641 642 msg = _("changeset %s backed out, "
642 643 "don't forget to commit.\n")
643 644 ui.status(msg % short(node))
644 645 return 0
645 646
646 647 def commitfunc(ui, repo, message, match, opts):
647 648 editform = 'backout'
648 649 e = cmdutil.getcommiteditor(editform=editform,
649 650 **pycompat.strkwargs(opts))
650 651 if not message:
651 652 # we don't translate commit messages
652 653 message = "Backed out changeset %s" % short(node)
653 654 e = cmdutil.getcommiteditor(edit=True, editform=editform)
654 655 return repo.commit(message, opts.get('user'), opts.get('date'),
655 656 match, editor=e)
656 657 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
657 658 if not newnode:
658 659 ui.status(_("nothing changed\n"))
659 660 return 1
660 661 cmdutil.commitstatus(repo, newnode, branch, bheads)
661 662
662 663 def nice(node):
663 664 return '%d:%s' % (repo.changelog.rev(node), short(node))
664 665 ui.status(_('changeset %s backs out changeset %s\n') %
665 666 (nice(repo.changelog.tip()), nice(node)))
666 667 if opts.get('merge') and op1 != node:
667 668 hg.clean(repo, op1, show_stats=False)
668 669 ui.status(_('merging with changeset %s\n')
669 670 % nice(repo.changelog.tip()))
670 671 try:
671 672 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
672 673 'backout')
673 674 return hg.merge(repo, hex(repo.changelog.tip()))
674 675 finally:
675 676 ui.setconfig('ui', 'forcemerge', '', '')
676 677 return 0
677 678
678 679 @command('bisect',
679 680 [('r', 'reset', False, _('reset bisect state')),
680 681 ('g', 'good', False, _('mark changeset good')),
681 682 ('b', 'bad', False, _('mark changeset bad')),
682 683 ('s', 'skip', False, _('skip testing changeset')),
683 684 ('e', 'extend', False, _('extend the bisect range')),
684 685 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
685 686 ('U', 'noupdate', False, _('do not update to target'))],
686 687 _("[-gbsr] [-U] [-c CMD] [REV]"))
687 688 def bisect(ui, repo, rev=None, extra=None, command=None,
688 689 reset=None, good=None, bad=None, skip=None, extend=None,
689 690 noupdate=None):
690 691 """subdivision search of changesets
691 692
692 693 This command helps to find changesets which introduce problems. To
693 694 use, mark the earliest changeset you know exhibits the problem as
694 695 bad, then mark the latest changeset which is free from the problem
695 696 as good. Bisect will update your working directory to a revision
696 697 for testing (unless the -U/--noupdate option is specified). Once
697 698 you have performed tests, mark the working directory as good or
698 699 bad, and bisect will either update to another candidate changeset
699 700 or announce that it has found the bad revision.
700 701
701 702 As a shortcut, you can also use the revision argument to mark a
702 703 revision as good or bad without checking it out first.
703 704
704 705 If you supply a command, it will be used for automatic bisection.
705 706 The environment variable HG_NODE will contain the ID of the
706 707 changeset being tested. The exit status of the command will be
707 708 used to mark revisions as good or bad: status 0 means good, 125
708 709 means to skip the revision, 127 (command not found) will abort the
709 710 bisection, and any other non-zero exit status means the revision
710 711 is bad.
711 712
712 713 .. container:: verbose
713 714
714 715 Some examples:
715 716
716 717 - start a bisection with known bad revision 34, and good revision 12::
717 718
718 719 hg bisect --bad 34
719 720 hg bisect --good 12
720 721
721 722 - advance the current bisection by marking current revision as good or
722 723 bad::
723 724
724 725 hg bisect --good
725 726 hg bisect --bad
726 727
727 728 - mark the current revision, or a known revision, to be skipped (e.g. if
728 729 that revision is not usable because of another issue)::
729 730
730 731 hg bisect --skip
731 732 hg bisect --skip 23
732 733
733 734 - skip all revisions that do not touch directories ``foo`` or ``bar``::
734 735
735 736 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
736 737
737 738 - forget the current bisection::
738 739
739 740 hg bisect --reset
740 741
741 742 - use 'make && make tests' to automatically find the first broken
742 743 revision::
743 744
744 745 hg bisect --reset
745 746 hg bisect --bad 34
746 747 hg bisect --good 12
747 748 hg bisect --command "make && make tests"
748 749
749 750 - see all changesets whose states are already known in the current
750 751 bisection::
751 752
752 753 hg log -r "bisect(pruned)"
753 754
754 755 - see the changeset currently being bisected (especially useful
755 756 if running with -U/--noupdate)::
756 757
757 758 hg log -r "bisect(current)"
758 759
759 760 - see all changesets that took part in the current bisection::
760 761
761 762 hg log -r "bisect(range)"
762 763
763 764 - you can even get a nice graph::
764 765
765 766 hg log --graph -r "bisect(range)"
766 767
767 768 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
768 769
769 770 Returns 0 on success.
770 771 """
771 772 # backward compatibility
772 773 if rev in "good bad reset init".split():
773 774 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
774 775 cmd, rev, extra = rev, extra, None
775 776 if cmd == "good":
776 777 good = True
777 778 elif cmd == "bad":
778 779 bad = True
779 780 else:
780 781 reset = True
781 782 elif extra:
782 783 raise error.Abort(_('incompatible arguments'))
783 784
784 785 incompatibles = {
785 786 '--bad': bad,
786 787 '--command': bool(command),
787 788 '--extend': extend,
788 789 '--good': good,
789 790 '--reset': reset,
790 791 '--skip': skip,
791 792 }
792 793
793 794 enabled = [x for x in incompatibles if incompatibles[x]]
794 795
795 796 if len(enabled) > 1:
796 797 raise error.Abort(_('%s and %s are incompatible') %
797 798 tuple(sorted(enabled)[0:2]))
798 799
799 800 if reset:
800 801 hbisect.resetstate(repo)
801 802 return
802 803
803 804 state = hbisect.load_state(repo)
804 805
805 806 # update state
806 807 if good or bad or skip:
807 808 if rev:
808 809 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
809 810 else:
810 811 nodes = [repo.lookup('.')]
811 812 if good:
812 813 state['good'] += nodes
813 814 elif bad:
814 815 state['bad'] += nodes
815 816 elif skip:
816 817 state['skip'] += nodes
817 818 hbisect.save_state(repo, state)
818 819 if not (state['good'] and state['bad']):
819 820 return
820 821
821 822 def mayupdate(repo, node, show_stats=True):
822 823 """common used update sequence"""
823 824 if noupdate:
824 825 return
825 826 cmdutil.checkunfinished(repo)
826 827 cmdutil.bailifchanged(repo)
827 828 return hg.clean(repo, node, show_stats=show_stats)
828 829
829 830 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
830 831
831 832 if command:
832 833 changesets = 1
833 834 if noupdate:
834 835 try:
835 836 node = state['current'][0]
836 837 except LookupError:
837 838 raise error.Abort(_('current bisect revision is unknown - '
838 839 'start a new bisect to fix'))
839 840 else:
840 841 node, p2 = repo.dirstate.parents()
841 842 if p2 != nullid:
842 843 raise error.Abort(_('current bisect revision is a merge'))
843 844 if rev:
844 845 node = repo[scmutil.revsingle(repo, rev, node)].node()
845 846 try:
846 847 while changesets:
847 848 # update state
848 849 state['current'] = [node]
849 850 hbisect.save_state(repo, state)
850 851 status = ui.system(command, environ={'HG_NODE': hex(node)},
851 852 blockedtag='bisect_check')
852 853 if status == 125:
853 854 transition = "skip"
854 855 elif status == 0:
855 856 transition = "good"
856 857 # status < 0 means process was killed
857 858 elif status == 127:
858 859 raise error.Abort(_("failed to execute %s") % command)
859 860 elif status < 0:
860 861 raise error.Abort(_("%s killed") % command)
861 862 else:
862 863 transition = "bad"
863 864 state[transition].append(node)
864 865 ctx = repo[node]
865 866 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
866 867 transition))
867 868 hbisect.checkstate(state)
868 869 # bisect
869 870 nodes, changesets, bgood = hbisect.bisect(repo, state)
870 871 # update to next check
871 872 node = nodes[0]
872 873 mayupdate(repo, node, show_stats=False)
873 874 finally:
874 875 state['current'] = [node]
875 876 hbisect.save_state(repo, state)
876 877 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
877 878 return
878 879
879 880 hbisect.checkstate(state)
880 881
881 882 # actually bisect
882 883 nodes, changesets, good = hbisect.bisect(repo, state)
883 884 if extend:
884 885 if not changesets:
885 886 extendnode = hbisect.extendrange(repo, state, nodes, good)
886 887 if extendnode is not None:
887 888 ui.write(_("Extending search to changeset %d:%s\n")
888 889 % (extendnode.rev(), extendnode))
889 890 state['current'] = [extendnode.node()]
890 891 hbisect.save_state(repo, state)
891 892 return mayupdate(repo, extendnode.node())
892 893 raise error.Abort(_("nothing to extend"))
893 894
894 895 if changesets == 0:
895 896 hbisect.printresult(ui, repo, state, displayer, nodes, good)
896 897 else:
897 898 assert len(nodes) == 1 # only a single node can be tested next
898 899 node = nodes[0]
899 900 # compute the approximate number of remaining tests
900 901 tests, size = 0, 2
901 902 while size <= changesets:
902 903 tests, size = tests + 1, size * 2
903 904 rev = repo.changelog.rev(node)
904 905 ui.write(_("Testing changeset %d:%s "
905 906 "(%d changesets remaining, ~%d tests)\n")
906 907 % (rev, short(node), changesets, tests))
907 908 state['current'] = [node]
908 909 hbisect.save_state(repo, state)
909 910 return mayupdate(repo, node)
910 911
911 912 @command('bookmarks|bookmark',
912 913 [('f', 'force', False, _('force')),
913 914 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
914 915 ('d', 'delete', False, _('delete a given bookmark')),
915 916 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
916 917 ('i', 'inactive', False, _('mark a bookmark inactive')),
917 918 ] + formatteropts,
918 919 _('hg bookmarks [OPTIONS]... [NAME]...'))
919 920 def bookmark(ui, repo, *names, **opts):
920 921 '''create a new bookmark or list existing bookmarks
921 922
922 923 Bookmarks are labels on changesets to help track lines of development.
923 924 Bookmarks are unversioned and can be moved, renamed and deleted.
924 925 Deleting or moving a bookmark has no effect on the associated changesets.
925 926
926 927 Creating or updating to a bookmark causes it to be marked as 'active'.
927 928 The active bookmark is indicated with a '*'.
928 929 When a commit is made, the active bookmark will advance to the new commit.
929 930 A plain :hg:`update` will also advance an active bookmark, if possible.
930 931 Updating away from a bookmark will cause it to be deactivated.
931 932
932 933 Bookmarks can be pushed and pulled between repositories (see
933 934 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
934 935 diverged, a new 'divergent bookmark' of the form 'name@path' will
935 936 be created. Using :hg:`merge` will resolve the divergence.
936 937
937 938 Specifying bookmark as '.' to -m or -d options is equivalent to specifying
938 939 the active bookmark's name.
939 940
940 941 A bookmark named '@' has the special property that :hg:`clone` will
941 942 check it out by default if it exists.
942 943
943 944 .. container:: verbose
944 945
945 946 Examples:
946 947
947 948 - create an active bookmark for a new line of development::
948 949
949 950 hg book new-feature
950 951
951 952 - create an inactive bookmark as a place marker::
952 953
953 954 hg book -i reviewed
954 955
955 956 - create an inactive bookmark on another changeset::
956 957
957 958 hg book -r .^ tested
958 959
959 960 - rename bookmark turkey to dinner::
960 961
961 962 hg book -m turkey dinner
962 963
963 964 - move the '@' bookmark from another branch::
964 965
965 966 hg book -f @
966 967 '''
967 968 force = opts.get(r'force')
968 969 rev = opts.get(r'rev')
969 970 delete = opts.get(r'delete')
970 971 rename = opts.get(r'rename')
971 972 inactive = opts.get(r'inactive')
972 973
973 974 if delete and rename:
974 975 raise error.Abort(_("--delete and --rename are incompatible"))
975 976 if delete and rev:
976 977 raise error.Abort(_("--rev is incompatible with --delete"))
977 978 if rename and rev:
978 979 raise error.Abort(_("--rev is incompatible with --rename"))
979 980 if not names and (delete or rev):
980 981 raise error.Abort(_("bookmark name required"))
981 982
982 983 if delete or rename or names or inactive:
983 984 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
984 985 if delete:
985 986 names = pycompat.maplist(repo._bookmarks.expandname, names)
986 987 bookmarks.delete(repo, tr, names)
987 988 elif rename:
988 989 if not names:
989 990 raise error.Abort(_("new bookmark name required"))
990 991 elif len(names) > 1:
991 992 raise error.Abort(_("only one new bookmark name allowed"))
992 993 rename = repo._bookmarks.expandname(rename)
993 994 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
994 995 elif names:
995 996 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
996 997 elif inactive:
997 998 if len(repo._bookmarks) == 0:
998 999 ui.status(_("no bookmarks set\n"))
999 1000 elif not repo._activebookmark:
1000 1001 ui.status(_("no active bookmark\n"))
1001 1002 else:
1002 1003 bookmarks.deactivate(repo)
1003 1004 else: # show bookmarks
1004 1005 bookmarks.printbookmarks(ui, repo, **opts)
1005 1006
1006 1007 @command('branch',
1007 1008 [('f', 'force', None,
1008 1009 _('set branch name even if it shadows an existing branch')),
1009 1010 ('C', 'clean', None, _('reset branch name to parent branch name')),
1010 1011 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1011 1012 ],
1012 1013 _('[-fC] [NAME]'))
1013 1014 def branch(ui, repo, label=None, **opts):
1014 1015 """set or show the current branch name
1015 1016
1016 1017 .. note::
1017 1018
1018 1019 Branch names are permanent and global. Use :hg:`bookmark` to create a
1019 1020 light-weight bookmark instead. See :hg:`help glossary` for more
1020 1021 information about named branches and bookmarks.
1021 1022
1022 1023 With no argument, show the current branch name. With one argument,
1023 1024 set the working directory branch name (the branch will not exist
1024 1025 in the repository until the next commit). Standard practice
1025 1026 recommends that primary development take place on the 'default'
1026 1027 branch.
1027 1028
1028 1029 Unless -f/--force is specified, branch will not let you set a
1029 1030 branch name that already exists.
1030 1031
1031 1032 Use -C/--clean to reset the working directory branch to that of
1032 1033 the parent of the working directory, negating a previous branch
1033 1034 change.
1034 1035
1035 1036 Use the command :hg:`update` to switch to an existing branch. Use
1036 1037 :hg:`commit --close-branch` to mark this branch head as closed.
1037 1038 When all heads of a branch are closed, the branch will be
1038 1039 considered closed.
1039 1040
1040 1041 Returns 0 on success.
1041 1042 """
1042 1043 opts = pycompat.byteskwargs(opts)
1043 1044 revs = opts.get('rev')
1044 1045 if label:
1045 1046 label = label.strip()
1046 1047
1047 1048 if not opts.get('clean') and not label:
1048 1049 if revs:
1049 1050 raise error.Abort(_("no branch name specified for the revisions"))
1050 1051 ui.write("%s\n" % repo.dirstate.branch())
1051 1052 return
1052 1053
1053 1054 with repo.wlock():
1054 1055 if opts.get('clean'):
1055 1056 label = repo[None].p1().branch()
1056 1057 repo.dirstate.setbranch(label)
1057 1058 ui.status(_('reset working directory to branch %s\n') % label)
1058 1059 elif label:
1059 1060
1060 1061 scmutil.checknewlabel(repo, label, 'branch')
1061 1062 if revs:
1062 1063 return cmdutil.changebranch(ui, repo, revs, label)
1063 1064
1064 1065 if not opts.get('force') and label in repo.branchmap():
1065 1066 if label not in [p.branch() for p in repo[None].parents()]:
1066 1067 raise error.Abort(_('a branch of the same name already'
1067 1068 ' exists'),
1068 1069 # i18n: "it" refers to an existing branch
1069 1070 hint=_("use 'hg update' to switch to it"))
1070 1071
1071 1072 repo.dirstate.setbranch(label)
1072 1073 ui.status(_('marked working directory as branch %s\n') % label)
1073 1074
1074 1075 # find any open named branches aside from default
1075 1076 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1076 1077 if n != "default" and not c]
1077 1078 if not others:
1078 1079 ui.status(_('(branches are permanent and global, '
1079 1080 'did you want a bookmark?)\n'))
1080 1081
1081 1082 @command('branches',
1082 1083 [('a', 'active', False,
1083 1084 _('show only branches that have unmerged heads (DEPRECATED)')),
1084 1085 ('c', 'closed', False, _('show normal and closed branches')),
1085 1086 ] + formatteropts,
1086 1087 _('[-c]'),
1087 1088 intents={INTENT_READONLY})
1088 1089 def branches(ui, repo, active=False, closed=False, **opts):
1089 1090 """list repository named branches
1090 1091
1091 1092 List the repository's named branches, indicating which ones are
1092 1093 inactive. If -c/--closed is specified, also list branches which have
1093 1094 been marked closed (see :hg:`commit --close-branch`).
1094 1095
1095 1096 Use the command :hg:`update` to switch to an existing branch.
1096 1097
1097 1098 Returns 0.
1098 1099 """
1099 1100
1100 1101 opts = pycompat.byteskwargs(opts)
1101 1102 ui.pager('branches')
1102 1103 fm = ui.formatter('branches', opts)
1103 1104 hexfunc = fm.hexfunc
1104 1105
1105 1106 allheads = set(repo.heads())
1106 1107 branches = []
1107 1108 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1108 1109 isactive = False
1109 1110 if not isclosed:
1110 1111 openheads = set(repo.branchmap().iteropen(heads))
1111 1112 isactive = bool(openheads & allheads)
1112 1113 branches.append((tag, repo[tip], isactive, not isclosed))
1113 1114 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1114 1115 reverse=True)
1115 1116
1116 1117 for tag, ctx, isactive, isopen in branches:
1117 1118 if active and not isactive:
1118 1119 continue
1119 1120 if isactive:
1120 1121 label = 'branches.active'
1121 1122 notice = ''
1122 1123 elif not isopen:
1123 1124 if not closed:
1124 1125 continue
1125 1126 label = 'branches.closed'
1126 1127 notice = _(' (closed)')
1127 1128 else:
1128 1129 label = 'branches.inactive'
1129 1130 notice = _(' (inactive)')
1130 1131 current = (tag == repo.dirstate.branch())
1131 1132 if current:
1132 1133 label = 'branches.current'
1133 1134
1134 1135 fm.startitem()
1135 1136 fm.write('branch', '%s', tag, label=label)
1136 1137 rev = ctx.rev()
1137 1138 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1138 1139 fmt = ' ' * padsize + ' %d:%s'
1139 1140 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1140 1141 label='log.changeset changeset.%s' % ctx.phasestr())
1141 1142 fm.context(ctx=ctx)
1142 1143 fm.data(active=isactive, closed=not isopen, current=current)
1143 1144 if not ui.quiet:
1144 1145 fm.plain(notice)
1145 1146 fm.plain('\n')
1146 1147 fm.end()
1147 1148
1148 1149 @command('bundle',
1149 1150 [('f', 'force', None, _('run even when the destination is unrelated')),
1150 1151 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1151 1152 _('REV')),
1152 1153 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1153 1154 _('BRANCH')),
1154 1155 ('', 'base', [],
1155 1156 _('a base changeset assumed to be available at the destination'),
1156 1157 _('REV')),
1157 1158 ('a', 'all', None, _('bundle all changesets in the repository')),
1158 1159 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1159 1160 ] + remoteopts,
1160 1161 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1161 1162 def bundle(ui, repo, fname, dest=None, **opts):
1162 1163 """create a bundle file
1163 1164
1164 1165 Generate a bundle file containing data to be transferred to another
1165 1166 repository.
1166 1167
1167 1168 To create a bundle containing all changesets, use -a/--all
1168 1169 (or --base null). Otherwise, hg assumes the destination will have
1169 1170 all the nodes you specify with --base parameters. Otherwise, hg
1170 1171 will assume the repository has all the nodes in destination, or
1171 1172 default-push/default if no destination is specified, where destination
1172 1173 is the repository you provide through DEST option.
1173 1174
1174 1175 You can change bundle format with the -t/--type option. See
1175 1176 :hg:`help bundlespec` for documentation on this format. By default,
1176 1177 the most appropriate format is used and compression defaults to
1177 1178 bzip2.
1178 1179
1179 1180 The bundle file can then be transferred using conventional means
1180 1181 and applied to another repository with the unbundle or pull
1181 1182 command. This is useful when direct push and pull are not
1182 1183 available or when exporting an entire repository is undesirable.
1183 1184
1184 1185 Applying bundles preserves all changeset contents including
1185 1186 permissions, copy/rename information, and revision history.
1186 1187
1187 1188 Returns 0 on success, 1 if no changes found.
1188 1189 """
1189 1190 opts = pycompat.byteskwargs(opts)
1190 1191 revs = None
1191 1192 if 'rev' in opts:
1192 1193 revstrings = opts['rev']
1193 1194 revs = scmutil.revrange(repo, revstrings)
1194 1195 if revstrings and not revs:
1195 1196 raise error.Abort(_('no commits to bundle'))
1196 1197
1197 1198 bundletype = opts.get('type', 'bzip2').lower()
1198 1199 try:
1199 1200 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1200 1201 except error.UnsupportedBundleSpecification as e:
1201 1202 raise error.Abort(pycompat.bytestr(e),
1202 1203 hint=_("see 'hg help bundlespec' for supported "
1203 1204 "values for --type"))
1204 1205 cgversion = bundlespec.contentopts["cg.version"]
1205 1206
1206 1207 # Packed bundles are a pseudo bundle format for now.
1207 1208 if cgversion == 's1':
1208 1209 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1209 1210 hint=_("use 'hg debugcreatestreamclonebundle'"))
1210 1211
1211 1212 if opts.get('all'):
1212 1213 if dest:
1213 1214 raise error.Abort(_("--all is incompatible with specifying "
1214 1215 "a destination"))
1215 1216 if opts.get('base'):
1216 1217 ui.warn(_("ignoring --base because --all was specified\n"))
1217 1218 base = ['null']
1218 1219 else:
1219 1220 base = scmutil.revrange(repo, opts.get('base'))
1220 1221 if cgversion not in changegroup.supportedoutgoingversions(repo):
1221 1222 raise error.Abort(_("repository does not support bundle version %s") %
1222 1223 cgversion)
1223 1224
1224 1225 if base:
1225 1226 if dest:
1226 1227 raise error.Abort(_("--base is incompatible with specifying "
1227 1228 "a destination"))
1228 1229 common = [repo[rev].node() for rev in base]
1229 1230 heads = [repo[r].node() for r in revs] if revs else None
1230 1231 outgoing = discovery.outgoing(repo, common, heads)
1231 1232 else:
1232 1233 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1233 1234 dest, branches = hg.parseurl(dest, opts.get('branch'))
1234 1235 other = hg.peer(repo, opts, dest)
1235 1236 revs = [repo[r].hex() for r in revs]
1236 1237 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1237 1238 heads = revs and map(repo.lookup, revs) or revs
1238 1239 outgoing = discovery.findcommonoutgoing(repo, other,
1239 1240 onlyheads=heads,
1240 1241 force=opts.get('force'),
1241 1242 portable=True)
1242 1243
1243 1244 if not outgoing.missing:
1244 1245 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1245 1246 return 1
1246 1247
1247 1248 bcompression = bundlespec.compression
1248 1249 if cgversion == '01': #bundle1
1249 1250 if bcompression is None:
1250 1251 bcompression = 'UN'
1251 1252 bversion = 'HG10' + bcompression
1252 1253 bcompression = None
1253 1254 elif cgversion in ('02', '03'):
1254 1255 bversion = 'HG20'
1255 1256 else:
1256 1257 raise error.ProgrammingError(
1257 1258 'bundle: unexpected changegroup version %s' % cgversion)
1258 1259
1259 1260 # TODO compression options should be derived from bundlespec parsing.
1260 1261 # This is a temporary hack to allow adjusting bundle compression
1261 1262 # level without a) formalizing the bundlespec changes to declare it
1262 1263 # b) introducing a command flag.
1263 1264 compopts = {}
1264 1265 complevel = ui.configint('experimental', 'bundlecomplevel')
1265 1266 if complevel is not None:
1266 1267 compopts['level'] = complevel
1267 1268
1268 1269 # Allow overriding the bundling of obsmarker in phases through
1269 1270 # configuration while we don't have a bundle version that include them
1270 1271 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1271 1272 bundlespec.contentopts['obsolescence'] = True
1272 1273 if repo.ui.configbool('experimental', 'bundle-phases'):
1273 1274 bundlespec.contentopts['phases'] = True
1274 1275
1275 1276 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1276 1277 bundlespec.contentopts, compression=bcompression,
1277 1278 compopts=compopts)
1278 1279
1279 1280 @command('cat',
1280 1281 [('o', 'output', '',
1281 1282 _('print output to file with formatted name'), _('FORMAT')),
1282 1283 ('r', 'rev', '', _('print the given revision'), _('REV')),
1283 1284 ('', 'decode', None, _('apply any matching decode filter')),
1284 1285 ] + walkopts + formatteropts,
1285 1286 _('[OPTION]... FILE...'),
1286 1287 inferrepo=True,
1287 1288 intents={INTENT_READONLY})
1288 1289 def cat(ui, repo, file1, *pats, **opts):
1289 1290 """output the current or given revision of files
1290 1291
1291 1292 Print the specified files as they were at the given revision. If
1292 1293 no revision is given, the parent of the working directory is used.
1293 1294
1294 1295 Output may be to a file, in which case the name of the file is
1295 1296 given using a template string. See :hg:`help templates`. In addition
1296 1297 to the common template keywords, the following formatting rules are
1297 1298 supported:
1298 1299
1299 1300 :``%%``: literal "%" character
1300 1301 :``%s``: basename of file being printed
1301 1302 :``%d``: dirname of file being printed, or '.' if in repository root
1302 1303 :``%p``: root-relative path name of file being printed
1303 1304 :``%H``: changeset hash (40 hexadecimal digits)
1304 1305 :``%R``: changeset revision number
1305 1306 :``%h``: short-form changeset hash (12 hexadecimal digits)
1306 1307 :``%r``: zero-padded changeset revision number
1307 1308 :``%b``: basename of the exporting repository
1308 1309 :``\\``: literal "\\" character
1309 1310
1310 1311 Returns 0 on success.
1311 1312 """
1312 1313 opts = pycompat.byteskwargs(opts)
1313 1314 rev = opts.get('rev')
1314 1315 if rev:
1315 1316 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1316 1317 ctx = scmutil.revsingle(repo, rev)
1317 1318 m = scmutil.match(ctx, (file1,) + pats, opts)
1318 1319 fntemplate = opts.pop('output', '')
1319 1320 if cmdutil.isstdiofilename(fntemplate):
1320 1321 fntemplate = ''
1321 1322
1322 1323 if fntemplate:
1323 1324 fm = formatter.nullformatter(ui, 'cat', opts)
1324 1325 else:
1325 1326 ui.pager('cat')
1326 1327 fm = ui.formatter('cat', opts)
1327 1328 with fm:
1328 1329 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1329 1330 **pycompat.strkwargs(opts))
1330 1331
1331 1332 @command('^clone',
1332 1333 [('U', 'noupdate', None, _('the clone will include an empty working '
1333 1334 'directory (only a repository)')),
1334 1335 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1335 1336 _('REV')),
1336 1337 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1337 1338 ' and its ancestors'), _('REV')),
1338 1339 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1339 1340 ' changesets and their ancestors'), _('BRANCH')),
1340 1341 ('', 'pull', None, _('use pull protocol to copy metadata')),
1341 1342 ('', 'uncompressed', None,
1342 1343 _('an alias to --stream (DEPRECATED)')),
1343 1344 ('', 'stream', None,
1344 1345 _('clone with minimal data processing')),
1345 1346 ] + remoteopts,
1346 1347 _('[OPTION]... SOURCE [DEST]'),
1347 1348 norepo=True)
1348 1349 def clone(ui, source, dest=None, **opts):
1349 1350 """make a copy of an existing repository
1350 1351
1351 1352 Create a copy of an existing repository in a new directory.
1352 1353
1353 1354 If no destination directory name is specified, it defaults to the
1354 1355 basename of the source.
1355 1356
1356 1357 The location of the source is added to the new repository's
1357 1358 ``.hg/hgrc`` file, as the default to be used for future pulls.
1358 1359
1359 1360 Only local paths and ``ssh://`` URLs are supported as
1360 1361 destinations. For ``ssh://`` destinations, no working directory or
1361 1362 ``.hg/hgrc`` will be created on the remote side.
1362 1363
1363 1364 If the source repository has a bookmark called '@' set, that
1364 1365 revision will be checked out in the new repository by default.
1365 1366
1366 1367 To check out a particular version, use -u/--update, or
1367 1368 -U/--noupdate to create a clone with no working directory.
1368 1369
1369 1370 To pull only a subset of changesets, specify one or more revisions
1370 1371 identifiers with -r/--rev or branches with -b/--branch. The
1371 1372 resulting clone will contain only the specified changesets and
1372 1373 their ancestors. These options (or 'clone src#rev dest') imply
1373 1374 --pull, even for local source repositories.
1374 1375
1375 1376 In normal clone mode, the remote normalizes repository data into a common
1376 1377 exchange format and the receiving end translates this data into its local
1377 1378 storage format. --stream activates a different clone mode that essentially
1378 1379 copies repository files from the remote with minimal data processing. This
1379 1380 significantly reduces the CPU cost of a clone both remotely and locally.
1380 1381 However, it often increases the transferred data size by 30-40%. This can
1381 1382 result in substantially faster clones where I/O throughput is plentiful,
1382 1383 especially for larger repositories. A side-effect of --stream clones is
1383 1384 that storage settings and requirements on the remote are applied locally:
1384 1385 a modern client may inherit legacy or inefficient storage used by the
1385 1386 remote or a legacy Mercurial client may not be able to clone from a
1386 1387 modern Mercurial remote.
1387 1388
1388 1389 .. note::
1389 1390
1390 1391 Specifying a tag will include the tagged changeset but not the
1391 1392 changeset containing the tag.
1392 1393
1393 1394 .. container:: verbose
1394 1395
1395 1396 For efficiency, hardlinks are used for cloning whenever the
1396 1397 source and destination are on the same filesystem (note this
1397 1398 applies only to the repository data, not to the working
1398 1399 directory). Some filesystems, such as AFS, implement hardlinking
1399 1400 incorrectly, but do not report errors. In these cases, use the
1400 1401 --pull option to avoid hardlinking.
1401 1402
1402 1403 Mercurial will update the working directory to the first applicable
1403 1404 revision from this list:
1404 1405
1405 1406 a) null if -U or the source repository has no changesets
1406 1407 b) if -u . and the source repository is local, the first parent of
1407 1408 the source repository's working directory
1408 1409 c) the changeset specified with -u (if a branch name, this means the
1409 1410 latest head of that branch)
1410 1411 d) the changeset specified with -r
1411 1412 e) the tipmost head specified with -b
1412 1413 f) the tipmost head specified with the url#branch source syntax
1413 1414 g) the revision marked with the '@' bookmark, if present
1414 1415 h) the tipmost head of the default branch
1415 1416 i) tip
1416 1417
1417 1418 When cloning from servers that support it, Mercurial may fetch
1418 1419 pre-generated data from a server-advertised URL or inline from the
1419 1420 same stream. When this is done, hooks operating on incoming changesets
1420 1421 and changegroups may fire more than once, once for each pre-generated
1421 1422 bundle and as well as for any additional remaining data. In addition,
1422 1423 if an error occurs, the repository may be rolled back to a partial
1423 1424 clone. This behavior may change in future releases.
1424 1425 See :hg:`help -e clonebundles` for more.
1425 1426
1426 1427 Examples:
1427 1428
1428 1429 - clone a remote repository to a new directory named hg/::
1429 1430
1430 1431 hg clone https://www.mercurial-scm.org/repo/hg/
1431 1432
1432 1433 - create a lightweight local clone::
1433 1434
1434 1435 hg clone project/ project-feature/
1435 1436
1436 1437 - clone from an absolute path on an ssh server (note double-slash)::
1437 1438
1438 1439 hg clone ssh://user@server//home/projects/alpha/
1439 1440
1440 1441 - do a streaming clone while checking out a specified version::
1441 1442
1442 1443 hg clone --stream http://server/repo -u 1.5
1443 1444
1444 1445 - create a repository without changesets after a particular revision::
1445 1446
1446 1447 hg clone -r 04e544 experimental/ good/
1447 1448
1448 1449 - clone (and track) a particular named branch::
1449 1450
1450 1451 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1451 1452
1452 1453 See :hg:`help urls` for details on specifying URLs.
1453 1454
1454 1455 Returns 0 on success.
1455 1456 """
1456 1457 opts = pycompat.byteskwargs(opts)
1457 1458 if opts.get('noupdate') and opts.get('updaterev'):
1458 1459 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1459 1460
1460 1461 r = hg.clone(ui, opts, source, dest,
1461 1462 pull=opts.get('pull'),
1462 1463 stream=opts.get('stream') or opts.get('uncompressed'),
1463 1464 revs=opts.get('rev'),
1464 1465 update=opts.get('updaterev') or not opts.get('noupdate'),
1465 1466 branch=opts.get('branch'),
1466 1467 shareopts=opts.get('shareopts'))
1467 1468
1468 1469 return r is None
1469 1470
1470 1471 @command('^commit|ci',
1471 1472 [('A', 'addremove', None,
1472 1473 _('mark new/missing files as added/removed before committing')),
1473 1474 ('', 'close-branch', None,
1474 1475 _('mark a branch head as closed')),
1475 1476 ('', 'amend', None, _('amend the parent of the working directory')),
1476 1477 ('s', 'secret', None, _('use the secret phase for committing')),
1477 1478 ('e', 'edit', None, _('invoke editor on commit messages')),
1478 1479 ('i', 'interactive', None, _('use interactive mode')),
1479 1480 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1480 1481 _('[OPTION]... [FILE]...'),
1481 1482 inferrepo=True)
1482 1483 def commit(ui, repo, *pats, **opts):
1483 1484 """commit the specified files or all outstanding changes
1484 1485
1485 1486 Commit changes to the given files into the repository. Unlike a
1486 1487 centralized SCM, this operation is a local operation. See
1487 1488 :hg:`push` for a way to actively distribute your changes.
1488 1489
1489 1490 If a list of files is omitted, all changes reported by :hg:`status`
1490 1491 will be committed.
1491 1492
1492 1493 If you are committing the result of a merge, do not provide any
1493 1494 filenames or -I/-X filters.
1494 1495
1495 1496 If no commit message is specified, Mercurial starts your
1496 1497 configured editor where you can enter a message. In case your
1497 1498 commit fails, you will find a backup of your message in
1498 1499 ``.hg/last-message.txt``.
1499 1500
1500 1501 The --close-branch flag can be used to mark the current branch
1501 1502 head closed. When all heads of a branch are closed, the branch
1502 1503 will be considered closed and no longer listed.
1503 1504
1504 1505 The --amend flag can be used to amend the parent of the
1505 1506 working directory with a new commit that contains the changes
1506 1507 in the parent in addition to those currently reported by :hg:`status`,
1507 1508 if there are any. The old commit is stored in a backup bundle in
1508 1509 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1509 1510 on how to restore it).
1510 1511
1511 1512 Message, user and date are taken from the amended commit unless
1512 1513 specified. When a message isn't specified on the command line,
1513 1514 the editor will open with the message of the amended commit.
1514 1515
1515 1516 It is not possible to amend public changesets (see :hg:`help phases`)
1516 1517 or changesets that have children.
1517 1518
1518 1519 See :hg:`help dates` for a list of formats valid for -d/--date.
1519 1520
1520 1521 Returns 0 on success, 1 if nothing changed.
1521 1522
1522 1523 .. container:: verbose
1523 1524
1524 1525 Examples:
1525 1526
1526 1527 - commit all files ending in .py::
1527 1528
1528 1529 hg commit --include "set:**.py"
1529 1530
1530 1531 - commit all non-binary files::
1531 1532
1532 1533 hg commit --exclude "set:binary()"
1533 1534
1534 1535 - amend the current commit and set the date to now::
1535 1536
1536 1537 hg commit --amend --date now
1537 1538 """
1538 1539 wlock = lock = None
1539 1540 try:
1540 1541 wlock = repo.wlock()
1541 1542 lock = repo.lock()
1542 1543 return _docommit(ui, repo, *pats, **opts)
1543 1544 finally:
1544 1545 release(lock, wlock)
1545 1546
1546 1547 def _docommit(ui, repo, *pats, **opts):
1547 1548 if opts.get(r'interactive'):
1548 1549 opts.pop(r'interactive')
1549 1550 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1550 1551 cmdutil.recordfilter, *pats,
1551 1552 **opts)
1552 1553 # ret can be 0 (no changes to record) or the value returned by
1553 1554 # commit(), 1 if nothing changed or None on success.
1554 1555 return 1 if ret == 0 else ret
1555 1556
1556 1557 opts = pycompat.byteskwargs(opts)
1557 1558 if opts.get('subrepos'):
1558 1559 if opts.get('amend'):
1559 1560 raise error.Abort(_('cannot amend with --subrepos'))
1560 1561 # Let --subrepos on the command line override config setting.
1561 1562 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1562 1563
1563 1564 cmdutil.checkunfinished(repo, commit=True)
1564 1565
1565 1566 branch = repo[None].branch()
1566 1567 bheads = repo.branchheads(branch)
1567 1568
1568 1569 extra = {}
1569 1570 if opts.get('close_branch'):
1570 1571 extra['close'] = '1'
1571 1572
1572 1573 if not bheads:
1573 1574 raise error.Abort(_('can only close branch heads'))
1574 1575 elif opts.get('amend'):
1575 1576 if repo[None].parents()[0].p1().branch() != branch and \
1576 1577 repo[None].parents()[0].p2().branch() != branch:
1577 1578 raise error.Abort(_('can only close branch heads'))
1578 1579
1579 1580 if opts.get('amend'):
1580 1581 if ui.configbool('ui', 'commitsubrepos'):
1581 1582 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1582 1583
1583 1584 old = repo['.']
1584 1585 rewriteutil.precheck(repo, [old.rev()], 'amend')
1585 1586
1586 1587 # Currently histedit gets confused if an amend happens while histedit
1587 1588 # is in progress. Since we have a checkunfinished command, we are
1588 1589 # temporarily honoring it.
1589 1590 #
1590 1591 # Note: eventually this guard will be removed. Please do not expect
1591 1592 # this behavior to remain.
1592 1593 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1593 1594 cmdutil.checkunfinished(repo)
1594 1595
1595 1596 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1596 1597 if node == old.node():
1597 1598 ui.status(_("nothing changed\n"))
1598 1599 return 1
1599 1600 else:
1600 1601 def commitfunc(ui, repo, message, match, opts):
1601 1602 overrides = {}
1602 1603 if opts.get('secret'):
1603 1604 overrides[('phases', 'new-commit')] = 'secret'
1604 1605
1605 1606 baseui = repo.baseui
1606 1607 with baseui.configoverride(overrides, 'commit'):
1607 1608 with ui.configoverride(overrides, 'commit'):
1608 1609 editform = cmdutil.mergeeditform(repo[None],
1609 1610 'commit.normal')
1610 1611 editor = cmdutil.getcommiteditor(
1611 1612 editform=editform, **pycompat.strkwargs(opts))
1612 1613 return repo.commit(message,
1613 1614 opts.get('user'),
1614 1615 opts.get('date'),
1615 1616 match,
1616 1617 editor=editor,
1617 1618 extra=extra)
1618 1619
1619 1620 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1620 1621
1621 1622 if not node:
1622 1623 stat = cmdutil.postcommitstatus(repo, pats, opts)
1623 1624 if stat[3]:
1624 1625 ui.status(_("nothing changed (%d missing files, see "
1625 1626 "'hg status')\n") % len(stat[3]))
1626 1627 else:
1627 1628 ui.status(_("nothing changed\n"))
1628 1629 return 1
1629 1630
1630 1631 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1631 1632
1632 1633 @command('config|showconfig|debugconfig',
1633 1634 [('u', 'untrusted', None, _('show untrusted configuration options')),
1634 1635 ('e', 'edit', None, _('edit user config')),
1635 1636 ('l', 'local', None, _('edit repository config')),
1636 1637 ('g', 'global', None, _('edit global config'))] + formatteropts,
1637 1638 _('[-u] [NAME]...'),
1638 1639 optionalrepo=True,
1639 1640 intents={INTENT_READONLY})
1640 1641 def config(ui, repo, *values, **opts):
1641 1642 """show combined config settings from all hgrc files
1642 1643
1643 1644 With no arguments, print names and values of all config items.
1644 1645
1645 1646 With one argument of the form section.name, print just the value
1646 1647 of that config item.
1647 1648
1648 1649 With multiple arguments, print names and values of all config
1649 1650 items with matching section names or section.names.
1650 1651
1651 1652 With --edit, start an editor on the user-level config file. With
1652 1653 --global, edit the system-wide config file. With --local, edit the
1653 1654 repository-level config file.
1654 1655
1655 1656 With --debug, the source (filename and line number) is printed
1656 1657 for each config item.
1657 1658
1658 1659 See :hg:`help config` for more information about config files.
1659 1660
1660 1661 Returns 0 on success, 1 if NAME does not exist.
1661 1662
1662 1663 """
1663 1664
1664 1665 opts = pycompat.byteskwargs(opts)
1665 1666 if opts.get('edit') or opts.get('local') or opts.get('global'):
1666 1667 if opts.get('local') and opts.get('global'):
1667 1668 raise error.Abort(_("can't use --local and --global together"))
1668 1669
1669 1670 if opts.get('local'):
1670 1671 if not repo:
1671 1672 raise error.Abort(_("can't use --local outside a repository"))
1672 1673 paths = [repo.vfs.join('hgrc')]
1673 1674 elif opts.get('global'):
1674 1675 paths = rcutil.systemrcpath()
1675 1676 else:
1676 1677 paths = rcutil.userrcpath()
1677 1678
1678 1679 for f in paths:
1679 1680 if os.path.exists(f):
1680 1681 break
1681 1682 else:
1682 1683 if opts.get('global'):
1683 1684 samplehgrc = uimod.samplehgrcs['global']
1684 1685 elif opts.get('local'):
1685 1686 samplehgrc = uimod.samplehgrcs['local']
1686 1687 else:
1687 1688 samplehgrc = uimod.samplehgrcs['user']
1688 1689
1689 1690 f = paths[0]
1690 1691 fp = open(f, "wb")
1691 1692 fp.write(util.tonativeeol(samplehgrc))
1692 1693 fp.close()
1693 1694
1694 1695 editor = ui.geteditor()
1695 1696 ui.system("%s \"%s\"" % (editor, f),
1696 1697 onerr=error.Abort, errprefix=_("edit failed"),
1697 1698 blockedtag='config_edit')
1698 1699 return
1699 1700 ui.pager('config')
1700 1701 fm = ui.formatter('config', opts)
1701 1702 for t, f in rcutil.rccomponents():
1702 1703 if t == 'path':
1703 1704 ui.debug('read config from: %s\n' % f)
1704 1705 elif t == 'items':
1705 1706 for section, name, value, source in f:
1706 1707 ui.debug('set config by: %s\n' % source)
1707 1708 else:
1708 1709 raise error.ProgrammingError('unknown rctype: %s' % t)
1709 1710 untrusted = bool(opts.get('untrusted'))
1710 1711
1711 1712 selsections = selentries = []
1712 1713 if values:
1713 1714 selsections = [v for v in values if '.' not in v]
1714 1715 selentries = [v for v in values if '.' in v]
1715 1716 uniquesel = (len(selentries) == 1 and not selsections)
1716 1717 selsections = set(selsections)
1717 1718 selentries = set(selentries)
1718 1719
1719 1720 matched = False
1720 1721 for section, name, value in ui.walkconfig(untrusted=untrusted):
1721 1722 source = ui.configsource(section, name, untrusted)
1722 1723 value = pycompat.bytestr(value)
1723 1724 if fm.isplain():
1724 1725 source = source or 'none'
1725 1726 value = value.replace('\n', '\\n')
1726 1727 entryname = section + '.' + name
1727 1728 if values and not (section in selsections or entryname in selentries):
1728 1729 continue
1729 1730 fm.startitem()
1730 1731 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1731 1732 if uniquesel:
1732 1733 fm.data(name=entryname)
1733 1734 fm.write('value', '%s\n', value)
1734 1735 else:
1735 1736 fm.write('name value', '%s=%s\n', entryname, value)
1736 1737 matched = True
1737 1738 fm.end()
1738 1739 if matched:
1739 1740 return 0
1740 1741 return 1
1741 1742
1742 1743 @command('copy|cp',
1743 1744 [('A', 'after', None, _('record a copy that has already occurred')),
1744 1745 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1745 1746 ] + walkopts + dryrunopts,
1746 1747 _('[OPTION]... [SOURCE]... DEST'))
1747 1748 def copy(ui, repo, *pats, **opts):
1748 1749 """mark files as copied for the next commit
1749 1750
1750 1751 Mark dest as having copies of source files. If dest is a
1751 1752 directory, copies are put in that directory. If dest is a file,
1752 1753 the source must be a single file.
1753 1754
1754 1755 By default, this command copies the contents of files as they
1755 1756 exist in the working directory. If invoked with -A/--after, the
1756 1757 operation is recorded, but no copying is performed.
1757 1758
1758 1759 This command takes effect with the next commit. To undo a copy
1759 1760 before that, see :hg:`revert`.
1760 1761
1761 1762 Returns 0 on success, 1 if errors are encountered.
1762 1763 """
1763 1764 opts = pycompat.byteskwargs(opts)
1764 1765 with repo.wlock(False):
1765 1766 return cmdutil.copy(ui, repo, pats, opts)
1766 1767
1767 1768 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1768 1769 def debugcommands(ui, cmd='', *args):
1769 1770 """list all available commands and options"""
1770 1771 for cmd, vals in sorted(table.iteritems()):
1771 1772 cmd = cmd.split('|')[0].strip('^')
1772 1773 opts = ', '.join([i[1] for i in vals[1]])
1773 1774 ui.write('%s: %s\n' % (cmd, opts))
1774 1775
1775 1776 @command('debugcomplete',
1776 1777 [('o', 'options', None, _('show the command options'))],
1777 1778 _('[-o] CMD'),
1778 1779 norepo=True)
1779 1780 def debugcomplete(ui, cmd='', **opts):
1780 1781 """returns the completion list associated with the given command"""
1781 1782
1782 1783 if opts.get(r'options'):
1783 1784 options = []
1784 1785 otables = [globalopts]
1785 1786 if cmd:
1786 1787 aliases, entry = cmdutil.findcmd(cmd, table, False)
1787 1788 otables.append(entry[1])
1788 1789 for t in otables:
1789 1790 for o in t:
1790 1791 if "(DEPRECATED)" in o[3]:
1791 1792 continue
1792 1793 if o[0]:
1793 1794 options.append('-%s' % o[0])
1794 1795 options.append('--%s' % o[1])
1795 1796 ui.write("%s\n" % "\n".join(options))
1796 1797 return
1797 1798
1798 1799 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1799 1800 if ui.verbose:
1800 1801 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1801 1802 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1802 1803
1803 1804 @command('^diff',
1804 1805 [('r', 'rev', [], _('revision'), _('REV')),
1805 1806 ('c', 'change', '', _('change made by revision'), _('REV'))
1806 1807 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1807 1808 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1808 1809 inferrepo=True,
1809 1810 intents={INTENT_READONLY})
1810 1811 def diff(ui, repo, *pats, **opts):
1811 1812 """diff repository (or selected files)
1812 1813
1813 1814 Show differences between revisions for the specified files.
1814 1815
1815 1816 Differences between files are shown using the unified diff format.
1816 1817
1817 1818 .. note::
1818 1819
1819 1820 :hg:`diff` may generate unexpected results for merges, as it will
1820 1821 default to comparing against the working directory's first
1821 1822 parent changeset if no revisions are specified.
1822 1823
1823 1824 When two revision arguments are given, then changes are shown
1824 1825 between those revisions. If only one revision is specified then
1825 1826 that revision is compared to the working directory, and, when no
1826 1827 revisions are specified, the working directory files are compared
1827 1828 to its first parent.
1828 1829
1829 1830 Alternatively you can specify -c/--change with a revision to see
1830 1831 the changes in that changeset relative to its first parent.
1831 1832
1832 1833 Without the -a/--text option, diff will avoid generating diffs of
1833 1834 files it detects as binary. With -a, diff will generate a diff
1834 1835 anyway, probably with undesirable results.
1835 1836
1836 1837 Use the -g/--git option to generate diffs in the git extended diff
1837 1838 format. For more information, read :hg:`help diffs`.
1838 1839
1839 1840 .. container:: verbose
1840 1841
1841 1842 Examples:
1842 1843
1843 1844 - compare a file in the current working directory to its parent::
1844 1845
1845 1846 hg diff foo.c
1846 1847
1847 1848 - compare two historical versions of a directory, with rename info::
1848 1849
1849 1850 hg diff --git -r 1.0:1.2 lib/
1850 1851
1851 1852 - get change stats relative to the last change on some date::
1852 1853
1853 1854 hg diff --stat -r "date('may 2')"
1854 1855
1855 1856 - diff all newly-added files that contain a keyword::
1856 1857
1857 1858 hg diff "set:added() and grep(GNU)"
1858 1859
1859 1860 - compare a revision and its parents::
1860 1861
1861 1862 hg diff -c 9353 # compare against first parent
1862 1863 hg diff -r 9353^:9353 # same using revset syntax
1863 1864 hg diff -r 9353^2:9353 # compare against the second parent
1864 1865
1865 1866 Returns 0 on success.
1866 1867 """
1867 1868
1868 1869 opts = pycompat.byteskwargs(opts)
1869 1870 revs = opts.get('rev')
1870 1871 change = opts.get('change')
1871 1872 stat = opts.get('stat')
1872 1873 reverse = opts.get('reverse')
1873 1874
1874 1875 if revs and change:
1875 1876 msg = _('cannot specify --rev and --change at the same time')
1876 1877 raise error.Abort(msg)
1877 1878 elif change:
1878 1879 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1879 1880 ctx2 = scmutil.revsingle(repo, change, None)
1880 1881 ctx1 = ctx2.p1()
1881 1882 else:
1882 1883 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
1883 1884 ctx1, ctx2 = scmutil.revpair(repo, revs)
1884 1885 node1, node2 = ctx1.node(), ctx2.node()
1885 1886
1886 1887 if reverse:
1887 1888 node1, node2 = node2, node1
1888 1889
1889 1890 diffopts = patch.diffallopts(ui, opts)
1890 1891 m = scmutil.match(ctx2, pats, opts)
1891 1892 ui.pager('diff')
1892 1893 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1893 1894 listsubrepos=opts.get('subrepos'),
1894 1895 root=opts.get('root'))
1895 1896
1896 1897 @command('^export',
1897 1898 [('o', 'output', '',
1898 1899 _('print output to file with formatted name'), _('FORMAT')),
1899 1900 ('', 'switch-parent', None, _('diff against the second parent')),
1900 1901 ('r', 'rev', [], _('revisions to export'), _('REV')),
1901 1902 ] + diffopts + formatteropts,
1902 1903 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
1903 1904 intents={INTENT_READONLY})
1904 1905 def export(ui, repo, *changesets, **opts):
1905 1906 """dump the header and diffs for one or more changesets
1906 1907
1907 1908 Print the changeset header and diffs for one or more revisions.
1908 1909 If no revision is given, the parent of the working directory is used.
1909 1910
1910 1911 The information shown in the changeset header is: author, date,
1911 1912 branch name (if non-default), changeset hash, parent(s) and commit
1912 1913 comment.
1913 1914
1914 1915 .. note::
1915 1916
1916 1917 :hg:`export` may generate unexpected diff output for merge
1917 1918 changesets, as it will compare the merge changeset against its
1918 1919 first parent only.
1919 1920
1920 1921 Output may be to a file, in which case the name of the file is
1921 1922 given using a template string. See :hg:`help templates`. In addition
1922 1923 to the common template keywords, the following formatting rules are
1923 1924 supported:
1924 1925
1925 1926 :``%%``: literal "%" character
1926 1927 :``%H``: changeset hash (40 hexadecimal digits)
1927 1928 :``%N``: number of patches being generated
1928 1929 :``%R``: changeset revision number
1929 1930 :``%b``: basename of the exporting repository
1930 1931 :``%h``: short-form changeset hash (12 hexadecimal digits)
1931 1932 :``%m``: first line of the commit message (only alphanumeric characters)
1932 1933 :``%n``: zero-padded sequence number, starting at 1
1933 1934 :``%r``: zero-padded changeset revision number
1934 1935 :``\\``: literal "\\" character
1935 1936
1936 1937 Without the -a/--text option, export will avoid generating diffs
1937 1938 of files it detects as binary. With -a, export will generate a
1938 1939 diff anyway, probably with undesirable results.
1939 1940
1940 1941 Use the -g/--git option to generate diffs in the git extended diff
1941 1942 format. See :hg:`help diffs` for more information.
1942 1943
1943 1944 With the --switch-parent option, the diff will be against the
1944 1945 second parent. It can be useful to review a merge.
1945 1946
1946 1947 .. container:: verbose
1947 1948
1948 1949 Examples:
1949 1950
1950 1951 - use export and import to transplant a bugfix to the current
1951 1952 branch::
1952 1953
1953 1954 hg export -r 9353 | hg import -
1954 1955
1955 1956 - export all the changesets between two revisions to a file with
1956 1957 rename information::
1957 1958
1958 1959 hg export --git -r 123:150 > changes.txt
1959 1960
1960 1961 - split outgoing changes into a series of patches with
1961 1962 descriptive names::
1962 1963
1963 1964 hg export -r "outgoing()" -o "%n-%m.patch"
1964 1965
1965 1966 Returns 0 on success.
1966 1967 """
1967 1968 opts = pycompat.byteskwargs(opts)
1968 1969 changesets += tuple(opts.get('rev', []))
1969 1970 if not changesets:
1970 1971 changesets = ['.']
1971 1972 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
1972 1973 revs = scmutil.revrange(repo, changesets)
1973 1974 if not revs:
1974 1975 raise error.Abort(_("export requires at least one changeset"))
1975 1976 if len(revs) > 1:
1976 1977 ui.note(_('exporting patches:\n'))
1977 1978 else:
1978 1979 ui.note(_('exporting patch:\n'))
1979 1980
1980 1981 fntemplate = opts.get('output')
1981 1982 if cmdutil.isstdiofilename(fntemplate):
1982 1983 fntemplate = ''
1983 1984
1984 1985 if fntemplate:
1985 1986 fm = formatter.nullformatter(ui, 'export', opts)
1986 1987 else:
1987 1988 ui.pager('export')
1988 1989 fm = ui.formatter('export', opts)
1989 1990 with fm:
1990 1991 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
1991 1992 switch_parent=opts.get('switch_parent'),
1992 1993 opts=patch.diffallopts(ui, opts))
1993 1994
1994 1995 @command('files',
1995 1996 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
1996 1997 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
1997 1998 ] + walkopts + formatteropts + subrepoopts,
1998 1999 _('[OPTION]... [FILE]...'),
1999 2000 intents={INTENT_READONLY})
2000 2001 def files(ui, repo, *pats, **opts):
2001 2002 """list tracked files
2002 2003
2003 2004 Print files under Mercurial control in the working directory or
2004 2005 specified revision for given files (excluding removed files).
2005 2006 Files can be specified as filenames or filesets.
2006 2007
2007 2008 If no files are given to match, this command prints the names
2008 2009 of all files under Mercurial control.
2009 2010
2010 2011 .. container:: verbose
2011 2012
2012 2013 Examples:
2013 2014
2014 2015 - list all files under the current directory::
2015 2016
2016 2017 hg files .
2017 2018
2018 2019 - shows sizes and flags for current revision::
2019 2020
2020 2021 hg files -vr .
2021 2022
2022 2023 - list all files named README::
2023 2024
2024 2025 hg files -I "**/README"
2025 2026
2026 2027 - list all binary files::
2027 2028
2028 2029 hg files "set:binary()"
2029 2030
2030 2031 - find files containing a regular expression::
2031 2032
2032 2033 hg files "set:grep('bob')"
2033 2034
2034 2035 - search tracked file contents with xargs and grep::
2035 2036
2036 2037 hg files -0 | xargs -0 grep foo
2037 2038
2038 2039 See :hg:`help patterns` and :hg:`help filesets` for more information
2039 2040 on specifying file patterns.
2040 2041
2041 2042 Returns 0 if a match is found, 1 otherwise.
2042 2043
2043 2044 """
2044 2045
2045 2046 opts = pycompat.byteskwargs(opts)
2046 2047 rev = opts.get('rev')
2047 2048 if rev:
2048 2049 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2049 2050 ctx = scmutil.revsingle(repo, rev, None)
2050 2051
2051 2052 end = '\n'
2052 2053 if opts.get('print0'):
2053 2054 end = '\0'
2054 2055 fmt = '%s' + end
2055 2056
2056 2057 m = scmutil.match(ctx, pats, opts)
2057 2058 ui.pager('files')
2058 2059 with ui.formatter('files', opts) as fm:
2059 2060 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2060 2061
2061 2062 @command(
2062 2063 '^forget',
2063 walkopts + dryrunopts,
2064 walkopts + dryrunopts + confirmopts,
2064 2065 _('[OPTION]... FILE...'), inferrepo=True)
2065 2066 def forget(ui, repo, *pats, **opts):
2066 2067 """forget the specified files on the next commit
2067 2068
2068 2069 Mark the specified files so they will no longer be tracked
2069 2070 after the next commit.
2070 2071
2071 2072 This only removes files from the current branch, not from the
2072 2073 entire project history, and it does not delete them from the
2073 2074 working directory.
2074 2075
2075 2076 To delete the file from the working directory, see :hg:`remove`.
2076 2077
2077 2078 To undo a forget before the next commit, see :hg:`add`.
2078 2079
2079 2080 .. container:: verbose
2080 2081
2081 2082 Examples:
2082 2083
2083 2084 - forget newly-added binary files::
2084 2085
2085 2086 hg forget "set:added() and binary()"
2086 2087
2087 2088 - forget files that would be excluded by .hgignore::
2088 2089
2089 2090 hg forget "set:hgignore()"
2090 2091
2091 2092 Returns 0 on success.
2092 2093 """
2093 2094
2094 2095 opts = pycompat.byteskwargs(opts)
2095 2096 if not pats:
2096 2097 raise error.Abort(_('no files specified'))
2097 2098
2098 2099 m = scmutil.match(repo[None], pats, opts)
2099 dryrun = opts.get('dry_run')
2100 dryrun, confirm = opts.get('dry_run'), opts.get('confirm')
2100 2101 rejected = cmdutil.forget(ui, repo, m, prefix="",
2101 explicitonly=False, dryrun=dryrun)[0]
2102 explicitonly=False, dryrun=dryrun,
2103 confirm=confirm)[0]
2102 2104 return rejected and 1 or 0
2103 2105
2104 2106 @command(
2105 2107 'graft',
2106 2108 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2107 2109 ('c', 'continue', False, _('resume interrupted graft')),
2108 2110 ('e', 'edit', False, _('invoke editor on commit messages')),
2109 2111 ('', 'log', None, _('append graft info to log message')),
2110 2112 ('f', 'force', False, _('force graft')),
2111 2113 ('D', 'currentdate', False,
2112 2114 _('record the current date as commit date')),
2113 2115 ('U', 'currentuser', False,
2114 2116 _('record the current user as committer'), _('DATE'))]
2115 2117 + commitopts2 + mergetoolopts + dryrunopts,
2116 2118 _('[OPTION]... [-r REV]... REV...'))
2117 2119 def graft(ui, repo, *revs, **opts):
2118 2120 '''copy changes from other branches onto the current branch
2119 2121
2120 2122 This command uses Mercurial's merge logic to copy individual
2121 2123 changes from other branches without merging branches in the
2122 2124 history graph. This is sometimes known as 'backporting' or
2123 2125 'cherry-picking'. By default, graft will copy user, date, and
2124 2126 description from the source changesets.
2125 2127
2126 2128 Changesets that are ancestors of the current revision, that have
2127 2129 already been grafted, or that are merges will be skipped.
2128 2130
2129 2131 If --log is specified, log messages will have a comment appended
2130 2132 of the form::
2131 2133
2132 2134 (grafted from CHANGESETHASH)
2133 2135
2134 2136 If --force is specified, revisions will be grafted even if they
2135 2137 are already ancestors of, or have been grafted to, the destination.
2136 2138 This is useful when the revisions have since been backed out.
2137 2139
2138 2140 If a graft merge results in conflicts, the graft process is
2139 2141 interrupted so that the current merge can be manually resolved.
2140 2142 Once all conflicts are addressed, the graft process can be
2141 2143 continued with the -c/--continue option.
2142 2144
2143 2145 .. note::
2144 2146
2145 2147 The -c/--continue option does not reapply earlier options, except
2146 2148 for --force.
2147 2149
2148 2150 .. container:: verbose
2149 2151
2150 2152 Examples:
2151 2153
2152 2154 - copy a single change to the stable branch and edit its description::
2153 2155
2154 2156 hg update stable
2155 2157 hg graft --edit 9393
2156 2158
2157 2159 - graft a range of changesets with one exception, updating dates::
2158 2160
2159 2161 hg graft -D "2085::2093 and not 2091"
2160 2162
2161 2163 - continue a graft after resolving conflicts::
2162 2164
2163 2165 hg graft -c
2164 2166
2165 2167 - show the source of a grafted changeset::
2166 2168
2167 2169 hg log --debug -r .
2168 2170
2169 2171 - show revisions sorted by date::
2170 2172
2171 2173 hg log -r "sort(all(), date)"
2172 2174
2173 2175 See :hg:`help revisions` for more about specifying revisions.
2174 2176
2175 2177 Returns 0 on successful completion.
2176 2178 '''
2177 2179 with repo.wlock():
2178 2180 return _dograft(ui, repo, *revs, **opts)
2179 2181
2180 2182 def _dograft(ui, repo, *revs, **opts):
2181 2183 opts = pycompat.byteskwargs(opts)
2182 2184 if revs and opts.get('rev'):
2183 2185 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2184 2186 'revision ordering!\n'))
2185 2187
2186 2188 revs = list(revs)
2187 2189 revs.extend(opts.get('rev'))
2188 2190
2189 2191 if not opts.get('user') and opts.get('currentuser'):
2190 2192 opts['user'] = ui.username()
2191 2193 if not opts.get('date') and opts.get('currentdate'):
2192 2194 opts['date'] = "%d %d" % dateutil.makedate()
2193 2195
2194 2196 editor = cmdutil.getcommiteditor(editform='graft',
2195 2197 **pycompat.strkwargs(opts))
2196 2198
2197 2199 cont = False
2198 2200 if opts.get('continue'):
2199 2201 cont = True
2200 2202 if revs:
2201 2203 raise error.Abort(_("can't specify --continue and revisions"))
2202 2204 # read in unfinished revisions
2203 2205 try:
2204 2206 nodes = repo.vfs.read('graftstate').splitlines()
2205 2207 revs = [repo[node].rev() for node in nodes]
2206 2208 except IOError as inst:
2207 2209 if inst.errno != errno.ENOENT:
2208 2210 raise
2209 2211 cmdutil.wrongtooltocontinue(repo, _('graft'))
2210 2212 else:
2211 2213 if not revs:
2212 2214 raise error.Abort(_('no revisions specified'))
2213 2215 cmdutil.checkunfinished(repo)
2214 2216 cmdutil.bailifchanged(repo)
2215 2217 revs = scmutil.revrange(repo, revs)
2216 2218
2217 2219 skipped = set()
2218 2220 # check for merges
2219 2221 for rev in repo.revs('%ld and merge()', revs):
2220 2222 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2221 2223 skipped.add(rev)
2222 2224 revs = [r for r in revs if r not in skipped]
2223 2225 if not revs:
2224 2226 return -1
2225 2227
2226 2228 # Don't check in the --continue case, in effect retaining --force across
2227 2229 # --continues. That's because without --force, any revisions we decided to
2228 2230 # skip would have been filtered out here, so they wouldn't have made their
2229 2231 # way to the graftstate. With --force, any revisions we would have otherwise
2230 2232 # skipped would not have been filtered out, and if they hadn't been applied
2231 2233 # already, they'd have been in the graftstate.
2232 2234 if not (cont or opts.get('force')):
2233 2235 # check for ancestors of dest branch
2234 2236 crev = repo['.'].rev()
2235 2237 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2236 2238 # XXX make this lazy in the future
2237 2239 # don't mutate while iterating, create a copy
2238 2240 for rev in list(revs):
2239 2241 if rev in ancestors:
2240 2242 ui.warn(_('skipping ancestor revision %d:%s\n') %
2241 2243 (rev, repo[rev]))
2242 2244 # XXX remove on list is slow
2243 2245 revs.remove(rev)
2244 2246 if not revs:
2245 2247 return -1
2246 2248
2247 2249 # analyze revs for earlier grafts
2248 2250 ids = {}
2249 2251 for ctx in repo.set("%ld", revs):
2250 2252 ids[ctx.hex()] = ctx.rev()
2251 2253 n = ctx.extra().get('source')
2252 2254 if n:
2253 2255 ids[n] = ctx.rev()
2254 2256
2255 2257 # check ancestors for earlier grafts
2256 2258 ui.debug('scanning for duplicate grafts\n')
2257 2259
2258 2260 # The only changesets we can be sure doesn't contain grafts of any
2259 2261 # revs, are the ones that are common ancestors of *all* revs:
2260 2262 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2261 2263 ctx = repo[rev]
2262 2264 n = ctx.extra().get('source')
2263 2265 if n in ids:
2264 2266 try:
2265 2267 r = repo[n].rev()
2266 2268 except error.RepoLookupError:
2267 2269 r = None
2268 2270 if r in revs:
2269 2271 ui.warn(_('skipping revision %d:%s '
2270 2272 '(already grafted to %d:%s)\n')
2271 2273 % (r, repo[r], rev, ctx))
2272 2274 revs.remove(r)
2273 2275 elif ids[n] in revs:
2274 2276 if r is None:
2275 2277 ui.warn(_('skipping already grafted revision %d:%s '
2276 2278 '(%d:%s also has unknown origin %s)\n')
2277 2279 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2278 2280 else:
2279 2281 ui.warn(_('skipping already grafted revision %d:%s '
2280 2282 '(%d:%s also has origin %d:%s)\n')
2281 2283 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2282 2284 revs.remove(ids[n])
2283 2285 elif ctx.hex() in ids:
2284 2286 r = ids[ctx.hex()]
2285 2287 ui.warn(_('skipping already grafted revision %d:%s '
2286 2288 '(was grafted from %d:%s)\n') %
2287 2289 (r, repo[r], rev, ctx))
2288 2290 revs.remove(r)
2289 2291 if not revs:
2290 2292 return -1
2291 2293
2292 2294 for pos, ctx in enumerate(repo.set("%ld", revs)):
2293 2295 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2294 2296 ctx.description().split('\n', 1)[0])
2295 2297 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2296 2298 if names:
2297 2299 desc += ' (%s)' % ' '.join(names)
2298 2300 ui.status(_('grafting %s\n') % desc)
2299 2301 if opts.get('dry_run'):
2300 2302 continue
2301 2303
2302 2304 source = ctx.extra().get('source')
2303 2305 extra = {}
2304 2306 if source:
2305 2307 extra['source'] = source
2306 2308 extra['intermediate-source'] = ctx.hex()
2307 2309 else:
2308 2310 extra['source'] = ctx.hex()
2309 2311 user = ctx.user()
2310 2312 if opts.get('user'):
2311 2313 user = opts['user']
2312 2314 date = ctx.date()
2313 2315 if opts.get('date'):
2314 2316 date = opts['date']
2315 2317 message = ctx.description()
2316 2318 if opts.get('log'):
2317 2319 message += '\n(grafted from %s)' % ctx.hex()
2318 2320
2319 2321 # we don't merge the first commit when continuing
2320 2322 if not cont:
2321 2323 # perform the graft merge with p1(rev) as 'ancestor'
2322 2324 try:
2323 2325 # ui.forcemerge is an internal variable, do not document
2324 2326 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2325 2327 'graft')
2326 2328 stats = mergemod.graft(repo, ctx, ctx.p1(),
2327 2329 ['local', 'graft'])
2328 2330 finally:
2329 2331 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2330 2332 # report any conflicts
2331 2333 if stats.unresolvedcount > 0:
2332 2334 # write out state for --continue
2333 2335 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2334 2336 repo.vfs.write('graftstate', ''.join(nodelines))
2335 2337 extra = ''
2336 2338 if opts.get('user'):
2337 2339 extra += ' --user %s' % procutil.shellquote(opts['user'])
2338 2340 if opts.get('date'):
2339 2341 extra += ' --date %s' % procutil.shellquote(opts['date'])
2340 2342 if opts.get('log'):
2341 2343 extra += ' --log'
2342 2344 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2343 2345 raise error.Abort(
2344 2346 _("unresolved conflicts, can't continue"),
2345 2347 hint=hint)
2346 2348 else:
2347 2349 cont = False
2348 2350
2349 2351 # commit
2350 2352 node = repo.commit(text=message, user=user,
2351 2353 date=date, extra=extra, editor=editor)
2352 2354 if node is None:
2353 2355 ui.warn(
2354 2356 _('note: graft of %d:%s created no changes to commit\n') %
2355 2357 (ctx.rev(), ctx))
2356 2358
2357 2359 # remove state when we complete successfully
2358 2360 if not opts.get('dry_run'):
2359 2361 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2360 2362
2361 2363 return 0
2362 2364
2363 2365 @command('grep',
2364 2366 [('0', 'print0', None, _('end fields with NUL')),
2365 2367 ('', 'all', None, _('print all revisions that match')),
2366 2368 ('a', 'text', None, _('treat all files as text')),
2367 2369 ('f', 'follow', None,
2368 2370 _('follow changeset history,'
2369 2371 ' or file history across copies and renames')),
2370 2372 ('i', 'ignore-case', None, _('ignore case when matching')),
2371 2373 ('l', 'files-with-matches', None,
2372 2374 _('print only filenames and revisions that match')),
2373 2375 ('n', 'line-number', None, _('print matching line numbers')),
2374 2376 ('r', 'rev', [],
2375 2377 _('only search files changed within revision range'), _('REV')),
2376 2378 ('u', 'user', None, _('list the author (long with -v)')),
2377 2379 ('d', 'date', None, _('list the date (short with -q)')),
2378 2380 ] + formatteropts + walkopts,
2379 2381 _('[OPTION]... PATTERN [FILE]...'),
2380 2382 inferrepo=True,
2381 2383 intents={INTENT_READONLY})
2382 2384 def grep(ui, repo, pattern, *pats, **opts):
2383 2385 """search revision history for a pattern in specified files
2384 2386
2385 2387 Search revision history for a regular expression in the specified
2386 2388 files or the entire project.
2387 2389
2388 2390 By default, grep prints the most recent revision number for each
2389 2391 file in which it finds a match. To get it to print every revision
2390 2392 that contains a change in match status ("-" for a match that becomes
2391 2393 a non-match, or "+" for a non-match that becomes a match), use the
2392 2394 --all flag.
2393 2395
2394 2396 PATTERN can be any Python (roughly Perl-compatible) regular
2395 2397 expression.
2396 2398
2397 2399 If no FILEs are specified (and -f/--follow isn't set), all files in
2398 2400 the repository are searched, including those that don't exist in the
2399 2401 current branch or have been deleted in a prior changeset.
2400 2402
2401 2403 Returns 0 if a match is found, 1 otherwise.
2402 2404 """
2403 2405 opts = pycompat.byteskwargs(opts)
2404 2406 reflags = re.M
2405 2407 if opts.get('ignore_case'):
2406 2408 reflags |= re.I
2407 2409 try:
2408 2410 regexp = util.re.compile(pattern, reflags)
2409 2411 except re.error as inst:
2410 2412 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2411 2413 return 1
2412 2414 sep, eol = ':', '\n'
2413 2415 if opts.get('print0'):
2414 2416 sep = eol = '\0'
2415 2417
2416 2418 getfile = util.lrucachefunc(repo.file)
2417 2419
2418 2420 def matchlines(body):
2419 2421 begin = 0
2420 2422 linenum = 0
2421 2423 while begin < len(body):
2422 2424 match = regexp.search(body, begin)
2423 2425 if not match:
2424 2426 break
2425 2427 mstart, mend = match.span()
2426 2428 linenum += body.count('\n', begin, mstart) + 1
2427 2429 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2428 2430 begin = body.find('\n', mend) + 1 or len(body) + 1
2429 2431 lend = begin - 1
2430 2432 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2431 2433
2432 2434 class linestate(object):
2433 2435 def __init__(self, line, linenum, colstart, colend):
2434 2436 self.line = line
2435 2437 self.linenum = linenum
2436 2438 self.colstart = colstart
2437 2439 self.colend = colend
2438 2440
2439 2441 def __hash__(self):
2440 2442 return hash((self.linenum, self.line))
2441 2443
2442 2444 def __eq__(self, other):
2443 2445 return self.line == other.line
2444 2446
2445 2447 def findpos(self):
2446 2448 """Iterate all (start, end) indices of matches"""
2447 2449 yield self.colstart, self.colend
2448 2450 p = self.colend
2449 2451 while p < len(self.line):
2450 2452 m = regexp.search(self.line, p)
2451 2453 if not m:
2452 2454 break
2453 2455 yield m.span()
2454 2456 p = m.end()
2455 2457
2456 2458 matches = {}
2457 2459 copies = {}
2458 2460 def grepbody(fn, rev, body):
2459 2461 matches[rev].setdefault(fn, [])
2460 2462 m = matches[rev][fn]
2461 2463 for lnum, cstart, cend, line in matchlines(body):
2462 2464 s = linestate(line, lnum, cstart, cend)
2463 2465 m.append(s)
2464 2466
2465 2467 def difflinestates(a, b):
2466 2468 sm = difflib.SequenceMatcher(None, a, b)
2467 2469 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2468 2470 if tag == 'insert':
2469 2471 for i in xrange(blo, bhi):
2470 2472 yield ('+', b[i])
2471 2473 elif tag == 'delete':
2472 2474 for i in xrange(alo, ahi):
2473 2475 yield ('-', a[i])
2474 2476 elif tag == 'replace':
2475 2477 for i in xrange(alo, ahi):
2476 2478 yield ('-', a[i])
2477 2479 for i in xrange(blo, bhi):
2478 2480 yield ('+', b[i])
2479 2481
2480 2482 def display(fm, fn, ctx, pstates, states):
2481 2483 rev = ctx.rev()
2482 2484 if fm.isplain():
2483 2485 formatuser = ui.shortuser
2484 2486 else:
2485 2487 formatuser = str
2486 2488 if ui.quiet:
2487 2489 datefmt = '%Y-%m-%d'
2488 2490 else:
2489 2491 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2490 2492 found = False
2491 2493 @util.cachefunc
2492 2494 def binary():
2493 2495 flog = getfile(fn)
2494 2496 return stringutil.binary(flog.read(ctx.filenode(fn)))
2495 2497
2496 2498 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2497 2499 if opts.get('all'):
2498 2500 iter = difflinestates(pstates, states)
2499 2501 else:
2500 2502 iter = [('', l) for l in states]
2501 2503 for change, l in iter:
2502 2504 fm.startitem()
2503 2505 fm.data(node=fm.hexfunc(ctx.node()))
2504 2506 cols = [
2505 2507 ('filename', fn, True),
2506 2508 ('rev', rev, True),
2507 2509 ('linenumber', l.linenum, opts.get('line_number')),
2508 2510 ]
2509 2511 if opts.get('all'):
2510 2512 cols.append(('change', change, True))
2511 2513 cols.extend([
2512 2514 ('user', formatuser(ctx.user()), opts.get('user')),
2513 2515 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2514 2516 ])
2515 2517 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2516 2518 for name, data, cond in cols:
2517 2519 field = fieldnamemap.get(name, name)
2518 2520 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2519 2521 if cond and name != lastcol:
2520 2522 fm.plain(sep, label='grep.sep')
2521 2523 if not opts.get('files_with_matches'):
2522 2524 fm.plain(sep, label='grep.sep')
2523 2525 if not opts.get('text') and binary():
2524 2526 fm.plain(_(" Binary file matches"))
2525 2527 else:
2526 2528 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2527 2529 fm.plain(eol)
2528 2530 found = True
2529 2531 if opts.get('files_with_matches'):
2530 2532 break
2531 2533 return found
2532 2534
2533 2535 def displaymatches(fm, l):
2534 2536 p = 0
2535 2537 for s, e in l.findpos():
2536 2538 if p < s:
2537 2539 fm.startitem()
2538 2540 fm.write('text', '%s', l.line[p:s])
2539 2541 fm.data(matched=False)
2540 2542 fm.startitem()
2541 2543 fm.write('text', '%s', l.line[s:e], label='grep.match')
2542 2544 fm.data(matched=True)
2543 2545 p = e
2544 2546 if p < len(l.line):
2545 2547 fm.startitem()
2546 2548 fm.write('text', '%s', l.line[p:])
2547 2549 fm.data(matched=False)
2548 2550 fm.end()
2549 2551
2550 2552 skip = {}
2551 2553 revfiles = {}
2552 2554 match = scmutil.match(repo[None], pats, opts)
2553 2555 found = False
2554 2556 follow = opts.get('follow')
2555 2557
2556 2558 def prep(ctx, fns):
2557 2559 rev = ctx.rev()
2558 2560 pctx = ctx.p1()
2559 2561 parent = pctx.rev()
2560 2562 matches.setdefault(rev, {})
2561 2563 matches.setdefault(parent, {})
2562 2564 files = revfiles.setdefault(rev, [])
2563 2565 for fn in fns:
2564 2566 flog = getfile(fn)
2565 2567 try:
2566 2568 fnode = ctx.filenode(fn)
2567 2569 except error.LookupError:
2568 2570 continue
2569 2571
2570 2572 copied = flog.renamed(fnode)
2571 2573 copy = follow and copied and copied[0]
2572 2574 if copy:
2573 2575 copies.setdefault(rev, {})[fn] = copy
2574 2576 if fn in skip:
2575 2577 if copy:
2576 2578 skip[copy] = True
2577 2579 continue
2578 2580 files.append(fn)
2579 2581
2580 2582 if fn not in matches[rev]:
2581 2583 grepbody(fn, rev, flog.read(fnode))
2582 2584
2583 2585 pfn = copy or fn
2584 2586 if pfn not in matches[parent]:
2585 2587 try:
2586 2588 fnode = pctx.filenode(pfn)
2587 2589 grepbody(pfn, parent, flog.read(fnode))
2588 2590 except error.LookupError:
2589 2591 pass
2590 2592
2591 2593 ui.pager('grep')
2592 2594 fm = ui.formatter('grep', opts)
2593 2595 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2594 2596 rev = ctx.rev()
2595 2597 parent = ctx.p1().rev()
2596 2598 for fn in sorted(revfiles.get(rev, [])):
2597 2599 states = matches[rev][fn]
2598 2600 copy = copies.get(rev, {}).get(fn)
2599 2601 if fn in skip:
2600 2602 if copy:
2601 2603 skip[copy] = True
2602 2604 continue
2603 2605 pstates = matches.get(parent, {}).get(copy or fn, [])
2604 2606 if pstates or states:
2605 2607 r = display(fm, fn, ctx, pstates, states)
2606 2608 found = found or r
2607 2609 if r and not opts.get('all'):
2608 2610 skip[fn] = True
2609 2611 if copy:
2610 2612 skip[copy] = True
2611 2613 del revfiles[rev]
2612 2614 # We will keep the matches dict for the duration of the window
2613 2615 # clear the matches dict once the window is over
2614 2616 if not revfiles:
2615 2617 matches.clear()
2616 2618 fm.end()
2617 2619
2618 2620 return not found
2619 2621
2620 2622 @command('heads',
2621 2623 [('r', 'rev', '',
2622 2624 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2623 2625 ('t', 'topo', False, _('show topological heads only')),
2624 2626 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2625 2627 ('c', 'closed', False, _('show normal and closed branch heads')),
2626 2628 ] + templateopts,
2627 2629 _('[-ct] [-r STARTREV] [REV]...'),
2628 2630 intents={INTENT_READONLY})
2629 2631 def heads(ui, repo, *branchrevs, **opts):
2630 2632 """show branch heads
2631 2633
2632 2634 With no arguments, show all open branch heads in the repository.
2633 2635 Branch heads are changesets that have no descendants on the
2634 2636 same branch. They are where development generally takes place and
2635 2637 are the usual targets for update and merge operations.
2636 2638
2637 2639 If one or more REVs are given, only open branch heads on the
2638 2640 branches associated with the specified changesets are shown. This
2639 2641 means that you can use :hg:`heads .` to see the heads on the
2640 2642 currently checked-out branch.
2641 2643
2642 2644 If -c/--closed is specified, also show branch heads marked closed
2643 2645 (see :hg:`commit --close-branch`).
2644 2646
2645 2647 If STARTREV is specified, only those heads that are descendants of
2646 2648 STARTREV will be displayed.
2647 2649
2648 2650 If -t/--topo is specified, named branch mechanics will be ignored and only
2649 2651 topological heads (changesets with no children) will be shown.
2650 2652
2651 2653 Returns 0 if matching heads are found, 1 if not.
2652 2654 """
2653 2655
2654 2656 opts = pycompat.byteskwargs(opts)
2655 2657 start = None
2656 2658 rev = opts.get('rev')
2657 2659 if rev:
2658 2660 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2659 2661 start = scmutil.revsingle(repo, rev, None).node()
2660 2662
2661 2663 if opts.get('topo'):
2662 2664 heads = [repo[h] for h in repo.heads(start)]
2663 2665 else:
2664 2666 heads = []
2665 2667 for branch in repo.branchmap():
2666 2668 heads += repo.branchheads(branch, start, opts.get('closed'))
2667 2669 heads = [repo[h] for h in heads]
2668 2670
2669 2671 if branchrevs:
2670 2672 branches = set(repo[r].branch()
2671 2673 for r in scmutil.revrange(repo, branchrevs))
2672 2674 heads = [h for h in heads if h.branch() in branches]
2673 2675
2674 2676 if opts.get('active') and branchrevs:
2675 2677 dagheads = repo.heads(start)
2676 2678 heads = [h for h in heads if h.node() in dagheads]
2677 2679
2678 2680 if branchrevs:
2679 2681 haveheads = set(h.branch() for h in heads)
2680 2682 if branches - haveheads:
2681 2683 headless = ', '.join(b for b in branches - haveheads)
2682 2684 msg = _('no open branch heads found on branches %s')
2683 2685 if opts.get('rev'):
2684 2686 msg += _(' (started at %s)') % opts['rev']
2685 2687 ui.warn((msg + '\n') % headless)
2686 2688
2687 2689 if not heads:
2688 2690 return 1
2689 2691
2690 2692 ui.pager('heads')
2691 2693 heads = sorted(heads, key=lambda x: -x.rev())
2692 2694 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
2693 2695 for ctx in heads:
2694 2696 displayer.show(ctx)
2695 2697 displayer.close()
2696 2698
2697 2699 @command('help',
2698 2700 [('e', 'extension', None, _('show only help for extensions')),
2699 2701 ('c', 'command', None, _('show only help for commands')),
2700 2702 ('k', 'keyword', None, _('show topics matching keyword')),
2701 2703 ('s', 'system', [], _('show help for specific platform(s)')),
2702 2704 ],
2703 2705 _('[-ecks] [TOPIC]'),
2704 2706 norepo=True,
2705 2707 intents={INTENT_READONLY})
2706 2708 def help_(ui, name=None, **opts):
2707 2709 """show help for a given topic or a help overview
2708 2710
2709 2711 With no arguments, print a list of commands with short help messages.
2710 2712
2711 2713 Given a topic, extension, or command name, print help for that
2712 2714 topic.
2713 2715
2714 2716 Returns 0 if successful.
2715 2717 """
2716 2718
2717 2719 keep = opts.get(r'system') or []
2718 2720 if len(keep) == 0:
2719 2721 if pycompat.sysplatform.startswith('win'):
2720 2722 keep.append('windows')
2721 2723 elif pycompat.sysplatform == 'OpenVMS':
2722 2724 keep.append('vms')
2723 2725 elif pycompat.sysplatform == 'plan9':
2724 2726 keep.append('plan9')
2725 2727 else:
2726 2728 keep.append('unix')
2727 2729 keep.append(pycompat.sysplatform.lower())
2728 2730 if ui.verbose:
2729 2731 keep.append('verbose')
2730 2732
2731 2733 commands = sys.modules[__name__]
2732 2734 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2733 2735 ui.pager('help')
2734 2736 ui.write(formatted)
2735 2737
2736 2738
2737 2739 @command('identify|id',
2738 2740 [('r', 'rev', '',
2739 2741 _('identify the specified revision'), _('REV')),
2740 2742 ('n', 'num', None, _('show local revision number')),
2741 2743 ('i', 'id', None, _('show global revision id')),
2742 2744 ('b', 'branch', None, _('show branch')),
2743 2745 ('t', 'tags', None, _('show tags')),
2744 2746 ('B', 'bookmarks', None, _('show bookmarks')),
2745 2747 ] + remoteopts + formatteropts,
2746 2748 _('[-nibtB] [-r REV] [SOURCE]'),
2747 2749 optionalrepo=True,
2748 2750 intents={INTENT_READONLY})
2749 2751 def identify(ui, repo, source=None, rev=None,
2750 2752 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2751 2753 """identify the working directory or specified revision
2752 2754
2753 2755 Print a summary identifying the repository state at REV using one or
2754 2756 two parent hash identifiers, followed by a "+" if the working
2755 2757 directory has uncommitted changes, the branch name (if not default),
2756 2758 a list of tags, and a list of bookmarks.
2757 2759
2758 2760 When REV is not given, print a summary of the current state of the
2759 2761 repository including the working directory. Specify -r. to get information
2760 2762 of the working directory parent without scanning uncommitted changes.
2761 2763
2762 2764 Specifying a path to a repository root or Mercurial bundle will
2763 2765 cause lookup to operate on that repository/bundle.
2764 2766
2765 2767 .. container:: verbose
2766 2768
2767 2769 Examples:
2768 2770
2769 2771 - generate a build identifier for the working directory::
2770 2772
2771 2773 hg id --id > build-id.dat
2772 2774
2773 2775 - find the revision corresponding to a tag::
2774 2776
2775 2777 hg id -n -r 1.3
2776 2778
2777 2779 - check the most recent revision of a remote repository::
2778 2780
2779 2781 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2780 2782
2781 2783 See :hg:`log` for generating more information about specific revisions,
2782 2784 including full hash identifiers.
2783 2785
2784 2786 Returns 0 if successful.
2785 2787 """
2786 2788
2787 2789 opts = pycompat.byteskwargs(opts)
2788 2790 if not repo and not source:
2789 2791 raise error.Abort(_("there is no Mercurial repository here "
2790 2792 "(.hg not found)"))
2791 2793
2792 2794 if ui.debugflag:
2793 2795 hexfunc = hex
2794 2796 else:
2795 2797 hexfunc = short
2796 2798 default = not (num or id or branch or tags or bookmarks)
2797 2799 output = []
2798 2800 revs = []
2799 2801
2800 2802 if source:
2801 2803 source, branches = hg.parseurl(ui.expandpath(source))
2802 2804 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2803 2805 repo = peer.local()
2804 2806 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2805 2807
2806 2808 fm = ui.formatter('identify', opts)
2807 2809 fm.startitem()
2808 2810
2809 2811 if not repo:
2810 2812 if num or branch or tags:
2811 2813 raise error.Abort(
2812 2814 _("can't query remote revision number, branch, or tags"))
2813 2815 if not rev and revs:
2814 2816 rev = revs[0]
2815 2817 if not rev:
2816 2818 rev = "tip"
2817 2819
2818 2820 remoterev = peer.lookup(rev)
2819 2821 hexrev = hexfunc(remoterev)
2820 2822 if default or id:
2821 2823 output = [hexrev]
2822 2824 fm.data(id=hexrev)
2823 2825
2824 2826 def getbms():
2825 2827 bms = []
2826 2828
2827 2829 if 'bookmarks' in peer.listkeys('namespaces'):
2828 2830 hexremoterev = hex(remoterev)
2829 2831 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2830 2832 if bmr == hexremoterev]
2831 2833
2832 2834 return sorted(bms)
2833 2835
2834 2836 bms = getbms()
2835 2837 if bookmarks:
2836 2838 output.extend(bms)
2837 2839 elif default and not ui.quiet:
2838 2840 # multiple bookmarks for a single parent separated by '/'
2839 2841 bm = '/'.join(bms)
2840 2842 if bm:
2841 2843 output.append(bm)
2842 2844
2843 2845 fm.data(node=hex(remoterev))
2844 2846 fm.data(bookmarks=fm.formatlist(bms, name='bookmark'))
2845 2847 else:
2846 2848 if rev:
2847 2849 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2848 2850 ctx = scmutil.revsingle(repo, rev, None)
2849 2851
2850 2852 if ctx.rev() is None:
2851 2853 ctx = repo[None]
2852 2854 parents = ctx.parents()
2853 2855 taglist = []
2854 2856 for p in parents:
2855 2857 taglist.extend(p.tags())
2856 2858
2857 2859 dirty = ""
2858 2860 if ctx.dirty(missing=True, merge=False, branch=False):
2859 2861 dirty = '+'
2860 2862 fm.data(dirty=dirty)
2861 2863
2862 2864 hexoutput = [hexfunc(p.node()) for p in parents]
2863 2865 if default or id:
2864 2866 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
2865 2867 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
2866 2868
2867 2869 if num:
2868 2870 numoutput = ["%d" % p.rev() for p in parents]
2869 2871 output.append("%s%s" % ('+'.join(numoutput), dirty))
2870 2872
2871 2873 fn = fm.nested('parents', tmpl='{rev}:{node|formatnode}', sep=' ')
2872 2874 for p in parents:
2873 2875 fn.startitem()
2874 2876 fn.data(rev=p.rev())
2875 2877 fn.data(node=p.hex())
2876 2878 fn.context(ctx=p)
2877 2879 fn.end()
2878 2880 else:
2879 2881 hexoutput = hexfunc(ctx.node())
2880 2882 if default or id:
2881 2883 output = [hexoutput]
2882 2884 fm.data(id=hexoutput)
2883 2885
2884 2886 if num:
2885 2887 output.append(pycompat.bytestr(ctx.rev()))
2886 2888 taglist = ctx.tags()
2887 2889
2888 2890 if default and not ui.quiet:
2889 2891 b = ctx.branch()
2890 2892 if b != 'default':
2891 2893 output.append("(%s)" % b)
2892 2894
2893 2895 # multiple tags for a single parent separated by '/'
2894 2896 t = '/'.join(taglist)
2895 2897 if t:
2896 2898 output.append(t)
2897 2899
2898 2900 # multiple bookmarks for a single parent separated by '/'
2899 2901 bm = '/'.join(ctx.bookmarks())
2900 2902 if bm:
2901 2903 output.append(bm)
2902 2904 else:
2903 2905 if branch:
2904 2906 output.append(ctx.branch())
2905 2907
2906 2908 if tags:
2907 2909 output.extend(taglist)
2908 2910
2909 2911 if bookmarks:
2910 2912 output.extend(ctx.bookmarks())
2911 2913
2912 2914 fm.data(node=ctx.hex())
2913 2915 fm.data(branch=ctx.branch())
2914 2916 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
2915 2917 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
2916 2918 fm.context(ctx=ctx)
2917 2919
2918 2920 fm.plain("%s\n" % ' '.join(output))
2919 2921 fm.end()
2920 2922
2921 2923 @command('import|patch',
2922 2924 [('p', 'strip', 1,
2923 2925 _('directory strip option for patch. This has the same '
2924 2926 'meaning as the corresponding patch option'), _('NUM')),
2925 2927 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2926 2928 ('e', 'edit', False, _('invoke editor on commit messages')),
2927 2929 ('f', 'force', None,
2928 2930 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2929 2931 ('', 'no-commit', None,
2930 2932 _("don't commit, just update the working directory")),
2931 2933 ('', 'bypass', None,
2932 2934 _("apply patch without touching the working directory")),
2933 2935 ('', 'partial', None,
2934 2936 _('commit even if some hunks fail')),
2935 2937 ('', 'exact', None,
2936 2938 _('abort if patch would apply lossily')),
2937 2939 ('', 'prefix', '',
2938 2940 _('apply patch to subdirectory'), _('DIR')),
2939 2941 ('', 'import-branch', None,
2940 2942 _('use any branch information in patch (implied by --exact)'))] +
2941 2943 commitopts + commitopts2 + similarityopts,
2942 2944 _('[OPTION]... PATCH...'))
2943 2945 def import_(ui, repo, patch1=None, *patches, **opts):
2944 2946 """import an ordered set of patches
2945 2947
2946 2948 Import a list of patches and commit them individually (unless
2947 2949 --no-commit is specified).
2948 2950
2949 2951 To read a patch from standard input (stdin), use "-" as the patch
2950 2952 name. If a URL is specified, the patch will be downloaded from
2951 2953 there.
2952 2954
2953 2955 Import first applies changes to the working directory (unless
2954 2956 --bypass is specified), import will abort if there are outstanding
2955 2957 changes.
2956 2958
2957 2959 Use --bypass to apply and commit patches directly to the
2958 2960 repository, without affecting the working directory. Without
2959 2961 --exact, patches will be applied on top of the working directory
2960 2962 parent revision.
2961 2963
2962 2964 You can import a patch straight from a mail message. Even patches
2963 2965 as attachments work (to use the body part, it must have type
2964 2966 text/plain or text/x-patch). From and Subject headers of email
2965 2967 message are used as default committer and commit message. All
2966 2968 text/plain body parts before first diff are added to the commit
2967 2969 message.
2968 2970
2969 2971 If the imported patch was generated by :hg:`export`, user and
2970 2972 description from patch override values from message headers and
2971 2973 body. Values given on command line with -m/--message and -u/--user
2972 2974 override these.
2973 2975
2974 2976 If --exact is specified, import will set the working directory to
2975 2977 the parent of each patch before applying it, and will abort if the
2976 2978 resulting changeset has a different ID than the one recorded in
2977 2979 the patch. This will guard against various ways that portable
2978 2980 patch formats and mail systems might fail to transfer Mercurial
2979 2981 data or metadata. See :hg:`bundle` for lossless transmission.
2980 2982
2981 2983 Use --partial to ensure a changeset will be created from the patch
2982 2984 even if some hunks fail to apply. Hunks that fail to apply will be
2983 2985 written to a <target-file>.rej file. Conflicts can then be resolved
2984 2986 by hand before :hg:`commit --amend` is run to update the created
2985 2987 changeset. This flag exists to let people import patches that
2986 2988 partially apply without losing the associated metadata (author,
2987 2989 date, description, ...).
2988 2990
2989 2991 .. note::
2990 2992
2991 2993 When no hunks apply cleanly, :hg:`import --partial` will create
2992 2994 an empty changeset, importing only the patch metadata.
2993 2995
2994 2996 With -s/--similarity, hg will attempt to discover renames and
2995 2997 copies in the patch in the same way as :hg:`addremove`.
2996 2998
2997 2999 It is possible to use external patch programs to perform the patch
2998 3000 by setting the ``ui.patch`` configuration option. For the default
2999 3001 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3000 3002 See :hg:`help config` for more information about configuration
3001 3003 files and how to use these options.
3002 3004
3003 3005 See :hg:`help dates` for a list of formats valid for -d/--date.
3004 3006
3005 3007 .. container:: verbose
3006 3008
3007 3009 Examples:
3008 3010
3009 3011 - import a traditional patch from a website and detect renames::
3010 3012
3011 3013 hg import -s 80 http://example.com/bugfix.patch
3012 3014
3013 3015 - import a changeset from an hgweb server::
3014 3016
3015 3017 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3016 3018
3017 3019 - import all the patches in an Unix-style mbox::
3018 3020
3019 3021 hg import incoming-patches.mbox
3020 3022
3021 3023 - import patches from stdin::
3022 3024
3023 3025 hg import -
3024 3026
3025 3027 - attempt to exactly restore an exported changeset (not always
3026 3028 possible)::
3027 3029
3028 3030 hg import --exact proposed-fix.patch
3029 3031
3030 3032 - use an external tool to apply a patch which is too fuzzy for
3031 3033 the default internal tool.
3032 3034
3033 3035 hg import --config ui.patch="patch --merge" fuzzy.patch
3034 3036
3035 3037 - change the default fuzzing from 2 to a less strict 7
3036 3038
3037 3039 hg import --config ui.fuzz=7 fuzz.patch
3038 3040
3039 3041 Returns 0 on success, 1 on partial success (see --partial).
3040 3042 """
3041 3043
3042 3044 opts = pycompat.byteskwargs(opts)
3043 3045 if not patch1:
3044 3046 raise error.Abort(_('need at least one patch to import'))
3045 3047
3046 3048 patches = (patch1,) + patches
3047 3049
3048 3050 date = opts.get('date')
3049 3051 if date:
3050 3052 opts['date'] = dateutil.parsedate(date)
3051 3053
3052 3054 exact = opts.get('exact')
3053 3055 update = not opts.get('bypass')
3054 3056 if not update and opts.get('no_commit'):
3055 3057 raise error.Abort(_('cannot use --no-commit with --bypass'))
3056 3058 try:
3057 3059 sim = float(opts.get('similarity') or 0)
3058 3060 except ValueError:
3059 3061 raise error.Abort(_('similarity must be a number'))
3060 3062 if sim < 0 or sim > 100:
3061 3063 raise error.Abort(_('similarity must be between 0 and 100'))
3062 3064 if sim and not update:
3063 3065 raise error.Abort(_('cannot use --similarity with --bypass'))
3064 3066 if exact:
3065 3067 if opts.get('edit'):
3066 3068 raise error.Abort(_('cannot use --exact with --edit'))
3067 3069 if opts.get('prefix'):
3068 3070 raise error.Abort(_('cannot use --exact with --prefix'))
3069 3071
3070 3072 base = opts["base"]
3071 3073 wlock = dsguard = lock = tr = None
3072 3074 msgs = []
3073 3075 ret = 0
3074 3076
3075 3077
3076 3078 try:
3077 3079 wlock = repo.wlock()
3078 3080
3079 3081 if update:
3080 3082 cmdutil.checkunfinished(repo)
3081 3083 if (exact or not opts.get('force')):
3082 3084 cmdutil.bailifchanged(repo)
3083 3085
3084 3086 if not opts.get('no_commit'):
3085 3087 lock = repo.lock()
3086 3088 tr = repo.transaction('import')
3087 3089 else:
3088 3090 dsguard = dirstateguard.dirstateguard(repo, 'import')
3089 3091 parents = repo[None].parents()
3090 3092 for patchurl in patches:
3091 3093 if patchurl == '-':
3092 3094 ui.status(_('applying patch from stdin\n'))
3093 3095 patchfile = ui.fin
3094 3096 patchurl = 'stdin' # for error message
3095 3097 else:
3096 3098 patchurl = os.path.join(base, patchurl)
3097 3099 ui.status(_('applying %s\n') % patchurl)
3098 3100 patchfile = hg.openpath(ui, patchurl)
3099 3101
3100 3102 haspatch = False
3101 3103 for hunk in patch.split(patchfile):
3102 3104 with patch.extract(ui, hunk) as patchdata:
3103 3105 msg, node, rej = cmdutil.tryimportone(ui, repo, patchdata,
3104 3106 parents, opts,
3105 3107 msgs, hg.clean)
3106 3108 if msg:
3107 3109 haspatch = True
3108 3110 ui.note(msg + '\n')
3109 3111 if update or exact:
3110 3112 parents = repo[None].parents()
3111 3113 else:
3112 3114 parents = [repo[node]]
3113 3115 if rej:
3114 3116 ui.write_err(_("patch applied partially\n"))
3115 3117 ui.write_err(_("(fix the .rej files and run "
3116 3118 "`hg commit --amend`)\n"))
3117 3119 ret = 1
3118 3120 break
3119 3121
3120 3122 if not haspatch:
3121 3123 raise error.Abort(_('%s: no diffs found') % patchurl)
3122 3124
3123 3125 if tr:
3124 3126 tr.close()
3125 3127 if msgs:
3126 3128 repo.savecommitmessage('\n* * *\n'.join(msgs))
3127 3129 if dsguard:
3128 3130 dsguard.close()
3129 3131 return ret
3130 3132 finally:
3131 3133 if tr:
3132 3134 tr.release()
3133 3135 release(lock, dsguard, wlock)
3134 3136
3135 3137 @command('incoming|in',
3136 3138 [('f', 'force', None,
3137 3139 _('run even if remote repository is unrelated')),
3138 3140 ('n', 'newest-first', None, _('show newest record first')),
3139 3141 ('', 'bundle', '',
3140 3142 _('file to store the bundles into'), _('FILE')),
3141 3143 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3142 3144 ('B', 'bookmarks', False, _("compare bookmarks")),
3143 3145 ('b', 'branch', [],
3144 3146 _('a specific branch you would like to pull'), _('BRANCH')),
3145 3147 ] + logopts + remoteopts + subrepoopts,
3146 3148 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3147 3149 def incoming(ui, repo, source="default", **opts):
3148 3150 """show new changesets found in source
3149 3151
3150 3152 Show new changesets found in the specified path/URL or the default
3151 3153 pull location. These are the changesets that would have been pulled
3152 3154 by :hg:`pull` at the time you issued this command.
3153 3155
3154 3156 See pull for valid source format details.
3155 3157
3156 3158 .. container:: verbose
3157 3159
3158 3160 With -B/--bookmarks, the result of bookmark comparison between
3159 3161 local and remote repositories is displayed. With -v/--verbose,
3160 3162 status is also displayed for each bookmark like below::
3161 3163
3162 3164 BM1 01234567890a added
3163 3165 BM2 1234567890ab advanced
3164 3166 BM3 234567890abc diverged
3165 3167 BM4 34567890abcd changed
3166 3168
3167 3169 The action taken locally when pulling depends on the
3168 3170 status of each bookmark:
3169 3171
3170 3172 :``added``: pull will create it
3171 3173 :``advanced``: pull will update it
3172 3174 :``diverged``: pull will create a divergent bookmark
3173 3175 :``changed``: result depends on remote changesets
3174 3176
3175 3177 From the point of view of pulling behavior, bookmark
3176 3178 existing only in the remote repository are treated as ``added``,
3177 3179 even if it is in fact locally deleted.
3178 3180
3179 3181 .. container:: verbose
3180 3182
3181 3183 For remote repository, using --bundle avoids downloading the
3182 3184 changesets twice if the incoming is followed by a pull.
3183 3185
3184 3186 Examples:
3185 3187
3186 3188 - show incoming changes with patches and full description::
3187 3189
3188 3190 hg incoming -vp
3189 3191
3190 3192 - show incoming changes excluding merges, store a bundle::
3191 3193
3192 3194 hg in -vpM --bundle incoming.hg
3193 3195 hg pull incoming.hg
3194 3196
3195 3197 - briefly list changes inside a bundle::
3196 3198
3197 3199 hg in changes.hg -T "{desc|firstline}\\n"
3198 3200
3199 3201 Returns 0 if there are incoming changes, 1 otherwise.
3200 3202 """
3201 3203 opts = pycompat.byteskwargs(opts)
3202 3204 if opts.get('graph'):
3203 3205 logcmdutil.checkunsupportedgraphflags([], opts)
3204 3206 def display(other, chlist, displayer):
3205 3207 revdag = logcmdutil.graphrevs(other, chlist, opts)
3206 3208 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3207 3209 graphmod.asciiedges)
3208 3210
3209 3211 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3210 3212 return 0
3211 3213
3212 3214 if opts.get('bundle') and opts.get('subrepos'):
3213 3215 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3214 3216
3215 3217 if opts.get('bookmarks'):
3216 3218 source, branches = hg.parseurl(ui.expandpath(source),
3217 3219 opts.get('branch'))
3218 3220 other = hg.peer(repo, opts, source)
3219 3221 if 'bookmarks' not in other.listkeys('namespaces'):
3220 3222 ui.warn(_("remote doesn't support bookmarks\n"))
3221 3223 return 0
3222 3224 ui.pager('incoming')
3223 3225 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3224 3226 return bookmarks.incoming(ui, repo, other)
3225 3227
3226 3228 repo._subtoppath = ui.expandpath(source)
3227 3229 try:
3228 3230 return hg.incoming(ui, repo, source, opts)
3229 3231 finally:
3230 3232 del repo._subtoppath
3231 3233
3232 3234
3233 3235 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3234 3236 norepo=True)
3235 3237 def init(ui, dest=".", **opts):
3236 3238 """create a new repository in the given directory
3237 3239
3238 3240 Initialize a new repository in the given directory. If the given
3239 3241 directory does not exist, it will be created.
3240 3242
3241 3243 If no directory is given, the current directory is used.
3242 3244
3243 3245 It is possible to specify an ``ssh://`` URL as the destination.
3244 3246 See :hg:`help urls` for more information.
3245 3247
3246 3248 Returns 0 on success.
3247 3249 """
3248 3250 opts = pycompat.byteskwargs(opts)
3249 3251 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3250 3252
3251 3253 @command('locate',
3252 3254 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3253 3255 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3254 3256 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3255 3257 ] + walkopts,
3256 3258 _('[OPTION]... [PATTERN]...'))
3257 3259 def locate(ui, repo, *pats, **opts):
3258 3260 """locate files matching specific patterns (DEPRECATED)
3259 3261
3260 3262 Print files under Mercurial control in the working directory whose
3261 3263 names match the given patterns.
3262 3264
3263 3265 By default, this command searches all directories in the working
3264 3266 directory. To search just the current directory and its
3265 3267 subdirectories, use "--include .".
3266 3268
3267 3269 If no patterns are given to match, this command prints the names
3268 3270 of all files under Mercurial control in the working directory.
3269 3271
3270 3272 If you want to feed the output of this command into the "xargs"
3271 3273 command, use the -0 option to both this command and "xargs". This
3272 3274 will avoid the problem of "xargs" treating single filenames that
3273 3275 contain whitespace as multiple filenames.
3274 3276
3275 3277 See :hg:`help files` for a more versatile command.
3276 3278
3277 3279 Returns 0 if a match is found, 1 otherwise.
3278 3280 """
3279 3281 opts = pycompat.byteskwargs(opts)
3280 3282 if opts.get('print0'):
3281 3283 end = '\0'
3282 3284 else:
3283 3285 end = '\n'
3284 3286 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3285 3287
3286 3288 ret = 1
3287 3289 m = scmutil.match(ctx, pats, opts, default='relglob',
3288 3290 badfn=lambda x, y: False)
3289 3291
3290 3292 ui.pager('locate')
3291 3293 for abs in ctx.matches(m):
3292 3294 if opts.get('fullpath'):
3293 3295 ui.write(repo.wjoin(abs), end)
3294 3296 else:
3295 3297 ui.write(((pats and m.rel(abs)) or abs), end)
3296 3298 ret = 0
3297 3299
3298 3300 return ret
3299 3301
3300 3302 @command('^log|history',
3301 3303 [('f', 'follow', None,
3302 3304 _('follow changeset history, or file history across copies and renames')),
3303 3305 ('', 'follow-first', None,
3304 3306 _('only follow the first parent of merge changesets (DEPRECATED)')),
3305 3307 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3306 3308 ('C', 'copies', None, _('show copied files')),
3307 3309 ('k', 'keyword', [],
3308 3310 _('do case-insensitive search for a given text'), _('TEXT')),
3309 3311 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3310 3312 ('L', 'line-range', [],
3311 3313 _('follow line range of specified file (EXPERIMENTAL)'),
3312 3314 _('FILE,RANGE')),
3313 3315 ('', 'removed', None, _('include revisions where files were removed')),
3314 3316 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3315 3317 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3316 3318 ('', 'only-branch', [],
3317 3319 _('show only changesets within the given named branch (DEPRECATED)'),
3318 3320 _('BRANCH')),
3319 3321 ('b', 'branch', [],
3320 3322 _('show changesets within the given named branch'), _('BRANCH')),
3321 3323 ('P', 'prune', [],
3322 3324 _('do not display revision or any of its ancestors'), _('REV')),
3323 3325 ] + logopts + walkopts,
3324 3326 _('[OPTION]... [FILE]'),
3325 3327 inferrepo=True,
3326 3328 intents={INTENT_READONLY})
3327 3329 def log(ui, repo, *pats, **opts):
3328 3330 """show revision history of entire repository or files
3329 3331
3330 3332 Print the revision history of the specified files or the entire
3331 3333 project.
3332 3334
3333 3335 If no revision range is specified, the default is ``tip:0`` unless
3334 3336 --follow is set, in which case the working directory parent is
3335 3337 used as the starting revision.
3336 3338
3337 3339 File history is shown without following rename or copy history of
3338 3340 files. Use -f/--follow with a filename to follow history across
3339 3341 renames and copies. --follow without a filename will only show
3340 3342 ancestors of the starting revision.
3341 3343
3342 3344 By default this command prints revision number and changeset id,
3343 3345 tags, non-trivial parents, user, date and time, and a summary for
3344 3346 each commit. When the -v/--verbose switch is used, the list of
3345 3347 changed files and full commit message are shown.
3346 3348
3347 3349 With --graph the revisions are shown as an ASCII art DAG with the most
3348 3350 recent changeset at the top.
3349 3351 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3350 3352 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3351 3353 changeset from the lines below is a parent of the 'o' merge on the same
3352 3354 line.
3353 3355 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3354 3356 of a '|' indicates one or more revisions in a path are omitted.
3355 3357
3356 3358 .. container:: verbose
3357 3359
3358 3360 Use -L/--line-range FILE,M:N options to follow the history of lines
3359 3361 from M to N in FILE. With -p/--patch only diff hunks affecting
3360 3362 specified line range will be shown. This option requires --follow;
3361 3363 it can be specified multiple times. Currently, this option is not
3362 3364 compatible with --graph. This option is experimental.
3363 3365
3364 3366 .. note::
3365 3367
3366 3368 :hg:`log --patch` may generate unexpected diff output for merge
3367 3369 changesets, as it will only compare the merge changeset against
3368 3370 its first parent. Also, only files different from BOTH parents
3369 3371 will appear in files:.
3370 3372
3371 3373 .. note::
3372 3374
3373 3375 For performance reasons, :hg:`log FILE` may omit duplicate changes
3374 3376 made on branches and will not show removals or mode changes. To
3375 3377 see all such changes, use the --removed switch.
3376 3378
3377 3379 .. container:: verbose
3378 3380
3379 3381 .. note::
3380 3382
3381 3383 The history resulting from -L/--line-range options depends on diff
3382 3384 options; for instance if white-spaces are ignored, respective changes
3383 3385 with only white-spaces in specified line range will not be listed.
3384 3386
3385 3387 .. container:: verbose
3386 3388
3387 3389 Some examples:
3388 3390
3389 3391 - changesets with full descriptions and file lists::
3390 3392
3391 3393 hg log -v
3392 3394
3393 3395 - changesets ancestral to the working directory::
3394 3396
3395 3397 hg log -f
3396 3398
3397 3399 - last 10 commits on the current branch::
3398 3400
3399 3401 hg log -l 10 -b .
3400 3402
3401 3403 - changesets showing all modifications of a file, including removals::
3402 3404
3403 3405 hg log --removed file.c
3404 3406
3405 3407 - all changesets that touch a directory, with diffs, excluding merges::
3406 3408
3407 3409 hg log -Mp lib/
3408 3410
3409 3411 - all revision numbers that match a keyword::
3410 3412
3411 3413 hg log -k bug --template "{rev}\\n"
3412 3414
3413 3415 - the full hash identifier of the working directory parent::
3414 3416
3415 3417 hg log -r . --template "{node}\\n"
3416 3418
3417 3419 - list available log templates::
3418 3420
3419 3421 hg log -T list
3420 3422
3421 3423 - check if a given changeset is included in a tagged release::
3422 3424
3423 3425 hg log -r "a21ccf and ancestor(1.9)"
3424 3426
3425 3427 - find all changesets by some user in a date range::
3426 3428
3427 3429 hg log -k alice -d "may 2008 to jul 2008"
3428 3430
3429 3431 - summary of all changesets after the last tag::
3430 3432
3431 3433 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3432 3434
3433 3435 - changesets touching lines 13 to 23 for file.c::
3434 3436
3435 3437 hg log -L file.c,13:23
3436 3438
3437 3439 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3438 3440 main.c with patch::
3439 3441
3440 3442 hg log -L file.c,13:23 -L main.c,2:6 -p
3441 3443
3442 3444 See :hg:`help dates` for a list of formats valid for -d/--date.
3443 3445
3444 3446 See :hg:`help revisions` for more about specifying and ordering
3445 3447 revisions.
3446 3448
3447 3449 See :hg:`help templates` for more about pre-packaged styles and
3448 3450 specifying custom templates. The default template used by the log
3449 3451 command can be customized via the ``ui.logtemplate`` configuration
3450 3452 setting.
3451 3453
3452 3454 Returns 0 on success.
3453 3455
3454 3456 """
3455 3457 opts = pycompat.byteskwargs(opts)
3456 3458 linerange = opts.get('line_range')
3457 3459
3458 3460 if linerange and not opts.get('follow'):
3459 3461 raise error.Abort(_('--line-range requires --follow'))
3460 3462
3461 3463 if linerange and pats:
3462 3464 # TODO: take pats as patterns with no line-range filter
3463 3465 raise error.Abort(
3464 3466 _('FILE arguments are not compatible with --line-range option')
3465 3467 )
3466 3468
3467 3469 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3468 3470 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3469 3471 if linerange:
3470 3472 # TODO: should follow file history from logcmdutil._initialrevs(),
3471 3473 # then filter the result by logcmdutil._makerevset() and --limit
3472 3474 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3473 3475
3474 3476 getrenamed = None
3475 3477 if opts.get('copies'):
3476 3478 endrev = None
3477 3479 if opts.get('rev'):
3478 3480 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3479 3481 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3480 3482
3481 3483 ui.pager('log')
3482 3484 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3483 3485 buffered=True)
3484 3486 if opts.get('graph'):
3485 3487 displayfn = logcmdutil.displaygraphrevs
3486 3488 else:
3487 3489 displayfn = logcmdutil.displayrevs
3488 3490 displayfn(ui, repo, revs, displayer, getrenamed)
3489 3491
3490 3492 @command('manifest',
3491 3493 [('r', 'rev', '', _('revision to display'), _('REV')),
3492 3494 ('', 'all', False, _("list files from all revisions"))]
3493 3495 + formatteropts,
3494 3496 _('[-r REV]'),
3495 3497 intents={INTENT_READONLY})
3496 3498 def manifest(ui, repo, node=None, rev=None, **opts):
3497 3499 """output the current or given revision of the project manifest
3498 3500
3499 3501 Print a list of version controlled files for the given revision.
3500 3502 If no revision is given, the first parent of the working directory
3501 3503 is used, or the null revision if no revision is checked out.
3502 3504
3503 3505 With -v, print file permissions, symlink and executable bits.
3504 3506 With --debug, print file revision hashes.
3505 3507
3506 3508 If option --all is specified, the list of all files from all revisions
3507 3509 is printed. This includes deleted and renamed files.
3508 3510
3509 3511 Returns 0 on success.
3510 3512 """
3511 3513 opts = pycompat.byteskwargs(opts)
3512 3514 fm = ui.formatter('manifest', opts)
3513 3515
3514 3516 if opts.get('all'):
3515 3517 if rev or node:
3516 3518 raise error.Abort(_("can't specify a revision with --all"))
3517 3519
3518 3520 res = set()
3519 3521 for rev in repo:
3520 3522 ctx = repo[rev]
3521 3523 res |= set(ctx.files())
3522 3524
3523 3525 ui.pager('manifest')
3524 3526 for f in sorted(res):
3525 3527 fm.startitem()
3526 3528 fm.write("path", '%s\n', f)
3527 3529 fm.end()
3528 3530 return
3529 3531
3530 3532 if rev and node:
3531 3533 raise error.Abort(_("please specify just one revision"))
3532 3534
3533 3535 if not node:
3534 3536 node = rev
3535 3537
3536 3538 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3537 3539 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3538 3540 if node:
3539 3541 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3540 3542 ctx = scmutil.revsingle(repo, node)
3541 3543 mf = ctx.manifest()
3542 3544 ui.pager('manifest')
3543 3545 for f in ctx:
3544 3546 fm.startitem()
3545 3547 fl = ctx[f].flags()
3546 3548 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3547 3549 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3548 3550 fm.write('path', '%s\n', f)
3549 3551 fm.end()
3550 3552
3551 3553 @command('^merge',
3552 3554 [('f', 'force', None,
3553 3555 _('force a merge including outstanding changes (DEPRECATED)')),
3554 3556 ('r', 'rev', '', _('revision to merge'), _('REV')),
3555 3557 ('P', 'preview', None,
3556 3558 _('review revisions to merge (no merge is performed)')),
3557 3559 ('', 'abort', None, _('abort the ongoing merge')),
3558 3560 ] + mergetoolopts,
3559 3561 _('[-P] [[-r] REV]'))
3560 3562 def merge(ui, repo, node=None, **opts):
3561 3563 """merge another revision into working directory
3562 3564
3563 3565 The current working directory is updated with all changes made in
3564 3566 the requested revision since the last common predecessor revision.
3565 3567
3566 3568 Files that changed between either parent are marked as changed for
3567 3569 the next commit and a commit must be performed before any further
3568 3570 updates to the repository are allowed. The next commit will have
3569 3571 two parents.
3570 3572
3571 3573 ``--tool`` can be used to specify the merge tool used for file
3572 3574 merges. It overrides the HGMERGE environment variable and your
3573 3575 configuration files. See :hg:`help merge-tools` for options.
3574 3576
3575 3577 If no revision is specified, the working directory's parent is a
3576 3578 head revision, and the current branch contains exactly one other
3577 3579 head, the other head is merged with by default. Otherwise, an
3578 3580 explicit revision with which to merge with must be provided.
3579 3581
3580 3582 See :hg:`help resolve` for information on handling file conflicts.
3581 3583
3582 3584 To undo an uncommitted merge, use :hg:`merge --abort` which
3583 3585 will check out a clean copy of the original merge parent, losing
3584 3586 all changes.
3585 3587
3586 3588 Returns 0 on success, 1 if there are unresolved files.
3587 3589 """
3588 3590
3589 3591 opts = pycompat.byteskwargs(opts)
3590 3592 abort = opts.get('abort')
3591 3593 if abort and repo.dirstate.p2() == nullid:
3592 3594 cmdutil.wrongtooltocontinue(repo, _('merge'))
3593 3595 if abort:
3594 3596 if node:
3595 3597 raise error.Abort(_("cannot specify a node with --abort"))
3596 3598 if opts.get('rev'):
3597 3599 raise error.Abort(_("cannot specify both --rev and --abort"))
3598 3600 if opts.get('preview'):
3599 3601 raise error.Abort(_("cannot specify --preview with --abort"))
3600 3602 if opts.get('rev') and node:
3601 3603 raise error.Abort(_("please specify just one revision"))
3602 3604 if not node:
3603 3605 node = opts.get('rev')
3604 3606
3605 3607 if node:
3606 3608 node = scmutil.revsingle(repo, node).node()
3607 3609
3608 3610 if not node and not abort:
3609 3611 node = repo[destutil.destmerge(repo)].node()
3610 3612
3611 3613 if opts.get('preview'):
3612 3614 # find nodes that are ancestors of p2 but not of p1
3613 3615 p1 = repo.lookup('.')
3614 3616 p2 = node
3615 3617 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3616 3618
3617 3619 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3618 3620 for node in nodes:
3619 3621 displayer.show(repo[node])
3620 3622 displayer.close()
3621 3623 return 0
3622 3624
3623 3625 try:
3624 3626 # ui.forcemerge is an internal variable, do not document
3625 3627 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3626 3628 force = opts.get('force')
3627 3629 labels = ['working copy', 'merge rev']
3628 3630 return hg.merge(repo, node, force=force, mergeforce=force,
3629 3631 labels=labels, abort=abort)
3630 3632 finally:
3631 3633 ui.setconfig('ui', 'forcemerge', '', 'merge')
3632 3634
3633 3635 @command('outgoing|out',
3634 3636 [('f', 'force', None, _('run even when the destination is unrelated')),
3635 3637 ('r', 'rev', [],
3636 3638 _('a changeset intended to be included in the destination'), _('REV')),
3637 3639 ('n', 'newest-first', None, _('show newest record first')),
3638 3640 ('B', 'bookmarks', False, _('compare bookmarks')),
3639 3641 ('b', 'branch', [], _('a specific branch you would like to push'),
3640 3642 _('BRANCH')),
3641 3643 ] + logopts + remoteopts + subrepoopts,
3642 3644 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3643 3645 def outgoing(ui, repo, dest=None, **opts):
3644 3646 """show changesets not found in the destination
3645 3647
3646 3648 Show changesets not found in the specified destination repository
3647 3649 or the default push location. These are the changesets that would
3648 3650 be pushed if a push was requested.
3649 3651
3650 3652 See pull for details of valid destination formats.
3651 3653
3652 3654 .. container:: verbose
3653 3655
3654 3656 With -B/--bookmarks, the result of bookmark comparison between
3655 3657 local and remote repositories is displayed. With -v/--verbose,
3656 3658 status is also displayed for each bookmark like below::
3657 3659
3658 3660 BM1 01234567890a added
3659 3661 BM2 deleted
3660 3662 BM3 234567890abc advanced
3661 3663 BM4 34567890abcd diverged
3662 3664 BM5 4567890abcde changed
3663 3665
3664 3666 The action taken when pushing depends on the
3665 3667 status of each bookmark:
3666 3668
3667 3669 :``added``: push with ``-B`` will create it
3668 3670 :``deleted``: push with ``-B`` will delete it
3669 3671 :``advanced``: push will update it
3670 3672 :``diverged``: push with ``-B`` will update it
3671 3673 :``changed``: push with ``-B`` will update it
3672 3674
3673 3675 From the point of view of pushing behavior, bookmarks
3674 3676 existing only in the remote repository are treated as
3675 3677 ``deleted``, even if it is in fact added remotely.
3676 3678
3677 3679 Returns 0 if there are outgoing changes, 1 otherwise.
3678 3680 """
3679 3681 opts = pycompat.byteskwargs(opts)
3680 3682 if opts.get('graph'):
3681 3683 logcmdutil.checkunsupportedgraphflags([], opts)
3682 3684 o, other = hg._outgoing(ui, repo, dest, opts)
3683 3685 if not o:
3684 3686 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3685 3687 return
3686 3688
3687 3689 revdag = logcmdutil.graphrevs(repo, o, opts)
3688 3690 ui.pager('outgoing')
3689 3691 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
3690 3692 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3691 3693 graphmod.asciiedges)
3692 3694 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3693 3695 return 0
3694 3696
3695 3697 if opts.get('bookmarks'):
3696 3698 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3697 3699 dest, branches = hg.parseurl(dest, opts.get('branch'))
3698 3700 other = hg.peer(repo, opts, dest)
3699 3701 if 'bookmarks' not in other.listkeys('namespaces'):
3700 3702 ui.warn(_("remote doesn't support bookmarks\n"))
3701 3703 return 0
3702 3704 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3703 3705 ui.pager('outgoing')
3704 3706 return bookmarks.outgoing(ui, repo, other)
3705 3707
3706 3708 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3707 3709 try:
3708 3710 return hg.outgoing(ui, repo, dest, opts)
3709 3711 finally:
3710 3712 del repo._subtoppath
3711 3713
3712 3714 @command('parents',
3713 3715 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3714 3716 ] + templateopts,
3715 3717 _('[-r REV] [FILE]'),
3716 3718 inferrepo=True)
3717 3719 def parents(ui, repo, file_=None, **opts):
3718 3720 """show the parents of the working directory or revision (DEPRECATED)
3719 3721
3720 3722 Print the working directory's parent revisions. If a revision is
3721 3723 given via -r/--rev, the parent of that revision will be printed.
3722 3724 If a file argument is given, the revision in which the file was
3723 3725 last changed (before the working directory revision or the
3724 3726 argument to --rev if given) is printed.
3725 3727
3726 3728 This command is equivalent to::
3727 3729
3728 3730 hg log -r "p1()+p2()" or
3729 3731 hg log -r "p1(REV)+p2(REV)" or
3730 3732 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3731 3733 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3732 3734
3733 3735 See :hg:`summary` and :hg:`help revsets` for related information.
3734 3736
3735 3737 Returns 0 on success.
3736 3738 """
3737 3739
3738 3740 opts = pycompat.byteskwargs(opts)
3739 3741 rev = opts.get('rev')
3740 3742 if rev:
3741 3743 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3742 3744 ctx = scmutil.revsingle(repo, rev, None)
3743 3745
3744 3746 if file_:
3745 3747 m = scmutil.match(ctx, (file_,), opts)
3746 3748 if m.anypats() or len(m.files()) != 1:
3747 3749 raise error.Abort(_('can only specify an explicit filename'))
3748 3750 file_ = m.files()[0]
3749 3751 filenodes = []
3750 3752 for cp in ctx.parents():
3751 3753 if not cp:
3752 3754 continue
3753 3755 try:
3754 3756 filenodes.append(cp.filenode(file_))
3755 3757 except error.LookupError:
3756 3758 pass
3757 3759 if not filenodes:
3758 3760 raise error.Abort(_("'%s' not found in manifest!") % file_)
3759 3761 p = []
3760 3762 for fn in filenodes:
3761 3763 fctx = repo.filectx(file_, fileid=fn)
3762 3764 p.append(fctx.node())
3763 3765 else:
3764 3766 p = [cp.node() for cp in ctx.parents()]
3765 3767
3766 3768 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3767 3769 for n in p:
3768 3770 if n != nullid:
3769 3771 displayer.show(repo[n])
3770 3772 displayer.close()
3771 3773
3772 3774 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True,
3773 3775 intents={INTENT_READONLY})
3774 3776 def paths(ui, repo, search=None, **opts):
3775 3777 """show aliases for remote repositories
3776 3778
3777 3779 Show definition of symbolic path name NAME. If no name is given,
3778 3780 show definition of all available names.
3779 3781
3780 3782 Option -q/--quiet suppresses all output when searching for NAME
3781 3783 and shows only the path names when listing all definitions.
3782 3784
3783 3785 Path names are defined in the [paths] section of your
3784 3786 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3785 3787 repository, ``.hg/hgrc`` is used, too.
3786 3788
3787 3789 The path names ``default`` and ``default-push`` have a special
3788 3790 meaning. When performing a push or pull operation, they are used
3789 3791 as fallbacks if no location is specified on the command-line.
3790 3792 When ``default-push`` is set, it will be used for push and
3791 3793 ``default`` will be used for pull; otherwise ``default`` is used
3792 3794 as the fallback for both. When cloning a repository, the clone
3793 3795 source is written as ``default`` in ``.hg/hgrc``.
3794 3796
3795 3797 .. note::
3796 3798
3797 3799 ``default`` and ``default-push`` apply to all inbound (e.g.
3798 3800 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3799 3801 and :hg:`bundle`) operations.
3800 3802
3801 3803 See :hg:`help urls` for more information.
3802 3804
3803 3805 Returns 0 on success.
3804 3806 """
3805 3807
3806 3808 opts = pycompat.byteskwargs(opts)
3807 3809 ui.pager('paths')
3808 3810 if search:
3809 3811 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3810 3812 if name == search]
3811 3813 else:
3812 3814 pathitems = sorted(ui.paths.iteritems())
3813 3815
3814 3816 fm = ui.formatter('paths', opts)
3815 3817 if fm.isplain():
3816 3818 hidepassword = util.hidepassword
3817 3819 else:
3818 3820 hidepassword = bytes
3819 3821 if ui.quiet:
3820 3822 namefmt = '%s\n'
3821 3823 else:
3822 3824 namefmt = '%s = '
3823 3825 showsubopts = not search and not ui.quiet
3824 3826
3825 3827 for name, path in pathitems:
3826 3828 fm.startitem()
3827 3829 fm.condwrite(not search, 'name', namefmt, name)
3828 3830 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3829 3831 for subopt, value in sorted(path.suboptions.items()):
3830 3832 assert subopt not in ('name', 'url')
3831 3833 if showsubopts:
3832 3834 fm.plain('%s:%s = ' % (name, subopt))
3833 3835 fm.condwrite(showsubopts, subopt, '%s\n', value)
3834 3836
3835 3837 fm.end()
3836 3838
3837 3839 if search and not pathitems:
3838 3840 if not ui.quiet:
3839 3841 ui.warn(_("not found!\n"))
3840 3842 return 1
3841 3843 else:
3842 3844 return 0
3843 3845
3844 3846 @command('phase',
3845 3847 [('p', 'public', False, _('set changeset phase to public')),
3846 3848 ('d', 'draft', False, _('set changeset phase to draft')),
3847 3849 ('s', 'secret', False, _('set changeset phase to secret')),
3848 3850 ('f', 'force', False, _('allow to move boundary backward')),
3849 3851 ('r', 'rev', [], _('target revision'), _('REV')),
3850 3852 ],
3851 3853 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3852 3854 def phase(ui, repo, *revs, **opts):
3853 3855 """set or show the current phase name
3854 3856
3855 3857 With no argument, show the phase name of the current revision(s).
3856 3858
3857 3859 With one of -p/--public, -d/--draft or -s/--secret, change the
3858 3860 phase value of the specified revisions.
3859 3861
3860 3862 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
3861 3863 lower phase to a higher phase. Phases are ordered as follows::
3862 3864
3863 3865 public < draft < secret
3864 3866
3865 3867 Returns 0 on success, 1 if some phases could not be changed.
3866 3868
3867 3869 (For more information about the phases concept, see :hg:`help phases`.)
3868 3870 """
3869 3871 opts = pycompat.byteskwargs(opts)
3870 3872 # search for a unique phase argument
3871 3873 targetphase = None
3872 3874 for idx, name in enumerate(phases.phasenames):
3873 3875 if opts[name]:
3874 3876 if targetphase is not None:
3875 3877 raise error.Abort(_('only one phase can be specified'))
3876 3878 targetphase = idx
3877 3879
3878 3880 # look for specified revision
3879 3881 revs = list(revs)
3880 3882 revs.extend(opts['rev'])
3881 3883 if not revs:
3882 3884 # display both parents as the second parent phase can influence
3883 3885 # the phase of a merge commit
3884 3886 revs = [c.rev() for c in repo[None].parents()]
3885 3887
3886 3888 revs = scmutil.revrange(repo, revs)
3887 3889
3888 3890 ret = 0
3889 3891 if targetphase is None:
3890 3892 # display
3891 3893 for r in revs:
3892 3894 ctx = repo[r]
3893 3895 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3894 3896 else:
3895 3897 with repo.lock(), repo.transaction("phase") as tr:
3896 3898 # set phase
3897 3899 if not revs:
3898 3900 raise error.Abort(_('empty revision set'))
3899 3901 nodes = [repo[r].node() for r in revs]
3900 3902 # moving revision from public to draft may hide them
3901 3903 # We have to check result on an unfiltered repository
3902 3904 unfi = repo.unfiltered()
3903 3905 getphase = unfi._phasecache.phase
3904 3906 olddata = [getphase(unfi, r) for r in unfi]
3905 3907 phases.advanceboundary(repo, tr, targetphase, nodes)
3906 3908 if opts['force']:
3907 3909 phases.retractboundary(repo, tr, targetphase, nodes)
3908 3910 getphase = unfi._phasecache.phase
3909 3911 newdata = [getphase(unfi, r) for r in unfi]
3910 3912 changes = sum(newdata[r] != olddata[r] for r in unfi)
3911 3913 cl = unfi.changelog
3912 3914 rejected = [n for n in nodes
3913 3915 if newdata[cl.rev(n)] < targetphase]
3914 3916 if rejected:
3915 3917 ui.warn(_('cannot move %i changesets to a higher '
3916 3918 'phase, use --force\n') % len(rejected))
3917 3919 ret = 1
3918 3920 if changes:
3919 3921 msg = _('phase changed for %i changesets\n') % changes
3920 3922 if ret:
3921 3923 ui.status(msg)
3922 3924 else:
3923 3925 ui.note(msg)
3924 3926 else:
3925 3927 ui.warn(_('no phases changed\n'))
3926 3928 return ret
3927 3929
3928 3930 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3929 3931 """Run after a changegroup has been added via pull/unbundle
3930 3932
3931 3933 This takes arguments below:
3932 3934
3933 3935 :modheads: change of heads by pull/unbundle
3934 3936 :optupdate: updating working directory is needed or not
3935 3937 :checkout: update destination revision (or None to default destination)
3936 3938 :brev: a name, which might be a bookmark to be activated after updating
3937 3939 """
3938 3940 if modheads == 0:
3939 3941 return
3940 3942 if optupdate:
3941 3943 try:
3942 3944 return hg.updatetotally(ui, repo, checkout, brev)
3943 3945 except error.UpdateAbort as inst:
3944 3946 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
3945 3947 hint = inst.hint
3946 3948 raise error.UpdateAbort(msg, hint=hint)
3947 3949 if modheads > 1:
3948 3950 currentbranchheads = len(repo.branchheads())
3949 3951 if currentbranchheads == modheads:
3950 3952 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3951 3953 elif currentbranchheads > 1:
3952 3954 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3953 3955 "merge)\n"))
3954 3956 else:
3955 3957 ui.status(_("(run 'hg heads' to see heads)\n"))
3956 3958 elif not ui.configbool('commands', 'update.requiredest'):
3957 3959 ui.status(_("(run 'hg update' to get a working copy)\n"))
3958 3960
3959 3961 @command('^pull',
3960 3962 [('u', 'update', None,
3961 3963 _('update to new branch head if new descendants were pulled')),
3962 3964 ('f', 'force', None, _('run even when remote repository is unrelated')),
3963 3965 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3964 3966 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3965 3967 ('b', 'branch', [], _('a specific branch you would like to pull'),
3966 3968 _('BRANCH')),
3967 3969 ] + remoteopts,
3968 3970 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3969 3971 def pull(ui, repo, source="default", **opts):
3970 3972 """pull changes from the specified source
3971 3973
3972 3974 Pull changes from a remote repository to a local one.
3973 3975
3974 3976 This finds all changes from the repository at the specified path
3975 3977 or URL and adds them to a local repository (the current one unless
3976 3978 -R is specified). By default, this does not update the copy of the
3977 3979 project in the working directory.
3978 3980
3979 3981 When cloning from servers that support it, Mercurial may fetch
3980 3982 pre-generated data. When this is done, hooks operating on incoming
3981 3983 changesets and changegroups may fire more than once, once for each
3982 3984 pre-generated bundle and as well as for any additional remaining
3983 3985 data. See :hg:`help -e clonebundles` for more.
3984 3986
3985 3987 Use :hg:`incoming` if you want to see what would have been added
3986 3988 by a pull at the time you issued this command. If you then decide
3987 3989 to add those changes to the repository, you should use :hg:`pull
3988 3990 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3989 3991
3990 3992 If SOURCE is omitted, the 'default' path will be used.
3991 3993 See :hg:`help urls` for more information.
3992 3994
3993 3995 Specifying bookmark as ``.`` is equivalent to specifying the active
3994 3996 bookmark's name.
3995 3997
3996 3998 Returns 0 on success, 1 if an update had unresolved files.
3997 3999 """
3998 4000
3999 4001 opts = pycompat.byteskwargs(opts)
4000 4002 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4001 4003 msg = _('update destination required by configuration')
4002 4004 hint = _('use hg pull followed by hg update DEST')
4003 4005 raise error.Abort(msg, hint=hint)
4004 4006
4005 4007 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4006 4008 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4007 4009 other = hg.peer(repo, opts, source)
4008 4010 try:
4009 4011 revs, checkout = hg.addbranchrevs(repo, other, branches,
4010 4012 opts.get('rev'))
4011 4013
4012 4014
4013 4015 pullopargs = {}
4014 4016 if opts.get('bookmark'):
4015 4017 if not revs:
4016 4018 revs = []
4017 4019 # The list of bookmark used here is not the one used to actually
4018 4020 # update the bookmark name. This can result in the revision pulled
4019 4021 # not ending up with the name of the bookmark because of a race
4020 4022 # condition on the server. (See issue 4689 for details)
4021 4023 remotebookmarks = other.listkeys('bookmarks')
4022 4024 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4023 4025 pullopargs['remotebookmarks'] = remotebookmarks
4024 4026 for b in opts['bookmark']:
4025 4027 b = repo._bookmarks.expandname(b)
4026 4028 if b not in remotebookmarks:
4027 4029 raise error.Abort(_('remote bookmark %s not found!') % b)
4028 4030 revs.append(hex(remotebookmarks[b]))
4029 4031
4030 4032 if revs:
4031 4033 try:
4032 4034 # When 'rev' is a bookmark name, we cannot guarantee that it
4033 4035 # will be updated with that name because of a race condition
4034 4036 # server side. (See issue 4689 for details)
4035 4037 oldrevs = revs
4036 4038 revs = [] # actually, nodes
4037 4039 for r in oldrevs:
4038 4040 node = other.lookup(r)
4039 4041 revs.append(node)
4040 4042 if r == checkout:
4041 4043 checkout = node
4042 4044 except error.CapabilityError:
4043 4045 err = _("other repository doesn't support revision lookup, "
4044 4046 "so a rev cannot be specified.")
4045 4047 raise error.Abort(err)
4046 4048
4047 4049 wlock = util.nullcontextmanager()
4048 4050 if opts.get('update'):
4049 4051 wlock = repo.wlock()
4050 4052 with wlock:
4051 4053 pullopargs.update(opts.get('opargs', {}))
4052 4054 modheads = exchange.pull(repo, other, heads=revs,
4053 4055 force=opts.get('force'),
4054 4056 bookmarks=opts.get('bookmark', ()),
4055 4057 opargs=pullopargs).cgresult
4056 4058
4057 4059 # brev is a name, which might be a bookmark to be activated at
4058 4060 # the end of the update. In other words, it is an explicit
4059 4061 # destination of the update
4060 4062 brev = None
4061 4063
4062 4064 if checkout:
4063 4065 checkout = repo.changelog.rev(checkout)
4064 4066
4065 4067 # order below depends on implementation of
4066 4068 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4067 4069 # because 'checkout' is determined without it.
4068 4070 if opts.get('rev'):
4069 4071 brev = opts['rev'][0]
4070 4072 elif opts.get('branch'):
4071 4073 brev = opts['branch'][0]
4072 4074 else:
4073 4075 brev = branches[0]
4074 4076 repo._subtoppath = source
4075 4077 try:
4076 4078 ret = postincoming(ui, repo, modheads, opts.get('update'),
4077 4079 checkout, brev)
4078 4080
4079 4081 finally:
4080 4082 del repo._subtoppath
4081 4083
4082 4084 finally:
4083 4085 other.close()
4084 4086 return ret
4085 4087
4086 4088 @command('^push',
4087 4089 [('f', 'force', None, _('force push')),
4088 4090 ('r', 'rev', [],
4089 4091 _('a changeset intended to be included in the destination'),
4090 4092 _('REV')),
4091 4093 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4092 4094 ('b', 'branch', [],
4093 4095 _('a specific branch you would like to push'), _('BRANCH')),
4094 4096 ('', 'new-branch', False, _('allow pushing a new branch')),
4095 4097 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4096 4098 ] + remoteopts,
4097 4099 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4098 4100 def push(ui, repo, dest=None, **opts):
4099 4101 """push changes to the specified destination
4100 4102
4101 4103 Push changesets from the local repository to the specified
4102 4104 destination.
4103 4105
4104 4106 This operation is symmetrical to pull: it is identical to a pull
4105 4107 in the destination repository from the current one.
4106 4108
4107 4109 By default, push will not allow creation of new heads at the
4108 4110 destination, since multiple heads would make it unclear which head
4109 4111 to use. In this situation, it is recommended to pull and merge
4110 4112 before pushing.
4111 4113
4112 4114 Use --new-branch if you want to allow push to create a new named
4113 4115 branch that is not present at the destination. This allows you to
4114 4116 only create a new branch without forcing other changes.
4115 4117
4116 4118 .. note::
4117 4119
4118 4120 Extra care should be taken with the -f/--force option,
4119 4121 which will push all new heads on all branches, an action which will
4120 4122 almost always cause confusion for collaborators.
4121 4123
4122 4124 If -r/--rev is used, the specified revision and all its ancestors
4123 4125 will be pushed to the remote repository.
4124 4126
4125 4127 If -B/--bookmark is used, the specified bookmarked revision, its
4126 4128 ancestors, and the bookmark will be pushed to the remote
4127 4129 repository. Specifying ``.`` is equivalent to specifying the active
4128 4130 bookmark's name.
4129 4131
4130 4132 Please see :hg:`help urls` for important details about ``ssh://``
4131 4133 URLs. If DESTINATION is omitted, a default path will be used.
4132 4134
4133 4135 .. container:: verbose
4134 4136
4135 4137 The --pushvars option sends strings to the server that become
4136 4138 environment variables prepended with ``HG_USERVAR_``. For example,
4137 4139 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4138 4140 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4139 4141
4140 4142 pushvars can provide for user-overridable hooks as well as set debug
4141 4143 levels. One example is having a hook that blocks commits containing
4142 4144 conflict markers, but enables the user to override the hook if the file
4143 4145 is using conflict markers for testing purposes or the file format has
4144 4146 strings that look like conflict markers.
4145 4147
4146 4148 By default, servers will ignore `--pushvars`. To enable it add the
4147 4149 following to your configuration file::
4148 4150
4149 4151 [push]
4150 4152 pushvars.server = true
4151 4153
4152 4154 Returns 0 if push was successful, 1 if nothing to push.
4153 4155 """
4154 4156
4155 4157 opts = pycompat.byteskwargs(opts)
4156 4158 if opts.get('bookmark'):
4157 4159 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4158 4160 for b in opts['bookmark']:
4159 4161 # translate -B options to -r so changesets get pushed
4160 4162 b = repo._bookmarks.expandname(b)
4161 4163 if b in repo._bookmarks:
4162 4164 opts.setdefault('rev', []).append(b)
4163 4165 else:
4164 4166 # if we try to push a deleted bookmark, translate it to null
4165 4167 # this lets simultaneous -r, -b options continue working
4166 4168 opts.setdefault('rev', []).append("null")
4167 4169
4168 4170 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4169 4171 if not path:
4170 4172 raise error.Abort(_('default repository not configured!'),
4171 4173 hint=_("see 'hg help config.paths'"))
4172 4174 dest = path.pushloc or path.loc
4173 4175 branches = (path.branch, opts.get('branch') or [])
4174 4176 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4175 4177 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4176 4178 other = hg.peer(repo, opts, dest)
4177 4179
4178 4180 if revs:
4179 4181 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4180 4182 if not revs:
4181 4183 raise error.Abort(_("specified revisions evaluate to an empty set"),
4182 4184 hint=_("use different revision arguments"))
4183 4185 elif path.pushrev:
4184 4186 # It doesn't make any sense to specify ancestor revisions. So limit
4185 4187 # to DAG heads to make discovery simpler.
4186 4188 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4187 4189 revs = scmutil.revrange(repo, [expr])
4188 4190 revs = [repo[rev].node() for rev in revs]
4189 4191 if not revs:
4190 4192 raise error.Abort(_('default push revset for path evaluates to an '
4191 4193 'empty set'))
4192 4194
4193 4195 repo._subtoppath = dest
4194 4196 try:
4195 4197 # push subrepos depth-first for coherent ordering
4196 4198 c = repo['.']
4197 4199 subs = c.substate # only repos that are committed
4198 4200 for s in sorted(subs):
4199 4201 result = c.sub(s).push(opts)
4200 4202 if result == 0:
4201 4203 return not result
4202 4204 finally:
4203 4205 del repo._subtoppath
4204 4206
4205 4207 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4206 4208 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4207 4209
4208 4210 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4209 4211 newbranch=opts.get('new_branch'),
4210 4212 bookmarks=opts.get('bookmark', ()),
4211 4213 opargs=opargs)
4212 4214
4213 4215 result = not pushop.cgresult
4214 4216
4215 4217 if pushop.bkresult is not None:
4216 4218 if pushop.bkresult == 2:
4217 4219 result = 2
4218 4220 elif not result and pushop.bkresult:
4219 4221 result = 2
4220 4222
4221 4223 return result
4222 4224
4223 4225 @command('recover', [])
4224 4226 def recover(ui, repo):
4225 4227 """roll back an interrupted transaction
4226 4228
4227 4229 Recover from an interrupted commit or pull.
4228 4230
4229 4231 This command tries to fix the repository status after an
4230 4232 interrupted operation. It should only be necessary when Mercurial
4231 4233 suggests it.
4232 4234
4233 4235 Returns 0 if successful, 1 if nothing to recover or verify fails.
4234 4236 """
4235 4237 if repo.recover():
4236 4238 return hg.verify(repo)
4237 4239 return 1
4238 4240
4239 4241 @command('^remove|rm',
4240 4242 [('A', 'after', None, _('record delete for missing files')),
4241 4243 ('f', 'force', None,
4242 4244 _('forget added files, delete modified files')),
4243 4245 ] + subrepoopts + walkopts + dryrunopts,
4244 4246 _('[OPTION]... FILE...'),
4245 4247 inferrepo=True)
4246 4248 def remove(ui, repo, *pats, **opts):
4247 4249 """remove the specified files on the next commit
4248 4250
4249 4251 Schedule the indicated files for removal from the current branch.
4250 4252
4251 4253 This command schedules the files to be removed at the next commit.
4252 4254 To undo a remove before that, see :hg:`revert`. To undo added
4253 4255 files, see :hg:`forget`.
4254 4256
4255 4257 .. container:: verbose
4256 4258
4257 4259 -A/--after can be used to remove only files that have already
4258 4260 been deleted, -f/--force can be used to force deletion, and -Af
4259 4261 can be used to remove files from the next revision without
4260 4262 deleting them from the working directory.
4261 4263
4262 4264 The following table details the behavior of remove for different
4263 4265 file states (columns) and option combinations (rows). The file
4264 4266 states are Added [A], Clean [C], Modified [M] and Missing [!]
4265 4267 (as reported by :hg:`status`). The actions are Warn, Remove
4266 4268 (from branch) and Delete (from disk):
4267 4269
4268 4270 ========= == == == ==
4269 4271 opt/state A C M !
4270 4272 ========= == == == ==
4271 4273 none W RD W R
4272 4274 -f R RD RD R
4273 4275 -A W W W R
4274 4276 -Af R R R R
4275 4277 ========= == == == ==
4276 4278
4277 4279 .. note::
4278 4280
4279 4281 :hg:`remove` never deletes files in Added [A] state from the
4280 4282 working directory, not even if ``--force`` is specified.
4281 4283
4282 4284 Returns 0 on success, 1 if any warnings encountered.
4283 4285 """
4284 4286
4285 4287 opts = pycompat.byteskwargs(opts)
4286 4288 after, force = opts.get('after'), opts.get('force')
4287 4289 dryrun = opts.get('dry_run')
4288 4290 if not pats and not after:
4289 4291 raise error.Abort(_('no files specified'))
4290 4292
4291 4293 m = scmutil.match(repo[None], pats, opts)
4292 4294 subrepos = opts.get('subrepos')
4293 4295 return cmdutil.remove(ui, repo, m, "", after, force, subrepos,
4294 4296 dryrun=dryrun)
4295 4297
4296 4298 @command('rename|move|mv',
4297 4299 [('A', 'after', None, _('record a rename that has already occurred')),
4298 4300 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4299 4301 ] + walkopts + dryrunopts,
4300 4302 _('[OPTION]... SOURCE... DEST'))
4301 4303 def rename(ui, repo, *pats, **opts):
4302 4304 """rename files; equivalent of copy + remove
4303 4305
4304 4306 Mark dest as copies of sources; mark sources for deletion. If dest
4305 4307 is a directory, copies are put in that directory. If dest is a
4306 4308 file, there can only be one source.
4307 4309
4308 4310 By default, this command copies the contents of files as they
4309 4311 exist in the working directory. If invoked with -A/--after, the
4310 4312 operation is recorded, but no copying is performed.
4311 4313
4312 4314 This command takes effect at the next commit. To undo a rename
4313 4315 before that, see :hg:`revert`.
4314 4316
4315 4317 Returns 0 on success, 1 if errors are encountered.
4316 4318 """
4317 4319 opts = pycompat.byteskwargs(opts)
4318 4320 with repo.wlock(False):
4319 4321 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4320 4322
4321 4323 @command('resolve',
4322 4324 [('a', 'all', None, _('select all unresolved files')),
4323 4325 ('l', 'list', None, _('list state of files needing merge')),
4324 4326 ('m', 'mark', None, _('mark files as resolved')),
4325 4327 ('u', 'unmark', None, _('mark files as unresolved')),
4326 4328 ('n', 'no-status', None, _('hide status prefix'))]
4327 4329 + mergetoolopts + walkopts + formatteropts,
4328 4330 _('[OPTION]... [FILE]...'),
4329 4331 inferrepo=True)
4330 4332 def resolve(ui, repo, *pats, **opts):
4331 4333 """redo merges or set/view the merge status of files
4332 4334
4333 4335 Merges with unresolved conflicts are often the result of
4334 4336 non-interactive merging using the ``internal:merge`` configuration
4335 4337 setting, or a command-line merge tool like ``diff3``. The resolve
4336 4338 command is used to manage the files involved in a merge, after
4337 4339 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4338 4340 working directory must have two parents). See :hg:`help
4339 4341 merge-tools` for information on configuring merge tools.
4340 4342
4341 4343 The resolve command can be used in the following ways:
4342 4344
4343 4345 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4344 4346 files, discarding any previous merge attempts. Re-merging is not
4345 4347 performed for files already marked as resolved. Use ``--all/-a``
4346 4348 to select all unresolved files. ``--tool`` can be used to specify
4347 4349 the merge tool used for the given files. It overrides the HGMERGE
4348 4350 environment variable and your configuration files. Previous file
4349 4351 contents are saved with a ``.orig`` suffix.
4350 4352
4351 4353 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4352 4354 (e.g. after having manually fixed-up the files). The default is
4353 4355 to mark all unresolved files.
4354 4356
4355 4357 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4356 4358 default is to mark all resolved files.
4357 4359
4358 4360 - :hg:`resolve -l`: list files which had or still have conflicts.
4359 4361 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4360 4362 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4361 4363 the list. See :hg:`help filesets` for details.
4362 4364
4363 4365 .. note::
4364 4366
4365 4367 Mercurial will not let you commit files with unresolved merge
4366 4368 conflicts. You must use :hg:`resolve -m ...` before you can
4367 4369 commit after a conflicting merge.
4368 4370
4369 4371 Returns 0 on success, 1 if any files fail a resolve attempt.
4370 4372 """
4371 4373
4372 4374 opts = pycompat.byteskwargs(opts)
4373 4375 flaglist = 'all mark unmark list no_status'.split()
4374 4376 all, mark, unmark, show, nostatus = \
4375 4377 [opts.get(o) for o in flaglist]
4376 4378
4377 4379 if (show and (mark or unmark)) or (mark and unmark):
4378 4380 raise error.Abort(_("too many options specified"))
4379 4381 if pats and all:
4380 4382 raise error.Abort(_("can't specify --all and patterns"))
4381 4383 if not (all or pats or show or mark or unmark):
4382 4384 raise error.Abort(_('no files or directories specified'),
4383 4385 hint=('use --all to re-merge all unresolved files'))
4384 4386
4385 4387 if show:
4386 4388 ui.pager('resolve')
4387 4389 fm = ui.formatter('resolve', opts)
4388 4390 ms = mergemod.mergestate.read(repo)
4389 4391 m = scmutil.match(repo[None], pats, opts)
4390 4392
4391 4393 # Labels and keys based on merge state. Unresolved path conflicts show
4392 4394 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4393 4395 # resolved conflicts.
4394 4396 mergestateinfo = {
4395 4397 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4396 4398 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4397 4399 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4398 4400 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4399 4401 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4400 4402 'D'),
4401 4403 }
4402 4404
4403 4405 for f in ms:
4404 4406 if not m(f):
4405 4407 continue
4406 4408
4407 4409 label, key = mergestateinfo[ms[f]]
4408 4410 fm.startitem()
4409 4411 fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
4410 4412 fm.write('path', '%s\n', f, label=label)
4411 4413 fm.end()
4412 4414 return 0
4413 4415
4414 4416 with repo.wlock():
4415 4417 ms = mergemod.mergestate.read(repo)
4416 4418
4417 4419 if not (ms.active() or repo.dirstate.p2() != nullid):
4418 4420 raise error.Abort(
4419 4421 _('resolve command not applicable when not merging'))
4420 4422
4421 4423 wctx = repo[None]
4422 4424
4423 4425 if (ms.mergedriver
4424 4426 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4425 4427 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4426 4428 ms.commit()
4427 4429 # allow mark and unmark to go through
4428 4430 if not mark and not unmark and not proceed:
4429 4431 return 1
4430 4432
4431 4433 m = scmutil.match(wctx, pats, opts)
4432 4434 ret = 0
4433 4435 didwork = False
4434 4436 runconclude = False
4435 4437
4436 4438 tocomplete = []
4437 4439 for f in ms:
4438 4440 if not m(f):
4439 4441 continue
4440 4442
4441 4443 didwork = True
4442 4444
4443 4445 # don't let driver-resolved files be marked, and run the conclude
4444 4446 # step if asked to resolve
4445 4447 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4446 4448 exact = m.exact(f)
4447 4449 if mark:
4448 4450 if exact:
4449 4451 ui.warn(_('not marking %s as it is driver-resolved\n')
4450 4452 % f)
4451 4453 elif unmark:
4452 4454 if exact:
4453 4455 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4454 4456 % f)
4455 4457 else:
4456 4458 runconclude = True
4457 4459 continue
4458 4460
4459 4461 # path conflicts must be resolved manually
4460 4462 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4461 4463 mergemod.MERGE_RECORD_RESOLVED_PATH):
4462 4464 if mark:
4463 4465 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4464 4466 elif unmark:
4465 4467 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4466 4468 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4467 4469 ui.warn(_('%s: path conflict must be resolved manually\n')
4468 4470 % f)
4469 4471 continue
4470 4472
4471 4473 if mark:
4472 4474 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4473 4475 elif unmark:
4474 4476 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4475 4477 else:
4476 4478 # backup pre-resolve (merge uses .orig for its own purposes)
4477 4479 a = repo.wjoin(f)
4478 4480 try:
4479 4481 util.copyfile(a, a + ".resolve")
4480 4482 except (IOError, OSError) as inst:
4481 4483 if inst.errno != errno.ENOENT:
4482 4484 raise
4483 4485
4484 4486 try:
4485 4487 # preresolve file
4486 4488 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4487 4489 'resolve')
4488 4490 complete, r = ms.preresolve(f, wctx)
4489 4491 if not complete:
4490 4492 tocomplete.append(f)
4491 4493 elif r:
4492 4494 ret = 1
4493 4495 finally:
4494 4496 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4495 4497 ms.commit()
4496 4498
4497 4499 # replace filemerge's .orig file with our resolve file, but only
4498 4500 # for merges that are complete
4499 4501 if complete:
4500 4502 try:
4501 4503 util.rename(a + ".resolve",
4502 4504 scmutil.origpath(ui, repo, a))
4503 4505 except OSError as inst:
4504 4506 if inst.errno != errno.ENOENT:
4505 4507 raise
4506 4508
4507 4509 for f in tocomplete:
4508 4510 try:
4509 4511 # resolve file
4510 4512 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4511 4513 'resolve')
4512 4514 r = ms.resolve(f, wctx)
4513 4515 if r:
4514 4516 ret = 1
4515 4517 finally:
4516 4518 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4517 4519 ms.commit()
4518 4520
4519 4521 # replace filemerge's .orig file with our resolve file
4520 4522 a = repo.wjoin(f)
4521 4523 try:
4522 4524 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4523 4525 except OSError as inst:
4524 4526 if inst.errno != errno.ENOENT:
4525 4527 raise
4526 4528
4527 4529 ms.commit()
4528 4530 ms.recordactions()
4529 4531
4530 4532 if not didwork and pats:
4531 4533 hint = None
4532 4534 if not any([p for p in pats if p.find(':') >= 0]):
4533 4535 pats = ['path:%s' % p for p in pats]
4534 4536 m = scmutil.match(wctx, pats, opts)
4535 4537 for f in ms:
4536 4538 if not m(f):
4537 4539 continue
4538 4540 flags = ''.join(['-%s ' % o[0:1] for o in flaglist
4539 4541 if opts.get(o)])
4540 4542 hint = _("(try: hg resolve %s%s)\n") % (
4541 4543 flags,
4542 4544 ' '.join(pats))
4543 4545 break
4544 4546 ui.warn(_("arguments do not match paths that need resolving\n"))
4545 4547 if hint:
4546 4548 ui.warn(hint)
4547 4549 elif ms.mergedriver and ms.mdstate() != 's':
4548 4550 # run conclude step when either a driver-resolved file is requested
4549 4551 # or there are no driver-resolved files
4550 4552 # we can't use 'ret' to determine whether any files are unresolved
4551 4553 # because we might not have tried to resolve some
4552 4554 if ((runconclude or not list(ms.driverresolved()))
4553 4555 and not list(ms.unresolved())):
4554 4556 proceed = mergemod.driverconclude(repo, ms, wctx)
4555 4557 ms.commit()
4556 4558 if not proceed:
4557 4559 return 1
4558 4560
4559 4561 # Nudge users into finishing an unfinished operation
4560 4562 unresolvedf = list(ms.unresolved())
4561 4563 driverresolvedf = list(ms.driverresolved())
4562 4564 if not unresolvedf and not driverresolvedf:
4563 4565 ui.status(_('(no more unresolved files)\n'))
4564 4566 cmdutil.checkafterresolved(repo)
4565 4567 elif not unresolvedf:
4566 4568 ui.status(_('(no more unresolved files -- '
4567 4569 'run "hg resolve --all" to conclude)\n'))
4568 4570
4569 4571 return ret
4570 4572
4571 4573 @command('revert',
4572 4574 [('a', 'all', None, _('revert all changes when no arguments given')),
4573 4575 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4574 4576 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4575 4577 ('C', 'no-backup', None, _('do not save backup copies of files')),
4576 4578 ('i', 'interactive', None, _('interactively select the changes')),
4577 4579 ] + walkopts + dryrunopts,
4578 4580 _('[OPTION]... [-r REV] [NAME]...'))
4579 4581 def revert(ui, repo, *pats, **opts):
4580 4582 """restore files to their checkout state
4581 4583
4582 4584 .. note::
4583 4585
4584 4586 To check out earlier revisions, you should use :hg:`update REV`.
4585 4587 To cancel an uncommitted merge (and lose your changes),
4586 4588 use :hg:`merge --abort`.
4587 4589
4588 4590 With no revision specified, revert the specified files or directories
4589 4591 to the contents they had in the parent of the working directory.
4590 4592 This restores the contents of files to an unmodified
4591 4593 state and unschedules adds, removes, copies, and renames. If the
4592 4594 working directory has two parents, you must explicitly specify a
4593 4595 revision.
4594 4596
4595 4597 Using the -r/--rev or -d/--date options, revert the given files or
4596 4598 directories to their states as of a specific revision. Because
4597 4599 revert does not change the working directory parents, this will
4598 4600 cause these files to appear modified. This can be helpful to "back
4599 4601 out" some or all of an earlier change. See :hg:`backout` for a
4600 4602 related method.
4601 4603
4602 4604 Modified files are saved with a .orig suffix before reverting.
4603 4605 To disable these backups, use --no-backup. It is possible to store
4604 4606 the backup files in a custom directory relative to the root of the
4605 4607 repository by setting the ``ui.origbackuppath`` configuration
4606 4608 option.
4607 4609
4608 4610 See :hg:`help dates` for a list of formats valid for -d/--date.
4609 4611
4610 4612 See :hg:`help backout` for a way to reverse the effect of an
4611 4613 earlier changeset.
4612 4614
4613 4615 Returns 0 on success.
4614 4616 """
4615 4617
4616 4618 opts = pycompat.byteskwargs(opts)
4617 4619 if opts.get("date"):
4618 4620 if opts.get("rev"):
4619 4621 raise error.Abort(_("you can't specify a revision and a date"))
4620 4622 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4621 4623
4622 4624 parent, p2 = repo.dirstate.parents()
4623 4625 if not opts.get('rev') and p2 != nullid:
4624 4626 # revert after merge is a trap for new users (issue2915)
4625 4627 raise error.Abort(_('uncommitted merge with no revision specified'),
4626 4628 hint=_("use 'hg update' or see 'hg help revert'"))
4627 4629
4628 4630 rev = opts.get('rev')
4629 4631 if rev:
4630 4632 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4631 4633 ctx = scmutil.revsingle(repo, rev)
4632 4634
4633 4635 if (not (pats or opts.get('include') or opts.get('exclude') or
4634 4636 opts.get('all') or opts.get('interactive'))):
4635 4637 msg = _("no files or directories specified")
4636 4638 if p2 != nullid:
4637 4639 hint = _("uncommitted merge, use --all to discard all changes,"
4638 4640 " or 'hg update -C .' to abort the merge")
4639 4641 raise error.Abort(msg, hint=hint)
4640 4642 dirty = any(repo.status())
4641 4643 node = ctx.node()
4642 4644 if node != parent:
4643 4645 if dirty:
4644 4646 hint = _("uncommitted changes, use --all to discard all"
4645 4647 " changes, or 'hg update %s' to update") % ctx.rev()
4646 4648 else:
4647 4649 hint = _("use --all to revert all files,"
4648 4650 " or 'hg update %s' to update") % ctx.rev()
4649 4651 elif dirty:
4650 4652 hint = _("uncommitted changes, use --all to discard all changes")
4651 4653 else:
4652 4654 hint = _("use --all to revert all files")
4653 4655 raise error.Abort(msg, hint=hint)
4654 4656
4655 4657 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
4656 4658 **pycompat.strkwargs(opts))
4657 4659
4658 4660 @command('rollback', dryrunopts +
4659 4661 [('f', 'force', False, _('ignore safety measures'))])
4660 4662 def rollback(ui, repo, **opts):
4661 4663 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4662 4664
4663 4665 Please use :hg:`commit --amend` instead of rollback to correct
4664 4666 mistakes in the last commit.
4665 4667
4666 4668 This command should be used with care. There is only one level of
4667 4669 rollback, and there is no way to undo a rollback. It will also
4668 4670 restore the dirstate at the time of the last transaction, losing
4669 4671 any dirstate changes since that time. This command does not alter
4670 4672 the working directory.
4671 4673
4672 4674 Transactions are used to encapsulate the effects of all commands
4673 4675 that create new changesets or propagate existing changesets into a
4674 4676 repository.
4675 4677
4676 4678 .. container:: verbose
4677 4679
4678 4680 For example, the following commands are transactional, and their
4679 4681 effects can be rolled back:
4680 4682
4681 4683 - commit
4682 4684 - import
4683 4685 - pull
4684 4686 - push (with this repository as the destination)
4685 4687 - unbundle
4686 4688
4687 4689 To avoid permanent data loss, rollback will refuse to rollback a
4688 4690 commit transaction if it isn't checked out. Use --force to
4689 4691 override this protection.
4690 4692
4691 4693 The rollback command can be entirely disabled by setting the
4692 4694 ``ui.rollback`` configuration setting to false. If you're here
4693 4695 because you want to use rollback and it's disabled, you can
4694 4696 re-enable the command by setting ``ui.rollback`` to true.
4695 4697
4696 4698 This command is not intended for use on public repositories. Once
4697 4699 changes are visible for pull by other users, rolling a transaction
4698 4700 back locally is ineffective (someone else may already have pulled
4699 4701 the changes). Furthermore, a race is possible with readers of the
4700 4702 repository; for example an in-progress pull from the repository
4701 4703 may fail if a rollback is performed.
4702 4704
4703 4705 Returns 0 on success, 1 if no rollback data is available.
4704 4706 """
4705 4707 if not ui.configbool('ui', 'rollback'):
4706 4708 raise error.Abort(_('rollback is disabled because it is unsafe'),
4707 4709 hint=('see `hg help -v rollback` for information'))
4708 4710 return repo.rollback(dryrun=opts.get(r'dry_run'),
4709 4711 force=opts.get(r'force'))
4710 4712
4711 4713 @command('root', [], intents={INTENT_READONLY})
4712 4714 def root(ui, repo):
4713 4715 """print the root (top) of the current working directory
4714 4716
4715 4717 Print the root directory of the current repository.
4716 4718
4717 4719 Returns 0 on success.
4718 4720 """
4719 4721 ui.write(repo.root + "\n")
4720 4722
4721 4723 @command('^serve',
4722 4724 [('A', 'accesslog', '', _('name of access log file to write to'),
4723 4725 _('FILE')),
4724 4726 ('d', 'daemon', None, _('run server in background')),
4725 4727 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4726 4728 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4727 4729 # use string type, then we can check if something was passed
4728 4730 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4729 4731 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4730 4732 _('ADDR')),
4731 4733 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4732 4734 _('PREFIX')),
4733 4735 ('n', 'name', '',
4734 4736 _('name to show in web pages (default: working directory)'), _('NAME')),
4735 4737 ('', 'web-conf', '',
4736 4738 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4737 4739 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4738 4740 _('FILE')),
4739 4741 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4740 4742 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4741 4743 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4742 4744 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4743 4745 ('', 'style', '', _('template style to use'), _('STYLE')),
4744 4746 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4745 4747 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4746 4748 + subrepoopts,
4747 4749 _('[OPTION]...'),
4748 4750 optionalrepo=True)
4749 4751 def serve(ui, repo, **opts):
4750 4752 """start stand-alone webserver
4751 4753
4752 4754 Start a local HTTP repository browser and pull server. You can use
4753 4755 this for ad-hoc sharing and browsing of repositories. It is
4754 4756 recommended to use a real web server to serve a repository for
4755 4757 longer periods of time.
4756 4758
4757 4759 Please note that the server does not implement access control.
4758 4760 This means that, by default, anybody can read from the server and
4759 4761 nobody can write to it by default. Set the ``web.allow-push``
4760 4762 option to ``*`` to allow everybody to push to the server. You
4761 4763 should use a real web server if you need to authenticate users.
4762 4764
4763 4765 By default, the server logs accesses to stdout and errors to
4764 4766 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4765 4767 files.
4766 4768
4767 4769 To have the server choose a free port number to listen on, specify
4768 4770 a port number of 0; in this case, the server will print the port
4769 4771 number it uses.
4770 4772
4771 4773 Returns 0 on success.
4772 4774 """
4773 4775
4774 4776 opts = pycompat.byteskwargs(opts)
4775 4777 if opts["stdio"] and opts["cmdserver"]:
4776 4778 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4777 4779
4778 4780 if opts["stdio"]:
4779 4781 if repo is None:
4780 4782 raise error.RepoError(_("there is no Mercurial repository here"
4781 4783 " (.hg not found)"))
4782 4784 s = wireprotoserver.sshserver(ui, repo)
4783 4785 s.serve_forever()
4784 4786
4785 4787 service = server.createservice(ui, repo, opts)
4786 4788 return server.runservice(opts, initfn=service.init, runfn=service.run)
4787 4789
4788 4790 @command('^status|st',
4789 4791 [('A', 'all', None, _('show status of all files')),
4790 4792 ('m', 'modified', None, _('show only modified files')),
4791 4793 ('a', 'added', None, _('show only added files')),
4792 4794 ('r', 'removed', None, _('show only removed files')),
4793 4795 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4794 4796 ('c', 'clean', None, _('show only files without changes')),
4795 4797 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4796 4798 ('i', 'ignored', None, _('show only ignored files')),
4797 4799 ('n', 'no-status', None, _('hide status prefix')),
4798 4800 ('t', 'terse', '', _('show the terse output (EXPERIMENTAL)')),
4799 4801 ('C', 'copies', None, _('show source of copied files')),
4800 4802 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4801 4803 ('', 'rev', [], _('show difference from revision'), _('REV')),
4802 4804 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4803 4805 ] + walkopts + subrepoopts + formatteropts,
4804 4806 _('[OPTION]... [FILE]...'),
4805 4807 inferrepo=True,
4806 4808 intents={INTENT_READONLY})
4807 4809 def status(ui, repo, *pats, **opts):
4808 4810 """show changed files in the working directory
4809 4811
4810 4812 Show status of files in the repository. If names are given, only
4811 4813 files that match are shown. Files that are clean or ignored or
4812 4814 the source of a copy/move operation, are not listed unless
4813 4815 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4814 4816 Unless options described with "show only ..." are given, the
4815 4817 options -mardu are used.
4816 4818
4817 4819 Option -q/--quiet hides untracked (unknown and ignored) files
4818 4820 unless explicitly requested with -u/--unknown or -i/--ignored.
4819 4821
4820 4822 .. note::
4821 4823
4822 4824 :hg:`status` may appear to disagree with diff if permissions have
4823 4825 changed or a merge has occurred. The standard diff format does
4824 4826 not report permission changes and diff only reports changes
4825 4827 relative to one merge parent.
4826 4828
4827 4829 If one revision is given, it is used as the base revision.
4828 4830 If two revisions are given, the differences between them are
4829 4831 shown. The --change option can also be used as a shortcut to list
4830 4832 the changed files of a revision from its first parent.
4831 4833
4832 4834 The codes used to show the status of files are::
4833 4835
4834 4836 M = modified
4835 4837 A = added
4836 4838 R = removed
4837 4839 C = clean
4838 4840 ! = missing (deleted by non-hg command, but still tracked)
4839 4841 ? = not tracked
4840 4842 I = ignored
4841 4843 = origin of the previous file (with --copies)
4842 4844
4843 4845 .. container:: verbose
4844 4846
4845 4847 The -t/--terse option abbreviates the output by showing only the directory
4846 4848 name if all the files in it share the same status. The option takes an
4847 4849 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
4848 4850 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
4849 4851 for 'ignored' and 'c' for clean.
4850 4852
4851 4853 It abbreviates only those statuses which are passed. Note that clean and
4852 4854 ignored files are not displayed with '--terse ic' unless the -c/--clean
4853 4855 and -i/--ignored options are also used.
4854 4856
4855 4857 The -v/--verbose option shows information when the repository is in an
4856 4858 unfinished merge, shelve, rebase state etc. You can have this behavior
4857 4859 turned on by default by enabling the ``commands.status.verbose`` option.
4858 4860
4859 4861 You can skip displaying some of these states by setting
4860 4862 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
4861 4863 'histedit', 'merge', 'rebase', or 'unshelve'.
4862 4864
4863 4865 Examples:
4864 4866
4865 4867 - show changes in the working directory relative to a
4866 4868 changeset::
4867 4869
4868 4870 hg status --rev 9353
4869 4871
4870 4872 - show changes in the working directory relative to the
4871 4873 current directory (see :hg:`help patterns` for more information)::
4872 4874
4873 4875 hg status re:
4874 4876
4875 4877 - show all changes including copies in an existing changeset::
4876 4878
4877 4879 hg status --copies --change 9353
4878 4880
4879 4881 - get a NUL separated list of added files, suitable for xargs::
4880 4882
4881 4883 hg status -an0
4882 4884
4883 4885 - show more information about the repository status, abbreviating
4884 4886 added, removed, modified, deleted, and untracked paths::
4885 4887
4886 4888 hg status -v -t mardu
4887 4889
4888 4890 Returns 0 on success.
4889 4891
4890 4892 """
4891 4893
4892 4894 opts = pycompat.byteskwargs(opts)
4893 4895 revs = opts.get('rev')
4894 4896 change = opts.get('change')
4895 4897 terse = opts.get('terse')
4896 4898
4897 4899 if revs and change:
4898 4900 msg = _('cannot specify --rev and --change at the same time')
4899 4901 raise error.Abort(msg)
4900 4902 elif revs and terse:
4901 4903 msg = _('cannot use --terse with --rev')
4902 4904 raise error.Abort(msg)
4903 4905 elif change:
4904 4906 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
4905 4907 ctx2 = scmutil.revsingle(repo, change, None)
4906 4908 ctx1 = ctx2.p1()
4907 4909 else:
4908 4910 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
4909 4911 ctx1, ctx2 = scmutil.revpair(repo, revs)
4910 4912
4911 4913 if pats or ui.configbool('commands', 'status.relative'):
4912 4914 cwd = repo.getcwd()
4913 4915 else:
4914 4916 cwd = ''
4915 4917
4916 4918 if opts.get('print0'):
4917 4919 end = '\0'
4918 4920 else:
4919 4921 end = '\n'
4920 4922 copy = {}
4921 4923 states = 'modified added removed deleted unknown ignored clean'.split()
4922 4924 show = [k for k in states if opts.get(k)]
4923 4925 if opts.get('all'):
4924 4926 show += ui.quiet and (states[:4] + ['clean']) or states
4925 4927
4926 4928 if not show:
4927 4929 if ui.quiet:
4928 4930 show = states[:4]
4929 4931 else:
4930 4932 show = states[:5]
4931 4933
4932 4934 m = scmutil.match(ctx2, pats, opts)
4933 4935 if terse:
4934 4936 # we need to compute clean and unknown to terse
4935 4937 stat = repo.status(ctx1.node(), ctx2.node(), m,
4936 4938 'ignored' in show or 'i' in terse,
4937 4939 True, True, opts.get('subrepos'))
4938 4940
4939 4941 stat = cmdutil.tersedir(stat, terse)
4940 4942 else:
4941 4943 stat = repo.status(ctx1.node(), ctx2.node(), m,
4942 4944 'ignored' in show, 'clean' in show,
4943 4945 'unknown' in show, opts.get('subrepos'))
4944 4946
4945 4947 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4946 4948
4947 4949 if (opts.get('all') or opts.get('copies')
4948 4950 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4949 4951 copy = copies.pathcopies(ctx1, ctx2, m)
4950 4952
4951 4953 ui.pager('status')
4952 4954 fm = ui.formatter('status', opts)
4953 4955 fmt = '%s' + end
4954 4956 showchar = not opts.get('no_status')
4955 4957
4956 4958 for state, char, files in changestates:
4957 4959 if state in show:
4958 4960 label = 'status.' + state
4959 4961 for f in files:
4960 4962 fm.startitem()
4961 4963 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4962 4964 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4963 4965 if f in copy:
4964 4966 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4965 4967 label='status.copied')
4966 4968
4967 4969 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
4968 4970 and not ui.plain()):
4969 4971 cmdutil.morestatus(repo, fm)
4970 4972 fm.end()
4971 4973
4972 4974 @command('^summary|sum',
4973 4975 [('', 'remote', None, _('check for push and pull'))],
4974 4976 '[--remote]',
4975 4977 intents={INTENT_READONLY})
4976 4978 def summary(ui, repo, **opts):
4977 4979 """summarize working directory state
4978 4980
4979 4981 This generates a brief summary of the working directory state,
4980 4982 including parents, branch, commit status, phase and available updates.
4981 4983
4982 4984 With the --remote option, this will check the default paths for
4983 4985 incoming and outgoing changes. This can be time-consuming.
4984 4986
4985 4987 Returns 0 on success.
4986 4988 """
4987 4989
4988 4990 opts = pycompat.byteskwargs(opts)
4989 4991 ui.pager('summary')
4990 4992 ctx = repo[None]
4991 4993 parents = ctx.parents()
4992 4994 pnode = parents[0].node()
4993 4995 marks = []
4994 4996
4995 4997 ms = None
4996 4998 try:
4997 4999 ms = mergemod.mergestate.read(repo)
4998 5000 except error.UnsupportedMergeRecords as e:
4999 5001 s = ' '.join(e.recordtypes)
5000 5002 ui.warn(
5001 5003 _('warning: merge state has unsupported record types: %s\n') % s)
5002 5004 unresolved = []
5003 5005 else:
5004 5006 unresolved = list(ms.unresolved())
5005 5007
5006 5008 for p in parents:
5007 5009 # label with log.changeset (instead of log.parent) since this
5008 5010 # shows a working directory parent *changeset*:
5009 5011 # i18n: column positioning for "hg summary"
5010 5012 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5011 5013 label=logcmdutil.changesetlabels(p))
5012 5014 ui.write(' '.join(p.tags()), label='log.tag')
5013 5015 if p.bookmarks():
5014 5016 marks.extend(p.bookmarks())
5015 5017 if p.rev() == -1:
5016 5018 if not len(repo):
5017 5019 ui.write(_(' (empty repository)'))
5018 5020 else:
5019 5021 ui.write(_(' (no revision checked out)'))
5020 5022 if p.obsolete():
5021 5023 ui.write(_(' (obsolete)'))
5022 5024 if p.isunstable():
5023 5025 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5024 5026 for instability in p.instabilities())
5025 5027 ui.write(' ('
5026 5028 + ', '.join(instabilities)
5027 5029 + ')')
5028 5030 ui.write('\n')
5029 5031 if p.description():
5030 5032 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5031 5033 label='log.summary')
5032 5034
5033 5035 branch = ctx.branch()
5034 5036 bheads = repo.branchheads(branch)
5035 5037 # i18n: column positioning for "hg summary"
5036 5038 m = _('branch: %s\n') % branch
5037 5039 if branch != 'default':
5038 5040 ui.write(m, label='log.branch')
5039 5041 else:
5040 5042 ui.status(m, label='log.branch')
5041 5043
5042 5044 if marks:
5043 5045 active = repo._activebookmark
5044 5046 # i18n: column positioning for "hg summary"
5045 5047 ui.write(_('bookmarks:'), label='log.bookmark')
5046 5048 if active is not None:
5047 5049 if active in marks:
5048 5050 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5049 5051 marks.remove(active)
5050 5052 else:
5051 5053 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5052 5054 for m in marks:
5053 5055 ui.write(' ' + m, label='log.bookmark')
5054 5056 ui.write('\n', label='log.bookmark')
5055 5057
5056 5058 status = repo.status(unknown=True)
5057 5059
5058 5060 c = repo.dirstate.copies()
5059 5061 copied, renamed = [], []
5060 5062 for d, s in c.iteritems():
5061 5063 if s in status.removed:
5062 5064 status.removed.remove(s)
5063 5065 renamed.append(d)
5064 5066 else:
5065 5067 copied.append(d)
5066 5068 if d in status.added:
5067 5069 status.added.remove(d)
5068 5070
5069 5071 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5070 5072
5071 5073 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5072 5074 (ui.label(_('%d added'), 'status.added'), status.added),
5073 5075 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5074 5076 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5075 5077 (ui.label(_('%d copied'), 'status.copied'), copied),
5076 5078 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5077 5079 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5078 5080 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5079 5081 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5080 5082 t = []
5081 5083 for l, s in labels:
5082 5084 if s:
5083 5085 t.append(l % len(s))
5084 5086
5085 5087 t = ', '.join(t)
5086 5088 cleanworkdir = False
5087 5089
5088 5090 if repo.vfs.exists('graftstate'):
5089 5091 t += _(' (graft in progress)')
5090 5092 if repo.vfs.exists('updatestate'):
5091 5093 t += _(' (interrupted update)')
5092 5094 elif len(parents) > 1:
5093 5095 t += _(' (merge)')
5094 5096 elif branch != parents[0].branch():
5095 5097 t += _(' (new branch)')
5096 5098 elif (parents[0].closesbranch() and
5097 5099 pnode in repo.branchheads(branch, closed=True)):
5098 5100 t += _(' (head closed)')
5099 5101 elif not (status.modified or status.added or status.removed or renamed or
5100 5102 copied or subs):
5101 5103 t += _(' (clean)')
5102 5104 cleanworkdir = True
5103 5105 elif pnode not in bheads:
5104 5106 t += _(' (new branch head)')
5105 5107
5106 5108 if parents:
5107 5109 pendingphase = max(p.phase() for p in parents)
5108 5110 else:
5109 5111 pendingphase = phases.public
5110 5112
5111 5113 if pendingphase > phases.newcommitphase(ui):
5112 5114 t += ' (%s)' % phases.phasenames[pendingphase]
5113 5115
5114 5116 if cleanworkdir:
5115 5117 # i18n: column positioning for "hg summary"
5116 5118 ui.status(_('commit: %s\n') % t.strip())
5117 5119 else:
5118 5120 # i18n: column positioning for "hg summary"
5119 5121 ui.write(_('commit: %s\n') % t.strip())
5120 5122
5121 5123 # all ancestors of branch heads - all ancestors of parent = new csets
5122 5124 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5123 5125 bheads))
5124 5126
5125 5127 if new == 0:
5126 5128 # i18n: column positioning for "hg summary"
5127 5129 ui.status(_('update: (current)\n'))
5128 5130 elif pnode not in bheads:
5129 5131 # i18n: column positioning for "hg summary"
5130 5132 ui.write(_('update: %d new changesets (update)\n') % new)
5131 5133 else:
5132 5134 # i18n: column positioning for "hg summary"
5133 5135 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5134 5136 (new, len(bheads)))
5135 5137
5136 5138 t = []
5137 5139 draft = len(repo.revs('draft()'))
5138 5140 if draft:
5139 5141 t.append(_('%d draft') % draft)
5140 5142 secret = len(repo.revs('secret()'))
5141 5143 if secret:
5142 5144 t.append(_('%d secret') % secret)
5143 5145
5144 5146 if draft or secret:
5145 5147 ui.status(_('phases: %s\n') % ', '.join(t))
5146 5148
5147 5149 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5148 5150 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5149 5151 numtrouble = len(repo.revs(trouble + "()"))
5150 5152 # We write all the possibilities to ease translation
5151 5153 troublemsg = {
5152 5154 "orphan": _("orphan: %d changesets"),
5153 5155 "contentdivergent": _("content-divergent: %d changesets"),
5154 5156 "phasedivergent": _("phase-divergent: %d changesets"),
5155 5157 }
5156 5158 if numtrouble > 0:
5157 5159 ui.status(troublemsg[trouble] % numtrouble + "\n")
5158 5160
5159 5161 cmdutil.summaryhooks(ui, repo)
5160 5162
5161 5163 if opts.get('remote'):
5162 5164 needsincoming, needsoutgoing = True, True
5163 5165 else:
5164 5166 needsincoming, needsoutgoing = False, False
5165 5167 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5166 5168 if i:
5167 5169 needsincoming = True
5168 5170 if o:
5169 5171 needsoutgoing = True
5170 5172 if not needsincoming and not needsoutgoing:
5171 5173 return
5172 5174
5173 5175 def getincoming():
5174 5176 source, branches = hg.parseurl(ui.expandpath('default'))
5175 5177 sbranch = branches[0]
5176 5178 try:
5177 5179 other = hg.peer(repo, {}, source)
5178 5180 except error.RepoError:
5179 5181 if opts.get('remote'):
5180 5182 raise
5181 5183 return source, sbranch, None, None, None
5182 5184 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5183 5185 if revs:
5184 5186 revs = [other.lookup(rev) for rev in revs]
5185 5187 ui.debug('comparing with %s\n' % util.hidepassword(source))
5186 5188 repo.ui.pushbuffer()
5187 5189 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5188 5190 repo.ui.popbuffer()
5189 5191 return source, sbranch, other, commoninc, commoninc[1]
5190 5192
5191 5193 if needsincoming:
5192 5194 source, sbranch, sother, commoninc, incoming = getincoming()
5193 5195 else:
5194 5196 source = sbranch = sother = commoninc = incoming = None
5195 5197
5196 5198 def getoutgoing():
5197 5199 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5198 5200 dbranch = branches[0]
5199 5201 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5200 5202 if source != dest:
5201 5203 try:
5202 5204 dother = hg.peer(repo, {}, dest)
5203 5205 except error.RepoError:
5204 5206 if opts.get('remote'):
5205 5207 raise
5206 5208 return dest, dbranch, None, None
5207 5209 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5208 5210 elif sother is None:
5209 5211 # there is no explicit destination peer, but source one is invalid
5210 5212 return dest, dbranch, None, None
5211 5213 else:
5212 5214 dother = sother
5213 5215 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5214 5216 common = None
5215 5217 else:
5216 5218 common = commoninc
5217 5219 if revs:
5218 5220 revs = [repo.lookup(rev) for rev in revs]
5219 5221 repo.ui.pushbuffer()
5220 5222 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5221 5223 commoninc=common)
5222 5224 repo.ui.popbuffer()
5223 5225 return dest, dbranch, dother, outgoing
5224 5226
5225 5227 if needsoutgoing:
5226 5228 dest, dbranch, dother, outgoing = getoutgoing()
5227 5229 else:
5228 5230 dest = dbranch = dother = outgoing = None
5229 5231
5230 5232 if opts.get('remote'):
5231 5233 t = []
5232 5234 if incoming:
5233 5235 t.append(_('1 or more incoming'))
5234 5236 o = outgoing.missing
5235 5237 if o:
5236 5238 t.append(_('%d outgoing') % len(o))
5237 5239 other = dother or sother
5238 5240 if 'bookmarks' in other.listkeys('namespaces'):
5239 5241 counts = bookmarks.summary(repo, other)
5240 5242 if counts[0] > 0:
5241 5243 t.append(_('%d incoming bookmarks') % counts[0])
5242 5244 if counts[1] > 0:
5243 5245 t.append(_('%d outgoing bookmarks') % counts[1])
5244 5246
5245 5247 if t:
5246 5248 # i18n: column positioning for "hg summary"
5247 5249 ui.write(_('remote: %s\n') % (', '.join(t)))
5248 5250 else:
5249 5251 # i18n: column positioning for "hg summary"
5250 5252 ui.status(_('remote: (synced)\n'))
5251 5253
5252 5254 cmdutil.summaryremotehooks(ui, repo, opts,
5253 5255 ((source, sbranch, sother, commoninc),
5254 5256 (dest, dbranch, dother, outgoing)))
5255 5257
5256 5258 @command('tag',
5257 5259 [('f', 'force', None, _('force tag')),
5258 5260 ('l', 'local', None, _('make the tag local')),
5259 5261 ('r', 'rev', '', _('revision to tag'), _('REV')),
5260 5262 ('', 'remove', None, _('remove a tag')),
5261 5263 # -l/--local is already there, commitopts cannot be used
5262 5264 ('e', 'edit', None, _('invoke editor on commit messages')),
5263 5265 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5264 5266 ] + commitopts2,
5265 5267 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5266 5268 def tag(ui, repo, name1, *names, **opts):
5267 5269 """add one or more tags for the current or given revision
5268 5270
5269 5271 Name a particular revision using <name>.
5270 5272
5271 5273 Tags are used to name particular revisions of the repository and are
5272 5274 very useful to compare different revisions, to go back to significant
5273 5275 earlier versions or to mark branch points as releases, etc. Changing
5274 5276 an existing tag is normally disallowed; use -f/--force to override.
5275 5277
5276 5278 If no revision is given, the parent of the working directory is
5277 5279 used.
5278 5280
5279 5281 To facilitate version control, distribution, and merging of tags,
5280 5282 they are stored as a file named ".hgtags" which is managed similarly
5281 5283 to other project files and can be hand-edited if necessary. This
5282 5284 also means that tagging creates a new commit. The file
5283 5285 ".hg/localtags" is used for local tags (not shared among
5284 5286 repositories).
5285 5287
5286 5288 Tag commits are usually made at the head of a branch. If the parent
5287 5289 of the working directory is not a branch head, :hg:`tag` aborts; use
5288 5290 -f/--force to force the tag commit to be based on a non-head
5289 5291 changeset.
5290 5292
5291 5293 See :hg:`help dates` for a list of formats valid for -d/--date.
5292 5294
5293 5295 Since tag names have priority over branch names during revision
5294 5296 lookup, using an existing branch name as a tag name is discouraged.
5295 5297
5296 5298 Returns 0 on success.
5297 5299 """
5298 5300 opts = pycompat.byteskwargs(opts)
5299 5301 wlock = lock = None
5300 5302 try:
5301 5303 wlock = repo.wlock()
5302 5304 lock = repo.lock()
5303 5305 rev_ = "."
5304 5306 names = [t.strip() for t in (name1,) + names]
5305 5307 if len(names) != len(set(names)):
5306 5308 raise error.Abort(_('tag names must be unique'))
5307 5309 for n in names:
5308 5310 scmutil.checknewlabel(repo, n, 'tag')
5309 5311 if not n:
5310 5312 raise error.Abort(_('tag names cannot consist entirely of '
5311 5313 'whitespace'))
5312 5314 if opts.get('rev') and opts.get('remove'):
5313 5315 raise error.Abort(_("--rev and --remove are incompatible"))
5314 5316 if opts.get('rev'):
5315 5317 rev_ = opts['rev']
5316 5318 message = opts.get('message')
5317 5319 if opts.get('remove'):
5318 5320 if opts.get('local'):
5319 5321 expectedtype = 'local'
5320 5322 else:
5321 5323 expectedtype = 'global'
5322 5324
5323 5325 for n in names:
5324 5326 if not repo.tagtype(n):
5325 5327 raise error.Abort(_("tag '%s' does not exist") % n)
5326 5328 if repo.tagtype(n) != expectedtype:
5327 5329 if expectedtype == 'global':
5328 5330 raise error.Abort(_("tag '%s' is not a global tag") % n)
5329 5331 else:
5330 5332 raise error.Abort(_("tag '%s' is not a local tag") % n)
5331 5333 rev_ = 'null'
5332 5334 if not message:
5333 5335 # we don't translate commit messages
5334 5336 message = 'Removed tag %s' % ', '.join(names)
5335 5337 elif not opts.get('force'):
5336 5338 for n in names:
5337 5339 if n in repo.tags():
5338 5340 raise error.Abort(_("tag '%s' already exists "
5339 5341 "(use -f to force)") % n)
5340 5342 if not opts.get('local'):
5341 5343 p1, p2 = repo.dirstate.parents()
5342 5344 if p2 != nullid:
5343 5345 raise error.Abort(_('uncommitted merge'))
5344 5346 bheads = repo.branchheads()
5345 5347 if not opts.get('force') and bheads and p1 not in bheads:
5346 5348 raise error.Abort(_('working directory is not at a branch head '
5347 5349 '(use -f to force)'))
5348 5350 node = scmutil.revsingle(repo, rev_).node()
5349 5351
5350 5352 if not message:
5351 5353 # we don't translate commit messages
5352 5354 message = ('Added tag %s for changeset %s' %
5353 5355 (', '.join(names), short(node)))
5354 5356
5355 5357 date = opts.get('date')
5356 5358 if date:
5357 5359 date = dateutil.parsedate(date)
5358 5360
5359 5361 if opts.get('remove'):
5360 5362 editform = 'tag.remove'
5361 5363 else:
5362 5364 editform = 'tag.add'
5363 5365 editor = cmdutil.getcommiteditor(editform=editform,
5364 5366 **pycompat.strkwargs(opts))
5365 5367
5366 5368 # don't allow tagging the null rev
5367 5369 if (not opts.get('remove') and
5368 5370 scmutil.revsingle(repo, rev_).rev() == nullrev):
5369 5371 raise error.Abort(_("cannot tag null revision"))
5370 5372
5371 5373 tagsmod.tag(repo, names, node, message, opts.get('local'),
5372 5374 opts.get('user'), date, editor=editor)
5373 5375 finally:
5374 5376 release(lock, wlock)
5375 5377
5376 5378 @command('tags', formatteropts, '', intents={INTENT_READONLY})
5377 5379 def tags(ui, repo, **opts):
5378 5380 """list repository tags
5379 5381
5380 5382 This lists both regular and local tags. When the -v/--verbose
5381 5383 switch is used, a third column "local" is printed for local tags.
5382 5384 When the -q/--quiet switch is used, only the tag name is printed.
5383 5385
5384 5386 Returns 0 on success.
5385 5387 """
5386 5388
5387 5389 opts = pycompat.byteskwargs(opts)
5388 5390 ui.pager('tags')
5389 5391 fm = ui.formatter('tags', opts)
5390 5392 hexfunc = fm.hexfunc
5391 5393 tagtype = ""
5392 5394
5393 5395 for t, n in reversed(repo.tagslist()):
5394 5396 hn = hexfunc(n)
5395 5397 label = 'tags.normal'
5396 5398 tagtype = ''
5397 5399 if repo.tagtype(t) == 'local':
5398 5400 label = 'tags.local'
5399 5401 tagtype = 'local'
5400 5402
5401 5403 fm.startitem()
5402 5404 fm.write('tag', '%s', t, label=label)
5403 5405 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5404 5406 fm.condwrite(not ui.quiet, 'rev node', fmt,
5405 5407 repo.changelog.rev(n), hn, label=label)
5406 5408 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5407 5409 tagtype, label=label)
5408 5410 fm.plain('\n')
5409 5411 fm.end()
5410 5412
5411 5413 @command('tip',
5412 5414 [('p', 'patch', None, _('show patch')),
5413 5415 ('g', 'git', None, _('use git extended diff format')),
5414 5416 ] + templateopts,
5415 5417 _('[-p] [-g]'))
5416 5418 def tip(ui, repo, **opts):
5417 5419 """show the tip revision (DEPRECATED)
5418 5420
5419 5421 The tip revision (usually just called the tip) is the changeset
5420 5422 most recently added to the repository (and therefore the most
5421 5423 recently changed head).
5422 5424
5423 5425 If you have just made a commit, that commit will be the tip. If
5424 5426 you have just pulled changes from another repository, the tip of
5425 5427 that repository becomes the current tip. The "tip" tag is special
5426 5428 and cannot be renamed or assigned to a different changeset.
5427 5429
5428 5430 This command is deprecated, please use :hg:`heads` instead.
5429 5431
5430 5432 Returns 0 on success.
5431 5433 """
5432 5434 opts = pycompat.byteskwargs(opts)
5433 5435 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5434 5436 displayer.show(repo['tip'])
5435 5437 displayer.close()
5436 5438
5437 5439 @command('unbundle',
5438 5440 [('u', 'update', None,
5439 5441 _('update to new branch head if changesets were unbundled'))],
5440 5442 _('[-u] FILE...'))
5441 5443 def unbundle(ui, repo, fname1, *fnames, **opts):
5442 5444 """apply one or more bundle files
5443 5445
5444 5446 Apply one or more bundle files generated by :hg:`bundle`.
5445 5447
5446 5448 Returns 0 on success, 1 if an update has unresolved files.
5447 5449 """
5448 5450 fnames = (fname1,) + fnames
5449 5451
5450 5452 with repo.lock():
5451 5453 for fname in fnames:
5452 5454 f = hg.openpath(ui, fname)
5453 5455 gen = exchange.readbundle(ui, f, fname)
5454 5456 if isinstance(gen, streamclone.streamcloneapplier):
5455 5457 raise error.Abort(
5456 5458 _('packed bundles cannot be applied with '
5457 5459 '"hg unbundle"'),
5458 5460 hint=_('use "hg debugapplystreamclonebundle"'))
5459 5461 url = 'bundle:' + fname
5460 5462 try:
5461 5463 txnname = 'unbundle'
5462 5464 if not isinstance(gen, bundle2.unbundle20):
5463 5465 txnname = 'unbundle\n%s' % util.hidepassword(url)
5464 5466 with repo.transaction(txnname) as tr:
5465 5467 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5466 5468 url=url)
5467 5469 except error.BundleUnknownFeatureError as exc:
5468 5470 raise error.Abort(
5469 5471 _('%s: unknown bundle feature, %s') % (fname, exc),
5470 5472 hint=_("see https://mercurial-scm.org/"
5471 5473 "wiki/BundleFeature for more "
5472 5474 "information"))
5473 5475 modheads = bundle2.combinechangegroupresults(op)
5474 5476
5475 5477 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5476 5478
5477 5479 @command('^update|up|checkout|co',
5478 5480 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5479 5481 ('c', 'check', None, _('require clean working directory')),
5480 5482 ('m', 'merge', None, _('merge uncommitted changes')),
5481 5483 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5482 5484 ('r', 'rev', '', _('revision'), _('REV'))
5483 5485 ] + mergetoolopts,
5484 5486 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5485 5487 def update(ui, repo, node=None, **opts):
5486 5488 """update working directory (or switch revisions)
5487 5489
5488 5490 Update the repository's working directory to the specified
5489 5491 changeset. If no changeset is specified, update to the tip of the
5490 5492 current named branch and move the active bookmark (see :hg:`help
5491 5493 bookmarks`).
5492 5494
5493 5495 Update sets the working directory's parent revision to the specified
5494 5496 changeset (see :hg:`help parents`).
5495 5497
5496 5498 If the changeset is not a descendant or ancestor of the working
5497 5499 directory's parent and there are uncommitted changes, the update is
5498 5500 aborted. With the -c/--check option, the working directory is checked
5499 5501 for uncommitted changes; if none are found, the working directory is
5500 5502 updated to the specified changeset.
5501 5503
5502 5504 .. container:: verbose
5503 5505
5504 5506 The -C/--clean, -c/--check, and -m/--merge options control what
5505 5507 happens if the working directory contains uncommitted changes.
5506 5508 At most of one of them can be specified.
5507 5509
5508 5510 1. If no option is specified, and if
5509 5511 the requested changeset is an ancestor or descendant of
5510 5512 the working directory's parent, the uncommitted changes
5511 5513 are merged into the requested changeset and the merged
5512 5514 result is left uncommitted. If the requested changeset is
5513 5515 not an ancestor or descendant (that is, it is on another
5514 5516 branch), the update is aborted and the uncommitted changes
5515 5517 are preserved.
5516 5518
5517 5519 2. With the -m/--merge option, the update is allowed even if the
5518 5520 requested changeset is not an ancestor or descendant of
5519 5521 the working directory's parent.
5520 5522
5521 5523 3. With the -c/--check option, the update is aborted and the
5522 5524 uncommitted changes are preserved.
5523 5525
5524 5526 4. With the -C/--clean option, uncommitted changes are discarded and
5525 5527 the working directory is updated to the requested changeset.
5526 5528
5527 5529 To cancel an uncommitted merge (and lose your changes), use
5528 5530 :hg:`merge --abort`.
5529 5531
5530 5532 Use null as the changeset to remove the working directory (like
5531 5533 :hg:`clone -U`).
5532 5534
5533 5535 If you want to revert just one file to an older revision, use
5534 5536 :hg:`revert [-r REV] NAME`.
5535 5537
5536 5538 See :hg:`help dates` for a list of formats valid for -d/--date.
5537 5539
5538 5540 Returns 0 on success, 1 if there are unresolved files.
5539 5541 """
5540 5542 rev = opts.get(r'rev')
5541 5543 date = opts.get(r'date')
5542 5544 clean = opts.get(r'clean')
5543 5545 check = opts.get(r'check')
5544 5546 merge = opts.get(r'merge')
5545 5547 if rev and node:
5546 5548 raise error.Abort(_("please specify just one revision"))
5547 5549
5548 5550 if ui.configbool('commands', 'update.requiredest'):
5549 5551 if not node and not rev and not date:
5550 5552 raise error.Abort(_('you must specify a destination'),
5551 5553 hint=_('for example: hg update ".::"'))
5552 5554
5553 5555 if rev is None or rev == '':
5554 5556 rev = node
5555 5557
5556 5558 if date and rev is not None:
5557 5559 raise error.Abort(_("you can't specify a revision and a date"))
5558 5560
5559 5561 if len([x for x in (clean, check, merge) if x]) > 1:
5560 5562 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5561 5563 "or -m/--merge"))
5562 5564
5563 5565 updatecheck = None
5564 5566 if check:
5565 5567 updatecheck = 'abort'
5566 5568 elif merge:
5567 5569 updatecheck = 'none'
5568 5570
5569 5571 with repo.wlock():
5570 5572 cmdutil.clearunfinished(repo)
5571 5573
5572 5574 if date:
5573 5575 rev = cmdutil.finddate(ui, repo, date)
5574 5576
5575 5577 # if we defined a bookmark, we have to remember the original name
5576 5578 brev = rev
5577 5579 if rev:
5578 5580 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5579 5581 ctx = scmutil.revsingle(repo, rev, rev)
5580 5582 rev = ctx.rev()
5581 5583 if ctx.hidden():
5582 5584 ctxstr = ctx.hex()[:12]
5583 5585 ui.warn(_("updating to a hidden changeset %s\n") % ctxstr)
5584 5586
5585 5587 if ctx.obsolete():
5586 5588 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
5587 5589 ui.warn("(%s)\n" % obsfatemsg)
5588 5590
5589 5591 repo.ui.setconfig('ui', 'forcemerge', opts.get(r'tool'), 'update')
5590 5592
5591 5593 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5592 5594 updatecheck=updatecheck)
5593 5595
5594 5596 @command('verify', [])
5595 5597 def verify(ui, repo):
5596 5598 """verify the integrity of the repository
5597 5599
5598 5600 Verify the integrity of the current repository.
5599 5601
5600 5602 This will perform an extensive check of the repository's
5601 5603 integrity, validating the hashes and checksums of each entry in
5602 5604 the changelog, manifest, and tracked files, as well as the
5603 5605 integrity of their crosslinks and indices.
5604 5606
5605 5607 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5606 5608 for more information about recovery from corruption of the
5607 5609 repository.
5608 5610
5609 5611 Returns 0 on success, 1 if errors are encountered.
5610 5612 """
5611 5613 return hg.verify(repo)
5612 5614
5613 5615 @command('version', [] + formatteropts, norepo=True,
5614 5616 intents={INTENT_READONLY})
5615 5617 def version_(ui, **opts):
5616 5618 """output version and copyright information"""
5617 5619 opts = pycompat.byteskwargs(opts)
5618 5620 if ui.verbose:
5619 5621 ui.pager('version')
5620 5622 fm = ui.formatter("version", opts)
5621 5623 fm.startitem()
5622 5624 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5623 5625 util.version())
5624 5626 license = _(
5625 5627 "(see https://mercurial-scm.org for more information)\n"
5626 5628 "\nCopyright (C) 2005-2018 Matt Mackall and others\n"
5627 5629 "This is free software; see the source for copying conditions. "
5628 5630 "There is NO\nwarranty; "
5629 5631 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5630 5632 )
5631 5633 if not ui.quiet:
5632 5634 fm.plain(license)
5633 5635
5634 5636 if ui.verbose:
5635 5637 fm.plain(_("\nEnabled extensions:\n\n"))
5636 5638 # format names and versions into columns
5637 5639 names = []
5638 5640 vers = []
5639 5641 isinternals = []
5640 5642 for name, module in extensions.extensions():
5641 5643 names.append(name)
5642 5644 vers.append(extensions.moduleversion(module) or None)
5643 5645 isinternals.append(extensions.ismoduleinternal(module))
5644 5646 fn = fm.nested("extensions", tmpl='{name}\n')
5645 5647 if names:
5646 5648 namefmt = " %%-%ds " % max(len(n) for n in names)
5647 5649 places = [_("external"), _("internal")]
5648 5650 for n, v, p in zip(names, vers, isinternals):
5649 5651 fn.startitem()
5650 5652 fn.condwrite(ui.verbose, "name", namefmt, n)
5651 5653 if ui.verbose:
5652 5654 fn.plain("%s " % places[p])
5653 5655 fn.data(bundled=p)
5654 5656 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5655 5657 if ui.verbose:
5656 5658 fn.plain("\n")
5657 5659 fn.end()
5658 5660 fm.end()
5659 5661
5660 5662 def loadcmdtable(ui, name, cmdtable):
5661 5663 """Load command functions from specified cmdtable
5662 5664 """
5663 5665 overrides = [cmd for cmd in cmdtable if cmd in table]
5664 5666 if overrides:
5665 5667 ui.warn(_("extension '%s' overrides commands: %s\n")
5666 5668 % (name, " ".join(overrides)))
5667 5669 table.update(cmdtable)
@@ -1,1814 +1,1814
1 1 # subrepo.py - sub-repository classes and factory
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 from __future__ import absolute_import
9 9
10 10 import copy
11 11 import errno
12 12 import hashlib
13 13 import os
14 14 import posixpath
15 15 import re
16 16 import stat
17 17 import subprocess
18 18 import sys
19 19 import tarfile
20 20 import xml.dom.minidom
21 21
22 22 from .i18n import _
23 23 from . import (
24 24 cmdutil,
25 25 encoding,
26 26 error,
27 27 exchange,
28 28 logcmdutil,
29 29 match as matchmod,
30 30 node,
31 31 pathutil,
32 32 phases,
33 33 pycompat,
34 34 scmutil,
35 35 subrepoutil,
36 36 util,
37 37 vfs as vfsmod,
38 38 )
39 39 from .utils import (
40 40 dateutil,
41 41 procutil,
42 42 stringutil,
43 43 )
44 44
45 45 hg = None
46 46 reporelpath = subrepoutil.reporelpath
47 47 subrelpath = subrepoutil.subrelpath
48 48 _abssource = subrepoutil._abssource
49 49 propertycache = util.propertycache
50 50
51 51 def _expandedabspath(path):
52 52 '''
53 53 get a path or url and if it is a path expand it and return an absolute path
54 54 '''
55 55 expandedpath = util.urllocalpath(util.expandpath(path))
56 56 u = util.url(expandedpath)
57 57 if not u.scheme:
58 58 path = util.normpath(os.path.abspath(u.path))
59 59 return path
60 60
61 61 def _getstorehashcachename(remotepath):
62 62 '''get a unique filename for the store hash cache of a remote repository'''
63 63 return node.hex(hashlib.sha1(_expandedabspath(remotepath)).digest())[0:12]
64 64
65 65 class SubrepoAbort(error.Abort):
66 66 """Exception class used to avoid handling a subrepo error more than once"""
67 67 def __init__(self, *args, **kw):
68 68 self.subrepo = kw.pop(r'subrepo', None)
69 69 self.cause = kw.pop(r'cause', None)
70 70 error.Abort.__init__(self, *args, **kw)
71 71
72 72 def annotatesubrepoerror(func):
73 73 def decoratedmethod(self, *args, **kargs):
74 74 try:
75 75 res = func(self, *args, **kargs)
76 76 except SubrepoAbort as ex:
77 77 # This exception has already been handled
78 78 raise ex
79 79 except error.Abort as ex:
80 80 subrepo = subrelpath(self)
81 81 errormsg = (stringutil.forcebytestr(ex) + ' '
82 82 + _('(in subrepository "%s")') % subrepo)
83 83 # avoid handling this exception by raising a SubrepoAbort exception
84 84 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
85 85 cause=sys.exc_info())
86 86 return res
87 87 return decoratedmethod
88 88
89 89 def _updateprompt(ui, sub, dirty, local, remote):
90 90 if dirty:
91 91 msg = (_(' subrepository sources for %s differ\n'
92 92 'use (l)ocal source (%s) or (r)emote source (%s)?'
93 93 '$$ &Local $$ &Remote')
94 94 % (subrelpath(sub), local, remote))
95 95 else:
96 96 msg = (_(' subrepository sources for %s differ (in checked out '
97 97 'version)\n'
98 98 'use (l)ocal source (%s) or (r)emote source (%s)?'
99 99 '$$ &Local $$ &Remote')
100 100 % (subrelpath(sub), local, remote))
101 101 return ui.promptchoice(msg, 0)
102 102
103 103 def _sanitize(ui, vfs, ignore):
104 104 for dirname, dirs, names in vfs.walk():
105 105 for i, d in enumerate(dirs):
106 106 if d.lower() == ignore:
107 107 del dirs[i]
108 108 break
109 109 if vfs.basename(dirname).lower() != '.hg':
110 110 continue
111 111 for f in names:
112 112 if f.lower() == 'hgrc':
113 113 ui.warn(_("warning: removing potentially hostile 'hgrc' "
114 114 "in '%s'\n") % vfs.join(dirname))
115 115 vfs.unlink(vfs.reljoin(dirname, f))
116 116
117 117 def _auditsubrepopath(repo, path):
118 118 # auditor doesn't check if the path itself is a symlink
119 119 pathutil.pathauditor(repo.root)(path)
120 120 if repo.wvfs.islink(path):
121 121 raise error.Abort(_("subrepo '%s' traverses symbolic link") % path)
122 122
123 123 SUBREPO_ALLOWED_DEFAULTS = {
124 124 'hg': True,
125 125 'git': False,
126 126 'svn': False,
127 127 }
128 128
129 129 def _checktype(ui, kind):
130 130 # subrepos.allowed is a master kill switch. If disabled, subrepos are
131 131 # disabled period.
132 132 if not ui.configbool('subrepos', 'allowed', True):
133 133 raise error.Abort(_('subrepos not enabled'),
134 134 hint=_("see 'hg help config.subrepos' for details"))
135 135
136 136 default = SUBREPO_ALLOWED_DEFAULTS.get(kind, False)
137 137 if not ui.configbool('subrepos', '%s:allowed' % kind, default):
138 138 raise error.Abort(_('%s subrepos not allowed') % kind,
139 139 hint=_("see 'hg help config.subrepos' for details"))
140 140
141 141 if kind not in types:
142 142 raise error.Abort(_('unknown subrepo type %s') % kind)
143 143
144 144 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
145 145 """return instance of the right subrepo class for subrepo in path"""
146 146 # subrepo inherently violates our import layering rules
147 147 # because it wants to make repo objects from deep inside the stack
148 148 # so we manually delay the circular imports to not break
149 149 # scripts that don't use our demand-loading
150 150 global hg
151 151 from . import hg as h
152 152 hg = h
153 153
154 154 repo = ctx.repo()
155 155 _auditsubrepopath(repo, path)
156 156 state = ctx.substate[path]
157 157 _checktype(repo.ui, state[2])
158 158 if allowwdir:
159 159 state = (state[0], ctx.subrev(path), state[2])
160 160 return types[state[2]](ctx, path, state[:2], allowcreate)
161 161
162 162 def nullsubrepo(ctx, path, pctx):
163 163 """return an empty subrepo in pctx for the extant subrepo in ctx"""
164 164 # subrepo inherently violates our import layering rules
165 165 # because it wants to make repo objects from deep inside the stack
166 166 # so we manually delay the circular imports to not break
167 167 # scripts that don't use our demand-loading
168 168 global hg
169 169 from . import hg as h
170 170 hg = h
171 171
172 172 repo = ctx.repo()
173 173 _auditsubrepopath(repo, path)
174 174 state = ctx.substate[path]
175 175 _checktype(repo.ui, state[2])
176 176 subrev = ''
177 177 if state[2] == 'hg':
178 178 subrev = "0" * 40
179 179 return types[state[2]](pctx, path, (state[0], subrev), True)
180 180
181 181 # subrepo classes need to implement the following abstract class:
182 182
183 183 class abstractsubrepo(object):
184 184
185 185 def __init__(self, ctx, path):
186 186 """Initialize abstractsubrepo part
187 187
188 188 ``ctx`` is the context referring this subrepository in the
189 189 parent repository.
190 190
191 191 ``path`` is the path to this subrepository as seen from
192 192 innermost repository.
193 193 """
194 194 self.ui = ctx.repo().ui
195 195 self._ctx = ctx
196 196 self._path = path
197 197
198 198 def addwebdirpath(self, serverpath, webconf):
199 199 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
200 200
201 201 ``serverpath`` is the path component of the URL for this repo.
202 202
203 203 ``webconf`` is the dictionary of hgwebdir entries.
204 204 """
205 205 pass
206 206
207 207 def storeclean(self, path):
208 208 """
209 209 returns true if the repository has not changed since it was last
210 210 cloned from or pushed to a given repository.
211 211 """
212 212 return False
213 213
214 214 def dirty(self, ignoreupdate=False, missing=False):
215 215 """returns true if the dirstate of the subrepo is dirty or does not
216 216 match current stored state. If ignoreupdate is true, only check
217 217 whether the subrepo has uncommitted changes in its dirstate. If missing
218 218 is true, check for deleted files.
219 219 """
220 220 raise NotImplementedError
221 221
222 222 def dirtyreason(self, ignoreupdate=False, missing=False):
223 223 """return reason string if it is ``dirty()``
224 224
225 225 Returned string should have enough information for the message
226 226 of exception.
227 227
228 228 This returns None, otherwise.
229 229 """
230 230 if self.dirty(ignoreupdate=ignoreupdate, missing=missing):
231 231 return _('uncommitted changes in subrepository "%s"'
232 232 ) % subrelpath(self)
233 233
234 234 def bailifchanged(self, ignoreupdate=False, hint=None):
235 235 """raise Abort if subrepository is ``dirty()``
236 236 """
237 237 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate,
238 238 missing=True)
239 239 if dirtyreason:
240 240 raise error.Abort(dirtyreason, hint=hint)
241 241
242 242 def basestate(self):
243 243 """current working directory base state, disregarding .hgsubstate
244 244 state and working directory modifications"""
245 245 raise NotImplementedError
246 246
247 247 def checknested(self, path):
248 248 """check if path is a subrepository within this repository"""
249 249 return False
250 250
251 251 def commit(self, text, user, date):
252 252 """commit the current changes to the subrepo with the given
253 253 log message. Use given user and date if possible. Return the
254 254 new state of the subrepo.
255 255 """
256 256 raise NotImplementedError
257 257
258 258 def phase(self, state):
259 259 """returns phase of specified state in the subrepository.
260 260 """
261 261 return phases.public
262 262
263 263 def remove(self):
264 264 """remove the subrepo
265 265
266 266 (should verify the dirstate is not dirty first)
267 267 """
268 268 raise NotImplementedError
269 269
270 270 def get(self, state, overwrite=False):
271 271 """run whatever commands are needed to put the subrepo into
272 272 this state
273 273 """
274 274 raise NotImplementedError
275 275
276 276 def merge(self, state):
277 277 """merge currently-saved state with the new state."""
278 278 raise NotImplementedError
279 279
280 280 def push(self, opts):
281 281 """perform whatever action is analogous to 'hg push'
282 282
283 283 This may be a no-op on some systems.
284 284 """
285 285 raise NotImplementedError
286 286
287 287 def add(self, ui, match, prefix, explicitonly, **opts):
288 288 return []
289 289
290 290 def addremove(self, matcher, prefix, opts):
291 291 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
292 292 return 1
293 293
294 294 def cat(self, match, fm, fntemplate, prefix, **opts):
295 295 return 1
296 296
297 297 def status(self, rev2, **opts):
298 298 return scmutil.status([], [], [], [], [], [], [])
299 299
300 300 def diff(self, ui, diffopts, node2, match, prefix, **opts):
301 301 pass
302 302
303 303 def outgoing(self, ui, dest, opts):
304 304 return 1
305 305
306 306 def incoming(self, ui, source, opts):
307 307 return 1
308 308
309 309 def files(self):
310 310 """return filename iterator"""
311 311 raise NotImplementedError
312 312
313 313 def filedata(self, name, decode):
314 314 """return file data, optionally passed through repo decoders"""
315 315 raise NotImplementedError
316 316
317 317 def fileflags(self, name):
318 318 """return file flags"""
319 319 return ''
320 320
321 321 def getfileset(self, expr):
322 322 """Resolve the fileset expression for this repo"""
323 323 return set()
324 324
325 325 def printfiles(self, ui, m, fm, fmt, subrepos):
326 326 """handle the files command for this subrepo"""
327 327 return 1
328 328
329 329 def archive(self, archiver, prefix, match=None, decode=True):
330 330 if match is not None:
331 331 files = [f for f in self.files() if match(f)]
332 332 else:
333 333 files = self.files()
334 334 total = len(files)
335 335 relpath = subrelpath(self)
336 336 self.ui.progress(_('archiving (%s)') % relpath, 0,
337 337 unit=_('files'), total=total)
338 338 for i, name in enumerate(files):
339 339 flags = self.fileflags(name)
340 340 mode = 'x' in flags and 0o755 or 0o644
341 341 symlink = 'l' in flags
342 342 archiver.addfile(prefix + self._path + '/' + name,
343 343 mode, symlink, self.filedata(name, decode))
344 344 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
345 345 unit=_('files'), total=total)
346 346 self.ui.progress(_('archiving (%s)') % relpath, None)
347 347 return total
348 348
349 349 def walk(self, match):
350 350 '''
351 351 walk recursively through the directory tree, finding all files
352 352 matched by the match function
353 353 '''
354 354
355 def forget(self, match, prefix, dryrun):
355 def forget(self, match, prefix, dryrun, confirm):
356 356 return ([], [])
357 357
358 358 def removefiles(self, matcher, prefix, after, force, subrepos,
359 359 dryrun, warnings):
360 360 """remove the matched files from the subrepository and the filesystem,
361 361 possibly by force and/or after the file has been removed from the
362 362 filesystem. Return 0 on success, 1 on any warning.
363 363 """
364 364 warnings.append(_("warning: removefiles not implemented (%s)")
365 365 % self._path)
366 366 return 1
367 367
368 368 def revert(self, substate, *pats, **opts):
369 369 self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
370 370 % (substate[0], substate[2]))
371 371 return []
372 372
373 373 def shortid(self, revid):
374 374 return revid
375 375
376 376 def unshare(self):
377 377 '''
378 378 convert this repository from shared to normal storage.
379 379 '''
380 380
381 381 def verify(self):
382 382 '''verify the integrity of the repository. Return 0 on success or
383 383 warning, 1 on any error.
384 384 '''
385 385 return 0
386 386
387 387 @propertycache
388 388 def wvfs(self):
389 389 """return vfs to access the working directory of this subrepository
390 390 """
391 391 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
392 392
393 393 @propertycache
394 394 def _relpath(self):
395 395 """return path to this subrepository as seen from outermost repository
396 396 """
397 397 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
398 398
399 399 class hgsubrepo(abstractsubrepo):
400 400 def __init__(self, ctx, path, state, allowcreate):
401 401 super(hgsubrepo, self).__init__(ctx, path)
402 402 self._state = state
403 403 r = ctx.repo()
404 404 root = r.wjoin(path)
405 405 create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
406 406 self._repo = hg.repository(r.baseui, root, create=create)
407 407
408 408 # Propagate the parent's --hidden option
409 409 if r is r.unfiltered():
410 410 self._repo = self._repo.unfiltered()
411 411
412 412 self.ui = self._repo.ui
413 413 for s, k in [('ui', 'commitsubrepos')]:
414 414 v = r.ui.config(s, k)
415 415 if v:
416 416 self.ui.setconfig(s, k, v, 'subrepo')
417 417 # internal config: ui._usedassubrepo
418 418 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
419 419 self._initrepo(r, state[0], create)
420 420
421 421 @annotatesubrepoerror
422 422 def addwebdirpath(self, serverpath, webconf):
423 423 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
424 424
425 425 def storeclean(self, path):
426 426 with self._repo.lock():
427 427 return self._storeclean(path)
428 428
429 429 def _storeclean(self, path):
430 430 clean = True
431 431 itercache = self._calcstorehash(path)
432 432 for filehash in self._readstorehashcache(path):
433 433 if filehash != next(itercache, None):
434 434 clean = False
435 435 break
436 436 if clean:
437 437 # if not empty:
438 438 # the cached and current pull states have a different size
439 439 clean = next(itercache, None) is None
440 440 return clean
441 441
442 442 def _calcstorehash(self, remotepath):
443 443 '''calculate a unique "store hash"
444 444
445 445 This method is used to to detect when there are changes that may
446 446 require a push to a given remote path.'''
447 447 # sort the files that will be hashed in increasing (likely) file size
448 448 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
449 449 yield '# %s\n' % _expandedabspath(remotepath)
450 450 vfs = self._repo.vfs
451 451 for relname in filelist:
452 452 filehash = node.hex(hashlib.sha1(vfs.tryread(relname)).digest())
453 453 yield '%s = %s\n' % (relname, filehash)
454 454
455 455 @propertycache
456 456 def _cachestorehashvfs(self):
457 457 return vfsmod.vfs(self._repo.vfs.join('cache/storehash'))
458 458
459 459 def _readstorehashcache(self, remotepath):
460 460 '''read the store hash cache for a given remote repository'''
461 461 cachefile = _getstorehashcachename(remotepath)
462 462 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
463 463
464 464 def _cachestorehash(self, remotepath):
465 465 '''cache the current store hash
466 466
467 467 Each remote repo requires its own store hash cache, because a subrepo
468 468 store may be "clean" versus a given remote repo, but not versus another
469 469 '''
470 470 cachefile = _getstorehashcachename(remotepath)
471 471 with self._repo.lock():
472 472 storehash = list(self._calcstorehash(remotepath))
473 473 vfs = self._cachestorehashvfs
474 474 vfs.writelines(cachefile, storehash, mode='wb', notindexed=True)
475 475
476 476 def _getctx(self):
477 477 '''fetch the context for this subrepo revision, possibly a workingctx
478 478 '''
479 479 if self._ctx.rev() is None:
480 480 return self._repo[None] # workingctx if parent is workingctx
481 481 else:
482 482 rev = self._state[1]
483 483 return self._repo[rev]
484 484
485 485 @annotatesubrepoerror
486 486 def _initrepo(self, parentrepo, source, create):
487 487 self._repo._subparent = parentrepo
488 488 self._repo._subsource = source
489 489
490 490 if create:
491 491 lines = ['[paths]\n']
492 492
493 493 def addpathconfig(key, value):
494 494 if value:
495 495 lines.append('%s = %s\n' % (key, value))
496 496 self.ui.setconfig('paths', key, value, 'subrepo')
497 497
498 498 defpath = _abssource(self._repo, abort=False)
499 499 defpushpath = _abssource(self._repo, True, abort=False)
500 500 addpathconfig('default', defpath)
501 501 if defpath != defpushpath:
502 502 addpathconfig('default-push', defpushpath)
503 503
504 504 self._repo.vfs.write('hgrc', util.tonativeeol(''.join(lines)))
505 505
506 506 @annotatesubrepoerror
507 507 def add(self, ui, match, prefix, explicitonly, **opts):
508 508 return cmdutil.add(ui, self._repo, match,
509 509 self.wvfs.reljoin(prefix, self._path),
510 510 explicitonly, **opts)
511 511
512 512 @annotatesubrepoerror
513 513 def addremove(self, m, prefix, opts):
514 514 # In the same way as sub directories are processed, once in a subrepo,
515 515 # always entry any of its subrepos. Don't corrupt the options that will
516 516 # be used to process sibling subrepos however.
517 517 opts = copy.copy(opts)
518 518 opts['subrepos'] = True
519 519 return scmutil.addremove(self._repo, m,
520 520 self.wvfs.reljoin(prefix, self._path), opts)
521 521
522 522 @annotatesubrepoerror
523 523 def cat(self, match, fm, fntemplate, prefix, **opts):
524 524 rev = self._state[1]
525 525 ctx = self._repo[rev]
526 526 return cmdutil.cat(self.ui, self._repo, ctx, match, fm, fntemplate,
527 527 prefix, **opts)
528 528
529 529 @annotatesubrepoerror
530 530 def status(self, rev2, **opts):
531 531 try:
532 532 rev1 = self._state[1]
533 533 ctx1 = self._repo[rev1]
534 534 ctx2 = self._repo[rev2]
535 535 return self._repo.status(ctx1, ctx2, **opts)
536 536 except error.RepoLookupError as inst:
537 537 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
538 538 % (inst, subrelpath(self)))
539 539 return scmutil.status([], [], [], [], [], [], [])
540 540
541 541 @annotatesubrepoerror
542 542 def diff(self, ui, diffopts, node2, match, prefix, **opts):
543 543 try:
544 544 node1 = node.bin(self._state[1])
545 545 # We currently expect node2 to come from substate and be
546 546 # in hex format
547 547 if node2 is not None:
548 548 node2 = node.bin(node2)
549 549 logcmdutil.diffordiffstat(ui, self._repo, diffopts,
550 550 node1, node2, match,
551 551 prefix=posixpath.join(prefix, self._path),
552 552 listsubrepos=True, **opts)
553 553 except error.RepoLookupError as inst:
554 554 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
555 555 % (inst, subrelpath(self)))
556 556
557 557 @annotatesubrepoerror
558 558 def archive(self, archiver, prefix, match=None, decode=True):
559 559 self._get(self._state + ('hg',))
560 560 files = self.files()
561 561 if match:
562 562 files = [f for f in files if match(f)]
563 563 rev = self._state[1]
564 564 ctx = self._repo[rev]
565 565 scmutil.fileprefetchhooks(self._repo, ctx, files)
566 566 total = abstractsubrepo.archive(self, archiver, prefix, match)
567 567 for subpath in ctx.substate:
568 568 s = subrepo(ctx, subpath, True)
569 569 submatch = matchmod.subdirmatcher(subpath, match)
570 570 total += s.archive(archiver, prefix + self._path + '/', submatch,
571 571 decode)
572 572 return total
573 573
574 574 @annotatesubrepoerror
575 575 def dirty(self, ignoreupdate=False, missing=False):
576 576 r = self._state[1]
577 577 if r == '' and not ignoreupdate: # no state recorded
578 578 return True
579 579 w = self._repo[None]
580 580 if r != w.p1().hex() and not ignoreupdate:
581 581 # different version checked out
582 582 return True
583 583 return w.dirty(missing=missing) # working directory changed
584 584
585 585 def basestate(self):
586 586 return self._repo['.'].hex()
587 587
588 588 def checknested(self, path):
589 589 return self._repo._checknested(self._repo.wjoin(path))
590 590
591 591 @annotatesubrepoerror
592 592 def commit(self, text, user, date):
593 593 # don't bother committing in the subrepo if it's only been
594 594 # updated
595 595 if not self.dirty(True):
596 596 return self._repo['.'].hex()
597 597 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
598 598 n = self._repo.commit(text, user, date)
599 599 if not n:
600 600 return self._repo['.'].hex() # different version checked out
601 601 return node.hex(n)
602 602
603 603 @annotatesubrepoerror
604 604 def phase(self, state):
605 605 return self._repo[state or '.'].phase()
606 606
607 607 @annotatesubrepoerror
608 608 def remove(self):
609 609 # we can't fully delete the repository as it may contain
610 610 # local-only history
611 611 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
612 612 hg.clean(self._repo, node.nullid, False)
613 613
614 614 def _get(self, state):
615 615 source, revision, kind = state
616 616 parentrepo = self._repo._subparent
617 617
618 618 if revision in self._repo.unfiltered():
619 619 # Allow shared subrepos tracked at null to setup the sharedpath
620 620 if len(self._repo) != 0 or not parentrepo.shared():
621 621 return True
622 622 self._repo._subsource = source
623 623 srcurl = _abssource(self._repo)
624 624 other = hg.peer(self._repo, {}, srcurl)
625 625 if len(self._repo) == 0:
626 626 # use self._repo.vfs instead of self.wvfs to remove .hg only
627 627 self._repo.vfs.rmtree()
628 628
629 629 # A remote subrepo could be shared if there is a local copy
630 630 # relative to the parent's share source. But clone pooling doesn't
631 631 # assemble the repos in a tree, so that can't be consistently done.
632 632 # A simpler option is for the user to configure clone pooling, and
633 633 # work with that.
634 634 if parentrepo.shared() and hg.islocal(srcurl):
635 635 self.ui.status(_('sharing subrepo %s from %s\n')
636 636 % (subrelpath(self), srcurl))
637 637 shared = hg.share(self._repo._subparent.baseui,
638 638 other, self._repo.root,
639 639 update=False, bookmarks=False)
640 640 self._repo = shared.local()
641 641 else:
642 642 # TODO: find a common place for this and this code in the
643 643 # share.py wrap of the clone command.
644 644 if parentrepo.shared():
645 645 pool = self.ui.config('share', 'pool')
646 646 if pool:
647 647 pool = util.expandpath(pool)
648 648
649 649 shareopts = {
650 650 'pool': pool,
651 651 'mode': self.ui.config('share', 'poolnaming'),
652 652 }
653 653 else:
654 654 shareopts = {}
655 655
656 656 self.ui.status(_('cloning subrepo %s from %s\n')
657 657 % (subrelpath(self), srcurl))
658 658 other, cloned = hg.clone(self._repo._subparent.baseui, {},
659 659 other, self._repo.root,
660 660 update=False, shareopts=shareopts)
661 661 self._repo = cloned.local()
662 662 self._initrepo(parentrepo, source, create=True)
663 663 self._cachestorehash(srcurl)
664 664 else:
665 665 self.ui.status(_('pulling subrepo %s from %s\n')
666 666 % (subrelpath(self), srcurl))
667 667 cleansub = self.storeclean(srcurl)
668 668 exchange.pull(self._repo, other)
669 669 if cleansub:
670 670 # keep the repo clean after pull
671 671 self._cachestorehash(srcurl)
672 672 return False
673 673
674 674 @annotatesubrepoerror
675 675 def get(self, state, overwrite=False):
676 676 inrepo = self._get(state)
677 677 source, revision, kind = state
678 678 repo = self._repo
679 679 repo.ui.debug("getting subrepo %s\n" % self._path)
680 680 if inrepo:
681 681 urepo = repo.unfiltered()
682 682 ctx = urepo[revision]
683 683 if ctx.hidden():
684 684 urepo.ui.warn(
685 685 _('revision %s in subrepository "%s" is hidden\n') \
686 686 % (revision[0:12], self._path))
687 687 repo = urepo
688 688 hg.updaterepo(repo, revision, overwrite)
689 689
690 690 @annotatesubrepoerror
691 691 def merge(self, state):
692 692 self._get(state)
693 693 cur = self._repo['.']
694 694 dst = self._repo[state[1]]
695 695 anc = dst.ancestor(cur)
696 696
697 697 def mergefunc():
698 698 if anc == cur and dst.branch() == cur.branch():
699 699 self.ui.debug('updating subrepository "%s"\n'
700 700 % subrelpath(self))
701 701 hg.update(self._repo, state[1])
702 702 elif anc == dst:
703 703 self.ui.debug('skipping subrepository "%s"\n'
704 704 % subrelpath(self))
705 705 else:
706 706 self.ui.debug('merging subrepository "%s"\n' % subrelpath(self))
707 707 hg.merge(self._repo, state[1], remind=False)
708 708
709 709 wctx = self._repo[None]
710 710 if self.dirty():
711 711 if anc != dst:
712 712 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
713 713 mergefunc()
714 714 else:
715 715 mergefunc()
716 716 else:
717 717 mergefunc()
718 718
719 719 @annotatesubrepoerror
720 720 def push(self, opts):
721 721 force = opts.get('force')
722 722 newbranch = opts.get('new_branch')
723 723 ssh = opts.get('ssh')
724 724
725 725 # push subrepos depth-first for coherent ordering
726 726 c = self._repo['.']
727 727 subs = c.substate # only repos that are committed
728 728 for s in sorted(subs):
729 729 if c.sub(s).push(opts) == 0:
730 730 return False
731 731
732 732 dsturl = _abssource(self._repo, True)
733 733 if not force:
734 734 if self.storeclean(dsturl):
735 735 self.ui.status(
736 736 _('no changes made to subrepo %s since last push to %s\n')
737 737 % (subrelpath(self), dsturl))
738 738 return None
739 739 self.ui.status(_('pushing subrepo %s to %s\n') %
740 740 (subrelpath(self), dsturl))
741 741 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
742 742 res = exchange.push(self._repo, other, force, newbranch=newbranch)
743 743
744 744 # the repo is now clean
745 745 self._cachestorehash(dsturl)
746 746 return res.cgresult
747 747
748 748 @annotatesubrepoerror
749 749 def outgoing(self, ui, dest, opts):
750 750 if 'rev' in opts or 'branch' in opts:
751 751 opts = copy.copy(opts)
752 752 opts.pop('rev', None)
753 753 opts.pop('branch', None)
754 754 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
755 755
756 756 @annotatesubrepoerror
757 757 def incoming(self, ui, source, opts):
758 758 if 'rev' in opts or 'branch' in opts:
759 759 opts = copy.copy(opts)
760 760 opts.pop('rev', None)
761 761 opts.pop('branch', None)
762 762 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
763 763
764 764 @annotatesubrepoerror
765 765 def files(self):
766 766 rev = self._state[1]
767 767 ctx = self._repo[rev]
768 768 return ctx.manifest().keys()
769 769
770 770 def filedata(self, name, decode):
771 771 rev = self._state[1]
772 772 data = self._repo[rev][name].data()
773 773 if decode:
774 774 data = self._repo.wwritedata(name, data)
775 775 return data
776 776
777 777 def fileflags(self, name):
778 778 rev = self._state[1]
779 779 ctx = self._repo[rev]
780 780 return ctx.flags(name)
781 781
782 782 @annotatesubrepoerror
783 783 def printfiles(self, ui, m, fm, fmt, subrepos):
784 784 # If the parent context is a workingctx, use the workingctx here for
785 785 # consistency.
786 786 if self._ctx.rev() is None:
787 787 ctx = self._repo[None]
788 788 else:
789 789 rev = self._state[1]
790 790 ctx = self._repo[rev]
791 791 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
792 792
793 793 @annotatesubrepoerror
794 794 def getfileset(self, expr):
795 795 if self._ctx.rev() is None:
796 796 ctx = self._repo[None]
797 797 else:
798 798 rev = self._state[1]
799 799 ctx = self._repo[rev]
800 800
801 801 files = ctx.getfileset(expr)
802 802
803 803 for subpath in ctx.substate:
804 804 sub = ctx.sub(subpath)
805 805
806 806 try:
807 807 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
808 808 except error.LookupError:
809 809 self.ui.status(_("skipping missing subrepository: %s\n")
810 810 % self.wvfs.reljoin(reporelpath(self), subpath))
811 811 return files
812 812
813 813 def walk(self, match):
814 814 ctx = self._repo[None]
815 815 return ctx.walk(match)
816 816
817 817 @annotatesubrepoerror
818 def forget(self, match, prefix, dryrun):
818 def forget(self, match, prefix, dryrun, confirm):
819 819 return cmdutil.forget(self.ui, self._repo, match,
820 820 self.wvfs.reljoin(prefix, self._path),
821 True, dryrun=dryrun)
821 True, dryrun=dryrun, confirm=confirm)
822 822
823 823 @annotatesubrepoerror
824 824 def removefiles(self, matcher, prefix, after, force, subrepos,
825 825 dryrun, warnings):
826 826 return cmdutil.remove(self.ui, self._repo, matcher,
827 827 self.wvfs.reljoin(prefix, self._path),
828 828 after, force, subrepos, dryrun)
829 829
830 830 @annotatesubrepoerror
831 831 def revert(self, substate, *pats, **opts):
832 832 # reverting a subrepo is a 2 step process:
833 833 # 1. if the no_backup is not set, revert all modified
834 834 # files inside the subrepo
835 835 # 2. update the subrepo to the revision specified in
836 836 # the corresponding substate dictionary
837 837 self.ui.status(_('reverting subrepo %s\n') % substate[0])
838 838 if not opts.get(r'no_backup'):
839 839 # Revert all files on the subrepo, creating backups
840 840 # Note that this will not recursively revert subrepos
841 841 # We could do it if there was a set:subrepos() predicate
842 842 opts = opts.copy()
843 843 opts[r'date'] = None
844 844 opts[r'rev'] = substate[1]
845 845
846 846 self.filerevert(*pats, **opts)
847 847
848 848 # Update the repo to the revision specified in the given substate
849 849 if not opts.get(r'dry_run'):
850 850 self.get(substate, overwrite=True)
851 851
852 852 def filerevert(self, *pats, **opts):
853 853 ctx = self._repo[opts[r'rev']]
854 854 parents = self._repo.dirstate.parents()
855 855 if opts.get(r'all'):
856 856 pats = ['set:modified()']
857 857 else:
858 858 pats = []
859 859 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
860 860
861 861 def shortid(self, revid):
862 862 return revid[:12]
863 863
864 864 @annotatesubrepoerror
865 865 def unshare(self):
866 866 # subrepo inherently violates our import layering rules
867 867 # because it wants to make repo objects from deep inside the stack
868 868 # so we manually delay the circular imports to not break
869 869 # scripts that don't use our demand-loading
870 870 global hg
871 871 from . import hg as h
872 872 hg = h
873 873
874 874 # Nothing prevents a user from sharing in a repo, and then making that a
875 875 # subrepo. Alternately, the previous unshare attempt may have failed
876 876 # part way through. So recurse whether or not this layer is shared.
877 877 if self._repo.shared():
878 878 self.ui.status(_("unsharing subrepo '%s'\n") % self._relpath)
879 879
880 880 hg.unshare(self.ui, self._repo)
881 881
882 882 def verify(self):
883 883 try:
884 884 rev = self._state[1]
885 885 ctx = self._repo.unfiltered()[rev]
886 886 if ctx.hidden():
887 887 # Since hidden revisions aren't pushed/pulled, it seems worth an
888 888 # explicit warning.
889 889 ui = self._repo.ui
890 890 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
891 891 (self._relpath, node.short(self._ctx.node())))
892 892 return 0
893 893 except error.RepoLookupError:
894 894 # A missing subrepo revision may be a case of needing to pull it, so
895 895 # don't treat this as an error.
896 896 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
897 897 (self._relpath, node.short(self._ctx.node())))
898 898 return 0
899 899
900 900 @propertycache
901 901 def wvfs(self):
902 902 """return own wvfs for efficiency and consistency
903 903 """
904 904 return self._repo.wvfs
905 905
906 906 @propertycache
907 907 def _relpath(self):
908 908 """return path to this subrepository as seen from outermost repository
909 909 """
910 910 # Keep consistent dir separators by avoiding vfs.join(self._path)
911 911 return reporelpath(self._repo)
912 912
913 913 class svnsubrepo(abstractsubrepo):
914 914 def __init__(self, ctx, path, state, allowcreate):
915 915 super(svnsubrepo, self).__init__(ctx, path)
916 916 self._state = state
917 917 self._exe = procutil.findexe('svn')
918 918 if not self._exe:
919 919 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
920 920 % self._path)
921 921
922 922 def _svncommand(self, commands, filename='', failok=False):
923 923 cmd = [self._exe]
924 924 extrakw = {}
925 925 if not self.ui.interactive():
926 926 # Making stdin be a pipe should prevent svn from behaving
927 927 # interactively even if we can't pass --non-interactive.
928 928 extrakw[r'stdin'] = subprocess.PIPE
929 929 # Starting in svn 1.5 --non-interactive is a global flag
930 930 # instead of being per-command, but we need to support 1.4 so
931 931 # we have to be intelligent about what commands take
932 932 # --non-interactive.
933 933 if commands[0] in ('update', 'checkout', 'commit'):
934 934 cmd.append('--non-interactive')
935 935 cmd.extend(commands)
936 936 if filename is not None:
937 937 path = self.wvfs.reljoin(self._ctx.repo().origroot,
938 938 self._path, filename)
939 939 cmd.append(path)
940 940 env = dict(encoding.environ)
941 941 # Avoid localized output, preserve current locale for everything else.
942 942 lc_all = env.get('LC_ALL')
943 943 if lc_all:
944 944 env['LANG'] = lc_all
945 945 del env['LC_ALL']
946 946 env['LC_MESSAGES'] = 'C'
947 947 p = subprocess.Popen(cmd, bufsize=-1, close_fds=procutil.closefds,
948 948 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
949 949 universal_newlines=True, env=env, **extrakw)
950 950 stdout, stderr = p.communicate()
951 951 stderr = stderr.strip()
952 952 if not failok:
953 953 if p.returncode:
954 954 raise error.Abort(stderr or 'exited with code %d'
955 955 % p.returncode)
956 956 if stderr:
957 957 self.ui.warn(stderr + '\n')
958 958 return stdout, stderr
959 959
960 960 @propertycache
961 961 def _svnversion(self):
962 962 output, err = self._svncommand(['--version', '--quiet'], filename=None)
963 963 m = re.search(br'^(\d+)\.(\d+)', output)
964 964 if not m:
965 965 raise error.Abort(_('cannot retrieve svn tool version'))
966 966 return (int(m.group(1)), int(m.group(2)))
967 967
968 968 def _svnmissing(self):
969 969 return not self.wvfs.exists('.svn')
970 970
971 971 def _wcrevs(self):
972 972 # Get the working directory revision as well as the last
973 973 # commit revision so we can compare the subrepo state with
974 974 # both. We used to store the working directory one.
975 975 output, err = self._svncommand(['info', '--xml'])
976 976 doc = xml.dom.minidom.parseString(output)
977 977 entries = doc.getElementsByTagName('entry')
978 978 lastrev, rev = '0', '0'
979 979 if entries:
980 980 rev = str(entries[0].getAttribute('revision')) or '0'
981 981 commits = entries[0].getElementsByTagName('commit')
982 982 if commits:
983 983 lastrev = str(commits[0].getAttribute('revision')) or '0'
984 984 return (lastrev, rev)
985 985
986 986 def _wcrev(self):
987 987 return self._wcrevs()[0]
988 988
989 989 def _wcchanged(self):
990 990 """Return (changes, extchanges, missing) where changes is True
991 991 if the working directory was changed, extchanges is
992 992 True if any of these changes concern an external entry and missing
993 993 is True if any change is a missing entry.
994 994 """
995 995 output, err = self._svncommand(['status', '--xml'])
996 996 externals, changes, missing = [], [], []
997 997 doc = xml.dom.minidom.parseString(output)
998 998 for e in doc.getElementsByTagName('entry'):
999 999 s = e.getElementsByTagName('wc-status')
1000 1000 if not s:
1001 1001 continue
1002 1002 item = s[0].getAttribute('item')
1003 1003 props = s[0].getAttribute('props')
1004 1004 path = e.getAttribute('path')
1005 1005 if item == 'external':
1006 1006 externals.append(path)
1007 1007 elif item == 'missing':
1008 1008 missing.append(path)
1009 1009 if (item not in ('', 'normal', 'unversioned', 'external')
1010 1010 or props not in ('', 'none', 'normal')):
1011 1011 changes.append(path)
1012 1012 for path in changes:
1013 1013 for ext in externals:
1014 1014 if path == ext or path.startswith(ext + pycompat.ossep):
1015 1015 return True, True, bool(missing)
1016 1016 return bool(changes), False, bool(missing)
1017 1017
1018 1018 @annotatesubrepoerror
1019 1019 def dirty(self, ignoreupdate=False, missing=False):
1020 1020 if self._svnmissing():
1021 1021 return self._state[1] != ''
1022 1022 wcchanged = self._wcchanged()
1023 1023 changed = wcchanged[0] or (missing and wcchanged[2])
1024 1024 if not changed:
1025 1025 if self._state[1] in self._wcrevs() or ignoreupdate:
1026 1026 return False
1027 1027 return True
1028 1028
1029 1029 def basestate(self):
1030 1030 lastrev, rev = self._wcrevs()
1031 1031 if lastrev != rev:
1032 1032 # Last committed rev is not the same than rev. We would
1033 1033 # like to take lastrev but we do not know if the subrepo
1034 1034 # URL exists at lastrev. Test it and fallback to rev it
1035 1035 # is not there.
1036 1036 try:
1037 1037 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1038 1038 return lastrev
1039 1039 except error.Abort:
1040 1040 pass
1041 1041 return rev
1042 1042
1043 1043 @annotatesubrepoerror
1044 1044 def commit(self, text, user, date):
1045 1045 # user and date are out of our hands since svn is centralized
1046 1046 changed, extchanged, missing = self._wcchanged()
1047 1047 if not changed:
1048 1048 return self.basestate()
1049 1049 if extchanged:
1050 1050 # Do not try to commit externals
1051 1051 raise error.Abort(_('cannot commit svn externals'))
1052 1052 if missing:
1053 1053 # svn can commit with missing entries but aborting like hg
1054 1054 # seems a better approach.
1055 1055 raise error.Abort(_('cannot commit missing svn entries'))
1056 1056 commitinfo, err = self._svncommand(['commit', '-m', text])
1057 1057 self.ui.status(commitinfo)
1058 1058 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1059 1059 if not newrev:
1060 1060 if not commitinfo.strip():
1061 1061 # Sometimes, our definition of "changed" differs from
1062 1062 # svn one. For instance, svn ignores missing files
1063 1063 # when committing. If there are only missing files, no
1064 1064 # commit is made, no output and no error code.
1065 1065 raise error.Abort(_('failed to commit svn changes'))
1066 1066 raise error.Abort(commitinfo.splitlines()[-1])
1067 1067 newrev = newrev.groups()[0]
1068 1068 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1069 1069 return newrev
1070 1070
1071 1071 @annotatesubrepoerror
1072 1072 def remove(self):
1073 1073 if self.dirty():
1074 1074 self.ui.warn(_('not removing repo %s because '
1075 1075 'it has changes.\n') % self._path)
1076 1076 return
1077 1077 self.ui.note(_('removing subrepo %s\n') % self._path)
1078 1078
1079 1079 self.wvfs.rmtree(forcibly=True)
1080 1080 try:
1081 1081 pwvfs = self._ctx.repo().wvfs
1082 1082 pwvfs.removedirs(pwvfs.dirname(self._path))
1083 1083 except OSError:
1084 1084 pass
1085 1085
1086 1086 @annotatesubrepoerror
1087 1087 def get(self, state, overwrite=False):
1088 1088 if overwrite:
1089 1089 self._svncommand(['revert', '--recursive'])
1090 1090 args = ['checkout']
1091 1091 if self._svnversion >= (1, 5):
1092 1092 args.append('--force')
1093 1093 # The revision must be specified at the end of the URL to properly
1094 1094 # update to a directory which has since been deleted and recreated.
1095 1095 args.append('%s@%s' % (state[0], state[1]))
1096 1096
1097 1097 # SEC: check that the ssh url is safe
1098 1098 util.checksafessh(state[0])
1099 1099
1100 1100 status, err = self._svncommand(args, failok=True)
1101 1101 _sanitize(self.ui, self.wvfs, '.svn')
1102 1102 if not re.search('Checked out revision [0-9]+.', status):
1103 1103 if ('is already a working copy for a different URL' in err
1104 1104 and (self._wcchanged()[:2] == (False, False))):
1105 1105 # obstructed but clean working copy, so just blow it away.
1106 1106 self.remove()
1107 1107 self.get(state, overwrite=False)
1108 1108 return
1109 1109 raise error.Abort((status or err).splitlines()[-1])
1110 1110 self.ui.status(status)
1111 1111
1112 1112 @annotatesubrepoerror
1113 1113 def merge(self, state):
1114 1114 old = self._state[1]
1115 1115 new = state[1]
1116 1116 wcrev = self._wcrev()
1117 1117 if new != wcrev:
1118 1118 dirty = old == wcrev or self._wcchanged()[0]
1119 1119 if _updateprompt(self.ui, self, dirty, wcrev, new):
1120 1120 self.get(state, False)
1121 1121
1122 1122 def push(self, opts):
1123 1123 # push is a no-op for SVN
1124 1124 return True
1125 1125
1126 1126 @annotatesubrepoerror
1127 1127 def files(self):
1128 1128 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1129 1129 doc = xml.dom.minidom.parseString(output)
1130 1130 paths = []
1131 1131 for e in doc.getElementsByTagName('entry'):
1132 1132 kind = pycompat.bytestr(e.getAttribute('kind'))
1133 1133 if kind != 'file':
1134 1134 continue
1135 1135 name = ''.join(c.data for c
1136 1136 in e.getElementsByTagName('name')[0].childNodes
1137 1137 if c.nodeType == c.TEXT_NODE)
1138 1138 paths.append(name.encode('utf-8'))
1139 1139 return paths
1140 1140
1141 1141 def filedata(self, name, decode):
1142 1142 return self._svncommand(['cat'], name)[0]
1143 1143
1144 1144
1145 1145 class gitsubrepo(abstractsubrepo):
1146 1146 def __init__(self, ctx, path, state, allowcreate):
1147 1147 super(gitsubrepo, self).__init__(ctx, path)
1148 1148 self._state = state
1149 1149 self._abspath = ctx.repo().wjoin(path)
1150 1150 self._subparent = ctx.repo()
1151 1151 self._ensuregit()
1152 1152
1153 1153 def _ensuregit(self):
1154 1154 try:
1155 1155 self._gitexecutable = 'git'
1156 1156 out, err = self._gitnodir(['--version'])
1157 1157 except OSError as e:
1158 1158 genericerror = _("error executing git for subrepo '%s': %s")
1159 1159 notfoundhint = _("check git is installed and in your PATH")
1160 1160 if e.errno != errno.ENOENT:
1161 1161 raise error.Abort(genericerror % (
1162 1162 self._path, encoding.strtolocal(e.strerror)))
1163 1163 elif pycompat.iswindows:
1164 1164 try:
1165 1165 self._gitexecutable = 'git.cmd'
1166 1166 out, err = self._gitnodir(['--version'])
1167 1167 except OSError as e2:
1168 1168 if e2.errno == errno.ENOENT:
1169 1169 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1170 1170 " for subrepo '%s'") % self._path,
1171 1171 hint=notfoundhint)
1172 1172 else:
1173 1173 raise error.Abort(genericerror % (self._path,
1174 1174 encoding.strtolocal(e2.strerror)))
1175 1175 else:
1176 1176 raise error.Abort(_("couldn't find git for subrepo '%s'")
1177 1177 % self._path, hint=notfoundhint)
1178 1178 versionstatus = self._checkversion(out)
1179 1179 if versionstatus == 'unknown':
1180 1180 self.ui.warn(_('cannot retrieve git version\n'))
1181 1181 elif versionstatus == 'abort':
1182 1182 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1183 1183 elif versionstatus == 'warning':
1184 1184 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1185 1185
1186 1186 @staticmethod
1187 1187 def _gitversion(out):
1188 1188 m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
1189 1189 if m:
1190 1190 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1191 1191
1192 1192 m = re.search(br'^git version (\d+)\.(\d+)', out)
1193 1193 if m:
1194 1194 return (int(m.group(1)), int(m.group(2)), 0)
1195 1195
1196 1196 return -1
1197 1197
1198 1198 @staticmethod
1199 1199 def _checkversion(out):
1200 1200 '''ensure git version is new enough
1201 1201
1202 1202 >>> _checkversion = gitsubrepo._checkversion
1203 1203 >>> _checkversion(b'git version 1.6.0')
1204 1204 'ok'
1205 1205 >>> _checkversion(b'git version 1.8.5')
1206 1206 'ok'
1207 1207 >>> _checkversion(b'git version 1.4.0')
1208 1208 'abort'
1209 1209 >>> _checkversion(b'git version 1.5.0')
1210 1210 'warning'
1211 1211 >>> _checkversion(b'git version 1.9-rc0')
1212 1212 'ok'
1213 1213 >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
1214 1214 'ok'
1215 1215 >>> _checkversion(b'git version 1.9.0.GIT')
1216 1216 'ok'
1217 1217 >>> _checkversion(b'git version 12345')
1218 1218 'unknown'
1219 1219 >>> _checkversion(b'no')
1220 1220 'unknown'
1221 1221 '''
1222 1222 version = gitsubrepo._gitversion(out)
1223 1223 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1224 1224 # despite the docstring comment. For now, error on 1.4.0, warn on
1225 1225 # 1.5.0 but attempt to continue.
1226 1226 if version == -1:
1227 1227 return 'unknown'
1228 1228 if version < (1, 5, 0):
1229 1229 return 'abort'
1230 1230 elif version < (1, 6, 0):
1231 1231 return 'warning'
1232 1232 return 'ok'
1233 1233
1234 1234 def _gitcommand(self, commands, env=None, stream=False):
1235 1235 return self._gitdir(commands, env=env, stream=stream)[0]
1236 1236
1237 1237 def _gitdir(self, commands, env=None, stream=False):
1238 1238 return self._gitnodir(commands, env=env, stream=stream,
1239 1239 cwd=self._abspath)
1240 1240
1241 1241 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1242 1242 """Calls the git command
1243 1243
1244 1244 The methods tries to call the git command. versions prior to 1.6.0
1245 1245 are not supported and very probably fail.
1246 1246 """
1247 1247 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1248 1248 if env is None:
1249 1249 env = encoding.environ.copy()
1250 1250 # disable localization for Git output (issue5176)
1251 1251 env['LC_ALL'] = 'C'
1252 1252 # fix for Git CVE-2015-7545
1253 1253 if 'GIT_ALLOW_PROTOCOL' not in env:
1254 1254 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1255 1255 # unless ui.quiet is set, print git's stderr,
1256 1256 # which is mostly progress and useful info
1257 1257 errpipe = None
1258 1258 if self.ui.quiet:
1259 1259 errpipe = open(os.devnull, 'w')
1260 1260 if self.ui._colormode and len(commands) and commands[0] == "diff":
1261 1261 # insert the argument in the front,
1262 1262 # the end of git diff arguments is used for paths
1263 1263 commands.insert(1, '--color')
1264 1264 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1265 1265 cwd=cwd, env=env, close_fds=procutil.closefds,
1266 1266 stdout=subprocess.PIPE, stderr=errpipe)
1267 1267 if stream:
1268 1268 return p.stdout, None
1269 1269
1270 1270 retdata = p.stdout.read().strip()
1271 1271 # wait for the child to exit to avoid race condition.
1272 1272 p.wait()
1273 1273
1274 1274 if p.returncode != 0 and p.returncode != 1:
1275 1275 # there are certain error codes that are ok
1276 1276 command = commands[0]
1277 1277 if command in ('cat-file', 'symbolic-ref'):
1278 1278 return retdata, p.returncode
1279 1279 # for all others, abort
1280 1280 raise error.Abort(_('git %s error %d in %s') %
1281 1281 (command, p.returncode, self._relpath))
1282 1282
1283 1283 return retdata, p.returncode
1284 1284
1285 1285 def _gitmissing(self):
1286 1286 return not self.wvfs.exists('.git')
1287 1287
1288 1288 def _gitstate(self):
1289 1289 return self._gitcommand(['rev-parse', 'HEAD'])
1290 1290
1291 1291 def _gitcurrentbranch(self):
1292 1292 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1293 1293 if err:
1294 1294 current = None
1295 1295 return current
1296 1296
1297 1297 def _gitremote(self, remote):
1298 1298 out = self._gitcommand(['remote', 'show', '-n', remote])
1299 1299 line = out.split('\n')[1]
1300 1300 i = line.index('URL: ') + len('URL: ')
1301 1301 return line[i:]
1302 1302
1303 1303 def _githavelocally(self, revision):
1304 1304 out, code = self._gitdir(['cat-file', '-e', revision])
1305 1305 return code == 0
1306 1306
1307 1307 def _gitisancestor(self, r1, r2):
1308 1308 base = self._gitcommand(['merge-base', r1, r2])
1309 1309 return base == r1
1310 1310
1311 1311 def _gitisbare(self):
1312 1312 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1313 1313
1314 1314 def _gitupdatestat(self):
1315 1315 """This must be run before git diff-index.
1316 1316 diff-index only looks at changes to file stat;
1317 1317 this command looks at file contents and updates the stat."""
1318 1318 self._gitcommand(['update-index', '-q', '--refresh'])
1319 1319
1320 1320 def _gitbranchmap(self):
1321 1321 '''returns 2 things:
1322 1322 a map from git branch to revision
1323 1323 a map from revision to branches'''
1324 1324 branch2rev = {}
1325 1325 rev2branch = {}
1326 1326
1327 1327 out = self._gitcommand(['for-each-ref', '--format',
1328 1328 '%(objectname) %(refname)'])
1329 1329 for line in out.split('\n'):
1330 1330 revision, ref = line.split(' ')
1331 1331 if (not ref.startswith('refs/heads/') and
1332 1332 not ref.startswith('refs/remotes/')):
1333 1333 continue
1334 1334 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1335 1335 continue # ignore remote/HEAD redirects
1336 1336 branch2rev[ref] = revision
1337 1337 rev2branch.setdefault(revision, []).append(ref)
1338 1338 return branch2rev, rev2branch
1339 1339
1340 1340 def _gittracking(self, branches):
1341 1341 'return map of remote branch to local tracking branch'
1342 1342 # assumes no more than one local tracking branch for each remote
1343 1343 tracking = {}
1344 1344 for b in branches:
1345 1345 if b.startswith('refs/remotes/'):
1346 1346 continue
1347 1347 bname = b.split('/', 2)[2]
1348 1348 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1349 1349 if remote:
1350 1350 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1351 1351 tracking['refs/remotes/%s/%s' %
1352 1352 (remote, ref.split('/', 2)[2])] = b
1353 1353 return tracking
1354 1354
1355 1355 def _abssource(self, source):
1356 1356 if '://' not in source:
1357 1357 # recognize the scp syntax as an absolute source
1358 1358 colon = source.find(':')
1359 1359 if colon != -1 and '/' not in source[:colon]:
1360 1360 return source
1361 1361 self._subsource = source
1362 1362 return _abssource(self)
1363 1363
1364 1364 def _fetch(self, source, revision):
1365 1365 if self._gitmissing():
1366 1366 # SEC: check for safe ssh url
1367 1367 util.checksafessh(source)
1368 1368
1369 1369 source = self._abssource(source)
1370 1370 self.ui.status(_('cloning subrepo %s from %s\n') %
1371 1371 (self._relpath, source))
1372 1372 self._gitnodir(['clone', source, self._abspath])
1373 1373 if self._githavelocally(revision):
1374 1374 return
1375 1375 self.ui.status(_('pulling subrepo %s from %s\n') %
1376 1376 (self._relpath, self._gitremote('origin')))
1377 1377 # try only origin: the originally cloned repo
1378 1378 self._gitcommand(['fetch'])
1379 1379 if not self._githavelocally(revision):
1380 1380 raise error.Abort(_('revision %s does not exist in subrepository '
1381 1381 '"%s"\n') % (revision, self._relpath))
1382 1382
1383 1383 @annotatesubrepoerror
1384 1384 def dirty(self, ignoreupdate=False, missing=False):
1385 1385 if self._gitmissing():
1386 1386 return self._state[1] != ''
1387 1387 if self._gitisbare():
1388 1388 return True
1389 1389 if not ignoreupdate and self._state[1] != self._gitstate():
1390 1390 # different version checked out
1391 1391 return True
1392 1392 # check for staged changes or modified files; ignore untracked files
1393 1393 self._gitupdatestat()
1394 1394 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1395 1395 return code == 1
1396 1396
1397 1397 def basestate(self):
1398 1398 return self._gitstate()
1399 1399
1400 1400 @annotatesubrepoerror
1401 1401 def get(self, state, overwrite=False):
1402 1402 source, revision, kind = state
1403 1403 if not revision:
1404 1404 self.remove()
1405 1405 return
1406 1406 self._fetch(source, revision)
1407 1407 # if the repo was set to be bare, unbare it
1408 1408 if self._gitisbare():
1409 1409 self._gitcommand(['config', 'core.bare', 'false'])
1410 1410 if self._gitstate() == revision:
1411 1411 self._gitcommand(['reset', '--hard', 'HEAD'])
1412 1412 return
1413 1413 elif self._gitstate() == revision:
1414 1414 if overwrite:
1415 1415 # first reset the index to unmark new files for commit, because
1416 1416 # reset --hard will otherwise throw away files added for commit,
1417 1417 # not just unmark them.
1418 1418 self._gitcommand(['reset', 'HEAD'])
1419 1419 self._gitcommand(['reset', '--hard', 'HEAD'])
1420 1420 return
1421 1421 branch2rev, rev2branch = self._gitbranchmap()
1422 1422
1423 1423 def checkout(args):
1424 1424 cmd = ['checkout']
1425 1425 if overwrite:
1426 1426 # first reset the index to unmark new files for commit, because
1427 1427 # the -f option will otherwise throw away files added for
1428 1428 # commit, not just unmark them.
1429 1429 self._gitcommand(['reset', 'HEAD'])
1430 1430 cmd.append('-f')
1431 1431 self._gitcommand(cmd + args)
1432 1432 _sanitize(self.ui, self.wvfs, '.git')
1433 1433
1434 1434 def rawcheckout():
1435 1435 # no branch to checkout, check it out with no branch
1436 1436 self.ui.warn(_('checking out detached HEAD in '
1437 1437 'subrepository "%s"\n') % self._relpath)
1438 1438 self.ui.warn(_('check out a git branch if you intend '
1439 1439 'to make changes\n'))
1440 1440 checkout(['-q', revision])
1441 1441
1442 1442 if revision not in rev2branch:
1443 1443 rawcheckout()
1444 1444 return
1445 1445 branches = rev2branch[revision]
1446 1446 firstlocalbranch = None
1447 1447 for b in branches:
1448 1448 if b == 'refs/heads/master':
1449 1449 # master trumps all other branches
1450 1450 checkout(['refs/heads/master'])
1451 1451 return
1452 1452 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1453 1453 firstlocalbranch = b
1454 1454 if firstlocalbranch:
1455 1455 checkout([firstlocalbranch])
1456 1456 return
1457 1457
1458 1458 tracking = self._gittracking(branch2rev.keys())
1459 1459 # choose a remote branch already tracked if possible
1460 1460 remote = branches[0]
1461 1461 if remote not in tracking:
1462 1462 for b in branches:
1463 1463 if b in tracking:
1464 1464 remote = b
1465 1465 break
1466 1466
1467 1467 if remote not in tracking:
1468 1468 # create a new local tracking branch
1469 1469 local = remote.split('/', 3)[3]
1470 1470 checkout(['-b', local, remote])
1471 1471 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1472 1472 # When updating to a tracked remote branch,
1473 1473 # if the local tracking branch is downstream of it,
1474 1474 # a normal `git pull` would have performed a "fast-forward merge"
1475 1475 # which is equivalent to updating the local branch to the remote.
1476 1476 # Since we are only looking at branching at update, we need to
1477 1477 # detect this situation and perform this action lazily.
1478 1478 if tracking[remote] != self._gitcurrentbranch():
1479 1479 checkout([tracking[remote]])
1480 1480 self._gitcommand(['merge', '--ff', remote])
1481 1481 _sanitize(self.ui, self.wvfs, '.git')
1482 1482 else:
1483 1483 # a real merge would be required, just checkout the revision
1484 1484 rawcheckout()
1485 1485
1486 1486 @annotatesubrepoerror
1487 1487 def commit(self, text, user, date):
1488 1488 if self._gitmissing():
1489 1489 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1490 1490 cmd = ['commit', '-a', '-m', text]
1491 1491 env = encoding.environ.copy()
1492 1492 if user:
1493 1493 cmd += ['--author', user]
1494 1494 if date:
1495 1495 # git's date parser silently ignores when seconds < 1e9
1496 1496 # convert to ISO8601
1497 1497 env['GIT_AUTHOR_DATE'] = dateutil.datestr(date,
1498 1498 '%Y-%m-%dT%H:%M:%S %1%2')
1499 1499 self._gitcommand(cmd, env=env)
1500 1500 # make sure commit works otherwise HEAD might not exist under certain
1501 1501 # circumstances
1502 1502 return self._gitstate()
1503 1503
1504 1504 @annotatesubrepoerror
1505 1505 def merge(self, state):
1506 1506 source, revision, kind = state
1507 1507 self._fetch(source, revision)
1508 1508 base = self._gitcommand(['merge-base', revision, self._state[1]])
1509 1509 self._gitupdatestat()
1510 1510 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1511 1511
1512 1512 def mergefunc():
1513 1513 if base == revision:
1514 1514 self.get(state) # fast forward merge
1515 1515 elif base != self._state[1]:
1516 1516 self._gitcommand(['merge', '--no-commit', revision])
1517 1517 _sanitize(self.ui, self.wvfs, '.git')
1518 1518
1519 1519 if self.dirty():
1520 1520 if self._gitstate() != revision:
1521 1521 dirty = self._gitstate() == self._state[1] or code != 0
1522 1522 if _updateprompt(self.ui, self, dirty,
1523 1523 self._state[1][:7], revision[:7]):
1524 1524 mergefunc()
1525 1525 else:
1526 1526 mergefunc()
1527 1527
1528 1528 @annotatesubrepoerror
1529 1529 def push(self, opts):
1530 1530 force = opts.get('force')
1531 1531
1532 1532 if not self._state[1]:
1533 1533 return True
1534 1534 if self._gitmissing():
1535 1535 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1536 1536 # if a branch in origin contains the revision, nothing to do
1537 1537 branch2rev, rev2branch = self._gitbranchmap()
1538 1538 if self._state[1] in rev2branch:
1539 1539 for b in rev2branch[self._state[1]]:
1540 1540 if b.startswith('refs/remotes/origin/'):
1541 1541 return True
1542 1542 for b, revision in branch2rev.iteritems():
1543 1543 if b.startswith('refs/remotes/origin/'):
1544 1544 if self._gitisancestor(self._state[1], revision):
1545 1545 return True
1546 1546 # otherwise, try to push the currently checked out branch
1547 1547 cmd = ['push']
1548 1548 if force:
1549 1549 cmd.append('--force')
1550 1550
1551 1551 current = self._gitcurrentbranch()
1552 1552 if current:
1553 1553 # determine if the current branch is even useful
1554 1554 if not self._gitisancestor(self._state[1], current):
1555 1555 self.ui.warn(_('unrelated git branch checked out '
1556 1556 'in subrepository "%s"\n') % self._relpath)
1557 1557 return False
1558 1558 self.ui.status(_('pushing branch %s of subrepository "%s"\n') %
1559 1559 (current.split('/', 2)[2], self._relpath))
1560 1560 ret = self._gitdir(cmd + ['origin', current])
1561 1561 return ret[1] == 0
1562 1562 else:
1563 1563 self.ui.warn(_('no branch checked out in subrepository "%s"\n'
1564 1564 'cannot push revision %s\n') %
1565 1565 (self._relpath, self._state[1]))
1566 1566 return False
1567 1567
1568 1568 @annotatesubrepoerror
1569 1569 def add(self, ui, match, prefix, explicitonly, **opts):
1570 1570 if self._gitmissing():
1571 1571 return []
1572 1572
1573 1573 (modified, added, removed,
1574 1574 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1575 1575 clean=True)
1576 1576
1577 1577 tracked = set()
1578 1578 # dirstates 'amn' warn, 'r' is added again
1579 1579 for l in (modified, added, deleted, clean):
1580 1580 tracked.update(l)
1581 1581
1582 1582 # Unknown files not of interest will be rejected by the matcher
1583 1583 files = unknown
1584 1584 files.extend(match.files())
1585 1585
1586 1586 rejected = []
1587 1587
1588 1588 files = [f for f in sorted(set(files)) if match(f)]
1589 1589 for f in files:
1590 1590 exact = match.exact(f)
1591 1591 command = ["add"]
1592 1592 if exact:
1593 1593 command.append("-f") #should be added, even if ignored
1594 1594 if ui.verbose or not exact:
1595 1595 ui.status(_('adding %s\n') % match.rel(f))
1596 1596
1597 1597 if f in tracked: # hg prints 'adding' even if already tracked
1598 1598 if exact:
1599 1599 rejected.append(f)
1600 1600 continue
1601 1601 if not opts.get(r'dry_run'):
1602 1602 self._gitcommand(command + [f])
1603 1603
1604 1604 for f in rejected:
1605 1605 ui.warn(_("%s already tracked!\n") % match.abs(f))
1606 1606
1607 1607 return rejected
1608 1608
1609 1609 @annotatesubrepoerror
1610 1610 def remove(self):
1611 1611 if self._gitmissing():
1612 1612 return
1613 1613 if self.dirty():
1614 1614 self.ui.warn(_('not removing repo %s because '
1615 1615 'it has changes.\n') % self._relpath)
1616 1616 return
1617 1617 # we can't fully delete the repository as it may contain
1618 1618 # local-only history
1619 1619 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1620 1620 self._gitcommand(['config', 'core.bare', 'true'])
1621 1621 for f, kind in self.wvfs.readdir():
1622 1622 if f == '.git':
1623 1623 continue
1624 1624 if kind == stat.S_IFDIR:
1625 1625 self.wvfs.rmtree(f)
1626 1626 else:
1627 1627 self.wvfs.unlink(f)
1628 1628
1629 1629 def archive(self, archiver, prefix, match=None, decode=True):
1630 1630 total = 0
1631 1631 source, revision = self._state
1632 1632 if not revision:
1633 1633 return total
1634 1634 self._fetch(source, revision)
1635 1635
1636 1636 # Parse git's native archive command.
1637 1637 # This should be much faster than manually traversing the trees
1638 1638 # and objects with many subprocess calls.
1639 1639 tarstream = self._gitcommand(['archive', revision], stream=True)
1640 1640 tar = tarfile.open(fileobj=tarstream, mode=r'r|')
1641 1641 relpath = subrelpath(self)
1642 1642 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1643 1643 for i, info in enumerate(tar):
1644 1644 if info.isdir():
1645 1645 continue
1646 1646 if match and not match(info.name):
1647 1647 continue
1648 1648 if info.issym():
1649 1649 data = info.linkname
1650 1650 else:
1651 1651 data = tar.extractfile(info).read()
1652 1652 archiver.addfile(prefix + self._path + '/' + info.name,
1653 1653 info.mode, info.issym(), data)
1654 1654 total += 1
1655 1655 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1656 1656 unit=_('files'))
1657 1657 self.ui.progress(_('archiving (%s)') % relpath, None)
1658 1658 return total
1659 1659
1660 1660
1661 1661 @annotatesubrepoerror
1662 1662 def cat(self, match, fm, fntemplate, prefix, **opts):
1663 1663 rev = self._state[1]
1664 1664 if match.anypats():
1665 1665 return 1 #No support for include/exclude yet
1666 1666
1667 1667 if not match.files():
1668 1668 return 1
1669 1669
1670 1670 # TODO: add support for non-plain formatter (see cmdutil.cat())
1671 1671 for f in match.files():
1672 1672 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1673 1673 fp = cmdutil.makefileobj(self._ctx, fntemplate,
1674 1674 pathname=self.wvfs.reljoin(prefix, f))
1675 1675 fp.write(output)
1676 1676 fp.close()
1677 1677 return 0
1678 1678
1679 1679
1680 1680 @annotatesubrepoerror
1681 1681 def status(self, rev2, **opts):
1682 1682 rev1 = self._state[1]
1683 1683 if self._gitmissing() or not rev1:
1684 1684 # if the repo is missing, return no results
1685 1685 return scmutil.status([], [], [], [], [], [], [])
1686 1686 modified, added, removed = [], [], []
1687 1687 self._gitupdatestat()
1688 1688 if rev2:
1689 1689 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
1690 1690 else:
1691 1691 command = ['diff-index', '--no-renames', rev1]
1692 1692 out = self._gitcommand(command)
1693 1693 for line in out.split('\n'):
1694 1694 tab = line.find('\t')
1695 1695 if tab == -1:
1696 1696 continue
1697 1697 status, f = line[tab - 1], line[tab + 1:]
1698 1698 if status == 'M':
1699 1699 modified.append(f)
1700 1700 elif status == 'A':
1701 1701 added.append(f)
1702 1702 elif status == 'D':
1703 1703 removed.append(f)
1704 1704
1705 1705 deleted, unknown, ignored, clean = [], [], [], []
1706 1706
1707 1707 command = ['status', '--porcelain', '-z']
1708 1708 if opts.get(r'unknown'):
1709 1709 command += ['--untracked-files=all']
1710 1710 if opts.get(r'ignored'):
1711 1711 command += ['--ignored']
1712 1712 out = self._gitcommand(command)
1713 1713
1714 1714 changedfiles = set()
1715 1715 changedfiles.update(modified)
1716 1716 changedfiles.update(added)
1717 1717 changedfiles.update(removed)
1718 1718 for line in out.split('\0'):
1719 1719 if not line:
1720 1720 continue
1721 1721 st = line[0:2]
1722 1722 #moves and copies show 2 files on one line
1723 1723 if line.find('\0') >= 0:
1724 1724 filename1, filename2 = line[3:].split('\0')
1725 1725 else:
1726 1726 filename1 = line[3:]
1727 1727 filename2 = None
1728 1728
1729 1729 changedfiles.add(filename1)
1730 1730 if filename2:
1731 1731 changedfiles.add(filename2)
1732 1732
1733 1733 if st == '??':
1734 1734 unknown.append(filename1)
1735 1735 elif st == '!!':
1736 1736 ignored.append(filename1)
1737 1737
1738 1738 if opts.get(r'clean'):
1739 1739 out = self._gitcommand(['ls-files'])
1740 1740 for f in out.split('\n'):
1741 1741 if not f in changedfiles:
1742 1742 clean.append(f)
1743 1743
1744 1744 return scmutil.status(modified, added, removed, deleted,
1745 1745 unknown, ignored, clean)
1746 1746
1747 1747 @annotatesubrepoerror
1748 1748 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1749 1749 node1 = self._state[1]
1750 1750 cmd = ['diff', '--no-renames']
1751 1751 if opts[r'stat']:
1752 1752 cmd.append('--stat')
1753 1753 else:
1754 1754 # for Git, this also implies '-p'
1755 1755 cmd.append('-U%d' % diffopts.context)
1756 1756
1757 1757 gitprefix = self.wvfs.reljoin(prefix, self._path)
1758 1758
1759 1759 if diffopts.noprefix:
1760 1760 cmd.extend(['--src-prefix=%s/' % gitprefix,
1761 1761 '--dst-prefix=%s/' % gitprefix])
1762 1762 else:
1763 1763 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1764 1764 '--dst-prefix=b/%s/' % gitprefix])
1765 1765
1766 1766 if diffopts.ignorews:
1767 1767 cmd.append('--ignore-all-space')
1768 1768 if diffopts.ignorewsamount:
1769 1769 cmd.append('--ignore-space-change')
1770 1770 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1771 1771 and diffopts.ignoreblanklines:
1772 1772 cmd.append('--ignore-blank-lines')
1773 1773
1774 1774 cmd.append(node1)
1775 1775 if node2:
1776 1776 cmd.append(node2)
1777 1777
1778 1778 output = ""
1779 1779 if match.always():
1780 1780 output += self._gitcommand(cmd) + '\n'
1781 1781 else:
1782 1782 st = self.status(node2)[:3]
1783 1783 files = [f for sublist in st for f in sublist]
1784 1784 for f in files:
1785 1785 if match(f):
1786 1786 output += self._gitcommand(cmd + ['--', f]) + '\n'
1787 1787
1788 1788 if output.strip():
1789 1789 ui.write(output)
1790 1790
1791 1791 @annotatesubrepoerror
1792 1792 def revert(self, substate, *pats, **opts):
1793 1793 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1794 1794 if not opts.get(r'no_backup'):
1795 1795 status = self.status(None)
1796 1796 names = status.modified
1797 1797 for name in names:
1798 1798 bakname = scmutil.origpath(self.ui, self._subparent, name)
1799 1799 self.ui.note(_('saving current version of %s as %s\n') %
1800 1800 (name, bakname))
1801 1801 self.wvfs.rename(name, bakname)
1802 1802
1803 1803 if not opts.get(r'dry_run'):
1804 1804 self.get(substate, overwrite=True)
1805 1805 return []
1806 1806
1807 1807 def shortid(self, revid):
1808 1808 return revid[:7]
1809 1809
1810 1810 types = {
1811 1811 'hg': hgsubrepo,
1812 1812 'svn': svnsubrepo,
1813 1813 'git': gitsubrepo,
1814 1814 }
@@ -1,274 +1,329
1 1 $ hg init a
2 2 $ cd a
3 3 $ echo a > a
4 4 $ hg add -n
5 5 adding a
6 6 $ hg st
7 7 ? a
8 8 $ hg add
9 9 adding a
10 10 $ hg st
11 11 A a
12 12 $ hg forget a
13 13 $ hg add
14 14 adding a
15 15 $ hg st
16 16 A a
17 17 $ mkdir dir
18 18 $ cd dir
19 19 $ hg add ../a
20 20 ../a already tracked!
21 21 $ cd ..
22 22
23 23 $ echo b > b
24 24 $ hg add -n b
25 25 $ hg st
26 26 A a
27 27 ? b
28 28 $ hg add b
29 29 $ hg st
30 30 A a
31 31 A b
32 32
33 33 should fail
34 34
35 35 $ hg add b
36 36 b already tracked!
37 37 $ hg st
38 38 A a
39 39 A b
40 40
41 41 #if no-windows
42 42 $ echo foo > con.xml
43 43 $ hg --config ui.portablefilenames=jump add con.xml
44 44 abort: ui.portablefilenames value is invalid ('jump')
45 45 [255]
46 46 $ hg --config ui.portablefilenames=abort add con.xml
47 47 abort: filename contains 'con', which is reserved on Windows: con.xml
48 48 [255]
49 49 $ hg st
50 50 A a
51 51 A b
52 52 ? con.xml
53 53 $ hg add con.xml
54 54 warning: filename contains 'con', which is reserved on Windows: con.xml
55 55 $ hg st
56 56 A a
57 57 A b
58 58 A con.xml
59 59 $ hg forget con.xml
60 60 $ rm con.xml
61 61 #endif
62 62
63 63 #if eol-in-paths
64 64 $ echo bla > 'hello:world'
65 65 $ hg --config ui.portablefilenames=abort add
66 66 adding hello:world
67 67 abort: filename contains ':', which is reserved on Windows: 'hello:world'
68 68 [255]
69 69 $ hg st
70 70 A a
71 71 A b
72 72 ? hello:world
73 73 $ hg --config ui.portablefilenames=ignore add
74 74 adding hello:world
75 75 $ hg st
76 76 A a
77 77 A b
78 78 A hello:world
79 79 #endif
80 80
81 81 $ hg ci -m 0 --traceback
82 82
83 83 $ hg log -r "heads(. or wdir() & file('**'))"
84 84 changeset: 0:* (glob)
85 85 tag: tip
86 86 user: test
87 87 date: Thu Jan 01 00:00:00 1970 +0000
88 88 summary: 0
89 89
90 90 should fail
91 91
92 92 $ hg add a
93 93 a already tracked!
94 94
95 95 $ echo aa > a
96 96 $ hg ci -m 1
97 97 $ hg up 0
98 98 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
99 99 $ echo aaa > a
100 100 $ hg ci -m 2
101 101 created new head
102 102
103 103 $ hg merge
104 104 merging a
105 105 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
106 106 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
107 107 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
108 108 [1]
109 109 $ hg st
110 110 M a
111 111 ? a.orig
112 112
113 113 wdir doesn't cause a crash, and can be dynamically selected if dirty
114 114
115 115 $ hg log -r "heads(. or wdir() & file('**'))"
116 116 changeset: 2147483647:ffffffffffff
117 117 parent: 2:* (glob)
118 118 parent: 1:* (glob)
119 119 user: test
120 120 date: * (glob)
121 121
122 122 should fail
123 123
124 124 $ hg add a
125 125 a already tracked!
126 126 $ hg st
127 127 M a
128 128 ? a.orig
129 129 $ hg resolve -m a
130 130 (no more unresolved files)
131 131 $ hg ci -m merge
132 132
133 133 Issue683: peculiarity with hg revert of an removed then added file
134 134
135 135 $ hg forget a
136 136 $ hg add a
137 137 $ hg st
138 138 ? a.orig
139 139 $ hg rm a
140 140 $ hg st
141 141 R a
142 142 ? a.orig
143 143 $ echo a > a
144 144 $ hg add a
145 145 $ hg st
146 146 M a
147 147 ? a.orig
148 148
149 149 excluded file shouldn't be added even if it is explicitly specified
150 150
151 151 $ hg add a.orig -X '*.orig'
152 152 $ hg st
153 153 M a
154 154 ? a.orig
155 155
156 156 Forgotten file can be added back (as either clean or modified)
157 157
158 158 $ hg forget b
159 159 $ hg add b
160 160 $ hg st -A b
161 161 C b
162 162 $ hg forget b
163 163 $ echo modified > b
164 164 $ hg add b
165 165 $ hg st -A b
166 166 M b
167 167 $ hg revert -qC b
168 168
169 169 $ hg add c && echo "unexpected addition of missing file"
170 170 c: * (glob)
171 171 [1]
172 172 $ echo c > c
173 173 $ hg add d c && echo "unexpected addition of missing file"
174 174 d: * (glob)
175 175 [1]
176 176 $ hg st
177 177 M a
178 178 A c
179 179 ? a.orig
180 180 $ hg up -C
181 181 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
182 182
183 183 forget and get should have the right order: added but missing dir should be
184 184 forgotten before file with same name is added
185 185
186 186 $ echo file d > d
187 187 $ hg add d
188 188 $ hg ci -md
189 189 $ hg rm d
190 190 $ mkdir d
191 191 $ echo a > d/a
192 192 $ hg add d/a
193 193 $ rm -r d
194 194 $ hg up -C
195 195 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
196 196 $ cat d
197 197 file d
198 198
199 199 Test that adding a directory doesn't require case matching (issue4578)
200 200 #if icasefs
201 201 $ mkdir -p CapsDir1/CapsDir
202 202 $ echo abc > CapsDir1/CapsDir/AbC.txt
203 203 $ mkdir CapsDir1/CapsDir/SubDir
204 204 $ echo def > CapsDir1/CapsDir/SubDir/Def.txt
205 205
206 206 $ hg add capsdir1/capsdir
207 207 adding CapsDir1/CapsDir/AbC.txt
208 208 adding CapsDir1/CapsDir/SubDir/Def.txt
209 209
210 210 $ hg forget capsdir1/capsdir/abc.txt
211 211
212 212 $ hg forget capsdir1/capsdir
213 213 removing CapsDir1/CapsDir/SubDir/Def.txt
214 214
215 215 $ hg add capsdir1
216 216 adding CapsDir1/CapsDir/AbC.txt
217 217 adding CapsDir1/CapsDir/SubDir/Def.txt
218 218
219 219 $ hg ci -m "AbCDef" capsdir1/capsdir
220 220
221 221 $ hg status -A capsdir1/capsdir
222 222 C CapsDir1/CapsDir/AbC.txt
223 223 C CapsDir1/CapsDir/SubDir/Def.txt
224 224
225 225 $ hg files capsdir1/capsdir
226 226 CapsDir1/CapsDir/AbC.txt
227 227 CapsDir1/CapsDir/SubDir/Def.txt
228 228
229 229 $ echo xyz > CapsDir1/CapsDir/SubDir/Def.txt
230 230 $ hg ci -m xyz capsdir1/capsdir/subdir/def.txt
231 231
232 232 $ hg revert -r '.^' capsdir1/capsdir
233 233 reverting CapsDir1/CapsDir/SubDir/Def.txt
234 234
235 235 The conditional tests above mean the hash on the diff line differs on Windows
236 236 and OS X
237 237 $ hg diff capsdir1/capsdir
238 238 diff -r * CapsDir1/CapsDir/SubDir/Def.txt (glob)
239 239 --- a/CapsDir1/CapsDir/SubDir/Def.txt Thu Jan 01 00:00:00 1970 +0000
240 240 +++ b/CapsDir1/CapsDir/SubDir/Def.txt * (glob)
241 241 @@ -1,1 +1,1 @@
242 242 -xyz
243 243 +def
244 244
245 245 $ hg mv CapsDir1/CapsDir/abc.txt CapsDir1/CapsDir/ABC.txt
246 246 $ hg ci -m "case changing rename" CapsDir1/CapsDir/AbC.txt CapsDir1/CapsDir/ABC.txt
247 247
248 248 $ hg status -A capsdir1/capsdir
249 249 M CapsDir1/CapsDir/SubDir/Def.txt
250 250 C CapsDir1/CapsDir/ABC.txt
251 251
252 252 $ hg remove -f 'glob:**.txt' -X capsdir1/capsdir
253 253 $ hg remove -f 'glob:**.txt' -I capsdir1/capsdir
254 254 removing CapsDir1/CapsDir/ABC.txt
255 255 removing CapsDir1/CapsDir/SubDir/Def.txt
256 256 #endif
257 257
258 258 $ cd ..
259 259
260 260 test --dry-run mode in forget
261 261
262 262 $ hg init testdir_forget
263 263 $ cd testdir_forget
264 264 $ echo foo > foo
265 265 $ hg add foo
266 266 $ hg commit -m "foo"
267 267 $ hg forget foo --dry-run -v
268 268 removing foo
269 269 $ hg diff
270 270 $ hg forget not_exist -n
271 271 not_exist: $ENOENT$
272 272 [1]
273 273
274 274 $ cd ..
275
276 test --confirm option in forget
277
278 $ hg init forgetconfirm
279 $ cd forgetconfirm
280 $ echo foo > foo
281 $ hg commit -qAm "foo"
282 $ echo bar > bar
283 $ hg commit -qAm "bar"
284 $ hg forget foo --dry-run --confirm
285 abort: cannot specify both --dry-run and --confirm
286 [255]
287
288 $ hg forget foo --config ui.interactive=True --confirm << EOF
289 > ?
290 > n
291 > EOF
292 forget foo [Ynsa?] ?
293 y - yes, forget this file
294 n - no, skip this file
295 s - skip remaining files
296 a - include all remaining files
297 ? - ? (display help)
298 forget foo [Ynsa?] n
299
300 $ hg forget foo bar --config ui.interactive=True --confirm << EOF
301 > y
302 > n
303 > EOF
304 forget bar [Ynsa?] y
305 forget foo [Ynsa?] n
306 removing bar
307 $ hg status
308 R bar
309 $ hg up -qC .
310
311 $ hg forget foo bar --config ui.interactive=True --confirm << EOF
312 > s
313 > EOF
314 forget bar [Ynsa?] s
315 $ hg st
316 $ hg up -qC .
317
318 $ hg forget foo bar --config ui.interactive=True --confirm << EOF
319 > a
320 > EOF
321 forget bar [Ynsa?] a
322 removing bar
323 removing foo
324 $ hg status
325 R bar
326 R foo
327 $ hg up -qC .
328
329 $ cd ..
@@ -1,401 +1,401
1 1 Show all commands except debug commands
2 2 $ hg debugcomplete
3 3 add
4 4 addremove
5 5 annotate
6 6 archive
7 7 backout
8 8 bisect
9 9 bookmarks
10 10 branch
11 11 branches
12 12 bundle
13 13 cat
14 14 clone
15 15 commit
16 16 config
17 17 copy
18 18 diff
19 19 export
20 20 files
21 21 forget
22 22 graft
23 23 grep
24 24 heads
25 25 help
26 26 identify
27 27 import
28 28 incoming
29 29 init
30 30 locate
31 31 log
32 32 manifest
33 33 merge
34 34 outgoing
35 35 parents
36 36 paths
37 37 phase
38 38 pull
39 39 push
40 40 recover
41 41 remove
42 42 rename
43 43 resolve
44 44 revert
45 45 rollback
46 46 root
47 47 serve
48 48 status
49 49 summary
50 50 tag
51 51 tags
52 52 tip
53 53 unbundle
54 54 update
55 55 verify
56 56 version
57 57
58 58 Show all commands that start with "a"
59 59 $ hg debugcomplete a
60 60 add
61 61 addremove
62 62 annotate
63 63 archive
64 64
65 65 Do not show debug commands if there are other candidates
66 66 $ hg debugcomplete d
67 67 diff
68 68
69 69 Show debug commands if there are no other candidates
70 70 $ hg debugcomplete debug
71 71 debugancestor
72 72 debugapplystreamclonebundle
73 73 debugbuilddag
74 74 debugbundle
75 75 debugcapabilities
76 76 debugcheckstate
77 77 debugcolor
78 78 debugcommands
79 79 debugcomplete
80 80 debugconfig
81 81 debugcreatestreamclonebundle
82 82 debugdag
83 83 debugdata
84 84 debugdate
85 85 debugdeltachain
86 86 debugdirstate
87 87 debugdiscovery
88 88 debugdownload
89 89 debugextensions
90 90 debugfileset
91 91 debugformat
92 92 debugfsinfo
93 93 debuggetbundle
94 94 debugignore
95 95 debugindex
96 96 debugindexdot
97 97 debuginstall
98 98 debugknown
99 99 debuglabelcomplete
100 100 debuglocks
101 101 debugmergestate
102 102 debugnamecomplete
103 103 debugobsolete
104 104 debugpathcomplete
105 105 debugpeer
106 106 debugpickmergetool
107 107 debugpushkey
108 108 debugpvec
109 109 debugrebuilddirstate
110 110 debugrebuildfncache
111 111 debugrename
112 112 debugrevlog
113 113 debugrevspec
114 114 debugserve
115 115 debugsetparents
116 116 debugssl
117 117 debugsub
118 118 debugsuccessorssets
119 119 debugtemplate
120 120 debuguigetpass
121 121 debuguiprompt
122 122 debugupdatecaches
123 123 debugupgraderepo
124 124 debugwalk
125 125 debugwhyunstable
126 126 debugwireargs
127 127 debugwireproto
128 128
129 129 Do not show the alias of a debug command if there are other candidates
130 130 (this should hide rawcommit)
131 131 $ hg debugcomplete r
132 132 recover
133 133 remove
134 134 rename
135 135 resolve
136 136 revert
137 137 rollback
138 138 root
139 139 Show the alias of a debug command if there are no other candidates
140 140 $ hg debugcomplete rawc
141 141
142 142
143 143 Show the global options
144 144 $ hg debugcomplete --options | sort
145 145 --color
146 146 --config
147 147 --cwd
148 148 --debug
149 149 --debugger
150 150 --encoding
151 151 --encodingmode
152 152 --help
153 153 --hidden
154 154 --noninteractive
155 155 --pager
156 156 --profile
157 157 --quiet
158 158 --repository
159 159 --time
160 160 --traceback
161 161 --verbose
162 162 --version
163 163 -R
164 164 -h
165 165 -q
166 166 -v
167 167 -y
168 168
169 169 Show the options for the "serve" command
170 170 $ hg debugcomplete --options serve | sort
171 171 --accesslog
172 172 --address
173 173 --certificate
174 174 --cmdserver
175 175 --color
176 176 --config
177 177 --cwd
178 178 --daemon
179 179 --daemon-postexec
180 180 --debug
181 181 --debugger
182 182 --encoding
183 183 --encodingmode
184 184 --errorlog
185 185 --help
186 186 --hidden
187 187 --ipv6
188 188 --name
189 189 --noninteractive
190 190 --pager
191 191 --pid-file
192 192 --port
193 193 --prefix
194 194 --profile
195 195 --quiet
196 196 --repository
197 197 --stdio
198 198 --style
199 199 --subrepos
200 200 --templates
201 201 --time
202 202 --traceback
203 203 --verbose
204 204 --version
205 205 --web-conf
206 206 -6
207 207 -A
208 208 -E
209 209 -R
210 210 -S
211 211 -a
212 212 -d
213 213 -h
214 214 -n
215 215 -p
216 216 -q
217 217 -t
218 218 -v
219 219 -y
220 220
221 221 Show an error if we use --options with an ambiguous abbreviation
222 222 $ hg debugcomplete --options s
223 223 hg: command 's' is ambiguous:
224 224 serve showconfig status summary
225 225 [255]
226 226
227 227 Show all commands + options
228 228 $ hg debugcommands
229 229 add: include, exclude, subrepos, dry-run
230 230 annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, skip, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, include, exclude, template
231 231 clone: noupdate, updaterev, rev, branch, pull, uncompressed, stream, ssh, remotecmd, insecure
232 232 commit: addremove, close-branch, amend, secret, edit, interactive, include, exclude, message, logfile, date, user, subrepos
233 233 diff: rev, change, text, git, binary, nodates, noprefix, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, unified, stat, root, include, exclude, subrepos
234 234 export: output, switch-parent, rev, text, git, binary, nodates, template
235 forget: include, exclude, dry-run
235 forget: include, exclude, dry-run, confirm
236 236 init: ssh, remotecmd, insecure
237 237 log: follow, follow-first, date, copies, keyword, rev, line-range, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
238 238 merge: force, rev, preview, abort, tool
239 239 pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
240 240 push: force, rev, bookmark, branch, new-branch, pushvars, ssh, remotecmd, insecure
241 241 remove: after, force, subrepos, include, exclude, dry-run
242 242 serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate, subrepos
243 243 status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, terse, copies, print0, rev, change, include, exclude, subrepos, template
244 244 summary: remote
245 245 update: clean, check, merge, date, rev, tool
246 246 addremove: similarity, subrepos, include, exclude, dry-run
247 247 archive: no-decode, prefix, rev, type, subrepos, include, exclude
248 248 backout: merge, commit, no-commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user
249 249 bisect: reset, good, bad, skip, extend, command, noupdate
250 250 bookmarks: force, rev, delete, rename, inactive, template
251 251 branch: force, clean, rev
252 252 branches: active, closed, template
253 253 bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure
254 254 cat: output, rev, decode, include, exclude, template
255 255 config: untrusted, edit, local, global, template
256 256 copy: after, force, include, exclude, dry-run
257 257 debugancestor:
258 258 debugapplystreamclonebundle:
259 259 debugbuilddag: mergeable-file, overwritten-file, new-file
260 260 debugbundle: all, part-type, spec
261 261 debugcapabilities:
262 262 debugcheckstate:
263 263 debugcolor: style
264 264 debugcommands:
265 265 debugcomplete: options
266 266 debugcreatestreamclonebundle:
267 267 debugdag: tags, branches, dots, spaces
268 268 debugdata: changelog, manifest, dir
269 269 debugdate: extended
270 270 debugdeltachain: changelog, manifest, dir, template
271 271 debugdirstate: nodates, datesort
272 272 debugdiscovery: old, nonheads, rev, ssh, remotecmd, insecure
273 273 debugdownload: output
274 274 debugextensions: template
275 275 debugfileset: rev
276 276 debugformat: template
277 277 debugfsinfo:
278 278 debuggetbundle: head, common, type
279 279 debugignore:
280 280 debugindex: changelog, manifest, dir, format
281 281 debugindexdot: changelog, manifest, dir
282 282 debuginstall: template
283 283 debugknown:
284 284 debuglabelcomplete:
285 285 debuglocks: force-lock, force-wlock, set-lock, set-wlock
286 286 debugmergestate:
287 287 debugnamecomplete:
288 288 debugobsolete: flags, record-parents, rev, exclusive, index, delete, date, user, template
289 289 debugpathcomplete: full, normal, added, removed
290 290 debugpeer:
291 291 debugpickmergetool: rev, changedelete, include, exclude, tool
292 292 debugpushkey:
293 293 debugpvec:
294 294 debugrebuilddirstate: rev, minimal
295 295 debugrebuildfncache:
296 296 debugrename: rev
297 297 debugrevlog: changelog, manifest, dir, dump
298 298 debugrevspec: optimize, show-revs, show-set, show-stage, no-optimized, verify-optimized
299 299 debugserve: sshstdio, logiofd, logiofile
300 300 debugsetparents:
301 301 debugssl:
302 302 debugsub: rev
303 303 debugsuccessorssets: closest
304 304 debugtemplate: rev, define
305 305 debuguigetpass: prompt
306 306 debuguiprompt: prompt
307 307 debugupdatecaches:
308 308 debugupgraderepo: optimize, run
309 309 debugwalk: include, exclude
310 310 debugwhyunstable:
311 311 debugwireargs: three, four, five, ssh, remotecmd, insecure
312 312 debugwireproto: localssh, peer, noreadstderr, nologhandshake, ssh, remotecmd, insecure
313 313 files: rev, print0, include, exclude, template, subrepos
314 314 graft: rev, continue, edit, log, force, currentdate, currentuser, date, user, tool, dry-run
315 315 grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, rev, user, date, template, include, exclude
316 316 heads: rev, topo, active, closed, style, template
317 317 help: extension, command, keyword, system
318 318 identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure, template
319 319 import: strip, base, edit, force, no-commit, bypass, partial, exact, prefix, import-branch, message, logfile, date, user, similarity
320 320 incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
321 321 locate: rev, print0, fullpath, include, exclude
322 322 manifest: rev, all, template
323 323 outgoing: force, rev, newest-first, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
324 324 parents: rev, style, template
325 325 paths: template
326 326 phase: public, draft, secret, force, rev
327 327 recover:
328 328 rename: after, force, include, exclude, dry-run
329 329 resolve: all, list, mark, unmark, no-status, tool, include, exclude, template
330 330 revert: all, date, rev, no-backup, interactive, include, exclude, dry-run
331 331 rollback: dry-run, force
332 332 root:
333 333 tag: force, local, rev, remove, edit, message, date, user
334 334 tags: template
335 335 tip: patch, git, style, template
336 336 unbundle: update
337 337 verify:
338 338 version: template
339 339
340 340 $ hg init a
341 341 $ cd a
342 342 $ echo fee > fee
343 343 $ hg ci -q -Amfee
344 344 $ hg tag fee
345 345 $ mkdir fie
346 346 $ echo dead > fie/dead
347 347 $ echo live > fie/live
348 348 $ hg bookmark fo
349 349 $ hg branch -q fie
350 350 $ hg ci -q -Amfie
351 351 $ echo fo > fo
352 352 $ hg branch -qf default
353 353 $ hg ci -q -Amfo
354 354 $ echo Fum > Fum
355 355 $ hg ci -q -AmFum
356 356 $ hg bookmark Fum
357 357
358 358 Test debugpathcomplete
359 359
360 360 $ hg debugpathcomplete f
361 361 fee
362 362 fie
363 363 fo
364 364 $ hg debugpathcomplete -f f
365 365 fee
366 366 fie/dead
367 367 fie/live
368 368 fo
369 369
370 370 $ hg rm Fum
371 371 $ hg debugpathcomplete -r F
372 372 Fum
373 373
374 374 Test debugnamecomplete
375 375
376 376 $ hg debugnamecomplete
377 377 Fum
378 378 default
379 379 fee
380 380 fie
381 381 fo
382 382 tip
383 383 $ hg debugnamecomplete f
384 384 fee
385 385 fie
386 386 fo
387 387
388 388 Test debuglabelcomplete, a deprecated name for debugnamecomplete that is still
389 389 used for completions in some shells.
390 390
391 391 $ hg debuglabelcomplete
392 392 Fum
393 393 default
394 394 fee
395 395 fie
396 396 fo
397 397 tip
398 398 $ hg debuglabelcomplete f
399 399 fee
400 400 fie
401 401 fo
General Comments 0
You need to be logged in to leave comments. Login now