Wt examples  3.3.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 <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& s, Git::Cache& cache) {
59  bool cached = false;
60 
61  for (Git::Cache::iterator i = cache.begin(); i != cache.end(); ++i)
62  if (i->first == s) {
63  content_ = i->second;
64  status_ = 0;
65  cached = true;
66  cache.splice(cache.begin(), cache, i); // implement LRU
67  break;
68  }
69 
70  if (!cached) {
71  std::cerr << s << std::endl;
72  FILE *stream = popen((s + " 2>&1").c_str(), "r");
73  if (!stream)
74  throw Git::Exception("Git: could not execute: '" + s + "'");
75 
76  int n = 0;
77  do {
78  char buffer[32000];
79  n = fread(buffer, 1, 30000, stream);
80  buffer[n] = 0;
81  content_ += std::string(buffer, n);
82  } while (n);
83 
84  status_ = pclose(stream);
85 
86  if (status_ == 0) {
87  cache.pop_back(); // implement LRU
88  cache.push_front(std::make_pair(s, content_));
89  }
90  }
91 
92  idx_ = 0;
93  }
94 
95  std::string& readLine(std::string& r, bool stripWhite = true) {
96  r.clear();
97 
98  while (stripWhite
99  && (idx_ < content_.length()) && isspace(content_[idx_]))
100  ++idx_;
101 
102  while (idx_ < content_.size() && content_[idx_] != '\n') {
103  r += content_[idx_];
104  ++idx_;
105  }
106 
107  if (idx_ < content_.size())
108  ++idx_;
109 
110  return r;
111  }
112 
113  const std::string& contents() const {
114  return content_;
115  }
116 
117  bool finished() const {
118  return idx_ == content_.size();
119  }
120 
121  int exitStatus() const {
122  return status_;
123  }
124 
125  private:
126  std::string content_;
127  unsigned int idx_;
128  int status_;
129  };
130 }
131 
132 /*
133  * About the git files:
134  * type="commit":
135  * - of a reference, like the SHA1 ID obtained from git-rev-parse of a
136  * particular revision
137  * - contains the SHA1 ID of the tree
138  *
139  * type="tree":
140  * 100644 blob 0732f5e4def48d6d5b556fbad005adc994af1e0b CMakeLists.txt
141  * 040000 tree 037d59672d37e116f6e0013a067a7ce1f8760b7c Wt
142  * <mode> SP <type> SP <object> TAB <file>
143  *
144  * type="blob": contents of a file
145  */
146 
147 Git::Exception::Exception(const std::string& msg)
148  : std::runtime_error(msg)
149 { }
150 
152 { }
153 
154 Git::ObjectId::ObjectId(const std::string& id)
155 {
156  if (id.length() != 40)
157  throw Git::Exception("Git: not a valid SHA1 id: " + id);
158 
159  for (int i = 0; i < 20; ++i)
160  (*this)[i] = fromHex(id[2 * i], id[2 * i + 1]);
161 }
162 
163 std::string Git::ObjectId::toString() const
164 {
165  std::string result(40, '-');
166 
167  for (int i = 0; i < 20; ++i)
168  toHex((*this)[i], result[2 * i], result[2 * i + 1]);
169 
170  return result;
171 }
172 
174  : id(anId),
175  type(aType)
176 { }
177 
179  : cache_(3) // cache of 3 git results
180 { }
181 
182 void Git::setRepositoryPath(const std::string& repositoryPath)
183 {
184  repository_ = repositoryPath;
185  checkRepository();
186 }
187 
188 Git::ObjectId Git::getCommitTree(const std::string& revision) const
189 {
190  Git::ObjectId commit = getCommit(revision);
191  return getTreeFromCommit(commit);
192 }
193 
194 std::string Git::catFile(const ObjectId& id) const
195 {
196  std::string result;
197 
198  if (!getCmdResult("cat-file -p " + id.toString(), result, -1))
199  throw Exception("Git: could not cat '" + id.toString() + "'");
200 
201  return result;
202 }
203 
204 Git::ObjectId Git::getCommit(const std::string& revision) const
205 {
206  std::string sha1Commit;
207  getCmdResult("rev-parse " + revision, sha1Commit, 0);
208  return ObjectId(sha1Commit);
209 }
210 
212 {
213  std::string treeLine;
214  if (!getCmdResult("cat-file -p " + commit.toString(), treeLine, "tree"))
215  throw Exception("Git: could not parse tree from commit '"
216  + commit.toString() + "'");
217 
218  std::vector<std::string> v;
219  boost::split(v, treeLine, boost::is_any_of(" "));
220  if (v.size() != 2)
221  throw Exception("Git: could not parse tree from commit '"
222  + commit.toString() + "': '" + treeLine + "'");
223  return ObjectId(v[1]);
224 }
225 
226 Git::Object Git::treeGetObject(const ObjectId& tree, int index) const
227 {
228  std::string objectLine;
229  if (!getCmdResult("cat-file -p " + tree.toString(), objectLine, index))
230  throw Exception("Git: could not read object %"
231  + boost::lexical_cast<std::string>(index)
232  + " from tree " + tree.toString());
233  else {
234  std::vector<std::string> v1, v2;
235  boost::split(v1, objectLine, boost::is_any_of("\t"));
236  if (v1.size() != 2)
237  throw Exception("Git: could not parse tree object line: '"
238  + objectLine + "'");
239  boost::split(v2, v1[0], boost::is_any_of(" "));
240  if (v2.size() != 3)
241  throw Exception("Git: could not parse tree object line: '"
242  + objectLine + "'");
243 
244  const std::string& stype = v2[1];
245  ObjectType type;
246  if (stype == "tree")
247  type = Tree;
248  else if (stype == "blob")
249  type = Blob;
250  else
251  throw Exception("Git: Unknown type: " + stype);
252 
253  Git::Object result(ObjectId(v2[2]), type);
254  result.name = v1[1];
255 
256  return result;
257  }
258 }
259 
260 int Git::treeSize(const ObjectId& tree) const
261 {
262  return getCmdResultLineCount("cat-file -p " + tree.toString());
263 }
264 
265 bool Git::getCmdResult(const std::string& gitCmd, std::string& result,
266  int index) const
267 {
268  POpenWrapper p("git --git-dir=" + repository_ + " " + gitCmd, cache_);
269 
270  if (p.exitStatus() != 0)
271  throw Exception("Git error: " + p.readLine(result));
272 
273  if (index == -1) {
274  result = p.contents();
275  return true;
276  } else
277  p.readLine(result);
278 
279  for (int i = 0; i < index; ++i) {
280  if (p.finished())
281  return false;
282  p.readLine(result);
283  }
284 
285  return true;
286 }
287 
288 bool Git::getCmdResult(const std::string& gitCmd, std::string& result,
289  const std::string& tag) const
290 {
291  POpenWrapper p("git --git-dir=" + repository_ + " " + gitCmd, cache_);
292 
293  if (p.exitStatus() != 0)
294  throw Exception("Git error: " + p.readLine(result));
295 
296  while (!p.finished()) {
297  p.readLine(result);
298  if (boost::starts_with(result, tag))
299  return true;
300  }
301 
302  return false;
303 }
304 
305 int Git::getCmdResultLineCount(const std::string& gitCmd) const
306 {
307  POpenWrapper p("git --git-dir=" + repository_ + " " + gitCmd, cache_);
308 
309  std::string r;
310 
311  if (p.exitStatus() != 0)
312  throw Exception("Git error: " + p.readLine(r));
313 
314  int result = 0;
315  while (!p.finished()) {
316  p.readLine(r);
317  ++result;
318  }
319 
320  return result;
321 }
322 
324 {
325  POpenWrapper p("git --git-dir=" + repository_ + " branch", cache_);
326 
327  std::string r;
328  if (p.exitStatus() != 0)
329  throw Exception("Git error: " + p.readLine(r));
330 }

Generated on Fri May 31 2013 for the C++ Web Toolkit (Wt) by doxygen 1.8.3.1