Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Git notes #576

Merged
merged 23 commits into from
Dec 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
abc1d74
Updating libgit2 pointer
slavikus Mar 23, 2016
41279e4
Updating for new libgit2 git_remote_connect() call
slavikus Mar 23, 2016
0871b0e
Merge remote-tracking branch 'upstream/master'
slavikus May 4, 2016
47dc0fc
Git notes support.
slavikus May 19, 2016
fe13986
Added support for [GTRepository pushNotes:...]
slavikus May 19, 2016
6df9faf
Fixes per @tiennou feedback
slavikus May 20, 2016
947058d
Push branches and notes in one operation
slavikus May 23, 2016
e99a62e
Check if notes reference exists before pushing it
slavikus May 26, 2016
b64f024
Merge remote-tracking branch 'upstream/master'
slavikus Jul 15, 2016
0661bab
Merge branch 'master' into git-notes
slavikus Jul 15, 2016
c27e838
Merge remote-tracking branch 'upstream/master' into git-notes
slavikus Sep 6, 2016
73037a4
Style fixes per @tiennou feedback
slavikus Sep 6, 2016
75e5356
Merge remote-tracking branch 'upstream/master' into git-notes
slavikus Nov 8, 2016
16ef587
Merge remote-tracking branch 'upstream/master'
slavikus Nov 23, 2016
2352543
Respect nullable returns in various methods
slavikus Nov 23, 2016
6e96e11
Merge branch 'master' into git-notes
slavikus Nov 23, 2016
6228da7
Merge remote-tracking branch 'upstream/master' into git-notes
slavikus Nov 30, 2016
2fea0a5
Fix nullability warning for GTNote
slavikus Dec 2, 2016
5bf9d5f
Corrections per @tiennou feedback
slavikus Dec 2, 2016
f4a1103
Rollback libgit2 back to globally used release
slavikus Dec 2, 2016
55898a7
More fixes for pull request #576
slavikus Dec 2, 2016
16f980f
Extra newlines removal
slavikus Dec 2, 2016
927cc49
Fix tests for GTNote
slavikus Dec 4, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ObjectiveGit/GTCredential.m
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ int GTCredentialAcquireCallback(git_cred **git_cred, const char *url, const char
return GIT_ERROR;
}

NSString *URL = (url != NULL ? @(url) : nil);
NSString *URL = (url != NULL ? @(url) : @"");
NSString *userName = (username_from_url != NULL ? @(username_from_url) : nil);

GTCredential *cred = [provider credentialForType:(GTCredentialType)allowed_types URL:URL userName:userName];
Expand Down
2 changes: 1 addition & 1 deletion ObjectiveGit/GTIndexEntry.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ NS_ASSUME_NONNULL_BEGIN
- (const git_index_entry *)git_index_entry __attribute__((objc_returns_inner_pointer));

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

/// The repository-relative path for the entry.
@property (nonatomic, readonly, copy) NSString *path;
Expand Down
91 changes: 91 additions & 0 deletions ObjectiveGit/GTNote.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//
// GTNote.h
// ObjectiveGitFramework
//
// Created by Slava Karpenko on 5/16/2016.
//
// The MIT License
//
// Copyright (c) 2016 Wildbit LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

#import <Foundation/Foundation.h>
#import "git2/oid.h"

@class GTSignature;
@class GTRepository;
@class GTOID;
@class GTObject;

NS_ASSUME_NONNULL_BEGIN

@interface GTNote : NSObject {}

/// The author of the note.
@property (nonatomic, readonly, strong, nullable) GTSignature *author;

/// The committer of the note.
@property (nonatomic, readonly, strong, nullable) GTSignature *committer;

/// Content of the note.
@property (nonatomic, readonly, strong) NSString *note;

@property (nonatomic, readonly, strong) GTObject *target;

/// The underlying `git_note` object.
- (git_note *)git_note __attribute__((objc_returns_inner_pointer));

/// Create a note with target OID in the given repository.
///
/// oid - OID of the target to attach to
/// repository - Repository containing the target OID refers to
/// referenceName - Name for the notes reference in the repo, or nil for default ("refs/notes/commits")
/// error - Will be filled with a NSError object in case of error.
/// May be NULL.
///
/// Returns initialized GTNote instance or nil on failure (error will be populated, if passed).
- (nullable instancetype)initWithTargetOID:(GTOID *)oid repository:(GTRepository *)repository referenceName:(nullable NSString *)referenceName error:(NSError **)error;

/// Create a note with target libgit2 oid in the given repository.
///
/// oid - git_oid of the target to attach to
/// repository - Repository containing the target OID refers to
/// referenceName - Name for the notes reference in the repo, or NULL for default ("refs/notes/commits")
///
/// Returns initialized GTNote instance or nil on failure.
- (nullable instancetype)initWithTargetGitOID:(git_oid *)oid repository:(git_repository *)repository referenceName:(const char * _Nullable)referenceName error:(NSError **)error NS_DESIGNATED_INITIALIZER;

- (instancetype)init NS_UNAVAILABLE;


/// Return a default reference name (that is used if you pass nil to any referenceName parameter)
///
/// repository - Repository for which to get the default notes reference name.
/// error - Will be filled with a git error code in case of error.
/// May be NULL.
///
/// Returns default reference name (usually "refs/notes/commits").
+ (nullable NSString *)defaultReferenceNameForRepository:(GTRepository *)repository error:(NSError **)error;

@end

NS_ASSUME_NONNULL_END

103 changes: 103 additions & 0 deletions ObjectiveGit/GTNote.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//
// GTNote.m
// ObjectiveGitFramework
//
// Created by Slava Karpenko on 16.05.16.
// Copyright © 2016 Wildbit LLC. All rights reserved.
//

#import "GTNote.h"
#import "NSError+Git.h"
#import "GTSignature.h"
#import "GTReference.h"
#import "GTRepository.h"
#import "NSString+Git.h"
#import "GTOID.h"

#import "git2/errors.h"
#import "git2/notes.h"

@interface GTNote ()
{
git_note *_note;
}

@end
@implementation GTNote

- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p>", NSStringFromClass([self class]), self];
}

#pragma mark API

- (void)dealloc {
if (_note != NULL) {
git_note_free(_note);
}
}

- (git_note *)git_note {
return _note;
}

- (NSString *)note {
return @(git_note_message(self.git_note));
}

- (GTSignature *)author {
return [[GTSignature alloc] initWithGitSignature:git_note_author(self.git_note)];
}

- (GTSignature *)committer {
return [[GTSignature alloc] initWithGitSignature:git_note_committer(self.git_note)];
}

- (GTOID *)targetOID {
return [GTOID oidWithGitOid:git_note_id(self.git_note)];
}

- (instancetype)initWithTargetOID:(GTOID *)oid repository:(GTRepository *)repository referenceName:(NSString *)referenceName error:(NSError **)error {
return [self initWithTargetGitOID:(git_oid *)oid.git_oid repository:repository.git_repository referenceName:referenceName.UTF8String error:error];
}

- (instancetype)initWithTargetGitOID:(git_oid *)oid repository:(git_repository *)repository referenceName:(const char *)referenceName error:(NSError **)error {
self = [super init];
if (self == nil) return nil;

int gitErr = git_note_read(&_note, repository, referenceName, oid);

if (gitErr != GIT_OK) {
if (error != NULL) *error = [NSError git_errorFor:gitErr description:@"Unable to read note"];
return nil;
}

return self;
}

- (instancetype)init {
NSAssert(NO, @"Call to an unavailable initializer.");
return nil;
}

+ (NSString *)defaultReferenceNameForRepository:(GTRepository *)repository error:(NSError **)error {
NSString *noteRef = nil;

git_buf default_ref_name = { 0 };
int gitErr = git_note_default_ref(&default_ref_name, repository.git_repository);
if (gitErr != GIT_OK) {
if (error != NULL) *error = [NSError git_errorFor:gitErr description:@"Unable to get default git notes reference name"];
return nil;
}

if (default_ref_name.ptr != NULL) {
noteRef = @(default_ref_name.ptr);
} else {
if (error != NULL) *error = [NSError git_errorFor:GIT_ERROR description:@"Unable to get default git notes reference name"];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/GIT_ERROR/gitErr. GIT_ERROR is one of libgit2 error classes, not the error code returned by git_note_default_ref.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if gitErr is not GIT_OK, then it would return that error earlier. If we got to that point when git_note_default_ref returned GIT_OK, but default_ref_name.ptr is NULL for some reason, then I am generating a generic GIT_ERROR. I know in reality it shouldn't happen, but I am being just extra cautious here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I got confused. Normally you would have to go through the hassle of setting up an NSError manually, but since those cases are scarce in the codebase, I'm not against reusing GIT_ERROR then.

}

git_buf_free(&default_ref_name);

return noteRef;
}
@end
2 changes: 1 addition & 1 deletion ObjectiveGit/GTOID.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface GTOID : NSObject <NSCopying>

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

/// Is the OID all zero? This usually indicates that the object has not been
/// inserted into the ODB yet.
Expand Down
21 changes: 20 additions & 1 deletion ObjectiveGit/GTRepository+RemoteOperations.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ extern NSString *const GTRepositoryRemoteOptionsFetchPrune;
/// A `GTRemoteAutoTagOption`, that will be used to determine how the fetch should handle tags.
extern NSString *const GTRepositoryRemoteOptionsDownloadTags;

/// 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.
extern NSString *const GTRepositoryRemoteOptionsPushNotes;

/// An enum describing the data needed for pruning.
/// See `git_fetch_prune_t`.
typedef NS_ENUM(NSInteger, GTFetchPruneOption) {
Expand Down Expand Up @@ -76,6 +79,7 @@ typedef NS_ENUM(NSInteger, GTFetchPruneOption) {
/// options - Options applied to the push operation. Can be NULL.
/// Recognized options are:
/// `GTRepositoryRemoteOptionsCredentialProvider`
/// `GTRepositoryRemoteOptionsPushNotes` (to push together with notes in one push)
/// error - The error if one occurred. Can be NULL.
/// progressBlock - An optional callback for monitoring progress. May be NULL.
///
Expand All @@ -89,14 +93,29 @@ typedef NS_ENUM(NSInteger, GTFetchPruneOption) {
/// remote - The remote to push to. Must not be nil.
/// options - Options applied to the push operation. Can be NULL.
/// Recognized options are:
/// `GTRepositoryRemoteOptionsCredentialProvider`
/// `GTRepositoryRemoteOptionsCredentialProvider`,
/// `GTRepositoryRemoteOptionsPushNotes` (to push together with notes in one push)
/// error - The error if one occurred. Can be NULL.
/// progressBlock - An optional callback for monitoring progress. May be NULL.
///
/// Returns YES if the push was successful, NO otherwise (and `error`, if provided,
/// will point to an error describing what happened).
- (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;

/// Push a given Git notes reference name to a remote.
///
/// noteReferenceName - Name of the notes reference. If NULL, will default to whatever the default is (e.g. "refs/notes/commits")
/// remote - The remote to push to. Must not be nil.
/// options - Options applied to the push operation. Can be NULL.
/// Recognized options are:
/// `GTRepositoryRemoteOptionsCredentialProvider`
/// error - The error if one occurred. Can be NULL.
/// progressBlock - An optional callback for monitoring progress. May be NULL.
///
/// Returns YES if the push was successful, NO otherwise (and `error`, if provided,
/// will point to an error describing what happened).
- (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;

/// Delete a remote branch
///
/// branch - The branch to push. Must not be nil.
Expand Down
46 changes: 45 additions & 1 deletion ObjectiveGit/GTRepository+RemoteOperations.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@
#import "NSArray+StringArray.h"
#import "NSError+Git.h"
#import "GTRepository+References.h"
#import "GTNote.h"

#import "git2/errors.h"
#import "git2/remote.h"
#import "git2/notes.h"
#import "git2/buffer.h"

NSString *const GTRepositoryRemoteOptionsCredentialProvider = @"GTRepositoryRemoteOptionsCredentialProvider";
NSString *const GTRepositoryRemoteOptionsFetchPrune = @"GTRepositoryRemoteOptionsFetchPrune";
NSString *const GTRepositoryRemoteOptionsDownloadTags = @"GTRepositoryRemoteOptionsDownloadTags";
NSString *const GTRepositoryRemoteOptionsPushNotes = @"GTRepositoryRemoteOptionsPushNotes";

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

[refspecs addObject:[NSString stringWithFormat:@"refs/heads/%@:%@", branch.shortName, remoteBranchReference]];
}


// Also push the notes reference(s), if needed.
id pushNotesOption = options[GTRepositoryRemoteOptionsPushNotes];
if (pushNotesOption != nil) {
if ([pushNotesOption isKindOfClass:[NSNumber class]]) { // Push notes is a bool, only push the default reference name if it's YES
if ([(NSNumber *)pushNotesOption boolValue]) {
NSString *notesReferenceName = [GTNote defaultReferenceNameForRepository:self error:nil];

// Check whether the reference name exists for the repo, or our push will fail
if (notesReferenceName != nil && [self lookUpReferenceWithName:notesReferenceName error:nil] != nil) {
[refspecs addObject:[NSString stringWithFormat:@"%@:%@", notesReferenceName, notesReferenceName]];
}
}
} else if ([pushNotesOption isKindOfClass:[NSArray class]]) {
for (NSString *notesReferenceName in (NSArray *)pushNotesOption) {
if ([notesReferenceName isKindOfClass:[NSString class]]) { // Just a sanity check, we only accept NSStrings in the array
// Check whether the reference name exists for the repo, or our push will fail
if (notesReferenceName != nil && [self lookUpReferenceWithName:notesReferenceName error:nil] != nil) {
[refspecs addObject:[NSString stringWithFormat:@"%@:%@", notesReferenceName, notesReferenceName]];
}
}
}
}
}

return [self pushRefspecs:refspecs toRemote:remote withOptions:options error:error progress:progressBlock];
}

- (BOOL)pushNotes:(NSString *)noteRef toRemote:(GTRemote *)remote withOptions:(NSDictionary *)options error:(NSError **)error progress:(GTRemotePushTransferProgressBlock)progressBlock {
NSParameterAssert(remote != nil);

if (noteRef == nil) {
noteRef = [GTNote defaultReferenceNameForRepository:self error:error];

if (noteRef == nil) return NO;
}

GTReference *notesReference = [self lookUpReferenceWithName:noteRef error:error];

if (notesReference == nil) return NO;

return [self pushRefspecs:@[[NSString stringWithFormat:@"%@:%@", noteRef, noteRef]] toRemote:remote withOptions:options error:error progress:progressBlock];
}

#pragma mark - Deletion (Public)
- (BOOL)deleteBranch:(GTBranch *)branch fromRemote:(GTRemote *)remote withOptions:(NSDictionary *)options error:(NSError **)error {
NSParameterAssert(branch != nil);
Expand Down
43 changes: 43 additions & 0 deletions ObjectiveGit/GTRepository.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
@class GTTag;
@class GTTree;
@class GTRemote;
@class GTNote;

NS_ASSUME_NONNULL_BEGIN

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

/// Creates a new note in this repo (using a default notes reference, e.g. "refs/notes/commits")
///
/// note - Note text.
/// theTarget - Object (usually a commit) to which this note refers to.
/// This object must belong to this repository.
/// referenceName - Name for the notes reference in the repo, or nil for default ("refs/notes/commits")
/// author - Signature of the author for this note, and
/// of the note creation time
/// committer - Signature of the committer for this note.
/// overwrite - If set to YES, the note will be overwritten if it already exists.
/// error - Will be filled with a NSError object in case of error.
/// May be NULL.
///
/// Returns the newly created note or nil on error.
- (nullable GTNote *)createNote:(NSString *)note target:(GTObject *)theTarget referenceName:(nullable NSString *)referenceName author:(GTSignature *)author committer:(GTSignature *)committer overwriteIfExists:(BOOL)overwrite error:(NSError **)error;

/// Removes a note attached to object in this repo
///
/// parentObject - Object (usually a commit) to which the note to be removed is attached to.
/// This object must belong to this repository.
/// referenceName - Name for the notes reference in the repo, or nil for default ("refs/notes/commits")
/// author - Signature of the author for this note removal, and
/// of the note removal time
/// committer - Signature of the committer for this note removal.
/// error - Will be filled with a NSError object in case of error.
/// May be NULL.
///
/// Returns the YES on success and NO on error.
- (BOOL)removeNoteFromObject:(GTObject *)parentObject referenceName:(nullable NSString *)referenceName author:(GTSignature *)author committer:(GTSignature *)committer error:(NSError **)error;

/// Enumerates through all stored notes in this repo
///
/// referenceName - Name for the notes reference in the repo, or nil for default ("refs/notes/commits")
/// error - Will be filled with a NSError object in case of error.
/// May be NULL.
/// block - A block to be called on each encountered note object. The block accepts
/// a reference to `note`, an `object` that is annotated with the note.
/// If the block sets `stop` to YES, the iterator is finished.
///
/// Returns YES on overall success or NO on error of any kind.
- (BOOL)enumerateNotesWithReferenceName:(nullable NSString *)referenceName error:(NSError **)error usingBlock:(void (^)(GTNote * __nullable note, GTObject * __nullable object, NSError * __nullable error, BOOL *stop))block;

@end

NS_ASSUME_NONNULL_END
Loading