git 5.2.0 → 5.4.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.
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,35 @@ 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
+ - [Legacy stash API deprecated](#legacy-stash-api-deprecated)
24
+ - [`Git::Repository#remotes` deprecated](#gitrepositoryremotes-deprecated)
25
+ - [`Git::Remote` deprecated](#gitremote-deprecated)
26
+ - [`Git::Commands::CatFile::Raw` `allow_unknown_type` option deprecated](#gitcommandscatfileraw-allow_unknown_type-option-deprecated)
27
+ - [`Git::Branch` and `Git::Branches` deprecated](#gitbranch-and-gitbranches-deprecated)
28
+ - [`Git::Object::Tag` deprecated](#gitobjecttag-deprecated)
29
+ - [`Git::Status` deprecated](#gitstatus-deprecated)
30
+ - [`Git::Worktree` and `Git::Worktrees` deprecated](#gitworktree-and-gitworktrees-deprecated)
31
+
32
+ ## Upgrading to v6.0.0
33
+
34
+ v6.0.0 is not yet released. This section will be completed when it ships.
35
+
36
+ v6.0.0 removes the APIs deprecated during v5.x under the project's
37
+ [deprecation policy](README.md#deprecation-policy).
38
+ [Issue 1717](https://github.com/ruby-git/ruby-git/issues/1717) tracks its scope.
39
+
40
+ To prepare:
41
+
42
+ 1. Upgrade to the latest v5.x release.
43
+ 2. Set `GIT_DEPRECATION_BEHAVIOR=raise` (or `Git::Deprecation.behavior = :raise`) in
44
+ your test suite and, if possible, staging.
45
+ 3. Fix each deprecation using the entries under
46
+ [Deprecated methods](#deprecated-methods) until the suite is clean.
47
+ 4. Upgrade to v6.0.0.
48
+
49
+ ---
20
50
 
21
51
  ## Upgrading to v5.x
22
52
 
@@ -208,7 +238,7 @@ to the replacement shown to silence it.
208
238
  | `g.lib.config_list` | `g.config_list` — returns `Array<Git::ConfigEntryInfo>` |
209
239
  | `g.lib.config_set(name, value)` | `g.config_set(name, value)` |
210
240
  | `g.lib.git_version` | `g.git_version` |
211
- | `g.lib.stash_list` | `g.stashes_all` |
241
+ | `g.lib.stash_list` | `g.stash_infos` — returns `Array<Git::StashInfo>`, newest first |
212
242
  | `g.lib.unmerged` | `g.unmerged` |
213
243
  | `g.lib.change_head_branch(name)` | `g.change_head_branch(name)` |
214
244
  | `g.lib.ls_remote(location, opts)` | `g.ls_remote(location, opts)` |
@@ -240,7 +270,7 @@ shim cannot forward them). Update call sites directly:
240
270
 
241
271
  | v4.x call | Notes |
242
272
  |-----------|-------|
243
- | `g.lib.list_files(ref_dir)` | Walked `.git/refs/` directly. Use `g.branches`, `g.tags`, or `g.remotes` instead. |
273
+ | `g.lib.list_files(ref_dir)` | Walked `.git/refs/` directly. Use `g.branch_list`, `g.tag_list`, or `g.remote_list` instead. |
244
274
 
245
275
  ##### Internal plumbing methods (no replacement)
246
276
 
@@ -346,7 +376,7 @@ purpose-named methods.
346
376
  | `g.global_config` | `g.config_list(global: true)` |
347
377
  | `g.global_config(name, value)` | `g.config_set(name, value, global: true)` |
348
378
  | `g.parse_config(file)` | `g.config_list(file: file)` |
349
- | `g.stash_list` | `g.stashes_all` |
379
+ | `g.stash_list` | `g.stash_infos` — returns `Array<Git::StashInfo>`; see [Legacy stash API deprecated](#legacy-stash-api-deprecated) |
350
380
 
351
381
  #### `Git` module mixin deprecations
352
382
 
@@ -362,4 +392,558 @@ as bare methods is deprecated:
362
392
  | `include Git; global_config(name, value)` | `Git.config_set(name, value, global: true)` |
363
393
  | `include Git; global_config` | `Git.config_list(global: true)` |
364
394
 
395
+ #### `Git::Author` deprecated
396
+
397
+ Starting in v5.3.0, methods that return author, committer, or tagger data —
398
+ `Git::Object::Commit#author`, `Git::Object::Commit#committer`,
399
+ `Git::Object::Tag#tagger`, and `Git::TagInfo#tagger` — return an immutable
400
+ `Git::AuthorInfo` value object instead of the mutable `Git::Author`.
401
+
402
+ `Git::AuthorInfo` exposes the same `name`, `email`, and `date` readers, so code
403
+ that only reads these attributes needs no changes. Code that mutated a
404
+ `Git::Author` (via `name=`, `email=`, or `date=`) must be updated:
405
+ `Git::AuthorInfo` is frozen, and `#with` returns a modified copy rather than
406
+ updating in place (e.g. `info = info.with(name: 'New Name')`).
407
+
408
+ Constructing `Git::Author` directly emits a deprecation warning naming
409
+ `Git::AuthorInfo` as the replacement. The class is removed in v6.0.0.
410
+
411
+ | Deprecated usage | Replacement |
412
+ |-----------------|-------------|
413
+ | `Git::Author.new('Name <email> 1627849923 +0200')` | `Git::AuthorInfo.parse('Name <email> 1627849923 +0200')` |
414
+ | `author.name = 'New Name'` | `author = author.with(name: 'New Name')` (returns a new object) |
415
+
416
+ #### `Git::Branch#stashes` deprecated
417
+
418
+ `Git::Branch#stashes` ignores the branch it is called on and returns every stash
419
+ in the repository, so `g.branch('feature').stashes` and `g.branch('main').stashes`
420
+ return the same entries. Call `Git::Repository#stash_infos` instead; it is the
421
+ query `Git::Branch#stashes` was already running.
422
+
423
+ > **Return type change:** `Git::Branch#stashes` returns a `Git::Stashes`
424
+ > collection of `Git::Stash` objects. `g.stash_infos` returns an array of
425
+ > `Git::StashInfo` values. Both are newest first, so indexes carry over unchanged.
426
+ > `Git::Stash#message` strips the `WIP on <branch>:` or `On <branch>:` prefix;
427
+ > `Git::StashInfo#message` keeps the full message and exposes the branch name as
428
+ > `Git::StashInfo#branch`.
429
+
430
+ `Git::Stashes` also exposes `save`, `apply`, and `clear`. Those map to the
431
+ repository's `stash_push`, `stash_apply`, and `stash_clear`. `Git::Stashes#apply(i)`
432
+ already passed `i` to git as `stash@{i}` (`0` = newest), and `g.stash_apply(i)` does
433
+ the same, so that index needs no conversion. The `Git::Stashes` class is deprecated
434
+ as well; [Legacy stash API deprecated](#legacy-stash-api-deprecated) maps each of
435
+ its methods.
436
+
437
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
438
+ |-----------------------------------------------------|-------------|
439
+ | `g.branch(name).stashes` | `g.stash_infos` — returns `Array<Git::StashInfo>`, newest first |
440
+ | `g.branch(name).stashes.each { \|s\| puts s.message }` | `g.stash_infos.each { \|info\| puts info.message }` |
441
+ | `g.branch(name).stashes.all` (`[index, message]` pairs, oldest first) | `g.stash_infos.reverse` — see the ordering note in [Legacy stash API deprecated](#legacy-stash-api-deprecated) |
442
+ | `g.branch(name).stashes.size` | `g.stash_infos.size` |
443
+ | `g.branch(name).stashes[i].message` (`0` = newest, `i` coerced with `to_i`) | `g.stash_infos[i.to_i].message` |
444
+ | `g.branch(name).stashes.save(message)` | `g.stash_push(message: message)` |
445
+ | `g.branch(name).stashes.apply` | `g.stash_apply` |
446
+ | `g.branch(name).stashes.apply(i)` (`0` = newest) | `g.stash_apply(i)` |
447
+ | `g.branch(name).stashes.clear` | `g.stash_clear` — returns git's stdout (normally `""`, which is truthy) where `Git::Stashes#clear` returned `nil` |
448
+
449
+ #### Legacy stash API deprecated
450
+
451
+ Starting in v5.4.0, the stash methods on `Git::Repository` are built around the
452
+ immutable `Git::StashInfo` value object. `g.stash_infos` returns every entry as a
453
+ `Git::StashInfo`, and `stash_push`, `stash_pop`, `stash_drop`, `stash_show`,
454
+ `stash_branch`, `stash_create`, and `stash_store` each map onto the `git stash`
455
+ subcommand of the same name. Every method that takes a stash (`stash_apply`,
456
+ `stash_pop`, `stash_drop`, `stash_show`, `stash_branch`) accepts a `Git::StashInfo`,
457
+ a `stash@{N}` name, an Integer index (`0` = newest), or `nil` for the newest entry.
458
+
459
+ The legacy methods and classes are deprecated and removed in v6.0.0:
460
+ `Git::Repository#stashes_all`, `Git::Repository#stash_save`,
461
+ `Git::Repository#stash_list`, `Git::Stash`, and `Git::Stashes`. Constructing a
462
+ `Git::Stash` or `Git::Stashes` emits one warning per object.
463
+
464
+ > **Ordering flip:** `g.stashes_all` returns entries **oldest first** with a
465
+ > sequential index of its own (`0` is the oldest). `g.stash_infos` returns entries
466
+ > **newest first**, the order `git stash list` uses, and `Git::StashInfo#index` is
467
+ > git's own `stash@{N}` number (`0` is the newest). `g.stashes_all.first` is
468
+ > `g.stash_infos.last`. Code that reads an entry by position must reverse the
469
+ > array or the index.
470
+
471
+ > **Message difference:** `g.stashes_all` strips the `WIP on <branch>:` or
472
+ > `On <branch>:` prefix from each message. `Git::StashInfo#message` keeps the full
473
+ > message git stores, and `Git::StashInfo#branch` holds the branch name. A stash
474
+ > created from a detached HEAD has the branch `"(no branch)"`, the label git writes
475
+ > in its message. `branch` is `nil` only when the message has no branch prefix at
476
+ > all, as for a `stash_store` entry with a custom message.
477
+
478
+ `g.stash_save(message)` returned `true` when it created a stash and `false` when
479
+ there were no local changes to save. `g.stash_push(message: message)` returns the
480
+ new `Git::StashInfo`, or `nil` when there were no local changes, so a truthiness
481
+ check such as `if g.stash_push(message: 'WIP')` still works.
482
+
483
+ `g.stash_list` returned the `git stash list` text as a String. Build that text from
484
+ `g.stash_infos` if you need it. In v6.0.0, `stash_list` returns
485
+ `Array<Git::StashInfo>`, the same value as `stash_infos`, and `stash_infos` stays as
486
+ a permanent alias. Move String callers of `stash_list` to `stash_infos` before
487
+ upgrading so the return type change cannot go unnoticed.
488
+
489
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
490
+ |-----------------------------------------------------|-------------|
491
+ | `g.stashes_all` | `g.stash_infos` — returns `Array<Git::StashInfo>`, newest first |
492
+ | `g.stashes_all.each { \|index, message\| ... }` | `g.stash_infos.reverse_each.with_index { \|info, index\| ... info.message }` |
493
+ | `g.stashes_all[i]` (`0` = oldest) | `g.stash_infos.reverse[i]` |
494
+ | `g.stashes_all.last` | `g.stash_infos.first` |
495
+ | `g.stash_save(message)` | `g.stash_push(message: message)` — returns `Git::StashInfo` or `nil` |
496
+ | `g.stash_list` (String) | `g.stash_infos.map { \|s\| "#{s.name}: #{s.message}" }.join("\n")` |
497
+ | `Git::Stash.new(g, message)` | `info = g.stash_push(message: message)` |
498
+ | `Git::Stash.new(g, message, existing: true)` | `message` — `existing: true` only wrapped the String and never looked an entry up; code that needs a real entry picks one from `g.stash_infos` by index or name |
499
+ | `stash.save` | `info = g.stash_push(message: message)` |
500
+ | `stash.saved?` | `!info.nil?` — check the value `stash_push` returned rather than pushing again |
501
+ | `stash.message` / `stash.to_s` | `info.message` — keeps the branch prefix; see the note above |
502
+ | `Git::Stashes.new(g)` | `g.stash_infos` |
503
+ | `stashes.all` (`[index, message]` pairs, oldest first) | `g.stash_infos.reverse` — see the ordering note above |
504
+ | `stashes.each { \|s\| ... }` (newest first) | `g.stash_infos.each { \|info\| ... }` |
505
+ | `stashes[i]` (`0` = newest, `i` coerced with `to_i`) | `g.stash_infos[i.to_i]` |
506
+ | `stashes.size` | `g.stash_infos.size` |
507
+ | `stashes.save(message)` | `g.stash_push(message: message)` |
508
+ | `stashes.apply` / `stashes.apply(i)` | `g.stash_apply` / `g.stash_apply(i)` |
509
+ | `stashes.clear` | `g.stash_clear` — returns git's stdout (normally `""`, which is truthy) where `Git::Stashes#clear` returned `nil` |
510
+
511
+ #### `Git::Repository#remotes` deprecated
512
+
513
+ `Git::Repository#remotes` is deprecated in favor of `Git::Repository#remote_list`
514
+ and is removed in v6.0.0. Its return value is unchanged. Calling `remotes` emits
515
+ one deprecation warning for itself plus one `Git::Remote` constructor warning for
516
+ each remote it returns (see the `Git::Remote` deprecation below), so a repository
517
+ with N remotes produces N + 1 warnings per call.
518
+
519
+ > **Return type change:** `remotes` returns `Array<Git::Remote>` — mutable
520
+ > objects with `name`, `url`, and `fetch_opts` accessors and `fetch`, `merge`,
521
+ > `branch`, and `remove` operations. `remote_list` returns
522
+ > `Array<Git::RemoteInfo>` — immutable value objects read from the repository's
523
+ > git config, with fields such as `name`, `url`, `push_url`, `fetch`, and `push`.
524
+ > Because a remote may carry more than one URL or refspec, `url`, `push_url`,
525
+ > `fetch`, and `push` are always frozen `Array<String>`. When a remote has more
526
+ > than one URL, git fetches from the first; the legacy `Git::Remote#url` returned
527
+ > the last one configured, so use `r.url.last` to reproduce that exact value.
528
+ > Likewise, `Git::Remote#fetch_opts` returned only the last configured fetch
529
+ > refspec, while `fetch` holds all of them. Operations that lived on
530
+ > `Git::Remote` are called on the repository with the remote name instead.
531
+ >
532
+ > **Order change:** `remotes` lists remotes in the order `git remote` prints
533
+ > them, while `remote_list` keeps the order in which remotes first appear in the
534
+ > config. When the legacy order matters, iterate `g.remote_names` (the same
535
+ > `git remote` order) or sort `g.remote_list` explicitly.
536
+
537
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
538
+ |-----------------------------------------------------|-------------|
539
+ | `g.remotes` | `g.remote_list` — returns `Array<Git::RemoteInfo>` |
540
+ | `g.remotes.map(&:name)` | `g.remote_list.map(&:name)` or `g.remote_names` |
541
+ | `g.remotes.map(&:to_s)` | `g.remote_list.map(&:name)` — `Git::RemoteInfo#to_s` is not the name |
542
+ | `g.remotes.map(&:url)` | `g.remote_list.map { \|r\| r.url.first }` — `url` is an `Array<String>` |
543
+ | `g.remotes.map(&:fetch_opts)` | `g.remote_list.map { \|r\| r.fetch.last }` — `fetch` holds every refspec |
544
+ | `g.remotes.each(&:fetch)` | `g.remote_names.each { \|name\| g.fetch(name) }` — same order as `remotes` |
545
+ | `remote.fetch` | `g.fetch(remote.name)` |
546
+ | `remote.fetch(opts)` | `g.fetch(remote.name, opts)` — same options hash |
547
+ | `remote.merge` | `g.merge("#{remote.name}/#{g.current_branch}")` |
548
+ | `remote.merge(branch)` | `g.merge("#{remote.name}/#{branch}")` |
549
+ | `remote.branch` | `g.branch_list("#{remote.name}/#{g.current_branch}").first` — returns a `Git::BranchInfo` |
550
+ | `remote.branch(name)` | `g.branch_list("#{remote.name}/#{name}").first` — returns a `Git::BranchInfo` |
551
+ | `remote.remove` | `g.remote_remove(remote.name)` |
552
+
553
+ #### `Git::Remote` deprecated
554
+
555
+ `Git::Remote`, `Git::Repository#remote`, and `Git::Repository#config_remote` are
556
+ deprecated and are removed in v6.0.0. Read a remote's configuration through
557
+ `Git::Repository#remote_list`, which returns one `Git::RemoteInfo` value object per
558
+ remote, and call the repository-level operations (`fetch`, `merge`, `branch_list`,
559
+ `remote_remove`) with the remote name. Return values are unchanged. Constructing a
560
+ `Git::Remote` directly emits one deprecation warning, and so does calling
561
+ `g.config_remote`. Calling `g.remote` emits two: one for `Git::Repository#remote`
562
+ and one for the `Git::Remote` it constructs. Likewise `g.remotes` emits one warning
563
+ for itself plus one per `Git::Remote` it returns (N + 1 for N remotes). The extra
564
+ warnings from `g.remote` and `g.remotes` are expected, not a bug.
565
+
566
+ > **Return type changes:** `Git::RemoteInfo#url` and `Git::RemoteInfo#fetch` are
567
+ > frozen `Array<String>` because a remote may carry more than one URL or fetch
568
+ > refspec. The legacy `Git::Remote#url` and `Git::Remote#fetch_opts` returned only
569
+ > the last configured value, so `r.url.last` and `r.fetch.last` reproduce them
570
+ > exactly; `r.url.first` is the URL git actually fetches from.
571
+ > `config_remote` returned a flat `Hash{String => String}` in which a repeated
572
+ > `url` or `fetch` key overwrote the earlier value, so it could not report every
573
+ > configured URL or refspec; `remote_list` keeps all of them. In the other
574
+ > direction, `Git::RemoteInfo` models only the remote variables git defines and
575
+ > drops any other `remote.<name>.*` key, while `config_remote` returned every key.
576
+ > Code that reads custom keys should filter `g.config_list` instead (see the
577
+ > table); that yields the same `Hash{String => String}` as `config_remote`.
578
+ > `Git::Remote#branch` returned a `Git::Branch`. Its replacement,
579
+ > `g.branch_list("#{name}/#{branch}").first`, returns a `Git::BranchInfo` value
580
+ > object, or `nil` when the remote-tracking branch does not exist.
581
+
582
+ In the table, `name` is the remote name (`g.remote` defaults it to `'origin'`).
583
+
584
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
585
+ |-----------------------------------------------------|-------------|
586
+ | `g.remote` | `g.remote_list.find { \|r\| r.name == 'origin' }` — returns a `Git::RemoteInfo`; the deprecated call emits two warnings |
587
+ | `g.remote(name)` | `g.remote_list.find { \|r\| r.name == name }` — returns a `Git::RemoteInfo`; the deprecated call emits two warnings |
588
+ | `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` |
589
+ | `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}` |
590
+ | `remote.name`, `remote.to_s` | `g.remote_list.find { \|r\| r.name == name }.name` or `g.remote_names` |
591
+ | `remote.url` | `g.remote_list.find { \|r\| r.name == name }.url` — `Array<String>`; `.first` for the single-URL case |
592
+ | `remote.fetch_opts` | `g.remote_list.find { \|r\| r.name == name }.fetch` — `Array<String>` of refspecs |
593
+ | `remote.fetch` | `g.fetch(name)` |
594
+ | `remote.fetch(opts)` | `g.fetch(name, opts)` — same option keys |
595
+ | `remote.merge` | `g.merge("#{name}/#{g.current_branch}")` |
596
+ | `remote.merge(branch)` | `g.merge("#{name}/#{branch}")` |
597
+ | `remote.branch` | `g.branch_list("#{name}/#{g.current_branch}").first` — returns a `Git::BranchInfo` |
598
+ | `remote.branch(branch)` | `g.branch_list("#{name}/#{branch}").first` — returns a `Git::BranchInfo` |
599
+ | `remote.remove` | `g.remote_remove(name)` |
600
+
601
+ #### `Git::Commands::CatFile::Raw` `allow_unknown_type` option deprecated
602
+
603
+ The `allow_unknown_type:` option of `Git::Commands::CatFile::Raw` is deprecated
604
+ and is removed in v6.0.0. Passing it emits a deprecation warning; the
605
+ `--allow-unknown-type` flag still reaches git unchanged until the option is
606
+ removed.
607
+
608
+ There is no replacement. Git 2.50 removed the unknown-type feature, so on git
609
+ 2.50 and later `--allow-unknown-type` is an accepted no-op and the option has no
610
+ effect. On git 2.28 through 2.49 the flag still lets `t: true` and `s: true`
611
+ report the type and size of an object whose type git does not recognize, but
612
+ that behavior is dropped together with the option. The class is internal
613
+ (`@api private`) and no `Git::Repository` method passes the option, so only code
614
+ that constructs the command class directly is affected.
615
+
616
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
617
+ |-----------------------------------------------------|-------------|
618
+ | `Git::Commands::CatFile::Raw.new(ctx).call(sha, t: true, allow_unknown_type: true)` | `Git::Commands::CatFile::Raw.new(ctx).call(sha, t: true)` |
619
+ | `Git::Commands::CatFile::Raw.new(ctx).call(sha, s: true, allow_unknown_type: true)` | `Git::Commands::CatFile::Raw.new(ctx).call(sha, s: true)` |
620
+
621
+ #### `Git::Branch` and `Git::Branches` deprecated
622
+
623
+ `Git::Branch`, `Git::Branches`, `Git::Repository#branch`, and
624
+ `Git::Repository#branches` are deprecated and are removed in v6.0.0. Read branch
625
+ data through `Git::Repository#branch_list`, which returns one `Git::BranchInfo`
626
+ value object per local and remote-tracking branch, and call the repository-level
627
+ operations (`checkout`, `branch_new`, `branch_delete`, `merge`, `merge_into`,
628
+ `in_branch`, and so on) with the branch name. Calling `g.branch` or `g.branches`,
629
+ constructing a `Git::Branches`, and calling any operation on a `Git::Branch` each
630
+ emit a deprecation warning; their return values are unchanged. The `full`,
631
+ `name`, `remote`, `to_s`, and `to_a` readers on `Git::Branch` do not warn.
632
+
633
+ > **Return shape change:** `Git::Branch` exposes `full` (`main` or
634
+ > `remotes/origin/main`), `name`, and `remote` (a `Git::Remote`, or `nil`).
635
+ > `Git::BranchInfo` exposes `refname` (always the full ref: `refs/heads/main` or
636
+ > `refs/remotes/origin/main`), `short_name` (`main` for both), `remote_name` (a
637
+ > `String`, or `nil`), `remote?`, `current?`, `target_oid`, `upstream`,
638
+ > `worktree_path`, and `symref`. `Git::BranchInfo#to_s` is the full ref, not the
639
+ > `remotes/origin/main` form `Git::Branch#to_s` returned. `branch_list` takes
640
+ > `git branch --list` patterns: `'main'` matches the local branch and
641
+ > `'origin/main'` matches the remote-tracking branch. The `remotes/origin/main`
642
+ > and `refs/...` forms that `g.branches[...]` accepted match nothing.
643
+ >
644
+ > **`checkout` no longer creates the branch:** `g.branch('x').checkout` created
645
+ > `x` when it did not exist, ignoring any error from that attempt, and then
646
+ > checked it out. `g.checkout('x')` does not create a missing local branch,
647
+ > with one exception that is git's own: when exactly one remote has a branch
648
+ > named `x`, git creates a local tracking branch from it (its default guess
649
+ > behavior). Otherwise the checkout fails. To reproduce create-or-checkout,
650
+ > call `g.branch_new('x') unless g.local_branch?('x')` and then
651
+ > `g.checkout('x')`. Use `g.checkout('x', new_branch: true)` only when `x` is
652
+ > known not to exist; like `g.branch_new('x')`, it fails when `x` already
653
+ > exists. Likewise `g.branch('x').create` ignored every error, while
654
+ > `g.branch_new('x')` raises `Git::FailedError` when `x` already exists.
655
+ >
656
+ > **`in_branch` and `merge_into` differences:**
657
+ >
658
+ > 1. **Branch creation.** `g.branch('x').in_branch { ... }` created `x` if it did
659
+ > not exist. `g.in_branch('x') { ... }` raises `ArgumentError` unless `x` is an
660
+ > existing local branch, so call `g.branch_new('x')` first. A commit SHA, tag,
661
+ > or remote-tracking name is also rejected before any checkout.
662
+ > 2. **Detached HEAD.** `Git::Branch#in_branch` recorded the literal `HEAD` and
663
+ > could not restore a detached HEAD to its original commit. `g.in_branch` and
664
+ > `g.merge_into` record the SHA and restore it.
665
+ > 3. **Unborn HEAD.** Both new methods raise `Git::Error` before checking anything
666
+ > out when HEAD is on a branch with no commits. The old methods failed later,
667
+ > mid-flow.
668
+ > 4. **Merge overload.** `g.branch('main').merge('feature')` returned stdout from
669
+ > the final restore checkout and ran a hard reset after the merge.
670
+ > `g.merge_into('main', 'feature')` returns the merge's stdout and does no
671
+ > reset. It also rejects the `:no_commit` option; callers who need
672
+ > `--no-commit` use `checkout` and `merge` directly.
673
+ > 5. **Remote-tracking receivers.** Called on a remote-tracking `Git::Branch`,
674
+ > `in_branch` and `merge(branch)` checked out the remote-tracking ref,
675
+ > detaching HEAD, and any commit made there was left dangling. `g.in_branch`
676
+ > and `g.merge_into` take an existing local branch only. Create one from the
677
+ > remote-tracking ref first, with
678
+ > `g.branch_new(name, "remotes/#{remote}/#{name}")`, and pass that branch.
679
+
680
+ In the table, `name` is the branch name (`g.branch` defaults it to the current
681
+ branch), `remote` is the remote name of a remote-tracking branch, `b` is a
682
+ `Git::Branch`, and `info` is the `Git::BranchInfo` that replaces it. Where a
683
+ row says to pass `info.refname` for a remote-tracking branch, `b.full` (the
684
+ `remotes/<remote>/<name>` form) works too; the shorter `"#{remote}/#{name}"`
685
+ can resolve a local branch of that name and is only used where git expects it
686
+ (`branch_delete` with `remotes: true`).
687
+
688
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
689
+ |-----------------------------------------------------|-------------|
690
+ | `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 |
691
+ | `g.branch` | `g.branch_list(g.current_branch).first` — `nil` when HEAD is detached or unborn; use `g.current_branch_state` there |
692
+ | `g.branches` | `g.branch_list` — returns `Array<Git::BranchInfo>` |
693
+ | `g.branches[name]` | `g.branch_list(name).first`, or `g.branch_list("#{remote}/#{name}").find(&:remote?)` for a remote-tracking branch |
694
+ | `g.branches.local` | `g.branch_list.reject(&:remote?)` |
695
+ | `g.branches.remote` | `g.branch_list.select(&:remote?)` |
696
+ | `g.branches.size` | `g.branch_list.size` |
697
+ | `g.branches.each { \|b\| ... }` | `g.branch_list.each { \|info\| ... }` |
698
+ | `g.branches.to_s` | `g.branch_list.map { \|i\| "#{i.current? ? '* ' : ' '}#{i.refname}\n" }.join` — full refs, not `remotes/...` |
699
+ | `b.full`, `b.to_s` | `info.refname` — `refs/remotes/origin/main` rather than `remotes/origin/main` |
700
+ | `b.to_a` | `[info.refname]` |
701
+ | `b.name` | `info.short_name` |
702
+ | `b.remote` | `info.remote_name` — a `String`, or `nil` for a local branch |
703
+ | `b.gcommit` | `g.gcommit(name)` — pass `info.refname` for a remote-tracking branch |
704
+ | `b.checkout` | `g.checkout(name)` — does not create the branch (see above); pass `info.refname` for a remote-tracking branch |
705
+ | `b.create` | `g.branch_new(name)` — raises when the branch already exists |
706
+ | `b.delete` (local) | `g.branch_delete(name)` |
707
+ | `b.delete` (remote-tracking) | `g.branch_delete("#{remote}/#{name}", remotes: true)` |
708
+ | `b.current` | `g.current_branch == name` |
709
+ | `b.contains?(commit)` | `!g.branch_contains(commit, name).empty?` |
710
+ | `b.merge` | `g.merge(name)` |
711
+ | `b.merge(branch, message)` | `g.merge_into(name, branch, message)` — local `b` only; see the differences above |
712
+ | `b.update_ref(commit)` (local) | `g.update_ref(name, commit)` |
713
+ | `b.update_ref(commit)` (remote-tracking) | `g.update_ref("remotes/#{remote}/#{name}", commit)` |
714
+ | `b.archive(file, opts)` | `g.archive(name, file, opts)` — pass `info.refname` for a remote-tracking branch |
715
+ | `b.in_branch(message) { ... }` | `g.in_branch(name, message) { ... }` — local `b` only; see the differences above |
716
+ | `b.stashes` | `g.stash_infos` — see [`Git::Branch#stashes` deprecated](#gitbranchstashes-deprecated) |
717
+
718
+ #### `Git::Object::Tag` deprecated
719
+
720
+ `Git::Object::Tag`, `Git::Repository#tag`, `Git::Repository#tags`, and
721
+ `Git::Repository#tag_add` are deprecated and are removed in v6.0.0. Read tag data
722
+ through `Git::Repository#tag_list`, which returns one `Git::TagInfo` value object per
723
+ tag, create tags with `Git::Repository#tag_create`, which returns the new tag's
724
+ `Git::TagInfo`, and call the repository-level operations (`archive`, `log`, `diff`,
725
+ `cat_file_contents`, and so on) with the tag's object ID,
726
+ `info.oid || info.target_oid`, which is the object a `Git::Object::Tag` pinned when
727
+ it was constructed. Calling `g.tag`, `g.tags`, or
728
+ `g.tag_add`, and constructing a `Git::Object::Tag`, each emit one deprecation
729
+ warning; their return values are unchanged. `g.add_tag` already warned, pointing at
730
+ `g.tag_add`, and now emits two warnings for a creation call, one for itself and one
731
+ for the `g.tag_add` it calls; `g.add_tag(name, d: true)` emits three, adding the
732
+ `:d`/`:delete` warning described below. The readers on a `Git::Object::Tag` do not
733
+ warn.
734
+
735
+ > **Return shape change:** `Git::Object::Tag` exposes `name`, `sha`, `objectish`,
736
+ > `annotated?`, `message`, and `tagger`. `Git::TagInfo` exposes `name`, `oid`,
737
+ > `target_oid`, `objecttype`, `annotated?`, `lightweight?`, `message`, and
738
+ > `tagger`. `name` and `annotated?` are unchanged. `tagger` keeps the same `name`
739
+ > and `email`, but `tagger.date` differs: `t.tagger.date` is a `Time` in the
740
+ > process's local zone, while `info.tagger.date` keeps the UTC offset recorded in
741
+ > the tag object. Both name the same instant. `message` differs for an annotated
742
+ > tag created with an empty message (`message: ''`): `t.message` returns `""` and
743
+ > `info.message` returns `nil`, the same value a lightweight tag has. `t.sha` and
744
+ > `t.objectish` are the tag object's ID for an annotated tag and
745
+ > the tagged object's ID for a lightweight tag. `Git::TagInfo` separates the two:
746
+ > `oid` is the tag object's ID (`nil` for a lightweight tag) and `target_oid` is
747
+ > the ID of the object the tag points to (set for both kinds), so
748
+ > `info.oid || info.target_oid` reproduces `t.sha`. The target is usually a
749
+ > commit, but a tag can point at any git object, and `info.objecttype` reports
750
+ > which kind (`tag` for an annotated tag, or the target's own type such as
751
+ > `commit` or `blob` for a lightweight one).
752
+ >
753
+ > **Missing tags:** `g.tag(name)` raises `Git::UnexpectedResultError` when no tag
754
+ > has that name. `g.tag_list(name).first` returns `nil`.
755
+ >
756
+ > **Deleting through `tag_add`:** `g.tag_add(name, d: true)`, which was already
757
+ > deprecated, deletes the tag and emits a second warning pointing at
758
+ > `g.tag_delete`. `g.tag_create` rejects `:d` and `:delete` with `ArgumentError`.
759
+ >
760
+ > **Extra positional arguments:** `g.tag_add(name, target, extra)` ignores
761
+ > `extra` and tags `target`. `g.tag_create` raises `ArgumentError` when more than
762
+ > one positional argument follows the name.
763
+ >
764
+ > **Object identity:** every `Git::Object::Tag` resolves its tag to an object ID
765
+ > when it is constructed and runs `size`, `contents`, `grep`, `diff`, `log`, and
766
+ > `archive` against that ID, so moving or deleting the tag afterwards does not
767
+ > redirect an existing object. `Git::Object::Tag.new(g, sha, name)` uses the
768
+ > supplied `sha` as that ID; the other forms look it up from the ref. `annotated?`,
769
+ > `message`, and `tagger` always read the ref `name`. `Git::TagInfo` describes the
770
+ > ref only: `g.tag_list(name).first` returns whatever `name` points at now, or
771
+ > `nil` once the tag is deleted. Keep the same identity by passing `id` (see the
772
+ > table) rather than `name` to the operation replacements; they accept any object.
773
+ > To read an annotated tag object by ID without going through its ref, use
774
+ > `g.cat_file_tag(id)`, which returns the tag object's `object`, `type`, `tag`,
775
+ > `tagger`, and `message`.
776
+
777
+ In the table, `name` is the tag name, `t` is a `Git::Object::Tag`, `info` is the
778
+ `Git::TagInfo` that replaces it, and `id` is `info.oid || info.target_oid` (or the
779
+ `sha` given to the three-argument constructor), the object `t` pinned.
780
+
781
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
782
+ |-----------------------------------------------------|-------------|
783
+ | `g.tag(name)` | `g.tag_list(name).first` — a `Git::TagInfo`, or `nil` when the tag does not exist |
784
+ | `g.tags` | `g.tag_list` — returns `Array<Git::TagInfo>` |
785
+ | `g.tags.map(&:name)` | `g.tag_list.map(&:name)` |
786
+ | `g.tag_add(name, opts)` | `g.tag_create(name, opts)` — returns a `Git::TagInfo` |
787
+ | `g.tag_add(name, target, opts)` | `g.tag_create(name, target, opts)` |
788
+ | `g.tag_add(name, d: true)` | `g.tag_delete(name)` |
789
+ | `g.add_tag(name, opts)`, `g.add_tag(name, target, opts)` | `g.tag_create(name, ...)` — its warning names `g.tag_add`, which is deprecated too; go straight to `g.tag_create` |
790
+ | `g.add_tag(name, d: true)` | `g.tag_delete(name)` — `g.tag_create` rejects `:d`; see the deletion note above |
791
+ | `Git::Object::Tag.new(g, name)` | `g.tag_list(name).first` |
792
+ | `Git::Object::Tag.new(g, sha, name)` | `g.tag_list(name).first` — reads the ref rather than `sha`; use `sha` as `id` for the operations below, or read the object with `g.cat_file_tag(sha)`; see the object identity note above |
793
+ | `Git::Object.new(g, name, nil, true)` | `g.tag_list(name).first` — its warning names `Git::Object::Tag.new`, which is deprecated too |
794
+ | `t.name` | `info.name` |
795
+ | `t.sha`, `t.objectish`, `t.to_s` | `info.oid \|\| info.target_oid` — see the return shape change above |
796
+ | `t.annotated?` | `info.annotated?` |
797
+ | `t.message` | `info.message` — `nil` rather than `""` for an annotated tag with an empty message |
798
+ | `t.tagger` | `info.tagger` — `date` keeps the recorded UTC offset; see the return shape change above |
799
+ | `t.tag?` | not needed; every `Git::TagInfo` is a tag |
800
+ | `t.size` | `g.cat_file_size(id)` — `id` rather than `name` keeps this and the operations below on the object `t` pinned; see the object identity note above |
801
+ | `t.contents` | `g.cat_file_contents(id)` |
802
+ | `t.contents { \|file\| ... }` | `g.cat_file_contents(id) { \|file\| ... }` — streams to a temporary file instead of buffering the object |
803
+ | `t.contents_array` | `g.cat_file_contents(id).split("\n")` |
804
+ | `t.grep(string, path, opts)` | `g.grep(string, path, opts.merge(object: id))` |
805
+ | `t.diff(other)` | `g.diff(id, other)` |
806
+ | `t.log(count)` | `g.log(count).object(id)` |
807
+ | `t.archive(file, opts)` | `g.archive(id, file, opts)` |
808
+
809
+ #### `Git::Status` deprecated
810
+
811
+ Starting in v5.4.0, `Git::Status`, `Git::Status::StatusFile`, and
812
+ `Git::Repository#status` are deprecated and will be removed in v6.0.0. Read the
813
+ index and working tree state through `Git::Repository#status_info`, which
814
+ returns an immutable `Git::StatusInfo` holding one `Git::StatusFileInfo` per
815
+ path that `git status --porcelain=v2` reports. Calling `g.status` emits one
816
+ deprecation warning, and so does constructing a `Git::Status` directly.
817
+
818
+ `Git::StatusInfo` keeps the `changed`, `added`, `deleted`, and `untracked`
819
+ readers and the `changed?`, `added?`, `deleted?`, and `untracked?` predicates,
820
+ so code that only uses those can change `status` to `status_info` and needs
821
+ no other edit, subject to the category differences below. The readers now
822
+ return `Hash{String => Git::StatusFileInfo}`, and a new `unmerged` reader
823
+ lists conflicted paths, which `Git::Status` did not report. The predicates
824
+ still compare paths case-insensitively when `core.ignoreCase` is `true`.
825
+ `Git::StatusInfo` is not `Enumerable`; iterate `status_info.files`, an
826
+ `Array<Git::StatusFileInfo>` in git's output order.
827
+
828
+ The categories are derived differently. `Git::Status` gave each file one
829
+ `type`, and `changed` held only files whose type was `M`, so `changed`,
830
+ `added`, and `deleted` were disjoint: a file staged as new and then modified
831
+ in the working tree was only `added`. `Git::StatusInfo` derives the
832
+ categories from both status characters, so `changed` also includes type
833
+ changes (`T`), and one path can be in more than one category: that same file
834
+ (`AM`) is in both `added` and `changed`, and a file modified in the index and
835
+ then deleted from the working tree (`MD`) is in both `changed` and `deleted`.
836
+ Code that relied on the sets being disjoint should test `index_status` and
837
+ `worktree_status` directly.
838
+
839
+ `Git::StatusInfo` holds only the paths `git status` reports. `Git::Status`
840
+ also held an entry for every clean tracked file, seeded from `git ls-files`,
841
+ so `status[path]` returned a `Git::Status::StatusFile` with a `nil` type for an
842
+ unchanged path and `status.each` yielded one. `status_info[path]` returns `nil`
843
+ for a clean path and `status_info.files` omits it. Code that inspected clean
844
+ files should read `g.ls_files`, which still returns the index mode and SHA of
845
+ every tracked path.
846
+
847
+ `Git::StatusFileInfo` replaces the single `type` character with the two status
848
+ characters of the porcelain v2 format, `index_status` (HEAD versus index) and
849
+ `worktree_status` (index versus working tree), plus the `changed?`, `added?`,
850
+ `deleted?`, `renamed?`, `unmerged?`, `untracked?`, and `ignored?` predicates:
851
+ `added?` is true when `index_status` is `A`, `deleted?` when either status is
852
+ `D`, and `changed?` when either status is `M` or `T`. It holds no repository
853
+ reference, so `blob` is gone; fetch the object through the repository instead.
854
+ `stage` is gone too: an unmerged entry carries its stage 1, 2, and 3 modes and
855
+ SHAs in `unmerged_stages`, and every other entry is at stage 0.
856
+
857
+ > **Field renames:** the legacy mode and SHA readers were named for the wrong
858
+ > sides. `sha_index` and `mode_index` held the working-tree side of the diff:
859
+ > the index blob when the working tree matched the index, and an all-zero SHA
860
+ > when it did not. `sha_repo` and `mode_repo` held the side git compared the
861
+ > working tree against: the index in a repository with no commits, and HEAD
862
+ > once a commit exists (the factory applied `git diff-index HEAD` last). The
863
+ > new names follow git: `sha_head` and `mode_head` are the HEAD side,
864
+ > `sha_index` and `mode_index` are the index (staged) side, and
865
+ > `mode_worktree` is the working-tree mode. There is no working-tree SHA
866
+ > because `git status` does not compute one; `worktree_status` says whether
867
+ > the working tree differs from the index.
868
+
869
+ In the table, `g` is a `Git::Repository`, `status` is the `Git::Status` from
870
+ `g.status`, `file` is a `Git::Status::StatusFile`, and `info` is the
871
+ `Git::StatusFileInfo` that replaces it.
872
+
873
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
874
+ |-----------------------------------------------------|-------------|
875
+ | `g.status` | `g.status_info` — returns a `Git::StatusInfo` |
876
+ | `Git::Status.new(g)` | `g.status_info` |
877
+ | `status.changed`, `status.added`, `status.deleted`, `status.untracked` | same names on `g.status_info` — now `Hash{String => Git::StatusFileInfo}` keyed by path |
878
+ | `status.changed?(path)`, `status.added?(path)`, `status.deleted?(path)`, `status.untracked?(path)` | same names on `g.status_info` |
879
+ | `status[path]` | `g.status_info[path]` — a `Git::StatusFileInfo`, or `nil`; `nil` for a clean tracked path, which `status[path]` reported (see above) |
880
+ | `status.each { \|file\| ... }` | `g.status_info.files.each { \|info\| ... }` — does not yield clean tracked paths (see above) |
881
+ | `status.pretty` | no replacement; format `g.status_info.files` yourself |
882
+ | `file.path` | `info.path` |
883
+ | `file.type` | `info.index_status` and `info.worktree_status`, or the `info.changed?`, `info.added?`, and `info.deleted?` predicates |
884
+ | `file.untracked` | `info.untracked?` |
885
+ | `file.stage` | gone; `info.unmerged?` and `info.unmerged_stages` describe conflicted entries |
886
+ | `file.sha_repo` | `info.sha_head`, or `info.sha_index` in a repository with no commits |
887
+ | `file.mode_repo` | `info.mode_head`, or `info.mode_index` in a repository with no commits |
888
+ | `file.sha_index` | `info.sha_index` for the staged blob; `info.worktree_status` says whether the working tree differs from it |
889
+ | `file.mode_index` | `info.mode_worktree` |
890
+ | `file.blob` | `g.object(info.sha_index)` when `info.sha_index` is set and not all zeros — it is `nil` for untracked, ignored, and unmerged entries and all zeros when the path is not in the index; legacy `blob` returned `nil` without a lookup when no SHA was available and fell back to `sha_repo` when `sha_index` was `nil`. For an unmerged entry read a stage instead: `g.object(info.unmerged_stages[2][:sha])` |
891
+ | `file.blob(:repo)` | `g.object(info.sha_head)` when `info.sha_head` is set and not all zeros — it is `nil` for untracked, ignored, and unmerged entries and all zeros when the path is not in HEAD |
892
+
893
+ #### `Git::Worktree` and `Git::Worktrees` deprecated
894
+
895
+ `Git::Worktree`, `Git::Worktrees`, `Git::Repository#worktree`,
896
+ `Git::Repository#worktrees`, and `Git::Repository#worktrees_all` are deprecated
897
+ and are removed in v6.0.0. Read worktree data through
898
+ `Git::Repository#worktree_list`, which returns one `Git::WorktreeInfo` value
899
+ object per worktree, and call the repository-level operations (`worktree_add`,
900
+ `worktree_remove`, `worktree_move`, `worktree_lock`, `worktree_unlock`,
901
+ `worktree_repair`, and `worktree_prune`) with the worktree path or its
902
+ `Git::WorktreeInfo`. Return values are unchanged. Calling `g.worktree`,
903
+ `g.worktrees`, or `g.worktrees_all`, constructing a `Git::Worktrees`, and calling
904
+ `gcommit`, `add`, or `remove` on a `Git::Worktree` each emit a deprecation
905
+ warning; the `dir`, `full`, `to_s`, and `to_a` readers on `Git::Worktree` do not.
906
+ `g.worktrees` emits two warnings, one for itself and one for the `Git::Worktrees`
907
+ it constructs, and `g.worktree(dir).add` emits one for `g.worktree` and one for
908
+ `add`.
909
+
910
+ > **Return shape change:** `worktrees_all` returns `[directory, sha]` pairs and
911
+ > omits the main worktree of a bare repository, which has no checked-out commit.
912
+ > `worktree_list` returns `Git::WorktreeInfo` objects with `path`, `head`,
913
+ > `branch` (the full refname, such as `refs/heads/main`, or `nil` when detached
914
+ > or bare), `bare?`, `detached?`, `locked?` with `lock_reason`, and `prunable?`
915
+ > with `prune_reason`. It includes the bare main worktree, with `head` and
916
+ > `branch` set to `nil`. `Git::WorktreeInfo#to_s` is the path, so an entry can be
917
+ > passed to any method that takes a worktree path.
918
+ >
919
+ > **`gcommit` return type:** `Git::Worktree#gcommit` returned a
920
+ > `Git::Object::Commit` for a worktree obtained from `g.worktree(dir)` and a raw
921
+ > SHA `String` for one obtained from `g.worktrees`. `info.head` is always a
922
+ > `String` (or `nil` for a bare main worktree); call `g.gcommit(info.head)` for
923
+ > the commit object.
924
+ >
925
+ > **`full` and `to_s`:** `Git::Worktree#full` and `#to_s` append the commitish
926
+ > given at construction to the path, so entries from `g.worktrees` read
927
+ > `"/path/to/wt <sha>"`. `Git::WorktreeInfo#to_s` is the path alone.
928
+
929
+ In the table, `dir` is the worktree path, `wt` is a `Git::Worktree`, and `info`
930
+ is the `Git::WorktreeInfo` that replaces it.
931
+
932
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
933
+ |-----------------------------------------------------|-------------|
934
+ | `g.worktrees_all` | `g.worktree_list.map { \|w\| [w.path, w.head] }` — includes a bare main worktree as `[path, nil]`; add `.reject(&:bare?)` before `map` to omit it as `worktrees_all` did |
935
+ | `g.worktrees` | `g.worktree_list` — returns `Array<Git::WorktreeInfo>`; the deprecated call emits two warnings |
936
+ | `g.worktrees[dir]` | `g.worktree_list.find { \|w\| w.path == dir }` — `nil` when not found; `dir` is the path as git reports it (absolute, with symlinks resolved), as before |
937
+ | `g.worktrees.size` | `g.worktree_list.size` |
938
+ | `g.worktrees.each { \|wt\| ... }` | `g.worktree_list.each { \|info\| ... }` |
939
+ | `g.worktrees.to_s` | `g.worktree_list.map { \|w\| "#{w.path} #{w.head}\n" }.join` |
940
+ | `g.worktrees.prune` | `g.worktree_prune` |
941
+ | `g.worktree(dir).add` | `g.worktree_add(dir)` |
942
+ | `g.worktree(dir, commitish).add` | `g.worktree_add(dir, commitish)` |
943
+ | `g.worktree(dir).remove` | `g.worktree_remove(dir)` — or `g.worktree_remove(info)` |
944
+ | `wt.gcommit` | `info.head` — always a `String`, or `nil` for a bare main worktree; `g.gcommit(info.head)` for the commit object |
945
+ | `wt.dir` | `info.path` |
946
+ | `wt.full`, `wt.to_s` | `info.path` — or `"#{info.path} #{info.head}"` for the descriptor that entries from `g.worktrees` produced |
947
+ | `wt.to_a` | `[info.path]` |
948
+
365
949
  ---
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]