1 /*
2 Copyright (C) 2010 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 "svn.h"
19 #include "../gource_settings.h"
20
21 #include <boost/format.hpp>
22
23 #ifdef HAVE_LIBTINYXML
24 #include <tinyxml.h>
25 #else
26 #include "../tinyxml/tinyxml.h"
27 #endif
28
29 Regex svn_xml_tag("^<\\??xml");
30 Regex svn_logentry_start("^<logentry");
31 Regex svn_logentry_end("^</logentry>");
32 Regex svn_logentry_timestamp("(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})");
33
logCommand()34 std::string SVNCommitLog::logCommand() {
35
36 std::string start = (!gGourceSettings.start_date.empty())
37 ? str(boost::format("{%s}") % gGourceSettings.start_date) : "1";
38
39 std::string stop = (!gGourceSettings.stop_date.empty())
40 ? str(boost::format("{%s}") % gGourceSettings.stop_date) : "HEAD";
41
42 std::string range = str(boost::format("%s:%s") % start % stop);
43
44 std::string log_command = str(boost::format("svn log -r %s --xml --verbose --quiet") % range);
45
46 return log_command;
47 }
48
SVNCommitLog(const std::string & logfile)49 SVNCommitLog::SVNCommitLog(const std::string& logfile) : RCommitLog(logfile, '<') {
50
51 log_command = logCommand();
52
53 //can generate log from directory
54 if(!logf && is_dir) {
55 logf = generateLog(logfile);
56
57 if(logf) {
58 success = true;
59 seekable = true;
60 }
61 }
62
63 logentry.reserve(1024);
64 }
65
66
generateLog(const std::string & dir)67 BaseLog* SVNCommitLog::generateLog(const std::string& dir) {
68 //get working directory
69 char cwd_buff[1024];
70
71 if(getcwd(cwd_buff, 1024) != cwd_buff) {
72 return 0;
73 }
74
75 //does directory have a .svn ?
76 std::string gitdir = dir + std::string("/.svn");
77 struct stat dirinfo;
78 int stat_rc = stat(gitdir.c_str(), &dirinfo);
79 if(stat_rc!=0 || !(dirinfo.st_mode & S_IFDIR)) {
80 return 0;
81 }
82
83 // do we have this client installed
84 requireExecutable("svn");
85
86 std::string command = getLogCommand();
87
88 //create temp file
89 createTempLog();
90
91 if(temp_file.size()==0) return 0;
92
93 if(chdir(dir.c_str()) != 0) {
94 return 0;
95 }
96
97 char cmd_buff[2048];
98 snprintf(cmd_buff, 2048, "%s > %s", command.c_str(), temp_file.c_str());
99
100 int command_rc = systemCommand(cmd_buff);
101
102 chdir(cwd_buff);
103
104 if(command_rc != 0) {
105 return 0;
106 }
107
108 BaseLog* seeklog = new SeekLog(temp_file);
109
110 return seeklog;
111 }
112
113 #ifndef HAVE_TIMEGM
114
115 std::string system_tz;
116 bool system_tz_init = false;
117
__timegm_hack(struct tm * tm)118 time_t __timegm_hack(struct tm* tm) {
119
120 if(!system_tz_init) {
121 char* current_tz_env = getenv("TZ");
122
123 if(current_tz_env != 0) {
124 system_tz = std::string("TZ=") + current_tz_env;
125 }
126
127 system_tz_init = true;
128 }
129
130 putenv((char*)"TZ=UTC");
131 tzset();
132
133 time_t timestamp = mktime(tm);
134
135 if(!system_tz.empty()) {
136 putenv((char*)system_tz.c_str());
137 } else {
138 #ifdef HAVE_UNSETENV
139 unsetenv("TZ");
140 #else
141 putenv((char*)"TZ=");
142 #endif
143 }
144 tzset();
145
146 return timestamp;
147 }
148 #endif
149
parseCommit(RCommit & commit)150 bool SVNCommitLog::parseCommit(RCommit& commit) {
151
152 //fprintf(stderr,"parsing svn log\n");
153
154 std::string line;
155
156 if(!getNextLine(line)) return false;
157
158 //start of log entry
159 if(!svn_logentry_start.match(line)) {
160
161 //is this the start of the document
162 if(!svn_xml_tag.match(line)) return false;
163
164 //fprintf(stderr,"found xml tag\n");
165
166 //if so find the first logentry tag
167
168 bool found_logentry = false;
169
170 while(getNextLine(line)) {
171 if(svn_logentry_start.match(line)) {
172 found_logentry = true;
173 break;
174 }
175 }
176
177 if(!found_logentry) return false;
178 }
179
180 //fprintf(stderr,"found logentry\n");
181
182 logentry.clear();
183
184 logentry.append(line);
185 logentry.append("\n");
186
187 //fprintf(stderr,"found opening tag\n");
188
189 bool endfound = false;
190
191 while(getNextLine(line)) {
192 logentry.append(line);
193 logentry.append("\n");
194 if(svn_logentry_end.match(line)) {
195 //fprintf(stderr,"found closing tag\n");
196 endfound=true;
197 break;
198 }
199 }
200
201 //incomplete commit
202 if(!endfound) return false;
203
204 //fprintf(stderr,"read logentry\n");
205
206 TiXmlDocument doc;
207
208 if(!doc.Parse(logentry.c_str())) return false;
209
210 //fprintf(stderr,"try to parse logentry: %s\n", logentry.c_str());
211
212 TiXmlElement* leE = doc.FirstChildElement( "logentry" );
213
214 std::vector<std::string> entries;
215
216 if(!leE) return false;
217
218 //parse date
219 TiXmlElement* dateE = leE->FirstChildElement( "date" );
220
221 if(!dateE) return false;
222
223 std::string timestamp_str(dateE->GetText());
224
225 if(!svn_logentry_timestamp.match(timestamp_str, &entries))
226 return false;
227
228 struct tm time_str;
229
230 time_str.tm_year = atoi(entries[0].c_str()) - 1900;
231 time_str.tm_mon = atoi(entries[1].c_str()) - 1;
232 time_str.tm_mday = atoi(entries[2].c_str());
233 time_str.tm_hour = atoi(entries[3].c_str());
234 time_str.tm_min = atoi(entries[4].c_str());
235 time_str.tm_sec = atoi(entries[5].c_str());
236 time_str.tm_isdst = -1;
237
238 #ifdef HAVE_TIMEGM
239 commit.timestamp = timegm(&time_str);
240 #else
241 commit.timestamp = __timegm_hack(&time_str);
242 #endif
243
244 //parse author
245 TiXmlElement* authorE = leE->FirstChildElement("author");
246
247 if(authorE != 0) {
248 // GetText() may return NULL, causing author instantiation to crash.
249 std::string author;
250 if(authorE->GetText()) author = authorE->GetText();
251 if(author.empty()) author = "Unknown";
252
253 commit.username = author;
254 }
255
256 TiXmlElement* pathsE = leE->FirstChildElement( "paths" );
257
258 //log entries sometimes dont have any paths
259 if(!pathsE) return true;
260
261 //parse changes
262
263 for(TiXmlElement* pathE = pathsE->FirstChildElement("path"); pathE != 0; pathE = pathE->NextSiblingElement()) {
264 //parse path
265
266 const char* kind = pathE->Attribute("kind");
267 const char* action = pathE->Attribute("action");
268
269 //check for action
270 if(action == 0) continue;
271
272 bool is_dir = false;
273
274 //if has the 'kind' attribute (old versions of svn dont have this), check if it is a dir
275 if(kind != 0 && strcmp(kind,"dir") == 0) {
276
277 //accept only deletes for directories
278 if(strcmp(action, "D") != 0) continue;
279
280 is_dir = true;
281 }
282
283 std::string file(pathE->GetText());
284 std::string status(action);
285
286 if(file.empty()) continue;
287 if(status.empty()) continue;
288
289 //append trailing slash if is directory
290 if(is_dir && file[file.size()-1] != '/') {
291 file = file + std::string("/");
292 }
293
294 commit.addFile(file, status);
295 }
296
297 //fprintf(stderr,"parsed logentry\n");
298
299 //read files
300
301 return true;
302 }
303