Skip to content

Commit fd8dfc4

Browse files
Checkout from search results (#666)
Allows you to search for local or remote branches or individual commits and hitting return will checkout to them using the smart target mechanism the map view uses.
1 parent 47de46a commit fd8dfc4

File tree

7 files changed

+168
-102
lines changed

7 files changed

+168
-102
lines changed

GitUpKit/Components/GICommitListViewController.m

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#import "GICommitListViewController.h"
2121

22+
#import "GCLiveRepository+Utilities.h"
2223
#import "GIInterface.h"
2324
#import "XLFacilityMacros.h"
2425

@@ -244,4 +245,13 @@ - (void)tableViewSelectionDidChange:(NSNotification*)notification {
244245
}
245246
}
246247

248+
- (void)keyDown:(NSEvent *)event {
249+
if (event.keyCode == kGIKeyCode_Return) {
250+
GCHistoryCommit *commit = [self selectedCommit];
251+
[self.repository smartCheckoutCommit:commit window:self.view.window];
252+
} else {
253+
[super keyDown:event];
254+
}
255+
}
256+
247257
@end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// GCLiveRepository+Utilities.h
3+
// GitUpKit (OSX)
4+
//
5+
// Created by Lucas Derraugh on 8/2/19.
6+
//
7+
8+
#import <GitUpKit/GitUpKit.h>
9+
10+
NS_ASSUME_NONNULL_BEGIN
11+
12+
@interface GCLiveRepository (Utilities)
13+
14+
/// Attempt to checkout branch matching commit or fallback to commit. Window is used to present modal dialog.
15+
- (void)smartCheckoutCommit:(GCHistoryCommit *)commit window:(NSWindow *)window;
16+
17+
/// Returns target for smart checkout
18+
- (id)smartCheckoutTarget:(GCHistoryCommit*)commit;
19+
20+
/// Checkout remote branch and ask user to create local branch if none exists. Window is used to present modal dialog.
21+
- (void)checkoutRemoteBranch:(GCHistoryRemoteBranch*)remoteBranch window:(NSWindow *)window;
22+
23+
@end
24+
25+
NS_ASSUME_NONNULL_END
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//
2+
// GCLiveRepository+Utilities.m
3+
// GitUpKit (OSX)
4+
//
5+
// Created by Lucas Derraugh on 8/2/19.
6+
//
7+
8+
#import "GCLiveRepository+Utilities.h"
9+
10+
@implementation GCLiveRepository (Utilities)
11+
12+
- (void)smartCheckoutCommit:(GCHistoryCommit *)commit window:(NSWindow *)window {
13+
if (![self validateCheckoutCommit:commit]) {
14+
NSBeep();
15+
return;
16+
}
17+
id target = [self smartCheckoutTarget:commit];
18+
if ([target isKindOfClass:[GCLocalBranch class]]) {
19+
[self _checkoutLocalBranch:target window:window];
20+
} else {
21+
GCHistoryRemoteBranch* branch = commit.remoteBranches.firstObject;
22+
if (branch && ![self.history historyLocalBranchWithName:branch.branchName]) {
23+
NSAlert* alert = [[NSAlert alloc] init];
24+
alert.messageText = NSLocalizedString(@"Do you want to just checkout the commit or also create a new local branch?", nil);
25+
alert.informativeText = [NSString stringWithFormat:NSLocalizedString(@"The selected commit is also the tip of the remote branch \"%@\".", nil), branch.name];
26+
[alert addButtonWithTitle:NSLocalizedString(@"Create Local Branch", nil)];
27+
[alert addButtonWithTitle:NSLocalizedString(@"Checkout Commit", nil)];
28+
[alert addButtonWithTitle:NSLocalizedString(@"Cancel", nil)];
29+
alert.type = kGIAlertType_Note;
30+
[alert beginSheetModalForWindow:window completionHandler:^(NSModalResponse returnCode) {
31+
if (returnCode == NSAlertFirstButtonReturn) {
32+
[self checkoutRemoteBranch:branch window:window];
33+
} else if (returnCode == NSAlertSecondButtonReturn) {
34+
[self _checkoutCommit:target window:window];
35+
}
36+
}];
37+
} else {
38+
[self _checkoutCommit:target window:window];
39+
}
40+
}
41+
}
42+
43+
// This will abort on conflicts in workdir or index so there's no need to require a clean repo
44+
- (void)checkoutRemoteBranch:(GCHistoryRemoteBranch*)remoteBranch window:(NSWindow *)window {
45+
NSError* error;
46+
[self setUndoActionName:[NSString stringWithFormat:NSLocalizedString(@"Checkout Remote Branch \"%@\"", nil), remoteBranch.name]];
47+
if (![self performOperationWithReason:@"checkout_remote_branch"
48+
argument:remoteBranch.name
49+
skipCheckoutOnUndo:NO
50+
error:&error
51+
usingBlock:^BOOL(GCLiveRepository* repository, NSError** outError) {
52+
GCLocalBranch* localBranch = [repository createLocalBranchFromCommit:remoteBranch.tipCommit withName:remoteBranch.branchName force:NO error:outError];
53+
if (localBranch == nil) {
54+
return NO;
55+
}
56+
if (![repository setUpstream:remoteBranch forLocalBranch:localBranch error:outError]) {
57+
return NO;
58+
}
59+
if (![repository checkoutLocalBranch:localBranch options:kGCCheckoutOption_UpdateSubmodulesRecursively error:outError]) {
60+
[repository deleteLocalBranch:localBranch error:NULL]; // Ignore errors
61+
return NO;
62+
}
63+
return YES;
64+
}]) {
65+
[window presentError:error];
66+
}
67+
}
68+
69+
// This will preemptively abort on conflicts in workdir or index so there's no need to require a clean repo
70+
- (void)_checkoutLocalBranch:(GCHistoryLocalBranch*)branch window:(NSWindow *)window {
71+
NSError* error;
72+
[self setUndoActionName:[NSString stringWithFormat:NSLocalizedString(@"Checkout Branch \"%@\"", nil), branch.name]];
73+
if (![self performOperationWithReason:@"checkout_branch"
74+
argument:branch.name
75+
skipCheckoutOnUndo:NO
76+
error:&error
77+
usingBlock:^BOOL(GCLiveRepository* repository, NSError** outError) {
78+
return [repository checkoutLocalBranch:branch options:kGCCheckoutOption_UpdateSubmodulesRecursively error:outError];
79+
}]) {
80+
[window presentError:error];
81+
}
82+
}
83+
84+
// This will preemptively abort on conflicts in workdir or index so there's no need to require a clean repo
85+
- (void)_checkoutCommit:(GCHistoryCommit*)commit window:(NSWindow *)window {
86+
NSError* error;
87+
[self setUndoActionName:NSLocalizedString(@"Checkout Commit", nil)];
88+
if (![self performOperationWithReason:@"checkout_commit"
89+
argument:commit.SHA1
90+
skipCheckoutOnUndo:NO
91+
error:&error
92+
usingBlock:^BOOL(GCLiveRepository* repository, NSError** outError) {
93+
return [repository checkoutCommit:commit options:kGCCheckoutOption_UpdateSubmodulesRecursively error:outError];
94+
}]) {
95+
[window presentError:error];
96+
}
97+
}
98+
99+
- (id)smartCheckoutTarget:(GCHistoryCommit*)commit {
100+
NSArray* branches = commit.localBranches;
101+
if (branches.count > 1) {
102+
GCHistoryLocalBranch* headBranch = self.history.HEADBranch;
103+
NSUInteger index = [branches indexOfObject:headBranch];
104+
if (index != NSNotFound) {
105+
return [branches objectAtIndex:((index + 1) % branches.count)];
106+
}
107+
}
108+
GCHistoryLocalBranch* branch = branches.firstObject;
109+
return branch ? branch : commit;
110+
}
111+
112+
- (BOOL)validateCheckoutCommit:(GCHistoryCommit*)commit {
113+
id target = [self smartCheckoutTarget:commit];
114+
if ([target isKindOfClass:[GCLocalBranch class]]) {
115+
return ![self.history.HEADBranch isEqualToBranch:target];
116+
} else {
117+
return ![self.history.HEADCommit isEqualToCommit:target];
118+
}
119+
}
120+
121+
@end

GitUpKit/GitUpKit.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
/* Begin PBXBuildFile section */
1010
0AC8525923A122C400479160 /* GILaunchServicesLocator.h in Headers */ = {isa = PBXBuildFile; fileRef = 0AC8525723A122C400479160 /* GILaunchServicesLocator.h */; settings = {ATTRIBUTES = (Public, ); }; };
1111
0AC8525A23A122C400479160 /* GILaunchServicesLocator.m in Sources */ = {isa = PBXBuildFile; fileRef = 0AC8525823A122C400479160 /* GILaunchServicesLocator.m */; };
12+
1DF371CD22F5262300EF7BD9 /* GCLiveRepository+Utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 1DF371CB22F5262300EF7BD9 /* GCLiveRepository+Utilities.h */; };
13+
1DF371CE22F5262300EF7BD9 /* GCLiveRepository+Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 1DF371CC22F5262300EF7BD9 /* GCLiveRepository+Utilities.m */; };
1214
743BF1841B871C0200E1CA49 /* GCOrderedSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 74EDB5D01B84D4C400F00E79 /* GCOrderedSet.h */; settings = {ATTRIBUTES = (Public, ); }; };
1315
749335CA1B9B7FF200225513 /* GCOrderedSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 74EDB5D11B84D4C400F00E79 /* GCOrderedSet.m */; };
1416
749786941B85AAB10065BD55 /* GCOrderedSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 74EDB5D11B84D4C400F00E79 /* GCOrderedSet.m */; };
@@ -427,6 +429,8 @@
427429
/* Begin PBXFileReference section */
428430
0AC8525723A122C400479160 /* GILaunchServicesLocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GILaunchServicesLocator.h; sourceTree = "<group>"; };
429431
0AC8525823A122C400479160 /* GILaunchServicesLocator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GILaunchServicesLocator.m; sourceTree = "<group>"; };
432+
1DF371CB22F5262300EF7BD9 /* GCLiveRepository+Utilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "GCLiveRepository+Utilities.h"; sourceTree = "<group>"; };
433+
1DF371CC22F5262300EF7BD9 /* GCLiveRepository+Utilities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "GCLiveRepository+Utilities.m"; sourceTree = "<group>"; };
430434
74EDB5D01B84D4C400F00E79 /* GCOrderedSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCOrderedSet.h; sourceTree = "<group>"; };
431435
74EDB5D11B84D4C400F00E79 /* GCOrderedSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCOrderedSet.m; sourceTree = "<group>"; };
432436
74EDB5D51B84E06500F00E79 /* GCOrderedSet-Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GCOrderedSet-Tests.m"; sourceTree = "<group>"; };
@@ -875,6 +879,8 @@
875879
E259C2D61A64FAEA0079616B /* GCRepository+Utilities-Tests.m */,
876880
E218A58C1A56706600DFF1DF /* GCRepository+Utilities.h */,
877881
E218A58D1A56706600DFF1DF /* GCRepository+Utilities.m */,
882+
1DF371CB22F5262300EF7BD9 /* GCLiveRepository+Utilities.h */,
883+
1DF371CC22F5262300EF7BD9 /* GCLiveRepository+Utilities.m */,
878884
);
879885
path = Extensions;
880886
sourceTree = "<group>";
@@ -1183,6 +1189,7 @@
11831189
E267E2371B84DC3900BAB377 /* GIDiffContentsViewController.h in Headers */,
11841190
E267E2381B84DC3900BAB377 /* GIDiffFilesViewController.h in Headers */,
11851191
E267E2391B84DC3900BAB377 /* GISnapshotListViewController.h in Headers */,
1192+
1DF371CD22F5262300EF7BD9 /* GCLiveRepository+Utilities.h in Headers */,
11861193
E267E23A1B84DC3900BAB377 /* GIUnifiedReflogViewController.h in Headers */,
11871194
E267E24D1B84DC7D00BAB377 /* GIAdvancedCommitViewController.h in Headers */,
11881195
E267E24E1B84DC7D00BAB377 /* GICommitRewriterViewController.h in Headers */,
@@ -1514,6 +1521,7 @@
15141521
E267E2311B84DC2600BAB377 /* GICommitListViewController.m in Sources */,
15151522
E267E2321B84DC2600BAB377 /* GIDiffContentsViewController.m in Sources */,
15161523
E267E2331B84DC2600BAB377 /* GIDiffFilesViewController.m in Sources */,
1524+
1DF371CE22F5262300EF7BD9 /* GCLiveRepository+Utilities.m in Sources */,
15171525
E267E2341B84DC2600BAB377 /* GISnapshotListViewController.m in Sources */,
15181526
E267E2351B84DC2600BAB377 /* GIUnifiedReflogViewController.m in Sources */,
15191527
E267E2591B84DCA100BAB377 /* GIAdvancedCommitViewController.m in Sources */,

GitUpKit/Views/GIMapViewController+Operations.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@
2121
- (BOOL)checkCleanRepositoryForOperationOnCommit:(GCCommit*)commit;
2222
- (BOOL)checkCleanRepositoryForOperationOnBranch:(GCLocalBranch*)branch;
2323

24-
- (void)checkoutCommit:(GCHistoryCommit*)commit;
25-
- (void)checkoutLocalBranch:(GCHistoryLocalBranch*)branch;
26-
- (void)checkoutRemoteBranch:(GCHistoryRemoteBranch*)remoteBranch;
27-
2824
- (void)swapCommitWithParent:(GCHistoryCommit*)commit;
2925
- (void)swapCommitWithChild:(GCHistoryCommit*)commit;
3026
- (void)squashCommitWithParent:(GCHistoryCommit*)commit;

GitUpKit/Views/GIMapViewController+Operations.m

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -105,64 +105,6 @@ - (GCMergeAnalysisResult)_analyzeMergingCommit:(GCCommit*)mergeCommit intoCommit
105105
return result;
106106
}
107107

108-
#pragma mark - Checkout
109-
110-
// This will preemptively abort on conflicts in workdir or index so there's no need to require a clean repo
111-
- (void)checkoutCommit:(GCHistoryCommit*)commit {
112-
NSError* error;
113-
[self.repository setUndoActionName:NSLocalizedString(@"Checkout Commit", nil)];
114-
if (![self.repository performOperationWithReason:@"checkout_commit"
115-
argument:commit.SHA1
116-
skipCheckoutOnUndo:NO
117-
error:&error
118-
usingBlock:^BOOL(GCLiveRepository* repository, NSError** outError) {
119-
return [repository checkoutCommit:commit options:kGCCheckoutOption_UpdateSubmodulesRecursively error:outError];
120-
}]) {
121-
[self presentError:error];
122-
}
123-
}
124-
125-
// This will preemptively abort on conflicts in workdir or index so there's no need to require a clean repo
126-
- (void)checkoutLocalBranch:(GCHistoryLocalBranch*)branch {
127-
NSError* error;
128-
[self.repository setUndoActionName:[NSString stringWithFormat:NSLocalizedString(@"Checkout Branch \"%@\"", nil), branch.name]];
129-
if (![self.repository performOperationWithReason:@"checkout_branch"
130-
argument:branch.name
131-
skipCheckoutOnUndo:NO
132-
error:&error
133-
usingBlock:^BOOL(GCLiveRepository* repository, NSError** outError) {
134-
return [repository checkoutLocalBranch:branch options:kGCCheckoutOption_UpdateSubmodulesRecursively error:outError];
135-
}]) {
136-
[self presentError:error];
137-
}
138-
}
139-
140-
// This will abort on conflicts in workdir or index so there's no need to require a clean repo
141-
- (void)checkoutRemoteBranch:(GCHistoryRemoteBranch*)remoteBranch {
142-
NSError* error;
143-
[self.repository setUndoActionName:[NSString stringWithFormat:NSLocalizedString(@"Checkout Remote Branch \"%@\"", nil), remoteBranch.name]];
144-
if (![self.repository performOperationWithReason:@"checkout_remote_branch"
145-
argument:remoteBranch.name
146-
skipCheckoutOnUndo:NO
147-
error:&error
148-
usingBlock:^BOOL(GCLiveRepository* repository, NSError** outError) {
149-
GCLocalBranch* localBranch = [repository createLocalBranchFromCommit:remoteBranch.tipCommit withName:remoteBranch.branchName force:NO error:outError];
150-
if (localBranch == nil) {
151-
return NO;
152-
}
153-
if (![repository setUpstream:remoteBranch forLocalBranch:localBranch error:outError]) {
154-
return NO;
155-
}
156-
if (![repository checkoutLocalBranch:localBranch options:kGCCheckoutOption_UpdateSubmodulesRecursively error:outError]) {
157-
[repository deleteLocalBranch:localBranch error:NULL]; // Ignore errors
158-
return NO;
159-
}
160-
return YES;
161-
}]) {
162-
[self presentError:error];
163-
}
164-
}
165-
166108
#pragma mark - Commits
167109

168110
- (void)swapCommitWithParent:(GCHistoryCommit*)commit {

GitUpKit/Views/GIMapViewController.m

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#import "GIWindowController.h"
2323
#import "GIInterface.h"
24+
#import "GCLiveRepository+Utilities.h"
2425
#import "GCRepository+Utilities.h"
2526
#import "GCHistory+Rewrite.h"
2627
#import "XLFacilityMacros.h"
@@ -568,19 +569,6 @@ - (void)keyDown:(NSEvent*)event {
568569
}
569570
}
570571

571-
- (id)_smartCheckoutTarget:(GCHistoryCommit*)commit {
572-
NSArray* branches = commit.localBranches;
573-
if (branches.count > 1) {
574-
GCHistoryLocalBranch* headBranch = self.repository.history.HEADBranch;
575-
NSUInteger index = [branches indexOfObject:headBranch];
576-
if (index != NSNotFound) {
577-
return [branches objectAtIndex:((index + 1) % branches.count)];
578-
}
579-
}
580-
GCHistoryLocalBranch* branch = branches.firstObject;
581-
return branch ? branch : commit;
582-
}
583-
584572
- (void)_promptForCommitMessage:(NSString*)message withTitle:(NSString*)title button:(NSString*)button block:(void (^)(NSString* message))block {
585573
_messageTextField.stringValue = title;
586574
_messageTextView.string = message;
@@ -677,7 +665,7 @@ - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
677665
}
678666

679667
if (item.action == @selector(checkoutSelectedCommit:)) {
680-
id target = [self _smartCheckoutTarget:commit];
668+
id target = [self.repository smartCheckoutTarget:commit];
681669
if ([target isKindOfClass:[GCLocalBranch class]]) {
682670
_checkoutMenuItem.title = [NSString stringWithFormat:NSLocalizedString(@"Checkout \"%@\" Branch", nil), [target name]];
683671
return ![self.repository.history.HEADBranch isEqualToBranch:target];
@@ -863,31 +851,7 @@ - (IBAction)externalDiffWithHEAD:(id)sender {
863851

864852
- (IBAction)checkoutSelectedCommit:(id)sender {
865853
GCHistoryCommit* commit = _graphView.selectedCommit;
866-
id target = [self _smartCheckoutTarget:commit];
867-
if ([target isKindOfClass:[GCLocalBranch class]]) {
868-
[self checkoutLocalBranch:target];
869-
} else {
870-
GCHistoryRemoteBranch* branch = commit.remoteBranches.firstObject;
871-
if (branch && ![self.repository.history historyLocalBranchWithName:branch.branchName]) {
872-
NSAlert* alert = [[NSAlert alloc] init];
873-
alert.messageText = NSLocalizedString(@"Do you want to just checkout the commit or also create a new local branch?", nil);
874-
alert.informativeText = [NSString stringWithFormat:NSLocalizedString(@"The selected commit is also the tip of the remote branch \"%@\".", nil), branch.name];
875-
[alert addButtonWithTitle:NSLocalizedString(@"Create Local Branch", nil)];
876-
[alert addButtonWithTitle:NSLocalizedString(@"Checkout Commit", nil)];
877-
[alert addButtonWithTitle:NSLocalizedString(@"Cancel", nil)];
878-
alert.type = kGIAlertType_Note;
879-
[self presentAlert:alert
880-
completionHandler:^(NSInteger returnCode) {
881-
if (returnCode == NSAlertFirstButtonReturn) {
882-
[self checkoutRemoteBranch:branch];
883-
} else if (returnCode == NSAlertSecondButtonReturn) {
884-
[self checkoutCommit:target];
885-
}
886-
}];
887-
} else {
888-
[self checkoutCommit:target];
889-
}
890-
}
854+
[self.repository smartCheckoutCommit:commit window:self.view.window];
891855
}
892856

893857
- (IBAction)createTagAtSelectedCommit:(id)sender {
@@ -1088,7 +1052,7 @@ - (IBAction)_pushTagToRemote:(id)sender {
10881052

10891053
- (IBAction)_checkoutRemoteBranch:(id)sender {
10901054
GCHistoryRemoteBranch* branch = [(NSMenuItem*)sender representedObject];
1091-
[self checkoutRemoteBranch:branch];
1055+
[self.repository checkoutRemoteBranch:branch window:self.view.window];
10921056
}
10931057

10941058
- (IBAction)_fetchRemoteBranch:(id)sender {

0 commit comments

Comments
 (0)