From 5dc4ee9acff4d0b59d623ed5397c8bbc2b1af9ff Mon Sep 17 00:00:00 2001 From: "Edward B. Smith" Date: Wed, 23 Jan 2019 18:02:45 -0800 Subject: [PATCH 1/9] Update docs. --- README.md | 2 +- xcode-github-app.xcodeproj/project.pbxproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 01c7419..21b0dca 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ I'd been using [Buildasaur](https://github.com/buildasaurs/Buildasaur), a great * A macOS app that monitors your GitHub repos for PRs and creates new Xcode bots for them. * A command line utility that has many of the same functions of the macOS app. * A static library that has interfaces for GitHub and the Xcode CI system. -* An xctest test bundle for testing. +* An xctest bundle for testing. ## Project Goals diff --git a/xcode-github-app.xcodeproj/project.pbxproj b/xcode-github-app.xcodeproj/project.pbxproj index 6a7714d..45f05cb 100644 --- a/xcode-github-app.xcodeproj/project.pbxproj +++ b/xcode-github-app.xcodeproj/project.pbxproj @@ -463,7 +463,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "xcode-github-app/xcode-github-app.entitlements"; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = JDPFH2Q73W; + DEVELOPMENT_TEAM = WRWLR8VRUH; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -482,7 +482,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "xcode-github-app/xcode-github-app.entitlements"; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = JDPFH2Q73W; + DEVELOPMENT_TEAM = WRWLR8VRUH; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", From a92903a0f595177521db40c7ac594b686360a7c8 Mon Sep 17 00:00:00 2001 From: "Edward B. Smith" Date: Wed, 23 Jan 2019 22:15:51 -0800 Subject: [PATCH 2/9] Server permissions. --- README.md | 10 ++-- XcodeGitHub/XGCommand.m | 6 ++- XcodeGitHub/XGSettings.m | 3 +- XcodeGitHub/XGXcodeBot.h | 4 +- XcodeGitHub/XGXcodeBot.m | 53 ++++++++++++------- xcode-github-app/XGAAddServerPanel.m | 2 +- .../XGAPreferencesViewController.m | 20 +++---- .../XGAPreferencesViewController.xib | 34 +++++++++--- xcode-github-app/XGASettings.h | 4 +- xcode-github-app/XGASettings.m | 26 ++++++--- xcode-github-app/XGAStatusViewController.m | 16 ++++-- 11 files changed, 119 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 21b0dca..df6cd81 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ +![](https://img.shields.io/badge/macOS-10.12+-brightgreen.svg) +[![MIT licensed](https://img.shields.io/badge/license-MIT-orange.svg)](https://raw.githubusercontent.com/hyperium/hyper/master/LICENSE) ![](https://img.shields.io/github/tag/E-B-Smith/xcode-github.svg) -[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/hyperium/hyper/master/LICENSE) # xcode-github @@ -19,6 +20,9 @@ I'd been using [Buildasaur](https://github.com/buildasaurs/Buildasaur), a great * A static library that has interfaces for GitHub and the Xcode CI system. * An xctest bundle for testing. +[Project Goals](#ProjectGoals)
+[Getting Started and Usage](#GettingStartedandUsage) + ## Project Goals ### Write a useful test automation tool @@ -29,9 +33,7 @@ I'd been using [Buildasaur](https://github.com/buildasaurs/Buildasaur), a great ### Experiment with XCTest unit tests for libraries -## Getting Started with XCode-Git +## Getting Started and Usage It was much easier to start over in stable Objective-C code. -## Installation and Usage - diff --git a/XcodeGitHub/XGCommand.m b/XcodeGitHub/XGCommand.m index fae30c6..3afeabd 100644 --- a/XcodeGitHub/XGCommand.m +++ b/XcodeGitHub/XGCommand.m @@ -39,7 +39,11 @@ gitHubPullRequestTitle:pr.title error:&error]; if (error) { - BNCLogError(@"Can't create Xcode bot: %@.", error); + if (error.code == -999) { + BNCLogError(@"Can't create Xcode bot, permission error: %@.", error); + } else { + BNCLogError(@"Can't create Xcode bot: %@.", error); + } } return error; } diff --git a/XcodeGitHub/XGSettings.m b/XcodeGitHub/XGSettings.m index 3968e48..5f681a7 100644 --- a/XcodeGitHub/XGSettings.m +++ b/XcodeGitHub/XGSettings.m @@ -9,6 +9,7 @@ */ #import "XGSettings.h" +#import "BNCLog.h" @interface NSMutableDictionary (XG) + (instancetype _Nonnull) mutableDeepCopy:(NSDictionary*)dictionary; @@ -33,7 +34,7 @@ + (instancetype _Nonnull) mutableDeepCopy:(NSDictionary*)dictionary { } exit: - if (error) NSLog(@"Error creating mutable dictionary: %@.", error); + if (error) BNCLogError(@"Error creating mutable dictionary: %@.", error); if (!result) result = [NSMutableDictionary new]; return result; } diff --git a/XcodeGitHub/XGXcodeBot.h b/XcodeGitHub/XGXcodeBot.h index c15ac2d..b4d7c66 100644 --- a/XcodeGitHub/XGXcodeBot.h +++ b/XcodeGitHub/XGXcodeBot.h @@ -100,7 +100,7 @@ NS_ASSUME_NONNULL_BEGIN @property (strong, readonly) NSString*_Nullable name; @property (strong, readonly) NSString*_Nullable botID; -@property (strong, readonly) NSString*_Nonnull serverName; +@property (strong, readonly) XGServer*_Nonnull server; /// @brief Repo Information @@ -129,7 +129,7 @@ NS_ASSUME_NONNULL_BEGIN @return A dictionary with a key of the bot name and value of the bot status. */ + (NSDictionary*_Nullable) botsForServer:(XGServer*)xcodeServer - error:(NSError*__autoreleasing _Nullable*_Nullable)error; + error:(NSError*__autoreleasing _Nullable*_Nullable)error; + (NSString*_Nonnull) botNameFromPRNumber:(NSString*_Nonnull)number title:(NSString*_Nonnull)title; diff --git a/XcodeGitHub/XGXcodeBot.m b/XcodeGitHub/XGXcodeBot.m index 41202dd..7248613 100644 --- a/XcodeGitHub/XGXcodeBot.m +++ b/XcodeGitHub/XGXcodeBot.m @@ -254,11 +254,11 @@ - (NSURL*) integrationLogURL { @implementation XGXcodeBot -- (instancetype) initWithServerName:(NSString *)serverName dictionary:(NSDictionary *)dictionary { +- (instancetype) initWithServer:(XGServer*)server dictionary:(NSDictionary *)dictionary { self = [super init]; if (!self) return self; - _serverName = [serverName copy]; + _server = server; _dictionary = dictionary; _name = _dictionary[@"name"]; _botID = _dictionary[@"_id"]; @@ -299,7 +299,8 @@ - (instancetype) initWithServerName:(NSString *)serverName dictionary:(NSDiction if ([_repoName hasSuffix:@".git"]) { _repoName = [_repoName substringWithRange:NSMakeRange(0, _repoName.length-4)]; } - NSDictionary*locations = _dictionary[@"configuration"][@"sourceControlBlueprint"][@"DVTSourceControlWorkspaceBlueprintLocationsKey"]; + NSDictionary*locations = + _dictionary[@"configuration"][@"sourceControlBlueprint"][@"DVTSourceControlWorkspaceBlueprintLocationsKey"]; for (NSDictionary*location in locations.objectEnumerator) { _branch = location[@"DVTSourceControlBranchIdentifierKey"]; } @@ -377,7 +378,7 @@ + (NSString*) botNameFromPRNumber:(NSString *)number title:(NSString *)title { bots = [NSMutableDictionary new]; for (NSDictionary *d in results) { - XGXcodeBot *bot = [[XGXcodeBot alloc] initWithServerName:xcodeServer.server dictionary:d]; + XGXcodeBot *bot = [[XGXcodeBot alloc] initWithServer:xcodeServer dictionary:d]; if (bot && bot.name) { bots[bot.name] = bot; } @@ -406,7 +407,7 @@ - (XGXcodeBotStatus*_Nonnull) status { NSString *statusString = [NSString stringWithFormat: @"https://%@:20343/api/bots/%@/integrations?last=1", - self.serverName, self.botID]; + self.server.server, self.botID]; NSURL *statusURL = [NSURL URLWithString:statusString]; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); @@ -415,6 +416,8 @@ - (XGXcodeBotStatus*_Nonnull) status { getOperationWithURL:statusURL completion:^(BNCNetworkOperation *operation) { dispatch_semaphore_signal(semaphore); }]; + if (self.server.user.length > 0) + [operation setUser:self.server.user password:self.server.password]; [operation start]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); @@ -433,12 +436,12 @@ - (XGXcodeBotStatus*_Nonnull) status { NSArray *a = response[@"results"]; if ([a isKindOfClass:NSArray.class]) { if (a.count >= 1) - status = [[XGXcodeBotStatus alloc] initWithServerName:self.serverName dictionary:a[0]]; + status = [[XGXcodeBotStatus alloc] initWithServerName:self.server.server dictionary:a[0]]; else { - status = [[XGXcodeBotStatus alloc] initWithServerName:self.serverName dictionary:nil]; + status = [[XGXcodeBotStatus alloc] initWithServerName:self.server.server dictionary:nil]; status.botID = self.botID; status.botName = self.name; - status.serverName = self.serverName; + status.serverName = self.server.server; status.integrationNumber = [NSNumber numberWithInteger:0]; status.currentStep = @"no integrations"; status.result = @"unknown"; @@ -453,7 +456,7 @@ - (XGXcodeBotStatus*_Nonnull) status { } exit: - if (!status) status = [[XGXcodeBotStatus alloc] initWithServerName:self.serverName dictionary:nil]; + if (!status) status = [[XGXcodeBotStatus alloc] initWithServerName:self.server.server dictionary:nil]; if (localError) status.error = localError; return status; } @@ -461,7 +464,7 @@ - (XGXcodeBotStatus*_Nonnull) status { - (NSError*_Nullable) deleteBot { NSError *localError = nil; NSString *string = [NSString stringWithFormat: - @"https://%@:20343/api/bots/%@", self.serverName, self.botID]; + @"https://%@:20343/api/bots/%@", self.server.server, self.botID]; NSURL *URL = [NSURL URLWithString:string]; if (!URL) { localError = @@ -469,10 +472,10 @@ - (NSError*_Nullable) deleteBot { code:NSURLErrorBadURL userInfo:@{ NSLocalizedDescriptionKey: - [NSString stringWithFormat:@"Bad server name '%@'.", self.serverName] + [NSString stringWithFormat:@"Bad server name '%@'.", self.server.server] } ]; - BNCLogError(@"Bad server name '%@'.", self.serverName); + BNCLogError(@"Bad server name '%@'.", self.server.server); return localError; } @@ -484,6 +487,8 @@ - (NSError*_Nullable) deleteBot { dispatch_semaphore_signal(semaphore); }]; operation.request.HTTPMethod = @"DELETE"; + if (self.server.user.length > 0) + [operation setUser:self.server.user password:self.server.password]; [operation start]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); if (operation.error) return operation.error; @@ -508,7 +513,7 @@ - (XGXcodeBot*_Nullable) duplicateBotWithNewName:(NSString*_Nonnull)newBotName { NSString *string = [NSString stringWithFormat:@"https://%@:20343/api/bots/%@/duplicate", - self.serverName, + self.server.server, self.botID]; NSURL *URL = [NSURL URLWithString:string]; if (!URL) { @@ -517,10 +522,10 @@ - (XGXcodeBot*_Nullable) duplicateBotWithNewName:(NSString*_Nonnull)newBotName code:NSURLErrorBadURL userInfo:@{ NSLocalizedDescriptionKey: - [NSString stringWithFormat:@"Bad server name '%@'.", self.serverName] + [NSString stringWithFormat:@"Bad server name '%@'.", self.server.server] } ]; - BNCLogError(@"Bad server name '%@'.", self.serverName); + BNCLogError(@"Bad server name '%@'.", self.server.server); goto exit; } @@ -545,7 +550,11 @@ - (XGXcodeBot*_Nullable) duplicateBotWithNewName:(NSString*_Nonnull)newBotName dictionary[@"pullRequestTitle"] = pullRequestTitle; NSData *data = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:&localError]; - if (localError) goto exit; + if (!data) { + localError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSKeyValueValidationError + userInfo:@{ NSLocalizedDescriptionKey: @"Can't create bot dictionary."}]; + } + if (localError || !data) goto exit; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); BNCNetworkOperation *operation = @@ -556,6 +565,8 @@ - (XGXcodeBot*_Nullable) duplicateBotWithNewName:(NSString*_Nonnull)newBotName completion:^(BNCNetworkOperation *operation) { dispatch_semaphore_signal(semaphore); }]; + if (self.server.user.length > 0) + [operation setUser:self.server.user password:self.server.password]; [operation start]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); if (operation.error) { @@ -574,7 +585,7 @@ - (XGXcodeBot*_Nullable) duplicateBotWithNewName:(NSString*_Nonnull)newBotName [operation deserializeJSONResponseData]; NSDictionary *d = (id) operation.responseData; if ([d isKindOfClass:NSDictionary.class]) { - bot = [[XGXcodeBot alloc] initWithServerName:self.serverName dictionary:d]; + bot = [[XGXcodeBot alloc] initWithServer:self.server dictionary:d]; if (bot) { [bot startIntegration]; goto exit; @@ -594,7 +605,7 @@ - (XGXcodeBot*_Nullable) duplicateBotWithNewName:(NSString*_Nonnull)newBotName - (NSError*) startIntegration { NSError *localError = nil; NSString *string = [NSString stringWithFormat: - @"https://%@:20343/api/bots/%@/integrations", self.serverName, self.botID]; + @"https://%@:20343/api/bots/%@/integrations", self.server.server, self.botID]; NSURL *URL = [NSURL URLWithString:string]; if (!URL) { localError = @@ -602,10 +613,10 @@ - (NSError*) startIntegration { code:NSURLErrorBadURL userInfo:@{ NSLocalizedDescriptionKey: - [NSString stringWithFormat:@"Bad server name '%@'.", self.serverName] + [NSString stringWithFormat:@"Bad server name '%@'.", self.server.server] } ]; - BNCLogError(@"Bad server name '%@'.", self.serverName); + BNCLogError(@"Bad server name '%@'.", self.server.server); return localError; } @@ -621,6 +632,8 @@ - (NSError*) startIntegration { completion:^(BNCNetworkOperation *operation) { dispatch_semaphore_signal(semaphore); }]; + if (self.server.user.length > 0) + [operation setUser:self.server.user password:self.server.password]; [operation start]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); if (operation.error) return operation.error; diff --git a/xcode-github-app/XGAAddServerPanel.m b/xcode-github-app/XGAAddServerPanel.m index bd38a57..0f6cdf0 100644 --- a/xcode-github-app/XGAAddServerPanel.m +++ b/xcode-github-app/XGAAddServerPanel.m @@ -145,7 +145,7 @@ - (void) updateNetworkSpinner { - (void) restartNetworkLookup { @synchronized(self) { - NSLog(@"restartNetworkLookup"); + //NSLog(@"restartNetworkLookup"); [self stopNetworkLookup]; self.networkLookupDate = [NSDate dateWithTimeInterval:kNetworkRefreshInterval sinceDate:[NSDate date]]; self.activityWheel.doubleValue = 0.0; diff --git a/xcode-github-app/XGAPreferencesViewController.m b/xcode-github-app/XGAPreferencesViewController.m index 8dd1519..7944ec2 100644 --- a/xcode-github-app/XGAPreferencesViewController.m +++ b/xcode-github-app/XGAPreferencesViewController.m @@ -14,7 +14,7 @@ @interface XGAPreferencesViewController () @property (strong) IBOutlet XGASettings*settings; -@property (strong) IBOutlet NSArrayController*serverArrayController; +@property (strong) IBOutlet NSDictionaryController*serverDictionaryController; @property (strong) IBOutlet NSTextField*gitHubTokenTextField; @property (strong) IBOutlet NSButton *removeButton; @property (strong) IBOutlet NSTableView *tableView; @@ -40,7 +40,7 @@ - (void)awakeFromNib { if (self.settings) return; self.settings = [XGASettings shared]; self.removeButton.enabled = NO; - self.serverArrayController.content = self.settings.servers; + //self.serverDictionaryController.content = self.settings.servers; self.refreshTimeFormatter.multiplier = @(1.0/60.0); self.refreshTimeFormatter.minimumFractionDigits = 0; self.refreshTimeFormatter.maximumFractionDigits = 0; @@ -53,7 +53,8 @@ - (IBAction)valueChanged:(id)sender { - (void)tableViewSelectionDidChange:(NSNotification *)notification { NSInteger idx = self.tableView.selectedRow; - self.removeButton.enabled = (idx >= 0 && idx < [self.serverArrayController.arrangedObjects count]); + self.removeButton.enabled = + (idx >= 0 && idx < [self.serverDictionaryController.arrangedObjects count]); } - (IBAction)addServerAction:(id)sender { @@ -62,8 +63,8 @@ - (IBAction)addServerAction:(id)sender { - (IBAction)serverDoubleAction:(id)sender { NSInteger idx = self.tableView.selectedRow; - if (idx >= 0 && idx < [self.serverArrayController.arrangedObjects count]) { - XGAServer*server = [self.serverArrayController.arrangedObjects objectAtIndex:idx]; + if (idx >= 0 && idx < [self.serverDictionaryController.arrangedObjects count]) { + XGAServer*server = [self.serverDictionaryController.arrangedObjects objectAtIndex:idx]; [self showServer:server]; } } @@ -74,17 +75,18 @@ - (void) showServer:(XGAServer*)server { [self.window beginSheet:self.addServerPanel completionHandler:^(NSModalResponse returnCode) { __auto_type result = self.addServerPanel.server; if (returnCode == NSModalResponseOK && result.server.length) { - [self.settings.servers addObject:result]; + self.settings.servers[result.server] = result; [self.settings save]; } self.addServerPanel = nil; + [self.tableView reloadData]; }]; } - (IBAction)removeServerAction:(id)sender { NSInteger idx = self.tableView.selectedRow; - if (idx >= 0 && idx < [self.serverArrayController.arrangedObjects count]) { - [self.serverArrayController removeObjectAtArrangedObjectIndex:idx]; + if (idx >= 0 && idx < [self.serverDictionaryController.arrangedObjects count]) { + [self.serverDictionaryController removeObjectAtArrangedObjectIndex:idx]; [self.settings save]; [self tableViewSelectionDidChange:nil]; } @@ -93,7 +95,7 @@ - (IBAction)removeServerAction:(id)sender { - (IBAction) resetSettingsAction:(id)sender { [self.settings clear]; [self.settings save]; - self.serverArrayController.content = self.settings.servers; + self.serverDictionaryController.content = self.settings.servers; } @end diff --git a/xcode-github-app/XGAPreferencesViewController.xib b/xcode-github-app/XGAPreferencesViewController.xib index 1d75b15..fe37531 100644 --- a/xcode-github-app/XGAPreferencesViewController.xib +++ b/xcode-github-app/XGAPreferencesViewController.xib @@ -1,8 +1,8 @@ - + - + @@ -11,7 +11,7 @@ - + @@ -110,6 +110,14 @@ + + + + + + + + @@ -150,22 +158,30 @@ + + + + + + + + - + - diff --git a/xcode-github-app/XGASettings.h b/xcode-github-app/XGASettings.h index feac22f..73ef0cd 100644 --- a/xcode-github-app/XGASettings.h +++ b/xcode-github-app/XGASettings.h @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN FOUNDATION_EXPORT NSString*_Nonnull XGACleanString(NSString*_Nullable string); -@interface XGAServer : XGServer +@interface XGAServer : XGServer @end #pragma mark XGGitHubSyncTask @@ -37,7 +37,7 @@ FOUNDATION_EXPORT NSString*_Nonnull XGACleanString(NSString*_Nullable string); @property (assign) BOOL showDebugMessages; @property (assign) NSTimeInterval refreshSeconds; @property (copy) NSString*gitHubToken; -@property (strong, null_resettable) NSMutableArray*servers; +@property (strong, null_resettable) NSMutableDictionary*servers; @property (strong, null_resettable) NSMutableArray*gitHubSyncTasks; @end diff --git a/xcode-github-app/XGASettings.m b/xcode-github-app/XGASettings.m index 6dfa2a7..05f16e3 100644 --- a/xcode-github-app/XGASettings.m +++ b/xcode-github-app/XGASettings.m @@ -67,6 +67,14 @@ - (void) encodeWithCoder:(NSCoder *)aCoder { } } +- (id) copyWithZone:(NSZone *)zone { + XGAServer*server = [[XGAServer alloc] init]; + server.server = self.server; + server.user = self.user; + server.password = self.password; + return server; +} + @end #pragma mark - XGAGitHubSyncTask @@ -77,7 +85,7 @@ @implementation XGAGitHubSyncTask #pragma mark - XGASettings @interface XGASettings () { - NSMutableArray*_servers; + NSMutableDictionary*_servers; NSMutableArray*_gitHubSyncTasks; } @end @@ -127,14 +135,14 @@ - (void) setGitHubToken:(NSString *)token { } } -- (NSMutableArray*) servers { +- (NSMutableDictionary*) servers { @synchronized (self) { - if (_servers == nil) _servers = [NSMutableArray new]; + if (_servers == nil) _servers = [NSMutableDictionary new]; return _servers; } } -- (void) setServers:(NSMutableArray*)servers_ { +- (void) setServers:(NSMutableDictionary*)servers_ { @synchronized (self) { _servers = servers_; } @@ -200,11 +208,15 @@ - (void) validate { // Assure that servers are unique: NSMutableDictionary*d = NSMutableDictionary.new; - for (XGAServer*server in self.servers) { - if (server.server.length) d[server.server] = server; + for (XGAServer*server in self.servers.objectEnumerator) { + NSString*name = XGACleanString(server.server); + if (name.length) { + server.server = name; + d[name] = server; + } } [self.servers removeAllObjects]; - [self.servers addObjectsFromArray:d.allValues]; + [self.servers addEntriesFromDictionary:d]; // Assure that tasks are unique: [d removeAllObjects]; diff --git a/xcode-github-app/XGAStatusViewController.m b/xcode-github-app/XGAStatusViewController.m index 57a7fb0..4064c83 100644 --- a/xcode-github-app/XGAStatusViewController.m +++ b/xcode-github-app/XGAStatusViewController.m @@ -146,7 +146,7 @@ - (IBAction) monitorRepo:(id)sender { } else { __auto_type task = [XGAGitHubSyncTask new]; - task.xcodeServer = item.bot.serverName; + task.xcodeServer = item.bot.server.server; task.botNameForTemplate = item.botName; [[XGASettings shared].gitHubSyncTasks addObject:task]; @@ -336,7 +336,7 @@ - (void) updateStatus { BNCLogDebug(@"Start updateStatus."); BNCPerformBlockOnMainThreadAsync(^{ self.statusTextField.stringValue = @""; }); NSMutableDictionary*statusServers = [NSMutableDictionary new]; - for (XGAServer*server in XGASettings.shared.servers) { + for (XGAServer*server in XGASettings.shared.servers.objectEnumerator) { if (server.server.length > 0) statusServers[server.server] = server; } @@ -364,10 +364,16 @@ - (void) updateStatus { - (void) updateSyncBots:(XGAGitHubSyncTask*)syncTask { NSError*error = nil; - if (syncTask.xcodeServer.length && - syncTask.botNameForTemplate.length) { + if (syncTask.xcodeServer.length && syncTask.botNameForTemplate.length) { + XGAServer*server = [XGASettings shared].servers[syncTask.xcodeServer]; + if (!server) { + server = [XGAServer new]; + server.server = syncTask.xcodeServer; + } XGCommandOptions*options = [XGCommandOptions new]; - options.xcodeServerName = syncTask.xcodeServer; + options.xcodeServerName = server.server; + options.xcodeServerUser = server.user; + options.xcodeServerPassword = server.password; options.templateBotName = syncTask.botNameForTemplate; options.githubAuthToken = XGASettings.shared.gitHubToken; options.dryRun = XGASettings.shared.dryRun; From 9bacd2c5507eaba41d593b1e58ee959009a3f583 Mon Sep 17 00:00:00 2001 From: "Edward B. Smith" Date: Thu, 24 Jan 2019 03:22:31 -0800 Subject: [PATCH 3/9] * Added start/cancel integration menu item. * Fixed adding a server in the preferences panel. * Cleaned up menu text. * Moved 'Show debug messages' option from preferences to the log window. --- Vendor/Branch/BNCNetworkService.h | 2 +- Vendor/Branch/BNCNetworkService.m | 1 - Vendor/Branch/BNCThreads.h | 4 ++ XcodeGitHub/XGXcodeBot.h | 1 + XcodeGitHub/XGXcodeBot.m | 45 +++++++++++++++ xcode-github-app/Base.lproj/Main.storyboard | 16 +++--- xcode-github-app/Info.plist | 2 +- xcode-github-app/XGALogViewController.m | 3 + xcode-github-app/XGALogViewController.xib | 55 ++++++++++++++----- .../XGAPreferencesViewController.m | 7 ++- .../XGAPreferencesViewController.xib | 24 +------- xcode-github-app/XGAStatusViewController.m | 28 ++++++++++ xcode-github-app/XGAStatusViewController.xib | 10 +++- xcode-github-app/xcode-github-app.md | 3 +- 14 files changed, 150 insertions(+), 51 deletions(-) diff --git a/Vendor/Branch/BNCNetworkService.h b/Vendor/Branch/BNCNetworkService.h index 8534a6a..c860fb4 100644 --- a/Vendor/Branch/BNCNetworkService.h +++ b/Vendor/Branch/BNCNetworkService.h @@ -45,7 +45,7 @@ NS_ASSUME_NONNULL_BEGIN completion:(void (^)(BNCNetworkOperation*operation))completion; - (BNCNetworkOperation*) postOperationWithURL:(NSURL *)URL - JSONData:(id)dictionaryOrArray + JSONData:(id _Nullable)dictionaryOrArray completion:(void (^)(BNCNetworkOperation*operation))completion; - (NSError*_Nullable) pinSessionToPublicSecKeyRefs:(NSArray/***/*)publicKeys; diff --git a/Vendor/Branch/BNCNetworkService.m b/Vendor/Branch/BNCNetworkService.m index 0bc000b..fcb2d30 100644 --- a/Vendor/Branch/BNCNetworkService.m +++ b/Vendor/Branch/BNCNetworkService.m @@ -172,7 +172,6 @@ - (void) setAnySSLCertHosts:(NSMutableSet*)anySSLCertHosts_ { } - (BNCNetworkOperation*) networkOperationWithURL:(NSURL*)URL { - BNCNetworkOperation *operation = [BNCNetworkOperation new]; operation.request = [[NSMutableURLRequest alloc] diff --git a/Vendor/Branch/BNCThreads.h b/Vendor/Branch/BNCThreads.h index 95d2351..7f9b9be 100644 --- a/Vendor/Branch/BNCThreads.h +++ b/Vendor/Branch/BNCThreads.h @@ -38,6 +38,10 @@ static inline void BNCPerformBlockOnMainThreadSync(dispatch_block_t block) { } } +static inline void BNCPerformBlockAsync(dispatch_block_t block) { + dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), block); +} + static inline void BNCSleepForTimeInterval(NSTimeInterval seconds) { double secPart = trunc(seconds); double nanoPart = trunc((seconds - secPart) * ((double)NSEC_PER_SEC)); diff --git a/XcodeGitHub/XGXcodeBot.h b/XcodeGitHub/XGXcodeBot.h index b4d7c66..8388fca 100644 --- a/XcodeGitHub/XGXcodeBot.h +++ b/XcodeGitHub/XGXcodeBot.h @@ -140,6 +140,7 @@ NS_ASSUME_NONNULL_BEGIN error:(NSError*__autoreleasing _Nullable*_Nullable)error; - (NSError*_Nullable) startIntegration; +- (NSError*_Nullable) cancelIntegrationID:(NSString*)integrationID; - (XGXcodeBotStatus*_Nonnull) status; - (NSError*_Nullable) deleteBot; @end diff --git a/XcodeGitHub/XGXcodeBot.m b/XcodeGitHub/XGXcodeBot.m index 7248613..4ec4996 100644 --- a/XcodeGitHub/XGXcodeBot.m +++ b/XcodeGitHub/XGXcodeBot.m @@ -648,4 +648,49 @@ - (NSError*) startIntegration { return nil; } +- (NSError*_Nullable) cancelIntegrationID:(NSString*)integrationID { + NSError *localError = nil; + NSString *string = [NSString stringWithFormat: + @"https://%@:20343/api/integrations/%@/cancel", self.server.server, integrationID]; + NSURL *URL = [NSURL URLWithString:string]; + if (!URL) { + localError = + [NSError errorWithDomain:NSNetServicesErrorDomain + code:NSURLErrorBadURL + userInfo:@{ + NSLocalizedDescriptionKey: + [NSString stringWithFormat:@"Bad server name '%@'.", self.server.server] + } + ]; + BNCLogError(@"Bad server name '%@'.", self.server.server); + return localError; + } + + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + BNCNetworkOperation *operation = + [[BNCNetworkService shared] + postOperationWithURL:URL + JSONData:nil + completion:^(BNCNetworkOperation *operation) { + dispatch_semaphore_signal(semaphore); + }]; + if (self.server.user.length > 0) + [operation setUser:self.server.user password:self.server.password]; + [operation start]; + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + if (operation.error) return operation.error; + + if (operation.HTTPStatusCode != 204) { + [operation deserializeJSONResponseData]; + localError = [NSError errorWithDomain:NSNetServicesErrorDomain + code:NSNetServicesInvalidError userInfo:@{NSLocalizedDescriptionKey: + [NSString stringWithFormat:@"HTTP Status %ld: %@", + (long) operation.HTTPStatusCode, + (id) operation.responseData] }]; + return localError; + } + + return nil; +} + @end diff --git a/xcode-github-app/Base.lproj/Main.storyboard b/xcode-github-app/Base.lproj/Main.storyboard index 46b43a6..09da71e 100644 --- a/xcode-github-app/Base.lproj/Main.storyboard +++ b/xcode-github-app/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -11,11 +11,11 @@ - + - + - + @@ -33,7 +33,7 @@ - + @@ -51,7 +51,7 @@ - + @@ -369,7 +369,7 @@ - + diff --git a/xcode-github-app/Info.plist b/xcode-github-app/Info.plist index 2eab773..b569e4d 100644 --- a/xcode-github-app/Info.plist +++ b/xcode-github-app/Info.plist @@ -30,7 +30,7 @@ NSHumanReadableCopyright - Copyright © 2018 Branch. All rights reserved. + Copyright © 2018–2019 Branch. All rights reserved. NSMainStoryboardFile Main NSPrincipalClass diff --git a/xcode-github-app/XGALogViewController.m b/xcode-github-app/XGALogViewController.m index cd8ca88..f6adbd4 100644 --- a/xcode-github-app/XGALogViewController.m +++ b/xcode-github-app/XGALogViewController.m @@ -36,6 +36,7 @@ + (NSImage*) imageForLogLevel:(BNCLogLevel)level; @property (strong) NSDateFormatter*dateFormatter; @property (strong) IBOutlet NSArrayController *arrayController; @property (weak) IBOutlet NSTableView*tableView; +@property (strong) IBOutlet XGASettings*settings; @property (strong) XGAStatusPopover*statusPopover; @end @@ -84,6 +85,7 @@ + (void) startLog { + (instancetype) new { XGALogViewController*controller = [[XGALogViewController alloc] init]; + controller.settings = XGASettings.shared; [[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self) owner:controller @@ -179,6 +181,7 @@ - (void) updateMessageFilter { self.arrayController.filterPredicate = [NSPredicate predicateWithFormat:@"logLevel > 2"]; } + [self.settings save]; } - (void)logUpdatedNotification:(NSNotification*)notification { diff --git a/xcode-github-app/XGALogViewController.xib b/xcode-github-app/XGALogViewController.xib index 2a89111..d7f82b7 100644 --- a/xcode-github-app/XGALogViewController.xib +++ b/xcode-github-app/XGALogViewController.xib @@ -1,8 +1,8 @@ - + - + @@ -17,7 +17,7 @@ - + @@ -27,20 +27,46 @@ + + + + + + + + + + + - + - + - + - + @@ -81,7 +107,7 @@ - + @@ -121,7 +147,7 @@ - + @@ -172,11 +198,11 @@ - + - + + + - + diff --git a/xcode-github-app/XGAPreferencesViewController.m b/xcode-github-app/XGAPreferencesViewController.m index 7944ec2..47abe13 100644 --- a/xcode-github-app/XGAPreferencesViewController.m +++ b/xcode-github-app/XGAPreferencesViewController.m @@ -75,11 +75,12 @@ - (void) showServer:(XGAServer*)server { [self.window beginSheet:self.addServerPanel completionHandler:^(NSModalResponse returnCode) { __auto_type result = self.addServerPanel.server; if (returnCode == NSModalResponseOK && result.server.length) { - self.settings.servers[result.server] = result; - [self.settings save]; + NSDictionaryControllerKeyValuePair*pair = [self.serverDictionaryController newObject]; + pair.key = result.server; + pair.value = result; + [self.serverDictionaryController addObject:pair]; } self.addServerPanel = nil; - [self.tableView reloadData]; }]; } diff --git a/xcode-github-app/XGAPreferencesViewController.xib b/xcode-github-app/XGAPreferencesViewController.xib index fe37531..31ba7fb 100644 --- a/xcode-github-app/XGAPreferencesViewController.xib +++ b/xcode-github-app/XGAPreferencesViewController.xib @@ -279,23 +279,6 @@ - - - - + - + diff --git a/xcode-github-app/XGAStatusViewController.m b/xcode-github-app/XGAStatusViewController.m index 4064c83..a6b7a06 100644 --- a/xcode-github-app/XGAStatusViewController.m +++ b/xcode-github-app/XGAStatusViewController.m @@ -213,6 +213,24 @@ - (IBAction) downloadAssets:(id)sender { [[NSWorkspace sharedWorkspace] openURL:URL]; } +- (IBAction)startStopIntegration:(id)sender { + XGAStatusViewItem*item = [self selectedTableItem]; + if (!item) return; + + BNCPerformBlockAsync(^ { + if (item.botStatus.integrationID != nil && + ![item.botStatus.currentStep isEqualToString:@"completed"]) { + [item.bot cancelIntegrationID:item.botStatus.integrationID]; + } else { + [item.bot startIntegration]; + } + BNCSleepForTimeInterval(0.5); + BNCPerformBlockOnMainThreadSync(^{ + [self reload:nil]; + }); + }); +} + - (IBAction)reload:(id)sender { [self updateStatusNow]; } @@ -249,6 +267,16 @@ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { } return NO; } + if (menuItem.action == @selector(startStopIntegration:)) { + XGAStatusViewItem*item = [self selectedTableItem]; + if (!item) return NO; + if (item.botStatus.integrationID != nil && + ![item.botStatus.currentStep isEqualToString:@"completed"]) + menuItem.title = @"Cancel Integration"; + else + menuItem.title = @"Start Integration"; + return YES; + } SEL contextMenuItems[] = { @selector(showInfo:), @selector(monitorRepo:), diff --git a/xcode-github-app/XGAStatusViewController.xib b/xcode-github-app/XGAStatusViewController.xib index 271e2fc..7d18af1 100644 --- a/xcode-github-app/XGAStatusViewController.xib +++ b/xcode-github-app/XGAStatusViewController.xib @@ -22,7 +22,7 @@ - + @@ -306,12 +306,20 @@ + + + + + + + + diff --git a/xcode-github-app/xcode-github-app.md b/xcode-github-app/xcode-github-app.md index bfc5848..220e317 100644 --- a/xcode-github-app/xcode-github-app.md +++ b/xcode-github-app/xcode-github-app.md @@ -18,9 +18,10 @@ * [x] On bots, if last integration# != current# then update the status on GitHub. * [x] Clean up code. * [x] Refresh Sec -> Min -* [x] Rename build products: XcodeGitHub.framework, Xcode-GitHub.app, xcode-github (cli tool), Tests +* [x] Rename build products: XcodeGitHub.framework, XcodeGitHub.app, xcode-github (cli tool), Tests * [x] Don't update 'Products' every time, just when making a release with a build script. * [x] Fix testing: fix the project layout, test bundle name, and project bindings. +* [x] Add 'Start/Cancel Integration' menu item. * [ ] Add 'New' menu items: Add new server. Add new Xcode bot? * [ ] Add 'About...' panel. * [ ] Add help. From 53d1a3196025c0e32b8388511eb3b5f11935738e Mon Sep 17 00:00:00 2001 From: "Edward B. Smith" Date: Thu, 24 Jan 2019 04:31:32 -0800 Subject: [PATCH 4/9] * Added About Box credits. * Added 'Add New Server...' option. --- xcode-github-app.xcodeproj/project.pbxproj | 12 ++++++++++++ xcode-github-app/Base.lproj/Credits.rtf | 15 +++++++++++++++ xcode-github-app/Base.lproj/Main.storyboard | 4 ++-- xcode-github-app/XGAAppDelegate.m | 5 +++++ xcode-github-app/XGAPreferencesViewController.h | 5 +++++ xcode-github-app/XGAPreferencesViewController.m | 4 +++- xcode-github-app/XGAStatusViewController.h | 4 ++++ xcode-github-app/xcode-github-app.md | 14 +++++++++----- 8 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 xcode-github-app/Base.lproj/Credits.rtf diff --git a/xcode-github-app.xcodeproj/project.pbxproj b/xcode-github-app.xcodeproj/project.pbxproj index 45f05cb..8f693b9 100644 --- a/xcode-github-app.xcodeproj/project.pbxproj +++ b/xcode-github-app.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 2BB6F4D321F9E3FF001E8693 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 2BB6F4D121F9E3FF001E8693 /* Credits.rtf */; }; 4D12BA0D20A252CE00E5B1DB /* XGALogViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D12BA0B20A252CE00E5B1DB /* XGALogViewController.m */; }; 4D12BA0E20A252CE00E5B1DB /* XGALogViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4D12BA0C20A252CE00E5B1DB /* XGALogViewController.xib */; }; 4D5CC05E2090CAB60042E98B /* BNCKeyChain.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D5CC05D2090CAB60042E98B /* BNCKeyChain.m */; }; @@ -51,6 +52,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 2BB6F4D221F9E3FF001E8693 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = Base; path = Base.lproj/Credits.rtf; sourceTree = ""; }; 4D0DD54420597FE6001721C1 /* xcode-github-app.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = "xcode-github-app.md"; path = "xcode-github-app/xcode-github-app.md"; sourceTree = ""; }; 4D12BA0A20A252CE00E5B1DB /* XGALogViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XGALogViewController.h; sourceTree = ""; }; 4D12BA0B20A252CE00E5B1DB /* XGALogViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XGALogViewController.m; sourceTree = ""; }; @@ -131,6 +133,7 @@ isa = PBXGroup; children = ( 4D7661712057565200216B72 /* Assets.xcassets */, + 2BB6F4D121F9E3FF001E8693 /* Credits.rtf */, 4D7661762057565200216B72 /* Info.plist */, 4D7661772057565300216B72 /* main.m */, 4D7661732057565200216B72 /* Main.storyboard */, @@ -293,6 +296,7 @@ 4D7661722057565200216B72 /* Assets.xcassets in Resources */, 4D8352D120A516A200184259 /* XGAPreferencesViewController.xib in Resources */, 4D6536382150486B000812E1 /* XGAStatusPopover.xib in Resources */, + 2BB6F4D321F9E3FF001E8693 /* Credits.rtf in Resources */, 4D12BA0E20A252CE00E5B1DB /* XGALogViewController.xib in Resources */, 4D8352C620A3F30A00184259 /* XGAStatusViewController.xib in Resources */, 4DAA4667214CBEFF007A39EB /* XGAAddServerPanel.xib in Resources */, @@ -337,6 +341,14 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 2BB6F4D121F9E3FF001E8693 /* Credits.rtf */ = { + isa = PBXVariantGroup; + children = ( + 2BB6F4D221F9E3FF001E8693 /* Base */, + ); + name = Credits.rtf; + sourceTree = ""; + }; 4D7661732057565200216B72 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( diff --git a/xcode-github-app/Base.lproj/Credits.rtf b/xcode-github-app/Base.lproj/Credits.rtf new file mode 100644 index 0000000..505399e --- /dev/null +++ b/xcode-github-app/Base.lproj/Credits.rtf @@ -0,0 +1,15 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf200 +{\fonttbl\f0\fswiss\fcharset0 ArialMT;} +{\colortbl;\red255\green255\blue255;} +{\*\expandedcolortbl;;} +\margl1440\margr1440\vieww10800\viewh8400\viewkind0 +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0 + +\f0\fs24 \cf0 Info, issues, and open source at\ +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0 +{\field{\*\fldinst{HYPERLINK "https://github.com/E-B-Smith/xcode-github"}}{\fldrslt \cf0 https://github.com/E-B-Smith/xcode-github}}\ +\ +Written by Edward Smith\ +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0 +{\field{\*\fldinst{HYPERLINK "https://shimmering.blue"}}{\fldrslt \cf0 https://shimmering.blue}}\ +} \ No newline at end of file diff --git a/xcode-github-app/Base.lproj/Main.storyboard b/xcode-github-app/Base.lproj/Main.storyboard index 09da71e..00d7a93 100644 --- a/xcode-github-app/Base.lproj/Main.storyboard +++ b/xcode-github-app/Base.lproj/Main.storyboard @@ -63,9 +63,9 @@ - + - + diff --git a/xcode-github-app/XGAAppDelegate.m b/xcode-github-app/XGAAppDelegate.m index 39f6e1d..718458a 100644 --- a/xcode-github-app/XGAAppDelegate.m +++ b/xcode-github-app/XGAAppDelegate.m @@ -47,6 +47,11 @@ - (IBAction)showPreferences:(id)sender { [self.preferencesController.window makeKeyAndOrderFront:self]; } +- (IBAction)addNewServer:(id)sender { + [self showPreferences:sender]; + [self.preferencesController addServerAction:sender]; +} + - (BOOL)windowShouldClose:(NSWindow*)window { self.preferencesController = nil; return YES; diff --git a/xcode-github-app/XGAPreferencesViewController.h b/xcode-github-app/XGAPreferencesViewController.h index e019799..6a42fdb 100644 --- a/xcode-github-app/XGAPreferencesViewController.h +++ b/xcode-github-app/XGAPreferencesViewController.h @@ -10,7 +10,12 @@ #import +NS_ASSUME_NONNULL_BEGIN + @interface XGAPreferencesViewController : NSViewController + (instancetype) new; +- (IBAction)addServerAction:(id _Nullable)sender; @property (strong) IBOutlet NSWindow*window; @end + +NS_ASSUME_NONNULL_END diff --git a/xcode-github-app/XGAPreferencesViewController.m b/xcode-github-app/XGAPreferencesViewController.m index 47abe13..1be93f3 100644 --- a/xcode-github-app/XGAPreferencesViewController.m +++ b/xcode-github-app/XGAPreferencesViewController.m @@ -74,7 +74,9 @@ - (void) showServer:(XGAServer*)server { self.addServerPanel = [[XGAAddServerPanel alloc] initWithServer:server]; [self.window beginSheet:self.addServerPanel completionHandler:^(NSModalResponse returnCode) { __auto_type result = self.addServerPanel.server; - if (returnCode == NSModalResponseOK && result.server.length) { + if (returnCode == NSModalResponseOK && + result.server.length && + self.settings.servers[result.server] == nil) { NSDictionaryControllerKeyValuePair*pair = [self.serverDictionaryController newObject]; pair.key = result.server; pair.value = result; diff --git a/xcode-github-app/XGAStatusViewController.h b/xcode-github-app/XGAStatusViewController.h index 3053ae1..01d043f 100644 --- a/xcode-github-app/XGAStatusViewController.h +++ b/xcode-github-app/XGAStatusViewController.h @@ -10,7 +10,11 @@ #import +NS_ASSUME_NONNULL_BEGIN + @interface XGAStatusViewController : NSViewController + (instancetype) new; @property (strong) IBOutlet NSWindow*window; @end + +NS_ASSUME_NONNULL_END diff --git a/xcode-github-app/xcode-github-app.md b/xcode-github-app/xcode-github-app.md index 220e317..05e05bb 100644 --- a/xcode-github-app/xcode-github-app.md +++ b/xcode-github-app/xcode-github-app.md @@ -22,12 +22,13 @@ * [x] Don't update 'Products' every time, just when making a release with a build script. * [x] Fix testing: fix the project layout, test bundle name, and project bindings. * [x] Add 'Start/Cancel Integration' menu item. -* [ ] Add 'New' menu items: Add new server. Add new Xcode bot? -* [ ] Add 'About...' panel. +* [x] Add 'New' menu items: Add new server. Add new Xcode bot? +* [x] Add 'About...' panel. * [ ] Add help. ``` - ## Help +## Help + * About - Problems it solves. - What it does. @@ -75,17 +76,20 @@ * Maybe: Button: Start / Stop Button * Maybe: Button: Download logs -- or - +-- or -- #### Tree with Columns +``` | Server | Repo | PRs | Status | Server +------ Repo - +----- PR -- Status + +----- PR -- Status +``` ### Settings Window + * Add server * Add GitHub Token * Dry run, show debug messages, refresh time From 6fddc53829df0f3f7accf9e2653c2eac563fb5c3 Mon Sep 17 00:00:00 2001 From: "Edward B. Smith" Date: Thu, 24 Jan 2019 04:47:26 -0800 Subject: [PATCH 5/9] On start, if no servers defined, prompt to add one. --- xcode-github-app/XGAAppDelegate.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xcode-github-app/XGAAppDelegate.m b/xcode-github-app/XGAAppDelegate.m index 718458a..2d92f8f 100644 --- a/xcode-github-app/XGAAppDelegate.m +++ b/xcode-github-app/XGAAppDelegate.m @@ -12,6 +12,7 @@ #import "XGALogViewController.h" #import "XGAStatusViewController.h" #import "XGAPreferencesViewController.h" +#import "XGASettings.h" #import @interface XGAAppDelegate () @@ -57,4 +58,9 @@ - (BOOL)windowShouldClose:(NSWindow*)window { return YES; } +- (void)applicationDidFinishLaunching:(NSNotification *)notification { + if (XGASettings.shared.servers.count == 0 && XGASettings.shared.gitHubSyncTasks.count == 0) + [self addNewServer:nil]; +} + @end From e49ec8b2d3c1c56c1280bee6cad68d8c5746aa20 Mon Sep 17 00:00:00 2001 From: "Edward B. Smith" Date: Fri, 25 Jan 2019 22:41:38 -0800 Subject: [PATCH 6/9] * Updated README documentation. * Zipped contents of the Products directory. * Cleaned up some analyzer warnings. * Fixed some 'Add server' dialog problems in the preferences. --- Art/StatusWindow.png | Bin 0 -> 1142481 bytes Products/XcodeGitHub.app.zip | Bin 0 -> 2179095 bytes Products/XcodeGitHub.app/Contents/Info.plist | 61 ----- .../Contents/MacOS/XcodeGitHub | Bin 513680 -> 0 bytes Products/XcodeGitHub.app/Contents/PkgInfo | 1 - .../Contents/Resources/AppIcon.icns | Bin 990504 -> 0 bytes .../Contents/Resources/Assets.car | Bin 967432 -> 0 bytes .../Base.lproj/Main.storyboardc/Info.plist | Bin 163 -> 0 bytes .../Base.lproj/Main.storyboardc/MainMenu.nib | Bin 21614 -> 0 bytes .../Contents/Resources/XGAAddServerPanel.nib | Bin 30070 -> 0 bytes .../Resources/XGALogViewController.nib | Bin 37777 -> 0 bytes .../XGAPreferencesViewController.nib | Bin 43329 -> 0 bytes .../Contents/Resources/XGAStatusPopover.nib | Bin 12765 -> 0 bytes .../Resources/XGAStatusViewController.nib | Bin 56829 -> 0 bytes .../Contents/_CodeSignature/CodeResources | 252 ------------------ Products/XcodeGitHub.framework.zip | Bin 0 -> 199761 bytes Products/XcodeGitHub.framework/Headers | 1 - Products/XcodeGitHub.framework/Modules | 1 - .../Versions/A/Headers/APFormattedString.h | 45 ---- .../Versions/A/Headers/BNCLog.h | 242 ----------------- .../Versions/A/Headers/BNCNetworkService.h | 60 ----- .../Versions/A/Headers/XGCommand.h | 32 --- .../Versions/A/Headers/XGCommandOptions.h | 33 --- .../Versions/A/Headers/XGGitHubPullRequest.h | 68 ----- .../Versions/A/Headers/XGXcodeBot.h | 147 ---------- .../Versions/A/Headers/XcodeGitHub.h | 20 -- .../Versions/A/Modules/module.modulemap | 5 - .../Versions/A/XcodeGitHub | Bin 536168 -> 0 bytes .../XcodeGitHub.framework/Versions/Current | 1 - Products/XcodeGitHub.framework/XcodeGitHub | 1 - Products/xcode-github | Bin 302352 -> 306896 bytes README.md | 138 +++++++++- Vendor/Branch/BNCLog.h | 27 +- Vendor/Branch/BNCLog.m | 2 +- Vendor/Branch/BNCNetworkService.h | 2 +- XcodeGitHub/XGCommandOptions.m | 2 +- build-release | 15 ++ xcode-github-app/XGAAddServerPanel.m | 53 ++-- xcode-github-app/XGANetworkServiceBrowser.m | 3 +- .../XGAPreferencesViewController.m | 4 +- xcode-github-app/XGASettings.m | 6 +- xcode-github-app/XGAStatusViewController.m | 10 +- xcode-github-app/XGAStatusViewItem.h | 2 +- xcode-github-app/XGAStatusViewItem.m | 2 +- xcode-github-cli.xcodeproj/project.pbxproj | 4 +- 45 files changed, 220 insertions(+), 1020 deletions(-) create mode 100644 Art/StatusWindow.png create mode 100644 Products/XcodeGitHub.app.zip delete mode 100644 Products/XcodeGitHub.app/Contents/Info.plist delete mode 100755 Products/XcodeGitHub.app/Contents/MacOS/XcodeGitHub delete mode 100644 Products/XcodeGitHub.app/Contents/PkgInfo delete mode 100644 Products/XcodeGitHub.app/Contents/Resources/AppIcon.icns delete mode 100644 Products/XcodeGitHub.app/Contents/Resources/Assets.car delete mode 100644 Products/XcodeGitHub.app/Contents/Resources/Base.lproj/Main.storyboardc/Info.plist delete mode 100644 Products/XcodeGitHub.app/Contents/Resources/Base.lproj/Main.storyboardc/MainMenu.nib delete mode 100644 Products/XcodeGitHub.app/Contents/Resources/XGAAddServerPanel.nib delete mode 100644 Products/XcodeGitHub.app/Contents/Resources/XGALogViewController.nib delete mode 100644 Products/XcodeGitHub.app/Contents/Resources/XGAPreferencesViewController.nib delete mode 100644 Products/XcodeGitHub.app/Contents/Resources/XGAStatusPopover.nib delete mode 100644 Products/XcodeGitHub.app/Contents/Resources/XGAStatusViewController.nib delete mode 100644 Products/XcodeGitHub.app/Contents/_CodeSignature/CodeResources create mode 100644 Products/XcodeGitHub.framework.zip delete mode 120000 Products/XcodeGitHub.framework/Headers delete mode 120000 Products/XcodeGitHub.framework/Modules delete mode 100644 Products/XcodeGitHub.framework/Versions/A/Headers/APFormattedString.h delete mode 100644 Products/XcodeGitHub.framework/Versions/A/Headers/BNCLog.h delete mode 100644 Products/XcodeGitHub.framework/Versions/A/Headers/BNCNetworkService.h delete mode 100644 Products/XcodeGitHub.framework/Versions/A/Headers/XGCommand.h delete mode 100644 Products/XcodeGitHub.framework/Versions/A/Headers/XGCommandOptions.h delete mode 100644 Products/XcodeGitHub.framework/Versions/A/Headers/XGGitHubPullRequest.h delete mode 100644 Products/XcodeGitHub.framework/Versions/A/Headers/XGXcodeBot.h delete mode 100644 Products/XcodeGitHub.framework/Versions/A/Headers/XcodeGitHub.h delete mode 100644 Products/XcodeGitHub.framework/Versions/A/Modules/module.modulemap delete mode 100644 Products/XcodeGitHub.framework/Versions/A/XcodeGitHub delete mode 120000 Products/XcodeGitHub.framework/Versions/Current delete mode 120000 Products/XcodeGitHub.framework/XcodeGitHub diff --git a/Art/StatusWindow.png b/Art/StatusWindow.png new file mode 100644 index 0000000000000000000000000000000000000000..a83abe556b187eeb6dcceb172bc9b150f692e40e GIT binary patch literal 1142481 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 From fcc94ddfc50ab1540e0640d796eff96dddaaa9b6 Mon Sep 17 00:00:00 2001 From: "Edward B. Smith" Date: Tue, 29 Jan 2019 09:19:27 -0800 Subject: [PATCH 9/9] Updated documentation. --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 45b6e2e..28bf50b 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Quickly automate your test and build process with XcodeGitHub, a macOS app that ### What's Included * A light weight macOS app with a clean UI that monitors your GitHub repos and creates Xcode test bots for them. -* A command line tool that has most of the same functions of the macOS app. +* An optional command line tool that has most of the same functions of the macOS app. * A static library that has interfaces for working with GitHub repositories and Xcode test bot system. ### Requirements @@ -70,8 +70,8 @@ You'll have to set up a build/test computer with Xcode, enable Xcode server, and Here are some guides for setting Xcode server and configuring bots: -[Xcode Help: Continuous Integration Overview](https://help.apple.com/xcode/mac/10.1/index.html?localePath=en.lproj#/dev466720061)
-[How to use Xcode server in Xcode 9] +[Xcode Help: Continuous Integration Overview](https://help.apple.com/xcode/mac/10.1/index.html?localePath=en.lproj#/dev466720061) +
[How to use Xcode server in Xcode 9] (https://medium.com/bobo-shone/how-to-use-xcode-server-in-xcode-9-93d6d2ee128f) #### 2. Create a Template Test Bot in Xcode @@ -189,7 +189,7 @@ While working at Branch Metrics I needed a way to automate the build and test pr For a while I used [Buildasaur](https://github.com/buildasaurs/Buildasaur) for automating tests and builds, a great app that just chugged along until Xcode 9, when the two became incompatible. I considered updating Buildasaur, but it was written in Swift 2.3 and I just didn't want to spend the effort converting the code to Swift 4 and then updating all the Xcode server parts. -It was much easier to start over in Objective-C which is more stable than Swift, is pretty fast to write, and has the option create static and dynamic libraries. +It was much easier to start over and write a light weight app in Objective-C which is more stable than Swift, is pretty fast to write, and has the option create static and dynamic libraries. As a shout out to Buildasaur I used much of the charming messaging it used for the PR statuses that are shown in GitHub. @@ -197,15 +197,15 @@ As a shout out to Buildasaur I used much of the charming messaging it used for t I've been writing iOS code almost exclusively for a number of years and I wanted to see how macOS has been evolving. -The first thing that stood out is that AppKit, the Mac UI library, hasn't been getting the same amount of love as iOS has and feels kind of neglected. +The first thing that stood out is that AppKit, the Mac UI library, hasn't been getting the same amount of love as iOS has and feels neglected. There's a lot of old NextStep ways of doing things in AppKit, and I mean really old, and it shows. This isn't going away any time soon because it'll break too much stuff, but it could be handled better. -For instance, user interface cells made a lot of sense in the 90s when memory and computing power where absolutely minimal compared to today. But now their purpose isn't clear and they get in the way. +For instance, user interface cells made a lot of sense in the 90s when memory and computing power where absolutely constrained compared to today. But now their purpose isn't clear and they get in the way. Some things have been updated, like `NSTableView`, which can now be view-based or cell-based. -On the other hand I really appreciated again how feature rich macOS and and the development environment is. Native apps are kind of on their way out which is kind of too bad, because the dev environment is pretty nice and the apps have the potential to be very powerful. +On the other hand I really appreciated again how feature rich macOS and the development environment is. Native apps are kind of on their way out which is kind of too bad, since native apps have the potential to be very powerful and expressive, and the developement environment is generally pretty good. ### Goal 3: Experiment with using XCTest with macOS / iOS libraries