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:
| Repository | What it is | History from |
|---|---|---|
| ingress-scripts | The ingress scripts (p5-freshports-scripts package) | 2000 |
| ingress-modules | The ingress Perl modules (p5-freshports-modules package) | 2000 |
| ingress-verify | Scripts which verify and repair the database | 2006 |
| database-schema | The database schema | 2001 |
| daemon-freshports | The freshports daemon (freshports-freshports package) | 2002 |
| daemon-ingress | The ingress daemon (freshports-ingress package) | 2002 |
| fp-listen | The daemon which listens for database notifications and clears caches (py-freshports-fp-listen package) | 2006 |
| daemontools | The remaining services: fp-daemon and ingress_svn | 2002 |
| packaging-for-svn | Packaging files as they stood in Subversion, including the cut-* release scripts | 2018 |
| freshports-archive | Ten dormant projects, one directory each | 2000 |
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 ismainin each repository. The oldtrunkstays as a branch namedtrunk; 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,
/scriptswas split intoingress/scriptsandingress/modulesby 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, sogit logandgit blamereach back to 2000. Tags from before the split are prefixedpre-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, astags/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 hotcopymakes one, as doessvnadmin loadfrom a dump. - git, git-svn, git-filter-repo, Subversion, Python 3 and gh. The scripts expect them in
/opt/homebrew/bin; elsewhere, edit thePATH=,GIT=andSVN=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.











