|
Wt examples
3.3.5
|
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 }
1.7.6.1