##// END OF EJS Templates
histedit: narrow the scope of discarded ui output...
Rodrigo Damazio Bovendorp -
r42219:15d2afa3 default
parent child Browse files
Show More
@@ -0,0 +1,57 b''
1 Test histedit extension: Merge tools
2 ====================================
3
4 Initialization
5 ---------------
6
7 $ . "$TESTDIR/histedit-helpers.sh"
8
9 $ cat >> $HGRCPATH <<EOF
10 > [alias]
11 > logt = log --template '{rev}:{node|short} {desc|firstline}\n'
12 > [extensions]
13 > histedit=
14 > mockmakedate = $TESTDIR/mockmakedate.py
15 > [ui]
16 > pre-merge-tool-output-template='pre-merge message for {node}\n'
17 > EOF
18
19 Merge conflict
20 --------------
21
22 $ hg init r
23 $ cd r
24 $ echo foo > file
25 $ hg add file
26 $ hg ci -m "First" -d "1 0"
27 $ echo bar > file
28 $ hg ci -m "Second" -d "2 0"
29
30 $ hg logt --graph
31 @ 1:2aa920f62fb9 Second
32 |
33 o 0:7181f42b8fca First
34
35
36 Invert the order of the commits, but fail the merge.
37 $ hg histedit --config ui.merge=false --commands - 2>&1 <<EOF | fixbundle
38 > pick 2aa920f62fb9 Second
39 > pick 7181f42b8fca First
40 > EOF
41 merging file
42 pre-merge message for b90fa2e91a6d11013945a5f684be45b84a8ca6ec
43 merging file failed!
44 Fix up the change (pick 7181f42b8fca)
45 (hg histedit --continue to resume)
46
47 $ hg histedit --abort | fixbundle
48 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
49
50 Invert the order of the commits, and pretend the merge succeeded.
51 $ hg histedit --config ui.merge=true --commands - 2>&1 <<EOF | fixbundle
52 > pick 2aa920f62fb9 Second
53 > pick 7181f42b8fca First
54 > EOF
55 merging file
56 pre-merge message for b90fa2e91a6d11013945a5f684be45b84a8ca6ec
57 7181f42b8fca: skipping changeset (no changes)
@@ -1,2272 +1,2271 b''
1 1 # histedit.py - interactive history editing for mercurial
2 2 #
3 3 # Copyright 2009 Augie Fackler <raf@durin42.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 """interactive history editing
8 8
9 9 With this extension installed, Mercurial gains one new command: histedit. Usage
10 10 is as follows, assuming the following history::
11 11
12 12 @ 3[tip] 7c2fd3b9020c 2009-04-27 18:04 -0500 durin42
13 13 | Add delta
14 14 |
15 15 o 2 030b686bedc4 2009-04-27 18:04 -0500 durin42
16 16 | Add gamma
17 17 |
18 18 o 1 c561b4e977df 2009-04-27 18:04 -0500 durin42
19 19 | Add beta
20 20 |
21 21 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
22 22 Add alpha
23 23
24 24 If you were to run ``hg histedit c561b4e977df``, you would see the following
25 25 file open in your editor::
26 26
27 27 pick c561b4e977df Add beta
28 28 pick 030b686bedc4 Add gamma
29 29 pick 7c2fd3b9020c Add delta
30 30
31 31 # Edit history between c561b4e977df and 7c2fd3b9020c
32 32 #
33 33 # Commits are listed from least to most recent
34 34 #
35 35 # Commands:
36 36 # p, pick = use commit
37 37 # e, edit = use commit, but stop for amending
38 38 # f, fold = use commit, but combine it with the one above
39 39 # r, roll = like fold, but discard this commit's description and date
40 40 # d, drop = remove commit from history
41 41 # m, mess = edit commit message without changing commit content
42 42 # b, base = checkout changeset and apply further changesets from there
43 43 #
44 44
45 45 In this file, lines beginning with ``#`` are ignored. You must specify a rule
46 46 for each revision in your history. For example, if you had meant to add gamma
47 47 before beta, and then wanted to add delta in the same revision as beta, you
48 48 would reorganize the file to look like this::
49 49
50 50 pick 030b686bedc4 Add gamma
51 51 pick c561b4e977df Add beta
52 52 fold 7c2fd3b9020c Add delta
53 53
54 54 # Edit history between c561b4e977df and 7c2fd3b9020c
55 55 #
56 56 # Commits are listed from least to most recent
57 57 #
58 58 # Commands:
59 59 # p, pick = use commit
60 60 # e, edit = use commit, but stop for amending
61 61 # f, fold = use commit, but combine it with the one above
62 62 # r, roll = like fold, but discard this commit's description and date
63 63 # d, drop = remove commit from history
64 64 # m, mess = edit commit message without changing commit content
65 65 # b, base = checkout changeset and apply further changesets from there
66 66 #
67 67
68 68 At which point you close the editor and ``histedit`` starts working. When you
69 69 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
70 70 those revisions together, offering you a chance to clean up the commit message::
71 71
72 72 Add beta
73 73 ***
74 74 Add delta
75 75
76 76 Edit the commit message to your liking, then close the editor. The date used
77 77 for the commit will be the later of the two commits' dates. For this example,
78 78 let's assume that the commit message was changed to ``Add beta and delta.``
79 79 After histedit has run and had a chance to remove any old or temporary
80 80 revisions it needed, the history looks like this::
81 81
82 82 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
83 83 | Add beta and delta.
84 84 |
85 85 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
86 86 | Add gamma
87 87 |
88 88 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
89 89 Add alpha
90 90
91 91 Note that ``histedit`` does *not* remove any revisions (even its own temporary
92 92 ones) until after it has completed all the editing operations, so it will
93 93 probably perform several strip operations when it's done. For the above example,
94 94 it had to run strip twice. Strip can be slow depending on a variety of factors,
95 95 so you might need to be a little patient. You can choose to keep the original
96 96 revisions by passing the ``--keep`` flag.
97 97
98 98 The ``edit`` operation will drop you back to a command prompt,
99 99 allowing you to edit files freely, or even use ``hg record`` to commit
100 100 some changes as a separate commit. When you're done, any remaining
101 101 uncommitted changes will be committed as well. When done, run ``hg
102 102 histedit --continue`` to finish this step. If there are uncommitted
103 103 changes, you'll be prompted for a new commit message, but the default
104 104 commit message will be the original message for the ``edit`` ed
105 105 revision, and the date of the original commit will be preserved.
106 106
107 107 The ``message`` operation will give you a chance to revise a commit
108 108 message without changing the contents. It's a shortcut for doing
109 109 ``edit`` immediately followed by `hg histedit --continue``.
110 110
111 111 If ``histedit`` encounters a conflict when moving a revision (while
112 112 handling ``pick`` or ``fold``), it'll stop in a similar manner to
113 113 ``edit`` with the difference that it won't prompt you for a commit
114 114 message when done. If you decide at this point that you don't like how
115 115 much work it will be to rearrange history, or that you made a mistake,
116 116 you can use ``hg histedit --abort`` to abandon the new changes you
117 117 have made and return to the state before you attempted to edit your
118 118 history.
119 119
120 120 If we clone the histedit-ed example repository above and add four more
121 121 changes, such that we have the following history::
122 122
123 123 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
124 124 | Add theta
125 125 |
126 126 o 5 140988835471 2009-04-27 18:04 -0500 stefan
127 127 | Add eta
128 128 |
129 129 o 4 122930637314 2009-04-27 18:04 -0500 stefan
130 130 | Add zeta
131 131 |
132 132 o 3 836302820282 2009-04-27 18:04 -0500 stefan
133 133 | Add epsilon
134 134 |
135 135 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
136 136 | Add beta and delta.
137 137 |
138 138 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
139 139 | Add gamma
140 140 |
141 141 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
142 142 Add alpha
143 143
144 144 If you run ``hg histedit --outgoing`` on the clone then it is the same
145 145 as running ``hg histedit 836302820282``. If you need plan to push to a
146 146 repository that Mercurial does not detect to be related to the source
147 147 repo, you can add a ``--force`` option.
148 148
149 149 Config
150 150 ------
151 151
152 152 Histedit rule lines are truncated to 80 characters by default. You
153 153 can customize this behavior by setting a different length in your
154 154 configuration file::
155 155
156 156 [histedit]
157 157 linelen = 120 # truncate rule lines at 120 characters
158 158
159 159 The summary of a change can be customized as well::
160 160
161 161 [histedit]
162 162 summary-template = '{rev} {bookmarks} {desc|firstline}'
163 163
164 164 The customized summary should be kept short enough that rule lines
165 165 will fit in the configured line length. See above if that requires
166 166 customization.
167 167
168 168 ``hg histedit`` attempts to automatically choose an appropriate base
169 169 revision to use. To change which base revision is used, define a
170 170 revset in your configuration file::
171 171
172 172 [histedit]
173 173 defaultrev = only(.) & draft()
174 174
175 175 By default each edited revision needs to be present in histedit commands.
176 176 To remove revision you need to use ``drop`` operation. You can configure
177 177 the drop to be implicit for missing commits by adding::
178 178
179 179 [histedit]
180 180 dropmissing = True
181 181
182 182 By default, histedit will close the transaction after each action. For
183 183 performance purposes, you can configure histedit to use a single transaction
184 184 across the entire histedit. WARNING: This setting introduces a significant risk
185 185 of losing the work you've done in a histedit if the histedit aborts
186 186 unexpectedly::
187 187
188 188 [histedit]
189 189 singletransaction = True
190 190
191 191 """
192 192
193 193 from __future__ import absolute_import
194 194
195 195 # chistedit dependencies that are not available everywhere
196 196 try:
197 197 import fcntl
198 198 import termios
199 199 except ImportError:
200 200 fcntl = None
201 201 termios = None
202 202
203 203 import functools
204 204 import os
205 205 import struct
206 206
207 207 from mercurial.i18n import _
208 208 from mercurial import (
209 209 bundle2,
210 210 cmdutil,
211 211 context,
212 212 copies,
213 213 destutil,
214 214 discovery,
215 215 error,
216 216 exchange,
217 217 extensions,
218 218 hg,
219 219 logcmdutil,
220 220 merge as mergemod,
221 221 mergeutil,
222 222 node,
223 223 obsolete,
224 224 pycompat,
225 225 registrar,
226 226 repair,
227 227 scmutil,
228 228 state as statemod,
229 229 util,
230 230 )
231 231 from mercurial.utils import (
232 232 dateutil,
233 233 stringutil,
234 234 )
235 235
236 236 pickle = util.pickle
237 237 cmdtable = {}
238 238 command = registrar.command(cmdtable)
239 239
240 240 configtable = {}
241 241 configitem = registrar.configitem(configtable)
242 242 configitem('experimental', 'histedit.autoverb',
243 243 default=False,
244 244 )
245 245 configitem('histedit', 'defaultrev',
246 246 default=None,
247 247 )
248 248 configitem('histedit', 'dropmissing',
249 249 default=False,
250 250 )
251 251 configitem('histedit', 'linelen',
252 252 default=80,
253 253 )
254 254 configitem('histedit', 'singletransaction',
255 255 default=False,
256 256 )
257 257 configitem('ui', 'interface.histedit',
258 258 default=None,
259 259 )
260 260 configitem('histedit', 'summary-template',
261 261 default='{rev} {desc|firstline}')
262 262
263 263 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
264 264 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
265 265 # be specifying the version(s) of Mercurial they are tested with, or
266 266 # leave the attribute unspecified.
267 267 testedwith = 'ships-with-hg-core'
268 268
269 269 actiontable = {}
270 270 primaryactions = set()
271 271 secondaryactions = set()
272 272 tertiaryactions = set()
273 273 internalactions = set()
274 274
275 275 def geteditcomment(ui, first, last):
276 276 """ construct the editor comment
277 277 The comment includes::
278 278 - an intro
279 279 - sorted primary commands
280 280 - sorted short commands
281 281 - sorted long commands
282 282 - additional hints
283 283
284 284 Commands are only included once.
285 285 """
286 286 intro = _("""Edit history between %s and %s
287 287
288 288 Commits are listed from least to most recent
289 289
290 290 You can reorder changesets by reordering the lines
291 291
292 292 Commands:
293 293 """)
294 294 actions = []
295 295 def addverb(v):
296 296 a = actiontable[v]
297 297 lines = a.message.split("\n")
298 298 if len(a.verbs):
299 299 v = ', '.join(sorted(a.verbs, key=lambda v: len(v)))
300 300 actions.append(" %s = %s" % (v, lines[0]))
301 301 actions.extend([' %s' for l in lines[1:]])
302 302
303 303 for v in (
304 304 sorted(primaryactions) +
305 305 sorted(secondaryactions) +
306 306 sorted(tertiaryactions)
307 307 ):
308 308 addverb(v)
309 309 actions.append('')
310 310
311 311 hints = []
312 312 if ui.configbool('histedit', 'dropmissing'):
313 313 hints.append("Deleting a changeset from the list "
314 314 "will DISCARD it from the edited history!")
315 315
316 316 lines = (intro % (first, last)).split('\n') + actions + hints
317 317
318 318 return ''.join(['# %s\n' % l if l else '#\n' for l in lines])
319 319
320 320 class histeditstate(object):
321 321 def __init__(self, repo):
322 322 self.repo = repo
323 323 self.actions = None
324 324 self.keep = None
325 325 self.topmost = None
326 326 self.parentctxnode = None
327 327 self.lock = None
328 328 self.wlock = None
329 329 self.backupfile = None
330 330 self.stateobj = statemod.cmdstate(repo, 'histedit-state')
331 331 self.replacements = []
332 332
333 333 def read(self):
334 334 """Load histedit state from disk and set fields appropriately."""
335 335 if not self.stateobj.exists():
336 336 cmdutil.wrongtooltocontinue(self.repo, _('histedit'))
337 337
338 338 data = self._read()
339 339
340 340 self.parentctxnode = data['parentctxnode']
341 341 actions = parserules(data['rules'], self)
342 342 self.actions = actions
343 343 self.keep = data['keep']
344 344 self.topmost = data['topmost']
345 345 self.replacements = data['replacements']
346 346 self.backupfile = data['backupfile']
347 347
348 348 def _read(self):
349 349 fp = self.repo.vfs.read('histedit-state')
350 350 if fp.startswith('v1\n'):
351 351 data = self._load()
352 352 parentctxnode, rules, keep, topmost, replacements, backupfile = data
353 353 else:
354 354 data = pickle.loads(fp)
355 355 parentctxnode, rules, keep, topmost, replacements = data
356 356 backupfile = None
357 357 rules = "\n".join(["%s %s" % (verb, rest) for [verb, rest] in rules])
358 358
359 359 return {'parentctxnode': parentctxnode, "rules": rules, "keep": keep,
360 360 "topmost": topmost, "replacements": replacements,
361 361 "backupfile": backupfile}
362 362
363 363 def write(self, tr=None):
364 364 if tr:
365 365 tr.addfilegenerator('histedit-state', ('histedit-state',),
366 366 self._write, location='plain')
367 367 else:
368 368 with self.repo.vfs("histedit-state", "w") as f:
369 369 self._write(f)
370 370
371 371 def _write(self, fp):
372 372 fp.write('v1\n')
373 373 fp.write('%s\n' % node.hex(self.parentctxnode))
374 374 fp.write('%s\n' % node.hex(self.topmost))
375 375 fp.write('%s\n' % ('True' if self.keep else 'False'))
376 376 fp.write('%d\n' % len(self.actions))
377 377 for action in self.actions:
378 378 fp.write('%s\n' % action.tostate())
379 379 fp.write('%d\n' % len(self.replacements))
380 380 for replacement in self.replacements:
381 381 fp.write('%s%s\n' % (node.hex(replacement[0]), ''.join(node.hex(r)
382 382 for r in replacement[1])))
383 383 backupfile = self.backupfile
384 384 if not backupfile:
385 385 backupfile = ''
386 386 fp.write('%s\n' % backupfile)
387 387
388 388 def _load(self):
389 389 fp = self.repo.vfs('histedit-state', 'r')
390 390 lines = [l[:-1] for l in fp.readlines()]
391 391
392 392 index = 0
393 393 lines[index] # version number
394 394 index += 1
395 395
396 396 parentctxnode = node.bin(lines[index])
397 397 index += 1
398 398
399 399 topmost = node.bin(lines[index])
400 400 index += 1
401 401
402 402 keep = lines[index] == 'True'
403 403 index += 1
404 404
405 405 # Rules
406 406 rules = []
407 407 rulelen = int(lines[index])
408 408 index += 1
409 409 for i in pycompat.xrange(rulelen):
410 410 ruleaction = lines[index]
411 411 index += 1
412 412 rule = lines[index]
413 413 index += 1
414 414 rules.append((ruleaction, rule))
415 415
416 416 # Replacements
417 417 replacements = []
418 418 replacementlen = int(lines[index])
419 419 index += 1
420 420 for i in pycompat.xrange(replacementlen):
421 421 replacement = lines[index]
422 422 original = node.bin(replacement[:40])
423 423 succ = [node.bin(replacement[i:i + 40]) for i in
424 424 range(40, len(replacement), 40)]
425 425 replacements.append((original, succ))
426 426 index += 1
427 427
428 428 backupfile = lines[index]
429 429 index += 1
430 430
431 431 fp.close()
432 432
433 433 return parentctxnode, rules, keep, topmost, replacements, backupfile
434 434
435 435 def clear(self):
436 436 if self.inprogress():
437 437 self.repo.vfs.unlink('histedit-state')
438 438
439 439 def inprogress(self):
440 440 return self.repo.vfs.exists('histedit-state')
441 441
442 442
443 443 class histeditaction(object):
444 444 def __init__(self, state, node):
445 445 self.state = state
446 446 self.repo = state.repo
447 447 self.node = node
448 448
449 449 @classmethod
450 450 def fromrule(cls, state, rule):
451 451 """Parses the given rule, returning an instance of the histeditaction.
452 452 """
453 453 ruleid = rule.strip().split(' ', 1)[0]
454 454 # ruleid can be anything from rev numbers, hashes, "bookmarks" etc
455 455 # Check for validation of rule ids and get the rulehash
456 456 try:
457 457 rev = node.bin(ruleid)
458 458 except TypeError:
459 459 try:
460 460 _ctx = scmutil.revsingle(state.repo, ruleid)
461 461 rulehash = _ctx.hex()
462 462 rev = node.bin(rulehash)
463 463 except error.RepoLookupError:
464 464 raise error.ParseError(_("invalid changeset %s") % ruleid)
465 465 return cls(state, rev)
466 466
467 467 def verify(self, prev, expected, seen):
468 468 """ Verifies semantic correctness of the rule"""
469 469 repo = self.repo
470 470 ha = node.hex(self.node)
471 471 self.node = scmutil.resolvehexnodeidprefix(repo, ha)
472 472 if self.node is None:
473 473 raise error.ParseError(_('unknown changeset %s listed') % ha[:12])
474 474 self._verifynodeconstraints(prev, expected, seen)
475 475
476 476 def _verifynodeconstraints(self, prev, expected, seen):
477 477 # by default command need a node in the edited list
478 478 if self.node not in expected:
479 479 raise error.ParseError(_('%s "%s" changeset was not a candidate')
480 480 % (self.verb, node.short(self.node)),
481 481 hint=_('only use listed changesets'))
482 482 # and only one command per node
483 483 if self.node in seen:
484 484 raise error.ParseError(_('duplicated command for changeset %s') %
485 485 node.short(self.node))
486 486
487 487 def torule(self):
488 488 """build a histedit rule line for an action
489 489
490 490 by default lines are in the form:
491 491 <hash> <rev> <summary>
492 492 """
493 493 ctx = self.repo[self.node]
494 494 ui = self.repo.ui
495 495 summary = cmdutil.rendertemplate(
496 496 ctx, ui.config('histedit', 'summary-template')) or ''
497 497 summary = summary.splitlines()[0]
498 498 line = '%s %s %s' % (self.verb, ctx, summary)
499 499 # trim to 75 columns by default so it's not stupidly wide in my editor
500 500 # (the 5 more are left for verb)
501 501 maxlen = self.repo.ui.configint('histedit', 'linelen')
502 502 maxlen = max(maxlen, 22) # avoid truncating hash
503 503 return stringutil.ellipsis(line, maxlen)
504 504
505 505 def tostate(self):
506 506 """Print an action in format used by histedit state files
507 507 (the first line is a verb, the remainder is the second)
508 508 """
509 509 return "%s\n%s" % (self.verb, node.hex(self.node))
510 510
511 511 def run(self):
512 512 """Runs the action. The default behavior is simply apply the action's
513 513 rulectx onto the current parentctx."""
514 514 self.applychange()
515 515 self.continuedirty()
516 516 return self.continueclean()
517 517
518 518 def applychange(self):
519 519 """Applies the changes from this action's rulectx onto the current
520 520 parentctx, but does not commit them."""
521 521 repo = self.repo
522 522 rulectx = repo[self.node]
523 523 repo.ui.pushbuffer(error=True, labeled=True)
524 524 hg.update(repo, self.state.parentctxnode, quietempty=True)
525 repo.ui.popbuffer()
525 526 stats = applychanges(repo.ui, repo, rulectx, {})
526 527 repo.dirstate.setbranch(rulectx.branch())
527 528 if stats.unresolvedcount:
528 buf = repo.ui.popbuffer()
529 repo.ui.write(buf)
530 529 raise error.InterventionRequired(
531 530 _('Fix up the change (%s %s)') %
532 531 (self.verb, node.short(self.node)),
533 532 hint=_('hg histedit --continue to resume'))
534 else:
535 repo.ui.popbuffer()
536 533
537 534 def continuedirty(self):
538 535 """Continues the action when changes have been applied to the working
539 536 copy. The default behavior is to commit the dirty changes."""
540 537 repo = self.repo
541 538 rulectx = repo[self.node]
542 539
543 540 editor = self.commiteditor()
544 541 commit = commitfuncfor(repo, rulectx)
545 542 if repo.ui.configbool('rewrite', 'update-timestamp'):
546 543 date = dateutil.makedate()
547 544 else:
548 545 date = rulectx.date()
549 546 commit(text=rulectx.description(), user=rulectx.user(),
550 547 date=date, extra=rulectx.extra(), editor=editor)
551 548
552 549 def commiteditor(self):
553 550 """The editor to be used to edit the commit message."""
554 551 return False
555 552
556 553 def continueclean(self):
557 554 """Continues the action when the working copy is clean. The default
558 555 behavior is to accept the current commit as the new version of the
559 556 rulectx."""
560 557 ctx = self.repo['.']
561 558 if ctx.node() == self.state.parentctxnode:
562 559 self.repo.ui.warn(_('%s: skipping changeset (no changes)\n') %
563 560 node.short(self.node))
564 561 return ctx, [(self.node, tuple())]
565 562 if ctx.node() == self.node:
566 563 # Nothing changed
567 564 return ctx, []
568 565 return ctx, [(self.node, (ctx.node(),))]
569 566
570 567 def commitfuncfor(repo, src):
571 568 """Build a commit function for the replacement of <src>
572 569
573 570 This function ensure we apply the same treatment to all changesets.
574 571
575 572 - Add a 'histedit_source' entry in extra.
576 573
577 574 Note that fold has its own separated logic because its handling is a bit
578 575 different and not easily factored out of the fold method.
579 576 """
580 577 phasemin = src.phase()
581 578 def commitfunc(**kwargs):
582 579 overrides = {('phases', 'new-commit'): phasemin}
583 580 with repo.ui.configoverride(overrides, 'histedit'):
584 581 extra = kwargs.get(r'extra', {}).copy()
585 582 extra['histedit_source'] = src.hex()
586 583 kwargs[r'extra'] = extra
587 584 return repo.commit(**kwargs)
588 585 return commitfunc
589 586
590 587 def applychanges(ui, repo, ctx, opts):
591 588 """Merge changeset from ctx (only) in the current working directory"""
592 589 wcpar = repo.dirstate.p1()
593 590 if ctx.p1().node() == wcpar:
594 591 # edits are "in place" we do not need to make any merge,
595 592 # just applies changes on parent for editing
593 ui.pushbuffer()
596 594 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
597 595 stats = mergemod.updateresult(0, 0, 0, 0)
596 ui.popbuffer()
598 597 else:
599 598 try:
600 599 # ui.forcemerge is an internal variable, do not document
601 600 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
602 601 'histedit')
603 602 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
604 603 finally:
605 604 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
606 605 return stats
607 606
608 607 def collapse(repo, firstctx, lastctx, commitopts, skipprompt=False):
609 608 """collapse the set of revisions from first to last as new one.
610 609
611 610 Expected commit options are:
612 611 - message
613 612 - date
614 613 - username
615 614 Commit message is edited in all cases.
616 615
617 616 This function works in memory."""
618 617 ctxs = list(repo.set('%d::%d', firstctx.rev(), lastctx.rev()))
619 618 if not ctxs:
620 619 return None
621 620 for c in ctxs:
622 621 if not c.mutable():
623 622 raise error.ParseError(
624 623 _("cannot fold into public change %s") % node.short(c.node()))
625 624 base = firstctx.p1()
626 625
627 626 # commit a new version of the old changeset, including the update
628 627 # collect all files which might be affected
629 628 files = set()
630 629 for ctx in ctxs:
631 630 files.update(ctx.files())
632 631
633 632 # Recompute copies (avoid recording a -> b -> a)
634 633 copied = copies.pathcopies(base, lastctx)
635 634
636 635 # prune files which were reverted by the updates
637 636 files = [f for f in files if not cmdutil.samefile(f, lastctx, base)]
638 637 # commit version of these files as defined by head
639 638 headmf = lastctx.manifest()
640 639 def filectxfn(repo, ctx, path):
641 640 if path in headmf:
642 641 fctx = lastctx[path]
643 642 flags = fctx.flags()
644 643 mctx = context.memfilectx(repo, ctx,
645 644 fctx.path(), fctx.data(),
646 645 islink='l' in flags,
647 646 isexec='x' in flags,
648 647 copysource=copied.get(path))
649 648 return mctx
650 649 return None
651 650
652 651 if commitopts.get('message'):
653 652 message = commitopts['message']
654 653 else:
655 654 message = firstctx.description()
656 655 user = commitopts.get('user')
657 656 date = commitopts.get('date')
658 657 extra = commitopts.get('extra')
659 658
660 659 parents = (firstctx.p1().node(), firstctx.p2().node())
661 660 editor = None
662 661 if not skipprompt:
663 662 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
664 663 new = context.memctx(repo,
665 664 parents=parents,
666 665 text=message,
667 666 files=files,
668 667 filectxfn=filectxfn,
669 668 user=user,
670 669 date=date,
671 670 extra=extra,
672 671 editor=editor)
673 672 return repo.commitctx(new)
674 673
675 674 def _isdirtywc(repo):
676 675 return repo[None].dirty(missing=True)
677 676
678 677 def abortdirty():
679 678 raise error.Abort(_('working copy has pending changes'),
680 679 hint=_('amend, commit, or revert them and run histedit '
681 680 '--continue, or abort with histedit --abort'))
682 681
683 682 def action(verbs, message, priority=False, internal=False):
684 683 def wrap(cls):
685 684 assert not priority or not internal
686 685 verb = verbs[0]
687 686 if priority:
688 687 primaryactions.add(verb)
689 688 elif internal:
690 689 internalactions.add(verb)
691 690 elif len(verbs) > 1:
692 691 secondaryactions.add(verb)
693 692 else:
694 693 tertiaryactions.add(verb)
695 694
696 695 cls.verb = verb
697 696 cls.verbs = verbs
698 697 cls.message = message
699 698 for verb in verbs:
700 699 actiontable[verb] = cls
701 700 return cls
702 701 return wrap
703 702
704 703 @action(['pick', 'p'],
705 704 _('use commit'),
706 705 priority=True)
707 706 class pick(histeditaction):
708 707 def run(self):
709 708 rulectx = self.repo[self.node]
710 709 if rulectx.p1().node() == self.state.parentctxnode:
711 710 self.repo.ui.debug('node %s unchanged\n' % node.short(self.node))
712 711 return rulectx, []
713 712
714 713 return super(pick, self).run()
715 714
716 715 @action(['edit', 'e'],
717 716 _('use commit, but stop for amending'),
718 717 priority=True)
719 718 class edit(histeditaction):
720 719 def run(self):
721 720 repo = self.repo
722 721 rulectx = repo[self.node]
723 722 hg.update(repo, self.state.parentctxnode, quietempty=True)
724 723 applychanges(repo.ui, repo, rulectx, {})
725 724 raise error.InterventionRequired(
726 725 _('Editing (%s), you may commit or record as needed now.')
727 726 % node.short(self.node),
728 727 hint=_('hg histedit --continue to resume'))
729 728
730 729 def commiteditor(self):
731 730 return cmdutil.getcommiteditor(edit=True, editform='histedit.edit')
732 731
733 732 @action(['fold', 'f'],
734 733 _('use commit, but combine it with the one above'))
735 734 class fold(histeditaction):
736 735 def verify(self, prev, expected, seen):
737 736 """ Verifies semantic correctness of the fold rule"""
738 737 super(fold, self).verify(prev, expected, seen)
739 738 repo = self.repo
740 739 if not prev:
741 740 c = repo[self.node].p1()
742 741 elif not prev.verb in ('pick', 'base'):
743 742 return
744 743 else:
745 744 c = repo[prev.node]
746 745 if not c.mutable():
747 746 raise error.ParseError(
748 747 _("cannot fold into public change %s") % node.short(c.node()))
749 748
750 749
751 750 def continuedirty(self):
752 751 repo = self.repo
753 752 rulectx = repo[self.node]
754 753
755 754 commit = commitfuncfor(repo, rulectx)
756 755 commit(text='fold-temp-revision %s' % node.short(self.node),
757 756 user=rulectx.user(), date=rulectx.date(),
758 757 extra=rulectx.extra())
759 758
760 759 def continueclean(self):
761 760 repo = self.repo
762 761 ctx = repo['.']
763 762 rulectx = repo[self.node]
764 763 parentctxnode = self.state.parentctxnode
765 764 if ctx.node() == parentctxnode:
766 765 repo.ui.warn(_('%s: empty changeset\n') %
767 766 node.short(self.node))
768 767 return ctx, [(self.node, (parentctxnode,))]
769 768
770 769 parentctx = repo[parentctxnode]
771 770 newcommits = set(c.node() for c in repo.set('(%d::. - %d)',
772 771 parentctx.rev(),
773 772 parentctx.rev()))
774 773 if not newcommits:
775 774 repo.ui.warn(_('%s: cannot fold - working copy is not a '
776 775 'descendant of previous commit %s\n') %
777 776 (node.short(self.node), node.short(parentctxnode)))
778 777 return ctx, [(self.node, (ctx.node(),))]
779 778
780 779 middlecommits = newcommits.copy()
781 780 middlecommits.discard(ctx.node())
782 781
783 782 return self.finishfold(repo.ui, repo, parentctx, rulectx, ctx.node(),
784 783 middlecommits)
785 784
786 785 def skipprompt(self):
787 786 """Returns true if the rule should skip the message editor.
788 787
789 788 For example, 'fold' wants to show an editor, but 'rollup'
790 789 doesn't want to.
791 790 """
792 791 return False
793 792
794 793 def mergedescs(self):
795 794 """Returns true if the rule should merge messages of multiple changes.
796 795
797 796 This exists mainly so that 'rollup' rules can be a subclass of
798 797 'fold'.
799 798 """
800 799 return True
801 800
802 801 def firstdate(self):
803 802 """Returns true if the rule should preserve the date of the first
804 803 change.
805 804
806 805 This exists mainly so that 'rollup' rules can be a subclass of
807 806 'fold'.
808 807 """
809 808 return False
810 809
811 810 def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
812 811 parent = ctx.p1().node()
813 812 hg.updaterepo(repo, parent, overwrite=False)
814 813 ### prepare new commit data
815 814 commitopts = {}
816 815 commitopts['user'] = ctx.user()
817 816 # commit message
818 817 if not self.mergedescs():
819 818 newmessage = ctx.description()
820 819 else:
821 820 newmessage = '\n***\n'.join(
822 821 [ctx.description()] +
823 822 [repo[r].description() for r in internalchanges] +
824 823 [oldctx.description()]) + '\n'
825 824 commitopts['message'] = newmessage
826 825 # date
827 826 if self.firstdate():
828 827 commitopts['date'] = ctx.date()
829 828 else:
830 829 commitopts['date'] = max(ctx.date(), oldctx.date())
831 830 # if date is to be updated to current
832 831 if ui.configbool('rewrite', 'update-timestamp'):
833 832 commitopts['date'] = dateutil.makedate()
834 833
835 834 extra = ctx.extra().copy()
836 835 # histedit_source
837 836 # note: ctx is likely a temporary commit but that the best we can do
838 837 # here. This is sufficient to solve issue3681 anyway.
839 838 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
840 839 commitopts['extra'] = extra
841 840 phasemin = max(ctx.phase(), oldctx.phase())
842 841 overrides = {('phases', 'new-commit'): phasemin}
843 842 with repo.ui.configoverride(overrides, 'histedit'):
844 843 n = collapse(repo, ctx, repo[newnode], commitopts,
845 844 skipprompt=self.skipprompt())
846 845 if n is None:
847 846 return ctx, []
848 847 hg.updaterepo(repo, n, overwrite=False)
849 848 replacements = [(oldctx.node(), (newnode,)),
850 849 (ctx.node(), (n,)),
851 850 (newnode, (n,)),
852 851 ]
853 852 for ich in internalchanges:
854 853 replacements.append((ich, (n,)))
855 854 return repo[n], replacements
856 855
857 856 @action(['base', 'b'],
858 857 _('checkout changeset and apply further changesets from there'))
859 858 class base(histeditaction):
860 859
861 860 def run(self):
862 861 if self.repo['.'].node() != self.node:
863 862 mergemod.update(self.repo, self.node, branchmerge=False, force=True)
864 863 return self.continueclean()
865 864
866 865 def continuedirty(self):
867 866 abortdirty()
868 867
869 868 def continueclean(self):
870 869 basectx = self.repo['.']
871 870 return basectx, []
872 871
873 872 def _verifynodeconstraints(self, prev, expected, seen):
874 873 # base can only be use with a node not in the edited set
875 874 if self.node in expected:
876 875 msg = _('%s "%s" changeset was an edited list candidate')
877 876 raise error.ParseError(
878 877 msg % (self.verb, node.short(self.node)),
879 878 hint=_('base must only use unlisted changesets'))
880 879
881 880 @action(['_multifold'],
882 881 _(
883 882 """fold subclass used for when multiple folds happen in a row
884 883
885 884 We only want to fire the editor for the folded message once when
886 885 (say) four changes are folded down into a single change. This is
887 886 similar to rollup, but we should preserve both messages so that
888 887 when the last fold operation runs we can show the user all the
889 888 commit messages in their editor.
890 889 """),
891 890 internal=True)
892 891 class _multifold(fold):
893 892 def skipprompt(self):
894 893 return True
895 894
896 895 @action(["roll", "r"],
897 896 _("like fold, but discard this commit's description and date"))
898 897 class rollup(fold):
899 898 def mergedescs(self):
900 899 return False
901 900
902 901 def skipprompt(self):
903 902 return True
904 903
905 904 def firstdate(self):
906 905 return True
907 906
908 907 @action(["drop", "d"],
909 908 _('remove commit from history'))
910 909 class drop(histeditaction):
911 910 def run(self):
912 911 parentctx = self.repo[self.state.parentctxnode]
913 912 return parentctx, [(self.node, tuple())]
914 913
915 914 @action(["mess", "m"],
916 915 _('edit commit message without changing commit content'),
917 916 priority=True)
918 917 class message(histeditaction):
919 918 def commiteditor(self):
920 919 return cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
921 920
922 921 def findoutgoing(ui, repo, remote=None, force=False, opts=None):
923 922 """utility function to find the first outgoing changeset
924 923
925 924 Used by initialization code"""
926 925 if opts is None:
927 926 opts = {}
928 927 dest = ui.expandpath(remote or 'default-push', remote or 'default')
929 928 dest, branches = hg.parseurl(dest, None)[:2]
930 929 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
931 930
932 931 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
933 932 other = hg.peer(repo, opts, dest)
934 933
935 934 if revs:
936 935 revs = [repo.lookup(rev) for rev in revs]
937 936
938 937 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
939 938 if not outgoing.missing:
940 939 raise error.Abort(_('no outgoing ancestors'))
941 940 roots = list(repo.revs("roots(%ln)", outgoing.missing))
942 941 if len(roots) > 1:
943 942 msg = _('there are ambiguous outgoing revisions')
944 943 hint = _("see 'hg help histedit' for more detail")
945 944 raise error.Abort(msg, hint=hint)
946 945 return repo[roots[0]].node()
947 946
948 947 # Curses Support
949 948 try:
950 949 import curses
951 950
952 951 # Curses requires setting the locale or it will default to the C
953 952 # locale. This sets the locale to the user's default system
954 953 # locale.
955 954 import locale
956 955 locale.setlocale(locale.LC_ALL, r'')
957 956 except ImportError:
958 957 curses = None
959 958
960 959 KEY_LIST = ['pick', 'edit', 'fold', 'drop', 'mess', 'roll']
961 960 ACTION_LABELS = {
962 961 'fold': '^fold',
963 962 'roll': '^roll',
964 963 }
965 964
966 965 COLOR_HELP, COLOR_SELECTED, COLOR_OK, COLOR_WARN, COLOR_CURRENT = 1, 2, 3, 4, 5
967 966
968 967 E_QUIT, E_HISTEDIT = 1, 2
969 968 E_PAGEDOWN, E_PAGEUP, E_LINEUP, E_LINEDOWN, E_RESIZE = 3, 4, 5, 6, 7
970 969 MODE_INIT, MODE_PATCH, MODE_RULES, MODE_HELP = 0, 1, 2, 3
971 970
972 971 KEYTABLE = {
973 972 'global': {
974 973 'h': 'next-action',
975 974 'KEY_RIGHT': 'next-action',
976 975 'l': 'prev-action',
977 976 'KEY_LEFT': 'prev-action',
978 977 'q': 'quit',
979 978 'c': 'histedit',
980 979 'C': 'histedit',
981 980 'v': 'showpatch',
982 981 '?': 'help',
983 982 },
984 983 MODE_RULES: {
985 984 'd': 'action-drop',
986 985 'e': 'action-edit',
987 986 'f': 'action-fold',
988 987 'm': 'action-mess',
989 988 'p': 'action-pick',
990 989 'r': 'action-roll',
991 990 ' ': 'select',
992 991 'j': 'down',
993 992 'k': 'up',
994 993 'KEY_DOWN': 'down',
995 994 'KEY_UP': 'up',
996 995 'J': 'move-down',
997 996 'K': 'move-up',
998 997 'KEY_NPAGE': 'move-down',
999 998 'KEY_PPAGE': 'move-up',
1000 999 '0': 'goto', # Used for 0..9
1001 1000 },
1002 1001 MODE_PATCH: {
1003 1002 ' ': 'page-down',
1004 1003 'KEY_NPAGE': 'page-down',
1005 1004 'KEY_PPAGE': 'page-up',
1006 1005 'j': 'line-down',
1007 1006 'k': 'line-up',
1008 1007 'KEY_DOWN': 'line-down',
1009 1008 'KEY_UP': 'line-up',
1010 1009 'J': 'down',
1011 1010 'K': 'up',
1012 1011 },
1013 1012 MODE_HELP: {
1014 1013 },
1015 1014 }
1016 1015
1017 1016 def screen_size():
1018 1017 return struct.unpack('hh', fcntl.ioctl(1, termios.TIOCGWINSZ, ' '))
1019 1018
1020 1019 class histeditrule(object):
1021 1020 def __init__(self, ctx, pos, action='pick'):
1022 1021 self.ctx = ctx
1023 1022 self.action = action
1024 1023 self.origpos = pos
1025 1024 self.pos = pos
1026 1025 self.conflicts = []
1027 1026
1028 1027 def __str__(self):
1029 1028 # Some actions ('fold' and 'roll') combine a patch with a previous one.
1030 1029 # Add a marker showing which patch they apply to, and also omit the
1031 1030 # description for 'roll' (since it will get discarded). Example display:
1032 1031 #
1033 1032 # #10 pick 316392:06a16c25c053 add option to skip tests
1034 1033 # #11 ^roll 316393:71313c964cc5
1035 1034 # #12 pick 316394:ab31f3973b0d include mfbt for mozilla-config.h
1036 1035 # #13 ^fold 316395:14ce5803f4c3 fix warnings
1037 1036 #
1038 1037 # The carets point to the changeset being folded into ("roll this
1039 1038 # changeset into the changeset above").
1040 1039 action = ACTION_LABELS.get(self.action, self.action)
1041 1040 h = self.ctx.hex()[0:12]
1042 1041 r = self.ctx.rev()
1043 1042 desc = self.ctx.description().splitlines()[0].strip()
1044 1043 if self.action == 'roll':
1045 1044 desc = ''
1046 1045 return "#{0:<2} {1:<6} {2}:{3} {4}".format(
1047 1046 self.origpos, action, r, h, desc)
1048 1047
1049 1048 def checkconflicts(self, other):
1050 1049 if other.pos > self.pos and other.origpos <= self.origpos:
1051 1050 if set(other.ctx.files()) & set(self.ctx.files()) != set():
1052 1051 self.conflicts.append(other)
1053 1052 return self.conflicts
1054 1053
1055 1054 if other in self.conflicts:
1056 1055 self.conflicts.remove(other)
1057 1056 return self.conflicts
1058 1057
1059 1058 # ============ EVENTS ===============
1060 1059 def movecursor(state, oldpos, newpos):
1061 1060 '''Change the rule/changeset that the cursor is pointing to, regardless of
1062 1061 current mode (you can switch between patches from the view patch window).'''
1063 1062 state['pos'] = newpos
1064 1063
1065 1064 mode, _ = state['mode']
1066 1065 if mode == MODE_RULES:
1067 1066 # Scroll through the list by updating the view for MODE_RULES, so that
1068 1067 # even if we are not currently viewing the rules, switching back will
1069 1068 # result in the cursor's rule being visible.
1070 1069 modestate = state['modes'][MODE_RULES]
1071 1070 if newpos < modestate['line_offset']:
1072 1071 modestate['line_offset'] = newpos
1073 1072 elif newpos > modestate['line_offset'] + state['page_height'] - 1:
1074 1073 modestate['line_offset'] = newpos - state['page_height'] + 1
1075 1074
1076 1075 # Reset the patch view region to the top of the new patch.
1077 1076 state['modes'][MODE_PATCH]['line_offset'] = 0
1078 1077
1079 1078 def changemode(state, mode):
1080 1079 curmode, _ = state['mode']
1081 1080 state['mode'] = (mode, curmode)
1082 1081
1083 1082 def makeselection(state, pos):
1084 1083 state['selected'] = pos
1085 1084
1086 1085 def swap(state, oldpos, newpos):
1087 1086 """Swap two positions and calculate necessary conflicts in
1088 1087 O(|newpos-oldpos|) time"""
1089 1088
1090 1089 rules = state['rules']
1091 1090 assert 0 <= oldpos < len(rules) and 0 <= newpos < len(rules)
1092 1091
1093 1092 rules[oldpos], rules[newpos] = rules[newpos], rules[oldpos]
1094 1093
1095 1094 # TODO: swap should not know about histeditrule's internals
1096 1095 rules[newpos].pos = newpos
1097 1096 rules[oldpos].pos = oldpos
1098 1097
1099 1098 start = min(oldpos, newpos)
1100 1099 end = max(oldpos, newpos)
1101 1100 for r in pycompat.xrange(start, end + 1):
1102 1101 rules[newpos].checkconflicts(rules[r])
1103 1102 rules[oldpos].checkconflicts(rules[r])
1104 1103
1105 1104 if state['selected']:
1106 1105 makeselection(state, newpos)
1107 1106
1108 1107 def changeaction(state, pos, action):
1109 1108 """Change the action state on the given position to the new action"""
1110 1109 rules = state['rules']
1111 1110 assert 0 <= pos < len(rules)
1112 1111 rules[pos].action = action
1113 1112
1114 1113 def cycleaction(state, pos, next=False):
1115 1114 """Changes the action state the next or the previous action from
1116 1115 the action list"""
1117 1116 rules = state['rules']
1118 1117 assert 0 <= pos < len(rules)
1119 1118 current = rules[pos].action
1120 1119
1121 1120 assert current in KEY_LIST
1122 1121
1123 1122 index = KEY_LIST.index(current)
1124 1123 if next:
1125 1124 index += 1
1126 1125 else:
1127 1126 index -= 1
1128 1127 changeaction(state, pos, KEY_LIST[index % len(KEY_LIST)])
1129 1128
1130 1129 def changeview(state, delta, unit):
1131 1130 '''Change the region of whatever is being viewed (a patch or the list of
1132 1131 changesets). 'delta' is an amount (+/- 1) and 'unit' is 'page' or 'line'.'''
1133 1132 mode, _ = state['mode']
1134 1133 if mode != MODE_PATCH:
1135 1134 return
1136 1135 mode_state = state['modes'][mode]
1137 1136 num_lines = len(patchcontents(state))
1138 1137 page_height = state['page_height']
1139 1138 unit = page_height if unit == 'page' else 1
1140 1139 num_pages = 1 + (num_lines - 1) / page_height
1141 1140 max_offset = (num_pages - 1) * page_height
1142 1141 newline = mode_state['line_offset'] + delta * unit
1143 1142 mode_state['line_offset'] = max(0, min(max_offset, newline))
1144 1143
1145 1144 def event(state, ch):
1146 1145 """Change state based on the current character input
1147 1146
1148 1147 This takes the current state and based on the current character input from
1149 1148 the user we change the state.
1150 1149 """
1151 1150 selected = state['selected']
1152 1151 oldpos = state['pos']
1153 1152 rules = state['rules']
1154 1153
1155 1154 if ch in (curses.KEY_RESIZE, "KEY_RESIZE"):
1156 1155 return E_RESIZE
1157 1156
1158 1157 lookup_ch = ch
1159 1158 if '0' <= ch <= '9':
1160 1159 lookup_ch = '0'
1161 1160
1162 1161 curmode, prevmode = state['mode']
1163 1162 action = KEYTABLE[curmode].get(lookup_ch, KEYTABLE['global'].get(lookup_ch))
1164 1163 if action is None:
1165 1164 return
1166 1165 if action in ('down', 'move-down'):
1167 1166 newpos = min(oldpos + 1, len(rules) - 1)
1168 1167 movecursor(state, oldpos, newpos)
1169 1168 if selected is not None or action == 'move-down':
1170 1169 swap(state, oldpos, newpos)
1171 1170 elif action in ('up', 'move-up'):
1172 1171 newpos = max(0, oldpos - 1)
1173 1172 movecursor(state, oldpos, newpos)
1174 1173 if selected is not None or action == 'move-up':
1175 1174 swap(state, oldpos, newpos)
1176 1175 elif action == 'next-action':
1177 1176 cycleaction(state, oldpos, next=True)
1178 1177 elif action == 'prev-action':
1179 1178 cycleaction(state, oldpos, next=False)
1180 1179 elif action == 'select':
1181 1180 selected = oldpos if selected is None else None
1182 1181 makeselection(state, selected)
1183 1182 elif action == 'goto' and int(ch) < len(rules) and len(rules) <= 10:
1184 1183 newrule = next((r for r in rules if r.origpos == int(ch)))
1185 1184 movecursor(state, oldpos, newrule.pos)
1186 1185 if selected is not None:
1187 1186 swap(state, oldpos, newrule.pos)
1188 1187 elif action.startswith('action-'):
1189 1188 changeaction(state, oldpos, action[7:])
1190 1189 elif action == 'showpatch':
1191 1190 changemode(state, MODE_PATCH if curmode != MODE_PATCH else prevmode)
1192 1191 elif action == 'help':
1193 1192 changemode(state, MODE_HELP if curmode != MODE_HELP else prevmode)
1194 1193 elif action == 'quit':
1195 1194 return E_QUIT
1196 1195 elif action == 'histedit':
1197 1196 return E_HISTEDIT
1198 1197 elif action == 'page-down':
1199 1198 return E_PAGEDOWN
1200 1199 elif action == 'page-up':
1201 1200 return E_PAGEUP
1202 1201 elif action == 'line-down':
1203 1202 return E_LINEDOWN
1204 1203 elif action == 'line-up':
1205 1204 return E_LINEUP
1206 1205
1207 1206 def makecommands(rules):
1208 1207 """Returns a list of commands consumable by histedit --commands based on
1209 1208 our list of rules"""
1210 1209 commands = []
1211 1210 for rules in rules:
1212 1211 commands.append("{0} {1}\n".format(rules.action, rules.ctx))
1213 1212 return commands
1214 1213
1215 1214 def addln(win, y, x, line, color=None):
1216 1215 """Add a line to the given window left padding but 100% filled with
1217 1216 whitespace characters, so that the color appears on the whole line"""
1218 1217 maxy, maxx = win.getmaxyx()
1219 1218 length = maxx - 1 - x
1220 1219 line = ("{0:<%d}" % length).format(str(line).strip())[:length]
1221 1220 if y < 0:
1222 1221 y = maxy + y
1223 1222 if x < 0:
1224 1223 x = maxx + x
1225 1224 if color:
1226 1225 win.addstr(y, x, line, color)
1227 1226 else:
1228 1227 win.addstr(y, x, line)
1229 1228
1230 1229 def patchcontents(state):
1231 1230 repo = state['repo']
1232 1231 rule = state['rules'][state['pos']]
1233 1232 displayer = logcmdutil.changesetdisplayer(repo.ui, repo, {
1234 1233 'patch': True, 'verbose': True
1235 1234 }, buffered=True)
1236 1235 displayer.show(rule.ctx)
1237 1236 displayer.close()
1238 1237 return displayer.hunk[rule.ctx.rev()].splitlines()
1239 1238
1240 1239 def _chisteditmain(repo, rules, stdscr):
1241 1240 # initialize color pattern
1242 1241 curses.init_pair(COLOR_HELP, curses.COLOR_WHITE, curses.COLOR_BLUE)
1243 1242 curses.init_pair(COLOR_SELECTED, curses.COLOR_BLACK, curses.COLOR_WHITE)
1244 1243 curses.init_pair(COLOR_WARN, curses.COLOR_BLACK, curses.COLOR_YELLOW)
1245 1244 curses.init_pair(COLOR_OK, curses.COLOR_BLACK, curses.COLOR_GREEN)
1246 1245 curses.init_pair(COLOR_CURRENT, curses.COLOR_WHITE, curses.COLOR_MAGENTA)
1247 1246
1248 1247 # don't display the cursor
1249 1248 try:
1250 1249 curses.curs_set(0)
1251 1250 except curses.error:
1252 1251 pass
1253 1252
1254 1253 def rendercommit(win, state):
1255 1254 """Renders the commit window that shows the log of the current selected
1256 1255 commit"""
1257 1256 pos = state['pos']
1258 1257 rules = state['rules']
1259 1258 rule = rules[pos]
1260 1259
1261 1260 ctx = rule.ctx
1262 1261 win.box()
1263 1262
1264 1263 maxy, maxx = win.getmaxyx()
1265 1264 length = maxx - 3
1266 1265
1267 1266 line = "changeset: {0}:{1:<12}".format(ctx.rev(), ctx)
1268 1267 win.addstr(1, 1, line[:length])
1269 1268
1270 1269 line = "user: {0}".format(ctx.user())
1271 1270 win.addstr(2, 1, line[:length])
1272 1271
1273 1272 bms = repo.nodebookmarks(ctx.node())
1274 1273 line = "bookmark: {0}".format(' '.join(bms))
1275 1274 win.addstr(3, 1, line[:length])
1276 1275
1277 1276 line = "files: {0}".format(','.join(ctx.files()))
1278 1277 win.addstr(4, 1, line[:length])
1279 1278
1280 1279 line = "summary: {0}".format(ctx.description().splitlines()[0])
1281 1280 win.addstr(5, 1, line[:length])
1282 1281
1283 1282 conflicts = rule.conflicts
1284 1283 if len(conflicts) > 0:
1285 1284 conflictstr = ','.join(map(lambda r: str(r.ctx), conflicts))
1286 1285 conflictstr = "changed files overlap with {0}".format(conflictstr)
1287 1286 else:
1288 1287 conflictstr = 'no overlap'
1289 1288
1290 1289 win.addstr(6, 1, conflictstr[:length])
1291 1290 win.noutrefresh()
1292 1291
1293 1292 def helplines(mode):
1294 1293 if mode == MODE_PATCH:
1295 1294 help = """\
1296 1295 ?: help, k/up: line up, j/down: line down, v: stop viewing patch
1297 1296 pgup: prev page, space/pgdn: next page, c: commit, q: abort
1298 1297 """
1299 1298 else:
1300 1299 help = """\
1301 1300 ?: help, k/up: move up, j/down: move down, space: select, v: view patch
1302 1301 d: drop, e: edit, f: fold, m: mess, p: pick, r: roll
1303 1302 pgup/K: move patch up, pgdn/J: move patch down, c: commit, q: abort
1304 1303 """
1305 1304 return help.splitlines()
1306 1305
1307 1306 def renderhelp(win, state):
1308 1307 maxy, maxx = win.getmaxyx()
1309 1308 mode, _ = state['mode']
1310 1309 for y, line in enumerate(helplines(mode)):
1311 1310 if y >= maxy:
1312 1311 break
1313 1312 addln(win, y, 0, line, curses.color_pair(COLOR_HELP))
1314 1313 win.noutrefresh()
1315 1314
1316 1315 def renderrules(rulesscr, state):
1317 1316 rules = state['rules']
1318 1317 pos = state['pos']
1319 1318 selected = state['selected']
1320 1319 start = state['modes'][MODE_RULES]['line_offset']
1321 1320
1322 1321 conflicts = [r.ctx for r in rules if r.conflicts]
1323 1322 if len(conflicts) > 0:
1324 1323 line = "potential conflict in %s" % ','.join(map(str, conflicts))
1325 1324 addln(rulesscr, -1, 0, line, curses.color_pair(COLOR_WARN))
1326 1325
1327 1326 for y, rule in enumerate(rules[start:]):
1328 1327 if y >= state['page_height']:
1329 1328 break
1330 1329 if len(rule.conflicts) > 0:
1331 1330 rulesscr.addstr(y, 0, " ", curses.color_pair(COLOR_WARN))
1332 1331 else:
1333 1332 rulesscr.addstr(y, 0, " ", curses.COLOR_BLACK)
1334 1333 if y + start == selected:
1335 1334 addln(rulesscr, y, 2, rule, curses.color_pair(COLOR_SELECTED))
1336 1335 elif y + start == pos:
1337 1336 addln(rulesscr, y, 2, rule,
1338 1337 curses.color_pair(COLOR_CURRENT) | curses.A_BOLD)
1339 1338 else:
1340 1339 addln(rulesscr, y, 2, rule)
1341 1340 rulesscr.noutrefresh()
1342 1341
1343 1342 def renderstring(win, state, output):
1344 1343 maxy, maxx = win.getmaxyx()
1345 1344 length = min(maxy - 1, len(output))
1346 1345 for y in range(0, length):
1347 1346 win.addstr(y, 0, output[y])
1348 1347 win.noutrefresh()
1349 1348
1350 1349 def renderpatch(win, state):
1351 1350 start = state['modes'][MODE_PATCH]['line_offset']
1352 1351 renderstring(win, state, patchcontents(state)[start:])
1353 1352
1354 1353 def layout(mode):
1355 1354 maxy, maxx = stdscr.getmaxyx()
1356 1355 helplen = len(helplines(mode))
1357 1356 return {
1358 1357 'commit': (8, maxx),
1359 1358 'help': (helplen, maxx),
1360 1359 'main': (maxy - helplen - 8, maxx),
1361 1360 }
1362 1361
1363 1362 def drawvertwin(size, y, x):
1364 1363 win = curses.newwin(size[0], size[1], y, x)
1365 1364 y += size[0]
1366 1365 return win, y, x
1367 1366
1368 1367 state = {
1369 1368 'pos': 0,
1370 1369 'rules': rules,
1371 1370 'selected': None,
1372 1371 'mode': (MODE_INIT, MODE_INIT),
1373 1372 'page_height': None,
1374 1373 'modes': {
1375 1374 MODE_RULES: {
1376 1375 'line_offset': 0,
1377 1376 },
1378 1377 MODE_PATCH: {
1379 1378 'line_offset': 0,
1380 1379 }
1381 1380 },
1382 1381 'repo': repo,
1383 1382 }
1384 1383
1385 1384 # eventloop
1386 1385 ch = None
1387 1386 stdscr.clear()
1388 1387 stdscr.refresh()
1389 1388 while True:
1390 1389 try:
1391 1390 oldmode, _ = state['mode']
1392 1391 if oldmode == MODE_INIT:
1393 1392 changemode(state, MODE_RULES)
1394 1393 e = event(state, ch)
1395 1394
1396 1395 if e == E_QUIT:
1397 1396 return False
1398 1397 if e == E_HISTEDIT:
1399 1398 return state['rules']
1400 1399 else:
1401 1400 if e == E_RESIZE:
1402 1401 size = screen_size()
1403 1402 if size != stdscr.getmaxyx():
1404 1403 curses.resizeterm(*size)
1405 1404
1406 1405 curmode, _ = state['mode']
1407 1406 sizes = layout(curmode)
1408 1407 if curmode != oldmode:
1409 1408 state['page_height'] = sizes['main'][0]
1410 1409 # Adjust the view to fit the current screen size.
1411 1410 movecursor(state, state['pos'], state['pos'])
1412 1411
1413 1412 # Pack the windows against the top, each pane spread across the
1414 1413 # full width of the screen.
1415 1414 y, x = (0, 0)
1416 1415 helpwin, y, x = drawvertwin(sizes['help'], y, x)
1417 1416 mainwin, y, x = drawvertwin(sizes['main'], y, x)
1418 1417 commitwin, y, x = drawvertwin(sizes['commit'], y, x)
1419 1418
1420 1419 if e in (E_PAGEDOWN, E_PAGEUP, E_LINEDOWN, E_LINEUP):
1421 1420 if e == E_PAGEDOWN:
1422 1421 changeview(state, +1, 'page')
1423 1422 elif e == E_PAGEUP:
1424 1423 changeview(state, -1, 'page')
1425 1424 elif e == E_LINEDOWN:
1426 1425 changeview(state, +1, 'line')
1427 1426 elif e == E_LINEUP:
1428 1427 changeview(state, -1, 'line')
1429 1428
1430 1429 # start rendering
1431 1430 commitwin.erase()
1432 1431 helpwin.erase()
1433 1432 mainwin.erase()
1434 1433 if curmode == MODE_PATCH:
1435 1434 renderpatch(mainwin, state)
1436 1435 elif curmode == MODE_HELP:
1437 1436 renderstring(mainwin, state, __doc__.strip().splitlines())
1438 1437 else:
1439 1438 renderrules(mainwin, state)
1440 1439 rendercommit(commitwin, state)
1441 1440 renderhelp(helpwin, state)
1442 1441 curses.doupdate()
1443 1442 # done rendering
1444 1443 ch = stdscr.getkey()
1445 1444 except curses.error:
1446 1445 pass
1447 1446
1448 1447 def _chistedit(ui, repo, *freeargs, **opts):
1449 1448 """interactively edit changeset history via a curses interface
1450 1449
1451 1450 Provides a ncurses interface to histedit. Press ? in chistedit mode
1452 1451 to see an extensive help. Requires python-curses to be installed."""
1453 1452
1454 1453 if curses is None:
1455 1454 raise error.Abort(_("Python curses library required"))
1456 1455
1457 1456 # disable color
1458 1457 ui._colormode = None
1459 1458
1460 1459 try:
1461 1460 keep = opts.get('keep')
1462 1461 revs = opts.get('rev', [])[:]
1463 1462 cmdutil.checkunfinished(repo)
1464 1463 cmdutil.bailifchanged(repo)
1465 1464
1466 1465 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1467 1466 raise error.Abort(_('history edit already in progress, try '
1468 1467 '--continue or --abort'))
1469 1468 revs.extend(freeargs)
1470 1469 if not revs:
1471 1470 defaultrev = destutil.desthistedit(ui, repo)
1472 1471 if defaultrev is not None:
1473 1472 revs.append(defaultrev)
1474 1473 if len(revs) != 1:
1475 1474 raise error.Abort(
1476 1475 _('histedit requires exactly one ancestor revision'))
1477 1476
1478 1477 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
1479 1478 if len(rr) != 1:
1480 1479 raise error.Abort(_('The specified revisions must have '
1481 1480 'exactly one common root'))
1482 1481 root = rr[0].node()
1483 1482
1484 1483 topmost = repo.dirstate.p1()
1485 1484 revs = between(repo, root, topmost, keep)
1486 1485 if not revs:
1487 1486 raise error.Abort(_('%s is not an ancestor of working directory') %
1488 1487 node.short(root))
1489 1488
1490 1489 ctxs = []
1491 1490 for i, r in enumerate(revs):
1492 1491 ctxs.append(histeditrule(repo[r], i))
1493 1492 rc = curses.wrapper(functools.partial(_chisteditmain, repo, ctxs))
1494 1493 curses.echo()
1495 1494 curses.endwin()
1496 1495 if rc is False:
1497 1496 ui.write(_("histedit aborted\n"))
1498 1497 return 0
1499 1498 if type(rc) is list:
1500 1499 ui.status(_("performing changes\n"))
1501 1500 rules = makecommands(rc)
1502 1501 filename = repo.vfs.join('chistedit')
1503 1502 with open(filename, 'w+') as fp:
1504 1503 for r in rules:
1505 1504 fp.write(r)
1506 1505 opts['commands'] = filename
1507 1506 return _texthistedit(ui, repo, *freeargs, **opts)
1508 1507 except KeyboardInterrupt:
1509 1508 pass
1510 1509 return -1
1511 1510
1512 1511 @command('histedit',
1513 1512 [('', 'commands', '',
1514 1513 _('read history edits from the specified file'), _('FILE')),
1515 1514 ('c', 'continue', False, _('continue an edit already in progress')),
1516 1515 ('', 'edit-plan', False, _('edit remaining actions list')),
1517 1516 ('k', 'keep', False,
1518 1517 _("don't strip old nodes after edit is complete")),
1519 1518 ('', 'abort', False, _('abort an edit in progress')),
1520 1519 ('o', 'outgoing', False, _('changesets not found in destination')),
1521 1520 ('f', 'force', False,
1522 1521 _('force outgoing even for unrelated repositories')),
1523 1522 ('r', 'rev', [], _('first revision to be edited'), _('REV'))] +
1524 1523 cmdutil.formatteropts,
1525 1524 _("[OPTIONS] ([ANCESTOR] | --outgoing [URL])"),
1526 1525 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
1527 1526 def histedit(ui, repo, *freeargs, **opts):
1528 1527 """interactively edit changeset history
1529 1528
1530 1529 This command lets you edit a linear series of changesets (up to
1531 1530 and including the working directory, which should be clean).
1532 1531 You can:
1533 1532
1534 1533 - `pick` to [re]order a changeset
1535 1534
1536 1535 - `drop` to omit changeset
1537 1536
1538 1537 - `mess` to reword the changeset commit message
1539 1538
1540 1539 - `fold` to combine it with the preceding changeset (using the later date)
1541 1540
1542 1541 - `roll` like fold, but discarding this commit's description and date
1543 1542
1544 1543 - `edit` to edit this changeset (preserving date)
1545 1544
1546 1545 - `base` to checkout changeset and apply further changesets from there
1547 1546
1548 1547 There are a number of ways to select the root changeset:
1549 1548
1550 1549 - Specify ANCESTOR directly
1551 1550
1552 1551 - Use --outgoing -- it will be the first linear changeset not
1553 1552 included in destination. (See :hg:`help config.paths.default-push`)
1554 1553
1555 1554 - Otherwise, the value from the "histedit.defaultrev" config option
1556 1555 is used as a revset to select the base revision when ANCESTOR is not
1557 1556 specified. The first revision returned by the revset is used. By
1558 1557 default, this selects the editable history that is unique to the
1559 1558 ancestry of the working directory.
1560 1559
1561 1560 .. container:: verbose
1562 1561
1563 1562 If you use --outgoing, this command will abort if there are ambiguous
1564 1563 outgoing revisions. For example, if there are multiple branches
1565 1564 containing outgoing revisions.
1566 1565
1567 1566 Use "min(outgoing() and ::.)" or similar revset specification
1568 1567 instead of --outgoing to specify edit target revision exactly in
1569 1568 such ambiguous situation. See :hg:`help revsets` for detail about
1570 1569 selecting revisions.
1571 1570
1572 1571 .. container:: verbose
1573 1572
1574 1573 Examples:
1575 1574
1576 1575 - A number of changes have been made.
1577 1576 Revision 3 is no longer needed.
1578 1577
1579 1578 Start history editing from revision 3::
1580 1579
1581 1580 hg histedit -r 3
1582 1581
1583 1582 An editor opens, containing the list of revisions,
1584 1583 with specific actions specified::
1585 1584
1586 1585 pick 5339bf82f0ca 3 Zworgle the foobar
1587 1586 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1588 1587 pick 0a9639fcda9d 5 Morgify the cromulancy
1589 1588
1590 1589 Additional information about the possible actions
1591 1590 to take appears below the list of revisions.
1592 1591
1593 1592 To remove revision 3 from the history,
1594 1593 its action (at the beginning of the relevant line)
1595 1594 is changed to 'drop'::
1596 1595
1597 1596 drop 5339bf82f0ca 3 Zworgle the foobar
1598 1597 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1599 1598 pick 0a9639fcda9d 5 Morgify the cromulancy
1600 1599
1601 1600 - A number of changes have been made.
1602 1601 Revision 2 and 4 need to be swapped.
1603 1602
1604 1603 Start history editing from revision 2::
1605 1604
1606 1605 hg histedit -r 2
1607 1606
1608 1607 An editor opens, containing the list of revisions,
1609 1608 with specific actions specified::
1610 1609
1611 1610 pick 252a1af424ad 2 Blorb a morgwazzle
1612 1611 pick 5339bf82f0ca 3 Zworgle the foobar
1613 1612 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1614 1613
1615 1614 To swap revision 2 and 4, its lines are swapped
1616 1615 in the editor::
1617 1616
1618 1617 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1619 1618 pick 5339bf82f0ca 3 Zworgle the foobar
1620 1619 pick 252a1af424ad 2 Blorb a morgwazzle
1621 1620
1622 1621 Returns 0 on success, 1 if user intervention is required (not only
1623 1622 for intentional "edit" command, but also for resolving unexpected
1624 1623 conflicts).
1625 1624 """
1626 1625 # kludge: _chistedit only works for starting an edit, not aborting
1627 1626 # or continuing, so fall back to regular _texthistedit for those
1628 1627 # operations.
1629 1628 if ui.interface('histedit') == 'curses' and _getgoal(
1630 1629 pycompat.byteskwargs(opts)) == goalnew:
1631 1630 return _chistedit(ui, repo, *freeargs, **opts)
1632 1631 return _texthistedit(ui, repo, *freeargs, **opts)
1633 1632
1634 1633 def _texthistedit(ui, repo, *freeargs, **opts):
1635 1634 state = histeditstate(repo)
1636 1635 with repo.wlock() as wlock, repo.lock() as lock:
1637 1636 state.wlock = wlock
1638 1637 state.lock = lock
1639 1638 _histedit(ui, repo, state, *freeargs, **opts)
1640 1639
1641 1640 goalcontinue = 'continue'
1642 1641 goalabort = 'abort'
1643 1642 goaleditplan = 'edit-plan'
1644 1643 goalnew = 'new'
1645 1644
1646 1645 def _getgoal(opts):
1647 1646 if opts.get(b'continue'):
1648 1647 return goalcontinue
1649 1648 if opts.get(b'abort'):
1650 1649 return goalabort
1651 1650 if opts.get(b'edit_plan'):
1652 1651 return goaleditplan
1653 1652 return goalnew
1654 1653
1655 1654 def _readfile(ui, path):
1656 1655 if path == '-':
1657 1656 with ui.timeblockedsection('histedit'):
1658 1657 return ui.fin.read()
1659 1658 else:
1660 1659 with open(path, 'rb') as f:
1661 1660 return f.read()
1662 1661
1663 1662 def _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs):
1664 1663 # TODO only abort if we try to histedit mq patches, not just
1665 1664 # blanket if mq patches are applied somewhere
1666 1665 mq = getattr(repo, 'mq', None)
1667 1666 if mq and mq.applied:
1668 1667 raise error.Abort(_('source has mq patches applied'))
1669 1668
1670 1669 # basic argument incompatibility processing
1671 1670 outg = opts.get('outgoing')
1672 1671 editplan = opts.get('edit_plan')
1673 1672 abort = opts.get('abort')
1674 1673 force = opts.get('force')
1675 1674 if force and not outg:
1676 1675 raise error.Abort(_('--force only allowed with --outgoing'))
1677 1676 if goal == 'continue':
1678 1677 if any((outg, abort, revs, freeargs, rules, editplan)):
1679 1678 raise error.Abort(_('no arguments allowed with --continue'))
1680 1679 elif goal == 'abort':
1681 1680 if any((outg, revs, freeargs, rules, editplan)):
1682 1681 raise error.Abort(_('no arguments allowed with --abort'))
1683 1682 elif goal == 'edit-plan':
1684 1683 if any((outg, revs, freeargs)):
1685 1684 raise error.Abort(_('only --commands argument allowed with '
1686 1685 '--edit-plan'))
1687 1686 else:
1688 1687 if state.inprogress():
1689 1688 raise error.Abort(_('history edit already in progress, try '
1690 1689 '--continue or --abort'))
1691 1690 if outg:
1692 1691 if revs:
1693 1692 raise error.Abort(_('no revisions allowed with --outgoing'))
1694 1693 if len(freeargs) > 1:
1695 1694 raise error.Abort(
1696 1695 _('only one repo argument allowed with --outgoing'))
1697 1696 else:
1698 1697 revs.extend(freeargs)
1699 1698 if len(revs) == 0:
1700 1699 defaultrev = destutil.desthistedit(ui, repo)
1701 1700 if defaultrev is not None:
1702 1701 revs.append(defaultrev)
1703 1702
1704 1703 if len(revs) != 1:
1705 1704 raise error.Abort(
1706 1705 _('histedit requires exactly one ancestor revision'))
1707 1706
1708 1707 def _histedit(ui, repo, state, *freeargs, **opts):
1709 1708 opts = pycompat.byteskwargs(opts)
1710 1709 fm = ui.formatter('histedit', opts)
1711 1710 fm.startitem()
1712 1711 goal = _getgoal(opts)
1713 1712 revs = opts.get('rev', [])
1714 1713 nobackup = not ui.configbool('rewrite', 'backup-bundle')
1715 1714 rules = opts.get('commands', '')
1716 1715 state.keep = opts.get('keep', False)
1717 1716
1718 1717 _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs)
1719 1718
1720 1719 hastags = False
1721 1720 if revs:
1722 1721 revs = scmutil.revrange(repo, revs)
1723 1722 ctxs = [repo[rev] for rev in revs]
1724 1723 for ctx in ctxs:
1725 1724 tags = [tag for tag in ctx.tags() if tag != 'tip']
1726 1725 if not hastags:
1727 1726 hastags = len(tags)
1728 1727 if hastags:
1729 1728 if ui.promptchoice(_('warning: tags associated with the given'
1730 1729 ' changeset will be lost after histedit.\n'
1731 1730 'do you want to continue (yN)? $$ &Yes $$ &No'),
1732 1731 default=1):
1733 1732 raise error.Abort(_('histedit cancelled\n'))
1734 1733 # rebuild state
1735 1734 if goal == goalcontinue:
1736 1735 state.read()
1737 1736 state = bootstrapcontinue(ui, state, opts)
1738 1737 elif goal == goaleditplan:
1739 1738 _edithisteditplan(ui, repo, state, rules)
1740 1739 return
1741 1740 elif goal == goalabort:
1742 1741 _aborthistedit(ui, repo, state, nobackup=nobackup)
1743 1742 return
1744 1743 else:
1745 1744 # goal == goalnew
1746 1745 _newhistedit(ui, repo, state, revs, freeargs, opts)
1747 1746
1748 1747 _continuehistedit(ui, repo, state)
1749 1748 _finishhistedit(ui, repo, state, fm)
1750 1749 fm.end()
1751 1750
1752 1751 def _continuehistedit(ui, repo, state):
1753 1752 """This function runs after either:
1754 1753 - bootstrapcontinue (if the goal is 'continue')
1755 1754 - _newhistedit (if the goal is 'new')
1756 1755 """
1757 1756 # preprocess rules so that we can hide inner folds from the user
1758 1757 # and only show one editor
1759 1758 actions = state.actions[:]
1760 1759 for idx, (action, nextact) in enumerate(
1761 1760 zip(actions, actions[1:] + [None])):
1762 1761 if action.verb == 'fold' and nextact and nextact.verb == 'fold':
1763 1762 state.actions[idx].__class__ = _multifold
1764 1763
1765 1764 # Force an initial state file write, so the user can run --abort/continue
1766 1765 # even if there's an exception before the first transaction serialize.
1767 1766 state.write()
1768 1767
1769 1768 tr = None
1770 1769 # Don't use singletransaction by default since it rolls the entire
1771 1770 # transaction back if an unexpected exception happens (like a
1772 1771 # pretxncommit hook throws, or the user aborts the commit msg editor).
1773 1772 if ui.configbool("histedit", "singletransaction"):
1774 1773 # Don't use a 'with' for the transaction, since actions may close
1775 1774 # and reopen a transaction. For example, if the action executes an
1776 1775 # external process it may choose to commit the transaction first.
1777 1776 tr = repo.transaction('histedit')
1778 1777 progress = ui.makeprogress(_("editing"), unit=_('changes'),
1779 1778 total=len(state.actions))
1780 1779 with progress, util.acceptintervention(tr):
1781 1780 while state.actions:
1782 1781 state.write(tr=tr)
1783 1782 actobj = state.actions[0]
1784 1783 progress.increment(item=actobj.torule())
1785 1784 ui.debug('histedit: processing %s %s\n' % (actobj.verb,
1786 1785 actobj.torule()))
1787 1786 parentctx, replacement_ = actobj.run()
1788 1787 state.parentctxnode = parentctx.node()
1789 1788 state.replacements.extend(replacement_)
1790 1789 state.actions.pop(0)
1791 1790
1792 1791 state.write()
1793 1792
1794 1793 def _finishhistedit(ui, repo, state, fm):
1795 1794 """This action runs when histedit is finishing its session"""
1796 1795 hg.updaterepo(repo, state.parentctxnode, overwrite=False)
1797 1796
1798 1797 mapping, tmpnodes, created, ntm = processreplacement(state)
1799 1798 if mapping:
1800 1799 for prec, succs in mapping.iteritems():
1801 1800 if not succs:
1802 1801 ui.debug('histedit: %s is dropped\n' % node.short(prec))
1803 1802 else:
1804 1803 ui.debug('histedit: %s is replaced by %s\n' % (
1805 1804 node.short(prec), node.short(succs[0])))
1806 1805 if len(succs) > 1:
1807 1806 m = 'histedit: %s'
1808 1807 for n in succs[1:]:
1809 1808 ui.debug(m % node.short(n))
1810 1809
1811 1810 if not state.keep:
1812 1811 if mapping:
1813 1812 movetopmostbookmarks(repo, state.topmost, ntm)
1814 1813 # TODO update mq state
1815 1814 else:
1816 1815 mapping = {}
1817 1816
1818 1817 for n in tmpnodes:
1819 1818 if n in repo:
1820 1819 mapping[n] = ()
1821 1820
1822 1821 # remove entries about unknown nodes
1823 1822 nodemap = repo.unfiltered().changelog.nodemap
1824 1823 mapping = {k: v for k, v in mapping.items()
1825 1824 if k in nodemap and all(n in nodemap for n in v)}
1826 1825 scmutil.cleanupnodes(repo, mapping, 'histedit')
1827 1826 hf = fm.hexfunc
1828 1827 fl = fm.formatlist
1829 1828 fd = fm.formatdict
1830 1829 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1831 1830 for oldn, newn in mapping.iteritems()},
1832 1831 key="oldnode", value="newnodes")
1833 1832 fm.data(nodechanges=nodechanges)
1834 1833
1835 1834 state.clear()
1836 1835 if os.path.exists(repo.sjoin('undo')):
1837 1836 os.unlink(repo.sjoin('undo'))
1838 1837 if repo.vfs.exists('histedit-last-edit.txt'):
1839 1838 repo.vfs.unlink('histedit-last-edit.txt')
1840 1839
1841 1840 def _aborthistedit(ui, repo, state, nobackup=False):
1842 1841 try:
1843 1842 state.read()
1844 1843 __, leafs, tmpnodes, __ = processreplacement(state)
1845 1844 ui.debug('restore wc to old parent %s\n'
1846 1845 % node.short(state.topmost))
1847 1846
1848 1847 # Recover our old commits if necessary
1849 1848 if not state.topmost in repo and state.backupfile:
1850 1849 backupfile = repo.vfs.join(state.backupfile)
1851 1850 f = hg.openpath(ui, backupfile)
1852 1851 gen = exchange.readbundle(ui, f, backupfile)
1853 1852 with repo.transaction('histedit.abort') as tr:
1854 1853 bundle2.applybundle(repo, gen, tr, source='histedit',
1855 1854 url='bundle:' + backupfile)
1856 1855
1857 1856 os.remove(backupfile)
1858 1857
1859 1858 # check whether we should update away
1860 1859 if repo.unfiltered().revs('parents() and (%n or %ln::)',
1861 1860 state.parentctxnode, leafs | tmpnodes):
1862 1861 hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
1863 1862 cleanupnode(ui, repo, tmpnodes, nobackup=nobackup)
1864 1863 cleanupnode(ui, repo, leafs, nobackup=nobackup)
1865 1864 except Exception:
1866 1865 if state.inprogress():
1867 1866 ui.warn(_('warning: encountered an exception during histedit '
1868 1867 '--abort; the repository may not have been completely '
1869 1868 'cleaned up\n'))
1870 1869 raise
1871 1870 finally:
1872 1871 state.clear()
1873 1872
1874 1873 def _edithisteditplan(ui, repo, state, rules):
1875 1874 state.read()
1876 1875 if not rules:
1877 1876 comment = geteditcomment(ui,
1878 1877 node.short(state.parentctxnode),
1879 1878 node.short(state.topmost))
1880 1879 rules = ruleeditor(repo, ui, state.actions, comment)
1881 1880 else:
1882 1881 rules = _readfile(ui, rules)
1883 1882 actions = parserules(rules, state)
1884 1883 ctxs = [repo[act.node]
1885 1884 for act in state.actions if act.node]
1886 1885 warnverifyactions(ui, repo, actions, state, ctxs)
1887 1886 state.actions = actions
1888 1887 state.write()
1889 1888
1890 1889 def _newhistedit(ui, repo, state, revs, freeargs, opts):
1891 1890 outg = opts.get('outgoing')
1892 1891 rules = opts.get('commands', '')
1893 1892 force = opts.get('force')
1894 1893
1895 1894 cmdutil.checkunfinished(repo)
1896 1895 cmdutil.bailifchanged(repo)
1897 1896
1898 1897 topmost = repo.dirstate.p1()
1899 1898 if outg:
1900 1899 if freeargs:
1901 1900 remote = freeargs[0]
1902 1901 else:
1903 1902 remote = None
1904 1903 root = findoutgoing(ui, repo, remote, force, opts)
1905 1904 else:
1906 1905 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
1907 1906 if len(rr) != 1:
1908 1907 raise error.Abort(_('The specified revisions must have '
1909 1908 'exactly one common root'))
1910 1909 root = rr[0].node()
1911 1910
1912 1911 revs = between(repo, root, topmost, state.keep)
1913 1912 if not revs:
1914 1913 raise error.Abort(_('%s is not an ancestor of working directory') %
1915 1914 node.short(root))
1916 1915
1917 1916 ctxs = [repo[r] for r in revs]
1918 1917 if not rules:
1919 1918 comment = geteditcomment(ui, node.short(root), node.short(topmost))
1920 1919 actions = [pick(state, r) for r in revs]
1921 1920 rules = ruleeditor(repo, ui, actions, comment)
1922 1921 else:
1923 1922 rules = _readfile(ui, rules)
1924 1923 actions = parserules(rules, state)
1925 1924 warnverifyactions(ui, repo, actions, state, ctxs)
1926 1925
1927 1926 parentctxnode = repo[root].p1().node()
1928 1927
1929 1928 state.parentctxnode = parentctxnode
1930 1929 state.actions = actions
1931 1930 state.topmost = topmost
1932 1931 state.replacements = []
1933 1932
1934 1933 ui.log("histedit", "%d actions to histedit\n", len(actions),
1935 1934 histedit_num_actions=len(actions))
1936 1935
1937 1936 # Create a backup so we can always abort completely.
1938 1937 backupfile = None
1939 1938 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1940 1939 backupfile = repair.backupbundle(repo, [parentctxnode],
1941 1940 [topmost], root, 'histedit')
1942 1941 state.backupfile = backupfile
1943 1942
1944 1943 def _getsummary(ctx):
1945 1944 # a common pattern is to extract the summary but default to the empty
1946 1945 # string
1947 1946 summary = ctx.description() or ''
1948 1947 if summary:
1949 1948 summary = summary.splitlines()[0]
1950 1949 return summary
1951 1950
1952 1951 def bootstrapcontinue(ui, state, opts):
1953 1952 repo = state.repo
1954 1953
1955 1954 ms = mergemod.mergestate.read(repo)
1956 1955 mergeutil.checkunresolved(ms)
1957 1956
1958 1957 if state.actions:
1959 1958 actobj = state.actions.pop(0)
1960 1959
1961 1960 if _isdirtywc(repo):
1962 1961 actobj.continuedirty()
1963 1962 if _isdirtywc(repo):
1964 1963 abortdirty()
1965 1964
1966 1965 parentctx, replacements = actobj.continueclean()
1967 1966
1968 1967 state.parentctxnode = parentctx.node()
1969 1968 state.replacements.extend(replacements)
1970 1969
1971 1970 return state
1972 1971
1973 1972 def between(repo, old, new, keep):
1974 1973 """select and validate the set of revision to edit
1975 1974
1976 1975 When keep is false, the specified set can't have children."""
1977 1976 revs = repo.revs('%n::%n', old, new)
1978 1977 if revs and not keep:
1979 1978 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
1980 1979 repo.revs('(%ld::) - (%ld)', revs, revs)):
1981 1980 raise error.Abort(_('can only histedit a changeset together '
1982 1981 'with all its descendants'))
1983 1982 if repo.revs('(%ld) and merge()', revs):
1984 1983 raise error.Abort(_('cannot edit history that contains merges'))
1985 1984 root = repo[revs.first()] # list is already sorted by repo.revs()
1986 1985 if not root.mutable():
1987 1986 raise error.Abort(_('cannot edit public changeset: %s') % root,
1988 1987 hint=_("see 'hg help phases' for details"))
1989 1988 return pycompat.maplist(repo.changelog.node, revs)
1990 1989
1991 1990 def ruleeditor(repo, ui, actions, editcomment=""):
1992 1991 """open an editor to edit rules
1993 1992
1994 1993 rules are in the format [ [act, ctx], ...] like in state.rules
1995 1994 """
1996 1995 if repo.ui.configbool("experimental", "histedit.autoverb"):
1997 1996 newact = util.sortdict()
1998 1997 for act in actions:
1999 1998 ctx = repo[act.node]
2000 1999 summary = _getsummary(ctx)
2001 2000 fword = summary.split(' ', 1)[0].lower()
2002 2001 added = False
2003 2002
2004 2003 # if it doesn't end with the special character '!' just skip this
2005 2004 if fword.endswith('!'):
2006 2005 fword = fword[:-1]
2007 2006 if fword in primaryactions | secondaryactions | tertiaryactions:
2008 2007 act.verb = fword
2009 2008 # get the target summary
2010 2009 tsum = summary[len(fword) + 1:].lstrip()
2011 2010 # safe but slow: reverse iterate over the actions so we
2012 2011 # don't clash on two commits having the same summary
2013 2012 for na, l in reversed(list(newact.iteritems())):
2014 2013 actx = repo[na.node]
2015 2014 asum = _getsummary(actx)
2016 2015 if asum == tsum:
2017 2016 added = True
2018 2017 l.append(act)
2019 2018 break
2020 2019
2021 2020 if not added:
2022 2021 newact[act] = []
2023 2022
2024 2023 # copy over and flatten the new list
2025 2024 actions = []
2026 2025 for na, l in newact.iteritems():
2027 2026 actions.append(na)
2028 2027 actions += l
2029 2028
2030 2029 rules = '\n'.join([act.torule() for act in actions])
2031 2030 rules += '\n\n'
2032 2031 rules += editcomment
2033 2032 rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'},
2034 2033 repopath=repo.path, action='histedit')
2035 2034
2036 2035 # Save edit rules in .hg/histedit-last-edit.txt in case
2037 2036 # the user needs to ask for help after something
2038 2037 # surprising happens.
2039 2038 with repo.vfs('histedit-last-edit.txt', 'wb') as f:
2040 2039 f.write(rules)
2041 2040
2042 2041 return rules
2043 2042
2044 2043 def parserules(rules, state):
2045 2044 """Read the histedit rules string and return list of action objects """
2046 2045 rules = [l for l in (r.strip() for r in rules.splitlines())
2047 2046 if l and not l.startswith('#')]
2048 2047 actions = []
2049 2048 for r in rules:
2050 2049 if ' ' not in r:
2051 2050 raise error.ParseError(_('malformed line "%s"') % r)
2052 2051 verb, rest = r.split(' ', 1)
2053 2052
2054 2053 if verb not in actiontable:
2055 2054 raise error.ParseError(_('unknown action "%s"') % verb)
2056 2055
2057 2056 action = actiontable[verb].fromrule(state, rest)
2058 2057 actions.append(action)
2059 2058 return actions
2060 2059
2061 2060 def warnverifyactions(ui, repo, actions, state, ctxs):
2062 2061 try:
2063 2062 verifyactions(actions, state, ctxs)
2064 2063 except error.ParseError:
2065 2064 if repo.vfs.exists('histedit-last-edit.txt'):
2066 2065 ui.warn(_('warning: histedit rules saved '
2067 2066 'to: .hg/histedit-last-edit.txt\n'))
2068 2067 raise
2069 2068
2070 2069 def verifyactions(actions, state, ctxs):
2071 2070 """Verify that there exists exactly one action per given changeset and
2072 2071 other constraints.
2073 2072
2074 2073 Will abort if there are to many or too few rules, a malformed rule,
2075 2074 or a rule on a changeset outside of the user-given range.
2076 2075 """
2077 2076 expected = set(c.node() for c in ctxs)
2078 2077 seen = set()
2079 2078 prev = None
2080 2079
2081 2080 if actions and actions[0].verb in ['roll', 'fold']:
2082 2081 raise error.ParseError(_('first changeset cannot use verb "%s"') %
2083 2082 actions[0].verb)
2084 2083
2085 2084 for action in actions:
2086 2085 action.verify(prev, expected, seen)
2087 2086 prev = action
2088 2087 if action.node is not None:
2089 2088 seen.add(action.node)
2090 2089 missing = sorted(expected - seen) # sort to stabilize output
2091 2090
2092 2091 if state.repo.ui.configbool('histedit', 'dropmissing'):
2093 2092 if len(actions) == 0:
2094 2093 raise error.ParseError(_('no rules provided'),
2095 2094 hint=_('use strip extension to remove commits'))
2096 2095
2097 2096 drops = [drop(state, n) for n in missing]
2098 2097 # put the in the beginning so they execute immediately and
2099 2098 # don't show in the edit-plan in the future
2100 2099 actions[:0] = drops
2101 2100 elif missing:
2102 2101 raise error.ParseError(_('missing rules for changeset %s') %
2103 2102 node.short(missing[0]),
2104 2103 hint=_('use "drop %s" to discard, see also: '
2105 2104 "'hg help -e histedit.config'")
2106 2105 % node.short(missing[0]))
2107 2106
2108 2107 def adjustreplacementsfrommarkers(repo, oldreplacements):
2109 2108 """Adjust replacements from obsolescence markers
2110 2109
2111 2110 Replacements structure is originally generated based on
2112 2111 histedit's state and does not account for changes that are
2113 2112 not recorded there. This function fixes that by adding
2114 2113 data read from obsolescence markers"""
2115 2114 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2116 2115 return oldreplacements
2117 2116
2118 2117 unfi = repo.unfiltered()
2119 2118 nm = unfi.changelog.nodemap
2120 2119 obsstore = repo.obsstore
2121 2120 newreplacements = list(oldreplacements)
2122 2121 oldsuccs = [r[1] for r in oldreplacements]
2123 2122 # successors that have already been added to succstocheck once
2124 2123 seensuccs = set().union(*oldsuccs) # create a set from an iterable of tuples
2125 2124 succstocheck = list(seensuccs)
2126 2125 while succstocheck:
2127 2126 n = succstocheck.pop()
2128 2127 missing = nm.get(n) is None
2129 2128 markers = obsstore.successors.get(n, ())
2130 2129 if missing and not markers:
2131 2130 # dead end, mark it as such
2132 2131 newreplacements.append((n, ()))
2133 2132 for marker in markers:
2134 2133 nsuccs = marker[1]
2135 2134 newreplacements.append((n, nsuccs))
2136 2135 for nsucc in nsuccs:
2137 2136 if nsucc not in seensuccs:
2138 2137 seensuccs.add(nsucc)
2139 2138 succstocheck.append(nsucc)
2140 2139
2141 2140 return newreplacements
2142 2141
2143 2142 def processreplacement(state):
2144 2143 """process the list of replacements to return
2145 2144
2146 2145 1) the final mapping between original and created nodes
2147 2146 2) the list of temporary node created by histedit
2148 2147 3) the list of new commit created by histedit"""
2149 2148 replacements = adjustreplacementsfrommarkers(state.repo, state.replacements)
2150 2149 allsuccs = set()
2151 2150 replaced = set()
2152 2151 fullmapping = {}
2153 2152 # initialize basic set
2154 2153 # fullmapping records all operations recorded in replacement
2155 2154 for rep in replacements:
2156 2155 allsuccs.update(rep[1])
2157 2156 replaced.add(rep[0])
2158 2157 fullmapping.setdefault(rep[0], set()).update(rep[1])
2159 2158 new = allsuccs - replaced
2160 2159 tmpnodes = allsuccs & replaced
2161 2160 # Reduce content fullmapping into direct relation between original nodes
2162 2161 # and final node created during history edition
2163 2162 # Dropped changeset are replaced by an empty list
2164 2163 toproceed = set(fullmapping)
2165 2164 final = {}
2166 2165 while toproceed:
2167 2166 for x in list(toproceed):
2168 2167 succs = fullmapping[x]
2169 2168 for s in list(succs):
2170 2169 if s in toproceed:
2171 2170 # non final node with unknown closure
2172 2171 # We can't process this now
2173 2172 break
2174 2173 elif s in final:
2175 2174 # non final node, replace with closure
2176 2175 succs.remove(s)
2177 2176 succs.update(final[s])
2178 2177 else:
2179 2178 final[x] = succs
2180 2179 toproceed.remove(x)
2181 2180 # remove tmpnodes from final mapping
2182 2181 for n in tmpnodes:
2183 2182 del final[n]
2184 2183 # we expect all changes involved in final to exist in the repo
2185 2184 # turn `final` into list (topologically sorted)
2186 2185 nm = state.repo.changelog.nodemap
2187 2186 for prec, succs in final.items():
2188 2187 final[prec] = sorted(succs, key=nm.get)
2189 2188
2190 2189 # computed topmost element (necessary for bookmark)
2191 2190 if new:
2192 2191 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
2193 2192 elif not final:
2194 2193 # Nothing rewritten at all. we won't need `newtopmost`
2195 2194 # It is the same as `oldtopmost` and `processreplacement` know it
2196 2195 newtopmost = None
2197 2196 else:
2198 2197 # every body died. The newtopmost is the parent of the root.
2199 2198 r = state.repo.changelog.rev
2200 2199 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
2201 2200
2202 2201 return final, tmpnodes, new, newtopmost
2203 2202
2204 2203 def movetopmostbookmarks(repo, oldtopmost, newtopmost):
2205 2204 """Move bookmark from oldtopmost to newly created topmost
2206 2205
2207 2206 This is arguably a feature and we may only want that for the active
2208 2207 bookmark. But the behavior is kept compatible with the old version for now.
2209 2208 """
2210 2209 if not oldtopmost or not newtopmost:
2211 2210 return
2212 2211 oldbmarks = repo.nodebookmarks(oldtopmost)
2213 2212 if oldbmarks:
2214 2213 with repo.lock(), repo.transaction('histedit') as tr:
2215 2214 marks = repo._bookmarks
2216 2215 changes = []
2217 2216 for name in oldbmarks:
2218 2217 changes.append((name, newtopmost))
2219 2218 marks.applychanges(repo, tr, changes)
2220 2219
2221 2220 def cleanupnode(ui, repo, nodes, nobackup=False):
2222 2221 """strip a group of nodes from the repository
2223 2222
2224 2223 The set of node to strip may contains unknown nodes."""
2225 2224 with repo.lock():
2226 2225 # do not let filtering get in the way of the cleanse
2227 2226 # we should probably get rid of obsolescence marker created during the
2228 2227 # histedit, but we currently do not have such information.
2229 2228 repo = repo.unfiltered()
2230 2229 # Find all nodes that need to be stripped
2231 2230 # (we use %lr instead of %ln to silently ignore unknown items)
2232 2231 nm = repo.changelog.nodemap
2233 2232 nodes = sorted(n for n in nodes if n in nm)
2234 2233 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
2235 2234 if roots:
2236 2235 backup = not nobackup
2237 2236 repair.strip(ui, repo, roots, backup=backup)
2238 2237
2239 2238 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
2240 2239 if isinstance(nodelist, str):
2241 2240 nodelist = [nodelist]
2242 2241 state = histeditstate(repo)
2243 2242 if state.inprogress():
2244 2243 state.read()
2245 2244 histedit_nodes = {action.node for action
2246 2245 in state.actions if action.node}
2247 2246 common_nodes = histedit_nodes & set(nodelist)
2248 2247 if common_nodes:
2249 2248 raise error.Abort(_("histedit in progress, can't strip %s")
2250 2249 % ', '.join(node.short(x) for x in common_nodes))
2251 2250 return orig(ui, repo, nodelist, *args, **kwargs)
2252 2251
2253 2252 extensions.wrapfunction(repair, 'strip', stripwrapper)
2254 2253
2255 2254 def summaryhook(ui, repo):
2256 2255 state = histeditstate(repo)
2257 2256 if not state.inprogress():
2258 2257 return
2259 2258 state.read()
2260 2259 if state.actions:
2261 2260 # i18n: column positioning for "hg summary"
2262 2261 ui.write(_('hist: %s (histedit --continue)\n') %
2263 2262 (ui.label(_('%d remaining'), 'histedit.remaining') %
2264 2263 len(state.actions)))
2265 2264
2266 2265 def extsetup(ui):
2267 2266 cmdutil.summaryhooks.add('histedit', summaryhook)
2268 2267 cmdutil.unfinishedstates.append(
2269 2268 ['histedit-state', False, True, _('histedit in progress'),
2270 2269 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
2271 2270 cmdutil.afterresolvedstates.append(
2272 2271 ['histedit-state', _('hg histedit --continue')])
@@ -1,597 +1,595 b''
1 1 Test argument handling and various data parsing
2 2 ==================================================
3 3
4 4
5 5 Enable extensions used by this test.
6 6 $ cat >>$HGRCPATH <<EOF
7 7 > [extensions]
8 8 > histedit=
9 9 > EOF
10 10
11 11 Repo setup.
12 12 $ hg init foo
13 13 $ cd foo
14 14 $ echo alpha >> alpha
15 15 $ hg addr
16 16 adding alpha
17 17 $ hg ci -m one
18 18 $ echo alpha >> alpha
19 19 $ hg ci -m two
20 20 $ echo alpha >> alpha
21 21 $ hg ci -m three
22 22 $ echo alpha >> alpha
23 23 $ hg ci -m four
24 24 $ echo alpha >> alpha
25 25 $ hg ci -m five
26 26
27 27 $ hg log --style compact --graph
28 28 @ 4[tip] 08d98a8350f3 1970-01-01 00:00 +0000 test
29 29 | five
30 30 |
31 31 o 3 c8e68270e35a 1970-01-01 00:00 +0000 test
32 32 | four
33 33 |
34 34 o 2 eb57da33312f 1970-01-01 00:00 +0000 test
35 35 | three
36 36 |
37 37 o 1 579e40513370 1970-01-01 00:00 +0000 test
38 38 | two
39 39 |
40 40 o 0 6058cbb6cfd7 1970-01-01 00:00 +0000 test
41 41 one
42 42
43 43
44 44 histedit --continue/--abort with no existing state
45 45 --------------------------------------------------
46 46
47 47 $ hg histedit --continue
48 48 abort: no histedit in progress
49 49 [255]
50 50 $ hg histedit --abort
51 51 abort: no histedit in progress
52 52 [255]
53 53
54 54 Run a dummy edit to make sure we get tip^^ correctly via revsingle.
55 55 --------------------------------------------------------------------
56 56
57 57 $ HGEDITOR=cat hg histedit "tip^^"
58 58 pick eb57da33312f 2 three
59 59 pick c8e68270e35a 3 four
60 60 pick 08d98a8350f3 4 five
61 61
62 62 # Edit history between eb57da33312f and 08d98a8350f3
63 63 #
64 64 # Commits are listed from least to most recent
65 65 #
66 66 # You can reorder changesets by reordering the lines
67 67 #
68 68 # Commands:
69 69 #
70 70 # e, edit = use commit, but stop for amending
71 71 # m, mess = edit commit message without changing commit content
72 72 # p, pick = use commit
73 73 # b, base = checkout changeset and apply further changesets from there
74 74 # d, drop = remove commit from history
75 75 # f, fold = use commit, but combine it with the one above
76 76 # r, roll = like fold, but discard this commit's description and date
77 77 #
78 78
79 79 Run on a revision not ancestors of the current working directory.
80 80 --------------------------------------------------------------------
81 81
82 82 $ hg up 2
83 83 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 84 $ hg histedit -r 4
85 85 abort: 08d98a8350f3 is not an ancestor of working directory
86 86 [255]
87 87 $ hg up --quiet
88 88
89 89
90 90 Test that we pick the minimum of a revrange
91 91 ---------------------------------------
92 92
93 93 $ HGEDITOR=cat hg histedit '2::' --commands - << EOF
94 94 > pick eb57da33312f 2 three
95 95 > pick c8e68270e35a 3 four
96 96 > pick 08d98a8350f3 4 five
97 97 > EOF
98 98 $ hg up --quiet
99 99
100 100 $ HGEDITOR=cat hg histedit 'tip:2' --commands - << EOF
101 101 > pick eb57da33312f 2 three
102 102 > pick c8e68270e35a 3 four
103 103 > pick 08d98a8350f3 4 five
104 104 > EOF
105 105 $ hg up --quiet
106 106
107 107 Test config specified default
108 108 -----------------------------
109 109
110 110 $ HGEDITOR=cat hg histedit --config "histedit.defaultrev=only(.) - ::eb57da33312f" --commands - << EOF
111 111 > pick c8e68270e35a 3 four
112 112 > pick 08d98a8350f3 4 five
113 113 > EOF
114 114
115 115 Test invalid config default
116 116 ---------------------------
117 117
118 118 $ hg histedit --config "histedit.defaultrev="
119 119 abort: config option histedit.defaultrev can't be empty
120 120 [255]
121 121
122 122 Run on a revision not descendants of the initial parent
123 123 --------------------------------------------------------------------
124 124
125 125 Test the message shown for inconsistent histedit state, which may be
126 126 created (and forgotten) by Mercurial earlier than 2.7. This emulates
127 127 Mercurial earlier than 2.7 by renaming ".hg/histedit-state"
128 128 temporarily.
129 129
130 130 $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2::
131 131 @ 4 08d9 five
132 132 |
133 133 o 3 c8e6 four
134 134 |
135 135 o 2 eb57 three
136 136 |
137 137 ~
138 138 $ HGEDITOR=cat hg histedit -r 4 --commands - << EOF
139 139 > edit 08d98a8350f3 4 five
140 140 > EOF
141 141 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 reverting alpha
143 142 Editing (08d98a8350f3), you may commit or record as needed now.
144 143 (hg histedit --continue to resume)
145 144 [1]
146 145
147 146 $ hg graft --continue
148 147 abort: no graft in progress
149 148 (continue: hg histedit --continue)
150 149 [255]
151 150
152 151 $ mv .hg/histedit-state .hg/histedit-state.back
153 152 $ hg update --quiet --clean 2
154 153 $ echo alpha >> alpha
155 154 $ mv .hg/histedit-state.back .hg/histedit-state
156 155
157 156 $ hg histedit --continue
158 157 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-histedit.hg
159 158 $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2::
160 159 @ 4 f5ed five
161 160 |
162 161 | o 3 c8e6 four
163 162 |/
164 163 o 2 eb57 three
165 164 |
166 165 ~
167 166
168 167 $ hg unbundle -q $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-histedit.hg
169 168 $ hg strip -q -r f5ed --config extensions.strip=
170 169 $ hg up -q 08d98a8350f3
171 170
172 171 Test that missing revisions are detected
173 172 ---------------------------------------
174 173
175 174 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
176 175 > pick eb57da33312f 2 three
177 176 > pick 08d98a8350f3 4 five
178 177 > EOF
179 178 hg: parse error: missing rules for changeset c8e68270e35a
180 179 (use "drop c8e68270e35a" to discard, see also: 'hg help -e histedit.config')
181 180 [255]
182 181
183 182 Test that extra revisions are detected
184 183 ---------------------------------------
185 184
186 185 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
187 186 > pick 6058cbb6cfd7 0 one
188 187 > pick c8e68270e35a 3 four
189 188 > pick 08d98a8350f3 4 five
190 189 > EOF
191 190 hg: parse error: pick "6058cbb6cfd7" changeset was not a candidate
192 191 (only use listed changesets)
193 192 [255]
194 193
195 194 Test malformed line
196 195 ---------------------------------------
197 196
198 197 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
199 198 > pickeb57da33312f2three
200 199 > pick c8e68270e35a 3 four
201 200 > pick 08d98a8350f3 4 five
202 201 > EOF
203 202 hg: parse error: malformed line "pickeb57da33312f2three"
204 203 [255]
205 204
206 205 Test unknown changeset
207 206 ---------------------------------------
208 207
209 208 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
210 209 > pick 0123456789ab 2 three
211 210 > pick c8e68270e35a 3 four
212 211 > pick 08d98a8350f3 4 five
213 212 > EOF
214 213 hg: parse error: unknown changeset 0123456789ab listed
215 214 [255]
216 215
217 216 Test unknown command
218 217 ---------------------------------------
219 218
220 219 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
221 220 > coin eb57da33312f 2 three
222 221 > pick c8e68270e35a 3 four
223 222 > pick 08d98a8350f3 4 five
224 223 > EOF
225 224 hg: parse error: unknown action "coin"
226 225 [255]
227 226
228 227 Test duplicated changeset
229 228 ---------------------------------------
230 229
231 230 So one is missing and one appear twice.
232 231
233 232 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
234 233 > pick eb57da33312f 2 three
235 234 > pick eb57da33312f 2 three
236 235 > pick 08d98a8350f3 4 five
237 236 > EOF
238 237 hg: parse error: duplicated command for changeset eb57da33312f
239 238 [255]
240 239
241 240 Test bogus rev
242 241 ---------------------------------------
243 242
244 243 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
245 244 > pick eb57da33312f 2 three
246 245 > pick 0u98
247 246 > pick 08d98a8350f3 4 five
248 247 > EOF
249 248 hg: parse error: invalid changeset 0u98
250 249 [255]
251 250
252 251 Test short version of command
253 252 ---------------------------------------
254 253
255 254 Note: we use varying amounts of white space between command name and changeset
256 255 short hash. This tests issue3893.
257 256
258 257 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
259 258 > pick eb57da33312f 2 three
260 259 > p c8e68270e35a 3 four
261 260 > f 08d98a8350f3 4 five
262 261 > EOF
263 262 four
264 263 ***
265 264 five
266 265
267 266
268 267
269 268 HG: Enter commit message. Lines beginning with 'HG:' are removed.
270 269 HG: Leave message empty to abort commit.
271 270 HG: --
272 271 HG: user: test
273 272 HG: branch 'default'
274 273 HG: changed alpha
275 274 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/c8e68270e35a-63d8b8d8-histedit.hg
276 275
277 276 $ hg update -q 2
278 277 $ echo x > x
279 278 $ hg add x
280 279 $ hg commit -m'x' x
281 280 created new head
282 281 $ hg histedit -r 'heads(all())'
283 282 abort: The specified revisions must have exactly one common root
284 283 [255]
285 284
286 285 Test that trimming description using multi-byte characters
287 286 --------------------------------------------------------------------
288 287
289 288 $ "$PYTHON" <<EOF
290 289 > fp = open('logfile', 'wb')
291 290 > fp.write(b'12345678901234567890123456789012345678901234567890' +
292 291 > b'12345') # there are 5 more columns for 80 columns
293 292 >
294 293 > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes
295 294 > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8'))
296 295 >
297 296 > fp.close()
298 297 > EOF
299 298 $ echo xx >> x
300 299 $ hg --encoding utf-8 commit --logfile logfile
301 300
302 301 $ HGEDITOR=cat hg --encoding utf-8 histedit tip
303 302 pick 3d3ea1f3a10b 5 1234567890123456789012345678901234567890123456789012345\xe3\x81\x82... (esc)
304 303
305 304 # Edit history between 3d3ea1f3a10b and 3d3ea1f3a10b
306 305 #
307 306 # Commits are listed from least to most recent
308 307 #
309 308 # You can reorder changesets by reordering the lines
310 309 #
311 310 # Commands:
312 311 #
313 312 # e, edit = use commit, but stop for amending
314 313 # m, mess = edit commit message without changing commit content
315 314 # p, pick = use commit
316 315 # b, base = checkout changeset and apply further changesets from there
317 316 # d, drop = remove commit from history
318 317 # f, fold = use commit, but combine it with the one above
319 318 # r, roll = like fold, but discard this commit's description and date
320 319 #
321 320
322 321 Test --continue with --keep
323 322
324 323 $ hg strip -q -r . --config extensions.strip=
325 324 $ hg histedit '.^' -q --keep --commands - << EOF
326 325 > edit eb57da33312f 2 three
327 326 > pick f3cfcca30c44 4 x
328 327 > EOF
329 328 Editing (eb57da33312f), you may commit or record as needed now.
330 329 (hg histedit --continue to resume)
331 330 [1]
332 331 $ echo edit >> alpha
333 332 $ hg histedit -q --continue
334 333 $ hg log -G -T '{rev}:{node|short} {desc}'
335 334 @ 6:8fda0c726bf2 x
336 335 |
337 336 o 5:63379946892c three
338 337 |
339 338 | o 4:f3cfcca30c44 x
340 339 | |
341 340 | | o 3:2a30f3cfee78 four
342 341 | |/ ***
343 342 | | five
344 343 | o 2:eb57da33312f three
345 344 |/
346 345 o 1:579e40513370 two
347 346 |
348 347 o 0:6058cbb6cfd7 one
349 348
350 349
351 350 Test that abort fails gracefully on exception
352 351 ----------------------------------------------
353 352 $ hg histedit . -q --commands - << EOF
354 353 > edit 8fda0c726bf2 6 x
355 354 > EOF
356 355 Editing (8fda0c726bf2), you may commit or record as needed now.
357 356 (hg histedit --continue to resume)
358 357 [1]
359 358 Corrupt histedit state file
360 359 $ sed 's/8fda0c726bf2/123456789012/' .hg/histedit-state > ../corrupt-histedit
361 360 $ mv ../corrupt-histedit .hg/histedit-state
362 361 $ hg histedit --abort
363 362 warning: encountered an exception during histedit --abort; the repository may not have been completely cleaned up
364 363 abort: $TESTTMP/foo/.hg/strip-backup/*-histedit.hg: $ENOENT$ (glob) (windows !)
365 364 abort: $ENOENT$: '$TESTTMP/foo/.hg/strip-backup/*-histedit.hg' (glob) (no-windows !)
366 365 [255]
367 366 Histedit state has been exited
368 367 $ hg summary -q
369 368 parent: 5:63379946892c
370 369 commit: 1 added, 1 unknown (new branch head)
371 370 update: 4 new changesets (update)
372 371
373 372 $ cd ..
374 373
375 374 Set up default base revision tests
376 375
377 376 $ hg init defaultbase
378 377 $ cd defaultbase
379 378 $ touch foo
380 379 $ hg -q commit -A -m root
381 380 $ echo 1 > foo
382 381 $ hg commit -m 'public 1'
383 382 $ hg phase --force --public -r .
384 383 $ echo 2 > foo
385 384 $ hg commit -m 'draft after public'
386 385 $ hg -q up -r 1
387 386 $ echo 3 > foo
388 387 $ hg commit -m 'head 1 public'
389 388 created new head
390 389 $ hg phase --force --public -r .
391 390 $ echo 4 > foo
392 391 $ hg commit -m 'head 1 draft 1'
393 392 $ echo 5 > foo
394 393 $ hg commit -m 'head 1 draft 2'
395 394 $ hg -q up -r 2
396 395 $ echo 6 > foo
397 396 $ hg commit -m 'head 2 commit 1'
398 397 $ echo 7 > foo
399 398 $ hg commit -m 'head 2 commit 2'
400 399 $ hg -q up -r 2
401 400 $ echo 8 > foo
402 401 $ hg commit -m 'head 3'
403 402 created new head
404 403 $ hg -q up -r 2
405 404 $ echo 9 > foo
406 405 $ hg commit -m 'head 4'
407 406 created new head
408 407 $ hg merge --tool :local -r 8
409 408 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
410 409 (branch merge, don't forget to commit)
411 410 $ hg commit -m 'merge head 3 into head 4'
412 411 $ echo 11 > foo
413 412 $ hg commit -m 'commit 1 after merge'
414 413 $ echo 12 > foo
415 414 $ hg commit -m 'commit 2 after merge'
416 415
417 416 $ hg log -G -T '{rev}:{node|short} {phase} {desc}\n'
418 417 @ 12:8cde254db839 draft commit 2 after merge
419 418 |
420 419 o 11:6f2f0241f119 draft commit 1 after merge
421 420 |
422 421 o 10:90506cc76b00 draft merge head 3 into head 4
423 422 |\
424 423 | o 9:f8607a373a97 draft head 4
425 424 | |
426 425 o | 8:0da92be05148 draft head 3
427 426 |/
428 427 | o 7:4c35cdf97d5e draft head 2 commit 2
429 428 | |
430 429 | o 6:931820154288 draft head 2 commit 1
431 430 |/
432 431 | o 5:8cdc02b9bc63 draft head 1 draft 2
433 432 | |
434 433 | o 4:463b8c0d2973 draft head 1 draft 1
435 434 | |
436 435 | o 3:23a0c4eefcbf public head 1 public
437 436 | |
438 437 o | 2:4117331c3abb draft draft after public
439 438 |/
440 439 o 1:4426d359ea59 public public 1
441 440 |
442 441 o 0:54136a8ddf32 public root
443 442
444 443
445 444 Default base revision should stop at public changesets
446 445
447 446 $ hg -q up 8cdc02b9bc63
448 447 $ hg histedit --commands - <<EOF
449 448 > pick 463b8c0d2973
450 449 > pick 8cdc02b9bc63
451 450 > EOF
452 451
453 452 Default base revision should stop at branchpoint
454 453
455 454 $ hg -q up 4c35cdf97d5e
456 455 $ hg histedit --commands - <<EOF
457 456 > pick 931820154288
458 457 > pick 4c35cdf97d5e
459 458 > EOF
460 459
461 460 Default base revision should stop at merge commit
462 461
463 462 $ hg -q up 8cde254db839
464 463 $ hg histedit --commands - <<EOF
465 464 > pick 6f2f0241f119
466 465 > pick 8cde254db839
467 466 > EOF
468 467
469 468 commit --amend should abort if histedit is in progress
470 469 (issue4800) and markers are not being created.
471 470 Eventually, histedit could perhaps look at `source` extra,
472 471 in which case this test should be revisited.
473 472
474 473 $ hg -q up 8cde254db839
475 474 $ hg histedit 6f2f0241f119 --commands - <<EOF
476 475 > pick 8cde254db839
477 476 > edit 6f2f0241f119
478 477 > EOF
479 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
480 478 merging foo
481 479 warning: conflicts while merging foo! (edit, then use 'hg resolve --mark')
482 480 Fix up the change (pick 8cde254db839)
483 481 (hg histedit --continue to resume)
484 482 [1]
485 483 $ hg resolve -m --all
486 484 (no more unresolved files)
487 485 continue: hg histedit --continue
488 486 $ hg histedit --cont
489 487 merging foo
490 488 warning: conflicts while merging foo! (edit, then use 'hg resolve --mark')
491 489 Editing (6f2f0241f119), you may commit or record as needed now.
492 490 (hg histedit --continue to resume)
493 491 [1]
494 492 $ hg resolve -m --all
495 493 (no more unresolved files)
496 494 continue: hg histedit --continue
497 495 $ hg commit --amend -m 'reject this fold'
498 496 abort: histedit in progress
499 497 (use 'hg histedit --continue' or 'hg histedit --abort')
500 498 [255]
501 499
502 500 With markers enabled, histedit does not get confused, and
503 501 amend should not be blocked by the ongoing histedit.
504 502
505 503 $ cat >>$HGRCPATH <<EOF
506 504 > [experimental]
507 505 > evolution.createmarkers=True
508 506 > evolution.allowunstable=True
509 507 > EOF
510 508 $ hg commit --amend -m 'allow this fold'
511 509 $ hg histedit --continue
512 510
513 511 $ cd ..
514 512
515 513 Test autoverb feature
516 514
517 515 $ hg init autoverb
518 516 $ cd autoverb
519 517 $ echo alpha >> alpha
520 518 $ hg ci -qAm one
521 519 $ echo alpha >> alpha
522 520 $ hg ci -qm two
523 521 $ echo beta >> beta
524 522 $ hg ci -qAm "roll! one"
525 523
526 524 $ hg log --style compact --graph
527 525 @ 2[tip] 4f34d0f8b5fa 1970-01-01 00:00 +0000 test
528 526 | roll! one
529 527 |
530 528 o 1 579e40513370 1970-01-01 00:00 +0000 test
531 529 | two
532 530 |
533 531 o 0 6058cbb6cfd7 1970-01-01 00:00 +0000 test
534 532 one
535 533
536 534
537 535 Check that 'roll' is selected by default
538 536
539 537 $ HGEDITOR=cat hg histedit 0 --config experimental.histedit.autoverb=True
540 538 pick 6058cbb6cfd7 0 one
541 539 roll 4f34d0f8b5fa 2 roll! one
542 540 pick 579e40513370 1 two
543 541
544 542 # Edit history between 6058cbb6cfd7 and 4f34d0f8b5fa
545 543 #
546 544 # Commits are listed from least to most recent
547 545 #
548 546 # You can reorder changesets by reordering the lines
549 547 #
550 548 # Commands:
551 549 #
552 550 # e, edit = use commit, but stop for amending
553 551 # m, mess = edit commit message without changing commit content
554 552 # p, pick = use commit
555 553 # b, base = checkout changeset and apply further changesets from there
556 554 # d, drop = remove commit from history
557 555 # f, fold = use commit, but combine it with the one above
558 556 # r, roll = like fold, but discard this commit's description and date
559 557 #
560 558
561 559 $ cd ..
562 560
563 561 Check that histedit's commands accept revsets
564 562 $ hg init bar
565 563 $ cd bar
566 564 $ echo w >> a
567 565 $ hg ci -qAm "adds a"
568 566 $ echo x >> b
569 567 $ hg ci -qAm "adds b"
570 568 $ echo y >> c
571 569 $ hg ci -qAm "adds c"
572 570 $ echo z >> d
573 571 $ hg ci -qAm "adds d"
574 572 $ hg log -G -T '{rev} {desc}\n'
575 573 @ 3 adds d
576 574 |
577 575 o 2 adds c
578 576 |
579 577 o 1 adds b
580 578 |
581 579 o 0 adds a
582 580
583 581 $ HGEDITOR=cat hg histedit "2" --commands - << EOF
584 582 > base -4 adds c
585 583 > pick 2 adds c
586 584 > pick tip adds d
587 585 > EOF
588 586 $ hg log -G -T '{rev} {desc}\n'
589 587 @ 5 adds d
590 588 |
591 589 o 4 adds c
592 590 |
593 591 | o 1 adds b
594 592 |/
595 593 o 0 adds a
596 594
597 595
@@ -1,555 +1,554 b''
1 1 $ . "$TESTDIR/histedit-helpers.sh"
2 2
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [extensions]
5 5 > histedit=
6 6 > strip=
7 7 > mockmakedate = $TESTDIR/mockmakedate.py
8 8 > EOF
9 9
10 10 $ initrepo ()
11 11 > {
12 12 > hg init r
13 13 > cd r
14 14 > for x in a b c d e f g; do
15 15 > echo $x > $x
16 16 > hg add $x
17 17 > hg ci -m $x
18 18 > done
19 19 > }
20 20
21 21 $ initrepo
22 22
23 23 log before edit
24 24 $ hg log --graph
25 25 @ changeset: 6:3c6a8ed2ebe8
26 26 | tag: tip
27 27 | user: test
28 28 | date: Thu Jan 01 00:00:00 1970 +0000
29 29 | summary: g
30 30 |
31 31 o changeset: 5:652413bf663e
32 32 | user: test
33 33 | date: Thu Jan 01 00:00:00 1970 +0000
34 34 | summary: f
35 35 |
36 36 o changeset: 4:e860deea161a
37 37 | user: test
38 38 | date: Thu Jan 01 00:00:00 1970 +0000
39 39 | summary: e
40 40 |
41 41 o changeset: 3:055a42cdd887
42 42 | user: test
43 43 | date: Thu Jan 01 00:00:00 1970 +0000
44 44 | summary: d
45 45 |
46 46 o changeset: 2:177f92b77385
47 47 | user: test
48 48 | date: Thu Jan 01 00:00:00 1970 +0000
49 49 | summary: c
50 50 |
51 51 o changeset: 1:d2ae7f538514
52 52 | user: test
53 53 | date: Thu Jan 01 00:00:00 1970 +0000
54 54 | summary: b
55 55 |
56 56 o changeset: 0:cb9a9f314b8b
57 57 user: test
58 58 date: Thu Jan 01 00:00:00 1970 +0000
59 59 summary: a
60 60
61 61 dirty a file
62 62 $ echo a > g
63 63 $ hg histedit 177f92b77385 --commands - 2>&1 << EOF
64 64 > EOF
65 65 abort: uncommitted changes
66 66 [255]
67 67 $ echo g > g
68 68
69 69 edit the history
70 70 $ hg histedit 177f92b77385 --commands - 2>&1 << EOF| fixbundle
71 71 > pick 177f92b77385 c
72 72 > pick 055a42cdd887 d
73 73 > edit e860deea161a e
74 74 > pick 652413bf663e f
75 75 > pick 3c6a8ed2ebe8 g
76 76 > EOF
77 77 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
78 78 Editing (e860deea161a), you may commit or record as needed now.
79 79 (hg histedit --continue to resume)
80 80
81 81 try to update and get an error
82 82 $ hg update tip
83 83 abort: histedit in progress
84 84 (use 'hg histedit --continue' or 'hg histedit --abort')
85 85 [255]
86 86
87 87 edit the plan via the editor
88 88 $ cat >> $TESTTMP/editplan.sh <<EOF
89 89 > cat > \$1 <<EOF2
90 90 > drop e860deea161a e
91 91 > drop 652413bf663e f
92 92 > drop 3c6a8ed2ebe8 g
93 93 > EOF2
94 94 > EOF
95 95 $ HGEDITOR="sh $TESTTMP/editplan.sh" hg histedit --edit-plan
96 96 $ cat .hg/histedit-state
97 97 v1
98 98 055a42cdd88768532f9cf79daa407fc8d138de9b
99 99 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799
100 100 False
101 101 3
102 102 drop
103 103 e860deea161a2f77de56603b340ebbb4536308ae
104 104 drop
105 105 652413bf663ef2a641cab26574e46d5f5a64a55a
106 106 drop
107 107 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799
108 108 0
109 109 strip-backup/177f92b77385-0ebe6a8f-histedit.hg
110 110
111 111 edit the plan via --commands
112 112 $ hg histedit --edit-plan --commands - 2>&1 << EOF
113 113 > edit e860deea161a e
114 114 > pick 652413bf663e f
115 115 > drop 3c6a8ed2ebe8 g
116 116 > EOF
117 117 $ cat .hg/histedit-state
118 118 v1
119 119 055a42cdd88768532f9cf79daa407fc8d138de9b
120 120 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799
121 121 False
122 122 3
123 123 edit
124 124 e860deea161a2f77de56603b340ebbb4536308ae
125 125 pick
126 126 652413bf663ef2a641cab26574e46d5f5a64a55a
127 127 drop
128 128 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799
129 129 0
130 130 strip-backup/177f92b77385-0ebe6a8f-histedit.hg
131 131
132 132 Go at a random point and try to continue
133 133
134 134 $ hg id -n
135 135 3+
136 136 $ hg up 0
137 137 abort: histedit in progress
138 138 (use 'hg histedit --continue' or 'hg histedit --abort')
139 139 [255]
140 140
141 141 Try to delete necessary commit
142 142 $ hg strip -r 652413b
143 143 abort: histedit in progress, can't strip 652413bf663e
144 144 [255]
145 145
146 146 commit, then edit the revision
147 147 $ hg ci -m 'wat'
148 148 created new head
149 149 $ echo a > e
150 150
151 151 qnew should fail while we're in the middle of the edit step
152 152
153 153 $ hg --config extensions.mq= qnew please-fail
154 154 abort: histedit in progress
155 155 (use 'hg histedit --continue' or 'hg histedit --abort')
156 156 [255]
157 157 $ HGEDITOR='echo foobaz > ' hg histedit --continue 2>&1 | fixbundle
158 158
159 159 $ hg log --graph
160 160 @ changeset: 6:b5f70786f9b0
161 161 | tag: tip
162 162 | user: test
163 163 | date: Thu Jan 01 00:00:00 1970 +0000
164 164 | summary: f
165 165 |
166 166 o changeset: 5:a5e1ba2f7afb
167 167 | user: test
168 168 | date: Thu Jan 01 00:00:00 1970 +0000
169 169 | summary: foobaz
170 170 |
171 171 o changeset: 4:1a60820cd1f6
172 172 | user: test
173 173 | date: Thu Jan 01 00:00:00 1970 +0000
174 174 | summary: wat
175 175 |
176 176 o changeset: 3:055a42cdd887
177 177 | user: test
178 178 | date: Thu Jan 01 00:00:00 1970 +0000
179 179 | summary: d
180 180 |
181 181 o changeset: 2:177f92b77385
182 182 | user: test
183 183 | date: Thu Jan 01 00:00:00 1970 +0000
184 184 | summary: c
185 185 |
186 186 o changeset: 1:d2ae7f538514
187 187 | user: test
188 188 | date: Thu Jan 01 00:00:00 1970 +0000
189 189 | summary: b
190 190 |
191 191 o changeset: 0:cb9a9f314b8b
192 192 user: test
193 193 date: Thu Jan 01 00:00:00 1970 +0000
194 194 summary: a
195 195
196 196
197 197 $ hg cat e
198 198 a
199 199
200 200 Stripping necessary commits should not break --abort
201 201
202 202 $ hg histedit 1a60820cd1f6 --commands - 2>&1 << EOF| fixbundle
203 203 > edit 1a60820cd1f6 wat
204 204 > pick a5e1ba2f7afb foobaz
205 205 > pick b5f70786f9b0 g
206 206 > EOF
207 207 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
208 208 Editing (1a60820cd1f6), you may commit or record as needed now.
209 209 (hg histedit --continue to resume)
210 210
211 211 $ mv .hg/histedit-state .hg/histedit-state.bak
212 212 $ hg strip -q -r b5f70786f9b0
213 213 $ mv .hg/histedit-state.bak .hg/histedit-state
214 214 $ hg histedit --abort
215 215 adding changesets
216 216 adding manifests
217 217 adding file changes
218 218 added 1 changesets with 1 changes to 3 files
219 219 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
220 220 $ hg log -r .
221 221 changeset: 6:b5f70786f9b0
222 222 tag: tip
223 223 user: test
224 224 date: Thu Jan 01 00:00:00 1970 +0000
225 225 summary: f
226 226
227 227
228 228 check histedit_source
229 229
230 230 $ hg log --debug --rev 5
231 231 changeset: 5:a5e1ba2f7afb899ef1581cea528fd885d2fca70d
232 232 phase: draft
233 233 parent: 4:1a60820cd1f6004a362aa622ebc47d59bc48eb34
234 234 parent: -1:0000000000000000000000000000000000000000
235 235 manifest: 5:5ad3be8791f39117565557781f5464363b918a45
236 236 user: test
237 237 date: Thu Jan 01 00:00:00 1970 +0000
238 238 files: e
239 239 extra: branch=default
240 240 extra: histedit_source=e860deea161a2f77de56603b340ebbb4536308ae
241 241 description:
242 242 foobaz
243 243
244 244
245 245
246 246 $ hg histedit tip --commands - 2>&1 <<EOF| fixbundle
247 247 > edit b5f70786f9b0 f
248 248 > EOF
249 249 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
250 250 Editing (b5f70786f9b0), you may commit or record as needed now.
251 251 (hg histedit --continue to resume)
252 252 $ hg status
253 253 A f
254 254
255 255 $ hg summary
256 256 parent: 5:a5e1ba2f7afb
257 257 foobaz
258 258 branch: default
259 259 commit: 1 added (new branch head)
260 260 update: 1 new changesets (update)
261 261 phases: 7 draft
262 262 hist: 1 remaining (histedit --continue)
263 263
264 264 (test also that editor is invoked if histedit is continued for
265 265 "edit" action)
266 266
267 267 $ HGEDITOR='cat' hg histedit --continue
268 268 f
269 269
270 270
271 271 HG: Enter commit message. Lines beginning with 'HG:' are removed.
272 272 HG: Leave message empty to abort commit.
273 273 HG: --
274 274 HG: user: test
275 275 HG: branch 'default'
276 276 HG: added f
277 277 saved backup bundle to $TESTTMP/r/.hg/strip-backup/b5f70786f9b0-c28d9c86-histedit.hg
278 278
279 279 $ hg status
280 280
281 281 log after edit
282 282 $ hg log --limit 1
283 283 changeset: 6:a107ee126658
284 284 tag: tip
285 285 user: test
286 286 date: Thu Jan 01 00:00:00 1970 +0000
287 287 summary: f
288 288
289 289
290 290 say we'll change the message, but don't.
291 291 $ cat > ../edit.sh <<EOF
292 292 > cat "\$1" | sed s/pick/mess/ > tmp
293 293 > mv tmp "\$1"
294 294 > EOF
295 295 $ HGEDITOR="sh ../edit.sh" hg histedit tip 2>&1 | fixbundle
296 296 $ hg status
297 297 $ hg log --limit 1
298 298 changeset: 6:1fd3b2fe7754
299 299 tag: tip
300 300 user: test
301 301 date: Thu Jan 01 00:00:00 1970 +0000
302 302 summary: f
303 303
304 304
305 305 modify the message
306 306
307 307 check saving last-message.txt, at first
308 308
309 309 $ cat > $TESTTMP/commitfailure.py <<EOF
310 310 > from mercurial import error
311 311 > def reposetup(ui, repo):
312 312 > class commitfailure(repo.__class__):
313 313 > def commit(self, *args, **kwargs):
314 314 > raise error.Abort('emulating unexpected abort')
315 315 > repo.__class__ = commitfailure
316 316 > EOF
317 317 $ cat >> .hg/hgrc <<EOF
318 318 > [extensions]
319 319 > # this failure occurs before editor invocation
320 320 > commitfailure = $TESTTMP/commitfailure.py
321 321 > EOF
322 322
323 323 $ cat > $TESTTMP/editor.sh <<EOF
324 324 > echo "==== before editing"
325 325 > cat \$1
326 326 > echo "===="
327 327 > echo "check saving last-message.txt" >> \$1
328 328 > EOF
329 329
330 330 (test that editor is not invoked before transaction starting)
331 331
332 332 $ rm -f .hg/last-message.txt
333 333 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF | fixbundle
334 334 > mess 1fd3b2fe7754 f
335 335 > EOF
336 336 abort: emulating unexpected abort
337 337 $ test -f .hg/last-message.txt
338 338 [1]
339 339
340 340 $ cat >> .hg/hgrc <<EOF
341 341 > [extensions]
342 342 > commitfailure = !
343 343 > EOF
344 344 $ hg histedit --abort -q
345 345
346 346 (test that editor is invoked and commit message is saved into
347 347 "last-message.txt")
348 348
349 349 $ cat >> .hg/hgrc <<EOF
350 350 > [hooks]
351 351 > # this failure occurs after editor invocation
352 352 > pretxncommit.unexpectedabort = false
353 353 > EOF
354 354
355 355 $ hg status --rev '1fd3b2fe7754^1' --rev 1fd3b2fe7754
356 356 A f
357 357
358 358 $ rm -f .hg/last-message.txt
359 359 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF
360 360 > mess 1fd3b2fe7754 f
361 361 > EOF
362 362 ==== before editing
363 363 f
364 364
365 365
366 366 HG: Enter commit message. Lines beginning with 'HG:' are removed.
367 367 HG: Leave message empty to abort commit.
368 368 HG: --
369 369 HG: user: test
370 370 HG: branch 'default'
371 371 HG: added f
372 372 ====
373 373 transaction abort!
374 374 rollback completed
375 375 note: commit message saved in .hg/last-message.txt
376 376 abort: pretxncommit.unexpectedabort hook exited with status 1
377 377 [255]
378 378 $ cat .hg/last-message.txt
379 379 f
380 380
381 381
382 382 check saving last-message.txt
383 383
384 384 (test also that editor is invoked if histedit is continued for "message"
385 385 action)
386 386
387 387 $ HGEDITOR=cat hg histedit --continue
388 388 f
389 389
390 390
391 391 HG: Enter commit message. Lines beginning with 'HG:' are removed.
392 392 HG: Leave message empty to abort commit.
393 393 HG: --
394 394 HG: user: test
395 395 HG: branch 'default'
396 396 HG: added f
397 397 transaction abort!
398 398 rollback completed
399 399 note: commit message saved in .hg/last-message.txt
400 400 abort: pretxncommit.unexpectedabort hook exited with status 1
401 401 [255]
402 402
403 403 $ cat >> .hg/hgrc <<EOF
404 404 > [hooks]
405 405 > pretxncommit.unexpectedabort =
406 406 > EOF
407 407 $ hg histedit --abort -q
408 408
409 409 then, check "modify the message" itself
410 410
411 411 $ hg histedit tip --commands - 2>&1 << EOF | fixbundle
412 412 > mess 1fd3b2fe7754 f
413 413 > EOF
414 414 $ hg status
415 415 $ hg log --limit 1
416 416 changeset: 6:62feedb1200e
417 417 tag: tip
418 418 user: test
419 419 date: Thu Jan 01 00:00:00 1970 +0000
420 420 summary: f
421 421
422 422
423 423 rollback should not work after a histedit
424 424 $ hg rollback
425 425 no rollback information available
426 426 [1]
427 427
428 428 $ cd ..
429 429 $ hg clone -qr0 r r0
430 430 $ cd r0
431 431 $ hg phase -fdr0
432 432 $ hg histedit --commands - 0 2>&1 << EOF
433 433 > edit cb9a9f314b8b a > $EDITED
434 434 > EOF
435 435 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
436 adding a
437 436 Editing (cb9a9f314b8b), you may commit or record as needed now.
438 437 (hg histedit --continue to resume)
439 438 [1]
440 439 $ HGEDITOR=true hg histedit --continue
441 440 saved backup bundle to $TESTTMP/r0/.hg/strip-backup/cb9a9f314b8b-cc5ccb0b-histedit.hg
442 441
443 442 $ hg log -G
444 443 @ changeset: 0:0efcea34f18a
445 444 tag: tip
446 445 user: test
447 446 date: Thu Jan 01 00:00:00 1970 +0000
448 447 summary: a
449 448
450 449 $ echo foo >> b
451 450 $ hg addr
452 451 adding b
453 452 $ hg ci -m 'add b'
454 453 $ echo foo >> a
455 454 $ hg ci -m 'extend a'
456 455 $ hg phase --public 1
457 456 Attempting to fold a change into a public change should not work:
458 457 $ cat > ../edit.sh <<EOF
459 458 > cat "\$1" | sed s/pick/fold/ > tmp
460 459 > mv tmp "\$1"
461 460 > EOF
462 461 $ HGEDITOR="sh ../edit.sh" hg histedit 2
463 462 warning: histedit rules saved to: .hg/histedit-last-edit.txt
464 463 hg: parse error: first changeset cannot use verb "fold"
465 464 [255]
466 465 $ cat .hg/histedit-last-edit.txt
467 466 fold 0012be4a27ea 2 extend a
468 467
469 468 # Edit history between 0012be4a27ea and 0012be4a27ea
470 469 #
471 470 # Commits are listed from least to most recent
472 471 #
473 472 # You can reorder changesets by reordering the lines
474 473 #
475 474 # Commands:
476 475 #
477 476 # e, edit = use commit, but stop for amending
478 477 # m, mess = edit commit message without changing commit content
479 478 # p, fold = use commit
480 479 # b, base = checkout changeset and apply further changesets from there
481 480 # d, drop = remove commit from history
482 481 # f, fold = use commit, but combine it with the one above
483 482 # r, roll = like fold, but discard this commit's description and date
484 483 #
485 484
486 485 $ cd ..
487 486
488 487 ============================================
489 488 Test update-timestamp config option in mess|
490 489 ============================================
491 490
492 491 $ addwithdate ()
493 492 > {
494 493 > echo $1 > $1
495 494 > hg add $1
496 495 > hg ci -m $1 -d "$2 0"
497 496 > }
498 497
499 498 $ initrepo ()
500 499 > {
501 500 > hg init r2
502 501 > cd r2
503 502 > addwithdate a 1
504 503 > addwithdate b 2
505 504 > addwithdate c 3
506 505 > addwithdate d 4
507 506 > addwithdate e 5
508 507 > addwithdate f 6
509 508 > }
510 509
511 510 $ initrepo
512 511
513 512 log before edit
514 513
515 514 $ hg log --limit 1
516 515 changeset: 5:178e35e0ce73
517 516 tag: tip
518 517 user: test
519 518 date: Thu Jan 01 00:00:06 1970 +0000
520 519 summary: f
521 520
522 521 $ hg histedit tip --commands - 2>&1 --config rewrite.update-timestamp=True << EOF | fixbundle
523 522 > mess 178e35e0ce73 f
524 523 > EOF
525 524
526 525 log after edit
527 526
528 527 $ hg log --limit 1
529 528 changeset: 5:98bf456d476b
530 529 tag: tip
531 530 user: test
532 531 date: Thu Jan 01 00:00:00 1970 +0000
533 532 summary: f
534 533
535 534
536 535 $ cd ..
537 536
538 537 warn the user on editing tagged commits
539 538
540 539 $ hg init issue4017
541 540 $ cd issue4017
542 541 $ echo > a
543 542 $ hg ci -Am 'add a'
544 543 adding a
545 544 $ hg tag a
546 545 $ hg tags
547 546 tip 1:bd7ee4f3939b
548 547 a 0:a8a82d372bb3
549 548 $ hg histedit
550 549 warning: tags associated with the given changeset will be lost after histedit.
551 550 do you want to continue (yN)? n
552 551 abort: histedit cancelled
553 552
554 553 [255]
555 554 $ cd ..
@@ -1,344 +1,342 b''
1 1 $ . "$TESTDIR/histedit-helpers.sh"
2 2
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [extensions]
5 5 > histedit=
6 6 > EOF
7 7
8 8 $ modwithdate ()
9 9 > {
10 10 > echo $1 > $1
11 11 > hg ci -m $1 -d "$2 0"
12 12 > }
13 13
14 14 $ initrepo ()
15 15 > {
16 16 > hg init $1
17 17 > cd $1
18 18 > for x in a b c d e f ; do
19 19 > echo $x$x$x$x$x > $x
20 20 > hg add $x
21 21 > done
22 22 > hg ci -m 'Initial commit'
23 23 > modwithdate a 1
24 24 > modwithdate b 2
25 25 > modwithdate c 3
26 26 > modwithdate d 4
27 27 > modwithdate e 5
28 28 > modwithdate f 6
29 29 > echo 'I can haz no commute' > e
30 30 > hg ci -m 'does not commute with e' -d '7 0'
31 31 > cd ..
32 32 > }
33 33
34 34 $ initrepo r
35 35 $ cd r
36 36 Initial generation of the command files
37 37
38 38 $ EDITED="$TESTTMP/editedhistory"
39 39 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 3 >> $EDITED
40 40 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 4 >> $EDITED
41 41 $ hg log --template 'fold {node|short} {rev} {desc}\n' -r 7 >> $EDITED
42 42 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 5 >> $EDITED
43 43 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 6 >> $EDITED
44 44 $ cat $EDITED
45 45 pick 092e4ce14829 3 c
46 46 pick ae78f4c9d74f 4 d
47 47 fold 42abbb61bede 7 does not commute with e
48 48 pick 7f3755409b00 5 e
49 49 pick dd184f2faeb0 6 f
50 50
51 51 log before edit
52 52 $ hg log --graph
53 53 @ changeset: 7:42abbb61bede
54 54 | tag: tip
55 55 | user: test
56 56 | date: Thu Jan 01 00:00:07 1970 +0000
57 57 | summary: does not commute with e
58 58 |
59 59 o changeset: 6:dd184f2faeb0
60 60 | user: test
61 61 | date: Thu Jan 01 00:00:06 1970 +0000
62 62 | summary: f
63 63 |
64 64 o changeset: 5:7f3755409b00
65 65 | user: test
66 66 | date: Thu Jan 01 00:00:05 1970 +0000
67 67 | summary: e
68 68 |
69 69 o changeset: 4:ae78f4c9d74f
70 70 | user: test
71 71 | date: Thu Jan 01 00:00:04 1970 +0000
72 72 | summary: d
73 73 |
74 74 o changeset: 3:092e4ce14829
75 75 | user: test
76 76 | date: Thu Jan 01 00:00:03 1970 +0000
77 77 | summary: c
78 78 |
79 79 o changeset: 2:40ccdd8beb95
80 80 | user: test
81 81 | date: Thu Jan 01 00:00:02 1970 +0000
82 82 | summary: b
83 83 |
84 84 o changeset: 1:cd997a145b29
85 85 | user: test
86 86 | date: Thu Jan 01 00:00:01 1970 +0000
87 87 | summary: a
88 88 |
89 89 o changeset: 0:1715188a53c7
90 90 user: test
91 91 date: Thu Jan 01 00:00:00 1970 +0000
92 92 summary: Initial commit
93 93
94 94
95 95 edit the history
96 96 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
97 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
98 97 merging e
99 98 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
100 99 Fix up the change (fold 42abbb61bede)
101 100 (hg histedit --continue to resume)
102 101
103 102 fix up
104 103 $ echo 'I can haz no commute' > e
105 104 $ hg resolve --mark e
106 105 (no more unresolved files)
107 106 continue: hg histedit --continue
108 107 $ HGEDITOR=cat hg histedit --continue 2>&1 | fixbundle | grep -v '2 files removed'
109 108 d
110 109 ***
111 110 does not commute with e
112 111
113 112
114 113
115 114 HG: Enter commit message. Lines beginning with 'HG:' are removed.
116 115 HG: Leave message empty to abort commit.
117 116 HG: --
118 117 HG: user: test
119 118 HG: branch 'default'
120 119 HG: changed d
121 120 HG: changed e
122 121 merging e
123 122 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
124 123 Fix up the change (pick 7f3755409b00)
125 124 (hg histedit --continue to resume)
126 125
127 126 just continue this time
128 127 keep the non-commuting change, and thus the pending change will be dropped
129 128 $ hg revert -r 'p1()' e
130 129 $ hg resolve --mark e
131 130 (no more unresolved files)
132 131 continue: hg histedit --continue
133 132 $ hg diff
134 133 $ hg histedit --continue 2>&1 | fixbundle
135 134 7f3755409b00: skipping changeset (no changes)
136 135
137 136 log after edit
138 137 $ hg log --graph
139 138 @ changeset: 5:1300355b1a54
140 139 | tag: tip
141 140 | user: test
142 141 | date: Thu Jan 01 00:00:06 1970 +0000
143 142 | summary: f
144 143 |
145 144 o changeset: 4:e2ac33269083
146 145 | user: test
147 146 | date: Thu Jan 01 00:00:07 1970 +0000
148 147 | summary: d
149 148 |
150 149 o changeset: 3:092e4ce14829
151 150 | user: test
152 151 | date: Thu Jan 01 00:00:03 1970 +0000
153 152 | summary: c
154 153 |
155 154 o changeset: 2:40ccdd8beb95
156 155 | user: test
157 156 | date: Thu Jan 01 00:00:02 1970 +0000
158 157 | summary: b
159 158 |
160 159 o changeset: 1:cd997a145b29
161 160 | user: test
162 161 | date: Thu Jan 01 00:00:01 1970 +0000
163 162 | summary: a
164 163 |
165 164 o changeset: 0:1715188a53c7
166 165 user: test
167 166 date: Thu Jan 01 00:00:00 1970 +0000
168 167 summary: Initial commit
169 168
170 169
171 170 contents of e
172 171 $ hg cat e
173 172 I can haz no commute
174 173
175 174 manifest
176 175 $ hg manifest
177 176 a
178 177 b
179 178 c
180 179 d
181 180 e
182 181 f
183 182
184 183 $ cd ..
185 184
186 185 Repeat test using "roll", not "fold". "roll" folds in changes but drops message and date
187 186
188 187 $ initrepo r2
189 188 $ cd r2
190 189
191 190 Initial generation of the command files
192 191
193 192 $ EDITED="$TESTTMP/editedhistory.2"
194 193 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 3 >> $EDITED
195 194 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 4 >> $EDITED
196 195 $ hg log --template 'roll {node|short} {rev} {desc}\n' -r 7 >> $EDITED
197 196 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 5 >> $EDITED
198 197 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 6 >> $EDITED
199 198 $ cat $EDITED
200 199 pick 092e4ce14829 3 c
201 200 pick ae78f4c9d74f 4 d
202 201 roll 42abbb61bede 7 does not commute with e
203 202 pick 7f3755409b00 5 e
204 203 pick dd184f2faeb0 6 f
205 204
206 205 log before edit
207 206 $ hg log --graph
208 207 @ changeset: 7:42abbb61bede
209 208 | tag: tip
210 209 | user: test
211 210 | date: Thu Jan 01 00:00:07 1970 +0000
212 211 | summary: does not commute with e
213 212 |
214 213 o changeset: 6:dd184f2faeb0
215 214 | user: test
216 215 | date: Thu Jan 01 00:00:06 1970 +0000
217 216 | summary: f
218 217 |
219 218 o changeset: 5:7f3755409b00
220 219 | user: test
221 220 | date: Thu Jan 01 00:00:05 1970 +0000
222 221 | summary: e
223 222 |
224 223 o changeset: 4:ae78f4c9d74f
225 224 | user: test
226 225 | date: Thu Jan 01 00:00:04 1970 +0000
227 226 | summary: d
228 227 |
229 228 o changeset: 3:092e4ce14829
230 229 | user: test
231 230 | date: Thu Jan 01 00:00:03 1970 +0000
232 231 | summary: c
233 232 |
234 233 o changeset: 2:40ccdd8beb95
235 234 | user: test
236 235 | date: Thu Jan 01 00:00:02 1970 +0000
237 236 | summary: b
238 237 |
239 238 o changeset: 1:cd997a145b29
240 239 | user: test
241 240 | date: Thu Jan 01 00:00:01 1970 +0000
242 241 | summary: a
243 242 |
244 243 o changeset: 0:1715188a53c7
245 244 user: test
246 245 date: Thu Jan 01 00:00:00 1970 +0000
247 246 summary: Initial commit
248 247
249 248
250 249 edit the history
251 250 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
252 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
253 251 merging e
254 252 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
255 253 Fix up the change (roll 42abbb61bede)
256 254 (hg histedit --continue to resume)
257 255
258 256 fix up
259 257 $ echo 'I can haz no commute' > e
260 258 $ hg resolve --mark e
261 259 (no more unresolved files)
262 260 continue: hg histedit --continue
263 261 $ hg histedit --continue 2>&1 | fixbundle | grep -v '2 files removed'
264 262 merging e
265 263 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
266 264 Fix up the change (pick 7f3755409b00)
267 265 (hg histedit --continue to resume)
268 266
269 267 just continue this time
270 268 $ hg revert -r 'p1()' e
271 269 $ hg resolve --mark e
272 270 (no more unresolved files)
273 271 continue: hg histedit --continue
274 272 $ hg histedit --continue 2>&1 | fixbundle
275 273 7f3755409b00: skipping changeset (no changes)
276 274
277 275 log after edit
278 276 $ hg log --graph
279 277 @ changeset: 5:b538bcb461be
280 278 | tag: tip
281 279 | user: test
282 280 | date: Thu Jan 01 00:00:06 1970 +0000
283 281 | summary: f
284 282 |
285 283 o changeset: 4:317e37cb6d66
286 284 | user: test
287 285 | date: Thu Jan 01 00:00:04 1970 +0000
288 286 | summary: d
289 287 |
290 288 o changeset: 3:092e4ce14829
291 289 | user: test
292 290 | date: Thu Jan 01 00:00:03 1970 +0000
293 291 | summary: c
294 292 |
295 293 o changeset: 2:40ccdd8beb95
296 294 | user: test
297 295 | date: Thu Jan 01 00:00:02 1970 +0000
298 296 | summary: b
299 297 |
300 298 o changeset: 1:cd997a145b29
301 299 | user: test
302 300 | date: Thu Jan 01 00:00:01 1970 +0000
303 301 | summary: a
304 302 |
305 303 o changeset: 0:1715188a53c7
306 304 user: test
307 305 date: Thu Jan 01 00:00:00 1970 +0000
308 306 summary: Initial commit
309 307
310 308
311 309 contents of e
312 310 $ hg cat e
313 311 I can haz no commute
314 312
315 313 manifest
316 314 $ hg manifest
317 315 a
318 316 b
319 317 c
320 318 d
321 319 e
322 320 f
323 321
324 322 description is taken from rollup target commit
325 323
326 324 $ hg log --debug --rev 4
327 325 changeset: 4:317e37cb6d66c1c84628c00e5bf4c8c292831951
328 326 phase: draft
329 327 parent: 3:092e4ce14829f4974399ce4316d59f64ef0b6725
330 328 parent: -1:0000000000000000000000000000000000000000
331 329 manifest: 4:b068a323d969f22af1296ec6a5ea9384cef437ac
332 330 user: test
333 331 date: Thu Jan 01 00:00:04 1970 +0000
334 332 files: d e
335 333 extra: branch=default
336 334 extra: histedit_source=ae78f4c9d74ffa4b6cb5045001c303fe9204e890,42abbb61bede6f4366fa1e74a664343e5d558a70
337 335 description:
338 336 d
339 337
340 338
341 339
342 340 done with repo r2
343 341
344 342 $ cd ..
@@ -1,707 +1,706 b''
1 1 Test histedit extension: Fold commands
2 2 ======================================
3 3
4 4 This test file is dedicated to testing the fold command in non conflicting
5 5 case.
6 6
7 7 Initialization
8 8 ---------------
9 9
10 10
11 11 $ . "$TESTDIR/histedit-helpers.sh"
12 12
13 13 $ cat >> $HGRCPATH <<EOF
14 14 > [alias]
15 15 > logt = log --template '{rev}:{node|short} {desc|firstline}\n'
16 16 > [extensions]
17 17 > histedit=
18 18 > mockmakedate = $TESTDIR/mockmakedate.py
19 19 > EOF
20 20
21 21
22 22 Simple folding
23 23 --------------------
24 24 $ addwithdate ()
25 25 > {
26 26 > echo $1 > $1
27 27 > hg add $1
28 28 > hg ci -m $1 -d "$2 0"
29 29 > }
30 30
31 31 $ initrepo ()
32 32 > {
33 33 > hg init r
34 34 > cd r
35 35 > addwithdate a 1
36 36 > addwithdate b 2
37 37 > addwithdate c 3
38 38 > addwithdate d 4
39 39 > addwithdate e 5
40 40 > addwithdate f 6
41 41 > }
42 42
43 43 $ initrepo
44 44
45 45 log before edit
46 46 $ hg logt --graph
47 47 @ 5:178e35e0ce73 f
48 48 |
49 49 o 4:1ddb6c90f2ee e
50 50 |
51 51 o 3:532247a8969b d
52 52 |
53 53 o 2:ff2c9fa2018b c
54 54 |
55 55 o 1:97d72e5f12c7 b
56 56 |
57 57 o 0:8580ff50825a a
58 58
59 59
60 60 $ hg histedit ff2c9fa2018b --commands - 2>&1 <<EOF | fixbundle
61 61 > pick 1ddb6c90f2ee e
62 62 > pick 178e35e0ce73 f
63 63 > fold ff2c9fa2018b c
64 64 > pick 532247a8969b d
65 65 > EOF
66 66
67 67 log after edit
68 68 $ hg logt --graph
69 69 @ 4:c4d7f3def76d d
70 70 |
71 71 o 3:575228819b7e f
72 72 |
73 73 o 2:505a591af19e e
74 74 |
75 75 o 1:97d72e5f12c7 b
76 76 |
77 77 o 0:8580ff50825a a
78 78
79 79
80 80 post-fold manifest
81 81 $ hg manifest
82 82 a
83 83 b
84 84 c
85 85 d
86 86 e
87 87 f
88 88
89 89
90 90 check histedit_source, including that it uses the later date, from the first changeset
91 91
92 92 $ hg log --debug --rev 3
93 93 changeset: 3:575228819b7e6ed69e8c0a6a383ee59a80db7358
94 94 phase: draft
95 95 parent: 2:505a591af19eed18f560af827b9e03d2076773dc
96 96 parent: -1:0000000000000000000000000000000000000000
97 97 manifest: 3:81eede616954057198ead0b2c73b41d1f392829a
98 98 user: test
99 99 date: Thu Jan 01 00:00:06 1970 +0000
100 100 files+: c f
101 101 extra: branch=default
102 102 extra: histedit_source=7cad1d7030207872dfd1c3a7cb430f24f2884086,ff2c9fa2018b15fa74b33363bda9527323e2a99f
103 103 description:
104 104 f
105 105 ***
106 106 c
107 107
108 108
109 109
110 110 rollup will fold without preserving the folded commit's message or date
111 111
112 112 $ OLDHGEDITOR=$HGEDITOR
113 113 $ HGEDITOR=false
114 114 $ hg histedit 97d72e5f12c7 --commands - 2>&1 <<EOF | fixbundle
115 115 > pick 97d72e5f12c7 b
116 116 > roll 505a591af19e e
117 117 > pick 575228819b7e f
118 118 > pick c4d7f3def76d d
119 119 > EOF
120 120
121 121 $ HGEDITOR=$OLDHGEDITOR
122 122
123 123 log after edit
124 124 $ hg logt --graph
125 125 @ 3:bab801520cec d
126 126 |
127 127 o 2:58c8f2bfc151 f
128 128 |
129 129 o 1:5d939c56c72e b
130 130 |
131 131 o 0:8580ff50825a a
132 132
133 133
134 134 description is taken from rollup target commit
135 135
136 136 $ hg log --debug --rev 1
137 137 changeset: 1:5d939c56c72e77e29f5167696218e2131a40f5cf
138 138 phase: draft
139 139 parent: 0:8580ff50825a50c8f716709acdf8de0deddcd6ab
140 140 parent: -1:0000000000000000000000000000000000000000
141 141 manifest: 1:b5e112a3a8354e269b1524729f0918662d847c38
142 142 user: test
143 143 date: Thu Jan 01 00:00:02 1970 +0000
144 144 files+: b e
145 145 extra: branch=default
146 146 extra: histedit_source=97d72e5f12c7e84f85064aa72e5a297142c36ed9,505a591af19eed18f560af827b9e03d2076773dc
147 147 description:
148 148 b
149 149
150 150
151 151
152 152 check saving last-message.txt
153 153
154 154 $ cat > $TESTTMP/abortfolding.py <<EOF
155 155 > from mercurial import util
156 156 > def abortfolding(ui, repo, hooktype, **kwargs):
157 157 > ctx = repo[kwargs.get('node')]
158 158 > if set(ctx.files()) == {b'c', b'd', b'f'}:
159 159 > return True # abort folding commit only
160 160 > ui.warn(b'allow non-folding commit\\n')
161 161 > EOF
162 162 $ cat > .hg/hgrc <<EOF
163 163 > [hooks]
164 164 > pretxncommit.abortfolding = python:$TESTTMP/abortfolding.py:abortfolding
165 165 > EOF
166 166
167 167 $ cat > $TESTTMP/editor.sh << EOF
168 168 > echo "==== before editing"
169 169 > cat \$1
170 170 > echo "===="
171 171 > echo "check saving last-message.txt" >> \$1
172 172 > EOF
173 173
174 174 $ rm -f .hg/last-message.txt
175 175 $ hg status --rev '58c8f2bfc151^1::bab801520cec'
176 176 A c
177 177 A d
178 178 A f
179 179 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit 58c8f2bfc151 --commands - 2>&1 <<EOF
180 180 > pick 58c8f2bfc151 f
181 181 > fold bab801520cec d
182 182 > EOF
183 183 allow non-folding commit
184 184 ==== before editing
185 185 f
186 186 ***
187 187 c
188 188 ***
189 189 d
190 190
191 191
192 192
193 193 HG: Enter commit message. Lines beginning with 'HG:' are removed.
194 194 HG: Leave message empty to abort commit.
195 195 HG: --
196 196 HG: user: test
197 197 HG: branch 'default'
198 198 HG: added c
199 199 HG: added d
200 200 HG: added f
201 201 ====
202 202 transaction abort!
203 203 rollback completed
204 204 abort: pretxncommit.abortfolding hook failed
205 205 [255]
206 206
207 207 $ cat .hg/last-message.txt
208 208 f
209 209 ***
210 210 c
211 211 ***
212 212 d
213 213
214 214
215 215
216 216 check saving last-message.txt
217 217
218 218 $ cd ..
219 219 $ rm -r r
220 220
221 221 folding preserves initial author but uses later date
222 222 ----------------------------------------------------
223 223
224 224 $ initrepo
225 225
226 226 $ hg ci -d '7 0' --user "someone else" --amend --quiet
227 227
228 228 tip before edit
229 229 $ hg log --rev .
230 230 changeset: 5:10c36dd37515
231 231 tag: tip
232 232 user: someone else
233 233 date: Thu Jan 01 00:00:07 1970 +0000
234 234 summary: f
235 235
236 236
237 237 $ hg --config progress.debug=1 --debug \
238 238 > histedit 1ddb6c90f2ee --commands - 2>&1 <<EOF | \
239 239 > egrep 'editing|unresolved'
240 240 > pick 1ddb6c90f2ee e
241 241 > fold 10c36dd37515 f
242 242 > EOF
243 243 editing: pick 1ddb6c90f2ee 4 e 1/2 changes (50.00%)
244 244 editing: fold 10c36dd37515 5 f 2/2 changes (100.00%)
245 245
246 246 tip after edit, which should use the later date, from the second changeset
247 247 $ hg log --rev .
248 248 changeset: 4:e4f3ec5d0b40
249 249 tag: tip
250 250 user: test
251 251 date: Thu Jan 01 00:00:07 1970 +0000
252 252 summary: e
253 253
254 254
255 255 $ cd ..
256 256 $ rm -r r
257 257
258 258 folding and creating no new change doesn't break:
259 259 -------------------------------------------------
260 260
261 261 folded content is dropped during a merge. The folded commit should properly disappear.
262 262
263 263 $ mkdir fold-to-empty-test
264 264 $ cd fold-to-empty-test
265 265 $ hg init
266 266 $ printf "1\n2\n3\n" > file
267 267 $ hg add file
268 268 $ hg commit -m '1+2+3'
269 269 $ echo 4 >> file
270 270 $ hg commit -m '+4'
271 271 $ echo 5 >> file
272 272 $ hg commit -m '+5'
273 273 $ echo 6 >> file
274 274 $ hg commit -m '+6'
275 275 $ hg logt --graph
276 276 @ 3:251d831eeec5 +6
277 277 |
278 278 o 2:888f9082bf99 +5
279 279 |
280 280 o 1:617f94f13c0f +4
281 281 |
282 282 o 0:0189ba417d34 1+2+3
283 283
284 284
285 285 $ hg histedit 1 --commands - << EOF
286 286 > pick 617f94f13c0f 1 +4
287 287 > drop 888f9082bf99 2 +5
288 288 > fold 251d831eeec5 3 +6
289 289 > EOF
290 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
291 290 merging file
292 291 warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
293 292 Fix up the change (fold 251d831eeec5)
294 293 (hg histedit --continue to resume)
295 294 [1]
296 295 There were conflicts, we keep P1 content. This
297 296 should effectively drop the changes from +6.
298 297
299 298 $ hg status -v
300 299 M file
301 300 ? file.orig
302 301 # The repository is in an unfinished *histedit* state.
303 302
304 303 # Unresolved merge conflicts:
305 304 #
306 305 # file
307 306 #
308 307 # To mark files as resolved: hg resolve --mark FILE
309 308
310 309 # To continue: hg histedit --continue
311 310 # To abort: hg histedit --abort
312 311
313 312 $ hg resolve -l
314 313 U file
315 314 $ hg revert -r 'p1()' file
316 315 $ hg resolve --mark file
317 316 (no more unresolved files)
318 317 continue: hg histedit --continue
319 318 $ hg histedit --continue
320 319 251d831eeec5: empty changeset
321 320 saved backup bundle to $TESTTMP/fold-to-empty-test/.hg/strip-backup/888f9082bf99-daa0b8b3-histedit.hg
322 321 $ hg logt --graph
323 322 @ 1:617f94f13c0f +4
324 323 |
325 324 o 0:0189ba417d34 1+2+3
326 325
327 326
328 327 $ cd ..
329 328
330 329
331 330 Test fold through dropped
332 331 -------------------------
333 332
334 333
335 334 Test corner case where folded revision is separated from its parent by a
336 335 dropped revision.
337 336
338 337
339 338 $ hg init fold-with-dropped
340 339 $ cd fold-with-dropped
341 340 $ printf "1\n2\n3\n" > file
342 341 $ hg commit -Am '1+2+3'
343 342 adding file
344 343 $ echo 4 >> file
345 344 $ hg commit -m '+4'
346 345 $ echo 5 >> file
347 346 $ hg commit -m '+5'
348 347 $ echo 6 >> file
349 348 $ hg commit -m '+6'
350 349 $ hg logt -G
351 350 @ 3:251d831eeec5 +6
352 351 |
353 352 o 2:888f9082bf99 +5
354 353 |
355 354 o 1:617f94f13c0f +4
356 355 |
357 356 o 0:0189ba417d34 1+2+3
358 357
359 358 $ hg histedit 1 --commands - << EOF
360 359 > pick 617f94f13c0f 1 +4
361 360 > drop 888f9082bf99 2 +5
362 361 > fold 251d831eeec5 3 +6
363 362 > EOF
364 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
365 363 merging file
366 364 warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
367 365 Fix up the change (fold 251d831eeec5)
368 366 (hg histedit --continue to resume)
369 367 [1]
370 368 $ cat > file << EOF
371 369 > 1
372 370 > 2
373 371 > 3
374 372 > 4
375 373 > 5
376 374 > EOF
377 375 $ hg resolve --mark file
378 376 (no more unresolved files)
379 377 continue: hg histedit --continue
380 378 $ hg commit -m '+5.2'
381 379 created new head
382 380 $ echo 6 >> file
383 381 $ HGEDITOR=cat hg histedit --continue
384 382 +4
385 383 ***
386 384 +5.2
387 385 ***
388 386 +6
389 387
390 388
391 389
392 390 HG: Enter commit message. Lines beginning with 'HG:' are removed.
393 391 HG: Leave message empty to abort commit.
394 392 HG: --
395 393 HG: user: test
396 394 HG: branch 'default'
397 395 HG: changed file
398 396 saved backup bundle to $TESTTMP/fold-with-dropped/.hg/strip-backup/617f94f13c0f-3d69522c-histedit.hg
399 397 $ hg logt -G
400 398 @ 1:10c647b2cdd5 +4
401 399 |
402 400 o 0:0189ba417d34 1+2+3
403 401
404 402 $ hg export tip
405 403 # HG changeset patch
406 404 # User test
407 405 # Date 0 0
408 406 # Thu Jan 01 00:00:00 1970 +0000
409 407 # Node ID 10c647b2cdd54db0603ecb99b2ff5ce66d5a5323
410 408 # Parent 0189ba417d34df9dda55f88b637dcae9917b5964
411 409 +4
412 410 ***
413 411 +5.2
414 412 ***
415 413 +6
416 414
417 415 diff -r 0189ba417d34 -r 10c647b2cdd5 file
418 416 --- a/file Thu Jan 01 00:00:00 1970 +0000
419 417 +++ b/file Thu Jan 01 00:00:00 1970 +0000
420 418 @@ -1,3 +1,6 @@
421 419 1
422 420 2
423 421 3
424 422 +4
425 423 +5
426 424 +6
427 425 $ cd ..
428 426
429 427
430 428 Folding with initial rename (issue3729)
431 429 ---------------------------------------
432 430
433 431 $ hg init fold-rename
434 432 $ cd fold-rename
435 433 $ echo a > a.txt
436 434 $ hg add a.txt
437 435 $ hg commit -m a
438 436 $ hg rename a.txt b.txt
439 437 $ hg commit -m rename
440 438 $ echo b >> b.txt
441 439 $ hg commit -m b
442 440
443 441 $ hg logt --follow b.txt
444 442 2:e0371e0426bc b
445 443 1:1c4f440a8085 rename
446 444 0:6c795aa153cb a
447 445
448 446 $ hg histedit 1c4f440a8085 --commands - 2>&1 << EOF | fixbundle
449 447 > pick 1c4f440a8085 rename
450 448 > fold e0371e0426bc b
451 449 > EOF
452 450
453 451 $ hg logt --follow b.txt
454 452 1:cf858d235c76 rename
455 453 0:6c795aa153cb a
456 454
457 455 $ cd ..
458 456
459 457 Folding with swapping
460 458 ---------------------
461 459
462 460 This is an excuse to test hook with histedit temporary commit (issue4422)
463 461
464 462
465 463 $ hg init issue4422
466 464 $ cd issue4422
467 465 $ echo a > a.txt
468 466 $ hg add a.txt
469 467 $ hg commit -m a
470 468 $ echo b > b.txt
471 469 $ hg add b.txt
472 470 $ hg commit -m b
473 471 $ echo c > c.txt
474 472 $ hg add c.txt
475 473 $ hg commit -m c
476 474
477 475 $ hg logt
478 476 2:a1a953ffb4b0 c
479 477 1:199b6bb90248 b
480 478 0:6c795aa153cb a
481 479
482 480 $ hg histedit 6c795aa153cb --config hooks.commit='echo commit $HG_NODE' --config hooks.tonative.commit=True \
483 481 > --commands - 2>&1 << EOF | fixbundle
484 482 > pick 199b6bb90248 b
485 483 > fold a1a953ffb4b0 c
486 484 > pick 6c795aa153cb a
487 485 > EOF
488 486 commit 9599899f62c05f4377548c32bf1c9f1a39634b0c
489 487
490 488 $ hg logt
491 489 1:9599899f62c0 a
492 490 0:79b99e9c8e49 b
493 491
494 492 Test unix -> windows style variable substitution in external hooks.
495 493
496 494 $ cat > $TESTTMP/tmp.hgrc <<'EOF'
497 495 > [hooks]
498 496 > pre-add = echo no variables
499 497 > post-add = echo ran $HG_ARGS, literal \$non-var, 'also $non-var', $HG_RESULT
500 498 > tonative.post-add = True
501 499 > EOF
502 500
503 501 $ echo "foo" > amended.txt
504 502 $ HGRCPATH=$TESTTMP/tmp.hgrc hg add -v amended.txt
505 503 running hook pre-add: echo no variables
506 504 no variables
507 505 adding amended.txt
508 506 converting hook "post-add" to native (windows !)
509 507 running hook post-add: echo ran %HG_ARGS%, literal $non-var, "also $non-var", %HG_RESULT% (windows !)
510 508 running hook post-add: echo ran $HG_ARGS, literal \$non-var, 'also $non-var', $HG_RESULT (no-windows !)
511 509 ran add -v amended.txt, literal $non-var, "also $non-var", 0 (windows !)
512 510 ran add -v amended.txt, literal $non-var, also $non-var, 0 (no-windows !)
513 511 $ hg ci -q --config extensions.largefiles= --amend -I amended.txt
514 512 The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !)
515 513
516 514 Test that folding multiple changes in a row doesn't show multiple
517 515 editors.
518 516
519 517 $ echo foo >> foo
520 518 $ hg add foo
521 519 $ hg ci -m foo1
522 520 $ echo foo >> foo
523 521 $ hg ci -m foo2
524 522 $ echo foo >> foo
525 523 $ hg ci -m foo3
526 524 $ hg logt
527 525 4:21679ff7675c foo3
528 526 3:b7389cc4d66e foo2
529 527 2:0e01aeef5fa8 foo1
530 528 1:578c7455730c a
531 529 0:79b99e9c8e49 b
532 530 $ cat > "$TESTTMP/editor.sh" <<EOF
533 531 > echo ran editor >> "$TESTTMP/editorlog.txt"
534 532 > cat \$1 >> "$TESTTMP/editorlog.txt"
535 533 > echo END >> "$TESTTMP/editorlog.txt"
536 534 > echo merged foos > \$1
537 535 > EOF
538 536 $ HGEDITOR="sh \"$TESTTMP/editor.sh\"" hg histedit 1 --commands - 2>&1 <<EOF | fixbundle
539 537 > pick 578c7455730c 1 a
540 538 > pick 0e01aeef5fa8 2 foo1
541 539 > fold b7389cc4d66e 3 foo2
542 540 > fold 21679ff7675c 4 foo3
543 541 > EOF
542 merging foo
544 543 $ hg logt
545 544 2:e8bedbda72c1 merged foos
546 545 1:578c7455730c a
547 546 0:79b99e9c8e49 b
548 547 Editor should have run only once
549 548 $ cat $TESTTMP/editorlog.txt
550 549 ran editor
551 550 foo1
552 551 ***
553 552 foo2
554 553 ***
555 554 foo3
556 555
557 556
558 557
559 558 HG: Enter commit message. Lines beginning with 'HG:' are removed.
560 559 HG: Leave message empty to abort commit.
561 560 HG: --
562 561 HG: user: test
563 562 HG: branch 'default'
564 563 HG: added foo
565 564 END
566 565
567 566 $ cd ..
568 567
569 568 Test rolling into a commit with multiple children (issue5498)
570 569
571 570 $ hg init roll
572 571 $ cd roll
573 572 $ echo a > a
574 573 $ hg commit -qAm aa
575 574 $ echo b > b
576 575 $ hg commit -qAm bb
577 576 $ hg up -q ".^"
578 577 $ echo c > c
579 578 $ hg commit -qAm cc
580 579 $ hg log -G -T '{node|short} {desc}'
581 580 @ 5db65b93a12b cc
582 581 |
583 582 | o 301d76bdc3ae bb
584 583 |/
585 584 o 8f0162e483d0 aa
586 585
587 586
588 587 $ hg histedit . --commands - << EOF
589 588 > r 5db65b93a12b
590 589 > EOF
591 590 hg: parse error: first changeset cannot use verb "roll"
592 591 [255]
593 592 $ hg log -G -T '{node|short} {desc}'
594 593 @ 5db65b93a12b cc
595 594 |
596 595 | o 301d76bdc3ae bb
597 596 |/
598 597 o 8f0162e483d0 aa
599 598
600 599
601 600 $ cd ..
602 601
603 602 ====================================
604 603 Test update-timestamp config option|
605 604 ====================================
606 605
607 606 $ addwithdate ()
608 607 > {
609 608 > echo $1 > $1
610 609 > hg add $1
611 610 > hg ci -m $1 -d "$2 0"
612 611 > }
613 612
614 613 $ initrepo ()
615 614 > {
616 615 > hg init r
617 616 > cd r
618 617 > addwithdate a 1
619 618 > addwithdate b 2
620 619 > addwithdate c 3
621 620 > addwithdate d 4
622 621 > addwithdate e 5
623 622 > addwithdate f 6
624 623 > }
625 624
626 625 $ initrepo
627 626
628 627 log before edit
629 628
630 629 $ hg log
631 630 changeset: 5:178e35e0ce73
632 631 tag: tip
633 632 user: test
634 633 date: Thu Jan 01 00:00:06 1970 +0000
635 634 summary: f
636 635
637 636 changeset: 4:1ddb6c90f2ee
638 637 user: test
639 638 date: Thu Jan 01 00:00:05 1970 +0000
640 639 summary: e
641 640
642 641 changeset: 3:532247a8969b
643 642 user: test
644 643 date: Thu Jan 01 00:00:04 1970 +0000
645 644 summary: d
646 645
647 646 changeset: 2:ff2c9fa2018b
648 647 user: test
649 648 date: Thu Jan 01 00:00:03 1970 +0000
650 649 summary: c
651 650
652 651 changeset: 1:97d72e5f12c7
653 652 user: test
654 653 date: Thu Jan 01 00:00:02 1970 +0000
655 654 summary: b
656 655
657 656 changeset: 0:8580ff50825a
658 657 user: test
659 658 date: Thu Jan 01 00:00:01 1970 +0000
660 659 summary: a
661 660
662 661
663 662 $ hg histedit 1ddb6c90f2ee --commands - 2>&1 --config rewrite.update-timestamp=True <<EOF | fixbundle
664 663 > pick 178e35e0ce73 f
665 664 > fold 1ddb6c90f2ee e
666 665 > EOF
667 666
668 667 log after edit
669 668 observe time from f is updated
670 669
671 670 $ hg log
672 671 changeset: 4:f7909b1863a2
673 672 tag: tip
674 673 user: test
675 674 date: Thu Jan 01 00:00:01 1970 +0000
676 675 summary: f
677 676
678 677 changeset: 3:532247a8969b
679 678 user: test
680 679 date: Thu Jan 01 00:00:04 1970 +0000
681 680 summary: d
682 681
683 682 changeset: 2:ff2c9fa2018b
684 683 user: test
685 684 date: Thu Jan 01 00:00:03 1970 +0000
686 685 summary: c
687 686
688 687 changeset: 1:97d72e5f12c7
689 688 user: test
690 689 date: Thu Jan 01 00:00:02 1970 +0000
691 690 summary: b
692 691
693 692 changeset: 0:8580ff50825a
694 693 user: test
695 694 date: Thu Jan 01 00:00:01 1970 +0000
696 695 summary: a
697 696
698 697 post-fold manifest
699 698 $ hg manifest
700 699 a
701 700 b
702 701 c
703 702 d
704 703 e
705 704 f
706 705
707 706 $ cd ..
@@ -1,306 +1,303 b''
1 1 $ . "$TESTDIR/histedit-helpers.sh"
2 2
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [extensions]
5 5 > histedit=
6 6 > EOF
7 7
8 8 $ initrepo ()
9 9 > {
10 10 > hg init $1
11 11 > cd $1
12 12 > for x in a b c d e f ; do
13 13 > echo $x$x$x$x$x > $x
14 14 > hg add $x
15 15 > done
16 16 > hg ci -m 'Initial commit'
17 17 > for x in a b c d e f ; do
18 18 > echo $x > $x
19 19 > hg ci -m $x
20 20 > done
21 21 > echo 'I can haz no commute' > e
22 22 > hg ci -m 'does not commute with e'
23 23 > cd ..
24 24 > }
25 25
26 26 $ initrepo r1
27 27 $ cd r1
28 28
29 29 Initial generation of the command files
30 30
31 31 $ EDITED="$TESTTMP/editedhistory"
32 32 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 3 >> $EDITED
33 33 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 4 >> $EDITED
34 34 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 7 >> $EDITED
35 35 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 5 >> $EDITED
36 36 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 6 >> $EDITED
37 37 $ cat $EDITED
38 38 pick 65a9a84f33fd 3 c
39 39 pick 00f1c5383965 4 d
40 40 pick 39522b764e3d 7 does not commute with e
41 41 pick 7b4e2f4b7bcd 5 e
42 42 pick 500cac37a696 6 f
43 43
44 44 log before edit
45 45 $ hg log --graph
46 46 @ changeset: 7:39522b764e3d
47 47 | tag: tip
48 48 | user: test
49 49 | date: Thu Jan 01 00:00:00 1970 +0000
50 50 | summary: does not commute with e
51 51 |
52 52 o changeset: 6:500cac37a696
53 53 | user: test
54 54 | date: Thu Jan 01 00:00:00 1970 +0000
55 55 | summary: f
56 56 |
57 57 o changeset: 5:7b4e2f4b7bcd
58 58 | user: test
59 59 | date: Thu Jan 01 00:00:00 1970 +0000
60 60 | summary: e
61 61 |
62 62 o changeset: 4:00f1c5383965
63 63 | user: test
64 64 | date: Thu Jan 01 00:00:00 1970 +0000
65 65 | summary: d
66 66 |
67 67 o changeset: 3:65a9a84f33fd
68 68 | user: test
69 69 | date: Thu Jan 01 00:00:00 1970 +0000
70 70 | summary: c
71 71 |
72 72 o changeset: 2:da6535b52e45
73 73 | user: test
74 74 | date: Thu Jan 01 00:00:00 1970 +0000
75 75 | summary: b
76 76 |
77 77 o changeset: 1:c1f09da44841
78 78 | user: test
79 79 | date: Thu Jan 01 00:00:00 1970 +0000
80 80 | summary: a
81 81 |
82 82 o changeset: 0:1715188a53c7
83 83 user: test
84 84 date: Thu Jan 01 00:00:00 1970 +0000
85 85 summary: Initial commit
86 86
87 87
88 88 edit the history
89 89 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
90 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 90 merging e
92 91 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
93 92 Fix up the change (pick 39522b764e3d)
94 93 (hg histedit --continue to resume)
95 94
96 95 abort the edit
97 96 $ hg histedit --abort 2>&1 | fixbundle
98 97 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
99 98
100 99
101 100 second edit set
102 101
103 102 $ hg log --graph
104 103 @ changeset: 7:39522b764e3d
105 104 | tag: tip
106 105 | user: test
107 106 | date: Thu Jan 01 00:00:00 1970 +0000
108 107 | summary: does not commute with e
109 108 |
110 109 o changeset: 6:500cac37a696
111 110 | user: test
112 111 | date: Thu Jan 01 00:00:00 1970 +0000
113 112 | summary: f
114 113 |
115 114 o changeset: 5:7b4e2f4b7bcd
116 115 | user: test
117 116 | date: Thu Jan 01 00:00:00 1970 +0000
118 117 | summary: e
119 118 |
120 119 o changeset: 4:00f1c5383965
121 120 | user: test
122 121 | date: Thu Jan 01 00:00:00 1970 +0000
123 122 | summary: d
124 123 |
125 124 o changeset: 3:65a9a84f33fd
126 125 | user: test
127 126 | date: Thu Jan 01 00:00:00 1970 +0000
128 127 | summary: c
129 128 |
130 129 o changeset: 2:da6535b52e45
131 130 | user: test
132 131 | date: Thu Jan 01 00:00:00 1970 +0000
133 132 | summary: b
134 133 |
135 134 o changeset: 1:c1f09da44841
136 135 | user: test
137 136 | date: Thu Jan 01 00:00:00 1970 +0000
138 137 | summary: a
139 138 |
140 139 o changeset: 0:1715188a53c7
141 140 user: test
142 141 date: Thu Jan 01 00:00:00 1970 +0000
143 142 summary: Initial commit
144 143
145 144
146 145 edit the history
147 146 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
148 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 147 merging e
150 148 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
151 149 Fix up the change (pick 39522b764e3d)
152 150 (hg histedit --continue to resume)
153 151
154 152 fix up
155 153 $ echo 'I can haz no commute' > e
156 154 $ hg resolve --mark e
157 155 (no more unresolved files)
158 156 continue: hg histedit --continue
159 157 $ hg histedit --continue 2>&1 | fixbundle
160 158 merging e
161 159 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
162 160 Fix up the change (pick 7b4e2f4b7bcd)
163 161 (hg histedit --continue to resume)
164 162 We forcibly enable curses here so we can verify that continuing works
165 163 with curses enabled.
166 164 $ hg histedit --continue --config ui.interactive=true \
167 165 > --config ui.interface=curses 2>&1 | fixbundle
168 166 abort: unresolved merge conflicts (see 'hg help resolve')
169 167
170 168 This failure is caused by 7b4e2f4b7bcd "e" not rebasing the non commutative
171 169 former children.
172 170
173 171 just continue this time
174 172 $ hg revert -r 'p1()' e
175 173 make sure the to-be-empty commit doesn't screw up the state (issue5545)
176 174 $ hg histedit --continue 2>&1 | fixbundle
177 175 abort: unresolved merge conflicts (see 'hg help resolve')
178 176 $ hg resolve --mark e
179 177 (no more unresolved files)
180 178 continue: hg histedit --continue
181 179 $ hg histedit --continue 2>&1 | fixbundle
182 180 7b4e2f4b7bcd: skipping changeset (no changes)
183 181
184 182 log after edit
185 183 $ hg log --graph
186 184 @ changeset: 6:7efe1373e4bc
187 185 | tag: tip
188 186 | user: test
189 187 | date: Thu Jan 01 00:00:00 1970 +0000
190 188 | summary: f
191 189 |
192 190 o changeset: 5:e334d87a1e55
193 191 | user: test
194 192 | date: Thu Jan 01 00:00:00 1970 +0000
195 193 | summary: does not commute with e
196 194 |
197 195 o changeset: 4:00f1c5383965
198 196 | user: test
199 197 | date: Thu Jan 01 00:00:00 1970 +0000
200 198 | summary: d
201 199 |
202 200 o changeset: 3:65a9a84f33fd
203 201 | user: test
204 202 | date: Thu Jan 01 00:00:00 1970 +0000
205 203 | summary: c
206 204 |
207 205 o changeset: 2:da6535b52e45
208 206 | user: test
209 207 | date: Thu Jan 01 00:00:00 1970 +0000
210 208 | summary: b
211 209 |
212 210 o changeset: 1:c1f09da44841
213 211 | user: test
214 212 | date: Thu Jan 01 00:00:00 1970 +0000
215 213 | summary: a
216 214 |
217 215 o changeset: 0:1715188a53c7
218 216 user: test
219 217 date: Thu Jan 01 00:00:00 1970 +0000
220 218 summary: Initial commit
221 219
222 220
223 221 start over
224 222
225 223 $ cd ..
226 224
227 225 $ initrepo r2
228 226 $ cd r2
229 227 $ rm $EDITED
230 228 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 3 >> $EDITED
231 229 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 4 >> $EDITED
232 230 $ hg log --template 'mess {node|short} {rev} {desc}\n' -r 7 >> $EDITED
233 231 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 5 >> $EDITED
234 232 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 6 >> $EDITED
235 233 $ cat $EDITED
236 234 pick 65a9a84f33fd 3 c
237 235 pick 00f1c5383965 4 d
238 236 mess 39522b764e3d 7 does not commute with e
239 237 pick 7b4e2f4b7bcd 5 e
240 238 pick 500cac37a696 6 f
241 239
242 240 edit the history, this time with a fold action
243 241 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
244 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
245 242 merging e
246 243 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
247 244 Fix up the change (mess 39522b764e3d)
248 245 (hg histedit --continue to resume)
249 246
250 247 $ echo 'I can haz no commute' > e
251 248 $ hg resolve --mark e
252 249 (no more unresolved files)
253 250 continue: hg histedit --continue
254 251 $ hg histedit --continue 2>&1 | fixbundle
255 252 merging e
256 253 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
257 254 Fix up the change (pick 7b4e2f4b7bcd)
258 255 (hg histedit --continue to resume)
259 256 second edit also fails, but just continue
260 257 $ hg revert -r 'p1()' e
261 258 $ hg resolve --mark e
262 259 (no more unresolved files)
263 260 continue: hg histedit --continue
264 261 $ hg histedit --continue 2>&1 | fixbundle
265 262 7b4e2f4b7bcd: skipping changeset (no changes)
266 263
267 264 post message fix
268 265 $ hg log --graph
269 266 @ changeset: 6:7efe1373e4bc
270 267 | tag: tip
271 268 | user: test
272 269 | date: Thu Jan 01 00:00:00 1970 +0000
273 270 | summary: f
274 271 |
275 272 o changeset: 5:e334d87a1e55
276 273 | user: test
277 274 | date: Thu Jan 01 00:00:00 1970 +0000
278 275 | summary: does not commute with e
279 276 |
280 277 o changeset: 4:00f1c5383965
281 278 | user: test
282 279 | date: Thu Jan 01 00:00:00 1970 +0000
283 280 | summary: d
284 281 |
285 282 o changeset: 3:65a9a84f33fd
286 283 | user: test
287 284 | date: Thu Jan 01 00:00:00 1970 +0000
288 285 | summary: c
289 286 |
290 287 o changeset: 2:da6535b52e45
291 288 | user: test
292 289 | date: Thu Jan 01 00:00:00 1970 +0000
293 290 | summary: b
294 291 |
295 292 o changeset: 1:c1f09da44841
296 293 | user: test
297 294 | date: Thu Jan 01 00:00:00 1970 +0000
298 295 | summary: a
299 296 |
300 297 o changeset: 0:1715188a53c7
301 298 user: test
302 299 date: Thu Jan 01 00:00:00 1970 +0000
303 300 summary: Initial commit
304 301
305 302
306 303 $ cd ..
@@ -1,579 +1,576 b''
1 1 $ . "$TESTDIR/histedit-helpers.sh"
2 2
3 3 Enable obsolete
4 4
5 5 $ cat >> $HGRCPATH << EOF
6 6 > [ui]
7 7 > logtemplate= {rev}:{node|short} {desc|firstline}
8 8 > [phases]
9 9 > publish=False
10 10 > [experimental]
11 11 > evolution.createmarkers=True
12 12 > evolution.allowunstable=True
13 13 > [extensions]
14 14 > histedit=
15 15 > rebase=
16 16 > EOF
17 17
18 18 Test that histedit learns about obsolescence not stored in histedit state
19 19 $ hg init boo
20 20 $ cd boo
21 21 $ echo a > a
22 22 $ hg ci -Am a
23 23 adding a
24 24 $ echo a > b
25 25 $ echo a > c
26 26 $ echo a > c
27 27 $ hg ci -Am b
28 28 adding b
29 29 adding c
30 30 $ echo a > d
31 31 $ hg ci -Am c
32 32 adding d
33 33 $ echo "pick `hg log -r 0 -T '{node|short}'`" > plan
34 34 $ echo "pick `hg log -r 2 -T '{node|short}'`" >> plan
35 35 $ echo "edit `hg log -r 1 -T '{node|short}'`" >> plan
36 36 $ hg histedit -r 'all()' --commands plan
37 37 Editing (1b2d564fad96), you may commit or record as needed now.
38 38 (hg histedit --continue to resume)
39 39 [1]
40 40 $ hg st
41 41 A b
42 42 A c
43 43 ? plan
44 44 $ hg commit --amend b
45 45 $ hg histedit --continue
46 46 $ hg log -G
47 47 @ 5:46abc7c4d873 b
48 48 |
49 49 o 4:49d44ab2be1b c
50 50 |
51 51 o 0:cb9a9f314b8b a
52 52
53 53 $ hg debugobsolete
54 54 e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
55 55 1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'histedit', 'user': 'test'}
56 56 114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'histedit', 'user': 'test'}
57 57
58 58 With some node gone missing during the edit.
59 59
60 60 $ echo "pick `hg log -r 0 -T '{node|short}'`" > plan
61 61 $ echo "pick `hg log -r 5 -T '{node|short}'`" >> plan
62 62 $ echo "edit `hg log -r 4 -T '{node|short}'`" >> plan
63 63 $ hg histedit -r 'all()' --commands plan
64 64 Editing (49d44ab2be1b), you may commit or record as needed now.
65 65 (hg histedit --continue to resume)
66 66 [1]
67 67 $ hg st
68 68 A b
69 69 A d
70 70 ? plan
71 71 $ hg commit --amend -X . -m XXXXXX
72 72 $ hg commit --amend -X . -m b2
73 73 $ hg --hidden --config extensions.strip= strip 'desc(XXXXXX)' --no-backup
74 74 $ hg histedit --continue
75 75 $ hg log -G
76 76 @ 8:273c1f3b8626 c
77 77 |
78 78 o 7:aba7da937030 b2
79 79 |
80 80 o 0:cb9a9f314b8b a
81 81
82 82 $ hg debugobsolete
83 83 e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
84 84 1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'histedit', 'user': 'test'}
85 85 114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'histedit', 'user': 'test'}
86 86 76f72745eac0643d16530e56e2f86e36e40631f1 2ca853e48edbd6453a0674dc0fe28a0974c51b9c 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
87 87 2ca853e48edbd6453a0674dc0fe28a0974c51b9c aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
88 88 49d44ab2be1b67a79127568a67c9c99430633b48 273c1f3b86267ed3ec684bb13af1fa4d6ba56e02 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'histedit', 'user': 'test'}
89 89 46abc7c4d8738e8563e577f7889e1b6db3da4199 aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '5', 'operation': 'histedit', 'user': 'test'}
90 90 $ cd ..
91 91
92 92 Base setup for the rest of the testing
93 93 ======================================
94 94
95 95 $ hg init base
96 96 $ cd base
97 97
98 98 $ for x in a b c d e f ; do
99 99 > echo $x > $x
100 100 > hg add $x
101 101 > hg ci -m $x
102 102 > done
103 103
104 104 $ hg log --graph
105 105 @ 5:652413bf663e f
106 106 |
107 107 o 4:e860deea161a e
108 108 |
109 109 o 3:055a42cdd887 d
110 110 |
111 111 o 2:177f92b77385 c
112 112 |
113 113 o 1:d2ae7f538514 b
114 114 |
115 115 o 0:cb9a9f314b8b a
116 116
117 117
118 118 $ HGEDITOR=cat hg histedit 1
119 119 pick d2ae7f538514 1 b
120 120 pick 177f92b77385 2 c
121 121 pick 055a42cdd887 3 d
122 122 pick e860deea161a 4 e
123 123 pick 652413bf663e 5 f
124 124
125 125 # Edit history between d2ae7f538514 and 652413bf663e
126 126 #
127 127 # Commits are listed from least to most recent
128 128 #
129 129 # You can reorder changesets by reordering the lines
130 130 #
131 131 # Commands:
132 132 #
133 133 # e, edit = use commit, but stop for amending
134 134 # m, mess = edit commit message without changing commit content
135 135 # p, pick = use commit
136 136 # b, base = checkout changeset and apply further changesets from there
137 137 # d, drop = remove commit from history
138 138 # f, fold = use commit, but combine it with the one above
139 139 # r, roll = like fold, but discard this commit's description and date
140 140 #
141 141 $ hg histedit 1 --commands - --verbose <<EOF | grep histedit
142 142 > pick 177f92b77385 2 c
143 143 > drop d2ae7f538514 1 b
144 144 > pick 055a42cdd887 3 d
145 145 > fold e860deea161a 4 e
146 146 > pick 652413bf663e 5 f
147 147 > EOF
148 148 [1]
149 149 $ hg log --graph --hidden
150 150 @ 10:cacdfd884a93 f
151 151 |
152 152 o 9:59d9f330561f d
153 153 |
154 154 | x 8:b558abc46d09 fold-temp-revision e860deea161a
155 155 | |
156 156 | x 7:96e494a2d553 d
157 157 |/
158 158 o 6:b346ab9a313d c
159 159 |
160 160 | x 5:652413bf663e f
161 161 | |
162 162 | x 4:e860deea161a e
163 163 | |
164 164 | x 3:055a42cdd887 d
165 165 | |
166 166 | x 2:177f92b77385 c
167 167 | |
168 168 | x 1:d2ae7f538514 b
169 169 |/
170 170 o 0:cb9a9f314b8b a
171 171
172 172 $ hg debugobsolete
173 173 d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'histedit', 'user': 'test'}
174 174 177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'histedit', 'user': 'test'}
175 175 055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'histedit', 'user': 'test'}
176 176 e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'histedit', 'user': 'test'}
177 177 652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'histedit', 'user': 'test'}
178 178 96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'histedit', 'user': 'test'}
179 179 b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'histedit', 'user': 'test'}
180 180
181 181
182 182 Ensure hidden revision does not prevent histedit
183 183 -------------------------------------------------
184 184
185 185 create an hidden revision
186 186
187 187 $ hg histedit 6 --commands - << EOF
188 188 > pick b346ab9a313d 6 c
189 189 > drop 59d9f330561f 7 d
190 190 > pick cacdfd884a93 8 f
191 191 > EOF
192 192 $ hg log --graph
193 193 @ 11:c13eb81022ca f
194 194 |
195 195 o 6:b346ab9a313d c
196 196 |
197 197 o 0:cb9a9f314b8b a
198 198
199 199 check hidden revision are ignored (6 have hidden children 7 and 8)
200 200
201 201 $ hg histedit 6 --commands - << EOF
202 202 > pick b346ab9a313d 6 c
203 203 > pick c13eb81022ca 8 f
204 204 > EOF
205 205
206 206
207 207
208 208 Test that rewriting leaving instability behind is allowed
209 209 ---------------------------------------------------------------------
210 210
211 211 $ hg up '.^'
212 212 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
213 213 $ hg log -r 'children(.)'
214 214 11:c13eb81022ca f (no-eol)
215 215 $ hg histedit -r '.' --commands - <<EOF
216 216 > edit b346ab9a313d 6 c
217 217 > EOF
218 218 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
219 adding c
220 219 Editing (b346ab9a313d), you may commit or record as needed now.
221 220 (hg histedit --continue to resume)
222 221 [1]
223 222 $ echo c >> c
224 223 $ hg histedit --continue
225 224 1 new orphan changesets
226 225
227 226 $ hg log -r 'orphan()'
228 227 11:c13eb81022ca f (no-eol)
229 228
230 229 stabilise
231 230
232 231 $ hg rebase -r 'orphan()' -d .
233 232 rebasing 11:c13eb81022ca "f"
234 233 $ hg up tip -q
235 234
236 235 Test dropping of changeset on the top of the stack
237 236 -------------------------------------------------------
238 237
239 238 Nothing is rewritten below, the working directory parent must be change for the
240 239 dropped changeset to be hidden.
241 240
242 241 $ cd ..
243 242 $ hg clone base droplast
244 243 updating to branch default
245 244 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
246 245 $ cd droplast
247 246 $ hg histedit -r '40db8afa467b' --commands - << EOF
248 247 > pick 40db8afa467b 10 c
249 248 > drop b449568bf7fc 11 f
250 249 > EOF
251 250 $ hg log -G
252 251 @ 12:40db8afa467b c
253 252 |
254 253 o 0:cb9a9f314b8b a
255 254
256 255
257 256 With rewritten ancestors
258 257
259 258 $ echo e > e
260 259 $ hg add e
261 260 $ hg commit -m g
262 261 $ echo f > f
263 262 $ hg add f
264 263 $ hg commit -m h
265 264 $ hg histedit -r '40db8afa467b' --commands - << EOF
266 265 > pick 47a8561c0449 12 g
267 266 > pick 40db8afa467b 10 c
268 267 > drop 1b3b05f35ff0 13 h
269 268 > EOF
270 269 $ hg log -G
271 270 @ 17:ee6544123ab8 c
272 271 |
273 272 o 16:269e713e9eae g
274 273 |
275 274 o 0:cb9a9f314b8b a
276 275
277 276 $ cd ../base
278 277
279 278
280 279
281 280 Test phases support
282 281 ===========================================
283 282
284 283 Check that histedit respect immutability
285 284 -------------------------------------------
286 285
287 286 $ cat >> $HGRCPATH << EOF
288 287 > [ui]
289 288 > logtemplate= {rev}:{node|short} ({phase}) {desc|firstline}\n
290 289 > EOF
291 290
292 291 $ hg ph -pv '.^'
293 292 phase changed for 2 changesets
294 293 $ hg log -G
295 294 @ 13:b449568bf7fc (draft) f
296 295 |
297 296 o 12:40db8afa467b (public) c
298 297 |
299 298 o 0:cb9a9f314b8b (public) a
300 299
301 300 $ hg histedit -r '.~2'
302 301 abort: cannot edit public changeset: cb9a9f314b8b
303 302 (see 'hg help phases' for details)
304 303 [255]
305 304
306 305
307 306 Prepare further testing
308 307 -------------------------------------------
309 308
310 309 $ for x in g h i j k ; do
311 310 > echo $x > $x
312 311 > hg add $x
313 312 > hg ci -m $x
314 313 > done
315 314 $ hg phase --force --secret .~2
316 315 $ hg log -G
317 316 @ 18:ee118ab9fa44 (secret) k
318 317 |
319 318 o 17:3a6c53ee7f3d (secret) j
320 319 |
321 320 o 16:b605fb7503f2 (secret) i
322 321 |
323 322 o 15:7395e1ff83bd (draft) h
324 323 |
325 324 o 14:6b70183d2492 (draft) g
326 325 |
327 326 o 13:b449568bf7fc (draft) f
328 327 |
329 328 o 12:40db8afa467b (public) c
330 329 |
331 330 o 0:cb9a9f314b8b (public) a
332 331
333 332 $ cd ..
334 333
335 334 simple phase conservation
336 335 -------------------------------------------
337 336
338 337 Resulting changeset should conserve the phase of the original one whatever the
339 338 phases.new-commit option is.
340 339
341 340 New-commit as draft (default)
342 341
343 342 $ cp -R base simple-draft
344 343 $ cd simple-draft
345 344 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
346 345 > edit b449568bf7fc 11 f
347 346 > pick 6b70183d2492 12 g
348 347 > pick 7395e1ff83bd 13 h
349 348 > pick b605fb7503f2 14 i
350 349 > pick 3a6c53ee7f3d 15 j
351 350 > pick ee118ab9fa44 16 k
352 351 > EOF
353 352 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
354 adding f
355 353 Editing (b449568bf7fc), you may commit or record as needed now.
356 354 (hg histedit --continue to resume)
357 355 [1]
358 356 $ echo f >> f
359 357 $ hg histedit --continue
360 358 $ hg log -G
361 359 @ 24:12e89af74238 (secret) k
362 360 |
363 361 o 23:636a8687b22e (secret) j
364 362 |
365 363 o 22:ccaf0a38653f (secret) i
366 364 |
367 365 o 21:11a89d1c2613 (draft) h
368 366 |
369 367 o 20:c1dec7ca82ea (draft) g
370 368 |
371 369 o 19:087281e68428 (draft) f
372 370 |
373 371 o 12:40db8afa467b (public) c
374 372 |
375 373 o 0:cb9a9f314b8b (public) a
376 374
377 375 $ cd ..
378 376
379 377
380 378 New-commit as secret (config)
381 379
382 380 $ cp -R base simple-secret
383 381 $ cd simple-secret
384 382 $ cat >> .hg/hgrc << EOF
385 383 > [phases]
386 384 > new-commit=secret
387 385 > EOF
388 386 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
389 387 > edit b449568bf7fc 11 f
390 388 > pick 6b70183d2492 12 g
391 389 > pick 7395e1ff83bd 13 h
392 390 > pick b605fb7503f2 14 i
393 391 > pick 3a6c53ee7f3d 15 j
394 392 > pick ee118ab9fa44 16 k
395 393 > EOF
396 394 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
397 adding f
398 395 Editing (b449568bf7fc), you may commit or record as needed now.
399 396 (hg histedit --continue to resume)
400 397 [1]
401 398 $ echo f >> f
402 399 $ hg histedit --continue
403 400 $ hg log -G
404 401 @ 24:12e89af74238 (secret) k
405 402 |
406 403 o 23:636a8687b22e (secret) j
407 404 |
408 405 o 22:ccaf0a38653f (secret) i
409 406 |
410 407 o 21:11a89d1c2613 (draft) h
411 408 |
412 409 o 20:c1dec7ca82ea (draft) g
413 410 |
414 411 o 19:087281e68428 (draft) f
415 412 |
416 413 o 12:40db8afa467b (public) c
417 414 |
418 415 o 0:cb9a9f314b8b (public) a
419 416
420 417 $ cd ..
421 418
422 419
423 420 Changeset reordering
424 421 -------------------------------------------
425 422
426 423 If a secret changeset is put before a draft one, all descendant should be secret.
427 424 It seems more important to present the secret phase.
428 425
429 426 $ cp -R base reorder
430 427 $ cd reorder
431 428 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
432 429 > pick b449568bf7fc 11 f
433 430 > pick 3a6c53ee7f3d 15 j
434 431 > pick 6b70183d2492 12 g
435 432 > pick b605fb7503f2 14 i
436 433 > pick 7395e1ff83bd 13 h
437 434 > pick ee118ab9fa44 16 k
438 435 > EOF
439 436 $ hg log -G
440 437 @ 23:558246857888 (secret) k
441 438 |
442 439 o 22:28bd44768535 (secret) h
443 440 |
444 441 o 21:d5395202aeb9 (secret) i
445 442 |
446 443 o 20:21edda8e341b (secret) g
447 444 |
448 445 o 19:5ab64f3a4832 (secret) j
449 446 |
450 447 o 13:b449568bf7fc (draft) f
451 448 |
452 449 o 12:40db8afa467b (public) c
453 450 |
454 451 o 0:cb9a9f314b8b (public) a
455 452
456 453 $ cd ..
457 454
458 455 Changeset folding
459 456 -------------------------------------------
460 457
461 458 Folding a secret changeset with a draft one turn the result secret (again,
462 459 better safe than sorry). Folding between same phase changeset still works
463 460
464 461 Note that there is a few reordering in this series for more extensive test
465 462
466 463 $ cp -R base folding
467 464 $ cd folding
468 465 $ cat >> .hg/hgrc << EOF
469 466 > [phases]
470 467 > new-commit=secret
471 468 > EOF
472 469 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
473 470 > pick 7395e1ff83bd 13 h
474 471 > fold b449568bf7fc 11 f
475 472 > pick 6b70183d2492 12 g
476 473 > fold 3a6c53ee7f3d 15 j
477 474 > pick b605fb7503f2 14 i
478 475 > fold ee118ab9fa44 16 k
479 476 > EOF
480 477 $ hg log -G
481 478 @ 27:f9daec13fb98 (secret) i
482 479 |
483 480 o 24:49807617f46a (secret) g
484 481 |
485 482 o 21:050280826e04 (draft) h
486 483 |
487 484 o 12:40db8afa467b (public) c
488 485 |
489 486 o 0:cb9a9f314b8b (public) a
490 487
491 488 $ hg co 49807617f46a
492 489 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
493 490 $ echo wat >> wat
494 491 $ hg add wat
495 492 $ hg ci -m 'add wat'
496 493 created new head
497 494 $ hg merge f9daec13fb98
498 495 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
499 496 (branch merge, don't forget to commit)
500 497 $ hg ci -m 'merge'
501 498 $ echo not wat > wat
502 499 $ hg ci -m 'modify wat'
503 500 $ hg histedit 050280826e04
504 501 abort: cannot edit history that contains merges
505 502 [255]
506 503 $ cd ..
507 504
508 505 Check abort behavior
509 506 -------------------------------------------
510 507
511 508 We checks that abort properly clean the repository so the same histedit can be
512 509 attempted later.
513 510
514 511 $ cp -R base abort
515 512 $ cd abort
516 513 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
517 514 > pick b449568bf7fc 13 f
518 515 > pick 7395e1ff83bd 15 h
519 516 > pick 6b70183d2492 14 g
520 517 > pick b605fb7503f2 16 i
521 518 > roll 3a6c53ee7f3d 17 j
522 519 > edit ee118ab9fa44 18 k
523 520 > EOF
524 521 Editing (ee118ab9fa44), you may commit or record as needed now.
525 522 (hg histedit --continue to resume)
526 523 [1]
527 524
528 525 $ hg histedit --abort
529 526 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
530 527 saved backup bundle to $TESTTMP/abort/.hg/strip-backup/4dc06258baa6-dff4ef05-backup.hg
531 528
532 529 $ hg log -G
533 530 @ 18:ee118ab9fa44 (secret) k
534 531 |
535 532 o 17:3a6c53ee7f3d (secret) j
536 533 |
537 534 o 16:b605fb7503f2 (secret) i
538 535 |
539 536 o 15:7395e1ff83bd (draft) h
540 537 |
541 538 o 14:6b70183d2492 (draft) g
542 539 |
543 540 o 13:b449568bf7fc (draft) f
544 541 |
545 542 o 12:40db8afa467b (public) c
546 543 |
547 544 o 0:cb9a9f314b8b (public) a
548 545
549 546 $ hg histedit -r 'b449568bf7fc' --commands - << EOF --config experimental.evolution.track-operation=1
550 547 > pick b449568bf7fc 13 f
551 548 > pick 7395e1ff83bd 15 h
552 549 > pick 6b70183d2492 14 g
553 550 > pick b605fb7503f2 16 i
554 551 > pick 3a6c53ee7f3d 17 j
555 552 > edit ee118ab9fa44 18 k
556 553 > EOF
557 554 Editing (ee118ab9fa44), you may commit or record as needed now.
558 555 (hg histedit --continue to resume)
559 556 [1]
560 557 $ hg histedit --continue --config experimental.evolution.track-operation=1
561 558 $ hg log -G
562 559 @ 23:175d6b286a22 (secret) k
563 560 |
564 561 o 22:44ca09d59ae4 (secret) j
565 562 |
566 563 o 21:31747692a644 (secret) i
567 564 |
568 565 o 20:9985cd4f21fa (draft) g
569 566 |
570 567 o 19:4dc06258baa6 (draft) h
571 568 |
572 569 o 13:b449568bf7fc (draft) f
573 570 |
574 571 o 12:40db8afa467b (public) c
575 572 |
576 573 o 0:cb9a9f314b8b (public) a
577 574
578 575 $ hg debugobsolete --rev .
579 576 ee118ab9fa44ebb86be85996548b5517a39e5093 175d6b286a224c23f192e79a581ce83131a53fa2 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'histedit', 'user': 'test'}
@@ -1,464 +1,463 b''
1 1 Create configuration
2 2
3 3 $ echo "[ui]" >> $HGRCPATH
4 4 $ echo "interactive=true" >> $HGRCPATH
5 5
6 6 help record (no record)
7 7
8 8 $ hg help record
9 9 record extension - commands to interactively select changes for
10 10 commit/qrefresh (DEPRECATED)
11 11
12 12 The feature provided by this extension has been moved into core Mercurial as
13 13 'hg commit --interactive'.
14 14
15 15 (use 'hg help extensions' for information on enabling extensions)
16 16
17 17 help qrecord (no record)
18 18
19 19 $ hg help qrecord
20 20 'qrecord' is provided by the following extension:
21 21
22 22 record commands to interactively select changes for commit/qrefresh
23 23 (DEPRECATED)
24 24
25 25 (use 'hg help extensions' for information on enabling extensions)
26 26
27 27 $ echo "[extensions]" >> $HGRCPATH
28 28 $ echo "record=" >> $HGRCPATH
29 29
30 30 help record (record)
31 31
32 32 $ hg help record
33 33 hg record [OPTION]... [FILE]...
34 34
35 35 interactively select changes to commit
36 36
37 37 If a list of files is omitted, all changes reported by 'hg status' will be
38 38 candidates for recording.
39 39
40 40 See 'hg help dates' for a list of formats valid for -d/--date.
41 41
42 42 If using the text interface (see 'hg help config'), you will be prompted
43 43 for whether to record changes to each modified file, and for files with
44 44 multiple changes, for each change to use. For each query, the following
45 45 responses are possible:
46 46
47 47 y - record this change
48 48 n - skip this change
49 49 e - edit this change manually
50 50
51 51 s - skip remaining changes to this file
52 52 f - record remaining changes to this file
53 53
54 54 d - done, skip remaining changes and files
55 55 a - record all changes to all remaining files
56 56 q - quit, recording no changes
57 57
58 58 ? - display help
59 59
60 60 This command is not available when committing a merge.
61 61
62 62 (use 'hg help -e record' to show help for the record extension)
63 63
64 64 options ([+] can be repeated):
65 65
66 66 -A --addremove mark new/missing files as added/removed before
67 67 committing
68 68 --close-branch mark a branch head as closed
69 69 --amend amend the parent of the working directory
70 70 -s --secret use the secret phase for committing
71 71 -e --edit invoke editor on commit messages
72 72 -I --include PATTERN [+] include names matching the given patterns
73 73 -X --exclude PATTERN [+] exclude names matching the given patterns
74 74 -m --message TEXT use text as commit message
75 75 -l --logfile FILE read commit message from file
76 76 -d --date DATE record the specified date as commit date
77 77 -u --user USER record the specified user as committer
78 78 -S --subrepos recurse into subrepositories
79 79 -w --ignore-all-space ignore white space when comparing lines
80 80 -b --ignore-space-change ignore changes in the amount of white space
81 81 -B --ignore-blank-lines ignore changes whose lines are all blank
82 82 -Z --ignore-space-at-eol ignore changes in whitespace at EOL
83 83
84 84 (some details hidden, use --verbose to show complete help)
85 85
86 86 help (no mq, so no qrecord)
87 87
88 88 $ hg help qrecord
89 89 hg qrecord [OPTION]... PATCH [FILE]...
90 90
91 91 interactively record a new patch
92 92
93 93 See 'hg help qnew' & 'hg help record' for more information and usage.
94 94
95 95 (some details hidden, use --verbose to show complete help)
96 96
97 97 $ hg init a
98 98
99 99 qrecord (mq not present)
100 100
101 101 $ hg -R a qrecord
102 102 hg qrecord: invalid arguments
103 103 hg qrecord [OPTION]... PATCH [FILE]...
104 104
105 105 interactively record a new patch
106 106
107 107 (use 'hg qrecord -h' to show more help)
108 108 [255]
109 109
110 110 qrecord patch (mq not present)
111 111
112 112 $ hg -R a qrecord patch
113 113 abort: 'mq' extension not loaded
114 114 [255]
115 115
116 116 help (bad mq)
117 117
118 118 $ echo "mq=nonexistent" >> $HGRCPATH
119 119 $ hg help qrecord
120 120 *** failed to import extension mq from nonexistent: [Errno *] * (glob)
121 121 hg qrecord [OPTION]... PATCH [FILE]...
122 122
123 123 interactively record a new patch
124 124
125 125 See 'hg help qnew' & 'hg help record' for more information and usage.
126 126
127 127 (some details hidden, use --verbose to show complete help)
128 128
129 129 help (mq present)
130 130
131 131 $ sed 's/mq=nonexistent/mq=/' $HGRCPATH > hgrc.tmp
132 132 $ mv hgrc.tmp $HGRCPATH
133 133
134 134 $ hg help qrecord
135 135 hg qrecord [OPTION]... PATCH [FILE]...
136 136
137 137 interactively record a new patch
138 138
139 139 See 'hg help qnew' & 'hg help record' for more information and usage.
140 140
141 141 options ([+] can be repeated):
142 142
143 143 -e --edit invoke editor on commit messages
144 144 -g --git use git extended diff format
145 145 -U --currentuser add "From: <current user>" to patch
146 146 -u --user USER add "From: <USER>" to patch
147 147 -D --currentdate add "Date: <current date>" to patch
148 148 -d --date DATE add "Date: <DATE>" to patch
149 149 -I --include PATTERN [+] include names matching the given patterns
150 150 -X --exclude PATTERN [+] exclude names matching the given patterns
151 151 -m --message TEXT use text as commit message
152 152 -l --logfile FILE read commit message from file
153 153 -w --ignore-all-space ignore white space when comparing lines
154 154 -b --ignore-space-change ignore changes in the amount of white space
155 155 -B --ignore-blank-lines ignore changes whose lines are all blank
156 156 -Z --ignore-space-at-eol ignore changes in whitespace at EOL
157 157 --mq operate on patch repository
158 158
159 159 (some details hidden, use --verbose to show complete help)
160 160
161 161 $ cd a
162 162
163 163 Base commit
164 164
165 165 $ cat > 1.txt <<EOF
166 166 > 1
167 167 > 2
168 168 > 3
169 169 > 4
170 170 > 5
171 171 > EOF
172 172 $ cat > 2.txt <<EOF
173 173 > a
174 174 > b
175 175 > c
176 176 > d
177 177 > e
178 178 > f
179 179 > EOF
180 180
181 181 $ mkdir dir
182 182 $ cat > dir/a.txt <<EOF
183 183 > hello world
184 184 >
185 185 > someone
186 186 > up
187 187 > there
188 188 > loves
189 189 > me
190 190 > EOF
191 191
192 192 $ hg add 1.txt 2.txt dir/a.txt
193 193 $ hg commit -m 'initial checkin'
194 194
195 195 Changing files
196 196
197 197 $ sed -e 's/2/2 2/;s/4/4 4/' 1.txt > 1.txt.new
198 198 $ sed -e 's/b/b b/' 2.txt > 2.txt.new
199 199 $ sed -e 's/hello world/hello world!/' dir/a.txt > dir/a.txt.new
200 200
201 201 $ mv -f 1.txt.new 1.txt
202 202 $ mv -f 2.txt.new 2.txt
203 203 $ mv -f dir/a.txt.new dir/a.txt
204 204
205 205 Whole diff
206 206
207 207 $ hg diff --nodates
208 208 diff -r 1057167b20ef 1.txt
209 209 --- a/1.txt
210 210 +++ b/1.txt
211 211 @@ -1,5 +1,5 @@
212 212 1
213 213 -2
214 214 +2 2
215 215 3
216 216 -4
217 217 +4 4
218 218 5
219 219 diff -r 1057167b20ef 2.txt
220 220 --- a/2.txt
221 221 +++ b/2.txt
222 222 @@ -1,5 +1,5 @@
223 223 a
224 224 -b
225 225 +b b
226 226 c
227 227 d
228 228 e
229 229 diff -r 1057167b20ef dir/a.txt
230 230 --- a/dir/a.txt
231 231 +++ b/dir/a.txt
232 232 @@ -1,4 +1,4 @@
233 233 -hello world
234 234 +hello world!
235 235
236 236 someone
237 237 up
238 238
239 239 qrecord with bad patch name, should abort before prompting
240 240
241 241 $ hg qrecord .hg
242 242 abort: patch name cannot begin with ".hg"
243 243 [255]
244 244 $ hg qrecord ' foo'
245 245 abort: patch name cannot begin or end with whitespace
246 246 [255]
247 247 $ hg qrecord 'foo '
248 248 abort: patch name cannot begin or end with whitespace
249 249 [255]
250 250
251 251 qrecord a.patch
252 252
253 253 $ hg qrecord -d '0 0' -m aaa a.patch <<EOF
254 254 > y
255 255 > y
256 256 > n
257 257 > y
258 258 > y
259 259 > n
260 260 > EOF
261 261 diff --git a/1.txt b/1.txt
262 262 2 hunks, 2 lines changed
263 263 examine changes to '1.txt'? [Ynesfdaq?] y
264 264
265 265 @@ -1,3 +1,3 @@
266 266 1
267 267 -2
268 268 +2 2
269 269 3
270 270 record change 1/4 to '1.txt'? [Ynesfdaq?] y
271 271
272 272 @@ -3,3 +3,3 @@
273 273 3
274 274 -4
275 275 +4 4
276 276 5
277 277 record change 2/4 to '1.txt'? [Ynesfdaq?] n
278 278
279 279 diff --git a/2.txt b/2.txt
280 280 1 hunks, 1 lines changed
281 281 examine changes to '2.txt'? [Ynesfdaq?] y
282 282
283 283 @@ -1,5 +1,5 @@
284 284 a
285 285 -b
286 286 +b b
287 287 c
288 288 d
289 289 e
290 290 record change 3/4 to '2.txt'? [Ynesfdaq?] y
291 291
292 292 diff --git a/dir/a.txt b/dir/a.txt
293 293 1 hunks, 1 lines changed
294 294 examine changes to 'dir/a.txt'? [Ynesfdaq?] n
295 295
296 296
297 297 After qrecord a.patch 'tip'"
298 298
299 299 $ hg tip -p
300 300 changeset: 1:5d1ca63427ee
301 301 tag: a.patch
302 302 tag: qbase
303 303 tag: qtip
304 304 tag: tip
305 305 user: test
306 306 date: Thu Jan 01 00:00:00 1970 +0000
307 307 summary: aaa
308 308
309 309 diff -r 1057167b20ef -r 5d1ca63427ee 1.txt
310 310 --- a/1.txt Thu Jan 01 00:00:00 1970 +0000
311 311 +++ b/1.txt Thu Jan 01 00:00:00 1970 +0000
312 312 @@ -1,5 +1,5 @@
313 313 1
314 314 -2
315 315 +2 2
316 316 3
317 317 4
318 318 5
319 319 diff -r 1057167b20ef -r 5d1ca63427ee 2.txt
320 320 --- a/2.txt Thu Jan 01 00:00:00 1970 +0000
321 321 +++ b/2.txt Thu Jan 01 00:00:00 1970 +0000
322 322 @@ -1,5 +1,5 @@
323 323 a
324 324 -b
325 325 +b b
326 326 c
327 327 d
328 328 e
329 329
330 330
331 331 After qrecord a.patch 'diff'"
332 332
333 333 $ hg diff --nodates
334 334 diff -r 5d1ca63427ee 1.txt
335 335 --- a/1.txt
336 336 +++ b/1.txt
337 337 @@ -1,5 +1,5 @@
338 338 1
339 339 2 2
340 340 3
341 341 -4
342 342 +4 4
343 343 5
344 344 diff -r 5d1ca63427ee dir/a.txt
345 345 --- a/dir/a.txt
346 346 +++ b/dir/a.txt
347 347 @@ -1,4 +1,4 @@
348 348 -hello world
349 349 +hello world!
350 350
351 351 someone
352 352 up
353 353
354 354 qrecord b.patch
355 355
356 356 $ hg qrecord -d '0 0' -m bbb b.patch <<EOF
357 357 > y
358 358 > y
359 359 > y
360 360 > y
361 361 > EOF
362 362 diff --git a/1.txt b/1.txt
363 363 1 hunks, 1 lines changed
364 364 examine changes to '1.txt'? [Ynesfdaq?] y
365 365
366 366 @@ -1,5 +1,5 @@
367 367 1
368 368 2 2
369 369 3
370 370 -4
371 371 +4 4
372 372 5
373 373 record change 1/2 to '1.txt'? [Ynesfdaq?] y
374 374
375 375 diff --git a/dir/a.txt b/dir/a.txt
376 376 1 hunks, 1 lines changed
377 377 examine changes to 'dir/a.txt'? [Ynesfdaq?] y
378 378
379 379 @@ -1,4 +1,4 @@
380 380 -hello world
381 381 +hello world!
382 382
383 383 someone
384 384 up
385 385 record change 2/2 to 'dir/a.txt'? [Ynesfdaq?] y
386 386
387 387
388 388 After qrecord b.patch 'tip'
389 389
390 390 $ hg tip -p
391 391 changeset: 2:b056198bf878
392 392 tag: b.patch
393 393 tag: qtip
394 394 tag: tip
395 395 user: test
396 396 date: Thu Jan 01 00:00:00 1970 +0000
397 397 summary: bbb
398 398
399 399 diff -r 5d1ca63427ee -r b056198bf878 1.txt
400 400 --- a/1.txt Thu Jan 01 00:00:00 1970 +0000
401 401 +++ b/1.txt Thu Jan 01 00:00:00 1970 +0000
402 402 @@ -1,5 +1,5 @@
403 403 1
404 404 2 2
405 405 3
406 406 -4
407 407 +4 4
408 408 5
409 409 diff -r 5d1ca63427ee -r b056198bf878 dir/a.txt
410 410 --- a/dir/a.txt Thu Jan 01 00:00:00 1970 +0000
411 411 +++ b/dir/a.txt Thu Jan 01 00:00:00 1970 +0000
412 412 @@ -1,4 +1,4 @@
413 413 -hello world
414 414 +hello world!
415 415
416 416 someone
417 417 up
418 418
419 419
420 420 After qrecord b.patch 'diff'
421 421
422 422 $ hg diff --nodates
423 423
424 424 $ cd ..
425 425
426 426 qrecord should throw an error when histedit in process
427 427
428 428 $ hg init issue5981
429 429 $ cd issue5981
430 430 $ cat >> $HGRCPATH <<EOF
431 431 > [extensions]
432 432 > histedit=
433 433 > mq=
434 434 > EOF
435 435 $ echo > a
436 436 $ hg ci -Am 'foo bar'
437 437 adding a
438 438 $ hg log
439 439 changeset: 0:ea55e2ae468f
440 440 tag: tip
441 441 user: test
442 442 date: Thu Jan 01 00:00:00 1970 +0000
443 443 summary: foo bar
444 444
445 445 $ hg histedit tip --commands - 2>&1 <<EOF
446 446 > edit ea55e2ae468f foo bar
447 447 > EOF
448 448 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
449 adding a
450 449 Editing (ea55e2ae468f), you may commit or record as needed now.
451 450 (hg histedit --continue to resume)
452 451 [1]
453 452 $ echo 'foo bar' > a
454 453 $ hg qrecord -d '0 0' -m aaa a.patch <<EOF
455 454 > y
456 455 > y
457 456 > n
458 457 > y
459 458 > y
460 459 > n
461 460 > EOF
462 461 abort: histedit in progress
463 462 (use 'hg histedit --continue' or 'hg histedit --abort')
464 463 [255]
General Comments 0
You need to be logged in to leave comments. Login now