Skip to content

Commit

Permalink
Merge pull request #576 from slavikus/git-notes
Browse files Browse the repository at this point in the history
Git notes
  • Loading branch information
tiennou authored Dec 5, 2016
2 parents 3480278 + 927cc49 commit 6dc2722
Show file tree
Hide file tree
Showing 12 changed files with 478 additions and 5 deletions.
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"];
}

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

0 comments on commit 6dc2722

Please sign in to comment.