Wt examples  3.3.5
/usr/src/RPM/BUILD/wt-3.3.5-rc2/examples/gitmodel/Git.C
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium.
00003  *
00004  * See the LICENSE file for terms of use.
00005  */
00006 #include "Git.h"
00007 
00008 #include <iostream>
00009 #include <vector>
00010 #include <stdio.h>
00011 #include <ctype.h>
00012 
00013 #include <boost/algorithm/string/classification.hpp>
00014 #include <boost/algorithm/string/predicate.hpp>
00015 #include <boost/algorithm/string/split.hpp>
00016 #include <boost/lexical_cast.hpp>
00017 
00018 /*
00019  * Small utility methods and classes.
00020  */
00021 namespace {
00022   unsigned char fromHex(char b)
00023   {
00024     if (b <= '9')
00025       return b - '0';
00026     else if (b <= 'F')
00027       return (b - 'A') + 0x0A;
00028     else 
00029       return (b - 'a') + 0x0A;
00030   }
00031 
00032   unsigned char fromHex(char msb, char lsb)
00033   {
00034     return (fromHex(msb) << 4) + fromHex(lsb);
00035   }
00036 
00037   char toHex(unsigned char b)
00038   {
00039     if (b < 0xA)
00040       return '0' + b;
00041     else
00042       return 'a' + (b - 0xA);
00043   }
00044 
00045   void toHex(unsigned char b, char& msb, char& lsb)
00046   {
00047     lsb = toHex(b & 0x0F);
00048     msb = toHex(b >> 4);
00049   }
00050 
00051   /*
00052    * Run a command and capture its stdout into a string.
00053    * Uses and maintains a cache.
00054    */
00055   class POpenWrapper
00056   {
00057   public:
00058     POpenWrapper(const std::string& cmd, Git::Cache& cache) {
00059       std::string s = sanitize(cmd);
00060 
00061       bool cached = false;
00062 
00063       for (Git::Cache::iterator i = cache.begin(); i != cache.end(); ++i)
00064         if (i->first == s) {
00065           content_ = i->second;
00066           status_ = 0;
00067           cached = true;
00068           cache.splice(cache.begin(), cache, i); // implement LRU
00069           break;
00070         }
00071 
00072       if (!cached) {
00073         std::cerr << s << std::endl;
00074         FILE *stream = popen((s + "  2>&1").c_str(), "r");
00075         if (!stream)
00076           throw Git::Exception("Git: could not execute: '" + s + "'");
00077 
00078         int n = 0;
00079         do {
00080           char buffer[32000];
00081           n = fread(buffer, 1, 30000, stream);
00082           buffer[n] = 0;
00083           content_ += std::string(buffer, n);
00084         } while (n);
00085 
00086         status_ = pclose(stream);
00087 
00088         if (status_ == 0) {
00089           cache.pop_back(); // implement LRU
00090           cache.push_front(std::make_pair(s, content_));
00091         }
00092       }
00093 
00094       idx_ = 0;
00095     }
00096 
00097     std::string& readLine(std::string& r, bool stripWhite = true) {
00098       r.clear();
00099 
00100       while (stripWhite
00101              && (idx_ < content_.length()) && isspace(content_[idx_]))
00102         ++idx_;
00103 
00104       while (idx_ < content_.size() && content_[idx_] != '\n') {
00105         r += content_[idx_];
00106         ++idx_;
00107       }
00108 
00109       if (idx_ < content_.size())
00110         ++idx_;
00111 
00112       return r;
00113     }
00114 
00115     const std::string& contents() const {
00116       return content_;
00117     }
00118 
00119     bool finished() const {
00120       return idx_ == content_.size();
00121     }
00122 
00123     int exitStatus() const {
00124       return status_;
00125     }
00126 
00127   private:
00128     std::string content_;
00129     unsigned int idx_;
00130     int status_;
00131 
00132 
00133     std::string sanitize(const std::string& cmd) {
00134       /*
00135        * Sanitize cmd to not include any dangerous tokens that could allow
00136        * execution of shell commands: <>&;|[$`
00137        */
00138       std::string result;
00139       std::string unsafe = "<>&;|[$`";
00140  
00141       for (unsigned i = 0; i < cmd.size(); ++i) {
00142         if (unsafe.find(cmd[i]) == std::string::npos)
00143           result += cmd[i];
00144       }
00145 
00146       return result;
00147     }
00148   };
00149 }
00150 
00151 /*
00152  * About the git files:
00153  * type="commit":
00154  *  - of a reference, like the SHA1 ID obtained from git-rev-parse of a
00155  *    particular revision
00156  *  - contains the SHA1 ID of the tree
00157  *
00158  * type="tree":
00159  *  100644 blob 0732f5e4def48d6d5b556fbad005adc994af1e0b    CMakeLists.txt
00160  *  040000 tree 037d59672d37e116f6e0013a067a7ce1f8760b7c    Wt
00161  *  <mode> SP <type> SP <object> TAB <file>
00162  *
00163  * type="blob": contents of a file
00164  */
00165 
00166 Git::Exception::Exception(const std::string& msg)
00167   : std::runtime_error(msg)
00168 { }
00169 
00170 Git::ObjectId::ObjectId()
00171 { }
00172 
00173 Git::ObjectId::ObjectId(const std::string& id)
00174 {
00175   if (id.length() != 40)
00176     throw Git::Exception("Git: not a valid SHA1 id: " + id);
00177 
00178   for (int i = 0; i < 20; ++i)
00179     (*this)[i] = fromHex(id[2 * i], id[2 * i + 1]);
00180 }
00181 
00182 std::string Git::ObjectId::toString() const
00183 {
00184   std::string result(40, '-');
00185 
00186   for (int i = 0; i < 20; ++i)
00187     toHex((*this)[i], result[2 * i], result[2 * i + 1]);
00188 
00189   return result;
00190 }
00191 
00192 Git::Object::Object(const ObjectId& anId, ObjectType aType)
00193   : id(anId),
00194     type(aType)
00195 { }
00196 
00197 Git::Git()
00198   : cache_(3) // cache of 3 git results
00199 { }
00200 
00201 void Git::setRepositoryPath(const std::string& repositoryPath)
00202 { 
00203   repository_ = repositoryPath;
00204   checkRepository();
00205 }
00206 
00207 Git::ObjectId Git::getCommitTree(const std::string& revision) const
00208 {
00209   Git::ObjectId commit = getCommit(revision);
00210   return getTreeFromCommit(commit);
00211 }
00212 
00213 std::string Git::catFile(const ObjectId& id) const
00214 {
00215   std::string result;
00216 
00217   if (!getCmdResult("cat-file -p " + id.toString(), result, -1))
00218     throw Exception("Git: could not cat '" + id.toString() + "'");
00219 
00220   return result;
00221 }
00222 
00223 Git::ObjectId Git::getCommit(const std::string& revision) const
00224 {
00225   std::string sha1Commit;
00226   getCmdResult("rev-parse " + revision, sha1Commit, 0);
00227   return ObjectId(sha1Commit);
00228 }
00229 
00230 Git::ObjectId Git::getTreeFromCommit(const ObjectId& commit) const
00231 {
00232   std::string treeLine;
00233   if (!getCmdResult("cat-file -p " + commit.toString(), treeLine, "tree"))
00234     throw Exception("Git: could not parse tree from commit '" 
00235                     + commit.toString() + "'");
00236 
00237   std::vector<std::string> v;
00238   boost::split(v, treeLine, boost::is_any_of(" "));
00239   if (v.size() != 2)
00240     throw Exception("Git: could not parse tree from commit '"
00241                     + commit.toString() + "': '" + treeLine + "'");
00242   return ObjectId(v[1]);
00243 }
00244 
00245 Git::Object Git::treeGetObject(const ObjectId& tree, int index) const
00246 {
00247   std::string objectLine;
00248   if (!getCmdResult("cat-file -p " + tree.toString(), objectLine, index))
00249     throw Exception("Git: could not read object %"
00250                     + boost::lexical_cast<std::string>(index)
00251                     + "  from tree " + tree.toString());
00252   else {
00253     std::vector<std::string> v1, v2;
00254     boost::split(v1, objectLine, boost::is_any_of("\t"));
00255     if (v1.size() != 2)
00256       throw Exception("Git: could not parse tree object line: '"
00257                       + objectLine + "'");
00258     boost::split(v2, v1[0], boost::is_any_of(" "));
00259     if (v2.size() != 3)
00260       throw Exception("Git: could not parse tree object line: '"
00261                       + objectLine + "'");
00262  
00263     const std::string& stype = v2[1];
00264     ObjectType type;
00265     if (stype == "tree")
00266       type = Tree;
00267     else if (stype == "blob")
00268       type = Blob;
00269     else
00270       throw Exception("Git: Unknown type: " + stype);
00271 
00272     Git::Object result(ObjectId(v2[2]), type);
00273     result.name = v1[1];
00274 
00275     return result;
00276   }
00277 }
00278 
00279 int Git::treeSize(const ObjectId& tree) const
00280 {
00281   return getCmdResultLineCount("cat-file -p " + tree.toString());
00282 }
00283 
00284 bool Git::getCmdResult(const std::string& gitCmd, std::string& result,
00285                        int index) const
00286 {
00287   POpenWrapper p("git --git-dir=" + repository_ + " " + gitCmd, cache_);
00288 
00289   if (p.exitStatus() != 0)
00290     throw Exception("Git error: " + p.readLine(result));
00291 
00292   if (index == -1) {
00293     result = p.contents();
00294     return true;
00295   } else
00296     p.readLine(result);
00297 
00298   for (int i = 0; i < index; ++i) {
00299     if (p.finished())
00300       return false;
00301     p.readLine(result);
00302   }
00303 
00304   return true;
00305 }
00306 
00307 bool Git::getCmdResult(const std::string& gitCmd, std::string& result,
00308                        const std::string& tag) const
00309 {
00310   POpenWrapper p("git --git-dir=" + repository_ + " " + gitCmd, cache_);
00311 
00312   if (p.exitStatus() != 0)
00313     throw Exception("Git error: " + p.readLine(result));
00314 
00315   while (!p.finished()) {
00316     p.readLine(result);
00317     if (boost::starts_with(result, tag))
00318       return true;
00319   }
00320 
00321   return false;
00322 }
00323 
00324 int Git::getCmdResultLineCount(const std::string& gitCmd) const
00325 {
00326   POpenWrapper p("git --git-dir=" + repository_ + " " + gitCmd, cache_);
00327 
00328   std::string r;
00329 
00330   if (p.exitStatus() != 0)
00331     throw Exception("Git error: " + p.readLine(r));
00332 
00333   int result = 0;
00334   while (!p.finished()) {
00335     p.readLine(r);
00336     ++result;
00337   }
00338 
00339   return result;
00340 }
00341 
00342 void Git::checkRepository() const
00343 {
00344   POpenWrapper p("git --git-dir=" + repository_ + " branch", cache_);
00345 
00346   std::string r;
00347   if (p.exitStatus() != 0)
00348     throw Exception("Git error: " + p.readLine(r));
00349 }

Generated on Tue Mar 22 2016 for the C++ Web Toolkit (Wt) by doxygen 1.7.6.1