Wt examples  4.0.0
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 <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 
20 using namespace Wt;
21 
22 /*
23  * Small utility methods and classes.
24  */
25 namespace {
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 
170 Git::Exception::Exception(const std::string& msg)
171  : std::runtime_error(msg)
172 { }
173 
175 { }
176 
177 Git::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 
186 std::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 
206 void 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;
212  checkRepository();
213 }
214 
215 Git::ObjectId Git::getCommitTree(const std::string& revision) const
216 {
217  Git::ObjectId commit = getCommit(revision);
218  return getTreeFromCommit(commit);
219 }
220 
221 std::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 
231 Git::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 
253 Git::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 
287 int Git::treeSize(const ObjectId& tree) const
288 {
289  return getCmdResultLineCount("cat-file -p " + tree.toString());
290 }
291 
292 std::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 
301 bool 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 
324 bool 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 
341 int 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 }
Definition: Git.h:59
void checkRepository() const
Checks the repository.
Definition: Git.C:359
int getCmdResultLineCount(const std::string &cmd) const
Returns the number of lines in the output of a git command.
Definition: Git.C:341
Object(const ObjectId &id, ObjectType type)
Definition: Git.C:196
int treeSize(const ObjectId &tree) const
Return the number of objects inside a tree object.
Definition: Git.C:287
Definition: Git.h:59
std::string baseCmd() const
The git base command after which extra arguments are added.
Definition: Git.C:292
Object treeGetObject(const ObjectId &tree, int index) const
Get some info on a tree object.
Definition: Git.C:253
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
Git object Id.
Definition: Git.h:39
bool is_bare_
Whether the repositoy is a bare repository.
Definition: Git.h:129
std::string toString() const
Print as a 40-digit hexadecimal number.
Definition: Git.C:186
Git object.
Definition: Git.h:63
std::string name
Definition: Git.h:66
Git()
Constructor.
Definition: Git.C:201
std::list< std::pair< std::string, std::string > > Cache
Definition: Git.h:120
void id(Action &action, V &value, const std::string &name="id", int size=-1)
WString asString(const cpp17::any &v, const WString &formatString=WString())
ObjectId getCommit(const std::string &revision) const
Get the commit for a particular revision.
Definition: Git.C:231
ObjectType
Git object type.
Definition: Git.h:59
std::string repository_
The path to the repository.
Definition: Git.h:133
Cache cache_
A small LRU cache that stores results of git commands.
Definition: Git.h:137
ObjectId getTreeFromCommit(const ObjectId &commit) const
Get the tree for a particular commit.
Definition: Git.C:238
Exception(const std::string &msg)
Constructor.
Definition: Git.C:170
ObjectId()
Default constructor.
Definition: Git.C:174
ObjectId getCommitTree(const std::string &revision) const
Get the tree for a particular revision.
Definition: Git.C:215
Exception class.
Definition: Git.h:28
std::string catFile(const ObjectId &id) const
Return the raw contents of a git object.
Definition: Git.C:221
void setRepositoryPath(const std::string &repository)
Set the git repository path.
Definition: Git.C:206

Generated on Mon Sep 4 2017 for the C++ Web Toolkit (Wt) by doxygen 1.8.11