25 import itertools |
25 import itertools |
26 from mercurial.i18n import _ |
26 from mercurial.i18n import _ |
27 from mercurial.node import nullid, nullrev, bin, hex |
27 from mercurial.node import nullid, nullrev, bin, hex |
28 from mercurial import changegroup, cmdutil, scmutil, phases, commands |
28 from mercurial import changegroup, cmdutil, scmutil, phases, commands |
29 from mercurial import error, hg, mdiff, merge, patch, repair, util |
29 from mercurial import error, hg, mdiff, merge, patch, repair, util |
30 from mercurial import templatefilters, exchange, bundlerepo |
30 from mercurial import templatefilters, exchange, bundlerepo, bundle2 |
31 from mercurial import lock as lockmod |
31 from mercurial import lock as lockmod |
32 from hgext import rebase |
32 from hgext import rebase |
33 import errno |
33 import errno |
34 |
34 |
35 cmdtable = {} |
35 cmdtable = {} |
88 try: |
88 try: |
89 return self.vfs(self.fname, mode) |
89 return self.vfs(self.fname, mode) |
90 except IOError as err: |
90 except IOError as err: |
91 if err.errno != errno.ENOENT: |
91 if err.errno != errno.ENOENT: |
92 raise |
92 raise |
93 raise util.Abort(_("shelved change '%s' not found") % self.name) |
93 raise error.Abort(_("shelved change '%s' not found") % self.name) |
94 |
94 |
95 def applybundle(self): |
95 def applybundle(self): |
96 fp = self.opener() |
96 fp = self.opener() |
97 try: |
97 try: |
98 gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs) |
98 gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs) |
99 changegroup.addchangegroup(self.repo, gen, 'unshelve', |
99 if not isinstance(gen, bundle2.unbundle20): |
100 'bundle:' + self.vfs.join(self.fname), |
100 gen.apply(self.repo, 'unshelve', |
101 targetphase=phases.secret) |
101 'bundle:' + self.vfs.join(self.fname), |
|
102 targetphase=phases.secret) |
|
103 if isinstance(gen, bundle2.unbundle20): |
|
104 bundle2.applybundle(self.repo, gen, |
|
105 self.repo.currenttransaction(), |
|
106 source='unshelve', |
|
107 url='bundle:' + self.vfs.join(self.fname)) |
102 finally: |
108 finally: |
103 fp.close() |
109 fp.close() |
104 |
110 |
105 def bundlerepo(self): |
111 def bundlerepo(self): |
106 return bundlerepo.bundlerepository(self.repo.baseui, self.repo.root, |
112 return bundlerepo.bundlerepository(self.repo.baseui, self.repo.root, |
107 self.vfs.join(self.fname)) |
113 self.vfs.join(self.fname)) |
108 def writebundle(self, cg): |
114 def writebundle(self, bases, node): |
109 changegroup.writebundle(self.ui, cg, self.fname, 'HG10UN', self.vfs) |
115 btype = 'HG10BZ' |
|
116 cgversion = '01' |
|
117 compression = None |
|
118 if 'generaldelta' in self.repo.requirements: |
|
119 btype = 'HG20' |
|
120 cgversion = '02' |
|
121 compression = 'BZ' |
|
122 |
|
123 cg = changegroup.changegroupsubset(self.repo, bases, [node], 'shelve', |
|
124 version=cgversion) |
|
125 changegroup.writebundle(self.ui, cg, self.fname, btype, self.vfs, |
|
126 compression=compression) |
110 |
127 |
111 class shelvedstate(object): |
128 class shelvedstate(object): |
112 """Handle persistence during unshelving operations. |
129 """Handle persistence during unshelving operations. |
113 |
130 |
114 Handles saving and restoring a shelved state. Ensures that different |
131 Handles saving and restoring a shelved state. Ensures that different |
122 fp = repo.vfs(cls._filename) |
139 fp = repo.vfs(cls._filename) |
123 try: |
140 try: |
124 version = int(fp.readline().strip()) |
141 version = int(fp.readline().strip()) |
125 |
142 |
126 if version != cls._version: |
143 if version != cls._version: |
127 raise util.Abort(_('this version of shelve is incompatible ' |
144 raise error.Abort(_('this version of shelve is incompatible ' |
128 'with the version used in this repo')) |
145 'with the version used in this repo')) |
129 name = fp.readline().strip() |
146 name = fp.readline().strip() |
130 wctx = fp.readline().strip() |
147 wctx = fp.readline().strip() |
131 pendingctx = fp.readline().strip() |
148 pendingctx = fp.readline().strip() |
132 parents = [bin(h) for h in fp.readline().split()] |
149 parents = [bin(h) for h in fp.readline().split()] |
177 vfs.unlink(base + '.' + ext) |
194 vfs.unlink(base + '.' + ext) |
178 except OSError as err: |
195 except OSError as err: |
179 if err.errno != errno.ENOENT: |
196 if err.errno != errno.ENOENT: |
180 raise |
197 raise |
181 |
198 |
|
199 def _aborttransaction(repo): |
|
200 '''Abort current transaction for shelve/unshelve, but keep dirstate |
|
201 ''' |
|
202 backupname = 'dirstate.shelve' |
|
203 dirstatebackup = None |
|
204 try: |
|
205 # create backup of (un)shelved dirstate, because aborting transaction |
|
206 # should restore dirstate to one at the beginning of the |
|
207 # transaction, which doesn't include the result of (un)shelving |
|
208 fp = repo.vfs.open(backupname, "w") |
|
209 dirstatebackup = backupname |
|
210 # clearing _dirty/_dirtypl of dirstate by _writedirstate below |
|
211 # is unintentional. but it doesn't cause problem in this case, |
|
212 # because no code path refers them until transaction is aborted. |
|
213 repo.dirstate._writedirstate(fp) # write in-memory changes forcibly |
|
214 |
|
215 tr = repo.currenttransaction() |
|
216 tr.abort() |
|
217 |
|
218 # restore to backuped dirstate |
|
219 repo.vfs.rename(dirstatebackup, 'dirstate') |
|
220 dirstatebackup = None |
|
221 finally: |
|
222 if dirstatebackup: |
|
223 repo.vfs.unlink(dirstatebackup) |
|
224 |
182 def createcmd(ui, repo, pats, opts): |
225 def createcmd(ui, repo, pats, opts): |
183 """subcommand that creates a new shelve""" |
226 """subcommand that creates a new shelve""" |
184 |
227 |
185 def publicancestors(ctx): |
228 def mutableancestors(ctx): |
186 """Compute the public ancestors of a commit. |
229 """return all mutable ancestors for ctx (included) |
187 |
230 |
188 Much faster than the revset ancestors(ctx) & draft()""" |
231 Much faster than the revset ancestors(ctx) & draft()""" |
189 seen = set([nullrev]) |
232 seen = set([nullrev]) |
190 visit = collections.deque() |
233 visit = collections.deque() |
191 visit.append(ctx) |
234 visit.append(ctx) |
240 if not opts['message']: |
283 if not opts['message']: |
241 opts['message'] = desc |
284 opts['message'] = desc |
242 |
285 |
243 name = opts['name'] |
286 name = opts['name'] |
244 |
287 |
245 wlock = lock = tr = bms = None |
288 wlock = lock = tr = None |
246 try: |
289 try: |
247 wlock = repo.wlock() |
290 wlock = repo.wlock() |
248 lock = repo.lock() |
291 lock = repo.lock() |
249 |
292 |
250 bms = repo._bookmarks.copy() |
|
251 # use an uncommitted transaction to generate the bundle to avoid |
293 # use an uncommitted transaction to generate the bundle to avoid |
252 # pull races. ensure we don't print the abort message to stderr. |
294 # pull races. ensure we don't print the abort message to stderr. |
253 tr = repo.transaction('commit', report=lambda x: None) |
295 tr = repo.transaction('commit', report=lambda x: None) |
254 |
296 |
255 if name: |
297 if name: |
256 if shelvedfile(repo, name, 'hg').exists(): |
298 if shelvedfile(repo, name, 'hg').exists(): |
257 raise util.Abort(_("a shelved change named '%s' already exists") |
299 raise error.Abort(_("a shelved change named '%s' already exists" |
258 % name) |
300 ) % name) |
259 else: |
301 else: |
260 for n in gennames(): |
302 for n in gennames(): |
261 if not shelvedfile(repo, n, 'hg').exists(): |
303 if not shelvedfile(repo, n, 'hg').exists(): |
262 name = n |
304 name = n |
263 break |
305 break |
264 else: |
306 else: |
265 raise util.Abort(_("too many shelved changes named '%s'") % |
307 raise error.Abort(_("too many shelved changes named '%s'") % |
266 label) |
308 label) |
267 |
309 |
268 # ensure we are not creating a subdirectory or a hidden file |
310 # ensure we are not creating a subdirectory or a hidden file |
269 if '/' in name or '\\' in name: |
311 if '/' in name or '\\' in name: |
270 raise util.Abort(_('shelved change names may not contain slashes')) |
312 raise error.Abort(_('shelved change names may not contain slashes')) |
271 if name.startswith('.'): |
313 if name.startswith('.'): |
272 raise util.Abort(_("shelved change names may not start with '.'")) |
314 raise error.Abort(_("shelved change names may not start with '.'")) |
273 interactive = opts.get('interactive', False) |
315 interactive = opts.get('interactive', False) |
274 |
316 |
275 def interactivecommitfunc(ui, repo, *pats, **opts): |
317 def interactivecommitfunc(ui, repo, *pats, **opts): |
276 match = scmutil.match(repo['.'], pats, {}) |
318 match = scmutil.match(repo['.'], pats, {}) |
277 message = opts['message'] |
319 message = opts['message'] |
288 "'hg status')\n") % len(stat.deleted)) |
330 "'hg status')\n") % len(stat.deleted)) |
289 else: |
331 else: |
290 ui.status(_("nothing changed\n")) |
332 ui.status(_("nothing changed\n")) |
291 return 1 |
333 return 1 |
292 |
334 |
293 bases = list(publicancestors(repo[node])) |
335 bases = list(mutableancestors(repo[node])) |
294 cg = changegroup.changegroupsubset(repo, bases, [node], 'shelve') |
336 shelvedfile(repo, name, 'hg').writebundle(bases, node) |
295 shelvedfile(repo, name, 'hg').writebundle(cg) |
|
296 cmdutil.export(repo, [node], |
337 cmdutil.export(repo, [node], |
297 fp=shelvedfile(repo, name, 'patch').opener('wb'), |
338 fp=shelvedfile(repo, name, 'patch').opener('wb'), |
298 opts=mdiff.diffopts(git=True)) |
339 opts=mdiff.diffopts(git=True)) |
299 |
340 |
300 |
341 |
301 if ui.formatted(): |
342 if ui.formatted(): |
302 desc = util.ellipsis(desc, ui.termwidth()) |
343 desc = util.ellipsis(desc, ui.termwidth()) |
303 ui.status(_('shelved as %s\n') % name) |
344 ui.status(_('shelved as %s\n') % name) |
304 hg.update(repo, parent.node()) |
345 hg.update(repo, parent.node()) |
|
346 |
|
347 _aborttransaction(repo) |
305 finally: |
348 finally: |
306 if bms: |
349 lockmod.release(tr, lock, wlock) |
307 # restore old bookmarks |
|
308 repo._bookmarks.update(bms) |
|
309 repo._bookmarks.write() |
|
310 if tr: |
|
311 tr.abort() |
|
312 lockmod.release(lock, wlock) |
|
313 |
350 |
314 def cleanupcmd(ui, repo): |
351 def cleanupcmd(ui, repo): |
315 """subcommand that deletes all shelves""" |
352 """subcommand that deletes all shelves""" |
316 |
353 |
317 wlock = None |
354 wlock = None |
326 lockmod.release(wlock) |
363 lockmod.release(wlock) |
327 |
364 |
328 def deletecmd(ui, repo, pats): |
365 def deletecmd(ui, repo, pats): |
329 """subcommand that deletes a specific shelve""" |
366 """subcommand that deletes a specific shelve""" |
330 if not pats: |
367 if not pats: |
331 raise util.Abort(_('no shelved changes specified!')) |
368 raise error.Abort(_('no shelved changes specified!')) |
332 wlock = repo.wlock() |
369 wlock = repo.wlock() |
333 try: |
370 try: |
334 for name in pats: |
371 for name in pats: |
335 for suffix in 'hg patch'.split(): |
372 for suffix in 'hg patch'.split(): |
336 shelvedfile(repo, name, suffix).movetobackup() |
373 shelvedfile(repo, name, suffix).movetobackup() |
337 cleanupoldbackups(repo) |
374 cleanupoldbackups(repo) |
338 except OSError as err: |
375 except OSError as err: |
339 if err.errno != errno.ENOENT: |
376 if err.errno != errno.ENOENT: |
340 raise |
377 raise |
341 raise util.Abort(_("shelved change '%s' not found") % name) |
378 raise error.Abort(_("shelved change '%s' not found") % name) |
342 finally: |
379 finally: |
343 lockmod.release(wlock) |
380 lockmod.release(wlock) |
344 |
381 |
345 def listshelves(repo): |
382 def listshelves(repo): |
346 """return all shelves in repo as list of (time, filename)""" |
383 """return all shelves in repo as list of (time, filename)""" |
408 fp.close() |
445 fp.close() |
409 |
446 |
410 def singlepatchcmds(ui, repo, pats, opts, subcommand): |
447 def singlepatchcmds(ui, repo, pats, opts, subcommand): |
411 """subcommand that displays a single shelf""" |
448 """subcommand that displays a single shelf""" |
412 if len(pats) != 1: |
449 if len(pats) != 1: |
413 raise util.Abort(_("--%s expects a single shelf") % subcommand) |
450 raise error.Abort(_("--%s expects a single shelf") % subcommand) |
414 shelfname = pats[0] |
451 shelfname = pats[0] |
415 |
452 |
416 if not shelvedfile(repo, shelfname, 'patch').exists(): |
453 if not shelvedfile(repo, shelfname, 'patch').exists(): |
417 raise util.Abort(_("cannot find shelf %s") % shelfname) |
454 raise error.Abort(_("cannot find shelf %s") % shelfname) |
418 |
455 |
419 listcmd(ui, repo, pats, opts) |
456 listcmd(ui, repo, pats, opts) |
420 |
457 |
421 def checkparents(repo, state): |
458 def checkparents(repo, state): |
422 """check parent while resuming an unshelve""" |
459 """check parent while resuming an unshelve""" |
423 if state.parents != repo.dirstate.parents(): |
460 if state.parents != repo.dirstate.parents(): |
424 raise util.Abort(_('working directory parents do not match unshelve ' |
461 raise error.Abort(_('working directory parents do not match unshelve ' |
425 'state')) |
462 'state')) |
426 |
463 |
427 def pathtofiles(repo, files): |
464 def pathtofiles(repo, files): |
428 cwd = repo.getcwd() |
465 cwd = repo.getcwd() |
429 return [repo.pathto(f, cwd) for f in files] |
466 return [repo.pathto(f, cwd) for f in files] |
449 lock = repo.lock() |
486 lock = repo.lock() |
450 |
487 |
451 mergefiles(ui, repo, state.wctx, state.pendingctx) |
488 mergefiles(ui, repo, state.wctx, state.pendingctx) |
452 |
489 |
453 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve') |
490 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve') |
|
491 finally: |
454 shelvedstate.clear(repo) |
492 shelvedstate.clear(repo) |
455 ui.warn(_("unshelve of '%s' aborted\n") % state.name) |
493 ui.warn(_("unshelve of '%s' aborted\n") % state.name) |
456 finally: |
|
457 lockmod.release(lock, wlock) |
494 lockmod.release(lock, wlock) |
458 |
495 |
459 def mergefiles(ui, repo, wctx, shelvectx): |
496 def mergefiles(ui, repo, wctx, shelvectx): |
460 """updates to wctx and merges the changes from shelvectx into the |
497 """updates to wctx and merges the changes from shelvectx into the |
461 dirstate.""" |
498 dirstate.""" |
494 lock = None |
531 lock = None |
495 try: |
532 try: |
496 checkparents(repo, state) |
533 checkparents(repo, state) |
497 ms = merge.mergestate(repo) |
534 ms = merge.mergestate(repo) |
498 if [f for f in ms if ms[f] == 'u']: |
535 if [f for f in ms if ms[f] == 'u']: |
499 raise util.Abort( |
536 raise error.Abort( |
500 _("unresolved conflicts, can't continue"), |
537 _("unresolved conflicts, can't continue"), |
501 hint=_("see 'hg resolve', then 'hg unshelve --continue'")) |
538 hint=_("see 'hg resolve', then 'hg unshelve --continue'")) |
502 |
539 |
503 lock = repo.lock() |
540 lock = repo.lock() |
504 |
541 |
577 if not abortf and not continuef: |
614 if not abortf and not continuef: |
578 cmdutil.checkunfinished(repo) |
615 cmdutil.checkunfinished(repo) |
579 |
616 |
580 if abortf or continuef: |
617 if abortf or continuef: |
581 if abortf and continuef: |
618 if abortf and continuef: |
582 raise util.Abort(_('cannot use both abort and continue')) |
619 raise error.Abort(_('cannot use both abort and continue')) |
583 if shelved: |
620 if shelved: |
584 raise util.Abort(_('cannot combine abort/continue with ' |
621 raise error.Abort(_('cannot combine abort/continue with ' |
585 'naming a shelved change')) |
622 'naming a shelved change')) |
586 |
623 |
587 try: |
624 try: |
588 state = shelvedstate.load(repo) |
625 state = shelvedstate.load(repo) |
589 except IOError as err: |
626 except IOError as err: |
590 if err.errno != errno.ENOENT: |
627 if err.errno != errno.ENOENT: |
591 raise |
628 raise |
592 raise util.Abort(_('no unshelve operation underway')) |
629 raise error.Abort(_('no unshelve operation underway')) |
593 |
630 |
594 if abortf: |
631 if abortf: |
595 return unshelveabort(ui, repo, state, opts) |
632 return unshelveabort(ui, repo, state, opts) |
596 elif continuef: |
633 elif continuef: |
597 return unshelvecontinue(ui, repo, state, opts) |
634 return unshelvecontinue(ui, repo, state, opts) |
598 elif len(shelved) > 1: |
635 elif len(shelved) > 1: |
599 raise util.Abort(_('can only unshelve one change at a time')) |
636 raise error.Abort(_('can only unshelve one change at a time')) |
600 elif not shelved: |
637 elif not shelved: |
601 shelved = listshelves(repo) |
638 shelved = listshelves(repo) |
602 if not shelved: |
639 if not shelved: |
603 raise util.Abort(_('no shelved changes to apply!')) |
640 raise error.Abort(_('no shelved changes to apply!')) |
604 basename = util.split(shelved[0][1])[1] |
641 basename = util.split(shelved[0][1])[1] |
605 ui.status(_("unshelving change '%s'\n") % basename) |
642 ui.status(_("unshelving change '%s'\n") % basename) |
606 else: |
643 else: |
607 basename = shelved[0] |
644 basename = shelved[0] |
608 |
645 |
609 if not shelvedfile(repo, basename, 'patch').exists(): |
646 if not shelvedfile(repo, basename, 'patch').exists(): |
610 raise util.Abort(_("shelved change '%s' not found") % basename) |
647 raise error.Abort(_("shelved change '%s' not found") % basename) |
611 |
648 |
612 oldquiet = ui.quiet |
649 oldquiet = ui.quiet |
613 wlock = lock = tr = None |
650 wlock = lock = tr = None |
614 try: |
651 try: |
615 wlock = repo.wlock() |
652 wlock = repo.wlock() |
698 # hooks still fire and try to operate on the missing commits. |
735 # hooks still fire and try to operate on the missing commits. |
699 # Clean up manually to prevent this. |
736 # Clean up manually to prevent this. |
700 repo.unfiltered().changelog.strip(oldtiprev, tr) |
737 repo.unfiltered().changelog.strip(oldtiprev, tr) |
701 |
738 |
702 unshelvecleanup(ui, repo, basename, opts) |
739 unshelvecleanup(ui, repo, basename, opts) |
|
740 |
|
741 _aborttransaction(repo) |
703 finally: |
742 finally: |
704 ui.quiet = oldquiet |
743 ui.quiet = oldquiet |
705 if tr: |
744 if tr: |
706 tr.release() |
745 tr.release() |
707 lockmod.release(lock, wlock) |
746 lockmod.release(lock, wlock) |
773 ] |
812 ] |
774 def checkopt(opt): |
813 def checkopt(opt): |
775 if opts[opt]: |
814 if opts[opt]: |
776 for i, allowable in allowables: |
815 for i, allowable in allowables: |
777 if opts[i] and opt not in allowable: |
816 if opts[i] and opt not in allowable: |
778 raise util.Abort(_("options '--%s' and '--%s' may not be " |
817 raise error.Abort(_("options '--%s' and '--%s' may not be " |
779 "used together") % (opt, i)) |
818 "used together") % (opt, i)) |
780 return True |
819 return True |
781 if checkopt('cleanup'): |
820 if checkopt('cleanup'): |
782 if pats: |
821 if pats: |
783 raise util.Abort(_("cannot specify names when using '--cleanup'")) |
822 raise error.Abort(_("cannot specify names when using '--cleanup'")) |
784 return cleanupcmd(ui, repo) |
823 return cleanupcmd(ui, repo) |
785 elif checkopt('delete'): |
824 elif checkopt('delete'): |
786 return deletecmd(ui, repo, pats) |
825 return deletecmd(ui, repo, pats) |
787 elif checkopt('list'): |
826 elif checkopt('list'): |
788 return listcmd(ui, repo, pats, opts) |
827 return listcmd(ui, repo, pats, opts) |