Release notes for 0.6.3
[GitX.git] / PBGitRepository.m
blobe59b129aa282af6630a15f639777026143122944
1 //
2 //  PBGitRepository.m
3 //  GitTest
4 //
5 //  Created by Pieter de Bie on 13-06-08.
6 //  Copyright 2008 __MyCompanyName__. All rights reserved.
7 //
9 #import "PBGitRepository.h"
10 #import "PBGitCommit.h"
11 #import "PBGitWindowController.h"
12 #import "PBGitBinary.h"
14 #import "NSFileHandleExt.h"
15 #import "PBEasyPipe.h"
16 #import "PBGitRef.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
27         if (outError) {
28                 *outError = [NSError errorWithDomain:PBGitRepositoryErrorDomain
29                                       code:0
30                                   userInfo:[NSDictionary dictionaryWithObject:@"Reading files is not supported." forKey:NSLocalizedFailureReasonErrorKey]];
31         }
32         return NO;
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])
43                 return nil;
45         NSString* repositoryPath = [repositoryURL path];
47         if ([self isBareRepository:repositoryPath])
48                 return repositoryURL;
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];
58         return nil;
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]];
70         }
72         return repositoryURL;
75 - (BOOL)readFromFileWrapper:(NSFileWrapper *)fileWrapper ofType:(NSString *)typeName error:(NSError **)outError
77         BOOL success = NO;
79         if (![PBGitBinary path])
80         {
81                 if (outError) {
82                         NSDictionary* userInfo = [NSDictionary dictionaryWithObject:[PBGitBinary notFoundError]
83                                                                                                                                  forKey:NSLocalizedRecoverySuggestionErrorKey];
84                         *outError = [NSError errorWithDomain:PBGitRepositoryErrorDomain code:0 userInfo:userInfo];
85                 }
86                 return NO;
87         }
89         if (![fileWrapper isDirectory]) {
90                 if (outError) {
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];
94                 }
95         } else {
96                 NSURL* gitDirURL = [PBGitRepository gitDirForURL:[self fileURL]];
97                 if (gitDirURL) {
98                         [self setFileURL:gitDirURL];
99                         success = YES;
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];
104                 }
106                 if (success) {
107                         [self setup];
108                         [self readCurrentBranch];
109                 }
110         }
112         return success;
115 - (void) setup
117         config = [[PBGitConfig alloc] initWithRepository:self.fileURL.path];
118         self.branches = [NSMutableArray array];
119         [self reloadRefs];
120         revisionList = [[PBGitRevList alloc] initWithRepository:self];
123 - (id) initWithURL: (NSURL*) path
125         if (![PBGitBinary path])
126                 return nil;
128         NSURL* gitDirURL = [PBGitRepository gitDirForURL:path];
129         if (!gitDirURL)
130                 return nil;
132         self = [self init];
133         [self setFileURL: gitDirURL];
135         [self setup];
136         
137         // We don't want the window controller to display anything yet..
138         // We'll leave that to the caller of this method.
139 #ifndef CLI
140         [self addWindowController:[[PBGitWindowController alloc] initWithRepository:self displayDefault:NO]];
141 #endif
143         [self showWindows];
145         return self;
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;
155         return displayName;
158 // Get the .gitignore file at the root of the repository
159 - (NSString*)gitIgnoreFilename
161         return [[self workingDirectory] stringByAppendingPathComponent:@".gitignore"];
164 - (BOOL)isBareRepository
166         return [PBGitRepository isBareRepository:[self fileURL].path];
169 // Overridden to create our custom window controller
170 - (void)makeWindowControllers
172 #ifndef CLI
173         [self addWindowController: [[PBGitWindowController alloc] initWithRepository:self displayDefault:YES]];
174 #endif
177 - (NSWindowController *)windowController
179         if ([[self windowControllers] count] == 0)
180                 return NULL;
181         
182         return [[self windowControllers] objectAtIndex:0];
185 - (void) addRef: (PBGitRef *) ref fromParameters: (NSArray *) components
187         NSString* type = [components objectAtIndex:1];
189         NSString* sha;
190         if ([type isEqualToString:@"tag"] && [components count] == 4)
191                 sha = [components objectAtIndex:3];
192         else
193                 sha = [components objectAtIndex:2];
195         NSMutableArray* curRefs;
196         if (curRefs = [refs objectForKey:sha])
197                 [curRefs addObject:ref];
198         else
199                 [refs setObject:[NSMutableArray arrayWithObject:ref] forKey:sha];
202 // reloadRefs: reload all refs in the repository, like in readRefs
203 // To stay compatible, this does not remove a ref from the branches list
204 // even after it has been deleted.
205 // returns YES when a ref was changed
206 - (BOOL) reloadRefs
208         _headRef = nil;
209         BOOL ret = NO;
211         refs = [NSMutableDictionary dictionary];
213         NSString* output = [PBEasyPipe outputForCommand:[PBGitBinary path]
214                                                                                    withArgs:[NSArray arrayWithObjects:@"for-each-ref", @"--format=%(refname) %(objecttype) %(objectname)"
215                                                                                                          " %(*objectname)", @"refs", nil]
216                                                                                           inDir: self.fileURL.path];
217         NSArray* lines = [output componentsSeparatedByString:@"\n"];
219         for (NSString* line in lines) {
220                 // If its an empty line, skip it (e.g. with empty repositories)
221                 if ([line length] == 0)
222                         continue;
224                 NSArray* components = [line componentsSeparatedByString:@" "];
226                 // First do the ref matching. If this ref is new, add it to our ref list
227                 PBGitRef *newRef = [PBGitRef refFromString:[components objectAtIndex:0]];
228                 PBGitRevSpecifier* revSpec = [[PBGitRevSpecifier alloc] initWithRef:newRef];
229                 if ([self addBranch:revSpec] != revSpec)
230                         ret = YES;
232                 // Also add this ref to the refs list
233                 [self addRef:newRef fromParameters:components];
234         }
236         // Add an "All branches" option in the branches list
237         [self addBranch:[PBGitRevSpecifier allBranchesRevSpec]];
238         [self addBranch:[PBGitRevSpecifier localBranchesRevSpec]];
239         return ret;
242 - (void) lazyReload
244         if (!hasChanged)
245                 return;
247         [self reloadRefs];
248         [self.revisionList reload];
249         hasChanged = NO;
252 - (PBGitRevSpecifier *)headRef
254         if (_headRef)
255                 return _headRef;
257         NSString* branch = [self parseSymbolicReference: @"HEAD"];
258         if (branch && [branch hasPrefix:@"refs/heads/"])
259                 _headRef = [[PBGitRevSpecifier alloc] initWithRef:[PBGitRef refFromString:branch]];
260         else
261                 _headRef = [[PBGitRevSpecifier alloc] initWithRef:[PBGitRef refFromString:@"HEAD"]];
263         return _headRef;
265                 
266 // Returns either this object, or an existing, equal object
267 - (PBGitRevSpecifier*) addBranch: (PBGitRevSpecifier*) rev
269         if ([[rev parameters] count] == 0)
270                 rev = [self headRef];
272         // First check if the branch doesn't exist already
273         for (PBGitRevSpecifier* r in branches)
274                 if ([rev isEqualTo: r])
275                         return r;
277         [self willChangeValueForKey:@"branches"];
278         [branches addObject: rev];
279         [self didChangeValueForKey:@"branches"];
280         return rev;
282         
283 - (void) readCurrentBranch
285                 self.currentBranch = [self addBranch: [self headRef]];
288 - (NSString *) workingDirectory
290         if ([self.fileURL.path hasSuffix:@"/.git"])
291                 return [self.fileURL.path substringToIndex:[self.fileURL.path length] - 5];
292         else if ([[self outputForCommand:@"rev-parse --is-inside-work-tree"] isEqualToString:@"true"])
293                 return [PBGitBinary path];
294         
295         return nil;
296 }               
298 - (int) returnValueForCommand:(NSString *)cmd
300         int i;
301         [self outputForCommand:cmd retValue: &i];
302         return i;
305 - (NSFileHandle*) handleForArguments:(NSArray *)args
307         NSString* gitDirArg = [@"--git-dir=" stringByAppendingString:self.fileURL.path];
308         NSMutableArray* arguments =  [NSMutableArray arrayWithObject: gitDirArg];
309         [arguments addObjectsFromArray: args];
310         return [PBEasyPipe handleForCommand:[PBGitBinary path] withArgs:arguments];
313 - (NSFileHandle*) handleInWorkDirForArguments:(NSArray *)args
315         NSString* gitDirArg = [@"--git-dir=" stringByAppendingString:self.fileURL.path];
316         NSMutableArray* arguments =  [NSMutableArray arrayWithObject: gitDirArg];
317         [arguments addObjectsFromArray: args];
318         return [PBEasyPipe handleForCommand:[PBGitBinary path] withArgs:arguments inDir:[self workingDirectory]];
321 - (NSFileHandle*) handleForCommand:(NSString *)cmd
323         NSArray* arguments = [cmd componentsSeparatedByString:@" "];
324         return [self handleForArguments:arguments];
327 - (NSString*) outputForCommand:(NSString *)cmd
329         NSArray* arguments = [cmd componentsSeparatedByString:@" "];
330         return [self outputForArguments: arguments];
333 - (NSString*) outputForCommand:(NSString *)str retValue:(int *)ret;
335         NSArray* arguments = [str componentsSeparatedByString:@" "];
336         return [self outputForArguments: arguments retValue: ret];
339 - (NSString*) outputForArguments:(NSArray*) arguments
341         return [PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:arguments inDir: self.fileURL.path];
344 - (NSString*) outputInWorkdirForArguments:(NSArray*) arguments
346         return [PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:arguments inDir: [self workingDirectory]];
349 - (NSString*) outputInWorkdirForArguments:(NSArray *)arguments retValue:(int *)ret
351         return [PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:arguments inDir:[self workingDirectory] retValue: ret];
354 - (NSString*) outputForArguments:(NSArray *)arguments retValue:(int *)ret
356         return [PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:arguments inDir: self.fileURL.path retValue: ret];
359 - (NSString*) outputForArguments:(NSArray *)arguments inputString:(NSString *)input retValue:(int *)ret;
361         return [PBEasyPipe outputForCommand:[PBGitBinary path]
362                                                            withArgs:arguments
363                                                                   inDir:[self workingDirectory]
364                                                         inputString:input
365                                                            retValue: ret];
368 - (NSString *)parseReference:(NSString *)reference
370         int ret = 1;
371         NSString *ref = [self outputForArguments:[NSArray arrayWithObjects: @"rev-parse", @"--verify", reference, nil] retValue: &ret];
372         if (ret)
373                 return nil;
375         return ref;
378 - (NSString*) parseSymbolicReference:(NSString*) reference
380         NSString* ref = [self outputForArguments:[NSArray arrayWithObjects: @"symbolic-ref", @"-q", reference, nil]];
381         if ([ref hasPrefix:@"refs/"])
382                 return ref;
384         return nil;
387 - (void) finalize
389         NSLog(@"Dealloc of repository");
390         [super finalize];
392 @end