/* * 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 @interface NSString (DPExtensions) /*! * @abstract A convenient methods for creating an autoreleased string * from an NSData instance. * * @discussion This is the equivalent to [[[NSString alloc] initWithData:data encoding:enc] autorelease]. */ + (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; }