"git@git-repo.iml.unibe.ch:iml-open-source/multi_ssh.git" did not exist on "a6bec484a40a7b0dfdd1349ae509bcfd5fcab1d7"
Select Git revision
rest-api-client.sh.md
-
Hahn Axel (hahn) authoredHahn Axel (hahn) authored
vcs.git.class.php 19.08 KiB
<?php
require_once("vcs.interface.php");
require_once 'cache.class.php';
/**
* version control system :: GIT
* implements vcs interface
*
*
* @author hahn
*/
class vcs implements iVcs {
// class vcs {
/**
* configuration
* @var array
*/
private $_aCfg = array();
/**
* temp dir to fetch repo version and ommit message; its value will be
* generated in set_config()
* @var string
*/
private $_sTempDir = false; //
/**
* filename of ssh key file with complete path
* @var string
*/
private $_sKeyfile = false;
/**
* filename of ssh wrapper script with complete path
* @var string
*/
private $_sWrapper = false;
/**
* flat array with remote branch names
* @var array
*/
private $_aRemoteBranches = array();
/**
* name of the default remote branch to access
* @var type
*/
private $_sCurrentBranch = "origin/master";
/**
* constructor
* @param array $aRepoConfig
*/
public function __construct($aRepoConfig = array()) {
$this->setConfig($aRepoConfig);
$this->getRemoteBranches(); // to fill the cache
}
/**
* add a log messsage
* @global object $oLog
* @param string $sMessage messeage text
* @param string $sLevel warnlevel of the given message
* @return bool
*/
private function log($sMessage,$sLevel="info"){
global $oCLog;
return $oCLog->add(basename(__FILE__)." class ".__CLASS__." - ".$sMessage,$sLevel);
}
/**
* set a config and update internal (private) variables
* @param array $aRepoConfig
* @return boolean
*/
public function setConfig($aRepoConfig = array()) {
// checks
// foreach (array("type", "url") as $key) {
foreach (array("type") as $key) {
if (!array_key_exists($key, $aRepoConfig)) {
die("ERROR: key $key does not exist in config <pre>" . print_r($aRepoConfig, true) . "</pre>");
}
if (!$aRepoConfig[$key]) {
die("ERROR: key $key in config exists but is empty<pre>" . print_r($aRepoConfig, true) . "</pre>");
}
}
if ($aRepoConfig["type"] !== "git") {
die("ERROR: type is not git<pre>" . print_r($aRepoConfig, true) . "</pre>");
}
// set config array
$this->_aCfg = $aRepoConfig;
// define temp dir
$this->_sKeyfile = $this->_aCfg["dataDir"] . "/sshkeys/" . $this->_aCfg["auth"];
$this->_sWrapper = $this->_aCfg["appRootDir"] . "/shellscripts/gitsshwrapper.sh";
$this->_setTempdir();
return $this->_aCfg = $aRepoConfig;
}
private function _setTempdir() {
$this->_sTempDir = $this->_aCfg["url"];
$this->_sTempDir = preg_replace('/[\@\.\:\/]/', '_', $this->_sTempDir);
$this->_sTempDir = $this->_aCfg["tmpDir"] . '/checkout_vcsgit_' . $this->_sTempDir . '/';
$this->_sTempDir .= preg_replace('/[\@\.\:\/]/', '_', $this->_sCurrentBranch) . '/';
if (!file_exists($this->_sTempDir . ".git") || true) {
$sGitCmd = 'export GIT_SSH="' . $this->_sWrapper . '" ; export PKEY="' . $this->_sKeyfile . '" ; ';
$sGitCmd.='mkdir -p "' . $this->_sTempDir . '" && cd "' . $this->_sTempDir . '" && ';
$sGitCmd.='git init >/dev/null && ';
$sGitCmd.='git remote add origin "' . $this->getUrl() . '" 2>&1 ';
// $sGitCmd='time ('.$sGitCmd.')';
// exec($sGitCmd, $aOutput, $iRc);
exec($sGitCmd);
}
return $this->_sTempDir;
}
/**
* set the current branch
* @param string $sBranchname name of the branch
*/
public function setCurrentBranch($sBranchname) {
$this->_sCurrentBranch = $sBranchname;
$this->_setTempdir();
return $this->_sCurrentBranch;
}
/**
* helper: dump values
* @return boolean
*/
public function dump() {
echo "<h3>Dump class " . __CLASS__ . "</h3>";
echo "config array: <pre>" . print_r($this->_aCfg, true) . "</pre>";
echo "temp dir to read revision: " . $this->_sTempDir . "<br>";
return true;
}
/**
* cleanup unneeded files and directories in a checked out directory
* and remove all vcs specific files and directories
* @return bool
*/
public function cleanupWorkdir($sWorkDir) {
if (!is_dir($sWorkDir)) {
return false;
}
shell_exec('rm -rf "' . $sWorkDir . '/.git"');
@unlink($sWorkDir . "/.gitignore");
return true;
}
/**
* get the current branch
* @param string $sBranchname name of the branch
*/
public function getCurrentBranch() {
return $this->_sCurrentBranch;
}
/**
* return the build type, i.e. git|svn|cvs|
* @return string
*/
public function getBuildType() {
return $this->_aCfg["type"];
}
/**
* get a nice name for a cache module based on repo url
* @return type
*/
private function _getNameOfCacheModule() {
return preg_replace('/([^0-9a-z])/i', "", $this->getUrl());
}
/**
* cleanup cache data for this project (revisions, list of branches+tags)
* @return bool
*/
public function cleanupCache($iAge) {
$this->log(__FUNCTION__." start");
$oCache = new AhCache($this->_getNameOfCacheModule());
ob_start();
$oCache->cleanup((int)$iAge);
$sOut = ob_get_contents();
ob_end_clean();
return $sOut;
}
/**
* helper: cache hash with all branches
* @return boolean
*/
private function _cacheRemoteBranches() {
$iTtl=300;
$oCache = new AhCache($this->_getNameOfCacheModule(), "RemoteBranches");
// $this->log(__FUNCTION__." <pre>".print_r($this->_aRemoteBranches, 1)."</pre>");
$oCache->write($this->_aRemoteBranches, $iTtl);
return true;
}
private function _extend_brancharray($a){
}
/**
* read remote repository and get an array with names and revisions of
* all branches and tags
* pre branch you get an array element with the keys revision, name, type
* @param bool $bForceNoCache flag to overrde cache
* @return array
*/
private function _fetchRemoteBranches($bForceNoCache = false) {
$this->log(__FUNCTION__." start");
$aReturn = array();
if (!$this->getUrl()) {
return false;
}
$oCache = new AhCache($this->_getNameOfCacheModule(), "RemoteBranches");
$aOutput=false;
$iRc=false;
// list of cached branch keys
if ($oCache->isExpired() || $bForceNoCache) {
$sWorkdir = dirname($this->_sTempDir) . '/fetchRemoteBranches/';
$sGitCmd = 'export GIT_SSH="' . $this->_sWrapper . '" ; export PKEY="' . $this->_sKeyfile . '" ; ';
if (!file_exists($sWorkdir . ".git")) {
$sGitCmd.='mkdir -p "' . $sWorkdir . '" && cd "' . $sWorkdir . '" && ';
$sGitCmd.='git init >/dev/null && ';
$sGitCmd.='git remote add origin "' . $this->getUrl() . '" 2>&1 && ';
} else {
$sGitCmd.='cd "' . $sWorkdir . '" 2>&1 && ';
}
$sGitCmd.='git ls-remote --heads --tags origin 2>&1 ;';
$this->log(__FUNCTION__." start command $sGitCmd");
exec($sGitCmd, $aOutput, $iRc);
$this->log(__FUNCTION__." end with rc=$iRc ", ($iRc==0 ? 'info':'error'));
if ($iRc == 0) {
// use cache that getCommitmessageByBranch can access it
$this->_aRemoteBranches = $oCache->read();
$this->log(__FUNCTION__ . ' data from cache: <pre>'.print_r($this->_aRemoteBranches, 1).'</pre>');
/**
* $aOutput = Array
* (
* [0] => cd7b238a75fe3df3f53ca3c258078d6736cc5bec refs/heads/foreman-integration
* [1] => 68cef2c74b58db8e13413c1c54104e060d8ffbb3 refs/heads/master
* [2] => cae3a1eaee180b05fff883d1cfb36d09778dec2c refs/heads/task-1726-gitssh-extensions
* )
*/
foreach ($aOutput as $sBranchLine) {
$this->log(__FUNCTION__ . ' loop over output of git ls-remote <pre>'.print_r($sBranchLine, 1).'</pre>');
$aTmp = explode("\t", $sBranchLine);
$aBranch = explode("/", $aTmp[1]);
$sBranch = array_pop($aBranch); // remove "refs"
// skip dereferences
// http://stackoverflow.com/questions/15472107/when-listing-git-ls-remote-why-theres-after-the-tag-name
if (!preg_match('/\^\{\}$/', $sBranch)) {
$sRevision = $aTmp[0];
$sType = array_pop($aBranch);
$sName = ($sType == "heads") ? "origin/" . $sBranch : $sBranch;
$sBranchKey = $sName;
$this->log(__FUNCTION__ . ' $sBranchKey = '.$sBranchKey);
$sMessage = $this->getCommitmessageByBranch($sName, $sRevision);
$aReturn[$sBranchKey] = array(
'debug'=> $aTmp,
'revision' => $sRevision,
'name' => $sName,
'label' => $sType . ': ' . $sBranch,
'type' => $sType,
'message' => $sMessage
);
}
}
$this->_aRemoteBranches = $aReturn;
$this->_cacheRemoteBranches();
}
} else {
$this->_aRemoteBranches = $oCache->read();
}
return $this->_aRemoteBranches;
}
/**
* get a flat array with names of all remote branches
* @return array
*/
public function getRemoteBranches() {
$this->log(__FUNCTION__." start");
if (!$this->_aRemoteBranches) {
$this->_fetchRemoteBranches();
}
return $this->_aRemoteBranches;
}
/**
* get current revision and commit message from remote repository
* @see $this::getRevision
* @param boolean $bRefresh optional: refresh data; default: use cache
* @return array
*/
public function getRepoRevision($bRefresh=false) {
$this->log(__FUNCTION__."($bRefresh) start");
$sMessage = $this->getCommitmessageByBranch(false, $bRefresh ? 'dummy_to_force_refresh' : false);
if ($sMessage) {
$aReturn = array(
'branch' => $this->_sCurrentBranch,
'revision' => $this->_aRemoteBranches[$this->_sCurrentBranch]['revision'],
'message' => $sMessage,
);
} else {
$aReturn = $this->getRevision(false);
}
return $aReturn;
}
/**
* get a commit message of a given branch
* @param string $sBranch name of a branch
* @param string $sVerifyRevision optional: revision to verify if it is the newsest
* @return string
*/
public function getCommitmessageByBranch($sBranch = false, $sVerifyRevision = false) {
$this->log(__FUNCTION__."($sBranch, $sVerifyRevision) start");
if (!$sBranch) {
$sBranch = $this->_sCurrentBranch;
}
// try to get infos from the cache
if (
is_array($this->_aRemoteBranches)
&& (
array_key_exists($sBranch, $this->_aRemoteBranches) && $sVerifyRevision && $this->_aRemoteBranches[$sBranch]['revision'] == $sVerifyRevision
||
array_key_exists($sBranch, $this->_aRemoteBranches) && !$sVerifyRevision
)
) {
// it is up to date - doing nothing
$this->log(__FUNCTION__." return cached data");
return $this->_aRemoteBranches[$sBranch]['message'];
}
// ok, then I need to read it
$this->log(__FUNCTION__." return fresh data");
if ($this->_sCurrentBranch != $sBranch) {
$sSaveBranch = $this->_sCurrentBranch;
$this->setCurrentBranch($sBranch);
$a = $this->getRevision(false);
$this->setCurrentBranch($sSaveBranch);
} else {
$a = $this->getRevision(false);
}
// merge with cached info ... to add type and label
if(isset($this->_aRemoteBranches[$a['branch']])){
$this->_aRemoteBranches[$a['branch']]=array_merge($this->_aRemoteBranches[$a['branch']], $a);
} else {
$this->_aRemoteBranches[$a['branch']]=$a;
}
// store in cache
$this->_cacheRemoteBranches();
return $a['message'];
}
/**
* get current revision and commit message from an existing directory or a
* remote repository
* the return will fill $this->_aData["phases"]["source"] in project class
* (array keys are revision, message or error)
* if ok:
* array(
* "branch" => $sRevision,
* "revision" => $sRevision,
* "message" => $sCommitmessage
* );
*
* on error:
* array(
* "error" => $sErrormessage,
* );
* @param string $sWorkDir optional: local directory with initialized git repo
* @return array
*/
public function getRevision($sWorkDir = false) {
$this->log(__FUNCTION__." start");
$aReturn = array();
$aOutput=array();
$iRc=false;
if (!$this->getUrl()) {
return false;
}
$sGitCmd = 'export GIT_SSH="' . $this->_sWrapper . '" ; export PKEY="' . $this->_sKeyfile . '" ; ';
if ($sWorkDir) {
$sGitCmd.='cd "' . $sWorkDir . '" && ';
} else {
if (!file_exists($this->_sTempDir . ".git")) {
$sGitCmd.='mkdir -p "' . $this->_sTempDir . '" && cd "' . $this->_sTempDir . '" && ';
$sGitCmd.='git init >/dev/null 2>&1 && ';
$sGitCmd.='git remote add origin "' . $this->getUrl() . '" 2>&1 && ';
} else {
$sGitCmd.='cd "' . $this->_sTempDir . '" && ';
}
// TODO: git 1.9 does needs only the line with --tags
$sGitCmd.=' ( '
. 'git fetch --update-head-ok --tags --depth 1 2>&1 ; ' // 1.5 s
. 'git fetch --update-head-ok --depth 1 2>&1 ' // 1.5 s
. ') && ';
}
$sGitCmd.='git log -1 "' . $this->_sCurrentBranch . '" 2>&1 ; '; // 0.0 s
$this->log(__FUNCTION__." start command $sGitCmd");
// $sLoginfo = shell_exec($sGitCmd);
exec($sGitCmd, $aOutput, $iRc);
$sLoginfo= implode("\n", $aOutput);
$this->log(__FUNCTION__." end with rc=$iRc <pre>$sLoginfo</pre>", ($iRc==0 ? 'info':'error'));
/*
*
* example output:
From gitlab.iml.unibe.ch:admins/imldeployment
* [new branch] master -> origin/master
commit 0b0dbe0dee80ca71ff43a54641d616c131e6fd8a
Author: Axel Hahn
Date: Fri Dec 12 16:35:38 2014 +0100
- added: skip dereferenced tags in git (tags ending ^{} )
- added: modal infobox in build page if you switch the branch
- added: git uses a cache for taglist and revision infos (ttl is 5 min)
*
*/
// parse revision
$sRevision = false;
$aRev=array();
if (preg_match('#commit\ (.*)#', $sLoginfo, $aRev)) {
$sRevision = $aRev[1];
}
if ($sRevision) {
$sCommitMsg = $sLoginfo;
$sCommitMsg = preg_replace('/From\ .*\n/', '', $sCommitMsg);
$sCommitMsg = preg_replace('/\ \*.*\n/', '', $sCommitMsg);
$sCommitMsg = preg_replace('/commit.*\n/', '', $sCommitMsg);
// keep these to see them in the output:
// $sCommitMsg=preg_replace('/Author:\ .*\n/', '', $sCommitMsg);
// $sCommitMsg=preg_replace('/Date:\ .*\n/', '', $sCommitMsg);
$aReturn = array(
"branch" => $this->_sCurrentBranch,
"revision" => $sRevision,
"message" => $sCommitMsg // ."\n". microtime(true),
);
} else {
if (!$sLoginfo) {
$sLoginfo = $sGitCmd;
}
// echo "DEBUG: error on reading git revision<br>";
$aReturn = array(
"error" => '<pre>' . $sLoginfo . '<hr>' . $sGitCmd . '</pre>'
);
}
// $this->log(__FUNCTION__ . ' return is <pre>'.print_r($aReturn, 1).'</pre>');
return $aReturn;
}
/**
* get sources from vsc and check them out in given directory
* @return bool
*/
public function getSources($sWorkDir) {
$this->log(__FUNCTION__." start");
if (!$sWorkDir || !is_dir($sWorkDir)) {
return false;
}
if (!$this->getUrl()) {
return false;
}
$sBranchname = str_replace("origin/", "", $this->_sCurrentBranch);
$sGitCmd = 'export GIT_SSH="' . $this->_sWrapper . '" ; export PKEY="' . $this->_sKeyfile . '" ; ';
$sReturn=false;
$iRc=false;
// this does not checkout tags in git v1.7 - only branches:
// $sGitCmd .= 'echo git clone --depth 1 --recursive --branch "' . $sBranchname . '" "' . $this->getUrl() . '" "' . $sWorkDir . '" ; ';
// $sGitCmd .= ' git clone --depth 1 --recursive --branch "' . $sBranchname . '" "' . $this->getUrl() . '" "' . $sWorkDir . '" 2>&1; ';
//
$sGitCmd .= 'echo git clone "' . $this->getUrl() . '" "'.$sWorkDir.'" 2>&1 \&\& cd "'.$sWorkDir.'" \&\& git checkout "' . $sBranchname . '" ; ';
$sGitCmd .= ' git clone "' . $this->getUrl() . '" "'.$sWorkDir.'" 2>&1 && cd "'.$sWorkDir.'" && git checkout "' . $sBranchname . '" 2>&1 ';
$this->log(__FUNCTION__." start command $sGitCmd");
// $sReturn = shell_exec($sGitCmd);
exec($sGitCmd, $sReturn, $iRc);
$this->log(__FUNCTION__." end command $sGitCmd");
return implode("\n", $sReturn). "\nrc=$iRc";
}
/**
* return url to vcs sources
*/
public function getUrl() {
$this->log(__FUNCTION__." start");
return $this->_aCfg["url"];
}
/**
* return url to view sources in webrowser to generate an infolink
*/
public function getWebGuiUrl() {
$this->log(__FUNCTION__." start");
return $this->_aCfg["webaccess"];
}
}