git 5.3.0 → 5.4.1

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
@@ -16,14 +16,24 @@ to update your code when upgrading from the preceding major version.
16
16
  - [`Git::CommandLineResult` deprecated](#gitcommandlineresult-deprecated)
17
17
  - [Deprecated methods](#deprecated-methods)
18
18
  - [Facade method renames](#facade-method-renames)
19
+ - [`Git::Repository` method renames](#gitrepository-method-renames)
20
+ - [`Git::Repository` option renames](#gitrepository-option-renames)
19
21
  - [v4.x-style configuration methods](#v4x-style-configuration-methods)
20
22
  - [`Git` module mixin deprecations](#git-module-mixin-deprecations)
23
+ - [Module-level `Git` function deprecations](#module-level-git-function-deprecations)
21
24
  - [`Git::Author` deprecated](#gitauthor-deprecated)
22
25
  - [`Git::Branch#stashes` deprecated](#gitbranchstashes-deprecated)
26
+ - [Legacy stash API deprecated](#legacy-stash-api-deprecated)
23
27
  - [`Git::Repository#remotes` deprecated](#gitrepositoryremotes-deprecated)
24
28
  - [`Git::Remote` deprecated](#gitremote-deprecated)
25
29
  - [`Git::Commands::CatFile::Raw` `allow_unknown_type` option deprecated](#gitcommandscatfileraw-allow_unknown_type-option-deprecated)
26
30
  - [`Git::Branch` and `Git::Branches` deprecated](#gitbranch-and-gitbranches-deprecated)
31
+ - [`Git::Object::Tag` deprecated](#gitobjecttag-deprecated)
32
+ - [`Git::Status` deprecated](#gitstatus-deprecated)
33
+ - [`Git::Worktree` and `Git::Worktrees` deprecated](#gitworktree-and-gitworktrees-deprecated)
34
+ - [`Git.clone` option renames](#gitclone-option-renames)
35
+ - [`Git::Log` Enumerable interface deprecated](#gitlog-enumerable-interface-deprecated)
36
+ - [`Git::Object::Commit#set_commit` deprecated](#gitobjectcommitset_commit-deprecated)
27
37
 
28
38
  ## Upgrading to v6.0.0
29
39
 
@@ -234,7 +244,7 @@ to the replacement shown to silence it.
234
244
  | `g.lib.config_list` | `g.config_list` — returns `Array<Git::ConfigEntryInfo>` |
235
245
  | `g.lib.config_set(name, value)` | `g.config_set(name, value)` |
236
246
  | `g.lib.git_version` | `g.git_version` |
237
- | `g.lib.stash_list` | `g.stashes_all` |
247
+ | `g.lib.stash_list` | `g.stash_infos` — returns `Array<Git::StashInfo>`, newest first |
238
248
  | `g.lib.unmerged` | `g.unmerged` |
239
249
  | `g.lib.change_head_branch(name)` | `g.change_head_branch(name)` |
240
250
  | `g.lib.ls_remote(location, opts)` | `g.ls_remote(location, opts)` |
@@ -266,7 +276,7 @@ shim cannot forward them). Update call sites directly:
266
276
 
267
277
  | v4.x call | Notes |
268
278
  |-----------|-------|
269
- | `g.lib.list_files(ref_dir)` | Walked `.git/refs/` directly. Use `g.branch_list`, `g.tags`, or `g.remote_list` instead. |
279
+ | `g.lib.list_files(ref_dir)` | Walked `.git/refs/` directly. Use `g.branch_list`, `g.tag_list`, or `g.remote_list` instead. |
270
280
 
271
281
  ##### Internal plumbing methods (no replacement)
272
282
 
@@ -350,18 +360,89 @@ The old names continue to work but emit deprecation warnings:
350
360
  | `g.add_tag(name, ...)` | `g.tag_add(name, ...)` |
351
361
  | `g.delete_tag(name)` | `g.tag_delete(name)` |
352
362
 
363
+ #### `Git::Repository` method renames
364
+
365
+ Seven more `Git::Repository` methods were renamed in v5.x. The old names continue
366
+ to work but emit deprecation warnings. Each old name returns exactly what its
367
+ replacement returns, except `branches_all`.
368
+
369
+ > **Return shape change:** `g.branches_all` returns an `Array` of 4-element
370
+ > tuples `[refname, current, worktree, symref]`, where `refname` is the short
371
+ > form (`main` or `remotes/origin/main`), `current` and `worktree` are booleans,
372
+ > and `symref` is the symbolic-ref target or `nil`. `g.branch_list` returns
373
+ > `Array<Git::BranchInfo>` with `refname` (always the full ref: `refs/heads/main`
374
+ > or `refs/remotes/origin/main`), `short_name`, `remote_name`, `remote?`,
375
+ > `current?`, `other_worktree?`, `symref`, `target_oid`, and `upstream`. This
376
+ > expression reproduces the legacy tuples:
377
+ >
378
+ > ```ruby
379
+ > g.branch_list.map do |i|
380
+ > refname = i.remote? ? "remotes/#{i.remote_name}/#{i.short_name}" : i.short_name
381
+ > [refname, i.current?, i.other_worktree?, i.symref]
382
+ > end
383
+ > ```
384
+
385
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
386
+ |-----------------------------------------------------|-------------|
387
+ | `g.empty?` | `g.no_commits?` — `true` when the repository has no commits |
388
+ | `g.reset_hard` | `g.reset(nil, hard: true)` — `reset` takes the commitish positionally, so pass `nil` before the options; returns git's stdout, as `reset_hard` did |
389
+ | `g.reset_hard(commitish)` | `g.reset(commitish, hard: true)` — `reset_hard` ignored any `:hard` option passed to it and always reset with `--hard` |
390
+ | `g.conflicts { \|file, yours, theirs\| ... }` | `g.each_conflict { \|file, yours, theirs\| ... }` — same block arguments; returns the unmerged paths |
391
+ | `g.is_local_branch?(name)` | `g.local_branch?(name)` |
392
+ | `g.is_remote_branch?(name)` | `g.remote_branch?(name)` |
393
+ | `g.is_branch?(name)` | `g.branch?(name)` |
394
+ | `g.branches_all` | `g.branch_list` — returns `Array<Git::BranchInfo>`; see the return shape change above |
395
+
396
+ #### `Git::Repository` option renames
397
+
398
+ Five methods accept a v4.x option or positional argument under its old name.
399
+ The old form still works in v5.x but emits a deprecation warning and is
400
+ translated to the v5.x form shown below.
401
+
402
+ > **`clean`:** `force: 2` runs `git clean -ff`, which also removes untracked
403
+ > nested git repositories. A `false` or `nil` value for `:ff` or `:force_force`
404
+ > still warns and has no effect; a value other than `true`, `false`, or `nil`
405
+ > raises `ArgumentError`. When the deprecated key is `true` and a valid
406
+ > `:force` is also given, `:force` is raised to `2` (a `:force` already at `2`
407
+ > is unchanged). An invalid `:force` value such as `0` is passed through
408
+ > unchanged and still raises `ArgumentError`; the deprecated key does not mask
409
+ > it.
410
+
411
+ > **`diff_path_status`:** `:path_limiter` accepts the same values as `:path`
412
+ > (a `String`, a `Pathname`, or an `Array` of them). When both keys are given,
413
+ > `:path_limiter` wins and no warning is emitted.
414
+
415
+ > **`set_working` and `set_index`:** `must_exist:` defaults to `true`. When
416
+ > both the positional argument and `must_exist:` are given, they are OR'ed so
417
+ > the more restrictive value wins.
418
+
419
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
420
+ |-----------------------------------------------------|-------------|
421
+ | `g.clean(ff: true)` | `g.clean(force: 2)` |
422
+ | `g.clean(force_force: true)` | `g.clean(force: 2)` |
423
+ | `g.diff_path_status(ref1, ref2, path: p)` | `g.diff_path_status(ref1, ref2, path_limiter: p)` |
424
+ | `g.commit(message, add_all: true)` | `g.commit(message, all: true)` — runs `git commit -a` |
425
+ | `g.set_working(dir, check)` | `g.set_working(dir, must_exist: check)` |
426
+ | `g.set_index(file, check)` | `g.set_index(file, must_exist: check)` |
427
+
353
428
  #### v4.x-style configuration methods
354
429
 
355
430
  The v4.x `config` and `global_config` methods accepted varying argument shapes
356
431
  to read, write, or list configuration. These are replaced by separate,
357
- purpose-named methods.
358
-
359
- > **Return type change:** The v4.x `g.config(name)` returned a `String` and
360
- > `g.config` returned a `Hash`. The v5.x replacements `config_get` and
361
- > `config_list` return `Git::ConfigEntryInfo` and `Array<Git::ConfigEntryInfo>`
432
+ purpose-named methods. The same applies to the module-level
433
+ `Git.global_config`, which is replaced by `Git.config_get`, `Git.config_set`,
434
+ and `Git.config_list` called with `global: true`.
435
+
436
+ > **Return type change:** The v4.x `g.config(name)` and `Git.global_config(name)`
437
+ > returned a `String`; `g.config` and `Git.global_config` returned a `Hash`. The
438
+ > v5.x replacements `config_get` and `config_list` return `Git::ConfigEntryInfo`
439
+ > (or `nil` when the key is not set) and `Array<Git::ConfigEntryInfo>`
362
440
  > respectively. Use `.value` to get the String value:
363
441
  > - `g.config_get(name)&.value` → String or nil
364
442
  > - `g.config_list.to_h { |e| [e.key, e.value] }` → Hash (key → value)
443
+ >
444
+ > The setters `g.config(name, value)` and `Git.global_config(name, value)`
445
+ > returned the raw command result; `config_set` returns `nil`.
365
446
 
366
447
  | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
367
448
  |-----------------------------------------------------|-------------|
@@ -371,8 +452,9 @@ purpose-named methods.
371
452
  | `g.global_config(name)` | `g.config_get(name, global: true)` |
372
453
  | `g.global_config` | `g.config_list(global: true)` |
373
454
  | `g.global_config(name, value)` | `g.config_set(name, value, global: true)` |
374
- | `g.parse_config(file)` | `g.config_list(file: file)` |
375
- | `g.stash_list` | `g.stashes_all` |
455
+ | `Git.global_config(name)` | `Git.config_get(name, global: true)` — returns `Git::ConfigEntryInfo` or `nil`; use `.value` for the String |
456
+ | `Git.global_config` | `Git.config_list(global: true)` — returns `Array<Git::ConfigEntryInfo>` |
457
+ | `Git.global_config(name, value)` | `Git.config_set(name, value, global: true)` |
376
458
 
377
459
  #### `Git` module mixin deprecations
378
460
 
@@ -381,13 +463,43 @@ as bare methods is deprecated:
381
463
 
382
464
  | Deprecated usage | Replacement |
383
465
  |-----------------|-------------|
384
- | `include Git; config(name)` | `Git.open(Dir.pwd).config_get(name)` |
385
- | `include Git; config(name, value)` | `Git.open(Dir.pwd).config_set(name, value)` |
386
- | `include Git; config` | `Git.open(Dir.pwd).config_list` |
466
+ | `include Git; config(name)` | `Git.config_get(name)` |
467
+ | `include Git; config(name, value)` | `Git.config_set(name, value)` |
468
+ | `include Git; config` | `Git.config_list` |
387
469
  | `include Git; global_config(name)` | `Git.config_get(name, global: true)` |
388
470
  | `include Git; global_config(name, value)` | `Git.config_set(name, value, global: true)` |
389
471
  | `include Git; global_config` | `Git.config_list(global: true)` |
390
472
 
473
+ `Git.config_get`, `Git.config_set`, and `Git.config_list` run `git config` in
474
+ the current directory, which is what the mixin `config` method did. The
475
+ return types differ as described under
476
+ [v4.x-style configuration methods](#v4x-style-configuration-methods).
477
+
478
+ #### Module-level `Git` function deprecations
479
+
480
+ Two module-level functions on `Git` accept a legacy call shape or return a
481
+ legacy type that is deprecated:
482
+
483
+ - `Git.ls_remote` defaults its repository argument to `'.'`. Passing `nil`
484
+ explicitly still works but warns; omit the argument or pass `'.'`. The
485
+ options hash is positional, so when you pass options you must also pass the
486
+ repository: `Git.ls_remote('.', opts)`, not `Git.ls_remote(opts)`.
487
+ - `Git.binary_version` is replaced by `Git.git_version`, which keeps the
488
+ optional binary path argument.
489
+
490
+ > **Return type change:** `Git.binary_version` returned an `Array<Integer>` of
491
+ > `[major, minor, patch]`. `Git.git_version` returns a `Git::Version`, which
492
+ > supports comparison and exposes `major`, `minor`, and `patch`.
493
+ > `Git.git_version.to_a` reproduces the legacy array. The return value of
494
+ > `Git.ls_remote` is unchanged.
495
+
496
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
497
+ |-----------------------------------------------------|-------------|
498
+ | `Git.ls_remote(nil)` | `Git.ls_remote` or `Git.ls_remote('.')` |
499
+ | `Git.ls_remote(nil, opts)` | `Git.ls_remote('.', opts)` |
500
+ | `Git.binary_version` | `Git.git_version` — returns `Git::Version`; use `.to_a` for the `[major, minor, patch]` Array |
501
+ | `Git.binary_version(binary_path)` | `Git.git_version(binary_path)` |
502
+
391
503
  #### `Git::Author` deprecated
392
504
 
393
505
  Starting in v5.3.0, methods that return author, committer, or tagger data —
@@ -413,34 +525,97 @@ Constructing `Git::Author` directly emits a deprecation warning naming
413
525
 
414
526
  `Git::Branch#stashes` ignores the branch it is called on and returns every stash
415
527
  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
528
+ return the same entries. Call `Git::Repository#stash_infos` instead; it is the
417
529
  query `Git::Branch#stashes` was already running.
418
530
 
419
531
  > **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.
532
+ > collection of `Git::Stash` objects. `g.stash_infos` returns an array of
533
+ > `Git::StashInfo` values. Both are newest first, so indexes carry over unchanged.
534
+ > `Git::Stash#message` strips the `WIP on <branch>:` or `On <branch>:` prefix;
535
+ > `Git::StashInfo#message` keeps the full message and exposes the branch name as
536
+ > `Git::StashInfo#branch`.
426
537
 
427
538
  `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.
539
+ repository's `stash_push`, `stash_apply`, and `stash_clear`. `Git::Stashes#apply(i)`
540
+ already passed `i` to git as `stash@{i}` (`0` = newest), and `g.stash_apply(i)` does
541
+ the same, so that index needs no conversion. The `Git::Stashes` class is deprecated
542
+ as well; [Legacy stash API deprecated](#legacy-stash-api-deprecated) maps each of
543
+ its methods.
432
544
 
433
545
  | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
434
546
  |-----------------------------------------------------|-------------|
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)` |
547
+ | `g.branch(name).stashes` | `g.stash_infos` — returns `Array<Git::StashInfo>`, newest first |
548
+ | `g.branch(name).stashes.each { \|s\| puts s.message }` | `g.stash_infos.each { \|info\| puts info.message }` |
549
+ | `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) |
550
+ | `g.branch(name).stashes.size` | `g.stash_infos.size` |
551
+ | `g.branch(name).stashes[i].message` (`0` = newest, `i` coerced with `to_i`) | `g.stash_infos[i.to_i].message` |
552
+ | `g.branch(name).stashes.save(message)` | `g.stash_push(message: message)` |
441
553
  | `g.branch(name).stashes.apply` | `g.stash_apply` |
442
554
  | `g.branch(name).stashes.apply(i)` (`0` = newest) | `g.stash_apply(i)` |
443
- | `g.branch(name).stashes.clear` | `g.stash_clear` |
555
+ | `g.branch(name).stashes.clear` | `g.stash_clear` — returns git's stdout (normally `""`, which is truthy) where `Git::Stashes#clear` returned `nil` |
556
+
557
+ #### Legacy stash API deprecated
558
+
559
+ Starting in v5.4.0, the stash methods on `Git::Repository` are built around the
560
+ immutable `Git::StashInfo` value object. `g.stash_infos` returns every entry as a
561
+ `Git::StashInfo`, and `stash_push`, `stash_pop`, `stash_drop`, `stash_show`,
562
+ `stash_branch`, `stash_create`, and `stash_store` each map onto the `git stash`
563
+ subcommand of the same name. Every method that takes a stash (`stash_apply`,
564
+ `stash_pop`, `stash_drop`, `stash_show`, `stash_branch`) accepts a `Git::StashInfo`,
565
+ a `stash@{N}` name, an Integer index (`0` = newest), or `nil` for the newest entry.
566
+
567
+ The legacy methods and classes are deprecated and removed in v6.0.0:
568
+ `Git::Repository#stashes_all`, `Git::Repository#stash_save`,
569
+ `Git::Repository#stash_list`, `Git::Stash`, and `Git::Stashes`. Constructing a
570
+ `Git::Stash` or `Git::Stashes` emits one warning per object.
571
+
572
+ > **Ordering flip:** `g.stashes_all` returns entries **oldest first** with a
573
+ > sequential index of its own (`0` is the oldest). `g.stash_infos` returns entries
574
+ > **newest first**, the order `git stash list` uses, and `Git::StashInfo#index` is
575
+ > git's own `stash@{N}` number (`0` is the newest). `g.stashes_all.first` is
576
+ > `g.stash_infos.last`. Code that reads an entry by position must reverse the
577
+ > array or the index.
578
+
579
+ > **Message difference:** `g.stashes_all` strips the `WIP on <branch>:` or
580
+ > `On <branch>:` prefix from each message. `Git::StashInfo#message` keeps the full
581
+ > message git stores, and `Git::StashInfo#branch` holds the branch name. A stash
582
+ > created from a detached HEAD has the branch `"(no branch)"`, the label git writes
583
+ > in its message. `branch` is `nil` only when the message has no branch prefix at
584
+ > all, as for a `stash_store` entry with a custom message.
585
+
586
+ `g.stash_save(message)` returned `true` when it created a stash and `false` when
587
+ there were no local changes to save. `g.stash_push(message: message)` returns the
588
+ new `Git::StashInfo`, or `nil` when there were no local changes, so a truthiness
589
+ check such as `if g.stash_push(message: 'WIP')` still works.
590
+
591
+ `g.stash_list` returned the `git stash list` text as a String. Build that text from
592
+ `g.stash_infos` if you need it. In v6.0.0, `stash_list` returns
593
+ `Array<Git::StashInfo>`, the same value as `stash_infos`, and `stash_infos` stays as
594
+ a permanent alias. Move String callers of `stash_list` to `stash_infos` before
595
+ upgrading so the return type change cannot go unnoticed.
596
+
597
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
598
+ |-----------------------------------------------------|-------------|
599
+ | `g.stashes_all` | `g.stash_infos` — returns `Array<Git::StashInfo>`, newest first |
600
+ | `g.stashes_all.each { \|index, message\| ... }` | `g.stash_infos.reverse_each.with_index { \|info, index\| ... info.message }` |
601
+ | `g.stashes_all[i]` (`0` = oldest) | `g.stash_infos.reverse[i]` |
602
+ | `g.stashes_all.last` | `g.stash_infos.first` |
603
+ | `g.stash_save(message)` | `g.stash_push(message: message)` — returns `Git::StashInfo` or `nil` |
604
+ | `g.stash_list` (String) | `g.stash_infos.map { \|s\| "#{s.name}: #{s.message}" }.join("\n")` |
605
+ | `Git::Stash.new(g, message)` | `info = g.stash_push(message: message)` |
606
+ | `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 |
607
+ | `stash.save` | `info = g.stash_push(message: message)` |
608
+ | `stash.saved?` | `!info.nil?` — check the value `stash_push` returned rather than pushing again |
609
+ | `stash.message` / `stash.to_s` | `info.message` — keeps the branch prefix; see the note above |
610
+ | `Git::Stashes.new(g)` | `g.stash_infos` |
611
+ | `stashes.all` (`[index, message]` pairs, oldest first) | `g.stash_infos.reverse` — see the ordering note above |
612
+ | `stashes.each { \|s\| ... }` (newest first) | `g.stash_infos.each { \|info\| ... }` |
613
+ | `stashes[i]` (`0` = newest, `i` coerced with `to_i`) | `g.stash_infos[i.to_i]` |
614
+ | `stashes.size` | `g.stash_infos.size` |
615
+ | `stashes.save(message)` | `g.stash_push(message: message)` |
616
+ | `stashes.apply` / `stashes.apply(i)` | `g.stash_apply` / `g.stash_apply(i)` |
617
+ | `stashes.clear` | `g.stash_clear` — returns git's stdout (normally `""`, which is truthy) where `Git::Stashes#clear` returned `nil` |
618
+
444
619
  #### `Git::Repository#remotes` deprecated
445
620
 
446
621
  `Git::Repository#remotes` is deprecated in favor of `Git::Repository#remote_list`
@@ -646,6 +821,300 @@ can resolve a local branch of that name and is only used where git expects it
646
821
  | `b.update_ref(commit)` (remote-tracking) | `g.update_ref("remotes/#{remote}/#{name}", commit)` |
647
822
  | `b.archive(file, opts)` | `g.archive(name, file, opts)` — pass `info.refname` for a remote-tracking branch |
648
823
  | `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) |
824
+ | `b.stashes` | `g.stash_infos` — see [`Git::Branch#stashes` deprecated](#gitbranchstashes-deprecated) |
825
+
826
+ #### `Git::Object::Tag` deprecated
827
+
828
+ `Git::Object::Tag`, `Git::Repository#tag`, `Git::Repository#tags`, and
829
+ `Git::Repository#tag_add` are deprecated and are removed in v6.0.0. Read tag data
830
+ through `Git::Repository#tag_list`, which returns one `Git::TagInfo` value object per
831
+ tag, create tags with `Git::Repository#tag_create`, which returns the new tag's
832
+ `Git::TagInfo`, and call the repository-level operations (`archive`, `log`, `diff`,
833
+ `cat_file_contents`, and so on) with the tag's object ID,
834
+ `info.oid || info.target_oid`, which is the object a `Git::Object::Tag` pinned when
835
+ it was constructed. Calling `g.tag`, `g.tags`, or
836
+ `g.tag_add`, and constructing a `Git::Object::Tag`, each emit one deprecation
837
+ warning; their return values are unchanged. `g.add_tag` already warned, pointing at
838
+ `g.tag_add`, and now emits two warnings for a creation call, one for itself and one
839
+ for the `g.tag_add` it calls; `g.add_tag(name, d: true)` emits three, adding the
840
+ `:d`/`:delete` warning described below. The readers on a `Git::Object::Tag` do not
841
+ warn.
842
+
843
+ > **Return shape change:** `Git::Object::Tag` exposes `name`, `sha`, `objectish`,
844
+ > `annotated?`, `message`, and `tagger`. `Git::TagInfo` exposes `name`, `oid`,
845
+ > `target_oid`, `objecttype`, `annotated?`, `lightweight?`, `message`, and
846
+ > `tagger`. `name` and `annotated?` are unchanged. `tagger` keeps the same `name`
847
+ > and `email`, but `tagger.date` differs: `t.tagger.date` is a `Time` in the
848
+ > process's local zone, while `info.tagger.date` keeps the UTC offset recorded in
849
+ > the tag object. Both name the same instant. `message` differs for an annotated
850
+ > tag created with an empty message (`message: ''`): `t.message` returns `""` and
851
+ > `info.message` returns `nil`, the same value a lightweight tag has. `t.sha` and
852
+ > `t.objectish` are the tag object's ID for an annotated tag and
853
+ > the tagged object's ID for a lightweight tag. `Git::TagInfo` separates the two:
854
+ > `oid` is the tag object's ID (`nil` for a lightweight tag) and `target_oid` is
855
+ > the ID of the object the tag points to (set for both kinds), so
856
+ > `info.oid || info.target_oid` reproduces `t.sha`. The target is usually a
857
+ > commit, but a tag can point at any git object, and `info.objecttype` reports
858
+ > which kind (`tag` for an annotated tag, or the target's own type such as
859
+ > `commit` or `blob` for a lightweight one).
860
+ >
861
+ > **Missing tags:** `g.tag(name)` raises `Git::UnexpectedResultError` when no tag
862
+ > has that name. `g.tag_list(name).first` returns `nil`.
863
+ >
864
+ > **Deleting through `tag_add`:** `g.tag_add(name, d: true)`, which was already
865
+ > deprecated, deletes the tag and emits a second warning pointing at
866
+ > `g.tag_delete`. `g.tag_create` rejects `:d` and `:delete` with `ArgumentError`.
867
+ >
868
+ > **Extra positional arguments:** `g.tag_add(name, target, extra)` ignores
869
+ > `extra` and tags `target`. `g.tag_create` raises `ArgumentError` when more than
870
+ > one positional argument follows the name.
871
+ >
872
+ > **Object identity:** every `Git::Object::Tag` resolves its tag to an object ID
873
+ > when it is constructed and runs `size`, `contents`, `grep`, `diff`, `log`, and
874
+ > `archive` against that ID, so moving or deleting the tag afterwards does not
875
+ > redirect an existing object. `Git::Object::Tag.new(g, sha, name)` uses the
876
+ > supplied `sha` as that ID; the other forms look it up from the ref. `annotated?`,
877
+ > `message`, and `tagger` always read the ref `name`. `Git::TagInfo` describes the
878
+ > ref only: `g.tag_list(name).first` returns whatever `name` points at now, or
879
+ > `nil` once the tag is deleted. Keep the same identity by passing `id` (see the
880
+ > table) rather than `name` to the operation replacements; they accept any object.
881
+ > To read an annotated tag object by ID without going through its ref, use
882
+ > `g.cat_file_tag(id)`, which returns the tag object's `object`, `type`, `tag`,
883
+ > `tagger`, and `message`.
884
+
885
+ In the table, `name` is the tag name, `t` is a `Git::Object::Tag`, `info` is the
886
+ `Git::TagInfo` that replaces it, and `id` is `info.oid || info.target_oid` (or the
887
+ `sha` given to the three-argument constructor), the object `t` pinned.
888
+
889
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
890
+ |-----------------------------------------------------|-------------|
891
+ | `g.tag(name)` | `g.tag_list(name).first` — a `Git::TagInfo`, or `nil` when the tag does not exist |
892
+ | `g.tags` | `g.tag_list` — returns `Array<Git::TagInfo>` |
893
+ | `g.tags.map(&:name)` | `g.tag_list.map(&:name)` |
894
+ | `g.tag_add(name, opts)` | `g.tag_create(name, opts)` — returns a `Git::TagInfo` |
895
+ | `g.tag_add(name, target, opts)` | `g.tag_create(name, target, opts)` |
896
+ | `g.tag_add(name, d: true)` | `g.tag_delete(name)` |
897
+ | `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` |
898
+ | `g.add_tag(name, d: true)` | `g.tag_delete(name)` — `g.tag_create` rejects `:d`; see the deletion note above |
899
+ | `Git::Object::Tag.new(g, name)` | `g.tag_list(name).first` |
900
+ | `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 |
901
+ | `Git::Object.new(g, name, nil, true)` | `g.tag_list(name).first` — its warning names `Git::Object::Tag.new`, which is deprecated too |
902
+ | `t.name` | `info.name` |
903
+ | `t.sha`, `t.objectish`, `t.to_s` | `info.oid \|\| info.target_oid` — see the return shape change above |
904
+ | `t.annotated?` | `info.annotated?` |
905
+ | `t.message` | `info.message` — `nil` rather than `""` for an annotated tag with an empty message |
906
+ | `t.tagger` | `info.tagger` — `date` keeps the recorded UTC offset; see the return shape change above |
907
+ | `t.tag?` | not needed; every `Git::TagInfo` is a tag |
908
+ | `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 |
909
+ | `t.contents` | `g.cat_file_contents(id)` |
910
+ | `t.contents { \|file\| ... }` | `g.cat_file_contents(id) { \|file\| ... }` — streams to a temporary file instead of buffering the object |
911
+ | `t.contents_array` | `g.cat_file_contents(id).split("\n")` |
912
+ | `t.grep(string, path, opts)` | `g.grep(string, path, opts.merge(object: id))` |
913
+ | `t.diff(other)` | `g.diff(id, other)` |
914
+ | `t.log(count)` | `g.log(count).object(id)` |
915
+ | `t.archive(file, opts)` | `g.archive(id, file, opts)` |
916
+
917
+ #### `Git::Status` deprecated
918
+
919
+ Starting in v5.4.0, `Git::Status`, `Git::Status::StatusFile`, and
920
+ `Git::Repository#status` are deprecated and will be removed in v6.0.0. Read the
921
+ index and working tree state through `Git::Repository#status_info`, which
922
+ returns an immutable `Git::StatusInfo` holding one `Git::StatusFileInfo` per
923
+ path that `git status --porcelain=v2` reports. Calling `g.status` emits one
924
+ deprecation warning, and so does constructing a `Git::Status` directly.
925
+
926
+ `Git::StatusInfo` keeps the `changed`, `added`, `deleted`, and `untracked`
927
+ readers and the `changed?`, `added?`, `deleted?`, and `untracked?` predicates,
928
+ so code that only uses those can change `status` to `status_info` and needs
929
+ no other edit, subject to the category differences below. The readers now
930
+ return `Hash{String => Git::StatusFileInfo}`, and a new `unmerged` reader
931
+ lists conflicted paths, which `Git::Status` did not report. The predicates
932
+ still compare paths case-insensitively when `core.ignoreCase` is `true`.
933
+ `Git::StatusInfo` is not `Enumerable`; iterate `status_info.files`, an
934
+ `Array<Git::StatusFileInfo>` in git's output order.
935
+
936
+ The categories are derived differently. `Git::Status` gave each file one
937
+ `type`, and `changed` held only files whose type was `M`, so `changed`,
938
+ `added`, and `deleted` were disjoint: a file staged as new and then modified
939
+ in the working tree was only `added`. `Git::StatusInfo` derives the
940
+ categories from both status characters, so `changed` also includes type
941
+ changes (`T`), and one path can be in more than one category: that same file
942
+ (`AM`) is in both `added` and `changed`, and a file modified in the index and
943
+ then deleted from the working tree (`MD`) is in both `changed` and `deleted`.
944
+ Code that relied on the sets being disjoint should test `index_status` and
945
+ `worktree_status` directly.
946
+
947
+ `Git::StatusInfo` holds only the paths `git status` reports. `Git::Status`
948
+ also held an entry for every clean tracked file, seeded from `git ls-files`,
949
+ so `status[path]` returned a `Git::Status::StatusFile` with a `nil` type for an
950
+ unchanged path and `status.each` yielded one. `status_info[path]` returns `nil`
951
+ for a clean path and `status_info.files` omits it. Code that inspected clean
952
+ files should read `g.ls_files`, which still returns the index mode and SHA of
953
+ every tracked path.
954
+
955
+ `Git::StatusFileInfo` replaces the single `type` character with the two status
956
+ characters of the porcelain v2 format, `index_status` (HEAD versus index) and
957
+ `worktree_status` (index versus working tree), plus the `changed?`, `added?`,
958
+ `deleted?`, `renamed?`, `unmerged?`, `untracked?`, and `ignored?` predicates:
959
+ `added?` is true when `index_status` is `A`, `deleted?` when either status is
960
+ `D`, and `changed?` when either status is `M` or `T`. It holds no repository
961
+ reference, so `blob` is gone; fetch the object through the repository instead.
962
+ `stage` is gone too: an unmerged entry carries its stage 1, 2, and 3 modes and
963
+ SHAs in `unmerged_stages`, and every other entry is at stage 0.
964
+
965
+ > **Field renames:** the legacy mode and SHA readers were named for the wrong
966
+ > sides. `sha_index` and `mode_index` held the working-tree side of the diff:
967
+ > the index blob when the working tree matched the index, and an all-zero SHA
968
+ > when it did not. `sha_repo` and `mode_repo` held the side git compared the
969
+ > working tree against: the index in a repository with no commits, and HEAD
970
+ > once a commit exists (the factory applied `git diff-index HEAD` last). The
971
+ > new names follow git: `sha_head` and `mode_head` are the HEAD side,
972
+ > `sha_index` and `mode_index` are the index (staged) side, and
973
+ > `mode_worktree` is the working-tree mode. There is no working-tree SHA
974
+ > because `git status` does not compute one; `worktree_status` says whether
975
+ > the working tree differs from the index.
976
+
977
+ In the table, `g` is a `Git::Repository`, `status` is the `Git::Status` from
978
+ `g.status`, `file` is a `Git::Status::StatusFile`, and `info` is the
979
+ `Git::StatusFileInfo` that replaces it.
980
+
981
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
982
+ |-----------------------------------------------------|-------------|
983
+ | `g.status` | `g.status_info` — returns a `Git::StatusInfo` |
984
+ | `Git::Status.new(g)` | `g.status_info` |
985
+ | `status.changed`, `status.added`, `status.deleted`, `status.untracked` | same names on `g.status_info` — now `Hash{String => Git::StatusFileInfo}` keyed by path |
986
+ | `status.changed?(path)`, `status.added?(path)`, `status.deleted?(path)`, `status.untracked?(path)` | same names on `g.status_info` |
987
+ | `status[path]` | `g.status_info[path]` — a `Git::StatusFileInfo`, or `nil`; `nil` for a clean tracked path, which `status[path]` reported (see above) |
988
+ | `status.each { \|file\| ... }` | `g.status_info.files.each { \|info\| ... }` — does not yield clean tracked paths (see above) |
989
+ | `status.pretty` | no replacement; format `g.status_info.files` yourself |
990
+ | `file.path` | `info.path` |
991
+ | `file.type` | `info.index_status` and `info.worktree_status`, or the `info.changed?`, `info.added?`, and `info.deleted?` predicates |
992
+ | `file.untracked` | `info.untracked?` |
993
+ | `file.stage` | gone; `info.unmerged?` and `info.unmerged_stages` describe conflicted entries |
994
+ | `file.sha_repo` | `info.sha_head`, or `info.sha_index` in a repository with no commits |
995
+ | `file.mode_repo` | `info.mode_head`, or `info.mode_index` in a repository with no commits |
996
+ | `file.sha_index` | `info.sha_index` for the staged blob; `info.worktree_status` says whether the working tree differs from it |
997
+ | `file.mode_index` | `info.mode_worktree` |
998
+ | `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])` |
999
+ | `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 |
1000
+
1001
+ #### `Git::Worktree` and `Git::Worktrees` deprecated
1002
+
1003
+ `Git::Worktree`, `Git::Worktrees`, `Git::Repository#worktree`,
1004
+ `Git::Repository#worktrees`, and `Git::Repository#worktrees_all` are deprecated
1005
+ and are removed in v6.0.0. Read worktree data through
1006
+ `Git::Repository#worktree_list`, which returns one `Git::WorktreeInfo` value
1007
+ object per worktree, and call the repository-level operations (`worktree_add`,
1008
+ `worktree_remove`, `worktree_move`, `worktree_lock`, `worktree_unlock`,
1009
+ `worktree_repair`, and `worktree_prune`) with the worktree path or its
1010
+ `Git::WorktreeInfo`. Return values are unchanged. Calling `g.worktree`,
1011
+ `g.worktrees`, or `g.worktrees_all`, constructing a `Git::Worktrees`, and calling
1012
+ `gcommit`, `add`, or `remove` on a `Git::Worktree` each emit a deprecation
1013
+ warning; the `dir`, `full`, `to_s`, and `to_a` readers on `Git::Worktree` do not.
1014
+ `g.worktrees` emits two warnings, one for itself and one for the `Git::Worktrees`
1015
+ it constructs, and `g.worktree(dir).add` emits one for `g.worktree` and one for
1016
+ `add`.
1017
+
1018
+ > **Return shape change:** `worktrees_all` returns `[directory, sha]` pairs and
1019
+ > omits the main worktree of a bare repository, which has no checked-out commit.
1020
+ > `worktree_list` returns `Git::WorktreeInfo` objects with `path`, `head`,
1021
+ > `branch` (the full refname, such as `refs/heads/main`, or `nil` when detached
1022
+ > or bare), `bare?`, `detached?`, `locked?` with `lock_reason`, and `prunable?`
1023
+ > with `prune_reason`. It includes the bare main worktree, with `head` and
1024
+ > `branch` set to `nil`. `Git::WorktreeInfo#to_s` is the path, so an entry can be
1025
+ > passed to any method that takes a worktree path.
1026
+ >
1027
+ > **`gcommit` return type:** `Git::Worktree#gcommit` returned a
1028
+ > `Git::Object::Commit` for a worktree obtained from `g.worktree(dir)` and a raw
1029
+ > SHA `String` for one obtained from `g.worktrees`. `info.head` is always a
1030
+ > `String` (or `nil` for a bare main worktree); call `g.gcommit(info.head)` for
1031
+ > the commit object.
1032
+ >
1033
+ > **`full` and `to_s`:** `Git::Worktree#full` and `#to_s` append the commitish
1034
+ > given at construction to the path, so entries from `g.worktrees` read
1035
+ > `"/path/to/wt <sha>"`. `Git::WorktreeInfo#to_s` is the path alone.
1036
+
1037
+ In the table, `dir` is the worktree path, `wt` is a `Git::Worktree`, and `info`
1038
+ is the `Git::WorktreeInfo` that replaces it.
1039
+
1040
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
1041
+ |-----------------------------------------------------|-------------|
1042
+ | `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 |
1043
+ | `g.worktrees` | `g.worktree_list` — returns `Array<Git::WorktreeInfo>`; the deprecated call emits two warnings |
1044
+ | `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 |
1045
+ | `g.worktrees.size` | `g.worktree_list.size` |
1046
+ | `g.worktrees.each { \|wt\| ... }` | `g.worktree_list.each { \|info\| ... }` |
1047
+ | `g.worktrees.to_s` | `g.worktree_list.map { \|w\| "#{w.path} #{w.head}\n" }.join` |
1048
+ | `g.worktrees.prune` | `g.worktree_prune` |
1049
+ | `g.worktree(dir).add` | `g.worktree_add(dir)` |
1050
+ | `g.worktree(dir, commitish).add` | `g.worktree_add(dir, commitish)` |
1051
+ | `g.worktree(dir).remove` | `g.worktree_remove(dir)` — or `g.worktree_remove(info)` |
1052
+ | `wt.gcommit` | `info.head` — always a `String`, or `nil` for a bare main worktree; `g.gcommit(info.head)` for the commit object |
1053
+ | `wt.dir` | `info.path` |
1054
+ | `wt.full`, `wt.to_s` | `info.path` — or `"#{info.path} #{info.head}"` for the descriptor that entries from `g.worktrees` produced |
1055
+ | `wt.to_a` | `[info.path]` |
1056
+
1057
+ #### `Git.clone` option renames
1058
+
1059
+ Three `Git.clone` options were renamed in v5.x. The v4.x names still work. Each
1060
+ deprecated option present on a call emits its own deprecation warning, so a call
1061
+ that uses two of them warns twice. Each value is passed through to the
1062
+ replacement option, except that `:path` is dropped when `:chdir` is also given.
1063
+
1064
+ > **Precedence and value notes:**
1065
+ > - `:path` and `:chdir` both run `git clone` from inside the given directory.
1066
+ > When both are given, `:chdir` wins and `:path` is dropped.
1067
+ > - `:recursive` carries its value over to `:recurse_submodules` unchanged.
1068
+ > `:recurse_submodules` also accepts a pathspec `String` or `Array<String>`
1069
+ > to initialize only a subset of submodules, which `:recursive` never did.
1070
+ > - `:remote` and `:origin` have the same effect (`git clone --origin name`).
1071
+
1072
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
1073
+ |-----------------------------------------------------|-------------|
1074
+ | `Git.clone(url, dir, path: p)` | `Git.clone(url, dir, chdir: p)` |
1075
+ | `Git.clone(url, dir, recursive: true)` | `Git.clone(url, dir, recurse_submodules: true)` — or a pathspec `String` or `Array<String>` for a subset of submodules |
1076
+ | `Git.clone(url, dir, remote: name)` | `Git.clone(url, dir, origin: name)` |
1077
+
1078
+ #### `Git::Log` Enumerable interface deprecated
1079
+
1080
+ `Git::Log` is a query builder. Calling `each`, `size`, `to_s`, `first`, `last`, or
1081
+ `[]` directly on it runs the query and emits a deprecation warning; those methods
1082
+ are removed in v6.0.0. Call `Git::Log#execute` instead. It runs the query and
1083
+ returns a `Git::Log::Result`, which includes `Enumerable` and provides the same
1084
+ six methods. The chainable query methods on `Git::Log` (`since`, `author`,
1085
+ `between`, `path`, `max_count`, and so on) are unchanged.
1086
+
1087
+ `Git::Log` includes `Enumerable`, so every `Enumerable` method called on the
1088
+ builder (`map`, `select`, `count`, `to_a`, `include?`, and so on) goes through the
1089
+ deprecated `each` and emits its warning. Move those calls to the result as well,
1090
+ not only the six named methods.
1091
+
1092
+ > **Snapshot results:** `execute` returns a snapshot. The builder re-runs
1093
+ > `git log` only when a query method (`since`, `max_count`, and so on) has been
1094
+ > called since the last run, even with the same value as before, so calling
1095
+ > `execute` twice on an untouched builder returns equal results without a second
1096
+ > `git log`. Keep the result object when a chain of operations needs the same
1097
+ > commits rather than calling `g.log` again, which builds a new query.
1098
+
1099
+ In the table, `g` is a `Git::Repository`.
1100
+
1101
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
1102
+ |-----------------------------------------------------|-------------|
1103
+ | `g.log.each { \|c\| ... }` | `g.log.execute.each { \|c\| ... }` |
1104
+ | `g.log.size` | `g.log.execute.size` |
1105
+ | `g.log.to_s` | `g.log.execute.to_s` — commits joined with newlines, as before |
1106
+ | `g.log.first`, `g.log.last` | `g.log.execute.first`, `g.log.execute.last` |
1107
+ | `g.log[i]`, `g.log[range]` | `g.log.execute[i]`, `g.log.execute[range]` |
1108
+ | any other `Enumerable` method on the log (`map`, `select`, `count`, `to_a`, `include?`, ...) | the same method on `g.log.execute` |
1109
+
1110
+ #### `Git::Object::Commit#set_commit` deprecated
1111
+
1112
+ `Git::Object::Commit#set_commit` is deprecated and is removed in v6.0.0. Call
1113
+ `from_data` instead; it takes the same parsed commit data hash and has the same
1114
+ effect.
1115
+
1116
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
1117
+ |-----------------------------------------------------|-------------|
1118
+ | `commit.set_commit(data)` | `commit.from_data(data)` |
650
1119
 
651
1120
  ---
data/lib/git/branch.rb CHANGED
@@ -132,22 +132,26 @@ module Git
132
132
  #
133
133
  # The result is memoized after the first call.
134
134
  #
135
- # @example Iterate over stash entries
135
+ # @example Iterate over stash entries (deprecated)
136
136
  # git.branch('main').stashes.each { |s| puts s }
137
137
  #
138
+ # @example The replacement
139
+ # repo.stash_infos.each { |info| puts info.message }
140
+ #
138
141
  # @return [Git::Stashes] the stash list
139
142
  #
140
- # @deprecated Use {Git::Repository#stashes_all} instead
143
+ # @deprecated Use {Git::Repository#stash_infos} instead
141
144
  #
142
- # @see Git::Repository#stashes_all
145
+ # @see Git::Repository#stash_infos
143
146
  #
144
147
  def stashes
145
148
  Git::Deprecation.warn(
146
149
  'Git::Branch#stashes is deprecated and will be removed in v6.0.0. ' \
147
150
  'It ignores the branch and returns all repository stashes. ' \
148
- 'Use Git::Repository#stashes_all instead.'
151
+ 'Use Git::Repository#stash_infos instead.'
149
152
  )
150
- @stashes ||= Git::Stashes.new(branch_repository)
153
+ # Git::Stashes is deprecated too; silence it so one stashes call emits one warning
154
+ @stashes ||= Git::Deprecation.silence { Git::Stashes.new(branch_repository) }
151
155
  end
152
156
 
153
157
  # Checks out this branch, attempting to create it first if it does not already exist