Skip to content

Commit

Permalink
Merge pull request SwiftGit2#128 from Maaimusic/git-commit
Browse files Browse the repository at this point in the history
Git commit
  • Loading branch information
mdiep authored May 4, 2018
2 parents 25f3ecc + f152ad7 commit 4111097
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 5 deletions.
23 changes: 23 additions & 0 deletions SwiftGit2/Objects.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import Foundation
import libgit2
import Result

/// A git object.
public protocol ObjectType {
Expand Down Expand Up @@ -37,13 +38,35 @@ public struct Signature {
/// The time zone that `time` should be interpreted relative to.
public let timeZone: TimeZone

/// Create an instance with custom name, email, dates, etc.
public init(name: String, email: String, time: Date = Date(), timeZone: TimeZone = TimeZone.autoupdatingCurrent) {
self.name = name
self.email = email
self.time = time
self.timeZone = timeZone
}

/// Create an instance with a libgit2 `git_signature`.
public init(_ signature: git_signature) {
name = String(validatingUTF8: signature.name)!
email = String(validatingUTF8: signature.email)!
time = Date(timeIntervalSince1970: TimeInterval(signature.when.time))
timeZone = TimeZone(secondsFromGMT: 60 * Int(signature.when.offset))!
}

/// Return an unsafe pointer to the `git_signature` struct.
/// Caller is responsible for freeing it with `git_signature_free`.
func makeUnsafeSignature() -> Result<UnsafeMutablePointer<git_signature>, NSError> {
var signature: UnsafeMutablePointer<git_signature>? = nil
let time = git_time_t(self.time.timeIntervalSince1970) // Unix epoch time
let offset: Int32 = Int32(timeZone.secondsFromGMT(for: self.time) / 60)
let signatureResult = git_signature_new(&signature, name, email, time, offset)
guard signatureResult == GIT_OK.rawValue, let signatureUnwrap = signature else {
let err = NSError(gitError: signatureResult, pointOfFailure: "git_signature_new")
return .failure(err)
}
return .success(signatureUnwrap)
}
}

extension Signature: Hashable {
Expand Down
101 changes: 96 additions & 5 deletions SwiftGit2/Repository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -593,23 +593,114 @@ final public class Repository {
}
return .success(index!)
}

/// Stage the file(s) under the specified path.
public func add(path: String) -> Result<(), NSError> {
let dir = path
var dirPointer = UnsafeMutablePointer<Int8>(mutating: (dir as NSString).utf8String)
var paths = git_strarray(strings: &dirPointer, count: 1)
return unsafeIndex().flatMap { index in
defer { git_index_free(index) }
let add_result = git_index_add_all(index, &paths, 0, nil, nil)
guard add_result == GIT_OK.rawValue else {
let err = NSError(gitError: add_result, pointOfFailure: "git_index_add_all")
return .failure(err)
let addResult = git_index_add_all(index, &paths, 0, nil, nil)
guard addResult == GIT_OK.rawValue else {
return .failure(NSError(gitError: addResult, pointOfFailure: "git_index_add_all"))
}
// write index to disk
let writeResult = git_index_write(index)
guard writeResult == GIT_OK.rawValue else {
return .failure(NSError(gitError: writeResult, pointOfFailure: "git_index_write"))
}
return .success(())
}
}

/// Perform a commit with arbitrary numbers of parent commits.
public func commit(
tree treeOID: OID,
parents: [Commit],
message: String,
signature: Signature
) -> Result<Commit, NSError> {
// create commit signature
return signature.makeUnsafeSignature().flatMap { signature in
defer { git_signature_free(signature) }
var tree: OpaquePointer? = nil
var treeOIDCopy = treeOID.oid
let lookupResult = git_tree_lookup(&tree, self.pointer, &treeOIDCopy)
guard lookupResult == GIT_OK.rawValue else {
let err = NSError(gitError: lookupResult, pointOfFailure: "git_tree_lookup")
return .failure(err)
}
defer { git_tree_free(tree) }

var msgBuf = git_buf()
git_message_prettify(&msgBuf, message, 0, /* ascii for # */ 35)
defer { git_buf_free(&msgBuf) }

// libgit2 expects a C-like array of parent git_commit pointer
var parentGitCommits: [OpaquePointer?] = []
defer {
for commit in parentGitCommits {
git_commit_free(commit)
}
}
for parentCommit in parents {
var parent: OpaquePointer? = nil
var oid = parentCommit.oid.oid
let lookupResult = git_commit_lookup(&parent, self.pointer, &oid)
guard lookupResult == GIT_OK.rawValue else {
let err = NSError(gitError: lookupResult, pointOfFailure: "git_commit_lookup")
return .failure(err)
}
parentGitCommits.append(parent!)
}

let parentsContiguous = ContiguousArray(parentGitCommits)
return parentsContiguous.withUnsafeBufferPointer { unsafeBuffer in
var commitOID = git_oid()
let parentsPtr = UnsafeMutablePointer(mutating: unsafeBuffer.baseAddress)
let result = git_commit_create(
&commitOID,
self.pointer,
"HEAD",
signature,
signature,
"UTF-8",
msgBuf.ptr,
tree,
parents.count,
parentsPtr
)
guard result == GIT_OK.rawValue else {
return .failure(NSError(gitError: result, pointOfFailure: "git_commit_create"))
}
return commit(OID(commitOID))
}
}
}

/// Perform a commit of the staged files with the specified message and signature,
/// assuming we are not doing a merge and using the current tip as the parent.
public func commit(message: String, signature: Signature) -> Result<Commit, NSError> {
return unsafeIndex().flatMap { index in
defer { git_index_free(index) }
var treeOID = git_oid()
let treeResult = git_index_write_tree(&treeOID, index)
guard treeResult == GIT_OK.rawValue else {
let err = NSError(gitError: treeResult, pointOfFailure: "git_index_write_tree")
return .failure(err)
}
var parentID = git_oid()
let nameToIDResult = git_reference_name_to_id(&parentID, self.pointer, "HEAD")
guard nameToIDResult == GIT_OK.rawValue else {
return .failure(NSError(gitError: nameToIDResult, pointOfFailure: "git_reference_name_to_id"))
}
return commit(OID(parentID)).flatMap { parentCommit in
commit(tree: OID(treeOID), parents: [parentCommit], message: message, signature: signature)
}
}
}

// MARK: - Diffs

public func diff(for commit: Commit) -> Result<Diff, NSError> {
Expand Down
32 changes: 32 additions & 0 deletions SwiftGit2Tests/RepositorySpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,38 @@ class RepositorySpec: QuickSpec {
}
}

describe("Repository.commit") {
it("Should perform a simple commit with specified signature") {
let repo = Fixtures.simpleRepository
let branch = repo.localBranch(named: "master").value!
expect(repo.checkout(branch, strategy: CheckoutStrategy.None).error).to(beNil())

// make a change to README
let untrackedURL = repo.directoryURL!.appendingPathComponent("untrackedtest")
try! "different".data(using: .utf8)?.write(to: untrackedURL)

expect(repo.add(path: ".").error).to(beNil())

let signature = Signature(
name: "swiftgit2",
email: "[email protected]",
time: Date(timeIntervalSince1970: 1525200858),
timeZone: TimeZone(secondsFromGMT: 3600)!
)
let message = "Test Commit"
expect(repo.commit(message: message, signature: signature).error).to(beNil())
let updatedBranch = repo.localBranch(named: "master").value!
expect(repo.commits(in: updatedBranch).next()?.value?.author).to(equal(signature))
expect(repo.commits(in: updatedBranch).next()?.value?.committer).to(equal(signature))
expect(repo.commits(in: updatedBranch).next()?.value?.message).to(equal("\(message)\n"))
expect(repo.commits(in: updatedBranch).next()?.value?.oid.description).to(equal("7d6b2d7492f29aee48022387f96dbfe996d435fe"))

// should be clean now
let newStatus = repo.status()
expect(newStatus.value?.count).to(equal(0))
}
}

describe("Repository.status") {
it("Should accurately report status for repositories with no status") {
let expectedCount = 0
Expand Down

0 comments on commit 4111097

Please sign in to comment.