2 // PBGitCommitController.m
5 // Created by Pieter de Bie on 19-09-08.
6 // Copyright 2008 __MyCompanyName__. All rights reserved.
9 #import "PBGitCommitController.h"
10 #import "NSFileHandleExt.h"
11 #import "PBChangedFile.h"
13 @implementation PBGitCommitController
15 @synthesize files, status, busy, amend;
19 self.files = [NSMutableArray array];
23 [commitMessageView setTypingAttributes:[NSDictionary dictionaryWithObject:[NSFont fontWithName:@"Monaco" size:12.0] forKey:NSFontAttributeName]];
25 [unstagedFilesController setFilterPredicate:[NSPredicate predicateWithFormat:@"hasUnstagedChanges == 1"]];
26 [cachedFilesController setFilterPredicate:[NSPredicate predicateWithFormat:@"hasCachedChanges == 1"]];
28 [unstagedFilesController setSortDescriptors:[NSArray arrayWithObjects:
29 [[NSSortDescriptor alloc] initWithKey:@"status" ascending:false],
30 [[NSSortDescriptor alloc] initWithKey:@"path" ascending:true], nil]];
31 [cachedFilesController setSortDescriptors:[NSArray arrayWithObject:
32 [[NSSortDescriptor alloc] initWithKey:@"path" ascending:true]]];
36 [webController closeView];
40 - (void) setAmend:(BOOL)newAmend
42 if (newAmend == amend)
46 if (amend && [[commitMessageView string] length] <= 3)
47 commitMessageView.string = [repository outputForCommand:@"log -1 --pretty=format:%s%n%n%b HEAD"];
52 - (NSArray *) linesFromNotification:(NSNotification *)notification
54 NSDictionary *userInfo = [notification userInfo];
55 NSData *data = [userInfo valueForKey:NSFileHandleNotificationDataItem];
59 NSString* string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
63 // Strip trailing newline
64 if ([string hasSuffix:@"\n"])
65 string = [string substringToIndex:[string length]-1];
67 NSArray *lines = [string componentsSeparatedByString:@"\0"];
71 - (NSString *) parentTree
73 NSString *parent = amend ? @"HEAD^" : @"HEAD";
75 if (![repository parseReference:parent])
76 // We don't have a head ref. Return the empty tree.
77 return @"4b825dc642cb6eb9a060e54bf8d69288fbee4904";
82 - (void) refresh:(id) sender
84 for (PBChangedFile *file in files)
85 file.shouldBeDeleted = YES;
87 self.status = @"Refreshing index…";
90 [repository outputInWorkdirForArguments:[NSArray arrayWithObjects:@"update-index", @"-q", @"--unmerged", @"--ignore-missing", @"--refresh", nil]];
93 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
94 [nc removeObserver:self];
97 NSArray *arguments = [NSArray arrayWithObjects:@"ls-files", @"--others", @"--exclude-standard", @"-z", nil];
98 NSFileHandle *handle = [repository handleInWorkDirForArguments:arguments];
99 [nc addObserver:self selector:@selector(readOtherFiles:) name:NSFileHandleReadToEndOfFileCompletionNotification object:handle];
101 [handle readToEndOfFileInBackgroundAndNotify];
104 handle = [repository handleInWorkDirForArguments:[NSArray arrayWithObjects:@"diff-files", @"-z", nil]];
105 [nc addObserver:self selector:@selector(readUnstagedFiles:) name:NSFileHandleReadToEndOfFileCompletionNotification object:handle];
107 [handle readToEndOfFileInBackgroundAndNotify];
110 handle = [repository handleInWorkDirForArguments:[NSArray arrayWithObjects:@"diff-index", @"--cached", @"-z", [self parentTree], nil]];
111 [nc addObserver:self selector:@selector(readCachedFiles:) name:NSFileHandleReadToEndOfFileCompletionNotification object:handle];
113 [handle readToEndOfFileInBackgroundAndNotify];
121 - (void) doneProcessingIndex
123 [self willChangeValueForKey:@"files"];
125 self.status = @"Ready";
126 NSArray *filesToBeDeleted = [files filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"shouldBeDeleted == 1"]];
127 for (PBChangedFile *file in filesToBeDeleted) {
128 NSLog(@"Deleting file: %@", [file path]);
129 [files removeObject:file];
132 [self didChangeValueForKey:@"files"];
135 - (void) readOtherFiles:(NSNotification *)notification;
137 NSArray *lines = [self linesFromNotification:notification];
138 for (NSString *line in lines) {
139 if ([line length] == 0)
143 // Check if file is already in our index
144 for (PBChangedFile *file in files) {
145 if ([[file path] isEqualToString:line]) {
146 file.shouldBeDeleted = NO;
149 file.hasCachedChanges = NO;
150 file.hasUnstagedChanges = YES;
158 // File does not exist yet, so add it
159 PBChangedFile *file =[[PBChangedFile alloc] initWithPath:line];
161 file.hasCachedChanges = NO;
162 file.hasUnstagedChanges = YES;
163 [files addObject: file];
166 [self doneProcessingIndex];
169 - (void) addFilesFromLines:(NSArray *)lines cached:(BOOL) cached
173 for (NSString *line in lines) {
176 fileStatus = [line componentsSeparatedByString:@" "];
181 NSString *mode = [[fileStatus objectAtIndex:0] substringFromIndex:1];
182 NSString *sha = [fileStatus objectAtIndex:2];
185 // If the file is already added, we shouldn't add it again
186 // but rather update it to incorporate our changes
187 for (PBChangedFile *file in files) {
188 if ([file.path isEqualToString:line]) {
189 file.shouldBeDeleted = NO;
191 file.commitBlobSHA = sha;
192 file.commitBlobMode = mode;
193 file.hasCachedChanges = YES;
196 file.hasUnstagedChanges = YES;
206 PBChangedFile *file = [[PBChangedFile alloc] initWithPath:line];
207 if ([[fileStatus objectAtIndex:4] isEqualToString:@"D"])
208 file.status = DELETED;
209 else if([[fileStatus objectAtIndex:0] isEqualToString:@":000000"])
212 file.status = MODIFIED;
214 file.commitBlobSHA = sha;
215 file.commitBlobMode = mode;
217 file.hasCachedChanges = cached;
218 file.hasUnstagedChanges = !cached;
220 [files addObject: file];
225 - (void) readUnstagedFiles:(NSNotification *)notification
227 NSArray *lines = [self linesFromNotification:notification];
228 [self addFilesFromLines:lines cached:NO];
229 [self doneProcessingIndex];
232 - (void) readCachedFiles:(NSNotification *)notification
234 NSArray *lines = [self linesFromNotification:notification];
235 [self addFilesFromLines:lines cached:YES];
236 [self doneProcessingIndex];
239 - (void) commitFailedBecause:(NSString *)reason
242 self.status = [@"Commit failed: " stringByAppendingString:reason];
243 [[NSAlert alertWithMessageText:@"Commit failed"
247 informativeTextWithFormat:reason] runModal];
251 - (IBAction) commit:(id) sender
253 if ([[cachedFilesController arrangedObjects] count] == 0) {
254 [[NSAlert alertWithMessageText:@"No changes to commit"
258 informativeTextWithFormat:@"You must first stage some changes before committing"] runModal];
262 NSString *commitMessage = [commitMessageView string];
263 if ([commitMessage length] < 3) {
264 [[NSAlert alertWithMessageText:@"Commitmessage missing"
268 informativeTextWithFormat:@"Please enter a commit message before committing"] runModal];
272 [cachedFilesController setSelectionIndexes:[NSIndexSet indexSet]];
273 [unstagedFilesController setSelectionIndexes:[NSIndexSet indexSet]];
275 NSString *commitSubject;
276 NSRange newLine = [commitMessage rangeOfString:@"\n"];
277 if (newLine.location == NSNotFound)
278 commitSubject = commitMessage;
280 commitSubject = [commitMessage substringToIndex:newLine.location];
282 commitSubject = [@"commit: " stringByAppendingString:commitSubject];
285 self.status = @"Creating tree..";
286 NSString *tree = [repository outputForCommand:@"write-tree"];
287 if ([tree length] != 40)
288 return [self commitFailedBecause:@"Could not create a tree"];
292 NSMutableArray *arguments = [NSMutableArray arrayWithObjects:@"commit-tree", tree, nil];
293 NSString *parent = amend ? @"HEAD^" : @"HEAD";
294 if ([repository parseReference:parent]) {
295 [arguments addObject:@"-p"];
296 [arguments addObject:parent];
299 NSString *commit = [repository outputForArguments:arguments
300 inputString:commitMessage
303 if (ret || [commit length] != 40)
304 return [self commitFailedBecause:@"Could not create a commit object"];
306 [repository outputForArguments:[NSArray arrayWithObjects:@"update-ref", @"-m", commitSubject, @"HEAD", commit, nil]
309 return [self commitFailedBecause:@"Could not update HEAD"];
311 [webController setStateMessage:[NSString stringWithFormat:@"Successfully created commit %@", commit]];
313 repository.hasChanged = YES;
315 [commitMessageView setString:@""];
321 - (void) stageHunk:(NSString *)hunk reverse:(BOOL)reverse
323 NSMutableArray *array = [NSMutableArray arrayWithObjects:@"apply", @"--cached", nil];
325 [array addObject:@"--reverse"];
328 NSString *error = [repository outputForArguments:array
332 NSLog(@"Error: %@", error);
333 [self refresh:self]; // TODO: We should do this smarter by checking if the file diff is empty, which is faster.