├── .git-gps ├── .gitignore ├── LICENSE.txt ├── README.md └── git-gps ├── git-gps.1 ├── git-gps.m ├── git-gps.xcodeproj └── project.pbxproj ├── git-gps_Prefix.pch └── source ├── GGCLDelegate.h ├── GGCLDelegate.m ├── GGGitTool.h └── GGGitTool.m /.git-gps: -------------------------------------------------------------------------------- 1 | { 2 | "latitude":45.782069, 3 | "longitude":-108.505118, 4 | "altitude":0.000000, 5 | "horizontalAccuracy":167.000000, 6 | "verticalAccuracy":-1.000000 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # xcode noise 2 | build/ 3 | build/* 4 | */build/* 5 | *.pbxuser 6 | *.mode1v3 7 | *.mode2v3 8 | *.perspectivev3 9 | xcuserdata/* 10 | xcuserdata/ 11 | 12 | # OSX noise 13 | .DS_Store 14 | profile 15 | Icon* 16 | 17 | # Emacs noise 18 | *~ 19 | 20 | # other noise 21 | .svn 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Andrew Wooster. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of git-gps, nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL Andrew Wooster BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-gps 2 | 3 | A tool to track commits in a git repository by GPS coordinates. 4 | 5 | Currently only works on OS X. 6 | 7 | ## Motivation 8 | 9 | For awhile now, I've wanted GPS logging of my checkins to source control 10 | systems. Between the `CoreLocation` framework on OS X and git's post-commit 11 | hooks, this is now possible. 12 | 13 | ## Installation 14 | 15 | Build the git-gps project, and put the resulting git-gps binary in 16 | `/usr/local/bin`. 17 | 18 | Now, to start logging GPS on checkins in a checkout of a git repo, go to 19 | your repo and run: 20 | 21 | ```sh 22 | git-gps init 23 | ``` 24 | 25 | Then, each time you commit to the repo, a post-commit hook will be run 26 | which updates the `.git-gps` file at the root of the repository and adds 27 | it to the commit. 28 | 29 | This is very alpha-level software, so please file issues, etc. 30 | 31 | Patches welcome! 32 | 33 | ## Similar Projects 34 | 35 | After writing this, I found out about [geocommit](https://github.com/peritus/geocommit), which does something very 36 | similar, except using git notes. The notes approach keeps the geo coordinates 37 | local until explicitly synced with the git remote. So, that would be more 38 | appropriate if you're concerned about privacy. 39 | -------------------------------------------------------------------------------- /git-gps/git-gps.1: -------------------------------------------------------------------------------- 1 | .\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples. 2 | .\"See Also: 3 | .\"man mdoc.samples for a complete listing of options 4 | .\"man mdoc for the short list of editing options 5 | .\"/usr/share/misc/mdoc.template 6 | .Dd 7/2/11 \" DATE 7 | .Dt git-gps 1 \" Program name and manual section number 8 | .Os Darwin 9 | .Sh NAME \" Section Header - required - don't modify 10 | .Nm git-gps, 11 | .\" The following lines are read in generating the apropos(man -k) database. Use only key 12 | .\" words here as the database is built based on the words here and in the .ND line. 13 | .Nm Other_name_for_same_program(), 14 | .Nm Yet another name for the same program. 15 | .\" Use .Nm macro to designate other names for the documented program. 16 | .Nd This line parsed for whatis database. 17 | .Sh SYNOPSIS \" Section Header - required - don't modify 18 | .Nm 19 | .Op Fl abcd \" [-abcd] 20 | .Op Fl a Ar path \" [-a path] 21 | .Op Ar file \" [file] 22 | .Op Ar \" [file ...] 23 | .Ar arg0 \" Underlined argument - use .Ar anywhere to underline 24 | arg2 ... \" Arguments 25 | .Sh DESCRIPTION \" Section Header - required - don't modify 26 | Use the .Nm macro to refer to your program throughout the man page like such: 27 | .Nm 28 | Underlining is accomplished with the .Ar macro like this: 29 | .Ar underlined text . 30 | .Pp \" Inserts a space 31 | A list of items with descriptions: 32 | .Bl -tag -width -indent \" Begins a tagged list 33 | .It item a \" Each item preceded by .It macro 34 | Description of item a 35 | .It item b 36 | Description of item b 37 | .El \" Ends the list 38 | .Pp 39 | A list of flags and their descriptions: 40 | .Bl -tag -width -indent \" Differs from above in tag removed 41 | .It Fl a \"-a flag as a list item 42 | Description of -a flag 43 | .It Fl b 44 | Description of -b flag 45 | .El \" Ends the list 46 | .Pp 47 | .\" .Sh ENVIRONMENT \" May not be needed 48 | .\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1 49 | .\" .It Ev ENV_VAR_1 50 | .\" Description of ENV_VAR_1 51 | .\" .It Ev ENV_VAR_2 52 | .\" Description of ENV_VAR_2 53 | .\" .El 54 | .Sh FILES \" File used or created by the topic of the man page 55 | .Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact 56 | .It Pa /usr/share/file_name 57 | FILE_1 description 58 | .It Pa /Users/joeuser/Library/really_long_file_name 59 | FILE_2 description 60 | .El \" Ends the list 61 | .\" .Sh DIAGNOSTICS \" May not be needed 62 | .\" .Bl -diag 63 | .\" .It Diagnostic Tag 64 | .\" Diagnostic informtion here. 65 | .\" .It Diagnostic Tag 66 | .\" Diagnostic informtion here. 67 | .\" .El 68 | .Sh SEE ALSO 69 | .\" List links in ascending order by section, alphabetically within a section. 70 | .\" Please do not reference files that do not exist without filing a bug report 71 | .Xr a 1 , 72 | .Xr b 1 , 73 | .Xr c 1 , 74 | .Xr a 2 , 75 | .Xr b 2 , 76 | .Xr a 3 , 77 | .Xr b 3 78 | .\" .Sh BUGS \" Document known, unremedied bugs 79 | .\" .Sh HISTORY \" Document history if command behaves in a unique manner -------------------------------------------------------------------------------- /git-gps/git-gps.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "GGCLDelegate.h" 3 | #import "GGGitTool.h" 4 | 5 | void usage(NSString *message) { 6 | printf("Usage: git-gps [init|commit|update|log]\n"); 7 | printf("\tinit\tInitializes git-gps in a git repository.\n"); 8 | printf("\tupdate\tUpdates the .git-gps file.\n"); 9 | printf("\tcommit\tUsed by the commit hook. Don't run!\n"); 10 | printf("\tlog\tUnimplemented.\n"); 11 | if (message) { 12 | printf("%s\n", [message UTF8String]); 13 | } 14 | } 15 | 16 | NSString *commitHook() { 17 | return @"# BEGIN git-gps HOOK\n" 18 | @"git-gps commit\n" 19 | @"# END git-gps HOOK\n"; 20 | } 21 | 22 | NSString * gitPath() { 23 | NSString *result = nil; 24 | do { // once 25 | NSFileManager *fm = [NSFileManager defaultManager]; 26 | NSString *cwd = [fm currentDirectoryPath]; 27 | if (!cwd) break; 28 | 29 | NSString *path = cwd; 30 | while (YES) { 31 | NSString *gitPath = [path stringByAppendingPathComponent:@".git"]; 32 | BOOL isDir = NO; 33 | if ([fm fileExistsAtPath:gitPath isDirectory:&isDir] && isDir) { 34 | result = gitPath; 35 | break; 36 | } 37 | if ([path length] == 1) break; 38 | 39 | path = [path stringByDeletingLastPathComponent]; 40 | if (!path || [path length] == 0) { 41 | break; 42 | } 43 | } 44 | 45 | } while (NO); 46 | return result; 47 | } 48 | 49 | NSString *gitGPSPath () { 50 | return [[gitPath() stringByDeletingLastPathComponent] stringByAppendingPathComponent:@".git-gps"]; 51 | } 52 | 53 | NSString *gitCommitHookPath() { 54 | return [gitPath() stringByAppendingPathComponent:@"hooks/post-commit"]; 55 | } 56 | 57 | int createGitGPSHook() { 58 | int result = EXIT_FAILURE; 59 | NSFileManager *fm = [NSFileManager defaultManager]; 60 | if ([fm fileExistsAtPath:gitCommitHookPath()]) { 61 | NSString *contents = [NSString stringWithContentsOfFile:gitCommitHookPath() encoding:NSUTF8StringEncoding error:nil]; 62 | if ([contents rangeOfString:@"BEGIN git-gps HOOK"].length == 0) { 63 | NSRange shRange = [contents rangeOfString:@"#!/bin/sh"]; 64 | if (shRange.location == 0 && shRange.length > 0) { 65 | NSString *newContents = [NSString stringWithFormat:@"%@\n%@", contents, commitHook()]; 66 | if ([newContents writeToFile:gitCommitHookPath() atomically:YES encoding:NSUTF8StringEncoding error:nil]) { 67 | result = EXIT_SUCCESS; 68 | } else { 69 | printf("Unable to add post-commit hook.\n"); 70 | } 71 | } else { 72 | printf("post-commit hook must be a sh script.\n"); 73 | } 74 | } else { 75 | result = EXIT_SUCCESS; 76 | } 77 | } else { 78 | NSString *contents = [NSString stringWithFormat:@"%@\n%@", @"#!/bin/sh\n", commitHook()]; 79 | if (![contents writeToFile:gitCommitHookPath() atomically:YES encoding:NSUTF8StringEncoding error:nil]) { 80 | printf("Unable to add post-commit hook.\n"); 81 | } else { 82 | result = EXIT_SUCCESS; 83 | } 84 | } 85 | if (result == EXIT_SUCCESS) { 86 | NSTask *chmod = [NSTask launchedTaskWithLaunchPath:@"/bin/chmod" arguments:[NSArray arrayWithObjects:@"+x", gitCommitHookPath(), nil]]; 87 | [chmod waitUntilExit]; 88 | } 89 | return result; 90 | } 91 | 92 | NSString * createGitGPSFile(NSString *gitPath) { 93 | NSFileManager *fm = [NSFileManager defaultManager]; 94 | NSString *filePath = gitGPSPath(); 95 | BOOL isDir = NO; 96 | if ([fm fileExistsAtPath:filePath isDirectory:&isDir]) { 97 | if (isDir) { 98 | usage([NSString stringWithFormat:@"\tDirectory found at: %@", filePath]); 99 | return nil; 100 | } else { 101 | return filePath; 102 | } 103 | } 104 | if ([fm createFileAtPath:filePath contents:nil attributes:nil]) { 105 | return filePath; 106 | } 107 | return nil; 108 | } 109 | 110 | int init() { 111 | int result = EXIT_FAILURE; 112 | NSString *newGitPath = gitPath(); 113 | if (!newGitPath) { 114 | usage(@"\tNo .git path found in parent directories."); 115 | return EXIT_FAILURE; 116 | } 117 | result = createGitGPSHook(); 118 | if (result == EXIT_SUCCESS) { 119 | printf("git-gps initialized at %s\n", [gitGPSPath() UTF8String]); 120 | } 121 | NSString *gitGPSPath = createGitGPSFile(newGitPath); 122 | if (!gitGPSPath) { 123 | return EXIT_FAILURE; 124 | } 125 | return result; 126 | } 127 | 128 | NSString *jsonForCLLocation(CLLocation *location) { 129 | NSString *result = @""; 130 | if (location) { 131 | NSMutableArray *parts = [NSMutableArray array]; 132 | [parts addObject:[NSString stringWithFormat:@"\"latitude\":%f", location.coordinate.latitude]]; 133 | [parts addObject:[NSString stringWithFormat:@"\"longitude\":%f", location.coordinate.longitude]]; 134 | [parts addObject:[NSString stringWithFormat:@"\"altitude\":%f", location.altitude]]; 135 | [parts addObject:[NSString stringWithFormat:@"\"horizontalAccuracy\":%f", location.horizontalAccuracy]]; 136 | [parts addObject:[NSString stringWithFormat:@"\"verticalAccuracy\":%f", location.verticalAccuracy]]; 137 | NSString *partsString = [parts componentsJoinedByString:@",\n "]; 138 | result = [NSString stringWithFormat:@"{\n %@\n}\n", partsString]; 139 | } 140 | return result; 141 | } 142 | 143 | BOOL updateGitGPSWithLocation(CLLocation *location) { 144 | NSString *result = jsonForCLLocation(location); 145 | if (![result writeToFile:gitGPSPath() atomically:YES encoding:NSUTF8StringEncoding error:nil]) { 146 | printf("%s\n", [[NSString stringWithFormat:@"Unable to update file at: %@", gitGPSPath()] UTF8String]); 147 | return NO; 148 | } else { 149 | printf("Added geolocation to commit.\n"); 150 | } 151 | return YES; 152 | } 153 | 154 | int update() { 155 | int result = EXIT_FAILURE; 156 | NSString *ourGitPath = gitPath(); 157 | if (!ourGitPath) { 158 | usage(@"\tNo .git path found in parent directories."); 159 | return result; 160 | } 161 | 162 | GGCLDelegate *cl = [[GGCLDelegate alloc] init]; 163 | 164 | NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 165 | 166 | while (YES) { 167 | NSDate *now = [[NSDate alloc] init]; 168 | NSTimer *timer = [[NSTimer alloc] initWithFireDate:now interval:.01 target:cl selector:@selector(start:) userInfo:nil repeats:YES]; 169 | NSDate *terminate = [[NSDate alloc] initWithTimeIntervalSinceNow:1.0]; 170 | 171 | [runLoop addTimer:timer forMode:NSDefaultRunLoopMode]; 172 | [runLoop runUntilDate:terminate]; 173 | 174 | [timer invalidate]; 175 | [timer release]; 176 | [now release]; 177 | [terminate release]; 178 | if ([cl goodEnough]) { 179 | break; 180 | } 181 | } 182 | 183 | if (updateGitGPSWithLocation(cl.location)) { 184 | result = EXIT_SUCCESS; 185 | } 186 | [cl release]; 187 | 188 | return result; 189 | } 190 | 191 | int commit() { 192 | NSFileManager *fm = [NSFileManager defaultManager]; 193 | NSString *tombstonePath = [[gitPath() stringByDeletingLastPathComponent] stringByAppendingPathComponent:@".git-gps-tombstone"]; 194 | if ([fm fileExistsAtPath:tombstonePath]) { 195 | [fm removeItemAtPath:tombstonePath error:nil]; 196 | return EXIT_SUCCESS; 197 | } else { 198 | [fm createFileAtPath:tombstonePath contents:nil attributes:nil]; 199 | } 200 | 201 | int result = update(); 202 | if (result == EXIT_SUCCESS) { 203 | // Try to re-commit by `git add .git-gps; git commit --amend -C HEAD` 204 | do { // once 205 | GGGitTool *git = [GGGitTool sharedGitTool]; 206 | 207 | [git gitAdd:gitGPSPath()]; 208 | if ([git terminationStatus] != EXIT_SUCCESS) { 209 | result = [git terminationStatus]; 210 | break; 211 | } 212 | 213 | [git gitCommand:[NSArray arrayWithObjects:@"commit", @"--amend", @"-C", @"HEAD", nil]]; 214 | if ([git terminationStatus] != EXIT_SUCCESS) { 215 | result = [git terminationStatus]; 216 | break; 217 | } 218 | result = EXIT_SUCCESS; 219 | } while (NO); 220 | } 221 | return result; 222 | } 223 | 224 | int log_all() { 225 | int result = EXIT_SUCCESS; 226 | 227 | 228 | 229 | return result; 230 | } 231 | 232 | int main (int argc, const char * argv[]) { 233 | NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 234 | int result = EXIT_FAILURE; 235 | do { // once 236 | if (argc >= 2) { 237 | NSString *arg = [NSString stringWithUTF8String:argv[1]]; 238 | if ([arg isEqualToString:@"init"]) { 239 | result = init(); 240 | break; 241 | } else if ([arg isEqualToString:@"update"]) { 242 | result = update(); 243 | break; 244 | } else if ([arg isEqualToString:@"commit"]) { 245 | result = commit(); 246 | break; 247 | } else if ([arg isEqualToString:@"log"]) { 248 | result = log_all(); 249 | break; 250 | } else { 251 | usage([NSString stringWithFormat:@"\tUnknown argument: %@", arg]); 252 | break; 253 | } 254 | } 255 | 256 | usage(nil); 257 | } while (NO); 258 | 259 | [pool drain]; 260 | return result; 261 | } 262 | 263 | -------------------------------------------------------------------------------- /git-gps/git-gps.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 45; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4971B5DC13BFDC3A00774B70 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4971B5DB13BFDC3A00774B70 /* CoreLocation.framework */; }; 11 | 4971B5F713BFDE9D00774B70 /* GGCLDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4971B5F613BFDE9D00774B70 /* GGCLDelegate.m */; }; 12 | 49D783BB13C902650003261C /* GGGitTool.m in Sources */ = {isa = PBXBuildFile; fileRef = 49D783BA13C902650003261C /* GGGitTool.m */; }; 13 | 8DD76F9A0486AA7600D96B5E /* git-gps.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* git-gps.m */; settings = {ATTRIBUTES = (); }; }; 14 | 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; }; 15 | 8DD76F9F0486AA7600D96B5E /* git-gps.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = C6859EA3029092ED04C91782 /* git-gps.1 */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 8DD76F9E0486AA7600D96B5E /* CopyFiles */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 8; 22 | dstPath = /usr/share/man/man1/; 23 | dstSubfolderSpec = 0; 24 | files = ( 25 | 8DD76F9F0486AA7600D96B5E /* git-gps.1 in CopyFiles */, 26 | ); 27 | runOnlyForDeploymentPostprocessing = 1; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 08FB7796FE84155DC02AAC07 /* git-gps.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "git-gps.m"; sourceTree = ""; }; 33 | 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 34 | 32A70AAB03705E1F00C91783 /* git-gps_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "git-gps_Prefix.pch"; sourceTree = ""; }; 35 | 4971B5DB13BFDC3A00774B70 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; 36 | 4971B5F513BFDE9D00774B70 /* GGCLDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GGCLDelegate.h; path = source/GGCLDelegate.h; sourceTree = ""; }; 37 | 4971B5F613BFDE9D00774B70 /* GGCLDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GGCLDelegate.m; path = source/GGCLDelegate.m; sourceTree = ""; }; 38 | 49D783B913C902650003261C /* GGGitTool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GGGitTool.h; path = source/GGGitTool.h; sourceTree = ""; }; 39 | 49D783BA13C902650003261C /* GGGitTool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GGGitTool.m; path = source/GGGitTool.m; sourceTree = ""; }; 40 | 8DD76FA10486AA7600D96B5E /* git-gps */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "git-gps"; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | C6859EA3029092ED04C91782 /* git-gps.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = "git-gps.1"; sourceTree = ""; }; 42 | /* End PBXFileReference section */ 43 | 44 | /* Begin PBXFrameworksBuildPhase section */ 45 | 8DD76F9B0486AA7600D96B5E /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */, 50 | 4971B5DC13BFDC3A00774B70 /* CoreLocation.framework in Frameworks */, 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | 08FB7794FE84155DC02AAC07 /* git-gps */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 08FB7795FE84155DC02AAC07 /* Source */, 61 | C6859EA2029092E104C91782 /* Documentation */, 62 | 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */, 63 | 1AB674ADFE9D54B511CA2CBB /* Products */, 64 | ); 65 | name = "git-gps"; 66 | sourceTree = ""; 67 | }; 68 | 08FB7795FE84155DC02AAC07 /* Source */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 32A70AAB03705E1F00C91783 /* git-gps_Prefix.pch */, 72 | 08FB7796FE84155DC02AAC07 /* git-gps.m */, 73 | 4971B5F513BFDE9D00774B70 /* GGCLDelegate.h */, 74 | 4971B5F613BFDE9D00774B70 /* GGCLDelegate.m */, 75 | 49D783B913C902650003261C /* GGGitTool.h */, 76 | 49D783BA13C902650003261C /* GGGitTool.m */, 77 | ); 78 | name = Source; 79 | sourceTree = ""; 80 | }; 81 | 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 08FB779EFE84155DC02AAC07 /* Foundation.framework */, 85 | 4971B5DB13BFDC3A00774B70 /* CoreLocation.framework */, 86 | ); 87 | name = "External Frameworks and Libraries"; 88 | sourceTree = ""; 89 | }; 90 | 1AB674ADFE9D54B511CA2CBB /* Products */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 8DD76FA10486AA7600D96B5E /* git-gps */, 94 | ); 95 | name = Products; 96 | sourceTree = ""; 97 | }; 98 | C6859EA2029092E104C91782 /* Documentation */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | C6859EA3029092ED04C91782 /* git-gps.1 */, 102 | ); 103 | name = Documentation; 104 | sourceTree = ""; 105 | }; 106 | /* End PBXGroup section */ 107 | 108 | /* Begin PBXNativeTarget section */ 109 | 8DD76F960486AA7600D96B5E /* git-gps */ = { 110 | isa = PBXNativeTarget; 111 | buildConfigurationList = 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "git-gps" */; 112 | buildPhases = ( 113 | 8DD76F990486AA7600D96B5E /* Sources */, 114 | 8DD76F9B0486AA7600D96B5E /* Frameworks */, 115 | 8DD76F9E0486AA7600D96B5E /* CopyFiles */, 116 | ); 117 | buildRules = ( 118 | ); 119 | dependencies = ( 120 | ); 121 | name = "git-gps"; 122 | productInstallPath = "$(HOME)/bin"; 123 | productName = "git-gps"; 124 | productReference = 8DD76FA10486AA7600D96B5E /* git-gps */; 125 | productType = "com.apple.product-type.tool"; 126 | }; 127 | /* End PBXNativeTarget section */ 128 | 129 | /* Begin PBXProject section */ 130 | 08FB7793FE84155DC02AAC07 /* Project object */ = { 131 | isa = PBXProject; 132 | attributes = { 133 | ORGANIZATIONNAME = "Andrew Wooster"; 134 | }; 135 | buildConfigurationList = 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "git-gps" */; 136 | compatibilityVersion = "Xcode 3.1"; 137 | developmentRegion = English; 138 | hasScannedForEncodings = 1; 139 | knownRegions = ( 140 | English, 141 | Japanese, 142 | French, 143 | German, 144 | ); 145 | mainGroup = 08FB7794FE84155DC02AAC07 /* git-gps */; 146 | projectDirPath = ""; 147 | projectRoot = ""; 148 | targets = ( 149 | 8DD76F960486AA7600D96B5E /* git-gps */, 150 | ); 151 | }; 152 | /* End PBXProject section */ 153 | 154 | /* Begin PBXSourcesBuildPhase section */ 155 | 8DD76F990486AA7600D96B5E /* Sources */ = { 156 | isa = PBXSourcesBuildPhase; 157 | buildActionMask = 2147483647; 158 | files = ( 159 | 8DD76F9A0486AA7600D96B5E /* git-gps.m in Sources */, 160 | 4971B5F713BFDE9D00774B70 /* GGCLDelegate.m in Sources */, 161 | 49D783BB13C902650003261C /* GGGitTool.m in Sources */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXSourcesBuildPhase section */ 166 | 167 | /* Begin XCBuildConfiguration section */ 168 | 1DEB927508733DD40010E9CD /* Debug */ = { 169 | isa = XCBuildConfiguration; 170 | buildSettings = { 171 | ALWAYS_SEARCH_USER_PATHS = NO; 172 | COPY_PHASE_STRIP = NO; 173 | GCC_DYNAMIC_NO_PIC = NO; 174 | GCC_ENABLE_FIX_AND_CONTINUE = YES; 175 | GCC_MODEL_TUNING = G5; 176 | GCC_OPTIMIZATION_LEVEL = 0; 177 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 178 | GCC_PREFIX_HEADER = "git-gps_Prefix.pch"; 179 | INSTALL_PATH = /usr/local/bin; 180 | PRODUCT_NAME = "git-gps"; 181 | }; 182 | name = Debug; 183 | }; 184 | 1DEB927608733DD40010E9CD /* Release */ = { 185 | isa = XCBuildConfiguration; 186 | buildSettings = { 187 | ALWAYS_SEARCH_USER_PATHS = NO; 188 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 189 | GCC_MODEL_TUNING = G5; 190 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 191 | GCC_PREFIX_HEADER = "git-gps_Prefix.pch"; 192 | INSTALL_PATH = /usr/local/bin; 193 | PRODUCT_NAME = "git-gps"; 194 | }; 195 | name = Release; 196 | }; 197 | 1DEB927908733DD40010E9CD /* Debug */ = { 198 | isa = XCBuildConfiguration; 199 | buildSettings = { 200 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 201 | GCC_C_LANGUAGE_STANDARD = gnu99; 202 | GCC_OPTIMIZATION_LEVEL = 0; 203 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 204 | GCC_WARN_UNUSED_VARIABLE = YES; 205 | ONLY_ACTIVE_ARCH = YES; 206 | PREBINDING = NO; 207 | SDKROOT = macosx10.6; 208 | }; 209 | name = Debug; 210 | }; 211 | 1DEB927A08733DD40010E9CD /* Release */ = { 212 | isa = XCBuildConfiguration; 213 | buildSettings = { 214 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 215 | GCC_C_LANGUAGE_STANDARD = gnu99; 216 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 217 | GCC_WARN_UNUSED_VARIABLE = YES; 218 | PREBINDING = NO; 219 | SDKROOT = macosx10.6; 220 | }; 221 | name = Release; 222 | }; 223 | /* End XCBuildConfiguration section */ 224 | 225 | /* Begin XCConfigurationList section */ 226 | 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "git-gps" */ = { 227 | isa = XCConfigurationList; 228 | buildConfigurations = ( 229 | 1DEB927508733DD40010E9CD /* Debug */, 230 | 1DEB927608733DD40010E9CD /* Release */, 231 | ); 232 | defaultConfigurationIsVisible = 0; 233 | defaultConfigurationName = Release; 234 | }; 235 | 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "git-gps" */ = { 236 | isa = XCConfigurationList; 237 | buildConfigurations = ( 238 | 1DEB927908733DD40010E9CD /* Debug */, 239 | 1DEB927A08733DD40010E9CD /* Release */, 240 | ); 241 | defaultConfigurationIsVisible = 0; 242 | defaultConfigurationName = Release; 243 | }; 244 | /* End XCConfigurationList section */ 245 | }; 246 | rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; 247 | } 248 | -------------------------------------------------------------------------------- /git-gps/git-gps_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'git-gps' target in the 'git-gps' project. 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /git-gps/source/GGCLDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // GGCLDelegate.h 3 | // git-gps 4 | // 5 | // Created by Andrew Wooster on 7/2/11. 6 | // Copyright 2011 Andrew Wooster. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | @interface GGCLDelegate : NSObject { 14 | CLLocation *location; 15 | @private 16 | BOOL isTracking; 17 | BOOL dirtied; 18 | NSUInteger timerCount; 19 | NSUInteger updateCount; 20 | CLLocationManager *locationManager; 21 | BOOL didFail; 22 | } 23 | @property (nonatomic, retain) CLLocation *location; 24 | - (void)start:(NSTimer *)timer; 25 | - (BOOL)goodEnough; 26 | @end 27 | -------------------------------------------------------------------------------- /git-gps/source/GGCLDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // GGCLDelegate.m 3 | // git-gps 4 | // 5 | // Created by Andrew Wooster on 7/2/11. 6 | // Copyright 2011 Andrew Wooster. All rights reserved. 7 | // 8 | 9 | #import "GGCLDelegate.h" 10 | 11 | 12 | @implementation GGCLDelegate 13 | @synthesize location; 14 | 15 | - (id)init { 16 | if ((self = [super init])) { 17 | locationManager = [[CLLocationManager alloc] init]; 18 | locationManager.delegate = self; 19 | } 20 | return self; 21 | } 22 | 23 | - (void)dealloc { 24 | self.location = nil; 25 | locationManager.delegate = nil; 26 | [locationManager stopUpdatingLocation]; 27 | [locationManager release], locationManager = nil; 28 | [super dealloc]; 29 | } 30 | 31 | - (void)start:(NSTimer *)timer { 32 | if (!isTracking) { 33 | updateCount = 0; 34 | isTracking = YES; 35 | [locationManager startUpdatingLocation]; 36 | } 37 | timerCount++; 38 | } 39 | 40 | - (BOOL)goodEnough { 41 | if (![locationManager locationServicesEnabled]) return YES; 42 | if (!dirtied) return NO; 43 | if (didFail) return YES; 44 | if (updateCount >= 2 && timerCount >= 20) return YES; 45 | return NO; 46 | } 47 | 48 | #pragma mark CLLocationManagerDelegate 49 | - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { 50 | dirtied = YES; 51 | updateCount++; 52 | self.location = newLocation; 53 | } 54 | 55 | - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { 56 | dirtied = YES; 57 | isTracking = NO; 58 | didFail = YES; 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /git-gps/source/GGGitTool.h: -------------------------------------------------------------------------------- 1 | // 2 | // GGGitTool.h 3 | // git-gps 4 | // 5 | // Created by Andrew Wooster on 7/9/11. 6 | // Copyright 2011 Andrew Wooster. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface GGGitTool : NSObject { 13 | @private 14 | NSString *gitPath; 15 | int terminationStatus; 16 | } 17 | + (GGGitTool *)sharedGitTool; 18 | 19 | - (NSString *)gitCommand:(NSArray *)arguments; 20 | - (NSString *)gitAdd:(NSString *)path; 21 | 22 | /*! The terminationStatus of the last command. */ 23 | - (int)terminationStatus; 24 | @end 25 | -------------------------------------------------------------------------------- /git-gps/source/GGGitTool.m: -------------------------------------------------------------------------------- 1 | // 2 | // GGGitTool.m 3 | // git-gps 4 | // 5 | // Created by Andrew Wooster on 7/9/11. 6 | // Copyright 2011 Andrew Wooster. All rights reserved. 7 | // 8 | 9 | #import "GGGitTool.h" 10 | 11 | @interface GGGitTool (Private) 12 | - (NSString *)determineGitPath; 13 | - (NSString *)runTaskAndGatherOutput:(NSTask *)task; 14 | - (NSString *)runGitCommandAndGatherOutput:(NSArray *)arguments; 15 | @end 16 | 17 | static GGGitTool *sharedGitTool = nil; 18 | 19 | @implementation GGGitTool 20 | 21 | + (GGGitTool *)sharedGitTool { 22 | @synchronized(self) { 23 | if (sharedGitTool == nil) { 24 | sharedGitTool = [[GGGitTool alloc] init]; 25 | } 26 | } 27 | return sharedGitTool; 28 | } 29 | 30 | - (id)init { 31 | if ((self = [super init])) { 32 | gitPath = [[self determineGitPath] retain]; 33 | } 34 | return self; 35 | } 36 | 37 | - (void)dealloc { 38 | [gitPath release], gitPath = nil; 39 | [super dealloc]; 40 | } 41 | 42 | - (int)terminationStatus { 43 | return terminationStatus; 44 | } 45 | 46 | - (NSString *)gitCommand:(NSArray *)arguments { 47 | return [self runGitCommandAndGatherOutput:arguments]; 48 | } 49 | 50 | - (NSString *)gitAdd:(NSString *)path { 51 | return [self runGitCommandAndGatherOutput:[NSArray arrayWithObjects:@"add", path, nil]]; 52 | } 53 | 54 | @end 55 | 56 | @implementation GGGitTool (Private) 57 | - (NSString *)determineGitPath { 58 | NSTask *task = [[NSTask alloc] init]; 59 | [task setLaunchPath:@"/usr/bin/which"]; 60 | [task setArguments:[NSArray arrayWithObjects:@"git", nil]]; 61 | NSString *path = [self runTaskAndGatherOutput:task]; 62 | [task release], task = nil; 63 | path = [path stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 64 | 65 | NSFileManager *fm = [NSFileManager defaultManager]; 66 | NSArray *paths = [NSArray arrayWithObjects:path, @"/usr/bin/git", @"/usr/local/bin/git", nil]; 67 | for (NSString *tmpPath in paths) { 68 | if ([fm isExecutableFileAtPath:tmpPath]) { 69 | path = tmpPath; 70 | break; 71 | } 72 | } 73 | 74 | return path; 75 | } 76 | 77 | - (NSString *)runTaskAndGatherOutput:(NSTask *)task { 78 | NSPipe *pipe = [NSPipe pipe]; 79 | [task setStandardOutput:pipe]; 80 | NSFileHandle *file = [pipe fileHandleForReading]; 81 | [task launch]; 82 | [task waitUntilExit]; 83 | NSData *data = [file readDataToEndOfFile]; 84 | NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 85 | return [result autorelease]; 86 | } 87 | 88 | - (NSString *)runGitCommandAndGatherOutput:(NSArray *)arguments { 89 | NSString *result = nil; 90 | 91 | NSTask *task = [[NSTask alloc] init]; 92 | 93 | @try { 94 | [task setLaunchPath:gitPath]; 95 | [task setArguments:arguments]; 96 | result = [self runTaskAndGatherOutput:task]; 97 | result = [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 98 | terminationStatus = [task terminationStatus]; 99 | } 100 | @catch (NSException * e) { 101 | NSLog(@"Exception running task: %@", e); 102 | NSLog(@"git path was: '%@'", gitPath); 103 | NSLog(@"arguments were: %@", arguments); 104 | @throw(e); 105 | } 106 | @finally { 107 | [task release], task = nil; 108 | } 109 | return result; 110 | } 111 | @end 112 | --------------------------------------------------------------------------------