1 /*
2 Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version
7 3 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "commitlog.h"
19 #include "../gource_settings.h"
20 #include "../core/sdlapp.h"
21
22 #include "../core/utf8/utf8.h"
23
filter_utf8(const std::string & str)24 std::string RCommitLog::filter_utf8(const std::string& str) {
25
26 std::string filtered;
27
28 try {
29 utf8::replace_invalid(str.begin(), str.end(), back_inserter(filtered), '?');
30 }
31 catch(...) {
32 filtered = "???";
33 }
34
35 return filtered;
36 }
37
38 //RCommitLog
39
RCommitLog(const std::string & logfile,int firstChar)40 RCommitLog::RCommitLog(const std::string& logfile, int firstChar) {
41
42 logf = 0;
43 seekable = false;
44 success = false;
45 is_dir = false;
46 buffered = false;
47
48 if(logfile == "-") {
49
50 //check first char
51 if(checkFirstChar(firstChar, std::cin)) {
52 logf = new StreamLog();
53 is_dir = false;
54 seekable = false;
55 success = true;
56 }
57
58 return;
59 }
60
61 struct stat fileinfo;
62 int rc = stat(logfile.c_str(), &fileinfo);
63
64 if(rc==0) {
65 is_dir = (fileinfo.st_mode & S_IFDIR) ? true : false;
66
67 if(!is_dir) {
68
69 //check first char
70 std::ifstream testf(logfile.c_str());
71
72 bool firstOK = checkFirstChar(firstChar, testf);
73
74 testf.close();
75
76 if(firstOK) {
77 logf = new SeekLog(logfile);
78 seekable = true;
79 success = true;
80 }
81 }
82 }
83 }
84
~RCommitLog()85 RCommitLog::~RCommitLog() {
86 if(logf!=0) delete logf;
87
88 if(!temp_file.empty()) {
89 remove(temp_file.c_str());
90 }
91 }
92
systemCommand(const std::string & command)93 int RCommitLog::systemCommand(const std::string& command) {
94 int rc = system(command.c_str());
95 return rc;
96 }
97
98 // TODO: implement check for 'nix OSs
requireExecutable(const std::string & exename)99 void RCommitLog::requireExecutable(const std::string& exename) {
100
101 #ifdef _WIN32
102 TCHAR exePath[MAX_PATH];
103 DWORD result = SearchPath(0, exename.c_str(), ".exe", MAX_PATH, exePath, 0);
104
105 if(result) return;
106
107 throw SDLAppException("unable to find %s.exe", exename.c_str());
108 #endif
109 }
110
111 //check firstChar of stream is as expected. if no firstChar defined just returns true.
checkFirstChar(int firstChar,std::istream & stream)112 bool RCommitLog::checkFirstChar(int firstChar, std::istream& stream) {
113
114 //cant check this
115 if(firstChar == -1) return true;
116
117 int c = stream.peek();
118
119 if(firstChar == c) return true;
120
121 return false;
122 }
123
checkFormat()124 bool RCommitLog::checkFormat() {
125 if(!success) return false;
126
127 //read a commit to see if the log is in the correct format
128 if(nextCommit(lastCommit, false)) {
129
130 if(seekable) {
131 //if the log is seekable, go back to the start
132 ((SeekLog*)logf)->seekTo(0.0);
133 lastline.clear();
134 } else {
135 //otherwise set the buffered flag as we have bufferd one commit
136 buffered = true;
137 }
138
139 return true;
140 }
141
142 return false;
143 }
144
getLogCommand()145 std::string RCommitLog::getLogCommand() {
146 return log_command;
147 }
148
isSeekable()149 bool RCommitLog::isSeekable() {
150 return seekable;
151 }
152
getCommitAt(float percent,RCommit & commit)153 bool RCommitLog::getCommitAt(float percent, RCommit& commit) {
154 if(!seekable) return false;
155
156 SeekLog* seeklog = ((SeekLog*)logf);
157
158 //save settings
159 long currpointer = seeklog->getPointer();
160 std::string currlastline = lastline;
161
162 seekTo(percent);
163 bool success = findNextCommit(commit,500);
164
165 //restore settings
166 seeklog->setPointer(currpointer);
167 lastline = currlastline;
168
169 return success;
170 }
171
getNextLine(std::string & line)172 bool RCommitLog::getNextLine(std::string& line) {
173 if(!lastline.empty()) {
174 line = lastline;
175 lastline.clear();
176 return true;
177 }
178
179 return logf->getNextLine(line);
180 }
181
182
seekTo(float percent)183 void RCommitLog::seekTo(float percent) {
184 if(!seekable) return;
185
186 lastline.clear();
187
188 ((SeekLog*)logf)->seekTo(percent);
189 }
190
getPercent()191 float RCommitLog::getPercent() {
192 if(seekable) return ((SeekLog*)logf)->getPercent();
193
194 return 0.0;
195 }
196
findNextCommit(RCommit & commit,int attempts)197 bool RCommitLog::findNextCommit(RCommit& commit, int attempts) {
198
199 for(int i=0;i<attempts;i++) {
200 RCommit c;
201
202 if(nextCommit(c)) {
203 commit = c;
204 return true;
205 }
206 }
207
208 return false;
209 }
210
bufferCommit(RCommit & commit)211 void RCommitLog::bufferCommit(RCommit& commit) {
212 lastCommit = commit;
213 buffered = true;
214 }
215
nextCommit(RCommit & commit,bool validate)216 bool RCommitLog::nextCommit(RCommit& commit, bool validate) {
217
218 if(buffered) {
219 commit = lastCommit;
220 buffered = false;
221 return true;
222 }
223
224 // ensure commit is re-initialized
225 commit = RCommit();
226
227 bool success = parseCommit(commit);
228
229 if(!success) return false;
230
231 commit.postprocess();
232
233 if(validate) return commit.isValid();
234
235 return true;
236 }
237
isFinished()238 bool RCommitLog::isFinished() {
239 if(seekable && logf->isFinished()) return true;
240
241 return false;
242 }
243
hasBufferedCommit()244 bool RCommitLog::hasBufferedCommit() {
245 return buffered;
246 }
247
248 //create temp file
createTempLog()249 bool RCommitLog::createTempLog() {
250 return createTempFile(temp_file);
251 }
252
createTempFile(std::string & temp_file)253 bool RCommitLog::createTempFile(std::string& temp_file) {
254
255 std::string tempdir;
256
257 #ifdef _WIN32
258 DWORD tmplen = GetTempPath(0, 0);
259
260 if(tmplen == 0) return false;
261
262 std::vector<TCHAR> temp(tmplen+1);
263
264 tmplen = GetTempPath(static_cast<DWORD>(temp.size()), &temp[0]);
265
266 if(tmplen == 0 || tmplen >= temp.size()) return false;
267
268 tempdir = std::string(temp.begin(), temp.begin() + static_cast<std::size_t>(tmplen));
269 tempdir += "\\";
270 #else
271 tempdir = "/tmp/";
272 #endif
273
274 char tmplate[1024];
275 snprintf(tmplate, 1024, "%sgource-XXXXXX", tempdir.c_str());
276
277 #ifdef _WIN32
278 if(mktemp(tmplate) < 0) return false;
279 #else
280 if(mkstemp(tmplate) < 0) return false;
281 #endif
282
283 temp_file = std::string(tmplate);
284
285 return true;
286 }
287
288 // RCommitFile
289
RCommitFile(const std::string & filename,const std::string & action,vec3 colour)290 RCommitFile::RCommitFile(const std::string& filename, const std::string& action, vec3 colour) {
291
292 this->filename = RCommitLog::filter_utf8(filename);
293
294 //prepend a root slash
295 if(this->filename[0] != '/') {
296 this->filename.insert(0, 1, '/');
297 }
298
299 this->action = action;
300 this->colour = colour;
301 }
302
RCommit()303 RCommit::RCommit() {
304 timestamp = 0;
305 }
306
fileColour(const std::string & filename)307 vec3 RCommit::fileColour(const std::string& filename) {
308
309 size_t slash = filename.rfind('/');
310 size_t dot = filename.rfind('.');
311
312 if(dot != std::string::npos && dot+1<filename.size() && (slash == std::string::npos || slash < dot)) {
313 std::string file_ext = filename.substr(dot+1);
314
315 return colourHash(file_ext);
316 } else {
317 return vec3(1.0, 1.0, 1.0);
318 }
319 }
320
addFile(const std::string & filename,const std::string & action)321 void RCommit::addFile(const std::string& filename, const std::string& action) {
322 addFile(filename, action, fileColour(filename));
323 }
324
addFile(const std::string & filename,const std::string & action,const vec3 & colour)325 void RCommit::addFile(const std::string& filename, const std::string& action, const vec3& colour) {
326 //check filename against filters
327 if(!gGourceSettings.file_filters.empty()) {
328
329 for(std::vector<Regex*>::iterator ri = gGourceSettings.file_filters.begin(); ri != gGourceSettings.file_filters.end(); ri++) {
330 Regex* r = *ri;
331
332 if(r->match(filename)) {
333 return;
334 }
335 }
336 }
337
338 // Only allow files that have been whitelisted
339 if(!gGourceSettings.file_show_filters.empty()) {
340
341 for(std::vector<Regex*>::iterator ri = gGourceSettings.file_show_filters.begin(); ri != gGourceSettings.file_show_filters.end(); ri++) {
342 Regex* r = *ri;
343
344 if(!r->match(filename)) {
345 return;
346 }
347 }
348 }
349
350 files.push_back(RCommitFile(filename, action, colour));
351 }
352
postprocess()353 void RCommit::postprocess() {
354 username = RCommitLog::filter_utf8(username);
355 }
356
isValid()357 bool RCommit::isValid() {
358
359 //check user against filters, if found, discard commit
360 if(!gGourceSettings.user_filters.empty()) {
361 for(std::vector<Regex*>::iterator ri = gGourceSettings.user_filters.begin(); ri != gGourceSettings.user_filters.end(); ri++) {
362 Regex* r = *ri;
363
364 if(r->match(username)) {
365 return false;
366 }
367 }
368 }
369
370 // Only allow users that have been whitelisted
371 if(!gGourceSettings.user_show_filters.empty()) {
372
373 for(std::vector<Regex*>::iterator ri = gGourceSettings.user_show_filters.begin(); ri != gGourceSettings.user_show_filters.end(); ri++) {
374 Regex* r = *ri;
375
376 if(!r->match(username)) {
377 return false;
378 }
379 }
380 }
381
382
383 return !files.empty();
384 }
385
debug()386 void RCommit::debug() {
387 debugLog("files:\n");
388
389 for(std::list<RCommitFile>::iterator it = files.begin(); it != files.end(); it++) {
390 RCommitFile f = *it;
391 debugLog("%s %s\n", f.action.c_str(), f.filename.c_str());
392 }
393 }
394