UPGRADE FROM 1.x to 2.0¶
gitlib 2.0 runs every git operation through a driver: CliDriver, the
default, runs the git binary like 1.x did, and the experimental
LibGit2Driver runs libgit2 in-process (see
"Choosing a driver").
Most of the API of 1.x that 2.0 replaces keeps working, deprecated: each
call triggers a deprecation (E_USER_DEPRECATED, through
symfony/deprecation-contracts) naming its replacement, and 3.0 removes
them. Log the deprecations of your application to find them. The other
changes, listed below too, break code that relies on them.
Upgrade to 1.7 first, which requires PHP 8.1: it has most of the replacements, and triggers the deprecations of the API they replace (see Supporting 1.x and 2.x).
Requirements¶
- PHP 8.4 or later is required.
- git 1.8.4 or later is required by
CliDriver, the default driver: before, git simplifies the log of a range filtered by path differently, and it can't count commits (Log::count()) before 1.7.2. symfony/processmust be^6.4.33,^7.4.5or^8.1: the older versions escape the arguments wrongly on Windows (CVE-2024-51736, CVE-2026-24739).symfony/string^7.4.6or^8.0.6,symfony/deprecation-contracts^2.5or^3, andpsr/log^1.1,^2.0or^3.0are new dependencies.- Repositories using SHA-256 hashes are refused, with an
UnsupportedOperationException, by both drivers.
Supporting 1.x and 2.x¶
gitlib 1.7 has the API of 2.0 that can be added without breaking 1.x, and
deprecates what 2.0 replaces or breaks: new Repository($dir, $workingDir,
$driver) with a CliDriver of the same arguments, the static methods of
Repository creating repositories, Diff::parse($rawDiff, $repository),
Repository::getDiff($from, $to), FileChange::getDiffLines(), iterating
a Tree, Log::withLimit(), Branch::isMergedInto(),
Blame::getHunks()... Fix the deprecations of 1.7, then require
^1.7 || ^2.0: a library supporting PHP 8.1 to 8.3 gets 1.7 there, and 2.0
from PHP 8.4, with the same code.
Before:
$repository = new Repository($gitDir, ['working_dir' => $workingDir, 'logger' => $logger]);
$diff = Diff::parse($rawDiff);
$diff->setRepository($repository);
After, with 1.7 and 2.0:
use Gitonomy\Git\Driver\Cli\CliDriver;
$repository = new Repository($gitDir, $workingDir, new CliDriver(logger: $logger));
$diff = Diff::parse($rawDiff, $repository);
1.7 breaks nothing of 1.x, so:
- Pass by position the arguments whose name changes in 2.0 (see
Extending gitlib): 1.7 keeps the names of 1.x. So
do the arguments 1.7 adds to
Repository::getDiff(),getBlame(),Diff::parse(),Diff::fromArray()andFile::fromArray(): their signature leaves them out, not to break the classes overriding these methods. ReferenceBag::createBranch()andcreateTag()keep a revision name as the hash of the reference they return, like 1.x: pass aRevisionto get the hash of its commit with both versions.- The submodules of trees are
CommitReferenceobjects, which extendSubmodulein 1.7: check them withinstanceof Submodule. File::toArray()has nois_copykey in 1.7.
What 1.7 can't bring, as it would break 1.x, behaves differently with both versions:
- With a range of lines,
Blame::getLines()andgetLine()number the lines from 1 in 1.7, like the file in 2.0: useLine::getNumber(), orBlame::getHunks(), numbered like the file in 1.7 too. - 1.7 doesn't read the references again after a write: open a new
Repositoryto read them, with both versions.getReferences(true)is deprecated in 1.7 too. - The classes
finalin 2.0 are only@finalin 1.7: see Testing code using gitlib. - The other changes listed below: dates, the stricter parser of diffs, the directory git runs in, the exceptions...
Opening a repository¶
- The second argument of
Repositoryis the working directory, when the git directory is kept apart from it. An array of options there is deprecated: itsworking_diroption is this argument, and itslogger,command,environment_variablesandprocess_timeoutoptions configure aGitonomy\Git\Driver\Cli\CliDriver, whichRepositorytakes as its last argument. They can't be combined with this argument. -
The
inherit_environment_variablesoption has no counterpart inCliDriver: git inherits the environment variables of PHP anyway, and$_SERVERholds, under php-fpm, the headers and the credentials of the request. Pass the variables git needs to theenvironmentVariablesargument instead.git inherits
$_ENV, and the variables ofgetenv()that$_SERVERholds too, like Symfony Process passes them: a variable an application only writes in$_SERVER, withoutputenv()nor$_ENV, which the option passed, no longer reaches git. A command line application relying on it passes$_SERVERitself:$driver = new CliDriver(environmentVariables: array_filter($_SERVER, is_string(...)));Before:
$repository = new Repository($path, [ 'command' => '/usr/bin/git', 'logger' => $logger, 'process_timeout' => 60, ]);After:
use Gitonomy\Git\Driver\Cli\CliDriver; use Gitonomy\Git\Repository; $repository = new Repository($path, driver: new CliDriver(command: '/usr/bin/git', processTimeout: 60, logger: $logger)); -
The logger is an argument of
CliDriver, which logs every git command it runs, cloning and initializing included:Repository::setLogger()andRepository::getLogger()are deprecated.LibGit2Driverruns no command, and logs nothing. - An unknown option throws an
InvalidArgumentException, instead of being ignored. - The
debugoption is deprecated, and setting it tofalsethrows anInvalidArgumentException: see Errors. $pathmay be a linked worktree, a submodule or a clone with--separate-git-dir, whose.gitis a file.- A directory that isn't a repository throws an
InvalidArgumentExceptionright away, instead of aProcessExceptionat the first operation.
Creating repositories¶
-
Adminis deprecated: use the static methods ofRepository. They take the URL before the path, have a method for each kind of repository instead of the$bareargument, whose default was a bare repository, and take the driver as last argument:1.x 2.0 Admin::init($path)Repository::initBare($path)Admin::init($path, false)Repository::init($path)Admin::cloneTo($path, $url)Repository::cloneBareFrom($url, $path)Admin::cloneTo($path, $url, false)Repository::cloneFrom($url, $path)Admin::cloneBranchTo($path, $url, $branch)Repository::cloneBareFrom($url, $path, $branch)Admin::cloneBranchTo($path, $url, $branch, false)Repository::cloneFrom($url, $path, $branch)Admin::mirrorTo($path, $url)Repository::mirrorFrom($url, $path)Admin::isValidRepository($url)Repository::isValidRepository($url)Admin::isValidRepositoryAndBranch($url, $branch)Repository::isValidRepositoryAndBranch($url, $branch)$repository->cloneTo($path)Repository::cloneBareFrom($repository->getGitDir(), $path)$repository->cloneTo($path, false)Repository::cloneFrom($repository->getGitDir(), $path)Until 3.0,
Adminkeeps working like in 1.x, withCliDriverand the options of 1.x. -
isValidRepositoryAndBranch()checks the exact name of the branch:releaseno longer matchesfeature/release, norrel*release. -
isValidRepository()andisValidRepositoryAndBranch()read a relative path as a path, never as the name of a remote of PHP's working directory, and throw aDriverExceptionwhen git can't start, instead of returningfalse.Before:
$repository = Admin::cloneTo($path, $url, false, ['process_timeout' => 60]);After:
use Gitonomy\Git\Driver\Cli\CliDriver; use Gitonomy\Git\Repository; $repository = Repository::cloneFrom($url, $path, driver: new CliDriver(processTimeout: 60)); -
Repository::cloneTo()cloned with the driver of the repository: pass it tocloneFrom()orcloneBareFrom()as well. Admin::cloneRepository()is deprecated, with no replacement for the arguments ofgit cloneother than--bare,--branchand--mirror: rungit cloneyourself, then open the repository withnew Repository($path).
References¶
Repository::getReferences()reads the references again after every write gitlib makes (creating or deleting a reference, a checkout, a merge,run()...): passingtrueto read them again is deprecated. Open a newRepositoryto see the writes of other processes. AReferenceobject keeps the commit it pointed to when it was read.Reference::getCommitHash()returns the commit an annotated tag points to, instead of the hash of the tag object:ReferenceBag::resolve(),resolveTags()andCommit::resolveReferences()find annotated tags.ReferenceBag::createBranch()andcreateTag()throw anInvalidArgumentExceptionwhen the reference exists, instead of moving it, and take aRevisionor a revision name as second argument, instead of a commit hash only.-
ReferenceBag::update()is deprecated: useBranch::moveTo().Before:
$repository->getReferences()->update(new Branch($repository, 'refs/heads/main', $hash));After:
$repository->getReferences()->getBranch('main')->moveTo($hash); -
Branch::isMergedTo()is deprecated: useisMergedInto(), which takes the destination as aRevisionor a revision name, without a default ('master'before) nor$compareOnlyWithRemote. -
Passing arguments to
Commit::getIncludingBranches()is deprecated: it returns the local and remote branches, whichBranch::isLocal()andisRemote()filter.Before:
$branches = $commit->getIncludingBranches(true, false);After:
use Gitonomy\Git\Reference\Branch; $branches = array_filter($commit->getIncludingBranches(), static fn (Branch $branch): bool => $branch->isLocal()); -
ReferenceBag::getFirstBranch()returnsnullinstead offalsewhen there is no branch, and only branches. Repository::getHead()returns aReference\Branchor aCommit.HEADpointing to another reference, like a tag, which git allows, throws aReferenceNotFoundException, where 1.x returned the reference:getHeadCommit()still returns its commit.- The getters of
Reference\Tag(getTaggerName(),getTaggerEmail(),getTaggerDate(),getMessage(),getSubjectMessage(),getBodyMessage(),getGpgSignature()) returnnullinstead offalsefor a lightweight tag. The tagger isnullfor an annotated tag without one too, where gitlib threw.getGpgSignature()returnsnullfor an unsigned tag instead of throwing, and finds X.509 and SSH signatures too. - A branch whose name ends with
HEADis listed: only theHEADof a remote is left out. - Moving or deleting a symbolic reference, like
refs/remotes/origin/HEAD, changes it, instead of the branch it points to. ReferenceBag::delete()throws anInvalidArgumentExceptionfor a name outsiderefs/:HEAD, whichCliDriverdeleted, leaving no repository, or a short name, likemaster, which did nothing.
History¶
Log::setOffset()andsetLimit()are deprecated:withOffset()andwithLimit()return anotherLog.count($log)returns the number of commits the log iterates, after its offset and up to its limit;countCommits()still counts them all.- A negative offset or limit throws an
InvalidArgumentException. - The revisions of a log are names, ranges (
a..b,a...b), names to exclude (^a) or--all: any other option ofgit logthrows anInvalidArgumentException, instead of being passed to git. An unknown revision throws aReferenceNotFoundExceptionnaming it. Log::getDiff()is deprecated: it ignored the paths of the log. UseRepository::getDiff().
Diffs¶
-
Repository::getDiff()takes the commits to compare:$fromand$to. Calling it with one argument is deprecated.1.x 2.0 $repository->getDiff('a..b')$repository->getDiff('a', 'b')$repository->getDiff(['a', 'b'])$repository->getDiff('a', 'b')$repository->getDiff('a...b')$repository->getDiff($repository->getMergeBase('a', 'b'), 'b')$repository->getDiff('a')$repository->getWorkingCopy()->getDiff('a') -
The diff of a merge commit,
Commit::getDiff(), is against its first parent, like GitHub shows it, with both drivers. Diff,Diff\FileandDiff\FileChangeget their repository and their changes when constructed:Diff::setRepository(),File::setRepository()andFile::addChange()are deprecated.Diff::parse()andfromArray()take the repository to read blobs from.-
FileChange::getDiffLines()returns the lines of a hunk asDiff\DiffLineobjects, withgetType(), aDiff\DiffLineType, andgetContent():FileChange::getLines(), which returns them as arrays, is deprecated, as theFileChange::LINE_*constants andFileChange::getCount(), replaced bygetAdditions()andgetDeletions(). The constructor ofFileChangetakesDiffLineobjects: arrays are deprecated.Before:
foreach ($change->getLines() as [$type, $content]) { if (FileChange::LINE_ADD === $type) { // ... } }After, with 1.7 and 2.0:
use Gitonomy\Git\Diff\DiffLineType; foreach ($change->getDiffLines() as $line) { if (DiffLineType::Addition === $line->getType()) { // ... $line->getContent() } } -
File::isChangeMode(),isDelete()andgetAnchor()are deprecated: useisModeChange(),isDeletion(), andsubstr($file->getNewIndex(), 0, 12). File::getOldIndex()andgetNewIndex()returnnullfor a missing hash, never an empty string;getName()returns astring.File::getOldBlob()andgetNewBlob()throw aLogicExceptionwithout repository, and aRuntimeExceptionwithout hash.Diff::parse()throws anInvalidArgumentExceptionfor a diff without its raw information, deprecated since 1.6.0: parse the output ofgit diff --raw --patch, with-zor not. It reads the names of the files from the raw information: names with spaces are no longer split at the wrong place.- It reads the status of a file, a creation, a modification, a deletion,
a rename..., from its raw line (
A,M,D,R100...), instead of the headers of the patch (new file mode...): a diff written by hand where they disagree gives another result. Like git, the raw line separates the status and the name with a TAB: spaces throw aRuntimeException. Generate the diffs of your tests with git, withgit diff --raw --patch --full-index, instead of writing them. - During a merge conflict,
WorkingCopy::getDiffPending()andgetDiffStaged()leave the unmerged files out, where they threw, andWorkingCopy::getDiff()compares them, conflict markers included, to the revision, likegit diff HEAD.
Blame¶
Repository::getBlame()takes the first and the last lines to blame: a range as a string ('3,5','3,+3') is deprecated, and a regular expression is no longer supported.Blame::getLines()andgetLine()number the lines like the file, from the first blamed line, instead of from 1.Blame\Line::getNumber()andgetOriginalNumber()return the number of the line in the file and in the commit that changed it, as integers:Line::getLine()is deprecated.Blame::getGroupedLines()is deprecated:getHunks()returnsBlame\Hunkobjects.
Trees¶
-
Iterating a
Treegives its entries by name,TreeEntryobjects withgetName(),getMode()andgetObject();getBlobs(),getTrees()andgetSubmodules()return them by kind.getEntries(),getBlobEntries()andgetTreeEntries(), which return them as arrays ([$mode, $object]), are deprecated.Before:
foreach ($tree->getEntries() as $name => [$mode, $object]) { // ... }After, with 1.7 and 2.0:
foreach ($tree as $name => $entry) { // ... $entry->getMode(), $entry->getObject() } -
CommitReferenceis deprecated: it isSubmodule. So isTree::getCommitReferenceEntries(): usegetSubmodules(). Tree::resolvePath()throws anInvalidArgumentExceptionexplaining why a path doesn't resolve, a path through a submodule included, which threw anUnexpectedValueException, and resolves/to the tree.
Working copy¶
WorkingCopy::checkout()takes aRevisionor a revision name: a localBranchobject attachesHEADto the branch, instead of detaching it. Its second argument, a branch to create, is deprecated: usecheckoutNewBranch($branch, $startPoint).-
WorkingCopy::checkout()throws aReferenceNotFoundExceptionfor a name that no revision has, with both drivers: the name of a remote branch alone, likefeaturefororigin/feature, created the local branch, and a path, likeREADME.md, discarded its changes.Before:
$workingCopy->checkout('feature');After:
$workingCopy->checkoutNewBranch('feature', 'origin/feature');
Commits, trees and blobs¶
Repository::getCommit()takes a full hash.getTree()andgetBlob()take a hash, abbreviated or not, instead of any revision: usegetRevision($name)->getCommit()for a name.- Reading an object of another type throws a
ReferenceNotFoundException("The object ... is not a tree."):getTree()read the tree of a commit, andgetBlob()threw aProcessException. - The subject of a commit or a tag is its first paragraph, on one line, and
its body what follows, like
git log --format=%sand%b: a message without an empty line after its first line has no body, where its second line was lost. Commit::getShortMessage()andgetFixedShortHash()are deprecated: usesymfony/string(u($commit->getSubjectMessage())->truncate(50, '...')) andsubstr().Blob::getMimetype()returns the mimetype alone (text/plain, instead oftext/plain; charset=us-ascii), and throws instead of returningfalse.Repositorykeeps the objects it returns only while your application uses them: memory no longer grows with every commit read.
Errors¶
-
A failing git operation always throws, a
Gitonomy\Git\Exception\ProcessExceptionwithCliDriver. Nothing returnsnullor empty data instead anymore:Repository::run()returns astring, andRepository::getHead()andRepository::getHeadCommit()never returnnull.Before:
$repository = new Repository($path, ['debug' => false]); if (null === $output = $repository->run('fetch', ['--all'])) { // handle the failure }After:
$repository = new Repository($path); try { $output = $repository->run('fetch', ['--all']); } catch (ProcessException $e) { // handle the failure } -
Failures are no longer logged at the
errorlevel: the exception reports them, and the logger gets their error output at thedebuglevel. Alerts based onerrorlogs must rely on exceptions instead. - A missing revision, reference or object throws a
ReferenceNotFoundException, with both drivers: it extendsGitonomy\Git\Exception\InvalidArgumentException, still an\InvalidArgumentException, and takes a whole message instead of a name. - A timeout throws a
Gitonomy\Git\Exception\ProcessTimedOutException, and the other failures of Symfony Process aDriverException: no exception of Symfony or of PHP leaks anymore, not even as the previous exception, as they hold the command line. ProcessExceptionextends the newGitonomy\Git\Exception\DriverException, itself aRuntimeException: catchingRuntimeExceptionorGitExceptionInterfacestill works.- An unknown revision throws a
ReferenceNotFoundException, anInvalidArgumentException, whereRepository::getDiff(),getBlame()orWorkingCopy::checkout()threw aProcessException: acatch (RuntimeException)doesn't catch it anymore. CatchGitExceptionInterface, which every exception of gitlib implements. - The message of a
ProcessExceptionis one line, with the error of git: the first line of its error output starting withfatal:orerror:, after the warnings, or its first line:The git command "clone" failed (exit 128): fatal: ..., orThe git command "diff" failed (exit 1).when git writes no error, but for the conflict of a merge, which git writes on its output (CONFLICT (content): ...). The command line, the rest of the error output and the output are no longer in the message:getErrorOutput()andgetOutput()return the outputs of git. Its constructor takes the git command as second argument, optional, and reads the exit code and the outputs of theProcess: a test double ofProcessmust return them. - When git can't start, a missing binary for instance,
CliDriverthrows aDriverExceptioninstead of aProcessException, with a message of one line, without the command line:The git command "clone" could not start: .... - These methods throw instead of returning a fallback value:
Commit::getIncludingBranches()threw nothing and returned[]when git failed;ReferenceBag(Repository::getReferences()) only returns an empty bag for a repository without references, and throws for any other failure.
- gitlib no longer throws
Gitonomy\Git\Exception\UnexpectedValueException: catchInvalidArgumentExceptionforTree::resolvePath(). Commit::getShortHash()throws aReferenceNotFoundExceptionfor an unknown commit, instead of aProcessException.Hooksthrows aRuntimeExceptionwhen a hook exists already, or can't be written, instead of aLogicExceptionor a silent failure, and creates the hooks directory.remove()of a hook that doesn't exist throws anInvalidArgumentException, likeget(), still aLogicException.- The
Adminmethods throw aProcessException, still aRuntimeException, with the first line of git's error output in its message, or aDriverExceptionwhen git can't start. - An argument holding a NUL byte throws an
InvalidArgumentException, where PHP threw aValueError: git and libgit2 read their arguments as C strings, which end at the first NUL byte. - A revision, a reference, a hash or a URL starting with a dash throws an
InvalidArgumentException, with both drivers: git read it as an option,getRevision('--output=/path')writing a file, orisValidRepository('--upload-pack=...')running a command. A path may start with a dash. - An empty URL throws an
InvalidArgumentException, with both drivers. - An
ext::URL throws anInvalidArgumentException, with both drivers: git runs its command, by default before git 2.12. So does anfd::URL, which makes git talk through a file descriptor it inherits from PHP. - An ssh URL whose host or user starts with a dash, even encoded (
%2D), likessh://-oProxyCommand=.../repositoryorssh://-oProxyCommand=...@host/path, or agit://URL whose host does, throws anInvalidArgumentException, with both drivers: git before 2.14.1 passes it to ssh, or to the command ofcore.gitProxy, which reads it as an option. Hooksthrows anInvalidArgumentExceptionfor a name that isn't the one of a file of the hooks directory, like../configor.hidden.- The messages of exceptions, their traces (
#[\SensitiveParameter]) and the logs hide the credentials of URLs (https://***@example.com/repository.git).getErrorOutput()andgetOutput()ofProcessExceptiondon't. - Every message is a sentence ending with a dot.
Git configuration¶
CliDriversets back to git's defaults the configuration of the user that changes what git prints, what a read returns, or the upstream of a new branch: colors, columns,branch.autoSetupMerge,branch.autoSetupRebase,branch.sort,log.follow,log.showSignature, and for diffsdiff.algorithm,diff.context,diff.external,diff.orderFile,diff.submodule, the prefixes, the textconv filters... Before, they made reads throw or return other data: a log of a path following its renames withlog.follow, for instance. See "Git configuration".CliDriverignores the replacements of objects (git replace,refs/replace/), likeLibGit2Driver:run()andrunProcess()too. Run git yourself to read them.CliDriverignorescore.fsmonitor, whose command git runs to read the working tree:run()andrunProcess()too.
Dates¶
Commit::getAuthorDate(),Commit::getCommitterDate()andReference\Tag::getTaggerDate()return a\DateTimeImmutableinstead of a\DateTime. Code that modifies the returned date must use the returned value ofmodify(),setTimezone()...-
These dates are at the UTC offset of their author, committer or tagger, like
git logshows them (2016-08-17T12:00:00+02:00), instead of in UTC. The instant doesn't change: comparisons andgetTimestamp()return the same results, butformat()returns the local time of the author, committer or tagger. To keep UTC, convert the date:Before:
$date = $commit->getAuthorDate()->format('Y-m-d H:i');After:
$date = $commit->getAuthorDate()->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i');
Extending gitlib¶
- These classes are
final:Admin,Blame,Blob,Commit,Diff\File,Log,Parser\DiffParser,PushReference,Reference\Branch,Reference\Stash,Reference\Tag,ReferenceBag,Repository,Tree, and the exceptionsLogicException,ProcessException,ReferenceNotFoundExceptionandUnexpectedValueException. - These classes are
final readonly:Blame\Line,Diff\Diff,Diff\FileChange,Hooks,RevisionList,WorkingCopy. - Every method has native parameter and return types. A class extending
Parser\ParserBase, which remains extendable, must declare the same types, and must not redeclare its properties. - The constructors of
Commit,Tree,Blob,Revision,Reference,Reference\TagandReferenceBagare internal: get these objects fromRepository(getCommit(),getTree(),getBlob(),getRevision(),getReferences()), which returns the same commit, tree or blob while your application uses it, and reads the references again after its writes. The constructor ofReferencerequires the commit hash. ExtendingRevisionorReferenceisn't supported anymore, and won't be possible in 3.0:ReferencedeclaresgetName(), abstract. - With named arguments, some parameters are renamed:
Repository::__construct()$options($workingDir),getBlame()$lineRange($firstLine),getDiff()$revisions($from),ReferenceBag::createBranch()andcreateTag()$commitHash($revision),resolve(),resolveBranches()andresolveTags()$hash($commit), andPushReference::__construct()$reference($fullname). - To change how gitlib runs git, for example in tests, implement
Gitonomy\Git\Driver\DriverInterfaceand pass it as the driver ofRepository, instead of extendingRepository: see "Using your own driver". gitlib may add methods to the interfaces of the drivers in a minor version of 2.x. Commit::__construct()takes aGitonomy\Git\Driver\Data\CommitDatainstead of an array, andCommit::setData()is internal.Gitonomy\Git\Util\StringHelperis removed, with its configurable encoding: usesymfony/string. Logs are always read in UTF-8.- The parsers of the git output moved to
Gitonomy\Git\Driver\Cli\Parser, and arefinaland internal, used byCliDriveronly, exceptParser\DiffParserandParser\ParserBase.ParserBaselosesconsumeGPGSignature()andconsumeMergeTag(), and getsconsumeNameEmailDate(),parseSignature()andsignatureDate().CommitParseris merged intoLogParser, and their results are objects ofGitonomy\Git\Driver\Data:LogParser::$commits(instead of$log),TagParser::$data,ReferenceParser::$references,TreeParser::$entriesandBlameParser::$lines.TreeParserparsesgit ls-tree -z, andBlameParserno longer takes aRepository.ParserBase::parse()takes astring. - Objects serialized with 1.x (a
Commitor aDiffin a cache...) can't be unserialized: clear such caches.Diff::toArray()andDiff::fromArray()remain compatible.
Testing code using gitlib¶
The classes final in 2.0 can't be doubled: createMock() of PHPUnit,
Prophecy and Mockery refuse them ("Class "Gitonomy\Git\WorkingCopy" is
declared "final" and cannot be doubled", "could not reflect class
Gitonomy\Git\WorkingCopy as it is marked final"). Instead, from the
simplest:
- Construct the value objects:
Diff,Diff\File,Diff\FileChangeandDiff\DiffLinehave public constructors.new Diff([new File(...)], '')works with 1.x too. - Parse the real output of git:
Diff::parse()ofgit diff --raw --patch --full-index(see Diffs). - Use a real repository, in a temporary directory:
Repository::init($path), orgit initwith 1.x before 1.7. AWorkingCopyneeds one. - Implement
Gitonomy\Git\Driver\DriverInterface, and pass it as the driver ofRepository: see "Using your own driver".
Data returned¶
- File names in diffs (
Diff\File) and tree entries (Tree) are returned in UTF-8, instead of git's quoted form ("caf\303\251.txt") for names with non-ASCII or special characters. Blame\Line::getContent()no longer starts with a TAB.Commit::getDiff()lists the files of a root commit, instead of returning an empty diff.- A commit read through
Repository::getCommit()has its message asgit logprints it, like a commit of aLogalready had: without blank lines around it, nor whitespace at the end of its lines. It also comes re-encoded to UTF-8 when the commit has anencodingheader, instead of throwing. Reference\Tag::isSigned()returnsfalsefor a lightweight tag.PushReference::isForce()returnstruefor histories without a common ancestor, where the constructor threw.PushReferenceruns git whenisForce()needs it, not when constructed.getReference(),getRevision(),isCreate()andisDelete()are deprecated: usegetFullname(),getRange(),isCreation()andisDeletion(), named like the ones ofDiff\File. The$referenceargument of its constructor is named$fullname.Blob::getContent()only reads blobs, wheregit cat-file -palso printed trees and commits.WorkingCopy::getUntrackedFiles()andRevisionList::getAll()return lists, without gaps in their keys.Diff\File::toArray()has a newis_copykey.Diff::getRawDiff()of a diff gitlib reads returns the output ofgit diff --raw -z --patch, NUL bytes included, with the options gitlib sets, instead of the text of--raw --patch; withLibGit2Driver, an empty string.
Other changes¶
-
Repository::run()andrunProcess()run git in the working directory of the repository, or its git directory when it is bare, instead of the current directory of PHP. Relative paths, in the arguments ofrun()and in the paths of a log, are relative to the repository. When PHP runs in a subdirectory of the repository, the output of the commands relative to the current directory changes too, and so does their default scope:run()inrepository/sub1.x 2.0 ls-filesin-sub.txtroot.txt,sub/in-sub.txtstatus --shortM ../root.txtM root.txtrev-parse --show-prefixsub/(empty) Nothing changes when PHP runs out of the repository. *
Hooks::set()makes hooks executable by their owner only (0700, instead of0777): change their permissions if another user runs them. *Repository::shell()is deprecated: it ignored the driver. Rungit shellwith Symfony Process instead. *Hooks::has()returnstruefor a symbolic link to a missing file:get()throws aRuntimeException, as it can't read it,set()andsetSymlink()refuse it, as a hook that exists already, andremove()removes the link. *Repository::getSize()is deprecated:getSizeInBytes()returns bytes, instead of kilobytes. Neither follows links: a link in the git directory, to a hook not deployed yet for instance, counts for nothing, wheregetSize()counted the file it points to, and threw for a missing one.