Wt examples 3.3.12
Git.C
Go to the documentation of this file.
1/*
2 * Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium.
3 *
4 * See the LICENSE file for terms of use.
5 */
6#include "Git.h"
7
8#include <iostream>
9#include <vector>
10#include <stdio.h>
11#include <ctype.h>
12
13#include <boost/algorithm/string/classification.hpp>
14#include <boost/algorithm/string/predicate.hpp>
15#include <boost/algorithm/string/split.hpp>
16#include <boost/lexical_cast.hpp>
17
18/*
19 * Small utility methods and classes.
20 */
21namespace {
22 unsigned char fromHex(char b)
23 {
24 if (b <= '9')
25 return b - '0';
26 else if (b <= 'F')
27 return (b - 'A') + 0x0A;
28 else
29 return (b - 'a') + 0x0A;
30 }
31
32 unsigned char fromHex(char msb, char lsb)
33 {
34 return (fromHex(msb) << 4) + fromHex(lsb);
35 }
36
37 char toHex(unsigned char b)
38 {
39 if (b < 0xA)
40 return '0' + b;
41 else
42 return 'a' + (b - 0xA);
43 }
44
45 void toHex(unsigned char b, char& msb, char& lsb)
46 {
47 lsb = toHex(b & 0x0F);
48 msb = toHex(b >> 4);
49 }
50
51 /*
52 * Run a command and capture its stdout into a string.
53 * Uses and maintains a cache.
54 */
55 class POpenWrapper
56 {
57 public:
58 POpenWrapper(const std::string& cmd, Git::Cache& cache) {
59 std::string s = sanitize(cmd);
60
61 bool cached = false;
62
63 for (Git::Cache::iterator i = cache.begin(); i != cache.end(); ++i)
64 if (i->first == s) {
65 content_ = i->second;
66 status_ = 0;
67 cached = true;
68 cache.splice(cache.begin(), cache, i); // implement LRU
69 break;
70 }
71
72 if (!cached) {
73 std::cerr << s << std::endl;
74 FILE *stream = popen((s + " 2>&1").c_str(), "r");
75 if (!stream)
76 throw Git::Exception("Git: could not execute: '" + s + "'");
77
78 int n = 0;
79 do {
80 char buffer[32000];
81 n = fread(buffer, 1, 30000, stream);
82 buffer[n] = 0;
83 content_ += std::string(buffer, n);
84 } while (n);
85
86 status_ = pclose(stream);
87
88 if (status_ == 0) {
89 cache.pop_back(); // implement LRU
90 cache.push_front(std::make_pair(s, content_));
91 }
92 }
93
94 idx_ = 0;
95 }
96
97 std::string& readLine(std::string& r, bool stripWhite = true) {
98 r.clear();
99
100 while (stripWhite
101 && (idx_ < content_.length()) && isspace(content_[idx_]))
102 ++idx_;
103
104 while (idx_ < content_.size() && content_[idx_] != '\n') {
105 r += content_[idx_];
106 ++idx_;
107 }
108
109 if (idx_ < content_.size())
110 ++idx_;
111
112 return r;
113 }
114
115 const std::string& contents() const {
116 return content_;
117 }
118
119 bool finished() const {
120 return idx_ == content_.size();
121 }
122
123 int exitStatus() const {
124 return status_;
125 }
126
127 private:
128 std::string content_;
129 unsigned int idx_;
130 int status_;
131
132
133 std::string sanitize(const std::string& cmd) {
134 /*
135 * Sanitize cmd to not include any dangerous tokens that could allow
136 * execution of shell commands: <>&;|[$`
137 */
138 std::string result;
139 std::string unsafe = "<>&;|[$`";
140
141 for (unsigned i = 0; i < cmd.size(); ++i) {
142 if (unsafe.find(cmd[i]) == std::string::npos)
143 result += cmd[i];
144 }
145
146 return result;
147 }
148 };
149}
150
151/*
152 * About the git files:
153 * type="commit":
154 * - of a reference, like the SHA1 ID obtained from git-rev-parse of a
155 * particular revision
156 * - contains the SHA1 ID of the tree
157 *
158 * type="tree":
159 * 100644 blob 0732f5e4def48d6d5b556fbad005adc994af1e0b CMakeLists.txt
160 * 040000 tree 037d59672d37e116f6e0013a067a7ce1f8760b7c Wt
161 * <mode> SP <type> SP <object> TAB <file>
162 *
163 * type="blob": contents of a file
164 */
165
166Git::Exception::Exception(const std::string& msg)
167 : std::runtime_error(msg)
168{ }
169
172
173Git::ObjectId::ObjectId(const std::string& id)
174{
175 if (id.length() != 40)
176 throw Git::Exception("Git: not a valid SHA1 id: " + id);
177
178 for (int i = 0; i < 20; ++i)
179 (*this)[i] = fromHex(id[2 * i], id[2 * i + 1]);
180}
181
182std::string Git::ObjectId::toString() const
183{
184 std::string result(40, '-');
185
186 for (int i = 0; i < 20; ++i)
187 toHex((*this)[i], result[2 * i], result[2 * i + 1]);
188
189 return result;
190}
191
193 : id(anId),
194 type(aType)
195{ }
196
198 : cache_(3) // cache of 3 git results
199{ }
200
201void Git::setRepositoryPath(const std::string& repositoryPath)
202{
203 repository_ = repositoryPath;
205}
206
207Git::ObjectId Git::getCommitTree(const std::string& revision) const
208{
209 Git::ObjectId commit = getCommit(revision);
210 return getTreeFromCommit(commit);
211}
212
213std::string Git::catFile(const ObjectId& id) const
214{
215 std::string result;
216
217 if (!getCmdResult("cat-file -p " + id.toString(), result, -1))
218 throw Exception("Git: could not cat '" + id.toString() + "'");
219
220 return result;
221}
222
223Git::ObjectId Git::getCommit(const std::string& revision) const
224{
225 std::string sha1Commit;
226 getCmdResult("rev-parse " + revision, sha1Commit, 0);
227 return ObjectId(sha1Commit);
228}
229
231{
232 std::string treeLine;
233 if (!getCmdResult("cat-file -p " + commit.toString(), treeLine, "tree"))
234 throw Exception("Git: could not parse tree from commit '"
235 + commit.toString() + "'");
236
237 std::vector<std::string> v;
238 boost::split(v, treeLine, boost::is_any_of(" "));
239 if (v.size() != 2)
240 throw Exception("Git: could not parse tree from commit '"
241 + commit.toString() + "': '" + treeLine + "'");
242 return ObjectId(v[1]);
243}
244
245Git::Object Git::treeGetObject(const ObjectId& tree, int index) const
246{
247 std::string objectLine;
248 if (!getCmdResult("cat-file -p " + tree.toString(), objectLine, index))
249 throw Exception("Git: could not read object %"
250 + boost::lexical_cast<std::string>(index)
251 + " from tree " + tree.toString());
252 else {
253 std::vector<std::string> v1, v2;
254 boost::split(v1, objectLine, boost::is_any_of("\t"));
255 if (v1.size() != 2)
256 throw Exception("Git: could not parse tree object line: '"
257 + objectLine + "'");
258 boost::split(v2, v1[0], boost::is_any_of(" "));
259 if (v2.size() != 3)
260 throw Exception("Git: could not parse tree object line: '"
261 + objectLine + "'");
262
263 const std::string& stype = v2[1];
264 ObjectType type;
265 if (stype == "tree")
266 type = Tree;
267 else if (stype == "blob")
268 type = Blob;
269 else
270 throw Exception("Git: Unknown type: " + stype);
271
272 Git::Object result(ObjectId(v2[2]), type);
273 result.name = v1[1];
274
275 return result;
276 }
277}
278
279int Git::treeSize(const ObjectId& tree) const
280{
281 return getCmdResultLineCount("cat-file -p " + tree.toString());
282}
283
284bool Git::getCmdResult(const std::string& gitCmd, std::string& result,
285 int index) const
286{
287 POpenWrapper p("git --git-dir=" + repository_ + " " + gitCmd, cache_);
288
289 if (p.exitStatus() != 0)
290 throw Exception("Git error: " + p.readLine(result));
291
292 if (index == -1) {
293 result = p.contents();
294 return true;
295 } else
296 p.readLine(result);
297
298 for (int i = 0; i < index; ++i) {
299 if (p.finished())
300 return false;
301 p.readLine(result);
302 }
303
304 return true;
305}
306
307bool Git::getCmdResult(const std::string& gitCmd, std::string& result,
308 const std::string& tag) const
309{
310 POpenWrapper p("git --git-dir=" + repository_ + " " + gitCmd, cache_);
311
312 if (p.exitStatus() != 0)
313 throw Exception("Git error: " + p.readLine(result));
314
315 while (!p.finished()) {
316 p.readLine(result);
317 if (boost::starts_with(result, tag))
318 return true;
319 }
320
321 return false;
322}
323
324int Git::getCmdResultLineCount(const std::string& gitCmd) const
325{
326 POpenWrapper p("git --git-dir=" + repository_ + " " + gitCmd, cache_);
327
328 std::string r;
329
330 if (p.exitStatus() != 0)
331 throw Exception("Git error: " + p.readLine(r));
332
333 int result = 0;
334 while (!p.finished()) {
335 p.readLine(r);
336 ++result;
337 }
338
339 return result;
340}
341
343{
344 POpenWrapper p("git --git-dir=" + repository_ + " branch", cache_);
345
346 std::string r;
347 if (p.exitStatus() != 0)
348 throw Exception("Git error: " + p.readLine(r));
349}
Exception class.
Definition Git.h:28
Exception(const std::string &msg)
Constructor.
Definition Git.C:166
Git object Id.
Definition Git.h:39
std::string toString() const
Print as a 40-digit hexadecimal number.
Definition Git.C:182
ObjectId()
Default constructor.
Definition Git.C:170
Cache cache_
A small LRU cache that stores results of git commands.
Definition Git.h:129
std::list< std::pair< std::string, std::string > > Cache
Definition Git.h:120
ObjectId getCommit(const std::string &revision) const
Get the commit for a particular revision.
Definition Git.C:223
std::string repository_
The path to the repository.
Definition Git.h:125
ObjectId getTreeFromCommit(const ObjectId &commit) const
Get the tree for a particular commit.
Definition Git.C:230
std::string catFile(const ObjectId &id) const
Return the raw contents of a git object.
Definition Git.C:213
int getCmdResultLineCount(const std::string &cmd) const
Returns the number of lines in the output of a git command.
Definition Git.C:324
Object treeGetObject(const ObjectId &tree, int index) const
Get some info on a tree object.
Definition Git.C:245
void checkRepository() const
Checks the repository.
Definition Git.C:342
int treeSize(const ObjectId &tree) const
Return the number of objects inside a tree object.
Definition Git.C:279
void setRepositoryPath(const std::string &repository)
Set the git repository path.
Definition Git.C:201
ObjectType
Git object type.
Definition Git.h:59
@ Blob
Definition Git.h:59
@ Tree
Definition Git.h:59
bool getCmdResult(const std::string &cmd, std::string &result, const std::string &tag) const
Returns a line identified by a tag from the output of a git command.
Definition Git.C:307
ObjectId getCommitTree(const std::string &revision) const
Get the tree for a particular revision.
Definition Git.C:207
Git()
Constructor.
Definition Git.C:197
Git object.
Definition Git.h:63
Object(const ObjectId &id, ObjectType type)
Definition Git.C:192
std::string name
Definition Git.h:66

Generated on Fri May 17 2024 for the C++ Web Toolkit (Wt) by doxygen 1.9.8