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