Gift provides Swift bindings to the libgit2 library.
That means you can clone a Git repository, list its commits, or even make a commit of your own--all from within your Swift program.
For example, we can grab the latest commit message of this repository:
let url = NSURL(fileURLWithPath: "/Users/modocache/Gift")!
let latestCommitMessage = openRepository(url)
.flatMap { $0.headReference }
.flatMap { $0.commit }
.flatMap { $0.message }
Gift returns result objects for any operation that might fail. The type
of lastCommitMessage
above is Result<String, NSError>
. If every
operation succeeded, you can access the commit message String
. But if
any of the operations failed, you'll have an NSError
that describes
what went wrong.
You can list all branches in a repository. Gift uses ReactiveCocoa to represent a sequence of values:
repository.branches().start(next: { (reference) in
println("Branch name: \(reference.name)")
})
You can list commits as well:
repository.commits().start { (commit) in
println("Commit message: \(commit.message)")
}
You can order the commits as you please, of course:
let commits = repository.commits(sorting: CommitSorting.Time | CommitSorting.Reverse)
You can make a commit, too:
let tree = repository.index // Grab the index for the repository
.flatMap { $0.add() } // Add any modified entries to that index
.flatMap { $0.writeTree() } // Grab a tree representing the changeset
.flatMap { $0.commit("Zing!") } // Commit
Swift allows Gift to provide default parameters. If you want to use the default behavior, you can easily clone a remote repository:
let remoteURL = NSURL(string: "git://git.libssh2.org/libssh2.git")!
let destinationURL = NSURL(fileURLWithPath: "/Users/modocache/libssh2")!
let repository = cloneRepository(remoteURL, destinationURL)
But you can also customize that behavior and have Gift issue download progress updates:
let options = CloneOptions(
checkoutOptions: CheckoutOptions(
strategy: CheckoutStrategy.SafeCreate,
progressCallback: { (path, completedSteps, totalSteps) in
// ...do something with checkout progress.
}
),
remoteCallbacks: RemoteCallbacks(
transportMessageCallback: { (message) in
// ...do something with messages from remote, like "Compressing objects: 1% (47/4619)"
},
transferProgressCallback: { (progress) in
// ...do something with progress (bytes received, etc.) updates.
})
)
let repository = cloneRepository(remoteURL, destinationURL, options: options)
ObjectiveGit is the official set of Objective-C bindings to Git. It is maintained by the same people who develop GitHub for Mac, so you can be sure it's solid. And you can use it from Swift!
If, however, you're willing to tolerate a less stable set of bindings, Gift utilizes features of Swift to make certain aspects of interfacing with libgit2 easy. For example, take the following code, which prints the latest commit message in a repository:
let latestCommitMessage = openRepository(url)
.flatMap { $0.headReference }
.flatMap { $0.commit }
.flatMap { $0.message }
println(latestCommitMessage) // Either prints commit or error
Because most Gift functions return Result
objects, and because you can
chain those Result
objects, Gift makes it easy to display precise
error information. To get an equivalent level of error handling in
ObjectiveGit, you'd need to write the following:
NSError *repositoryError = nil;
GTRepository *repository = [GTRepository repositoryWithURL:url error:&error];
if (repositoryError != nil) {
NSLog(@"An error occurred: %@", repositoryError);
return;
}
NSError *headReferenceError = nil;
GTReference *headReference = [repository headReferenceWithError:&headReferenceError];
if (headReferenceError != nil) {
NSLog(@"An error occurred: %@", headReferenceError);
return;
}
NSError *lookupError = nil;
GTCommit *commit = [repository lookupObjectByOID:headReference.OID
objectType:GTObjectTypeCommit
error:&lookupError];
if (lookupError != nil) {
NSLog(@"An error occurred: %@", lookupError);
return;
}
NSString *message = commit.message;
if (message == nil) {
NSLog(@"An error occurred");
} else {
NSLog(@"Commit message: %@", message);
}
As you can see, Gift requires significantly less code. ObjectiveGit is, however, far more mature. It's up to you to decide which is right for your project.
You'll need to have homebrew installed. If you don't already have CMake installed, run:
$ brew install cmake
Then, to build the dependencies for Gift, just run:
$ rake dependencies build
If it's your first time running the script, it'll take a while--it needs to build static libraries for OpenSSL, libssh2, and libgit2. And that's for both OS X and iOS.
You can test your changes by running:
$ rake
If you see a non-descript error like "Cannot build module Gift", there's an extremely sophisticated workaround: first, remove all the source files from the main and test target you're trying to build.
Then, build the framework. It should work fine (magic, I know). Now that
it builds, revert the removal of the files by running
git checkout -- Gift.xcodeproj
. And voila! Now everything builds fine.
You can find an example iOS app that uses Gift in the
Examples
directory. Using Gift
is (sort of) easy:
- Drag
Gift.xcodeproj
,LlamaKit.xcodeproj
,Quick.xcodeproj
, andNimble.xcodeproj
into your app's workspace. - In the "Link Binary with Libraries" build phase of your app, link your app to Gift.framework.
- Set the “Header Search Paths” (
HEADER_SEARCH_PATHS
) build setting to the correct path for the libgit2 headers in your project. For example, if you added the submodule to your project asExternal/Gift
, you would set this build setting toExternal/Gift/External/libgit2/include
. import Gift
in any Swift file, and you're off to the races!