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