diff --git a/GitX.xcodeproj/project.pbxproj b/GitX.xcodeproj/project.pbxproj
index be97512..d091d92 100644
--- a/GitX.xcodeproj/project.pbxproj
+++ b/GitX.xcodeproj/project.pbxproj
@@ -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 = ""; };
F5945E150E02B0C200706420 /* PBGitRepository.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitRepository.h; sourceTree = ""; };
F5945E160E02B0C200706420 /* PBGitRepository.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBGitRepository.m; sourceTree = ""; };
+ F59F1DD3105C4FF300115F88 /* PBGitIndex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitIndex.h; sourceTree = ""; };
+ F59F1DD4105C4FF300115F88 /* PBGitIndex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBGitIndex.m; sourceTree = ""; };
F5AD56770E79B78100EDAAFE /* PBCommitList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBCommitList.h; sourceTree = ""; };
F5AD56780E79B78100EDAAFE /* PBCommitList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBCommitList.m; sourceTree = ""; };
F5B721C30E05CF7E00AF29DC /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = ""; };
@@ -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 = "";
};
+ F59F1DD2105C4FDE00115F88 /* Index */ = {
+ isa = PBXGroup;
+ children = (
+ F5E927F60E883E7200056E75 /* PBChangedFile.h */,
+ F5E927F70E883E7200056E75 /* PBChangedFile.m */,
+ F59F1DD3105C4FF300115F88 /* PBGitIndex.h */,
+ F59F1DD4105C4FF300115F88 /* PBGitIndex.m */,
+ );
+ name = Index;
+ sourceTree = "";
+ };
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;
};
diff --git a/NSString_RegEx.m b/NSString_RegEx.m
index ee0e9bd..38d3848 100644
--- a/NSString_RegEx.m
+++ b/NSString_RegEx.m
@@ -57,7 +57,9 @@
break;
NSRange range = NSMakeRange(pmatch[i].rm_so, pmatch[i].rm_eo - pmatch[i].rm_so);
- NSString * substring = [self substringWithRange:range];
+ NSString * substring = [[[NSString alloc] initWithBytes:[self UTF8String] + range.location
+ length:range.length
+ encoding:NSUTF8StringEncoding] autorelease];
[outMatches addObject:substring];
if (ranges)
diff --git a/PBCLIProxy.m b/PBCLIProxy.m
index 1ac0e03..83507d2 100644
--- a/PBCLIProxy.m
+++ b/PBCLIProxy.m
@@ -56,6 +56,7 @@
[document.windowController showCommitView:self];
else {
PBGitRevSpecifier* rev = [[PBGitRevSpecifier alloc] initWithParameters:arguments];
+ rev.workingDirectory = repositoryPath;
document.currentBranch = [document addBranch: rev];
[document.windowController showHistoryView:self];
}
diff --git a/PBGitBinary.h b/PBGitBinary.h
index 804cbe9..5008b3d 100644
--- a/PBGitBinary.h
+++ b/PBGitBinary.h
@@ -8,6 +8,7 @@
#import
+#define MIN_GIT_VERSION "1.6.0"
@interface PBGitBinary : NSObject {
diff --git a/PBGitBinary.m b/PBGitBinary.m
index ccb0b0b..795891d 100644
--- a/PBGitBinary.m
+++ b/PBGitBinary.m
@@ -37,7 +37,7 @@ static NSString* gitPath = nil;
if (!version)
return NO;
- int c = [version compare:@"1.5.4"];
+ int c = [version compare:@"" MIN_GIT_VERSION];
if (c == NSOrderedSame || c == NSOrderedDescending) {
gitPath = path;
return YES;
@@ -60,7 +60,7 @@ static NSString* gitPath = nil;
alternateButton:nil
otherButton:nil
informativeTextWithFormat:@"You entered a custom git path in the Preferences pane, "
- "but this path is not a valid git v1.5.4 or higher binary. We're going to use the default "
+ "but this path is not a valid git v" MIN_GIT_VERSION " or higher binary. We're going to use the default "
"search paths instead"] runModal];
}
@@ -80,7 +80,7 @@ static NSString* gitPath = nil;
return;
}
- NSLog(@"Could not find a git binary higher than version 1.5.4.");
+ NSLog(@"Could not find a git binary higher than version " MIN_GIT_VERSION);
}
+ (NSString *) path;
@@ -109,7 +109,7 @@ static NSMutableArray *locations = nil;
+ (NSString *) notFoundError
{
NSMutableString *error = [NSMutableString stringWithString:
- @"Could not find a git binary version 1.5.4 or higher.\n"
+ @"Could not find a git binary version " MIN_GIT_VERSION " or higher.\n"
"Please make sure there is a git binary in one of the following locations:\n\n"];
for (NSString *location in [PBGitBinary searchLocations]) {
[error appendFormat:@"\t%@\n", location];
diff --git a/PBGitCommit.m b/PBGitCommit.m
index b883c95..9ebe3b5 100644
--- a/PBGitCommit.m
+++ b/PBGitCommit.m
@@ -66,20 +66,10 @@
return str;
}
-// NOTE: This method should remain threadsafe, as we load it in async
-// from the web view.
+// FIXME: Remove this method once it's unused.
- (NSString*) details
{
- if (details != nil)
- return details;
-
- NSMutableArray *arguments = [NSMutableArray arrayWithObjects:@"show", @"--pretty=raw", @"-M", @"--no-color", [self realSha], nil];
- if (![PBGitDefaults showWhitespaceDifferences])
- [arguments insertObject:@"-w" atIndex:1];
-
- details = [self.repository outputForArguments:arguments];
-
- return details;
+ return @"";
}
- (NSString *) patch
diff --git a/PBGitCommitController.h b/PBGitCommitController.h
index 0976738..f29f884 100644
--- a/PBGitCommitController.h
+++ b/PBGitCommitController.h
@@ -9,12 +9,12 @@
#import
#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;
diff --git a/PBGitCommitController.m b/PBGitCommitController.m
index 1e894ee..6a77348 100644
--- a/PBGitCommitController.m
+++ b/PBGitCommitController.m
@@ -10,26 +10,44 @@
#import "NSFileHandleExt.h"
#import "PBChangedFile.h"
#import "PBWebChangesController.h"
-#import "NSString_RegEx.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,152 +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
- 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;
- }
- }
-
- // 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"]]) {
@@ -345,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
diff --git a/PBGitCommitView.xib b/PBGitCommitView.xib
index 643bfa3..5b8021f 100644
--- a/PBGitCommitView.xib
+++ b/PBGitCommitView.xib
@@ -2,15 +2,14 @@
1050
- 9J61
+ 9L31a677
- 949.46
+ 949.54353.00
@@ -963,38 +962,6 @@
139
-
-
-
- contentArray: files
-
-
-
-
-
- contentArray: files
- contentArray
- files
- 2
-
-
- 150
- cachedFilesController
@@ -1067,22 +1034,6 @@
241
-
-
- value: amend
-
-
-
-
-
- value: amend
- value
- amend
- 2
-
-
- 252
- webController
@@ -1163,22 +1114,6 @@
264
-
-
- stagedButtonCell
-
-
-
- 265
-
-
-
- unstagedButtonCell
-
-
-
- 266
- rowClicked:
@@ -1219,6 +1154,54 @@
280
+
+
+ contentArray: index.indexChanges
+
+
+
+
+
+ contentArray: index.indexChanges
+ contentArray
+ index.indexChanges
+ 2
+
+
+ 281
+
+
+
+ contentArray: index.indexChanges
+
+
+
+
+
+ contentArray: index.indexChanges
+ contentArray
+ index.indexChanges
+ 2
+
+
+ 282
+
+
+
+ value: index.amend
+
+
+
+
+
+ value: index.amend
+ value
+ index.amend
+ 2
+
+
+ 283
+
@@ -1616,7 +1599,7 @@
com.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilderKitcom.apple.InterfaceBuilderKit
- {{59, 63}, {852, 432}}
+ {{428, 510}, {852, 432}}com.apple.InterfaceBuilder.CocoaPlugin
@@ -1680,7 +1663,7 @@
- 280
+ 283
@@ -1764,20 +1747,16 @@
YEScommitController
- stagedButtonCellstagedFilesControllerstagedTable
- unstagedButtonCellunstagedFilesControllerunstagedTableYESPBGitCommitController
- PBIconAndTextCellNSArrayControllerNSTableView
- PBIconAndTextCellNSArrayControllerNSTableView
diff --git a/PBGitHistoryController.m b/PBGitHistoryController.m
index f8ad04a..a9fde36 100644
--- a/PBGitHistoryController.m
+++ b/PBGitHistoryController.m
@@ -66,8 +66,7 @@
switch (self.selectedTab) {
case 0: self.webCommit = realCommit; break;
- case 1: self.rawCommit = realCommit; break;
- case 2: self.gitTree = realCommit.tree; break;
+ case 1: self.gitTree = realCommit.tree; break;
}
}
diff --git a/PBGitHistoryView.xib b/PBGitHistoryView.xib
index 741ed2c..68b7789 100644
--- a/PBGitHistoryView.xib
+++ b/PBGitHistoryView.xib
@@ -2,14 +2,14 @@
1050
- 9L30
+ 9L31a677949.54353.00YES
-
+ YES
@@ -98,7 +98,7 @@
293
- {{376, 6}, {99, 25}}
+ {{376, 6}, {71, 25}}YES
@@ -124,23 +124,14 @@
2
-
- NSImage
- NSListViewTemplate
-
-
- Raw View
- 1
- 2
-
-
- 3.200000e+01NSImageNSPathTemplate
+ Tree View
- 0
+ 9
+ 22
@@ -153,7 +144,7 @@
YES
- 274
+ 4370YES
@@ -320,7 +311,6 @@
3YES
- NOYES
@@ -410,7 +400,7 @@
1
-
+ 274YES
@@ -477,268 +467,10 @@
-
- 2
-
-
- 256
-
- YES
-
-
- 274
-
- YES
-
-
- 2304
-
- YES
-
-
- 2322
- {835, 70}
-
-
-
-
-
- Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum Et harumd und lookum like Greek to me, dereud facilis est er expedit distinct. Nam liber te conscient to factor tum poen legum odioque civiuda
-
-
- YES
-
- YES
- NSFont
- NSParagraphStyle
-
-
- YES
-
- Monaco
- 1.000000e+01
- 16
-
-
- 3
-
- YES
-
- 0.000000e+00
-
-
- 5.600000e+01
-
-
- 1.120000e+02
-
-
- 1.680000e+02
-
-
- 2.240000e+02
-
-
- 2.800000e+02
-
-
- 3.360000e+02
-
-
- 3.920000e+02
-
-
- 4.480000e+02
-
-
- 5.040000e+02
-
-
- 5.600000e+02
-
-
- 6.160000e+02
-
-
- 6.720000e+02
-
-
- 7.280000e+02
-
-
- 7.840000e+02
-
-
- 8.400000e+02
-
-
- 8.960000e+02
-
-
- 9.520000e+02
-
-
- 1.008000e+03
-
-
- 1.064000e+03
-
-
- 1.120000e+03
-
-
- 1.176000e+03
-
-
- 1.232000e+03
-
-
- 1.288000e+03
-
-
- 1.344000e+03
-
-
- 1.400000e+03
-
-
- 1.456000e+03
-
-
- 1.512000e+03
-
-
- 1.568000e+03
-
-
- 1.624000e+03
-
-
- 1.680000e+03
-
-
- 1.736000e+03
-
-
-
-
-
-
-
-
- YES
-
-
- 6
-
-
-
- 8.350000e+02
- 1
-
-
- 2369
-
-
-
- YES
-
- YES
- NSBackgroundColor
- NSColor
-
-
- YES
-
- 6
- System
- selectedTextBackgroundColor
-
-
-
- 6
- System
- selectedTextColor
-
-
-
-
-
-
- YES
-
- YES
- NSColor
- NSUnderline
-
-
- YES
-
- 1
- MCAwIDEAA
-
-
-
-
-
-
- 6
- {1687, 1e+07}
- {83, 0}
-
-
-
- {{1, 1}, {835, 185}}
-
-
-
-
-
- {4, -5}
- 1
-
- 4
-
-
-
- 256
- {{836, 1}, {15, 185}}
-
-
- _doScroller:
- 2.240493e-01
-
-
-
- 256
- {{-100, -100}, {87, 18}}
-
- 1
-
- _doScroller:
- 1.000000e+00
- 9.456522e-01
-
-
- {852, 187}
-
-
- 18
-
-
-
-
-
- {852, 186}
-
- Raw
-
-
- Item 2
-
+ 256YES
@@ -869,7 +601,124 @@
Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum Et harumd und lookum like Greek to me, dereud facilis est er expedit distinct. Nam liber te conscient to factor tum poen legum odioque civiuda
-
+
+ YES
+
+ YES
+ NSFont
+ NSParagraphStyle
+
+
+ YES
+
+ Monaco
+ 1.000000e+01
+ 16
+
+
+ 3
+
+ YES
+
+ 0.000000e+00
+
+
+ 5.600000e+01
+
+
+ 1.120000e+02
+
+
+ 1.680000e+02
+
+
+ 2.240000e+02
+
+
+ 2.800000e+02
+
+
+ 3.360000e+02
+
+
+ 3.920000e+02
+
+
+ 4.480000e+02
+
+
+ 5.040000e+02
+
+
+ 5.600000e+02
+
+
+ 6.160000e+02
+
+
+ 6.720000e+02
+
+
+ 7.280000e+02
+
+
+ 7.840000e+02
+
+
+ 8.400000e+02
+
+
+ 8.960000e+02
+
+
+ 9.520000e+02
+
+
+ 1.008000e+03
+
+
+ 1.064000e+03
+
+
+ 1.120000e+03
+
+
+ 1.176000e+03
+
+
+ 1.232000e+03
+
+
+ 1.288000e+03
+
+
+ 1.344000e+03
+
+
+ 1.400000e+03
+
+
+ 1.456000e+03
+
+
+ 1.512000e+03
+
+
+ 1.568000e+03
+
+
+ 1.624000e+03
+
+
+ 1.680000e+03
+
+
+ 1.736000e+03
+
+
+
+
+
@@ -896,8 +745,18 @@
YES
-
-
+
+ 6
+ System
+ selectedTextBackgroundColor
+
+
+
+ 6
+ System
+ selectedTextColor
+
+
@@ -910,7 +769,10 @@
YES
-
+
+ 1
+ MCAwIDEAA
+
@@ -927,7 +789,10 @@
-
+
+ {4, -5}
+ 1
+ 4
@@ -967,21 +832,20 @@
{852, 186}
- Tree
-
+ 6YESYESYES
-
+
@@ -1936,22 +1800,6 @@
217
-
-
- value: rawCommit.details
-
-
-
-
-
- value: rawCommit.details
- value
- rawCommit.details
- 2
-
-
- 230
- commitController
@@ -2221,23 +2069,12 @@
YES
- Bottom View
-
- 5
-
-
- YES
-
-
-
- Raw View
- 6
@@ -2360,41 +2197,6 @@
-
- 22
-
-
- YES
-
-
-
-
-
- 23
-
-
- YES
-
-
-
-
-
-
-
- 24
-
-
-
-
- 25
-
-
-
-
- 26
-
-
- 47
@@ -2819,11 +2621,9 @@
20.IBPluginDependency21.IBAttributePlaceholdersKey21.IBPluginDependency
- 22.IBPluginDependency223.IBPluginDependency224.IBPluginDependency225.IBPluginDependency
- 23.IBPluginDependency231.IBPluginDependency236.IBEditorWindowLastContentRect236.IBWindowTemplateEditedContentRect
@@ -2835,15 +2635,12 @@
237.IBPluginDependency238.IBPluginDependency239.IBPluginDependency
- 24.IBPluginDependency240.IBPluginDependency242.IBPluginDependency243.IBPluginDependency245.IBPluginDependency246.IBPluginDependency247.IBPluginDependency
- 25.IBPluginDependency
- 26.IBPluginDependency261.IBAttributePlaceholdersKey261.IBPluginDependency262.IBPluginDependency
@@ -2890,7 +2687,6 @@
47.IBPluginDependency48.IBPluginDependency49.IBPluginDependency
- 5.IBPluginDependency50.IBPluginDependency51.IBPluginDependency52.IBPluginDependency
@@ -2949,8 +2745,6 @@
com.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugin
- com.apple.InterfaceBuilder.CocoaPlugin
- com.apple.InterfaceBuilder.CocoaPlugin{{504, 612}, {346, 102}}{{504, 612}, {346, 102}}
@@ -2967,9 +2761,6 @@
com.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugin
- com.apple.InterfaceBuilder.CocoaPlugin
- com.apple.InterfaceBuilder.CocoaPlugin
- com.apple.InterfaceBuilder.CocoaPluginToolTip
@@ -3038,7 +2829,6 @@
com.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugin
- com.apple.InterfaceBuilder.CocoaPlugin
diff --git a/PBGitIndex.h b/PBGitIndex.h
new file mode 100644
index 0000000..7ca1db7
--- /dev/null
+++ b/PBGitIndex.h
@@ -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
+
+@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
diff --git a/PBGitIndex.m b/PBGitIndex.m
new file mode 100644
index 0000000..4174d49
--- /dev/null
+++ b/PBGitIndex.m
@@ -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 --
+ 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
diff --git a/PBGitIndexController.h b/PBGitIndexController.h
index a3bc191..1a455e6 100644
--- a/PBGitIndexController.h
+++ b/PBGitIndexController.h
@@ -11,30 +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;
-
- (NSMenu *) menuForTable:(NSTableView *)table;
@end
diff --git a/PBGitIndexController.m b/PBGitIndexController.m
index 21c7076..5d2a949 100644
--- a/PBGitIndexController.m
+++ b/PBGitIndexController.m
@@ -9,22 +9,18 @@
#import "PBGitIndexController.h"
#import "PBChangedFile.h"
#import "PBGitRepository.h"
+#import "PBGitIndex.h"
#define FileChangesTableViewType @"GitFileChangedType"
-@interface PBGitIndexController (PrivateMethods)
-- (void)stopTrackingIndex;
-- (void)resumeTrackingIndex;
+@interface PBGitIndexController ()
+- (void)discardChangesForFiles:(NSArray *)files force:(BOOL)force;
@end
@implementation PBGitIndexController
-@synthesize contextSize;
-
- (void)awakeFromNib
{
- contextSize = 3;
-
[unstagedTable setDoubleAction:@selector(tableClicked:)];
[stagedTable setDoubleAction:@selector(tableClicked:)];
@@ -33,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];
@@ -127,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
{
@@ -260,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
@@ -279,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
@@ -310,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
@@ -325,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
@@ -387,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
diff --git a/PBGitRevList.h b/PBGitRevList.h
index 2f71c3f..42bc82a 100644
--- a/PBGitRevList.h
+++ b/PBGitRevList.h
@@ -8,14 +8,15 @@
#import
+@class PBGitRepository;
@interface PBGitRevList : NSObject {
NSArray* commits;
- id repository;
+ PBGitRepository *repository;
NSString* lastSha;
}
-- initWithRepository:(id)repo;
+- initWithRepository:(PBGitRepository *)repo;
- (void) readCommitsForce: (BOOL) force;
- (void) reload;
diff --git a/PBGitRevList.mm b/PBGitRevList.mm
index 1d8023a..ca3704f 100644
--- a/PBGitRevList.mm
+++ b/PBGitRevList.mm
@@ -23,7 +23,7 @@ using namespace std;
@implementation PBGitRevList
@synthesize commits;
-- initWithRepository: (id) repo
+- (id)initWithRepository:(PBGitRepository *)repo
{
repository = repo;
[repository addObserver:self forKeyPath:@"currentBranch" options:0 context:nil];
@@ -74,23 +74,21 @@ using namespace std;
PBGitGrapher* g = [[PBGitGrapher alloc] initWithRepository: repository];
std::map encodingMap;
- NSMutableArray* arguments;
+ NSString *formatString = @"--pretty=format:%H\01%e\01%an\01%s\01%P\01%at";
BOOL showSign = [rev hasLeftRight];
if (showSign)
- arguments = [NSMutableArray arrayWithObjects:@"log", @"-z", @"--early-output", @"--topo-order", @"--pretty=format:%H\01%e\01%an\01%s\01%P\01%at\01%m", nil];
- else
- arguments = [NSMutableArray arrayWithObjects:@"log", @"-z", @"--early-output", @"--topo-order", @"--pretty=format:%H\01%e\01%an\01%s\01%P\01%at", nil];
+ formatString = [formatString stringByAppendingString:@"\01%m"];
+
+ NSMutableArray *arguments = [NSMutableArray arrayWithObjects:@"log", @"-z", @"--early-output", @"--topo-order", @"--children", formatString, nil];
if (!rev)
[arguments addObject:@"HEAD"];
else
[arguments addObjectsFromArray:[rev parameters]];
- if ([rev hasPathLimiter])
- [arguments insertObject:@"--children" atIndex:1];
-
- NSTask *task = [PBEasyPipe taskForCommand:[PBGitBinary path] withArgs:arguments inDir:[repository fileURL].path];
+ NSString *directory = rev.workingDirectory ? rev.workingDirectory.path : repository.fileURL.path;
+ NSTask *task = [PBEasyPipe taskForCommand:[PBGitBinary path] withArgs:arguments inDir:directory];
[task launch];
NSFileHandle* handle = [task.standardOutput fileHandleForReading];
diff --git a/PBGitRevSpecifier.h b/PBGitRevSpecifier.h
index 1be1199..0461908 100644
--- a/PBGitRevSpecifier.h
+++ b/PBGitRevSpecifier.h
@@ -10,8 +10,9 @@
#import
@interface PBGitRevSpecifier : NSObject {
- NSString* description;
- NSArray* parameters;
+ NSString *description;
+ NSArray *parameters;
+ NSURL *workingDirectory;
}
- (id) initWithParameters:(NSArray*) params;
@@ -27,6 +28,8 @@
+ (PBGitRevSpecifier *)allBranchesRevSpec;
+ (PBGitRevSpecifier *)localBranchesRevSpec;
-@property(copy) NSString* description;
-@property(readonly) NSArray* parameters;
+@property(retain) NSString *description;
+@property(readonly) NSArray *parameters;
+@property(retain) NSURL *workingDirectory;
+
@end
diff --git a/PBGitRevSpecifier.m b/PBGitRevSpecifier.m
index ded8575..0fe9093 100644
--- a/PBGitRevSpecifier.m
+++ b/PBGitRevSpecifier.m
@@ -11,7 +11,7 @@
@implementation PBGitRevSpecifier
-@synthesize parameters, description;
+@synthesize parameters, description, workingDirectory;
- (id) initWithParameters:(NSArray*) params
{
diff --git a/PBGitTree.m b/PBGitTree.m
index 21c91f8..c8003d1 100644
--- a/PBGitTree.m
+++ b/PBGitTree.m
@@ -63,18 +63,12 @@
return NO;
}
-- (BOOL)hasBinaryHeader:(NSString *)fileHeader
+- (BOOL)hasBinaryHeader:(NSString*)contents
{
- if (!fileHeader)
+ if(!contents)
return NO;
- NSString *filetype = [PBEasyPipe outputForCommand:@"/usr/bin/file"
- withArgs:[NSArray arrayWithObjects:@"-b", @"-N", @"-", nil]
- inDir:[repository workingDirectory]
- inputString:fileHeader
- retValue:nil];
-
- return [filetype rangeOfString:@"text"].location == NSNotFound;
+ return [contents rangeOfString:@"\0" options:0 range:NSMakeRange(0, ([contents length] >= 8000) ? 7999 : [contents length])].location != NSNotFound;
}
- (BOOL)hasBinaryAttributes
@@ -143,14 +137,13 @@
if ([self hasBinaryAttributes])
return [NSString stringWithFormat:@"%@ appears to be a binary file of %d bytes", [self fullPath], [self fileSize]];
- long long fileSize = [self fileSize];
- if (fileSize > 52428800) // ~50MB
- return [NSString stringWithFormat:@"%@ is too big to be displayed (%d bytes)", [self fullPath], fileSize];
+ if ([self fileSize] > 52428800) // ~50MB
+ return [NSString stringWithFormat:@"%@ is too big to be displayed (%d bytes)", [self fullPath], [self fileSize]];
- NSString *contents = [self contents];
+ NSString* contents = [self contents];
- if ([self hasBinaryHeader:([contents length] >= 100) ? [contents substringToIndex:99] : contents])
- return [NSString stringWithFormat:@"%@ appears to be a binary file of %d bytes", [self fullPath], fileSize];
+ if ([self hasBinaryHeader:contents])
+ return [NSString stringWithFormat:@"%@ appears to be a binary file of %d bytes", [self fullPath], [self fileSize]];
return contents;
}
diff --git a/PBWebChangesController.h b/PBWebChangesController.h
index 0b0ec13..069f6fc 100644
--- a/PBWebChangesController.h
+++ b/PBWebChangesController.h
@@ -27,5 +27,4 @@
- (void) setStateMessage:(NSString *)state;
- (void) showMultiple:(NSArray *)files;
-- (void) setContextSize:(int)size;
@end
diff --git a/PBWebChangesController.m b/PBWebChangesController.m
index d24a8af..933fecc 100644
--- a/PBWebChangesController.m
+++ b/PBWebChangesController.m
@@ -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
diff --git a/PBWebHistoryController.m b/PBWebHistoryController.m
index 35979a4..1934b73 100644
--- a/PBWebHistoryController.m
+++ b/PBWebHistoryController.m
@@ -7,6 +7,7 @@
//
#import "PBWebHistoryController.h"
+#import "PBGitDefaults.h"
@implementation PBWebHistoryController
@@ -49,6 +50,37 @@
NSArray *arguments = [NSArray arrayWithObjects:content, [[[historyController repository] headRef] simpleRef], nil];
[[self script] callWebScriptMethod:@"loadCommit" withArguments: arguments];
+
+ // Now we load the extended details. We used to do this in a separate thread,
+ // but this caused some funny behaviour because NSTask's and NSThread's don't really
+ // like each other. Instead, just do it async.
+
+ NSMutableArray *taskArguments = [NSMutableArray arrayWithObjects:@"show", @"--pretty=raw", @"-M", @"--no-color", currentSha, nil];
+ if (![PBGitDefaults showWhitespaceDifferences])
+ [taskArguments insertObject:@"-w" atIndex:1];
+
+ NSFileHandle *handle = [repository handleForArguments:taskArguments];
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ // Remove notification, in case we have another one running
+ [nc removeObserver:self];
+ [nc addObserver:self selector:@selector(commitDetailsLoaded:) name:NSFileHandleReadToEndOfFileCompletionNotification object:handle];
+ [handle readToEndOfFileInBackgroundAndNotify];
+}
+
+- (void)commitDetailsLoaded:(NSNotification *)notification
+{
+ NSData *data = [[notification userInfo] valueForKey:NSFileHandleNotificationDataItem];
+ if (!data)
+ return;
+
+ NSString *details = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ if (!details)
+ details = [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding];
+
+ if (!details)
+ return;
+
+ [[view windowScriptObject] callWebScriptMethod:@"loadCommitDetails" withArguments:[NSArray arrayWithObject:details]];
}
- (void) selectCommit: (NSString*) sha
diff --git a/Site/UserManual/text/03 - Committing.markdown b/Site/UserManual/text/03 - Committing.markdown
index 51c3b27..dbd53aa 100644
--- a/Site/UserManual/text/03 - Committing.markdown
+++ b/Site/UserManual/text/03 - Committing.markdown
@@ -31,9 +31,9 @@ changes will be staged. Clicking the 'Discard' button will irreversibly throw aw
confirmation can be silenced using Alt-Click.
GitX 0.7 introduced a new way of staging lines: Simply drag-select a few of the lines you want to stage/unstage and a
-'Stage lines' button will appear next to it. This allows for much finer granularity than the hunks determined by diff.
-Double-clicking a line selects the sub-part of this hunk which isn't separated by blank lines. Selecting lines across
-hunks is currently not possible.
+'Stage lines' button will appear next to it. This allows for much finer granularity than the hunks determined by diff.
+Double-clicking a changed line selects the entire surrounding block of changes. Selecting lines across hunks is not
+possible.
#### Committing
diff --git a/Site/text/index.markdown b/Site/text/index.markdown
index 12f32f9..1f60b02 100644
--- a/Site/text/index.markdown
+++ b/Site/text/index.markdown
@@ -27,7 +27,7 @@ then in silky smooth OS X style!
Requirements
- GitX runs on Mac OS X 10.5 Leopard and Mac OS X 10.6 Snow Leopard. Because it uses features like Garbage Collection, you can't compile it on earlier systems. GitX also requires a fairly recent Git -- version 1.5.6 and higher are all supported.
+ GitX runs on Mac OS X 10.5 Leopard and Mac OS X 10.6 Snow Leopard. Because it uses features like Garbage Collection, you can't compile it on earlier systems. GitX also requires a fairly recent Git -- version 1.6.0 and higher are all supported.
This is a bugfix release. The following bugs have been fixed:
--
--
--
The commit view shows new files with linebreaks
--
The history view works with Git >= 1.5.4 again
--
Reloading the detailed view in the History no longer causes an empty page
--
--
--
Version 0.6 has the following new features and enhancements:
--
--
--
The diff display now looks much nicer, using boxes to segment files
--
The toolbar can now me customized
--
Images that have been changed or added in a commit can now be viewed
-- inline in GitX
--
GitX has gained a preference pane which allows you to specify a git path
-- and disable the Gist and Gravatar integration
--
The commit interface is now more intuitive. Particularly, you can now
-- select multiple files and use drag and drop to stage / unstage files
--
You can now drag and drop files out of the commit view
--
The files in the commit view have gained a context menu that allows you
-- to revert changes / open the file / ignore the file
--
It is now possible to adjust the amount of context lines in the commit view.
-- Using a smaller context size allows you to do more fine-grained commits
--
The branch menu is now organized in branches/remotes/tags
--
The view switch button now uses icons rather than words
--
The view shortcuts have changed to use command 1/2 for the history/commit
-- view. The history’s subviews can now be changed using command-option-1/2/3
--
Listing commits has become much faster
--
GitX no longer spawns zombie processes
--
GitX now shows a list of files that have been changed in a commit
--
GitX now uses libgit2 to store object id’s, reducing it’s memory footprint
--
--
--
In addition many bugs were fixed, including the correct calculation of a
-- gravatar MD5 hash.
--
-- ]]>
--
--
--
--
--
-diff --git a/Site/lib/release_notes.rb b/Site/lib/release_notes.rb
-new file mode 100644
-index 0000000..8ff598b
---- /dev/null
-+++ b/Site/lib/release_notes.rb
-@@ -0,0 +1,50 @@
-+#!/usr/bin/ruby
-+
-+RELEASE_NOTES_PATH = File.join(File.dirname(__FILE__), "..", "..", "Documentation", "ReleaseNotes")
-+
-+module ReleaseNotes
-+
-+ VERSION_MATCH = /v([0-9.]*).txt$/
-+
-+ # Find all release not files
-+ def self.release_files
-+ notes = Dir.glob(File.join(RELEASE_NOTES_PATH, "v*.txt"))
-+
-+ # Sort files by version number
-+ notes.sort do |x,y|
-+ x = x.match VERSION_MATCH
-+ y = y.match VERSION_MATCH
-+ # Puts nonmatching files at the bottom
-+ if !x && y
-+ 1
-+ elsif !y && x
-+ -1
-+ else
-+ # compare version strings, newest at the top
-+ y[1].split(".").map { |a| a.to_i } <=> x[1].split(".").map { |a| a.to_i }
-+ end
-+ end
-+ end
-+
-+ # Aggregate all release notes in a string
-+ def self.aggregate_notes
-+ file = ""
-+ release_files.each do |x|
-+ file << File.read(x)
-+ file << "\n"
-+ end
-+ file
-+ end
-+
-+ def self.last_version
-+ last_file = release_files.first
-+ if last_file =~ VERSION_MATCH
-+ return $1
-+ end
-+ nil
-+ end
-+
-+ def self.last_notes
-+ File.read(release_files.first)
-+ end
-+end
-\ No newline at end of file
-diff --git a/Site/template.html b/Site/template.html
-deleted file mode 100644
-index c1001b6..0000000
---- a/Site/template.html
-+++ /dev/null
-@@ -1,55 +0,0 @@
--<%
--def create_link(name, description)
-- "#{description}"
--end
-- %>
--
--
--
--
-- GitX - <%= @title %>
--
--
--
--
--
--
--
--
--
-@@ -37,7 +37,7 @@ then in silky smooth OS X style!
- Download
-
-
-- The newest version of GitX is 0.6.1. This version can be downloaded from here. To see what has changed, read the Release History.
-+ The newest version of GitX is <%= ReleaseNotes::last_version %>. This version can be downloaded from here. To see what has changed, read the Release History.
-
-
- After you have started it once, you can install the command-line tool through the menu (GitX->Enable Terminal Usage…). This will install a “gitx” binary in /usr/local/bin.
-diff --git a/Site/text/release_history.markdown b/Site/text/release_history.markdown
-index 9b95961..1909539 100644
---- a/Site/text/release_history.markdown
-+++ b/Site/text/release_history.markdown
-@@ -3,105 +3,4 @@
- Release history
-
-
--### Changes in v0.6.1:
--
--This is a bugfix release. The following bugs have been fixed:
--
--* The commit view shows new files with linebreaks
--* The history view works with Git >= 1.5.4 again
--* Reloading the detailed view in the History no longer causes an empty page
--
--### Changes in v0.6
--
--This release has the following new features and enhancements:
--
--* The diff display now looks much nicer, using boxes to segment files
--* The toolbar can now me customized
--* Images that have been changed or added in a commit can now be viewed
-- inline in GitX
--* GitX has gained a preference pane which allows you to specify a git path
-- and disable the Gist and Gravatar integration
--* The commit interface is now more intuitive. Particularly, you can now
-- select multiple files and use drag and drop to stage / unstage files
--* You can now drag and drop files out of the commit view
--* The files in the commit view have gained a context menu that allows you
-- to revert changes / open the file / ignore the file
--* It is now possible to adjust the amount of context lines in the commit view.
-- Using a smaller context size allows you to do more fine-grained commits
--* The branch menu is now organized in branches/remotes/tags
--* The view switch button now uses icons rather than words
--* The view shortcuts have changed to use command 1/2 for the history/commit
-- view. The history's subviews can now be changed using command-option-1/2/3
--* Listing commits has become much faster
--* GitX no longer spawns zombie processes
--* GitX now shows a list of files that have been changed in a commit
--* GitX now uses libgit2 to store object id's, reducing it's memory footprint
--
--In addition many bugs were fixed, including the correct calculation of a
--gravatar MD5 hash.
--
--### Changes in v0.5
--
--This feature release has several new smaller or larger features:
--
--* The current branch is now highlighted
--* In the commit view, there is an option to amend commits
--* The "Gist it" button now respects github.user/token
--* Display a gravatar of the committer
--* The commit message view now displays a vertical line at 50 characters
--* It is now possible to revert changes by using the context menu in the
-- commit view
--* You can now stage only parts of a file by using the "Stage Hunk" buttons
-- in the commit view
--* You can now use GitX to show a diff of anything, for example by using
-- 'gitx --diff HEAD^^' or 'git diff HEAD~3 | gitx --diff'
--* You can now drag and drop refs to move them and also create branches
--
--In addition, the following bugs have been fixed:
--
--* Better detection of git version
--* Branch lines are no longer interspersed with half a pixel of whitespace
--* The toolbar keeps its state when switching views
--
--
Changes in v0.4.1:
--
--
The diff display is now much faster
--
More locations are now searched for a default git
--
Code pasted online is now private
--
--
--
Changes in v0.4:
--
--
A new commitview, allowing you to selectively add changes and commit them.
--
You can now upload a commit as a patch to gist.github.com
--
--
GitX now searches for your git binary in more directories and is smarter
-- about reporting errors regarding git paths.
--
You can now remove branches by right-clicking on them in the detailed view
--
GitX now comes with a spicy new icon
--
The diff view has become prettier and now also highlights trailing
-- whitespace.
--
Various little changes and stability improvement
--
--
Changes in v0.3:
--
--
You can now pass on command-line arguments just like you can with ‘git log’
--
The program has an icon
--
Also displays remote branches in the branch list
--
--
Is better in determining if a directory is a bare git repository
--
Support for—left-right: use ‘gitx—left-right HEAD..origin/master’
-- to see which commits are only on your branch or on their branch
--
Navigate through changed hunks by using j/k keys
--
Scroll down in webview by using space / shift-space