// // PBGitCommitController.m // GitX // // Created by Pieter de Bie on 19-09-08. // Copyright 2008 __MyCompanyName__. All rights reserved. // #import "PBGitCommitController.h" #import "NSFileHandleExt.h" #import "PBChangedFile.h" #import "PBWebChangesController.h" #import "PBGitIndex.h" #import "NSString_RegEx.h" @interface PBGitCommitController (PrivateMethods) - (void)processHunk:(NSString *)hunk stage:(BOOL)stage reverse:(BOOL)reverse; @end @implementation PBGitCommitController @synthesize status, index; - (id)initWithRepository:(PBGitRepository *)theRepository superController:(PBGitWindowController *)controller { if (!(self = [super initWithRepository:theRepository superController:controller])) return nil; index = [[PBGitIndex alloc] initWithRepository:theRepository workingDirectory:[NSURL fileURLWithPath:[theRepository workingDirectory]]]; [index refresh]; return self; } - (BOOL)busy { return NO; } - (void)awakeFromNib { [super awakeFromNib]; [commitMessageView setTypingAttributes:[NSDictionary dictionaryWithObject:[NSFont fontWithName:@"Monaco" size:12.0] forKey:NSFontAttributeName]]; [unstagedFilesController setFilterPredicate:[NSPredicate predicateWithFormat:@"hasUnstagedChanges == 1"]]; [cachedFilesController setFilterPredicate:[NSPredicate predicateWithFormat:@"hasStagedChanges == 1"]]; [unstagedFilesController setSortDescriptors:[NSArray arrayWithObjects: [[NSSortDescriptor alloc] initWithKey:@"status" ascending:false], [[NSSortDescriptor alloc] initWithKey:@"path" ascending:true], nil]]; [cachedFilesController setSortDescriptors:[NSArray arrayWithObject: [[NSSortDescriptor alloc] initWithKey:@"path" ascending:true]]]; } - (void) removeView { [webController closeView]; [super finalize]; } - (NSResponder *)firstResponder; { return commitMessageView; } - (IBAction)signOff:(id)sender { if (![repository.config valueForKeyPath:@"user.name"] || ![repository.config valueForKeyPath:@"user.email"]) return [[repository windowController] showMessageSheet:@"User's name not set" infoText:@"Signing off a commit requires setting user.name and user.email in your git config"]; NSString *SOBline = [NSString stringWithFormat:@"Signed-off-by: %@ <%@>", [repository.config valueForKeyPath:@"user.name"], [repository.config valueForKeyPath:@"user.email"]]; if([commitMessageView.string rangeOfString:SOBline].location == NSNotFound) { NSArray *selectedRanges = [commitMessageView selectedRanges]; commitMessageView.string = [NSString stringWithFormat:@"%@\n\n%@", commitMessageView.string, SOBline]; [commitMessageView setSelectedRanges: selectedRanges]; } } - (void) refresh:(id) sender { [index refresh]; // Reload refs (in case HEAD changed) [repository reloadRefs]; } - (void) updateView { [self refresh:nil]; } - (void) commitFailedBecause:(NSString *)reason { //self.busy--; self.status = [@"Commit failed: " stringByAppendingString:reason]; [[repository windowController] showMessageSheet:@"Commit failed" infoText:reason]; return; } - (IBAction) commit:(id) sender { if ([[NSFileManager defaultManager] fileExistsAtPath:[repository.fileURL.path stringByAppendingPathComponent:@"MERGE_HEAD"]]) { [[repository windowController] showMessageSheet:@"Cannot commit merges" infoText:@"GitX cannot commit merges yet. Please commit your changes from the command line."]; return; } if ([[cachedFilesController arrangedObjects] count] == 0) { [[repository windowController] showMessageSheet:@"No changes to commit" infoText:@"You must first stage some changes before committing"]; return; } NSString *commitMessage = [commitMessageView string]; if ([commitMessage length] < 3) { [[repository windowController] showMessageSheet:@"Commitmessage missing" infoText:@"Please enter a commit message before committing"]; return; } [cachedFilesController setSelectionIndexes:[NSIndexSet indexSet]]; [unstagedFilesController setSelectionIndexes:[NSIndexSet indexSet]]; NSString *commitSubject; NSRange newLine = [commitMessage rangeOfString:@"\n"]; if (newLine.location == NSNotFound) commitSubject = commitMessage; else commitSubject = [commitMessage substringToIndex:newLine.location]; commitSubject = [@"commit: " stringByAppendingString:commitSubject]; NSString *commitMessageFile; commitMessageFile = [repository.fileURL.path stringByAppendingPathComponent:@"COMMIT_EDITMSG"]; [commitMessage writeToFile:commitMessageFile atomically:YES encoding:NSUTF8StringEncoding error:nil]; //self.busy++; self.status = @"Creating tree.."; NSString *tree = [repository outputForCommand:@"write-tree"]; if ([tree length] != 40) return [self commitFailedBecause:@"Could not create a tree"]; int ret; NSMutableArray *arguments = [NSMutableArray arrayWithObjects:@"commit-tree", tree, nil]; NSString *parent = index.amend ? @"HEAD^" : @"HEAD"; if ([repository parseReference:parent]) { [arguments addObject:@"-p"]; [arguments addObject:parent]; } NSString *commit = [repository outputForArguments:arguments inputString:commitMessage byExtendingEnvironment:amendEnvironment retValue: &ret]; if (ret || [commit length] != 40) return [self commitFailedBecause:@"Could not create a commit object"]; if (![repository executeHook:@"pre-commit" output:nil]) return [self commitFailedBecause:@"Pre-commit hook failed"]; if (![repository executeHook:@"commit-msg" withArgs:[NSArray arrayWithObject:commitMessageFile] output:nil]) return [self commitFailedBecause:@"Commit-msg hook failed"]; [repository outputForArguments:[NSArray arrayWithObjects:@"update-ref", @"-m", commitSubject, @"HEAD", commit, nil] retValue: &ret]; if (ret) return [self commitFailedBecause:@"Could not update HEAD"]; if (![repository executeHook:@"post-commit" output:nil]) [webController setStateMessage:[NSString stringWithFormat:@"Post-commit hook failed, however, successfully created commit %@", commit]]; else [webController setStateMessage:[NSString stringWithFormat:@"Successfully created commit %@", commit]]; repository.hasChanged = YES; //self.busy--; [commitMessageView setString:@""]; amendEnvironment = nil; index.amend = NO; } - (void) stageHunk:(NSString *)hunk reverse:(BOOL)reverse { [self processHunk:hunk stage:TRUE reverse:reverse]; } - (void)discardHunk:(NSString *)hunk { [self processHunk:hunk stage:FALSE reverse:TRUE]; } - (void)processHunk:(NSString *)hunk stage:(BOOL)stage reverse:(BOOL)reverse { NSMutableArray *array = [NSMutableArray arrayWithObjects:@"apply", nil]; if (stage) [array addObject:@"--cached"]; if (reverse) [array addObject:@"--reverse"]; int ret = 1; NSString *error = [repository outputForArguments:array inputString:hunk retValue:&ret]; // FIXME: show this error, rather than just logging it if (ret) NSLog(@"Error: %@", error); // TODO: We should do this smarter by checking if the file diff is empty, which is faster. [self refresh:self]; } @end