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

add support for git repos #5

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ The `extra.downloads` section contains a list of files to download. Each extra-f
* `archive`: The `url` references a zip or tarball which should be extracted at the given `path`. (Default for URLs involving `*.zip`, `*.tar.gz`, or `*.tgz`.)
* `file`: The `url` should be downloaded to the given `path`. (Default for all other URLs.)
* `phar`: The `url` references a PHP executable which should be installed at the given `path`.
* `git`: The `url` references a git repository address and commit, tag or branch (separated by an @ sign) which should be checked out at the given `path`. (E.g. `https://git.me.org/foo/bar@123abc`.)

* `ignore`: (*Optional*) A list of a files that should be omited from the extracted folder. (This supports a subset of `.gitignore` notation.)
* `ignore`: (*Optional*) A list of a files that should be omited from the extracted folder. (This supports a subset of `.gitignore` notation.) The `ignore` property is ignored when using the `git` type.

* `version`: (*Optional*) A version number for the downloaded artifact. This has no functional impact on the lifecycle of the artifact, but
it can affect the console output, and it can be optionally used as a variable when setting `url` or `path`.
Expand Down
2 changes: 2 additions & 0 deletions src/DownloadsParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use LastCall\DownloadsPlugin\Handler\BaseHandler;
use LastCall\DownloadsPlugin\Handler\FileHandler;
use LastCall\DownloadsPlugin\Handler\PharHandler;
use LastCall\DownloadsPlugin\Handler\GitHandler;

class DownloadsParser
{
Expand Down Expand Up @@ -62,6 +63,7 @@ public function pickClass($extraFile)
'archive' => ArchiveHandler::CLASS,
'file' => FileHandler::CLASS,
'phar' => PharHandler::CLASS,
'git' => GitHandler::CLASS,
];
if (isset($extraFile['type'], $types[$extraFile['type']])) {
return $types[$extraFile['type']];
Expand Down
66 changes: 66 additions & 0 deletions src/Handler/GitHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace LastCall\DownloadsPlugin\Handler;

use Composer\Composer;
use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Util\Git;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use React\Promise\PromiseInterface;

class GitHandler extends BaseHandler
{
const TMP_PREFIX = '.composer-extra-tmp-';

public function createSubpackage()
{
$pkg = parent::createSubpackage();
$pkg->setDistType('git');
return $pkg;
}

public function getTrackingFile()
{
$file = basename($this->extraFile['id']) . '-' . md5($this->extraFile['id']) . '.json';
return
dirname($this->getTargetPath()) .
DIRECTORY_SEPARATOR . self::DOT_DIR .
DIRECTORY_SEPARATOR . $file;
}

/**
* @param Composer $composer
* @param IOInterface $io
*/
public function download(Composer $composer, IOInterface $io) {
$urlAndVersion = $this->getSubpackage()->getDistUrl();
$config = $composer->getConfig();
$wd = $this->getTargetPath();
$process = new ProcessExecutor($io);
$cfs = new Filesystem();
$git = new Git($io, $config, $process, $cfs);
if (file_exists($wd)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tested, but I see these two code-paths (based on file_exists()), and it makes me imagine the following use-case:

  1. In top-level composer.json, add extra.downloads definition with a git URL.
  2. Run composer install.
  3. In top-level composer.json, edit extra.downloads and change the git commit/tag.
  4. Run composer install.

I guess the idea is that step 2 makes a new folder, and step 4 updates that same folder.

But what if that folder doesn't have any .git data? For example:

  1. In top-level composer.json, add extra.downloads definition with a zip URL.
  2. Run composer install.
  3. In top-level composer.json, edit extra.downloads definition to a git URL.
  4. Run composer install.

At step 4, shouldn't we clear out the folder before doing any git operations?

(IIRC, this only matters for extra.downloads in the top-level folder. In other contexts, there's a higher level rm -rf.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback! And yes, I agree, I think adding git support here is a better way forward than adding the alternative.

I can work on extending the test coverage.

Regarding the file_exists code paths - I added it without much thought in the interest of speed and efficiency (if we already have the directory in place and it's checked out to the same commit, we move on very quickly). However, it does sacrifice correctness in the use case you provide (and perhaps others we haven't even thought of).

I think, in the context of this extension, the .composer-downloads/<id>-<hash>.json is kinda like the composer.lock file. So, on composer install we could test for the existance of that file. If it doesn't exist and we have a folder, than rm -rf the folder and install from scratch. If it does exist, we could assume the contents properly describe the folder and run git fetch (if someone mucked with the folder git will return an error and that seems reasonable).

On composer update we would need to compare composer.json with .composer-downloads/<id>-<hash>.json. If we are moving from zip to git, then rm -rf the folder and start from scratch. If the repo URL changes, rm -rf and start from scratch, and otherwise git fetch.

Having spelled all this out, I'm leaning towards your initial suggestion of just rm -rf. I'm not sure all the added complexity is worth the small efficiency gain. Open to writing the more complex approach though if you prefer.

$cfs->removeDirectory($wd);
}
// Make the directory recursively.
$cfs->ensureDirectoryExists($wd);
$gitCallable = static function ($urlAndVersion): string {
$parts = explode('@', $urlAndVersion);
$url = $parts[0];
if (count($parts) > 1) {
$version = $parts[1];
}
else {
$version = 'master';
}
return sprintf('git init && git fetch %s "+refs/heads/*:refs/remotes/origin/*" && git checkout %s',
ProcessExecutor::escape($url),
ProcessExecutor::escape($version)
);
};
$git->runCommand($gitCallable, $urlAndVersion, $wd);
}

}
12 changes: 9 additions & 3 deletions tests/SniffTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ public static function getComposerJson() {
'url' => 'https://download.civicrm.org/cv/cv.phar-2019-08-20-14fe9da8',
'path' => 'bin/cv',
],
'org.civicrm.sms.twilio' => [
'type' => 'git',
'url' => 'https://github.com/civicrm/org.civicrm.sms.twilio@494728cf13bb65c228429665c158d899cd7e6dc1',
'path' => 'web/sites/default/files/civicrm/ext/org.civicrm.sms.twilio',
],
],
],
'config' => [
Expand All @@ -69,7 +74,8 @@ public function getExampleChecksums() {
['extern/jquery-full', 'extern/jquery-full/Gruntfile.js', '3508ff74f8ef106a80f25f28f44a20c47a2b67d84396bb141928ff978ba4012e'],
['extern/jquery-lesser', 'extern/jquery-lesser/dist/jquery.js', '5f2caf09052782caf67e1772c0abce31747ffbc7a1c50690e331b99c7d9ea8dc'],
['extern/jquery-lesser', 'extern/jquery-lesser/Gruntfile.js', NULL],
['bin/cv', 'bin/cv', 'bf162d5d7dd0bef087d7dd07f474039b2e25c4bcca328a2b2097958ac6294476']
['bin/cv', 'bin/cv', 'bf162d5d7dd0bef087d7dd07f474039b2e25c4bcca328a2b2097958ac6294476'],
['web/sites/default/files/civicrm/ext/org.civicrm.sms.twilio', 'web/sites/default/files/civicrm/ext/org.civicrm.sms.twilio/info.xml', 'fce6bbd3af2315b4357393b3fc76b52ebc6ab3c27290c82ce7545f0bf2928109'],
];
}

Expand All @@ -92,7 +98,7 @@ public function testDownloadAndRedownload($path, $file, $sha256) {
else {
unlink($path);
}
$this->assertFileNotExists($file);
$this->assertFileDoesNotExist($file);
$composer_path = self::getComposerPath();
PH::runOk("$composer_path install -v");

Expand All @@ -102,7 +108,7 @@ public function testDownloadAndRedownload($path, $file, $sha256) {

public function assertFileChecksum($file, $sha256, $message = NULL) {
if ($sha256 === NULL) {
$this->assertFileNotExists($file, "($message) File should not exist");
$this->assertFileDoesNotExist($file, "($message) File should not exist");
}
else {
$this->assertFileExists($file, "($message) File should exist");
Expand Down