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|[email protected]
com.io7m.ftgr.name_map.someone_else = Some One|[email protected]
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
Fossilrepository into a directed acyclic graph structure, keeping track of thebranch,time,user, andcommentof the original commits. The vertices of the graph are the commits and the edges of the graph are the parent → child links.Fossilimplicitly 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
Fossiluser names toGitauthors. This is so that the author/committer user names in the original commits can be transformed into the conventionalAn A Author <[email protected]>format. Give up if any are missing. -
Create an empty
Gitrepository. Create an initial unsigned root commit consisting of a single.gitignorefile that hides any relevantFossilmetadata files. -
Open the
Fossilrepository into theGitrepository directory. This allowsFossiloperations to be performed by executing thefossilcommand line with that directory as the current working directory. -
Order the commits by time. Oldest commits come first.
-
For each commit
c:
- If
cwas the first commit of a branch, create a newGitbranch. - If
chas two parents, merge the current branch with whichever of the two parents are on the other branch. - Otherwise, checkout commit
cfromFossil, replacing all files in the current directory (and removing all files that are not part of the commit). SwitchGitto the relevant branch. Add all files to theGitindex 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.