-While working at Branch Metrics I needed a way to automate the build and test process for
-our SDKs and other projects. I wrote the Xcode-GitHub macOS app that monitors new pull
-requests on GitHub and creates Xcode bots to test them.
+### Automate your Xcode test and build process with the XcodeGitHub app
-I'd been using [Buildasaur](https://github.com/buildasaurs/Buildasaur), a great app that just chugged along for a while, but new versions of Xcode it didn't support newer versions of Xcode and, alas, it was written in Swift 2.3 and I just didn't want to spend the effort converting the code to Swift 4 and updating the Xcode server part.
+Quickly automate your test and build process with XcodeGitHub, a macOS app that monitors your GitHub repos for new pull requests and creates Xcode bots that test them.
+
+
+
+### Features
+
+* Runs on your own build computer so you can easily do on-device testing with your own phones or Macs.
+* Runs inside your network without any firewall changes.
+* Easy to configure and run.
### What's Included
-* 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 bundle for testing.
+* A light weight macOS app with a clean UI that monitors your GitHub repos and creates Xcode test bots for them.
+* 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
+
+* Xcode 9 and above to run the bots.
+* macOS 10.12 and above to run Xcode.
+
+## Contents
+
+* [Getting Started](#getting-started)
+ - [Installation](#installation)
+ - [Monitoring Your First Repo](#monitoring-your-first-repo)
+ - [The Command Line Tool](#the-command-line-tool)
+* [Trouble Shooting & Support](#trouble-shooting-support)
+ - [Diagnosing Problems](#diagnosing-problems)
+ - [Reporting Issues & Requesting Features](#reporting-issues-requesting-features)
+* [Project Goals](#project-goals)
+ - [Goal 1: Write a useful test automation tool](#goal-1-write-a-useful-test-automation-tool)
+ - [Goal 2: Write a new macOS app](#goal-2-write-a-new-macos-app)
+ - [Goal 3: Experiment with using XCTest with macOS/iOS libraries](#goal-3-experiment-with-using-xctest-with-macos-ios-libraries
+)
+
+## Getting Started and Usage
+
+### Installation
+
+##### The Easy Installation
+
+Download this file: [`Products/XcodeGitHub.app.zip`](Products/XcodeGitHub.app.zip) from the project, unzip it, and move it to your Applications directory. Done. The app is code signed, but you'll have to approve the app in the system dialog that pops up the first time you run the app.
+
+##### The Slightly Harder Installation
+
+Clone this repo, open the `xcode-github-app.xcodeproj` project file in Xcode, and select *Product > Build* to build the project. After the project finishes compiling the app can be found in the Xcode project tree under the 'Products' folder. Right click to show the app in the Finder and move the app to your Applications directory.
+
+### Monitoring Your First Repo
+
+XcodeGitHub works by using an existing Xcode test bot you've already set up as a template for the new bots it creates for the pull requests on your GitHub repo.
+
+You'll have to set up a build/test computer with Xcode, enable Xcode server, and then create a template test bot to get started.
+
+#### 1. Set Up an Xcode Bot Server
+
+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]
+(https://medium.com/bobo-shone/how-to-use-xcode-server-in-xcode-9-93d6d2ee128f)
+
+#### 2. Create a Template Test Bot in Xcode
+
+I usually create my template test bot on the `staging` or `master` branch of my repo and configure the bot exactly as I want the PRs to be tested.
+
+[Xcode Help: Create a bot](https://help.apple.com/xcode/mac/10.1/index.html?localePath=en.lproj#/devc4a08a3ef)
+
+#### 3. Create a GitHub Access Token
+
+Xcode-GitHub updates the status of the PR at GitHub with the result of the tests.
+
+For the Xcode-GitHub to update the status, create an access token on the GitHub web site by navigating to your *Setting > Developer Settings > Personal Access Tokens* and creating a token with **repo** access.
+
+*Pro tip:* I created a special GitHub account just for my test bots to limit any security exposure.
+
+[GitHub: Personal access tokens](https://github.com/settings/tokens)
+
+#### 4. Run Xcode-GitHub
+
+The first time you run Xcode-GitHub it will prompt you to add an Xcode server to monitor.
+
+Also enter the user name and password to access the Xcode server. The user name and password are stored securely in the Mac keychain.
+
+You can leave these fields blank if a user name and password aren't required to access your Xcode server.
+
+
+
+Next, add your GitHub access token in the Preferences window.
+
+Again, the GitHub access token is stored securely in the Mac keychain.
+
+
+
+Finally, in the status window you'll see the test bot you set up previously. Select the bot, right click on it, and select 'Monitor Repo'.
+
+
+
+New test bots will be created for your PRs:
+
+
+
+That's it!
+
+Keep the Xcode-GitHub app running so that it can continue monitoring your repos.
+
+### The Command Line Tool
+
+There's also an optional command line tool included that has most of the same functionality as the macOS app. It that can be handy for scripted builds.
+
+The tool is built with the `xcode-github-cli.xcodeproj` project. For your convenience the pre-compiled tool downloaded from [`Products/xcode-github.`](Products/xcode-github)
+
+```
+xcode-github - Creates Xcode test bots for new GitHub PRs.
+
+usage: xcode-github [-dhsVv] -g
+ -t -x
+
+
+ -d, --dryrun
+ Dry run. Print what would be done.
+
+ -g, --github
+ A GitHub auth token that allows checking the status of a repo
+ and change a PR's status.
+
+ -h, --help
+ Print this help information.
+
+ -p, --password
+ Password for the Xcode server.
+
+ -r, --repeat
+ Repeat updating the status forever, waiting 60 seconds between updates.
+
+ -s, --status
+ Only print the status of the Xcode server bots and quit.
+
+ -t --template
+ An existing bot on the Xcode server that is used as a template
+ for the new GitHub PR bots.
+
+ -u, --user
+ User for the Xcode server.
+
+ -V, --version
+ Show version and exit.
+
+ -v, --verbose
+ Verbose. Extra 'v' increases the verbosity.
+
+ -x, --xcodeserver
+ The network name of the Xcode server.
+
+The tool returns 0 on success, otherwise a non-zero value.
+```
+
+## Trouble Shooting & Support
+
+### Diagnosing Problems
+
+If you're having problems the first diagnostic step is to open the log window (under the 'Window' menu) and look for any issues there. Toggling 'Show debug messages' on the lower right will show more verbose logging.
+
+### Reporting Issues & Feature Requests
+
+Report issues and make suggestions [here, at the issue section of this repository on GitHub.](https://github.com/E-B-Smith/xcode-github/issues)
## Project Goals
-### Write a useful test automation tool
+I had several goals for the project when I started. First, I needed an automated way to build and test software on a local computer with specific test devices attached. I couldn't find anything that fit my needs, so I this seemed like a good opportunity to explore some other things I'd been thinking about, such as writing a new macOS app to see how the landscape has changed during the years I had been concentrating on iOS development. Finally I wanted to try some different ways of testing libraries with the Xcode XCTest environment.
+
+### Goal 1: Write a useful test automation tool
+
+While working at Branch Metrics I needed a way to automate the build and test process for our SDKs and other projects. Because of the nature of our SDK it had to be tested on real devices since the simulators just don't have all the features and quirks of an actual device.
+
+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 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.
+
+### Goal 2: Write a new macOS app
+
+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 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 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 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.
-### Write a new macOS app
+### Goal 3: Experiment with using XCTest with macOS / iOS libraries
-### Experiment with macOS static libraries
+At Branch we distribute iOS, tvOS, and macOS SDK libraries that are widely used and have to be tested and correct. Further, many developers want to run the Branch SDK tests as part of their testing.
-### Experiment with XCTest unit tests for libraries
+I wanted to figure out how to modularize XCTest bundles so that they could be included in other project's test suites.
-## Getting Started with XCode-Git
+What I envisioned was creating XCTest bundles that could be shipped with a library and included in the main project test suite. They could be dropped in and would automatically run.
-It was much easier to start over in stable Objective-C code.
+I never figured out a great way to do this.
-## Installation and Usage
+I tried different combinations of complicated build configurations using xcconfig files that set the `TEST_HOST` and other flags. But the solutions were fragile and required un-intuitive set up. Worse, the working solutions I found increasing got further from the original goal of simple drag-and-drop testing.
+I'd love to hear other people's thoughts on this. Ultimately Xcode testing is just not built to do this, so any solution is simply fighting Xcode.
diff --git a/Vendor/Branch/BNCLog.h b/Vendor/Branch/BNCLog.h
index f9ce975..799fccf 100644
--- a/Vendor/Branch/BNCLog.h
+++ b/Vendor/Branch/BNCLog.h
@@ -151,6 +151,17 @@ FOUNDATION_EXPORT void BNCLogFlushMessages(void);
///@return Returns true if the app is currently attached to a debugger.
FOUNDATION_EXPORT BOOL BNCLogDebuggerIsAttached(void);
+#pragma - Debugging
+
+///@return Returns true if the app is currently attached to a debugger.
+extern BOOL BNCLogDebuggerIsAttached(void);
+
+/// Stops execution at the current execution point.
+/// If attached to a debugger, current app will halt and wait for the debugger.
+/// If not attached to a debugger then the current app will probably quit executing.
+#define BNCLogDebugBreakpoint() \
+ do { raise(SIGINT); } while (0)
+
#pragma mark - Logging
///@info Logging
@@ -179,9 +190,9 @@ FOUNDATION_EXPORT BOOL BNCLogDebuggerIsAttached(void);
do { \
if (BNCLogBreakPointsAreEnabled()) { \
BNCLogWriteMessageFormat(BNCLogLevelBreakPoint, __FILE__, __LINE__, @"Programmatic breakpoint."); \
- if (BNCDebuggerIsAttached()) { \
+ if (BNCLogDebuggerIsAttached()) { \
BNCLogFlushMessages(); \
- BNCDebugBreakpoint(); \
+ BNCLogDebugBreakpoint(); \
} \
} \
} while (0)
@@ -191,9 +202,9 @@ FOUNDATION_EXPORT BOOL BNCLogDebuggerIsAttached(void);
do { \
if (BNCLogBreakPointsAreEnabled() { \
BNCLogWriteMessageFormat(BNCLogLevelBreakPoint, __FILE__, __LINE__, __VA_ARGS__); \
- if (BNCDebuggerIsAttached()) { \
+ if (BNCLogDebuggerIsAttached()) { \
BNCLogFlushMessages(); \
- BNCDebugBreakpoint(); \
+ BNCLogDebugBreakpoint(); \
} \
} \
} while (0)
@@ -203,9 +214,9 @@ FOUNDATION_EXPORT BOOL BNCLogDebuggerIsAttached(void);
do { \
if (!(condition)) { \
BNCLogWriteMessageFormat(BNCLogLevelAssert, __FILE__, __LINE__, @"(%s) !!!", #condition); \
- if (BNCLogBreakPointsAreEnabled() && BNCDebuggerIsAttached()) { \
+ if (BNCLogBreakPointsAreEnabled() && BNCLogDebuggerIsAttached()) { \
BNCLogFlushMessages(); \
- BNCDebugBreakpoint(); \
+ BNCLogDebugBreakpoint(); \
} \
} \
} while (0)
@@ -217,9 +228,9 @@ FOUNDATION_EXPORT BOOL BNCLogDebuggerIsAttached(void);
if (!(condition)) { \
NSString *m = [NSString stringWithFormat:message, __VA_ARGS__]; \
BNCLogWriteMessageFormat(BNCLogLevelAssert, __FILE__, __LINE__, @"(%s) !!! %@", #condition, m); \
- if (BNCLogBreakPointsAreEnabled() && BNCDebuggerIsAttached()) { \
+ if (BNCLogBreakPointsAreEnabled() && BNCLogDebuggerIsAttached()) { \
BNCLogFlushMessages(); \
- BNCDebugBreakpoint(); \
+ BNCLogDebugBreakpoint(); \
} \
} \
} while (0)
diff --git a/Vendor/Branch/BNCLog.m b/Vendor/Branch/BNCLog.m
index 52c9260..3c17d98 100644
--- a/Vendor/Branch/BNCLog.m
+++ b/Vendor/Branch/BNCLog.m
@@ -43,7 +43,7 @@ BOOL BNCLogDebuggerIsAttached() {
size = sizeof(info);
junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
if (junk != 0) {
- NSLog(@"Program error in BNCDebuggerIsAttached. Junk != 0!");
+ NSLog(@"Program error in BNCLogDebuggerIsAttached. Junk != 0!");
}
// We're being debugged if the P_TRACED flag is set.
diff --git a/Vendor/Branch/BNCNetworkService.h b/Vendor/Branch/BNCNetworkService.h
index 8534a6a..aa10c92 100644
--- a/Vendor/Branch/BNCNetworkService.h
+++ b/Vendor/Branch/BNCNetworkService.h
@@ -41,11 +41,11 @@ NS_ASSUME_NONNULL_BEGIN
- (BNCNetworkOperation*) postOperationWithURL:(NSURL *)URL
contentType:(NSString*)contentType
- data:(NSData *)data
+ data:(NSData*_Nullable)data
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/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/XGCommandOptions.m b/XcodeGitHub/XGCommandOptions.m
index a89779e..b7ad1f9 100644
--- a/XcodeGitHub/XGCommandOptions.m
+++ b/XcodeGitHub/XGCommandOptions.m
@@ -67,7 +67,7 @@ + (NSString*) stringFromParameter {
+ (NSString*) helpString {
NSString *kHelpString =
- @"xcode-github - Creates an Xcode test bots for new GitHub PRs.\n"
+ @"xcode-github - Creates Xcode test bots for new GitHub PRs.\n"
"\n"
"usage: xcode-github [-dhsVv] -g \n"
" -t -x \n"
@@ -108,6 +108,8 @@ + (NSString*) helpString {
" -x, --xcodeserver \n"
" The network name of the xcode server.\n"
"\n"
+ "The tool returns 0 on success, otherwise a non-zero value.\n"
+ "\n"
;
return kHelpString;
}
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..8388fca 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;
@@ -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 41202dd..4ec4996 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;
@@ -635,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/build-release b/build-release
index 8daf0fc..0fefe5a 100755
--- a/build-release
+++ b/build-release
@@ -54,12 +54,27 @@ build_project XcodeGitHub
build_project xcode-github-cli
build_project xcode-github-app
+ditto -c -k --sequesterRsrc --keepParent \
+ "$build_products_directory"/XcodeGitHub.app \
+ "$build_products_directory"/XcodeGitHub.app.zip
+rm -rf "$build_products_directory"/XcodeGitHub.app
+
+ditto -c -k --sequesterRsrc --keepParent \
+ "$build_products_directory"/XcodeGitHub.framework \
+ "$build_products_directory"/XcodeGitHub.framework.zip
+rm -rf "$build_products_directory"/XcodeGitHub.framework
+
rm -Rf "$build_products_directory"/libXcodeGitHub.a
rm -Rf "$build_products_directory"/usr
echo ">>> Build ${marketing_version}(${build_version}) products in 'Products':"
ls -al Products
+if ! askYN "Release version ${marketing_version}?"
+then
+ exit 1
+fi
+
git add --all
git commit --allow-empty -m "Release ${marketing_version}."
git tag v"${marketing_version}"
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 46b43a6..00d7a93 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 @@