I’ve been using Fossil for quite a while now as my SCM. I like Fossil. But Fossil is not Git, and most people seem to like Git. It could be better to say that most people like GitHub because it’s the first hosted SCM that’s free for open source with good social interaction I’m aware of. And GitHub is huge. Some might say that if you’re not on GitHub, you don’t exist as a project. Well this obviously isn’t true, but you get the idea. You can get free Fossil hosting at Chisel, which is nice, but it’s also not GitHub. Plus I like to be 100% self hosted.

So, to get myself on GitHub (as a mirror only), there needs to be a bridge between Fossil and Git. Fossil documentation implies this is quite easy. Sadly, this isn’t the case as the Fossil-1.37 releases (note there is no guaranntee that future versions of follow will not have these flaws- my branch may not be comitted to trunk) have the following flaws:

  • Branch and Tag name mangling (dhcpcd-6 becomes dhcpcd_6)
  • Silent master branch renaming into trunk on inport, but not on export
  • No tag comments (Fossil lacks the feature) which means syncing tags back and forth results in tag conflict due to signature change

I submitted some initial patches the the Fossil mailing list and I now have a Fossil commit bit! You can find my branch here to fix the Fossil Git bridge.

But that’s not the end of the story. A bridge has two ends. With my initial setup, the Git end was bare bones repository which I pushed to GitHub. This is no longer the case. I now need a staging repository to pull both ends. And this requires a script because Git needs a little more hand-holding to completely track a remote. The below script is tailored for my needs, yours may differ. It also reflects the above initial design and the subsequent change. As such it it may need editing if you need to create a git clone from fossil. This comes with no support, just as an idea of how you might implement such a bridge.

#!/bin/sh

fossildir=/var/fossil
# Cannot be a bare directory for git as we cannot write to the host directly.
# So we have a staging directory instead.
# This requires a bit of hand-holding to track all the branches.
gitdir=/var/git-staging

marksdir=/var/scm-marks

# Respect default naming at either end
fossil_export_opts="--rename-trunk master"
fossil_import_opts="--rename-master trunk"

# Only used when creating a git bare bones repo from Fossil.
export_fossil_to_git_new()
{

        rm-f "$fossilmarks" "$gitmarks"
        git init
        fossil export--git 
--export-marks "$fossilmarks" 
                $fossil_export_opts "$fossildir/$fossilrepo" | 
                git fast-import 
--export-marks="$gitmarks"
}

export_fossil_to_git()
{

        fossil export--git 
--import-marks "$fossilmarks"--export-marks "$fossilmarks" 
                $fossil_export_opts "$fossildir/$fossilrepo" | 
                git fast-import 
--import-marks="$gitmarks"--export-marks="$gitmarks"
}

export_git_to_fossil()
{

        git fast-export--all 
--import-marks="$gitmarks"--export-marks="$gitmarks" | 
                fossil import--git--incremental 
--import-marks "$fossilmarks"--export-marks "$fossilmarks" 
                $fossil_import_opts "$fossildir/$fossilrepo"
}

pull_git()
{
        local remote

        git fetch--all
        # Track all remote branches
        git branch-r | grep-v '->' | while read remote; do
                if [-z "$(git branch--list "${remote#origin/}")" ]; then
                        git branch--track "${remote#origin/}" "$remote"
                fi
        done
        git branch--list | sed-e 's/^* //' | while read branch; do
                git checkout "$branch"
                git merge--ff-only
        done
}

push_git()
{

        git push--all
        git push--tags
        # Reset the current branch checkout.
        # If we don't, the next run will complain about unstashed changes.
        # This maybe a bug in git, but maybe not because the live checkout
        # *is* behind at this point as we just fast-imported.
        git reset--hard
}

echo "Syncing git and fossil."
for repo in "$fossildir"/*.fossil; do
        fossilrepo=${repo#${fossildir}/*}
        repo=${fossilrepo%.fossil}
        gitrepo="$repo"
        fossilmarks="$marksdir/$repo.fossil.marks"
        gitmarks="$marksdir/$repo.git.marks"

        # We just sync old fossil repos to new phab clones
        if [-d "$gitdir/$gitrepo" ]; then
                cd "$gitdir/$gitrepo"
                pull_git # staging only
                export_git_to_fossil
                export_fossil_to_git
                push_git # staging only
# Enable the below if pusing to a bare git repo from fossil
#       else
#               export_fossil_to_git_new
        fi
done

Direct download to script