2 // PBGitIndexController.m
5 // Created by Pieter de Bie on 18-11-08.
6 // Copyright 2008 Pieter de Bie. All rights reserved.
9 #import "PBGitIndexController.h"
10 #import "PBChangedFile.h"
11 #import "PBGitRepository.h"
13 #define FileChangesTableViewType @"GitFileChangedType"
15 @implementation PBGitIndexController
17 @synthesize contextSize;
23 [unstagedTable setDoubleAction:@selector(tableClicked:)];
24 [stagedTable setDoubleAction:@selector(tableClicked:)];
26 [unstagedTable setTarget:self];
27 [stagedTable setTarget:self];
29 [unstagedTable registerForDraggedTypes: [NSArray arrayWithObject:FileChangesTableViewType]];
30 [stagedTable registerForDraggedTypes: [NSArray arrayWithObject:FileChangesTableViewType]];
34 - (void) stageFiles:(NSArray *)files
36 NSMutableString *input = [NSMutableString string];
38 for (PBChangedFile *file in files) {
39 [input appendFormat:@"%@\0", file.path];
43 [commitController.repository outputForArguments:[NSArray arrayWithObjects:@"update-index", @"--add", @"--remove", @"-z", @"--stdin", nil]
44 inputString:input retValue:&ret];
48 NSLog(@"Error when updating index. Retvalue: %i", ret);
52 for (PBChangedFile *file in files)
54 file.hasUnstagedChanges = NO;
55 file.hasCachedChanges = YES;
59 - (void) unstageFiles:(NSArray *)files
61 NSMutableString *input = [NSMutableString string];
63 for (PBChangedFile *file in files) {
64 [input appendString:[file indexInfo]];
68 [commitController.repository outputForArguments:[NSArray arrayWithObjects:@"update-index", @"-z", @"--index-info", nil]
69 inputString:input retValue:&ret];
73 NSLog(@"Error when updating index. Retvalue: %i", ret);
77 for (PBChangedFile *file in files)
79 file.hasUnstagedChanges = YES;
80 file.hasCachedChanges = NO;
84 - (void) ignoreFiles:(NSArray *)files
86 // Build output string
87 NSMutableArray *fileList = [NSMutableArray array];
88 for (PBChangedFile *file in files) {
89 NSString *name = file.path;
90 if ([name length] > 0)
91 [fileList addObject:name];
93 NSString *filesAsString = [fileList componentsJoinedByString:@"\n"];
96 NSString *gitIgnoreName = [commitController.repository gitIgnoreFilename];
100 NSMutableString *ignoreFile;
102 if (![[NSFileManager defaultManager] fileExistsAtPath:gitIgnoreName]) {
103 ignoreFile = [filesAsString mutableCopy];
105 ignoreFile = [NSMutableString stringWithContentsOfFile:gitIgnoreName usedEncoding:&enc error:&error];
107 [[NSAlert alertWithError:error] runModal];
110 // Add a newline if not yet present
111 if ([ignoreFile characterAtIndex:([ignoreFile length] - 1)] != '\n')
112 [ignoreFile appendString:@"\n"];
113 [ignoreFile appendString:filesAsString];
116 [ignoreFile writeToFile:gitIgnoreName atomically:YES encoding:enc error:&error];
118 [[NSAlert alertWithError:error] runModal];
121 # pragma mark Displaying diffs
123 - (NSString *) stagedChangesForFile:(PBChangedFile *)file
125 NSString *indexPath = [@":0:" stringByAppendingString:file.path];
127 if (file.status == NEW)
128 return [commitController.repository outputForArguments:[NSArray arrayWithObjects:@"show", indexPath, nil]];
130 return [commitController.repository outputInWorkdirForArguments:[NSArray arrayWithObjects:@"diff-index", [self contextParameter], @"--cached", [commitController parentTree], @"--", file.path, nil]];
133 - (NSString *)unstagedChangesForFile:(PBChangedFile *)file
135 if (file.status == NEW) {
136 NSStringEncoding encoding;
137 NSError *error = nil;
138 NSString *path = [[commitController.repository workingDirectory] stringByAppendingPathComponent:file.path];
139 NSString *contents = [NSString stringWithContentsOfFile:path
140 usedEncoding:&encoding
148 return [commitController.repository outputInWorkdirForArguments:[NSArray arrayWithObjects:@"diff", [self contextParameter], @"--", file.path, nil]];
151 - (void) forceRevertChangesForFiles:(NSArray *)files
153 NSArray *paths = [files valueForKey:@"path"];
154 NSString *input = [paths componentsJoinedByString:@"\0"];
156 NSArray *arguments = [NSArray arrayWithObjects:@"checkout-index", @"--index", @"--quiet", @"--force", @"-z", @"--stdin", nil];
158 [commitController.repository outputForArguments:arguments inputString:input retValue:&ret];
160 [[NSAlert alertWithMessageText:@"Reverting changes failed"
164 informativeTextWithFormat:@"Reverting changes failed with error code %i", ret] runModal];
168 for (PBChangedFile *file in files)
169 file.hasUnstagedChanges = NO;
172 - (void) revertChangesForFiles:(NSArray *)files
174 int ret = [[NSAlert alertWithMessageText:@"Revert changes"
176 alternateButton:@"Cancel"
178 informativeTextWithFormat:@"Are you sure you wish to revert changes?\n\n You cannot undo this operation."] runModal];
180 if (ret == NSAlertDefaultReturn)
181 [self forceRevertChangesForFiles:files];
185 # pragma mark Context Menu methods
186 - (BOOL) allSelectedCanBeIgnored:(NSArray *)selectedFiles
188 for (PBChangedFile *selectedItem in selectedFiles) {
189 if (selectedItem.status != NEW) {
196 - (NSMenu *) menuForTable:(NSTableView *)table
198 NSMenu *menu = [[NSMenu alloc] init];
199 id controller = [table tag] == 0 ? unstagedFilesController : stagedFilesController;
200 NSArray *selectedFiles = [controller selectedObjects];
203 if ([table tag] == 0) {
204 NSMenuItem *stageItem = [[NSMenuItem alloc] initWithTitle:@"Stage Changes" action:@selector(stageFilesAction:) keyEquivalent:@""];
205 [stageItem setTarget:self];
206 [stageItem setRepresentedObject:selectedFiles];
207 [menu addItem:stageItem];
209 else if ([table tag] == 1) {
210 NSMenuItem *unstageItem = [[NSMenuItem alloc] initWithTitle:@"Unstage Changes" action:@selector(unstageFilesAction:) keyEquivalent:@""];
211 [unstageItem setTarget:self];
212 [unstageItem setRepresentedObject:selectedFiles];
213 [menu addItem:unstageItem];
216 NSString *title = [selectedFiles count] == 1 ? @"Open file" : @"Open files";
217 NSMenuItem *openItem = [[NSMenuItem alloc] initWithTitle:title action:@selector(openFilesAction:) keyEquivalent:@""];
218 [openItem setTarget:self];
219 [openItem setRepresentedObject:selectedFiles];
220 [menu addItem:openItem];
223 if ([self allSelectedCanBeIgnored:selectedFiles]) {
224 NSString *ignoreText = [selectedFiles count] == 1 ? @"Ignore File": @"Ignore Files";
225 NSMenuItem *ignoreItem = [[NSMenuItem alloc] initWithTitle:ignoreText action:@selector(ignoreFilesAction:) keyEquivalent:@""];
226 [ignoreItem setTarget:self];
227 [ignoreItem setRepresentedObject:selectedFiles];
228 [menu addItem:ignoreItem];
231 for (PBChangedFile *file in selectedFiles)
232 if (!file.hasUnstagedChanges)
235 NSMenuItem *revertItem = [[NSMenuItem alloc] initWithTitle:@"Revert Changes…" action:@selector(revertFilesAction:) keyEquivalent:@""];
236 [revertItem setTarget:self];
237 [revertItem setAlternate:NO];
238 [revertItem setRepresentedObject:selectedFiles];
240 [menu addItem:revertItem];
242 NSMenuItem *revertForceItem = [[NSMenuItem alloc] initWithTitle:@"Revert Changes" action:@selector(forceRevertFilesAction:) keyEquivalent:@""];
243 [revertForceItem setTarget:self];
244 [revertForceItem setAlternate:YES];
245 [revertForceItem setRepresentedObject:selectedFiles];
246 [revertForceItem setKeyEquivalentModifierMask:NSAlternateKeyMask];
247 [menu addItem:revertForceItem];
252 - (void) stageFilesAction:(id) sender
254 [self stageFiles:[sender representedObject]];
257 - (void) unstageFilesAction:(id) sender
259 [self unstageFiles:[sender representedObject]];
262 - (void) openFilesAction:(id) sender
264 NSArray *files = [sender representedObject];
265 NSString *workingDirectory = [commitController.repository workingDirectory];
266 for (PBChangedFile *file in files)
267 [[NSWorkspace sharedWorkspace] openFile:[workingDirectory stringByAppendingPathComponent:[file path]]];
270 - (void) ignoreFilesAction:(id) sender
272 NSArray *selectedFiles = [sender representedObject];
273 if ([selectedFiles count] > 0) {
274 [self ignoreFiles:selectedFiles];
276 [commitController refresh:NULL];
279 - (void) revertFilesAction:(id) sender
281 NSArray *selectedFiles = [sender representedObject];
282 if ([selectedFiles count] > 0)
283 [self revertChangesForFiles:selectedFiles];
286 - (void) forceRevertFilesAction:(id) sender
288 NSArray *selectedFiles = [sender representedObject];
289 if ([selectedFiles count] > 0)
290 [self forceRevertChangesForFiles:selectedFiles];
294 # pragma mark TableView icon delegate
295 - (void)tableView:(NSTableView*)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn*)tableColumn row:(int)rowIndex
297 id controller = [tableView tag] == 0 ? unstagedFilesController : stagedFilesController;
298 [[tableColumn dataCell] setImage:[[[controller arrangedObjects] objectAtIndex:rowIndex] icon]];
301 - (void) tableClicked:(NSTableView *) tableView
303 NSArrayController *controller = [tableView tag] == 0 ? unstagedFilesController : stagedFilesController;
305 NSIndexSet *selectionIndexes = [tableView selectedRowIndexes];
306 NSArray *files = [[controller arrangedObjects] objectsAtIndexes:selectionIndexes];
307 if ([tableView tag] == 0)
308 [self stageFiles:files];
310 [self unstageFiles:files];
313 - (void) rowClicked:(NSCell *)sender
315 NSTableView *tableView = (NSTableView *)[sender controlView];
316 if([tableView numberOfSelectedRows] != 1)
318 [self tableClicked: tableView];
321 - (BOOL)tableView:(NSTableView *)tv
322 writeRowsWithIndexes:(NSIndexSet *)rowIndexes
323 toPasteboard:(NSPasteboard*)pboard
325 // Copy the row numbers to the pasteboard.
326 [pboard declareTypes:[NSArray arrayWithObjects:FileChangesTableViewType, NSFilenamesPboardType, nil] owner:self];
328 // Internal, for dragging from one tableview to the other
329 NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes];
330 [pboard setData:data forType:FileChangesTableViewType];
332 // External, to drag them to for example XCode or Textmate
333 NSArrayController *controller = [tv tag] == 0 ? unstagedFilesController : stagedFilesController;
334 NSArray *files = [[controller arrangedObjects] objectsAtIndexes:rowIndexes];
335 NSString *workingDirectory = [commitController.repository workingDirectory];
337 NSMutableArray *filenames = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
338 for (PBChangedFile *file in files)
339 [filenames addObject:[workingDirectory stringByAppendingPathComponent:[file path]]];
341 [pboard setPropertyList:filenames forType:NSFilenamesPboardType];
345 - (NSDragOperation)tableView:(NSTableView*)tableView
346 validateDrop:(id <NSDraggingInfo>)info
348 proposedDropOperation:(NSTableViewDropOperation)operation
350 if ([info draggingSource] == tableView)
351 return NSDragOperationNone;
353 [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
354 return NSDragOperationCopy;
357 - (BOOL)tableView:(NSTableView *)aTableView
358 acceptDrop:(id <NSDraggingInfo>)info
360 dropOperation:(NSTableViewDropOperation)operation
362 NSPasteboard* pboard = [info draggingPasteboard];
363 NSData* rowData = [pboard dataForType:FileChangesTableViewType];
364 NSIndexSet* rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:rowData];
366 NSArrayController *controller = [aTableView tag] == 0 ? stagedFilesController : unstagedFilesController;
367 NSArray *files = [[controller arrangedObjects] objectsAtIndexes:rowIndexes];
369 if ([aTableView tag] == 0)
370 [self unstageFiles:files];
372 [self stageFiles:files];
377 - (NSString *) contextParameter
379 return [[NSString alloc] initWithFormat:@"-U%i", contextSize];
382 # pragma mark WebKit Accessibility
384 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector