git 5.1.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +78 -0
- data/CONTRIBUTING.md +202 -169
- data/LICENSE +1 -1
- data/README.md +192 -151
- data/UPGRADING.md +287 -1
- data/git.gemspec +35 -3
- data/lib/git/author.rb +11 -0
- data/lib/git/author_info.rb +66 -0
- data/lib/git/branch.rb +210 -15
- data/lib/git/branch_info.rb +1 -1
- data/lib/git/branches.rb +35 -7
- data/lib/git/command_line/base.rb +1 -2
- data/lib/git/commands/base.rb +1 -1
- data/lib/git/commands/cat_file/raw.rb +62 -7
- data/lib/git/object.rb +13 -7
- data/lib/git/parsers/stash.rb +50 -17
- data/lib/git/parsers/tag.rb +54 -8
- data/lib/git/remote.rb +37 -7
- data/lib/git/remote_info.rb +67 -10
- data/lib/git/repository/branching.rb +190 -6
- data/lib/git/repository/merging.rb +96 -2
- data/lib/git/repository/remote_operations.rb +57 -0
- data/lib/git/repository/shared_private.rb +67 -0
- data/lib/git/stash_info.rb +32 -34
- data/lib/git/tag_info.rb +21 -29
- data/lib/git/version.rb +1 -1
- data/lib/git.rb +1 -0
- metadata +6 -94
- data/.claude/commands/address-copilot-reviews.md +0 -14
- data/.claude/settings.json +0 -16
- data/.claude/skills +0 -1
- data/.commitlintrc.yml +0 -38
- data/.dockerignore +0 -27
- data/.github/copilot-instructions.md +0 -30
- data/.github/hooks/bin-setup-on-worktree.json +0 -11
- data/.github/hooks/run-bin-setup-once.sh +0 -20
- data/.github/issue_template.md +0 -15
- data/.github/prompts/iteratively-address-copilot-reviews.prompt.md +0 -188
- data/.github/pull_request_template.md +0 -21
- data/.github/skills/breaking-change-analysis/SKILL.md +0 -99
- data/.github/skills/ci-cd-troubleshooting/SKILL.md +0 -264
- data/.github/skills/command-implementation/REFERENCE.md +0 -994
- data/.github/skills/command-implementation/SKILL.md +0 -230
- data/.github/skills/command-test-conventions/SKILL.md +0 -664
- data/.github/skills/command-yard-documentation/SKILL.md +0 -434
- data/.github/skills/dependency-management/SKILL.md +0 -72
- data/.github/skills/development-workflow/SKILL.md +0 -512
- data/.github/skills/facade-implementation/REFERENCE.md +0 -837
- data/.github/skills/facade-implementation/SKILL.md +0 -269
- data/.github/skills/facade-test-conventions/SKILL.md +0 -391
- data/.github/skills/facade-yard-documentation/SKILL.md +0 -435
- data/.github/skills/make-skill-template/SKILL.md +0 -226
- data/.github/skills/pr-readiness-review/SKILL.md +0 -205
- data/.github/skills/project-context/SKILL.md +0 -306
- data/.github/skills/pull-request-review/SKILL.md +0 -168
- data/.github/skills/rebase/SKILL.md +0 -148
- data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +0 -131
- data/.github/skills/release-management/SKILL.md +0 -125
- data/.github/skills/resolve-feedback/SKILL.md +0 -288
- data/.github/skills/review-arguments-dsl/CHECKLIST.md +0 -788
- data/.github/skills/review-arguments-dsl/SKILL.md +0 -214
- data/.github/skills/review-cross-command-consistency/SKILL.md +0 -139
- data/.github/skills/reviewing-skills/SKILL.md +0 -214
- data/.github/skills/rspec-unit-testing-standards/SKILL.md +0 -685
- data/.github/skills/tdd-refactor-step/SKILL.md +0 -236
- data/.github/skills/test-debugging/SKILL.md +0 -161
- data/.github/skills/yard-documentation/SKILL.md +0 -981
- data/.github/skills/yard-documentation/element-rules.md +0 -162
- data/.github/skills-deprecated/README.md +0 -21
- data/.github/skills-deprecated/extract-command-from-lib/SKILL.md +0 -487
- data/.github/skills-deprecated/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +0 -22
- data/.github/skills-deprecated/extract-facade-from-base-lib/SKILL.md +0 -600
- data/.github/skills-deprecated/review-backward-compatibility/SKILL.md +0 -275
- data/.github/workflows/continuous_integration.yml +0 -358
- data/.github/workflows/enforce_conventional_commits.yml +0 -35
- data/.github/workflows/experimental_continuous_integration.yml +0 -59
- data/.github/workflows/release.yml +0 -52
- data/.github/workflows/warm_bundler_caches.yml +0 -82
- data/.gitignore +0 -30
- data/.husky/commit-msg +0 -1
- data/.husky/pre-commit +0 -13
- data/.release-please-config.json +0 -36
- data/.release-please-manifest.json +0 -3
- data/.rspec +0 -2
- data/.rubocop.yml +0 -44
- data/.rubocop_todo.yml +0 -30
- data/.yard-lint.yml +0 -75
- data/CLAUDE.md +0 -11
- data/Gemfile +0 -22
- data/Rakefile +0 -41
- data/docker/test/Dockerfile +0 -32
- data/docker/test/docker-compose.yml +0 -0
- data/package.json +0 -10
- data/redesign/1_architecture_existing.md +0 -102
- data/redesign/2_architecture_redesign.md +0 -449
- data/redesign/3_architecture_implementation.md +0 -1623
- data/redesign/Phase 4 - Step A.md +0 -366
- data/redesign/Phase 4 - Step B.md +0 -921
- data/redesign/Phase 4 - Step C.md +0 -833
- data/redesign/beta_release.md +0 -107
- data/redesign/branch_parse_refactor_plan.md +0 -163
- data/redesign/c1a-public-api-scope.tsv +0 -256
- data/redesign/c1c2_audit.md +0 -566
- data/redesign/c1c2_bucket6_lib_orphans.md +0 -626
- data/redesign/config_design.rb +0 -501
- data/redesign/index.md +0 -34
- data/redesign/info_object_migration_plan.md +0 -126
- data/redesign/integration_test_analysis.md +0 -521
- data/redesign/phase-4-step-b-test-audit.tsv +0 -485
- data/redesign/remote_refactor_plan.md +0 -164
- data/redesign/reverse_dependencies.sql +0 -44
- data/tasks/gem_tasks.rake +0 -14
- data/tasks/npm_tasks.rake +0 -7
- data/tasks/rspec.rake +0 -111
- data/tasks/rubocop.rake +0 -5
- data/tasks/test_gem.rake +0 -12
- data/tasks/yard.rake +0 -57
data/UPGRADING.md
CHANGED
|
@@ -4,6 +4,7 @@ This document covers breaking changes and migration steps when upgrading the
|
|
|
4
4
|
`git` gem to a new major version. Each section describes what changed and how
|
|
5
5
|
to update your code when upgrading from the preceding major version.
|
|
6
6
|
|
|
7
|
+
- [Upgrading to v6.0.0](#upgrading-to-v600)
|
|
7
8
|
- [Upgrading to v5.x](#upgrading-to-v5x)
|
|
8
9
|
- [Overview](#overview)
|
|
9
10
|
- [Breaking changes](#breaking-changes)
|
|
@@ -17,6 +18,31 @@ to update your code when upgrading from the preceding major version.
|
|
|
17
18
|
- [Facade method renames](#facade-method-renames)
|
|
18
19
|
- [v4.x-style configuration methods](#v4x-style-configuration-methods)
|
|
19
20
|
- [`Git` module mixin deprecations](#git-module-mixin-deprecations)
|
|
21
|
+
- [`Git::Author` deprecated](#gitauthor-deprecated)
|
|
22
|
+
- [`Git::Branch#stashes` deprecated](#gitbranchstashes-deprecated)
|
|
23
|
+
- [`Git::Repository#remotes` deprecated](#gitrepositoryremotes-deprecated)
|
|
24
|
+
- [`Git::Remote` deprecated](#gitremote-deprecated)
|
|
25
|
+
- [`Git::Commands::CatFile::Raw` `allow_unknown_type` option deprecated](#gitcommandscatfileraw-allow_unknown_type-option-deprecated)
|
|
26
|
+
- [`Git::Branch` and `Git::Branches` deprecated](#gitbranch-and-gitbranches-deprecated)
|
|
27
|
+
|
|
28
|
+
## Upgrading to v6.0.0
|
|
29
|
+
|
|
30
|
+
v6.0.0 is not yet released. This section will be completed when it ships.
|
|
31
|
+
|
|
32
|
+
v6.0.0 removes the APIs deprecated during v5.x under the project's
|
|
33
|
+
[deprecation policy](README.md#deprecation-policy).
|
|
34
|
+
[Issue 1717](https://github.com/ruby-git/ruby-git/issues/1717) tracks its scope.
|
|
35
|
+
|
|
36
|
+
To prepare:
|
|
37
|
+
|
|
38
|
+
1. Upgrade to the latest v5.x release.
|
|
39
|
+
2. Set `GIT_DEPRECATION_BEHAVIOR=raise` (or `Git::Deprecation.behavior = :raise`) in
|
|
40
|
+
your test suite and, if possible, staging.
|
|
41
|
+
3. Fix each deprecation using the entries under
|
|
42
|
+
[Deprecated methods](#deprecated-methods) until the suite is clean.
|
|
43
|
+
4. Upgrade to v6.0.0.
|
|
44
|
+
|
|
45
|
+
---
|
|
20
46
|
|
|
21
47
|
## Upgrading to v5.x
|
|
22
48
|
|
|
@@ -240,7 +266,7 @@ shim cannot forward them). Update call sites directly:
|
|
|
240
266
|
|
|
241
267
|
| v4.x call | Notes |
|
|
242
268
|
|-----------|-------|
|
|
243
|
-
| `g.lib.list_files(ref_dir)` | Walked `.git/refs/` directly. Use `g.
|
|
269
|
+
| `g.lib.list_files(ref_dir)` | Walked `.git/refs/` directly. Use `g.branch_list`, `g.tags`, or `g.remote_list` instead. |
|
|
244
270
|
|
|
245
271
|
##### Internal plumbing methods (no replacement)
|
|
246
272
|
|
|
@@ -362,4 +388,264 @@ as bare methods is deprecated:
|
|
|
362
388
|
| `include Git; global_config(name, value)` | `Git.config_set(name, value, global: true)` |
|
|
363
389
|
| `include Git; global_config` | `Git.config_list(global: true)` |
|
|
364
390
|
|
|
391
|
+
#### `Git::Author` deprecated
|
|
392
|
+
|
|
393
|
+
Starting in v5.3.0, methods that return author, committer, or tagger data —
|
|
394
|
+
`Git::Object::Commit#author`, `Git::Object::Commit#committer`,
|
|
395
|
+
`Git::Object::Tag#tagger`, and `Git::TagInfo#tagger` — return an immutable
|
|
396
|
+
`Git::AuthorInfo` value object instead of the mutable `Git::Author`.
|
|
397
|
+
|
|
398
|
+
`Git::AuthorInfo` exposes the same `name`, `email`, and `date` readers, so code
|
|
399
|
+
that only reads these attributes needs no changes. Code that mutated a
|
|
400
|
+
`Git::Author` (via `name=`, `email=`, or `date=`) must be updated:
|
|
401
|
+
`Git::AuthorInfo` is frozen, and `#with` returns a modified copy rather than
|
|
402
|
+
updating in place (e.g. `info = info.with(name: 'New Name')`).
|
|
403
|
+
|
|
404
|
+
Constructing `Git::Author` directly emits a deprecation warning naming
|
|
405
|
+
`Git::AuthorInfo` as the replacement. The class is removed in v6.0.0.
|
|
406
|
+
|
|
407
|
+
| Deprecated usage | Replacement |
|
|
408
|
+
|-----------------|-------------|
|
|
409
|
+
| `Git::Author.new('Name <email> 1627849923 +0200')` | `Git::AuthorInfo.parse('Name <email> 1627849923 +0200')` |
|
|
410
|
+
| `author.name = 'New Name'` | `author = author.with(name: 'New Name')` (returns a new object) |
|
|
411
|
+
|
|
412
|
+
#### `Git::Branch#stashes` deprecated
|
|
413
|
+
|
|
414
|
+
`Git::Branch#stashes` ignores the branch it is called on and returns every stash
|
|
415
|
+
in the repository, so `g.branch('feature').stashes` and `g.branch('main').stashes`
|
|
416
|
+
return the same entries. Call `Git::Repository#stashes_all` instead; it is the
|
|
417
|
+
query `Git::Branch#stashes` was already running.
|
|
418
|
+
|
|
419
|
+
> **Return type change:** `Git::Branch#stashes` returns a `Git::Stashes`
|
|
420
|
+
> collection of `Git::Stash` objects, newest first. `g.stashes_all` returns an
|
|
421
|
+
> array of `[index, message]` pairs, oldest first. Code that read `stash.message`
|
|
422
|
+
> from each entry should read the second element of each pair instead. Code that
|
|
423
|
+
> iterated or indexed the collection must reverse the order first, because
|
|
424
|
+
> `Git::Stashes` yields and indexes newest first while `g.stashes_all` is oldest
|
|
425
|
+
> first.
|
|
426
|
+
|
|
427
|
+
`Git::Stashes` also exposes `save`, `apply`, and `clear`. Those map to the
|
|
428
|
+
repository's `stash_save`, `stash_apply`, and `stash_clear`, which are not
|
|
429
|
+
deprecated. `Git::Stashes#apply(i)` already passed `i` to git as `stash@{i}`
|
|
430
|
+
(`0` = newest), and `g.stash_apply(i)` does the same, so that index needs no
|
|
431
|
+
conversion.
|
|
432
|
+
|
|
433
|
+
| Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
|
|
434
|
+
|-----------------------------------------------------|-------------|
|
|
435
|
+
| `g.branch(name).stashes` | `g.stashes_all` — returns `[[index, message], ...]` |
|
|
436
|
+
| `g.branch(name).stashes.each { \|s\| puts s.message }` | `g.stashes_all.reverse_each { \|_index, message\| puts message }` |
|
|
437
|
+
| `g.branch(name).stashes.all` | `g.stashes_all` |
|
|
438
|
+
| `g.branch(name).stashes.size` | `g.stashes_all.size` |
|
|
439
|
+
| `g.branch(name).stashes[i].message` (`0` = newest, `i` coerced with `to_i`) | `g.stashes_all.reverse[i.to_i][1]` |
|
|
440
|
+
| `g.branch(name).stashes.save(message)` | `g.stash_save(message)` |
|
|
441
|
+
| `g.branch(name).stashes.apply` | `g.stash_apply` |
|
|
442
|
+
| `g.branch(name).stashes.apply(i)` (`0` = newest) | `g.stash_apply(i)` |
|
|
443
|
+
| `g.branch(name).stashes.clear` | `g.stash_clear` |
|
|
444
|
+
#### `Git::Repository#remotes` deprecated
|
|
445
|
+
|
|
446
|
+
`Git::Repository#remotes` is deprecated in favor of `Git::Repository#remote_list`
|
|
447
|
+
and is removed in v6.0.0. Its return value is unchanged. Calling `remotes` emits
|
|
448
|
+
one deprecation warning for itself plus one `Git::Remote` constructor warning for
|
|
449
|
+
each remote it returns (see the `Git::Remote` deprecation below), so a repository
|
|
450
|
+
with N remotes produces N + 1 warnings per call.
|
|
451
|
+
|
|
452
|
+
> **Return type change:** `remotes` returns `Array<Git::Remote>` — mutable
|
|
453
|
+
> objects with `name`, `url`, and `fetch_opts` accessors and `fetch`, `merge`,
|
|
454
|
+
> `branch`, and `remove` operations. `remote_list` returns
|
|
455
|
+
> `Array<Git::RemoteInfo>` — immutable value objects read from the repository's
|
|
456
|
+
> git config, with fields such as `name`, `url`, `push_url`, `fetch`, and `push`.
|
|
457
|
+
> Because a remote may carry more than one URL or refspec, `url`, `push_url`,
|
|
458
|
+
> `fetch`, and `push` are always frozen `Array<String>`. When a remote has more
|
|
459
|
+
> than one URL, git fetches from the first; the legacy `Git::Remote#url` returned
|
|
460
|
+
> the last one configured, so use `r.url.last` to reproduce that exact value.
|
|
461
|
+
> Likewise, `Git::Remote#fetch_opts` returned only the last configured fetch
|
|
462
|
+
> refspec, while `fetch` holds all of them. Operations that lived on
|
|
463
|
+
> `Git::Remote` are called on the repository with the remote name instead.
|
|
464
|
+
>
|
|
465
|
+
> **Order change:** `remotes` lists remotes in the order `git remote` prints
|
|
466
|
+
> them, while `remote_list` keeps the order in which remotes first appear in the
|
|
467
|
+
> config. When the legacy order matters, iterate `g.remote_names` (the same
|
|
468
|
+
> `git remote` order) or sort `g.remote_list` explicitly.
|
|
469
|
+
|
|
470
|
+
| Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
|
|
471
|
+
|-----------------------------------------------------|-------------|
|
|
472
|
+
| `g.remotes` | `g.remote_list` — returns `Array<Git::RemoteInfo>` |
|
|
473
|
+
| `g.remotes.map(&:name)` | `g.remote_list.map(&:name)` or `g.remote_names` |
|
|
474
|
+
| `g.remotes.map(&:to_s)` | `g.remote_list.map(&:name)` — `Git::RemoteInfo#to_s` is not the name |
|
|
475
|
+
| `g.remotes.map(&:url)` | `g.remote_list.map { \|r\| r.url.first }` — `url` is an `Array<String>` |
|
|
476
|
+
| `g.remotes.map(&:fetch_opts)` | `g.remote_list.map { \|r\| r.fetch.last }` — `fetch` holds every refspec |
|
|
477
|
+
| `g.remotes.each(&:fetch)` | `g.remote_names.each { \|name\| g.fetch(name) }` — same order as `remotes` |
|
|
478
|
+
| `remote.fetch` | `g.fetch(remote.name)` |
|
|
479
|
+
| `remote.fetch(opts)` | `g.fetch(remote.name, opts)` — same options hash |
|
|
480
|
+
| `remote.merge` | `g.merge("#{remote.name}/#{g.current_branch}")` |
|
|
481
|
+
| `remote.merge(branch)` | `g.merge("#{remote.name}/#{branch}")` |
|
|
482
|
+
| `remote.branch` | `g.branch_list("#{remote.name}/#{g.current_branch}").first` — returns a `Git::BranchInfo` |
|
|
483
|
+
| `remote.branch(name)` | `g.branch_list("#{remote.name}/#{name}").first` — returns a `Git::BranchInfo` |
|
|
484
|
+
| `remote.remove` | `g.remote_remove(remote.name)` |
|
|
485
|
+
|
|
486
|
+
#### `Git::Remote` deprecated
|
|
487
|
+
|
|
488
|
+
`Git::Remote`, `Git::Repository#remote`, and `Git::Repository#config_remote` are
|
|
489
|
+
deprecated and are removed in v6.0.0. Read a remote's configuration through
|
|
490
|
+
`Git::Repository#remote_list`, which returns one `Git::RemoteInfo` value object per
|
|
491
|
+
remote, and call the repository-level operations (`fetch`, `merge`, `branch_list`,
|
|
492
|
+
`remote_remove`) with the remote name. Return values are unchanged. Constructing a
|
|
493
|
+
`Git::Remote` directly emits one deprecation warning, and so does calling
|
|
494
|
+
`g.config_remote`. Calling `g.remote` emits two: one for `Git::Repository#remote`
|
|
495
|
+
and one for the `Git::Remote` it constructs. Likewise `g.remotes` emits one warning
|
|
496
|
+
for itself plus one per `Git::Remote` it returns (N + 1 for N remotes). The extra
|
|
497
|
+
warnings from `g.remote` and `g.remotes` are expected, not a bug.
|
|
498
|
+
|
|
499
|
+
> **Return type changes:** `Git::RemoteInfo#url` and `Git::RemoteInfo#fetch` are
|
|
500
|
+
> frozen `Array<String>` because a remote may carry more than one URL or fetch
|
|
501
|
+
> refspec. The legacy `Git::Remote#url` and `Git::Remote#fetch_opts` returned only
|
|
502
|
+
> the last configured value, so `r.url.last` and `r.fetch.last` reproduce them
|
|
503
|
+
> exactly; `r.url.first` is the URL git actually fetches from.
|
|
504
|
+
> `config_remote` returned a flat `Hash{String => String}` in which a repeated
|
|
505
|
+
> `url` or `fetch` key overwrote the earlier value, so it could not report every
|
|
506
|
+
> configured URL or refspec; `remote_list` keeps all of them. In the other
|
|
507
|
+
> direction, `Git::RemoteInfo` models only the remote variables git defines and
|
|
508
|
+
> drops any other `remote.<name>.*` key, while `config_remote` returned every key.
|
|
509
|
+
> Code that reads custom keys should filter `g.config_list` instead (see the
|
|
510
|
+
> table); that yields the same `Hash{String => String}` as `config_remote`.
|
|
511
|
+
> `Git::Remote#branch` returned a `Git::Branch`. Its replacement,
|
|
512
|
+
> `g.branch_list("#{name}/#{branch}").first`, returns a `Git::BranchInfo` value
|
|
513
|
+
> object, or `nil` when the remote-tracking branch does not exist.
|
|
514
|
+
|
|
515
|
+
In the table, `name` is the remote name (`g.remote` defaults it to `'origin'`).
|
|
516
|
+
|
|
517
|
+
| Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
|
|
518
|
+
|-----------------------------------------------------|-------------|
|
|
519
|
+
| `g.remote` | `g.remote_list.find { \|r\| r.name == 'origin' }` — returns a `Git::RemoteInfo`; the deprecated call emits two warnings |
|
|
520
|
+
| `g.remote(name)` | `g.remote_list.find { \|r\| r.name == name }` — returns a `Git::RemoteInfo`; the deprecated call emits two warnings |
|
|
521
|
+
| `g.config_remote(name)` for `url`, `fetch`, and the other modeled fields | `g.remote_list.find { \|r\| r.name == name }` — a `Git::RemoteInfo`, not a `Hash` |
|
|
522
|
+
| `g.config_remote(name)` for every key, including custom ones | `g.config_list.select { \|e\| e.key.start_with?("remote.#{name}.") }.to_h { \|e\| [e.key.delete_prefix("remote.#{name}."), e.value] }` — the same `Hash{String => String}` |
|
|
523
|
+
| `remote.name`, `remote.to_s` | `g.remote_list.find { \|r\| r.name == name }.name` or `g.remote_names` |
|
|
524
|
+
| `remote.url` | `g.remote_list.find { \|r\| r.name == name }.url` — `Array<String>`; `.first` for the single-URL case |
|
|
525
|
+
| `remote.fetch_opts` | `g.remote_list.find { \|r\| r.name == name }.fetch` — `Array<String>` of refspecs |
|
|
526
|
+
| `remote.fetch` | `g.fetch(name)` |
|
|
527
|
+
| `remote.fetch(opts)` | `g.fetch(name, opts)` — same option keys |
|
|
528
|
+
| `remote.merge` | `g.merge("#{name}/#{g.current_branch}")` |
|
|
529
|
+
| `remote.merge(branch)` | `g.merge("#{name}/#{branch}")` |
|
|
530
|
+
| `remote.branch` | `g.branch_list("#{name}/#{g.current_branch}").first` — returns a `Git::BranchInfo` |
|
|
531
|
+
| `remote.branch(branch)` | `g.branch_list("#{name}/#{branch}").first` — returns a `Git::BranchInfo` |
|
|
532
|
+
| `remote.remove` | `g.remote_remove(name)` |
|
|
533
|
+
|
|
534
|
+
#### `Git::Commands::CatFile::Raw` `allow_unknown_type` option deprecated
|
|
535
|
+
|
|
536
|
+
The `allow_unknown_type:` option of `Git::Commands::CatFile::Raw` is deprecated
|
|
537
|
+
and is removed in v6.0.0. Passing it emits a deprecation warning; the
|
|
538
|
+
`--allow-unknown-type` flag still reaches git unchanged until the option is
|
|
539
|
+
removed.
|
|
540
|
+
|
|
541
|
+
There is no replacement. Git 2.50 removed the unknown-type feature, so on git
|
|
542
|
+
2.50 and later `--allow-unknown-type` is an accepted no-op and the option has no
|
|
543
|
+
effect. On git 2.28 through 2.49 the flag still lets `t: true` and `s: true`
|
|
544
|
+
report the type and size of an object whose type git does not recognize, but
|
|
545
|
+
that behavior is dropped together with the option. The class is internal
|
|
546
|
+
(`@api private`) and no `Git::Repository` method passes the option, so only code
|
|
547
|
+
that constructs the command class directly is affected.
|
|
548
|
+
|
|
549
|
+
| Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
|
|
550
|
+
|-----------------------------------------------------|-------------|
|
|
551
|
+
| `Git::Commands::CatFile::Raw.new(ctx).call(sha, t: true, allow_unknown_type: true)` | `Git::Commands::CatFile::Raw.new(ctx).call(sha, t: true)` |
|
|
552
|
+
| `Git::Commands::CatFile::Raw.new(ctx).call(sha, s: true, allow_unknown_type: true)` | `Git::Commands::CatFile::Raw.new(ctx).call(sha, s: true)` |
|
|
553
|
+
|
|
554
|
+
#### `Git::Branch` and `Git::Branches` deprecated
|
|
555
|
+
|
|
556
|
+
`Git::Branch`, `Git::Branches`, `Git::Repository#branch`, and
|
|
557
|
+
`Git::Repository#branches` are deprecated and are removed in v6.0.0. Read branch
|
|
558
|
+
data through `Git::Repository#branch_list`, which returns one `Git::BranchInfo`
|
|
559
|
+
value object per local and remote-tracking branch, and call the repository-level
|
|
560
|
+
operations (`checkout`, `branch_new`, `branch_delete`, `merge`, `merge_into`,
|
|
561
|
+
`in_branch`, and so on) with the branch name. Calling `g.branch` or `g.branches`,
|
|
562
|
+
constructing a `Git::Branches`, and calling any operation on a `Git::Branch` each
|
|
563
|
+
emit a deprecation warning; their return values are unchanged. The `full`,
|
|
564
|
+
`name`, `remote`, `to_s`, and `to_a` readers on `Git::Branch` do not warn.
|
|
565
|
+
|
|
566
|
+
> **Return shape change:** `Git::Branch` exposes `full` (`main` or
|
|
567
|
+
> `remotes/origin/main`), `name`, and `remote` (a `Git::Remote`, or `nil`).
|
|
568
|
+
> `Git::BranchInfo` exposes `refname` (always the full ref: `refs/heads/main` or
|
|
569
|
+
> `refs/remotes/origin/main`), `short_name` (`main` for both), `remote_name` (a
|
|
570
|
+
> `String`, or `nil`), `remote?`, `current?`, `target_oid`, `upstream`,
|
|
571
|
+
> `worktree_path`, and `symref`. `Git::BranchInfo#to_s` is the full ref, not the
|
|
572
|
+
> `remotes/origin/main` form `Git::Branch#to_s` returned. `branch_list` takes
|
|
573
|
+
> `git branch --list` patterns: `'main'` matches the local branch and
|
|
574
|
+
> `'origin/main'` matches the remote-tracking branch. The `remotes/origin/main`
|
|
575
|
+
> and `refs/...` forms that `g.branches[...]` accepted match nothing.
|
|
576
|
+
>
|
|
577
|
+
> **`checkout` no longer creates the branch:** `g.branch('x').checkout` created
|
|
578
|
+
> `x` when it did not exist, ignoring any error from that attempt, and then
|
|
579
|
+
> checked it out. `g.checkout('x')` does not create a missing local branch,
|
|
580
|
+
> with one exception that is git's own: when exactly one remote has a branch
|
|
581
|
+
> named `x`, git creates a local tracking branch from it (its default guess
|
|
582
|
+
> behavior). Otherwise the checkout fails. To reproduce create-or-checkout,
|
|
583
|
+
> call `g.branch_new('x') unless g.local_branch?('x')` and then
|
|
584
|
+
> `g.checkout('x')`. Use `g.checkout('x', new_branch: true)` only when `x` is
|
|
585
|
+
> known not to exist; like `g.branch_new('x')`, it fails when `x` already
|
|
586
|
+
> exists. Likewise `g.branch('x').create` ignored every error, while
|
|
587
|
+
> `g.branch_new('x')` raises `Git::FailedError` when `x` already exists.
|
|
588
|
+
>
|
|
589
|
+
> **`in_branch` and `merge_into` differences:**
|
|
590
|
+
>
|
|
591
|
+
> 1. **Branch creation.** `g.branch('x').in_branch { ... }` created `x` if it did
|
|
592
|
+
> not exist. `g.in_branch('x') { ... }` raises `ArgumentError` unless `x` is an
|
|
593
|
+
> existing local branch, so call `g.branch_new('x')` first. A commit SHA, tag,
|
|
594
|
+
> or remote-tracking name is also rejected before any checkout.
|
|
595
|
+
> 2. **Detached HEAD.** `Git::Branch#in_branch` recorded the literal `HEAD` and
|
|
596
|
+
> could not restore a detached HEAD to its original commit. `g.in_branch` and
|
|
597
|
+
> `g.merge_into` record the SHA and restore it.
|
|
598
|
+
> 3. **Unborn HEAD.** Both new methods raise `Git::Error` before checking anything
|
|
599
|
+
> out when HEAD is on a branch with no commits. The old methods failed later,
|
|
600
|
+
> mid-flow.
|
|
601
|
+
> 4. **Merge overload.** `g.branch('main').merge('feature')` returned stdout from
|
|
602
|
+
> the final restore checkout and ran a hard reset after the merge.
|
|
603
|
+
> `g.merge_into('main', 'feature')` returns the merge's stdout and does no
|
|
604
|
+
> reset. It also rejects the `:no_commit` option; callers who need
|
|
605
|
+
> `--no-commit` use `checkout` and `merge` directly.
|
|
606
|
+
> 5. **Remote-tracking receivers.** Called on a remote-tracking `Git::Branch`,
|
|
607
|
+
> `in_branch` and `merge(branch)` checked out the remote-tracking ref,
|
|
608
|
+
> detaching HEAD, and any commit made there was left dangling. `g.in_branch`
|
|
609
|
+
> and `g.merge_into` take an existing local branch only. Create one from the
|
|
610
|
+
> remote-tracking ref first, with
|
|
611
|
+
> `g.branch_new(name, "remotes/#{remote}/#{name}")`, and pass that branch.
|
|
612
|
+
|
|
613
|
+
In the table, `name` is the branch name (`g.branch` defaults it to the current
|
|
614
|
+
branch), `remote` is the remote name of a remote-tracking branch, `b` is a
|
|
615
|
+
`Git::Branch`, and `info` is the `Git::BranchInfo` that replaces it. Where a
|
|
616
|
+
row says to pass `info.refname` for a remote-tracking branch, `b.full` (the
|
|
617
|
+
`remotes/<remote>/<name>` form) works too; the shorter `"#{remote}/#{name}"`
|
|
618
|
+
can resolve a local branch of that name and is only used where git expects it
|
|
619
|
+
(`branch_delete` with `remotes: true`).
|
|
620
|
+
|
|
621
|
+
| Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
|
|
622
|
+
|-----------------------------------------------------|-------------|
|
|
623
|
+
| `g.branch(name)` | `g.branch_list(name).first` for a local branch, or `g.branch_list("#{remote}/#{name}").find(&:remote?)` for a remote-tracking one — a `Git::BranchInfo`, or `nil` when the branch does not exist; the `remotes/` and `refs/` forms match nothing |
|
|
624
|
+
| `g.branch` | `g.branch_list(g.current_branch).first` — `nil` when HEAD is detached or unborn; use `g.current_branch_state` there |
|
|
625
|
+
| `g.branches` | `g.branch_list` — returns `Array<Git::BranchInfo>` |
|
|
626
|
+
| `g.branches[name]` | `g.branch_list(name).first`, or `g.branch_list("#{remote}/#{name}").find(&:remote?)` for a remote-tracking branch |
|
|
627
|
+
| `g.branches.local` | `g.branch_list.reject(&:remote?)` |
|
|
628
|
+
| `g.branches.remote` | `g.branch_list.select(&:remote?)` |
|
|
629
|
+
| `g.branches.size` | `g.branch_list.size` |
|
|
630
|
+
| `g.branches.each { \|b\| ... }` | `g.branch_list.each { \|info\| ... }` |
|
|
631
|
+
| `g.branches.to_s` | `g.branch_list.map { \|i\| "#{i.current? ? '* ' : ' '}#{i.refname}\n" }.join` — full refs, not `remotes/...` |
|
|
632
|
+
| `b.full`, `b.to_s` | `info.refname` — `refs/remotes/origin/main` rather than `remotes/origin/main` |
|
|
633
|
+
| `b.to_a` | `[info.refname]` |
|
|
634
|
+
| `b.name` | `info.short_name` |
|
|
635
|
+
| `b.remote` | `info.remote_name` — a `String`, or `nil` for a local branch |
|
|
636
|
+
| `b.gcommit` | `g.gcommit(name)` — pass `info.refname` for a remote-tracking branch |
|
|
637
|
+
| `b.checkout` | `g.checkout(name)` — does not create the branch (see above); pass `info.refname` for a remote-tracking branch |
|
|
638
|
+
| `b.create` | `g.branch_new(name)` — raises when the branch already exists |
|
|
639
|
+
| `b.delete` (local) | `g.branch_delete(name)` |
|
|
640
|
+
| `b.delete` (remote-tracking) | `g.branch_delete("#{remote}/#{name}", remotes: true)` |
|
|
641
|
+
| `b.current` | `g.current_branch == name` |
|
|
642
|
+
| `b.contains?(commit)` | `!g.branch_contains(commit, name).empty?` |
|
|
643
|
+
| `b.merge` | `g.merge(name)` |
|
|
644
|
+
| `b.merge(branch, message)` | `g.merge_into(name, branch, message)` — local `b` only; see the differences above |
|
|
645
|
+
| `b.update_ref(commit)` (local) | `g.update_ref(name, commit)` |
|
|
646
|
+
| `b.update_ref(commit)` (remote-tracking) | `g.update_ref("remotes/#{remote}/#{name}", commit)` |
|
|
647
|
+
| `b.archive(file, opts)` | `g.archive(name, file, opts)` — pass `info.refname` for a remote-tracking branch |
|
|
648
|
+
| `b.in_branch(message) { ... }` | `g.in_branch(name, message) { ... }` — local `b` only; see the differences above |
|
|
649
|
+
| `b.stashes` | `g.stashes_all` — see [`Git::Branch#stashes` deprecated](#gitbranchstashes-deprecated) |
|
|
650
|
+
|
|
365
651
|
---
|
data/git.gemspec
CHANGED
|
@@ -43,7 +43,7 @@ Gem::Specification.new do |spec|
|
|
|
43
43
|
|
|
44
44
|
spec.add_dependency 'activesupport', '>= 5.0'
|
|
45
45
|
spec.add_dependency 'addressable', '~> 2.8'
|
|
46
|
-
spec.add_dependency 'process_executer', '~> 4.
|
|
46
|
+
spec.add_dependency 'process_executer', '~> 4.1'
|
|
47
47
|
spec.add_dependency 'rchardet', '~> 1.9'
|
|
48
48
|
|
|
49
49
|
# Not every development dependency is installed on every runtime. Each predicate
|
|
@@ -101,8 +101,40 @@ Gem::Specification.new do |spec|
|
|
|
101
101
|
spec.add_development_dependency 'yard-lint', '~> 1.8' if install_yard_lint
|
|
102
102
|
|
|
103
103
|
# Specify which files should be added to the gem when it is released.
|
|
104
|
-
#
|
|
104
|
+
#
|
|
105
|
+
# This is an allowlist rather than a denylist. A denylist admitted every new
|
|
106
|
+
# development-only path by default, so the gem shipped `.github/`, `tasks/`, the
|
|
107
|
+
# husky hooks, and -- the reason this became an allowlist -- the `.claude/skills`
|
|
108
|
+
# symlink. Extracting a symlink needs a privilege that Windows grants only under
|
|
109
|
+
# Developer Mode or an elevated shell, so installing the gem there either failed
|
|
110
|
+
# outright or, on RubyGems new enough to fall back to a copy, silently duplicated
|
|
111
|
+
# the whole skills tree into the installed gem.
|
|
112
|
+
#
|
|
113
|
+
# spec/unit/gemspec_spec.rb guards both directions: nothing in the list may be a
|
|
114
|
+
# symlink, and every tracked file under lib/ must be present, so the allowlist
|
|
115
|
+
# cannot quietly drop runtime code.
|
|
116
|
+
#
|
|
117
|
+
# doc_files must stay in sync with the extra files named in .yardopts -- those are
|
|
118
|
+
# what rubydoc.info renders for the published documentation, so a file listed there
|
|
119
|
+
# but absent from the gem becomes a broken link.
|
|
120
|
+
doc_files = %w[
|
|
121
|
+
AI_POLICY.md
|
|
122
|
+
CHANGELOG.md
|
|
123
|
+
CODE_OF_CONDUCT.md
|
|
124
|
+
CONTRIBUTING.md
|
|
125
|
+
GOVERNANCE.md
|
|
126
|
+
LICENSE
|
|
127
|
+
MAINTAINERS.md
|
|
128
|
+
README.md
|
|
129
|
+
UPGRADING.md
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
# .yardopts drives the rubydoc.info build; the gemspec is included by convention.
|
|
133
|
+
build_files = %w[.yardopts git.gemspec]
|
|
134
|
+
|
|
105
135
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
106
|
-
`git ls-files -z`.split("\x0").
|
|
136
|
+
`git ls-files -z`.split("\x0").select do |f|
|
|
137
|
+
f.start_with?('lib/') || doc_files.include?(f) || build_files.include?(f)
|
|
138
|
+
end
|
|
107
139
|
end
|
|
108
140
|
end
|
data/lib/git/author.rb
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
module Git
|
|
4
4
|
# An author in a Git commit
|
|
5
5
|
#
|
|
6
|
+
# @deprecated Use {Git::AuthorInfo} instead; this mutable class will be
|
|
7
|
+
# removed in v6.0.0
|
|
8
|
+
#
|
|
6
9
|
# @api public
|
|
7
10
|
#
|
|
8
11
|
class Author
|
|
@@ -24,7 +27,15 @@ module Git
|
|
|
24
27
|
#
|
|
25
28
|
# @return [void]
|
|
26
29
|
#
|
|
30
|
+
# @deprecated Use {Git::AuthorInfo.parse} instead
|
|
31
|
+
#
|
|
27
32
|
def initialize(author_string)
|
|
33
|
+
if defined?(Git::Deprecation)
|
|
34
|
+
Git::Deprecation.warn(
|
|
35
|
+
'Git::Author is deprecated and will be removed in v6.0.0. Use Git::AuthorInfo instead.'
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
28
39
|
return unless (m = /(.*?) <(.*?)> (\d+) (.*)/.match(author_string))
|
|
29
40
|
|
|
30
41
|
@name = m[1]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
# Immutable value object representing an author or committer identity
|
|
5
|
+
#
|
|
6
|
+
# This is a lightweight, immutable data structure holding the identity data
|
|
7
|
+
# git records for commit authors, committers, and taggers. It replaces the
|
|
8
|
+
# mutable {Git::Author}, which is deprecated.
|
|
9
|
+
#
|
|
10
|
+
# @example Construct from individual values
|
|
11
|
+
# info = Git::AuthorInfo.new(
|
|
12
|
+
# name: 'John Doe',
|
|
13
|
+
# email: 'john.doe@example.com',
|
|
14
|
+
# date: Time.at(1627849923)
|
|
15
|
+
# )
|
|
16
|
+
# info.name #=> 'John Doe'
|
|
17
|
+
#
|
|
18
|
+
# @example Parse from a raw git author string
|
|
19
|
+
# info = Git::AuthorInfo.parse('John Doe <john.doe@example.com> 1627849923 +0200')
|
|
20
|
+
# info.email #=> 'john.doe@example.com'
|
|
21
|
+
# info.date.to_i #=> 1627849923
|
|
22
|
+
#
|
|
23
|
+
# @see Git::Object::Commit#author
|
|
24
|
+
#
|
|
25
|
+
# @see Git::Object::Commit#committer
|
|
26
|
+
#
|
|
27
|
+
# @api public
|
|
28
|
+
#
|
|
29
|
+
# @!attribute [r] name
|
|
30
|
+
# @return [String, nil] the person's name, or `nil` if not available
|
|
31
|
+
#
|
|
32
|
+
# @!attribute [r] email
|
|
33
|
+
# @return [String, nil] the person's email address, or `nil` if not available
|
|
34
|
+
#
|
|
35
|
+
# @!attribute [r] date
|
|
36
|
+
# @return [Time, nil] the timestamp of the change, or `nil` if not available
|
|
37
|
+
#
|
|
38
|
+
AuthorInfo = Data.define(:name, :email, :date) do
|
|
39
|
+
# Parses a raw git identity string into a Git::AuthorInfo
|
|
40
|
+
#
|
|
41
|
+
# The expected format is `"Name <email> timestamp offset"` as emitted by
|
|
42
|
+
# `git cat-file` for the `author`, `committer`, and `tagger` headers. The
|
|
43
|
+
# timestamp is interpreted as seconds since the Unix epoch; the timezone
|
|
44
|
+
# offset is not preserved in the resulting `date`.
|
|
45
|
+
#
|
|
46
|
+
# @example Parse a well-formed identity string
|
|
47
|
+
# Git::AuthorInfo.parse('John Doe <john.doe@example.com> 1627849923 +0200')
|
|
48
|
+
# #=> #<data Git::AuthorInfo name="John Doe", email="john.doe@example.com", ...>
|
|
49
|
+
#
|
|
50
|
+
# @example A string that does not match the expected format
|
|
51
|
+
# Git::AuthorInfo.parse('garbage')
|
|
52
|
+
# #=> #<data Git::AuthorInfo name=nil, email=nil, date=nil>
|
|
53
|
+
#
|
|
54
|
+
# @param author_string [String] the raw identity string to parse
|
|
55
|
+
#
|
|
56
|
+
# @return [Git::AuthorInfo] the parsed identity; all attributes are `nil`
|
|
57
|
+
# when the string does not match the expected format
|
|
58
|
+
#
|
|
59
|
+
def self.parse(author_string)
|
|
60
|
+
match = /(.*?) <(.*?)> (\d+) (.*)/.match(author_string)
|
|
61
|
+
return new(name: nil, email: nil, date: nil) unless match
|
|
62
|
+
|
|
63
|
+
new(name: match[1], email: match[2], date: Time.at(match[3].to_i))
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|