in src/parser/ArcanistBundle.php [313:485]
public function toGitPatch() {
$this->reservedBytes = 0;
$eol = $this->getEOL('git');
$result = array();
$changes = $this->getChanges();
$binary_sources = array();
foreach ($changes as $change) {
if (!$this->isGitBinaryChange($change)) {
continue;
}
$type = $change->getType();
if ($type == ArcanistDiffChangeType::TYPE_MOVE_AWAY ||
$type == ArcanistDiffChangeType::TYPE_COPY_AWAY ||
$type == ArcanistDiffChangeType::TYPE_MULTICOPY) {
foreach ($change->getAwayPaths() as $path) {
$binary_sources[$path] = $change;
}
}
}
foreach (array_keys($changes) as $multicopy_key) {
$multicopy_change = $changes[$multicopy_key];
$type = $multicopy_change->getType();
if ($type != ArcanistDiffChangeType::TYPE_MULTICOPY) {
continue;
}
// Decompose MULTICOPY into one MOVE_HERE and several COPY_HERE because
// we need more information than we have in order to build a delete patch
// and represent it as a bunch of COPY_HERE plus a delete. For details,
// see T419.
// Basically, MULTICOPY means there are 2 or more corresponding COPY_HERE
// changes, so find one of them arbitrarily and turn it into a MOVE_HERE.
// TODO: We might be able to do this more cleanly after T230 is resolved.
$decompose_okay = false;
foreach ($changes as $change_key => $change) {
if ($change->getType() != ArcanistDiffChangeType::TYPE_COPY_HERE) {
continue;
}
if ($change->getOldPath() != $multicopy_change->getCurrentPath()) {
continue;
}
$decompose_okay = true;
$change = clone $change;
$change->setType(ArcanistDiffChangeType::TYPE_MOVE_HERE);
$changes[$change_key] = $change;
// The multicopy is now fully represented by MOVE_HERE plus one or more
// COPY_HERE, so throw it away.
unset($changes[$multicopy_key]);
break;
}
if (!$decompose_okay) {
throw new Exception(
pht(
'Failed to decompose multicopy changeset in '.
'order to generate diff.'));
}
}
foreach ($changes as $change) {
$type = $change->getType();
$file_type = $change->getFileType();
if ($file_type == ArcanistDiffChangeType::FILE_DIRECTORY) {
// TODO: We should raise a FYI about this, so the user is aware
// that we omitted it, if the directory is empty or has permissions
// which git can't represent.
// Git doesn't support empty directories, so we simply ignore them. If
// the directory is nonempty, 'git apply' will create it when processing
// the changesets for files inside it.
continue;
}
if ($type == ArcanistDiffChangeType::TYPE_MOVE_AWAY) {
// Git will apply this in the corresponding MOVE_HERE.
continue;
}
$old_mode = idx($change->getOldProperties(), 'unix:filemode', '100644');
$new_mode = idx($change->getNewProperties(), 'unix:filemode', '100644');
$is_binary = $this->isGitBinaryChange($change);
if ($is_binary) {
$old_binary = idx($binary_sources, $this->getCurrentPath($change));
$change_body = $this->buildBinaryChange($change, $old_binary);
} else {
$change_body = $this->buildHunkChanges($change->getHunks(), $eol);
}
if ($type == ArcanistDiffChangeType::TYPE_COPY_AWAY) {
// TODO: This is only relevant when patching old Differential diffs
// which were created prior to arc pruning TYPE_COPY_AWAY for files
// with no modifications.
if (!strlen($change_body) && ($old_mode == $new_mode)) {
continue;
}
}
$old_path = $this->getOldPath($change);
$cur_path = $this->getCurrentPath($change);
if ($old_path === null) {
$old_index = 'a/'.$cur_path;
$old_target = '/dev/null';
} else {
$old_index = 'a/'.$old_path;
$old_target = 'a/'.$old_path;
}
if ($cur_path === null) {
$cur_index = 'b/'.$old_path;
$cur_target = '/dev/null';
} else {
$cur_index = 'b/'.$cur_path;
$cur_target = 'b/'.$cur_path;
}
$old_target = $this->encodeGitTargetPath($old_target);
$cur_target = $this->encodeGitTargetPath($cur_target);
$result[] = "diff --git {$old_index} {$cur_index}".$eol;
if ($type == ArcanistDiffChangeType::TYPE_ADD) {
$result[] = "new file mode {$new_mode}".$eol;
}
if ($type == ArcanistDiffChangeType::TYPE_COPY_HERE ||
$type == ArcanistDiffChangeType::TYPE_MOVE_HERE ||
$type == ArcanistDiffChangeType::TYPE_COPY_AWAY ||
$type == ArcanistDiffChangeType::TYPE_CHANGE) {
if ($old_mode !== $new_mode) {
$result[] = "old mode {$old_mode}".$eol;
$result[] = "new mode {$new_mode}".$eol;
}
}
if ($type == ArcanistDiffChangeType::TYPE_COPY_HERE) {
$result[] = "copy from {$old_path}".$eol;
$result[] = "copy to {$cur_path}".$eol;
} else if ($type == ArcanistDiffChangeType::TYPE_MOVE_HERE) {
$result[] = "rename from {$old_path}".$eol;
$result[] = "rename to {$cur_path}".$eol;
} else if ($type == ArcanistDiffChangeType::TYPE_DELETE ||
$type == ArcanistDiffChangeType::TYPE_MULTICOPY) {
$old_mode = idx($change->getOldProperties(), 'unix:filemode');
if ($old_mode) {
$result[] = "deleted file mode {$old_mode}".$eol;
}
}
if ($change_body) {
if (!$is_binary) {
$result[] = "--- {$old_target}".$eol;
$result[] = "+++ {$cur_target}".$eol;
}
$result[] = $change_body;
}
}
$diff = implode('', $result).$eol;
return $this->convertNonUTF8Diff($diff);
}