def _commitworkingcopychanges(ui, repo, opts, tmpwctx): """Temporarily commit working copy changes before moving unshelve commit""" # Store pending changes in a commit and remember added in case a shelve # contains unknown files that are part of the pending change s = repo.status() addedbefore = frozenset(s.added) if not (s.modified or s.added or s.removed): return tmpwctx, addedbefore ui.status(_("temporarily committing pending changes " "(restore with 'hg unshelve --abort')\n")) commitfunc = getcommitfunc(extra=None, interactive=False, editor=False) tempopts = {} tempopts['message'] = "pending changes temporary commit" tempopts['date'] = opts.get('date') with ui.configoverride({('ui', 'quiet'): True}): node = cmdutil.commit(ui, repo, commitfunc, [], tempopts) tmpwctx = repo[node] return tmpwctx, addedbefore
def _commitworkingcopychanges(ui, repo, opts, tmpwctx): """Temporarily commit working copy changes before moving unshelve commit""" # Store pending changes in a commit and remember added in case a shelve # contains unknown files that are part of the pending change s = repo.status() addedbefore = frozenset(s.added) if not (s.modified or s.added or s.removed): return tmpwctx, addedbefore ui.status(_("temporarily committing pending changes " "(restore with 'hg unshelve --abort')\n")) commitfunc = getcommitfunc(extra=None, interactive=False, editor=False) tempopts = {} tempopts['message'] = "pending changes temporary commit" tempopts['date'] = opts.get('date') with ui.configoverride({('ui', 'quiet'): True}): node = cmdutil.commit(ui, repo, commitfunc, [], tempopts) tmpwctx = repo[node] ui.debug("temporary working copy commit: %s:%s\n" % (tmpwctx.rev(), nodemod.short(node))) return tmpwctx, addedbefore
def createcmd(ui, repo, pats, opts): """subcommand that creates a new shelve""" def publicancestors(ctx): """Compute the public ancestors of a commit. Much faster than the revset ancestors(ctx) & draft()""" seen = set([nullrev]) visit = util.deque() visit.append(ctx) while visit: ctx = visit.popleft() yield ctx.node() for parent in ctx.parents(): rev = parent.rev() if rev not in seen: seen.add(rev) if parent.mutable(): visit.append(parent) wctx = repo[None] parents = wctx.parents() if len(parents) > 1: raise util.Abort(_('cannot shelve while merging')) parent = parents[0] # we never need the user, so we use a generic user for all shelve operations user = '******' label = repo._bookmarkcurrent or parent.branch() or 'default' # slashes aren't allowed in filenames, therefore we rename it label = label.replace('/', '_') def gennames(): yield label for i in xrange(1, 100): yield '%s-%02d' % (label, i) shelvedfiles = [] def commitfunc(ui, repo, message, match, opts): # check modified, added, removed, deleted only for flist in repo.status(match=match)[:4]: shelvedfiles.extend(flist) hasmq = util.safehasattr(repo, 'mq') if hasmq: saved, repo.mq.checkapplied = repo.mq.checkapplied, False try: return repo.commit(message, user, opts.get('date'), match, editor=cmdutil.getcommiteditor(**opts)) finally: if hasmq: repo.mq.checkapplied = saved if parent.node() != nullid: desc = "changes to '%s'" % parent.description().split('\n', 1)[0] else: desc = '(changes in empty repository)' if not opts['message']: opts['message'] = desc name = opts['name'] wlock = lock = tr = bms = None try: wlock = repo.wlock() lock = repo.lock() bms = repo._bookmarks.copy() # use an uncommitted transaction to generate the bundle to avoid # pull races. ensure we don't print the abort message to stderr. tr = repo.transaction('commit', report=lambda x: None) if name: if shelvedfile(repo, name, 'hg').exists(): raise util.Abort(_("a shelved change named '%s' already exists") % name) else: for n in gennames(): if not shelvedfile(repo, n, 'hg').exists(): name = n break else: raise util.Abort(_("too many shelved changes named '%s'") % label) # ensure we are not creating a subdirectory or a hidden file if '/' in name or '\\' in name: raise util.Abort(_('shelved change names may not contain slashes')) if name.startswith('.'): raise util.Abort(_("shelved change names may not start with '.'")) node = cmdutil.commit(ui, repo, commitfunc, pats, opts) if not node: stat = repo.status(match=scmutil.match(repo[None], pats, opts)) if stat[3]: ui.status(_("nothing changed (%d missing files, see " "'hg status')\n") % len(stat[3])) else: ui.status(_("nothing changed\n")) return 1 phases.retractboundary(repo, phases.secret, [node]) fp = shelvedfile(repo, name, 'files').opener('wb') fp.write('\0'.join(shelvedfiles)) bases = list(publicancestors(repo[node])) cg = changegroup.changegroupsubset(repo, bases, [node], 'shelve') shelvedfile(repo, name, 'hg').writebundle(cg) cmdutil.export(repo, [node], fp=shelvedfile(repo, name, 'patch').opener('wb'), opts=mdiff.diffopts(git=True)) if ui.formatted(): desc = util.ellipsis(desc, ui.termwidth()) ui.status(_('shelved as %s\n') % name) hg.update(repo, parent.node()) finally: if bms: # restore old bookmarks repo._bookmarks.update(bms) repo._bookmarks.write() if tr: tr.abort() lockmod.release(lock, wlock)
hasmq = util.safehasattr(repo, 'mq') if hasmq: saved, repo.mq.checkapplied = repo.mq.checkapplied, False try: return repo.commit(message, 'shelve@localhost', opts.get('date'), match) finally: if hasmq: repo.mq.checkapplied = saved tempopts = {} tempopts['message'] = "pending changes temporary commit" tempopts['date'] = opts.get('date') ui.quiet = True node = cmdutil.commit(ui, repo, commitfunc, [], tempopts) tmpwctx = repo[node] ui.quiet = True shelvedfile(repo, basename, 'hg').applybundle() nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)] phases.retractboundary(repo, phases.secret, nodes) ui.quiet = oldquiet shelvectx = repo['tip'] # If the shelve is not immediately on top of the commit # we'll be merging with, rebase it to be on top. if tmpwctx.node() != shelvectx.parents()[0].node(): ui.status(_('rebasing shelved changes\n'))
def shelve(ui, repo, *pats, **opts): '''interactively select changes to set aside If a list of files is omitted, all changes reported by "hg status" will be candidates for shelving. You will be prompted for whether to shelve changes to each modified file, and for files with multiple changes, for each change to use. The shelve command works with the Color extension to display diffs in color. On each prompt, the following responses are possible: y - shelve this change n - skip this change s - skip remaining changes to this file f - shelve remaining changes to this file d - done, skip remaining changes and files a - shelve all changes to all remaining files q - quit, shelving no changes ? - display help''' if not ui.interactive: raise util.Abort(_('shelve can only be run interactively')) # List all the active shelves by name and return ' if opts['list']: listshelves(ui,repo) return forced = opts['force'] or opts['append'] # Shelf name and path shelfname = opts.get('name') shelfpath = getshelfpath(repo, shelfname) if os.path.exists(repo.join(shelfpath)) and not forced: raise util.Abort(_('shelve data already exists')) def shelvefunc(ui, repo, message, match, opts): changes = repo.status(match=match)[:5] modified, added, removed = changes[:3] files = modified + added + removed diffopts = mdiff.diffopts(git=True, nodates=True) patch_diff = ''.join(patch.diff(repo, repo.dirstate.parents()[0], match=match, changes=changes, opts=diffopts)) fp = cStringIO.StringIO(patch_diff) ac = parsepatch(fp) fp.close() chunks = filterpatch(ui, ac, not opts['all']) rc = refilterpatch(ac, chunks) contenders = {} for h in chunks: try: contenders.update(dict.fromkeys(h.files())) except AttributeError: pass newfiles = [f for f in files if f in contenders] if not newfiles: ui.status(_('no changes to shelve\n')) return 0 modified = dict.fromkeys(changes[0]) backupdir = repo.join('shelve-backups') try: bkfiles = [f for f in newfiles if f in modified] backups = makebackup(ui, repo, backupdir, bkfiles) # patch to shelve sp = cStringIO.StringIO() for c in chunks: if c.filename() in backups: c.write(sp) doshelve = sp.tell() sp.seek(0) # patch to apply to shelved files fp = cStringIO.StringIO() for c in rc: if c.filename() in backups: c.write(fp) dopatch = fp.tell() fp.seek(0) try: # 3a. apply filtered patch to clean repo (clean) if backups: hg.revert(repo, repo.dirstate.parents()[0], backups.has_key) # 3b. apply filtered patch to clean repo (apply) if dopatch: ui.debug('applying patch\n') ui.debug(fp.getvalue()) patch.internalpatch(fp, ui, 1, repo.root) del fp # 3c. apply filtered patch to clean repo (shelve) if doshelve: ui.debug("saving patch to shelve\n") if opts['append']: f = repo.opener(shelfpath, "a") else: f = repo.opener(shelfpath, "w") f.write(sp.getvalue()) del f del sp except: try: for realname, tmpname in backups.iteritems(): ui.debug('restoring %r to %r\n' % (tmpname, realname)) util.copyfile(tmpname, repo.wjoin(realname)) ui.debug('removing shelve file\n') os.unlink(repo.join(shelfpath)) except OSError: pass return 0 finally: try: for realname, tmpname in backups.iteritems(): ui.debug('removing backup for %r : %r\n' % (realname, tmpname)) os.unlink(tmpname) os.rmdir(backupdir) except OSError: pass fancyopts.fancyopts([], commands.commitopts, opts) return cmdutil.commit(ui, repo, shelvefunc, pats, opts)
def _dounshelve(ui, repo, *shelved, **opts): abortf = opts.get('abort') continuef = opts.get('continue') if not abortf and not continuef: cmdutil.checkunfinished(repo) if abortf or continuef: if abortf and continuef: raise error.Abort(_('cannot use both abort and continue')) if shelved: raise error.Abort( _('cannot combine abort/continue with ' 'naming a shelved change')) if abortf and opts.get('tool', False): ui.warn(_('tool option will be ignored\n')) try: state = shelvedstate.load(repo) except IOError as err: if err.errno != errno.ENOENT: raise cmdutil.wrongtooltocontinue(repo, _('unshelve')) except error.CorruptedState as err: ui.debug(str(err) + '\n') if continuef: msg = _('corrupted shelved state file') hint = _('please run hg unshelve --abort to abort unshelve ' 'operation') raise error.Abort(msg, hint=hint) elif abortf: msg = _('could not read shelved state file, your working copy ' 'may be in an unexpected state\nplease update to some ' 'commit\n') ui.warn(msg) shelvedstate.clear(repo) return if abortf: return unshelveabort(ui, repo, state, opts) elif continuef: return unshelvecontinue(ui, repo, state, opts) elif len(shelved) > 1: raise error.Abort(_('can only unshelve one change at a time')) elif not shelved: shelved = listshelves(repo) if not shelved: raise error.Abort(_('no shelved changes to apply!')) basename = util.split(shelved[0][1])[1] ui.status(_("unshelving change '%s'\n") % basename) else: basename = shelved[0] if not shelvedfile(repo, basename, 'patch').exists(): raise error.Abort(_("shelved change '%s' not found") % basename) oldquiet = ui.quiet lock = tr = None forcemerge = ui.backupconfig('ui', 'forcemerge') try: ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve') lock = repo.lock() tr = repo.transaction('unshelve', report=lambda x: None) oldtiprev = len(repo) pctx = repo['.'] tmpwctx = pctx # The goal is to have a commit structure like so: # ...-> pctx -> tmpwctx -> shelvectx # where tmpwctx is an optional commit with the user's pending changes # and shelvectx is the unshelved changes. Then we merge it all down # to the original pctx. # Store pending changes in a commit and remember added in case a shelve # contains unknown files that are part of the pending change s = repo.status() addedbefore = frozenset(s.added) if s.modified or s.added or s.removed or s.deleted: ui.status( _("temporarily committing pending changes " "(restore with 'hg unshelve --abort')\n")) def commitfunc(ui, repo, message, match, opts): hasmq = util.safehasattr(repo, 'mq') if hasmq: saved, repo.mq.checkapplied = repo.mq.checkapplied, False backup = repo.ui.backupconfig('phases', 'new-commit') try: repo.ui.setconfig('phases', 'new-commit', phases.secret) return repo.commit(message, 'shelve@localhost', opts.get('date'), match) finally: repo.ui.restoreconfig(backup) if hasmq: repo.mq.checkapplied = saved tempopts = {} tempopts['message'] = "pending changes temporary commit" tempopts['date'] = opts.get('date') ui.quiet = True node = cmdutil.commit(ui, repo, commitfunc, [], tempopts) tmpwctx = repo[node] ui.quiet = True shelvedfile(repo, basename, 'hg').applybundle() ui.quiet = oldquiet shelvectx = repo['tip'] branchtorestore = '' if shelvectx.branch() != shelvectx.p1().branch(): branchtorestore = shelvectx.branch() # If the shelve is not immediately on top of the commit # we'll be merging with, rebase it to be on top. if tmpwctx.node() != shelvectx.parents()[0].node(): ui.status(_('rebasing shelved changes\n')) try: rebase.rebase( ui, repo, **{ 'rev': [shelvectx.rev()], 'dest': str(tmpwctx.rev()), 'keep': True, 'tool': opts.get('tool', ''), }) except error.InterventionRequired: tr.close() stripnodes = [ repo.changelog.node(rev) for rev in xrange(oldtiprev, len(repo)) ] shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes, branchtorestore) util.rename(repo.join('rebasestate'), repo.join('unshelverebasestate')) raise error.InterventionRequired( _("unresolved conflicts (see 'hg resolve', then " "'hg unshelve --continue')")) # refresh ctx after rebase completes shelvectx = repo['tip'] if not shelvectx in tmpwctx.children(): # rebase was a no-op, so it produced no child commit shelvectx = tmpwctx mergefiles(ui, repo, pctx, shelvectx) restorebranch(ui, repo, branchtorestore) # Forget any files that were unknown before the shelve, unknown before # unshelve started, but are now added. shelveunknown = shelvectx.extra().get('shelve_unknown') if shelveunknown: shelveunknown = frozenset(shelveunknown.split('\0')) addedafter = frozenset(repo.status().added) toforget = (addedafter & shelveunknown) - addedbefore repo[None].forget(toforget) shelvedstate.clear(repo) # The transaction aborting will strip all the commits for us, # but it doesn't update the inmemory structures, so addchangegroup # hooks still fire and try to operate on the missing commits. # Clean up manually to prevent this. repo.unfiltered().changelog.strip(oldtiprev, tr) unshelvecleanup(ui, repo, basename, opts) _aborttransaction(repo) finally: ui.quiet = oldquiet if tr: tr.release() lockmod.release(lock) ui.restoreconfig(forcemerge)
def unshelve(ui, repo, *shelved, **opts): """restore a shelved change to the working directory This command accepts an optional name of a shelved change to restore. If none is given, the most recent shelved change is used. If a shelved change is applied successfully, the bundle that contains the shelved changes is moved to a backup location (.hg/shelve-backup). Since you can restore a shelved change on top of an arbitrary commit, it is possible that unshelving will result in a conflict between your changes and the commits you are unshelving onto. If this occurs, you must resolve the conflict, then use ``--continue`` to complete the unshelve operation. (The bundle will not be moved until you successfully complete the unshelve.) (Alternatively, you can use ``--abort`` to abandon an unshelve that causes a conflict. This reverts the unshelved changes, and leaves the bundle in place.) After a successful unshelve, the shelved changes are stored in a backup directory. Only the N most recent backups are kept. N defaults to 10 but can be overridden using the ``shelve.maxbackups`` configuration option. .. container:: verbose Timestamp in seconds is used to decide order of backups. More than ``maxbackups`` backups are kept, if same timestamp prevents from deciding exact order of them, for safety. """ abortf = opts['abort'] continuef = opts['continue'] if not abortf and not continuef: cmdutil.checkunfinished(repo) if abortf or continuef: if abortf and continuef: raise error.Abort(_('cannot use both abort and continue')) if shelved: raise error.Abort( _('cannot combine abort/continue with ' 'naming a shelved change')) try: state = shelvedstate.load(repo) except IOError as err: if err.errno != errno.ENOENT: raise raise error.Abort(_('no unshelve operation underway')) if abortf: return unshelveabort(ui, repo, state, opts) elif continuef: return unshelvecontinue(ui, repo, state, opts) elif len(shelved) > 1: raise error.Abort(_('can only unshelve one change at a time')) elif not shelved: shelved = listshelves(repo) if not shelved: raise error.Abort(_('no shelved changes to apply!')) basename = util.split(shelved[0][1])[1] ui.status(_("unshelving change '%s'\n") % basename) else: basename = shelved[0] if not shelvedfile(repo, basename, 'patch').exists(): raise error.Abort(_("shelved change '%s' not found") % basename) oldquiet = ui.quiet wlock = lock = tr = None try: wlock = repo.wlock() lock = repo.lock() tr = repo.transaction('unshelve', report=lambda x: None) oldtiprev = len(repo) pctx = repo['.'] tmpwctx = pctx # The goal is to have a commit structure like so: # ...-> pctx -> tmpwctx -> shelvectx # where tmpwctx is an optional commit with the user's pending changes # and shelvectx is the unshelved changes. Then we merge it all down # to the original pctx. # Store pending changes in a commit s = repo.status() if s.modified or s.added or s.removed or s.deleted: ui.status( _("temporarily committing pending changes " "(restore with 'hg unshelve --abort')\n")) def commitfunc(ui, repo, message, match, opts): hasmq = util.safehasattr(repo, 'mq') if hasmq: saved, repo.mq.checkapplied = repo.mq.checkapplied, False backup = repo.ui.backupconfig('phases', 'new-commit') try: repo.ui.setconfig('phases', 'new-commit', phases.secret) return repo.commit(message, 'shelve@localhost', opts.get('date'), match) finally: repo.ui.restoreconfig(backup) if hasmq: repo.mq.checkapplied = saved tempopts = {} tempopts['message'] = "pending changes temporary commit" tempopts['date'] = opts.get('date') ui.quiet = True node = cmdutil.commit(ui, repo, commitfunc, [], tempopts) tmpwctx = repo[node] ui.quiet = True shelvedfile(repo, basename, 'hg').applybundle() ui.quiet = oldquiet shelvectx = repo['tip'] # If the shelve is not immediately on top of the commit # we'll be merging with, rebase it to be on top. if tmpwctx.node() != shelvectx.parents()[0].node(): ui.status(_('rebasing shelved changes\n')) try: rebase.rebase( ui, repo, **{ 'rev': [shelvectx.rev()], 'dest': str(tmpwctx.rev()), 'keep': True, }) except error.InterventionRequired: tr.close() stripnodes = [ repo.changelog.node(rev) for rev in xrange(oldtiprev, len(repo)) ] shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes) util.rename(repo.join('rebasestate'), repo.join('unshelverebasestate')) raise error.InterventionRequired( _("unresolved conflicts (see 'hg resolve', then " "'hg unshelve --continue')")) # refresh ctx after rebase completes shelvectx = repo['tip'] if not shelvectx in tmpwctx.children(): # rebase was a no-op, so it produced no child commit shelvectx = tmpwctx mergefiles(ui, repo, pctx, shelvectx) shelvedstate.clear(repo) # The transaction aborting will strip all the commits for us, # but it doesn't update the inmemory structures, so addchangegroup # hooks still fire and try to operate on the missing commits. # Clean up manually to prevent this. repo.unfiltered().changelog.strip(oldtiprev, tr) unshelvecleanup(ui, repo, basename, opts) _aborttransaction(repo) finally: ui.quiet = oldquiet if tr: tr.release() lockmod.release(lock, wlock)
def _docreatecmd(ui, repo, pats, opts): wctx = repo[None] parents = wctx.parents() if len(parents) > 1: raise error.Abort(_('cannot shelve while merging')) parent = parents[0] origbranch = wctx.branch() if parent.node() != nodemod.nullid: desc = "changes to: %s" % parent.description().split('\n', 1)[0] else: desc = '(changes in empty repository)' if not opts.get('message'): opts['message'] = desc lock = tr = activebookmark = None try: lock = repo.lock() # use an uncommitted transaction to generate the bundle to avoid # pull races. ensure we don't print the abort message to stderr. tr = repo.transaction('commit', report=lambda x: None) interactive = opts.get('interactive', False) includeunknown = (opts.get('unknown', False) and not opts.get('addremove', False)) name = getshelvename(repo, parent, opts) activebookmark = _backupactivebookmark(repo) extra = {} if includeunknown: _includeunknownfiles(repo, pats, opts, extra) if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts): # In non-bare shelve we don't store newly created branch # at bundled commit repo.dirstate.setbranch(repo['.'].branch()) commitfunc = getcommitfunc(extra, interactive, editor=True) if not interactive: node = cmdutil.commit(ui, repo, commitfunc, pats, opts) else: node = cmdutil.dorecord(ui, repo, commitfunc, None, False, cmdutil.recordfilter, *pats, **pycompat.strkwargs(opts)) if not node: _nothingtoshelvemessaging(ui, repo, pats, opts) return 1 _shelvecreatedcommit(repo, node, name) if ui.formatted(): desc = stringutil.ellipsis(desc, ui.termwidth()) ui.status(_('shelved as %s\n') % name) hg.update(repo, parent.node()) if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts): repo.dirstate.setbranch(origbranch) _finishshelve(repo) finally: _restoreactivebookmark(repo, activebookmark) lockmod.release(tr, lock)
ui.warn("removing backup failed: %s\n" % err) fancyopts.fancyopts([], commands.commitopts, opts) # wrap ui.write so diff output can be labeled/colorized def wrapwrite(orig, *args, **kw): label = kw.pop('label', '') if label: label += ' ' for chunk, l in patch.difflabel(lambda: args): orig(chunk, label=label + l) oldwrite = ui.write extensions.wrapfunction(ui, 'write', wrapwrite) try: return cmdutil.commit(ui, repo, shelvefunc, pats, opts) finally: ui.write = oldwrite def listshelves(ui, repo): # Check for shelve file at old location first if os.path.isfile(repo.join('shelve')): ui.status('default\n') # Now go through all the files in the shelves folder and list them out dirname = repo.join('shelves') if os.path.isdir(dirname): for filename in sorted(os.listdir(repo.join('shelves'))): ui.status(filename + '\n')
if s: raise util.Abort(s) else: raise util.Abort(_('patch failed to apply')) del fp # 4. We prepared working directory according to filtered patch. # Now is the time to delegate the job to commit/qrefresh or the like! # it is important to first chdir to repo root -- we'll call a # highlevel command with list of pathnames relative to repo root newfiles = [repo.wjoin(n) for n in newfiles] commitfunc(ui, repo, *newfiles, **opts) return 0 finally: # 5. finally restore backed-up files try: for realname, tmpname in backups.iteritems(): ui.debug('restoring %r to %r\n' % (tmpname, realname)) util.copyfile(tmpname, repo.wjoin(realname)) os.unlink(tmpname) for realname, tmpname in newly_added_backups.iteritems(): ui.debug('restoring %r to %r\n' % (tmpname, realname)) util.copyfile(tmpname, repo.wjoin(realname)) os.unlink(tmpname) os.rmdir(backupdir) except OSError: pass return cmdutil.commit(ui, repo, recordfunc, pats, opts)
def unshelve(ui, repo, *shelved, **opts): """restore a shelved change to the working directory This command accepts an optional name of a shelved change to restore. If none is given, the most recent shelved change is used. If a shelved change is applied successfully, the bundle that contains the shelved changes is moved to a backup location (.hg/shelve-backup). Since you can restore a shelved change on top of an arbitrary commit, it is possible that unshelving will result in a conflict between your changes and the commits you are unshelving onto. If this occurs, you must resolve the conflict, then use ``--continue`` to complete the unshelve operation. (The bundle will not be moved until you successfully complete the unshelve.) (Alternatively, you can use ``--abort`` to abandon an unshelve that causes a conflict. This reverts the unshelved changes, and leaves the bundle in place.) After a successful unshelve, the shelved changes are stored in a backup directory. Only the N most recent backups are kept. N defaults to 10 but can be overridden using the ``shelve.maxbackups`` configuration option. .. container:: verbose Timestamp in seconds is used to decide order of backups. More than ``maxbackups`` backups are kept, if same timestamp prevents from deciding exact order of them, for safety. """ abortf = opts['abort'] continuef = opts['continue'] if not abortf and not continuef: cmdutil.checkunfinished(repo) if abortf or continuef: if abortf and continuef: raise error.Abort(_('cannot use both abort and continue')) if shelved: raise error.Abort(_('cannot combine abort/continue with ' 'naming a shelved change')) try: state = shelvedstate.load(repo) except IOError as err: if err.errno != errno.ENOENT: raise raise error.Abort(_('no unshelve operation underway')) if abortf: return unshelveabort(ui, repo, state, opts) elif continuef: return unshelvecontinue(ui, repo, state, opts) elif len(shelved) > 1: raise error.Abort(_('can only unshelve one change at a time')) elif not shelved: shelved = listshelves(repo) if not shelved: raise error.Abort(_('no shelved changes to apply!')) basename = util.split(shelved[0][1])[1] ui.status(_("unshelving change '%s'\n") % basename) else: basename = shelved[0] if not shelvedfile(repo, basename, 'patch').exists(): raise error.Abort(_("shelved change '%s' not found") % basename) oldquiet = ui.quiet wlock = lock = tr = None try: wlock = repo.wlock() lock = repo.lock() tr = repo.transaction('unshelve', report=lambda x: None) oldtiprev = len(repo) pctx = repo['.'] tmpwctx = pctx # The goal is to have a commit structure like so: # ...-> pctx -> tmpwctx -> shelvectx # where tmpwctx is an optional commit with the user's pending changes # and shelvectx is the unshelved changes. Then we merge it all down # to the original pctx. # Store pending changes in a commit s = repo.status() if s.modified or s.added or s.removed or s.deleted: ui.status(_("temporarily committing pending changes " "(restore with 'hg unshelve --abort')\n")) def commitfunc(ui, repo, message, match, opts): hasmq = util.safehasattr(repo, 'mq') if hasmq: saved, repo.mq.checkapplied = repo.mq.checkapplied, False backup = repo.ui.backupconfig('phases', 'new-commit') try: repo.ui. setconfig('phases', 'new-commit', phases.secret) return repo.commit(message, 'shelve@localhost', opts.get('date'), match) finally: repo.ui.restoreconfig(backup) if hasmq: repo.mq.checkapplied = saved tempopts = {} tempopts['message'] = "pending changes temporary commit" tempopts['date'] = opts.get('date') ui.quiet = True node = cmdutil.commit(ui, repo, commitfunc, [], tempopts) tmpwctx = repo[node] ui.quiet = True shelvedfile(repo, basename, 'hg').applybundle() ui.quiet = oldquiet shelvectx = repo['tip'] # If the shelve is not immediately on top of the commit # we'll be merging with, rebase it to be on top. if tmpwctx.node() != shelvectx.parents()[0].node(): ui.status(_('rebasing shelved changes\n')) try: rebase.rebase(ui, repo, **{ 'rev' : [shelvectx.rev()], 'dest' : str(tmpwctx.rev()), 'keep' : True, }) except error.InterventionRequired: tr.close() stripnodes = [repo.changelog.node(rev) for rev in xrange(oldtiprev, len(repo))] shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes) util.rename(repo.join('rebasestate'), repo.join('unshelverebasestate')) raise error.InterventionRequired( _("unresolved conflicts (see 'hg resolve', then " "'hg unshelve --continue')")) # refresh ctx after rebase completes shelvectx = repo['tip'] if not shelvectx in tmpwctx.children(): # rebase was a no-op, so it produced no child commit shelvectx = tmpwctx mergefiles(ui, repo, pctx, shelvectx) shelvedstate.clear(repo) # The transaction aborting will strip all the commits for us, # but it doesn't update the inmemory structures, so addchangegroup # hooks still fire and try to operate on the missing commits. # Clean up manually to prevent this. repo.unfiltered().changelog.strip(oldtiprev, tr) unshelvecleanup(ui, repo, basename, opts) _aborttransaction(repo) finally: ui.quiet = oldquiet if tr: tr.release() lockmod.release(lock, wlock)
def createcmd(ui, repo, pats, opts): """subcommand that creates a new shelve""" def mutableancestors(ctx): """return all mutable ancestors for ctx (included) Much faster than the revset ancestors(ctx) & draft()""" seen = set([nullrev]) visit = collections.deque() visit.append(ctx) while visit: ctx = visit.popleft() yield ctx.node() for parent in ctx.parents(): rev = parent.rev() if rev not in seen: seen.add(rev) if parent.mutable(): visit.append(parent) wctx = repo[None] parents = wctx.parents() if len(parents) > 1: raise error.Abort(_('cannot shelve while merging')) parent = parents[0] # we never need the user, so we use a generic user for all shelve operations user = '******' label = repo._activebookmark or parent.branch() or 'default' # slashes aren't allowed in filenames, therefore we rename it label = label.replace('/', '_') def gennames(): yield label for i in xrange(1, 100): yield '%s-%02d' % (label, i) def commitfunc(ui, repo, message, match, opts): hasmq = util.safehasattr(repo, 'mq') if hasmq: saved, repo.mq.checkapplied = repo.mq.checkapplied, False backup = repo.ui.backupconfig('phases', 'new-commit') try: repo.ui. setconfig('phases', 'new-commit', phases.secret) editor = cmdutil.getcommiteditor(editform='shelve.shelve', **opts) return repo.commit(message, user, opts.get('date'), match, editor=editor) finally: repo.ui.restoreconfig(backup) if hasmq: repo.mq.checkapplied = saved if parent.node() != nullid: desc = "changes to '%s'" % parent.description().split('\n', 1)[0] else: desc = '(changes in empty repository)' if not opts['message']: opts['message'] = desc name = opts['name'] wlock = lock = tr = None try: wlock = repo.wlock() lock = repo.lock() # use an uncommitted transaction to generate the bundle to avoid # pull races. ensure we don't print the abort message to stderr. tr = repo.transaction('commit', report=lambda x: None) if name: if shelvedfile(repo, name, 'hg').exists(): raise error.Abort(_("a shelved change named '%s' already exists" ) % name) else: for n in gennames(): if not shelvedfile(repo, n, 'hg').exists(): name = n break else: raise error.Abort(_("too many shelved changes named '%s'") % label) # ensure we are not creating a subdirectory or a hidden file if '/' in name or '\\' in name: raise error.Abort(_('shelved change names may not contain slashes')) if name.startswith('.'): raise error.Abort(_("shelved change names may not start with '.'")) interactive = opts.get('interactive', False) def interactivecommitfunc(ui, repo, *pats, **opts): match = scmutil.match(repo['.'], pats, {}) message = opts['message'] return commitfunc(ui, repo, message, match, opts) if not interactive: node = cmdutil.commit(ui, repo, commitfunc, pats, opts) else: node = cmdutil.dorecord(ui, repo, interactivecommitfunc, None, False, cmdutil.recordfilter, *pats, **opts) if not node: stat = repo.status(match=scmutil.match(repo[None], pats, opts)) if stat.deleted: ui.status(_("nothing changed (%d missing files, see " "'hg status')\n") % len(stat.deleted)) else: ui.status(_("nothing changed\n")) return 1 bases = list(mutableancestors(repo[node])) shelvedfile(repo, name, 'hg').writebundle(bases, node) cmdutil.export(repo, [node], fp=shelvedfile(repo, name, 'patch').opener('wb'), opts=mdiff.diffopts(git=True)) if ui.formatted(): desc = util.ellipsis(desc, ui.termwidth()) ui.status(_('shelved as %s\n') % name) hg.update(repo, parent.node()) _aborttransaction(repo) finally: lockmod.release(tr, lock, wlock)
def interactiveshelve(ui, repo, name, pats, opts): """interactively select changes to set aside""" if not ui.interactive: raise util.Abort(_('shelve --interactive can only be run interactively')) try: record = extensions.find('record') except KeyError: raise util.Abort(_("'record' extension not loaded")) # monkeypatch record extension record.hunk.__eq__ = hunk__eq__ def shelvefunc(ui, repo, message, match, opts): files = [] if match.files(): changes = None else: changes = repo.status(match = match)[:3] modified, added, removed = changes files = modified + added + removed match = cmdutil.matchfiles(repo, files) diffopts = repo.attic.diffopts( {'git':True, 'nodates':True}) chunks = patch.diff(repo, repo.dirstate.parents()[0], match = match, changes = changes, opts = diffopts) fp = cStringIO.StringIO() fp.write(''.join(chunks)) fp.seek(0) # 1. filter patch, so we have intending-to apply subset of it ac = record.parsepatch(fp) chunks = record.filterpatch(ui, ac) # and a not-intending-to apply subset of it rc = refilterpatch(ac, chunks) del fp contenders = {} for h in chunks: try: contenders.update(dict.fromkeys(h.files())) except AttributeError: pass newfiles = [f for f in files if f in contenders] if not newfiles: ui.status(_('no changes to shelve\n')) return 0 modified = dict.fromkeys(changes[0]) backups = {} backupdir = repo.join('shelve-backups') try: bkfiles = [f for f in newfiles if f in modified] backups = makebackup(ui, repo, backupdir, bkfiles) # patch to shelve sp = cStringIO.StringIO() for c in chunks: if c.filename() in backups: c.write(sp) doshelve = sp.tell() sp.seek(0) # patch to apply to shelved files fp = cStringIO.StringIO() for c in rc: if c.filename() in backups: c.write(fp) dopatch = fp.tell() fp.seek(0) try: # 3a. apply filtered patch to clean repo (clean) if backups: hg.revert(repo, repo.dirstate.parents()[0], backups.has_key) # 3b. apply filtered patch to clean repo (apply) if dopatch: ui.debug(_('applying patch\n')) ui.debug(fp.getvalue()) patch.internalpatch(fp, ui, 1, repo.root) del fp # 3c. apply filtered patch to clean repo (shelve) if doshelve: ui.debug(_("saving patch to %s\n") % (name)) s = repo.attic f = s.opener(name, 'w') f.write(sp.getvalue()) del f s.currentpatch = name s.persiststate() del sp except: try: for realname, tmpname in backups.iteritems(): ui.debug(_('restoring %r to %r\n') % (tmpname, realname)) util.copyfile(tmpname, repo.wjoin(realname)) except OSError: pass return 0 finally: try: for realname, tmpname in backups.iteritems(): ui.debug(_('removing backup for %r : %r\n') % (realname, tmpname)) os.unlink(tmpname) os.rmdir(backupdir) except OSError: pass fancyopts.fancyopts([], commands.commitopts, opts) return cmdutil.commit(ui, repo, shelvefunc, pats, opts)
def shelve(ui, repo, *pats, **opts): '''interactively select changes to set aside If a list of files is omitted, all changes reported by "hg status" will be candidates for shelving. You will be prompted for whether to shelve changes to each modified file, and for files with multiple changes, for each change to use. The shelve command works with the Color extension to display diffs in color. On each prompt, the following responses are possible:: y - shelve this change n - skip this change s - skip remaining changes to this file f - shelve remaining changes to this file d - done, skip remaining changes and files a - shelve all changes to all remaining files q - quit, shelving no changes ? - display help''' if not ui.interactive: raise util.Abort(_('shelve can only be run interactively')) # List all the active shelves by name and return ' if opts['list']: listshelves(ui, repo) return forced = opts['force'] or opts['append'] # Shelf name and path shelfname = opts.get('name') shelfpath = getshelfpath(repo, shelfname) if os.path.exists(repo.join(shelfpath)) and not forced: raise util.Abort(_('shelve data already exists')) def shelvefunc(ui, repo, message, match, opts): changes = repo.status(match=match)[:5] modified, added, removed = changes[:3] files = modified + added + removed diffopts = mdiff.diffopts(git=True, nodates=True) patch_diff = ''.join( patch.diff(repo, repo.dirstate.parents()[0], match=match, changes=changes, opts=diffopts)) fp = cStringIO.StringIO(patch_diff) ac = parsepatch(fp) fp.close() chunks = filterpatch(ui, ac, not opts['all']) rc = refilterpatch(ac, chunks) contenders = {} for h in chunks: try: contenders.update(dict.fromkeys(h.files())) except AttributeError: pass newfiles = [f for f in files if f in contenders] if not newfiles: ui.status(_('no changes to shelve\n')) return 0 modified = dict.fromkeys(changes[0]) backupdir = repo.join('shelve-backups') try: bkfiles = [f for f in newfiles if f in modified] backups = makebackup(ui, repo, backupdir, bkfiles) # patch to shelve sp = cStringIO.StringIO() for c in chunks: if c.filename() in backups: c.write(sp) doshelve = sp.tell() sp.seek(0) # patch to apply to shelved files fp = cStringIO.StringIO() for c in rc: if c.filename() in backups: c.write(fp) dopatch = fp.tell() fp.seek(0) try: # 3a. apply filtered patch to clean repo (clean) if backups: hg.revert(repo, repo.dirstate.parents()[0], backups.has_key) # 3b. apply filtered patch to clean repo (apply) if dopatch: ui.debug('applying patch\n') ui.debug(fp.getvalue()) patch.internalpatch(fp, ui, 1, repo.root) del fp # 3c. apply filtered patch to clean repo (shelve) if doshelve: ui.debug("saving patch to shelve\n") if opts['append']: f = repo.opener(shelfpath, "a") else: f = repo.opener(shelfpath, "w") f.write(sp.getvalue()) del f del sp except: try: for realname, tmpname in backups.iteritems(): ui.debug('restoring %r to %r\n' % (tmpname, realname)) util.copyfile(tmpname, repo.wjoin(realname)) ui.debug('removing shelve file\n') os.unlink(repo.join(shelfpath)) except OSError: pass return 0 finally: try: for realname, tmpname in backups.iteritems(): ui.debug('removing backup for %r : %r\n' % (realname, tmpname)) os.unlink(tmpname) os.rmdir(backupdir) except OSError: pass fancyopts.fancyopts([], commands.commitopts, opts) return cmdutil.commit(ui, repo, shelvefunc, pats, opts) # wrap ui.write so diff output can be labeled/colorized def wrapwrite(orig, *args, **kw): label = kw.pop('label', '') for chunk, l in patch.difflabel(lambda: args): orig(chunk, label=label + l) oldwrite = ui.write extensions.wrapfunction(ui, 'write', wrapwrite) try: return cmdutil.commit(ui, repo, shelvefunc, pats, opts) finally: ui.write = oldwrite
def _docreatecmd(ui, repo, pats, opts): wctx = repo[None] parents = wctx.parents() if len(parents) > 1: raise error.Abort(_('cannot shelve while merging')) parent = parents[0] origbranch = wctx.branch() if parent.node() != nodemod.nullid: desc = "changes to: %s" % parent.description().split('\n', 1)[0] else: desc = '(changes in empty repository)' if not opts.get('message'): opts['message'] = desc lock = tr = activebookmark = None try: lock = repo.lock() # depending on whether shelve is traditional or # obsolescense-based, we either abort or commit this # transaction in the end. If we abort it, we don't # want to print anything to stderr report = None tr = repo.transaction('commit', report=report) interactive = opts.get('interactive', False) includeunknown = (opts.get('unknown', False) and not opts.get('addremove', False)) name = getshelvename(repo, parent, opts) activebookmark = _backupactivebookmark(repo) extra = {} if includeunknown: _includeunknownfiles(repo, pats, opts, extra) if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts): # In non-bare shelve we don't store newly created branch # at bundled commit repo.dirstate.setbranch(repo['.'].branch()) commitfunc = getcommitfunc(extra, interactive, editor=True) if not interactive: node = cmdutil.commit(ui, repo, commitfunc, pats, opts) else: node = cmdutil.dorecord(ui, repo, commitfunc, None, False, cmdutil.recordfilter, *pats, **opts) if not node: _nothingtoshelvemessaging(ui, repo, pats, opts) return 1 _shelvecreatedcommit(ui, repo, node, name, tr) if ui.formatted(): desc = util.ellipsis(desc, ui.termwidth()) ui.status(_('shelved as %s\n') % name) # current wc parent may be already obsolete becuase # it might have been created previously and shelve just # reuses it hg.update(repo.unfiltered(), parent.node()) if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts): repo.dirstate.setbranch(origbranch) _finishshelve(ui, repo, tr, node, activebookmark) finally: _restoreactivebookmark(repo, activebookmark) lockmod.release(tr, lock)
ui.warn("removing backup failed: %s\n" % err) fancyopts.fancyopts([], commands.commitopts, opts) # wrap ui.write so diff output can be labeled/colorized def wrapwrite(orig, *args, **kw): label = kw.pop("label", "") if label: label += " " for chunk, l in patch.difflabel(lambda: args): orig(chunk, label=label + l) oldwrite = ui.write extensions.wrapfunction(ui, "write", wrapwrite) try: return cmdutil.commit(ui, repo, shelvefunc, pats, opts) finally: ui.write = oldwrite def listshelves(ui, repo): # Check for shelve file at old location first if os.path.isfile(repo.join("shelve")): ui.status("default\n") # Now go through all the files in the shelves folder and list them out dirname = repo.join("shelves") if os.path.isdir(dirname): for filename in sorted(os.listdir(repo.join("shelves"))): ui.status(filename + "\n")
def interactiveshelve(ui, repo, name, pats, opts): """interactively select changes to set aside""" if not ui.interactive: raise util.Abort( _('shelve --interactive can only be run interactively')) try: record = extensions.find('record') except KeyError: raise util.Abort(_("'record' extension not loaded")) # monkeypatch record extension record.hunk.__eq__ = hunk__eq__ def shelvefunc(ui, repo, message, match, opts): files = [] if match.files(): changes = None else: changes = repo.status(match=match)[:3] modified, added, removed = changes files = modified + added + removed match = cmdutil.matchfiles(repo, files) diffopts = repo.attic.diffopts({'git': True, 'nodates': True}) chunks = patch.diff(repo, repo.dirstate.parents()[0], match=match, changes=changes, opts=diffopts) fp = cStringIO.StringIO() fp.write(''.join(chunks)) fp.seek(0) # 1. filter patch, so we have intending-to apply subset of it ac = record.parsepatch(fp) chunks = record.filterpatch(ui, ac) # and a not-intending-to apply subset of it rc = refilterpatch(ac, chunks) del fp contenders = {} for h in chunks: try: contenders.update(dict.fromkeys(h.files())) except AttributeError: pass newfiles = [f for f in files if f in contenders] if not newfiles: ui.status(_('no changes to shelve\n')) return 0 modified = dict.fromkeys(changes[0]) backups = {} backupdir = repo.join('shelve-backups') try: bkfiles = [f for f in newfiles if f in modified] backups = makebackup(ui, repo, backupdir, bkfiles) # patch to shelve sp = cStringIO.StringIO() for c in chunks: if c.filename() in backups: c.write(sp) doshelve = sp.tell() sp.seek(0) # patch to apply to shelved files fp = cStringIO.StringIO() for c in rc: if c.filename() in backups: c.write(fp) dopatch = fp.tell() fp.seek(0) try: # 3a. apply filtered patch to clean repo (clean) if backups: hg.revert(repo, repo.dirstate.parents()[0], backups.has_key) # 3b. apply filtered patch to clean repo (apply) if dopatch: ui.debug(_('applying patch\n')) ui.debug(fp.getvalue()) patch.internalpatch(fp, ui, 1, repo.root) del fp # 3c. apply filtered patch to clean repo (shelve) if doshelve: ui.debug(_("saving patch to %s\n") % (name)) s = repo.attic f = s.opener(name, 'w') f.write(sp.getvalue()) del f s.currentpatch = name s.persiststate() del sp except: try: for realname, tmpname in backups.iteritems(): ui.debug( _('restoring %r to %r\n') % (tmpname, realname)) util.copyfile(tmpname, repo.wjoin(realname)) except OSError: pass return 0 finally: try: for realname, tmpname in backups.iteritems(): ui.debug( _('removing backup for %r : %r\n') % (realname, tmpname)) os.unlink(tmpname) os.rmdir(backupdir) except OSError: pass fancyopts.fancyopts([], commands.commitopts, opts) return cmdutil.commit(ui, repo, shelvefunc, pats, opts)
os.unlink(tmpname) if tobackup: os.rmdir(backupdir) except OSError: pass # wrap ui.write so diff output can be labeled/colorized def wrapwrite(orig, *args, **kw): label = kw.pop("label", "") for chunk, l in patch.difflabel(lambda: args): orig(chunk, label=label + l) oldwrite = ui.write extensions.wrapfunction(ui, "write", wrapwrite) try: return cmdutil.commit(ui, repo, recordfunc, pats, opts) finally: ui.write = oldwrite cmdtable["qrecord"] = (qrecord, [], _("hg qrecord [OPTION]... PATCH [FILE]...")) # placeholder until mq is available def uisetup(ui): try: mq = extensions.find("mq") except KeyError: return cmdtable["qrecord"] = ( qrecord,
def createcmd(ui, repo, pats, opts): """subcommand that creates a new shelve""" def publicancestors(ctx): """Compute the public ancestors of a commit. Much faster than the revset ancestors(ctx) & draft()""" seen = set([nullrev]) visit = util.deque() visit.append(ctx) while visit: ctx = visit.popleft() yield ctx.node() for parent in ctx.parents(): rev = parent.rev() if rev not in seen: seen.add(rev) if parent.mutable(): visit.append(parent) wctx = repo[None] parents = wctx.parents() if len(parents) > 1: raise util.Abort(_('cannot shelve while merging')) parent = parents[0] # we never need the user, so we use a generic user for all shelve operations user = '******' label = repo._bookmarkcurrent or parent.branch() or 'default' # slashes aren't allowed in filenames, therefore we rename it label = label.replace('/', '_') def gennames(): yield label for i in xrange(1, 100): yield '%s-%02d' % (label, i) shelvedfiles = [] def commitfunc(ui, repo, message, match, opts): # check modified, added, removed, deleted only for flist in repo.status(match=match)[:4]: shelvedfiles.extend(flist) hasmq = util.safehasattr(repo, 'mq') if hasmq: saved, repo.mq.checkapplied = repo.mq.checkapplied, False try: return repo.commit(message, user, opts.get('date'), match, editor=cmdutil.getcommiteditor(**opts)) finally: if hasmq: repo.mq.checkapplied = saved if parent.node() != nullid: desc = "changes to '%s'" % parent.description().split('\n', 1)[0] else: desc = '(changes in empty repository)' if not opts['message']: opts['message'] = desc name = opts['name'] wlock = lock = tr = bms = None try: wlock = repo.wlock() lock = repo.lock() bms = repo._bookmarks.copy() # use an uncommitted transaction to generate the bundle to avoid # pull races. ensure we don't print the abort message to stderr. tr = repo.transaction('commit', report=lambda x: None) if name: if shelvedfile(repo, name, 'hg').exists(): raise util.Abort( _("a shelved change named '%s' already exists") % name) else: for n in gennames(): if not shelvedfile(repo, n, 'hg').exists(): name = n break else: raise util.Abort( _("too many shelved changes named '%s'") % label) # ensure we are not creating a subdirectory or a hidden file if '/' in name or '\\' in name: raise util.Abort(_('shelved change names may not contain slashes')) if name.startswith('.'): raise util.Abort(_("shelved change names may not start with '.'")) node = cmdutil.commit(ui, repo, commitfunc, pats, opts) if not node: stat = repo.status(match=scmutil.match(repo[None], pats, opts)) if stat[3]: ui.status( _("nothing changed (%d missing files, see " "'hg status')\n") % len(stat[3])) else: ui.status(_("nothing changed\n")) return 1 phases.retractboundary(repo, phases.secret, [node]) fp = shelvedfile(repo, name, 'files').opener('wb') fp.write('\0'.join(shelvedfiles)) bases = list(publicancestors(repo[node])) cg = changegroup.changegroupsubset(repo, bases, [node], 'shelve') shelvedfile(repo, name, 'hg').writebundle(cg) cmdutil.export(repo, [node], fp=shelvedfile(repo, name, 'patch').opener('wb'), opts=mdiff.diffopts(git=True)) if ui.formatted(): desc = util.ellipsis(desc, ui.termwidth()) ui.status(_('shelved as %s\n') % name) hg.update(repo, parent.node()) finally: if bms: # restore old bookmarks repo._bookmarks.update(bms) repo._bookmarks.write() if tr: tr.abort() lockmod.release(lock, wlock)
def _docreatecmd(ui, repo, pats, opts): def mutableancestors(ctx): """return all mutable ancestors for ctx (included) Much faster than the revset ancestors(ctx) & draft()""" seen = set([nodemod.nullrev]) visit = collections.deque() visit.append(ctx) while visit: ctx = visit.popleft() yield ctx.node() for parent in ctx.parents(): rev = parent.rev() if rev not in seen: seen.add(rev) if parent.mutable(): visit.append(parent) wctx = repo[None] parents = wctx.parents() if len(parents) > 1: raise error.Abort(_('cannot shelve while merging')) parent = parents[0] origbranch = wctx.branch() # we never need the user, so we use a generic user for all shelve operations user = '******' label = repo._activebookmark or parent.branch() or 'default' # slashes aren't allowed in filenames, therefore we rename it label = label.replace('/', '_') def gennames(): yield label for i in xrange(1, 100): yield '%s-%02d' % (label, i) if parent.node() != nodemod.nullid: desc = "changes to: %s" % parent.description().split('\n', 1)[0] else: desc = '(changes in empty repository)' if not opts.get('message'): opts['message'] = desc name = opts.get('name') lock = tr = None try: lock = repo.lock() # use an uncommitted transaction to generate the bundle to avoid # pull races. ensure we don't print the abort message to stderr. tr = repo.transaction('commit', report=lambda x: None) if name: if shelvedfile(repo, name, 'hg').exists(): raise error.Abort( _("a shelved change named '%s' already exists") % name) else: for n in gennames(): if not shelvedfile(repo, n, 'hg').exists(): name = n break else: raise error.Abort( _("too many shelved changes named '%s'") % label) # ensure we are not creating a subdirectory or a hidden file if '/' in name or '\\' in name: raise error.Abort( _('shelved change names may not contain slashes')) if name.startswith('.'): raise error.Abort(_("shelved change names may not start with '.'")) interactive = opts.get('interactive', False) includeunknown = (opts.get('unknown', False) and not opts.get('addremove', False)) extra = {} if includeunknown: s = repo.status(match=scmutil.match(repo[None], pats, opts), unknown=True) if s.unknown: extra['shelve_unknown'] = '\0'.join(s.unknown) repo[None].add(s.unknown) if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts): # In non-bare shelve we don't store newly created branch # at bundled commit repo.dirstate.setbranch(repo['.'].branch()) def commitfunc(ui, repo, message, match, opts): hasmq = util.safehasattr(repo, 'mq') if hasmq: saved, repo.mq.checkapplied = repo.mq.checkapplied, False backup = repo.ui.backupconfig('phases', 'new-commit') try: repo.ui.setconfig('phases', 'new-commit', phases.secret) editor = cmdutil.getcommiteditor(editform='shelve.shelve', **opts) return repo.commit(message, user, opts.get('date'), match, editor=editor, extra=extra) finally: repo.ui.restoreconfig(backup) if hasmq: repo.mq.checkapplied = saved def interactivecommitfunc(ui, repo, *pats, **opts): match = scmutil.match(repo['.'], pats, {}) message = opts['message'] return commitfunc(ui, repo, message, match, opts) if not interactive: node = cmdutil.commit(ui, repo, commitfunc, pats, opts) else: node = cmdutil.dorecord(ui, repo, interactivecommitfunc, None, False, cmdutil.recordfilter, *pats, **opts) if not node: stat = repo.status(match=scmutil.match(repo[None], pats, opts)) if stat.deleted: ui.status( _("nothing changed (%d missing files, see " "'hg status')\n") % len(stat.deleted)) else: ui.status(_("nothing changed\n")) return 1 bases = list(mutableancestors(repo[node])) shelvedfile(repo, name, 'hg').writebundle(bases, node) cmdutil.export(repo, [node], fp=shelvedfile(repo, name, 'patch').opener('wb'), opts=mdiff.diffopts(git=True)) if ui.formatted(): desc = util.ellipsis(desc, ui.termwidth()) ui.status(_('shelved as %s\n') % name) hg.update(repo, parent.node()) if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts): repo.dirstate.setbranch(origbranch) _aborttransaction(repo) finally: lockmod.release(tr, lock)
def _dounshelve(ui, repo, *shelved, **opts): abortf = opts.get('abort') continuef = opts.get('continue') if not abortf and not continuef: cmdutil.checkunfinished(repo) if abortf or continuef: if abortf and continuef: raise error.Abort(_('cannot use both abort and continue')) if shelved: raise error.Abort(_('cannot combine abort/continue with ' 'naming a shelved change')) if abortf and opts.get('tool', False): ui.warn(_('tool option will be ignored\n')) try: state = shelvedstate.load(repo) except IOError as err: if err.errno != errno.ENOENT: raise cmdutil.wrongtooltocontinue(repo, _('unshelve')) except error.CorruptedState as err: ui.debug(str(err) + '\n') if continuef: msg = _('corrupted shelved state file') hint = _('please run hg unshelve --abort to abort unshelve ' 'operation') raise error.Abort(msg, hint=hint) elif abortf: msg = _('could not read shelved state file, your working copy ' 'may be in an unexpected state\nplease update to some ' 'commit\n') ui.warn(msg) shelvedstate.clear(repo) return if abortf: return unshelveabort(ui, repo, state, opts) elif continuef: return unshelvecontinue(ui, repo, state, opts) elif len(shelved) > 1: raise error.Abort(_('can only unshelve one change at a time')) elif not shelved: shelved = listshelves(repo) if not shelved: raise error.Abort(_('no shelved changes to apply!')) basename = util.split(shelved[0][1])[1] ui.status(_("unshelving change '%s'\n") % basename) else: basename = shelved[0] if not shelvedfile(repo, basename, 'patch').exists(): raise error.Abort(_("shelved change '%s' not found") % basename) oldquiet = ui.quiet lock = tr = None forcemerge = ui.backupconfig('ui', 'forcemerge') try: ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve') lock = repo.lock() tr = repo.transaction('unshelve', report=lambda x: None) oldtiprev = len(repo) pctx = repo['.'] tmpwctx = pctx # The goal is to have a commit structure like so: # ...-> pctx -> tmpwctx -> shelvectx # where tmpwctx is an optional commit with the user's pending changes # and shelvectx is the unshelved changes. Then we merge it all down # to the original pctx. # Store pending changes in a commit and remember added in case a shelve # contains unknown files that are part of the pending change s = repo.status() addedbefore = frozenset(s.added) if s.modified or s.added or s.removed or s.deleted: ui.status(_("temporarily committing pending changes " "(restore with 'hg unshelve --abort')\n")) def commitfunc(ui, repo, message, match, opts): hasmq = util.safehasattr(repo, 'mq') if hasmq: saved, repo.mq.checkapplied = repo.mq.checkapplied, False backup = repo.ui.backupconfig('phases', 'new-commit') try: repo.ui.setconfig('phases', 'new-commit', phases.secret) return repo.commit(message, 'shelve@localhost', opts.get('date'), match) finally: repo.ui.restoreconfig(backup) if hasmq: repo.mq.checkapplied = saved tempopts = {} tempopts['message'] = "pending changes temporary commit" tempopts['date'] = opts.get('date') ui.quiet = True node = cmdutil.commit(ui, repo, commitfunc, [], tempopts) tmpwctx = repo[node] ui.quiet = True shelvedfile(repo, basename, 'hg').applybundle() ui.quiet = oldquiet shelvectx = repo['tip'] branchtorestore = '' if shelvectx.branch() != shelvectx.p1().branch(): branchtorestore = shelvectx.branch() # If the shelve is not immediately on top of the commit # we'll be merging with, rebase it to be on top. if tmpwctx.node() != shelvectx.parents()[0].node(): ui.status(_('rebasing shelved changes\n')) try: rebase.rebase(ui, repo, **{ 'rev' : [shelvectx.rev()], 'dest' : str(tmpwctx.rev()), 'keep' : True, 'tool' : opts.get('tool', ''), }) except error.InterventionRequired: tr.close() stripnodes = [repo.changelog.node(rev) for rev in xrange(oldtiprev, len(repo))] shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes, branchtorestore) util.rename(repo.join('rebasestate'), repo.join('unshelverebasestate')) raise error.InterventionRequired( _("unresolved conflicts (see 'hg resolve', then " "'hg unshelve --continue')")) # refresh ctx after rebase completes shelvectx = repo['tip'] if not shelvectx in tmpwctx.children(): # rebase was a no-op, so it produced no child commit shelvectx = tmpwctx mergefiles(ui, repo, pctx, shelvectx) restorebranch(ui, repo, branchtorestore) # Forget any files that were unknown before the shelve, unknown before # unshelve started, but are now added. shelveunknown = shelvectx.extra().get('shelve_unknown') if shelveunknown: shelveunknown = frozenset(shelveunknown.split('\0')) addedafter = frozenset(repo.status().added) toforget = (addedafter & shelveunknown) - addedbefore repo[None].forget(toforget) shelvedstate.clear(repo) # The transaction aborting will strip all the commits for us, # but it doesn't update the inmemory structures, so addchangegroup # hooks still fire and try to operate on the missing commits. # Clean up manually to prevent this. repo.unfiltered().changelog.strip(oldtiprev, tr) unshelvecleanup(ui, repo, basename, opts) _aborttransaction(repo) finally: ui.quiet = oldquiet if tr: tr.release() lockmod.release(lock) ui.restoreconfig(forcemerge)