Wt examples  4.10.4
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 
19 using namespace Wt;
20 
21 /*
22  * Small utility methods and classes.
23  */
24 namespace {
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 
169 Git::Exception::Exception(const std::string& msg)
170  : std::runtime_error(msg)
171 { }
172 
174 { }
175 
176 Git::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 
185 std::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 
205 void 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;
211  checkRepository();
212 }
213 
214 Git::ObjectId Git::getCommitTree(const std::string& revision) const
215 {
216  Git::ObjectId commit = getCommit(revision);
217  return getTreeFromCommit(commit);
218 }
219 
220 std::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 
230 Git::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 
252 Git::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 
286 int Git::treeSize(const ObjectId& tree) const
287 {
288  return getCmdResultLineCount("cat-file -p " + tree.toString());
289 }
290 
291 std::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 
300 bool 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 
323 bool 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 
340 int 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
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:195
std::string name
Definition: Git.h:66