2 /*************************** GITManagedWebpage **************************
3 * Copyright (C) 2013 Philipp Kreil (pk910) *
5 * This program is free software: you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation, either version 3 of the License, or *
8 * (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
18 ************************************************************************
20 * GITManagedWebpage.class.php
22 * Functions to manage a Website using GIT Version Control
26 class GITManagedWebpage {
27 const ERROR_CRITICAL = 1;
28 const SESSION_PREFIX = "GITManagedWebpage_";
31 private $workdir, $localdir;
32 private $ready = false;
33 private $loopedcall = false;
34 private $config = null;
35 private $config_changed = true;
36 private $activeSession = null;
38 public function __construct($giturl, $workdir = null, $localdir = null) {
39 if(session_status() != PHP_SESSION_ACTIVE) {
43 if(defined("GITMANAGED_EXECUTED")) {
44 $this->loopedcall = true;
48 if($workdir === null) {
49 $workdir = dirname(__FILE__);
50 if(substr($workdir, -1) != "/")
52 $workdir .= ".gitmanaged/";
54 else if(substr($workdir, -1) != "/")
57 if($localdir === null) {
58 $localdir = dirname(__FILE__);
59 if(substr($localdir, -1) != "/")
62 else if(substr($localdir, -1) != "/")
65 $this->giturl = $giturl;
66 $this->workdir = $workdir;
67 $this->localdir = $localdir;
69 if(!file_exists($this->workdir) || !is_dir($this->workdir)) {
70 if(file_exists($this->workdir) && !is_dir($this->workdir)) {
71 $this->error(self::ERROR_CRITICAL, "local workdir (".htmlspecialchars($this->workdir).") is not a directory.");
74 $this->setupWorkdir();
79 /* private function gitcmd(...)
80 * Execute a git command and return output
82 private function gitcmd() {
83 $args = func_get_args();
85 foreach($args as $arg) {
87 foreach($arg as $subarg) {
88 $argstr .= " ".escapeshellarg($subarg);
91 $argstr .= " ".escapeshellarg($arg);
93 $gitcmd = 'git '.escapeshellarg('--git-dir='.$this->workdir.'repository/.git').' '.escapeshellarg('--work-tree='.$this->workdir.'repository').$argstr;
94 $output = shell_exec($gitcmd);
98 /* private function setConfig($name, $value)
99 * store a option in the configuration
101 private function setConfig($name, $value) {
102 $this->config[strtolower($name)] = $value;
103 $this->config_changed = true;
106 private function checkConfigIntegrity() {
107 foreach($this->config as $key => $value) {
108 if(substr($key, 0, strlen('branch_')) == 'branch_') {
109 if(!file_exists($this->workdir.$key)) {
110 unset($this->config['key']);
116 /* private function getConfig($name)
117 * get an option from the configuration
119 private function getConfig($name) {
120 if($this->config == null) {
123 if(file_exists($this->workdir."config.txt")) {
124 $config_txt = @file_get_contents($this->workdir."config.txt");
125 $this->config = unserialize($config_txt);
126 $this->checkConfigIntegrity();
128 $this->config = array();
132 if(array_key_exists(strtolower($name), $this->config))
133 return $this->config[strtolower($name)];
138 private function saveConfig() {
139 if($this->config_changed && $this->ready) {
140 $fp = fopen($this->workdir."config.txt", "w");
141 fwrite($fp, serialize($this->config));
146 /* private function setupWorkdir()
147 * Setup local GITManagedWebpage Work directory with git repository
149 private function setupWorkdir() {
150 // check requirements
151 $git_exec = shell_exec('which git');
152 if(!preg_match('#git#', $git_exec)) {
153 $this->error(self::ERROR_CRITICAL, "git not installed locally.");
157 mkdir($this->workdir);
158 mkdir($this->workdir.'repository');
159 shell_exec('git clone '.escapeshellarg($this->giturl).' '.escapeshellarg($this->workdir.'repository'));
160 $gitok = $this->gitcmd("status");
161 if(preg_match("#Not a git repository#", $gitok)) {
162 rmdir($this->workdir.'repository');
163 rmdir($this->workdir);
164 $this->error(self::ERROR_CRITICAL, "error cloning git repository.");
168 $fp = fopen($this->workdir.'.htaccess');
177 $default_branch = str_replace(array("\r", "\n"), array("", ""), $this->gitcmd("rev-parse", "--abbrev-ref", "HEAD"));
178 $this->setConfig("defaultbranch", $default_branch);
182 private function getActiveBranch() {
183 if($this->activeSession)
184 return $this->activeSession;
185 else if(isset($_SESSION[self::SESSION_PREFIX.'branch']))
186 return $_SESSION[self::SESSION_PREFIX.'branch'];
188 return $this->getConfig("defaultbranch");
191 private function setActiveBranch($branch, $remember) {
192 $this->activeSession = $branch;
194 $_SESSION[self::SESSION_PREFIX.'branch'] = $branch;
197 private function getLocalUntrackedFiles() {
198 $default_branch = $this->getConfig("defaultbranch");
199 $tracked_files = $this->gitcmd("ls-tree", $default_branch, "--full-name", "--name-only");
200 $local_files = shell_exec("find ".escapeshellarg($this->localdir));
201 $untracked_files = array();
203 $tracked_files = explode("\n", str_replace(array("\r"), array(""), $tracked_files));
204 $local_files = explode("\n", str_replace(array("\r"), array(""), $local_files));
206 foreach($local_files as $local_file) {
209 if($strip_local || (($strip_local = strlen($this->localdir)) && substr($local_file, 0, $strip_local) == $this->localdir)) {
210 $local_file = substr($local_file, $strip_local);
213 foreach($tracked_files as $tracked_file) {
214 if($tracked_file == $local_file) {
220 $untracked_files[] = $local_file;
223 return $untracked_files;
226 private function branchExists($branch) {
227 //check if branch exists
228 $gitret = $this->gitcmd("rev-list", "--max-count=1", $branch);
229 if(!preg_match("#([a-z0-9]{40})#", $gitret, $match))
235 private function localBranchPath($branch, $create = false) {
236 $default_branch = $this->getConfig("defaultbranch");
237 if($branch == $default_branch)
238 $dir = $this->localdir;
240 $dir = $this->workdir.'branch_'.str_replace(array('/'), array('_'), $branch).'/';
241 if(file_exists($dir))
250 private function updateBranch($branch, $path, $force = false) {
251 if(substr($path, -1) != '/')
253 $current_branch = str_replace(array("\r", "\n"), array("", ""), $this->gitcmd("rev-parse", "--abbrev-ref", "HEAD"));
254 $this->gitcmd("fetch");
255 $this->gitcmd("reset", "--hard", "origin/".$branch);
256 $gitret = $this->gitcmd("rev-list", "--max-count=1", $branch);
257 preg_match("#([a-z0-9]{40})#", $gitret, $match);
258 $newest_version = $match[1];
260 $deleted_files = array();
261 if(($current_version = $this->getConfig('version_'.$branch))) {
262 if($current_version == $newest_version && !$force)
265 $override_all = true;
266 $delfiles = $this->gitcmd("diff", "--diff-filter=D", "--name-only", $current_version, $newest_version);
267 $delfiles = explode("\n", str_replace(array("\r"), array(""), $delfiles));
268 foreach($delfiles as $file) {
271 $deleted_files[] = $file;
275 $override_all = true;
277 $rsync_present = preg_match("#rsync#", `which rsync`);
279 shell_exec('rsync -avz --exclude ".git" '.escapeshellarg($this->workdir."repository/").' '.escapeshellarg($path));
281 shell_exec('tar -c --exclude ".git" -C '.escapeshellarg($this->workdir."repository").' . | tar -x -C '.escapeshellarg($path));
283 // remove deleted files
284 foreach($deleted_files as $file) {
288 $this->setConfig('version_'.$branch, $newest_version);
292 /* public function update()
293 * Pulls latest commit of active branch and overwrites files in branch folder
295 public function update() {
296 if($this->loopedcall)
299 $active_branch = $this->getActiveBranch();
301 if(!$this->branchExists($active_branch))
303 $dir = $this->localBranchPath($active_branch, true);
304 $this->updateBranch($active_branch, $dir);
307 public function setBranch($branch, $remember = false) {
308 if($this->loopedcall)
311 if(!$this->branchExists($branch)) {
312 $this->gitcmd("fetch");
313 if(!$this->branchExists('origin/'.$branch))
316 $this->setActiveBranch($branch, $remember);
318 if(!$this->localBranchPath($branch)) {
319 $dir = $this->localBranchPath($branch, true);
320 $this->updateBranch($branch, $dir, true);
324 public function getExecFile($file = null) {
325 if($this->loopedcall)
327 define("GITMANAGED_EXECUTED", true);
330 $file = $_SERVER['PHP_SELF'];
332 $file = substr($file, 1);
334 $default_branch = $this->getConfig("defaultbranch");
335 $active_branch = $this->getActiveBranch();
336 if($active_branch != $default_branch) {
337 if(!($dir = $this->localBranchPath($active_branch))) {
338 $dir = $this->localBranchPath($active_branch, true);
339 $this->updateBranch($active_branch, $dir, true);