mirror of
https://github.com/kennethreitz-archive/gitx.git
synced 2026-06-05 23:40:18 +00:00
Add a new class, PBGitIndex, which integrates functionality from both indexcontrollers
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;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
// 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)commit;
|
||||
|
||||
// Inter-file changes:
|
||||
//- (void)stageFiles:(NSArray *)files;
|
||||
//- (void)unstageFiles:(NSArray *)files;
|
||||
|
||||
// Intra-file changes
|
||||
//- (void)applyPatch:(NSString *)hunk stage:(BOOL)stage reverse:(BOOL)reverse;
|
||||
|
||||
@end
|
||||
+345
@@ -0,0 +1,345 @@
|
||||
//
|
||||
// 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"
|
||||
|
||||
@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;
|
||||
|
||||
@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];
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation PBGitIndex (IndexRefreshMethods)
|
||||
|
||||
- (void)indexRefreshFinished:(NSNotification *)notification
|
||||
{
|
||||
if ([(NSNumber *)[(NSDictionary *)[notification userInfo] objectForKey:@"NSFileHandleError"] intValue])
|
||||
{
|
||||
// TODO: send updatefailed notification?
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 used to add the file to the index
|
||||
// FIXME: request the real file mode
|
||||
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
|
||||
{
|
||||
// TODO: Stop tracking files
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
// TODO: Finish tracking files
|
||||
|
||||
// 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: Return 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)
|
||||
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"];
|
||||
}
|
||||
|
||||
// TODO: Sent index refresh finished operation
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user