├── .gitignore ├── .gitmodules ├── .travis.yml ├── Examples └── ExampleApp │ ├── Default-568h@2x.png │ ├── ExampleApp.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── ExampleApp.xcworkspace │ └── contents.xcworkspacedata │ ├── ExampleApp │ ├── AppDelegate.swift │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ └── Info.plist │ └── ExampleAppTests │ ├── AppDelegateTests.swift │ └── Info.plist ├── Gift.modulemap ├── Gift.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── Gift-OSX.xcscheme │ └── Gift-iOS.xcscheme ├── Gift.xcworkspace └── contents.xcworkspacedata ├── Gift ├── Branch │ ├── BranchIterator.swift │ └── BranchType.swift ├── Commit │ ├── Commit+Branch.swift │ ├── Commit+Merge.swift │ ├── Commit+ObjectID.swift │ ├── Commit+Repository.swift │ ├── Commit.swift │ ├── CommitSorting.swift │ ├── CommitWalker+RAC.swift │ ├── CommitWalker.swift │ └── Options │ │ ├── MergeOptions+C.swift │ │ ├── MergeOptions.swift │ │ └── MergeStrategy.swift ├── Error │ ├── Domain.swift │ ├── Error.swift │ └── NSError+Domain.swift ├── Gift.h ├── Gift.m ├── Index │ ├── Index+Repository.swift │ ├── Index+Tree.swift │ ├── Index.swift │ └── Options │ │ └── IndexAddOptions.swift ├── Info.plist ├── Object │ ├── Object+ObjectID.swift │ ├── Object.swift │ ├── ObjectID.swift │ └── ObjectType.swift ├── Reference │ ├── Reference+Commit.swift │ ├── Reference+Object.swift │ ├── Reference+ObjectID.swift │ ├── Reference+Repository.swift │ ├── Reference+Tag.swift │ └── Reference.swift ├── Remote │ └── Remote.swift ├── Repository │ ├── Options │ │ ├── Checkout │ │ │ ├── CheckoutOptions+C.swift │ │ │ ├── CheckoutOptions.swift │ │ │ ├── CheckoutStrategy.swift │ │ │ ├── GIFTCheckoutOptions.h │ │ │ └── GIFTCheckoutOptions.m │ │ ├── Clone │ │ │ ├── CloneOptions+C.swift │ │ │ ├── CloneOptions.swift │ │ │ └── LocalCloneBehavior.swift │ │ ├── Initialization │ │ │ ├── RepositoryInitializationMode.swift │ │ │ ├── RepositoryInitializationOptionSet.swift │ │ │ ├── RepositoryInitializationOptions+C.swift │ │ │ └── RepositoryInitializationOptions.swift │ │ ├── RemoteCallbacks │ │ │ ├── GIFTRemoteCallbacks.h │ │ │ ├── GIFTRemoteCallbacks.m │ │ │ ├── GIFTRemoteCallbacksPayload.h │ │ │ ├── GIFTRemoteCallbacksPayload.m │ │ │ ├── GIFTTransferProgress.h │ │ │ ├── RemoteCallbacks+C.swift │ │ │ └── RemoteCallbacks.swift │ │ ├── Reset │ │ │ └── ResetType.swift │ │ └── Tag │ │ │ ├── GIFTTagForEach.h │ │ │ └── GIFTTagForEach.m │ ├── Repository+Branch.swift │ ├── Repository+Commit.swift │ ├── Repository+Index.swift │ ├── Repository+Reference.swift │ ├── Repository+Remote.swift │ ├── Repository+Stash.swift │ ├── Repository+Status.swift │ ├── Repository+Tag.swift │ └── Repository.swift ├── Signature │ ├── Signature+C.swift │ └── Signature.swift ├── Status │ ├── EntryStatus │ │ └── EntryStatus.swift │ └── StatusDelta │ │ ├── Options │ │ ├── StatusDeltaBehavior.swift │ │ ├── StatusDeltaBetween.swift │ │ ├── StatusDeltaOptions+C.swift │ │ └── StatusDeltaOptions.swift │ │ ├── Status.swift │ │ ├── StatusDelta.swift │ │ ├── StatusDeltaType.swift │ │ ├── StatusDeltas.swift │ │ └── StatusDescriptorSet.swift ├── String │ └── String+GitStrArray.swift ├── Tag │ ├── Tag+ObjectID.swift │ └── Tag.swift ├── Tree │ ├── Tree+Commit.swift │ ├── Tree+ObjectID.swift │ ├── Tree+Repository.swift │ └── Tree.swift └── URL │ └── NSURL+String.swift ├── GiftTests ├── Commit │ ├── Commit+BranchSpec.swift │ └── Commit+MergeSpec.swift ├── Fixtures │ ├── .gitignore │ └── Fixtures.zip ├── GiftTests-Bridging-Header.h ├── Helpers │ ├── FilePathHelpers.swift │ ├── Result+Compact.swift │ └── SignalProducer+Array.swift ├── Index │ ├── Index+TreeSpec.swift │ └── IndexSpec.swift ├── Info.plist ├── Matchers │ └── ResultMatchers.swift ├── Reference │ ├── Reference+CommitSpec.swift │ ├── Reference+TagSpec.swift │ └── ReferenceSpec.swift ├── Repository │ ├── Repository+BranchSpec.swift │ ├── Repository+CommitSpec.swift │ ├── Repository+ReferenceSpec.swift │ ├── Repository+RemoteSpec.swift │ ├── Repository+StashSpec.swift │ ├── Repository+StatusSpec.swift │ ├── Repository+TagSpec.swift │ └── RepositorySpec.swift └── Tree │ └── Tree+CommitSpec.swift ├── LICENSE ├── README.md ├── Rakefile └── Scripts ├── build_ios.rake ├── build_osx.rake ├── dependencies.rake ├── helpers.rb └── test.rake /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.pbxuser 3 | !default.pbxuser 4 | *.mode1v3 5 | !default.mode1v3 6 | *.mode2v3 7 | !default.mode2v3 8 | *.perspectivev3 9 | !default.perspectivev3 10 | xcuserdata 11 | *.xccheckout 12 | *.moved-aside 13 | DerivedData 14 | *.hmap 15 | *.ipa 16 | *.xcuserstate 17 | 18 | # Unit tests make use of a set of prepared Git repositories. 19 | # There repositories are stored in a Fixtures directory. 20 | # The repositories themselves should not be tracked. Instead, 21 | # a zip file containing the repositories is checked into source 22 | # control. The tests then unzip that zip file as needed. 23 | GiftTests/Fixtures/Fixtures/ # Zipped to "GiftTests/Fixtures/Fixtures.zip" 24 | 25 | # Gift-iOS requires fat libraries for OpenSSL (libcrypto.a and libssl.a), 26 | # libssh2, and libgit2. Rake tasks build these libraries to the following 27 | # directories: 28 | External/openssl-iOS/ 29 | External/libgit2-iOS/ 30 | 31 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "External/libgit2"] 2 | path = External/libgit2 3 | url = https://github.com/libgit2/libgit2.git 4 | [submodule "External/LlamaKit"] 5 | path = External/LlamaKit 6 | url = https://github.com/LlamaKit/LlamaKit.git 7 | [submodule "External/Quick"] 8 | path = External/Quick 9 | url = https://github.com/Quick/Quick.git 10 | [submodule "External/Nimble"] 11 | path = External/Nimble 12 | url = https://github.com/Quick/Nimble.git 13 | [submodule "External/ssziparchive"] 14 | path = External/ssziparchive 15 | url = https://github.com/soffes/ssziparchive.git 16 | [submodule "External/libssh2-for-iOS"] 17 | path = External/libssh2-for-iOS 18 | url = https://github.com/x2on/libssh2-for-iOS.git 19 | [submodule "External/ReactiveCocoa"] 20 | path = External/ReactiveCocoa 21 | url = https://github.com/ReactiveCocoa/ReactiveCocoa.git 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | before_install: "rake dependencies build:ios" 4 | script: "xcodebuild -workspace Gift.xcworkspace -scheme Gift-iOS" 5 | 6 | -------------------------------------------------------------------------------- /Examples/ExampleApp/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modocache/Gift/33d51b2f0d1e718121999019a1c827ec2512e168/Examples/ExampleApp/Default-568h@2x.png -------------------------------------------------------------------------------- /Examples/ExampleApp/ExampleApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | DAF3143D1A71FFDA0054CCDC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAF3143C1A71FFDA0054CCDC /* AppDelegate.swift */; }; 11 | DAF314441A71FFDA0054CCDC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DAF314431A71FFDA0054CCDC /* Images.xcassets */; }; 12 | DAF314531A71FFDA0054CCDC /* AppDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAF314521A71FFDA0054CCDC /* AppDelegateTests.swift */; }; 13 | DAF3145D1A7200480054CCDC /* Gift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAF3145C1A7200480054CCDC /* Gift.framework */; }; 14 | DAF314871A7209470054CCDC /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAF314861A7209470054CCDC /* Default-568h@2x.png */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXContainerItemProxy section */ 18 | DAF3144D1A71FFDA0054CCDC /* PBXContainerItemProxy */ = { 19 | isa = PBXContainerItemProxy; 20 | containerPortal = DAF3142F1A71FFDA0054CCDC /* Project object */; 21 | proxyType = 1; 22 | remoteGlobalIDString = DAF314361A71FFDA0054CCDC; 23 | remoteInfo = ExampleApp; 24 | }; 25 | /* End PBXContainerItemProxy section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | DAF314371A71FFDA0054CCDC /* ExampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ExampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | DAF3143B1A71FFDA0054CCDC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | DAF3143C1A71FFDA0054CCDC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 31 | DAF314431A71FFDA0054CCDC /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 32 | DAF3144C1A71FFDA0054CCDC /* ExampleAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | DAF314511A71FFDA0054CCDC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | DAF314521A71FFDA0054CCDC /* AppDelegateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegateTests.swift; sourceTree = ""; }; 35 | DAF3145C1A7200480054CCDC /* Gift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Gift.framework; path = "../../../build/Debug-iphoneos/Gift.framework"; sourceTree = ""; }; 36 | DAF314861A7209470054CCDC /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | DAF314341A71FFDA0054CCDC /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | DAF3145D1A7200480054CCDC /* Gift.framework in Frameworks */, 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | DAF314491A71FFDA0054CCDC /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | DAF3142E1A71FFDA0054CCDC = { 59 | isa = PBXGroup; 60 | children = ( 61 | DAF314391A71FFDA0054CCDC /* ExampleApp */, 62 | DAF3144F1A71FFDA0054CCDC /* ExampleAppTests */, 63 | DAF314381A71FFDA0054CCDC /* Products */, 64 | ); 65 | sourceTree = ""; 66 | }; 67 | DAF314381A71FFDA0054CCDC /* Products */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | DAF314371A71FFDA0054CCDC /* ExampleApp.app */, 71 | DAF3144C1A71FFDA0054CCDC /* ExampleAppTests.xctest */, 72 | ); 73 | name = Products; 74 | sourceTree = ""; 75 | }; 76 | DAF314391A71FFDA0054CCDC /* ExampleApp */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | DAF3143C1A71FFDA0054CCDC /* AppDelegate.swift */, 80 | DAF314431A71FFDA0054CCDC /* Images.xcassets */, 81 | DAF3143A1A71FFDA0054CCDC /* Supporting Files */, 82 | ); 83 | path = ExampleApp; 84 | sourceTree = ""; 85 | }; 86 | DAF3143A1A71FFDA0054CCDC /* Supporting Files */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | DAF3143B1A71FFDA0054CCDC /* Info.plist */, 90 | ); 91 | name = "Supporting Files"; 92 | sourceTree = ""; 93 | }; 94 | DAF3144F1A71FFDA0054CCDC /* ExampleAppTests */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | DAF314521A71FFDA0054CCDC /* AppDelegateTests.swift */, 98 | DAF314501A71FFDA0054CCDC /* Supporting Files */, 99 | ); 100 | path = ExampleAppTests; 101 | sourceTree = ""; 102 | }; 103 | DAF314501A71FFDA0054CCDC /* Supporting Files */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | DAF314861A7209470054CCDC /* Default-568h@2x.png */, 107 | DAF314511A71FFDA0054CCDC /* Info.plist */, 108 | DAF3145C1A7200480054CCDC /* Gift.framework */, 109 | ); 110 | name = "Supporting Files"; 111 | sourceTree = ""; 112 | }; 113 | /* End PBXGroup section */ 114 | 115 | /* Begin PBXNativeTarget section */ 116 | DAF314361A71FFDA0054CCDC /* ExampleApp */ = { 117 | isa = PBXNativeTarget; 118 | buildConfigurationList = DAF314561A71FFDA0054CCDC /* Build configuration list for PBXNativeTarget "ExampleApp" */; 119 | buildPhases = ( 120 | DAF314331A71FFDA0054CCDC /* Sources */, 121 | DAF314341A71FFDA0054CCDC /* Frameworks */, 122 | DAF314351A71FFDA0054CCDC /* Resources */, 123 | ); 124 | buildRules = ( 125 | ); 126 | dependencies = ( 127 | ); 128 | name = ExampleApp; 129 | productName = ExampleApp; 130 | productReference = DAF314371A71FFDA0054CCDC /* ExampleApp.app */; 131 | productType = "com.apple.product-type.application"; 132 | }; 133 | DAF3144B1A71FFDA0054CCDC /* ExampleAppTests */ = { 134 | isa = PBXNativeTarget; 135 | buildConfigurationList = DAF314591A71FFDA0054CCDC /* Build configuration list for PBXNativeTarget "ExampleAppTests" */; 136 | buildPhases = ( 137 | DAF314481A71FFDA0054CCDC /* Sources */, 138 | DAF314491A71FFDA0054CCDC /* Frameworks */, 139 | DAF3144A1A71FFDA0054CCDC /* Resources */, 140 | ); 141 | buildRules = ( 142 | ); 143 | dependencies = ( 144 | DAF3144E1A71FFDA0054CCDC /* PBXTargetDependency */, 145 | ); 146 | name = ExampleAppTests; 147 | productName = ExampleAppTests; 148 | productReference = DAF3144C1A71FFDA0054CCDC /* ExampleAppTests.xctest */; 149 | productType = "com.apple.product-type.bundle.unit-test"; 150 | }; 151 | /* End PBXNativeTarget section */ 152 | 153 | /* Begin PBXProject section */ 154 | DAF3142F1A71FFDA0054CCDC /* Project object */ = { 155 | isa = PBXProject; 156 | attributes = { 157 | LastUpgradeCheck = 0620; 158 | ORGANIZATIONNAME = "Gift Contributors"; 159 | TargetAttributes = { 160 | DAF314361A71FFDA0054CCDC = { 161 | CreatedOnToolsVersion = 6.2; 162 | }; 163 | DAF3144B1A71FFDA0054CCDC = { 164 | CreatedOnToolsVersion = 6.2; 165 | TestTargetID = DAF314361A71FFDA0054CCDC; 166 | }; 167 | }; 168 | }; 169 | buildConfigurationList = DAF314321A71FFDA0054CCDC /* Build configuration list for PBXProject "ExampleApp" */; 170 | compatibilityVersion = "Xcode 3.2"; 171 | developmentRegion = English; 172 | hasScannedForEncodings = 0; 173 | knownRegions = ( 174 | en, 175 | Base, 176 | ); 177 | mainGroup = DAF3142E1A71FFDA0054CCDC; 178 | productRefGroup = DAF314381A71FFDA0054CCDC /* Products */; 179 | projectDirPath = ""; 180 | projectRoot = ""; 181 | targets = ( 182 | DAF314361A71FFDA0054CCDC /* ExampleApp */, 183 | DAF3144B1A71FFDA0054CCDC /* ExampleAppTests */, 184 | ); 185 | }; 186 | /* End PBXProject section */ 187 | 188 | /* Begin PBXResourcesBuildPhase section */ 189 | DAF314351A71FFDA0054CCDC /* Resources */ = { 190 | isa = PBXResourcesBuildPhase; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | DAF314441A71FFDA0054CCDC /* Images.xcassets in Resources */, 194 | DAF314871A7209470054CCDC /* Default-568h@2x.png in Resources */, 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | DAF3144A1A71FFDA0054CCDC /* Resources */ = { 199 | isa = PBXResourcesBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | ); 203 | runOnlyForDeploymentPostprocessing = 0; 204 | }; 205 | /* End PBXResourcesBuildPhase section */ 206 | 207 | /* Begin PBXSourcesBuildPhase section */ 208 | DAF314331A71FFDA0054CCDC /* Sources */ = { 209 | isa = PBXSourcesBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | DAF3143D1A71FFDA0054CCDC /* AppDelegate.swift in Sources */, 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | DAF314481A71FFDA0054CCDC /* Sources */ = { 217 | isa = PBXSourcesBuildPhase; 218 | buildActionMask = 2147483647; 219 | files = ( 220 | DAF314531A71FFDA0054CCDC /* AppDelegateTests.swift in Sources */, 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | }; 224 | /* End PBXSourcesBuildPhase section */ 225 | 226 | /* Begin PBXTargetDependency section */ 227 | DAF3144E1A71FFDA0054CCDC /* PBXTargetDependency */ = { 228 | isa = PBXTargetDependency; 229 | target = DAF314361A71FFDA0054CCDC /* ExampleApp */; 230 | targetProxy = DAF3144D1A71FFDA0054CCDC /* PBXContainerItemProxy */; 231 | }; 232 | /* End PBXTargetDependency section */ 233 | 234 | /* Begin XCBuildConfiguration section */ 235 | DAF314541A71FFDA0054CCDC /* Debug */ = { 236 | isa = XCBuildConfiguration; 237 | buildSettings = { 238 | ALWAYS_SEARCH_USER_PATHS = NO; 239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 240 | CLANG_CXX_LIBRARY = "libc++"; 241 | CLANG_ENABLE_MODULES = YES; 242 | CLANG_ENABLE_OBJC_ARC = YES; 243 | CLANG_WARN_BOOL_CONVERSION = YES; 244 | CLANG_WARN_CONSTANT_CONVERSION = YES; 245 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 246 | CLANG_WARN_EMPTY_BODY = YES; 247 | CLANG_WARN_ENUM_CONVERSION = YES; 248 | CLANG_WARN_INT_CONVERSION = YES; 249 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 250 | CLANG_WARN_UNREACHABLE_CODE = YES; 251 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 252 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 253 | COPY_PHASE_STRIP = NO; 254 | ENABLE_STRICT_OBJC_MSGSEND = YES; 255 | GCC_C_LANGUAGE_STANDARD = gnu99; 256 | GCC_DYNAMIC_NO_PIC = NO; 257 | GCC_OPTIMIZATION_LEVEL = 0; 258 | GCC_PREPROCESSOR_DEFINITIONS = ( 259 | "DEBUG=1", 260 | "$(inherited)", 261 | ); 262 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 263 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 264 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 265 | GCC_WARN_UNDECLARED_SELECTOR = YES; 266 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 267 | GCC_WARN_UNUSED_FUNCTION = YES; 268 | GCC_WARN_UNUSED_VARIABLE = YES; 269 | IPHONEOS_DEPLOYMENT_TARGET = 8.2; 270 | MTL_ENABLE_DEBUG_INFO = YES; 271 | ONLY_ACTIVE_ARCH = YES; 272 | SDKROOT = iphoneos; 273 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 274 | TARGETED_DEVICE_FAMILY = "1,2"; 275 | }; 276 | name = Debug; 277 | }; 278 | DAF314551A71FFDA0054CCDC /* Release */ = { 279 | isa = XCBuildConfiguration; 280 | buildSettings = { 281 | ALWAYS_SEARCH_USER_PATHS = NO; 282 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 283 | CLANG_CXX_LIBRARY = "libc++"; 284 | CLANG_ENABLE_MODULES = YES; 285 | CLANG_ENABLE_OBJC_ARC = YES; 286 | CLANG_WARN_BOOL_CONVERSION = YES; 287 | CLANG_WARN_CONSTANT_CONVERSION = YES; 288 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 289 | CLANG_WARN_EMPTY_BODY = YES; 290 | CLANG_WARN_ENUM_CONVERSION = YES; 291 | CLANG_WARN_INT_CONVERSION = YES; 292 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 293 | CLANG_WARN_UNREACHABLE_CODE = YES; 294 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 295 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 296 | COPY_PHASE_STRIP = YES; 297 | ENABLE_NS_ASSERTIONS = NO; 298 | ENABLE_STRICT_OBJC_MSGSEND = YES; 299 | GCC_C_LANGUAGE_STANDARD = gnu99; 300 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 301 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 302 | GCC_WARN_UNDECLARED_SELECTOR = YES; 303 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 304 | GCC_WARN_UNUSED_FUNCTION = YES; 305 | GCC_WARN_UNUSED_VARIABLE = YES; 306 | IPHONEOS_DEPLOYMENT_TARGET = 8.2; 307 | MTL_ENABLE_DEBUG_INFO = NO; 308 | SDKROOT = iphoneos; 309 | TARGETED_DEVICE_FAMILY = "1,2"; 310 | VALIDATE_PRODUCT = YES; 311 | }; 312 | name = Release; 313 | }; 314 | DAF314571A71FFDA0054CCDC /* Debug */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 318 | DEFINES_MODULE = YES; 319 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 320 | HEADER_SEARCH_PATHS = ( 321 | "$(inherited)", 322 | "$(SOURCE_ROOT)/../../External/libgit2/include", 323 | ); 324 | INFOPLIST_FILE = ExampleApp/Info.plist; 325 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 326 | PRODUCT_NAME = "$(TARGET_NAME)"; 327 | }; 328 | name = Debug; 329 | }; 330 | DAF314581A71FFDA0054CCDC /* Release */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 334 | DEFINES_MODULE = YES; 335 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 336 | HEADER_SEARCH_PATHS = ( 337 | "$(inherited)", 338 | "$(SOURCE_ROOT)/../../External/libgit2/include", 339 | ); 340 | INFOPLIST_FILE = ExampleApp/Info.plist; 341 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 342 | PRODUCT_NAME = "$(TARGET_NAME)"; 343 | }; 344 | name = Release; 345 | }; 346 | DAF3145A1A71FFDA0054CCDC /* Debug */ = { 347 | isa = XCBuildConfiguration; 348 | buildSettings = { 349 | BUNDLE_LOADER = "$(TEST_HOST)"; 350 | FRAMEWORK_SEARCH_PATHS = ( 351 | "$(SDKROOT)/Developer/Library/Frameworks", 352 | "$(inherited)", 353 | ); 354 | GCC_PREPROCESSOR_DEFINITIONS = ( 355 | "DEBUG=1", 356 | "$(inherited)", 357 | ); 358 | INFOPLIST_FILE = ExampleAppTests/Info.plist; 359 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 360 | PRODUCT_NAME = "$(TARGET_NAME)"; 361 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ExampleApp.app/ExampleApp"; 362 | }; 363 | name = Debug; 364 | }; 365 | DAF3145B1A71FFDA0054CCDC /* Release */ = { 366 | isa = XCBuildConfiguration; 367 | buildSettings = { 368 | BUNDLE_LOADER = "$(TEST_HOST)"; 369 | FRAMEWORK_SEARCH_PATHS = ( 370 | "$(SDKROOT)/Developer/Library/Frameworks", 371 | "$(inherited)", 372 | ); 373 | INFOPLIST_FILE = ExampleAppTests/Info.plist; 374 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 375 | PRODUCT_NAME = "$(TARGET_NAME)"; 376 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ExampleApp.app/ExampleApp"; 377 | }; 378 | name = Release; 379 | }; 380 | /* End XCBuildConfiguration section */ 381 | 382 | /* Begin XCConfigurationList section */ 383 | DAF314321A71FFDA0054CCDC /* Build configuration list for PBXProject "ExampleApp" */ = { 384 | isa = XCConfigurationList; 385 | buildConfigurations = ( 386 | DAF314541A71FFDA0054CCDC /* Debug */, 387 | DAF314551A71FFDA0054CCDC /* Release */, 388 | ); 389 | defaultConfigurationIsVisible = 0; 390 | defaultConfigurationName = Release; 391 | }; 392 | DAF314561A71FFDA0054CCDC /* Build configuration list for PBXNativeTarget "ExampleApp" */ = { 393 | isa = XCConfigurationList; 394 | buildConfigurations = ( 395 | DAF314571A71FFDA0054CCDC /* Debug */, 396 | DAF314581A71FFDA0054CCDC /* Release */, 397 | ); 398 | defaultConfigurationIsVisible = 0; 399 | }; 400 | DAF314591A71FFDA0054CCDC /* Build configuration list for PBXNativeTarget "ExampleAppTests" */ = { 401 | isa = XCConfigurationList; 402 | buildConfigurations = ( 403 | DAF3145A1A71FFDA0054CCDC /* Debug */, 404 | DAF3145B1A71FFDA0054CCDC /* Release */, 405 | ); 406 | defaultConfigurationIsVisible = 0; 407 | }; 408 | /* End XCConfigurationList section */ 409 | }; 410 | rootObject = DAF3142F1A71FFDA0054CCDC /* Project object */; 411 | } 412 | -------------------------------------------------------------------------------- /Examples/ExampleApp/ExampleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/ExampleApp/ExampleApp.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Examples/ExampleApp/ExampleApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Gift 3 | import LlamaKit 4 | 5 | @UIApplicationMain 6 | public class AppDelegate: UIResponder, UIApplicationDelegate { 7 | var window: UIWindow! 8 | 9 | public func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 10 | // Create a window and set its root view controller. 11 | window = UIWindow(frame: UIScreen.mainScreen().bounds) 12 | window.makeKeyAndVisible() 13 | 14 | let rootViewController = UIViewController() 15 | rootViewController.view.backgroundColor = UIColor.whiteColor() 16 | window.rootViewController = rootViewController 17 | 18 | // Now the interesting part: clone the libssh2 Git repository. 19 | let documentsDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String 20 | let repo = cloneRepository( 21 | NSURL(string: "git://git.libssh2.org/libssh2.git")!, 22 | NSURL(string: documentsDirectory.stringByAppendingPathComponent("Repo"))! 23 | ) 24 | 25 | // Print the directory the Git repository was cloned to. 26 | println(repo.flatMap { $0.gitDirectoryURL }) 27 | 28 | return true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Examples/ExampleApp/ExampleApp/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Examples/ExampleApp/ExampleApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.libgit2.gift.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Examples/ExampleApp/ExampleAppTests/AppDelegateTests.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import XCTest 3 | import ExampleApp 4 | 5 | class AppDelegateTests: XCTestCase { 6 | func testApplicationDidFinishLaunchingWithOptions() { 7 | let appDelegate = AppDelegate() 8 | appDelegate.application(UIApplication.sharedApplication(), didFinishLaunchingWithOptions: nil) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Examples/ExampleApp/ExampleAppTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.libgit2.gift.$(PRODUCT_NAME:rfc1034identifier) 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 | -------------------------------------------------------------------------------- /Gift.modulemap: -------------------------------------------------------------------------------- 1 | framework module Gift { 2 | umbrella header "Gift.h" 3 | 4 | header "git2/attr.h" 5 | header "git2/blob.h" 6 | header "git2/blame.h" 7 | header "git2/branch.h" 8 | header "git2/buffer.h" 9 | header "git2/checkout.h" 10 | header "git2/cherrypick.h" 11 | header "git2/clone.h" 12 | header "git2/commit.h" 13 | header "git2/common.h" 14 | header "git2/config.h" 15 | header "git2/describe.h" 16 | header "git2/diff.h" 17 | header "git2/errors.h" 18 | header "git2/filter.h" 19 | header "git2/graph.h" 20 | header "git2/ignore.h" 21 | header "git2/index.h" 22 | header "git2/indexer.h" 23 | header "git2/merge.h" 24 | header "git2/message.h" 25 | header "git2/net.h" 26 | header "git2/notes.h" 27 | header "git2/object.h" 28 | header "git2/odb.h" 29 | header "git2/odb_backend.h" 30 | header "git2/oid.h" 31 | header "git2/pack.h" 32 | header "git2/patch.h" 33 | header "git2/pathspec.h" 34 | header "git2/push.h" 35 | header "git2/refdb.h" 36 | header "git2/reflog.h" 37 | header "git2/refs.h" 38 | header "git2/refspec.h" 39 | header "git2/remote.h" 40 | header "git2/repository.h" 41 | header "git2/reset.h" 42 | header "git2/revert.h" 43 | header "git2/revparse.h" 44 | header "git2/revwalk.h" 45 | header "git2/signature.h" 46 | header "git2/stash.h" 47 | header "git2/status.h" 48 | header "git2/submodule.h" 49 | header "git2/tag.h" 50 | header "git2/threads.h" 51 | header "git2/transport.h" 52 | header "git2/tree.h" 53 | header "git2/types.h" 54 | header "git2/version.h" 55 | header "git2/sys/commit.h" 56 | header "git2/sys/config.h" 57 | header "git2/sys/diff.h" 58 | header "git2/sys/filter.h" 59 | header "git2/sys/hashsig.h" 60 | header "git2/sys/index.h" 61 | header "git2/sys/mempack.h" 62 | header "git2/sys/odb_backend.h" 63 | header "git2/sys/refdb_backend.h" 64 | header "git2/sys/reflog.h" 65 | header "git2/sys/refs.h" 66 | header "git2/sys/repository.h" 67 | header "git2/sys/transport.h" 68 | 69 | export * 70 | module * { export * } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /Gift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Gift.xcodeproj/xcshareddata/xcschemes/Gift-OSX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /Gift.xcodeproj/xcshareddata/xcschemes/Gift-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /Gift.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Gift/Branch/BranchIterator.swift: -------------------------------------------------------------------------------- 1 | /** 2 | An iterator for branch references. 3 | */ 4 | internal class BranchIterator { 5 | internal let cBranchIterator: COpaquePointer 6 | 7 | internal init(cBranchIterator: COpaquePointer) { 8 | self.cBranchIterator = cBranchIterator 9 | } 10 | 11 | deinit { 12 | git_branch_iterator_free(cBranchIterator) 13 | } 14 | } 15 | 16 | internal extension BranchIterator { 17 | /** The libgit2 function used by the branch iterator to enumerate branches. */ 18 | internal var pointOfFailure: String { return "git_branch_next" } 19 | 20 | /** 21 | Continues to the next branch reference. 22 | 23 | :returns: A tuple containing the error code returned by the libgit2 function, 24 | an opaque pointer to the next branch reference (if one exists), and its 25 | branch type (if it exists). 26 | */ 27 | internal func next() -> (errorCode: Int32, cReference: COpaquePointer, branchType: git_branch_t) { 28 | var cReference = COpaquePointer.null() 29 | var branchType = git_branch_t(0) 30 | let errorCode = git_branch_next(&cReference, &branchType, cBranchIterator) 31 | return (Int32(errorCode), cReference, branchType) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Gift/Branch/BranchType.swift: -------------------------------------------------------------------------------- 1 | /** A classifier used to determine which branches to iterate over. */ 2 | public enum BranchType: UInt32 { 3 | /** Only iterate over local branches. */ 4 | case Local = 1 5 | /** Only iterate over remote branches. */ 6 | case Remote = 2 7 | /** Iterate over all branches, both local and remote. */ 8 | case All = 3 9 | } 10 | -------------------------------------------------------------------------------- /Gift/Commit/Commit+Branch.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | public extension Commit { 5 | /** 6 | Creates a branch that refers to this commit. 7 | 8 | :param: name The name of the new branch. This name must be unique, unless 9 | the `force` parameter is specified. 10 | :param: signature The identity that will be used to popular the reflog entry. 11 | By default, this will be recorded as "com.libgit2.Gift". 12 | :param: force If true, attempts to create branches with a name that already 13 | exists will overwrite the previous branch. 14 | :returns: The result of the operation: either a reference to the newly created 15 | branch, or an error indicating what went wrong. 16 | */ 17 | public func createBranch(name: String, signature: Signature = giftSignature, force: Bool = false) -> Result { 18 | var out = COpaquePointer.null() 19 | var cSignature = signature.cSignature 20 | let cForce: Int32 = force ? 1 : 0 21 | let errorCode = git_branch_create(&out, cRepository, name, cCommit, cForce, &cSignature, nil) 22 | if errorCode == GIT_OK.value { 23 | return success(Reference(cReference: out)) 24 | } else { 25 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_branch_create")) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Gift/Commit/Commit+Merge.swift: -------------------------------------------------------------------------------- 1 | import LlamaKit 2 | 3 | public extension Commit { 4 | /** 5 | Merges another commit into this commit to produce an index. 6 | 7 | :param: commit The commit to merge into this one. 8 | :param: options A set of options to configure merge behavior. 9 | :returns: A result that is either: the resulting index, or an error 10 | indicating what went wrong. 11 | */ 12 | public func merge(commit: Commit, options: MergeOptions = MergeOptions()) -> Result { 13 | var out = COpaquePointer.null() 14 | var cOptions = options.cOptions 15 | let errorCode = git_merge_commits(&out, cRepository, cCommit, commit.cCommit, &cOptions) 16 | if errorCode == GIT_OK.value { 17 | return success(Index(cIndex: out)) 18 | } else { 19 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_merge_commits")) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Gift/Commit/Commit+ObjectID.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | internal extension Commit { 5 | /** 6 | Looks up a commit object with the given object ID in the given repository. 7 | 8 | :param: objectID The object ID of the commit object. 9 | :param: cRepository An opaque C struct representing a repository that 10 | the commit object ID belongs to. 11 | :returns: The result of the lookup: either a commit object, or a failure 12 | indicating what went wrong. 13 | */ 14 | internal class func lookup(objectID: UnsafeMutablePointer, cRepository: COpaquePointer) -> Result { 15 | var out = COpaquePointer.null() 16 | let errorCode = git_commit_lookup(&out, cRepository, objectID) 17 | if errorCode == GIT_OK.value { 18 | return success(Commit(cCommit: out)) 19 | } else { 20 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_tag_lookup")) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Gift/Commit/Commit+Repository.swift: -------------------------------------------------------------------------------- 1 | internal extension Commit { 2 | /** 3 | Returns a pointer to the C struct representing 4 | the repository this commit belongs to. 5 | 6 | This pointer is not retained here, and so cannot 7 | be used to initialize a Repository object (which 8 | would call free on the pointer when it's deinitialized). 9 | */ 10 | internal var cRepository: COpaquePointer { 11 | return git_commit_owner(cCommit) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Gift/Commit/Commit.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | /** 5 | A commit object holds metadata for each change introduced into the 6 | repository, including the author, committer, commit date, and log message. 7 | Each commit points to a tree object that captures, in one complete snapshot, 8 | the state of the repository at the time the commit was performed. 9 | 10 | The initial commit, or root commit, has no parent. Most commits have one 11 | commit parent, although it is possible to have more than one parent. 12 | */ 13 | public class Commit { 14 | internal let cCommit: COpaquePointer 15 | 16 | internal init(cCommit: COpaquePointer) { 17 | self.cCommit = cCommit 18 | } 19 | 20 | deinit { 21 | git_commit_free(cCommit) 22 | } 23 | } 24 | 25 | public extension Commit { 26 | /** 27 | Returns the full message of a commit, or a failure indicating 28 | what went wrong when retrieving the message. The returned message 29 | will be slightly prettified by removing any potential leading newlines. 30 | */ 31 | public var message: Result { 32 | let cMessage = git_commit_message(cCommit) 33 | if let commitMessage = String.fromCString(cMessage) { 34 | return success(commitMessage) 35 | } else { 36 | let description = "An error occurred when attempting to convert commit message " 37 | + "'\(cMessage)', provided by git_commit_message, to a String." 38 | return failure(NSError.giftError(.StringConversionFailure, description: description)) 39 | } 40 | } 41 | 42 | /** 43 | Returns the author's signature for this commit, or a failure indicating what went 44 | wrong when retrieving the signature. The author of a commit is the person who authored 45 | the changes in the commit. 46 | */ 47 | public var author: Result { 48 | return Signature.fromCSignature(git_commit_author(cCommit).memory) 49 | } 50 | 51 | /** 52 | Returns the committer's signature for this commit, or a failure indicating what went 53 | wrong when retrieving the signature. The committer of a commit is the person who 54 | committed the code on behalf of the original author. 55 | */ 56 | public var committer: Result { 57 | return Signature.fromCSignature(git_commit_committer(cCommit).memory) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Gift/Commit/CommitSorting.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A set of options to specify how commits should be sorted when 3 | being enumerated. 4 | */ 5 | public struct CommitSorting : RawOptionSetType, BooleanType { 6 | private let value: UInt = 0 7 | 8 | // MARK: NilLiteralConvertible Protocol Methods 9 | 10 | public init(nilLiteral: ()) { } 11 | 12 | // MARK: _RawOptionSetType Protocol Methods 13 | 14 | public init(rawValue value: UInt) { 15 | self.value = value 16 | } 17 | 18 | // MARK: RawRepresentable Protocol Methods 19 | 20 | public var rawValue: UInt { 21 | return value 22 | } 23 | 24 | // MARK: BitwiseOperationsType Protocol Methods 25 | 26 | public static var allZeros: CommitSorting { 27 | return self(rawValue: 0) 28 | } 29 | 30 | // MARK: BooleanType Protocol Methods 31 | 32 | public var boolValue: Bool { 33 | return value != 0 34 | } 35 | 36 | // MARK: Enum Values 37 | 38 | /** 39 | Sort the repository commits in no particular order. 40 | This sorting is arbitrary, implementation-specific, and subject to 41 | change at any time. This is the default sorting. 42 | */ 43 | public static var None: CommitSorting { return self(rawValue: 0b0) } 44 | 45 | /** 46 | Sort the repository commits in topological order, with parents before 47 | their children. This sorting mode can be combined with time sorting. 48 | */ 49 | public static var Topological: CommitSorting { return self(rawValue: 0b1) } 50 | 51 | /** 52 | Sort the repository commits by the time they were committed. 53 | This sorting mode can be combined with topological sorting. 54 | */ 55 | public static var Time: CommitSorting { return self(rawValue: 0b10) } 56 | 57 | /** 58 | Iterate over the repository commits in reverse order. 59 | This sorting mode can be combined with any of the other sorting options. 60 | */ 61 | public static var Reverse: CommitSorting { return self(rawValue: 0b100) } 62 | } 63 | -------------------------------------------------------------------------------- /Gift/Commit/CommitWalker+RAC.swift: -------------------------------------------------------------------------------- 1 | import ReactiveCocoa 2 | 3 | internal extension CommitWalker { 4 | /** 5 | Walks over a series of commits, notifying the subscriber along the way. 6 | 7 | :param: subscriber A subscriber that will be notified of commits or errors 8 | as the walker iterates over commits. 9 | :param: disposable Used to cancel the walk operation. 10 | */ 11 | internal func walk(observer: Signal.Observer, disposable: CompositeDisposable) { 12 | var errorCode: Int32 13 | 14 | // Begin walking from HEAD. If this operation fails, notify the subsciber 15 | // of an error and return immediately. 16 | errorCode = git_revwalk_push_head(cWalker) 17 | if errorCode != GIT_OK.value { 18 | sendError(observer, NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_revwalk_push_head")) 19 | return 20 | } 21 | 22 | // Iterate over the commits, storing their object IDs along the way. 23 | var objectID = UnsafeMutablePointer.alloc(1) 24 | errorCode = git_revwalk_next(objectID, cWalker) 25 | while errorCode == GIT_OK.value { 26 | // For each commit, check if the signal has been disposed of. If it has, 27 | // return immediately. 28 | if disposable.disposed { 29 | objectID.dealloc(1) 30 | return 31 | } 32 | 33 | // Lookup the commit with the object ID provided by the walker. 34 | // If the lookup succeeds, notify the subscriber. Otherwise, 35 | // let the subscriber know there was an error and halt the walk. 36 | let commit = Commit.lookup(objectID, cRepository: cRepository) 37 | switch commit { 38 | case .Success(let boxedCommit): 39 | sendNext(observer, boxedCommit.unbox) 40 | case .Failure(let boxedError): 41 | sendError(observer, boxedError.unbox) 42 | objectID.dealloc(1) 43 | return 44 | } 45 | 46 | // Continue walking. 47 | errorCode = git_revwalk_next(objectID, cWalker) 48 | } 49 | 50 | // Iteration completion means one of two things: 51 | objectID.dealloc(1) 52 | if errorCode == GIT_ITEROVER.value { 53 | // 1. There are no more commits to iterate over, i.e.: GIT_ITEROVER. 54 | // If that's the case, notify the subsciber that the signal has completed. 55 | sendCompleted(observer) 56 | } else { 57 | // 2. An error occurred while iterating. If that's the case, notify the 58 | // subscriber of the error. 59 | sendError(observer, NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_revwalk_next")) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Gift/Commit/CommitWalker.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Walkers are used internally to iterate over a list of commits. 3 | */ 4 | internal class CommitWalker { 5 | internal let cWalker: COpaquePointer 6 | internal let cRepository: COpaquePointer 7 | 8 | internal init(cWalker: COpaquePointer, cRepository: COpaquePointer, sorting: CommitSorting) { 9 | self.cWalker = cWalker 10 | self.cRepository = cRepository 11 | git_revwalk_sorting(cWalker, UInt32(sorting.rawValue)) 12 | } 13 | 14 | deinit { 15 | git_revwalk_free(cWalker) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Gift/Commit/Options/MergeOptions+C.swift: -------------------------------------------------------------------------------- 1 | extension MergeOptions { 2 | /** 3 | Returns a C struct initialized with this options instance's values. 4 | Used by libgit2 functions. 5 | */ 6 | internal var cOptions: git_merge_options { 7 | return git_merge_options( 8 | version: 1, 9 | flags: git_merge_tree_flag_t(GIT_MERGE_TREE_FIND_RENAMES.value), 10 | rename_threshold: renameThreshold, 11 | target_limit: renameTargetLimit, 12 | metric: nil, 13 | file_favor: git_merge_file_favor_t(strategy.rawValue) 14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Gift/Commit/Options/MergeOptions.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A collection of options to configure the behavior 3 | of a merge. 4 | */ 5 | public struct MergeOptions { 6 | /** 7 | Similarity to consider a file renamed (default 50). Added files 8 | will be compared with deleted files to determine their similarity. 9 | Files that are more similar than the rename threshold 10 | (percentage-wise) will be treated as a rename. 11 | */ 12 | internal let renameThreshold: UInt32 13 | /** 14 | Maximum similarity sources to examine for renames (default 200). 15 | If the number of rename candidates (add/delete pairs) is greater 16 | than this value, inexact rename detection is aborted. 17 | 18 | This setting overrides the `merge.renameLimit` configuration value. 19 | */ 20 | internal let renameTargetLimit: UInt32 21 | 22 | /** A strategy to determine how to handle conflicts during a merge. */ 23 | internal let strategy: MergeStrategy 24 | 25 | public init( 26 | renameThreshold: UInt32 = 50, 27 | renameTargetLimit: UInt32 = 200, 28 | strategy: MergeStrategy = MergeStrategy.Normal 29 | ) { 30 | self.renameThreshold = renameThreshold 31 | self.renameTargetLimit = renameTargetLimit 32 | self.strategy = strategy 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Gift/Commit/Options/MergeStrategy.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Strategies to determine how to handle conflicts during a merge. 3 | */ 4 | public enum MergeStrategy: UInt32 { 5 | /** 6 | When a region of a file is changed in both branches, a conflict 7 | will be recorded in the index so that `git_checkout` can produce 8 | a merge file with conflict markers in the working directory. 9 | This is the default. 10 | */ 11 | case Normal = 0 12 | /** 13 | When a region of a file is changed in both branches, the file 14 | created in the index will contain the "ours" side of any conflicting 15 | region. The index will not record a conflict. 16 | */ 17 | case Ours = 1 18 | /** 19 | When a region of a file is changed in both branches, the file 20 | created in the index will contain the "theirs" side of any conflicting 21 | region. The index will not record a conflict. 22 | */ 23 | case Theirs = 2 24 | /** 25 | When a region of a file is changed in both branches, the file 26 | created in the index will contain each unique line from each side, 27 | which has the result of combining both files. The index will not 28 | record a conflict. 29 | */ 30 | case Union = 3 31 | } 32 | -------------------------------------------------------------------------------- /Gift/Error/Domain.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Reserved for errors that occurred from within libgit2 function calls. 3 | */ 4 | public let libGit2ErrorDomain = "com.libgit2" 5 | 6 | /** 7 | Reserved for errors that occurred from within Gift function calls, 8 | either because of an unexpected code path, or because of user error. 9 | */ 10 | public let giftErrorDomain = "com.libgit2.gift" 11 | 12 | /** 13 | Error codes for errors that occurred from within Gift function calls. 14 | */ 15 | public enum GiftErrorCode: Int { 16 | /** 17 | An error occurred when attempting to parse an NSURL object. 18 | */ 19 | case InvalidURI = 1 20 | 21 | /** 22 | An error occurred when attempting to convert a C string to a Swift String. 23 | */ 24 | case StringConversionFailure = 2 25 | 26 | /** 27 | An error occurred when attempting to convert a libgit2 enum raw value to 28 | a Gift enum value. 29 | */ 30 | case EnumConversionFailure = 3 31 | 32 | /** 33 | An error occurred when attempting to pass a Swift closure through to a 34 | C callback function used by libgit2. 35 | */ 36 | case CFunctionCallbackConversionFailure = 4 37 | } 38 | -------------------------------------------------------------------------------- /Gift/Error/Error.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Returns the libgit2 error message for the given error code. 3 | 4 | The error message represents the last error message generated by 5 | libgit2 in the current thread. 6 | 7 | :param: errorCode An error code returned by a libgit2 function. 8 | :returns: If the error message exists either in libgit2's thread-specific registry, 9 | or errno has been set by the system, this function returns the 10 | corresponding string representation of that error. Otherwise, it returns 11 | nil. 12 | */ 13 | internal func errorMessage(errorCode: Int32) -> String? { 14 | let last = giterr_last() 15 | if last != nil { 16 | return String.fromCString(last.memory.message) 17 | } else if UInt32(errorCode) == GITERR_OS.value { 18 | return String.fromCString(strerror(errno)) 19 | } else { 20 | return nil 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Gift/Error/NSError+Domain.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal extension NSError { 4 | /** 5 | Returns an NSError with an error domain and message for libgit2 errors. 6 | 7 | :param: errorCode An error code returned by a libgit2 function. 8 | :param: libGit2PointOfFailure The name of the libgit2 function that produced the 9 | error code. 10 | :returns: An NSError with a libgit2 error domain, code, and message. 11 | */ 12 | internal class func libGit2Error(errorCode: Int32, libGit2PointOfFailure: String? = nil) -> NSError { 13 | let code = Int(errorCode) 14 | var userInfo: [String: String] = [:] 15 | 16 | if let message = errorMessage(errorCode) { 17 | userInfo[NSLocalizedDescriptionKey] = message 18 | } else { 19 | userInfo[NSLocalizedDescriptionKey] = "Unknown libgit2 error." 20 | } 21 | 22 | if let pointOfFailure = libGit2PointOfFailure { 23 | userInfo[NSLocalizedFailureReasonErrorKey] = "\(pointOfFailure) failed." 24 | } 25 | 26 | return NSError(domain: libGit2ErrorDomain, code: code, userInfo: userInfo) 27 | } 28 | 29 | /** 30 | Returns an NSError with an error domain and message for Gift errors. 31 | 32 | :param: errorCode An error code corresponding to the type of failure that occurred. 33 | :param: description A localized description for the error. 34 | :returns: An NSError with a Gift error domain, plus the given error code and description. 35 | */ 36 | internal class func giftError(errorCode: GiftErrorCode, description: String) -> NSError { 37 | return NSError(domain: giftErrorDomain, code: errorCode.rawValue, userInfo: [NSLocalizedDescriptionKey: description]) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Gift/Gift.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | //! Project version number for Gift. 4 | FOUNDATION_EXPORT double GiftVersionNumber; 5 | 6 | //! Project version string for Gift. 7 | FOUNDATION_EXPORT const unsigned char GiftVersionString[]; 8 | 9 | #import 10 | #import 11 | #import 12 | -------------------------------------------------------------------------------- /Gift/Gift.m: -------------------------------------------------------------------------------- 1 | #include "git2/global.h" 2 | 3 | /** 4 | libgit2 uses global state for error handling, among other things. 5 | Execute this function to initialize libgit2 when Gift is loaded by 6 | utilizing the `constructor` attribute. 7 | */ 8 | __attribute__((constructor)) 9 | static void GiftInitializeLibGit2(void) { 10 | git_libgit2_init(); 11 | } 12 | -------------------------------------------------------------------------------- /Gift/Index/Index+Repository.swift: -------------------------------------------------------------------------------- 1 | internal extension Index { 2 | /** 3 | Returns a pointer to the C struct representing 4 | the repository this index belongs to. 5 | 6 | This pointer is not retained here, and so cannot 7 | be used to initialize a Repository object (which 8 | would call free on the pointer when it's deinitialized). 9 | */ 10 | internal var cRepository: COpaquePointer { 11 | return git_index_owner(cIndex) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Gift/Index/Index+Tree.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | public extension Index { 5 | /** 6 | Writes the a representation of the index as a tree to disk. 7 | This method returns a tree object that can be used to, for example, 8 | create a commit. 9 | 10 | The index must not be bare, has to be associated with an existing 11 | repository, and must notcontain files in conflict. 12 | 13 | :returns: The result of the write operation: either the written tree 14 | object, or a failure indicating what went wrong. 15 | */ 16 | public func writeTree() -> Result { 17 | var out = UnsafeMutablePointer.alloc(1) 18 | let errorCode = git_index_write_tree(out, cIndex) 19 | if errorCode == GIT_OK.value { 20 | let tree = Tree.lookup(out, cRepository: cRepository) 21 | out.dealloc(1) 22 | return tree 23 | } else { 24 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_index_write_tree")) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Gift/Index/Index.swift: -------------------------------------------------------------------------------- 1 | import LlamaKit 2 | 3 | /** 4 | A data structure representing an index, also referred to as 5 | a "staging area" for changes. The index is a temporary and 6 | dynamic binary file that describes the directory structure 7 | of the entire repository. The index captures a version of the 8 | project’s overall structure at some moment in time. 9 | 10 | The index allows a separation between incremental developmental 11 | changes and the committal of those changes. Changes are "staged" 12 | in an index, keeping those changes until they are ready to be 13 | committed. 14 | */ 15 | public class Index { 16 | internal let cIndex: COpaquePointer 17 | 18 | internal init(cIndex: COpaquePointer) { 19 | self.cIndex = cIndex 20 | } 21 | 22 | deinit { 23 | git_index_free(cIndex) 24 | } 25 | } 26 | 27 | public extension Index { 28 | /** 29 | Returns the number of entries in the index. 30 | */ 31 | public var entryCount: UInt { 32 | return git_index_entrycount(cIndex) 33 | } 34 | 35 | /** 36 | Adds the files at the specified paths to the index, using the given options. 37 | 38 | :param: paths Paths, or patterns used to match paths, of the files to be 39 | added to the index. If no patterns are specified, all files 40 | are added to the index. 41 | :param: options A set of options for adding files to the index. 42 | :returns: The result of the operation: either the index to which files have been 43 | added, or an error indicating what went wrong. 44 | */ 45 | public func add(paths: [String] = [], options: IndexAddOptions = IndexAddOptions.Default) -> Result { 46 | let count = countElements(paths) 47 | var cPaths = UnsafeMutablePointer>.alloc(count) 48 | for i in 0.. 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | io.gift.$(PRODUCT_NAME:rfc1034identifier) 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 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Gift/Object/Object+ObjectID.swift: -------------------------------------------------------------------------------- 1 | internal extension Object { 2 | /** 3 | Returns the object ID of the object. 4 | */ 5 | internal var objectID: UnsafeMutablePointer { 6 | return UnsafeMutablePointer(git_object_id(cObject)) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Gift/Object/Object.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A Git object can represent one of four types of data: 3 | blobs (binary large objects), trees, commits, and annotated 4 | tags. Git stores these objects in its object database, also 5 | known as the object store. 6 | */ 7 | internal class Object { 8 | internal let cObject: COpaquePointer 9 | 10 | internal init(cObject: COpaquePointer) { 11 | self.cObject = cObject 12 | } 13 | 14 | deinit { 15 | git_object_free(cObject) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Gift/Object/ObjectID.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | /** 5 | Determine the object ID of the given data via SHA1 hashing. 6 | The object ID returned would be the ID used if the data were 7 | written to the object store. 8 | 9 | The object ID returned by this function must be deallocated, 10 | otherwise it is never freed. 11 | 12 | :param: data The data for which an object ID is to be generated. 13 | :param: type The type of the object to be written. 14 | :returns: The result of the hashing operation: either the object ID that would 15 | be used to store the data to the object store, or a failure indicating 16 | what went wrong. 17 | */ 18 | internal func allocateObjectID(data: NSData, type: ObjectType) -> Result, NSError> { 19 | var out = UnsafeMutablePointer.alloc(1) 20 | let errorCode = git_odb_hash(out, data.bytes, UInt(data.length), git_otype(type.rawValue)) 21 | if errorCode == GIT_OK.value { 22 | return success(out) 23 | } else { 24 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_odb_hash")) 25 | } 26 | } 27 | 28 | /** 29 | Determine the object ID of the data at the given file URL via SHA1 30 | hashing. The object ID returned would be the ID used if the data were 31 | written to the object store. 32 | 33 | The object ID returned by this function must be deallocated, 34 | otherwise it is never freed. 35 | 36 | :param: fileURL The URL of the file. 37 | :param: type The type of the object to be written. 38 | :returns: The result of the hashing operation: either the object ID that would 39 | be used to store the data to the object store, or a failure indicating 40 | what went wrong. 41 | */ 42 | internal func allocateObjectID(fileURL: NSURL, type: ObjectType) -> Result, NSError> { 43 | if let path = fileURL.path { 44 | var out = UnsafeMutablePointer.alloc(1) 45 | let errorCode = git_odb_hashfile(out, path, git_otype(type.rawValue)) 46 | if errorCode == GIT_OK.value { 47 | return success(out) 48 | } else { 49 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_odb_hashfile")) 50 | } 51 | } else { 52 | return failure(NSError.giftError(.InvalidURI, description: "Invalid fileURL: '\(fileURL)'")) 53 | } 54 | } 55 | 56 | /** 57 | Converts a 40-digit hexadecimal string representation of an object ID 58 | (a SHA1 hash of the object's contents) to an object ID. 59 | 60 | The object ID returned by this function must be deallocated, 61 | otherwise it is never freed. 62 | 63 | :param: SHA A string representation of an object ID. 64 | :returns: The result of the conversion: either a pointer to an objectID, or a failure 65 | indicating why the SHA string could not be converted. 66 | */ 67 | internal func allocateObjectID(SHA: String) -> Result, NSError> { 68 | var out = UnsafeMutablePointer.alloc(1) 69 | let errorCode = git_oid_fromstr(out, SHA) 70 | if errorCode == GIT_OK.value { 71 | return success(out) 72 | } else { 73 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_oid_fromstr")) 74 | } 75 | } 76 | 77 | /** 78 | Converts an object ID (a SHA1 hash of the object's contents) to a 40-digit 79 | hexadecimal string representation of an object ID. 80 | 81 | :param: objectID The object ID to convert. 82 | :returns: The result of the conversion: either a string representation of the objectID, 83 | or a failure indicating why the objectID could not be converted. 84 | */ 85 | internal func objectIDSHA(objectID: UnsafeMutablePointer) -> Result { 86 | let cString = git_oid_tostr_s(objectID) 87 | if let sha = String.fromCString(cString) { 88 | return success(sha) 89 | } else { 90 | let description = "An error occurred when attempting to convert SHA '\(cString)'" 91 | + "provided by git_oid_tostr_s to a String." 92 | return failure(NSError.giftError(.StringConversionFailure, description: description)) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Gift/Object/ObjectType.swift: -------------------------------------------------------------------------------- 1 | /** A classifier for the underlying type of a Git object. */ 2 | internal enum ObjectType: Int32 { 3 | /** An object that could be any of the other enumerated types. */ 4 | case Any = -2 5 | /** An invalid object. */ 6 | case Bad = -1 7 | /** A commit object. */ 8 | case Commit = 1 9 | /** A tree or directory listing object. */ 10 | case Tree = 2 11 | /** A file revision object. */ 12 | case Blob = 3 13 | /** An annotated tag object. */ 14 | case Tag = 4 15 | /** A delta where the base is given by an offset. */ 16 | case DeltaByOffset = 6 17 | /** A delta where the base is given by an object ID. */ 18 | case DeltaByObjectID = 7 19 | } 20 | -------------------------------------------------------------------------------- /Gift/Reference/Reference+Commit.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | public extension Reference { 5 | /** 6 | Returns the commit this reference is referring to, 7 | or a failure indicating what went wrong when attempting to 8 | retrieve that commit. 9 | */ 10 | public var commit: Result { 11 | var out = COpaquePointer.null() 12 | let errorCode = git_reference_peel(&out, cReference, git_otype(ObjectType.Commit.rawValue)) 13 | if errorCode == GIT_OK.value { 14 | return success(Commit(cCommit: out)) 15 | } else { 16 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_reference_peel")) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Gift/Reference/Reference+Object.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | internal extension Reference { 5 | /** 6 | Returns the underlying object referred to by this reference, 7 | or a failure indicating what went wrong when looking up that 8 | object. 9 | */ 10 | internal var object: Result { 11 | var out = COpaquePointer.null() 12 | let errorCode = git_reference_peel(&out, cReference, git_otype(ObjectType.Any.rawValue)) 13 | if errorCode == GIT_OK.value { 14 | return success(Object(cObject: out)) 15 | } else { 16 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_reference_peel")) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Gift/Reference/Reference+ObjectID.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | internal extension Reference { 5 | /** 6 | Looks up a reference object with the given object ID in the given repository. 7 | 8 | :param: name The name of the reference object. 9 | :param: cRepository An opaque C struct representing a repository that 10 | the reference belongs to. 11 | :returns: The result of the lookup: either a reference object, or a failure 12 | indicating what went wrong. 13 | */ 14 | internal class func lookup(name: String, cRepository: COpaquePointer) -> Result { 15 | var out = COpaquePointer.null() 16 | let errorCode = git_reference_lookup(&out, cRepository, name) 17 | if errorCode == GIT_OK.value { 18 | return success(Reference(cReference: out)) 19 | } else { 20 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_reference_lookup")) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Gift/Reference/Reference+Repository.swift: -------------------------------------------------------------------------------- 1 | internal extension Reference { 2 | /** 3 | Returns a pointer to the C struct representing 4 | the repository this reference belongs to. 5 | 6 | This pointer is not retained here, and so cannot 7 | be used to initialize a Repository object (which 8 | would call free on the pointer when it's deinitialized). 9 | */ 10 | internal var cRepository: COpaquePointer { 11 | return git_reference_owner(cReference) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Gift/Reference/Reference+Tag.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | public extension Reference { 5 | /** 6 | Creates an annotated tag with the given name, message, 7 | and signature. If the tag is successfully created, a 8 | reference to that tag is returned. Otherwise, a failure 9 | indicating what went wrong is returned. 10 | 11 | :param: name The name of the tag to be created. 12 | :param: message The tag message. 13 | :param: signature The author, email, and timestamp of the tag to be created. 14 | :param: force Whether to overwrite any existing tags with the same name. 15 | :returns: Either a tag, or a failure message indicating why the tag couldn't be created. 16 | */ 17 | public func tag(name: String, message: String, signature: Signature, force: Bool = false) -> Result { 18 | return self.object.flatMap { (referenceObject: Object) in 19 | var out = UnsafeMutablePointer.alloc(1) 20 | var cSignature = signature.cSignature 21 | let errorCode = git_tag_create( 22 | out, self.cRepository, name, referenceObject.cObject, &cSignature, message, force ? 1 : 0) 23 | 24 | if errorCode == GIT_OK.value { 25 | let tag = Tag.lookup(out, cRepository: self.cRepository) 26 | out.dealloc(1) 27 | return tag 28 | } else { 29 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_tag_create")) 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Gift/Reference/Reference.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | /** 5 | References point to an underlying Git object, but have more 6 | semantically meaningful names. A branch, for example, is a named 7 | reference pointing to the latest commit in a series of work. 8 | */ 9 | public class Reference { 10 | internal let cReference: COpaquePointer 11 | 12 | internal init(cReference: COpaquePointer) { 13 | self.cReference = cReference 14 | } 15 | 16 | deinit { 17 | git_reference_free(cReference) 18 | } 19 | } 20 | 21 | public extension Reference { 22 | /** 23 | The name of the reference. 24 | */ 25 | public var name: Result { 26 | let referenceName = git_reference_name(cReference) 27 | if let name = String.fromCString(referenceName) { 28 | return success(name) 29 | } else { 30 | let description = "An error occurred when attempting to convert reference name \(referenceName) " 31 | + "provided by git_reference_name to a String." 32 | return failure(NSError.giftError(.StringConversionFailure, description: description)) 33 | } 34 | } 35 | 36 | /** 37 | Returns a 40-digit hexadecimal number string representation of the SHA1 of the 38 | reference. The SHA1 is a unique name obtained by applying the SHA1 hashing 39 | algorithm to the contents of the reference. This is a string representation of the 40 | object ID. 41 | */ 42 | public var SHA: Result { 43 | return object.flatMap { objectIDSHA($0.objectID) } 44 | } 45 | 46 | /** 47 | Whether or not the reference is to a remote tracking branch. 48 | */ 49 | public var isRemote: Bool { 50 | return git_reference_is_remote(cReference) != 0 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Gift/Remote/Remote.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | /** 5 | A data structure representing a remote repository. 6 | This is used to push and pull changes to and from a remote repository. 7 | */ 8 | public final class Remote { 9 | internal let cRemote: COpaquePointer 10 | 11 | internal init(cRemote: COpaquePointer) { 12 | self.cRemote = cRemote 13 | } 14 | 15 | deinit { 16 | git_remote_free(cRemote) 17 | } 18 | } 19 | 20 | public extension Remote { 21 | /** 22 | Returns the name of the remote, or an error indicating what went wrong 23 | when retrieving the name. 24 | */ 25 | public var name: Result { 26 | let cRemoteName = git_remote_name(cRemote) 27 | if let remoteName = String.fromCString(cRemoteName) { 28 | return success(remoteName) 29 | } else { 30 | let description = "An error occurred when attempting to convert remote name " 31 | + "'\(cRemoteName)', provided by git_remote_name, to a String." 32 | return failure(NSError.giftError(.StringConversionFailure, description: description)) 33 | } 34 | } 35 | 36 | /** 37 | Returns the URL of the remote, or an error indicating what went wrong 38 | when retrieving the URL. 39 | */ 40 | public var url: Result { 41 | let cRemoteURLString = git_remote_url(cRemote) 42 | if let remoteURLString = String.fromCString(cRemoteURLString) { 43 | if let remoteURL = NSURL(string: remoteURLString) { 44 | return success(remoteURL) 45 | } else { 46 | return failure(NSError.giftError(.InvalidURI, description: "Invalid URI: \(remoteURLString)")) 47 | } 48 | } else { 49 | let description = "An error occurred when attempting to convert remote URL " 50 | + "'\(cRemoteURLString)', provided by git_remote_url, to a String." 51 | return failure(NSError.giftError(.StringConversionFailure, description: description)) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Gift/Repository/Options/Checkout/CheckoutOptions+C.swift: -------------------------------------------------------------------------------- 1 | internal extension CheckoutOptions { 2 | /** 3 | Returns a C struct initialized with this options instance's values. 4 | Used by libgit2 functions. 5 | */ 6 | internal var cOptions: git_checkout_options { 7 | return gift_checkoutOptions(UInt32(strategy.rawValue), progressCallback) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Gift/Repository/Options/Checkout/CheckoutOptions.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A set of options used to perform a checkout. 3 | The designated initializer provides a set of reasonable defaults. 4 | */ 5 | public struct CheckoutOptions { 6 | internal let strategy: CheckoutStrategy 7 | internal let progressCallback: GIFTCheckoutProgressCallback! 8 | 9 | public init( 10 | strategy: CheckoutStrategy, 11 | progressCallback: GIFTCheckoutProgressCallback! = nil 12 | ) { 13 | self.strategy = strategy 14 | self.progressCallback = progressCallback 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Gift/Repository/Options/Checkout/CheckoutStrategy.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A set of flags indicating how a checkout should be performed. 3 | 4 | These values map directly to those enumerated in the libgit2 enum git_checkout_strategy_t. 5 | */ 6 | public struct CheckoutStrategy : RawOptionSetType, BooleanType { 7 | private let value: UInt = 0 8 | 9 | // MARK: NilLiteralConvertible Protocol Methods 10 | 11 | public init(nilLiteral: ()) { } 12 | 13 | // MARK: _RawOptionSetType Protocol Methods 14 | 15 | public init(rawValue value: UInt) { 16 | self.value = value 17 | } 18 | 19 | // MARK: RawRepresentable Protocol Methods 20 | 21 | public var rawValue: UInt { 22 | return value 23 | } 24 | 25 | // MARK: BitwiseOperationsType Protocol Methods 26 | 27 | public static var allZeros: CheckoutStrategy { 28 | return self(rawValue: 0) 29 | } 30 | 31 | // MARK: BooleanType Protocol Methods 32 | 33 | public var boolValue: Bool { 34 | return value != 0 35 | } 36 | 37 | // MARK: Enum Values 38 | 39 | /** 40 | The default strategy is none--a dry run that does not actually perform a checkout. 41 | */ 42 | public static var None: CheckoutStrategy { return self(rawValue: 0b0) } 43 | /** 44 | Allow safe updates that cannot overwrite any uncommitted data. 45 | Emulates `git checkout` without any additional option flags specified. 46 | */ 47 | public static var Safe: CheckoutStrategy { return self(rawValue: 0b1) } 48 | /** 49 | Allow safe updates, as well as the creation of missing files. 50 | Emulates `git clone` without any additional option flags specified. 51 | */ 52 | public static var SafeCreate: CheckoutStrategy { return self(rawValue: 0b10) } 53 | /** 54 | Allow all updates to force the working directory to mirror the index. 55 | Emulates `git checkout --force`. 56 | */ 57 | public static var Force: CheckoutStrategy { return self(rawValue: 0b100) } 58 | 59 | /** Allow the checkout to make safe updates, even if conflicts are found. */ 60 | public static var AllowConflicts: CheckoutStrategy { return self(rawValue: 0b1000) } 61 | /** Remove untracked files (that are not ignored) that are not in the index. */ 62 | public static var RemoveUntracked: CheckoutStrategy { return self(rawValue: 0b10000) } 63 | /** Remove ignored files not in the index. */ 64 | public static var RemoveIgnored: CheckoutStrategy { return self(rawValue: 0b00000000100000) } 65 | /** Only update existing files, don't create any new ones. */ 66 | public static var UpdateOnly: CheckoutStrategy { return self(rawValue: 0b00000001000000) } 67 | 68 | /** In the absence of this flag, a checkout updates the index. */ 69 | public static var DoNotUpdateIndex: CheckoutStrategy { return self(rawValue: 0b00000010000000) } 70 | /** In the absence of this flag, a checkout refreshes the index, config, and other metadata before it begins. */ 71 | public static var NoRefresh: CheckoutStrategy { return self(rawValue: 0b00000100000000) } 72 | 73 | /** Skip unmerged files. */ 74 | public static var SkipUnmerged: CheckoutStrategy { return self(rawValue: 0b00001000000000) } 75 | /** For unmerged files, use the local version. */ 76 | public static var UseOurs: CheckoutStrategy { return self(rawValue: 0b00010000000000) } 77 | /** For unmerged files, use the checkout target version. */ 78 | public static var UseTheirs: CheckoutStrategy { return self(rawValue: 0b00100000000000) } 79 | 80 | /** 81 | Treat pathspec as a simple list of exact match file paths. 82 | In the absence of this flag, partial matching is used. 83 | */ 84 | public static var DisablePathspecMatch: CheckoutStrategy { return self(rawValue: 0b1000000000000) } 85 | /** Ignore directories in use or locked by another Git process. These will be left empty. */ 86 | public static var SkipLockedDirectories: CheckoutStrategy { return self(rawValue: 0b100000000000000000) } 87 | /** In the absence of this flag, ignored files that exist in the checkout target are overwritten. */ 88 | public static var DoNotOverwriteIgnored: CheckoutStrategy { return self(rawValue: 0b1000000000000000000) } 89 | /** Write standard merge files in the case of a conflict. */ 90 | public static var ConflictStyleMerge: CheckoutStrategy { return self(rawValue: 0b10000000000000000000) } 91 | /** Include common ancestor data in diff3 format in the case of a conflict. */ 92 | public static var ConflictStyleDiff3: CheckoutStrategy { return self(rawValue: 0b100000000000000000000) } 93 | } 94 | -------------------------------------------------------------------------------- /Gift/Repository/Options/Checkout/GIFTCheckoutOptions.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | typedef struct git_checkout_options git_checkout_options; 4 | 5 | /** 6 | A callback function that is executed during a checkout. 7 | 8 | @param checkedOutFilePath A path to the file that is being checked out. 9 | @param fileIndex The index of the file whose checkout triggered this 10 | callback. For example, when the first file has been 11 | checked out, this is '0'. The second file is '1', the 12 | third is '2', and so on. 13 | @param fileCount The total number of files to be checked out. When the 14 | file index is equal to this file count, it can be 15 | inferred that this callback will no longer be called 16 | for the current checkout. 17 | */ 18 | typedef void (^GIFTCheckoutProgressCallback)(NSString *checkedOutFilePath, 19 | NSUInteger fileIndex, 20 | NSUInteger fileCount); 21 | 22 | /** 23 | Returns a set of checkout options with the given paramters. 24 | This function is used internally by Gift. 25 | 26 | @warning This function is necessary in order to allow a Swift closure 27 | to be used as a progress callback. It is impossible to 28 | obtain a reference to a C function from Swift that we may 29 | then use to configure a git_checkout_options struct. 30 | 31 | @param strategyValue The raw value of a CheckoutStrategy options set 32 | used to control how a checkout should be performed. 33 | @param progressCallback An optional progress callback block to be invoked 34 | as remote files are checked out. Note that this 35 | callback will not be invoked for local checkouts. 36 | @return A set of checkout options initialized with the given values. 37 | */ 38 | extern git_checkout_options gift_checkoutOptions(unsigned int strategyValue, 39 | GIFTCheckoutProgressCallback progressCallback); 40 | -------------------------------------------------------------------------------- /Gift/Repository/Options/Checkout/GIFTCheckoutOptions.m: -------------------------------------------------------------------------------- 1 | #import "GIFTCheckoutOptions.h" 2 | #import 3 | 4 | #pragma mark - Internal Functions 5 | 6 | static void gift_checkoutProgressCallback(const char *cPath, 7 | size_t completedSteps, 8 | size_t totalSteps, 9 | void *payload) { 10 | if (payload == NULL) { 11 | return; 12 | } 13 | 14 | NSString *path = nil; 15 | if (cPath != NULL) { 16 | path = @(cPath); 17 | } 18 | 19 | GIFTCheckoutProgressCallback block = (__bridge GIFTCheckoutProgressCallback)payload; 20 | block(path, completedSteps, totalSteps); 21 | } 22 | 23 | #pragma mark - Public Interface 24 | 25 | extern git_checkout_options gift_checkoutOptions(unsigned int strategyValue, 26 | GIFTCheckoutProgressCallback progressCallback) { 27 | git_checkout_options options = GIT_CHECKOUT_OPTIONS_INIT; 28 | options.checkout_strategy = strategyValue; 29 | if (progressCallback != nil) { 30 | options.progress_cb = gift_checkoutProgressCallback; 31 | options.progress_payload = (__bridge void *)[progressCallback copy]; 32 | } 33 | return options; 34 | } 35 | -------------------------------------------------------------------------------- /Gift/Repository/Options/Clone/CloneOptions+C.swift: -------------------------------------------------------------------------------- 1 | extension CloneOptions { 2 | /** 3 | Returns a C struct initialized with this options instance's values. 4 | Used by libgit2 functions. 5 | */ 6 | internal var cOptions: git_clone_options { 7 | return git_clone_options( 8 | version: 1, 9 | checkout_opts: checkoutOptions.cOptions, 10 | remote_callbacks: remoteCallbacks.cCallbacks, 11 | bare: 0, 12 | local: git_clone_local_t(localCloneBehavior.rawValue), 13 | checkout_branch: nil, 14 | signature: nil, 15 | repository_cb: nil, 16 | repository_cb_payload: nil, 17 | remote_cb: nil, 18 | remote_cb_payload: nil 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Gift/Repository/Options/Clone/CloneOptions.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A set of options used to clone a repository. 3 | 4 | These are made up of options used to checkout a repository, combined with 5 | a set of callbacks updated with cloning progress. 6 | */ 7 | public struct CloneOptions { 8 | internal let checkoutOptions: CheckoutOptions 9 | internal let remoteCallbacks: RemoteCallbacks 10 | internal let localCloneBehavior: LocalCloneBehavior 11 | 12 | public init( 13 | checkoutOptions: CheckoutOptions = CheckoutOptions(strategy: CheckoutStrategy.SafeCreate), 14 | remoteCallbacks: RemoteCallbacks = RemoteCallbacks(), 15 | localCloneBehavior: LocalCloneBehavior = .Automatic 16 | ) { 17 | self.checkoutOptions = checkoutOptions 18 | self.remoteCallbacks = remoteCallbacks 19 | self.localCloneBehavior = localCloneBehavior 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Gift/Repository/Options/Clone/LocalCloneBehavior.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Options that specify, when cloning a local repository, whether to fetch an 3 | object database, or merely copy it (which is faster). 4 | */ 5 | public enum LocalCloneBehavior: UInt32 { 6 | /** Copy the object database, but fetch when given `file://` URLs. */ 7 | case Automatic = 0 8 | 9 | /** Always copy, even when given `file://` URLs. */ 10 | case AlwaysCopy = 1 11 | 12 | /** Always clone, never copy, for all local repositories. */ 13 | case NeverCopy = 2 14 | 15 | /** Always clone, but do not try to use hardlinks. */ 16 | case NeverCopyAndNoHardlinks = 3 17 | } 18 | -------------------------------------------------------------------------------- /Gift/Repository/Options/Initialization/RepositoryInitializationMode.swift: -------------------------------------------------------------------------------- 1 | /** 2 | File mode options for the files created when initializing a repository. 3 | */ 4 | public enum RepositoryInitializationMode: UInt32 { 5 | /** Permissions used by umask--the default. */ 6 | case User = 0 7 | /** 8 | Emulates the permissions used by `git clone --shared=group`. 9 | The repository is made group-writeable. 10 | */ 11 | case Group = 0002775 12 | /** 13 | Emulates the permissions used by `git clone --shared=all`. 14 | The repository is made world-readable. 15 | */ 16 | case All = 0002777 17 | } 18 | -------------------------------------------------------------------------------- /Gift/Repository/Options/Initialization/RepositoryInitializationOptionSet.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A set of options indicating how a repository should be initialized. 3 | 4 | These values map directly to those enumerated in the libgit2 enum git_repository_init_flag_t. 5 | */ 6 | public struct RepositoryInitializationOptionSet : RawOptionSetType, BooleanType { 7 | private let value: UInt = 0 8 | 9 | // MARK: NilLiteralConvertible Protocol Methods 10 | 11 | public init(nilLiteral: ()) { } 12 | 13 | // MARK: _RawOptionSetType Protocol Methods 14 | 15 | public init(rawValue value: UInt) { 16 | self.value = value 17 | } 18 | 19 | // MARK: RawRepresentable Protocol Methods 20 | 21 | public var rawValue: UInt { 22 | return value 23 | } 24 | 25 | // MARK: BitwiseOperationsType Protocol Methods 26 | 27 | public static var allZeros: RepositoryInitializationOptionSet { 28 | return self(rawValue: 0) 29 | } 30 | 31 | // MARK: BooleanType Protocol Methods 32 | 33 | public var boolValue: Bool { 34 | return value != 0 35 | } 36 | 37 | // MARK: Enum Values 38 | 39 | /** Create a bare repository with no working directory. */ 40 | public static var Bare: RepositoryInitializationOptionSet { return self(rawValue: 0b0) } 41 | 42 | /** Raise an error (GIT_EEXISTS) if there already appears to be a repository at the given location. */ 43 | public static var DoNotReinitialize: RepositoryInitializationOptionSet { return self(rawValue: 0b1) } 44 | 45 | /** 46 | In the absence of this flag, and in the case that the repository being created is not bare, 47 | a '.git' directory will be created at the root of the repository path, provided that one 48 | does not already exist at that location. 49 | */ 50 | public static var NoDotGitDirectory: RepositoryInitializationOptionSet { return self(rawValue: 0b10) } 51 | 52 | /** Create the repository and working directory as needed. */ 53 | public static var MakeDirectory: RepositoryInitializationOptionSet { return self(rawValue: 0b100) } 54 | 55 | /** Recursively create all components of the repository and working directory path as needed. */ 56 | public static var MakePath: RepositoryInitializationOptionSet { return self(rawValue: 0b1000) } 57 | 58 | /** 59 | Normally, internal templates are used when creating a new repository. 60 | This flag, in combination with a template path that is provided to `RepositoryInitializationOptions`, 61 | or the `init.templatedir` global config value, will use an external template at the given location. 62 | */ 63 | public static var ExternalTemplate: RepositoryInitializationOptionSet { return self(rawValue: 0b10000) } 64 | 65 | /** 66 | If an alternate working directory path is provided to `RepositoryInitializationOptions`, 67 | use relative paths for the .git directory and working directory. 68 | */ 69 | public static var RelativeGitLink: RepositoryInitializationOptionSet { return self(rawValue: 0b100000) } 70 | } 71 | -------------------------------------------------------------------------------- /Gift/Repository/Options/Initialization/RepositoryInitializationOptions+C.swift: -------------------------------------------------------------------------------- 1 | extension RepositoryInitializationOptions { 2 | /** 3 | Returns a C struct initialized with this options instance's values. 4 | Used by libgit2 functions. 5 | */ 6 | internal var cOptions: git_repository_init_options { 7 | return git_repository_init_options( 8 | version: 1, 9 | flags: UInt32(optionsSet.rawValue), 10 | mode: mode.rawValue, 11 | workdir_path: nil, 12 | description: nil, 13 | template_path: nil, 14 | initial_head: nil, 15 | origin_url: nil 16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Gift/Repository/Options/Initialization/RepositoryInitializationOptions.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A set of options used to initialize a Git repository. 3 | The designated initializer provides a set of reasonable defaults. 4 | */ 5 | public struct RepositoryInitializationOptions { 6 | /** 7 | A set of options indicating how the repository should be initialized. 8 | */ 9 | internal let optionsSet: RepositoryInitializationOptionSet 10 | 11 | /** 12 | The mode of permissions on the newly created Git repository and its files. 13 | */ 14 | internal let mode: RepositoryInitializationMode 15 | 16 | public init( 17 | optionsSet: RepositoryInitializationOptionSet = RepositoryInitializationOptionSet.MakePath, 18 | mode: RepositoryInitializationMode = .User 19 | ) { 20 | self.optionsSet = optionsSet 21 | self.mode = mode 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Gift/Repository/Options/RemoteCallbacks/GIFTRemoteCallbacks.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | typedef struct git_remote_callbacks git_remote_callbacks; 5 | 6 | /** 7 | A callback function that is called when the remote sends textual progress updates. 8 | This includes messages such as "Compressing objects: 1% (47/4619)". 9 | 10 | @param message The message sent from the remote. 11 | @return A boolean indicating whether to stop the network operation. 12 | Return true to abort the operation, and false to keep going. 13 | */ 14 | typedef BOOL (^GIFTTransportMessageCallback)(NSString *message); 15 | 16 | /** 17 | A callback function that is called when new data is downloaded. 18 | 19 | @param progress A struct containing data on transfer progress, including 20 | total bytes received and other useful information. 21 | @return A boolean indicating whether to stop the network operation. 22 | Return true to abort the operation, and false to keep going. 23 | */ 24 | typedef BOOL (^GIFTTransferProgressCallback)(GIFTTransferProgress progress); 25 | 26 | /** 27 | Returns a set of remote callbacks with the given parameters. 28 | This function is used internally by Gift. 29 | 30 | @param transportMessageCallback A callback function that is called when the 31 | remote sends textual progress updates. 32 | @param transferProgressCallback A callback function that is called when new 33 | data is downloaded. 34 | @return A set of remote callbacks initialized with the given values. 35 | */ 36 | extern git_remote_callbacks gift_remoteCallbacks(GIFTTransportMessageCallback transportMessageCallback, 37 | GIFTTransferProgressCallback transferProgressCallback); 38 | -------------------------------------------------------------------------------- /Gift/Repository/Options/RemoteCallbacks/GIFTRemoteCallbacks.m: -------------------------------------------------------------------------------- 1 | #import "GIFTRemoteCallbacks.h" 2 | #import "GIFTRemoteCallbacksPayload.h" 3 | #import 4 | 5 | const int GIFTRemoteCallbacksPayloadError = -1989; 6 | 7 | #pragma mark - Internal Functions 8 | 9 | int gift_continueCode(BOOL shouldCancel) { 10 | return shouldCancel ? -1990 : 0; 11 | } 12 | 13 | static GIFTRemoteCallbacksPayload *_payload = nil; 14 | 15 | static int gift_transportMessageCallback(const char *str, int len, void *payload) { 16 | if (payload == NULL) { 17 | return GIFTRemoteCallbacksPayloadError; 18 | } 19 | 20 | GIFTRemoteCallbacksPayload *callbacksPayload = (__bridge GIFTRemoteCallbacksPayload *)payload; 21 | NSString *message; 22 | if (str != NULL && (message = @(str))) { 23 | return gift_continueCode(callbacksPayload.transportMessageCallback(message)); 24 | } 25 | 26 | return gift_continueCode(NO); 27 | } 28 | 29 | static int gift_transferProgressCallback(const git_transfer_progress *stats, void *payload) { 30 | if (payload == NULL) { 31 | return GIFTRemoteCallbacksPayloadError; 32 | } 33 | 34 | GIFTRemoteCallbacksPayload *callbacksPayload = (__bridge GIFTRemoteCallbacksPayload *)payload; 35 | return gift_continueCode(callbacksPayload.transferProgressCallback((GIFTTransferProgress){ 36 | .totalObjects = stats->total_objects, 37 | .indexedObjects = stats->indexed_objects, 38 | .receivedObjects = stats->received_objects, 39 | .localObjects = stats->local_objects, 40 | .totalDeltas = stats->total_deltas, 41 | .indexedDeltas = stats->indexed_deltas, 42 | .receivedBytes = stats->received_bytes, 43 | })); 44 | } 45 | 46 | #pragma mark - Public Interface 47 | 48 | extern git_remote_callbacks gift_remoteCallbacks(GIFTTransportMessageCallback transportMessageCallback, 49 | GIFTTransferProgressCallback transferProgressCallback) { 50 | git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; 51 | callbacks.sideband_progress = gift_transportMessageCallback; 52 | callbacks.transfer_progress = gift_transferProgressCallback; 53 | 54 | _payload = [[GIFTRemoteCallbacksPayload alloc] initWithTransportMessageCallback:transportMessageCallback 55 | transferProgressCallback:transferProgressCallback]; 56 | callbacks.payload = (__bridge void *)_payload; 57 | return callbacks; 58 | } 59 | -------------------------------------------------------------------------------- /Gift/Repository/Options/RemoteCallbacks/GIFTRemoteCallbacksPayload.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "GIFTRemoteCallbacks.h" 3 | 4 | /** 5 | An object used internally by Gift to store Swift callback closures 6 | that will be passed to C functions. 7 | */ 8 | @interface GIFTRemoteCallbacksPayload : NSObject 9 | 10 | @property (nonatomic, copy, readonly) GIFTTransportMessageCallback transportMessageCallback; 11 | @property (nonatomic, copy, readonly) GIFTTransferProgressCallback transferProgressCallback; 12 | 13 | - (instancetype)initWithTransportMessageCallback:(GIFTTransportMessageCallback)transportMessageCallback 14 | transferProgressCallback:(GIFTTransferProgressCallback)transferProgressCallback; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Gift/Repository/Options/RemoteCallbacks/GIFTRemoteCallbacksPayload.m: -------------------------------------------------------------------------------- 1 | #import "GIFTRemoteCallbacksPayload.h" 2 | 3 | @implementation GIFTRemoteCallbacksPayload 4 | 5 | #pragma mark - Object Initializer 6 | 7 | - (instancetype)initWithTransportMessageCallback:(GIFTTransportMessageCallback)transportMessageCallback 8 | transferProgressCallback:(GIFTTransferProgressCallback)transferProgressCallback 9 | { 10 | self = [super init]; 11 | if (self) { 12 | _transportMessageCallback = [transportMessageCallback copy]; 13 | _transferProgressCallback = [transferProgressCallback copy]; 14 | } 15 | return self; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Gift/Repository/Options/RemoteCallbacks/GIFTTransferProgress.h: -------------------------------------------------------------------------------- 1 | /** Information on the progress of a transfer. */ 2 | typedef struct { 3 | /** The number of objects in the packfile that will downloaded. */ 4 | NSUInteger totalObjects; 5 | /** The number of objects in the packfile that have been downloaded. */ 6 | NSUInteger receivedObjects; 7 | /** The number of received objects that have been hashed and indexed. */ 8 | NSUInteger indexedObjects; 9 | /** The number of locally available objects that have been injected in order to fix a thin pack. */ 10 | NSUInteger localObjects; 11 | /** The number of deltas that will be downloaded. */ 12 | NSUInteger totalDeltas; 13 | /** The number of deltas that have been downloaded, hashed, and indexed. */ 14 | NSUInteger indexedDeltas; 15 | /** The number of bytes of the packfile that have been downloaded so far. */ 16 | NSUInteger receivedBytes; 17 | } GIFTTransferProgress; 18 | -------------------------------------------------------------------------------- /Gift/Repository/Options/RemoteCallbacks/RemoteCallbacks+C.swift: -------------------------------------------------------------------------------- 1 | extension RemoteCallbacks { 2 | /** 3 | Returns a C struct initialized with this callbacks instance's values. 4 | Used by libgit2 functions. 5 | */ 6 | internal var cCallbacks: git_remote_callbacks { 7 | return gift_remoteCallbacks( 8 | transportMessageCallback, 9 | transferProgressCallback 10 | ) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Gift/Repository/Options/RemoteCallbacks/RemoteCallbacks.swift: -------------------------------------------------------------------------------- 1 | /** A default value for RemoteCallbacks.transportMessageCallback that does nothing. */ 2 | private let transportMessageDoNothing: GIFTTransportMessageCallback = { (message) in false } 3 | 4 | /** A default value for RemoteCallbacks.transferProgressCallback that does nothing. */ 5 | private let transferProgressDoNothing: GIFTTransferProgressCallback = { (progress) in false } 6 | 7 | /** 8 | Callbacks used to inform the user about 9 | the progress of a network operation. 10 | */ 11 | public struct RemoteCallbacks { 12 | internal let transportMessageCallback: GIFTTransportMessageCallback 13 | internal let transferProgressCallback: GIFTTransferProgressCallback 14 | 15 | public init( 16 | transportMessageCallback: GIFTTransportMessageCallback = transportMessageDoNothing, 17 | transferProgressCallback: GIFTTransferProgressCallback = transferProgressDoNothing 18 | ) { 19 | self.transportMessageCallback = transportMessageCallback 20 | self.transferProgressCallback = transferProgressCallback 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Gift/Repository/Options/Reset/ResetType.swift: -------------------------------------------------------------------------------- 1 | /** Options for how to perform a repository reset. */ 2 | public enum ResetType: UInt32 { 3 | /** 4 | Moves HEAD to a given commit. 5 | The index and working directory are preserved. 6 | */ 7 | case Soft = 1 8 | 9 | /** 10 | Not only moves HEAD to a given commit, but also 11 | resets the index to that commit as well. 12 | */ 13 | case Mixed = 2 14 | 15 | /** 16 | Moves HEAD to a given commit, resets the index to that 17 | commit, and discards any changes in the working directory. 18 | */ 19 | case Hard = 3 20 | } 21 | -------------------------------------------------------------------------------- /Gift/Repository/Options/Tag/GIFTTagForEach.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | typedef struct git_oid git_oid; 4 | typedef struct git_repository git_repository; 5 | 6 | extern const int GIFTTagForEachCallbackPayloadError; 7 | 8 | /** 9 | A callback function that is executed for each tag in the repository. 10 | 11 | @param referenceName The name of the tag. 12 | @param referenceObjectID A pointer to the object ID of the tag. 13 | @return An integer representing whether tag enumeration should continue. 14 | Anything but zero will halt enumeration. If enumeration is 15 | halted, the result code of the tag enumeration function will 16 | be equal to any non-zero value returned here. 17 | */ 18 | typedef int (^GIFTTagForEachCallback)(NSString *referenceName, 19 | git_oid *referenceObjectID); 20 | 21 | /** 22 | Enumerates over all tags in a repository. Used internally by Gift. 23 | 24 | @param repository A pointer to a C struct used to represent a repository by libgit2. 25 | @param tagCallback A callback, which may be defined in Swift, that will be passed 26 | to a static C callback function. 27 | @return A status code. Anything other than 0 will halt the tag enumeration. 28 | */ 29 | extern int gift_tagForEach(git_repository *repository, 30 | GIFTTagForEachCallback tagCallback); 31 | -------------------------------------------------------------------------------- /Gift/Repository/Options/Tag/GIFTTagForEach.m: -------------------------------------------------------------------------------- 1 | #import "GIFTTagForEach.h" 2 | #import 3 | 4 | const int GIFTTagForEachCallbackPayloadError = -1988; 5 | 6 | static int gift_tagForEachCallback(const char *cName, git_oid *oid, void *payload) { 7 | if (payload == NULL) { 8 | return GIFTTagForEachCallbackPayloadError; 9 | } 10 | 11 | NSString *name = nil; 12 | if (cName != NULL) { 13 | name = @(cName); 14 | } 15 | 16 | GIFTTagForEachCallback block = (__bridge GIFTTagForEachCallback)payload; 17 | return block(name, oid); 18 | } 19 | 20 | int gift_tagForEach(git_repository *repository, 21 | GIFTTagForEachCallback tagCallback) { 22 | return git_tag_foreach(repository, 23 | gift_tagForEachCallback, 24 | (__bridge void *)[tagCallback copy]); 25 | } 26 | -------------------------------------------------------------------------------- /Gift/Repository/Repository+Branch.swift: -------------------------------------------------------------------------------- 1 | import ReactiveCocoa 2 | import LlamaKit 3 | 4 | public extension Repository { 5 | /** 6 | Enumerates references that refer to branches in a repository. 7 | As the references are enumerated, their values are sent via a signal. 8 | 9 | :param: type The type of branch to include in the enumeration. 10 | :returns: A signal that will notify subscribers of branches as they are 11 | enumerated. Dispose of the signal in order to discontinue 12 | the enueration. 13 | */ 14 | public func branches(type: BranchType = .Local) -> SignalProducer { 15 | return SignalProducer { (observer, disposable) in 16 | // Create a branch iterator. If this fails, notify the subscriber 17 | // of an error and exit early. 18 | var out = COpaquePointer.null() 19 | let errorCode = git_branch_iterator_new(&out, self.cRepository, git_branch_t(type.rawValue)) 20 | if errorCode == GIT_OK.value { 21 | let iterator = BranchIterator(cBranchIterator: out) 22 | 23 | // Iterate over each branch reference. 24 | var next = iterator.next() 25 | while next.errorCode == GIT_OK.value { 26 | // For each reference, check if the signal has been disposed of. 27 | // If so, cancel early. 28 | if disposable.disposed { 29 | return 30 | } 31 | // Otherwise, continue sending the subscriber references. 32 | sendNext(observer, Reference(cReference: next.cReference)) 33 | next = iterator.next() 34 | } 35 | 36 | // Iteration completion means one of two things: 37 | if next.errorCode == GIT_ITEROVER.value { 38 | // 1. There are no branch references to iterate over, i.e.: GIT_ITEROVER. 39 | // If that's the case, notify the subsciber that the signal has completed. 40 | sendCompleted(observer) 41 | } else { 42 | // 2. An error occurred while iterating. If that's the case, notify the 43 | // subscriber of the error. 44 | sendError(observer, NSError.libGit2Error(next.errorCode, libGit2PointOfFailure: iterator.pointOfFailure)) 45 | } 46 | } else { 47 | sendError(observer, NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_branch_iterator_new")) 48 | } 49 | } 50 | } 51 | 52 | /** 53 | Looks up a branch by a given name. 54 | 55 | :param: name The name of the branch to look up. 56 | :param: branchType A classier specifying whether to include results from 57 | local branches, remote branches, or both. 58 | :returns: The result of the lookup: either a reference to a 59 | branch or an error indicating what went wrong. 60 | */ 61 | public func lookupBranch(name: String, branchType: BranchType = .Local) -> Result { 62 | var out = COpaquePointer.null() 63 | let errorCode = git_branch_lookup(&out, cRepository, name, git_branch_t(branchType.rawValue)) 64 | if errorCode == GIT_OK.value { 65 | return success(Reference(cReference: out)) 66 | } else { 67 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_branch_lookup")) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Gift/Repository/Repository+Commit.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | import ReactiveCocoa 4 | 5 | public extension Repository { 6 | /** 7 | Enumerates the commits in a repository in the given order. 8 | 9 | :param: sorting The sorting rules to use when ordering commits to enumerate. 10 | :returns: A signal that will notify subscribers of commits as they are 11 | enumerated. Dispose of the signal in order to discontinue 12 | the enueration. 13 | */ 14 | public func commits(sorting: CommitSorting = CommitSorting.Time) -> SignalProducer { 15 | return SignalProducer { (observer, disposable) in 16 | var out = COpaquePointer.null() 17 | let errorCode = git_revwalk_new(&out, self.cRepository) 18 | if errorCode == GIT_OK.value { 19 | CommitWalker(cWalker: out, cRepository: self.cRepository, sorting: sorting) 20 | .walk(observer, disposable: disposable) 21 | } else { 22 | sendError(observer, NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_revwalk_new")) 23 | } 24 | } 25 | } 26 | 27 | /** 28 | Sets the current HEAD of the repository to the given commit. 29 | Depending on the reset type specified, this may also reset the index 30 | and working directory. 31 | 32 | :param: commit The commit HEAD should be reset to. 33 | :param: resetType A type specifying whether the reset should be "soft", 34 | "hard", or "mixed"--each of those being different options 35 | for resetting the index and working directory. 36 | :param: checkoutOptions In the case of a hard commit, these options will be 37 | used to determine exactly how files will be reset. 38 | These can also be used in order to register progress 39 | callbacks. The "checkout strategy" of the options will 40 | be overridden based on the reset type. 41 | :param: signature The identity that will be used to popular the reflog entry. 42 | By default, this will be recorded as "com.libgit2.Gift". 43 | :returns: The result of the operation: either a repository that has been reset to 44 | point to the given commit, or an error indicating what went wrong. 45 | */ 46 | public func reset(commit: Commit, resetType: ResetType, checkoutOptions: CheckoutOptions = CheckoutOptions(strategy: CheckoutStrategy.None), signature: Signature = giftSignature) -> Result { 47 | var cOptions = checkoutOptions.cOptions 48 | var cSignature = signature.cSignature 49 | let errorCode = git_reset(cRepository, commit.cCommit, git_reset_t(resetType.rawValue), &cOptions, &cSignature, nil) 50 | if errorCode == GIT_OK.value { 51 | return success(self) 52 | } else { 53 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_reset")) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Gift/Repository/Repository+Index.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | public extension Repository { 5 | /** 6 | Creates and returns an index object for the repository. 7 | */ 8 | public var index: Result { 9 | var cIndex = COpaquePointer.null() 10 | let errorCode = git_repository_index(&cIndex, cRepository) 11 | if errorCode == GIT_OK.value { 12 | return success(Index(cIndex: cIndex)) 13 | } else { 14 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_repository_index")) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Gift/Repository/Repository+Reference.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | public extension Repository { 5 | /** 6 | Retrieves the reference pointed at by HEAD. 7 | */ 8 | public var headReference: Result { 9 | var out = COpaquePointer.null() 10 | let errorCode = git_repository_head(&out, cRepository) 11 | 12 | if errorCode == GIT_OK.value { 13 | return success(Reference(cReference: out)) 14 | } else { 15 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_repository_head")) 16 | } 17 | } 18 | 19 | /** 20 | Sets the head of this repository to the reference with the given name. 21 | 22 | :param: referenceName The name of the reference. For example, to set the head 23 | to a local branch named "development", this parameter 24 | should be specified as "refs/heads/development". 25 | :param: signature The identity that will be used to popular the reflog entry. 26 | By default, this will be recorded as "com.libgit2.Gift". 27 | :returns: The result of the operation: either a reference to the repository whose 28 | head has been changed, or an error indicating what went wrong. 29 | */ 30 | public func setHead(referenceName: String, signature: Signature = giftSignature) -> Result { 31 | var cSignature = signature.cSignature 32 | let errorCode = git_repository_set_head(cRepository, referenceName, &cSignature, nil) 33 | if errorCode == GIT_OK.value { 34 | return success(self) 35 | } else { 36 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_repository_set_head")) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Gift/Repository/Repository+Remote.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | public extension Repository { 5 | /** 6 | Adds a remote to the repository. 7 | 8 | :param: name The name of the remote. If this is invalid, this method will fail. 9 | :param: url The remote URL. If this is invalid, this method will fail. 10 | :returns: The result of the operation: either the newly created remote, 11 | or an error indicating what went wrong. 12 | */ 13 | public func createRemote(name: String, url: NSURL) -> Result { 14 | if let remoteURL = url.string { 15 | var out = COpaquePointer.null() 16 | let errorCode = git_remote_create(&out, cRepository, name, remoteURL) 17 | if errorCode == GIT_OK.value { 18 | return success(Remote(cRemote: out)) 19 | } else { 20 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_remote_create")) 21 | } 22 | } else { 23 | return failure(NSError.giftError(.InvalidURI, description: "Invalid URL: \(url)")) 24 | } 25 | } 26 | 27 | /** 28 | Checks this repository's remotes for one with the given name, 29 | and returns it if it exists. 30 | 31 | :param: name The name of the remote to look up. 32 | :returns: The result of the operation: either a remote, or an error 33 | indicating what went wrong. 34 | */ 35 | public func lookupRemote(name: String) -> Result { 36 | var out = COpaquePointer.null() 37 | let errorCode = git_remote_lookup(&out, cRepository, name) 38 | if errorCode == GIT_OK.value { 39 | return success(Remote(cRemote: out)) 40 | } else { 41 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_remote_lookup")) 42 | } 43 | } 44 | 45 | /** 46 | Returns a list of the names of this repository's remotes, or an error 47 | indicating what went wrong when retrieving the names. 48 | */ 49 | public var remoteNames: Result<[String], NSError> { 50 | var out = git_strarray(strings: nil, count: 0) 51 | let errorCode = git_remote_list(&out, cRepository) 52 | if errorCode == GIT_OK.value { 53 | let names = strings(out) 54 | git_strarray_free(&out) 55 | return names 56 | } else { 57 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_remote_list")) 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Gift/Repository/Repository+Stash.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | public extension Repository { 5 | /** 6 | Stash changes in the index into a commit object. 7 | This commit can be checked out later in order to apply the stash. 8 | 9 | :param: message A message associated with the stash. 10 | :returns: The result of the operation: either a commit with the stashed 11 | changes, or an error indicating what went wrong. 12 | */ 13 | public func stash(message: String) -> Result { 14 | var objectID = UnsafeMutablePointer.alloc(1) 15 | var cSignature = giftSignature.cSignature 16 | let errorCode = git_stash_save(objectID, cRepository, &cSignature, message, 0) 17 | if errorCode == GIT_OK.value { 18 | let commit = Commit.lookup(objectID, cRepository: cRepository) 19 | objectID.dealloc(1) 20 | return commit 21 | } else { 22 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_stash_save")) 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Gift/Repository/Repository+Status.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | import ReactiveCocoa 4 | 5 | public extension Repository { 6 | /** 7 | Enumerates over all status deltas in a repository. 8 | 9 | :param: options Options for enumerating status deltas. Use this to control whether 10 | unmodified files are included in the enumeration, whether to detect 11 | renames, and more. 12 | :returns: A signal that sends subscribers status deltas as they're 13 | enumerated, or an error to indicate what went wrong during 14 | the enumeration. 15 | */ 16 | public func status(options: StatusDeltaOptions = StatusDeltaOptions()) -> SignalProducer { 17 | return SignalProducer { (observer, disposable) in 18 | var statusList = COpaquePointer.null() 19 | var cOptions = options.cOptions 20 | let errorCode = git_status_list_new(&statusList, self.cRepository, &cOptions) 21 | if errorCode == GIT_OK.value { 22 | let statusCount = git_status_list_entrycount(statusList) 23 | for statusIndex in 0.. Result { 46 | var statusFlags: UInt32 = 0 47 | let errorCode = git_status_file(&statusFlags, cRepository, path) 48 | if errorCode == GIT_OK.value { 49 | return success(EntryStatus(rawValue: UInt(statusFlags))) 50 | } else { 51 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_status_file")) 52 | } 53 | } 54 | 55 | /** 56 | Whether the file at the given path matches the ignore rules. 57 | 58 | :param: path The path of the file. 59 | :returns: The result of the operation: either a boolean value indicating whether 60 | the file is ignored, or an error indicating what went wrong. 61 | */ 62 | public func shouldIgnore(path: String) -> Result { 63 | var ignored: Int32 = 0 64 | let errorCode = git_status_should_ignore(&ignored, cRepository, path) 65 | if errorCode == GIT_OK.value { 66 | return success(ignored != 0) 67 | } else { 68 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_status_should_ignore")) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Gift/Repository/Repository+Tag.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | import ReactiveCocoa 4 | 5 | private let tagEnumerationOver: Int32 = -1987 6 | 7 | public extension Repository { 8 | /** 9 | Enumerates over all tags in a repository. 10 | 11 | :returns: A signal that sends subscribers tag references as they're 12 | enumerated, or an error to indicate what went wrong during 13 | the enumeration. 14 | */ 15 | public func tags() -> SignalProducer { 16 | return SignalProducer { (observer, disposable) in 17 | let errorCode = gift_tagForEach(self.cRepository) { (referenceName, referenceObjectID) in 18 | if disposable.disposed { 19 | return tagEnumerationOver 20 | } 21 | 22 | let reference = Reference.lookup(referenceName, cRepository: self.cRepository) 23 | switch reference { 24 | case .Success(let boxedReference): 25 | sendNext(observer, boxedReference.unbox) 26 | return GIT_OK.value 27 | case .Failure(let boxedError): 28 | sendError(observer, boxedError.unbox) 29 | return tagEnumerationOver 30 | } 31 | } 32 | 33 | if errorCode == GIT_OK.value { 34 | sendCompleted(observer) 35 | } else if errorCode == GIFTTagForEachCallbackPayloadError { 36 | let description = "An error occurred when attempting to enumerate tags in a repository." 37 | sendError(observer, NSError.giftError(.CFunctionCallbackConversionFailure, description: description)) 38 | } else if errorCode != tagEnumerationOver { 39 | sendError(observer, NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_tag_foreach")) 40 | } 41 | } 42 | } 43 | 44 | /** 45 | A list of tag names in the repository. 46 | 47 | :param: matchingPattern If a pattern is provided, only the tags 48 | matching this pattern are returned. 49 | :returns: Either a list of tag names matching the given pattern (if any), 50 | or a failure indicating what went wrong when retrieving the tags. 51 | */ 52 | public func tagNames(matchingPattern: String = "*") -> Result<[String], NSError> { 53 | var out = git_strarray(strings: nil, count: 0) 54 | let errorCode = git_tag_list_match(&out, matchingPattern, cRepository) 55 | if errorCode == GIT_OK.value { 56 | let names = strings(out) 57 | git_strarray_free(&out) 58 | return names 59 | } else { 60 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_tag_list_match")) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Gift/Repository/Repository.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | /** 5 | A data structure representing a Git repository. 6 | 7 | A warning when using this class: when a repository object 8 | goes out of scope, and its deinitializer is called, it frees 9 | the underlying C struct representing the repository. 10 | 11 | If this is done prematurely, it may cause undefined behavior 12 | and even crashes. For example, many methods provided by an 13 | index object (`Gift.Index`) require that the repository backing 14 | that index still exist. If the repository goes out of scope, 15 | but the index is still used, Gift may crash. 16 | 17 | To avoid these pitfalls, make sure you maintain a reference to 18 | a repository object as long as you continue to use objects 19 | that rely on that repository. 20 | */ 21 | public class Repository { 22 | internal let cRepository: COpaquePointer 23 | 24 | internal init(cRepository: COpaquePointer) { 25 | self.cRepository = cRepository 26 | } 27 | 28 | deinit { 29 | git_repository_free(cRepository) 30 | } 31 | } 32 | 33 | public extension Repository { 34 | /** 35 | Returns either the repository's .git directory's URL, or an error 36 | indicating what went wrong. 37 | */ 38 | public var gitDirectoryURL: Result { 39 | if let path = String.fromCString(git_repository_path(cRepository)) { 40 | if let url = NSURL(fileURLWithPath: path, isDirectory: true) { 41 | return success(url) 42 | } else { 43 | let description = "Could not create NSURL from path '\(path)', provided by git_repository_path." 44 | return failure(NSError.giftError(.InvalidURI, description: description)) 45 | } 46 | } else { 47 | return failure("libgit2 error: git_repository_path failed") 48 | } 49 | } 50 | } 51 | 52 | /** 53 | Creates a new Git repository at the given directory. 54 | 55 | :param: directoryURL The directory at which the new repository should be created. 56 | If this is not a valid directory URL, this function will fail. 57 | :param: options A set of options used to customize the Git repository that is created. 58 | :returns: The result of the operation: either a newly created repository, or an error explaining what went wrong. 59 | */ 60 | public func initializeEmptyRepository(directoryURL: NSURL, options: RepositoryInitializationOptions = RepositoryInitializationOptions()) -> Result { 61 | if let repoPath = directoryURL.path?.fileSystemRepresentation() { 62 | var out = COpaquePointer.null() 63 | var cOptions = options.cOptions 64 | let errorCode = git_repository_init_ext(&out, repoPath, &cOptions) 65 | 66 | if errorCode == GIT_OK.value { 67 | return success(Repository(cRepository: out)) 68 | } else { 69 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_repository_init_ext")) 70 | } 71 | } else { 72 | return failure(NSError.giftError(.InvalidURI, description: "Invalid directoryURL: '\(directoryURL)'")) 73 | } 74 | } 75 | 76 | /** 77 | Opens a Git repository at the given fileURL. 78 | 79 | :param: fileURL The URL at which the Git repository is located. 80 | :returns: The result of the operation: either a Repository object, or an error 81 | indicating why the repository could not be opened. 82 | */ 83 | public func openRepository(fileURL: NSURL) -> Result { 84 | if !fileURL.fileURL || fileURL.path == nil { 85 | return failure(NSError.giftError(.InvalidURI, description: "Invalid fileURL: '\(fileURL)'")) 86 | } else { 87 | var out = COpaquePointer.null() 88 | let errorCode = git_repository_open(&out, fileURL.path!) 89 | if errorCode == GIT_OK.value { 90 | return success(Repository(cRepository: out)) 91 | } else { 92 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_repository_open")) 93 | } 94 | } 95 | } 96 | 97 | /** 98 | Clones a remote repository. 99 | 100 | :param: originURL The URL at which the remote repository is located. 101 | This URL may represent a location on the local file system. 102 | :param: destinationWorkingDirectory The file system URL the cloned repository will be written to. 103 | :options: A set of options used to configure how the repository will be cloned. 104 | :returns: The result of the operation: either the cloned repository, or an error explaining what went wrong. 105 | */ 106 | public func cloneRepository(originURL: NSURL, destinationWorkingDirectory: NSURL, options: CloneOptions = CloneOptions()) -> Result { 107 | if let url = originURL.string { 108 | if let localPath = destinationWorkingDirectory.path?.fileSystemRepresentation() { 109 | var out = COpaquePointer.null() 110 | var cOptions = options.cOptions 111 | let errorCode = git_clone(&out, url, localPath, &cOptions) 112 | 113 | if errorCode == GIT_OK.value { 114 | return success(Repository(cRepository: out)) 115 | } else { 116 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_clone")) 117 | } 118 | } else { 119 | return failure(NSError.giftError(.InvalidURI, description: "Invalid destinationWorkingDirectory: \(destinationWorkingDirectory)")) 120 | } 121 | } else { 122 | return failure(NSError.giftError(.InvalidURI, description: "Invalid originURL: \(originURL)")) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Gift/Signature/Signature+C.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | internal extension Signature { 5 | /** 6 | Returns a C struct initialized with this signature instance's values. 7 | Used by libgit2 functions. 8 | */ 9 | internal var cSignature: git_signature { 10 | let utf8Name = (name as NSString).UTF8String 11 | var cName: UnsafeMutablePointer = UnsafeMutablePointer(utf8Name) 12 | 13 | let utf8Email = (email as NSString).UTF8String 14 | var cEmail: UnsafeMutablePointer = UnsafeMutablePointer(utf8Email) 15 | 16 | return git_signature( 17 | name: cName as UnsafeMutablePointer, 18 | email: cEmail as UnsafeMutablePointer, 19 | when: git_time( 20 | time: git_time_t(date.timeIntervalSince1970), 21 | offset: Int32(timeZone.secondsFromGMT / 60) 22 | ) 23 | ) 24 | } 25 | 26 | /** 27 | Creates a Signature object based on a git_signature C struct, or a failure 28 | indicating what went wrong when attempting to create the object. 29 | */ 30 | internal static func fromCSignature(cSignature: git_signature) -> Result { 31 | if let signatureName = String.fromCString(cSignature.name) { 32 | if let signatureEmail = String.fromCString(cSignature.email) { 33 | return success(Signature( 34 | name: signatureName, 35 | email: signatureEmail, 36 | date: NSDate(timeIntervalSince1970: NSTimeInterval(cSignature.when.time)), 37 | timeZone: NSTimeZone(forSecondsFromGMT: Int(cSignature.when.offset) * 60) 38 | )) 39 | } 40 | } 41 | 42 | let description = "An error occurred when attempting to convert signature " 43 | + "name '\(cSignature.name)' and email '\(cSignature.email)' " 44 | + "to strings." 45 | return failure(NSError.giftError(.StringConversionFailure, description: description)) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Gift/Signature/Signature.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | A signature represents information about the creator of 5 | an object. For example, tags and commits have signatures 6 | that contain information about who created the tag or commit, 7 | and when they did so. 8 | */ 9 | public struct Signature { 10 | /** The name of the person associated with this signature. */ 11 | public let name: String 12 | /** The email of the person associated with this signature. */ 13 | public let email: String 14 | /** The date this signature was made. */ 15 | public let date: NSDate 16 | /** The time zone in which this signature was made. */ 17 | public let timeZone: NSTimeZone 18 | 19 | public init(name: String, email: String, date: NSDate = NSDate(timeIntervalSinceNow: 0), timeZone: NSTimeZone = NSTimeZone.defaultTimeZone()) { 20 | self.name = name 21 | self.email = email 22 | self.date = date 23 | self.timeZone = timeZone 24 | } 25 | } 26 | 27 | /** 28 | A signature used by Gift when updating the reflog and the 29 | user has not provided a signature of their own. 30 | */ 31 | internal let giftSignature = Signature(name: "com.libgit2.Gift", email: "") 32 | -------------------------------------------------------------------------------- /Gift/Status/EntryStatus/EntryStatus.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A set of flags indicating the status of an entry. 3 | 4 | In the absence of any changes, the entry is considered "current". 5 | Otherwise, it may have changed in the index, working directory, 6 | or both. It may also be ignored. 7 | */ 8 | public struct EntryStatus : RawOptionSetType, BooleanType { 9 | private let value: UInt = 0 10 | 11 | // MARK: NilLiteralConvertible Protocol Methods 12 | 13 | public init(nilLiteral: ()) { } 14 | 15 | // MARK: _RawOptionSetType Protocol Methods 16 | 17 | public init(rawValue value: UInt) { 18 | self.value = value 19 | } 20 | 21 | // MARK: RawRepresentable Protocol Methods 22 | 23 | public var rawValue: UInt { 24 | return value 25 | } 26 | 27 | // MARK: BitwiseOperationsType Protocol Methods 28 | 29 | public static var allZeros: EntryStatus { 30 | return self(rawValue: 0) 31 | } 32 | 33 | // MARK: BooleanType Protocol Methods 34 | 35 | public var boolValue: Bool { 36 | return value != 0 37 | } 38 | 39 | // MARK: Enum Values 40 | 41 | /** No changes have been made to the entry. */ 42 | public static var Current: EntryStatus { return self(rawValue: 0b0) } 43 | 44 | /** The entry has been newly added since HEAD, and has been staged in the index. */ 45 | public static var IndexNew: EntryStatus { return self(rawValue: 0b1) } 46 | /** The entry has been modified since HEAD, and has been staged in the index. */ 47 | public static var IndexModified: EntryStatus { return self(rawValue: 0b10) } 48 | /** The entry has been deleted since HEAD, and has been staged in the index. */ 49 | public static var IndexDeleted: EntryStatus { return self(rawValue: 0b100) } 50 | /** The entry has been renamed since HEAD, and has been staged in the index. */ 51 | public static var IndexRenamed: EntryStatus { return self(rawValue: 0b1000) } 52 | /** The entry has had its type changed since HEAD, and has been staged in the index. */ 53 | public static var IndexTypeChanged: EntryStatus { return self(rawValue: 0b10000) } 54 | 55 | /** The entry has been newly added when compared to the index and HEAD. */ 56 | public static var WorkingDirectoryNew: EntryStatus { return self(rawValue: 0b10000000) } 57 | /** The entry has been modified when compared to the index and HEAD. */ 58 | public static var WorkingDirectoryModified: EntryStatus { return self(rawValue: 0b100000000) } 59 | /** The entry has been newly deleted when compared to the index and HEAD. */ 60 | public static var WorkingDirectoryDeleted: EntryStatus { return self(rawValue: 0b1000000000) } 61 | /** The entry has had its type changed when compared to the index and HEAD. */ 62 | public static var WorkingDirectoryTypeChanged: EntryStatus { return self(rawValue: 0b10000000000) } 63 | /** The entry has been renamed when compared to the index and HEAD. */ 64 | public static var WorkingDirectoryRenamed: EntryStatus { return self(rawValue: 0b100000000000) } 65 | 66 | /** The entry is ignored. */ 67 | public static var Ignored: EntryStatus { return self(rawValue: 0b10000000000000) } 68 | } -------------------------------------------------------------------------------- /Gift/Status/StatusDelta/Options/StatusDeltaBehavior.swift: -------------------------------------------------------------------------------- 1 | /** A set of options to customize the behavior of status delta enumeration. */ 2 | public struct StatusDeltaBehavior : RawOptionSetType, BooleanType { 3 | private let value: UInt = 0 4 | 5 | // MARK: NilLiteralConvertible Protocol Methods 6 | 7 | public init(nilLiteral: ()) { } 8 | 9 | // MARK: _RawOptionSetType Protocol Methods 10 | 11 | public init(rawValue value: UInt) { 12 | self.value = value 13 | } 14 | 15 | // MARK: RawRepresentable Protocol Methods 16 | 17 | public var rawValue: UInt { 18 | return value 19 | } 20 | 21 | // MARK: BitwiseOperationsType Protocol Methods 22 | 23 | public static var allZeros: StatusDeltaBehavior { 24 | return self(rawValue: 0) 25 | } 26 | 27 | // MARK: BooleanType Protocol Methods 28 | 29 | public var boolValue: Bool { 30 | return value != 0 31 | } 32 | 33 | // MARK: Enum Values 34 | 35 | /** 36 | Include untracked files in the enumeration, provided the paths to 37 | those files are included in the first place. 38 | */ 39 | public static var IncludeUntracked: StatusDeltaBehavior { return self(rawValue: 0b1) } 40 | 41 | /** 42 | Include ignored files in the enumeration, provided the paths to 43 | those files are included in the first place. 44 | */ 45 | public static var IncludeIgnored: StatusDeltaBehavior { return self(rawValue: 0b10) } 46 | 47 | /** 48 | Include unmodified files in the enumeration, provided the paths to 49 | those files are included in the first place. 50 | */ 51 | public static var IncludeUnmodified: StatusDeltaBehavior { return self(rawValue: 0b100) } 52 | 53 | /** Exclude submodules in the enumeration. */ 54 | public static var ExcludeSubmodules: StatusDeltaBehavior { return self(rawValue: 0b1000) } 55 | 56 | /** 57 | Include all files in untracked directories in the enumeration. 58 | In the absence of this flag, if an entire directory is new, then just the 59 | top-level directory is included in the enumeration, along with a trailing slash 60 | on the entry name. This flag overrides that default behavior, to instead 61 | include every single file in the enumeration. 62 | */ 63 | public static var RecurseUntrackedDirectories: StatusDeltaBehavior { return self(rawValue: 0b10000) } 64 | 65 | /** 66 | Treat pathspec as a simple list of exact match file paths. In the absence of this 67 | flag, partial matching is used. 68 | */ 69 | public static var DisablePathspecMatch: StatusDeltaBehavior { return self(rawValue: 0b100000) } 70 | 71 | /** Include the contents of ignored directories in the enumeration. */ 72 | public static var RecurseIgnoredDirectories: StatusDeltaBehavior { return self(rawValue: 0b1000000) } 73 | 74 | /** 75 | Detect when a file has been renamed between HEAD and the index. 76 | This requires additional computation, but in return files may be marked as having 77 | type `StatusDeltaType.Renamed` when compared between HEAD and index. 78 | */ 79 | public static var DetectRenamesBetweenHeadAndIndex: StatusDeltaBehavior { return self(rawValue: 0b10000000) } 80 | 81 | /** 82 | Detect when a file has been renamed between the index and the working directory. 83 | This requires additional computation, but in return files may be marked as having 84 | type `StatusDeltaType.Renamed` when compared between the index and the working directory. 85 | */ 86 | public static var DetectRenamesBetweenIndexAndWorkingDirectory: StatusDeltaBehavior { return self(rawValue: 0b100000000) } 87 | 88 | /** 89 | Overrides the native case-sensitivity for the platform, and enumerates files 90 | in case-sensitive, alphabetical order. 91 | */ 92 | public static var SortCaseSensitively: StatusDeltaBehavior { return self(rawValue: 0b1000000000) } 93 | 94 | /** 95 | Overrides the native case-sensitivity for the platform, and enumerates files 96 | in case-insensitive, alphabetical order. 97 | */ 98 | public static var SortCaseInsensitively: StatusDeltaBehavior { return self(rawValue: 0b10000000000) } 99 | 100 | /** Detect when a file has been renamed, even among rewritten files. */ 101 | public static var DetectRenamesAmongRewrittenFiles: StatusDeltaBehavior { return self(rawValue: 0b100000000000) } 102 | 103 | /** 104 | Normally, a status delta enumeration reloads the index, picking up any changes that may 105 | have been made outside of Gift itself. This flag disables that default behavior, so that 106 | status delta enumeration does not refresh the index. 107 | */ 108 | public static var NoRefresh: StatusDeltaBehavior { return self(rawValue: 0b1000000000000) } 109 | 110 | /** 111 | Cache statistics on the status delta that were gathered during the status delta 112 | enumeration. This will result in less work being done on subsequent enumerations. 113 | This is mutually exclusive with the .NoRefresh option. 114 | */ 115 | public static var CacheStatistics: StatusDeltaBehavior { return self(rawValue: 0b1000000000000) } 116 | 117 | /** 118 | Include unreadable files in the enumeration, provided the paths to 119 | those files are included in the first place. 120 | */ 121 | public static var IncludeUnreadable: StatusDeltaBehavior { return self(rawValue: 0b10000000000000) } 122 | 123 | /** 124 | Include untracked files as untracked files in the enumeration. 125 | */ 126 | public static var IncludeUnreadableAsUntracked: StatusDeltaBehavior { return self(rawValue: 0b100000000000000) } 127 | } 128 | -------------------------------------------------------------------------------- /Gift/Status/StatusDelta/Options/StatusDeltaBetween.swift: -------------------------------------------------------------------------------- 1 | /** Indicates what should be compared when enumerating file status deltas. */ 2 | public enum StatusDeltaBetween: UInt32 { 3 | /** 4 | Include status deltas between HEAD and the index, 5 | as well as between the index and the working directory. 6 | */ 7 | case All = 0 8 | /** Only include status deltas between HEAD and the index. */ 9 | case Index = 1 10 | /** Only include status deltas between the index and the working directory. */ 11 | case WorkingDirectory = 2 12 | } 13 | -------------------------------------------------------------------------------- /Gift/Status/StatusDelta/Options/StatusDeltaOptions+C.swift: -------------------------------------------------------------------------------- 1 | extension StatusDeltaOptions { 2 | /** 3 | Returns a C struct initialized with this options instance's values. 4 | Used by libgit2 functions. 5 | */ 6 | internal var cOptions: git_status_options { 7 | return git_status_options( 8 | version: 1, 9 | show: git_status_show_t(between.rawValue), 10 | flags: UInt32(behavior.rawValue), 11 | pathspec: git_strarray(strings: nil, count: 0) 12 | ) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Gift/Status/StatusDelta/Options/StatusDeltaOptions.swift: -------------------------------------------------------------------------------- 1 | /** Options for enumerating status deltas. */ 2 | public struct StatusDeltaOptions { 3 | /** Indicates what should be compared when enumerating file status deltas. */ 4 | public let between: StatusDeltaBetween 5 | 6 | /** A set of options to customize the behavior of status delta enumeration. */ 7 | public let behavior: StatusDeltaBehavior 8 | 9 | /** 10 | A list of path or path patterns to files that should be included in the 11 | status delta enumeration. If none are provided, all files in the repository 12 | (excluding those specifically excluded by the `behavior` options) are enumerated. 13 | */ 14 | public let path: [String] 15 | 16 | public init( 17 | between: StatusDeltaBetween = StatusDeltaBetween.All, 18 | behavior: StatusDeltaBehavior = StatusDeltaBehavior.IncludeIgnored | StatusDeltaBehavior.IncludeUntracked | StatusDeltaBehavior.RecurseUntrackedDirectories | StatusDeltaBehavior.DetectRenamesBetweenHeadAndIndex | StatusDeltaBehavior.DetectRenamesBetweenIndexAndWorkingDirectory | StatusDeltaBehavior.DetectRenamesAmongRewrittenFiles, 19 | path: [String] = [] 20 | ) { 21 | self.between = between 22 | self.behavior = behavior 23 | self.path = path 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Gift/Status/StatusDelta/Status.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The status of an entry in a particular snapshot. 3 | A `StatusDelta` represents the difference in an entry between two 4 | snapshots by providing the entry's `Status` in the old snapshot, 5 | as well as its `Status` in the new snapshot. 6 | */ 7 | public struct Status { 8 | /** 9 | The object ID of the entry. If the entry represents the absent side of 10 | a delta, such as the `oldStatus` of a delta of type `StatusDeltaType.Added`, 11 | then the object ID will be invalid. This will also be indicated by the absence 12 | of `StatusDescriptorSet.ValidID` on this `Status` struct's descriptors. 13 | */ 14 | internal let objectID: git_oid 15 | 16 | /** The path to the entry relative to the working directory. */ 17 | public let path: String 18 | 19 | /** The size of the entry in bytes. */ 20 | public let size: UInt 21 | 22 | /** 23 | A set of descriptors for this status object, used to indicate whether 24 | it refers to binary or text, and whether it has a valid ID. 25 | */ 26 | public let descriptors: StatusDescriptorSet 27 | 28 | /** The file mode of the entry. */ 29 | public let mode: mode_t 30 | 31 | internal init(cDiffFile: git_diff_file) { 32 | objectID = cDiffFile.id 33 | 34 | // Crashes if the git_diff_file path is NULL. 35 | // Not sure when, if ever, this is the case. 36 | path = String.fromCString(cDiffFile.path)! 37 | 38 | size = UInt(cDiffFile.size) 39 | descriptors = StatusDescriptorSet(rawValue: UInt(cDiffFile.flags)) 40 | mode = cDiffFile.mode 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Gift/Status/StatusDelta/StatusDelta.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The difference in an entry between one snapshot and another. 3 | For example, the difference between an entry in HEAD and in the index, 4 | or between an entry in the index and the working directory. 5 | 6 | In the case of this representing the difference between HEAD and the index, 7 | `oldStatus` would refer to the status of the entry in HEAD, and `newStatus` 8 | would refer to status of the entry in the index. 9 | */ 10 | public struct StatusDelta { 11 | /** 12 | The status of an entry in the old snapshot. 13 | For example, when comparing the status of an entry between HEAD and the index, 14 | this would refer to the status of the entry in HEAD. 15 | */ 16 | public let oldStatus: Status 17 | 18 | /** 19 | The status of an entry in the new snapshot. 20 | For example, when comparing the status of an entry between HEAD and the index, 21 | this would refer to the status of the entry in the index. 22 | */ 23 | public let newStatus: Status 24 | 25 | /** 26 | The type of difference between the old and new snapshots. 27 | For example, when comparing the status of an entry between the index and working 28 | directory, and the entry has been modified in the working directory only, this 29 | would have a value of `StatusDeltaType.Modified`. 30 | */ 31 | public let type: StatusDeltaType 32 | 33 | /** 34 | A value indicating how similar the old and new snapshots are, represented 35 | by a number between 0 and 1. Note, however, that this value is only relevant 36 | if the type of this status delta is `.Copied` or `.Renamed`. For all other 37 | types, this value is always zero. 38 | */ 39 | public let similarity: Double 40 | 41 | internal init(cDiffDelta: git_diff_delta) { 42 | oldStatus = Status(cDiffFile: cDiffDelta.old_file) 43 | newStatus = Status(cDiffFile: cDiffDelta.new_file) 44 | type = StatusDeltaType(rawValue: cDiffDelta.status.value)! 45 | similarity = Double(cDiffDelta.similarity)/100.0 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Gift/Status/StatusDelta/StatusDeltaType.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The type of difference in an entry between one snapshot and another. 3 | */ 4 | public enum StatusDeltaType: UInt32 { 5 | /** No changes have been made to the entry. */ 6 | case Unmodified = 0 7 | /** The entry does not exist in the old version. */ 8 | case Added = 1 9 | /** The entry does not exist in the new version. */ 10 | case Deleted = 2 11 | /** The entry's content has been changed between the old and new versions. */ 12 | case Modified = 3 13 | /** The entry's content has been renamed between the old and new versions. */ 14 | case Renamed = 4 15 | /** The entry was copied from another old entry. */ 16 | case Copied = 5 17 | /** The entry is ignored in the working directory. */ 18 | case Ignored = 6 19 | /** The entry is untracked in the working directory. */ 20 | case Untracked = 7 21 | /** The type of the entry has been changed between the old and new versions. */ 22 | case TypeChanged = 8 23 | /** The entry is unreadable. */ 24 | case Unreadable = 9 25 | } 26 | -------------------------------------------------------------------------------- /Gift/Status/StatusDelta/StatusDeltas.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A pair of status deltas used to represent the deltas between 3 | HEAD and the index, and between the index and the working directory. 4 | */ 5 | public struct StatusDeltas { 6 | /** 7 | The status delta between HEAD and the index. 8 | If there is no delta, and `StatusDeltaBehavior.IncludeUnmodified` 9 | is not enabled, this is nil. 10 | */ 11 | public let headToIndex: StatusDelta? 12 | 13 | /** 14 | The status delta between the index and the working directory. 15 | If there is no delta, and `StatusDeltaBehavior.IncludeUnmodified` 16 | is not enabled, this is nil. 17 | */ 18 | public let indexToWorkingDirectory: StatusDelta? 19 | 20 | internal init(cEntry: UnsafePointer) { 21 | if cEntry.memory.head_to_index != nil { 22 | headToIndex = StatusDelta(cDiffDelta: cEntry.memory.head_to_index.memory) 23 | } 24 | if cEntry.memory.index_to_workdir != nil { 25 | indexToWorkingDirectory = StatusDelta(cDiffDelta: cEntry.memory.index_to_workdir.memory) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Gift/Status/StatusDelta/StatusDescriptorSet.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A set of descriptors for a status object, used to indicate whether 3 | it is binary, text, has a valid ID, and so on. 4 | */ 5 | public struct StatusDescriptorSet : RawOptionSetType, BooleanType { 6 | private let value: UInt = 0 7 | 8 | // MARK: NilLiteralConvertible Protocol Methods 9 | 10 | public init(nilLiteral: ()) { } 11 | 12 | // MARK: _RawOptionSetType Protocol Methods 13 | 14 | public init(rawValue value: UInt) { 15 | self.value = value 16 | } 17 | 18 | // MARK: RawRepresentable Protocol Methods 19 | 20 | public var rawValue: UInt { 21 | return value 22 | } 23 | 24 | // MARK: BitwiseOperationsType Protocol Methods 25 | 26 | public static var allZeros: StatusDescriptorSet { 27 | return self(rawValue: 0) 28 | } 29 | 30 | // MARK: BooleanType Protocol Methods 31 | 32 | public var boolValue: Bool { 33 | return value != 0 34 | } 35 | 36 | // MARK: Enum Values 37 | 38 | /** The entry is treated as binary data. */ 39 | public static var Binary: StatusDescriptorSet { return self(rawValue: 0b1) } 40 | 41 | /** The entry is treated as text data. */ 42 | public static var NotBinary: StatusDescriptorSet { return self(rawValue: 0b10) } 43 | 44 | /** The ID value of the entry is known to be valid. */ 45 | public static var ValidID: StatusDescriptorSet { return self(rawValue: 0b100) } 46 | } 47 | -------------------------------------------------------------------------------- /Gift/String/String+GitStrArray.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | /** 5 | Converts each of the strings in a git_strarray to a Swift string, 6 | then returns a list of those strings. If any of the strings cannot 7 | be converted, returns an error indicating what went wrong. 8 | Used internally by Gift. 9 | */ 10 | internal func strings(gitStrArray: git_strarray) -> Result<[String], NSError> { 11 | var allStrings: [String] = [] 12 | for i in 0.., cRepository: COpaquePointer) -> Result { 15 | var out = COpaquePointer.null() 16 | let errorCode = git_tag_lookup(&out, cRepository, objectID) 17 | if errorCode == GIT_OK.value { 18 | return success(Tag(cTag: out)) 19 | } else { 20 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_tag_lookup")) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Gift/Tag/Tag.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | /** 5 | Tags represent specific points in history that are important. 6 | Git uses two kinds of tags: 7 | 8 | 1. Lightweight tags are simply named references to specific 9 | commits. They're like branches that never change. 10 | 2. Annotated tags are Git objects in their own right. In 11 | addition to a simple name, they have a message and information 12 | on their author. They're like a commit with no entries. 13 | 14 | This object represents annotated tags. 15 | */ 16 | public class Tag { 17 | internal let cTag: COpaquePointer 18 | 19 | internal init(cTag: COpaquePointer) { 20 | self.cTag = cTag 21 | } 22 | 23 | deinit { 24 | git_tag_free(cTag) 25 | } 26 | } 27 | 28 | public extension Tag { 29 | /** 30 | Returns the name of the tag, or a failure indicating what 31 | may have gone wrong when retrieving the tag name. 32 | */ 33 | public var name: Result { 34 | let tagName = git_tag_name(cTag) 35 | if let nameString = String.fromCString(tagName) { 36 | return success(nameString) 37 | } else { 38 | let description = "An error occurred when attempting to convert tag name \(tagName) " 39 | + "provided by git_tag_name to a String." 40 | return failure(NSError.giftError(.StringConversionFailure, description: description)) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Gift/Tree/Tree+Commit.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | public extension Tree { 5 | /** 6 | Creates a commit object based on the tree. 7 | 8 | :param: message The commit message. 9 | :param: updateReference The name of the reference that will be updated to point 10 | to the new commit. Use "HEAD" to update the HEAD of the 11 | current branch and make it point to this commit. If the 12 | reference doesn't exist yet, it will be created. If it 13 | does exist, the first parent must be the tip of this branch. 14 | :param: author The author's signature for the commit. 15 | :param: committer The committer's signature for the commit. If nil, the author's 16 | signature will be used as the committer's signature as well. 17 | :param: parents The parents of this commit. This may be an empty array if creating the 18 | first commit in a repository. 19 | :returns: The result of the commit operation: either the newly created commit object, 20 | or a failure indicating what went wrong. 21 | */ 22 | public func commit(message: String, updateReference: String = "HEAD", author: Signature, committer: Signature? = nil, parents: [Commit] = []) -> Result { 23 | var out = UnsafeMutablePointer.alloc(1) 24 | var authorSignature = author.cSignature 25 | var committerSignature = committer?.cSignature ?? author.cSignature 26 | var cParents = parents.map { $0.cCommit } 27 | let errorCode = git_commit_create( 28 | out, 29 | cRepository, 30 | updateReference, 31 | &authorSignature, 32 | &committerSignature, 33 | "UTF-8", 34 | message, 35 | cTree, 36 | UInt(countElements(cParents)), 37 | &cParents 38 | ) 39 | 40 | if errorCode == GIT_OK.value { 41 | let commit = Commit.lookup(out, cRepository: cRepository) 42 | out.dealloc(1) 43 | return commit 44 | } else { 45 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_commit_create")) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Gift/Tree/Tree+ObjectID.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LlamaKit 3 | 4 | internal extension Tree { 5 | /** 6 | Looks up a tree object with the given object ID in the given repository. 7 | 8 | :param: objectID The object ID of the tree object. 9 | :param: cRepository An opaque C struct representing a repository that 10 | the tree object ID belongs to. 11 | :returns: The result of the lookup: either a tree object, or a failure 12 | indicating what went wrong. 13 | */ 14 | internal class func lookup(objectID: UnsafeMutablePointer, cRepository: COpaquePointer) -> Result { 15 | var out = COpaquePointer.null() 16 | let errorCode = git_tree_lookup(&out, cRepository, objectID) 17 | if errorCode == GIT_OK.value { 18 | return success(Tree(cTree: out)) 19 | } else { 20 | return failure(NSError.libGit2Error(errorCode, libGit2PointOfFailure: "git_tree_lookup")) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Gift/Tree/Tree+Repository.swift: -------------------------------------------------------------------------------- 1 | internal extension Tree { 2 | /** 3 | Returns a pointer to the C struct representing 4 | the repository this tree belongs to. 5 | 6 | This pointer is not retained here, and so cannot 7 | be used to initialize a Repository object (which 8 | would call free on the pointer when it's deinitialized). 9 | */ 10 | internal var cRepository: COpaquePointer { 11 | return git_tree_owner(cTree) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Gift/Tree/Tree.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A tree object represents directory information. It records blob identifiers, 3 | path names, and a bit of metadata for all the files in a directory. 4 | It can also recursively reference other (sub)tree objects and thus build a 5 | complete hierarchy of files and subdirectories. 6 | */ 7 | public class Tree { 8 | internal let cTree: COpaquePointer 9 | 10 | internal init(cTree: COpaquePointer) { 11 | self.cTree = cTree 12 | } 13 | 14 | deinit { 15 | git_tree_free(cTree) 16 | } 17 | } 18 | 19 | public extension Tree { 20 | /** 21 | Returns the number of entries listed in a tree. 22 | */ 23 | public var entryCount: UInt { 24 | return git_tree_entrycount(cTree) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Gift/URL/NSURL+String.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal extension NSURL { 4 | /** 5 | Returns a string based on the type of URL. 6 | For remote URLs, this returns the absolute string for that URL. 7 | For file reference URLs, this returns the absolute path of that URL. 8 | Used internally by Gift. 9 | */ 10 | internal var string: String? { 11 | if isFileReferenceURL() { 12 | return path 13 | } else { 14 | return absoluteString 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /GiftTests/Commit/Commit+BranchSpec.swift: -------------------------------------------------------------------------------- 1 | import Gift 2 | import LlamaKit 3 | import Quick 4 | import Nimble 5 | 6 | class Commit_BranchSpec: QuickSpec { 7 | override func spec() { 8 | describe("createBranch") { 9 | var repository: Result! 10 | var commit: Result! 11 | beforeEach { 12 | repository = openFixturesRepository("Commit+BranchSpec_OneBranchBesidesMaster") 13 | commit = repository.map { $0.commits().array[0] } 14 | } 15 | 16 | context("when a branch doesn't already exist with the given name") { 17 | it("creates a new branch") { 18 | expect(commit.flatMap { $0.createBranch("brownsville") }.flatMap { $0.name }) 19 | .to(haveSucceeded("refs/heads/brownsville")) 20 | } 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /GiftTests/Commit/Commit+MergeSpec.swift: -------------------------------------------------------------------------------- 1 | import Gift 2 | import LlamaKit 3 | import Quick 4 | import Nimble 5 | 6 | class Commit_MergeSpec: QuickSpec { 7 | override func spec() { 8 | describe("merge") { 9 | var master: Result! 10 | var repository: Result! 11 | 12 | beforeEach { 13 | repository = openFixturesRepository("Commit+MergeSpec_OneBranchAheadOfMaster") 14 | master = repository.flatMap { $0.lookupBranch("master") }.flatMap { $0.commit } 15 | } 16 | 17 | it("merges the commit") { 18 | let commitToMerge = repository.flatMap { $0.lookupBranch("two") }.flatMap { $0.commit } 19 | let mergedIndex: Result = commitToMerge.flatMap { (toMerge: Commit) in 20 | return master.flatMap { $0.merge(toMerge) } 21 | } 22 | expect(mergedIndex.map { $0.entryCount }).to(haveSucceeded(2)) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /GiftTests/Fixtures/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !*.zip 4 | 5 | -------------------------------------------------------------------------------- /GiftTests/Fixtures/Fixtures.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modocache/Gift/33d51b2f0d1e718121999019a1c827ec2512e168/GiftTests/Fixtures/Fixtures.zip -------------------------------------------------------------------------------- /GiftTests/GiftTests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "SSZipArchive.h" 2 | -------------------------------------------------------------------------------- /GiftTests/Helpers/FilePathHelpers.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Gift 3 | import LlamaKit 4 | 5 | /** 6 | Creates a directory in the user's temporary directory. 7 | 8 | :returns: The URL of the newly created directory, or nil if the directory 9 | could not be created. Since failing to create a temporary 10 | directory represents a fatal error for the test suite, no attempt 11 | is made to recover from such a condition; the URL is unwrapped 12 | when accessed and the test suite will crash. 13 | */ 14 | internal func temporaryDirectoryURL() -> NSURL! { 15 | let tempDirectoryPath = NSTemporaryDirectory() 16 | .stringByAppendingPathComponent("com.libgit2.gift") 17 | .stringByAppendingPathComponent(NSProcessInfo.processInfo().globallyUniqueString) 18 | 19 | var error: NSError? 20 | NSFileManager.defaultManager() 21 | .createDirectoryAtPath(tempDirectoryPath, withIntermediateDirectories: true, attributes: nil, error: &error) 22 | 23 | if error == nil { 24 | return NSURL(fileURLWithPath: tempDirectoryPath)! 25 | } else { 26 | return nil 27 | } 28 | } 29 | 30 | /** 31 | Unzips the test fixture repositories directory and opens the repository 32 | with the given name. 33 | 34 | :param: name The name of the repository to open. This should be a directory on the top level 35 | of "GiftTests/Fixtures/Fixtures.zip". 36 | :returns: The result of opening the repository: either a Repository object, or a failure 37 | indicating what went wrong. 38 | */ 39 | internal func openFixturesRepository(name: String) -> Result { 40 | let source = NSBundle(forClass: TestBundleLocator.classForCoder()).pathForResource("Fixtures", ofType: "zip") 41 | let destination = temporaryDirectoryURL() 42 | 43 | // system() and NSTask() are not available when running tests in 44 | // the iOS simulator, so we use zlib (wrapped by Sam Soffes) directly 45 | // instead of shelling out to /usr/bin/zip. 46 | SSZipArchive.unzipFileAtPath(source!, toDestination: destination.path!) 47 | 48 | return openRepository(destination 49 | .URLByAppendingPathComponent("Fixtures") 50 | .URLByAppendingPathComponent(name)) 51 | } 52 | 53 | /** 54 | A class used to locate the test bundle. Used by the openFixturesRepository() function. 55 | */ 56 | private class TestBundleLocator: NSObject {} 57 | -------------------------------------------------------------------------------- /GiftTests/Helpers/Result+Compact.swift: -------------------------------------------------------------------------------- 1 | import LlamaKit 2 | 3 | /** 4 | Given an array of results, return an array of successful values, 5 | discarding any results that were unsuccessful. 6 | */ 7 | internal func compact(results: [Result]) -> [T] { 8 | var compacted: [T] = [] 9 | results.map { $0.map { compacted.append($0) } } 10 | return compacted 11 | } 12 | -------------------------------------------------------------------------------- /GiftTests/Helpers/SignalProducer+Array.swift: -------------------------------------------------------------------------------- 1 | import ReactiveCocoa 2 | 3 | internal extension SignalProducer { 4 | /** Reduces all events in a signal to an array. */ 5 | internal var array: [T] { 6 | var elements: [T] = [] 7 | start(next: { (element) in 8 | elements.append(element) 9 | }) 10 | return elements 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GiftTests/Index/Index+TreeSpec.swift: -------------------------------------------------------------------------------- 1 | import Gift 2 | import LlamaKit 3 | import Quick 4 | import Nimble 5 | 6 | class Index_TreeSpec: QuickSpec { 7 | override func spec() { 8 | var repository: Result! 9 | var index: Result! 10 | beforeEach { 11 | repository = openFixturesRepository("IndexSpec_EntryCount") 12 | index = repository.flatMap { $0.index } 13 | } 14 | 15 | describe("writeTree") { 16 | it("returns a tree") { 17 | expect(index.flatMap { $0.writeTree() }.map { $0.entryCount }).to(haveSucceeded(3)) 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /GiftTests/Index/IndexSpec.swift: -------------------------------------------------------------------------------- 1 | import Gift 2 | import LlamaKit 3 | import Quick 4 | import Nimble 5 | 6 | class IndexSpec: QuickSpec { 7 | override func spec() { 8 | var repository: Result! 9 | var index: Result! 10 | 11 | describe("entryCount") { 12 | beforeEach { 13 | repository = openFixturesRepository("IndexSpec_EntryCount") 14 | index = repository.flatMap { $0.index } 15 | } 16 | 17 | it("returns the number of entries in the index") { 18 | expect(index.map { $0.entryCount }).to(haveSucceeded(3)) 19 | } 20 | } 21 | 22 | describe("add") { 23 | context("when there are unstaged entries") { 24 | beforeEach { 25 | repository = openFixturesRepository("IndexSpec_Add_TwoUnstagedEntries") 26 | index = repository.flatMap { $0.index } 27 | } 28 | 29 | it("adds all of them using the default parameters") { 30 | expect(index.map { $0.entryCount }).to(haveSucceeded(1)) 31 | expect(index.flatMap { $0.add() }.map { $0.entryCount }).to(haveSucceeded(3)) 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /GiftTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | io.gift.$(PRODUCT_NAME:rfc1034identifier) 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 | -------------------------------------------------------------------------------- /GiftTests/Matchers/ResultMatchers.swift: -------------------------------------------------------------------------------- 1 | import Nimble 2 | import LlamaKit 3 | 4 | public func haveSucceeded() -> MatcherFunc> { 5 | return MatcherFunc { actualExpression, failureMessage in 6 | failureMessage.postfixMessage = "have succeeded" 7 | if let result = actualExpression.evaluate() { 8 | return result.isSuccess 9 | } else { 10 | return false 11 | } 12 | } 13 | } 14 | 15 | public func haveSucceeded(value: T) -> MatcherFunc> { 16 | return MatcherFunc { actualExpression, failureMessage in 17 | failureMessage.postfixMessage = "have succeeded with a value of \(value)" 18 | if let result = actualExpression.evaluate() { 19 | switch result { 20 | case .Success(let box): 21 | return box.unbox == value 22 | case .Failure: 23 | return false 24 | } 25 | } else { 26 | return false 27 | } 28 | } 29 | } 30 | 31 | public func haveFailed(domain: String? = nil, localizedDescription: String? = nil) -> MatcherFunc> { 32 | return MatcherFunc { actualExpression, failureMessage in 33 | failureMessage.postfixMessage = "have failed" 34 | if let result = actualExpression.evaluate() { 35 | switch result { 36 | case .Success: 37 | return false 38 | case .Failure(let error): 39 | var allEqualityChecksAreTrue = true 40 | if let someDomain = domain { 41 | failureMessage.postfixMessage = "\(failureMessage.postfixMessage), with a domain of '\(someDomain)'" 42 | allEqualityChecksAreTrue = allEqualityChecksAreTrue && error.unbox.domain == someDomain 43 | } 44 | if let description = localizedDescription { 45 | failureMessage.postfixMessage = "\(failureMessage.postfixMessage), with the description '\(description)'" 46 | allEqualityChecksAreTrue = allEqualityChecksAreTrue && error.unbox.localizedDescription == description 47 | } 48 | return allEqualityChecksAreTrue 49 | } 50 | } else { 51 | return false 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /GiftTests/Reference/Reference+CommitSpec.swift: -------------------------------------------------------------------------------- 1 | import Gift 2 | import LlamaKit 3 | import Quick 4 | import Nimble 5 | 6 | class Reference_CommitSpec: QuickSpec { 7 | override func spec() { 8 | var repository: Result! 9 | var reference: Result! 10 | beforeEach { 11 | repository = openFixturesRepository("Spec_EmptyRepository") 12 | reference = repository.flatMap { $0.headReference } 13 | } 14 | 15 | describe("commit") { 16 | it("returns the commit the reference is referring to") { 17 | expect(reference.flatMap { $0.commit }.flatMap { $0.message }).to(haveSucceeded("Initial commit.\n")) 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /GiftTests/Reference/Reference+TagSpec.swift: -------------------------------------------------------------------------------- 1 | import Gift 2 | import LlamaKit 3 | import Quick 4 | import Nimble 5 | 6 | class Reference_TagSpec: QuickSpec { 7 | override func spec() { 8 | var repository: Result! 9 | var reference: Result! 10 | beforeEach { 11 | repository = openFixturesRepository("Spec_EmptyRepository") 12 | reference = repository.flatMap { $0.headReference } 13 | } 14 | 15 | describe("tag") { 16 | it("creates an annotated tag with the given name") { 17 | expect(reference.flatMap { $0.tag( 18 | "carroll-gardens", 19 | message: "bushwick", 20 | signature: Signature(name: "dumbo", email: "bedford-stuyvesant") 21 | ) }.flatMap { $0.name }).to(haveSucceeded("carroll-gardens")) 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /GiftTests/Reference/ReferenceSpec.swift: -------------------------------------------------------------------------------- 1 | import Gift 2 | import LlamaKit 3 | import Quick 4 | import Nimble 5 | 6 | class ReferenceSpec: QuickSpec { 7 | override func spec() { 8 | var repository: Result! 9 | var reference: Result! 10 | beforeEach { 11 | repository = openFixturesRepository("Spec_EmptyRepository") 12 | reference = repository.flatMap { $0.headReference } 13 | } 14 | 15 | describe("name") { 16 | context("when the reference has a valid name") { 17 | it("returns a successful result with the reference name") { 18 | expect(reference.flatMap { $0.name }).to(haveSucceeded("refs/heads/master")) 19 | } 20 | } 21 | 22 | xcontext("when the reference doesn't have a valid name") { 23 | // Not sure under when, if ever, this may occur. 24 | it("returns a failing result") {} 25 | } 26 | } 27 | 28 | describe("SHA") { 29 | it("returns the SHA") { 30 | expect(reference.flatMap { $0.SHA }) 31 | .to(haveSucceeded("8dcbbd29ec495e2dbcaa8f97e9a31af9cb4ae7cd")) 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /GiftTests/Repository/Repository+BranchSpec.swift: -------------------------------------------------------------------------------- 1 | import Gift 2 | import LlamaKit 3 | import ReactiveCocoa 4 | import Quick 5 | import Nimble 6 | 7 | private extension Repository { 8 | /** 9 | Maps each branch reference to a name and returns a list of names. 10 | Used for testing. 11 | 12 | :param: type The type of branches that should be enumerated over. 13 | :returns: A list of branch names in the order in which they were enumerated. 14 | */ 15 | private func branchNames(type: BranchType) -> [String] { 16 | let branchReferences = branches(type: type).array 17 | return compact(branchReferences.map { $0.name }) 18 | } 19 | } 20 | 21 | class Repository_BranchSpec: QuickSpec { 22 | override func spec() { 23 | describe("branches") { 24 | var repository: Result! 25 | 26 | context("when the repository contains several local branches") { 27 | beforeEach { 28 | repository = openFixturesRepository("Repository+BranchSpec_ThreeLocalBranches") 29 | } 30 | 31 | it("iterates over all of them") { 32 | expect(repository.map { $0.branchNames(.Local) as NSArray }).to(haveSucceeded([ 33 | "refs/heads/master", 34 | "refs/heads/one", 35 | "refs/heads/three", 36 | "refs/heads/two" 37 | ])) 38 | } 39 | } 40 | 41 | context("when the repository contains local and remote branches") { 42 | beforeEach { 43 | repository = openFixturesRepository("TestGitRepository") 44 | } 45 | 46 | it("iterates over remote branches when filtering on remote branches") { 47 | expect(repository.map { $0.branchNames(.Remote) as NSArray }).to(haveSucceeded([ 48 | "refs/remotes/origin/HEAD", 49 | "refs/remotes/origin/first-merge", 50 | "refs/remotes/origin/master", 51 | "refs/remotes/origin/no-parent", 52 | ])) 53 | } 54 | 55 | it("iterates over all branches when not filtering") { 56 | expect(repository.map { $0.branchNames(.All) as NSArray }).to(haveSucceeded([ 57 | "refs/heads/master", 58 | "refs/remotes/origin/HEAD", 59 | "refs/remotes/origin/first-merge", 60 | "refs/remotes/origin/master", 61 | "refs/remotes/origin/no-parent", 62 | ])) 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /GiftTests/Repository/Repository+CommitSpec.swift: -------------------------------------------------------------------------------- 1 | import Gift 2 | import LlamaKit 3 | import Quick 4 | import Nimble 5 | 6 | class Repository_CommitSpec: QuickSpec { 7 | override func spec() { 8 | var repository: Result! 9 | beforeEach { 10 | repository = openFixturesRepository("Repository+CommitSpec_FourCommits") 11 | } 12 | 13 | describe("commits") { 14 | it("enumerates the commits in the repository in the given sorting order") { 15 | expect(repository.map { countElements($0.commits().array) }).to(haveSucceeded(4)) 16 | } 17 | } 18 | 19 | describe("reset") { 20 | it("resets HEAD to the given commit") { 21 | // First, make sure HEAD is set to the fourth commit. 22 | expect(repository 23 | .flatMap { $0.headReference } 24 | .flatMap { $0.commit } 25 | .flatMap { $0.message } 26 | ).to(haveSucceeded("Fourth commit.\n")) 27 | 28 | // Grab the first commit. 29 | let firstCommit = repository 30 | .map { $0.commits(sorting: CommitSorting.Time | CommitSorting.Reverse).array.first! } 31 | 32 | // Reset HEAD to the first commit, then grab that commit. 33 | let resetHeadCommit: Result = firstCommit.flatMap { (commit: Commit) in 34 | return repository 35 | .flatMap { $0.reset(commit, resetType: .Hard) } 36 | .flatMap { $0.headReference } 37 | .flatMap { $0.commit } 38 | } 39 | 40 | // Assert that the commit at HEAD is the first commit in the repository. 41 | expect(resetHeadCommit.flatMap { $0.message }).to(haveSucceeded("First commit.\n")) 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /GiftTests/Repository/Repository+ReferenceSpec.swift: -------------------------------------------------------------------------------- 1 | import Gift 2 | import LlamaKit 3 | import Quick 4 | import Nimble 5 | 6 | class Repository_ReferenceSpec: QuickSpec { 7 | override func spec() { 8 | var repository: Result! 9 | beforeEach { 10 | repository = openFixturesRepository("Repository+ReferenceSpec_OneBranchBesidesMaster") 11 | } 12 | 13 | describe("setHead") { 14 | context("when the reference name is resolved to an existing reference") { 15 | it("sets the head to that reference") { 16 | let message = repository 17 | .flatMap { $0.setHead("refs/heads/development") } 18 | .flatMap { $0.headReference } 19 | .flatMap { $0.commit } 20 | .flatMap { $0.message } 21 | expect(message).to(haveSucceeded("Brooklyn Heights\n")) 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /GiftTests/Repository/Repository+RemoteSpec.swift: -------------------------------------------------------------------------------- 1 | import Gift 2 | import Foundation 3 | import LlamaKit 4 | import Quick 5 | import Nimble 6 | 7 | class Repository_RemoteSpec: QuickSpec { 8 | override func spec() { 9 | var repository: Result! 10 | beforeEach { 11 | let remoteURL = temporaryDirectoryURL().URLByAppendingPathComponent("weeksville") 12 | initializeEmptyRepository(remoteURL) 13 | let destinationURL = temporaryDirectoryURL().URLByAppendingPathComponent("ditmas-park") 14 | repository = cloneRepository(remoteURL, destinationURL) 15 | } 16 | 17 | describe("createRemote") { 18 | context("when given a valid URL") { 19 | var url: NSURL! 20 | beforeEach { 21 | url = temporaryDirectoryURL().URLByAppendingPathComponent("east-williamsburg") 22 | } 23 | 24 | context("but an invalid name") { 25 | it("fails") { 26 | expect(repository.flatMap { $0.createRemote("", url: url) }) 27 | .to(haveFailed(domain: libGit2ErrorDomain)) 28 | } 29 | } 30 | 31 | context("and a valid name") { 32 | it("creates a remote") { 33 | repository.flatMap { $0.createRemote("wyckoff-heights", url: url) } 34 | expect(repository.flatMap { $0.lookupRemote("wyckoff-heights") }.flatMap { $0.url }) 35 | .to(haveSucceeded(url)) 36 | } 37 | } 38 | } 39 | } 40 | 41 | describe("lookupRemote") { 42 | context("when a remote with the given name does not exist") { 43 | it("fails") { 44 | expect(repository.flatMap { $0.lookupRemote("fiske-terrace") }) 45 | .to(haveFailed(domain: libGit2ErrorDomain)) 46 | } 47 | } 48 | 49 | context("when a remote with the given name exists") { 50 | it("returns that remote") { 51 | expect(repository.flatMap { $0.lookupRemote("origin").flatMap { $0.name } }) 52 | .to(haveSucceeded("origin")) 53 | } 54 | } 55 | } 56 | 57 | describe("remoteNames") { 58 | context("when the repository has no remotes") { 59 | it("returns an empty list") { 60 | let url = temporaryDirectoryURL().URLByAppendingPathComponent("kensington") 61 | let emptyRepository = initializeEmptyRepository(url) 62 | expect(emptyRepository.flatMap { $0.remoteNames }.map { $0 as NSArray }) 63 | .to(haveSucceeded([])) 64 | } 65 | } 66 | 67 | context("when the repository has remotes") { 68 | it("returns a list of their names") { 69 | repository.flatMap { $0.createRemote("pigtown", url: NSURL(string: "prospect-park-south")!) } 70 | expect(repository.flatMap { $0.remoteNames }.map { $0 as NSArray }) 71 | .to(haveSucceeded(["origin", "pigtown"])) 72 | } 73 | } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /GiftTests/Repository/Repository+StashSpec.swift: -------------------------------------------------------------------------------- 1 | import Gift 2 | import LlamaKit 3 | import Quick 4 | import Nimble 5 | 6 | class Repository_StashSpec: QuickSpec { 7 | override func spec() { 8 | var repository: Result! 9 | beforeEach { 10 | repository = openFixturesRepository("Repository+StashSpec_TwoFilesInIndex") 11 | } 12 | 13 | describe("stash") { 14 | it("stashes entries staged in the index into a commit") { 15 | // First, make sure there are two entries in the index. 16 | expect(repository.flatMap { $0.index }.map { $0.entryCount }).to(haveSucceeded(2)) 17 | 18 | // Stash the changes into a commit object. 19 | let commit = repository.map { $0.stash("gowanus") } 20 | 21 | // Assert that the index no longer contains any staged entries, since they were stashed. 22 | expect(repository.flatMap { $0.index }.map { $0.entryCount }).to(haveSucceeded(0)) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /GiftTests/Repository/Repository+StatusSpec.swift: -------------------------------------------------------------------------------- 1 | import Gift 2 | import LlamaKit 3 | import Quick 4 | import Nimble 5 | 6 | class Repository_StatusSpec: QuickSpec { 7 | override func spec() { 8 | var repository: Result! 9 | beforeEach { 10 | repository = openFixturesRepository("Repository+StatusSpec_AllStatusDeltas") 11 | } 12 | 13 | describe("status (enumeration)") { 14 | it("reports each delta") { 15 | // Arrange: Prepare data structures for storing deltas. 16 | var headToIndexes = FileDeltas() 17 | var indexToWorkingDirectories = FileDeltas() 18 | 19 | // Act: Iterate over status deltas, storing them in data structures. 20 | if repository.isSuccess { 21 | for deltas in (repository.value!.status().array) { 22 | headToIndexes.addDelta(deltas.headToIndex) 23 | indexToWorkingDirectories.addDelta(deltas.indexToWorkingDirectory) 24 | } 25 | } 26 | 27 | // Assert: Deltas are correctly reported. 28 | expect(headToIndexes.deltas).to(equal([ 29 | "head_to_index_added.txt": StatusDeltaType.Added, 30 | "head_to_index_deleted.txt": StatusDeltaType.Deleted, 31 | "head_to_index_modified.txt": StatusDeltaType.Modified, 32 | "head_to_index_modified+index_to_working_directory_modified.txt": StatusDeltaType.Modified, 33 | "head_to_index_named.txt": StatusDeltaType.Renamed, 34 | ])) 35 | expect(indexToWorkingDirectories.deltas).to(equal([ 36 | ".DS_Store": StatusDeltaType.Ignored, 37 | "index_to_working_directory_ignored.txt": StatusDeltaType.Ignored, 38 | "index_to_working_directory_modified.txt": StatusDeltaType.Modified, 39 | "head_to_index_modified+index_to_working_directory_modified.txt": StatusDeltaType.Modified, 40 | "index_to_working_directory_untracked.txt": StatusDeltaType.Untracked, 41 | ])) 42 | } 43 | } 44 | 45 | describe("status (individual file)") { 46 | context("when the file doesn't exist") { 47 | it("returns a failing result") { 48 | expect(repository.flatMap { $0.status("does-not-exist") }) 49 | .to(haveFailed(domain: libGit2ErrorDomain)) 50 | } 51 | } 52 | 53 | context("when the file exists and has a single status") { 54 | it("returns a successful result with that status") { 55 | expect(repository.flatMap { $0.status("head_to_index_unmodified.txt") }) 56 | .to(haveSucceeded(EntryStatus.Current)) 57 | } 58 | } 59 | 60 | context("when the file exists and has a set of statuses") { 61 | it("returns a successful result with multiple statuses") { 62 | expect(repository.flatMap { $0.status("head_to_index_modified+index_to_working_directory_modified.txt") }) 63 | .to(haveSucceeded(EntryStatus.IndexModified | EntryStatus.WorkingDirectoryModified)) 64 | } 65 | } 66 | } 67 | 68 | describe("shouldIgnore") { 69 | context("when the file doesn't exist") { 70 | it("returns a successful result with a value of false") { 71 | expect(repository.flatMap { $0.shouldIgnore("does-not-exist") }) 72 | .to(haveSucceeded(false)) 73 | } 74 | } 75 | 76 | context("when the file exists and is not ignored") { 77 | it("returns a successful result with a value of false") { 78 | expect(repository.flatMap { $0.shouldIgnore("head_to_index_added.txt") }) 79 | .to(haveSucceeded(false)) 80 | } 81 | } 82 | 83 | context("when the file exists and is ignored") { 84 | it("returns a successful result with a value of true") { 85 | expect(repository.flatMap { $0.shouldIgnore("index_to_working_directory_ignored.txt") }) 86 | .to(haveSucceeded(true)) 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | // MARK: Test Helpers 94 | 95 | private struct FileDeltas { 96 | private var deltas: [String: StatusDeltaType] = [:] 97 | private mutating func addDelta(statusDelta: StatusDelta?) { 98 | if let delta = statusDelta { 99 | deltas[delta.oldStatus.path] = delta.type 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /GiftTests/Repository/Repository+TagSpec.swift: -------------------------------------------------------------------------------- 1 | import Gift 2 | import ReactiveCocoa 3 | import LlamaKit 4 | import Quick 5 | import Nimble 6 | 7 | class Repository_TagSpec: QuickSpec { 8 | override func spec() { 9 | var repository: Result! 10 | 11 | describe("tags") { 12 | context("when the repository has tags") { 13 | beforeEach { 14 | repository = openFixturesRepository("Repository+TagSpec_ThreeTags") 15 | } 16 | 17 | it("enumerates them") { 18 | let names = repository 19 | .map { compact($0.tags().array.map { $0.name }) } 20 | .map { $0 as NSArray } 21 | expect(names).to(haveSucceeded([ 22 | "refs/tags/first", 23 | "refs/tags/second", 24 | "refs/tags/third", 25 | ])) 26 | } 27 | } 28 | } 29 | 30 | describe("tagNames") { 31 | context("when the repository has no tags") { 32 | beforeEach { 33 | repository = openFixturesRepository("Repository+TagSpec_NoTags") 34 | } 35 | 36 | it("returns an empty list") { 37 | expect(repository.flatMap { $0.tagNames() }.map { $0 as NSArray }) 38 | .to(haveSucceeded([])) 39 | } 40 | } 41 | 42 | context("when the repository has tags") { 43 | beforeEach { 44 | repository = openFixturesRepository("Repository+TagSpec_ThreeTags") 45 | } 46 | 47 | context("and no pattern is specified") { 48 | it("returns a list of all the tag names") { 49 | expect(repository.flatMap { $0.tagNames() }.map { $0 as NSArray }) 50 | .to(haveSucceeded(["first", "second", "third"])) 51 | } 52 | } 53 | 54 | context("and a pattern is specified") { 55 | it("returns a list of all tags with a name that matches the pattern") { 56 | expect(repository.flatMap { $0.tagNames(matchingPattern: "second") }.map { $0 as NSArray }) 57 | .to(haveSucceeded(["second"])) 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /GiftTests/Repository/RepositorySpec.swift: -------------------------------------------------------------------------------- 1 | import Gift 2 | import Quick 3 | import Nimble 4 | 5 | class RepositorySpec: QuickSpec { 6 | override func spec() { 7 | describe("initializeEmptyRepository") { 8 | context("with default settings") { 9 | it("initializes a repository with a working directory") { 10 | let newRepoURL = temporaryDirectoryURL().URLByAppendingPathComponent("greenpoint") 11 | let repository = initializeEmptyRepository(newRepoURL) 12 | 13 | expect(repository.flatMap { $0.gitDirectoryURL }.map { $0.path!.stringByResolvingSymlinksInPath }) 14 | .to(haveSucceeded(newRepoURL.URLByAppendingPathComponent(".git").path!.stringByResolvingSymlinksInPath)) 15 | } 16 | } 17 | } 18 | 19 | describe("cloneRepository") { 20 | var remoteURL: NSURL! 21 | var destinationURL: NSURL! 22 | 23 | context("on the local filesystem") { 24 | beforeEach { 25 | remoteURL = temporaryDirectoryURL().URLByAppendingPathComponent("park-slope") 26 | destinationURL = temporaryDirectoryURL().URLByAppendingPathComponent("sunset-park") 27 | } 28 | 29 | context("but the remote does not exist") { 30 | it("fails") { 31 | let repository = cloneRepository(remoteURL,destinationURL) 32 | let path = remoteURL.path!.stringByResolvingSymlinksInPath 33 | let faiureMessage = "Failed to resolve path '\(path)': No such file or directory" 34 | expect(repository).to(haveFailed(localizedDescription: faiureMessage)) 35 | } 36 | } 37 | 38 | context("and the remote exists") { 39 | beforeEach { 40 | let _ = initializeEmptyRepository(remoteURL) 41 | } 42 | 43 | it("is succesful") { 44 | expect(cloneRepository(remoteURL, destinationURL)).to(haveSucceeded()) 45 | } 46 | 47 | it("creates a remote named 'origin'") { 48 | let repository = cloneRepository(remoteURL, destinationURL) 49 | expect(repository.flatMap { $0.lookupRemote("origin") }).to(haveSucceeded()) 50 | } 51 | } 52 | } 53 | 54 | xcontext("from a remote URL") { 55 | beforeEach { 56 | remoteURL = NSURL(string: "git://git.libssh2.org/libssh2.git") 57 | destinationURL = temporaryDirectoryURL().URLByAppendingPathComponent("libssh2") 58 | } 59 | 60 | context("and the remote exists") { 61 | it("is succesful") { 62 | let options = CloneOptions( 63 | checkoutOptions: CheckoutOptions( 64 | strategy: CheckoutStrategy.SafeCreate, 65 | progressCallback: { (path: String!, completedSteps: UInt, totalSteps: UInt) in 66 | println("path '\(path)', " 67 | + "completedSteps '\(completedSteps)', " 68 | + "totalSteps '\(totalSteps)'") 69 | } 70 | ), 71 | remoteCallbacks: RemoteCallbacks( 72 | transportMessageCallback: { (message) in 73 | println("transportMessageCallback: \(message)") 74 | return false 75 | }, 76 | transferProgressCallback: { (progress) in 77 | println("transferProgressCallback bytes: \(progress.receivedBytes)") 78 | return false 79 | }) 80 | ) 81 | 82 | expect(cloneRepository(remoteURL, destinationURL, options: options)) 83 | .to(haveSucceeded()) 84 | } 85 | } 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /GiftTests/Tree/Tree+CommitSpec.swift: -------------------------------------------------------------------------------- 1 | import Gift 2 | import LlamaKit 3 | import Quick 4 | import Nimble 5 | 6 | class Tree_CommitSpec: QuickSpec { 7 | override func spec() { 8 | var repository: Result! 9 | var index: Result! 10 | var tree: Result! 11 | 12 | describe("commit") { 13 | context("when the commit has no parents") { 14 | beforeEach { 15 | repository = openFixturesRepository("Tree+CommitSpec_InitialCommit") 16 | index = repository.flatMap { $0.index } 17 | tree = index.flatMap { $0.writeTree() } 18 | } 19 | 20 | it("creates a new commit") { 21 | let commit = tree.flatMap { $0.commit( 22 | "cadman-plaza", 23 | author: Signature(name: "redhook", email: "fort-greene") 24 | )} 25 | expect(commit.flatMap { $0.message }).to(haveSucceeded("cadman-plaza")) 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014 Gift 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 13 | all 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 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gift (Deprecated) 2 | 3 | [![Build 4 | Status](https://travis-ci.org/modocache/Gift.svg?branch=master)](https://travis-ci.org/modocache/Gift) 5 | 6 | **This library isn't really maintained anymore. You should use [SwiftGit2](https://github.com/SwiftGit2/SwiftGit2) instead.** 7 | 8 | Gift provides Swift bindings to the 9 | [libgit2](https://github.com/libgit2/libgit2) library. 10 | 11 | That means you can clone a Git repository, list its commits, 12 | or even make a commit of your own--all from within your Swift 13 | program. 14 | 15 | For example, we can grab the latest commit message of *this* 16 | repository: 17 | 18 | ```swift 19 | let url = NSURL(fileURLWithPath: "/Users/modocache/Gift")! 20 | let latestCommitMessage = openRepository(url) 21 | .flatMap { $0.headReference } 22 | .flatMap { $0.commit } 23 | .flatMap { $0.message } 24 | ``` 25 | 26 | Gift returns result objects for any operation that might fail. The type 27 | of `lastCommitMessage` above is `Result`. If every 28 | operation succeeded, you can access the commit message `String`. But if 29 | any of the operations failed, you'll have an `NSError` that describes 30 | what went wrong. 31 | 32 | # Features 33 | 34 | You can list all branches in a repository. Gift uses ReactiveCocoa to 35 | represent a sequence of values: 36 | 37 | ```swift 38 | repository.branches().start(next: { (reference) in 39 | println("Branch name: \(reference.name)") 40 | }) 41 | ``` 42 | 43 | You can list commits as well: 44 | 45 | ```swift 46 | repository.commits().start { (commit) in 47 | println("Commit message: \(commit.message)") 48 | } 49 | ``` 50 | 51 | You can order the commits as you please, of course: 52 | 53 | ```swift 54 | let commits = repository.commits(sorting: CommitSorting.Time | CommitSorting.Reverse) 55 | ``` 56 | 57 | You can *make* a commit, too: 58 | 59 | ```swift 60 | let tree = repository.index // Grab the index for the repository 61 | .flatMap { $0.add() } // Add any modified entries to that index 62 | .flatMap { $0.writeTree() } // Grab a tree representing the changeset 63 | .flatMap { $0.commit("Zing!") } // Commit 64 | ``` 65 | 66 | Swift allows Gift to provide default parameters. If you want to use the 67 | default behavior, you can easily clone a remote repository: 68 | 69 | ```swift 70 | let remoteURL = NSURL(string: "git://git.libssh2.org/libssh2.git")! 71 | let destinationURL = NSURL(fileURLWithPath: "/Users/modocache/libssh2")! 72 | let repository = cloneRepository(remoteURL, destinationURL) 73 | ``` 74 | 75 | But you can also customize that behavior and have Gift issue download 76 | progress updates: 77 | 78 | ```swift 79 | let options = CloneOptions( 80 | checkoutOptions: CheckoutOptions( 81 | strategy: CheckoutStrategy.SafeCreate, 82 | progressCallback: { (path, completedSteps, totalSteps) in 83 | // ...do something with checkout progress. 84 | } 85 | ), 86 | remoteCallbacks: RemoteCallbacks( 87 | transportMessageCallback: { (message) in 88 | // ...do something with messages from remote, like "Compressing objects: 1% (47/4619)" 89 | }, 90 | transferProgressCallback: { (progress) in 91 | // ...do something with progress (bytes received, etc.) updates. 92 | }) 93 | ) 94 | let repository = cloneRepository(remoteURL, destinationURL, options: options) 95 | ``` 96 | 97 | # Why Not ObjectiveGit? 98 | 99 | [ObjectiveGit](https://github.com/libgit2/objective-git) is the official 100 | set of Objective-C bindings to Git. It is maintained by the same people 101 | who develop [GitHub for Mac](https://mac.github.com/), so you can be 102 | sure it's solid. And you can use it from Swift! 103 | 104 | If, however, you're willing to tolerate a less stable set of bindings, 105 | Gift utilizes features of Swift to make certain aspects of interfacing 106 | with libgit2 easy. For example, take the following code, which prints 107 | the latest commit message in a repository: 108 | 109 | ```swift 110 | let latestCommitMessage = openRepository(url) 111 | .flatMap { $0.headReference } 112 | .flatMap { $0.commit } 113 | .flatMap { $0.message } 114 | println(latestCommitMessage) // Either prints commit or error 115 | ``` 116 | 117 | Because most Gift functions return `Result` objects, and because you can 118 | chain those `Result` objects, Gift makes it easy to display precise 119 | error information. To get an equivalent level of error handling in 120 | ObjectiveGit, you'd need to write the following: 121 | 122 | ```objc 123 | NSError *repositoryError = nil; 124 | GTRepository *repository = [GTRepository repositoryWithURL:url error:&error]; 125 | if (repositoryError != nil) { 126 | NSLog(@"An error occurred: %@", repositoryError); 127 | return; 128 | } 129 | 130 | NSError *headReferenceError = nil; 131 | GTReference *headReference = [repository headReferenceWithError:&headReferenceError]; 132 | if (headReferenceError != nil) { 133 | NSLog(@"An error occurred: %@", headReferenceError); 134 | return; 135 | } 136 | 137 | NSError *lookupError = nil; 138 | GTCommit *commit = [repository lookupObjectByOID:headReference.OID 139 | objectType:GTObjectTypeCommit 140 | error:&lookupError]; 141 | if (lookupError != nil) { 142 | NSLog(@"An error occurred: %@", lookupError); 143 | return; 144 | } 145 | 146 | NSString *message = commit.message; 147 | if (message == nil) { 148 | NSLog(@"An error occurred"); 149 | } else { 150 | NSLog(@"Commit message: %@", message); 151 | } 152 | ``` 153 | 154 | As you can see, Gift requires significantly less code. ObjectiveGit is, 155 | however, far more mature. It's up to you to decide which is right for 156 | your project. 157 | 158 | # How to Build 159 | 160 | You'll need to have [homebrew](https://github.com/Homebrew/homebrew/) 161 | installed. If you don't already have CMake installed, run: 162 | 163 | ``` 164 | $ brew install cmake 165 | ``` 166 | 167 | Then, to build the dependencies for Gift, just run: 168 | 169 | ``` 170 | $ rake dependencies build 171 | ``` 172 | 173 | If it's your first time running the script, it'll take a while--it 174 | needs to build static libraries for OpenSSL, libssh2, and libgit2. 175 | And that's for both OS X _and_ iOS. 176 | 177 | ## Testing Your Changes 178 | 179 | You can test your changes by running: 180 | 181 | ``` 182 | $ rake 183 | ``` 184 | 185 | ## Build Errors 186 | 187 | If you see a non-descript error like "Cannot build module Gift", there's 188 | an extremely sophisticated workaround: first, remove all the source files from 189 | the main and test target you're trying to build. 190 | 191 | ![](https://s3.amazonaws.com/f.cl.ly/items/0M0V1y07081s2W34412w/Screen%20Shot%202015-01-22%20at%2010.09.29%20PM.png) 192 | 193 | Then, build the framework. It should work fine (magic, I know). Now that 194 | it builds, revert the removal of the files by running 195 | `git checkout -- Gift.xcodeproj`. And voila! Now everything builds fine. 196 | 197 | # How to Use Gift in Your iOS App 198 | 199 | You can find an example iOS app that uses Gift in the 200 | [`Examples`](https://github.com/Quick/Quick/tree/master/Examples) directory. Using Gift 201 | is (sort of) easy: 202 | 203 | 1. Drag `Gift.xcodeproj`, `LlamaKit.xcodeproj`, `Quick.xcodeproj`, and 204 | `Nimble.xcodeproj` into your app's workspace. 205 | 2. In the "Link Binary with Libraries" build phase of your app, link 206 | your app to Gift.framework. 207 | 3. Set the “Header Search Paths” (`HEADER_SEARCH_PATHS`) build setting to 208 | the correct path for the libgit2 headers in your project. For 209 | example, if you added the submodule to your project as 210 | `External/Gift`, you would set this build setting to 211 | `External/Gift/External/libgit2/include`. 212 | 4. `import Gift` in any Swift file, and you're off to the races! 213 | 214 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | Dir.glob("Scripts/*.rake").each { |rake_file| load rake_file } 2 | 3 | desc "Build libssh2 and libgit2 for iOS and OS X" 4 | task :build => ["build:osx", "build:ios"] 5 | 6 | desc "Install all dependencies and build libssh2 and libgit2 for iOS and OS X" 7 | task :default => ["test"] 8 | 9 | -------------------------------------------------------------------------------- /Scripts/build_ios.rake: -------------------------------------------------------------------------------- 1 | require_relative "helpers.rb" 2 | 3 | LIBSSH2_IOS_INSTALL_PATH = File.expand_path("../External/libssh2-for-iOS/lib/libssh2.a", File.dirname(__FILE__)) 4 | LIBGIT2_IOS_INSTALL_PATH = File.expand_path("../External/libgit2-iOS/libgit2-iOS.a", File.dirname(__FILE__)) 5 | 6 | def sdk_root(arch) 7 | if %w(x86_64 i386).include? arch 8 | platform = "iphonesimulator" 9 | else 10 | platform = "iphoneos" 11 | end 12 | `xcodebuild -version -sdk 2> /dev/null | grep -i #{platform}#{sdk_version} | grep 'Path:' | awk '{ print $2 }'`.chomp 13 | end 14 | 15 | def sdk_version 16 | `xcodebuild -version -sdk 2> /dev/null | grep SDKVersion | tail -n 1 | awk '{ print $2 }'`.chomp 17 | end 18 | 19 | 20 | namespace "build" do 21 | desc "Build libssh2 and libgit2 for iOS" 22 | task :ios => ["build:ios:libssh2", "build:ios:libgit2"] 23 | 24 | namespace :ios do 25 | desc "Build OpenSSL and libssh2 for iOS" 26 | task :libssh2 do 27 | if File.exist? LIBSSH2_IOS_INSTALL_PATH 28 | puts "libssh2.a already exists at \"#{LIBSSH2_IOS_INSTALL_PATH}\"." 29 | next 30 | end 31 | 32 | run "cd External/libssh2-for-iOS && ./build-all.sh openssl" 33 | run "cd External/libssh2-for-iOS && git checkout -- . && git clean -fd" 34 | end 35 | 36 | desc "Build libgit2 for iOS" 37 | task :libgit2 do 38 | if File.exist? LIBGIT2_IOS_INSTALL_PATH 39 | puts "libgit2-iOS.a already exists at \"#{LIBGIT2_IOS_INSTALL_PATH}\"." 40 | next 41 | end 42 | 43 | root_path = `pwd`.strip 44 | xcode_path = `xcode-select --print-path`.strip 45 | 46 | %w(x86_64 i386 armv7 armv7s arm64).each do |arch| 47 | run "rm -rf External/libgit2/build" 48 | run "mkdir External/libgit2/build" 49 | 50 | install_prefix = "#{root_path}/External/libgit2-iOS/#{arch}" 51 | run "mkdir -p #{install_prefix}" 52 | 53 | run <<-command 54 | cd External/libgit2/build && \\ 55 | export IPHONEOS_DEPLOYMENT_TARGET=8.0 && \\ 56 | export SDKROOT=#{xcode_path}/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator#{sdk_version}.sdk && \\ 57 | /usr/local/bin/cmake \\ 58 | -DCMAKE_C_COMPILER=clang \\ 59 | -DCMAKE_C_COMPILER_WORKS:BOOL=ON \\ 60 | -DBUILD_SHARED_LIBS:BOOL=OFF \\ 61 | -DOPENSSL_INCLUDE_DIR:PATH=#{root_path}/External/libssh2-for-iOS/include/ \\ 62 | -DCMAKE_LIBRARY_PATH:PATH=#{root_path}/External/libssh2-for-iOS/lib/ \\ 63 | -DCMAKE_INCLUDE_PATH:PATH=#{root_path}/External/libssh2-for-iOS/include/libssh2/ \\ 64 | -DOPENSSL_SSL_LIBRARY:FILEPATH=#{root_path}/External/libssh2-for-iOS/lib/libssl.a \\ 65 | -DCMAKE_LIBRARY_PATH:PATH="#{sdk_root(arch)}/usr/lib/" \\ 66 | -DOPENSSL_CRYPTO_LIBRARY:FILEPATH=#{root_path}/External/libssh2-for-iOS/lib/libcrypto.a \\ 67 | -DCMAKE_INSTALL_PREFIX:PATH="#{install_prefix}" \\ 68 | -DBUILD_CLAR:BOOL=OFF \\ 69 | -DTHREADSAFE:BOOL=ON \\ 70 | #{"-DCMAKE_OSX_SYSROOT=#{sdk_root(arch)}" unless %w(x86_64 i386).include? arch} \\ 71 | -DCMAKE_OSX_ARCHITECTURES:STRING="#{arch}" \\ 72 | .. 73 | command 74 | run "cd External/libgit2/build && cmake --build . --target install" 75 | end 76 | 77 | run <<-command 78 | lipo -create \\ 79 | External/libgit2-iOS/x86_64/lib/libgit2.a \\ 80 | External/libgit2-iOS/i386/lib/libgit2.a \\ 81 | External/libgit2-iOS/armv7/lib/libgit2.a \\ 82 | External/libgit2-iOS/armv7s/lib/libgit2.a \\ 83 | External/libgit2-iOS/arm64/lib/libgit2.a \\ 84 | -output External/libgit2-iOS/libgit2-iOS.a 85 | command 86 | end 87 | end 88 | end 89 | 90 | -------------------------------------------------------------------------------- /Scripts/build_osx.rake: -------------------------------------------------------------------------------- 1 | require_relative "helpers.rb" 2 | 3 | LIBGIT2_INSTALL_PATH = File.expand_path("../External/libgit2/build/libgit2.a", File.dirname(__FILE__)) 4 | 5 | namespace "build" do 6 | desc "Build libssh2 and libgit2 for OS X" 7 | task :osx => ["build:osx:libssh2", "build:osx:libgit2"] 8 | 9 | namespace :osx do 10 | desc "Upgrade the version of libssh2 installed by homebrew" 11 | task :libssh2 do 12 | `brew upgrade libssh2` 13 | end 14 | 15 | desc "Build libgit2.a for OS X" 16 | task :libgit2 do 17 | if File.exist? LIBGIT2_INSTALL_PATH 18 | puts "libgit2.a already exists at \"#{LIBGIT2_INSTALL_PATH}\"" 19 | next 20 | end 21 | 22 | run <<-command 23 | mkdir -p External/libgit2/build && \\ 24 | cd External/libgit2/build && \\ 25 | cmake -DBUILD_SHARED_LIBS:BOOL=OFF -DBUILD_CLAR:BOOL=OFF -DTHREADSAFE:BOOL=ON .. 26 | cmake --build . 27 | command 28 | end 29 | end 30 | end 31 | 32 | -------------------------------------------------------------------------------- /Scripts/dependencies.rake: -------------------------------------------------------------------------------- 1 | require_relative "helpers.rb" 2 | 3 | desc "Updates all the dependencies required to build libssh2 and libgit2" 4 | task :dependencies => [ 5 | "dependencies:homebrew", 6 | "dependencies:cmake", 7 | "dependencies:submodules", 8 | "dependencies:reactivecocoa" 9 | ] 10 | 11 | namespace :dependencies do 12 | desc "Update homebrew" 13 | task :homebrew do 14 | `brew update` 15 | end 16 | 17 | desc "Upgrade the cmake installed by homebrew" 18 | task :cmake do 19 | `brew upgrade cmake` 20 | end 21 | 22 | desc "Downloads and updates all Git submodules used by this project" 23 | task :submodules do 24 | run "git submodule update --init --recursive" 25 | end 26 | 27 | desc "Run the ReactiveCocoa setup script" 28 | task :reactivecocoa do 29 | run "cd External/ReactiveCocoa && script/bootstrap" 30 | end 31 | end 32 | 33 | -------------------------------------------------------------------------------- /Scripts/helpers.rb: -------------------------------------------------------------------------------- 1 | def run(command) 2 | puts command 3 | system(command) or raise "RAKE TASK FAILED: #{command}" 4 | end 5 | 6 | -------------------------------------------------------------------------------- /Scripts/test.rake: -------------------------------------------------------------------------------- 1 | require_relative "helpers.rb" 2 | 3 | desc "Test the Gift-iOS and Gift-OSX targets" 4 | task :test => ["test:ios", "test:osx"] 5 | 6 | namespace :test do 7 | desc "Test the Gift-iOS target" 8 | task :ios do 9 | run "xcodebuild -workspace Gift.xcworkspace -scheme Gift-iOS test" 10 | end 11 | 12 | desc "Test the Gift-OSX target" 13 | task :osx do 14 | run "xcodebuild -workspace Gift.xcworkspace -scheme Gift-OSX test" 15 | end 16 | end 17 | 18 | --------------------------------------------------------------------------------