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