mirror of
https://github.com/kennethreitz-archive/gitx.git
synced 2026-06-05 23:40:18 +00:00
8d729dae4c
We used to read in a completely new array when refreshing the index. The problem with this is that the selection changes when reading in the new array. We avoid this by changing the current array, rather than loading in a completely new one.
333 lines
9.4 KiB
Objective-C
333 lines
9.4 KiB
Objective-C
//
|
|
// PBGitCommitController.m
|
|
// GitX
|
|
//
|
|
// Created by Pieter de Bie on 19-09-08.
|
|
// Copyright 2008 __MyCompanyName__. All rights reserved.
|
|
//
|
|
|
|
#import "PBGitCommitController.h"
|
|
#import "NSFileHandleExt.h"
|
|
#import "PBChangedFile.h"
|
|
|
|
@implementation PBGitCommitController
|
|
|
|
@synthesize files, status, busy, amend;
|
|
|
|
- (void)awakeFromNib
|
|
{
|
|
self.files = [NSMutableArray array];
|
|
[super awakeFromNib];
|
|
[self refresh:self];
|
|
|
|
[commitMessageView setTypingAttributes:[NSDictionary dictionaryWithObject:[NSFont fontWithName:@"Monaco" size:12.0] forKey:NSFontAttributeName]];
|
|
|
|
[unstagedFilesController setFilterPredicate:[NSPredicate predicateWithFormat:@"hasUnstagedChanges == 1"]];
|
|
[cachedFilesController setFilterPredicate:[NSPredicate predicateWithFormat:@"hasCachedChanges == 1"]];
|
|
|
|
[unstagedFilesController setSortDescriptors:[NSArray arrayWithObjects:
|
|
[[NSSortDescriptor alloc] initWithKey:@"status" ascending:false],
|
|
[[NSSortDescriptor alloc] initWithKey:@"path" ascending:true], nil]];
|
|
[cachedFilesController setSortDescriptors:[NSArray arrayWithObject:
|
|
[[NSSortDescriptor alloc] initWithKey:@"path" ascending:true]]];
|
|
}
|
|
- (void) removeView
|
|
{
|
|
[webController closeView];
|
|
[super finalize];
|
|
}
|
|
|
|
- (void) setAmend:(BOOL)newAmend
|
|
{
|
|
if (newAmend == amend)
|
|
return;
|
|
amend = newAmend;
|
|
|
|
if (amend && [[commitMessageView string] length] <= 3)
|
|
commitMessageView.string = [repository outputForCommand:@"log -1 --pretty=format:%s%n%n%b HEAD"];
|
|
|
|
[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
|
|
{
|
|
for (PBChangedFile *file in files)
|
|
file.shouldBeDeleted = YES;
|
|
|
|
self.status = @"Refreshing index…";
|
|
self.busy++;
|
|
|
|
[repository outputInWorkdirForArguments:[NSArray arrayWithObjects:@"update-index", @"-q", @"--unmerged", @"--ignore-missing", @"--refresh", nil]];
|
|
self.busy--;
|
|
|
|
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
|
[nc removeObserver:self];
|
|
|
|
// Other files
|
|
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];
|
|
|
|
// Cached 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];
|
|
}
|
|
|
|
- (void) updateView
|
|
{
|
|
[self refresh:nil];
|
|
}
|
|
|
|
- (void) doneProcessingIndex
|
|
{
|
|
[self willChangeValueForKey:@"files"];
|
|
if (!--self.busy) {
|
|
self.status = @"Ready";
|
|
NSArray *filesToBeDeleted = [files filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"shouldBeDeleted == 1"]];
|
|
for (PBChangedFile *file in filesToBeDeleted) {
|
|
NSLog(@"Deleting file: %@", [file path]);
|
|
[files removeObject:file];
|
|
}
|
|
}
|
|
[self didChangeValueForKey:@"files"];
|
|
}
|
|
|
|
- (void) readOtherFiles:(NSNotification *)notification;
|
|
{
|
|
NSArray *lines = [self linesFromNotification:notification];
|
|
for (NSString *line in lines) {
|
|
if ([line length] == 0)
|
|
continue;
|
|
|
|
BOOL added = NO;
|
|
// Check if file is already in our index
|
|
for (PBChangedFile *file in files) {
|
|
if ([[file path] isEqualToString:line]) {
|
|
file.shouldBeDeleted = NO;
|
|
added = YES;
|
|
file.status = NEW;
|
|
file.hasCachedChanges = NO;
|
|
file.hasUnstagedChanges = YES;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (added)
|
|
continue;
|
|
|
|
// File does not exist yet, so add it
|
|
PBChangedFile *file =[[PBChangedFile alloc] initWithPath:line];
|
|
file.status = NEW;
|
|
file.hasCachedChanges = NO;
|
|
file.hasUnstagedChanges = YES;
|
|
[files addObject: file];
|
|
}
|
|
|
|
[self doneProcessingIndex];
|
|
}
|
|
|
|
- (void) addFilesFromLines:(NSArray *)lines cached:(BOOL) cached
|
|
{
|
|
NSArray *fileStatus;
|
|
int even = 0;
|
|
for (NSString *line in lines) {
|
|
if (!even) {
|
|
even = 1;
|
|
fileStatus = [line componentsSeparatedByString:@" "];
|
|
continue;
|
|
}
|
|
even = 0;
|
|
|
|
NSString *mode = [[fileStatus objectAtIndex:0] substringFromIndex:1];
|
|
NSString *sha = [fileStatus objectAtIndex:2];
|
|
|
|
BOOL isNew = YES;
|
|
// If the file is already added, we shouldn't add it again
|
|
// but rather update it to incorporate our changes
|
|
for (PBChangedFile *file in files) {
|
|
if ([file.path isEqualToString:line]) {
|
|
file.shouldBeDeleted = NO;
|
|
if (cached) {
|
|
file.commitBlobSHA = sha;
|
|
file.commitBlobMode = mode;
|
|
file.hasCachedChanges = YES;
|
|
}
|
|
else
|
|
file.hasUnstagedChanges = YES;
|
|
|
|
isNew = NO;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!isNew)
|
|
continue;
|
|
|
|
PBChangedFile *file = [[PBChangedFile alloc] initWithPath:line];
|
|
if ([[fileStatus objectAtIndex:4] isEqualToString:@"D"])
|
|
file.status = DELETED;
|
|
else if([[fileStatus objectAtIndex:0] isEqualToString:@":000000"])
|
|
file.status = NEW;
|
|
else
|
|
file.status = MODIFIED;
|
|
|
|
file.commitBlobSHA = sha;
|
|
file.commitBlobMode = mode;
|
|
|
|
file.hasCachedChanges = cached;
|
|
file.hasUnstagedChanges = !cached;
|
|
|
|
[files addObject: file];
|
|
}
|
|
|
|
}
|
|
|
|
- (void) readUnstagedFiles:(NSNotification *)notification
|
|
{
|
|
NSArray *lines = [self linesFromNotification:notification];
|
|
[self addFilesFromLines:lines cached:NO];
|
|
[self doneProcessingIndex];
|
|
}
|
|
|
|
- (void) readCachedFiles:(NSNotification *)notification
|
|
{
|
|
NSArray *lines = [self linesFromNotification:notification];
|
|
[self addFilesFromLines:lines cached:YES];
|
|
[self doneProcessingIndex];
|
|
}
|
|
|
|
- (void) commitFailedBecause:(NSString *)reason
|
|
{
|
|
self.busy--;
|
|
self.status = [@"Commit failed: " stringByAppendingString:reason];
|
|
[[NSAlert alertWithMessageText:@"Commit failed"
|
|
defaultButton:nil
|
|
alternateButton:nil
|
|
otherButton:nil
|
|
informativeTextWithFormat:reason] runModal];
|
|
return;
|
|
}
|
|
|
|
- (IBAction) commit:(id) sender
|
|
{
|
|
if ([[cachedFilesController arrangedObjects] count] == 0) {
|
|
[[NSAlert alertWithMessageText:@"No changes to commit"
|
|
defaultButton:nil
|
|
alternateButton:nil
|
|
otherButton:nil
|
|
informativeTextWithFormat:@"You must first stage some changes before committing"] runModal];
|
|
return;
|
|
}
|
|
|
|
NSString *commitMessage = [commitMessageView string];
|
|
if ([commitMessage length] < 3) {
|
|
[[NSAlert alertWithMessageText:@"Commitmessage missing"
|
|
defaultButton:nil
|
|
alternateButton:nil
|
|
otherButton:nil
|
|
informativeTextWithFormat:@"Please enter a commit message before committing"] runModal];
|
|
return;
|
|
}
|
|
|
|
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++;
|
|
self.status = @"Creating tree..";
|
|
NSString *tree = [repository outputForCommand:@"write-tree"];
|
|
if ([tree length] != 40)
|
|
return [self commitFailedBecause:@"Could not create a tree"];
|
|
|
|
int ret;
|
|
|
|
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
|
|
retValue: &ret];
|
|
|
|
if (ret || [commit length] != 40)
|
|
return [self commitFailedBecause:@"Could not create a commit object"];
|
|
|
|
[repository outputForArguments:[NSArray arrayWithObjects:@"update-ref", @"-m", commitSubject, @"HEAD", commit, nil]
|
|
retValue: &ret];
|
|
if (ret)
|
|
return [self commitFailedBecause:@"Could not update HEAD"];
|
|
|
|
[webController setStateMessage:[NSString stringWithFormat:@"Succesfully created commit %@", commit]];
|
|
|
|
repository.hasChanged = YES;
|
|
self.busy--;
|
|
[commitMessageView setString:@""];
|
|
amend = NO;
|
|
[self refresh:self];
|
|
self.amend = NO;
|
|
}
|
|
|
|
- (void) stageHunk:(NSString *)hunk reverse:(BOOL)reverse
|
|
{
|
|
NSMutableArray *array = [NSMutableArray arrayWithObjects:@"apply", @"--cached", nil];
|
|
if (reverse)
|
|
[array addObject:@"--reverse"];
|
|
|
|
int ret;
|
|
NSString *error = [repository outputForArguments:array
|
|
inputString:hunk
|
|
retValue: &ret];
|
|
if (ret)
|
|
NSLog(@"Error: %@", error);
|
|
[self refresh:self]; // TODO: We should do this smarter by checking if the file diff is empty, which is faster.
|
|
}
|
|
@end
|