Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issues/47 multiple repos #74

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
104 changes: 80 additions & 24 deletions action/editcommit.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,74 @@ public function register(Doku_Event_Handler $controller) {
$controller->register_hook('DOKUWIKI_DONE', 'AFTER', $this, 'handle_periodic_pull');
}

private function initRepo() {
//get path to the repo root (by default DokuWiki's savedir)
$repoPath = GitBackedUtil::getEffectivePath($this->getConf('repoPath'));
/**
* Create a GitRepo class instance according to this plugins config.
* If auto determination of git rpos is configured, this method will return null,
* if there is no git repo found.
*
* @access private
* @param string path to the file or directory to be commited (required for auto determination only)
* @return GitRepo instance or null if there is no repo related to fileOrDirPath
*/
private function initRepo($fileOrDirPath="") {
global $conf;

//set the path to the git binary
$gitPath = trim($this->getConf('gitPath'));
if ($gitPath !== '') {
Git::set_bin($gitPath);
}
//init the repo and create a new one if it is not present
io_mkdir_p($repoPath);
$repo = new GitRepo($repoPath, $this, true, true);
//set git working directory (by default DokuWiki's savedir)
$repoWorkDir = $this->getConf('repoWorkDir');
if (!empty($repoWorkDir)) {
$repoWorkDir = GitBackedUtil::getEffectivePath($repoWorkDir);
}

$configuredRepoPath = trim($this->getConf('repoPath'));
$configuredRepoWorkDir = trim($this->getConf('repoWorkDir'));
if (!empty($configuredRepoPath)) {
$configuredRepoPath = GitBackedUtil::getEffectivePath($configuredRepoPath);
}
if (!empty($configuredRepoWorkDir)) {
$configuredRepoWorkDir = GitBackedUtil::getEffectivePath($configuredRepoWorkDir);
}
$isAutoDetermineRepos = $this->getConf('autoDetermineRepos');
if ($isAutoDetermineRepos) {
if (empty($fileOrDirPath)) {
return null;
}
$repoPath = is_dir($fileOrDirPath) ? $fileOrDirPath : dirname($fileOrDirPath);
$repo = new GitRepo($repoPath, $this, false, false);
$repoPath = $repo->get_repo_path();
if (empty($repoPath)) {
return null;
}
// Validate that the git repoPath found is within or below the DokuWiki 'savedir' configured:
if (strpos(realpath($repoPath), realpath($conf['savedir'])) === false) {
//dbglog("GitBacked - WARNING: repoPath=".$repoPath." is above the configured savedir=".realpath($conf['savedir'])." => this git repo will be ignored!");
return null;
}
$repoWorkDir = '';
if (!empty($configuredRepoPath)) {
// For backward compatibility to legacy configuration:
// We will use the configured workDir, in case we have determined
// the repoPath configured.
if (realpath($configuredRepoPath) === realpath($repoPath)) {
$repoWorkDir = $configuredRepoWorkDir;
//dbglog("GitBacked - INFO: repoPath=".$repoPath." is the one explicitly configured => we use the configured workDir=[".$repoWorkDir."]");
}
}
//dbglog("GitBacked - AUTO_DETERMINE_USE_CASE: repoPath=".$repoPath);
//dbglog("GitBacked - AUTO_DETERMINE_USE_CASE: repoWorkDir=".$repoWorkDir);
} else {
//get path to the repo root from configuration (by default DokuWiki's savedir)
$repoPath = $configuredRepoPath;
//init the repo and create a new one if it is not present
io_mkdir_p($repoPath);
$repo = new GitRepo($repoPath, $this, true, true);
//set git working directory from configuration (by default DokuWiki's savedir)
$repoWorkDir = $configuredRepoWorkDir;
//dbglog("GitBacked - CONFIG_USE_CASE: configured repoPath=".$repoPath);
//dbglog("GitBacked - CONFIG_USE_CASE: configured repoWorkDir=".$repoWorkDir);
}

Git::set_bin(empty($repoWorkDir) ? Git::get_bin() : Git::get_bin().' --work-tree '.escapeshellarg($repoWorkDir));

$params = str_replace(
array('%mail%','%user%'),
array($this->getAuthorMail(),$this->getAuthor()),
Expand Down Expand Up @@ -73,8 +125,10 @@ private function isIgnored($filePath) {
private function commitFile($filePath,$message) {
if (!$this->isIgnored($filePath)) {
try {
$repo = $this->initRepo();

$repo = $this->initRepo($filePath);
if (is_null($repo)) {
return;
}
//add the changed file and set the commit message
$repo->add($filePath);
$repo->commit($message);
Expand Down Expand Up @@ -115,17 +169,19 @@ public function handle_periodic_pull(Doku_Event &$event, $param) {

//if it is time to run a pull request
if ($lastPull+$timeToWait < $now) {
try {
$repo = $this->initRepo();

//execute the pull request
$repo->pull('origin',$repo->active_branch());
} catch (Exception $e) {
if (!$this->isNotifyByEmailOnGitCommandError()) {
throw new Exception('Git command failed to perform periodic pull: '.$e->getMessage(), 2, $e);
}
return;
}
try {
$repo = $this->initRepo();
if (is_null($repo)) {
return;
}
//execute the pull request
$repo->pull('origin',$repo->active_branch());
} catch (Exception $e) {
if (!$this->isNotifyByEmailOnGitCommandError()) {
throw new Exception('Git command failed to perform periodic pull: '.$e->getMessage(), 2, $e);
}
return;
}

//save the current time to the file to track the last pull execution
file_put_contents($lastPullFile,serialize(time()));
Expand Down
3 changes: 2 additions & 1 deletion conf/default.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
$conf['commitPageMsgDel'] = 'Wiki page %page% deleted with reason [%summary%] by %user%';
$conf['commitMediaMsg'] = 'Wiki media %media% uploaded by %user%';
$conf['commitMediaMsgDel'] = 'Wiki media %media% deleted by %user%';
$conf['repoPath'] = $GLOBALS['conf']['savedir'];
$conf['autoDetermineRepos'] = 1;
$conf['repoPath'] = ''; //$GLOBALS['conf']['savedir']
$conf['repoWorkDir'] = '';
$conf['gitPath'] = '';
$conf['addParams'] = '-c user.name="%user%" -c user.email="<%mail%>"';
Expand Down
1 change: 1 addition & 0 deletions conf/metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
$meta['commitPageMsgDel'] = array('string');
$meta['commitMediaMsg'] = array('string');
$meta['commitMediaMsgDel'] = array('string');
$meta['autoDetermineRepos'] = array('onoff');
$meta['repoPath'] = array('string');
$meta['repoWorkDir'] = array('string');
$meta['gitPath'] = array('string');
Expand Down
13 changes: 7 additions & 6 deletions lang/de/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
$lang['pushAfterCommit'] = 'Push des aktiven Branch zum remote origin nach jedem commit';
$lang['periodicPull'] = 'Pull des remote git Repositories alle "periodicMinutes", getriggert von einem http Page Request';
$lang['periodicMinutes'] = 'Zeitraum (in Minuten) zwischen den periodischen pull requests';
$lang['commitPageMsg'] = 'Commit Kommentar für Seitenänderungen (%user%,%summary%,%page% werden durch die tatsächlichen Werte ersetzt)';
$lang['commitPageMsgDel'] = 'Commit Kommentar für gelöschte Seiten (%user%,%summary%,%page% werden durch die tatsächlichen Werte ersetzt)';
$lang['commitMediaMsg'] = 'Commit Kommentar for media Dateien (%user%,%media% werden durch die tatsächlichen Werte ersetzt)';
$lang['commitMediaMsgDel'] = 'Commit Kommentar für gelöschte media Dateien (%user%,%media% werden durch die tatsächlichen Werte ersetzt)';
$lang['repoPath'] = 'Pfad des git repo (z.B. das <code>savedir</code> '.$GLOBALS['conf']['savedir'].')';
$lang['repoWorkDir'] = 'Pfad des git working tree. Dieser muss die "pages" and "media" Verzeichnisse enthalten (z.B. das <code>savedir</code> '.$GLOBALS['conf']['savedir'].')';
$lang['commitPageMsg'] = 'Commit Kommentar für Seitenänderungen (%user%,%summary%,%page% werden durch die tatsächlichen Werte ersetzt)';
$lang['commitPageMsgDel'] = 'Commit Kommentar für gelöschte Seiten (%user%,%summary%,%page% werden durch die tatsächlichen Werte ersetzt)';
$lang['commitMediaMsg'] = 'Commit Kommentar for media Dateien (%user%,%media% werden durch die tatsächlichen Werte ersetzt)';
$lang['commitMediaMsgDel'] = 'Commit Kommentar für gelöschte media Dateien (%user%,%media% werden durch die tatsächlichen Werte ersetzt)';
$lang['autoDetermineRepos'] = 'Findet das nächste git Repo oberhalb des Pfades der geänderten Datei. Wenn gesetzt, dann werden mehrere Repos z.B. in Namespaces oder separate Repos für Pages und Media generisch unterstützt.';
$lang['repoPath'] = '<b>Veraltete Konfiguration:</b> Pfad des git Repo (z.B. das <code>savedir</code> <code>$GLOBALS[\'conf\'][\'savedir\']</code>)<br><b>Hinweis:</b> Diese Einstellung ist nur für Rückwärtskompatibilität einer vorhandenen Konfiguration gedacht. Wenn <code>autoDetermineRepos</code> aktiviert ist, dann sollte diese Einstellung für neue Installationen nicht gesetzt werden.';
$lang['repoWorkDir'] = '<b>Veraltete Konfiguration:</b> Pfad des git working tree. Dieser muss die "pages" and "media" Verzeichnisse enthalten (z.B. das <code>savedir</code> <code>$GLOBALS[\'conf\'][\'savedir\']</code>)<br><b>Hinweis:</b> Diese Einstellung wird nur berücksichtigt, wenn <code>repoPath</code> gesetzt ist. In diesem Fall wird es nur für das Repo in <code>repoPath</code> angewandt.';
$lang['gitPath'] = 'Pfad zum git binary (Wenn leer, dann wird der Standard "/usr/bin/git" verwendet)';
$lang['addParams'] = 'Zusätzliche git Parameter (diese werden dem git Kommando zugefügt) (%user% und %mail% werden durch die tatsächlichen Werte ersetzt)';
$lang['ignorePaths'] = 'Pfade/Dateien die ignoriert werden und nicht von git archiviert werden sollen (durch Kommata getrennt)';
Expand Down
11 changes: 6 additions & 5 deletions lang/en/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
$lang['pushAfterCommit'] = 'Push active branch to remote origin after every commit';
$lang['periodicPull'] = 'Pull the remote git repository every "periodicMinutes" triggered by a http page request';
$lang['periodicMinutes'] = 'Timespan (in minutes) between periodic pull requests';
$lang['commitPageMsg'] = 'Commit message for page edits (%user%,%summary%,%page% are replaced by the corresponding values)';
$lang['commitPageMsgDel'] = 'Commit message for deleted pages (%user%,%summary%,%page% are replaced by the corresponding values)';
$lang['commitPageMsg'] = 'Commit message for page edits (%user%,%summary%,%page% are replaced by the corresponding values)';
$lang['commitPageMsgDel'] = 'Commit message for deleted pages (%user%,%summary%,%page% are replaced by the corresponding values)';
$lang['commitMediaMsg'] = 'Commit message for media files (%user%,%media% are replaced by the corresponding values)';
$lang['commitMediaMsgDel'] = 'Commit message for deleted media files (%user%,%media% are replaced by the corresponding values)';
$lang['repoPath'] = 'Path of the git repo(s) (e.g. the savedir '.$GLOBALS['conf']['savedir'].')';
$lang['repoWorkDir'] = 'Path of the git working tree, must contain "pages" and "media" directories (e.g. the savedir '.$GLOBALS['conf']['savedir'].')';
$lang['commitMediaMsgDel'] = 'Commit message for deleted media files (%user%,%media% are replaced by the corresponding values)';
$lang['autoDetermineRepos'] = 'Auto determine the next git repo path upwards from the path of the file to commit. If enabled, then multiple repos e.g. within namespaces or separate repos for pages and media are supported in a generic way.';
$lang['repoPath'] = '<b>Legacy config:</b> Path of the git repo (e.g. the <code>savedir</code> <code>$GLOBALS[\'conf\'][\'savedir\']</code>)<br><b>NOTE:</b> This config is for backward compatibility of an existing configuration only. If <code>autoDetermineRepos</code> is on, then this config should not be set for new installations.';
$lang['repoWorkDir'] = '<b>Legacy config:</b> Path of the git working tree, must contain "pages" and "media" directories (e.g. the <code>savedir</code> <code>$GLOBALS[\'conf\'][\'savedir\']</code>)<br><b>NOTE:</b> This config is considered only, if <code>repoPath</code> is set. In this case it does apply for the repo in <code>repoPath</code> only.';
$lang['gitPath'] = 'Path to the git binary (if empty, the default "/usr/bin/git" will be used)';
$lang['addParams'] = 'Additional git parameters (added to the git execution command) (%user% and %mail% are replaced by the corresponding values)';
$lang['ignorePaths'] = 'Paths/files which are ignored and not added to git (comma-separated)';
Expand Down
70 changes: 66 additions & 4 deletions lib/Git.php
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,12 @@ public function set_repo_path($repo_path, $create_new = false, $_init = true) {
if ($new_path = realpath($repo_path)) {
$repo_path = $new_path;
if (is_dir($repo_path)) {
$next_parent_repo_path = $this->absolute_git_dir($repo_path);
if (!empty($next_parent_repo_path)) {
$this->repo_path = $next_parent_repo_path;
$this->bare = false;
// Is this a work tree?
if (file_exists($repo_path."/.git") && is_dir($repo_path."/.git")) {
} else if (file_exists($repo_path."/.git") && is_dir($repo_path."/.git")) {
$this->repo_path = $repo_path;
$this->bare = false;
// Is this a bare repo?
Expand All @@ -229,6 +233,10 @@ public function set_repo_path($repo_path, $create_new = false, $_init = true) {
if ($_init) {
$this->run('init');
}
} if (!$_init) {
// If we do not have to init the repo, we just reflect that there is no repo path yet.
// This may be the case for auto determining repos, if there is no repo related to the current resource going to be commited.
$this->repo_path = '';
} else {
throw new Exception($this->handle_repo_path_error($repo_path, '"'.$repo_path.'" is not a git repository'));
}
Expand All @@ -252,6 +260,16 @@ public function set_repo_path($repo_path, $create_new = false, $_init = true) {
}
}

/**
* Get the path to the repo directory
*
* @access public
* @return string
*/
public function get_repo_path() {
return $this->repo_path;
}

/**
* Get the path to the git repo directory (eg. the ".git" directory)
*
Expand Down Expand Up @@ -286,6 +304,48 @@ public function test_git() {
return ($status != 127);
}

/**
* Determine closest parent git repository for a given path as absolute PHP realpath().
*
* @access public
* @return string the next parent git repo root dir as absolute PHP realpath() or empty string, if no parent repo found
*/
public function absolute_git_dir($path) {
$descriptorspec = array(
1 => array('pipe', 'w'),
2 => array('pipe', 'w'),
);
$pipes = array();
// Using --git-dir rather than --absolute-git-dir for a wider git versions compatibility
//$command = Git::get_bin()." rev-parse --absolute-git-dir";
$command = Git::get_bin()." rev-parse --git-dir";
//dbglog("GitBacked - Command: ".$command);
$resource = proc_open($command, $descriptorspec, $pipes, $path);
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
foreach ($pipes as $pipe) {
fclose($pipe);
}

$status = trim(proc_close($resource));
if ($status == 0) {
$repo_git_dir = trim($stdout);
//dbglog("GitBacked - $command: '".$repo_git_dir."'");
if (!empty($repo_git_dir)) {
if (strcmp($repo_git_dir, ".git") === 0) {
// convert to absolute path based on this command execution directory
$repo_git_dir = $path.'/'.$repo_git_dir;
}
$repo_path = dirname(realpath($repo_git_dir));
//dbglog('GitBacked - $repo_path: '.$repo_path);
if (file_exists($repo_path."/.git") && is_dir($repo_path."/.git")) {
return $repo_path;
}
}
}
return '';
}

/**
* Run a command in the git repository
*
Expand All @@ -296,6 +356,10 @@ public function test_git() {
* @return string or null in case of an error
*/
protected function run_command($command) {
//dbglog("Git->run_command: repo_path=[".$this->repo_path."])");
if (empty($this->repo_path)) {
throw new Exception($this->handle_repo_path_error($this->repo_path, "Failure on GitRepo->run_command(): Git command must not be run for an empty repo path"));
}
//dbglog("Git->run_command(command=[".$command."])");
$descriptorspec = array(
1 => array('pipe', 'w'),
Expand All @@ -320,9 +384,7 @@ protected function run_command($command) {
} else {
$env = array_merge($_ENV, $this->envopts);
}
$cwd = $this->repo_path;
//dbglog("GitBacked - cwd: [".$cwd."]");
$resource = proc_open($command, $descriptorspec, $pipes, $cwd, $env);
$resource = proc_open($command, $descriptorspec, $pipes, $this->repo_path, $env);

$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
Expand Down