Skip to content

Instantly share code, notes, and snippets.

@smoser
Last active March 8, 2022 20:07
Show Gist options
  • Save smoser/e4e5388faa6dcc92d8acfb1b7fbabb7c to your computer and use it in GitHub Desktop.
Save smoser/e4e5388faa6dcc92d8acfb1b7fbabb7c to your computer and use it in GitHub Desktop.
bzr convert to git things (bzr2git)

converting bzr 2 git things

Bug 1606973 was my original file, and then changes to bzr export.

The patch was improved by some others, and I'm storing my version of it here.

Features

The benefit of the patch is that you get:

  • bug metadata converted: Bzr contains 'fixes' metadata (bzr commit --fixes) that does not get moved over to git. This updates the commit message exported to git to contain LP: #XXXXXX for each --fixes=lp:XXXXXX in bzr.

  • bzr revno info: Often times bug or other references to code may say 'fixed in revno XXX'. That information gets lost in a conversion to git. The patch here updates commit messages to contain bzr-revno: XXX for each bzr revision.

Installing

I just installed from Ubuntu distro.

$ sudo apt-get install bzr-fastimport bzr

Patching

I just patched the package-installed version of fastexport.py. If you find that unacceptable, then update the path to the right directory and it will still apply.

$ ( cd /usr/lib/python2.7/dist-packages/bzrlib/plugins/ &&
  sudo patch -p0 --dry-run ) < fast-export.patch

Doing a convert

$ ./bzr2git bzr-dir git-dir
...
$ cd git-dir
$ git branch
master
$ git log
...
#!/bin/bash
VERBOSITY=0
error() { echo "$@" 1>&2; }
fail() { local r=$?; [ $r -eq 0 ] && r=1; failrc "$r" "$@"; }
failrc() { local r=$1; shift; [ $# -eq 0 ] || error "$@"; exit $r; }
Usage() {
cat <<EOF
Usage: ${0##*/} [ options ] bzr-repo new-git-dir
convert bzr to git for 'bzr-repo' with output in 'new-git-dir'
options:
-v | --verbose be more verbose
EOF
}
bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; return 1; }
cleanup() {
[ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
}
debug() {
local level=${1}; shift;
[ "${level}" -gt "${VERBOSITY}" ] && return
error "${@}"
}
main() {
local short_opts="hv"
local long_opts="help,verbose"
local getopt_out=""
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
{ bad_Usage; return; }
local cur="" next="" out_d="" bzr_d=""
while [ $# -ne 0 ]; do
cur="$1"; next="$2";
case "$cur" in
-h|--help) Usage ; exit 0;;
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
--) shift; break;;
esac
shift;
done
[ $# -eq 2 ] || { bad_Usage "Expected 2 args, got $# ($*)"; return; }
bzr_d="$1"
out_d="$2"
[ -d "$bzr_d" ] || fail "bzr-repo '$bzr_d' is not a directory"
if [ ! -d "$out_d" ]; then
debug 1 "mkdir -p \"$out_d\""
mkdir -p "$out_d" || fail "failed mkdir -p $out_d"
fi
if [ ! -d "$out_d/.git" ]; then
debug 1 "git init \"$out_d\""
git init "$out_d" ||
fail "failed to git init in $out_d"
fi
set -o pipefail
local rcs=""
bzr_export=( bzr fast-export --rewrite-tag-names --plain "$bzr_d" )
git_import=( git fast-import )
debug 1 "${bzr_export[*]}" "|" \
"( cd \"$out_d\" && ${git_import[*]} )"
bzr fast-export --plain "$bzr_d" | ( cd "$out_d" && "${git_import[@]}" )
rcs=( "${PIPESTATUS[@]}" )
[ "${rcs[0]}" != 0 ] &&
failrc ${rcs[0]} "failed: ${bzr_export[*]}"
[ "${rcs[1]}" != 0 ] &&
failrc ${rcs[0]} "failed: ( cd $out_d && ${git_import[*]} )"
debug 1 "( cd \"$out_d\" && git checkout master )"
local out=""
out=$( cd "$out_d" && git checkout master 2>&1 ) ||
fail "failed git checkout master: $out"
error "successfully created git directory in ${out_d}"
return 0
}
main "$@"
# vi: ts=4 expandtab
--- fastimport/exporter.py.dist 2017-10-26 09:00:36.375185796 -0400
+++ fastimport/exporter.py 2017-12-18 12:28:39.386484215 -0500
@@ -57,6 +57,7 @@
progress,
trace,
)
+from bzrlib.log import _compute_revno_str
from bzrlib.plugins.fastimport import (
helpers,
@@ -425,7 +426,70 @@
# Filter the revision properties. Some metadata (like the
# author information) is already exposed in other ways so
# don't repeat it here.
+ addlines = []
+ message = revobj.message.encode("utf-8")
+ fixed_bugs = []
+ if b'\nLP: ' in message:
+ # Join any old LP: # in the bzr commit message to avoid duplicate
+ lines = []
+ for line in message.splitlines():
+ if not line.startswith(b'LP: '):
+ lines.append(line)
+ continue
+ replace_line = True
+ linebugs = []
+ for tok in line.split():
+ if tok == b'LP:':
+ continue
+ tok = tok.replace(b"#", "").replace(b",", "")
+ try:
+ int(tok)
+ linebugs.append(tok)
+ except ValueError as e:
+ sys.stderr.write(
+ "WARNING: failed to parse '%s': %s\n" % (tok, line))
+ replace_line = False
+ break
+ if replace_line:
+ fixed_bugs.extend(linebugs)
+ else:
+ lines.append(line)
+ if fixed_bugs:
+ sys.stderr.write(
+ "UPDATED: adjusted existing bzr LP: for %s\n" % fixed_bugs)
+ message = b'\n'.join(lines)
+ if fixed_bugs:
+ message = message.rstrip(b'\n') + b'\n'
+
if self.plain_format:
+ if 'bugs' in revobj.properties:
+ # properties are a newline separated list of: bugurl action
+ # http://bugs.launchpad.net/bug/XXXXX fixed
+ for bugstatement in revobj.properties["bugs"].split("\n"):
+ bugurl, comment = bugstatement.decode().split()
+ if not bugurl.startswith("https://launchpad.net/bugs/"):
+ sys.stderr.write("unknown bug url: %s\n" % bugurl)
+ continue
+ if comment == "fixed":
+ n = bugurl.rpartition("/")[2]
+ fixed_bugs.append(bugurl.rpartition("/")[2])
+
+ # append to the message with LP: #XXX[, #XXX]
+ if fixed_bugs:
+ fixed_bugs = sorted(set(fixed_bugs))
+ addlines.append(
+ "LP: %s" % ', '.join(["#" + n for n in fixed_bugs]))
+
+ if 'authors' in revobj.properties:
+ authors = revobj.properties['authors'].split('\n')
+ if len(authors) > 1:
+ addlines.append("Original authors:")
+ addlines.extend([" - " + author for author in authors])
+
+ revno = _compute_revno_str(self.branch, revobj.revision_id)
+ if revno is not None:
+ addlines.append("bzr-revno: %s" % revno)
+
properties = None
else:
properties = revobj.properties
@@ -435,9 +499,14 @@
except KeyError:
pass
+ if addlines:
+ if message[-1] != b"\n":
+ message += b"\n"
+ message = '\n'.join(
+ [message] + [a.encode("utf-8") for a in addlines])
# Build and return the result
return commands.CommitCommand(git_ref, str(mark), author_info,
- committer_info, revobj.message.encode("utf-8"), from_, merges, iter(file_cmds),
+ committer_info, message, from_, merges, iter(file_cmds),
more_authors=more_author_info, properties=properties)
def _get_revision_trees(self, parent, revision_id):
## 2018-06-08 Currently not working, results in a segfault when run.
## not sure that patching is being done right.
## to check:
## ( cd /snap/bzr2git/current/usr/lib/python2.*/dist-packages/
## grep LP bzrlib/plugins/fastimport/exporter.py )
name: bzr2git
version: '0.1'
summary: Utility to convert bzr repos to git.
description: |
Utility to convert bzr repos to git.
This tool provides easy command to convert a bzr repo into git.
It's additional features are:
* bug metadata converted:
This updates the commit message exported to git to contain
'LP: #XXXXXX' for each `--fixes=lp:XXXXXX` in bzr.
* bzr revno info: Often times bug or other references to code may
say 'fixed in revno XXX'. This updates git commit messages with
'bzr-revno: XXX' for each bzr revision.
Usage:
bzr clone lp:your-repo
bzr2git your-repo your-repo.git
grade: stable
confinement: strict
apps:
bzr2git:
command: bzr2git
parts:
bzr2git:
plugin: dump
stage-packages:
- bzr
- bzr-fastimport
- libc6
override-build: |
snapcraftctl build
( set -e
plugdir=usr/lib/python2.7/dist-packages/bzrlib/plugins/
cd ../install/$plugdir
patch -p0 --dry-run
) < fast-export.patch
organize:
bzr2git: usr/bin/bzr2git
@mpontillo
Copy link

mpontillo commented Nov 28, 2018

See also: my version of a bzr2git script (currently also a snap of the same name, but I'm willing to give it up if others want to maintain it). This supports iterative transitions, if you don't want a "flag day". But doesn't support bug metadata (or any bzr metadata, for that matter).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment