mirror of
https://github.com/kennethreitz-archive/gitx.git
synced 2026-06-05 23:40:18 +00:00
Merge branch 'pu/pb/index_refactor'
* pu/pb/index_refactor: (24 commits) PBGitIndex: post notifications when index stuff fails GitIndexController: reorder methods a bit, remove unnecessary stuff PBGitIndex: Migrate discardChanges to the GitIndex CommitController: Make commit message editable after commit failed GitIndex: Fix a few comments GitIndex: explicitly tell when stuff is updated Remove cruft Show previous commit message when amending CommitController: Empty commit title when commit is successful CommitView: Remove cruft Add failed commit notifications GitIndex: add commit notifications CommitController: Add status messages for index operations GitIndex: Add a few notifications CommitView: Migrate patch apply stuff to GitIndex GitIndex: Add support for applying patches CommitController: Replace commit method with the one from GitIndex GitIndex: add basic commit method GitIndexController: Migrate stageFiles functions to GitIndex GitIndex: Add methods to stage and unstage files ... Conflicts: PBGitCommitController.m PBGitIndexController.h PBGitIndexController.m
This commit is contained in:
@@ -97,6 +97,7 @@
|
||||
F59116E90E843BCB0072CCB1 /* PBGitCommitController.m in Sources */ = {isa = PBXBuildFile; fileRef = F59116E80E843BCB0072CCB1 /* PBGitCommitController.m */; };
|
||||
F593DF780E9E636C003A8559 /* PBFileChangesTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = F593DF770E9E636C003A8559 /* PBFileChangesTableView.m */; };
|
||||
F5945E170E02B0C200706420 /* PBGitRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = F5945E160E02B0C200706420 /* PBGitRepository.m */; };
|
||||
F59F1DD5105C4FF300115F88 /* PBGitIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = F59F1DD4105C4FF300115F88 /* PBGitIndex.m */; };
|
||||
F5AD56790E79B78100EDAAFE /* PBCommitList.m in Sources */ = {isa = PBXBuildFile; fileRef = F5AD56780E79B78100EDAAFE /* PBCommitList.m */; };
|
||||
F5B721C40E05CF7E00AF29DC /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = F5B721C20E05CF7E00AF29DC /* MainMenu.xib */; };
|
||||
F5C007750E731B48007B84B2 /* PBGitRef.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C007740E731B48007B84B2 /* PBGitRef.m */; };
|
||||
@@ -250,6 +251,8 @@
|
||||
F593DF770E9E636C003A8559 /* PBFileChangesTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBFileChangesTableView.m; sourceTree = "<group>"; };
|
||||
F5945E150E02B0C200706420 /* PBGitRepository.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitRepository.h; sourceTree = "<group>"; };
|
||||
F5945E160E02B0C200706420 /* PBGitRepository.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBGitRepository.m; sourceTree = "<group>"; };
|
||||
F59F1DD3105C4FF300115F88 /* PBGitIndex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitIndex.h; sourceTree = "<group>"; };
|
||||
F59F1DD4105C4FF300115F88 /* PBGitIndex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBGitIndex.m; sourceTree = "<group>"; };
|
||||
F5AD56770E79B78100EDAAFE /* PBCommitList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBCommitList.h; sourceTree = "<group>"; };
|
||||
F5AD56780E79B78100EDAAFE /* PBCommitList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBCommitList.m; sourceTree = "<group>"; };
|
||||
F5B721C30E05CF7E00AF29DC /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
@@ -467,6 +470,7 @@
|
||||
F56174540E05887E001DCD79 /* Git */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F59F1DD2105C4FDE00115F88 /* Index */,
|
||||
F5E927E30E883D6800056E75 /* Commit */,
|
||||
F5E927E10E883D2E00056E75 /* History */,
|
||||
F5945E150E02B0C200706420 /* PBGitRepository.h */,
|
||||
@@ -549,6 +553,17 @@
|
||||
name = SpeedTest;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F59F1DD2105C4FDE00115F88 /* Index */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F5E927F60E883E7200056E75 /* PBChangedFile.h */,
|
||||
F5E927F70E883E7200056E75 /* PBChangedFile.m */,
|
||||
F59F1DD3105C4FF300115F88 /* PBGitIndex.h */,
|
||||
F59F1DD4105C4FF300115F88 /* PBGitIndex.m */,
|
||||
);
|
||||
name = Index;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F5B161BB0EAB6E0C005A1DE1 /* Diff */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -604,8 +619,6 @@
|
||||
children = (
|
||||
93F7857D0EA3ABF100C1F443 /* PBCommitMessageView.h */,
|
||||
93F7857E0EA3ABF100C1F443 /* PBCommitMessageView.m */,
|
||||
F5E927F60E883E7200056E75 /* PBChangedFile.h */,
|
||||
F5E927F70E883E7200056E75 /* PBChangedFile.m */,
|
||||
F593DF760E9E636C003A8559 /* PBFileChangesTableView.h */,
|
||||
F593DF770E9E636C003A8559 /* PBFileChangesTableView.m */,
|
||||
);
|
||||
@@ -854,6 +867,7 @@
|
||||
47DBDBCA0E95016F00671A1E /* PBNSURLPathUserDefaultsTransfomer.m in Sources */,
|
||||
F562C8870FE1766C000EC528 /* NSString_RegEx.m in Sources */,
|
||||
EB2A734A0FEE3F09006601CF /* PBCollapsibleSplitView.m in Sources */,
|
||||
F59F1DD5105C4FF300115F88 /* PBGitIndex.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
+8
-24
@@ -9,12 +9,12 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "PBViewController.h"
|
||||
|
||||
@class PBGitIndexController;
|
||||
@class PBIconAndTextCell;
|
||||
@class PBWebChangesController;
|
||||
@class PBGitIndexController, PBIconAndTextCell, PBWebChangesController, PBGitIndex;
|
||||
|
||||
@interface PBGitCommitController : PBViewController {
|
||||
NSMutableArray *files;
|
||||
// This might have to transfer over to the PBGitRepository
|
||||
// object sometime
|
||||
PBGitIndex *index;
|
||||
|
||||
IBOutlet NSTextView *commitMessageView;
|
||||
IBOutlet NSArrayController *unstagedFilesController;
|
||||
@@ -24,28 +24,12 @@
|
||||
IBOutlet PBWebChangesController *webController;
|
||||
|
||||
NSString *status;
|
||||
|
||||
// We use busy as a count of active processes.
|
||||
// You can increase it when your process start
|
||||
// And decrease it after you have finished.
|
||||
int busy;
|
||||
BOOL amend;
|
||||
NSDictionary *amendEnvironment;
|
||||
|
||||
BOOL busy;
|
||||
}
|
||||
|
||||
@property (retain) NSMutableArray *files;
|
||||
@property (copy) NSString *status;
|
||||
@property (assign) int busy;
|
||||
@property (assign) BOOL amend;
|
||||
|
||||
- (void) readCachedFiles:(NSNotification *)notification;
|
||||
- (void) readOtherFiles:(NSNotification *)notification;
|
||||
- (void) readUnstagedFiles:(NSNotification *)notification;
|
||||
- (void) stageHunk: (NSString *)hunk reverse:(BOOL)reverse;
|
||||
- (void)discardHunk:(NSString *)hunk;
|
||||
|
||||
- (NSString *)parentTree;
|
||||
@property(copy) NSString *status;
|
||||
@property(readonly) PBGitIndex *index;
|
||||
@property(assign) BOOL busy;
|
||||
|
||||
- (IBAction) refresh:(id) sender;
|
||||
- (IBAction) commit:(id) sender;
|
||||
|
||||
+76
-333
@@ -10,26 +10,44 @@
|
||||
#import "NSFileHandleExt.h"
|
||||
#import "PBChangedFile.h"
|
||||
#import "PBWebChangesController.h"
|
||||
#import "NSString_RegEx.h"
|
||||
#import "PBGitIndexController.h"
|
||||
#import "PBGitIndex.h"
|
||||
|
||||
@interface PBGitCommitController (PrivateMethods)
|
||||
- (NSArray *) linesFromNotification:(NSNotification *)notification;
|
||||
- (void) doneProcessingIndex;
|
||||
- (NSMutableDictionary *)dictionaryForLines:(NSArray *)lines;
|
||||
- (void) addFilesFromDictionary:(NSMutableDictionary *)dictionary staged:(BOOL)staged tracked:(BOOL)tracked;
|
||||
- (void)processHunk:(NSString *)hunk stage:(BOOL)stage reverse:(BOOL)reverse;
|
||||
@interface PBGitCommitController ()
|
||||
- (void)refreshFinished:(NSNotification *)notification;
|
||||
- (void)commitStatusUpdated:(NSNotification *)notification;
|
||||
- (void)commitFinished:(NSNotification *)notification;
|
||||
- (void)commitFailed:(NSNotification *)notification;
|
||||
- (void)amendCommit:(NSNotification *)notification;
|
||||
- (void)indexChanged:(NSNotification *)notification;
|
||||
- (void)indexOperationFailed:(NSNotification *)notification;
|
||||
@end
|
||||
|
||||
@implementation PBGitCommitController
|
||||
|
||||
@synthesize files, status, busy, amend;
|
||||
@synthesize status, index, busy;
|
||||
|
||||
- (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];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshFinished:) name:PBGitIndexFinishedIndexRefresh object:index];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(commitStatusUpdated:) name:PBGitIndexCommitStatus object:index];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(commitFinished:) name:PBGitIndexFinishedCommit object:index];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(commitFailed:) name:PBGitIndexCommitFailed object:index];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(amendCommit:) name:PBGitIndexAmendMessageAvailable object:index];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(indexChanged:) name:PBGitIndexIndexUpdated object:index];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(indexOperationFailed:) name:PBGitIndexOperationFailed object:index];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
self.files = [NSMutableArray array];
|
||||
[super awakeFromNib];
|
||||
[self refresh:self];
|
||||
|
||||
[commitMessageView setTypingAttributes:[NSDictionary dictionaryWithObject:[NSFont fontWithName:@"Monaco" size:12.0] forKey:NSFontAttributeName]];
|
||||
|
||||
@@ -41,12 +59,17 @@
|
||||
[[NSSortDescriptor alloc] initWithKey:@"path" ascending:true], nil]];
|
||||
[cachedFilesController setSortDescriptors:[NSArray arrayWithObject:
|
||||
[[NSSortDescriptor alloc] initWithKey:@"path" ascending:true]]];
|
||||
|
||||
[cachedFilesController setAutomaticallyRearrangesObjects:NO];
|
||||
[unstagedFilesController setAutomaticallyRearrangesObjects:NO];
|
||||
}
|
||||
|
||||
- (void) removeView
|
||||
{
|
||||
[webController closeView];
|
||||
[super finalize];
|
||||
}
|
||||
|
||||
- (NSResponder *)firstResponder;
|
||||
{
|
||||
return commitMessageView;
|
||||
@@ -68,106 +91,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void) setAmend:(BOOL)newAmend
|
||||
{
|
||||
if (newAmend == amend)
|
||||
return;
|
||||
|
||||
amend = newAmend;
|
||||
amendEnvironment = nil;
|
||||
|
||||
// If we amend, we want to keep the author information for the previous commit
|
||||
// We do this by reading in the previous commit, and storing the information
|
||||
// in a dictionary. This dictionary will then later be read by [self commit:]
|
||||
if (amend) {
|
||||
NSString *message = [repository outputForCommand:@"cat-file commit HEAD"];
|
||||
NSArray *match = [message substringsMatchingRegularExpression:@"\nauthor ([^\n]*) <([^\n>]*)> ([0-9]+[^\n]*)\n" count:3 options:0 ranges:nil error:nil];
|
||||
if (match)
|
||||
amendEnvironment = [NSDictionary dictionaryWithObjectsAndKeys:[match objectAtIndex:1], @"GIT_AUTHOR_NAME",
|
||||
[match objectAtIndex:2], @"GIT_AUTHOR_EMAIL",
|
||||
[match objectAtIndex:3], @"GIT_AUTHOR_DATE",
|
||||
nil];
|
||||
|
||||
// Replace commit message with the old one if it's less than 3 characters long.
|
||||
// This is just a random number.
|
||||
if ([[commitMessageView string] length] <= 3) {
|
||||
// Find the commit message
|
||||
NSRange r = [message rangeOfString:@"\n\n"];
|
||||
if (r.location != NSNotFound)
|
||||
message = [message substringFromIndex:r.location + 2];
|
||||
|
||||
commitMessageView.string = message;
|
||||
}
|
||||
}
|
||||
|
||||
[self refresh:self];
|
||||
}
|
||||
|
||||
- (NSArray *) linesFromNotification:(NSNotification *)notification
|
||||
{
|
||||
NSDictionary *userInfo = [notification userInfo];
|
||||
NSData *data = [userInfo valueForKey:NSFileHandleNotificationDataItem];
|
||||
if (!data)
|
||||
return NULL;
|
||||
|
||||
NSString* string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
if (!string)
|
||||
return NULL;
|
||||
|
||||
// Strip trailing newline
|
||||
if ([string hasSuffix:@"\n"])
|
||||
string = [string substringToIndex:[string length]-1];
|
||||
|
||||
NSArray *lines = [string componentsSeparatedByString:@"\0"];
|
||||
return lines;
|
||||
}
|
||||
|
||||
- (NSString *) parentTree
|
||||
{
|
||||
NSString *parent = amend ? @"HEAD^" : @"HEAD";
|
||||
|
||||
if (![repository parseReference:parent])
|
||||
// We don't have a head ref. Return the empty tree.
|
||||
return @"4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
- (void) refresh:(id) sender
|
||||
{
|
||||
if (![repository workingDirectory])
|
||||
return;
|
||||
|
||||
self.busy = YES;
|
||||
self.status = @"Refreshing index…";
|
||||
|
||||
// If self.busy reaches 0, all tasks have finished
|
||||
self.busy = 0;
|
||||
|
||||
// Refresh the index, necessary for the next methods (that's why it's blocking)
|
||||
// FIXME: Make this non-blocking. This call can be expensive in large repositories
|
||||
[repository outputInWorkdirForArguments:[NSArray arrayWithObjects:@"update-index", @"-q", @"--unmerged", @"--ignore-missing", @"--refresh", nil]];
|
||||
|
||||
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
||||
[nc removeObserver:self];
|
||||
|
||||
// Other files (not tracked, not ignored)
|
||||
NSArray *arguments = [NSArray arrayWithObjects:@"ls-files", @"--others", @"--exclude-standard", @"-z", nil];
|
||||
NSFileHandle *handle = [repository handleInWorkDirForArguments:arguments];
|
||||
[nc addObserver:self selector:@selector(readOtherFiles:) name:NSFileHandleReadToEndOfFileCompletionNotification object:handle];
|
||||
self.busy++;
|
||||
[handle readToEndOfFileInBackgroundAndNotify];
|
||||
|
||||
// Unstaged files
|
||||
handle = [repository handleInWorkDirForArguments:[NSArray arrayWithObjects:@"diff-files", @"-z", nil]];
|
||||
[nc addObserver:self selector:@selector(readUnstagedFiles:) name:NSFileHandleReadToEndOfFileCompletionNotification object:handle];
|
||||
self.busy++;
|
||||
[handle readToEndOfFileInBackgroundAndNotify];
|
||||
|
||||
// Staged files
|
||||
handle = [repository handleInWorkDirForArguments:[NSArray arrayWithObjects:@"diff-index", @"--cached", @"-z", [self parentTree], nil]];
|
||||
[nc addObserver:self selector:@selector(readCachedFiles:) name:NSFileHandleReadToEndOfFileCompletionNotification object:handle];
|
||||
self.busy++;
|
||||
[handle readToEndOfFileInBackgroundAndNotify];
|
||||
[index refresh];
|
||||
|
||||
// Reload refs (in case HEAD changed)
|
||||
[repository reloadRefs];
|
||||
@@ -178,154 +106,6 @@
|
||||
[self refresh:nil];
|
||||
}
|
||||
|
||||
// This method is called for each of the three processes from above.
|
||||
// If all three are finished (self.busy == 0), then we can delete
|
||||
// all files previously marked as deletable
|
||||
- (void) doneProcessingIndex
|
||||
{
|
||||
// if we're still busy, do nothing :)
|
||||
if (--self.busy)
|
||||
return;
|
||||
|
||||
NSMutableArray *deleteFiles = [NSMutableArray array];
|
||||
for (PBChangedFile *file in files) {
|
||||
if (!file.hasStagedChanges && !file.hasUnstagedChanges)
|
||||
[deleteFiles addObject:file];
|
||||
}
|
||||
|
||||
if ([deleteFiles count]) {
|
||||
[self willChangeValueForKey:@"files"];
|
||||
for (PBChangedFile *file in deleteFiles)
|
||||
[files removeObject:file];
|
||||
[self didChangeValueForKey:@"files"];
|
||||
}
|
||||
self.status = @"Ready";
|
||||
}
|
||||
|
||||
- (void) readOtherFiles:(NSNotification *)notification;
|
||||
{
|
||||
NSArray *lines = [self linesFromNotification:notification];
|
||||
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithCapacity:[lines count]];
|
||||
// We fake this files status as good as possible.
|
||||
NSArray *fileStatus = [NSArray arrayWithObjects:@":000000", @"100644", @"0000000000000000000000000000000000000000", @"0000000000000000000000000000000000000000", @"A", nil];
|
||||
for (NSString *path in lines) {
|
||||
if ([path length] == 0)
|
||||
continue;
|
||||
[dictionary setObject:fileStatus forKey:path];
|
||||
}
|
||||
[self addFilesFromDictionary:dictionary staged:NO tracked:NO];
|
||||
[self doneProcessingIndex];
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)dictionaryForLines:(NSArray *)lines
|
||||
{
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:[lines count]/2];
|
||||
|
||||
// Fill the dictionary with the new information
|
||||
NSArray *fileStatus;
|
||||
BOOL even = FALSE;
|
||||
for (NSString *line in lines) {
|
||||
if (!even) {
|
||||
even = TRUE;
|
||||
fileStatus = [line componentsSeparatedByString:@" "];
|
||||
continue;
|
||||
}
|
||||
|
||||
even = FALSE;
|
||||
[dictionary setObject:fileStatus forKey:line];
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
- (void) addFilesFromDictionary:(NSMutableDictionary *)dictionary staged:(BOOL)staged tracked:(BOOL)tracked
|
||||
{
|
||||
// Iterate over all existing files
|
||||
[indexController stopTrackingIndex];
|
||||
for (PBChangedFile *file in files) {
|
||||
NSArray *fileStatus = [dictionary objectForKey:file.path];
|
||||
// Object found, this is still a cached / uncached thing
|
||||
if (fileStatus) {
|
||||
if (tracked) {
|
||||
NSString *mode = [[fileStatus objectAtIndex:0] substringFromIndex:1];
|
||||
NSString *sha = [fileStatus objectAtIndex:2];
|
||||
file.commitBlobSHA = sha;
|
||||
file.commitBlobMode = mode;
|
||||
|
||||
if (staged)
|
||||
file.hasStagedChanges = YES;
|
||||
else
|
||||
file.hasUnstagedChanges = YES;
|
||||
} else {
|
||||
// Untracked file, set status to NEW, only unstaged changes
|
||||
file.hasStagedChanges = NO;
|
||||
file.hasUnstagedChanges = YES;
|
||||
file.status = NEW;
|
||||
}
|
||||
[dictionary removeObjectForKey:file.path];
|
||||
} else { // Object not found, let's remove it from the changes
|
||||
if (staged)
|
||||
file.hasStagedChanges = NO;
|
||||
else if (tracked && file.status != NEW) // Only remove it if it's not an untracked file. We handle that with the other thing
|
||||
file.hasUnstagedChanges = NO;
|
||||
else if (!tracked && file.status == NEW)
|
||||
file.hasUnstagedChanges = NO;
|
||||
}
|
||||
}
|
||||
[indexController resumeTrackingIndex];
|
||||
|
||||
// Do new files
|
||||
if (![[dictionary allKeys] count])
|
||||
return;
|
||||
|
||||
[self willChangeValueForKey:@"files"];
|
||||
for (NSString *path in [dictionary allKeys]) {
|
||||
NSArray *fileStatus = [dictionary objectForKey:path];
|
||||
|
||||
PBChangedFile *file = [[PBChangedFile alloc] initWithPath:path];
|
||||
if ([[fileStatus objectAtIndex:4] isEqualToString:@"D"])
|
||||
file.status = DELETED;
|
||||
else if([[fileStatus objectAtIndex:0] isEqualToString:@":000000"])
|
||||
file.status = NEW;
|
||||
else
|
||||
file.status = MODIFIED;
|
||||
|
||||
if (tracked) {
|
||||
file.commitBlobMode = [[fileStatus objectAtIndex:0] substringFromIndex:1];
|
||||
file.commitBlobSHA = [fileStatus objectAtIndex:2];
|
||||
}
|
||||
|
||||
file.hasStagedChanges = staged;
|
||||
file.hasUnstagedChanges = !staged;
|
||||
|
||||
[files addObject: file];
|
||||
}
|
||||
[self didChangeValueForKey:@"files"];
|
||||
}
|
||||
|
||||
- (void) readUnstagedFiles:(NSNotification *)notification
|
||||
{
|
||||
NSArray *lines = [self linesFromNotification:notification];
|
||||
NSMutableDictionary *dic = [self dictionaryForLines:lines];
|
||||
[self addFilesFromDictionary:dic staged:NO tracked:YES];
|
||||
[self doneProcessingIndex];
|
||||
}
|
||||
|
||||
- (void) readCachedFiles:(NSNotification *)notification
|
||||
{
|
||||
NSArray *lines = [self linesFromNotification:notification];
|
||||
NSMutableDictionary *dic = [self dictionaryForLines:lines];
|
||||
[self addFilesFromDictionary:dic staged:YES tracked:YES];
|
||||
[self doneProcessingIndex];
|
||||
}
|
||||
|
||||
- (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"]]) {
|
||||
@@ -347,98 +127,61 @@
|
||||
[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];
|
||||
self.busy = YES;
|
||||
[commitMessageView setEditable:NO];
|
||||
|
||||
NSString *commitMessageFile;
|
||||
commitMessageFile = [repository.fileURL.path
|
||||
stringByAppendingPathComponent:@"COMMIT_EDITMSG"];
|
||||
[index commitWithMessage:commitMessage];
|
||||
}
|
||||
|
||||
[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"];
|
||||
# pragma mark PBGitIndex Notification handling
|
||||
- (void)refreshFinished:(NSNotification *)notification
|
||||
{
|
||||
self.busy = NO;
|
||||
self.status = @"Index refresh finished";
|
||||
}
|
||||
|
||||
int ret;
|
||||
- (void)commitStatusUpdated:(NSNotification *)notification
|
||||
{
|
||||
self.status = [[notification userInfo] objectForKey:@"description"];
|
||||
}
|
||||
|
||||
NSMutableArray *arguments = [NSMutableArray arrayWithObjects:@"commit-tree", tree, nil];
|
||||
NSString *parent = 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--;
|
||||
- (void)commitFinished:(NSNotification *)notification
|
||||
{
|
||||
[commitMessageView setEditable:YES];
|
||||
[commitMessageView setString:@""];
|
||||
amend = NO;
|
||||
amendEnvironment = nil;
|
||||
[self refresh:self];
|
||||
self.amend = NO;
|
||||
[webController setStateMessage:[NSString stringWithFormat:[[notification userInfo] objectForKey:@"description"]]];
|
||||
}
|
||||
|
||||
- (void)commitFailed:(NSNotification *)notification
|
||||
{
|
||||
self.busy = NO;
|
||||
NSString *reason = [[notification userInfo] objectForKey:@"description"];
|
||||
self.status = [@"Commit failed: " stringByAppendingString:reason];
|
||||
[commitMessageView setEditable:YES];
|
||||
[[repository windowController] showMessageSheet:@"Commit failed" infoText:reason];
|
||||
}
|
||||
|
||||
- (void) stageHunk:(NSString *)hunk reverse:(BOOL)reverse
|
||||
- (void)amendCommit:(NSNotification *)notification
|
||||
{
|
||||
[self processHunk:hunk stage:TRUE reverse:reverse];
|
||||
// Replace commit message with the old one if it's less than 3 characters long.
|
||||
// This is just a random number.
|
||||
if ([[commitMessageView string] length] > 3)
|
||||
return;
|
||||
|
||||
NSString *message = [[notification userInfo] objectForKey:@"message"];
|
||||
commitMessageView.string = message;
|
||||
}
|
||||
|
||||
- (void)discardHunk:(NSString *)hunk
|
||||
- (void)indexChanged:(NSNotification *)notification
|
||||
{
|
||||
[self processHunk:hunk stage:FALSE reverse:TRUE];
|
||||
[cachedFilesController rearrangeObjects];
|
||||
[unstagedFilesController rearrangeObjects];
|
||||
}
|
||||
|
||||
- (void)processHunk:(NSString *)hunk stage:(BOOL)stage reverse:(BOOL)reverse
|
||||
- (void)indexOperationFailed:(NSNotification *)notification
|
||||
{
|
||||
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];
|
||||
[[repository windowController] showMessageSheet:@"Index operation failed" infoText:[[notification userInfo] objectForKey:@"description"]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+54
-75
@@ -2,15 +2,14 @@
|
||||
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.03">
|
||||
<data>
|
||||
<int key="IBDocument.SystemTarget">1050</int>
|
||||
<string key="IBDocument.SystemVersion">9J61</string>
|
||||
<string key="IBDocument.SystemVersion">9L31a</string>
|
||||
<string key="IBDocument.InterfaceBuilderVersion">677</string>
|
||||
<string key="IBDocument.AppKitVersion">949.46</string>
|
||||
<string key="IBDocument.AppKitVersion">949.54</string>
|
||||
<string key="IBDocument.HIToolboxVersion">353.00</string>
|
||||
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<integer value="207"/>
|
||||
<integer value="225"/>
|
||||
<integer value="113"/>
|
||||
<integer value="1" id="9"/>
|
||||
</object>
|
||||
<object class="NSArray" key="IBDocument.PluginDependencies">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
@@ -127,7 +126,7 @@
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<integer value="12" id="777559147"/>
|
||||
<reference ref="777559147"/>
|
||||
<reference ref="9"/>
|
||||
<integer value="1" id="9"/>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
@@ -963,38 +962,6 @@
|
||||
</object>
|
||||
<int key="connectionID">139</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBBindingConnection" key="connection">
|
||||
<string key="label">contentArray: files</string>
|
||||
<reference key="source" ref="667905213"/>
|
||||
<reference key="destination" ref="1001"/>
|
||||
<object class="NSNibBindingConnector" key="connector">
|
||||
<reference key="NSSource" ref="667905213"/>
|
||||
<reference key="NSDestination" ref="1001"/>
|
||||
<string key="NSLabel">contentArray: files</string>
|
||||
<string key="NSBinding">contentArray</string>
|
||||
<string key="NSKeyPath">files</string>
|
||||
<int key="NSNibBindingConnectorVersion">2</int>
|
||||
</object>
|
||||
</object>
|
||||
<int key="connectionID">149</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBBindingConnection" key="connection">
|
||||
<string key="label">contentArray: files</string>
|
||||
<reference key="source" ref="128809524"/>
|
||||
<reference key="destination" ref="1001"/>
|
||||
<object class="NSNibBindingConnector" key="connector">
|
||||
<reference key="NSSource" ref="128809524"/>
|
||||
<reference key="NSDestination" ref="1001"/>
|
||||
<string key="NSLabel">contentArray: files</string>
|
||||
<string key="NSBinding">contentArray</string>
|
||||
<string key="NSKeyPath">files</string>
|
||||
<int key="NSNibBindingConnectorVersion">2</int>
|
||||
</object>
|
||||
</object>
|
||||
<int key="connectionID">150</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">cachedFilesController</string>
|
||||
@@ -1067,22 +1034,6 @@
|
||||
</object>
|
||||
<int key="connectionID">241</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBBindingConnection" key="connection">
|
||||
<string key="label">value: amend</string>
|
||||
<reference key="source" ref="18874447"/>
|
||||
<reference key="destination" ref="1001"/>
|
||||
<object class="NSNibBindingConnector" key="connector">
|
||||
<reference key="NSSource" ref="18874447"/>
|
||||
<reference key="NSDestination" ref="1001"/>
|
||||
<string key="NSLabel">value: amend</string>
|
||||
<string key="NSBinding">value</string>
|
||||
<string key="NSKeyPath">amend</string>
|
||||
<int key="NSNibBindingConnectorVersion">2</int>
|
||||
</object>
|
||||
</object>
|
||||
<int key="connectionID">252</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">webController</string>
|
||||
@@ -1163,22 +1114,6 @@
|
||||
</object>
|
||||
<int key="connectionID">264</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">stagedButtonCell</string>
|
||||
<reference key="source" ref="446885874"/>
|
||||
<reference key="destination" ref="39450212"/>
|
||||
</object>
|
||||
<int key="connectionID">265</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">unstagedButtonCell</string>
|
||||
<reference key="source" ref="446885874"/>
|
||||
<reference key="destination" ref="45690317"/>
|
||||
</object>
|
||||
<int key="connectionID">266</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBActionConnection" key="connection">
|
||||
<string key="label">rowClicked:</string>
|
||||
@@ -1219,6 +1154,54 @@
|
||||
</object>
|
||||
<int key="connectionID">280</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBBindingConnection" key="connection">
|
||||
<string key="label">contentArray: index.indexChanges</string>
|
||||
<reference key="source" ref="128809524"/>
|
||||
<reference key="destination" ref="1001"/>
|
||||
<object class="NSNibBindingConnector" key="connector">
|
||||
<reference key="NSSource" ref="128809524"/>
|
||||
<reference key="NSDestination" ref="1001"/>
|
||||
<string key="NSLabel">contentArray: index.indexChanges</string>
|
||||
<string key="NSBinding">contentArray</string>
|
||||
<string key="NSKeyPath">index.indexChanges</string>
|
||||
<int key="NSNibBindingConnectorVersion">2</int>
|
||||
</object>
|
||||
</object>
|
||||
<int key="connectionID">281</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBBindingConnection" key="connection">
|
||||
<string key="label">contentArray: index.indexChanges</string>
|
||||
<reference key="source" ref="667905213"/>
|
||||
<reference key="destination" ref="1001"/>
|
||||
<object class="NSNibBindingConnector" key="connector">
|
||||
<reference key="NSSource" ref="667905213"/>
|
||||
<reference key="NSDestination" ref="1001"/>
|
||||
<string key="NSLabel">contentArray: index.indexChanges</string>
|
||||
<string key="NSBinding">contentArray</string>
|
||||
<string key="NSKeyPath">index.indexChanges</string>
|
||||
<int key="NSNibBindingConnectorVersion">2</int>
|
||||
</object>
|
||||
</object>
|
||||
<int key="connectionID">282</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBBindingConnection" key="connection">
|
||||
<string key="label">value: index.amend</string>
|
||||
<reference key="source" ref="18874447"/>
|
||||
<reference key="destination" ref="1001"/>
|
||||
<object class="NSNibBindingConnector" key="connector">
|
||||
<reference key="NSSource" ref="18874447"/>
|
||||
<reference key="NSDestination" ref="1001"/>
|
||||
<string key="NSLabel">value: index.amend</string>
|
||||
<string key="NSBinding">value</string>
|
||||
<string key="NSKeyPath">index.amend</string>
|
||||
<int key="NSNibBindingConnectorVersion">2</int>
|
||||
</object>
|
||||
</object>
|
||||
<int key="connectionID">283</int>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBMutableOrderedSet" key="objectRecords">
|
||||
<object class="NSArray" key="orderedObjects">
|
||||
@@ -1616,7 +1599,7 @@
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilderKit</string>
|
||||
<string>com.apple.InterfaceBuilderKit</string>
|
||||
<string>{{59, 63}, {852, 432}}</string>
|
||||
<string>{{428, 510}, {852, 432}}</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<integer value="0" id="8"/>
|
||||
<reference ref="8"/>
|
||||
@@ -1680,7 +1663,7 @@
|
||||
</object>
|
||||
</object>
|
||||
<nil key="sourceID"/>
|
||||
<int key="maxID">280</int>
|
||||
<int key="maxID">283</int>
|
||||
</object>
|
||||
<object class="IBClassDescriber" key="IBDocument.Classes">
|
||||
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
|
||||
@@ -1764,20 +1747,16 @@
|
||||
<object class="NSMutableArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>commitController</string>
|
||||
<string>stagedButtonCell</string>
|
||||
<string>stagedFilesController</string>
|
||||
<string>stagedTable</string>
|
||||
<string>unstagedButtonCell</string>
|
||||
<string>unstagedFilesController</string>
|
||||
<string>unstagedTable</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>PBGitCommitController</string>
|
||||
<string>PBIconAndTextCell</string>
|
||||
<string>NSArrayController</string>
|
||||
<string>NSTableView</string>
|
||||
<string>PBIconAndTextCell</string>
|
||||
<string>NSArrayController</string>
|
||||
<string>NSTableView</string>
|
||||
</object>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// PBGitIndex.h
|
||||
// GitX
|
||||
//
|
||||
// Created by Pieter de Bie on 9/12/09.
|
||||
// Copyright 2009 Pieter de Bie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class PBGitRepository;
|
||||
@class PBChangedFile;
|
||||
|
||||
/*
|
||||
* Notifications this class will send
|
||||
*/
|
||||
|
||||
// Refreshing index
|
||||
extern NSString *PBGitIndexIndexRefreshStatus;
|
||||
extern NSString *PBGitIndexIndexRefreshFailed;
|
||||
extern NSString *PBGitIndexFinishedIndexRefresh;
|
||||
|
||||
// The "indexChanges" array has changed
|
||||
extern NSString *PBGitIndexIndexUpdated;
|
||||
|
||||
// Committing files
|
||||
extern NSString *PBGitIndexCommitStatus;
|
||||
extern NSString *PBGitIndexCommitFailed;
|
||||
extern NSString *PBGitIndexFinishedCommit;
|
||||
|
||||
// Changing to amend
|
||||
extern NSString *PBGitIndexAmendMessageAvailable;
|
||||
|
||||
// This is for general operations, like applying a patch
|
||||
extern NSString *PBGitIndexOperationFailed;
|
||||
|
||||
|
||||
|
||||
// Represents a git index for a given work tree.
|
||||
// As a single git repository can have multiple trees,
|
||||
// the tree has to be given explicitly, even though
|
||||
// multiple trees is not yet supported in GitX
|
||||
@interface PBGitIndex : NSObject {
|
||||
|
||||
@private
|
||||
PBGitRepository *repository;
|
||||
NSURL *workingDirectory;
|
||||
NSMutableArray *files;
|
||||
|
||||
NSUInteger refreshStatus;
|
||||
NSDictionary *amendEnvironment;
|
||||
BOOL amend;
|
||||
}
|
||||
|
||||
// Whether we want the changes for amending,
|
||||
// or for
|
||||
@property BOOL amend;
|
||||
|
||||
- (id)initWithRepository:(PBGitRepository *)repository workingDirectory:(NSURL *)workingDirectory;
|
||||
|
||||
// A list of PBChangedFile's with differences between the work tree and the index
|
||||
// This method is KVO-aware, so changes when any of the index-modifying methods are called
|
||||
// (including -refresh)
|
||||
- (NSArray *)indexChanges;
|
||||
|
||||
// Refresh the index
|
||||
- (void)refresh;
|
||||
|
||||
- (void)commitWithMessage:(NSString *)commitMessage;
|
||||
|
||||
// Inter-file changes:
|
||||
- (BOOL)stageFiles:(NSArray *)stageFiles;
|
||||
- (BOOL)unstageFiles:(NSArray *)unstageFiles;
|
||||
- (void)discardChangesForFiles:(NSArray *)discardFiles;
|
||||
|
||||
// Intra-file changes
|
||||
- (BOOL)applyPatch:(NSString *)hunk stage:(BOOL)stage reverse:(BOOL)reverse;
|
||||
- (NSString *)diffForFile:(PBChangedFile *)file staged:(BOOL)staged contextLines:(NSUInteger)context;
|
||||
|
||||
@end
|
||||
+630
@@ -0,0 +1,630 @@
|
||||
//
|
||||
// PBGitIndex.m
|
||||
// GitX
|
||||
//
|
||||
// Created by Pieter de Bie on 9/12/09.
|
||||
// Copyright 2009 Pieter de Bie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PBGitIndex.h"
|
||||
#import "PBGitRepository.h"
|
||||
#import "PBGitBinary.h"
|
||||
#import "PBEasyPipe.h"
|
||||
#import "NSString_RegEx.h"
|
||||
#import "PBChangedFile.h"
|
||||
|
||||
NSString *PBGitIndexIndexRefreshStatus = @"PBGitIndexIndexRefreshStatus";
|
||||
NSString *PBGitIndexIndexRefreshFailed = @"PBGitIndexIndexRefreshFailed";
|
||||
NSString *PBGitIndexFinishedIndexRefresh = @"PBGitIndexFinishedIndexRefresh";
|
||||
|
||||
NSString *PBGitIndexIndexUpdated = @"GBGitIndexIndexUpdated";
|
||||
|
||||
NSString *PBGitIndexCommitStatus = @"PBGitIndexCommitStatus";
|
||||
NSString *PBGitIndexCommitFailed = @"PBGitIndexCommitFailed";
|
||||
NSString *PBGitIndexFinishedCommit = @"PBGitIndexFinishedCommit";
|
||||
|
||||
NSString *PBGitIndexAmendMessageAvailable = @"PBGitIndexAmendMessageAvailable";
|
||||
NSString *PBGitIndexOperationFailed = @"PBGitIndexOperationFailed";
|
||||
|
||||
@interface PBGitIndex (IndexRefreshMethods)
|
||||
|
||||
- (NSArray *)linesFromNotification:(NSNotification *)notification;
|
||||
- (NSMutableDictionary *)dictionaryForLines:(NSArray *)lines;
|
||||
- (void)addFilesFromDictionary:(NSMutableDictionary *)dictionary staged:(BOOL)staged tracked:(BOOL)tracked;
|
||||
|
||||
- (void)indexStepComplete;
|
||||
|
||||
- (void)indexRefreshFinished:(NSNotification *)notification;
|
||||
- (void)readOtherFiles:(NSNotification *)notification;
|
||||
- (void)readUnstagedFiles:(NSNotification *)notification;
|
||||
- (void)readStagedFiles:(NSNotification *)notification;
|
||||
|
||||
@end
|
||||
|
||||
@interface PBGitIndex ()
|
||||
|
||||
// Returns the tree to compare the index to, based
|
||||
// on whether amend is set or not.
|
||||
- (NSString *) parentTree;
|
||||
- (void)postCommitUpdate:(NSString *)update;
|
||||
- (void)postCommitFailure:(NSString *)reason;
|
||||
- (void)postIndexChange;
|
||||
- (void)postOperationFailed:(NSString *)description;
|
||||
@end
|
||||
|
||||
@implementation PBGitIndex
|
||||
|
||||
@synthesize amend;
|
||||
|
||||
- (id)initWithRepository:(PBGitRepository *)theRepository workingDirectory:(NSURL *)theWorkingDirectory
|
||||
{
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
NSAssert(theWorkingDirectory, @"PBGitIndex requires a working directory");
|
||||
NSAssert(theRepository, @"PBGitIndex requires a repository");
|
||||
|
||||
repository = theRepository;
|
||||
workingDirectory = theWorkingDirectory;
|
||||
files = [NSMutableArray array];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSArray *)indexChanges
|
||||
{
|
||||
return files;
|
||||
}
|
||||
|
||||
- (void)setAmend:(BOOL)newAmend
|
||||
{
|
||||
if (newAmend == amend)
|
||||
return;
|
||||
|
||||
amend = newAmend;
|
||||
amendEnvironment = nil;
|
||||
|
||||
[self refresh];
|
||||
|
||||
if (!newAmend)
|
||||
return;
|
||||
|
||||
// If we amend, we want to keep the author information for the previous commit
|
||||
// We do this by reading in the previous commit, and storing the information
|
||||
// in a dictionary. This dictionary will then later be read by [self commit:]
|
||||
NSString *message = [repository outputForCommand:@"cat-file commit HEAD"];
|
||||
NSArray *match = [message substringsMatchingRegularExpression:@"\nauthor ([^\n]*) <([^\n>]*)> ([0-9]+[^\n]*)\n" count:3 options:0 ranges:nil error:nil];
|
||||
if (match)
|
||||
amendEnvironment = [NSDictionary dictionaryWithObjectsAndKeys:[match objectAtIndex:1], @"GIT_AUTHOR_NAME",
|
||||
[match objectAtIndex:2], @"GIT_AUTHOR_EMAIL",
|
||||
[match objectAtIndex:3], @"GIT_AUTHOR_DATE",
|
||||
nil];
|
||||
|
||||
// Find the commit message
|
||||
NSRange r = [message rangeOfString:@"\n\n"];
|
||||
if (r.location != NSNotFound) {
|
||||
NSString *commitMessage = [message substringFromIndex:r.location + 2];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:PBGitIndexAmendMessageAvailable
|
||||
object: self
|
||||
userInfo:[NSDictionary dictionaryWithObject:commitMessage forKey:@"message"]];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (void)refresh
|
||||
{
|
||||
// If we were already refreshing the index, we don't want
|
||||
// double notifications. As we can't stop the tasks anymore,
|
||||
// just cancel the notifications
|
||||
refreshStatus = 0;
|
||||
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
||||
[nc removeObserver:self];
|
||||
|
||||
// Ask Git to refresh the index
|
||||
NSFileHandle *updateHandle = [PBEasyPipe handleForCommand:[PBGitBinary path]
|
||||
withArgs:[NSArray arrayWithObjects:@"update-index", @"-q", @"--unmerged", @"--ignore-missing", @"--refresh", nil]
|
||||
inDir:[workingDirectory path]];
|
||||
|
||||
[nc addObserver:self
|
||||
selector:@selector(indexRefreshFinished:)
|
||||
name:NSFileHandleReadToEndOfFileCompletionNotification
|
||||
object:updateHandle];
|
||||
[updateHandle readToEndOfFileInBackgroundAndNotify];
|
||||
|
||||
}
|
||||
|
||||
- (NSString *) parentTree
|
||||
{
|
||||
NSString *parent = amend ? @"HEAD^" : @"HEAD";
|
||||
|
||||
if (![repository parseReference:parent])
|
||||
// We don't have a head ref. Return the empty tree.
|
||||
return @"4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
// TODO: make Asynchronous
|
||||
- (void)commitWithMessage:(NSString *)commitMessage
|
||||
{
|
||||
NSMutableString *commitSubject = [@"commit: " mutableCopy];
|
||||
NSRange newLine = [commitMessage rangeOfString:@"\n"];
|
||||
if (newLine.location == NSNotFound)
|
||||
[commitSubject appendString:commitMessage];
|
||||
else
|
||||
[commitSubject appendString:[commitMessage substringToIndex:newLine.location]];
|
||||
|
||||
NSString *commitMessageFile;
|
||||
commitMessageFile = [repository.fileURL.path stringByAppendingPathComponent:@"COMMIT_EDITMSG"];
|
||||
|
||||
[commitMessage writeToFile:commitMessageFile atomically:YES encoding:NSUTF8StringEncoding error:nil];
|
||||
|
||||
|
||||
[self postCommitUpdate:@"Creating tree"];
|
||||
NSString *tree = [repository outputForCommand:@"write-tree"];
|
||||
if ([tree length] != 40)
|
||||
return [self postCommitFailure:@"Creating tree failed"];
|
||||
|
||||
|
||||
NSMutableArray *arguments = [NSMutableArray arrayWithObjects:@"commit-tree", tree, nil];
|
||||
NSString *parent = amend ? @"HEAD^" : @"HEAD";
|
||||
if ([repository parseReference:parent]) {
|
||||
[arguments addObject:@"-p"];
|
||||
[arguments addObject:parent];
|
||||
}
|
||||
|
||||
[self postCommitUpdate:@"Creating commit"];
|
||||
int ret = 1;
|
||||
NSString *commit = [repository outputForArguments:arguments
|
||||
inputString:commitMessage
|
||||
byExtendingEnvironment:amendEnvironment
|
||||
retValue: &ret];
|
||||
|
||||
if (ret || [commit length] != 40)
|
||||
return [self postCommitFailure:@"Could not create a commit object"];
|
||||
|
||||
[self postCommitUpdate:@"Running hooks"];
|
||||
if (![repository executeHook:@"pre-commit" output:nil])
|
||||
return [self postCommitFailure:@"Pre-commit hook failed"];
|
||||
|
||||
if (![repository executeHook:@"commit-msg" withArgs:[NSArray arrayWithObject:commitMessageFile] output:nil])
|
||||
return [self postCommitFailure:@"Commit-msg hook failed"];
|
||||
|
||||
[self postCommitUpdate:@"Updating HEAD"];
|
||||
[repository outputForArguments:[NSArray arrayWithObjects:@"update-ref", @"-m", commitSubject, @"HEAD", commit, nil]
|
||||
retValue: &ret];
|
||||
if (ret)
|
||||
return [self postCommitFailure:@"Could not update HEAD"];
|
||||
|
||||
[self postCommitUpdate:@"Running post-commit hook"];
|
||||
|
||||
BOOL success = [repository executeHook:@"post-commit" output:nil];
|
||||
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithBool:success] forKey:@"success"];
|
||||
NSString *description;
|
||||
if (success)
|
||||
description = [NSString stringWithFormat:@"Successfull created commit %@", commit];
|
||||
else
|
||||
description = [NSString stringWithFormat:@"Post-commit hook failed, but successfully created commit %@", commit];
|
||||
|
||||
[userInfo setObject:description forKey:@"description"];
|
||||
[userInfo setObject:commit forKey:@"sha"];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:PBGitIndexFinishedCommit
|
||||
object:self
|
||||
userInfo:userInfo];
|
||||
if (!success)
|
||||
return;
|
||||
|
||||
repository.hasChanged = YES;
|
||||
|
||||
amendEnvironment = nil;
|
||||
if (amend)
|
||||
self.amend = NO;
|
||||
else
|
||||
[self refresh];
|
||||
|
||||
}
|
||||
|
||||
- (void)postCommitUpdate:(NSString *)update
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:PBGitIndexCommitStatus
|
||||
object:self
|
||||
userInfo:[NSDictionary dictionaryWithObject:update forKey:@"description"]];
|
||||
}
|
||||
|
||||
- (void)postCommitFailure:(NSString *)reason
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:PBGitIndexCommitFailed
|
||||
object:self
|
||||
userInfo:[NSDictionary dictionaryWithObject:reason forKey:@"description"]];
|
||||
}
|
||||
|
||||
- (void)postOperationFailed:(NSString *)description
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:PBGitIndexOperationFailed
|
||||
object:self
|
||||
userInfo:[NSDictionary dictionaryWithObject:description forKey:@"description"]];
|
||||
}
|
||||
|
||||
- (BOOL)stageFiles:(NSArray *)stageFiles
|
||||
{
|
||||
// Input string for update-index
|
||||
// This will be a list of filenames that
|
||||
// should be updated. It's similar to
|
||||
// "git add -- <files>
|
||||
NSMutableString *input = [NSMutableString string];
|
||||
|
||||
for (PBChangedFile *file in stageFiles) {
|
||||
[input appendFormat:@"%@\0", file.path];
|
||||
}
|
||||
|
||||
int ret = 1;
|
||||
[repository outputForArguments:[NSArray arrayWithObjects:@"update-index", @"--add", @"--remove", @"-z", @"--stdin", nil]
|
||||
inputString:input
|
||||
retValue:&ret];
|
||||
|
||||
if (ret) {
|
||||
[self postOperationFailed:[NSString stringWithFormat:@"Error in staging files. Return value: %i", ret]];
|
||||
return NO;
|
||||
}
|
||||
|
||||
for (PBChangedFile *file in stageFiles)
|
||||
{
|
||||
file.hasUnstagedChanges = NO;
|
||||
file.hasStagedChanges = YES;
|
||||
}
|
||||
|
||||
[self postIndexChange];
|
||||
return YES;
|
||||
}
|
||||
|
||||
// TODO: Refactor with above. What's a better name for this?
|
||||
- (BOOL)unstageFiles:(NSArray *)unstageFiles
|
||||
{
|
||||
NSMutableString *input = [NSMutableString string];
|
||||
|
||||
for (PBChangedFile *file in unstageFiles) {
|
||||
[input appendString:[file indexInfo]];
|
||||
}
|
||||
|
||||
int ret = 1;
|
||||
[repository outputForArguments:[NSArray arrayWithObjects:@"update-index", @"-z", @"--index-info", nil]
|
||||
inputString:input
|
||||
retValue:&ret];
|
||||
|
||||
if (ret)
|
||||
{
|
||||
[self postOperationFailed:[NSString stringWithFormat:@"Error in unstaging files. Return value: %i", ret]];
|
||||
return NO;
|
||||
}
|
||||
|
||||
for (PBChangedFile *file in unstageFiles)
|
||||
{
|
||||
file.hasUnstagedChanges = YES;
|
||||
file.hasStagedChanges = NO;
|
||||
}
|
||||
|
||||
[self postIndexChange];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)discardChangesForFiles:(NSArray *)discardFiles
|
||||
{
|
||||
NSArray *paths = [discardFiles valueForKey:@"path"];
|
||||
NSString *input = [paths componentsJoinedByString:@"\0"];
|
||||
|
||||
NSArray *arguments = [NSArray arrayWithObjects:@"checkout-index", @"--index", @"--quiet", @"--force", @"-z", @"--stdin", nil];
|
||||
|
||||
int ret = 1;
|
||||
[PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:arguments inDir:[workingDirectory path] inputString:input retValue:&ret];
|
||||
|
||||
if (ret) {
|
||||
[self postOperationFailed:[NSString stringWithFormat:@"Discarding changes failed with return value %i", ret]];
|
||||
return;
|
||||
}
|
||||
|
||||
for (PBChangedFile *file in discardFiles)
|
||||
file.hasUnstagedChanges = NO;
|
||||
|
||||
[self postIndexChange];
|
||||
}
|
||||
|
||||
- (BOOL)applyPatch:(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];
|
||||
|
||||
if (ret) {
|
||||
[self postOperationFailed:[NSString stringWithFormat:@"Applying patch failed with return value %i. Error: %@", ret, error]];
|
||||
return NO;
|
||||
}
|
||||
|
||||
// TODO: Try to be smarter about what to refresh
|
||||
[self refresh];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)diffForFile:(PBChangedFile *)file staged:(BOOL)staged contextLines:(NSUInteger)context
|
||||
{
|
||||
NSString *parameter = [NSString stringWithFormat:@"-U%u", context];
|
||||
if (staged) {
|
||||
NSString *indexPath = [@":0:" stringByAppendingString:file.path];
|
||||
|
||||
if (file.status == NEW)
|
||||
return [repository outputForArguments:[NSArray arrayWithObjects:@"show", indexPath, nil]];
|
||||
|
||||
return [repository outputInWorkdirForArguments:[NSArray arrayWithObjects:@"diff-index", parameter, @"--cached", [self parentTree], @"--", file.path, nil]];
|
||||
}
|
||||
|
||||
// unstaged
|
||||
if (file.status == NEW) {
|
||||
NSStringEncoding encoding;
|
||||
NSError *error = nil;
|
||||
NSString *path = [[repository workingDirectory] stringByAppendingPathComponent:file.path];
|
||||
NSString *contents = [NSString stringWithContentsOfFile:path
|
||||
usedEncoding:&encoding
|
||||
error:&error];
|
||||
if (error)
|
||||
return nil;
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
return [repository outputInWorkdirForArguments:[NSArray arrayWithObjects:@"diff-files", parameter, @"--", file.path, nil]];
|
||||
}
|
||||
|
||||
- (void)postIndexChange
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:PBGitIndexIndexUpdated
|
||||
object:self];
|
||||
}
|
||||
|
||||
# pragma mark WebKit Accessibility
|
||||
|
||||
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation PBGitIndex (IndexRefreshMethods)
|
||||
|
||||
- (void)indexRefreshFinished:(NSNotification *)notification
|
||||
{
|
||||
if ([(NSNumber *)[(NSDictionary *)[notification userInfo] objectForKey:@"NSFileHandleError"] intValue])
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:PBGitIndexIndexRefreshFailed
|
||||
object:self
|
||||
userInfo:[NSDictionary dictionaryWithObject:@"update-index failed" forKey:@"description"]];
|
||||
return;
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:PBGitIndexIndexRefreshStatus
|
||||
object:self
|
||||
userInfo:[NSDictionary dictionaryWithObject:@"update-index success" forKey:@"description"]];
|
||||
|
||||
// Now that the index is refreshed, we need to read the information from the index
|
||||
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
||||
|
||||
// Other files (not tracked, not ignored)
|
||||
NSFileHandle *handle = [PBEasyPipe handleForCommand:[PBGitBinary path]
|
||||
withArgs:[NSArray arrayWithObjects:@"ls-files", @"--others", @"--exclude-standard", @"-z", nil]
|
||||
inDir:[workingDirectory path]];
|
||||
[nc addObserver:self selector:@selector(readOtherFiles:) name:NSFileHandleReadToEndOfFileCompletionNotification object:handle];
|
||||
[handle readToEndOfFileInBackgroundAndNotify];
|
||||
refreshStatus++;
|
||||
|
||||
// Unstaged files
|
||||
handle = [PBEasyPipe handleForCommand:[PBGitBinary path]
|
||||
withArgs:[NSArray arrayWithObjects:@"diff-files", @"-z", nil]
|
||||
inDir:[workingDirectory path]];
|
||||
[nc addObserver:self selector:@selector(readUnstagedFiles:) name:NSFileHandleReadToEndOfFileCompletionNotification object:handle];
|
||||
[handle readToEndOfFileInBackgroundAndNotify];
|
||||
refreshStatus++;
|
||||
|
||||
// Staged files
|
||||
handle = [PBEasyPipe handleForCommand:[PBGitBinary path]
|
||||
withArgs:[NSArray arrayWithObjects:@"diff-index", @"--cached", @"-z", [self parentTree], nil]
|
||||
inDir:[workingDirectory path]];
|
||||
[nc addObserver:self selector:@selector(readStagedFiles:) name:NSFileHandleReadToEndOfFileCompletionNotification object:handle];
|
||||
[handle readToEndOfFileInBackgroundAndNotify];
|
||||
refreshStatus++;
|
||||
}
|
||||
|
||||
- (void)readOtherFiles:(NSNotification *)notification
|
||||
{
|
||||
NSArray *lines = [self linesFromNotification:notification];
|
||||
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithCapacity:[lines count]];
|
||||
// Other files are untracked, so we don't have any real index information. Instead, we can just fake it.
|
||||
// The line below is not used at all, as for these files the commitBlob isn't set
|
||||
NSArray *fileStatus = [NSArray arrayWithObjects:@":000000", @"100644", @"0000000000000000000000000000000000000000", @"0000000000000000000000000000000000000000", @"A", nil];
|
||||
for (NSString *path in lines) {
|
||||
if ([path length] == 0)
|
||||
continue;
|
||||
[dictionary setObject:fileStatus forKey:path];
|
||||
}
|
||||
|
||||
[self addFilesFromDictionary:dictionary staged:NO tracked:NO];
|
||||
[self indexStepComplete];
|
||||
}
|
||||
|
||||
- (void) readStagedFiles:(NSNotification *)notification
|
||||
{
|
||||
NSArray *lines = [self linesFromNotification:notification];
|
||||
NSMutableDictionary *dic = [self dictionaryForLines:lines];
|
||||
[self addFilesFromDictionary:dic staged:YES tracked:YES];
|
||||
[self indexStepComplete];
|
||||
}
|
||||
|
||||
- (void) readUnstagedFiles:(NSNotification *)notification
|
||||
{
|
||||
NSArray *lines = [self linesFromNotification:notification];
|
||||
NSMutableDictionary *dic = [self dictionaryForLines:lines];
|
||||
[self addFilesFromDictionary:dic staged:NO tracked:YES];
|
||||
[self indexStepComplete];
|
||||
}
|
||||
|
||||
- (void) addFilesFromDictionary:(NSMutableDictionary *)dictionary staged:(BOOL)staged tracked:(BOOL)tracked
|
||||
{
|
||||
// Iterate over all existing files
|
||||
for (PBChangedFile *file in files) {
|
||||
NSArray *fileStatus = [dictionary objectForKey:file.path];
|
||||
// Object found, this is still a cached / uncached thing
|
||||
if (fileStatus) {
|
||||
if (tracked) {
|
||||
NSString *mode = [[fileStatus objectAtIndex:0] substringFromIndex:1];
|
||||
NSString *sha = [fileStatus objectAtIndex:2];
|
||||
file.commitBlobSHA = sha;
|
||||
file.commitBlobMode = mode;
|
||||
|
||||
if (staged)
|
||||
file.hasStagedChanges = YES;
|
||||
else
|
||||
file.hasUnstagedChanges = YES;
|
||||
} else {
|
||||
// Untracked file, set status to NEW, only unstaged changes
|
||||
file.hasStagedChanges = NO;
|
||||
file.hasUnstagedChanges = YES;
|
||||
file.status = NEW;
|
||||
}
|
||||
|
||||
// We handled this file, remove it from the dictionary
|
||||
[dictionary removeObjectForKey:file.path];
|
||||
} else {
|
||||
// Object not found in the dictionary, so let's reset its appropriate
|
||||
// change (stage or untracked) if necessary.
|
||||
|
||||
// Staged dictionary, so file does not have staged changes
|
||||
if (staged)
|
||||
file.hasStagedChanges = NO;
|
||||
// Tracked file does not have unstaged changes, file is not new,
|
||||
// so we can set it to No. (If it would be new, it would not
|
||||
// be in this dictionary, but in the "other dictionary").
|
||||
else if (tracked && file.status != NEW)
|
||||
file.hasUnstagedChanges = NO;
|
||||
// Unstaged, untracked dictionary ("Other" files), and file
|
||||
// is indicated as new (which would be untracked), so let's
|
||||
// remove it
|
||||
else if (!tracked && file.status == NEW)
|
||||
file.hasUnstagedChanges = NO;
|
||||
}
|
||||
}
|
||||
|
||||
// Do new files only if necessary
|
||||
if (![[dictionary allKeys] count])
|
||||
return;
|
||||
|
||||
// All entries left in the dictionary haven't been accounted for
|
||||
// above, so we need to add them to the "files" array
|
||||
[self willChangeValueForKey:@"indexChanges"];
|
||||
for (NSString *path in [dictionary allKeys]) {
|
||||
NSArray *fileStatus = [dictionary objectForKey:path];
|
||||
|
||||
PBChangedFile *file = [[PBChangedFile alloc] initWithPath:path];
|
||||
if ([[fileStatus objectAtIndex:4] isEqualToString:@"D"])
|
||||
file.status = DELETED;
|
||||
else if([[fileStatus objectAtIndex:0] isEqualToString:@":000000"])
|
||||
file.status = NEW;
|
||||
else
|
||||
file.status = MODIFIED;
|
||||
|
||||
if (tracked) {
|
||||
file.commitBlobMode = [[fileStatus objectAtIndex:0] substringFromIndex:1];
|
||||
file.commitBlobSHA = [fileStatus objectAtIndex:2];
|
||||
}
|
||||
|
||||
file.hasStagedChanges = staged;
|
||||
file.hasUnstagedChanges = !staged;
|
||||
|
||||
[files addObject:file];
|
||||
}
|
||||
[self didChangeValueForKey:@"indexChanges"];
|
||||
}
|
||||
|
||||
# pragma mark Utility methods
|
||||
- (NSArray *)linesFromNotification:(NSNotification *)notification
|
||||
{
|
||||
NSData *data = [[notification userInfo] valueForKey:NSFileHandleNotificationDataItem];
|
||||
if (!data)
|
||||
return [NSArray array];
|
||||
|
||||
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
// FIXME: throw an error?
|
||||
if (!string)
|
||||
return [NSArray array];
|
||||
|
||||
// Strip trailing null
|
||||
if ([string hasSuffix:@"\0"])
|
||||
string = [string substringToIndex:[string length]-1];
|
||||
|
||||
if ([string length] == 0)
|
||||
return [NSArray array];
|
||||
|
||||
return [string componentsSeparatedByString:@"\0"];
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)dictionaryForLines:(NSArray *)lines
|
||||
{
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:[lines count]/2];
|
||||
|
||||
// Fill the dictionary with the new information. These lines are in the form of:
|
||||
// :00000 :0644 OTHER INDEX INFORMATION
|
||||
// Filename
|
||||
|
||||
NSAssert1([lines count] % 2 == 0, @"Lines must have an even number of lines: %@", lines);
|
||||
|
||||
NSEnumerator *enumerator = [lines objectEnumerator];
|
||||
NSString *fileStatus;
|
||||
while (fileStatus = [enumerator nextObject]) {
|
||||
NSString *fileName = [enumerator nextObject];
|
||||
[dictionary setObject:[fileStatus componentsSeparatedByString:@" "] forKey:fileName];
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
// This method is called for each of the three processes from above.
|
||||
// If all three are finished (self.busy == 0), then we can delete
|
||||
// all files previously marked as deletable
|
||||
- (void)indexStepComplete
|
||||
{
|
||||
// if we're still busy, do nothing :)
|
||||
if (--refreshStatus) {
|
||||
[self postIndexChange];
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point, all index operations have finished.
|
||||
// We need to find all files that don't have either
|
||||
// staged or unstaged files, and delete them
|
||||
|
||||
NSMutableArray *deleteFiles = [NSMutableArray array];
|
||||
for (PBChangedFile *file in files) {
|
||||
if (!file.hasStagedChanges && !file.hasUnstagedChanges)
|
||||
[deleteFiles addObject:file];
|
||||
}
|
||||
|
||||
if ([deleteFiles count]) {
|
||||
[self willChangeValueForKey:@"indexChanges"];
|
||||
for (PBChangedFile *file in deleteFiles)
|
||||
[files removeObject:file];
|
||||
[self didChangeValueForKey:@"indexChanges"];
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:PBGitIndexFinishedIndexRefresh
|
||||
object:self];
|
||||
[self postIndexChange];
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -11,33 +11,15 @@
|
||||
#import "PBChangedFile.h"
|
||||
|
||||
@interface PBGitIndexController : NSObject {
|
||||
int contextSize;
|
||||
|
||||
IBOutlet NSArrayController *stagedFilesController, *unstagedFilesController;
|
||||
IBOutlet PBGitCommitController *commitController;
|
||||
|
||||
IBOutlet PBIconAndTextCell* unstagedButtonCell;
|
||||
IBOutlet PBIconAndTextCell* stagedButtonCell;
|
||||
|
||||
IBOutlet NSTableView *unstagedTable;
|
||||
IBOutlet NSTableView *stagedTable;
|
||||
}
|
||||
|
||||
@property (assign) int contextSize;
|
||||
|
||||
- (NSString *) contextParameter;
|
||||
|
||||
- (void) stageFiles:(NSArray *)files;
|
||||
- (void) unstageFiles:(NSArray *)files;
|
||||
|
||||
- (IBAction) rowClicked:(NSCell *) sender;
|
||||
- (IBAction) tableClicked:(NSTableView *)tableView;
|
||||
|
||||
- (NSString *) stagedChangesForFile:(PBChangedFile *)file;
|
||||
- (NSString *) unstagedChangesForFile:(PBChangedFile *)file;
|
||||
|
||||
- (void)stopTrackingIndex;
|
||||
- (void)resumeTrackingIndex;
|
||||
|
||||
- (NSMenu *) menuForTable:(NSTableView *)table;
|
||||
@end
|
||||
|
||||
+32
-152
@@ -9,17 +9,18 @@
|
||||
#import "PBGitIndexController.h"
|
||||
#import "PBChangedFile.h"
|
||||
#import "PBGitRepository.h"
|
||||
#import "PBGitIndex.h"
|
||||
|
||||
#define FileChangesTableViewType @"GitFileChangedType"
|
||||
|
||||
@implementation PBGitIndexController
|
||||
@interface PBGitIndexController ()
|
||||
- (void)discardChangesForFiles:(NSArray *)files force:(BOOL)force;
|
||||
@end
|
||||
|
||||
@synthesize contextSize;
|
||||
@implementation PBGitIndexController
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
contextSize = 3;
|
||||
|
||||
[unstagedTable setDoubleAction:@selector(tableClicked:)];
|
||||
[stagedTable setDoubleAction:@selector(tableClicked:)];
|
||||
|
||||
@@ -28,64 +29,10 @@
|
||||
|
||||
[unstagedTable registerForDraggedTypes: [NSArray arrayWithObject:FileChangesTableViewType]];
|
||||
[stagedTable registerForDraggedTypes: [NSArray arrayWithObject:FileChangesTableViewType]];
|
||||
|
||||
}
|
||||
|
||||
- (void) stageFiles:(NSArray *)files
|
||||
{
|
||||
NSMutableString *input = [NSMutableString string];
|
||||
|
||||
for (PBChangedFile *file in files) {
|
||||
[input appendFormat:@"%@\0", file.path];
|
||||
}
|
||||
|
||||
int ret = 1;
|
||||
[commitController.repository outputForArguments:[NSArray arrayWithObjects:@"update-index", @"--add", @"--remove", @"-z", @"--stdin", nil]
|
||||
inputString:input retValue:&ret];
|
||||
|
||||
if (ret)
|
||||
{
|
||||
NSLog(@"Error when updating index. Retvalue: %i", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
[self stopTrackingIndex];
|
||||
for (PBChangedFile *file in files)
|
||||
{
|
||||
file.hasUnstagedChanges = NO;
|
||||
file.hasStagedChanges = YES;
|
||||
}
|
||||
[self resumeTrackingIndex];
|
||||
}
|
||||
|
||||
- (void) unstageFiles:(NSArray *)files
|
||||
{
|
||||
NSMutableString *input = [NSMutableString string];
|
||||
|
||||
for (PBChangedFile *file in files) {
|
||||
[input appendString:[file indexInfo]];
|
||||
}
|
||||
|
||||
int ret = 1;
|
||||
[commitController.repository outputForArguments:[NSArray arrayWithObjects:@"update-index", @"-z", @"--index-info", nil]
|
||||
inputString:input retValue:&ret];
|
||||
|
||||
if (ret)
|
||||
{
|
||||
NSLog(@"Error when updating index. Retvalue: %i", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
[self stopTrackingIndex];
|
||||
for (PBChangedFile *file in files)
|
||||
{
|
||||
file.hasUnstagedChanges = YES;
|
||||
file.hasStagedChanges = NO;
|
||||
}
|
||||
[self resumeTrackingIndex];
|
||||
}
|
||||
|
||||
- (void) ignoreFiles:(NSArray *)files
|
||||
// FIXME: Find a proper place for this method -- this is not it.
|
||||
- (void)ignoreFiles:(NSArray *)files
|
||||
{
|
||||
// Build output string
|
||||
NSMutableArray *fileList = [NSMutableArray array];
|
||||
@@ -122,63 +69,6 @@
|
||||
[[commitController.repository windowController] showErrorSheet:error];
|
||||
}
|
||||
|
||||
# pragma mark Displaying diffs
|
||||
|
||||
- (NSString *) stagedChangesForFile:(PBChangedFile *)file
|
||||
{
|
||||
NSString *indexPath = [@":0:" stringByAppendingString:file.path];
|
||||
|
||||
if (file.status == NEW)
|
||||
return [commitController.repository outputForArguments:[NSArray arrayWithObjects:@"show", indexPath, nil]];
|
||||
|
||||
return [commitController.repository outputInWorkdirForArguments:[NSArray arrayWithObjects:@"diff-index", [self contextParameter], @"--cached", [commitController parentTree], @"--", file.path, nil]];
|
||||
}
|
||||
|
||||
- (NSString *)unstagedChangesForFile:(PBChangedFile *)file
|
||||
{
|
||||
if (file.status == NEW) {
|
||||
NSStringEncoding encoding;
|
||||
NSError *error = nil;
|
||||
NSString *path = [[commitController.repository workingDirectory] stringByAppendingPathComponent:file.path];
|
||||
NSString *contents = [NSString stringWithContentsOfFile:path
|
||||
usedEncoding:&encoding
|
||||
error:&error];
|
||||
if (error)
|
||||
return nil;
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
return [commitController.repository outputInWorkdirForArguments:[NSArray arrayWithObjects:@"diff-files", [self contextParameter], @"--", file.path, nil]];
|
||||
}
|
||||
|
||||
- (void)discardChangesForFiles:(NSArray *)files force:(BOOL)force
|
||||
{
|
||||
if(!force) {
|
||||
int ret = [[NSAlert alertWithMessageText:@"Discard changes"
|
||||
defaultButton:nil
|
||||
alternateButton:@"Cancel"
|
||||
otherButton:nil
|
||||
informativeTextWithFormat:@"Are you sure you wish to discard the changes to this file?\n\nYou cannot undo this operation."] runModal];
|
||||
if (ret != NSAlertDefaultReturn)
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *paths = [files valueForKey:@"path"];
|
||||
NSString *input = [paths componentsJoinedByString:@"\0"];
|
||||
|
||||
NSArray *arguments = [NSArray arrayWithObjects:@"checkout-index", @"--index", @"--quiet", @"--force", @"-z", @"--stdin", nil];
|
||||
int ret = 1;
|
||||
[commitController.repository outputForArguments:arguments inputString:input retValue:&ret];
|
||||
if (ret) {
|
||||
[[commitController.repository windowController] showMessageSheet:@"Discarding changes failed" infoText:[NSString stringWithFormat:@"Discarding changes failed with error code %i", ret]];
|
||||
return;
|
||||
}
|
||||
|
||||
for (PBChangedFile *file in files)
|
||||
file.hasUnstagedChanges = NO;
|
||||
}
|
||||
|
||||
# pragma mark Context Menu methods
|
||||
- (BOOL) allSelectedCanBeIgnored:(NSArray *)selectedFiles
|
||||
{
|
||||
@@ -255,12 +145,12 @@
|
||||
|
||||
- (void) stageFilesAction:(id) sender
|
||||
{
|
||||
[self stageFiles:[sender representedObject]];
|
||||
[commitController.index stageFiles:[sender representedObject]];
|
||||
}
|
||||
|
||||
- (void) unstageFilesAction:(id) sender
|
||||
{
|
||||
[self unstageFiles:[sender representedObject]];
|
||||
[commitController.index unstageFiles:[sender representedObject]];
|
||||
}
|
||||
|
||||
- (void) openFilesAction:(id) sender
|
||||
@@ -274,10 +164,11 @@
|
||||
- (void) ignoreFilesAction:(id) sender
|
||||
{
|
||||
NSArray *selectedFiles = [sender representedObject];
|
||||
if ([selectedFiles count] > 0) {
|
||||
[self ignoreFiles:selectedFiles];
|
||||
}
|
||||
[commitController refresh:NULL];
|
||||
if ([selectedFiles count] == 0)
|
||||
return;
|
||||
|
||||
[self ignoreFiles:selectedFiles];
|
||||
[commitController.index refresh];
|
||||
}
|
||||
|
||||
- (void)discardFilesAction:(id) sender
|
||||
@@ -305,6 +196,20 @@
|
||||
[ws selectFile: path inFileViewerRootedAtPath:nil];
|
||||
}
|
||||
|
||||
- (void)discardChangesForFiles:(NSArray *)files force:(BOOL)force
|
||||
{
|
||||
if (!force) {
|
||||
int ret = [[NSAlert alertWithMessageText:@"Discard changes"
|
||||
defaultButton:nil
|
||||
alternateButton:@"Cancel"
|
||||
otherButton:nil
|
||||
informativeTextWithFormat:@"Are you sure you wish to discard the changes to this file?\n\nYou cannot undo this operation."] runModal];
|
||||
if (ret != NSAlertDefaultReturn)
|
||||
return;
|
||||
}
|
||||
|
||||
[commitController.index discardChangesForFiles:files];
|
||||
}
|
||||
|
||||
# pragma mark TableView icon delegate
|
||||
- (void)tableView:(NSTableView*)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn*)tableColumn row:(NSInteger)rowIndex
|
||||
@@ -320,9 +225,9 @@
|
||||
NSIndexSet *selectionIndexes = [tableView selectedRowIndexes];
|
||||
NSArray *files = [[controller arrangedObjects] objectsAtIndexes:selectionIndexes];
|
||||
if ([tableView tag] == 0)
|
||||
[self stageFiles:files];
|
||||
[commitController.index stageFiles:files];
|
||||
else
|
||||
[self unstageFiles:files];
|
||||
[commitController.index unstageFiles:files];
|
||||
}
|
||||
|
||||
- (void) rowClicked:(NSCell *)sender
|
||||
@@ -382,36 +287,11 @@ writeRowsWithIndexes:(NSIndexSet *)rowIndexes
|
||||
NSArray *files = [[controller arrangedObjects] objectsAtIndexes:rowIndexes];
|
||||
|
||||
if ([aTableView tag] == 0)
|
||||
[self unstageFiles:files];
|
||||
[commitController.index unstageFiles:files];
|
||||
else
|
||||
[self stageFiles:files];
|
||||
[commitController.index stageFiles:files];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *) contextParameter
|
||||
{
|
||||
return [[NSString alloc] initWithFormat:@"-U%i", contextSize];
|
||||
}
|
||||
|
||||
# pragma mark WebKit Accessibility
|
||||
|
||||
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark Private Methods
|
||||
- (void)stopTrackingIndex
|
||||
{
|
||||
[stagedFilesController setAutomaticallyRearrangesObjects:NO];
|
||||
[unstagedFilesController setAutomaticallyRearrangesObjects:NO];
|
||||
}
|
||||
- (void)resumeTrackingIndex
|
||||
{
|
||||
[stagedFilesController setAutomaticallyRearrangesObjects:YES];
|
||||
[unstagedFilesController setAutomaticallyRearrangesObjects:YES];
|
||||
[stagedFilesController rearrangeObjects];
|
||||
[unstagedFilesController rearrangeObjects];
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -27,5 +27,4 @@
|
||||
- (void) setStateMessage:(NSString *)state;
|
||||
|
||||
- (void) showMultiple:(NSArray *)files;
|
||||
- (void) setContextSize:(int)size;
|
||||
@end
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#import "PBWebChangesController.h"
|
||||
#import "PBGitIndexController.h"
|
||||
#import "PBGitIndex.h"
|
||||
|
||||
@implementation PBWebChangesController
|
||||
|
||||
@@ -25,15 +26,10 @@
|
||||
|
||||
- (void) didLoad
|
||||
{
|
||||
[[self script] setValue:indexController forKey:@"IndexController"];
|
||||
[[self script] setValue:controller.index forKey:@"Index"];
|
||||
[self refresh];
|
||||
}
|
||||
|
||||
- (BOOL) amend
|
||||
{
|
||||
return controller.amend;
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
@@ -81,9 +77,11 @@
|
||||
[NSNumber numberWithBool:selectedFileIsCached], nil]];
|
||||
}
|
||||
|
||||
- (void) stageHunk:(NSString *)hunk reverse:(BOOL)reverse
|
||||
- (void)stageHunk:(NSString *)hunk reverse:(BOOL)reverse
|
||||
{
|
||||
[controller stageHunk: hunk reverse:reverse];
|
||||
[controller.index applyPatch:hunk stage:YES reverse:reverse];
|
||||
// FIXME: Don't need a hard refresh
|
||||
|
||||
[self refresh];
|
||||
}
|
||||
|
||||
@@ -99,7 +97,7 @@
|
||||
}
|
||||
|
||||
if (ret == NSAlertDefaultReturn) {
|
||||
[controller discardHunk:hunk];
|
||||
[controller.index applyPatch:hunk stage:NO reverse:YES];
|
||||
[self refresh];
|
||||
}
|
||||
}
|
||||
@@ -110,12 +108,4 @@
|
||||
[script callWebScriptMethod:@"setState" withArguments: [NSArray arrayWithObject:state]];
|
||||
}
|
||||
|
||||
- (void) setContextSize:(int)size
|
||||
{
|
||||
if (size == indexController.contextSize)
|
||||
return;
|
||||
|
||||
indexController.contextSize = size;
|
||||
[self refresh];
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
/* Commit: Interface for selecting, staging, discarding, and unstaging
|
||||
hunks, individual lines, or ranges of lines. */
|
||||
|
||||
var contextLines = 5;
|
||||
|
||||
var showNewFile = function(file)
|
||||
{
|
||||
setTitle("New file: " + file.path);
|
||||
|
||||
var contents = IndexController.unstagedChangesForFile_(file);
|
||||
var contents = Index.diffForFile_staged_contextLines_(file, false, contextLines);
|
||||
if (!contents) {
|
||||
notify("Can not display changes (Binary file?)", -1);
|
||||
diff.innerHTML = "";
|
||||
@@ -49,23 +51,16 @@ var showFileChanges = function(file, cached) {
|
||||
hideState();
|
||||
|
||||
$("contextSize").oninput = function(element) {
|
||||
Controller.setContextSize_($("contextSize").value);
|
||||
contextSize = $("contextSize").value;
|
||||
}
|
||||
|
||||
if (file.status == 0) // New file?
|
||||
return showNewFile(file);
|
||||
|
||||
var changes;
|
||||
if (cached) {
|
||||
setTitle("Staged changes for " + file.path);
|
||||
displayContext();
|
||||
changes = IndexController.stagedChangesForFile_(file);
|
||||
}
|
||||
else {
|
||||
setTitle("Unstaged changes for " + file.path);
|
||||
displayContext();
|
||||
changes = IndexController.unstagedChangesForFile_(file);
|
||||
}
|
||||
setTitle((cached ? "Staged": "Unstaged") + " changes for" + file.path);
|
||||
displayContext();
|
||||
var changes = Index.diffForFile_staged_contextLines_(file, cached, contextLines);
|
||||
|
||||
|
||||
if (changes == "") {
|
||||
notify("This file has no more changes", 1);
|
||||
|
||||
Reference in New Issue
Block a user