{"thread":{"id":"32006","subject":"[PATCH 0/3] New remote-bzr remote helper","startedAt":"2012-11-04T02:22:00Z","lastAt":"2012-11-04T02:22:03Z","messageCount":4,"participants":["Felipe Contreras"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"202491","messageId":"1351995723-20396-1-git-send-email-felipe.contreras@gmail.com","threadId":"32006","inReplyTo":null,"subject":"[PATCH 0/3] New remote-bzr remote helper","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-11-04T02:22:00Z","receivedAt":"2012-11-04T02:22:00Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Hi,\n\nThis is a proof-of-concept remote helper for bzr, that turns out to work rather well.\n\nIt uses bzr-fastimport[1], which is what most current bzr<->git tools uses, but\nthe quality is not that great. After applying an important fix[2], it works\nnicely, but you will get tons of verbose messages.\n\nEventually we might want to implement the same functionality as bzr-fastimport,\nbut for now this already works nicely enough.\n\nCheers.\n\n[1] http://wiki.bazaar.canonical.com/BzrFastImport\n[2] https://launchpadlibrarian.net/121967844/bzr-fastimport-no-graph.patch\n\nFelipe Contreras (3):\n  Add new remote-bzr transport helper\n  remote-bzr: add support for pushing\n  remote-bzr: add simple tests\n\n contrib/remote-helpers/git-remote-bzr | 221 ++++++++++++++++++++++++++++++++++\n contrib/remote-helpers/test-bzr.sh    | 111 +++++++++++++++++\n 2 files changed, 332 insertions(+)\n create mode 100755 contrib/remote-helpers/git-remote-bzr\n create mode 100755 contrib/remote-helpers/test-bzr.sh\n\n-- \n1.8.0\n"},{"id":"202492","messageId":"1351995723-20396-2-git-send-email-felipe.contreras@gmail.com","threadId":"32006","inReplyTo":"1351995723-20396-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH 1/3] Add new remote-bzr transport helper","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-11-04T02:22:01Z","receivedAt":"2012-11-04T02:22:01Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-helpers/git-remote-bzr | 187 ++++++++++++++++++++++++++++++++++\n 1 file changed, 187 insertions(+)\n create mode 100755 contrib/remote-helpers/git-remote-bzr\n\ndiff --git a/contrib/remote-helpers/git-remote-bzr b/contrib/remote-helpers/git-remote-bzr\nnew file mode 100755\nindex 0000000..76a609a\n--- /dev/null\n+++ b/contrib/remote-helpers/git-remote-bzr\n@@ -0,0 +1,187 @@\n+#!/usr/bin/env python\n+#\n+# Copyright (c) 2012 Felipe Contreras\n+#\n+\n+# Just copy to your ~/bin, or anywhere in your $PATH.\n+# Then you can clone with:\n+# git clone bzr::/path/to/mercurial/repo/\n+#\n+# You need to have bzr-fastimport installed:\n+# http://wiki.bazaar.canonical.com/BzrFastImport\n+#\n+# You might also need to find this line bzr-fastimport's\n+# code, and modify it:\n+#\n+# self._use_known_graph = False\n+\n+import sys\n+\n+import bzrlib\n+bzrlib.initialize()\n+\n+import bzrlib.plugin\n+bzrlib.plugin.load_plugins()\n+\n+import bzrlib.revisionspec\n+\n+from bzrlib.plugins.fastimport import exporter as bzr_exporter\n+\n+import sys\n+import os\n+import json\n+\n+def die(msg, *args):\n+    sys.stderr.write('ERROR: %s\\n' % (msg % args))\n+    sys.exit(1)\n+\n+def warn(msg, *args):\n+    sys.stderr.write('WARNING: %s\\n' % (msg % args))\n+\n+class Marks:\n+\n+    def __init__(self, path):\n+        self.path = path\n+        self.tips = {}\n+        self.load()\n+\n+    def load(self):\n+        if not os.path.exists(self.path):\n+            return\n+\n+        tmp = json.load(open(self.path))\n+        self.tips = tmp['tips']\n+\n+    def dict(self):\n+        return { 'tips': self.tips }\n+\n+    def store(self):\n+        json.dump(self.dict(), open(self.path, 'w'))\n+\n+    def __str__(self):\n+        return str(self.dict())\n+\n+    def get_tip(self, branch):\n+        return self.tips.get(branch, '1')\n+\n+    def set_tip(self, branch, tip):\n+        self.tips[branch] = tip\n+\n+class Parser:\n+\n+    def __init__(self, repo):\n+        self.repo = repo\n+        self.line = self.get_line()\n+\n+    def get_line(self):\n+        return sys.stdin.readline().strip()\n+\n+    def __getitem__(self, i):\n+        return self.line.split()[i]\n+\n+    def check(self, word):\n+        return self.line.startswith(word)\n+\n+    def each_block(self, separator):\n+        while self.line != separator:\n+            yield self.line\n+            self.line = self.get_line()\n+\n+    def __iter__(self):\n+        return self.each_block('')\n+\n+    def next(self):\n+        self.line = self.get_line()\n+        if self.line == 'done':\n+            self.line = None\n+\n+def export_branch(branch, name):\n+    global prefix, dirname\n+\n+    marks_path = os.path.join(dirname, 'marks-bzr')\n+    ref = '%s/heads/%s' % (prefix, name)\n+    tip = marks.get_tip(name)\n+    start = \"before:%s\" % tip\n+    rev1 = bzrlib.revisionspec.RevisionSpec.from_string(start)\n+    rev2 = bzrlib.revisionspec.RevisionSpec.from_string(None)\n+\n+    exporter = bzr_exporter.BzrFastExporter(branch,\n+        outf=sys.stdout, ref=ref,\n+        checkpoint=None,\n+        import_marks_file=marks_path,\n+        export_marks_file=marks_path,\n+        revision=[rev1, rev2],\n+        verbose=None, plain_format=True,\n+        rewrite_tags=False)\n+    exporter.run()\n+\n+    marks.set_tip(name, branch.last_revision())\n+\n+def do_import(parser):\n+    global dirname\n+\n+    branch = parser.repo\n+    path = os.path.join(dirname, 'marks-git')\n+\n+    print \"feature done\"\n+    if os.path.exists(path):\n+        print \"feature import-marks=%s\" % path\n+    print \"feature export-marks=%s\" % path\n+    sys.stdout.flush()\n+\n+    while parser.check('import'):\n+        ref = parser[1]\n+        if ref.startswith('refs/heads/'):\n+            name = ref[len('refs/heads/'):]\n+            export_branch(branch, name)\n+        parser.next()\n+\n+    print 'done'\n+\n+    sys.stdout.flush()\n+\n+def do_capabilities(parser):\n+    print \"import\"\n+    print \"refspec refs/heads/*:%s/heads/*\" % prefix\n+    print\n+\n+def do_list(parser):\n+    print \"? refs/heads/%s\" % 'master'\n+    print \"@refs/heads/%s HEAD\" % 'master'\n+    print\n+\n+def main(args):\n+    global marks, prefix, dirname\n+\n+    alias = args[1]\n+    url = args[2]\n+\n+    d = bzrlib.controldir.ControlDir.open(url)\n+    repo = d.open_branch()\n+\n+    prefix = 'refs/bzr/%s' % alias\n+\n+    gitdir = os.environ['GIT_DIR']\n+    dirname = os.path.join(gitdir, 'bzr', alias)\n+\n+    if not os.path.exists(dirname):\n+        os.makedirs(dirname)\n+\n+    marks_path = os.path.join(dirname, 'marks-int')\n+    marks = Marks(marks_path)\n+\n+    parser = Parser(repo)\n+    for line in parser:\n+        if parser.check('capabilities'):\n+            do_capabilities(parser)\n+        elif parser.check('list'):\n+            do_list(parser)\n+        elif parser.check('import'):\n+            do_import(parser)\n+        else:\n+            die('unhandled command: %s' % line)\n+        sys.stdout.flush()\n+\n+    marks.store()\n+\n+sys.exit(main(sys.argv))\n-- \n1.8.0\n"},{"id":"202494","messageId":"1351995723-20396-3-git-send-email-felipe.contreras@gmail.com","threadId":"32006","inReplyTo":"1351995723-20396-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH 2/3] remote-bzr: add support for pushing","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-11-04T02:22:02Z","receivedAt":"2012-11-04T02:22:02Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-helpers/git-remote-bzr | 34 ++++++++++++++++++++++++++++++++++\n 1 file changed, 34 insertions(+)\n\ndiff --git a/contrib/remote-helpers/git-remote-bzr b/contrib/remote-helpers/git-remote-bzr\nindex 76a609a..de37217 100755\n--- a/contrib/remote-helpers/git-remote-bzr\n+++ b/contrib/remote-helpers/git-remote-bzr\n@@ -26,6 +26,9 @@ bzrlib.plugin.load_plugins()\n import bzrlib.revisionspec\n \n from bzrlib.plugins.fastimport import exporter as bzr_exporter\n+from bzrlib.plugins.fastimport.processors import generic_processor as bzr_generic_processor\n+\n+import fastimport.parser\n \n import sys\n import os\n@@ -140,9 +143,38 @@ def do_import(parser):\n \n     sys.stdout.flush()\n \n+def do_export(parser):\n+    global dirname\n+\n+    parser.next() # can't handle 'done'?\n+\n+    controldir = parser.repo.bzrdir\n+\n+    path = os.path.join(dirname, 'marks-bzr')\n+    params = { 'import-marks': path, 'export-marks': path }\n+\n+    proc = bzr_generic_processor.GenericProcessor(controldir, params)\n+    p = fastimport.parser.ImportParser(sys.stdin)\n+    proc.process(p.iter_commands)\n+\n+    if parser.repo.last_revision() != marks.get_tip('master'):\n+        print 'ok %s' % 'refs/heads/master'\n+\n+    print\n+\n def do_capabilities(parser):\n+    global dirname\n+\n     print \"import\"\n+    print \"export\"\n     print \"refspec refs/heads/*:%s/heads/*\" % prefix\n+\n+    path = os.path.join(dirname, 'marks-git')\n+\n+    if os.path.exists(path):\n+        print \"*import-marks %s\" % path\n+    print \"*export-marks %s\" % path\n+\n     print\n \n def do_list(parser):\n@@ -178,6 +210,8 @@ def main(args):\n             do_list(parser)\n         elif parser.check('import'):\n             do_import(parser)\n+        elif parser.check('export'):\n+            do_export(parser)\n         else:\n             die('unhandled command: %s' % line)\n         sys.stdout.flush()\n-- \n1.8.0\n"},{"id":"202493","messageId":"1351995723-20396-4-git-send-email-felipe.contreras@gmail.com","threadId":"32006","inReplyTo":"1351995723-20396-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH 3/3] remote-bzr: add simple tests","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-11-04T02:22:03Z","receivedAt":"2012-11-04T02:22:03Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-helpers/test-bzr.sh | 111 +++++++++++++++++++++++++++++++++++++\n 1 file changed, 111 insertions(+)\n create mode 100755 contrib/remote-helpers/test-bzr.sh\n\ndiff --git a/contrib/remote-helpers/test-bzr.sh b/contrib/remote-helpers/test-bzr.sh\nnew file mode 100755\nindex 0000000..8594ffc\n--- /dev/null\n+++ b/contrib/remote-helpers/test-bzr.sh\n@@ -0,0 +1,111 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2012 Felipe Contreras\n+#\n+\n+test_description='Test remote-bzr'\n+\n+. ./test-lib.sh\n+\n+if ! test_have_prereq PYTHON; then\n+\tskip_all='skipping remote-bzr tests; python not available'\n+\ttest_done\n+fi\n+\n+if ! \"$PYTHON_PATH\" -c 'import bzrlib'; then\n+\tskip_all='skipping remote-bzr tests; bzr not available'\n+\ttest_done\n+fi\n+\n+cmd=<<EOF\n+import bzrlib\n+bzrlib.initialize()\n+import bzrlib.plugin\n+bzrlib.plugin.load_plugins()\n+import bzrlib.plugins.fastimport\n+EOF\n+\n+if ! \"$PYTHON_PATH\" -c \"$cmd\"; then\n+\techo \"consider setting BZR_PLUGIN_PATH=$HOME/.bazaar/plugins\" 1>&2\n+\tskip_all='skipping remote-bzr tests; bzr-fastimport not available'\n+\ttest_done\n+fi\n+\n+check () {\n+\t(cd $1 &&\n+\tgit log --format='%s' -1 &&\n+\tgit symbolic-ref HEAD) > actual &&\n+\t(echo $2 &&\n+\techo \"refs/heads/$3\") > expected &&\n+\ttest_cmp expected actual\n+}\n+\n+bzr whoami \"A U Thor <author@example.com>\"\n+\n+test_expect_success 'cloning' '\n+  (bzr init bzrrepo &&\n+  cd bzrrepo &&\n+  echo one > content &&\n+  bzr add content &&\n+  bzr commit -m one\n+  ) &&\n+\n+  git clone \"bzr::$PWD/bzrrepo\" gitrepo &&\n+  check gitrepo one master\n+'\n+\n+test_expect_success 'pulling' '\n+  (cd bzrrepo &&\n+  echo two > content &&\n+  bzr commit -m two\n+  ) &&\n+\n+  (cd gitrepo && git pull) &&\n+\n+  check gitrepo two master\n+'\n+\n+test_expect_success 'pushing' '\n+  (cd gitrepo &&\n+  echo three > content &&\n+  git commit -a -m three &&\n+  git push\n+  ) &&\n+\n+  echo three > expected &&\n+  cat bzrrepo/content > actual &&\n+  test_cmp expected actual\n+'\n+\n+test_expect_success 'roundtrip' '\n+  (cd gitrepo &&\n+  git pull &&\n+  git log --format=\"%s\" -1 origin/master > actual) &&\n+  echo three > expected &&\n+  test_cmp expected actual &&\n+\n+  (cd gitrepo && git push && git pull) &&\n+\n+  (cd bzrrepo &&\n+  echo four > content &&\n+  bzr commit -m four\n+  ) &&\n+\n+  (cd gitrepo && git pull && git push) &&\n+\n+  check gitrepo four master &&\n+\n+  (cd gitrepo &&\n+  echo five > content &&\n+  git commit -a -m five &&\n+  git push && git pull\n+  ) &&\n+\n+  (cd bzrrepo && bzr revert) &&\n+\n+  echo five > expected &&\n+  cat bzrrepo/content > actual &&\n+  test_cmp expected actual\n+'\n+\n+test_done\n-- \n1.8.0\n"}]}