Merge branch 'pu/pb/index_quick_fix' into stable
[GitX.git] / PBGitRevList.mm
blob1d8023adcd2fc0b6601f1b3315cb29f55c35bec2
1 //
2 //  PBGitRevList.m
3 //  GitX
4 //
5 //  Created by Pieter de Bie on 17-06-08.
6 //  Copyright 2008 __MyCompanyName__. All rights reserved.
7 //
9 #import "PBGitRevList.h"
10 #import "PBGitRepository.h"
11 #import "PBGitCommit.h"
12 #import "PBGitGrapher.h"
13 #import "PBGitRevSpecifier.h"
15 #include "git/oid.h"
16 #include <ext/stdio_filebuf.h>
17 #include <iostream>
18 #include <string>
19 #include <map>
21 using namespace std;
23 @implementation PBGitRevList
25 @synthesize commits;
26 - initWithRepository: (id) repo
28         repository = repo;
29         [repository addObserver:self forKeyPath:@"currentBranch" options:0 context:nil];
31         return self;
34 - (void) reload
36         [self readCommitsForce: YES];
39 - (void) readCommitsForce: (BOOL) force
41         // We use refparse to get the commit sha that we will parse. That way,
42         // we can check if the current branch is the same as the previous one
43         // and in that case we don't have to reload the revision list.
45         // If no branch is selected, don't do anything
46         if (![repository currentBranch])
47                 return;
49         PBGitRevSpecifier* newRev = [repository currentBranch];
50         NSString* newSha = nil;
52         if (!force && newRev && [newRev isSimpleRef]) {
53                 newSha = [repository parseReference:[newRev simpleRef]];
54                 if ([newSha isEqualToString:lastSha])
55                         return;
56         }
57         lastSha = newSha;
59         NSThread * commitThread = [[NSThread alloc] initWithTarget: self selector: @selector(walkRevisionListWithSpecifier:) object:newRev];
60         [commitThread start];
63 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
64         change:(NSDictionary *)change context:(void *)context
66         if (object == repository)
67                 [self readCommitsForce: NO];
70 - (void) walkRevisionListWithSpecifier: (PBGitRevSpecifier*) rev
72         NSDate *start = [NSDate date];
73         NSMutableArray* revisions = [NSMutableArray array];
74         PBGitGrapher* g = [[PBGitGrapher alloc] initWithRepository: repository];
75         std::map<string, NSStringEncoding> encodingMap;
77         NSMutableArray* arguments;
78         BOOL showSign = [rev hasLeftRight];
80         if (showSign)
81                 arguments = [NSMutableArray arrayWithObjects:@"log", @"-z", @"--early-output", @"--topo-order", @"--pretty=format:%H\01%e\01%an\01%s\01%P\01%at\01%m", nil];
82         else
83                 arguments = [NSMutableArray arrayWithObjects:@"log", @"-z",  @"--early-output", @"--topo-order", @"--pretty=format:%H\01%e\01%an\01%s\01%P\01%at", nil];
85         if (!rev)
86                 [arguments addObject:@"HEAD"];
87         else
88                 [arguments addObjectsFromArray:[rev parameters]];
90         if ([rev hasPathLimiter])
91                 [arguments insertObject:@"--children" atIndex:1];
93         NSTask *task = [PBEasyPipe taskForCommand:[PBGitBinary path] withArgs:arguments inDir:[repository fileURL].path];
94         [task launch];
95         NSFileHandle* handle = [task.standardOutput fileHandleForReading];
96         
97         int fd = [handle fileDescriptor];
98         __gnu_cxx::stdio_filebuf<char> buf(fd, std::ios::in);
99         std::istream stream(&buf);
101         int num = 0;
102         while (true) {
103                 string sha;
104                 if (!getline(stream, sha, '\1'))
105                         break;
107                 // We reached the end of some temporary output. Show what we have
108                 // until now, and then start again. The sha of the next thing is still
109                 // in this buffer. So, we use a substring of current input.
110                 if (sha[1] == 'i') // Matches 'Final output'
111                 {
112                         num = 0;
113                         [self performSelectorOnMainThread:@selector(setCommits:) withObject:revisions waitUntilDone:NO];
114                         g = [[PBGitGrapher alloc] initWithRepository: repository];
115                         revisions = [NSMutableArray array];
117                         // If the length is < 40, then there are no commits.. quit now
118                         if (sha.length() < 40)
119                                 break;
121                         sha = sha.substr(sha.length() - 40, 40);
122                 }
124                 // From now on, 1.2 seconds
125                 string encoding_str;
126                 getline(stream, encoding_str, '\1');
127                 NSStringEncoding encoding = NSUTF8StringEncoding;
128                 if (encoding_str.length())
129                 {
130                         if (encodingMap.find(encoding_str) != encodingMap.end()) {
131                                 encoding = encodingMap[encoding_str];
132                         } else {
133                                 encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)[NSString stringWithUTF8String:encoding_str.c_str()]));
134                                 encodingMap[encoding_str] = encoding;
135                         }
136                 }
138                 git_oid oid;
139                 git_oid_mkstr(&oid, sha.c_str());
140                 PBGitCommit* newCommit = [[PBGitCommit alloc] initWithRepository:repository andSha:oid];
142                 string author;
143                 getline(stream, author, '\1');
145                 string subject;
146                 getline(stream, subject, '\1');
148                 string parentString;
149                 getline(stream, parentString, '\1');
150                 if (parentString.size() != 0)
151                 {
152                         if (((parentString.size() + 1) % 41) != 0) {
153                                 NSLog(@"invalid parents: %i", parentString.size());
154                                 continue;
155                         }
156                         int nParents = (parentString.size() + 1) / 41;
157                         git_oid *parents = (git_oid *)malloc(sizeof(git_oid) * nParents);
158                         int parentIndex;
159                         for (parentIndex = 0; parentIndex < nParents; ++parentIndex)
160                                 git_oid_mkstr(parents + parentIndex, parentString.substr(parentIndex * 41, 40).c_str());
161                         
162                         newCommit.parentShas = parents;
163                         newCommit.nParents = nParents;
164                 }
166                 int time;
167                 stream >> time;
169                 
170                 [newCommit setSubject:[NSString stringWithCString:subject.c_str() encoding:encoding]];
171                 [newCommit setAuthor:[NSString stringWithCString:author.c_str() encoding:encoding]];
172                 [newCommit setTimestamp:time];
173                 
174                 if (showSign)
175                 {
176                         char c;
177                         stream >> c; // Remove separator
178                         stream >> c;
179                         if (c != '>' && c != '<' && c != '^' && c != '-')
180                                 NSLog(@"Error loading commits: sign not correct");
181                         [newCommit setSign: c];
182                 }
184                 char c;
185                 stream >> c;
186                 if (c != '\0')
187                         cout << "Error" << endl;
189                 [revisions addObject: newCommit];
190                 [g decorateCommit: newCommit];
192                 if (++num % 1000 == 0)
193                         [self performSelectorOnMainThread:@selector(setCommits:) withObject:revisions waitUntilDone:NO];
194         }
195         
196         NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:start];
197         NSLog(@"Loaded %i commits in %f seconds", num, duration);
198         // Make sure the commits are stored before exiting.
199         [self performSelectorOnMainThread:@selector(setCommits:) withObject:revisions waitUntilDone:YES];
200         [task waitUntilExit];
203 @end