Skip to content

Commit 9bf1a41

Browse files
committed
Relocate context menu building to the history controller
Since most impacted controllers have a direct reference to it, it makes more sense to use that instead of carrying a dedicated controller with required ivars and a protocol.
1 parent e093561 commit 9bf1a41

16 files changed

+290
-429
lines changed

Classes/Controllers/PBGitHistoryController.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
@class PBGitSidebarController;
1616
@class PBWebHistoryController;
1717
@class PBGitGradientBarView;
18-
@class PBRefController;
1918
@class PBCommitList;
2019
@class GLFileView;
2120
@class GTOID;
@@ -27,7 +26,6 @@ NS_ASSUME_NONNULL_BEGIN
2726

2827
@property (readonly) NSArrayController *commitController;
2928
@property (readonly) NSTreeController *treeController;
30-
@property (readonly) PBRefController *refController;
3129
@property (readonly) PBHistorySearchController *searchController;
3230

3331
@property (assign) NSInteger selectedCommitDetailsIndex;
@@ -70,5 +68,12 @@ NS_ASSUME_NONNULL_BEGIN
7068

7169
@end
7270

71+
@interface PBGitHistoryController (PBContextMenu)
72+
73+
- (NSArray *)menuItemsForRef:(PBGitRef *)refs;
74+
- (NSArray *)menuItemsForCommits:(NSArray<PBGitCommit *> *)commits;
75+
76+
@end
77+
7378
NS_ASSUME_NONNULL_END
7479

Classes/Controllers/PBGitHistoryController.m

Lines changed: 267 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
#import "NSSplitView+GitX.h"
2727
#import "PBGitRevisionRow.h"
2828
#import "PBGitRevisionCell.h"
29-
#import "PBRefMenuItem.h"
3029
#import "PBGitStash.h"
3130

3231
#define kHistorySelectedDetailIndexKey @"PBHistorySelectedDetailIndex"
@@ -38,7 +37,6 @@ @interface PBGitHistoryController () <NSTableViewDelegate> {
3837
IBOutlet NSTreeController *treeController;
3938
IBOutlet PBWebHistoryController *webHistoryController;
4039
IBOutlet GLFileView *fileView;
41-
IBOutlet PBRefController *refController;
4240
IBOutlet PBHistorySearchController *searchController;
4341

4442
__weak IBOutlet NSSearchField *searchField;
@@ -68,7 +66,7 @@ - (void) saveFileBrowserSelection;
6866

6967

7068
@implementation PBGitHistoryController
71-
@synthesize webCommits, gitTree, commitController, refController;
69+
@synthesize webCommits, gitTree, commitController;
7270
@synthesize searchController;
7371
@synthesize commitList;
7472
@synthesize treeController;
@@ -845,3 +843,269 @@ - (NSRect)previewPanel:(id)panel sourceFrameOnScreenForPreviewItem:(id <QLPrevie
845843
}
846844

847845
@end
846+
847+
/*
848+
* TODO: This is kept for simplicity reasons. A "more correct" way of handling
849+
* menus would be to have them in NIBs, and handle them using NSMenuValidation.
850+
*/
851+
852+
@implementation NSMenuItem (PBRefMenuItem)
853+
854+
+ (NSMenuItem *)pb_itemWithTitle:(NSString *)title action:(SEL)selector enabled:(BOOL)isEnabled
855+
{
856+
if (!isEnabled)
857+
selector = nil;
858+
859+
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title action:selector keyEquivalent:@""];
860+
[item setEnabled:isEnabled];
861+
return item;
862+
}
863+
864+
@end
865+
866+
@implementation PBGitHistoryController (PBContextMenu)
867+
868+
- (NSArray<NSMenuItem *> *)menuItemsForStashRef:(PBGitRef *)ref
869+
{
870+
NSMutableArray *items = [NSMutableArray array];
871+
NSString *targetRefName = [ref shortName];
872+
BOOL isCleanWorkingCopy = YES;
873+
874+
// pop
875+
NSString *stashPopTitle = [NSString stringWithFormat:NSLocalizedString(@"Pop %@", @"Contextual Menu Item to pop the selected stash ref"), targetRefName];
876+
[items addObject:[NSMenuItem pb_itemWithTitle:stashPopTitle action:@selector(stashPop:) enabled:isCleanWorkingCopy]];
877+
878+
// apply
879+
NSString *stashApplyTitle = [NSString stringWithFormat:NSLocalizedString(@"Apply %@", @"Contextual Menu Item to apply the selected stash ref"), targetRefName];
880+
[items addObject:[NSMenuItem pb_itemWithTitle:stashApplyTitle action:@selector(stashApply:) enabled:YES]];
881+
882+
// view diff
883+
NSString *stashDiffTitle = @"View Diff";
884+
[items addObject:[NSMenuItem pb_itemWithTitle:stashDiffTitle action:@selector(stashViewDiff:) enabled:YES]];
885+
886+
[items addObject:[NSMenuItem separatorItem]];
887+
888+
// drop
889+
NSString *stashDropTitle = [NSString stringWithFormat:NSLocalizedString(@"Drop %@", @"Contextual Menu Item to drop the selected stash ref"), targetRefName];
890+
[items addObject:[NSMenuItem pb_itemWithTitle:stashDropTitle action:@selector(stashDrop:) enabled:YES]];
891+
892+
for (NSMenuItem *item in items) {
893+
if (!item.representedObject) {
894+
item.representedObject = ref;
895+
}
896+
}
897+
898+
return items;
899+
}
900+
901+
- (NSArray<NSMenuItem *> *)menuItemsForRef:(PBGitRef *)ref
902+
{
903+
if (!ref) {
904+
return nil;
905+
}
906+
907+
/* FIXME: this is a workaround so we don't show a non-working menu when
908+
* right-clicking the "actual" stash ref
909+
*/
910+
if ([ref.refishName isEqualToString:@"refs/stash"]) {
911+
return @[];
912+
}
913+
if (ref.isStash) {
914+
return [self menuItemsForStashRef:ref];
915+
}
916+
917+
NSString *refName = ref.shortName;
918+
919+
PBGitRef *headRef = self.repository.headRef.ref;
920+
NSString *headRefName = headRef.shortName;
921+
922+
BOOL isHead = [ref isEqualToRef:headRef];
923+
BOOL isOnHeadBranch = isHead ? YES : [self.repository isRefOnHeadBranch:ref];
924+
BOOL isDetachedHead = (isHead && [headRefName isEqualToString:@"HEAD"]);
925+
926+
NSString *remoteName = ref.remoteName;
927+
if (!remoteName && ref.isBranch) {
928+
remoteName = [[self.repository remoteRefForBranch:ref error:NULL] remoteName];
929+
}
930+
BOOL hasRemote = (remoteName ? YES : NO);
931+
BOOL isRemote = (ref.isRemote && !ref.isRemoteBranch);
932+
933+
NSMutableArray *items = [NSMutableArray array];
934+
if (!isRemote) {
935+
// checkout ref
936+
NSString *checkoutTitle = [NSString stringWithFormat:NSLocalizedString(@"Checkout “%@", @"Contextual Menu Item to check out the selected ref"), refName];
937+
[items addObject:[NSMenuItem pb_itemWithTitle:checkoutTitle action:@selector(checkout:) enabled:!isHead]];
938+
[items addObject:[NSMenuItem separatorItem]];
939+
940+
// create branch
941+
NSString *createBranchTitle = ref.isRemoteBranch
942+
? [NSString stringWithFormat:NSLocalizedString(@"Create Branch tracking “%@”…", @"Contextual Menu Item to create a branch tracking the selected remote branch"), refName]
943+
: NSLocalizedString(@"Create Branch…", @"Contextual Menu Item to create a new branch at the selected ref");
944+
[items addObject:[NSMenuItem pb_itemWithTitle:createBranchTitle action:@selector(createBranch:) enabled:YES]];
945+
946+
// create tag
947+
[items addObject:[NSMenuItem pb_itemWithTitle:NSLocalizedString(@"Create Tag…", @"Contextual Menu Item to create a tag at the selected ref") action:@selector(createTag:) enabled:YES]];
948+
949+
// view tag info
950+
if (ref.isTag) {
951+
[items addObject:[NSMenuItem pb_itemWithTitle:NSLocalizedString(@"View Tag Info…", @"Contextual Menu Item to view Information about the selected tag") action:@selector(showTagInfoSheet:) enabled:YES]];
952+
}
953+
954+
// Diff
955+
NSString *diffTitle = [NSString stringWithFormat:NSLocalizedString(@"Diff with “%@", @"Contextual Menu Item to view a diff between the selected ref and HEAD"), headRefName];
956+
[items addObject:[NSMenuItem pb_itemWithTitle:diffTitle action:@selector(diffWithHEAD:) enabled:!isHead]];
957+
[items addObject:[NSMenuItem separatorItem]];
958+
959+
// merge ref
960+
NSString *mergeTitle = isOnHeadBranch
961+
? NSLocalizedString(@"Merge", @"Inactive Contextual Menu Item for merging")
962+
: [NSString stringWithFormat:@"Merge %@ into %@", refName, headRefName];
963+
[items addObject:[NSMenuItem pb_itemWithTitle:mergeTitle action:@selector(merge:) enabled:!isOnHeadBranch]];
964+
965+
// rebase
966+
NSString *rebaseTitle = isOnHeadBranch
967+
? NSLocalizedString(@"Rebase", @"Inactive Contextual Menu Item for rebasing")
968+
: [NSString stringWithFormat:NSLocalizedString(@"Rebase ”%@“ onto “%@", @"Contextual Menu Item to rebase HEAD onto the selected ref"), headRefName, refName];
969+
[items addObject:[NSMenuItem pb_itemWithTitle:rebaseTitle action:@selector(rebaseHeadBranch:) enabled:!isOnHeadBranch]];
970+
971+
[items addObject:[NSMenuItem separatorItem]];
972+
973+
// reset
974+
NSString *resetTitle = [NSString stringWithFormat:NSLocalizedString(@"Reset to “%@", @"Contextual Menu Item to reset to the selected ref"), refName];
975+
[items addObject:[NSMenuItem pb_itemWithTitle:resetTitle action:@selector(resetSoft:) enabled:!isHead]];
976+
977+
[items addObject:[NSMenuItem separatorItem]];
978+
}
979+
980+
// fetch
981+
NSString *fetchTitle = hasRemote
982+
? [NSString stringWithFormat:NSLocalizedString(@"Fetch “%@", @"Contextual Menu Item to fetch the selected remote"), remoteName]
983+
: NSLocalizedString(@"Fetch", @"Inactive Contextual Menu Item for fetching");
984+
[items addObject:[NSMenuItem pb_itemWithTitle:fetchTitle action:@selector(fetchRemote:) enabled:hasRemote]];
985+
986+
// pull
987+
NSString *pullTitle = hasRemote
988+
? [NSString stringWithFormat:NSLocalizedString(@"Pull “%@” and Update “%@", @"Contextual Menu Item to pull the remote and update the selected branch"), remoteName, headRefName]
989+
: NSLocalizedString(@"Pull", @"Inactive Contextual Menu Item for pulling");
990+
[items addObject:[NSMenuItem pb_itemWithTitle:pullTitle action:@selector(pullRemote:) enabled:hasRemote]];
991+
992+
// push
993+
if (isRemote || ref.isRemoteBranch) {
994+
// push updates to remote
995+
NSString *pushTitle = [NSString stringWithFormat:NSLocalizedString(@"Push Updates to “%@", @"Contextual Menu Item to push updates of the selected ref to he named remote"), remoteName];
996+
[items addObject:[NSMenuItem pb_itemWithTitle:pushTitle action:@selector(pushUpdatesToRemote:) enabled:YES]];
997+
}
998+
else if (isDetachedHead) {
999+
[items addObject:[NSMenuItem pb_itemWithTitle:NSLocalizedString(@"Push", @"Inactive Contextual Menu Item for pushing") action:nil enabled:NO]];
1000+
}
1001+
else {
1002+
// push to default remote
1003+
BOOL hasDefaultRemote = NO;
1004+
if (!ref.isTag && hasRemote) {
1005+
hasDefaultRemote = YES;
1006+
NSString *pushTitle = [NSString stringWithFormat:NSLocalizedString(@"Push “%@” to “%@", @"Contextual Menu Item to push a ref to a specific remote"), refName, remoteName];
1007+
[items addObject:[NSMenuItem pb_itemWithTitle:pushTitle action:@selector(pushDefaultRemoteForRef:) enabled:YES]];
1008+
}
1009+
1010+
// push to remotes submenu
1011+
NSArray *remoteNames = [self.repository remotes];
1012+
if ([remoteNames count] && !(hasDefaultRemote && ([remoteNames count] == 1))) {
1013+
NSString *pushToTitle = [NSString stringWithFormat:NSLocalizedString(@"Push “%@” to", @"Contextual Menu Submenu Item containing the remotes the selected ref can be pushed to"), refName];
1014+
NSMenuItem *pushToItem = [NSMenuItem pb_itemWithTitle:pushToTitle action:nil enabled:YES];
1015+
NSMenu *remotesMenu = [[NSMenu alloc] initWithTitle:NSLocalizedString(@"Remotes Menu", @"Menu listing the repository’s remotes")];
1016+
for (NSString *remote in remoteNames) {
1017+
NSMenuItem *remoteItem = [NSMenuItem pb_itemWithTitle:remote action:@selector(pushToRemote:) enabled:YES];
1018+
remoteItem.representedObject = remote;
1019+
[remotesMenu addItem:remoteItem];
1020+
}
1021+
[pushToItem setSubmenu:remotesMenu];
1022+
pushToItem.representedObject = ref;
1023+
[items addObject:pushToItem];
1024+
}
1025+
}
1026+
1027+
// delete ref
1028+
[items addObject:[NSMenuItem separatorItem]];
1029+
BOOL isStash = [[ref ref] hasPrefix:@"refs/stash"];
1030+
BOOL isDeleteEnabled = !(isDetachedHead || isHead || isStash);
1031+
if (isDeleteEnabled) {
1032+
NSString *deleteFormat = ref.isRemote
1033+
? NSLocalizedString(@"Delete “%@”…", @"Contextual Menu Item to delete a local ref (e.g. branch)")
1034+
: NSLocalizedString(@"Remove “%@”…", @"Contextual Menu Item to remove a remote");
1035+
NSString *deleteItemTitle = [NSString stringWithFormat:deleteFormat, refName];
1036+
NSMenuItem *deleteItem = [NSMenuItem pb_itemWithTitle:deleteItemTitle action:@selector(deleteRef:) enabled:YES];
1037+
[items addObject:deleteItem];
1038+
}
1039+
1040+
for (NSMenuItem *item in items) {
1041+
if (!item.representedObject) {
1042+
item.representedObject = ref;
1043+
}
1044+
}
1045+
1046+
return items;
1047+
}
1048+
1049+
- (NSArray<NSMenuItem *> *)menuItemsForCommits:(NSArray<PBGitCommit *> *)commits
1050+
{
1051+
NSMutableArray *items = [NSMutableArray array];
1052+
1053+
BOOL isSingleCommitSelection = commits.count == 1;
1054+
PBGitCommit *firstCommit = commits.firstObject;
1055+
1056+
NSString *headBranchName = firstCommit.repository.headRef.ref.shortName;
1057+
BOOL isOnHeadBranch = firstCommit.isOnHeadBranch;
1058+
BOOL isHead = [firstCommit.OID isEqual:firstCommit.repository.headOID];
1059+
1060+
if (isSingleCommitSelection) {
1061+
[items addObject:[NSMenuItem pb_itemWithTitle:NSLocalizedString(@"Checkout Commit", @"Contextual Menu Item to check out the selected commit") action:@selector(checkout:) enabled:YES]];
1062+
[items addObject:[NSMenuItem separatorItem]];
1063+
1064+
[items addObject:[NSMenuItem pb_itemWithTitle:NSLocalizedString(@"Create Branch…", @"Contextual Menu Item to create a branch at the selected commit") action:@selector(createBranch:) enabled:YES]];
1065+
[items addObject:[NSMenuItem pb_itemWithTitle:NSLocalizedString(@"Create Tag…", @"Contextual Menu Item to create a tag at the selected commit") action:@selector(createTag:) enabled:YES]];
1066+
[items addObject:[NSMenuItem separatorItem]];
1067+
}
1068+
1069+
[items addObject:[NSMenuItem pb_itemWithTitle:NSLocalizedString(@"Copy SHA", @"Contextual Menu Item to copy the selected commits’ full SHA(s)") action:@selector(copySHA:) enabled:YES]];
1070+
[items addObject:[NSMenuItem pb_itemWithTitle:NSLocalizedString(@"Copy short SHA", @"Contextual Menu Item to copy the selected commits’ short SHA(s)") action:@selector(copyShortName:) enabled:YES]];
1071+
[items addObject:[NSMenuItem pb_itemWithTitle:NSLocalizedString(@"Copy Patch", @"Contextual Menu Item to copy the selected commits as patch(es)") action:@selector(copyPatch:) enabled:YES]];
1072+
1073+
if (isSingleCommitSelection) {
1074+
NSString *diffTitle = [NSString stringWithFormat:NSLocalizedString(@"Diff with “%@", @"Contextual Menu Item to view a diff between the selected commit and HEAD"), headBranchName];
1075+
[items addObject:[NSMenuItem pb_itemWithTitle:diffTitle action:@selector(diffWithHEAD:) enabled:!isHead]];
1076+
[items addObject:[NSMenuItem separatorItem]];
1077+
1078+
// merge commit
1079+
NSString *mergeTitle = isOnHeadBranch
1080+
? NSLocalizedString(@"Merge Commit", @"Inactive Contextual Menu Item for merging commits")
1081+
: [NSString stringWithFormat:NSLocalizedString(@"Merge Commit into “%@", @"Contextual Menu Item to merge the selected commit into HEAD"), headBranchName];
1082+
[items addObject:[NSMenuItem pb_itemWithTitle:mergeTitle action:@selector(merge:) enabled:!isOnHeadBranch]];
1083+
1084+
// cherry pick
1085+
NSString *cherryPickTitle = isOnHeadBranch
1086+
? NSLocalizedString(@"Cherry Pick Commit", @"Inactive Contextual Menu Item for cherry-picking commits")
1087+
: [NSString stringWithFormat:NSLocalizedString(@"Cherry Pick Commit to “%@", @"Contextual Menu Item to cherry-pick the selected commit on top of HEAD"), headBranchName];
1088+
[items addObject:[NSMenuItem pb_itemWithTitle:cherryPickTitle action:@selector(cherryPick:) enabled:!isOnHeadBranch]];
1089+
1090+
// rebase
1091+
NSString *rebaseTitle = isOnHeadBranch
1092+
? NSLocalizedString(@"Rebase Commit", @"Inactive Contextual Menu Item for rebasing onto commits")
1093+
: [NSString stringWithFormat:NSLocalizedString(@"Rebase “%@” onto Commit", @"Contextual Menu Item to rebase the HEAD branch onto the selected commit"), headBranchName];
1094+
[items addObject:[NSMenuItem pb_itemWithTitle:rebaseTitle action:@selector(rebaseHeadBranch:) enabled:!isOnHeadBranch]];
1095+
1096+
// reset
1097+
NSString *resetTitle = NSLocalizedString(@"Reset to commit", @"Contextual Menu Item to reset to the selected ref");
1098+
[items addObject:[NSMenuItem pb_itemWithTitle:resetTitle action:@selector(resetSoft:) enabled:!isHead]];
1099+
}
1100+
1101+
for (NSMenuItem *item in items) {
1102+
if (!item.representedObject) {
1103+
item.representedObject = isSingleCommitSelection ? firstCommit : commits;
1104+
}
1105+
}
1106+
1107+
return items;
1108+
}
1109+
1110+
@end
1111+

Classes/Controllers/PBGitSidebarController.m

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
#import "PBSourceViewItems.h"
1111
#import "PBGitHistoryController.h"
1212
#import "PBGitCommitController.h"
13-
#import "PBRefController.h"
1413
#import "NSOutlineViewExt.h"
1514
#import "PBAddRemoteSheet.h"
1615
#import "PBGitDefaults.h"
@@ -404,7 +403,7 @@ - (void) addMenuItemsForRef:(PBGitRef *)ref toMenu:(NSMenu *)menu
404403
if (!ref)
405404
return;
406405

407-
for (NSMenuItem *menuItem in [superController.historyViewController.refController menuItemsForRef:ref])
406+
for (NSMenuItem *menuItem in [superController.historyViewController menuItemsForRef:ref])
408407
[menu addItem:menuItem];
409408
}
410409

Classes/Controllers/PBGitWindowController.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
@class PBGitRepository;
1717
@class RJModalRepoSheet;
1818
@class PBGitRef;
19+
@class PBGitCommit;
1920
@class PBGitRepositoryDocument;
2021

2122
NS_ASSUME_NONNULL_BEGIN

Classes/Controllers/PBRefController.h

Lines changed: 0 additions & 20 deletions
This file was deleted.

0 commit comments

Comments
 (0)