Source code of the Git explorer example

Browse below the source code for Wt's Git explorer example.

  • gitmodel
    • class Git
      • Git.C
        • Git.h
        • class GitModel
          • GitView.C
            • gitview.css
              • icons
              /*
              * Copyright (C) 2008 Emweb bv, Herent, Belgium.
              *
              * See the LICENSE file for terms of use.
              */
              #include "Git.h"

              #include <iostream>
              #include <vector>
              #include <stdio.h>
              #include <ctype.h>

              #include <Wt/WAny.h>
              #include <boost/algorithm/string/classification.hpp>
              #include <boost/algorithm/string/predicate.hpp>
              #include <boost/algorithm/string/split.hpp>
              #include <boost/filesystem.hpp>

              using namespace Wt;

              /*
              * Small utility methods and classes.
              */
              namespace {
              unsigned char fromHex(char b)
              {
              if (b <= '9')
              return b - '0';
              else if (b <= 'F')
              return (b - 'A') + 0x0A;
              else
              return (b - 'a') + 0x0A;
              }

              unsigned char fromHex(char msb, char lsb)
              {
              return (fromHex(msb) << 4) + fromHex(lsb);
              }

              char toHex(unsigned char b)
              {
              if (b < 0xA)
              return '0' + b;
              else
              return 'a' + (b - 0xA);
              }

              void toHex(unsigned char b, char& msb, char& lsb)
              {
              lsb = toHex(b & 0x0F);
              msb = toHex(b >> 4);
              }

              /*
              * Run a command and capture its stdout into a string.
              * Uses and maintains a cache.
              */
              class POpenWrapper
              {
              public:
              POpenWrapper(const std::string& cmd, Git::Cache& cache) {
              std::string s = sanitize(cmd);

              bool cached = false;

              for (Git::Cache::iterator i = cache.begin(); i != cache.end(); ++i)
              if (i->first == s) {
              content_ = i->second;
              status_ = 0;
              cached = true;
              cache.splice(cache.begin(), cache, i); // implement LRU
              break;
              }

              if (!cached) {
              std::cerr << s << std::endl;
              FILE *stream = popen((s + " 2>&1").c_str(), "r");
              if (!stream)
              throw Git::Exception("Git: could not execute: '" + s + "'");

              int n = 0;
              do {
              char buffer[32000];
              n = fread(buffer, 1, 30000, stream);
              buffer[n] = 0;
              content_ += std::string(buffer, n);
              } while (n);

              status_ = pclose(stream);

              if (status_ == 0) {
              cache.pop_back(); // implement LRU
              cache.push_front(std::make_pair(s, content_));
              }
              }

              idx_ = 0;
              }

              std::string& readLine(std::string& r, bool stripWhite = true) {
              r.clear();

              while (stripWhite
              && (idx_ < content_.length()) && isspace(content_[idx_]))
              ++idx_;

              while (idx_ < content_.size() && content_[idx_] != '\n') {
              r += content_[idx_];
              ++idx_;
              }

              if (idx_ < content_.size())
              ++idx_;

              return r;
              }

              const std::string& contents() const {
              return content_;
              }

              bool finished() const {
              return idx_ == content_.size();
              }

              int exitStatus() const {
              return status_;
              }

              private:
              std::string content_;
              unsigned int idx_;
              int status_;


              std::string sanitize(const std::string& cmd) {
              /*
              * Sanitize cmd to not include any dangerous tokens that could allow
              * execution of shell commands: <>&;|[$`
              */
              std::string result;
              std::string unsafe = "<>&;|[$`";

              for (auto item : cmd) {
              if (unsafe.find(item) == std::string::npos)
              result += item;
              }

              return result;
              }
              };
              }

              /*
              * About the git files:
              * type="commit":
              * - of a reference, like the SHA1 ID obtained from git-rev-parse of a
              * particular revision
              * - contains the SHA1 ID of the tree
              *
              * type="tree":
              * 100644 blob 0732f5e4def48d6d5b556fbad005adc994af1e0b CMakeLists.txt
              * 040000 tree 037d59672d37e116f6e0013a067a7ce1f8760b7c Wt
              * <mode> SP <type> SP <object> TAB <file>
              *
              * type="blob": contents of a file
              */

              Git::Exception::Exception(const std::string& msg)
              : std::runtime_error(msg)
              { }

              Git::ObjectId::ObjectId()
              { }

              Git::ObjectId::ObjectId(const std::string& id)
              {
              if (id.length() != 40)
              throw Git::Exception("Git: not a valid SHA1 id: " + id);

              for (int i = 0; i < 20; ++i)
              (*this)[i] = fromHex(id[2 * i], id[2 * i + 1]);
              }

              std::string Git::ObjectId::toString() const
              {
              std::string result(40, '-');

              for (int i = 0; i < 20; ++i)
              toHex((*this)[i], result[2 * i], result[2 * i + 1]);

              return result;
              }

              Git::Object::Object(const ObjectId& anId, ObjectType aType)
              : id(anId),
              type(aType)
              { }

              Git::Git()
              : is_bare_(false),
              cache_(3) // cache of 3 git results
              { }

              void Git::setRepositoryPath(const std::string& repositoryPath)
              {
              namespace fs = boost::filesystem;
              boost::system::error_code ignored;
              is_bare_ = !fs::is_directory(fs::path(repositoryPath) / ".git", ignored);
              repository_ = repositoryPath;
              checkRepository();
              }

              Git::ObjectId Git::getCommitTree(const std::string& revision) const
              {
              Git::ObjectId commit = getCommit(revision);
              return getTreeFromCommit(commit);
              }

              std::string Git::catFile(const ObjectId& id) const
              {
              std::string result;

              if (!getCmdResult("cat-file -p " + id.toString(), result, -1))
              throw Exception("Git: could not cat '" + id.toString() + "'");

              return result;
              }

              Git::ObjectId Git::getCommit(const std::string& revision) const
              {
              std::string sha1Commit;
              getCmdResult("rev-parse " + revision, sha1Commit, 0);
              return ObjectId(sha1Commit);
              }

              Git::ObjectId Git::getTreeFromCommit(const ObjectId& commit) const
              {
              std::string treeLine;
              if (!getCmdResult("cat-file -p " + commit.toString(), treeLine, "tree"))
              throw Exception("Git: could not parse tree from commit '"
              + commit.toString() + "'");

              std::vector<std::string> v;
              boost::split(v, treeLine, boost::is_any_of(" "));
              if (v.size() != 2)
              throw Exception("Git: could not parse tree from commit '"
              + commit.toString() + "': '" + treeLine + "'");
              return ObjectId(v[1]);
              }

              Git::Object Git::treeGetObject(const ObjectId& tree, int index) const
              {
              std::string objectLine;
              if (!getCmdResult("cat-file -p " + tree.toString(), objectLine, index))
              throw Exception("Git: could not read object %"
              + asString(index).toUTF8()
              + " from tree " + tree.toString());
              else {
              std::vector<std::string> v1, v2;
              boost::split(v1, objectLine, boost::is_any_of("\t"));
              if (v1.size() != 2)
              throw Exception("Git: could not parse tree object line: '"
              + objectLine + "'");
              boost::split(v2, v1[0], boost::is_any_of(" "));
              if (v2.size() != 3)
              throw Exception("Git: could not parse tree object line: '"
              + objectLine + "'");

              const std::string& stype = v2[1];
              ObjectType type;
              if (stype == "tree")
              type = Tree;
              else if (stype == "blob")
              type = Blob;
              else
              throw Exception("Git: Unknown type: " + stype);

              Git::Object result(ObjectId(v2[2]), type);
              result.name = v1[1];

              return result;
              }
              }

              int Git::treeSize(const ObjectId& tree) const
              {
              return getCmdResultLineCount("cat-file -p " + tree.toString());
              }

              std::string Git::baseCmd() const
              {
              if (is_bare_)
              return "git --git-dir=" + repository_;
              else
              return "git --git-dir=" + repository_ + "/.git"
              " --work-tree=" + repository_;
              }

              bool Git::getCmdResult(const std::string& gitCmd, std::string& result,
              int index) const
              {
              POpenWrapper p(baseCmd() + " " + gitCmd, cache_);

              if (p.exitStatus() != 0)
              throw Exception("Git error: " + p.readLine(result));

              if (index == -1) {
              result = p.contents();
              return true;
              } else
              p.readLine(result);

              for (int i = 0; i < index; ++i) {
              if (p.finished())
              return false;
              p.readLine(result);
              }

              return true;
              }

              bool Git::getCmdResult(const std::string& gitCmd, std::string& result,
              const std::string& tag) const
              {
              POpenWrapper p(baseCmd() + " " + gitCmd, cache_);

              if (p.exitStatus() != 0)
              throw Exception("Git error: " + p.readLine(result));

              while (!p.finished()) {
              p.readLine(result);
              if (boost::starts_with(result, tag))
              return true;
              }

              return false;
              }

              int Git::getCmdResultLineCount(const std::string& gitCmd) const
              {
              POpenWrapper p(baseCmd() + " " + gitCmd, cache_);

              std::string r;

              if (p.exitStatus() != 0)
              throw Exception("Git error: " + p.readLine(r));

              int result = 0;
              while (!p.finished()) {
              p.readLine(r);
              ++result;
              }

              return result;
              }

              void Git::checkRepository() const
              {
              POpenWrapper p(baseCmd() + " branch", cache_);

              std::string r;
              if (p.exitStatus() != 0)
              throw Exception("Git error: " + p.readLine(r));
              }