1 /*
2     Copyright (C) 2012 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 "logmill.h"
19 #include "gource_settings.h"
20 
21 #include "formats/git.h"
22 #include "formats/gitraw.h"
23 #include "formats/custom.h"
24 #include "formats/hg.h"
25 #include "formats/bzr.h"
26 #include "formats/svn.h"
27 #include "formats/apache.h"
28 #include "formats/cvs-exp.h"
29 #include "formats/cvs2cl.h"
30 
31 #include <boost/filesystem.hpp>
32 
33 extern "C" {
34 
logmill_thread(void * lmill)35     static int logmill_thread(void *lmill) {
36 
37         RLogMill *logmill = static_cast<RLogMill*> (lmill);
38         logmill->run();
39 
40         return 0;
41     }
42 
43 };
44 
RLogMill(const std::string & logfile)45 RLogMill::RLogMill(const std::string& logfile)
46     : logfile(logfile) {
47 
48     logmill_thread_state = LOGMILL_STATE_STARTUP;
49     clog = 0;
50 
51 #if SDL_VERSION_ATLEAST(2,0,0)
52     thread = SDL_CreateThread( logmill_thread, "logmill", this );
53 #else
54     thread = SDL_CreateThread( logmill_thread, this );
55 #endif
56 }
57 
~RLogMill()58 RLogMill::~RLogMill() {
59 
60     abort();
61 
62     if(clog) delete clog;
63 }
64 
run()65 void RLogMill::run() {
66     logmill_thread_state = LOGMILL_STATE_FETCHING;
67 
68 #if defined(HAVE_PTHREAD) && !defined(_WIN32)
69     sigset_t mask;
70     sigemptyset(&mask);
71 
72     // unblock SIGINT so user can cancel
73     // NOTE: assumes SDL is using pthreads
74 
75     sigaddset(&mask, SIGINT);
76     pthread_sigmask(SIG_UNBLOCK, &mask, 0);
77 #endif
78 
79     std::string log_format = gGourceSettings.log_format;
80 
81     try {
82 
83         clog = fetchLog(log_format);
84 
85         // find first commit after start_timestamp if specified
86         if(clog != 0 && gGourceSettings.start_timestamp != 0) {
87 
88             RCommit commit;
89 
90             while(!gGourceSettings.shutdown && !clog->isFinished()) {
91 
92                 if(clog->nextCommit(commit) && commit.timestamp >= gGourceSettings.start_timestamp) {
93                     clog->bufferCommit(commit);
94                     break;
95                 }
96             }
97         }
98 
99     } catch(SeekLogException& exception) {
100         error = "unable to read log file";
101     } catch(SDLAppException& exception) {
102         error = exception.what();
103     }
104 
105     if(!clog && error.empty()) {
106         if(boost::filesystem::is_directory(logfile)) {
107             if(!log_format.empty()) {
108 
109                 if(gGourceSettings.start_timestamp || gGourceSettings.stop_timestamp) {
110                     error = "failed to generate log file for the specified time period";
111                 } else {
112                     error = "failed to generate log file";
113                 }
114 #ifdef _WIN32
115             // no error - should trigger help message
116             } else if(gGourceSettings.default_path && boost::filesystem::exists("./gource.exe")) {
117                 error = "";
118 #endif
119             } else {
120                 error = "directory not supported";
121             }
122         } else {
123             error = "unsupported log format (you may need to regenerate your log file)";
124         }
125     }
126 
127     logmill_thread_state = clog ? LOGMILL_STATE_SUCCESS : LOGMILL_STATE_FAILURE;
128 }
129 
abort()130 void RLogMill::abort() {
131     if(!thread) return;
132 
133     // TODO: make abort nicer by notifying the log process
134     //       we want to shutdown
135     SDL_WaitThread(thread, 0);
136 
137     thread = 0;
138 }
139 
isFinished()140 bool RLogMill::isFinished() {
141     return logmill_thread_state > LOGMILL_STATE_FETCHING;
142 }
143 
getStatus()144 int RLogMill::getStatus() {
145     return logmill_thread_state;
146 }
147 
getError()148 std::string RLogMill::getError() {
149     return error;
150 }
151 
152 
getLog()153 RCommitLog* RLogMill::getLog() {
154 
155     if(thread != 0) {
156         SDL_WaitThread(thread, 0);
157         thread = 0;
158     }
159 
160     return clog;
161 }
162 
findRepository(boost::filesystem::path & dir,std::string & log_format)163 bool RLogMill::findRepository(boost::filesystem::path& dir, std::string& log_format) {
164 
165     dir = canonical(dir);
166 
167     //fprintf(stderr, "find repository from initial path: %s\n", dir.string().c_str());
168 
169     while(is_directory(dir)) {
170 
171              if(is_directory(dir / ".git") || is_regular_file(dir / ".git")) log_format = "git";
172         else if(is_directory(dir / ".hg"))  log_format = "hg";
173         else if(is_directory(dir / ".bzr")) log_format = "bzr";
174         else if(is_directory(dir / ".svn")) log_format = "svn";
175 
176         if(!log_format.empty()) {
177             //fprintf(stderr, "found '%s' repository at: %s\n", log_format.c_str(), dir.string().c_str());
178             return true;
179         }
180 
181         if(!dir.has_parent_path()) return false;
182 
183         dir = dir.parent_path();
184     }
185 
186     return false;
187 }
188 
189 
fetchLog(std::string & log_format)190 RCommitLog* RLogMill::fetchLog(std::string& log_format) {
191 
192     RCommitLog* clog = 0;
193 
194     //if the log format is not specified and 'logfile' is a directory, recursively look for a version control repository.
195     //this method allows for something strange like someone who having an svn repository inside a git repository
196     //(in which case it would pick the svn directory as it would encounter that first)
197 
198     if(log_format.empty() && logfile != "-") {
199 
200         try {
201             boost::filesystem::path repo_path(logfile);
202 
203             if(is_directory(repo_path)) {
204                 if(findRepository(repo_path, log_format)) {
205                     logfile = repo_path.string();
206                 }
207             }
208         } catch(boost::filesystem::filesystem_error& error) {
209         }
210     }
211 
212     //we've been told what format to use
213     if(log_format.size() > 0) {
214         debugLog("log-format = %s", log_format.c_str());
215 
216         if(log_format == "git") {
217             clog = new GitCommitLog(logfile);
218             if(clog->checkFormat()) return clog;
219             delete clog;
220 
221             clog = new GitRawCommitLog(logfile);
222             if(clog->checkFormat()) return clog;
223             delete clog;
224         }
225 
226         if(log_format == "hg") {
227             clog = new MercurialLog(logfile);
228             if(clog->checkFormat()) return clog;
229             delete clog;
230         }
231 
232         if(log_format == "bzr") {
233             clog = new BazaarLog(logfile);
234             if(clog->checkFormat()) return clog;
235             delete clog;
236         }
237 
238         if(log_format == "cvs") {
239             clog = new CVSEXPCommitLog(logfile);
240             if(clog->checkFormat()) return clog;
241             delete clog;
242         }
243 
244         if(log_format == "custom") {
245             clog = new CustomLog(logfile);
246             if(clog->checkFormat()) return clog;
247             delete clog;
248         }
249 
250         if(log_format == "apache") {
251             clog = new ApacheCombinedLog(logfile);
252             if(clog->checkFormat()) return clog;
253             delete clog;
254         }
255 
256         if(log_format == "svn") {
257             clog = new SVNCommitLog(logfile);
258             if(clog->checkFormat()) return clog;
259             delete clog;
260         }
261 
262         if(log_format == "cvs2cl") {
263             clog = new CVS2CLCommitLog(logfile);
264             if(clog->checkFormat()) return clog;
265             delete clog;
266         }
267 
268         return 0;
269     }
270 
271     // try different formats until one works
272 
273     //git
274     debugLog("trying git...");
275     clog = new GitCommitLog(logfile);
276     if(clog->checkFormat()) return clog;
277 
278     delete clog;
279 
280     //mercurial
281     debugLog("trying mercurial...");
282     clog = new MercurialLog(logfile);
283     if(clog->checkFormat()) return clog;
284 
285     delete clog;
286 
287     //bzr
288     debugLog("trying bzr...");
289     clog = new BazaarLog(logfile);
290     if(clog->checkFormat()) return clog;
291 
292     delete clog;
293 
294     //git raw
295     debugLog("trying git raw...");
296     clog = new GitRawCommitLog(logfile);
297     if(clog->checkFormat()) return clog;
298 
299     delete clog;
300 
301     //cvs exp
302     debugLog("trying cvs-exp...");
303     clog = new CVSEXPCommitLog(logfile);
304     if(clog->checkFormat()) return clog;
305 
306     delete clog;
307 
308     //svn
309     debugLog("trying svn...");
310     clog = new SVNCommitLog(logfile);
311     if(clog->checkFormat()) return clog;
312 
313     delete clog;
314 
315     //cvs2cl
316     debugLog("trying cvs2cl...");
317     clog = new CVS2CLCommitLog(logfile);
318     if(clog->checkFormat()) return clog;
319 
320     delete clog;
321 
322     //custom
323     debugLog("trying custom...");
324     clog = new CustomLog(logfile);
325     if(clog->checkFormat()) return clog;
326 
327     delete clog;
328 
329     //apache
330     debugLog("trying apache combined...");
331     clog = new ApacheCombinedLog(logfile);
332     if(clog->checkFormat()) return clog;
333 
334     delete clog;
335 
336     return 0;
337 }
338