00001
00002
00003
00004
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
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
00053
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);
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();
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
00134
00135
00136
00137
00138
00139
00140
00141
00142
00143
00144
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)
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 }