├── .gitignore ├── .hound.yml ├── .swiftlint.yml ├── .travis.yml ├── Cartfile.private ├── Cartfile.resolved ├── External ├── libcrypto.a └── libssl.a ├── LICENSE.md ├── Package.swift ├── README.md ├── SwiftGit2.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── SwiftGit2-OSX.xcscheme │ └── SwiftGit2-iOS.xcscheme ├── SwiftGit2.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── SwiftGit2 ├── CheckoutStrategy.swift ├── CommitIterator.swift ├── Credentials.swift ├── Diffs.swift ├── Errors.swift ├── Info.plist ├── Libgit2.swift ├── OID.swift ├── Objects.swift ├── Pointers.swift ├── References.swift ├── Remotes.swift ├── Repository.swift └── StatusOptions.swift ├── SwiftGit2Tests ├── Fixtures │ ├── Fixtures.swift │ ├── Mantle.zip │ ├── detached-head.zip │ ├── repository-with-status.zip │ └── simple-repository.zip ├── FixturesSpec.swift ├── Info.plist ├── OIDSpec.swift ├── ObjectsSpec.swift ├── ReferencesSpec.swift ├── RemotesSpec.swift ├── RepositorySpec.swift └── ResultShims.swift ├── libgit2 ├── git2 ├── git2.h └── module.modulemap └── script ├── bootstrap ├── cibuild ├── clean_externals ├── ios_build_functions.sh ├── update_libgit2 ├── update_libgit2_ios ├── update_libssh2_ios ├── update_libssl_ios └── xcode_functions.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .build/ 3 | 4 | # Xcode 5 | # 6 | build/ 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | xcuserdata 16 | *.xccheckout 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | *.xcuserstate 22 | *.xcscmblueprint 23 | *.idea* 24 | 25 | External/libgit2*.a 26 | External/ios-openssl 27 | External/libgit2-ios 28 | External/libssh2-ios 29 | 30 | ### fastlane ### 31 | # fastlane - A streamlined workflow tool for Cocoa deployment 32 | 33 | # fastlane specific 34 | fastlane/report.xml 35 | 36 | # deliver temporary files 37 | fastlane/Preview.html 38 | 39 | # snapshot generated screenshots 40 | fastlane/screenshots/**/*.png 41 | fastlane/screenshots/screenshots.html 42 | 43 | # scan temporary files 44 | fastlane/test_output 45 | test_output 46 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | # Configuration for Hound (https://houndci.com) 2 | 3 | swiftlint: 4 | config_file: .swiftlint.yml 5 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - file_length 3 | - force_cast 4 | - force_try 5 | - function_body_length 6 | - identifier_name 7 | - redundant_optional_initialization 8 | - type_body_length 9 | 10 | opt_in_rules: 11 | - array_init 12 | - attributes 13 | - closure_end_indentation 14 | - closure_spacing 15 | - collection_alignment 16 | - conditional_returns_on_newline 17 | - contains_over_first_not_nil 18 | - convenience_type 19 | - discouraged_object_literal 20 | - discouraged_optional_boolean 21 | - discouraged_optional_collection 22 | - empty_count 23 | - empty_string 24 | - empty_xctest_method 25 | - explicit_enum_raw_value 26 | - explicit_init 27 | - extension_access_modifier 28 | - fallthrough 29 | - fatal_error_message 30 | - first_where 31 | - function_default_parameter_at_end 32 | - identical_operands 33 | - implicitly_unwrapped_optional 34 | - joined_default_parameter 35 | - last_where 36 | - legacy_random 37 | - literal_expression_end_indentation 38 | - lower_acl_than_parent 39 | - modifier_order 40 | - multiline_arguments 41 | - multiline_function_chains 42 | - multiline_literal_brackets 43 | - nslocalizedstring_key 44 | - overridden_super_call 45 | - override_in_extension 46 | - private_action 47 | - private_outlet 48 | - prohibited_super_call 49 | - quick_discouraged_call 50 | - quick_discouraged_focused_test 51 | - quick_discouraged_pending_test 52 | - redundant_nil_coalescing 53 | - redundant_type_annotation 54 | - sorted_first_last 55 | - static_operator 56 | - strict_fileprivate 57 | - switch_case_on_newline 58 | - toggle_bool 59 | - unavailable_function 60 | - untyped_error_in_catch 61 | - unused_import 62 | - unused_private_declaration 63 | - vertical_parameter_alignment_on_call 64 | - xct_specific_matcher 65 | - yoda_condition 66 | 67 | excluded: 68 | - Carthage 69 | - External 70 | 71 | line_length: 72 | ignores_function_declarations: true 73 | 74 | trailing_comma: 75 | mandatory_comma: true 76 | 77 | trailing_whitespace: 78 | ignores_comments: false 79 | ignores_empty_lines: false 80 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | os: osx 4 | osx_image: xcode11.4 5 | 6 | sudo: false # Enable container-based builds 7 | 8 | env: 9 | matrix: 10 | - SCHEME="SwiftGit2-OSX" 11 | - SCHEME="SwiftGit2-iOS" 12 | 13 | matrix: 14 | fast_finish: true 15 | 16 | before_install: 17 | - gem update bundler # https://github.com/bundler/bundler/pull/4981 18 | - gem install xcpretty 19 | - gem install xcpretty-travis-formatter 20 | 21 | install: script/bootstrap 22 | 23 | script: script/cibuild 24 | 25 | branches: 26 | only: # whitelist 27 | - master 28 | 29 | notifications: 30 | email: false 31 | -------------------------------------------------------------------------------- /Cartfile.private: -------------------------------------------------------------------------------- 1 | github "jspahrsummers/xcconfigs" ~> 1.1 2 | github "Quick/Quick" ~> 2.0 3 | github "Quick/Nimble" ~> 8.0 4 | github "ZipArchive/ZipArchive" ~> 2.0 5 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Quick/Nimble" "v8.1.2" 2 | github "Quick/Quick" "v2.2.1" 3 | github "ZipArchive/ZipArchive" "v2.2.3" 4 | github "jspahrsummers/xcconfigs" "1.1" 5 | -------------------------------------------------------------------------------- /External/libcrypto.a: -------------------------------------------------------------------------------- 1 | /usr/local/opt/openssl/lib/libcrypto.a -------------------------------------------------------------------------------- /External/libssl.a: -------------------------------------------------------------------------------- 1 | /usr/local/opt/openssl/lib/libssl.a -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014-2016 SwiftGit2 contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftGit2", 8 | platforms: [.iOS(.v13)], 9 | products: [ 10 | // Products define the executables and libraries a package produces, and make them visible to other packages. 11 | .library( 12 | name: "SwiftGit2", 13 | targets: ["SwiftGit2"]), 14 | ], 15 | dependencies: [ 16 | // Dependencies declare other packages that this package depends on. 17 | .package( 18 | name: "Clibgit2", 19 | url: "https://github.com/light-tech/Clibgit2.git", 20 | .branch("master") 21 | ), 22 | ], 23 | targets: [ 24 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 25 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 26 | .target( 27 | name: "SwiftGit2", 28 | dependencies: ["Clibgit2"], 29 | path: "SwiftGit2" 30 | ), 31 | .testTarget( 32 | name: "SwiftGit2Tests", 33 | dependencies: ["SwiftGit2"], 34 | path: "SwiftGit2Tests" 35 | ), 36 | ] 37 | ) 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftGit2 2 | [![Build Status](https://travis-ci.org/SwiftGit2/SwiftGit2.svg)](https://travis-ci.org/SwiftGit2/SwiftGit2) 3 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](#carthage) 4 | [![GitHub release](https://img.shields.io/github/release/SwiftGit2/SwiftGit2.svg)](https://github.com/SwiftGit2/SwiftGit2/releases) 5 | ![Swift 5.3.x](https://img.shields.io/badge/Swift-5.3.x-orange.svg) 6 | 7 | Swift bindings to [libgit2](https://github.com/libgit2/libgit2). 8 | 9 | ```swift 10 | let URL: URL = ... 11 | let result = Repository.at(URL) 12 | switch result { 13 | case let .success(repo): 14 | let latestCommit = repo 15 | .HEAD() 16 | .flatMap { 17 | repo.commit($0.oid) 18 | } 19 | 20 | switch latestCommit { 21 | case let .success(commit): 22 | print("Latest Commit: \(commit.message) by \(commit.author.name)") 23 | 24 | case let .failure(error): 25 | print("Could not get commit: \(error)") 26 | } 27 | 28 | case let .failure(error): 29 | print("Could not open repository: \(error)") 30 | } 31 | ``` 32 | 33 | See [our iOS example app project](https://github.com/light-tech/SwiftGit2-SampleApp) to see the code in action. 34 | 35 | ## Design 36 | SwiftGit2 uses value objects wherever possible. That means using Swift's `struct`s and `enum`s without holding references to libgit2 objects. This has a number of advantages: 37 | 38 | 1. Values can be used concurrently. 39 | 2. Consuming values won't result in disk access. 40 | 3. Disk access can be contained to a smaller number of APIs. 41 | 42 | This vastly simplifies the design of long-lived applications, which are the most common use case with Swift. Consequently, SwiftGit2 APIs don't necessarily map 1-to-1 with libgit2 APIs. 43 | 44 | All methods for reading from or writing to a repository are on SwiftGit2's only `class`: `Repository`. This highlights the failability and mutation of these methods, while freeing up all other instances to be immutable `struct`s and `enum`s. 45 | 46 | ## Adding SwiftGit2 to your Project 47 | The easiest way to add SwiftGit2 to your project is to [add our repository as a Swift package](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app). 48 | 49 | Note that **you need to choose `spm` branch of our fork https://github.com/light-tech/SwiftGit2 which was configured to be compatible with Swift package manager**. NEITHER the original repository https://github.com/SwiftGit2/SwiftGit2 nor other branch of our fork support Swift package manager as of this writing. 50 | 51 | You also need to add `libz.tbd` and `libiconv.tbd` to the app target's **Frameworks, Libraries and Embedded Content**. 52 | 53 | Before using any of the SwiftGit2 API, add 54 | ```swift 55 | Repository.initialize_libgit2() 56 | ``` 57 | to your app initialization method. 58 | 59 | Check out [our iOS example app project](https://github.com/light-tech/SwiftGit2-SampleApp) for a starting point. 60 | 61 | ## Contributions 62 | We :heart: to receive pull requests! GitHub makes it easy: 63 | 64 | 1. Fork the repository 65 | 2. Create a branch with your changes 66 | 3. Send a Pull Request 67 | 68 | All contributions should match GitHub's [Swift Style Guide](https://github.com/github/swift-style-guide). 69 | 70 | ## License 71 | SwiftGit2 is available under the MIT license. 72 | -------------------------------------------------------------------------------- /SwiftGit2.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftGit2.xcodeproj/xcshareddata/xcschemes/SwiftGit2-OSX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 77 | 83 | 84 | 85 | 86 | 92 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /SwiftGit2.xcodeproj/xcshareddata/xcschemes/SwiftGit2-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 77 | 83 | 84 | 85 | 86 | 92 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /SwiftGit2.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /SwiftGit2.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftGit2/CheckoutStrategy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CheckoutStrategy.swift 3 | // SwiftGit2 4 | // 5 | // Created by Matt Diephouse on 4/1/15. 6 | // Copyright (c) 2015 GitHub, Inc. All rights reserved. 7 | // 8 | 9 | import Clibgit2 10 | 11 | /// The flags defining how a checkout should be performed. 12 | /// More detail is available in the libgit2 documentation for `git_checkout_strategy_t`. 13 | public struct CheckoutStrategy: OptionSet { 14 | private let value: UInt 15 | 16 | // MARK: - Initialization 17 | 18 | /// Create an instance initialized with `nil`. 19 | public init(nilLiteral: ()) { 20 | self.value = 0 21 | } 22 | 23 | public init(rawValue value: UInt) { 24 | self.value = value 25 | } 26 | 27 | public init(_ strategy: git_checkout_strategy_t) { 28 | self.value = UInt(strategy.rawValue) 29 | } 30 | 31 | public static var allZeros: CheckoutStrategy { 32 | return self.init(rawValue: 0) 33 | } 34 | 35 | // MARK: - Properties 36 | 37 | public var rawValue: UInt { 38 | return value 39 | } 40 | 41 | public var gitCheckoutStrategy: git_checkout_strategy_t { 42 | return git_checkout_strategy_t(UInt32(self.value)) 43 | } 44 | 45 | // MARK: - Values 46 | 47 | /// Default is a dry run, no actual updates. 48 | public static let None = CheckoutStrategy(GIT_CHECKOUT_NONE) 49 | 50 | /// Allow safe updates that cannot overwrite uncommitted data. 51 | public static let Safe = CheckoutStrategy(GIT_CHECKOUT_SAFE) 52 | 53 | /// Allow all updates to force working directory to look like index 54 | public static let Force = CheckoutStrategy(GIT_CHECKOUT_FORCE) 55 | 56 | /// Allow checkout to recreate missing files. 57 | public static let RecreateMissing = CheckoutStrategy(GIT_CHECKOUT_RECREATE_MISSING) 58 | 59 | /// Allow checkout to make safe updates even if conflicts are found. 60 | public static let AllowConflicts = CheckoutStrategy(GIT_CHECKOUT_ALLOW_CONFLICTS) 61 | 62 | /// Remove untracked files not in index (that are not ignored). 63 | public static let RemoveUntracked = CheckoutStrategy(GIT_CHECKOUT_REMOVE_UNTRACKED) 64 | 65 | /// Remove ignored files not in index. 66 | public static let RemoveIgnored = CheckoutStrategy(GIT_CHECKOUT_REMOVE_IGNORED) 67 | 68 | /// Only update existing files, don't create new ones. 69 | public static let UpdateOnly = CheckoutStrategy(GIT_CHECKOUT_UPDATE_ONLY) 70 | 71 | /// Normally checkout updates index entries as it goes; this stops that. 72 | /// Implies `DontWriteIndex`. 73 | public static let DontUpdateIndex = CheckoutStrategy(GIT_CHECKOUT_DONT_UPDATE_INDEX) 74 | 75 | /// Don't refresh index/config/etc before doing checkout 76 | public static let NoRefresh = CheckoutStrategy(GIT_CHECKOUT_NO_REFRESH) 77 | 78 | /// Allow checkout to skip unmerged files 79 | public static let SkipUnmerged = CheckoutStrategy(GIT_CHECKOUT_SKIP_UNMERGED) 80 | 81 | /// For unmerged files, checkout stage 2 from index 82 | public static let UseOurs = CheckoutStrategy(GIT_CHECKOUT_USE_OURS) 83 | 84 | /// For unmerged files, checkout stage 3 from index 85 | public static let UseTheirs = CheckoutStrategy(GIT_CHECKOUT_USE_THEIRS) 86 | 87 | /// Treat pathspec as simple list of exact match file paths 88 | public static let DisablePathspecMatch = CheckoutStrategy(GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) 89 | 90 | /// Ignore directories in use, they will be left empty 91 | public static let SkipLockedDirectories = CheckoutStrategy(GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES) 92 | 93 | /// Don't overwrite ignored files that exist in the checkout target 94 | public static let DontOverwriteIgnored = CheckoutStrategy(GIT_CHECKOUT_DONT_OVERWRITE_IGNORED) 95 | 96 | /// Write normal merge files for conflicts 97 | public static let ConflictStyleMerge = CheckoutStrategy(GIT_CHECKOUT_CONFLICT_STYLE_MERGE) 98 | 99 | /// Include common ancestor data in diff3 format files for conflicts 100 | public static let ConflictStyleDiff3 = CheckoutStrategy(GIT_CHECKOUT_CONFLICT_STYLE_DIFF3) 101 | 102 | /// Don't overwrite existing files or folders 103 | public static let DontRemoveExisting = CheckoutStrategy(GIT_CHECKOUT_DONT_REMOVE_EXISTING) 104 | 105 | /// Normally checkout writes the index upon completion; this prevents that. 106 | public static let DontWriteIndex = CheckoutStrategy(GIT_CHECKOUT_DONT_WRITE_INDEX) 107 | } 108 | -------------------------------------------------------------------------------- /SwiftGit2/CommitIterator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Arnon Keereena on 4/28/17. 3 | // Copyright (c) 2017 GitHub, Inc. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import Clibgit2 8 | 9 | public class CommitIterator: IteratorProtocol, Sequence { 10 | public typealias Iterator = CommitIterator 11 | public typealias Element = Result 12 | let repo: Repository 13 | private var revisionWalker: OpaquePointer? 14 | 15 | private enum Next { 16 | case over 17 | case okay 18 | case error(NSError) 19 | 20 | init(_ result: Int32, name: String) { 21 | switch result { 22 | case GIT_ITEROVER.rawValue: 23 | self = .over 24 | case GIT_OK.rawValue: 25 | self = .okay 26 | default: 27 | self = .error(NSError(gitError: result, pointOfFailure: name)) 28 | } 29 | } 30 | } 31 | 32 | init(repo: Repository, root: git_oid) { 33 | self.repo = repo 34 | setupRevisionWalker(root: root) 35 | } 36 | 37 | deinit { 38 | git_revwalk_free(self.revisionWalker) 39 | } 40 | 41 | private func setupRevisionWalker(root: git_oid) { 42 | var oid = root 43 | git_revwalk_new(&revisionWalker, repo.pointer) 44 | git_revwalk_sorting(revisionWalker, GIT_SORT_TOPOLOGICAL.rawValue) 45 | git_revwalk_sorting(revisionWalker, GIT_SORT_TIME.rawValue) 46 | git_revwalk_push(revisionWalker, &oid) 47 | } 48 | 49 | public func next() -> Element? { 50 | var oid = git_oid() 51 | let revwalkGitResult = git_revwalk_next(&oid, revisionWalker) 52 | let nextResult = Next(revwalkGitResult, name: "git_revwalk_next") 53 | switch nextResult { 54 | case let .error(error): 55 | return Result.failure(error) 56 | case .over: 57 | return nil 58 | case .okay: 59 | var unsafeCommit: OpaquePointer? = nil 60 | let lookupGitResult = git_commit_lookup(&unsafeCommit, repo.pointer, &oid) 61 | guard lookupGitResult == GIT_OK.rawValue, 62 | let unwrapCommit = unsafeCommit else { 63 | return Result.failure(NSError(gitError: lookupGitResult, pointOfFailure: "git_commit_lookup")) 64 | } 65 | let result: Element = Result.success(Commit(unwrapCommit)) 66 | git_commit_free(unsafeCommit) 67 | return result 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /SwiftGit2/Credentials.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Credentials.swift 3 | // SwiftGit2 4 | // 5 | // Created by Tom Booth on 29/02/2016. 6 | // Copyright © 2016 GitHub, Inc. All rights reserved. 7 | // 8 | 9 | import Clibgit2 10 | 11 | private class Wrapper { 12 | let value: T 13 | 14 | init(_ value: T) { 15 | self.value = value 16 | } 17 | } 18 | 19 | public enum Credentials { 20 | case `default` 21 | case sshAgent 22 | case plaintext(username: String, password: String) 23 | case sshMemory(username: String, publicKey: String, privateKey: String, passphrase: String) 24 | 25 | internal static func fromPointer(_ pointer: UnsafeMutableRawPointer) -> Credentials { 26 | return Unmanaged>.fromOpaque(UnsafeRawPointer(pointer)).takeRetainedValue().value 27 | } 28 | 29 | internal func toPointer() -> UnsafeMutableRawPointer { 30 | return Unmanaged.passRetained(Wrapper(self)).toOpaque() 31 | } 32 | } 33 | 34 | /// Handle the request of credentials, passing through to a wrapped block after converting the arguments. 35 | /// Converts the result to the correct error code required by libgit2 (0 = success, 1 = rejected setting creds, 36 | /// -1 = error) 37 | internal func credentialsCallback( 38 | cred: UnsafeMutablePointer?>?, 39 | url: UnsafePointer?, 40 | username: UnsafePointer?, 41 | _: UInt32, 42 | payload: UnsafeMutableRawPointer? ) -> Int32 { 43 | 44 | let result: Int32 45 | 46 | // Find username_from_url 47 | let name = username.map(String.init(cString:)) 48 | 49 | switch Credentials.fromPointer(payload!) { 50 | case .default: 51 | result = git_cred_default_new(cred) 52 | case .sshAgent: 53 | result = git_cred_ssh_key_from_agent(cred, name!) 54 | case .plaintext(let username, let password): 55 | result = git_cred_userpass_plaintext_new(cred, username, password) 56 | case .sshMemory(let username, let publicKey, let privateKey, let passphrase): 57 | result = git_cred_ssh_key_memory_new(cred, username, publicKey, privateKey, passphrase) 58 | } 59 | 60 | return (result != GIT_OK.rawValue) ? -1 : 0 61 | } 62 | -------------------------------------------------------------------------------- /SwiftGit2/Diffs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Diffs.swift 3 | // SwiftGit2 4 | // 5 | // Created by Jake Van Alstyne on 8/20/17. 6 | // Copyright © 2017 GitHub, Inc. All rights reserved. 7 | // 8 | 9 | import Clibgit2 10 | 11 | public struct StatusEntry { 12 | public var status: Diff.Status 13 | public var headToIndex: Diff.Delta? 14 | public var indexToWorkDir: Diff.Delta? 15 | 16 | public init(from statusEntry: git_status_entry) { 17 | self.status = Diff.Status(rawValue: statusEntry.status.rawValue) 18 | 19 | if let htoi = statusEntry.head_to_index { 20 | self.headToIndex = Diff.Delta(htoi.pointee) 21 | } 22 | 23 | if let itow = statusEntry.index_to_workdir { 24 | self.indexToWorkDir = Diff.Delta(itow.pointee) 25 | } 26 | } 27 | } 28 | 29 | public struct Diff { 30 | 31 | /// The set of deltas. 32 | public var deltas = [Delta]() 33 | 34 | public struct Delta { 35 | public static let type = GIT_OBJECT_REF_DELTA 36 | 37 | public var status: Status 38 | public var flags: Flags 39 | public var oldFile: File? 40 | public var newFile: File? 41 | 42 | public init(_ delta: git_diff_delta) { 43 | self.status = Status(rawValue: UInt32(git_diff_status_char(delta.status))) 44 | self.flags = Flags(rawValue: delta.flags) 45 | self.oldFile = File(delta.old_file) 46 | self.newFile = File(delta.new_file) 47 | } 48 | } 49 | 50 | public struct File { 51 | public var oid: OID 52 | public var path: String 53 | public var size: UInt64 54 | public var flags: Flags 55 | 56 | public init(_ diffFile: git_diff_file) { 57 | self.oid = OID(diffFile.id) 58 | let path = diffFile.path 59 | self.path = path.map(String.init(cString:))! 60 | self.size = diffFile.size 61 | self.flags = Flags(rawValue: diffFile.flags) 62 | } 63 | } 64 | 65 | public struct Status: OptionSet { 66 | // This appears to be necessary due to bug in Swift 67 | // https://bugs.swift.org/browse/SR-3003 68 | public init(rawValue: UInt32) { 69 | self.rawValue = rawValue 70 | } 71 | public let rawValue: UInt32 72 | 73 | public static let current = Status(rawValue: GIT_STATUS_CURRENT.rawValue) 74 | public static let indexNew = Status(rawValue: GIT_STATUS_INDEX_NEW.rawValue) 75 | public static let indexModified = Status(rawValue: GIT_STATUS_INDEX_MODIFIED.rawValue) 76 | public static let indexDeleted = Status(rawValue: GIT_STATUS_INDEX_DELETED.rawValue) 77 | public static let indexRenamed = Status(rawValue: GIT_STATUS_INDEX_RENAMED.rawValue) 78 | public static let indexTypeChange = Status(rawValue: GIT_STATUS_INDEX_TYPECHANGE.rawValue) 79 | public static let workTreeNew = Status(rawValue: GIT_STATUS_WT_NEW.rawValue) 80 | public static let workTreeModified = Status(rawValue: GIT_STATUS_WT_MODIFIED.rawValue) 81 | public static let workTreeDeleted = Status(rawValue: GIT_STATUS_WT_DELETED.rawValue) 82 | public static let workTreeTypeChange = Status(rawValue: GIT_STATUS_WT_TYPECHANGE.rawValue) 83 | public static let workTreeRenamed = Status(rawValue: GIT_STATUS_WT_RENAMED.rawValue) 84 | public static let workTreeUnreadable = Status(rawValue: GIT_STATUS_WT_UNREADABLE.rawValue) 85 | public static let ignored = Status(rawValue: GIT_STATUS_IGNORED.rawValue) 86 | public static let conflicted = Status(rawValue: GIT_STATUS_CONFLICTED.rawValue) 87 | } 88 | 89 | public struct Flags: OptionSet { 90 | // This appears to be necessary due to bug in Swift 91 | // https://bugs.swift.org/browse/SR-3003 92 | public init(rawValue: UInt32) { 93 | self.rawValue = rawValue 94 | } 95 | public let rawValue: UInt32 96 | 97 | public static let binary = Flags([]) 98 | public static let notBinary = Flags(rawValue: 1 << 0) 99 | public static let validId = Flags(rawValue: 1 << 1) 100 | public static let exists = Flags(rawValue: 1 << 2) 101 | } 102 | 103 | /// Create an instance with a libgit2 `git_diff`. 104 | public init(_ pointer: OpaquePointer) { 105 | for i in 0.. String? { 42 | let last = giterr_last() 43 | if let lastErrorPointer = last { 44 | return String(validatingUTF8: lastErrorPointer.pointee.message) 45 | } else if UInt32(errorCode) == GIT_ERROR_OS.rawValue { 46 | return String(validatingUTF8: strerror(errno)) 47 | } else { 48 | return nil 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /SwiftGit2/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2014 GitHub, Inc. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /SwiftGit2/Libgit2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Libgit2.swift 3 | // SwiftGit2 4 | // 5 | // Created by Matt Diephouse on 1/11/15. 6 | // Copyright (c) 2015 GitHub, Inc. All rights reserved. 7 | // 8 | 9 | import Clibgit2 10 | 11 | extension git_strarray { 12 | func filter(_ isIncluded: (String) -> Bool) -> [String] { 13 | return map { $0 }.filter(isIncluded) 14 | } 15 | 16 | func map(_ transform: (String) -> T) -> [T] { 17 | return (0.. 40 { 22 | return nil 23 | } 24 | 25 | let pointer = UnsafeMutablePointer.allocate(capacity: 1) 26 | let result = git_oid_fromstr(pointer, string) 27 | 28 | if result < GIT_OK.rawValue { 29 | pointer.deallocate() 30 | return nil 31 | } 32 | 33 | oid = pointer.pointee 34 | pointer.deallocate() 35 | } 36 | 37 | /// Create an instance from a libgit2 `git_oid`. 38 | public init(_ oid: git_oid) { 39 | self.oid = oid 40 | } 41 | 42 | // MARK: - Properties 43 | 44 | public let oid: git_oid 45 | } 46 | 47 | extension OID: CustomStringConvertible { 48 | public var description: String { 49 | let length = Int(GIT_OID_RAWSZ) * 2 50 | let string = UnsafeMutablePointer.allocate(capacity: length) 51 | var oid = self.oid 52 | git_oid_fmt(string, &oid) 53 | 54 | return String(bytesNoCopy: string, length: length, encoding: .ascii, freeWhenDone: true)! 55 | } 56 | } 57 | 58 | extension OID: Hashable { 59 | public func hash(into hasher: inout Hasher) { 60 | withUnsafeBytes(of: oid.id) { 61 | hasher.combine(bytes: $0) 62 | } 63 | } 64 | 65 | public static func == (lhs: OID, rhs: OID) -> Bool { 66 | var left = lhs.oid 67 | var right = rhs.oid 68 | return git_oid_cmp(&left, &right) == 0 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /SwiftGit2/Objects.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Objects.swift 3 | // SwiftGit2 4 | // 5 | // Created by Matt Diephouse on 12/4/14. 6 | // Copyright (c) 2014 GitHub, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Clibgit2 11 | 12 | /// A git object. 13 | public protocol ObjectType { 14 | static var type: git_object_t { get } 15 | 16 | /// The OID of the object. 17 | var oid: OID { get } 18 | 19 | /// Create an instance with the underlying libgit2 type. 20 | init(_ pointer: OpaquePointer) 21 | } 22 | 23 | public extension ObjectType { 24 | static func == (lhs: Self, rhs: Self) -> Bool { 25 | return lhs.oid == rhs.oid 26 | } 27 | 28 | func hash(into hasher: inout Hasher) { 29 | hasher.combine(oid) 30 | } 31 | } 32 | 33 | public struct Signature { 34 | /// The name of the person. 35 | public let name: String 36 | 37 | /// The email of the person. 38 | public let email: String 39 | 40 | /// The time when the action happened. 41 | public let time: Date 42 | 43 | /// The time zone that `time` should be interpreted relative to. 44 | public let timeZone: TimeZone 45 | 46 | /// Create an instance with custom name, email, dates, etc. 47 | public init(name: String, email: String, time: Date = Date(), timeZone: TimeZone = TimeZone.autoupdatingCurrent) { 48 | self.name = name 49 | self.email = email 50 | self.time = time 51 | self.timeZone = timeZone 52 | } 53 | 54 | /// Create an instance with a libgit2 `git_signature`. 55 | public init(_ signature: git_signature) { 56 | name = String(validatingUTF8: signature.name)! 57 | email = String(validatingUTF8: signature.email)! 58 | time = Date(timeIntervalSince1970: TimeInterval(signature.when.time)) 59 | timeZone = TimeZone(secondsFromGMT: 60 * Int(signature.when.offset))! 60 | } 61 | 62 | /// Return an unsafe pointer to the `git_signature` struct. 63 | /// Caller is responsible for freeing it with `git_signature_free`. 64 | func makeUnsafeSignature() -> Result, NSError> { 65 | var signature: UnsafeMutablePointer? = nil 66 | let time = git_time_t(self.time.timeIntervalSince1970) // Unix epoch time 67 | let offset = Int32(timeZone.secondsFromGMT(for: self.time) / 60) 68 | let signatureResult = git_signature_new(&signature, name, email, time, offset) 69 | guard signatureResult == GIT_OK.rawValue, let signatureUnwrap = signature else { 70 | let err = NSError(gitError: signatureResult, pointOfFailure: "git_signature_new") 71 | return .failure(err) 72 | } 73 | return .success(signatureUnwrap) 74 | } 75 | } 76 | 77 | extension Signature: Hashable { 78 | public func hash(into hasher: inout Hasher) { 79 | hasher.combine(name) 80 | hasher.combine(email) 81 | hasher.combine(time) 82 | } 83 | } 84 | 85 | /// A git commit. 86 | public struct Commit: ObjectType, Hashable { 87 | public static let type = GIT_OBJECT_COMMIT 88 | 89 | /// The OID of the commit. 90 | public let oid: OID 91 | 92 | /// The OID of the commit's tree. 93 | public let tree: PointerTo 94 | 95 | /// The OIDs of the commit's parents. 96 | public let parents: [PointerTo] 97 | 98 | /// The author of the commit. 99 | public let author: Signature 100 | 101 | /// The committer of the commit. 102 | public let committer: Signature 103 | 104 | /// The full message of the commit. 105 | public let message: String 106 | 107 | /// Create an instance with a libgit2 `git_commit` object. 108 | public init(_ pointer: OpaquePointer) { 109 | oid = OID(git_object_id(pointer).pointee) 110 | message = String(validatingUTF8: git_commit_message(pointer))! 111 | author = Signature(git_commit_author(pointer).pointee) 112 | committer = Signature(git_commit_committer(pointer).pointee) 113 | tree = PointerTo(OID(git_commit_tree_id(pointer).pointee)) 114 | 115 | self.parents = (0.. Bool { 22 | return lhs.oid == rhs.oid 23 | && lhs.type == rhs.type 24 | } 25 | 26 | func hash(into hasher: inout Hasher) { 27 | hasher.combine(oid) 28 | } 29 | } 30 | 31 | /// A pointer to a git object. 32 | public enum Pointer: PointerType { 33 | case commit(OID) 34 | case tree(OID) 35 | case blob(OID) 36 | case tag(OID) 37 | 38 | public var oid: OID { 39 | switch self { 40 | case let .commit(oid): 41 | return oid 42 | case let .tree(oid): 43 | return oid 44 | case let .blob(oid): 45 | return oid 46 | case let .tag(oid): 47 | return oid 48 | } 49 | } 50 | 51 | public var type: git_object_t { 52 | switch self { 53 | case .commit: 54 | return GIT_OBJECT_COMMIT 55 | case .tree: 56 | return GIT_OBJECT_TREE 57 | case .blob: 58 | return GIT_OBJECT_BLOB 59 | case .tag: 60 | return GIT_OBJECT_TAG 61 | } 62 | } 63 | 64 | /// Create an instance with an OID and a libgit2 `git_object_t`. 65 | init?(oid: OID, type: git_object_t) { 66 | switch type { 67 | case GIT_OBJECT_COMMIT: 68 | self = .commit(oid) 69 | case GIT_OBJECT_TREE: 70 | self = .tree(oid) 71 | case GIT_OBJECT_BLOB: 72 | self = .blob(oid) 73 | case GIT_OBJECT_TAG: 74 | self = .tag(oid) 75 | default: 76 | return nil 77 | } 78 | } 79 | } 80 | 81 | extension Pointer: CustomStringConvertible { 82 | public var description: String { 83 | switch self { 84 | case .commit: 85 | return "commit(\(oid))" 86 | case .tree: 87 | return "tree(\(oid))" 88 | case .blob: 89 | return "blob(\(oid))" 90 | case .tag: 91 | return "tag(\(oid))" 92 | } 93 | } 94 | } 95 | 96 | public struct PointerTo: PointerType { 97 | public let oid: OID 98 | 99 | public var type: git_object_t { 100 | return T.type 101 | } 102 | 103 | public init(_ oid: OID) { 104 | self.oid = oid 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /SwiftGit2/References.swift: -------------------------------------------------------------------------------- 1 | // 2 | // References.swift 3 | // SwiftGit2 4 | // 5 | // Created by Matt Diephouse on 1/2/15. 6 | // Copyright (c) 2015 GitHub, Inc. All rights reserved. 7 | // 8 | 9 | import Clibgit2 10 | 11 | /// A reference to a git object. 12 | public protocol ReferenceType { 13 | /// The full name of the reference (e.g., `refs/heads/master`). 14 | var longName: String { get } 15 | 16 | /// The short human-readable name of the reference if one exists (e.g., `master`). 17 | var shortName: String? { get } 18 | 19 | /// The OID of the referenced object. 20 | var oid: OID { get } 21 | } 22 | 23 | public extension ReferenceType { 24 | static func == (lhs: Self, rhs: Self) -> Bool { 25 | return lhs.longName == rhs.longName 26 | && lhs.oid == rhs.oid 27 | } 28 | 29 | func hash(into hasher: inout Hasher) { 30 | hasher.combine(longName) 31 | hasher.combine(oid) 32 | } 33 | } 34 | 35 | /// Create a Reference, Branch, or TagReference from a libgit2 `git_reference`. 36 | internal func referenceWithLibGit2Reference(_ pointer: OpaquePointer) -> ReferenceType { 37 | if git_reference_is_branch(pointer) != 0 || git_reference_is_remote(pointer) != 0 { 38 | return Branch(pointer)! 39 | } else if git_reference_is_tag(pointer) != 0 { 40 | return TagReference(pointer)! 41 | } else { 42 | return Reference(pointer) 43 | } 44 | } 45 | 46 | /// A generic reference to a git object. 47 | public struct Reference: ReferenceType, Hashable { 48 | /// The full name of the reference (e.g., `refs/heads/master`). 49 | public let longName: String 50 | 51 | /// The short human-readable name of the reference if one exists (e.g., `master`). 52 | public let shortName: String? 53 | 54 | /// The OID of the referenced object. 55 | public let oid: OID 56 | 57 | /// Create an instance with a libgit2 `git_reference` object. 58 | public init(_ pointer: OpaquePointer) { 59 | let shorthand = String(validatingUTF8: git_reference_shorthand(pointer))! 60 | longName = String(validatingUTF8: git_reference_name(pointer))! 61 | shortName = (shorthand == longName ? nil : shorthand) 62 | oid = OID(git_reference_target(pointer).pointee) 63 | } 64 | } 65 | 66 | /// A git branch. 67 | public struct Branch: ReferenceType, Hashable { 68 | /// The full name of the reference (e.g., `refs/heads/master`). 69 | public let longName: String 70 | 71 | /// The short human-readable name of the branch (e.g., `master`). 72 | public let name: String 73 | 74 | /// A pointer to the referenced commit. 75 | public let commit: PointerTo 76 | 77 | // MARK: Derived Properties 78 | 79 | /// The short human-readable name of the branch (e.g., `master`). 80 | /// 81 | /// This is the same as `name`, but is declared with an Optional type to adhere to 82 | /// `ReferenceType`. 83 | public var shortName: String? { return name } 84 | 85 | /// The OID of the referenced object. 86 | /// 87 | /// This is the same as `commit.oid`, but is declared here to adhere to `ReferenceType`. 88 | public var oid: OID { return commit.oid } 89 | 90 | /// Whether the branch is a local branch. 91 | public var isLocal: Bool { return longName.hasPrefix("refs/heads/") } 92 | 93 | /// Whether the branch is a remote branch. 94 | public var isRemote: Bool { return longName.hasPrefix("refs/remotes/") } 95 | 96 | /// Create an instance with a libgit2 `git_reference` object. 97 | /// 98 | /// Returns `nil` if the pointer isn't a branch. 99 | public init?(_ pointer: OpaquePointer) { 100 | var namePointer: UnsafePointer? = nil 101 | let success = git_branch_name(&namePointer, pointer) 102 | guard success == GIT_OK.rawValue else { 103 | return nil 104 | } 105 | name = String(validatingUTF8: namePointer!)! 106 | 107 | longName = String(validatingUTF8: git_reference_name(pointer))! 108 | 109 | var oid: OID 110 | if git_reference_type(pointer).rawValue == GIT_REFERENCE_SYMBOLIC.rawValue { 111 | var resolved: OpaquePointer? = nil 112 | let success = git_reference_resolve(&resolved, pointer) 113 | guard success == GIT_OK.rawValue else { 114 | return nil 115 | } 116 | oid = OID(git_reference_target(resolved).pointee) 117 | git_reference_free(resolved) 118 | } else { 119 | oid = OID(git_reference_target(pointer).pointee) 120 | } 121 | commit = PointerTo(oid) 122 | } 123 | } 124 | 125 | /// A git tag reference, which can be either a lightweight tag or a Tag object. 126 | public enum TagReference: ReferenceType, Hashable { 127 | /// A lightweight tag, which is just a name and an OID. 128 | case lightweight(String, OID) 129 | 130 | /// An annotated tag, which points to a Tag object. 131 | case annotated(String, Tag) 132 | 133 | /// The full name of the reference (e.g., `refs/tags/my-tag`). 134 | public var longName: String { 135 | switch self { 136 | case let .lightweight(name, _): 137 | return name 138 | case let .annotated(name, _): 139 | return name 140 | } 141 | } 142 | 143 | /// The short human-readable name of the branch (e.g., `master`). 144 | public var name: String { 145 | return String(longName["refs/tags/".endIndex...]) 146 | } 147 | 148 | /// The OID of the target object. 149 | /// 150 | /// If this is an annotated tag, the OID will be the tag's target. 151 | public var oid: OID { 152 | switch self { 153 | case let .lightweight(_, oid): 154 | return oid 155 | case let .annotated(_, tag): 156 | return tag.target.oid 157 | } 158 | } 159 | 160 | // MARK: Derived Properties 161 | 162 | /// The short human-readable name of the branch (e.g., `master`). 163 | /// 164 | /// This is the same as `name`, but is declared with an Optional type to adhere to 165 | /// `ReferenceType`. 166 | public var shortName: String? { return name } 167 | 168 | /// Create an instance with a libgit2 `git_reference` object. 169 | /// 170 | /// Returns `nil` if the pointer isn't a branch. 171 | public init?(_ pointer: OpaquePointer) { 172 | if git_reference_is_tag(pointer) == 0 { 173 | return nil 174 | } 175 | 176 | let name = String(validatingUTF8: git_reference_name(pointer))! 177 | let repo = git_reference_owner(pointer) 178 | var oid = git_reference_target(pointer).pointee 179 | 180 | var pointer: OpaquePointer? = nil 181 | let result = git_object_lookup(&pointer, repo, &oid, GIT_OBJECT_TAG) 182 | if result == GIT_OK.rawValue { 183 | self = .annotated(name, Tag(pointer!)) 184 | } else { 185 | self = .lightweight(name, OID(oid)) 186 | } 187 | git_object_free(pointer) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /SwiftGit2/Remotes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Remotes.swift 3 | // SwiftGit2 4 | // 5 | // Created by Matt Diephouse on 1/2/15. 6 | // Copyright (c) 2015 GitHub, Inc. All rights reserved. 7 | // 8 | 9 | import Clibgit2 10 | 11 | /// A remote in a git repository. 12 | public struct Remote: Hashable { 13 | /// The name of the remote. 14 | public let name: String 15 | 16 | /// The URL of the remote. 17 | /// 18 | /// This may be an SSH URL, which isn't representable using `NSURL`. 19 | public let URL: String 20 | 21 | /// Create an instance with a libgit2 `git_remote`. 22 | public init(_ pointer: OpaquePointer) { 23 | name = String(validatingUTF8: git_remote_name(pointer))! 24 | URL = String(validatingUTF8: git_remote_url(pointer))! 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SwiftGit2/Repository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Repository.swift 3 | // SwiftGit2 4 | // 5 | // Created by Matt Diephouse on 11/7/14. 6 | // Copyright (c) 2014 GitHub, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Clibgit2 11 | 12 | public typealias CheckoutProgressBlock = (String?, Int, Int) -> Void 13 | 14 | /// Helper function used as the libgit2 progress callback in git_checkout_options. 15 | /// This is a function with a type signature of git_checkout_progress_cb. 16 | private func checkoutProgressCallback(path: UnsafePointer?, completedSteps: Int, totalSteps: Int, 17 | payload: UnsafeMutableRawPointer?) { 18 | if let payload = payload { 19 | let buffer = payload.assumingMemoryBound(to: CheckoutProgressBlock.self) 20 | let block: CheckoutProgressBlock 21 | if completedSteps < totalSteps { 22 | block = buffer.pointee 23 | } else { 24 | block = buffer.move() 25 | buffer.deallocate() 26 | } 27 | block(path.flatMap(String.init(validatingUTF8:)), completedSteps, totalSteps) 28 | } 29 | } 30 | 31 | /// Helper function for initializing libgit2 git_checkout_options. 32 | /// 33 | /// :param: strategy The strategy to be used when checking out the repo, see CheckoutStrategy 34 | /// :param: progress A block that's called with the progress of the checkout. 35 | /// :returns: Returns a git_checkout_options struct with the progress members set. 36 | private func checkoutOptions(strategy: CheckoutStrategy, 37 | progress: CheckoutProgressBlock? = nil) -> git_checkout_options { 38 | // Do this because GIT_CHECKOUT_OPTIONS_INIT is unavailable in swift 39 | let pointer = UnsafeMutablePointer.allocate(capacity: 1) 40 | git_checkout_init_options(pointer, UInt32(GIT_CHECKOUT_OPTIONS_VERSION)) 41 | var options = pointer.move() 42 | pointer.deallocate() 43 | 44 | options.checkout_strategy = strategy.gitCheckoutStrategy.rawValue 45 | 46 | if progress != nil { 47 | options.progress_cb = checkoutProgressCallback 48 | let blockPointer = UnsafeMutablePointer.allocate(capacity: 1) 49 | blockPointer.initialize(to: progress!) 50 | options.progress_payload = UnsafeMutableRawPointer(blockPointer) 51 | } 52 | 53 | return options 54 | } 55 | 56 | private func fetchOptions(credentials: Credentials) -> git_fetch_options { 57 | let pointer = UnsafeMutablePointer.allocate(capacity: 1) 58 | git_fetch_init_options(pointer, UInt32(GIT_FETCH_OPTIONS_VERSION)) 59 | 60 | var options = pointer.move() 61 | 62 | pointer.deallocate() 63 | 64 | options.callbacks.payload = credentials.toPointer() 65 | options.callbacks.credentials = credentialsCallback 66 | 67 | return options 68 | } 69 | 70 | private func cloneOptions(bare: Bool = false, localClone: Bool = false, fetchOptions: git_fetch_options? = nil, 71 | checkoutOptions: git_checkout_options? = nil) -> git_clone_options { 72 | let pointer = UnsafeMutablePointer.allocate(capacity: 1) 73 | git_clone_init_options(pointer, UInt32(GIT_CLONE_OPTIONS_VERSION)) 74 | 75 | var options = pointer.move() 76 | 77 | pointer.deallocate() 78 | 79 | options.bare = bare ? 1 : 0 80 | 81 | if localClone { 82 | options.local = GIT_CLONE_NO_LOCAL 83 | } 84 | 85 | if let checkoutOptions = checkoutOptions { 86 | options.checkout_opts = checkoutOptions 87 | } 88 | 89 | if let fetchOptions = fetchOptions { 90 | options.fetch_opts = fetchOptions 91 | } 92 | 93 | return options 94 | } 95 | 96 | /// A git repository. 97 | public final class Repository { 98 | 99 | /// MARK: - Initialize libgit2 library, must be called by client before anything else 100 | public static func initialize_libgit2() { 101 | git_libgit2_init() 102 | } 103 | 104 | // MARK: - Creating Repositories 105 | 106 | /// Create a new repository at the given URL. 107 | /// 108 | /// URL - The URL of the repository. 109 | /// 110 | /// Returns a `Result` with a `Repository` or an error. 111 | public class func create(at url: URL) -> Result { 112 | var pointer: OpaquePointer? = nil 113 | let result = url.withUnsafeFileSystemRepresentation { 114 | git_repository_init(&pointer, $0, 0) 115 | } 116 | 117 | guard result == GIT_OK.rawValue else { 118 | return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_init")) 119 | } 120 | 121 | let repository = Repository(pointer!) 122 | return Result.success(repository) 123 | } 124 | 125 | /// Load the repository at the given URL. 126 | /// 127 | /// URL - The URL of the repository. 128 | /// 129 | /// Returns a `Result` with a `Repository` or an error. 130 | public class func at(_ url: URL) -> Result { 131 | var pointer: OpaquePointer? = nil 132 | let result = url.withUnsafeFileSystemRepresentation { 133 | git_repository_open(&pointer, $0) 134 | } 135 | 136 | guard result == GIT_OK.rawValue else { 137 | return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_open")) 138 | } 139 | 140 | let repository = Repository(pointer!) 141 | return Result.success(repository) 142 | } 143 | 144 | /// Clone the repository from a given URL. 145 | /// 146 | /// remoteURL - The URL of the remote repository 147 | /// localURL - The URL to clone the remote repository into 148 | /// localClone - Will not bypass the git-aware transport, even if remote is local. 149 | /// bare - Clone remote as a bare repository. 150 | /// credentials - Credentials to be used when connecting to the remote. 151 | /// checkoutStrategy - The checkout strategy to use, if being checked out. 152 | /// checkoutProgress - A block that's called with the progress of the checkout. 153 | /// 154 | /// Returns a `Result` with a `Repository` or an error. 155 | public class func clone(from remoteURL: URL, to localURL: URL, localClone: Bool = false, bare: Bool = false, 156 | credentials: Credentials = .default, checkoutStrategy: CheckoutStrategy = .Safe, 157 | checkoutProgress: CheckoutProgressBlock? = nil) -> Result { 158 | var options = cloneOptions( 159 | bare: bare, 160 | localClone: localClone, 161 | fetchOptions: fetchOptions(credentials: credentials), 162 | checkoutOptions: checkoutOptions(strategy: checkoutStrategy, progress: checkoutProgress)) 163 | 164 | var pointer: OpaquePointer? = nil 165 | let remoteURLString = (remoteURL as NSURL).isFileReferenceURL() ? remoteURL.path : remoteURL.absoluteString 166 | let result = localURL.withUnsafeFileSystemRepresentation { localPath in 167 | git_clone(&pointer, remoteURLString, localPath, &options) 168 | } 169 | 170 | guard result == GIT_OK.rawValue else { 171 | return Result.failure(NSError(gitError: result, pointOfFailure: "git_clone")) 172 | } 173 | 174 | let repository = Repository(pointer!) 175 | return Result.success(repository) 176 | } 177 | 178 | // MARK: - Initializers 179 | 180 | /// Create an instance with a libgit2 `git_repository` object. 181 | /// 182 | /// The Repository assumes ownership of the `git_repository` object. 183 | public init(_ pointer: OpaquePointer) { 184 | self.pointer = pointer 185 | 186 | let path = git_repository_workdir(pointer) 187 | self.directoryURL = path.map({ URL(fileURLWithPath: String(validatingUTF8: $0)!, isDirectory: true) }) 188 | } 189 | 190 | deinit { 191 | git_repository_free(pointer) 192 | } 193 | 194 | // MARK: - Properties 195 | 196 | /// The underlying libgit2 `git_repository` object. 197 | public let pointer: OpaquePointer 198 | 199 | /// The URL of the repository's working directory, or `nil` if the 200 | /// repository is bare. 201 | public let directoryURL: URL? 202 | 203 | // MARK: - Object Lookups 204 | 205 | /// Load a libgit2 object and transform it to something else. 206 | /// 207 | /// oid - The OID of the object to look up. 208 | /// type - The type of the object to look up. 209 | /// transform - A function that takes the libgit2 object and transforms it 210 | /// into something else. 211 | /// 212 | /// Returns the result of calling `transform` or an error if the object 213 | /// cannot be loaded. 214 | private func withGitObject(_ oid: OID, type: git_object_t, 215 | transform: (OpaquePointer) -> Result) -> Result { 216 | var pointer: OpaquePointer? = nil 217 | var oid = oid.oid 218 | let result = git_object_lookup(&pointer, self.pointer, &oid, type) 219 | 220 | guard result == GIT_OK.rawValue else { 221 | return Result.failure(NSError(gitError: result, pointOfFailure: "git_object_lookup")) 222 | } 223 | 224 | let value = transform(pointer!) 225 | git_object_free(pointer) 226 | return value 227 | } 228 | 229 | private func withGitObject(_ oid: OID, type: git_object_t, transform: (OpaquePointer) -> T) -> Result { 230 | return withGitObject(oid, type: type) { Result.success(transform($0)) } 231 | } 232 | 233 | private func withGitObjects(_ oids: [OID], type: git_object_t, transform: ([OpaquePointer]) -> Result) -> Result { 234 | var pointers = [OpaquePointer]() 235 | defer { 236 | for pointer in pointers { 237 | git_object_free(pointer) 238 | } 239 | } 240 | 241 | for oid in oids { 242 | var pointer: OpaquePointer? = nil 243 | var oid = oid.oid 244 | let result = git_object_lookup(&pointer, self.pointer, &oid, type) 245 | 246 | guard result == GIT_OK.rawValue else { 247 | return Result.failure(NSError(gitError: result, pointOfFailure: "git_object_lookup")) 248 | } 249 | 250 | pointers.append(pointer!) 251 | } 252 | 253 | return transform(pointers) 254 | } 255 | 256 | /// Loads the object with the given OID. 257 | /// 258 | /// oid - The OID of the blob to look up. 259 | /// 260 | /// Returns a `Blob`, `Commit`, `Tag`, or `Tree` if one exists, or an error. 261 | public func object(_ oid: OID) -> Result { 262 | return withGitObject(oid, type: GIT_OBJECT_ANY) { object in 263 | let type = git_object_type(object) 264 | if type == Blob.type { 265 | return Result.success(Blob(object)) 266 | } else if type == Commit.type { 267 | return Result.success(Commit(object)) 268 | } else if type == Tag.type { 269 | return Result.success(Tag(object)) 270 | } else if type == Tree.type { 271 | return Result.success(Tree(object)) 272 | } 273 | 274 | let error = NSError( 275 | domain: "org.libgit2.SwiftGit2", 276 | code: 1, 277 | userInfo: [ 278 | NSLocalizedDescriptionKey: "Unrecognized git_object_t '\(type)' for oid '\(oid)'.", 279 | ] 280 | ) 281 | return Result.failure(error) 282 | } 283 | } 284 | 285 | /// Loads the blob with the given OID. 286 | /// 287 | /// oid - The OID of the blob to look up. 288 | /// 289 | /// Returns the blob if it exists, or an error. 290 | public func blob(_ oid: OID) -> Result { 291 | return withGitObject(oid, type: GIT_OBJECT_BLOB) { Blob($0) } 292 | } 293 | 294 | /// Loads the commit with the given OID. 295 | /// 296 | /// oid - The OID of the commit to look up. 297 | /// 298 | /// Returns the commit if it exists, or an error. 299 | public func commit(_ oid: OID) -> Result { 300 | return withGitObject(oid, type: GIT_OBJECT_COMMIT) { Commit($0) } 301 | } 302 | 303 | /// Loads the tag with the given OID. 304 | /// 305 | /// oid - The OID of the tag to look up. 306 | /// 307 | /// Returns the tag if it exists, or an error. 308 | public func tag(_ oid: OID) -> Result { 309 | return withGitObject(oid, type: GIT_OBJECT_TAG) { Tag($0) } 310 | } 311 | 312 | /// Loads the tree with the given OID. 313 | /// 314 | /// oid - The OID of the tree to look up. 315 | /// 316 | /// Returns the tree if it exists, or an error. 317 | public func tree(_ oid: OID) -> Result { 318 | return withGitObject(oid, type: GIT_OBJECT_TREE) { Tree($0) } 319 | } 320 | 321 | /// Loads the referenced object from the pointer. 322 | /// 323 | /// pointer - A pointer to an object. 324 | /// 325 | /// Returns the object if it exists, or an error. 326 | public func object(from pointer: PointerTo) -> Result { 327 | return withGitObject(pointer.oid, type: pointer.type) { T($0) } 328 | } 329 | 330 | /// Loads the referenced object from the pointer. 331 | /// 332 | /// pointer - A pointer to an object. 333 | /// 334 | /// Returns the object if it exists, or an error. 335 | public func object(from pointer: Pointer) -> Result { 336 | switch pointer { 337 | case let .blob(oid): 338 | return blob(oid).map { $0 as ObjectType } 339 | case let .commit(oid): 340 | return commit(oid).map { $0 as ObjectType } 341 | case let .tag(oid): 342 | return tag(oid).map { $0 as ObjectType } 343 | case let .tree(oid): 344 | return tree(oid).map { $0 as ObjectType } 345 | } 346 | } 347 | 348 | // MARK: - Remote Lookups 349 | 350 | /// Loads all the remotes in the repository. 351 | /// 352 | /// Returns an array of remotes, or an error. 353 | public func allRemotes() -> Result<[Remote], NSError> { 354 | let pointer = UnsafeMutablePointer.allocate(capacity: 1) 355 | let result = git_remote_list(pointer, self.pointer) 356 | 357 | guard result == GIT_OK.rawValue else { 358 | pointer.deallocate() 359 | return Result.failure(NSError(gitError: result, pointOfFailure: "git_remote_list")) 360 | } 361 | 362 | let strarray = pointer.pointee 363 | let remotes: [Result] = strarray.map { 364 | return self.remote(named: $0) 365 | } 366 | git_strarray_free(pointer) 367 | pointer.deallocate() 368 | 369 | return remotes.aggregateResult() 370 | } 371 | 372 | private func remoteLookup(named name: String, _ callback: (Result) -> A) -> A { 373 | var pointer: OpaquePointer? = nil 374 | defer { git_remote_free(pointer) } 375 | 376 | let result = git_remote_lookup(&pointer, self.pointer, name) 377 | 378 | guard result == GIT_OK.rawValue else { 379 | return callback(.failure(NSError(gitError: result, pointOfFailure: "git_remote_lookup"))) 380 | } 381 | 382 | return callback(.success(pointer!)) 383 | } 384 | 385 | /// Load a remote from the repository. 386 | /// 387 | /// name - The name of the remote. 388 | /// 389 | /// Returns the remote if it exists, or an error. 390 | public func remote(named name: String) -> Result { 391 | return remoteLookup(named: name) { $0.map(Remote.init) } 392 | } 393 | 394 | /// Download new data and update tips 395 | public func fetch(_ remote: Remote) -> Result<(), NSError> { 396 | return remoteLookup(named: remote.name) { remote in 397 | remote.flatMap { pointer in 398 | var opts = git_fetch_options() 399 | let resultInit = git_fetch_init_options(&opts, UInt32(GIT_FETCH_OPTIONS_VERSION)) 400 | assert(resultInit == GIT_OK.rawValue) 401 | 402 | let result = git_remote_fetch(pointer, nil, &opts, nil) 403 | guard result == GIT_OK.rawValue else { 404 | let err = NSError(gitError: result, pointOfFailure: "git_remote_fetch") 405 | return .failure(err) 406 | } 407 | return .success(()) 408 | } 409 | } 410 | } 411 | 412 | // MARK: - Reference Lookups 413 | 414 | /// Load all the references with the given prefix (e.g. "refs/heads/") 415 | public func references(withPrefix prefix: String) -> Result<[ReferenceType], NSError> { 416 | let pointer = UnsafeMutablePointer.allocate(capacity: 1) 417 | let result = git_reference_list(pointer, self.pointer) 418 | 419 | guard result == GIT_OK.rawValue else { 420 | pointer.deallocate() 421 | return Result.failure(NSError(gitError: result, pointOfFailure: "git_reference_list")) 422 | } 423 | 424 | let strarray = pointer.pointee 425 | let references = strarray 426 | .filter { 427 | $0.hasPrefix(prefix) 428 | } 429 | .map { 430 | self.reference(named: $0) 431 | } 432 | git_strarray_free(pointer) 433 | pointer.deallocate() 434 | 435 | return references.aggregateResult() 436 | } 437 | 438 | /// Load the reference with the given long name (e.g. "refs/heads/master") 439 | /// 440 | /// If the reference is a branch, a `Branch` will be returned. If the 441 | /// reference is a tag, a `TagReference` will be returned. Otherwise, a 442 | /// `Reference` will be returned. 443 | public func reference(named name: String) -> Result { 444 | var pointer: OpaquePointer? = nil 445 | let result = git_reference_lookup(&pointer, self.pointer, name) 446 | 447 | guard result == GIT_OK.rawValue else { 448 | return Result.failure(NSError(gitError: result, pointOfFailure: "git_reference_lookup")) 449 | } 450 | 451 | let value = referenceWithLibGit2Reference(pointer!) 452 | git_reference_free(pointer) 453 | return Result.success(value) 454 | } 455 | 456 | /// Load and return a list of all local branches. 457 | public func localBranches() -> Result<[Branch], NSError> { 458 | return references(withPrefix: "refs/heads/") 459 | .map { (refs: [ReferenceType]) in 460 | return refs.map { $0 as! Branch } 461 | } 462 | } 463 | 464 | /// Load and return a list of all remote branches. 465 | public func remoteBranches() -> Result<[Branch], NSError> { 466 | return references(withPrefix: "refs/remotes/") 467 | .map { (refs: [ReferenceType]) in 468 | return refs.map { $0 as! Branch } 469 | } 470 | } 471 | 472 | /// Load the local branch with the given name (e.g., "master"). 473 | public func localBranch(named name: String) -> Result { 474 | return reference(named: "refs/heads/" + name).map { $0 as! Branch } 475 | } 476 | 477 | /// Load the remote branch with the given name (e.g., "origin/master"). 478 | public func remoteBranch(named name: String) -> Result { 479 | return reference(named: "refs/remotes/" + name).map { $0 as! Branch } 480 | } 481 | 482 | /// Load and return a list of all the `TagReference`s. 483 | public func allTags() -> Result<[TagReference], NSError> { 484 | return references(withPrefix: "refs/tags/") 485 | .map { (refs: [ReferenceType]) in 486 | return refs.map { $0 as! TagReference } 487 | } 488 | } 489 | 490 | /// Load the tag with the given name (e.g., "tag-2"). 491 | public func tag(named name: String) -> Result { 492 | return reference(named: "refs/tags/" + name).map { $0 as! TagReference } 493 | } 494 | 495 | // MARK: - Working Directory 496 | 497 | /// Load the reference pointed at by HEAD. 498 | /// 499 | /// When on a branch, this will return the current `Branch`. 500 | public func HEAD() -> Result { 501 | var pointer: OpaquePointer? = nil 502 | let result = git_repository_head(&pointer, self.pointer) 503 | guard result == GIT_OK.rawValue else { 504 | return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_head")) 505 | } 506 | let value = referenceWithLibGit2Reference(pointer!) 507 | git_reference_free(pointer) 508 | return Result.success(value) 509 | } 510 | 511 | /// Set HEAD to the given oid (detached). 512 | /// 513 | /// :param: oid The OID to set as HEAD. 514 | /// :returns: Returns a result with void or the error that occurred. 515 | public func setHEAD(_ oid: OID) -> Result<(), NSError> { 516 | var oid = oid.oid 517 | let result = git_repository_set_head_detached(self.pointer, &oid) 518 | guard result == GIT_OK.rawValue else { 519 | return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_set_head")) 520 | } 521 | return Result.success(()) 522 | } 523 | 524 | /// Set HEAD to the given reference. 525 | /// 526 | /// :param: reference The reference to set as HEAD. 527 | /// :returns: Returns a result with void or the error that occurred. 528 | public func setHEAD(_ reference: ReferenceType) -> Result<(), NSError> { 529 | let result = git_repository_set_head(self.pointer, reference.longName) 530 | guard result == GIT_OK.rawValue else { 531 | return Result.failure(NSError(gitError: result, pointOfFailure: "git_repository_set_head")) 532 | } 533 | return Result.success(()) 534 | } 535 | 536 | /// Check out HEAD. 537 | /// 538 | /// :param: strategy The checkout strategy to use. 539 | /// :param: progress A block that's called with the progress of the checkout. 540 | /// :returns: Returns a result with void or the error that occurred. 541 | public func checkout(strategy: CheckoutStrategy, progress: CheckoutProgressBlock? = nil) -> Result<(), NSError> { 542 | var options = checkoutOptions(strategy: strategy, progress: progress) 543 | 544 | let result = git_checkout_head(self.pointer, &options) 545 | guard result == GIT_OK.rawValue else { 546 | return Result.failure(NSError(gitError: result, pointOfFailure: "git_checkout_head")) 547 | } 548 | 549 | return Result.success(()) 550 | } 551 | 552 | /// Check out the given OID. 553 | /// 554 | /// :param: oid The OID of the commit to check out. 555 | /// :param: strategy The checkout strategy to use. 556 | /// :param: progress A block that's called with the progress of the checkout. 557 | /// :returns: Returns a result with void or the error that occurred. 558 | public func checkout(_ oid: OID, strategy: CheckoutStrategy, 559 | progress: CheckoutProgressBlock? = nil) -> Result<(), NSError> { 560 | return setHEAD(oid).flatMap { self.checkout(strategy: strategy, progress: progress) } 561 | } 562 | 563 | /// Check out the given reference. 564 | /// 565 | /// :param: reference The reference to check out. 566 | /// :param: strategy The checkout strategy to use. 567 | /// :param: progress A block that's called with the progress of the checkout. 568 | /// :returns: Returns a result with void or the error that occurred. 569 | public func checkout(_ reference: ReferenceType, strategy: CheckoutStrategy, 570 | progress: CheckoutProgressBlock? = nil) -> Result<(), NSError> { 571 | return setHEAD(reference).flatMap { self.checkout(strategy: strategy, progress: progress) } 572 | } 573 | 574 | /// Load all commits in the specified branch in topological & time order descending 575 | /// 576 | /// :param: branch The branch to get all commits from 577 | /// :returns: Returns a result with array of branches or the error that occurred 578 | public func commits(in branch: Branch) -> CommitIterator { 579 | let iterator = CommitIterator(repo: self, root: branch.oid.oid) 580 | return iterator 581 | } 582 | 583 | /// Get the index for the repo. The caller is responsible for freeing the index. 584 | func unsafeIndex() -> Result { 585 | var index: OpaquePointer? = nil 586 | let result = git_repository_index(&index, self.pointer) 587 | guard result == GIT_OK.rawValue && index != nil else { 588 | let err = NSError(gitError: result, pointOfFailure: "git_repository_index") 589 | return .failure(err) 590 | } 591 | return .success(index!) 592 | } 593 | 594 | /// Stage the file(s) under the specified path. 595 | public func add(path: String) -> Result<(), NSError> { 596 | var dirPointer = UnsafeMutablePointer(mutating: (path as NSString).utf8String) 597 | var paths = withUnsafeMutablePointer(to: &dirPointer) { 598 | git_strarray(strings: $0, count: 1) 599 | } 600 | return unsafeIndex().flatMap { index in 601 | defer { git_index_free(index) } 602 | let addResult = git_index_add_all(index, &paths, 0, nil, nil) 603 | guard addResult == GIT_OK.rawValue else { 604 | return .failure(NSError(gitError: addResult, pointOfFailure: "git_index_add_all")) 605 | } 606 | // write index to disk 607 | let writeResult = git_index_write(index) 608 | guard writeResult == GIT_OK.rawValue else { 609 | return .failure(NSError(gitError: writeResult, pointOfFailure: "git_index_write")) 610 | } 611 | return .success(()) 612 | } 613 | } 614 | 615 | /// Perform a commit with arbitrary numbers of parent commits. 616 | public func commit( 617 | tree treeOID: OID, 618 | parents: [Commit], 619 | message: String, 620 | signature: Signature 621 | ) -> Result { 622 | // create commit signature 623 | return signature.makeUnsafeSignature().flatMap { signature in 624 | defer { git_signature_free(signature) } 625 | var tree: OpaquePointer? = nil 626 | var treeOIDCopy = treeOID.oid 627 | let lookupResult = git_tree_lookup(&tree, self.pointer, &treeOIDCopy) 628 | guard lookupResult == GIT_OK.rawValue else { 629 | let err = NSError(gitError: lookupResult, pointOfFailure: "git_tree_lookup") 630 | return .failure(err) 631 | } 632 | defer { git_tree_free(tree) } 633 | 634 | var msgBuf = git_buf() 635 | git_message_prettify(&msgBuf, message, 0, /* ascii for # */ 35) 636 | defer { git_buf_free(&msgBuf) } 637 | 638 | // libgit2 expects a C-like array of parent git_commit pointer 639 | var parentGitCommits: [OpaquePointer?] = [] 640 | defer { 641 | for commit in parentGitCommits { 642 | git_commit_free(commit) 643 | } 644 | } 645 | for parentCommit in parents { 646 | var parent: OpaquePointer? = nil 647 | var oid = parentCommit.oid.oid 648 | let lookupResult = git_commit_lookup(&parent, self.pointer, &oid) 649 | guard lookupResult == GIT_OK.rawValue else { 650 | let err = NSError(gitError: lookupResult, pointOfFailure: "git_commit_lookup") 651 | return .failure(err) 652 | } 653 | parentGitCommits.append(parent!) 654 | } 655 | 656 | let parentsContiguous = ContiguousArray(parentGitCommits) 657 | return parentsContiguous.withUnsafeBufferPointer { unsafeBuffer in 658 | var commitOID = git_oid() 659 | let parentsPtr = UnsafeMutablePointer(mutating: unsafeBuffer.baseAddress) 660 | let result = git_commit_create( 661 | &commitOID, 662 | self.pointer, 663 | "HEAD", 664 | signature, 665 | signature, 666 | "UTF-8", 667 | msgBuf.ptr, 668 | tree, 669 | parents.count, 670 | parentsPtr 671 | ) 672 | guard result == GIT_OK.rawValue else { 673 | return .failure(NSError(gitError: result, pointOfFailure: "git_commit_create")) 674 | } 675 | return commit(OID(commitOID)) 676 | } 677 | } 678 | } 679 | 680 | /// Perform a commit of the staged files with the specified message and signature, 681 | /// assuming we are not doing a merge and using the current tip as the parent. 682 | public func commit(message: String, signature: Signature) -> Result { 683 | return unsafeIndex().flatMap { index in 684 | defer { git_index_free(index) } 685 | var treeOID = git_oid() 686 | let treeResult = git_index_write_tree(&treeOID, index) 687 | guard treeResult == GIT_OK.rawValue else { 688 | let err = NSError(gitError: treeResult, pointOfFailure: "git_index_write_tree") 689 | return .failure(err) 690 | } 691 | var parentID = git_oid() 692 | let nameToIDResult = git_reference_name_to_id(&parentID, self.pointer, "HEAD") 693 | guard nameToIDResult == GIT_OK.rawValue else { 694 | return .failure(NSError(gitError: nameToIDResult, pointOfFailure: "git_reference_name_to_id")) 695 | } 696 | return commit(OID(parentID)).flatMap { parentCommit in 697 | commit(tree: OID(treeOID), parents: [parentCommit], message: message, signature: signature) 698 | } 699 | } 700 | } 701 | 702 | // MARK: - Diffs 703 | 704 | public func diff(for commit: Commit) -> Result { 705 | guard !commit.parents.isEmpty else { 706 | // Initial commit in a repository 707 | return self.diff(from: nil, to: commit.oid) 708 | } 709 | 710 | var mergeDiff: OpaquePointer? = nil 711 | defer { git_object_free(mergeDiff) } 712 | for parent in commit.parents { 713 | let error = self.diff(from: parent.oid, to: commit.oid) { 714 | switch $0 { 715 | case .failure(let error): 716 | return error 717 | 718 | case .success(let newDiff): 719 | if mergeDiff == nil { 720 | mergeDiff = newDiff 721 | } else { 722 | let mergeResult = git_diff_merge(mergeDiff, newDiff) 723 | guard mergeResult == GIT_OK.rawValue else { 724 | return NSError(gitError: mergeResult, pointOfFailure: "git_diff_merge") 725 | } 726 | } 727 | return nil 728 | } 729 | } 730 | 731 | if error != nil { 732 | return Result.failure(error!) 733 | } 734 | } 735 | 736 | return .success(Diff(mergeDiff!)) 737 | } 738 | 739 | private func diff(from oldCommitOid: OID?, to newCommitOid: OID?, transform: (Result) -> NSError?) -> NSError? { 740 | assert(oldCommitOid != nil || newCommitOid != nil, "It is an error to pass nil for both the oldOid and newOid") 741 | 742 | var oldTree: OpaquePointer? = nil 743 | defer { git_object_free(oldTree) } 744 | if let oid = oldCommitOid { 745 | switch unsafeTreeForCommitId(oid) { 746 | case .failure(let error): 747 | return transform(.failure(error)) 748 | case .success(let value): 749 | oldTree = value 750 | } 751 | } 752 | 753 | var newTree: OpaquePointer? = nil 754 | defer { git_object_free(newTree) } 755 | if let oid = newCommitOid { 756 | switch unsafeTreeForCommitId(oid) { 757 | case .failure(let error): 758 | return transform(.failure(error)) 759 | case .success(let value): 760 | newTree = value 761 | } 762 | } 763 | 764 | var diff: OpaquePointer? = nil 765 | let diffResult = git_diff_tree_to_tree(&diff, 766 | self.pointer, 767 | oldTree, 768 | newTree, 769 | nil) 770 | 771 | guard diffResult == GIT_OK.rawValue else { 772 | return transform(.failure(NSError(gitError: diffResult, 773 | pointOfFailure: "git_diff_tree_to_tree"))) 774 | } 775 | 776 | return transform(Result.success(diff!)) 777 | } 778 | 779 | /// Memory safe 780 | private func diff(from oldCommitOid: OID?, to newCommitOid: OID?) -> Result { 781 | assert(oldCommitOid != nil || newCommitOid != nil, "It is an error to pass nil for both the oldOid and newOid") 782 | 783 | var oldTree: Tree? = nil 784 | if let oldCommitOid = oldCommitOid { 785 | switch safeTreeForCommitId(oldCommitOid) { 786 | case .failure(let error): 787 | return .failure(error) 788 | case .success(let value): 789 | oldTree = value 790 | } 791 | } 792 | 793 | var newTree: Tree? = nil 794 | if let newCommitOid = newCommitOid { 795 | switch safeTreeForCommitId(newCommitOid) { 796 | case .failure(let error): 797 | return .failure(error) 798 | case .success(let value): 799 | newTree = value 800 | } 801 | } 802 | 803 | if oldTree != nil && newTree != nil { 804 | return withGitObjects([oldTree!.oid, newTree!.oid], type: GIT_OBJECT_TREE) { objects in 805 | var diff: OpaquePointer? = nil 806 | let diffResult = git_diff_tree_to_tree(&diff, 807 | self.pointer, 808 | objects[0], 809 | objects[1], 810 | nil) 811 | return processTreeToTreeDiff(diffResult, diff: diff) 812 | } 813 | } else if let tree = oldTree { 814 | return withGitObject(tree.oid, type: GIT_OBJECT_TREE, transform: { tree in 815 | var diff: OpaquePointer? = nil 816 | let diffResult = git_diff_tree_to_tree(&diff, 817 | self.pointer, 818 | tree, 819 | nil, 820 | nil) 821 | return processTreeToTreeDiff(diffResult, diff: diff) 822 | }) 823 | } else if let tree = newTree { 824 | return withGitObject(tree.oid, type: GIT_OBJECT_TREE, transform: { tree in 825 | var diff: OpaquePointer? = nil 826 | let diffResult = git_diff_tree_to_tree(&diff, 827 | self.pointer, 828 | nil, 829 | tree, 830 | nil) 831 | return processTreeToTreeDiff(diffResult, diff: diff) 832 | }) 833 | } 834 | 835 | return .failure(NSError(gitError: -1, pointOfFailure: "diff(from: to:)")) 836 | } 837 | 838 | private func processTreeToTreeDiff(_ diffResult: Int32, diff: OpaquePointer?) -> Result { 839 | guard diffResult == GIT_OK.rawValue else { 840 | return .failure(NSError(gitError: diffResult, 841 | pointOfFailure: "git_diff_tree_to_tree")) 842 | } 843 | 844 | let diffObj = Diff(diff!) 845 | git_diff_free(diff) 846 | return .success(diffObj) 847 | } 848 | 849 | private func processDiffDeltas(_ diffResult: OpaquePointer) -> Result<[Diff.Delta], NSError> { 850 | var returnDict = [Diff.Delta]() 851 | 852 | let count = git_diff_num_deltas(diffResult) 853 | 854 | for i in 0...success(returnDict) 862 | return result 863 | } 864 | 865 | private func safeTreeForCommitId(_ oid: OID) -> Result { 866 | return withGitObject(oid, type: GIT_OBJECT_COMMIT) { commit in 867 | let treeId = git_commit_tree_id(commit) 868 | return tree(OID(treeId!.pointee)) 869 | } 870 | } 871 | 872 | /// Caller responsible to free returned tree with git_object_free 873 | private func unsafeTreeForCommitId(_ oid: OID) -> Result { 874 | var commit: OpaquePointer? = nil 875 | var oid = oid.oid 876 | let commitResult = git_object_lookup(&commit, self.pointer, &oid, GIT_OBJECT_COMMIT) 877 | guard commitResult == GIT_OK.rawValue else { 878 | return .failure(NSError(gitError: commitResult, pointOfFailure: "git_object_lookup")) 879 | } 880 | 881 | var tree: OpaquePointer? = nil 882 | let treeId = git_commit_tree_id(commit) 883 | let treeResult = git_object_lookup(&tree, self.pointer, treeId, GIT_OBJECT_TREE) 884 | 885 | git_object_free(commit) 886 | 887 | guard treeResult == GIT_OK.rawValue else { 888 | return .failure(NSError(gitError: treeResult, pointOfFailure: "git_object_lookup")) 889 | } 890 | 891 | return Result.success(tree!) 892 | } 893 | 894 | // MARK: - Status 895 | 896 | public func status(options: StatusOptions = [.includeUntracked]) -> Result<[StatusEntry], NSError> { 897 | var returnArray = [StatusEntry]() 898 | 899 | // Do this because GIT_STATUS_OPTIONS_INIT is unavailable in swift 900 | let pointer = UnsafeMutablePointer.allocate(capacity: 1) 901 | let optionsResult = git_status_init_options(pointer, UInt32(GIT_STATUS_OPTIONS_VERSION)) 902 | guard optionsResult == GIT_OK.rawValue else { 903 | return .failure(NSError(gitError: optionsResult, pointOfFailure: "git_status_init_options")) 904 | } 905 | var listOptions = pointer.move() 906 | listOptions.flags = options.rawValue 907 | pointer.deallocate() 908 | 909 | var unsafeStatus: OpaquePointer? = nil 910 | defer { git_status_list_free(unsafeStatus) } 911 | let statusResult = git_status_list_new(&unsafeStatus, self.pointer, &listOptions) 912 | guard statusResult == GIT_OK.rawValue, let unwrapStatusResult = unsafeStatus else { 913 | return .failure(NSError(gitError: statusResult, pointOfFailure: "git_status_list_new")) 914 | } 915 | 916 | let count = git_status_list_entrycount(unwrapStatusResult) 917 | 918 | for i in 0.. Result { 937 | var pointer: OpaquePointer? 938 | 939 | let result = url.withUnsafeFileSystemRepresentation { 940 | git_repository_open_ext(&pointer, $0, GIT_REPOSITORY_OPEN_NO_SEARCH.rawValue, nil) 941 | } 942 | 943 | switch result { 944 | case GIT_ENOTFOUND.rawValue: 945 | return .success(false) 946 | case GIT_OK.rawValue: 947 | return .success(true) 948 | default: 949 | return .failure(NSError(gitError: result, pointOfFailure: "git_repository_open_ext")) 950 | } 951 | } 952 | } 953 | 954 | private extension Array { 955 | func aggregateResult() -> Result<[Value], Error> where Element == Result { 956 | var values: [Value] = [] 957 | for result in self { 958 | switch result { 959 | case .success(let value): 960 | values.append(value) 961 | case .failure(let error): 962 | return .failure(error) 963 | } 964 | } 965 | return .success(values) 966 | } 967 | } 968 | -------------------------------------------------------------------------------- /SwiftGit2/StatusOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2020 GitHub, Inc. All rights reserved. 3 | // 4 | 5 | import Clibgit2 6 | import Foundation 7 | 8 | public struct StatusOptions: OptionSet { 9 | public let rawValue: UInt32 10 | 11 | public init(rawValue: UInt32) { 12 | self.rawValue = rawValue 13 | } 14 | 15 | public static let includeUntracked = StatusOptions( 16 | rawValue: GIT_STATUS_OPT_INCLUDE_UNTRACKED.rawValue 17 | ) 18 | public static let includeIgnored = StatusOptions( 19 | rawValue: GIT_STATUS_OPT_INCLUDE_IGNORED.rawValue 20 | ) 21 | public static let includeUnmodified = StatusOptions( 22 | rawValue: GIT_STATUS_OPT_INCLUDE_UNMODIFIED.rawValue 23 | ) 24 | public static let excludeSubmodules = StatusOptions( 25 | rawValue: GIT_STATUS_OPT_EXCLUDE_SUBMODULES.rawValue 26 | ) 27 | public static let recurseUntrackedDirs = StatusOptions( 28 | rawValue: GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS.rawValue 29 | ) 30 | public static let disablePathSpecMatch = StatusOptions( 31 | rawValue: GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH.rawValue 32 | ) 33 | public static let recurseIgnoredDirs = StatusOptions( 34 | rawValue: GIT_STATUS_OPT_RECURSE_IGNORED_DIRS.rawValue 35 | ) 36 | public static let renamesHeadToIndex = StatusOptions( 37 | rawValue: GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX.rawValue 38 | ) 39 | public static let renamesIndexToWorkDir = StatusOptions( 40 | rawValue: GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR.rawValue 41 | ) 42 | public static let sortCasesSensitively = StatusOptions( 43 | rawValue: GIT_STATUS_OPT_SORT_CASE_SENSITIVELY.rawValue 44 | ) 45 | public static let sortCasesInSensitively = StatusOptions( 46 | rawValue: GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY.rawValue 47 | ) 48 | public static let renamesFromRewrites = StatusOptions( 49 | rawValue: GIT_STATUS_OPT_RENAMES_FROM_REWRITES.rawValue 50 | ) 51 | public static let noRefresh = StatusOptions( 52 | rawValue: GIT_STATUS_OPT_NO_REFRESH.rawValue 53 | ) 54 | public static let updateIndex = StatusOptions( 55 | rawValue: GIT_STATUS_OPT_UPDATE_INDEX.rawValue 56 | ) 57 | public static let includeUnreadable = StatusOptions( 58 | rawValue: GIT_STATUS_OPT_INCLUDE_UNREADABLE.rawValue 59 | ) 60 | public static let includeUnreadableAsUntracked = StatusOptions( 61 | rawValue: GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED.rawValue 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /SwiftGit2Tests/Fixtures/Fixtures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Fixtures.swift 3 | // SwiftGit2 4 | // 5 | // Created by Matt Diephouse on 11/16/14. 6 | // Copyright (c) 2014 GitHub, Inc. All rights reserved. 7 | // 8 | 9 | import SwiftGit2 10 | import ZipArchive 11 | 12 | final class Fixtures { 13 | 14 | // MARK: Lifecycle 15 | 16 | class var sharedInstance: Fixtures { 17 | enum Singleton { 18 | static let instance = Fixtures() 19 | } 20 | return Singleton.instance 21 | } 22 | 23 | init() { 24 | directoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) 25 | .appendingPathComponent("org.libgit2.SwiftGit2") 26 | .appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString) 27 | } 28 | 29 | // MARK: - Setup and Teardown 30 | 31 | let directoryURL: URL 32 | 33 | func setUp() { 34 | try! FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) 35 | 36 | #if os(OSX) 37 | let platform = "OSX" 38 | #else 39 | let platform = "iOS" 40 | #endif 41 | let bundleIdentifier = String(format: "org.libgit2.SwiftGit2-%@Tests", arguments: [platform]) 42 | let bundle = Bundle(identifier: bundleIdentifier)! 43 | let zipURLs = bundle.urls(forResourcesWithExtension: "zip", subdirectory: nil)! 44 | 45 | for URL in zipURLs { 46 | SSZipArchive.unzipFile(atPath: URL.path, toDestination: directoryURL.path) 47 | } 48 | } 49 | 50 | func tearDown() { 51 | try! FileManager.default.removeItem(at: directoryURL) 52 | } 53 | 54 | // MARK: - Helpers 55 | 56 | func repository(named name: String) -> Repository { 57 | let url = directoryURL.appendingPathComponent(name, isDirectory: true) 58 | return Repository.at(url).value! 59 | } 60 | 61 | // MARK: - The Fixtures 62 | 63 | class var detachedHeadRepository: Repository { 64 | return Fixtures.sharedInstance.repository(named: "detached-head") 65 | } 66 | 67 | class var simpleRepository: Repository { 68 | return Fixtures.sharedInstance.repository(named: "simple-repository") 69 | } 70 | 71 | class var mantleRepository: Repository { 72 | return Fixtures.sharedInstance.repository(named: "Mantle") 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /SwiftGit2Tests/Fixtures/Mantle.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/light-tech/SwiftGit2/03e7f41edf53658958c74db106749923ca9dcda0/SwiftGit2Tests/Fixtures/Mantle.zip -------------------------------------------------------------------------------- /SwiftGit2Tests/Fixtures/detached-head.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/light-tech/SwiftGit2/03e7f41edf53658958c74db106749923ca9dcda0/SwiftGit2Tests/Fixtures/detached-head.zip -------------------------------------------------------------------------------- /SwiftGit2Tests/Fixtures/repository-with-status.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/light-tech/SwiftGit2/03e7f41edf53658958c74db106749923ca9dcda0/SwiftGit2Tests/Fixtures/repository-with-status.zip -------------------------------------------------------------------------------- /SwiftGit2Tests/Fixtures/simple-repository.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/light-tech/SwiftGit2/03e7f41edf53658958c74db106749923ca9dcda0/SwiftGit2Tests/Fixtures/simple-repository.zip -------------------------------------------------------------------------------- /SwiftGit2Tests/FixturesSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FixturesSpec.swift 3 | // SwiftGit2 4 | // 5 | // Created by Matt Diephouse on 11/16/14. 6 | // Copyright (c) 2014 GitHub, Inc. All rights reserved. 7 | // 8 | 9 | import Quick 10 | 11 | class FixturesSpec: QuickSpec { 12 | override func spec() { 13 | beforeSuite { 14 | Fixtures.sharedInstance.setUp() 15 | } 16 | 17 | afterSuite { 18 | Fixtures.sharedInstance.tearDown() 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftGit2Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftGit2Tests/OIDSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OIDSpec.swift 3 | // SwiftGit2 4 | // 5 | // Created by Matt Diephouse on 11/17/14. 6 | // Copyright (c) 2014 GitHub, Inc. All rights reserved. 7 | // 8 | 9 | import SwiftGit2 10 | import Nimble 11 | import Quick 12 | 13 | class OIDSpec: QuickSpec { 14 | override func spec() { 15 | describe("OID(string:)") { 16 | it("should be nil if string is too short") { 17 | expect(OID(string: "123456789012345678901234567890123456789")).to(beNil()) 18 | } 19 | 20 | it("should be nil if string is too long") { 21 | expect(OID(string: "12345678901234567890123456789012345678901")).to(beNil()) 22 | } 23 | 24 | it("should not be nil if string is just right") { 25 | expect(OID(string: "1234567890123456789012345678ABCDEFabcdef")).notTo(beNil()) 26 | } 27 | 28 | it("should be nil with non-hex characters") { 29 | expect(OID(string: "123456789012345678901234567890123456789j")).to(beNil()) 30 | } 31 | } 32 | 33 | describe("OID(oid)") { 34 | it("should equal an OID with the same git_oid") { 35 | let oid = OID(string: "1234567890123456789012345678901234567890")! 36 | expect(OID(oid.oid)).to(equal(oid)) 37 | } 38 | } 39 | 40 | describe("OID.description") { 41 | it("should return the SHA") { 42 | let SHA = "1234567890123456789012345678901234567890" 43 | let oid = OID(string: SHA)! 44 | expect(oid.description).to(equal(SHA)) 45 | } 46 | } 47 | 48 | describe("==(OID, OID)") { 49 | it("should be equal when identical") { 50 | let SHA = "1234567890123456789012345678901234567890" 51 | let oid1 = OID(string: SHA)! 52 | let oid2 = OID(string: SHA)! 53 | expect(oid1).to(equal(oid2)) 54 | } 55 | 56 | it("should be not equal when different") { 57 | let oid1 = OID(string: "1234567890123456789012345678901234567890")! 58 | let oid2 = OID(string: "0000000000000000000000000000000000000000")! 59 | expect(oid1).notTo(equal(oid2)) 60 | } 61 | } 62 | 63 | describe("OID.hashValue") { 64 | it("should be equal when OIDs are equal") { 65 | let SHA = "1234567890123456789012345678901234567890" 66 | let oid1 = OID(string: SHA)! 67 | let oid2 = OID(string: SHA)! 68 | expect(oid1.hashValue).to(equal(oid2.hashValue)) 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /SwiftGit2Tests/ObjectsSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ObjectSpec.swift 3 | // SwiftGit2 4 | // 5 | // Created by Matt Diephouse on 12/4/14. 6 | // Copyright (c) 2014 GitHub, Inc. All rights reserved. 7 | // 8 | 9 | import SwiftGit2 10 | import Nimble 11 | import Quick 12 | import Clibgit2 13 | 14 | private extension Repository { 15 | func withGitObject(_ oid: OID, transform: (OpaquePointer) -> T) -> T { 16 | let repository = self.pointer 17 | var oid = oid.oid 18 | 19 | var pointer: OpaquePointer? = nil 20 | git_object_lookup(&pointer, repository, &oid, GIT_OBJECT_ANY) 21 | let result = transform(pointer!) 22 | git_object_free(pointer) 23 | 24 | return result 25 | } 26 | } 27 | 28 | class SignatureSpec: FixturesSpec { 29 | override func spec() { 30 | describe("Signature(signature)") { 31 | it("should initialize its properties") { 32 | let repo = Fixtures.simpleRepository 33 | let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! 34 | 35 | let raw_signature = repo.withGitObject(oid) { git_commit_author($0).pointee } 36 | let signature = Signature(raw_signature) 37 | 38 | expect(signature.name).to(equal("Matt Diephouse")) 39 | expect(signature.email).to(equal("matt@diephouse.com")) 40 | expect(signature.time).to(equal(Date(timeIntervalSince1970: 1416186947))) 41 | expect(signature.timeZone.abbreviation()).to(equal("GMT-5")) 42 | } 43 | } 44 | 45 | describe("==(Signature, Signature)") { 46 | it("should be true with equal objects") { 47 | let repo = Fixtures.simpleRepository 48 | let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! 49 | 50 | let author1 = repo.withGitObject(oid) { commit in 51 | Signature(git_commit_author(commit).pointee) 52 | } 53 | let author2 = author1 54 | 55 | expect(author1).to(equal(author2)) 56 | } 57 | 58 | it("should be false with unequal objects") { 59 | let repo = Fixtures.simpleRepository 60 | let oid1 = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! 61 | let oid2 = OID(string: "24e1e40ee77525d9e279f079f9906ad6d98c8940")! 62 | 63 | let author1 = repo.withGitObject(oid1) { commit in 64 | Signature(git_commit_author(commit).pointee) 65 | } 66 | let author2 = repo.withGitObject(oid2) { commit in 67 | Signature(git_commit_author(commit).pointee) 68 | } 69 | 70 | expect(author1).notTo(equal(author2)) 71 | } 72 | } 73 | 74 | describe("Signature.hashValue") { 75 | it("should be equal with equal objects") { 76 | let repo = Fixtures.simpleRepository 77 | let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! 78 | 79 | let author1 = repo.withGitObject(oid) { commit in 80 | Signature(git_commit_author(commit).pointee) 81 | } 82 | let author2 = author1 83 | 84 | expect(author1.hashValue).to(equal(author2.hashValue)) 85 | } 86 | } 87 | } 88 | } 89 | 90 | class CommitSpec: QuickSpec { 91 | override func spec() { 92 | describe("Commit(pointer)") { 93 | it("should initialize its properties") { 94 | let repo = Fixtures.simpleRepository 95 | let oid = OID(string: "24e1e40ee77525d9e279f079f9906ad6d98c8940")! 96 | 97 | let commit = repo.withGitObject(oid) { Commit($0) } 98 | let author = repo.withGitObject(oid) { commit in 99 | Signature(git_commit_author(commit).pointee) 100 | } 101 | let committer = repo.withGitObject(oid) { commit in 102 | Signature(git_commit_committer(commit).pointee) 103 | } 104 | let tree = PointerTo(OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")!) 105 | let parents: [PointerTo] = [ 106 | PointerTo(OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")!), 107 | ] 108 | expect(commit.oid).to(equal(oid)) 109 | expect(commit.tree).to(equal(tree)) 110 | expect(commit.parents).to(equal(parents)) 111 | expect(commit.message).to(equal("List branches in README\n")) 112 | expect(commit.author).to(equal(author)) 113 | expect(commit.committer).to(equal(committer)) 114 | } 115 | 116 | it("should handle 0 parents") { 117 | let repo = Fixtures.simpleRepository 118 | let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! 119 | 120 | let commit = repo.withGitObject(oid) { Commit($0) } 121 | expect(commit.parents).to(equal([])) 122 | } 123 | 124 | it("should handle multiple parents") { 125 | let repo = Fixtures.simpleRepository 126 | let oid = OID(string: "c4ed03a6b7d7ce837d31d83757febbe84dd465fd")! 127 | 128 | let commit = repo.withGitObject(oid) { Commit($0) } 129 | let parents: [PointerTo] = [ 130 | PointerTo(OID(string: "315b3f344221db91ddc54b269f3c9af422da0f2e")!), 131 | PointerTo(OID(string: "57f6197561d1f99b03c160f4026a07f06b43cf20")!), 132 | ] 133 | expect(commit.parents).to(equal(parents)) 134 | } 135 | } 136 | 137 | describe("==(Commit, Commit)") { 138 | it("should be true with equal objects") { 139 | let repo = Fixtures.simpleRepository 140 | let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! 141 | 142 | let commit1 = repo.withGitObject(oid) { Commit($0) } 143 | let commit2 = commit1 144 | expect(commit1).to(equal(commit2)) 145 | } 146 | 147 | it("should be false with unequal objects") { 148 | let repo = Fixtures.simpleRepository 149 | let oid1 = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! 150 | let oid2 = OID(string: "c4ed03a6b7d7ce837d31d83757febbe84dd465fd")! 151 | 152 | let commit1 = repo.withGitObject(oid1) { Commit($0) } 153 | let commit2 = repo.withGitObject(oid2) { Commit($0) } 154 | expect(commit1).notTo(equal(commit2)) 155 | } 156 | } 157 | 158 | describe("Commit.hashValue") { 159 | it("should be equal with equal objects") { 160 | let repo = Fixtures.simpleRepository 161 | let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! 162 | 163 | let commit1 = repo.withGitObject(oid) { Commit($0) } 164 | let commit2 = commit1 165 | expect(commit1.hashValue).to(equal(commit2.hashValue)) 166 | } 167 | } 168 | } 169 | } 170 | 171 | class TreeEntrySpec: QuickSpec { 172 | override func spec() { 173 | describe("Tree.Entry(attributes:object:name:)") { 174 | it("should set its properties") { 175 | let attributes = Int32(GIT_FILEMODE_BLOB.rawValue) 176 | let object = Pointer.blob(OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")!) 177 | let name = "README.md" 178 | 179 | let entry = Tree.Entry(attributes: attributes, object: object, name: name) 180 | expect(entry.attributes).to(equal(attributes)) 181 | expect(entry.object).to(equal(object)) 182 | expect(entry.name).to(equal(name)) 183 | } 184 | } 185 | 186 | describe("Tree.Entry(pointer)") { 187 | it("should set its properties") { 188 | let repo = Fixtures.simpleRepository 189 | let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! 190 | 191 | let entry = repo.withGitObject(oid) { Tree.Entry(git_tree_entry_byindex($0, 0)) } 192 | expect(entry.attributes).to(equal(Int32(GIT_FILEMODE_BLOB.rawValue))) 193 | expect(entry.object).to(equal(Pointer.blob(OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")!))) 194 | expect(entry.name).to(equal("README.md")) 195 | } 196 | } 197 | 198 | describe("==(Tree.Entry, Tree.Entry)") { 199 | it("should be true with equal objects") { 200 | let repo = Fixtures.simpleRepository 201 | let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! 202 | 203 | let entry1 = repo.withGitObject(oid) { Tree.Entry(git_tree_entry_byindex($0, 0)) } 204 | let entry2 = entry1 205 | expect(entry1).to(equal(entry2)) 206 | } 207 | 208 | it("should be false with unequal objects") { 209 | let repo = Fixtures.simpleRepository 210 | let oid1 = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! 211 | let oid2 = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! 212 | 213 | let entry1 = repo.withGitObject(oid1) { Tree.Entry(git_tree_entry_byindex($0, 0)) } 214 | let entry2 = repo.withGitObject(oid2) { Tree.Entry(git_tree_entry_byindex($0, 0)) } 215 | expect(entry1).notTo(equal(entry2)) 216 | } 217 | } 218 | 219 | describe("Tree.Entry.hashValue") { 220 | it("should be equal with equal objects") { 221 | let repo = Fixtures.simpleRepository 222 | let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! 223 | 224 | let entry1 = repo.withGitObject(oid) { Tree.Entry(git_tree_entry_byindex($0, 0)) } 225 | let entry2 = entry1 226 | expect(entry1.hashValue).to(equal(entry2.hashValue)) 227 | } 228 | } 229 | } 230 | } 231 | 232 | class TreeSpec: QuickSpec { 233 | override func spec() { 234 | describe("Tree(pointer)") { 235 | it("should initialize its properties") { 236 | let repo = Fixtures.simpleRepository 237 | let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! 238 | 239 | let tree = repo.withGitObject(oid) { Tree($0) } 240 | let entries = [ 241 | "README.md": Tree.Entry(attributes: Int32(GIT_FILEMODE_BLOB.rawValue), 242 | object: .blob(OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")!), 243 | name: "README.md"), 244 | ] 245 | expect(tree.entries).to(equal(entries)) 246 | } 247 | } 248 | 249 | describe("==(Tree, Tree)") { 250 | it("should be true with equal objects") { 251 | let repo = Fixtures.simpleRepository 252 | let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! 253 | 254 | let tree1 = repo.withGitObject(oid) { Tree($0) } 255 | let tree2 = tree1 256 | expect(tree1).to(equal(tree2)) 257 | } 258 | 259 | it("should be false with unequal objects") { 260 | let repo = Fixtures.simpleRepository 261 | let oid1 = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! 262 | let oid2 = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! 263 | 264 | let tree1 = repo.withGitObject(oid1) { Tree($0) } 265 | let tree2 = repo.withGitObject(oid2) { Tree($0) } 266 | expect(tree1).notTo(equal(tree2)) 267 | } 268 | } 269 | 270 | describe("Tree.hashValue") { 271 | it("should be equal with equal objects") { 272 | let repo = Fixtures.simpleRepository 273 | let oid = OID(string: "219e9f39c2fb59ed1dfb3e78ed75055a57528f31")! 274 | 275 | let tree1 = repo.withGitObject(oid) { Tree($0) } 276 | let tree2 = tree1 277 | expect(tree1.hashValue).to(equal(tree2.hashValue)) 278 | } 279 | } 280 | } 281 | } 282 | 283 | class BlobSpec: QuickSpec { 284 | override func spec() { 285 | describe("Blob(pointer)") { 286 | it("should initialize its properties") { 287 | let repo = Fixtures.simpleRepository 288 | let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! 289 | 290 | let blob = repo.withGitObject(oid) { Blob($0) } 291 | let contents = "# Simple Repository\nA simple repository used for testing SwiftGit2.\n\n## Branches\n\n- master\n\n" 292 | let data = contents.data(using: String.Encoding.utf8)! 293 | expect(blob.oid).to(equal(oid)) 294 | expect(blob.data).to(equal(data)) 295 | } 296 | } 297 | 298 | describe("==(Blob, Blob)") { 299 | it("should be true with equal objects") { 300 | let repo = Fixtures.simpleRepository 301 | let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! 302 | 303 | let blob1 = repo.withGitObject(oid) { Blob($0) } 304 | let blob2 = blob1 305 | expect(blob1).to(equal(blob2)) 306 | } 307 | 308 | it("should be false with unequal objects") { 309 | let repo = Fixtures.simpleRepository 310 | let oid1 = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! 311 | let oid2 = OID(string: "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")! 312 | 313 | let blob1 = repo.withGitObject(oid1) { Blob($0) } 314 | let blob2 = repo.withGitObject(oid2) { Blob($0) } 315 | expect(blob1).notTo(equal(blob2)) 316 | } 317 | } 318 | 319 | describe("Blob.hashValue") { 320 | it("should be equal with equal objects") { 321 | let repo = Fixtures.simpleRepository 322 | let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! 323 | 324 | let blob1 = repo.withGitObject(oid) { Blob($0) } 325 | let blob2 = blob1 326 | expect(blob1.hashValue).to(equal(blob2.hashValue)) 327 | } 328 | } 329 | } 330 | } 331 | 332 | class TagSpec: QuickSpec { 333 | override func spec() { 334 | describe("Tag(pointer)") { 335 | it("should set its properties") { 336 | let repo = Fixtures.simpleRepository 337 | let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! 338 | 339 | let tag = repo.withGitObject(oid) { Tag($0) } 340 | let tagger = repo.withGitObject(oid) { Signature(git_tag_tagger($0).pointee) } 341 | 342 | expect(tag.oid).to(equal(oid)) 343 | expect(tag.target).to(equal(Pointer.commit(OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")!))) 344 | expect(tag.name).to(equal("tag-1")) 345 | expect(tag.tagger).to(equal(tagger)) 346 | expect(tag.message).to(equal("tag-1\n")) 347 | } 348 | } 349 | 350 | describe("==(Tag, Tag)") { 351 | it("should be true with equal objects") { 352 | let repo = Fixtures.simpleRepository 353 | let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! 354 | 355 | let tag1 = repo.withGitObject(oid) { Tag($0) } 356 | let tag2 = tag1 357 | expect(tag1).to(equal(tag2)) 358 | } 359 | 360 | it("should be false with unequal objects") { 361 | let repo = Fixtures.simpleRepository 362 | let oid1 = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! 363 | let oid2 = OID(string: "13bda91157f255ab224ff88d0a11a82041c9d0c1")! 364 | 365 | let tag1 = repo.withGitObject(oid1) { Tag($0) } 366 | let tag2 = repo.withGitObject(oid2) { Tag($0) } 367 | expect(tag1).notTo(equal(tag2)) 368 | } 369 | } 370 | 371 | describe("Tag.hashValue") { 372 | it("should be equal with equal objects") { 373 | let repo = Fixtures.simpleRepository 374 | let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! 375 | 376 | let tag1 = repo.withGitObject(oid) { Tag($0) } 377 | let tag2 = tag1 378 | expect(tag1.hashValue).to(equal(tag2.hashValue)) 379 | } 380 | } 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /SwiftGit2Tests/ReferencesSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReferencesSpec.swift 3 | // SwiftGit2 4 | // 5 | // Created by Matt Diephouse on 1/2/15. 6 | // Copyright (c) 2015 GitHub, Inc. All rights reserved. 7 | // 8 | 9 | import SwiftGit2 10 | import Nimble 11 | import Quick 12 | import Clibgit2 13 | 14 | private extension Repository { 15 | func withGitReference(named name: String, transform: (OpaquePointer) -> T) -> T { 16 | let repository = self.pointer 17 | 18 | var pointer: OpaquePointer? = nil 19 | git_reference_lookup(&pointer, repository, name) 20 | let result = transform(pointer!) 21 | git_reference_free(pointer) 22 | 23 | return result 24 | } 25 | } 26 | 27 | class ReferenceSpec: FixturesSpec { 28 | override func spec() { 29 | describe("Reference(pointer)") { 30 | it("should initialize its properties") { 31 | let repo = Fixtures.simpleRepository 32 | let ref = repo.withGitReference(named: "refs/heads/master") { Reference($0) } 33 | expect(ref.longName).to(equal("refs/heads/master")) 34 | expect(ref.shortName).to(equal("master")) 35 | expect(ref.oid).to(equal(OID(string: "c4ed03a6b7d7ce837d31d83757febbe84dd465fd")!)) 36 | } 37 | } 38 | 39 | describe("==(Reference, Reference)") { 40 | it("should be true with equal references") { 41 | let repo = Fixtures.simpleRepository 42 | let ref1 = repo.withGitReference(named: "refs/heads/master") { Reference($0) } 43 | let ref2 = repo.withGitReference(named: "refs/heads/master") { Reference($0) } 44 | expect(ref1).to(equal(ref2)) 45 | } 46 | 47 | it("should be false with unequal references") { 48 | let repo = Fixtures.simpleRepository 49 | let ref1 = repo.withGitReference(named: "refs/heads/master") { Reference($0) } 50 | let ref2 = repo.withGitReference(named: "refs/heads/another-branch") { Reference($0) } 51 | expect(ref1).notTo(equal(ref2)) 52 | } 53 | } 54 | 55 | describe("Reference.hashValue") { 56 | it("should be equal with equal references") { 57 | let repo = Fixtures.simpleRepository 58 | let ref1 = repo.withGitReference(named: "refs/heads/master") { Reference($0) } 59 | let ref2 = repo.withGitReference(named: "refs/heads/master") { Reference($0) } 60 | expect(ref1.hashValue).to(equal(ref2.hashValue)) 61 | } 62 | } 63 | } 64 | } 65 | 66 | class BranchSpec: QuickSpec { 67 | override func spec() { 68 | describe("Branch(pointer)") { 69 | it("should initialize its properties") { 70 | let repo = Fixtures.mantleRepository 71 | let branch = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } 72 | expect(branch.longName).to(equal("refs/heads/master")) 73 | expect(branch.name).to(equal("master")) 74 | expect(branch.shortName).to(equal(branch.name)) 75 | expect(branch.commit.oid).to(equal(OID(string: "f797bd4837b61d37847a4833024aab268599a681")!)) 76 | expect(branch.oid).to(equal(branch.commit.oid)) 77 | expect(branch.isLocal).to(beTrue()) 78 | expect(branch.isRemote).to(beFalse()) 79 | } 80 | 81 | it("should work with symoblic refs") { 82 | let repo = Fixtures.mantleRepository 83 | let branch = repo.withGitReference(named: "refs/remotes/origin/HEAD") { Branch($0)! } 84 | expect(branch.longName).to(equal("refs/remotes/origin/HEAD")) 85 | expect(branch.name).to(equal("origin/HEAD")) 86 | expect(branch.shortName).to(equal(branch.name)) 87 | expect(branch.commit.oid).to(equal(OID(string: "f797bd4837b61d37847a4833024aab268599a681")!)) 88 | expect(branch.oid).to(equal(branch.commit.oid)) 89 | expect(branch.isLocal).to(beFalse()) 90 | expect(branch.isRemote).to(beTrue()) 91 | } 92 | } 93 | 94 | describe("==(Branch, Branch)") { 95 | it("should be true with equal branches") { 96 | let repo = Fixtures.simpleRepository 97 | let branch1 = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } 98 | let branch2 = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } 99 | expect(branch1).to(equal(branch2)) 100 | } 101 | 102 | it("should be false with unequal branches") { 103 | let repo = Fixtures.simpleRepository 104 | let branch1 = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } 105 | let branch2 = repo.withGitReference(named: "refs/heads/another-branch") { Branch($0)! } 106 | expect(branch1).notTo(equal(branch2)) 107 | } 108 | } 109 | 110 | describe("Branch.hashValue") { 111 | it("should be equal with equal references") { 112 | let repo = Fixtures.simpleRepository 113 | let branch1 = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } 114 | let branch2 = repo.withGitReference(named: "refs/heads/master") { Branch($0)! } 115 | expect(branch1.hashValue).to(equal(branch2.hashValue)) 116 | } 117 | } 118 | } 119 | } 120 | 121 | class TagReferenceSpec: QuickSpec { 122 | override func spec() { 123 | describe("TagReference(pointer)") { 124 | it("should work with an annotated tag") { 125 | let repo = Fixtures.simpleRepository 126 | let tag = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } 127 | expect(tag.longName).to(equal("refs/tags/tag-2")) 128 | expect(tag.name).to(equal("tag-2")) 129 | expect(tag.shortName).to(equal(tag.name)) 130 | expect(tag.oid).to(equal(OID(string: "24e1e40ee77525d9e279f079f9906ad6d98c8940")!)) 131 | } 132 | 133 | it("should work with a lightweight tag") { 134 | let repo = Fixtures.mantleRepository 135 | let tag = repo.withGitReference(named: "refs/tags/1.5.4") { TagReference($0)! } 136 | expect(tag.longName).to(equal("refs/tags/1.5.4")) 137 | expect(tag.name).to(equal("1.5.4")) 138 | expect(tag.shortName).to(equal(tag.name)) 139 | expect(tag.oid).to(equal(OID(string: "d9dc95002cfbf3929d2b70d2c8a77e6bf5b1b88a")!)) 140 | } 141 | 142 | it("should return nil if not a tag") { 143 | let repo = Fixtures.simpleRepository 144 | let tag = repo.withGitReference(named: "refs/heads/master") { TagReference($0) } 145 | expect(tag).to(beNil()) 146 | } 147 | } 148 | 149 | describe("==(TagReference, TagReference)") { 150 | it("should be true with equal tag references") { 151 | let repo = Fixtures.simpleRepository 152 | let tag1 = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } 153 | let tag2 = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } 154 | expect(tag1).to(equal(tag2)) 155 | } 156 | 157 | it("should be false with unequal tag references") { 158 | let repo = Fixtures.simpleRepository 159 | let tag1 = repo.withGitReference(named: "refs/tags/tag-1") { TagReference($0)! } 160 | let tag2 = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } 161 | expect(tag1).notTo(equal(tag2)) 162 | } 163 | } 164 | 165 | describe("TagReference.hashValue") { 166 | it("should be equal with equal references") { 167 | let repo = Fixtures.simpleRepository 168 | let tag1 = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } 169 | let tag2 = repo.withGitReference(named: "refs/tags/tag-2") { TagReference($0)! } 170 | expect(tag1.hashValue).to(equal(tag2.hashValue)) 171 | } 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /SwiftGit2Tests/RemotesSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemotesSpec.swift 3 | // SwiftGit2 4 | // 5 | // Created by Matt Diephouse on 1/2/15. 6 | // Copyright (c) 2015 GitHub, Inc. All rights reserved. 7 | // 8 | 9 | import SwiftGit2 10 | import Nimble 11 | import Quick 12 | import Clibgit2 13 | 14 | private extension Repository { 15 | func withGitRemote(named name: String, transform: (OpaquePointer) -> T) -> T { 16 | let repository = self.pointer 17 | 18 | var pointer: OpaquePointer? = nil 19 | git_remote_lookup(&pointer, repository, name) 20 | let result = transform(pointer!) 21 | git_remote_free(pointer) 22 | 23 | return result 24 | } 25 | } 26 | 27 | class RemoteSpec: FixturesSpec { 28 | override func spec() { 29 | describe("Remote(pointer)") { 30 | it("should initialize its properties") { 31 | let repo = Fixtures.mantleRepository 32 | let remote = repo.withGitRemote(named: "upstream") { Remote($0) } 33 | 34 | expect(remote.name).to(equal("upstream")) 35 | expect(remote.URL).to(equal("git@github.com:Mantle/Mantle.git")) 36 | } 37 | } 38 | 39 | describe("==(Remote, Remote)") { 40 | it("should be true with equal objects") { 41 | let repo = Fixtures.mantleRepository 42 | let remote1 = repo.withGitRemote(named: "upstream") { Remote($0) } 43 | let remote2 = remote1 44 | expect(remote1).to(equal(remote2)) 45 | } 46 | 47 | it("should be false with unequal objcets") { 48 | let repo = Fixtures.mantleRepository 49 | let origin = repo.withGitRemote(named: "origin") { Remote($0) } 50 | let upstream = repo.withGitRemote(named: "upstream") { Remote($0) } 51 | expect(origin).notTo(equal(upstream)) 52 | } 53 | } 54 | 55 | describe("Remote.hashValue") { 56 | it("should be equal with equal objcets") { 57 | let repo = Fixtures.mantleRepository 58 | let remote1 = repo.withGitRemote(named: "upstream") { Remote($0) } 59 | let remote2 = remote1 60 | expect(remote1.hashValue).to(equal(remote2.hashValue)) 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /SwiftGit2Tests/RepositorySpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RepositorySpec.swift 3 | // RepositorySpec 4 | // 5 | // Created by Matt Diephouse on 11/7/14. 6 | // Copyright (c) 2014 GitHub, Inc. All rights reserved. 7 | // 8 | 9 | import SwiftGit2 10 | import Nimble 11 | import Quick 12 | 13 | // swiftlint:disable cyclomatic_complexity 14 | 15 | class RepositorySpec: FixturesSpec { 16 | override func spec() { 17 | describe("Repository.Type.at(_:)") { 18 | it("should work if the repo exists") { 19 | let repo = Fixtures.simpleRepository 20 | expect(repo.directoryURL).notTo(beNil()) 21 | } 22 | 23 | it("should fail if the repo doesn't exist") { 24 | let url = URL(fileURLWithPath: "blah") 25 | let result = Repository.at(url) 26 | expect(result.error?.domain) == libGit2ErrorDomain 27 | expect(result.error?.localizedDescription).to(match("failed to resolve path")) 28 | } 29 | } 30 | 31 | describe("Repository.Type.isValid(url:)") { 32 | it("should return true if the repo exists") { 33 | guard let repositoryURL = Fixtures.simpleRepository.directoryURL else { 34 | fail("Fixture setup broken: Repository does not exist"); return 35 | } 36 | 37 | let result = Repository.isValid(url: repositoryURL) 38 | 39 | expect(result.error).to(beNil()) 40 | 41 | if case .success(let isValid) = result { 42 | expect(isValid).to(beTruthy()) 43 | } 44 | } 45 | 46 | it("should return false if the directory does not contain a repo") { 47 | let tmpURL = URL(fileURLWithPath: "/dev/null") 48 | let result = Repository.isValid(url: tmpURL) 49 | 50 | expect(result.error).to(beNil()) 51 | 52 | if case .success(let isValid) = result { 53 | expect(isValid).to(beFalsy()) 54 | } 55 | } 56 | 57 | it("should return error if .git is not readable") { 58 | let localURL = self.temporaryURL(forPurpose: "git-isValid-unreadable").appendingPathComponent(".git") 59 | let nonReadablePermissions: [FileAttributeKey: Any] = [.posixPermissions: 0o077] 60 | try! FileManager.default.createDirectory( 61 | at: localURL, 62 | withIntermediateDirectories: true, 63 | attributes: nonReadablePermissions) 64 | let result = Repository.isValid(url: localURL) 65 | 66 | expect(result.value).to(beNil()) 67 | expect(result.error).notTo(beNil()) 68 | } 69 | } 70 | 71 | describe("Repository.Type.create(at:)") { 72 | it("should create a new repo at the specified location") { 73 | let localURL = self.temporaryURL(forPurpose: "local-create") 74 | let result = Repository.create(at: localURL) 75 | 76 | expect(result.error).to(beNil()) 77 | 78 | if case .success(let clonedRepo) = result { 79 | expect(clonedRepo.directoryURL).notTo(beNil()) 80 | } 81 | } 82 | } 83 | 84 | describe("Repository.Type.clone(from:to:)") { 85 | it("should handle local clones") { 86 | let remoteRepo = Fixtures.simpleRepository 87 | let localURL = self.temporaryURL(forPurpose: "local-clone") 88 | let result = Repository.clone(from: remoteRepo.directoryURL!, to: localURL, localClone: true) 89 | 90 | expect(result.error).to(beNil()) 91 | 92 | if case .success(let clonedRepo) = result { 93 | expect(clonedRepo.directoryURL).notTo(beNil()) 94 | } 95 | } 96 | 97 | it("should handle bare clones") { 98 | let remoteRepo = Fixtures.simpleRepository 99 | let localURL = self.temporaryURL(forPurpose: "bare-clone") 100 | let result = Repository.clone(from: remoteRepo.directoryURL!, to: localURL, localClone: true, bare: true) 101 | 102 | expect(result.error).to(beNil()) 103 | 104 | if case .success(let clonedRepo) = result { 105 | expect(clonedRepo.directoryURL).to(beNil()) 106 | } 107 | } 108 | 109 | it("should have set a valid remote url") { 110 | let remoteRepo = Fixtures.simpleRepository 111 | let localURL = self.temporaryURL(forPurpose: "valid-remote-clone") 112 | let cloneResult = Repository.clone(from: remoteRepo.directoryURL!, to: localURL, localClone: true) 113 | 114 | expect(cloneResult.error).to(beNil()) 115 | 116 | if case .success(let clonedRepo) = cloneResult { 117 | let remoteResult = clonedRepo.remote(named: "origin") 118 | expect(remoteResult.error).to(beNil()) 119 | 120 | if case .success(let remote) = remoteResult { 121 | expect(remote.URL).to(equal(remoteRepo.directoryURL?.absoluteString)) 122 | } 123 | } 124 | } 125 | 126 | it("should be able to clone a remote repository") { 127 | let remoteRepoURL = URL(string: "https://github.com/libgit2/libgit2.github.com.git") 128 | let localURL = self.temporaryURL(forPurpose: "public-remote-clone") 129 | let cloneResult = Repository.clone(from: remoteRepoURL!, to: localURL) 130 | 131 | expect(cloneResult.error).to(beNil()) 132 | 133 | if case .success(let clonedRepo) = cloneResult { 134 | let remoteResult = clonedRepo.remote(named: "origin") 135 | expect(remoteResult.error).to(beNil()) 136 | 137 | if case .success(let remote) = remoteResult { 138 | expect(remote.URL).to(equal(remoteRepoURL?.absoluteString)) 139 | } 140 | } 141 | } 142 | 143 | let env = ProcessInfo.processInfo.environment 144 | 145 | if let privateRepo = env["SG2TestPrivateRepo"], 146 | let gitUsername = env["SG2TestUsername"], 147 | let publicKey = env["SG2TestPublicKey"], 148 | let privateKey = env["SG2TestPrivateKey"], 149 | let passphrase = env["SG2TestPassphrase"] { 150 | 151 | it("should be able to clone a remote repository requiring credentials") { 152 | let remoteRepoURL = URL(string: privateRepo) 153 | let localURL = self.temporaryURL(forPurpose: "private-remote-clone") 154 | let credentials = Credentials.sshMemory(username: gitUsername, 155 | publicKey: publicKey, 156 | privateKey: privateKey, 157 | passphrase: passphrase) 158 | 159 | let cloneResult = Repository.clone(from: remoteRepoURL!, to: localURL, credentials: credentials) 160 | 161 | expect(cloneResult.error).to(beNil()) 162 | 163 | if case .success(let clonedRepo) = cloneResult { 164 | let remoteResult = clonedRepo.remote(named: "origin") 165 | expect(remoteResult.error).to(beNil()) 166 | 167 | if case .success(let remote) = remoteResult { 168 | expect(remote.URL).to(equal(remoteRepoURL?.absoluteString)) 169 | } 170 | } 171 | } 172 | } 173 | } 174 | 175 | describe("Repository.blob(_:)") { 176 | it("should return the commit if it exists") { 177 | let repo = Fixtures.simpleRepository 178 | let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! 179 | 180 | let result = repo.blob(oid) 181 | expect(result.map { $0.oid }.value) == oid 182 | } 183 | 184 | it("should error if the blob doesn't exist") { 185 | let repo = Fixtures.simpleRepository 186 | let oid = OID(string: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")! 187 | 188 | let result = repo.blob(oid) 189 | expect(result.error?.domain) == libGit2ErrorDomain 190 | } 191 | 192 | it("should error if the oid doesn't point to a blob") { 193 | let repo = Fixtures.simpleRepository 194 | // This is a tree in the repository 195 | let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! 196 | 197 | let result = repo.blob(oid) 198 | expect(result.error).notTo(beNil()) 199 | } 200 | } 201 | 202 | describe("Repository.commit(_:)") { 203 | it("should return the commit if it exists") { 204 | let repo = Fixtures.simpleRepository 205 | let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! 206 | 207 | let result = repo.commit(oid) 208 | expect(result.map { $0.oid }.value) == oid 209 | } 210 | 211 | it("should error if the commit doesn't exist") { 212 | let repo = Fixtures.simpleRepository 213 | let oid = OID(string: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")! 214 | 215 | let result = repo.commit(oid) 216 | expect(result.error?.domain) == libGit2ErrorDomain 217 | } 218 | 219 | it("should error if the oid doesn't point to a commit") { 220 | let repo = Fixtures.simpleRepository 221 | // This is a tree in the repository 222 | let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! 223 | 224 | let result = repo.commit(oid) 225 | expect(result.error?.domain) == libGit2ErrorDomain 226 | } 227 | } 228 | 229 | describe("Repository.tag(_:)") { 230 | it("should return the tag if it exists") { 231 | let repo = Fixtures.simpleRepository 232 | let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! 233 | 234 | let result = repo.tag(oid) 235 | expect(result.map { $0.oid }.value) == oid 236 | } 237 | 238 | it("should error if the tag doesn't exist") { 239 | let repo = Fixtures.simpleRepository 240 | let oid = OID(string: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")! 241 | 242 | let result = repo.tag(oid) 243 | expect(result.error?.domain) == libGit2ErrorDomain 244 | } 245 | 246 | it("should error if the oid doesn't point to a tag") { 247 | let repo = Fixtures.simpleRepository 248 | // This is a commit in the repository 249 | let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! 250 | 251 | let result = repo.tag(oid) 252 | expect(result.error?.domain) == libGit2ErrorDomain 253 | } 254 | } 255 | 256 | describe("Repository.tree(_:)") { 257 | it("should return the tree if it exists") { 258 | let repo = Fixtures.simpleRepository 259 | let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! 260 | 261 | let result = repo.tree(oid) 262 | expect(result.map { $0.oid }.value) == oid 263 | } 264 | 265 | it("should error if the tree doesn't exist") { 266 | let repo = Fixtures.simpleRepository 267 | let oid = OID(string: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")! 268 | 269 | let result = repo.tree(oid) 270 | expect(result.error?.domain) == libGit2ErrorDomain 271 | } 272 | 273 | it("should error if the oid doesn't point to a tree") { 274 | let repo = Fixtures.simpleRepository 275 | // This is a commit in the repository 276 | let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! 277 | 278 | let result = repo.tree(oid) 279 | expect(result.error?.domain) == libGit2ErrorDomain 280 | } 281 | } 282 | 283 | describe("Repository.object(_:)") { 284 | it("should work with a blob") { 285 | let repo = Fixtures.simpleRepository 286 | let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! 287 | let blob = repo.blob(oid).value 288 | let result = repo.object(oid) 289 | expect(result.map { $0 as! Blob }.value) == blob 290 | } 291 | 292 | it("should work with a commit") { 293 | let repo = Fixtures.simpleRepository 294 | let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! 295 | let commit = repo.commit(oid).value 296 | let result = repo.object(oid) 297 | expect(result.map { $0 as! Commit }.value) == commit 298 | } 299 | 300 | it("should work with a tag") { 301 | let repo = Fixtures.simpleRepository 302 | let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! 303 | let tag = repo.tag(oid).value 304 | let result = repo.object(oid) 305 | expect(result.map { $0 as! Tag }.value) == tag 306 | } 307 | 308 | it("should work with a tree") { 309 | let repo = Fixtures.simpleRepository 310 | let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! 311 | let tree = repo.tree(oid).value 312 | let result = repo.object(oid) 313 | expect(result.map { $0 as! Tree }.value) == tree 314 | } 315 | 316 | it("should error if there's no object with that oid") { 317 | let repo = Fixtures.simpleRepository 318 | let oid = OID(string: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")! 319 | let result = repo.object(oid) 320 | expect(result.error?.domain) == libGit2ErrorDomain 321 | } 322 | } 323 | 324 | describe("Repository.object(from: PointerTo)") { 325 | it("should work with commits") { 326 | let repo = Fixtures.simpleRepository 327 | let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! 328 | 329 | let pointer = PointerTo(oid) 330 | let commit = repo.commit(oid).value! 331 | expect(repo.object(from: pointer).value) == commit 332 | } 333 | 334 | it("should work with trees") { 335 | let repo = Fixtures.simpleRepository 336 | let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! 337 | 338 | let pointer = PointerTo(oid) 339 | let tree = repo.tree(oid).value! 340 | expect(repo.object(from: pointer).value) == tree 341 | } 342 | 343 | it("should work with blobs") { 344 | let repo = Fixtures.simpleRepository 345 | let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! 346 | 347 | let pointer = PointerTo(oid) 348 | let blob = repo.blob(oid).value! 349 | expect(repo.object(from: pointer).value) == blob 350 | } 351 | 352 | it("should work with tags") { 353 | let repo = Fixtures.simpleRepository 354 | let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! 355 | 356 | let pointer = PointerTo(oid) 357 | let tag = repo.tag(oid).value! 358 | expect(repo.object(from: pointer).value) == tag 359 | } 360 | } 361 | 362 | describe("Repository.object(from: Pointer)") { 363 | it("should work with commits") { 364 | let repo = Fixtures.simpleRepository 365 | let oid = OID(string: "dc220a3f0c22920dab86d4a8d3a3cb7e69d6205a")! 366 | 367 | let pointer = Pointer.commit(oid) 368 | let commit = repo.commit(oid).value! 369 | let result = repo.object(from: pointer).map { $0 as! Commit } 370 | expect(result.value) == commit 371 | } 372 | 373 | it("should work with trees") { 374 | let repo = Fixtures.simpleRepository 375 | let oid = OID(string: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc")! 376 | 377 | let pointer = Pointer.tree(oid) 378 | let tree = repo.tree(oid).value! 379 | let result = repo.object(from: pointer).map { $0 as! Tree } 380 | expect(result.value) == tree 381 | } 382 | 383 | it("should work with blobs") { 384 | let repo = Fixtures.simpleRepository 385 | let oid = OID(string: "41078396f5187daed5f673e4a13b185bbad71fba")! 386 | 387 | let pointer = Pointer.blob(oid) 388 | let blob = repo.blob(oid).value! 389 | let result = repo.object(from: pointer).map { $0 as! Blob } 390 | expect(result.value) == blob 391 | } 392 | 393 | it("should work with tags") { 394 | let repo = Fixtures.simpleRepository 395 | let oid = OID(string: "57943b8ee00348180ceeedc960451562750f6d33")! 396 | 397 | let pointer = Pointer.tag(oid) 398 | let tag = repo.tag(oid).value! 399 | let result = repo.object(from: pointer).map { $0 as! Tag } 400 | expect(result.value) == tag 401 | } 402 | } 403 | 404 | describe("Repository.allRemotes()") { 405 | it("should return an empty list if there are no remotes") { 406 | let repo = Fixtures.simpleRepository 407 | let result = repo.allRemotes() 408 | expect(result.value) == [] 409 | } 410 | 411 | it("should return all the remotes") { 412 | let repo = Fixtures.mantleRepository 413 | let remotes = repo.allRemotes() 414 | let names = remotes.map { $0.map { $0.name } } 415 | expect(remotes.map { $0.count }.value) == 2 416 | expect(names.value).to(contain("origin", "upstream")) 417 | } 418 | } 419 | 420 | describe("Repository.remote(named:)") { 421 | it("should return the remote if it exists") { 422 | let repo = Fixtures.mantleRepository 423 | let result = repo.remote(named: "upstream") 424 | expect(result.map { $0.name }.value) == "upstream" 425 | } 426 | 427 | it("should error if the remote doesn't exist") { 428 | let repo = Fixtures.simpleRepository 429 | let result = repo.remote(named: "nonexistent") 430 | expect(result.error?.domain) == libGit2ErrorDomain 431 | } 432 | } 433 | 434 | describe("Repository.reference(named:)") { 435 | it("should return a local branch if it exists") { 436 | let name = "refs/heads/master" 437 | let result = Fixtures.simpleRepository.reference(named: name) 438 | expect(result.map { $0.longName }.value) == name 439 | expect(result.value as? Branch).notTo(beNil()) 440 | } 441 | 442 | it("should return a remote branch if it exists") { 443 | let name = "refs/remotes/upstream/master" 444 | let result = Fixtures.mantleRepository.reference(named: name) 445 | expect(result.map { $0.longName }.value) == name 446 | expect(result.value as? Branch).notTo(beNil()) 447 | } 448 | 449 | it("should return a tag if it exists") { 450 | let name = "refs/tags/tag-2" 451 | let result = Fixtures.simpleRepository.reference(named: name) 452 | expect(result.value?.longName).to(equal(name)) 453 | expect(result.value as? TagReference).notTo(beNil()) 454 | } 455 | 456 | it("should return the reference if it exists") { 457 | let name = "refs/other-ref" 458 | let result = Fixtures.simpleRepository.reference(named: name) 459 | expect(result.value?.longName).to(equal(name)) 460 | } 461 | 462 | it("should error if the reference doesn't exist") { 463 | let result = Fixtures.simpleRepository.reference(named: "refs/heads/nonexistent") 464 | expect(result.error?.domain) == libGit2ErrorDomain 465 | } 466 | } 467 | 468 | describe("Repository.localBranches()") { 469 | it("should return all the local branches") { 470 | let repo = Fixtures.simpleRepository 471 | let expected = [ 472 | repo.localBranch(named: "another-branch").value!, 473 | repo.localBranch(named: "master").value!, 474 | repo.localBranch(named: "yet-another-branch").value!, 475 | ] 476 | expect(repo.localBranches().value).to(equal(expected)) 477 | } 478 | } 479 | 480 | describe("Repository.remoteBranches()") { 481 | it("should return all the remote branches") { 482 | let repo = Fixtures.mantleRepository 483 | let expectedNames = [ 484 | "origin/2.0-development", 485 | "origin/HEAD", 486 | "origin/bump-config", 487 | "origin/bump-xcconfigs", 488 | "origin/github-reversible-transformer", 489 | "origin/master", 490 | "origin/mtlmanagedobject", 491 | "origin/reversible-transformer", 492 | "origin/subclassing-notes", 493 | "upstream/2.0-development", 494 | "upstream/bump-config", 495 | "upstream/bump-xcconfigs", 496 | "upstream/github-reversible-transformer", 497 | "upstream/master", 498 | "upstream/mtlmanagedobject", 499 | "upstream/reversible-transformer", 500 | "upstream/subclassing-notes", 501 | ] 502 | let expected = expectedNames.map { repo.remoteBranch(named: $0).value! } 503 | let actual = repo.remoteBranches().value!.sorted { 504 | return $0.longName.lexicographicallyPrecedes($1.longName) 505 | } 506 | expect(actual).to(equal(expected)) 507 | expect(actual.map { $0.name }).to(equal(expectedNames)) 508 | } 509 | } 510 | 511 | describe("Repository.localBranch(named:)") { 512 | it("should return the branch if it exists") { 513 | let result = Fixtures.simpleRepository.localBranch(named: "master") 514 | expect(result.value?.longName).to(equal("refs/heads/master")) 515 | } 516 | 517 | it("should error if the branch doesn't exists") { 518 | let result = Fixtures.simpleRepository.localBranch(named: "nonexistent") 519 | expect(result.error?.domain) == libGit2ErrorDomain 520 | } 521 | } 522 | 523 | describe("Repository.remoteBranch(named:)") { 524 | it("should return the branch if it exists") { 525 | let result = Fixtures.mantleRepository.remoteBranch(named: "upstream/master") 526 | expect(result.value?.longName).to(equal("refs/remotes/upstream/master")) 527 | } 528 | 529 | it("should error if the branch doesn't exists") { 530 | let result = Fixtures.simpleRepository.remoteBranch(named: "origin/nonexistent") 531 | expect(result.error?.domain) == libGit2ErrorDomain 532 | } 533 | } 534 | 535 | describe("Repository.fetch(_:)") { 536 | it("should fetch the data") { 537 | let repo = Fixtures.mantleRepository 538 | let remote = repo.remote(named: "origin").value! 539 | expect(repo.fetch(remote).value).toNot(beNil()) 540 | } 541 | } 542 | 543 | describe("Repository.allTags()") { 544 | it("should return all the tags") { 545 | let repo = Fixtures.simpleRepository 546 | let expected = [ 547 | repo.tag(named: "tag-1").value!, 548 | repo.tag(named: "tag-2").value!, 549 | ] 550 | expect(repo.allTags().value).to(equal(expected)) 551 | } 552 | } 553 | 554 | describe("Repository.tag(named:)") { 555 | it("should return the tag if it exists") { 556 | let result = Fixtures.simpleRepository.tag(named: "tag-2") 557 | expect(result.value?.longName).to(equal("refs/tags/tag-2")) 558 | } 559 | 560 | it("should error if the branch doesn't exists") { 561 | let result = Fixtures.simpleRepository.tag(named: "nonexistent") 562 | expect(result.error?.domain) == libGit2ErrorDomain 563 | } 564 | } 565 | 566 | describe("Repository.HEAD()") { 567 | it("should work when on a branch") { 568 | let result = Fixtures.simpleRepository.HEAD() 569 | expect(result.value?.longName).to(equal("refs/heads/master")) 570 | expect(result.value?.shortName).to(equal("master")) 571 | expect(result.value as? Branch).notTo(beNil()) 572 | } 573 | 574 | it("should work when on a detached HEAD") { 575 | let result = Fixtures.detachedHeadRepository.HEAD() 576 | expect(result.value?.longName).to(equal("HEAD")) 577 | expect(result.value?.shortName).to(beNil()) 578 | expect(result.value?.oid).to(equal(OID(string: "315b3f344221db91ddc54b269f3c9af422da0f2e")!)) 579 | expect(result.value as? Reference).notTo(beNil()) 580 | } 581 | } 582 | 583 | describe("Repository.setHEAD(OID)") { 584 | it("should set HEAD to the OID") { 585 | let repo = Fixtures.simpleRepository 586 | let oid = OID(string: "315b3f344221db91ddc54b269f3c9af422da0f2e")! 587 | expect(repo.HEAD().value?.shortName).to(equal("master")) 588 | 589 | expect(repo.setHEAD(oid).error).to(beNil()) 590 | let HEAD = repo.HEAD().value 591 | expect(HEAD?.longName).to(equal("HEAD")) 592 | expect(HEAD?.oid).to(equal(oid)) 593 | 594 | expect(repo.setHEAD(repo.localBranch(named: "master").value!).error).to(beNil()) 595 | expect(repo.HEAD().value?.shortName).to(equal("master")) 596 | } 597 | } 598 | 599 | describe("Repository.setHEAD(ReferenceType)") { 600 | it("should set HEAD to a branch") { 601 | let repo = Fixtures.detachedHeadRepository 602 | let oid = repo.HEAD().value!.oid 603 | expect(repo.HEAD().value?.longName).to(equal("HEAD")) 604 | 605 | let branch = repo.localBranch(named: "another-branch").value! 606 | expect(repo.setHEAD(branch).error).to(beNil()) 607 | expect(repo.HEAD().value?.shortName).to(equal(branch.name)) 608 | 609 | expect(repo.setHEAD(oid).error).to(beNil()) 610 | expect(repo.HEAD().value?.longName).to(equal("HEAD")) 611 | } 612 | } 613 | 614 | describe("Repository.checkout()") { 615 | // We're not really equipped to test this yet. :( 616 | } 617 | 618 | describe("Repository.checkout(OID)") { 619 | it("should set HEAD") { 620 | let repo = Fixtures.simpleRepository 621 | let oid = OID(string: "315b3f344221db91ddc54b269f3c9af422da0f2e")! 622 | expect(repo.HEAD().value?.shortName).to(equal("master")) 623 | 624 | expect(repo.checkout(oid, strategy: CheckoutStrategy.None).error).to(beNil()) 625 | let HEAD = repo.HEAD().value 626 | expect(HEAD?.longName).to(equal("HEAD")) 627 | expect(HEAD?.oid).to(equal(oid)) 628 | 629 | expect(repo.checkout(repo.localBranch(named: "master").value!, strategy: CheckoutStrategy.None).error).to(beNil()) 630 | expect(repo.HEAD().value?.shortName).to(equal("master")) 631 | } 632 | 633 | it("should call block on progress") { 634 | let repo = Fixtures.simpleRepository 635 | let oid = OID(string: "315b3f344221db91ddc54b269f3c9af422da0f2e")! 636 | expect(repo.HEAD().value?.shortName).to(equal("master")) 637 | 638 | expect(repo.checkout(oid, strategy: .None, progress: { (_, completedSteps, totalSteps) -> Void in 639 | expect(completedSteps).to(beLessThanOrEqualTo(totalSteps)) 640 | }).error).to(beNil()) 641 | 642 | let HEAD = repo.HEAD().value 643 | expect(HEAD?.longName).to(equal("HEAD")) 644 | expect(HEAD?.oid).to(equal(oid)) 645 | } 646 | } 647 | 648 | describe("Repository.checkout(ReferenceType)") { 649 | it("should set HEAD") { 650 | let repo = Fixtures.detachedHeadRepository 651 | let oid = repo.HEAD().value!.oid 652 | expect(repo.HEAD().value?.longName).to(equal("HEAD")) 653 | 654 | let branch = repo.localBranch(named: "another-branch").value! 655 | expect(repo.checkout(branch, strategy: CheckoutStrategy.None).error).to(beNil()) 656 | expect(repo.HEAD().value?.shortName).to(equal(branch.name)) 657 | 658 | expect(repo.checkout(oid, strategy: CheckoutStrategy.None).error).to(beNil()) 659 | expect(repo.HEAD().value?.longName).to(equal("HEAD")) 660 | } 661 | } 662 | 663 | describe("Repository.allCommits(in:)") { 664 | it("should return all (9) commits") { 665 | let repo = Fixtures.simpleRepository 666 | let branches = repo.localBranches().value! 667 | let expectedCount = 9 668 | let expectedMessages: [String] = [ 669 | "List branches in README\n", 670 | "Create a README\n", 671 | "Merge branch 'alphabetize'\n", 672 | "Alphabetize branches\n", 673 | "List new branches\n", 674 | "List branches in README\n", 675 | "Create a README\n", 676 | "List branches in README\n", 677 | "Create a README\n", 678 | ] 679 | var commitMessages: [String] = [] 680 | for branch in branches { 681 | for commit in repo.commits(in: branch) { 682 | commitMessages.append(commit.value!.message) 683 | } 684 | } 685 | expect(commitMessages.count).to(equal(expectedCount)) 686 | expect(commitMessages).to(equal(expectedMessages)) 687 | } 688 | } 689 | 690 | describe("Repository.add") { 691 | it("Should add the modification under a path") { 692 | let repo = Fixtures.simpleRepository 693 | let branch = repo.localBranch(named: "master").value! 694 | expect(repo.checkout(branch, strategy: CheckoutStrategy.None).error).to(beNil()) 695 | 696 | // make a change to README 697 | let readmeURL = repo.directoryURL!.appendingPathComponent("README.md") 698 | let readmeData = try! Data(contentsOf: readmeURL) 699 | defer { try! readmeData.write(to: readmeURL) } 700 | 701 | try! "different".data(using: .utf8)?.write(to: readmeURL) 702 | 703 | let status = repo.status() 704 | expect(status.value?.count).to(equal(1)) 705 | expect(status.value!.first!.status).to(equal(.workTreeModified)) 706 | 707 | expect(repo.add(path: "README.md").error).to(beNil()) 708 | 709 | let newStatus = repo.status() 710 | expect(newStatus.value?.count).to(equal(1)) 711 | expect(newStatus.value!.first!.status).to(equal(.indexModified)) 712 | } 713 | 714 | it("Should add an untracked file under a path") { 715 | let repo = Fixtures.simpleRepository 716 | let branch = repo.localBranch(named: "master").value! 717 | expect(repo.checkout(branch, strategy: CheckoutStrategy.None).error).to(beNil()) 718 | 719 | // make a change to README 720 | let untrackedURL = repo.directoryURL!.appendingPathComponent("untracked") 721 | try! "different".data(using: .utf8)?.write(to: untrackedURL) 722 | defer { try! FileManager.default.removeItem(at: untrackedURL) } 723 | 724 | expect(repo.add(path: ".").error).to(beNil()) 725 | 726 | let newStatus = repo.status() 727 | expect(newStatus.value?.count).to(equal(1)) 728 | expect(newStatus.value!.first!.status).to(equal(.indexNew)) 729 | } 730 | } 731 | 732 | describe("Repository.commit") { 733 | it("Should perform a simple commit with specified signature") { 734 | let repo = Fixtures.simpleRepository 735 | let branch = repo.localBranch(named: "master").value! 736 | expect(repo.checkout(branch, strategy: CheckoutStrategy.None).error).to(beNil()) 737 | 738 | // make a change to README 739 | let untrackedURL = repo.directoryURL!.appendingPathComponent("untrackedtest") 740 | try! "different".data(using: .utf8)?.write(to: untrackedURL) 741 | 742 | expect(repo.add(path: ".").error).to(beNil()) 743 | 744 | let signature = Signature( 745 | name: "swiftgit2", 746 | email: "foobar@example.com", 747 | time: Date(timeIntervalSince1970: 1525200858), 748 | timeZone: TimeZone(secondsFromGMT: 3600)! 749 | ) 750 | let message = "Test Commit" 751 | expect(repo.commit(message: message, signature: signature).error).to(beNil()) 752 | let updatedBranch = repo.localBranch(named: "master").value! 753 | expect(repo.commits(in: updatedBranch).next()?.value?.author).to(equal(signature)) 754 | expect(repo.commits(in: updatedBranch).next()?.value?.committer).to(equal(signature)) 755 | expect(repo.commits(in: updatedBranch).next()?.value?.message).to(equal("\(message)\n")) 756 | expect(repo.commits(in: updatedBranch).next()?.value?.oid.description) 757 | .to(equal("7d6b2d7492f29aee48022387f96dbfe996d435fe")) 758 | 759 | // should be clean now 760 | let newStatus = repo.status() 761 | expect(newStatus.value?.count).to(equal(0)) 762 | } 763 | } 764 | 765 | describe("Repository.status") { 766 | it("Should accurately report status for repositories with no status") { 767 | let expectedCount = 0 768 | 769 | let repo = Fixtures.mantleRepository 770 | let branch = repo.localBranch(named: "master").value! 771 | expect(repo.checkout(branch, strategy: CheckoutStrategy.None).error).to(beNil()) 772 | 773 | let status = repo.status() 774 | 775 | expect(status.value?.count).to(equal(expectedCount)) 776 | } 777 | 778 | it("Should accurately report status for repositories with status") { 779 | let expectedCount = 6 780 | let expectedNewFilePaths = [ 781 | "stage-file-1", 782 | "stage-file-2", 783 | "stage-file-3", 784 | "stage-file-4", 785 | "stage-file-5", 786 | ] 787 | let expectedOldFilePaths = [ 788 | "stage-file-1", 789 | "stage-file-2", 790 | "stage-file-3", 791 | "stage-file-4", 792 | "stage-file-5", 793 | ] 794 | let expectedUntrackedFiles = [ 795 | "unstaged-file", 796 | ] 797 | 798 | let repoWithStatus = Fixtures.sharedInstance.repository(named: "repository-with-status") 799 | let branchWithStatus = repoWithStatus.localBranch(named: "master").value! 800 | expect(repoWithStatus.checkout(branchWithStatus, strategy: CheckoutStrategy.None).error).to(beNil()) 801 | 802 | let statuses = repoWithStatus.status().value! 803 | 804 | var newFilePaths: [String] = [] 805 | for status in statuses { 806 | if let path = status.headToIndex?.newFile?.path { 807 | newFilePaths.append(path) 808 | } 809 | } 810 | var oldFilePaths: [String] = [] 811 | for status in statuses { 812 | if let path = status.headToIndex?.oldFile?.path { 813 | oldFilePaths.append(path) 814 | } 815 | } 816 | 817 | var newUntrackedFilePaths: [String] = [] 818 | for status in statuses { 819 | if let path = status.indexToWorkDir?.newFile?.path { 820 | newUntrackedFilePaths.append(path) 821 | } 822 | } 823 | 824 | expect(statuses.count).to(equal(expectedCount)) 825 | expect(newFilePaths).to(equal(expectedNewFilePaths)) 826 | expect(oldFilePaths).to(equal(expectedOldFilePaths)) 827 | expect(newUntrackedFilePaths).to(equal(expectedUntrackedFiles)) 828 | } 829 | } 830 | 831 | describe("Repository.diff") { 832 | it("Should have accurate delta information") { 833 | let expectedCount = 13 834 | let expectedNewFilePaths = [ 835 | ".gitmodules", 836 | "Cartfile", 837 | "Cartfile.lock", 838 | "Cartfile.private", 839 | "Cartfile.resolved", 840 | "Carthage.checkout/Nimble", 841 | "Carthage.checkout/Quick", 842 | "Carthage.checkout/xcconfigs", 843 | "Carthage/Checkouts/Nimble", 844 | "Carthage/Checkouts/Quick", 845 | "Carthage/Checkouts/xcconfigs", 846 | "Mantle.xcodeproj/project.pbxproj", 847 | "Mantle.xcworkspace/contents.xcworkspacedata", 848 | ] 849 | let expectedOldFilePaths = [ 850 | ".gitmodules", 851 | "Cartfile", 852 | "Cartfile.lock", 853 | "Cartfile.private", 854 | "Cartfile.resolved", 855 | "Carthage.checkout/Nimble", 856 | "Carthage.checkout/Quick", 857 | "Carthage.checkout/xcconfigs", 858 | "Carthage/Checkouts/Nimble", 859 | "Carthage/Checkouts/Quick", 860 | "Carthage/Checkouts/xcconfigs", 861 | "Mantle.xcodeproj/project.pbxproj", 862 | "Mantle.xcworkspace/contents.xcworkspacedata", 863 | ] 864 | 865 | let repo = Fixtures.mantleRepository 866 | let branch = repo.localBranch(named: "master").value! 867 | expect(repo.checkout(branch, strategy: CheckoutStrategy.None).error).to(beNil()) 868 | 869 | let head = repo.HEAD().value! 870 | let commit = repo.object(head.oid).value! as! Commit 871 | let diff = repo.diff(for: commit).value! 872 | 873 | let newFilePaths = diff.deltas.map { $0.newFile!.path } 874 | let oldFilePaths = diff.deltas.map { $0.oldFile!.path } 875 | 876 | expect(diff.deltas.count).to(equal(expectedCount)) 877 | expect(newFilePaths).to(equal(expectedNewFilePaths)) 878 | expect(oldFilePaths).to(equal(expectedOldFilePaths)) 879 | } 880 | 881 | it("Should handle initial commit well") { 882 | let expectedCount = 2 883 | let expectedNewFilePaths = [ 884 | ".gitignore", 885 | "README.md", 886 | ] 887 | let expectedOldFilePaths = [ 888 | ".gitignore", 889 | "README.md", 890 | ] 891 | 892 | let repo = Fixtures.mantleRepository 893 | expect(repo.checkout(OID(string: "047b931bd7f5478340cef5885a6fff713005f4d6")!, 894 | strategy: CheckoutStrategy.None).error).to(beNil()) 895 | let head = repo.HEAD().value! 896 | let initalCommit = repo.object(head.oid).value! as! Commit 897 | let diff = repo.diff(for: initalCommit).value! 898 | 899 | var newFilePaths: [String] = [] 900 | for delta in diff.deltas { 901 | newFilePaths.append((delta.newFile?.path)!) 902 | } 903 | var oldFilePaths: [String] = [] 904 | for delta in diff.deltas { 905 | oldFilePaths.append((delta.oldFile?.path)!) 906 | } 907 | 908 | expect(diff.deltas.count).to(equal(expectedCount)) 909 | expect(newFilePaths).to(equal(expectedNewFilePaths)) 910 | expect(oldFilePaths).to(equal(expectedOldFilePaths)) 911 | } 912 | 913 | it("Should handle merge commits well") { 914 | let expectedCount = 20 915 | let expectedNewFilePaths = [ 916 | "Mantle.xcodeproj/project.pbxproj", 917 | "Mantle/MTLModel+NSCoding.m", 918 | "Mantle/Mantle.h", 919 | "Mantle/NSArray+MTLHigherOrderAdditions.h", 920 | "Mantle/NSArray+MTLHigherOrderAdditions.m", 921 | "Mantle/NSArray+MTLManipulationAdditions.m", 922 | "Mantle/NSDictionary+MTLHigherOrderAdditions.h", 923 | "Mantle/NSDictionary+MTLHigherOrderAdditions.m", 924 | "Mantle/NSDictionary+MTLManipulationAdditions.m", 925 | "Mantle/NSNotificationCenter+MTLWeakReferenceAdditions.h", 926 | "Mantle/NSNotificationCenter+MTLWeakReferenceAdditions.m", 927 | "Mantle/NSOrderedSet+MTLHigherOrderAdditions.h", 928 | "Mantle/NSOrderedSet+MTLHigherOrderAdditions.m", 929 | "Mantle/NSSet+MTLHigherOrderAdditions.h", 930 | "Mantle/NSSet+MTLHigherOrderAdditions.m", 931 | "Mantle/NSValueTransformer+MTLPredefinedTransformerAdditions.m", 932 | "MantleTests/MTLHigherOrderAdditionsSpec.m", 933 | "MantleTests/MTLNotificationCenterAdditionsSpec.m", 934 | "MantleTests/MTLPredefinedTransformerAdditionsSpec.m", 935 | "README.md", 936 | ] 937 | let expectedOldFilePaths = [ 938 | "Mantle.xcodeproj/project.pbxproj", 939 | "Mantle/MTLModel+NSCoding.m", 940 | "Mantle/Mantle.h", 941 | "Mantle/NSArray+MTLHigherOrderAdditions.h", 942 | "Mantle/NSArray+MTLHigherOrderAdditions.m", 943 | "Mantle/NSArray+MTLManipulationAdditions.m", 944 | "Mantle/NSDictionary+MTLHigherOrderAdditions.h", 945 | "Mantle/NSDictionary+MTLHigherOrderAdditions.m", 946 | "Mantle/NSDictionary+MTLManipulationAdditions.m", 947 | "Mantle/NSNotificationCenter+MTLWeakReferenceAdditions.h", 948 | "Mantle/NSNotificationCenter+MTLWeakReferenceAdditions.m", 949 | "Mantle/NSOrderedSet+MTLHigherOrderAdditions.h", 950 | "Mantle/NSOrderedSet+MTLHigherOrderAdditions.m", 951 | "Mantle/NSSet+MTLHigherOrderAdditions.h", 952 | "Mantle/NSSet+MTLHigherOrderAdditions.m", 953 | "Mantle/NSValueTransformer+MTLPredefinedTransformerAdditions.m", 954 | "MantleTests/MTLHigherOrderAdditionsSpec.m", 955 | "MantleTests/MTLNotificationCenterAdditionsSpec.m", 956 | "MantleTests/MTLPredefinedTransformerAdditionsSpec.m", 957 | "README.md", 958 | ] 959 | 960 | let repo = Fixtures.mantleRepository 961 | expect(repo.checkout(OID(string: "d0d9c13da5eb5f9e8cf2a9f1f6ca3bdbe975b57d")!, 962 | strategy: CheckoutStrategy.None).error).to(beNil()) 963 | let head = repo.HEAD().value! 964 | let initalCommit = repo.object(head.oid).value! as! Commit 965 | let diff = repo.diff(for: initalCommit).value! 966 | 967 | var newFilePaths: [String] = [] 968 | for delta in diff.deltas { 969 | newFilePaths.append((delta.newFile?.path)!) 970 | } 971 | var oldFilePaths: [String] = [] 972 | for delta in diff.deltas { 973 | oldFilePaths.append((delta.oldFile?.path)!) 974 | } 975 | 976 | expect(diff.deltas.count).to(equal(expectedCount)) 977 | expect(newFilePaths).to(equal(expectedNewFilePaths)) 978 | expect(oldFilePaths).to(equal(expectedOldFilePaths)) 979 | } 980 | } 981 | } 982 | 983 | func temporaryURL(forPurpose purpose: String) -> URL { 984 | let globallyUniqueString = ProcessInfo.processInfo.globallyUniqueString 985 | let path = "\(NSTemporaryDirectory())\(globallyUniqueString)_\(purpose)" 986 | return URL(fileURLWithPath: path) 987 | } 988 | } 989 | -------------------------------------------------------------------------------- /SwiftGit2Tests/ResultShims.swift: -------------------------------------------------------------------------------- 1 | // Once Nimble adds matchers for the Result type, remove these shims and refactor the tests that use them. 2 | extension Result { 3 | var value: Success? { 4 | guard case .success(let value) = self else { 5 | return nil 6 | } 7 | return value 8 | } 9 | 10 | var error: Failure? { 11 | guard case .failure(let error) = self else { 12 | return nil 13 | } 14 | return error 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /libgit2/git2: -------------------------------------------------------------------------------- 1 | ../External/libgit2/include/git2 -------------------------------------------------------------------------------- /libgit2/git2.h: -------------------------------------------------------------------------------- 1 | ../External/libgit2/include/git2.h -------------------------------------------------------------------------------- /libgit2/module.modulemap: -------------------------------------------------------------------------------- 1 | module Clibgit2 { 2 | umbrella header "git2.h" 3 | 4 | export * 5 | module * { export * } 6 | 7 | // Exclude headers intended only for Microsoft compilers 8 | exclude header "git2/inttypes.h" 9 | exclude header "git2/stdint.h" 10 | 11 | // Explicit modules for headers not included in the umbrella header: 12 | explicit module cred_helpers { 13 | header "git2/cred_helpers.h" 14 | 15 | export * 16 | } 17 | 18 | explicit module trace { 19 | header "git2/trace.h" 20 | 21 | export * 22 | } 23 | 24 | // Explicit module for the "sys" headers: 25 | explicit module sys { 26 | umbrella "git2/sys" 27 | 28 | export * 29 | module * { export * } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /script/bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export SCRIPT_DIR=$(dirname "$0") 4 | 5 | ## 6 | ## Configuration Variables 7 | ## 8 | 9 | config () 10 | { 11 | # A whitespace-separated list of executables that must be present and locatable. 12 | # These will each be installed through Homebrew if not found. 13 | : ${REQUIRED_TOOLS="cmake libssh2 libtool autoconf automake pkg-config"} 14 | 15 | export REQUIRED_TOOLS 16 | } 17 | 18 | ## 19 | ## Bootstrap Process 20 | ## 21 | 22 | main () 23 | { 24 | config 25 | 26 | local submodules=$(git submodule status) 27 | local result=$? 28 | 29 | if [ "$result" -ne "0" ] 30 | then 31 | exit $result 32 | fi 33 | 34 | if [ -n "$submodules" ] 35 | then 36 | echo "*** Updating submodules..." 37 | update_submodules 38 | fi 39 | 40 | if [ -n "$REQUIRED_TOOLS" ] 41 | then 42 | echo "*** Checking dependencies..." 43 | check_deps 44 | fi 45 | } 46 | 47 | check_deps () 48 | { 49 | # Check if Homebrew is installed 50 | which -s brew 51 | local result=$? 52 | 53 | if [ "$result" -ne "0" ] 54 | then 55 | echo 56 | echo "Homebrew is not installed (http://brew.sh). You will need to manually ensure the following tools are installed:" 57 | echo " $REQUIRED_TOOLS" 58 | echo 59 | echo "Additionally, the following libssh2 files must be symlinked under /usr/local:" 60 | echo " lib/libssh2.a include/libssh2.h include/libssh2_sftp.h include/libssh2_publickey.h" 61 | exit $result 62 | fi 63 | 64 | # Ensure that we have libgit2's dependencies installed. 65 | installed=`brew list` 66 | 67 | for tool in $REQUIRED_TOOLS 68 | do 69 | # Skip packages that are already installed. 70 | echo "$installed" | grep -q "$tool" && code=$? || code=$? 71 | 72 | if [ "$code" -eq "0" ] 73 | then 74 | continue 75 | elif [ "$code" -ne "1" ] 76 | then 77 | exit $code 78 | fi 79 | 80 | echo "*** Installing $tool with Homebrew..." 81 | brew install "$tool" 82 | done 83 | 84 | brew_prefix=`brew --prefix` 85 | expected_prefix=/usr/local 86 | 87 | if [ "$brew_prefix" != "$expected_prefix" ] 88 | then 89 | echo "*** Adding soft links into $expected_prefix..." 90 | 91 | products=(lib/libssh2.a include/libssh2.h include/libssh2_sftp.h include/libssh2_publickey.h) 92 | 93 | for product in "${products[@]}" 94 | do 95 | destination="$expected_prefix/$product" 96 | if [ -e "$destination" ] 97 | then 98 | continue 99 | fi 100 | 101 | sudo mkdir -p "$(dirname "$destination")" 102 | sudo ln -s "$brew_prefix/$product" "$destination" 103 | done 104 | fi 105 | } 106 | 107 | bootstrap_submodule () 108 | { 109 | local bootstrap="script/bootstrap" 110 | 111 | if [ -e "$bootstrap" ] 112 | then 113 | echo "*** Bootstrapping $name..." 114 | "$bootstrap" >/dev/null 115 | else 116 | update_submodules 117 | fi 118 | } 119 | 120 | update_submodules () 121 | { 122 | git submodule sync --quiet && \ 123 | git submodule update --init && \ 124 | git submodule foreach --quiet bootstrap_submodule 125 | } 126 | 127 | export -f bootstrap_submodule 128 | export -f update_submodules 129 | 130 | main 131 | -------------------------------------------------------------------------------- /script/cibuild: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | # 3 | # script/cibuild 4 | # SwiftGit2 5 | # 6 | # Executes the build and runs tests for Mac and iOS. Designed to be invoked by 7 | # Travis as a matrix build so that the two platform builds can run in parallel. 8 | # 9 | # Dependent tools & scripts: 10 | # - script/bootstrap 11 | # - script/update_libssl_ios 12 | # - [xcodebuild](https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/xcodebuild.1.html) 13 | # - xcpretty (gem) 14 | # - xcpretty-travis-formatter (gem) 15 | # 16 | # Environment Variables: 17 | # - SCHEME: specifies which Xcode scheme to build. Set to one of the following: 18 | # - SwiftGit2-OSX 19 | # - SwiftGit2-iOS 20 | # - TRAVIS: indicates when the build is being run by travis, used to invoke 21 | # the xcpretty-travis-formatter gem for output. 22 | 23 | if [ -z "$SCHEME" ]; then 24 | echo "The SCHEME environment variable is empty. Please set this to one of:" 25 | echo "- SwiftGit2-OSX" 26 | echo "- SwiftGit2-iOS" 27 | exit 1 28 | fi 29 | 30 | 31 | ## 32 | ## Configuration Variables 33 | ## 34 | 35 | set -o pipefail 36 | SCRIPT_DIR=$(dirname "$0") 37 | XCWORKSPACE="SwiftGit2.xcworkspace" 38 | XCODE_OPTIONS=$(RUN_CLANG_STATIC_ANALYZER=NO ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO) 39 | 40 | if [ -n "$TRAVIS" ]; then 41 | # Use a special formatter when running on TravisCI 42 | XCPRETTY_FORMAT_OPTIONS="-f `xcpretty-travis-formatter`" 43 | else 44 | XCPRETTY_FORMAT_OPTIONS="--color" 45 | fi 46 | 47 | ## 48 | ## Build Process 49 | ## 50 | 51 | echo "*** Bootstrapping..." 52 | "$SCRIPT_DIR/bootstrap" 53 | 54 | if [ "$SCHEME" == "SwiftGit2-OSX" ]; then 55 | echo "*** Building and testing $SCHEME..." 56 | echo 57 | 58 | xcodebuild -workspace "$XCWORKSPACE" \ 59 | -scheme "$SCHEME" \ 60 | ${XCODE_OPTIONS[*]} \ 61 | build test \ 62 | 2>&1 | xcpretty $XCPRETTY_FORMAT_OPTIONS 63 | elif [ "$SCHEME" == "SwiftGit2-iOS" ]; then 64 | echo "*** Prebuilding OpenSSL" 65 | "$SCRIPT_DIR/update_libssl_ios" 66 | 67 | echo "*** Creating a simulator for testing..." 68 | # Create a new simulator device and reference it by id to avoid xcodebuild exit status 70 with the following errors: 69 | # "The requested device could not be found because no available devices matched the request." 70 | # "The requested device could not be found because multiple devices matched the request." 71 | SIMULATOR_NAME="Custom Simulator" 72 | DEVICE_ID=com.apple.CoreSimulator.SimDeviceType.iPhone-X 73 | RUNTIME_ID=com.apple.CoreSimulator.SimRuntime.iOS-11-4 74 | DESTINATION_ID=$(xcrun simctl create "$SIMULATOR_NAME" $DEVICE_ID $RUNTIME_ID) 75 | 76 | echo "*** Building and testing $SCHEME..." 77 | echo 78 | 79 | xcodebuild -workspace "$XCWORKSPACE" \ 80 | -scheme "$SCHEME" \ 81 | -destination "id=$DESTINATION_ID" \ 82 | -sdk iphonesimulator \ 83 | ${XCODE_OPTIONS[*]} \ 84 | build test \ 85 | 2>&1 | xcpretty $XCPRETTY_FORMAT_OPTIONS 86 | fi 87 | -------------------------------------------------------------------------------- /script/clean_externals: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | # 4 | # clean_externals 5 | # SwiftGit2 6 | # 7 | # Removes the outputs from the various static library targets. 8 | # Necessary when switching platforms/architectures as Xcode does not clean 9 | # these for you. 10 | # 11 | 12 | # A list of external static libraries included in the SwiftGit2 framework 13 | libraries=( 14 | External/libgit2.a 15 | External/libgit2-ios/libgit2-ios.a 16 | External/libssh2-ios/lib/libssh2-ios.a 17 | External/ios-openssl/lib/libssl.a 18 | External/ios-openssl/lib/libcrypto.a 19 | External/ios-openssl/include 20 | ) 21 | 22 | rm -vrf "${libraries[@]}" 23 | -------------------------------------------------------------------------------- /script/ios_build_functions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=$(dirname "$0") 4 | source "${SCRIPT_DIR}/xcode_functions.sh" 5 | 6 | function setup_build_environment () 7 | { 8 | # augment path to help it find cmake installed in /usr/local/bin, 9 | # e.g. via brew. Xcode's Run Script phase doesn't seem to honor 10 | # ~/.MacOSX/environment.plist 11 | PATH="/usr/local/bin:/opt/boxen/homebrew/bin:$PATH" 12 | 13 | pushd "$SCRIPT_DIR/.." > /dev/null 14 | ROOT_PATH="$PWD" 15 | popd > /dev/null 16 | 17 | CLANG="/usr/bin/xcrun clang" 18 | CC="${CLANG}" 19 | CPP="${CLANG} -E" 20 | 21 | # We need to clear this so that cmake doesn't have a conniption 22 | MACOSX_DEPLOYMENT_TARGET="" 23 | 24 | XCODE_MAJOR_VERSION=$(xcode_major_version) 25 | 26 | CAN_BUILD_64BIT="0" 27 | 28 | # If IPHONEOS_DEPLOYMENT_TARGET has not been specified 29 | # setup reasonable defaults to allow running of a build script 30 | # directly (ie not from an Xcode proj) 31 | if [ -z "${IPHONEOS_DEPLOYMENT_TARGET}" ] 32 | then 33 | IPHONEOS_DEPLOYMENT_TARGET="6.0" 34 | fi 35 | 36 | # Determine if we can be building 64-bit binaries 37 | if [ "${XCODE_MAJOR_VERSION}" -ge "5" ] && [ $(echo ${IPHONEOS_DEPLOYMENT_TARGET} '>=' 6.0 | bc -l) == "1" ] 38 | then 39 | CAN_BUILD_64BIT="1" 40 | fi 41 | 42 | ARCHS="i386 armv7 armv7s" 43 | if [ "${CAN_BUILD_64BIT}" -eq "1" ] 44 | then 45 | # For some stupid reason cmake needs simulator 46 | # builds to be first 47 | ARCHS="x86_64 ${ARCHS} arm64" 48 | fi 49 | } 50 | 51 | function build_all_archs () 52 | { 53 | setup_build_environment 54 | 55 | local setup=$1 56 | local build_arch=$2 57 | local finish_build=$3 58 | 59 | # run the prepare function 60 | eval $setup 61 | 62 | echo "Building for ${ARCHS}" 63 | 64 | for ARCH in ${ARCHS} 65 | do 66 | if [ "${ARCH}" == "i386" ] || [ "${ARCH}" == "x86_64" ] 67 | then 68 | PLATFORM="iphonesimulator" 69 | else 70 | PLATFORM="iphoneos" 71 | fi 72 | 73 | SDKVERSION=$(ios_sdk_version) 74 | 75 | if [ "${ARCH}" == "arm64" ] 76 | then 77 | HOST="aarch64-apple-darwin" 78 | else 79 | HOST="${ARCH}-apple-darwin" 80 | fi 81 | 82 | SDKNAME="${PLATFORM}${SDKVERSION}" 83 | SDKROOT="$(ios_sdk_path ${SDKNAME})" 84 | 85 | echo "Building ${LIBRARY_NAME} for ${SDKNAME} ${ARCH}" 86 | echo "Please stand by..." 87 | 88 | # run the per arch build command 89 | eval $build_arch 90 | done 91 | 92 | # finish the build (usually lipo) 93 | eval $finish_build 94 | } 95 | 96 | -------------------------------------------------------------------------------- /script/update_libgit2: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | # augment path to help it find cmake installed in /usr/local/bin, 6 | # e.g. via brew. Xcode's Run Script phase doesn't seem to honor 7 | # ~/.MacOSX/environment.plist 8 | PATH="/usr/local/bin:$PATH" 9 | 10 | if [ "External/libgit2.a" -nt "External/libgit2" ] 11 | then 12 | echo "No update needed." 13 | exit 0 14 | fi 15 | 16 | cd "External/libgit2" 17 | 18 | if [ -d "build" ]; then 19 | rm -rf "build" 20 | fi 21 | 22 | mkdir build 23 | cd build 24 | 25 | cmake -DBUILD_SHARED_LIBS:BOOL=OFF \ 26 | -DLIBSSH2_INCLUDE_DIRS:PATH=/usr/local/include/ \ 27 | -DBUILD_CLAR:BOOL=OFF \ 28 | -DTHREADSAFE:BOOL=ON \ 29 | .. 30 | cmake --build . 31 | 32 | product="libgit2.a" 33 | install_path="../../${product}" 34 | if [ "${product}" -nt "${install_path}" ]; then 35 | cp -v "${product}" "${install_path}" 36 | fi 37 | 38 | echo "libgit2 has been updated." 39 | -------------------------------------------------------------------------------- /script/update_libgit2_ios: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # source the common build functions 6 | SCRIPT_DIR=$(dirname "$0") 7 | source "${SCRIPT_DIR}/ios_build_functions.sh" 8 | 9 | function setup () 10 | { 11 | if [ "${ROOT_PATH}/External/libgit2-ios/libgit2-ios.a" -nt "${ROOT_PATH}/External/libgit2" ] 12 | then 13 | echo "No update needed." 14 | exit 0 15 | fi 16 | 17 | LIBRARY_NAME="libgit2" 18 | LIB_PATH="${ROOT_PATH}/External/libgit2-ios" 19 | rm -rf "${LIB_PATH}" 20 | 21 | pushd "${ROOT_PATH}/External/libgit2" > /dev/null 22 | } 23 | 24 | function build_libgit2 () 25 | { 26 | rm -rf "build" 27 | mkdir "build" 28 | 29 | pushd "build" > /dev/null 30 | 31 | # LOL Cmake 32 | if [ "${ARCH}" != "i386" ] && [ "${ARCH}" != "x86_64" ] 33 | then 34 | SYS_ROOT="-DCMAKE_OSX_SYSROOT=${SDKROOT}" 35 | fi 36 | 37 | # install the each built arch somewhere sane 38 | INSTALL_PREFIX="${LIB_PATH}/${SDKNAME}-${ARCH}.sdk" 39 | 40 | mkdir -p "${INSTALL_PREFIX}" 41 | 42 | LOG="${INSTALL_PREFIX}/build-libgit2.log" 43 | echo "$LOG" 44 | 45 | cmake \ 46 | -DCMAKE_C_COMPILER_WORKS:BOOL=ON \ 47 | -DBUILD_SHARED_LIBS:BOOL=OFF \ 48 | -DCMAKE_PREFIX_PATH:PATH="${ROOT_PATH}/External/libssh2-ios/bin/${SDKNAME}-${ARCH}.sdk" \ 49 | -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH:BOOL=ON \ 50 | -DCMAKE_INSTALL_PREFIX:PATH="${INSTALL_PREFIX}/" \ 51 | -DBUILD_CLAR:BOOL=OFF \ 52 | -DTHREADSAFE:BOOL=ON \ 53 | -DCURL:BOOL=OFF \ 54 | -DCMAKE_C_FLAGS:STRING="-fembed-bitcode" \ 55 | "${SYS_ROOT}" \ 56 | -DCMAKE_OSX_ARCHITECTURES:STRING="${ARCH}" \ 57 | .. >> "${LOG}" 2>&1 58 | cmake --build . --target install >> "${LOG}" 2>&1 59 | 60 | # push the built library into the list 61 | BUILT_LIB_PATHS+=("${INSTALL_PREFIX}/lib/libgit2.a") 62 | popd > /dev/null 63 | } 64 | 65 | function fat_binary () 66 | { 67 | echo "Building fat binary..." 68 | 69 | lipo -create "${BUILT_LIB_PATHS[@]}" -output "${ROOT_PATH}/External/libgit2-ios/libgit2-ios.a" 70 | 71 | echo "Building done." 72 | 73 | popd > /dev/null 74 | } 75 | 76 | build_all_archs setup build_libgit2 fat_binary 77 | -------------------------------------------------------------------------------- /script/update_libssh2_ios: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # source the common build functions 6 | SCRIPT_DIR=$(dirname "$0") 7 | source "${SCRIPT_DIR}/ios_build_functions.sh" 8 | 9 | function setup () 10 | { 11 | if [ -f "${ROOT_PATH}/External/libssh2-ios/lib/libssh2-ios.a" ] 12 | then 13 | echo "No update needed." 14 | exit 0 15 | fi 16 | LIBRARY_NAME="libssh2" 17 | } 18 | 19 | function build_ssh2 () 20 | { 21 | mkdir -p "${ROOT_PATH}/External/libssh2-ios/lib" "${ROOT_PATH}/External/libssh2-ios/lib" "${ROOT_PATH}/External/libssh2-ios/src" 22 | 23 | rm -rf "${ROOT_PATH}/External/libssh2-ios/src/libssh2" 24 | cp -R "${ROOT_PATH}/External/libssh2" "${ROOT_PATH}/External/libssh2-ios/src/" 25 | pushd "${ROOT_PATH}/External/libssh2-ios/src/libssh2" > /dev/null 26 | 27 | export CFLAGS="-arch ${ARCH} -fembed-bitcode -pipe -no-cpp-precomp -isysroot ${SDKROOT} -miphoneos-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" 28 | export CPPFLAGS="-arch ${ARCH} -fembed-bitcode -pipe -no-cpp-precomp -isysroot ${SDKROOT} -miphoneos-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" 29 | 30 | mkdir -p "${ROOT_PATH}/External/libssh2-ios/bin/${SDKNAME}-${ARCH}.sdk" 31 | LOG="${ROOT_PATH}/External/libssh2-ios/bin/${SDKNAME}-${ARCH}.sdk/build-libssh2.log" 32 | 33 | echo "${LOG}" 34 | 35 | ./buildconf >> "${LOG}" 2>&1 36 | ./configure --host=${HOST} --prefix="${ROOT_PATH}/External/libssh2-ios/bin/${SDKNAME}-${ARCH}.sdk" --with-openssl --with-libssl-prefix="${ROOT_PATH}/External/ios-openssl" --disable-shared --enable-static >> "${LOG}" 2>&1 37 | make >> "${LOG}" 2>&1 38 | make install >> "${LOG}" 2>&1 39 | popd > /dev/null 40 | 41 | BUILT_LIBS+=("${ROOT_PATH}/External/libssh2-ios/bin/${SDKNAME}-${ARCH}.sdk/lib/libssh2.a") 42 | } 43 | 44 | function fat_binary () 45 | { 46 | echo "Building fat binary..." 47 | 48 | lipo -create "${BUILT_LIBS[@]}" -output "${ROOT_PATH}/External/libssh2-ios/lib/libssh2-ios.a" 49 | mkdir -p "${ROOT_PATH}/External/libssh2-ios/include/libssh2" 50 | cp -R "${ROOT_PATH}/External/libssh2-ios/bin/iphonesimulator${SDKVERSION}-i386.sdk/include/libssh2.h" "${ROOT_PATH}/External/libssh2-ios/include/libssh2/" 51 | 52 | echo "Building done." 53 | } 54 | 55 | build_all_archs setup build_ssh2 fat_binary 56 | -------------------------------------------------------------------------------- /script/update_libssl_ios: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # source the common build functions 4 | SCRIPT_DIR=$(dirname "$0") 5 | source "${SCRIPT_DIR}/ios_build_functions.sh" 6 | 7 | function setup () 8 | { 9 | if [ -f "${ROOT_PATH}/External/ios-openssl/lib/libssl.a" ] && [ -f "${ROOT_PATH}/External/ios-openssl/lib/libcrypto.a" ] && [ -d "${ROOT_PATH}/External/ios-openssl/include" ] 10 | then 11 | echo "No update needed." 12 | exit 0 13 | fi 14 | 15 | LIBRARY_NAME="OpenSSL" 16 | 17 | rm -rf "${ROOT_PATH}/External/ios-openssl/include" "External/ios-openssl/lib" 18 | } 19 | 20 | function cleanup () 21 | { 22 | rm -rf "/tmp/openssl" 23 | rm -rf "/tmp/openssl-*.log" 24 | } 25 | 26 | function build_ssl () 27 | { 28 | rm -rf "/tmp/openssl" 29 | cp -r "${ROOT_PATH}/External/openssl" "/tmp/" 30 | pushd "/tmp/openssl" > /dev/null 31 | 32 | LOG="/tmp/openssl-${ARCH}.log" 33 | 34 | if [ "${ARCH}" == "arm64" ] || [ "${ARCH}" == "x86_64" ] 35 | then 36 | HOST="BSD-generic64" 37 | CONFIG="no-gost no-asm enable-ec_nistp_64_gcc_128" 38 | else 39 | HOST="BSD-generic32" 40 | CONFIG="no-gost no-asm" 41 | perl -i -pe 's|static volatile sig_atomic_t intr_signal|static volatile int intr_signal|' crypto/ui/ui_openssl.c 42 | fi 43 | echo "$LOG" 44 | 45 | ./Configure ${HOST} ${CONFIG} --openssldir="/tmp/openssl-${ARCH}" >> "${LOG}" 2>&1 46 | perl -i -pe "s|^CC= gcc|CC= ${CLANG} -miphoneos-version-min=${IPHONEOS_DEPLOYMENT_TARGET} -arch ${ARCH} -fembed-bitcode |g" Makefile >> "${LOG}" 2>&1 47 | perl -i -pe "s|^CFLAG= (.*)|CFLAG= -isysroot ${SDKROOT} \$1|g" Makefile >> "${LOG}" 2>&1 48 | make >> "${LOG}" 2>&1 49 | 50 | make install_sw >> "${LOG}" 2>&1 51 | popd > /dev/null 52 | rm -rf "/tmp/openssl" 53 | 54 | BUILT_CRYPTO_PATHS+=("/tmp/openssl-${ARCH}/lib/libcrypto.a") 55 | BUILT_SSL_PATHS+=("/tmp/openssl-${ARCH}/lib/libssl.a") 56 | } 57 | 58 | function fat_binary () 59 | { 60 | echo "Building fat binary..." 61 | 62 | mkdir -p "${ROOT_PATH}/External/ios-openssl/include" 63 | cp -r /tmp/openssl-i386/include/openssl "${ROOT_PATH}/External/ios-openssl/include/" 64 | 65 | mkdir -p "${ROOT_PATH}/External/ios-openssl/lib" 66 | 67 | lipo -create "${BUILT_CRYPTO_PATHS[@]}" -output "${ROOT_PATH}/External/ios-openssl/lib/libcrypto.a" 68 | lipo -create "${BUILT_SSL_PATHS[@]}" -output "${ROOT_PATH}/External/ios-openssl/lib/libssl.a" 69 | 70 | echo "Building done." 71 | } 72 | 73 | cleanup 74 | build_all_archs setup build_ssl fat_binary 75 | cleanup 76 | -------------------------------------------------------------------------------- /script/xcode_functions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Returns the version # of xcodebuild 4 | # eg. (4.6.3, 5.0, 5.0.1) 5 | function xcode_version () 6 | { 7 | /usr/bin/xcodebuild -version 2> /dev/null | head -n 1 | awk '{ print $2 }' 8 | } 9 | 10 | # Returns the major version of xcodebuild 11 | # eg. (4, 5, 6) 12 | function xcode_major_version () 13 | { 14 | xcode_version | awk -F '.' '{ print $1 }' 15 | } 16 | 17 | # Returns the latest iOS SDK version available via xcodebuild. 18 | function ios_sdk_version () 19 | { 20 | # The grep command produces output like the following, singling out the 21 | # SDKVersion of just the iPhone* SDKs: 22 | # 23 | # iPhoneOS9.0.sdk - iOS 9.0 (iphoneos9.0) 24 | # SDKVersion: 9.0 25 | # -- 26 | # iPhoneSimulator9.0.sdk - Simulator - iOS 9.0 (iphonesimulator9.0) 27 | # SDKVersion: 9.0 28 | 29 | /usr/bin/xcodebuild -version -sdk 2> /dev/null | grep -A 1 '^iPhone' | tail -n 1 | awk '{ print $2 }' 30 | } 31 | 32 | # Returns the path to the specified iOS SDK name 33 | function ios_sdk_path () 34 | { 35 | /usr/bin/xcodebuild -version -sdk 2> /dev/null | grep -i $1 | grep 'Path:' | awk '{ print $2 }' 36 | } 37 | --------------------------------------------------------------------------------