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

Generated on Tue Dec 15 2020 for the C++ Web Toolkit (Wt) by doxygen 1.8.13