From 7e17d4e00943f73a30924114aee880330f3f2081 Mon Sep 17 00:00:00 2001 From: Roman Gardukevich Date: Fri, 23 Sep 2022 10:06:09 +0300 Subject: [PATCH] Basic commit GPG sign Supported operations: * Commit * Merge * Cherry-pick --- GitUpKit/Core/GCRepository+Bare.m | 42 ++++- GitUpKit/Core/GPGContext+Private.h | 24 +++ GitUpKit/Core/GPGContext.h | 24 +++ GitUpKit/Core/GPGContext.m | 41 +++++ GitUpKit/Core/GPGKeys.h | 33 ++++ GitUpKit/Core/GPGKeys.m | 163 ++++++++++++++++++++ GitUpKit/GitUpKit.xcodeproj/project.pbxproj | 34 ++++ 7 files changed, 360 insertions(+), 1 deletion(-) create mode 100644 GitUpKit/Core/GPGContext+Private.h create mode 100644 GitUpKit/Core/GPGContext.h create mode 100644 GitUpKit/Core/GPGContext.m create mode 100644 GitUpKit/Core/GPGKeys.h create mode 100644 GitUpKit/Core/GPGKeys.m diff --git a/GitUpKit/Core/GCRepository+Bare.m b/GitUpKit/Core/GCRepository+Bare.m index 97923b1d..b933d1a2 100644 --- a/GitUpKit/Core/GCRepository+Bare.m +++ b/GitUpKit/Core/GCRepository+Bare.m @@ -18,6 +18,7 @@ #endif #import "GCPrivate.h" +#import "GPGKeys.h" @implementation GCRepository (Bare) @@ -365,19 +366,58 @@ - (GCCommit*)createCommitFromTree:(git_tree*)tree error:(NSError**)error { GCCommit* commit = nil; git_signature* signature = NULL; + const char *gpgSignature = NULL; git_oid oid; + + GCConfigOption* shouldSignOption = [self readConfigOptionForVariable:@"commit.gpgsign" error:nil]; + BOOL shouldSign = [shouldSignOption.value isEqualToString:@"true"]; + CALL_LIBGIT2_FUNCTION_GOTO(cleanup, git_signature_default, &signature, self.private); - CALL_LIBGIT2_FUNCTION_GOTO(cleanup, git_commit_create, &oid, self.private, NULL, author ? author : signature, signature, NULL, GCCleanedUpCommitMessage(message).bytes, tree, count, parents); + + git_buf commitBuffer = GIT_BUF_INIT; + CALL_LIBGIT2_FUNCTION_GOTO(cleanupBuffer, git_commit_create_buffer, &commitBuffer, self.private, author ? author : signature, signature, NULL, GCCleanedUpCommitMessage(message).bytes, tree, count, parents); + + if (shouldSign) { + GCConfigOption* signingKeyOption = [self readConfigOptionForVariable:@"user.signingkey" error:nil]; + gpgSignature = [self gpgSig:commitBuffer.ptr keyId:signingKeyOption.value]; + } + + CALL_LIBGIT2_FUNCTION_GOTO(cleanupBuffer, git_commit_create_with_signature, &oid, self.private, commitBuffer.ptr, gpgSignature, NULL); + git_commit* newCommit = NULL; CALL_LIBGIT2_FUNCTION_GOTO(cleanup, git_commit_lookup, &newCommit, self.private, &oid); commit = [[GCCommit alloc] initWithRepository:self commit:newCommit]; +cleanupBuffer: + git_buf_dispose(&commitBuffer); + cleanup: git_signature_free(signature); return commit; } +-(const char*)gpgSig:(const char*)body keyId:(NSString*)keyId { + GPGKey *key = nil; + + if (keyId.length > 0) { + key = [GPGKey secretKeyForId:keyId]; + } + + if (key == nil) { + key = [[GPGKey allSecretKeys] firstObject]; + } + + if (key == nil) { + return NULL; + } + + NSString* plainToSign = [[NSString alloc] initWithCString:body encoding:NSUTF8StringEncoding]; + NSString* signature = [key signSignature:plainToSign]; + + return [signature UTF8String]; +} + - (GCCommit*)createCommitFromIndex:(git_index*)index withParents:(const git_commit**)parents count:(NSUInteger)count diff --git a/GitUpKit/Core/GPGContext+Private.h b/GitUpKit/Core/GPGContext+Private.h new file mode 100644 index 00000000..1da8b790 --- /dev/null +++ b/GitUpKit/Core/GPGContext+Private.h @@ -0,0 +1,24 @@ +// Copyright (C) 2015-2022 Pierre-Olivier Latour +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#import "GPGContext.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface GPGContext() +@property (nonatomic, assign) gpgme_ctx_t gpgContext; +@end + +NS_ASSUME_NONNULL_END diff --git a/GitUpKit/Core/GPGContext.h b/GitUpKit/Core/GPGContext.h new file mode 100644 index 00000000..3365c7ae --- /dev/null +++ b/GitUpKit/Core/GPGContext.h @@ -0,0 +1,24 @@ +// Copyright (C) 2015-2022 Pierre-Olivier Latour +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#import +#include + +NS_ASSUME_NONNULL_BEGIN + +@interface GPGContext : NSObject +@end + +NS_ASSUME_NONNULL_END diff --git a/GitUpKit/Core/GPGContext.m b/GitUpKit/Core/GPGContext.m new file mode 100644 index 00000000..061226d2 --- /dev/null +++ b/GitUpKit/Core/GPGContext.m @@ -0,0 +1,41 @@ +// Copyright (C) 2015-2022 Pierre-Olivier Latour +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#import "GPGContext.h" +#import "GPGContext+Private.h" +#import "XLFacilityMacros.h" + +@implementation GPGContext +-(instancetype)init { + self = [super init]; + if (self) { + static dispatch_once_t initializeThreadInfo; + dispatch_once(&initializeThreadInfo, ^{ + gpgme_check_version(NULL); + }); + + gpgme_error_t initError = gpgme_new(&_gpgContext); + if (initError) { + XLOG_ERROR(@"Failed to initialize GPGME context"); + return nil; + } + } + return self; +} + +-(void)dealloc { + gpgme_release(_gpgContext); +} +@end diff --git a/GitUpKit/Core/GPGKeys.h b/GitUpKit/Core/GPGKeys.h new file mode 100644 index 00000000..82d35222 --- /dev/null +++ b/GitUpKit/Core/GPGKeys.h @@ -0,0 +1,33 @@ +// Copyright (C) 2015-2022 Pierre-Olivier Latour +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#import +#include + +NS_ASSUME_NONNULL_BEGIN + +@interface GPGKey : NSObject +@property (readonly) NSString* email; +@property (readonly) NSString* name; +@property (readonly) NSString* keyId; + ++(NSArray *)allSecretKeys; ++(nullable instancetype)secretKeyForId:(NSString*)keyId; + +-(NSString*)signSignature:(NSString*)document; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GitUpKit/Core/GPGKeys.m b/GitUpKit/Core/GPGKeys.m new file mode 100644 index 00000000..e516e6aa --- /dev/null +++ b/GitUpKit/Core/GPGKeys.m @@ -0,0 +1,163 @@ +// Copyright (C) 2015-2022 Pierre-Olivier Latour +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#import "GPGKeys.h" +#import "GPGContext.h" +#import "GPGContext+Private.h" +#import "XLFacilityMacros.h" + +@interface GPGKey() +@property (nonatomic, assign) gpgme_key_t key; +@property (nonatomic, strong) GPGContext* gpgContext; +@property (nonatomic, strong, nullable) NSString* name; +@property (nonatomic, strong, nullable) NSString* email; +@property (nonatomic, strong, nullable) NSString* keyId; +@end + +@interface GPGKeys : NSObject +-(instancetype)initWithContext:(GPGContext*)context; + +-(NSArray*)allSecretKeys; +@end + + +NSString* helperGpgDataToString(gpgme_data_t data) { + gpgme_data_seek(data, 0, SEEK_SET); + char buffer[1024] = {0}; + ssize_t readCount = gpgme_data_read(data, buffer, 1024); + + NSData* readData = [[NSData alloc] initWithBytes:buffer length:readCount]; + NSString* readString = [[NSString alloc] initWithData:readData encoding:NSUTF8StringEncoding]; + + return readString; +} + +@implementation GPGKey +static dispatch_once_t initializeThreadInfo; + ++(NSArray *)allSecretKeys { + dispatch_once(&initializeThreadInfo, ^{ + gpgme_check_version(NULL); + }); + + GPGContext* contextWrapper = [[GPGContext alloc] init]; + gpg_error_t keylistStartError = gpgme_op_keylist_start(contextWrapper.gpgContext, NULL, 1); + + if (keylistStartError) { + XLOG_ERROR(@"Failed to start keylist: %s", gpg_strerror(keylistStartError)); + return nil; + } + + NSMutableArray *keys = [NSMutableArray array]; + gpg_error_t err = 0; + while (!err) { + gpgme_key_t key; + err = gpgme_op_keylist_next (contextWrapper.gpgContext, &key); + if (err) { + break; + } + + GPGKey* gpgKey = [[GPGKey alloc] initWithGPGKey:key context:contextWrapper]; + [keys addObject:gpgKey]; + } + + if (gpg_err_code (err) != GPG_ERR_EOF) { + XLOG_ERROR(@"Cannot list keys: %s", gpg_strerror(err)); + return nil; + } + + return [keys copy]; +} + ++(instancetype)secretKeyForId:(NSString *)keyId { + NSArray* allKeys = [self allSecretKeys]; + NSUInteger indexOfKey = [allKeys indexOfObjectPassingTest:^BOOL(GPGKey * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + return [obj.keyId isEqualToString:keyId]; + }]; + + if (indexOfKey == NSNotFound) { + return nil; + } + return allKeys[indexOfKey]; +} + +- (instancetype)initWithGPGKey:(gpgme_key_t)key context:(GPGContext*)context { + self = [super init]; + if (self) { + // retain key on initializer, release on object dealloc. + gpgme_key_ref(key); + self.key = key; + self.gpgContext = context; + + if (_key->uids) { + if (_key->uids->name) { + _name = [[NSString alloc] initWithCString:_key->uids->name + encoding:NSUTF8StringEncoding]; + } + if (_key->uids->email) { + _email = [[NSString alloc] initWithCString:_key->uids->email + encoding:NSUTF8StringEncoding]; + } + } + + if (_key->subkeys) { + if (_key->subkeys->keyid) { + _keyId = [[NSString alloc] initWithCString:_key->subkeys->keyid + encoding:NSUTF8StringEncoding]; + } + } + } + return self; +} + +-(NSString *)description { + return [NSString stringWithFormat:@"<%@: %p 'keyId: %@' 'email: %@' 'name: %@'>", self.class, self, self.keyId, self.email, self.name]; +} + +-(void)dealloc { + gpgme_key_unref(_key); +} + +-(NSString*)signSignature:(NSString*)document { + gpgme_signers_clear(_gpgContext.gpgContext); + gpgme_signers_add(_gpgContext.gpgContext, _key); + + gpgme_data_t in, out; + gpgme_data_new(&out); + + gpgme_error_t err = gpgme_data_new_from_mem(&in, [document UTF8String], document.length, 1); + if (err) { + XLOG_ERROR(@"Failed to initialize input data: %s", gpg_strerror(err)); + return nil; + } + + gpgme_set_textmode(_gpgContext.gpgContext, 0); + gpgme_set_armor(_gpgContext.gpgContext, 1); + + err = gpgme_op_sign(_gpgContext.gpgContext, in, out, GPGME_SIG_MODE_DETACH); + if (err) { + XLOG_ERROR(@"Signing failed due: %s", gpg_strerror(err)); + return nil; + } + + NSString* signatureString = helperGpgDataToString(out); + + gpgme_data_release(in); + gpgme_data_release(out); + + return signatureString; +} + +@end diff --git a/GitUpKit/GitUpKit.xcodeproj/project.pbxproj b/GitUpKit/GitUpKit.xcodeproj/project.pbxproj index 299808df..064762b4 100644 --- a/GitUpKit/GitUpKit.xcodeproj/project.pbxproj +++ b/GitUpKit/GitUpKit.xcodeproj/project.pbxproj @@ -338,6 +338,18 @@ E2F5C2831A81C53A00C30739 /* GCRepository+Reflog-Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = E2F5C2821A81C53A00C30739 /* GCRepository+Reflog-Tests.m */; }; E2FEED491AEAA6B500CBED80 /* GCCommitDatabase-Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = E2FEED481AEAA6B500CBED80 /* GCCommitDatabase-Tests.m */; }; E2FEED4A1AEAA75F00CBED80 /* GCCommitDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = E2FEED451AEAA6AD00CBED80 /* GCCommitDatabase.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + E69C1DFF217867DB0080117C /* GPGKeys.h in Headers */ = {isa = PBXBuildFile; fileRef = E69C1DFD217867DA0080117C /* GPGKeys.h */; }; + E69C1E00217867DB0080117C /* GPGKeys.h in Headers */ = {isa = PBXBuildFile; fileRef = E69C1DFD217867DA0080117C /* GPGKeys.h */; }; + E69C1E01217867DB0080117C /* GPGKeys.m in Sources */ = {isa = PBXBuildFile; fileRef = E69C1DFE217867DA0080117C /* GPGKeys.m */; }; + E69C1E02217867DB0080117C /* GPGKeys.m in Sources */ = {isa = PBXBuildFile; fileRef = E69C1DFE217867DA0080117C /* GPGKeys.m */; }; + E69C1E0C217893580080117C /* GPGContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E69C1E0A217893580080117C /* GPGContext.h */; }; + E69C1E0D217893580080117C /* GPGContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E69C1E0A217893580080117C /* GPGContext.h */; }; + E69C1E0E217893580080117C /* GPGContext.m in Sources */ = {isa = PBXBuildFile; fileRef = E69C1E0B217893580080117C /* GPGContext.m */; }; + E69C1E0F217893580080117C /* GPGContext.m in Sources */ = {isa = PBXBuildFile; fileRef = E69C1E0B217893580080117C /* GPGContext.m */; }; + E69C1E11217894AE0080117C /* GPGContext+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = E69C1E10217894AE0080117C /* GPGContext+Private.h */; }; + E69C1E12217894AE0080117C /* GPGContext+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = E69C1E10217894AE0080117C /* GPGContext+Private.h */; }; + E69C1E132178AC3E0080117C /* GPGKeys.m in Sources */ = {isa = PBXBuildFile; fileRef = E69C1DFE217867DA0080117C /* GPGKeys.m */; }; + E69C1E142178AC400080117C /* GPGContext.m in Sources */ = {isa = PBXBuildFile; fileRef = E69C1E0B217893580080117C /* GPGContext.m */; }; FE22513028DC40D00093E362 /* libgpgme in Frameworks */ = {isa = PBXBuildFile; productRef = FE22512F28DC40D00093E362 /* libgpgme */; }; FEA9874C28DDB8AD00D494A1 /* libgpgme in Frameworks */ = {isa = PBXBuildFile; productRef = FEA9874B28DDB8AD00D494A1 /* libgpgme */; }; /* End PBXBuildFile section */ @@ -558,6 +570,11 @@ E2FEED441AEAA6AD00CBED80 /* GCCommitDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCCommitDatabase.h; sourceTree = ""; }; E2FEED451AEAA6AD00CBED80 /* GCCommitDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCCommitDatabase.m; sourceTree = ""; }; E2FEED481AEAA6B500CBED80 /* GCCommitDatabase-Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GCCommitDatabase-Tests.m"; sourceTree = ""; }; + E69C1DFD217867DA0080117C /* GPGKeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GPGKeys.h; sourceTree = ""; }; + E69C1DFE217867DA0080117C /* GPGKeys.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GPGKeys.m; sourceTree = ""; }; + E69C1E0A217893580080117C /* GPGContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GPGContext.h; sourceTree = ""; }; + E69C1E0B217893580080117C /* GPGContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GPGContext.m; sourceTree = ""; }; + E69C1E10217894AE0080117C /* GPGContext+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "GPGContext+Private.h"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -829,6 +846,11 @@ E2C3AA5919FF0B0600BA89F3 /* GCRepository+Bare.h */, E2C3AA5A19FF0B0600BA89F3 /* GCRepository+Bare.m */, E24509021A9A50F3003E602D /* GCRepository+Config-Tests.m */, + E69C1DFD217867DA0080117C /* GPGKeys.h */, + E69C1DFE217867DA0080117C /* GPGKeys.m */, + E69C1E0A217893580080117C /* GPGContext.h */, + E69C1E10217894AE0080117C /* GPGContext+Private.h */, + E69C1E0B217893580080117C /* GPGContext.m */, E24508FE1A9A50EA003E602D /* GCRepository+Config.h */, E24508FF1A9A50EA003E602D /* GCRepository+Config.m */, E259C2DC1A64FDD00079616B /* GCRepository+HEAD-Tests.m */, @@ -936,6 +958,7 @@ E21753571B91626C00BE234A /* GCRepository+Mock.h in Headers */, E21753581B91626C00BE234A /* GCRepository+Reflog.h in Headers */, E21753591B91626C00BE234A /* GCRepository+Reset.h in Headers */, + E69C1E12217894AE0080117C /* GPGContext+Private.h in Headers */, E217535A1B91626C00BE234A /* GCRepository+Status.h in Headers */, E217535B1B91626C00BE234A /* GCSnapshot.h in Headers */, E217535C1B91626C00BE234A /* GCSQLiteRepository.h in Headers */, @@ -954,6 +977,8 @@ E2B9879B1B9172470097629D /* GILayer.h in Headers */, E2B9879C1B9172470097629D /* GILine.h in Headers */, E2B9879D1B9172470097629D /* GINode.h in Headers */, + E69C1E0D217893580080117C /* GPGContext.h in Headers */, + E69C1E00217867DB0080117C /* GPGKeys.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -992,6 +1017,7 @@ E267E1CD1B84D80500BAB377 /* GCRepository+Mock.h in Headers */, E267E1CE1B84D80500BAB377 /* GCRepository+Reflog.h in Headers */, E267E1CF1B84D80500BAB377 /* GCRepository+Reset.h in Headers */, + E69C1E11217894AE0080117C /* GPGContext+Private.h in Headers */, E267E1D01B84D80500BAB377 /* GCRepository+Status.h in Headers */, E267E1F01B84D85500BAB377 /* GCHistory+Rewrite.h in Headers */, E267E1F11B84D85500BAB377 /* GCRepository+Index.h in Headers */, @@ -1016,6 +1042,7 @@ E267E2251B84DBE700BAB377 /* GIModalView.h in Headers */, E267E2261B84DBE700BAB377 /* GIViewController.h in Headers */, E267E2271B84DBE700BAB377 /* GIViewController+Utilities.h in Headers */, + E69C1E0C217893580080117C /* GPGContext.h in Headers */, E267E2281B84DBE700BAB377 /* GIWindowController.h in Headers */, E267E2361B84DC3900BAB377 /* GICommitListViewController.h in Headers */, E267E2371B84DC3900BAB377 /* GIDiffContentsViewController.h in Headers */, @@ -1030,6 +1057,7 @@ 1D615D81286BEDC600FFF7E7 /* XLFacilityMacros.h in Headers */, E267E2501B84DC7D00BAB377 /* GICommitViewController.h in Headers */, DBDFBC0F22B61135003EEC6C /* NSBundle+GitUpKit.h in Headers */, + E69C1DFF217867DB0080117C /* GPGKeys.h in Headers */, E267E2511B84DC7D00BAB377 /* GIConfigViewController.h in Headers */, E267E2521B84DC7D00BAB377 /* GIConflictResolverViewController.h in Headers */, E267E2531B84DC7D00BAB377 /* GIDiffViewController.h in Headers */, @@ -1255,7 +1283,9 @@ E2B987921B9171D20097629D /* GILayer.m in Sources */, E2B987931B9171D20097629D /* GILine.m in Sources */, E2B987941B9171D20097629D /* GINode.m in Sources */, + E69C1E02217867DB0080117C /* GPGKeys.m in Sources */, E2B987951B9171D20097629D /* GIPrivate.m in Sources */, + E69C1E0F217893580080117C /* GPGContext.m in Sources */, DBDFBC1222B61135003EEC6C /* NSBundle+GitUpKit.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1270,6 +1300,7 @@ E267E1D41B84D83100BAB377 /* GCDiff.m in Sources */, E267E1D51B84D83100BAB377 /* GCFoundation.m in Sources */, E267E1D61B84D83100BAB377 /* GCFunctions.m in Sources */, + E69C1E01217867DB0080117C /* GPGKeys.m in Sources */, E267E1D71B84D83100BAB377 /* GCHistory.m in Sources */, E267E1D81B84D83100BAB377 /* GCIndex.m in Sources */, E267E1D91B84D83100BAB377 /* GCLiveRepository.m in Sources */, @@ -1323,6 +1354,7 @@ E267E2341B84DC2600BAB377 /* GISnapshotListViewController.m in Sources */, E267E2351B84DC2600BAB377 /* GIUnifiedReflogViewController.m in Sources */, E267E2591B84DCA100BAB377 /* GIAdvancedCommitViewController.m in Sources */, + E69C1E0E217893580080117C /* GPGContext.m in Sources */, E267E25A1B84DCA100BAB377 /* GICommitRewriterViewController.m in Sources */, E267E25B1B84DCA100BAB377 /* GICommitSplitterViewController.m in Sources */, E267E25C1B84DCA100BAB377 /* GICommitViewController.m in Sources */, @@ -1404,7 +1436,9 @@ E299D00F1A7206E7005035F7 /* GCSQLiteRepository-Tests.m in Sources */, E2790D431ACB1B1100965A98 /* GCFoundation.m in Sources */, E20EB08C19FC75CA0031A075 /* GCRepository+Reset.m in Sources */, + E69C1E142178AC400080117C /* GPGContext.m in Sources */, E259C2D31A64F9FF0079616B /* GCHistory-Tests.m in Sources */, + E69C1E132178AC3E0080117C /* GPGKeys.m in Sources */, E27E43061A74A96000D04ED1 /* GILayer.m in Sources */, E2C338F219F85C8600063D95 /* GCRemote.m in Sources */, E21739F41A4FE39E00EC6777 /* GCFunctions.m in Sources */,