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