function GitDownloader::getUnpushedChanges
Overrides DvcsDownloaderInterface::getUnpushedChanges
1 call to GitDownloader::getUnpushedChanges()
- GitDownloader::cleanChanges in vendor/
composer/ composer/ src/ Composer/ Downloader/ GitDownloader.php - @inheritDoc
File
-
vendor/
composer/ composer/ src/ Composer/ Downloader/ GitDownloader.php, line 231
Class
- GitDownloader
- @author Jordi Boggiano <j.boggiano@seld.be>
Namespace
Composer\DownloaderCode
public function getUnpushedChanges(PackageInterface $package, string $path) : ?string {
GitUtil::cleanEnv();
$path = $this->normalizePath($path);
if (!$this->hasMetadataRepository($path)) {
return null;
}
$command = [
'git',
'show-ref',
'--head',
'-d',
];
if (0 !== $this->process
->execute($command, $output, $path)) {
throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process
->getErrorOutput());
}
$refs = trim($output);
if (!Preg::isMatchStrictGroups('{^([a-f0-9]+) HEAD$}mi', $refs, $match)) {
// could not match the HEAD for some reason
return null;
}
$headRef = $match[1];
if (!Preg::isMatchAllStrictGroups('{^' . preg_quote($headRef) . ' refs/heads/(.+)$}mi', $refs, $matches)) {
// not on a branch, we are either on a not-modified tag or some sort of detached head, so skip this
return null;
}
$candidateBranches = $matches[1];
// use the first match as branch name for now
$branch = $candidateBranches[0];
$unpushedChanges = null;
$branchNotFoundError = false;
// do two passes, as if we find anything we want to fetch and then re-try
for ($i = 0; $i <= 1; $i++) {
$remoteBranches = [];
// try to find matching branch names in remote repos
foreach ($candidateBranches as $candidate) {
if (Preg::isMatchAllStrictGroups('{^[a-f0-9]+ refs/remotes/((?:[^/]+)/' . preg_quote($candidate) . ')$}mi', $refs, $matches)) {
foreach ($matches[1] as $match) {
$branch = $candidate;
$remoteBranches[] = $match;
}
break;
}
}
// if it doesn't exist, then we assume it is an unpushed branch
// this is bad as we have no reference point to do a diff so we just bail listing
// the branch as being unpushed
if (count($remoteBranches) === 0) {
$unpushedChanges = 'Branch ' . $branch . ' could not be found on any remote and appears to be unpushed';
$branchNotFoundError = true;
}
else {
// if first iteration found no remote branch but it has now found some, reset $unpushedChanges
// so we get the real diff output no matter its length
if ($branchNotFoundError) {
$unpushedChanges = null;
}
foreach ($remoteBranches as $remoteBranch) {
$command = [
'git',
'diff',
'--name-status',
$remoteBranch . '...' . $branch,
'--',
];
if (0 !== $this->process
->execute($command, $output, $path)) {
throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process
->getErrorOutput());
}
$output = trim($output);
// keep the shortest diff from all remote branches we compare against
if ($unpushedChanges === null || strlen($output) < strlen($unpushedChanges)) {
$unpushedChanges = $output;
}
}
}
// first pass and we found unpushed changes, fetch from all remotes to make sure we have up to date
// remotes and then try again as outdated remotes can sometimes cause false-positives
if ($unpushedChanges && $i === 0) {
$this->process
->execute([
'git',
'fetch',
'--all',
], $output, $path);
// update list of refs after fetching
$command = [
'git',
'show-ref',
'--head',
'-d',
];
if (0 !== $this->process
->execute($command, $output, $path)) {
throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process
->getErrorOutput());
}
$refs = trim($output);
}
// abort after first pass if we didn't find anything
if (!$unpushedChanges) {
break;
}
}
return $unpushedChanges;
}