Skip to content

Commit 8743ffb

Browse files
committed
Smooth the upgrade from 1.7
1.7 has the API of 2.0 that can be added without breaking 1.x, and deprecates what 2.0 replaces: 2.0 introduces no deprecation of its own, they are all deprecated since 1.7. So the lines of a hunk and the entries of a tree get the methods 1.7 has: FileChange::getDiffLines(), iterating a Tree, Tree::getBlobs(), getTrees() and getSubmodules(). The methods returning arrays are deprecated, and keep returning the arrays of 1.x until 3.0, instead of objects readable as arrays. UnexpectedValueException, which 2.0 no longer throws, has nothing to replace it in 1.7: it isn't deprecated. UPGRADE-2.0.md explains how to support 1.x and 2.x with a single code path, and the branch alias lets "^2.0@dev" install main.
1 parent fc17694 commit 8743ffb

42 files changed

Lines changed: 582 additions & 331 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ deprecated until 3.0.
1616
missing revision or object a `ReferenceNotFoundException`, a timeout a
1717
`ProcessTimedOutException`; dates are `\DateTimeImmutable`; names in diffs and trees are
1818
UTF-8; references are read again after every write gitlib makes, and peel annotated tags;
19-
the diff of a merge commit is against its first parent; blame lines, tree entries, diff
20-
lines and logs are typed objects; the subject of a message is its first paragraph. See
19+
the diff of a merge commit is against its first parent; blame lines and logs are typed
20+
objects; the subject of a message is its first paragraph. See
2121
[UPGRADE-2.0.md](UPGRADE-2.0.md) for every change.
2222
([#253](https://github.com/gitonomy/gitlib/pull/253))
2323
- Return the dates of commits and tags at the UTC offset of their author, committer or tagger,
@@ -43,6 +43,9 @@ deprecated until 3.0.
4343
`The git command "clone" failed (exit 128): fatal: ...`. The command line, the rest of the
4444
error output and the output are no longer in the message: `getErrorOutput()` and
4545
`getOutput()` return the outputs of git. ([#254](https://github.com/gitonomy/gitlib/pull/254))
46+
- `Tree::resolvePath()` throws an `InvalidArgumentException` for a path through a submodule,
47+
instead of an `Exception\UnexpectedValueException`, which gitlib no longer throws.
48+
([#254](https://github.com/gitonomy/gitlib/pull/254))
4649
- When git can't start, a missing binary for instance, `CliDriver` throws a `DriverException`
4750
instead of a `ProcessException`, with a message of one line, without the command line:
4851
`The git command "clone" could not start: ...`.
@@ -78,7 +81,9 @@ deprecated until 3.0.
7881
`Repository::getSizeInBytes()` and `WorkingCopy::getDiff()`,
7982
`WorkingCopy::checkoutNewBranch()`, `Branch::moveTo()` and `Branch::isMergedInto()`,
8083
`Log::withOffset()` and `withLimit()`, `Blame::getHunks()` and the line numbers of
81-
`Blame\Line`, `TreeEntry` and `Submodule`, `Diff\DiffLine` and `Diff\DiffLineType`,
84+
`Blame\Line`, `FileChange::getDiffLines()`, which returns `Diff\DiffLine` objects typed by
85+
`Diff\DiffLineType`, iterating a `Tree`, which gives `TreeEntry` objects,
86+
`Tree::getBlobs()`, `getTrees()` and `getSubmodules()`, `Submodule`,
8287
`File::isModeChange()`, `FileChange::getAdditions()` and `getDeletions()`,
8388
`PushReference::getFullname()`, `getRange()`, `isCreation()` and `isDeletion()`, and the SSH
8489
and X.509 signatures of tags. ([#253](https://github.com/gitonomy/gitlib/pull/253),
@@ -103,7 +108,8 @@ deprecated until 3.0.
103108
### Deprecations
104109

105110
All are removed in 3.0; see [UPGRADE-2.0.md](UPGRADE-2.0.md).
106-
([#253](https://github.com/gitonomy/gitlib/pull/253))
111+
([#253](https://github.com/gitonomy/gitlib/pull/253)) 1.7 deprecates them already: they are
112+
all deprecated since 1.7.
107113

108114
- `Admin`, replaced by the static methods of `Repository`, and `Repository::cloneTo()`,
109115
replaced by `cloneFrom()` and `cloneBareFrom()`.
@@ -116,18 +122,20 @@ All are removed in 3.0; see [UPGRADE-2.0.md](UPGRADE-2.0.md).
116122
replaced by `Branch::moveTo()` and `isMergedInto()`.
117123
- Calling `Repository::getDiff()` with one argument, `Log::getDiff()`, `Diff::setRepository()`,
118124
`File::setRepository()`, `addChange()`, `isChangeMode()`, `isDelete()` and `getAnchor()`,
119-
`FileChange::getCount()` and its `LINE_*` constants, reading diff lines as arrays.
125+
`FileChange::getCount()`, `getLines()`, replaced by `getDiffLines()`, and the `LINE_*`
126+
constants, replaced by `DiffLineType`, lines passed to the constructor of `FileChange` as
127+
arrays.
120128
- `Log::setOffset()` and `setLimit()`.
121129
- A range of lines as a string in `Repository::getBlame()`, `Blame::getGroupedLines()` and
122130
`Blame\Line::getLine()`.
123-
- `CommitReference`, `Tree::getCommitReferenceEntries()`, reading tree entries as arrays.
131+
- `CommitReference`, `Tree::getCommitReferenceEntries()`, `getEntries()`, `getBlobEntries()`
132+
and `getTreeEntries()`: iterate the tree, or use `getBlobs()`, `getTrees()` and
133+
`getSubmodules()`.
124134
- The branch to create of `WorkingCopy::checkout()`.
125135
- `Commit::getShortMessage()` and `getFixedShortHash()`, `Repository::shell()`,
126136
`PushReference::getReference()`, `getRevision()`, `isCreate()` and `isDelete()`.
127137
- `Repository::getSize()`, in kilobytes: use `getSizeInBytes()`.
128138
([#254](https://github.com/gitonomy/gitlib/pull/254))
129-
- `Exception\UnexpectedValueException`, which gitlib no longer throws.
130-
([#254](https://github.com/gitonomy/gitlib/pull/254))
131139

132140
### Security
133141

‎UPGRADE-2.0.md‎

Lines changed: 152 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ call triggers a deprecation (`E_USER_DEPRECATED`, through
1212
them. Log the deprecations of your application to find them. The other
1313
changes, listed below too, break code that relies on them.
1414

15+
Upgrade to 1.7 first, which requires PHP 8.1: it has most of the
16+
replacements, and triggers the deprecations of the API they replace (see
17+
[Supporting 1.x and 2.x](#supporting-1x-and-2x)).
18+
1519
Requirements
1620
------------
1721

@@ -28,6 +32,65 @@ Requirements
2832
* Repositories using SHA-256 hashes are refused, with an
2933
`UnsupportedOperationException`, by both drivers.
3034

35+
Supporting 1.x and 2.x
36+
----------------------
37+
38+
gitlib 1.7 has the API of 2.0 that can be added without breaking 1.x, and
39+
deprecates what 2.0 replaces or breaks: `new Repository($dir, $workingDir,
40+
$driver)` with a `CliDriver` of the same arguments, the static methods of
41+
`Repository` creating repositories, `Diff::parse($rawDiff, $repository)`,
42+
`Repository::getDiff($from, $to)`, `FileChange::getDiffLines()`, iterating
43+
a `Tree`, `Log::withLimit()`, `Branch::isMergedInto()`,
44+
`Blame::getHunks()`... Fix the deprecations of 1.7, then require
45+
`^1.7 || ^2.0`: a library supporting PHP 8.1 to 8.3 gets 1.7 there, and 2.0
46+
from PHP 8.4, with the same code.
47+
48+
Before:
49+
50+
```php
51+
$repository = new Repository($gitDir, ['working_dir' => $workingDir, 'logger' => $logger]);
52+
$diff = Diff::parse($rawDiff);
53+
$diff->setRepository($repository);
54+
```
55+
56+
After, with 1.7 and 2.0:
57+
58+
```php
59+
use Gitonomy\Git\Driver\Cli\CliDriver;
60+
61+
$repository = new Repository($gitDir, $workingDir, new CliDriver(logger: $logger));
62+
$diff = Diff::parse($rawDiff, $repository);
63+
```
64+
65+
1.7 breaks nothing of 1.x, so:
66+
67+
* Pass by position the arguments whose name changes in 2.0 (see
68+
[Extending gitlib](#extending-gitlib)): 1.7 keeps the names of 1.x. So
69+
do the arguments 1.7 adds to `Repository::getDiff()`, `getBlame()`,
70+
`Diff::parse()`, `Diff::fromArray()` and `File::fromArray()`: their
71+
signature leaves them out, not to break the classes overriding these
72+
methods.
73+
* `ReferenceBag::createBranch()` and `createTag()` keep a revision name as
74+
the hash of the reference they return, like 1.x: pass a `Revision` to
75+
get the hash of its commit with both versions.
76+
* The submodules of trees are `CommitReference` objects, which extend
77+
`Submodule` in 1.7: check them with `instanceof Submodule`.
78+
* `File::toArray()` has no `is_copy` key in 1.7.
79+
80+
What 1.7 can't bring, as it would break 1.x, behaves differently with
81+
both versions:
82+
83+
* With a range of lines, `Blame::getLines()` and `getLine()` number the
84+
lines from 1 in 1.7, like the file in 2.0: use `Line::getNumber()`, or
85+
`Blame::getHunks()`, numbered like the file in 1.7 too.
86+
* 1.7 doesn't read the references again after a write: open a new
87+
`Repository` to read them, with both versions. `getReferences(true)` is
88+
deprecated in 1.7 too.
89+
* The classes `final` in 2.0 are only `@final` in 1.7: see
90+
[Testing code using gitlib](#testing-code-using-gitlib).
91+
* The other changes listed below: dates, the stricter parser of diffs,
92+
the directory git runs in, the exceptions...
93+
3194
Opening a repository
3295
--------------------
3396

@@ -43,6 +106,16 @@ Opening a repository
43106
request. Pass the variables git needs to the `environmentVariables`
44107
argument instead.
45108

109+
git inherits `$_ENV`, and the variables of `getenv()` that `$_SERVER`
110+
holds too, like Symfony Process passes them: a variable an application
111+
only writes in `$_SERVER`, without `putenv()` nor `$_ENV`, which the
112+
option passed, no longer reaches git. A command line application
113+
relying on it passes `$_SERVER` itself:
114+
115+
```php
116+
$driver = new CliDriver(environmentVariables: array_filter($_SERVER, is_string(...)));
117+
```
118+
46119
Before:
47120

48121
```php
@@ -233,11 +306,13 @@ Diffs
233306
changes when constructed: `Diff::setRepository()`,
234307
`File::setRepository()` and `File::addChange()` are deprecated.
235308
`Diff::parse()` and `fromArray()` take the repository to read blobs from.
236-
* The lines of a hunk, `FileChange::getLines()`, are `Diff\DiffLine`
237-
objects, with `getType()`, a `Diff\DiffLineType`, and `getContent()`:
238-
reading them as arrays is deprecated, as the `FileChange::LINE_*`
239-
constants and `FileChange::getCount()`, replaced by `getAdditions()` and
240-
`getDeletions()`.
309+
* `FileChange::getDiffLines()` returns the lines of a hunk as
310+
`Diff\DiffLine` objects, with `getType()`, a `Diff\DiffLineType`, and
311+
`getContent()`: `FileChange::getLines()`, which returns them as arrays,
312+
is deprecated, as the `FileChange::LINE_*` constants and
313+
`FileChange::getCount()`, replaced by `getAdditions()` and
314+
`getDeletions()`. The constructor of `FileChange` takes `DiffLine`
315+
objects: arrays are deprecated.
241316

242317
Before:
243318

@@ -249,12 +324,12 @@ Diffs
249324
}
250325
```
251326

252-
After:
327+
After, with 1.7 and 2.0:
253328

254329
```php
255330
use Gitonomy\Git\Diff\DiffLineType;
256331

257-
foreach ($change->getLines() as $line) {
332+
foreach ($change->getDiffLines() as $line) {
258333
if (DiffLineType::Addition === $line->getType()) {
259334
// ... $line->getContent()
260335
}
@@ -273,6 +348,13 @@ Diffs
273348
`git diff --raw --patch`, with `-z` or not. It reads the names of the
274349
files from the raw information: names with spaces are no longer split at
275350
the wrong place.
351+
* It reads the status of a file, a creation, a modification, a deletion,
352+
a rename..., from its raw line (`A`, `M`, `D`, `R100`...), instead of
353+
the headers of the patch (`new file mode`...): a diff written by hand
354+
where they disagree gives another result. Like git, the raw line
355+
separates the status and the name with a TAB: spaces throw a
356+
`RuntimeException`. Generate the diffs of your tests with git, with
357+
`git diff --raw --patch --full-index`, instead of writing them.
276358
* During a merge conflict, `WorkingCopy::getDiffPending()` and
277359
`getDiffStaged()` leave the unmerged files out, where they threw, and
278360
`WorkingCopy::getDiff()` compares them, conflict markers included, to the
@@ -295,12 +377,30 @@ Blame
295377
Trees
296378
-----
297379

298-
* `Tree::getEntries()`, `getTreeEntries()`, `getBlobEntries()` and
299-
`getSubmoduleEntries()` return `TreeEntry` objects, with `getName()`,
300-
`getMode()` and `getObject()`: reading them as arrays
301-
(`[$mode, $object]`) is deprecated.
380+
* Iterating a `Tree` gives its entries by name, `TreeEntry` objects with
381+
`getName()`, `getMode()` and `getObject()`; `getBlobs()`, `getTrees()`
382+
and `getSubmodules()` return them by kind. `getEntries()`,
383+
`getBlobEntries()` and `getTreeEntries()`, which return them as arrays
384+
(`[$mode, $object]`), are deprecated.
385+
386+
Before:
387+
388+
```php
389+
foreach ($tree->getEntries() as $name => [$mode, $object]) {
390+
// ...
391+
}
392+
```
393+
394+
After, with 1.7 and 2.0:
395+
396+
```php
397+
foreach ($tree as $name => $entry) {
398+
// ... $entry->getMode(), $entry->getObject()
399+
}
400+
```
401+
302402
* `CommitReference` is deprecated: it is `Submodule`. So is
303-
`Tree::getCommitReferenceEntries()`: use `getSubmoduleEntries()`.
403+
`Tree::getCommitReferenceEntries()`: use `getSubmodules()`.
304404
* `Tree::resolvePath()` throws an `InvalidArgumentException` explaining
305405
why a path doesn't resolve, a path through a submodule included, which
306406
threw an `UnexpectedValueException`, and resolves `/` to the tree.
@@ -409,7 +509,9 @@ Errors
409509
writes on its output (`CONFLICT (content): ...`). The command line, the
410510
rest of the error output and the output are no longer in the message:
411511
`getErrorOutput()` and `getOutput()` return the outputs of git. Its
412-
constructor takes the git command as second argument, optional.
512+
constructor takes the git command as second argument, optional, and
513+
reads the exit code and the outputs of the `Process`: a test double of
514+
`Process` must return them.
413515
* When git can't start, a missing binary for instance, `CliDriver` throws
414516
a `DriverException` instead of a `ProcessException`, with a message of
415517
one line, without the command line: `The git command "clone" could not
@@ -419,9 +521,8 @@ Errors
419521
failed;
420522
* `ReferenceBag` (`Repository::getReferences()`) only returns an empty bag
421523
for a repository without references, and throws for any other failure.
422-
* `Gitonomy\Git\Exception\UnexpectedValueException` is deprecated: gitlib
423-
no longer throws it. Catch `InvalidArgumentException` for
424-
`Tree::resolvePath()`.
524+
* gitlib no longer throws `Gitonomy\Git\Exception\UnexpectedValueException`:
525+
catch `InvalidArgumentException` for `Tree::resolvePath()`.
425526
* `Commit::getShortHash()` throws a `ReferenceNotFoundException` for an
426527
unknown commit, instead of a `ProcessException`.
427528
* `Hooks` throws a `RuntimeException` when a hook exists already, or can't
@@ -555,6 +656,27 @@ Extending gitlib
555656
be unserialized: clear such caches. `Diff::toArray()` and
556657
`Diff::fromArray()` remain compatible.
557658

659+
Testing code using gitlib
660+
-------------------------
661+
662+
The classes `final` in 2.0 can't be doubled: `createMock()` of PHPUnit,
663+
Prophecy and Mockery refuse them ("Class "Gitonomy\Git\WorkingCopy" is
664+
declared "final" and cannot be doubled", "could not reflect class
665+
Gitonomy\Git\WorkingCopy as it is marked final"). Instead, from the
666+
simplest:
667+
668+
1. Construct the value objects: `Diff`, `Diff\File`, `Diff\FileChange`
669+
and `Diff\DiffLine` have public constructors. `new Diff([new File(...)],
670+
'')` works with 1.x too.
671+
2. Parse the real output of git: `Diff::parse()` of
672+
`git diff --raw --patch --full-index` (see [Diffs](#diffs)).
673+
3. Use a real repository, in a temporary directory:
674+
`Repository::init($path)`, or `git init` with 1.x before 1.7. A
675+
`WorkingCopy` needs one.
676+
4. Implement `Gitonomy\Git\Driver\DriverInterface`, and pass it as the
677+
driver of `Repository`: see
678+
["Using your own driver"](doc/repository.md#using-your-own-driver).
679+
558680
Data returned
559681
-------------
560682

@@ -590,9 +712,20 @@ Data returned
590712
Other changes
591713
-------------
592714

593-
* Relative paths, in the arguments of `Repository::run()` and in the paths
594-
of a log, are relative to the repository instead of the current directory
595-
of PHP.
715+
* `Repository::run()` and `runProcess()` run git in the working directory
716+
of the repository, or its git directory when it is bare, instead of the
717+
current directory of PHP. Relative paths, in the arguments of `run()`
718+
and in the paths of a log, are relative to the repository. When PHP runs
719+
in a subdirectory of the repository, the output of the commands relative
720+
to the current directory changes too, and so does their default scope:
721+
722+
| `run()` in `repository/sub` | 1.x | 2.0 |
723+
|------------------------------|-------------------|-------------------------------|
724+
| `ls-files` | `in-sub.txt` | `root.txt`, `sub/in-sub.txt` |
725+
| `status --short` | ` M ../root.txt` | ` M root.txt` |
726+
| `rev-parse --show-prefix` | `sub/` | (empty) |
727+
728+
Nothing changes when PHP runs out of the repository.
596729
* `Hooks::set()` makes hooks executable by their owner only (`0700`,
597730
instead of `0777`): change their permissions if another user runs them.
598731
* `Repository::shell()` is deprecated: it ignored the driver. Run

‎composer.json‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,10 @@
5555
"config": {
5656
"preferred-install": "dist",
5757
"sort-packages": true
58+
},
59+
"extra": {
60+
"branch-alias": {
61+
"dev-main": "2.0.x-dev"
62+
}
5863
}
5964
}

‎doc/admin.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Create and access git repositories
22
==================================
33

44
The static methods of `Repository` create new repositories, and return them.
5-
They replace `Admin`, deprecated since 2.0: see
5+
They replace `Admin`, deprecated since 1.7: see
66
[UPGRADE-2.0.md](../UPGRADE-2.0.md#creating-repositories).
77

88
To open a repository that exists already, use `new Repository($path)`: see

‎doc/blame.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ throws a `DriverException`. A last line before the first one blames the lines
2626
in order, like `git blame -L 5,3`.
2727

2828
Passing a range as a string (`'2,3'` or `'2,+2'`, like `git blame -L`) is
29-
deprecated since 2.0: pass the first and the last lines instead.
29+
deprecated since 1.7: pass the first and the last lines instead.
3030

3131
Line-per-line iteration
3232
-----------------------
@@ -66,7 +66,7 @@ $line->getCommit(); // The commit that last changed it
6666
$author = $line->getCommit()->getAuthorName();
6767
```
6868

69-
`Line::getLine()` is deprecated since 2.0: use `getOriginalNumber()`, which
69+
`Line::getLine()` is deprecated since 1.7: use `getOriginalNumber()`, which
7070
returns an integer.
7171

7272
Group reading by commit
@@ -87,4 +87,4 @@ foreach ($blame->getHunks() as $hunk) {
8787
}
8888
```
8989

90-
`getGroupedLines()` is deprecated since 2.0: use `getHunks()`.
90+
`getGroupedLines()` is deprecated since 1.7: use `getHunks()`.

‎doc/branch.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ $branch = $branch->moveTo('master~2');
4141
```
4242

4343
Like `git update-ref`, it doesn't touch the working tree, even for the
44-
branch checked out. `ReferenceBag::update()` is deprecated since 2.0: use
44+
branch checked out. `ReferenceBag::update()` is deprecated since 1.7: use
4545
`moveTo()`.
4646

4747
Merged branches
@@ -56,7 +56,7 @@ if ($branch->isMergedInto('master')) {
5656
}
5757
```
5858

59-
`isMergedTo()` is deprecated since 2.0: use `isMergedInto()`, which has no
59+
`isMergedTo()` is deprecated since 1.7: use `isMergedInto()`, which has no
6060
default destination (`master`) nor flag to compare remote branches only.
6161

6262
Branches containing a commit

0 commit comments

Comments
 (0)