##// END OF EJS Templates
shelve: use rebase instead of merge (issue4068)...
Durham Goode -
r19961:1d7a36ff stable
parent child Browse files
Show More
@@ -1,640 +1,708 b''
1 1 # shelve.py - save/restore working directory state
2 2 #
3 3 # Copyright 2013 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """save and restore changes to the working directory
9 9
10 10 The "hg shelve" command saves changes made to the working directory
11 11 and reverts those changes, resetting the working directory to a clean
12 12 state.
13 13
14 14 Later on, the "hg unshelve" command restores the changes saved by "hg
15 15 shelve". Changes can be restored even after updating to a different
16 16 parent, in which case Mercurial's merge machinery will resolve any
17 17 conflicts if necessary.
18 18
19 19 You can have more than one shelved change outstanding at a time; each
20 20 shelved change has a distinct name. For details, see the help for "hg
21 21 shelve".
22 22 """
23 23
24 24 from mercurial.i18n import _
25 25 from mercurial.node import nullid, bin, hex
26 26 from mercurial import changegroup, cmdutil, scmutil, phases
27 27 from mercurial import error, hg, mdiff, merge, patch, repair, util
28 28 from mercurial import templatefilters
29 29 from mercurial import lock as lockmod
30 from hgext import rebase
30 31 import errno
31 32
32 33 cmdtable = {}
33 34 command = cmdutil.command(cmdtable)
34 35 testedwith = 'internal'
35 36
36 37 class shelvedfile(object):
37 38 """Helper for the file storing a single shelve
38 39
39 40 Handles common functions on shelve files (.hg/.files/.patch) using
40 41 the vfs layer"""
41 42 def __init__(self, repo, name, filetype=None):
42 43 self.repo = repo
43 44 self.name = name
44 45 self.vfs = scmutil.vfs(repo.join('shelved'))
45 46 if filetype:
46 47 self.fname = name + '.' + filetype
47 48 else:
48 49 self.fname = name
49 50
50 51 def exists(self):
51 52 return self.vfs.exists(self.fname)
52 53
53 54 def filename(self):
54 55 return self.vfs.join(self.fname)
55 56
56 57 def unlink(self):
57 58 util.unlink(self.filename())
58 59
59 60 def stat(self):
60 61 return self.vfs.stat(self.fname)
61 62
62 63 def opener(self, mode='rb'):
63 64 try:
64 65 return self.vfs(self.fname, mode)
65 66 except IOError, err:
66 67 if err.errno != errno.ENOENT:
67 68 raise
68 69 if mode[0] in 'wa':
69 70 try:
70 71 self.vfs.mkdir()
71 72 return self.vfs(self.fname, mode)
72 73 except IOError, err:
73 74 if err.errno != errno.EEXIST:
74 75 raise
75 76 elif mode[0] == 'r':
76 77 raise util.Abort(_("shelved change '%s' not found") %
77 78 self.name)
78 79
79 80 class shelvedstate(object):
80 81 """Handle persistence during unshelving operations.
81 82
82 83 Handles saving and restoring a shelved state. Ensures that different
83 84 versions of a shelved state are possible and handles them appropriately.
84 85 """
85 86 _version = 1
86 87 _filename = 'shelvedstate'
87 88
88 89 @classmethod
89 90 def load(cls, repo):
90 91 fp = repo.opener(cls._filename)
91 92 try:
92 93 version = int(fp.readline().strip())
93 94
94 95 if version != cls._version:
95 96 raise util.Abort(_('this version of shelve is incompatible '
96 97 'with the version used in this repo'))
97 98 name = fp.readline().strip()
99 wctx = fp.readline().strip()
100 pendingctx = fp.readline().strip()
98 101 parents = [bin(h) for h in fp.readline().split()]
99 102 stripnodes = [bin(h) for h in fp.readline().split()]
103 unknownfiles = fp.readline()[:-1].split('\0')
100 104 finally:
101 105 fp.close()
102 106
103 107 obj = cls()
104 108 obj.name = name
109 obj.wctx = repo[bin(wctx)]
110 obj.pendingctx = repo[bin(pendingctx)]
105 111 obj.parents = parents
106 112 obj.stripnodes = stripnodes
113 obj.unknownfiles = unknownfiles
107 114
108 115 return obj
109 116
110 117 @classmethod
111 def save(cls, repo, name, stripnodes):
118 def save(cls, repo, name, originalwctx, pendingctx, stripnodes,
119 unknownfiles):
112 120 fp = repo.opener(cls._filename, 'wb')
113 121 fp.write('%i\n' % cls._version)
114 122 fp.write('%s\n' % name)
123 fp.write('%s\n' % hex(originalwctx.node()))
124 fp.write('%s\n' % hex(pendingctx.node()))
115 125 fp.write('%s\n' % ' '.join([hex(p) for p in repo.dirstate.parents()]))
116 126 fp.write('%s\n' % ' '.join([hex(n) for n in stripnodes]))
127 fp.write('%s\n' % '\0'.join(unknownfiles))
117 128 fp.close()
118 129
119 130 @classmethod
120 131 def clear(cls, repo):
121 132 util.unlinkpath(repo.join(cls._filename), ignoremissing=True)
122 133
123 134 def createcmd(ui, repo, pats, opts):
124 135 """subcommand that creates a new shelve"""
125 136
126 137 def publicancestors(ctx):
127 138 """Compute the heads of the public ancestors of a commit.
128 139
129 140 Much faster than the revset heads(ancestors(ctx) - draft())"""
130 141 seen = set()
131 142 visit = util.deque()
132 143 visit.append(ctx)
133 144 while visit:
134 145 ctx = visit.popleft()
135 146 for parent in ctx.parents():
136 147 rev = parent.rev()
137 148 if rev not in seen:
138 149 seen.add(rev)
139 150 if parent.mutable():
140 151 visit.append(parent)
141 152 else:
142 153 yield parent.node()
143 154
144 155 wctx = repo[None]
145 156 parents = wctx.parents()
146 157 if len(parents) > 1:
147 158 raise util.Abort(_('cannot shelve while merging'))
148 159 parent = parents[0]
149 160
150 161 # we never need the user, so we use a generic user for all shelve operations
151 162 user = 'shelve@localhost'
152 163 label = repo._bookmarkcurrent or parent.branch() or 'default'
153 164
154 165 # slashes aren't allowed in filenames, therefore we rename it
155 166 origlabel, label = label, label.replace('/', '_')
156 167
157 168 def gennames():
158 169 yield label
159 170 for i in xrange(1, 100):
160 171 yield '%s-%02d' % (label, i)
161 172
162 173 shelvedfiles = []
163 174
164 175 def commitfunc(ui, repo, message, match, opts):
165 176 # check modified, added, removed, deleted only
166 177 for flist in repo.status(match=match)[:4]:
167 178 shelvedfiles.extend(flist)
168 179 hasmq = util.safehasattr(repo, 'mq')
169 180 if hasmq:
170 181 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
171 182 try:
172 183 return repo.commit(message, user, opts.get('date'), match)
173 184 finally:
174 185 if hasmq:
175 186 repo.mq.checkapplied = saved
176 187
177 188 if parent.node() != nullid:
178 189 desc = parent.description().split('\n', 1)[0]
179 190 else:
180 191 desc = '(empty repository)'
181 192
182 193 if not opts['message']:
183 194 opts['message'] = desc
184 195
185 196 name = opts['name']
186 197
187 198 wlock = lock = tr = bms = None
188 199 try:
189 200 wlock = repo.wlock()
190 201 lock = repo.lock()
191 202
192 203 bms = repo._bookmarks.copy()
193 204 # use an uncommitted transaction to generate the bundle to avoid
194 205 # pull races. ensure we don't print the abort message to stderr.
195 206 tr = repo.transaction('commit', report=lambda x: None)
196 207
197 208 if name:
198 209 if shelvedfile(repo, name, 'hg').exists():
199 210 raise util.Abort(_("a shelved change named '%s' already exists")
200 211 % name)
201 212 else:
202 213 for n in gennames():
203 214 if not shelvedfile(repo, n, 'hg').exists():
204 215 name = n
205 216 break
206 217 else:
207 218 raise util.Abort(_("too many shelved changes named '%s'") %
208 219 label)
209 220
210 221 # ensure we are not creating a subdirectory or a hidden file
211 222 if '/' in name or '\\' in name:
212 223 raise util.Abort(_('shelved change names may not contain slashes'))
213 224 if name.startswith('.'):
214 225 raise util.Abort(_("shelved change names may not start with '.'"))
215 226
216 227 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
217 228
218 229 if not node:
219 230 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
220 231 if stat[3]:
221 232 ui.status(_("nothing changed (%d missing files, see "
222 233 "'hg status')\n") % len(stat[3]))
223 234 else:
224 235 ui.status(_("nothing changed\n"))
225 236 return 1
226 237
227 238 phases.retractboundary(repo, phases.secret, [node])
228 239
229 240 fp = shelvedfile(repo, name, 'files').opener('wb')
230 241 fp.write('\0'.join(shelvedfiles))
231 242
232 243 bases = list(publicancestors(repo[node]))
233 244 cg = repo.changegroupsubset(bases, [node], 'shelve')
234 245 changegroup.writebundle(cg, shelvedfile(repo, name, 'hg').filename(),
235 246 'HG10UN')
236 247 cmdutil.export(repo, [node],
237 248 fp=shelvedfile(repo, name, 'patch').opener('wb'),
238 249 opts=mdiff.diffopts(git=True))
239 250
240 251
241 252 if ui.formatted():
242 253 desc = util.ellipsis(desc, ui.termwidth())
243 254 ui.status(_('shelved as %s\n') % name)
244 255 hg.update(repo, parent.node())
245 256 finally:
246 257 if bms:
247 258 # restore old bookmarks
248 259 repo._bookmarks.update(bms)
249 260 repo._bookmarks.write()
250 261 if tr:
251 262 tr.abort()
252 263 lockmod.release(lock, wlock)
253 264
254 265 def cleanupcmd(ui, repo):
255 266 """subcommand that deletes all shelves"""
256 267
257 268 wlock = None
258 269 try:
259 270 wlock = repo.wlock()
260 271 for (name, _) in repo.vfs.readdir('shelved'):
261 272 suffix = name.rsplit('.', 1)[-1]
262 273 if suffix in ('hg', 'files', 'patch'):
263 274 shelvedfile(repo, name).unlink()
264 275 finally:
265 276 lockmod.release(wlock)
266 277
267 278 def deletecmd(ui, repo, pats):
268 279 """subcommand that deletes a specific shelve"""
269 280 if not pats:
270 281 raise util.Abort(_('no shelved changes specified!'))
271 282 wlock = None
272 283 try:
273 284 wlock = repo.wlock()
274 285 try:
275 286 for name in pats:
276 287 for suffix in 'hg files patch'.split():
277 288 shelvedfile(repo, name, suffix).unlink()
278 289 except OSError, err:
279 290 if err.errno != errno.ENOENT:
280 291 raise
281 292 raise util.Abort(_("shelved change '%s' not found") % name)
282 293 finally:
283 294 lockmod.release(wlock)
284 295
285 296 def listshelves(repo):
286 297 """return all shelves in repo as list of (time, filename)"""
287 298 try:
288 299 names = repo.vfs.readdir('shelved')
289 300 except OSError, err:
290 301 if err.errno != errno.ENOENT:
291 302 raise
292 303 return []
293 304 info = []
294 305 for (name, _) in names:
295 306 pfx, sfx = name.rsplit('.', 1)
296 307 if not pfx or sfx != 'patch':
297 308 continue
298 309 st = shelvedfile(repo, name).stat()
299 310 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
300 311 return sorted(info, reverse=True)
301 312
302 313 def listcmd(ui, repo, pats, opts):
303 314 """subcommand that displays the list of shelves"""
304 315 pats = set(pats)
305 316 width = 80
306 317 if not ui.plain():
307 318 width = ui.termwidth()
308 319 namelabel = 'shelve.newest'
309 320 for mtime, name in listshelves(repo):
310 321 sname = util.split(name)[1]
311 322 if pats and sname not in pats:
312 323 continue
313 324 ui.write(sname, label=namelabel)
314 325 namelabel = 'shelve.name'
315 326 if ui.quiet:
316 327 ui.write('\n')
317 328 continue
318 329 ui.write(' ' * (16 - len(sname)))
319 330 used = 16
320 331 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
321 332 ui.write(age, label='shelve.age')
322 333 ui.write(' ' * (12 - len(age)))
323 334 used += 12
324 335 fp = open(name + '.patch', 'rb')
325 336 try:
326 337 while True:
327 338 line = fp.readline()
328 339 if not line:
329 340 break
330 341 if not line.startswith('#'):
331 342 desc = line.rstrip()
332 343 if ui.formatted():
333 344 desc = util.ellipsis(desc, width - used)
334 345 ui.write(desc)
335 346 break
336 347 ui.write('\n')
337 348 if not (opts['patch'] or opts['stat']):
338 349 continue
339 350 difflines = fp.readlines()
340 351 if opts['patch']:
341 352 for chunk, label in patch.difflabel(iter, difflines):
342 353 ui.write(chunk, label=label)
343 354 if opts['stat']:
344 355 for chunk, label in patch.diffstatui(difflines, width=width,
345 356 git=True):
346 357 ui.write(chunk, label=label)
347 358 finally:
348 359 fp.close()
349 360
350 361 def readshelvedfiles(repo, basename):
351 362 """return the list of files touched in a shelve"""
352 363 fp = shelvedfile(repo, basename, 'files').opener()
353 364 return fp.read().split('\0')
354 365
355 366 def checkparents(repo, state):
356 367 """check parent while resuming an unshelve"""
357 368 if state.parents != repo.dirstate.parents():
358 369 raise util.Abort(_('working directory parents do not match unshelve '
359 370 'state'))
360 371
361 372 def pathtofiles(repo, files):
362 373 cwd = repo.getcwd()
363 374 return [repo.pathto(f, cwd) for f in files]
364 375
365 376 def unshelveabort(ui, repo, state, opts):
366 377 """subcommand that abort an in-progress unshelve"""
367 378 wlock = repo.wlock()
368 379 lock = None
369 380 try:
370 381 checkparents(repo, state)
382
383 util.rename(repo.join('unshelverebasestate'),
384 repo.join('rebasestate'))
385 try:
386 rebase.rebase(ui, repo, **{
387 'abort' : True
388 })
389 except Exception:
390 util.rename(repo.join('rebasestate'),
391 repo.join('unshelverebasestate'))
392 raise
393
371 394 lock = repo.lock()
372 merge.mergestate(repo).reset()
373 if opts['keep']:
374 repo.setparents(repo.dirstate.parents()[0])
375 else:
376 revertfiles = readshelvedfiles(repo, state.name)
377 wctx = repo.parents()[0]
378 cmdutil.revert(ui, repo, wctx, [wctx.node(), nullid],
379 *pathtofiles(repo, revertfiles),
380 **{'no_backup': True})
381 # fix up the weird dirstate states the merge left behind
382 mf = wctx.manifest()
383 dirstate = repo.dirstate
384 for f in revertfiles:
385 if f in mf:
386 dirstate.normallookup(f)
387 else:
388 dirstate.drop(f)
389 dirstate._pl = (wctx.node(), nullid)
390 dirstate._dirty = True
395
396 mergefiles(ui, repo, state.wctx, state.pendingctx, state.unknownfiles)
397
391 398 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
392 399 shelvedstate.clear(repo)
393 400 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
394 401 finally:
395 402 lockmod.release(lock, wlock)
396 403
404 def mergefiles(ui, repo, wctx, shelvectx, unknownfiles):
405 """updates to wctx and merges the changes from shelvectx into the
406 dirstate. drops any files in unknownfiles from the dirstate."""
407 oldquiet = ui.quiet
408 try:
409 ui.quiet = True
410 hg.update(repo, wctx.node())
411 files = []
412 files.extend(shelvectx.files())
413 files.extend(shelvectx.parents()[0].files())
414 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
415 *pathtofiles(repo, files),
416 **{'no_backup': True})
417 finally:
418 ui.quiet = oldquiet
419
420 # Send untracked files back to being untracked
421 dirstate = repo.dirstate
422 for f in unknownfiles:
423 dirstate.drop(f)
424
397 425 def unshelvecleanup(ui, repo, name, opts):
398 426 """remove related files after an unshelve"""
399 427 if not opts['keep']:
400 428 for filetype in 'hg files patch'.split():
401 429 shelvedfile(repo, name, filetype).unlink()
402 430
403 def finishmerge(ui, repo, ms, stripnodes, name, opts):
404 # Reset the working dir so it's no longer in a merge state.
405 dirstate = repo.dirstate
406 dirstate.setparents(dirstate._pl[0])
407 shelvedstate.clear(repo)
408
409 431 def unshelvecontinue(ui, repo, state, opts):
410 432 """subcommand to continue an in-progress unshelve"""
411 433 # We're finishing off a merge. First parent is our original
412 434 # parent, second is the temporary "fake" commit we're unshelving.
413 435 wlock = repo.wlock()
414 436 lock = None
415 437 try:
416 438 checkparents(repo, state)
417 439 ms = merge.mergestate(repo)
418 440 if [f for f in ms if ms[f] == 'u']:
419 441 raise util.Abort(
420 442 _("unresolved conflicts, can't continue"),
421 443 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
422 finishmerge(ui, repo, ms, state.stripnodes, state.name, opts)
444
423 445 lock = repo.lock()
446
447 util.rename(repo.join('unshelverebasestate'),
448 repo.join('rebasestate'))
449 try:
450 rebase.rebase(ui, repo, **{
451 'continue' : True
452 })
453 except Exception:
454 util.rename(repo.join('rebasestate'),
455 repo.join('unshelverebasestate'))
456 raise
457
458 shelvectx = repo['tip']
459 if not shelvectx in state.pendingctx.children():
460 # rebase was a no-op, so it produced no child commit
461 shelvectx = state.pendingctx
462
463 mergefiles(ui, repo, state.wctx, shelvectx, state.unknownfiles)
464
465 state.stripnodes.append(shelvectx.node())
424 466 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
467 shelvedstate.clear(repo)
425 468 unshelvecleanup(ui, repo, state.name, opts)
426 469 ui.status(_("unshelve of '%s' complete\n") % state.name)
427 470 finally:
428 471 lockmod.release(lock, wlock)
429 472
430 473 @command('unshelve',
431 474 [('a', 'abort', None,
432 475 _('abort an incomplete unshelve operation')),
433 476 ('c', 'continue', None,
434 477 _('continue an incomplete unshelve operation')),
435 478 ('', 'keep', None,
436 479 _('keep shelve after unshelving'))],
437 480 _('hg unshelve [SHELVED]'))
438 481 def unshelve(ui, repo, *shelved, **opts):
439 482 """restore a shelved change to the working directory
440 483
441 484 This command accepts an optional name of a shelved change to
442 485 restore. If none is given, the most recent shelved change is used.
443 486
444 487 If a shelved change is applied successfully, the bundle that
445 488 contains the shelved changes is deleted afterwards.
446 489
447 490 Since you can restore a shelved change on top of an arbitrary
448 491 commit, it is possible that unshelving will result in a conflict
449 492 between your changes and the commits you are unshelving onto. If
450 493 this occurs, you must resolve the conflict, then use
451 494 ``--continue`` to complete the unshelve operation. (The bundle
452 495 will not be deleted until you successfully complete the unshelve.)
453 496
454 497 (Alternatively, you can use ``--abort`` to abandon an unshelve
455 498 that causes a conflict. This reverts the unshelved changes, and
456 499 does not delete the bundle.)
457 500 """
458 501 abortf = opts['abort']
459 502 continuef = opts['continue']
460 503 if not abortf and not continuef:
461 504 cmdutil.checkunfinished(repo)
462 505
463 506 if abortf or continuef:
464 507 if abortf and continuef:
465 508 raise util.Abort(_('cannot use both abort and continue'))
466 509 if shelved:
467 510 raise util.Abort(_('cannot combine abort/continue with '
468 511 'naming a shelved change'))
469 512
470 513 try:
471 514 state = shelvedstate.load(repo)
472 515 except IOError, err:
473 516 if err.errno != errno.ENOENT:
474 517 raise
475 518 raise util.Abort(_('no unshelve operation underway'))
476 519
477 520 if abortf:
478 521 return unshelveabort(ui, repo, state, opts)
479 522 elif continuef:
480 523 return unshelvecontinue(ui, repo, state, opts)
481 524 elif len(shelved) > 1:
482 525 raise util.Abort(_('can only unshelve one change at a time'))
483 526 elif not shelved:
484 527 shelved = listshelves(repo)
485 528 if not shelved:
486 529 raise util.Abort(_('no shelved changes to apply!'))
487 530 basename = util.split(shelved[0][1])[1]
488 531 ui.status(_("unshelving change '%s'\n") % basename)
489 532 else:
490 533 basename = shelved[0]
491 534
492 535 shelvedfiles = readshelvedfiles(repo, basename)
493 536
494 m, a, r, d = repo.status()[:4]
495 unsafe = set(m + a + r + d).intersection(shelvedfiles)
496 if unsafe:
497 ui.warn(_('the following shelved files have been modified:\n'))
498 for f in sorted(unsafe):
499 ui.warn(' %s\n' % f)
500 ui.warn(_('you must commit, revert, or shelve your changes before you '
501 'can proceed\n'))
502 raise util.Abort(_('cannot unshelve due to local changes\n'))
503
504 537 wlock = lock = tr = None
505 538 try:
506 539 lock = repo.lock()
540 wlock = repo.wlock()
507 541
508 542 tr = repo.transaction('unshelve', report=lambda x: None)
509 543 oldtiprev = len(repo)
544
545 wctx = repo['.']
546 tmpwctx = wctx
547 # The goal is to have a commit structure like so:
548 # ...-> wctx -> tmpwctx -> shelvectx
549 # where tmpwctx is an optional commit with the user's pending changes
550 # and shelvectx is the unshelved changes. Then we merge it all down
551 # to the original wctx.
552
553 # Store pending changes in a commit
554 m, a, r, d, u = repo.status(unknown=True)[:5]
555 if m or a or r or d or u:
556 def commitfunc(ui, repo, message, match, opts):
557 hasmq = util.safehasattr(repo, 'mq')
558 if hasmq:
559 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
560
561 try:
562 return repo.commit(message, 'shelve@localhost',
563 opts.get('date'), match)
564 finally:
565 if hasmq:
566 repo.mq.checkapplied = saved
567
568 tempopts = {}
569 tempopts['message'] = "pending changes temporary commit"
570 tempopts['addremove'] = True
571 oldquiet = ui.quiet
572 try:
573 ui.quiet = True
574 node = cmdutil.commit(ui, repo, commitfunc, None, tempopts)
575 finally:
576 ui.quiet = oldquiet
577 tmpwctx = repo[node]
578
510 579 try:
511 580 fp = shelvedfile(repo, basename, 'hg').opener()
512 581 gen = changegroup.readbundle(fp, fp.name)
513 582 repo.addchangegroup(gen, 'unshelve', 'bundle:' + fp.name)
514 583 nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)]
515 584 phases.retractboundary(repo, phases.secret, nodes)
516 tr.close()
517 585 finally:
518 586 fp.close()
519 587
520 tip = repo['tip']
521 wctx = repo['.']
522 ancestor = tip.ancestor(wctx)
523
524 wlock = repo.wlock()
588 shelvectx = repo['tip']
525 589
526 if ancestor.node() != wctx.node():
527 conflicts = hg.merge(repo, tip.node(), force=True, remind=False)
528 ms = merge.mergestate(repo)
529 stripnodes = [repo.changelog.node(rev)
530 for rev in xrange(oldtiprev, len(repo))]
531 if conflicts:
532 shelvedstate.save(repo, basename, stripnodes)
533 # Fix up the dirstate entries of files from the second
534 # parent as if we were not merging, except for those
535 # with unresolved conflicts.
536 parents = repo.parents()
537 revertfiles = set(parents[1].files()).difference(ms)
538 cmdutil.revert(ui, repo, parents[1],
539 (parents[0].node(), nullid),
540 *pathtofiles(repo, revertfiles),
541 **{'no_backup': True})
590 # If the shelve is not immediately on top of the commit
591 # we'll be merging with, rebase it to be on top.
592 if tmpwctx.node() != shelvectx.parents()[0].node():
593 try:
594 rebase.rebase(ui, repo, **{
595 'rev' : [shelvectx.rev()],
596 'dest' : str(tmpwctx.rev()),
597 'keep' : True,
598 })
599 except error.InterventionRequired:
600 tr.close()
601
602 stripnodes = [repo.changelog.node(rev)
603 for rev in xrange(oldtiprev, len(repo))]
604 shelvedstate.save(repo, basename, wctx, tmpwctx, stripnodes, u)
605
606 util.rename(repo.join('rebasestate'),
607 repo.join('unshelverebasestate'))
542 608 raise error.InterventionRequired(
543 609 _("unresolved conflicts (see 'hg resolve', then "
544 610 "'hg unshelve --continue')"))
545 finishmerge(ui, repo, ms, stripnodes, basename, opts)
546 else:
547 parent = tip.parents()[0]
548 hg.update(repo, parent.node())
549 cmdutil.revert(ui, repo, tip, repo.dirstate.parents(),
550 *pathtofiles(repo, tip.files()),
551 **{'no_backup': True})
611
612 # refresh ctx after rebase completes
613 shelvectx = repo['tip']
614
615 if not shelvectx in tmpwctx.children():
616 # rebase was a no-op, so it produced no child commit
617 shelvectx = tmpwctx
552 618
553 prevquiet = ui.quiet
554 ui.quiet = True
555 try:
556 repo.rollback(force=True)
557 finally:
558 ui.quiet = prevquiet
619 mergefiles(ui, repo, wctx, shelvectx, u)
620 shelvedstate.clear(repo)
621
622 # The transaction aborting will strip all the commits for us,
623 # but it doesn't update the inmemory structures, so addchangegroup
624 # hooks still fire and try to operate on the missing commits.
625 # Clean up manually to prevent this.
626 repo.changelog.strip(oldtiprev, tr)
559 627
560 628 unshelvecleanup(ui, repo, basename, opts)
561 629 finally:
562 630 if tr:
563 631 tr.release()
564 632 lockmod.release(lock, wlock)
565 633
566 634 @command('shelve',
567 635 [('A', 'addremove', None,
568 636 _('mark new/missing files as added/removed before shelving')),
569 637 ('', 'cleanup', None,
570 638 _('delete all shelved changes')),
571 639 ('', 'date', '',
572 640 _('shelve with the specified commit date'), _('DATE')),
573 641 ('d', 'delete', None,
574 642 _('delete the named shelved change(s)')),
575 643 ('l', 'list', None,
576 644 _('list current shelves')),
577 645 ('m', 'message', '',
578 646 _('use text as shelve message'), _('TEXT')),
579 647 ('n', 'name', '',
580 648 _('use the given name for the shelved commit'), _('NAME')),
581 649 ('p', 'patch', None,
582 650 _('show patch')),
583 651 ('', 'stat', None,
584 652 _('output diffstat-style summary of changes'))],
585 653 _('hg shelve'))
586 654 def shelvecmd(ui, repo, *pats, **opts):
587 655 '''save and set aside changes from the working directory
588 656
589 657 Shelving takes files that "hg status" reports as not clean, saves
590 658 the modifications to a bundle (a shelved change), and reverts the
591 659 files so that their state in the working directory becomes clean.
592 660
593 661 To restore these changes to the working directory, using "hg
594 662 unshelve"; this will work even if you switch to a different
595 663 commit.
596 664
597 665 When no files are specified, "hg shelve" saves all not-clean
598 666 files. If specific files or directories are named, only changes to
599 667 those files are shelved.
600 668
601 669 Each shelved change has a name that makes it easier to find later.
602 670 The name of a shelved change defaults to being based on the active
603 671 bookmark, or if there is no active bookmark, the current named
604 672 branch. To specify a different name, use ``--name``.
605 673
606 674 To see a list of existing shelved changes, use the ``--list``
607 675 option. For each shelved change, this will print its name, age,
608 676 and description; use ``--patch`` or ``--stat`` for more details.
609 677
610 678 To delete specific shelved changes, use ``--delete``. To delete
611 679 all shelved changes, use ``--cleanup``.
612 680 '''
613 681 cmdutil.checkunfinished(repo)
614 682
615 683 def checkopt(opt, incompatible):
616 684 if opts[opt]:
617 685 for i in incompatible.split():
618 686 if opts[i]:
619 687 raise util.Abort(_("options '--%s' and '--%s' may not be "
620 688 "used together") % (opt, i))
621 689 return True
622 690 if checkopt('cleanup', 'addremove delete list message name patch stat'):
623 691 if pats:
624 692 raise util.Abort(_("cannot specify names when using '--cleanup'"))
625 693 return cleanupcmd(ui, repo)
626 694 elif checkopt('delete', 'addremove cleanup list message name patch stat'):
627 695 return deletecmd(ui, repo, pats)
628 696 elif checkopt('list', 'addremove cleanup delete message name'):
629 697 return listcmd(ui, repo, pats, opts)
630 698 else:
631 699 for i in ('patch', 'stat'):
632 700 if opts[i]:
633 701 raise util.Abort(_("option '--%s' may not be "
634 702 "used when shelving a change") % (i,))
635 703 return createcmd(ui, repo, pats, opts)
636 704
637 705 def extsetup(ui):
638 706 cmdutil.unfinishedstates.append(
639 707 [shelvedstate._filename, False, True, _('unshelve already in progress'),
640 708 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
@@ -1,486 +1,516 b''
1 1 $ echo "[extensions]" >> $HGRCPATH
2 2 $ echo "mq=" >> $HGRCPATH
3 3 $ echo "shelve=" >> $HGRCPATH
4 4 $ echo "[defaults]" >> $HGRCPATH
5 5 $ echo "diff = --nodates --git" >> $HGRCPATH
6 6 $ echo "qnew = --date '0 0'" >> $HGRCPATH
7 7
8 8 $ hg init repo
9 9 $ cd repo
10 10 $ mkdir a b
11 11 $ echo a > a/a
12 12 $ echo b > b/b
13 13 $ echo c > c
14 14 $ echo d > d
15 15 $ echo x > x
16 16 $ hg addremove -q
17 17
18 18 shelving in an empty repo should be possible
19 19
20 20 $ hg shelve
21 21 shelved as default
22 22 0 files updated, 0 files merged, 5 files removed, 0 files unresolved
23 23
24 24 $ hg unshelve
25 25 unshelving change 'default'
26 26 adding changesets
27 27 adding manifests
28 28 adding file changes
29 29 added 1 changesets with 5 changes to 5 files
30 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
31 30
32 31 $ hg commit -q -m 'initial commit'
33 32
34 33 $ hg shelve
35 34 nothing changed
36 35 [1]
37 36
38 37 create an mq patch - shelving should work fine with a patch applied
39 38
40 39 $ echo n > n
41 40 $ hg add n
42 41 $ hg commit n -m second
43 42 $ hg qnew second.patch
44 43
45 44 shelve a change that we will delete later
46 45
47 46 $ echo a >> a/a
48 47 $ hg shelve
49 48 shelved as default
50 49 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
51 50
52 51 set up some more complex changes to shelve
53 52
54 53 $ echo a >> a/a
55 54 $ hg mv b b.rename
56 55 moving b/b to b.rename/b (glob)
57 56 $ hg cp c c.copy
58 57 $ hg status -C
59 58 M a/a
60 59 A b.rename/b
61 60 b/b
62 61 A c.copy
63 62 c
64 63 R b/b
65 64
66 65 prevent some foot-shooting
67 66
68 67 $ hg shelve -n foo/bar
69 68 abort: shelved change names may not contain slashes
70 69 [255]
71 70 $ hg shelve -n .baz
72 71 abort: shelved change names may not start with '.'
73 72 [255]
74 73
75 74 the common case - no options or filenames
76 75
77 76 $ hg shelve
78 77 shelved as default-01
79 78 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
80 79 $ hg status -C
81 80
82 81 ensure that our shelved changes exist
83 82
84 83 $ hg shelve -l
85 84 default-01 (*) [mq]: second.patch (glob)
86 85 default (*) [mq]: second.patch (glob)
87 86
88 87 $ hg shelve -l -p default
89 88 default (*) [mq]: second.patch (glob)
90 89
91 90 diff --git a/a/a b/a/a
92 91 --- a/a/a
93 92 +++ b/a/a
94 93 @@ -1,1 +1,2 @@
95 94 a
96 95 +a
97 96
98 97 delete our older shelved change
99 98
100 99 $ hg shelve -d default
101 100 $ hg qfinish -a -q
102 101
103 local edits should prevent a shelved change from applying
102 local edits should not prevent a shelved change from applying
104 103
105 $ echo e>>a/a
106 $ hg unshelve
104 $ printf "z\na\n" > a/a
105 $ hg unshelve --keep
107 106 unshelving change 'default-01'
108 the following shelved files have been modified:
109 a/a
110 you must commit, revert, or shelve your changes before you can proceed
111 abort: cannot unshelve due to local changes
112
113 [255]
107 adding changesets
108 adding manifests
109 adding file changes
110 added 1 changesets with 3 changes to 8 files (+1 heads)
111 merging a/a
114 112
115 $ hg revert -C a/a
113 $ hg revert --all -q
114 $ rm a/a.orig b.rename/b c.copy
116 115
117 116 apply it and make sure our state is as expected
118 117
119 118 $ hg unshelve
120 119 unshelving change 'default-01'
121 120 adding changesets
122 121 adding manifests
123 122 adding file changes
124 123 added 1 changesets with 3 changes to 8 files
125 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
126 124 $ hg status -C
127 125 M a/a
128 126 A b.rename/b
129 127 b/b
130 128 A c.copy
131 129 c
132 130 R b/b
133 131 $ hg shelve -l
134 132
135 133 $ hg unshelve
136 134 abort: no shelved changes to apply!
137 135 [255]
138 136 $ hg unshelve foo
139 137 abort: shelved change 'foo' not found
140 138 [255]
141 139
142 140 named shelves, specific filenames, and "commit messages" should all work
143 141
144 142 $ hg status -C
145 143 M a/a
146 144 A b.rename/b
147 145 b/b
148 146 A c.copy
149 147 c
150 148 R b/b
151 149 $ hg shelve -q -n wibble -m wat a
152 150
153 151 expect "a" to no longer be present, but status otherwise unchanged
154 152
155 153 $ hg status -C
156 154 A b.rename/b
157 155 b/b
158 156 A c.copy
159 157 c
160 158 R b/b
161 159 $ hg shelve -l --stat
162 160 wibble (*) wat (glob)
163 161 a/a | 1 +
164 162 1 files changed, 1 insertions(+), 0 deletions(-)
165 163
166 164 and now "a/a" should reappear
167 165
168 166 $ cd a
169 167 $ hg unshelve -q wibble
170 168 $ cd ..
171 169 $ hg status -C
172 170 M a/a
173 171 A b.rename/b
174 172 b/b
175 173 A c.copy
176 174 c
177 175 R b/b
178 176
179 177 cause unshelving to result in a merge with 'a' conflicting
180 178
181 179 $ hg shelve -q
182 180 $ echo c>>a/a
183 181 $ hg commit -m second
184 182 $ hg tip --template '{files}\n'
185 183 a/a
186 184
187 185 add an unrelated change that should be preserved
188 186
189 187 $ mkdir foo
190 188 $ echo foo > foo/foo
191 189 $ hg add foo/foo
192 190
193 191 force a conflicted merge to occur
194 192
195 193 $ hg unshelve
196 194 unshelving change 'default'
197 195 adding changesets
198 196 adding manifests
199 197 adding file changes
200 198 added 1 changesets with 3 changes to 8 files (+1 heads)
201 199 merging a/a
202 200 warning: conflicts during merge.
203 201 merging a/a incomplete! (edit conflicts, then use 'hg resolve --mark')
204 2 files updated, 0 files merged, 1 files removed, 1 files unresolved
205 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
206 202 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
207 203 [1]
208 204
209 205 ensure that we have a merge with unresolved conflicts
210 206
211 $ hg heads -q
212 4:cebf2b8de087
213 3:2e69b451d1ea
214 $ hg parents -q
215 3:2e69b451d1ea
216 4:cebf2b8de087
207 $ hg heads -q --template '{rev}\n'
208 5
209 4
210 $ hg parents -q --template '{rev}\n'
211 4
212 5
217 213 $ hg status
218 214 M a/a
219 215 M b.rename/b
220 216 M c.copy
221 A foo/foo
222 217 R b/b
223 218 ? a/a.orig
224 219 $ hg diff
225 220 diff --git a/a/a b/a/a
226 221 --- a/a/a
227 222 +++ b/a/a
228 223 @@ -1,2 +1,6 @@
229 224 a
230 225 +<<<<<<< local
231 226 c
232 227 +=======
233 228 +a
234 229 +>>>>>>> other
235 230 diff --git a/b.rename/b b/b.rename/b
236 231 --- /dev/null
237 232 +++ b/b.rename/b
238 233 @@ -0,0 +1,1 @@
239 234 +b
240 235 diff --git a/b/b b/b/b
241 236 deleted file mode 100644
242 237 --- a/b/b
243 238 +++ /dev/null
244 239 @@ -1,1 +0,0 @@
245 240 -b
246 241 diff --git a/c.copy b/c.copy
247 242 --- /dev/null
248 243 +++ b/c.copy
249 244 @@ -0,0 +1,1 @@
250 245 +c
251 diff --git a/foo/foo b/foo/foo
252 new file mode 100644
253 --- /dev/null
254 +++ b/foo/foo
255 @@ -0,0 +1,1 @@
256 +foo
257 246 $ hg resolve -l
258 247 U a/a
259 248
260 249 $ hg shelve
261 250 abort: unshelve already in progress
262 251 (use 'hg unshelve --continue' or 'hg unshelve --abort')
263 252 [255]
264 253
265 254 abort the unshelve and be happy
266 255
267 256 $ hg status
268 257 M a/a
269 258 M b.rename/b
270 259 M c.copy
271 A foo/foo
272 260 R b/b
273 261 ? a/a.orig
274 262 $ hg unshelve -a
263 rebase aborted
275 264 unshelve of 'default' aborted
276 265 $ hg heads -q
277 266 3:2e69b451d1ea
278 267 $ hg parents
279 268 changeset: 3:2e69b451d1ea
280 269 tag: tip
281 270 user: test
282 271 date: Thu Jan 01 00:00:00 1970 +0000
283 272 summary: second
284 273
285 274 $ hg resolve -l
286 275 $ hg status
287 276 A foo/foo
288 277 ? a/a.orig
289 278
290 279 try to continue with no unshelve underway
291 280
292 281 $ hg unshelve -c
293 282 abort: no unshelve operation underway
294 283 [255]
295 284 $ hg status
296 285 A foo/foo
297 286 ? a/a.orig
298 287
299 288 redo the unshelve to get a conflict
300 289
301 290 $ hg unshelve -q
302 291 warning: conflicts during merge.
303 292 merging a/a incomplete! (edit conflicts, then use 'hg resolve --mark')
304 293 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
305 294 [1]
306 295
307 296 attempt to continue
308 297
309 298 $ hg unshelve -c
310 299 abort: unresolved conflicts, can't continue
311 300 (see 'hg resolve', then 'hg unshelve --continue')
312 301 [255]
313 302
314 303 $ hg revert -r . a/a
315 304 $ hg resolve -m a/a
316 305
317 306 $ hg unshelve -c
318 307 unshelve of 'default' complete
319 308
320 309 ensure the repo is as we hope
321 310
322 311 $ hg parents
323 312 changeset: 3:2e69b451d1ea
324 313 tag: tip
325 314 user: test
326 315 date: Thu Jan 01 00:00:00 1970 +0000
327 316 summary: second
328 317
329 318 $ hg heads -q
330 319 3:2e69b451d1ea
331 320
332 321 $ hg status -C
333 M b.rename/b
322 A b.rename/b
334 323 b/b
335 M c.copy
324 A c.copy
336 325 c
337 326 A foo/foo
338 327 R b/b
339 328 ? a/a.orig
340 329
341 330 there should be no shelves left
342 331
343 332 $ hg shelve -l
344 333
345 334 #if execbit
346 335
347 336 ensure that metadata-only changes are shelved
348 337
349 338 $ chmod +x a/a
350 339 $ hg shelve -q -n execbit a/a
351 340 $ hg status a/a
352 341 $ hg unshelve -q execbit
353 342 $ hg status a/a
354 343 M a/a
355 344 $ hg revert a/a
356 345
357 346 #endif
358 347
359 348 #if symlink
360 349
361 350 $ rm a/a
362 351 $ ln -s foo a/a
363 352 $ hg shelve -q -n symlink a/a
364 353 $ hg status a/a
365 354 $ hg unshelve -q symlink
366 355 $ hg status a/a
367 356 M a/a
368 357 $ hg revert a/a
369 358
370 359 #endif
371 360
372 361 set up another conflict between a commit and a shelved change
373 362
374 363 $ hg revert -q -C -a
364 $ rm a/a.orig b.rename/b c.copy
375 365 $ echo a >> a/a
376 366 $ hg shelve -q
377 367 $ echo x >> a/a
378 368 $ hg ci -m 'create conflict'
379 369 $ hg add foo/foo
380 370
381 371 if we resolve a conflict while unshelving, the unshelve should succeed
382 372
383 373 $ HGMERGE=true hg unshelve
384 374 unshelving change 'default'
385 375 adding changesets
386 376 adding manifests
387 377 adding file changes
388 378 added 1 changesets with 1 changes to 6 files (+1 heads)
389 379 merging a/a
390 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
391 380 $ hg parents -q
392 381 4:33f7f61e6c5e
393 382 $ hg shelve -l
394 383 $ hg status
395 384 A foo/foo
396 385 $ cat a/a
397 386 a
398 387 c
399 388 x
400 389
401 390 test keep and cleanup
402 391
403 392 $ hg shelve
404 393 shelved as default
405 394 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
406 395 $ hg shelve --list
407 396 default (*) create conflict (glob)
408 397 $ hg unshelve --keep
409 398 unshelving change 'default'
410 399 adding changesets
411 400 adding manifests
412 401 adding file changes
413 402 added 1 changesets with 1 changes to 7 files
414 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
415 403 $ hg shelve --list
416 404 default (*) create conflict (glob)
417 405 $ hg shelve --cleanup
418 406 $ hg shelve --list
419 407
420 408 test bookmarks
421 409
422 410 $ hg bookmark test
423 411 $ hg bookmark
424 412 * test 4:33f7f61e6c5e
425 413 $ hg shelve
426 414 shelved as test
427 415 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
428 416 $ hg bookmark
429 417 * test 4:33f7f61e6c5e
430 418 $ hg unshelve
431 419 unshelving change 'test'
432 420 adding changesets
433 421 adding manifests
434 422 adding file changes
435 423 added 1 changesets with 1 changes to 7 files
436 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
437 424 $ hg bookmark
438 425 * test 4:33f7f61e6c5e
439 426
440 427 shelve should still work even if mq is disabled
441 428
442 429 $ hg --config extensions.mq=! shelve
443 430 shelved as test
444 431 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
445 432 $ hg --config extensions.mq=! shelve --list
446 433 test (1s ago) create conflict
447 434 $ hg --config extensions.mq=! unshelve
448 435 unshelving change 'test'
449 436 adding changesets
450 437 adding manifests
451 438 adding file changes
452 439 added 1 changesets with 1 changes to 7 files
453 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
454 440
455 441 shelve should leave dirstate clean (issue 4055)
456 442
457 443 $ cd ..
458 444 $ hg init shelverebase
459 445 $ cd shelverebase
460 446 $ printf 'x\ny\n' > x
461 447 $ echo z > z
462 448 $ hg commit -Aqm xy
463 449 $ echo z >> x
464 450 $ hg commit -Aqm z
465 451 $ hg up 0
466 452 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
467 453 $ printf 'a\nx\ny\nz\n' > x
468 454 $ hg commit -Aqm xyz
469 455 $ echo c >> z
470 456 $ hg shelve
471 457 shelved as default
472 458 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
473 459 $ hg rebase -d 1 --config extensions.rebase=
474 460 merging x
475 461 saved backup bundle to $TESTTMP/shelverebase/.hg/strip-backup/323bfa07f744-backup.hg (glob)
476 462 $ hg unshelve
477 463 unshelving change 'default'
478 464 adding changesets
479 465 adding manifests
480 466 adding file changes
481 467 added 2 changesets with 2 changes to 2 files (+1 heads)
482 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
483 468 $ hg status
484 469 M z
485 470
486 471 $ cd ..
472
473 shelve should only unshelve pending changes (issue 4068)
474
475 $ hg init onlypendingchanges
476 $ cd onlypendingchanges
477 $ touch a
478 $ hg ci -Aqm a
479 $ touch b
480 $ hg ci -Aqm b
481 $ hg up -q 0
482 $ touch c
483 $ hg ci -Aqm c
484
485 $ touch d
486 $ hg add d
487 $ hg shelve
488 shelved as default
489 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
490 $ hg up -q 1
491 $ hg unshelve
492 unshelving change 'default'
493 adding changesets
494 adding manifests
495 adding file changes
496 added 1 changesets with 1 changes to 3 files
497 $ hg status
498 A d
499
500 unshelve should work on an ancestor of the original commit
501
502 $ hg shelve
503 shelved as default
504 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
505 $ hg up 0
506 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
507 $ hg unshelve
508 unshelving change 'default'
509 adding changesets
510 adding manifests
511 adding file changes
512 added 1 changesets with 1 changes to 3 files
513 $ hg status
514 A d
515
516 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now