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 "hg.h"
19 #include "../core/sdlapp.h"
20 #include "../gource_settings.h"
21 
22 #include <boost/format.hpp>
23 
24 Regex hg_regex("^([0-9]+) -?[0-9]+\\|([^|]+)\\|([ADM]?)\\|(.+)$");
25 
logCommand()26 std::string MercurialLog::logCommand() {
27 
28     // parse Mercurial log entries (using the gource.style template)
29     std::string gource_style_path = gSDLAppResourceDir + std::string("gource.style");
30 
31     std::string range =
32         // date range
33         (!gGourceSettings.start_date.empty() && !gGourceSettings.stop_date.empty()) ?
34           str(boost::format("--date '%s to %s'") % gGourceSettings.start_date % gGourceSettings.stop_date)
35 
36         // start date only
37         : (!gGourceSettings.start_date.empty()) ?
38           str(boost::format("--date '>%s'") % gGourceSettings.start_date)
39 
40         // stop date only
41         : (!gGourceSettings.stop_date.empty()) ?
42           str(boost::format("--date '<%s'") % gGourceSettings.stop_date)
43 
44         // default
45         : "";
46 
47     std::string log_command = str(boost::format("hg log %s -r 0:tip --style '%s'") % range % gource_style_path);
48 
49 #ifdef _WIN32
50     std::replace(log_command.begin(), log_command.end(), '\'', '"');
51 #endif
52 
53     return log_command;
54 }
55 
MercurialLog(const std::string & logfile)56 MercurialLog::MercurialLog(const std::string& logfile) : RCommitLog(logfile) {
57 
58     log_command = logCommand();
59 
60     //can generate log from directory
61     if(!logf && is_dir) {
62         logf = generateLog(logfile);
63 
64         if(logf) {
65             success  = true;
66             seekable = true;
67         }
68     }
69 }
70 
generateLog(const std::string & dir)71 BaseLog* MercurialLog::generateLog(const std::string& dir) {
72 
73     //does directory have a .hg ?
74     std::string hgdir = dir + std::string("/.hg");
75     struct stat dirinfo;
76     int stat_rc = stat(hgdir.c_str(), &dirinfo);
77     if(stat_rc!=0 || !(dirinfo.st_mode & S_IFDIR)) {
78         return 0;
79     }
80 
81     // do we have this client installed
82     requireExecutable("hg");
83 
84     std::string command = getLogCommand();
85 
86     createTempLog();
87 
88     if(temp_file.size()==0) return 0;
89 
90     char cmd_buff[2048];
91     snprintf(cmd_buff, 2048, "%s -R \"%s\" > %s", command.c_str(), dir.c_str(), temp_file.c_str());
92 
93     int command_rc = systemCommand(cmd_buff);
94 
95     if(command_rc != 0) {
96         return 0;
97     }
98 
99     BaseLog* seeklog = new SeekLog(temp_file);
100 
101     return seeklog;
102 }
103 
104 
parseCommit(RCommit & commit)105 bool MercurialLog::parseCommit(RCommit& commit) {
106 
107     while(parseCommitEntry(commit));
108 
109     return !commit.files.empty();
110 }
111 
parseCommitEntry(RCommit & commit)112 bool MercurialLog::parseCommitEntry(RCommit& commit) {
113 
114     std::string line;
115     std::vector<std::string> entries;
116 
117     if(!getNextLine(line)) return false;
118 
119     //custom line
120     if(!hg_regex.match(line, &entries)) return false;
121 
122     time_t timestamp     = atol(entries[0].c_str());
123     std::string username = entries[1];
124 
125     //if this file is for the same person and timestamp
126     //we add to the commit, else we save the lastline
127     //and return false
128     if(commit.files.empty()) {
129         commit.timestamp = timestamp;
130         commit.username  = username;
131     } else {
132         if(commit.timestamp != timestamp || commit.username  != username) {
133             lastline = line;
134             return false;
135         }
136     }
137 
138     std::string action = "A";
139 
140     if(!entries[2].empty()) {
141         action = entries[2];
142     }
143 
144     commit.addFile(entries[3], action);
145 
146     //commit.debug();
147 
148     return true;
149 }
150