Wt examples  3.2.2
/home/koen/project/wt/public-git/wt/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& s, Git::Cache& cache) {
00059       bool cached = false;
00060 
00061       for (Git::Cache::iterator i = cache.begin(); i != cache.end(); ++i)
00062         if (i->first == s) {
00063           content_ = i->second;
00064           status_ = 0;
00065           cached = true;
00066           cache.splice(cache.begin(), cache, i); // implement LRU
00067           break;
00068         }
00069 
00070       if (!cached) {
00071         std::cerr << s << std::endl;
00072         FILE *stream = popen((s + "  2>&1").c_str(), "r");
00073         if (!stream)
00074           throw Git::Exception("Git: could not execute: '" + s + "'");
00075 
00076         int n = 0;
00077         do {
00078           char buffer[32000];
00079           n = fread(buffer, 1, 30000, stream);
00080           buffer[n] = 0;
00081           content_ += std::string(buffer, n);
00082         } while (n);
00083 
00084         status_ = pclose(stream);
00085 
00086         if (status_ == 0) {
00087           cache.pop_back(); // implement LRU
00088           cache.push_front(std::make_pair(s, content_));
00089         }
00090       }
00091 
00092       idx_ = 0;
00093     }
00094 
00095     std::string& readLine(std::string& r, bool stripWhite = true) {
00096       r.clear();
00097 
00098       while (stripWhite
00099              && (idx_ < content_.length()) && isspace(content_[idx_]))
00100         ++idx_;
00101 
00102       while (idx_ < content_.size() && content_[idx_] != '\n') {
00103         r += content_[idx_];
00104         ++idx_;
00105       }
00106 
00107       if (idx_ < content_.size())
00108         ++idx_;
00109 
00110       return r;
00111     }
00112 
00113     const std::string& contents() const {
00114       return content_;
00115     }
00116 
00117     bool finished() const {
00118       return idx_ == content_.size();
00119     }
00120 
00121     int exitStatus() const {
00122       return status_;
00123     }
00124 
00125   private:
00126     std::string content_;
00127     unsigned int idx_;
00128     int status_;
00129   };
00130 }
00131 
00132 /*
00133  * About the git files:
00134  * type="commit":
00135  *  - of a reference, like the SHA1 ID obtained from git-rev-parse of a
00136  *    particular revision
00137  *  - contains the SHA1 ID of the tree
00138  *
00139  * type="tree":
00140  *  100644 blob 0732f5e4def48d6d5b556fbad005adc994af1e0b    CMakeLists.txt
00141  *  040000 tree 037d59672d37e116f6e0013a067a7ce1f8760b7c    Wt
00142  *  <mode> SP <type> SP <object> TAB <file>
00143  *
00144  * type="blob": contents of a file
00145  */
00146 
00147 Git::Exception::Exception(const std::string& msg)
00148   : std::runtime_error(msg)
00149 { }
00150 
00151 Git::ObjectId::ObjectId()
00152 { }
00153 
00154 Git::ObjectId::ObjectId(const std::string& id)
00155 {
00156   if (id.length() != 40)
00157     throw Git::Exception("Git: not a valid SHA1 id: " + id);
00158 
00159   for (int i = 0; i < 20; ++i)
00160     (*this)[i] = fromHex(id[2 * i], id[2 * i + 1]);
00161 }
00162 
00163 std::string Git::ObjectId::toString() const
00164 {
00165   std::string result(40, '-');
00166 
00167   for (int i = 0; i < 20; ++i)
00168     toHex((*this)[i], result[2 * i], result[2 * i + 1]);
00169 
00170   return result;
00171 }
00172 
00173 Git::Object::Object(const ObjectId& anId, ObjectType aType)
00174   : id(anId),
00175     type(aType)
00176 { }
00177 
00178 Git::Git()
00179   : cache_(3) // cache of 3 git results
00180 { }
00181 
00182 void Git::setRepositoryPath(const std::string& repositoryPath)
00183 { 
00184   repository_ = repositoryPath;
00185   checkRepository();
00186 }
00187 
00188 Git::ObjectId Git::getCommitTree(const std::string& revision) const
00189 {
00190   Git::ObjectId commit = getCommit(revision);
00191   return getTreeFromCommit(commit);
00192 }
00193 
00194 std::string Git::catFile(const ObjectId& id) const
00195 {
00196   std::string result;
00197 
00198   if (!getCmdResult("cat-file -p " + id.toString(), result, -1))
00199     throw Exception("Git: could not cat '" + id.toString() + "'");
00200 
00201   return result;
00202 }
00203 
00204 Git::ObjectId Git::getCommit(const std::string& revision) const
00205 {
00206   std::string sha1Commit;
00207   getCmdResult("rev-parse " + revision, sha1Commit, 0);
00208   return ObjectId(sha1Commit);
00209 }
00210 
00211 Git::ObjectId Git::getTreeFromCommit(const ObjectId& commit) const
00212 {
00213   std::string treeLine;
00214   if (!getCmdResult("cat-file -p " + commit.toString(), treeLine, "tree"))
00215     throw Exception("Git: could not parse tree from commit '" 
00216                     + commit.toString() + "'");
00217 
00218   std::vector<std::string> v;
00219   boost::split(v, treeLine, boost::is_any_of(" "));
00220   if (v.size() != 2)
00221     throw Exception("Git: could not parse tree from commit '"
00222                     + commit.toString() + "': '" + treeLine + "'");
00223   return ObjectId(v[1]);
00224 }
00225 
00226 Git::Object Git::treeGetObject(const ObjectId& tree, int index) const
00227 {
00228   std::string objectLine;
00229   if (!getCmdResult("cat-file -p " + tree.toString(), objectLine, index))
00230     throw Exception("Git: could not read object %"
00231                     + boost::lexical_cast<std::string>(index)
00232                     + "  from tree " + tree.toString());
00233   else {
00234     std::vector<std::string> v1, v2;
00235     boost::split(v1, objectLine, boost::is_any_of("\t"));
00236     if (v1.size() != 2)
00237       throw Exception("Git: could not parse tree object line: '"
00238                       + objectLine + "'");
00239     boost::split(v2, v1[0], boost::is_any_of(" "));
00240     if (v2.size() != 3)
00241       throw Exception("Git: could not parse tree object line: '"
00242                       + objectLine + "'");
00243  
00244     const std::string& stype = v2[1];
00245     ObjectType type;
00246     if (stype == "tree")
00247       type = Tree;
00248     else if (stype == "blob")
00249       type = Blob;
00250     else
00251       throw Exception("Git: Unknown type: " + stype);
00252 
00253     Git::Object result(ObjectId(v2[2]), type);
00254     result.name = v1[1];
00255 
00256     return result;
00257   }
00258 }
00259 
00260 int Git::treeSize(const ObjectId& tree) const
00261 {
00262   return getCmdResultLineCount("cat-file -p " + tree.toString());
00263 }
00264 
00265 bool Git::getCmdResult(const std::string& gitCmd, std::string& result,
00266                        int index) const
00267 {
00268   POpenWrapper p("git --git-dir=" + repository_ + " " + gitCmd, cache_);
00269 
00270   if (p.exitStatus() != 0)
00271     throw Exception("Git error: " + p.readLine(result));
00272 
00273   if (index == -1) {
00274     result = p.contents();
00275     return true;
00276   } else
00277     p.readLine(result);
00278 
00279   for (int i = 0; i < index; ++i) {
00280     if (p.finished())
00281       return false;
00282     p.readLine(result);
00283   }
00284 
00285   return true;
00286 }
00287 
00288 bool Git::getCmdResult(const std::string& gitCmd, std::string& result,
00289                        const std::string& tag) const
00290 {
00291   POpenWrapper p("git --git-dir=" + repository_ + " " + gitCmd, cache_);
00292 
00293   if (p.exitStatus() != 0)
00294     throw Exception("Git error: " + p.readLine(result));
00295 
00296   while (!p.finished()) {
00297     p.readLine(result);
00298     if (boost::starts_with(result, tag))
00299       return true;
00300   }
00301 
00302   return false;
00303 }
00304 
00305 int Git::getCmdResultLineCount(const std::string& gitCmd) const
00306 {
00307   POpenWrapper p("git --git-dir=" + repository_ + " " + gitCmd, cache_);
00308 
00309   std::string r;
00310 
00311   if (p.exitStatus() != 0)
00312     throw Exception("Git error: " + p.readLine(r));
00313 
00314   int result = 0;
00315   while (!p.finished()) {
00316     p.readLine(r);
00317     ++result;
00318   }
00319 
00320   return result;
00321 }
00322 
00323 void Git::checkRepository() const
00324 {
00325   POpenWrapper p("git --git-dir=" + repository_ + " branch", cache_);
00326 
00327   std::string r;
00328   if (p.exitStatus() != 0)
00329     throw Exception("Git error: " + p.readLine(r));
00330 }

Generated on Fri Jul 27 2012 for the C++ Web Toolkit (Wt) by doxygen 1.7.5.1