From 0dcb71df2a2ff61465df808aecebdd6c996db70a Mon Sep 17 00:00:00 2001 From: Ryan Boog Date: Mon, 24 Jul 2023 16:13:41 -0500 Subject: [PATCH 1/8] Make sparse checkout optional --- blueprints.yaml | 11 +++++++++++ classes/GitSync.php | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/blueprints.yaml b/blueprints.yaml index fe6c5ee..fe0f9a8 100644 --- a/blueprints.yaml +++ b/blueprints.yaml @@ -37,6 +37,17 @@ form: validate: type: bool + sparse_checkout: + type: toggle + label: Enable Sparse Checkout + highlight: 1 + default: 0 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + folders: type: select multiple: true diff --git a/classes/GitSync.php b/classes/GitSync.php index e6aaa91..90586a9 100644 --- a/classes/GitSync.php +++ b/classes/GitSync.php @@ -136,7 +136,10 @@ public function initializeRepository() $this->execute('checkout -b ' . $local_branch, true); } - $this->enableSparseCheckout(); + // Check if the 'sparse_checkout' config option is enabled + if ($this->getConfig('sparse_checkout', false)) { + $this->enableSparseCheckout(); + } return true; } From b9727a4cb780650e433381ca10b24cf1d9373f6d Mon Sep 17 00:00:00 2001 From: Ryan Boog Date: Tue, 25 Jul 2023 10:00:34 -0500 Subject: [PATCH 2/8] [imp] use the folders when sparse checkout config is enabled --- classes/GitSync.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/classes/GitSync.php b/classes/GitSync.php index 90586a9..b735d73 100644 --- a/classes/GitSync.php +++ b/classes/GitSync.php @@ -438,19 +438,21 @@ public function isWorkingCopyClean() */ public function hasChangesToCommit() { - $folders = $this->config['folders']; + $sparseCheckoutEnabled = $this->config['sparseCheckoutEnabled']; + $folders = $sparseCheckoutEnabled ? $this->config['folders'] : ['']; // If sparse checkout is disabled, check the whole repository $paths = []; - + foreach ($folders as $folder) { $folder = explode('/', $folder); $paths[] = array_shift($folder); } - + $message = 'nothing to commit'; $output = $this->execute('status ' . implode(' ', $paths)); - + return strpos($output[count($output) - 1], $message) !== 0; } + /** * @param string $command From 0aab36f92f842ac2736d374b6a187899c771872f Mon Sep 17 00:00:00 2001 From: Joe Palkowitsch Date: Tue, 24 Oct 2023 10:04:51 -0500 Subject: [PATCH 3/8] add some logic around sparseCheckoutEnabled config --- classes/GitSync.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/GitSync.php b/classes/GitSync.php index b735d73..f11d98a 100644 --- a/classes/GitSync.php +++ b/classes/GitSync.php @@ -438,7 +438,7 @@ public function isWorkingCopyClean() */ public function hasChangesToCommit() { - $sparseCheckoutEnabled = $this->config['sparseCheckoutEnabled']; + $sparseCheckoutEnabled = isset($this->config['sparseCheckoutEnabled']) && $this->config['sparseCheckoutEnabled'] ? $this->config['sparseCheckoutEnabled'] : false; $folders = $sparseCheckoutEnabled ? $this->config['folders'] : ['']; // If sparse checkout is disabled, check the whole repository $paths = []; From 3b0f68bbb978096caad5f22482d34ff7c762bac2 Mon Sep 17 00:00:00 2001 From: Ryan Boog Date: Tue, 7 Nov 2023 18:57:44 -0600 Subject: [PATCH 4/8] [imp] add auth to fetch, it didn't seem to work before this change. Also, a feeble attempt to add a .gitignore to the local file before the initial sync so that it doesn't add the entire repo in the first sync. Close but no cigar --- classes/GitSync.php | 52 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/classes/GitSync.php b/classes/GitSync.php index f11d98a..d772740 100644 --- a/classes/GitSync.php +++ b/classes/GitSync.php @@ -129,19 +129,48 @@ public function testRepository($url, $branch) */ public function initializeRepository() { + if (!Helper::isGitInitialized()) { + $branch = $this->getRemote('branch', null); $local_branch = $this->getConfig('branch', $branch); $this->execute('init'); $this->execute('checkout -b ' . $local_branch, true); - } + + // Ensure .gitignore is in place and properly configured + $this->ensureGitignore(); + + // Check if the 'sparse_checkout' config option is enabled + if ($this->getConfig('sparse_checkout', false)) { + $this->enableSparseCheckout(); + } - // Check if the 'sparse_checkout' config option is enabled - if ($this->getConfig('sparse_checkout', false)) { - $this->enableSparseCheckout(); } - + return true; + } + + private function ensureGitignore() { + $branch = $this->getRemote('branch', null); + $local_branch = $this->getConfig('branch', $branch); + $gitignorePath = $this->repositoryPath . '/.gitignore'; + + // Check if .gitignore exists + if (!file_exists($gitignorePath)) { + + // Ensure the remote is set up correctly + if (!$this->hasRemote('origin')) { + $url = $this->getConfig('repository', null); + $this->addRemote('origin', $url); + } + + $this->fetch('origin', null, true); + + // Checkout the .gitignore file from the remote repository + $this->execute('checkout origin/' . $local_branch . ' -- .gitignore'); + } else { + $this->grav['log']->info('.gitignore already exists.'); + } } /** @@ -351,11 +380,20 @@ public function commit($message = '(Grav GitSync) Automatic Commit') * @param string|null $branch * @return string[] */ - public function fetch($name = null, $branch = null) + public function fetch($name = null, $branch = null, $authenticated = false) { $name = $this->getRemote('name', $name); $branch = $this->getRemote('branch', $branch); - + + if ($authenticated) { + $user = $this->user ?? ''; + $password = $this->password ? Helper::decrypt($this->password) : ''; + $url = $this->getConfig('repository', null); + $url = Helper::prepareRepository($user, $password, $url); + // Set the remote URL with credentials + $this->execute("remote set-url {$name} \"{$url}\""); + } + return $this->execute("fetch {$name} {$branch}"); } From cbad9093d13658494190c10793be9ab146fc0a49 Mon Sep 17 00:00:00 2001 From: Ryan Boog Date: Wed, 8 Nov 2023 16:13:01 -0600 Subject: [PATCH 5/8] [imp] init seems to be wired correctly for now --- classes/GitSync.php | 141 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 122 insertions(+), 19 deletions(-) diff --git a/classes/GitSync.php b/classes/GitSync.php index d772740..109de2b 100644 --- a/classes/GitSync.php +++ b/classes/GitSync.php @@ -126,7 +126,7 @@ public function testRepository($url, $branch) /** * @return bool - */ + */ public function initializeRepository() { @@ -134,11 +134,33 @@ public function initializeRepository() $branch = $this->getRemote('branch', null); $local_branch = $this->getConfig('branch', $branch); + + // Create the .git folder $this->execute('init'); + + // Add the repo as a remote upstream + $this->remoteAddUpstream(true); + + // Check out the appropriate branch $this->execute('checkout -b ' . $local_branch, true); - - // Ensure .gitignore is in place and properly configured - $this->ensureGitignore(); + + // Fetch from the upstream (get info from the source of truth) + $this->fetchUpstream(); + + // Save untracked files + $this->saveUntrackedFiles(); + + // Integrate fetched updates, minus untracked files, into the local branch + $this->mergeUpstream(); + + // Restore the untracked files if there are any, crossing fingers that there are no conflicts + $this->restoreUntrackedFiles(); + + // Now that the updates are integrated, add and commit them locally + $this->initialAddCommit(); + + // Push this initial commit + $this->pushUpstream(); // Check if the 'sparse_checkout' config option is enabled if ($this->getConfig('sparse_checkout', false)) { @@ -150,27 +172,106 @@ public function initializeRepository() return true; } - private function ensureGitignore() { + private function remoteAddUpstream($authenticated = false) { + if (!$this->hasRemote('origin')) { + $url = $this->getConfig('repository', null); + if ($authenticated) { + // You should retrieve the username and password in a secure way + $user = $this->user ?? ''; + $password = $this->password ? Helper::decrypt($this->password) : ''; + // Perhaps you need to update the remote URL with the credentials here + $url = $this->getConfig('repository', null); + $url = Helper::prepareRepository($user, $password, $url); + // fetch upstream with credentials + $this->execute('remote add upstream '.$url); + } else { + $this->grav['log']->error('Authentication needed'); + } + } + } + + private function fetchUpstream() { $branch = $this->getRemote('branch', null); $local_branch = $this->getConfig('branch', $branch); - $gitignorePath = $this->repositoryPath . '/.gitignore'; - - // Check if .gitignore exists - if (!file_exists($gitignorePath)) { + $this->execute('fetch upstream '. $local_branch); + } - // Ensure the remote is set up correctly - if (!$this->hasRemote('origin')) { - $url = $this->getConfig('repository', null); - $this->addRemote('origin', $url); + private function saveUntrackedFiles() { + $untrackedFiles = $this->execute('ls-files --others --exclude-standard'); + if (!empty($untrackedFiles)) { + $this->backupUntrackedFiles($untrackedFiles); + } + } + + private function backupUntrackedFiles($untrackedFiles) { + $backupDirectory = 'tmp/git-sync/'; + $userDirectory = 'user/'; // Set the correct directory where the files reside + + if (!is_dir($backupDirectory) && !mkdir($backupDirectory, 0777, true) && !is_dir($backupDirectory)) { + throw new \Exception("Unable to create directory: " . $backupDirectory); + } + + foreach ($untrackedFiles as $file) { + $sourcePath = realpath($userDirectory . $file); // Prepend the /user/ directory to the path + if ($sourcePath === false) { + throw new \Exception("Source file does not exist: " . $userDirectory . $file); + } + + // Maintain the directory structure + $destinationPath = $backupDirectory . $file; + $destinationDir = dirname($destinationPath); + + // Create the directory structure if it doesn't exist + if (!is_dir($destinationDir) && !mkdir($destinationDir, 0777, true)) { + throw new \Exception("Unable to create directory: " . $destinationDir); } + + if (!rename($sourcePath, $destinationPath)) { + throw new \Exception("Unable to move file: " . $userDirectory . $file); + } + } + } - $this->fetch('origin', null, true); + private function mergeUpstream() { + $branch = $this->getRemote('branch', null); + $local_branch = $this->getConfig('branch', $branch); + $this->execute('merge upstream/'. $local_branch); + } - // Checkout the .gitignore file from the remote repository - $this->execute('checkout origin/' . $local_branch . ' -- .gitignore'); - } else { - $this->grav['log']->info('.gitignore already exists.'); + private function restoreUntrackedFiles() { + $backupDirectory = 'tmp/git-sync/'; + $userDirectory = 'user/'; // Set the correct directory where the files should be restored + + // Use rsync to synchronize directories + $rsyncCommand = "rsync -av --ignore-existing '$backupDirectory' '$userDirectory'"; + exec($rsyncCommand, $output, $returnVar); + + if ($returnVar !== 0) { + throw new \Exception("Rsync failed with error code $returnVar"); } + + // After successful rsync, remove the backup directory + $this->cleanUpBackupDirectory($backupDirectory); + } + + private function cleanUpBackupDirectory($backupDirectory) { + $command = "rm -rf " . escapeshellarg($backupDirectory); + exec($command, $output, $returnVar); + if ($returnVar !== 0) { + throw new \Exception("Failed to remove the backup directory."); + } + } + + private function initialAddCommit() { + $this->execute('add .'); + $commitCommand = '-c user.name="Your Grav Site" -c user.email="sales@happydog.digital" commit -m "Initial merge of the repo and the project"'; + $this->execute($commitCommand); + } + + private function pushUpstream() { + $branch = $this->getRemote('branch', null); + $local_branch = $this->getConfig('branch', $branch); + $this->execute('push upstream '. $local_branch); } /** @@ -386,8 +487,10 @@ public function fetch($name = null, $branch = null, $authenticated = false) $branch = $this->getRemote('branch', $branch); if ($authenticated) { + // You should retrieve the username and password in a secure way $user = $this->user ?? ''; $password = $this->password ? Helper::decrypt($this->password) : ''; + // Perhaps you need to update the remote URL with the credentials here $url = $this->getConfig('repository', null); $url = Helper::prepareRepository($user, $password, $url); // Set the remote URL with credentials @@ -395,7 +498,7 @@ public function fetch($name = null, $branch = null, $authenticated = false) } return $this->execute("fetch {$name} {$branch}"); - } + } /** * @param string|null $name From 5f0378a9bc52c9cf1d45efe2d724cd2cf8a49189 Mon Sep 17 00:00:00 2001 From: Ryan Boog Date: Fri, 10 Nov 2023 08:59:53 -0600 Subject: [PATCH 6/8] [imp] fix fetch and add remove upstream --- classes/GitSync.php | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/classes/GitSync.php b/classes/GitSync.php index 109de2b..79628bd 100644 --- a/classes/GitSync.php +++ b/classes/GitSync.php @@ -161,6 +161,9 @@ public function initializeRepository() // Push this initial commit $this->pushUpstream(); + + // We are up to date. We can remove upstream and rely on origin + $this->removeUpstream(); // Check if the 'sparse_checkout' config option is enabled if ($this->getConfig('sparse_checkout', false)) { @@ -274,6 +277,10 @@ private function pushUpstream() { $this->execute('push upstream '. $local_branch); } + private function removeUpstream() { + $this->execute('remote remove upstream'); + } + /** * @param string|null $name * @param string|null $email @@ -481,22 +488,10 @@ public function commit($message = '(Grav GitSync) Automatic Commit') * @param string|null $branch * @return string[] */ - public function fetch($name = null, $branch = null, $authenticated = false) + public function fetch($name = null, $branch = null) { $name = $this->getRemote('name', $name); - $branch = $this->getRemote('branch', $branch); - - if ($authenticated) { - // You should retrieve the username and password in a secure way - $user = $this->user ?? ''; - $password = $this->password ? Helper::decrypt($this->password) : ''; - // Perhaps you need to update the remote URL with the credentials here - $url = $this->getConfig('repository', null); - $url = Helper::prepareRepository($user, $password, $url); - // Set the remote URL with credentials - $this->execute("remote set-url {$name} \"{$url}\""); - } - + $branch = $this->getRemote('branch', $branch); return $this->execute("fetch {$name} {$branch}"); } From 524030ebe4306a671ac7ae426a169d713dee8e85 Mon Sep 17 00:00:00 2001 From: Ryan Boog Date: Fri, 10 Nov 2023 14:10:10 -0600 Subject: [PATCH 7/8] [imp] fixed order of things, failsafe check on empty commit --- classes/GitSync.php | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/classes/GitSync.php b/classes/GitSync.php index 79628bd..2d32ac2 100644 --- a/classes/GitSync.php +++ b/classes/GitSync.php @@ -141,17 +141,17 @@ public function initializeRepository() // Add the repo as a remote upstream $this->remoteAddUpstream(true); - // Check out the appropriate branch - $this->execute('checkout -b ' . $local_branch, true); - // Fetch from the upstream (get info from the source of truth) $this->fetchUpstream(); // Save untracked files $this->saveUntrackedFiles(); + // Check out the appropriate branch then set upstream to that branch + $this->execute('checkout -b ' . $local_branch); + // Integrate fetched updates, minus untracked files, into the local branch - $this->mergeUpstream(); + $this->pullUpstream(); // Restore the untracked files if there are any, crossing fingers that there are no conflicts $this->restoreUntrackedFiles(); @@ -241,6 +241,12 @@ private function mergeUpstream() { $this->execute('merge upstream/'. $local_branch); } + private function pullUpstream() { + $branch = $this->getRemote('branch', null); + $local_branch = $this->getConfig('branch', $branch); + $this->execute('pull upstream '. $local_branch); + } + private function restoreUntrackedFiles() { $backupDirectory = 'tmp/git-sync/'; $userDirectory = 'user/'; // Set the correct directory where the files should be restored @@ -267,9 +273,15 @@ private function cleanUpBackupDirectory($backupDirectory) { private function initialAddCommit() { $this->execute('add .'); - $commitCommand = '-c user.name="Your Grav Site" -c user.email="sales@happydog.digital" commit -m "Initial merge of the repo and the project"'; - $this->execute($commitCommand); - } + // Check if there are changes to commit + $status = $this->execute('status --porcelain'); + if (!empty($status)) { + $commitCommand = '-c user.name="Your Grav Site" -c user.email="sales@happydog.digital" commit -m "Initial merge of the repo and the project"'; + $this->execute($commitCommand); + } else { + $this->grav['log']->info('No changes to commit'); + } + } private function pushUpstream() { $branch = $this->getRemote('branch', null); @@ -488,10 +500,20 @@ public function commit($message = '(Grav GitSync) Automatic Commit') * @param string|null $branch * @return string[] */ - public function fetch($name = null, $branch = null) + public function fetch($name = null, $branch = null, $authenticated = false) { $name = $this->getRemote('name', $name); - $branch = $this->getRemote('branch', $branch); + $branch = $this->getRemote('branch', $branch); + if ($authenticated) { + // You should retrieve the username and password in a secure way + $user = $this->user ?? ''; + $password = $this->password ? Helper::decrypt($this->password) : ''; + // Perhaps you need to update the remote URL with the credentials here + $url = $this->getConfig('repository', null); + $url = Helper::prepareRepository($user, $password, $url); + // Set the remote URL with credentials + $this->execute("remote set-url {$name} \"{$url}\""); + } return $this->execute("fetch {$name} {$branch}"); } From 9b6f26e6ae0f3ce0bdb3e0dafa87c8a461052e45 Mon Sep 17 00:00:00 2001 From: Ryan Boog Date: Wed, 20 Dec 2023 14:57:53 -0600 Subject: [PATCH 8/8] Use {$var} instead of ${var} --- classes/Helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/Helper.php b/classes/Helper.php index 9dac08f..7e3ebef 100644 --- a/classes/Helper.php +++ b/classes/Helper.php @@ -77,7 +77,7 @@ public static function prepareRepository($user, $password, $repository) return $repository; } - return str_replace('://', "://${user}${password}@", $repository); + return str_replace('://', "://{$user}{$password}@", $repository); } /**