ftgr
is a tool made for a very specific purpose: Take a
Fossil repository that has only had PGP-signed
commits from one person (possibly with multiple keys), and produce
a semantically equivalent Git repository. The
resulting Git
repository's commits will have the same dates and
signatures as the original Fossil
commits (and the dates inside
those signatures will also match).
- Git
- Fossil (tested with
1.32 [715f88811a]
) - libfaketime
- GnuPG2
- A JRE supporting Java 8 or greater.
$ mvn clean package
Compilation produces a jar file containing all of the dependencies.
$ java -jar target/io7m-ftgr-*-main.jar
usage: ftgr.conf [logback.xml]
The program accepts a configuration file and an optional Logback configuration file to control logging. The default is to log everything.
The ftgr
configuration file is in Java Properties
format:
# Absolute path to fossil executable
com.io7m.ftgr.fossil_executable = /usr/bin/fossil
# Absolute path to git executable
com.io7m.ftgr.git_executable = /usr/bin/git
# Absolute path to GPG executable
com.io7m.ftgr.gpg_executable = /usr/bin/gpg
# Absolute path to faketime executable
com.io7m.ftgr.faketime_executable = /usr/bin/faketime
# True if no changes should be written to disk
com.io7m.ftgr.dry_run = false
# True if repository verification should be performed (See "Verification" below)
com.io7m.ftgr.verify = true
# Absolute path to the Git repository that will be created
com.io7m.ftgr.git_repository = /tmp/output
# Absolute path to the input Fossil repository
com.io7m.ftgr.fossil_repository = /tmp/input.fossil
# See "Commit mappings" below.
com.io7m.ftgr.commit_map = /tmp/output-commits.txt
# See "Name mappings" below.
com.io7m.ftgr.name_map.someone = Some One|someone@example.org
com.io7m.ftgr.name_map.someone_else = Some One|someone@example.org
Fossil
uses short usernames to mark each commit. Git
, however,
tends towards full names and email addresses in each message. In
order to produce the latter from the former, the configuration file
requires the definition of name mappings. For a given username u
,
the key com.io7m.ftgr.name_map.u
defines a name and email address
separated by the pipe symbol |
(U+007C)
. It is an error to fail
to provide a mapping for a Fossil
username: The program will
fail loudly before attempting to write any data if one or more are
missing.
In order to allow for verification of commits at a later date, ftgr
saves a log of the Git
commits made for each Fossil
commit. The
resulting log consists of one mapping m
per line, where m
has the
form:
git:gc|fossil:fc
Where gc
is a the SHA-1 hash of a Git
commit, and fc
is the
SHA-1 hash of a Fossil
commit.
Verification of commits proceeds by checking out each Git
commit
mentioned in the recorded commit map, and then checking out the
corresponding Fossil
commit and checking that the same files with
the same contents are present in both commits.
Fossil
and Git
use a similar internal model: A directed acyclic
graph of immutable objects representing files, directories, and
commits. Therefore, it's a fairly simple case of transforming one
to the other by literally performing each commit in a Git
repository
as it was performed in the original Fossil
repository, with minor
adjustments to account for differences in how merges and branches are
represented.
-
Read all commits from the
Fossil
repository into a directed acyclic graph structure, keeping track of thebranch
,time
,user
, andcomment
of the original commits. The vertices of the graph are the commits and the edges of the graph are the parent → child links.Fossil
implicitly marks the first commit of each branch; the details of how that happens aren't important here, just the fact that it's possible to know unambiguously whether a commit was responsible for "creating" the branch or not. -
Fetch the manifest for each commit, and extract the key ID of the PGP key used to sign the manifest. Maintain a list of all keys used.
-
Check that the running user has the private key for each of the listed keys above. Give up if any are missing.
-
Check that a mapping exists from all
Fossil
user names toGit
authors. This is so that the author/committer user names in the original commits can be transformed into the conventionalAn A Author <someone@example.com>
format. Give up if any are missing. -
Create an empty
Git
repository. Create an initial unsigned root commit consisting of a single.gitignore
file that hides any relevantFossil
metadata files. -
Open the
Fossil
repository into theGit
repository directory. This allowsFossil
operations to be performed by executing thefossil
command line with that directory as the current working directory. -
Order the commits by time. Oldest commits come first.
-
For each commit
c
:
- If
c
was the first commit of a branch, create a newGit
branch. - If
c
has two parents, merge the current branch with whichever of the two parents are on the other branch. - Otherwise, checkout commit
c
fromFossil
, replacing all files in the current directory (and removing all files that are not part of the commit). SwitchGit
to the relevant branch. Add all files to theGit
index and commit using the original message, time, and signing the result with the original PGP key.
Internally, faketime
is used to execute both git
and gpg
. This
allows for new commits to have the exact times as specified in the
original commits. Amusingly, it also allows for the use of expired
keys in GnuPG.