. * * * ************************************************************************ * * GITManagedWebpage.class.php * * Functions to manage a Website using GIT Version Control * */ class GITManagedWebpage { const ERROR_CRITICAL = 1; const SESSION_PREFIX = "GITManagedWebpage_"; private $giturl; private $workdir, $localdir; private $ready = false; private $loopedcall = false; private $config = null; private $config_changed = true; private $activeSession = null; public function __construct($giturl, $workdir = null, $localdir = null) { if(session_status() != PHP_SESSION_ACTIVE) { session_start(); } if(defined("GITMANAGED_EXECUTED")) { $this->loopedcall = true; return; } if($workdir === null) { $workdir = dirname(__FILE__); if(substr($workdir, -1) != "/") $workdir .= "/"; $workdir .= ".gitmanaged/"; } else if(substr($workdir, -1) != "/") $workdir .= "/"; if($localdir === null) { $localdir = dirname(__FILE__); if(substr($localdir, -1) != "/") $localdir .= "/"; } else if(substr($localdir, -1) != "/") $localdir .= "/"; $this->giturl = $giturl; $this->workdir = $workdir; $this->localdir = $localdir; if(!file_exists($this->workdir) || !is_dir($this->workdir)) { if(file_exists($this->workdir) && !is_dir($this->workdir)) { $this->error(self::ERROR_CRITICAL, "local workdir (".htmlspecialchars($this->workdir).") is not a directory."); return; } $this->setupWorkdir(); } $this->ready = true; } /* private function gitcmd(...) * Execute a git command and return output */ private function gitcmd() { $args = func_get_args(); $argstr = ""; foreach($args as $arg) { if(is_array($arg)) { foreach($arg as $subarg) { $argstr .= " ".escapeshellarg($subarg); } } else $argstr .= " ".escapeshellarg($arg); } $gitcmd = 'git '.escapeshellarg('--git-dir='.$this->workdir.'repository/.git').' '.escapeshellarg('--work-tree='.$this->workdir.'repository').$argstr; $output = shell_exec($gitcmd); return $output; } /* private function setConfig($name, $value) * store a option in the configuration */ private function setConfig($name, $value) { $this->config[strtolower($name)] = $value; $this->config_changed = true; } private function checkConfigIntegrity() { foreach($this->config as $key => $value) { if(substr($key, 0, strlen('branch_')) == 'branch_') { if(!file_exists($this->workdir.$key)) { unset($this->config['key']); } } } } /* private function getConfig($name) * get an option from the configuration */ private function getConfig($name) { if($this->config == null) { if(!$this->ready) return null; if(file_exists($this->workdir."config.txt")) { $config_txt = @file_get_contents($this->workdir."config.txt"); $this->config = unserialize($config_txt); $this->checkConfigIntegrity(); } else { $this->config = array(); return null; } } if(array_key_exists(strtolower($name), $this->config)) return $this->config[strtolower($name)]; else return null; } private function saveConfig() { if($this->config_changed && $this->ready) { $fp = fopen($this->workdir."config.txt", "w"); fwrite($fp, serialize($this->config)); fclose($fp); } } /* private function setupWorkdir() * Setup local GITManagedWebpage Work directory with git repository */ private function setupWorkdir() { // check requirements $git_exec = shell_exec('which git'); if(!preg_match('#git#', $git_exec)) { $this->error(self::ERROR_CRITICAL, "git not installed locally."); return; } mkdir($this->workdir); mkdir($this->workdir.'repository'); shell_exec('git clone '.escapeshellarg($this->giturl).' '.escapeshellarg($this->workdir.'repository')); $gitok = $this->gitcmd("status"); if(preg_match("#Not a git repository#", $gitok)) { rmdir($this->workdir.'repository'); rmdir($this->workdir); $this->error(self::ERROR_CRITICAL, "error cloning git repository."); return; } $this->ready = true; $default_branch = str_replace(array("\r", "\n"), array("", ""), $this->gitcmd("rev-parse", "--abbrev-ref", "HEAD")); $this->setConfig("defaultbranch", $default_branch); $this->saveConfig(); } private function getActiveBranch() { if($this->activeSession) return $this->activeSession; else if(isset($_SESSION[self::SESSION_PREFIX.'branch'])) return $_SESSION[self::SESSION_PREFIX.'branch']; else return $this->getConfig("defaultbranch"); } private function setActiveBranch($branch, $remember) { $this->activeSession = $branch; if($remember) $_SESSION[self::SESSION_PREFIX.'branch'] = $branch; } private function getLocalUntrackedFiles() { $default_branch = $this->getConfig("defaultbranch"); $tracked_files = $this->gitcmd("ls-tree", $default_branch, "--full-name", "--name-only"); $local_files = shell_exec("find ".escapeshellarg($this->localdir)); $untracked_files = array(); $tracked_files = explode("\n", str_replace(array("\r"), array(""), $tracked_files)); $local_files = explode("\n", str_replace(array("\r"), array(""), $local_files)); foreach($local_files as $local_file) { if(!$local_file) continue; if($strip_local || (($strip_local = strlen($this->localdir)) && substr($local_file, 0, $strip_local) == $this->localdir)) { $local_file = substr($local_file, $strip_local); } $tracked = false; foreach($tracked_files as $tracked_file) { if($tracked_file == $local_file) { $tracked = true; break; } } if(!$tracked) { $untracked_files[] = $local_file; } } return $untracked_files; } private function branchExists($branch) { //check if branch exists $gitret = $this->gitcmd("rev-list", "--max-count=1", $branch); if(!preg_match("#([a-z0-9]{40})#", $gitret, $match)) return false; else return $match[1]; } private function localBranchPath($branch, $create = false) { $default_branch = $this->getConfig("defaultbranch"); if($branch == $default_branch) $dir = $this->localdir; else $dir = $this->workdir.'branch_'.str_replace(array('/'), array('_'), $branch).'/'; if(file_exists($dir)) return $dir; else if($create) { mkdir($dir); return $dir; } else return false; } private function updateBranch($branch, $path, $force = false) { if(substr($path, -1) != '/') $path .= '/'; $current_branch = str_replace(array("\r", "\n"), array("", ""), $this->gitcmd("rev-parse", "--abbrev-ref", "HEAD")); if($current_branch != $branch) $this->gitcmd("checkout", $branch); $this->gitcmd("pull"); $gitret = $this->gitcmd("rev-list", "--max-count=1", $branch); preg_match("#([a-z0-9]{40})#", $gitret, $match); $newest_version = $match[1]; $deleted_files = array(); if(($current_version = $this->getConfig('version_'.$branch))) { if($current_version == $newest_version && !$force) return; else { $override_all = true; $delfiles = $this->gitcmd("diff", "--diff-filter=D", "--name-only", $current_version, $newest_version); $delfiles = explode("\n", str_replace(array("\r"), array(""), $delfiles)); foreach($delfiles as $file) { if(!$file) continue; $deleted_files[] = $file; } } } else $override_all = true; if($override_all) { $rsync_present = preg_match("#rsync#", `which rsync`); if($rsync_present) shell_exec('rsync -avz --exclude ".git" '.escapeshellarg($this->workdir."repository/").' '.escapeshellarg($path)); else shell_exec('tar -c --exclude ".git" -C '.escapeshellarg($this->workdir."repository").' . | tar -x -C '.escapeshellarg($path)); // remove deleted files foreach($deleted_files as $file) { unlink($path.$file); } } $this->setConfig('version_'.$branch, $newest_version); $this->saveConfig(); } /* public function update() * Pulls latest commit of active branch and overwrites files in branch folder */ public function update() { if($this->loopedcall) return; $active_branch = $this->getActiveBranch(); if(!$this->branchExists($active_branch)) return false; $dir = $this->localBranchPath($active_branch, true); $this->updateBranch($active_branch, $dir); } public function setBranch($branch, $remember = false) { if($this->loopedcall) return; if(!$this->branchExists($branch)) { if(!$this->branchExists('origin/'.$branch)) return false; } $this->setActiveBranch($branch, $remember); if(!$this->localBranchPath($branch)) { $dir = $this->localBranchPath($branch, true); $this->updateBranch($branch, $dir, true); } } public function getExecFile($file = null) { if($this->loopedcall) return; define("GITMANAGED_EXECUTED", true); if(!$file) $file = $_SERVER['PHP_SELF']; if($file[0] == '/') $file = substr($file, 1); $default_branch = $this->getConfig("defaultbranch"); $active_branch = $this->getActiveBranch(); if($active_branch != $default_branch) { if(!($dir = $this->localBranchPath($branch))) { $dir = $this->localBranchPath($active_branch, true); $this->updateBranch($active_branch, $dir, true); } chdir($dir); return $dir.$file; } else { return $file; } } } ?>