From e60bb3226cd2d4f7ffb05a22fc3010d062fb695d Mon Sep 17 00:00:00 2001 From: Nathan Kinsinger Date: Thu, 19 Aug 2010 00:20:33 -0600 Subject: [PATCH] Refactor the gitx CLI to use apple events and the scripting bridge Sending the arguments with the openURL:... message allows the repository document to modify it's UI without the UI flashing between states as it opens. Covers all the existing functionality of the CLI, but modifies: - "--all" "--local" "--branch" change the branch filter - cleaned up the usage (help) text and added info on missing commands - looks up the full ref name of refs so the name of a branch or tag can be entered (the user can enter "master" instead of "refs/heads/master") Modified the History Controller to watch for and react to branch filter changes. The GitX.h file is generated by the 'sdp' tool in a run script build phase called 'Generate Scripting Bridge Header' based on the content of GitX.sdef. It is used by the Scripting Bridge so that other apps (in this case the gitx CLI) can call Applescript commands on GitX in objective-c. --- ApplicationController.h | 3 - ApplicationController.m | 13 +-- GitX.h | 69 +++++++++++ GitX.sdef | 166 ++++++++++++++++++++++++++ GitX.xcodeproj/project.pbxproj | 49 +++++++- GitXScriptingConstants.h | 12 ++ Info.plist | 4 + NSApplication+GitXScripting.h | 16 +++ NSApplication+GitXScripting.m | 25 ++++ PBGitHistoryController.m | 8 ++ PBGitRepository.h | 1 + PBGitRepository.m | 133 +++++++++++++++++++++ gitx.m | 208 ++++++++++++++++++++------------- 13 files changed, 610 insertions(+), 97 deletions(-) create mode 100644 GitX.h create mode 100644 GitX.sdef create mode 100644 GitXScriptingConstants.h create mode 100644 NSApplication+GitXScripting.h create mode 100644 NSApplication+GitXScripting.m diff --git a/ApplicationController.h b/ApplicationController.h index 4305662..f368344 100644 --- a/ApplicationController.h +++ b/ApplicationController.h @@ -9,7 +9,6 @@ #import #import "PBGitRepository.h" -@class PBCLIProxy; @class PBCloneRepositoryPanel; @interface ApplicationController : NSObject @@ -20,10 +19,8 @@ NSManagedObjectModel *managedObjectModel; NSManagedObjectContext *managedObjectContext; - PBCLIProxy *cliProxy; PBCloneRepositoryPanel *cloneRepositoryPanel; } -@property (retain) PBCLIProxy* cliProxy; - (NSPersistentStoreCoordinator *)persistentStoreCoordinator; - (NSManagedObjectModel *)managedObjectModel; diff --git a/ApplicationController.m b/ApplicationController.m index 2a3d2ae..c73b524 100644 --- a/ApplicationController.m +++ b/ApplicationController.m @@ -10,7 +10,6 @@ #import "PBGitRevisionCell.h" #import "PBGitWindowController.h" #import "PBRepositoryDocumentController.h" -#import "PBCLIProxy.h" #import "PBServicesController.h" #import "PBGitXProtocol.h" #import "PBPrefsWindowController.h" @@ -20,7 +19,6 @@ #import "Sparkle/SUUpdater.h" @implementation ApplicationController -@synthesize cliProxy; - (ApplicationController*)init { @@ -28,13 +26,12 @@ [NSApp activateIgnoringOtherApps:YES]; #endif - if(self = [super init]) { - if(![[NSBundle bundleWithPath:@"/System/Library/Frameworks/Quartz.framework/Frameworks/QuickLookUI.framework"] load]) - if(![[NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/QuickLookUI.framework"] load]) - NSLog(@"Could not load QuickLook"); + if(!(self = [super init])) + return nil; - self.cliProxy = [PBCLIProxy new]; - } + if(![[NSBundle bundleWithPath:@"/System/Library/Frameworks/Quartz.framework/Frameworks/QuickLookUI.framework"] load]) + if(![[NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/QuickLookUI.framework"] load]) + NSLog(@"Could not load QuickLook"); /* Value Transformers */ NSValueTransformer *transformer = [[PBNSURLPathUserDefaultsTransfomer alloc] init]; diff --git a/GitX.h b/GitX.h new file mode 100644 index 0000000..edae903 --- /dev/null +++ b/GitX.h @@ -0,0 +1,69 @@ +/* + * GitX.h + */ + +#import +#import + + +@class GitXApplication, GitXDocument, GitXWindow; + + + +/* + * Standard Suite + */ + +// The application's top-level scripting object. +@interface GitXApplication : SBApplication + +- (SBElementArray *) documents; +- (SBElementArray *) windows; + +@property (copy, readonly) NSString *name; // The name of the application. +@property (readonly) BOOL frontmost; // Is this the active application? +@property (copy, readonly) NSString *version; // The version number of the application. + +- (void) open:(NSArray *)x; // Open a document. +- (void) quit; // Quit the application. +- (BOOL) exists:(id)x; // Verify that an object exists. +- (void) showDiff:(NSString *)x; // Show the supplied diff output in a GitX window. + +@end + +// A document. +@interface GitXDocument : SBObject + +@property (copy, readonly) NSString *name; // Its name. +@property (copy, readonly) NSURL *file; // Its location on disk, if it has one. + +- (void) close; // Close a document. +- (void) delete; // Delete an object. +- (void) duplicateTo:(SBObject *)to withProperties:(NSDictionary *)withProperties; // Copy an object. +- (void) moveTo:(SBObject *)to; // Move an object to a new location. + +@end + +// A window. +@interface GitXWindow : SBObject + +@property (copy, readonly) NSString *name; // The title of the window. +- (NSInteger) id; // The unique identifier of the window. +@property NSInteger index; // The index of the window, ordered front to back. +@property NSRect bounds; // The bounding rectangle of the window. +@property (readonly) BOOL closeable; // Does the window have a close button? +@property (readonly) BOOL miniaturizable; // Does the window have a minimize button? +@property BOOL miniaturized; // Is the window minimized right now? +@property (readonly) BOOL resizable; // Can the window be resized? +@property BOOL visible; // Is the window visible right now? +@property (readonly) BOOL zoomable; // Does the window have a zoom button? +@property BOOL zoomed; // Is the window zoomed right now? +@property (copy, readonly) GitXDocument *document; // The document whose contents are displayed in the window. + +- (void) close; // Close a document. +- (void) delete; // Delete an object. +- (void) duplicateTo:(SBObject *)to withProperties:(NSDictionary *)withProperties; // Copy an object. +- (void) moveTo:(SBObject *)to; // Move an object to a new location. + +@end + diff --git a/GitX.sdef b/GitX.sdef new file mode 100644 index 0000000..a0a0642 --- /dev/null +++ b/GitX.sdef @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GitX.xcodeproj/project.pbxproj b/GitX.xcodeproj/project.pbxproj index 469427b..5cb9bd1 100644 --- a/GitX.xcodeproj/project.pbxproj +++ b/GitX.xcodeproj/project.pbxproj @@ -41,7 +41,6 @@ 911112370E5A097800BF76B4 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 911112360E5A097800BF76B4 /* Security.framework */; }; 913D5E4D0E55644E00CECEA2 /* gitx.m in Sources */ = {isa = PBXBuildFile; fileRef = 913D5E440E55640C00CECEA2 /* gitx.m */; }; 913D5E500E55645900CECEA2 /* gitx in Resources */ = {isa = PBXBuildFile; fileRef = 913D5E490E55644600CECEA2 /* gitx */; }; - 913D5E5F0E556A9300CECEA2 /* PBCLIProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 913D5E5E0E556A9300CECEA2 /* PBCLIProxy.m */; }; 91B103CC0E898EC300C84364 /* PBIconAndTextCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 91B103CB0E898EC300C84364 /* PBIconAndTextCell.m */; }; 93CB42C20EAB7B2200530609 /* PBGitDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 93CB42C10EAB7B2200530609 /* PBGitDefaults.m */; }; 93F7857F0EA3ABF100C1F443 /* PBCommitMessageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93F7857E0EA3ABF100C1F443 /* PBCommitMessageView.m */; }; @@ -70,6 +69,7 @@ D858108511274D28007F254B /* Tag.png in Resources */ = {isa = PBXBuildFile; fileRef = D858108211274D28007F254B /* Tag.png */; }; D85B939310E3D8B4007F3C28 /* PBCreateBranchSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = D85B939210E3D8B4007F3C28 /* PBCreateBranchSheet.xib */; }; D889EB3110E6BCBB00F08413 /* PBCreateTagSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = D889EB3010E6BCBB00F08413 /* PBCreateTagSheet.xib */; }; + D89E9B141218BA260097A90B /* ScriptingBridge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D89E9AB21218A9DA0097A90B /* ScriptingBridge.framework */; }; D8A4BB6F11337D5C00E92D51 /* PBGitGradientBarView.m in Sources */ = {isa = PBXBuildFile; fileRef = D8A4BB6E11337D5C00E92D51 /* PBGitGradientBarView.m */; }; D8A4BD071134AD2900E92D51 /* CherryPickTemplate.png in Resources */ = {isa = PBXBuildFile; fileRef = D8A4BD041134AD2900E92D51 /* CherryPickTemplate.png */; }; D8A4BD081134AD2900E92D51 /* MergeTemplate.png in Resources */ = {isa = PBXBuildFile; fileRef = D8A4BD051134AD2900E92D51 /* MergeTemplate.png */; }; @@ -78,6 +78,8 @@ D8E3B2B810DC9FB2001096A3 /* ScriptingBridge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8E3B2B710DC9FB2001096A3 /* ScriptingBridge.framework */; }; D8E3B34D10DCA958001096A3 /* PBCreateTagSheet.m in Sources */ = {isa = PBXBuildFile; fileRef = D8E3B34C10DCA958001096A3 /* PBCreateTagSheet.m */; }; D8EB616A122F643E00FCCAF4 /* GitXRelativeDateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = D8EB6169122F643E00FCCAF4 /* GitXRelativeDateFormatter.m */; }; + D8F01C4B12182F19007F729F /* GitX.sdef in Resources */ = {isa = PBXBuildFile; fileRef = D8F01C4A12182F19007F729F /* GitX.sdef */; }; + D8F01D531218A164007F729F /* NSApplication+GitXScripting.m in Sources */ = {isa = PBXBuildFile; fileRef = D8F01D521218A164007F729F /* NSApplication+GitXScripting.m */; }; D8FBCF19115FA20C0098676A /* PBGitSHA.m in Sources */ = {isa = PBXBuildFile; fileRef = D8FBCF18115FA20C0098676A /* PBGitSHA.m */; }; D8FDD9F711432A12005647F6 /* PBCloneRepositoryPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = D8FDD9F511432A12005647F6 /* PBCloneRepositoryPanel.xib */; }; D8FDDA6A114335E8005647F6 /* PBGitSVBranchItem.m in Sources */ = {isa = PBXBuildFile; fileRef = D8FDDA5D114335E8005647F6 /* PBGitSVBranchItem.m */; }; @@ -262,8 +264,6 @@ 911112360E5A097800BF76B4 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = /System/Library/Frameworks/Security.framework; sourceTree = ""; }; 913D5E440E55640C00CECEA2 /* gitx.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = gitx.m; sourceTree = ""; }; 913D5E490E55644600CECEA2 /* gitx */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = gitx; sourceTree = BUILT_PRODUCTS_DIR; }; - 913D5E5D0E556A9300CECEA2 /* PBCLIProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBCLIProxy.h; sourceTree = ""; }; - 913D5E5E0E556A9300CECEA2 /* PBCLIProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBCLIProxy.m; sourceTree = ""; }; 91B103CA0E898EC300C84364 /* PBIconAndTextCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBIconAndTextCell.h; sourceTree = ""; }; 91B103CB0E898EC300C84364 /* PBIconAndTextCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBIconAndTextCell.m; sourceTree = ""; }; 93CB42C00EAB7B2200530609 /* PBGitDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitDefaults.h; sourceTree = ""; }; @@ -307,6 +307,8 @@ D858108111274D28007F254B /* RemoteBranch.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = RemoteBranch.png; path = Images/RemoteBranch.png; sourceTree = ""; }; D858108211274D28007F254B /* Tag.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Tag.png; path = Images/Tag.png; sourceTree = ""; }; D85B93F610E51279007F3C28 /* PBGitRefish.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitRefish.h; sourceTree = ""; }; + D89E9AB21218A9DA0097A90B /* ScriptingBridge.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScriptingBridge.framework; path = System/Library/Frameworks/ScriptingBridge.framework; sourceTree = SDKROOT; }; + D89E9B4F1218C2750097A90B /* GitXScriptingConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GitXScriptingConstants.h; sourceTree = ""; }; D8A4BB6D11337D5C00E92D51 /* PBGitGradientBarView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitGradientBarView.h; sourceTree = ""; }; D8A4BB6E11337D5C00E92D51 /* PBGitGradientBarView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBGitGradientBarView.m; sourceTree = ""; }; D8A4BD041134AD2900E92D51 /* CherryPickTemplate.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = CherryPickTemplate.png; path = Images/CherryPickTemplate.png; sourceTree = ""; }; @@ -321,6 +323,10 @@ D8E3B38110DD4E2C001096A3 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/PBCreateTagSheet.xib; sourceTree = ""; }; D8EB6168122F643E00FCCAF4 /* GitXRelativeDateFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GitXRelativeDateFormatter.h; sourceTree = ""; }; D8EB6169122F643E00FCCAF4 /* GitXRelativeDateFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GitXRelativeDateFormatter.m; sourceTree = ""; }; + D8F01C4A12182F19007F729F /* GitX.sdef */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.sdef; path = GitX.sdef; sourceTree = ""; }; + D8F01D511218A164007F729F /* NSApplication+GitXScripting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSApplication+GitXScripting.h"; sourceTree = ""; }; + D8F01D521218A164007F729F /* NSApplication+GitXScripting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSApplication+GitXScripting.m"; sourceTree = ""; }; + D8F01D841218A406007F729F /* GitX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GitX.h; sourceTree = ""; }; D8FBCF17115FA20C0098676A /* PBGitSHA.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitSHA.h; sourceTree = ""; }; D8FBCF18115FA20C0098676A /* PBGitSHA.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBGitSHA.m; sourceTree = ""; }; D8FDD9F611432A12005647F6 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/PBCloneRepositoryPanel.xib; sourceTree = ""; }; @@ -478,6 +484,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D89E9B141218BA260097A90B /* ScriptingBridge.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -548,6 +555,7 @@ children = ( F5886A080ED5D26B0066E74C /* SpeedTest */, 913D5E420E5563FD00CECEA2 /* cli */, + D89E9B4C1218C22A0097A90B /* GitXScripting */, F57CC43E0E05E472000472E2 /* Aux */, F57CC3850E05DDC1000472E2 /* Controllers */, F56174540E05887E001DCD79 /* Git */, @@ -561,6 +569,7 @@ 19C28FACFE9D520D11CA2CBB /* Products */, F5886A120ED5D33D0066E74C /* SpeedTest-Info.plist */, F567CC3A106E6B910059BB9D /* GitXTesting-Info.plist */, + D89E9AB21218A9DA0097A90B /* ScriptingBridge.framework */, ); name = GitTest; sourceTree = ""; @@ -604,6 +613,7 @@ D26DC6440E782C9000C777B2 /* gitx.icns */, 8D1107310486CEB800E47090 /* Info.plist */, 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, + D8F01C4A12182F19007F729F /* GitX.sdef */, F5B721C20E05CF7E00AF29DC /* MainMenu.xib */, 911111E00E58BD5A00BF76B4 /* RepositoryWindow.xib */, F5E424100EA3E4D60046E362 /* PBDiffWindow.xib */, @@ -660,8 +670,6 @@ 913D5E420E5563FD00CECEA2 /* cli */ = { isa = PBXGroup; children = ( - 913D5E5D0E556A9300CECEA2 /* PBCLIProxy.h */, - 913D5E5E0E556A9300CECEA2 /* PBCLIProxy.m */, 913D5E440E55640C00CECEA2 /* gitx.m */, ); name = cli; @@ -688,6 +696,17 @@ name = Sheets; sourceTree = ""; }; + D89E9B4C1218C22A0097A90B /* GitXScripting */ = { + isa = PBXGroup; + children = ( + D8F01D841218A406007F729F /* GitX.h */, + D89E9B4F1218C2750097A90B /* GitXScriptingConstants.h */, + D8F01D511218A164007F729F /* NSApplication+GitXScripting.h */, + D8F01D521218A164007F729F /* NSApplication+GitXScripting.m */, + ); + name = GitXScripting; + sourceTree = ""; + }; D8FDDA58114335B0005647F6 /* Source View Items */ = { isa = PBXGroup; children = ( @@ -997,6 +1016,7 @@ 8D11072E0486CEB800E47090 /* Frameworks */, F580E6BD0E73329C009E2D3F /* CopyFiles */, F5CF04A20EAE696C00D75C81 /* Copy HTML files */, + D81E15ED121CE83D00269E61 /* Scripting Bridge Header Script */, ); buildRules = ( ); @@ -1133,6 +1153,7 @@ D8A4BD091134AD2900E92D51 /* RebaseTemplate.png in Resources */, D828AEEC112F411100F09D11 /* CloneRepositoryTemplate.png in Resources */, D8022FE811E124A0003C21F6 /* PBGitXMessageSheet.xib in Resources */, + D8F01C4B12182F19007F729F /* GitX.sdef in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1146,6 +1167,22 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + D81E15ED121CE83D00269E61 /* Scripting Bridge Header Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/GitX.sdef", + ); + name = "Scripting Bridge Header Script "; + outputPaths = ( + "$(SRCROOT)/GitX.h", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Build the scripting bridge header GitX.h if the GitX.sdef file changes\nsdef $TARGET_BUILD_DIR/GitX.app | sdp -fh --basename GitX"; + }; F56439F60F792B2100A579C2 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1207,7 +1244,6 @@ F5FF4E180E0829C20006317A /* PBGitRevList.mm in Sources */, F5FF4E7A0E082E440006317A /* PBGitGrapher.mm in Sources */, 911111F80E594F3F00BF76B4 /* PBRepositoryDocumentController.m in Sources */, - 913D5E5F0E556A9300CECEA2 /* PBCLIProxy.m in Sources */, F56CC7320E65E0E5004307B4 /* PBGraphCellInfo.m in Sources */, F5C6F68D0E65FF9300478D97 /* PBGitLane.mm in Sources */, F5C007750E731B48007B84B2 /* PBGitRef.m in Sources */, @@ -1264,6 +1300,7 @@ D8FBCF19115FA20C0098676A /* PBGitSHA.m in Sources */, D8022FED11E124C8003C21F6 /* PBGitXMessageSheet.m in Sources */, D8EB616A122F643E00FCCAF4 /* GitXRelativeDateFormatter.m in Sources */, + D8F01D531218A164007F729F /* NSApplication+GitXScripting.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/GitXScriptingConstants.h b/GitXScriptingConstants.h new file mode 100644 index 0000000..cba0ce8 --- /dev/null +++ b/GitXScriptingConstants.h @@ -0,0 +1,12 @@ +// +// GitXScriptingConstants.h +// GitX +// +// Created by Nathan Kinsinger on 8/15/10. +// Copyright 2010 Nathan Kinsinger. All rights reserved. +// + +#define kGitXBundleIdentifier @"nl.frim.GitX" + + +#define kGitXAEKeyArgumentsList 'ARGS' \ No newline at end of file diff --git a/Info.plist b/Info.plist index b7a8de4..d7e5e5b 100644 --- a/Info.plist +++ b/Info.plist @@ -89,5 +89,9 @@ + NSAppleScriptEnabled + + OSAScriptingDefinition + GitX.sdef diff --git a/NSApplication+GitXScripting.h b/NSApplication+GitXScripting.h new file mode 100644 index 0000000..d5cab8c --- /dev/null +++ b/NSApplication+GitXScripting.h @@ -0,0 +1,16 @@ +// +// NSApplication+GitXScripting.h +// GitX +// +// Created by Nathan Kinsinger on 8/15/10. +// Copyright 2010 Nathan Kinsinger. All rights reserved. +// + +#import + + +@interface NSApplication (GitXScripting) + +- (void)showDiffScriptCommand:(NSScriptCommand *)command; + +@end diff --git a/NSApplication+GitXScripting.m b/NSApplication+GitXScripting.m new file mode 100644 index 0000000..7c8489c --- /dev/null +++ b/NSApplication+GitXScripting.m @@ -0,0 +1,25 @@ +// +// NSApplication+GitXScripting.m +// GitX +// +// Created by Nathan Kinsinger on 8/15/10. +// Copyright 2010 Nathan Kinsinger. All rights reserved. +// + +#import "NSApplication+GitXScripting.h" +#import "PBDiffWindowController.h" + + +@implementation NSApplication (GitXScripting) + +- (void)showDiffScriptCommand:(NSScriptCommand *)command +{ + NSString *diffText = [command directParameter]; + if (diffText) { + PBDiffWindowController *diffController = [[PBDiffWindowController alloc] initWithDiff:diffText]; + [diffController showWindow:nil]; + [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; + } +} + +@end diff --git a/PBGitHistoryController.m b/PBGitHistoryController.m index 89cd1ab..3f08f65 100644 --- a/PBGitHistoryController.m +++ b/PBGitHistoryController.m @@ -50,6 +50,7 @@ [repository.revisionList addObserver:self forKeyPath:@"isUpdating" options:0 context:@"revisionListUpdating"]; [repository addObserver:self forKeyPath:@"currentBranch" options:0 context:@"branchChange"]; [repository addObserver:self forKeyPath:@"refs" options:0 context:@"updateRefs"]; + [repository addObserver:self forKeyPath:@"currentBranchFilter" options:0 context:@"branchFilterChange"]; forceSelectionUpdate = YES; NSSize cellSpacing = [commitList intercellSpacing]; @@ -240,6 +241,12 @@ return; } + if ([(NSString *)context isEqualToString:@"branchFilterChange"]) { + [PBGitDefaults setBranchFilter:repository.currentBranchFilter]; + [self updateBranchFilterMatrix]; + return; + } + if([(NSString *)context isEqualToString:@"updateCommitCount"] || [(NSString *)context isEqualToString:@"revisionListUpdating"]) { [self updateStatus]; @@ -454,6 +461,7 @@ [repository.revisionList removeObserver:self forKeyPath:@"isUpdating"]; [repository removeObserver:self forKeyPath:@"currentBranch"]; [repository removeObserver:self forKeyPath:@"refs"]; + [repository removeObserver:self forKeyPath:@"currentBranchFilter"]; } [webHistoryController closeView]; diff --git a/PBGitRepository.h b/PBGitRepository.h index 25176cd..86b094a 100644 --- a/PBGitRepository.h +++ b/PBGitRepository.h @@ -104,6 +104,7 @@ static NSString * PBStringFromBranchFilterType(PBGitXBranchFilterType type) { - (BOOL)isRefOnHeadBranch:(PBGitRef *)testRef; - (BOOL)checkRefFormat:(NSString *)refName; - (BOOL)refExists:(PBGitRef *)ref; +- (PBGitRef *)refForName:(NSString *)name; - (NSArray *) remotes; - (BOOL) hasRemotes; diff --git a/PBGitRepository.m b/PBGitRepository.m index 6f93a71..48a70f4 100644 --- a/PBGitRepository.m +++ b/PBGitRepository.m @@ -18,6 +18,7 @@ #import "PBRemoteProgressSheet.h" #import "PBGitRevList.h" #import "PBGitDefaults.h" +#import "GitXScriptingConstants.h" NSString* PBGitRepositoryErrorDomain = @"GitXErrorDomain"; @@ -429,6 +430,31 @@ NSString* PBGitRepositoryErrorDomain = @"GitXErrorDomain"; return NO; return YES; } + +// useful for getting the full ref for a user entered name +// EX: name: master +// ref: refs/heads/master +- (PBGitRef *)refForName:(NSString *)name +{ + if (!name) + return nil; + + int retValue = 1; + NSString *output = [self outputInWorkdirForArguments:[NSArray arrayWithObjects:@"show-ref", name, nil] retValue:&retValue]; + if (retValue) + return nil; + + // the output is in the format: + // with potentially multiple lines if there are multiple matching refs (ex: refs/remotes/origin/master) + // here we only care about the first match + NSArray *refList = [output componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if ([refList count] > 1) { + NSString *refName = [refList objectAtIndex:1]; + return [PBGitRef refFromString:refName]; + } + + return nil; +} // Returns either this object, or an existing, equal object - (PBGitRevSpecifier*) addBranch:(PBGitRevSpecifier*)branch @@ -876,6 +902,113 @@ NSString* PBGitRepositoryErrorDomain = @"GitXErrorDomain"; } +#pragma mark GitX Scripting + +- (void)handleRevListArguments:(NSArray *)arguments +{ + if (![arguments count]) + return; + + PBGitRevSpecifier *revListSpecifier = nil; + + // the argument may be a branch or tag name but will probably not be the full reference + if ([arguments count] == 1) { + PBGitRef *refArgument = [self refForName:[arguments lastObject]]; + if (refArgument) + revListSpecifier = [[PBGitRevSpecifier alloc] initWithRef:refArgument]; + } + + if (!revListSpecifier) + revListSpecifier = [[PBGitRevSpecifier alloc] initWithParameters:arguments]; + + self.currentBranch = [self addBranch:revListSpecifier]; + [PBGitDefaults setShowStageView:NO]; + [self.windowController showHistoryView:self]; +} + +- (void)handleBranchFilterEventForFilter:(PBGitXBranchFilterType)filter additionalArguments:(NSMutableArray *)arguments +{ + self.currentBranchFilter = filter; + [PBGitDefaults setShowStageView:NO]; + [self.windowController showHistoryView:self]; + + // treat any additional arguments as a rev-list specifier + if ([arguments count] > 1) { + [arguments removeObjectAtIndex:0]; + [self handleRevListArguments:arguments]; + } +} + +- (void)handleGitXScriptingArguments:(NSAppleEventDescriptor *)argumentsList +{ + NSMutableArray *arguments = [NSMutableArray array]; + uint argumentsIndex = 1; // AppleEvent list descriptor's are one based + while(1) { + NSAppleEventDescriptor *arg = [argumentsList descriptorAtIndex:argumentsIndex++]; + if (arg) + [arguments addObject:[arg stringValue]]; + else + break; + } + + if (![arguments count]) + return; + + NSString *firstArgument = [arguments objectAtIndex:0]; + + if ([firstArgument isEqualToString:@"-c"] || [firstArgument isEqualToString:@"--commit"]) { + [PBGitDefaults setShowStageView:YES]; + [self.windowController showCommitView:self]; + return; + } + + if ([firstArgument isEqualToString:@"--all"]) { + [self handleBranchFilterEventForFilter:kGitXAllBranchesFilter additionalArguments:arguments]; + return; + } + + if ([firstArgument isEqualToString:@"--local"]) { + [self handleBranchFilterEventForFilter:kGitXLocalRemoteBranchesFilter additionalArguments:arguments]; + return; + } + + if ([firstArgument isEqualToString:@"--branch"]) { + [self handleBranchFilterEventForFilter:kGitXSelectedBranchFilter additionalArguments:arguments]; + return; + } + + // if the argument is not a known command then treat it as a rev-list specifier + [self handleRevListArguments:arguments]; +} + +// see if the current appleEvent has the command line arguments from the gitx cli +// this could be from an openApplication or an openDocument apple event +// when opening a repository this is called before the sidebar controller gets it's awakeFromNib: message +// if the repository is already open then this is also a good place to catch the event as the window is about to be brought forward +- (void)showWindows +{ + NSAppleEventDescriptor *currentAppleEvent = [[NSAppleEventManager sharedAppleEventManager] currentAppleEvent]; + + if (currentAppleEvent) { + NSAppleEventDescriptor *eventRecord = [currentAppleEvent paramDescriptorForKeyword:keyAEPropData]; + + // on app launch there may be many repositories opening, so double check that this is the right repo + NSString *path = [[eventRecord paramDescriptorForKeyword:typeFileURL] stringValue]; + if (path) { + if ([[PBGitRepository gitDirForURL:[NSURL URLWithString:path]] isEqual:[self fileURL]]) { + NSAppleEventDescriptor *argumentsList = [eventRecord paramDescriptorForKeyword:kGitXAEKeyArgumentsList]; + [self handleGitXScriptingArguments:argumentsList]; + + // showWindows may be called more than once during app launch so remove the CLI data after we handle the event + [currentAppleEvent removeDescriptorWithKeyword:keyAEPropData]; + } + } + } + + [super showWindows]; +} + + #pragma mark low level - (int) returnValueForCommand:(NSString *)cmd diff --git a/gitx.m b/gitx.m index 464a5a0..8c3ef77 100644 --- a/gitx.m +++ b/gitx.m @@ -6,61 +6,52 @@ // Copyright 2008 __MyCompanyName__. All rights reserved. // -#import "PBCLIProxy.h" #import "PBGitBinary.h" #import "PBEasyPipe.h" +#import "GitXScriptingConstants.h" +#import "GitX.h" -NSDistantObject* connect() -{ - id proxy = [NSConnection rootProxyForConnectionWithRegisteredName:ConnectionName host:nil]; - [proxy setProtocolForProxy:@protocol(GitXCliToolProtocol)]; - return proxy; -} -NSDistantObject *createProxy() -{ - NSDistantObject *proxy = connect(); - if (proxy) - return proxy; - - // The connection failed, so try to launch the app - [[NSWorkspace sharedWorkspace] launchAppWithBundleIdentifier: @"nl.frim.GitX" - options: NSWorkspaceLaunchWithoutActivation - additionalEventParamDescriptor: nil - launchIdentifier: nil]; - - // Now attempt to connect, allowing the app time to startup - int attempt; - for (attempt = 0; proxy == nil && attempt < 50; ++attempt) { - if ( (proxy = connect()) != nil ) - return proxy; - - usleep(15000); - } - - // not succesful! - fprintf(stderr, "Couldn't connect to app server!\n"); - exit(1); - return nil; -} +#pragma mark Commands handled locally void usage(char const *programName) { - printf("Usage: %s (--help|--version)\n", programName); - printf(" or: %s (--commit|-h)\n", programName); + printf("Usage: %s (--help|--version|--git-path)\n", programName); + printf(" or: %s (--commit)\n", programName); + printf(" or: %s (--all|--local|--branch) [branch/tag]\n", programName); printf(" or: %s \n", programName); + printf(" or: %s (--diff)\n", programName); printf("\n"); - printf("\t-h, --help print this help\n"); - printf("\t--commit, -c start GitX in commit mode\n"); + printf(" -h, --help print this help\n"); + printf(" -v, --version prints version info for both GitX and git\n"); + printf(" --git-path prints the path to the directory containing git\n"); + printf("\n"); + printf("Commit/Stage view\n"); + printf(" -c, --commit start GitX in commit/stage mode\n"); + printf("\n"); + printf("Branch filter options\n"); + printf(" Add an optional branch or tag name to select that branch using the given branch filter\n"); + printf("\n"); + printf(" --all [branch] view history for all branches\n"); + printf(" --local [branch] view history for local branches only\n"); + printf(" --branch [branch] view history for the selected branch only\n"); printf("\n"); printf("RevList options\n"); - printf("\tSee 'man git-log' and 'man git-rev-list' for options you can pass to gitx\n"); + printf(" See 'man git-log' and 'man git-rev-list' for options you can pass to gitx\n"); printf("\n"); - printf("\t--all show all branches\n"); - printf("\t show specific branch\n"); - printf("\t -- show commits touching paths\n"); + printf(" select specific branch or tag\n"); + printf(" -- show commits touching paths\n"); + printf(" -S show commits that introduce or remove an instance of \n"); + printf("\n"); + printf("Diff options\n"); + printf(" See 'man git-diff' for options you can pass to gitx --diff\n"); + printf("\n"); + printf(" -d, --diff [] {0,2} [--] [...]\n"); + printf(" shows the diff in a window in GitX\n"); + printf(" git diff [options] | gitx\n"); + printf(" use gitx to pipe diff output to a GitX window\n"); exit(1); } @@ -82,36 +73,107 @@ void git_path() exit(101); NSString *path = [[PBGitBinary path] stringByDeletingLastPathComponent]; - printf("%s", [path UTF8String]); + printf("%s\n", [path UTF8String]); exit(0); } -void handleSTDINDiff(id proxy) + +#pragma mark - +#pragma mark Commands sent to GitX + +void handleSTDINDiff() { NSFileHandle *handle = [NSFileHandle fileHandleWithStandardInput]; NSData *data = [handle readDataToEndOfFile]; - NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSString *diff = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - if (string && [string length] > 0) { - [proxy openDiffWindowWithDiff:string]; - exit(0); + if (diff && [diff length] > 0) { + GitXApplication *gitXApp = [SBApplication applicationWithBundleIdentifier:kGitXBundleIdentifier]; + [gitXApp showDiff:diff]; } + + exit(0); } -void handleDiffWithArguments(NSArray *arguments, NSString *directory, id proxy) +void handleDiffWithArguments(NSURL *repositoryURL, NSMutableArray *arguments) { - int ret; - arguments = [[NSArray arrayWithObject:@"diff"] arrayByAddingObjectsFromArray:arguments]; - NSString *diff = [PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:arguments inDir:directory retValue:&ret]; - if (ret) { - printf("Invalid diff command\n"); + [arguments insertObject:@"diff" atIndex:0]; + + int retValue = 1; + NSString *diffOutput = [PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:arguments inDir:[repositoryURL path] retValue:&retValue]; + if (retValue) { + // if there is an error diffOutput should have the error output from git + if (diffOutput) + printf("%s\n", [diffOutput UTF8String]); + else + printf("Invalid diff command [%d]\n", retValue); exit(3); } - [proxy openDiffWindowWithDiff:diff]; + GitXApplication *gitXApp = [SBApplication applicationWithBundleIdentifier:kGitXBundleIdentifier]; + [gitXApp showDiff:diffOutput]; + exit(0); } +void handleOpenRepository(NSURL *repositoryURL, NSMutableArray *arguments) +{ + // if there are command line arguments send them to GitX through an Apple Event + // the recordDescriptor will be stored in keyAEPropData inside the openDocument or openApplication event + NSAppleEventDescriptor *recordDescriptor = nil; + if ([arguments count]) { + recordDescriptor = [NSAppleEventDescriptor recordDescriptor]; + + NSAppleEventDescriptor *listDescriptor = [NSAppleEventDescriptor listDescriptor]; + uint listIndex = 1; // AppleEvent list descriptor's are one based + for (NSString *argument in arguments) + [listDescriptor insertDescriptor:[NSAppleEventDescriptor descriptorWithString:argument] atIndex:listIndex++]; + + [recordDescriptor setParamDescriptor:listDescriptor forKeyword:kGitXAEKeyArgumentsList]; + + // this is used as a double check in GitX + NSAppleEventDescriptor *url = [NSAppleEventDescriptor descriptorWithString:[repositoryURL absoluteString]]; + [recordDescriptor setParamDescriptor:url forKeyword:typeFileURL]; + } + + // use NSWorkspace to open GitX and send the arguments + // this allows the repository document to modify itself before it shows it's GUI + BOOL didOpenURLs = [[NSWorkspace sharedWorkspace] openURLs:[NSArray arrayWithObject:repositoryURL] + withAppBundleIdentifier:kGitXBundleIdentifier + options:0 + additionalEventParamDescriptor:recordDescriptor + launchIdentifiers:NULL]; + if (!didOpenURLs) { + printf("Unable to open GitX.app\n"); + exit(2); + } +} + + +#pragma mark - +#pragma mark main + +NSURL *workingDirectoryURL() +{ + NSString *path = [[[NSProcessInfo processInfo] environment] objectForKey:@"PWD"]; + + NSURL *url = [NSURL fileURLWithPath:path isDirectory:YES]; + if (!url) { + printf("Unable to create url to path: %s\n", [path UTF8String]); + exit(2); + } + + return url; +} + +NSMutableArray *argumentsArray() +{ + NSMutableArray *arguments = [[[NSProcessInfo processInfo] arguments] mutableCopy]; + [arguments removeObjectAtIndex:0]; // url to executable path is not needed + + return arguments; +} + int main(int argc, const char** argv) { if (argc >= 2 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) @@ -121,42 +183,28 @@ int main(int argc, const char** argv) if (argc >= 2 && !strcmp(argv[1], "--git-path")) git_path(); + // From here on everything needs to access git, so make sure it's installed if (![PBGitBinary path]) { printf("%s\n", [[PBGitBinary notFoundError] cStringUsingEncoding:NSUTF8StringEncoding]); exit(2); } - // Attempt to connect to the app - id proxy = createProxy(); - - // Create arguments - argv++; argc--; - NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:argc]; - int i = 0; - for (i = 0; i < argc; i++) - [arguments addObject: [NSString stringWithUTF8String:argv[i]]]; - + // gitx can be used to pipe diff output to be displayed in GitX if (!isatty(STDIN_FILENO) && fdopen(STDIN_FILENO, "r")) - handleSTDINDiff(proxy); + handleSTDINDiff(); - // From this point, we require a working directory - NSString *pwd = [[[NSProcessInfo processInfo] environment] objectForKey:@"PWD"]; - if (!pwd) - exit(2); + // From this point, we require a working directory and the arguments + NSMutableArray *arguments = argumentsArray(); + NSURL *wdURL = workingDirectoryURL(); if ([arguments count] > 0 && ([[arguments objectAtIndex:0] isEqualToString:@"--diff"] || - [[arguments objectAtIndex:0] isEqualToString:@"-d"])) - handleDiffWithArguments([arguments subarrayWithRange:NSMakeRange(1, [arguments count] - 1)], pwd, proxy); - - // No diff, just open the current dir - NSURL* url = [NSURL fileURLWithPath:pwd]; - NSError* error = nil; - - if (![proxy openRepository:url arguments: arguments error:&error]) { - fprintf(stderr, "Error opening repository at %s\n", [[url path] UTF8String]); - if (error) - fprintf(stderr, "\t%s\n", [[error localizedFailureReason] UTF8String]); + [[arguments objectAtIndex:0] isEqualToString:@"-d"])) { + [arguments removeObjectAtIndex:0]; + handleDiffWithArguments(wdURL, arguments); } + // No commands handled by gitx, open the current dir in GitX with the arguments + handleOpenRepository(wdURL, arguments); + return 0; }