diff --git a/.github/workflows/BuildPR.yml b/.github/workflows/BuildPR.yml index e05c8213..3d9f01e1 100644 --- a/.github/workflows/BuildPR.yml +++ b/.github/workflows/BuildPR.yml @@ -13,7 +13,7 @@ jobs: - name: ls Xcode run: ls -la /Applications/Xcode* - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set Xcode Version run: sudo xcode-select -s /Applications/Xcode_15.2.app - name: Build and test on macOS diff --git a/.gitignore b/.gitignore index 4876dda1..bb460e7b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,47 +1,7 @@ -.vscode/ -.build/ -.swiftpm/ - -# Xcode -# -build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside -DerivedData -*.hmap -*.ipa -*.xcuserstate -*.xcscmblueprint -*.idea* - -External/libgit2*.a -External/libgit2-mac -External/ios-openssl -External/libgit2-ios -External/libssh2-ios - -### fastlane ### -# fastlane - A streamlined workflow tool for Cocoa deployment - -# fastlane specific -fastlane/report.xml - -# deliver temporary files -fastlane/Preview.html - -# snapshot generated screenshots -fastlane/screenshots/**/*.png -fastlane/screenshots/screenshots.html - -# scan temporary files -fastlane/test_output -test_output +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 00000000..919deee6 --- /dev/null +++ b/.swiftformat @@ -0,0 +1,2 @@ +--disable unusedArguments +--indent 4 diff --git a/Sources/SwiftGit2/CheckoutStrategy.swift b/Sources/SwiftGit2/CheckoutStrategy.swift index b8076779..1860a844 100644 --- a/Sources/SwiftGit2/CheckoutStrategy.swift +++ b/Sources/SwiftGit2/CheckoutStrategy.swift @@ -11,97 +11,89 @@ import libgit2 /// The flags defining how a checkout should be performed. /// More detail is available in the libgit2 documentation for `git_checkout_strategy_t`. public struct CheckoutStrategy: OptionSet { - private let value: UInt + // MARK: - Properties - // MARK: - Initialization + public let rawValue: UInt - /// Create an instance initialized with `nil`. - public init(nilLiteral: ()) { - self.value = 0 - } + // MARK: - Initialization - public init(rawValue value: UInt) { - self.value = value - } + /// Create an instance initialized with `nil`. + public init(nilLiteral: ()) { + rawValue = 0 + } - public init(_ strategy: git_checkout_strategy_t) { - self.value = UInt(strategy.rawValue) - } + public init(rawValue: UInt) { + self.rawValue = rawValue + } - public static var allZeros: CheckoutStrategy { - return self.init(rawValue: 0) - } + init(_ strategy: git_checkout_strategy_t) { + rawValue = UInt(strategy.rawValue) + } - // MARK: - Properties + var gitCheckoutStrategy: git_checkout_strategy_t { + git_checkout_strategy_t(UInt32(rawValue)) + } - public var rawValue: UInt { - return value - } + // MARK: - Values - public var gitCheckoutStrategy: git_checkout_strategy_t { - return git_checkout_strategy_t(UInt32(self.value)) - } + /// Default is a dry run, no actual updates. + public static let none = CheckoutStrategy(GIT_CHECKOUT_NONE) - // MARK: - Values + /// Allow safe updates that cannot overwrite uncommitted data. + public static let safe = CheckoutStrategy(GIT_CHECKOUT_SAFE) - /// Default is a dry run, no actual updates. - public static let None = CheckoutStrategy(GIT_CHECKOUT_NONE) + /// Allow all updates to force working directory to look like index + public static let force = CheckoutStrategy(GIT_CHECKOUT_FORCE) - /// Allow safe updates that cannot overwrite uncommitted data. - public static let Safe = CheckoutStrategy(GIT_CHECKOUT_SAFE) + /// Allow checkout to recreate missing files. + public static let recreateMissing = CheckoutStrategy(GIT_CHECKOUT_RECREATE_MISSING) - /// Allow all updates to force working directory to look like index - public static let Force = CheckoutStrategy(GIT_CHECKOUT_FORCE) + /// Allow checkout to make safe updates even if conflicts are found. + public static let allowConflicts = CheckoutStrategy(GIT_CHECKOUT_ALLOW_CONFLICTS) - /// Allow checkout to recreate missing files. - public static let RecreateMissing = CheckoutStrategy(GIT_CHECKOUT_RECREATE_MISSING) + /// Remove untracked files not in index (that are not ignored). + public static let removeUntracked = CheckoutStrategy(GIT_CHECKOUT_REMOVE_UNTRACKED) - /// Allow checkout to make safe updates even if conflicts are found. - public static let AllowConflicts = CheckoutStrategy(GIT_CHECKOUT_ALLOW_CONFLICTS) + /// Remove ignored files not in index. + public static let removeIgnored = CheckoutStrategy(GIT_CHECKOUT_REMOVE_IGNORED) - /// Remove untracked files not in index (that are not ignored). - public static let RemoveUntracked = CheckoutStrategy(GIT_CHECKOUT_REMOVE_UNTRACKED) + /// Only update existing files, don't create new ones. + public static let updateOnly = CheckoutStrategy(GIT_CHECKOUT_UPDATE_ONLY) - /// Remove ignored files not in index. - public static let RemoveIgnored = CheckoutStrategy(GIT_CHECKOUT_REMOVE_IGNORED) + /// Normally checkout updates index entries as it goes; this stops that. + /// Implies `DontWriteIndex`. + public static let dontUpdateIndex = CheckoutStrategy(GIT_CHECKOUT_DONT_UPDATE_INDEX) - /// Only update existing files, don't create new ones. - public static let UpdateOnly = CheckoutStrategy(GIT_CHECKOUT_UPDATE_ONLY) + /// Don't refresh index/config/etc before doing checkout + public static let noRefresh = CheckoutStrategy(GIT_CHECKOUT_NO_REFRESH) - /// Normally checkout updates index entries as it goes; this stops that. - /// Implies `DontWriteIndex`. - public static let DontUpdateIndex = CheckoutStrategy(GIT_CHECKOUT_DONT_UPDATE_INDEX) + /// Allow checkout to skip unmerged files + public static let skipUnmerged = CheckoutStrategy(GIT_CHECKOUT_SKIP_UNMERGED) - /// Don't refresh index/config/etc before doing checkout - public static let NoRefresh = CheckoutStrategy(GIT_CHECKOUT_NO_REFRESH) + /// For unmerged files, checkout stage 2 from index + public static let useOurs = CheckoutStrategy(GIT_CHECKOUT_USE_OURS) - /// Allow checkout to skip unmerged files - public static let SkipUnmerged = CheckoutStrategy(GIT_CHECKOUT_SKIP_UNMERGED) + /// For unmerged files, checkout stage 3 from index + public static let useTheirs = CheckoutStrategy(GIT_CHECKOUT_USE_THEIRS) - /// For unmerged files, checkout stage 2 from index - public static let UseOurs = CheckoutStrategy(GIT_CHECKOUT_USE_OURS) + /// Treat pathspec as simple list of exact match file paths + public static let disablePathspecMatch = CheckoutStrategy(GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) - /// For unmerged files, checkout stage 3 from index - public static let UseTheirs = CheckoutStrategy(GIT_CHECKOUT_USE_THEIRS) + /// Ignore directories in use, they will be left empty + public static let skipLockedDirectories = CheckoutStrategy(GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES) - /// Treat pathspec as simple list of exact match file paths - public static let DisablePathspecMatch = CheckoutStrategy(GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) + /// Don't overwrite ignored files that exist in the checkout target + public static let dontOverwriteIgnored = CheckoutStrategy(GIT_CHECKOUT_DONT_OVERWRITE_IGNORED) - /// Ignore directories in use, they will be left empty - public static let SkipLockedDirectories = CheckoutStrategy(GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES) + /// Write normal merge files for conflicts + public static let conflictStyleMerge = CheckoutStrategy(GIT_CHECKOUT_CONFLICT_STYLE_MERGE) - /// Don't overwrite ignored files that exist in the checkout target - public static let DontOverwriteIgnored = CheckoutStrategy(GIT_CHECKOUT_DONT_OVERWRITE_IGNORED) + /// Include common ancestor data in diff3 format files for conflicts + public static let conflictStyleDiff3 = CheckoutStrategy(GIT_CHECKOUT_CONFLICT_STYLE_DIFF3) - /// Write normal merge files for conflicts - public static let ConflictStyleMerge = CheckoutStrategy(GIT_CHECKOUT_CONFLICT_STYLE_MERGE) + /// Don't overwrite existing files or folders + public static let dontRemoveExisting = CheckoutStrategy(GIT_CHECKOUT_DONT_REMOVE_EXISTING) - /// Include common ancestor data in diff3 format files for conflicts - public static let ConflictStyleDiff3 = CheckoutStrategy(GIT_CHECKOUT_CONFLICT_STYLE_DIFF3) - - /// Don't overwrite existing files or folders - public static let DontRemoveExisting = CheckoutStrategy(GIT_CHECKOUT_DONT_REMOVE_EXISTING) - - /// Normally checkout writes the index upon completion; this prevents that. - public static let DontWriteIndex = CheckoutStrategy(GIT_CHECKOUT_DONT_WRITE_INDEX) + /// Normally checkout writes the index upon completion; this prevents that. + public static let dontWriteIndex = CheckoutStrategy(GIT_CHECKOUT_DONT_WRITE_INDEX) } diff --git a/Sources/SwiftGit2/CommitIterator.swift b/Sources/SwiftGit2/CommitIterator.swift index 4f63ecf1..93d13dc2 100644 --- a/Sources/SwiftGit2/CommitIterator.swift +++ b/Sources/SwiftGit2/CommitIterator.swift @@ -7,64 +7,65 @@ import Foundation import libgit2 public class CommitIterator: IteratorProtocol, Sequence { - public typealias Iterator = CommitIterator - public typealias Element = Result - let repo: Repository - private var revisionWalker: OpaquePointer? + public typealias Iterator = CommitIterator + public typealias Element = Result + let repo: Repository + private var revisionWalker: OpaquePointer? - private enum Next { - case over - case okay - case error(NSError) + private enum Next { + case over + case okay + case error(NSError) - init(_ result: Int32, name: String) { - switch result { - case GIT_ITEROVER.rawValue: - self = .over - case GIT_OK.rawValue: - self = .okay - default: - self = .error(NSError(gitError: result, pointOfFailure: name)) - } - } - } + init(_ result: Int32, name: String) { + switch result { + case GIT_ITEROVER.rawValue: + self = .over + case GIT_OK.rawValue: + self = .okay + default: + self = .error(NSError(gitError: result, pointOfFailure: name)) + } + } + } - init(repo: Repository, root: git_oid) { - self.repo = repo - setupRevisionWalker(root: root) - } + init(repo: Repository, root: git_oid) { + self.repo = repo + setupRevisionWalker(root: root) + } - deinit { - git_revwalk_free(self.revisionWalker) - } + deinit { + git_revwalk_free(self.revisionWalker) + } - private func setupRevisionWalker(root: git_oid) { - var oid = root - git_revwalk_new(&revisionWalker, repo.pointer) - git_revwalk_sorting(revisionWalker, GIT_SORT_TOPOLOGICAL.rawValue) - git_revwalk_sorting(revisionWalker, GIT_SORT_TIME.rawValue) - git_revwalk_push(revisionWalker, &oid) - } + private func setupRevisionWalker(root: git_oid) { + var oid = root + git_revwalk_new(&revisionWalker, repo.pointer) + git_revwalk_sorting(revisionWalker, GIT_SORT_TOPOLOGICAL.rawValue) + git_revwalk_sorting(revisionWalker, GIT_SORT_TIME.rawValue) + git_revwalk_push(revisionWalker, &oid) + } - public func next() -> Element? { - var oid = git_oid() - let revwalkGitResult = git_revwalk_next(&oid, revisionWalker) - let nextResult = Next(revwalkGitResult, name: "git_revwalk_next") - switch nextResult { - case let .error(error): - return Result.failure(error) - case .over: - return nil - case .okay: - var unsafeCommit: OpaquePointer? = nil - let lookupGitResult = git_commit_lookup(&unsafeCommit, repo.pointer, &oid) - guard lookupGitResult == GIT_OK.rawValue, - let unwrapCommit = unsafeCommit else { - return Result.failure(NSError(gitError: lookupGitResult, pointOfFailure: "git_commit_lookup")) - } - let result: Element = Result.success(Commit(unwrapCommit)) - git_commit_free(unsafeCommit) - return result - } - } + public func next() -> Element? { + var oid = git_oid() + let revwalkGitResult = git_revwalk_next(&oid, revisionWalker) + let nextResult = Next(revwalkGitResult, name: "git_revwalk_next") + switch nextResult { + case let .error(error): + return Result.failure(error) + case .over: + return nil + case .okay: + var unsafeCommit: OpaquePointer? = nil + let lookupGitResult = git_commit_lookup(&unsafeCommit, repo.pointer, &oid) + guard lookupGitResult == GIT_OK.rawValue, + let unwrapCommit = unsafeCommit + else { + return Result.failure(NSError(gitError: lookupGitResult, pointOfFailure: "git_commit_lookup")) + } + let result: Element = Result.success(Commit(unwrapCommit)) + git_commit_free(unsafeCommit) + return result + } + } } diff --git a/Sources/SwiftGit2/Credentials.swift b/Sources/SwiftGit2/Credentials.swift index 2ac5884c..280f19d5 100644 --- a/Sources/SwiftGit2/Credentials.swift +++ b/Sources/SwiftGit2/Credentials.swift @@ -9,53 +9,53 @@ import libgit2 private class Wrapper { - let value: T + let value: T - init(_ value: T) { - self.value = value - } + init(_ value: T) { + self.value = value + } } public enum Credentials { - case `default` - case sshAgent - case plaintext(username: String, password: String) - case sshMemory(username: String, publicKey: String, privateKey: String, passphrase: String) - - internal static func fromPointer(_ pointer: UnsafeMutableRawPointer) -> Credentials { - return Unmanaged>.fromOpaque(UnsafeRawPointer(pointer)).takeRetainedValue().value - } - - internal func toPointer() -> UnsafeMutableRawPointer { - return Unmanaged.passRetained(Wrapper(self)).toOpaque() - } + case `default` + case sshAgent + case plaintext(username: String, password: String) + case sshMemory(username: String, publicKey: String, privateKey: String, passphrase: String) + + static func fromPointer(_ pointer: UnsafeMutableRawPointer) -> Credentials { + return Unmanaged>.fromOpaque(UnsafeRawPointer(pointer)).takeRetainedValue().value + } + + func toPointer() -> UnsafeMutableRawPointer { + return Unmanaged.passRetained(Wrapper(self)).toOpaque() + } } /// Handle the request of credentials, passing through to a wrapped block after converting the arguments. /// Converts the result to the correct error code required by libgit2 (0 = success, 1 = rejected setting creds, /// -1 = error) -internal func credentialsCallback( - cred: UnsafeMutablePointer?>?, - url: UnsafePointer?, - username: UnsafePointer?, - _: UInt32, - payload: UnsafeMutableRawPointer? ) -> Int32 { - - let result: Int32 - - // Find username_from_url - let name = username.map(String.init(cString:)) - - switch Credentials.fromPointer(payload!) { - case .default: - result = git_cred_default_new(cred) - case .sshAgent: - result = git_cred_ssh_key_from_agent(cred, name!) - case .plaintext(let username, let password): - result = git_cred_userpass_plaintext_new(cred, username, password) - case .sshMemory(let username, let publicKey, let privateKey, let passphrase): - result = git_cred_ssh_key_memory_new(cred, username, publicKey, privateKey, passphrase) - } - - return (result != GIT_OK.rawValue) ? -1 : 0 +func credentialsCallback( + cred: UnsafeMutablePointer?>?, + url: UnsafePointer?, + username_from_url: UnsafePointer?, + allowed_types: UInt32, + payload: UnsafeMutableRawPointer? +) -> Int32 { + let result: Int32 + + // Find username_from_url + let name = username_from_url.map(String.init(cString:)) + + switch Credentials.fromPointer(payload!) { + case .default: + result = git_cred_default_new(cred) + case .sshAgent: + result = git_cred_ssh_key_from_agent(cred, name!) + case let .plaintext(username, password): + result = git_cred_userpass_plaintext_new(cred, username, password) + case let .sshMemory(username, publicKey, privateKey, passphrase): + result = git_cred_ssh_key_memory_new(cred, username, publicKey, privateKey, passphrase) + } + + return (result != GIT_OK.rawValue) ? -1 : 0 } diff --git a/Sources/SwiftGit2/Diffs.swift b/Sources/SwiftGit2/Diffs.swift index 0aed06f2..0ecd0c90 100644 --- a/Sources/SwiftGit2/Diffs.swift +++ b/Sources/SwiftGit2/Diffs.swift @@ -9,103 +9,104 @@ import libgit2 public struct StatusEntry { - public var status: Diff.Status - public var headToIndex: Diff.Delta? - public var indexToWorkDir: Diff.Delta? + 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) + init(from statusEntry: git_status_entry) { + status = Diff.Status(rawValue: statusEntry.status.rawValue) - if let htoi = statusEntry.head_to_index { - self.headToIndex = Diff.Delta(htoi.pointee) - } + if let htoi = statusEntry.head_to_index { + headToIndex = Diff.Delta(htoi.pointee) + } - if let itow = statusEntry.index_to_workdir { - self.indexToWorkDir = Diff.Delta(itow.pointee) - } - } + if let itow = statusEntry.index_to_workdir { + indexToWorkDir = Diff.Delta(itow.pointee) + } + } } public struct Diff { - - /// The set of deltas. - public var deltas = [Delta]() - - public struct Delta { - public static let type = GIT_OBJECT_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: UInt64 - 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: GIT_STATUS_CURRENT.rawValue) - public static let indexNew = Status(rawValue: GIT_STATUS_INDEX_NEW.rawValue) - public static let indexModified = Status(rawValue: GIT_STATUS_INDEX_MODIFIED.rawValue) - public static let indexDeleted = Status(rawValue: GIT_STATUS_INDEX_DELETED.rawValue) - public static let indexRenamed = Status(rawValue: GIT_STATUS_INDEX_RENAMED.rawValue) - public static let indexTypeChange = Status(rawValue: GIT_STATUS_INDEX_TYPECHANGE.rawValue) - public static let workTreeNew = Status(rawValue: GIT_STATUS_WT_NEW.rawValue) - public static let workTreeModified = Status(rawValue: GIT_STATUS_WT_MODIFIED.rawValue) - public static let workTreeDeleted = Status(rawValue: GIT_STATUS_WT_DELETED.rawValue) - public static let workTreeTypeChange = Status(rawValue: GIT_STATUS_WT_TYPECHANGE.rawValue) - public static let workTreeRenamed = Status(rawValue: GIT_STATUS_WT_RENAMED.rawValue) - public static let workTreeUnreadable = Status(rawValue: GIT_STATUS_WT_UNREADABLE.rawValue) - public static let ignored = Status(rawValue: GIT_STATUS_IGNORED.rawValue) - public static let conflicted = Status(rawValue: GIT_STATUS_CONFLICTED.rawValue) - } - - 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([]) - 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.. String? { - let last = giterr_last() - if let lastErrorPointer = last { - return String(validatingUTF8: lastErrorPointer.pointee.message) - } else if UInt32(errorCode) == GIT_ERROR_OS.rawValue { - return String(validatingUTF8: strerror(errno)) - } else { - return nil - } + let last = giterr_last() + if let lastErrorPointer = last { + return String(validatingUTF8: lastErrorPointer.pointee.message) + } else if UInt32(errorCode) == GIT_ERROR_OS.rawValue { + return String(validatingUTF8: strerror(errno)) + } else { + return nil + } } diff --git a/Sources/SwiftGit2/Libgit2.swift b/Sources/SwiftGit2/Libgit2.swift deleted file mode 100644 index d544b320..00000000 --- a/Sources/SwiftGit2/Libgit2.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// Libgit2.swift -// SwiftGit2 -// -// Created by Matt Diephouse on 1/11/15. -// Copyright (c) 2015 GitHub, Inc. All rights reserved. -// - -import libgit2 - -extension git_strarray { - func filter(_ isIncluded: (String) -> Bool) -> [String] { - return map { $0 }.filter(isIncluded) - } - - func map(_ transform: (String) -> T) -> [T] { - return (0.. 40 { + return nil + } - /// Create an instance from a hex formatted string. - /// - /// string - A 40-byte hex formatted string. - public init?(string: String) { - // libgit2 doesn't enforce a maximum length - if string.lengthOfBytes(using: String.Encoding.ascii) > 40 { - return nil - } + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + let result = git_oid_fromstr(pointer, string) - let pointer = UnsafeMutablePointer.allocate(capacity: 1) - let result = git_oid_fromstr(pointer, string) + if result < GIT_OK.rawValue { + pointer.deallocate() + return nil + } - if result < GIT_OK.rawValue { - pointer.deallocate() - return nil - } + oid = pointer.pointee + pointer.deallocate() + } - oid = pointer.pointee - pointer.deallocate() - } + /// Create an instance from a libgit2 `git_oid`. + init(_ oid: git_oid) { + self.oid = oid + } - /// Create an instance from a libgit2 `git_oid`. - public init(_ oid: git_oid) { - self.oid = oid - } + // MARK: - Properties - // MARK: - Properties - - public let oid: git_oid + let oid: git_oid } extension OID: CustomStringConvertible { - public var description: String { - let length = Int(GIT_OID_RAWSZ) * 2 - let string = UnsafeMutablePointer.allocate(capacity: length) - var oid = self.oid - git_oid_fmt(string, &oid) + public var description: String { + let length = Int(GIT_OID_RAWSZ) * 2 + let string = UnsafeMutablePointer.allocate(capacity: length) + var oid = self.oid + git_oid_fmt(string, &oid) - return String(bytesNoCopy: string, length: length, encoding: .ascii, freeWhenDone: true)! - } + return String(bytesNoCopy: string, length: length, encoding: .ascii, freeWhenDone: true)! + } } extension OID: Hashable { - public func hash(into hasher: inout Hasher) { - withUnsafeBytes(of: oid.id) { - hasher.combine(bytes: $0) - } - } + public func hash(into hasher: inout Hasher) { + withUnsafeBytes(of: oid.id) { + hasher.combine(bytes: $0) + } + } - public static func == (lhs: OID, rhs: OID) -> Bool { - var left = lhs.oid - var right = rhs.oid - return git_oid_cmp(&left, &right) == 0 - } + public static func == (lhs: OID, rhs: OID) -> Bool { + var left = lhs.oid + var right = rhs.oid + return git_oid_cmp(&left, &right) == 0 + } } diff --git a/Sources/SwiftGit2/Objects.swift b/Sources/SwiftGit2/Objects.swift index cf2c4cc5..b151f1c0 100644 --- a/Sources/SwiftGit2/Objects.swift +++ b/Sources/SwiftGit2/Objects.swift @@ -9,216 +9,277 @@ import Foundation import libgit2 +public enum GitObjectType { + /// Object can be any of the following + case any + /// Object is invalid. + case invalid + /// A commit object. + case commit + /// A tree (directory listing) object. + case tree + /// A file revision object. + case blob + /// An annotated tag object. + case tag + /// A delta, base is given by an offset. + case deltaGivenByOffset + /// A delta, base is given by object id. + case deltaGivenByObjectID + + init?(_ type: git_object_t) { + switch type { + case GIT_OBJECT_ANY: + self = .any + case GIT_OBJECT_INVALID: + self = .invalid + case GIT_OBJECT_COMMIT: + self = .commit + case GIT_OBJECT_TREE: + self = .tree + case GIT_OBJECT_BLOB: + self = .blob + case GIT_OBJECT_TAG: + self = .tag + case GIT_OBJECT_OFS_DELTA: + self = .deltaGivenByOffset + case GIT_OBJECT_REF_DELTA: + self = .deltaGivenByObjectID + default: + return nil + } + } + + var git_object_type: git_object_t { + switch self { + case .any: GIT_OBJECT_ANY + case .invalid: GIT_OBJECT_INVALID + case .commit: GIT_OBJECT_COMMIT + case .tree: GIT_OBJECT_TREE + case .blob: GIT_OBJECT_BLOB + case .tag: GIT_OBJECT_TAG + case .deltaGivenByOffset: GIT_OBJECT_OFS_DELTA + case .deltaGivenByObjectID: GIT_OBJECT_REF_DELTA + } + } +} + /// A git object. public protocol ObjectType { - static var type: git_object_t { get } + static var type: GitObjectType { get } - /// The OID of the object. - var oid: OID { get } + /// The OID of the object. + var oid: OID { get } - /// Create an instance with the underlying libgit2 type. - init(_ pointer: OpaquePointer) + init(_ pointer: OpaquePointer) } public extension ObjectType { - static func == (lhs: Self, rhs: Self) -> Bool { - return lhs.oid == rhs.oid - } + static func == (lhs: Self, rhs: Self) -> Bool { + return lhs.oid == rhs.oid + } - func hash(into hasher: inout Hasher) { - hasher.combine(oid) - } + func hash(into hasher: inout Hasher) { + hasher.combine(oid) + } } public struct Signature { - /// The name of the person. - public let name: String - - /// The email of the person. - public let email: String - - /// The time when the action happened. - public let time: Date - - /// 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, NSError> { - var signature: UnsafeMutablePointer? = nil - let time = git_time_t(self.time.timeIntervalSince1970) // Unix epoch time - let offset = 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) - } + /// The name of the person. + public let name: String + + /// The email of the person. + public let email: String + + /// The time when the action happened. + public let time: Date + + /// 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`. + 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, NSError> { + var signature: UnsafeMutablePointer? = nil + let time = git_time_t(self.time.timeIntervalSince1970) // Unix epoch time + let offset = 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 { - public func hash(into hasher: inout Hasher) { - hasher.combine(name) - hasher.combine(email) - hasher.combine(time) - } + public func hash(into hasher: inout Hasher) { + hasher.combine(name) + hasher.combine(email) + hasher.combine(time) + } } /// A git commit. public struct Commit: ObjectType, Hashable { - public static let type = GIT_OBJECT_COMMIT + public static let type: GitObjectType = .commit - /// The OID of the commit. - public let oid: OID + /// The OID of the commit. + public let oid: OID - /// The OID of the commit's tree. - public let tree: PointerTo + /// The OID of the commit's tree. + public let tree: PointerTo - /// The OIDs of the commit's parents. - public let parents: [PointerTo] + /// The OIDs of the commit's parents. + public let parents: [PointerTo] - /// The author of the commit. - public let author: Signature + /// The author of the commit. + public let author: Signature - /// The committer of the commit. - public let committer: Signature + /// The committer of the commit. + public let committer: Signature - /// The full message of the commit. - public let message: String + /// The full message of the commit. + public let message: String - /// Create an instance with a libgit2 `git_commit` object. - public init(_ pointer: OpaquePointer) { - oid = OID(git_object_id(pointer).pointee) - message = String(validatingUTF8: git_commit_message(pointer))! - author = Signature(git_commit_author(pointer).pointee) - committer = Signature(git_commit_committer(pointer).pointee) - tree = PointerTo(OID(git_commit_tree_id(pointer).pointee)) + /// The timestamp of the commit + public let timestamp: Date + + /// Create an instance with a libgit2 `git_commit` object. + public init(_ pointer: OpaquePointer) { + oid = OID(git_object_id(pointer).pointee) + message = String(validatingUTF8: git_commit_message(pointer))! + author = Signature(git_commit_author(pointer).pointee) + committer = Signature(git_commit_committer(pointer).pointee) + tree = PointerTo(OID(git_commit_tree_id(pointer).pointee)) + timestamp = Date(timeIntervalSince1970: TimeInterval(git_commit_time(pointer))) + parents = (0 ..< git_commit_parentcount(pointer)).map { + PointerTo(OID(git_commit_parent_id(pointer, $0).pointee)) + } + } +} - self.parents = (0.. Bool { - return lhs.oid == rhs.oid - && lhs.type == rhs.type - } + static func == (lhs: Self, rhs: Self) -> Bool { + return lhs.oid == rhs.oid + && lhs.type == rhs.type + } - func hash(into hasher: inout Hasher) { - hasher.combine(oid) - } + func hash(into hasher: inout Hasher) { + hasher.combine(oid) + } } /// A pointer to a git object. public enum Pointer: PointerType { - case commit(OID) - case tree(OID) - case blob(OID) - case tag(OID) + case commit(OID) + case tree(OID) + case blob(OID) + case tag(OID) - public var oid: OID { - switch self { - case let .commit(oid): - return oid - case let .tree(oid): - return oid - case let .blob(oid): - return oid - case let .tag(oid): - return oid - } - } + public var oid: OID { + switch self { + case let .commit(oid): + return oid + case let .tree(oid): + return oid + case let .blob(oid): + return oid + case let .tag(oid): + return oid + } + } - public var type: git_object_t { - switch self { - case .commit: - return GIT_OBJECT_COMMIT - case .tree: - return GIT_OBJECT_TREE - case .blob: - return GIT_OBJECT_BLOB - case .tag: - return GIT_OBJECT_TAG - } - } + public var type: GitObjectType { + switch self { + case .commit: + return .commit + case .tree: + return .tree + case .blob: + return .blob + case .tag: + return .tag + } + } - /// Create an instance with an OID and a libgit2 `git_object_t`. - init?(oid: OID, type: git_object_t) { - switch type { - case GIT_OBJECT_COMMIT: - self = .commit(oid) - case GIT_OBJECT_TREE: - self = .tree(oid) - case GIT_OBJECT_BLOB: - self = .blob(oid) - case GIT_OBJECT_TAG: - self = .tag(oid) - default: - return nil - } - } + /// Create an instance with an OID and a libgit2 `git_object_t`. + init?(oid: OID, type: git_object_t) { + switch type { + case GIT_OBJECT_COMMIT: + self = .commit(oid) + case GIT_OBJECT_TREE: + self = .tree(oid) + case GIT_OBJECT_BLOB: + self = .blob(oid) + case GIT_OBJECT_TAG: + self = .tag(oid) + default: + return nil + } + } } extension Pointer: CustomStringConvertible { - public var description: String { - switch self { - case .commit: - return "commit(\(oid))" - case .tree: - return "tree(\(oid))" - case .blob: - return "blob(\(oid))" - case .tag: - return "tag(\(oid))" - } - } + public var description: String { + switch self { + case .commit: + return "commit(\(oid))" + case .tree: + return "tree(\(oid))" + case .blob: + return "blob(\(oid))" + case .tag: + return "tag(\(oid))" + } + } } public struct PointerTo: PointerType { - public let oid: OID + public let oid: OID - public var type: git_object_t { - return T.type - } + public var type: GitObjectType { + return T.type + } - public init(_ oid: OID) { - self.oid = oid - } + init(_ oid: OID) { + self.oid = oid + } } diff --git a/Sources/SwiftGit2/References.swift b/Sources/SwiftGit2/References.swift index 42ca908d..0f4a17ad 100644 --- a/Sources/SwiftGit2/References.swift +++ b/Sources/SwiftGit2/References.swift @@ -10,180 +10,184 @@ import libgit2 /// A reference to a git object. public protocol ReferenceType { - /// The full name of the reference (e.g., `refs/heads/master`). - var longName: String { get } + /// The full name of the reference (e.g., `refs/heads/master`). + var longName: String { get } - /// The short human-readable name of the reference if one exists (e.g., `master`). - var shortName: String? { get } + /// The short human-readable name of the reference if one exists (e.g., `master`). + var shortName: String? { get } - /// The OID of the referenced object. - var oid: OID { get } + /// The OID of the referenced object. + var oid: OID { get } } public extension ReferenceType { - static func == (lhs: Self, rhs: Self) -> Bool { - return lhs.longName == rhs.longName - && lhs.oid == rhs.oid - } - - func hash(into hasher: inout Hasher) { - hasher.combine(longName) - hasher.combine(oid) - } + static func == (lhs: Self, rhs: Self) -> Bool { + return lhs.longName == rhs.longName + && lhs.oid == rhs.oid + } + + func hash(into hasher: inout Hasher) { + hasher.combine(longName) + hasher.combine(oid) + } } /// Create a Reference, Branch, or TagReference from a libgit2 `git_reference`. -internal func referenceWithLibGit2Reference(_ pointer: OpaquePointer) -> ReferenceType { - if git_reference_is_branch(pointer) != 0 || git_reference_is_remote(pointer) != 0 { - return Branch(pointer)! - } else if git_reference_is_tag(pointer) != 0 { - return TagReference(pointer)! - } else { - return Reference(pointer) - } +func referenceWithLibGit2Reference(_ pointer: OpaquePointer) -> ReferenceType { + if git_reference_is_branch(pointer) != 0 || git_reference_is_remote(pointer) != 0 { + return Branch(pointer)! + } else if git_reference_is_tag(pointer) != 0 { + return TagReference(pointer)! + } else { + return Reference(pointer) + } } /// A generic reference to a git object. public struct Reference: ReferenceType, Hashable { - /// The full name of the reference (e.g., `refs/heads/master`). - public let longName: String - - /// The short human-readable name of the reference if one exists (e.g., `master`). - public let shortName: String? - - /// The OID of the referenced object. - public let oid: OID - - /// Create an instance with a libgit2 `git_reference` object. - public init(_ pointer: OpaquePointer) { - let shorthand = String(validatingUTF8: git_reference_shorthand(pointer))! - longName = String(validatingUTF8: git_reference_name(pointer))! - shortName = (shorthand == longName ? nil : shorthand) - oid = OID(git_reference_target(pointer).pointee) - } + /// The full name of the reference (e.g., `refs/heads/master`). + public let longName: String + + /// The short human-readable name of the reference if one exists (e.g., `master`). + public let shortName: String? + + /// The OID of the referenced object. + public let oid: OID + + /// Create an instance with a libgit2 `git_reference` object. + init(_ pointer: OpaquePointer) { + let shorthand = String(validatingUTF8: git_reference_shorthand(pointer))! + longName = String(validatingUTF8: git_reference_name(pointer))! + shortName = (shorthand == longName ? nil : shorthand) + oid = OID(git_reference_target(pointer).pointee) + } } /// A git branch. public struct Branch: ReferenceType, Hashable { - /// The full name of the reference (e.g., `refs/heads/master`). - public let longName: String - - /// The short human-readable name of the branch (e.g., `master`). - public let name: String - - /// A pointer to the referenced commit. - public let commit: PointerTo - - // MARK: Derived Properties - - /// The short human-readable name of the branch (e.g., `master`). - /// - /// This is the same as `name`, but is declared with an Optional type to adhere to - /// `ReferenceType`. - public var shortName: String? { return name } - - /// The OID of the referenced object. - /// - /// This is the same as `commit.oid`, but is declared here to adhere to `ReferenceType`. - public var oid: OID { return commit.oid } - - /// Whether the branch is a local branch. - public var isLocal: Bool { return longName.hasPrefix("refs/heads/") } - - /// Whether the branch is a remote branch. - public var isRemote: Bool { return longName.hasPrefix("refs/remotes/") } - - /// Create an instance with a libgit2 `git_reference` object. - /// - /// Returns `nil` if the pointer isn't a branch. - public init?(_ pointer: OpaquePointer) { - var namePointer: UnsafePointer? = nil - let success = git_branch_name(&namePointer, pointer) - guard success == GIT_OK.rawValue else { - return nil - } - name = String(validatingUTF8: namePointer!)! - - longName = String(validatingUTF8: git_reference_name(pointer))! - - var oid: OID - if git_reference_type(pointer).rawValue == GIT_REFERENCE_SYMBOLIC.rawValue { - var resolved: OpaquePointer? = nil - let success = git_reference_resolve(&resolved, pointer) - guard success == GIT_OK.rawValue else { - return nil - } - oid = OID(git_reference_target(resolved).pointee) - git_reference_free(resolved) - } else { - oid = OID(git_reference_target(pointer).pointee) - } - commit = PointerTo(oid) - } + /// The full name of the reference (e.g., `refs/heads/master`). + public let longName: String + + /// The short human-readable name of the branch (e.g., `master`). + public let name: String + + /// A pointer to the referenced commit. + public let commit: PointerTo + + // MARK: Derived Properties + + /// The short human-readable name of the branch (e.g., `master`). + /// + /// This is the same as `name`, but is declared with an Optional type to adhere to + /// `ReferenceType`. + public var shortName: String? { return name } + + /// The OID of the referenced object. + /// + /// This is the same as `commit.oid`, but is declared here to adhere to `ReferenceType`. + public var oid: OID { return commit.oid } + + /// Whether the branch is a local branch. + public var isLocal: Bool { return longName.hasPrefix("refs/heads/") } + + /// Whether the branch is a remote branch. + public var isRemote: Bool { return longName.hasPrefix("refs/remotes/") } + + /// Create an instance with a libgit2 `git_reference` object. + /// + /// Returns `nil` if the pointer isn't a branch. + init?(_ pointer: OpaquePointer) { + var namePointer: UnsafePointer? = nil + let success = git_branch_name(&namePointer, pointer) + guard success == GIT_OK.rawValue else { + return nil + } + name = String(validatingUTF8: namePointer!)! + + longName = String(validatingUTF8: git_reference_name(pointer))! + + var oid: OID + if git_reference_type(pointer).rawValue == GIT_REFERENCE_SYMBOLIC.rawValue { + var resolved: OpaquePointer? = nil + let success = git_reference_resolve(&resolved, pointer) + guard success == GIT_OK.rawValue else { + return nil + } + oid = OID(git_reference_target(resolved).pointee) + git_reference_free(resolved) + } else { + oid = OID(git_reference_target(pointer).pointee) + } + commit = PointerTo(oid) + } +} + +extension Branch: Identifiable { + public var id: OID { oid } } /// A git tag reference, which can be either a lightweight tag or a Tag object. public enum TagReference: ReferenceType, Hashable { - /// A lightweight tag, which is just a name and an OID. - case lightweight(String, OID) - - /// An annotated tag, which points to a Tag object. - case annotated(String, Tag) - - /// The full name of the reference (e.g., `refs/tags/my-tag`). - public var longName: String { - switch self { - case let .lightweight(name, _): - return name - case let .annotated(name, _): - return name - } - } - - /// The short human-readable name of the branch (e.g., `master`). - public var name: String { - return String(longName["refs/tags/".endIndex...]) - } - - /// The OID of the target object. - /// - /// If this is an annotated tag, the OID will be the tag's target. - public var oid: OID { - switch self { - case let .lightweight(_, oid): - return oid - case let .annotated(_, tag): - return tag.target.oid - } - } - - // MARK: Derived Properties - - /// The short human-readable name of the branch (e.g., `master`). - /// - /// This is the same as `name`, but is declared with an Optional type to adhere to - /// `ReferenceType`. - public var shortName: String? { return name } - - /// Create an instance with a libgit2 `git_reference` object. - /// - /// Returns `nil` if the pointer isn't a branch. - public init?(_ pointer: OpaquePointer) { - if git_reference_is_tag(pointer) == 0 { - return nil - } - - let name = String(validatingUTF8: git_reference_name(pointer))! - let repo = git_reference_owner(pointer) - var oid = git_reference_target(pointer).pointee - - var pointer: OpaquePointer? = nil - let result = git_object_lookup(&pointer, repo, &oid, GIT_OBJECT_TAG) - if result == GIT_OK.rawValue { - self = .annotated(name, Tag(pointer!)) - } else { - self = .lightweight(name, OID(oid)) - } - git_object_free(pointer) - } + /// A lightweight tag, which is just a name and an OID. + case lightweight(String, OID) + + /// An annotated tag, which points to a Tag object. + case annotated(String, Tag) + + /// The full name of the reference (e.g., `refs/tags/my-tag`). + public var longName: String { + switch self { + case let .lightweight(name, _): + return name + case let .annotated(name, _): + return name + } + } + + /// The short human-readable name of the branch (e.g., `master`). + public var name: String { + return String(longName["refs/tags/".endIndex...]) + } + + /// The OID of the target object. + /// + /// If this is an annotated tag, the OID will be the tag's target. + public var oid: OID { + switch self { + case let .lightweight(_, oid): + return oid + case let .annotated(_, tag): + return tag.target.oid + } + } + + // MARK: Derived Properties + + /// The short human-readable name of the branch (e.g., `master`). + /// + /// This is the same as `name`, but is declared with an Optional type to adhere to + /// `ReferenceType`. + public var shortName: String? { return name } + + /// Create an instance with a libgit2 `git_reference` object. + /// + /// Returns `nil` if the pointer isn't a branch. + public init?(_ pointer: OpaquePointer) { + if git_reference_is_tag(pointer) == 0 { + return nil + } + + let name = String(validatingUTF8: git_reference_name(pointer))! + let repo = git_reference_owner(pointer) + var oid = git_reference_target(pointer).pointee + + var pointer: OpaquePointer? = nil + let result = git_object_lookup(&pointer, repo, &oid, GIT_OBJECT_TAG) + if result == GIT_OK.rawValue { + self = .annotated(name, Tag(pointer!)) + } else { + self = .lightweight(name, OID(oid)) + } + git_object_free(pointer) + } } diff --git a/Sources/SwiftGit2/Remotes.swift b/Sources/SwiftGit2/Remotes.swift index 78ea2612..5bc42384 100644 --- a/Sources/SwiftGit2/Remotes.swift +++ b/Sources/SwiftGit2/Remotes.swift @@ -10,17 +10,17 @@ import libgit2 /// A remote in a git repository. public struct Remote: Hashable { - /// The name of the remote. - public let name: String + /// The name of the remote. + public let name: String - /// The URL of the remote. - /// - /// This may be an SSH URL, which isn't representable using `NSURL`. - public let URL: String + /// The URL of the remote. + /// + /// This may be an SSH URL, which isn't representable using `NSURL`. + public let URL: String - /// Create an instance with a libgit2 `git_remote`. - public init(_ pointer: OpaquePointer) { - name = String(validatingUTF8: git_remote_name(pointer))! - URL = String(validatingUTF8: git_remote_url(pointer))! - } + /// Create an instance with a libgit2 `git_remote`. + init(_ pointer: OpaquePointer) { + name = String(validatingUTF8: git_remote_name(pointer))! + URL = String(validatingUTF8: git_remote_url(pointer))! + } } diff --git a/Sources/SwiftGit2/Repository.swift b/Sources/SwiftGit2/Repository.swift index 2d5bf7ef..c404747c 100644 --- a/Sources/SwiftGit2/Repository.swift +++ b/Sources/SwiftGit2/Repository.swift @@ -9,23 +9,25 @@ import Foundation import libgit2 +/// Parameters: `path`, `completed_steps`, `total_steps` public typealias CheckoutProgressBlock = (String?, Int, Int) -> Void /// Helper function used as the libgit2 progress callback in git_checkout_options. /// This is a function with a type signature of git_checkout_progress_cb. private func checkoutProgressCallback(path: UnsafePointer?, completedSteps: Int, totalSteps: Int, - payload: UnsafeMutableRawPointer?) { - if let payload = payload { - let buffer = payload.assumingMemoryBound(to: CheckoutProgressBlock.self) - let block: CheckoutProgressBlock - if completedSteps < totalSteps { - block = buffer.pointee - } else { - block = buffer.move() - buffer.deallocate() - } - block(path.flatMap(String.init(validatingUTF8:)), completedSteps, totalSteps) - } + payload: UnsafeMutableRawPointer?) +{ + if let payload = payload { + let buffer = payload.assumingMemoryBound(to: CheckoutProgressBlock.self) + let block: CheckoutProgressBlock + if completedSteps < totalSteps { + block = buffer.pointee + } else { + block = buffer.move() + buffer.deallocate() + } + block(path.flatMap(String.init(validatingUTF8:)), completedSteps, totalSteps) + } } /// Helper function for initializing libgit2 git_checkout_options. @@ -34,937 +36,944 @@ private func checkoutProgressCallback(path: UnsafePointer?, completedSteps /// :param: progress A block that's called with the progress of the checkout. /// :returns: Returns a git_checkout_options struct with the progress members set. private func checkoutOptions(strategy: CheckoutStrategy, - progress: CheckoutProgressBlock? = nil) -> git_checkout_options { - // Do this because GIT_CHECKOUT_OPTIONS_INIT is unavailable in swift - let pointer = UnsafeMutablePointer.allocate(capacity: 1) - git_checkout_init_options(pointer, UInt32(GIT_CHECKOUT_OPTIONS_VERSION)) - var options = pointer.move() - pointer.deallocate() - - options.checkout_strategy = strategy.gitCheckoutStrategy.rawValue - - if progress != nil { - options.progress_cb = checkoutProgressCallback - let blockPointer = UnsafeMutablePointer.allocate(capacity: 1) - blockPointer.initialize(to: progress!) - options.progress_payload = UnsafeMutableRawPointer(blockPointer) - } - - return options + progress: CheckoutProgressBlock? = nil) -> git_checkout_options +{ + // Do this because `git_checkout_options_init` is unavailable in swift + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + git_checkout_options_init(pointer, UInt32(GIT_CHECKOUT_OPTIONS_VERSION)) + var options = pointer.move() + pointer.deallocate() + + options.checkout_strategy = strategy.gitCheckoutStrategy.rawValue + + if progress != nil { + options.progress_cb = checkoutProgressCallback + let blockPointer = UnsafeMutablePointer.allocate(capacity: 1) + blockPointer.initialize(to: progress!) + options.progress_payload = UnsafeMutableRawPointer(blockPointer) + } + + return options } private func fetchOptions(credentials: Credentials) -> git_fetch_options { - let pointer = UnsafeMutablePointer.allocate(capacity: 1) - git_fetch_init_options(pointer, UInt32(GIT_FETCH_OPTIONS_VERSION)) + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + git_fetch_init_options(pointer, UInt32(GIT_FETCH_OPTIONS_VERSION)) - var options = pointer.move() + var options = pointer.move() - pointer.deallocate() + pointer.deallocate() - options.callbacks.payload = credentials.toPointer() - options.callbacks.credentials = credentialsCallback + options.callbacks.payload = credentials.toPointer() + options.callbacks.credentials = credentialsCallback - return options + return options } private func cloneOptions(bare: Bool = false, localClone: Bool = false, fetchOptions: git_fetch_options? = nil, - checkoutOptions: git_checkout_options? = nil) -> git_clone_options { - let pointer = UnsafeMutablePointer.allocate(capacity: 1) - git_clone_init_options(pointer, UInt32(GIT_CLONE_OPTIONS_VERSION)) + checkoutOptions: git_checkout_options? = nil) -> git_clone_options +{ + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + git_clone_init_options(pointer, UInt32(GIT_CLONE_OPTIONS_VERSION)) - var options = pointer.move() + var options = pointer.move() - pointer.deallocate() + pointer.deallocate() - options.bare = bare ? 1 : 0 + options.bare = bare ? 1 : 0 - if localClone { - options.local = GIT_CLONE_NO_LOCAL - } + if localClone { + options.local = GIT_CLONE_NO_LOCAL + } - if let checkoutOptions = checkoutOptions { - options.checkout_opts = checkoutOptions - } + if let checkoutOptions = checkoutOptions { + options.checkout_opts = checkoutOptions + } - if let fetchOptions = fetchOptions { - options.fetch_opts = fetchOptions - } + if let fetchOptions = fetchOptions { + options.fetch_opts = fetchOptions + } - return options + return options } /// A git repository. public final class Repository { - - // MARK: - Creating Repositories - - /// Create a new repository at the given URL. - /// - /// URL - The URL of the repository. - /// - /// Returns a `Result` with a `Repository` or an error. - public class func create(at url: URL) -> Result { - var pointer: OpaquePointer? = nil - let result = url.withUnsafeFileSystemRepresentation { - git_repository_init(&pointer, $0, 0) - } - - guard result == GIT_OK.rawValue else { - return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_init")) - } - - let repository = Repository(pointer!) - return Result.success(repository) - } - - /// Load the repository at the given URL. - /// - /// URL - The URL of the repository. - /// - /// Returns a `Result` with a `Repository` or an error. - public class func at(_ url: URL) -> Result { - var pointer: OpaquePointer? = nil - let result = url.withUnsafeFileSystemRepresentation { - git_repository_open(&pointer, $0) - } - - guard result == GIT_OK.rawValue else { - return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_open")) - } - - let repository = Repository(pointer!) - return Result.success(repository) - } - - /// Clone the repository from a given URL. - /// - /// remoteURL - The URL of the remote repository - /// localURL - The URL to clone the remote repository into - /// localClone - Will not bypass the git-aware transport, even if remote is local. - /// bare - Clone remote as a bare repository. - /// credentials - Credentials to be used when connecting to the remote. - /// checkoutStrategy - The checkout strategy to use, if being checked out. - /// checkoutProgress - A block that's called with the progress of the checkout. - /// - /// Returns a `Result` with a `Repository` or an error. - public class func clone(from remoteURL: URL, to localURL: URL, localClone: Bool = false, bare: Bool = false, - credentials: Credentials = .default, checkoutStrategy: CheckoutStrategy = .Safe, - checkoutProgress: CheckoutProgressBlock? = nil) -> Result { - var options = cloneOptions( - bare: bare, - localClone: localClone, - fetchOptions: fetchOptions(credentials: credentials), - checkoutOptions: checkoutOptions(strategy: checkoutStrategy, progress: checkoutProgress)) - - var pointer: OpaquePointer? = nil - let remoteURLString = (remoteURL as NSURL).isFileReferenceURL() ? remoteURL.path : remoteURL.absoluteString - let result = localURL.withUnsafeFileSystemRepresentation { localPath in - git_clone(&pointer, remoteURLString, localPath, &options) - } - - guard result == GIT_OK.rawValue else { - return Result.failure(NSError(gitError: result, pointOfFailure: "git_clone")) - } - - let repository = Repository(pointer!) - return Result.success(repository) - } - - // MARK: - Initializers - - /// Create an instance with a libgit2 `git_repository` object. - /// - /// The Repository assumes ownership of the `git_repository` object. - public init(_ pointer: OpaquePointer) { - self.pointer = pointer - - let path = git_repository_workdir(pointer) - self.directoryURL = path.map({ URL(fileURLWithPath: String(validatingUTF8: $0)!, isDirectory: true) }) - } - - deinit { - git_repository_free(pointer) - } - - // MARK: - Properties - - /// The underlying libgit2 `git_repository` object. - public let pointer: OpaquePointer - - /// The URL of the repository's working directory, or `nil` if the - /// repository is bare. - public let directoryURL: URL? - - // MARK: - Object Lookups - - /// Load a libgit2 object and transform it to something else. - /// - /// oid - The OID of the object to look up. - /// type - The type of the object to look up. - /// transform - A function that takes the libgit2 object and transforms it - /// into something else. - /// - /// Returns the result of calling `transform` or an error if the object - /// cannot be loaded. - private func withGitObject(_ oid: OID, type: git_object_t, - transform: (OpaquePointer) -> Result) -> Result { - 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")) - } - - let value = transform(pointer!) - git_object_free(pointer) - return value - } - - private func withGitObject(_ oid: OID, type: git_object_t, transform: (OpaquePointer) -> T) -> Result { - return withGitObject(oid, type: type) { Result.success(transform($0)) } - } - - private func withGitObjects(_ oids: [OID], type: git_object_t, transform: ([OpaquePointer]) -> Result) -> Result { - 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. - /// - /// Returns a `Blob`, `Commit`, `Tag`, or `Tree` if one exists, or an error. - public func object(_ oid: OID) -> Result { - return withGitObject(oid, type: GIT_OBJECT_ANY) { object in - let type = git_object_type(object) - if type == Blob.type { - return Result.success(Blob(object)) - } else if type == Commit.type { - return Result.success(Commit(object)) - } else if type == Tag.type { - return Result.success(Tag(object)) - } else if type == Tree.type { - return Result.success(Tree(object)) - } - - let error = NSError( - domain: "org.libgit2.SwiftGit2", - code: 1, - userInfo: [ - NSLocalizedDescriptionKey: "Unrecognized git_object_t '\(type)' for oid '\(oid)'.", - ] - ) - return Result.failure(error) - } - } - - /// Loads the blob with the given OID. - /// - /// oid - The OID of the blob to look up. - /// - /// Returns the blob if it exists, or an error. - public func blob(_ oid: OID) -> Result { - return withGitObject(oid, type: GIT_OBJECT_BLOB) { Blob($0) } - } - - /// Loads the commit with the given OID. - /// - /// oid - The OID of the commit to look up. - /// - /// Returns the commit if it exists, or an error. - public func commit(_ oid: OID) -> Result { - return withGitObject(oid, type: GIT_OBJECT_COMMIT) { Commit($0) } - } - - /// Loads the tag with the given OID. - /// - /// oid - The OID of the tag to look up. - /// - /// Returns the tag if it exists, or an error. - public func tag(_ oid: OID) -> Result { - return withGitObject(oid, type: GIT_OBJECT_TAG) { Tag($0) } - } - - /// Loads the tree with the given OID. - /// - /// oid - The OID of the tree to look up. - /// - /// Returns the tree if it exists, or an error. - public func tree(_ oid: OID) -> Result { - return withGitObject(oid, type: GIT_OBJECT_TREE) { Tree($0) } - } - - /// Loads the referenced object from the pointer. - /// - /// pointer - A pointer to an object. - /// - /// Returns the object if it exists, or an error. - public func object(from pointer: PointerTo) -> Result { - return withGitObject(pointer.oid, type: pointer.type) { T($0) } - } - - /// Loads the referenced object from the pointer. - /// - /// pointer - A pointer to an object. - /// - /// Returns the object if it exists, or an error. - public func object(from pointer: Pointer) -> Result { - switch pointer { - case let .blob(oid): - return blob(oid).map { $0 as ObjectType } - case let .commit(oid): - return commit(oid).map { $0 as ObjectType } - case let .tag(oid): - return tag(oid).map { $0 as ObjectType } - case let .tree(oid): - return tree(oid).map { $0 as ObjectType } - } - } - - // MARK: - Remote Lookups - - /// Loads all the remotes in the repository. - /// - /// Returns an array of remotes, or an error. - public func allRemotes() -> Result<[Remote], NSError> { - let pointer = UnsafeMutablePointer.allocate(capacity: 1) - let result = git_remote_list(pointer, self.pointer) - - guard result == GIT_OK.rawValue else { - pointer.deallocate() - return Result.failure(NSError(gitError: result, pointOfFailure: "git_remote_list")) - } - - let strarray = pointer.pointee - let remotes: [Result] = strarray.map { - return self.remote(named: $0) - } - git_strarray_free(pointer) - pointer.deallocate() - - return remotes.aggregateResult() - } - - private func remoteLookup(named name: String, _ callback: (Result) -> 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"))) - } - - return callback(.success(pointer!)) - } - - /// Load a remote from the repository. - /// - /// name - The name of the remote. - /// - /// Returns the remote if it exists, or an error. - public func remote(named name: String) -> Result { - return remoteLookup(named: name) { $0.map(Remote.init) } - } - - /// Download new data and update tips - public func fetch(_ remote: Remote) -> Result<(), NSError> { - return remoteLookup(named: remote.name) { remote in - remote.flatMap { pointer in - var opts = git_fetch_options() - let resultInit = git_fetch_init_options(&opts, UInt32(GIT_FETCH_OPTIONS_VERSION)) - assert(resultInit == GIT_OK.rawValue) - - let result = git_remote_fetch(pointer, nil, &opts, nil) - guard result == GIT_OK.rawValue else { - let err = NSError(gitError: result, pointOfFailure: "git_remote_fetch") - return .failure(err) - } - return .success(()) - } - } - } - - // MARK: - Reference Lookups - - /// Load all the references with the given prefix (e.g. "refs/heads/") - public func references(withPrefix prefix: String) -> Result<[ReferenceType], NSError> { - let pointer = UnsafeMutablePointer.allocate(capacity: 1) - let result = git_reference_list(pointer, self.pointer) - - guard result == GIT_OK.rawValue else { - pointer.deallocate() - return Result.failure(NSError(gitError: result, pointOfFailure: "git_reference_list")) - } - - let strarray = pointer.pointee - let references = strarray - .filter { - $0.hasPrefix(prefix) - } - .map { - self.reference(named: $0) - } - git_strarray_free(pointer) - pointer.deallocate() - - return references.aggregateResult() - } - - /// Load the reference with the given long name (e.g. "refs/heads/master") - /// - /// If the reference is a branch, a `Branch` will be returned. If the - /// reference is a tag, a `TagReference` will be returned. Otherwise, a - /// `Reference` will be returned. - public func reference(named name: String) -> Result { - var pointer: OpaquePointer? = nil - let result = git_reference_lookup(&pointer, self.pointer, name) - - guard result == GIT_OK.rawValue else { - return Result.failure(NSError(gitError: result, pointOfFailure: "git_reference_lookup")) - } - - let value = referenceWithLibGit2Reference(pointer!) - git_reference_free(pointer) - return Result.success(value) - } - - /// Load and return a list of all local branches. - public func localBranches() -> Result<[Branch], NSError> { - return references(withPrefix: "refs/heads/") - .map { (refs: [ReferenceType]) in - return refs.map { $0 as! Branch } - } - } - - /// Load and return a list of all remote branches. - public func remoteBranches() -> Result<[Branch], NSError> { - return references(withPrefix: "refs/remotes/") - .map { (refs: [ReferenceType]) in - return refs.map { $0 as! Branch } - } - } - - /// Load the local branch with the given name (e.g., "master"). - public func localBranch(named name: String) -> Result { - return reference(named: "refs/heads/" + name).map { $0 as! Branch } - } - - /// Load the remote branch with the given name (e.g., "origin/master"). - public func remoteBranch(named name: String) -> Result { - return reference(named: "refs/remotes/" + name).map { $0 as! Branch } - } - - /// Load and return a list of all the `TagReference`s. - public func allTags() -> Result<[TagReference], NSError> { - return references(withPrefix: "refs/tags/") - .map { (refs: [ReferenceType]) in - return refs.map { $0 as! TagReference } - } - } - - /// Load the tag with the given name (e.g., "tag-2"). - public func tag(named name: String) -> Result { - return reference(named: "refs/tags/" + name).map { $0 as! TagReference } - } - - // MARK: - Working Directory - - /// Load the reference pointed at by HEAD. - /// - /// When on a branch, this will return the current `Branch`. - public func HEAD() -> Result { - var pointer: OpaquePointer? = nil - let result = git_repository_head(&pointer, self.pointer) - guard result == GIT_OK.rawValue else { - return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_head")) - } - let value = referenceWithLibGit2Reference(pointer!) - git_reference_free(pointer) - return Result.success(value) - } - - /// Set HEAD to the given oid (detached). - /// - /// :param: oid The OID to set as HEAD. - /// :returns: Returns a result with void or the error that occurred. - public func setHEAD(_ oid: OID) -> Result<(), NSError> { - var oid = oid.oid - let result = git_repository_set_head_detached(self.pointer, &oid) - guard result == GIT_OK.rawValue else { - return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_set_head")) - } - return Result.success(()) - } - - /// Set HEAD to the given reference. - /// - /// :param: reference The reference to set as HEAD. - /// :returns: Returns a result with void or the error that occurred. - public func setHEAD(_ reference: ReferenceType) -> Result<(), NSError> { - let result = git_repository_set_head(self.pointer, reference.longName) - guard result == GIT_OK.rawValue else { - return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_set_head")) - } - return Result.success(()) - } - - /// Check out HEAD. - /// - /// :param: strategy The checkout strategy to use. - /// :param: progress A block that's called with the progress of the checkout. - /// :returns: Returns a result with void or the error that occurred. - public func checkout(strategy: CheckoutStrategy, progress: CheckoutProgressBlock? = nil) -> Result<(), NSError> { - var options = checkoutOptions(strategy: strategy, progress: progress) - - let result = git_checkout_head(self.pointer, &options) - guard result == GIT_OK.rawValue else { - return Result.failure(NSError(gitError: result, pointOfFailure: "git_checkout_head")) - } - - return Result.success(()) - } - - /// Check out the given OID. - /// - /// :param: oid The OID of the commit to check out. - /// :param: strategy The checkout strategy to use. - /// :param: progress A block that's called with the progress of the checkout. - /// :returns: Returns a result with void or the error that occurred. - public func checkout(_ oid: OID, strategy: CheckoutStrategy, - progress: CheckoutProgressBlock? = nil) -> Result<(), NSError> { - return setHEAD(oid).flatMap { self.checkout(strategy: strategy, progress: progress) } - } - - /// Check out the given reference. - /// - /// :param: reference The reference to check out. - /// :param: strategy The checkout strategy to use. - /// :param: progress A block that's called with the progress of the checkout. - /// :returns: Returns a result with void or the error that occurred. - public func checkout(_ reference: ReferenceType, strategy: CheckoutStrategy, - progress: CheckoutProgressBlock? = nil) -> Result<(), NSError> { - return setHEAD(reference).flatMap { self.checkout(strategy: strategy, progress: progress) } - } - - /// Load all commits in the specified branch in topological & time order descending - /// - /// :param: branch The branch to get all commits from - /// :returns: Returns a result with array of branches or the error that occurred - public func commits(in branch: Branch) -> CommitIterator { - return commits(from: branch.oid) - } - - /// Load all commits from the given base in topological & time order descending - /// - /// :param: base The oid to get all commits from - /// :returns: Returns a result with array of branches or the error that occurred - public func commits(from base: OID) -> CommitIterator { - let iterator = CommitIterator(repo: self, root: base.oid) - return iterator - } - - /// Get the index for the repo. The caller is responsible for freeing the index. - func unsafeIndex() -> Result { - var index: OpaquePointer? = nil - let result = git_repository_index(&index, self.pointer) - guard result == GIT_OK.rawValue && index != nil else { - let err = NSError(gitError: result, pointOfFailure: "git_repository_index") - return .failure(err) - } - return .success(index!) - } - - /// Stage the file(s) under the specified path. - public func add(path: String) -> Result<(), NSError> { - var dirPointer = UnsafeMutablePointer(mutating: (path as NSString).utf8String) - var paths = withUnsafeMutablePointer(to: &dirPointer) { - git_strarray(strings: $0, count: 1) - } - return unsafeIndex().flatMap { index in - defer { git_index_free(index) } - 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 { - // 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 { - 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 { - 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) { - switch $0 { - case .failure(let error): - return error - - case .success(let newDiff): - if mergeDiff == nil { - mergeDiff = newDiff - } else { - let mergeResult = git_diff_merge(mergeDiff, newDiff) - guard mergeResult == GIT_OK.rawValue else { - return NSError(gitError: mergeResult, pointOfFailure: "git_diff_merge") - } - } - return nil - } - } - - if error != nil { - return Result.failure(error!) - } - } - - return .success(Diff(mergeDiff!)) - } - - private func diff(from oldCommitOid: OID?, to newCommitOid: OID?, transform: (Result) -> 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 { - switch unsafeTreeForCommitId(oid) { - case .failure(let error): - return transform(.failure(error)) - case .success(let value): - oldTree = value - } - } - - var newTree: OpaquePointer? = nil - defer { git_object_free(newTree) } - if let oid = newCommitOid { - switch unsafeTreeForCommitId(oid) { - case .failure(let error): - return transform(.failure(error)) - case .success(let value): - newTree = 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.success(diff!)) - } - - /// Memory safe - private func diff(from oldCommitOid: OID?, to newCommitOid: OID?) -> Result { - assert(oldCommitOid != nil || newCommitOid != nil, "It is an error to pass nil for both the oldOid and newOid") - - var oldTree: Tree? = nil - if let oldCommitOid = oldCommitOid { - switch safeTreeForCommitId(oldCommitOid) { - case .failure(let error): - return .failure(error) - case .success(let value): - oldTree = value - } - } - - var newTree: Tree? = nil - if let newCommitOid = newCommitOid { - switch safeTreeForCommitId(newCommitOid) { - case .failure(let error): - return .failure(error) - case .success(let value): - newTree = value - } - } - - if oldTree != nil && newTree != nil { - return withGitObjects([oldTree!.oid, newTree!.oid], type: GIT_OBJECT_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_OBJECT_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_OBJECT_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 { - 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> { - var returnDict = [Diff.Delta]() - - let count = git_diff_num_deltas(diffResult) - - for i in 0...success(returnDict) - return result - } - - private func safeTreeForCommitId(_ oid: OID) -> Result { - return withGitObject(oid, type: GIT_OBJECT_COMMIT) { commit in - let treeId = git_commit_tree_id(commit) - return tree(OID(treeId!.pointee)) - } - } - - /// Caller responsible to free returned tree with git_object_free - private func unsafeTreeForCommitId(_ oid: OID) -> Result { - var commit: OpaquePointer? = nil - var oid = oid.oid - let commitResult = git_object_lookup(&commit, self.pointer, &oid, GIT_OBJECT_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_OBJECT_TREE) - - git_object_free(commit) - - guard treeResult == GIT_OK.rawValue else { - return .failure(NSError(gitError: treeResult, pointOfFailure: "git_object_lookup")) - } - - return Result.success(tree!) - } - - // MARK: - Status - - public func status(options: StatusOptions = [.includeUntracked]) -> Result<[StatusEntry], NSError> { - var returnArray = [StatusEntry]() - - // Do this because GIT_STATUS_OPTIONS_INIT is unavailable in swift - let pointer = UnsafeMutablePointer.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 listOptions = pointer.move() - listOptions.flags = options.rawValue - pointer.deallocate() - - var unsafeStatus: OpaquePointer? = nil - defer { git_status_list_free(unsafeStatus) } - let statusResult = git_status_list_new(&unsafeStatus, self.pointer, &listOptions) - 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.. Result { - var pointer: OpaquePointer? - - let result = url.withUnsafeFileSystemRepresentation { - git_repository_open_ext(&pointer, $0, GIT_REPOSITORY_OPEN_NO_SEARCH.rawValue, nil) - } - - switch result { - case GIT_ENOTFOUND.rawValue: - return .success(false) - case GIT_OK.rawValue: - return .success(true) - default: - return .failure(NSError(gitError: result, pointOfFailure: "git_repository_open_ext")) - } - } + // MARK: - Creating Repositories + + /// Create a new repository at the given URL. + /// + /// URL - The URL of the repository. + /// + /// Returns a `Result` with a `Repository` or an error. + public class func create(at url: URL) -> Result { + var pointer: OpaquePointer? = nil + let result = url.withUnsafeFileSystemRepresentation { + git_repository_init(&pointer, $0, 0) + } + + guard result == GIT_OK.rawValue else { + return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_init")) + } + + let repository = Repository(pointer!) + return Result.success(repository) + } + + /// Load the repository at the given URL. + /// + /// URL - The URL of the repository. + /// + /// Returns a `Result` with a `Repository` or an error. + public class func at(_ url: URL) -> Result { + var pointer: OpaquePointer? = nil + let result = url.withUnsafeFileSystemRepresentation { + git_repository_open(&pointer, $0) + } + + guard result == GIT_OK.rawValue else { + return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_open")) + } + + let repository = Repository(pointer!) + return Result.success(repository) + } + + /// Clone the repository from a given URL. + /// + /// remoteURL - The URL of the remote repository + /// localURL - The URL to clone the remote repository into + /// localClone - Will not bypass the git-aware transport, even if remote is local. + /// bare - Clone remote as a bare repository. + /// credentials - Credentials to be used when connecting to the remote. + /// checkoutStrategy - The checkout strategy to use, if being checked out. + /// checkoutProgress - A block that's called with the progress of the checkout. + /// + /// Returns a `Result` with a `Repository` or an error. + public class func clone(from remoteURL: URL, to localURL: URL, localClone: Bool = false, bare: Bool = false, + credentials: Credentials = .default, checkoutStrategy: CheckoutStrategy = .safe, + checkoutProgress: CheckoutProgressBlock? = nil) -> Result + { + var options = cloneOptions( + bare: bare, + localClone: localClone, + fetchOptions: fetchOptions(credentials: credentials), + checkoutOptions: checkoutOptions(strategy: checkoutStrategy, progress: checkoutProgress) + ) + + var pointer: OpaquePointer? = nil + let remoteURLString = (remoteURL as NSURL).isFileReferenceURL() ? remoteURL.path : remoteURL.absoluteString + let result = localURL.withUnsafeFileSystemRepresentation { localPath in + git_clone(&pointer, remoteURLString, localPath, &options) + } + + guard result == GIT_OK.rawValue else { + return Result.failure(NSError(gitError: result, pointOfFailure: "git_clone")) + } + + let repository = Repository(pointer!) + return Result.success(repository) + } + + // MARK: - Initializers + + /// Create an instance with a libgit2 `git_repository` object. + /// + /// The Repository assumes ownership of the `git_repository` object. + init(_ pointer: OpaquePointer) { + self.pointer = pointer + + let path = git_repository_workdir(pointer) + directoryURL = path.map { URL(fileURLWithPath: String(validatingUTF8: $0)!, isDirectory: true) } + } + + deinit { + git_repository_free(pointer) + } + + // MARK: - Properties + + /// The underlying libgit2 `git_repository` object. + let pointer: OpaquePointer + + /// The URL of the repository's working directory, or `nil` if the + /// repository is bare. + public let directoryURL: URL? + + // MARK: - Object Lookups + + /// Load a libgit2 object and transform it to something else. + /// + /// oid - The OID of the object to look up. + /// type - The type of the object to look up. + /// transform - A function that takes the libgit2 object and transforms it + /// into something else. + /// + /// Returns the result of calling `transform` or an error if the object + /// cannot be loaded. + private func withGitObject(_ oid: OID, type: GitObjectType, + transform: (OpaquePointer) -> Result) -> Result + { + var pointer: OpaquePointer? = nil + var oid = oid.oid + let result = git_object_lookup(&pointer, self.pointer, &oid, type.git_object_type) + + guard result == GIT_OK.rawValue else { + return Result.failure(NSError(gitError: result, pointOfFailure: "git_object_lookup")) + } + + let value = transform(pointer!) + git_object_free(pointer) + return value + } + + private func withGitObject(_ oid: OID, type: GitObjectType, transform: (OpaquePointer) -> T) -> Result { + return withGitObject(oid, type: type) { Result.success(transform($0)) } + } + + private func withGitObjects(_ oids: [OID], type: GitObjectType, transform: ([OpaquePointer]) -> Result) -> Result { + 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.git_object_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. + /// + /// Returns a `Blob`, `Commit`, `Tag`, or `Tree` if one exists, or an error. + public func object(_ oid: OID) -> Result { + return withGitObject(oid, type: .any) { object in + let type = GitObjectType(git_object_type(object)) + switch type { + case .blob: + return Result.success(Blob(object)) + case .commit: + return Result.success(Commit(object)) + case .tag: + return Result.success(Tag(object)) + case .tree: + return Result.success(Tree(object)) + default: + let error = NSError( + domain: "org.libgit2.SwiftGit2", + code: 1, + userInfo: [ + NSLocalizedDescriptionKey: "Unrecognized git_object_t '\(String(describing: type))' for oid '\(oid)'.", + ] + ) + return Result.failure(error) + } + } + } + + /// Loads the blob with the given OID. + /// + /// oid - The OID of the blob to look up. + /// + /// Returns the blob if it exists, or an error. + public func blob(_ oid: OID) -> Result { + return withGitObject(oid, type: .blob) { Blob($0) } + } + + /// Loads the commit with the given OID. + /// + /// oid - The OID of the commit to look up. + /// + /// Returns the commit if it exists, or an error. + public func commit(_ oid: OID) -> Result { + return withGitObject(oid, type: .commit) { Commit($0) } + } + + /// Loads the tag with the given OID. + /// + /// oid - The OID of the tag to look up. + /// + /// Returns the tag if it exists, or an error. + public func tag(_ oid: OID) -> Result { + return withGitObject(oid, type: .tag) { Tag($0) } + } + + /// Loads the tree with the given OID. + /// + /// oid - The OID of the tree to look up. + /// + /// Returns the tree if it exists, or an error. + public func tree(_ oid: OID) -> Result { + return withGitObject(oid, type: .tree) { Tree($0) } + } + + /// Loads the referenced object from the pointer. + /// + /// pointer - A pointer to an object. + /// + /// Returns the object if it exists, or an error. + public func object(from pointer: PointerTo) -> Result { + return withGitObject(pointer.oid, type: pointer.type) { T($0) } + } + + /// Loads the referenced object from the pointer. + /// + /// pointer - A pointer to an object. + /// + /// Returns the object if it exists, or an error. + public func object(from pointer: Pointer) -> Result { + switch pointer { + case let .blob(oid): + return blob(oid).map { $0 as ObjectType } + case let .commit(oid): + return commit(oid).map { $0 as ObjectType } + case let .tag(oid): + return tag(oid).map { $0 as ObjectType } + case let .tree(oid): + return tree(oid).map { $0 as ObjectType } + } + } + + // MARK: - Remote Lookups + + /// Loads all the remotes in the repository. + /// + /// Returns an array of remotes, or an error. + public func allRemotes() -> Result<[Remote], NSError> { + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + let result = git_remote_list(pointer, self.pointer) + + guard result == GIT_OK.rawValue else { + pointer.deallocate() + return Result.failure(NSError(gitError: result, pointOfFailure: "git_remote_list")) + } + + let strarray = pointer.pointee + let remotes: [Result] = strarray.map { + self.remote(named: $0) + } + git_strarray_free(pointer) + pointer.deallocate() + + return remotes.aggregateResult() + } + + private func remoteLookup(named name: String, _ callback: (Result) -> 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"))) + } + + return callback(.success(pointer!)) + } + + /// Load a remote from the repository. + /// + /// name - The name of the remote. + /// + /// Returns the remote if it exists, or an error. + public func remote(named name: String) -> Result { + return remoteLookup(named: name) { $0.map(Remote.init) } + } + + /// Download new data and update tips + public func fetch(_ remote: Remote) -> Result { + return remoteLookup(named: remote.name) { remote in + remote.flatMap { pointer in + var opts = git_fetch_options() + let resultInit = git_fetch_init_options(&opts, UInt32(GIT_FETCH_OPTIONS_VERSION)) + assert(resultInit == GIT_OK.rawValue) + + let result = git_remote_fetch(pointer, nil, &opts, nil) + guard result == GIT_OK.rawValue else { + let err = NSError(gitError: result, pointOfFailure: "git_remote_fetch") + return .failure(err) + } + return .success(()) + } + } + } + + // MARK: - Reference Lookups + + /// Load all the references with the given prefix (e.g. "refs/heads/") + public func references(withPrefix prefix: String) -> Result<[ReferenceType], NSError> { + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + let result = git_reference_list(pointer, self.pointer) + + guard result == GIT_OK.rawValue else { + pointer.deallocate() + return Result.failure(NSError(gitError: result, pointOfFailure: "git_reference_list")) + } + + let strarray = pointer.pointee + let references = strarray + .filter { + $0.hasPrefix(prefix) + } + .map { + self.reference(named: $0) + } + git_strarray_free(pointer) + pointer.deallocate() + + return references.aggregateResult() + } + + /// Load the reference with the given long name (e.g. "refs/heads/master") + /// + /// If the reference is a branch, a `Branch` will be returned. If the + /// reference is a tag, a `TagReference` will be returned. Otherwise, a + /// `Reference` will be returned. + public func reference(named name: String) -> Result { + var pointer: OpaquePointer? = nil + let result = git_reference_lookup(&pointer, self.pointer, name) + + guard result == GIT_OK.rawValue else { + return Result.failure(NSError(gitError: result, pointOfFailure: "git_reference_lookup")) + } + + let value = referenceWithLibGit2Reference(pointer!) + git_reference_free(pointer) + return Result.success(value) + } + + /// Load and return a list of all local branches. + public func localBranches() -> Result<[Branch], NSError> { + return references(withPrefix: "refs/heads/") + .map { (refs: [ReferenceType]) in + refs.map { $0 as! Branch } + } + } + + /// Load and return a list of all remote branches. + public func remoteBranches() -> Result<[Branch], NSError> { + return references(withPrefix: "refs/remotes/") + .map { (refs: [ReferenceType]) in + refs.map { $0 as! Branch } + } + } + + /// Load the local branch with the given name (e.g., "master"). + public func localBranch(named name: String) -> Result { + return reference(named: "refs/heads/" + name).map { $0 as! Branch } + } + + /// Load the remote branch with the given name (e.g., "origin/master"). + public func remoteBranch(named name: String) -> Result { + return reference(named: "refs/remotes/" + name).map { $0 as! Branch } + } + + /// Load and return a list of all the `TagReference`s. + public func allTags() -> Result<[TagReference], NSError> { + return references(withPrefix: "refs/tags/") + .map { (refs: [ReferenceType]) in + refs.map { $0 as! TagReference } + } + } + + /// Load the tag with the given name (e.g., "tag-2"). + public func tag(named name: String) -> Result { + return reference(named: "refs/tags/" + name).map { $0 as! TagReference } + } + + // MARK: - Working Directory + + /// Load the reference pointed at by HEAD. + /// + /// When on a branch, this will return the current `Branch`. + public func HEAD() -> Result { + var pointer: OpaquePointer? = nil + let result = git_repository_head(&pointer, self.pointer) + guard result == GIT_OK.rawValue else { + return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_head")) + } + let value = referenceWithLibGit2Reference(pointer!) + git_reference_free(pointer) + return Result.success(value) + } + + /// Set HEAD to the given oid (detached). + /// + /// :param: oid The OID to set as HEAD. + /// :returns: Returns a result with void or the error that occurred. + public func setHEAD(_ oid: OID) -> Result { + var oid = oid.oid + let result = git_repository_set_head_detached(pointer, &oid) + guard result == GIT_OK.rawValue else { + return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_set_head")) + } + return Result.success(()) + } + + /// Set HEAD to the given reference. + /// + /// :param: reference The reference to set as HEAD. + /// :returns: Returns a result with void or the error that occurred. + public func setHEAD(_ reference: ReferenceType) -> Result { + let result = git_repository_set_head(pointer, reference.longName) + guard result == GIT_OK.rawValue else { + return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_set_head")) + } + return Result.success(()) + } + + /// Check out HEAD. + /// + /// :param: strategy The checkout strategy to use. + /// :param: progress A block that's called with the progress of the checkout. + /// :returns: Returns a result with void or the error that occurred. + public func checkout(strategy: CheckoutStrategy, progress: CheckoutProgressBlock? = nil) -> Result { + var options = checkoutOptions(strategy: strategy, progress: progress) + + let result = git_checkout_head(pointer, &options) + guard result == GIT_OK.rawValue else { + return Result.failure(NSError(gitError: result, pointOfFailure: "git_checkout_head")) + } + + return Result.success(()) + } + + /// Check out the given OID. + /// + /// :param: oid The OID of the commit to check out. + /// :param: strategy The checkout strategy to use. + /// :param: progress A block that's called with the progress of the checkout. + /// :returns: Returns a result with void or the error that occurred. + public func checkout(_ oid: OID, strategy: CheckoutStrategy, + progress: CheckoutProgressBlock? = nil) -> Result + { + return setHEAD(oid).flatMap { self.checkout(strategy: strategy, progress: progress) } + } + + /// Check out the given reference. + /// + /// :param: reference The reference to check out. + /// :param: strategy The checkout strategy to use. + /// :param: progress A block that's called with the progress of the checkout. + /// :returns: Returns a result with void or the error that occurred. + public func checkout(_ reference: ReferenceType, strategy: CheckoutStrategy, + progress: CheckoutProgressBlock? = nil) -> Result + { + return setHEAD(reference).flatMap { self.checkout(strategy: strategy, progress: progress) } + } + + /// Load all commits in the specified branch in topological & time order descending + /// + /// :param: branch The branch to get all commits from + /// :returns: Returns a result with array of branches or the error that occurred + public func commits(in branch: Branch) -> CommitIterator { + return commits(from: branch.oid) + } + + /// Load all commits from the given base in topological & time order descending + /// + /// :param: base The oid to get all commits from + /// :returns: Returns a result with array of branches or the error that occurred + public func commits(from base: OID) -> CommitIterator { + let iterator = CommitIterator(repo: self, root: base.oid) + return iterator + } + + /// Get the index for the repo. The caller is responsible for freeing the index. + func unsafeIndex() -> Result { + var index: OpaquePointer? = nil + let result = git_repository_index(&index, pointer) + guard result == GIT_OK.rawValue && index != nil else { + let err = NSError(gitError: result, pointOfFailure: "git_repository_index") + return .failure(err) + } + return .success(index!) + } + + /// Stage the file(s) under the specified path. + public func add(path: String) -> Result { + var dirPointer = UnsafeMutablePointer(mutating: (path as NSString).utf8String) + var paths = withUnsafeMutablePointer(to: &dirPointer) { + git_strarray(strings: $0, count: 1) + } + return unsafeIndex().flatMap { index in + defer { git_index_free(index) } + 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 { + // 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 { + 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 { + guard !commit.parents.isEmpty else { + // Initial commit in a repository + return diff(from: nil, to: commit.oid) + } + + var mergeDiff: OpaquePointer? = nil + defer { git_object_free(mergeDiff) } + for parent in commit.parents { + let error = diff(from: parent.oid, to: commit.oid) { + switch $0 { + case let .failure(error): + return error + + case let .success(newDiff): + if mergeDiff == nil { + mergeDiff = newDiff + } else { + let mergeResult = git_diff_merge(mergeDiff, newDiff) + guard mergeResult == GIT_OK.rawValue else { + return NSError(gitError: mergeResult, pointOfFailure: "git_diff_merge") + } + } + return nil + } + } + + if error != nil { + return Result.failure(error!) + } + } + + return .success(Diff(mergeDiff!)) + } + + private func diff(from oldCommitOid: OID?, to newCommitOid: OID?, transform: (Result) -> NSError?) -> NSError? { + assert(oldCommitOid != nil || newCommitOid != nil, "It is an error to pass nil for both the oldOid and newOid") + + var oldTree: OpaquePointer? + defer { git_object_free(oldTree) } + if let oid = oldCommitOid { + switch unsafeTreeForCommitId(oid) { + case let .failure(error): + return transform(.failure(error)) + case let .success(value): + oldTree = value + } + } + + var newTree: OpaquePointer? + defer { git_object_free(newTree) } + if let oid = newCommitOid { + switch unsafeTreeForCommitId(oid) { + case let .failure(error): + return transform(.failure(error)) + case let .success(value): + newTree = value + } + } + + var diff: OpaquePointer? + let diffResult = git_diff_tree_to_tree(&diff, + 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.success(diff!)) + } + + /// Memory safe + private func diff(from oldCommitOid: OID?, to newCommitOid: OID?) -> Result { + assert(oldCommitOid != nil || newCommitOid != nil, "It is an error to pass nil for both the oldOid and newOid") + + var oldTree: Tree? = nil + if let oldCommitOid = oldCommitOid { + switch safeTreeForCommitId(oldCommitOid) { + case let .failure(error): + return .failure(error) + case let .success(value): + oldTree = value + } + } + + var newTree: Tree? = nil + if let newCommitOid = newCommitOid { + switch safeTreeForCommitId(newCommitOid) { + case let .failure(error): + return .failure(error) + case let .success(value): + newTree = value + } + } + + if oldTree != nil && newTree != nil { + return withGitObjects([oldTree!.oid, newTree!.oid], type: .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: .tree, transform: { tree in + var diff: OpaquePointer? + 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: .tree, transform: { tree in + var diff: OpaquePointer? + 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 { + 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> { + var returnDict = [Diff.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 { + return withGitObject(oid, type: .commit) { commit in + let treeId = git_commit_tree_id(commit) + return tree(OID(treeId!.pointee)) + } + } + + /// Caller responsible to free returned tree with git_object_free + private func unsafeTreeForCommitId(_ oid: OID) -> Result { + var commit: OpaquePointer? = nil + var oid = oid.oid + let commitResult = git_object_lookup(&commit, pointer, &oid, GIT_OBJECT_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, pointer, treeId, GIT_OBJECT_TREE) + + git_object_free(commit) + + guard treeResult == GIT_OK.rawValue else { + return .failure(NSError(gitError: treeResult, pointOfFailure: "git_object_lookup")) + } + + return Result.success(tree!) + } + + // MARK: - Status + + public func status(options: StatusOptions = [.includeUntracked]) -> Result<[StatusEntry], NSError> { + var returnArray = [StatusEntry]() + + // Do this because GIT_STATUS_OPTIONS_INIT is unavailable in swift + let pointer = UnsafeMutablePointer.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 listOptions = pointer.move() + listOptions.flags = options.rawValue + pointer.deallocate() + + var unsafeStatus: OpaquePointer? = nil + defer { git_status_list_free(unsafeStatus) } + let statusResult = git_status_list_new(&unsafeStatus, self.pointer, &listOptions) + 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) + } + + // MARK: - Validity/Existence Check + + /// - returns: `.success(true)` iff there is a git repository at `url`, + /// `.success(false)` if there isn't, + /// and a `.failure` if there's been an error. + public static func isValid(url: URL) -> Result { + var pointer: OpaquePointer? + + let result = url.withUnsafeFileSystemRepresentation { + git_repository_open_ext(&pointer, $0, GIT_REPOSITORY_OPEN_NO_SEARCH.rawValue, nil) + } + + switch result { + case GIT_ENOTFOUND.rawValue: + return .success(false) + case GIT_OK.rawValue: + return .success(true) + default: + return .failure(NSError(gitError: result, pointOfFailure: "git_repository_open_ext")) + } + } } private extension Array { - func aggregateResult() -> Result<[Value], Error> where Element == Result { - var values: [Value] = [] - for result in self { - switch result { - case .success(let value): - values.append(value) - case .failure(let error): - return .failure(error) - } - } - return .success(values) - } + func aggregateResult() -> Result<[Value], Error> where Element == Result { + var values: [Value] = [] + for result in self { + switch result { + case let .success(value): + values.append(value) + case let .failure(error): + return .failure(error) + } + } + return .success(values) + } } diff --git a/Sources/SwiftGit2/StatusOptions.swift b/Sources/SwiftGit2/StatusOptions.swift index c4951960..ca9bd28b 100644 --- a/Sources/SwiftGit2/StatusOptions.swift +++ b/Sources/SwiftGit2/StatusOptions.swift @@ -2,62 +2,62 @@ // Copyright © 2020 GitHub, Inc. All rights reserved. // -import libgit2 import Foundation +import libgit2 public struct StatusOptions: OptionSet { - public let rawValue: UInt32 + public let rawValue: UInt32 - public init(rawValue: UInt32) { - self.rawValue = rawValue - } + public init(rawValue: UInt32) { + self.rawValue = rawValue + } - public static let includeUntracked = StatusOptions( - rawValue: GIT_STATUS_OPT_INCLUDE_UNTRACKED.rawValue - ) - public static let includeIgnored = StatusOptions( - rawValue: GIT_STATUS_OPT_INCLUDE_IGNORED.rawValue - ) - public static let includeUnmodified = StatusOptions( - rawValue: GIT_STATUS_OPT_INCLUDE_UNMODIFIED.rawValue - ) - public static let excludeSubmodules = StatusOptions( - rawValue: GIT_STATUS_OPT_EXCLUDE_SUBMODULES.rawValue - ) - public static let recurseUntrackedDirs = StatusOptions( - rawValue: GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS.rawValue - ) - public static let disablePathSpecMatch = StatusOptions( - rawValue: GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH.rawValue - ) - public static let recurseIgnoredDirs = StatusOptions( - rawValue: GIT_STATUS_OPT_RECURSE_IGNORED_DIRS.rawValue - ) - public static let renamesHeadToIndex = StatusOptions( - rawValue: GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX.rawValue - ) - public static let renamesIndexToWorkDir = StatusOptions( - rawValue: GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR.rawValue - ) - public static let sortCasesSensitively = StatusOptions( - rawValue: GIT_STATUS_OPT_SORT_CASE_SENSITIVELY.rawValue - ) - public static let sortCasesInSensitively = StatusOptions( - rawValue: GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY.rawValue - ) - public static let renamesFromRewrites = StatusOptions( - rawValue: GIT_STATUS_OPT_RENAMES_FROM_REWRITES.rawValue - ) - public static let noRefresh = StatusOptions( - rawValue: GIT_STATUS_OPT_NO_REFRESH.rawValue - ) - public static let updateIndex = StatusOptions( - rawValue: GIT_STATUS_OPT_UPDATE_INDEX.rawValue - ) - public static let includeUnreadable = StatusOptions( - rawValue: GIT_STATUS_OPT_INCLUDE_UNREADABLE.rawValue - ) - public static let includeUnreadableAsUntracked = StatusOptions( - rawValue: GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED.rawValue - ) + public static let includeUntracked = StatusOptions( + rawValue: GIT_STATUS_OPT_INCLUDE_UNTRACKED.rawValue + ) + public static let includeIgnored = StatusOptions( + rawValue: GIT_STATUS_OPT_INCLUDE_IGNORED.rawValue + ) + public static let includeUnmodified = StatusOptions( + rawValue: GIT_STATUS_OPT_INCLUDE_UNMODIFIED.rawValue + ) + public static let excludeSubmodules = StatusOptions( + rawValue: GIT_STATUS_OPT_EXCLUDE_SUBMODULES.rawValue + ) + public static let recurseUntrackedDirs = StatusOptions( + rawValue: GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS.rawValue + ) + public static let disablePathSpecMatch = StatusOptions( + rawValue: GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH.rawValue + ) + public static let recurseIgnoredDirs = StatusOptions( + rawValue: GIT_STATUS_OPT_RECURSE_IGNORED_DIRS.rawValue + ) + public static let renamesHeadToIndex = StatusOptions( + rawValue: GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX.rawValue + ) + public static let renamesIndexToWorkDir = StatusOptions( + rawValue: GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR.rawValue + ) + public static let sortCasesSensitively = StatusOptions( + rawValue: GIT_STATUS_OPT_SORT_CASE_SENSITIVELY.rawValue + ) + public static let sortCasesInSensitively = StatusOptions( + rawValue: GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY.rawValue + ) + public static let renamesFromRewrites = StatusOptions( + rawValue: GIT_STATUS_OPT_RENAMES_FROM_REWRITES.rawValue + ) + public static let noRefresh = StatusOptions( + rawValue: GIT_STATUS_OPT_NO_REFRESH.rawValue + ) + public static let updateIndex = StatusOptions( + rawValue: GIT_STATUS_OPT_UPDATE_INDEX.rawValue + ) + public static let includeUnreadable = StatusOptions( + rawValue: GIT_STATUS_OPT_INCLUDE_UNREADABLE.rawValue + ) + public static let includeUnreadableAsUntracked = StatusOptions( + rawValue: GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED.rawValue + ) } diff --git a/Sources/SwiftGit2/SwiftGit2.swift b/Sources/SwiftGit2/SwiftGit2.swift index 6f2186b6..e4469b61 100644 --- a/Sources/SwiftGit2/SwiftGit2.swift +++ b/Sources/SwiftGit2/SwiftGit2.swift @@ -1,14 +1,9 @@ -// -// SwiftGit2.swift -// -// -// Created by Mathijs Bernson on 01/03/2024. -// - import Foundation import libgit2 public class SwiftGit2 { + private init() {} + public static func initialize() -> Result { let status = git_libgit2_init() if status < 0 { diff --git a/Sources/SwiftGit2/git_strarray+Extensions.swift b/Sources/SwiftGit2/git_strarray+Extensions.swift new file mode 100644 index 00000000..62079efd --- /dev/null +++ b/Sources/SwiftGit2/git_strarray+Extensions.swift @@ -0,0 +1,22 @@ +// +// git_strarray+Extensions.swift +// libgit2+Extensions +// +// Created by Mathijs Bernson on 27/08/2021. +// + +import Foundation +import libgit2 + +extension git_strarray { + func filter(_ isIncluded: (String) -> Bool) -> [String] { + return map { $0 }.filter(isIncluded) + } + + func map(_ transform: (String) throws -> T) rethrows -> [T] { + return try (0 ..< count).map { + let string = String(validatingUTF8: self.strings[$0]!)! + return try transform(string) + } + } +} diff --git a/Tests/SwiftGit2Tests/Fixtures.swift b/Tests/SwiftGit2Tests/Fixtures.swift index 71df55dd..690a40e4 100644 --- a/Tests/SwiftGit2Tests/Fixtures.swift +++ b/Tests/SwiftGit2Tests/Fixtures.swift @@ -6,63 +6,62 @@ // Copyright (c) 2014 GitHub, Inc. All rights reserved. // -import SwiftGit2 +@testable import SwiftGit2 import ZipArchive final class Fixtures { + // MARK: Lifecycle - // MARK: Lifecycle + class var sharedInstance: Fixtures { + enum Singleton { + static let instance = Fixtures() + } + return Singleton.instance + } - class var sharedInstance: Fixtures { - enum Singleton { - static let instance = Fixtures() - } - return Singleton.instance - } + init() { + directoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) + .appendingPathComponent("org.libgit2.SwiftGit2") + .appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString) + } - init() { - directoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) - .appendingPathComponent("org.libgit2.SwiftGit2") - .appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString) - } + // MARK: - Setup and Teardown - // MARK: - Setup and Teardown + let directoryURL: URL - let directoryURL: URL - - func setUp() { - try! FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) + func setUp() { + try! FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) let bundle = Bundle.module - let zipURLs = bundle.urls(forResourcesWithExtension: "zip", subdirectory: "Fixtures")! + let zipURLs = bundle.urls(forResourcesWithExtension: "zip", subdirectory: "Fixtures")! - for URL in zipURLs { - SSZipArchive.unzipFile(atPath: URL.path, toDestination: directoryURL.path) - } - } + for URL in zipURLs { + SSZipArchive.unzipFile(atPath: URL.path, toDestination: directoryURL.path) + } + } - func tearDown() { - try! FileManager.default.removeItem(at: directoryURL) - } + func tearDown() { + try! FileManager.default.removeItem(at: directoryURL) + } - // MARK: - Helpers + // MARK: - Helpers - func repository(named name: String) -> Repository { - let url = directoryURL.appendingPathComponent(name, isDirectory: true) + func repository(named name: String) -> Repository { + let url = directoryURL.appendingPathComponent(name, isDirectory: true) return Repository.at(url).value! - } + } - // MARK: - The Fixtures + // MARK: - The Fixtures - class var detachedHeadRepository: Repository { - return Fixtures.sharedInstance.repository(named: "detached-head") - } + class var detachedHeadRepository: Repository { + return Fixtures.sharedInstance.repository(named: "detached-head") + } - class var simpleRepository: Repository { - return Fixtures.sharedInstance.repository(named: "simple-repository") - } + class var simpleRepository: Repository { + return Fixtures.sharedInstance.repository(named: "simple-repository") + } - class var mantleRepository: Repository { - return Fixtures.sharedInstance.repository(named: "Mantle") - } + class var mantleRepository: Repository { + return Fixtures.sharedInstance.repository(named: "Mantle") + } } diff --git a/Tests/SwiftGit2Tests/FixturesSpec.swift b/Tests/SwiftGit2Tests/FixturesSpec.swift index d47f63e6..885a1644 100644 --- a/Tests/SwiftGit2Tests/FixturesSpec.swift +++ b/Tests/SwiftGit2Tests/FixturesSpec.swift @@ -7,18 +7,18 @@ // import Quick -import SwiftGit2 +@testable import SwiftGit2 class FixturesSpec: QuickSpec { - override class func spec() { - beforeSuite { + override class func spec() { + beforeSuite { _ = SwiftGit2.initialize() - Fixtures.sharedInstance.setUp() - } + Fixtures.sharedInstance.setUp() + } - afterSuite { - Fixtures.sharedInstance.tearDown() + afterSuite { + Fixtures.sharedInstance.tearDown() _ = SwiftGit2.shutdown() - } - } + } + } } diff --git a/Tests/SwiftGit2Tests/OIDSpec.swift b/Tests/SwiftGit2Tests/OIDSpec.swift index 54c2549f..2c3403bb 100644 --- a/Tests/SwiftGit2Tests/OIDSpec.swift +++ b/Tests/SwiftGit2Tests/OIDSpec.swift @@ -6,67 +6,67 @@ // Copyright (c) 2014 GitHub, Inc. All rights reserved. // -import SwiftGit2 import Nimble import Quick +@testable import SwiftGit2 class OIDSpec: QuickSpec { - override class func spec() { - describe("OID(string:)") { - it("should be nil if string is too short") { - expect(OID(string: "123456789012345678901234567890123456789")).to(beNil()) - } + override class func spec() { + describe("OID(string:)") { + it("should be nil if string is too short") { + expect(OID(string: "123456789012345678901234567890123456789")).to(beNil()) + } - it("should be nil if string is too long") { - expect(OID(string: "12345678901234567890123456789012345678901")).to(beNil()) - } + it("should be nil if string is too long") { + expect(OID(string: "12345678901234567890123456789012345678901")).to(beNil()) + } - it("should not be nil if string is just right") { - expect(OID(string: "1234567890123456789012345678ABCDEFabcdef")).notTo(beNil()) - } + it("should not be nil if string is just right") { + expect(OID(string: "1234567890123456789012345678ABCDEFabcdef")).notTo(beNil()) + } - it("should be nil with non-hex characters") { - expect(OID(string: "123456789012345678901234567890123456789j")).to(beNil()) - } - } + it("should be nil with non-hex characters") { + expect(OID(string: "123456789012345678901234567890123456789j")).to(beNil()) + } + } - describe("OID(oid)") { - it("should equal an OID with the same git_oid") { - let oid = OID(string: "1234567890123456789012345678901234567890")! - expect(OID(oid.oid)).to(equal(oid)) - } - } + describe("OID(oid)") { + it("should equal an OID with the same git_oid") { + let oid = OID(string: "1234567890123456789012345678901234567890")! + expect(OID(oid.oid)).to(equal(oid)) + } + } - describe("OID.description") { - it("should return the SHA") { - let SHA = "1234567890123456789012345678901234567890" - let oid = OID(string: SHA)! - expect(oid.description).to(equal(SHA)) - } - } + describe("OID.description") { + it("should return the SHA") { + let SHA = "1234567890123456789012345678901234567890" + let oid = OID(string: SHA)! + expect(oid.description).to(equal(SHA)) + } + } - describe("==(OID, OID)") { - it("should be equal when identical") { - let SHA = "1234567890123456789012345678901234567890" - let oid1 = OID(string: SHA)! - let oid2 = OID(string: SHA)! - expect(oid1).to(equal(oid2)) - } + describe("==(OID, OID)") { + it("should be equal when identical") { + let SHA = "1234567890123456789012345678901234567890" + let oid1 = OID(string: SHA)! + let oid2 = OID(string: SHA)! + expect(oid1).to(equal(oid2)) + } - it("should be not equal when different") { - let oid1 = OID(string: "1234567890123456789012345678901234567890")! - let oid2 = OID(string: "0000000000000000000000000000000000000000")! - expect(oid1).notTo(equal(oid2)) - } - } + it("should be not equal when different") { + let oid1 = OID(string: "1234567890123456789012345678901234567890")! + let oid2 = OID(string: "0000000000000000000000000000000000000000")! + expect(oid1).notTo(equal(oid2)) + } + } - describe("OID.hashValue") { - it("should be equal when OIDs are equal") { - let SHA = "1234567890123456789012345678901234567890" - let oid1 = OID(string: SHA)! - let oid2 = OID(string: SHA)! - expect(oid1.hashValue).to(equal(oid2.hashValue)) - } - } - } + describe("OID.hashValue") { + it("should be equal when OIDs are equal") { + let SHA = "1234567890123456789012345678901234567890" + let oid1 = OID(string: SHA)! + let oid2 = OID(string: SHA)! + expect(oid1.hashValue).to(equal(oid2.hashValue)) + } + } + } } diff --git a/Tests/SwiftGit2Tests/ObjectsSpec.swift b/Tests/SwiftGit2Tests/ObjectsSpec.swift index 5b6ff7a7..5d118a53 100644 --- a/Tests/SwiftGit2Tests/ObjectsSpec.swift +++ b/Tests/SwiftGit2Tests/ObjectsSpec.swift @@ -1,5 +1,5 @@ // -// ObjectSpec.swift +// ObjectsSpec.swift // SwiftGit2 // // Created by Matt Diephouse on 12/4/14. @@ -7,377 +7,377 @@ // import Foundation -import SwiftGit2 +import libgit2 import Nimble import Quick -import libgit2 +@testable import SwiftGit2 private extension Repository { - func withGitObject(_ oid: OID, transform: (OpaquePointer) -> T) -> T { - let repository = self.pointer - var oid = oid.oid + func withGitObject(_ oid: OID, transform: (OpaquePointer) -> T) -> T { + let repository = self.pointer + var oid = oid.oid - var pointer: OpaquePointer? = nil - git_object_lookup(&pointer, repository, &oid, GIT_OBJECT_ANY) - let result = transform(pointer!) - git_object_free(pointer) + var pointer: OpaquePointer? = nil + git_object_lookup(&pointer, repository, &oid, GIT_OBJECT_ANY) + let result = transform(pointer!) + git_object_free(pointer) - return result - } + return result + } } class SignatureSpec: FixturesSpec { - override class func spec() { - describe("Signature(signature)") { - it("should initialize its properties") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! - - let raw_signature = repo.withGitObject(oid) { git_commit_author($0).pointee } - let signature = Signature(raw_signature) - - expect(signature.name).to(equal("Matt Diephouse")) - expect(signature.email).to(equal("matt@diephouse.com")) - expect(signature.time).to(equal(Date(timeIntervalSince1970: 1416186947))) - expect(signature.timeZone.abbreviation()).to(equal("GMT-5")) - } - } - - describe("==(Signature, Signature)") { - it("should be true with equal objects") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! - - let author1 = repo.withGitObject(oid) { commit in - Signature(git_commit_author(commit).pointee) - } - let author2 = author1 - - expect(author1).to(equal(author2)) - } - - it("should be false with unequal objects") { - let repo = Fixtures.simpleRepository - let oid1 = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! - let oid2 = OID(string: "24e1e40ee77525d9e279f079f9906ad6d98c8940")! - - let author1 = repo.withGitObject(oid1) { commit in - Signature(git_commit_author(commit).pointee) - } - let author2 = repo.withGitObject(oid2) { commit in - Signature(git_commit_author(commit).pointee) - } - - expect(author1).notTo(equal(author2)) - } - } - - describe("Signature.hashValue") { - it("should be equal with equal objects") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! - - let author1 = repo.withGitObject(oid) { commit in - Signature(git_commit_author(commit).pointee) - } - let author2 = author1 - - expect(author1.hashValue).to(equal(author2.hashValue)) - } - } - } + override class func spec() { + describe("Signature(signature)") { + it("should initialize its properties") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! + + let raw_signature = repo.withGitObject(oid) { git_commit_author($0).pointee } + let signature = Signature(raw_signature) + + expect(signature.name).to(equal("Matt Diephouse")) + expect(signature.email).to(equal("matt@diephouse.com")) + expect(signature.time).to(equal(Date(timeIntervalSince1970: 1_416_186_947))) + expect(signature.timeZone.abbreviation()).to(equal("GMT-5")) + } + } + + describe("==(Signature, Signature)") { + it("should be true with equal objects") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! + + let author1 = repo.withGitObject(oid) { commit in + Signature(git_commit_author(commit).pointee) + } + let author2 = author1 + + expect(author1).to(equal(author2)) + } + + it("should be false with unequal objects") { + let repo = Fixtures.simpleRepository + let oid1 = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! + let oid2 = OID(string: "24e1e40ee77525d9e279f079f9906ad6d98c8940")! + + let author1 = repo.withGitObject(oid1) { commit in + Signature(git_commit_author(commit).pointee) + } + let author2 = repo.withGitObject(oid2) { commit in + Signature(git_commit_author(commit).pointee) + } + + expect(author1).notTo(equal(author2)) + } + } + + describe("Signature.hashValue") { + it("should be equal with equal objects") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! + + let author1 = repo.withGitObject(oid) { commit in + Signature(git_commit_author(commit).pointee) + } + let author2 = author1 + + expect(author1.hashValue).to(equal(author2.hashValue)) + } + } + } } class CommitSpec: QuickSpec { - override class func spec() { - describe("Commit(pointer)") { - it("should initialize its properties") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "24e1e40ee77525d9e279f079f9906ad6d98c8940")! - - let commit = repo.withGitObject(oid) { Commit($0) } - let author = repo.withGitObject(oid) { commit in - Signature(git_commit_author(commit).pointee) - } - let committer = repo.withGitObject(oid) { commit in - Signature(git_commit_committer(commit).pointee) - } - let tree = PointerTo(OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")!) - let parents: [PointerTo] = [ - PointerTo(OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")!), - ] - expect(commit.oid).to(equal(oid)) - expect(commit.tree).to(equal(tree)) - expect(commit.parents).to(equal(parents)) - expect(commit.message).to(equal("List branches in README\n")) - expect(commit.author).to(equal(author)) - expect(commit.committer).to(equal(committer)) - } - - it("should handle 0 parents") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! - - let commit = repo.withGitObject(oid) { Commit($0) } - expect(commit.parents).to(equal([])) - } - - it("should handle multiple parents") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "c4ed03a6b7d7ce837d31d83757febbe84dd465fd")! - - let commit = repo.withGitObject(oid) { Commit($0) } - let parents: [PointerTo] = [ - PointerTo(OID(string: "315b3f344221db91ddc54b269f3c9af422da0f2e")!), - PointerTo(OID(string: "57f6197561d1f99b03c160f4026a07f06b43cf20")!), - ] - expect(commit.parents).to(equal(parents)) - } - } - - describe("==(Commit, Commit)") { - it("should be true with equal objects") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! - - let commit1 = repo.withGitObject(oid) { Commit($0) } - let commit2 = commit1 - expect(commit1).to(equal(commit2)) - } - - it("should be false with unequal objects") { - let repo = Fixtures.simpleRepository - let oid1 = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! - let oid2 = OID(string: "c4ed03a6b7d7ce837d31d83757febbe84dd465fd")! - - let commit1 = repo.withGitObject(oid1) { Commit($0) } - let commit2 = repo.withGitObject(oid2) { Commit($0) } - expect(commit1).notTo(equal(commit2)) - } - } - - describe("Commit.hashValue") { - it("should be equal with equal objects") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! - - let commit1 = repo.withGitObject(oid) { Commit($0) } - let commit2 = commit1 - expect(commit1.hashValue).to(equal(commit2.hashValue)) - } - } - } + override class func spec() { + describe("Commit(pointer)") { + it("should initialize its properties") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "24e1e40ee77525d9e279f079f9906ad6d98c8940")! + + let commit = repo.withGitObject(oid) { Commit($0) } + let author = repo.withGitObject(oid) { commit in + Signature(git_commit_author(commit).pointee) + } + let committer = repo.withGitObject(oid) { commit in + Signature(git_commit_committer(commit).pointee) + } + let tree = PointerTo(OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")!) + let parents: [PointerTo] = [ + PointerTo(OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")!), + ] + expect(commit.oid).to(equal(oid)) + expect(commit.tree).to(equal(tree)) + expect(commit.parents).to(equal(parents)) + expect(commit.message).to(equal("List branches in README\n")) + expect(commit.author).to(equal(author)) + expect(commit.committer).to(equal(committer)) + } + + it("should handle 0 parents") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! + + let commit = repo.withGitObject(oid) { Commit($0) } + expect(commit.parents).to(equal([])) + } + + it("should handle multiple parents") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "c4ed03a6b7d7ce837d31d83757febbe84dd465fd")! + + let commit = repo.withGitObject(oid) { Commit($0) } + let parents: [PointerTo] = [ + PointerTo(OID(string: "315b3f344221db91ddc54b269f3c9af422da0f2e")!), + PointerTo(OID(string: "57f6197561d1f99b03c160f4026a07f06b43cf20")!), + ] + expect(commit.parents).to(equal(parents)) + } + } + + describe("==(Commit, Commit)") { + it("should be true with equal objects") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! + + let commit1 = repo.withGitObject(oid) { Commit($0) } + let commit2 = commit1 + expect(commit1).to(equal(commit2)) + } + + it("should be false with unequal objects") { + let repo = Fixtures.simpleRepository + let oid1 = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! + let oid2 = OID(string: "c4ed03a6b7d7ce837d31d83757febbe84dd465fd")! + + let commit1 = repo.withGitObject(oid1) { Commit($0) } + let commit2 = repo.withGitObject(oid2) { Commit($0) } + expect(commit1).notTo(equal(commit2)) + } + } + + describe("Commit.hashValue") { + it("should be equal with equal objects") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! + + let commit1 = repo.withGitObject(oid) { Commit($0) } + let commit2 = commit1 + expect(commit1.hashValue).to(equal(commit2.hashValue)) + } + } + } } class TreeEntrySpec: QuickSpec { - override class func spec() { - describe("Tree.Entry(attributes:object:name:)") { - it("should set its properties") { - let attributes = Int32(GIT_FILEMODE_BLOB.rawValue) - let object = Pointer.blob(OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")!) - let name = "README.md" - - let entry = Tree.Entry(attributes: attributes, object: object, name: name) - expect(entry.attributes).to(equal(attributes)) - expect(entry.object).to(equal(object)) - expect(entry.name).to(equal(name)) - } - } - - describe("Tree.Entry(pointer)") { - it("should set its properties") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! - - let entry = repo.withGitObject(oid) { Tree.Entry(git_tree_entry_byindex($0, 0)) } - expect(entry.attributes).to(equal(Int32(GIT_FILEMODE_BLOB.rawValue))) - expect(entry.object).to(equal(Pointer.blob(OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")!))) - expect(entry.name).to(equal("README.md")) - } - } - - describe("==(Tree.Entry, Tree.Entry)") { - it("should be true with equal objects") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! - - let entry1 = repo.withGitObject(oid) { Tree.Entry(git_tree_entry_byindex($0, 0)) } - let entry2 = entry1 - expect(entry1).to(equal(entry2)) - } - - it("should be false with unequal objects") { - let repo = Fixtures.simpleRepository - let oid1 = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! - let oid2 = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! - - let entry1 = repo.withGitObject(oid1) { Tree.Entry(git_tree_entry_byindex($0, 0)) } - let entry2 = repo.withGitObject(oid2) { Tree.Entry(git_tree_entry_byindex($0, 0)) } - expect(entry1).notTo(equal(entry2)) - } - } - - describe("Tree.Entry.hashValue") { - it("should be equal with equal objects") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! - - let entry1 = repo.withGitObject(oid) { Tree.Entry(git_tree_entry_byindex($0, 0)) } - let entry2 = entry1 - expect(entry1.hashValue).to(equal(entry2.hashValue)) - } - } - } + override class func spec() { + describe("Tree.Entry(attributes:object:name:)") { + it("should set its properties") { + let attributes = Int32(GIT_FILEMODE_BLOB.rawValue) + let object = Pointer.blob(OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")!) + let name = "README.md" + + let entry = Tree.Entry(attributes: attributes, object: object, name: name) + expect(entry.attributes).to(equal(attributes)) + expect(entry.object).to(equal(object)) + expect(entry.name).to(equal(name)) + } + } + + describe("Tree.Entry(pointer)") { + it("should set its properties") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! + + let entry = repo.withGitObject(oid) { Tree.Entry(git_tree_entry_byindex($0, 0)) } + expect(entry.attributes).to(equal(Int32(GIT_FILEMODE_BLOB.rawValue))) + expect(entry.object).to(equal(Pointer.blob(OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")!))) + expect(entry.name).to(equal("README.md")) + } + } + + describe("==(Tree.Entry, Tree.Entry)") { + it("should be true with equal objects") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! + + let entry1 = repo.withGitObject(oid) { Tree.Entry(git_tree_entry_byindex($0, 0)) } + let entry2 = entry1 + expect(entry1).to(equal(entry2)) + } + + it("should be false with unequal objects") { + let repo = Fixtures.simpleRepository + let oid1 = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! + let oid2 = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! + + let entry1 = repo.withGitObject(oid1) { Tree.Entry(git_tree_entry_byindex($0, 0)) } + let entry2 = repo.withGitObject(oid2) { Tree.Entry(git_tree_entry_byindex($0, 0)) } + expect(entry1).notTo(equal(entry2)) + } + } + + describe("Tree.Entry.hashValue") { + it("should be equal with equal objects") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! + + let entry1 = repo.withGitObject(oid) { Tree.Entry(git_tree_entry_byindex($0, 0)) } + let entry2 = entry1 + expect(entry1.hashValue).to(equal(entry2.hashValue)) + } + } + } } class TreeSpec: QuickSpec { - override class func spec() { - describe("Tree(pointer)") { - it("should initialize its properties") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! - - let tree = repo.withGitObject(oid) { Tree($0) } - let entries = [ - "README.md": Tree.Entry(attributes: Int32(GIT_FILEMODE_BLOB.rawValue), - object: .blob(OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")!), - name: "README.md"), - ] - expect(tree.entries).to(equal(entries)) - } - } - - describe("==(Tree, Tree)") { - it("should be true with equal objects") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! - - let tree1 = repo.withGitObject(oid) { Tree($0) } - let tree2 = tree1 - expect(tree1).to(equal(tree2)) - } - - it("should be false with unequal objects") { - let repo = Fixtures.simpleRepository - let oid1 = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! - let oid2 = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! - - let tree1 = repo.withGitObject(oid1) { Tree($0) } - let tree2 = repo.withGitObject(oid2) { Tree($0) } - expect(tree1).notTo(equal(tree2)) - } - } - - describe("Tree.hashValue") { - it("should be equal with equal objects") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! - - let tree1 = repo.withGitObject(oid) { Tree($0) } - let tree2 = tree1 - expect(tree1.hashValue).to(equal(tree2.hashValue)) - } - } - } + override class func spec() { + describe("Tree(pointer)") { + it("should initialize its properties") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! + + let tree = repo.withGitObject(oid) { Tree($0) } + let entries = [ + "README.md": Tree.Entry(attributes: Int32(GIT_FILEMODE_BLOB.rawValue), + object: .blob(OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")!), + name: "README.md"), + ] + expect(tree.entries).to(equal(entries)) + } + } + + describe("==(Tree, Tree)") { + it("should be true with equal objects") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! + + let tree1 = repo.withGitObject(oid) { Tree($0) } + let tree2 = tree1 + expect(tree1).to(equal(tree2)) + } + + it("should be false with unequal objects") { + let repo = Fixtures.simpleRepository + let oid1 = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! + let oid2 = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! + + let tree1 = repo.withGitObject(oid1) { Tree($0) } + let tree2 = repo.withGitObject(oid2) { Tree($0) } + expect(tree1).notTo(equal(tree2)) + } + } + + describe("Tree.hashValue") { + it("should be equal with equal objects") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! + + let tree1 = repo.withGitObject(oid) { Tree($0) } + let tree2 = tree1 + expect(tree1.hashValue).to(equal(tree2.hashValue)) + } + } + } } class BlobSpec: QuickSpec { - override class func spec() { - describe("Blob(pointer)") { - it("should initialize its properties") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! - - let blob = repo.withGitObject(oid) { Blob($0) } - let contents = "# Simple Repository\nA simple repository used for testing SwiftGit2.\n\n## Branches\n\n- master\n\n" - let data = contents.data(using: String.Encoding.utf8)! - expect(blob.oid).to(equal(oid)) - expect(blob.data).to(equal(data)) - } - } - - describe("==(Blob, Blob)") { - it("should be true with equal objects") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! - - let blob1 = repo.withGitObject(oid) { Blob($0) } - let blob2 = blob1 - expect(blob1).to(equal(blob2)) - } - - it("should be false with unequal objects") { - let repo = Fixtures.simpleRepository - let oid1 = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! - let oid2 = OID(string: "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")! - - let blob1 = repo.withGitObject(oid1) { Blob($0) } - let blob2 = repo.withGitObject(oid2) { Blob($0) } - expect(blob1).notTo(equal(blob2)) - } - } - - describe("Blob.hashValue") { - it("should be equal with equal objects") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! - - let blob1 = repo.withGitObject(oid) { Blob($0) } - let blob2 = blob1 - expect(blob1.hashValue).to(equal(blob2.hashValue)) - } - } - } + override class func spec() { + describe("Blob(pointer)") { + it("should initialize its properties") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! + + let blob = repo.withGitObject(oid) { Blob($0) } + let contents = "# Simple Repository\nA simple repository used for testing SwiftGit2.\n\n## Branches\n\n- master\n\n" + let data = contents.data(using: String.Encoding.utf8)! + expect(blob.oid).to(equal(oid)) + expect(blob.data).to(equal(data)) + } + } + + describe("==(Blob, Blob)") { + it("should be true with equal objects") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! + + let blob1 = repo.withGitObject(oid) { Blob($0) } + let blob2 = blob1 + expect(blob1).to(equal(blob2)) + } + + it("should be false with unequal objects") { + let repo = Fixtures.simpleRepository + let oid1 = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! + let oid2 = OID(string: "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")! + + let blob1 = repo.withGitObject(oid1) { Blob($0) } + let blob2 = repo.withGitObject(oid2) { Blob($0) } + expect(blob1).notTo(equal(blob2)) + } + } + + describe("Blob.hashValue") { + it("should be equal with equal objects") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! + + let blob1 = repo.withGitObject(oid) { Blob($0) } + let blob2 = blob1 + expect(blob1.hashValue).to(equal(blob2.hashValue)) + } + } + } } class TagSpec: QuickSpec { - override class func spec() { - describe("Tag(pointer)") { - it("should set its properties") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! - - let tag = repo.withGitObject(oid) { Tag($0) } - let tagger = repo.withGitObject(oid) { Signature(git_tag_tagger($0).pointee) } - - expect(tag.oid).to(equal(oid)) - expect(tag.target).to(equal(Pointer.commit(OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")!))) - expect(tag.name).to(equal("tag-1")) - expect(tag.tagger).to(equal(tagger)) - expect(tag.message).to(equal("tag-1\n")) - } - } - - describe("==(Tag, Tag)") { - it("should be true with equal objects") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! - - let tag1 = repo.withGitObject(oid) { Tag($0) } - let tag2 = tag1 - expect(tag1).to(equal(tag2)) - } - - it("should be false with unequal objects") { - let repo = Fixtures.simpleRepository - let oid1 = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! - let oid2 = OID(string: "13bda91157f255ab224ff88d0a11a82041c9d0c1")! - - let tag1 = repo.withGitObject(oid1) { Tag($0) } - let tag2 = repo.withGitObject(oid2) { Tag($0) } - expect(tag1).notTo(equal(tag2)) - } - } - - describe("Tag.hashValue") { - it("should be equal with equal objects") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! - - let tag1 = repo.withGitObject(oid) { Tag($0) } - let tag2 = tag1 - expect(tag1.hashValue).to(equal(tag2.hashValue)) - } - } - } + override class func spec() { + describe("Tag(pointer)") { + it("should set its properties") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! + + let tag = repo.withGitObject(oid) { Tag($0) } + let tagger = repo.withGitObject(oid) { Signature(git_tag_tagger($0).pointee) } + + expect(tag.oid).to(equal(oid)) + expect(tag.target).to(equal(Pointer.commit(OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")!))) + expect(tag.name).to(equal("tag-1")) + expect(tag.tagger).to(equal(tagger)) + expect(tag.message).to(equal("tag-1\n")) + } + } + + describe("==(Tag, Tag)") { + it("should be true with equal objects") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! + + let tag1 = repo.withGitObject(oid) { Tag($0) } + let tag2 = tag1 + expect(tag1).to(equal(tag2)) + } + + it("should be false with unequal objects") { + let repo = Fixtures.simpleRepository + let oid1 = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! + let oid2 = OID(string: "13bda91157f255ab224ff88d0a11a82041c9d0c1")! + + let tag1 = repo.withGitObject(oid1) { Tag($0) } + let tag2 = repo.withGitObject(oid2) { Tag($0) } + expect(tag1).notTo(equal(tag2)) + } + } + + describe("Tag.hashValue") { + it("should be equal with equal objects") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! + + let tag1 = repo.withGitObject(oid) { Tag($0) } + let tag2 = tag1 + expect(tag1.hashValue).to(equal(tag2.hashValue)) + } + } + } } diff --git a/Tests/SwiftGit2Tests/ReferencesSpec.swift b/Tests/SwiftGit2Tests/ReferencesSpec.swift index a0529292..492d0412 100644 --- a/Tests/SwiftGit2Tests/ReferencesSpec.swift +++ b/Tests/SwiftGit2Tests/ReferencesSpec.swift @@ -6,169 +6,169 @@ // Copyright (c) 2015 GitHub, Inc. All rights reserved. // -import SwiftGit2 +import libgit2 import Nimble import Quick -import libgit2 +@testable import SwiftGit2 private extension Repository { - func withGitReference(named name: String, transform: (OpaquePointer) -> T) -> T { - let repository = self.pointer + func withGitReference(named name: String, transform: (OpaquePointer) -> T) -> T { + let repository = self.pointer - var pointer: OpaquePointer? = nil - git_reference_lookup(&pointer, repository, name) - let result = transform(pointer!) - git_reference_free(pointer) + var pointer: OpaquePointer? = nil + git_reference_lookup(&pointer, repository, name) + let result = transform(pointer!) + git_reference_free(pointer) - return result - } + return result + } } class ReferenceSpec: FixturesSpec { - override class func spec() { - describe("Reference(pointer)") { - it("should initialize its properties") { - let repo = Fixtures.simpleRepository - let ref = repo.withGitReference(named: "refs/heads/master") { Reference($0) } - expect(ref.longName).to(equal("refs/heads/master")) - expect(ref.shortName).to(equal("master")) - expect(ref.oid).to(equal(OID(string: "c4ed03a6b7d7ce837d31d83757febbe84dd465fd")!)) - } - } - - describe("==(Reference, Reference)") { - it("should be true with equal references") { - let repo = Fixtures.simpleRepository - let ref1 = repo.withGitReference(named: "refs/heads/master") { Reference($0) } - let ref2 = repo.withGitReference(named: "refs/heads/master") { Reference($0) } - expect(ref1).to(equal(ref2)) - } - - it("should be false with unequal references") { - let repo = Fixtures.simpleRepository - let ref1 = repo.withGitReference(named: "refs/heads/master") { Reference($0) } - let ref2 = repo.withGitReference(named: "refs/heads/another-branch") { Reference($0) } - expect(ref1).notTo(equal(ref2)) - } - } - - describe("Reference.hashValue") { - it("should be equal with equal references") { - let repo = Fixtures.simpleRepository - let ref1 = repo.withGitReference(named: "refs/heads/master") { Reference($0) } - let ref2 = repo.withGitReference(named: "refs/heads/master") { Reference($0) } - expect(ref1.hashValue).to(equal(ref2.hashValue)) - } - } - } + override class func spec() { + describe("Reference(pointer)") { + it("should initialize its properties") { + let repo = Fixtures.simpleRepository + let ref = repo.withGitReference(named: "refs/heads/master") { Reference($0) } + expect(ref.longName).to(equal("refs/heads/master")) + expect(ref.shortName).to(equal("master")) + expect(ref.oid).to(equal(OID(string: "c4ed03a6b7d7ce837d31d83757febbe84dd465fd")!)) + } + } + + describe("==(Reference, Reference)") { + it("should be true with equal references") { + let repo = Fixtures.simpleRepository + let ref1 = repo.withGitReference(named: "refs/heads/master") { Reference($0) } + let ref2 = repo.withGitReference(named: "refs/heads/master") { Reference($0) } + expect(ref1).to(equal(ref2)) + } + + it("should be false with unequal references") { + let repo = Fixtures.simpleRepository + let ref1 = repo.withGitReference(named: "refs/heads/master") { Reference($0) } + let ref2 = repo.withGitReference(named: "refs/heads/another-branch") { Reference($0) } + expect(ref1).notTo(equal(ref2)) + } + } + + describe("Reference.hashValue") { + it("should be equal with equal references") { + let repo = Fixtures.simpleRepository + let ref1 = repo.withGitReference(named: "refs/heads/master") { Reference($0) } + let ref2 = repo.withGitReference(named: "refs/heads/master") { Reference($0) } + expect(ref1.hashValue).to(equal(ref2.hashValue)) + } + } + } } class BranchSpec: QuickSpec { - override class func spec() { - describe("Branch(pointer)") { - it("should initialize its properties") { - let repo = Fixtures.mantleRepository - let branch = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } - expect(branch.longName).to(equal("refs/heads/master")) - expect(branch.name).to(equal("master")) - expect(branch.shortName).to(equal(branch.name)) - expect(branch.commit.oid).to(equal(OID(string: "f797bd4837b61d37847a4833024aab268599a681")!)) - expect(branch.oid).to(equal(branch.commit.oid)) - expect(branch.isLocal).to(beTrue()) - expect(branch.isRemote).to(beFalse()) - } - - it("should work with symoblic refs") { - let repo = Fixtures.mantleRepository - let branch = repo.withGitReference(named: "refs/remotes/origin/HEAD") { Branch($0)! } - expect(branch.longName).to(equal("refs/remotes/origin/HEAD")) - expect(branch.name).to(equal("origin/HEAD")) - expect(branch.shortName).to(equal(branch.name)) - expect(branch.commit.oid).to(equal(OID(string: "f797bd4837b61d37847a4833024aab268599a681")!)) - expect(branch.oid).to(equal(branch.commit.oid)) - expect(branch.isLocal).to(beFalse()) - expect(branch.isRemote).to(beTrue()) - } - } - - describe("==(Branch, Branch)") { - it("should be true with equal branches") { - let repo = Fixtures.simpleRepository - let branch1 = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } - let branch2 = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } - expect(branch1).to(equal(branch2)) - } - - it("should be false with unequal branches") { - let repo = Fixtures.simpleRepository - let branch1 = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } - let branch2 = repo.withGitReference(named: "refs/heads/another-branch") { Branch($0)! } - expect(branch1).notTo(equal(branch2)) - } - } - - describe("Branch.hashValue") { - it("should be equal with equal references") { - let repo = Fixtures.simpleRepository - let branch1 = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } - let branch2 = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } - expect(branch1.hashValue).to(equal(branch2.hashValue)) - } - } - } + override class func spec() { + describe("Branch(pointer)") { + it("should initialize its properties") { + let repo = Fixtures.mantleRepository + let branch = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } + expect(branch.longName).to(equal("refs/heads/master")) + expect(branch.name).to(equal("master")) + expect(branch.shortName).to(equal(branch.name)) + expect(branch.commit.oid).to(equal(OID(string: "f797bd4837b61d37847a4833024aab268599a681")!)) + expect(branch.oid).to(equal(branch.commit.oid)) + expect(branch.isLocal).to(beTrue()) + expect(branch.isRemote).to(beFalse()) + } + + it("should work with symoblic refs") { + let repo = Fixtures.mantleRepository + let branch = repo.withGitReference(named: "refs/remotes/origin/HEAD") { Branch($0)! } + expect(branch.longName).to(equal("refs/remotes/origin/HEAD")) + expect(branch.name).to(equal("origin/HEAD")) + expect(branch.shortName).to(equal(branch.name)) + expect(branch.commit.oid).to(equal(OID(string: "f797bd4837b61d37847a4833024aab268599a681")!)) + expect(branch.oid).to(equal(branch.commit.oid)) + expect(branch.isLocal).to(beFalse()) + expect(branch.isRemote).to(beTrue()) + } + } + + describe("==(Branch, Branch)") { + it("should be true with equal branches") { + let repo = Fixtures.simpleRepository + let branch1 = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } + let branch2 = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } + expect(branch1).to(equal(branch2)) + } + + it("should be false with unequal branches") { + let repo = Fixtures.simpleRepository + let branch1 = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } + let branch2 = repo.withGitReference(named: "refs/heads/another-branch") { Branch($0)! } + expect(branch1).notTo(equal(branch2)) + } + } + + describe("Branch.hashValue") { + it("should be equal with equal references") { + let repo = Fixtures.simpleRepository + let branch1 = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } + let branch2 = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } + expect(branch1.hashValue).to(equal(branch2.hashValue)) + } + } + } } class TagReferenceSpec: QuickSpec { - override class func spec() { - describe("TagReference(pointer)") { - it("should work with an annotated tag") { - let repo = Fixtures.simpleRepository - let tag = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } - expect(tag.longName).to(equal("refs/tags/tag-2")) - expect(tag.name).to(equal("tag-2")) - expect(tag.shortName).to(equal(tag.name)) - expect(tag.oid).to(equal(OID(string: "24e1e40ee77525d9e279f079f9906ad6d98c8940")!)) - } - - it("should work with a lightweight tag") { - let repo = Fixtures.mantleRepository - let tag = repo.withGitReference(named: "refs/tags/1.5.4") { TagReference($0)! } - expect(tag.longName).to(equal("refs/tags/1.5.4")) - expect(tag.name).to(equal("1.5.4")) - expect(tag.shortName).to(equal(tag.name)) - expect(tag.oid).to(equal(OID(string: "d9dc95002cfbf3929d2b70d2c8a77e6bf5b1b88a")!)) - } - - it("should return nil if not a tag") { - let repo = Fixtures.simpleRepository - let tag = repo.withGitReference(named: "refs/heads/master") { TagReference($0) } - expect(tag).to(beNil()) - } - } - - describe("==(TagReference, TagReference)") { - it("should be true with equal tag references") { - let repo = Fixtures.simpleRepository - let tag1 = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } - let tag2 = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } - expect(tag1).to(equal(tag2)) - } - - it("should be false with unequal tag references") { - let repo = Fixtures.simpleRepository - let tag1 = repo.withGitReference(named: "refs/tags/tag-1") { TagReference($0)! } - let tag2 = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } - expect(tag1).notTo(equal(tag2)) - } - } - - describe("TagReference.hashValue") { - it("should be equal with equal references") { - let repo = Fixtures.simpleRepository - let tag1 = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } - let tag2 = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } - expect(tag1.hashValue).to(equal(tag2.hashValue)) - } - } - } + override class func spec() { + describe("TagReference(pointer)") { + it("should work with an annotated tag") { + let repo = Fixtures.simpleRepository + let tag = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } + expect(tag.longName).to(equal("refs/tags/tag-2")) + expect(tag.name).to(equal("tag-2")) + expect(tag.shortName).to(equal(tag.name)) + expect(tag.oid).to(equal(OID(string: "24e1e40ee77525d9e279f079f9906ad6d98c8940")!)) + } + + it("should work with a lightweight tag") { + let repo = Fixtures.mantleRepository + let tag = repo.withGitReference(named: "refs/tags/1.5.4") { TagReference($0)! } + expect(tag.longName).to(equal("refs/tags/1.5.4")) + expect(tag.name).to(equal("1.5.4")) + expect(tag.shortName).to(equal(tag.name)) + expect(tag.oid).to(equal(OID(string: "d9dc95002cfbf3929d2b70d2c8a77e6bf5b1b88a")!)) + } + + it("should return nil if not a tag") { + let repo = Fixtures.simpleRepository + let tag = repo.withGitReference(named: "refs/heads/master") { TagReference($0) } + expect(tag).to(beNil()) + } + } + + describe("==(TagReference, TagReference)") { + it("should be true with equal tag references") { + let repo = Fixtures.simpleRepository + let tag1 = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } + let tag2 = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } + expect(tag1).to(equal(tag2)) + } + + it("should be false with unequal tag references") { + let repo = Fixtures.simpleRepository + let tag1 = repo.withGitReference(named: "refs/tags/tag-1") { TagReference($0)! } + let tag2 = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } + expect(tag1).notTo(equal(tag2)) + } + } + + describe("TagReference.hashValue") { + it("should be equal with equal references") { + let repo = Fixtures.simpleRepository + let tag1 = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } + let tag2 = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } + expect(tag1.hashValue).to(equal(tag2.hashValue)) + } + } + } } diff --git a/Tests/SwiftGit2Tests/RemotesSpec.swift b/Tests/SwiftGit2Tests/RemotesSpec.swift index d29bd129..3493bbea 100644 --- a/Tests/SwiftGit2Tests/RemotesSpec.swift +++ b/Tests/SwiftGit2Tests/RemotesSpec.swift @@ -6,59 +6,59 @@ // Copyright (c) 2015 GitHub, Inc. All rights reserved. // -import SwiftGit2 +import libgit2 import Nimble import Quick -import libgit2 +@testable import SwiftGit2 private extension Repository { - func withGitRemote(named name: String, transform: (OpaquePointer) -> T) -> T { - let repository = self.pointer + func withGitRemote(named name: String, transform: (OpaquePointer) -> T) -> T { + let repository = self.pointer - var pointer: OpaquePointer? = nil - git_remote_lookup(&pointer, repository, name) - let result = transform(pointer!) - git_remote_free(pointer) + var pointer: OpaquePointer? = nil + git_remote_lookup(&pointer, repository, name) + let result = transform(pointer!) + git_remote_free(pointer) - return result - } + return result + } } class RemoteSpec: FixturesSpec { - override class func spec() { - describe("Remote(pointer)") { - it("should initialize its properties") { - let repo = Fixtures.mantleRepository - let remote = repo.withGitRemote(named: "upstream") { Remote($0) } + override class func spec() { + describe("Remote(pointer)") { + it("should initialize its properties") { + let repo = Fixtures.mantleRepository + let remote = repo.withGitRemote(named: "upstream") { Remote($0) } - expect(remote.name).to(equal("upstream")) - expect(remote.URL).to(equal("git@github.com:Mantle/Mantle.git")) - } - } + expect(remote.name).to(equal("upstream")) + expect(remote.URL).to(equal("git@github.com:Mantle/Mantle.git")) + } + } - describe("==(Remote, Remote)") { - it("should be true with equal objects") { - let repo = Fixtures.mantleRepository - let remote1 = repo.withGitRemote(named: "upstream") { Remote($0) } - let remote2 = remote1 - expect(remote1).to(equal(remote2)) - } + describe("==(Remote, Remote)") { + it("should be true with equal objects") { + let repo = Fixtures.mantleRepository + let remote1 = repo.withGitRemote(named: "upstream") { Remote($0) } + let remote2 = remote1 + expect(remote1).to(equal(remote2)) + } - it("should be false with unequal objcets") { - let repo = Fixtures.mantleRepository - let origin = repo.withGitRemote(named: "origin") { Remote($0) } - let upstream = repo.withGitRemote(named: "upstream") { Remote($0) } - expect(origin).notTo(equal(upstream)) - } - } + it("should be false with unequal objcets") { + let repo = Fixtures.mantleRepository + let origin = repo.withGitRemote(named: "origin") { Remote($0) } + let upstream = repo.withGitRemote(named: "upstream") { Remote($0) } + expect(origin).notTo(equal(upstream)) + } + } - describe("Remote.hashValue") { - it("should be equal with equal objcets") { - let repo = Fixtures.mantleRepository - let remote1 = repo.withGitRemote(named: "upstream") { Remote($0) } - let remote2 = remote1 - expect(remote1.hashValue).to(equal(remote2.hashValue)) - } - } - } + describe("Remote.hashValue") { + it("should be equal with equal objcets") { + let repo = Fixtures.mantleRepository + let remote1 = repo.withGitRemote(named: "upstream") { Remote($0) } + let remote2 = remote1 + expect(remote1.hashValue).to(equal(remote2.hashValue)) + } + } + } } diff --git a/Tests/SwiftGit2Tests/RepositorySpec.swift b/Tests/SwiftGit2Tests/RepositorySpec.swift index 401e4ad9..6ef3f337 100644 --- a/Tests/SwiftGit2Tests/RepositorySpec.swift +++ b/Tests/SwiftGit2Tests/RepositorySpec.swift @@ -7,983 +7,984 @@ // import Foundation -import SwiftGit2 import Nimble import Quick +@testable import SwiftGit2 // swiftlint:disable cyclomatic_complexity class RepositorySpec: FixturesSpec { - override class func spec() { - describe("Repository.Type.at(_:)") { - it("should work if the repo exists") { - let repo = Fixtures.simpleRepository - expect(repo.directoryURL).notTo(beNil()) - } - - it("should fail if the repo doesn't exist") { - let url = URL(fileURLWithPath: "blah") - let result = Repository.at(url) - expect(result.error?.domain) == libGit2ErrorDomain - expect(result.error?.localizedDescription).to(match("failed to resolve path")) - } - } - - describe("Repository.Type.isValid(url:)") { - it("should return true if the repo exists") { - guard let repositoryURL = Fixtures.simpleRepository.directoryURL else { - fail("Fixture setup broken: Repository does not exist"); return - } - - let result = Repository.isValid(url: repositoryURL) - - expect(result.error).to(beNil()) - - if case .success(let isValid) = result { - expect(isValid).to(beTruthy()) - } - } - - it("should return false if the directory does not contain a repo") { - let tmpURL = URL(fileURLWithPath: "/dev/null") - let result = Repository.isValid(url: tmpURL) - - expect(result.error).to(beNil()) - - if case .success(let isValid) = result { - expect(isValid).to(beFalsy()) - } - } - - it("should return error if .git is not readable") { - let localURL = self.temporaryURL(forPurpose: "git-isValid-unreadable").appendingPathComponent(".git") - let nonReadablePermissions: [FileAttributeKey: Any] = [.posixPermissions: 0o077] - try! FileManager.default.createDirectory( - at: localURL, - withIntermediateDirectories: true, - attributes: nonReadablePermissions) - let result = Repository.isValid(url: localURL) - - expect(result.value).to(beNil()) - expect(result.error).notTo(beNil()) - } - } - - describe("Repository.Type.create(at:)") { - it("should create a new repo at the specified location") { - let localURL = self.temporaryURL(forPurpose: "local-create") - let result = Repository.create(at: localURL) - - expect(result.error).to(beNil()) - - if case .success(let clonedRepo) = result { - expect(clonedRepo.directoryURL).notTo(beNil()) - } - } - } - - describe("Repository.Type.clone(from:to:)") { - it("should handle local clones") { - let remoteRepo = Fixtures.simpleRepository - let localURL = self.temporaryURL(forPurpose: "local-clone") - let result = Repository.clone(from: remoteRepo.directoryURL!, to: localURL, localClone: true) - - expect(result.error).to(beNil()) - - if case .success(let clonedRepo) = result { - expect(clonedRepo.directoryURL).notTo(beNil()) - } - } - - it("should handle bare clones") { - let remoteRepo = Fixtures.simpleRepository - let localURL = self.temporaryURL(forPurpose: "bare-clone") - let result = Repository.clone(from: remoteRepo.directoryURL!, to: localURL, localClone: true, bare: true) - - expect(result.error).to(beNil()) - - if case .success(let clonedRepo) = result { - expect(clonedRepo.directoryURL).to(beNil()) - } - } - - it("should have set a valid remote url") { - let remoteRepo = Fixtures.simpleRepository - let localURL = self.temporaryURL(forPurpose: "valid-remote-clone") - let cloneResult = Repository.clone(from: remoteRepo.directoryURL!, to: localURL, localClone: true) - - expect(cloneResult.error).to(beNil()) - - if case .success(let clonedRepo) = cloneResult { - let remoteResult = clonedRepo.remote(named: "origin") - expect(remoteResult.error).to(beNil()) - - if case .success(let remote) = remoteResult { - expect(remote.URL).to(equal(remoteRepo.directoryURL?.absoluteString)) - } - } - } - - it("should be able to clone a remote repository") { - let remoteRepoURL = URL(string: "https://github.com/libgit2/libgit2.github.com.git") - let localURL = self.temporaryURL(forPurpose: "public-remote-clone") - let cloneResult = Repository.clone(from: remoteRepoURL!, to: localURL) - - expect(cloneResult.error).to(beNil()) - - if case .success(let clonedRepo) = cloneResult { - let remoteResult = clonedRepo.remote(named: "origin") - expect(remoteResult.error).to(beNil()) - - if case .success(let remote) = remoteResult { - expect(remote.URL).to(equal(remoteRepoURL?.absoluteString)) - } - } - } - - let env = ProcessInfo.processInfo.environment - - if let privateRepo = env["SG2TestPrivateRepo"], - let gitUsername = env["SG2TestUsername"], - let publicKey = env["SG2TestPublicKey"], - let privateKey = env["SG2TestPrivateKey"], - let passphrase = env["SG2TestPassphrase"] { - - it("should be able to clone a remote repository requiring credentials") { - let remoteRepoURL = URL(string: privateRepo) - let localURL = self.temporaryURL(forPurpose: "private-remote-clone") - let credentials = Credentials.sshMemory(username: gitUsername, - publicKey: publicKey, - privateKey: privateKey, - passphrase: passphrase) - - let cloneResult = Repository.clone(from: remoteRepoURL!, to: localURL, credentials: credentials) - - expect(cloneResult.error).to(beNil()) - - if case .success(let clonedRepo) = cloneResult { - let remoteResult = clonedRepo.remote(named: "origin") - expect(remoteResult.error).to(beNil()) - - if case .success(let remote) = remoteResult { - expect(remote.URL).to(equal(remoteRepoURL?.absoluteString)) - } - } - } - } - } - - describe("Repository.blob(_:)") { - it("should return the commit if it exists") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! - - let result = repo.blob(oid) - expect(result.map { $0.oid }.value) == oid - } - - it("should error if the blob doesn't exist") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")! - - let result = repo.blob(oid) - expect(result.error?.domain) == libGit2ErrorDomain - } - - it("should error if the oid doesn't point to a blob") { - let repo = Fixtures.simpleRepository - // This is a tree in the repository - let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! - - let result = repo.blob(oid) - expect(result.error).notTo(beNil()) - } - } - - describe("Repository.commit(_:)") { - it("should return the commit if it exists") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! - - let result = repo.commit(oid) - expect(result.map { $0.oid }.value) == oid - } - - it("should error if the commit doesn't exist") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")! - - let result = repo.commit(oid) - expect(result.error?.domain) == libGit2ErrorDomain - } - - it("should error if the oid doesn't point to a commit") { - let repo = Fixtures.simpleRepository - // This is a tree in the repository - let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! - - let result = repo.commit(oid) - expect(result.error?.domain) == libGit2ErrorDomain - } - } - - describe("Repository.tag(_:)") { - it("should return the tag if it exists") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! - - let result = repo.tag(oid) - expect(result.map { $0.oid }.value) == oid - } - - it("should error if the tag doesn't exist") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")! - - let result = repo.tag(oid) - expect(result.error?.domain) == libGit2ErrorDomain - } - - it("should error if the oid doesn't point to a tag") { - let repo = Fixtures.simpleRepository - // This is a commit in the repository - let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! - - let result = repo.tag(oid) - expect(result.error?.domain) == libGit2ErrorDomain - } - } - - describe("Repository.tree(_:)") { - it("should return the tree if it exists") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! - - let result = repo.tree(oid) - expect(result.map { $0.oid }.value) == oid - } - - it("should error if the tree doesn't exist") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")! - - let result = repo.tree(oid) - expect(result.error?.domain) == libGit2ErrorDomain - } - - it("should error if the oid doesn't point to a tree") { - let repo = Fixtures.simpleRepository - // This is a commit in the repository - let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! - - let result = repo.tree(oid) - expect(result.error?.domain) == libGit2ErrorDomain - } - } - - describe("Repository.object(_:)") { - it("should work with a blob") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! - let blob = repo.blob(oid).value - let result = repo.object(oid) - expect(result.map { $0 as! Blob }.value) == blob - } - - it("should work with a commit") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! - let commit = repo.commit(oid).value - let result = repo.object(oid) - expect(result.map { $0 as! Commit }.value) == commit - } - - it("should work with a tag") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! - let tag = repo.tag(oid).value - let result = repo.object(oid) - expect(result.map { $0 as! Tag }.value) == tag - } - - it("should work with a tree") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! - let tree = repo.tree(oid).value - let result = repo.object(oid) - expect(result.map { $0 as! Tree }.value) == tree - } - - it("should error if there's no object with that oid") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")! - let result = repo.object(oid) - expect(result.error?.domain) == libGit2ErrorDomain - } - } - - describe("Repository.object(from: PointerTo)") { - it("should work with commits") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! - - let pointer = PointerTo(oid) - let commit = repo.commit(oid).value! - expect(repo.object(from: pointer).value) == commit - } - - it("should work with trees") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! - - let pointer = PointerTo(oid) - let tree = repo.tree(oid).value! - expect(repo.object(from: pointer).value) == tree - } - - it("should work with blobs") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! - - let pointer = PointerTo(oid) - let blob = repo.blob(oid).value! - expect(repo.object(from: pointer).value) == blob - } - - it("should work with tags") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! - - let pointer = PointerTo(oid) - let tag = repo.tag(oid).value! - expect(repo.object(from: pointer).value) == tag - } - } - - describe("Repository.object(from: Pointer)") { - it("should work with commits") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! - - let pointer = Pointer.commit(oid) - let commit = repo.commit(oid).value! - let result = repo.object(from: pointer).map { $0 as! Commit } - expect(result.value) == commit - } - - it("should work with trees") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! - - let pointer = Pointer.tree(oid) - let tree = repo.tree(oid).value! - let result = repo.object(from: pointer).map { $0 as! Tree } - expect(result.value) == tree - } - - it("should work with blobs") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! - - let pointer = Pointer.blob(oid) - let blob = repo.blob(oid).value! - let result = repo.object(from: pointer).map { $0 as! Blob } - expect(result.value) == blob - } - - it("should work with tags") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! - - let pointer = Pointer.tag(oid) - let tag = repo.tag(oid).value! - let result = repo.object(from: pointer).map { $0 as! Tag } - expect(result.value) == tag - } - } - - describe("Repository.allRemotes()") { - it("should return an empty list if there are no remotes") { - let repo = Fixtures.simpleRepository - let result = repo.allRemotes() - expect(result.value) == [] - } - - it("should return all the remotes") { - let repo = Fixtures.mantleRepository - let remotes = repo.allRemotes() - let names = remotes.map { $0.map { $0.name } } - expect(remotes.map { $0.count }.value) == 2 - expect(names.value).to(contain("origin", "upstream")) - } - } - - describe("Repository.remote(named:)") { - it("should return the remote if it exists") { - let repo = Fixtures.mantleRepository - let result = repo.remote(named: "upstream") - expect(result.map { $0.name }.value) == "upstream" - } - - it("should error if the remote doesn't exist") { - let repo = Fixtures.simpleRepository - let result = repo.remote(named: "nonexistent") - expect(result.error?.domain) == libGit2ErrorDomain - } - } - - describe("Repository.reference(named:)") { - it("should return a local branch if it exists") { - let name = "refs/heads/master" - let result = Fixtures.simpleRepository.reference(named: name) - expect(result.map { $0.longName }.value) == name - expect(result.value as? Branch).notTo(beNil()) - } - - it("should return a remote branch if it exists") { - let name = "refs/remotes/upstream/master" - let result = Fixtures.mantleRepository.reference(named: name) - expect(result.map { $0.longName }.value) == name - expect(result.value as? Branch).notTo(beNil()) - } - - it("should return a tag if it exists") { - let name = "refs/tags/tag-2" - let result = Fixtures.simpleRepository.reference(named: name) - expect(result.value?.longName).to(equal(name)) - expect(result.value as? TagReference).notTo(beNil()) - } - - it("should return the reference if it exists") { - let name = "refs/other-ref" - let result = Fixtures.simpleRepository.reference(named: name) - expect(result.value?.longName).to(equal(name)) - } - - it("should error if the reference doesn't exist") { - let result = Fixtures.simpleRepository.reference(named: "refs/heads/nonexistent") - expect(result.error?.domain) == libGit2ErrorDomain - } - } - - describe("Repository.localBranches()") { - it("should return all the local branches") { - let repo = Fixtures.simpleRepository - let expected = [ - repo.localBranch(named: "another-branch").value!, - repo.localBranch(named: "master").value!, - repo.localBranch(named: "yet-another-branch").value!, - ] - expect(repo.localBranches().value).to(equal(expected)) - } - } - - describe("Repository.remoteBranches()") { - it("should return all the remote branches") { - let repo = Fixtures.mantleRepository - let expectedNames = [ - "origin/2.0-development", - "origin/HEAD", - "origin/bump-config", - "origin/bump-xcconfigs", - "origin/github-reversible-transformer", - "origin/master", - "origin/mtlmanagedobject", - "origin/reversible-transformer", - "origin/subclassing-notes", - "upstream/2.0-development", - "upstream/bump-config", - "upstream/bump-xcconfigs", - "upstream/github-reversible-transformer", - "upstream/master", - "upstream/mtlmanagedobject", - "upstream/reversible-transformer", - "upstream/subclassing-notes", - ] - let expected = expectedNames.map { repo.remoteBranch(named: $0).value! } - let actual = repo.remoteBranches().value!.sorted { - return $0.longName.lexicographicallyPrecedes($1.longName) - } - expect(actual).to(equal(expected)) - expect(actual.map { $0.name }).to(equal(expectedNames)) - } - } - - describe("Repository.localBranch(named:)") { - it("should return the branch if it exists") { - let result = Fixtures.simpleRepository.localBranch(named: "master") - expect(result.value?.longName).to(equal("refs/heads/master")) - } - - it("should error if the branch doesn't exists") { - let result = Fixtures.simpleRepository.localBranch(named: "nonexistent") - expect(result.error?.domain) == libGit2ErrorDomain - } - } - - describe("Repository.remoteBranch(named:)") { - it("should return the branch if it exists") { - let result = Fixtures.mantleRepository.remoteBranch(named: "upstream/master") - expect(result.value?.longName).to(equal("refs/remotes/upstream/master")) - } - - it("should error if the branch doesn't exists") { - let result = Fixtures.simpleRepository.remoteBranch(named: "origin/nonexistent") - expect(result.error?.domain) == libGit2ErrorDomain - } - } - - describe("Repository.fetch(_:)") { - it("should fetch the data") { - let repo = Fixtures.mantleRepository - let remote = repo.remote(named: "origin").value! - expect(repo.fetch(remote).value).toNot(beNil()) - } - } - - describe("Repository.allTags()") { - it("should return all the tags") { - let repo = Fixtures.simpleRepository - let expected = [ - repo.tag(named: "tag-1").value!, - repo.tag(named: "tag-2").value!, - ] - expect(repo.allTags().value).to(equal(expected)) - } - } - - describe("Repository.tag(named:)") { - it("should return the tag if it exists") { - let result = Fixtures.simpleRepository.tag(named: "tag-2") - expect(result.value?.longName).to(equal("refs/tags/tag-2")) - } - - it("should error if the branch doesn't exists") { - let result = Fixtures.simpleRepository.tag(named: "nonexistent") - expect(result.error?.domain) == libGit2ErrorDomain - } - } - - describe("Repository.HEAD()") { - it("should work when on a branch") { - let result = Fixtures.simpleRepository.HEAD() - expect(result.value?.longName).to(equal("refs/heads/master")) - expect(result.value?.shortName).to(equal("master")) - expect(result.value as? Branch).notTo(beNil()) - } - - it("should work when on a detached HEAD") { - let result = Fixtures.detachedHeadRepository.HEAD() - expect(result.value?.longName).to(equal("HEAD")) - expect(result.value?.shortName).to(beNil()) - expect(result.value?.oid).to(equal(OID(string: "315b3f344221db91ddc54b269f3c9af422da0f2e")!)) - expect(result.value as? Reference).notTo(beNil()) - } - } - - describe("Repository.setHEAD(OID)") { - it("should set HEAD to the OID") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "315b3f344221db91ddc54b269f3c9af422da0f2e")! - expect(repo.HEAD().value?.shortName).to(equal("master")) - - expect(repo.setHEAD(oid).error).to(beNil()) - let HEAD = repo.HEAD().value - expect(HEAD?.longName).to(equal("HEAD")) - expect(HEAD?.oid).to(equal(oid)) - - expect(repo.setHEAD(repo.localBranch(named: "master").value!).error).to(beNil()) - expect(repo.HEAD().value?.shortName).to(equal("master")) - } - } - - describe("Repository.setHEAD(ReferenceType)") { - it("should set HEAD to a branch") { - let repo = Fixtures.detachedHeadRepository - let oid = repo.HEAD().value!.oid - expect(repo.HEAD().value?.longName).to(equal("HEAD")) - - let branch = repo.localBranch(named: "another-branch").value! - expect(repo.setHEAD(branch).error).to(beNil()) - expect(repo.HEAD().value?.shortName).to(equal(branch.name)) - - expect(repo.setHEAD(oid).error).to(beNil()) - expect(repo.HEAD().value?.longName).to(equal("HEAD")) - } - } - - describe("Repository.checkout()") { - // We're not really equipped to test this yet. :( - } - - describe("Repository.checkout(OID)") { - it("should set HEAD") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "315b3f344221db91ddc54b269f3c9af422da0f2e")! - expect(repo.HEAD().value?.shortName).to(equal("master")) - - expect(repo.checkout(oid, strategy: CheckoutStrategy.None).error).to(beNil()) - let HEAD = repo.HEAD().value - expect(HEAD?.longName).to(equal("HEAD")) - expect(HEAD?.oid).to(equal(oid)) - - expect(repo.checkout(repo.localBranch(named: "master").value!, strategy: CheckoutStrategy.None).error).to(beNil()) - expect(repo.HEAD().value?.shortName).to(equal("master")) - } - - it("should call block on progress") { - let repo = Fixtures.simpleRepository - let oid = OID(string: "315b3f344221db91ddc54b269f3c9af422da0f2e")! - expect(repo.HEAD().value?.shortName).to(equal("master")) - - expect(repo.checkout(oid, strategy: .None, progress: { (_, completedSteps, totalSteps) -> Void in - expect(completedSteps).to(beLessThanOrEqualTo(totalSteps)) - }).error).to(beNil()) - - let HEAD = repo.HEAD().value - expect(HEAD?.longName).to(equal("HEAD")) - expect(HEAD?.oid).to(equal(oid)) - } - } - - describe("Repository.checkout(ReferenceType)") { - it("should set HEAD") { - let repo = Fixtures.detachedHeadRepository - let oid = repo.HEAD().value!.oid - expect(repo.HEAD().value?.longName).to(equal("HEAD")) - - let branch = repo.localBranch(named: "another-branch").value! - expect(repo.checkout(branch, strategy: CheckoutStrategy.None).error).to(beNil()) - expect(repo.HEAD().value?.shortName).to(equal(branch.name)) - - expect(repo.checkout(oid, strategy: CheckoutStrategy.None).error).to(beNil()) - expect(repo.HEAD().value?.longName).to(equal("HEAD")) - } - } - - describe("Repository.allCommits(in:)") { - it("should return all (9) commits") { - let repo = Fixtures.simpleRepository - let branches = repo.localBranches().value! - let expectedCount = 9 - let expectedMessages: [String] = [ - "List branches in README\n", - "Create a README\n", - "Merge branch 'alphabetize'\n", - "Alphabetize branches\n", - "List new branches\n", - "List branches in README\n", - "Create a README\n", - "List branches in README\n", - "Create a README\n", - ] - var commitMessages: [String] = [] - for branch in branches { - for commit in repo.commits(in: branch) { - commitMessages.append(commit.value!.message) - } - } - expect(commitMessages.count).to(equal(expectedCount)) - expect(commitMessages).to(equal(expectedMessages)) - } - } - - describe("Repository.add") { - it("Should add the modification under a path") { - 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 readmeURL = repo.directoryURL!.appendingPathComponent("README.md") - let readmeData = try! Data(contentsOf: readmeURL) - defer { try! readmeData.write(to: readmeURL) } - - try! "different".data(using: .utf8)?.write(to: readmeURL) - - let status = repo.status() - expect(status.value?.count).to(equal(1)) - expect(status.value!.first!.status).to(equal(.workTreeModified)) - - expect(repo.add(path: "README.md").error).to(beNil()) - - let newStatus = repo.status() - expect(newStatus.value?.count).to(equal(1)) - expect(newStatus.value!.first!.status).to(equal(.indexModified)) - } - - it("Should add an untracked file under a path") { - 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("untracked") - try! "different".data(using: .utf8)?.write(to: untrackedURL) - defer { try! FileManager.default.removeItem(at: untrackedURL) } - - expect(repo.add(path: ".").error).to(beNil()) - - let newStatus = repo.status() - expect(newStatus.value?.count).to(equal(1)) - expect(newStatus.value!.first!.status).to(equal(.indexNew)) - } - } - - 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: "foobar@example.com", - 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 - - 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 = 6 - 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 expectedUntrackedFiles = [ - "unstaged-file", - ] - - 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 { - if let path = status.headToIndex?.newFile?.path { - newFilePaths.append(path) - } - } - var oldFilePaths: [String] = [] - for status in statuses { - if let path = status.headToIndex?.oldFile?.path { - oldFilePaths.append(path) - } - } - - var newUntrackedFilePaths: [String] = [] - for status in statuses { - if let path = status.indexToWorkDir?.newFile?.path { - newUntrackedFilePaths.append(path) - } - } - - expect(statuses.count).to(equal(expectedCount)) - expect(newFilePaths).to(equal(expectedNewFilePaths)) - expect(oldFilePaths).to(equal(expectedOldFilePaths)) - expect(newUntrackedFilePaths).to(equal(expectedUntrackedFiles)) - } - } - - 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)) - } - } - } - - static func temporaryURL(forPurpose purpose: String) -> URL { - let globallyUniqueString = ProcessInfo.processInfo.globallyUniqueString - let path = "\(NSTemporaryDirectory())\(globallyUniqueString)_\(purpose)" - return URL(fileURLWithPath: path) - } + override class func spec() { + describe("Repository.Type.at(_:)") { + it("should work if the repo exists") { + let repo = Fixtures.simpleRepository + expect(repo.directoryURL).notTo(beNil()) + } + + it("should fail if the repo doesn't exist") { + let url = URL(fileURLWithPath: "blah") + let result = Repository.at(url) + expect(result.error?.domain) == libGit2ErrorDomain + expect(result.error?.localizedDescription).to(match("failed to resolve path")) + } + } + + describe("Repository.Type.isValid(url:)") { + it("should return true if the repo exists") { + guard let repositoryURL = Fixtures.simpleRepository.directoryURL else { + fail("Fixture setup broken: Repository does not exist"); return + } + + let result = Repository.isValid(url: repositoryURL) + + expect(result.error).to(beNil()) + + if case let .success(isValid) = result { + expect(isValid).to(beTruthy()) + } + } + + it("should return false if the directory does not contain a repo") { + let tmpURL = URL(fileURLWithPath: "/dev/null") + let result = Repository.isValid(url: tmpURL) + + expect(result.error).to(beNil()) + + if case let .success(isValid) = result { + expect(isValid).to(beFalsy()) + } + } + + it("should return error if .git is not readable") { + let localURL = self.temporaryURL(forPurpose: "git-isValid-unreadable").appendingPathComponent(".git") + let nonReadablePermissions: [FileAttributeKey: Any] = [.posixPermissions: 0o077] + try! FileManager.default.createDirectory( + at: localURL, + withIntermediateDirectories: true, + attributes: nonReadablePermissions + ) + let result = Repository.isValid(url: localURL) + + expect(result.value).to(beNil()) + expect(result.error).notTo(beNil()) + } + } + + describe("Repository.Type.create(at:)") { + it("should create a new repo at the specified location") { + let localURL = self.temporaryURL(forPurpose: "local-create") + let result = Repository.create(at: localURL) + + expect(result.error).to(beNil()) + + if case let .success(clonedRepo) = result { + expect(clonedRepo.directoryURL).notTo(beNil()) + } + } + } + + describe("Repository.Type.clone(from:to:)") { + it("should handle local clones") { + let remoteRepo = Fixtures.simpleRepository + let localURL = self.temporaryURL(forPurpose: "local-clone") + let result = Repository.clone(from: remoteRepo.directoryURL!, to: localURL, localClone: true) + + expect(result.error).to(beNil()) + + if case let .success(clonedRepo) = result { + expect(clonedRepo.directoryURL).notTo(beNil()) + } + } + + it("should handle bare clones") { + let remoteRepo = Fixtures.simpleRepository + let localURL = self.temporaryURL(forPurpose: "bare-clone") + let result = Repository.clone(from: remoteRepo.directoryURL!, to: localURL, localClone: true, bare: true) + + expect(result.error).to(beNil()) + + if case let .success(clonedRepo) = result { + expect(clonedRepo.directoryURL).to(beNil()) + } + } + + it("should have set a valid remote url") { + let remoteRepo = Fixtures.simpleRepository + let localURL = self.temporaryURL(forPurpose: "valid-remote-clone") + let cloneResult = Repository.clone(from: remoteRepo.directoryURL!, to: localURL, localClone: true) + + expect(cloneResult.error).to(beNil()) + + if case let .success(clonedRepo) = cloneResult { + let remoteResult = clonedRepo.remote(named: "origin") + expect(remoteResult.error).to(beNil()) + + if case let .success(remote) = remoteResult { + expect(remote.URL).to(equal(remoteRepo.directoryURL?.absoluteString)) + } + } + } + + it("should be able to clone a remote repository") { + let remoteRepoURL = URL(string: "https://github.com/libgit2/libgit2.github.com.git") + let localURL = self.temporaryURL(forPurpose: "public-remote-clone") + let cloneResult = Repository.clone(from: remoteRepoURL!, to: localURL) + + expect(cloneResult.error).to(beNil()) + + if case let .success(clonedRepo) = cloneResult { + let remoteResult = clonedRepo.remote(named: "origin") + expect(remoteResult.error).to(beNil()) + + if case let .success(remote) = remoteResult { + expect(remote.URL).to(equal(remoteRepoURL?.absoluteString)) + } + } + } + + let env = ProcessInfo.processInfo.environment + + if let privateRepo = env["SG2TestPrivateRepo"], + let gitUsername = env["SG2TestUsername"], + let publicKey = env["SG2TestPublicKey"], + let privateKey = env["SG2TestPrivateKey"], + let passphrase = env["SG2TestPassphrase"] + { + it("should be able to clone a remote repository requiring credentials") { + let remoteRepoURL = URL(string: privateRepo) + let localURL = self.temporaryURL(forPurpose: "private-remote-clone") + let credentials = Credentials.sshMemory(username: gitUsername, + publicKey: publicKey, + privateKey: privateKey, + passphrase: passphrase) + + let cloneResult = Repository.clone(from: remoteRepoURL!, to: localURL, credentials: credentials) + + expect(cloneResult.error).to(beNil()) + + if case let .success(clonedRepo) = cloneResult { + let remoteResult = clonedRepo.remote(named: "origin") + expect(remoteResult.error).to(beNil()) + + if case let .success(remote) = remoteResult { + expect(remote.URL).to(equal(remoteRepoURL?.absoluteString)) + } + } + } + } + } + + describe("Repository.blob(_:)") { + it("should return the commit if it exists") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! + + let result = repo.blob(oid) + expect(result.map { $0.oid }.value) == oid + } + + it("should error if the blob doesn't exist") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")! + + let result = repo.blob(oid) + expect(result.error?.domain) == libGit2ErrorDomain + } + + it("should error if the oid doesn't point to a blob") { + let repo = Fixtures.simpleRepository + // This is a tree in the repository + let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! + + let result = repo.blob(oid) + expect(result.error).notTo(beNil()) + } + } + + describe("Repository.commit(_:)") { + it("should return the commit if it exists") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! + + let result = repo.commit(oid) + expect(result.map { $0.oid }.value) == oid + } + + it("should error if the commit doesn't exist") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")! + + let result = repo.commit(oid) + expect(result.error?.domain) == libGit2ErrorDomain + } + + it("should error if the oid doesn't point to a commit") { + let repo = Fixtures.simpleRepository + // This is a tree in the repository + let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! + + let result = repo.commit(oid) + expect(result.error?.domain) == libGit2ErrorDomain + } + } + + describe("Repository.tag(_:)") { + it("should return the tag if it exists") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! + + let result = repo.tag(oid) + expect(result.map { $0.oid }.value) == oid + } + + it("should error if the tag doesn't exist") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")! + + let result = repo.tag(oid) + expect(result.error?.domain) == libGit2ErrorDomain + } + + it("should error if the oid doesn't point to a tag") { + let repo = Fixtures.simpleRepository + // This is a commit in the repository + let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! + + let result = repo.tag(oid) + expect(result.error?.domain) == libGit2ErrorDomain + } + } + + describe("Repository.tree(_:)") { + it("should return the tree if it exists") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! + + let result = repo.tree(oid) + expect(result.map { $0.oid }.value) == oid + } + + it("should error if the tree doesn't exist") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")! + + let result = repo.tree(oid) + expect(result.error?.domain) == libGit2ErrorDomain + } + + it("should error if the oid doesn't point to a tree") { + let repo = Fixtures.simpleRepository + // This is a commit in the repository + let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! + + let result = repo.tree(oid) + expect(result.error?.domain) == libGit2ErrorDomain + } + } + + describe("Repository.object(_:)") { + it("should work with a blob") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! + let blob = repo.blob(oid).value + let result = repo.object(oid) + expect(result.map { $0 as! Blob }.value) == blob + } + + it("should work with a commit") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! + let commit = repo.commit(oid).value + let result = repo.object(oid) + expect(result.map { $0 as! Commit }.value) == commit + } + + it("should work with a tag") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! + let tag = repo.tag(oid).value + let result = repo.object(oid) + expect(result.map { $0 as! Tag }.value) == tag + } + + it("should work with a tree") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! + let tree = repo.tree(oid).value + let result = repo.object(oid) + expect(result.map { $0 as! Tree }.value) == tree + } + + it("should error if there's no object with that oid") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")! + let result = repo.object(oid) + expect(result.error?.domain) == libGit2ErrorDomain + } + } + + describe("Repository.object(from: PointerTo)") { + it("should work with commits") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! + + let pointer = PointerTo(oid) + let commit = repo.commit(oid).value! + expect(repo.object(from: pointer).value?.oid) == commit.oid + } + + it("should work with trees") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! + + let pointer = PointerTo(oid) + let tree = repo.tree(oid).value! + expect(repo.object(from: pointer).value?.oid) == tree.oid + } + + it("should work with blobs") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! + + let pointer = PointerTo(oid) + let blob = repo.blob(oid).value! + expect(repo.object(from: pointer).value?.oid) == blob.oid + } + + it("should work with tags") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! + + let pointer = PointerTo(oid) + let tag = repo.tag(oid).value! + expect(repo.object(from: pointer).value?.oid) == tag.oid + } + } + + describe("Repository.object(from: Pointer)") { + it("should work with commits") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! + + let pointer = Pointer.commit(oid) + let commit = repo.commit(oid).value! + let result = repo.object(from: pointer).map { $0 as! Commit } + expect(result.value) == commit + } + + it("should work with trees") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! + + let pointer = Pointer.tree(oid) + let tree = repo.tree(oid).value! + let result = repo.object(from: pointer).map { $0 as! Tree } + expect(result.value) == tree + } + + it("should work with blobs") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! + + let pointer = Pointer.blob(oid) + let blob = repo.blob(oid).value! + let result = repo.object(from: pointer).map { $0 as! Blob } + expect(result.value) == blob + } + + it("should work with tags") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! + + let pointer = Pointer.tag(oid) + let tag = repo.tag(oid).value! + let result = repo.object(from: pointer).map { $0 as! Tag } + expect(result.value) == tag + } + } + + describe("Repository.allRemotes()") { + it("should return an empty list if there are no remotes") { + let repo = Fixtures.simpleRepository + let result = repo.allRemotes() + expect(result.value) == [] + } + + it("should return all the remotes") { + let repo = Fixtures.mantleRepository + let remotes = repo.allRemotes() + let names = remotes.map { $0.map { $0.name } } + expect(remotes.map { $0.count }.value) == 2 + expect(names.value).to(contain("origin", "upstream")) + } + } + + describe("Repository.remote(named:)") { + it("should return the remote if it exists") { + let repo = Fixtures.mantleRepository + let result = repo.remote(named: "upstream") + expect(result.map { $0.name }.value) == "upstream" + } + + it("should error if the remote doesn't exist") { + let repo = Fixtures.simpleRepository + let result = repo.remote(named: "nonexistent") + expect(result.error?.domain) == libGit2ErrorDomain + } + } + + describe("Repository.reference(named:)") { + it("should return a local branch if it exists") { + let name = "refs/heads/master" + let result = Fixtures.simpleRepository.reference(named: name) + expect(result.map { $0.longName }.value) == name + expect(result.value as? Branch).notTo(beNil()) + } + + it("should return a remote branch if it exists") { + let name = "refs/remotes/upstream/master" + let result = Fixtures.mantleRepository.reference(named: name) + expect(result.map { $0.longName }.value) == name + expect(result.value as? Branch).notTo(beNil()) + } + + it("should return a tag if it exists") { + let name = "refs/tags/tag-2" + let result = Fixtures.simpleRepository.reference(named: name) + expect(result.value?.longName).to(equal(name)) + expect(result.value as? TagReference).notTo(beNil()) + } + + it("should return the reference if it exists") { + let name = "refs/other-ref" + let result = Fixtures.simpleRepository.reference(named: name) + expect(result.value?.longName).to(equal(name)) + } + + it("should error if the reference doesn't exist") { + let result = Fixtures.simpleRepository.reference(named: "refs/heads/nonexistent") + expect(result.error?.domain) == libGit2ErrorDomain + } + } + + describe("Repository.localBranches()") { + it("should return all the local branches") { + let repo = Fixtures.simpleRepository + let expected = [ + repo.localBranch(named: "another-branch").value!, + repo.localBranch(named: "master").value!, + repo.localBranch(named: "yet-another-branch").value!, + ] + expect(repo.localBranches().value).to(equal(expected)) + } + } + + describe("Repository.remoteBranches()") { + it("should return all the remote branches") { + let repo = Fixtures.mantleRepository + let expectedNames = [ + "origin/2.0-development", + "origin/HEAD", + "origin/bump-config", + "origin/bump-xcconfigs", + "origin/github-reversible-transformer", + "origin/master", + "origin/mtlmanagedobject", + "origin/reversible-transformer", + "origin/subclassing-notes", + "upstream/2.0-development", + "upstream/bump-config", + "upstream/bump-xcconfigs", + "upstream/github-reversible-transformer", + "upstream/master", + "upstream/mtlmanagedobject", + "upstream/reversible-transformer", + "upstream/subclassing-notes", + ] + let expected = expectedNames.map { repo.remoteBranch(named: $0).value! } + let actual = repo.remoteBranches().value!.sorted { + $0.longName.lexicographicallyPrecedes($1.longName) + } + expect(actual).to(equal(expected)) + expect(actual.map { $0.name }).to(equal(expectedNames)) + } + } + + describe("Repository.localBranch(named:)") { + it("should return the branch if it exists") { + let result = Fixtures.simpleRepository.localBranch(named: "master") + expect(result.value?.longName).to(equal("refs/heads/master")) + } + + it("should error if the branch doesn't exists") { + let result = Fixtures.simpleRepository.localBranch(named: "nonexistent") + expect(result.error?.domain) == libGit2ErrorDomain + } + } + + describe("Repository.remoteBranch(named:)") { + it("should return the branch if it exists") { + let result = Fixtures.mantleRepository.remoteBranch(named: "upstream/master") + expect(result.value?.longName).to(equal("refs/remotes/upstream/master")) + } + + it("should error if the branch doesn't exists") { + let result = Fixtures.simpleRepository.remoteBranch(named: "origin/nonexistent") + expect(result.error?.domain) == libGit2ErrorDomain + } + } + + describe("Repository.fetch(_:)") { + it("should fetch the data") { + let repo = Fixtures.mantleRepository + let remote = repo.remote(named: "origin").value! + expect(repo.fetch(remote).value).toNot(beNil()) + } + } + + describe("Repository.allTags()") { + it("should return all the tags") { + let repo = Fixtures.simpleRepository + let expected = [ + repo.tag(named: "tag-1").value!, + repo.tag(named: "tag-2").value!, + ] + expect(repo.allTags().value).to(equal(expected)) + } + } + + describe("Repository.tag(named:)") { + it("should return the tag if it exists") { + let result = Fixtures.simpleRepository.tag(named: "tag-2") + expect(result.value?.longName).to(equal("refs/tags/tag-2")) + } + + it("should error if the branch doesn't exists") { + let result = Fixtures.simpleRepository.tag(named: "nonexistent") + expect(result.error?.domain) == libGit2ErrorDomain + } + } + + describe("Repository.HEAD()") { + it("should work when on a branch") { + let result = Fixtures.simpleRepository.HEAD() + expect(result.value?.longName).to(equal("refs/heads/master")) + expect(result.value?.shortName).to(equal("master")) + expect(result.value as? Branch).notTo(beNil()) + } + + it("should work when on a detached HEAD") { + let result = Fixtures.detachedHeadRepository.HEAD() + expect(result.value?.longName).to(equal("HEAD")) + expect(result.value?.shortName).to(beNil()) + expect(result.value?.oid).to(equal(OID(string: "315b3f344221db91ddc54b269f3c9af422da0f2e")!)) + expect(result.value as? Reference).notTo(beNil()) + } + } + + describe("Repository.setHEAD(OID)") { + it("should set HEAD to the OID") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "315b3f344221db91ddc54b269f3c9af422da0f2e")! + expect(repo.HEAD().value?.shortName).to(equal("master")) + + expect(repo.setHEAD(oid).error).to(beNil()) + let HEAD = repo.HEAD().value + expect(HEAD?.longName).to(equal("HEAD")) + expect(HEAD?.oid).to(equal(oid)) + + expect(repo.setHEAD(repo.localBranch(named: "master").value!).error).to(beNil()) + expect(repo.HEAD().value?.shortName).to(equal("master")) + } + } + + describe("Repository.setHEAD(ReferenceType)") { + it("should set HEAD to a branch") { + let repo = Fixtures.detachedHeadRepository + let oid = repo.HEAD().value!.oid + expect(repo.HEAD().value?.longName).to(equal("HEAD")) + + let branch = repo.localBranch(named: "another-branch").value! + expect(repo.setHEAD(branch).error).to(beNil()) + expect(repo.HEAD().value?.shortName).to(equal(branch.name)) + + expect(repo.setHEAD(oid).error).to(beNil()) + expect(repo.HEAD().value?.longName).to(equal("HEAD")) + } + } + + describe("Repository.checkout()") { + // We're not really equipped to test this yet. :( + } + + describe("Repository.checkout(OID)") { + it("should set HEAD") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "315b3f344221db91ddc54b269f3c9af422da0f2e")! + expect(repo.HEAD().value?.shortName).to(equal("master")) + + expect(repo.checkout(oid, strategy: CheckoutStrategy.none).error).to(beNil()) + let HEAD = repo.HEAD().value + expect(HEAD?.longName).to(equal("HEAD")) + expect(HEAD?.oid).to(equal(oid)) + + expect(repo.checkout(repo.localBranch(named: "master").value!, strategy: CheckoutStrategy.none).error).to(beNil()) + expect(repo.HEAD().value?.shortName).to(equal("master")) + } + + it("should call block on progress") { + let repo = Fixtures.simpleRepository + let oid = OID(string: "315b3f344221db91ddc54b269f3c9af422da0f2e")! + expect(repo.HEAD().value?.shortName).to(equal("master")) + + expect(repo.checkout(oid, strategy: .none, progress: { _, completedSteps, totalSteps in + expect(completedSteps).to(beLessThanOrEqualTo(totalSteps)) + }).error).to(beNil()) + + let HEAD = repo.HEAD().value + expect(HEAD?.longName).to(equal("HEAD")) + expect(HEAD?.oid).to(equal(oid)) + } + } + + describe("Repository.checkout(ReferenceType)") { + it("should set HEAD") { + let repo = Fixtures.detachedHeadRepository + let oid = repo.HEAD().value!.oid + expect(repo.HEAD().value?.longName).to(equal("HEAD")) + + let branch = repo.localBranch(named: "another-branch").value! + expect(repo.checkout(branch, strategy: CheckoutStrategy.none).error).to(beNil()) + expect(repo.HEAD().value?.shortName).to(equal(branch.name)) + + expect(repo.checkout(oid, strategy: CheckoutStrategy.none).error).to(beNil()) + expect(repo.HEAD().value?.longName).to(equal("HEAD")) + } + } + + describe("Repository.allCommits(in:)") { + it("should return all (9) commits") { + let repo = Fixtures.simpleRepository + let branches = repo.localBranches().value! + let expectedCount = 9 + let expectedMessages: [String] = [ + "List branches in README\n", + "Create a README\n", + "Merge branch 'alphabetize'\n", + "Alphabetize branches\n", + "List new branches\n", + "List branches in README\n", + "Create a README\n", + "List branches in README\n", + "Create a README\n", + ] + var commitMessages: [String] = [] + for branch in branches { + for commit in repo.commits(in: branch) { + commitMessages.append(commit.value!.message) + } + } + expect(commitMessages.count).to(equal(expectedCount)) + expect(commitMessages).to(equal(expectedMessages)) + } + } + + describe("Repository.add") { + it("Should add the modification under a path") { + 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 readmeURL = repo.directoryURL!.appendingPathComponent("README.md") + let readmeData = try! Data(contentsOf: readmeURL) + defer { try! readmeData.write(to: readmeURL) } + + try! "different".data(using: .utf8)?.write(to: readmeURL) + + let status = repo.status() + expect(status.value?.count).to(equal(1)) + expect(status.value!.first!.status).to(equal(.workTreeModified)) + + expect(repo.add(path: "README.md").error).to(beNil()) + + let newStatus = repo.status() + expect(newStatus.value?.count).to(equal(1)) + expect(newStatus.value!.first!.status).to(equal(.indexModified)) + } + + it("Should add an untracked file under a path") { + 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("untracked") + try! "different".data(using: .utf8)?.write(to: untrackedURL) + defer { try! FileManager.default.removeItem(at: untrackedURL) } + + expect(repo.add(path: ".").error).to(beNil()) + + let newStatus = repo.status() + expect(newStatus.value?.count).to(equal(1)) + expect(newStatus.value!.first!.status).to(equal(.indexNew)) + } + } + + 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: "foobar@example.com", + time: Date(timeIntervalSince1970: 1_525_200_858), + 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 + + 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 = 6 + 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 expectedUntrackedFiles = [ + "unstaged-file", + ] + + 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 { + if let path = status.headToIndex?.newFile?.path { + newFilePaths.append(path) + } + } + var oldFilePaths: [String] = [] + for status in statuses { + if let path = status.headToIndex?.oldFile?.path { + oldFilePaths.append(path) + } + } + + var newUntrackedFilePaths: [String] = [] + for status in statuses { + if let path = status.indexToWorkDir?.newFile?.path { + newUntrackedFilePaths.append(path) + } + } + + expect(statuses.count).to(equal(expectedCount)) + expect(newFilePaths).to(equal(expectedNewFilePaths)) + expect(oldFilePaths).to(equal(expectedOldFilePaths)) + expect(newUntrackedFilePaths).to(equal(expectedUntrackedFiles)) + } + } + + 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)) + } + } + } + + static func temporaryURL(forPurpose purpose: String) -> URL { + let globallyUniqueString = ProcessInfo.processInfo.globallyUniqueString + let path = "\(NSTemporaryDirectory())\(globallyUniqueString)_\(purpose)" + return URL(fileURLWithPath: path) + } } diff --git a/Tests/SwiftGit2Tests/ResultShims.swift b/Tests/SwiftGit2Tests/ResultShims.swift index 4095a7a4..221c5f58 100644 --- a/Tests/SwiftGit2Tests/ResultShims.swift +++ b/Tests/SwiftGit2Tests/ResultShims.swift @@ -1,16 +1,16 @@ // Once Nimble adds matchers for the Result type, remove these shims and refactor the tests that use them. extension Result { - var value: Success? { - guard case .success(let value) = self else { - return nil - } - return value - } + var value: Success? { + guard case let .success(value) = self else { + return nil + } + return value + } - var error: Failure? { - guard case .failure(let error) = self else { - return nil - } - return error - } + var error: Failure? { + guard case let .failure(error) = self else { + return nil + } + return error + } }