diff --git a/public_html/deployment/classes/vcs.git.class.php b/public_html/deployment/classes/vcs.git.class.php index fd1f6db67e2d5422b8b397ef3ba1aaf13a6acfdb..66999944b3a537a9543de9cf4f5e68fe68f25c7a 100644 --- a/public_html/deployment/classes/vcs.git.class.php +++ b/public_html/deployment/classes/vcs.git.class.php @@ -44,7 +44,7 @@ class vcs implements iVcs { private $_aRemoteBranches = array(); /** - * name of the remote branch to access + * name of the default remote branch to access * @var type */ private $_sCurrentBranch = "origin/master"; @@ -55,6 +55,7 @@ class vcs implements iVcs { */ public function __construct($aRepoConfig = array()) { $this->setConfig($aRepoConfig); + $this->getRemoteBranches(); // to fill the cache } /** @@ -82,21 +83,38 @@ class vcs implements iVcs { $this->_aCfg = $aRepoConfig; // define temp dir - $this->_sTempDir = $this->_aCfg["url"]; - $this->_sTempDir = preg_replace('/[\@\.\:\/]/', '_', $this->_sTempDir); - $this->_sTempDir = (getenv("temp") ? getenv("temp") : "/tmp") . '/checkout_vcsgit_' . $this->_sTempDir . "/"; + $this->_setTempdir(); $this->_sKeyfile = $this->_aCfg["dataDir"] . "/sshkeys/" . $this->_aCfg["auth"]; $this->_sWrapper = $this->_aCfg["appRootDir"] . "/shellscripts/gitsshwrapper.sh"; return $this->_aCfg = $aRepoConfig; } + private function _setTempdir() { + $this->_sTempDir = $this->_aCfg["url"]; + $this->_sTempDir = preg_replace('/[\@\.\:\/]/', '_', $this->_sTempDir); + $this->_sTempDir = (getenv("temp") ? getenv("temp") : "/tmp") . '/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); + } + return $this->_sTempDir; + } + /** * set the current branch * @param string $sBranchname name of the branch */ public function setCurrentBranch($sBranchname) { - return $this->_sCurrentBranch = $sBranchname; + $this->_sCurrentBranch = $sBranchname; + $this->_setTempdir(); + return $this->_sCurrentBranch; } /** @@ -140,24 +158,23 @@ class vcs implements iVcs { return $this->_aCfg["type"]; } - - private function _getNameOfCacheModule(){ - $sReturn="git".$this->getUrl(); - $sReturn=preg_replace('/([^0-9a-z])/i', "", $sReturn); - // echo "DEBUG: getNameOfCacheModule $sReturn<br>"; - return $sReturn; + /** + * 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 */ - private function _cleanupCache(){ + private function _cleanupCache($iAge) { require_once 'cache.class.php'; - $oCache=new AhCache($this->_getNameOfCacheModule()); - return $oCache->cleanup(); + $oCache = new AhCache($this->_getNameOfCacheModule()); + return $oCache->cleanup((int)$iAge); } - - /** * read remote repository and get an array with names and revisions of @@ -166,61 +183,68 @@ class vcs implements iVcs { * @param bool $bForceNoCache flag to overrde cache * @return array */ - private function _fetchRemoteBranches($bForceNoCache=false) { + private function _fetchRemoteBranches($bForceNoCache = false) { $aReturn = array(); - - $sGitCmd = 'export GIT_SSH="' . $this->_sWrapper . '" ; export PKEY="' . $this->_sKeyfile . '" ; '; + if (!$this->getUrl()) { return false; } - $iTtl=300; // cache for 5 min + + $iTtl = 300; // cache for 5 min require_once 'cache.class.php'; - $oCache=new AhCache($this->_getNameOfCacheModule(), "RemoteBranches"); - if ($oCache->isExpired() || $bForceNoCache){ - if (!file_exists($this->_sTempDir . ".git")) { - $sGitCmd.='mkdir "' . $this->_sTempDir . '" && cd "' . $this->_sTempDir . '" && '; + $oCache = new AhCache($this->_getNameOfCacheModule(), "RemoteBranches"); + + // list of cached branch keys + $aKeysToDelete = array(); + 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 "' . $this->_sTempDir . '" 2>&1 && '; + $sGitCmd.='cd "' . $sWorkdir . '" 2>&1 && '; } - // $sGitCmd.='git branch -r ; '; - $sGitCmd.='git ls-remote --heads origin 2>&1 ; git ls-remote --tags origin 2>&1 '; + $sGitCmd.='git ls-remote --heads --tags origin 2>&1 ;'; exec($sGitCmd, $aOutput, $iRc); if ($iRc == 0) { - foreach ($aOutput as $sBranch) { - $aTmp = explode("\t", $sBranch); - $aBranch = explode("/", $aTmp[1]); + // use cache that getCommitmessageByBranch can access it + $this->_aRemoteBranches = $oCache->read(); + + foreach ($aOutput as $sBranchLine) { + $aTmp = explode("\t", $sBranchLine); + $aBranch = explode("/", $aTmp[1]); $sBranch = array_pop($aBranch); - + $sRevision = $aTmp[0]; + // skip dereferences // http://stackoverflow.com/questions/15472107/when-listing-git-ls-remote-why-theres-after-the-tag-name - if (!preg_match('/\^\{\}$/', $sBranch)){ + if (!preg_match('/\^\{\}$/', $sBranch)) { $sType = array_pop($aBranch); - if ($sType == "heads") { - $sBranch = "origin/" . $sBranch; - } + $sName = ($sType == "heads") ? "origin/" . $sBranch : $sBranch; + $sBranchKey = $sName; - $aReturn[] = array( + $sMessage = $this->getCommitmessageByBranch($sName, $sRevision); + $aReturn[$sBranchKey] = array( // 'debug'=> $aTmp, - 'revision' => $aTmp[0], - 'name' => $sBranch, + 'revision' => $sRevision, + 'name' => $sName, 'label' => $sType . ': ' . $sBranch, - 'type' => $sType + 'type' => $sType, + 'message' => $sMessage ); } } - // echo "DEBUG: refresh cache for branches and tags <br>"; - $oCache->write($aReturn, $iTtl); + $this->_aRemoteBranches = $aReturn; + $oCache->write($aReturn, $iTtl); } } else { - // echo "DEBUG: using cache for branches and tags <br>"; - $aReturn=$oCache->read(); + $this->_aRemoteBranches = $oCache->read(); } - $this->_aRemoteBranches = $aReturn; - return $aReturn; + return $this->_aRemoteBranches; } /** @@ -229,27 +253,70 @@ class vcs implements iVcs { */ public function getRemoteBranches() { if (!$this->_aRemoteBranches) { - $this->_aRemoteBranches = $this->_fetchRemoteBranches(); + $this->_fetchRemoteBranches(); } return $this->_aRemoteBranches; } /** - * get current revision and log message from remote repository + * get current revision and commit message from remote repository * @see $this::getRevision * @return array */ public function getRepoRevision() { - return $this->getRevision(false); + $sMessage = $this->getCommitmessageByBranch(); + if ($sMessage) { + $aReturn = array( + 'branch' => $this->_sCurrentBranch, + 'revision' => $this->_aRemoteBranches[$this->_sCurrentBranch]['revision'], + 'message' => $sMessage, + ); + } else { + $aReturn = $this->getRevision(false); + } + return $aReturn; } /** - * get current revision and log message from an existing directory or a + * get a commit message of a given branch + * @param string $sBranch name of a branch + * @param string $sVerifyRevision optional: verify if this revision is the newsest + * @return string + */ + public function getCommitmessageByBranch($sBranch = false, $sVerifyRevision = false) { + if (!$sBranch) { + $sBranch = $this->_sCurrentBranch; + } + // try to get infos from the cache + if ( + (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 + return $this->_aRemoteBranches[$sBranch]['message']; + } + // ok, then I need to read it + if ($this->_sCurrentBranch != $sBranch) { + $sSaveBranch = $this->_sCurrentBranch; + $this->setCurrentBranch($sBranch); + $a = $this->getRevision(false); + $this->setCurrentBranch($sSaveBranch); + } else { + $a = $this->getRevision(false); + } + 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 * ); @@ -258,68 +325,80 @@ class vcs implements iVcs { * array( * "error" => $sErrormessage, * ); + * @param string $sWorkDir optional: local directory with initialized git repo * @return array */ public function getRevision($sWorkDir = false) { $aReturn = array(); - $sGitCmd = 'export GIT_SSH="' . $this->_sWrapper . '" ; export PKEY="' . $this->_sKeyfile . '" ; '; if (!$this->getUrl()) { return false; } - - $iTtl=300; // cache for 5 min - require_once 'cache.class.php'; - $oCache=new AhCache($this->_getNameOfCacheModule(), "Revision-".$this->_sCurrentBranch ."-". $sWorkDir); - - if ($oCache->isExpired()){ - if ($sWorkDir) { - $sGitCmd.='cd "' . $sWorkDir . '" && '; + $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 { - if (!file_exists($this->_sTempDir . ".git")) { - $sGitCmd.='mkdir "' . $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 ; ' - . 'git fetch --update-head-ok --depth 1 2>&1 ' - . ') && '; + $sGitCmd.='cd "' . $this->_sTempDir . '" && '; } - $sGitCmd.='git log -1 "' . $this->_sCurrentBranch . '" 2>&1 ; '; - $sLoginfo = shell_exec($sGitCmd); + // 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 + . ') && '; + } - $sRevision = false; - if (preg_match('#commit\ (.*)#', $sLoginfo, $aRev)) { - $sRevision = $aRev[1]; - } - - - if ($sRevision) { - $aReturn = array( - "branch" => $this->_sCurrentBranch, - "revision" => $sRevision, - "message" => $sLoginfo - ); - // echo "DEBUG: refresh cache for git revision<br>"; - $oCache->write($aReturn, $iTtl); - } else { - if (!$sLoginfo) { - $sLoginfo = $sGitCmd; - } - // echo "DEBUG: error on reading git revision<br>"; - $aReturn = array( - "error" => '<pre>' . $sLoginfo . '<hr>' . $sGitCmd . '</pre>' - ); - } + $sGitCmd.='git log -1 "' . $this->_sCurrentBranch . '" 2>&1 ; '; // 0.0 s + $sLoginfo = shell_exec($sGitCmd); + /* + * + * 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; + 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 + ); } else { - // echo "DEBUG: use cache for git revision<br>"; - $aReturn=$oCache->read(); + if (!$sLoginfo) { + $sLoginfo = $sGitCmd; + } + // echo "DEBUG: error on reading git revision<br>"; + $aReturn = array( + "error" => '<pre>' . $sLoginfo . '<hr>' . $sGitCmd . '</pre>' + ); } return $aReturn; }