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