##// END OF EJS Templates
graphlog: use '%' for other context in merge conflict...
Martin von Zweigbergk -
r44819:14d0e895 default
parent child Browse files
Show More
@@ -1,107 +1,109 b''
1 1 # -*- coding: UTF-8 -*-
2 2 # beautifygraph.py - improve graph output by using Unicode characters
3 3 #
4 4 # Copyright 2018 John Stiles <johnstiles@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 '''beautify log -G output by using Unicode characters (EXPERIMENTAL)
10 10
11 11 A terminal with UTF-8 support and monospace narrow text are required.
12 12 '''
13 13
14 14 from __future__ import absolute_import
15 15
16 16 from mercurial.i18n import _
17 17 from mercurial import (
18 18 encoding,
19 19 extensions,
20 20 graphmod,
21 21 pycompat,
22 22 templatekw,
23 23 )
24 24
25 25 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
26 26 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
27 27 # be specifying the version(s) of Mercurial they are tested with, or
28 28 # leave the attribute unspecified.
29 29 testedwith = b'ships-with-hg-core'
30 30
31 31
32 32 def prettyedge(before, edge, after):
33 33 if edge == b'~':
34 34 return b'\xE2\x95\xA7' # U+2567 ╧
35 35 if edge == b'/':
36 36 return b'\xE2\x95\xB1' # U+2571 β•±
37 37 if edge == b'-':
38 38 return b'\xE2\x94\x80' # U+2500 ─
39 39 if edge == b'|':
40 40 return b'\xE2\x94\x82' # U+2502 β”‚
41 41 if edge == b':':
42 42 return b'\xE2\x94\x86' # U+2506 ┆
43 43 if edge == b'\\':
44 44 return b'\xE2\x95\xB2' # U+2572 β•²
45 45 if edge == b'+':
46 46 if before == b' ' and not after == b' ':
47 47 return b'\xE2\x94\x9C' # U+251C β”œ
48 48 if after == b' ' and not before == b' ':
49 49 return b'\xE2\x94\xA4' # U+2524 ─
50 50 return b'\xE2\x94\xBC' # U+253C β”Ό
51 51 return edge
52 52
53 53
54 54 def convertedges(line):
55 55 line = b' %s ' % line
56 56 pretty = []
57 57 for idx in pycompat.xrange(len(line) - 2):
58 58 pretty.append(
59 59 prettyedge(
60 60 line[idx : idx + 1],
61 61 line[idx + 1 : idx + 2],
62 62 line[idx + 2 : idx + 3],
63 63 )
64 64 )
65 65 return b''.join(pretty)
66 66
67 67
68 68 def getprettygraphnode(orig, *args, **kwargs):
69 69 node = orig(*args, **kwargs)
70 70 if node == b'o':
71 71 return b'\xE2\x97\x8B' # U+25CB β—‹
72 72 if node == b'@':
73 73 return b'\xE2\x97\x8D' # U+25CD ◍
74 if node == b'%':
75 return b'\xE2\x97\x8D' # U+25CE β—Ž
74 76 if node == b'*':
75 77 return b'\xE2\x88\x97' # U+2217 βˆ—
76 78 if node == b'x':
77 79 return b'\xE2\x97\x8C' # U+25CC β—Œ
78 80 if node == b'_':
79 81 return b'\xE2\x95\xA4' # U+2564 β•€
80 82 return node
81 83
82 84
83 85 def outputprettygraph(orig, ui, graph, *args, **kwargs):
84 86 (edges, text) = zip(*graph)
85 87 graph = zip([convertedges(e) for e in edges], text)
86 88 return orig(ui, graph, *args, **kwargs)
87 89
88 90
89 91 def extsetup(ui):
90 92 if ui.plain(b'graph'):
91 93 return
92 94
93 95 if encoding.encoding != b'UTF-8':
94 96 ui.warn(_(b'beautifygraph: unsupported encoding, UTF-8 required\n'))
95 97 return
96 98
97 99 if 'A' in encoding._wide:
98 100 ui.warn(
99 101 _(
100 102 b'beautifygraph: unsupported terminal settings, '
101 103 b'monospace narrow text required\n'
102 104 )
103 105 )
104 106 return
105 107
106 108 extensions.wrapfunction(graphmod, b'outputgraph', outputprettygraph)
107 109 extensions.wrapfunction(templatekw, b'getgraphnode', getprettygraphnode)
@@ -1,940 +1,940 b''
1 1 # hgweb/webutil.py - utility library for the web interface.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import copy
12 12 import difflib
13 13 import os
14 14 import re
15 15
16 16 from ..i18n import _
17 17 from ..node import hex, nullid, short
18 18 from ..pycompat import setattr
19 19
20 20 from .common import (
21 21 ErrorResponse,
22 22 HTTP_BAD_REQUEST,
23 23 HTTP_NOT_FOUND,
24 24 paritygen,
25 25 )
26 26
27 27 from .. import (
28 28 context,
29 29 diffutil,
30 30 error,
31 31 match,
32 32 mdiff,
33 33 obsutil,
34 34 patch,
35 35 pathutil,
36 36 pycompat,
37 37 scmutil,
38 38 templatefilters,
39 39 templatekw,
40 40 templateutil,
41 41 ui as uimod,
42 42 util,
43 43 )
44 44
45 45 from ..utils import stringutil
46 46
47 47 archivespecs = util.sortdict(
48 48 (
49 49 (b'zip', (b'application/zip', b'zip', b'.zip', None)),
50 50 (b'gz', (b'application/x-gzip', b'tgz', b'.tar.gz', None)),
51 51 (b'bz2', (b'application/x-bzip2', b'tbz2', b'.tar.bz2', None)),
52 52 )
53 53 )
54 54
55 55
56 56 def archivelist(ui, nodeid, url=None):
57 57 allowed = ui.configlist(b'web', b'allow-archive', untrusted=True)
58 58 archives = []
59 59
60 60 for typ, spec in pycompat.iteritems(archivespecs):
61 61 if typ in allowed or ui.configbool(
62 62 b'web', b'allow' + typ, untrusted=True
63 63 ):
64 64 archives.append(
65 65 {
66 66 b'type': typ,
67 67 b'extension': spec[2],
68 68 b'node': nodeid,
69 69 b'url': url,
70 70 }
71 71 )
72 72
73 73 return templateutil.mappinglist(archives)
74 74
75 75
76 76 def up(p):
77 77 if p[0:1] != b"/":
78 78 p = b"/" + p
79 79 if p[-1:] == b"/":
80 80 p = p[:-1]
81 81 up = os.path.dirname(p)
82 82 if up == b"/":
83 83 return b"/"
84 84 return up + b"/"
85 85
86 86
87 87 def _navseq(step, firststep=None):
88 88 if firststep:
89 89 yield firststep
90 90 if firststep >= 20 and firststep <= 40:
91 91 firststep = 50
92 92 yield firststep
93 93 assert step > 0
94 94 assert firststep > 0
95 95 while step <= firststep:
96 96 step *= 10
97 97 while True:
98 98 yield 1 * step
99 99 yield 3 * step
100 100 step *= 10
101 101
102 102
103 103 class revnav(object):
104 104 def __init__(self, repo):
105 105 """Navigation generation object
106 106
107 107 :repo: repo object we generate nav for
108 108 """
109 109 # used for hex generation
110 110 self._revlog = repo.changelog
111 111
112 112 def __nonzero__(self):
113 113 """return True if any revision to navigate over"""
114 114 return self._first() is not None
115 115
116 116 __bool__ = __nonzero__
117 117
118 118 def _first(self):
119 119 """return the minimum non-filtered changeset or None"""
120 120 try:
121 121 return next(iter(self._revlog))
122 122 except StopIteration:
123 123 return None
124 124
125 125 def hex(self, rev):
126 126 return hex(self._revlog.node(rev))
127 127
128 128 def gen(self, pos, pagelen, limit):
129 129 """computes label and revision id for navigation link
130 130
131 131 :pos: is the revision relative to which we generate navigation.
132 132 :pagelen: the size of each navigation page
133 133 :limit: how far shall we link
134 134
135 135 The return is:
136 136 - a single element mappinglist
137 137 - containing a dictionary with a `before` and `after` key
138 138 - values are dictionaries with `label` and `node` keys
139 139 """
140 140 if not self:
141 141 # empty repo
142 142 return templateutil.mappinglist(
143 143 [
144 144 {
145 145 b'before': templateutil.mappinglist([]),
146 146 b'after': templateutil.mappinglist([]),
147 147 },
148 148 ]
149 149 )
150 150
151 151 targets = []
152 152 for f in _navseq(1, pagelen):
153 153 if f > limit:
154 154 break
155 155 targets.append(pos + f)
156 156 targets.append(pos - f)
157 157 targets.sort()
158 158
159 159 first = self._first()
160 160 navbefore = [{b'label': b'(%i)' % first, b'node': self.hex(first)}]
161 161 navafter = []
162 162 for rev in targets:
163 163 if rev not in self._revlog:
164 164 continue
165 165 if pos < rev < limit:
166 166 navafter.append(
167 167 {b'label': b'+%d' % abs(rev - pos), b'node': self.hex(rev)}
168 168 )
169 169 if 0 < rev < pos:
170 170 navbefore.append(
171 171 {b'label': b'-%d' % abs(rev - pos), b'node': self.hex(rev)}
172 172 )
173 173
174 174 navafter.append({b'label': b'tip', b'node': b'tip'})
175 175
176 176 # TODO: maybe this can be a scalar object supporting tomap()
177 177 return templateutil.mappinglist(
178 178 [
179 179 {
180 180 b'before': templateutil.mappinglist(navbefore),
181 181 b'after': templateutil.mappinglist(navafter),
182 182 },
183 183 ]
184 184 )
185 185
186 186
187 187 class filerevnav(revnav):
188 188 def __init__(self, repo, path):
189 189 """Navigation generation object
190 190
191 191 :repo: repo object we generate nav for
192 192 :path: path of the file we generate nav for
193 193 """
194 194 # used for iteration
195 195 self._changelog = repo.unfiltered().changelog
196 196 # used for hex generation
197 197 self._revlog = repo.file(path)
198 198
199 199 def hex(self, rev):
200 200 return hex(self._changelog.node(self._revlog.linkrev(rev)))
201 201
202 202
203 203 # TODO: maybe this can be a wrapper class for changectx/filectx list, which
204 204 # yields {'ctx': ctx}
205 205 def _ctxsgen(context, ctxs):
206 206 for s in ctxs:
207 207 d = {
208 208 b'node': s.hex(),
209 209 b'rev': s.rev(),
210 210 b'user': s.user(),
211 211 b'date': s.date(),
212 212 b'description': s.description(),
213 213 b'branch': s.branch(),
214 214 }
215 215 if util.safehasattr(s, b'path'):
216 216 d[b'file'] = s.path()
217 217 yield d
218 218
219 219
220 220 def _siblings(siblings=None, hiderev=None):
221 221 if siblings is None:
222 222 siblings = []
223 223 siblings = [s for s in siblings if s.node() != nullid]
224 224 if len(siblings) == 1 and siblings[0].rev() == hiderev:
225 225 siblings = []
226 226 return templateutil.mappinggenerator(_ctxsgen, args=(siblings,))
227 227
228 228
229 229 def difffeatureopts(req, ui, section):
230 230 diffopts = diffutil.difffeatureopts(
231 231 ui, untrusted=True, section=section, whitespace=True
232 232 )
233 233
234 234 for k in (
235 235 b'ignorews',
236 236 b'ignorewsamount',
237 237 b'ignorewseol',
238 238 b'ignoreblanklines',
239 239 ):
240 240 v = req.qsparams.get(k)
241 241 if v is not None:
242 242 v = stringutil.parsebool(v)
243 243 setattr(diffopts, k, v if v is not None else True)
244 244
245 245 return diffopts
246 246
247 247
248 248 def annotate(req, fctx, ui):
249 249 diffopts = difffeatureopts(req, ui, b'annotate')
250 250 return fctx.annotate(follow=True, diffopts=diffopts)
251 251
252 252
253 253 def parents(ctx, hide=None):
254 254 if isinstance(ctx, context.basefilectx):
255 255 introrev = ctx.introrev()
256 256 if ctx.changectx().rev() != introrev:
257 257 return _siblings([ctx.repo()[introrev]], hide)
258 258 return _siblings(ctx.parents(), hide)
259 259
260 260
261 261 def children(ctx, hide=None):
262 262 return _siblings(ctx.children(), hide)
263 263
264 264
265 265 def renamelink(fctx):
266 266 r = fctx.renamed()
267 267 if r:
268 268 return templateutil.mappinglist([{b'file': r[0], b'node': hex(r[1])}])
269 269 return templateutil.mappinglist([])
270 270
271 271
272 272 def nodetagsdict(repo, node):
273 273 return templateutil.hybridlist(repo.nodetags(node), name=b'name')
274 274
275 275
276 276 def nodebookmarksdict(repo, node):
277 277 return templateutil.hybridlist(repo.nodebookmarks(node), name=b'name')
278 278
279 279
280 280 def nodebranchdict(repo, ctx):
281 281 branches = []
282 282 branch = ctx.branch()
283 283 # If this is an empty repo, ctx.node() == nullid,
284 284 # ctx.branch() == 'default'.
285 285 try:
286 286 branchnode = repo.branchtip(branch)
287 287 except error.RepoLookupError:
288 288 branchnode = None
289 289 if branchnode == ctx.node():
290 290 branches.append(branch)
291 291 return templateutil.hybridlist(branches, name=b'name')
292 292
293 293
294 294 def nodeinbranch(repo, ctx):
295 295 branches = []
296 296 branch = ctx.branch()
297 297 try:
298 298 branchnode = repo.branchtip(branch)
299 299 except error.RepoLookupError:
300 300 branchnode = None
301 301 if branch != b'default' and branchnode != ctx.node():
302 302 branches.append(branch)
303 303 return templateutil.hybridlist(branches, name=b'name')
304 304
305 305
306 306 def nodebranchnodefault(ctx):
307 307 branches = []
308 308 branch = ctx.branch()
309 309 if branch != b'default':
310 310 branches.append(branch)
311 311 return templateutil.hybridlist(branches, name=b'name')
312 312
313 313
314 314 def _nodenamesgen(context, f, node, name):
315 315 for t in f(node):
316 316 yield {name: t}
317 317
318 318
319 319 def showtag(repo, t1, node=nullid):
320 320 args = (repo.nodetags, node, b'tag')
321 321 return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
322 322
323 323
324 324 def showbookmark(repo, t1, node=nullid):
325 325 args = (repo.nodebookmarks, node, b'bookmark')
326 326 return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
327 327
328 328
329 329 def branchentries(repo, stripecount, limit=0):
330 330 tips = []
331 331 heads = repo.heads()
332 332 parity = paritygen(stripecount)
333 333 sortkey = lambda item: (not item[1], item[0].rev())
334 334
335 335 def entries(context):
336 336 count = 0
337 337 if not tips:
338 338 for tag, hs, tip, closed in repo.branchmap().iterbranches():
339 339 tips.append((repo[tip], closed))
340 340 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
341 341 if limit > 0 and count >= limit:
342 342 return
343 343 count += 1
344 344 if closed:
345 345 status = b'closed'
346 346 elif ctx.node() not in heads:
347 347 status = b'inactive'
348 348 else:
349 349 status = b'open'
350 350 yield {
351 351 b'parity': next(parity),
352 352 b'branch': ctx.branch(),
353 353 b'status': status,
354 354 b'node': ctx.hex(),
355 355 b'date': ctx.date(),
356 356 }
357 357
358 358 return templateutil.mappinggenerator(entries)
359 359
360 360
361 361 def cleanpath(repo, path):
362 362 path = path.lstrip(b'/')
363 363 auditor = pathutil.pathauditor(repo.root, realfs=False)
364 364 return pathutil.canonpath(repo.root, b'', path, auditor=auditor)
365 365
366 366
367 367 def changectx(repo, req):
368 368 changeid = b"tip"
369 369 if b'node' in req.qsparams:
370 370 changeid = req.qsparams[b'node']
371 371 ipos = changeid.find(b':')
372 372 if ipos != -1:
373 373 changeid = changeid[(ipos + 1) :]
374 374
375 375 return scmutil.revsymbol(repo, changeid)
376 376
377 377
378 378 def basechangectx(repo, req):
379 379 if b'node' in req.qsparams:
380 380 changeid = req.qsparams[b'node']
381 381 ipos = changeid.find(b':')
382 382 if ipos != -1:
383 383 changeid = changeid[:ipos]
384 384 return scmutil.revsymbol(repo, changeid)
385 385
386 386 return None
387 387
388 388
389 389 def filectx(repo, req):
390 390 if b'file' not in req.qsparams:
391 391 raise ErrorResponse(HTTP_NOT_FOUND, b'file not given')
392 392 path = cleanpath(repo, req.qsparams[b'file'])
393 393 if b'node' in req.qsparams:
394 394 changeid = req.qsparams[b'node']
395 395 elif b'filenode' in req.qsparams:
396 396 changeid = req.qsparams[b'filenode']
397 397 else:
398 398 raise ErrorResponse(HTTP_NOT_FOUND, b'node or filenode not given')
399 399 try:
400 400 fctx = scmutil.revsymbol(repo, changeid)[path]
401 401 except error.RepoError:
402 402 fctx = repo.filectx(path, fileid=changeid)
403 403
404 404 return fctx
405 405
406 406
407 407 def linerange(req):
408 408 linerange = req.qsparams.getall(b'linerange')
409 409 if not linerange:
410 410 return None
411 411 if len(linerange) > 1:
412 412 raise ErrorResponse(HTTP_BAD_REQUEST, b'redundant linerange parameter')
413 413 try:
414 414 fromline, toline = map(int, linerange[0].split(b':', 1))
415 415 except ValueError:
416 416 raise ErrorResponse(HTTP_BAD_REQUEST, b'invalid linerange parameter')
417 417 try:
418 418 return util.processlinerange(fromline, toline)
419 419 except error.ParseError as exc:
420 420 raise ErrorResponse(HTTP_BAD_REQUEST, pycompat.bytestr(exc))
421 421
422 422
423 423 def formatlinerange(fromline, toline):
424 424 return b'%d:%d' % (fromline + 1, toline)
425 425
426 426
427 427 def _succsandmarkersgen(context, mapping):
428 428 repo = context.resource(mapping, b'repo')
429 429 itemmappings = templatekw.showsuccsandmarkers(context, mapping)
430 430 for item in itemmappings.tovalue(context, mapping):
431 431 item[b'successors'] = _siblings(
432 432 repo[successor] for successor in item[b'successors']
433 433 )
434 434 yield item
435 435
436 436
437 437 def succsandmarkers(context, mapping):
438 438 return templateutil.mappinggenerator(_succsandmarkersgen, args=(mapping,))
439 439
440 440
441 441 # teach templater succsandmarkers is switched to (context, mapping) API
442 442 succsandmarkers._requires = {b'repo', b'ctx'}
443 443
444 444
445 445 def _whyunstablegen(context, mapping):
446 446 repo = context.resource(mapping, b'repo')
447 447 ctx = context.resource(mapping, b'ctx')
448 448
449 449 entries = obsutil.whyunstable(repo, ctx)
450 450 for entry in entries:
451 451 if entry.get(b'divergentnodes'):
452 452 entry[b'divergentnodes'] = _siblings(entry[b'divergentnodes'])
453 453 yield entry
454 454
455 455
456 456 def whyunstable(context, mapping):
457 457 return templateutil.mappinggenerator(_whyunstablegen, args=(mapping,))
458 458
459 459
460 460 whyunstable._requires = {b'repo', b'ctx'}
461 461
462 462
463 463 def commonentry(repo, ctx):
464 464 node = scmutil.binnode(ctx)
465 465 return {
466 466 # TODO: perhaps ctx.changectx() should be assigned if ctx is a
467 467 # filectx, but I'm not pretty sure if that would always work because
468 468 # fctx.parents() != fctx.changectx.parents() for example.
469 469 b'ctx': ctx,
470 470 b'rev': ctx.rev(),
471 471 b'node': hex(node),
472 472 b'author': ctx.user(),
473 473 b'desc': ctx.description(),
474 474 b'date': ctx.date(),
475 475 b'extra': ctx.extra(),
476 476 b'phase': ctx.phasestr(),
477 477 b'obsolete': ctx.obsolete(),
478 478 b'succsandmarkers': succsandmarkers,
479 479 b'instabilities': templateutil.hybridlist(
480 480 ctx.instabilities(), name=b'instability'
481 481 ),
482 482 b'whyunstable': whyunstable,
483 483 b'branch': nodebranchnodefault(ctx),
484 484 b'inbranch': nodeinbranch(repo, ctx),
485 485 b'branches': nodebranchdict(repo, ctx),
486 486 b'tags': nodetagsdict(repo, node),
487 487 b'bookmarks': nodebookmarksdict(repo, node),
488 488 b'parent': lambda context, mapping: parents(ctx),
489 489 b'child': lambda context, mapping: children(ctx),
490 490 }
491 491
492 492
493 493 def changelistentry(web, ctx):
494 494 '''Obtain a dictionary to be used for entries in a changelist.
495 495
496 496 This function is called when producing items for the "entries" list passed
497 497 to the "shortlog" and "changelog" templates.
498 498 '''
499 499 repo = web.repo
500 500 rev = ctx.rev()
501 501 n = scmutil.binnode(ctx)
502 502 showtags = showtag(repo, b'changelogtag', n)
503 503 files = listfilediffs(ctx.files(), n, web.maxfiles)
504 504
505 505 entry = commonentry(repo, ctx)
506 506 entry.update(
507 507 {
508 508 b'allparents': lambda context, mapping: parents(ctx),
509 509 b'parent': lambda context, mapping: parents(ctx, rev - 1),
510 510 b'child': lambda context, mapping: children(ctx, rev + 1),
511 511 b'changelogtag': showtags,
512 512 b'files': files,
513 513 }
514 514 )
515 515 return entry
516 516
517 517
518 518 def changelistentries(web, revs, maxcount, parityfn):
519 519 """Emit up to N records for an iterable of revisions."""
520 520 repo = web.repo
521 521
522 522 count = 0
523 523 for rev in revs:
524 524 if count >= maxcount:
525 525 break
526 526
527 527 count += 1
528 528
529 529 entry = changelistentry(web, repo[rev])
530 530 entry[b'parity'] = next(parityfn)
531 531
532 532 yield entry
533 533
534 534
535 535 def symrevorshortnode(req, ctx):
536 536 if b'node' in req.qsparams:
537 537 return templatefilters.revescape(req.qsparams[b'node'])
538 538 else:
539 539 return short(scmutil.binnode(ctx))
540 540
541 541
542 542 def _listfilesgen(context, ctx, stripecount):
543 543 parity = paritygen(stripecount)
544 544 filesadded = ctx.filesadded()
545 545 for blockno, f in enumerate(ctx.files()):
546 546 if f not in ctx:
547 547 status = b'removed'
548 548 elif f in filesadded:
549 549 status = b'added'
550 550 else:
551 551 status = b'modified'
552 552 template = b'filenolink' if status == b'removed' else b'filenodelink'
553 553 yield context.process(
554 554 template,
555 555 {
556 556 b'node': ctx.hex(),
557 557 b'file': f,
558 558 b'blockno': blockno + 1,
559 559 b'parity': next(parity),
560 560 b'status': status,
561 561 },
562 562 )
563 563
564 564
565 565 def changesetentry(web, ctx):
566 566 '''Obtain a dictionary to be used to render the "changeset" template.'''
567 567
568 568 showtags = showtag(web.repo, b'changesettag', scmutil.binnode(ctx))
569 569 showbookmarks = showbookmark(
570 570 web.repo, b'changesetbookmark', scmutil.binnode(ctx)
571 571 )
572 572 showbranch = nodebranchnodefault(ctx)
573 573
574 574 basectx = basechangectx(web.repo, web.req)
575 575 if basectx is None:
576 576 basectx = ctx.p1()
577 577
578 578 style = web.config(b'web', b'style')
579 579 if b'style' in web.req.qsparams:
580 580 style = web.req.qsparams[b'style']
581 581
582 582 diff = diffs(web, ctx, basectx, None, style)
583 583
584 584 parity = paritygen(web.stripecount)
585 585 diffstatsgen = diffstatgen(web.repo.ui, ctx, basectx)
586 586 diffstats = diffstat(ctx, diffstatsgen, parity)
587 587
588 588 return dict(
589 589 diff=diff,
590 590 symrev=symrevorshortnode(web.req, ctx),
591 591 basenode=basectx.hex(),
592 592 changesettag=showtags,
593 593 changesetbookmark=showbookmarks,
594 594 changesetbranch=showbranch,
595 595 files=templateutil.mappedgenerator(
596 596 _listfilesgen, args=(ctx, web.stripecount)
597 597 ),
598 598 diffsummary=lambda context, mapping: diffsummary(diffstatsgen),
599 599 diffstat=diffstats,
600 600 archives=web.archivelist(ctx.hex()),
601 601 **pycompat.strkwargs(commonentry(web.repo, ctx))
602 602 )
603 603
604 604
605 605 def _listfilediffsgen(context, files, node, max):
606 606 for f in files[:max]:
607 607 yield context.process(b'filedifflink', {b'node': hex(node), b'file': f})
608 608 if len(files) > max:
609 609 yield context.process(b'fileellipses', {})
610 610
611 611
612 612 def listfilediffs(files, node, max):
613 613 return templateutil.mappedgenerator(
614 614 _listfilediffsgen, args=(files, node, max)
615 615 )
616 616
617 617
618 618 def _prettyprintdifflines(context, lines, blockno, lineidprefix):
619 619 for lineno, l in enumerate(lines, 1):
620 620 difflineno = b"%d.%d" % (blockno, lineno)
621 621 if l.startswith(b'+'):
622 622 ltype = b"difflineplus"
623 623 elif l.startswith(b'-'):
624 624 ltype = b"difflineminus"
625 625 elif l.startswith(b'@'):
626 626 ltype = b"difflineat"
627 627 else:
628 628 ltype = b"diffline"
629 629 yield context.process(
630 630 ltype,
631 631 {
632 632 b'line': l,
633 633 b'lineno': lineno,
634 634 b'lineid': lineidprefix + b"l%s" % difflineno,
635 635 b'linenumber': b"% 8s" % difflineno,
636 636 },
637 637 )
638 638
639 639
640 640 def _diffsgen(
641 641 context,
642 642 repo,
643 643 ctx,
644 644 basectx,
645 645 files,
646 646 style,
647 647 stripecount,
648 648 linerange,
649 649 lineidprefix,
650 650 ):
651 651 if files:
652 652 m = match.exact(files)
653 653 else:
654 654 m = match.always()
655 655
656 656 diffopts = patch.diffopts(repo.ui, untrusted=True)
657 657 parity = paritygen(stripecount)
658 658
659 659 diffhunks = patch.diffhunks(repo, basectx, ctx, m, opts=diffopts)
660 660 for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
661 661 if style != b'raw':
662 662 header = header[1:]
663 663 lines = [h + b'\n' for h in header]
664 664 for hunkrange, hunklines in hunks:
665 665 if linerange is not None and hunkrange is not None:
666 666 s1, l1, s2, l2 = hunkrange
667 667 if not mdiff.hunkinrange((s2, l2), linerange):
668 668 continue
669 669 lines.extend(hunklines)
670 670 if lines:
671 671 l = templateutil.mappedgenerator(
672 672 _prettyprintdifflines, args=(lines, blockno, lineidprefix)
673 673 )
674 674 yield {
675 675 b'parity': next(parity),
676 676 b'blockno': blockno,
677 677 b'lines': l,
678 678 }
679 679
680 680
681 681 def diffs(web, ctx, basectx, files, style, linerange=None, lineidprefix=b''):
682 682 args = (
683 683 web.repo,
684 684 ctx,
685 685 basectx,
686 686 files,
687 687 style,
688 688 web.stripecount,
689 689 linerange,
690 690 lineidprefix,
691 691 )
692 692 return templateutil.mappinggenerator(
693 693 _diffsgen, args=args, name=b'diffblock'
694 694 )
695 695
696 696
697 697 def _compline(type, leftlineno, leftline, rightlineno, rightline):
698 698 lineid = leftlineno and (b"l%d" % leftlineno) or b''
699 699 lineid += rightlineno and (b"r%d" % rightlineno) or b''
700 700 llno = b'%d' % leftlineno if leftlineno else b''
701 701 rlno = b'%d' % rightlineno if rightlineno else b''
702 702 return {
703 703 b'type': type,
704 704 b'lineid': lineid,
705 705 b'leftlineno': leftlineno,
706 706 b'leftlinenumber': b"% 6s" % llno,
707 707 b'leftline': leftline or b'',
708 708 b'rightlineno': rightlineno,
709 709 b'rightlinenumber': b"% 6s" % rlno,
710 710 b'rightline': rightline or b'',
711 711 }
712 712
713 713
714 714 def _getcompblockgen(context, leftlines, rightlines, opcodes):
715 715 for type, llo, lhi, rlo, rhi in opcodes:
716 716 type = pycompat.sysbytes(type)
717 717 len1 = lhi - llo
718 718 len2 = rhi - rlo
719 719 count = min(len1, len2)
720 720 for i in pycompat.xrange(count):
721 721 yield _compline(
722 722 type=type,
723 723 leftlineno=llo + i + 1,
724 724 leftline=leftlines[llo + i],
725 725 rightlineno=rlo + i + 1,
726 726 rightline=rightlines[rlo + i],
727 727 )
728 728 if len1 > len2:
729 729 for i in pycompat.xrange(llo + count, lhi):
730 730 yield _compline(
731 731 type=type,
732 732 leftlineno=i + 1,
733 733 leftline=leftlines[i],
734 734 rightlineno=None,
735 735 rightline=None,
736 736 )
737 737 elif len2 > len1:
738 738 for i in pycompat.xrange(rlo + count, rhi):
739 739 yield _compline(
740 740 type=type,
741 741 leftlineno=None,
742 742 leftline=None,
743 743 rightlineno=i + 1,
744 744 rightline=rightlines[i],
745 745 )
746 746
747 747
748 748 def _getcompblock(leftlines, rightlines, opcodes):
749 749 args = (leftlines, rightlines, opcodes)
750 750 return templateutil.mappinggenerator(
751 751 _getcompblockgen, args=args, name=b'comparisonline'
752 752 )
753 753
754 754
755 755 def _comparegen(context, contextnum, leftlines, rightlines):
756 756 '''Generator function that provides side-by-side comparison data.'''
757 757 s = difflib.SequenceMatcher(None, leftlines, rightlines)
758 758 if contextnum < 0:
759 759 l = _getcompblock(leftlines, rightlines, s.get_opcodes())
760 760 yield {b'lines': l}
761 761 else:
762 762 for oc in s.get_grouped_opcodes(n=contextnum):
763 763 l = _getcompblock(leftlines, rightlines, oc)
764 764 yield {b'lines': l}
765 765
766 766
767 767 def compare(contextnum, leftlines, rightlines):
768 768 args = (contextnum, leftlines, rightlines)
769 769 return templateutil.mappinggenerator(
770 770 _comparegen, args=args, name=b'comparisonblock'
771 771 )
772 772
773 773
774 774 def diffstatgen(ui, ctx, basectx):
775 775 '''Generator function that provides the diffstat data.'''
776 776
777 777 diffopts = patch.diffopts(ui, {b'noprefix': False})
778 778 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx, opts=diffopts)))
779 779 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
780 780 while True:
781 781 yield stats, maxname, maxtotal, addtotal, removetotal, binary
782 782
783 783
784 784 def diffsummary(statgen):
785 785 '''Return a short summary of the diff.'''
786 786
787 787 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
788 788 return _(b' %d files changed, %d insertions(+), %d deletions(-)\n') % (
789 789 len(stats),
790 790 addtotal,
791 791 removetotal,
792 792 )
793 793
794 794
795 795 def _diffstattmplgen(context, ctx, statgen, parity):
796 796 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
797 797 files = ctx.files()
798 798
799 799 def pct(i):
800 800 if maxtotal == 0:
801 801 return 0
802 802 return (float(i) / maxtotal) * 100
803 803
804 804 fileno = 0
805 805 for filename, adds, removes, isbinary in stats:
806 806 template = b'diffstatlink' if filename in files else b'diffstatnolink'
807 807 total = adds + removes
808 808 fileno += 1
809 809 yield context.process(
810 810 template,
811 811 {
812 812 b'node': ctx.hex(),
813 813 b'file': filename,
814 814 b'fileno': fileno,
815 815 b'total': total,
816 816 b'addpct': pct(adds),
817 817 b'removepct': pct(removes),
818 818 b'parity': next(parity),
819 819 },
820 820 )
821 821
822 822
823 823 def diffstat(ctx, statgen, parity):
824 824 '''Return a diffstat template for each file in the diff.'''
825 825 args = (ctx, statgen, parity)
826 826 return templateutil.mappedgenerator(_diffstattmplgen, args=args)
827 827
828 828
829 829 class sessionvars(templateutil.wrapped):
830 830 def __init__(self, vars, start=b'?'):
831 831 self._start = start
832 832 self._vars = vars
833 833
834 834 def __getitem__(self, key):
835 835 return self._vars[key]
836 836
837 837 def __setitem__(self, key, value):
838 838 self._vars[key] = value
839 839
840 840 def __copy__(self):
841 841 return sessionvars(copy.copy(self._vars), self._start)
842 842
843 843 def contains(self, context, mapping, item):
844 844 item = templateutil.unwrapvalue(context, mapping, item)
845 845 return item in self._vars
846 846
847 847 def getmember(self, context, mapping, key):
848 848 key = templateutil.unwrapvalue(context, mapping, key)
849 849 return self._vars.get(key)
850 850
851 851 def getmin(self, context, mapping):
852 852 raise error.ParseError(_(b'not comparable'))
853 853
854 854 def getmax(self, context, mapping):
855 855 raise error.ParseError(_(b'not comparable'))
856 856
857 857 def filter(self, context, mapping, select):
858 858 # implement if necessary
859 859 raise error.ParseError(_(b'not filterable'))
860 860
861 861 def itermaps(self, context):
862 862 separator = self._start
863 863 for key, value in sorted(pycompat.iteritems(self._vars)):
864 864 yield {
865 865 b'name': key,
866 866 b'value': pycompat.bytestr(value),
867 867 b'separator': separator,
868 868 }
869 869 separator = b'&'
870 870
871 871 def join(self, context, mapping, sep):
872 872 # could be '{separator}{name}={value|urlescape}'
873 873 raise error.ParseError(_(b'not displayable without template'))
874 874
875 875 def show(self, context, mapping):
876 876 return self.join(context, mapping, b'')
877 877
878 878 def tobool(self, context, mapping):
879 879 return bool(self._vars)
880 880
881 881 def tovalue(self, context, mapping):
882 882 return self._vars
883 883
884 884
885 885 class wsgiui(uimod.ui):
886 886 # default termwidth breaks under mod_wsgi
887 887 def termwidth(self):
888 888 return 80
889 889
890 890
891 891 def getwebsubs(repo):
892 892 websubtable = []
893 893 websubdefs = repo.ui.configitems(b'websub')
894 894 # we must maintain interhg backwards compatibility
895 895 websubdefs += repo.ui.configitems(b'interhg')
896 896 for key, pattern in websubdefs:
897 897 # grab the delimiter from the character after the "s"
898 898 unesc = pattern[1:2]
899 899 delim = stringutil.reescape(unesc)
900 900
901 901 # identify portions of the pattern, taking care to avoid escaped
902 902 # delimiters. the replace format and flags are optional, but
903 903 # delimiters are required.
904 904 match = re.match(
905 905 br'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
906 906 % (delim, delim, delim),
907 907 pattern,
908 908 )
909 909 if not match:
910 910 repo.ui.warn(
911 911 _(b"websub: invalid pattern for %s: %s\n") % (key, pattern)
912 912 )
913 913 continue
914 914
915 915 # we need to unescape the delimiter for regexp and format
916 916 delim_re = re.compile(br'(?<!\\)\\%s' % delim)
917 917 regexp = delim_re.sub(unesc, match.group(1))
918 918 format = delim_re.sub(unesc, match.group(2))
919 919
920 920 # the pattern allows for 6 regexp flags, so set them if necessary
921 921 flagin = match.group(3)
922 922 flags = 0
923 923 if flagin:
924 924 for flag in pycompat.sysstr(flagin.upper()):
925 925 flags |= re.__dict__[flag]
926 926
927 927 try:
928 928 regexp = re.compile(regexp, flags)
929 929 websubtable.append((regexp, format))
930 930 except re.error:
931 931 repo.ui.warn(
932 932 _(b"websub: invalid regexp for %s: %s\n") % (key, regexp)
933 933 )
934 934 return websubtable
935 935
936 936
937 937 def getgraphnode(repo, ctx):
938 938 return templatekw.getgraphnodecurrent(
939 repo, ctx
939 repo, ctx, {}
940 940 ) + templatekw.getgraphnodesymbol(ctx)
@@ -1,1088 +1,1089 b''
1 1 # logcmdutil.py - utility for log-like commands
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 itertools
11 11 import os
12 12 import posixpath
13 13
14 14 from .i18n import _
15 15 from .node import (
16 16 nullid,
17 17 wdirid,
18 18 wdirrev,
19 19 )
20 20
21 21 from . import (
22 22 dagop,
23 23 error,
24 24 formatter,
25 25 graphmod,
26 26 match as matchmod,
27 27 mdiff,
28 28 patch,
29 29 pathutil,
30 30 pycompat,
31 31 revset,
32 32 revsetlang,
33 33 scmutil,
34 34 smartset,
35 35 templatekw,
36 36 templater,
37 37 util,
38 38 )
39 39 from .utils import (
40 40 dateutil,
41 41 stringutil,
42 42 )
43 43
44 44
45 45 if pycompat.TYPE_CHECKING:
46 46 from typing import (
47 47 Any,
48 48 Optional,
49 49 Tuple,
50 50 )
51 51
52 52 for t in (Any, Optional, Tuple):
53 53 assert t
54 54
55 55
56 56 def getlimit(opts):
57 57 """get the log limit according to option -l/--limit"""
58 58 limit = opts.get(b'limit')
59 59 if limit:
60 60 try:
61 61 limit = int(limit)
62 62 except ValueError:
63 63 raise error.Abort(_(b'limit must be a positive integer'))
64 64 if limit <= 0:
65 65 raise error.Abort(_(b'limit must be positive'))
66 66 else:
67 67 limit = None
68 68 return limit
69 69
70 70
71 71 def diffordiffstat(
72 72 ui,
73 73 repo,
74 74 diffopts,
75 75 node1,
76 76 node2,
77 77 match,
78 78 changes=None,
79 79 stat=False,
80 80 fp=None,
81 81 graphwidth=0,
82 82 prefix=b'',
83 83 root=b'',
84 84 listsubrepos=False,
85 85 hunksfilterfn=None,
86 86 ):
87 87 '''show diff or diffstat.'''
88 88 ctx1 = repo[node1]
89 89 ctx2 = repo[node2]
90 90 if root:
91 91 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
92 92 else:
93 93 relroot = b''
94 94 copysourcematch = None
95 95
96 96 def compose(f, g):
97 97 return lambda x: f(g(x))
98 98
99 99 def pathfn(f):
100 100 return posixpath.join(prefix, f)
101 101
102 102 if relroot != b'':
103 103 # XXX relative roots currently don't work if the root is within a
104 104 # subrepo
105 105 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
106 106 uirelroot = uipathfn(pathfn(relroot))
107 107 relroot += b'/'
108 108 for matchroot in match.files():
109 109 if not matchroot.startswith(relroot):
110 110 ui.warn(
111 111 _(b'warning: %s not inside relative root %s\n')
112 112 % (uipathfn(pathfn(matchroot)), uirelroot)
113 113 )
114 114
115 115 relrootmatch = scmutil.match(ctx2, pats=[relroot], default=b'path')
116 116 match = matchmod.intersectmatchers(match, relrootmatch)
117 117 copysourcematch = relrootmatch
118 118
119 119 checkroot = repo.ui.configbool(
120 120 b'devel', b'all-warnings'
121 121 ) or repo.ui.configbool(b'devel', b'check-relroot')
122 122
123 123 def relrootpathfn(f):
124 124 if checkroot and not f.startswith(relroot):
125 125 raise AssertionError(
126 126 b"file %s doesn't start with relroot %s" % (f, relroot)
127 127 )
128 128 return f[len(relroot) :]
129 129
130 130 pathfn = compose(relrootpathfn, pathfn)
131 131
132 132 if stat:
133 133 diffopts = diffopts.copy(context=0, noprefix=False)
134 134 width = 80
135 135 if not ui.plain():
136 136 width = ui.termwidth() - graphwidth
137 137 # If an explicit --root was given, don't respect ui.relative-paths
138 138 if not relroot:
139 139 pathfn = compose(scmutil.getuipathfn(repo), pathfn)
140 140
141 141 chunks = ctx2.diff(
142 142 ctx1,
143 143 match,
144 144 changes,
145 145 opts=diffopts,
146 146 pathfn=pathfn,
147 147 copysourcematch=copysourcematch,
148 148 hunksfilterfn=hunksfilterfn,
149 149 )
150 150
151 151 if fp is not None or ui.canwritewithoutlabels():
152 152 out = fp or ui
153 153 if stat:
154 154 chunks = [patch.diffstat(util.iterlines(chunks), width=width)]
155 155 for chunk in util.filechunkiter(util.chunkbuffer(chunks)):
156 156 out.write(chunk)
157 157 else:
158 158 if stat:
159 159 chunks = patch.diffstatui(util.iterlines(chunks), width=width)
160 160 else:
161 161 chunks = patch.difflabel(
162 162 lambda chunks, **kwargs: chunks, chunks, opts=diffopts
163 163 )
164 164 if ui.canbatchlabeledwrites():
165 165
166 166 def gen():
167 167 for chunk, label in chunks:
168 168 yield ui.label(chunk, label=label)
169 169
170 170 for chunk in util.filechunkiter(util.chunkbuffer(gen())):
171 171 ui.write(chunk)
172 172 else:
173 173 for chunk, label in chunks:
174 174 ui.write(chunk, label=label)
175 175
176 176 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
177 177 tempnode2 = node2
178 178 try:
179 179 if node2 is not None:
180 180 tempnode2 = ctx2.substate[subpath][1]
181 181 except KeyError:
182 182 # A subrepo that existed in node1 was deleted between node1 and
183 183 # node2 (inclusive). Thus, ctx2's substate won't contain that
184 184 # subpath. The best we can do is to ignore it.
185 185 tempnode2 = None
186 186 submatch = matchmod.subdirmatcher(subpath, match)
187 187 subprefix = repo.wvfs.reljoin(prefix, subpath)
188 188 if listsubrepos or match.exact(subpath) or any(submatch.files()):
189 189 sub.diff(
190 190 ui,
191 191 diffopts,
192 192 tempnode2,
193 193 submatch,
194 194 changes=changes,
195 195 stat=stat,
196 196 fp=fp,
197 197 prefix=subprefix,
198 198 )
199 199
200 200
201 201 class changesetdiffer(object):
202 202 """Generate diff of changeset with pre-configured filtering functions"""
203 203
204 204 def _makefilematcher(self, ctx):
205 205 return scmutil.matchall(ctx.repo())
206 206
207 207 def _makehunksfilter(self, ctx):
208 208 return None
209 209
210 210 def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False):
211 211 repo = ctx.repo()
212 212 node = ctx.node()
213 213 prev = ctx.p1().node()
214 214 diffordiffstat(
215 215 ui,
216 216 repo,
217 217 diffopts,
218 218 prev,
219 219 node,
220 220 match=self._makefilematcher(ctx),
221 221 stat=stat,
222 222 graphwidth=graphwidth,
223 223 hunksfilterfn=self._makehunksfilter(ctx),
224 224 )
225 225
226 226
227 227 def changesetlabels(ctx):
228 228 labels = [b'log.changeset', b'changeset.%s' % ctx.phasestr()]
229 229 if ctx.obsolete():
230 230 labels.append(b'changeset.obsolete')
231 231 if ctx.isunstable():
232 232 labels.append(b'changeset.unstable')
233 233 for instability in ctx.instabilities():
234 234 labels.append(b'instability.%s' % instability)
235 235 return b' '.join(labels)
236 236
237 237
238 238 class changesetprinter(object):
239 239 '''show changeset information when templating not requested.'''
240 240
241 241 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
242 242 self.ui = ui
243 243 self.repo = repo
244 244 self.buffered = buffered
245 245 self._differ = differ or changesetdiffer()
246 246 self._diffopts = patch.diffallopts(ui, diffopts)
247 247 self._includestat = diffopts and diffopts.get(b'stat')
248 248 self._includediff = diffopts and diffopts.get(b'patch')
249 249 self.header = {}
250 250 self.hunk = {}
251 251 self.lastheader = None
252 252 self.footer = None
253 253 self._columns = templatekw.getlogcolumns()
254 254
255 255 def flush(self, ctx):
256 256 rev = ctx.rev()
257 257 if rev in self.header:
258 258 h = self.header[rev]
259 259 if h != self.lastheader:
260 260 self.lastheader = h
261 261 self.ui.write(h)
262 262 del self.header[rev]
263 263 if rev in self.hunk:
264 264 self.ui.write(self.hunk[rev])
265 265 del self.hunk[rev]
266 266
267 267 def close(self):
268 268 if self.footer:
269 269 self.ui.write(self.footer)
270 270
271 271 def show(self, ctx, copies=None, **props):
272 272 props = pycompat.byteskwargs(props)
273 273 if self.buffered:
274 274 self.ui.pushbuffer(labeled=True)
275 275 self._show(ctx, copies, props)
276 276 self.hunk[ctx.rev()] = self.ui.popbuffer()
277 277 else:
278 278 self._show(ctx, copies, props)
279 279
280 280 def _show(self, ctx, copies, props):
281 281 '''show a single changeset or file revision'''
282 282 changenode = ctx.node()
283 283 graphwidth = props.get(b'graphwidth', 0)
284 284
285 285 if self.ui.quiet:
286 286 self.ui.write(
287 287 b"%s\n" % scmutil.formatchangeid(ctx), label=b'log.node'
288 288 )
289 289 return
290 290
291 291 columns = self._columns
292 292 self.ui.write(
293 293 columns[b'changeset'] % scmutil.formatchangeid(ctx),
294 294 label=changesetlabels(ctx),
295 295 )
296 296
297 297 # branches are shown first before any other names due to backwards
298 298 # compatibility
299 299 branch = ctx.branch()
300 300 # don't show the default branch name
301 301 if branch != b'default':
302 302 self.ui.write(columns[b'branch'] % branch, label=b'log.branch')
303 303
304 304 for nsname, ns in pycompat.iteritems(self.repo.names):
305 305 # branches has special logic already handled above, so here we just
306 306 # skip it
307 307 if nsname == b'branches':
308 308 continue
309 309 # we will use the templatename as the color name since those two
310 310 # should be the same
311 311 for name in ns.names(self.repo, changenode):
312 312 self.ui.write(ns.logfmt % name, label=b'log.%s' % ns.colorname)
313 313 if self.ui.debugflag:
314 314 self.ui.write(
315 315 columns[b'phase'] % ctx.phasestr(), label=b'log.phase'
316 316 )
317 317 for pctx in scmutil.meaningfulparents(self.repo, ctx):
318 318 label = b'log.parent changeset.%s' % pctx.phasestr()
319 319 self.ui.write(
320 320 columns[b'parent'] % scmutil.formatchangeid(pctx), label=label
321 321 )
322 322
323 323 if self.ui.debugflag:
324 324 mnode = ctx.manifestnode()
325 325 if mnode is None:
326 326 mnode = wdirid
327 327 mrev = wdirrev
328 328 else:
329 329 mrev = self.repo.manifestlog.rev(mnode)
330 330 self.ui.write(
331 331 columns[b'manifest']
332 332 % scmutil.formatrevnode(self.ui, mrev, mnode),
333 333 label=b'ui.debug log.manifest',
334 334 )
335 335 self.ui.write(columns[b'user'] % ctx.user(), label=b'log.user')
336 336 self.ui.write(
337 337 columns[b'date'] % dateutil.datestr(ctx.date()), label=b'log.date'
338 338 )
339 339
340 340 if ctx.isunstable():
341 341 instabilities = ctx.instabilities()
342 342 self.ui.write(
343 343 columns[b'instability'] % b', '.join(instabilities),
344 344 label=b'log.instability',
345 345 )
346 346
347 347 elif ctx.obsolete():
348 348 self._showobsfate(ctx)
349 349
350 350 self._exthook(ctx)
351 351
352 352 if self.ui.debugflag:
353 353 files = ctx.p1().status(ctx)
354 354 for key, value in zip(
355 355 [b'files', b'files+', b'files-'],
356 356 [files.modified, files.added, files.removed],
357 357 ):
358 358 if value:
359 359 self.ui.write(
360 360 columns[key] % b" ".join(value),
361 361 label=b'ui.debug log.files',
362 362 )
363 363 elif ctx.files() and self.ui.verbose:
364 364 self.ui.write(
365 365 columns[b'files'] % b" ".join(ctx.files()),
366 366 label=b'ui.note log.files',
367 367 )
368 368 if copies and self.ui.verbose:
369 369 copies = [b'%s (%s)' % c for c in copies]
370 370 self.ui.write(
371 371 columns[b'copies'] % b' '.join(copies),
372 372 label=b'ui.note log.copies',
373 373 )
374 374
375 375 extra = ctx.extra()
376 376 if extra and self.ui.debugflag:
377 377 for key, value in sorted(extra.items()):
378 378 self.ui.write(
379 379 columns[b'extra'] % (key, stringutil.escapestr(value)),
380 380 label=b'ui.debug log.extra',
381 381 )
382 382
383 383 description = ctx.description().strip()
384 384 if description:
385 385 if self.ui.verbose:
386 386 self.ui.write(
387 387 _(b"description:\n"), label=b'ui.note log.description'
388 388 )
389 389 self.ui.write(description, label=b'ui.note log.description')
390 390 self.ui.write(b"\n\n")
391 391 else:
392 392 self.ui.write(
393 393 columns[b'summary'] % description.splitlines()[0],
394 394 label=b'log.summary',
395 395 )
396 396 self.ui.write(b"\n")
397 397
398 398 self._showpatch(ctx, graphwidth)
399 399
400 400 def _showobsfate(self, ctx):
401 401 # TODO: do not depend on templater
402 402 tres = formatter.templateresources(self.repo.ui, self.repo)
403 403 t = formatter.maketemplater(
404 404 self.repo.ui,
405 405 b'{join(obsfate, "\n")}',
406 406 defaults=templatekw.keywords,
407 407 resources=tres,
408 408 )
409 409 obsfate = t.renderdefault({b'ctx': ctx}).splitlines()
410 410
411 411 if obsfate:
412 412 for obsfateline in obsfate:
413 413 self.ui.write(
414 414 self._columns[b'obsolete'] % obsfateline,
415 415 label=b'log.obsfate',
416 416 )
417 417
418 418 def _exthook(self, ctx):
419 419 '''empty method used by extension as a hook point
420 420 '''
421 421
422 422 def _showpatch(self, ctx, graphwidth=0):
423 423 if self._includestat:
424 424 self._differ.showdiff(
425 425 self.ui, ctx, self._diffopts, graphwidth, stat=True
426 426 )
427 427 if self._includestat and self._includediff:
428 428 self.ui.write(b"\n")
429 429 if self._includediff:
430 430 self._differ.showdiff(
431 431 self.ui, ctx, self._diffopts, graphwidth, stat=False
432 432 )
433 433 if self._includestat or self._includediff:
434 434 self.ui.write(b"\n")
435 435
436 436
437 437 class changesetformatter(changesetprinter):
438 438 """Format changeset information by generic formatter"""
439 439
440 440 def __init__(
441 441 self, ui, repo, fm, differ=None, diffopts=None, buffered=False
442 442 ):
443 443 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
444 444 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
445 445 self._fm = fm
446 446
447 447 def close(self):
448 448 self._fm.end()
449 449
450 450 def _show(self, ctx, copies, props):
451 451 '''show a single changeset or file revision'''
452 452 fm = self._fm
453 453 fm.startitem()
454 454 fm.context(ctx=ctx)
455 455 fm.data(rev=scmutil.intrev(ctx), node=fm.hexfunc(scmutil.binnode(ctx)))
456 456
457 457 datahint = fm.datahint()
458 458 if self.ui.quiet and not datahint:
459 459 return
460 460
461 461 fm.data(
462 462 branch=ctx.branch(),
463 463 phase=ctx.phasestr(),
464 464 user=ctx.user(),
465 465 date=fm.formatdate(ctx.date()),
466 466 desc=ctx.description(),
467 467 bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'),
468 468 tags=fm.formatlist(ctx.tags(), name=b'tag'),
469 469 parents=fm.formatlist(
470 470 [fm.hexfunc(c.node()) for c in ctx.parents()], name=b'node'
471 471 ),
472 472 )
473 473
474 474 if self.ui.debugflag or b'manifest' in datahint:
475 475 fm.data(manifest=fm.hexfunc(ctx.manifestnode() or wdirid))
476 476 if self.ui.debugflag or b'extra' in datahint:
477 477 fm.data(extra=fm.formatdict(ctx.extra()))
478 478
479 479 if (
480 480 self.ui.debugflag
481 481 or b'modified' in datahint
482 482 or b'added' in datahint
483 483 or b'removed' in datahint
484 484 ):
485 485 files = ctx.p1().status(ctx)
486 486 fm.data(
487 487 modified=fm.formatlist(files.modified, name=b'file'),
488 488 added=fm.formatlist(files.added, name=b'file'),
489 489 removed=fm.formatlist(files.removed, name=b'file'),
490 490 )
491 491
492 492 verbose = not self.ui.debugflag and self.ui.verbose
493 493 if verbose or b'files' in datahint:
494 494 fm.data(files=fm.formatlist(ctx.files(), name=b'file'))
495 495 if verbose and copies or b'copies' in datahint:
496 496 fm.data(
497 497 copies=fm.formatdict(copies or {}, key=b'name', value=b'source')
498 498 )
499 499
500 500 if self._includestat or b'diffstat' in datahint:
501 501 self.ui.pushbuffer()
502 502 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
503 503 fm.data(diffstat=self.ui.popbuffer())
504 504 if self._includediff or b'diff' in datahint:
505 505 self.ui.pushbuffer()
506 506 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
507 507 fm.data(diff=self.ui.popbuffer())
508 508
509 509
510 510 class changesettemplater(changesetprinter):
511 511 '''format changeset information.
512 512
513 513 Note: there are a variety of convenience functions to build a
514 514 changesettemplater for common cases. See functions such as:
515 515 maketemplater, changesetdisplayer, buildcommittemplate, or other
516 516 functions that use changesest_templater.
517 517 '''
518 518
519 519 # Arguments before "buffered" used to be positional. Consider not
520 520 # adding/removing arguments before "buffered" to not break callers.
521 521 def __init__(
522 522 self, ui, repo, tmplspec, differ=None, diffopts=None, buffered=False
523 523 ):
524 524 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
525 525 # tres is shared with _graphnodeformatter()
526 526 self._tresources = tres = formatter.templateresources(ui, repo)
527 527 self.t = formatter.loadtemplater(
528 528 ui,
529 529 tmplspec,
530 530 defaults=templatekw.keywords,
531 531 resources=tres,
532 532 cache=templatekw.defaulttempl,
533 533 )
534 534 self._counter = itertools.count()
535 535
536 536 self._tref = tmplspec.ref
537 537 self._parts = {
538 538 b'header': b'',
539 539 b'footer': b'',
540 540 tmplspec.ref: tmplspec.ref,
541 541 b'docheader': b'',
542 542 b'docfooter': b'',
543 543 b'separator': b'',
544 544 }
545 545 if tmplspec.mapfile:
546 546 # find correct templates for current mode, for backward
547 547 # compatibility with 'log -v/-q/--debug' using a mapfile
548 548 tmplmodes = [
549 549 (True, b''),
550 550 (self.ui.verbose, b'_verbose'),
551 551 (self.ui.quiet, b'_quiet'),
552 552 (self.ui.debugflag, b'_debug'),
553 553 ]
554 554 for mode, postfix in tmplmodes:
555 555 for t in self._parts:
556 556 cur = t + postfix
557 557 if mode and cur in self.t:
558 558 self._parts[t] = cur
559 559 else:
560 560 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
561 561 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
562 562 self._parts.update(m)
563 563
564 564 if self._parts[b'docheader']:
565 565 self.ui.write(self.t.render(self._parts[b'docheader'], {}))
566 566
567 567 def close(self):
568 568 if self._parts[b'docfooter']:
569 569 if not self.footer:
570 570 self.footer = b""
571 571 self.footer += self.t.render(self._parts[b'docfooter'], {})
572 572 return super(changesettemplater, self).close()
573 573
574 574 def _show(self, ctx, copies, props):
575 575 '''show a single changeset or file revision'''
576 576 props = props.copy()
577 577 props[b'ctx'] = ctx
578 578 props[b'index'] = index = next(self._counter)
579 579 props[b'revcache'] = {b'copies': copies}
580 580 graphwidth = props.get(b'graphwidth', 0)
581 581
582 582 # write separator, which wouldn't work well with the header part below
583 583 # since there's inherently a conflict between header (across items) and
584 584 # separator (per item)
585 585 if self._parts[b'separator'] and index > 0:
586 586 self.ui.write(self.t.render(self._parts[b'separator'], {}))
587 587
588 588 # write header
589 589 if self._parts[b'header']:
590 590 h = self.t.render(self._parts[b'header'], props)
591 591 if self.buffered:
592 592 self.header[ctx.rev()] = h
593 593 else:
594 594 if self.lastheader != h:
595 595 self.lastheader = h
596 596 self.ui.write(h)
597 597
598 598 # write changeset metadata, then patch if requested
599 599 key = self._parts[self._tref]
600 600 self.ui.write(self.t.render(key, props))
601 601 self._exthook(ctx)
602 602 self._showpatch(ctx, graphwidth)
603 603
604 604 if self._parts[b'footer']:
605 605 if not self.footer:
606 606 self.footer = self.t.render(self._parts[b'footer'], props)
607 607
608 608
609 609 def templatespec(tmpl, mapfile):
610 610 if pycompat.ispy3:
611 611 assert not isinstance(tmpl, str), b'tmpl must not be a str'
612 612 if mapfile:
613 613 return formatter.templatespec(b'changeset', tmpl, mapfile)
614 614 else:
615 615 return formatter.templatespec(b'', tmpl, None)
616 616
617 617
618 618 def _lookuptemplate(ui, tmpl, style):
619 619 """Find the template matching the given template spec or style
620 620
621 621 See formatter.lookuptemplate() for details.
622 622 """
623 623
624 624 # ui settings
625 625 if not tmpl and not style: # template are stronger than style
626 626 tmpl = ui.config(b'ui', b'logtemplate')
627 627 if tmpl:
628 628 return templatespec(templater.unquotestring(tmpl), None)
629 629 else:
630 630 style = util.expandpath(ui.config(b'ui', b'style'))
631 631
632 632 if not tmpl and style:
633 633 mapfile = style
634 634 if not os.path.split(mapfile)[0]:
635 635 mapname = templater.templatepath(
636 636 b'map-cmdline.' + mapfile
637 637 ) or templater.templatepath(mapfile)
638 638 if mapname:
639 639 mapfile = mapname
640 640 return templatespec(None, mapfile)
641 641
642 642 return formatter.lookuptemplate(ui, b'changeset', tmpl)
643 643
644 644
645 645 def maketemplater(ui, repo, tmpl, buffered=False):
646 646 """Create a changesettemplater from a literal template 'tmpl'
647 647 byte-string."""
648 648 spec = templatespec(tmpl, None)
649 649 return changesettemplater(ui, repo, spec, buffered=buffered)
650 650
651 651
652 652 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
653 653 """show one changeset using template or regular display.
654 654
655 655 Display format will be the first non-empty hit of:
656 656 1. option 'template'
657 657 2. option 'style'
658 658 3. [ui] setting 'logtemplate'
659 659 4. [ui] setting 'style'
660 660 If all of these values are either the unset or the empty string,
661 661 regular display via changesetprinter() is done.
662 662 """
663 663 postargs = (differ, opts, buffered)
664 664 spec = _lookuptemplate(ui, opts.get(b'template'), opts.get(b'style'))
665 665
666 666 # machine-readable formats have slightly different keyword set than
667 667 # plain templates, which are handled by changesetformatter.
668 668 # note that {b'pickle', b'debug'} can also be added to the list if needed.
669 669 if spec.ref in {b'cbor', b'json'}:
670 670 fm = ui.formatter(b'log', opts)
671 671 return changesetformatter(ui, repo, fm, *postargs)
672 672
673 673 if not spec.ref and not spec.tmpl and not spec.mapfile:
674 674 return changesetprinter(ui, repo, *postargs)
675 675
676 676 return changesettemplater(ui, repo, spec, *postargs)
677 677
678 678
679 679 def _makematcher(repo, revs, pats, opts):
680 680 """Build matcher and expanded patterns from log options
681 681
682 682 If --follow, revs are the revisions to follow from.
683 683
684 684 Returns (match, pats, slowpath) where
685 685 - match: a matcher built from the given pats and -I/-X opts
686 686 - pats: patterns used (globs are expanded on Windows)
687 687 - slowpath: True if patterns aren't as simple as scanning filelogs
688 688 """
689 689 # pats/include/exclude are passed to match.match() directly in
690 690 # _matchfiles() revset but walkchangerevs() builds its matcher with
691 691 # scmutil.match(). The difference is input pats are globbed on
692 692 # platforms without shell expansion (windows).
693 693 wctx = repo[None]
694 694 match, pats = scmutil.matchandpats(wctx, pats, opts)
695 695 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
696 696 if not slowpath:
697 697 follow = opts.get(b'follow') or opts.get(b'follow_first')
698 698 startctxs = []
699 699 if follow and opts.get(b'rev'):
700 700 startctxs = [repo[r] for r in revs]
701 701 for f in match.files():
702 702 if follow and startctxs:
703 703 # No idea if the path was a directory at that revision, so
704 704 # take the slow path.
705 705 if any(f not in c for c in startctxs):
706 706 slowpath = True
707 707 continue
708 708 elif follow and f not in wctx:
709 709 # If the file exists, it may be a directory, so let it
710 710 # take the slow path.
711 711 if os.path.exists(repo.wjoin(f)):
712 712 slowpath = True
713 713 continue
714 714 else:
715 715 raise error.Abort(
716 716 _(
717 717 b'cannot follow file not in parent '
718 718 b'revision: "%s"'
719 719 )
720 720 % f
721 721 )
722 722 filelog = repo.file(f)
723 723 if not filelog:
724 724 # A zero count may be a directory or deleted file, so
725 725 # try to find matching entries on the slow path.
726 726 if follow:
727 727 raise error.Abort(
728 728 _(b'cannot follow nonexistent file: "%s"') % f
729 729 )
730 730 slowpath = True
731 731
732 732 # We decided to fall back to the slowpath because at least one
733 733 # of the paths was not a file. Check to see if at least one of them
734 734 # existed in history - in that case, we'll continue down the
735 735 # slowpath; otherwise, we can turn off the slowpath
736 736 if slowpath:
737 737 for path in match.files():
738 738 if path == b'.' or path in repo.store:
739 739 break
740 740 else:
741 741 slowpath = False
742 742
743 743 return match, pats, slowpath
744 744
745 745
746 746 def _fileancestors(repo, revs, match, followfirst):
747 747 fctxs = []
748 748 for r in revs:
749 749 ctx = repo[r]
750 750 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
751 751
752 752 # When displaying a revision with --patch --follow FILE, we have
753 753 # to know which file of the revision must be diffed. With
754 754 # --follow, we want the names of the ancestors of FILE in the
755 755 # revision, stored in "fcache". "fcache" is populated as a side effect
756 756 # of the graph traversal.
757 757 fcache = {}
758 758
759 759 def filematcher(ctx):
760 760 return scmutil.matchfiles(repo, fcache.get(ctx.rev(), []))
761 761
762 762 def revgen():
763 763 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
764 764 fcache[rev] = [c.path() for c in cs]
765 765 yield rev
766 766
767 767 return smartset.generatorset(revgen(), iterasc=False), filematcher
768 768
769 769
770 770 def _makenofollowfilematcher(repo, pats, opts):
771 771 '''hook for extensions to override the filematcher for non-follow cases'''
772 772 return None
773 773
774 774
775 775 _opt2logrevset = {
776 776 b'no_merges': (b'not merge()', None),
777 777 b'only_merges': (b'merge()', None),
778 778 b'_matchfiles': (None, b'_matchfiles(%ps)'),
779 779 b'date': (b'date(%s)', None),
780 780 b'branch': (b'branch(%s)', b'%lr'),
781 781 b'_patslog': (b'filelog(%s)', b'%lr'),
782 782 b'keyword': (b'keyword(%s)', b'%lr'),
783 783 b'prune': (b'ancestors(%s)', b'not %lr'),
784 784 b'user': (b'user(%s)', b'%lr'),
785 785 }
786 786
787 787
788 788 def _makerevset(repo, match, pats, slowpath, opts):
789 789 """Return a revset string built from log options and file patterns"""
790 790 opts = dict(opts)
791 791 # follow or not follow?
792 792 follow = opts.get(b'follow') or opts.get(b'follow_first')
793 793
794 794 # branch and only_branch are really aliases and must be handled at
795 795 # the same time
796 796 opts[b'branch'] = opts.get(b'branch', []) + opts.get(b'only_branch', [])
797 797 opts[b'branch'] = [repo.lookupbranch(b) for b in opts[b'branch']]
798 798
799 799 if slowpath:
800 800 # See walkchangerevs() slow path.
801 801 #
802 802 # pats/include/exclude cannot be represented as separate
803 803 # revset expressions as their filtering logic applies at file
804 804 # level. For instance "-I a -X b" matches a revision touching
805 805 # "a" and "b" while "file(a) and not file(b)" does
806 806 # not. Besides, filesets are evaluated against the working
807 807 # directory.
808 808 matchargs = [b'r:', b'd:relpath']
809 809 for p in pats:
810 810 matchargs.append(b'p:' + p)
811 811 for p in opts.get(b'include', []):
812 812 matchargs.append(b'i:' + p)
813 813 for p in opts.get(b'exclude', []):
814 814 matchargs.append(b'x:' + p)
815 815 opts[b'_matchfiles'] = matchargs
816 816 elif not follow:
817 817 opts[b'_patslog'] = list(pats)
818 818
819 819 expr = []
820 820 for op, val in sorted(pycompat.iteritems(opts)):
821 821 if not val:
822 822 continue
823 823 if op not in _opt2logrevset:
824 824 continue
825 825 revop, listop = _opt2logrevset[op]
826 826 if revop and b'%' not in revop:
827 827 expr.append(revop)
828 828 elif not listop:
829 829 expr.append(revsetlang.formatspec(revop, val))
830 830 else:
831 831 if revop:
832 832 val = [revsetlang.formatspec(revop, v) for v in val]
833 833 expr.append(revsetlang.formatspec(listop, val))
834 834
835 835 if expr:
836 836 expr = b'(' + b' and '.join(expr) + b')'
837 837 else:
838 838 expr = None
839 839 return expr
840 840
841 841
842 842 def _initialrevs(repo, opts):
843 843 """Return the initial set of revisions to be filtered or followed"""
844 844 follow = opts.get(b'follow') or opts.get(b'follow_first')
845 845 if opts.get(b'rev'):
846 846 revs = scmutil.revrange(repo, opts[b'rev'])
847 847 elif follow and repo.dirstate.p1() == nullid:
848 848 revs = smartset.baseset()
849 849 elif follow:
850 850 revs = repo.revs(b'.')
851 851 else:
852 852 revs = smartset.spanset(repo)
853 853 revs.reverse()
854 854 return revs
855 855
856 856
857 857 def getrevs(repo, pats, opts):
858 858 # type: (Any, Any, Any) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]]
859 859 """Return (revs, differ) where revs is a smartset
860 860
861 861 differ is a changesetdiffer with pre-configured file matcher.
862 862 """
863 863 follow = opts.get(b'follow') or opts.get(b'follow_first')
864 864 followfirst = opts.get(b'follow_first')
865 865 limit = getlimit(opts)
866 866 revs = _initialrevs(repo, opts)
867 867 if not revs:
868 868 return smartset.baseset(), None
869 869 match, pats, slowpath = _makematcher(repo, revs, pats, opts)
870 870 filematcher = None
871 871 if follow:
872 872 if slowpath or match.always():
873 873 revs = dagop.revancestors(repo, revs, followfirst=followfirst)
874 874 else:
875 875 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
876 876 revs.reverse()
877 877 if filematcher is None:
878 878 filematcher = _makenofollowfilematcher(repo, pats, opts)
879 879 if filematcher is None:
880 880
881 881 def filematcher(ctx):
882 882 return match
883 883
884 884 expr = _makerevset(repo, match, pats, slowpath, opts)
885 885 if opts.get(b'graph'):
886 886 # User-specified revs might be unsorted, but don't sort before
887 887 # _makerevset because it might depend on the order of revs
888 888 if repo.ui.configbool(b'experimental', b'log.topo'):
889 889 if not revs.istopo():
890 890 revs = dagop.toposort(revs, repo.changelog.parentrevs)
891 891 # TODO: try to iterate the set lazily
892 892 revs = revset.baseset(list(revs), istopo=True)
893 893 elif not (revs.isdescending() or revs.istopo()):
894 894 revs.sort(reverse=True)
895 895 if expr:
896 896 matcher = revset.match(None, expr)
897 897 revs = matcher(repo, revs)
898 898 if limit is not None:
899 899 revs = revs.slice(0, limit)
900 900
901 901 differ = changesetdiffer()
902 902 differ._makefilematcher = filematcher
903 903 return revs, differ
904 904
905 905
906 906 def _parselinerangeopt(repo, opts):
907 907 """Parse --line-range log option and return a list of tuples (filename,
908 908 (fromline, toline)).
909 909 """
910 910 linerangebyfname = []
911 911 for pat in opts.get(b'line_range', []):
912 912 try:
913 913 pat, linerange = pat.rsplit(b',', 1)
914 914 except ValueError:
915 915 raise error.Abort(_(b'malformatted line-range pattern %s') % pat)
916 916 try:
917 917 fromline, toline = map(int, linerange.split(b':'))
918 918 except ValueError:
919 919 raise error.Abort(_(b"invalid line range for %s") % pat)
920 920 msg = _(b"line range pattern '%s' must match exactly one file") % pat
921 921 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
922 922 linerangebyfname.append(
923 923 (fname, util.processlinerange(fromline, toline))
924 924 )
925 925 return linerangebyfname
926 926
927 927
928 928 def getlinerangerevs(repo, userrevs, opts):
929 929 """Return (revs, differ).
930 930
931 931 "revs" are revisions obtained by processing "line-range" log options and
932 932 walking block ancestors of each specified file/line-range.
933 933
934 934 "differ" is a changesetdiffer with pre-configured file matcher and hunks
935 935 filter.
936 936 """
937 937 wctx = repo[None]
938 938
939 939 # Two-levels map of "rev -> file ctx -> [line range]".
940 940 linerangesbyrev = {}
941 941 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
942 942 if fname not in wctx:
943 943 raise error.Abort(
944 944 _(b'cannot follow file not in parent revision: "%s"') % fname
945 945 )
946 946 fctx = wctx.filectx(fname)
947 947 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
948 948 rev = fctx.introrev()
949 949 if rev is None:
950 950 rev = wdirrev
951 951 if rev not in userrevs:
952 952 continue
953 953 linerangesbyrev.setdefault(rev, {}).setdefault(
954 954 fctx.path(), []
955 955 ).append(linerange)
956 956
957 957 def nofilterhunksfn(fctx, hunks):
958 958 return hunks
959 959
960 960 def hunksfilter(ctx):
961 961 fctxlineranges = linerangesbyrev.get(scmutil.intrev(ctx))
962 962 if fctxlineranges is None:
963 963 return nofilterhunksfn
964 964
965 965 def filterfn(fctx, hunks):
966 966 lineranges = fctxlineranges.get(fctx.path())
967 967 if lineranges is not None:
968 968 for hr, lines in hunks:
969 969 if hr is None: # binary
970 970 yield hr, lines
971 971 continue
972 972 if any(mdiff.hunkinrange(hr[2:], lr) for lr in lineranges):
973 973 yield hr, lines
974 974 else:
975 975 for hunk in hunks:
976 976 yield hunk
977 977
978 978 return filterfn
979 979
980 980 def filematcher(ctx):
981 981 files = list(linerangesbyrev.get(scmutil.intrev(ctx), []))
982 982 return scmutil.matchfiles(repo, files)
983 983
984 984 revs = sorted(linerangesbyrev, reverse=True)
985 985
986 986 differ = changesetdiffer()
987 987 differ._makefilematcher = filematcher
988 988 differ._makehunksfilter = hunksfilter
989 989 return smartset.baseset(revs), differ
990 990
991 991
992 992 def _graphnodeformatter(ui, displayer):
993 993 spec = ui.config(b'ui', b'graphnodetemplate')
994 994 if not spec:
995 995 return templatekw.getgraphnode # fast path for "{graphnode}"
996 996
997 997 spec = templater.unquotestring(spec)
998 998 if isinstance(displayer, changesettemplater):
999 999 # reuse cache of slow templates
1000 1000 tres = displayer._tresources
1001 1001 else:
1002 1002 tres = formatter.templateresources(ui)
1003 1003 templ = formatter.maketemplater(
1004 1004 ui, spec, defaults=templatekw.keywords, resources=tres
1005 1005 )
1006 1006
1007 def formatnode(repo, ctx):
1007 def formatnode(repo, ctx, cache):
1008 1008 props = {b'ctx': ctx, b'repo': repo}
1009 1009 return templ.renderdefault(props)
1010 1010
1011 1011 return formatnode
1012 1012
1013 1013
1014 1014 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None):
1015 1015 props = props or {}
1016 1016 formatnode = _graphnodeformatter(ui, displayer)
1017 1017 state = graphmod.asciistate()
1018 1018 styles = state.styles
1019 1019
1020 1020 # only set graph styling if HGPLAIN is not set.
1021 1021 if ui.plain(b'graph'):
1022 1022 # set all edge styles to |, the default pre-3.8 behaviour
1023 1023 styles.update(dict.fromkeys(styles, b'|'))
1024 1024 else:
1025 1025 edgetypes = {
1026 1026 b'parent': graphmod.PARENT,
1027 1027 b'grandparent': graphmod.GRANDPARENT,
1028 1028 b'missing': graphmod.MISSINGPARENT,
1029 1029 }
1030 1030 for name, key in edgetypes.items():
1031 1031 # experimental config: experimental.graphstyle.*
1032 1032 styles[key] = ui.config(
1033 1033 b'experimental', b'graphstyle.%s' % name, styles[key]
1034 1034 )
1035 1035 if not styles[key]:
1036 1036 styles[key] = None
1037 1037
1038 1038 # experimental config: experimental.graphshorten
1039 1039 state.graphshorten = ui.configbool(b'experimental', b'graphshorten')
1040 1040
1041 formatnode_cache = {}
1041 1042 for rev, type, ctx, parents in dag:
1042 char = formatnode(repo, ctx)
1043 char = formatnode(repo, ctx, formatnode_cache)
1043 1044 copies = getcopies(ctx) if getcopies else None
1044 1045 edges = edgefn(type, char, state, rev, parents)
1045 1046 firstedge = next(edges)
1046 1047 width = firstedge[2]
1047 1048 displayer.show(
1048 1049 ctx, copies=copies, graphwidth=width, **pycompat.strkwargs(props)
1049 1050 )
1050 1051 lines = displayer.hunk.pop(rev).split(b'\n')
1051 1052 if not lines[-1]:
1052 1053 del lines[-1]
1053 1054 displayer.flush(ctx)
1054 1055 for type, char, width, coldata in itertools.chain([firstedge], edges):
1055 1056 graphmod.ascii(ui, state, type, char, lines, coldata)
1056 1057 lines = []
1057 1058 displayer.close()
1058 1059
1059 1060
1060 1061 def displaygraphrevs(ui, repo, revs, displayer, getrenamed):
1061 1062 revdag = graphmod.dagwalker(repo, revs)
1062 1063 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
1063 1064
1064 1065
1065 1066 def displayrevs(ui, repo, revs, displayer, getcopies):
1066 1067 for rev in revs:
1067 1068 ctx = repo[rev]
1068 1069 copies = getcopies(ctx) if getcopies else None
1069 1070 displayer.show(ctx, copies=copies)
1070 1071 displayer.flush(ctx)
1071 1072 displayer.close()
1072 1073
1073 1074
1074 1075 def checkunsupportedgraphflags(pats, opts):
1075 1076 for op in [b"newest_first"]:
1076 1077 if op in opts and opts[op]:
1077 1078 raise error.Abort(
1078 1079 _(b"-G/--graph option is incompatible with --%s")
1079 1080 % op.replace(b"_", b"-")
1080 1081 )
1081 1082
1082 1083
1083 1084 def graphrevs(repo, nodes, opts):
1084 1085 limit = getlimit(opts)
1085 1086 nodes.reverse()
1086 1087 if limit is not None:
1087 1088 nodes = nodes[:limit]
1088 1089 return graphmod.nodes(repo, nodes)
@@ -1,992 +1,1004 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 stringutil
33 33
34 34 _hybrid = templateutil.hybrid
35 35 hybriddict = templateutil.hybriddict
36 36 hybridlist = templateutil.hybridlist
37 37 compatdict = templateutil.compatdict
38 38 compatlist = templateutil.compatlist
39 39 _showcompatlist = templateutil._showcompatlist
40 40
41 41
42 42 def getlatesttags(context, mapping, pattern=None):
43 43 '''return date, distance and name for the latest tag of rev'''
44 44 repo = context.resource(mapping, b'repo')
45 45 ctx = context.resource(mapping, b'ctx')
46 46 cache = context.resource(mapping, b'cache')
47 47
48 48 cachename = b'latesttags'
49 49 if pattern is not None:
50 50 cachename += b'-' + pattern
51 51 match = stringutil.stringmatcher(pattern)[2]
52 52 else:
53 53 match = util.always
54 54
55 55 if cachename not in cache:
56 56 # Cache mapping from rev to a tuple with tag date, tag
57 57 # distance and tag name
58 58 cache[cachename] = {-1: (0, 0, [b'null'])}
59 59 latesttags = cache[cachename]
60 60
61 61 rev = ctx.rev()
62 62 todo = [rev]
63 63 while todo:
64 64 rev = todo.pop()
65 65 if rev in latesttags:
66 66 continue
67 67 ctx = repo[rev]
68 68 tags = [
69 69 t
70 70 for t in ctx.tags()
71 71 if (repo.tagtype(t) and repo.tagtype(t) != b'local' and match(t))
72 72 ]
73 73 if tags:
74 74 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
75 75 continue
76 76 try:
77 77 ptags = [latesttags[p.rev()] for p in ctx.parents()]
78 78 if len(ptags) > 1:
79 79 if ptags[0][2] == ptags[1][2]:
80 80 # The tuples are laid out so the right one can be found by
81 81 # comparison in this case.
82 82 pdate, pdist, ptag = max(ptags)
83 83 else:
84 84
85 85 def key(x):
86 86 tag = x[2][0]
87 87 if ctx.rev() is None:
88 88 # only() doesn't support wdir
89 89 prevs = [c.rev() for c in ctx.parents()]
90 90 changes = repo.revs(b'only(%ld, %s)', prevs, tag)
91 91 changessincetag = len(changes) + 1
92 92 else:
93 93 changes = repo.revs(b'only(%d, %s)', ctx.rev(), tag)
94 94 changessincetag = len(changes)
95 95 # Smallest number of changes since tag wins. Date is
96 96 # used as tiebreaker.
97 97 return [-changessincetag, x[0]]
98 98
99 99 pdate, pdist, ptag = max(ptags, key=key)
100 100 else:
101 101 pdate, pdist, ptag = ptags[0]
102 102 except KeyError:
103 103 # Cache miss - recurse
104 104 todo.append(rev)
105 105 todo.extend(p.rev() for p in ctx.parents())
106 106 continue
107 107 latesttags[rev] = pdate, pdist + 1, ptag
108 108 return latesttags[rev]
109 109
110 110
111 111 def getlogcolumns():
112 112 """Return a dict of log column labels"""
113 113 _ = pycompat.identity # temporarily disable gettext
114 114 # i18n: column positioning for "hg log"
115 115 columns = _(
116 116 b'bookmark: %s\n'
117 117 b'branch: %s\n'
118 118 b'changeset: %s\n'
119 119 b'copies: %s\n'
120 120 b'date: %s\n'
121 121 b'extra: %s=%s\n'
122 122 b'files+: %s\n'
123 123 b'files-: %s\n'
124 124 b'files: %s\n'
125 125 b'instability: %s\n'
126 126 b'manifest: %s\n'
127 127 b'obsolete: %s\n'
128 128 b'parent: %s\n'
129 129 b'phase: %s\n'
130 130 b'summary: %s\n'
131 131 b'tag: %s\n'
132 132 b'user: %s\n'
133 133 )
134 134 return dict(
135 135 zip(
136 136 [s.split(b':', 1)[0] for s in columns.splitlines()],
137 137 i18n._(columns).splitlines(True),
138 138 )
139 139 )
140 140
141 141
142 142 # basic internal templates
143 143 _changeidtmpl = b'{rev}:{node|formatnode}'
144 144
145 145 # default templates internally used for rendering of lists
146 146 defaulttempl = {
147 147 b'parent': _changeidtmpl + b' ',
148 148 b'manifest': _changeidtmpl,
149 149 b'file_copy': b'{name} ({source})',
150 150 b'envvar': b'{key}={value}',
151 151 b'extra': b'{key}={value|stringescape}',
152 152 }
153 153 # filecopy is preserved for compatibility reasons
154 154 defaulttempl[b'filecopy'] = defaulttempl[b'file_copy']
155 155
156 156 # keywords are callables (see registrar.templatekeyword for details)
157 157 keywords = {}
158 158 templatekeyword = registrar.templatekeyword(keywords)
159 159
160 160
161 161 @templatekeyword(b'author', requires={b'ctx'})
162 162 def showauthor(context, mapping):
163 163 """Alias for ``{user}``"""
164 164 return showuser(context, mapping)
165 165
166 166
167 167 @templatekeyword(b'bisect', requires={b'repo', b'ctx'})
168 168 def showbisect(context, mapping):
169 169 """String. The changeset bisection status."""
170 170 repo = context.resource(mapping, b'repo')
171 171 ctx = context.resource(mapping, b'ctx')
172 172 return hbisect.label(repo, ctx.node())
173 173
174 174
175 175 @templatekeyword(b'branch', requires={b'ctx'})
176 176 def showbranch(context, mapping):
177 177 """String. The name of the branch on which the changeset was
178 178 committed.
179 179 """
180 180 ctx = context.resource(mapping, b'ctx')
181 181 return ctx.branch()
182 182
183 183
184 184 @templatekeyword(b'branches', requires={b'ctx'})
185 185 def showbranches(context, mapping):
186 186 """List of strings. The name of the branch on which the
187 187 changeset was committed. Will be empty if the branch name was
188 188 default. (DEPRECATED)
189 189 """
190 190 ctx = context.resource(mapping, b'ctx')
191 191 branch = ctx.branch()
192 192 if branch != b'default':
193 193 return compatlist(
194 194 context, mapping, b'branch', [branch], plural=b'branches'
195 195 )
196 196 return compatlist(context, mapping, b'branch', [], plural=b'branches')
197 197
198 198
199 199 @templatekeyword(b'bookmarks', requires={b'repo', b'ctx'})
200 200 def showbookmarks(context, mapping):
201 201 """List of strings. Any bookmarks associated with the
202 202 changeset. Also sets 'active', the name of the active bookmark.
203 203 """
204 204 repo = context.resource(mapping, b'repo')
205 205 ctx = context.resource(mapping, b'ctx')
206 206 bookmarks = ctx.bookmarks()
207 207 active = repo._activebookmark
208 208 makemap = lambda v: {b'bookmark': v, b'active': active, b'current': active}
209 209 f = _showcompatlist(context, mapping, b'bookmark', bookmarks)
210 210 return _hybrid(f, bookmarks, makemap, pycompat.identity)
211 211
212 212
213 213 @templatekeyword(b'children', requires={b'ctx'})
214 214 def showchildren(context, mapping):
215 215 """List of strings. The children of the changeset."""
216 216 ctx = context.resource(mapping, b'ctx')
217 217 childrevs = [b'%d:%s' % (cctx.rev(), cctx) for cctx in ctx.children()]
218 218 return compatlist(
219 219 context, mapping, b'children', childrevs, element=b'child'
220 220 )
221 221
222 222
223 223 # Deprecated, but kept alive for help generation a purpose.
224 224 @templatekeyword(b'currentbookmark', requires={b'repo', b'ctx'})
225 225 def showcurrentbookmark(context, mapping):
226 226 """String. The active bookmark, if it is associated with the changeset.
227 227 (DEPRECATED)"""
228 228 return showactivebookmark(context, mapping)
229 229
230 230
231 231 @templatekeyword(b'activebookmark', requires={b'repo', b'ctx'})
232 232 def showactivebookmark(context, mapping):
233 233 """String. The active bookmark, if it is associated with the changeset."""
234 234 repo = context.resource(mapping, b'repo')
235 235 ctx = context.resource(mapping, b'ctx')
236 236 active = repo._activebookmark
237 237 if active and active in ctx.bookmarks():
238 238 return active
239 239 return b''
240 240
241 241
242 242 @templatekeyword(b'date', requires={b'ctx'})
243 243 def showdate(context, mapping):
244 244 """Date information. The date when the changeset was committed."""
245 245 ctx = context.resource(mapping, b'ctx')
246 246 # the default string format is '<float(unixtime)><tzoffset>' because
247 247 # python-hglib splits date at decimal separator.
248 248 return templateutil.date(ctx.date(), showfmt=b'%d.0%d')
249 249
250 250
251 251 @templatekeyword(b'desc', requires={b'ctx'})
252 252 def showdescription(context, mapping):
253 253 """String. The text of the changeset description."""
254 254 ctx = context.resource(mapping, b'ctx')
255 255 s = ctx.description()
256 256 if isinstance(s, encoding.localstr):
257 257 # try hard to preserve utf-8 bytes
258 258 return encoding.tolocal(encoding.fromlocal(s).strip())
259 259 elif isinstance(s, encoding.safelocalstr):
260 260 return encoding.safelocalstr(s.strip())
261 261 else:
262 262 return s.strip()
263 263
264 264
265 265 @templatekeyword(b'diffstat', requires={b'ui', b'ctx'})
266 266 def showdiffstat(context, mapping):
267 267 """String. Statistics of changes with the following format:
268 268 "modified files: +added/-removed lines"
269 269 """
270 270 ui = context.resource(mapping, b'ui')
271 271 ctx = context.resource(mapping, b'ctx')
272 272 diffopts = diffutil.diffallopts(ui, {b'noprefix': False})
273 273 diff = ctx.diff(opts=diffopts)
274 274 stats = patch.diffstatdata(util.iterlines(diff))
275 275 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
276 276 return b'%d: +%d/-%d' % (len(stats), adds, removes)
277 277
278 278
279 279 @templatekeyword(b'envvars', requires={b'ui'})
280 280 def showenvvars(context, mapping):
281 281 """A dictionary of environment variables. (EXPERIMENTAL)"""
282 282 ui = context.resource(mapping, b'ui')
283 283 env = ui.exportableenviron()
284 284 env = util.sortdict((k, env[k]) for k in sorted(env))
285 285 return compatdict(context, mapping, b'envvar', env, plural=b'envvars')
286 286
287 287
288 288 @templatekeyword(b'extras', requires={b'ctx'})
289 289 def showextras(context, mapping):
290 290 """List of dicts with key, value entries of the 'extras'
291 291 field of this changeset."""
292 292 ctx = context.resource(mapping, b'ctx')
293 293 extras = ctx.extra()
294 294 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
295 295 makemap = lambda k: {b'key': k, b'value': extras[k]}
296 296 c = [makemap(k) for k in extras]
297 297 f = _showcompatlist(context, mapping, b'extra', c, plural=b'extras')
298 298 return _hybrid(
299 299 f,
300 300 extras,
301 301 makemap,
302 302 lambda k: b'%s=%s' % (k, stringutil.escapestr(extras[k])),
303 303 )
304 304
305 305
306 306 def _getfilestatus(context, mapping, listall=False):
307 307 ctx = context.resource(mapping, b'ctx')
308 308 revcache = context.resource(mapping, b'revcache')
309 309 if b'filestatus' not in revcache or revcache[b'filestatusall'] < listall:
310 310 stat = ctx.p1().status(
311 311 ctx, listignored=listall, listclean=listall, listunknown=listall
312 312 )
313 313 revcache[b'filestatus'] = stat
314 314 revcache[b'filestatusall'] = listall
315 315 return revcache[b'filestatus']
316 316
317 317
318 318 def _getfilestatusmap(context, mapping, listall=False):
319 319 revcache = context.resource(mapping, b'revcache')
320 320 if b'filestatusmap' not in revcache or revcache[b'filestatusall'] < listall:
321 321 stat = _getfilestatus(context, mapping, listall=listall)
322 322 revcache[b'filestatusmap'] = statmap = {}
323 323 for char, files in zip(pycompat.iterbytestr(b'MAR!?IC'), stat):
324 324 statmap.update((f, char) for f in files)
325 325 return revcache[b'filestatusmap'] # {path: statchar}
326 326
327 327
328 328 @templatekeyword(
329 329 b'file_copies', requires={b'repo', b'ctx', b'cache', b'revcache'}
330 330 )
331 331 def showfilecopies(context, mapping):
332 332 """List of strings. Files copied in this changeset with
333 333 their sources.
334 334 """
335 335 repo = context.resource(mapping, b'repo')
336 336 ctx = context.resource(mapping, b'ctx')
337 337 cache = context.resource(mapping, b'cache')
338 338 copies = context.resource(mapping, b'revcache').get(b'copies')
339 339 if copies is None:
340 340 if b'getcopies' not in cache:
341 341 cache[b'getcopies'] = scmutil.getcopiesfn(repo)
342 342 getcopies = cache[b'getcopies']
343 343 copies = getcopies(ctx)
344 344 return templateutil.compatfilecopiesdict(
345 345 context, mapping, b'file_copy', copies
346 346 )
347 347
348 348
349 349 # showfilecopiesswitch() displays file copies only if copy records are
350 350 # provided before calling the templater, usually with a --copies
351 351 # command line switch.
352 352 @templatekeyword(b'file_copies_switch', requires={b'revcache'})
353 353 def showfilecopiesswitch(context, mapping):
354 354 """List of strings. Like "file_copies" but displayed
355 355 only if the --copied switch is set.
356 356 """
357 357 copies = context.resource(mapping, b'revcache').get(b'copies') or []
358 358 return templateutil.compatfilecopiesdict(
359 359 context, mapping, b'file_copy', copies
360 360 )
361 361
362 362
363 363 @templatekeyword(b'file_adds', requires={b'ctx', b'revcache'})
364 364 def showfileadds(context, mapping):
365 365 """List of strings. Files added by this changeset."""
366 366 ctx = context.resource(mapping, b'ctx')
367 367 return templateutil.compatfileslist(
368 368 context, mapping, b'file_add', ctx.filesadded()
369 369 )
370 370
371 371
372 372 @templatekeyword(b'file_dels', requires={b'ctx', b'revcache'})
373 373 def showfiledels(context, mapping):
374 374 """List of strings. Files removed by this changeset."""
375 375 ctx = context.resource(mapping, b'ctx')
376 376 return templateutil.compatfileslist(
377 377 context, mapping, b'file_del', ctx.filesremoved()
378 378 )
379 379
380 380
381 381 @templatekeyword(b'file_mods', requires={b'ctx', b'revcache'})
382 382 def showfilemods(context, mapping):
383 383 """List of strings. Files modified by this changeset."""
384 384 ctx = context.resource(mapping, b'ctx')
385 385 return templateutil.compatfileslist(
386 386 context, mapping, b'file_mod', ctx.filesmodified()
387 387 )
388 388
389 389
390 390 @templatekeyword(b'files', requires={b'ctx'})
391 391 def showfiles(context, mapping):
392 392 """List of strings. All files modified, added, or removed by this
393 393 changeset.
394 394 """
395 395 ctx = context.resource(mapping, b'ctx')
396 396 return templateutil.compatfileslist(context, mapping, b'file', ctx.files())
397 397
398 398
399 @templatekeyword(b'graphnode', requires={b'repo', b'ctx'})
399 @templatekeyword(b'graphnode', requires={b'repo', b'ctx', b'cache'})
400 400 def showgraphnode(context, mapping):
401 401 """String. The character representing the changeset node in an ASCII
402 402 revision graph."""
403 403 repo = context.resource(mapping, b'repo')
404 404 ctx = context.resource(mapping, b'ctx')
405 return getgraphnode(repo, ctx)
405 cache = context.resource(mapping, b'cache')
406 return getgraphnode(repo, ctx, cache)
406 407
407 408
408 def getgraphnode(repo, ctx):
409 return getgraphnodecurrent(repo, ctx) or getgraphnodesymbol(ctx)
409 def getgraphnode(repo, ctx, cache):
410 return getgraphnodecurrent(repo, ctx, cache) or getgraphnodesymbol(ctx)
410 411
411 412
412 def getgraphnodecurrent(repo, ctx):
413 def getgraphnodecurrent(repo, ctx, cache):
413 414 wpnodes = repo.dirstate.parents()
414 415 if wpnodes[1] == nullid:
415 416 wpnodes = wpnodes[:1]
416 417 if ctx.node() in wpnodes:
417 418 return b'@'
418 419 else:
420 merge_nodes = cache.get(b'merge_nodes', ())
421 if not merge_nodes:
422 from . import merge
423
424 mergestate = merge.mergestate.read(repo)
425 if mergestate.active():
426 merge_nodes = (mergestate.local, mergestate.other)
427 cache[b'merge_nodes'] = merge_nodes
428
429 if ctx.node() in merge_nodes:
430 return b'%'
419 431 return b''
420 432
421 433
422 434 def getgraphnodesymbol(ctx):
423 435 if ctx.obsolete():
424 436 return b'x'
425 437 elif ctx.isunstable():
426 438 return b'*'
427 439 elif ctx.closesbranch():
428 440 return b'_'
429 441 else:
430 442 return b'o'
431 443
432 444
433 445 @templatekeyword(b'graphwidth', requires=())
434 446 def showgraphwidth(context, mapping):
435 447 """Integer. The width of the graph drawn by 'log --graph' or zero."""
436 448 # just hosts documentation; should be overridden by template mapping
437 449 return 0
438 450
439 451
440 452 @templatekeyword(b'index', requires=())
441 453 def showindex(context, mapping):
442 454 """Integer. The current iteration of the loop. (0 indexed)"""
443 455 # just hosts documentation; should be overridden by template mapping
444 456 raise error.Abort(_(b"can't use index in this context"))
445 457
446 458
447 459 @templatekeyword(b'latesttag', requires={b'repo', b'ctx', b'cache'})
448 460 def showlatesttag(context, mapping):
449 461 """List of strings. The global tags on the most recent globally
450 462 tagged ancestor of this changeset. If no such tags exist, the list
451 463 consists of the single string "null".
452 464 """
453 465 return showlatesttags(context, mapping, None)
454 466
455 467
456 468 def showlatesttags(context, mapping, pattern):
457 469 """helper method for the latesttag keyword and function"""
458 470 latesttags = getlatesttags(context, mapping, pattern)
459 471
460 472 # latesttag[0] is an implementation detail for sorting csets on different
461 473 # branches in a stable manner- it is the date the tagged cset was created,
462 474 # not the date the tag was created. Therefore it isn't made visible here.
463 475 makemap = lambda v: {
464 476 b'changes': _showchangessincetag,
465 477 b'distance': latesttags[1],
466 478 b'latesttag': v, # BC with {latesttag % '{latesttag}'}
467 479 b'tag': v,
468 480 }
469 481
470 482 tags = latesttags[2]
471 483 f = _showcompatlist(context, mapping, b'latesttag', tags, separator=b':')
472 484 return _hybrid(f, tags, makemap, pycompat.identity)
473 485
474 486
475 487 @templatekeyword(b'latesttagdistance', requires={b'repo', b'ctx', b'cache'})
476 488 def showlatesttagdistance(context, mapping):
477 489 """Integer. Longest path to the latest tag."""
478 490 return getlatesttags(context, mapping)[1]
479 491
480 492
481 493 @templatekeyword(b'changessincelatesttag', requires={b'repo', b'ctx', b'cache'})
482 494 def showchangessincelatesttag(context, mapping):
483 495 """Integer. All ancestors not in the latest tag."""
484 496 tag = getlatesttags(context, mapping)[2][0]
485 497 mapping = context.overlaymap(mapping, {b'tag': tag})
486 498 return _showchangessincetag(context, mapping)
487 499
488 500
489 501 def _showchangessincetag(context, mapping):
490 502 repo = context.resource(mapping, b'repo')
491 503 ctx = context.resource(mapping, b'ctx')
492 504 offset = 0
493 505 revs = [ctx.rev()]
494 506 tag = context.symbol(mapping, b'tag')
495 507
496 508 # The only() revset doesn't currently support wdir()
497 509 if ctx.rev() is None:
498 510 offset = 1
499 511 revs = [p.rev() for p in ctx.parents()]
500 512
501 513 return len(repo.revs(b'only(%ld, %s)', revs, tag)) + offset
502 514
503 515
504 516 # teach templater latesttags.changes is switched to (context, mapping) API
505 517 _showchangessincetag._requires = {b'repo', b'ctx'}
506 518
507 519
508 520 @templatekeyword(b'manifest', requires={b'repo', b'ctx'})
509 521 def showmanifest(context, mapping):
510 522 repo = context.resource(mapping, b'repo')
511 523 ctx = context.resource(mapping, b'ctx')
512 524 mnode = ctx.manifestnode()
513 525 if mnode is None:
514 526 mnode = wdirid
515 527 mrev = wdirrev
516 528 else:
517 529 mrev = repo.manifestlog.rev(mnode)
518 530 mhex = hex(mnode)
519 531 mapping = context.overlaymap(mapping, {b'rev': mrev, b'node': mhex})
520 532 f = context.process(b'manifest', mapping)
521 533 return templateutil.hybriditem(
522 534 f, None, f, lambda x: {b'rev': mrev, b'node': mhex}
523 535 )
524 536
525 537
526 538 @templatekeyword(b'obsfate', requires={b'ui', b'repo', b'ctx'})
527 539 def showobsfate(context, mapping):
528 540 # this function returns a list containing pre-formatted obsfate strings.
529 541 #
530 542 # This function will be replaced by templates fragments when we will have
531 543 # the verbosity templatekw available.
532 544 succsandmarkers = showsuccsandmarkers(context, mapping)
533 545
534 546 ui = context.resource(mapping, b'ui')
535 547 repo = context.resource(mapping, b'repo')
536 548 values = []
537 549
538 550 for x in succsandmarkers.tovalue(context, mapping):
539 551 v = obsutil.obsfateprinter(
540 552 ui, repo, x[b'successors'], x[b'markers'], scmutil.formatchangeid
541 553 )
542 554 values.append(v)
543 555
544 556 return compatlist(context, mapping, b"fate", values)
545 557
546 558
547 559 def shownames(context, mapping, namespace):
548 560 """helper method to generate a template keyword for a namespace"""
549 561 repo = context.resource(mapping, b'repo')
550 562 ctx = context.resource(mapping, b'ctx')
551 563 ns = repo.names[namespace]
552 564 names = ns.names(repo, ctx.node())
553 565 return compatlist(
554 566 context, mapping, ns.templatename, names, plural=namespace
555 567 )
556 568
557 569
558 570 @templatekeyword(b'namespaces', requires={b'repo', b'ctx'})
559 571 def shownamespaces(context, mapping):
560 572 """Dict of lists. Names attached to this changeset per
561 573 namespace."""
562 574 repo = context.resource(mapping, b'repo')
563 575 ctx = context.resource(mapping, b'ctx')
564 576
565 577 namespaces = util.sortdict()
566 578
567 579 def makensmapfn(ns):
568 580 # 'name' for iterating over namespaces, templatename for local reference
569 581 return lambda v: {b'name': v, ns.templatename: v}
570 582
571 583 for k, ns in pycompat.iteritems(repo.names):
572 584 names = ns.names(repo, ctx.node())
573 585 f = _showcompatlist(context, mapping, b'name', names)
574 586 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
575 587
576 588 f = _showcompatlist(context, mapping, b'namespace', list(namespaces))
577 589
578 590 def makemap(ns):
579 591 return {
580 592 b'namespace': ns,
581 593 b'names': namespaces[ns],
582 594 b'builtin': repo.names[ns].builtin,
583 595 b'colorname': repo.names[ns].colorname,
584 596 }
585 597
586 598 return _hybrid(f, namespaces, makemap, pycompat.identity)
587 599
588 600
589 601 @templatekeyword(b'negrev', requires={b'repo', b'ctx'})
590 602 def shownegrev(context, mapping):
591 603 """Integer. The repository-local changeset negative revision number,
592 604 which counts in the opposite direction."""
593 605 ctx = context.resource(mapping, b'ctx')
594 606 rev = ctx.rev()
595 607 if rev is None or rev < 0: # wdir() or nullrev?
596 608 return None
597 609 repo = context.resource(mapping, b'repo')
598 610 return rev - len(repo)
599 611
600 612
601 613 @templatekeyword(b'node', requires={b'ctx'})
602 614 def shownode(context, mapping):
603 615 """String. The changeset identification hash, as a 40 hexadecimal
604 616 digit string.
605 617 """
606 618 ctx = context.resource(mapping, b'ctx')
607 619 return ctx.hex()
608 620
609 621
610 622 @templatekeyword(b'obsolete', requires={b'ctx'})
611 623 def showobsolete(context, mapping):
612 624 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
613 625 ctx = context.resource(mapping, b'ctx')
614 626 if ctx.obsolete():
615 627 return b'obsolete'
616 628 return b''
617 629
618 630
619 631 @templatekeyword(b'path', requires={b'fctx'})
620 632 def showpath(context, mapping):
621 633 """String. Repository-absolute path of the current file. (EXPERIMENTAL)"""
622 634 fctx = context.resource(mapping, b'fctx')
623 635 return fctx.path()
624 636
625 637
626 638 @templatekeyword(b'peerurls', requires={b'repo'})
627 639 def showpeerurls(context, mapping):
628 640 """A dictionary of repository locations defined in the [paths] section
629 641 of your configuration file."""
630 642 repo = context.resource(mapping, b'repo')
631 643 # see commands.paths() for naming of dictionary keys
632 644 paths = repo.ui.paths
633 645 urls = util.sortdict(
634 646 (k, p.rawloc) for k, p in sorted(pycompat.iteritems(paths))
635 647 )
636 648
637 649 def makemap(k):
638 650 p = paths[k]
639 651 d = {b'name': k, b'url': p.rawloc}
640 652 d.update((o, v) for o, v in sorted(pycompat.iteritems(p.suboptions)))
641 653 return d
642 654
643 655 return _hybrid(None, urls, makemap, lambda k: b'%s=%s' % (k, urls[k]))
644 656
645 657
646 658 @templatekeyword(b"predecessors", requires={b'repo', b'ctx'})
647 659 def showpredecessors(context, mapping):
648 660 """Returns the list of the closest visible predecessors. (EXPERIMENTAL)"""
649 661 repo = context.resource(mapping, b'repo')
650 662 ctx = context.resource(mapping, b'ctx')
651 663 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
652 664 predecessors = pycompat.maplist(hex, predecessors)
653 665
654 666 return _hybrid(
655 667 None,
656 668 predecessors,
657 669 lambda x: {b'ctx': repo[x]},
658 670 lambda x: scmutil.formatchangeid(repo[x]),
659 671 )
660 672
661 673
662 674 @templatekeyword(b'reporoot', requires={b'repo'})
663 675 def showreporoot(context, mapping):
664 676 """String. The root directory of the current repository."""
665 677 repo = context.resource(mapping, b'repo')
666 678 return repo.root
667 679
668 680
669 681 @templatekeyword(b'size', requires={b'fctx'})
670 682 def showsize(context, mapping):
671 683 """Integer. Size of the current file in bytes. (EXPERIMENTAL)"""
672 684 fctx = context.resource(mapping, b'fctx')
673 685 return fctx.size()
674 686
675 687
676 688 # requires 'fctx' to denote {status} depends on (ctx, path) pair
677 689 @templatekeyword(b'status', requires={b'ctx', b'fctx', b'revcache'})
678 690 def showstatus(context, mapping):
679 691 """String. Status code of the current file. (EXPERIMENTAL)"""
680 692 path = templateutil.runsymbol(context, mapping, b'path')
681 693 path = templateutil.stringify(context, mapping, path)
682 694 if not path:
683 695 return
684 696 statmap = _getfilestatusmap(context, mapping)
685 697 if path not in statmap:
686 698 statmap = _getfilestatusmap(context, mapping, listall=True)
687 699 return statmap.get(path)
688 700
689 701
690 702 @templatekeyword(b"successorssets", requires={b'repo', b'ctx'})
691 703 def showsuccessorssets(context, mapping):
692 704 """Returns a string of sets of successors for a changectx. Format used
693 705 is: [ctx1, ctx2], [ctx3] if ctx has been split into ctx1 and ctx2
694 706 while also diverged into ctx3. (EXPERIMENTAL)"""
695 707 repo = context.resource(mapping, b'repo')
696 708 ctx = context.resource(mapping, b'ctx')
697 709 if not ctx.obsolete():
698 710 return b''
699 711
700 712 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
701 713 ssets = [[hex(n) for n in ss] for ss in ssets]
702 714
703 715 data = []
704 716 for ss in ssets:
705 717 h = _hybrid(
706 718 None,
707 719 ss,
708 720 lambda x: {b'ctx': repo[x]},
709 721 lambda x: scmutil.formatchangeid(repo[x]),
710 722 )
711 723 data.append(h)
712 724
713 725 # Format the successorssets
714 726 def render(d):
715 727 return templateutil.stringify(context, mapping, d)
716 728
717 729 def gen(data):
718 730 yield b"; ".join(render(d) for d in data)
719 731
720 732 return _hybrid(
721 733 gen(data), data, lambda x: {b'successorset': x}, pycompat.identity
722 734 )
723 735
724 736
725 737 @templatekeyword(b"succsandmarkers", requires={b'repo', b'ctx'})
726 738 def showsuccsandmarkers(context, mapping):
727 739 """Returns a list of dict for each final successor of ctx. The dict
728 740 contains successors node id in "successors" keys and the list of
729 741 obs-markers from ctx to the set of successors in "markers".
730 742 (EXPERIMENTAL)
731 743 """
732 744 repo = context.resource(mapping, b'repo')
733 745 ctx = context.resource(mapping, b'ctx')
734 746
735 747 values = obsutil.successorsandmarkers(repo, ctx)
736 748
737 749 if values is None:
738 750 values = []
739 751
740 752 # Format successors and markers to avoid exposing binary to templates
741 753 data = []
742 754 for i in values:
743 755 # Format successors
744 756 successors = i[b'successors']
745 757
746 758 successors = [hex(n) for n in successors]
747 759 successors = _hybrid(
748 760 None,
749 761 successors,
750 762 lambda x: {b'ctx': repo[x]},
751 763 lambda x: scmutil.formatchangeid(repo[x]),
752 764 )
753 765
754 766 # Format markers
755 767 finalmarkers = []
756 768 for m in i[b'markers']:
757 769 hexprec = hex(m[0])
758 770 hexsucs = tuple(hex(n) for n in m[1])
759 771 hexparents = None
760 772 if m[5] is not None:
761 773 hexparents = tuple(hex(n) for n in m[5])
762 774 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
763 775 finalmarkers.append(newmarker)
764 776
765 777 data.append({b'successors': successors, b'markers': finalmarkers})
766 778
767 779 return templateutil.mappinglist(data)
768 780
769 781
770 782 @templatekeyword(b'p1', requires={b'ctx'})
771 783 def showp1(context, mapping):
772 784 """Changeset. The changeset's first parent. ``{p1.rev}`` for the revision
773 785 number, and ``{p1.node}`` for the identification hash."""
774 786 ctx = context.resource(mapping, b'ctx')
775 787 return templateutil.mappingdict({b'ctx': ctx.p1()}, tmpl=_changeidtmpl)
776 788
777 789
778 790 @templatekeyword(b'p2', requires={b'ctx'})
779 791 def showp2(context, mapping):
780 792 """Changeset. The changeset's second parent. ``{p2.rev}`` for the revision
781 793 number, and ``{p2.node}`` for the identification hash."""
782 794 ctx = context.resource(mapping, b'ctx')
783 795 return templateutil.mappingdict({b'ctx': ctx.p2()}, tmpl=_changeidtmpl)
784 796
785 797
786 798 @templatekeyword(b'p1rev', requires={b'ctx'})
787 799 def showp1rev(context, mapping):
788 800 """Integer. The repository-local revision number of the changeset's
789 801 first parent, or -1 if the changeset has no parents. (DEPRECATED)"""
790 802 ctx = context.resource(mapping, b'ctx')
791 803 return ctx.p1().rev()
792 804
793 805
794 806 @templatekeyword(b'p2rev', requires={b'ctx'})
795 807 def showp2rev(context, mapping):
796 808 """Integer. The repository-local revision number of the changeset's
797 809 second parent, or -1 if the changeset has no second parent. (DEPRECATED)"""
798 810 ctx = context.resource(mapping, b'ctx')
799 811 return ctx.p2().rev()
800 812
801 813
802 814 @templatekeyword(b'p1node', requires={b'ctx'})
803 815 def showp1node(context, mapping):
804 816 """String. The identification hash of the changeset's first parent,
805 817 as a 40 digit hexadecimal string. If the changeset has no parents, all
806 818 digits are 0. (DEPRECATED)"""
807 819 ctx = context.resource(mapping, b'ctx')
808 820 return ctx.p1().hex()
809 821
810 822
811 823 @templatekeyword(b'p2node', requires={b'ctx'})
812 824 def showp2node(context, mapping):
813 825 """String. The identification hash of the changeset's second
814 826 parent, as a 40 digit hexadecimal string. If the changeset has no second
815 827 parent, all digits are 0. (DEPRECATED)"""
816 828 ctx = context.resource(mapping, b'ctx')
817 829 return ctx.p2().hex()
818 830
819 831
820 832 @templatekeyword(b'parents', requires={b'repo', b'ctx'})
821 833 def showparents(context, mapping):
822 834 """List of strings. The parents of the changeset in "rev:node"
823 835 format. If the changeset has only one "natural" parent (the predecessor
824 836 revision) nothing is shown."""
825 837 repo = context.resource(mapping, b'repo')
826 838 ctx = context.resource(mapping, b'ctx')
827 839 pctxs = scmutil.meaningfulparents(repo, ctx)
828 840 prevs = [p.rev() for p in pctxs]
829 841 parents = [
830 842 [(b'rev', p.rev()), (b'node', p.hex()), (b'phase', p.phasestr())]
831 843 for p in pctxs
832 844 ]
833 845 f = _showcompatlist(context, mapping, b'parent', parents)
834 846 return _hybrid(
835 847 f,
836 848 prevs,
837 849 lambda x: {b'ctx': repo[x]},
838 850 lambda x: scmutil.formatchangeid(repo[x]),
839 851 keytype=int,
840 852 )
841 853
842 854
843 855 @templatekeyword(b'phase', requires={b'ctx'})
844 856 def showphase(context, mapping):
845 857 """String. The changeset phase name."""
846 858 ctx = context.resource(mapping, b'ctx')
847 859 return ctx.phasestr()
848 860
849 861
850 862 @templatekeyword(b'phaseidx', requires={b'ctx'})
851 863 def showphaseidx(context, mapping):
852 864 """Integer. The changeset phase index. (ADVANCED)"""
853 865 ctx = context.resource(mapping, b'ctx')
854 866 return ctx.phase()
855 867
856 868
857 869 @templatekeyword(b'rev', requires={b'ctx'})
858 870 def showrev(context, mapping):
859 871 """Integer. The repository-local changeset revision number."""
860 872 ctx = context.resource(mapping, b'ctx')
861 873 return scmutil.intrev(ctx)
862 874
863 875
864 876 def showrevslist(context, mapping, name, revs):
865 877 """helper to generate a list of revisions in which a mapped template will
866 878 be evaluated"""
867 879 repo = context.resource(mapping, b'repo')
868 880 # revs may be a smartset; don't compute it until f() has to be evaluated
869 881 def f():
870 882 srevs = [b'%d' % r for r in revs]
871 883 return _showcompatlist(context, mapping, name, srevs)
872 884
873 885 return _hybrid(
874 886 f,
875 887 revs,
876 888 lambda x: {name: x, b'ctx': repo[x]},
877 889 pycompat.identity,
878 890 keytype=int,
879 891 )
880 892
881 893
882 894 @templatekeyword(b'subrepos', requires={b'ctx'})
883 895 def showsubrepos(context, mapping):
884 896 """List of strings. Updated subrepositories in the changeset."""
885 897 ctx = context.resource(mapping, b'ctx')
886 898 substate = ctx.substate
887 899 if not substate:
888 900 return compatlist(context, mapping, b'subrepo', [])
889 901 psubstate = ctx.p1().substate or {}
890 902 subrepos = []
891 903 for sub in substate:
892 904 if sub not in psubstate or substate[sub] != psubstate[sub]:
893 905 subrepos.append(sub) # modified or newly added in ctx
894 906 for sub in psubstate:
895 907 if sub not in substate:
896 908 subrepos.append(sub) # removed in ctx
897 909 return compatlist(context, mapping, b'subrepo', sorted(subrepos))
898 910
899 911
900 912 # don't remove "showtags" definition, even though namespaces will put
901 913 # a helper function for "tags" keyword into "keywords" map automatically,
902 914 # because online help text is built without namespaces initialization
903 915 @templatekeyword(b'tags', requires={b'repo', b'ctx'})
904 916 def showtags(context, mapping):
905 917 """List of strings. Any tags associated with the changeset."""
906 918 return shownames(context, mapping, b'tags')
907 919
908 920
909 921 @templatekeyword(b'termwidth', requires={b'ui'})
910 922 def showtermwidth(context, mapping):
911 923 """Integer. The width of the current terminal."""
912 924 ui = context.resource(mapping, b'ui')
913 925 return ui.termwidth()
914 926
915 927
916 928 @templatekeyword(b'user', requires={b'ctx'})
917 929 def showuser(context, mapping):
918 930 """String. The unmodified author of the changeset."""
919 931 ctx = context.resource(mapping, b'ctx')
920 932 return ctx.user()
921 933
922 934
923 935 @templatekeyword(b'instabilities', requires={b'ctx'})
924 936 def showinstabilities(context, mapping):
925 937 """List of strings. Evolution instabilities affecting the changeset.
926 938 (EXPERIMENTAL)
927 939 """
928 940 ctx = context.resource(mapping, b'ctx')
929 941 return compatlist(
930 942 context,
931 943 mapping,
932 944 b'instability',
933 945 ctx.instabilities(),
934 946 plural=b'instabilities',
935 947 )
936 948
937 949
938 950 @templatekeyword(b'verbosity', requires={b'ui'})
939 951 def showverbosity(context, mapping):
940 952 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
941 953 or ''."""
942 954 ui = context.resource(mapping, b'ui')
943 955 # see logcmdutil.changesettemplater for priority of these flags
944 956 if ui.debugflag:
945 957 return b'debug'
946 958 elif ui.quiet:
947 959 return b'quiet'
948 960 elif ui.verbose:
949 961 return b'verbose'
950 962 return b''
951 963
952 964
953 965 @templatekeyword(b'whyunstable', requires={b'repo', b'ctx'})
954 966 def showwhyunstable(context, mapping):
955 967 """List of dicts explaining all instabilities of a changeset.
956 968 (EXPERIMENTAL)
957 969 """
958 970 repo = context.resource(mapping, b'repo')
959 971 ctx = context.resource(mapping, b'ctx')
960 972
961 973 def formatnode(ctx):
962 974 return b'%s (%s)' % (scmutil.formatchangeid(ctx), ctx.phasestr())
963 975
964 976 entries = obsutil.whyunstable(repo, ctx)
965 977
966 978 for entry in entries:
967 979 if entry.get(b'divergentnodes'):
968 980 dnodes = entry[b'divergentnodes']
969 981 dnhybrid = _hybrid(
970 982 None,
971 983 [dnode.hex() for dnode in dnodes],
972 984 lambda x: {b'ctx': repo[x]},
973 985 lambda x: formatnode(repo[x]),
974 986 )
975 987 entry[b'divergentnodes'] = dnhybrid
976 988
977 989 tmpl = (
978 990 b'{instability}:{if(divergentnodes, " ")}{divergentnodes} '
979 991 b'{reason} {node|short}'
980 992 )
981 993 return templateutil.mappinglist(entries, tmpl=tmpl, sep=b'\n')
982 994
983 995
984 996 def loadkeyword(ui, extname, registrarobj):
985 997 """Load template keyword from specified registrarobj
986 998 """
987 999 for name, func in pycompat.iteritems(registrarobj._table):
988 1000 keywords[name] = func
989 1001
990 1002
991 1003 # tell hggettext to extract docstrings from these functions:
992 1004 i18nfunctions = keywords.values()
@@ -1,29 +1,34 b''
1 1 == New Features ==
2 2
3 3 * `hg purge`/`hg clean` can now delete ignored files instead of
4 4 untracked files, with the new -i flag.
5 5
6 * New `conflictlocal()` and `conflictother()` revsets returns the
6 * `hg log` now defaults to using an '%' symbol for commits involved
7 in unresolved merge conflicts. That includes unresolved conflicts
8 caused by e.g. `hg update --merge` and `hg graft`. '@' still takes
9 precedence, so what used to be marked '@' still is.
10
11 * New `conflictlocal()` and `conflictother()` revsets return the
7 12 commits that are being merged, when there are conflicts. Also works
8 13 for conflicts caused by e.g. `hg graft`.
9 14
10 15
11 16 == New Experimental Features ==
12 17
13 18
14 19 == Bug Fixes ==
15 20
16 21
17 22 == Backwards Compatibility Changes ==
18 23
19 24
20 25 == Internal API Changes ==
21 26
22 27 * The deprecated `ui.progress()` has now been deleted. Please use
23 28 `ui.makeprogress()` instead.
24 29
25 30 * `hg.merge()` has lost its `abort` argument. Please call
26 31 `hg.abortmerge()` directly instead.
27 32
28 33 * The `*others` argument of `cmdutil.check_incompatible_arguments()`
29 34 changed from being varargs argument to being a single collection.
@@ -1,807 +1,807 b''
1 1 $ hg init basic
2 2 $ cd basic
3 3
4 4 should complain
5 5
6 6 $ hg backout
7 7 abort: please specify a revision to backout
8 8 [255]
9 9 $ hg backout -r 0 0
10 10 abort: please specify just one revision
11 11 [255]
12 12
13 13 basic operation
14 14 (this also tests that editor is invoked if the commit message is not
15 15 specified explicitly)
16 16
17 17 $ echo a > a
18 18 $ hg commit -d '0 0' -A -m a
19 19 adding a
20 20 $ echo b >> a
21 21 $ hg commit -d '1 0' -m b
22 22
23 23 $ hg status --rev tip --rev "tip^1"
24 24 M a
25 25 $ HGEDITOR=cat hg backout -d '2 0' tip --tool=true
26 26 reverting a
27 27 Backed out changeset a820f4f40a57
28 28
29 29
30 30 HG: Enter commit message. Lines beginning with 'HG:' are removed.
31 31 HG: Leave message empty to abort commit.
32 32 HG: --
33 33 HG: user: test
34 34 HG: branch 'default'
35 35 HG: changed a
36 36 changeset 2:2929462c3dff backs out changeset 1:a820f4f40a57
37 37 $ cat a
38 38 a
39 39 $ hg summary
40 40 parent: 2:2929462c3dff tip
41 41 Backed out changeset a820f4f40a57
42 42 branch: default
43 43 commit: (clean)
44 44 update: (current)
45 45 phases: 3 draft
46 46
47 47 commit option
48 48
49 49 $ cd ..
50 50 $ hg init commit
51 51 $ cd commit
52 52
53 53 $ echo tomatoes > a
54 54 $ hg add a
55 55 $ hg commit -d '0 0' -m tomatoes
56 56
57 57 $ echo chair > b
58 58 $ hg add b
59 59 $ hg commit -d '1 0' -m chair
60 60
61 61 $ echo grapes >> a
62 62 $ hg commit -d '2 0' -m grapes
63 63
64 64 $ hg backout -d '4 0' 1 --tool=:fail
65 65 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
66 66 changeset 3:1c2161e97c0a backs out changeset 1:22cb4f70d813
67 67 $ hg summary
68 68 parent: 3:1c2161e97c0a tip
69 69 Backed out changeset 22cb4f70d813
70 70 branch: default
71 71 commit: (clean)
72 72 update: (current)
73 73 phases: 4 draft
74 74
75 75 $ echo ypples > a
76 76 $ hg commit -d '5 0' -m ypples
77 77
78 78 $ hg backout -d '6 0' 2 --tool=:fail
79 79 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
80 80 use 'hg resolve' to retry unresolved file merges
81 81 [1]
82 82 $ hg summary
83 83 parent: 4:ed99997b793d tip
84 84 ypples
85 85 branch: default
86 86 commit: 1 unresolved (clean)
87 87 update: (current)
88 88 phases: 5 draft
89 89 $ hg log -G
90 90 @ changeset: 4:ed99997b793d
91 91 | tag: tip
92 92 | user: test
93 93 | date: Thu Jan 01 00:00:05 1970 +0000
94 94 | summary: ypples
95 95 |
96 96 o changeset: 3:1c2161e97c0a
97 97 | user: test
98 98 | date: Thu Jan 01 00:00:04 1970 +0000
99 99 | summary: Backed out changeset 22cb4f70d813
100 100 |
101 101 o changeset: 2:a8c6e511cfee
102 102 | user: test
103 103 | date: Thu Jan 01 00:00:02 1970 +0000
104 104 | summary: grapes
105 105 |
106 o changeset: 1:22cb4f70d813
106 % changeset: 1:22cb4f70d813
107 107 | user: test
108 108 | date: Thu Jan 01 00:00:01 1970 +0000
109 109 | summary: chair
110 110 |
111 111 o changeset: 0:a5cb2dde5805
112 112 user: test
113 113 date: Thu Jan 01 00:00:00 1970 +0000
114 114 summary: tomatoes
115 115
116 116
117 117 file that was removed is recreated
118 118 (this also tests that editor is not invoked if the commit message is
119 119 specified explicitly)
120 120
121 121 $ cd ..
122 122 $ hg init remove
123 123 $ cd remove
124 124
125 125 $ echo content > a
126 126 $ hg commit -d '0 0' -A -m a
127 127 adding a
128 128
129 129 $ hg rm a
130 130 $ hg commit -d '1 0' -m b
131 131
132 132 $ HGEDITOR=cat hg backout -d '2 0' tip --tool=true -m "Backed out changeset 76862dcce372"
133 133 adding a
134 134 changeset 2:de31bdc76c0d backs out changeset 1:76862dcce372
135 135 $ cat a
136 136 content
137 137 $ hg summary
138 138 parent: 2:de31bdc76c0d tip
139 139 Backed out changeset 76862dcce372
140 140 branch: default
141 141 commit: (clean)
142 142 update: (current)
143 143 phases: 3 draft
144 144
145 145 backout of backout is as if nothing happened
146 146
147 147 $ hg backout -d '3 0' --merge tip --tool=true
148 148 removing a
149 149 changeset 3:7f6d0f120113 backs out changeset 2:de31bdc76c0d
150 150 $ test -f a
151 151 [1]
152 152 $ hg summary
153 153 parent: 3:7f6d0f120113 tip
154 154 Backed out changeset de31bdc76c0d
155 155 branch: default
156 156 commit: (clean)
157 157 update: (current)
158 158 phases: 4 draft
159 159
160 160 Test that 'hg rollback' restores dirstate just before opening
161 161 transaction: in-memory dirstate changes should be written into
162 162 '.hg/journal.dirstate' as expected.
163 163
164 164 $ echo 'removed soon' > b
165 165 $ hg commit -A -d '4 0' -m 'prepare for subsequent removing'
166 166 adding b
167 167 $ echo 'newly added' > c
168 168 $ hg add c
169 169 $ hg remove b
170 170 $ hg commit -d '5 0' -m 'prepare for subsequent backout'
171 171 $ touch -t 200001010000 c
172 172 $ hg status -A
173 173 C c
174 174 $ hg debugstate --no-dates
175 175 n 644 12 set c
176 176 $ hg backout -d '6 0' -m 'to be rollback-ed soon' -r .
177 177 removing c
178 178 adding b
179 179 changeset 6:4bfec048029d backs out changeset 5:fac0b729a654
180 180 $ hg rollback -q
181 181 $ hg status -A
182 182 A b
183 183 R c
184 184 $ hg debugstate --no-dates
185 185 a 0 -1 unset b
186 186 r 0 0 set c
187 187
188 188 across branch
189 189
190 190 $ cd ..
191 191 $ hg init branch
192 192 $ cd branch
193 193 $ echo a > a
194 194 $ hg ci -Am0
195 195 adding a
196 196 $ echo b > b
197 197 $ hg ci -Am1
198 198 adding b
199 199 $ hg co -C 0
200 200 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
201 201 $ hg summary
202 202 parent: 0:f7b1eb17ad24
203 203 0
204 204 branch: default
205 205 commit: (clean)
206 206 update: 1 new changesets (update)
207 207 phases: 2 draft
208 208
209 209 should fail
210 210
211 211 $ hg backout 1
212 212 abort: cannot backout change that is not an ancestor
213 213 [255]
214 214 $ echo c > c
215 215 $ hg ci -Am2
216 216 adding c
217 217 created new head
218 218 $ hg summary
219 219 parent: 2:db815d6d32e6 tip
220 220 2
221 221 branch: default
222 222 commit: (clean)
223 223 update: 1 new changesets, 2 branch heads (merge)
224 224 phases: 3 draft
225 225
226 226 should fail
227 227
228 228 $ hg backout 1
229 229 abort: cannot backout change that is not an ancestor
230 230 [255]
231 231 $ hg summary
232 232 parent: 2:db815d6d32e6 tip
233 233 2
234 234 branch: default
235 235 commit: (clean)
236 236 update: 1 new changesets, 2 branch heads (merge)
237 237 phases: 3 draft
238 238
239 239 backout with merge
240 240
241 241 $ cd ..
242 242 $ hg init merge
243 243 $ cd merge
244 244
245 245 $ echo line 1 > a
246 246 $ echo line 2 >> a
247 247 $ hg commit -d '0 0' -A -m a
248 248 adding a
249 249 $ hg summary
250 250 parent: 0:59395513a13a tip
251 251 a
252 252 branch: default
253 253 commit: (clean)
254 254 update: (current)
255 255 phases: 1 draft
256 256
257 257 remove line 1
258 258
259 259 $ echo line 2 > a
260 260 $ hg commit -d '1 0' -m b
261 261
262 262 $ echo line 3 >> a
263 263 $ hg commit -d '2 0' -m c
264 264
265 265 $ hg backout --merge -d '3 0' 1 --tool=true
266 266 reverting a
267 267 created new head
268 268 changeset 3:26b8ccb9ad91 backs out changeset 1:5a50a024c182
269 269 merging with changeset 3:26b8ccb9ad91
270 270 merging a
271 271 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
272 272 (branch merge, don't forget to commit)
273 273 $ hg commit -d '4 0' -m d
274 274 $ hg summary
275 275 parent: 4:c7df5e0b9c09 tip
276 276 d
277 277 branch: default
278 278 commit: (clean)
279 279 update: (current)
280 280 phases: 5 draft
281 281
282 282 check line 1 is back
283 283
284 284 $ cat a
285 285 line 1
286 286 line 2
287 287 line 3
288 288
289 289 Test visibility of in-memory dirstate changes outside transaction to
290 290 external hook process
291 291
292 292 $ cat > $TESTTMP/checkvisibility.sh <<EOF
293 293 > echo "==== \$1:"
294 294 > hg parents --template "{rev}:{node|short}\n"
295 295 > echo "===="
296 296 > EOF
297 297
298 298 "hg backout --merge REV1" at REV2 below implies steps below:
299 299
300 300 (1) update to REV1 (REV2 => REV1)
301 301 (2) revert by REV1^1
302 302 (3) commit backing out revision (REV3)
303 303 (4) update to REV2 (REV3 => REV2)
304 304 (5) merge with REV3 (REV2 => REV2, REV3)
305 305
306 306 == test visibility to external preupdate hook
307 307
308 308 $ hg update -q -C 2
309 309 $ hg --config extensions.strip= strip 3
310 310 saved backup bundle to * (glob)
311 311
312 312 $ cat >> .hg/hgrc <<EOF
313 313 > [hooks]
314 314 > preupdate.visibility = sh $TESTTMP/checkvisibility.sh preupdate
315 315 > EOF
316 316
317 317 ("-m" is needed to avoid writing dirstate changes out at other than
318 318 invocation of the hook to be examined)
319 319
320 320 $ hg backout --merge -d '3 0' 1 --tool=true -m 'fixed comment'
321 321 ==== preupdate:
322 322 2:6ea3f2a197a2
323 323 ====
324 324 reverting a
325 325 created new head
326 326 changeset 3:d92a3f57f067 backs out changeset 1:5a50a024c182
327 327 ==== preupdate:
328 328 3:d92a3f57f067
329 329 ====
330 330 merging with changeset 3:d92a3f57f067
331 331 ==== preupdate:
332 332 2:6ea3f2a197a2
333 333 ====
334 334 merging a
335 335 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
336 336 (branch merge, don't forget to commit)
337 337
338 338 $ cat >> .hg/hgrc <<EOF
339 339 > [hooks]
340 340 > preupdate.visibility =
341 341 > EOF
342 342
343 343 == test visibility to external update hook
344 344
345 345 $ hg update -q -C 2
346 346 $ hg --config extensions.strip= strip 3
347 347 saved backup bundle to * (glob)
348 348
349 349 $ cat >> .hg/hgrc <<EOF
350 350 > [hooks]
351 351 > update.visibility = sh $TESTTMP/checkvisibility.sh update
352 352 > EOF
353 353
354 354 $ hg backout --merge -d '3 0' 1 --tool=true -m 'fixed comment'
355 355 ==== update:
356 356 1:5a50a024c182
357 357 ====
358 358 reverting a
359 359 created new head
360 360 changeset 3:d92a3f57f067 backs out changeset 1:5a50a024c182
361 361 ==== update:
362 362 2:6ea3f2a197a2
363 363 ====
364 364 merging with changeset 3:d92a3f57f067
365 365 merging a
366 366 ==== update:
367 367 2:6ea3f2a197a2
368 368 3:d92a3f57f067
369 369 ====
370 370 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
371 371 (branch merge, don't forget to commit)
372 372
373 373 $ cat >> .hg/hgrc <<EOF
374 374 > [hooks]
375 375 > update.visibility =
376 376 > EOF
377 377
378 378 $ cd ..
379 379
380 380 backout should not back out subsequent changesets
381 381
382 382 $ hg init onecs
383 383 $ cd onecs
384 384 $ echo 1 > a
385 385 $ hg commit -d '0 0' -A -m a
386 386 adding a
387 387 $ echo 2 >> a
388 388 $ hg commit -d '1 0' -m b
389 389 $ echo 1 > b
390 390 $ hg commit -d '2 0' -A -m c
391 391 adding b
392 392 $ hg summary
393 393 parent: 2:882396649954 tip
394 394 c
395 395 branch: default
396 396 commit: (clean)
397 397 update: (current)
398 398 phases: 3 draft
399 399
400 400 without --merge
401 401 $ hg backout --no-commit -d '3 0' 1 --tool=true
402 402 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
403 403 changeset 22bca4c721e5 backed out, don't forget to commit.
404 404 $ hg locate b
405 405 b
406 406 $ hg update -C tip
407 407 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
408 408 $ hg locate b
409 409 b
410 410 $ hg summary
411 411 parent: 2:882396649954 tip
412 412 c
413 413 branch: default
414 414 commit: (clean)
415 415 update: (current)
416 416 phases: 3 draft
417 417
418 418 with --merge
419 419 $ hg backout --merge -d '3 0' 1 --tool=true
420 420 reverting a
421 421 created new head
422 422 changeset 3:3202beb76721 backs out changeset 1:22bca4c721e5
423 423 merging with changeset 3:3202beb76721
424 424 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
425 425 (branch merge, don't forget to commit)
426 426 $ hg locate b
427 427 b
428 428 $ hg update -C tip
429 429 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
430 430 $ hg locate b
431 431 [1]
432 432
433 433 $ cd ..
434 434 $ hg init m
435 435 $ cd m
436 436 $ echo a > a
437 437 $ hg commit -d '0 0' -A -m a
438 438 adding a
439 439 $ echo b > b
440 440 $ hg commit -d '1 0' -A -m b
441 441 adding b
442 442 $ echo c > c
443 443 $ hg commit -d '2 0' -A -m b
444 444 adding c
445 445 $ hg update 1
446 446 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
447 447 $ echo d > d
448 448 $ hg commit -d '3 0' -A -m c
449 449 adding d
450 450 created new head
451 451 $ hg merge 2
452 452 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
453 453 (branch merge, don't forget to commit)
454 454 $ hg commit -d '4 0' -A -m d
455 455 $ hg summary
456 456 parent: 4:b2f3bb92043e tip
457 457 d
458 458 branch: default
459 459 commit: (clean)
460 460 update: (current)
461 461 phases: 5 draft
462 462
463 463 backout of merge should fail
464 464
465 465 $ hg backout 4
466 466 abort: cannot backout a merge changeset
467 467 [255]
468 468
469 469 backout of merge with bad parent should fail
470 470
471 471 $ hg backout --parent 0 4
472 472 abort: cb9a9f314b8b is not a parent of b2f3bb92043e
473 473 [255]
474 474
475 475 backout of non-merge with parent should fail
476 476
477 477 $ hg backout --parent 0 3
478 478 abort: cannot use --parent on non-merge changeset
479 479 [255]
480 480
481 481 backout with valid parent should be ok
482 482
483 483 $ hg backout -d '5 0' --parent 2 4 --tool=true
484 484 removing d
485 485 changeset 5:10e5328c8435 backs out changeset 4:b2f3bb92043e
486 486 $ hg summary
487 487 parent: 5:10e5328c8435 tip
488 488 Backed out changeset b2f3bb92043e
489 489 branch: default
490 490 commit: (clean)
491 491 update: (current)
492 492 phases: 6 draft
493 493
494 494 $ hg rollback
495 495 repository tip rolled back to revision 4 (undo commit)
496 496 working directory now based on revision 4
497 497 $ hg update -C
498 498 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
499 499 $ hg summary
500 500 parent: 4:b2f3bb92043e tip
501 501 d
502 502 branch: default
503 503 commit: (clean)
504 504 update: (current)
505 505 phases: 5 draft
506 506
507 507 $ hg backout -d '6 0' --parent 3 4 --tool=true
508 508 removing c
509 509 changeset 5:033590168430 backs out changeset 4:b2f3bb92043e
510 510 $ hg summary
511 511 parent: 5:033590168430 tip
512 512 Backed out changeset b2f3bb92043e
513 513 branch: default
514 514 commit: (clean)
515 515 update: (current)
516 516 phases: 6 draft
517 517
518 518 $ cd ..
519 519
520 520 named branches
521 521
522 522 $ hg init named_branches
523 523 $ cd named_branches
524 524
525 525 $ echo default > default
526 526 $ hg ci -d '0 0' -Am default
527 527 adding default
528 528 $ hg branch branch1
529 529 marked working directory as branch branch1
530 530 (branches are permanent and global, did you want a bookmark?)
531 531 $ echo branch1 > file1
532 532 $ hg ci -d '1 0' -Am file1
533 533 adding file1
534 534 $ hg branch branch2
535 535 marked working directory as branch branch2
536 536 $ echo branch2 > file2
537 537 $ hg ci -d '2 0' -Am file2
538 538 adding file2
539 539
540 540 without --merge
541 541 $ hg backout --no-commit -r 1 --tool=true
542 542 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
543 543 changeset bf1602f437f3 backed out, don't forget to commit.
544 544 $ hg branch
545 545 branch2
546 546 $ hg status -A
547 547 R file1
548 548 C default
549 549 C file2
550 550 $ hg summary
551 551 parent: 2:45bbcd363bf0 tip
552 552 file2
553 553 branch: branch2
554 554 commit: 1 removed
555 555 update: (current)
556 556 phases: 3 draft
557 557
558 558 with --merge
559 559 (this also tests that editor is invoked if '--edit' is specified
560 560 explicitly regardless of '--message')
561 561
562 562 $ hg update -qC
563 563 $ HGEDITOR=cat hg backout --merge -d '3 0' -r 1 -m 'backout on branch1' --tool=true --edit
564 564 removing file1
565 565 backout on branch1
566 566
567 567
568 568 HG: Enter commit message. Lines beginning with 'HG:' are removed.
569 569 HG: Leave message empty to abort commit.
570 570 HG: --
571 571 HG: user: test
572 572 HG: branch 'branch2'
573 573 HG: removed file1
574 574 created new head
575 575 changeset 3:d4e8f6db59fb backs out changeset 1:bf1602f437f3
576 576 merging with changeset 3:d4e8f6db59fb
577 577 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
578 578 (branch merge, don't forget to commit)
579 579 $ hg summary
580 580 parent: 2:45bbcd363bf0
581 581 file2
582 582 parent: 3:d4e8f6db59fb tip
583 583 backout on branch1
584 584 branch: branch2
585 585 commit: 1 removed (merge)
586 586 update: (current)
587 587 phases: 4 draft
588 588 $ hg update -q -C 2
589 589
590 590 on branch2 with branch1 not merged, so file1 should still exist:
591 591
592 592 $ hg id
593 593 45bbcd363bf0 (branch2)
594 594 $ hg st -A
595 595 C default
596 596 C file1
597 597 C file2
598 598 $ hg summary
599 599 parent: 2:45bbcd363bf0
600 600 file2
601 601 branch: branch2
602 602 commit: (clean)
603 603 update: 1 new changesets, 2 branch heads (merge)
604 604 phases: 4 draft
605 605
606 606 on branch2 with branch1 merged, so file1 should be gone:
607 607
608 608 $ hg merge
609 609 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
610 610 (branch merge, don't forget to commit)
611 611 $ hg ci -d '4 0' -m 'merge backout of branch1'
612 612 $ hg id
613 613 d97a8500a969 (branch2) tip
614 614 $ hg st -A
615 615 C default
616 616 C file2
617 617 $ hg summary
618 618 parent: 4:d97a8500a969 tip
619 619 merge backout of branch1
620 620 branch: branch2
621 621 commit: (clean)
622 622 update: (current)
623 623 phases: 5 draft
624 624
625 625 on branch1, so no file1 and file2:
626 626
627 627 $ hg co -C branch1
628 628 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
629 629 $ hg id
630 630 bf1602f437f3 (branch1)
631 631 $ hg st -A
632 632 C default
633 633 C file1
634 634 $ hg summary
635 635 parent: 1:bf1602f437f3
636 636 file1
637 637 branch: branch1
638 638 commit: (clean)
639 639 update: (current)
640 640 phases: 5 draft
641 641
642 642 $ cd ..
643 643
644 644 backout of empty changeset (issue4190)
645 645
646 646 $ hg init emptycommit
647 647 $ cd emptycommit
648 648
649 649 $ touch file1
650 650 $ hg ci -Aqm file1
651 651 $ hg branch -q branch1
652 652 $ hg ci -qm branch1
653 653 $ hg backout -v 1
654 654 resolving manifests
655 655 nothing changed
656 656 [1]
657 657
658 658 $ cd ..
659 659
660 660
661 661 Test usage of `hg resolve` in case of conflict
662 662 (issue4163)
663 663
664 664 $ hg init issue4163
665 665 $ cd issue4163
666 666 $ touch foo
667 667 $ hg add foo
668 668 $ cat > foo << EOF
669 669 > one
670 670 > two
671 671 > three
672 672 > four
673 673 > five
674 674 > six
675 675 > seven
676 676 > height
677 677 > nine
678 678 > ten
679 679 > EOF
680 680 $ hg ci -m 'initial'
681 681 $ cat > foo << EOF
682 682 > one
683 683 > two
684 684 > THREE
685 685 > four
686 686 > five
687 687 > six
688 688 > seven
689 689 > height
690 690 > nine
691 691 > ten
692 692 > EOF
693 693 $ hg ci -m 'capital three'
694 694 $ cat > foo << EOF
695 695 > one
696 696 > two
697 697 > THREE
698 698 > four
699 699 > five
700 700 > six
701 701 > seven
702 702 > height
703 703 > nine
704 704 > TEN
705 705 > EOF
706 706 $ hg ci -m 'capital ten'
707 707 $ hg backout -r 'desc("capital three")' --tool internal:fail
708 708 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
709 709 use 'hg resolve' to retry unresolved file merges
710 710 [1]
711 711 $ hg status
712 712 $ hg debugmergestate
713 713 * version 2 records
714 714 local: b71750c4b0fdf719734971e3ef90dbeab5919a2d
715 715 other: a30dd8addae3ce71b8667868478542bc417439e6
716 716 file extras: foo (ancestorlinknode = 91360952243723bd5b1138d5f26bd8c8564cb553)
717 717 file: foo (record type "F", state "u", hash 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33)
718 718 local path: foo (flags "")
719 719 ancestor path: foo (node f89532f44c247a0e993d63e3a734dd781ab04708)
720 720 other path: foo (node f50039b486d6fa1a90ae51778388cad161f425ee)
721 721 $ mv .hg/merge/state2 .hg/merge/state2-moved
722 722 $ hg debugmergestate
723 723 * version 1 records
724 724 local: b71750c4b0fdf719734971e3ef90dbeab5919a2d
725 725 file: foo (record type "F", state "u", hash 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33)
726 726 local path: foo (flags "")
727 727 ancestor path: foo (node f89532f44c247a0e993d63e3a734dd781ab04708)
728 728 other path: foo (node not stored in v1 format)
729 729 $ mv .hg/merge/state2-moved .hg/merge/state2
730 730 $ hg resolve -l # still unresolved
731 731 U foo
732 732 $ hg summary
733 733 parent: 2:b71750c4b0fd tip
734 734 capital ten
735 735 branch: default
736 736 commit: 1 unresolved (clean)
737 737 update: (current)
738 738 phases: 3 draft
739 739 $ hg log -G
740 740 @ changeset: 2:b71750c4b0fd
741 741 | tag: tip
742 742 | user: test
743 743 | date: Thu Jan 01 00:00:00 1970 +0000
744 744 | summary: capital ten
745 745 |
746 746 o changeset: 1:913609522437
747 747 | user: test
748 748 | date: Thu Jan 01 00:00:00 1970 +0000
749 749 | summary: capital three
750 750 |
751 o changeset: 0:a30dd8addae3
751 % changeset: 0:a30dd8addae3
752 752 user: test
753 753 date: Thu Jan 01 00:00:00 1970 +0000
754 754 summary: initial
755 755
756 756 $ hg resolve --all --debug
757 757 picked tool ':merge' for foo (binary False symlink False changedelete False)
758 758 merging foo
759 759 my foo@b71750c4b0fd+ other foo@a30dd8addae3 ancestor foo@913609522437
760 760 premerge successful
761 761 (no more unresolved files)
762 762 continue: hg commit
763 763 $ hg status
764 764 M foo
765 765 ? foo.orig
766 766 $ hg resolve -l
767 767 R foo
768 768 $ hg summary
769 769 parent: 2:b71750c4b0fd tip
770 770 capital ten
771 771 branch: default
772 772 commit: 1 modified, 1 unknown
773 773 update: (current)
774 774 phases: 3 draft
775 775 $ cat foo
776 776 one
777 777 two
778 778 three
779 779 four
780 780 five
781 781 six
782 782 seven
783 783 height
784 784 nine
785 785 TEN
786 786
787 787 --no-commit shouldn't commit
788 788
789 789 $ hg init a
790 790 $ cd a
791 791 $ for i in 1 2 3; do
792 792 > touch $i
793 793 > hg ci -Am $i
794 794 > done
795 795 adding 1
796 796 adding 2
797 797 adding 3
798 798 $ hg backout --no-commit .
799 799 removing 3
800 800 changeset cccc23d9d68f backed out, don't forget to commit.
801 801 $ hg revert -aq
802 802
803 803 --no-commit can't be used with --merge
804 804
805 805 $ hg backout --merge --no-commit 2
806 806 abort: cannot use --merge with --no-commit
807 807 [255]
@@ -1,771 +1,771 b''
1 1 #testcases abortcommand abortflag
2 2
3 3 #if abortflag
4 4 $ cat >> $HGRCPATH <<EOF
5 5 > [alias]
6 6 > abort = graft --abort
7 7 > EOF
8 8 #endif
9 9
10 10
11 11 Testing the reading of old format graftstate file with newer mercurial
12 12
13 13 $ hg init oldgraft
14 14 $ cd oldgraft
15 15 $ for ch in a b c; do echo foo > $ch; hg add $ch; hg ci -Aqm "added "$ch; done;
16 16 $ hg log -GT "{rev}:{node|short} {desc}\n"
17 17 @ 2:8be98ac1a569 added c
18 18 |
19 19 o 1:80e6d2c47cfe added b
20 20 |
21 21 o 0:f7ad41964313 added a
22 22
23 23 $ hg up 0
24 24 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
25 25 $ echo bar > b
26 26 $ hg add b
27 27 $ hg ci -m "bar to b"
28 28 created new head
29 29 $ hg graft -r 1 -r 2
30 30 grafting 1:80e6d2c47cfe "added b"
31 31 merging b
32 32 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
33 33 abort: unresolved conflicts, can't continue
34 34 (use 'hg resolve' and 'hg graft --continue')
35 35 [255]
36 36
37 37 Writing the nodes in old format to graftstate
38 38
39 39 $ hg log -r 1 -r 2 -T '{node}\n' > .hg/graftstate
40 40 $ echo foo > b
41 41 $ hg resolve -m
42 42 (no more unresolved files)
43 43 continue: hg graft --continue
44 44 $ hg graft --continue
45 45 grafting 1:80e6d2c47cfe "added b"
46 46 grafting 2:8be98ac1a569 "added c"
47 47
48 48 Testing that --user is preserved during conflicts and value is reused while
49 49 running `hg graft --continue`
50 50
51 51 $ hg log -G
52 52 @ changeset: 5:711e9fa999f1
53 53 | tag: tip
54 54 | user: test
55 55 | date: Thu Jan 01 00:00:00 1970 +0000
56 56 | summary: added c
57 57 |
58 58 o changeset: 4:e5ad7353b408
59 59 | user: test
60 60 | date: Thu Jan 01 00:00:00 1970 +0000
61 61 | summary: added b
62 62 |
63 63 o changeset: 3:9e887f7a939c
64 64 | parent: 0:f7ad41964313
65 65 | user: test
66 66 | date: Thu Jan 01 00:00:00 1970 +0000
67 67 | summary: bar to b
68 68 |
69 69 | o changeset: 2:8be98ac1a569
70 70 | | user: test
71 71 | | date: Thu Jan 01 00:00:00 1970 +0000
72 72 | | summary: added c
73 73 | |
74 74 | o changeset: 1:80e6d2c47cfe
75 75 |/ user: test
76 76 | date: Thu Jan 01 00:00:00 1970 +0000
77 77 | summary: added b
78 78 |
79 79 o changeset: 0:f7ad41964313
80 80 user: test
81 81 date: Thu Jan 01 00:00:00 1970 +0000
82 82 summary: added a
83 83
84 84
85 85 $ hg up '.^^'
86 86 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
87 87
88 88 $ hg graft -r 1 -r 2 --user batman
89 89 grafting 1:80e6d2c47cfe "added b"
90 90 merging b
91 91 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
92 92 abort: unresolved conflicts, can't continue
93 93 (use 'hg resolve' and 'hg graft --continue')
94 94 [255]
95 95
96 96 $ echo wat > b
97 97 $ hg resolve -m
98 98 (no more unresolved files)
99 99 continue: hg graft --continue
100 100
101 101 $ hg graft --continue
102 102 grafting 1:80e6d2c47cfe "added b"
103 103 grafting 2:8be98ac1a569 "added c"
104 104
105 105 $ hg log -Gr 3::
106 106 @ changeset: 7:11a36ffaacf2
107 107 | tag: tip
108 108 | user: batman
109 109 | date: Thu Jan 01 00:00:00 1970 +0000
110 110 | summary: added c
111 111 |
112 112 o changeset: 6:76803afc6511
113 113 | parent: 3:9e887f7a939c
114 114 | user: batman
115 115 | date: Thu Jan 01 00:00:00 1970 +0000
116 116 | summary: added b
117 117 |
118 118 | o changeset: 5:711e9fa999f1
119 119 | | user: test
120 120 | | date: Thu Jan 01 00:00:00 1970 +0000
121 121 | | summary: added c
122 122 | |
123 123 | o changeset: 4:e5ad7353b408
124 124 |/ user: test
125 125 | date: Thu Jan 01 00:00:00 1970 +0000
126 126 | summary: added b
127 127 |
128 128 o changeset: 3:9e887f7a939c
129 129 | parent: 0:f7ad41964313
130 130 ~ user: test
131 131 date: Thu Jan 01 00:00:00 1970 +0000
132 132 summary: bar to b
133 133
134 134 Test that --date is preserved and reused in `hg graft --continue`
135 135
136 136 $ hg up '.^^'
137 137 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
138 138 $ hg graft -r 1 -r 2 --date '1234560000 120'
139 139 grafting 1:80e6d2c47cfe "added b"
140 140 merging b
141 141 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
142 142 abort: unresolved conflicts, can't continue
143 143 (use 'hg resolve' and 'hg graft --continue')
144 144 [255]
145 145
146 146 $ echo foobar > b
147 147 $ hg resolve -m
148 148 (no more unresolved files)
149 149 continue: hg graft --continue
150 150 $ hg graft --continue
151 151 grafting 1:80e6d2c47cfe "added b"
152 152 grafting 2:8be98ac1a569 "added c"
153 153
154 154 $ hg log -Gr '.^^::.'
155 155 @ changeset: 9:1896b76e007a
156 156 | tag: tip
157 157 | user: test
158 158 | date: Fri Feb 13 21:18:00 2009 -0002
159 159 | summary: added c
160 160 |
161 161 o changeset: 8:ce2b4f1632af
162 162 | parent: 3:9e887f7a939c
163 163 | user: test
164 164 | date: Fri Feb 13 21:18:00 2009 -0002
165 165 | summary: added b
166 166 |
167 167 o changeset: 3:9e887f7a939c
168 168 | parent: 0:f7ad41964313
169 169 ~ user: test
170 170 date: Thu Jan 01 00:00:00 1970 +0000
171 171 summary: bar to b
172 172
173 173 Test that --log is preserved and reused in `hg graft --continue`
174 174
175 175 $ hg up '.^^'
176 176 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
177 177 $ hg graft -r 1 -r 2 --log
178 178 grafting 1:80e6d2c47cfe "added b"
179 179 merging b
180 180 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
181 181 abort: unresolved conflicts, can't continue
182 182 (use 'hg resolve' and 'hg graft --continue')
183 183 [255]
184 184
185 185 $ echo foobar > b
186 186 $ hg resolve -m
187 187 (no more unresolved files)
188 188 continue: hg graft --continue
189 189
190 190 $ hg graft --continue
191 191 grafting 1:80e6d2c47cfe "added b"
192 192 grafting 2:8be98ac1a569 "added c"
193 193
194 194 $ hg log -GT "{rev}:{node|short} {desc}" -r '.^^::.'
195 195 @ 11:30c1050a58b2 added c
196 196 | (grafted from 8be98ac1a56990c2d9ca6861041b8390af7bd6f3)
197 197 o 10:ec7eda2313e2 added b
198 198 | (grafted from 80e6d2c47cfe5b3185519568327a17a061c7efb6)
199 199 o 3:9e887f7a939c bar to b
200 200 |
201 201 ~
202 202
203 203 $ cd ..
204 204
205 205 Testing the --stop flag of `hg graft` which stops the interrupted graft
206 206
207 207 $ hg init stopgraft
208 208 $ cd stopgraft
209 209 $ for ch in a b c d; do echo $ch > $ch; hg add $ch; hg ci -Aqm "added "$ch; done;
210 210
211 211 $ hg log -G
212 212 @ changeset: 3:9150fe93bec6
213 213 | tag: tip
214 214 | user: test
215 215 | date: Thu Jan 01 00:00:00 1970 +0000
216 216 | summary: added d
217 217 |
218 218 o changeset: 2:155349b645be
219 219 | user: test
220 220 | date: Thu Jan 01 00:00:00 1970 +0000
221 221 | summary: added c
222 222 |
223 223 o changeset: 1:5f6d8a4bf34a
224 224 | user: test
225 225 | date: Thu Jan 01 00:00:00 1970 +0000
226 226 | summary: added b
227 227 |
228 228 o changeset: 0:9092f1db7931
229 229 user: test
230 230 date: Thu Jan 01 00:00:00 1970 +0000
231 231 summary: added a
232 232
233 233 $ hg up '.^^'
234 234 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
235 235
236 236 $ echo foo > d
237 237 $ hg ci -Aqm "added foo to d"
238 238
239 239 $ hg graft --stop
240 240 abort: no interrupted graft found
241 241 [255]
242 242
243 243 $ hg graft -r 3
244 244 grafting 3:9150fe93bec6 "added d"
245 245 merging d
246 246 warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
247 247 abort: unresolved conflicts, can't continue
248 248 (use 'hg resolve' and 'hg graft --continue')
249 249 [255]
250 250
251 251 $ hg graft --stop --continue
252 252 abort: cannot use '--continue' and '--stop' together
253 253 [255]
254 254
255 255 $ hg graft --stop -U
256 256 abort: cannot specify any other flag with '--stop'
257 257 [255]
258 258 $ hg graft --stop --rev 4
259 259 abort: cannot specify any other flag with '--stop'
260 260 [255]
261 261 $ hg graft --stop --log
262 262 abort: cannot specify any other flag with '--stop'
263 263 [255]
264 264
265 265 $ hg graft --stop
266 266 stopped the interrupted graft
267 267 working directory is now at a0deacecd59d
268 268
269 269 $ hg diff
270 270
271 271 $ hg log -Gr '.'
272 272 @ changeset: 4:a0deacecd59d
273 273 | tag: tip
274 274 ~ parent: 1:5f6d8a4bf34a
275 275 user: test
276 276 date: Thu Jan 01 00:00:00 1970 +0000
277 277 summary: added foo to d
278 278
279 279 $ hg graft -r 2 -r 3
280 280 grafting 2:155349b645be "added c"
281 281 grafting 3:9150fe93bec6 "added d"
282 282 merging d
283 283 warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
284 284 abort: unresolved conflicts, can't continue
285 285 (use 'hg resolve' and 'hg graft --continue')
286 286 [255]
287 287
288 288 $ hg graft --stop
289 289 stopped the interrupted graft
290 290 working directory is now at 75b447541a9e
291 291
292 292 $ hg diff
293 293
294 294 $ hg log -G -T "{rev}:{node|short} {desc}"
295 295 @ 5:75b447541a9e added c
296 296 |
297 297 o 4:a0deacecd59d added foo to d
298 298 |
299 299 | o 3:9150fe93bec6 added d
300 300 | |
301 301 | o 2:155349b645be added c
302 302 |/
303 303 o 1:5f6d8a4bf34a added b
304 304 |
305 305 o 0:9092f1db7931 added a
306 306
307 307 $ cd ..
308 308
309 309 Testing the --abort flag for `hg graft` which aborts and rollback to state
310 310 before the graft
311 311
312 312 $ hg init abortgraft
313 313 $ cd abortgraft
314 314 $ for ch in a b c d; do echo $ch > $ch; hg add $ch; hg ci -Aqm "added "$ch; done;
315 315
316 316 $ hg up '.^^'
317 317 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
318 318
319 319 $ echo x > x
320 320 $ hg ci -Aqm "added x"
321 321 $ hg up '.^'
322 322 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
323 323 $ echo foo > c
324 324 $ hg ci -Aqm "added foo to c"
325 325
326 326 $ hg log -GT "{rev}:{node|short} {desc}"
327 327 @ 5:36b793615f78 added foo to c
328 328 |
329 329 | o 4:863a25e1a9ea added x
330 330 |/
331 331 | o 3:9150fe93bec6 added d
332 332 | |
333 333 | o 2:155349b645be added c
334 334 |/
335 335 o 1:5f6d8a4bf34a added b
336 336 |
337 337 o 0:9092f1db7931 added a
338 338
339 339 $ hg up 9150fe93bec6
340 340 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
341 341
342 342 $ hg abort
343 343 abort: no interrupted graft to abort (abortflag !)
344 344 abort: no operation in progress (abortcommand !)
345 345 [255]
346 346
347 347 when stripping is required
348 348 $ hg graft -r 4 -r 5
349 349 grafting 4:863a25e1a9ea "added x"
350 350 grafting 5:36b793615f78 "added foo to c" (tip)
351 351 merging c
352 352 warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
353 353 abort: unresolved conflicts, can't continue
354 354 (use 'hg resolve' and 'hg graft --continue')
355 355 [255]
356 356
357 357 $ hg graft --continue --abort
358 358 abort: cannot use '--continue' and '--abort' together
359 359 [255]
360 360
361 361 $ hg graft --abort --stop
362 362 abort: cannot use '--abort' and '--stop' together
363 363 [255]
364 364
365 365 $ hg graft --abort --currentuser
366 366 abort: cannot specify any other flag with '--abort'
367 367 [255]
368 368
369 369 $ hg graft --abort --edit
370 370 abort: cannot specify any other flag with '--abort'
371 371 [255]
372 372
373 373 #if abortcommand
374 374 when in dry-run mode
375 375 $ hg abort --dry-run
376 376 graft in progress, will be aborted
377 377 #endif
378 378
379 379 $ hg abort
380 380 graft aborted
381 381 working directory is now at 9150fe93bec6
382 382 $ hg log -GT "{rev}:{node|short} {desc}"
383 383 o 5:36b793615f78 added foo to c
384 384 |
385 385 | o 4:863a25e1a9ea added x
386 386 |/
387 387 | @ 3:9150fe93bec6 added d
388 388 | |
389 389 | o 2:155349b645be added c
390 390 |/
391 391 o 1:5f6d8a4bf34a added b
392 392 |
393 393 o 0:9092f1db7931 added a
394 394
395 395 when stripping is not required
396 396 $ hg graft -r 5
397 397 grafting 5:36b793615f78 "added foo to c" (tip)
398 398 merging c
399 399 warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
400 400 abort: unresolved conflicts, can't continue
401 401 (use 'hg resolve' and 'hg graft --continue')
402 402 [255]
403 403
404 404 $ hg abort
405 405 graft aborted
406 406 working directory is now at 9150fe93bec6
407 407 $ hg log -GT "{rev}:{node|short} {desc}"
408 408 o 5:36b793615f78 added foo to c
409 409 |
410 410 | o 4:863a25e1a9ea added x
411 411 |/
412 412 | @ 3:9150fe93bec6 added d
413 413 | |
414 414 | o 2:155349b645be added c
415 415 |/
416 416 o 1:5f6d8a4bf34a added b
417 417 |
418 418 o 0:9092f1db7931 added a
419 419
420 420 when some of the changesets became public
421 421
422 422 $ hg graft -r 4 -r 5
423 423 grafting 4:863a25e1a9ea "added x"
424 424 grafting 5:36b793615f78 "added foo to c" (tip)
425 425 merging c
426 426 warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
427 427 abort: unresolved conflicts, can't continue
428 428 (use 'hg resolve' and 'hg graft --continue')
429 429 [255]
430 430
431 431 $ hg log -GT "{rev}:{node|short} {desc}"
432 432 @ 6:6ec71c037d94 added x
433 433 |
434 | o 5:36b793615f78 added foo to c
434 | % 5:36b793615f78 added foo to c
435 435 | |
436 436 | | o 4:863a25e1a9ea added x
437 437 | |/
438 438 o | 3:9150fe93bec6 added d
439 439 | |
440 440 o | 2:155349b645be added c
441 441 |/
442 442 o 1:5f6d8a4bf34a added b
443 443 |
444 444 o 0:9092f1db7931 added a
445 445
446 446 $ hg phase -r 6 --public
447 447
448 448 $ hg abort
449 449 cannot clean up public changesets 6ec71c037d94
450 450 graft aborted
451 451 working directory is now at 6ec71c037d94
452 452
453 453 when we created new changesets on top of existing one
454 454
455 455 $ hg up '.^^'
456 456 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
457 457 $ echo y > y
458 458 $ hg ci -Aqm "added y"
459 459 $ echo z > z
460 460 $ hg ci -Aqm "added z"
461 461
462 462 $ hg up 3
463 463 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
464 464 $ hg log -GT "{rev}:{node|short} {desc}"
465 465 o 8:637f9e9bbfd4 added z
466 466 |
467 467 o 7:123221671fd4 added y
468 468 |
469 469 | o 6:6ec71c037d94 added x
470 470 | |
471 471 | | o 5:36b793615f78 added foo to c
472 472 | | |
473 473 | | | o 4:863a25e1a9ea added x
474 474 | | |/
475 475 | @ | 3:9150fe93bec6 added d
476 476 |/ /
477 477 o / 2:155349b645be added c
478 478 |/
479 479 o 1:5f6d8a4bf34a added b
480 480 |
481 481 o 0:9092f1db7931 added a
482 482
483 483 $ hg graft -r 8 -r 7 -r 5
484 484 grafting 8:637f9e9bbfd4 "added z" (tip)
485 485 grafting 7:123221671fd4 "added y"
486 486 grafting 5:36b793615f78 "added foo to c"
487 487 merging c
488 488 warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
489 489 abort: unresolved conflicts, can't continue
490 490 (use 'hg resolve' and 'hg graft --continue')
491 491 [255]
492 492
493 493 $ cd ..
494 494 $ hg init pullrepo
495 495 $ cd pullrepo
496 496 $ cat >> .hg/hgrc <<EOF
497 497 > [phases]
498 498 > publish=False
499 499 > EOF
500 500 $ hg pull ../abortgraft --config phases.publish=False
501 501 pulling from ../abortgraft
502 502 requesting all changes
503 503 adding changesets
504 504 adding manifests
505 505 adding file changes
506 506 added 11 changesets with 9 changes to 8 files (+4 heads)
507 507 new changesets 9092f1db7931:6b98ff0062dd (6 drafts)
508 508 (run 'hg heads' to see heads, 'hg merge' to merge)
509 509 $ hg up 9
510 510 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
511 511 $ echo w > w
512 512 $ hg ci -Aqm "added w" --config phases.publish=False
513 513
514 514 $ cd ../abortgraft
515 515 $ hg pull ../pullrepo
516 516 pulling from ../pullrepo
517 517 searching for changes
518 518 adding changesets
519 519 adding manifests
520 520 adding file changes
521 521 added 1 changesets with 1 changes to 1 files (+1 heads)
522 522 new changesets 311dfc6cf3bf (1 drafts)
523 523 (run 'hg heads .' to see heads, 'hg merge' to merge)
524 524
525 525 $ hg abort
526 526 new changesets detected on destination branch, can't strip
527 527 graft aborted
528 528 working directory is now at 6b98ff0062dd
529 529
530 530 $ cd ..
531 531
532 532 ============================
533 533 Testing --no-commit option:|
534 534 ============================
535 535
536 536 $ hg init nocommit
537 537 $ cd nocommit
538 538 $ echo a > a
539 539 $ hg ci -qAma
540 540 $ echo b > b
541 541 $ hg ci -qAmb
542 542 $ hg up -q 0
543 543 $ echo c > c
544 544 $ hg ci -qAmc
545 545 $ hg log -GT "{rev}:{node|short} {desc}\n"
546 546 @ 2:d36c0562f908 c
547 547 |
548 548 | o 1:d2ae7f538514 b
549 549 |/
550 550 o 0:cb9a9f314b8b a
551 551
552 552
553 553 Check reporting when --no-commit used with non-applicable options:
554 554
555 555 $ hg graft 1 --no-commit -e
556 556 abort: cannot specify --no-commit and --edit together
557 557 [255]
558 558
559 559 $ hg graft 1 --no-commit --log
560 560 abort: cannot specify --no-commit and --log together
561 561 [255]
562 562
563 563 $ hg graft 1 --no-commit -D
564 564 abort: cannot specify --no-commit and --currentdate together
565 565 [255]
566 566
567 567 Test --no-commit is working:
568 568 $ hg graft 1 --no-commit
569 569 grafting 1:d2ae7f538514 "b"
570 570
571 571 $ hg log -GT "{rev}:{node|short} {desc}\n"
572 572 @ 2:d36c0562f908 c
573 573 |
574 574 | o 1:d2ae7f538514 b
575 575 |/
576 576 o 0:cb9a9f314b8b a
577 577
578 578
579 579 $ hg diff
580 580 diff -r d36c0562f908 b
581 581 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
582 582 +++ b/b Thu Jan 01 00:00:00 1970 +0000
583 583 @@ -0,0 +1,1 @@
584 584 +b
585 585
586 586 Prepare wrdir to check --no-commit is resepected after --continue:
587 587
588 588 $ hg up -qC
589 589 $ echo A>a
590 590 $ hg ci -qm "A in file a"
591 591 $ hg up -q 1
592 592 $ echo B>a
593 593 $ hg ci -qm "B in file a"
594 594 $ hg log -GT "{rev}:{node|short} {desc}\n"
595 595 @ 4:2aa9ad1006ff B in file a
596 596 |
597 597 | o 3:09e253b87e17 A in file a
598 598 | |
599 599 | o 2:d36c0562f908 c
600 600 | |
601 601 o | 1:d2ae7f538514 b
602 602 |/
603 603 o 0:cb9a9f314b8b a
604 604
605 605
606 606 $ hg graft 3 --no-commit
607 607 grafting 3:09e253b87e17 "A in file a"
608 608 merging a
609 609 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
610 610 abort: unresolved conflicts, can't continue
611 611 (use 'hg resolve' and 'hg graft --continue')
612 612 [255]
613 613
614 614 Resolve conflict:
615 615 $ echo A>a
616 616 $ hg resolve --mark
617 617 (no more unresolved files)
618 618 continue: hg graft --continue
619 619
620 620 $ hg graft --continue
621 621 grafting 3:09e253b87e17 "A in file a"
622 622 $ hg log -GT "{rev}:{node|short} {desc}\n"
623 623 @ 4:2aa9ad1006ff B in file a
624 624 |
625 | o 3:09e253b87e17 A in file a
625 | % 3:09e253b87e17 A in file a
626 626 | |
627 627 | o 2:d36c0562f908 c
628 628 | |
629 629 o | 1:d2ae7f538514 b
630 630 |/
631 631 o 0:cb9a9f314b8b a
632 632
633 633 $ hg diff
634 634 diff -r 2aa9ad1006ff a
635 635 --- a/a Thu Jan 01 00:00:00 1970 +0000
636 636 +++ b/a Thu Jan 01 00:00:00 1970 +0000
637 637 @@ -1,1 +1,1 @@
638 638 -B
639 639 +A
640 640
641 641 $ hg up -qC
642 642
643 643 Check --no-commit is resepected when passed with --continue:
644 644
645 645 $ hg graft 3
646 646 grafting 3:09e253b87e17 "A in file a"
647 647 merging a
648 648 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
649 649 abort: unresolved conflicts, can't continue
650 650 (use 'hg resolve' and 'hg graft --continue')
651 651 [255]
652 652
653 653 Resolve conflict:
654 654 $ echo A>a
655 655 $ hg resolve --mark
656 656 (no more unresolved files)
657 657 continue: hg graft --continue
658 658
659 659 $ hg graft --continue --no-commit
660 660 grafting 3:09e253b87e17 "A in file a"
661 661 $ hg diff
662 662 diff -r 2aa9ad1006ff a
663 663 --- a/a Thu Jan 01 00:00:00 1970 +0000
664 664 +++ b/a Thu Jan 01 00:00:00 1970 +0000
665 665 @@ -1,1 +1,1 @@
666 666 -B
667 667 +A
668 668
669 669 $ hg log -GT "{rev}:{node|short} {desc}\n"
670 670 @ 4:2aa9ad1006ff B in file a
671 671 |
672 | o 3:09e253b87e17 A in file a
672 | % 3:09e253b87e17 A in file a
673 673 | |
674 674 | o 2:d36c0562f908 c
675 675 | |
676 676 o | 1:d2ae7f538514 b
677 677 |/
678 678 o 0:cb9a9f314b8b a
679 679
680 680 $ hg up -qC
681 681
682 682 Test --no-commit when graft multiple revisions:
683 683 When there is conflict:
684 684 $ hg graft -r "2::3" --no-commit
685 685 grafting 2:d36c0562f908 "c"
686 686 grafting 3:09e253b87e17 "A in file a"
687 687 merging a
688 688 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
689 689 abort: unresolved conflicts, can't continue
690 690 (use 'hg resolve' and 'hg graft --continue')
691 691 [255]
692 692
693 693 $ echo A>a
694 694 $ hg resolve --mark
695 695 (no more unresolved files)
696 696 continue: hg graft --continue
697 697 $ hg graft --continue
698 698 grafting 3:09e253b87e17 "A in file a"
699 699 $ hg diff
700 700 diff -r 2aa9ad1006ff a
701 701 --- a/a Thu Jan 01 00:00:00 1970 +0000
702 702 +++ b/a Thu Jan 01 00:00:00 1970 +0000
703 703 @@ -1,1 +1,1 @@
704 704 -B
705 705 +A
706 706 diff -r 2aa9ad1006ff c
707 707 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
708 708 +++ b/c Thu Jan 01 00:00:00 1970 +0000
709 709 @@ -0,0 +1,1 @@
710 710 +c
711 711
712 712 $ hg log -GT "{rev}:{node|short} {desc}\n"
713 713 @ 4:2aa9ad1006ff B in file a
714 714 |
715 | o 3:09e253b87e17 A in file a
715 | % 3:09e253b87e17 A in file a
716 716 | |
717 717 | o 2:d36c0562f908 c
718 718 | |
719 719 o | 1:d2ae7f538514 b
720 720 |/
721 721 o 0:cb9a9f314b8b a
722 722
723 723 $ hg up -qC
724 724
725 725 When there is no conflict:
726 726 $ echo d>d
727 727 $ hg add d -q
728 728 $ hg ci -qmd
729 729 $ hg up 3 -q
730 730 $ hg log -GT "{rev}:{node|short} {desc}\n"
731 731 o 5:baefa8927fc0 d
732 732 |
733 733 o 4:2aa9ad1006ff B in file a
734 734 |
735 735 | @ 3:09e253b87e17 A in file a
736 736 | |
737 737 | o 2:d36c0562f908 c
738 738 | |
739 739 o | 1:d2ae7f538514 b
740 740 |/
741 741 o 0:cb9a9f314b8b a
742 742
743 743
744 744 $ hg graft -r 1 -r 5 --no-commit
745 745 grafting 1:d2ae7f538514 "b"
746 746 grafting 5:baefa8927fc0 "d" (tip)
747 747 $ hg diff
748 748 diff -r 09e253b87e17 b
749 749 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
750 750 +++ b/b Thu Jan 01 00:00:00 1970 +0000
751 751 @@ -0,0 +1,1 @@
752 752 +b
753 753 diff -r 09e253b87e17 d
754 754 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
755 755 +++ b/d Thu Jan 01 00:00:00 1970 +0000
756 756 @@ -0,0 +1,1 @@
757 757 +d
758 758 $ hg log -GT "{rev}:{node|short} {desc}\n"
759 759 o 5:baefa8927fc0 d
760 760 |
761 761 o 4:2aa9ad1006ff B in file a
762 762 |
763 763 | @ 3:09e253b87e17 A in file a
764 764 | |
765 765 | o 2:d36c0562f908 c
766 766 | |
767 767 o | 1:d2ae7f538514 b
768 768 |/
769 769 o 0:cb9a9f314b8b a
770 770
771 771 $ cd ..
@@ -1,776 +1,776 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 > mq=
5 5 > drawdag=$TESTDIR/drawdag.py
6 6 >
7 7 > [phases]
8 8 > publish=False
9 9 >
10 10 > [alias]
11 11 > tglog = log -G --template "{rev}: {node|short} '{desc}' {branches}\n"
12 12 > tglogp = log -G --template "{rev}: {node|short} {phase} '{desc}' {branches}\n"
13 13 > EOF
14 14
15 15 Highest phase of source commits is used:
16 16
17 17 $ hg init phase
18 18 $ cd phase
19 19 $ hg debugdrawdag << 'EOF'
20 20 > D
21 21 > |
22 22 > F C
23 23 > | |
24 24 > E B
25 25 > |/
26 26 > A
27 27 > EOF
28 28
29 29 $ hg phase --force --secret D
30 30
31 31 $ cat > $TESTTMP/editor.sh <<EOF
32 32 > echo "==== before editing"
33 33 > cat \$1
34 34 > echo "===="
35 35 > echo "edited manually" >> \$1
36 36 > EOF
37 37 $ HGEDITOR="sh $TESTTMP/editor.sh" hg rebase --collapse --keepbranches -e --source B --dest F
38 38 rebasing 1:112478962961 "B" (B)
39 39 rebasing 3:26805aba1e60 "C" (C)
40 40 rebasing 5:f585351a92f8 "D" (D tip)
41 41 ==== before editing
42 42 Collapsed revision
43 43 * B
44 44 * C
45 45 * D
46 46
47 47
48 48 HG: Enter commit message. Lines beginning with 'HG:' are removed.
49 49 HG: Leave message empty to abort commit.
50 50 HG: --
51 51 HG: user: test
52 52 HG: branch 'default'
53 53 HG: added B
54 54 HG: added C
55 55 HG: added D
56 56 ====
57 57 saved backup bundle to $TESTTMP/phase/.hg/strip-backup/112478962961-cb2a9b47-rebase.hg
58 58
59 59 $ hg tglogp
60 60 o 3: 92fa5f5fe108 secret 'Collapsed revision
61 61 | * B
62 62 | * C
63 63 | * D
64 64 |
65 65 |
66 66 | edited manually'
67 67 o 2: 64a8289d2492 draft 'F'
68 68 |
69 69 o 1: 7fb047a69f22 draft 'E'
70 70 |
71 71 o 0: 426bada5c675 draft 'A'
72 72
73 73 $ hg manifest --rev tip
74 74 A
75 75 B
76 76 C
77 77 D
78 78 E
79 79 F
80 80
81 81 $ cd ..
82 82
83 83
84 84 Merge gets linearized:
85 85
86 86 $ hg init linearized-merge
87 87 $ cd linearized-merge
88 88
89 89 $ hg debugdrawdag << 'EOF'
90 90 > F D
91 91 > |/|
92 92 > C B
93 93 > |/
94 94 > A
95 95 > EOF
96 96
97 97 $ hg phase --force --secret D
98 98 $ hg rebase --source B --collapse --dest F
99 99 rebasing 1:112478962961 "B" (B)
100 100 rebasing 3:4e4f9194f9f1 "D" (D)
101 101 saved backup bundle to $TESTTMP/linearized-merge/.hg/strip-backup/112478962961-e389075b-rebase.hg
102 102
103 103 $ hg tglog
104 104 o 3: 5bdc08b7da2b 'Collapsed revision
105 105 | * B
106 106 | * D'
107 107 o 2: afc707c82df0 'F'
108 108 |
109 109 o 1: dc0947a82db8 'C'
110 110 |
111 111 o 0: 426bada5c675 'A'
112 112
113 113 $ hg manifest --rev tip
114 114 A
115 115 B
116 116 C
117 117 F
118 118
119 119 $ cd ..
120 120
121 121 Custom message:
122 122
123 123 $ hg init message
124 124 $ cd message
125 125
126 126 $ hg debugdrawdag << 'EOF'
127 127 > C
128 128 > |
129 129 > D B
130 130 > |/
131 131 > A
132 132 > EOF
133 133
134 134
135 135 $ hg rebase --base B -m 'custom message'
136 136 abort: message can only be specified with collapse
137 137 [255]
138 138
139 139 $ cat > $TESTTMP/checkeditform.sh <<EOF
140 140 > env | grep HGEDITFORM
141 141 > true
142 142 > EOF
143 143 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg rebase --source B --collapse -m 'custom message' -e --dest D
144 144 rebasing 1:112478962961 "B" (B)
145 145 rebasing 3:26805aba1e60 "C" (C tip)
146 146 HGEDITFORM=rebase.collapse
147 147 saved backup bundle to $TESTTMP/message/.hg/strip-backup/112478962961-f4131707-rebase.hg
148 148
149 149 $ hg tglog
150 150 o 2: 2f197b9a08f3 'custom message'
151 151 |
152 152 o 1: b18e25de2cf5 'D'
153 153 |
154 154 o 0: 426bada5c675 'A'
155 155
156 156 $ hg manifest --rev tip
157 157 A
158 158 B
159 159 C
160 160 D
161 161
162 162 $ cd ..
163 163
164 164 Rebase and collapse - more than one external (fail):
165 165
166 166 $ hg init multiple-external-parents
167 167 $ cd multiple-external-parents
168 168
169 169 $ hg debugdrawdag << 'EOF'
170 170 > G
171 171 > |\
172 172 > | F
173 173 > | |
174 174 > D E
175 175 > |\|
176 176 > H C B
177 177 > \|/
178 178 > A
179 179 > EOF
180 180
181 181 $ hg rebase -s C --dest H --collapse
182 182 abort: unable to collapse on top of 3, there is more than one external parent: 1, 6
183 183 [255]
184 184
185 185 Rebase and collapse - E onto H:
186 186
187 187 $ hg rebase -s E --dest I --collapse # root (E) is not a merge
188 188 abort: unknown revision 'I'!
189 189 [255]
190 190
191 191 $ hg tglog
192 192 o 7: 64e264db77f0 'G'
193 193 |\
194 194 | o 6: 11abe3fb10b8 'F'
195 195 | |
196 196 | o 5: 49cb92066bfd 'E'
197 197 | |
198 198 o | 4: 4e4f9194f9f1 'D'
199 199 |\|
200 200 | | o 3: 575c4b5ec114 'H'
201 201 | | |
202 202 o---+ 2: dc0947a82db8 'C'
203 203 / /
204 204 o / 1: 112478962961 'B'
205 205 |/
206 206 o 0: 426bada5c675 'A'
207 207
208 208 $ hg manifest --rev tip
209 209 A
210 210 B
211 211 C
212 212 E
213 213 F
214 214
215 215 $ cd ..
216 216
217 217
218 218
219 219
220 220 Test that branchheads cache is updated correctly when doing a strip in which
221 221 the parent of the ancestor node to be stripped does not become a head and also,
222 222 the parent of a node that is a child of the node stripped becomes a head (node
223 223 3). The code is now much simpler and we could just test a simpler scenario
224 224 We keep it the test this way in case new complexity is injected.
225 225
226 226 Create repo b:
227 227
228 228 $ hg init branch-heads
229 229 $ cd branch-heads
230 230
231 231 $ hg debugdrawdag << 'EOF'
232 232 > G
233 233 > |\
234 234 > | F
235 235 > | |
236 236 > D E
237 237 > |\|
238 238 > H C B
239 239 > \|/
240 240 > A
241 241 > EOF
242 242
243 243 $ hg heads --template="{rev}:{node} {branch}\n"
244 244 7:64e264db77f061f16d9132b70c5a58e2461fb630 default
245 245 3:575c4b5ec114d64b681d33f8792853568bfb2b2c default
246 246
247 247 $ cat $TESTTMP/branch-heads/.hg/cache/branch2-served
248 248 64e264db77f061f16d9132b70c5a58e2461fb630 7
249 249 575c4b5ec114d64b681d33f8792853568bfb2b2c o default
250 250 64e264db77f061f16d9132b70c5a58e2461fb630 o default
251 251
252 252 $ hg strip 4
253 253 saved backup bundle to $TESTTMP/branch-heads/.hg/strip-backup/4e4f9194f9f1-5ec4b5e6-backup.hg
254 254
255 255 $ cat $TESTTMP/branch-heads/.hg/cache/branch2-served
256 256 11abe3fb10b8689b560681094b17fe161871d043 5
257 257 dc0947a82db884575bb76ea10ac97b08536bfa03 o default
258 258 575c4b5ec114d64b681d33f8792853568bfb2b2c o default
259 259 11abe3fb10b8689b560681094b17fe161871d043 o default
260 260
261 261 $ hg heads --template="{rev}:{node} {branch}\n"
262 262 5:11abe3fb10b8689b560681094b17fe161871d043 default
263 263 3:575c4b5ec114d64b681d33f8792853568bfb2b2c default
264 264 2:dc0947a82db884575bb76ea10ac97b08536bfa03 default
265 265
266 266 $ cd ..
267 267
268 268
269 269
270 270 Preserves external parent
271 271
272 272 $ hg init external-parent
273 273 $ cd external-parent
274 274
275 275 $ hg debugdrawdag << 'EOF'
276 276 > H
277 277 > |\
278 278 > | G
279 279 > | |
280 280 > | F # F/E = F\n
281 281 > | |
282 282 > D E # D/D = D\n
283 283 > |\|
284 284 > I C B
285 285 > \|/
286 286 > A
287 287 > EOF
288 288
289 289 $ hg rebase -s F --dest I --collapse # root (F) is not a merge
290 290 rebasing 6:c82b08f646f1 "F" (F)
291 291 file 'E' was deleted in local [dest] but was modified in other [source].
292 292 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
293 293 What do you want to do? u
294 294 unresolved conflicts (see hg resolve, then hg rebase --continue)
295 295 [1]
296 296
297 297 $ echo F > E
298 298 $ hg resolve -m
299 299 (no more unresolved files)
300 300 continue: hg rebase --continue
301 301 $ hg rebase -c
302 302 rebasing 6:c82b08f646f1 "F" (F)
303 303 rebasing 7:a6db7fa104e1 "G" (G)
304 304 rebasing 8:e1d201b72d91 "H" (H tip)
305 305 saved backup bundle to $TESTTMP/external-parent/.hg/strip-backup/c82b08f646f1-f2721fbf-rebase.hg
306 306
307 307 $ hg tglog
308 308 o 6: 681daa3e686d 'Collapsed revision
309 309 |\ * F
310 310 | | * G
311 311 | | * H'
312 312 | | o 5: 49cb92066bfd 'E'
313 313 | | |
314 314 | o | 4: 09143c0bf13e 'D'
315 315 | |\|
316 316 o | | 3: 08ebfeb61bac 'I'
317 317 | | |
318 318 | o | 2: dc0947a82db8 'C'
319 319 |/ /
320 320 | o 1: 112478962961 'B'
321 321 |/
322 322 o 0: 426bada5c675 'A'
323 323
324 324 $ hg manifest --rev tip
325 325 A
326 326 C
327 327 D
328 328 E
329 329 F
330 330 G
331 331 I
332 332
333 333 $ hg up tip -q
334 334 $ cat E
335 335 F
336 336
337 337 $ cd ..
338 338
339 339 Rebasing from multiple bases:
340 340
341 341 $ hg init multiple-bases
342 342 $ cd multiple-bases
343 343 $ hg debugdrawdag << 'EOF'
344 344 > C B
345 345 > D |/
346 346 > |/
347 347 > A
348 348 > EOF
349 349 $ hg rebase --collapse -r 'B+C' -d D
350 350 rebasing 1:fc2b737bb2e5 "B" (B)
351 351 rebasing 2:dc0947a82db8 "C" (C)
352 352 saved backup bundle to $TESTTMP/multiple-bases/.hg/strip-backup/dc0947a82db8-b0c1a7ea-rebase.hg
353 353 $ hg tglog
354 354 o 2: 2127ae44d291 'Collapsed revision
355 355 | * B
356 356 | * C'
357 357 o 1: b18e25de2cf5 'D'
358 358 |
359 359 o 0: 426bada5c675 'A'
360 360
361 361 $ cd ..
362 362
363 363 With non-contiguous commits:
364 364
365 365 $ hg init non-contiguous
366 366 $ cd non-contiguous
367 367 $ cat >> .hg/hgrc <<EOF
368 368 > [experimental]
369 369 > evolution=all
370 370 > EOF
371 371
372 372 $ hg debugdrawdag << 'EOF'
373 373 > F
374 374 > |
375 375 > E
376 376 > |
377 377 > D
378 378 > |
379 379 > C
380 380 > |
381 381 > B G
382 382 > |/
383 383 > A
384 384 > EOF
385 385
386 386 BROKEN: should be allowed
387 387 $ hg rebase --collapse -r 'B+D+F' -d G
388 388 abort: unable to collapse on top of 2, there is more than one external parent: 3, 5
389 389 [255]
390 390 $ cd ..
391 391
392 392
393 393 $ hg init multiple-external-parents-2
394 394 $ cd multiple-external-parents-2
395 395 $ hg debugdrawdag << 'EOF'
396 396 > D G
397 397 > |\ /|
398 398 > B C E F
399 399 > \| |/
400 400 > \ H /
401 401 > \|/
402 402 > A
403 403 > EOF
404 404
405 405 $ hg rebase --collapse -d H -s 'B+F'
406 406 abort: unable to collapse on top of 5, there is more than one external parent: 1, 3
407 407 [255]
408 408 $ cd ..
409 409
410 410 With internal merge:
411 411
412 412 $ hg init internal-merge
413 413 $ cd internal-merge
414 414
415 415 $ hg debugdrawdag << 'EOF'
416 416 > E
417 417 > |\
418 418 > C D
419 419 > |/
420 420 > F B
421 421 > |/
422 422 > A
423 423 > EOF
424 424
425 425
426 426 $ hg rebase -s B --collapse --dest F
427 427 rebasing 1:112478962961 "B" (B)
428 428 rebasing 3:26805aba1e60 "C" (C)
429 429 rebasing 4:be0ef73c17ad "D" (D)
430 430 rebasing 5:02c4367d6973 "E" (E tip)
431 431 saved backup bundle to $TESTTMP/internal-merge/.hg/strip-backup/112478962961-1dfb057b-rebase.hg
432 432
433 433 $ hg tglog
434 434 o 2: c0512a1797b0 'Collapsed revision
435 435 | * B
436 436 | * C
437 437 | * D
438 438 | * E'
439 439 o 1: 8908a377a434 'F'
440 440 |
441 441 o 0: 426bada5c675 'A'
442 442
443 443 $ hg manifest --rev tip
444 444 A
445 445 B
446 446 C
447 447 D
448 448 F
449 449 $ cd ..
450 450
451 451 Interactions between collapse and keepbranches
452 452 $ hg init e
453 453 $ cd e
454 454 $ echo 'a' > a
455 455 $ hg ci -Am 'A'
456 456 adding a
457 457
458 458 $ hg branch 'one'
459 459 marked working directory as branch one
460 460 (branches are permanent and global, did you want a bookmark?)
461 461 $ echo 'b' > b
462 462 $ hg ci -Am 'B'
463 463 adding b
464 464
465 465 $ hg branch 'two'
466 466 marked working directory as branch two
467 467 $ echo 'c' > c
468 468 $ hg ci -Am 'C'
469 469 adding c
470 470
471 471 $ hg up -q 0
472 472 $ echo 'd' > d
473 473 $ hg ci -Am 'D'
474 474 adding d
475 475
476 476 $ hg tglog
477 477 @ 3: 41acb9dca9eb 'D'
478 478 |
479 479 | o 2: 8ac4a08debf1 'C' two
480 480 | |
481 481 | o 1: 1ba175478953 'B' one
482 482 |/
483 483 o 0: 1994f17a630e 'A'
484 484
485 485 $ hg rebase --keepbranches --collapse -s 1 -d 3
486 486 abort: cannot collapse multiple named branches
487 487 [255]
488 488
489 489 $ cd ..
490 490
491 491 Rebase, collapse and copies
492 492
493 493 $ hg init copies
494 494 $ cd copies
495 495 $ hg unbundle "$TESTDIR/bundles/renames.hg"
496 496 adding changesets
497 497 adding manifests
498 498 adding file changes
499 499 added 4 changesets with 11 changes to 7 files (+1 heads)
500 500 new changesets f447d5abf5ea:338e84e2e558 (4 drafts)
501 501 (run 'hg heads' to see heads, 'hg merge' to merge)
502 502 $ hg up -q tip
503 503 $ hg tglog
504 504 @ 3: 338e84e2e558 'move2'
505 505 |
506 506 o 2: 6e7340ee38c0 'move1'
507 507 |
508 508 | o 1: 1352765a01d4 'change'
509 509 |/
510 510 o 0: f447d5abf5ea 'add'
511 511
512 512 $ hg rebase --collapse -d 1
513 513 rebasing 2:6e7340ee38c0 "move1"
514 514 merging a and d to d
515 515 merging b and e to e
516 516 merging c and f to f
517 517 rebasing 3:338e84e2e558 "move2" (tip)
518 518 merging f and c to c
519 519 merging e and g to g
520 520 saved backup bundle to $TESTTMP/copies/.hg/strip-backup/6e7340ee38c0-ef8ef003-rebase.hg
521 521 $ hg st
522 522 $ hg st --copies --change tip
523 523 A d
524 524 a
525 525 A g
526 526 b
527 527 R b
528 528 $ hg up tip -q
529 529 $ cat c
530 530 c
531 531 c
532 532 $ cat d
533 533 a
534 534 a
535 535 $ cat g
536 536 b
537 537 b
538 538 $ hg log -r . --template "{file_copies}\n"
539 539 d (a)g (b)
540 540
541 541 Test collapsing a middle revision in-place
542 542
543 543 $ hg tglog
544 544 @ 2: 64b456429f67 'Collapsed revision
545 545 | * move1
546 546 | * move2'
547 547 o 1: 1352765a01d4 'change'
548 548 |
549 549 o 0: f447d5abf5ea 'add'
550 550
551 551 $ hg rebase --collapse -r 1 -d 0
552 552 abort: cannot rebase changeset with children
553 553 (use --keep to keep original changesets)
554 554 [255]
555 555
556 556 Test collapsing in place
557 557
558 558 $ hg rebase --collapse -b . -d 0
559 559 rebasing 1:1352765a01d4 "change"
560 560 rebasing 2:64b456429f67 "Collapsed revision" (tip)
561 561 saved backup bundle to $TESTTMP/copies/.hg/strip-backup/1352765a01d4-45a352ea-rebase.hg
562 562 $ hg st --change tip --copies
563 563 M a
564 564 M c
565 565 A d
566 566 a
567 567 A g
568 568 b
569 569 R b
570 570 $ hg up tip -q
571 571 $ cat a
572 572 a
573 573 a
574 574 $ cat c
575 575 c
576 576 c
577 577 $ cat d
578 578 a
579 579 a
580 580 $ cat g
581 581 b
582 582 b
583 583 $ cd ..
584 584
585 585
586 586 Test stripping a revision with another child
587 587
588 588 $ hg init f
589 589 $ cd f
590 590
591 591 $ hg debugdrawdag << 'EOF'
592 592 > C B
593 593 > |/
594 594 > A
595 595 > EOF
596 596
597 597 $ hg heads --template="{rev}:{node} {branch}: {desc}\n"
598 598 2:dc0947a82db884575bb76ea10ac97b08536bfa03 default: C
599 599 1:112478962961147124edd43549aedd1a335e44bf default: B
600 600
601 601 $ hg strip C
602 602 saved backup bundle to $TESTTMP/f/.hg/strip-backup/dc0947a82db8-d21b92a4-backup.hg
603 603
604 604 $ hg tglog
605 605 o 1: 112478962961 'B'
606 606 |
607 607 o 0: 426bada5c675 'A'
608 608
609 609
610 610
611 611 $ hg heads --template="{rev}:{node} {branch}: {desc}\n"
612 612 1:112478962961147124edd43549aedd1a335e44bf default: B
613 613
614 614 $ cd ..
615 615
616 616 Test collapsing changes that add then remove a file
617 617
618 618 $ hg init collapseaddremove
619 619 $ cd collapseaddremove
620 620
621 621 $ touch base
622 622 $ hg commit -Am base
623 623 adding base
624 624 $ touch a
625 625 $ hg commit -Am a
626 626 adding a
627 627 $ hg rm a
628 628 $ touch b
629 629 $ hg commit -Am b
630 630 adding b
631 631 $ hg book foo
632 632 $ hg rebase -d 0 -r "1::2" --collapse -m collapsed
633 633 rebasing 1:6d8d9f24eec3 "a"
634 634 rebasing 2:1cc73eca5ecc "b" (foo tip)
635 635 saved backup bundle to $TESTTMP/collapseaddremove/.hg/strip-backup/6d8d9f24eec3-77d3b6e2-rebase.hg
636 636 $ hg log -G --template "{rev}: '{desc}' {bookmarks}"
637 637 @ 1: 'collapsed' foo
638 638 |
639 639 o 0: 'base'
640 640
641 641 $ hg manifest --rev tip
642 642 b
643 643 base
644 644
645 645 $ cd ..
646 646
647 647 Test that rebase --collapse will remember message after
648 648 running into merge conflict and invoking rebase --continue.
649 649
650 650 $ hg init collapse_remember_message
651 651 $ cd collapse_remember_message
652 652 $ hg debugdrawdag << 'EOF'
653 653 > C B # B/A = B\n
654 654 > |/ # C/A = C\n
655 655 > A
656 656 > EOF
657 657 $ hg rebase --collapse -m "new message" -b B -d C
658 658 rebasing 1:81e5401e4d37 "B" (B)
659 659 merging A
660 660 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
661 661 unresolved conflicts (see hg resolve, then hg rebase --continue)
662 662 [1]
663 663 $ rm A.orig
664 664 $ hg resolve --mark A
665 665 (no more unresolved files)
666 666 continue: hg rebase --continue
667 667 $ hg rebase --continue
668 668 rebasing 1:81e5401e4d37 "B" (B)
669 669 saved backup bundle to $TESTTMP/collapse_remember_message/.hg/strip-backup/81e5401e4d37-96c3dd30-rebase.hg
670 670 $ hg log
671 671 changeset: 2:17186933e123
672 672 tag: tip
673 673 user: test
674 674 date: Thu Jan 01 00:00:00 1970 +0000
675 675 summary: new message
676 676
677 677 changeset: 1:043039e9df84
678 678 tag: C
679 679 user: test
680 680 date: Thu Jan 01 00:00:00 1970 +0000
681 681 summary: C
682 682
683 683 changeset: 0:426bada5c675
684 684 tag: A
685 685 user: test
686 686 date: Thu Jan 01 00:00:00 1970 +0000
687 687 summary: A
688 688
689 689 $ cd ..
690 690
691 691 Test aborted editor on final message
692 692
693 693 $ HGMERGE=:merge3
694 694 $ export HGMERGE
695 695 $ hg init aborted-editor
696 696 $ cd aborted-editor
697 697 $ hg debugdrawdag << 'EOF'
698 698 > C # D/A = D\n
699 699 > | # C/A = C\n
700 700 > B D # B/A = B\n
701 701 > |/ # A/A = A\n
702 702 > A
703 703 > EOF
704 704 $ hg rebase --collapse -t internal:merge3 -s B -d D
705 705 rebasing 1:f899f3910ce7 "B" (B)
706 706 merging A
707 707 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
708 708 unresolved conflicts (see hg resolve, then hg rebase --continue)
709 709 [1]
710 710 $ hg tglog
711 711 o 3: 63668d570d21 'C'
712 712 |
713 713 | @ 2: 82b8abf9c185 'D'
714 714 | |
715 715 @ | 1: f899f3910ce7 'B'
716 716 |/
717 717 o 0: 4a2df7238c3b 'A'
718 718
719 719 $ cat A
720 720 <<<<<<< dest: 82b8abf9c185 D - test: D
721 721 D
722 722 ||||||| base
723 723 A
724 724 =======
725 725 B
726 726 >>>>>>> source: f899f3910ce7 B - test: B
727 727 $ echo BC > A
728 728 $ hg resolve -m
729 729 (no more unresolved files)
730 730 continue: hg rebase --continue
731 731 $ hg rebase --continue
732 732 rebasing 1:f899f3910ce7 "B" (B)
733 733 rebasing 3:63668d570d21 "C" (C tip)
734 734 merging A
735 735 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
736 736 unresolved conflicts (see hg resolve, then hg rebase --continue)
737 737 [1]
738 738 $ hg tglog
739 739 @ 3: 63668d570d21 'C'
740 740 |
741 741 | @ 2: 82b8abf9c185 'D'
742 742 | |
743 743 o | 1: f899f3910ce7 'B'
744 744 |/
745 745 o 0: 4a2df7238c3b 'A'
746 746
747 747 $ cat A
748 748 <<<<<<< dest: 82b8abf9c185 D - test: D
749 749 BC
750 750 ||||||| base
751 751 B
752 752 =======
753 753 C
754 754 >>>>>>> source: 63668d570d21 C tip - test: C
755 755 $ echo BD > A
756 756 $ hg resolve -m
757 757 (no more unresolved files)
758 758 continue: hg rebase --continue
759 759 $ HGEDITOR=false hg rebase --continue --config ui.interactive=1
760 760 already rebased 1:f899f3910ce7 "B" (B) as 82b8abf9c185
761 761 rebasing 3:63668d570d21 "C" (C tip)
762 762 abort: edit failed: false exited with status 1
763 763 [255]
764 764 $ hg tglog
765 o 3: 63668d570d21 'C'
765 % 3: 63668d570d21 'C'
766 766 |
767 767 | @ 2: 82b8abf9c185 'D'
768 768 | |
769 769 o | 1: f899f3910ce7 'B'
770 770 |/
771 771 o 0: 4a2df7238c3b 'A'
772 772
773 773 $ hg rebase --continue
774 774 already rebased 1:f899f3910ce7 "B" (B) as 82b8abf9c185
775 775 already rebased 3:63668d570d21 "C" (C tip) as 82b8abf9c185
776 776 saved backup bundle to $TESTTMP/aborted-editor/.hg/strip-backup/f899f3910ce7-7cab5e15-rebase.hg
@@ -1,1407 +1,1407 b''
1 1 $ echo "[extensions]" >> $HGRCPATH
2 2 $ echo "strip=" >> $HGRCPATH
3 3 $ echo "drawdag=$TESTDIR/drawdag.py" >> $HGRCPATH
4 4
5 5 $ restore() {
6 6 > hg unbundle -q .hg/strip-backup/*
7 7 > rm .hg/strip-backup/*
8 8 > }
9 9 $ teststrip() {
10 10 > hg up -C $1
11 11 > echo % before update $1, strip $2
12 12 > hg log -G -T '{rev}:{node}'
13 13 > hg --traceback strip $2
14 14 > echo % after update $1, strip $2
15 15 > hg log -G -T '{rev}:{node}'
16 16 > restore
17 17 > }
18 18
19 19 $ hg init test
20 20 $ cd test
21 21
22 22 $ echo foo > bar
23 23 $ hg ci -Ama
24 24 adding bar
25 25
26 26 $ echo more >> bar
27 27 $ hg ci -Amb
28 28
29 29 $ echo blah >> bar
30 30 $ hg ci -Amc
31 31
32 32 $ hg up 1
33 33 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 34 $ echo blah >> bar
35 35 $ hg ci -Amd
36 36 created new head
37 37
38 38 $ echo final >> bar
39 39 $ hg ci -Ame
40 40
41 41 $ hg log
42 42 changeset: 4:443431ffac4f
43 43 tag: tip
44 44 user: test
45 45 date: Thu Jan 01 00:00:00 1970 +0000
46 46 summary: e
47 47
48 48 changeset: 3:65bd5f99a4a3
49 49 parent: 1:ef3a871183d7
50 50 user: test
51 51 date: Thu Jan 01 00:00:00 1970 +0000
52 52 summary: d
53 53
54 54 changeset: 2:264128213d29
55 55 user: test
56 56 date: Thu Jan 01 00:00:00 1970 +0000
57 57 summary: c
58 58
59 59 changeset: 1:ef3a871183d7
60 60 user: test
61 61 date: Thu Jan 01 00:00:00 1970 +0000
62 62 summary: b
63 63
64 64 changeset: 0:9ab35a2d17cb
65 65 user: test
66 66 date: Thu Jan 01 00:00:00 1970 +0000
67 67 summary: a
68 68
69 69
70 70 $ teststrip 4 4
71 71 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
72 72 % before update 4, strip 4
73 73 @ 4:443431ffac4f5b5a19b0b6c298a21b7ba736bcce
74 74 |
75 75 o 3:65bd5f99a4a376cdea23a1153f07856b0d881d64
76 76 |
77 77 | o 2:264128213d290d868c54642d13aeaa3675551a78
78 78 |/
79 79 o 1:ef3a871183d7199c541cc140218298bbfcc6c28a
80 80 |
81 81 o 0:9ab35a2d17cb64271241ea881efcc19dd953215b
82 82
83 83 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 84 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
85 85 % after update 4, strip 4
86 86 @ 3:65bd5f99a4a376cdea23a1153f07856b0d881d64
87 87 |
88 88 | o 2:264128213d290d868c54642d13aeaa3675551a78
89 89 |/
90 90 o 1:ef3a871183d7199c541cc140218298bbfcc6c28a
91 91 |
92 92 o 0:9ab35a2d17cb64271241ea881efcc19dd953215b
93 93
94 94 $ teststrip 4 3
95 95 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
96 96 % before update 4, strip 3
97 97 @ 4:443431ffac4f5b5a19b0b6c298a21b7ba736bcce
98 98 |
99 99 o 3:65bd5f99a4a376cdea23a1153f07856b0d881d64
100 100 |
101 101 | o 2:264128213d290d868c54642d13aeaa3675551a78
102 102 |/
103 103 o 1:ef3a871183d7199c541cc140218298bbfcc6c28a
104 104 |
105 105 o 0:9ab35a2d17cb64271241ea881efcc19dd953215b
106 106
107 107 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
108 108 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
109 109 % after update 4, strip 3
110 110 o 2:264128213d290d868c54642d13aeaa3675551a78
111 111 |
112 112 @ 1:ef3a871183d7199c541cc140218298bbfcc6c28a
113 113 |
114 114 o 0:9ab35a2d17cb64271241ea881efcc19dd953215b
115 115
116 116 $ teststrip 1 4
117 117 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 118 % before update 1, strip 4
119 119 o 4:443431ffac4f5b5a19b0b6c298a21b7ba736bcce
120 120 |
121 121 o 3:65bd5f99a4a376cdea23a1153f07856b0d881d64
122 122 |
123 123 | o 2:264128213d290d868c54642d13aeaa3675551a78
124 124 |/
125 125 @ 1:ef3a871183d7199c541cc140218298bbfcc6c28a
126 126 |
127 127 o 0:9ab35a2d17cb64271241ea881efcc19dd953215b
128 128
129 129 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
130 130 % after update 1, strip 4
131 131 o 3:65bd5f99a4a376cdea23a1153f07856b0d881d64
132 132 |
133 133 | o 2:264128213d290d868c54642d13aeaa3675551a78
134 134 |/
135 135 @ 1:ef3a871183d7199c541cc140218298bbfcc6c28a
136 136 |
137 137 o 0:9ab35a2d17cb64271241ea881efcc19dd953215b
138 138
139 139 $ teststrip 4 2
140 140 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 141 % before update 4, strip 2
142 142 @ 4:443431ffac4f5b5a19b0b6c298a21b7ba736bcce
143 143 |
144 144 o 3:65bd5f99a4a376cdea23a1153f07856b0d881d64
145 145 |
146 146 | o 2:264128213d290d868c54642d13aeaa3675551a78
147 147 |/
148 148 o 1:ef3a871183d7199c541cc140218298bbfcc6c28a
149 149 |
150 150 o 0:9ab35a2d17cb64271241ea881efcc19dd953215b
151 151
152 152 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
153 153 % after update 4, strip 2
154 154 @ 3:443431ffac4f5b5a19b0b6c298a21b7ba736bcce
155 155 |
156 156 o 2:65bd5f99a4a376cdea23a1153f07856b0d881d64
157 157 |
158 158 o 1:ef3a871183d7199c541cc140218298bbfcc6c28a
159 159 |
160 160 o 0:9ab35a2d17cb64271241ea881efcc19dd953215b
161 161
162 162 $ teststrip 4 1
163 163 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
164 164 % before update 4, strip 1
165 165 @ 4:264128213d290d868c54642d13aeaa3675551a78
166 166 |
167 167 | o 3:443431ffac4f5b5a19b0b6c298a21b7ba736bcce
168 168 | |
169 169 | o 2:65bd5f99a4a376cdea23a1153f07856b0d881d64
170 170 |/
171 171 o 1:ef3a871183d7199c541cc140218298bbfcc6c28a
172 172 |
173 173 o 0:9ab35a2d17cb64271241ea881efcc19dd953215b
174 174
175 175 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
176 176 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
177 177 % after update 4, strip 1
178 178 @ 0:9ab35a2d17cb64271241ea881efcc19dd953215b
179 179
180 180 $ teststrip null 4
181 181 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
182 182 % before update null, strip 4
183 183 o 4:264128213d290d868c54642d13aeaa3675551a78
184 184 |
185 185 | o 3:443431ffac4f5b5a19b0b6c298a21b7ba736bcce
186 186 | |
187 187 | o 2:65bd5f99a4a376cdea23a1153f07856b0d881d64
188 188 |/
189 189 o 1:ef3a871183d7199c541cc140218298bbfcc6c28a
190 190 |
191 191 o 0:9ab35a2d17cb64271241ea881efcc19dd953215b
192 192
193 193 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
194 194 % after update null, strip 4
195 195 o 3:443431ffac4f5b5a19b0b6c298a21b7ba736bcce
196 196 |
197 197 o 2:65bd5f99a4a376cdea23a1153f07856b0d881d64
198 198 |
199 199 o 1:ef3a871183d7199c541cc140218298bbfcc6c28a
200 200 |
201 201 o 0:9ab35a2d17cb64271241ea881efcc19dd953215b
202 202
203 203
204 204 $ hg log
205 205 changeset: 4:264128213d29
206 206 tag: tip
207 207 parent: 1:ef3a871183d7
208 208 user: test
209 209 date: Thu Jan 01 00:00:00 1970 +0000
210 210 summary: c
211 211
212 212 changeset: 3:443431ffac4f
213 213 user: test
214 214 date: Thu Jan 01 00:00:00 1970 +0000
215 215 summary: e
216 216
217 217 changeset: 2:65bd5f99a4a3
218 218 user: test
219 219 date: Thu Jan 01 00:00:00 1970 +0000
220 220 summary: d
221 221
222 222 changeset: 1:ef3a871183d7
223 223 user: test
224 224 date: Thu Jan 01 00:00:00 1970 +0000
225 225 summary: b
226 226
227 227 changeset: 0:9ab35a2d17cb
228 228 user: test
229 229 date: Thu Jan 01 00:00:00 1970 +0000
230 230 summary: a
231 231
232 232 $ hg up -C 4
233 233 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
234 234 $ hg parents
235 235 changeset: 4:264128213d29
236 236 tag: tip
237 237 parent: 1:ef3a871183d7
238 238 user: test
239 239 date: Thu Jan 01 00:00:00 1970 +0000
240 240 summary: c
241 241
242 242
243 243 $ hg --traceback strip 4
244 244 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
245 245 saved backup bundle to $TESTTMP/test/.hg/strip-backup/264128213d29-0b39d6bf-backup.hg
246 246 $ hg parents
247 247 changeset: 1:ef3a871183d7
248 248 user: test
249 249 date: Thu Jan 01 00:00:00 1970 +0000
250 250 summary: b
251 251
252 252 $ hg debugbundle .hg/strip-backup/*
253 253 Stream params: {Compression: BZ}
254 254 changegroup -- {nbchanges: 1, version: 02} (mandatory: True)
255 255 264128213d290d868c54642d13aeaa3675551a78
256 256 cache:rev-branch-cache -- {} (mandatory: False)
257 257 phase-heads -- {} (mandatory: True)
258 258 264128213d290d868c54642d13aeaa3675551a78 draft
259 259 $ hg unbundle .hg/strip-backup/*
260 260 adding changesets
261 261 adding manifests
262 262 adding file changes
263 263 added 1 changesets with 0 changes to 1 files (+1 heads)
264 264 new changesets 264128213d29 (1 drafts)
265 265 (run 'hg heads' to see heads, 'hg merge' to merge)
266 266 $ rm .hg/strip-backup/*
267 267 $ hg log --graph
268 268 o changeset: 4:264128213d29
269 269 | tag: tip
270 270 | parent: 1:ef3a871183d7
271 271 | user: test
272 272 | date: Thu Jan 01 00:00:00 1970 +0000
273 273 | summary: c
274 274 |
275 275 | o changeset: 3:443431ffac4f
276 276 | | user: test
277 277 | | date: Thu Jan 01 00:00:00 1970 +0000
278 278 | | summary: e
279 279 | |
280 280 | o changeset: 2:65bd5f99a4a3
281 281 |/ user: test
282 282 | date: Thu Jan 01 00:00:00 1970 +0000
283 283 | summary: d
284 284 |
285 285 @ changeset: 1:ef3a871183d7
286 286 | user: test
287 287 | date: Thu Jan 01 00:00:00 1970 +0000
288 288 | summary: b
289 289 |
290 290 o changeset: 0:9ab35a2d17cb
291 291 user: test
292 292 date: Thu Jan 01 00:00:00 1970 +0000
293 293 summary: a
294 294
295 295 $ hg up -C 2
296 296 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
297 297 $ hg merge 4
298 298 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
299 299 (branch merge, don't forget to commit)
300 300
301 301 before strip of merge parent
302 302
303 303 $ hg parents
304 304 changeset: 2:65bd5f99a4a3
305 305 user: test
306 306 date: Thu Jan 01 00:00:00 1970 +0000
307 307 summary: d
308 308
309 309 changeset: 4:264128213d29
310 310 tag: tip
311 311 parent: 1:ef3a871183d7
312 312 user: test
313 313 date: Thu Jan 01 00:00:00 1970 +0000
314 314 summary: c
315 315
316 316 ##strip not allowed with merge in progress
317 317 $ hg strip 4
318 318 abort: outstanding uncommitted merge
319 319 (use 'hg commit' or 'hg merge --abort')
320 320 [255]
321 321 ##strip allowed --force with merge in progress
322 322 $ hg strip 4 --force
323 323 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
324 324 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
325 325
326 326 after strip of merge parent
327 327
328 328 $ hg parents
329 329 changeset: 1:ef3a871183d7
330 330 user: test
331 331 date: Thu Jan 01 00:00:00 1970 +0000
332 332 summary: b
333 333
334 334 $ restore
335 335
336 336 $ hg up
337 337 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
338 338 updated to "264128213d29: c"
339 339 1 other heads for branch "default"
340 340 $ hg log -G
341 341 @ changeset: 4:264128213d29
342 342 | tag: tip
343 343 | parent: 1:ef3a871183d7
344 344 | user: test
345 345 | date: Thu Jan 01 00:00:00 1970 +0000
346 346 | summary: c
347 347 |
348 348 | o changeset: 3:443431ffac4f
349 349 | | user: test
350 350 | | date: Thu Jan 01 00:00:00 1970 +0000
351 351 | | summary: e
352 352 | |
353 353 | o changeset: 2:65bd5f99a4a3
354 354 |/ user: test
355 355 | date: Thu Jan 01 00:00:00 1970 +0000
356 356 | summary: d
357 357 |
358 358 o changeset: 1:ef3a871183d7
359 359 | user: test
360 360 | date: Thu Jan 01 00:00:00 1970 +0000
361 361 | summary: b
362 362 |
363 363 o changeset: 0:9ab35a2d17cb
364 364 user: test
365 365 date: Thu Jan 01 00:00:00 1970 +0000
366 366 summary: a
367 367
368 368
369 369 2 is parent of 3, only one strip should happen
370 370
371 371 $ hg strip "roots(2)" 3
372 372 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
373 373 $ hg log -G
374 374 @ changeset: 2:264128213d29
375 375 | tag: tip
376 376 | user: test
377 377 | date: Thu Jan 01 00:00:00 1970 +0000
378 378 | summary: c
379 379 |
380 380 o changeset: 1:ef3a871183d7
381 381 | user: test
382 382 | date: Thu Jan 01 00:00:00 1970 +0000
383 383 | summary: b
384 384 |
385 385 o changeset: 0:9ab35a2d17cb
386 386 user: test
387 387 date: Thu Jan 01 00:00:00 1970 +0000
388 388 summary: a
389 389
390 390 $ restore
391 391 $ hg log -G
392 392 o changeset: 4:443431ffac4f
393 393 | tag: tip
394 394 | user: test
395 395 | date: Thu Jan 01 00:00:00 1970 +0000
396 396 | summary: e
397 397 |
398 398 o changeset: 3:65bd5f99a4a3
399 399 | parent: 1:ef3a871183d7
400 400 | user: test
401 401 | date: Thu Jan 01 00:00:00 1970 +0000
402 402 | summary: d
403 403 |
404 404 | @ changeset: 2:264128213d29
405 405 |/ user: test
406 406 | date: Thu Jan 01 00:00:00 1970 +0000
407 407 | summary: c
408 408 |
409 409 o changeset: 1:ef3a871183d7
410 410 | user: test
411 411 | date: Thu Jan 01 00:00:00 1970 +0000
412 412 | summary: b
413 413 |
414 414 o changeset: 0:9ab35a2d17cb
415 415 user: test
416 416 date: Thu Jan 01 00:00:00 1970 +0000
417 417 summary: a
418 418
419 419 Failed hook while applying "saveheads" bundle.
420 420
421 421 $ hg strip 2 --config hooks.pretxnchangegroup.bad=false
422 422 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
423 423 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
424 424 transaction abort!
425 425 rollback completed
426 426 strip failed, backup bundle stored in '$TESTTMP/test/.hg/strip-backup/*-backup.hg' (glob)
427 427 strip failed, unrecovered changes stored in '$TESTTMP/test/.hg/strip-backup/*-temp.hg' (glob)
428 428 (fix the problem, then recover the changesets with "hg unbundle '$TESTTMP/test/.hg/strip-backup/*-temp.hg'") (glob)
429 429 abort: pretxnchangegroup.bad hook exited with status 1
430 430 [255]
431 431 $ restore
432 432 $ hg log -G
433 433 o changeset: 4:443431ffac4f
434 434 | tag: tip
435 435 | user: test
436 436 | date: Thu Jan 01 00:00:00 1970 +0000
437 437 | summary: e
438 438 |
439 439 o changeset: 3:65bd5f99a4a3
440 440 | parent: 1:ef3a871183d7
441 441 | user: test
442 442 | date: Thu Jan 01 00:00:00 1970 +0000
443 443 | summary: d
444 444 |
445 445 | o changeset: 2:264128213d29
446 446 |/ user: test
447 447 | date: Thu Jan 01 00:00:00 1970 +0000
448 448 | summary: c
449 449 |
450 450 @ changeset: 1:ef3a871183d7
451 451 | user: test
452 452 | date: Thu Jan 01 00:00:00 1970 +0000
453 453 | summary: b
454 454 |
455 455 o changeset: 0:9ab35a2d17cb
456 456 user: test
457 457 date: Thu Jan 01 00:00:00 1970 +0000
458 458 summary: a
459 459
460 460
461 461 2 different branches: 2 strips
462 462
463 463 $ hg strip 2 4
464 464 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
465 465 $ hg log -G
466 466 o changeset: 2:65bd5f99a4a3
467 467 | tag: tip
468 468 | user: test
469 469 | date: Thu Jan 01 00:00:00 1970 +0000
470 470 | summary: d
471 471 |
472 472 @ changeset: 1:ef3a871183d7
473 473 | user: test
474 474 | date: Thu Jan 01 00:00:00 1970 +0000
475 475 | summary: b
476 476 |
477 477 o changeset: 0:9ab35a2d17cb
478 478 user: test
479 479 date: Thu Jan 01 00:00:00 1970 +0000
480 480 summary: a
481 481
482 482 $ restore
483 483
484 484 2 different branches and a common ancestor: 1 strip
485 485
486 486 $ hg strip 1 "2|4"
487 487 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
488 488 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
489 489 $ restore
490 490
491 491 verify fncache is kept up-to-date
492 492
493 493 $ touch a
494 494 $ hg ci -qAm a
495 495 #if repofncache
496 496 $ cat .hg/store/fncache | sort
497 497 data/a.i
498 498 data/bar.i
499 499 #endif
500 500
501 501 $ hg strip tip
502 502 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
503 503 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
504 504 #if repofncache
505 505 $ cat .hg/store/fncache
506 506 data/bar.i
507 507 #endif
508 508
509 509 stripping an empty revset
510 510
511 511 $ hg strip "1 and not 1"
512 512 abort: empty revision set
513 513 [255]
514 514
515 515 remove branchy history for qimport tests
516 516
517 517 $ hg strip 3
518 518 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
519 519
520 520
521 521 strip of applied mq should cleanup status file
522 522
523 523 $ echo "mq=" >> $HGRCPATH
524 524 $ hg up -C 3
525 525 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
526 526 $ echo fooagain >> bar
527 527 $ hg ci -mf
528 528 $ hg qimport -r tip:2
529 529
530 530 applied patches before strip
531 531
532 532 $ hg qapplied
533 533 d
534 534 e
535 535 f
536 536
537 537 stripping revision in queue
538 538
539 539 $ hg strip 3
540 540 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
541 541 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
542 542
543 543 applied patches after stripping rev in queue
544 544
545 545 $ hg qapplied
546 546 d
547 547
548 548 stripping ancestor of queue
549 549
550 550 $ hg strip 1
551 551 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
552 552 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
553 553
554 554 applied patches after stripping ancestor of queue
555 555
556 556 $ hg qapplied
557 557
558 558 Verify strip protects against stripping wc parent when there are uncommitted mods
559 559
560 560 $ echo b > b
561 561 $ echo bb > bar
562 562 $ hg add b
563 563 $ hg ci -m 'b'
564 564 $ hg log --graph
565 565 @ changeset: 1:76dcf9fab855
566 566 | tag: tip
567 567 | user: test
568 568 | date: Thu Jan 01 00:00:00 1970 +0000
569 569 | summary: b
570 570 |
571 571 o changeset: 0:9ab35a2d17cb
572 572 user: test
573 573 date: Thu Jan 01 00:00:00 1970 +0000
574 574 summary: a
575 575
576 576 $ hg up 0
577 577 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
578 578 $ echo c > bar
579 579 $ hg up -t false
580 580 merging bar
581 581 merging bar failed!
582 582 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
583 583 use 'hg resolve' to retry unresolved file merges
584 584 [1]
585 585 $ hg sum
586 586 parent: 1:76dcf9fab855 tip
587 587 b
588 588 branch: default
589 589 commit: 1 modified, 1 unknown, 1 unresolved
590 590 update: (current)
591 591 phases: 2 draft
592 592 mq: 3 unapplied
593 593
594 594 $ hg log --graph
595 595 @ changeset: 1:76dcf9fab855
596 596 | tag: tip
597 597 | user: test
598 598 | date: Thu Jan 01 00:00:00 1970 +0000
599 599 | summary: b
600 600 |
601 o changeset: 0:9ab35a2d17cb
601 % changeset: 0:9ab35a2d17cb
602 602 user: test
603 603 date: Thu Jan 01 00:00:00 1970 +0000
604 604 summary: a
605 605
606 606 $ echo c > b
607 607 $ hg strip tip
608 608 abort: uncommitted changes
609 609 [255]
610 610 $ hg strip tip --keep
611 611 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
612 612 $ hg log --graph
613 613 @ changeset: 0:9ab35a2d17cb
614 614 tag: tip
615 615 user: test
616 616 date: Thu Jan 01 00:00:00 1970 +0000
617 617 summary: a
618 618
619 619 $ hg status
620 620 M bar
621 621 ? b
622 622 ? bar.orig
623 623
624 624 $ rm bar.orig
625 625 $ hg sum
626 626 parent: 0:9ab35a2d17cb tip
627 627 a
628 628 branch: default
629 629 commit: 1 modified, 1 unknown
630 630 update: (current)
631 631 phases: 1 draft
632 632 mq: 3 unapplied
633 633
634 634 Strip adds, removes, modifies with --keep
635 635
636 636 $ touch b
637 637 $ hg add b
638 638 $ hg commit -mb
639 639 $ touch c
640 640
641 641 ... with a clean working dir
642 642
643 643 $ hg add c
644 644 $ hg rm bar
645 645 $ hg commit -mc
646 646 $ hg status
647 647 $ hg strip --keep tip
648 648 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
649 649 $ hg status
650 650 ! bar
651 651 ? c
652 652
653 653 ... with a dirty working dir
654 654
655 655 $ hg add c
656 656 $ hg rm bar
657 657 $ hg commit -mc
658 658 $ hg status
659 659 $ echo b > b
660 660 $ echo d > d
661 661 $ hg strip --keep tip
662 662 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
663 663 $ hg status
664 664 M b
665 665 ! bar
666 666 ? c
667 667 ? d
668 668
669 669 ... after updating the dirstate
670 670 $ hg add c
671 671 $ hg commit -mc
672 672 $ hg rm c
673 673 $ hg commit -mc
674 674 $ hg strip --keep '.^' -q
675 675 $ cd ..
676 676
677 677 stripping many nodes on a complex graph (issue3299)
678 678
679 679 $ hg init issue3299
680 680 $ cd issue3299
681 681 $ hg debugbuilddag '@a.:a@b.:b.:x<a@a.:a<b@b.:b<a@a.:a'
682 682 $ hg strip 'not ancestors(x)'
683 683 saved backup bundle to $TESTTMP/issue3299/.hg/strip-backup/*-backup.hg (glob)
684 684
685 685 test hg strip -B bookmark
686 686
687 687 $ cd ..
688 688 $ hg init bookmarks
689 689 $ cd bookmarks
690 690 $ hg debugbuilddag '..<2.*1/2:m<2+3:c<m+3:a<2.:b<m+2:d<2.:e<m+1:f'
691 691 $ hg bookmark -r 'a' 'todelete'
692 692 $ hg bookmark -r 'b' 'B'
693 693 $ hg bookmark -r 'b' 'nostrip'
694 694 $ hg bookmark -r 'c' 'delete'
695 695 $ hg bookmark -r 'd' 'multipledelete1'
696 696 $ hg bookmark -r 'e' 'multipledelete2'
697 697 $ hg bookmark -r 'f' 'singlenode1'
698 698 $ hg bookmark -r 'f' 'singlenode2'
699 699 $ hg up -C todelete
700 700 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
701 701 (activating bookmark todelete)
702 702 $ hg strip -B nostrip
703 703 bookmark 'nostrip' deleted
704 704 abort: empty revision set
705 705 [255]
706 706 $ hg strip -B todelete
707 707 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
708 708 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob)
709 709 bookmark 'todelete' deleted
710 710 $ hg id -ir dcbb326fdec2
711 711 abort: unknown revision 'dcbb326fdec2'!
712 712 [255]
713 713 $ hg id -ir d62d843c9a01
714 714 d62d843c9a01
715 715 $ hg bookmarks
716 716 B 9:ff43616e5d0f
717 717 delete 6:2702dd0c91e7
718 718 multipledelete1 11:e46a4836065c
719 719 multipledelete2 12:b4594d867745
720 720 singlenode1 13:43227190fef8
721 721 singlenode2 13:43227190fef8
722 722 $ hg strip -B multipledelete1 -B multipledelete2
723 723 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/e46a4836065c-89ec65c2-backup.hg
724 724 bookmark 'multipledelete1' deleted
725 725 bookmark 'multipledelete2' deleted
726 726 $ hg id -ir e46a4836065c
727 727 abort: unknown revision 'e46a4836065c'!
728 728 [255]
729 729 $ hg id -ir b4594d867745
730 730 abort: unknown revision 'b4594d867745'!
731 731 [255]
732 732 $ hg strip -B singlenode1 -B singlenode2
733 733 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/43227190fef8-8da858f2-backup.hg
734 734 bookmark 'singlenode1' deleted
735 735 bookmark 'singlenode2' deleted
736 736 $ hg id -ir 43227190fef8
737 737 abort: unknown revision '43227190fef8'!
738 738 [255]
739 739 $ hg strip -B unknownbookmark
740 740 abort: bookmark 'unknownbookmark' not found
741 741 [255]
742 742 $ hg strip -B unknownbookmark1 -B unknownbookmark2
743 743 abort: bookmark 'unknownbookmark1,unknownbookmark2' not found
744 744 [255]
745 745 $ hg strip -B delete -B unknownbookmark
746 746 abort: bookmark 'unknownbookmark' not found
747 747 [255]
748 748 $ hg strip -B delete
749 749 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob)
750 750 bookmark 'delete' deleted
751 751 $ hg id -ir 6:2702dd0c91e7
752 752 abort: unknown revision '2702dd0c91e7'!
753 753 [255]
754 754 $ hg update B
755 755 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
756 756 (activating bookmark B)
757 757 $ echo a > a
758 758 $ hg add a
759 759 $ hg strip -B B
760 760 abort: uncommitted changes
761 761 [255]
762 762 $ hg bookmarks
763 763 * B 6:ff43616e5d0f
764 764
765 765 Make sure no one adds back a -b option:
766 766
767 767 $ hg strip -b tip
768 768 hg strip: option -b not recognized
769 769 hg strip [-k] [-f] [-B bookmark] [-r] REV...
770 770
771 771 strip changesets and all their descendants from the repository
772 772
773 773 (use 'hg help -e strip' to show help for the strip extension)
774 774
775 775 options ([+] can be repeated):
776 776
777 777 -r --rev REV [+] strip specified revision (optional, can specify
778 778 revisions without this option)
779 779 -f --force force removal of changesets, discard uncommitted
780 780 changes (no backup)
781 781 --no-backup do not save backup bundle
782 782 -k --keep do not modify working directory during strip
783 783 -B --bookmark BOOKMARK [+] remove revs only reachable from given bookmark
784 784 --mq operate on patch repository
785 785
786 786 (use 'hg strip -h' to show more help)
787 787 [255]
788 788
789 789 $ cd ..
790 790
791 791 Verify bundles don't get overwritten:
792 792
793 793 $ hg init doublebundle
794 794 $ cd doublebundle
795 795 $ touch a
796 796 $ hg commit -Aqm a
797 797 $ touch b
798 798 $ hg commit -Aqm b
799 799 $ hg strip -r 0
800 800 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
801 801 saved backup bundle to $TESTTMP/doublebundle/.hg/strip-backup/3903775176ed-e68910bd-backup.hg
802 802 $ ls .hg/strip-backup
803 803 3903775176ed-e68910bd-backup.hg
804 804 #if repobundlerepo
805 805 $ hg pull -q -r 3903775176ed .hg/strip-backup/3903775176ed-e68910bd-backup.hg
806 806 $ hg strip -r 0
807 807 saved backup bundle to $TESTTMP/doublebundle/.hg/strip-backup/3903775176ed-54390173-backup.hg
808 808 $ ls .hg/strip-backup
809 809 3903775176ed-54390173-backup.hg
810 810 3903775176ed-e68910bd-backup.hg
811 811 #endif
812 812 $ cd ..
813 813
814 814 Test that we only bundle the stripped changesets (issue4736)
815 815 ------------------------------------------------------------
816 816
817 817 initialization (previous repo is empty anyway)
818 818
819 819 $ hg init issue4736
820 820 $ cd issue4736
821 821 $ echo a > a
822 822 $ hg add a
823 823 $ hg commit -m commitA
824 824 $ echo b > b
825 825 $ hg add b
826 826 $ hg commit -m commitB
827 827 $ echo c > c
828 828 $ hg add c
829 829 $ hg commit -m commitC
830 830 $ hg up 'desc(commitB)'
831 831 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
832 832 $ echo d > d
833 833 $ hg add d
834 834 $ hg commit -m commitD
835 835 created new head
836 836 $ hg up 'desc(commitC)'
837 837 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
838 838 $ hg merge 'desc(commitD)'
839 839 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
840 840 (branch merge, don't forget to commit)
841 841 $ hg ci -m 'mergeCD'
842 842 $ hg log -G
843 843 @ changeset: 4:d8db9d137221
844 844 |\ tag: tip
845 845 | | parent: 2:5c51d8d6557d
846 846 | | parent: 3:6625a5168474
847 847 | | user: test
848 848 | | date: Thu Jan 01 00:00:00 1970 +0000
849 849 | | summary: mergeCD
850 850 | |
851 851 | o changeset: 3:6625a5168474
852 852 | | parent: 1:eca11cf91c71
853 853 | | user: test
854 854 | | date: Thu Jan 01 00:00:00 1970 +0000
855 855 | | summary: commitD
856 856 | |
857 857 o | changeset: 2:5c51d8d6557d
858 858 |/ user: test
859 859 | date: Thu Jan 01 00:00:00 1970 +0000
860 860 | summary: commitC
861 861 |
862 862 o changeset: 1:eca11cf91c71
863 863 | user: test
864 864 | date: Thu Jan 01 00:00:00 1970 +0000
865 865 | summary: commitB
866 866 |
867 867 o changeset: 0:105141ef12d0
868 868 user: test
869 869 date: Thu Jan 01 00:00:00 1970 +0000
870 870 summary: commitA
871 871
872 872
873 873 Check bundle behavior:
874 874
875 875 $ hg bundle -r 'desc(mergeCD)' --base 'desc(commitC)' ../issue4736.hg
876 876 2 changesets found
877 877 #if repobundlerepo
878 878 $ hg log -r 'bundle()' -R ../issue4736.hg
879 879 changeset: 3:6625a5168474
880 880 parent: 1:eca11cf91c71
881 881 user: test
882 882 date: Thu Jan 01 00:00:00 1970 +0000
883 883 summary: commitD
884 884
885 885 changeset: 4:d8db9d137221
886 886 tag: tip
887 887 parent: 2:5c51d8d6557d
888 888 parent: 3:6625a5168474
889 889 user: test
890 890 date: Thu Jan 01 00:00:00 1970 +0000
891 891 summary: mergeCD
892 892
893 893 #endif
894 894
895 895 check strip behavior
896 896
897 897 $ hg --config extensions.strip= strip 'desc(commitD)' --debug
898 898 resolving manifests
899 899 branchmerge: False, force: True, partial: False
900 900 ancestor: d8db9d137221+, local: d8db9d137221+, remote: eca11cf91c71
901 901 c: other deleted -> r
902 902 removing c
903 903 d: other deleted -> r
904 904 removing d
905 905 starting 4 threads for background file closing (?)
906 906 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
907 907 2 changesets found
908 908 list of changesets:
909 909 6625a516847449b6f0fa3737b9ba56e9f0f3032c
910 910 d8db9d1372214336d2b5570f20ee468d2c72fa8b
911 911 bundle2-output-bundle: "HG20", (1 params) 3 parts total
912 912 bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
913 913 bundle2-output-part: "cache:rev-branch-cache" (advisory) streamed payload
914 914 bundle2-output-part: "phase-heads" 24 bytes payload
915 915 saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/6625a5168474-345bb43d-backup.hg
916 916 updating the branch cache
917 917 invalid branch cache (served): tip differs
918 918 $ hg log -G
919 919 o changeset: 2:5c51d8d6557d
920 920 | tag: tip
921 921 | user: test
922 922 | date: Thu Jan 01 00:00:00 1970 +0000
923 923 | summary: commitC
924 924 |
925 925 @ changeset: 1:eca11cf91c71
926 926 | user: test
927 927 | date: Thu Jan 01 00:00:00 1970 +0000
928 928 | summary: commitB
929 929 |
930 930 o changeset: 0:105141ef12d0
931 931 user: test
932 932 date: Thu Jan 01 00:00:00 1970 +0000
933 933 summary: commitA
934 934
935 935
936 936 strip backup content
937 937
938 938 #if repobundlerepo
939 939 $ hg log -r 'bundle()' -R .hg/strip-backup/6625a5168474-*-backup.hg
940 940 changeset: 3:6625a5168474
941 941 parent: 1:eca11cf91c71
942 942 user: test
943 943 date: Thu Jan 01 00:00:00 1970 +0000
944 944 summary: commitD
945 945
946 946 changeset: 4:d8db9d137221
947 947 tag: tip
948 948 parent: 2:5c51d8d6557d
949 949 parent: 3:6625a5168474
950 950 user: test
951 951 date: Thu Jan 01 00:00:00 1970 +0000
952 952 summary: mergeCD
953 953
954 954
955 955 #endif
956 956
957 957 Check that the phase cache is properly invalidated after a strip with bookmark.
958 958
959 959 $ cat > ../stripstalephasecache.py << EOF
960 960 > from mercurial import extensions, localrepo
961 961 > def transactioncallback(orig, repo, desc, *args, **kwargs):
962 962 > def test(transaction):
963 963 > # observe cache inconsistency
964 964 > try:
965 965 > [repo.changelog.node(r) for r in repo.revs(b"not public()")]
966 966 > except IndexError:
967 967 > repo.ui.status(b"Index error!\n")
968 968 > transaction = orig(repo, desc, *args, **kwargs)
969 969 > # warm up the phase cache
970 970 > list(repo.revs(b"not public()"))
971 971 > if desc != b'strip':
972 972 > transaction.addpostclose(b"phase invalidation test", test)
973 973 > return transaction
974 974 > def extsetup(ui):
975 975 > extensions.wrapfunction(localrepo.localrepository, b"transaction",
976 976 > transactioncallback)
977 977 > EOF
978 978 $ hg up -C 2
979 979 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
980 980 $ echo k > k
981 981 $ hg add k
982 982 $ hg commit -m commitK
983 983 $ echo l > l
984 984 $ hg add l
985 985 $ hg commit -m commitL
986 986 $ hg book -r tip blah
987 987 $ hg strip ".^" --config extensions.crash=$TESTTMP/stripstalephasecache.py
988 988 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
989 989 saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/8f0b4384875c-4fa10deb-backup.hg
990 990 $ hg up -C 1
991 991 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
992 992
993 993 Error during post-close callback of the strip transaction
994 994 (They should be gracefully handled and reported)
995 995
996 996 $ cat > ../crashstrip.py << EOF
997 997 > from mercurial import error
998 998 > def reposetup(ui, repo):
999 999 > class crashstriprepo(repo.__class__):
1000 1000 > def transaction(self, desc, *args, **kwargs):
1001 1001 > tr = super(crashstriprepo, self).transaction(desc, *args, **kwargs)
1002 1002 > if desc == b'strip':
1003 1003 > def crash(tra): raise error.Abort(b'boom')
1004 1004 > tr.addpostclose(b'crash', crash)
1005 1005 > return tr
1006 1006 > repo.__class__ = crashstriprepo
1007 1007 > EOF
1008 1008 $ hg strip tip --config extensions.crash=$TESTTMP/crashstrip.py
1009 1009 saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/5c51d8d6557d-70daef06-backup.hg
1010 1010 strip failed, backup bundle stored in '$TESTTMP/issue4736/.hg/strip-backup/5c51d8d6557d-70daef06-backup.hg'
1011 1011 abort: boom
1012 1012 [255]
1013 1013
1014 1014 test stripping a working directory parent doesn't switch named branches
1015 1015
1016 1016 $ hg log -G
1017 1017 @ changeset: 1:eca11cf91c71
1018 1018 | tag: tip
1019 1019 | user: test
1020 1020 | date: Thu Jan 01 00:00:00 1970 +0000
1021 1021 | summary: commitB
1022 1022 |
1023 1023 o changeset: 0:105141ef12d0
1024 1024 user: test
1025 1025 date: Thu Jan 01 00:00:00 1970 +0000
1026 1026 summary: commitA
1027 1027
1028 1028
1029 1029 $ hg branch new-branch
1030 1030 marked working directory as branch new-branch
1031 1031 (branches are permanent and global, did you want a bookmark?)
1032 1032 $ hg ci -m "start new branch"
1033 1033 $ echo 'foo' > foo.txt
1034 1034 $ hg ci -Aqm foo
1035 1035 $ hg up default
1036 1036 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1037 1037 $ echo 'bar' > bar.txt
1038 1038 $ hg ci -Aqm bar
1039 1039 $ hg up new-branch
1040 1040 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1041 1041 $ hg merge default
1042 1042 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1043 1043 (branch merge, don't forget to commit)
1044 1044 $ hg log -G
1045 1045 @ changeset: 4:35358f982181
1046 1046 | tag: tip
1047 1047 | parent: 1:eca11cf91c71
1048 1048 | user: test
1049 1049 | date: Thu Jan 01 00:00:00 1970 +0000
1050 1050 | summary: bar
1051 1051 |
1052 1052 | @ changeset: 3:f62c6c09b707
1053 1053 | | branch: new-branch
1054 1054 | | user: test
1055 1055 | | date: Thu Jan 01 00:00:00 1970 +0000
1056 1056 | | summary: foo
1057 1057 | |
1058 1058 | o changeset: 2:b1d33a8cadd9
1059 1059 |/ branch: new-branch
1060 1060 | user: test
1061 1061 | date: Thu Jan 01 00:00:00 1970 +0000
1062 1062 | summary: start new branch
1063 1063 |
1064 1064 o changeset: 1:eca11cf91c71
1065 1065 | user: test
1066 1066 | date: Thu Jan 01 00:00:00 1970 +0000
1067 1067 | summary: commitB
1068 1068 |
1069 1069 o changeset: 0:105141ef12d0
1070 1070 user: test
1071 1071 date: Thu Jan 01 00:00:00 1970 +0000
1072 1072 summary: commitA
1073 1073
1074 1074
1075 1075 $ hg strip --force -r 35358f982181
1076 1076 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1077 1077 saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/35358f982181-50d992d4-backup.hg
1078 1078 $ hg log -G
1079 1079 @ changeset: 3:f62c6c09b707
1080 1080 | branch: new-branch
1081 1081 | tag: tip
1082 1082 | user: test
1083 1083 | date: Thu Jan 01 00:00:00 1970 +0000
1084 1084 | summary: foo
1085 1085 |
1086 1086 o changeset: 2:b1d33a8cadd9
1087 1087 | branch: new-branch
1088 1088 | user: test
1089 1089 | date: Thu Jan 01 00:00:00 1970 +0000
1090 1090 | summary: start new branch
1091 1091 |
1092 1092 o changeset: 1:eca11cf91c71
1093 1093 | user: test
1094 1094 | date: Thu Jan 01 00:00:00 1970 +0000
1095 1095 | summary: commitB
1096 1096 |
1097 1097 o changeset: 0:105141ef12d0
1098 1098 user: test
1099 1099 date: Thu Jan 01 00:00:00 1970 +0000
1100 1100 summary: commitA
1101 1101
1102 1102
1103 1103 $ hg up default
1104 1104 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1105 1105 $ echo 'bar' > bar.txt
1106 1106 $ hg ci -Aqm bar
1107 1107 $ hg up new-branch
1108 1108 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1109 1109 $ hg merge default
1110 1110 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1111 1111 (branch merge, don't forget to commit)
1112 1112 $ hg ci -m merge
1113 1113 $ hg log -G
1114 1114 @ changeset: 5:4cf5e92caec2
1115 1115 |\ branch: new-branch
1116 1116 | | tag: tip
1117 1117 | | parent: 3:f62c6c09b707
1118 1118 | | parent: 4:35358f982181
1119 1119 | | user: test
1120 1120 | | date: Thu Jan 01 00:00:00 1970 +0000
1121 1121 | | summary: merge
1122 1122 | |
1123 1123 | o changeset: 4:35358f982181
1124 1124 | | parent: 1:eca11cf91c71
1125 1125 | | user: test
1126 1126 | | date: Thu Jan 01 00:00:00 1970 +0000
1127 1127 | | summary: bar
1128 1128 | |
1129 1129 o | changeset: 3:f62c6c09b707
1130 1130 | | branch: new-branch
1131 1131 | | user: test
1132 1132 | | date: Thu Jan 01 00:00:00 1970 +0000
1133 1133 | | summary: foo
1134 1134 | |
1135 1135 o | changeset: 2:b1d33a8cadd9
1136 1136 |/ branch: new-branch
1137 1137 | user: test
1138 1138 | date: Thu Jan 01 00:00:00 1970 +0000
1139 1139 | summary: start new branch
1140 1140 |
1141 1141 o changeset: 1:eca11cf91c71
1142 1142 | user: test
1143 1143 | date: Thu Jan 01 00:00:00 1970 +0000
1144 1144 | summary: commitB
1145 1145 |
1146 1146 o changeset: 0:105141ef12d0
1147 1147 user: test
1148 1148 date: Thu Jan 01 00:00:00 1970 +0000
1149 1149 summary: commitA
1150 1150
1151 1151
1152 1152 $ hg strip -r 35358f982181
1153 1153 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1154 1154 saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/35358f982181-a6f020aa-backup.hg
1155 1155 $ hg log -G
1156 1156 @ changeset: 3:f62c6c09b707
1157 1157 | branch: new-branch
1158 1158 | tag: tip
1159 1159 | user: test
1160 1160 | date: Thu Jan 01 00:00:00 1970 +0000
1161 1161 | summary: foo
1162 1162 |
1163 1163 o changeset: 2:b1d33a8cadd9
1164 1164 | branch: new-branch
1165 1165 | user: test
1166 1166 | date: Thu Jan 01 00:00:00 1970 +0000
1167 1167 | summary: start new branch
1168 1168 |
1169 1169 o changeset: 1:eca11cf91c71
1170 1170 | user: test
1171 1171 | date: Thu Jan 01 00:00:00 1970 +0000
1172 1172 | summary: commitB
1173 1173 |
1174 1174 o changeset: 0:105141ef12d0
1175 1175 user: test
1176 1176 date: Thu Jan 01 00:00:00 1970 +0000
1177 1177 summary: commitA
1178 1178
1179 1179
1180 1180 $ hg unbundle -u $TESTTMP/issue4736/.hg/strip-backup/35358f982181-a6f020aa-backup.hg
1181 1181 adding changesets
1182 1182 adding manifests
1183 1183 adding file changes
1184 1184 added 2 changesets with 1 changes to 1 files
1185 1185 new changesets 35358f982181:4cf5e92caec2 (2 drafts)
1186 1186 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1187 1187
1188 1188 $ hg strip -k -r 35358f982181
1189 1189 saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/35358f982181-a6f020aa-backup.hg
1190 1190 $ hg log -G
1191 1191 @ changeset: 3:f62c6c09b707
1192 1192 | branch: new-branch
1193 1193 | tag: tip
1194 1194 | user: test
1195 1195 | date: Thu Jan 01 00:00:00 1970 +0000
1196 1196 | summary: foo
1197 1197 |
1198 1198 o changeset: 2:b1d33a8cadd9
1199 1199 | branch: new-branch
1200 1200 | user: test
1201 1201 | date: Thu Jan 01 00:00:00 1970 +0000
1202 1202 | summary: start new branch
1203 1203 |
1204 1204 o changeset: 1:eca11cf91c71
1205 1205 | user: test
1206 1206 | date: Thu Jan 01 00:00:00 1970 +0000
1207 1207 | summary: commitB
1208 1208 |
1209 1209 o changeset: 0:105141ef12d0
1210 1210 user: test
1211 1211 date: Thu Jan 01 00:00:00 1970 +0000
1212 1212 summary: commitA
1213 1213
1214 1214 $ hg diff
1215 1215 diff -r f62c6c09b707 bar.txt
1216 1216 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1217 1217 +++ b/bar.txt Thu Jan 01 00:00:00 1970 +0000
1218 1218 @@ -0,0 +1,1 @@
1219 1219 +bar
1220 1220
1221 1221 Use delayedstrip to strip inside a transaction
1222 1222
1223 1223 $ cd $TESTTMP
1224 1224 $ hg init delayedstrip
1225 1225 $ cd delayedstrip
1226 1226 $ hg debugdrawdag <<'EOS'
1227 1227 > D
1228 1228 > |
1229 1229 > C F H # Commit on top of "I",
1230 1230 > | |/| # Strip B+D+I+E+G+H+Z
1231 1231 > I B E G
1232 1232 > \|/
1233 1233 > A Z
1234 1234 > EOS
1235 1235 $ cp -R . ../scmutilcleanup
1236 1236
1237 1237 $ hg up -C I
1238 1238 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1239 1239 $ echo 3 >> I
1240 1240 $ cat > $TESTTMP/delayedstrip.py <<EOF
1241 1241 > from __future__ import absolute_import
1242 1242 > from mercurial import commands, registrar, repair
1243 1243 > cmdtable = {}
1244 1244 > command = registrar.command(cmdtable)
1245 1245 > @command(b'testdelayedstrip')
1246 1246 > def testdelayedstrip(ui, repo):
1247 1247 > def getnodes(expr):
1248 1248 > return [repo.changelog.node(r) for r in repo.revs(expr)]
1249 1249 > with repo.wlock():
1250 1250 > with repo.lock():
1251 1251 > with repo.transaction(b'delayedstrip'):
1252 1252 > repair.delayedstrip(ui, repo, getnodes(b'B+I+Z+D+E'), b'J')
1253 1253 > repair.delayedstrip(ui, repo, getnodes(b'G+H+Z'), b'I')
1254 1254 > commands.commit(ui, repo, message=b'J', date=b'0 0')
1255 1255 > EOF
1256 1256 $ hg testdelayedstrip --config extensions.t=$TESTTMP/delayedstrip.py
1257 1257 warning: orphaned descendants detected, not stripping 08ebfeb61bac, 112478962961, 7fb047a69f22
1258 1258 saved backup bundle to $TESTTMP/delayedstrip/.hg/strip-backup/f585351a92f8-17475721-I.hg
1259 1259
1260 1260 $ hg log -G -T '{rev}:{node|short} {desc}' -r 'sort(all(), topo)'
1261 1261 @ 6:2f2d51af6205 J
1262 1262 |
1263 1263 o 3:08ebfeb61bac I
1264 1264 |
1265 1265 | o 5:64a8289d2492 F
1266 1266 | |
1267 1267 | o 2:7fb047a69f22 E
1268 1268 |/
1269 1269 | o 4:26805aba1e60 C
1270 1270 | |
1271 1271 | o 1:112478962961 B
1272 1272 |/
1273 1273 o 0:426bada5c675 A
1274 1274
1275 1275 Test high-level scmutil.cleanupnodes API
1276 1276
1277 1277 $ cd $TESTTMP/scmutilcleanup
1278 1278 $ hg debugdrawdag <<'EOS'
1279 1279 > D2 F2 G2 # D2, F2, G2 are replacements for D, F, G
1280 1280 > | | |
1281 1281 > C H G
1282 1282 > EOS
1283 1283 $ for i in B C D F G I Z; do
1284 1284 > hg bookmark -i -r $i b-$i
1285 1285 > done
1286 1286 $ hg bookmark -i -r E 'b-F@divergent1'
1287 1287 $ hg bookmark -i -r H 'b-F@divergent2'
1288 1288 $ hg bookmark -i -r G 'b-F@divergent3'
1289 1289 $ cp -R . ../scmutilcleanup.obsstore
1290 1290
1291 1291 $ cat > $TESTTMP/scmutilcleanup.py <<EOF
1292 1292 > from mercurial import registrar, scmutil
1293 1293 > cmdtable = {}
1294 1294 > command = registrar.command(cmdtable)
1295 1295 > @command(b'testnodescleanup')
1296 1296 > def testnodescleanup(ui, repo):
1297 1297 > def nodes(expr):
1298 1298 > return [repo.changelog.node(r) for r in repo.revs(expr)]
1299 1299 > def node(expr):
1300 1300 > return nodes(expr)[0]
1301 1301 > with repo.wlock():
1302 1302 > with repo.lock():
1303 1303 > with repo.transaction(b'delayedstrip'):
1304 1304 > mapping = {node(b'F'): [node(b'F2')],
1305 1305 > node(b'D'): [node(b'D2')],
1306 1306 > node(b'G'): [node(b'G2')]}
1307 1307 > scmutil.cleanupnodes(repo, mapping, b'replace')
1308 1308 > scmutil.cleanupnodes(repo, nodes(b'((B::)+I+Z)-D2-obsolete()'),
1309 1309 > b'replace')
1310 1310 > EOF
1311 1311 $ hg testnodescleanup --config extensions.t=$TESTTMP/scmutilcleanup.py
1312 1312 warning: orphaned descendants detected, not stripping 112478962961, 1fc8102cda62, 26805aba1e60
1313 1313 saved backup bundle to $TESTTMP/scmutilcleanup/.hg/strip-backup/f585351a92f8-73fb7c03-replace.hg
1314 1314
1315 1315 $ hg log -G -T '{rev}:{node|short} {desc} {bookmarks}' -r 'sort(all(), topo)'
1316 1316 o 8:1473d4b996d1 G2 b-F@divergent3 b-G
1317 1317 |
1318 1318 | o 7:d11b3456a873 F2 b-F
1319 1319 | |
1320 1320 | o 5:5cb05ba470a7 H
1321 1321 |/|
1322 1322 | o 3:7fb047a69f22 E b-F@divergent1
1323 1323 | |
1324 1324 | | o 6:7c78f703e465 D2 b-D
1325 1325 | | |
1326 1326 | | o 4:26805aba1e60 C
1327 1327 | | |
1328 1328 | | o 2:112478962961 B
1329 1329 | |/
1330 1330 o | 1:1fc8102cda62 G
1331 1331 /
1332 1332 o 0:426bada5c675 A b-B b-C b-I
1333 1333
1334 1334 $ hg bookmark
1335 1335 b-B 0:426bada5c675
1336 1336 b-C 0:426bada5c675
1337 1337 b-D 6:7c78f703e465
1338 1338 b-F 7:d11b3456a873
1339 1339 b-F@divergent1 3:7fb047a69f22
1340 1340 b-F@divergent3 8:1473d4b996d1
1341 1341 b-G 8:1473d4b996d1
1342 1342 b-I 0:426bada5c675
1343 1343 b-Z -1:000000000000
1344 1344
1345 1345 Test the above using obsstore "by the way". Not directly related to strip, but
1346 1346 we have reusable code here
1347 1347
1348 1348 $ cd $TESTTMP/scmutilcleanup.obsstore
1349 1349 $ cat >> .hg/hgrc <<EOF
1350 1350 > [experimental]
1351 1351 > evolution=true
1352 1352 > evolution.track-operation=1
1353 1353 > EOF
1354 1354
1355 1355 $ hg testnodescleanup --config extensions.t=$TESTTMP/scmutilcleanup.py
1356 1356 4 new orphan changesets
1357 1357
1358 1358 $ rm .hg/localtags
1359 1359 $ hg log -G -T '{rev}:{node|short} {desc} {bookmarks}' -r 'sort(all(), topo)'
1360 1360 * 12:1473d4b996d1 G2 b-F@divergent3 b-G
1361 1361 |
1362 1362 | * 11:d11b3456a873 F2 b-F
1363 1363 | |
1364 1364 | * 8:5cb05ba470a7 H
1365 1365 |/|
1366 1366 | o 4:7fb047a69f22 E b-F@divergent1
1367 1367 | |
1368 1368 | | * 10:7c78f703e465 D2 b-D
1369 1369 | | |
1370 1370 | | x 6:26805aba1e60 C
1371 1371 | | |
1372 1372 | | x 3:112478962961 B
1373 1373 | |/
1374 1374 x | 1:1fc8102cda62 G
1375 1375 /
1376 1376 o 0:426bada5c675 A b-B b-C b-I
1377 1377
1378 1378 $ hg debugobsolete
1379 1379 1fc8102cda6204549f031015641606ccf5513ec3 1473d4b996d1d1b121de6b39fab6a04fbf9d873e 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'replace', 'user': 'test'}
1380 1380 64a8289d249234b9886244d379f15e6b650b28e3 d11b3456a873daec7c7bc53e5622e8df6d741bd2 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'replace', 'user': 'test'}
1381 1381 f585351a92f85104bff7c284233c338b10eb1df7 7c78f703e465d73102cc8780667ce269c5208a40 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '9', 'operation': 'replace', 'user': 'test'}
1382 1382 48b9aae0607f43ff110d84e6883c151942add5ab 0 {0000000000000000000000000000000000000000} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'replace', 'user': 'test'}
1383 1383 112478962961147124edd43549aedd1a335e44bf 0 {426bada5c67598ca65036d57d9e4b64b0c1ce7a0} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'replace', 'user': 'test'}
1384 1384 08ebfeb61bac6e3f12079de774d285a0d6689eba 0 {426bada5c67598ca65036d57d9e4b64b0c1ce7a0} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'replace', 'user': 'test'}
1385 1385 26805aba1e600a82e93661149f2313866a221a7b 0 {112478962961147124edd43549aedd1a335e44bf} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'replace', 'user': 'test'}
1386 1386 $ cd ..
1387 1387
1388 1388 Test that obsmarkers are restored even when not using generaldelta
1389 1389
1390 1390 $ hg --config format.usegeneraldelta=no init issue5678
1391 1391 $ cd issue5678
1392 1392 $ cat >> .hg/hgrc <<EOF
1393 1393 > [experimental]
1394 1394 > evolution=true
1395 1395 > EOF
1396 1396 $ echo a > a
1397 1397 $ hg ci -Aqm a
1398 1398 $ hg ci --amend -m a2
1399 1399 $ hg debugobsolete
1400 1400 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
1401 1401 $ hg strip .
1402 1402 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1403 1403 saved backup bundle to $TESTTMP/issue5678/.hg/strip-backup/489bac576828-bef27e14-backup.hg
1404 1404 $ hg unbundle -q .hg/strip-backup/*
1405 1405 $ hg debugobsolete
1406 1406 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
1407 1407 $ cd ..
@@ -1,691 +1,691 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [commands]
3 3 > status.verbose=1
4 4 > EOF
5 5
6 6 # Construct the following history tree:
7 7 #
8 8 # @ 5:e1bb631146ca b1
9 9 # |
10 10 # o 4:a4fdb3b883c4 0:b608b9236435 b1
11 11 # |
12 12 # | o 3:4b57d2520816 1:44592833ba9f
13 13 # | |
14 14 # | | o 2:063f31070f65
15 15 # | |/
16 16 # | o 1:44592833ba9f
17 17 # |/
18 18 # o 0:b608b9236435
19 19
20 20 $ mkdir b1
21 21 $ cd b1
22 22 $ hg init
23 23 $ echo foo > foo
24 24 $ echo zero > a
25 25 $ hg init sub
26 26 $ echo suba > sub/suba
27 27 $ hg --cwd sub ci -Am addsuba
28 28 adding suba
29 29 $ echo 'sub = sub' > .hgsub
30 30 $ hg ci -qAm0
31 31 $ echo one > a ; hg ci -m1
32 32 $ echo two > a ; hg ci -m2
33 33 $ hg up -q 1
34 34 $ echo three > a ; hg ci -qm3
35 35 $ hg up -q 0
36 36 $ hg branch -q b1
37 37 $ echo four > a ; hg ci -qm4
38 38 $ echo five > a ; hg ci -qm5
39 39
40 40 Initial repo state:
41 41
42 42 $ hg log -G --template '{rev}:{node|short} {parents} {branches}\n'
43 43 @ 5:ff252e8273df b1
44 44 |
45 45 o 4:d047485b3896 0:60829823a42a b1
46 46 |
47 47 | o 3:6efa171f091b 1:0786582aa4b1
48 48 | |
49 49 | | o 2:bd10386d478c
50 50 | |/
51 51 | o 1:0786582aa4b1
52 52 |/
53 53 o 0:60829823a42a
54 54
55 55
56 56 Make sure update doesn't assume b1 is a repository if invoked from outside:
57 57
58 58 $ cd ..
59 59 $ hg update b1
60 60 abort: no repository found in '$TESTTMP' (.hg not found)!
61 61 [255]
62 62 $ cd b1
63 63
64 64 Test helper functions:
65 65
66 66 $ revtest () {
67 67 > msg=$1
68 68 > dirtyflag=$2 # 'clean', 'dirty' or 'dirtysub'
69 69 > startrev=$3
70 70 > targetrev=$4
71 71 > opt=$5
72 72 > hg up -qC $startrev
73 73 > test $dirtyflag = dirty && echo dirty > foo
74 74 > test $dirtyflag = dirtysub && echo dirty > sub/suba
75 75 > hg up $opt $targetrev
76 76 > hg parent --template 'parent={rev}\n'
77 77 > hg stat -S
78 78 > }
79 79
80 80 $ norevtest () {
81 81 > msg=$1
82 82 > dirtyflag=$2 # 'clean', 'dirty' or 'dirtysub'
83 83 > startrev=$3
84 84 > opt=$4
85 85 > hg up -qC $startrev
86 86 > test $dirtyflag = dirty && echo dirty > foo
87 87 > test $dirtyflag = dirtysub && echo dirty > sub/suba
88 88 > hg up $opt
89 89 > hg parent --template 'parent={rev}\n'
90 90 > hg stat -S
91 91 > }
92 92
93 93 Test cases are documented in a table in the update function of merge.py.
94 94 Cases are run as shown in that table, row by row.
95 95
96 96 $ norevtest 'none clean linear' clean 4
97 97 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
98 98 parent=5
99 99
100 100 $ norevtest 'none clean same' clean 2
101 101 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 102 updated to "bd10386d478c: 2"
103 103 1 other heads for branch "default"
104 104 parent=2
105 105
106 106
107 107 $ revtest 'none clean linear' clean 1 2
108 108 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
109 109 parent=2
110 110
111 111 $ revtest 'none clean same' clean 2 3
112 112 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
113 113 parent=3
114 114
115 115 $ revtest 'none clean cross' clean 3 4
116 116 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 117 parent=4
118 118
119 119
120 120 $ revtest 'none dirty linear' dirty 1 2
121 121 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
122 122 parent=2
123 123 M foo
124 124
125 125 $ revtest 'none dirtysub linear' dirtysub 1 2
126 126 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
127 127 parent=2
128 128 M sub/suba
129 129
130 130 $ revtest 'none dirty same' dirty 2 3
131 131 abort: uncommitted changes
132 132 (commit or update --clean to discard changes)
133 133 parent=2
134 134 M foo
135 135
136 136 $ revtest 'none dirtysub same' dirtysub 2 3
137 137 abort: uncommitted changes
138 138 (commit or update --clean to discard changes)
139 139 parent=2
140 140 M sub/suba
141 141
142 142 $ revtest 'none dirty cross' dirty 3 4
143 143 abort: uncommitted changes
144 144 (commit or update --clean to discard changes)
145 145 parent=3
146 146 M foo
147 147
148 148 $ norevtest 'none dirty cross' dirty 2
149 149 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
150 150 updated to "bd10386d478c: 2"
151 151 1 other heads for branch "default"
152 152 parent=2
153 153 M foo
154 154
155 155 $ revtest 'none dirtysub cross' dirtysub 3 4
156 156 abort: uncommitted changes
157 157 (commit or update --clean to discard changes)
158 158 parent=3
159 159 M sub/suba
160 160
161 161 $ revtest '-C dirty linear' dirty 1 2 -C
162 162 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
163 163 parent=2
164 164
165 165 $ revtest '-c dirty linear' dirty 1 2 -c
166 166 abort: uncommitted changes
167 167 parent=1
168 168 M foo
169 169
170 170 $ revtest '-m dirty linear' dirty 1 2 -m
171 171 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
172 172 parent=2
173 173 M foo
174 174
175 175 $ revtest '-m dirty cross' dirty 3 4 -m
176 176 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
177 177 parent=4
178 178 M foo
179 179
180 180 $ revtest '-c dirtysub linear' dirtysub 1 2 -c
181 181 abort: uncommitted changes in subrepository "sub"
182 182 parent=1
183 183 M sub/suba
184 184
185 185 $ norevtest '-c clean same' clean 2 -c
186 186 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
187 187 updated to "bd10386d478c: 2"
188 188 1 other heads for branch "default"
189 189 parent=2
190 190
191 191 $ revtest '-cC dirty linear' dirty 1 2 -cC
192 192 abort: can only specify one of -C/--clean, -c/--check, or -m/--merge
193 193 parent=1
194 194 M foo
195 195
196 196 $ revtest '-mc dirty linear' dirty 1 2 -mc
197 197 abort: can only specify one of -C/--clean, -c/--check, or -m/--merge
198 198 parent=1
199 199 M foo
200 200
201 201 $ revtest '-mC dirty linear' dirty 1 2 -mC
202 202 abort: can only specify one of -C/--clean, -c/--check, or -m/--merge
203 203 parent=1
204 204 M foo
205 205
206 206 $ echo '[commands]' >> .hg/hgrc
207 207 $ echo 'update.check = abort' >> .hg/hgrc
208 208
209 209 $ revtest 'none dirty linear' dirty 1 2
210 210 abort: uncommitted changes
211 211 parent=1
212 212 M foo
213 213
214 214 $ revtest 'none dirty linear' dirty 1 2 -c
215 215 abort: uncommitted changes
216 216 parent=1
217 217 M foo
218 218
219 219 $ revtest 'none dirty linear' dirty 1 2 -C
220 220 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
221 221 parent=2
222 222
223 223 $ echo 'update.check = none' >> .hg/hgrc
224 224
225 225 $ revtest 'none dirty cross' dirty 3 4
226 226 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
227 227 parent=4
228 228 M foo
229 229
230 230 $ revtest 'none dirty linear' dirty 1 2
231 231 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
232 232 parent=2
233 233 M foo
234 234
235 235 $ revtest 'none dirty linear' dirty 1 2 -c
236 236 abort: uncommitted changes
237 237 parent=1
238 238 M foo
239 239
240 240 $ revtest 'none dirty linear' dirty 1 2 -C
241 241 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
242 242 parent=2
243 243
244 244 $ hg co -qC 3
245 245 $ echo dirty >> a
246 246 $ hg co --tool :merge3 4
247 247 merging a
248 248 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
249 249 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
250 250 use 'hg resolve' to retry unresolved file merges
251 251 [1]
252 252 $ hg log -G --template '{rev}:{node|short} {parents} {branches}\n'
253 253 o 5:ff252e8273df b1
254 254 |
255 255 @ 4:d047485b3896 0:60829823a42a b1
256 256 |
257 | o 3:6efa171f091b 1:0786582aa4b1
257 | % 3:6efa171f091b 1:0786582aa4b1
258 258 | |
259 259 | | o 2:bd10386d478c
260 260 | |/
261 261 | o 1:0786582aa4b1
262 262 |/
263 263 o 0:60829823a42a
264 264
265 265 $ hg st
266 266 M a
267 267 ? a.orig
268 268 # Unresolved merge conflicts:
269 269 #
270 270 # a
271 271 #
272 272 # To mark files as resolved: hg resolve --mark FILE
273 273
274 274 $ cat a
275 275 <<<<<<< working copy: 6efa171f091b - test: 3
276 276 three
277 277 dirty
278 278 ||||||| base
279 279 three
280 280 =======
281 281 four
282 282 >>>>>>> destination: d047485b3896 b1 - test: 4
283 283 $ rm a.orig
284 284
285 285 $ echo 'update.check = noconflict' >> .hg/hgrc
286 286
287 287 $ revtest 'none dirty cross' dirty 3 4
288 288 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
289 289 parent=4
290 290 M foo
291 291
292 292 $ revtest 'none dirty linear' dirty 1 2
293 293 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
294 294 parent=2
295 295 M foo
296 296
297 297 $ revtest 'none dirty linear' dirty 1 2 -c
298 298 abort: uncommitted changes
299 299 parent=1
300 300 M foo
301 301
302 302 $ revtest 'none dirty linear' dirty 1 2 -C
303 303 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
304 304 parent=2
305 305
306 306 Locally added file is allowed
307 307 $ hg up -qC 3
308 308 $ echo a > bar
309 309 $ hg add bar
310 310 $ hg up -q 4
311 311 $ hg st
312 312 A bar
313 313 $ hg forget bar
314 314 $ rm bar
315 315
316 316 Locally removed file is allowed
317 317 $ hg up -qC 3
318 318 $ hg rm foo
319 319 $ hg up -q 4
320 320
321 321 File conflict is not allowed
322 322 $ hg up -qC 3
323 323 $ echo dirty >> a
324 324 $ hg up -q 4
325 325 abort: conflicting changes
326 326 (commit or update --clean to discard changes)
327 327 [255]
328 328 $ hg up -m 4
329 329 merging a
330 330 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
331 331 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
332 332 use 'hg resolve' to retry unresolved file merges
333 333 [1]
334 334 $ rm a.orig
335 335 $ hg status
336 336 M a
337 337 # Unresolved merge conflicts:
338 338 #
339 339 # a
340 340 #
341 341 # To mark files as resolved: hg resolve --mark FILE
342 342
343 343 $ hg resolve -l
344 344 U a
345 345
346 346 Change/delete conflict is not allowed
347 347 $ hg up -qC 3
348 348 $ hg rm foo
349 349 $ hg up -q 4
350 350
351 351 Uses default value of "linear" when value is misspelled
352 352 $ echo 'update.check = linyar' >> .hg/hgrc
353 353
354 354 $ revtest 'dirty cross' dirty 3 4
355 355 abort: uncommitted changes
356 356 (commit or update --clean to discard changes)
357 357 parent=3
358 358 M foo
359 359
360 360 Setup for later tests
361 361 $ revtest 'none dirty linear' dirty 1 2 -c
362 362 abort: uncommitted changes
363 363 parent=1
364 364 M foo
365 365
366 366 $ cd ..
367 367
368 368 Test updating to null revision
369 369
370 370 $ hg init null-repo
371 371 $ cd null-repo
372 372 $ echo a > a
373 373 $ hg add a
374 374 $ hg ci -m a
375 375 $ hg up -qC 0
376 376 $ echo b > b
377 377 $ hg add b
378 378 $ hg up null
379 379 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
380 380 $ hg st
381 381 A b
382 382 $ hg up -q 0
383 383 $ hg st
384 384 A b
385 385 $ hg up -qC null
386 386 $ hg st
387 387 ? b
388 388 $ cd ..
389 389
390 390 Test updating with closed head
391 391 ---------------------------------------------------------------------
392 392
393 393 $ hg clone -U -q b1 closed-heads
394 394 $ cd closed-heads
395 395
396 396 Test updating if at least one non-closed branch head exists
397 397
398 398 if on the closed branch head:
399 399 - update to "."
400 400 - "updated to a closed branch head ...." message is displayed
401 401 - "N other heads for ...." message is displayed
402 402
403 403 $ hg update -q -C 3
404 404 $ hg commit --close-branch -m 6
405 405 $ norevtest "on closed branch head" clean 6
406 406 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
407 407 no open descendant heads on branch "default", updating to a closed head
408 408 (committing will reopen the head, use 'hg heads .' to see 1 other heads)
409 409 parent=6
410 410
411 411 if descendant non-closed branch head exists, and it is only one branch head:
412 412 - update to it, even if its revision is less than closed one
413 413 - "N other heads for ...." message isn't displayed
414 414
415 415 $ norevtest "non-closed 2 should be chosen" clean 1
416 416 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
417 417 parent=2
418 418
419 419 if all descendant branch heads are closed, but there is another branch head:
420 420 - update to the tipmost descendant head
421 421 - "updated to a closed branch head ...." message is displayed
422 422 - "N other heads for ...." message is displayed
423 423
424 424 $ norevtest "all descendant branch heads are closed" clean 3
425 425 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
426 426 no open descendant heads on branch "default", updating to a closed head
427 427 (committing will reopen the head, use 'hg heads .' to see 1 other heads)
428 428 parent=6
429 429
430 430 Test updating if all branch heads are closed
431 431
432 432 if on the closed branch head:
433 433 - update to "."
434 434 - "updated to a closed branch head ...." message is displayed
435 435 - "all heads of branch ...." message is displayed
436 436
437 437 $ hg update -q -C 2
438 438 $ hg commit --close-branch -m 7
439 439 $ norevtest "all heads of branch default are closed" clean 6
440 440 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
441 441 no open descendant heads on branch "default", updating to a closed head
442 442 (committing will reopen branch "default")
443 443 parent=6
444 444
445 445 if not on the closed branch head:
446 446 - update to the tipmost descendant (closed) head
447 447 - "updated to a closed branch head ...." message is displayed
448 448 - "all heads of branch ...." message is displayed
449 449
450 450 $ norevtest "all heads of branch default are closed" clean 1
451 451 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
452 452 no open descendant heads on branch "default", updating to a closed head
453 453 (committing will reopen branch "default")
454 454 parent=7
455 455
456 456 $ cd ..
457 457
458 458 Test updating if "default" branch doesn't exist and no revision is
459 459 checked out (= "default" is used as current branch)
460 460
461 461 $ hg init no-default-branch
462 462 $ cd no-default-branch
463 463
464 464 $ hg branch foobar
465 465 marked working directory as branch foobar
466 466 (branches are permanent and global, did you want a bookmark?)
467 467 $ echo a > a
468 468 $ hg commit -m "#0" -A
469 469 adding a
470 470 $ echo 1 >> a
471 471 $ hg commit -m "#1"
472 472 $ hg update -q 0
473 473 $ echo 3 >> a
474 474 $ hg commit -m "#2"
475 475 created new head
476 476 $ hg commit --close-branch -m "#3"
477 477
478 478 if there is at least one non-closed branch head:
479 479 - update to the tipmost branch head
480 480
481 481 $ norevtest "non-closed 1 should be chosen" clean null
482 482 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
483 483 parent=1
484 484
485 485 if all branch heads are closed
486 486 - update to "tip"
487 487 - "updated to a closed branch head ...." message is displayed
488 488 - "all heads for branch "XXXX" are closed" message is displayed
489 489
490 490 $ hg update -q -C 1
491 491 $ hg commit --close-branch -m "#4"
492 492
493 493 $ norevtest "all branches are closed" clean null
494 494 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
495 495 no open descendant heads on branch "foobar", updating to a closed head
496 496 (committing will reopen branch "foobar")
497 497 parent=4
498 498
499 499 $ cd ../b1
500 500
501 501 Test obsolescence behavior
502 502 ---------------------------------------------------------------------
503 503
504 504 successors should be taken in account when checking head destination
505 505
506 506 $ cat << EOF >> $HGRCPATH
507 507 > [ui]
508 508 > logtemplate={rev}:{node|short} {desc|firstline}
509 509 > [experimental]
510 510 > evolution.createmarkers=True
511 511 > EOF
512 512
513 513 Test no-argument update to a successor of an obsoleted changeset
514 514
515 515 $ hg log -G
516 516 o 5:ff252e8273df 5
517 517 |
518 518 o 4:d047485b3896 4
519 519 |
520 520 | o 3:6efa171f091b 3
521 521 | |
522 522 | | o 2:bd10386d478c 2
523 523 | |/
524 524 | @ 1:0786582aa4b1 1
525 525 |/
526 526 o 0:60829823a42a 0
527 527
528 528 $ hg book bm -r 3
529 529 $ hg status
530 530 M foo
531 531
532 532 We add simple obsolescence marker between 3 and 4 (indirect successors)
533 533
534 534 $ hg id --debug -i -r 3
535 535 6efa171f091b00a3c35edc15d48c52a498929953
536 536 $ hg id --debug -i -r 4
537 537 d047485b3896813b2a624e86201983520f003206
538 538 $ hg debugobsolete 6efa171f091b00a3c35edc15d48c52a498929953 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
539 539 1 new obsolescence markers
540 540 obsoleted 1 changesets
541 541 $ hg debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa d047485b3896813b2a624e86201983520f003206
542 542 1 new obsolescence markers
543 543
544 544 Test that 5 is detected as a valid destination from 3 and also accepts moving
545 545 the bookmark (issue4015)
546 546
547 547 $ hg up --quiet --hidden 3
548 548 $ hg up 5
549 549 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
550 550 $ hg book bm
551 551 moving bookmark 'bm' forward from 6efa171f091b
552 552 $ hg bookmarks
553 553 * bm 5:ff252e8273df
554 554
555 555 Test that we abort before we warn about the hidden commit if the working
556 556 directory is dirty
557 557 $ echo conflict > a
558 558 $ hg up --hidden 3
559 559 abort: uncommitted changes
560 560 (commit or update --clean to discard changes)
561 561 [255]
562 562
563 563 Test that we still warn also when there are conflicts
564 564 $ hg up -m --hidden 3
565 565 merging a
566 566 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
567 567 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
568 568 use 'hg resolve' to retry unresolved file merges
569 569 (leaving bookmark bm)
570 570 updated to hidden changeset 6efa171f091b
571 571 (hidden revision '6efa171f091b' was rewritten as: d047485b3896)
572 572 [1]
573 573
574 574 Test that statuses are reported properly before and after merge resolution.
575 575 $ rm a.orig
576 576 $ hg resolve -l
577 577 U a
578 578 $ hg status
579 579 M a
580 580 M foo
581 581 # Unresolved merge conflicts:
582 582 #
583 583 # a
584 584 #
585 585 # To mark files as resolved: hg resolve --mark FILE
586 586
587 587
588 588 $ hg revert -r . a
589 589
590 590 $ rm a.orig
591 591 $ hg resolve -l
592 592 U a
593 593 $ hg status
594 594 M foo
595 595 # Unresolved merge conflicts:
596 596 #
597 597 # a
598 598 #
599 599 # To mark files as resolved: hg resolve --mark FILE
600 600
601 601 $ hg status -Tjson
602 602 [
603 603 {
604 604 "itemtype": "file",
605 605 "path": "foo",
606 606 "status": "M"
607 607 },
608 608 {
609 609 "itemtype": "file",
610 610 "path": "a",
611 611 "unresolved": true
612 612 }
613 613 ]
614 614
615 615 $ hg resolve -m
616 616 (no more unresolved files)
617 617
618 618 $ hg resolve -l
619 619 R a
620 620 $ hg status
621 621 M foo
622 622 # No unresolved merge conflicts.
623 623
624 624 $ hg status -Tjson
625 625 [
626 626 {
627 627 "itemtype": "file",
628 628 "path": "foo",
629 629 "status": "M"
630 630 }
631 631 ]
632 632
633 633 Test that 4 is detected as the no-argument destination from 3 and also moves
634 634 the bookmark with it
635 635 $ hg up --quiet 0 # we should be able to update to 3 directly
636 636 $ hg status
637 637 M foo
638 638 $ hg up --quiet --hidden 3 # but not implemented yet.
639 639 updated to hidden changeset 6efa171f091b
640 640 (hidden revision '6efa171f091b' was rewritten as: d047485b3896)
641 641 $ hg book -f bm
642 642 $ hg up
643 643 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
644 644 updating bookmark bm
645 645 $ hg book
646 646 * bm 4:d047485b3896
647 647
648 648 Test that 5 is detected as a valid destination from 1
649 649 $ hg up --quiet 0 # we should be able to update to 3 directly
650 650 $ hg up --quiet --hidden 3 # but not implemented yet.
651 651 updated to hidden changeset 6efa171f091b
652 652 (hidden revision '6efa171f091b' was rewritten as: d047485b3896)
653 653 $ hg up 5
654 654 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
655 655
656 656 Test that 5 is not detected as a valid destination from 2
657 657 $ hg up --quiet 0
658 658 $ hg up --quiet 2
659 659 $ hg up 5
660 660 abort: uncommitted changes
661 661 (commit or update --clean to discard changes)
662 662 [255]
663 663
664 664 Test that we don't crash when updating from a pruned changeset (i.e. has no
665 665 successors). Behavior should probably be that we update to the first
666 666 non-obsolete parent but that will be decided later.
667 667 $ hg id --debug -r 2
668 668 bd10386d478cd5a9faf2e604114c8e6da62d3889
669 669 $ hg up --quiet 0
670 670 $ hg up --quiet 2
671 671 $ hg debugobsolete bd10386d478cd5a9faf2e604114c8e6da62d3889
672 672 1 new obsolescence markers
673 673 obsoleted 1 changesets
674 674 $ hg up
675 675 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
676 676
677 677 Test experimental revset support
678 678
679 679 $ hg log -r '_destupdate()'
680 680 2:bd10386d478c 2 (no-eol)
681 681
682 682 Test that boolean flags allow --no-flag specification to override [defaults]
683 683 $ cat >> $HGRCPATH <<EOF
684 684 > [defaults]
685 685 > update = --check
686 686 > EOF
687 687 $ hg co 2
688 688 abort: uncommitted changes
689 689 [255]
690 690 $ hg co --no-check 2
691 691 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
General Comments 0
You need to be logged in to leave comments. Login now