Commits

Yuya Nishihara committed 28e6526

hglib: helper to reorder patches without loss of guards and comments (refs #441)

Unlike HgRepoListModel.dropMimeData, this function won't lose meta data of
series file because it reads q.fullseries.

  • Participants
  • Parent commits edf75e4

Comments (0)

Files changed (3)

File tests/movemqpatches_test.py

+import os, tempfile
+from nose.tools import *
+
+from mercurial import hg, ui
+from tortoisehg.util import hglib
+
+import helpers
+
+def setup():
+    global _tmpdir
+    _tmpdir = helpers.mktmpdir(__name__)
+
+    hgcli = helpers.HgClient(os.path.join(_tmpdir, 'unapplied'))
+    hgcli.init()
+    hgcli.qinit('-c')
+    hgcli.qnew('foo')
+    hgcli.qnew('bar')
+    hgcli.qnew('baz')
+    hgcli.qnew('qux')
+    hgcli.qnew('quux')
+    hgcli.qpop('-a')
+    hgcli.qguard('bar', '+guard')
+    # series file may contain comment or empty line
+    hgcli.fwrite('.hg/patches/series',
+                 '# comment\n' + hgcli.fread('.hg/patches/series'))
+    hgcli.commit('--mq', '-m', 'patches')
+
+def openrepo(name):
+    dest = tempfile.mkdtemp('', name + '-clone', _tmpdir)
+    hgcli = helpers.HgClient(dest)
+    hgcli.qclone(os.path.join(_tmpdir, name), '.')
+    return hg.repository(ui.ui(), dest), hgcli
+
+
+def test_movefirst():
+    repo, hgcli = openrepo('unapplied')
+    hglib.movemqpatches(repo, None, ['baz'])
+    assert_equal(['baz', 'foo', 'bar', 'qux', 'quux'], repo.mq.series)
+    assert_equal(['baz', '# comment', 'foo', 'bar #+guard', 'qux', 'quux'],
+                 repo.mq.fullseries)
+
+def test_movelast():
+    repo, hgcli = openrepo('unapplied')
+    hglib.movemqpatches(repo, 'quux', ['baz'])
+    assert_equal(['foo', 'bar', 'qux', 'quux', 'baz'], repo.mq.series)
+    assert_equal(['# comment', 'foo', 'bar #+guard', 'qux', 'quux', 'baz'],
+                 repo.mq.fullseries)
+
+def test_moveintersect():
+    repo, hgcli = openrepo('unapplied')
+    hglib.movemqpatches(repo, 'baz', ['bar', 'quux'])
+    assert_equal(['foo', 'baz', 'bar', 'quux', 'qux'], repo.mq.series)
+    assert_equal(['# comment', 'foo', 'baz', 'bar #+guard', 'quux', 'qux'],
+                 repo.mq.fullseries)
+
+
+def test_moveafterapplied():
+    repo, hgcli = openrepo('unapplied')
+    hgcli.qpush('baz')
+    hglib.movemqpatches(repo, 'baz', ['quux'])
+    assert_equal(['foo', 'bar', 'baz', 'quux', 'qux'], repo.mq.series)
+
+def test_moveguarded():
+    repo, hgcli = openrepo('unapplied')
+    hgcli.qpush('baz')
+    hglib.movemqpatches(repo, 'baz', ['bar'])
+    assert_equal(['foo', 'baz', 'bar', 'qux', 'quux'], repo.mq.series)
+
+@raises(ValueError)
+def test_movebeforeapplied():
+    repo, hgcli = openrepo('unapplied')
+    hgcli.qpush('baz')
+    hglib.movemqpatches(repo, 'bar', ['quux'])
+
+@raises(ValueError)
+def test_moveapplied():
+    repo, hgcli = openrepo('unapplied')
+    hgcli.qpush('baz')
+    hglib.movemqpatches(repo, 'baz', ['foo'])
+
+
+@raises(ValueError)
+def test_unknownpatch():
+    repo, hgcli = openrepo('unapplied')
+    hglib.movemqpatches(repo, 'baz', ['unknown'])
+
+@raises(ValueError)
+def test_unknowninsertpos():
+    repo, hgcli = openrepo('unapplied')
+    hglib.movemqpatches(repo, 'unknown', ['foo'])
+
+@raises(ValueError)
+def test_selfinsertpos():
+    repo, hgcli = openrepo('unapplied')
+    hglib.movemqpatches(repo, 'foo', ['foo'])
+
+
+def test_save():
+    repo, hgcli = openrepo('unapplied')
+    hglib.movemqpatches(repo, 'baz', ['bar', 'quux'])
+    assert_equal('# comment\nfoo\nbaz\nbar #+guard\nquux\nqux\n',
+                 hgcli.fread('.hg/patches/series'))
+
+def test_locked():
+    repo, hgcli = openrepo('unapplied')
+    hgcli.ftouch('.hg/wlock')
+    r = hglib.movemqpatches(repo, 'baz', ['quux'])
+    assert_equals(False, r)

File tests/nosehgenv.py

             f.write('backout = -d "0 0"\n')
             f.write('commit = -d "0 0"\n')
             f.write('tag = -d "0 0"\n')
+            # TODO: run mq-dependent tests in separate process?
+            f.write('[extensions]\n')
+            f.write('mq=\n')
         finally:
             f.close()
 

File tortoisehg/util/hglib.py

         cur = cur[8:]
     return cur
 
+def _applymovemqpatches(q, after, patches):
+    fullindexes = dict((q.guard_re.split(rpn, 1)[0], i)
+                       for i, rpn in enumerate(q.fullseries))
+    fullmap = {}  # patch: line in series file
+    for i, n in sorted([(fullindexes[n], n) for n in patches], reverse=True):
+        fullmap[n] = q.fullseries.pop(i)
+    del fullindexes  # invalid
+
+    if after is None:
+        fullat = 0
+    else:
+        for i, rpn in enumerate(q.fullseries):
+            if q.guard_re.split(rpn, 1)[0] == after:
+                fullat = i + 1
+                break
+        else:
+            fullat = len(q.fullseries)  # last ditch (should not happen)
+    q.fullseries[fullat:fullat] = (fullmap[n] for n in patches)
+    q.parseseries()
+    q.seriesdirty = True
+
+# maybe this can be implemented as hg extension
+def movemqpatches(repo, after, patches):
+    """Move the given patches after the specified patch, or to the beginning
+    of the series if after is None"""
+    q = repo.mq
+    if util.any(n not in q.series for n in patches):
+        raise ValueError('unknown patch to move specified')
+    if after in patches:
+        raise ValueError('invalid patch position specified')
+    if util.any(q.isapplied(n) for n in patches):
+        raise ValueError('cannot move applied patches')
+
+    if after is None:
+        at = 0
+    else:
+        at = q.series.index(after) + 1
+    if at < q.seriesend(True):
+        raise ValueError('cannot move into applied patches')
+
+    try:
+        wlock = repo.wlock(False)  # no wait to avoid blocking GUI thread
+    except error.LockHeld:
+        return False
+    try:
+        _applymovemqpatches(q, after, patches)
+        try:
+            q.savedirty()
+            return True
+        except EnvironmentError:
+            return False
+    finally:
+        wlock.release()
+
 def enabledextensions():
     """Return the {name: shortdesc} dict of enabled extensions