Converting the last FreshPorts subversion repos to git

I have attempted this process twice before. My thanks to those who helped me. I never completed your work. I appreciate your assistance and apologize that I never completed my part.

Tonight, I resorted to Claude for help. This is a summary.

FreshPorts has kept its code in version control since its first commit on 23 April 2000: CVS at first, then Subversion. The website moved to git some time ago, as FreshPorts/freshports. Everything else is now in git too, and on 5 October 2026 I shut down the Subversion server. Its last commit was r6278.

This post covers what moved where, what was kept, what I fixed along the way, and how to repeat the whole conversion.

The new repositories

Each project, and each separately packaged daemon, now has a repository of its own:

RepositoryWhat it isHistory from
ingress-scriptsThe ingress scripts (p5-freshports-scripts package)2000
ingress-modulesThe ingress Perl modules (p5-freshports-modules package)2000
ingress-verifyScripts which verify and repair the database2006
database-schemaThe database schema2001
daemon-freshportsThe freshports daemon (freshports-freshports package)2002
daemon-ingressThe ingress daemon (freshports-ingress package)2002
fp-listenThe daemon which listens for database notifications and clears caches (py-freshports-fp-listen package)2006
daemontoolsThe remaining services: fp-daemon and ingress_svn2002
packaging-for-svnPackaging files as they stood in Subversion, including the cut-* release scripts2018
freshports-archiveTen dormant projects, one directory each2000

FreshPorts/packaging and FreshPorts/periodics are unchanged; both were already being developed on GitHub. The Subversion copy of packaging went its own way after that, keeping the cut-* release scripts, so it is preserved separately as packaging-for-svn.

What was kept

  • All the history. Every commit keeps its author, date and message, and a git-svn-id: line giving its Subversion path and revision, so references to old revisions such as r5068 can still be traced.
  • Every branch. Development happened on branches/git, so that is main in each repository. The old trunk stays as a branch named trunk; most stopped in early 2021.
  • Every release tag. Subversion tags became annotated git tags, carrying the date and message of the revision that created them.
  • Real names. Subversion usernames now map to names and email addresses.

Each repository was checked against Subversion: every branch, and every tag as it was when created, matches an svn export of the same path, file for file.

What I fixed along the way

  • Ingress history now goes back to 2000. In May 2018, /scripts was split into ingress/scripts and ingress/modules by copying files one at a time. git cannot follow that, so a plain conversion of either starts in 2018. Both repositories now have the earlier history grafted on, so git log and git blame reach back to 2000. Tags from before the split are prefixed pre-split/, because some of their version numbers were reused afterwards.
  • Tags spoiled by a second svn cp. Copying trunk onto a tag which already exists does not replace the tag; Subversion nests a second copy inside it, as tags/X/trunk/. That happened 22 times. Those tags now point at the release as it was created, without the nested copy.
  • Tags deleted in Subversion stay deleted. Ten tags were removed in Subversion as created in error, and they are not in git.
  • daemontools is split up. freshports, ingress and fp-listen are built as separate FreeBSD packages, so each now has its own repository and its own tags. Each repository follows its code through its earlier names (fp-daemon, fp-freshports, fp-ingress), and its release tags are now plain version numbers, such as 2.0.16.
  • Dormant projects are combined. Ten small projects untouched for years, such as walkports (last commit in 2000), are now directories in freshports-archive, with their history.

Earlier conversions, now archived

Some of these projects were first copied to GitHub around January 2025, with Subversion’s trunk/, branches/ and tags/ kept as plain directories. Laurent Chardon brought two of them up to date by applying Subversion changes by hand; thank you, Laurent. They have been renamed, and the new repositories replace them:

  • archive.ingress: replaced by ingress-scripts, ingress-modules and ingress-verify
  • archive.database-schema: replaced by database-schema
  • archive.fp-listen: despite its name, this held all of daemontools as of November 2024. It is replaced by fp-listen, daemon-freshports, daemon-ingress and daemontools.

If you have a clone of FreshPorts/database-schema from before today, it still points at the old repository’s name, which now belongs to the new one. Clone it again, or point it at archive.database-schema.

What comes next

Releases used to be cut by scripts which tagged in Subversion, exported the tag and copied a tarball to the package builder. From now on a release is a git tag, and the ports in FreshPorts/ports will fetch their source with USE_GITHUB, as the website already does.

How to repeat the conversion

All the scripts are in FreshPorts/git-conversion. Given the same Subversion repository, they rebuild every repository above and check each against Subversion.

You need:

  • The Subversion repository itself, not a working copy, at ~/src/freshports/freshports-1. svnadmin hotcopy makes one, as does svnadmin load from a dump.
  • git, git-svn, git-filter-repo, Subversion, Python 3 and gh. The scripts expect them in /opt/homebrew/bin; elsewhere, edit the PATH=, GIT= and SVN= lines.
  • About two hours. One project alone takes more than an hour, because git-svn replays every tag that was deleted and recreated.

Then:

git clone https://github.com/FreshPorts/git-conversion.git
cd git-conversion
./run-all.sh

run-all.sh converts each project with git-svn, fixes the tags, combines the dormant projects, grafts the ingress history, verifies every branch and tag against Subversion, adds the READMEs, then splits daemontools and verifies that too. It stops at the first difference. The repositories land in repos/ and the logs in logs/. The README in git-conversion describes each step.

To publish the results, push-to-github.sh creates each repository as private in a GitHub organization, and pushes every branch and tag. It skips any repository which already exists.

Website Pin Facebook Twitter Myspace Friendfeed Technorati del.icio.us Digg Google StumbleUpon Premium Responsive

Leave a Comment

Scroll to Top