Computing diffs¶
Git stores snapshots of files, not diffs: gitlib computes them, like
git diff.
Comparing two commits¶
Repository::getDiff($from, $to) returns the changes from the commit
$from to the commit $to. Each one is a revision name (a branch, a tag, a
hash, master~2...) or a Revision object (a Commit, a Branch...):
$diff = $repository->getDiff('0.1', 'master');
Like git diff 0.1 master, it compares the files of both commits. To compare
a branch with the commit it forked from, like git diff master...feature,
start from their best common ancestor:
$diff = $repository->getDiff($repository->getMergeBase('master', 'feature'), 'feature');
Calling getDiff() with one argument (a range like a..b or a...b, two
revisions in an array, or one revision to compare with the working tree)
is deprecated since 1.7: see UPGRADE-2.0.md.
Log::getDiff() is deprecated too: it ignored the paths of the log.
Other diffs¶
Commit::getDiff()returns the changes of a commit, against its first parent: see commit.md.WorkingCopy::getDiff($revision)returns the changes of the working tree, staged or not, since a revision (HEADby default).WorkingCopy::getDiffPending()returns the changes of the working tree not staged yet.WorkingCopy::getDiffStaged()returns the changes staged in the index.
See workingcopy.md for the working copy.
Every diff detects renames, like git diff -M. LibGit2Driver computes
the similarity of files like libgit2 does: it can find a rename where git
finds a deletion and a creation (see
Choosing a driver). During a merge
conflict, WorkingCopy::getDiffStaged() and getDiffPending() leave the
unmerged files out, and WorkingCopy::getDiff() compares them, conflict
markers included, to the revision, like git diff HEAD.
Iterating a diff¶
getFiles() returns the files of a diff, File objects:
$files = $diff->getFiles();
echo count($files).' files changed'.PHP_EOL;
foreach ($files as $file) {
echo $file->getName().': +'.$file->getAdditions().' -'.$file->getDeletions().PHP_EOL;
}
The File object¶
A File tells what happened to a file:
$file->isCreation(); // There is no old file
$file->isDeletion(); // There is no new file
$file->isModification(); // There are both: a rename, a copy or a mode change included
$file->isRename();
$file->isCopy(); // Only for a diff detecting copies, like "git diff -C"
$file->isModeChange(); // From "100644" to "100755", for example
$file->isBinary(); // A binary file has no changes
A type change, like a file replaced by a symbolic link, is a deletion followed by a creation, like git shows it.
Its names, modes and blob hashes:
$file->getName(); // The new name, or the old one for a deletion
$file->getOldName(); // null for a creation
$file->getNewName(); // null for a deletion
$file->getOldMode(); // "100644", "100755", "120000" (a symbolic link)... null for a creation
$file->getNewMode(); // null for a deletion
$file->getOldIndex(); // The hash of the old blob, null for a creation
$file->getNewIndex(); // The hash of the new blob, null for a deletion
getOldBlob() and getNewBlob() return the Blob objects, to read the
content of the file before and after (see blob.md):
if (!$file->isCreation() && !$file->isBinary()) {
$before = $file->getOldBlob()->getContent();
}
They throw a LogicException when there is no such file (getOldBlob()
for a creation, getNewBlob() for a deletion), or when the diff has no
repository. The diffs gitlib returns have one: see
Parsing a diff for the others. They throw a
RuntimeException when the diff doesn't know the hash of the blob.
Two cases have no blob in the repository, and reading the content of their
Blob throws a ReferenceNotFoundException:
- a submodule, of mode
160000: its hashes are commits of the submodule; - a file of the working tree not staged yet: read it from the working tree instead.
The number of lines added and deleted, and the changes themselves:
$file->getAdditions();
$file->getDeletions();
$file->getChanges(); // FileChange objects: see below
isChangeMode(), isDelete() and getAnchor() are deprecated since 1.7:
use isModeChange(), isDeletion() and substr($file->getNewIndex(), 0, 12).
The FileChange object¶
The changes of a file are its hunks, the blocks starting with @@ in a
patch. Each one is a FileChange object: getDiffLines() returns the lines
of the hunk, DiffLine objects:
use Gitonomy\Git\Diff\DiffLineType;
foreach ($file->getChanges() as $change) {
echo sprintf('@@ -%d,%d +%d,%d @@', $change->getRangeOldStart(), $change->getRangeOldCount(), $change->getRangeNewStart(), $change->getRangeNewCount()).PHP_EOL;
foreach ($change->getDiffLines() as $line) {
echo match ($line->getType()) {
DiffLineType::Addition => '+',
DiffLineType::Context => ' ',
DiffLineType::Deletion => '-',
}.$line->getContent().PHP_EOL;
}
}
getRangeOldStart()andgetRangeOldCount()are the first line of the hunk in the old file, and its number of lines there;getRangeNewStart()andgetRangeNewCount()are the same in the new file;DiffLine::getType()returns aDiffLineType:Addition,ContextorDeletion;DiffLine::getContent()returns the line, without its prefix (+,-or a space) nor its line break.
getAdditions() and getDeletions() count the lines the hunk adds and
deletes.
getLines(), which returns each line as an array ([$type, $content]), the
FileChange::LINE_ADD, LINE_CONTEXT and LINE_REMOVE constants, and
FileChange::getCount() are deprecated since 1.7: use getDiffLines(),
DiffLineType, and getAdditions() or getDeletions(). So is passing the
lines to the constructor of FileChange as arrays: pass DiffLine
objects.
Parsing a diff¶
Diff::parse() reads the output of git diff --raw --patch, with -z or
not. The raw information gives the names, modes, hashes and status of the
files: a patch without it throws an InvalidArgumentException. What the
parser doesn't know throws a RuntimeException, like the log of a submodule
that git diff writes instead of its patch, when the configuration sets
diff.submodule: pass --submodule=short. Pass the repository to read the
blobs of the files. Here, the output comes from Repository::run(), which
needs CliDriver:
use Gitonomy\Git\Diff\Diff;
$output = $repository->run('diff', ['--raw', '--patch', '--submodule=short', '0.1', 'master']);
$diff = Diff::parse($output, $repository);
getRawDiff() returns the output a diff was parsed from: for a diff gitlib
reads with CliDriver, the one of git diff --raw -z --patch, NUL bytes
included, with the options gitlib sets. It is empty for the diffs of
LibGit2Driver, which runs no git command.
Storing a diff¶
toArray() exports a diff as an array of scalars, to store it in a cache
or as JSON. Diff::fromArray() reads it back, with the repository to read
the blobs from:
use Gitonomy\Git\Diff\Diff;
$array = $diff->toArray();
$diff = Diff::fromArray($array, $repository);
The arrays of 1.x are read too.
Diff::setRepository(), File::setRepository() and File::addChange() are
deprecated since 1.7: pass the repository to Diff::parse() or
Diff::fromArray(), and the changes to the constructor of File.