From e2bc846b181aad83efb93ee8eed027f354019add Mon Sep 17 00:00:00 2001 From: Markus Hoffrogge Date: Mon, 2 Feb 2026 08:18:50 +0100 Subject: [PATCH 1/2] Re-base branch to current master - squashed commit of the following: commit 6ecab8020e6c988d2b8e782a633e6cf725e679d2 Author: Markus Hoffrogge Date: Mon Feb 2 01:05:54 2026 +0100 Re-base branch to current master - add missing files - align GH actions - align *.md files - align plugin.info.txt commit 52c67e4c24adbe051b656e70fb7268e611c21221 Author: Markus Hoffrogge Date: Mon Feb 2 00:29:24 2026 +0100 Re-base branch to current master commit 99861f05c8ffbfad8c843ecf89e720e53774c0c8 Author: Markus Hoffrogge Date: Mon Feb 2 00:41:40 2026 +0100 Re-base branch to current master - split GitRepo.php from Git.php, no code changes commit 31ed615e20ec19b5a6b771a9ccf0f278afb704ca Author: Markus Hoffrogge Date: Mon Feb 2 00:31:09 2026 +0100 Re-base branch to current master - move lib -> classes, no code changes commit 57bb4536a9041d856e77ec10553e35014aa053d9 Author: Markus Hoffrogge Date: Fri May 6 01:52:59 2022 +0200 Allow using several git repos - improved German description of config settings commit d2a66e79e57619102cf02af868c661303ff7cb19 Author: Markus Hoffrogge Date: Fri May 6 01:22:29 2022 +0200 Allow using several git repos - improved description of config settings - set default value to empty string for $conf['repoPath'] and $conf['repoWorkDir'] - changed position of config setting 'autoDetermineRepos' to be listed before 'repoPath' commit 8e783f47fba6ef0f8b00430c3ae72848c0fbde83 Author: Markus Hoffrogge Date: Fri May 6 01:17:30 2022 +0200 Allow using several git repos - editcommit.php: - improved to be backward compatible with existing single repo path configured installations commit ee6827c43bae9c593de120cbd1841bd1777ef8f5 Author: Markus Hoffrogge Date: Sun Mar 20 17:12:11 2022 +0100 Allow using several git repos - Git.php: - in case of auto determined repos: - use git rev-parse --git-dir option rather than --absolute-git-dir to support a maximum range of git versions - function absolute_git_dir($path): - extended the logic to ensure to return an absolute repo_path in any case commit ff5ae89fd43cf5b52c8c48978623fd6ac319230a Author: Markus Hoffrogge Date: Sat Mar 19 22:34:17 2022 +0100 Allow using several git repos - editcommit.php: - added a restriction not to use auto determined git repos found in directories above the DokuWiki configured $conf['savedir']. commit dbee1e7d3c5eb51cb8aad5d01389b693fc07dfa4 Author: Markus Hoffrogge Date: Fri Feb 11 15:24:25 2022 +0100 Allow using several git repos - editcommit.php, Git.php: - fixed issue for non git repo related paths in case of auto determining repos commit 718bdcd77b5b5c0e53d6d42279704346f5276dc8 Author: Markus Hoffrogge Date: Thu Feb 10 01:23:29 2022 +0100 Allow using several git repos - editcommit.php: - another simplification of the code - made ->initRepo(...) work for both: a file path name as well as a directory path name commit 634ce7901ffc9e10580476651e990ac92840a934 Author: Markus Hoffrogge Date: Tue Feb 8 23:16:30 2022 +0100 Allow using several git repos - editcommit.php: - streamlined the code of initRepo to improve readability commit 5e541014c09005815eea2304ef946a0d9823af11 Author: Markus Hoffrogge Date: Tue Feb 8 20:11:59 2022 +0100 Allow using several git repos - fix for auto determining the next parent repo path: - Git.php: - fixed type declaration of $plugin for null initialization - replaced method is_in_git_repo($path) by absolute_git_dir($path) - added method get_repo_path() - editcommit.php: - clear repoWorkDir in case of auto determined repo_path - add --work-tree option only, if repoWorkDir is not empty Fixes #47 commit 8be0bb0a173c233cb04568f9bec897796c5cd088 Author: Markus Hoffrogge Date: Sun Feb 6 00:47:37 2022 +0100 Allow using several git repos - improvements due to code review: - changed config 'initRepo' -> 'autoDetermineRepos' to leverage self explanation - added missing language description for this config Fixes #47 commit 8574d382ad8b0791732225d796756bb92364765f Author: Markus Hoffrogge Date: Sun Feb 6 00:00:09 2022 +0100 Fix editcommit.php line 56: missing variable assignment commit d3a61cd4459973b3fcaad7c1263c168e76bb2cd3 Author: Olivier Churlaud Date: Thu Jan 13 17:39:18 2022 +0100 Allow using several git repos Fixes #47 --- action/editcommit.php | 84 +++++++++++++++++++++++++++++++++++-------- classes/GitRepo.php | 67 ++++++++++++++++++++++++++++++++-- conf/default.php | 3 +- conf/metadata.php | 1 + lang/de/settings.php | 5 +-- lang/en/settings.php | 5 +-- 6 files changed, 144 insertions(+), 21 deletions(-) diff --git a/action/editcommit.php b/action/editcommit.php index 91b68a3..d0d39b9 100644 --- a/action/editcommit.php +++ b/action/editcommit.php @@ -50,24 +50,75 @@ public function register(EventHandler $controller) $controller->register_hook('DOKUWIKI_DONE', 'AFTER', $this, 'handlePeriodicPull'); } - 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::setBin($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::setBin(empty($repoWorkDir) ? Git::getBin() - : Git::getBin() . ' --work-tree ' . escapeshellarg($repoWorkDir)); + : Git::getBin().' --work-tree ' . escapeshellarg($repoWorkDir)); + $params = str_replace( ['%mail%', '%user%'], [$this->getAuthorMail(), $this->getAuthor()], @@ -98,8 +149,10 @@ 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); @@ -178,6 +231,9 @@ public function handlePeriodicPull(Event &$event, $param) if ($lastPull + $timeToWait < $now) { try { $repo = $this->initRepo(); + if (is_null($repo)) { + return; + } if ($enableIndexUpdate) { $localPath = $this->computeLocalPath(); diff --git a/classes/GitRepo.php b/classes/GitRepo.php index b389115..ba2d039 100644 --- a/classes/GitRepo.php +++ b/classes/GitRepo.php @@ -120,11 +120,15 @@ public function setRepoPath($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->absoluteGitDir($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")) { + } elseif (file_exists($repo_path . "/.git") && is_dir($repo_path . "/.git")) { $this->repo_path = $repo_path; $this->bare = false; - // Is this a bare repo? + // Is this a bare repo? } elseif (is_file($repo_path . "/config")) { $parse_ini = parse_ini_file($repo_path . "/config"); if ($parse_ini['bare']) { @@ -136,6 +140,10 @@ public function setRepoPath($repo_path, $create_new = false, $_init = true) if ($_init) { $this->run('init'); } + } elseif (!$_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->handleRepoPathError( $repo_path, @@ -168,6 +176,17 @@ public function setRepoPath($repo_path, $create_new = false, $_init = true) } } + /** + * Get the path to the repo directory + * + * @access public + * @return string + */ + public function getRepoPath() + { + return $this->repo_path; + } + /** * Get the path to the git repo directory (eg. the ".git" directory) * @@ -201,6 +220,46 @@ public function testGit() 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 absoluteGitDir($path) + { + $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $pipes = []; + // Using --git-dir rather than --absolute-git-dir for a wider git versions compatibility + //$command = Git::getBin() . " rev-parse --absolute-git-dir"; + $command = Git::getBin() . " 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 * @@ -212,6 +271,10 @@ public function testGit() */ protected function runCommand($command) { + //dbglog("Git->run_command: repo_path=[" . $this->repo_path . "])"); + if (empty($this->repo_path)) { + throw new Exception($this->handleRepoPathError($this->repo_path, "Failure on GitRepo->runCommand(): Git command must not be run for an empty repo path")); + } //dbglog("Git->runCommand(command=[".$command."])"); $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; $pipes = []; diff --git a/conf/default.php b/conf/default.php index 72909cd..700c0cc 100644 --- a/conf/default.php +++ b/conf/default.php @@ -14,7 +14,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%>"'; diff --git a/conf/metadata.php b/conf/metadata.php index 0cf783b..d3c1405 100644 --- a/conf/metadata.php +++ b/conf/metadata.php @@ -14,6 +14,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'); diff --git a/lang/de/settings.php b/lang/de/settings.php index 4c12e33..3bdc051 100644 --- a/lang/de/settings.php +++ b/lang/de/settings.php @@ -14,8 +14,9 @@ $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 savedir ' . $GLOBALS['conf']['savedir'] . ')'; -$lang['repoWorkDir'] = 'Pfad des git working tree. Dieser muss die "pages" and "media" Verzeichnisse enthalten (z.B. das savedir ' . $GLOBALS['conf']['savedir'] . ')'; +$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'] = 'Veraltete Konfiguration: Pfad des git Repo (z.B. das savedir $GLOBALS[\'conf\'][\'savedir\'])
Hinweis: Diese Einstellung ist nur für Rückwärtskompatibilität einer vorhandenen Konfiguration gedacht. Wenn autoDetermineRepos aktiviert ist, dann sollte diese Einstellung für neue Installationen nicht gesetzt werden.'; +$lang['repoWorkDir'] = 'Veraltete Konfiguration: Pfad des git working tree. Dieser muss die "pages" and "media" Verzeichnisse enthalten (z.B. das savedir $GLOBALS[\'conf\'][\'savedir\'])
Hinweis: Diese Einstellung wird nur berücksichtigt, wenn repoPath gesetzt ist. In diesem Fall wird es nur für das Repo in repoPath 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)'; diff --git a/lang/en/settings.php b/lang/en/settings.php index 1486e36..0211093 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -14,8 +14,9 @@ $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['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'] = 'Legacy config: Path of the git repo (e.g. the savedir $GLOBALS[\'conf\'][\'savedir\'])
NOTE: This config is for backward compatibility of an existing configuration only. If autoDetermineRepos is on, then this config should not be set for new installations.'; +$lang['repoWorkDir'] = 'Legacy config: Path of the git working tree, must contain "pages" and "media" directories (e.g. the savedir $GLOBALS[\'conf\'][\'savedir\'])
NOTE: This config is considered only, if repoPath is set. In this case it does apply for the repo in repoPath 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)'; From 5a94880381877ffc279e518dc580e9986d9c37a8 Mon Sep 17 00:00:00 2001 From: Markus Hoffrogge Date: Mon, 2 Feb 2026 09:22:26 +0100 Subject: [PATCH 2/2] Fix codestyle warnings --- action/editcommit.php | 16 +++++++++++----- classes/GitRepo.php | 13 +++++++++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/action/editcommit.php b/action/editcommit.php index d0d39b9..eeb4469 100644 --- a/action/editcommit.php +++ b/action/editcommit.php @@ -59,9 +59,10 @@ public function register(EventHandler $controller) * @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="") { + private function initRepo($fileOrDirPath = "") + { global $conf; - + //set the path to the git binary $gitPath = trim($this->getConf('gitPath')); if ($gitPath !== '') { @@ -89,7 +90,9 @@ private function initRepo($fileOrDirPath="") { } // 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!"); + //dbglog("GitBacked - WARNING: repoPath=" . $repoPath . " is above the configured savedir=" + // . realpath($conf['savedir']) . " => this git repo will be ignored!" + //); return null; } $repoWorkDir = ''; @@ -99,7 +102,10 @@ private function initRepo($fileOrDirPath="") { // 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 - INFO: repoPath=" . $repoPath + // . " is the one explicitly configured => we use the configured workDir=[" + // . $repoWorkDir . "]" + //); } } //dbglog("GitBacked - AUTO_DETERMINE_USE_CASE: repoPath=" . $repoPath); @@ -117,7 +123,7 @@ private function initRepo($fileOrDirPath="") { } Git::setBin(empty($repoWorkDir) ? Git::getBin() - : Git::getBin().' --work-tree ' . escapeshellarg($repoWorkDir)); + : Git::getBin() . ' --work-tree ' . escapeshellarg($repoWorkDir)); $params = str_replace( ['%mail%', '%user%'], diff --git a/classes/GitRepo.php b/classes/GitRepo.php index ba2d039..f52426e 100644 --- a/classes/GitRepo.php +++ b/classes/GitRepo.php @@ -142,7 +142,8 @@ public function setRepoPath($repo_path, $create_new = false, $_init = true) } } elseif (!$_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 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->handleRepoPathError( @@ -178,7 +179,7 @@ public function setRepoPath($repo_path, $create_new = false, $_init = true) /** * Get the path to the repo directory - * + * * @access public * @return string */ @@ -224,7 +225,8 @@ public function testGit() * 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 + * @return string the next parent git repo root dir as absolute PHP realpath() + * or empty string, if no parent repo found. */ public function absoluteGitDir($path) { @@ -273,7 +275,10 @@ protected function runCommand($command) { //dbglog("Git->run_command: repo_path=[" . $this->repo_path . "])"); if (empty($this->repo_path)) { - throw new Exception($this->handleRepoPathError($this->repo_path, "Failure on GitRepo->runCommand(): Git command must not be run for an empty repo path")); + throw new \Exception($this->handleRepoPathError( + $this->repo_path, + "Failure on GitRepo->runCommand(): Git command must not be run for an empty repo path" + )); } //dbglog("Git->runCommand(command=[".$command."])"); $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];