// Loads a revision from the cache Revision *Cache::get(const std::string &id) { if (!m_loaded) { load(); } std::string dir = cacheDir(); std::pair<uint32_t, uint32_t> offset = m_index[id]; std::string path = str::printf("%s/cache.%u", dir.c_str(), offset.first); if (m_cin == NULL || offset.first != m_ciindex) { delete m_cin; m_cin = new BIStream(path); m_ciindex = offset.first; if (!m_cin->ok()) { throw PEX(str::printf("Unable to read from cache file: %s", path.c_str())); } } if (!m_cin->seek(offset.second)) { throw PEX(str::printf("Unable to read from cache file: %s", path.c_str())); } Revision *rev = new Revision(id); std::vector<char> data; *m_cin >> data; data = utils::uncompress(data); if (data.empty()) { throw PEX(str::printf("Unable to read from cache file: %s", path.c_str())); } MIStream rin(data); if (!rev->load03(rin)) { throw PEX(str::printf("Unable to read from cache file: %s", path.c_str())); } return rev; }
// Searches the current PATH for the given program std::string which(const std::string &program) { std::string progPath; char *path = getenv("PATH"); if (path == NULL) { throw PEX("PATH is not set"); } std::vector<std::string> ls; #ifdef POS_WIN ls = str::split(path, ";"); #else ls = str::split(path, ":"); #endif for (size_t i = 0; i < ls.size(); i++) { std::string t = ls[i] + "/" + program; #ifdef POS_WIN t += ".exe"; #endif if (sys::fs::fileExecutable(t)) { progPath = t; break; } } if (progPath.empty()) { throw PEX(std::string("Can't find ") + program + " in PATH"); } return progPath; }
// Loads the index file void Cache::load() { std::string path = cacheDir(); PDEBUG << "Using cache dir: " << path << endl; m_index.clear(); m_loaded = true; bool created; checkDir(path, &created); lock(); if (created) { return; } // For git repositories, the hardest part is calling uuid() sys::datetime::Watch watch; GZIStream *in = new GZIStream(path+"/index"); if (!in->ok()) { Logger::info() << "Cache: Empty cache for '" << uuid() << '\'' << endl; return; } uint32_t version; *in >> version; switch (checkVersion(version)) { case OutOfDate: delete in; throw PEX(str::printf("Cache is out of date - please run the check_cache report", version)); case UnknownVersion: delete in; throw PEX(str::printf("Unknown cache version number %u - please run the check_cache report", version)); default: break; } Logger::status() << "Loading cache index... " << ::flush; std::string buffer; std::pair<uint32_t, uint32_t> pos; uint32_t crc; while (!(*in >> buffer).eof()) { if (buffer.empty()) { break; } *in >> pos.first >> pos.second; *in >> crc; m_index[buffer] = pos; } Logger::status() << "done" << endl; delete in; Logger::info() << "Cache: Loaded " << m_index.size() << " revisions in " << watch.elapsedMSecs() << " ms" << endl; }
Revision *LdbCache::get(const std::string &id) { if (!m_db) opendb(); std::string value; leveldb::Status s = m_db->Get(leveldb::ReadOptions(), id, &value); if (!s.ok()) { throw PEX(str::printf("Error reading from cache: %s", s.ToString().c_str())); } Revision *rev = new Revision(id); MIStream rin(value.c_str(), value.length()); if (!rev->load(rin)) { throw PEX(str::printf("Unable to read from cache: Data corrupted")); } return rev; }
// Constructor Mutex::Mutex() { int ret = pthread_mutex_init(&m_pmx, NULL); switch (ret) { case 0: break; case EBUSY: throw PEX("Mutex is locked by a thread"); default: throw PEX_ERR(ret); } }
// Wrapper for strptime() int64_t ptime(const std::string &str, const std::string &format) { struct tm time; memset(&time, 0x00, sizeof(struct tm)); if (strptime(str.c_str(), format.c_str(), &time) == NULL) { throw PEX(std::string("Error parsing time '" + str + "' with format '" + format + "'")); } return mktime(&time); }
// Destructor Mutex::~Mutex() { int ret = pthread_mutex_destroy(&m_pmx); switch (ret) { case 0: break; case EBUSY: throw PEX("Mutex is locked by a thread"); default: throw PEX_ERR(ret); } }
// Wrapper for pthread_cancel() void Thread::cancel() { int ret = pthread_cancel(m_pth); switch (ret) { case 0: break; case ESRCH: throw PEX("No such thread"); default: throw PEX_ERR(ret); } }
// Adds the revision to the cache void LdbCache::put(const std::string &id, const Revision &rev) { if (!m_db) opendb(); MOStream rout; rev.write(rout); std::vector<char> data(rout.data()); leveldb::Status s = m_db->Put(leveldb::WriteOptions(), id, std::string(data.begin(), data.end())); if (!s.ok()) { throw PEX(str::printf("Error writing to cache: %s", s.ToString().c_str())); } }
// Locks the cache directory for this process void Cache::lock() { if (m_lock >= 0) { PDEBUG << "Already locked (" << m_lock << ")" << endl; return; } std::string path = cacheDir(); std::string lock = path + "/lock"; m_lock = ::open(lock.c_str(), O_WRONLY | O_CREAT, S_IWUSR); if (m_lock == -1) { throw PEX(str::printf("Unable to lock cache %s: %s", path.c_str(), PepperException::strerror(errno).c_str())); } PTRACE << "Locking file " << lock << endl; struct flock flck; memset(&flck, 0x00, sizeof(struct flock)); flck.l_type = F_WRLCK; if (fcntl(m_lock, F_SETLK, &flck) == -1) { throw PEX(str::printf("Unable to lock cache %s, it may be used by another instance", path.c_str())); } }
// Creates a temporary file std::string Plot::tempfile(std::ofstream &out) { std::string path; sys::fs::mkstemp(&path); out.open(path.c_str()); if (out.bad()) { throw PEX(str::printf("Unable to open temporary file '%s'", path.c_str())); } m_tempfiles.push_back(path); return path; }
// Checks if the diffstat of the given revision is already cached bool LdbCache::lookup(const std::string &id) { if (!m_db) opendb(); std::string value; leveldb::Status s = m_db->Get(leveldb::ReadOptions(), id, &value); if (s.IsNotFound()) { return false; } if (!s.ok()) { throw PEX(str::printf("Error reading from cache: %s", s.ToString().c_str())); } return true; }
// Unlocks the cache directory void Cache::unlock() { if (m_lock < 0) { PDEBUG << "Not locked yet (" << m_lock << ")" << endl; return; } std::string path = cacheDir(); PTRACE << "Unlocking file " << path + "/lock" << endl; struct flock flck; memset(&flck, 0x00, sizeof(struct flock)); flck.l_type = F_UNLCK; if (fcntl(m_lock, F_SETLK, &flck) == -1) { throw PEX(str::printf("Unable to unlock cache, please delete %s/lock manually if required", path.c_str())); } if (::close(m_lock) == -1) { throw PEX_ERRNO(); } }
// Allocates a backend of a specific repository type Backend *Backend::backendForName(const std::string &name, const Options &options) { #ifdef USE_SUBVERSION if (name == "svn" || name == "subversion") { return new SubversionBackend(options); } #endif #ifdef USE_GIT if (name == "git") { return new GitBackend(options); } #endif #ifdef USE_MERCURIAL if (name == "hg" || name == "mercurial") { return new MercurialBackend(options); } #endif throw PEX(std::string("No such backend: " + name)); }
// Opens the database connection void LdbCache::opendb() { if (m_db) return; std::string path = cacheDir() + "/ldb"; PDEBUG << "Using cache dir: " << path << endl; if (!sys::fs::dirExists(path)) { sys::fs::mkpath(path); } leveldb::Options options; options.create_if_missing = false; leveldb::Status s = leveldb::DB::Open(options, path, &m_db); if (!s.ok()) { // New cache: Import revisions from old cache options.create_if_missing = true; leveldb::Status s = leveldb::DB::Open(options, path, &m_db); if (!s.ok()) { throw PEX(str::printf("Unable to open database %s: %s", path.c_str(), s.ToString().c_str())); } Cache c(m_backend, m_opts); import(&c); } }
// Detects Gnuplot terminals, and checks wheter the X11 terminal is available void Plot::detectTerminals() { if (!s_detectTerminals) { // Run only once return; } std::string terms; try { // Set dummy pager for terminal listing char *oldPager = getenv("PAGER"); if (oldPager) { oldPager = strdup(oldPager); } setenv("PAGER", "cat", 1); int ret; std::string path = sys::fs::which("gnuplot"); terms = sys::io::exec(&ret, path.c_str(), "-e", "set terminal; quit"); if (ret != 0) { throw PEX(terms); } if (!oldPager) { unsetenv("PAGER"); } else { setenv("PAGER", oldPager, 1); free(oldPager); } } catch (const std::exception &ex) { Logger::info() << "Can't query list of supported Gnuplot terminals (" << ex.what() << ")" << std::endl; } s_hasX11Term = (terms.find("x11") != std::string::npos); s_detectTerminals = false; }
// Static diff parsing function for unified diffs Diffstat DiffParser::parse(std::istream &in) { static const char marker[] = "==================================================================="; std::string str, file; Diffstat ds; Diffstat::Stat stat; int chunk[2] = {0, 0}; while (in.good()) { std::getline(in, str); if (chunk[0] <= 0 && chunk[1] <= 0 && (!str.compare(0, 4, "--- ") || !str.compare(0, 4, "+++ "))) { if (!file.empty() && !stat.empty()) { ds.m_stats[file] = stat; file = std::string(); } stat = Diffstat::Stat(); std::vector<std::string> header = str::split(str.substr(4), "\t"); if (header.empty()) { throw PEX(std::string("EMPTY HEADER: ")+str); } if (header[0] != "/dev/null") { file = header[0]; if (file[0] == '"' && file[file.length()-1] == '"') { file = file.substr(1, file.length()-2); } if (!file.compare(0, 2, "a/") || !file.compare(0, 2, "b/")) { file = file.substr(2); } } } else if (!str.compare(0, 2, "@@")) { std::vector<std::string> header = str::split(str.substr(2), "@@", true); if (header.empty()) { throw PEX(std::string("EMPTY HEADER: ")+str); } std::vector<std::string> ranges = str::split(header[0], " ", true); if (ranges.size() < 2 || ranges[0].empty() || ranges[1].empty()) { throw PEX(std::string("EMPTY HEADER: ")+str); } size_t pos; if ((pos = ranges[0].find(',')) != std::string::npos) { str::str2int(ranges[0].substr(pos+1), &chunk[(ranges[0][0] == '-' ? 0 : 1)]); } else { chunk[(ranges[0][0] == '-' ? 0 : 1)] = 1; } if ((pos = ranges[1].find(',')) != std::string::npos) { str::str2int(ranges[1].substr(pos+1), &chunk[(ranges[1][0] == '-' ? 0 : 1)]); } else { chunk[(ranges[1][0] == '-' ? 0 : 1)] = 1; } } else if (!str.empty() && str[0] == '-') { stat.cdel += str.length(); ++stat.ldel; --chunk[0]; } else if (!str.empty() && str[0] == '+') { stat.cadd += str.length(); ++stat.ladd; --chunk[1]; } else if (str == marker) { chunk[0] = chunk[1] = 0; } else if (!str.empty() && str[0] == (char)EOF) { // git diff-tree pipe prints EOF after diff data break; } else { if (chunk[0] > 0) --chunk[0]; if (chunk[1] > 0) --chunk[1]; } } if (!file.empty() && !stat.empty()) { ds.m_stats[file] = stat; } return ds; }