Skip to content

Commit 8d3034f

Browse files
committed
Apply patches using git
1 parent b6deb40 commit 8d3034f

File tree

8 files changed

+173
-14
lines changed

8 files changed

+173
-14
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ It's (kind-of) an alternative to two other great plugins (differences will becom
2929
| Specify target package version constraints | yes | no | yes |
3030
| Uninstall removed patches in all cases | yes | no | TBD |
3131
| Reapply package patches if order has changed | yes | TBD | TBD |
32+
| Choose application method (git/patch) per patch | yes | TBD | TBD |
3233

3334
### Some feature hilights
3435

@@ -61,7 +62,11 @@ carries the risk of producing invalid state at the end. This plugin takes a diff
6162
all actions at once, after the installation/update was performed, just before autoload dump (in case patching changes it).
6263

6364
This guarantees a consistent state as the plugin compares the current state with the desired one and peforms only
64-
the actions necessary to get there.
65+
the actions necessary to get there.
66+
67+
**This has one drawback, if your project uses any composer plugins that copy files from vendors to the root
68+
of your project then you should patch the root package because the patch applications will happen after those
69+
plugins have done their work so patching source files in vendor will have no effect.**
6570

6671

6772
### No remote patches

src/Patch.php

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,28 +48,36 @@ class Patch
4848
*/
4949
private $stripPathComponents = 1;
5050

51+
/**
52+
* @var string
53+
*/
54+
private $method = PatchApplicator::METHOD_PATCH;
55+
5156
/**
5257
* @param $sourcePackage
5358
* @param string $targetPackage
5459
* @param string $versionConstraint
5560
* @param string $filename
5661
* @param string $description
5762
* @param int $stripPathComponents
63+
* @param string $method
5864
*/
5965
public function __construct(
6066
$sourcePackage,
6167
$targetPackage,
6268
$versionConstraint,
6369
$filename,
6470
$description,
65-
$stripPathComponents = 1
71+
$stripPathComponents = 1,
72+
$method = PatchApplicator::METHOD_PATCH
6673
) {
6774
$this->sourcePackage = $sourcePackage;
6875
$this->targetPackage = $targetPackage;
6976
$this->versionConstraint = $versionConstraint;
7077
$this->filename = $filename;
7178
$this->description = $description;
7279
$this->stripPathComponents = $stripPathComponents;
80+
$this->method = $method;
7381
}
7482

7583
/**
@@ -83,16 +91,26 @@ public static function createFromConfig($sourcePackage, $targetPackage, array $c
8391
$config = array_merge([
8492
'version-constraint' => '*',
8593
'description' => null,
86-
'strip-path-components' => 1
94+
'strip-path-components' => 1,
95+
'method' => PatchApplicator::METHOD_PATCH
8796
], $config);
8897

98+
if (!in_array($config['method'], PatchApplicator::METHODS)) {
99+
throw new \RuntimeException(sprintf('Unsupported patch application method "%s" in patchset "%s", use one of %s',
100+
$config['method'],
101+
$sourcePackage,
102+
join(', ', PatchApplicator::METHODS)
103+
));
104+
}
105+
89106
return new static(
90107
$sourcePackage,
91108
$targetPackage,
92109
$config['version-constraint'],
93110
$config['filename'],
94111
$config['description'],
95-
$config['strip-path-components']
112+
$config['strip-path-components'],
113+
$config['method']
96114
);
97115
}
98116

@@ -105,7 +123,8 @@ public static function createFromArray(array $data)
105123
$data['version_constraint'],
106124
$data['filename'],
107125
$data['description'],
108-
$data['strip_path_components']
126+
$data['strip_path_components'],
127+
$data['method']
109128
);
110129
}
111130

@@ -174,6 +193,13 @@ public function getStripPathComponents()
174193
return $this->stripPathComponents;
175194
}
176195

196+
/**
197+
* @return string
198+
*/
199+
public function getMethod()
200+
{
201+
return $this->method;
202+
}
177203

178204
public function toArray()
179205
{
@@ -183,7 +209,8 @@ public function toArray()
183209
'version_constraint' => $this->versionConstraint,
184210
'filename' => $this->filename,
185211
'description' => $this->description,
186-
'strip_path_components' => $this->stripPathComponents
212+
'strip_path_components' => $this->stripPathComponents,
213+
'method' => $this->method
187214
];
188215
}
189216
}

src/PatchApplicator.php

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,21 @@
44

55
use Composer\Installer\InstallationManager;
66
use Composer\Package\PackageInterface;
7+
use Composer\Util\Filesystem;
78
use Composer\Util\ProcessExecutor;
9+
810
use Psr\Log\LoggerInterface;
911

1012
class PatchApplicator
1113
{
14+
const METHOD_PATCH = 'patch';
15+
const METHOD_GIT = 'git';
16+
17+
const METHODS = [
18+
PatchApplicator::METHOD_PATCH,
19+
PatchApplicator::METHOD_GIT
20+
];
21+
1222
/**
1323
* @var InstallationManager
1424
*/
@@ -39,6 +49,11 @@ class PatchApplicator
3949
*/
4050
private $cmdErr;
4151

52+
/**
53+
* @var Filesystem
54+
*/
55+
private $filesystem;
56+
4257
public function __construct(
4358
LoggerInterface $logger,
4459
InstallationManager $installationManager,
@@ -49,6 +64,7 @@ public function __construct(
4964
$this->installationManager = $installationManager;
5065
$this->pathResolver = $pathResolver;
5166
$this->executor = $executor;
67+
$this->filesystem = new Filesystem($this->executor);
5268

5369
if (!$this->hasPatchCommand()) {
5470
$this->logger->warning('<warning>No `patch` command found, will fall-back to `git apply` for patching</warning>');
@@ -85,36 +101,59 @@ private function hasPatchCommand()
85101
}
86102

87103
/**
104+
* @param string $method
88105
* @param string $targetDirectory
89106
* @param string $patchFile
90107
* @param int $stripPathComponents
91108
* @return bool
92109
*/
93-
private function executePatchCommand($targetDirectory, $patchFile, $stripPathComponents)
110+
private function executePatchCommand($method, $targetDirectory, $patchFile, $stripPathComponents)
94111
{
95-
if ($this->hasPatchCommand()) {
112+
$cwd = null;
113+
114+
if ($method === self::METHOD_PATCH && $this->hasPatchCommand()) {
96115
$cmd = ['patch', '--posix', '--strip=' . $stripPathComponents, '--input='.$patchFile, '--directory='.$targetDirectory];
97116
} else {
98-
$cmd = ['patch', '--posix', '--strip=' . $stripPathComponents, '--input='.$patchFile, '--directory='.$targetDirectory];
117+
$cmd = ['git', 'apply', '-v', '-p' . $stripPathComponents, '--inaccurate-eof', '--ignore-whitespace', $patchFile];
118+
119+
if (is_dir(rtrim($targetDirectory, '/') . '/.git')) {
120+
// Target dir is a git repo so apply relative to it - apparently some git versions have problems otherwise.
121+
// I haven't found problems with patching "subprepos" using git 2.x, however, this has been reported here:
122+
// - https://github.com/cweagans/composer-patches/issues/172
123+
// - https://stackoverflow.com/questions/24821431/git-apply-patch-fails-silently-no-errors-but-nothing-happens/27283285#27283285
124+
// - http://data.agaric.com/git-apply-does-not-work-from-within-local-checkout-unrelated-git-repository
125+
$cwd = $targetDirectory;
126+
} else {
127+
// If target directory is not a git repo apply relative to project root
128+
$rootDirectory = $this->filesystem->normalizePath(getcwd());
129+
$targetDirectory = $this->filesystem->normalizePath($targetDirectory);
130+
131+
// Do this only if we're not patching the root package
132+
if ($rootDirectory !== $targetDirectory) {
133+
$relativeTargetDirectory = $this->filesystem->findShortestPath($rootDirectory, $targetDirectory);
134+
$cmd[] = '--directory=' . $relativeTargetDirectory;
135+
}
136+
}
99137
}
100138

101-
return !$this->executeCommand($cmd);
139+
return !$this->executeCommand($cmd, $cwd);
102140
}
103141

104142
public function applyPatch(Patch $patch, PackageInterface $sourcePackage, PackageInterface $targetPackage)
105143
{
106144
$targetDirectory = $this->pathResolver->getPackageInstallPath($targetPackage);
107145
$patchFilename = $this->pathResolver->getPatchSourceFilePath($sourcePackage, $patch);
108146

109-
if (!$this->executePatchCommand($targetDirectory, $patchFilename, $patch->getStripPathComponents())) {
147+
if (!$this->executePatchCommand($patch->getMethod(), $targetDirectory, $patchFilename, $patch->getStripPathComponents())) {
110148
throw new \RuntimeException('Could not apply patch: ' . $this->cmdErr);
111149
}
112150

113-
$this->logger->notice(sprintf('Applied patch <info>%s:%s</info> [<comment>%s</comment>] (<comment>%s</comment>)',
151+
$this->logger->notice(sprintf('Applied patch <info>%s:%s</info> [<comment>%s</comment>] (<comment>%s</comment>) using <comment>%s</comment> method',
114152
$patch->getSourcePackage(),
115153
$patch->getFilename(),
116154
$patch->getVersionConstraint(),
117-
$patch->getDescription()
155+
$patch->getDescription(),
156+
$patch->getMethod()
118157
));
119158
}
120159
}

tests/Functional/CoreFeatureTest.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ public function testArbitraryNumberOfPathComponentsCanBeStripped()
236236
]
237237
]);
238238

239-
$installRun = $project->runComposerCommand('install', '-vvv');
239+
$installRun = $project->runComposerCommand('install');
240240

241241
$this->assertThatComposerRunHasAppliedPatches($installRun,
242242
array_merge_recursive(
@@ -245,4 +245,27 @@ public function testArbitraryNumberOfPathComponentsCanBeStripped()
245245
)
246246
);
247247
}
248+
249+
public function testGitApplyCanBeUsedForPatching()
250+
{
251+
$project = $this->getSandbox()->createProjectSandBox('test/project-template', 'dev-master', [
252+
'require' => [
253+
'test/patchset-git' => '1.0',
254+
'test/package-a' => 'dev-master',
255+
]
256+
]);
257+
258+
$installRun = $project->runComposerCommand('install', '-vvv');
259+
260+
$this->assertThatComposerRunHasAppliedPatches($installRun,
261+
array_merge_recursive(
262+
self::PACKAGEA_PATCH1_APPLICATIONS,
263+
self::PACKAGEA_PATCH2_APPLICATIONS,
264+
self::ROOT_PACKAGE_APPLICATIONS
265+
)
266+
);
267+
268+
$this->assertContains('using git method', $installRun->getFullOutput(), 'patches were applied using git', true);
269+
$this->assertNotContains('using patch method', $installRun->getFullOutput(), 'no patches were applied using patch command', true);
270+
}
248271
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "test/patchset-git",
3+
"type": "patchset",
4+
"version": "1.0",
5+
"require": {
6+
"creativestyle/composer-plugin-patchset": "dev-master"
7+
},
8+
"extra": {
9+
"patchset": {
10+
"test/package-a": [
11+
{
12+
"description": "Patch in echo in the middle",
13+
"filename": "patches/package-a-patch-1.diff",
14+
"method": "git"
15+
},
16+
{
17+
"description": "Patch in echo at the end",
18+
"filename": "patches/package-a-patch-2-p2.diff",
19+
"strip-path-components": 2,
20+
"method": "git"
21+
}
22+
],
23+
"test/project-template": [
24+
{
25+
"description": "Patch project root",
26+
"filename": "patches/root-project-patch-1.diff",
27+
"method": "git"
28+
}
29+
]
30+
}
31+
}
32+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
diff --git a/src/test.php b/src/test.php
2+
index 863f87b..655bb2c 100644
3+
--- a/src/test.php
4+
+++ b/src/test.php
5+
@@ -1,4 +1,5 @@
6+
<?php
7+
8+
echo 'first-echo';
9+
+echo 'patched-in-echo';
10+
echo 'last-echo';
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--- vendor/test/src/test.php
2+
+++ vendor/test/src/test.php
3+
@@ -1,5 +1,8 @@
4+
<?php
5+
6+
+echo 'layered-patch-line-1'
7+
echo 'first-echo';
8+
echo 'patched-in-echo';
9+
+echo 'layered-patch-line-2'
10+
echo 'last-echo';
11+
+echo 'layered-patch-line-3'
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
diff --git a/src/root-code.php b/src/root-code.php
2+
index 9200757..5df64a1 100644
3+
--- a/src/root-code.php
4+
+++ b/src/root-code.php
5+
@@ -2,6 +2,7 @@
6+
7+
foreach (['this', 'is', 'root', 'package', 'code'] as $word) {
8+
echo $word;
9+
+ echo "-is-patched\n";
10+
}
11+
12+
echo 'done';

0 commit comments

Comments
 (0)