Using Git from Swift

Recently I’ve been exploring using “files in a Git repository” as the main storage for iOS and Mac apps. I’ve got two little projects using this.

The key technology that enables this approach is libgit2, which is a C language implementation of the core git methods. There are at least two popular ways to use libgit2 from iOS / Mac. The first, ObjectiveGit, is Objective-C bindings to the C API. The second, SwiftGit2, is a set of Swift bindings.

Being me, I wound up going with neither of these libraries. Things that made me shy away:

  1. Neither project use Swift Package Manager, which I use exclusively in my personal projects.
  2. Neither project has been updated recently. SwiftGit2 links against version 1.1 of libgit2 (the library is now, at the time of this writing, at version 1.5). ObjectiveGit is worse, linking against version 0.28.1!

So I’ve approached git integration to Swift iOS/Mac apps from first principles and created two projects:

  1. static-libgit2 is a Swift package that exposes the libgit2 C API through the Clibgit2 module. This project is a modification of LibGit2-On-iOS and follows the same basic strategy:

    1. Use build scripts to build libgit2 and its dependencies (libssh, openssl) and create a single xcframework for all of the necessary SDK and architecture variations.
    2. Create a Package.swift file to let projects include the xcframework through Swift Package Manager.

    static-libgit2 is pretty stable, and if you want to just use the C APIs in a Swift app, it gives you want you want. It’s ready for public consumption now.

    import Clibgit2
    import SwiftUI
    
    struct ContentView: View {
        var body: some View {
            Text(LIBGIT2_VERSION)
                .padding()
        }
    }
  2. AsyncSwiftGit is much more experimental and much less stable. It’s my attempt to write Swift wrappers around the C API that uses the new concurrency features of Swift 5.5. For example, instead of passing in C-compatible callback functions when fetching changes from a remote repository, I can write:

    for try await progress in repository.fetchProgressStream(remote: "origin", credentials: credentials) {
        // do something with `progress` here
    }

    This is “more experimental and less stable” because I’m still figuring out the right way to use Swift concurrency, the best way to design wrappers around a C API, etc. This one is not yet ready for public consumption.

Overall, though, I’ve been really impressed with how fun and reliable it is to use git as the main storage system for personal programming projects! I predict I’ll be using it more and more.