Files
gitx/PBRefController.m
T
2009-11-08 10:06:37 -07:00

578 lines
19 KiB
Objective-C

//
// PBLabelController.m
// GitX
//
// Created by Pieter de Bie on 21-10-08.
// Copyright 2008 Pieter de Bie. All rights reserved.
//
#import "PBRefController.h"
#import "PBGitRevisionCell.h"
#import "PBRefMenuItem.h"
@implementation PBRefController
- (void)awakeFromNib
{
[commitList registerForDraggedTypes:[NSArray arrayWithObject:@"PBGitRef"]];
[historyController addObserver:self forKeyPath:@"repository.branches" options:0 context:@"branchChange"];
[historyController addObserver:self forKeyPath:@"repository.currentBranch" options:0 context:@"currentBranchChange"];
[self updateBranchMenu];
[self selectCurrentBranch];
}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([(NSString *)context isEqualToString: @"branchChange"]) {
[self updateBranchMenu];
}
else if ([(NSString *)context isEqualToString:@"currentBranchChange"]) {
[self selectCurrentBranch];
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void) removeRefSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
if (returnCode == NSAlertDefaultReturn) {
int ret = 1;
PBRefMenuItem *refMenuItem = contextInfo;
[historyController.repository outputForArguments:[NSArray arrayWithObjects:@"update-ref", @"-d", [[refMenuItem ref] ref], nil] retValue: &ret];
if (ret) {
NSLog(@"Removing ref failed!");
return;
}
[historyController.repository removeBranch:[[PBGitRevSpecifier alloc] initWithRef:[refMenuItem ref]]];
[[refMenuItem commit] removeRef:[refMenuItem ref]];
[commitController rearrangeObjects];
}
}
- (void) removeRef:(PBRefMenuItem *)sender
{
NSString *ref_desc = [NSString stringWithFormat:@"%@ %@", [[sender ref] type], [[sender ref] shortName]];
NSString *question = [NSString stringWithFormat:@"Are you sure you want to remove the %@?", ref_desc];
NSBeginAlertSheet([NSString stringWithFormat:@"Delete %@?", ref_desc], @"Delete", @"Cancel", nil, [[historyController view] window], self, @selector(removeRefSheetDidEnd:returnCode:contextInfo:), NULL, sender, question);
}
- (void) checkoutRef:(PBRefMenuItem *)sender
{
int ret = 1;
[historyController.repository outputInWorkdirForArguments:[NSArray arrayWithObjects:@"checkout", [[sender ref] shortName], nil] retValue: &ret];
if (ret) {
NSString *info = [NSString stringWithFormat:@"There was an error checking out the branch. Perhaps your working directory is not clean?"];
[[historyController.repository windowController] showMessageSheet:@"Checking out branch failed" infoText:info];
return;
}
[historyController.repository reloadRefs];
[historyController.repository readCurrentBranch];
[commitController rearrangeObjects];
}
- (void) pushRef:(PBRefMenuItem *)sender
{
[self pushImpl:[[sender ref] shortName]];
}
- (void) pullRef:(PBRefMenuItem *)sender
{
[self pullImpl:[[sender ref] shortName]];
}
- (void) rebaseRef:(PBRefMenuItem *)sender
{
[self rebaseImpl:[[sender ref] shortName]];
}
- (void) tagInfo:(PBRefMenuItem *)sender
{
NSString *message = [NSString stringWithFormat:@"Info for tag: %@", [[sender ref] shortName]];
int ret = 1;
NSString *info = [historyController.repository outputInWorkdirForArguments:[NSArray arrayWithObjects:@"tag", @"-n50", @"-l", [[sender ref] shortName], nil] retValue: &ret];
if (!ret) {
[[historyController.repository windowController] showMessageSheet:message infoText:info];
}
return;
}
- (NSArray *) menuItemsForRef:(PBGitRef *)ref commit:(PBGitCommit *)commit
{
return [PBRefMenuItem defaultMenuItemsForRef:ref commit:commit target:self];
}
- (BOOL) pushImpl:(NSString *)refName
{
int ret = 1;
BOOL success = NO;
NSString *remote = [[historyController.repository config] valueForKeyPath:[NSString stringWithFormat:@"branch.%@.remote", refName]];
NSString *rval = [historyController.repository outputInWorkdirForArguments:[NSArray arrayWithObjects:@"push", remote, refName, nil] retValue: &ret];
if (!remote) {
[self showMessageSheet:@"Push to Remote" message:PBMissingRemoteErrorMessage];
return success;
}
if (ret) {
NSString *info = [NSString stringWithFormat:@"There was an error pushing the branch to the remote repository.\n\n%d\n%@", ret, rval];
[[historyController.repository windowController] showMessageSheet:@"Pushing branch failed" infoText:info];
return success;
}
[historyController.repository reloadRefs];
[commitController rearrangeObjects];
success = YES;
return success;
}
- (BOOL) pullImpl:(NSString *)refName
{
int ret = 1;
BOOL success = NO;
NSString *remote = [[historyController.repository config] valueForKeyPath:[NSString stringWithFormat:@"branch.%@.remote", refName]];
if (!remote) {
[self showMessageSheet:@"Pull from Remote" message:PBMissingRemoteErrorMessage];
return success;
}
NSString *rval = [historyController.repository outputInWorkdirForArguments:[NSArray arrayWithObjects:@"pull", remote, refName, nil] retValue: &ret];
if (ret) {
NSString *info = [NSString stringWithFormat:@"There was an error pulling from the remote repository.\n\n%d\n%@", ret, rval];
[[historyController.repository windowController] showMessageSheet:@"Pulling from remote failed" infoText:info];
return success;
}
[historyController.repository reloadRefs];
[commitController rearrangeObjects];
success = YES;
return success;
}
- (BOOL) rebaseImpl:(NSString *)refName
{
int ret = 1;
BOOL success = NO;
NSString *remote = [[[historyController repository] config] valueForKeyPath:[NSString stringWithFormat:@"branch.%@.remote", refName]];
if (!remote) {
[self showMessageSheet:@"Pull Rebase from Remote" message:PBMissingRemoteErrorMessage];
return success;
}
NSString *rval = [[historyController repository] outputInWorkdirForArguments:[NSArray arrayWithObjects:@"pull", @"--rebase", remote, refName, nil] retValue: &ret];
if (ret) {
NSString *info = [NSString stringWithFormat:@"There was an error rebasing from the remote repository.\n\n%d\n%@", ret, rval];
[[[historyController repository] windowController] showMessageSheet:@"Rebasing from remote failed" infoText:info];
return success;
}
[[historyController repository] reloadRefs];
[commitController rearrangeObjects];
success = YES;
return success;
}
- (BOOL) fetchImpl:(NSString *)refName
{
int ret = 1;
BOOL success = NO;
NSString *remote = nil;
if(refName != nil) remote = [[historyController.repository config] valueForKeyPath:[NSString stringWithFormat:@"branch.%@.remote", refName]];
NSString *rval = [historyController.repository outputInWorkdirForArguments:[NSArray arrayWithObjects:@"fetch", remote, refName, nil] retValue: &ret];
if (ret) {
NSString *info = [NSString stringWithFormat:@"There was an error fetching from the remote repository.\n\n%d\n%@", ret, rval];
[[historyController.repository windowController] showMessageSheet:@"Fetching from remote failed" infoText:info];
return success;
}
[historyController.repository reloadRefs];
[commitController rearrangeObjects];
success = YES;
return success;
}
- (BOOL) addRemoteImplWithName:(NSString *)remoteName forURL:(NSString *)remoteURL
{
int ret = 1;
BOOL success = NO;
if (!remoteName || !remoteURL) {
return success;
}
NSString *rval = [historyController.repository outputInWorkdirForArguments:[NSArray arrayWithObjects:@"remote", @"add", @"-f", remoteName, remoteURL, nil] retValue: &ret];
if (ret) {
NSString *info = [NSString stringWithFormat:@"There was an error adding the remote.\n\n%d\n%@", ret, rval];
[[historyController.repository windowController] showMessageSheet:@"Adding Remote failed" infoText:info];
return success;
}
[historyController.repository reloadRefs];
[commitController rearrangeObjects];
success = YES;
return success;
}
# pragma mark Tableview delegate methods
- (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard
{
NSPoint location = [tv convertPointFromBase:[(PBCommitList *)tv mouseDownPoint]];
int row = [tv rowAtPoint:location];
int column = [tv columnAtPoint:location];
if (column != 0)
return NO;
PBGitRevisionCell *cell = (PBGitRevisionCell *)[tv preparedCellAtColumn:column row:row];
int index = [cell indexAtX:location.x];
if (index == -1)
return NO;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:[NSArray arrayWithObjects:[NSNumber numberWithInt:row], [NSNumber numberWithInt:index], NULL]];
[pboard declareTypes:[NSArray arrayWithObject:@"PBGitRef"] owner:self];
[pboard setData:data forType:@"PBGitRef"];
return YES;
}
- (NSDragOperation)tableView:(NSTableView*)tv
validateDrop:(id <NSDraggingInfo>)info
proposedRow:(NSInteger)row
proposedDropOperation:(NSTableViewDropOperation)operation
{
if (operation == NSTableViewDropAbove)
return NSDragOperationNone;
NSPasteboard *pboard = [info draggingPasteboard];
if ([pboard dataForType:@"PBGitRef"])
return NSDragOperationMove;
return NSDragOperationNone;
}
- (BOOL)tableView:(NSTableView *)aTableView
acceptDrop:(id <NSDraggingInfo>)info
row:(NSInteger)row
dropOperation:(NSTableViewDropOperation)operation
{
if (operation != NSTableViewDropOn)
return NO;
NSPasteboard *pboard = [info draggingPasteboard];
NSData *data = [pboard dataForType:@"PBGitRef"];
if (!data)
return NO;
NSArray *numbers = [NSKeyedUnarchiver unarchiveObjectWithData:data];
int oldRow = [[numbers objectAtIndex:0] intValue];
int oldRefIndex = [[numbers objectAtIndex:1] intValue];
PBGitCommit *oldCommit = [[commitController arrangedObjects] objectAtIndex: oldRow];
PBGitRef *ref = [[oldCommit refs] objectAtIndex:oldRefIndex];
PBGitCommit *dropCommit = [[commitController arrangedObjects] objectAtIndex:row];
int a = [[NSAlert alertWithMessageText:@"Change branch"
defaultButton:@"Change"
alternateButton:@"Cancel"
otherButton:nil
informativeTextWithFormat:@"Do you want to change branch\n\n\t'%@'\n\n to point to commit\n\n\t'%@'", [ref shortName], [dropCommit subject]] runModal];
if (a != NSAlertDefaultReturn)
return NO;
int retValue = 1;
[historyController.repository outputForArguments:[NSArray arrayWithObjects:@"update-ref", @"-mUpdate from GitX", [ref ref], [dropCommit realSha], NULL] retValue:&retValue];
if (retValue)
return NO;
[dropCommit addRef:ref];
[oldCommit removeRef:ref];
[commitController rearrangeObjects];
[aTableView needsToDrawRect:[aTableView rectOfRow:oldRow]];
return YES;
}
# pragma mark Add ref methods
-(void)addRef:(id)sender
{
[errorMessage setStringValue:@""];
[NSApp beginSheet:newBranchSheet
modalForWindow:[[historyController view] window]
modalDelegate:NULL
didEndSelector:NULL
contextInfo:NULL];
}
- (void) showMessageSheet:(NSString *)title message:(NSString *)msg {
[[NSAlert alertWithMessageText:title
defaultButton:@"OK"
alternateButton:nil
otherButton:nil
informativeTextWithFormat:msg]
beginSheetModalForWindow:[[historyController view] window]
modalDelegate:self
didEndSelector:nil
contextInfo:nil];
return;
}
-(void)rebaseButton:(id)sender
{
NSString *refName = [[[[historyController repository] currentBranch] simpleRef] refForSpec];
if (refName) {
[self rebaseImpl:refName];
} else {
[self showMessageSheet:@"Pull Rebase from Remote" message:PBInvalidBranchErrorMessage];
}
// NSLog([NSString stringWithFormat:@"Rebase hit for %@!", refName]);
}
-(void)pushButton:(id)sender
{
NSString *refName = [[[[historyController repository] currentBranch] simpleRef] refForSpec];
if (refName) {
[self pushImpl:refName];
} else {
[self showMessageSheet:@"Push to Remote" message:PBInvalidBranchErrorMessage];
}
// NSLog([NSString stringWithFormat:@"Push hit for %@!", refName]);
}
- (void) pullButton:(id)sender
{
NSString * refName = [[[[historyController repository] currentBranch] simpleRef] refForSpec];
if (refName) {
[self pullImpl:refName];
} else {
[sender setEnabled:YES];
[self showMessageSheet:@"Pull from Remote" message:PBInvalidBranchErrorMessage];
}
// NSLog([NSString stringWithFormat:@"Pull hit for %@!", refName]);
}
-(void)fetchButton:(id)sender
{
NSString *refName = [[[[historyController repository] currentBranch] simpleRef] refForSpec];
if (refName) {
[self fetchImpl:refName];
} else {
[sender setEnabled:YES];
[self showMessageSheet:@"Fetch from Remote" message:PBInvalidBranchErrorMessage];
}
// NSLog([NSString stringWithFormat:@"Fetch hit for %@!", refName]);
}
-(void)saveSheet:(id) sender
{
NSString *branchName = [@"refs/heads/" stringByAppendingString:[newBranchName stringValue]];
if ([[commitController selectedObjects] count] == 0)
return;
PBGitCommit *commit = [[commitController selectedObjects] objectAtIndex:0];
int retValue = 1;
[historyController.repository outputForArguments:[NSArray arrayWithObjects:@"check-ref-format", branchName, nil] retValue:&retValue];
if (retValue != 0) {
[errorMessage setStringValue:@"Invalid name"];
return;
}
retValue = 1;
[historyController.repository outputForArguments:[NSArray arrayWithObjects:@"update-ref", @"-mCreate branch from GitX", branchName, [commit realSha], @"0000000000000000000000000000000000000000", NULL] retValue:&retValue];
if (retValue)
{
[errorMessage setStringValue:@"Branch exists"];
return;
}
[historyController.repository addBranch:[[PBGitRevSpecifier alloc] initWithRef:[PBGitRef refFromString:branchName]]];
[self closeSheet:sender];
[commit addRef:[PBGitRef refFromString:branchName]];
[commitController rearrangeObjects];
}
-(void)closeSheet:(id) sender
{
[NSApp endSheet:newBranchSheet];
[newBranchName setStringValue:@""];
[newBranchSheet orderOut:self];
}
- (void) addRemoteButton:(id)sender
{
[addRemoteErrorMessage setStringValue:@""];
[addRemoteName setStringValue:@""];
[addRemoteName setTextColor:[NSColor blackColor]];
[addRemoteURL setStringValue:@""];
[NSApp beginSheet:addRemoteSheet
modalForWindow:[[historyController view] window]
modalDelegate:NULL
didEndSelector:NULL
contextInfo:NULL];
}
- (void) addRemoteSheet:(id)sender
{
NSString *remoteName = [addRemoteName stringValue];
NSString *remoteURL = [addRemoteURL stringValue];
NSLog(@"%s remoteName = %@ remoteURL = %@", _cmd, remoteName, remoteURL);
if ([remoteName isEqualToString:@""]) {
[addRemoteErrorMessage setStringValue:@"Remote name is required"];
return;
}
NSRange range = [remoteName rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]];
if (range.location != NSNotFound) {
[addRemoteErrorMessage setStringValue:@"Whitespace is not allowed"];
[addRemoteName setTextColor:[NSColor redColor]];
return;
}
[addRemoteName setTextColor:[NSColor blackColor]];
if ([remoteURL isEqualToString:@""]) {
[addRemoteErrorMessage setStringValue:@"Remote URL is required"];
return;
}
[addRemoteURL setTextColor:[NSColor blackColor]];
[self closeAddRemoteSheet:sender];
[self addRemoteImplWithName:remoteName forURL:remoteURL];
}
- (void) closeAddRemoteSheet:(id)sender
{
[NSApp endSheet:addRemoteSheet];
[addRemoteErrorMessage setStringValue:@""];
[addRemoteName setStringValue:@""];
[addRemoteName setTextColor:[NSColor blackColor]];
[addRemoteURL setStringValue:@""];
[addRemoteSheet orderOut:self];
}
# pragma mark Branches menu
- (void) updateBranchMenu
{
if (!branchPopUp)
return;
NSMutableArray *localBranches = [NSMutableArray array];
NSMutableArray *remoteBranches = [NSMutableArray array];
NSMutableArray *tags = [NSMutableArray array];
NSMutableArray *other = [NSMutableArray array];
NSMenu *menu = [[NSMenu alloc] initWithTitle:@"Branch menu"];
for (PBGitRevSpecifier *rev in historyController.repository.branches)
{
if (![rev isSimpleRef])
{
[other addObject:rev];
continue;
}
NSString *ref = [rev simpleRef];
if ([ref hasPrefix:@"refs/heads"])
[localBranches addObject:rev];
else if ([ref hasPrefix:@"refs/tags"])
[tags addObject:rev];
else if ([ref hasPrefix:@"refs/remote"])
[remoteBranches addObject:rev];
}
for (PBGitRevSpecifier *rev in localBranches)
{
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[rev description] action:@selector(changeBranch:) keyEquivalent:@""];
[item setRepresentedObject:rev];
[item setTarget:self];
[menu addItem:item];
}
[menu addItem:[NSMenuItem separatorItem]];
// Remotes
NSMenu *remoteMenu = [[NSMenu alloc] initWithTitle:@"Remotes"];
NSMenu *currentMenu = NULL;
for (PBGitRevSpecifier *rev in remoteBranches)
{
NSString *ref = [rev simpleRef];
NSArray *components = [ref componentsSeparatedByString:@"/"];
NSString *remoteName = [components objectAtIndex:2];
NSString *branchName = [[components subarrayWithRange:NSMakeRange(3, [components count] - 3)] componentsJoinedByString:@"/"];
if (![[currentMenu title] isEqualToString:remoteName])
{
currentMenu = [[NSMenu alloc] initWithTitle:remoteName];
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:remoteName action:NULL keyEquivalent:@""];
[item setSubmenu:currentMenu];
[remoteMenu addItem:item];
}
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:branchName action:@selector(changeBranch:) keyEquivalent:@""];
[item setTarget:self];
[item setRepresentedObject:rev];
[currentMenu addItem:item];
}
NSMenuItem *remoteItem = [[NSMenuItem alloc] initWithTitle:@"Remotes" action:NULL keyEquivalent:@""];
[remoteItem setSubmenu:remoteMenu];
[menu addItem:remoteItem];
// Tags
NSMenu *tagMenu = [[NSMenu alloc] initWithTitle:@"Tags"];
for (PBGitRevSpecifier *rev in tags)
{
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[rev description] action:@selector(changeBranch:) keyEquivalent:@""];
[item setTarget:self];
[item setRepresentedObject:rev];
[tagMenu addItem:item];
}
NSMenuItem *tagItem = [[NSMenuItem alloc] initWithTitle:@"Tags" action:NULL keyEquivalent:@""];
[tagItem setSubmenu:tagMenu];
[menu addItem:tagItem];
// Others
[menu addItem:[NSMenuItem separatorItem]];
for (PBGitRevSpecifier *rev in other)
{
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[rev description] action:@selector(changeBranch:) keyEquivalent:@""];
[item setRepresentedObject:rev];
[item setTarget:self];
[menu addItem:item];
}
[[branchPopUp cell] setMenu: menu];
}
- (void) changeBranch:(NSMenuItem *)sender
{
PBGitRevSpecifier *rev = [sender representedObject];
historyController.repository.currentBranch = rev;
}
- (void) selectCurrentBranch
{
PBGitRevSpecifier *rev = historyController.repository.currentBranch;
if (rev)
[branchPopUp setTitle:[rev description]];
}
@end
@implementation NSString (PBRefSpecAdditions)
/* convenience method to get the last part of a simple refspec like refs/heads/master -> master*/
- (NSString *) refForSpec {
if ([self hasPrefix:@"refs/"]) {
NSArray * parts = [self componentsSeparatedByString:@"/"];
return [parts lastObject];
}
return self;
}
@end