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