mirror of
https://github.com/gosticks/SwiftGit2.git
synced 2025-10-16 11:55:34 +00:00
Merge pull request #102 from jakeva/develop
Implement git status and diffs
This commit is contained in:
commit
d35dac654c
@ -21,6 +21,9 @@
|
||||
/* End PBXAggregateTarget section */
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
232861431F4A3A2E00276D65 /* Diffs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232861421F4A3A2E00276D65 /* Diffs.swift */; };
|
||||
232861451F4A3A2E00276D65 /* Diffs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232861421F4A3A2E00276D65 /* Diffs.swift */; };
|
||||
237731C71F46542B0020A3FE /* repository-with-status.zip in Resources */ = {isa = PBXBuildFile; fileRef = 237731C61F46542B0020A3FE /* repository-with-status.zip */; };
|
||||
2549921B34FFC36AF8C9CD6D /* CommitIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25499A996CA7BD416620A397 /* CommitIterator.swift */; };
|
||||
25499D325997CAB9BEFFCA4D /* CommitIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25499A996CA7BD416620A397 /* CommitIterator.swift */; };
|
||||
621E66A01C72958800A0F352 /* OID.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE70B3E41A1ACB1A002C3F4E /* OID.swift */; };
|
||||
@ -129,6 +132,8 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
232861421F4A3A2E00276D65 /* Diffs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Diffs.swift; sourceTree = "<group>"; };
|
||||
237731C61F46542B0020A3FE /* repository-with-status.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = "repository-with-status.zip"; sourceTree = "<group>"; };
|
||||
25499A996CA7BD416620A397 /* CommitIterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommitIterator.swift; sourceTree = "<group>"; };
|
||||
621E66B41C72958800A0F352 /* SwiftGit2.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftGit2.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
621E66CE1C72958D00A0F352 /* SwiftGit2-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SwiftGit2-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@ -260,6 +265,7 @@
|
||||
BE14AA531A1983520015B439 /* Fixtures */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
237731C61F46542B0020A3FE /* repository-with-status.zip */,
|
||||
BE0B1C5C1A9978890004726D /* detached-head.zip */,
|
||||
BE14AA541A1984550015B439 /* Fixtures.swift */,
|
||||
BE0991F61A578FB1007D4E6A /* Mantle.zip */,
|
||||
@ -272,6 +278,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BEB31F251A0D6F7A00F525B9 /* SwiftGit2 */,
|
||||
BEB31F261A0D6F7A00F525B9 /* Supporting Files */,
|
||||
BEB31F321A0D6F7A00F525B9 /* SwiftGit2Tests */,
|
||||
BEB31FA11A0E63C100F525B9 /* Libraries */,
|
||||
BEB31F411A0D75EE00F525B9 /* Configuration */,
|
||||
@ -305,12 +312,12 @@
|
||||
DA5914751A94579000AED74C /* Errors.swift */,
|
||||
BE36354B1A632C9700D37EC8 /* Libgit2.swift */,
|
||||
BE2E3BE51A31261300C67092 /* Objects.swift */,
|
||||
232861421F4A3A2E00276D65 /* Diffs.swift */,
|
||||
BE70B3E41A1ACB1A002C3F4E /* OID.swift */,
|
||||
BE7A753E1A4A2BCC002DA7E3 /* Pointers.swift */,
|
||||
BEB31F6C1A0D78F300F525B9 /* Repository.swift */,
|
||||
BECB5F691A56F19900999413 /* References.swift */,
|
||||
BECB5F6D1A57284700999413 /* Remotes.swift */,
|
||||
BEB31F261A0D6F7A00F525B9 /* Supporting Files */,
|
||||
25499A996CA7BD416620A397 /* CommitIterator.swift */,
|
||||
);
|
||||
path = SwiftGit2;
|
||||
@ -322,6 +329,7 @@
|
||||
BEB31F271A0D6F7A00F525B9 /* Info.plist */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
path = SwiftGit2;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BEB31F321A0D6F7A00F525B9 /* SwiftGit2Tests */ = {
|
||||
@ -655,6 +663,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
BE0B1C5D1A9978890004726D /* detached-head.zip in Resources */,
|
||||
237731C71F46542B0020A3FE /* repository-with-status.zip in Resources */,
|
||||
BE0991F71A578FB1007D4E6A /* Mantle.zip in Resources */,
|
||||
BE14AA571A198C6E0015B439 /* simple-repository.zip in Resources */,
|
||||
);
|
||||
@ -742,6 +751,7 @@
|
||||
622726351C84E52500C53D17 /* Credentials.swift in Sources */,
|
||||
621E66A31C72958800A0F352 /* Repository.swift in Sources */,
|
||||
621E66A41C72958800A0F352 /* Objects.swift in Sources */,
|
||||
232861451F4A3A2E00276D65 /* Diffs.swift in Sources */,
|
||||
621E66A51C72958800A0F352 /* References.swift in Sources */,
|
||||
621E66A61C72958800A0F352 /* Libgit2.swift in Sources */,
|
||||
621E66A71C72958800A0F352 /* Pointers.swift in Sources */,
|
||||
@ -775,6 +785,7 @@
|
||||
622726341C84E52500C53D17 /* Credentials.swift in Sources */,
|
||||
BEB31F6D1A0D78F300F525B9 /* Repository.swift in Sources */,
|
||||
BE2E3BE61A31261300C67092 /* Objects.swift in Sources */,
|
||||
232861431F4A3A2E00276D65 /* Diffs.swift in Sources */,
|
||||
BECB5F6A1A56F19900999413 /* References.swift in Sources */,
|
||||
BE36354C1A632C9700D37EC8 /* Libgit2.swift in Sources */,
|
||||
BE7A753F1A4A2BCC002DA7E3 /* Pointers.swift in Sources */,
|
||||
@ -1014,6 +1025,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.libgit2.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = SwiftGit2;
|
||||
SWIFT_INCLUDE_PATHS = "$(SRCROOT)/libgit2";
|
||||
SWIFT_VERSION = 4.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -1042,6 +1054,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.libgit2.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = SwiftGit2;
|
||||
SWIFT_INCLUDE_PATHS = "$(SRCROOT)/libgit2";
|
||||
SWIFT_VERSION = 4.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
||||
@ -10,11 +10,11 @@ public class CommitIterator: IteratorProtocol, Sequence {
|
||||
public typealias Iterator = CommitIterator
|
||||
public typealias Element = Result<Commit, NSError>
|
||||
let repo: Repository
|
||||
private var revisionWalker: OpaquePointer? = nil
|
||||
private var revisionWalker: OpaquePointer?
|
||||
|
||||
private enum Next {
|
||||
case over
|
||||
case ok
|
||||
case okay
|
||||
case error(NSError)
|
||||
|
||||
init(_ result: Int32, name: String) {
|
||||
@ -22,7 +22,7 @@ public class CommitIterator: IteratorProtocol, Sequence {
|
||||
case GIT_ITEROVER.rawValue:
|
||||
self = .over
|
||||
case GIT_OK.rawValue:
|
||||
self = .ok
|
||||
self = .okay
|
||||
default:
|
||||
self = .error(NSError(gitError: result, pointOfFailure: name))
|
||||
}
|
||||
@ -55,7 +55,7 @@ public class CommitIterator: IteratorProtocol, Sequence {
|
||||
return Result.failure(error)
|
||||
case .over:
|
||||
return nil
|
||||
case .ok:
|
||||
case .okay:
|
||||
var unsafeCommit: OpaquePointer? = nil
|
||||
let lookupGitResult = git_commit_lookup(&unsafeCommit, repo.pointer, &oid)
|
||||
guard lookupGitResult == GIT_OK.rawValue,
|
||||
@ -76,7 +76,7 @@ public class CommitIterator: IteratorProtocol, Sequence {
|
||||
public func map<T>(_ transform: (Result<Commit, NSError>) throws -> T) rethrows -> [T] {
|
||||
var new: [T] = []
|
||||
for item in self {
|
||||
new = new + [try transform(item)]
|
||||
new += [try transform(item)]
|
||||
}
|
||||
return new
|
||||
}
|
||||
@ -85,7 +85,7 @@ public class CommitIterator: IteratorProtocol, Sequence {
|
||||
var new: [Result<Commit, NSError>] = []
|
||||
for item in self {
|
||||
if try isIncluded(item) {
|
||||
new = new + [item]
|
||||
new += [item]
|
||||
}
|
||||
}
|
||||
return new
|
||||
@ -104,12 +104,12 @@ public class CommitIterator: IteratorProtocol, Sequence {
|
||||
self.repo = repo
|
||||
}
|
||||
|
||||
public func dropFirst(_ n: Int) -> AnySequence<Iterator.Element> {
|
||||
public func dropFirst(_ num: Int) -> AnySequence<Iterator.Element> {
|
||||
notImplemented(functionName: self.dropFirst)
|
||||
return AnySequence<Iterator.Element> { return CommitIterator(repo: self.repo) }
|
||||
}
|
||||
|
||||
public func dropLast(_ n: Int) -> AnySequence<Iterator.Element> {
|
||||
public func dropLast(_ num: Int) -> AnySequence<Iterator.Element> {
|
||||
notImplemented(functionName: self.dropLast)
|
||||
return AnySequence<Iterator.Element> { return CommitIterator(repo: self.repo) }
|
||||
}
|
||||
|
||||
111
SwiftGit2/Diffs.swift
Normal file
111
SwiftGit2/Diffs.swift
Normal file
@ -0,0 +1,111 @@
|
||||
//
|
||||
// Diffs.swift
|
||||
// SwiftGit2
|
||||
//
|
||||
// Created by Jake Van Alstyne on 8/20/17.
|
||||
// Copyright © 2017 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
import Foundation
|
||||
import libgit2
|
||||
|
||||
public struct StatusEntry {
|
||||
public var status: Diff.Status
|
||||
public var headToIndex: Diff.Delta?
|
||||
public var indexToWorkDir: Diff.Delta?
|
||||
|
||||
public init(from statusEntry: git_status_entry) {
|
||||
self.status = Diff.Status(rawValue: statusEntry.status.rawValue)
|
||||
|
||||
if let htoi = statusEntry.head_to_index {
|
||||
self.headToIndex = Diff.Delta(htoi.pointee)
|
||||
}
|
||||
|
||||
if let itow = statusEntry.index_to_workdir {
|
||||
self.indexToWorkDir = Diff.Delta(itow.pointee)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct Diff {
|
||||
|
||||
/// The set of deltas.
|
||||
public var deltas = [Delta]()
|
||||
|
||||
public struct Delta {
|
||||
public static let type = GIT_OBJ_REF_DELTA
|
||||
|
||||
public var status: Status
|
||||
public var flags: Flags
|
||||
public var oldFile: File?
|
||||
public var newFile: File?
|
||||
|
||||
public init(_ delta: git_diff_delta) {
|
||||
self.status = Status(rawValue: UInt32(git_diff_status_char(delta.status)))
|
||||
self.flags = Flags(rawValue: delta.flags)
|
||||
self.oldFile = File(delta.old_file)
|
||||
self.newFile = File(delta.new_file)
|
||||
}
|
||||
}
|
||||
|
||||
public struct File {
|
||||
public var oid: OID
|
||||
public var path: String
|
||||
public var size: Int64
|
||||
public var flags: Flags
|
||||
|
||||
public init(_ diffFile: git_diff_file) {
|
||||
self.oid = OID(diffFile.id)
|
||||
let path = diffFile.path
|
||||
self.path = path.map(String.init(cString:))!
|
||||
self.size = diffFile.size
|
||||
self.flags = Flags(rawValue: diffFile.flags)
|
||||
}
|
||||
}
|
||||
|
||||
public struct Status: OptionSet {
|
||||
// This appears to be necessary due to bug in Swift
|
||||
// https://bugs.swift.org/browse/SR-3003
|
||||
public init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
public let rawValue: UInt32
|
||||
|
||||
public static let current = Status(rawValue: 0)
|
||||
public static let indexNew = Status(rawValue: 1 << 0)
|
||||
public static let indexModified = Status(rawValue: 1 << 1)
|
||||
public static let indexDeleted = Status(rawValue: 1 << 2)
|
||||
public static let indexRenamed = Status(rawValue: 1 << 3)
|
||||
public static let indexTypeChange = Status(rawValue: 1 << 4)
|
||||
public static let workTreeNew = Status(rawValue: 1 << 5)
|
||||
public static let workTreeModified = Status(rawValue: 1 << 6)
|
||||
public static let workTreeDeleted = Status(rawValue: 1 << 7)
|
||||
public static let workTreeTypeChange = Status(rawValue: 1 << 8)
|
||||
public static let workTreeRenamed = Status(rawValue: 1 << 9)
|
||||
public static let workTreeUnreadable = Status(rawValue: 1 << 10)
|
||||
public static let ignored = Status(rawValue: 1 << 11)
|
||||
public static let conflicted = Status(rawValue: 1 << 12)
|
||||
}
|
||||
|
||||
public struct Flags: OptionSet {
|
||||
// This appears to be necessary due to bug in Swift
|
||||
// https://bugs.swift.org/browse/SR-3003
|
||||
public init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
public let rawValue: UInt32
|
||||
|
||||
public static let binary = Flags(rawValue: 0)
|
||||
public static let notBinary = Flags(rawValue: 1 << 0)
|
||||
public static let validId = Flags(rawValue: 1 << 1)
|
||||
public static let exists = Flags(rawValue: 1 << 2)
|
||||
}
|
||||
|
||||
/// Create an instance with a libgit2 `git_diff`.
|
||||
public init(_ pointer: OpaquePointer) {
|
||||
for i in 0..<git_diff_num_deltas(pointer) {
|
||||
if let delta = git_diff_get_delta(pointer, i) {
|
||||
deltas.append(Diff.Delta(delta.pointee))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -225,6 +225,29 @@ final public class Repository {
|
||||
return withGitObject(oid, type: type) { Result.success(transform($0)) }
|
||||
}
|
||||
|
||||
private func withGitObjects<T>(_ oids: [OID], type: git_otype, transform: ([OpaquePointer]) -> Result<T, NSError>) -> Result<T, NSError> {
|
||||
var pointers = [OpaquePointer]()
|
||||
defer {
|
||||
for pointer in pointers {
|
||||
git_object_free(pointer)
|
||||
}
|
||||
}
|
||||
|
||||
for oid in oids {
|
||||
var pointer: OpaquePointer? = nil
|
||||
var oid = oid.oid
|
||||
let result = git_object_lookup(&pointer, self.pointer, &oid, type)
|
||||
|
||||
guard result == GIT_OK.rawValue else {
|
||||
return Result.failure(NSError(gitError: result, pointOfFailure: "git_object_lookup"))
|
||||
}
|
||||
|
||||
pointers.append(pointer!)
|
||||
}
|
||||
|
||||
return transform(pointers)
|
||||
}
|
||||
|
||||
/// Loads the object with the given OID.
|
||||
///
|
||||
/// oid - The OID of the blob to look up.
|
||||
@ -347,13 +370,14 @@ final public class Repository {
|
||||
|
||||
private func remoteLookup<A>(named name: String, _ callback: (Result<OpaquePointer, NSError>) -> A) -> A {
|
||||
var pointer: OpaquePointer? = nil
|
||||
defer { git_remote_free(pointer) }
|
||||
|
||||
let result = git_remote_lookup(&pointer, self.pointer, name)
|
||||
|
||||
guard result == GIT_OK.rawValue else {
|
||||
return callback(.failure(NSError(gitError: result, pointOfFailure: "git_remote_lookup")))
|
||||
}
|
||||
|
||||
defer { git_remote_free(pointer) }
|
||||
return callback(.success(pointer!))
|
||||
}
|
||||
|
||||
@ -379,7 +403,7 @@ final public class Repository {
|
||||
let err = NSError(gitError: result, pointOfFailure: "git_remote_fetch")
|
||||
return .failure(err)
|
||||
}
|
||||
return .success()
|
||||
return .success(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -497,7 +521,7 @@ final public class Repository {
|
||||
guard result == GIT_OK.rawValue else {
|
||||
return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_set_head"))
|
||||
}
|
||||
return Result.success()
|
||||
return Result.success(())
|
||||
}
|
||||
|
||||
/// Set HEAD to the given reference.
|
||||
@ -509,7 +533,7 @@ final public class Repository {
|
||||
guard result == GIT_OK.rawValue else {
|
||||
return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_set_head"))
|
||||
}
|
||||
return Result.success()
|
||||
return Result.success(())
|
||||
}
|
||||
|
||||
/// Check out HEAD.
|
||||
@ -525,7 +549,7 @@ final public class Repository {
|
||||
return Result.failure(NSError(gitError: result, pointOfFailure: "git_checkout_head"))
|
||||
}
|
||||
|
||||
return Result.success()
|
||||
return Result.success(())
|
||||
}
|
||||
|
||||
/// Check out the given OID.
|
||||
@ -558,4 +582,235 @@ final public class Repository {
|
||||
let iterator = CommitIterator(repo: self, root: branch.oid.oid)
|
||||
return iterator
|
||||
}
|
||||
|
||||
// MARK: - Diffs
|
||||
|
||||
public func diff(for commit: Commit) -> Result<Diff, NSError> {
|
||||
typealias Delta = Diff.Delta
|
||||
|
||||
guard !commit.parents.isEmpty else {
|
||||
// Initial commit in a repository
|
||||
return self.diff(from: nil, to: commit.oid)
|
||||
}
|
||||
|
||||
var mergeDiff: OpaquePointer? = nil
|
||||
defer { git_object_free(mergeDiff) }
|
||||
for parent in commit.parents {
|
||||
let error = self.diff(from: parent.oid, to: commit.oid) { (diff: Result<OpaquePointer, NSError>) -> NSError? in
|
||||
guard diff.error == nil else {
|
||||
return diff.error!
|
||||
}
|
||||
|
||||
if mergeDiff == nil {
|
||||
mergeDiff = diff.value!
|
||||
} else {
|
||||
let mergeResult = git_diff_merge(mergeDiff, diff.value)
|
||||
guard mergeResult == GIT_OK.rawValue else {
|
||||
return NSError(gitError: mergeResult, pointOfFailure: "git_diff_merge")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if error != nil {
|
||||
return Result<Diff, NSError>.failure(error!)
|
||||
}
|
||||
}
|
||||
|
||||
return .success(Diff(mergeDiff!))
|
||||
}
|
||||
|
||||
private func diff(from oldCommitOid: OID?, to newCommitOid: OID?, transform: (Result<OpaquePointer, NSError>) -> NSError?) -> NSError? {
|
||||
assert(oldCommitOid != nil || newCommitOid != nil, "It is an error to pass nil for both the oldOid and newOid")
|
||||
|
||||
var oldTree: OpaquePointer? = nil
|
||||
defer { git_object_free(oldTree) }
|
||||
if let oid = oldCommitOid {
|
||||
let result = unsafeTreeForCommitId(oid)
|
||||
guard result.error == nil else {
|
||||
return transform(Result.failure(result.error!))
|
||||
}
|
||||
|
||||
oldTree = result.value
|
||||
}
|
||||
|
||||
var newTree: OpaquePointer? = nil
|
||||
defer { git_object_free(newTree) }
|
||||
if let oid = newCommitOid {
|
||||
let result = unsafeTreeForCommitId(oid)
|
||||
guard result.error == nil else {
|
||||
return transform(Result.failure(result.error!))
|
||||
}
|
||||
|
||||
newTree = result.value
|
||||
}
|
||||
|
||||
var diff: OpaquePointer? = nil
|
||||
let diffResult = git_diff_tree_to_tree(&diff,
|
||||
self.pointer,
|
||||
oldTree,
|
||||
newTree,
|
||||
nil)
|
||||
|
||||
guard diffResult == GIT_OK.rawValue else {
|
||||
return transform(.failure(NSError(gitError: diffResult,
|
||||
pointOfFailure: "git_diff_tree_to_tree")))
|
||||
}
|
||||
|
||||
return transform(Result<OpaquePointer, NSError>.success(diff!))
|
||||
}
|
||||
|
||||
/// Memory safe
|
||||
private func diff(from oldCommitOid: OID?, to newCommitOid: OID?) -> Result<Diff, NSError> {
|
||||
assert(oldCommitOid != nil || newCommitOid != nil, "It is an error to pass nil for both the oldOid and newOid")
|
||||
|
||||
var oldTree: Tree? = nil
|
||||
if oldCommitOid != nil {
|
||||
let result = safeTreeForCommitId(oldCommitOid!)
|
||||
guard result.error == nil else {
|
||||
return Result<Diff, NSError>.failure(result.error!)
|
||||
}
|
||||
oldTree = result.value
|
||||
}
|
||||
|
||||
var newTree: Tree? = nil
|
||||
if newCommitOid != nil {
|
||||
let result = self.safeTreeForCommitId(newCommitOid!)
|
||||
guard result.error == nil else {
|
||||
return Result<Diff, NSError>.failure(result.error!)
|
||||
}
|
||||
newTree = result.value!
|
||||
}
|
||||
|
||||
if oldTree != nil && newTree != nil {
|
||||
return withGitObjects([oldTree!.oid, newTree!.oid], type: GIT_OBJ_TREE) { objects in
|
||||
var diff: OpaquePointer? = nil
|
||||
let diffResult = git_diff_tree_to_tree(&diff,
|
||||
self.pointer,
|
||||
objects[0],
|
||||
objects[1],
|
||||
nil)
|
||||
return processTreeToTreeDiff(diffResult, diff: diff)
|
||||
}
|
||||
} else if let tree = oldTree {
|
||||
return withGitObject(tree.oid, type: GIT_OBJ_TREE, transform: { tree in
|
||||
var diff: OpaquePointer? = nil
|
||||
let diffResult = git_diff_tree_to_tree(&diff,
|
||||
self.pointer,
|
||||
tree,
|
||||
nil,
|
||||
nil)
|
||||
return processTreeToTreeDiff(diffResult, diff: diff)
|
||||
})
|
||||
} else if let tree = newTree {
|
||||
return withGitObject(tree.oid, type: GIT_OBJ_TREE, transform: { tree in
|
||||
var diff: OpaquePointer? = nil
|
||||
let diffResult = git_diff_tree_to_tree(&diff,
|
||||
self.pointer,
|
||||
nil,
|
||||
tree,
|
||||
nil)
|
||||
return processTreeToTreeDiff(diffResult, diff: diff)
|
||||
})
|
||||
}
|
||||
|
||||
return .failure(NSError(gitError: -1, pointOfFailure: "diff(from: to:)"))
|
||||
}
|
||||
|
||||
private func processTreeToTreeDiff(_ diffResult: Int32, diff: OpaquePointer?) -> Result<Diff, NSError> {
|
||||
guard diffResult == GIT_OK.rawValue else {
|
||||
return .failure(NSError(gitError: diffResult,
|
||||
pointOfFailure: "git_diff_tree_to_tree"))
|
||||
}
|
||||
|
||||
let diffObj = Diff(diff!)
|
||||
git_diff_free(diff)
|
||||
return .success(diffObj)
|
||||
}
|
||||
|
||||
private func processDiffDeltas(_ diffResult: OpaquePointer) -> Result<[Diff.Delta], NSError> {
|
||||
typealias Delta = Diff.Delta
|
||||
var returnDict = [Delta]()
|
||||
|
||||
let count = git_diff_num_deltas(diffResult)
|
||||
|
||||
for i in 0..<count {
|
||||
let delta = git_diff_get_delta(diffResult, i)
|
||||
let gitDiffDelta = Diff.Delta((delta?.pointee)!)
|
||||
|
||||
returnDict.append(gitDiffDelta)
|
||||
}
|
||||
|
||||
let result = Result<[Diff.Delta], NSError>.success(returnDict)
|
||||
return result
|
||||
}
|
||||
|
||||
private func safeTreeForCommitId(_ oid: OID) -> Result<Tree, NSError> {
|
||||
return withGitObject(oid, type: GIT_OBJ_COMMIT) { commit in
|
||||
let treeId = git_commit_tree_id(commit)
|
||||
let tree = self.tree(OID(treeId!.pointee))
|
||||
guard tree.error == nil else {
|
||||
return .failure(tree.error!)
|
||||
}
|
||||
return tree
|
||||
}
|
||||
}
|
||||
|
||||
/// Caller responsible to free returned tree with git_object_free
|
||||
private func unsafeTreeForCommitId(_ oid: OID) -> Result<OpaquePointer, NSError> {
|
||||
var commit: OpaquePointer? = nil
|
||||
var oid = oid.oid
|
||||
let commitResult = git_object_lookup(&commit, self.pointer, &oid, GIT_OBJ_COMMIT)
|
||||
guard commitResult == GIT_OK.rawValue else {
|
||||
return .failure(NSError(gitError: commitResult, pointOfFailure: "git_object_lookup"))
|
||||
}
|
||||
|
||||
var tree: OpaquePointer? = nil
|
||||
let treeId = git_commit_tree_id(commit)
|
||||
let treeResult = git_object_lookup(&tree, self.pointer, treeId, GIT_OBJ_TREE)
|
||||
|
||||
git_object_free(commit)
|
||||
|
||||
guard treeResult == GIT_OK.rawValue else {
|
||||
return .failure(NSError(gitError: treeResult, pointOfFailure: "git_object_lookup"))
|
||||
}
|
||||
|
||||
return Result<OpaquePointer, NSError>.success(tree!)
|
||||
}
|
||||
|
||||
// MARK: - Status
|
||||
|
||||
public func status() -> Result<[StatusEntry], NSError> {
|
||||
var returnArray = [StatusEntry]()
|
||||
|
||||
// Do this because GIT_STATUS_OPTIONS_INIT is unavailable in swift
|
||||
let pointer = UnsafeMutablePointer<git_status_options>.allocate(capacity: 1)
|
||||
let optionsResult = git_status_init_options(pointer, UInt32(GIT_STATUS_OPTIONS_VERSION))
|
||||
guard optionsResult == GIT_OK.rawValue else {
|
||||
return .failure(NSError(gitError: optionsResult, pointOfFailure: "git_status_init_options"))
|
||||
}
|
||||
var options = pointer.move()
|
||||
pointer.deallocate(capacity: 1)
|
||||
|
||||
var unsafeStatus: OpaquePointer? = nil
|
||||
defer { git_status_list_free(unsafeStatus) }
|
||||
let statusResult = git_status_list_new(&unsafeStatus, self.pointer, &options)
|
||||
guard statusResult == GIT_OK.rawValue, let unwrapStatusResult = unsafeStatus else {
|
||||
return .failure(NSError(gitError: statusResult, pointOfFailure: "git_status_list_new"))
|
||||
}
|
||||
|
||||
let count = git_status_list_entrycount(unwrapStatusResult)
|
||||
|
||||
for i in 0..<count {
|
||||
let s = git_status_byindex(unwrapStatusResult, i)
|
||||
if s?.pointee.status.rawValue == GIT_STATUS_CURRENT.rawValue {
|
||||
continue
|
||||
}
|
||||
|
||||
let statusEntry = StatusEntry(from: s!.pointee)
|
||||
returnArray.append(statusEntry)
|
||||
}
|
||||
|
||||
return .success(returnArray)
|
||||
}
|
||||
}
|
||||
|
||||
BIN
SwiftGit2Tests/Fixtures/repository-with-status.zip
Normal file
BIN
SwiftGit2Tests/Fixtures/repository-with-status.zip
Normal file
Binary file not shown.
@ -633,7 +633,7 @@ class RepositorySpec: QuickSpec {
|
||||
"List branches in README\n",
|
||||
"Create a README\n",
|
||||
"List branches in README\n",
|
||||
"Create a README\n"
|
||||
"Create a README\n",
|
||||
]
|
||||
var commitMessages: [String] = []
|
||||
for branch in branches {
|
||||
@ -645,6 +645,208 @@ class RepositorySpec: QuickSpec {
|
||||
expect(commitMessages).to(equal(expectedMessages))
|
||||
}
|
||||
}
|
||||
|
||||
describe("Repository.status") {
|
||||
it("Should accurately report status for repositories with no status") {
|
||||
let expectedCount = 0
|
||||
|
||||
let repo = Fixtures.mantleRepository
|
||||
let branch = repo.localBranch(named: "master").value!
|
||||
expect(repo.checkout(branch, strategy: CheckoutStrategy.None).error).to(beNil())
|
||||
|
||||
let status = repo.status()
|
||||
|
||||
expect(status.value?.count).to(equal(expectedCount))
|
||||
}
|
||||
|
||||
it("Should accurately report status for repositories with status") {
|
||||
let expectedCount = 5
|
||||
let expectedNewFilePaths = [
|
||||
"stage-file-1",
|
||||
"stage-file-2",
|
||||
"stage-file-3",
|
||||
"stage-file-4",
|
||||
"stage-file-5",
|
||||
]
|
||||
let expectedOldFilePaths = [
|
||||
"stage-file-1",
|
||||
"stage-file-2",
|
||||
"stage-file-3",
|
||||
"stage-file-4",
|
||||
"stage-file-5",
|
||||
]
|
||||
|
||||
let repoWithStatus = Fixtures.sharedInstance.repository(named: "repository-with-status")
|
||||
let branchWithStatus = repoWithStatus.localBranch(named: "master").value!
|
||||
expect(repoWithStatus.checkout(branchWithStatus, strategy: CheckoutStrategy.None).error).to(beNil())
|
||||
|
||||
let statuses = repoWithStatus.status().value!
|
||||
|
||||
var newFilePaths: [String] = []
|
||||
for status in statuses {
|
||||
newFilePaths.append((status.headToIndex?.newFile?.path)!)
|
||||
}
|
||||
var oldFilePaths: [String] = []
|
||||
for status in statuses {
|
||||
oldFilePaths.append((status.headToIndex?.oldFile?.path)!)
|
||||
}
|
||||
|
||||
expect(statuses.count).to(equal(expectedCount))
|
||||
expect(newFilePaths).to(equal(expectedNewFilePaths))
|
||||
expect(oldFilePaths).to(equal(expectedOldFilePaths))
|
||||
}
|
||||
}
|
||||
|
||||
describe("Repository.diff") {
|
||||
it("Should have accurate delta information") {
|
||||
let expectedCount = 13
|
||||
let expectedNewFilePaths = [
|
||||
".gitmodules",
|
||||
"Cartfile",
|
||||
"Cartfile.lock",
|
||||
"Cartfile.private",
|
||||
"Cartfile.resolved",
|
||||
"Carthage.checkout/Nimble",
|
||||
"Carthage.checkout/Quick",
|
||||
"Carthage.checkout/xcconfigs",
|
||||
"Carthage/Checkouts/Nimble",
|
||||
"Carthage/Checkouts/Quick",
|
||||
"Carthage/Checkouts/xcconfigs",
|
||||
"Mantle.xcodeproj/project.pbxproj",
|
||||
"Mantle.xcworkspace/contents.xcworkspacedata",
|
||||
]
|
||||
let expectedOldFilePaths = [
|
||||
".gitmodules",
|
||||
"Cartfile",
|
||||
"Cartfile.lock",
|
||||
"Cartfile.private",
|
||||
"Cartfile.resolved",
|
||||
"Carthage.checkout/Nimble",
|
||||
"Carthage.checkout/Quick",
|
||||
"Carthage.checkout/xcconfigs",
|
||||
"Carthage/Checkouts/Nimble",
|
||||
"Carthage/Checkouts/Quick",
|
||||
"Carthage/Checkouts/xcconfigs",
|
||||
"Mantle.xcodeproj/project.pbxproj",
|
||||
"Mantle.xcworkspace/contents.xcworkspacedata",
|
||||
]
|
||||
|
||||
let repo = Fixtures.mantleRepository
|
||||
let branch = repo.localBranch(named: "master").value!
|
||||
expect(repo.checkout(branch, strategy: CheckoutStrategy.None).error).to(beNil())
|
||||
|
||||
let head = repo.HEAD().value!
|
||||
let commit = repo.object(head.oid).value! as! Commit
|
||||
let diff = repo.diff(for: commit).value!
|
||||
|
||||
let newFilePaths = diff.deltas.map { $0.newFile!.path }
|
||||
let oldFilePaths = diff.deltas.map { $0.oldFile!.path }
|
||||
|
||||
expect(diff.deltas.count).to(equal(expectedCount))
|
||||
expect(newFilePaths).to(equal(expectedNewFilePaths))
|
||||
expect(oldFilePaths).to(equal(expectedOldFilePaths))
|
||||
}
|
||||
|
||||
it("Should handle initial commit well") {
|
||||
let expectedCount = 2
|
||||
let expectedNewFilePaths = [
|
||||
".gitignore",
|
||||
"README.md",
|
||||
]
|
||||
let expectedOldFilePaths = [
|
||||
".gitignore",
|
||||
"README.md",
|
||||
]
|
||||
|
||||
let repo = Fixtures.mantleRepository
|
||||
expect(repo.checkout(OID(string: "047b931bd7f5478340cef5885a6fff713005f4d6")!,
|
||||
strategy: CheckoutStrategy.None).error).to(beNil())
|
||||
let head = repo.HEAD().value!
|
||||
let initalCommit = repo.object(head.oid).value! as! Commit
|
||||
let diff = repo.diff(for: initalCommit).value!
|
||||
|
||||
var newFilePaths: [String] = []
|
||||
for delta in diff.deltas {
|
||||
newFilePaths.append((delta.newFile?.path)!)
|
||||
}
|
||||
var oldFilePaths: [String] = []
|
||||
for delta in diff.deltas {
|
||||
oldFilePaths.append((delta.oldFile?.path)!)
|
||||
}
|
||||
|
||||
expect(diff.deltas.count).to(equal(expectedCount))
|
||||
expect(newFilePaths).to(equal(expectedNewFilePaths))
|
||||
expect(oldFilePaths).to(equal(expectedOldFilePaths))
|
||||
}
|
||||
|
||||
it("Should handle merge commits well") {
|
||||
let expectedCount = 20
|
||||
let expectedNewFilePaths = [
|
||||
"Mantle.xcodeproj/project.pbxproj",
|
||||
"Mantle/MTLModel+NSCoding.m",
|
||||
"Mantle/Mantle.h",
|
||||
"Mantle/NSArray+MTLHigherOrderAdditions.h",
|
||||
"Mantle/NSArray+MTLHigherOrderAdditions.m",
|
||||
"Mantle/NSArray+MTLManipulationAdditions.m",
|
||||
"Mantle/NSDictionary+MTLHigherOrderAdditions.h",
|
||||
"Mantle/NSDictionary+MTLHigherOrderAdditions.m",
|
||||
"Mantle/NSDictionary+MTLManipulationAdditions.m",
|
||||
"Mantle/NSNotificationCenter+MTLWeakReferenceAdditions.h",
|
||||
"Mantle/NSNotificationCenter+MTLWeakReferenceAdditions.m",
|
||||
"Mantle/NSOrderedSet+MTLHigherOrderAdditions.h",
|
||||
"Mantle/NSOrderedSet+MTLHigherOrderAdditions.m",
|
||||
"Mantle/NSSet+MTLHigherOrderAdditions.h",
|
||||
"Mantle/NSSet+MTLHigherOrderAdditions.m",
|
||||
"Mantle/NSValueTransformer+MTLPredefinedTransformerAdditions.m",
|
||||
"MantleTests/MTLHigherOrderAdditionsSpec.m",
|
||||
"MantleTests/MTLNotificationCenterAdditionsSpec.m",
|
||||
"MantleTests/MTLPredefinedTransformerAdditionsSpec.m",
|
||||
"README.md",
|
||||
]
|
||||
let expectedOldFilePaths = [
|
||||
"Mantle.xcodeproj/project.pbxproj",
|
||||
"Mantle/MTLModel+NSCoding.m",
|
||||
"Mantle/Mantle.h",
|
||||
"Mantle/NSArray+MTLHigherOrderAdditions.h",
|
||||
"Mantle/NSArray+MTLHigherOrderAdditions.m",
|
||||
"Mantle/NSArray+MTLManipulationAdditions.m",
|
||||
"Mantle/NSDictionary+MTLHigherOrderAdditions.h",
|
||||
"Mantle/NSDictionary+MTLHigherOrderAdditions.m",
|
||||
"Mantle/NSDictionary+MTLManipulationAdditions.m",
|
||||
"Mantle/NSNotificationCenter+MTLWeakReferenceAdditions.h",
|
||||
"Mantle/NSNotificationCenter+MTLWeakReferenceAdditions.m",
|
||||
"Mantle/NSOrderedSet+MTLHigherOrderAdditions.h",
|
||||
"Mantle/NSOrderedSet+MTLHigherOrderAdditions.m",
|
||||
"Mantle/NSSet+MTLHigherOrderAdditions.h",
|
||||
"Mantle/NSSet+MTLHigherOrderAdditions.m",
|
||||
"Mantle/NSValueTransformer+MTLPredefinedTransformerAdditions.m",
|
||||
"MantleTests/MTLHigherOrderAdditionsSpec.m",
|
||||
"MantleTests/MTLNotificationCenterAdditionsSpec.m",
|
||||
"MantleTests/MTLPredefinedTransformerAdditionsSpec.m",
|
||||
"README.md",
|
||||
]
|
||||
|
||||
let repo = Fixtures.mantleRepository
|
||||
expect(repo.checkout(OID(string: "d0d9c13da5eb5f9e8cf2a9f1f6ca3bdbe975b57d")!,
|
||||
strategy: CheckoutStrategy.None).error).to(beNil())
|
||||
let head = repo.HEAD().value!
|
||||
let initalCommit = repo.object(head.oid).value! as! Commit
|
||||
let diff = repo.diff(for: initalCommit).value!
|
||||
|
||||
var newFilePaths: [String] = []
|
||||
for delta in diff.deltas {
|
||||
newFilePaths.append((delta.newFile?.path)!)
|
||||
}
|
||||
var oldFilePaths: [String] = []
|
||||
for delta in diff.deltas {
|
||||
oldFilePaths.append((delta.oldFile?.path)!)
|
||||
}
|
||||
|
||||
expect(diff.deltas.count).to(equal(expectedCount))
|
||||
expect(newFilePaths).to(equal(expectedNewFilePaths))
|
||||
expect(oldFilePaths).to(equal(expectedOldFilePaths))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func temporaryURL(forPurpose purpose: String) -> URL {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user