Skip to content

Instantly share code, notes, and snippets.

@atr000
Created February 9, 2010 18:50
Show Gist options
  • Save atr000/299513 to your computer and use it in GitHub Desktop.
Save atr000/299513 to your computer and use it in GitHub Desktop.
/*
* git-update-infoplist.m
* git-update-infoplist
*
* Created by Ofri Wolfus on 25/05/07.
* Copyright 2007 Ofri Wolfus. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Ofri Wolfus nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* v0.3:
* - We now check the contents of the repository directory for a git repo layout.
* - The passed paths are now standardized before using.
* - Paths relative to the current directory are now valid.
* - Added -[NSString absolutePath].
* - Added +[NSTask fullPathToExecutable:additionalSearchPaths:].
* - A help is now displayed if no arguments are available, or if '-h' or '--help'
* were passed.
*
* v0.2:
* - Rewrote +[NSTask fullPathToExecutable:] with pure Cocoa.
* - Added custom executable paths.
*
* v0.1:
* - Initial release
*/
#import <Foundation/Foundation.h>
@interface NSString (DPExtensions)
/*!
* @abstract A convenient methods for creating an autoreleased string
* from an NSData instance.
*
* @discussion This is the equivalent to <code>[[[NSString alloc] initWithData:data encoding:enc] autorelease]</code>.
*/
+ (id)stringWithData:(NSData *)data encoding:(NSStringEncoding)enc;
/*!
* @abstract Returns an absolute path from the receiver.
* @discussion The path is made by standardizing the receiver and appending it to the current directory if needed.
*/
- (NSString *)absolutePath;
@end
@implementation NSString (DPExtensions)
+ (id)stringWithData:(NSData *)data encoding:(NSStringEncoding)enc {
return [[[NSString alloc] initWithData:data
encoding:enc] autorelease];
}
- (NSString *)absolutePath {
self = [self stringByStandardizingPath];
if (![self hasPrefix:@"/"])
self = [[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:self];
return self;
}
@end
@interface NSTask (DPExtensions)
+ (NSString *)fullPathToExecutable:(NSString *)execName;
+ (NSString *)fullPathToExecutable:(NSString *)execName additionalSearchPaths:(NSArray *)paths;
@end
@implementation NSTask (DPExtensions)
+ (NSString *)fullPathToExecutable:(NSString *)execName {
return [self fullPathToExecutable:execName
additionalSearchPaths:[NSArray arrayWithObjects:@"/usr/local/bin",
@"/usr/local/sbin",
@"/opt/local/bin",
@"/opt/local/sbin", nil]];
}
+ (NSString *)fullPathToExecutable:(NSString *)execName additionalSearchPaths:(NSArray *)paths {
NSString *result = nil;
NSPipe *pipe = [NSPipe pipe];
NSTask *task = [[NSTask alloc] init];
NSMutableDictionary *env = [[[NSProcessInfo processInfo] environment] mutableCopy];
NSMutableString *path_var = [[env objectForKey:@"PATH"] mutableCopy];
NSEnumerator *enumerator = [paths objectEnumerator];
NSString *searchPath;
// Add any additional search paths
while ((searchPath = [enumerator nextObject])) {
if ([path_var rangeOfString:searchPath].location == NSNotFound)
[path_var appendFormat:@":%@", searchPath];
}
// Set the new PATH variable
[env setObject:path_var forKey:@"PATH"];
// Initialize our task
[task setLaunchPath:@"/usr/bin/which"];
[task setEnvironment:env];
[task setArguments:[NSArray arrayWithObject:execName]];
[task setStandardOutput:pipe];
// Launch and wait
[task launch];
[task waitUntilExit];
if ([task terminationStatus] == 0) {
result = [NSString stringWithData:[[pipe fileHandleForReading] readDataToEndOfFile]
encoding:NSUTF8StringEncoding];
result = [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
// Some error occured, and we didn't get a full path
if (![result isAbsolutePath])
result = nil;
}
// Clean up
[path_var release];
[env release];
[task release];
return result;
}
@end
BOOL directoryIsGitRepo(NSString *path) {
NSArray *contents = [[NSFileManager defaultManager] directoryContentsAtPath:path];
if (contents &&
[contents containsObject:@"HEAD"] &&
[contents containsObject:@"branches"] &&
[contents containsObject:@"config"] &&
[contents containsObject:@"description"] &&
[contents containsObject:@"hooks"] &&
[contents containsObject:@"info"] &&
[contents containsObject:@"objects"] &&
[contents containsObject:@"refs"])
{
return YES;
}
return NO;
}
void printHelp(void) {
puts("usage: git-info-plist REPO_PATH PLIST_PATH\n\n"
"The first argument is a path to the Git repository. It may point to a bare repository,\n"
"a \"regular\" repository or to the .git repository inside a repository.\n"
"The second argument is a path to a plist file to which git-update-infoplist will write\n"
"the commit ID.\n\n"
"git-info-plist also accepts two optional environment variables:\n"
" COMMIT_KEY_NAME The name of the key to write the commit ID.\n"
" The default is \"DPGitCommit\"\n"
" GIT_LOG_PATH A full path to the git-log executable. If git-info-plist fails to\n"
" locate the git-log executable, use this variable to point it to it.\n"
" Alternatively, this can be used to force the use of a given executable\n"
" if more than one are available. Note: The default search paths together\n"
" with /usr/local/bin, /usr/local/sbin, /opt/local/bin and \n"
" /opt/local/sbin are searched for the git-log executable.\n");
}
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSProcessInfo *pinfo = [NSProcessInfo processInfo];
NSArray *args = [pinfo arguments];
if ([args count] < 3 || [args containsObject:@"-h"] || [args containsObject:@"--help"]) {
printHelp();
return 0;
}
NSString *repoPath = [[args objectAtIndex:1] absolutePath];
NSString *plistPath = [[args objectAtIndex:2] absolutePath];
NSMutableDictionary *plist = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
// Make sure our repository is a real repo
repoPath = directoryIsGitRepo(repoPath) ? repoPath
: [repoPath stringByAppendingPathComponent:@".git"];
if (!directoryIsGitRepo(repoPath)) {
printf("Error: The passed repository does not appear to be a git repository.");
return EXIT_FAILURE;
}
// Something went wrong with loading our plist
if (!plist) {
printf("Error: Can't load plist from %s.\n", [plistPath UTF8String]);
return EXIT_FAILURE;
}
NSPipe *pipe = [NSPipe pipe];
NSTask *gitLog = [[NSTask alloc] init];
NSMutableDictionary *env = [NSMutableDictionary dictionaryWithDictionary:[pinfo environment]];
NSString *gitLogPath = [env objectForKey:@"GIT_LOG_PATH"] ?: [NSTask fullPathToExecutable:@"git-log"];
// Make sure we found git-log
if (!gitLogPath) {
printf("Error: Can't find the git-log exectable.\n");
return EXIT_FAILURE;
}
// Let git-log know where the repo is located
[env setObject:repoPath forKey:@"GIT_DIR"];
// Initialize our task
[gitLog setLaunchPath:gitLogPath];
[gitLog setArguments:[NSArray arrayWithObjects:@"-1",
@"--pretty=format:%H", nil]];
[gitLog setEnvironment:env];
[gitLog setStandardOutput:pipe];
[gitLog setStandardError:pipe];
// Launch and wait for its termination
[gitLog launch];
[gitLog waitUntilExit];
// Get the output of git-log
NSString *output = [NSString stringWithData:[[pipe fileHandleForReading] readDataToEndOfFile]
encoding:NSUTF8StringEncoding];
// An error had occured and git-log exited with non-zero value
if ([gitLog terminationStatus] != 0) {
// Log the error message of git-log if one was provided
if ([output length] > 0)
printf("%s", [output UTF8String]);
return EXIT_FAILURE;
}
// Modify the dictionary,
[plist setObject:[output stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]
forKey:[env objectForKey:@"COMMIT_KEY_NAME"] ?: @"DPGitCommit"];
// and write it back to disk
if (![plist writeToFile:plistPath atomically:YES]) {
printf("Error: Can't write plist to %s.\n", [plistPath UTF8String]);
return EXIT_FAILURE;
}
[pool release];
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment