Changelog¶
All notable changes to this project are documented in this file.
Unreleased¶
Changes since v1.6.0. To upgrade, see UPGRADE-2.0.md: most of the API of 1.x that 2.0 replaces keeps working, deprecated until 3.0.
Breaking changes¶
- Run every git operation through a driver (
Gitonomy\Git\Driver): the domain objects call aDriver\RepositoryDriverInterface, typed (hashes, enums, value objects ofDriver\Data), instead of building processes and parsing their output. A failing operation always throws, a missing revision or object aReferenceNotFoundException, a timeout aProcessTimedOutException; dates are\DateTimeImmutable; names in diffs and trees are UTF-8; references are read again after every write gitlib makes, and peel annotated tags; the diff of a merge commit is against its first parent; blame lines and logs are typed objects; the subject of a message is its first paragraph. See UPGRADE-2.0.md for every change. (#253) - Return the dates of commits and tags at the UTC offset of their author, committer or tagger,
like
git logshows them, instead of in UTC: the instant doesn't change, butformat()returns their local time. (#254) - Require PHP 8.4 or later, Symfony 7.4/8.1+: most classes are
final(somereadonly), and every method has native types. (#238) - Remove
Gitonomy\Git\Util\StringHelper, replaced bysymfony/string, and its configurable encoding: gitlib reads everything in UTF-8. (#252) - Require git 1.8.4 or later, the oldest version the test suite passes with: it simplifies the
log of a range filtered by path like the current versions, and
Log::count()needs git 1.7.2. (#253) Diff::parse()throws for a diff without its raw information, deprecated since 1.6.0 (#229): parse the output ofgit diff --raw --patch. (#253)- The constructors of
Commit,Tree,Blob,Revision, the references andReferenceBagare internal: get these objects fromRepository, which returns the same commit, tree or blob while the application uses it, and reads the references again after its writes. ExtendingRevisionorReferenceisn't supported anymore. (#254) ProcessExceptionhas a message of one line, with the error of git, the first line of its error output starting withfatal:orerror:, after the warnings:The git command "clone" failed (exit 128): fatal: .... 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. (#254)Tree::resolvePath()throws anInvalidArgumentExceptionfor a path through a submodule, instead of anException\UnexpectedValueException, which gitlib no longer throws. (#254)- 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: .... (#254) 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. UsecheckoutNewBranch(). (#254)
Features¶
- Add an experimental
Driver\Ffi\LibGit2Driver, running libgit2 1.9 in-process through PHP's FFI extension, as an alternative to the defaultDriver\Cli\CliDriver: pass it as the driver ofRepository. It avoids spawning agitprocess for every operation; see "Choosing a driver" for its requirements, its differences withCliDriver, and when it is faster.benchmark.php, from a clone of gitlib, compares both drivers on a repository. With php-fpm,LibGit2Driver::preload()keeps libgit2 loaded fromopcache.preload. (#253) Repositorytakes the working directory and the driver as arguments: aDriver\Cli\CliDriverby default, configured through its constructor (git binary, environment, timeout and logger, which gets every command, clones included), or anyDriver\DriverInterface, to replace it in tests; see "Using your own driver". The driver finds the repository: linked worktrees, submodules, clones with--separate-git-dirand reftable repositories (withCliDriver) open, and a directory that isn't a repository throws right away. (#253, #261, fixes #200)- Add static methods to
Repositorycreating repositories,init(),initBare(),cloneFrom(),cloneBareFrom()andmirrorFrom(), and checking a repository URL,isValidRepository()andisValidRepositoryAndBranch(); see "Create and access git repositories". (#253) - Add
Repository::getDiff($from, $to),Repository::getMergeBase(),Repository::getSizeInBytes()andWorkingCopy::getDiff(),WorkingCopy::checkoutNewBranch(),Branch::moveTo()andBranch::isMergedInto(),Log::withOffset()andwithLimit(),Blame::getHunks()and the line numbers ofBlame\Line,FileChange::getDiffLines(), which returnsDiff\DiffLineobjects typed byDiff\DiffLineType, iterating aTree, which givesTreeEntryobjects,Tree::getBlobs(),getTrees()andgetSubmodules(),Submodule,File::isModeChange(),FileChange::getAdditions()andgetDeletions(),PushReference::getFullname(),getRange(),isCreation()andisDeletion(), and the SSH and X.509 signatures of tags. (#253, #254, #263) - Add
WorkingCopy::merge()to supportgit merge, fast-forwarding or not, with aFastForwardenum. (#248, fixes #118) - Add
Repository::runProcess()to expose stderr from successful commands:run()only ever returned stdout, even on success, but some git commands (e.g.push) write meaningful output to stderr even when they succeed.run()still returns the standard output. (#247, fixes #205) - Support git's mnemonic diff prefixes (
diff.mnemonicPrefix) in the diff parser: enabling this option replaces the defaulta//b/prefixes with context-specific ones (c//i//o//w/), which used to raise aRuntimeExceptionin tools like GrumPHP that enable it. (#245, fixes #114) - Support
"copy from"/"copy to"in the diff parser: diffs produced with copy detection enabled (e.g.diff.renames = copies) used to fail to parse.Filegains a newisCopy()flag, andisRename()no longer reportstruefor copies, since a copy's source file still exists afterward. (#244, fixes #14)
Deprecations¶
All are removed in 3.0; see UPGRADE-2.0.md. (#253) 1.7 deprecates them already: they are all deprecated since 1.7.
Admin, replaced by the static methods ofRepository, andRepository::cloneTo(), replaced bycloneFrom()andcloneBareFrom().- The array of options of
RepositoryandAdmin(working_dir,logger,command,environment_variables,inherit_environment_variables,process_timeout,debug): pass the working directory and aCliDriverinstead;inherit_environment_variableshas no counterpart, pass the variables git needs instead.Repository::setLogger()andgetLogger(). - Passing
$reloadtoRepository::getReferences(), and arguments toCommit::getIncludingBranches().ReferenceBag::update()andBranch::isMergedTo(), replaced byBranch::moveTo()andisMergedInto(). - Calling
Repository::getDiff()with one argument,Log::getDiff(),Diff::setRepository(),File::setRepository(),addChange(),isChangeMode(),isDelete()andgetAnchor(),FileChange::getCount(),getLines(), replaced bygetDiffLines(), and theLINE_*constants, replaced byDiffLineType, lines passed to the constructor ofFileChangeas arrays. Log::setOffset()andsetLimit().- A range of lines as a string in
Repository::getBlame(),Blame::getGroupedLines()andBlame\Line::getLine(). CommitReference,Tree::getCommitReferenceEntries(),getEntries(),getBlobEntries()andgetTreeEntries(): iterate the tree, or usegetBlobs(),getTrees()andgetSubmodules().- The branch to create of
WorkingCopy::checkout(). Commit::getShortMessage()andgetFixedShortHash(),Repository::shell(),PushReference::getReference(),getRevision(),isCreate()andisDelete().Repository::getSize(), in kilobytes: usegetSizeInBytes(). (#254)
Security¶
- Refuse a revision, a reference, a hash or a URL starting with a dash, which git read as an
option:
getRevision('--output=/path'),WorkingCopy::checkout()ormerge()could write a file, andRepository::isValidRepository('--upload-pack=...')run a command. A path may start with a dash:init()and the clones end the options of git with--. (#254) - Require
symfony/process6.4.33, 7.4.5 or 8.1 and later: the older versions escape the arguments wrongly on Windows (CVE-2024-51736, CVE-2026-24739). (#263) - Refuse an
ext::URL, likeext::sh -c ..., whose command git runs, by default before git 2.12, and anfd::URL, which makes git talk through a file descriptor it inherits from PHP, like the socket of a database, and wait on it. (#254, #263) - Refuse an ssh URL whose host or user starts with a dash, even encoded (
%2D), likessh://-oProxyCommand=.../repository: git before 2.14.1 passes it to ssh, which reads it as an option and runs the command (CVE-2017-1000117). Agit://URL whose host starts with a dash is refused too: git passes it to the command ofcore.gitProxy. (#254) - Hide the credentials of URLs (
https://user:token@host), a password holding a#or a?included, in the messages of exceptions, in their traces (#[\SensitiveParameter]), and in the logs. The exceptions of Symfony Process, which hold the command line, no longer leak, not even as the previous exception. (#254) - Refuse a hook name that isn't the one of a file of the hooks directory:
Hooks::set('../config', ...)andremove('../config')wrote and deleted the configuration of the repository. (#254) - Fix
Hookswriting through a symbolic link to a missing file, out of the hooks directory:has()returnedfalsefor it, andset()followed it. (#254) - Ignore the replacements of objects (
git replace,refs/replace/) withCliDriver, likeLibGit2Driver: whoever could push such a reference to a repository changed the message of a commit, or the content of a blob, gitlib read for a hash. (#254) - Ignore
core.fsmonitorwithCliDriver: git ran the command the configuration of a repository names there to read the working tree, and even to diff a commit. Like git with--git-dir, gitlib trusts the repositories it opens:safe.directorydoesn't apply, see "Git configuration". (#263) - Fix
WorkingCopy::checkout()of a path, likecheckout('README.md'), discarding the changes of the file: a path is no revision, and throws aReferenceNotFoundException. (#254) - Fix moving or deleting a symbolic reference, which changed the branch it points to instead:
ReferenceBag::delete('refs/remotes/origin/HEAD')deletedorigin/main. (#254) - Fix
ReferenceBag::delete('HEAD')deletingHEAD, which left no repository: a name outsiderefs/throws anInvalidArgumentException. (#254)
Bug fixes¶
- Fix
Commit::getIncludingBranches()andRepository::getHead()throwing for a branch another process created or checked out since gitlib read the references, andgetIncludingBranches()throwing for a regularrefs/remotes/origin/HEAD, as a mirror leaves it. (#263) - Fix
isValidRepository()andisValidRepositoryAndBranch()reading, withCliDriver, the configuration of the repository PHP runs in, like its rewrites of URLs, and returningtruefor an empty URL there: it throws anInvalidArgumentException. (#263) - Fix the blame of a file throwing with
CliDriverafter a merge whose sides kept it under two names: git repeats the path of a commit it blames under both, which gitlib didn't read. (#263) - Fix a log throwing with
CliDriverfor a commit whose author or committer git wouldn't write anymore, likeName<email>or no date, as old imports have: it is read like libgit2 reads it, the epoch in UTC without date. (#263) - Fix the configuration of the user changing what
CliDriverreads:color.ui=always,log.showSignature,diff.noprefix,diff.external,diff.submodule, textconv filters,column.ui... made reads throw or return wrong data, anddiff.algorithm,diff.context,diff.orderFile,log.followorbranch.sortchanged diffs, logs and the branches of a commit. gitlib sets them back to git's defaults; see "Git configuration". (#254) - Fix
Commit::getShortHash(),Commit::getDiff()andRepository::getDiff()failing when a file of the working tree has the name of the revision they read, which git found ambiguous. (#254) - Fix logs of thousands of revisions exceeding the limit of the command line:
CliDriverpasses them to git on its standard input. (#254) - Fix
Commit::getDiff()returning an empty diff for a root commit, and throwing for a merge commit (CliDriver). (#253) - Fix
Repository::getDiff(),WorkingCopy::getDiffPending()andWorkingCopy::getDiffStaged()failing to parse the output of git before 2.16, which writes "..." after abbreviated hashes, and throwing during a conflict. (#253) - Fix the names of files with spaces in diffs, split at the wrong place, the hashes of the files following a type change, shifted, and hunks of one line, counted as empty. (#253)
- Fix annotated tags never resolving to their commit (
ReferenceBag::resolveTags(),Commit::resolveReferences()), tags created or deleted missing fromgetTags(), andgetHead()throwing after a checkout of a new branch: references are read again after every write gitlib makes. (#253) - Fix
createBranch()andcreateTag()moving an existing reference instead of refusing it, a branch named "remotes/x" taken for a remote one, and branches whose name ends with "HEAD" missing. (#253) - Fix
isValidRepositoryAndBranch()matching the end of the name of a branch ("release" for "feature/release"), andisValidRepository()reading a relative path as the name of a remote of PHP's working directory, or returningfalsewhen git can't run. (#253) - Fix
getTree()reading the tree of a commit, and arguments holding a NUL byte throwing aValueError: they throw aReferenceNotFoundExceptionand anInvalidArgumentException. (#253) - Fix the log of a merge of an unsigned or SSH-signed tag throwing, or swallowing commits up to the next PGP signature. (#253)
- Fix
Commit::getBodyMessage()losing the second line of a message without an empty line after its subject, tags without tagger throwing, and SSH-signed tags not seen as signed. (#253) - Fix blame lines numbered from 1 for a range of lines. (#253)
- Fix
Hooksfailing silently, in a repository without hooks directory, and writing the hooks of a linked worktree where git doesn't run them. (#253) - Fix
Repository::getSize()throwing for a link to a missing file in the git directory, like a hook not deployed yet: it no longer follows links. A file or a directory git deletes meanwhile, like a lock during agcor the quarantine of a push, no longer makes it throw either, and one it can't read throws aRuntimeExceptionof gitlib, instead of PHP's. (#254, #263) - Fix blame lines starting with a TAB character (
Blame\Line::getContent()): code removing it must stop doing so. (#253) - Fix reading a commit whose
encodingheader isn't UTF-8, which threw: its message, author and committer are now re-encoded to UTF-8, likegit logdoes. (#253) - Fix file names holding non-ASCII or special characters (tab, quote, backslash) in trees
(
Tree::getEntries()), and special characters in diffs, which kept git's C-style quoting. (#253) - Fix relative paths not resolving against the repository in
Repository::run(): the git process spawned byrun()never had its working directory set, so commands such asapplyresolved paths against the calling script's cwd instead of--work-tree, failing when run from outside the repository directory. (#249, fixes #67) - Fix diff parsing for dirty submodules without an index line: a submodule whose working
tree changed but still points at the same commit is reported by git without an
indexline, which made the parser choke on the next hunk. (#246, fixes #165) - Fix bugs and hardening issues found by a code audit:
(#239)
ReferenceBag::getFirstBranch()could return a non-branch reference, or throw, when no branch existed.Tag::getBodyMessage()dropped or truncated a line for annotated tags using the standardsubject\n\nbodyconvention.Repository::shell()interpolated the repository path into the shell command without escaping it; it is now passed throughescapeshellarg().Hooks::set()created hook scripts world-writable (0777); they are now created0700.- Fixed the
isSuccessFul()casing typo inAdmin.
Minor¶
- CI tests git 1.8.4 and 2.25.1, analyses the tests and the libgit2 driver with PHPStan, without baseline, checks the archive Composer installs, pins the third-party actions by commit, and the suite deletes what it creates, in any order. (#253)
- The tests run with a git configuration of their own, whatever the developer's
(
tests/fixtures/home), which sets what gitlib sets back to git's defaults, and without the variables of git of their environment: run from a hook, they wrote the repository of the hook (GIT_DIR,GIT_INDEX_FILE...). (#254) - Add a regression test for reading a blob's content in a tree that also contains a submodule. (#243)
- Replace the network-dependent test fixture (cloned from GitHub on every run) with a local, audited git bundle, plus a Console tool to regenerate it. (#241, #242)
- CI: bump
actions/checkoutto v7 to fix a Node 20 deprecation warning. (#240)
v1.6.0 - 2025-12-09¶
Earlier versions are not documented here. See the GitHub releases and commit history for prior changes.