Wt examples
3.2.1
|
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 }