mirror of
https://github.com/gosticks/SwiftGit2.git
synced 2025-10-16 11:55:34 +00:00
refactor diff calls to be memory safe
- use existing `withGitObject` method - introduce extended version that can handle multiple git objects for the purpose of handling multiple parents
This commit is contained in:
parent
1a369b7e9e
commit
0397b29d24
@ -5,6 +5,7 @@
|
|||||||
// Created by Jake Van Alstyne on 8/20/17.
|
// Created by Jake Van Alstyne on 8/20/17.
|
||||||
// Copyright © 2017 GitHub, Inc. All rights reserved.
|
// Copyright © 2017 GitHub, Inc. All rights reserved.
|
||||||
//
|
//
|
||||||
|
import Foundation
|
||||||
import libgit2
|
import libgit2
|
||||||
|
|
||||||
public struct StatusEntry {
|
public struct StatusEntry {
|
||||||
@ -26,6 +27,26 @@ public struct StatusEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct Diff {
|
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 struct File {
|
||||||
public var oid: OID
|
public var oid: OID
|
||||||
public var path: String
|
public var path: String
|
||||||
@ -79,17 +100,12 @@ public struct Diff {
|
|||||||
public static let exists = Flags(rawValue: 1 << 2)
|
public static let exists = Flags(rawValue: 1 << 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Delta {
|
/// Create an instance with a libgit2 `git_diff`.
|
||||||
public var status: Status
|
public init(_ pointer: OpaquePointer) {
|
||||||
public var flags: Flags
|
for i in 0..<git_diff_num_deltas(pointer) {
|
||||||
public var oldFile: File?
|
if let delta = git_diff_get_delta(pointer, i) {
|
||||||
public var newFile: File?
|
deltas.append(Diff.Delta(delta.pointee))
|
||||||
|
}
|
||||||
public init(_ delta: git_diff_delta) {
|
|
||||||
self.status = Status(rawValue: delta.status.rawValue)
|
|
||||||
self.flags = Flags(rawValue: delta.flags)
|
|
||||||
self.oldFile = File(delta.old_file)
|
|
||||||
self.newFile = File(delta.new_file)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -225,6 +225,27 @@ final public class Repository {
|
|||||||
return withGitObject(oid, type: type) { Result.success(transform($0)) }
|
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]()
|
||||||
|
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!)
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = transform(pointers)
|
||||||
|
for pointer in pointers {
|
||||||
|
git_object_free(pointer)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
/// Loads the object with the given OID.
|
/// Loads the object with the given OID.
|
||||||
///
|
///
|
||||||
/// oid - The OID of the blob to look up.
|
/// oid - The OID of the blob to look up.
|
||||||
@ -561,24 +582,26 @@ final public class Repository {
|
|||||||
|
|
||||||
// MARK: - Diffs
|
// MARK: - Diffs
|
||||||
|
|
||||||
public func diff(for commit: Commit) -> Result<[Diff.Delta], NSError> {
|
public func diff(for commit: Commit) -> Result<Diff, NSError> {
|
||||||
|
typealias Delta = Diff.Delta
|
||||||
|
|
||||||
guard !commit.parents.isEmpty else {
|
guard !commit.parents.isEmpty else {
|
||||||
// Initial commit in a repository
|
// Initial commit in a repository
|
||||||
let diff = self.diff(from: nil, to: commit.oid)
|
return self.diff(from: nil, to: commit.oid)
|
||||||
if let value = diff.value {
|
|
||||||
return self.processDiffDeltas(value)
|
|
||||||
}
|
|
||||||
return Result<[Diff.Delta], NSError>.failure(diff.error!)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var diffs = [OpaquePointer]()
|
||||||
var mergeDiff: OpaquePointer? = nil
|
var mergeDiff: OpaquePointer? = nil
|
||||||
for parent in commit.parents {
|
for parent in commit.parents {
|
||||||
let diff = self.diff(from: parent.oid, to: commit.oid)
|
let diff: Result<OpaquePointer, NSError> = self.diff(from: parent.oid, to: commit.oid)
|
||||||
guard diff.error == nil else {
|
guard diff.error == nil else {
|
||||||
return Result<[Diff.Delta], NSError>.failure(diff.error!)
|
return Result<Diff, NSError>.failure(diff.error!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diffs.append(diff.value!)
|
||||||
|
|
||||||
if mergeDiff == nil {
|
if mergeDiff == nil {
|
||||||
mergeDiff = diff.value
|
mergeDiff = diff.value!
|
||||||
} else {
|
} else {
|
||||||
let mergeResult = git_diff_merge(mergeDiff, diff.value)
|
let mergeResult = git_diff_merge(mergeDiff, diff.value)
|
||||||
guard mergeResult == GIT_OK.rawValue else {
|
guard mergeResult == GIT_OK.rawValue else {
|
||||||
@ -586,64 +609,132 @@ final public class Repository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return self.processDiffDeltas(mergeDiff!)
|
|
||||||
|
for diff in diffs {
|
||||||
|
git_object_free(diff)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func diff(from oldOid: OID?,
|
return .success(Diff(mergeDiff!))
|
||||||
to newOid: OID) -> Result<OpaquePointer, NSError> {
|
|
||||||
var oldPointer: OpaquePointer? = nil
|
|
||||||
if oldOid != nil {
|
|
||||||
let result = self.treePointer(from: oldOid!)
|
|
||||||
if let value = result.value {
|
|
||||||
oldPointer = value
|
|
||||||
} else {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var newPointer: OpaquePointer? = nil
|
/// Caller is responsible to free returned git_diff with git_object_free
|
||||||
let newResult = self.treePointer(from: newOid)
|
private func diff(from oldCommitOid: OID?, to newCommitOid: OID?) -> Result<OpaquePointer, NSError> {
|
||||||
if let value = newResult.value {
|
assert(oldCommitOid != nil || newCommitOid != nil, "It is an error to pass nil for both the oldOid and newOid")
|
||||||
newPointer = value
|
|
||||||
} else {
|
var oldTree: OpaquePointer? = nil
|
||||||
return newResult
|
if let oid = oldCommitOid {
|
||||||
|
let result = unsafeTreeForCommitId(oid)
|
||||||
|
guard result.error == nil else {
|
||||||
|
return Result.failure(result.error!)
|
||||||
}
|
}
|
||||||
|
|
||||||
var unsafeDiff: OpaquePointer? = nil
|
oldTree = result.value
|
||||||
let diffResult = git_diff_tree_to_tree(&unsafeDiff,
|
}
|
||||||
|
|
||||||
|
var newTree: OpaquePointer? = nil
|
||||||
|
if let oid = newCommitOid {
|
||||||
|
let result = unsafeTreeForCommitId(oid)
|
||||||
|
guard result.error == nil else {
|
||||||
|
return Result.failure(result.error!)
|
||||||
|
}
|
||||||
|
|
||||||
|
newTree = result.value
|
||||||
|
}
|
||||||
|
|
||||||
|
var diff: OpaquePointer? = nil
|
||||||
|
let diffResult = git_diff_tree_to_tree(&diff,
|
||||||
self.pointer,
|
self.pointer,
|
||||||
oldPointer,
|
oldTree,
|
||||||
newPointer,
|
newTree,
|
||||||
nil)
|
nil)
|
||||||
guard diffResult == GIT_OK.rawValue,
|
|
||||||
let unwrapDiffResult = unsafeDiff else {
|
git_object_free(oldTree)
|
||||||
|
git_object_free(newTree)
|
||||||
|
|
||||||
|
guard diffResult == GIT_OK.rawValue else {
|
||||||
return .failure(NSError(gitError: diffResult,
|
return .failure(NSError(gitError: diffResult,
|
||||||
pointOfFailure: "git_diff_tree_to_tree"))
|
pointOfFailure: "git_diff_tree_to_tree"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return .success(unwrapDiffResult)
|
return Result<OpaquePointer, NSError>.success(diff!)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func treePointer(from commitOid: OID) -> Result<OpaquePointer, NSError> {
|
/// Memory safe
|
||||||
var commit: OpaquePointer? = nil
|
private func diff(from oldCommitOid: OID?, to newCommitOid: OID?) -> Result<Diff, NSError> {
|
||||||
var _oid = commitOid.oid
|
assert(oldCommitOid != nil || newCommitOid != nil, "It is an error to pass nil for both the oldOid and newOid")
|
||||||
let commitResult = git_object_lookup(&commit, self.pointer, &_oid, GIT_OBJ_COMMIT)
|
|
||||||
guard commitResult == GIT_OK.rawValue else {
|
var oldTree: Tree? = nil
|
||||||
return .failure(NSError(gitError: commitResult,
|
if oldCommitOid != nil {
|
||||||
pointOfFailure: "git_object_lookup"))
|
let result = safeTreeForCommitId(oldCommitOid!)
|
||||||
|
guard result.error == nil else {
|
||||||
|
return Result<Diff, NSError>.failure(result.error!)
|
||||||
|
}
|
||||||
|
oldTree = result.value
|
||||||
}
|
}
|
||||||
|
|
||||||
var tree: OpaquePointer? = nil
|
var newTree: Tree? = nil
|
||||||
let treeResult = git_commit_tree(&tree, commit)
|
if newCommitOid != nil {
|
||||||
guard treeResult == GIT_OK.rawValue else {
|
let result = self.safeTreeForCommitId(newCommitOid!)
|
||||||
return .failure(NSError(gitError: treeResult,
|
guard result.error == nil else {
|
||||||
pointOfFailure: "git_object_lookup"))
|
return Result<Diff, NSError>.failure(result.error!)
|
||||||
|
}
|
||||||
|
newTree = result.value!
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = Result<OpaquePointer, NSError>.success(tree!)
|
if oldTree != nil && newTree != nil {
|
||||||
git_tree_free(tree)
|
return withGitObjects([oldTree!.oid, newTree!.oid], type: GIT_OBJ_TREE) { objects in
|
||||||
git_commit_free(commit)
|
var diff: OpaquePointer? = nil
|
||||||
return result
|
let diffResult = git_diff_tree_to_tree(&diff,
|
||||||
|
self.pointer,
|
||||||
|
objects[0],
|
||||||
|
objects[1],
|
||||||
|
nil)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
} 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)
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
} 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)
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return .failure(NSError(gitError: -1, pointOfFailure: "diff(from: to:)"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func processDiffDeltas(_ diffResult: OpaquePointer) -> Result<[Diff.Delta], NSError> {
|
private func processDiffDeltas(_ diffResult: OpaquePointer) -> Result<[Diff.Delta], NSError> {
|
||||||
@ -657,14 +748,45 @@ final public class Repository {
|
|||||||
let gitDiffDelta = Diff.Delta((delta?.pointee)!)
|
let gitDiffDelta = Diff.Delta((delta?.pointee)!)
|
||||||
|
|
||||||
returnDict.append(gitDiffDelta)
|
returnDict.append(gitDiffDelta)
|
||||||
|
|
||||||
git_diff_free(OpaquePointer(delta))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = Result<[Diff.Delta], NSError>.success(returnDict)
|
let result = Result<[Diff.Delta], NSError>.success(returnDict)
|
||||||
return result
|
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
|
// MARK: - Status
|
||||||
|
|
||||||
public func status() -> Result<[StatusEntry], NSError> {
|
public func status() -> Result<[StatusEntry], NSError> {
|
||||||
@ -693,7 +815,7 @@ final public class Repository {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let statusEntry = StatusEntry(from: (s?.pointee)!)
|
let statusEntry = StatusEntry(from: s!.pointee)
|
||||||
returnArray.append(statusEntry)
|
returnArray.append(statusEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -737,18 +737,12 @@ class RepositorySpec: QuickSpec {
|
|||||||
|
|
||||||
let head = repo.HEAD().value!
|
let head = repo.HEAD().value!
|
||||||
let commit = repo.object(head.oid).value! as! Commit
|
let commit = repo.object(head.oid).value! as! Commit
|
||||||
let deltas = repo.diff(for: commit).value!
|
let diff = repo.diff(for: commit).value!
|
||||||
|
|
||||||
var newFilePaths: [String] = []
|
let newFilePaths = diff.deltas.map { $0.newFile!.path }
|
||||||
for delta in deltas {
|
let oldFilePaths = diff.deltas.map { $0.oldFile!.path }
|
||||||
newFilePaths.append((delta.newFile?.path)!)
|
|
||||||
}
|
|
||||||
var oldFilePaths: [String] = []
|
|
||||||
for delta in deltas {
|
|
||||||
oldFilePaths.append((delta.oldFile?.path)!)
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(deltas.count).to(equal(expectedCount))
|
expect(diff.deltas.count).to(equal(expectedCount))
|
||||||
expect(newFilePaths).to(equal(expectedNewFilePaths))
|
expect(newFilePaths).to(equal(expectedNewFilePaths))
|
||||||
expect(oldFilePaths).to(equal(expectedOldFilePaths))
|
expect(oldFilePaths).to(equal(expectedOldFilePaths))
|
||||||
}
|
}
|
||||||
@ -769,18 +763,18 @@ class RepositorySpec: QuickSpec {
|
|||||||
strategy: CheckoutStrategy.None).error).to(beNil())
|
strategy: CheckoutStrategy.None).error).to(beNil())
|
||||||
let head = repo.HEAD().value!
|
let head = repo.HEAD().value!
|
||||||
let initalCommit = repo.object(head.oid).value! as! Commit
|
let initalCommit = repo.object(head.oid).value! as! Commit
|
||||||
let deltas = repo.diff(for: initalCommit).value!
|
let diff = repo.diff(for: initalCommit).value!
|
||||||
|
|
||||||
var newFilePaths: [String] = []
|
var newFilePaths: [String] = []
|
||||||
for delta in deltas {
|
for delta in diff.deltas {
|
||||||
newFilePaths.append((delta.newFile?.path)!)
|
newFilePaths.append((delta.newFile?.path)!)
|
||||||
}
|
}
|
||||||
var oldFilePaths: [String] = []
|
var oldFilePaths: [String] = []
|
||||||
for delta in deltas {
|
for delta in diff.deltas {
|
||||||
oldFilePaths.append((delta.oldFile?.path)!)
|
oldFilePaths.append((delta.oldFile?.path)!)
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(deltas.count).to(equal(expectedCount))
|
expect(diff.deltas.count).to(equal(expectedCount))
|
||||||
expect(newFilePaths).to(equal(expectedNewFilePaths))
|
expect(newFilePaths).to(equal(expectedNewFilePaths))
|
||||||
expect(oldFilePaths).to(equal(expectedOldFilePaths))
|
expect(oldFilePaths).to(equal(expectedOldFilePaths))
|
||||||
}
|
}
|
||||||
@ -837,18 +831,18 @@ class RepositorySpec: QuickSpec {
|
|||||||
strategy: CheckoutStrategy.None).error).to(beNil())
|
strategy: CheckoutStrategy.None).error).to(beNil())
|
||||||
let head = repo.HEAD().value!
|
let head = repo.HEAD().value!
|
||||||
let initalCommit = repo.object(head.oid).value! as! Commit
|
let initalCommit = repo.object(head.oid).value! as! Commit
|
||||||
let deltas = repo.diff(for: initalCommit).value!
|
let diff = repo.diff(for: initalCommit).value!
|
||||||
|
|
||||||
var newFilePaths: [String] = []
|
var newFilePaths: [String] = []
|
||||||
for delta in deltas {
|
for delta in diff.deltas {
|
||||||
newFilePaths.append((delta.newFile?.path)!)
|
newFilePaths.append((delta.newFile?.path)!)
|
||||||
}
|
}
|
||||||
var oldFilePaths: [String] = []
|
var oldFilePaths: [String] = []
|
||||||
for delta in deltas {
|
for delta in diff.deltas {
|
||||||
oldFilePaths.append((delta.oldFile?.path)!)
|
oldFilePaths.append((delta.oldFile?.path)!)
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(deltas.count).to(equal(expectedCount))
|
expect(diff.deltas.count).to(equal(expectedCount))
|
||||||
expect(newFilePaths).to(equal(expectedNewFilePaths))
|
expect(newFilePaths).to(equal(expectedNewFilePaths))
|
||||||
expect(oldFilePaths).to(equal(expectedOldFilePaths))
|
expect(oldFilePaths).to(equal(expectedOldFilePaths))
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user