Wt examples  4.12.0
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 
21 using namespace Wt;
22 
23 /*
24  * Small utility methods and classes.
25  */
26 namespace {
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 
171 Git::Exception::Exception(const std::string& msg)
172  : std::runtime_error(msg)
173 { }
174 
176 { }
177 
178 Git::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 
187 std::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 
207 void 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;
217  checkRepository();
218 }
219 
220 Git::ObjectId Git::getCommitTree(const std::string& revision) const
221 {
222  Git::ObjectId commit = getCommit(revision);
223  return getTreeFromCommit(commit);
224 }
225 
226 std::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 
236 Git::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 
258 Git::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 
292 int Git::treeSize(const ObjectId& tree) const
293 {
294  return getCmdResultLineCount("cat-file -p " + tree.toString());
295 }
296 
297 std::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 
306 bool 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 
329 bool 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 
346 int 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
void id(Action &action, V &value, const std::string &name="id", int size=-1)
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