changeset 17139:ad1b5e070f16

merge with main
author Martin Geisler <mg@aragost.com>
date Thu, 12 Jul 2012 10:03:50 +0200
parents 528cb91a90ee (current diff) 4fb2d3d16743 (diff)
children f7152a0d90df
files mercurial/localrepo.py mercurial/revlog.py
diffstat 12 files changed, 389 insertions(+), 136 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/histedit.py	Wed Jul 11 15:39:00 2012 -0700
+++ b/hgext/histedit.py	Thu Jul 12 10:03:50 2012 +0200
@@ -4,10 +4,142 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-"""Interactive history editing.
+"""interactive history editing
+
+With this extension installed, Mercurial gains one new command: histedit. Usage
+is as follows, assuming the following history::
+
+ @  3[tip]   7c2fd3b9020c   2009-04-27 18:04 -0500   durin42
+ |    Add delta
+ |
+ o  2   030b686bedc4   2009-04-27 18:04 -0500   durin42
+ |    Add gamma
+ |
+ o  1   c561b4e977df   2009-04-27 18:04 -0500   durin42
+ |    Add beta
+ |
+ o  0   d8d2fcd0e319   2009-04-27 18:04 -0500   durin42
+      Add alpha
+
+If you were to run ``hg histedit c561b4e977df``, you would see the following
+file open in your editor::
+
+ pick c561b4e977df Add beta
+ pick 030b686bedc4 Add gamma
+ pick 7c2fd3b9020c Add delta
+
+ # Edit history between 633536316234 and 7c2fd3b9020c
+ #
+ # Commands:
+ #  p, pick = use commit
+ #  e, edit = use commit, but stop for amending
+ #  f, fold = use commit, but fold into previous commit
+ #  d, drop = remove commit from history
+ #  m, mess = edit message without changing commit content
+ #
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+In this file, lines beginning with ``#`` are ignored. You must specify a rule
+for each revision in your history. For example, if you had meant to add gamma
+before beta, and then wanted to add delta in the same revision as beta, you
+would reorganize the file to look like this::
+
+ pick 030b686bedc4 Add gamma
+ pick c561b4e977df Add beta
+ fold 7c2fd3b9020c Add delta
+
+ # Edit history between 633536316234 and 7c2fd3b9020c
+ #
+ # Commands:
+ #  p, pick = use commit
+ #  e, edit = use commit, but stop for amending
+ #  f, fold = use commit, but fold into previous commit
+ #  d, drop = remove commit from history
+ #  m, mess = edit message without changing commit content
+ #
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+At which point you close the editor and ``histedit`` starts working. When you
+specify a ``fold`` operation, ``histedit`` will open an editor when it folds
+those revisions together, offering you a chance to clean up the commit message::
+
+ Add beta
+ ***
+ Add delta
 
-Inspired by git rebase --interactive.
+Edit the commit message to your liking, then close the editor. For
+this example, let's assume that the commit message was changed to
+``Add beta and delta.`` After histedit has run and had a chance to
+remove any old or temporary revisions it needed, the history looks
+like this::
+
+ @  2[tip]   989b4d060121   2009-04-27 18:04 -0500   durin42
+ |    Add beta and delta.
+ |
+ o  1   081603921c3f   2009-04-27 18:04 -0500   durin42
+ |    Add gamma
+ |
+ o  0   d8d2fcd0e319   2009-04-27 18:04 -0500   durin42
+      Add alpha
+
+Note that ``histedit`` does *not* remove any revisions (even its own temporary
+ones) until after it has completed all the editing operations, so it will
+probably perform several strip operations when it's done. For the above example,
+it had to run strip twice. Strip can be slow depending on a variety of factors,
+so you might need to be a little patient. You can choose to keep the original
+revisions by passing the ``--keep`` flag.
+
+The ``edit`` operation will drop you back to a command prompt,
+allowing you to edit files freely, or even use ``hg record`` to commit
+some changes as a separate commit. When you're done, any remaining
+uncommitted changes will be committed as well. When done, run ``hg
+histedit --continue`` to finish this step. You'll be prompted for a
+new commit message, but the default commit message will be the
+original message for the ``edit`` ed revision.
+
+The ``message`` operation will give you a chance to revise a commit
+message without changing the contents. It's a shortcut for doing
+``edit`` immediately followed by `hg histedit --continue``.
+
+If ``histedit`` encounters a conflict when moving a revision (while
+handling ``pick`` or ``fold``), it'll stop in a similar manner to
+``edit`` with the difference that it won't prompt you for a commit
+message when done. If you decide at this point that you don't like how
+much work it will be to rearrange history, or that you made a mistake,
+you can use ``hg histedit --abort`` to abandon the new changes you
+have made and return to the state before you attempted to edit your
+history.
+
+If we clone the example repository above and add three more changes, such that
+we have the following history::
+
+   @  6[tip]   038383181893   2009-04-27 18:04 -0500   stefan
+   |    Add theta
+   |
+   o  5   140988835471   2009-04-27 18:04 -0500   stefan
+   |    Add eta
+   |
+   o  4   122930637314   2009-04-27 18:04 -0500   stefan
+   |    Add zeta
+   |
+   o  3   836302820282   2009-04-27 18:04 -0500   stefan
+   |    Add epsilon
+   |
+   o  2   989b4d060121   2009-04-27 18:04 -0500   durin42
+   |    Add beta and delta.
+   |
+   o  1   081603921c3f   2009-04-27 18:04 -0500   durin42
+   |    Add gamma
+   |
+   o  0   d8d2fcd0e319   2009-04-27 18:04 -0500   durin42
+        Add alpha
+
+If you run ``hg histedit --outgoing`` on the clone then it is the same
+as running ``hg histedit 836302820282``. If you need plan to push to a
+repository that Mercurial does not detect to be related to the source
+repo, you can add a ``--force`` option.
 """
+
 try:
     import cPickle as pickle
 except ImportError:
@@ -243,7 +375,7 @@
                'mess': message,
                }
 def histedit(ui, repo, *parent, **opts):
-    """hg histedit <parent>
+    """interactively edit changeset history
     """
     # TODO only abort if we try and histedit mq patches, not just
     # blanket if mq patches are applied somewhere
@@ -307,7 +439,11 @@
             new = repo.commit(text=message, user=oldctx.user(),
                               date=oldctx.date(), extra=oldctx.extra())
 
-        if action in ('f', 'fold'):
+        # If we're resuming a fold and we have new changes, mark the
+        # replacements and finish the fold. If not, it's more like a
+        # drop of the changesets that disappeared, and we can skip
+        # this step.
+        if action in ('f', 'fold') and (new or newchildren):
             if new:
                 tmpnodes.append(new)
             else:
@@ -396,13 +532,11 @@
         (parentctx, created_, replaced_, tmpnodes_) = actiontable[action](
             ui, repo, parentctx, ha, opts)
 
-        hexshort = lambda x: node.hex(x)[:12]
-
         if replaced_:
             clen, rlen = len(created_), len(replaced_)
             if clen == rlen == 1:
                 ui.debug('histedit: exact replacement of %s with %s\n' % (
-                    hexshort(replaced_[0]), hexshort(created_[0])))
+                    node.short(replaced_[0]), node.short(created_[0])))
 
                 replacemap[replaced_[0]] = created_[0]
             elif clen > rlen:
@@ -412,7 +546,7 @@
                 # TODO synthesize patch names for created patches
                 replacemap[replaced_[0]] = created_[-1]
                 ui.debug('histedit: created many, assuming %s replaced by %s' %
-                         (hexshort(replaced_[0]), hexshort(created_[-1])))
+                         (node.short(replaced_[0]), node.short(created_[-1])))
             elif rlen > clen:
                 if not created_:
                     # This must be a drop. Try and put our metadata on
@@ -420,7 +554,7 @@
                     assert rlen == 1
                     r = replaced_[0]
                     ui.debug('histedit: %s seems replaced with nothing, '
-                            'finding a parent\n' % (hexshort(r)))
+                            'finding a parent\n' % (node.short(r)))
                     pctx = repo[r].parents()[0]
                     if pctx.node() in replacemap:
                         ui.debug('histedit: parent is already replaced\n')
@@ -428,12 +562,12 @@
                     else:
                         replacemap[r] = pctx.node()
                     ui.debug('histedit: %s best replaced by %s\n' % (
-                        hexshort(r), hexshort(replacemap[r])))
+                        node.short(r), node.short(replacemap[r])))
                 else:
                     assert len(created_) == 1
                     for r in replaced_:
                         ui.debug('histedit: %s replaced by %s\n' % (
-                            hexshort(r), hexshort(created_[0])))
+                            node.short(r), node.short(created_[0])))
                         replacemap[r] = created_[0]
             else:
                 assert False, (
@@ -456,8 +590,8 @@
                     return
                 while new in replacemap:
                     new = replacemap[new]
-                ui.note(_('histedit:  %s to %s\n') % (hexshort(old),
-                                                      hexshort(new)))
+                ui.note(_('histedit:  %s to %s\n') % (node.short(old),
+                                                      node.short(new)))
                 octx = repo[old]
                 marks = octx.bookmarks()
                 if marks:
@@ -559,6 +693,6 @@
               'force outgoing even for unrelated repositories')),
           ('r', 'rev', [], _('first revision to be edited')),
           ],
-         __doc__,
+         _("[PARENT]"),
          ),
 }
--- a/hgext/largefiles/basestore.py	Wed Jul 11 15:39:00 2012 -0700
+++ b/hgext/largefiles/basestore.py	Thu Jul 12 10:03:50 2012 +0200
@@ -48,8 +48,8 @@
         '''Put source file into the store under <filename>/<hash>.'''
         raise NotImplementedError('abstract method')
 
-    def exists(self, hash):
-        '''Check to see if the store contains the given hash.'''
+    def exists(self, hashes):
+        '''Check to see if the store contains the given hashes.'''
         raise NotImplementedError('abstract method')
 
     def get(self, files):
--- a/hgext/largefiles/lfcommands.py	Wed Jul 11 15:39:00 2012 -0700
+++ b/hgext/largefiles/lfcommands.py	Thu Jul 12 10:03:50 2012 +0200
@@ -340,7 +340,11 @@
     store = basestore._openstore(rsrc, rdst, put=True)
 
     at = 0
-    files = filter(lambda h: not store.exists(h), files)
+    ui.debug("sending statlfile command for %d largefiles\n" % len(files))
+    retval = store.exists(files)
+    files = filter(lambda h: not retval[h], files)
+    ui.debug("%d largefiles need to be uploaded\n" % len(files))
+
     for hash in files:
         ui.progress(_('uploading largefiles'), at, unit='largefile',
                     total=len(files))
--- a/hgext/largefiles/proto.py	Wed Jul 11 15:39:00 2012 -0700
+++ b/hgext/largefiles/proto.py	Thu Jul 12 10:03:50 2012 +0200
@@ -7,6 +7,7 @@
 import urllib2
 
 from mercurial import error, httprepo, util, wireproto
+from mercurial.wireproto import batchable, future
 from mercurial.i18n import _
 
 import lfutil
@@ -119,15 +120,19 @@
                                                 length))
             return (length, stream)
 
+        @batchable
         def statlfile(self, sha):
+            f = future()
+            result = {'sha': sha}
+            yield result, f
             try:
-                return int(self._call("statlfile", sha=sha))
+                yield int(f.value)
             except (ValueError, urllib2.HTTPError):
                 # If the server returns anything but an integer followed by a
                 # newline, newline, it's not speaking our language; if we get
                 # an HTTP error, we can't be sure the largefile is present;
                 # either way, consider it missing.
-                return 2
+                yield 2
 
     repo.__class__ = lfileswirerepository
 
--- a/hgext/largefiles/remotestore.py	Wed Jul 11 15:39:00 2012 -0700
+++ b/hgext/largefiles/remotestore.py	Thu Jul 12 10:03:50 2012 +0200
@@ -10,6 +10,7 @@
 
 from mercurial import util
 from mercurial.i18n import _
+from mercurial.wireproto import remotebatch
 
 import lfutil
 import basestore
@@ -20,8 +21,6 @@
         super(remotestore, self).__init__(ui, repo, url)
 
     def put(self, source, hash):
-        if self._verify(hash):
-            return
         if self.sendfile(source, hash):
             raise util.Abort(
                 _('remotestore: could not put %s to remote store %s')
@@ -29,8 +28,8 @@
         self.ui.debug(
             _('remotestore: put %s to remote store %s') % (source, self.url))
 
-    def exists(self, hash):
-        return self._verify(hash)
+    def exists(self, hashes):
+        return self._verify(hashes)
 
     def sendfile(self, filename, hash):
         self.ui.debug('remotestore: sendfile(%s, %s)\n' % (filename, hash))
@@ -74,8 +73,8 @@
             infile = lfutil.limitreader(infile, length)
         return lfutil.copyandhash(lfutil.blockstream(infile), tmpfile)
 
-    def _verify(self, hash):
-        return not self._stat(hash)
+    def _verify(self, hashes):
+        return self._stat(hashes)
 
     def _verifyfile(self, cctx, cset, contents, standin, verified):
         filename = lfutil.splitstandin(standin)
@@ -104,3 +103,8 @@
         else:
             raise RuntimeError('verify failed: unexpected response from '
                                'statlfile (%r)' % stat)
+
+    def batch(self):
+        '''Support for remote batching.'''
+        return remotebatch(self)
+
--- a/hgext/largefiles/wirestore.py	Wed Jul 11 15:39:00 2012 -0700
+++ b/hgext/largefiles/wirestore.py	Thu Jul 12 10:03:50 2012 +0200
@@ -25,5 +25,13 @@
     def _get(self, hash):
         return self.remote.getlfile(hash)
 
-    def _stat(self, hash):
-        return self.remote.statlfile(hash)
+    def _stat(self, hashes):
+        batch = self.remote.batch()
+        futures = {}
+        for hash in hashes:
+            futures[hash] = batch.statlfile(hash)
+        batch.submit()
+        retval = {}
+        for hash in hashes:
+            retval[hash] = not futures[hash].value
+        return retval
--- a/mercurial/commands.py	Wed Jul 11 15:39:00 2012 -0700
+++ b/mercurial/commands.py	Thu Jul 12 10:03:50 2012 +0200
@@ -2061,17 +2061,22 @@
         succs = tuple(bin(succ) for succ in successors)
         l = repo.lock()
         try:
-            repo.obsstore.create(bin(precursor), succs, 0, metadata)
+            tr = repo.transaction('debugobsolete')
+            try:
+                repo.obsstore.create(tr, bin(precursor), succs, 0, metadata)
+                tr.close()
+            finally:
+                tr.release()
         finally:
             l.release()
     else:
-        for mctx in obsolete.allmarkers(repo):
-            ui.write(hex(mctx.precnode()))
-            for repl in mctx.succnodes():
+        for m in obsolete.allmarkers(repo):
+            ui.write(hex(m.precnode()))
+            for repl in m.succnodes():
                 ui.write(' ')
                 ui.write(hex(repl))
-            ui.write(' %X ' % mctx._data[2])
-            ui.write(mctx.metadata())
+            ui.write(' %X ' % m._data[2])
+            ui.write(m.metadata())
             ui.write('\n')
 
 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
--- a/mercurial/localrepo.py	Wed Jul 11 15:39:00 2012 -0700
+++ b/mercurial/localrepo.py	Thu Jul 12 10:03:50 2012 +0200
@@ -197,10 +197,7 @@
 
     @storecache('obsstore')
     def obsstore(self):
-        store = obsolete.obsstore()
-        data = self.sopener.tryread('obsstore')
-        if data:
-            store.loadmarkers(data)
+        store = obsolete.obsstore(self.sopener)
         return store
 
     @storecache('00changelog.i')
@@ -994,16 +991,6 @@
             self.store.write()
             if '_phasecache' in vars(self):
                 self._phasecache.write()
-            if 'obsstore' in vars(self) and self.obsstore._new:
-                # XXX: transaction logic should be used here. But for
-                # now rewriting the whole file is good enough.
-                f = self.sopener('obsstore', 'wb', atomictemp=True)
-                try:
-                    self.obsstore.flushmarkers(f)
-                    f.close()
-                except: # re-raises
-                    f.discard()
-                    raise
             for k, ce in self._filecache.items():
                 if k == 'dirstate':
                     continue
@@ -1622,6 +1609,10 @@
         return r
 
     def pull(self, remote, heads=None, force=False):
+        # don't open transaction for nothing or you break future useful
+        # rollback call
+        tr = None
+        trname = 'pull\n' + util.hidepassword(remote.url())
         lock = self.lock()
         try:
             tmp = discovery.findcommonincoming(self, remote, heads=heads,
@@ -1632,6 +1623,7 @@
                 added = []
                 result = 0
             else:
+                tr = self.transaction(trname)
                 if heads is None and list(common) == [nullid]:
                     self.ui.status(_("requesting all changes\n"))
                 elif heads is None and remote.capable('changegroupsubset'):
@@ -1680,9 +1672,15 @@
 
             remoteobs = remote.listkeys('obsolete')
             if 'dump' in remoteobs:
+                if tr is None:
+                    tr = self.transaction(trname)
                 data = base85.b85decode(remoteobs['dump'])
-                self.obsstore.mergemarkers(data)
+                self.obsstore.mergemarkers(tr, data)
+            if tr is not None:
+                tr.close()
         finally:
+            if tr is not None:
+                tr.release()
             lock.release()
 
         return result
@@ -1823,9 +1821,8 @@
                             self.ui.warn(_('updating %s to public failed!\n')
                                             % newremotehead)
                 if 'obsolete' in self.listkeys('namespaces') and self.obsstore:
-                    data = self.obsstore._writemarkers()
-                    r = remote.pushkey('obsolete', 'dump', '',
-                                       base85.b85encode(data))
+                    data = self.listkeys('obsolete')['dump']
+                    r = remote.pushkey('obsolete', 'dump', '', data)
                     if not r:
                         self.ui.warn(_('failed to push obsolete markers!\n'))
             finally:
--- a/mercurial/obsolete.py	Wed Jul 11 15:39:00 2012 -0700
+++ b/mercurial/obsolete.py	Thu Jul 12 10:03:50 2012 +0200
@@ -156,12 +156,16 @@
     - successors: new -> set(old)
     """
 
-    def __init__(self):
+    def __init__(self, sopener):
         self._all = []
         # new markers to serialize
-        self._new = []
         self.precursors = {}
         self.successors = {}
+        self.sopener = sopener
+        data = sopener.tryread('obsstore')
+        if data:
+            for marker in _readmarkers(data):
+                self._load(marker)
 
     def __iter__(self):
         return iter(self._all)
@@ -169,7 +173,7 @@
     def __nonzero__(self):
         return bool(self._all)
 
-    def create(self, prec, succs=(), flag=0, metadata=None):
+    def create(self, transaction, prec, succs=(), flag=0, metadata=None):
         """obsolete: add a new obsolete marker
 
         * ensuring it is hashable
@@ -184,33 +188,33 @@
             if len(succ) != 20:
                 raise ValueError(succ)
         marker = (str(prec), tuple(succs), int(flag), encodemeta(metadata))
-        self.add(marker)
-
-    def add(self, marker):
-        """Add a new marker to the store
+        self.add(transaction, marker)
 
-        This marker still needs to be written to disk"""
-        self._new.append(marker)
-        self._load(marker)
-
-    def loadmarkers(self, data):
-        """Load all markers in data, mark them as known."""
-        for marker in _readmarkers(data):
+    def add(self, transaction, marker):
+        """Add a new marker to the store"""
+        if marker not in self._all:
+            f = self.sopener('obsstore', 'ab')
+            try:
+                offset = f.tell()
+                transaction.add('obsstore', offset)
+                if offset == 0:
+                    # new file add version header
+                    f.write(_pack('>B', _fmversion))
+                _writemarkers(f.write, [marker])
+            finally:
+                # XXX: f.close() == filecache invalidation == obsstore rebuilt.
+                # call 'filecacheentry.refresh()'  here
+                f.close()
             self._load(marker)
 
-    def mergemarkers(self, data):
-        other = set(_readmarkers(data))
+    def mergemarkers(self, transation, data):
+        other = _readmarkers(data)
         local = set(self._all)
-        new = other - local
+        new = [m for m in other if m not in local]
         for marker in new:
-            self.add(marker)
-
-    def flushmarkers(self, stream):
-        """Write all markers to a stream
-
-        After this operation, "new" markers are considered "known"."""
-        self._writemarkers(stream)
-        self._new[:] = []
+            # XXX: N marker == N x (open, write, close)
+            # we should write them all at once
+            self.add(transation, marker)
 
     def _load(self, marker):
         self._all.append(marker)
@@ -219,32 +223,25 @@
         for suc in sucs:
             self.successors.setdefault(suc, set()).add(marker)
 
-    def _writemarkers(self, stream=None):
-        # Kept separate from flushmarkers(), it will be reused for
-        # markers exchange.
-        if stream is None:
-            final = []
-            w = final.append
-        else:
-            w = stream.write
-        w(_pack('>B', _fmversion))
-        for marker in self._all:
-            pre, sucs, flags, metadata = marker
-            nbsuc = len(sucs)
-            format = _fmfixed + (_fmnode * nbsuc)
-            data = [nbsuc, len(metadata), flags, pre]
-            data.extend(sucs)
-            w(_pack(format, *data))
-            w(metadata)
-        if stream is None:
-            return ''.join(final)
+def _writemarkers(write, markers):
+    # Kept separate from flushmarkers(), it will be reused for
+    # markers exchange.
+    for marker in markers:
+        pre, sucs, flags, metadata = marker
+        nbsuc = len(sucs)
+        format = _fmfixed + (_fmnode * nbsuc)
+        data = [nbsuc, len(metadata), flags, pre]
+        data.extend(sucs)
+        write(_pack(format, *data))
+        write(metadata)
 
 def listmarkers(repo):
     """List markers over pushkey"""
     if not repo.obsstore:
         return {}
-    data = repo.obsstore._writemarkers()
-    return {'dump': base85.b85encode(data)}
+    data = [_pack('>B', _fmversion)]
+    _writemarkers(data.append, repo.obsstore)
+    return {'dump': base85.b85encode(''.join(data))}
 
 def pushmarker(repo, key, old, new):
     """Push markers over pushkey"""
@@ -257,8 +254,13 @@
     data = base85.b85decode(new)
     lock = repo.lock()
     try:
-        repo.obsstore.mergemarkers(data)
-        return 1
+        tr = repo.transaction('pushkey: obsolete markers')
+        try:
+            repo.obsstore.mergemarkers(tr, data)
+            tr.close()
+            return 1
+        finally:
+            tr.release()
     finally:
         lock.release()
 
--- a/mercurial/revlog.py	Wed Jul 11 15:39:00 2012 -0700
+++ b/mercurial/revlog.py	Thu Jul 12 10:03:50 2012 +0200
@@ -75,35 +75,6 @@
     s.update(text)
     return s.digest()
 
-def compress(text):
-    """ generate a possibly-compressed representation of text """
-    if not text:
-        return ("", text)
-    l = len(text)
-    bin = None
-    if l < 44:
-        pass
-    elif l > 1000000:
-        # zlib makes an internal copy, thus doubling memory usage for
-        # large files, so lets do this in pieces
-        z = zlib.compressobj()
-        p = []
-        pos = 0
-        while pos < l:
-            pos2 = pos + 2**20
-            p.append(z.compress(text[pos:pos2]))
-            pos = pos2
-        p.append(z.flush())
-        if sum(map(len, p)) < l:
-            bin = "".join(p)
-    else:
-        bin = _compress(text)
-    if bin is None or len(bin) > l:
-        if text[0] == '\0':
-            return ("", text)
-        return ('u', text)
-    return ("", bin)
-
 def decompress(bin):
     """ decompress the given input """
     if not bin:
@@ -1017,6 +988,35 @@
                 dfh.close()
             ifh.close()
 
+    def compress(self, text):
+        """ generate a possibly-compressed representation of text """
+        if not text:
+            return ("", text)
+        l = len(text)
+        bin = None
+        if l < 44:
+            pass
+        elif l > 1000000:
+            # zlib makes an internal copy, thus doubling memory usage for
+            # large files, so lets do this in pieces
+            z = zlib.compressobj()
+            p = []
+            pos = 0
+            while pos < l:
+                pos2 = pos + 2**20
+                p.append(z.compress(text[pos:pos2]))
+                pos = pos2
+            p.append(z.flush())
+            if sum(map(len, p)) < l:
+                bin = "".join(p)
+        else:
+            bin = _compress(text)
+        if bin is None or len(bin) > l:
+            if text[0] == '\0':
+                return ("", text)
+            return ('u', text)
+        return ("", bin)
+
     def _addrevision(self, node, text, transaction, link, p1, p2,
                      cachedelta, ifh, dfh):
         """internal function to add revisions to the log
@@ -1049,7 +1049,7 @@
                 t = buildtext()
                 ptext = self.revision(self.node(rev))
                 delta = mdiff.textdiff(ptext, t)
-            data = compress(delta)
+            data = self.compress(delta)
             l = len(data[1]) + len(data[0])
             if basecache[0] == rev:
                 chainbase = basecache[1]
@@ -1094,7 +1094,7 @@
             textlen = len(text)
         if d is None or dist > textlen * 2:
             text = buildtext()
-            data = compress(text)
+            data = self.compress(text)
             l = len(data[1]) + len(data[0])
             base = chainbase = curr
 
--- a/tests/test-histedit-fold.t	Wed Jul 11 15:39:00 2012 -0700
+++ b/tests/test-histedit-fold.t	Thu Jul 12 10:03:50 2012 +0200
@@ -108,3 +108,78 @@
   f
 
   $ cd ..
+
+folding and creating no new change doesn't break:
+  $ mkdir fold-to-empty-test
+  $ cd fold-to-empty-test
+  $ hg init
+  $ printf "1\n2\n3\n" > file
+  $ hg add file
+  $ hg commit -m '1+2+3'
+  $ echo 4 >> file
+  $ hg commit -m '+4'
+  $ echo 5 >> file
+  $ hg commit -m '+5'
+  $ echo 6 >> file
+  $ hg commit -m '+6'
+  $ hg log --graph
+  @  changeset:   3:251d831eeec5
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     +6
+  |
+  o  changeset:   2:888f9082bf99
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     +5
+  |
+  o  changeset:   1:617f94f13c0f
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     +4
+  |
+  o  changeset:   0:0189ba417d34
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     1+2+3
+  
+
+  $ cat > editor.py <<EOF
+  > import re, sys
+  > rules = sys.argv[1]
+  > data = open(rules).read()
+  > data = re.sub(r'pick ([0-9a-f]{12} 2 \+5)', r'drop \1', data)
+  > data = re.sub(r'pick ([0-9a-f]{12} 2 \+6)', r'fold \1', data)
+  > open(rules, 'w').write(data)
+  > EOF
+
+  $ HGEDITOR='python editor.py' hg histedit 1
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  patching file file
+  Hunk #1 FAILED at 2
+  1 out of 1 hunks FAILED -- saving rejects to file file.rej
+  abort: Fix up the change and run hg histedit --continue
+  [255]
+There were conflicts, but we'll continue without resolving. This
+should effectively drop the changes from +6.
+  $ hg status
+  ? editor.py
+  ? file.rej
+  $ hg histedit --continue
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/*-backup.hg (glob)
+  $ hg log --graph
+  @  changeset:   1:617f94f13c0f
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     +4
+  |
+  o  changeset:   0:0189ba417d34
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     1+2+3
+  
+
+  $ cd ..
--- a/tests/test-obsolete.t	Wed Jul 11 15:39:00 2012 -0700
+++ b/tests/test-obsolete.t	Thu Jul 12 10:03:50 2012 +0200
@@ -158,9 +158,28 @@
   added 6 changesets with 6 changes to 6 files (+3 heads)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg debugobsolete
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
   ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
+  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
+
+Rollback//Transaction support
+
+  $ hg debugobsolete -d '1340 0' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+  $ hg debugobsolete
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
   cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
+  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
+  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
+  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 0 {'date': '1340 0', 'user': 'test'}
+  $ hg rollback -n
+  repository tip rolled back to revision 5 (undo debugobsolete)
+  $ hg rollback
+  repository tip rolled back to revision 5 (undo debugobsolete)
+  $ hg debugobsolete
   245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
+  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
   1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
 
   $ cd ..
@@ -176,9 +195,9 @@
   adding file changes
   added 6 changesets with 6 changes to 6 files (+3 heads)
   $ hg -R tmpd debugobsolete
-  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
   cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
-  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
+  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
   1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
 
 
@@ -200,9 +219,9 @@
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg debugobsolete
   2448244824482448244824482448244824482448 1339133913391339133913391339133913391339 0 {'date': '1339 0', 'user': 'test'}
-  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
   cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
-  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
+  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
   1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
 
 On push
@@ -213,8 +232,8 @@
   no changes found
   [1]
   $ hg -R ../tmpc debugobsolete
-  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
   cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
-  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
+  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
   1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
   2448244824482448244824482448244824482448 1339133913391339133913391339133913391339 0 {'date': '1339 0', 'user': 'test'}