Minimal Swift package to provide most common Git functionalities.
Check out OmiGit on the App Store for illustration of the package's features. See also our sample app.
While SwiftGit2 already provides a Swift binding for libgit2, it is lacking some crucial features such as git push.
In an attempt to implement those, we come to believe it is best to write C code in C/C++ instead of Swift's syntax for C code so that it will be much easier for future development and maintenance.
Thankfully, Apple's Swift Package Manager now supported Swift modules written in Objective-C.
This leads us to make a new Swift package MiniGit to provide the bare minimal Git functionality written in Objective-C that could be used in Swift-based projects.
(But really it is just C++ mostly: Objective-C was to establish an interface to the Swift side.)
There are two libraries (modules, to be precise) in this Swift package:
- XGit: The eXtensible Git module, written in Objective-C, whose classes could be subclassed to provide extra functionalities desired. All interactions with
libgit2happen here. - MiniGit: The UI extension of XGit whose classes extend those of XGit and conform to
IdentifiableandObservableObjectto support SwiftUI binding.
Provide key features from the following commonly used git commands:
git initgit clonegit statusgit diffgit addgit restore --stagedgit commitgit loggit branchgit pushgit fetchgit mergegit checkoutgit reset
There is one notable behavioral differece in the merge implementation: We do not create the merge commit automatically.
After a merge, the client must do that to clear the MERGE state (after resolving all conflicts) or reset to discard the unwanted merge.
This allows user to fill in the commit message and update user name/email in the UI if necessary since we do not support amend last commit at this point.
If you use both XGit and MiniGit i.e. add both to your project's target Frameworks, Libraries and Embedded Contents, then you are good to go.
If you use the core module XGit by itself only, then you must add the libgit2.xcframework, libz.tbd and libiconv.tbd to your project's target Frameworks, Libraries and Embedded Contents.
When building for real iPhone, disable Bitcode.
See our sample app for a starting point.
XGit was designed with SwiftUI interoperability in mind.
The module single entry point is the Repository class.
Its methods pretty much follows the corresponding git command, except for git init since init has dedicated meaning in Objective-C.
There is only one constructor with a supplied repository path which does not open the repository automatically.
The constructor does not fail so one checks exists method after open to see if there is really a repository there.
To allow data classes to be extensible and interoperate with SwiftUI, we make use of the factory methods: Subclass of Repository can override makeCommit to return the desired instance of a Commit subclass.
The majority of other classes are designed as wrapper for their libgit2 C counterparts: We e
6516
ssentially keep a private pointer to the C struct and free it automatically on deinit.
For example, we have class Commit as wrappper for libgit2's git_commit to expose the latter's key information (author, time, OID, ...) as Objective-C's properties.
Notable exceptions are the Diff-related, OID and GitError classes which are meant to be used as data-converted value structs (immutable state) rather than actual classes.
We also use @protocol such as StatusProtocol, CheckoutProtocol, MergeProtocol to capture complicated returned results such as git status, to report the progress of operations and to report errors.
For example: In git status implementation, instead of returning a Status object in Objective-C, we take a StatusProtocol as input through which we report the staged changes, unstaged changes, etc.
This is to make it easier to use in SwiftUI: Swift client could then implement the Status class conforming to StatusProtocol and ObservableObject so that the status could be bound to a SwiftUI View.
Most functionalities are implemented by literally copy and paste libgit2's sample codes.
However, to avoid usage of goto statements (to perform clean-up, deallocate objects upon failure), we use single-use C++ struct and make use of the fact that as an object goes out of scope, it gets destroyed automatically.
include/: Module export, umbrella headerRepository.hand the supplementary headers.internal/: Internal implementation, excluded from the package, as they are#imported directly into the single main implementation fileRepository.mm.Repository.mm: Our main implementation file.
Same as libgit2.