Submitter | Simon Farnsworth |
---|---|
Date | July 4, 2016, 3:19 p.m. |
Message ID | <90e654b4d86d9290d545.1467645545@devvm631.lla1.facebook.com> |
Download | mbox | patch |
Permalink | /patch/15736/ |
State | Superseded |
Headers | show |
Comments
Ignore this, please - I pulled the wrong command up from history, and sent the version that fails tests. On 04/07/2016 16:19, Simon Farnsworth wrote: > # HG changeset patch > # User Simon Farnsworth <simonfar@fb.com> > # Date 1467645533 25200 > # Mon Jul 04 08:18:53 2016 -0700 > # Node ID 90e654b4d86d9290d5453c71be7a48c9923b433c > # Parent fd93b15b5c30d16fd9c9eba61402d07fc4085db3 > debug: automate the process of truncating a damaged obsstore (issue5265) > > We occasionally see users who've had a system crash damage the obsstore file > in their .hg/store directory; this makes all `hg` commands fail until we go > in and remove the damaged section of the obsstore by hand. > > Automate the process we use when this happens, as a debug command because it > loses the corrupted data. We only use it in rare circumstances when it's > important to retrieve a user's work and apply it to a fresh clone. > > diff --git a/mercurial/commands.py b/mercurial/commands.py > --- a/mercurial/commands.py > +++ b/mercurial/commands.py > @@ -3702,6 +3702,62 @@ > displayer.show(repo[r], **props) > displayer.close() > > +@command('debugtruncatestore', > + [('', 'obsolete', None, _('truncate bad markers in obsstore'))], > + _('[OPTION]')) > +def debugtruncatestore(ui, repo, **opts): > + """Fix up repository corruption by truncating damaged files > + > + Most on-disk data structures are designed to be append-only. A failed write > + (e.g. due to an unexpected power failure) can leave the file corrupted. > + > + This command attempts to recover from that situation by replacing the > + corrupted file with a version that only contains the valid records from the > + broken file. It is not guaranteed to remove all corrupt records - it will > + only remove corrupt records where normal use of the repo would result in a > + crash. > + > + Corrupt files will be renamed with a .corrupt extension before the fixed > + version is written out, so that you can examine the corruption and/or undo > + this command. > + > + You should normally use :hg:`recover` before resorting to this command. > + """ > + > + if 'obsolete' in opts: > + data = repo.svfs.tryread('obsstore') > + if data: > + # Slow algorithm - but this is an emergency debug operation > + version = None > + corrupt = False > + while version is None and len(data) > 0: > + try: > + (version, markers) = obsolete._readmarkers(data) > + # Force evaluation of all the markers - the pure > + # implementation returns a generator which won't detonate > + # until you evaluate the bad marker. > + for marker in markers: > + pass > + except: > + corrupt = True > + version = None > + data = data[:-1] > + continue > + break > + if corrupt: > + with repo.lock(): > + repo.svfs. > + repo.svfs.rename('obsstore', 'obsstore.corrupt') > + if len(data) > 0: > + repo.svfs.write('obsstore', data) > + ui.write(_('truncated damaged obsstore\n')) > + else: > + ui.write(_('deleted unreadable obsstore\n')) > + else: > + ui.write(_('no damage to obsstore\n')) > + else: > + ui.write(_('no obsstore\n')) > + > @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'), inferrepo=True) > def debugwalk(ui, repo, *pats, **opts): > """show how files match on given patterns""" > diff --git a/tests/test-debugcommands.t b/tests/test-debugcommands.t > --- a/tests/test-debugcommands.t > +++ b/tests/test-debugcommands.t > @@ -126,3 +126,45 @@ > debugstacktrace.py:7 *in * (glob) > debugstacktrace.py:6 *in g (glob) > */util.py:* in debugstacktrace (glob) > + > +Test corruption-fixing debugtruncatestore command > + > + $ hg init corrupt-obsstore > + $ cd corrupt-obsstore > + $ cat >> .hg/hgrc << EOF > + > [experimental] > + > evolution = all > + > [extensions] > + > evolve = > + > EOF > + $ echo a > file > + $ hg commit -qAm file-a -d 1/1/2001 > + $ hg debugobsolete > + > + $ echo corrupt > .hg/store/obsstore > + $ hg debugobsolete 2> /dev/null > + [255] > + $ hg debugtruncatestore --obsolete > + truncated obsstore > + $ hg debugobsolete > + > + $ echo bee > file > + $ hg commit -qAm file-b -d 1/1/2001 > + $ echo b > file > + $ hg commit -qAm file-b -d 1/1/2001 > + $ hg fold -m file-b -r .^::. -d 1/1/2001 > + 2 changesets folded > + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved > + > + $ hg debugobsolete > + b429646ef7b48810f0aeaceb257eb589ff2e7e07 a7a6f057a0dda49d17b8ddb53d228494f939fed3 0 \(.*\) {'user': 'test'} (re) > + 12af3ef7db84073f5b5802cb875af46c6fc8f420 a7a6f057a0dda49d17b8ddb53d228494f939fed3 0 \(.*\) {'user': 'test'} (re) > + $ python -c 'print("CORRUPT" * 120)' >> .hg/store/obsstore > + $ hg debugobsolete 2> /dev/null > + [1] > + $ hg debugtruncatestore --obsolete > + truncated obsstore > + $ hg debugobsolete > + b429646ef7b48810f0aeaceb257eb589ff2e7e07 a7a6f057a0dda49d17b8ddb53d228494f939fed3 0 \(.*\) {'user': 'test'} (re) > + 12af3ef7db84073f5b5802cb875af46c6fc8f420 a7a6f057a0dda49d17b8ddb53d228494f939fed3 0 \(.*\) {'user': 'test'} (re) > + $ cd .. > _______________________________________________ > Mercurial-devel mailing list > Mercurial-devel@mercurial-scm.org > https://urldefense.proofpoint.com/v2/url?u=https-3A__www.mercurial-2Dscm.org_mailman_listinfo_mercurial-2Ddevel&d=CwIGaQ&c=5VD0RTtNlTh3ycd41b3MUw&r=mEgSWILcY4c4W3zjApBQLA&m=p_xFjDSVOdH4iVmx8TBt3nSw_eIhZVm5I-ouRP4Q65w&s=0YHHb8MBE5dd3qrFGGsU5Dvs9kQaoVOJLznOQ3GQf2I&e= >
Patch
diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -3702,6 +3702,62 @@ displayer.show(repo[r], **props) displayer.close() +@command('debugtruncatestore', + [('', 'obsolete', None, _('truncate bad markers in obsstore'))], + _('[OPTION]')) +def debugtruncatestore(ui, repo, **opts): + """Fix up repository corruption by truncating damaged files + + Most on-disk data structures are designed to be append-only. A failed write + (e.g. due to an unexpected power failure) can leave the file corrupted. + + This command attempts to recover from that situation by replacing the + corrupted file with a version that only contains the valid records from the + broken file. It is not guaranteed to remove all corrupt records - it will + only remove corrupt records where normal use of the repo would result in a + crash. + + Corrupt files will be renamed with a .corrupt extension before the fixed + version is written out, so that you can examine the corruption and/or undo + this command. + + You should normally use :hg:`recover` before resorting to this command. + """ + + if 'obsolete' in opts: + data = repo.svfs.tryread('obsstore') + if data: + # Slow algorithm - but this is an emergency debug operation + version = None + corrupt = False + while version is None and len(data) > 0: + try: + (version, markers) = obsolete._readmarkers(data) + # Force evaluation of all the markers - the pure + # implementation returns a generator which won't detonate + # until you evaluate the bad marker. + for marker in markers: + pass + except: + corrupt = True + version = None + data = data[:-1] + continue + break + if corrupt: + with repo.lock(): + repo.svfs. + repo.svfs.rename('obsstore', 'obsstore.corrupt') + if len(data) > 0: + repo.svfs.write('obsstore', data) + ui.write(_('truncated damaged obsstore\n')) + else: + ui.write(_('deleted unreadable obsstore\n')) + else: + ui.write(_('no damage to obsstore\n')) + else: + ui.write(_('no obsstore\n')) + @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'), inferrepo=True) def debugwalk(ui, repo, *pats, **opts): """show how files match on given patterns""" diff --git a/tests/test-debugcommands.t b/tests/test-debugcommands.t --- a/tests/test-debugcommands.t +++ b/tests/test-debugcommands.t @@ -126,3 +126,45 @@ debugstacktrace.py:7 *in * (glob) debugstacktrace.py:6 *in g (glob) */util.py:* in debugstacktrace (glob) + +Test corruption-fixing debugtruncatestore command + + $ hg init corrupt-obsstore + $ cd corrupt-obsstore + $ cat >> .hg/hgrc << EOF + > [experimental] + > evolution = all + > [extensions] + > evolve = + > EOF + $ echo a > file + $ hg commit -qAm file-a -d 1/1/2001 + $ hg debugobsolete + + $ echo corrupt > .hg/store/obsstore + $ hg debugobsolete 2> /dev/null + [255] + $ hg debugtruncatestore --obsolete + truncated obsstore + $ hg debugobsolete + + $ echo bee > file + $ hg commit -qAm file-b -d 1/1/2001 + $ echo b > file + $ hg commit -qAm file-b -d 1/1/2001 + $ hg fold -m file-b -r .^::. -d 1/1/2001 + 2 changesets folded + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ hg debugobsolete + b429646ef7b48810f0aeaceb257eb589ff2e7e07 a7a6f057a0dda49d17b8ddb53d228494f939fed3 0 \(.*\) {'user': 'test'} (re) + 12af3ef7db84073f5b5802cb875af46c6fc8f420 a7a6f057a0dda49d17b8ddb53d228494f939fed3 0 \(.*\) {'user': 'test'} (re) + $ python -c 'print("CORRUPT" * 120)' >> .hg/store/obsstore + $ hg debugobsolete 2> /dev/null + [1] + $ hg debugtruncatestore --obsolete + truncated obsstore + $ hg debugobsolete + b429646ef7b48810f0aeaceb257eb589ff2e7e07 a7a6f057a0dda49d17b8ddb53d228494f939fed3 0 \(.*\) {'user': 'test'} (re) + 12af3ef7db84073f5b5802cb875af46c6fc8f420 a7a6f057a0dda49d17b8ddb53d228494f939fed3 0 \(.*\) {'user': 'test'} (re) + $ cd ..