diff --git a/Cartfile.private b/Cartfile.private index 2121ad9..e299b6c 100644 --- a/Cartfile.private +++ b/Cartfile.private @@ -1,5 +1,5 @@ github "jspahrsummers/xcconfigs" >= 0.7.2 -github "Quick/Quick" "v0.8.0" +github "Quick/Quick" "v0.9.1" github "Quick/Nimble" ~> 3.0.0 github "modocache/Guanaco" "5031bf67297afbe61ac0f2fbf3e3e8400b3f8888" github "ZipArchive/ZipArchive" ~> 1.1 diff --git a/Cartfile.resolved b/Cartfile.resolved index 652edea..24f5043 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,6 +1,6 @@ github "modocache/Guanaco" "5031bf67297afbe61ac0f2fbf3e3e8400b3f8888" github "Quick/Nimble" "v3.0.0" -github "Quick/Quick" "v0.8.0" +github "Quick/Quick" "v0.9.1" github "antitypical/Result" "1.0.1" github "ZipArchive/ZipArchive" "v1.1" github "jspahrsummers/xcconfigs" "0.8.1" diff --git a/Carthage/Checkouts/Quick b/Carthage/Checkouts/Quick index 46b38c9..2f03756 160000 --- a/Carthage/Checkouts/Quick +++ b/Carthage/Checkouts/Quick @@ -1 +1 @@ -Subproject commit 46b38c9c06b068baede09586aa281a6f075b2494 +Subproject commit 2f037560be197f0f5ae992512549bc29fabb3818 diff --git a/External/libgit2 b/External/libgit2 index 7c63a33..e8feafe 160000 --- a/External/libgit2 +++ b/External/libgit2 @@ -1 +1 @@ -Subproject commit 7c63a33ffe1198b77b481974cd0e74e9ace1745c +Subproject commit e8feafe32007ebd16a61820c70abd221655d053c diff --git a/README.md b/README.md index a18fd33..49b47fa 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ if let repo = repo.value { .HEAD() .flatMap { repo.commitWithOID($0.oid) } if let commit = latestCommit.value { - println("Latest Commit: \(commit.message) by \(commit.author.name)") + print("Latest Commit: \(commit.message) by \(commit.author.name)") } else { - println("Could not get commit: \(latestCommit.error)") + print("Could not get commit: \(latestCommit.error)") } } else { println("Could not open repository: \(repo.error)") diff --git a/SwiftGit2.xcodeproj/project.pbxproj b/SwiftGit2.xcodeproj/project.pbxproj index a53cc63..ccf3db5 100644 --- a/SwiftGit2.xcodeproj/project.pbxproj +++ b/SwiftGit2.xcodeproj/project.pbxproj @@ -38,7 +38,6 @@ 621E66811C72958800A0F352 /* pack.h in Copy libgit2 Headers */ = {isa = PBXBuildFile; fileRef = BE8DEDBC1AA6A7E200AFE62D /* pack.h */; }; 621E66821C72958800A0F352 /* patch.h in Copy libgit2 Headers */ = {isa = PBXBuildFile; fileRef = BE8DEDBD1AA6A7E200AFE62D /* patch.h */; }; 621E66831C72958800A0F352 /* pathspec.h in Copy libgit2 Headers */ = {isa = PBXBuildFile; fileRef = BE8DEDBE1AA6A7E200AFE62D /* pathspec.h */; }; - 621E66841C72958800A0F352 /* push.h in Copy libgit2 Headers */ = {isa = PBXBuildFile; fileRef = BE8DEDBF1AA6A7E200AFE62D /* push.h */; }; 621E66851C72958800A0F352 /* rebase.h in Copy libgit2 Headers */ = {isa = PBXBuildFile; fileRef = BE8DEDC01AA6A7E200AFE62D /* rebase.h */; }; 621E66861C72958800A0F352 /* refdb.h in Copy libgit2 Headers */ = {isa = PBXBuildFile; fileRef = BE8DEDC11AA6A7E200AFE62D /* refdb.h */; }; 621E66871C72958800A0F352 /* reflog.h in Copy libgit2 Headers */ = {isa = PBXBuildFile; fileRef = BE8DEDC21AA6A7E200AFE62D /* reflog.h */; }; @@ -95,6 +94,8 @@ 621E66E61C729D9600A0F352 /* SwiftGit2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 621E66B41C72958800A0F352 /* SwiftGit2.framework */; }; 621E66FE1C72A5FF00A0F352 /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 621E66FD1C72A5FF00A0F352 /* libiconv.tbd */; }; 621E67001C72A60B00A0F352 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 621E66FF1C72A60B00A0F352 /* libz.tbd */; }; + 622726341C84E52500C53D17 /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 622726331C84E52500C53D17 /* Credentials.swift */; }; + 622726351C84E52500C53D17 /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 622726331C84E52500C53D17 /* Credentials.swift */; }; 62E6FD8F1C727E9C00A312B0 /* ZipArchive.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62E6FD8E1C727E9C00A312B0 /* ZipArchive.framework */; }; BE0991F71A578FB1007D4E6A /* Mantle.zip in Resources */ = {isa = PBXBuildFile; fileRef = BE0991F61A578FB1007D4E6A /* Mantle.zip */; }; BE0B1C5D1A9978890004726D /* detached-head.zip in Resources */ = {isa = PBXBuildFile; fileRef = BE0B1C5C1A9978890004726D /* detached-head.zip */; }; @@ -143,7 +144,6 @@ BE8DEE6D1AA6A8AD00AFE62D /* pack.h in Copy libgit2 Headers */ = {isa = PBXBuildFile; fileRef = BE8DEDBC1AA6A7E200AFE62D /* pack.h */; }; BE8DEE6E1AA6A8AD00AFE62D /* patch.h in Copy libgit2 Headers */ = {isa = PBXBuildFile; fileRef = BE8DEDBD1AA6A7E200AFE62D /* patch.h */; }; BE8DEE6F1AA6A8AD00AFE62D /* pathspec.h in Copy libgit2 Headers */ = {isa = PBXBuildFile; fileRef = BE8DEDBE1AA6A7E200AFE62D /* pathspec.h */; }; - BE8DEE701AA6A8AD00AFE62D /* push.h in Copy libgit2 Headers */ = {isa = PBXBuildFile; fileRef = BE8DEDBF1AA6A7E200AFE62D /* push.h */; }; BE8DEE711AA6A8AD00AFE62D /* rebase.h in Copy libgit2 Headers */ = {isa = PBXBuildFile; fileRef = BE8DEDC01AA6A7E200AFE62D /* rebase.h */; }; BE8DEE721AA6A8AD00AFE62D /* refdb.h in Copy libgit2 Headers */ = {isa = PBXBuildFile; fileRef = BE8DEDC11AA6A7E200AFE62D /* refdb.h */; }; BE8DEE731AA6A8AD00AFE62D /* reflog.h in Copy libgit2 Headers */ = {isa = PBXBuildFile; fileRef = BE8DEDC21AA6A7E200AFE62D /* reflog.h */; }; @@ -193,20 +193,6 @@ remoteGlobalIDString = 621E66611C72958800A0F352; remoteInfo = "SwiftGit2-iOS"; }; - 621E66F71C729F0200A0F352 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BEB31F1A1A0D6F7A00F525B9 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 621E66E71C729EB800A0F352; - remoteInfo = "OpenSSL-iOS"; - }; - 621E66F91C729F0200A0F352 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BEB31F1A1A0D6F7A00F525B9 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 621E66ED1C729EBB00A0F352; - remoteInfo = "libssh2-iOS"; - }; 621E66FB1C72A25D00A0F352 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BEB31F1A1A0D6F7A00F525B9 /* Project object */; @@ -214,6 +200,20 @@ remoteGlobalIDString = 621E66DC1C729CE500A0F352; remoteInfo = "libgit2-iOS"; }; + 624349871C7CADCD0087C234 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BEB31F1A1A0D6F7A00F525B9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 621E66ED1C729EBB00A0F352; + remoteInfo = "libssh2-iOS"; + }; + 624349891C7CADD90087C234 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BEB31F1A1A0D6F7A00F525B9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 621E66E71C729EB800A0F352; + remoteInfo = "OpenSSL-iOS"; + }; BEB31F301A0D6F7A00F525B9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BEB31F1A1A0D6F7A00F525B9 /* Project object */; @@ -268,7 +268,6 @@ 621E66811C72958800A0F352 /* pack.h in Copy libgit2 Headers */, 621E66821C72958800A0F352 /* patch.h in Copy libgit2 Headers */, 621E66831C72958800A0F352 /* pathspec.h in Copy libgit2 Headers */, - 621E66841C72958800A0F352 /* push.h in Copy libgit2 Headers */, 621E66851C72958800A0F352 /* rebase.h in Copy libgit2 Headers */, 621E66861C72958800A0F352 /* refdb.h in Copy libgit2 Headers */, 621E66871C72958800A0F352 /* reflog.h in Copy libgit2 Headers */, @@ -336,7 +335,6 @@ BE8DEE6D1AA6A8AD00AFE62D /* pack.h in Copy libgit2 Headers */, BE8DEE6E1AA6A8AD00AFE62D /* patch.h in Copy libgit2 Headers */, BE8DEE6F1AA6A8AD00AFE62D /* pathspec.h in Copy libgit2 Headers */, - BE8DEE701AA6A8AD00AFE62D /* push.h in Copy libgit2 Headers */, BE8DEE711AA6A8AD00AFE62D /* rebase.h in Copy libgit2 Headers */, BE8DEE721AA6A8AD00AFE62D /* refdb.h in Copy libgit2 Headers */, BE8DEE731AA6A8AD00AFE62D /* reflog.h in Copy libgit2 Headers */, @@ -378,6 +376,7 @@ 621E66F21C729EBB00A0F352 /* liblibssh2-iOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "liblibssh2-iOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 621E66FD1C72A5FF00A0F352 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.2.sdk/usr/lib/libiconv.tbd; sourceTree = DEVELOPER_DIR; }; 621E66FF1C72A60B00A0F352 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.2.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; }; + 622726331C84E52500C53D17 /* Credentials.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Credentials.swift; sourceTree = ""; }; 62E6FD8E1C727E9C00A312B0 /* ZipArchive.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ZipArchive.framework; path = ../Carthage/Checkouts/ZipArchive/build/Debug/ZipArchive.framework; sourceTree = ""; }; BE0991F61A578FB1007D4E6A /* Mantle.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = Mantle.zip; sourceTree = ""; }; BE0B1C5C1A9978890004726D /* detached-head.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = "detached-head.zip"; sourceTree = ""; }; @@ -427,7 +426,6 @@ BE8DEDBC1AA6A7E200AFE62D /* pack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = pack.h; path = git2/pack.h; sourceTree = ""; }; BE8DEDBD1AA6A7E200AFE62D /* patch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = patch.h; path = git2/patch.h; sourceTree = ""; }; BE8DEDBE1AA6A7E200AFE62D /* pathspec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = pathspec.h; path = git2/pathspec.h; sourceTree = ""; }; - BE8DEDBF1AA6A7E200AFE62D /* push.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = push.h; path = git2/push.h; sourceTree = ""; }; BE8DEDC01AA6A7E200AFE62D /* rebase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = rebase.h; path = git2/rebase.h; sourceTree = ""; }; BE8DEDC11AA6A7E200AFE62D /* refdb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = refdb.h; path = git2/refdb.h; sourceTree = ""; }; BE8DEDC21AA6A7E200AFE62D /* reflog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = reflog.h; path = git2/reflog.h; sourceTree = ""; }; @@ -607,7 +605,6 @@ BE8DEDBC1AA6A7E200AFE62D /* pack.h */, BE8DEDBD1AA6A7E200AFE62D /* patch.h */, BE8DEDBE1AA6A7E200AFE62D /* pathspec.h */, - BE8DEDBF1AA6A7E200AFE62D /* push.h */, BE8DEDC01AA6A7E200AFE62D /* rebase.h */, BE8DEDC11AA6A7E200AFE62D /* refdb.h */, BE8DEDC21AA6A7E200AFE62D /* reflog.h */, @@ -669,6 +666,7 @@ BEB31F281A0D6F7A00F525B9 /* SwiftGit2.h */, BE14AA4F1A1974010015B439 /* SwiftGit2.m */, BE276B281ACCD3CF00D6DAD7 /* CheckoutStrategy.swift */, + 622726331C84E52500C53D17 /* Credentials.swift */, DA5914751A94579000AED74C /* Errors.swift */, BE36354B1A632C9700D37EC8 /* Libgit2.swift */, BE2E3BE51A31261300C67092 /* Objects.swift */, @@ -873,8 +871,7 @@ buildRules = ( ); dependencies = ( - 621E66F81C729F0200A0F352 /* PBXTargetDependency */, - 621E66FA1C729F0200A0F352 /* PBXTargetDependency */, + 624349881C7CADCD0087C234 /* PBXTargetDependency */, ); name = "libgit2-iOS"; productName = libgit2; @@ -905,6 +902,7 @@ buildRules = ( ); dependencies = ( + 6243498A1C7CADD90087C234 /* PBXTargetDependency */, ); name = "libssh2-iOS"; productName = libgit2; @@ -1094,6 +1092,7 @@ 621E66A01C72958800A0F352 /* OID.swift in Sources */, 621E66A11C72958800A0F352 /* Remotes.swift in Sources */, 621E66A21C72958800A0F352 /* CheckoutStrategy.swift in Sources */, + 622726351C84E52500C53D17 /* Credentials.swift in Sources */, 621E66A31C72958800A0F352 /* Repository.swift in Sources */, 621E66A41C72958800A0F352 /* Objects.swift in Sources */, 621E66A51C72958800A0F352 /* References.swift in Sources */, @@ -1125,6 +1124,7 @@ BE70B3E51A1ACB1A002C3F4E /* OID.swift in Sources */, BECB5F6E1A57284700999413 /* Remotes.swift in Sources */, BE276B291ACCD3CF00D6DAD7 /* CheckoutStrategy.swift in Sources */, + 622726341C84E52500C53D17 /* Credentials.swift in Sources */, BEB31F6D1A0D78F300F525B9 /* Repository.swift in Sources */, BE2E3BE61A31261300C67092 /* Objects.swift in Sources */, BECB5F6A1A56F19900999413 /* References.swift in Sources */, @@ -1157,21 +1157,21 @@ target = 621E66611C72958800A0F352 /* SwiftGit2-iOS */; targetProxy = 621E66E41C729D8A00A0F352 /* PBXContainerItemProxy */; }; - 621E66F81C729F0200A0F352 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 621E66E71C729EB800A0F352 /* OpenSSL-iOS */; - targetProxy = 621E66F71C729F0200A0F352 /* PBXContainerItemProxy */; - }; - 621E66FA1C729F0200A0F352 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 621E66ED1C729EBB00A0F352 /* libssh2-iOS */; - targetProxy = 621E66F91C729F0200A0F352 /* PBXContainerItemProxy */; - }; 621E66FC1C72A25D00A0F352 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 621E66DC1C729CE500A0F352 /* libgit2-iOS */; targetProxy = 621E66FB1C72A25D00A0F352 /* PBXContainerItemProxy */; }; + 624349881C7CADCD0087C234 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 621E66ED1C729EBB00A0F352 /* libssh2-iOS */; + targetProxy = 624349871C7CADCD0087C234 /* PBXContainerItemProxy */; + }; + 6243498A1C7CADD90087C234 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 621E66E71C729EB800A0F352 /* OpenSSL-iOS */; + targetProxy = 624349891C7CADD90087C234 /* PBXContainerItemProxy */; + }; BEB31F311A0D6F7A00F525B9 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = BEB31F221A0D6F7A00F525B9 /* SwiftGit2-OSX */; @@ -1524,6 +1524,7 @@ /usr/local/lib/libssh2.a, "-lcrypto", "-lssl", + "-lcurl", ); PRODUCT_BUNDLE_IDENTIFIER = "org.libgit2.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = SwiftGit2; @@ -1555,6 +1556,7 @@ /usr/local/lib/libssh2.a, "-lcrypto", "-lssl", + "-lcurl", ); PRODUCT_BUNDLE_IDENTIFIER = "org.libgit2.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = SwiftGit2; diff --git a/SwiftGit2/Credentials.swift b/SwiftGit2/Credentials.swift new file mode 100644 index 0000000..b2273bd --- /dev/null +++ b/SwiftGit2/Credentials.swift @@ -0,0 +1,50 @@ +// +// Credentials.swift +// SwiftGit2 +// +// Created by Tom Booth on 29/02/2016. +// Copyright © 2016 GitHub, Inc. All rights reserved. +// + +import Foundation +import Result + +private class Wrapper { + let value: T + + init(_ value: T) { + self.value = value + } +} + +public enum Credentials { + case Default() + case Plaintext(username: String, password: String) + case SSHMemory(username: String, publicKey: String, privateKey: String, passphrase: String) + + internal static func fromPointer(pointer: UnsafeMutablePointer<()>) -> Credentials { + return Unmanaged>.fromOpaque(COpaquePointer(pointer)).takeRetainedValue().value + } + + internal func toPointer() -> UnsafeMutablePointer<()> { + return UnsafeMutablePointer(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>, _: UnsafePointer, _: UnsafePointer, _: UInt32, + payload: UnsafeMutablePointer<()>) -> Int32 { + let result: Int32 + + switch Credentials.fromPointer(payload) { + case .Default(): + result = git_cred_default_new(cred) + 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 +} diff --git a/SwiftGit2/Repository.swift b/SwiftGit2/Repository.swift index f5f2205..1fbe8d6 100644 --- a/SwiftGit2/Repository.swift +++ b/SwiftGit2/Repository.swift @@ -9,11 +9,92 @@ import Foundation import Result -public typealias CheckoutProgressBlock = SG2CheckoutProgressBlock +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, completed_steps: Int, total_steps: Int, payload: UnsafeMutablePointer) -> Void { + if (payload != nil) { + let buffer = UnsafeMutablePointer(payload) + let block: CheckoutProgressBlock + if completed_steps < total_steps { + block = buffer.memory + } else { + block = buffer.move() + buffer.dealloc(1) + } + block(String.fromCString(path), completed_steps, total_steps); + } +} + +/// Helper function for initializing libgit2 git_checkout_options. +/// +/// :param: strategy The strategy to be used when checking out the repo, see CheckoutStrategy +/// :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.alloc(1) + git_checkout_init_options(pointer, UInt32(GIT_CHECKOUT_OPTIONS_VERSION)) + var options = pointer.move() + pointer.dealloc(1) + + options.checkout_strategy = strategy.git_checkout_strategy.rawValue + + if progress != nil { + options.progress_cb = checkoutProgressCallback + let blockPointer = UnsafeMutablePointer.alloc(1) + blockPointer.initialize(progress!) + options.progress_payload = UnsafeMutablePointer(blockPointer) + } + + return options +} + +private func fetchOptions(credentials: Credentials) -> git_fetch_options { + let pointer = UnsafeMutablePointer.alloc(1) + git_fetch_init_options(pointer, UInt32(GIT_FETCH_OPTIONS_VERSION)) + + var options = pointer.move() + + pointer.dealloc(1) + + options.callbacks.payload = credentials.toPointer() + options.callbacks.credentials = credentialsCallback + + 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.alloc(1) + git_clone_init_options(pointer, UInt32(GIT_CLONE_OPTIONS_VERSION)) + + var options = pointer.move() + + pointer.dealloc(1) + + options.bare = bare ? 1 : 0 + + if localClone { + options.local = GIT_CLONE_NO_LOCAL + } + + if let checkoutOptions = checkoutOptions { + options.checkout_opts = checkoutOptions + } + + if let fetchOptions = fetchOptions { + options.fetch_opts = fetchOptions + } + + return options +} /// A git repository. final public class Repository { - + // MARK: - Creating Repositories /// Load the repository at the given URL. @@ -32,6 +113,36 @@ final public class Repository { 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. + class public func cloneFromURL(remoteURL: NSURL, toURL: NSURL, localClone: Bool = false, bare: Bool = false, + credentials: Credentials = .Default(), checkoutStrategy: CheckoutStrategy = .Safe, checkoutProgress: CheckoutProgressBlock? = nil) -> Result { + var options = cloneOptions( + bare, localClone: localClone, + fetchOptions: fetchOptions(credentials), + checkoutOptions: checkoutOptions(checkoutStrategy, progress: checkoutProgress)) + + var pointer: COpaquePointer = nil + let remoteURLString = remoteURL.isFileReferenceURL() ? remoteURL.path! : remoteURL.absoluteString + let result = git_clone(&pointer, remoteURLString, toURL.fileSystemRepresentation, &options) + + if result != GIT_OK.rawValue { + return Result.Failure(libGit2Error(result, libGit2PointOfFailure: "git_clone")) + } + + let repository = Repository(pointer) + return Result.Success(repository) + } // MARK: - Initializers @@ -359,8 +470,7 @@ final public class Repository { /// :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 strategy: CheckoutStrategy, progress: CheckoutProgressBlock? = nil) -> Result<(), NSError> { - var options = SG2CheckoutOptions(progress) - options.checkout_strategy = strategy.git_checkout_strategy.rawValue + var options = checkoutOptions(strategy, progress: progress) let result = git_checkout_head(self.pointer, &options) if result != GIT_OK.rawValue { diff --git a/SwiftGit2/SwiftGit2.h b/SwiftGit2/SwiftGit2.h index 54943aa..59b2b64 100644 --- a/SwiftGit2/SwiftGit2.h +++ b/SwiftGit2/SwiftGit2.h @@ -15,11 +15,3 @@ FOUNDATION_EXPORT double SwiftGit2VersionNumber; FOUNDATION_EXPORT const unsigned char SwiftGit2VersionString[]; // In this header, you should import all the public headers of your framework using statements like #import - -#import "git2.h" - -typedef void (^SG2CheckoutProgressBlock)(NSString * __nullable, NSUInteger, NSUInteger); - -/// A C function for working with Libgit2. This shouldn't be called directly. It's an -/// implementation detail that, unfortunately, leaks through to the public headers. -extern git_checkout_options SG2CheckoutOptions(SG2CheckoutProgressBlock __nullable progress); diff --git a/SwiftGit2/SwiftGit2.m b/SwiftGit2/SwiftGit2.m index 0e5a55c..3ac5099 100644 --- a/SwiftGit2/SwiftGit2.m +++ b/SwiftGit2/SwiftGit2.m @@ -7,30 +7,9 @@ // #import "SwiftGit2.h" - +#import "git2.h" __attribute__((constructor)) static void SwiftGit2Init(void) { git_libgit2_init(); } - -static void SG2CheckoutProgressCallback(const char *path, size_t completed_steps, size_t total_steps, void *payload) { - if (payload == NULL) return; - - SG2CheckoutProgressBlock block = (__bridge SG2CheckoutProgressBlock)payload; - block((path == nil ? nil : @(path)), completed_steps, total_steps); -} - -git_checkout_options SG2CheckoutOptions(SG2CheckoutProgressBlock progress) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - git_checkout_options result = GIT_CHECKOUT_OPTIONS_INIT; -#pragma clang diagnostic pop - - if (progress != nil) { - result.progress_cb = SG2CheckoutProgressCallback; - result.progress_payload = (__bridge void *)[progress copy]; - } - - return result; -} diff --git a/SwiftGit2/SwiftGit2.modulemap b/SwiftGit2/SwiftGit2.modulemap index 2e2d4b8..2289f9e 100644 --- a/SwiftGit2/SwiftGit2.modulemap +++ b/SwiftGit2/SwiftGit2.modulemap @@ -36,7 +36,6 @@ framework module SwiftGit2 { header "git2/pack.h" header "git2/patch.h" header "git2/pathspec.h" - header "git2/push.h" header "git2/rebase.h" header "git2/refdb.h" header "git2/reflog.h" diff --git a/SwiftGit2Tests/RepositorySpec.swift b/SwiftGit2Tests/RepositorySpec.swift index c499824..820d68d 100644 --- a/SwiftGit2Tests/RepositorySpec.swift +++ b/SwiftGit2Tests/RepositorySpec.swift @@ -29,6 +29,91 @@ class RepositorySpec: QuickSpec { ))) } } + + describe("Repository.Type.clone()") { + it("should handle local clones") { + let remoteRepo = Fixtures.simpleRepository + let localURL = self.temporaryURLForPurpose("local-clone") + let result = Repository.cloneFromURL(remoteRepo.directoryURL!, toURL: localURL, localClone: true) + + expect(result).to(haveSucceeded()) + + if case .Success(let clonedRepo) = result { + expect(clonedRepo.directoryURL).notTo(beNil()) + } + } + + it("should handle bare clones") { + let remoteRepo = Fixtures.simpleRepository + let localURL = self.temporaryURLForPurpose("bare-clone") + let result = Repository.cloneFromURL(remoteRepo.directoryURL!, toURL: localURL, localClone: true, bare: true) + + expect(result).to(haveSucceeded()) + + 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.temporaryURLForPurpose("valid-remote-clone") + let cloneResult = Repository.cloneFromURL(remoteRepo.directoryURL!, toURL: localURL, localClone: true) + + expect(cloneResult).to(haveSucceeded()) + + if case .Success(let clonedRepo) = cloneResult { + let remoteResult = clonedRepo.remoteWithName("origin") + expect(remoteResult).to(haveSucceeded()) + + 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 = NSURL(string: "https://github.com/libgit2/libgit2.github.com.git") + let localURL = self.temporaryURLForPurpose("public-remote-clone") + let cloneResult = Repository.cloneFromURL(remoteRepoURL!, toURL: localURL) + + expect(cloneResult).to(haveSucceeded()) + + if case .Success(let clonedRepo) = cloneResult { + let remoteResult = clonedRepo.remoteWithName("origin") + expect(remoteResult).to(haveSucceeded()) + + if case .Success(let remote) = remoteResult { + expect(remote.URL).to(equal(remoteRepoURL?.absoluteString)) + } + } + } + + let env = NSProcessInfo.processInfo().environment + + if let privateRepo = env["SG2TestPrivateRepo"], gitUsername = env["SG2TestUsername"], publicKey = env["SG2TestPublicKey"], + privateKey = env["SG2TestPrivateKey"], passphrase = env["SG2TestPassphrase"] { + + it("should be able to clone a remote repository requiring credentials") { + let remoteRepoURL = NSURL(string: privateRepo) + let localURL = self.temporaryURLForPurpose("private-remote-clone") + + let cloneResult = Repository.cloneFromURL(remoteRepoURL!, toURL: localURL, + credentials: .SSHMemory(username: gitUsername, publicKey: publicKey, privateKey: privateKey, passphrase: passphrase)) + + expect(cloneResult).to(haveSucceeded()) + + if case .Success(let clonedRepo) = cloneResult { + let remoteResult = clonedRepo.remoteWithName("origin") + expect(remoteResult).to(haveSucceeded()) + + if case .Success(let remote) = remoteResult { + expect(remote.URL).to(equal(remoteRepoURL?.absoluteString)) + } + } + } + } + } describe("Repository.blobWithOID()") { it("should return the commit if it exists") { @@ -475,10 +560,24 @@ class RepositorySpec: QuickSpec { let HEAD = repo.HEAD().value expect(HEAD?.longName).to(equal("HEAD")) expect(HEAD?.oid).to(equal(oid)) - + expect(repo.checkout(repo.localBranchWithName("master").value!, strategy: CheckoutStrategy.None)).to(haveSucceeded()) 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: { (path, completedSteps, totalSteps) -> Void in + expect(completedSteps).to(beLessThanOrEqualTo(totalSteps)) + })).to(haveSucceeded()) + + let HEAD = repo.HEAD().value + expect(HEAD?.longName).to(equal("HEAD")) + expect(HEAD?.oid).to(equal(oid)) + } } describe("Repository.checkout(ReferenceType)") { @@ -496,4 +595,10 @@ class RepositorySpec: QuickSpec { } } } + + func temporaryURLForPurpose(purpose: String) -> NSURL { + let globallyUniqueString = NSProcessInfo.processInfo().globallyUniqueString + let path = "\(NSTemporaryDirectory())\(globallyUniqueString)_\(purpose)" + return NSURL(fileURLWithPath: path) + } } diff --git a/script/bootstrap b/script/bootstrap index 84b7dd6..92d7d75 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -10,9 +10,12 @@ config () { # A whitespace-separated list of executables that must be present and locatable. # These will each be installed through Homebrew if not found. - : ${REQUIRED_TOOLS="xctool cmake libssh2 libtool autoconf automake pkg-config"} + : ${REQUIRED_TOOLS="cmake libssh2 libtool autoconf automake pkg-config"} + : ${REQUIRED_GEMS="xcpretty"} export REQUIRED_TOOLS + export REQUIRED_GEMS + } ## @@ -102,6 +105,11 @@ check_deps () sudo ln -s "$brew_prefix/$product" "$destination" done fi + + for gem in $REQUIRED_GEMS + do + gem install "$gem" + done } bootstrap_submodule ()