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));
              }