38 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
38 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
39 # be specifying the version(s) of Mercurial they are tested with, or |
39 # be specifying the version(s) of Mercurial they are tested with, or |
40 # leave the attribute unspecified. |
40 # leave the attribute unspecified. |
41 testedwith = 'internal' |
41 testedwith = 'internal' |
42 |
42 |
|
43 backupdir = 'shelve-backup' |
|
44 |
43 class shelvedfile(object): |
45 class shelvedfile(object): |
44 """Helper for the file storing a single shelve |
46 """Helper for the file storing a single shelve |
45 |
47 |
46 Handles common functions on shelve files (.hg/.patch) using |
48 Handles common functions on shelve files (.hg/.patch) using |
47 the vfs layer""" |
49 the vfs layer""" |
48 def __init__(self, repo, name, filetype=None): |
50 def __init__(self, repo, name, filetype=None): |
49 self.repo = repo |
51 self.repo = repo |
50 self.name = name |
52 self.name = name |
51 self.vfs = scmutil.vfs(repo.join('shelved')) |
53 self.vfs = scmutil.vfs(repo.join('shelved')) |
52 self.backupvfs = scmutil.vfs(repo.join('shelve-backup')) |
54 self.backupvfs = scmutil.vfs(repo.join(backupdir)) |
53 self.ui = self.repo.ui |
55 self.ui = self.repo.ui |
54 if filetype: |
56 if filetype: |
55 self.fname = name + '.' + filetype |
57 self.fname = name + '.' + filetype |
56 else: |
58 else: |
57 self.fname = name |
59 self.fname = name |
153 fp.close() |
155 fp.close() |
154 |
156 |
155 @classmethod |
157 @classmethod |
156 def clear(cls, repo): |
158 def clear(cls, repo): |
157 util.unlinkpath(repo.join(cls._filename), ignoremissing=True) |
159 util.unlinkpath(repo.join(cls._filename), ignoremissing=True) |
|
160 |
|
161 def cleanupoldbackups(repo): |
|
162 vfs = scmutil.vfs(repo.join(backupdir)) |
|
163 maxbackups = repo.ui.configint('shelve', 'maxbackups', 10) |
|
164 hgfiles = [f for f in vfs.listdir() if f.endswith('.hg')] |
|
165 hgfiles = sorted([(vfs.stat(f).st_mtime, f) for f in hgfiles]) |
|
166 for mtime, f in hgfiles[:len(hgfiles) - maxbackups]: |
|
167 base = f[:-3] |
|
168 for ext in 'hg patch'.split(): |
|
169 try: |
|
170 vfs.unlink(base + '.' + ext) |
|
171 except OSError as err: |
|
172 if err.errno != errno.ENOENT: |
|
173 raise |
158 |
174 |
159 def createcmd(ui, repo, pats, opts): |
175 def createcmd(ui, repo, pats, opts): |
160 """subcommand that creates a new shelve""" |
176 """subcommand that creates a new shelve""" |
161 |
177 |
162 def publicancestors(ctx): |
178 def publicancestors(ctx): |
296 wlock = repo.wlock() |
312 wlock = repo.wlock() |
297 for (name, _type) in repo.vfs.readdir('shelved'): |
313 for (name, _type) in repo.vfs.readdir('shelved'): |
298 suffix = name.rsplit('.', 1)[-1] |
314 suffix = name.rsplit('.', 1)[-1] |
299 if suffix in ('hg', 'patch'): |
315 if suffix in ('hg', 'patch'): |
300 shelvedfile(repo, name).movetobackup() |
316 shelvedfile(repo, name).movetobackup() |
|
317 cleanupoldbackups(repo) |
301 finally: |
318 finally: |
302 lockmod.release(wlock) |
319 lockmod.release(wlock) |
303 |
320 |
304 def deletecmd(ui, repo, pats): |
321 def deletecmd(ui, repo, pats): |
305 """subcommand that deletes a specific shelve""" |
322 """subcommand that deletes a specific shelve""" |
308 wlock = repo.wlock() |
325 wlock = repo.wlock() |
309 try: |
326 try: |
310 for name in pats: |
327 for name in pats: |
311 for suffix in 'hg patch'.split(): |
328 for suffix in 'hg patch'.split(): |
312 shelvedfile(repo, name, suffix).movetobackup() |
329 shelvedfile(repo, name, suffix).movetobackup() |
|
330 cleanupoldbackups(repo) |
313 except OSError as err: |
331 except OSError as err: |
314 if err.errno != errno.ENOENT: |
332 if err.errno != errno.ENOENT: |
315 raise |
333 raise |
316 raise util.Abort(_("shelved change '%s' not found") % name) |
334 raise util.Abort(_("shelved change '%s' not found") % name) |
317 finally: |
335 finally: |
457 def unshelvecleanup(ui, repo, name, opts): |
475 def unshelvecleanup(ui, repo, name, opts): |
458 """remove related files after an unshelve""" |
476 """remove related files after an unshelve""" |
459 if not opts['keep']: |
477 if not opts['keep']: |
460 for filetype in 'hg patch'.split(): |
478 for filetype in 'hg patch'.split(): |
461 shelvedfile(repo, name, filetype).movetobackup() |
479 shelvedfile(repo, name, filetype).movetobackup() |
|
480 cleanupoldbackups(repo) |
462 |
481 |
463 def unshelvecontinue(ui, repo, state, opts): |
482 def unshelvecontinue(ui, repo, state, opts): |
464 """subcommand to continue an in-progress unshelve""" |
483 """subcommand to continue an in-progress unshelve""" |
465 # We're finishing off a merge. First parent is our original |
484 # We're finishing off a merge. First parent is our original |
466 # parent, second is the temporary "fake" commit we're unshelving. |
485 # parent, second is the temporary "fake" commit we're unshelving. |
532 will not be moved until you successfully complete the unshelve.) |
551 will not be moved until you successfully complete the unshelve.) |
533 |
552 |
534 (Alternatively, you can use ``--abort`` to abandon an unshelve |
553 (Alternatively, you can use ``--abort`` to abandon an unshelve |
535 that causes a conflict. This reverts the unshelved changes, and |
554 that causes a conflict. This reverts the unshelved changes, and |
536 leaves the bundle in place.) |
555 leaves the bundle in place.) |
|
556 |
|
557 After a successful unshelve, the shelved changes are stored in a |
|
558 backup directory. Only the N most recent backups are kept. N |
|
559 defaults to 10 but can be overridden using the shelve.maxbackups |
|
560 configuration option. |
537 """ |
561 """ |
538 abortf = opts['abort'] |
562 abortf = opts['abort'] |
539 continuef = opts['continue'] |
563 continuef = opts['continue'] |
540 if not abortf and not continuef: |
564 if not abortf and not continuef: |
541 cmdutil.checkunfinished(repo) |
565 cmdutil.checkunfinished(repo) |