##// END OF EJS Templates
templatekw: move getrenamedfn() to scmutil (API)...
Martin von Zweigbergk -
r41947:e9b9ee9a default
parent child Browse files
Show More
@@ -1,1142 +1,1141 b''
1 1 # __init__.py - remotefilelog extension
2 2 #
3 3 # Copyright 2013 Facebook, Inc.
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 """remotefilelog causes Mercurial to lazilly fetch file contents (EXPERIMENTAL)
8 8
9 9 This extension is HIGHLY EXPERIMENTAL. There are NO BACKWARDS COMPATIBILITY
10 10 GUARANTEES. This means that repositories created with this extension may
11 11 only be usable with the exact version of this extension/Mercurial that was
12 12 used. The extension attempts to enforce this in order to prevent repository
13 13 corruption.
14 14
15 15 remotefilelog works by fetching file contents lazily and storing them
16 16 in a cache on the client rather than in revlogs. This allows enormous
17 17 histories to be transferred only partially, making them easier to
18 18 operate on.
19 19
20 20 Configs:
21 21
22 22 ``packs.maxchainlen`` specifies the maximum delta chain length in pack files
23 23
24 24 ``packs.maxpacksize`` specifies the maximum pack file size
25 25
26 26 ``packs.maxpackfilecount`` specifies the maximum number of packs in the
27 27 shared cache (trees only for now)
28 28
29 29 ``remotefilelog.backgroundprefetch`` runs prefetch in background when True
30 30
31 31 ``remotefilelog.bgprefetchrevs`` specifies revisions to fetch on commit and
32 32 update, and on other commands that use them. Different from pullprefetch.
33 33
34 34 ``remotefilelog.gcrepack`` does garbage collection during repack when True
35 35
36 36 ``remotefilelog.nodettl`` specifies maximum TTL of a node in seconds before
37 37 it is garbage collected
38 38
39 39 ``remotefilelog.repackonhggc`` runs repack on hg gc when True
40 40
41 41 ``remotefilelog.prefetchdays`` specifies the maximum age of a commit in
42 42 days after which it is no longer prefetched.
43 43
44 44 ``remotefilelog.prefetchdelay`` specifies delay between background
45 45 prefetches in seconds after operations that change the working copy parent
46 46
47 47 ``remotefilelog.data.gencountlimit`` constraints the minimum number of data
48 48 pack files required to be considered part of a generation. In particular,
49 49 minimum number of packs files > gencountlimit.
50 50
51 51 ``remotefilelog.data.generations`` list for specifying the lower bound of
52 52 each generation of the data pack files. For example, list ['100MB','1MB']
53 53 or ['1MB', '100MB'] will lead to three generations: [0, 1MB), [
54 54 1MB, 100MB) and [100MB, infinity).
55 55
56 56 ``remotefilelog.data.maxrepackpacks`` the maximum number of pack files to
57 57 include in an incremental data repack.
58 58
59 59 ``remotefilelog.data.repackmaxpacksize`` the maximum size of a pack file for
60 60 it to be considered for an incremental data repack.
61 61
62 62 ``remotefilelog.data.repacksizelimit`` the maximum total size of pack files
63 63 to include in an incremental data repack.
64 64
65 65 ``remotefilelog.history.gencountlimit`` constraints the minimum number of
66 66 history pack files required to be considered part of a generation. In
67 67 particular, minimum number of packs files > gencountlimit.
68 68
69 69 ``remotefilelog.history.generations`` list for specifying the lower bound of
70 70 each generation of the history pack files. For example, list [
71 71 '100MB', '1MB'] or ['1MB', '100MB'] will lead to three generations: [
72 72 0, 1MB), [1MB, 100MB) and [100MB, infinity).
73 73
74 74 ``remotefilelog.history.maxrepackpacks`` the maximum number of pack files to
75 75 include in an incremental history repack.
76 76
77 77 ``remotefilelog.history.repackmaxpacksize`` the maximum size of a pack file
78 78 for it to be considered for an incremental history repack.
79 79
80 80 ``remotefilelog.history.repacksizelimit`` the maximum total size of pack
81 81 files to include in an incremental history repack.
82 82
83 83 ``remotefilelog.backgroundrepack`` automatically consolidate packs in the
84 84 background
85 85
86 86 ``remotefilelog.cachepath`` path to cache
87 87
88 88 ``remotefilelog.cachegroup`` if set, make cache directory sgid to this
89 89 group
90 90
91 91 ``remotefilelog.cacheprocess`` binary to invoke for fetching file data
92 92
93 93 ``remotefilelog.debug`` turn on remotefilelog-specific debug output
94 94
95 95 ``remotefilelog.excludepattern`` pattern of files to exclude from pulls
96 96
97 97 ``remotefilelog.includepattern`` pattern of files to include in pulls
98 98
99 99 ``remotefilelog.fetchwarning``: message to print when too many
100 100 single-file fetches occur
101 101
102 102 ``remotefilelog.getfilesstep`` number of files to request in a single RPC
103 103
104 104 ``remotefilelog.getfilestype`` if set to 'threaded' use threads to fetch
105 105 files, otherwise use optimistic fetching
106 106
107 107 ``remotefilelog.pullprefetch`` revset for selecting files that should be
108 108 eagerly downloaded rather than lazily
109 109
110 110 ``remotefilelog.reponame`` name of the repo. If set, used to partition
111 111 data from other repos in a shared store.
112 112
113 113 ``remotefilelog.server`` if true, enable server-side functionality
114 114
115 115 ``remotefilelog.servercachepath`` path for caching blobs on the server
116 116
117 117 ``remotefilelog.serverexpiration`` number of days to keep cached server
118 118 blobs
119 119
120 120 ``remotefilelog.validatecache`` if set, check cache entries for corruption
121 121 before returning blobs
122 122
123 123 ``remotefilelog.validatecachelog`` if set, check cache entries for
124 124 corruption before returning metadata
125 125
126 126 """
127 127 from __future__ import absolute_import
128 128
129 129 import os
130 130 import time
131 131 import traceback
132 132
133 133 from mercurial.node import hex
134 134 from mercurial.i18n import _
135 135 from mercurial import (
136 136 changegroup,
137 137 changelog,
138 138 cmdutil,
139 139 commands,
140 140 configitems,
141 141 context,
142 142 copies,
143 143 debugcommands as hgdebugcommands,
144 144 dispatch,
145 145 error,
146 146 exchange,
147 147 extensions,
148 148 hg,
149 149 localrepo,
150 150 match,
151 151 merge,
152 152 node as nodemod,
153 153 patch,
154 154 pycompat,
155 155 registrar,
156 156 repair,
157 157 repoview,
158 158 revset,
159 159 scmutil,
160 160 smartset,
161 161 streamclone,
162 templatekw,
163 162 util,
164 163 )
165 164 from . import (
166 165 constants,
167 166 debugcommands,
168 167 fileserverclient,
169 168 remotefilectx,
170 169 remotefilelog,
171 170 remotefilelogserver,
172 171 repack as repackmod,
173 172 shallowbundle,
174 173 shallowrepo,
175 174 shallowstore,
176 175 shallowutil,
177 176 shallowverifier,
178 177 )
179 178
180 179 # ensures debug commands are registered
181 180 hgdebugcommands.command
182 181
183 182 cmdtable = {}
184 183 command = registrar.command(cmdtable)
185 184
186 185 configtable = {}
187 186 configitem = registrar.configitem(configtable)
188 187
189 188 configitem('remotefilelog', 'debug', default=False)
190 189
191 190 configitem('remotefilelog', 'reponame', default='')
192 191 configitem('remotefilelog', 'cachepath', default=None)
193 192 configitem('remotefilelog', 'cachegroup', default=None)
194 193 configitem('remotefilelog', 'cacheprocess', default=None)
195 194 configitem('remotefilelog', 'cacheprocess.includepath', default=None)
196 195 configitem("remotefilelog", "cachelimit", default="1000 GB")
197 196
198 197 configitem('remotefilelog', 'fallbackpath', default=configitems.dynamicdefault,
199 198 alias=[('remotefilelog', 'fallbackrepo')])
200 199
201 200 configitem('remotefilelog', 'validatecachelog', default=None)
202 201 configitem('remotefilelog', 'validatecache', default='on')
203 202 configitem('remotefilelog', 'server', default=None)
204 203 configitem('remotefilelog', 'servercachepath', default=None)
205 204 configitem("remotefilelog", "serverexpiration", default=30)
206 205 configitem('remotefilelog', 'backgroundrepack', default=False)
207 206 configitem('remotefilelog', 'bgprefetchrevs', default=None)
208 207 configitem('remotefilelog', 'pullprefetch', default=None)
209 208 configitem('remotefilelog', 'backgroundprefetch', default=False)
210 209 configitem('remotefilelog', 'prefetchdelay', default=120)
211 210 configitem('remotefilelog', 'prefetchdays', default=14)
212 211
213 212 configitem('remotefilelog', 'getfilesstep', default=10000)
214 213 configitem('remotefilelog', 'getfilestype', default='optimistic')
215 214 configitem('remotefilelog', 'batchsize', configitems.dynamicdefault)
216 215 configitem('remotefilelog', 'fetchwarning', default='')
217 216
218 217 configitem('remotefilelog', 'includepattern', default=None)
219 218 configitem('remotefilelog', 'excludepattern', default=None)
220 219
221 220 configitem('remotefilelog', 'gcrepack', default=False)
222 221 configitem('remotefilelog', 'repackonhggc', default=False)
223 222 configitem('repack', 'chainorphansbysize', default=True)
224 223
225 224 configitem('packs', 'maxpacksize', default=0)
226 225 configitem('packs', 'maxchainlen', default=1000)
227 226
228 227 # default TTL limit is 30 days
229 228 _defaultlimit = 60 * 60 * 24 * 30
230 229 configitem('remotefilelog', 'nodettl', default=_defaultlimit)
231 230
232 231 configitem('remotefilelog', 'data.gencountlimit', default=2),
233 232 configitem('remotefilelog', 'data.generations',
234 233 default=['1GB', '100MB', '1MB'])
235 234 configitem('remotefilelog', 'data.maxrepackpacks', default=50)
236 235 configitem('remotefilelog', 'data.repackmaxpacksize', default='4GB')
237 236 configitem('remotefilelog', 'data.repacksizelimit', default='100MB')
238 237
239 238 configitem('remotefilelog', 'history.gencountlimit', default=2),
240 239 configitem('remotefilelog', 'history.generations', default=['100MB'])
241 240 configitem('remotefilelog', 'history.maxrepackpacks', default=50)
242 241 configitem('remotefilelog', 'history.repackmaxpacksize', default='400MB')
243 242 configitem('remotefilelog', 'history.repacksizelimit', default='100MB')
244 243
245 244 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
246 245 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
247 246 # be specifying the version(s) of Mercurial they are tested with, or
248 247 # leave the attribute unspecified.
249 248 testedwith = 'ships-with-hg-core'
250 249
251 250 repoclass = localrepo.localrepository
252 251 repoclass._basesupported.add(constants.SHALLOWREPO_REQUIREMENT)
253 252
254 253 isenabled = shallowutil.isenabled
255 254
256 255 def uisetup(ui):
257 256 """Wraps user facing Mercurial commands to swap them out with shallow
258 257 versions.
259 258 """
260 259 hg.wirepeersetupfuncs.append(fileserverclient.peersetup)
261 260
262 261 entry = extensions.wrapcommand(commands.table, 'clone', cloneshallow)
263 262 entry[1].append(('', 'shallow', None,
264 263 _("create a shallow clone which uses remote file "
265 264 "history")))
266 265
267 266 extensions.wrapcommand(commands.table, 'debugindex',
268 267 debugcommands.debugindex)
269 268 extensions.wrapcommand(commands.table, 'debugindexdot',
270 269 debugcommands.debugindexdot)
271 270 extensions.wrapcommand(commands.table, 'log', log)
272 271 extensions.wrapcommand(commands.table, 'pull', pull)
273 272
274 273 # Prevent 'hg manifest --all'
275 274 def _manifest(orig, ui, repo, *args, **opts):
276 275 if (isenabled(repo) and opts.get(r'all')):
277 276 raise error.Abort(_("--all is not supported in a shallow repo"))
278 277
279 278 return orig(ui, repo, *args, **opts)
280 279 extensions.wrapcommand(commands.table, "manifest", _manifest)
281 280
282 281 # Wrap remotefilelog with lfs code
283 282 def _lfsloaded(loaded=False):
284 283 lfsmod = None
285 284 try:
286 285 lfsmod = extensions.find('lfs')
287 286 except KeyError:
288 287 pass
289 288 if lfsmod:
290 289 lfsmod.wrapfilelog(remotefilelog.remotefilelog)
291 290 fileserverclient._lfsmod = lfsmod
292 291 extensions.afterloaded('lfs', _lfsloaded)
293 292
294 293 # debugdata needs remotefilelog.len to work
295 294 extensions.wrapcommand(commands.table, 'debugdata', debugdatashallow)
296 295
297 296 def cloneshallow(orig, ui, repo, *args, **opts):
298 297 if opts.get(r'shallow'):
299 298 repos = []
300 299 def pull_shallow(orig, self, *args, **kwargs):
301 300 if not isenabled(self):
302 301 repos.append(self.unfiltered())
303 302 # set up the client hooks so the post-clone update works
304 303 setupclient(self.ui, self.unfiltered())
305 304
306 305 # setupclient fixed the class on the repo itself
307 306 # but we also need to fix it on the repoview
308 307 if isinstance(self, repoview.repoview):
309 308 self.__class__.__bases__ = (self.__class__.__bases__[0],
310 309 self.unfiltered().__class__)
311 310 self.requirements.add(constants.SHALLOWREPO_REQUIREMENT)
312 311 self._writerequirements()
313 312
314 313 # Since setupclient hadn't been called, exchange.pull was not
315 314 # wrapped. So we need to manually invoke our version of it.
316 315 return exchangepull(orig, self, *args, **kwargs)
317 316 else:
318 317 return orig(self, *args, **kwargs)
319 318 extensions.wrapfunction(exchange, 'pull', pull_shallow)
320 319
321 320 # Wrap the stream logic to add requirements and to pass include/exclude
322 321 # patterns around.
323 322 def setup_streamout(repo, remote):
324 323 # Replace remote.stream_out with a version that sends file
325 324 # patterns.
326 325 def stream_out_shallow(orig):
327 326 caps = remote.capabilities()
328 327 if constants.NETWORK_CAP_LEGACY_SSH_GETFILES in caps:
329 328 opts = {}
330 329 if repo.includepattern:
331 330 opts[r'includepattern'] = '\0'.join(repo.includepattern)
332 331 if repo.excludepattern:
333 332 opts[r'excludepattern'] = '\0'.join(repo.excludepattern)
334 333 return remote._callstream('stream_out_shallow', **opts)
335 334 else:
336 335 return orig()
337 336 extensions.wrapfunction(remote, 'stream_out', stream_out_shallow)
338 337 def stream_wrap(orig, op):
339 338 setup_streamout(op.repo, op.remote)
340 339 return orig(op)
341 340 extensions.wrapfunction(
342 341 streamclone, 'maybeperformlegacystreamclone', stream_wrap)
343 342
344 343 def canperformstreamclone(orig, pullop, bundle2=False):
345 344 # remotefilelog is currently incompatible with the
346 345 # bundle2 flavor of streamclones, so force us to use
347 346 # v1 instead.
348 347 if 'v2' in pullop.remotebundle2caps.get('stream', []):
349 348 pullop.remotebundle2caps['stream'] = [
350 349 c for c in pullop.remotebundle2caps['stream']
351 350 if c != 'v2']
352 351 if bundle2:
353 352 return False, None
354 353 supported, requirements = orig(pullop, bundle2=bundle2)
355 354 if requirements is not None:
356 355 requirements.add(constants.SHALLOWREPO_REQUIREMENT)
357 356 return supported, requirements
358 357 extensions.wrapfunction(
359 358 streamclone, 'canperformstreamclone', canperformstreamclone)
360 359
361 360 try:
362 361 orig(ui, repo, *args, **opts)
363 362 finally:
364 363 if opts.get(r'shallow'):
365 364 for r in repos:
366 365 if util.safehasattr(r, 'fileservice'):
367 366 r.fileservice.close()
368 367
369 368 def debugdatashallow(orig, *args, **kwds):
370 369 oldlen = remotefilelog.remotefilelog.__len__
371 370 try:
372 371 remotefilelog.remotefilelog.__len__ = lambda x: 1
373 372 return orig(*args, **kwds)
374 373 finally:
375 374 remotefilelog.remotefilelog.__len__ = oldlen
376 375
377 376 def reposetup(ui, repo):
378 377 if not repo.local():
379 378 return
380 379
381 380 # put here intentionally bc doesnt work in uisetup
382 381 ui.setconfig('hooks', 'update.prefetch', wcpprefetch)
383 382 ui.setconfig('hooks', 'commit.prefetch', wcpprefetch)
384 383
385 384 isserverenabled = ui.configbool('remotefilelog', 'server')
386 385 isshallowclient = isenabled(repo)
387 386
388 387 if isserverenabled and isshallowclient:
389 388 raise RuntimeError("Cannot be both a server and shallow client.")
390 389
391 390 if isshallowclient:
392 391 setupclient(ui, repo)
393 392
394 393 if isserverenabled:
395 394 remotefilelogserver.setupserver(ui, repo)
396 395
397 396 def setupclient(ui, repo):
398 397 if not isinstance(repo, localrepo.localrepository):
399 398 return
400 399
401 400 # Even clients get the server setup since they need to have the
402 401 # wireprotocol endpoints registered.
403 402 remotefilelogserver.onetimesetup(ui)
404 403 onetimeclientsetup(ui)
405 404
406 405 shallowrepo.wraprepo(repo)
407 406 repo.store = shallowstore.wrapstore(repo.store)
408 407
409 408 clientonetime = False
410 409 def onetimeclientsetup(ui):
411 410 global clientonetime
412 411 if clientonetime:
413 412 return
414 413 clientonetime = True
415 414
416 415 changegroup.cgpacker = shallowbundle.shallowcg1packer
417 416
418 417 extensions.wrapfunction(changegroup, '_addchangegroupfiles',
419 418 shallowbundle.addchangegroupfiles)
420 419 extensions.wrapfunction(
421 420 changegroup, 'makechangegroup', shallowbundle.makechangegroup)
422 421
423 422 def storewrapper(orig, requirements, path, vfstype):
424 423 s = orig(requirements, path, vfstype)
425 424 if constants.SHALLOWREPO_REQUIREMENT in requirements:
426 425 s = shallowstore.wrapstore(s)
427 426
428 427 return s
429 428 extensions.wrapfunction(localrepo, 'makestore', storewrapper)
430 429
431 430 extensions.wrapfunction(exchange, 'pull', exchangepull)
432 431
433 432 # prefetch files before update
434 433 def applyupdates(orig, repo, actions, wctx, mctx, overwrite, labels=None):
435 434 if isenabled(repo):
436 435 manifest = mctx.manifest()
437 436 files = []
438 437 for f, args, msg in actions['g']:
439 438 files.append((f, hex(manifest[f])))
440 439 # batch fetch the needed files from the server
441 440 repo.fileservice.prefetch(files)
442 441 return orig(repo, actions, wctx, mctx, overwrite, labels=labels)
443 442 extensions.wrapfunction(merge, 'applyupdates', applyupdates)
444 443
445 444 # Prefetch merge checkunknownfiles
446 445 def checkunknownfiles(orig, repo, wctx, mctx, force, actions,
447 446 *args, **kwargs):
448 447 if isenabled(repo):
449 448 files = []
450 449 sparsematch = repo.maybesparsematch(mctx.rev())
451 450 for f, (m, actionargs, msg) in actions.iteritems():
452 451 if sparsematch and not sparsematch(f):
453 452 continue
454 453 if m in ('c', 'dc', 'cm'):
455 454 files.append((f, hex(mctx.filenode(f))))
456 455 elif m == 'dg':
457 456 f2 = actionargs[0]
458 457 files.append((f2, hex(mctx.filenode(f2))))
459 458 # batch fetch the needed files from the server
460 459 repo.fileservice.prefetch(files)
461 460 return orig(repo, wctx, mctx, force, actions, *args, **kwargs)
462 461 extensions.wrapfunction(merge, '_checkunknownfiles', checkunknownfiles)
463 462
464 463 # Prefetch files before status attempts to look at their size and contents
465 464 def checklookup(orig, self, files):
466 465 repo = self._repo
467 466 if isenabled(repo):
468 467 prefetchfiles = []
469 468 for parent in self._parents:
470 469 for f in files:
471 470 if f in parent:
472 471 prefetchfiles.append((f, hex(parent.filenode(f))))
473 472 # batch fetch the needed files from the server
474 473 repo.fileservice.prefetch(prefetchfiles)
475 474 return orig(self, files)
476 475 extensions.wrapfunction(context.workingctx, '_checklookup', checklookup)
477 476
478 477 # Prefetch the logic that compares added and removed files for renames
479 478 def findrenames(orig, repo, matcher, added, removed, *args, **kwargs):
480 479 if isenabled(repo):
481 480 files = []
482 481 parentctx = repo['.']
483 482 for f in removed:
484 483 files.append((f, hex(parentctx.filenode(f))))
485 484 # batch fetch the needed files from the server
486 485 repo.fileservice.prefetch(files)
487 486 return orig(repo, matcher, added, removed, *args, **kwargs)
488 487 extensions.wrapfunction(scmutil, '_findrenames', findrenames)
489 488
490 489 # prefetch files before mergecopies check
491 490 def computenonoverlap(orig, repo, c1, c2, *args, **kwargs):
492 491 u1, u2 = orig(repo, c1, c2, *args, **kwargs)
493 492 if isenabled(repo):
494 493 m1 = c1.manifest()
495 494 m2 = c2.manifest()
496 495 files = []
497 496
498 497 sparsematch1 = repo.maybesparsematch(c1.rev())
499 498 if sparsematch1:
500 499 sparseu1 = []
501 500 for f in u1:
502 501 if sparsematch1(f):
503 502 files.append((f, hex(m1[f])))
504 503 sparseu1.append(f)
505 504 u1 = sparseu1
506 505
507 506 sparsematch2 = repo.maybesparsematch(c2.rev())
508 507 if sparsematch2:
509 508 sparseu2 = []
510 509 for f in u2:
511 510 if sparsematch2(f):
512 511 files.append((f, hex(m2[f])))
513 512 sparseu2.append(f)
514 513 u2 = sparseu2
515 514
516 515 # batch fetch the needed files from the server
517 516 repo.fileservice.prefetch(files)
518 517 return u1, u2
519 518 extensions.wrapfunction(copies, '_computenonoverlap', computenonoverlap)
520 519
521 520 # prefetch files before pathcopies check
522 521 def computeforwardmissing(orig, a, b, match=None):
523 522 missing = list(orig(a, b, match=match))
524 523 repo = a._repo
525 524 if isenabled(repo):
526 525 mb = b.manifest()
527 526
528 527 files = []
529 528 sparsematch = repo.maybesparsematch(b.rev())
530 529 if sparsematch:
531 530 sparsemissing = []
532 531 for f in missing:
533 532 if sparsematch(f):
534 533 files.append((f, hex(mb[f])))
535 534 sparsemissing.append(f)
536 535 missing = sparsemissing
537 536
538 537 # batch fetch the needed files from the server
539 538 repo.fileservice.prefetch(files)
540 539 return missing
541 540 extensions.wrapfunction(copies, '_computeforwardmissing',
542 541 computeforwardmissing)
543 542
544 543 # close cache miss server connection after the command has finished
545 544 def runcommand(orig, lui, repo, *args, **kwargs):
546 545 fileservice = None
547 546 # repo can be None when running in chg:
548 547 # - at startup, reposetup was called because serve is not norepo
549 548 # - a norepo command like "help" is called
550 549 if repo and isenabled(repo):
551 550 fileservice = repo.fileservice
552 551 try:
553 552 return orig(lui, repo, *args, **kwargs)
554 553 finally:
555 554 if fileservice:
556 555 fileservice.close()
557 556 extensions.wrapfunction(dispatch, 'runcommand', runcommand)
558 557
559 558 # disappointing hacks below
560 templatekw.getrenamedfn = getrenamedfn
559 scmutil.getrenamedfn = getrenamedfn
561 560 extensions.wrapfunction(revset, 'filelog', filelogrevset)
562 561 revset.symbols['filelog'] = revset.filelog
563 562 extensions.wrapfunction(cmdutil, 'walkfilerevs', walkfilerevs)
564 563
565 564 # prevent strip from stripping remotefilelogs
566 565 def _collectbrokencsets(orig, repo, files, striprev):
567 566 if isenabled(repo):
568 567 files = list([f for f in files if not repo.shallowmatch(f)])
569 568 return orig(repo, files, striprev)
570 569 extensions.wrapfunction(repair, '_collectbrokencsets', _collectbrokencsets)
571 570
572 571 # Don't commit filelogs until we know the commit hash, since the hash
573 572 # is present in the filelog blob.
574 573 # This violates Mercurial's filelog->manifest->changelog write order,
575 574 # but is generally fine for client repos.
576 575 pendingfilecommits = []
577 576 def addrawrevision(orig, self, rawtext, transaction, link, p1, p2, node,
578 577 flags, cachedelta=None, _metatuple=None):
579 578 if isinstance(link, int):
580 579 pendingfilecommits.append(
581 580 (self, rawtext, transaction, link, p1, p2, node, flags,
582 581 cachedelta, _metatuple))
583 582 return node
584 583 else:
585 584 return orig(self, rawtext, transaction, link, p1, p2, node, flags,
586 585 cachedelta, _metatuple=_metatuple)
587 586 extensions.wrapfunction(
588 587 remotefilelog.remotefilelog, 'addrawrevision', addrawrevision)
589 588
590 589 def changelogadd(orig, self, *args):
591 590 oldlen = len(self)
592 591 node = orig(self, *args)
593 592 newlen = len(self)
594 593 if oldlen != newlen:
595 594 for oldargs in pendingfilecommits:
596 595 log, rt, tr, link, p1, p2, n, fl, c, m = oldargs
597 596 linknode = self.node(link)
598 597 if linknode == node:
599 598 log.addrawrevision(rt, tr, linknode, p1, p2, n, fl, c, m)
600 599 else:
601 600 raise error.ProgrammingError(
602 601 'pending multiple integer revisions are not supported')
603 602 else:
604 603 # "link" is actually wrong here (it is set to len(changelog))
605 604 # if changelog remains unchanged, skip writing file revisions
606 605 # but still do a sanity check about pending multiple revisions
607 606 if len(set(x[3] for x in pendingfilecommits)) > 1:
608 607 raise error.ProgrammingError(
609 608 'pending multiple integer revisions are not supported')
610 609 del pendingfilecommits[:]
611 610 return node
612 611 extensions.wrapfunction(changelog.changelog, 'add', changelogadd)
613 612
614 613 # changectx wrappers
615 614 def filectx(orig, self, path, fileid=None, filelog=None):
616 615 if fileid is None:
617 616 fileid = self.filenode(path)
618 617 if (isenabled(self._repo) and self._repo.shallowmatch(path)):
619 618 return remotefilectx.remotefilectx(self._repo, path,
620 619 fileid=fileid, changectx=self, filelog=filelog)
621 620 return orig(self, path, fileid=fileid, filelog=filelog)
622 621 extensions.wrapfunction(context.changectx, 'filectx', filectx)
623 622
624 623 def workingfilectx(orig, self, path, filelog=None):
625 624 if (isenabled(self._repo) and self._repo.shallowmatch(path)):
626 625 return remotefilectx.remoteworkingfilectx(self._repo,
627 626 path, workingctx=self, filelog=filelog)
628 627 return orig(self, path, filelog=filelog)
629 628 extensions.wrapfunction(context.workingctx, 'filectx', workingfilectx)
630 629
631 630 # prefetch required revisions before a diff
632 631 def trydiff(orig, repo, revs, ctx1, ctx2, modified, added, removed,
633 632 copy, getfilectx, *args, **kwargs):
634 633 if isenabled(repo):
635 634 prefetch = []
636 635 mf1 = ctx1.manifest()
637 636 for fname in modified + added + removed:
638 637 if fname in mf1:
639 638 fnode = getfilectx(fname, ctx1).filenode()
640 639 # fnode can be None if it's a edited working ctx file
641 640 if fnode:
642 641 prefetch.append((fname, hex(fnode)))
643 642 if fname not in removed:
644 643 fnode = getfilectx(fname, ctx2).filenode()
645 644 if fnode:
646 645 prefetch.append((fname, hex(fnode)))
647 646
648 647 repo.fileservice.prefetch(prefetch)
649 648
650 649 return orig(repo, revs, ctx1, ctx2, modified, added, removed,
651 650 copy, getfilectx, *args, **kwargs)
652 651 extensions.wrapfunction(patch, 'trydiff', trydiff)
653 652
654 653 # Prevent verify from processing files
655 654 # a stub for mercurial.hg.verify()
656 655 def _verify(orig, repo):
657 656 lock = repo.lock()
658 657 try:
659 658 return shallowverifier.shallowverifier(repo).verify()
660 659 finally:
661 660 lock.release()
662 661
663 662 extensions.wrapfunction(hg, 'verify', _verify)
664 663
665 664 scmutil.fileprefetchhooks.add('remotefilelog', _fileprefetchhook)
666 665
667 666 def getrenamedfn(repo, endrev=None):
668 667 rcache = {}
669 668
670 669 def getrenamed(fn, rev):
671 670 '''looks up all renames for a file (up to endrev) the first
672 671 time the file is given. It indexes on the changerev and only
673 672 parses the manifest if linkrev != changerev.
674 673 Returns rename info for fn at changerev rev.'''
675 674 if rev in rcache.setdefault(fn, {}):
676 675 return rcache[fn][rev]
677 676
678 677 try:
679 678 fctx = repo[rev].filectx(fn)
680 679 for ancestor in fctx.ancestors():
681 680 if ancestor.path() == fn:
682 681 renamed = ancestor.renamed()
683 682 rcache[fn][ancestor.rev()] = renamed and renamed[0]
684 683
685 684 renamed = fctx.renamed()
686 685 return renamed and renamed[0]
687 686 except error.LookupError:
688 687 return None
689 688
690 689 return getrenamed
691 690
692 691 def walkfilerevs(orig, repo, match, follow, revs, fncache):
693 692 if not isenabled(repo):
694 693 return orig(repo, match, follow, revs, fncache)
695 694
696 695 # remotefilelog's can't be walked in rev order, so throw.
697 696 # The caller will see the exception and walk the commit tree instead.
698 697 if not follow:
699 698 raise cmdutil.FileWalkError("Cannot walk via filelog")
700 699
701 700 wanted = set()
702 701 minrev, maxrev = min(revs), max(revs)
703 702
704 703 pctx = repo['.']
705 704 for filename in match.files():
706 705 if filename not in pctx:
707 706 raise error.Abort(_('cannot follow file not in parent '
708 707 'revision: "%s"') % filename)
709 708 fctx = pctx[filename]
710 709
711 710 linkrev = fctx.linkrev()
712 711 if linkrev >= minrev and linkrev <= maxrev:
713 712 fncache.setdefault(linkrev, []).append(filename)
714 713 wanted.add(linkrev)
715 714
716 715 for ancestor in fctx.ancestors():
717 716 linkrev = ancestor.linkrev()
718 717 if linkrev >= minrev and linkrev <= maxrev:
719 718 fncache.setdefault(linkrev, []).append(ancestor.path())
720 719 wanted.add(linkrev)
721 720
722 721 return wanted
723 722
724 723 def filelogrevset(orig, repo, subset, x):
725 724 """``filelog(pattern)``
726 725 Changesets connected to the specified filelog.
727 726
728 727 For performance reasons, ``filelog()`` does not show every changeset
729 728 that affects the requested file(s). See :hg:`help log` for details. For
730 729 a slower, more accurate result, use ``file()``.
731 730 """
732 731
733 732 if not isenabled(repo):
734 733 return orig(repo, subset, x)
735 734
736 735 # i18n: "filelog" is a keyword
737 736 pat = revset.getstring(x, _("filelog requires a pattern"))
738 737 m = match.match(repo.root, repo.getcwd(), [pat], default='relpath',
739 738 ctx=repo[None])
740 739 s = set()
741 740
742 741 if not match.patkind(pat):
743 742 # slow
744 743 for r in subset:
745 744 ctx = repo[r]
746 745 cfiles = ctx.files()
747 746 for f in m.files():
748 747 if f in cfiles:
749 748 s.add(ctx.rev())
750 749 break
751 750 else:
752 751 # partial
753 752 files = (f for f in repo[None] if m(f))
754 753 for f in files:
755 754 fctx = repo[None].filectx(f)
756 755 s.add(fctx.linkrev())
757 756 for actx in fctx.ancestors():
758 757 s.add(actx.linkrev())
759 758
760 759 return smartset.baseset([r for r in subset if r in s])
761 760
762 761 @command('gc', [], _('hg gc [REPO...]'), norepo=True)
763 762 def gc(ui, *args, **opts):
764 763 '''garbage collect the client and server filelog caches
765 764 '''
766 765 cachepaths = set()
767 766
768 767 # get the system client cache
769 768 systemcache = shallowutil.getcachepath(ui, allowempty=True)
770 769 if systemcache:
771 770 cachepaths.add(systemcache)
772 771
773 772 # get repo client and server cache
774 773 repopaths = []
775 774 pwd = ui.environ.get('PWD')
776 775 if pwd:
777 776 repopaths.append(pwd)
778 777
779 778 repopaths.extend(args)
780 779 repos = []
781 780 for repopath in repopaths:
782 781 try:
783 782 repo = hg.peer(ui, {}, repopath)
784 783 repos.append(repo)
785 784
786 785 repocache = shallowutil.getcachepath(repo.ui, allowempty=True)
787 786 if repocache:
788 787 cachepaths.add(repocache)
789 788 except error.RepoError:
790 789 pass
791 790
792 791 # gc client cache
793 792 for cachepath in cachepaths:
794 793 gcclient(ui, cachepath)
795 794
796 795 # gc server cache
797 796 for repo in repos:
798 797 remotefilelogserver.gcserver(ui, repo._repo)
799 798
800 799 def gcclient(ui, cachepath):
801 800 # get list of repos that use this cache
802 801 repospath = os.path.join(cachepath, 'repos')
803 802 if not os.path.exists(repospath):
804 803 ui.warn(_("no known cache at %s\n") % cachepath)
805 804 return
806 805
807 806 reposfile = open(repospath, 'rb')
808 807 repos = set([r[:-1] for r in reposfile.readlines()])
809 808 reposfile.close()
810 809
811 810 # build list of useful files
812 811 validrepos = []
813 812 keepkeys = set()
814 813
815 814 sharedcache = None
816 815 filesrepacked = False
817 816
818 817 count = 0
819 818 progress = ui.makeprogress(_("analyzing repositories"), unit="repos",
820 819 total=len(repos))
821 820 for path in repos:
822 821 progress.update(count)
823 822 count += 1
824 823 try:
825 824 path = ui.expandpath(os.path.normpath(path))
826 825 except TypeError as e:
827 826 ui.warn(_("warning: malformed path: %r:%s\n") % (path, e))
828 827 traceback.print_exc()
829 828 continue
830 829 try:
831 830 peer = hg.peer(ui, {}, path)
832 831 repo = peer._repo
833 832 except error.RepoError:
834 833 continue
835 834
836 835 validrepos.append(path)
837 836
838 837 # Protect against any repo or config changes that have happened since
839 838 # this repo was added to the repos file. We'd rather this loop succeed
840 839 # and too much be deleted, than the loop fail and nothing gets deleted.
841 840 if not isenabled(repo):
842 841 continue
843 842
844 843 if not util.safehasattr(repo, 'name'):
845 844 ui.warn(_("repo %s is a misconfigured remotefilelog repo\n") % path)
846 845 continue
847 846
848 847 # If garbage collection on repack and repack on hg gc are enabled
849 848 # then loose files are repacked and garbage collected.
850 849 # Otherwise regular garbage collection is performed.
851 850 repackonhggc = repo.ui.configbool('remotefilelog', 'repackonhggc')
852 851 gcrepack = repo.ui.configbool('remotefilelog', 'gcrepack')
853 852 if repackonhggc and gcrepack:
854 853 try:
855 854 repackmod.incrementalrepack(repo)
856 855 filesrepacked = True
857 856 continue
858 857 except (IOError, repackmod.RepackAlreadyRunning):
859 858 # If repack cannot be performed due to not enough disk space
860 859 # continue doing garbage collection of loose files w/o repack
861 860 pass
862 861
863 862 reponame = repo.name
864 863 if not sharedcache:
865 864 sharedcache = repo.sharedstore
866 865
867 866 # Compute a keepset which is not garbage collected
868 867 def keyfn(fname, fnode):
869 868 return fileserverclient.getcachekey(reponame, fname, hex(fnode))
870 869 keepkeys = repackmod.keepset(repo, keyfn=keyfn, lastkeepkeys=keepkeys)
871 870
872 871 progress.complete()
873 872
874 873 # write list of valid repos back
875 874 oldumask = os.umask(0o002)
876 875 try:
877 876 reposfile = open(repospath, 'wb')
878 877 reposfile.writelines([("%s\n" % r) for r in validrepos])
879 878 reposfile.close()
880 879 finally:
881 880 os.umask(oldumask)
882 881
883 882 # prune cache
884 883 if sharedcache is not None:
885 884 sharedcache.gc(keepkeys)
886 885 elif not filesrepacked:
887 886 ui.warn(_("warning: no valid repos in repofile\n"))
888 887
889 888 def log(orig, ui, repo, *pats, **opts):
890 889 if not isenabled(repo):
891 890 return orig(ui, repo, *pats, **opts)
892 891
893 892 follow = opts.get(r'follow')
894 893 revs = opts.get(r'rev')
895 894 if pats:
896 895 # Force slowpath for non-follow patterns and follows that start from
897 896 # non-working-copy-parent revs.
898 897 if not follow or revs:
899 898 # This forces the slowpath
900 899 opts[r'removed'] = True
901 900
902 901 # If this is a non-follow log without any revs specified, recommend that
903 902 # the user add -f to speed it up.
904 903 if not follow and not revs:
905 904 match = scmutil.match(repo['.'], pats, pycompat.byteskwargs(opts))
906 905 isfile = not match.anypats()
907 906 if isfile:
908 907 for file in match.files():
909 908 if not os.path.isfile(repo.wjoin(file)):
910 909 isfile = False
911 910 break
912 911
913 912 if isfile:
914 913 ui.warn(_("warning: file log can be slow on large repos - " +
915 914 "use -f to speed it up\n"))
916 915
917 916 return orig(ui, repo, *pats, **opts)
918 917
919 918 def revdatelimit(ui, revset):
920 919 """Update revset so that only changesets no older than 'prefetchdays' days
921 920 are included. The default value is set to 14 days. If 'prefetchdays' is set
922 921 to zero or negative value then date restriction is not applied.
923 922 """
924 923 days = ui.configint('remotefilelog', 'prefetchdays')
925 924 if days > 0:
926 925 revset = '(%s) & date(-%s)' % (revset, days)
927 926 return revset
928 927
929 928 def readytofetch(repo):
930 929 """Check that enough time has passed since the last background prefetch.
931 930 This only relates to prefetches after operations that change the working
932 931 copy parent. Default delay between background prefetches is 2 minutes.
933 932 """
934 933 timeout = repo.ui.configint('remotefilelog', 'prefetchdelay')
935 934 fname = repo.vfs.join('lastprefetch')
936 935
937 936 ready = False
938 937 with open(fname, 'a'):
939 938 # the with construct above is used to avoid race conditions
940 939 modtime = os.path.getmtime(fname)
941 940 if (time.time() - modtime) > timeout:
942 941 os.utime(fname, None)
943 942 ready = True
944 943
945 944 return ready
946 945
947 946 def wcpprefetch(ui, repo, **kwargs):
948 947 """Prefetches in background revisions specified by bgprefetchrevs revset.
949 948 Does background repack if backgroundrepack flag is set in config.
950 949 """
951 950 shallow = isenabled(repo)
952 951 bgprefetchrevs = ui.config('remotefilelog', 'bgprefetchrevs')
953 952 isready = readytofetch(repo)
954 953
955 954 if not (shallow and bgprefetchrevs and isready):
956 955 return
957 956
958 957 bgrepack = repo.ui.configbool('remotefilelog', 'backgroundrepack')
959 958 # update a revset with a date limit
960 959 bgprefetchrevs = revdatelimit(ui, bgprefetchrevs)
961 960
962 961 def anon():
963 962 if util.safehasattr(repo, 'ranprefetch') and repo.ranprefetch:
964 963 return
965 964 repo.ranprefetch = True
966 965 repo.backgroundprefetch(bgprefetchrevs, repack=bgrepack)
967 966
968 967 repo._afterlock(anon)
969 968
970 969 def pull(orig, ui, repo, *pats, **opts):
971 970 result = orig(ui, repo, *pats, **opts)
972 971
973 972 if isenabled(repo):
974 973 # prefetch if it's configured
975 974 prefetchrevset = ui.config('remotefilelog', 'pullprefetch')
976 975 bgrepack = repo.ui.configbool('remotefilelog', 'backgroundrepack')
977 976 bgprefetch = repo.ui.configbool('remotefilelog', 'backgroundprefetch')
978 977
979 978 if prefetchrevset:
980 979 ui.status(_("prefetching file contents\n"))
981 980 revs = scmutil.revrange(repo, [prefetchrevset])
982 981 base = repo['.'].rev()
983 982 if bgprefetch:
984 983 repo.backgroundprefetch(prefetchrevset, repack=bgrepack)
985 984 else:
986 985 repo.prefetch(revs, base=base)
987 986 if bgrepack:
988 987 repackmod.backgroundrepack(repo, incremental=True)
989 988 elif bgrepack:
990 989 repackmod.backgroundrepack(repo, incremental=True)
991 990
992 991 return result
993 992
994 993 def exchangepull(orig, repo, remote, *args, **kwargs):
995 994 # Hook into the callstream/getbundle to insert bundle capabilities
996 995 # during a pull.
997 996 def localgetbundle(orig, source, heads=None, common=None, bundlecaps=None,
998 997 **kwargs):
999 998 if not bundlecaps:
1000 999 bundlecaps = set()
1001 1000 bundlecaps.add(constants.BUNDLE2_CAPABLITY)
1002 1001 return orig(source, heads=heads, common=common, bundlecaps=bundlecaps,
1003 1002 **kwargs)
1004 1003
1005 1004 if util.safehasattr(remote, '_callstream'):
1006 1005 remote._localrepo = repo
1007 1006 elif util.safehasattr(remote, 'getbundle'):
1008 1007 extensions.wrapfunction(remote, 'getbundle', localgetbundle)
1009 1008
1010 1009 return orig(repo, remote, *args, **kwargs)
1011 1010
1012 1011 def _fileprefetchhook(repo, revs, match):
1013 1012 if isenabled(repo):
1014 1013 allfiles = []
1015 1014 for rev in revs:
1016 1015 if rev == nodemod.wdirrev or rev is None:
1017 1016 continue
1018 1017 ctx = repo[rev]
1019 1018 mf = ctx.manifest()
1020 1019 sparsematch = repo.maybesparsematch(ctx.rev())
1021 1020 for path in ctx.walk(match):
1022 1021 if path.endswith('/'):
1023 1022 # Tree manifest that's being excluded as part of narrow
1024 1023 continue
1025 1024 if (not sparsematch or sparsematch(path)) and path in mf:
1026 1025 allfiles.append((path, hex(mf[path])))
1027 1026 repo.fileservice.prefetch(allfiles)
1028 1027
1029 1028 @command('debugremotefilelog', [
1030 1029 ('d', 'decompress', None, _('decompress the filelog first')),
1031 1030 ], _('hg debugremotefilelog <path>'), norepo=True)
1032 1031 def debugremotefilelog(ui, path, **opts):
1033 1032 return debugcommands.debugremotefilelog(ui, path, **opts)
1034 1033
1035 1034 @command('verifyremotefilelog', [
1036 1035 ('d', 'decompress', None, _('decompress the filelogs first')),
1037 1036 ], _('hg verifyremotefilelogs <directory>'), norepo=True)
1038 1037 def verifyremotefilelog(ui, path, **opts):
1039 1038 return debugcommands.verifyremotefilelog(ui, path, **opts)
1040 1039
1041 1040 @command('debugdatapack', [
1042 1041 ('', 'long', None, _('print the long hashes')),
1043 1042 ('', 'node', '', _('dump the contents of node'), 'NODE'),
1044 1043 ], _('hg debugdatapack <paths>'), norepo=True)
1045 1044 def debugdatapack(ui, *paths, **opts):
1046 1045 return debugcommands.debugdatapack(ui, *paths, **opts)
1047 1046
1048 1047 @command('debughistorypack', [
1049 1048 ], _('hg debughistorypack <path>'), norepo=True)
1050 1049 def debughistorypack(ui, path, **opts):
1051 1050 return debugcommands.debughistorypack(ui, path)
1052 1051
1053 1052 @command('debugkeepset', [
1054 1053 ], _('hg debugkeepset'))
1055 1054 def debugkeepset(ui, repo, **opts):
1056 1055 # The command is used to measure keepset computation time
1057 1056 def keyfn(fname, fnode):
1058 1057 return fileserverclient.getcachekey(repo.name, fname, hex(fnode))
1059 1058 repackmod.keepset(repo, keyfn)
1060 1059 return
1061 1060
1062 1061 @command('debugwaitonrepack', [
1063 1062 ], _('hg debugwaitonrepack'))
1064 1063 def debugwaitonrepack(ui, repo, **opts):
1065 1064 return debugcommands.debugwaitonrepack(repo)
1066 1065
1067 1066 @command('debugwaitonprefetch', [
1068 1067 ], _('hg debugwaitonprefetch'))
1069 1068 def debugwaitonprefetch(ui, repo, **opts):
1070 1069 return debugcommands.debugwaitonprefetch(repo)
1071 1070
1072 1071 def resolveprefetchopts(ui, opts):
1073 1072 if not opts.get('rev'):
1074 1073 revset = ['.', 'draft()']
1075 1074
1076 1075 prefetchrevset = ui.config('remotefilelog', 'pullprefetch', None)
1077 1076 if prefetchrevset:
1078 1077 revset.append('(%s)' % prefetchrevset)
1079 1078 bgprefetchrevs = ui.config('remotefilelog', 'bgprefetchrevs', None)
1080 1079 if bgprefetchrevs:
1081 1080 revset.append('(%s)' % bgprefetchrevs)
1082 1081 revset = '+'.join(revset)
1083 1082
1084 1083 # update a revset with a date limit
1085 1084 revset = revdatelimit(ui, revset)
1086 1085
1087 1086 opts['rev'] = [revset]
1088 1087
1089 1088 if not opts.get('base'):
1090 1089 opts['base'] = None
1091 1090
1092 1091 return opts
1093 1092
1094 1093 @command('prefetch', [
1095 1094 ('r', 'rev', [], _('prefetch the specified revisions'), _('REV')),
1096 1095 ('', 'repack', False, _('run repack after prefetch')),
1097 1096 ('b', 'base', '', _("rev that is assumed to already be local")),
1098 1097 ] + commands.walkopts, _('hg prefetch [OPTIONS] [FILE...]'))
1099 1098 def prefetch(ui, repo, *pats, **opts):
1100 1099 """prefetch file revisions from the server
1101 1100
1102 1101 Prefetchs file revisions for the specified revs and stores them in the
1103 1102 local remotefilelog cache. If no rev is specified, the default rev is
1104 1103 used which is the union of dot, draft, pullprefetch and bgprefetchrev.
1105 1104 File names or patterns can be used to limit which files are downloaded.
1106 1105
1107 1106 Return 0 on success.
1108 1107 """
1109 1108 opts = pycompat.byteskwargs(opts)
1110 1109 if not isenabled(repo):
1111 1110 raise error.Abort(_("repo is not shallow"))
1112 1111
1113 1112 opts = resolveprefetchopts(ui, opts)
1114 1113 revs = scmutil.revrange(repo, opts.get('rev'))
1115 1114 repo.prefetch(revs, opts.get('base'), pats, opts)
1116 1115
1117 1116 # Run repack in background
1118 1117 if opts.get('repack'):
1119 1118 repackmod.backgroundrepack(repo, incremental=True)
1120 1119
1121 1120 @command('repack', [
1122 1121 ('', 'background', None, _('run in a background process'), None),
1123 1122 ('', 'incremental', None, _('do an incremental repack'), None),
1124 1123 ('', 'packsonly', None, _('only repack packs (skip loose objects)'), None),
1125 1124 ], _('hg repack [OPTIONS]'))
1126 1125 def repack_(ui, repo, *pats, **opts):
1127 1126 if opts.get(r'background'):
1128 1127 repackmod.backgroundrepack(repo, incremental=opts.get(r'incremental'),
1129 1128 packsonly=opts.get(r'packsonly', False))
1130 1129 return
1131 1130
1132 1131 options = {'packsonly': opts.get(r'packsonly')}
1133 1132
1134 1133 try:
1135 1134 if opts.get(r'incremental'):
1136 1135 repackmod.incrementalrepack(repo, options=options)
1137 1136 else:
1138 1137 repackmod.fullrepack(repo, options=options)
1139 1138 except repackmod.RepackAlreadyRunning as ex:
1140 1139 # Don't propogate the exception if the repack is already in
1141 1140 # progress, since we want the command to exit 0.
1142 1141 repo.ui.warn('%s\n' % ex)
@@ -1,6240 +1,6239 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __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 wdirhex,
23 23 wdirrev,
24 24 )
25 25 from . import (
26 26 archival,
27 27 bookmarks,
28 28 bundle2,
29 29 changegroup,
30 30 cmdutil,
31 31 copies,
32 32 debugcommands as debugcommandsmod,
33 33 destutil,
34 34 dirstateguard,
35 35 discovery,
36 36 encoding,
37 37 error,
38 38 exchange,
39 39 extensions,
40 40 filemerge,
41 41 formatter,
42 42 graphmod,
43 43 hbisect,
44 44 help,
45 45 hg,
46 46 logcmdutil,
47 47 merge as mergemod,
48 48 narrowspec,
49 49 obsolete,
50 50 obsutil,
51 51 patch,
52 52 phases,
53 53 pycompat,
54 54 rcutil,
55 55 registrar,
56 56 repair,
57 57 revsetlang,
58 58 rewriteutil,
59 59 scmutil,
60 60 server,
61 61 state as statemod,
62 62 streamclone,
63 63 tags as tagsmod,
64 templatekw,
65 64 ui as uimod,
66 65 util,
67 66 wireprotoserver,
68 67 )
69 68 from .utils import (
70 69 dateutil,
71 70 stringutil,
72 71 )
73 72
74 73 table = {}
75 74 table.update(debugcommandsmod.command._table)
76 75
77 76 command = registrar.command(table)
78 77 INTENT_READONLY = registrar.INTENT_READONLY
79 78
80 79 # common command options
81 80
82 81 globalopts = [
83 82 ('R', 'repository', '',
84 83 _('repository root directory or name of overlay bundle file'),
85 84 _('REPO')),
86 85 ('', 'cwd', '',
87 86 _('change working directory'), _('DIR')),
88 87 ('y', 'noninteractive', None,
89 88 _('do not prompt, automatically pick the first choice for all prompts')),
90 89 ('q', 'quiet', None, _('suppress output')),
91 90 ('v', 'verbose', None, _('enable additional output')),
92 91 ('', 'color', '',
93 92 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
94 93 # and should not be translated
95 94 _("when to colorize (boolean, always, auto, never, or debug)"),
96 95 _('TYPE')),
97 96 ('', 'config', [],
98 97 _('set/override config option (use \'section.name=value\')'),
99 98 _('CONFIG')),
100 99 ('', 'debug', None, _('enable debugging output')),
101 100 ('', 'debugger', None, _('start debugger')),
102 101 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
103 102 _('ENCODE')),
104 103 ('', 'encodingmode', encoding.encodingmode,
105 104 _('set the charset encoding mode'), _('MODE')),
106 105 ('', 'traceback', None, _('always print a traceback on exception')),
107 106 ('', 'time', None, _('time how long the command takes')),
108 107 ('', 'profile', None, _('print command execution profile')),
109 108 ('', 'version', None, _('output version information and exit')),
110 109 ('h', 'help', None, _('display help and exit')),
111 110 ('', 'hidden', False, _('consider hidden changesets')),
112 111 ('', 'pager', 'auto',
113 112 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
114 113 ]
115 114
116 115 dryrunopts = cmdutil.dryrunopts
117 116 remoteopts = cmdutil.remoteopts
118 117 walkopts = cmdutil.walkopts
119 118 commitopts = cmdutil.commitopts
120 119 commitopts2 = cmdutil.commitopts2
121 120 formatteropts = cmdutil.formatteropts
122 121 templateopts = cmdutil.templateopts
123 122 logopts = cmdutil.logopts
124 123 diffopts = cmdutil.diffopts
125 124 diffwsopts = cmdutil.diffwsopts
126 125 diffopts2 = cmdutil.diffopts2
127 126 mergetoolopts = cmdutil.mergetoolopts
128 127 similarityopts = cmdutil.similarityopts
129 128 subrepoopts = cmdutil.subrepoopts
130 129 debugrevlogopts = cmdutil.debugrevlogopts
131 130
132 131 # Commands start here, listed alphabetically
133 132
134 133 @command('add',
135 134 walkopts + subrepoopts + dryrunopts,
136 135 _('[OPTION]... [FILE]...'),
137 136 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
138 137 helpbasic=True, inferrepo=True)
139 138 def add(ui, repo, *pats, **opts):
140 139 """add the specified files on the next commit
141 140
142 141 Schedule files to be version controlled and added to the
143 142 repository.
144 143
145 144 The files will be added to the repository at the next commit. To
146 145 undo an add before that, see :hg:`forget`.
147 146
148 147 If no names are given, add all files to the repository (except
149 148 files matching ``.hgignore``).
150 149
151 150 .. container:: verbose
152 151
153 152 Examples:
154 153
155 154 - New (unknown) files are added
156 155 automatically by :hg:`add`::
157 156
158 157 $ ls
159 158 foo.c
160 159 $ hg status
161 160 ? foo.c
162 161 $ hg add
163 162 adding foo.c
164 163 $ hg status
165 164 A foo.c
166 165
167 166 - Specific files to be added can be specified::
168 167
169 168 $ ls
170 169 bar.c foo.c
171 170 $ hg status
172 171 ? bar.c
173 172 ? foo.c
174 173 $ hg add bar.c
175 174 $ hg status
176 175 A bar.c
177 176 ? foo.c
178 177
179 178 Returns 0 if all files are successfully added.
180 179 """
181 180
182 181 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
183 182 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
184 183 rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts)
185 184 return rejected and 1 or 0
186 185
187 186 @command('addremove',
188 187 similarityopts + subrepoopts + walkopts + dryrunopts,
189 188 _('[OPTION]... [FILE]...'),
190 189 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
191 190 inferrepo=True)
192 191 def addremove(ui, repo, *pats, **opts):
193 192 """add all new files, delete all missing files
194 193
195 194 Add all new files and remove all missing files from the
196 195 repository.
197 196
198 197 Unless names are given, new files are ignored if they match any of
199 198 the patterns in ``.hgignore``. As with add, these changes take
200 199 effect at the next commit.
201 200
202 201 Use the -s/--similarity option to detect renamed files. This
203 202 option takes a percentage between 0 (disabled) and 100 (files must
204 203 be identical) as its parameter. With a parameter greater than 0,
205 204 this compares every removed file with every added file and records
206 205 those similar enough as renames. Detecting renamed files this way
207 206 can be expensive. After using this option, :hg:`status -C` can be
208 207 used to check which files were identified as moved or renamed. If
209 208 not specified, -s/--similarity defaults to 100 and only renames of
210 209 identical files are detected.
211 210
212 211 .. container:: verbose
213 212
214 213 Examples:
215 214
216 215 - A number of files (bar.c and foo.c) are new,
217 216 while foobar.c has been removed (without using :hg:`remove`)
218 217 from the repository::
219 218
220 219 $ ls
221 220 bar.c foo.c
222 221 $ hg status
223 222 ! foobar.c
224 223 ? bar.c
225 224 ? foo.c
226 225 $ hg addremove
227 226 adding bar.c
228 227 adding foo.c
229 228 removing foobar.c
230 229 $ hg status
231 230 A bar.c
232 231 A foo.c
233 232 R foobar.c
234 233
235 234 - A file foobar.c was moved to foo.c without using :hg:`rename`.
236 235 Afterwards, it was edited slightly::
237 236
238 237 $ ls
239 238 foo.c
240 239 $ hg status
241 240 ! foobar.c
242 241 ? foo.c
243 242 $ hg addremove --similarity 90
244 243 removing foobar.c
245 244 adding foo.c
246 245 recording removal of foobar.c as rename to foo.c (94% similar)
247 246 $ hg status -C
248 247 A foo.c
249 248 foobar.c
250 249 R foobar.c
251 250
252 251 Returns 0 if all files are successfully added.
253 252 """
254 253 opts = pycompat.byteskwargs(opts)
255 254 if not opts.get('similarity'):
256 255 opts['similarity'] = '100'
257 256 matcher = scmutil.match(repo[None], pats, opts)
258 257 relative = scmutil.anypats(pats, opts)
259 258 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
260 259 return scmutil.addremove(repo, matcher, "", uipathfn, opts)
261 260
262 261 @command('annotate|blame',
263 262 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
264 263 ('', 'follow', None,
265 264 _('follow copies/renames and list the filename (DEPRECATED)')),
266 265 ('', 'no-follow', None, _("don't follow copies and renames")),
267 266 ('a', 'text', None, _('treat all files as text')),
268 267 ('u', 'user', None, _('list the author (long with -v)')),
269 268 ('f', 'file', None, _('list the filename')),
270 269 ('d', 'date', None, _('list the date (short with -q)')),
271 270 ('n', 'number', None, _('list the revision number (default)')),
272 271 ('c', 'changeset', None, _('list the changeset')),
273 272 ('l', 'line-number', None, _('show line number at the first appearance')),
274 273 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
275 274 ] + diffwsopts + walkopts + formatteropts,
276 275 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
277 276 helpcategory=command.CATEGORY_FILE_CONTENTS,
278 277 helpbasic=True, inferrepo=True)
279 278 def annotate(ui, repo, *pats, **opts):
280 279 """show changeset information by line for each file
281 280
282 281 List changes in files, showing the revision id responsible for
283 282 each line.
284 283
285 284 This command is useful for discovering when a change was made and
286 285 by whom.
287 286
288 287 If you include --file, --user, or --date, the revision number is
289 288 suppressed unless you also include --number.
290 289
291 290 Without the -a/--text option, annotate will avoid processing files
292 291 it detects as binary. With -a, annotate will annotate the file
293 292 anyway, although the results will probably be neither useful
294 293 nor desirable.
295 294
296 295 .. container:: verbose
297 296
298 297 Template:
299 298
300 299 The following keywords are supported in addition to the common template
301 300 keywords and functions. See also :hg:`help templates`.
302 301
303 302 :lines: List of lines with annotation data.
304 303 :path: String. Repository-absolute path of the specified file.
305 304
306 305 And each entry of ``{lines}`` provides the following sub-keywords in
307 306 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
308 307
309 308 :line: String. Line content.
310 309 :lineno: Integer. Line number at that revision.
311 310 :path: String. Repository-absolute path of the file at that revision.
312 311
313 312 See :hg:`help templates.operators` for the list expansion syntax.
314 313
315 314 Returns 0 on success.
316 315 """
317 316 opts = pycompat.byteskwargs(opts)
318 317 if not pats:
319 318 raise error.Abort(_('at least one filename or pattern is required'))
320 319
321 320 if opts.get('follow'):
322 321 # --follow is deprecated and now just an alias for -f/--file
323 322 # to mimic the behavior of Mercurial before version 1.5
324 323 opts['file'] = True
325 324
326 325 if (not opts.get('user') and not opts.get('changeset')
327 326 and not opts.get('date') and not opts.get('file')):
328 327 opts['number'] = True
329 328
330 329 linenumber = opts.get('line_number') is not None
331 330 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
332 331 raise error.Abort(_('at least one of -n/-c is required for -l'))
333 332
334 333 rev = opts.get('rev')
335 334 if rev:
336 335 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
337 336 ctx = scmutil.revsingle(repo, rev)
338 337
339 338 ui.pager('annotate')
340 339 rootfm = ui.formatter('annotate', opts)
341 340 if ui.debugflag:
342 341 shorthex = pycompat.identity
343 342 else:
344 343 def shorthex(h):
345 344 return h[:12]
346 345 if ui.quiet:
347 346 datefunc = dateutil.shortdate
348 347 else:
349 348 datefunc = dateutil.datestr
350 349 if ctx.rev() is None:
351 350 if opts.get('changeset'):
352 351 # omit "+" suffix which is appended to node hex
353 352 def formatrev(rev):
354 353 if rev == wdirrev:
355 354 return '%d' % ctx.p1().rev()
356 355 else:
357 356 return '%d' % rev
358 357 else:
359 358 def formatrev(rev):
360 359 if rev == wdirrev:
361 360 return '%d+' % ctx.p1().rev()
362 361 else:
363 362 return '%d ' % rev
364 363 def formathex(h):
365 364 if h == wdirhex:
366 365 return '%s+' % shorthex(hex(ctx.p1().node()))
367 366 else:
368 367 return '%s ' % shorthex(h)
369 368 else:
370 369 formatrev = b'%d'.__mod__
371 370 formathex = shorthex
372 371
373 372 opmap = [
374 373 ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
375 374 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
376 375 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
377 376 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
378 377 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
379 378 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
380 379 ]
381 380 opnamemap = {
382 381 'rev': 'number',
383 382 'node': 'changeset',
384 383 'path': 'file',
385 384 'lineno': 'line_number',
386 385 }
387 386
388 387 if rootfm.isplain():
389 388 def makefunc(get, fmt):
390 389 return lambda x: fmt(get(x))
391 390 else:
392 391 def makefunc(get, fmt):
393 392 return get
394 393 datahint = rootfm.datahint()
395 394 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
396 395 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
397 396 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
398 397 fields = ' '.join(fn for fn, sep, get, fmt in opmap
399 398 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
400 399
401 400 def bad(x, y):
402 401 raise error.Abort("%s: %s" % (x, y))
403 402
404 403 m = scmutil.match(ctx, pats, opts, badfn=bad)
405 404
406 405 follow = not opts.get('no_follow')
407 406 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
408 407 whitespace=True)
409 408 skiprevs = opts.get('skip')
410 409 if skiprevs:
411 410 skiprevs = scmutil.revrange(repo, skiprevs)
412 411
413 412 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
414 413 for abs in ctx.walk(m):
415 414 fctx = ctx[abs]
416 415 rootfm.startitem()
417 416 rootfm.data(path=abs)
418 417 if not opts.get('text') and fctx.isbinary():
419 418 rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
420 419 continue
421 420
422 421 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
423 422 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
424 423 diffopts=diffopts)
425 424 if not lines:
426 425 fm.end()
427 426 continue
428 427 formats = []
429 428 pieces = []
430 429
431 430 for f, sep in funcmap:
432 431 l = [f(n) for n in lines]
433 432 if fm.isplain():
434 433 sizes = [encoding.colwidth(x) for x in l]
435 434 ml = max(sizes)
436 435 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
437 436 else:
438 437 formats.append(['%s' for x in l])
439 438 pieces.append(l)
440 439
441 440 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
442 441 fm.startitem()
443 442 fm.context(fctx=n.fctx)
444 443 fm.write(fields, "".join(f), *p)
445 444 if n.skip:
446 445 fmt = "* %s"
447 446 else:
448 447 fmt = ": %s"
449 448 fm.write('line', fmt, n.text)
450 449
451 450 if not lines[-1].text.endswith('\n'):
452 451 fm.plain('\n')
453 452 fm.end()
454 453
455 454 rootfm.end()
456 455
457 456 @command('archive',
458 457 [('', 'no-decode', None, _('do not pass files through decoders')),
459 458 ('p', 'prefix', '', _('directory prefix for files in archive'),
460 459 _('PREFIX')),
461 460 ('r', 'rev', '', _('revision to distribute'), _('REV')),
462 461 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
463 462 ] + subrepoopts + walkopts,
464 463 _('[OPTION]... DEST'),
465 464 helpcategory=command.CATEGORY_IMPORT_EXPORT)
466 465 def archive(ui, repo, dest, **opts):
467 466 '''create an unversioned archive of a repository revision
468 467
469 468 By default, the revision used is the parent of the working
470 469 directory; use -r/--rev to specify a different revision.
471 470
472 471 The archive type is automatically detected based on file
473 472 extension (to override, use -t/--type).
474 473
475 474 .. container:: verbose
476 475
477 476 Examples:
478 477
479 478 - create a zip file containing the 1.0 release::
480 479
481 480 hg archive -r 1.0 project-1.0.zip
482 481
483 482 - create a tarball excluding .hg files::
484 483
485 484 hg archive project.tar.gz -X ".hg*"
486 485
487 486 Valid types are:
488 487
489 488 :``files``: a directory full of files (default)
490 489 :``tar``: tar archive, uncompressed
491 490 :``tbz2``: tar archive, compressed using bzip2
492 491 :``tgz``: tar archive, compressed using gzip
493 492 :``uzip``: zip archive, uncompressed
494 493 :``zip``: zip archive, compressed using deflate
495 494
496 495 The exact name of the destination archive or directory is given
497 496 using a format string; see :hg:`help export` for details.
498 497
499 498 Each member added to an archive file has a directory prefix
500 499 prepended. Use -p/--prefix to specify a format string for the
501 500 prefix. The default is the basename of the archive, with suffixes
502 501 removed.
503 502
504 503 Returns 0 on success.
505 504 '''
506 505
507 506 opts = pycompat.byteskwargs(opts)
508 507 rev = opts.get('rev')
509 508 if rev:
510 509 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
511 510 ctx = scmutil.revsingle(repo, rev)
512 511 if not ctx:
513 512 raise error.Abort(_('no working directory: please specify a revision'))
514 513 node = ctx.node()
515 514 dest = cmdutil.makefilename(ctx, dest)
516 515 if os.path.realpath(dest) == repo.root:
517 516 raise error.Abort(_('repository root cannot be destination'))
518 517
519 518 kind = opts.get('type') or archival.guesskind(dest) or 'files'
520 519 prefix = opts.get('prefix')
521 520
522 521 if dest == '-':
523 522 if kind == 'files':
524 523 raise error.Abort(_('cannot archive plain files to stdout'))
525 524 dest = cmdutil.makefileobj(ctx, dest)
526 525 if not prefix:
527 526 prefix = os.path.basename(repo.root) + '-%h'
528 527
529 528 prefix = cmdutil.makefilename(ctx, prefix)
530 529 match = scmutil.match(ctx, [], opts)
531 530 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
532 531 match, prefix, subrepos=opts.get('subrepos'))
533 532
534 533 @command('backout',
535 534 [('', 'merge', None, _('merge with old dirstate parent after backout')),
536 535 ('', 'commit', None,
537 536 _('commit if no conflicts were encountered (DEPRECATED)')),
538 537 ('', 'no-commit', None, _('do not commit')),
539 538 ('', 'parent', '',
540 539 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
541 540 ('r', 'rev', '', _('revision to backout'), _('REV')),
542 541 ('e', 'edit', False, _('invoke editor on commit messages')),
543 542 ] + mergetoolopts + walkopts + commitopts + commitopts2,
544 543 _('[OPTION]... [-r] REV'),
545 544 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
546 545 def backout(ui, repo, node=None, rev=None, **opts):
547 546 '''reverse effect of earlier changeset
548 547
549 548 Prepare a new changeset with the effect of REV undone in the
550 549 current working directory. If no conflicts were encountered,
551 550 it will be committed immediately.
552 551
553 552 If REV is the parent of the working directory, then this new changeset
554 553 is committed automatically (unless --no-commit is specified).
555 554
556 555 .. note::
557 556
558 557 :hg:`backout` cannot be used to fix either an unwanted or
559 558 incorrect merge.
560 559
561 560 .. container:: verbose
562 561
563 562 Examples:
564 563
565 564 - Reverse the effect of the parent of the working directory.
566 565 This backout will be committed immediately::
567 566
568 567 hg backout -r .
569 568
570 569 - Reverse the effect of previous bad revision 23::
571 570
572 571 hg backout -r 23
573 572
574 573 - Reverse the effect of previous bad revision 23 and
575 574 leave changes uncommitted::
576 575
577 576 hg backout -r 23 --no-commit
578 577 hg commit -m "Backout revision 23"
579 578
580 579 By default, the pending changeset will have one parent,
581 580 maintaining a linear history. With --merge, the pending
582 581 changeset will instead have two parents: the old parent of the
583 582 working directory and a new child of REV that simply undoes REV.
584 583
585 584 Before version 1.7, the behavior without --merge was equivalent
586 585 to specifying --merge followed by :hg:`update --clean .` to
587 586 cancel the merge and leave the child of REV as a head to be
588 587 merged separately.
589 588
590 589 See :hg:`help dates` for a list of formats valid for -d/--date.
591 590
592 591 See :hg:`help revert` for a way to restore files to the state
593 592 of another revision.
594 593
595 594 Returns 0 on success, 1 if nothing to backout or there are unresolved
596 595 files.
597 596 '''
598 597 with repo.wlock(), repo.lock():
599 598 return _dobackout(ui, repo, node, rev, **opts)
600 599
601 600 def _dobackout(ui, repo, node=None, rev=None, **opts):
602 601 opts = pycompat.byteskwargs(opts)
603 602 if opts.get('commit') and opts.get('no_commit'):
604 603 raise error.Abort(_("cannot use --commit with --no-commit"))
605 604 if opts.get('merge') and opts.get('no_commit'):
606 605 raise error.Abort(_("cannot use --merge with --no-commit"))
607 606
608 607 if rev and node:
609 608 raise error.Abort(_("please specify just one revision"))
610 609
611 610 if not rev:
612 611 rev = node
613 612
614 613 if not rev:
615 614 raise error.Abort(_("please specify a revision to backout"))
616 615
617 616 date = opts.get('date')
618 617 if date:
619 618 opts['date'] = dateutil.parsedate(date)
620 619
621 620 cmdutil.checkunfinished(repo)
622 621 cmdutil.bailifchanged(repo)
623 622 node = scmutil.revsingle(repo, rev).node()
624 623
625 624 op1, op2 = repo.dirstate.parents()
626 625 if not repo.changelog.isancestor(node, op1):
627 626 raise error.Abort(_('cannot backout change that is not an ancestor'))
628 627
629 628 p1, p2 = repo.changelog.parents(node)
630 629 if p1 == nullid:
631 630 raise error.Abort(_('cannot backout a change with no parents'))
632 631 if p2 != nullid:
633 632 if not opts.get('parent'):
634 633 raise error.Abort(_('cannot backout a merge changeset'))
635 634 p = repo.lookup(opts['parent'])
636 635 if p not in (p1, p2):
637 636 raise error.Abort(_('%s is not a parent of %s') %
638 637 (short(p), short(node)))
639 638 parent = p
640 639 else:
641 640 if opts.get('parent'):
642 641 raise error.Abort(_('cannot use --parent on non-merge changeset'))
643 642 parent = p1
644 643
645 644 # the backout should appear on the same branch
646 645 branch = repo.dirstate.branch()
647 646 bheads = repo.branchheads(branch)
648 647 rctx = scmutil.revsingle(repo, hex(parent))
649 648 if not opts.get('merge') and op1 != node:
650 649 with dirstateguard.dirstateguard(repo, 'backout'):
651 650 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
652 651 with ui.configoverride(overrides, 'backout'):
653 652 stats = mergemod.update(repo, parent, branchmerge=True,
654 653 force=True, ancestor=node,
655 654 mergeancestor=False)
656 655 repo.setparents(op1, op2)
657 656 hg._showstats(repo, stats)
658 657 if stats.unresolvedcount:
659 658 repo.ui.status(_("use 'hg resolve' to retry unresolved "
660 659 "file merges\n"))
661 660 return 1
662 661 else:
663 662 hg.clean(repo, node, show_stats=False)
664 663 repo.dirstate.setbranch(branch)
665 664 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
666 665
667 666 if opts.get('no_commit'):
668 667 msg = _("changeset %s backed out, "
669 668 "don't forget to commit.\n")
670 669 ui.status(msg % short(node))
671 670 return 0
672 671
673 672 def commitfunc(ui, repo, message, match, opts):
674 673 editform = 'backout'
675 674 e = cmdutil.getcommiteditor(editform=editform,
676 675 **pycompat.strkwargs(opts))
677 676 if not message:
678 677 # we don't translate commit messages
679 678 message = "Backed out changeset %s" % short(node)
680 679 e = cmdutil.getcommiteditor(edit=True, editform=editform)
681 680 return repo.commit(message, opts.get('user'), opts.get('date'),
682 681 match, editor=e)
683 682 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
684 683 if not newnode:
685 684 ui.status(_("nothing changed\n"))
686 685 return 1
687 686 cmdutil.commitstatus(repo, newnode, branch, bheads)
688 687
689 688 def nice(node):
690 689 return '%d:%s' % (repo.changelog.rev(node), short(node))
691 690 ui.status(_('changeset %s backs out changeset %s\n') %
692 691 (nice(repo.changelog.tip()), nice(node)))
693 692 if opts.get('merge') and op1 != node:
694 693 hg.clean(repo, op1, show_stats=False)
695 694 ui.status(_('merging with changeset %s\n')
696 695 % nice(repo.changelog.tip()))
697 696 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
698 697 with ui.configoverride(overrides, 'backout'):
699 698 return hg.merge(repo, hex(repo.changelog.tip()))
700 699 return 0
701 700
702 701 @command('bisect',
703 702 [('r', 'reset', False, _('reset bisect state')),
704 703 ('g', 'good', False, _('mark changeset good')),
705 704 ('b', 'bad', False, _('mark changeset bad')),
706 705 ('s', 'skip', False, _('skip testing changeset')),
707 706 ('e', 'extend', False, _('extend the bisect range')),
708 707 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
709 708 ('U', 'noupdate', False, _('do not update to target'))],
710 709 _("[-gbsr] [-U] [-c CMD] [REV]"),
711 710 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
712 711 def bisect(ui, repo, rev=None, extra=None, command=None,
713 712 reset=None, good=None, bad=None, skip=None, extend=None,
714 713 noupdate=None):
715 714 """subdivision search of changesets
716 715
717 716 This command helps to find changesets which introduce problems. To
718 717 use, mark the earliest changeset you know exhibits the problem as
719 718 bad, then mark the latest changeset which is free from the problem
720 719 as good. Bisect will update your working directory to a revision
721 720 for testing (unless the -U/--noupdate option is specified). Once
722 721 you have performed tests, mark the working directory as good or
723 722 bad, and bisect will either update to another candidate changeset
724 723 or announce that it has found the bad revision.
725 724
726 725 As a shortcut, you can also use the revision argument to mark a
727 726 revision as good or bad without checking it out first.
728 727
729 728 If you supply a command, it will be used for automatic bisection.
730 729 The environment variable HG_NODE will contain the ID of the
731 730 changeset being tested. The exit status of the command will be
732 731 used to mark revisions as good or bad: status 0 means good, 125
733 732 means to skip the revision, 127 (command not found) will abort the
734 733 bisection, and any other non-zero exit status means the revision
735 734 is bad.
736 735
737 736 .. container:: verbose
738 737
739 738 Some examples:
740 739
741 740 - start a bisection with known bad revision 34, and good revision 12::
742 741
743 742 hg bisect --bad 34
744 743 hg bisect --good 12
745 744
746 745 - advance the current bisection by marking current revision as good or
747 746 bad::
748 747
749 748 hg bisect --good
750 749 hg bisect --bad
751 750
752 751 - mark the current revision, or a known revision, to be skipped (e.g. if
753 752 that revision is not usable because of another issue)::
754 753
755 754 hg bisect --skip
756 755 hg bisect --skip 23
757 756
758 757 - skip all revisions that do not touch directories ``foo`` or ``bar``::
759 758
760 759 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
761 760
762 761 - forget the current bisection::
763 762
764 763 hg bisect --reset
765 764
766 765 - use 'make && make tests' to automatically find the first broken
767 766 revision::
768 767
769 768 hg bisect --reset
770 769 hg bisect --bad 34
771 770 hg bisect --good 12
772 771 hg bisect --command "make && make tests"
773 772
774 773 - see all changesets whose states are already known in the current
775 774 bisection::
776 775
777 776 hg log -r "bisect(pruned)"
778 777
779 778 - see the changeset currently being bisected (especially useful
780 779 if running with -U/--noupdate)::
781 780
782 781 hg log -r "bisect(current)"
783 782
784 783 - see all changesets that took part in the current bisection::
785 784
786 785 hg log -r "bisect(range)"
787 786
788 787 - you can even get a nice graph::
789 788
790 789 hg log --graph -r "bisect(range)"
791 790
792 791 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
793 792
794 793 Returns 0 on success.
795 794 """
796 795 # backward compatibility
797 796 if rev in "good bad reset init".split():
798 797 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
799 798 cmd, rev, extra = rev, extra, None
800 799 if cmd == "good":
801 800 good = True
802 801 elif cmd == "bad":
803 802 bad = True
804 803 else:
805 804 reset = True
806 805 elif extra:
807 806 raise error.Abort(_('incompatible arguments'))
808 807
809 808 incompatibles = {
810 809 '--bad': bad,
811 810 '--command': bool(command),
812 811 '--extend': extend,
813 812 '--good': good,
814 813 '--reset': reset,
815 814 '--skip': skip,
816 815 }
817 816
818 817 enabled = [x for x in incompatibles if incompatibles[x]]
819 818
820 819 if len(enabled) > 1:
821 820 raise error.Abort(_('%s and %s are incompatible') %
822 821 tuple(sorted(enabled)[0:2]))
823 822
824 823 if reset:
825 824 hbisect.resetstate(repo)
826 825 return
827 826
828 827 state = hbisect.load_state(repo)
829 828
830 829 # update state
831 830 if good or bad or skip:
832 831 if rev:
833 832 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
834 833 else:
835 834 nodes = [repo.lookup('.')]
836 835 if good:
837 836 state['good'] += nodes
838 837 elif bad:
839 838 state['bad'] += nodes
840 839 elif skip:
841 840 state['skip'] += nodes
842 841 hbisect.save_state(repo, state)
843 842 if not (state['good'] and state['bad']):
844 843 return
845 844
846 845 def mayupdate(repo, node, show_stats=True):
847 846 """common used update sequence"""
848 847 if noupdate:
849 848 return
850 849 cmdutil.checkunfinished(repo)
851 850 cmdutil.bailifchanged(repo)
852 851 return hg.clean(repo, node, show_stats=show_stats)
853 852
854 853 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
855 854
856 855 if command:
857 856 changesets = 1
858 857 if noupdate:
859 858 try:
860 859 node = state['current'][0]
861 860 except LookupError:
862 861 raise error.Abort(_('current bisect revision is unknown - '
863 862 'start a new bisect to fix'))
864 863 else:
865 864 node, p2 = repo.dirstate.parents()
866 865 if p2 != nullid:
867 866 raise error.Abort(_('current bisect revision is a merge'))
868 867 if rev:
869 868 node = repo[scmutil.revsingle(repo, rev, node)].node()
870 869 try:
871 870 while changesets:
872 871 # update state
873 872 state['current'] = [node]
874 873 hbisect.save_state(repo, state)
875 874 status = ui.system(command, environ={'HG_NODE': hex(node)},
876 875 blockedtag='bisect_check')
877 876 if status == 125:
878 877 transition = "skip"
879 878 elif status == 0:
880 879 transition = "good"
881 880 # status < 0 means process was killed
882 881 elif status == 127:
883 882 raise error.Abort(_("failed to execute %s") % command)
884 883 elif status < 0:
885 884 raise error.Abort(_("%s killed") % command)
886 885 else:
887 886 transition = "bad"
888 887 state[transition].append(node)
889 888 ctx = repo[node]
890 889 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
891 890 transition))
892 891 hbisect.checkstate(state)
893 892 # bisect
894 893 nodes, changesets, bgood = hbisect.bisect(repo, state)
895 894 # update to next check
896 895 node = nodes[0]
897 896 mayupdate(repo, node, show_stats=False)
898 897 finally:
899 898 state['current'] = [node]
900 899 hbisect.save_state(repo, state)
901 900 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
902 901 return
903 902
904 903 hbisect.checkstate(state)
905 904
906 905 # actually bisect
907 906 nodes, changesets, good = hbisect.bisect(repo, state)
908 907 if extend:
909 908 if not changesets:
910 909 extendnode = hbisect.extendrange(repo, state, nodes, good)
911 910 if extendnode is not None:
912 911 ui.write(_("Extending search to changeset %d:%s\n")
913 912 % (extendnode.rev(), extendnode))
914 913 state['current'] = [extendnode.node()]
915 914 hbisect.save_state(repo, state)
916 915 return mayupdate(repo, extendnode.node())
917 916 raise error.Abort(_("nothing to extend"))
918 917
919 918 if changesets == 0:
920 919 hbisect.printresult(ui, repo, state, displayer, nodes, good)
921 920 else:
922 921 assert len(nodes) == 1 # only a single node can be tested next
923 922 node = nodes[0]
924 923 # compute the approximate number of remaining tests
925 924 tests, size = 0, 2
926 925 while size <= changesets:
927 926 tests, size = tests + 1, size * 2
928 927 rev = repo.changelog.rev(node)
929 928 ui.write(_("Testing changeset %d:%s "
930 929 "(%d changesets remaining, ~%d tests)\n")
931 930 % (rev, short(node), changesets, tests))
932 931 state['current'] = [node]
933 932 hbisect.save_state(repo, state)
934 933 return mayupdate(repo, node)
935 934
936 935 @command('bookmarks|bookmark',
937 936 [('f', 'force', False, _('force')),
938 937 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
939 938 ('d', 'delete', False, _('delete a given bookmark')),
940 939 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
941 940 ('i', 'inactive', False, _('mark a bookmark inactive')),
942 941 ('l', 'list', False, _('list existing bookmarks')),
943 942 ] + formatteropts,
944 943 _('hg bookmarks [OPTIONS]... [NAME]...'),
945 944 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
946 945 def bookmark(ui, repo, *names, **opts):
947 946 '''create a new bookmark or list existing bookmarks
948 947
949 948 Bookmarks are labels on changesets to help track lines of development.
950 949 Bookmarks are unversioned and can be moved, renamed and deleted.
951 950 Deleting or moving a bookmark has no effect on the associated changesets.
952 951
953 952 Creating or updating to a bookmark causes it to be marked as 'active'.
954 953 The active bookmark is indicated with a '*'.
955 954 When a commit is made, the active bookmark will advance to the new commit.
956 955 A plain :hg:`update` will also advance an active bookmark, if possible.
957 956 Updating away from a bookmark will cause it to be deactivated.
958 957
959 958 Bookmarks can be pushed and pulled between repositories (see
960 959 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
961 960 diverged, a new 'divergent bookmark' of the form 'name@path' will
962 961 be created. Using :hg:`merge` will resolve the divergence.
963 962
964 963 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
965 964 the active bookmark's name.
966 965
967 966 A bookmark named '@' has the special property that :hg:`clone` will
968 967 check it out by default if it exists.
969 968
970 969 .. container:: verbose
971 970
972 971 Template:
973 972
974 973 The following keywords are supported in addition to the common template
975 974 keywords and functions such as ``{bookmark}``. See also
976 975 :hg:`help templates`.
977 976
978 977 :active: Boolean. True if the bookmark is active.
979 978
980 979 Examples:
981 980
982 981 - create an active bookmark for a new line of development::
983 982
984 983 hg book new-feature
985 984
986 985 - create an inactive bookmark as a place marker::
987 986
988 987 hg book -i reviewed
989 988
990 989 - create an inactive bookmark on another changeset::
991 990
992 991 hg book -r .^ tested
993 992
994 993 - rename bookmark turkey to dinner::
995 994
996 995 hg book -m turkey dinner
997 996
998 997 - move the '@' bookmark from another branch::
999 998
1000 999 hg book -f @
1001 1000
1002 1001 - print only the active bookmark name::
1003 1002
1004 1003 hg book -ql .
1005 1004 '''
1006 1005 opts = pycompat.byteskwargs(opts)
1007 1006 force = opts.get('force')
1008 1007 rev = opts.get('rev')
1009 1008 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1010 1009
1011 1010 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1012 1011 if len(selactions) > 1:
1013 1012 raise error.Abort(_('--%s and --%s are incompatible')
1014 1013 % tuple(selactions[:2]))
1015 1014 if selactions:
1016 1015 action = selactions[0]
1017 1016 elif names or rev:
1018 1017 action = 'add'
1019 1018 elif inactive:
1020 1019 action = 'inactive' # meaning deactivate
1021 1020 else:
1022 1021 action = 'list'
1023 1022
1024 1023 if rev and action in {'delete', 'rename', 'list'}:
1025 1024 raise error.Abort(_("--rev is incompatible with --%s") % action)
1026 1025 if inactive and action in {'delete', 'list'}:
1027 1026 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1028 1027 if not names and action in {'add', 'delete'}:
1029 1028 raise error.Abort(_("bookmark name required"))
1030 1029
1031 1030 if action in {'add', 'delete', 'rename', 'inactive'}:
1032 1031 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1033 1032 if action == 'delete':
1034 1033 names = pycompat.maplist(repo._bookmarks.expandname, names)
1035 1034 bookmarks.delete(repo, tr, names)
1036 1035 elif action == 'rename':
1037 1036 if not names:
1038 1037 raise error.Abort(_("new bookmark name required"))
1039 1038 elif len(names) > 1:
1040 1039 raise error.Abort(_("only one new bookmark name allowed"))
1041 1040 oldname = repo._bookmarks.expandname(opts['rename'])
1042 1041 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1043 1042 elif action == 'add':
1044 1043 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1045 1044 elif action == 'inactive':
1046 1045 if len(repo._bookmarks) == 0:
1047 1046 ui.status(_("no bookmarks set\n"))
1048 1047 elif not repo._activebookmark:
1049 1048 ui.status(_("no active bookmark\n"))
1050 1049 else:
1051 1050 bookmarks.deactivate(repo)
1052 1051 elif action == 'list':
1053 1052 names = pycompat.maplist(repo._bookmarks.expandname, names)
1054 1053 with ui.formatter('bookmarks', opts) as fm:
1055 1054 bookmarks.printbookmarks(ui, repo, fm, names)
1056 1055 else:
1057 1056 raise error.ProgrammingError('invalid action: %s' % action)
1058 1057
1059 1058 @command('branch',
1060 1059 [('f', 'force', None,
1061 1060 _('set branch name even if it shadows an existing branch')),
1062 1061 ('C', 'clean', None, _('reset branch name to parent branch name')),
1063 1062 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1064 1063 ],
1065 1064 _('[-fC] [NAME]'),
1066 1065 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1067 1066 def branch(ui, repo, label=None, **opts):
1068 1067 """set or show the current branch name
1069 1068
1070 1069 .. note::
1071 1070
1072 1071 Branch names are permanent and global. Use :hg:`bookmark` to create a
1073 1072 light-weight bookmark instead. See :hg:`help glossary` for more
1074 1073 information about named branches and bookmarks.
1075 1074
1076 1075 With no argument, show the current branch name. With one argument,
1077 1076 set the working directory branch name (the branch will not exist
1078 1077 in the repository until the next commit). Standard practice
1079 1078 recommends that primary development take place on the 'default'
1080 1079 branch.
1081 1080
1082 1081 Unless -f/--force is specified, branch will not let you set a
1083 1082 branch name that already exists.
1084 1083
1085 1084 Use -C/--clean to reset the working directory branch to that of
1086 1085 the parent of the working directory, negating a previous branch
1087 1086 change.
1088 1087
1089 1088 Use the command :hg:`update` to switch to an existing branch. Use
1090 1089 :hg:`commit --close-branch` to mark this branch head as closed.
1091 1090 When all heads of a branch are closed, the branch will be
1092 1091 considered closed.
1093 1092
1094 1093 Returns 0 on success.
1095 1094 """
1096 1095 opts = pycompat.byteskwargs(opts)
1097 1096 revs = opts.get('rev')
1098 1097 if label:
1099 1098 label = label.strip()
1100 1099
1101 1100 if not opts.get('clean') and not label:
1102 1101 if revs:
1103 1102 raise error.Abort(_("no branch name specified for the revisions"))
1104 1103 ui.write("%s\n" % repo.dirstate.branch())
1105 1104 return
1106 1105
1107 1106 with repo.wlock():
1108 1107 if opts.get('clean'):
1109 1108 label = repo['.'].branch()
1110 1109 repo.dirstate.setbranch(label)
1111 1110 ui.status(_('reset working directory to branch %s\n') % label)
1112 1111 elif label:
1113 1112
1114 1113 scmutil.checknewlabel(repo, label, 'branch')
1115 1114 if revs:
1116 1115 return cmdutil.changebranch(ui, repo, revs, label)
1117 1116
1118 1117 if not opts.get('force') and label in repo.branchmap():
1119 1118 if label not in [p.branch() for p in repo[None].parents()]:
1120 1119 raise error.Abort(_('a branch of the same name already'
1121 1120 ' exists'),
1122 1121 # i18n: "it" refers to an existing branch
1123 1122 hint=_("use 'hg update' to switch to it"))
1124 1123
1125 1124 repo.dirstate.setbranch(label)
1126 1125 ui.status(_('marked working directory as branch %s\n') % label)
1127 1126
1128 1127 # find any open named branches aside from default
1129 1128 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1130 1129 if n != "default" and not c]
1131 1130 if not others:
1132 1131 ui.status(_('(branches are permanent and global, '
1133 1132 'did you want a bookmark?)\n'))
1134 1133
1135 1134 @command('branches',
1136 1135 [('a', 'active', False,
1137 1136 _('show only branches that have unmerged heads (DEPRECATED)')),
1138 1137 ('c', 'closed', False, _('show normal and closed branches')),
1139 1138 ('r', 'rev', [], _('show branch name(s) of the given rev'))
1140 1139 ] + formatteropts,
1141 1140 _('[-c]'),
1142 1141 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1143 1142 intents={INTENT_READONLY})
1144 1143 def branches(ui, repo, active=False, closed=False, **opts):
1145 1144 """list repository named branches
1146 1145
1147 1146 List the repository's named branches, indicating which ones are
1148 1147 inactive. If -c/--closed is specified, also list branches which have
1149 1148 been marked closed (see :hg:`commit --close-branch`).
1150 1149
1151 1150 Use the command :hg:`update` to switch to an existing branch.
1152 1151
1153 1152 .. container:: verbose
1154 1153
1155 1154 Template:
1156 1155
1157 1156 The following keywords are supported in addition to the common template
1158 1157 keywords and functions such as ``{branch}``. See also
1159 1158 :hg:`help templates`.
1160 1159
1161 1160 :active: Boolean. True if the branch is active.
1162 1161 :closed: Boolean. True if the branch is closed.
1163 1162 :current: Boolean. True if it is the current branch.
1164 1163
1165 1164 Returns 0.
1166 1165 """
1167 1166
1168 1167 opts = pycompat.byteskwargs(opts)
1169 1168 revs = opts.get('rev')
1170 1169 selectedbranches = None
1171 1170 if revs:
1172 1171 revs = scmutil.revrange(repo, revs)
1173 1172 getbi = repo.revbranchcache().branchinfo
1174 1173 selectedbranches = {getbi(r)[0] for r in revs}
1175 1174
1176 1175 ui.pager('branches')
1177 1176 fm = ui.formatter('branches', opts)
1178 1177 hexfunc = fm.hexfunc
1179 1178
1180 1179 allheads = set(repo.heads())
1181 1180 branches = []
1182 1181 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1183 1182 if selectedbranches is not None and tag not in selectedbranches:
1184 1183 continue
1185 1184 isactive = False
1186 1185 if not isclosed:
1187 1186 openheads = set(repo.branchmap().iteropen(heads))
1188 1187 isactive = bool(openheads & allheads)
1189 1188 branches.append((tag, repo[tip], isactive, not isclosed))
1190 1189 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1191 1190 reverse=True)
1192 1191
1193 1192 for tag, ctx, isactive, isopen in branches:
1194 1193 if active and not isactive:
1195 1194 continue
1196 1195 if isactive:
1197 1196 label = 'branches.active'
1198 1197 notice = ''
1199 1198 elif not isopen:
1200 1199 if not closed:
1201 1200 continue
1202 1201 label = 'branches.closed'
1203 1202 notice = _(' (closed)')
1204 1203 else:
1205 1204 label = 'branches.inactive'
1206 1205 notice = _(' (inactive)')
1207 1206 current = (tag == repo.dirstate.branch())
1208 1207 if current:
1209 1208 label = 'branches.current'
1210 1209
1211 1210 fm.startitem()
1212 1211 fm.write('branch', '%s', tag, label=label)
1213 1212 rev = ctx.rev()
1214 1213 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1215 1214 fmt = ' ' * padsize + ' %d:%s'
1216 1215 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1217 1216 label='log.changeset changeset.%s' % ctx.phasestr())
1218 1217 fm.context(ctx=ctx)
1219 1218 fm.data(active=isactive, closed=not isopen, current=current)
1220 1219 if not ui.quiet:
1221 1220 fm.plain(notice)
1222 1221 fm.plain('\n')
1223 1222 fm.end()
1224 1223
1225 1224 @command('bundle',
1226 1225 [('f', 'force', None, _('run even when the destination is unrelated')),
1227 1226 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1228 1227 _('REV')),
1229 1228 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1230 1229 _('BRANCH')),
1231 1230 ('', 'base', [],
1232 1231 _('a base changeset assumed to be available at the destination'),
1233 1232 _('REV')),
1234 1233 ('a', 'all', None, _('bundle all changesets in the repository')),
1235 1234 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1236 1235 ] + remoteopts,
1237 1236 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1238 1237 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1239 1238 def bundle(ui, repo, fname, dest=None, **opts):
1240 1239 """create a bundle file
1241 1240
1242 1241 Generate a bundle file containing data to be transferred to another
1243 1242 repository.
1244 1243
1245 1244 To create a bundle containing all changesets, use -a/--all
1246 1245 (or --base null). Otherwise, hg assumes the destination will have
1247 1246 all the nodes you specify with --base parameters. Otherwise, hg
1248 1247 will assume the repository has all the nodes in destination, or
1249 1248 default-push/default if no destination is specified, where destination
1250 1249 is the repository you provide through DEST option.
1251 1250
1252 1251 You can change bundle format with the -t/--type option. See
1253 1252 :hg:`help bundlespec` for documentation on this format. By default,
1254 1253 the most appropriate format is used and compression defaults to
1255 1254 bzip2.
1256 1255
1257 1256 The bundle file can then be transferred using conventional means
1258 1257 and applied to another repository with the unbundle or pull
1259 1258 command. This is useful when direct push and pull are not
1260 1259 available or when exporting an entire repository is undesirable.
1261 1260
1262 1261 Applying bundles preserves all changeset contents including
1263 1262 permissions, copy/rename information, and revision history.
1264 1263
1265 1264 Returns 0 on success, 1 if no changes found.
1266 1265 """
1267 1266 opts = pycompat.byteskwargs(opts)
1268 1267 revs = None
1269 1268 if 'rev' in opts:
1270 1269 revstrings = opts['rev']
1271 1270 revs = scmutil.revrange(repo, revstrings)
1272 1271 if revstrings and not revs:
1273 1272 raise error.Abort(_('no commits to bundle'))
1274 1273
1275 1274 bundletype = opts.get('type', 'bzip2').lower()
1276 1275 try:
1277 1276 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1278 1277 except error.UnsupportedBundleSpecification as e:
1279 1278 raise error.Abort(pycompat.bytestr(e),
1280 1279 hint=_("see 'hg help bundlespec' for supported "
1281 1280 "values for --type"))
1282 1281 cgversion = bundlespec.contentopts["cg.version"]
1283 1282
1284 1283 # Packed bundles are a pseudo bundle format for now.
1285 1284 if cgversion == 's1':
1286 1285 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1287 1286 hint=_("use 'hg debugcreatestreamclonebundle'"))
1288 1287
1289 1288 if opts.get('all'):
1290 1289 if dest:
1291 1290 raise error.Abort(_("--all is incompatible with specifying "
1292 1291 "a destination"))
1293 1292 if opts.get('base'):
1294 1293 ui.warn(_("ignoring --base because --all was specified\n"))
1295 1294 base = [nullrev]
1296 1295 else:
1297 1296 base = scmutil.revrange(repo, opts.get('base'))
1298 1297 if cgversion not in changegroup.supportedoutgoingversions(repo):
1299 1298 raise error.Abort(_("repository does not support bundle version %s") %
1300 1299 cgversion)
1301 1300
1302 1301 if base:
1303 1302 if dest:
1304 1303 raise error.Abort(_("--base is incompatible with specifying "
1305 1304 "a destination"))
1306 1305 common = [repo[rev].node() for rev in base]
1307 1306 heads = [repo[r].node() for r in revs] if revs else None
1308 1307 outgoing = discovery.outgoing(repo, common, heads)
1309 1308 else:
1310 1309 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1311 1310 dest, branches = hg.parseurl(dest, opts.get('branch'))
1312 1311 other = hg.peer(repo, opts, dest)
1313 1312 revs = [repo[r].hex() for r in revs]
1314 1313 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1315 1314 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1316 1315 outgoing = discovery.findcommonoutgoing(repo, other,
1317 1316 onlyheads=heads,
1318 1317 force=opts.get('force'),
1319 1318 portable=True)
1320 1319
1321 1320 if not outgoing.missing:
1322 1321 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1323 1322 return 1
1324 1323
1325 1324 if cgversion == '01': #bundle1
1326 1325 bversion = 'HG10' + bundlespec.wirecompression
1327 1326 bcompression = None
1328 1327 elif cgversion in ('02', '03'):
1329 1328 bversion = 'HG20'
1330 1329 bcompression = bundlespec.wirecompression
1331 1330 else:
1332 1331 raise error.ProgrammingError(
1333 1332 'bundle: unexpected changegroup version %s' % cgversion)
1334 1333
1335 1334 # TODO compression options should be derived from bundlespec parsing.
1336 1335 # This is a temporary hack to allow adjusting bundle compression
1337 1336 # level without a) formalizing the bundlespec changes to declare it
1338 1337 # b) introducing a command flag.
1339 1338 compopts = {}
1340 1339 complevel = ui.configint('experimental',
1341 1340 'bundlecomplevel.' + bundlespec.compression)
1342 1341 if complevel is None:
1343 1342 complevel = ui.configint('experimental', 'bundlecomplevel')
1344 1343 if complevel is not None:
1345 1344 compopts['level'] = complevel
1346 1345
1347 1346 # Allow overriding the bundling of obsmarker in phases through
1348 1347 # configuration while we don't have a bundle version that include them
1349 1348 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1350 1349 bundlespec.contentopts['obsolescence'] = True
1351 1350 if repo.ui.configbool('experimental', 'bundle-phases'):
1352 1351 bundlespec.contentopts['phases'] = True
1353 1352
1354 1353 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1355 1354 bundlespec.contentopts, compression=bcompression,
1356 1355 compopts=compopts)
1357 1356
1358 1357 @command('cat',
1359 1358 [('o', 'output', '',
1360 1359 _('print output to file with formatted name'), _('FORMAT')),
1361 1360 ('r', 'rev', '', _('print the given revision'), _('REV')),
1362 1361 ('', 'decode', None, _('apply any matching decode filter')),
1363 1362 ] + walkopts + formatteropts,
1364 1363 _('[OPTION]... FILE...'),
1365 1364 helpcategory=command.CATEGORY_FILE_CONTENTS,
1366 1365 inferrepo=True,
1367 1366 intents={INTENT_READONLY})
1368 1367 def cat(ui, repo, file1, *pats, **opts):
1369 1368 """output the current or given revision of files
1370 1369
1371 1370 Print the specified files as they were at the given revision. If
1372 1371 no revision is given, the parent of the working directory is used.
1373 1372
1374 1373 Output may be to a file, in which case the name of the file is
1375 1374 given using a template string. See :hg:`help templates`. In addition
1376 1375 to the common template keywords, the following formatting rules are
1377 1376 supported:
1378 1377
1379 1378 :``%%``: literal "%" character
1380 1379 :``%s``: basename of file being printed
1381 1380 :``%d``: dirname of file being printed, or '.' if in repository root
1382 1381 :``%p``: root-relative path name of file being printed
1383 1382 :``%H``: changeset hash (40 hexadecimal digits)
1384 1383 :``%R``: changeset revision number
1385 1384 :``%h``: short-form changeset hash (12 hexadecimal digits)
1386 1385 :``%r``: zero-padded changeset revision number
1387 1386 :``%b``: basename of the exporting repository
1388 1387 :``\\``: literal "\\" character
1389 1388
1390 1389 .. container:: verbose
1391 1390
1392 1391 Template:
1393 1392
1394 1393 The following keywords are supported in addition to the common template
1395 1394 keywords and functions. See also :hg:`help templates`.
1396 1395
1397 1396 :data: String. File content.
1398 1397 :path: String. Repository-absolute path of the file.
1399 1398
1400 1399 Returns 0 on success.
1401 1400 """
1402 1401 opts = pycompat.byteskwargs(opts)
1403 1402 rev = opts.get('rev')
1404 1403 if rev:
1405 1404 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1406 1405 ctx = scmutil.revsingle(repo, rev)
1407 1406 m = scmutil.match(ctx, (file1,) + pats, opts)
1408 1407 fntemplate = opts.pop('output', '')
1409 1408 if cmdutil.isstdiofilename(fntemplate):
1410 1409 fntemplate = ''
1411 1410
1412 1411 if fntemplate:
1413 1412 fm = formatter.nullformatter(ui, 'cat', opts)
1414 1413 else:
1415 1414 ui.pager('cat')
1416 1415 fm = ui.formatter('cat', opts)
1417 1416 with fm:
1418 1417 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1419 1418 **pycompat.strkwargs(opts))
1420 1419
1421 1420 @command('clone',
1422 1421 [('U', 'noupdate', None, _('the clone will include an empty working '
1423 1422 'directory (only a repository)')),
1424 1423 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1425 1424 _('REV')),
1426 1425 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1427 1426 ' and its ancestors'), _('REV')),
1428 1427 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1429 1428 ' changesets and their ancestors'), _('BRANCH')),
1430 1429 ('', 'pull', None, _('use pull protocol to copy metadata')),
1431 1430 ('', 'uncompressed', None,
1432 1431 _('an alias to --stream (DEPRECATED)')),
1433 1432 ('', 'stream', None,
1434 1433 _('clone with minimal data processing')),
1435 1434 ] + remoteopts,
1436 1435 _('[OPTION]... SOURCE [DEST]'),
1437 1436 helpcategory=command.CATEGORY_REPO_CREATION,
1438 1437 helpbasic=True, norepo=True)
1439 1438 def clone(ui, source, dest=None, **opts):
1440 1439 """make a copy of an existing repository
1441 1440
1442 1441 Create a copy of an existing repository in a new directory.
1443 1442
1444 1443 If no destination directory name is specified, it defaults to the
1445 1444 basename of the source.
1446 1445
1447 1446 The location of the source is added to the new repository's
1448 1447 ``.hg/hgrc`` file, as the default to be used for future pulls.
1449 1448
1450 1449 Only local paths and ``ssh://`` URLs are supported as
1451 1450 destinations. For ``ssh://`` destinations, no working directory or
1452 1451 ``.hg/hgrc`` will be created on the remote side.
1453 1452
1454 1453 If the source repository has a bookmark called '@' set, that
1455 1454 revision will be checked out in the new repository by default.
1456 1455
1457 1456 To check out a particular version, use -u/--update, or
1458 1457 -U/--noupdate to create a clone with no working directory.
1459 1458
1460 1459 To pull only a subset of changesets, specify one or more revisions
1461 1460 identifiers with -r/--rev or branches with -b/--branch. The
1462 1461 resulting clone will contain only the specified changesets and
1463 1462 their ancestors. These options (or 'clone src#rev dest') imply
1464 1463 --pull, even for local source repositories.
1465 1464
1466 1465 In normal clone mode, the remote normalizes repository data into a common
1467 1466 exchange format and the receiving end translates this data into its local
1468 1467 storage format. --stream activates a different clone mode that essentially
1469 1468 copies repository files from the remote with minimal data processing. This
1470 1469 significantly reduces the CPU cost of a clone both remotely and locally.
1471 1470 However, it often increases the transferred data size by 30-40%. This can
1472 1471 result in substantially faster clones where I/O throughput is plentiful,
1473 1472 especially for larger repositories. A side-effect of --stream clones is
1474 1473 that storage settings and requirements on the remote are applied locally:
1475 1474 a modern client may inherit legacy or inefficient storage used by the
1476 1475 remote or a legacy Mercurial client may not be able to clone from a
1477 1476 modern Mercurial remote.
1478 1477
1479 1478 .. note::
1480 1479
1481 1480 Specifying a tag will include the tagged changeset but not the
1482 1481 changeset containing the tag.
1483 1482
1484 1483 .. container:: verbose
1485 1484
1486 1485 For efficiency, hardlinks are used for cloning whenever the
1487 1486 source and destination are on the same filesystem (note this
1488 1487 applies only to the repository data, not to the working
1489 1488 directory). Some filesystems, such as AFS, implement hardlinking
1490 1489 incorrectly, but do not report errors. In these cases, use the
1491 1490 --pull option to avoid hardlinking.
1492 1491
1493 1492 Mercurial will update the working directory to the first applicable
1494 1493 revision from this list:
1495 1494
1496 1495 a) null if -U or the source repository has no changesets
1497 1496 b) if -u . and the source repository is local, the first parent of
1498 1497 the source repository's working directory
1499 1498 c) the changeset specified with -u (if a branch name, this means the
1500 1499 latest head of that branch)
1501 1500 d) the changeset specified with -r
1502 1501 e) the tipmost head specified with -b
1503 1502 f) the tipmost head specified with the url#branch source syntax
1504 1503 g) the revision marked with the '@' bookmark, if present
1505 1504 h) the tipmost head of the default branch
1506 1505 i) tip
1507 1506
1508 1507 When cloning from servers that support it, Mercurial may fetch
1509 1508 pre-generated data from a server-advertised URL or inline from the
1510 1509 same stream. When this is done, hooks operating on incoming changesets
1511 1510 and changegroups may fire more than once, once for each pre-generated
1512 1511 bundle and as well as for any additional remaining data. In addition,
1513 1512 if an error occurs, the repository may be rolled back to a partial
1514 1513 clone. This behavior may change in future releases.
1515 1514 See :hg:`help -e clonebundles` for more.
1516 1515
1517 1516 Examples:
1518 1517
1519 1518 - clone a remote repository to a new directory named hg/::
1520 1519
1521 1520 hg clone https://www.mercurial-scm.org/repo/hg/
1522 1521
1523 1522 - create a lightweight local clone::
1524 1523
1525 1524 hg clone project/ project-feature/
1526 1525
1527 1526 - clone from an absolute path on an ssh server (note double-slash)::
1528 1527
1529 1528 hg clone ssh://user@server//home/projects/alpha/
1530 1529
1531 1530 - do a streaming clone while checking out a specified version::
1532 1531
1533 1532 hg clone --stream http://server/repo -u 1.5
1534 1533
1535 1534 - create a repository without changesets after a particular revision::
1536 1535
1537 1536 hg clone -r 04e544 experimental/ good/
1538 1537
1539 1538 - clone (and track) a particular named branch::
1540 1539
1541 1540 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1542 1541
1543 1542 See :hg:`help urls` for details on specifying URLs.
1544 1543
1545 1544 Returns 0 on success.
1546 1545 """
1547 1546 opts = pycompat.byteskwargs(opts)
1548 1547 if opts.get('noupdate') and opts.get('updaterev'):
1549 1548 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1550 1549
1551 1550 # --include/--exclude can come from narrow or sparse.
1552 1551 includepats, excludepats = None, None
1553 1552
1554 1553 # hg.clone() differentiates between None and an empty set. So make sure
1555 1554 # patterns are sets if narrow is requested without patterns.
1556 1555 if opts.get('narrow'):
1557 1556 includepats = set()
1558 1557 excludepats = set()
1559 1558
1560 1559 if opts.get('include'):
1561 1560 includepats = narrowspec.parsepatterns(opts.get('include'))
1562 1561 if opts.get('exclude'):
1563 1562 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1564 1563
1565 1564 r = hg.clone(ui, opts, source, dest,
1566 1565 pull=opts.get('pull'),
1567 1566 stream=opts.get('stream') or opts.get('uncompressed'),
1568 1567 revs=opts.get('rev'),
1569 1568 update=opts.get('updaterev') or not opts.get('noupdate'),
1570 1569 branch=opts.get('branch'),
1571 1570 shareopts=opts.get('shareopts'),
1572 1571 storeincludepats=includepats,
1573 1572 storeexcludepats=excludepats,
1574 1573 depth=opts.get('depth') or None)
1575 1574
1576 1575 return r is None
1577 1576
1578 1577 @command('commit|ci',
1579 1578 [('A', 'addremove', None,
1580 1579 _('mark new/missing files as added/removed before committing')),
1581 1580 ('', 'close-branch', None,
1582 1581 _('mark a branch head as closed')),
1583 1582 ('', 'amend', None, _('amend the parent of the working directory')),
1584 1583 ('s', 'secret', None, _('use the secret phase for committing')),
1585 1584 ('e', 'edit', None, _('invoke editor on commit messages')),
1586 1585 ('i', 'interactive', None, _('use interactive mode')),
1587 1586 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1588 1587 _('[OPTION]... [FILE]...'),
1589 1588 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1590 1589 inferrepo=True)
1591 1590 def commit(ui, repo, *pats, **opts):
1592 1591 """commit the specified files or all outstanding changes
1593 1592
1594 1593 Commit changes to the given files into the repository. Unlike a
1595 1594 centralized SCM, this operation is a local operation. See
1596 1595 :hg:`push` for a way to actively distribute your changes.
1597 1596
1598 1597 If a list of files is omitted, all changes reported by :hg:`status`
1599 1598 will be committed.
1600 1599
1601 1600 If you are committing the result of a merge, do not provide any
1602 1601 filenames or -I/-X filters.
1603 1602
1604 1603 If no commit message is specified, Mercurial starts your
1605 1604 configured editor where you can enter a message. In case your
1606 1605 commit fails, you will find a backup of your message in
1607 1606 ``.hg/last-message.txt``.
1608 1607
1609 1608 The --close-branch flag can be used to mark the current branch
1610 1609 head closed. When all heads of a branch are closed, the branch
1611 1610 will be considered closed and no longer listed.
1612 1611
1613 1612 The --amend flag can be used to amend the parent of the
1614 1613 working directory with a new commit that contains the changes
1615 1614 in the parent in addition to those currently reported by :hg:`status`,
1616 1615 if there are any. The old commit is stored in a backup bundle in
1617 1616 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1618 1617 on how to restore it).
1619 1618
1620 1619 Message, user and date are taken from the amended commit unless
1621 1620 specified. When a message isn't specified on the command line,
1622 1621 the editor will open with the message of the amended commit.
1623 1622
1624 1623 It is not possible to amend public changesets (see :hg:`help phases`)
1625 1624 or changesets that have children.
1626 1625
1627 1626 See :hg:`help dates` for a list of formats valid for -d/--date.
1628 1627
1629 1628 Returns 0 on success, 1 if nothing changed.
1630 1629
1631 1630 .. container:: verbose
1632 1631
1633 1632 Examples:
1634 1633
1635 1634 - commit all files ending in .py::
1636 1635
1637 1636 hg commit --include "set:**.py"
1638 1637
1639 1638 - commit all non-binary files::
1640 1639
1641 1640 hg commit --exclude "set:binary()"
1642 1641
1643 1642 - amend the current commit and set the date to now::
1644 1643
1645 1644 hg commit --amend --date now
1646 1645 """
1647 1646 with repo.wlock(), repo.lock():
1648 1647 return _docommit(ui, repo, *pats, **opts)
1649 1648
1650 1649 def _docommit(ui, repo, *pats, **opts):
1651 1650 if opts.get(r'interactive'):
1652 1651 opts.pop(r'interactive')
1653 1652 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1654 1653 cmdutil.recordfilter, *pats,
1655 1654 **opts)
1656 1655 # ret can be 0 (no changes to record) or the value returned by
1657 1656 # commit(), 1 if nothing changed or None on success.
1658 1657 return 1 if ret == 0 else ret
1659 1658
1660 1659 opts = pycompat.byteskwargs(opts)
1661 1660 if opts.get('subrepos'):
1662 1661 if opts.get('amend'):
1663 1662 raise error.Abort(_('cannot amend with --subrepos'))
1664 1663 # Let --subrepos on the command line override config setting.
1665 1664 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1666 1665
1667 1666 cmdutil.checkunfinished(repo, commit=True)
1668 1667
1669 1668 branch = repo[None].branch()
1670 1669 bheads = repo.branchheads(branch)
1671 1670
1672 1671 extra = {}
1673 1672 if opts.get('close_branch'):
1674 1673 extra['close'] = '1'
1675 1674
1676 1675 if not bheads:
1677 1676 raise error.Abort(_('can only close branch heads'))
1678 1677 elif opts.get('amend'):
1679 1678 if (repo['.'].p1().branch() != branch and
1680 1679 repo['.'].p2().branch() != branch):
1681 1680 raise error.Abort(_('can only close branch heads'))
1682 1681
1683 1682 if opts.get('amend'):
1684 1683 if ui.configbool('ui', 'commitsubrepos'):
1685 1684 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1686 1685
1687 1686 old = repo['.']
1688 1687 rewriteutil.precheck(repo, [old.rev()], 'amend')
1689 1688
1690 1689 # Currently histedit gets confused if an amend happens while histedit
1691 1690 # is in progress. Since we have a checkunfinished command, we are
1692 1691 # temporarily honoring it.
1693 1692 #
1694 1693 # Note: eventually this guard will be removed. Please do not expect
1695 1694 # this behavior to remain.
1696 1695 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1697 1696 cmdutil.checkunfinished(repo)
1698 1697
1699 1698 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1700 1699 if node == old.node():
1701 1700 ui.status(_("nothing changed\n"))
1702 1701 return 1
1703 1702 else:
1704 1703 def commitfunc(ui, repo, message, match, opts):
1705 1704 overrides = {}
1706 1705 if opts.get('secret'):
1707 1706 overrides[('phases', 'new-commit')] = 'secret'
1708 1707
1709 1708 baseui = repo.baseui
1710 1709 with baseui.configoverride(overrides, 'commit'):
1711 1710 with ui.configoverride(overrides, 'commit'):
1712 1711 editform = cmdutil.mergeeditform(repo[None],
1713 1712 'commit.normal')
1714 1713 editor = cmdutil.getcommiteditor(
1715 1714 editform=editform, **pycompat.strkwargs(opts))
1716 1715 return repo.commit(message,
1717 1716 opts.get('user'),
1718 1717 opts.get('date'),
1719 1718 match,
1720 1719 editor=editor,
1721 1720 extra=extra)
1722 1721
1723 1722 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1724 1723
1725 1724 if not node:
1726 1725 stat = cmdutil.postcommitstatus(repo, pats, opts)
1727 1726 if stat[3]:
1728 1727 ui.status(_("nothing changed (%d missing files, see "
1729 1728 "'hg status')\n") % len(stat[3]))
1730 1729 else:
1731 1730 ui.status(_("nothing changed\n"))
1732 1731 return 1
1733 1732
1734 1733 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1735 1734
1736 1735 @command('config|showconfig|debugconfig',
1737 1736 [('u', 'untrusted', None, _('show untrusted configuration options')),
1738 1737 ('e', 'edit', None, _('edit user config')),
1739 1738 ('l', 'local', None, _('edit repository config')),
1740 1739 ('g', 'global', None, _('edit global config'))] + formatteropts,
1741 1740 _('[-u] [NAME]...'),
1742 1741 helpcategory=command.CATEGORY_HELP,
1743 1742 optionalrepo=True,
1744 1743 intents={INTENT_READONLY})
1745 1744 def config(ui, repo, *values, **opts):
1746 1745 """show combined config settings from all hgrc files
1747 1746
1748 1747 With no arguments, print names and values of all config items.
1749 1748
1750 1749 With one argument of the form section.name, print just the value
1751 1750 of that config item.
1752 1751
1753 1752 With multiple arguments, print names and values of all config
1754 1753 items with matching section names or section.names.
1755 1754
1756 1755 With --edit, start an editor on the user-level config file. With
1757 1756 --global, edit the system-wide config file. With --local, edit the
1758 1757 repository-level config file.
1759 1758
1760 1759 With --debug, the source (filename and line number) is printed
1761 1760 for each config item.
1762 1761
1763 1762 See :hg:`help config` for more information about config files.
1764 1763
1765 1764 .. container:: verbose
1766 1765
1767 1766 Template:
1768 1767
1769 1768 The following keywords are supported. See also :hg:`help templates`.
1770 1769
1771 1770 :name: String. Config name.
1772 1771 :source: String. Filename and line number where the item is defined.
1773 1772 :value: String. Config value.
1774 1773
1775 1774 Returns 0 on success, 1 if NAME does not exist.
1776 1775
1777 1776 """
1778 1777
1779 1778 opts = pycompat.byteskwargs(opts)
1780 1779 if opts.get('edit') or opts.get('local') or opts.get('global'):
1781 1780 if opts.get('local') and opts.get('global'):
1782 1781 raise error.Abort(_("can't use --local and --global together"))
1783 1782
1784 1783 if opts.get('local'):
1785 1784 if not repo:
1786 1785 raise error.Abort(_("can't use --local outside a repository"))
1787 1786 paths = [repo.vfs.join('hgrc')]
1788 1787 elif opts.get('global'):
1789 1788 paths = rcutil.systemrcpath()
1790 1789 else:
1791 1790 paths = rcutil.userrcpath()
1792 1791
1793 1792 for f in paths:
1794 1793 if os.path.exists(f):
1795 1794 break
1796 1795 else:
1797 1796 if opts.get('global'):
1798 1797 samplehgrc = uimod.samplehgrcs['global']
1799 1798 elif opts.get('local'):
1800 1799 samplehgrc = uimod.samplehgrcs['local']
1801 1800 else:
1802 1801 samplehgrc = uimod.samplehgrcs['user']
1803 1802
1804 1803 f = paths[0]
1805 1804 fp = open(f, "wb")
1806 1805 fp.write(util.tonativeeol(samplehgrc))
1807 1806 fp.close()
1808 1807
1809 1808 editor = ui.geteditor()
1810 1809 ui.system("%s \"%s\"" % (editor, f),
1811 1810 onerr=error.Abort, errprefix=_("edit failed"),
1812 1811 blockedtag='config_edit')
1813 1812 return
1814 1813 ui.pager('config')
1815 1814 fm = ui.formatter('config', opts)
1816 1815 for t, f in rcutil.rccomponents():
1817 1816 if t == 'path':
1818 1817 ui.debug('read config from: %s\n' % f)
1819 1818 elif t == 'items':
1820 1819 for section, name, value, source in f:
1821 1820 ui.debug('set config by: %s\n' % source)
1822 1821 else:
1823 1822 raise error.ProgrammingError('unknown rctype: %s' % t)
1824 1823 untrusted = bool(opts.get('untrusted'))
1825 1824
1826 1825 selsections = selentries = []
1827 1826 if values:
1828 1827 selsections = [v for v in values if '.' not in v]
1829 1828 selentries = [v for v in values if '.' in v]
1830 1829 uniquesel = (len(selentries) == 1 and not selsections)
1831 1830 selsections = set(selsections)
1832 1831 selentries = set(selentries)
1833 1832
1834 1833 matched = False
1835 1834 for section, name, value in ui.walkconfig(untrusted=untrusted):
1836 1835 source = ui.configsource(section, name, untrusted)
1837 1836 value = pycompat.bytestr(value)
1838 1837 if fm.isplain():
1839 1838 source = source or 'none'
1840 1839 value = value.replace('\n', '\\n')
1841 1840 entryname = section + '.' + name
1842 1841 if values and not (section in selsections or entryname in selentries):
1843 1842 continue
1844 1843 fm.startitem()
1845 1844 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1846 1845 if uniquesel:
1847 1846 fm.data(name=entryname)
1848 1847 fm.write('value', '%s\n', value)
1849 1848 else:
1850 1849 fm.write('name value', '%s=%s\n', entryname, value)
1851 1850 matched = True
1852 1851 fm.end()
1853 1852 if matched:
1854 1853 return 0
1855 1854 return 1
1856 1855
1857 1856 @command('copy|cp',
1858 1857 [('A', 'after', None, _('record a copy that has already occurred')),
1859 1858 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1860 1859 ] + walkopts + dryrunopts,
1861 1860 _('[OPTION]... [SOURCE]... DEST'),
1862 1861 helpcategory=command.CATEGORY_FILE_CONTENTS)
1863 1862 def copy(ui, repo, *pats, **opts):
1864 1863 """mark files as copied for the next commit
1865 1864
1866 1865 Mark dest as having copies of source files. If dest is a
1867 1866 directory, copies are put in that directory. If dest is a file,
1868 1867 the source must be a single file.
1869 1868
1870 1869 By default, this command copies the contents of files as they
1871 1870 exist in the working directory. If invoked with -A/--after, the
1872 1871 operation is recorded, but no copying is performed.
1873 1872
1874 1873 This command takes effect with the next commit. To undo a copy
1875 1874 before that, see :hg:`revert`.
1876 1875
1877 1876 Returns 0 on success, 1 if errors are encountered.
1878 1877 """
1879 1878 opts = pycompat.byteskwargs(opts)
1880 1879 with repo.wlock(False):
1881 1880 return cmdutil.copy(ui, repo, pats, opts)
1882 1881
1883 1882 @command(
1884 1883 'debugcommands', [], _('[COMMAND]'),
1885 1884 helpcategory=command.CATEGORY_HELP,
1886 1885 norepo=True)
1887 1886 def debugcommands(ui, cmd='', *args):
1888 1887 """list all available commands and options"""
1889 1888 for cmd, vals in sorted(table.iteritems()):
1890 1889 cmd = cmd.split('|')[0]
1891 1890 opts = ', '.join([i[1] for i in vals[1]])
1892 1891 ui.write('%s: %s\n' % (cmd, opts))
1893 1892
1894 1893 @command('debugcomplete',
1895 1894 [('o', 'options', None, _('show the command options'))],
1896 1895 _('[-o] CMD'),
1897 1896 helpcategory=command.CATEGORY_HELP,
1898 1897 norepo=True)
1899 1898 def debugcomplete(ui, cmd='', **opts):
1900 1899 """returns the completion list associated with the given command"""
1901 1900
1902 1901 if opts.get(r'options'):
1903 1902 options = []
1904 1903 otables = [globalopts]
1905 1904 if cmd:
1906 1905 aliases, entry = cmdutil.findcmd(cmd, table, False)
1907 1906 otables.append(entry[1])
1908 1907 for t in otables:
1909 1908 for o in t:
1910 1909 if "(DEPRECATED)" in o[3]:
1911 1910 continue
1912 1911 if o[0]:
1913 1912 options.append('-%s' % o[0])
1914 1913 options.append('--%s' % o[1])
1915 1914 ui.write("%s\n" % "\n".join(options))
1916 1915 return
1917 1916
1918 1917 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1919 1918 if ui.verbose:
1920 1919 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1921 1920 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1922 1921
1923 1922 @command('diff',
1924 1923 [('r', 'rev', [], _('revision'), _('REV')),
1925 1924 ('c', 'change', '', _('change made by revision'), _('REV'))
1926 1925 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1927 1926 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1928 1927 helpcategory=command.CATEGORY_FILE_CONTENTS,
1929 1928 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1930 1929 def diff(ui, repo, *pats, **opts):
1931 1930 """diff repository (or selected files)
1932 1931
1933 1932 Show differences between revisions for the specified files.
1934 1933
1935 1934 Differences between files are shown using the unified diff format.
1936 1935
1937 1936 .. note::
1938 1937
1939 1938 :hg:`diff` may generate unexpected results for merges, as it will
1940 1939 default to comparing against the working directory's first
1941 1940 parent changeset if no revisions are specified.
1942 1941
1943 1942 When two revision arguments are given, then changes are shown
1944 1943 between those revisions. If only one revision is specified then
1945 1944 that revision is compared to the working directory, and, when no
1946 1945 revisions are specified, the working directory files are compared
1947 1946 to its first parent.
1948 1947
1949 1948 Alternatively you can specify -c/--change with a revision to see
1950 1949 the changes in that changeset relative to its first parent.
1951 1950
1952 1951 Without the -a/--text option, diff will avoid generating diffs of
1953 1952 files it detects as binary. With -a, diff will generate a diff
1954 1953 anyway, probably with undesirable results.
1955 1954
1956 1955 Use the -g/--git option to generate diffs in the git extended diff
1957 1956 format. For more information, read :hg:`help diffs`.
1958 1957
1959 1958 .. container:: verbose
1960 1959
1961 1960 Examples:
1962 1961
1963 1962 - compare a file in the current working directory to its parent::
1964 1963
1965 1964 hg diff foo.c
1966 1965
1967 1966 - compare two historical versions of a directory, with rename info::
1968 1967
1969 1968 hg diff --git -r 1.0:1.2 lib/
1970 1969
1971 1970 - get change stats relative to the last change on some date::
1972 1971
1973 1972 hg diff --stat -r "date('may 2')"
1974 1973
1975 1974 - diff all newly-added files that contain a keyword::
1976 1975
1977 1976 hg diff "set:added() and grep(GNU)"
1978 1977
1979 1978 - compare a revision and its parents::
1980 1979
1981 1980 hg diff -c 9353 # compare against first parent
1982 1981 hg diff -r 9353^:9353 # same using revset syntax
1983 1982 hg diff -r 9353^2:9353 # compare against the second parent
1984 1983
1985 1984 Returns 0 on success.
1986 1985 """
1987 1986
1988 1987 opts = pycompat.byteskwargs(opts)
1989 1988 revs = opts.get('rev')
1990 1989 change = opts.get('change')
1991 1990 stat = opts.get('stat')
1992 1991 reverse = opts.get('reverse')
1993 1992
1994 1993 if revs and change:
1995 1994 msg = _('cannot specify --rev and --change at the same time')
1996 1995 raise error.Abort(msg)
1997 1996 elif change:
1998 1997 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1999 1998 ctx2 = scmutil.revsingle(repo, change, None)
2000 1999 ctx1 = ctx2.p1()
2001 2000 else:
2002 2001 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
2003 2002 ctx1, ctx2 = scmutil.revpair(repo, revs)
2004 2003 node1, node2 = ctx1.node(), ctx2.node()
2005 2004
2006 2005 if reverse:
2007 2006 node1, node2 = node2, node1
2008 2007
2009 2008 diffopts = patch.diffallopts(ui, opts)
2010 2009 m = scmutil.match(ctx2, pats, opts)
2011 2010 m = repo.narrowmatch(m)
2012 2011 ui.pager('diff')
2013 2012 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2014 2013 listsubrepos=opts.get('subrepos'),
2015 2014 root=opts.get('root'))
2016 2015
2017 2016 @command('export',
2018 2017 [('B', 'bookmark', '',
2019 2018 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2020 2019 ('o', 'output', '',
2021 2020 _('print output to file with formatted name'), _('FORMAT')),
2022 2021 ('', 'switch-parent', None, _('diff against the second parent')),
2023 2022 ('r', 'rev', [], _('revisions to export'), _('REV')),
2024 2023 ] + diffopts + formatteropts,
2025 2024 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2026 2025 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2027 2026 helpbasic=True, intents={INTENT_READONLY})
2028 2027 def export(ui, repo, *changesets, **opts):
2029 2028 """dump the header and diffs for one or more changesets
2030 2029
2031 2030 Print the changeset header and diffs for one or more revisions.
2032 2031 If no revision is given, the parent of the working directory is used.
2033 2032
2034 2033 The information shown in the changeset header is: author, date,
2035 2034 branch name (if non-default), changeset hash, parent(s) and commit
2036 2035 comment.
2037 2036
2038 2037 .. note::
2039 2038
2040 2039 :hg:`export` may generate unexpected diff output for merge
2041 2040 changesets, as it will compare the merge changeset against its
2042 2041 first parent only.
2043 2042
2044 2043 Output may be to a file, in which case the name of the file is
2045 2044 given using a template string. See :hg:`help templates`. In addition
2046 2045 to the common template keywords, the following formatting rules are
2047 2046 supported:
2048 2047
2049 2048 :``%%``: literal "%" character
2050 2049 :``%H``: changeset hash (40 hexadecimal digits)
2051 2050 :``%N``: number of patches being generated
2052 2051 :``%R``: changeset revision number
2053 2052 :``%b``: basename of the exporting repository
2054 2053 :``%h``: short-form changeset hash (12 hexadecimal digits)
2055 2054 :``%m``: first line of the commit message (only alphanumeric characters)
2056 2055 :``%n``: zero-padded sequence number, starting at 1
2057 2056 :``%r``: zero-padded changeset revision number
2058 2057 :``\\``: literal "\\" character
2059 2058
2060 2059 Without the -a/--text option, export will avoid generating diffs
2061 2060 of files it detects as binary. With -a, export will generate a
2062 2061 diff anyway, probably with undesirable results.
2063 2062
2064 2063 With -B/--bookmark changesets reachable by the given bookmark are
2065 2064 selected.
2066 2065
2067 2066 Use the -g/--git option to generate diffs in the git extended diff
2068 2067 format. See :hg:`help diffs` for more information.
2069 2068
2070 2069 With the --switch-parent option, the diff will be against the
2071 2070 second parent. It can be useful to review a merge.
2072 2071
2073 2072 .. container:: verbose
2074 2073
2075 2074 Template:
2076 2075
2077 2076 The following keywords are supported in addition to the common template
2078 2077 keywords and functions. See also :hg:`help templates`.
2079 2078
2080 2079 :diff: String. Diff content.
2081 2080 :parents: List of strings. Parent nodes of the changeset.
2082 2081
2083 2082 Examples:
2084 2083
2085 2084 - use export and import to transplant a bugfix to the current
2086 2085 branch::
2087 2086
2088 2087 hg export -r 9353 | hg import -
2089 2088
2090 2089 - export all the changesets between two revisions to a file with
2091 2090 rename information::
2092 2091
2093 2092 hg export --git -r 123:150 > changes.txt
2094 2093
2095 2094 - split outgoing changes into a series of patches with
2096 2095 descriptive names::
2097 2096
2098 2097 hg export -r "outgoing()" -o "%n-%m.patch"
2099 2098
2100 2099 Returns 0 on success.
2101 2100 """
2102 2101 opts = pycompat.byteskwargs(opts)
2103 2102 bookmark = opts.get('bookmark')
2104 2103 changesets += tuple(opts.get('rev', []))
2105 2104
2106 2105 if bookmark and changesets:
2107 2106 raise error.Abort(_("-r and -B are mutually exclusive"))
2108 2107
2109 2108 if bookmark:
2110 2109 if bookmark not in repo._bookmarks:
2111 2110 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2112 2111
2113 2112 revs = scmutil.bookmarkrevs(repo, bookmark)
2114 2113 else:
2115 2114 if not changesets:
2116 2115 changesets = ['.']
2117 2116
2118 2117 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2119 2118 revs = scmutil.revrange(repo, changesets)
2120 2119
2121 2120 if not revs:
2122 2121 raise error.Abort(_("export requires at least one changeset"))
2123 2122 if len(revs) > 1:
2124 2123 ui.note(_('exporting patches:\n'))
2125 2124 else:
2126 2125 ui.note(_('exporting patch:\n'))
2127 2126
2128 2127 fntemplate = opts.get('output')
2129 2128 if cmdutil.isstdiofilename(fntemplate):
2130 2129 fntemplate = ''
2131 2130
2132 2131 if fntemplate:
2133 2132 fm = formatter.nullformatter(ui, 'export', opts)
2134 2133 else:
2135 2134 ui.pager('export')
2136 2135 fm = ui.formatter('export', opts)
2137 2136 with fm:
2138 2137 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2139 2138 switch_parent=opts.get('switch_parent'),
2140 2139 opts=patch.diffallopts(ui, opts))
2141 2140
2142 2141 @command('files',
2143 2142 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2144 2143 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2145 2144 ] + walkopts + formatteropts + subrepoopts,
2146 2145 _('[OPTION]... [FILE]...'),
2147 2146 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2148 2147 intents={INTENT_READONLY})
2149 2148 def files(ui, repo, *pats, **opts):
2150 2149 """list tracked files
2151 2150
2152 2151 Print files under Mercurial control in the working directory or
2153 2152 specified revision for given files (excluding removed files).
2154 2153 Files can be specified as filenames or filesets.
2155 2154
2156 2155 If no files are given to match, this command prints the names
2157 2156 of all files under Mercurial control.
2158 2157
2159 2158 .. container:: verbose
2160 2159
2161 2160 Template:
2162 2161
2163 2162 The following keywords are supported in addition to the common template
2164 2163 keywords and functions. See also :hg:`help templates`.
2165 2164
2166 2165 :flags: String. Character denoting file's symlink and executable bits.
2167 2166 :path: String. Repository-absolute path of the file.
2168 2167 :size: Integer. Size of the file in bytes.
2169 2168
2170 2169 Examples:
2171 2170
2172 2171 - list all files under the current directory::
2173 2172
2174 2173 hg files .
2175 2174
2176 2175 - shows sizes and flags for current revision::
2177 2176
2178 2177 hg files -vr .
2179 2178
2180 2179 - list all files named README::
2181 2180
2182 2181 hg files -I "**/README"
2183 2182
2184 2183 - list all binary files::
2185 2184
2186 2185 hg files "set:binary()"
2187 2186
2188 2187 - find files containing a regular expression::
2189 2188
2190 2189 hg files "set:grep('bob')"
2191 2190
2192 2191 - search tracked file contents with xargs and grep::
2193 2192
2194 2193 hg files -0 | xargs -0 grep foo
2195 2194
2196 2195 See :hg:`help patterns` and :hg:`help filesets` for more information
2197 2196 on specifying file patterns.
2198 2197
2199 2198 Returns 0 if a match is found, 1 otherwise.
2200 2199
2201 2200 """
2202 2201
2203 2202 opts = pycompat.byteskwargs(opts)
2204 2203 rev = opts.get('rev')
2205 2204 if rev:
2206 2205 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2207 2206 ctx = scmutil.revsingle(repo, rev, None)
2208 2207
2209 2208 end = '\n'
2210 2209 if opts.get('print0'):
2211 2210 end = '\0'
2212 2211 fmt = '%s' + end
2213 2212
2214 2213 m = scmutil.match(ctx, pats, opts)
2215 2214 ui.pager('files')
2216 2215 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2217 2216 with ui.formatter('files', opts) as fm:
2218 2217 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt,
2219 2218 opts.get('subrepos'))
2220 2219
2221 2220 @command(
2222 2221 'forget',
2223 2222 [('i', 'interactive', None, _('use interactive mode')),
2224 2223 ] + walkopts + dryrunopts,
2225 2224 _('[OPTION]... FILE...'),
2226 2225 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2227 2226 helpbasic=True, inferrepo=True)
2228 2227 def forget(ui, repo, *pats, **opts):
2229 2228 """forget the specified files on the next commit
2230 2229
2231 2230 Mark the specified files so they will no longer be tracked
2232 2231 after the next commit.
2233 2232
2234 2233 This only removes files from the current branch, not from the
2235 2234 entire project history, and it does not delete them from the
2236 2235 working directory.
2237 2236
2238 2237 To delete the file from the working directory, see :hg:`remove`.
2239 2238
2240 2239 To undo a forget before the next commit, see :hg:`add`.
2241 2240
2242 2241 .. container:: verbose
2243 2242
2244 2243 Examples:
2245 2244
2246 2245 - forget newly-added binary files::
2247 2246
2248 2247 hg forget "set:added() and binary()"
2249 2248
2250 2249 - forget files that would be excluded by .hgignore::
2251 2250
2252 2251 hg forget "set:hgignore()"
2253 2252
2254 2253 Returns 0 on success.
2255 2254 """
2256 2255
2257 2256 opts = pycompat.byteskwargs(opts)
2258 2257 if not pats:
2259 2258 raise error.Abort(_('no files specified'))
2260 2259
2261 2260 m = scmutil.match(repo[None], pats, opts)
2262 2261 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2263 2262 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2264 2263 rejected = cmdutil.forget(ui, repo, m, prefix="", uipathfn=uipathfn,
2265 2264 explicitonly=False, dryrun=dryrun,
2266 2265 interactive=interactive)[0]
2267 2266 return rejected and 1 or 0
2268 2267
2269 2268 @command(
2270 2269 'graft',
2271 2270 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2272 2271 ('', 'base', '',
2273 2272 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2274 2273 ('c', 'continue', False, _('resume interrupted graft')),
2275 2274 ('', 'stop', False, _('stop interrupted graft')),
2276 2275 ('', 'abort', False, _('abort interrupted graft')),
2277 2276 ('e', 'edit', False, _('invoke editor on commit messages')),
2278 2277 ('', 'log', None, _('append graft info to log message')),
2279 2278 ('', 'no-commit', None,
2280 2279 _("don't commit, just apply the changes in working directory")),
2281 2280 ('f', 'force', False, _('force graft')),
2282 2281 ('D', 'currentdate', False,
2283 2282 _('record the current date as commit date')),
2284 2283 ('U', 'currentuser', False,
2285 2284 _('record the current user as committer'))]
2286 2285 + commitopts2 + mergetoolopts + dryrunopts,
2287 2286 _('[OPTION]... [-r REV]... REV...'),
2288 2287 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2289 2288 def graft(ui, repo, *revs, **opts):
2290 2289 '''copy changes from other branches onto the current branch
2291 2290
2292 2291 This command uses Mercurial's merge logic to copy individual
2293 2292 changes from other branches without merging branches in the
2294 2293 history graph. This is sometimes known as 'backporting' or
2295 2294 'cherry-picking'. By default, graft will copy user, date, and
2296 2295 description from the source changesets.
2297 2296
2298 2297 Changesets that are ancestors of the current revision, that have
2299 2298 already been grafted, or that are merges will be skipped.
2300 2299
2301 2300 If --log is specified, log messages will have a comment appended
2302 2301 of the form::
2303 2302
2304 2303 (grafted from CHANGESETHASH)
2305 2304
2306 2305 If --force is specified, revisions will be grafted even if they
2307 2306 are already ancestors of, or have been grafted to, the destination.
2308 2307 This is useful when the revisions have since been backed out.
2309 2308
2310 2309 If a graft merge results in conflicts, the graft process is
2311 2310 interrupted so that the current merge can be manually resolved.
2312 2311 Once all conflicts are addressed, the graft process can be
2313 2312 continued with the -c/--continue option.
2314 2313
2315 2314 The -c/--continue option reapplies all the earlier options.
2316 2315
2317 2316 .. container:: verbose
2318 2317
2319 2318 The --base option exposes more of how graft internally uses merge with a
2320 2319 custom base revision. --base can be used to specify another ancestor than
2321 2320 the first and only parent.
2322 2321
2323 2322 The command::
2324 2323
2325 2324 hg graft -r 345 --base 234
2326 2325
2327 2326 is thus pretty much the same as::
2328 2327
2329 2328 hg diff -r 234 -r 345 | hg import
2330 2329
2331 2330 but using merge to resolve conflicts and track moved files.
2332 2331
2333 2332 The result of a merge can thus be backported as a single commit by
2334 2333 specifying one of the merge parents as base, and thus effectively
2335 2334 grafting the changes from the other side.
2336 2335
2337 2336 It is also possible to collapse multiple changesets and clean up history
2338 2337 by specifying another ancestor as base, much like rebase --collapse
2339 2338 --keep.
2340 2339
2341 2340 The commit message can be tweaked after the fact using commit --amend .
2342 2341
2343 2342 For using non-ancestors as the base to backout changes, see the backout
2344 2343 command and the hidden --parent option.
2345 2344
2346 2345 .. container:: verbose
2347 2346
2348 2347 Examples:
2349 2348
2350 2349 - copy a single change to the stable branch and edit its description::
2351 2350
2352 2351 hg update stable
2353 2352 hg graft --edit 9393
2354 2353
2355 2354 - graft a range of changesets with one exception, updating dates::
2356 2355
2357 2356 hg graft -D "2085::2093 and not 2091"
2358 2357
2359 2358 - continue a graft after resolving conflicts::
2360 2359
2361 2360 hg graft -c
2362 2361
2363 2362 - show the source of a grafted changeset::
2364 2363
2365 2364 hg log --debug -r .
2366 2365
2367 2366 - show revisions sorted by date::
2368 2367
2369 2368 hg log -r "sort(all(), date)"
2370 2369
2371 2370 - backport the result of a merge as a single commit::
2372 2371
2373 2372 hg graft -r 123 --base 123^
2374 2373
2375 2374 - land a feature branch as one changeset::
2376 2375
2377 2376 hg up -cr default
2378 2377 hg graft -r featureX --base "ancestor('featureX', 'default')"
2379 2378
2380 2379 See :hg:`help revisions` for more about specifying revisions.
2381 2380
2382 2381 Returns 0 on successful completion.
2383 2382 '''
2384 2383 with repo.wlock():
2385 2384 return _dograft(ui, repo, *revs, **opts)
2386 2385
2387 2386 def _dograft(ui, repo, *revs, **opts):
2388 2387 opts = pycompat.byteskwargs(opts)
2389 2388 if revs and opts.get('rev'):
2390 2389 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2391 2390 'revision ordering!\n'))
2392 2391
2393 2392 revs = list(revs)
2394 2393 revs.extend(opts.get('rev'))
2395 2394 basectx = None
2396 2395 if opts.get('base'):
2397 2396 basectx = scmutil.revsingle(repo, opts['base'], None)
2398 2397 # a dict of data to be stored in state file
2399 2398 statedata = {}
2400 2399 # list of new nodes created by ongoing graft
2401 2400 statedata['newnodes'] = []
2402 2401
2403 2402 if opts.get('user') and opts.get('currentuser'):
2404 2403 raise error.Abort(_('--user and --currentuser are mutually exclusive'))
2405 2404 if opts.get('date') and opts.get('currentdate'):
2406 2405 raise error.Abort(_('--date and --currentdate are mutually exclusive'))
2407 2406 if not opts.get('user') and opts.get('currentuser'):
2408 2407 opts['user'] = ui.username()
2409 2408 if not opts.get('date') and opts.get('currentdate'):
2410 2409 opts['date'] = "%d %d" % dateutil.makedate()
2411 2410
2412 2411 editor = cmdutil.getcommiteditor(editform='graft',
2413 2412 **pycompat.strkwargs(opts))
2414 2413
2415 2414 cont = False
2416 2415 if opts.get('no_commit'):
2417 2416 if opts.get('edit'):
2418 2417 raise error.Abort(_("cannot specify --no-commit and "
2419 2418 "--edit together"))
2420 2419 if opts.get('currentuser'):
2421 2420 raise error.Abort(_("cannot specify --no-commit and "
2422 2421 "--currentuser together"))
2423 2422 if opts.get('currentdate'):
2424 2423 raise error.Abort(_("cannot specify --no-commit and "
2425 2424 "--currentdate together"))
2426 2425 if opts.get('log'):
2427 2426 raise error.Abort(_("cannot specify --no-commit and "
2428 2427 "--log together"))
2429 2428
2430 2429 graftstate = statemod.cmdstate(repo, 'graftstate')
2431 2430
2432 2431 if opts.get('stop'):
2433 2432 if opts.get('continue'):
2434 2433 raise error.Abort(_("cannot use '--continue' and "
2435 2434 "'--stop' together"))
2436 2435 if opts.get('abort'):
2437 2436 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2438 2437
2439 2438 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2440 2439 opts.get('date'), opts.get('currentdate'),
2441 2440 opts.get('currentuser'), opts.get('rev'))):
2442 2441 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2443 2442 return _stopgraft(ui, repo, graftstate)
2444 2443 elif opts.get('abort'):
2445 2444 if opts.get('continue'):
2446 2445 raise error.Abort(_("cannot use '--continue' and "
2447 2446 "'--abort' together"))
2448 2447 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2449 2448 opts.get('date'), opts.get('currentdate'),
2450 2449 opts.get('currentuser'), opts.get('rev'))):
2451 2450 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2452 2451
2453 2452 return _abortgraft(ui, repo, graftstate)
2454 2453 elif opts.get('continue'):
2455 2454 cont = True
2456 2455 if revs:
2457 2456 raise error.Abort(_("can't specify --continue and revisions"))
2458 2457 # read in unfinished revisions
2459 2458 if graftstate.exists():
2460 2459 statedata = _readgraftstate(repo, graftstate)
2461 2460 if statedata.get('date'):
2462 2461 opts['date'] = statedata['date']
2463 2462 if statedata.get('user'):
2464 2463 opts['user'] = statedata['user']
2465 2464 if statedata.get('log'):
2466 2465 opts['log'] = True
2467 2466 if statedata.get('no_commit'):
2468 2467 opts['no_commit'] = statedata.get('no_commit')
2469 2468 nodes = statedata['nodes']
2470 2469 revs = [repo[node].rev() for node in nodes]
2471 2470 else:
2472 2471 cmdutil.wrongtooltocontinue(repo, _('graft'))
2473 2472 else:
2474 2473 if not revs:
2475 2474 raise error.Abort(_('no revisions specified'))
2476 2475 cmdutil.checkunfinished(repo)
2477 2476 cmdutil.bailifchanged(repo)
2478 2477 revs = scmutil.revrange(repo, revs)
2479 2478
2480 2479 skipped = set()
2481 2480 if basectx is None:
2482 2481 # check for merges
2483 2482 for rev in repo.revs('%ld and merge()', revs):
2484 2483 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2485 2484 skipped.add(rev)
2486 2485 revs = [r for r in revs if r not in skipped]
2487 2486 if not revs:
2488 2487 return -1
2489 2488 if basectx is not None and len(revs) != 1:
2490 2489 raise error.Abort(_('only one revision allowed with --base '))
2491 2490
2492 2491 # Don't check in the --continue case, in effect retaining --force across
2493 2492 # --continues. That's because without --force, any revisions we decided to
2494 2493 # skip would have been filtered out here, so they wouldn't have made their
2495 2494 # way to the graftstate. With --force, any revisions we would have otherwise
2496 2495 # skipped would not have been filtered out, and if they hadn't been applied
2497 2496 # already, they'd have been in the graftstate.
2498 2497 if not (cont or opts.get('force')) and basectx is None:
2499 2498 # check for ancestors of dest branch
2500 2499 crev = repo['.'].rev()
2501 2500 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2502 2501 # XXX make this lazy in the future
2503 2502 # don't mutate while iterating, create a copy
2504 2503 for rev in list(revs):
2505 2504 if rev in ancestors:
2506 2505 ui.warn(_('skipping ancestor revision %d:%s\n') %
2507 2506 (rev, repo[rev]))
2508 2507 # XXX remove on list is slow
2509 2508 revs.remove(rev)
2510 2509 if not revs:
2511 2510 return -1
2512 2511
2513 2512 # analyze revs for earlier grafts
2514 2513 ids = {}
2515 2514 for ctx in repo.set("%ld", revs):
2516 2515 ids[ctx.hex()] = ctx.rev()
2517 2516 n = ctx.extra().get('source')
2518 2517 if n:
2519 2518 ids[n] = ctx.rev()
2520 2519
2521 2520 # check ancestors for earlier grafts
2522 2521 ui.debug('scanning for duplicate grafts\n')
2523 2522
2524 2523 # The only changesets we can be sure doesn't contain grafts of any
2525 2524 # revs, are the ones that are common ancestors of *all* revs:
2526 2525 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2527 2526 ctx = repo[rev]
2528 2527 n = ctx.extra().get('source')
2529 2528 if n in ids:
2530 2529 try:
2531 2530 r = repo[n].rev()
2532 2531 except error.RepoLookupError:
2533 2532 r = None
2534 2533 if r in revs:
2535 2534 ui.warn(_('skipping revision %d:%s '
2536 2535 '(already grafted to %d:%s)\n')
2537 2536 % (r, repo[r], rev, ctx))
2538 2537 revs.remove(r)
2539 2538 elif ids[n] in revs:
2540 2539 if r is None:
2541 2540 ui.warn(_('skipping already grafted revision %d:%s '
2542 2541 '(%d:%s also has unknown origin %s)\n')
2543 2542 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2544 2543 else:
2545 2544 ui.warn(_('skipping already grafted revision %d:%s '
2546 2545 '(%d:%s also has origin %d:%s)\n')
2547 2546 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2548 2547 revs.remove(ids[n])
2549 2548 elif ctx.hex() in ids:
2550 2549 r = ids[ctx.hex()]
2551 2550 if r in revs:
2552 2551 ui.warn(_('skipping already grafted revision %d:%s '
2553 2552 '(was grafted from %d:%s)\n') %
2554 2553 (r, repo[r], rev, ctx))
2555 2554 revs.remove(r)
2556 2555 if not revs:
2557 2556 return -1
2558 2557
2559 2558 if opts.get('no_commit'):
2560 2559 statedata['no_commit'] = True
2561 2560 for pos, ctx in enumerate(repo.set("%ld", revs)):
2562 2561 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2563 2562 ctx.description().split('\n', 1)[0])
2564 2563 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2565 2564 if names:
2566 2565 desc += ' (%s)' % ' '.join(names)
2567 2566 ui.status(_('grafting %s\n') % desc)
2568 2567 if opts.get('dry_run'):
2569 2568 continue
2570 2569
2571 2570 source = ctx.extra().get('source')
2572 2571 extra = {}
2573 2572 if source:
2574 2573 extra['source'] = source
2575 2574 extra['intermediate-source'] = ctx.hex()
2576 2575 else:
2577 2576 extra['source'] = ctx.hex()
2578 2577 user = ctx.user()
2579 2578 if opts.get('user'):
2580 2579 user = opts['user']
2581 2580 statedata['user'] = user
2582 2581 date = ctx.date()
2583 2582 if opts.get('date'):
2584 2583 date = opts['date']
2585 2584 statedata['date'] = date
2586 2585 message = ctx.description()
2587 2586 if opts.get('log'):
2588 2587 message += '\n(grafted from %s)' % ctx.hex()
2589 2588 statedata['log'] = True
2590 2589
2591 2590 # we don't merge the first commit when continuing
2592 2591 if not cont:
2593 2592 # perform the graft merge with p1(rev) as 'ancestor'
2594 2593 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2595 2594 base = ctx.p1() if basectx is None else basectx
2596 2595 with ui.configoverride(overrides, 'graft'):
2597 2596 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2598 2597 # report any conflicts
2599 2598 if stats.unresolvedcount > 0:
2600 2599 # write out state for --continue
2601 2600 nodes = [repo[rev].hex() for rev in revs[pos:]]
2602 2601 statedata['nodes'] = nodes
2603 2602 stateversion = 1
2604 2603 graftstate.save(stateversion, statedata)
2605 2604 hint = _("use 'hg resolve' and 'hg graft --continue'")
2606 2605 raise error.Abort(
2607 2606 _("unresolved conflicts, can't continue"),
2608 2607 hint=hint)
2609 2608 else:
2610 2609 cont = False
2611 2610
2612 2611 # commit if --no-commit is false
2613 2612 if not opts.get('no_commit'):
2614 2613 node = repo.commit(text=message, user=user, date=date, extra=extra,
2615 2614 editor=editor)
2616 2615 if node is None:
2617 2616 ui.warn(
2618 2617 _('note: graft of %d:%s created no changes to commit\n') %
2619 2618 (ctx.rev(), ctx))
2620 2619 # checking that newnodes exist because old state files won't have it
2621 2620 elif statedata.get('newnodes') is not None:
2622 2621 statedata['newnodes'].append(node)
2623 2622
2624 2623 # remove state when we complete successfully
2625 2624 if not opts.get('dry_run'):
2626 2625 graftstate.delete()
2627 2626
2628 2627 return 0
2629 2628
2630 2629 def _abortgraft(ui, repo, graftstate):
2631 2630 """abort the interrupted graft and rollbacks to the state before interrupted
2632 2631 graft"""
2633 2632 if not graftstate.exists():
2634 2633 raise error.Abort(_("no interrupted graft to abort"))
2635 2634 statedata = _readgraftstate(repo, graftstate)
2636 2635 newnodes = statedata.get('newnodes')
2637 2636 if newnodes is None:
2638 2637 # and old graft state which does not have all the data required to abort
2639 2638 # the graft
2640 2639 raise error.Abort(_("cannot abort using an old graftstate"))
2641 2640
2642 2641 # changeset from which graft operation was started
2643 2642 if len(newnodes) > 0:
2644 2643 startctx = repo[newnodes[0]].p1()
2645 2644 else:
2646 2645 startctx = repo['.']
2647 2646 # whether to strip or not
2648 2647 cleanup = False
2649 2648 if newnodes:
2650 2649 newnodes = [repo[r].rev() for r in newnodes]
2651 2650 cleanup = True
2652 2651 # checking that none of the newnodes turned public or is public
2653 2652 immutable = [c for c in newnodes if not repo[c].mutable()]
2654 2653 if immutable:
2655 2654 repo.ui.warn(_("cannot clean up public changesets %s\n")
2656 2655 % ', '.join(bytes(repo[r]) for r in immutable),
2657 2656 hint=_("see 'hg help phases' for details"))
2658 2657 cleanup = False
2659 2658
2660 2659 # checking that no new nodes are created on top of grafted revs
2661 2660 desc = set(repo.changelog.descendants(newnodes))
2662 2661 if desc - set(newnodes):
2663 2662 repo.ui.warn(_("new changesets detected on destination "
2664 2663 "branch, can't strip\n"))
2665 2664 cleanup = False
2666 2665
2667 2666 if cleanup:
2668 2667 with repo.wlock(), repo.lock():
2669 2668 hg.updaterepo(repo, startctx.node(), overwrite=True)
2670 2669 # stripping the new nodes created
2671 2670 strippoints = [c.node() for c in repo.set("roots(%ld)",
2672 2671 newnodes)]
2673 2672 repair.strip(repo.ui, repo, strippoints, backup=False)
2674 2673
2675 2674 if not cleanup:
2676 2675 # we don't update to the startnode if we can't strip
2677 2676 startctx = repo['.']
2678 2677 hg.updaterepo(repo, startctx.node(), overwrite=True)
2679 2678
2680 2679 ui.status(_("graft aborted\n"))
2681 2680 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
2682 2681 graftstate.delete()
2683 2682 return 0
2684 2683
2685 2684 def _readgraftstate(repo, graftstate):
2686 2685 """read the graft state file and return a dict of the data stored in it"""
2687 2686 try:
2688 2687 return graftstate.read()
2689 2688 except error.CorruptedState:
2690 2689 nodes = repo.vfs.read('graftstate').splitlines()
2691 2690 return {'nodes': nodes}
2692 2691
2693 2692 def _stopgraft(ui, repo, graftstate):
2694 2693 """stop the interrupted graft"""
2695 2694 if not graftstate.exists():
2696 2695 raise error.Abort(_("no interrupted graft found"))
2697 2696 pctx = repo['.']
2698 2697 hg.updaterepo(repo, pctx.node(), overwrite=True)
2699 2698 graftstate.delete()
2700 2699 ui.status(_("stopped the interrupted graft\n"))
2701 2700 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2702 2701 return 0
2703 2702
2704 2703 @command('grep',
2705 2704 [('0', 'print0', None, _('end fields with NUL')),
2706 2705 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2707 2706 ('', 'diff', None, _('print all revisions when the term was introduced '
2708 2707 'or removed')),
2709 2708 ('a', 'text', None, _('treat all files as text')),
2710 2709 ('f', 'follow', None,
2711 2710 _('follow changeset history,'
2712 2711 ' or file history across copies and renames')),
2713 2712 ('i', 'ignore-case', None, _('ignore case when matching')),
2714 2713 ('l', 'files-with-matches', None,
2715 2714 _('print only filenames and revisions that match')),
2716 2715 ('n', 'line-number', None, _('print matching line numbers')),
2717 2716 ('r', 'rev', [],
2718 2717 _('only search files changed within revision range'), _('REV')),
2719 2718 ('', 'all-files', None,
2720 2719 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2721 2720 ('u', 'user', None, _('list the author (long with -v)')),
2722 2721 ('d', 'date', None, _('list the date (short with -q)')),
2723 2722 ] + formatteropts + walkopts,
2724 2723 _('[OPTION]... PATTERN [FILE]...'),
2725 2724 helpcategory=command.CATEGORY_FILE_CONTENTS,
2726 2725 inferrepo=True,
2727 2726 intents={INTENT_READONLY})
2728 2727 def grep(ui, repo, pattern, *pats, **opts):
2729 2728 """search revision history for a pattern in specified files
2730 2729
2731 2730 Search revision history for a regular expression in the specified
2732 2731 files or the entire project.
2733 2732
2734 2733 By default, grep prints the most recent revision number for each
2735 2734 file in which it finds a match. To get it to print every revision
2736 2735 that contains a change in match status ("-" for a match that becomes
2737 2736 a non-match, or "+" for a non-match that becomes a match), use the
2738 2737 --diff flag.
2739 2738
2740 2739 PATTERN can be any Python (roughly Perl-compatible) regular
2741 2740 expression.
2742 2741
2743 2742 If no FILEs are specified (and -f/--follow isn't set), all files in
2744 2743 the repository are searched, including those that don't exist in the
2745 2744 current branch or have been deleted in a prior changeset.
2746 2745
2747 2746 .. container:: verbose
2748 2747
2749 2748 Template:
2750 2749
2751 2750 The following keywords are supported in addition to the common template
2752 2751 keywords and functions. See also :hg:`help templates`.
2753 2752
2754 2753 :change: String. Character denoting insertion ``+`` or removal ``-``.
2755 2754 Available if ``--diff`` is specified.
2756 2755 :lineno: Integer. Line number of the match.
2757 2756 :path: String. Repository-absolute path of the file.
2758 2757 :texts: List of text chunks.
2759 2758
2760 2759 And each entry of ``{texts}`` provides the following sub-keywords.
2761 2760
2762 2761 :matched: Boolean. True if the chunk matches the specified pattern.
2763 2762 :text: String. Chunk content.
2764 2763
2765 2764 See :hg:`help templates.operators` for the list expansion syntax.
2766 2765
2767 2766 Returns 0 if a match is found, 1 otherwise.
2768 2767 """
2769 2768 opts = pycompat.byteskwargs(opts)
2770 2769 diff = opts.get('all') or opts.get('diff')
2771 2770 all_files = opts.get('all_files')
2772 2771 if diff and opts.get('all_files'):
2773 2772 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2774 2773 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2775 2774 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2776 2775 # experimental config: commands.grep.all-files
2777 2776 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2778 2777 plaingrep = opts.get('all_files') and not opts.get('rev')
2779 2778 if plaingrep:
2780 2779 opts['rev'] = ['wdir()']
2781 2780
2782 2781 reflags = re.M
2783 2782 if opts.get('ignore_case'):
2784 2783 reflags |= re.I
2785 2784 try:
2786 2785 regexp = util.re.compile(pattern, reflags)
2787 2786 except re.error as inst:
2788 2787 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2789 2788 return 1
2790 2789 sep, eol = ':', '\n'
2791 2790 if opts.get('print0'):
2792 2791 sep = eol = '\0'
2793 2792
2794 2793 getfile = util.lrucachefunc(repo.file)
2795 2794
2796 2795 def matchlines(body):
2797 2796 begin = 0
2798 2797 linenum = 0
2799 2798 while begin < len(body):
2800 2799 match = regexp.search(body, begin)
2801 2800 if not match:
2802 2801 break
2803 2802 mstart, mend = match.span()
2804 2803 linenum += body.count('\n', begin, mstart) + 1
2805 2804 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2806 2805 begin = body.find('\n', mend) + 1 or len(body) + 1
2807 2806 lend = begin - 1
2808 2807 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2809 2808
2810 2809 class linestate(object):
2811 2810 def __init__(self, line, linenum, colstart, colend):
2812 2811 self.line = line
2813 2812 self.linenum = linenum
2814 2813 self.colstart = colstart
2815 2814 self.colend = colend
2816 2815
2817 2816 def __hash__(self):
2818 2817 return hash((self.linenum, self.line))
2819 2818
2820 2819 def __eq__(self, other):
2821 2820 return self.line == other.line
2822 2821
2823 2822 def findpos(self):
2824 2823 """Iterate all (start, end) indices of matches"""
2825 2824 yield self.colstart, self.colend
2826 2825 p = self.colend
2827 2826 while p < len(self.line):
2828 2827 m = regexp.search(self.line, p)
2829 2828 if not m:
2830 2829 break
2831 2830 yield m.span()
2832 2831 p = m.end()
2833 2832
2834 2833 matches = {}
2835 2834 copies = {}
2836 2835 def grepbody(fn, rev, body):
2837 2836 matches[rev].setdefault(fn, [])
2838 2837 m = matches[rev][fn]
2839 2838 for lnum, cstart, cend, line in matchlines(body):
2840 2839 s = linestate(line, lnum, cstart, cend)
2841 2840 m.append(s)
2842 2841
2843 2842 def difflinestates(a, b):
2844 2843 sm = difflib.SequenceMatcher(None, a, b)
2845 2844 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2846 2845 if tag == r'insert':
2847 2846 for i in pycompat.xrange(blo, bhi):
2848 2847 yield ('+', b[i])
2849 2848 elif tag == r'delete':
2850 2849 for i in pycompat.xrange(alo, ahi):
2851 2850 yield ('-', a[i])
2852 2851 elif tag == r'replace':
2853 2852 for i in pycompat.xrange(alo, ahi):
2854 2853 yield ('-', a[i])
2855 2854 for i in pycompat.xrange(blo, bhi):
2856 2855 yield ('+', b[i])
2857 2856
2858 2857 uipathfn = scmutil.getuipathfn(repo)
2859 2858 def display(fm, fn, ctx, pstates, states):
2860 2859 rev = scmutil.intrev(ctx)
2861 2860 if fm.isplain():
2862 2861 formatuser = ui.shortuser
2863 2862 else:
2864 2863 formatuser = pycompat.bytestr
2865 2864 if ui.quiet:
2866 2865 datefmt = '%Y-%m-%d'
2867 2866 else:
2868 2867 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2869 2868 found = False
2870 2869 @util.cachefunc
2871 2870 def binary():
2872 2871 flog = getfile(fn)
2873 2872 try:
2874 2873 return stringutil.binary(flog.read(ctx.filenode(fn)))
2875 2874 except error.WdirUnsupported:
2876 2875 return ctx[fn].isbinary()
2877 2876
2878 2877 fieldnamemap = {'linenumber': 'lineno'}
2879 2878 if diff:
2880 2879 iter = difflinestates(pstates, states)
2881 2880 else:
2882 2881 iter = [('', l) for l in states]
2883 2882 for change, l in iter:
2884 2883 fm.startitem()
2885 2884 fm.context(ctx=ctx)
2886 2885 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
2887 2886 fm.plain(uipathfn(fn), label='grep.filename')
2888 2887
2889 2888 cols = [
2890 2889 ('rev', '%d', rev, not plaingrep, ''),
2891 2890 ('linenumber', '%d', l.linenum, opts.get('line_number'), ''),
2892 2891 ]
2893 2892 if diff:
2894 2893 cols.append(
2895 2894 ('change', '%s', change, True,
2896 2895 'grep.inserted ' if change == '+' else 'grep.deleted ')
2897 2896 )
2898 2897 cols.extend([
2899 2898 ('user', '%s', formatuser(ctx.user()), opts.get('user'), ''),
2900 2899 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2901 2900 opts.get('date'), ''),
2902 2901 ])
2903 2902 for name, fmt, data, cond, extra_label in cols:
2904 2903 if cond:
2905 2904 fm.plain(sep, label='grep.sep')
2906 2905 field = fieldnamemap.get(name, name)
2907 2906 label = extra_label + ('grep.%s' % name)
2908 2907 fm.condwrite(cond, field, fmt, data, label=label)
2909 2908 if not opts.get('files_with_matches'):
2910 2909 fm.plain(sep, label='grep.sep')
2911 2910 if not opts.get('text') and binary():
2912 2911 fm.plain(_(" Binary file matches"))
2913 2912 else:
2914 2913 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2915 2914 fm.plain(eol)
2916 2915 found = True
2917 2916 if opts.get('files_with_matches'):
2918 2917 break
2919 2918 return found
2920 2919
2921 2920 def displaymatches(fm, l):
2922 2921 p = 0
2923 2922 for s, e in l.findpos():
2924 2923 if p < s:
2925 2924 fm.startitem()
2926 2925 fm.write('text', '%s', l.line[p:s])
2927 2926 fm.data(matched=False)
2928 2927 fm.startitem()
2929 2928 fm.write('text', '%s', l.line[s:e], label='grep.match')
2930 2929 fm.data(matched=True)
2931 2930 p = e
2932 2931 if p < len(l.line):
2933 2932 fm.startitem()
2934 2933 fm.write('text', '%s', l.line[p:])
2935 2934 fm.data(matched=False)
2936 2935 fm.end()
2937 2936
2938 2937 skip = set()
2939 2938 revfiles = {}
2940 2939 match = scmutil.match(repo[None], pats, opts)
2941 2940 found = False
2942 2941 follow = opts.get('follow')
2943 2942
2944 2943 def prep(ctx, fns):
2945 2944 rev = ctx.rev()
2946 2945 pctx = ctx.p1()
2947 2946 parent = pctx.rev()
2948 2947 matches.setdefault(rev, {})
2949 2948 matches.setdefault(parent, {})
2950 2949 files = revfiles.setdefault(rev, [])
2951 2950 for fn in fns:
2952 2951 flog = getfile(fn)
2953 2952 try:
2954 2953 fnode = ctx.filenode(fn)
2955 2954 except error.LookupError:
2956 2955 continue
2957 2956 copy = None
2958 2957 if follow:
2959 2958 try:
2960 2959 copied = flog.renamed(fnode)
2961 2960 except error.WdirUnsupported:
2962 2961 copied = ctx[fn].renamed()
2963 2962 copy = copied and copied[0]
2964 2963 if copy:
2965 2964 copies.setdefault(rev, {})[fn] = copy
2966 2965 if fn in skip:
2967 2966 skip.add(copy)
2968 2967 if fn in skip:
2969 2968 continue
2970 2969 files.append(fn)
2971 2970
2972 2971 if fn not in matches[rev]:
2973 2972 try:
2974 2973 content = flog.read(fnode)
2975 2974 except error.WdirUnsupported:
2976 2975 content = ctx[fn].data()
2977 2976 grepbody(fn, rev, content)
2978 2977
2979 2978 pfn = copy or fn
2980 2979 if pfn not in matches[parent]:
2981 2980 try:
2982 2981 fnode = pctx.filenode(pfn)
2983 2982 grepbody(pfn, parent, flog.read(fnode))
2984 2983 except error.LookupError:
2985 2984 pass
2986 2985
2987 2986 ui.pager('grep')
2988 2987 fm = ui.formatter('grep', opts)
2989 2988 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2990 2989 rev = ctx.rev()
2991 2990 parent = ctx.p1().rev()
2992 2991 for fn in sorted(revfiles.get(rev, [])):
2993 2992 states = matches[rev][fn]
2994 2993 copy = copies.get(rev, {}).get(fn)
2995 2994 if fn in skip:
2996 2995 if copy:
2997 2996 skip.add(copy)
2998 2997 continue
2999 2998 pstates = matches.get(parent, {}).get(copy or fn, [])
3000 2999 if pstates or states:
3001 3000 r = display(fm, fn, ctx, pstates, states)
3002 3001 found = found or r
3003 3002 if r and not diff and not all_files:
3004 3003 skip.add(fn)
3005 3004 if copy:
3006 3005 skip.add(copy)
3007 3006 del revfiles[rev]
3008 3007 # We will keep the matches dict for the duration of the window
3009 3008 # clear the matches dict once the window is over
3010 3009 if not revfiles:
3011 3010 matches.clear()
3012 3011 fm.end()
3013 3012
3014 3013 return not found
3015 3014
3016 3015 @command('heads',
3017 3016 [('r', 'rev', '',
3018 3017 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3019 3018 ('t', 'topo', False, _('show topological heads only')),
3020 3019 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3021 3020 ('c', 'closed', False, _('show normal and closed branch heads')),
3022 3021 ] + templateopts,
3023 3022 _('[-ct] [-r STARTREV] [REV]...'),
3024 3023 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3025 3024 intents={INTENT_READONLY})
3026 3025 def heads(ui, repo, *branchrevs, **opts):
3027 3026 """show branch heads
3028 3027
3029 3028 With no arguments, show all open branch heads in the repository.
3030 3029 Branch heads are changesets that have no descendants on the
3031 3030 same branch. They are where development generally takes place and
3032 3031 are the usual targets for update and merge operations.
3033 3032
3034 3033 If one or more REVs are given, only open branch heads on the
3035 3034 branches associated with the specified changesets are shown. This
3036 3035 means that you can use :hg:`heads .` to see the heads on the
3037 3036 currently checked-out branch.
3038 3037
3039 3038 If -c/--closed is specified, also show branch heads marked closed
3040 3039 (see :hg:`commit --close-branch`).
3041 3040
3042 3041 If STARTREV is specified, only those heads that are descendants of
3043 3042 STARTREV will be displayed.
3044 3043
3045 3044 If -t/--topo is specified, named branch mechanics will be ignored and only
3046 3045 topological heads (changesets with no children) will be shown.
3047 3046
3048 3047 Returns 0 if matching heads are found, 1 if not.
3049 3048 """
3050 3049
3051 3050 opts = pycompat.byteskwargs(opts)
3052 3051 start = None
3053 3052 rev = opts.get('rev')
3054 3053 if rev:
3055 3054 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3056 3055 start = scmutil.revsingle(repo, rev, None).node()
3057 3056
3058 3057 if opts.get('topo'):
3059 3058 heads = [repo[h] for h in repo.heads(start)]
3060 3059 else:
3061 3060 heads = []
3062 3061 for branch in repo.branchmap():
3063 3062 heads += repo.branchheads(branch, start, opts.get('closed'))
3064 3063 heads = [repo[h] for h in heads]
3065 3064
3066 3065 if branchrevs:
3067 3066 branches = set(repo[r].branch()
3068 3067 for r in scmutil.revrange(repo, branchrevs))
3069 3068 heads = [h for h in heads if h.branch() in branches]
3070 3069
3071 3070 if opts.get('active') and branchrevs:
3072 3071 dagheads = repo.heads(start)
3073 3072 heads = [h for h in heads if h.node() in dagheads]
3074 3073
3075 3074 if branchrevs:
3076 3075 haveheads = set(h.branch() for h in heads)
3077 3076 if branches - haveheads:
3078 3077 headless = ', '.join(b for b in branches - haveheads)
3079 3078 msg = _('no open branch heads found on branches %s')
3080 3079 if opts.get('rev'):
3081 3080 msg += _(' (started at %s)') % opts['rev']
3082 3081 ui.warn((msg + '\n') % headless)
3083 3082
3084 3083 if not heads:
3085 3084 return 1
3086 3085
3087 3086 ui.pager('heads')
3088 3087 heads = sorted(heads, key=lambda x: -x.rev())
3089 3088 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3090 3089 for ctx in heads:
3091 3090 displayer.show(ctx)
3092 3091 displayer.close()
3093 3092
3094 3093 @command('help',
3095 3094 [('e', 'extension', None, _('show only help for extensions')),
3096 3095 ('c', 'command', None, _('show only help for commands')),
3097 3096 ('k', 'keyword', None, _('show topics matching keyword')),
3098 3097 ('s', 'system', [],
3099 3098 _('show help for specific platform(s)'), _('PLATFORM')),
3100 3099 ],
3101 3100 _('[-eck] [-s PLATFORM] [TOPIC]'),
3102 3101 helpcategory=command.CATEGORY_HELP,
3103 3102 norepo=True,
3104 3103 intents={INTENT_READONLY})
3105 3104 def help_(ui, name=None, **opts):
3106 3105 """show help for a given topic or a help overview
3107 3106
3108 3107 With no arguments, print a list of commands with short help messages.
3109 3108
3110 3109 Given a topic, extension, or command name, print help for that
3111 3110 topic.
3112 3111
3113 3112 Returns 0 if successful.
3114 3113 """
3115 3114
3116 3115 keep = opts.get(r'system') or []
3117 3116 if len(keep) == 0:
3118 3117 if pycompat.sysplatform.startswith('win'):
3119 3118 keep.append('windows')
3120 3119 elif pycompat.sysplatform == 'OpenVMS':
3121 3120 keep.append('vms')
3122 3121 elif pycompat.sysplatform == 'plan9':
3123 3122 keep.append('plan9')
3124 3123 else:
3125 3124 keep.append('unix')
3126 3125 keep.append(pycompat.sysplatform.lower())
3127 3126 if ui.verbose:
3128 3127 keep.append('verbose')
3129 3128
3130 3129 commands = sys.modules[__name__]
3131 3130 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3132 3131 ui.pager('help')
3133 3132 ui.write(formatted)
3134 3133
3135 3134
3136 3135 @command('identify|id',
3137 3136 [('r', 'rev', '',
3138 3137 _('identify the specified revision'), _('REV')),
3139 3138 ('n', 'num', None, _('show local revision number')),
3140 3139 ('i', 'id', None, _('show global revision id')),
3141 3140 ('b', 'branch', None, _('show branch')),
3142 3141 ('t', 'tags', None, _('show tags')),
3143 3142 ('B', 'bookmarks', None, _('show bookmarks')),
3144 3143 ] + remoteopts + formatteropts,
3145 3144 _('[-nibtB] [-r REV] [SOURCE]'),
3146 3145 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3147 3146 optionalrepo=True,
3148 3147 intents={INTENT_READONLY})
3149 3148 def identify(ui, repo, source=None, rev=None,
3150 3149 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3151 3150 """identify the working directory or specified revision
3152 3151
3153 3152 Print a summary identifying the repository state at REV using one or
3154 3153 two parent hash identifiers, followed by a "+" if the working
3155 3154 directory has uncommitted changes, the branch name (if not default),
3156 3155 a list of tags, and a list of bookmarks.
3157 3156
3158 3157 When REV is not given, print a summary of the current state of the
3159 3158 repository including the working directory. Specify -r. to get information
3160 3159 of the working directory parent without scanning uncommitted changes.
3161 3160
3162 3161 Specifying a path to a repository root or Mercurial bundle will
3163 3162 cause lookup to operate on that repository/bundle.
3164 3163
3165 3164 .. container:: verbose
3166 3165
3167 3166 Template:
3168 3167
3169 3168 The following keywords are supported in addition to the common template
3170 3169 keywords and functions. See also :hg:`help templates`.
3171 3170
3172 3171 :dirty: String. Character ``+`` denoting if the working directory has
3173 3172 uncommitted changes.
3174 3173 :id: String. One or two nodes, optionally followed by ``+``.
3175 3174 :parents: List of strings. Parent nodes of the changeset.
3176 3175
3177 3176 Examples:
3178 3177
3179 3178 - generate a build identifier for the working directory::
3180 3179
3181 3180 hg id --id > build-id.dat
3182 3181
3183 3182 - find the revision corresponding to a tag::
3184 3183
3185 3184 hg id -n -r 1.3
3186 3185
3187 3186 - check the most recent revision of a remote repository::
3188 3187
3189 3188 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3190 3189
3191 3190 See :hg:`log` for generating more information about specific revisions,
3192 3191 including full hash identifiers.
3193 3192
3194 3193 Returns 0 if successful.
3195 3194 """
3196 3195
3197 3196 opts = pycompat.byteskwargs(opts)
3198 3197 if not repo and not source:
3199 3198 raise error.Abort(_("there is no Mercurial repository here "
3200 3199 "(.hg not found)"))
3201 3200
3202 3201 default = not (num or id or branch or tags or bookmarks)
3203 3202 output = []
3204 3203 revs = []
3205 3204
3206 3205 if source:
3207 3206 source, branches = hg.parseurl(ui.expandpath(source))
3208 3207 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3209 3208 repo = peer.local()
3210 3209 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3211 3210
3212 3211 fm = ui.formatter('identify', opts)
3213 3212 fm.startitem()
3214 3213
3215 3214 if not repo:
3216 3215 if num or branch or tags:
3217 3216 raise error.Abort(
3218 3217 _("can't query remote revision number, branch, or tags"))
3219 3218 if not rev and revs:
3220 3219 rev = revs[0]
3221 3220 if not rev:
3222 3221 rev = "tip"
3223 3222
3224 3223 remoterev = peer.lookup(rev)
3225 3224 hexrev = fm.hexfunc(remoterev)
3226 3225 if default or id:
3227 3226 output = [hexrev]
3228 3227 fm.data(id=hexrev)
3229 3228
3230 3229 @util.cachefunc
3231 3230 def getbms():
3232 3231 bms = []
3233 3232
3234 3233 if 'bookmarks' in peer.listkeys('namespaces'):
3235 3234 hexremoterev = hex(remoterev)
3236 3235 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3237 3236 if bmr == hexremoterev]
3238 3237
3239 3238 return sorted(bms)
3240 3239
3241 3240 if fm.isplain():
3242 3241 if bookmarks:
3243 3242 output.extend(getbms())
3244 3243 elif default and not ui.quiet:
3245 3244 # multiple bookmarks for a single parent separated by '/'
3246 3245 bm = '/'.join(getbms())
3247 3246 if bm:
3248 3247 output.append(bm)
3249 3248 else:
3250 3249 fm.data(node=hex(remoterev))
3251 3250 if bookmarks or 'bookmarks' in fm.datahint():
3252 3251 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3253 3252 else:
3254 3253 if rev:
3255 3254 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3256 3255 ctx = scmutil.revsingle(repo, rev, None)
3257 3256
3258 3257 if ctx.rev() is None:
3259 3258 ctx = repo[None]
3260 3259 parents = ctx.parents()
3261 3260 taglist = []
3262 3261 for p in parents:
3263 3262 taglist.extend(p.tags())
3264 3263
3265 3264 dirty = ""
3266 3265 if ctx.dirty(missing=True, merge=False, branch=False):
3267 3266 dirty = '+'
3268 3267 fm.data(dirty=dirty)
3269 3268
3270 3269 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3271 3270 if default or id:
3272 3271 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3273 3272 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3274 3273
3275 3274 if num:
3276 3275 numoutput = ["%d" % p.rev() for p in parents]
3277 3276 output.append("%s%s" % ('+'.join(numoutput), dirty))
3278 3277
3279 3278 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3280 3279 for p in parents], name='node'))
3281 3280 else:
3282 3281 hexoutput = fm.hexfunc(ctx.node())
3283 3282 if default or id:
3284 3283 output = [hexoutput]
3285 3284 fm.data(id=hexoutput)
3286 3285
3287 3286 if num:
3288 3287 output.append(pycompat.bytestr(ctx.rev()))
3289 3288 taglist = ctx.tags()
3290 3289
3291 3290 if default and not ui.quiet:
3292 3291 b = ctx.branch()
3293 3292 if b != 'default':
3294 3293 output.append("(%s)" % b)
3295 3294
3296 3295 # multiple tags for a single parent separated by '/'
3297 3296 t = '/'.join(taglist)
3298 3297 if t:
3299 3298 output.append(t)
3300 3299
3301 3300 # multiple bookmarks for a single parent separated by '/'
3302 3301 bm = '/'.join(ctx.bookmarks())
3303 3302 if bm:
3304 3303 output.append(bm)
3305 3304 else:
3306 3305 if branch:
3307 3306 output.append(ctx.branch())
3308 3307
3309 3308 if tags:
3310 3309 output.extend(taglist)
3311 3310
3312 3311 if bookmarks:
3313 3312 output.extend(ctx.bookmarks())
3314 3313
3315 3314 fm.data(node=ctx.hex())
3316 3315 fm.data(branch=ctx.branch())
3317 3316 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3318 3317 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3319 3318 fm.context(ctx=ctx)
3320 3319
3321 3320 fm.plain("%s\n" % ' '.join(output))
3322 3321 fm.end()
3323 3322
3324 3323 @command('import|patch',
3325 3324 [('p', 'strip', 1,
3326 3325 _('directory strip option for patch. This has the same '
3327 3326 'meaning as the corresponding patch option'), _('NUM')),
3328 3327 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3329 3328 ('e', 'edit', False, _('invoke editor on commit messages')),
3330 3329 ('f', 'force', None,
3331 3330 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3332 3331 ('', 'no-commit', None,
3333 3332 _("don't commit, just update the working directory")),
3334 3333 ('', 'bypass', None,
3335 3334 _("apply patch without touching the working directory")),
3336 3335 ('', 'partial', None,
3337 3336 _('commit even if some hunks fail')),
3338 3337 ('', 'exact', None,
3339 3338 _('abort if patch would apply lossily')),
3340 3339 ('', 'prefix', '',
3341 3340 _('apply patch to subdirectory'), _('DIR')),
3342 3341 ('', 'import-branch', None,
3343 3342 _('use any branch information in patch (implied by --exact)'))] +
3344 3343 commitopts + commitopts2 + similarityopts,
3345 3344 _('[OPTION]... PATCH...'),
3346 3345 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3347 3346 def import_(ui, repo, patch1=None, *patches, **opts):
3348 3347 """import an ordered set of patches
3349 3348
3350 3349 Import a list of patches and commit them individually (unless
3351 3350 --no-commit is specified).
3352 3351
3353 3352 To read a patch from standard input (stdin), use "-" as the patch
3354 3353 name. If a URL is specified, the patch will be downloaded from
3355 3354 there.
3356 3355
3357 3356 Import first applies changes to the working directory (unless
3358 3357 --bypass is specified), import will abort if there are outstanding
3359 3358 changes.
3360 3359
3361 3360 Use --bypass to apply and commit patches directly to the
3362 3361 repository, without affecting the working directory. Without
3363 3362 --exact, patches will be applied on top of the working directory
3364 3363 parent revision.
3365 3364
3366 3365 You can import a patch straight from a mail message. Even patches
3367 3366 as attachments work (to use the body part, it must have type
3368 3367 text/plain or text/x-patch). From and Subject headers of email
3369 3368 message are used as default committer and commit message. All
3370 3369 text/plain body parts before first diff are added to the commit
3371 3370 message.
3372 3371
3373 3372 If the imported patch was generated by :hg:`export`, user and
3374 3373 description from patch override values from message headers and
3375 3374 body. Values given on command line with -m/--message and -u/--user
3376 3375 override these.
3377 3376
3378 3377 If --exact is specified, import will set the working directory to
3379 3378 the parent of each patch before applying it, and will abort if the
3380 3379 resulting changeset has a different ID than the one recorded in
3381 3380 the patch. This will guard against various ways that portable
3382 3381 patch formats and mail systems might fail to transfer Mercurial
3383 3382 data or metadata. See :hg:`bundle` for lossless transmission.
3384 3383
3385 3384 Use --partial to ensure a changeset will be created from the patch
3386 3385 even if some hunks fail to apply. Hunks that fail to apply will be
3387 3386 written to a <target-file>.rej file. Conflicts can then be resolved
3388 3387 by hand before :hg:`commit --amend` is run to update the created
3389 3388 changeset. This flag exists to let people import patches that
3390 3389 partially apply without losing the associated metadata (author,
3391 3390 date, description, ...).
3392 3391
3393 3392 .. note::
3394 3393
3395 3394 When no hunks apply cleanly, :hg:`import --partial` will create
3396 3395 an empty changeset, importing only the patch metadata.
3397 3396
3398 3397 With -s/--similarity, hg will attempt to discover renames and
3399 3398 copies in the patch in the same way as :hg:`addremove`.
3400 3399
3401 3400 It is possible to use external patch programs to perform the patch
3402 3401 by setting the ``ui.patch`` configuration option. For the default
3403 3402 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3404 3403 See :hg:`help config` for more information about configuration
3405 3404 files and how to use these options.
3406 3405
3407 3406 See :hg:`help dates` for a list of formats valid for -d/--date.
3408 3407
3409 3408 .. container:: verbose
3410 3409
3411 3410 Examples:
3412 3411
3413 3412 - import a traditional patch from a website and detect renames::
3414 3413
3415 3414 hg import -s 80 http://example.com/bugfix.patch
3416 3415
3417 3416 - import a changeset from an hgweb server::
3418 3417
3419 3418 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3420 3419
3421 3420 - import all the patches in an Unix-style mbox::
3422 3421
3423 3422 hg import incoming-patches.mbox
3424 3423
3425 3424 - import patches from stdin::
3426 3425
3427 3426 hg import -
3428 3427
3429 3428 - attempt to exactly restore an exported changeset (not always
3430 3429 possible)::
3431 3430
3432 3431 hg import --exact proposed-fix.patch
3433 3432
3434 3433 - use an external tool to apply a patch which is too fuzzy for
3435 3434 the default internal tool.
3436 3435
3437 3436 hg import --config ui.patch="patch --merge" fuzzy.patch
3438 3437
3439 3438 - change the default fuzzing from 2 to a less strict 7
3440 3439
3441 3440 hg import --config ui.fuzz=7 fuzz.patch
3442 3441
3443 3442 Returns 0 on success, 1 on partial success (see --partial).
3444 3443 """
3445 3444
3446 3445 opts = pycompat.byteskwargs(opts)
3447 3446 if not patch1:
3448 3447 raise error.Abort(_('need at least one patch to import'))
3449 3448
3450 3449 patches = (patch1,) + patches
3451 3450
3452 3451 date = opts.get('date')
3453 3452 if date:
3454 3453 opts['date'] = dateutil.parsedate(date)
3455 3454
3456 3455 exact = opts.get('exact')
3457 3456 update = not opts.get('bypass')
3458 3457 if not update and opts.get('no_commit'):
3459 3458 raise error.Abort(_('cannot use --no-commit with --bypass'))
3460 3459 try:
3461 3460 sim = float(opts.get('similarity') or 0)
3462 3461 except ValueError:
3463 3462 raise error.Abort(_('similarity must be a number'))
3464 3463 if sim < 0 or sim > 100:
3465 3464 raise error.Abort(_('similarity must be between 0 and 100'))
3466 3465 if sim and not update:
3467 3466 raise error.Abort(_('cannot use --similarity with --bypass'))
3468 3467 if exact:
3469 3468 if opts.get('edit'):
3470 3469 raise error.Abort(_('cannot use --exact with --edit'))
3471 3470 if opts.get('prefix'):
3472 3471 raise error.Abort(_('cannot use --exact with --prefix'))
3473 3472
3474 3473 base = opts["base"]
3475 3474 msgs = []
3476 3475 ret = 0
3477 3476
3478 3477 with repo.wlock():
3479 3478 if update:
3480 3479 cmdutil.checkunfinished(repo)
3481 3480 if (exact or not opts.get('force')):
3482 3481 cmdutil.bailifchanged(repo)
3483 3482
3484 3483 if not opts.get('no_commit'):
3485 3484 lock = repo.lock
3486 3485 tr = lambda: repo.transaction('import')
3487 3486 dsguard = util.nullcontextmanager
3488 3487 else:
3489 3488 lock = util.nullcontextmanager
3490 3489 tr = util.nullcontextmanager
3491 3490 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3492 3491 with lock(), tr(), dsguard():
3493 3492 parents = repo[None].parents()
3494 3493 for patchurl in patches:
3495 3494 if patchurl == '-':
3496 3495 ui.status(_('applying patch from stdin\n'))
3497 3496 patchfile = ui.fin
3498 3497 patchurl = 'stdin' # for error message
3499 3498 else:
3500 3499 patchurl = os.path.join(base, patchurl)
3501 3500 ui.status(_('applying %s\n') % patchurl)
3502 3501 patchfile = hg.openpath(ui, patchurl)
3503 3502
3504 3503 haspatch = False
3505 3504 for hunk in patch.split(patchfile):
3506 3505 with patch.extract(ui, hunk) as patchdata:
3507 3506 msg, node, rej = cmdutil.tryimportone(ui, repo,
3508 3507 patchdata,
3509 3508 parents, opts,
3510 3509 msgs, hg.clean)
3511 3510 if msg:
3512 3511 haspatch = True
3513 3512 ui.note(msg + '\n')
3514 3513 if update or exact:
3515 3514 parents = repo[None].parents()
3516 3515 else:
3517 3516 parents = [repo[node]]
3518 3517 if rej:
3519 3518 ui.write_err(_("patch applied partially\n"))
3520 3519 ui.write_err(_("(fix the .rej files and run "
3521 3520 "`hg commit --amend`)\n"))
3522 3521 ret = 1
3523 3522 break
3524 3523
3525 3524 if not haspatch:
3526 3525 raise error.Abort(_('%s: no diffs found') % patchurl)
3527 3526
3528 3527 if msgs:
3529 3528 repo.savecommitmessage('\n* * *\n'.join(msgs))
3530 3529 return ret
3531 3530
3532 3531 @command('incoming|in',
3533 3532 [('f', 'force', None,
3534 3533 _('run even if remote repository is unrelated')),
3535 3534 ('n', 'newest-first', None, _('show newest record first')),
3536 3535 ('', 'bundle', '',
3537 3536 _('file to store the bundles into'), _('FILE')),
3538 3537 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3539 3538 ('B', 'bookmarks', False, _("compare bookmarks")),
3540 3539 ('b', 'branch', [],
3541 3540 _('a specific branch you would like to pull'), _('BRANCH')),
3542 3541 ] + logopts + remoteopts + subrepoopts,
3543 3542 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3544 3543 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3545 3544 def incoming(ui, repo, source="default", **opts):
3546 3545 """show new changesets found in source
3547 3546
3548 3547 Show new changesets found in the specified path/URL or the default
3549 3548 pull location. These are the changesets that would have been pulled
3550 3549 by :hg:`pull` at the time you issued this command.
3551 3550
3552 3551 See pull for valid source format details.
3553 3552
3554 3553 .. container:: verbose
3555 3554
3556 3555 With -B/--bookmarks, the result of bookmark comparison between
3557 3556 local and remote repositories is displayed. With -v/--verbose,
3558 3557 status is also displayed for each bookmark like below::
3559 3558
3560 3559 BM1 01234567890a added
3561 3560 BM2 1234567890ab advanced
3562 3561 BM3 234567890abc diverged
3563 3562 BM4 34567890abcd changed
3564 3563
3565 3564 The action taken locally when pulling depends on the
3566 3565 status of each bookmark:
3567 3566
3568 3567 :``added``: pull will create it
3569 3568 :``advanced``: pull will update it
3570 3569 :``diverged``: pull will create a divergent bookmark
3571 3570 :``changed``: result depends on remote changesets
3572 3571
3573 3572 From the point of view of pulling behavior, bookmark
3574 3573 existing only in the remote repository are treated as ``added``,
3575 3574 even if it is in fact locally deleted.
3576 3575
3577 3576 .. container:: verbose
3578 3577
3579 3578 For remote repository, using --bundle avoids downloading the
3580 3579 changesets twice if the incoming is followed by a pull.
3581 3580
3582 3581 Examples:
3583 3582
3584 3583 - show incoming changes with patches and full description::
3585 3584
3586 3585 hg incoming -vp
3587 3586
3588 3587 - show incoming changes excluding merges, store a bundle::
3589 3588
3590 3589 hg in -vpM --bundle incoming.hg
3591 3590 hg pull incoming.hg
3592 3591
3593 3592 - briefly list changes inside a bundle::
3594 3593
3595 3594 hg in changes.hg -T "{desc|firstline}\\n"
3596 3595
3597 3596 Returns 0 if there are incoming changes, 1 otherwise.
3598 3597 """
3599 3598 opts = pycompat.byteskwargs(opts)
3600 3599 if opts.get('graph'):
3601 3600 logcmdutil.checkunsupportedgraphflags([], opts)
3602 3601 def display(other, chlist, displayer):
3603 3602 revdag = logcmdutil.graphrevs(other, chlist, opts)
3604 3603 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3605 3604 graphmod.asciiedges)
3606 3605
3607 3606 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3608 3607 return 0
3609 3608
3610 3609 if opts.get('bundle') and opts.get('subrepos'):
3611 3610 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3612 3611
3613 3612 if opts.get('bookmarks'):
3614 3613 source, branches = hg.parseurl(ui.expandpath(source),
3615 3614 opts.get('branch'))
3616 3615 other = hg.peer(repo, opts, source)
3617 3616 if 'bookmarks' not in other.listkeys('namespaces'):
3618 3617 ui.warn(_("remote doesn't support bookmarks\n"))
3619 3618 return 0
3620 3619 ui.pager('incoming')
3621 3620 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3622 3621 return bookmarks.incoming(ui, repo, other)
3623 3622
3624 3623 repo._subtoppath = ui.expandpath(source)
3625 3624 try:
3626 3625 return hg.incoming(ui, repo, source, opts)
3627 3626 finally:
3628 3627 del repo._subtoppath
3629 3628
3630 3629
3631 3630 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3632 3631 helpcategory=command.CATEGORY_REPO_CREATION,
3633 3632 helpbasic=True, norepo=True)
3634 3633 def init(ui, dest=".", **opts):
3635 3634 """create a new repository in the given directory
3636 3635
3637 3636 Initialize a new repository in the given directory. If the given
3638 3637 directory does not exist, it will be created.
3639 3638
3640 3639 If no directory is given, the current directory is used.
3641 3640
3642 3641 It is possible to specify an ``ssh://`` URL as the destination.
3643 3642 See :hg:`help urls` for more information.
3644 3643
3645 3644 Returns 0 on success.
3646 3645 """
3647 3646 opts = pycompat.byteskwargs(opts)
3648 3647 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3649 3648
3650 3649 @command('locate',
3651 3650 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3652 3651 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3653 3652 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3654 3653 ] + walkopts,
3655 3654 _('[OPTION]... [PATTERN]...'),
3656 3655 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3657 3656 def locate(ui, repo, *pats, **opts):
3658 3657 """locate files matching specific patterns (DEPRECATED)
3659 3658
3660 3659 Print files under Mercurial control in the working directory whose
3661 3660 names match the given patterns.
3662 3661
3663 3662 By default, this command searches all directories in the working
3664 3663 directory. To search just the current directory and its
3665 3664 subdirectories, use "--include .".
3666 3665
3667 3666 If no patterns are given to match, this command prints the names
3668 3667 of all files under Mercurial control in the working directory.
3669 3668
3670 3669 If you want to feed the output of this command into the "xargs"
3671 3670 command, use the -0 option to both this command and "xargs". This
3672 3671 will avoid the problem of "xargs" treating single filenames that
3673 3672 contain whitespace as multiple filenames.
3674 3673
3675 3674 See :hg:`help files` for a more versatile command.
3676 3675
3677 3676 Returns 0 if a match is found, 1 otherwise.
3678 3677 """
3679 3678 opts = pycompat.byteskwargs(opts)
3680 3679 if opts.get('print0'):
3681 3680 end = '\0'
3682 3681 else:
3683 3682 end = '\n'
3684 3683 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3685 3684
3686 3685 ret = 1
3687 3686 m = scmutil.match(ctx, pats, opts, default='relglob',
3688 3687 badfn=lambda x, y: False)
3689 3688
3690 3689 ui.pager('locate')
3691 3690 if ctx.rev() is None:
3692 3691 # When run on the working copy, "locate" includes removed files, so
3693 3692 # we get the list of files from the dirstate.
3694 3693 filesgen = sorted(repo.dirstate.matches(m))
3695 3694 else:
3696 3695 filesgen = ctx.matches(m)
3697 3696 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
3698 3697 for abs in filesgen:
3699 3698 if opts.get('fullpath'):
3700 3699 ui.write(repo.wjoin(abs), end)
3701 3700 else:
3702 3701 ui.write(uipathfn(abs), end)
3703 3702 ret = 0
3704 3703
3705 3704 return ret
3706 3705
3707 3706 @command('log|history',
3708 3707 [('f', 'follow', None,
3709 3708 _('follow changeset history, or file history across copies and renames')),
3710 3709 ('', 'follow-first', None,
3711 3710 _('only follow the first parent of merge changesets (DEPRECATED)')),
3712 3711 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3713 3712 ('C', 'copies', None, _('show copied files')),
3714 3713 ('k', 'keyword', [],
3715 3714 _('do case-insensitive search for a given text'), _('TEXT')),
3716 3715 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3717 3716 ('L', 'line-range', [],
3718 3717 _('follow line range of specified file (EXPERIMENTAL)'),
3719 3718 _('FILE,RANGE')),
3720 3719 ('', 'removed', None, _('include revisions where files were removed')),
3721 3720 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3722 3721 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3723 3722 ('', 'only-branch', [],
3724 3723 _('show only changesets within the given named branch (DEPRECATED)'),
3725 3724 _('BRANCH')),
3726 3725 ('b', 'branch', [],
3727 3726 _('show changesets within the given named branch'), _('BRANCH')),
3728 3727 ('P', 'prune', [],
3729 3728 _('do not display revision or any of its ancestors'), _('REV')),
3730 3729 ] + logopts + walkopts,
3731 3730 _('[OPTION]... [FILE]'),
3732 3731 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3733 3732 helpbasic=True, inferrepo=True,
3734 3733 intents={INTENT_READONLY})
3735 3734 def log(ui, repo, *pats, **opts):
3736 3735 """show revision history of entire repository or files
3737 3736
3738 3737 Print the revision history of the specified files or the entire
3739 3738 project.
3740 3739
3741 3740 If no revision range is specified, the default is ``tip:0`` unless
3742 3741 --follow is set, in which case the working directory parent is
3743 3742 used as the starting revision.
3744 3743
3745 3744 File history is shown without following rename or copy history of
3746 3745 files. Use -f/--follow with a filename to follow history across
3747 3746 renames and copies. --follow without a filename will only show
3748 3747 ancestors of the starting revision.
3749 3748
3750 3749 By default this command prints revision number and changeset id,
3751 3750 tags, non-trivial parents, user, date and time, and a summary for
3752 3751 each commit. When the -v/--verbose switch is used, the list of
3753 3752 changed files and full commit message are shown.
3754 3753
3755 3754 With --graph the revisions are shown as an ASCII art DAG with the most
3756 3755 recent changeset at the top.
3757 3756 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3758 3757 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3759 3758 changeset from the lines below is a parent of the 'o' merge on the same
3760 3759 line.
3761 3760 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3762 3761 of a '|' indicates one or more revisions in a path are omitted.
3763 3762
3764 3763 .. container:: verbose
3765 3764
3766 3765 Use -L/--line-range FILE,M:N options to follow the history of lines
3767 3766 from M to N in FILE. With -p/--patch only diff hunks affecting
3768 3767 specified line range will be shown. This option requires --follow;
3769 3768 it can be specified multiple times. Currently, this option is not
3770 3769 compatible with --graph. This option is experimental.
3771 3770
3772 3771 .. note::
3773 3772
3774 3773 :hg:`log --patch` may generate unexpected diff output for merge
3775 3774 changesets, as it will only compare the merge changeset against
3776 3775 its first parent. Also, only files different from BOTH parents
3777 3776 will appear in files:.
3778 3777
3779 3778 .. note::
3780 3779
3781 3780 For performance reasons, :hg:`log FILE` may omit duplicate changes
3782 3781 made on branches and will not show removals or mode changes. To
3783 3782 see all such changes, use the --removed switch.
3784 3783
3785 3784 .. container:: verbose
3786 3785
3787 3786 .. note::
3788 3787
3789 3788 The history resulting from -L/--line-range options depends on diff
3790 3789 options; for instance if white-spaces are ignored, respective changes
3791 3790 with only white-spaces in specified line range will not be listed.
3792 3791
3793 3792 .. container:: verbose
3794 3793
3795 3794 Some examples:
3796 3795
3797 3796 - changesets with full descriptions and file lists::
3798 3797
3799 3798 hg log -v
3800 3799
3801 3800 - changesets ancestral to the working directory::
3802 3801
3803 3802 hg log -f
3804 3803
3805 3804 - last 10 commits on the current branch::
3806 3805
3807 3806 hg log -l 10 -b .
3808 3807
3809 3808 - changesets showing all modifications of a file, including removals::
3810 3809
3811 3810 hg log --removed file.c
3812 3811
3813 3812 - all changesets that touch a directory, with diffs, excluding merges::
3814 3813
3815 3814 hg log -Mp lib/
3816 3815
3817 3816 - all revision numbers that match a keyword::
3818 3817
3819 3818 hg log -k bug --template "{rev}\\n"
3820 3819
3821 3820 - the full hash identifier of the working directory parent::
3822 3821
3823 3822 hg log -r . --template "{node}\\n"
3824 3823
3825 3824 - list available log templates::
3826 3825
3827 3826 hg log -T list
3828 3827
3829 3828 - check if a given changeset is included in a tagged release::
3830 3829
3831 3830 hg log -r "a21ccf and ancestor(1.9)"
3832 3831
3833 3832 - find all changesets by some user in a date range::
3834 3833
3835 3834 hg log -k alice -d "may 2008 to jul 2008"
3836 3835
3837 3836 - summary of all changesets after the last tag::
3838 3837
3839 3838 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3840 3839
3841 3840 - changesets touching lines 13 to 23 for file.c::
3842 3841
3843 3842 hg log -L file.c,13:23
3844 3843
3845 3844 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3846 3845 main.c with patch::
3847 3846
3848 3847 hg log -L file.c,13:23 -L main.c,2:6 -p
3849 3848
3850 3849 See :hg:`help dates` for a list of formats valid for -d/--date.
3851 3850
3852 3851 See :hg:`help revisions` for more about specifying and ordering
3853 3852 revisions.
3854 3853
3855 3854 See :hg:`help templates` for more about pre-packaged styles and
3856 3855 specifying custom templates. The default template used by the log
3857 3856 command can be customized via the ``ui.logtemplate`` configuration
3858 3857 setting.
3859 3858
3860 3859 Returns 0 on success.
3861 3860
3862 3861 """
3863 3862 opts = pycompat.byteskwargs(opts)
3864 3863 linerange = opts.get('line_range')
3865 3864
3866 3865 if linerange and not opts.get('follow'):
3867 3866 raise error.Abort(_('--line-range requires --follow'))
3868 3867
3869 3868 if linerange and pats:
3870 3869 # TODO: take pats as patterns with no line-range filter
3871 3870 raise error.Abort(
3872 3871 _('FILE arguments are not compatible with --line-range option')
3873 3872 )
3874 3873
3875 3874 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3876 3875 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3877 3876 if linerange:
3878 3877 # TODO: should follow file history from logcmdutil._initialrevs(),
3879 3878 # then filter the result by logcmdutil._makerevset() and --limit
3880 3879 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3881 3880
3882 3881 getrenamed = None
3883 3882 if opts.get('copies'):
3884 3883 endrev = None
3885 3884 if revs:
3886 3885 endrev = revs.max() + 1
3887 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3886 getrenamed = scmutil.getrenamedfn(repo, endrev=endrev)
3888 3887
3889 3888 ui.pager('log')
3890 3889 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3891 3890 buffered=True)
3892 3891 if opts.get('graph'):
3893 3892 displayfn = logcmdutil.displaygraphrevs
3894 3893 else:
3895 3894 displayfn = logcmdutil.displayrevs
3896 3895 displayfn(ui, repo, revs, displayer, getrenamed)
3897 3896
3898 3897 @command('manifest',
3899 3898 [('r', 'rev', '', _('revision to display'), _('REV')),
3900 3899 ('', 'all', False, _("list files from all revisions"))]
3901 3900 + formatteropts,
3902 3901 _('[-r REV]'),
3903 3902 helpcategory=command.CATEGORY_MAINTENANCE,
3904 3903 intents={INTENT_READONLY})
3905 3904 def manifest(ui, repo, node=None, rev=None, **opts):
3906 3905 """output the current or given revision of the project manifest
3907 3906
3908 3907 Print a list of version controlled files for the given revision.
3909 3908 If no revision is given, the first parent of the working directory
3910 3909 is used, or the null revision if no revision is checked out.
3911 3910
3912 3911 With -v, print file permissions, symlink and executable bits.
3913 3912 With --debug, print file revision hashes.
3914 3913
3915 3914 If option --all is specified, the list of all files from all revisions
3916 3915 is printed. This includes deleted and renamed files.
3917 3916
3918 3917 Returns 0 on success.
3919 3918 """
3920 3919 opts = pycompat.byteskwargs(opts)
3921 3920 fm = ui.formatter('manifest', opts)
3922 3921
3923 3922 if opts.get('all'):
3924 3923 if rev or node:
3925 3924 raise error.Abort(_("can't specify a revision with --all"))
3926 3925
3927 3926 res = set()
3928 3927 for rev in repo:
3929 3928 ctx = repo[rev]
3930 3929 res |= set(ctx.files())
3931 3930
3932 3931 ui.pager('manifest')
3933 3932 for f in sorted(res):
3934 3933 fm.startitem()
3935 3934 fm.write("path", '%s\n', f)
3936 3935 fm.end()
3937 3936 return
3938 3937
3939 3938 if rev and node:
3940 3939 raise error.Abort(_("please specify just one revision"))
3941 3940
3942 3941 if not node:
3943 3942 node = rev
3944 3943
3945 3944 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3946 3945 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3947 3946 if node:
3948 3947 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3949 3948 ctx = scmutil.revsingle(repo, node)
3950 3949 mf = ctx.manifest()
3951 3950 ui.pager('manifest')
3952 3951 for f in ctx:
3953 3952 fm.startitem()
3954 3953 fm.context(ctx=ctx)
3955 3954 fl = ctx[f].flags()
3956 3955 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3957 3956 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3958 3957 fm.write('path', '%s\n', f)
3959 3958 fm.end()
3960 3959
3961 3960 @command('merge',
3962 3961 [('f', 'force', None,
3963 3962 _('force a merge including outstanding changes (DEPRECATED)')),
3964 3963 ('r', 'rev', '', _('revision to merge'), _('REV')),
3965 3964 ('P', 'preview', None,
3966 3965 _('review revisions to merge (no merge is performed)')),
3967 3966 ('', 'abort', None, _('abort the ongoing merge')),
3968 3967 ] + mergetoolopts,
3969 3968 _('[-P] [[-r] REV]'),
3970 3969 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3971 3970 def merge(ui, repo, node=None, **opts):
3972 3971 """merge another revision into working directory
3973 3972
3974 3973 The current working directory is updated with all changes made in
3975 3974 the requested revision since the last common predecessor revision.
3976 3975
3977 3976 Files that changed between either parent are marked as changed for
3978 3977 the next commit and a commit must be performed before any further
3979 3978 updates to the repository are allowed. The next commit will have
3980 3979 two parents.
3981 3980
3982 3981 ``--tool`` can be used to specify the merge tool used for file
3983 3982 merges. It overrides the HGMERGE environment variable and your
3984 3983 configuration files. See :hg:`help merge-tools` for options.
3985 3984
3986 3985 If no revision is specified, the working directory's parent is a
3987 3986 head revision, and the current branch contains exactly one other
3988 3987 head, the other head is merged with by default. Otherwise, an
3989 3988 explicit revision with which to merge with must be provided.
3990 3989
3991 3990 See :hg:`help resolve` for information on handling file conflicts.
3992 3991
3993 3992 To undo an uncommitted merge, use :hg:`merge --abort` which
3994 3993 will check out a clean copy of the original merge parent, losing
3995 3994 all changes.
3996 3995
3997 3996 Returns 0 on success, 1 if there are unresolved files.
3998 3997 """
3999 3998
4000 3999 opts = pycompat.byteskwargs(opts)
4001 4000 abort = opts.get('abort')
4002 4001 if abort and repo.dirstate.p2() == nullid:
4003 4002 cmdutil.wrongtooltocontinue(repo, _('merge'))
4004 4003 if abort:
4005 4004 if node:
4006 4005 raise error.Abort(_("cannot specify a node with --abort"))
4007 4006 if opts.get('rev'):
4008 4007 raise error.Abort(_("cannot specify both --rev and --abort"))
4009 4008 if opts.get('preview'):
4010 4009 raise error.Abort(_("cannot specify --preview with --abort"))
4011 4010 if opts.get('rev') and node:
4012 4011 raise error.Abort(_("please specify just one revision"))
4013 4012 if not node:
4014 4013 node = opts.get('rev')
4015 4014
4016 4015 if node:
4017 4016 node = scmutil.revsingle(repo, node).node()
4018 4017
4019 4018 if not node and not abort:
4020 4019 node = repo[destutil.destmerge(repo)].node()
4021 4020
4022 4021 if opts.get('preview'):
4023 4022 # find nodes that are ancestors of p2 but not of p1
4024 4023 p1 = repo.lookup('.')
4025 4024 p2 = node
4026 4025 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4027 4026
4028 4027 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4029 4028 for node in nodes:
4030 4029 displayer.show(repo[node])
4031 4030 displayer.close()
4032 4031 return 0
4033 4032
4034 4033 # ui.forcemerge is an internal variable, do not document
4035 4034 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4036 4035 with ui.configoverride(overrides, 'merge'):
4037 4036 force = opts.get('force')
4038 4037 labels = ['working copy', 'merge rev']
4039 4038 return hg.merge(repo, node, force=force, mergeforce=force,
4040 4039 labels=labels, abort=abort)
4041 4040
4042 4041 @command('outgoing|out',
4043 4042 [('f', 'force', None, _('run even when the destination is unrelated')),
4044 4043 ('r', 'rev', [],
4045 4044 _('a changeset intended to be included in the destination'), _('REV')),
4046 4045 ('n', 'newest-first', None, _('show newest record first')),
4047 4046 ('B', 'bookmarks', False, _('compare bookmarks')),
4048 4047 ('b', 'branch', [], _('a specific branch you would like to push'),
4049 4048 _('BRANCH')),
4050 4049 ] + logopts + remoteopts + subrepoopts,
4051 4050 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4052 4051 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4053 4052 def outgoing(ui, repo, dest=None, **opts):
4054 4053 """show changesets not found in the destination
4055 4054
4056 4055 Show changesets not found in the specified destination repository
4057 4056 or the default push location. These are the changesets that would
4058 4057 be pushed if a push was requested.
4059 4058
4060 4059 See pull for details of valid destination formats.
4061 4060
4062 4061 .. container:: verbose
4063 4062
4064 4063 With -B/--bookmarks, the result of bookmark comparison between
4065 4064 local and remote repositories is displayed. With -v/--verbose,
4066 4065 status is also displayed for each bookmark like below::
4067 4066
4068 4067 BM1 01234567890a added
4069 4068 BM2 deleted
4070 4069 BM3 234567890abc advanced
4071 4070 BM4 34567890abcd diverged
4072 4071 BM5 4567890abcde changed
4073 4072
4074 4073 The action taken when pushing depends on the
4075 4074 status of each bookmark:
4076 4075
4077 4076 :``added``: push with ``-B`` will create it
4078 4077 :``deleted``: push with ``-B`` will delete it
4079 4078 :``advanced``: push will update it
4080 4079 :``diverged``: push with ``-B`` will update it
4081 4080 :``changed``: push with ``-B`` will update it
4082 4081
4083 4082 From the point of view of pushing behavior, bookmarks
4084 4083 existing only in the remote repository are treated as
4085 4084 ``deleted``, even if it is in fact added remotely.
4086 4085
4087 4086 Returns 0 if there are outgoing changes, 1 otherwise.
4088 4087 """
4089 4088 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4090 4089 # style URLs, so don't overwrite dest.
4091 4090 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4092 4091 if not path:
4093 4092 raise error.Abort(_('default repository not configured!'),
4094 4093 hint=_("see 'hg help config.paths'"))
4095 4094
4096 4095 opts = pycompat.byteskwargs(opts)
4097 4096 if opts.get('graph'):
4098 4097 logcmdutil.checkunsupportedgraphflags([], opts)
4099 4098 o, other = hg._outgoing(ui, repo, dest, opts)
4100 4099 if not o:
4101 4100 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4102 4101 return
4103 4102
4104 4103 revdag = logcmdutil.graphrevs(repo, o, opts)
4105 4104 ui.pager('outgoing')
4106 4105 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4107 4106 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4108 4107 graphmod.asciiedges)
4109 4108 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4110 4109 return 0
4111 4110
4112 4111 if opts.get('bookmarks'):
4113 4112 dest = path.pushloc or path.loc
4114 4113 other = hg.peer(repo, opts, dest)
4115 4114 if 'bookmarks' not in other.listkeys('namespaces'):
4116 4115 ui.warn(_("remote doesn't support bookmarks\n"))
4117 4116 return 0
4118 4117 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4119 4118 ui.pager('outgoing')
4120 4119 return bookmarks.outgoing(ui, repo, other)
4121 4120
4122 4121 repo._subtoppath = path.pushloc or path.loc
4123 4122 try:
4124 4123 return hg.outgoing(ui, repo, dest, opts)
4125 4124 finally:
4126 4125 del repo._subtoppath
4127 4126
4128 4127 @command('parents',
4129 4128 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4130 4129 ] + templateopts,
4131 4130 _('[-r REV] [FILE]'),
4132 4131 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4133 4132 inferrepo=True)
4134 4133 def parents(ui, repo, file_=None, **opts):
4135 4134 """show the parents of the working directory or revision (DEPRECATED)
4136 4135
4137 4136 Print the working directory's parent revisions. If a revision is
4138 4137 given via -r/--rev, the parent of that revision will be printed.
4139 4138 If a file argument is given, the revision in which the file was
4140 4139 last changed (before the working directory revision or the
4141 4140 argument to --rev if given) is printed.
4142 4141
4143 4142 This command is equivalent to::
4144 4143
4145 4144 hg log -r "p1()+p2()" or
4146 4145 hg log -r "p1(REV)+p2(REV)" or
4147 4146 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4148 4147 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4149 4148
4150 4149 See :hg:`summary` and :hg:`help revsets` for related information.
4151 4150
4152 4151 Returns 0 on success.
4153 4152 """
4154 4153
4155 4154 opts = pycompat.byteskwargs(opts)
4156 4155 rev = opts.get('rev')
4157 4156 if rev:
4158 4157 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4159 4158 ctx = scmutil.revsingle(repo, rev, None)
4160 4159
4161 4160 if file_:
4162 4161 m = scmutil.match(ctx, (file_,), opts)
4163 4162 if m.anypats() or len(m.files()) != 1:
4164 4163 raise error.Abort(_('can only specify an explicit filename'))
4165 4164 file_ = m.files()[0]
4166 4165 filenodes = []
4167 4166 for cp in ctx.parents():
4168 4167 if not cp:
4169 4168 continue
4170 4169 try:
4171 4170 filenodes.append(cp.filenode(file_))
4172 4171 except error.LookupError:
4173 4172 pass
4174 4173 if not filenodes:
4175 4174 raise error.Abort(_("'%s' not found in manifest!") % file_)
4176 4175 p = []
4177 4176 for fn in filenodes:
4178 4177 fctx = repo.filectx(file_, fileid=fn)
4179 4178 p.append(fctx.node())
4180 4179 else:
4181 4180 p = [cp.node() for cp in ctx.parents()]
4182 4181
4183 4182 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4184 4183 for n in p:
4185 4184 if n != nullid:
4186 4185 displayer.show(repo[n])
4187 4186 displayer.close()
4188 4187
4189 4188 @command('paths', formatteropts, _('[NAME]'),
4190 4189 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4191 4190 optionalrepo=True, intents={INTENT_READONLY})
4192 4191 def paths(ui, repo, search=None, **opts):
4193 4192 """show aliases for remote repositories
4194 4193
4195 4194 Show definition of symbolic path name NAME. If no name is given,
4196 4195 show definition of all available names.
4197 4196
4198 4197 Option -q/--quiet suppresses all output when searching for NAME
4199 4198 and shows only the path names when listing all definitions.
4200 4199
4201 4200 Path names are defined in the [paths] section of your
4202 4201 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4203 4202 repository, ``.hg/hgrc`` is used, too.
4204 4203
4205 4204 The path names ``default`` and ``default-push`` have a special
4206 4205 meaning. When performing a push or pull operation, they are used
4207 4206 as fallbacks if no location is specified on the command-line.
4208 4207 When ``default-push`` is set, it will be used for push and
4209 4208 ``default`` will be used for pull; otherwise ``default`` is used
4210 4209 as the fallback for both. When cloning a repository, the clone
4211 4210 source is written as ``default`` in ``.hg/hgrc``.
4212 4211
4213 4212 .. note::
4214 4213
4215 4214 ``default`` and ``default-push`` apply to all inbound (e.g.
4216 4215 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4217 4216 and :hg:`bundle`) operations.
4218 4217
4219 4218 See :hg:`help urls` for more information.
4220 4219
4221 4220 .. container:: verbose
4222 4221
4223 4222 Template:
4224 4223
4225 4224 The following keywords are supported. See also :hg:`help templates`.
4226 4225
4227 4226 :name: String. Symbolic name of the path alias.
4228 4227 :pushurl: String. URL for push operations.
4229 4228 :url: String. URL or directory path for the other operations.
4230 4229
4231 4230 Returns 0 on success.
4232 4231 """
4233 4232
4234 4233 opts = pycompat.byteskwargs(opts)
4235 4234 ui.pager('paths')
4236 4235 if search:
4237 4236 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4238 4237 if name == search]
4239 4238 else:
4240 4239 pathitems = sorted(ui.paths.iteritems())
4241 4240
4242 4241 fm = ui.formatter('paths', opts)
4243 4242 if fm.isplain():
4244 4243 hidepassword = util.hidepassword
4245 4244 else:
4246 4245 hidepassword = bytes
4247 4246 if ui.quiet:
4248 4247 namefmt = '%s\n'
4249 4248 else:
4250 4249 namefmt = '%s = '
4251 4250 showsubopts = not search and not ui.quiet
4252 4251
4253 4252 for name, path in pathitems:
4254 4253 fm.startitem()
4255 4254 fm.condwrite(not search, 'name', namefmt, name)
4256 4255 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4257 4256 for subopt, value in sorted(path.suboptions.items()):
4258 4257 assert subopt not in ('name', 'url')
4259 4258 if showsubopts:
4260 4259 fm.plain('%s:%s = ' % (name, subopt))
4261 4260 fm.condwrite(showsubopts, subopt, '%s\n', value)
4262 4261
4263 4262 fm.end()
4264 4263
4265 4264 if search and not pathitems:
4266 4265 if not ui.quiet:
4267 4266 ui.warn(_("not found!\n"))
4268 4267 return 1
4269 4268 else:
4270 4269 return 0
4271 4270
4272 4271 @command('phase',
4273 4272 [('p', 'public', False, _('set changeset phase to public')),
4274 4273 ('d', 'draft', False, _('set changeset phase to draft')),
4275 4274 ('s', 'secret', False, _('set changeset phase to secret')),
4276 4275 ('f', 'force', False, _('allow to move boundary backward')),
4277 4276 ('r', 'rev', [], _('target revision'), _('REV')),
4278 4277 ],
4279 4278 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4280 4279 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4281 4280 def phase(ui, repo, *revs, **opts):
4282 4281 """set or show the current phase name
4283 4282
4284 4283 With no argument, show the phase name of the current revision(s).
4285 4284
4286 4285 With one of -p/--public, -d/--draft or -s/--secret, change the
4287 4286 phase value of the specified revisions.
4288 4287
4289 4288 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4290 4289 lower phase to a higher phase. Phases are ordered as follows::
4291 4290
4292 4291 public < draft < secret
4293 4292
4294 4293 Returns 0 on success, 1 if some phases could not be changed.
4295 4294
4296 4295 (For more information about the phases concept, see :hg:`help phases`.)
4297 4296 """
4298 4297 opts = pycompat.byteskwargs(opts)
4299 4298 # search for a unique phase argument
4300 4299 targetphase = None
4301 4300 for idx, name in enumerate(phases.cmdphasenames):
4302 4301 if opts[name]:
4303 4302 if targetphase is not None:
4304 4303 raise error.Abort(_('only one phase can be specified'))
4305 4304 targetphase = idx
4306 4305
4307 4306 # look for specified revision
4308 4307 revs = list(revs)
4309 4308 revs.extend(opts['rev'])
4310 4309 if not revs:
4311 4310 # display both parents as the second parent phase can influence
4312 4311 # the phase of a merge commit
4313 4312 revs = [c.rev() for c in repo[None].parents()]
4314 4313
4315 4314 revs = scmutil.revrange(repo, revs)
4316 4315
4317 4316 ret = 0
4318 4317 if targetphase is None:
4319 4318 # display
4320 4319 for r in revs:
4321 4320 ctx = repo[r]
4322 4321 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4323 4322 else:
4324 4323 with repo.lock(), repo.transaction("phase") as tr:
4325 4324 # set phase
4326 4325 if not revs:
4327 4326 raise error.Abort(_('empty revision set'))
4328 4327 nodes = [repo[r].node() for r in revs]
4329 4328 # moving revision from public to draft may hide them
4330 4329 # We have to check result on an unfiltered repository
4331 4330 unfi = repo.unfiltered()
4332 4331 getphase = unfi._phasecache.phase
4333 4332 olddata = [getphase(unfi, r) for r in unfi]
4334 4333 phases.advanceboundary(repo, tr, targetphase, nodes)
4335 4334 if opts['force']:
4336 4335 phases.retractboundary(repo, tr, targetphase, nodes)
4337 4336 getphase = unfi._phasecache.phase
4338 4337 newdata = [getphase(unfi, r) for r in unfi]
4339 4338 changes = sum(newdata[r] != olddata[r] for r in unfi)
4340 4339 cl = unfi.changelog
4341 4340 rejected = [n for n in nodes
4342 4341 if newdata[cl.rev(n)] < targetphase]
4343 4342 if rejected:
4344 4343 ui.warn(_('cannot move %i changesets to a higher '
4345 4344 'phase, use --force\n') % len(rejected))
4346 4345 ret = 1
4347 4346 if changes:
4348 4347 msg = _('phase changed for %i changesets\n') % changes
4349 4348 if ret:
4350 4349 ui.status(msg)
4351 4350 else:
4352 4351 ui.note(msg)
4353 4352 else:
4354 4353 ui.warn(_('no phases changed\n'))
4355 4354 return ret
4356 4355
4357 4356 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4358 4357 """Run after a changegroup has been added via pull/unbundle
4359 4358
4360 4359 This takes arguments below:
4361 4360
4362 4361 :modheads: change of heads by pull/unbundle
4363 4362 :optupdate: updating working directory is needed or not
4364 4363 :checkout: update destination revision (or None to default destination)
4365 4364 :brev: a name, which might be a bookmark to be activated after updating
4366 4365 """
4367 4366 if modheads == 0:
4368 4367 return
4369 4368 if optupdate:
4370 4369 try:
4371 4370 return hg.updatetotally(ui, repo, checkout, brev)
4372 4371 except error.UpdateAbort as inst:
4373 4372 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4374 4373 hint = inst.hint
4375 4374 raise error.UpdateAbort(msg, hint=hint)
4376 4375 if modheads is not None and modheads > 1:
4377 4376 currentbranchheads = len(repo.branchheads())
4378 4377 if currentbranchheads == modheads:
4379 4378 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4380 4379 elif currentbranchheads > 1:
4381 4380 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4382 4381 "merge)\n"))
4383 4382 else:
4384 4383 ui.status(_("(run 'hg heads' to see heads)\n"))
4385 4384 elif not ui.configbool('commands', 'update.requiredest'):
4386 4385 ui.status(_("(run 'hg update' to get a working copy)\n"))
4387 4386
4388 4387 @command('pull',
4389 4388 [('u', 'update', None,
4390 4389 _('update to new branch head if new descendants were pulled')),
4391 4390 ('f', 'force', None, _('run even when remote repository is unrelated')),
4392 4391 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4393 4392 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4394 4393 ('b', 'branch', [], _('a specific branch you would like to pull'),
4395 4394 _('BRANCH')),
4396 4395 ] + remoteopts,
4397 4396 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4398 4397 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4399 4398 helpbasic=True)
4400 4399 def pull(ui, repo, source="default", **opts):
4401 4400 """pull changes from the specified source
4402 4401
4403 4402 Pull changes from a remote repository to a local one.
4404 4403
4405 4404 This finds all changes from the repository at the specified path
4406 4405 or URL and adds them to a local repository (the current one unless
4407 4406 -R is specified). By default, this does not update the copy of the
4408 4407 project in the working directory.
4409 4408
4410 4409 When cloning from servers that support it, Mercurial may fetch
4411 4410 pre-generated data. When this is done, hooks operating on incoming
4412 4411 changesets and changegroups may fire more than once, once for each
4413 4412 pre-generated bundle and as well as for any additional remaining
4414 4413 data. See :hg:`help -e clonebundles` for more.
4415 4414
4416 4415 Use :hg:`incoming` if you want to see what would have been added
4417 4416 by a pull at the time you issued this command. If you then decide
4418 4417 to add those changes to the repository, you should use :hg:`pull
4419 4418 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4420 4419
4421 4420 If SOURCE is omitted, the 'default' path will be used.
4422 4421 See :hg:`help urls` for more information.
4423 4422
4424 4423 Specifying bookmark as ``.`` is equivalent to specifying the active
4425 4424 bookmark's name.
4426 4425
4427 4426 Returns 0 on success, 1 if an update had unresolved files.
4428 4427 """
4429 4428
4430 4429 opts = pycompat.byteskwargs(opts)
4431 4430 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4432 4431 msg = _('update destination required by configuration')
4433 4432 hint = _('use hg pull followed by hg update DEST')
4434 4433 raise error.Abort(msg, hint=hint)
4435 4434
4436 4435 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4437 4436 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4438 4437 other = hg.peer(repo, opts, source)
4439 4438 try:
4440 4439 revs, checkout = hg.addbranchrevs(repo, other, branches,
4441 4440 opts.get('rev'))
4442 4441
4443 4442 pullopargs = {}
4444 4443
4445 4444 nodes = None
4446 4445 if opts.get('bookmark') or revs:
4447 4446 # The list of bookmark used here is the same used to actually update
4448 4447 # the bookmark names, to avoid the race from issue 4689 and we do
4449 4448 # all lookup and bookmark queries in one go so they see the same
4450 4449 # version of the server state (issue 4700).
4451 4450 nodes = []
4452 4451 fnodes = []
4453 4452 revs = revs or []
4454 4453 if revs and not other.capable('lookup'):
4455 4454 err = _("other repository doesn't support revision lookup, "
4456 4455 "so a rev cannot be specified.")
4457 4456 raise error.Abort(err)
4458 4457 with other.commandexecutor() as e:
4459 4458 fremotebookmarks = e.callcommand('listkeys', {
4460 4459 'namespace': 'bookmarks'
4461 4460 })
4462 4461 for r in revs:
4463 4462 fnodes.append(e.callcommand('lookup', {'key': r}))
4464 4463 remotebookmarks = fremotebookmarks.result()
4465 4464 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4466 4465 pullopargs['remotebookmarks'] = remotebookmarks
4467 4466 for b in opts.get('bookmark', []):
4468 4467 b = repo._bookmarks.expandname(b)
4469 4468 if b not in remotebookmarks:
4470 4469 raise error.Abort(_('remote bookmark %s not found!') % b)
4471 4470 nodes.append(remotebookmarks[b])
4472 4471 for i, rev in enumerate(revs):
4473 4472 node = fnodes[i].result()
4474 4473 nodes.append(node)
4475 4474 if rev == checkout:
4476 4475 checkout = node
4477 4476
4478 4477 wlock = util.nullcontextmanager()
4479 4478 if opts.get('update'):
4480 4479 wlock = repo.wlock()
4481 4480 with wlock:
4482 4481 pullopargs.update(opts.get('opargs', {}))
4483 4482 modheads = exchange.pull(repo, other, heads=nodes,
4484 4483 force=opts.get('force'),
4485 4484 bookmarks=opts.get('bookmark', ()),
4486 4485 opargs=pullopargs).cgresult
4487 4486
4488 4487 # brev is a name, which might be a bookmark to be activated at
4489 4488 # the end of the update. In other words, it is an explicit
4490 4489 # destination of the update
4491 4490 brev = None
4492 4491
4493 4492 if checkout:
4494 4493 checkout = repo.changelog.rev(checkout)
4495 4494
4496 4495 # order below depends on implementation of
4497 4496 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4498 4497 # because 'checkout' is determined without it.
4499 4498 if opts.get('rev'):
4500 4499 brev = opts['rev'][0]
4501 4500 elif opts.get('branch'):
4502 4501 brev = opts['branch'][0]
4503 4502 else:
4504 4503 brev = branches[0]
4505 4504 repo._subtoppath = source
4506 4505 try:
4507 4506 ret = postincoming(ui, repo, modheads, opts.get('update'),
4508 4507 checkout, brev)
4509 4508
4510 4509 finally:
4511 4510 del repo._subtoppath
4512 4511
4513 4512 finally:
4514 4513 other.close()
4515 4514 return ret
4516 4515
4517 4516 @command('push',
4518 4517 [('f', 'force', None, _('force push')),
4519 4518 ('r', 'rev', [],
4520 4519 _('a changeset intended to be included in the destination'),
4521 4520 _('REV')),
4522 4521 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4523 4522 ('b', 'branch', [],
4524 4523 _('a specific branch you would like to push'), _('BRANCH')),
4525 4524 ('', 'new-branch', False, _('allow pushing a new branch')),
4526 4525 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4527 4526 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4528 4527 ] + remoteopts,
4529 4528 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4530 4529 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4531 4530 helpbasic=True)
4532 4531 def push(ui, repo, dest=None, **opts):
4533 4532 """push changes to the specified destination
4534 4533
4535 4534 Push changesets from the local repository to the specified
4536 4535 destination.
4537 4536
4538 4537 This operation is symmetrical to pull: it is identical to a pull
4539 4538 in the destination repository from the current one.
4540 4539
4541 4540 By default, push will not allow creation of new heads at the
4542 4541 destination, since multiple heads would make it unclear which head
4543 4542 to use. In this situation, it is recommended to pull and merge
4544 4543 before pushing.
4545 4544
4546 4545 Use --new-branch if you want to allow push to create a new named
4547 4546 branch that is not present at the destination. This allows you to
4548 4547 only create a new branch without forcing other changes.
4549 4548
4550 4549 .. note::
4551 4550
4552 4551 Extra care should be taken with the -f/--force option,
4553 4552 which will push all new heads on all branches, an action which will
4554 4553 almost always cause confusion for collaborators.
4555 4554
4556 4555 If -r/--rev is used, the specified revision and all its ancestors
4557 4556 will be pushed to the remote repository.
4558 4557
4559 4558 If -B/--bookmark is used, the specified bookmarked revision, its
4560 4559 ancestors, and the bookmark will be pushed to the remote
4561 4560 repository. Specifying ``.`` is equivalent to specifying the active
4562 4561 bookmark's name.
4563 4562
4564 4563 Please see :hg:`help urls` for important details about ``ssh://``
4565 4564 URLs. If DESTINATION is omitted, a default path will be used.
4566 4565
4567 4566 .. container:: verbose
4568 4567
4569 4568 The --pushvars option sends strings to the server that become
4570 4569 environment variables prepended with ``HG_USERVAR_``. For example,
4571 4570 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4572 4571 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4573 4572
4574 4573 pushvars can provide for user-overridable hooks as well as set debug
4575 4574 levels. One example is having a hook that blocks commits containing
4576 4575 conflict markers, but enables the user to override the hook if the file
4577 4576 is using conflict markers for testing purposes or the file format has
4578 4577 strings that look like conflict markers.
4579 4578
4580 4579 By default, servers will ignore `--pushvars`. To enable it add the
4581 4580 following to your configuration file::
4582 4581
4583 4582 [push]
4584 4583 pushvars.server = true
4585 4584
4586 4585 Returns 0 if push was successful, 1 if nothing to push.
4587 4586 """
4588 4587
4589 4588 opts = pycompat.byteskwargs(opts)
4590 4589 if opts.get('bookmark'):
4591 4590 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4592 4591 for b in opts['bookmark']:
4593 4592 # translate -B options to -r so changesets get pushed
4594 4593 b = repo._bookmarks.expandname(b)
4595 4594 if b in repo._bookmarks:
4596 4595 opts.setdefault('rev', []).append(b)
4597 4596 else:
4598 4597 # if we try to push a deleted bookmark, translate it to null
4599 4598 # this lets simultaneous -r, -b options continue working
4600 4599 opts.setdefault('rev', []).append("null")
4601 4600
4602 4601 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4603 4602 if not path:
4604 4603 raise error.Abort(_('default repository not configured!'),
4605 4604 hint=_("see 'hg help config.paths'"))
4606 4605 dest = path.pushloc or path.loc
4607 4606 branches = (path.branch, opts.get('branch') or [])
4608 4607 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4609 4608 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4610 4609 other = hg.peer(repo, opts, dest)
4611 4610
4612 4611 if revs:
4613 4612 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4614 4613 if not revs:
4615 4614 raise error.Abort(_("specified revisions evaluate to an empty set"),
4616 4615 hint=_("use different revision arguments"))
4617 4616 elif path.pushrev:
4618 4617 # It doesn't make any sense to specify ancestor revisions. So limit
4619 4618 # to DAG heads to make discovery simpler.
4620 4619 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4621 4620 revs = scmutil.revrange(repo, [expr])
4622 4621 revs = [repo[rev].node() for rev in revs]
4623 4622 if not revs:
4624 4623 raise error.Abort(_('default push revset for path evaluates to an '
4625 4624 'empty set'))
4626 4625
4627 4626 repo._subtoppath = dest
4628 4627 try:
4629 4628 # push subrepos depth-first for coherent ordering
4630 4629 c = repo['.']
4631 4630 subs = c.substate # only repos that are committed
4632 4631 for s in sorted(subs):
4633 4632 result = c.sub(s).push(opts)
4634 4633 if result == 0:
4635 4634 return not result
4636 4635 finally:
4637 4636 del repo._subtoppath
4638 4637
4639 4638 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4640 4639 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4641 4640
4642 4641 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4643 4642 newbranch=opts.get('new_branch'),
4644 4643 bookmarks=opts.get('bookmark', ()),
4645 4644 publish=opts.get('publish'),
4646 4645 opargs=opargs)
4647 4646
4648 4647 result = not pushop.cgresult
4649 4648
4650 4649 if pushop.bkresult is not None:
4651 4650 if pushop.bkresult == 2:
4652 4651 result = 2
4653 4652 elif not result and pushop.bkresult:
4654 4653 result = 2
4655 4654
4656 4655 return result
4657 4656
4658 4657 @command('recover', [], helpcategory=command.CATEGORY_MAINTENANCE)
4659 4658 def recover(ui, repo):
4660 4659 """roll back an interrupted transaction
4661 4660
4662 4661 Recover from an interrupted commit or pull.
4663 4662
4664 4663 This command tries to fix the repository status after an
4665 4664 interrupted operation. It should only be necessary when Mercurial
4666 4665 suggests it.
4667 4666
4668 4667 Returns 0 if successful, 1 if nothing to recover or verify fails.
4669 4668 """
4670 4669 if repo.recover():
4671 4670 return hg.verify(repo)
4672 4671 return 1
4673 4672
4674 4673 @command('remove|rm',
4675 4674 [('A', 'after', None, _('record delete for missing files')),
4676 4675 ('f', 'force', None,
4677 4676 _('forget added files, delete modified files')),
4678 4677 ] + subrepoopts + walkopts + dryrunopts,
4679 4678 _('[OPTION]... FILE...'),
4680 4679 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4681 4680 helpbasic=True, inferrepo=True)
4682 4681 def remove(ui, repo, *pats, **opts):
4683 4682 """remove the specified files on the next commit
4684 4683
4685 4684 Schedule the indicated files for removal from the current branch.
4686 4685
4687 4686 This command schedules the files to be removed at the next commit.
4688 4687 To undo a remove before that, see :hg:`revert`. To undo added
4689 4688 files, see :hg:`forget`.
4690 4689
4691 4690 .. container:: verbose
4692 4691
4693 4692 -A/--after can be used to remove only files that have already
4694 4693 been deleted, -f/--force can be used to force deletion, and -Af
4695 4694 can be used to remove files from the next revision without
4696 4695 deleting them from the working directory.
4697 4696
4698 4697 The following table details the behavior of remove for different
4699 4698 file states (columns) and option combinations (rows). The file
4700 4699 states are Added [A], Clean [C], Modified [M] and Missing [!]
4701 4700 (as reported by :hg:`status`). The actions are Warn, Remove
4702 4701 (from branch) and Delete (from disk):
4703 4702
4704 4703 ========= == == == ==
4705 4704 opt/state A C M !
4706 4705 ========= == == == ==
4707 4706 none W RD W R
4708 4707 -f R RD RD R
4709 4708 -A W W W R
4710 4709 -Af R R R R
4711 4710 ========= == == == ==
4712 4711
4713 4712 .. note::
4714 4713
4715 4714 :hg:`remove` never deletes files in Added [A] state from the
4716 4715 working directory, not even if ``--force`` is specified.
4717 4716
4718 4717 Returns 0 on success, 1 if any warnings encountered.
4719 4718 """
4720 4719
4721 4720 opts = pycompat.byteskwargs(opts)
4722 4721 after, force = opts.get('after'), opts.get('force')
4723 4722 dryrun = opts.get('dry_run')
4724 4723 if not pats and not after:
4725 4724 raise error.Abort(_('no files specified'))
4726 4725
4727 4726 m = scmutil.match(repo[None], pats, opts)
4728 4727 subrepos = opts.get('subrepos')
4729 4728 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
4730 4729 return cmdutil.remove(ui, repo, m, "", uipathfn, after, force, subrepos,
4731 4730 dryrun=dryrun)
4732 4731
4733 4732 @command('rename|move|mv',
4734 4733 [('A', 'after', None, _('record a rename that has already occurred')),
4735 4734 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4736 4735 ] + walkopts + dryrunopts,
4737 4736 _('[OPTION]... SOURCE... DEST'),
4738 4737 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4739 4738 def rename(ui, repo, *pats, **opts):
4740 4739 """rename files; equivalent of copy + remove
4741 4740
4742 4741 Mark dest as copies of sources; mark sources for deletion. If dest
4743 4742 is a directory, copies are put in that directory. If dest is a
4744 4743 file, there can only be one source.
4745 4744
4746 4745 By default, this command copies the contents of files as they
4747 4746 exist in the working directory. If invoked with -A/--after, the
4748 4747 operation is recorded, but no copying is performed.
4749 4748
4750 4749 This command takes effect at the next commit. To undo a rename
4751 4750 before that, see :hg:`revert`.
4752 4751
4753 4752 Returns 0 on success, 1 if errors are encountered.
4754 4753 """
4755 4754 opts = pycompat.byteskwargs(opts)
4756 4755 with repo.wlock(False):
4757 4756 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4758 4757
4759 4758 @command('resolve',
4760 4759 [('a', 'all', None, _('select all unresolved files')),
4761 4760 ('l', 'list', None, _('list state of files needing merge')),
4762 4761 ('m', 'mark', None, _('mark files as resolved')),
4763 4762 ('u', 'unmark', None, _('mark files as unresolved')),
4764 4763 ('n', 'no-status', None, _('hide status prefix')),
4765 4764 ('', 're-merge', None, _('re-merge files'))]
4766 4765 + mergetoolopts + walkopts + formatteropts,
4767 4766 _('[OPTION]... [FILE]...'),
4768 4767 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4769 4768 inferrepo=True)
4770 4769 def resolve(ui, repo, *pats, **opts):
4771 4770 """redo merges or set/view the merge status of files
4772 4771
4773 4772 Merges with unresolved conflicts are often the result of
4774 4773 non-interactive merging using the ``internal:merge`` configuration
4775 4774 setting, or a command-line merge tool like ``diff3``. The resolve
4776 4775 command is used to manage the files involved in a merge, after
4777 4776 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4778 4777 working directory must have two parents). See :hg:`help
4779 4778 merge-tools` for information on configuring merge tools.
4780 4779
4781 4780 The resolve command can be used in the following ways:
4782 4781
4783 4782 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4784 4783 the specified files, discarding any previous merge attempts. Re-merging
4785 4784 is not performed for files already marked as resolved. Use ``--all/-a``
4786 4785 to select all unresolved files. ``--tool`` can be used to specify
4787 4786 the merge tool used for the given files. It overrides the HGMERGE
4788 4787 environment variable and your configuration files. Previous file
4789 4788 contents are saved with a ``.orig`` suffix.
4790 4789
4791 4790 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4792 4791 (e.g. after having manually fixed-up the files). The default is
4793 4792 to mark all unresolved files.
4794 4793
4795 4794 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4796 4795 default is to mark all resolved files.
4797 4796
4798 4797 - :hg:`resolve -l`: list files which had or still have conflicts.
4799 4798 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4800 4799 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4801 4800 the list. See :hg:`help filesets` for details.
4802 4801
4803 4802 .. note::
4804 4803
4805 4804 Mercurial will not let you commit files with unresolved merge
4806 4805 conflicts. You must use :hg:`resolve -m ...` before you can
4807 4806 commit after a conflicting merge.
4808 4807
4809 4808 .. container:: verbose
4810 4809
4811 4810 Template:
4812 4811
4813 4812 The following keywords are supported in addition to the common template
4814 4813 keywords and functions. See also :hg:`help templates`.
4815 4814
4816 4815 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4817 4816 :path: String. Repository-absolute path of the file.
4818 4817
4819 4818 Returns 0 on success, 1 if any files fail a resolve attempt.
4820 4819 """
4821 4820
4822 4821 opts = pycompat.byteskwargs(opts)
4823 4822 confirm = ui.configbool('commands', 'resolve.confirm')
4824 4823 flaglist = 'all mark unmark list no_status re_merge'.split()
4825 4824 all, mark, unmark, show, nostatus, remerge = [
4826 4825 opts.get(o) for o in flaglist]
4827 4826
4828 4827 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4829 4828 if actioncount > 1:
4830 4829 raise error.Abort(_("too many actions specified"))
4831 4830 elif (actioncount == 0
4832 4831 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4833 4832 hint = _('use --mark, --unmark, --list or --re-merge')
4834 4833 raise error.Abort(_('no action specified'), hint=hint)
4835 4834 if pats and all:
4836 4835 raise error.Abort(_("can't specify --all and patterns"))
4837 4836 if not (all or pats or show or mark or unmark):
4838 4837 raise error.Abort(_('no files or directories specified'),
4839 4838 hint=('use --all to re-merge all unresolved files'))
4840 4839
4841 4840 if confirm:
4842 4841 if all:
4843 4842 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4844 4843 b'$$ &Yes $$ &No')):
4845 4844 raise error.Abort(_('user quit'))
4846 4845 if mark and not pats:
4847 4846 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4848 4847 b'$$ &Yes $$ &No')):
4849 4848 raise error.Abort(_('user quit'))
4850 4849 if unmark and not pats:
4851 4850 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4852 4851 b'$$ &Yes $$ &No')):
4853 4852 raise error.Abort(_('user quit'))
4854 4853
4855 4854 uipathfn = scmutil.getuipathfn(repo)
4856 4855
4857 4856 if show:
4858 4857 ui.pager('resolve')
4859 4858 fm = ui.formatter('resolve', opts)
4860 4859 ms = mergemod.mergestate.read(repo)
4861 4860 wctx = repo[None]
4862 4861 m = scmutil.match(wctx, pats, opts)
4863 4862
4864 4863 # Labels and keys based on merge state. Unresolved path conflicts show
4865 4864 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4866 4865 # resolved conflicts.
4867 4866 mergestateinfo = {
4868 4867 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4869 4868 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4870 4869 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4871 4870 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4872 4871 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4873 4872 'D'),
4874 4873 }
4875 4874
4876 4875 for f in ms:
4877 4876 if not m(f):
4878 4877 continue
4879 4878
4880 4879 label, key = mergestateinfo[ms[f]]
4881 4880 fm.startitem()
4882 4881 fm.context(ctx=wctx)
4883 4882 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4884 4883 fm.data(path=f)
4885 4884 fm.plain('%s\n' % uipathfn(f), label=label)
4886 4885 fm.end()
4887 4886 return 0
4888 4887
4889 4888 with repo.wlock():
4890 4889 ms = mergemod.mergestate.read(repo)
4891 4890
4892 4891 if not (ms.active() or repo.dirstate.p2() != nullid):
4893 4892 raise error.Abort(
4894 4893 _('resolve command not applicable when not merging'))
4895 4894
4896 4895 wctx = repo[None]
4897 4896
4898 4897 if (ms.mergedriver
4899 4898 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4900 4899 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4901 4900 ms.commit()
4902 4901 # allow mark and unmark to go through
4903 4902 if not mark and not unmark and not proceed:
4904 4903 return 1
4905 4904
4906 4905 m = scmutil.match(wctx, pats, opts)
4907 4906 ret = 0
4908 4907 didwork = False
4909 4908 runconclude = False
4910 4909
4911 4910 tocomplete = []
4912 4911 hasconflictmarkers = []
4913 4912 if mark:
4914 4913 markcheck = ui.config('commands', 'resolve.mark-check')
4915 4914 if markcheck not in ['warn', 'abort']:
4916 4915 # Treat all invalid / unrecognized values as 'none'.
4917 4916 markcheck = False
4918 4917 for f in ms:
4919 4918 if not m(f):
4920 4919 continue
4921 4920
4922 4921 didwork = True
4923 4922
4924 4923 # don't let driver-resolved files be marked, and run the conclude
4925 4924 # step if asked to resolve
4926 4925 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4927 4926 exact = m.exact(f)
4928 4927 if mark:
4929 4928 if exact:
4930 4929 ui.warn(_('not marking %s as it is driver-resolved\n')
4931 4930 % uipathfn(f))
4932 4931 elif unmark:
4933 4932 if exact:
4934 4933 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4935 4934 % uipathfn(f))
4936 4935 else:
4937 4936 runconclude = True
4938 4937 continue
4939 4938
4940 4939 # path conflicts must be resolved manually
4941 4940 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4942 4941 mergemod.MERGE_RECORD_RESOLVED_PATH):
4943 4942 if mark:
4944 4943 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4945 4944 elif unmark:
4946 4945 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4947 4946 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4948 4947 ui.warn(_('%s: path conflict must be resolved manually\n')
4949 4948 % uipathfn(f))
4950 4949 continue
4951 4950
4952 4951 if mark:
4953 4952 if markcheck:
4954 4953 fdata = repo.wvfs.tryread(f)
4955 4954 if (filemerge.hasconflictmarkers(fdata) and
4956 4955 ms[f] != mergemod.MERGE_RECORD_RESOLVED):
4957 4956 hasconflictmarkers.append(f)
4958 4957 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4959 4958 elif unmark:
4960 4959 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4961 4960 else:
4962 4961 # backup pre-resolve (merge uses .orig for its own purposes)
4963 4962 a = repo.wjoin(f)
4964 4963 try:
4965 4964 util.copyfile(a, a + ".resolve")
4966 4965 except (IOError, OSError) as inst:
4967 4966 if inst.errno != errno.ENOENT:
4968 4967 raise
4969 4968
4970 4969 try:
4971 4970 # preresolve file
4972 4971 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4973 4972 with ui.configoverride(overrides, 'resolve'):
4974 4973 complete, r = ms.preresolve(f, wctx)
4975 4974 if not complete:
4976 4975 tocomplete.append(f)
4977 4976 elif r:
4978 4977 ret = 1
4979 4978 finally:
4980 4979 ms.commit()
4981 4980
4982 4981 # replace filemerge's .orig file with our resolve file, but only
4983 4982 # for merges that are complete
4984 4983 if complete:
4985 4984 try:
4986 4985 util.rename(a + ".resolve",
4987 4986 scmutil.backuppath(ui, repo, f))
4988 4987 except OSError as inst:
4989 4988 if inst.errno != errno.ENOENT:
4990 4989 raise
4991 4990
4992 4991 if hasconflictmarkers:
4993 4992 ui.warn(_('warning: the following files still have conflict '
4994 4993 'markers:\n') + ''.join(' ' + uipathfn(f) + '\n'
4995 4994 for f in hasconflictmarkers))
4996 4995 if markcheck == 'abort' and not all and not pats:
4997 4996 raise error.Abort(_('conflict markers detected'),
4998 4997 hint=_('use --all to mark anyway'))
4999 4998
5000 4999 for f in tocomplete:
5001 5000 try:
5002 5001 # resolve file
5003 5002 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
5004 5003 with ui.configoverride(overrides, 'resolve'):
5005 5004 r = ms.resolve(f, wctx)
5006 5005 if r:
5007 5006 ret = 1
5008 5007 finally:
5009 5008 ms.commit()
5010 5009
5011 5010 # replace filemerge's .orig file with our resolve file
5012 5011 a = repo.wjoin(f)
5013 5012 try:
5014 5013 util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f))
5015 5014 except OSError as inst:
5016 5015 if inst.errno != errno.ENOENT:
5017 5016 raise
5018 5017
5019 5018 ms.commit()
5020 5019 ms.recordactions()
5021 5020
5022 5021 if not didwork and pats:
5023 5022 hint = None
5024 5023 if not any([p for p in pats if p.find(':') >= 0]):
5025 5024 pats = ['path:%s' % p for p in pats]
5026 5025 m = scmutil.match(wctx, pats, opts)
5027 5026 for f in ms:
5028 5027 if not m(f):
5029 5028 continue
5030 5029 def flag(o):
5031 5030 if o == 're_merge':
5032 5031 return '--re-merge '
5033 5032 return '-%s ' % o[0:1]
5034 5033 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
5035 5034 hint = _("(try: hg resolve %s%s)\n") % (
5036 5035 flags,
5037 5036 ' '.join(pats))
5038 5037 break
5039 5038 ui.warn(_("arguments do not match paths that need resolving\n"))
5040 5039 if hint:
5041 5040 ui.warn(hint)
5042 5041 elif ms.mergedriver and ms.mdstate() != 's':
5043 5042 # run conclude step when either a driver-resolved file is requested
5044 5043 # or there are no driver-resolved files
5045 5044 # we can't use 'ret' to determine whether any files are unresolved
5046 5045 # because we might not have tried to resolve some
5047 5046 if ((runconclude or not list(ms.driverresolved()))
5048 5047 and not list(ms.unresolved())):
5049 5048 proceed = mergemod.driverconclude(repo, ms, wctx)
5050 5049 ms.commit()
5051 5050 if not proceed:
5052 5051 return 1
5053 5052
5054 5053 # Nudge users into finishing an unfinished operation
5055 5054 unresolvedf = list(ms.unresolved())
5056 5055 driverresolvedf = list(ms.driverresolved())
5057 5056 if not unresolvedf and not driverresolvedf:
5058 5057 ui.status(_('(no more unresolved files)\n'))
5059 5058 cmdutil.checkafterresolved(repo)
5060 5059 elif not unresolvedf:
5061 5060 ui.status(_('(no more unresolved files -- '
5062 5061 'run "hg resolve --all" to conclude)\n'))
5063 5062
5064 5063 return ret
5065 5064
5066 5065 @command('revert',
5067 5066 [('a', 'all', None, _('revert all changes when no arguments given')),
5068 5067 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5069 5068 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5070 5069 ('C', 'no-backup', None, _('do not save backup copies of files')),
5071 5070 ('i', 'interactive', None, _('interactively select the changes')),
5072 5071 ] + walkopts + dryrunopts,
5073 5072 _('[OPTION]... [-r REV] [NAME]...'),
5074 5073 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5075 5074 def revert(ui, repo, *pats, **opts):
5076 5075 """restore files to their checkout state
5077 5076
5078 5077 .. note::
5079 5078
5080 5079 To check out earlier revisions, you should use :hg:`update REV`.
5081 5080 To cancel an uncommitted merge (and lose your changes),
5082 5081 use :hg:`merge --abort`.
5083 5082
5084 5083 With no revision specified, revert the specified files or directories
5085 5084 to the contents they had in the parent of the working directory.
5086 5085 This restores the contents of files to an unmodified
5087 5086 state and unschedules adds, removes, copies, and renames. If the
5088 5087 working directory has two parents, you must explicitly specify a
5089 5088 revision.
5090 5089
5091 5090 Using the -r/--rev or -d/--date options, revert the given files or
5092 5091 directories to their states as of a specific revision. Because
5093 5092 revert does not change the working directory parents, this will
5094 5093 cause these files to appear modified. This can be helpful to "back
5095 5094 out" some or all of an earlier change. See :hg:`backout` for a
5096 5095 related method.
5097 5096
5098 5097 Modified files are saved with a .orig suffix before reverting.
5099 5098 To disable these backups, use --no-backup. It is possible to store
5100 5099 the backup files in a custom directory relative to the root of the
5101 5100 repository by setting the ``ui.origbackuppath`` configuration
5102 5101 option.
5103 5102
5104 5103 See :hg:`help dates` for a list of formats valid for -d/--date.
5105 5104
5106 5105 See :hg:`help backout` for a way to reverse the effect of an
5107 5106 earlier changeset.
5108 5107
5109 5108 Returns 0 on success.
5110 5109 """
5111 5110
5112 5111 opts = pycompat.byteskwargs(opts)
5113 5112 if opts.get("date"):
5114 5113 if opts.get("rev"):
5115 5114 raise error.Abort(_("you can't specify a revision and a date"))
5116 5115 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5117 5116
5118 5117 parent, p2 = repo.dirstate.parents()
5119 5118 if not opts.get('rev') and p2 != nullid:
5120 5119 # revert after merge is a trap for new users (issue2915)
5121 5120 raise error.Abort(_('uncommitted merge with no revision specified'),
5122 5121 hint=_("use 'hg update' or see 'hg help revert'"))
5123 5122
5124 5123 rev = opts.get('rev')
5125 5124 if rev:
5126 5125 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5127 5126 ctx = scmutil.revsingle(repo, rev)
5128 5127
5129 5128 if (not (pats or opts.get('include') or opts.get('exclude') or
5130 5129 opts.get('all') or opts.get('interactive'))):
5131 5130 msg = _("no files or directories specified")
5132 5131 if p2 != nullid:
5133 5132 hint = _("uncommitted merge, use --all to discard all changes,"
5134 5133 " or 'hg update -C .' to abort the merge")
5135 5134 raise error.Abort(msg, hint=hint)
5136 5135 dirty = any(repo.status())
5137 5136 node = ctx.node()
5138 5137 if node != parent:
5139 5138 if dirty:
5140 5139 hint = _("uncommitted changes, use --all to discard all"
5141 5140 " changes, or 'hg update %d' to update") % ctx.rev()
5142 5141 else:
5143 5142 hint = _("use --all to revert all files,"
5144 5143 " or 'hg update %d' to update") % ctx.rev()
5145 5144 elif dirty:
5146 5145 hint = _("uncommitted changes, use --all to discard all changes")
5147 5146 else:
5148 5147 hint = _("use --all to revert all files")
5149 5148 raise error.Abort(msg, hint=hint)
5150 5149
5151 5150 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5152 5151 **pycompat.strkwargs(opts))
5153 5152
5154 5153 @command(
5155 5154 'rollback',
5156 5155 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5157 5156 helpcategory=command.CATEGORY_MAINTENANCE)
5158 5157 def rollback(ui, repo, **opts):
5159 5158 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5160 5159
5161 5160 Please use :hg:`commit --amend` instead of rollback to correct
5162 5161 mistakes in the last commit.
5163 5162
5164 5163 This command should be used with care. There is only one level of
5165 5164 rollback, and there is no way to undo a rollback. It will also
5166 5165 restore the dirstate at the time of the last transaction, losing
5167 5166 any dirstate changes since that time. This command does not alter
5168 5167 the working directory.
5169 5168
5170 5169 Transactions are used to encapsulate the effects of all commands
5171 5170 that create new changesets or propagate existing changesets into a
5172 5171 repository.
5173 5172
5174 5173 .. container:: verbose
5175 5174
5176 5175 For example, the following commands are transactional, and their
5177 5176 effects can be rolled back:
5178 5177
5179 5178 - commit
5180 5179 - import
5181 5180 - pull
5182 5181 - push (with this repository as the destination)
5183 5182 - unbundle
5184 5183
5185 5184 To avoid permanent data loss, rollback will refuse to rollback a
5186 5185 commit transaction if it isn't checked out. Use --force to
5187 5186 override this protection.
5188 5187
5189 5188 The rollback command can be entirely disabled by setting the
5190 5189 ``ui.rollback`` configuration setting to false. If you're here
5191 5190 because you want to use rollback and it's disabled, you can
5192 5191 re-enable the command by setting ``ui.rollback`` to true.
5193 5192
5194 5193 This command is not intended for use on public repositories. Once
5195 5194 changes are visible for pull by other users, rolling a transaction
5196 5195 back locally is ineffective (someone else may already have pulled
5197 5196 the changes). Furthermore, a race is possible with readers of the
5198 5197 repository; for example an in-progress pull from the repository
5199 5198 may fail if a rollback is performed.
5200 5199
5201 5200 Returns 0 on success, 1 if no rollback data is available.
5202 5201 """
5203 5202 if not ui.configbool('ui', 'rollback'):
5204 5203 raise error.Abort(_('rollback is disabled because it is unsafe'),
5205 5204 hint=('see `hg help -v rollback` for information'))
5206 5205 return repo.rollback(dryrun=opts.get(r'dry_run'),
5207 5206 force=opts.get(r'force'))
5208 5207
5209 5208 @command(
5210 5209 'root', [], intents={INTENT_READONLY},
5211 5210 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5212 5211 def root(ui, repo):
5213 5212 """print the root (top) of the current working directory
5214 5213
5215 5214 Print the root directory of the current repository.
5216 5215
5217 5216 Returns 0 on success.
5218 5217 """
5219 5218 ui.write(repo.root + "\n")
5220 5219
5221 5220 @command('serve',
5222 5221 [('A', 'accesslog', '', _('name of access log file to write to'),
5223 5222 _('FILE')),
5224 5223 ('d', 'daemon', None, _('run server in background')),
5225 5224 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5226 5225 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5227 5226 # use string type, then we can check if something was passed
5228 5227 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5229 5228 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5230 5229 _('ADDR')),
5231 5230 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5232 5231 _('PREFIX')),
5233 5232 ('n', 'name', '',
5234 5233 _('name to show in web pages (default: working directory)'), _('NAME')),
5235 5234 ('', 'web-conf', '',
5236 5235 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5237 5236 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5238 5237 _('FILE')),
5239 5238 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5240 5239 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5241 5240 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5242 5241 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5243 5242 ('', 'style', '', _('template style to use'), _('STYLE')),
5244 5243 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5245 5244 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5246 5245 ('', 'print-url', None, _('start and print only the URL'))]
5247 5246 + subrepoopts,
5248 5247 _('[OPTION]...'),
5249 5248 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5250 5249 helpbasic=True, optionalrepo=True)
5251 5250 def serve(ui, repo, **opts):
5252 5251 """start stand-alone webserver
5253 5252
5254 5253 Start a local HTTP repository browser and pull server. You can use
5255 5254 this for ad-hoc sharing and browsing of repositories. It is
5256 5255 recommended to use a real web server to serve a repository for
5257 5256 longer periods of time.
5258 5257
5259 5258 Please note that the server does not implement access control.
5260 5259 This means that, by default, anybody can read from the server and
5261 5260 nobody can write to it by default. Set the ``web.allow-push``
5262 5261 option to ``*`` to allow everybody to push to the server. You
5263 5262 should use a real web server if you need to authenticate users.
5264 5263
5265 5264 By default, the server logs accesses to stdout and errors to
5266 5265 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5267 5266 files.
5268 5267
5269 5268 To have the server choose a free port number to listen on, specify
5270 5269 a port number of 0; in this case, the server will print the port
5271 5270 number it uses.
5272 5271
5273 5272 Returns 0 on success.
5274 5273 """
5275 5274
5276 5275 opts = pycompat.byteskwargs(opts)
5277 5276 if opts["stdio"] and opts["cmdserver"]:
5278 5277 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5279 5278 if opts["print_url"] and ui.verbose:
5280 5279 raise error.Abort(_("cannot use --print-url with --verbose"))
5281 5280
5282 5281 if opts["stdio"]:
5283 5282 if repo is None:
5284 5283 raise error.RepoError(_("there is no Mercurial repository here"
5285 5284 " (.hg not found)"))
5286 5285 s = wireprotoserver.sshserver(ui, repo)
5287 5286 s.serve_forever()
5288 5287
5289 5288 service = server.createservice(ui, repo, opts)
5290 5289 return server.runservice(opts, initfn=service.init, runfn=service.run)
5291 5290
5292 5291 _NOTTERSE = 'nothing'
5293 5292
5294 5293 @command('status|st',
5295 5294 [('A', 'all', None, _('show status of all files')),
5296 5295 ('m', 'modified', None, _('show only modified files')),
5297 5296 ('a', 'added', None, _('show only added files')),
5298 5297 ('r', 'removed', None, _('show only removed files')),
5299 5298 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5300 5299 ('c', 'clean', None, _('show only files without changes')),
5301 5300 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5302 5301 ('i', 'ignored', None, _('show only ignored files')),
5303 5302 ('n', 'no-status', None, _('hide status prefix')),
5304 5303 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5305 5304 ('C', 'copies', None, _('show source of copied files')),
5306 5305 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5307 5306 ('', 'rev', [], _('show difference from revision'), _('REV')),
5308 5307 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5309 5308 ] + walkopts + subrepoopts + formatteropts,
5310 5309 _('[OPTION]... [FILE]...'),
5311 5310 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5312 5311 helpbasic=True, inferrepo=True,
5313 5312 intents={INTENT_READONLY})
5314 5313 def status(ui, repo, *pats, **opts):
5315 5314 """show changed files in the working directory
5316 5315
5317 5316 Show status of files in the repository. If names are given, only
5318 5317 files that match are shown. Files that are clean or ignored or
5319 5318 the source of a copy/move operation, are not listed unless
5320 5319 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5321 5320 Unless options described with "show only ..." are given, the
5322 5321 options -mardu are used.
5323 5322
5324 5323 Option -q/--quiet hides untracked (unknown and ignored) files
5325 5324 unless explicitly requested with -u/--unknown or -i/--ignored.
5326 5325
5327 5326 .. note::
5328 5327
5329 5328 :hg:`status` may appear to disagree with diff if permissions have
5330 5329 changed or a merge has occurred. The standard diff format does
5331 5330 not report permission changes and diff only reports changes
5332 5331 relative to one merge parent.
5333 5332
5334 5333 If one revision is given, it is used as the base revision.
5335 5334 If two revisions are given, the differences between them are
5336 5335 shown. The --change option can also be used as a shortcut to list
5337 5336 the changed files of a revision from its first parent.
5338 5337
5339 5338 The codes used to show the status of files are::
5340 5339
5341 5340 M = modified
5342 5341 A = added
5343 5342 R = removed
5344 5343 C = clean
5345 5344 ! = missing (deleted by non-hg command, but still tracked)
5346 5345 ? = not tracked
5347 5346 I = ignored
5348 5347 = origin of the previous file (with --copies)
5349 5348
5350 5349 .. container:: verbose
5351 5350
5352 5351 The -t/--terse option abbreviates the output by showing only the directory
5353 5352 name if all the files in it share the same status. The option takes an
5354 5353 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5355 5354 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5356 5355 for 'ignored' and 'c' for clean.
5357 5356
5358 5357 It abbreviates only those statuses which are passed. Note that clean and
5359 5358 ignored files are not displayed with '--terse ic' unless the -c/--clean
5360 5359 and -i/--ignored options are also used.
5361 5360
5362 5361 The -v/--verbose option shows information when the repository is in an
5363 5362 unfinished merge, shelve, rebase state etc. You can have this behavior
5364 5363 turned on by default by enabling the ``commands.status.verbose`` option.
5365 5364
5366 5365 You can skip displaying some of these states by setting
5367 5366 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5368 5367 'histedit', 'merge', 'rebase', or 'unshelve'.
5369 5368
5370 5369 Template:
5371 5370
5372 5371 The following keywords are supported in addition to the common template
5373 5372 keywords and functions. See also :hg:`help templates`.
5374 5373
5375 5374 :path: String. Repository-absolute path of the file.
5376 5375 :source: String. Repository-absolute path of the file originated from.
5377 5376 Available if ``--copies`` is specified.
5378 5377 :status: String. Character denoting file's status.
5379 5378
5380 5379 Examples:
5381 5380
5382 5381 - show changes in the working directory relative to a
5383 5382 changeset::
5384 5383
5385 5384 hg status --rev 9353
5386 5385
5387 5386 - show changes in the working directory relative to the
5388 5387 current directory (see :hg:`help patterns` for more information)::
5389 5388
5390 5389 hg status re:
5391 5390
5392 5391 - show all changes including copies in an existing changeset::
5393 5392
5394 5393 hg status --copies --change 9353
5395 5394
5396 5395 - get a NUL separated list of added files, suitable for xargs::
5397 5396
5398 5397 hg status -an0
5399 5398
5400 5399 - show more information about the repository status, abbreviating
5401 5400 added, removed, modified, deleted, and untracked paths::
5402 5401
5403 5402 hg status -v -t mardu
5404 5403
5405 5404 Returns 0 on success.
5406 5405
5407 5406 """
5408 5407
5409 5408 opts = pycompat.byteskwargs(opts)
5410 5409 revs = opts.get('rev')
5411 5410 change = opts.get('change')
5412 5411 terse = opts.get('terse')
5413 5412 if terse is _NOTTERSE:
5414 5413 if revs:
5415 5414 terse = ''
5416 5415 else:
5417 5416 terse = ui.config('commands', 'status.terse')
5418 5417
5419 5418 if revs and change:
5420 5419 msg = _('cannot specify --rev and --change at the same time')
5421 5420 raise error.Abort(msg)
5422 5421 elif revs and terse:
5423 5422 msg = _('cannot use --terse with --rev')
5424 5423 raise error.Abort(msg)
5425 5424 elif change:
5426 5425 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5427 5426 ctx2 = scmutil.revsingle(repo, change, None)
5428 5427 ctx1 = ctx2.p1()
5429 5428 else:
5430 5429 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5431 5430 ctx1, ctx2 = scmutil.revpair(repo, revs)
5432 5431
5433 5432 forcerelativevalue = None
5434 5433 if ui.hasconfig('commands', 'status.relative'):
5435 5434 forcerelativevalue = ui.configbool('commands', 'status.relative')
5436 5435 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats),
5437 5436 forcerelativevalue=forcerelativevalue)
5438 5437
5439 5438 if opts.get('print0'):
5440 5439 end = '\0'
5441 5440 else:
5442 5441 end = '\n'
5443 5442 copy = {}
5444 5443 states = 'modified added removed deleted unknown ignored clean'.split()
5445 5444 show = [k for k in states if opts.get(k)]
5446 5445 if opts.get('all'):
5447 5446 show += ui.quiet and (states[:4] + ['clean']) or states
5448 5447
5449 5448 if not show:
5450 5449 if ui.quiet:
5451 5450 show = states[:4]
5452 5451 else:
5453 5452 show = states[:5]
5454 5453
5455 5454 m = scmutil.match(ctx2, pats, opts)
5456 5455 if terse:
5457 5456 # we need to compute clean and unknown to terse
5458 5457 stat = repo.status(ctx1.node(), ctx2.node(), m,
5459 5458 'ignored' in show or 'i' in terse,
5460 5459 clean=True, unknown=True,
5461 5460 listsubrepos=opts.get('subrepos'))
5462 5461
5463 5462 stat = cmdutil.tersedir(stat, terse)
5464 5463 else:
5465 5464 stat = repo.status(ctx1.node(), ctx2.node(), m,
5466 5465 'ignored' in show, 'clean' in show,
5467 5466 'unknown' in show, opts.get('subrepos'))
5468 5467
5469 5468 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5470 5469
5471 5470 if (opts.get('all') or opts.get('copies')
5472 5471 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5473 5472 copy = copies.pathcopies(ctx1, ctx2, m)
5474 5473
5475 5474 ui.pager('status')
5476 5475 fm = ui.formatter('status', opts)
5477 5476 fmt = '%s' + end
5478 5477 showchar = not opts.get('no_status')
5479 5478
5480 5479 for state, char, files in changestates:
5481 5480 if state in show:
5482 5481 label = 'status.' + state
5483 5482 for f in files:
5484 5483 fm.startitem()
5485 5484 fm.context(ctx=ctx2)
5486 5485 fm.data(path=f)
5487 5486 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5488 5487 fm.plain(fmt % uipathfn(f), label=label)
5489 5488 if f in copy:
5490 5489 fm.data(source=copy[f])
5491 5490 fm.plain((' %s' + end) % uipathfn(copy[f]),
5492 5491 label='status.copied')
5493 5492
5494 5493 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5495 5494 and not ui.plain()):
5496 5495 cmdutil.morestatus(repo, fm)
5497 5496 fm.end()
5498 5497
5499 5498 @command('summary|sum',
5500 5499 [('', 'remote', None, _('check for push and pull'))],
5501 5500 '[--remote]',
5502 5501 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5503 5502 helpbasic=True,
5504 5503 intents={INTENT_READONLY})
5505 5504 def summary(ui, repo, **opts):
5506 5505 """summarize working directory state
5507 5506
5508 5507 This generates a brief summary of the working directory state,
5509 5508 including parents, branch, commit status, phase and available updates.
5510 5509
5511 5510 With the --remote option, this will check the default paths for
5512 5511 incoming and outgoing changes. This can be time-consuming.
5513 5512
5514 5513 Returns 0 on success.
5515 5514 """
5516 5515
5517 5516 opts = pycompat.byteskwargs(opts)
5518 5517 ui.pager('summary')
5519 5518 ctx = repo[None]
5520 5519 parents = ctx.parents()
5521 5520 pnode = parents[0].node()
5522 5521 marks = []
5523 5522
5524 5523 try:
5525 5524 ms = mergemod.mergestate.read(repo)
5526 5525 except error.UnsupportedMergeRecords as e:
5527 5526 s = ' '.join(e.recordtypes)
5528 5527 ui.warn(
5529 5528 _('warning: merge state has unsupported record types: %s\n') % s)
5530 5529 unresolved = []
5531 5530 else:
5532 5531 unresolved = list(ms.unresolved())
5533 5532
5534 5533 for p in parents:
5535 5534 # label with log.changeset (instead of log.parent) since this
5536 5535 # shows a working directory parent *changeset*:
5537 5536 # i18n: column positioning for "hg summary"
5538 5537 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5539 5538 label=logcmdutil.changesetlabels(p))
5540 5539 ui.write(' '.join(p.tags()), label='log.tag')
5541 5540 if p.bookmarks():
5542 5541 marks.extend(p.bookmarks())
5543 5542 if p.rev() == -1:
5544 5543 if not len(repo):
5545 5544 ui.write(_(' (empty repository)'))
5546 5545 else:
5547 5546 ui.write(_(' (no revision checked out)'))
5548 5547 if p.obsolete():
5549 5548 ui.write(_(' (obsolete)'))
5550 5549 if p.isunstable():
5551 5550 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5552 5551 for instability in p.instabilities())
5553 5552 ui.write(' ('
5554 5553 + ', '.join(instabilities)
5555 5554 + ')')
5556 5555 ui.write('\n')
5557 5556 if p.description():
5558 5557 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5559 5558 label='log.summary')
5560 5559
5561 5560 branch = ctx.branch()
5562 5561 bheads = repo.branchheads(branch)
5563 5562 # i18n: column positioning for "hg summary"
5564 5563 m = _('branch: %s\n') % branch
5565 5564 if branch != 'default':
5566 5565 ui.write(m, label='log.branch')
5567 5566 else:
5568 5567 ui.status(m, label='log.branch')
5569 5568
5570 5569 if marks:
5571 5570 active = repo._activebookmark
5572 5571 # i18n: column positioning for "hg summary"
5573 5572 ui.write(_('bookmarks:'), label='log.bookmark')
5574 5573 if active is not None:
5575 5574 if active in marks:
5576 5575 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5577 5576 marks.remove(active)
5578 5577 else:
5579 5578 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5580 5579 for m in marks:
5581 5580 ui.write(' ' + m, label='log.bookmark')
5582 5581 ui.write('\n', label='log.bookmark')
5583 5582
5584 5583 status = repo.status(unknown=True)
5585 5584
5586 5585 c = repo.dirstate.copies()
5587 5586 copied, renamed = [], []
5588 5587 for d, s in c.iteritems():
5589 5588 if s in status.removed:
5590 5589 status.removed.remove(s)
5591 5590 renamed.append(d)
5592 5591 else:
5593 5592 copied.append(d)
5594 5593 if d in status.added:
5595 5594 status.added.remove(d)
5596 5595
5597 5596 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5598 5597
5599 5598 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5600 5599 (ui.label(_('%d added'), 'status.added'), status.added),
5601 5600 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5602 5601 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5603 5602 (ui.label(_('%d copied'), 'status.copied'), copied),
5604 5603 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5605 5604 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5606 5605 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5607 5606 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5608 5607 t = []
5609 5608 for l, s in labels:
5610 5609 if s:
5611 5610 t.append(l % len(s))
5612 5611
5613 5612 t = ', '.join(t)
5614 5613 cleanworkdir = False
5615 5614
5616 5615 if repo.vfs.exists('graftstate'):
5617 5616 t += _(' (graft in progress)')
5618 5617 if repo.vfs.exists('updatestate'):
5619 5618 t += _(' (interrupted update)')
5620 5619 elif len(parents) > 1:
5621 5620 t += _(' (merge)')
5622 5621 elif branch != parents[0].branch():
5623 5622 t += _(' (new branch)')
5624 5623 elif (parents[0].closesbranch() and
5625 5624 pnode in repo.branchheads(branch, closed=True)):
5626 5625 t += _(' (head closed)')
5627 5626 elif not (status.modified or status.added or status.removed or renamed or
5628 5627 copied or subs):
5629 5628 t += _(' (clean)')
5630 5629 cleanworkdir = True
5631 5630 elif pnode not in bheads:
5632 5631 t += _(' (new branch head)')
5633 5632
5634 5633 if parents:
5635 5634 pendingphase = max(p.phase() for p in parents)
5636 5635 else:
5637 5636 pendingphase = phases.public
5638 5637
5639 5638 if pendingphase > phases.newcommitphase(ui):
5640 5639 t += ' (%s)' % phases.phasenames[pendingphase]
5641 5640
5642 5641 if cleanworkdir:
5643 5642 # i18n: column positioning for "hg summary"
5644 5643 ui.status(_('commit: %s\n') % t.strip())
5645 5644 else:
5646 5645 # i18n: column positioning for "hg summary"
5647 5646 ui.write(_('commit: %s\n') % t.strip())
5648 5647
5649 5648 # all ancestors of branch heads - all ancestors of parent = new csets
5650 5649 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5651 5650 bheads))
5652 5651
5653 5652 if new == 0:
5654 5653 # i18n: column positioning for "hg summary"
5655 5654 ui.status(_('update: (current)\n'))
5656 5655 elif pnode not in bheads:
5657 5656 # i18n: column positioning for "hg summary"
5658 5657 ui.write(_('update: %d new changesets (update)\n') % new)
5659 5658 else:
5660 5659 # i18n: column positioning for "hg summary"
5661 5660 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5662 5661 (new, len(bheads)))
5663 5662
5664 5663 t = []
5665 5664 draft = len(repo.revs('draft()'))
5666 5665 if draft:
5667 5666 t.append(_('%d draft') % draft)
5668 5667 secret = len(repo.revs('secret()'))
5669 5668 if secret:
5670 5669 t.append(_('%d secret') % secret)
5671 5670
5672 5671 if draft or secret:
5673 5672 ui.status(_('phases: %s\n') % ', '.join(t))
5674 5673
5675 5674 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5676 5675 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5677 5676 numtrouble = len(repo.revs(trouble + "()"))
5678 5677 # We write all the possibilities to ease translation
5679 5678 troublemsg = {
5680 5679 "orphan": _("orphan: %d changesets"),
5681 5680 "contentdivergent": _("content-divergent: %d changesets"),
5682 5681 "phasedivergent": _("phase-divergent: %d changesets"),
5683 5682 }
5684 5683 if numtrouble > 0:
5685 5684 ui.status(troublemsg[trouble] % numtrouble + "\n")
5686 5685
5687 5686 cmdutil.summaryhooks(ui, repo)
5688 5687
5689 5688 if opts.get('remote'):
5690 5689 needsincoming, needsoutgoing = True, True
5691 5690 else:
5692 5691 needsincoming, needsoutgoing = False, False
5693 5692 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5694 5693 if i:
5695 5694 needsincoming = True
5696 5695 if o:
5697 5696 needsoutgoing = True
5698 5697 if not needsincoming and not needsoutgoing:
5699 5698 return
5700 5699
5701 5700 def getincoming():
5702 5701 source, branches = hg.parseurl(ui.expandpath('default'))
5703 5702 sbranch = branches[0]
5704 5703 try:
5705 5704 other = hg.peer(repo, {}, source)
5706 5705 except error.RepoError:
5707 5706 if opts.get('remote'):
5708 5707 raise
5709 5708 return source, sbranch, None, None, None
5710 5709 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5711 5710 if revs:
5712 5711 revs = [other.lookup(rev) for rev in revs]
5713 5712 ui.debug('comparing with %s\n' % util.hidepassword(source))
5714 5713 repo.ui.pushbuffer()
5715 5714 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5716 5715 repo.ui.popbuffer()
5717 5716 return source, sbranch, other, commoninc, commoninc[1]
5718 5717
5719 5718 if needsincoming:
5720 5719 source, sbranch, sother, commoninc, incoming = getincoming()
5721 5720 else:
5722 5721 source = sbranch = sother = commoninc = incoming = None
5723 5722
5724 5723 def getoutgoing():
5725 5724 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5726 5725 dbranch = branches[0]
5727 5726 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5728 5727 if source != dest:
5729 5728 try:
5730 5729 dother = hg.peer(repo, {}, dest)
5731 5730 except error.RepoError:
5732 5731 if opts.get('remote'):
5733 5732 raise
5734 5733 return dest, dbranch, None, None
5735 5734 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5736 5735 elif sother is None:
5737 5736 # there is no explicit destination peer, but source one is invalid
5738 5737 return dest, dbranch, None, None
5739 5738 else:
5740 5739 dother = sother
5741 5740 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5742 5741 common = None
5743 5742 else:
5744 5743 common = commoninc
5745 5744 if revs:
5746 5745 revs = [repo.lookup(rev) for rev in revs]
5747 5746 repo.ui.pushbuffer()
5748 5747 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5749 5748 commoninc=common)
5750 5749 repo.ui.popbuffer()
5751 5750 return dest, dbranch, dother, outgoing
5752 5751
5753 5752 if needsoutgoing:
5754 5753 dest, dbranch, dother, outgoing = getoutgoing()
5755 5754 else:
5756 5755 dest = dbranch = dother = outgoing = None
5757 5756
5758 5757 if opts.get('remote'):
5759 5758 t = []
5760 5759 if incoming:
5761 5760 t.append(_('1 or more incoming'))
5762 5761 o = outgoing.missing
5763 5762 if o:
5764 5763 t.append(_('%d outgoing') % len(o))
5765 5764 other = dother or sother
5766 5765 if 'bookmarks' in other.listkeys('namespaces'):
5767 5766 counts = bookmarks.summary(repo, other)
5768 5767 if counts[0] > 0:
5769 5768 t.append(_('%d incoming bookmarks') % counts[0])
5770 5769 if counts[1] > 0:
5771 5770 t.append(_('%d outgoing bookmarks') % counts[1])
5772 5771
5773 5772 if t:
5774 5773 # i18n: column positioning for "hg summary"
5775 5774 ui.write(_('remote: %s\n') % (', '.join(t)))
5776 5775 else:
5777 5776 # i18n: column positioning for "hg summary"
5778 5777 ui.status(_('remote: (synced)\n'))
5779 5778
5780 5779 cmdutil.summaryremotehooks(ui, repo, opts,
5781 5780 ((source, sbranch, sother, commoninc),
5782 5781 (dest, dbranch, dother, outgoing)))
5783 5782
5784 5783 @command('tag',
5785 5784 [('f', 'force', None, _('force tag')),
5786 5785 ('l', 'local', None, _('make the tag local')),
5787 5786 ('r', 'rev', '', _('revision to tag'), _('REV')),
5788 5787 ('', 'remove', None, _('remove a tag')),
5789 5788 # -l/--local is already there, commitopts cannot be used
5790 5789 ('e', 'edit', None, _('invoke editor on commit messages')),
5791 5790 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5792 5791 ] + commitopts2,
5793 5792 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5794 5793 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5795 5794 def tag(ui, repo, name1, *names, **opts):
5796 5795 """add one or more tags for the current or given revision
5797 5796
5798 5797 Name a particular revision using <name>.
5799 5798
5800 5799 Tags are used to name particular revisions of the repository and are
5801 5800 very useful to compare different revisions, to go back to significant
5802 5801 earlier versions or to mark branch points as releases, etc. Changing
5803 5802 an existing tag is normally disallowed; use -f/--force to override.
5804 5803
5805 5804 If no revision is given, the parent of the working directory is
5806 5805 used.
5807 5806
5808 5807 To facilitate version control, distribution, and merging of tags,
5809 5808 they are stored as a file named ".hgtags" which is managed similarly
5810 5809 to other project files and can be hand-edited if necessary. This
5811 5810 also means that tagging creates a new commit. The file
5812 5811 ".hg/localtags" is used for local tags (not shared among
5813 5812 repositories).
5814 5813
5815 5814 Tag commits are usually made at the head of a branch. If the parent
5816 5815 of the working directory is not a branch head, :hg:`tag` aborts; use
5817 5816 -f/--force to force the tag commit to be based on a non-head
5818 5817 changeset.
5819 5818
5820 5819 See :hg:`help dates` for a list of formats valid for -d/--date.
5821 5820
5822 5821 Since tag names have priority over branch names during revision
5823 5822 lookup, using an existing branch name as a tag name is discouraged.
5824 5823
5825 5824 Returns 0 on success.
5826 5825 """
5827 5826 opts = pycompat.byteskwargs(opts)
5828 5827 with repo.wlock(), repo.lock():
5829 5828 rev_ = "."
5830 5829 names = [t.strip() for t in (name1,) + names]
5831 5830 if len(names) != len(set(names)):
5832 5831 raise error.Abort(_('tag names must be unique'))
5833 5832 for n in names:
5834 5833 scmutil.checknewlabel(repo, n, 'tag')
5835 5834 if not n:
5836 5835 raise error.Abort(_('tag names cannot consist entirely of '
5837 5836 'whitespace'))
5838 5837 if opts.get('rev') and opts.get('remove'):
5839 5838 raise error.Abort(_("--rev and --remove are incompatible"))
5840 5839 if opts.get('rev'):
5841 5840 rev_ = opts['rev']
5842 5841 message = opts.get('message')
5843 5842 if opts.get('remove'):
5844 5843 if opts.get('local'):
5845 5844 expectedtype = 'local'
5846 5845 else:
5847 5846 expectedtype = 'global'
5848 5847
5849 5848 for n in names:
5850 5849 if repo.tagtype(n) == 'global':
5851 5850 alltags = tagsmod.findglobaltags(ui, repo)
5852 5851 if alltags[n][0] == nullid:
5853 5852 raise error.Abort(_("tag '%s' is already removed") % n)
5854 5853 if not repo.tagtype(n):
5855 5854 raise error.Abort(_("tag '%s' does not exist") % n)
5856 5855 if repo.tagtype(n) != expectedtype:
5857 5856 if expectedtype == 'global':
5858 5857 raise error.Abort(_("tag '%s' is not a global tag") % n)
5859 5858 else:
5860 5859 raise error.Abort(_("tag '%s' is not a local tag") % n)
5861 5860 rev_ = 'null'
5862 5861 if not message:
5863 5862 # we don't translate commit messages
5864 5863 message = 'Removed tag %s' % ', '.join(names)
5865 5864 elif not opts.get('force'):
5866 5865 for n in names:
5867 5866 if n in repo.tags():
5868 5867 raise error.Abort(_("tag '%s' already exists "
5869 5868 "(use -f to force)") % n)
5870 5869 if not opts.get('local'):
5871 5870 p1, p2 = repo.dirstate.parents()
5872 5871 if p2 != nullid:
5873 5872 raise error.Abort(_('uncommitted merge'))
5874 5873 bheads = repo.branchheads()
5875 5874 if not opts.get('force') and bheads and p1 not in bheads:
5876 5875 raise error.Abort(_('working directory is not at a branch head '
5877 5876 '(use -f to force)'))
5878 5877 node = scmutil.revsingle(repo, rev_).node()
5879 5878
5880 5879 if not message:
5881 5880 # we don't translate commit messages
5882 5881 message = ('Added tag %s for changeset %s' %
5883 5882 (', '.join(names), short(node)))
5884 5883
5885 5884 date = opts.get('date')
5886 5885 if date:
5887 5886 date = dateutil.parsedate(date)
5888 5887
5889 5888 if opts.get('remove'):
5890 5889 editform = 'tag.remove'
5891 5890 else:
5892 5891 editform = 'tag.add'
5893 5892 editor = cmdutil.getcommiteditor(editform=editform,
5894 5893 **pycompat.strkwargs(opts))
5895 5894
5896 5895 # don't allow tagging the null rev
5897 5896 if (not opts.get('remove') and
5898 5897 scmutil.revsingle(repo, rev_).rev() == nullrev):
5899 5898 raise error.Abort(_("cannot tag null revision"))
5900 5899
5901 5900 tagsmod.tag(repo, names, node, message, opts.get('local'),
5902 5901 opts.get('user'), date, editor=editor)
5903 5902
5904 5903 @command(
5905 5904 'tags', formatteropts, '',
5906 5905 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5907 5906 intents={INTENT_READONLY})
5908 5907 def tags(ui, repo, **opts):
5909 5908 """list repository tags
5910 5909
5911 5910 This lists both regular and local tags. When the -v/--verbose
5912 5911 switch is used, a third column "local" is printed for local tags.
5913 5912 When the -q/--quiet switch is used, only the tag name is printed.
5914 5913
5915 5914 .. container:: verbose
5916 5915
5917 5916 Template:
5918 5917
5919 5918 The following keywords are supported in addition to the common template
5920 5919 keywords and functions such as ``{tag}``. See also
5921 5920 :hg:`help templates`.
5922 5921
5923 5922 :type: String. ``local`` for local tags.
5924 5923
5925 5924 Returns 0 on success.
5926 5925 """
5927 5926
5928 5927 opts = pycompat.byteskwargs(opts)
5929 5928 ui.pager('tags')
5930 5929 fm = ui.formatter('tags', opts)
5931 5930 hexfunc = fm.hexfunc
5932 5931
5933 5932 for t, n in reversed(repo.tagslist()):
5934 5933 hn = hexfunc(n)
5935 5934 label = 'tags.normal'
5936 5935 tagtype = ''
5937 5936 if repo.tagtype(t) == 'local':
5938 5937 label = 'tags.local'
5939 5938 tagtype = 'local'
5940 5939
5941 5940 fm.startitem()
5942 5941 fm.context(repo=repo)
5943 5942 fm.write('tag', '%s', t, label=label)
5944 5943 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5945 5944 fm.condwrite(not ui.quiet, 'rev node', fmt,
5946 5945 repo.changelog.rev(n), hn, label=label)
5947 5946 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5948 5947 tagtype, label=label)
5949 5948 fm.plain('\n')
5950 5949 fm.end()
5951 5950
5952 5951 @command('tip',
5953 5952 [('p', 'patch', None, _('show patch')),
5954 5953 ('g', 'git', None, _('use git extended diff format')),
5955 5954 ] + templateopts,
5956 5955 _('[-p] [-g]'),
5957 5956 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
5958 5957 def tip(ui, repo, **opts):
5959 5958 """show the tip revision (DEPRECATED)
5960 5959
5961 5960 The tip revision (usually just called the tip) is the changeset
5962 5961 most recently added to the repository (and therefore the most
5963 5962 recently changed head).
5964 5963
5965 5964 If you have just made a commit, that commit will be the tip. If
5966 5965 you have just pulled changes from another repository, the tip of
5967 5966 that repository becomes the current tip. The "tip" tag is special
5968 5967 and cannot be renamed or assigned to a different changeset.
5969 5968
5970 5969 This command is deprecated, please use :hg:`heads` instead.
5971 5970
5972 5971 Returns 0 on success.
5973 5972 """
5974 5973 opts = pycompat.byteskwargs(opts)
5975 5974 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5976 5975 displayer.show(repo['tip'])
5977 5976 displayer.close()
5978 5977
5979 5978 @command('unbundle',
5980 5979 [('u', 'update', None,
5981 5980 _('update to new branch head if changesets were unbundled'))],
5982 5981 _('[-u] FILE...'),
5983 5982 helpcategory=command.CATEGORY_IMPORT_EXPORT)
5984 5983 def unbundle(ui, repo, fname1, *fnames, **opts):
5985 5984 """apply one or more bundle files
5986 5985
5987 5986 Apply one or more bundle files generated by :hg:`bundle`.
5988 5987
5989 5988 Returns 0 on success, 1 if an update has unresolved files.
5990 5989 """
5991 5990 fnames = (fname1,) + fnames
5992 5991
5993 5992 with repo.lock():
5994 5993 for fname in fnames:
5995 5994 f = hg.openpath(ui, fname)
5996 5995 gen = exchange.readbundle(ui, f, fname)
5997 5996 if isinstance(gen, streamclone.streamcloneapplier):
5998 5997 raise error.Abort(
5999 5998 _('packed bundles cannot be applied with '
6000 5999 '"hg unbundle"'),
6001 6000 hint=_('use "hg debugapplystreamclonebundle"'))
6002 6001 url = 'bundle:' + fname
6003 6002 try:
6004 6003 txnname = 'unbundle'
6005 6004 if not isinstance(gen, bundle2.unbundle20):
6006 6005 txnname = 'unbundle\n%s' % util.hidepassword(url)
6007 6006 with repo.transaction(txnname) as tr:
6008 6007 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
6009 6008 url=url)
6010 6009 except error.BundleUnknownFeatureError as exc:
6011 6010 raise error.Abort(
6012 6011 _('%s: unknown bundle feature, %s') % (fname, exc),
6013 6012 hint=_("see https://mercurial-scm.org/"
6014 6013 "wiki/BundleFeature for more "
6015 6014 "information"))
6016 6015 modheads = bundle2.combinechangegroupresults(op)
6017 6016
6018 6017 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
6019 6018
6020 6019 @command('update|up|checkout|co',
6021 6020 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6022 6021 ('c', 'check', None, _('require clean working directory')),
6023 6022 ('m', 'merge', None, _('merge uncommitted changes')),
6024 6023 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6025 6024 ('r', 'rev', '', _('revision'), _('REV'))
6026 6025 ] + mergetoolopts,
6027 6026 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
6028 6027 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6029 6028 helpbasic=True)
6030 6029 def update(ui, repo, node=None, **opts):
6031 6030 """update working directory (or switch revisions)
6032 6031
6033 6032 Update the repository's working directory to the specified
6034 6033 changeset. If no changeset is specified, update to the tip of the
6035 6034 current named branch and move the active bookmark (see :hg:`help
6036 6035 bookmarks`).
6037 6036
6038 6037 Update sets the working directory's parent revision to the specified
6039 6038 changeset (see :hg:`help parents`).
6040 6039
6041 6040 If the changeset is not a descendant or ancestor of the working
6042 6041 directory's parent and there are uncommitted changes, the update is
6043 6042 aborted. With the -c/--check option, the working directory is checked
6044 6043 for uncommitted changes; if none are found, the working directory is
6045 6044 updated to the specified changeset.
6046 6045
6047 6046 .. container:: verbose
6048 6047
6049 6048 The -C/--clean, -c/--check, and -m/--merge options control what
6050 6049 happens if the working directory contains uncommitted changes.
6051 6050 At most of one of them can be specified.
6052 6051
6053 6052 1. If no option is specified, and if
6054 6053 the requested changeset is an ancestor or descendant of
6055 6054 the working directory's parent, the uncommitted changes
6056 6055 are merged into the requested changeset and the merged
6057 6056 result is left uncommitted. If the requested changeset is
6058 6057 not an ancestor or descendant (that is, it is on another
6059 6058 branch), the update is aborted and the uncommitted changes
6060 6059 are preserved.
6061 6060
6062 6061 2. With the -m/--merge option, the update is allowed even if the
6063 6062 requested changeset is not an ancestor or descendant of
6064 6063 the working directory's parent.
6065 6064
6066 6065 3. With the -c/--check option, the update is aborted and the
6067 6066 uncommitted changes are preserved.
6068 6067
6069 6068 4. With the -C/--clean option, uncommitted changes are discarded and
6070 6069 the working directory is updated to the requested changeset.
6071 6070
6072 6071 To cancel an uncommitted merge (and lose your changes), use
6073 6072 :hg:`merge --abort`.
6074 6073
6075 6074 Use null as the changeset to remove the working directory (like
6076 6075 :hg:`clone -U`).
6077 6076
6078 6077 If you want to revert just one file to an older revision, use
6079 6078 :hg:`revert [-r REV] NAME`.
6080 6079
6081 6080 See :hg:`help dates` for a list of formats valid for -d/--date.
6082 6081
6083 6082 Returns 0 on success, 1 if there are unresolved files.
6084 6083 """
6085 6084 rev = opts.get(r'rev')
6086 6085 date = opts.get(r'date')
6087 6086 clean = opts.get(r'clean')
6088 6087 check = opts.get(r'check')
6089 6088 merge = opts.get(r'merge')
6090 6089 if rev and node:
6091 6090 raise error.Abort(_("please specify just one revision"))
6092 6091
6093 6092 if ui.configbool('commands', 'update.requiredest'):
6094 6093 if not node and not rev and not date:
6095 6094 raise error.Abort(_('you must specify a destination'),
6096 6095 hint=_('for example: hg update ".::"'))
6097 6096
6098 6097 if rev is None or rev == '':
6099 6098 rev = node
6100 6099
6101 6100 if date and rev is not None:
6102 6101 raise error.Abort(_("you can't specify a revision and a date"))
6103 6102
6104 6103 if len([x for x in (clean, check, merge) if x]) > 1:
6105 6104 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6106 6105 "or -m/--merge"))
6107 6106
6108 6107 updatecheck = None
6109 6108 if check:
6110 6109 updatecheck = 'abort'
6111 6110 elif merge:
6112 6111 updatecheck = 'none'
6113 6112
6114 6113 with repo.wlock():
6115 6114 cmdutil.clearunfinished(repo)
6116 6115
6117 6116 if date:
6118 6117 rev = cmdutil.finddate(ui, repo, date)
6119 6118
6120 6119 # if we defined a bookmark, we have to remember the original name
6121 6120 brev = rev
6122 6121 if rev:
6123 6122 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6124 6123 ctx = scmutil.revsingle(repo, rev, default=None)
6125 6124 rev = ctx.rev()
6126 6125 hidden = ctx.hidden()
6127 6126 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6128 6127 with ui.configoverride(overrides, 'update'):
6129 6128 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6130 6129 updatecheck=updatecheck)
6131 6130 if hidden:
6132 6131 ctxstr = ctx.hex()[:12]
6133 6132 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6134 6133
6135 6134 if ctx.obsolete():
6136 6135 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6137 6136 ui.warn("(%s)\n" % obsfatemsg)
6138 6137 return ret
6139 6138
6140 6139 @command('verify', [], helpcategory=command.CATEGORY_MAINTENANCE)
6141 6140 def verify(ui, repo):
6142 6141 """verify the integrity of the repository
6143 6142
6144 6143 Verify the integrity of the current repository.
6145 6144
6146 6145 This will perform an extensive check of the repository's
6147 6146 integrity, validating the hashes and checksums of each entry in
6148 6147 the changelog, manifest, and tracked files, as well as the
6149 6148 integrity of their crosslinks and indices.
6150 6149
6151 6150 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6152 6151 for more information about recovery from corruption of the
6153 6152 repository.
6154 6153
6155 6154 Returns 0 on success, 1 if errors are encountered.
6156 6155 """
6157 6156 return hg.verify(repo)
6158 6157
6159 6158 @command(
6160 6159 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6161 6160 norepo=True, intents={INTENT_READONLY})
6162 6161 def version_(ui, **opts):
6163 6162 """output version and copyright information
6164 6163
6165 6164 .. container:: verbose
6166 6165
6167 6166 Template:
6168 6167
6169 6168 The following keywords are supported. See also :hg:`help templates`.
6170 6169
6171 6170 :extensions: List of extensions.
6172 6171 :ver: String. Version number.
6173 6172
6174 6173 And each entry of ``{extensions}`` provides the following sub-keywords
6175 6174 in addition to ``{ver}``.
6176 6175
6177 6176 :bundled: Boolean. True if included in the release.
6178 6177 :name: String. Extension name.
6179 6178 """
6180 6179 opts = pycompat.byteskwargs(opts)
6181 6180 if ui.verbose:
6182 6181 ui.pager('version')
6183 6182 fm = ui.formatter("version", opts)
6184 6183 fm.startitem()
6185 6184 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6186 6185 util.version())
6187 6186 license = _(
6188 6187 "(see https://mercurial-scm.org for more information)\n"
6189 6188 "\nCopyright (C) 2005-2019 Matt Mackall and others\n"
6190 6189 "This is free software; see the source for copying conditions. "
6191 6190 "There is NO\nwarranty; "
6192 6191 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6193 6192 )
6194 6193 if not ui.quiet:
6195 6194 fm.plain(license)
6196 6195
6197 6196 if ui.verbose:
6198 6197 fm.plain(_("\nEnabled extensions:\n\n"))
6199 6198 # format names and versions into columns
6200 6199 names = []
6201 6200 vers = []
6202 6201 isinternals = []
6203 6202 for name, module in extensions.extensions():
6204 6203 names.append(name)
6205 6204 vers.append(extensions.moduleversion(module) or None)
6206 6205 isinternals.append(extensions.ismoduleinternal(module))
6207 6206 fn = fm.nested("extensions", tmpl='{name}\n')
6208 6207 if names:
6209 6208 namefmt = " %%-%ds " % max(len(n) for n in names)
6210 6209 places = [_("external"), _("internal")]
6211 6210 for n, v, p in zip(names, vers, isinternals):
6212 6211 fn.startitem()
6213 6212 fn.condwrite(ui.verbose, "name", namefmt, n)
6214 6213 if ui.verbose:
6215 6214 fn.plain("%s " % places[p])
6216 6215 fn.data(bundled=p)
6217 6216 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6218 6217 if ui.verbose:
6219 6218 fn.plain("\n")
6220 6219 fn.end()
6221 6220 fm.end()
6222 6221
6223 6222 def loadcmdtable(ui, name, cmdtable):
6224 6223 """Load command functions from specified cmdtable
6225 6224 """
6226 6225 cmdtable = cmdtable.copy()
6227 6226 for cmd in list(cmdtable):
6228 6227 if not cmd.startswith('^'):
6229 6228 continue
6230 6229 ui.deprecwarn("old-style command registration '%s' in extension '%s'"
6231 6230 % (cmd, name), '4.8')
6232 6231 entry = cmdtable.pop(cmd)
6233 6232 entry[0].helpbasic = True
6234 6233 cmdtable[cmd[1:]] = entry
6235 6234
6236 6235 overrides = [cmd for cmd in cmdtable if cmd in table]
6237 6236 if overrides:
6238 6237 ui.warn(_("extension '%s' overrides commands: %s\n")
6239 6238 % (name, " ".join(overrides)))
6240 6239 table.update(cmdtable)
@@ -1,1860 +1,1891 b''
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright 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 glob
12 12 import hashlib
13 13 import os
14 14 import posixpath
15 15 import re
16 16 import subprocess
17 17 import weakref
18 18
19 19 from .i18n import _
20 20 from .node import (
21 21 bin,
22 22 hex,
23 23 nullid,
24 24 nullrev,
25 25 short,
26 26 wdirid,
27 27 wdirrev,
28 28 )
29 29
30 30 from . import (
31 31 encoding,
32 32 error,
33 33 match as matchmod,
34 34 obsolete,
35 35 obsutil,
36 36 pathutil,
37 37 phases,
38 38 policy,
39 39 pycompat,
40 40 revsetlang,
41 41 similar,
42 42 smartset,
43 43 url,
44 44 util,
45 45 vfs,
46 46 )
47 47
48 48 from .utils import (
49 49 procutil,
50 50 stringutil,
51 51 )
52 52
53 53 if pycompat.iswindows:
54 54 from . import scmwindows as scmplatform
55 55 else:
56 56 from . import scmposix as scmplatform
57 57
58 58 parsers = policy.importmod(r'parsers')
59 59
60 60 termsize = scmplatform.termsize
61 61
62 62 class status(tuple):
63 63 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
64 64 and 'ignored' properties are only relevant to the working copy.
65 65 '''
66 66
67 67 __slots__ = ()
68 68
69 69 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
70 70 clean):
71 71 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
72 72 ignored, clean))
73 73
74 74 @property
75 75 def modified(self):
76 76 '''files that have been modified'''
77 77 return self[0]
78 78
79 79 @property
80 80 def added(self):
81 81 '''files that have been added'''
82 82 return self[1]
83 83
84 84 @property
85 85 def removed(self):
86 86 '''files that have been removed'''
87 87 return self[2]
88 88
89 89 @property
90 90 def deleted(self):
91 91 '''files that are in the dirstate, but have been deleted from the
92 92 working copy (aka "missing")
93 93 '''
94 94 return self[3]
95 95
96 96 @property
97 97 def unknown(self):
98 98 '''files not in the dirstate that are not ignored'''
99 99 return self[4]
100 100
101 101 @property
102 102 def ignored(self):
103 103 '''files not in the dirstate that are ignored (by _dirignore())'''
104 104 return self[5]
105 105
106 106 @property
107 107 def clean(self):
108 108 '''files that have not been modified'''
109 109 return self[6]
110 110
111 111 def __repr__(self, *args, **kwargs):
112 112 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
113 113 r'unknown=%s, ignored=%s, clean=%s>') %
114 114 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
115 115
116 116 def itersubrepos(ctx1, ctx2):
117 117 """find subrepos in ctx1 or ctx2"""
118 118 # Create a (subpath, ctx) mapping where we prefer subpaths from
119 119 # ctx1. The subpaths from ctx2 are important when the .hgsub file
120 120 # has been modified (in ctx2) but not yet committed (in ctx1).
121 121 subpaths = dict.fromkeys(ctx2.substate, ctx2)
122 122 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
123 123
124 124 missing = set()
125 125
126 126 for subpath in ctx2.substate:
127 127 if subpath not in ctx1.substate:
128 128 del subpaths[subpath]
129 129 missing.add(subpath)
130 130
131 131 for subpath, ctx in sorted(subpaths.iteritems()):
132 132 yield subpath, ctx.sub(subpath)
133 133
134 134 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
135 135 # status and diff will have an accurate result when it does
136 136 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
137 137 # against itself.
138 138 for subpath in missing:
139 139 yield subpath, ctx2.nullsub(subpath, ctx1)
140 140
141 141 def nochangesfound(ui, repo, excluded=None):
142 142 '''Report no changes for push/pull, excluded is None or a list of
143 143 nodes excluded from the push/pull.
144 144 '''
145 145 secretlist = []
146 146 if excluded:
147 147 for n in excluded:
148 148 ctx = repo[n]
149 149 if ctx.phase() >= phases.secret and not ctx.extinct():
150 150 secretlist.append(n)
151 151
152 152 if secretlist:
153 153 ui.status(_("no changes found (ignored %d secret changesets)\n")
154 154 % len(secretlist))
155 155 else:
156 156 ui.status(_("no changes found\n"))
157 157
158 158 def callcatch(ui, func):
159 159 """call func() with global exception handling
160 160
161 161 return func() if no exception happens. otherwise do some error handling
162 162 and return an exit code accordingly. does not handle all exceptions.
163 163 """
164 164 try:
165 165 try:
166 166 return func()
167 167 except: # re-raises
168 168 ui.traceback()
169 169 raise
170 170 # Global exception handling, alphabetically
171 171 # Mercurial-specific first, followed by built-in and library exceptions
172 172 except error.LockHeld as inst:
173 173 if inst.errno == errno.ETIMEDOUT:
174 174 reason = _('timed out waiting for lock held by %r') % (
175 175 pycompat.bytestr(inst.locker))
176 176 else:
177 177 reason = _('lock held by %r') % inst.locker
178 178 ui.error(_("abort: %s: %s\n") % (
179 179 inst.desc or stringutil.forcebytestr(inst.filename), reason))
180 180 if not inst.locker:
181 181 ui.error(_("(lock might be very busy)\n"))
182 182 except error.LockUnavailable as inst:
183 183 ui.error(_("abort: could not lock %s: %s\n") %
184 184 (inst.desc or stringutil.forcebytestr(inst.filename),
185 185 encoding.strtolocal(inst.strerror)))
186 186 except error.OutOfBandError as inst:
187 187 if inst.args:
188 188 msg = _("abort: remote error:\n")
189 189 else:
190 190 msg = _("abort: remote error\n")
191 191 ui.error(msg)
192 192 if inst.args:
193 193 ui.error(''.join(inst.args))
194 194 if inst.hint:
195 195 ui.error('(%s)\n' % inst.hint)
196 196 except error.RepoError as inst:
197 197 ui.error(_("abort: %s!\n") % inst)
198 198 if inst.hint:
199 199 ui.error(_("(%s)\n") % inst.hint)
200 200 except error.ResponseError as inst:
201 201 ui.error(_("abort: %s") % inst.args[0])
202 202 msg = inst.args[1]
203 203 if isinstance(msg, type(u'')):
204 204 msg = pycompat.sysbytes(msg)
205 205 if not isinstance(msg, bytes):
206 206 ui.error(" %r\n" % (msg,))
207 207 elif not msg:
208 208 ui.error(_(" empty string\n"))
209 209 else:
210 210 ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
211 211 except error.CensoredNodeError as inst:
212 212 ui.error(_("abort: file censored %s!\n") % inst)
213 213 except error.StorageError as inst:
214 214 ui.error(_("abort: %s!\n") % inst)
215 215 if inst.hint:
216 216 ui.error(_("(%s)\n") % inst.hint)
217 217 except error.InterventionRequired as inst:
218 218 ui.error("%s\n" % inst)
219 219 if inst.hint:
220 220 ui.error(_("(%s)\n") % inst.hint)
221 221 return 1
222 222 except error.WdirUnsupported:
223 223 ui.error(_("abort: working directory revision cannot be specified\n"))
224 224 except error.Abort as inst:
225 225 ui.error(_("abort: %s\n") % inst)
226 226 if inst.hint:
227 227 ui.error(_("(%s)\n") % inst.hint)
228 228 except ImportError as inst:
229 229 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
230 230 m = stringutil.forcebytestr(inst).split()[-1]
231 231 if m in "mpatch bdiff".split():
232 232 ui.error(_("(did you forget to compile extensions?)\n"))
233 233 elif m in "zlib".split():
234 234 ui.error(_("(is your Python install correct?)\n"))
235 235 except (IOError, OSError) as inst:
236 236 if util.safehasattr(inst, "code"): # HTTPError
237 237 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
238 238 elif util.safehasattr(inst, "reason"): # URLError or SSLError
239 239 try: # usually it is in the form (errno, strerror)
240 240 reason = inst.reason.args[1]
241 241 except (AttributeError, IndexError):
242 242 # it might be anything, for example a string
243 243 reason = inst.reason
244 244 if isinstance(reason, pycompat.unicode):
245 245 # SSLError of Python 2.7.9 contains a unicode
246 246 reason = encoding.unitolocal(reason)
247 247 ui.error(_("abort: error: %s\n") % reason)
248 248 elif (util.safehasattr(inst, "args")
249 249 and inst.args and inst.args[0] == errno.EPIPE):
250 250 pass
251 251 elif getattr(inst, "strerror", None): # common IOError or OSError
252 252 if getattr(inst, "filename", None) is not None:
253 253 ui.error(_("abort: %s: '%s'\n") % (
254 254 encoding.strtolocal(inst.strerror),
255 255 stringutil.forcebytestr(inst.filename)))
256 256 else:
257 257 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
258 258 else: # suspicious IOError
259 259 raise
260 260 except MemoryError:
261 261 ui.error(_("abort: out of memory\n"))
262 262 except SystemExit as inst:
263 263 # Commands shouldn't sys.exit directly, but give a return code.
264 264 # Just in case catch this and and pass exit code to caller.
265 265 return inst.code
266 266
267 267 return -1
268 268
269 269 def checknewlabel(repo, lbl, kind):
270 270 # Do not use the "kind" parameter in ui output.
271 271 # It makes strings difficult to translate.
272 272 if lbl in ['tip', '.', 'null']:
273 273 raise error.Abort(_("the name '%s' is reserved") % lbl)
274 274 for c in (':', '\0', '\n', '\r'):
275 275 if c in lbl:
276 276 raise error.Abort(
277 277 _("%r cannot be used in a name") % pycompat.bytestr(c))
278 278 try:
279 279 int(lbl)
280 280 raise error.Abort(_("cannot use an integer as a name"))
281 281 except ValueError:
282 282 pass
283 283 if lbl.strip() != lbl:
284 284 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
285 285
286 286 def checkfilename(f):
287 287 '''Check that the filename f is an acceptable filename for a tracked file'''
288 288 if '\r' in f or '\n' in f:
289 289 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
290 290 % pycompat.bytestr(f))
291 291
292 292 def checkportable(ui, f):
293 293 '''Check if filename f is portable and warn or abort depending on config'''
294 294 checkfilename(f)
295 295 abort, warn = checkportabilityalert(ui)
296 296 if abort or warn:
297 297 msg = util.checkwinfilename(f)
298 298 if msg:
299 299 msg = "%s: %s" % (msg, procutil.shellquote(f))
300 300 if abort:
301 301 raise error.Abort(msg)
302 302 ui.warn(_("warning: %s\n") % msg)
303 303
304 304 def checkportabilityalert(ui):
305 305 '''check if the user's config requests nothing, a warning, or abort for
306 306 non-portable filenames'''
307 307 val = ui.config('ui', 'portablefilenames')
308 308 lval = val.lower()
309 309 bval = stringutil.parsebool(val)
310 310 abort = pycompat.iswindows or lval == 'abort'
311 311 warn = bval or lval == 'warn'
312 312 if bval is None and not (warn or abort or lval == 'ignore'):
313 313 raise error.ConfigError(
314 314 _("ui.portablefilenames value is invalid ('%s')") % val)
315 315 return abort, warn
316 316
317 317 class casecollisionauditor(object):
318 318 def __init__(self, ui, abort, dirstate):
319 319 self._ui = ui
320 320 self._abort = abort
321 321 allfiles = '\0'.join(dirstate._map)
322 322 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
323 323 self._dirstate = dirstate
324 324 # The purpose of _newfiles is so that we don't complain about
325 325 # case collisions if someone were to call this object with the
326 326 # same filename twice.
327 327 self._newfiles = set()
328 328
329 329 def __call__(self, f):
330 330 if f in self._newfiles:
331 331 return
332 332 fl = encoding.lower(f)
333 333 if fl in self._loweredfiles and f not in self._dirstate:
334 334 msg = _('possible case-folding collision for %s') % f
335 335 if self._abort:
336 336 raise error.Abort(msg)
337 337 self._ui.warn(_("warning: %s\n") % msg)
338 338 self._loweredfiles.add(fl)
339 339 self._newfiles.add(f)
340 340
341 341 def filteredhash(repo, maxrev):
342 342 """build hash of filtered revisions in the current repoview.
343 343
344 344 Multiple caches perform up-to-date validation by checking that the
345 345 tiprev and tipnode stored in the cache file match the current repository.
346 346 However, this is not sufficient for validating repoviews because the set
347 347 of revisions in the view may change without the repository tiprev and
348 348 tipnode changing.
349 349
350 350 This function hashes all the revs filtered from the view and returns
351 351 that SHA-1 digest.
352 352 """
353 353 cl = repo.changelog
354 354 if not cl.filteredrevs:
355 355 return None
356 356 key = None
357 357 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
358 358 if revs:
359 359 s = hashlib.sha1()
360 360 for rev in revs:
361 361 s.update('%d;' % rev)
362 362 key = s.digest()
363 363 return key
364 364
365 365 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
366 366 '''yield every hg repository under path, always recursively.
367 367 The recurse flag will only control recursion into repo working dirs'''
368 368 def errhandler(err):
369 369 if err.filename == path:
370 370 raise err
371 371 samestat = getattr(os.path, 'samestat', None)
372 372 if followsym and samestat is not None:
373 373 def adddir(dirlst, dirname):
374 374 dirstat = os.stat(dirname)
375 375 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
376 376 if not match:
377 377 dirlst.append(dirstat)
378 378 return not match
379 379 else:
380 380 followsym = False
381 381
382 382 if (seen_dirs is None) and followsym:
383 383 seen_dirs = []
384 384 adddir(seen_dirs, path)
385 385 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
386 386 dirs.sort()
387 387 if '.hg' in dirs:
388 388 yield root # found a repository
389 389 qroot = os.path.join(root, '.hg', 'patches')
390 390 if os.path.isdir(os.path.join(qroot, '.hg')):
391 391 yield qroot # we have a patch queue repo here
392 392 if recurse:
393 393 # avoid recursing inside the .hg directory
394 394 dirs.remove('.hg')
395 395 else:
396 396 dirs[:] = [] # don't descend further
397 397 elif followsym:
398 398 newdirs = []
399 399 for d in dirs:
400 400 fname = os.path.join(root, d)
401 401 if adddir(seen_dirs, fname):
402 402 if os.path.islink(fname):
403 403 for hgname in walkrepos(fname, True, seen_dirs):
404 404 yield hgname
405 405 else:
406 406 newdirs.append(d)
407 407 dirs[:] = newdirs
408 408
409 409 def binnode(ctx):
410 410 """Return binary node id for a given basectx"""
411 411 node = ctx.node()
412 412 if node is None:
413 413 return wdirid
414 414 return node
415 415
416 416 def intrev(ctx):
417 417 """Return integer for a given basectx that can be used in comparison or
418 418 arithmetic operation"""
419 419 rev = ctx.rev()
420 420 if rev is None:
421 421 return wdirrev
422 422 return rev
423 423
424 424 def formatchangeid(ctx):
425 425 """Format changectx as '{rev}:{node|formatnode}', which is the default
426 426 template provided by logcmdutil.changesettemplater"""
427 427 repo = ctx.repo()
428 428 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
429 429
430 430 def formatrevnode(ui, rev, node):
431 431 """Format given revision and node depending on the current verbosity"""
432 432 if ui.debugflag:
433 433 hexfunc = hex
434 434 else:
435 435 hexfunc = short
436 436 return '%d:%s' % (rev, hexfunc(node))
437 437
438 438 def resolvehexnodeidprefix(repo, prefix):
439 439 if (prefix.startswith('x') and
440 440 repo.ui.configbool('experimental', 'revisions.prefixhexnode')):
441 441 prefix = prefix[1:]
442 442 try:
443 443 # Uses unfiltered repo because it's faster when prefix is ambiguous/
444 444 # This matches the shortesthexnodeidprefix() function below.
445 445 node = repo.unfiltered().changelog._partialmatch(prefix)
446 446 except error.AmbiguousPrefixLookupError:
447 447 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
448 448 if revset:
449 449 # Clear config to avoid infinite recursion
450 450 configoverrides = {('experimental',
451 451 'revisions.disambiguatewithin'): None}
452 452 with repo.ui.configoverride(configoverrides):
453 453 revs = repo.anyrevs([revset], user=True)
454 454 matches = []
455 455 for rev in revs:
456 456 node = repo.changelog.node(rev)
457 457 if hex(node).startswith(prefix):
458 458 matches.append(node)
459 459 if len(matches) == 1:
460 460 return matches[0]
461 461 raise
462 462 if node is None:
463 463 return
464 464 repo.changelog.rev(node) # make sure node isn't filtered
465 465 return node
466 466
467 467 def mayberevnum(repo, prefix):
468 468 """Checks if the given prefix may be mistaken for a revision number"""
469 469 try:
470 470 i = int(prefix)
471 471 # if we are a pure int, then starting with zero will not be
472 472 # confused as a rev; or, obviously, if the int is larger
473 473 # than the value of the tip rev. We still need to disambiguate if
474 474 # prefix == '0', since that *is* a valid revnum.
475 475 if (prefix != b'0' and prefix[0:1] == b'0') or i >= len(repo):
476 476 return False
477 477 return True
478 478 except ValueError:
479 479 return False
480 480
481 481 def shortesthexnodeidprefix(repo, node, minlength=1, cache=None):
482 482 """Find the shortest unambiguous prefix that matches hexnode.
483 483
484 484 If "cache" is not None, it must be a dictionary that can be used for
485 485 caching between calls to this method.
486 486 """
487 487 # _partialmatch() of filtered changelog could take O(len(repo)) time,
488 488 # which would be unacceptably slow. so we look for hash collision in
489 489 # unfiltered space, which means some hashes may be slightly longer.
490 490
491 491 minlength=max(minlength, 1)
492 492
493 493 def disambiguate(prefix):
494 494 """Disambiguate against revnums."""
495 495 if repo.ui.configbool('experimental', 'revisions.prefixhexnode'):
496 496 if mayberevnum(repo, prefix):
497 497 return 'x' + prefix
498 498 else:
499 499 return prefix
500 500
501 501 hexnode = hex(node)
502 502 for length in range(len(prefix), len(hexnode) + 1):
503 503 prefix = hexnode[:length]
504 504 if not mayberevnum(repo, prefix):
505 505 return prefix
506 506
507 507 cl = repo.unfiltered().changelog
508 508 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
509 509 if revset:
510 510 revs = None
511 511 if cache is not None:
512 512 revs = cache.get('disambiguationrevset')
513 513 if revs is None:
514 514 revs = repo.anyrevs([revset], user=True)
515 515 if cache is not None:
516 516 cache['disambiguationrevset'] = revs
517 517 if cl.rev(node) in revs:
518 518 hexnode = hex(node)
519 519 nodetree = None
520 520 if cache is not None:
521 521 nodetree = cache.get('disambiguationnodetree')
522 522 if not nodetree:
523 523 try:
524 524 nodetree = parsers.nodetree(cl.index, len(revs))
525 525 except AttributeError:
526 526 # no native nodetree
527 527 pass
528 528 else:
529 529 for r in revs:
530 530 nodetree.insert(r)
531 531 if cache is not None:
532 532 cache['disambiguationnodetree'] = nodetree
533 533 if nodetree is not None:
534 534 length = max(nodetree.shortest(node), minlength)
535 535 prefix = hexnode[:length]
536 536 return disambiguate(prefix)
537 537 for length in range(minlength, len(hexnode) + 1):
538 538 matches = []
539 539 prefix = hexnode[:length]
540 540 for rev in revs:
541 541 otherhexnode = repo[rev].hex()
542 542 if prefix == otherhexnode[:length]:
543 543 matches.append(otherhexnode)
544 544 if len(matches) == 1:
545 545 return disambiguate(prefix)
546 546
547 547 try:
548 548 return disambiguate(cl.shortest(node, minlength))
549 549 except error.LookupError:
550 550 raise error.RepoLookupError()
551 551
552 552 def isrevsymbol(repo, symbol):
553 553 """Checks if a symbol exists in the repo.
554 554
555 555 See revsymbol() for details. Raises error.AmbiguousPrefixLookupError if the
556 556 symbol is an ambiguous nodeid prefix.
557 557 """
558 558 try:
559 559 revsymbol(repo, symbol)
560 560 return True
561 561 except error.RepoLookupError:
562 562 return False
563 563
564 564 def revsymbol(repo, symbol):
565 565 """Returns a context given a single revision symbol (as string).
566 566
567 567 This is similar to revsingle(), but accepts only a single revision symbol,
568 568 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
569 569 not "max(public())".
570 570 """
571 571 if not isinstance(symbol, bytes):
572 572 msg = ("symbol (%s of type %s) was not a string, did you mean "
573 573 "repo[symbol]?" % (symbol, type(symbol)))
574 574 raise error.ProgrammingError(msg)
575 575 try:
576 576 if symbol in ('.', 'tip', 'null'):
577 577 return repo[symbol]
578 578
579 579 try:
580 580 r = int(symbol)
581 581 if '%d' % r != symbol:
582 582 raise ValueError
583 583 l = len(repo.changelog)
584 584 if r < 0:
585 585 r += l
586 586 if r < 0 or r >= l and r != wdirrev:
587 587 raise ValueError
588 588 return repo[r]
589 589 except error.FilteredIndexError:
590 590 raise
591 591 except (ValueError, OverflowError, IndexError):
592 592 pass
593 593
594 594 if len(symbol) == 40:
595 595 try:
596 596 node = bin(symbol)
597 597 rev = repo.changelog.rev(node)
598 598 return repo[rev]
599 599 except error.FilteredLookupError:
600 600 raise
601 601 except (TypeError, LookupError):
602 602 pass
603 603
604 604 # look up bookmarks through the name interface
605 605 try:
606 606 node = repo.names.singlenode(repo, symbol)
607 607 rev = repo.changelog.rev(node)
608 608 return repo[rev]
609 609 except KeyError:
610 610 pass
611 611
612 612 node = resolvehexnodeidprefix(repo, symbol)
613 613 if node is not None:
614 614 rev = repo.changelog.rev(node)
615 615 return repo[rev]
616 616
617 617 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
618 618
619 619 except error.WdirUnsupported:
620 620 return repo[None]
621 621 except (error.FilteredIndexError, error.FilteredLookupError,
622 622 error.FilteredRepoLookupError):
623 623 raise _filterederror(repo, symbol)
624 624
625 625 def _filterederror(repo, changeid):
626 626 """build an exception to be raised about a filtered changeid
627 627
628 628 This is extracted in a function to help extensions (eg: evolve) to
629 629 experiment with various message variants."""
630 630 if repo.filtername.startswith('visible'):
631 631
632 632 # Check if the changeset is obsolete
633 633 unfilteredrepo = repo.unfiltered()
634 634 ctx = revsymbol(unfilteredrepo, changeid)
635 635
636 636 # If the changeset is obsolete, enrich the message with the reason
637 637 # that made this changeset not visible
638 638 if ctx.obsolete():
639 639 msg = obsutil._getfilteredreason(repo, changeid, ctx)
640 640 else:
641 641 msg = _("hidden revision '%s'") % changeid
642 642
643 643 hint = _('use --hidden to access hidden revisions')
644 644
645 645 return error.FilteredRepoLookupError(msg, hint=hint)
646 646 msg = _("filtered revision '%s' (not in '%s' subset)")
647 647 msg %= (changeid, repo.filtername)
648 648 return error.FilteredRepoLookupError(msg)
649 649
650 650 def revsingle(repo, revspec, default='.', localalias=None):
651 651 if not revspec and revspec != 0:
652 652 return repo[default]
653 653
654 654 l = revrange(repo, [revspec], localalias=localalias)
655 655 if not l:
656 656 raise error.Abort(_('empty revision set'))
657 657 return repo[l.last()]
658 658
659 659 def _pairspec(revspec):
660 660 tree = revsetlang.parse(revspec)
661 661 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
662 662
663 663 def revpair(repo, revs):
664 664 if not revs:
665 665 return repo['.'], repo[None]
666 666
667 667 l = revrange(repo, revs)
668 668
669 669 if not l:
670 670 raise error.Abort(_('empty revision range'))
671 671
672 672 first = l.first()
673 673 second = l.last()
674 674
675 675 if (first == second and len(revs) >= 2
676 676 and not all(revrange(repo, [r]) for r in revs)):
677 677 raise error.Abort(_('empty revision on one side of range'))
678 678
679 679 # if top-level is range expression, the result must always be a pair
680 680 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
681 681 return repo[first], repo[None]
682 682
683 683 return repo[first], repo[second]
684 684
685 685 def revrange(repo, specs, localalias=None):
686 686 """Execute 1 to many revsets and return the union.
687 687
688 688 This is the preferred mechanism for executing revsets using user-specified
689 689 config options, such as revset aliases.
690 690
691 691 The revsets specified by ``specs`` will be executed via a chained ``OR``
692 692 expression. If ``specs`` is empty, an empty result is returned.
693 693
694 694 ``specs`` can contain integers, in which case they are assumed to be
695 695 revision numbers.
696 696
697 697 It is assumed the revsets are already formatted. If you have arguments
698 698 that need to be expanded in the revset, call ``revsetlang.formatspec()``
699 699 and pass the result as an element of ``specs``.
700 700
701 701 Specifying a single revset is allowed.
702 702
703 703 Returns a ``revset.abstractsmartset`` which is a list-like interface over
704 704 integer revisions.
705 705 """
706 706 allspecs = []
707 707 for spec in specs:
708 708 if isinstance(spec, int):
709 709 spec = revsetlang.formatspec('%d', spec)
710 710 allspecs.append(spec)
711 711 return repo.anyrevs(allspecs, user=True, localalias=localalias)
712 712
713 713 def meaningfulparents(repo, ctx):
714 714 """Return list of meaningful (or all if debug) parentrevs for rev.
715 715
716 716 For merges (two non-nullrev revisions) both parents are meaningful.
717 717 Otherwise the first parent revision is considered meaningful if it
718 718 is not the preceding revision.
719 719 """
720 720 parents = ctx.parents()
721 721 if len(parents) > 1:
722 722 return parents
723 723 if repo.ui.debugflag:
724 724 return [parents[0], repo[nullrev]]
725 725 if parents[0].rev() >= intrev(ctx) - 1:
726 726 return []
727 727 return parents
728 728
729 729 def getuipathfn(repo, legacyrelativevalue=False, forcerelativevalue=None):
730 730 """Return a function that produced paths for presenting to the user.
731 731
732 732 The returned function takes a repo-relative path and produces a path
733 733 that can be presented in the UI.
734 734
735 735 Depending on the value of ui.relative-paths, either a repo-relative or
736 736 cwd-relative path will be produced.
737 737
738 738 legacyrelativevalue is the value to use if ui.relative-paths=legacy
739 739
740 740 If forcerelativevalue is not None, then that value will be used regardless
741 741 of what ui.relative-paths is set to.
742 742 """
743 743 if forcerelativevalue is not None:
744 744 relative = forcerelativevalue
745 745 else:
746 746 config = repo.ui.config('ui', 'relative-paths')
747 747 if config == 'legacy':
748 748 relative = legacyrelativevalue
749 749 else:
750 750 relative = stringutil.parsebool(config)
751 751 if relative is None:
752 752 raise error.ConfigError(
753 753 _("ui.relative-paths is not a boolean ('%s')") % config)
754 754
755 755 if relative:
756 756 cwd = repo.getcwd()
757 757 pathto = repo.pathto
758 758 return lambda f: pathto(f, cwd)
759 759 elif repo.ui.configbool('ui', 'slash'):
760 760 return lambda f: f
761 761 else:
762 762 return util.localpath
763 763
764 764 def subdiruipathfn(subpath, uipathfn):
765 765 '''Create a new uipathfn that treats the file as relative to subpath.'''
766 766 return lambda f: uipathfn(posixpath.join(subpath, f))
767 767
768 768 def anypats(pats, opts):
769 769 '''Checks if any patterns, including --include and --exclude were given.
770 770
771 771 Some commands (e.g. addremove) use this condition for deciding whether to
772 772 print absolute or relative paths.
773 773 '''
774 774 return bool(pats or opts.get('include') or opts.get('exclude'))
775 775
776 776 def expandpats(pats):
777 777 '''Expand bare globs when running on windows.
778 778 On posix we assume it already has already been done by sh.'''
779 779 if not util.expandglobs:
780 780 return list(pats)
781 781 ret = []
782 782 for kindpat in pats:
783 783 kind, pat = matchmod._patsplit(kindpat, None)
784 784 if kind is None:
785 785 try:
786 786 globbed = glob.glob(pat)
787 787 except re.error:
788 788 globbed = [pat]
789 789 if globbed:
790 790 ret.extend(globbed)
791 791 continue
792 792 ret.append(kindpat)
793 793 return ret
794 794
795 795 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
796 796 badfn=None):
797 797 '''Return a matcher and the patterns that were used.
798 798 The matcher will warn about bad matches, unless an alternate badfn callback
799 799 is provided.'''
800 800 if opts is None:
801 801 opts = {}
802 802 if not globbed and default == 'relpath':
803 803 pats = expandpats(pats or [])
804 804
805 805 uipathfn = getuipathfn(ctx.repo(), legacyrelativevalue=True)
806 806 def bad(f, msg):
807 807 ctx.repo().ui.warn("%s: %s\n" % (uipathfn(f), msg))
808 808
809 809 if badfn is None:
810 810 badfn = bad
811 811
812 812 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
813 813 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
814 814
815 815 if m.always():
816 816 pats = []
817 817 return m, pats
818 818
819 819 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
820 820 badfn=None):
821 821 '''Return a matcher that will warn about bad matches.'''
822 822 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
823 823
824 824 def matchall(repo):
825 825 '''Return a matcher that will efficiently match everything.'''
826 826 return matchmod.always()
827 827
828 828 def matchfiles(repo, files, badfn=None):
829 829 '''Return a matcher that will efficiently match exactly these files.'''
830 830 return matchmod.exact(files, badfn=badfn)
831 831
832 832 def parsefollowlinespattern(repo, rev, pat, msg):
833 833 """Return a file name from `pat` pattern suitable for usage in followlines
834 834 logic.
835 835 """
836 836 if not matchmod.patkind(pat):
837 837 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
838 838 else:
839 839 ctx = repo[rev]
840 840 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
841 841 files = [f for f in ctx if m(f)]
842 842 if len(files) != 1:
843 843 raise error.ParseError(msg)
844 844 return files[0]
845 845
846 846 def getorigvfs(ui, repo):
847 847 """return a vfs suitable to save 'orig' file
848 848
849 849 return None if no special directory is configured"""
850 850 origbackuppath = ui.config('ui', 'origbackuppath')
851 851 if not origbackuppath:
852 852 return None
853 853 return vfs.vfs(repo.wvfs.join(origbackuppath))
854 854
855 855 def backuppath(ui, repo, filepath):
856 856 '''customize where working copy backup files (.orig files) are created
857 857
858 858 Fetch user defined path from config file: [ui] origbackuppath = <path>
859 859 Fall back to default (filepath with .orig suffix) if not specified
860 860
861 861 filepath is repo-relative
862 862
863 863 Returns an absolute path
864 864 '''
865 865 origvfs = getorigvfs(ui, repo)
866 866 if origvfs is None:
867 867 return repo.wjoin(filepath + ".orig")
868 868
869 869 origbackupdir = origvfs.dirname(filepath)
870 870 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
871 871 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
872 872
873 873 # Remove any files that conflict with the backup file's path
874 874 for f in reversed(list(util.finddirs(filepath))):
875 875 if origvfs.isfileorlink(f):
876 876 ui.note(_('removing conflicting file: %s\n')
877 877 % origvfs.join(f))
878 878 origvfs.unlink(f)
879 879 break
880 880
881 881 origvfs.makedirs(origbackupdir)
882 882
883 883 if origvfs.isdir(filepath) and not origvfs.islink(filepath):
884 884 ui.note(_('removing conflicting directory: %s\n')
885 885 % origvfs.join(filepath))
886 886 origvfs.rmtree(filepath, forcibly=True)
887 887
888 888 return origvfs.join(filepath)
889 889
890 890 class _containsnode(object):
891 891 """proxy __contains__(node) to container.__contains__ which accepts revs"""
892 892
893 893 def __init__(self, repo, revcontainer):
894 894 self._torev = repo.changelog.rev
895 895 self._revcontains = revcontainer.__contains__
896 896
897 897 def __contains__(self, node):
898 898 return self._revcontains(self._torev(node))
899 899
900 900 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
901 901 fixphase=False, targetphase=None, backup=True):
902 902 """do common cleanups when old nodes are replaced by new nodes
903 903
904 904 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
905 905 (we might also want to move working directory parent in the future)
906 906
907 907 By default, bookmark moves are calculated automatically from 'replacements',
908 908 but 'moves' can be used to override that. Also, 'moves' may include
909 909 additional bookmark moves that should not have associated obsmarkers.
910 910
911 911 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
912 912 have replacements. operation is a string, like "rebase".
913 913
914 914 metadata is dictionary containing metadata to be stored in obsmarker if
915 915 obsolescence is enabled.
916 916 """
917 917 assert fixphase or targetphase is None
918 918 if not replacements and not moves:
919 919 return
920 920
921 921 # translate mapping's other forms
922 922 if not util.safehasattr(replacements, 'items'):
923 923 replacements = {(n,): () for n in replacements}
924 924 else:
925 925 # upgrading non tuple "source" to tuple ones for BC
926 926 repls = {}
927 927 for key, value in replacements.items():
928 928 if not isinstance(key, tuple):
929 929 key = (key,)
930 930 repls[key] = value
931 931 replacements = repls
932 932
933 933 # Unfiltered repo is needed since nodes in replacements might be hidden.
934 934 unfi = repo.unfiltered()
935 935
936 936 # Calculate bookmark movements
937 937 if moves is None:
938 938 moves = {}
939 939 for oldnodes, newnodes in replacements.items():
940 940 for oldnode in oldnodes:
941 941 if oldnode in moves:
942 942 continue
943 943 if len(newnodes) > 1:
944 944 # usually a split, take the one with biggest rev number
945 945 newnode = next(unfi.set('max(%ln)', newnodes)).node()
946 946 elif len(newnodes) == 0:
947 947 # move bookmark backwards
948 948 allreplaced = []
949 949 for rep in replacements:
950 950 allreplaced.extend(rep)
951 951 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
952 952 allreplaced))
953 953 if roots:
954 954 newnode = roots[0].node()
955 955 else:
956 956 newnode = nullid
957 957 else:
958 958 newnode = newnodes[0]
959 959 moves[oldnode] = newnode
960 960
961 961 allnewnodes = [n for ns in replacements.values() for n in ns]
962 962 toretract = {}
963 963 toadvance = {}
964 964 if fixphase:
965 965 precursors = {}
966 966 for oldnodes, newnodes in replacements.items():
967 967 for oldnode in oldnodes:
968 968 for newnode in newnodes:
969 969 precursors.setdefault(newnode, []).append(oldnode)
970 970
971 971 allnewnodes.sort(key=lambda n: unfi[n].rev())
972 972 newphases = {}
973 973 def phase(ctx):
974 974 return newphases.get(ctx.node(), ctx.phase())
975 975 for newnode in allnewnodes:
976 976 ctx = unfi[newnode]
977 977 parentphase = max(phase(p) for p in ctx.parents())
978 978 if targetphase is None:
979 979 oldphase = max(unfi[oldnode].phase()
980 980 for oldnode in precursors[newnode])
981 981 newphase = max(oldphase, parentphase)
982 982 else:
983 983 newphase = max(targetphase, parentphase)
984 984 newphases[newnode] = newphase
985 985 if newphase > ctx.phase():
986 986 toretract.setdefault(newphase, []).append(newnode)
987 987 elif newphase < ctx.phase():
988 988 toadvance.setdefault(newphase, []).append(newnode)
989 989
990 990 with repo.transaction('cleanup') as tr:
991 991 # Move bookmarks
992 992 bmarks = repo._bookmarks
993 993 bmarkchanges = []
994 994 for oldnode, newnode in moves.items():
995 995 oldbmarks = repo.nodebookmarks(oldnode)
996 996 if not oldbmarks:
997 997 continue
998 998 from . import bookmarks # avoid import cycle
999 999 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
1000 1000 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
1001 1001 hex(oldnode), hex(newnode)))
1002 1002 # Delete divergent bookmarks being parents of related newnodes
1003 1003 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
1004 1004 allnewnodes, newnode, oldnode)
1005 1005 deletenodes = _containsnode(repo, deleterevs)
1006 1006 for name in oldbmarks:
1007 1007 bmarkchanges.append((name, newnode))
1008 1008 for b in bookmarks.divergent2delete(repo, deletenodes, name):
1009 1009 bmarkchanges.append((b, None))
1010 1010
1011 1011 if bmarkchanges:
1012 1012 bmarks.applychanges(repo, tr, bmarkchanges)
1013 1013
1014 1014 for phase, nodes in toretract.items():
1015 1015 phases.retractboundary(repo, tr, phase, nodes)
1016 1016 for phase, nodes in toadvance.items():
1017 1017 phases.advanceboundary(repo, tr, phase, nodes)
1018 1018
1019 1019 # Obsolete or strip nodes
1020 1020 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1021 1021 # If a node is already obsoleted, and we want to obsolete it
1022 1022 # without a successor, skip that obssolete request since it's
1023 1023 # unnecessary. That's the "if s or not isobs(n)" check below.
1024 1024 # Also sort the node in topology order, that might be useful for
1025 1025 # some obsstore logic.
1026 1026 # NOTE: the sorting might belong to createmarkers.
1027 1027 torev = unfi.changelog.rev
1028 1028 sortfunc = lambda ns: torev(ns[0][0])
1029 1029 rels = []
1030 1030 for ns, s in sorted(replacements.items(), key=sortfunc):
1031 1031 rel = (tuple(unfi[n] for n in ns), tuple(unfi[m] for m in s))
1032 1032 rels.append(rel)
1033 1033 if rels:
1034 1034 obsolete.createmarkers(repo, rels, operation=operation,
1035 1035 metadata=metadata)
1036 1036 else:
1037 1037 from . import repair # avoid import cycle
1038 1038 tostrip = list(n for ns in replacements for n in ns)
1039 1039 if tostrip:
1040 1040 repair.delayedstrip(repo.ui, repo, tostrip, operation,
1041 1041 backup=backup)
1042 1042
1043 1043 def addremove(repo, matcher, prefix, uipathfn, opts=None):
1044 1044 if opts is None:
1045 1045 opts = {}
1046 1046 m = matcher
1047 1047 dry_run = opts.get('dry_run')
1048 1048 try:
1049 1049 similarity = float(opts.get('similarity') or 0)
1050 1050 except ValueError:
1051 1051 raise error.Abort(_('similarity must be a number'))
1052 1052 if similarity < 0 or similarity > 100:
1053 1053 raise error.Abort(_('similarity must be between 0 and 100'))
1054 1054 similarity /= 100.0
1055 1055
1056 1056 ret = 0
1057 1057
1058 1058 wctx = repo[None]
1059 1059 for subpath in sorted(wctx.substate):
1060 1060 submatch = matchmod.subdirmatcher(subpath, m)
1061 1061 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
1062 1062 sub = wctx.sub(subpath)
1063 1063 subprefix = repo.wvfs.reljoin(prefix, subpath)
1064 1064 subuipathfn = subdiruipathfn(subpath, uipathfn)
1065 1065 try:
1066 1066 if sub.addremove(submatch, subprefix, subuipathfn, opts):
1067 1067 ret = 1
1068 1068 except error.LookupError:
1069 1069 repo.ui.status(_("skipping missing subrepository: %s\n")
1070 1070 % uipathfn(subpath))
1071 1071
1072 1072 rejected = []
1073 1073 def badfn(f, msg):
1074 1074 if f in m.files():
1075 1075 m.bad(f, msg)
1076 1076 rejected.append(f)
1077 1077
1078 1078 badmatch = matchmod.badmatch(m, badfn)
1079 1079 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
1080 1080 badmatch)
1081 1081
1082 1082 unknownset = set(unknown + forgotten)
1083 1083 toprint = unknownset.copy()
1084 1084 toprint.update(deleted)
1085 1085 for abs in sorted(toprint):
1086 1086 if repo.ui.verbose or not m.exact(abs):
1087 1087 if abs in unknownset:
1088 1088 status = _('adding %s\n') % uipathfn(abs)
1089 1089 label = 'ui.addremove.added'
1090 1090 else:
1091 1091 status = _('removing %s\n') % uipathfn(abs)
1092 1092 label = 'ui.addremove.removed'
1093 1093 repo.ui.status(status, label=label)
1094 1094
1095 1095 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1096 1096 similarity, uipathfn)
1097 1097
1098 1098 if not dry_run:
1099 1099 _markchanges(repo, unknown + forgotten, deleted, renames)
1100 1100
1101 1101 for f in rejected:
1102 1102 if f in m.files():
1103 1103 return 1
1104 1104 return ret
1105 1105
1106 1106 def marktouched(repo, files, similarity=0.0):
1107 1107 '''Assert that files have somehow been operated upon. files are relative to
1108 1108 the repo root.'''
1109 1109 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
1110 1110 rejected = []
1111 1111
1112 1112 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
1113 1113
1114 1114 if repo.ui.verbose:
1115 1115 unknownset = set(unknown + forgotten)
1116 1116 toprint = unknownset.copy()
1117 1117 toprint.update(deleted)
1118 1118 for abs in sorted(toprint):
1119 1119 if abs in unknownset:
1120 1120 status = _('adding %s\n') % abs
1121 1121 else:
1122 1122 status = _('removing %s\n') % abs
1123 1123 repo.ui.status(status)
1124 1124
1125 1125 # TODO: We should probably have the caller pass in uipathfn and apply it to
1126 1126 # the messages above too. legacyrelativevalue=True is consistent with how
1127 1127 # it used to work.
1128 1128 uipathfn = getuipathfn(repo, legacyrelativevalue=True)
1129 1129 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1130 1130 similarity, uipathfn)
1131 1131
1132 1132 _markchanges(repo, unknown + forgotten, deleted, renames)
1133 1133
1134 1134 for f in rejected:
1135 1135 if f in m.files():
1136 1136 return 1
1137 1137 return 0
1138 1138
1139 1139 def _interestingfiles(repo, matcher):
1140 1140 '''Walk dirstate with matcher, looking for files that addremove would care
1141 1141 about.
1142 1142
1143 1143 This is different from dirstate.status because it doesn't care about
1144 1144 whether files are modified or clean.'''
1145 1145 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1146 1146 audit_path = pathutil.pathauditor(repo.root, cached=True)
1147 1147
1148 1148 ctx = repo[None]
1149 1149 dirstate = repo.dirstate
1150 1150 matcher = repo.narrowmatch(matcher, includeexact=True)
1151 1151 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1152 1152 unknown=True, ignored=False, full=False)
1153 1153 for abs, st in walkresults.iteritems():
1154 1154 dstate = dirstate[abs]
1155 1155 if dstate == '?' and audit_path.check(abs):
1156 1156 unknown.append(abs)
1157 1157 elif dstate != 'r' and not st:
1158 1158 deleted.append(abs)
1159 1159 elif dstate == 'r' and st:
1160 1160 forgotten.append(abs)
1161 1161 # for finding renames
1162 1162 elif dstate == 'r' and not st:
1163 1163 removed.append(abs)
1164 1164 elif dstate == 'a':
1165 1165 added.append(abs)
1166 1166
1167 1167 return added, unknown, deleted, removed, forgotten
1168 1168
1169 1169 def _findrenames(repo, matcher, added, removed, similarity, uipathfn):
1170 1170 '''Find renames from removed files to added ones.'''
1171 1171 renames = {}
1172 1172 if similarity > 0:
1173 1173 for old, new, score in similar.findrenames(repo, added, removed,
1174 1174 similarity):
1175 1175 if (repo.ui.verbose or not matcher.exact(old)
1176 1176 or not matcher.exact(new)):
1177 1177 repo.ui.status(_('recording removal of %s as rename to %s '
1178 1178 '(%d%% similar)\n') %
1179 1179 (uipathfn(old), uipathfn(new),
1180 1180 score * 100))
1181 1181 renames[new] = old
1182 1182 return renames
1183 1183
1184 1184 def _markchanges(repo, unknown, deleted, renames):
1185 1185 '''Marks the files in unknown as added, the files in deleted as removed,
1186 1186 and the files in renames as copied.'''
1187 1187 wctx = repo[None]
1188 1188 with repo.wlock():
1189 1189 wctx.forget(deleted)
1190 1190 wctx.add(unknown)
1191 1191 for new, old in renames.iteritems():
1192 1192 wctx.copy(old, new)
1193 1193
1194 def getrenamedfn(repo, endrev=None):
1195 rcache = {}
1196 if endrev is None:
1197 endrev = len(repo)
1198
1199 def getrenamed(fn, rev):
1200 '''looks up all renames for a file (up to endrev) the first
1201 time the file is given. It indexes on the changerev and only
1202 parses the manifest if linkrev != changerev.
1203 Returns rename info for fn at changerev rev.'''
1204 if fn not in rcache:
1205 rcache[fn] = {}
1206 fl = repo.file(fn)
1207 for i in fl:
1208 lr = fl.linkrev(i)
1209 renamed = fl.renamed(fl.node(i))
1210 rcache[fn][lr] = renamed and renamed[0]
1211 if lr >= endrev:
1212 break
1213 if rev in rcache[fn]:
1214 return rcache[fn][rev]
1215
1216 # If linkrev != rev (i.e. rev not found in rcache) fallback to
1217 # filectx logic.
1218 try:
1219 return repo[rev][fn].copysource()
1220 except error.LookupError:
1221 return None
1222
1223 return getrenamed
1224
1194 1225 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1195 1226 """Update the dirstate to reflect the intent of copying src to dst. For
1196 1227 different reasons it might not end with dst being marked as copied from src.
1197 1228 """
1198 1229 origsrc = repo.dirstate.copied(src) or src
1199 1230 if dst == origsrc: # copying back a copy?
1200 1231 if repo.dirstate[dst] not in 'mn' and not dryrun:
1201 1232 repo.dirstate.normallookup(dst)
1202 1233 else:
1203 1234 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1204 1235 if not ui.quiet:
1205 1236 ui.warn(_("%s has not been committed yet, so no copy "
1206 1237 "data will be stored for %s.\n")
1207 1238 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1208 1239 if repo.dirstate[dst] in '?r' and not dryrun:
1209 1240 wctx.add([dst])
1210 1241 elif not dryrun:
1211 1242 wctx.copy(origsrc, dst)
1212 1243
1213 1244 def writerequires(opener, requirements):
1214 1245 with opener('requires', 'w', atomictemp=True) as fp:
1215 1246 for r in sorted(requirements):
1216 1247 fp.write("%s\n" % r)
1217 1248
1218 1249 class filecachesubentry(object):
1219 1250 def __init__(self, path, stat):
1220 1251 self.path = path
1221 1252 self.cachestat = None
1222 1253 self._cacheable = None
1223 1254
1224 1255 if stat:
1225 1256 self.cachestat = filecachesubentry.stat(self.path)
1226 1257
1227 1258 if self.cachestat:
1228 1259 self._cacheable = self.cachestat.cacheable()
1229 1260 else:
1230 1261 # None means we don't know yet
1231 1262 self._cacheable = None
1232 1263
1233 1264 def refresh(self):
1234 1265 if self.cacheable():
1235 1266 self.cachestat = filecachesubentry.stat(self.path)
1236 1267
1237 1268 def cacheable(self):
1238 1269 if self._cacheable is not None:
1239 1270 return self._cacheable
1240 1271
1241 1272 # we don't know yet, assume it is for now
1242 1273 return True
1243 1274
1244 1275 def changed(self):
1245 1276 # no point in going further if we can't cache it
1246 1277 if not self.cacheable():
1247 1278 return True
1248 1279
1249 1280 newstat = filecachesubentry.stat(self.path)
1250 1281
1251 1282 # we may not know if it's cacheable yet, check again now
1252 1283 if newstat and self._cacheable is None:
1253 1284 self._cacheable = newstat.cacheable()
1254 1285
1255 1286 # check again
1256 1287 if not self._cacheable:
1257 1288 return True
1258 1289
1259 1290 if self.cachestat != newstat:
1260 1291 self.cachestat = newstat
1261 1292 return True
1262 1293 else:
1263 1294 return False
1264 1295
1265 1296 @staticmethod
1266 1297 def stat(path):
1267 1298 try:
1268 1299 return util.cachestat(path)
1269 1300 except OSError as e:
1270 1301 if e.errno != errno.ENOENT:
1271 1302 raise
1272 1303
1273 1304 class filecacheentry(object):
1274 1305 def __init__(self, paths, stat=True):
1275 1306 self._entries = []
1276 1307 for path in paths:
1277 1308 self._entries.append(filecachesubentry(path, stat))
1278 1309
1279 1310 def changed(self):
1280 1311 '''true if any entry has changed'''
1281 1312 for entry in self._entries:
1282 1313 if entry.changed():
1283 1314 return True
1284 1315 return False
1285 1316
1286 1317 def refresh(self):
1287 1318 for entry in self._entries:
1288 1319 entry.refresh()
1289 1320
1290 1321 class filecache(object):
1291 1322 """A property like decorator that tracks files under .hg/ for updates.
1292 1323
1293 1324 On first access, the files defined as arguments are stat()ed and the
1294 1325 results cached. The decorated function is called. The results are stashed
1295 1326 away in a ``_filecache`` dict on the object whose method is decorated.
1296 1327
1297 1328 On subsequent access, the cached result is used as it is set to the
1298 1329 instance dictionary.
1299 1330
1300 1331 On external property set/delete operations, the caller must update the
1301 1332 corresponding _filecache entry appropriately. Use __class__.<attr>.set()
1302 1333 instead of directly setting <attr>.
1303 1334
1304 1335 When using the property API, the cached data is always used if available.
1305 1336 No stat() is performed to check if the file has changed.
1306 1337
1307 1338 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1308 1339 can populate an entry before the property's getter is called. In this case,
1309 1340 entries in ``_filecache`` will be used during property operations,
1310 1341 if available. If the underlying file changes, it is up to external callers
1311 1342 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1312 1343 method result as well as possibly calling ``del obj._filecache[attr]`` to
1313 1344 remove the ``filecacheentry``.
1314 1345 """
1315 1346
1316 1347 def __init__(self, *paths):
1317 1348 self.paths = paths
1318 1349
1319 1350 def join(self, obj, fname):
1320 1351 """Used to compute the runtime path of a cached file.
1321 1352
1322 1353 Users should subclass filecache and provide their own version of this
1323 1354 function to call the appropriate join function on 'obj' (an instance
1324 1355 of the class that its member function was decorated).
1325 1356 """
1326 1357 raise NotImplementedError
1327 1358
1328 1359 def __call__(self, func):
1329 1360 self.func = func
1330 1361 self.sname = func.__name__
1331 1362 self.name = pycompat.sysbytes(self.sname)
1332 1363 return self
1333 1364
1334 1365 def __get__(self, obj, type=None):
1335 1366 # if accessed on the class, return the descriptor itself.
1336 1367 if obj is None:
1337 1368 return self
1338 1369
1339 1370 assert self.sname not in obj.__dict__
1340 1371
1341 1372 entry = obj._filecache.get(self.name)
1342 1373
1343 1374 if entry:
1344 1375 if entry.changed():
1345 1376 entry.obj = self.func(obj)
1346 1377 else:
1347 1378 paths = [self.join(obj, path) for path in self.paths]
1348 1379
1349 1380 # We stat -before- creating the object so our cache doesn't lie if
1350 1381 # a writer modified between the time we read and stat
1351 1382 entry = filecacheentry(paths, True)
1352 1383 entry.obj = self.func(obj)
1353 1384
1354 1385 obj._filecache[self.name] = entry
1355 1386
1356 1387 obj.__dict__[self.sname] = entry.obj
1357 1388 return entry.obj
1358 1389
1359 1390 # don't implement __set__(), which would make __dict__ lookup as slow as
1360 1391 # function call.
1361 1392
1362 1393 def set(self, obj, value):
1363 1394 if self.name not in obj._filecache:
1364 1395 # we add an entry for the missing value because X in __dict__
1365 1396 # implies X in _filecache
1366 1397 paths = [self.join(obj, path) for path in self.paths]
1367 1398 ce = filecacheentry(paths, False)
1368 1399 obj._filecache[self.name] = ce
1369 1400 else:
1370 1401 ce = obj._filecache[self.name]
1371 1402
1372 1403 ce.obj = value # update cached copy
1373 1404 obj.__dict__[self.sname] = value # update copy returned by obj.x
1374 1405
1375 1406 def extdatasource(repo, source):
1376 1407 """Gather a map of rev -> value dict from the specified source
1377 1408
1378 1409 A source spec is treated as a URL, with a special case shell: type
1379 1410 for parsing the output from a shell command.
1380 1411
1381 1412 The data is parsed as a series of newline-separated records where
1382 1413 each record is a revision specifier optionally followed by a space
1383 1414 and a freeform string value. If the revision is known locally, it
1384 1415 is converted to a rev, otherwise the record is skipped.
1385 1416
1386 1417 Note that both key and value are treated as UTF-8 and converted to
1387 1418 the local encoding. This allows uniformity between local and
1388 1419 remote data sources.
1389 1420 """
1390 1421
1391 1422 spec = repo.ui.config("extdata", source)
1392 1423 if not spec:
1393 1424 raise error.Abort(_("unknown extdata source '%s'") % source)
1394 1425
1395 1426 data = {}
1396 1427 src = proc = None
1397 1428 try:
1398 1429 if spec.startswith("shell:"):
1399 1430 # external commands should be run relative to the repo root
1400 1431 cmd = spec[6:]
1401 1432 proc = subprocess.Popen(procutil.tonativestr(cmd),
1402 1433 shell=True, bufsize=-1,
1403 1434 close_fds=procutil.closefds,
1404 1435 stdout=subprocess.PIPE,
1405 1436 cwd=procutil.tonativestr(repo.root))
1406 1437 src = proc.stdout
1407 1438 else:
1408 1439 # treat as a URL or file
1409 1440 src = url.open(repo.ui, spec)
1410 1441 for l in src:
1411 1442 if " " in l:
1412 1443 k, v = l.strip().split(" ", 1)
1413 1444 else:
1414 1445 k, v = l.strip(), ""
1415 1446
1416 1447 k = encoding.tolocal(k)
1417 1448 try:
1418 1449 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1419 1450 except (error.LookupError, error.RepoLookupError):
1420 1451 pass # we ignore data for nodes that don't exist locally
1421 1452 finally:
1422 1453 if proc:
1423 1454 proc.communicate()
1424 1455 if src:
1425 1456 src.close()
1426 1457 if proc and proc.returncode != 0:
1427 1458 raise error.Abort(_("extdata command '%s' failed: %s")
1428 1459 % (cmd, procutil.explainexit(proc.returncode)))
1429 1460
1430 1461 return data
1431 1462
1432 1463 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1433 1464 if lock is None:
1434 1465 raise error.LockInheritanceContractViolation(
1435 1466 'lock can only be inherited while held')
1436 1467 if environ is None:
1437 1468 environ = {}
1438 1469 with lock.inherit() as locker:
1439 1470 environ[envvar] = locker
1440 1471 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1441 1472
1442 1473 def wlocksub(repo, cmd, *args, **kwargs):
1443 1474 """run cmd as a subprocess that allows inheriting repo's wlock
1444 1475
1445 1476 This can only be called while the wlock is held. This takes all the
1446 1477 arguments that ui.system does, and returns the exit code of the
1447 1478 subprocess."""
1448 1479 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1449 1480 **kwargs)
1450 1481
1451 1482 class progress(object):
1452 1483 def __init__(self, ui, updatebar, topic, unit="", total=None):
1453 1484 self.ui = ui
1454 1485 self.pos = 0
1455 1486 self.topic = topic
1456 1487 self.unit = unit
1457 1488 self.total = total
1458 1489 self.debug = ui.configbool('progress', 'debug')
1459 1490 self._updatebar = updatebar
1460 1491
1461 1492 def __enter__(self):
1462 1493 return self
1463 1494
1464 1495 def __exit__(self, exc_type, exc_value, exc_tb):
1465 1496 self.complete()
1466 1497
1467 1498 def update(self, pos, item="", total=None):
1468 1499 assert pos is not None
1469 1500 if total:
1470 1501 self.total = total
1471 1502 self.pos = pos
1472 1503 self._updatebar(self.topic, self.pos, item, self.unit, self.total)
1473 1504 if self.debug:
1474 1505 self._printdebug(item)
1475 1506
1476 1507 def increment(self, step=1, item="", total=None):
1477 1508 self.update(self.pos + step, item, total)
1478 1509
1479 1510 def complete(self):
1480 1511 self.pos = None
1481 1512 self.unit = ""
1482 1513 self.total = None
1483 1514 self._updatebar(self.topic, self.pos, "", self.unit, self.total)
1484 1515
1485 1516 def _printdebug(self, item):
1486 1517 if self.unit:
1487 1518 unit = ' ' + self.unit
1488 1519 if item:
1489 1520 item = ' ' + item
1490 1521
1491 1522 if self.total:
1492 1523 pct = 100.0 * self.pos / self.total
1493 1524 self.ui.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1494 1525 % (self.topic, item, self.pos, self.total, unit, pct))
1495 1526 else:
1496 1527 self.ui.debug('%s:%s %d%s\n' % (self.topic, item, self.pos, unit))
1497 1528
1498 1529 def gdinitconfig(ui):
1499 1530 """helper function to know if a repo should be created as general delta
1500 1531 """
1501 1532 # experimental config: format.generaldelta
1502 1533 return (ui.configbool('format', 'generaldelta')
1503 1534 or ui.configbool('format', 'usegeneraldelta'))
1504 1535
1505 1536 def gddeltaconfig(ui):
1506 1537 """helper function to know if incoming delta should be optimised
1507 1538 """
1508 1539 # experimental config: format.generaldelta
1509 1540 return ui.configbool('format', 'generaldelta')
1510 1541
1511 1542 class simplekeyvaluefile(object):
1512 1543 """A simple file with key=value lines
1513 1544
1514 1545 Keys must be alphanumerics and start with a letter, values must not
1515 1546 contain '\n' characters"""
1516 1547 firstlinekey = '__firstline'
1517 1548
1518 1549 def __init__(self, vfs, path, keys=None):
1519 1550 self.vfs = vfs
1520 1551 self.path = path
1521 1552
1522 1553 def read(self, firstlinenonkeyval=False):
1523 1554 """Read the contents of a simple key-value file
1524 1555
1525 1556 'firstlinenonkeyval' indicates whether the first line of file should
1526 1557 be treated as a key-value pair or reuturned fully under the
1527 1558 __firstline key."""
1528 1559 lines = self.vfs.readlines(self.path)
1529 1560 d = {}
1530 1561 if firstlinenonkeyval:
1531 1562 if not lines:
1532 1563 e = _("empty simplekeyvalue file")
1533 1564 raise error.CorruptedState(e)
1534 1565 # we don't want to include '\n' in the __firstline
1535 1566 d[self.firstlinekey] = lines[0][:-1]
1536 1567 del lines[0]
1537 1568
1538 1569 try:
1539 1570 # the 'if line.strip()' part prevents us from failing on empty
1540 1571 # lines which only contain '\n' therefore are not skipped
1541 1572 # by 'if line'
1542 1573 updatedict = dict(line[:-1].split('=', 1) for line in lines
1543 1574 if line.strip())
1544 1575 if self.firstlinekey in updatedict:
1545 1576 e = _("%r can't be used as a key")
1546 1577 raise error.CorruptedState(e % self.firstlinekey)
1547 1578 d.update(updatedict)
1548 1579 except ValueError as e:
1549 1580 raise error.CorruptedState(str(e))
1550 1581 return d
1551 1582
1552 1583 def write(self, data, firstline=None):
1553 1584 """Write key=>value mapping to a file
1554 1585 data is a dict. Keys must be alphanumerical and start with a letter.
1555 1586 Values must not contain newline characters.
1556 1587
1557 1588 If 'firstline' is not None, it is written to file before
1558 1589 everything else, as it is, not in a key=value form"""
1559 1590 lines = []
1560 1591 if firstline is not None:
1561 1592 lines.append('%s\n' % firstline)
1562 1593
1563 1594 for k, v in data.items():
1564 1595 if k == self.firstlinekey:
1565 1596 e = "key name '%s' is reserved" % self.firstlinekey
1566 1597 raise error.ProgrammingError(e)
1567 1598 if not k[0:1].isalpha():
1568 1599 e = "keys must start with a letter in a key-value file"
1569 1600 raise error.ProgrammingError(e)
1570 1601 if not k.isalnum():
1571 1602 e = "invalid key name in a simple key-value file"
1572 1603 raise error.ProgrammingError(e)
1573 1604 if '\n' in v:
1574 1605 e = "invalid value in a simple key-value file"
1575 1606 raise error.ProgrammingError(e)
1576 1607 lines.append("%s=%s\n" % (k, v))
1577 1608 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1578 1609 fp.write(''.join(lines))
1579 1610
1580 1611 _reportobsoletedsource = [
1581 1612 'debugobsolete',
1582 1613 'pull',
1583 1614 'push',
1584 1615 'serve',
1585 1616 'unbundle',
1586 1617 ]
1587 1618
1588 1619 _reportnewcssource = [
1589 1620 'pull',
1590 1621 'unbundle',
1591 1622 ]
1592 1623
1593 1624 def prefetchfiles(repo, revs, match):
1594 1625 """Invokes the registered file prefetch functions, allowing extensions to
1595 1626 ensure the corresponding files are available locally, before the command
1596 1627 uses them."""
1597 1628 if match:
1598 1629 # The command itself will complain about files that don't exist, so
1599 1630 # don't duplicate the message.
1600 1631 match = matchmod.badmatch(match, lambda fn, msg: None)
1601 1632 else:
1602 1633 match = matchall(repo)
1603 1634
1604 1635 fileprefetchhooks(repo, revs, match)
1605 1636
1606 1637 # a list of (repo, revs, match) prefetch functions
1607 1638 fileprefetchhooks = util.hooks()
1608 1639
1609 1640 # A marker that tells the evolve extension to suppress its own reporting
1610 1641 _reportstroubledchangesets = True
1611 1642
1612 1643 def registersummarycallback(repo, otr, txnname=''):
1613 1644 """register a callback to issue a summary after the transaction is closed
1614 1645 """
1615 1646 def txmatch(sources):
1616 1647 return any(txnname.startswith(source) for source in sources)
1617 1648
1618 1649 categories = []
1619 1650
1620 1651 def reportsummary(func):
1621 1652 """decorator for report callbacks."""
1622 1653 # The repoview life cycle is shorter than the one of the actual
1623 1654 # underlying repository. So the filtered object can die before the
1624 1655 # weakref is used leading to troubles. We keep a reference to the
1625 1656 # unfiltered object and restore the filtering when retrieving the
1626 1657 # repository through the weakref.
1627 1658 filtername = repo.filtername
1628 1659 reporef = weakref.ref(repo.unfiltered())
1629 1660 def wrapped(tr):
1630 1661 repo = reporef()
1631 1662 if filtername:
1632 1663 repo = repo.filtered(filtername)
1633 1664 func(repo, tr)
1634 1665 newcat = '%02i-txnreport' % len(categories)
1635 1666 otr.addpostclose(newcat, wrapped)
1636 1667 categories.append(newcat)
1637 1668 return wrapped
1638 1669
1639 1670 if txmatch(_reportobsoletedsource):
1640 1671 @reportsummary
1641 1672 def reportobsoleted(repo, tr):
1642 1673 obsoleted = obsutil.getobsoleted(repo, tr)
1643 1674 if obsoleted:
1644 1675 repo.ui.status(_('obsoleted %i changesets\n')
1645 1676 % len(obsoleted))
1646 1677
1647 1678 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1648 1679 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1649 1680 instabilitytypes = [
1650 1681 ('orphan', 'orphan'),
1651 1682 ('phase-divergent', 'phasedivergent'),
1652 1683 ('content-divergent', 'contentdivergent'),
1653 1684 ]
1654 1685
1655 1686 def getinstabilitycounts(repo):
1656 1687 filtered = repo.changelog.filteredrevs
1657 1688 counts = {}
1658 1689 for instability, revset in instabilitytypes:
1659 1690 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1660 1691 filtered)
1661 1692 return counts
1662 1693
1663 1694 oldinstabilitycounts = getinstabilitycounts(repo)
1664 1695 @reportsummary
1665 1696 def reportnewinstabilities(repo, tr):
1666 1697 newinstabilitycounts = getinstabilitycounts(repo)
1667 1698 for instability, revset in instabilitytypes:
1668 1699 delta = (newinstabilitycounts[instability] -
1669 1700 oldinstabilitycounts[instability])
1670 1701 msg = getinstabilitymessage(delta, instability)
1671 1702 if msg:
1672 1703 repo.ui.warn(msg)
1673 1704
1674 1705 if txmatch(_reportnewcssource):
1675 1706 @reportsummary
1676 1707 def reportnewcs(repo, tr):
1677 1708 """Report the range of new revisions pulled/unbundled."""
1678 1709 origrepolen = tr.changes.get('origrepolen', len(repo))
1679 1710 unfi = repo.unfiltered()
1680 1711 if origrepolen >= len(unfi):
1681 1712 return
1682 1713
1683 1714 # Compute the bounds of new visible revisions' range.
1684 1715 revs = smartset.spanset(repo, start=origrepolen)
1685 1716 if revs:
1686 1717 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1687 1718
1688 1719 if minrev == maxrev:
1689 1720 revrange = minrev
1690 1721 else:
1691 1722 revrange = '%s:%s' % (minrev, maxrev)
1692 1723 draft = len(repo.revs('%ld and draft()', revs))
1693 1724 secret = len(repo.revs('%ld and secret()', revs))
1694 1725 if not (draft or secret):
1695 1726 msg = _('new changesets %s\n') % revrange
1696 1727 elif draft and secret:
1697 1728 msg = _('new changesets %s (%d drafts, %d secrets)\n')
1698 1729 msg %= (revrange, draft, secret)
1699 1730 elif draft:
1700 1731 msg = _('new changesets %s (%d drafts)\n')
1701 1732 msg %= (revrange, draft)
1702 1733 elif secret:
1703 1734 msg = _('new changesets %s (%d secrets)\n')
1704 1735 msg %= (revrange, secret)
1705 1736 else:
1706 1737 errormsg = 'entered unreachable condition'
1707 1738 raise error.ProgrammingError(errormsg)
1708 1739 repo.ui.status(msg)
1709 1740
1710 1741 # search new changesets directly pulled as obsolete
1711 1742 duplicates = tr.changes.get('revduplicates', ())
1712 1743 obsadded = unfi.revs('(%d: + %ld) and obsolete()',
1713 1744 origrepolen, duplicates)
1714 1745 cl = repo.changelog
1715 1746 extinctadded = [r for r in obsadded if r not in cl]
1716 1747 if extinctadded:
1717 1748 # They are not just obsolete, but obsolete and invisible
1718 1749 # we call them "extinct" internally but the terms have not been
1719 1750 # exposed to users.
1720 1751 msg = '(%d other changesets obsolete on arrival)\n'
1721 1752 repo.ui.status(msg % len(extinctadded))
1722 1753
1723 1754 @reportsummary
1724 1755 def reportphasechanges(repo, tr):
1725 1756 """Report statistics of phase changes for changesets pre-existing
1726 1757 pull/unbundle.
1727 1758 """
1728 1759 origrepolen = tr.changes.get('origrepolen', len(repo))
1729 1760 phasetracking = tr.changes.get('phases', {})
1730 1761 if not phasetracking:
1731 1762 return
1732 1763 published = [
1733 1764 rev for rev, (old, new) in phasetracking.iteritems()
1734 1765 if new == phases.public and rev < origrepolen
1735 1766 ]
1736 1767 if not published:
1737 1768 return
1738 1769 repo.ui.status(_('%d local changesets published\n')
1739 1770 % len(published))
1740 1771
1741 1772 def getinstabilitymessage(delta, instability):
1742 1773 """function to return the message to show warning about new instabilities
1743 1774
1744 1775 exists as a separate function so that extension can wrap to show more
1745 1776 information like how to fix instabilities"""
1746 1777 if delta > 0:
1747 1778 return _('%i new %s changesets\n') % (delta, instability)
1748 1779
1749 1780 def nodesummaries(repo, nodes, maxnumnodes=4):
1750 1781 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1751 1782 return ' '.join(short(h) for h in nodes)
1752 1783 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1753 1784 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1754 1785
1755 1786 def enforcesinglehead(repo, tr, desc):
1756 1787 """check that no named branch has multiple heads"""
1757 1788 if desc in ('strip', 'repair'):
1758 1789 # skip the logic during strip
1759 1790 return
1760 1791 visible = repo.filtered('visible')
1761 1792 # possible improvement: we could restrict the check to affected branch
1762 1793 for name, heads in visible.branchmap().iteritems():
1763 1794 if len(heads) > 1:
1764 1795 msg = _('rejecting multiple heads on branch "%s"')
1765 1796 msg %= name
1766 1797 hint = _('%d heads: %s')
1767 1798 hint %= (len(heads), nodesummaries(repo, heads))
1768 1799 raise error.Abort(msg, hint=hint)
1769 1800
1770 1801 def wrapconvertsink(sink):
1771 1802 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1772 1803 before it is used, whether or not the convert extension was formally loaded.
1773 1804 """
1774 1805 return sink
1775 1806
1776 1807 def unhidehashlikerevs(repo, specs, hiddentype):
1777 1808 """parse the user specs and unhide changesets whose hash or revision number
1778 1809 is passed.
1779 1810
1780 1811 hiddentype can be: 1) 'warn': warn while unhiding changesets
1781 1812 2) 'nowarn': don't warn while unhiding changesets
1782 1813
1783 1814 returns a repo object with the required changesets unhidden
1784 1815 """
1785 1816 if not repo.filtername or not repo.ui.configbool('experimental',
1786 1817 'directaccess'):
1787 1818 return repo
1788 1819
1789 1820 if repo.filtername not in ('visible', 'visible-hidden'):
1790 1821 return repo
1791 1822
1792 1823 symbols = set()
1793 1824 for spec in specs:
1794 1825 try:
1795 1826 tree = revsetlang.parse(spec)
1796 1827 except error.ParseError: # will be reported by scmutil.revrange()
1797 1828 continue
1798 1829
1799 1830 symbols.update(revsetlang.gethashlikesymbols(tree))
1800 1831
1801 1832 if not symbols:
1802 1833 return repo
1803 1834
1804 1835 revs = _getrevsfromsymbols(repo, symbols)
1805 1836
1806 1837 if not revs:
1807 1838 return repo
1808 1839
1809 1840 if hiddentype == 'warn':
1810 1841 unfi = repo.unfiltered()
1811 1842 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1812 1843 repo.ui.warn(_("warning: accessing hidden changesets for write "
1813 1844 "operation: %s\n") % revstr)
1814 1845
1815 1846 # we have to use new filtername to separate branch/tags cache until we can
1816 1847 # disbale these cache when revisions are dynamically pinned.
1817 1848 return repo.filtered('visible-hidden', revs)
1818 1849
1819 1850 def _getrevsfromsymbols(repo, symbols):
1820 1851 """parse the list of symbols and returns a set of revision numbers of hidden
1821 1852 changesets present in symbols"""
1822 1853 revs = set()
1823 1854 unfi = repo.unfiltered()
1824 1855 unficl = unfi.changelog
1825 1856 cl = repo.changelog
1826 1857 tiprev = len(unficl)
1827 1858 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1828 1859 for s in symbols:
1829 1860 try:
1830 1861 n = int(s)
1831 1862 if n <= tiprev:
1832 1863 if not allowrevnums:
1833 1864 continue
1834 1865 else:
1835 1866 if n not in cl:
1836 1867 revs.add(n)
1837 1868 continue
1838 1869 except ValueError:
1839 1870 pass
1840 1871
1841 1872 try:
1842 1873 s = resolvehexnodeidprefix(unfi, s)
1843 1874 except (error.LookupError, error.WdirUnsupported):
1844 1875 s = None
1845 1876
1846 1877 if s is not None:
1847 1878 rev = unficl.rev(s)
1848 1879 if rev not in cl:
1849 1880 revs.add(rev)
1850 1881
1851 1882 return revs
1852 1883
1853 1884 def bookmarkrevs(repo, mark):
1854 1885 """
1855 1886 Select revisions reachable by a given bookmark
1856 1887 """
1857 1888 return repo.revs("ancestors(bookmark(%s)) - "
1858 1889 "ancestors(head() and not bookmark(%s)) - "
1859 1890 "ancestors(bookmark() and not bookmark(%s))",
1860 1891 mark, mark, mark)
@@ -1,894 +1,863 b''
1 1 # templatekw.py - common changeset template keywords
2 2 #
3 3 # Copyright 2005-2009 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 from .i18n import _
11 11 from .node import (
12 12 hex,
13 13 nullid,
14 14 wdirid,
15 15 wdirrev,
16 16 )
17 17
18 18 from . import (
19 19 diffutil,
20 20 encoding,
21 21 error,
22 22 hbisect,
23 23 i18n,
24 24 obsutil,
25 25 patch,
26 26 pycompat,
27 27 registrar,
28 28 scmutil,
29 29 templateutil,
30 30 util,
31 31 )
32 32 from .utils import (
33 33 stringutil,
34 34 )
35 35
36 36 _hybrid = templateutil.hybrid
37 37 hybriddict = templateutil.hybriddict
38 38 hybridlist = templateutil.hybridlist
39 39 compatdict = templateutil.compatdict
40 40 compatlist = templateutil.compatlist
41 41 _showcompatlist = templateutil._showcompatlist
42 42
43 43 def getlatesttags(context, mapping, pattern=None):
44 44 '''return date, distance and name for the latest tag of rev'''
45 45 repo = context.resource(mapping, 'repo')
46 46 ctx = context.resource(mapping, 'ctx')
47 47 cache = context.resource(mapping, 'cache')
48 48
49 49 cachename = 'latesttags'
50 50 if pattern is not None:
51 51 cachename += '-' + pattern
52 52 match = stringutil.stringmatcher(pattern)[2]
53 53 else:
54 54 match = util.always
55 55
56 56 if cachename not in cache:
57 57 # Cache mapping from rev to a tuple with tag date, tag
58 58 # distance and tag name
59 59 cache[cachename] = {-1: (0, 0, ['null'])}
60 60 latesttags = cache[cachename]
61 61
62 62 rev = ctx.rev()
63 63 todo = [rev]
64 64 while todo:
65 65 rev = todo.pop()
66 66 if rev in latesttags:
67 67 continue
68 68 ctx = repo[rev]
69 69 tags = [t for t in ctx.tags()
70 70 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
71 71 and match(t))]
72 72 if tags:
73 73 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
74 74 continue
75 75 try:
76 76 ptags = [latesttags[p.rev()] for p in ctx.parents()]
77 77 if len(ptags) > 1:
78 78 if ptags[0][2] == ptags[1][2]:
79 79 # The tuples are laid out so the right one can be found by
80 80 # comparison in this case.
81 81 pdate, pdist, ptag = max(ptags)
82 82 else:
83 83 def key(x):
84 84 tag = x[2][0]
85 85 if ctx.rev() is None:
86 86 # only() doesn't support wdir
87 87 prevs = [c.rev() for c in ctx.parents()]
88 88 changes = repo.revs('only(%ld, %s)', prevs, tag)
89 89 changessincetag = len(changes) + 1
90 90 else:
91 91 changes = repo.revs('only(%d, %s)', ctx.rev(), tag)
92 92 changessincetag = len(changes)
93 93 # Smallest number of changes since tag wins. Date is
94 94 # used as tiebreaker.
95 95 return [-changessincetag, x[0]]
96 96 pdate, pdist, ptag = max(ptags, key=key)
97 97 else:
98 98 pdate, pdist, ptag = ptags[0]
99 99 except KeyError:
100 100 # Cache miss - recurse
101 101 todo.append(rev)
102 102 todo.extend(p.rev() for p in ctx.parents())
103 103 continue
104 104 latesttags[rev] = pdate, pdist + 1, ptag
105 105 return latesttags[rev]
106 106
107 def getrenamedfn(repo, endrev=None):
108 rcache = {}
109 if endrev is None:
110 endrev = len(repo)
111
112 def getrenamed(fn, rev):
113 '''looks up all renames for a file (up to endrev) the first
114 time the file is given. It indexes on the changerev and only
115 parses the manifest if linkrev != changerev.
116 Returns rename info for fn at changerev rev.'''
117 if fn not in rcache:
118 rcache[fn] = {}
119 fl = repo.file(fn)
120 for i in fl:
121 lr = fl.linkrev(i)
122 renamed = fl.renamed(fl.node(i))
123 rcache[fn][lr] = renamed and renamed[0]
124 if lr >= endrev:
125 break
126 if rev in rcache[fn]:
127 return rcache[fn][rev]
128
129 # If linkrev != rev (i.e. rev not found in rcache) fallback to
130 # filectx logic.
131 try:
132 return repo[rev][fn].copysource()
133 except error.LookupError:
134 return None
135
136 return getrenamed
137
138 107 def getlogcolumns():
139 108 """Return a dict of log column labels"""
140 109 _ = pycompat.identity # temporarily disable gettext
141 110 # i18n: column positioning for "hg log"
142 111 columns = _('bookmark: %s\n'
143 112 'branch: %s\n'
144 113 'changeset: %s\n'
145 114 'copies: %s\n'
146 115 'date: %s\n'
147 116 'extra: %s=%s\n'
148 117 'files+: %s\n'
149 118 'files-: %s\n'
150 119 'files: %s\n'
151 120 'instability: %s\n'
152 121 'manifest: %s\n'
153 122 'obsolete: %s\n'
154 123 'parent: %s\n'
155 124 'phase: %s\n'
156 125 'summary: %s\n'
157 126 'tag: %s\n'
158 127 'user: %s\n')
159 128 return dict(zip([s.split(':', 1)[0] for s in columns.splitlines()],
160 129 i18n._(columns).splitlines(True)))
161 130
162 131 # basic internal templates
163 132 _changeidtmpl = '{rev}:{node|formatnode}'
164 133
165 134 # default templates internally used for rendering of lists
166 135 defaulttempl = {
167 136 'parent': _changeidtmpl + ' ',
168 137 'manifest': _changeidtmpl,
169 138 'file_copy': '{name} ({source})',
170 139 'envvar': '{key}={value}',
171 140 'extra': '{key}={value|stringescape}'
172 141 }
173 142 # filecopy is preserved for compatibility reasons
174 143 defaulttempl['filecopy'] = defaulttempl['file_copy']
175 144
176 145 # keywords are callables (see registrar.templatekeyword for details)
177 146 keywords = {}
178 147 templatekeyword = registrar.templatekeyword(keywords)
179 148
180 149 @templatekeyword('author', requires={'ctx'})
181 150 def showauthor(context, mapping):
182 151 """Alias for ``{user}``"""
183 152 return showuser(context, mapping)
184 153
185 154 @templatekeyword('bisect', requires={'repo', 'ctx'})
186 155 def showbisect(context, mapping):
187 156 """String. The changeset bisection status."""
188 157 repo = context.resource(mapping, 'repo')
189 158 ctx = context.resource(mapping, 'ctx')
190 159 return hbisect.label(repo, ctx.node())
191 160
192 161 @templatekeyword('branch', requires={'ctx'})
193 162 def showbranch(context, mapping):
194 163 """String. The name of the branch on which the changeset was
195 164 committed.
196 165 """
197 166 ctx = context.resource(mapping, 'ctx')
198 167 return ctx.branch()
199 168
200 169 @templatekeyword('branches', requires={'ctx'})
201 170 def showbranches(context, mapping):
202 171 """List of strings. The name of the branch on which the
203 172 changeset was committed. Will be empty if the branch name was
204 173 default. (DEPRECATED)
205 174 """
206 175 ctx = context.resource(mapping, 'ctx')
207 176 branch = ctx.branch()
208 177 if branch != 'default':
209 178 return compatlist(context, mapping, 'branch', [branch],
210 179 plural='branches')
211 180 return compatlist(context, mapping, 'branch', [], plural='branches')
212 181
213 182 @templatekeyword('bookmarks', requires={'repo', 'ctx'})
214 183 def showbookmarks(context, mapping):
215 184 """List of strings. Any bookmarks associated with the
216 185 changeset. Also sets 'active', the name of the active bookmark.
217 186 """
218 187 repo = context.resource(mapping, 'repo')
219 188 ctx = context.resource(mapping, 'ctx')
220 189 bookmarks = ctx.bookmarks()
221 190 active = repo._activebookmark
222 191 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
223 192 f = _showcompatlist(context, mapping, 'bookmark', bookmarks)
224 193 return _hybrid(f, bookmarks, makemap, pycompat.identity)
225 194
226 195 @templatekeyword('children', requires={'ctx'})
227 196 def showchildren(context, mapping):
228 197 """List of strings. The children of the changeset."""
229 198 ctx = context.resource(mapping, 'ctx')
230 199 childrevs = ['%d:%s' % (cctx.rev(), cctx) for cctx in ctx.children()]
231 200 return compatlist(context, mapping, 'children', childrevs, element='child')
232 201
233 202 # Deprecated, but kept alive for help generation a purpose.
234 203 @templatekeyword('currentbookmark', requires={'repo', 'ctx'})
235 204 def showcurrentbookmark(context, mapping):
236 205 """String. The active bookmark, if it is associated with the changeset.
237 206 (DEPRECATED)"""
238 207 return showactivebookmark(context, mapping)
239 208
240 209 @templatekeyword('activebookmark', requires={'repo', 'ctx'})
241 210 def showactivebookmark(context, mapping):
242 211 """String. The active bookmark, if it is associated with the changeset."""
243 212 repo = context.resource(mapping, 'repo')
244 213 ctx = context.resource(mapping, 'ctx')
245 214 active = repo._activebookmark
246 215 if active and active in ctx.bookmarks():
247 216 return active
248 217 return ''
249 218
250 219 @templatekeyword('date', requires={'ctx'})
251 220 def showdate(context, mapping):
252 221 """Date information. The date when the changeset was committed."""
253 222 ctx = context.resource(mapping, 'ctx')
254 223 # the default string format is '<float(unixtime)><tzoffset>' because
255 224 # python-hglib splits date at decimal separator.
256 225 return templateutil.date(ctx.date(), showfmt='%d.0%d')
257 226
258 227 @templatekeyword('desc', requires={'ctx'})
259 228 def showdescription(context, mapping):
260 229 """String. The text of the changeset description."""
261 230 ctx = context.resource(mapping, 'ctx')
262 231 s = ctx.description()
263 232 if isinstance(s, encoding.localstr):
264 233 # try hard to preserve utf-8 bytes
265 234 return encoding.tolocal(encoding.fromlocal(s).strip())
266 235 elif isinstance(s, encoding.safelocalstr):
267 236 return encoding.safelocalstr(s.strip())
268 237 else:
269 238 return s.strip()
270 239
271 240 @templatekeyword('diffstat', requires={'ui', 'ctx'})
272 241 def showdiffstat(context, mapping):
273 242 """String. Statistics of changes with the following format:
274 243 "modified files: +added/-removed lines"
275 244 """
276 245 ui = context.resource(mapping, 'ui')
277 246 ctx = context.resource(mapping, 'ctx')
278 247 diffopts = diffutil.diffallopts(ui, {'noprefix': False})
279 248 diff = ctx.diff(opts=diffopts)
280 249 stats = patch.diffstatdata(util.iterlines(diff))
281 250 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
282 251 return '%d: +%d/-%d' % (len(stats), adds, removes)
283 252
284 253 @templatekeyword('envvars', requires={'ui'})
285 254 def showenvvars(context, mapping):
286 255 """A dictionary of environment variables. (EXPERIMENTAL)"""
287 256 ui = context.resource(mapping, 'ui')
288 257 env = ui.exportableenviron()
289 258 env = util.sortdict((k, env[k]) for k in sorted(env))
290 259 return compatdict(context, mapping, 'envvar', env, plural='envvars')
291 260
292 261 @templatekeyword('extras', requires={'ctx'})
293 262 def showextras(context, mapping):
294 263 """List of dicts with key, value entries of the 'extras'
295 264 field of this changeset."""
296 265 ctx = context.resource(mapping, 'ctx')
297 266 extras = ctx.extra()
298 267 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
299 268 makemap = lambda k: {'key': k, 'value': extras[k]}
300 269 c = [makemap(k) for k in extras]
301 270 f = _showcompatlist(context, mapping, 'extra', c, plural='extras')
302 271 return _hybrid(f, extras, makemap,
303 272 lambda k: '%s=%s' % (k, stringutil.escapestr(extras[k])))
304 273
305 274 def _getfilestatus(context, mapping, listall=False):
306 275 ctx = context.resource(mapping, 'ctx')
307 276 revcache = context.resource(mapping, 'revcache')
308 277 if 'filestatus' not in revcache or revcache['filestatusall'] < listall:
309 278 stat = ctx.p1().status(ctx, listignored=listall, listclean=listall,
310 279 listunknown=listall)
311 280 revcache['filestatus'] = stat
312 281 revcache['filestatusall'] = listall
313 282 return revcache['filestatus']
314 283
315 284 def _getfilestatusmap(context, mapping, listall=False):
316 285 revcache = context.resource(mapping, 'revcache')
317 286 if 'filestatusmap' not in revcache or revcache['filestatusall'] < listall:
318 287 stat = _getfilestatus(context, mapping, listall=listall)
319 288 revcache['filestatusmap'] = statmap = {}
320 289 for char, files in zip(pycompat.iterbytestr('MAR!?IC'), stat):
321 290 statmap.update((f, char) for f in files)
322 291 return revcache['filestatusmap'] # {path: statchar}
323 292
324 293 def _showfilesbystat(context, mapping, name, index):
325 294 stat = _getfilestatus(context, mapping)
326 295 files = stat[index]
327 296 return templateutil.compatfileslist(context, mapping, name, files)
328 297
329 298 @templatekeyword('file_adds', requires={'ctx', 'revcache'})
330 299 def showfileadds(context, mapping):
331 300 """List of strings. Files added by this changeset."""
332 301 return _showfilesbystat(context, mapping, 'file_add', 1)
333 302
334 303 @templatekeyword('file_copies',
335 304 requires={'repo', 'ctx', 'cache', 'revcache'})
336 305 def showfilecopies(context, mapping):
337 306 """List of strings. Files copied in this changeset with
338 307 their sources.
339 308 """
340 309 repo = context.resource(mapping, 'repo')
341 310 ctx = context.resource(mapping, 'ctx')
342 311 cache = context.resource(mapping, 'cache')
343 312 copies = context.resource(mapping, 'revcache').get('copies')
344 313 if copies is None:
345 314 if 'getrenamed' not in cache:
346 cache['getrenamed'] = getrenamedfn(repo)
315 cache['getrenamed'] = scmutil.getrenamedfn(repo)
347 316 copies = []
348 317 getrenamed = cache['getrenamed']
349 318 for fn in ctx.files():
350 319 rename = getrenamed(fn, ctx.rev())
351 320 if rename:
352 321 copies.append((fn, rename))
353 322 return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
354 323 copies)
355 324
356 325 # showfilecopiesswitch() displays file copies only if copy records are
357 326 # provided before calling the templater, usually with a --copies
358 327 # command line switch.
359 328 @templatekeyword('file_copies_switch', requires={'revcache'})
360 329 def showfilecopiesswitch(context, mapping):
361 330 """List of strings. Like "file_copies" but displayed
362 331 only if the --copied switch is set.
363 332 """
364 333 copies = context.resource(mapping, 'revcache').get('copies') or []
365 334 return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
366 335 copies)
367 336
368 337 @templatekeyword('file_dels', requires={'ctx', 'revcache'})
369 338 def showfiledels(context, mapping):
370 339 """List of strings. Files removed by this changeset."""
371 340 return _showfilesbystat(context, mapping, 'file_del', 2)
372 341
373 342 @templatekeyword('file_mods', requires={'ctx', 'revcache'})
374 343 def showfilemods(context, mapping):
375 344 """List of strings. Files modified by this changeset."""
376 345 return _showfilesbystat(context, mapping, 'file_mod', 0)
377 346
378 347 @templatekeyword('files', requires={'ctx'})
379 348 def showfiles(context, mapping):
380 349 """List of strings. All files modified, added, or removed by this
381 350 changeset.
382 351 """
383 352 ctx = context.resource(mapping, 'ctx')
384 353 return templateutil.compatfileslist(context, mapping, 'file', ctx.files())
385 354
386 355 @templatekeyword('graphnode', requires={'repo', 'ctx'})
387 356 def showgraphnode(context, mapping):
388 357 """String. The character representing the changeset node in an ASCII
389 358 revision graph."""
390 359 repo = context.resource(mapping, 'repo')
391 360 ctx = context.resource(mapping, 'ctx')
392 361 return getgraphnode(repo, ctx)
393 362
394 363 def getgraphnode(repo, ctx):
395 364 return getgraphnodecurrent(repo, ctx) or getgraphnodesymbol(ctx)
396 365
397 366 def getgraphnodecurrent(repo, ctx):
398 367 wpnodes = repo.dirstate.parents()
399 368 if wpnodes[1] == nullid:
400 369 wpnodes = wpnodes[:1]
401 370 if ctx.node() in wpnodes:
402 371 return '@'
403 372 else:
404 373 return ''
405 374
406 375 def getgraphnodesymbol(ctx):
407 376 if ctx.obsolete():
408 377 return 'x'
409 378 elif ctx.isunstable():
410 379 return '*'
411 380 elif ctx.closesbranch():
412 381 return '_'
413 382 else:
414 383 return 'o'
415 384
416 385 @templatekeyword('graphwidth', requires=())
417 386 def showgraphwidth(context, mapping):
418 387 """Integer. The width of the graph drawn by 'log --graph' or zero."""
419 388 # just hosts documentation; should be overridden by template mapping
420 389 return 0
421 390
422 391 @templatekeyword('index', requires=())
423 392 def showindex(context, mapping):
424 393 """Integer. The current iteration of the loop. (0 indexed)"""
425 394 # just hosts documentation; should be overridden by template mapping
426 395 raise error.Abort(_("can't use index in this context"))
427 396
428 397 @templatekeyword('latesttag', requires={'repo', 'ctx', 'cache'})
429 398 def showlatesttag(context, mapping):
430 399 """List of strings. The global tags on the most recent globally
431 400 tagged ancestor of this changeset. If no such tags exist, the list
432 401 consists of the single string "null".
433 402 """
434 403 return showlatesttags(context, mapping, None)
435 404
436 405 def showlatesttags(context, mapping, pattern):
437 406 """helper method for the latesttag keyword and function"""
438 407 latesttags = getlatesttags(context, mapping, pattern)
439 408
440 409 # latesttag[0] is an implementation detail for sorting csets on different
441 410 # branches in a stable manner- it is the date the tagged cset was created,
442 411 # not the date the tag was created. Therefore it isn't made visible here.
443 412 makemap = lambda v: {
444 413 'changes': _showchangessincetag,
445 414 'distance': latesttags[1],
446 415 'latesttag': v, # BC with {latesttag % '{latesttag}'}
447 416 'tag': v
448 417 }
449 418
450 419 tags = latesttags[2]
451 420 f = _showcompatlist(context, mapping, 'latesttag', tags, separator=':')
452 421 return _hybrid(f, tags, makemap, pycompat.identity)
453 422
454 423 @templatekeyword('latesttagdistance', requires={'repo', 'ctx', 'cache'})
455 424 def showlatesttagdistance(context, mapping):
456 425 """Integer. Longest path to the latest tag."""
457 426 return getlatesttags(context, mapping)[1]
458 427
459 428 @templatekeyword('changessincelatesttag', requires={'repo', 'ctx', 'cache'})
460 429 def showchangessincelatesttag(context, mapping):
461 430 """Integer. All ancestors not in the latest tag."""
462 431 tag = getlatesttags(context, mapping)[2][0]
463 432 mapping = context.overlaymap(mapping, {'tag': tag})
464 433 return _showchangessincetag(context, mapping)
465 434
466 435 def _showchangessincetag(context, mapping):
467 436 repo = context.resource(mapping, 'repo')
468 437 ctx = context.resource(mapping, 'ctx')
469 438 offset = 0
470 439 revs = [ctx.rev()]
471 440 tag = context.symbol(mapping, 'tag')
472 441
473 442 # The only() revset doesn't currently support wdir()
474 443 if ctx.rev() is None:
475 444 offset = 1
476 445 revs = [p.rev() for p in ctx.parents()]
477 446
478 447 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
479 448
480 449 # teach templater latesttags.changes is switched to (context, mapping) API
481 450 _showchangessincetag._requires = {'repo', 'ctx'}
482 451
483 452 @templatekeyword('manifest', requires={'repo', 'ctx'})
484 453 def showmanifest(context, mapping):
485 454 repo = context.resource(mapping, 'repo')
486 455 ctx = context.resource(mapping, 'ctx')
487 456 mnode = ctx.manifestnode()
488 457 if mnode is None:
489 458 mnode = wdirid
490 459 mrev = wdirrev
491 460 else:
492 461 mrev = repo.manifestlog.rev(mnode)
493 462 mhex = hex(mnode)
494 463 mapping = context.overlaymap(mapping, {'rev': mrev, 'node': mhex})
495 464 f = context.process('manifest', mapping)
496 465 return templateutil.hybriditem(f, None, f,
497 466 lambda x: {'rev': mrev, 'node': mhex})
498 467
499 468 @templatekeyword('obsfate', requires={'ui', 'repo', 'ctx'})
500 469 def showobsfate(context, mapping):
501 470 # this function returns a list containing pre-formatted obsfate strings.
502 471 #
503 472 # This function will be replaced by templates fragments when we will have
504 473 # the verbosity templatekw available.
505 474 succsandmarkers = showsuccsandmarkers(context, mapping)
506 475
507 476 ui = context.resource(mapping, 'ui')
508 477 repo = context.resource(mapping, 'repo')
509 478 values = []
510 479
511 480 for x in succsandmarkers.tovalue(context, mapping):
512 481 v = obsutil.obsfateprinter(ui, repo, x['successors'], x['markers'],
513 482 scmutil.formatchangeid)
514 483 values.append(v)
515 484
516 485 return compatlist(context, mapping, "fate", values)
517 486
518 487 def shownames(context, mapping, namespace):
519 488 """helper method to generate a template keyword for a namespace"""
520 489 repo = context.resource(mapping, 'repo')
521 490 ctx = context.resource(mapping, 'ctx')
522 491 ns = repo.names[namespace]
523 492 names = ns.names(repo, ctx.node())
524 493 return compatlist(context, mapping, ns.templatename, names,
525 494 plural=namespace)
526 495
527 496 @templatekeyword('namespaces', requires={'repo', 'ctx'})
528 497 def shownamespaces(context, mapping):
529 498 """Dict of lists. Names attached to this changeset per
530 499 namespace."""
531 500 repo = context.resource(mapping, 'repo')
532 501 ctx = context.resource(mapping, 'ctx')
533 502
534 503 namespaces = util.sortdict()
535 504 def makensmapfn(ns):
536 505 # 'name' for iterating over namespaces, templatename for local reference
537 506 return lambda v: {'name': v, ns.templatename: v}
538 507
539 508 for k, ns in repo.names.iteritems():
540 509 names = ns.names(repo, ctx.node())
541 510 f = _showcompatlist(context, mapping, 'name', names)
542 511 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
543 512
544 513 f = _showcompatlist(context, mapping, 'namespace', list(namespaces))
545 514
546 515 def makemap(ns):
547 516 return {
548 517 'namespace': ns,
549 518 'names': namespaces[ns],
550 519 'builtin': repo.names[ns].builtin,
551 520 'colorname': repo.names[ns].colorname,
552 521 }
553 522
554 523 return _hybrid(f, namespaces, makemap, pycompat.identity)
555 524
556 525 @templatekeyword('negrev', requires={'repo', 'ctx'})
557 526 def shownegrev(context, mapping):
558 527 """Integer. The repository-local changeset negative revision number,
559 528 which counts in the opposite direction."""
560 529 ctx = context.resource(mapping, 'ctx')
561 530 rev = ctx.rev()
562 531 if rev is None or rev < 0: # wdir() or nullrev?
563 532 return None
564 533 repo = context.resource(mapping, 'repo')
565 534 return rev - len(repo)
566 535
567 536 @templatekeyword('node', requires={'ctx'})
568 537 def shownode(context, mapping):
569 538 """String. The changeset identification hash, as a 40 hexadecimal
570 539 digit string.
571 540 """
572 541 ctx = context.resource(mapping, 'ctx')
573 542 return ctx.hex()
574 543
575 544 @templatekeyword('obsolete', requires={'ctx'})
576 545 def showobsolete(context, mapping):
577 546 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
578 547 ctx = context.resource(mapping, 'ctx')
579 548 if ctx.obsolete():
580 549 return 'obsolete'
581 550 return ''
582 551
583 552 @templatekeyword('path', requires={'fctx'})
584 553 def showpath(context, mapping):
585 554 """String. Repository-absolute path of the current file. (EXPERIMENTAL)"""
586 555 fctx = context.resource(mapping, 'fctx')
587 556 return fctx.path()
588 557
589 558 @templatekeyword('peerurls', requires={'repo'})
590 559 def showpeerurls(context, mapping):
591 560 """A dictionary of repository locations defined in the [paths] section
592 561 of your configuration file."""
593 562 repo = context.resource(mapping, 'repo')
594 563 # see commands.paths() for naming of dictionary keys
595 564 paths = repo.ui.paths
596 565 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
597 566 def makemap(k):
598 567 p = paths[k]
599 568 d = {'name': k, 'url': p.rawloc}
600 569 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
601 570 return d
602 571 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
603 572
604 573 @templatekeyword("predecessors", requires={'repo', 'ctx'})
605 574 def showpredecessors(context, mapping):
606 575 """Returns the list of the closest visible successors. (EXPERIMENTAL)"""
607 576 repo = context.resource(mapping, 'repo')
608 577 ctx = context.resource(mapping, 'ctx')
609 578 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
610 579 predecessors = pycompat.maplist(hex, predecessors)
611 580
612 581 return _hybrid(None, predecessors,
613 582 lambda x: {'ctx': repo[x]},
614 583 lambda x: scmutil.formatchangeid(repo[x]))
615 584
616 585 @templatekeyword('reporoot', requires={'repo'})
617 586 def showreporoot(context, mapping):
618 587 """String. The root directory of the current repository."""
619 588 repo = context.resource(mapping, 'repo')
620 589 return repo.root
621 590
622 591 @templatekeyword('size', requires={'fctx'})
623 592 def showsize(context, mapping):
624 593 """Integer. Size of the current file in bytes. (EXPERIMENTAL)"""
625 594 fctx = context.resource(mapping, 'fctx')
626 595 return fctx.size()
627 596
628 597 # requires 'fctx' to denote {status} depends on (ctx, path) pair
629 598 @templatekeyword('status', requires={'ctx', 'fctx', 'revcache'})
630 599 def showstatus(context, mapping):
631 600 """String. Status code of the current file. (EXPERIMENTAL)"""
632 601 path = templateutil.runsymbol(context, mapping, 'path')
633 602 path = templateutil.stringify(context, mapping, path)
634 603 if not path:
635 604 return
636 605 statmap = _getfilestatusmap(context, mapping)
637 606 if path not in statmap:
638 607 statmap = _getfilestatusmap(context, mapping, listall=True)
639 608 return statmap.get(path)
640 609
641 610 @templatekeyword("successorssets", requires={'repo', 'ctx'})
642 611 def showsuccessorssets(context, mapping):
643 612 """Returns a string of sets of successors for a changectx. Format used
644 613 is: [ctx1, ctx2], [ctx3] if ctx has been split into ctx1 and ctx2
645 614 while also diverged into ctx3. (EXPERIMENTAL)"""
646 615 repo = context.resource(mapping, 'repo')
647 616 ctx = context.resource(mapping, 'ctx')
648 617 if not ctx.obsolete():
649 618 return ''
650 619
651 620 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
652 621 ssets = [[hex(n) for n in ss] for ss in ssets]
653 622
654 623 data = []
655 624 for ss in ssets:
656 625 h = _hybrid(None, ss, lambda x: {'ctx': repo[x]},
657 626 lambda x: scmutil.formatchangeid(repo[x]))
658 627 data.append(h)
659 628
660 629 # Format the successorssets
661 630 def render(d):
662 631 return templateutil.stringify(context, mapping, d)
663 632
664 633 def gen(data):
665 634 yield "; ".join(render(d) for d in data)
666 635
667 636 return _hybrid(gen(data), data, lambda x: {'successorset': x},
668 637 pycompat.identity)
669 638
670 639 @templatekeyword("succsandmarkers", requires={'repo', 'ctx'})
671 640 def showsuccsandmarkers(context, mapping):
672 641 """Returns a list of dict for each final successor of ctx. The dict
673 642 contains successors node id in "successors" keys and the list of
674 643 obs-markers from ctx to the set of successors in "markers".
675 644 (EXPERIMENTAL)
676 645 """
677 646 repo = context.resource(mapping, 'repo')
678 647 ctx = context.resource(mapping, 'ctx')
679 648
680 649 values = obsutil.successorsandmarkers(repo, ctx)
681 650
682 651 if values is None:
683 652 values = []
684 653
685 654 # Format successors and markers to avoid exposing binary to templates
686 655 data = []
687 656 for i in values:
688 657 # Format successors
689 658 successors = i['successors']
690 659
691 660 successors = [hex(n) for n in successors]
692 661 successors = _hybrid(None, successors,
693 662 lambda x: {'ctx': repo[x]},
694 663 lambda x: scmutil.formatchangeid(repo[x]))
695 664
696 665 # Format markers
697 666 finalmarkers = []
698 667 for m in i['markers']:
699 668 hexprec = hex(m[0])
700 669 hexsucs = tuple(hex(n) for n in m[1])
701 670 hexparents = None
702 671 if m[5] is not None:
703 672 hexparents = tuple(hex(n) for n in m[5])
704 673 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
705 674 finalmarkers.append(newmarker)
706 675
707 676 data.append({'successors': successors, 'markers': finalmarkers})
708 677
709 678 return templateutil.mappinglist(data)
710 679
711 680 @templatekeyword('p1', requires={'ctx'})
712 681 def showp1(context, mapping):
713 682 """Changeset. The changeset's first parent. ``{p1.rev}`` for the revision
714 683 number, and ``{p1.node}`` for the identification hash."""
715 684 ctx = context.resource(mapping, 'ctx')
716 685 return templateutil.mappingdict({'ctx': ctx.p1()}, tmpl=_changeidtmpl)
717 686
718 687 @templatekeyword('p2', requires={'ctx'})
719 688 def showp2(context, mapping):
720 689 """Changeset. The changeset's second parent. ``{p2.rev}`` for the revision
721 690 number, and ``{p2.node}`` for the identification hash."""
722 691 ctx = context.resource(mapping, 'ctx')
723 692 return templateutil.mappingdict({'ctx': ctx.p2()}, tmpl=_changeidtmpl)
724 693
725 694 @templatekeyword('p1rev', requires={'ctx'})
726 695 def showp1rev(context, mapping):
727 696 """Integer. The repository-local revision number of the changeset's
728 697 first parent, or -1 if the changeset has no parents. (DEPRECATED)"""
729 698 ctx = context.resource(mapping, 'ctx')
730 699 return ctx.p1().rev()
731 700
732 701 @templatekeyword('p2rev', requires={'ctx'})
733 702 def showp2rev(context, mapping):
734 703 """Integer. The repository-local revision number of the changeset's
735 704 second parent, or -1 if the changeset has no second parent. (DEPRECATED)"""
736 705 ctx = context.resource(mapping, 'ctx')
737 706 return ctx.p2().rev()
738 707
739 708 @templatekeyword('p1node', requires={'ctx'})
740 709 def showp1node(context, mapping):
741 710 """String. The identification hash of the changeset's first parent,
742 711 as a 40 digit hexadecimal string. If the changeset has no parents, all
743 712 digits are 0. (DEPRECATED)"""
744 713 ctx = context.resource(mapping, 'ctx')
745 714 return ctx.p1().hex()
746 715
747 716 @templatekeyword('p2node', requires={'ctx'})
748 717 def showp2node(context, mapping):
749 718 """String. The identification hash of the changeset's second
750 719 parent, as a 40 digit hexadecimal string. If the changeset has no second
751 720 parent, all digits are 0. (DEPRECATED)"""
752 721 ctx = context.resource(mapping, 'ctx')
753 722 return ctx.p2().hex()
754 723
755 724 @templatekeyword('parents', requires={'repo', 'ctx'})
756 725 def showparents(context, mapping):
757 726 """List of strings. The parents of the changeset in "rev:node"
758 727 format. If the changeset has only one "natural" parent (the predecessor
759 728 revision) nothing is shown."""
760 729 repo = context.resource(mapping, 'repo')
761 730 ctx = context.resource(mapping, 'ctx')
762 731 pctxs = scmutil.meaningfulparents(repo, ctx)
763 732 prevs = [p.rev() for p in pctxs]
764 733 parents = [[('rev', p.rev()),
765 734 ('node', p.hex()),
766 735 ('phase', p.phasestr())]
767 736 for p in pctxs]
768 737 f = _showcompatlist(context, mapping, 'parent', parents)
769 738 return _hybrid(f, prevs, lambda x: {'ctx': repo[x]},
770 739 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
771 740
772 741 @templatekeyword('phase', requires={'ctx'})
773 742 def showphase(context, mapping):
774 743 """String. The changeset phase name."""
775 744 ctx = context.resource(mapping, 'ctx')
776 745 return ctx.phasestr()
777 746
778 747 @templatekeyword('phaseidx', requires={'ctx'})
779 748 def showphaseidx(context, mapping):
780 749 """Integer. The changeset phase index. (ADVANCED)"""
781 750 ctx = context.resource(mapping, 'ctx')
782 751 return ctx.phase()
783 752
784 753 @templatekeyword('rev', requires={'ctx'})
785 754 def showrev(context, mapping):
786 755 """Integer. The repository-local changeset revision number."""
787 756 ctx = context.resource(mapping, 'ctx')
788 757 return scmutil.intrev(ctx)
789 758
790 759 def showrevslist(context, mapping, name, revs):
791 760 """helper to generate a list of revisions in which a mapped template will
792 761 be evaluated"""
793 762 repo = context.resource(mapping, 'repo')
794 763 # revs may be a smartset; don't compute it until f() has to be evaluated
795 764 def f():
796 765 srevs = ['%d' % r for r in revs]
797 766 return _showcompatlist(context, mapping, name, srevs)
798 767 return _hybrid(f, revs,
799 768 lambda x: {name: x, 'ctx': repo[x]},
800 769 pycompat.identity, keytype=int)
801 770
802 771 @templatekeyword('subrepos', requires={'ctx'})
803 772 def showsubrepos(context, mapping):
804 773 """List of strings. Updated subrepositories in the changeset."""
805 774 ctx = context.resource(mapping, 'ctx')
806 775 substate = ctx.substate
807 776 if not substate:
808 777 return compatlist(context, mapping, 'subrepo', [])
809 778 psubstate = ctx.p1().substate or {}
810 779 subrepos = []
811 780 for sub in substate:
812 781 if sub not in psubstate or substate[sub] != psubstate[sub]:
813 782 subrepos.append(sub) # modified or newly added in ctx
814 783 for sub in psubstate:
815 784 if sub not in substate:
816 785 subrepos.append(sub) # removed in ctx
817 786 return compatlist(context, mapping, 'subrepo', sorted(subrepos))
818 787
819 788 # don't remove "showtags" definition, even though namespaces will put
820 789 # a helper function for "tags" keyword into "keywords" map automatically,
821 790 # because online help text is built without namespaces initialization
822 791 @templatekeyword('tags', requires={'repo', 'ctx'})
823 792 def showtags(context, mapping):
824 793 """List of strings. Any tags associated with the changeset."""
825 794 return shownames(context, mapping, 'tags')
826 795
827 796 @templatekeyword('termwidth', requires={'ui'})
828 797 def showtermwidth(context, mapping):
829 798 """Integer. The width of the current terminal."""
830 799 ui = context.resource(mapping, 'ui')
831 800 return ui.termwidth()
832 801
833 802 @templatekeyword('user', requires={'ctx'})
834 803 def showuser(context, mapping):
835 804 """String. The unmodified author of the changeset."""
836 805 ctx = context.resource(mapping, 'ctx')
837 806 return ctx.user()
838 807
839 808 @templatekeyword('instabilities', requires={'ctx'})
840 809 def showinstabilities(context, mapping):
841 810 """List of strings. Evolution instabilities affecting the changeset.
842 811 (EXPERIMENTAL)
843 812 """
844 813 ctx = context.resource(mapping, 'ctx')
845 814 return compatlist(context, mapping, 'instability', ctx.instabilities(),
846 815 plural='instabilities')
847 816
848 817 @templatekeyword('verbosity', requires={'ui'})
849 818 def showverbosity(context, mapping):
850 819 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
851 820 or ''."""
852 821 ui = context.resource(mapping, 'ui')
853 822 # see logcmdutil.changesettemplater for priority of these flags
854 823 if ui.debugflag:
855 824 return 'debug'
856 825 elif ui.quiet:
857 826 return 'quiet'
858 827 elif ui.verbose:
859 828 return 'verbose'
860 829 return ''
861 830
862 831 @templatekeyword('whyunstable', requires={'repo', 'ctx'})
863 832 def showwhyunstable(context, mapping):
864 833 """List of dicts explaining all instabilities of a changeset.
865 834 (EXPERIMENTAL)
866 835 """
867 836 repo = context.resource(mapping, 'repo')
868 837 ctx = context.resource(mapping, 'ctx')
869 838
870 839 def formatnode(ctx):
871 840 return '%s (%s)' % (scmutil.formatchangeid(ctx), ctx.phasestr())
872 841
873 842 entries = obsutil.whyunstable(repo, ctx)
874 843
875 844 for entry in entries:
876 845 if entry.get('divergentnodes'):
877 846 dnodes = entry['divergentnodes']
878 847 dnhybrid = _hybrid(None, [dnode.hex() for dnode in dnodes],
879 848 lambda x: {'ctx': repo[x]},
880 849 lambda x: formatnode(repo[x]))
881 850 entry['divergentnodes'] = dnhybrid
882 851
883 852 tmpl = ('{instability}:{if(divergentnodes, " ")}{divergentnodes} '
884 853 '{reason} {node|short}')
885 854 return templateutil.mappinglist(entries, tmpl=tmpl, sep='\n')
886 855
887 856 def loadkeyword(ui, extname, registrarobj):
888 857 """Load template keyword from specified registrarobj
889 858 """
890 859 for name, func in registrarobj._table.iteritems():
891 860 keywords[name] = func
892 861
893 862 # tell hggettext to extract docstrings from these functions:
894 863 i18nfunctions = keywords.values()
General Comments 0
You need to be logged in to leave comments. Login now