5 // Created by Pieter de Bie on 13-06-08.
6 // Copyright 2008 __MyCompanyName__. All rights reserved.
9 #import "PBGitRepository.h"
10 #import "PBGitCommit.h"
11 #import "PBGitWindowController.h"
12 #import "PBGitBinary.h"
14 #import "NSFileHandleExt.h"
15 #import "PBEasyPipe.h"
17 #import "PBGitRevSpecifier.h"
19 NSString* PBGitRepositoryErrorDomain = @"GitXErrorDomain";
21 @implementation PBGitRepository
23 @synthesize revisionList, branches, currentBranch, refs, hasChanged, config;
25 - (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
28 *outError = [NSError errorWithDomain:PBGitRepositoryErrorDomain
30 userInfo:[NSDictionary dictionaryWithObject:@"Reading files is not supported." forKey:NSLocalizedFailureReasonErrorKey]];
35 + (BOOL) isBareRepository: (NSString*) path
37 return [[PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:[NSArray arrayWithObjects:@"rev-parse", @"--is-bare-repository", nil] inDir:path] isEqualToString:@"true"];
40 + (NSURL*)gitDirForURL:(NSURL*)repositoryURL;
42 if (![PBGitBinary path])
45 NSString* repositoryPath = [repositoryURL path];
47 if ([self isBareRepository:repositoryPath])
51 // Use rev-parse to find the .git dir for the repository being opened
52 NSString* newPath = [PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:[NSArray arrayWithObjects:@"rev-parse", @"--git-dir", nil] inDir:repositoryPath];
53 if ([newPath isEqualToString:@".git"])
54 return [NSURL fileURLWithPath:[repositoryPath stringByAppendingPathComponent:@".git"]];
55 if ([newPath length] > 0)
56 return [NSURL fileURLWithPath:newPath];
61 // For a given path inside a repository, return either the .git dir
62 // (for a bare repo) or the directory above the .git dir otherwise
63 + (NSURL*)baseDirForURL:(NSURL*)repositoryURL;
65 NSURL* gitDirURL = [self gitDirForURL:repositoryURL];
66 NSString* repositoryPath = [gitDirURL path];
68 if (![self isBareRepository:repositoryPath]) {
69 repositoryURL = [NSURL fileURLWithPath:[[repositoryURL path] stringByDeletingLastPathComponent]];
75 - (BOOL)readFromFileWrapper:(NSFileWrapper *)fileWrapper ofType:(NSString *)typeName error:(NSError **)outError
79 if (![PBGitBinary path])
82 NSDictionary* userInfo = [NSDictionary dictionaryWithObject:[PBGitBinary notFoundError]
83 forKey:NSLocalizedRecoverySuggestionErrorKey];
84 *outError = [NSError errorWithDomain:PBGitRepositoryErrorDomain code:0 userInfo:userInfo];
89 if (![fileWrapper isDirectory]) {
91 NSDictionary* userInfo = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Reading files is not supported.", [fileWrapper filename]]
92 forKey:NSLocalizedRecoverySuggestionErrorKey];
93 *outError = [NSError errorWithDomain:PBGitRepositoryErrorDomain code:0 userInfo:userInfo];
96 NSURL* gitDirURL = [PBGitRepository gitDirForURL:[self fileURL]];
98 [self setFileURL:gitDirURL];
100 } else if (outError) {
101 NSDictionary* userInfo = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"%@ does not appear to be a git repository.", [fileWrapper filename]]
102 forKey:NSLocalizedRecoverySuggestionErrorKey];
103 *outError = [NSError errorWithDomain:PBGitRepositoryErrorDomain code:0 userInfo:userInfo];
108 [self readCurrentBranch];
117 config = [[PBGitConfig alloc] initWithRepository:self.fileURL.path];
118 self.branches = [NSMutableArray array];
120 revisionList = [[PBGitRevList alloc] initWithRepository:self];
123 - (id) initWithURL: (NSURL*) path
125 if (![PBGitBinary path])
128 NSURL* gitDirURL = [PBGitRepository gitDirForURL:path];
133 [self setFileURL: gitDirURL];
137 // We don't want the window controller to display anything yet..
138 // We'll leave that to the caller of this method.
140 [self addWindowController:[[PBGitWindowController alloc] initWithRepository:self displayDefault:NO]];
148 // The fileURL the document keeps is to the .git dir, but that’s pretty
149 // useless for display in the window title bar, so we show the directory above
150 - (NSString*)displayName
152 NSString* displayName = self.fileURL.path.lastPathComponent;
153 if ([displayName isEqualToString:@".git"])
154 displayName = [self.fileURL.path stringByDeletingLastPathComponent].lastPathComponent;
158 // Get the .gitignore file at the root of the repository
159 - (NSString*)gitIgnoreFilename
161 return [[self workingDirectory] stringByAppendingPathComponent:@".gitignore"];
164 // Overridden to create our custom window controller
165 - (void)makeWindowControllers
168 [self addWindowController: [[PBGitWindowController alloc] initWithRepository:self displayDefault:YES]];
172 - (NSWindowController *)windowController
174 if ([[self windowControllers] count] == 0)
177 return [[self windowControllers] objectAtIndex:0];
180 - (void) addRef: (PBGitRef *) ref fromParameters: (NSArray *) components
182 NSString* type = [components objectAtIndex:1];
185 if ([type isEqualToString:@"tag"] && [components count] == 4)
186 sha = [components objectAtIndex:3];
188 sha = [components objectAtIndex:2];
190 NSMutableArray* curRefs;
191 if (curRefs = [refs objectForKey:sha])
192 [curRefs addObject:ref];
194 [refs setObject:[NSMutableArray arrayWithObject:ref] forKey:sha];
197 // reloadRefs: reload all refs in the repository, like in readRefs
198 // To stay compatible, this does not remove a ref from the branches list
199 // even after it has been deleted.
200 // returns YES when a ref was changed
206 refs = [NSMutableDictionary dictionary];
208 NSString* output = [PBEasyPipe outputForCommand:[PBGitBinary path]
209 withArgs:[NSArray arrayWithObjects:@"for-each-ref", @"--format=%(refname) %(objecttype) %(objectname)"
210 " %(*objectname)", @"refs", nil]
211 inDir: self.fileURL.path];
212 NSArray* lines = [output componentsSeparatedByString:@"\n"];
214 for (NSString* line in lines) {
215 // If its an empty line, skip it (e.g. with empty repositories)
216 if ([line length] == 0)
219 NSArray* components = [line componentsSeparatedByString:@" "];
221 // First do the ref matching. If this ref is new, add it to our ref list
222 PBGitRef *newRef = [PBGitRef refFromString:[components objectAtIndex:0]];
223 PBGitRevSpecifier* revSpec = [[PBGitRevSpecifier alloc] initWithRef:newRef];
224 if ([self addBranch:revSpec] != revSpec)
227 // Also add this ref to the refs list
228 [self addRef:newRef fromParameters:components];
231 // Add an "All branches" option in the branches list
232 [self addBranch:[PBGitRevSpecifier allBranchesRevSpec]];
233 [self addBranch:[PBGitRevSpecifier localBranchesRevSpec]];
243 [self.revisionList reload];
247 - (PBGitRevSpecifier *)headRef
252 NSString* branch = [self parseSymbolicReference: @"HEAD"];
253 if (branch && [branch hasPrefix:@"refs/heads/"])
254 _headRef = [[PBGitRevSpecifier alloc] initWithRef:[PBGitRef refFromString:branch]];
256 _headRef = [[PBGitRevSpecifier alloc] initWithRef:[PBGitRef refFromString:@"HEAD"]];
261 // Returns either this object, or an existing, equal object
262 - (PBGitRevSpecifier*) addBranch: (PBGitRevSpecifier*) rev
264 if ([[rev parameters] count] == 0)
265 rev = [self headRef];
267 // First check if the branch doesn't exist already
268 for (PBGitRevSpecifier* r in branches)
269 if ([rev isEqualTo: r])
272 [self willChangeValueForKey:@"branches"];
273 [branches addObject: rev];
274 [self didChangeValueForKey:@"branches"];
278 - (void) readCurrentBranch
280 self.currentBranch = [self addBranch: [self headRef]];
283 - (NSString *) workingDirectory
285 if ([self.fileURL.path hasSuffix:@"/.git"])
286 return [self.fileURL.path substringToIndex:[self.fileURL.path length] - 5];
287 else if ([[self outputForCommand:@"rev-parse --is-inside-work-tree"] isEqualToString:@"true"])
288 return [PBGitBinary path];
293 - (int) returnValueForCommand:(NSString *)cmd
296 [self outputForCommand:cmd retValue: &i];
300 - (NSFileHandle*) handleForArguments:(NSArray *)args
302 NSString* gitDirArg = [@"--git-dir=" stringByAppendingString:self.fileURL.path];
303 NSMutableArray* arguments = [NSMutableArray arrayWithObject: gitDirArg];
304 [arguments addObjectsFromArray: args];
305 return [PBEasyPipe handleForCommand:[PBGitBinary path] withArgs:arguments];
308 - (NSFileHandle*) handleInWorkDirForArguments:(NSArray *)args
310 NSString* gitDirArg = [@"--git-dir=" stringByAppendingString:self.fileURL.path];
311 NSMutableArray* arguments = [NSMutableArray arrayWithObject: gitDirArg];
312 [arguments addObjectsFromArray: args];
313 return [PBEasyPipe handleForCommand:[PBGitBinary path] withArgs:arguments inDir:[self workingDirectory]];
316 - (NSFileHandle*) handleForCommand:(NSString *)cmd
318 NSArray* arguments = [cmd componentsSeparatedByString:@" "];
319 return [self handleForArguments:arguments];
322 - (NSString*) outputForCommand:(NSString *)cmd
324 NSArray* arguments = [cmd componentsSeparatedByString:@" "];
325 return [self outputForArguments: arguments];
328 - (NSString*) outputForCommand:(NSString *)str retValue:(int *)ret;
330 NSArray* arguments = [str componentsSeparatedByString:@" "];
331 return [self outputForArguments: arguments retValue: ret];
334 - (NSString*) outputForArguments:(NSArray*) arguments
336 return [PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:arguments inDir: self.fileURL.path];
339 - (NSString*) outputInWorkdirForArguments:(NSArray*) arguments
341 return [PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:arguments inDir: [self workingDirectory]];
344 - (NSString*) outputInWorkdirForArguments:(NSArray *)arguments retValue:(int *)ret
346 return [PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:arguments inDir:[self workingDirectory] retValue: ret];
349 - (NSString*) outputForArguments:(NSArray *)arguments retValue:(int *)ret
351 return [PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:arguments inDir: self.fileURL.path retValue: ret];
354 - (NSString*) outputForArguments:(NSArray *)arguments inputString:(NSString *)input retValue:(int *)ret;
356 return [PBEasyPipe outputForCommand:[PBGitBinary path]
358 inDir:[self workingDirectory]
363 - (NSString *)parseReference:(NSString *)reference
366 NSString *ref = [self outputForArguments:[NSArray arrayWithObjects: @"rev-parse", @"--verify", reference, nil] retValue: &ret];
373 - (NSString*) parseSymbolicReference:(NSString*) reference
375 NSString* ref = [self outputForArguments:[NSArray arrayWithObjects: @"symbolic-ref", @"-q", reference, nil]];
376 if ([ref hasPrefix:@"refs/"])
384 NSLog(@"Dealloc of repository");