diff --git a/SwiftGit2.xcodeproj/project.pbxproj b/SwiftGit2.xcodeproj/project.pbxproj index ab3abc3..50406a9 100644 --- a/SwiftGit2.xcodeproj/project.pbxproj +++ b/SwiftGit2.xcodeproj/project.pbxproj @@ -21,6 +21,10 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 232861431F4A3A2E00276D65 /* Diffs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232861421F4A3A2E00276D65 /* Diffs.swift */; }; + 232861441F4A3A2E00276D65 /* Diffs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232861421F4A3A2E00276D65 /* Diffs.swift */; }; + 232861451F4A3A2E00276D65 /* Diffs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232861421F4A3A2E00276D65 /* Diffs.swift */; }; + 232861461F4A3A2E00276D65 /* 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 */; }; @@ -132,6 +136,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 232861421F4A3A2E00276D65 /* Diffs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Diffs.swift; sourceTree = ""; }; 237731C61F46542B0020A3FE /* repository-with-status.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = "repository-with-status.zip"; sourceTree = ""; }; 25499A996CA7BD416620A397 /* CommitIterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommitIterator.swift; sourceTree = ""; }; 621E66B41C72958800A0F352 /* SwiftGit2.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftGit2.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -280,6 +285,7 @@ isa = PBXGroup; children = ( BEB31F251A0D6F7A00F525B9 /* SwiftGit2 */, + BEB31F261A0D6F7A00F525B9 /* Supporting Files */, BEB31F321A0D6F7A00F525B9 /* SwiftGit2Tests */, BEB31FA11A0E63C100F525B9 /* Libraries */, BEB31F411A0D75EE00F525B9 /* Configuration */, @@ -313,12 +319,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; @@ -330,6 +336,7 @@ BEB31F271A0D6F7A00F525B9 /* Info.plist */, ); name = "Supporting Files"; + path = SwiftGit2; sourceTree = ""; }; BEB31F321A0D6F7A00F525B9 /* SwiftGit2Tests */ = { @@ -752,6 +759,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 */, @@ -767,6 +775,7 @@ files = ( 621E66BA1C72958D00A0F352 /* RepositorySpec.swift in Sources */, 621E66BB1C72958D00A0F352 /* ObjectsSpec.swift in Sources */, + 232861461F4A3A2E00276D65 /* Diffs.swift in Sources */, 621E66BC1C72958D00A0F352 /* RemotesSpec.swift in Sources */, 621E66BD1C72958D00A0F352 /* FixturesSpec.swift in Sources */, 621E66BE1C72958D00A0F352 /* Fixtures.swift in Sources */, @@ -785,6 +794,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 */, @@ -800,6 +810,7 @@ files = ( BEB31F361A0D6F7A00F525B9 /* RepositorySpec.swift in Sources */, BE2E3BE81A31262800C67092 /* ObjectsSpec.swift in Sources */, + 232861441F4A3A2E00276D65 /* Diffs.swift in Sources */, BECB5F701A57286200999413 /* RemotesSpec.swift in Sources */, BE14AA591A1996B70015B439 /* FixturesSpec.swift in Sources */, BE14AA551A1984550015B439 /* Fixtures.swift in Sources */, diff --git a/SwiftGit2/Diffs.swift b/SwiftGit2/Diffs.swift new file mode 100644 index 0000000..0725e2b --- /dev/null +++ b/SwiftGit2/Diffs.swift @@ -0,0 +1,60 @@ +// +// Diffs.swift +// SwiftGit2 +// +// Created by Jake Van Alstyne on 8/20/17. +// Copyright © 2017 GitHub, Inc. All rights reserved. +// + +public struct GitDiffFile { + var oid: OID + var path: String + var size: Int64 + var flags: UInt32 +} + +public enum GitDeltaStatus: Int { + case current + case indexNew + case indexModified + case indexDeleted + case indexRenamed + case indexTypeChange + case workTreeNew + case workTreeModified + case workTreeDeleted + case workTreeTypeChange + case workTreeRenamed + case workTreeUnreadable + case ignored + case conflicted + + var value: UInt32 { + if self.rawValue == 0 { + return UInt32(0) + } + return UInt32(1 << (self.rawValue - 1)) + } +} + +public struct GitDiffDelta { + var status: GitDeltaStatus + var flags: UInt32 + var oldFile: GitDiffFile + var newFile: GitDiffFile +} + +public enum GitDiffFlag: Int { + case binary + case notBinary + case validId + case exists + + var value: UInt32 { + if self.rawValue == 0 { + return UInt32(0) + } + return UInt32(1 << (self.rawValue - 1)) + } +} + diff --git a/SwiftGit2/Repository.swift b/SwiftGit2/Repository.swift index db1f565..fcd961b 100644 --- a/SwiftGit2/Repository.swift +++ b/SwiftGit2/Repository.swift @@ -538,11 +538,10 @@ final public class Repository { return iterator } - // MARK: - Status - - public func getObjectsWithStatus(for commit: Commit) -> Result<[ObjectType], NSError> { - var returnDict = [ObjectType]() + // MARK: - Diffs + public func getDiffDeltas(for commit: Commit) -> Result<[GitDiffDelta], NSError> { + /// Get the Base Tree var unsafeBaseCommit: OpaquePointer? = nil let unsafeBaseOid = UnsafeMutablePointer.allocate(capacity: 1) git_oid_fromstr(unsafeBaseOid, commit.oid.description) @@ -552,10 +551,35 @@ final public class Repository { } git_commit_free(unsafeBaseCommit) - guard !commit.parents.isEmpty else { - // TODO: need to handle the initial commit - return Result.failure(NSError(gitError: 0, pointOfFailure: "getObjectsWithStatus")) + var unsafeBaseTree: OpaquePointer? = nil + let baseTreeResult = git_commit_tree(&unsafeBaseTree, unwrapBaseCommit) + guard baseTreeResult == GIT_OK.rawValue, let unwrapBaseTree = unsafeBaseTree else { + return Result.failure(NSError(gitError: baseTreeResult, pointOfFailure: "git_commit_tree")) } + git_tree_free(unsafeBaseTree) + + if commit.parents.isEmpty { + return self.getDiffDeltasWithNoParents(from: unwrapBaseTree) + } else if commit.parents.count == 1 { + return self.getDiffDeltasWithOneParent(from: unwrapBaseTree, in: commit) + } else { + return self.getDiffDeltasWithMultipleParents(from: unwrapBaseTree, in: commit) + } + } + + private func getDiffDeltasWithNoParents(from baseTree: OpaquePointer) -> Result<[GitDiffDelta], NSError> { + var unsafeDiff: OpaquePointer? = nil + let diffResult = git_diff_tree_to_tree(&unsafeDiff, self.pointer, nil, baseTree, nil) + guard diffResult == GIT_OK.rawValue, let unwrapDiffResult = unsafeDiff else { + return Result.failure(NSError(gitError: diffResult, pointOfFailure: "git_diff_tree_to_tree")) + } + + return self.processDiffDeltas(unwrapDiffResult) + } + + private func getDiffDeltasWithOneParent(from baseTree: OpaquePointer, + in commit: Commit) -> Result<[GitDiffDelta], NSError> { + /// Get the Parent Tree let parent = commit.parents[0] var unsafeParentCommit: OpaquePointer? = nil let unsafeParentOid = UnsafeMutablePointer.allocate(capacity: 1) @@ -566,13 +590,6 @@ final public class Repository { } git_commit_free(unsafeParentCommit) - var unsafeBaseTree: OpaquePointer? = nil - let baseTreeResult = git_commit_tree(&unsafeBaseTree, unwrapBaseCommit) - guard baseTreeResult == GIT_OK.rawValue, let unwrapBaseTree = unsafeBaseTree else { - return Result.failure(NSError(gitError: baseTreeResult, pointOfFailure: "git_commit_tree")) - } - git_tree_free(unsafeBaseTree) - var unsafeParentTree: OpaquePointer? = nil let parentTreeResult = git_commit_tree(&unsafeParentTree, unwrapParentCommit) guard parentTreeResult == GIT_OK.rawValue, let unwrapParentTree = unsafeParentTree else { @@ -581,32 +598,117 @@ final public class Repository { git_tree_free(unsafeParentTree) var unsafeDiff: OpaquePointer? = nil - let diffResult = git_diff_tree_to_tree(&unsafeDiff, self.pointer, unwrapBaseTree, unwrapParentTree, nil) + let diffResult = git_diff_tree_to_tree(&unsafeDiff, self.pointer, unwrapParentTree, baseTree, nil) guard diffResult == GIT_OK.rawValue, let unwrapDiffResult = unsafeDiff else { return Result.failure(NSError(gitError: diffResult, pointOfFailure: "git_diff_tree_to_tree")) } - let count = git_diff_num_deltas(unwrapDiffResult) + return self.processDiffDeltas(unwrapDiffResult) + } + + private func getDiffDeltasWithMultipleParents(from baseTree: OpaquePointer, + in commit: Commit) -> Result<[GitDiffDelta], NSError> { + // Merge Commit, merge diffs of base with each parent + var mergeDiff: OpaquePointer? = nil + for parent in commit.parents { + var unsafeParentCommit: OpaquePointer? = nil + let unsafeParentOid = UnsafeMutablePointer.allocate(capacity: 1) + git_oid_fromstr(unsafeParentOid, parent.oid.description) + let lookupParentGitResult = git_commit_lookup(&unsafeParentCommit, self.pointer, unsafeParentOid) + guard lookupParentGitResult == GIT_OK.rawValue, let unwrapParentCommit = unsafeParentCommit else { + return Result.failure(NSError(gitError: lookupParentGitResult, pointOfFailure: "git_commit_lookup")) + } + git_commit_free(unsafeParentCommit) + + var unsafeParentTree: OpaquePointer? = nil + let parentTreeResult = git_commit_tree(&unsafeParentTree, unwrapParentCommit) + guard parentTreeResult == GIT_OK.rawValue, let unwrapParentTree = unsafeParentTree else { + return Result.failure(NSError(gitError: parentTreeResult, pointOfFailure: "git_commit_tree")) + } + git_tree_free(unsafeParentTree) + + var unsafeDiff: OpaquePointer? = nil + let diffResult = git_diff_tree_to_tree(&unsafeDiff, self.pointer, unwrapParentTree, baseTree, nil) + guard diffResult == GIT_OK.rawValue, let unwrapDiffResult = unsafeDiff else { + return Result.failure(NSError(gitError: diffResult, pointOfFailure: "git_diff_tree_to_tree")) + } + + if mergeDiff == nil { + mergeDiff = unwrapDiffResult + } else { + let mergeResult = git_diff_merge(mergeDiff, unwrapDiffResult) + guard mergeResult == GIT_OK.rawValue else { + return Result.failure(NSError(gitError: mergeResult, pointOfFailure: "git_diff_merge")) + } + } + } + return self.processDiffDeltas(mergeDiff!) + } + + private func processDiffDeltas(_ diffResult: OpaquePointer) -> Result<[GitDiffDelta], NSError> { + var returnDict = [GitDiffDelta]() + + let count = git_diff_num_deltas(diffResult) for i in 0...success(returnDict) + let result = Result<[GitDiffDelta], NSError>.success(returnDict) return result } - public func getStatus(for object: ObjectType, in commit: Commit) -> String { - let returnString = "" - - return returnString - } + // MARK: - Status public func getRepositoryStatus() -> String { diff --git a/SwiftGit2Tests/RepositorySpec.swift b/SwiftGit2Tests/RepositorySpec.swift index caa4b17..5347261 100644 --- a/SwiftGit2Tests/RepositorySpec.swift +++ b/SwiftGit2Tests/RepositorySpec.swift @@ -643,24 +643,52 @@ class RepositorySpec: QuickSpec { } describe("Repository.getRepositoryStatus") { - it("Should not return nothing") { - let repo = Fixtures.sharedInstance.repository(named: "repository-with-status") + it("Should return accurate status") { + let repo = Fixtures.mantleRepository + let branch = repo.localBranch(named: "master").value! + expect(repo.checkout(branch, strategy: CheckoutStrategy.None)).to(haveSucceeded()) + let status = repo.getRepositoryStatus() - expect(status).to(equal("A staged-file\n")) + expect(status).to(equal("")) } - it("Should have objects with status") { - let repo = Fixtures.sharedInstance.repository(named: "repository-with-status") + it("Should have accurate delta information") { + let repo = Fixtures.mantleRepository + let branch = repo.localBranch(named: "master").value! + expect(repo.checkout(branch, strategy: CheckoutStrategy.None)).to(haveSucceeded()) + let head = repo.HEAD().value! let commit = repo.object(head.oid).value! as! Commit - let objects = repo.getObjectsWithStatus(for: commit) + let objects = repo.getDiffDeltas(for: commit) - expect(objects.value?.count).to(equal(1)) + expect(objects.value?.count).to(equal(13)) + } + + it("Should handle initial commit well") { + let repo = Fixtures.mantleRepository + expect(repo.checkout(OID(string: "047b931bd7f5478340cef5885a6fff713005f4d6")!, + strategy: CheckoutStrategy.None)).to(haveSucceeded()) + let head = repo.HEAD().value! + let initalCommit = repo.object(head.oid).value! as! Commit + let objects = repo.getDiffDeltas(for: initalCommit) + + expect(objects.value?.count).to(equal(2)) + } + + it("Should handle merge commits well") { + let repo = Fixtures.mantleRepository + expect(repo.checkout(OID(string: "d0d9c13da5eb5f9e8cf2a9f1f6ca3bdbe975b57d")!, + strategy: CheckoutStrategy.None)).to(haveSucceeded()) + let head = repo.HEAD().value! + let initalCommit = repo.object(head.oid).value! as! Commit + let objects = repo.getDiffDeltas(for: initalCommit) + + expect(objects.value?.count).to(equal(20)) } } } - + func temporaryURL(forPurpose purpose: String) -> URL { let globallyUniqueString = ProcessInfo.processInfo.globallyUniqueString let path = "\(NSTemporaryDirectory())\(globallyUniqueString)_\(purpose)"