├── .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 | [](https://travis-ci.org/SwiftGit2/SwiftGit2)
3 | [](#carthage)
4 | [](https://github.com/SwiftGit2/SwiftGit2/releases)
5 | 
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 |
--------------------------------------------------------------------------------