Skip to content

Commit 6dc2722

Browse files
authored
Merge pull request #576 from slavikus/git-notes
Git notes
2 parents 3480278 + 927cc49 commit 6dc2722

File tree

12 files changed

+478
-5
lines changed

12 files changed

+478
-5
lines changed

ObjectiveGit/GTCredential.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ int GTCredentialAcquireCallback(git_cred **git_cred, const char *url, const char
109109
return GIT_ERROR;
110110
}
111111

112-
NSString *URL = (url != NULL ? @(url) : nil);
112+
NSString *URL = (url != NULL ? @(url) : @"");
113113
NSString *userName = (username_from_url != NULL ? @(username_from_url) : nil);
114114

115115
GTCredential *cred = [provider credentialForType:(GTCredentialType)allowed_types URL:URL userName:userName];

ObjectiveGit/GTIndexEntry.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ NS_ASSUME_NONNULL_BEGIN
6161
- (const git_index_entry *)git_index_entry __attribute__((objc_returns_inner_pointer));
6262

6363
/// The entry's index. This may be nil if nil is passed in to -initWithGitIndexEntry:
64-
@property (nonatomic, strong, readonly) GTIndex *index;
64+
@property (nonatomic, strong, readonly, nullable) GTIndex *index;
6565

6666
/// The repository-relative path for the entry.
6767
@property (nonatomic, readonly, copy) NSString *path;

ObjectiveGit/GTNote.h

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//
2+
// GTNote.h
3+
// ObjectiveGitFramework
4+
//
5+
// Created by Slava Karpenko on 5/16/2016.
6+
//
7+
// The MIT License
8+
//
9+
// Copyright (c) 2016 Wildbit LLC
10+
//
11+
// Permission is hereby granted, free of charge, to any person obtaining a copy
12+
// of this software and associated documentation files (the "Software"), to deal
13+
// in the Software without restriction, including without limitation the rights
14+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15+
// copies of the Software, and to permit persons to whom the Software is
16+
// furnished to do so, subject to the following conditions:
17+
//
18+
// The above copyright notice and this permission notice shall be included in
19+
// all copies or substantial portions of the Software.
20+
//
21+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27+
// THE SOFTWARE.
28+
//
29+
30+
#import <Foundation/Foundation.h>
31+
#import "git2/oid.h"
32+
33+
@class GTSignature;
34+
@class GTRepository;
35+
@class GTOID;
36+
@class GTObject;
37+
38+
NS_ASSUME_NONNULL_BEGIN
39+
40+
@interface GTNote : NSObject {}
41+
42+
/// The author of the note.
43+
@property (nonatomic, readonly, strong, nullable) GTSignature *author;
44+
45+
/// The committer of the note.
46+
@property (nonatomic, readonly, strong, nullable) GTSignature *committer;
47+
48+
/// Content of the note.
49+
@property (nonatomic, readonly, strong) NSString *note;
50+
51+
@property (nonatomic, readonly, strong) GTObject *target;
52+
53+
/// The underlying `git_note` object.
54+
- (git_note *)git_note __attribute__((objc_returns_inner_pointer));
55+
56+
/// Create a note with target OID in the given repository.
57+
///
58+
/// oid - OID of the target to attach to
59+
/// repository - Repository containing the target OID refers to
60+
/// referenceName - Name for the notes reference in the repo, or nil for default ("refs/notes/commits")
61+
/// error - Will be filled with a NSError object in case of error.
62+
/// May be NULL.
63+
///
64+
/// Returns initialized GTNote instance or nil on failure (error will be populated, if passed).
65+
- (nullable instancetype)initWithTargetOID:(GTOID *)oid repository:(GTRepository *)repository referenceName:(nullable NSString *)referenceName error:(NSError **)error;
66+
67+
/// Create a note with target libgit2 oid in the given repository.
68+
///
69+
/// oid - git_oid of the target to attach to
70+
/// repository - Repository containing the target OID refers to
71+
/// referenceName - Name for the notes reference in the repo, or NULL for default ("refs/notes/commits")
72+
///
73+
/// Returns initialized GTNote instance or nil on failure.
74+
- (nullable instancetype)initWithTargetGitOID:(git_oid *)oid repository:(git_repository *)repository referenceName:(const char * _Nullable)referenceName error:(NSError **)error NS_DESIGNATED_INITIALIZER;
75+
76+
- (instancetype)init NS_UNAVAILABLE;
77+
78+
79+
/// Return a default reference name (that is used if you pass nil to any referenceName parameter)
80+
///
81+
/// repository - Repository for which to get the default notes reference name.
82+
/// error - Will be filled with a git error code in case of error.
83+
/// May be NULL.
84+
///
85+
/// Returns default reference name (usually "refs/notes/commits").
86+
+ (nullable NSString *)defaultReferenceNameForRepository:(GTRepository *)repository error:(NSError **)error;
87+
88+
@end
89+
90+
NS_ASSUME_NONNULL_END
91+

ObjectiveGit/GTNote.m

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//
2+
// GTNote.m
3+
// ObjectiveGitFramework
4+
//
5+
// Created by Slava Karpenko on 16.05.16.
6+
// Copyright © 2016 Wildbit LLC. All rights reserved.
7+
//
8+
9+
#import "GTNote.h"
10+
#import "NSError+Git.h"
11+
#import "GTSignature.h"
12+
#import "GTReference.h"
13+
#import "GTRepository.h"
14+
#import "NSString+Git.h"
15+
#import "GTOID.h"
16+
17+
#import "git2/errors.h"
18+
#import "git2/notes.h"
19+
20+
@interface GTNote ()
21+
{
22+
git_note *_note;
23+
}
24+
25+
@end
26+
@implementation GTNote
27+
28+
- (NSString *)description {
29+
return [NSString stringWithFormat:@"<%@: %p>", NSStringFromClass([self class]), self];
30+
}
31+
32+
#pragma mark API
33+
34+
- (void)dealloc {
35+
if (_note != NULL) {
36+
git_note_free(_note);
37+
}
38+
}
39+
40+
- (git_note *)git_note {
41+
return _note;
42+
}
43+
44+
- (NSString *)note {
45+
return @(git_note_message(self.git_note));
46+
}
47+
48+
- (GTSignature *)author {
49+
return [[GTSignature alloc] initWithGitSignature:git_note_author(self.git_note)];
50+
}
51+
52+
- (GTSignature *)committer {
53+
return [[GTSignature alloc] initWithGitSignature:git_note_committer(self.git_note)];
54+
}
55+
56+
- (GTOID *)targetOID {
57+
return [GTOID oidWithGitOid:git_note_id(self.git_note)];
58+
}
59+
60+
- (instancetype)initWithTargetOID:(GTOID *)oid repository:(GTRepository *)repository referenceName:(NSString *)referenceName error:(NSError **)error {
61+
return [self initWithTargetGitOID:(git_oid *)oid.git_oid repository:repository.git_repository referenceName:referenceName.UTF8String error:error];
62+
}
63+
64+
- (instancetype)initWithTargetGitOID:(git_oid *)oid repository:(git_repository *)repository referenceName:(const char *)referenceName error:(NSError **)error {
65+
self = [super init];
66+
if (self == nil) return nil;
67+
68+
int gitErr = git_note_read(&_note, repository, referenceName, oid);
69+
70+
if (gitErr != GIT_OK) {
71+
if (error != NULL) *error = [NSError git_errorFor:gitErr description:@"Unable to read note"];
72+
return nil;
73+
}
74+
75+
return self;
76+
}
77+
78+
- (instancetype)init {
79+
NSAssert(NO, @"Call to an unavailable initializer.");
80+
return nil;
81+
}
82+
83+
+ (NSString *)defaultReferenceNameForRepository:(GTRepository *)repository error:(NSError **)error {
84+
NSString *noteRef = nil;
85+
86+
git_buf default_ref_name = { 0 };
87+
int gitErr = git_note_default_ref(&default_ref_name, repository.git_repository);
88+
if (gitErr != GIT_OK) {
89+
if (error != NULL) *error = [NSError git_errorFor:gitErr description:@"Unable to get default git notes reference name"];
90+
return nil;
91+
}
92+
93+
if (default_ref_name.ptr != NULL) {
94+
noteRef = @(default_ref_name.ptr);
95+
} else {
96+
if (error != NULL) *error = [NSError git_errorFor:GIT_ERROR description:@"Unable to get default git notes reference name"];
97+
}
98+
99+
git_buf_free(&default_ref_name);
100+
101+
return noteRef;
102+
}
103+
@end

ObjectiveGit/GTOID.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN
1616
@interface GTOID : NSObject <NSCopying>
1717

1818
/// The SHA pointed to by the OID.
19-
@property (nonatomic, readonly, copy) NSString *SHA;
19+
@property (nonatomic, readonly, copy, nullable) NSString *SHA;
2020

2121
/// Is the OID all zero? This usually indicates that the object has not been
2222
/// inserted into the ODB yet.

ObjectiveGit/GTRepository+RemoteOperations.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ extern NSString *const GTRepositoryRemoteOptionsFetchPrune;
2222
/// A `GTRemoteAutoTagOption`, that will be used to determine how the fetch should handle tags.
2323
extern NSString *const GTRepositoryRemoteOptionsDownloadTags;
2424

25+
/// A `@(BOOL)`, indicating git notes should also be pushed to the default notes reference name (if `@(YES)`), or an `NSArray(NSString)` containing reference names to be pushed through.
26+
extern NSString *const GTRepositoryRemoteOptionsPushNotes;
27+
2528
/// An enum describing the data needed for pruning.
2629
/// See `git_fetch_prune_t`.
2730
typedef NS_ENUM(NSInteger, GTFetchPruneOption) {
@@ -76,6 +79,7 @@ typedef NS_ENUM(NSInteger, GTFetchPruneOption) {
7679
/// options - Options applied to the push operation. Can be NULL.
7780
/// Recognized options are:
7881
/// `GTRepositoryRemoteOptionsCredentialProvider`
82+
/// `GTRepositoryRemoteOptionsPushNotes` (to push together with notes in one push)
7983
/// error - The error if one occurred. Can be NULL.
8084
/// progressBlock - An optional callback for monitoring progress. May be NULL.
8185
///
@@ -89,14 +93,29 @@ typedef NS_ENUM(NSInteger, GTFetchPruneOption) {
8993
/// remote - The remote to push to. Must not be nil.
9094
/// options - Options applied to the push operation. Can be NULL.
9195
/// Recognized options are:
92-
/// `GTRepositoryRemoteOptionsCredentialProvider`
96+
/// `GTRepositoryRemoteOptionsCredentialProvider`,
97+
/// `GTRepositoryRemoteOptionsPushNotes` (to push together with notes in one push)
9398
/// error - The error if one occurred. Can be NULL.
9499
/// progressBlock - An optional callback for monitoring progress. May be NULL.
95100
///
96101
/// Returns YES if the push was successful, NO otherwise (and `error`, if provided,
97102
/// will point to an error describing what happened).
98103
- (BOOL)pushBranches:(NSArray<GTBranch *> *)branches toRemote:(GTRemote *)remote withOptions:(nullable NSDictionary *)options error:(NSError **)error progress:(nullable void (^)(unsigned int current, unsigned int total, size_t bytes, BOOL *stop))progressBlock;
99104

105+
/// Push a given Git notes reference name to a remote.
106+
///
107+
/// noteReferenceName - Name of the notes reference. If NULL, will default to whatever the default is (e.g. "refs/notes/commits")
108+
/// remote - The remote to push to. Must not be nil.
109+
/// options - Options applied to the push operation. Can be NULL.
110+
/// Recognized options are:
111+
/// `GTRepositoryRemoteOptionsCredentialProvider`
112+
/// error - The error if one occurred. Can be NULL.
113+
/// progressBlock - An optional callback for monitoring progress. May be NULL.
114+
///
115+
/// Returns YES if the push was successful, NO otherwise (and `error`, if provided,
116+
/// will point to an error describing what happened).
117+
- (BOOL)pushNotes:(nullable NSString *)noteReferenceName toRemote:(GTRemote *)remote withOptions:(nullable NSDictionary *)options error:(NSError **)error progress:(nullable void (^)(unsigned int current, unsigned int total, size_t bytes, BOOL *stop))progressBlock;
118+
100119
/// Delete a remote branch
101120
///
102121
/// branch - The branch to push. Must not be nil.

ObjectiveGit/GTRepository+RemoteOperations.m

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@
1818
#import "NSArray+StringArray.h"
1919
#import "NSError+Git.h"
2020
#import "GTRepository+References.h"
21+
#import "GTNote.h"
2122

2223
#import "git2/errors.h"
2324
#import "git2/remote.h"
25+
#import "git2/notes.h"
26+
#import "git2/buffer.h"
2427

2528
NSString *const GTRepositoryRemoteOptionsCredentialProvider = @"GTRepositoryRemoteOptionsCredentialProvider";
2629
NSString *const GTRepositoryRemoteOptionsFetchPrune = @"GTRepositoryRemoteOptionsFetchPrune";
2730
NSString *const GTRepositoryRemoteOptionsDownloadTags = @"GTRepositoryRemoteOptionsDownloadTags";
31+
NSString *const GTRepositoryRemoteOptionsPushNotes = @"GTRepositoryRemoteOptionsPushNotes";
2832

2933
typedef void (^GTRemoteFetchTransferProgressBlock)(const git_transfer_progress *stats, BOOL *stop);
3034
typedef void (^GTRemotePushTransferProgressBlock)(unsigned int current, unsigned int total, size_t bytes, BOOL *stop);
@@ -194,10 +198,50 @@ - (BOOL)pushBranches:(NSArray *)branches toRemote:(GTRemote *)remote withOptions
194198

195199
[refspecs addObject:[NSString stringWithFormat:@"refs/heads/%@:%@", branch.shortName, remoteBranchReference]];
196200
}
197-
201+
202+
// Also push the notes reference(s), if needed.
203+
id pushNotesOption = options[GTRepositoryRemoteOptionsPushNotes];
204+
if (pushNotesOption != nil) {
205+
if ([pushNotesOption isKindOfClass:[NSNumber class]]) { // Push notes is a bool, only push the default reference name if it's YES
206+
if ([(NSNumber *)pushNotesOption boolValue]) {
207+
NSString *notesReferenceName = [GTNote defaultReferenceNameForRepository:self error:nil];
208+
209+
// Check whether the reference name exists for the repo, or our push will fail
210+
if (notesReferenceName != nil && [self lookUpReferenceWithName:notesReferenceName error:nil] != nil) {
211+
[refspecs addObject:[NSString stringWithFormat:@"%@:%@", notesReferenceName, notesReferenceName]];
212+
}
213+
}
214+
} else if ([pushNotesOption isKindOfClass:[NSArray class]]) {
215+
for (NSString *notesReferenceName in (NSArray *)pushNotesOption) {
216+
if ([notesReferenceName isKindOfClass:[NSString class]]) { // Just a sanity check, we only accept NSStrings in the array
217+
// Check whether the reference name exists for the repo, or our push will fail
218+
if (notesReferenceName != nil && [self lookUpReferenceWithName:notesReferenceName error:nil] != nil) {
219+
[refspecs addObject:[NSString stringWithFormat:@"%@:%@", notesReferenceName, notesReferenceName]];
220+
}
221+
}
222+
}
223+
}
224+
}
225+
198226
return [self pushRefspecs:refspecs toRemote:remote withOptions:options error:error progress:progressBlock];
199227
}
200228

229+
- (BOOL)pushNotes:(NSString *)noteRef toRemote:(GTRemote *)remote withOptions:(NSDictionary *)options error:(NSError **)error progress:(GTRemotePushTransferProgressBlock)progressBlock {
230+
NSParameterAssert(remote != nil);
231+
232+
if (noteRef == nil) {
233+
noteRef = [GTNote defaultReferenceNameForRepository:self error:error];
234+
235+
if (noteRef == nil) return NO;
236+
}
237+
238+
GTReference *notesReference = [self lookUpReferenceWithName:noteRef error:error];
239+
240+
if (notesReference == nil) return NO;
241+
242+
return [self pushRefspecs:@[[NSString stringWithFormat:@"%@:%@", noteRef, noteRef]] toRemote:remote withOptions:options error:error progress:progressBlock];
243+
}
244+
201245
#pragma mark - Deletion (Public)
202246
- (BOOL)deleteBranch:(GTBranch *)branch fromRemote:(GTRemote *)remote withOptions:(NSDictionary *)options error:(NSError **)error {
203247
NSParameterAssert(branch != nil);

ObjectiveGit/GTRepository.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
@class GTTag;
5252
@class GTTree;
5353
@class GTRemote;
54+
@class GTNote;
5455

5556
NS_ASSUME_NONNULL_BEGIN
5657

@@ -626,6 +627,48 @@ typedef NS_ENUM(NSInteger, GTRepositoryStateType) {
626627
/// Returns YES if operation was successful, NO otherwise
627628
- (BOOL)cleanupStateWithError:(NSError **)error;
628629

630+
/// Creates a new note in this repo (using a default notes reference, e.g. "refs/notes/commits")
631+
///
632+
/// note - Note text.
633+
/// theTarget - Object (usually a commit) to which this note refers to.
634+
/// This object must belong to this repository.
635+
/// referenceName - Name for the notes reference in the repo, or nil for default ("refs/notes/commits")
636+
/// author - Signature of the author for this note, and
637+
/// of the note creation time
638+
/// committer - Signature of the committer for this note.
639+
/// overwrite - If set to YES, the note will be overwritten if it already exists.
640+
/// error - Will be filled with a NSError object in case of error.
641+
/// May be NULL.
642+
///
643+
/// Returns the newly created note or nil on error.
644+
- (nullable GTNote *)createNote:(NSString *)note target:(GTObject *)theTarget referenceName:(nullable NSString *)referenceName author:(GTSignature *)author committer:(GTSignature *)committer overwriteIfExists:(BOOL)overwrite error:(NSError **)error;
645+
646+
/// Removes a note attached to object in this repo
647+
///
648+
/// parentObject - Object (usually a commit) to which the note to be removed is attached to.
649+
/// This object must belong to this repository.
650+
/// referenceName - Name for the notes reference in the repo, or nil for default ("refs/notes/commits")
651+
/// author - Signature of the author for this note removal, and
652+
/// of the note removal time
653+
/// committer - Signature of the committer for this note removal.
654+
/// error - Will be filled with a NSError object in case of error.
655+
/// May be NULL.
656+
///
657+
/// Returns the YES on success and NO on error.
658+
- (BOOL)removeNoteFromObject:(GTObject *)parentObject referenceName:(nullable NSString *)referenceName author:(GTSignature *)author committer:(GTSignature *)committer error:(NSError **)error;
659+
660+
/// Enumerates through all stored notes in this repo
661+
///
662+
/// referenceName - Name for the notes reference in the repo, or nil for default ("refs/notes/commits")
663+
/// error - Will be filled with a NSError object in case of error.
664+
/// May be NULL.
665+
/// block - A block to be called on each encountered note object. The block accepts
666+
/// a reference to `note`, an `object` that is annotated with the note.
667+
/// If the block sets `stop` to YES, the iterator is finished.
668+
///
669+
/// Returns YES on overall success or NO on error of any kind.
670+
- (BOOL)enumerateNotesWithReferenceName:(nullable NSString *)referenceName error:(NSError **)error usingBlock:(void (^)(GTNote * __nullable note, GTObject * __nullable object, NSError * __nullable error, BOOL *stop))block;
671+
629672
@end
630673

631674
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)