Wt examples: /home/koen/project/wt/public-git/wt/examples/gitmodel/Git.C Source File

Wt examples 3.3.0

- Main Page
- Modules
- Namespaces
- Classes
- Files

- File List
- File Members

/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 }