GitX 0.6.1
[GitX.git] / PBGitIndexController.m
blobd5dbb912acb4af88a0f5dbf4ac0189aed02e4c7e
1 //
2 //  PBGitIndexController.m
3 //  GitX
4 //
5 //  Created by Pieter de Bie on 18-11-08.
6 //  Copyright 2008 Pieter de Bie. All rights reserved.
7 //
9 #import "PBGitIndexController.h"
10 #import "PBChangedFile.h"
11 #import "PBGitRepository.h"
13 #define FileChangesTableViewType @"GitFileChangedType"
15 @implementation PBGitIndexController
17 @synthesize contextSize;
19 - (void)awakeFromNib
21         contextSize = 3;
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];
40         }
42         int ret = 1;
43         [commitController.repository outputForArguments:[NSArray arrayWithObjects:@"update-index", @"--add", @"--remove", @"-z", @"--stdin", nil]
44          inputString:input retValue:&ret];
46         if (ret)
47         {
48                 NSLog(@"Error when updating index. Retvalue: %i", ret);
49                 return;
50         }
52         for (PBChangedFile *file in files)
53         {
54                 file.hasUnstagedChanges = NO;
55                 file.hasCachedChanges = YES;
56         }
59 - (void) unstageFiles:(NSArray *)files
61         NSMutableString *input = [NSMutableString string];
62         
63         for (PBChangedFile *file in files) {
64                 [input appendString:[file indexInfo]];
65         }
67         int ret = 1;
68         [commitController.repository outputForArguments:[NSArray arrayWithObjects:@"update-index", @"-z", @"--index-info", nil]
69          inputString:input retValue:&ret];
70         
71         if (ret)
72         {
73                 NSLog(@"Error when updating index. Retvalue: %i", ret);
74                 return;
75         }
76         
77         for (PBChangedFile *file in files)
78         {
79                 file.hasUnstagedChanges = YES;
80                 file.hasCachedChanges = NO;
81         }
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];
92         }
93         NSString *filesAsString = [fileList componentsJoinedByString:@"\n"];
95         // Write to the file
96         NSString *gitIgnoreName = [commitController.repository gitIgnoreFilename];
98         NSStringEncoding enc;
99         NSError *error = nil;
100         NSMutableString *ignoreFile;
102         if (![[NSFileManager defaultManager] fileExistsAtPath:gitIgnoreName]) {
103                 ignoreFile = [filesAsString mutableCopy];
104         } else {
105                 ignoreFile = [NSMutableString stringWithContentsOfFile:gitIgnoreName usedEncoding:&enc error:&error];
106                 if (error) {
107                         [[NSAlert alertWithError:error] runModal];
108                         return;
109                 }
110                 // Add a newline if not yet present
111                 if ([ignoreFile characterAtIndex:([ignoreFile length] - 1)] != '\n')
112                         [ignoreFile appendString:@"\n"];
113                 [ignoreFile appendString:filesAsString];
114         }
116         [ignoreFile writeToFile:gitIgnoreName atomically:YES encoding:enc error:&error];
117         if (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
141                                                                                                                   error:&error];
142                 if (error)
143                         return nil;
145                 return contents;
146         }
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];
157         int ret = 1;
158         [commitController.repository outputForArguments:arguments inputString:input retValue:&ret];
159         if (ret) {
160                 [[NSAlert alertWithMessageText:@"Reverting changes failed"
161                                                  defaultButton:nil
162                                            alternateButton:nil
163                                                    otherButton:nil
164                          informativeTextWithFormat:@"Reverting changes failed with error code %i", ret] runModal];
165                 return;
166         }
168         for (PBChangedFile *file in files)
169                 file.hasUnstagedChanges = NO;
172 - (void) revertChangesForFiles:(NSArray *)files
174         int ret = [[NSAlert alertWithMessageText:@"Revert changes"
175                                          defaultButton:nil
176                                    alternateButton:@"Cancel"
177                                            otherButton:nil
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) {
190                         return NO;
191                 }
192         }
193         return YES;
196 - (NSMenu *) menuForTable:(NSTableView *)table
198         NSMenu *menu = [[NSMenu alloc] init];
199         id controller = [table tag] == 0 ? unstagedFilesController : stagedFilesController;
200         NSArray *selectedFiles = [controller selectedObjects];
202         // Unstaged changes
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];
208         }
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];
214         }
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];
222         // Attempt to ignore
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];
229         }
231         for (PBChangedFile *file in selectedFiles)
232                 if (!file.hasUnstagedChanges)
233                         return menu;
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];
248         
249         return menu;
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];
275         }
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];
309         else
310                 [self unstageFiles:files];
313 - (void) rowClicked:(NSCell *)sender
315         NSTableView *tableView = (NSTableView *)[sender controlView];
316         if([tableView numberOfSelectedRows] != 1)
317                 return;
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];
342     return YES;
345 - (NSDragOperation)tableView:(NSTableView*)tableView
346                                 validateDrop:(id <NSDraggingInfo>)info
347                                  proposedRow:(int)row
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
359                           row:(int)row
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];
371         else
372                 [self stageFiles:files];
374         return YES;
377 - (NSString *) contextParameter
379         return [[NSString alloc] initWithFormat:@"-U%i", contextSize];
382 # pragma mark WebKit Accessibility
384 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
386         return NO;
389 @end