git 5.0.0.beta.5 → 5.0.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.
@@ -7,6 +7,7 @@ require 'git/commands/pull'
7
7
  require 'git/commands/push'
8
8
  require 'git/commands/remote'
9
9
  require 'git/parsers/ls_remote'
10
+ require 'git/parsers/remote'
10
11
  require 'git/remote'
11
12
 
12
13
  require 'git/repository/shared_private'
@@ -330,11 +331,11 @@ module Git
330
331
  # @option opts [String, nil] :track (nil) track only the given branch during
331
332
  # fetch (`-t`)
332
333
  #
333
- # @return [Git::Remote] the newly added remote
334
+ # @return [void]
334
335
  #
335
336
  # @raise [ArgumentError] when unsupported option keys are provided
336
337
  #
337
- # @raise [Git::FailedError] when git exits with a non-zero status
338
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
338
339
  #
339
340
  def remote_add(name, url, opts = {})
340
341
  url = url.repo.to_s if url.is_a?(Git::Repository)
@@ -342,7 +343,7 @@ module Git
342
343
  SharedPrivate.assert_valid_opts!(REMOTE_ADD_ALLOWED_OPTS, **opts)
343
344
  Git::Commands::Remote::Add.new(@execution_context).call(name, url, **opts)
344
345
 
345
- Git::Remote.new(self, name)
346
+ nil
346
347
  end
347
348
 
348
349
  # @param name [String] the name for the new remote
@@ -367,7 +368,7 @@ module Git
367
368
  #
368
369
  # @raise [ArgumentError] when unsupported option keys are provided
369
370
  #
370
- # @raise [Git::FailedError] when git exits with a non-zero status
371
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
371
372
  #
372
373
  # @deprecated Use {#remote_add} instead
373
374
  #
@@ -377,6 +378,7 @@ module Git
377
378
  'Use Git::Repository#remote_add instead.'
378
379
  )
379
380
  remote_add(name, url, opts)
381
+ Git::Remote.new(self, name)
380
382
  end
381
383
 
382
384
  # Removes a remote from this repository
@@ -431,15 +433,15 @@ module Git
431
433
  # A {Git::Repository} instance is accepted for local references and converted
432
434
  # to `url.repo.to_s`.
433
435
  #
434
- # @return [Git::Remote] the updated remote
436
+ # @return [void]
435
437
  #
436
- # @raise [Git::FailedError] when git exits with a non-zero status
438
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
437
439
  #
438
440
  def remote_set_url(name, url)
439
441
  url = url.repo.to_s if url.is_a?(Git::Repository)
440
442
  Git::Commands::Remote::SetUrl.new(@execution_context).call(name, url)
441
443
 
442
- Git::Remote.new(self, name)
444
+ nil
443
445
  end
444
446
 
445
447
  # @param name [String] the name of the remote to update
@@ -451,7 +453,7 @@ module Git
451
453
  #
452
454
  # @return [Git::Remote] the updated remote
453
455
  #
454
- # @raise [Git::FailedError] when git exits with a non-zero status
456
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
455
457
  #
456
458
  # @deprecated Use {#remote_set_url} instead
457
459
  #
@@ -461,6 +463,7 @@ module Git
461
463
  'Use Git::Repository#remote_set_url instead.'
462
464
  )
463
465
  remote_set_url(name, url)
466
+ Git::Remote.new(self, name)
464
467
  end
465
468
 
466
469
  # Configures which branches are fetched for a remote
@@ -529,6 +532,34 @@ module Git
529
532
  end
530
533
  end
531
534
 
535
+ # List all configured remotes as {Git::RemoteInfo} objects
536
+ #
537
+ # Reads the repository configuration via {Git::Configuring#config_list} and
538
+ # returns one {Git::RemoteInfo} per configured remote, preserving the order
539
+ # in which remotes appear in the config. Multi-value fields (`:url`,
540
+ # `:push_url`, `:fetch`, `:push`) are always `Array<String>` (never `nil`).
541
+ #
542
+ # @example List all remotes
543
+ # repo.remote_list
544
+ # # => [#<data Git::RemoteInfo name="origin" ...>,
545
+ # # #<data Git::RemoteInfo name="upstream" ...>]
546
+ #
547
+ # @example Get fetch URLs for all remotes
548
+ # repo.remote_list.map { |r| [r.name, r.url] }.to_h
549
+ #
550
+ # @return [Array<Git::RemoteInfo>] one entry per configured remote
551
+ #
552
+ # Returns an empty array when no remotes are configured.
553
+ #
554
+ # @raise [ArgumentError] if a `remote.*` config entry carries an
555
+ # unrecognized boolean value (e.g. `remote.origin.prune=maybe`)
556
+ #
557
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
558
+ #
559
+ def remote_list
560
+ Git::Parsers::Remote.parse_list(config_list)
561
+ end
562
+
532
563
  # Returns a {Git::Remote} object for the named remote
533
564
  #
534
565
  # @example Get the default 'origin' remote
@@ -563,6 +594,31 @@ module Git
563
594
  result.stdout.split("\n").map { |name| Git::Remote.new(self, name) }
564
595
  end
565
596
 
597
+ # Returns the names of all configured remotes
598
+ #
599
+ # Lists remote names by running `git remote`. This is a lightweight
600
+ # alternative to {#remote_list} when only names are needed — in
601
+ # particular, it returns the raw name (e.g. `"team/upstream"`) preserving
602
+ # any slashes in the remote name.
603
+ #
604
+ # @example Get all remote names
605
+ # repo.remote_names #=> ["origin", "upstream"]
606
+ #
607
+ # @example With a slash-containing remote name
608
+ # repo.remote_names #=> ["origin", "team/upstream"]
609
+ #
610
+ # @return [Array<String>] the configured remote names
611
+ #
612
+ # Returns an empty array when no remotes are configured.
613
+ #
614
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
615
+ #
616
+ # @api public
617
+ #
618
+ def remote_names
619
+ Git::Commands::Remote::List.new(@execution_context).call.stdout.split("\n")
620
+ end
621
+
566
622
  # Option keys accepted by {#ls_remote}
567
623
  #
568
624
  # @return [Array<Symbol>]
@@ -118,8 +118,8 @@ module Git
118
118
  #
119
119
  def reset_hard(commitish = nil, opts = {})
120
120
  Git::Deprecation.warn(
121
- 'Git::Repository::Staging#reset_hard is deprecated and will be removed in a future version. ' \
122
- 'Use #reset(commitish, hard: true) instead.'
121
+ 'Git::Repository#reset_hard is deprecated and will be removed in v6.0.0. ' \
122
+ 'Use Git::Repository#reset(commitish, hard: true) instead.'
123
123
  )
124
124
  reset(commitish, **opts, hard: true)
125
125
  end
@@ -438,8 +438,17 @@ module Git
438
438
  # @raise [ArgumentError] when a deprecated value is not `true`, `false`, or `nil`
439
439
  #
440
440
  def migrate_clean_legacy_options(opts)
441
- opts = deprecate_clean_option(:ff, ':ff option is deprecated. Use force: 2 instead.', opts)
442
- deprecate_clean_option(:force_force, ':force_force option is deprecated. Use force: 2 instead.', opts)
441
+ opts = deprecate_clean_option(
442
+ :ff,
443
+ ':ff option is deprecated and will be removed in v6.0.0. Use force: 2 instead.',
444
+ opts
445
+ )
446
+
447
+ deprecate_clean_option(
448
+ :force_force,
449
+ ':force_force option is deprecated and will be removed in v6.0.0. Use force: 2 instead.',
450
+ opts
451
+ )
443
452
  end
444
453
 
445
454
  # Translate a single deprecated clean option key onto `:force`
@@ -65,7 +65,7 @@ module Git
65
65
  #
66
66
  def stash_list
67
67
  Git::Deprecation.warn(
68
- 'Git::Repository#stash_list is deprecated and will be removed in a future version. ' \
68
+ 'Git::Repository#stash_list is deprecated and will be removed in v6.0.0. ' \
69
69
  'Use Git::Repository#stashes_all instead.'
70
70
  )
71
71
  result = Git::Commands::Stash::List.new(@execution_context).call
@@ -58,7 +58,7 @@ module Git
58
58
  #
59
59
  def empty?
60
60
  Git::Deprecation.warn(
61
- 'Git::Repository#empty? is deprecated and will be removed in a future version. ' \
61
+ 'Git::Repository#empty? is deprecated and will be removed in v6.0.0. ' \
62
62
  'Use Git::Repository#no_commits? instead.'
63
63
  )
64
64
  no_commits?
data/lib/git/version.rb CHANGED
@@ -4,7 +4,7 @@ module Git
4
4
  # The current gem version
5
5
  #
6
6
  # @return [String] the current gem version
7
- VERSION = '5.0.0.beta.5'
7
+ VERSION = '5.0.0'
8
8
 
9
9
  # Represents a git version with major, minor, and patch components
10
10
  #
data/lib/git.rb CHANGED
@@ -72,11 +72,13 @@ require 'git/file_ref'
72
72
  require 'git/fsck_object'
73
73
  require 'git/fsck_result'
74
74
  require 'git/parsers/ls_remote'
75
+ require 'git/parsers/remote'
75
76
  require 'git/version_constraint'
76
77
  require 'git/commands'
77
78
  require 'git/log'
78
79
  require 'git/object'
79
80
  require 'git/remote'
81
+ require 'git/remote_info'
80
82
  require 'git/repository'
81
83
  require 'git/status'
82
84
  require 'git/stash'
@@ -116,7 +118,7 @@ module Git
116
118
  # `Git::Base` is a module included in {Git::Repository}, so any instance
117
119
  # methods added to `Git::Base` are automatically available on
118
120
  # {Git::Repository} instances. A deprecation warning is emitted for each
119
- # method added, encouraging migration to {Git::Repository}.
121
+ # method added, encouraging migration to an application-owned extension module.
120
122
  #
121
123
  # @example Monkeypatching Git::Base (deprecated)
122
124
  # module Git::Base
@@ -124,16 +126,17 @@ module Git
124
126
  # end
125
127
  # Git.open('.').my_helper # => "hello"
126
128
  #
127
- # @deprecated Define instance methods on {Git::Repository} instead.
129
+ # @deprecated Move custom methods to an application-owned extension module and
130
+ # include or prepend it into {Git::Repository}.
128
131
  #
129
132
  # @api public
130
133
  Base = Module.new do
131
134
  # Emit a deprecation warning each time a method is defined in Git::Base so
132
- # that authors of monkeypatches are nudged toward Git::Repository.
135
+ # that authors of monkeypatches are nudged toward application-owned extensions.
133
136
  def self.method_added(method_name)
134
137
  Git::Deprecation.warn(
135
138
  'Monkeypatching Git::Base is deprecated and will be removed in v6.0.0. ' \
136
- "Define #{method_name} in Git::Repository instead."
139
+ "Move #{method_name} to an application-owned extension module for Git::Repository."
137
140
  )
138
141
  super
139
142
  end
@@ -751,7 +754,7 @@ module Git
751
754
  def self.binary_version(binary_path = nil)
752
755
  binary_path ||= Git::Config.instance.binary_path
753
756
  Git::Deprecation.warn(
754
- 'Git.binary_version is deprecated and will be removed in 6.0. ' \
757
+ 'Git.binary_version is deprecated and will be removed in v6.0.0. ' \
755
758
  'Use Git.git_version instead, which returns a Git::Version ' \
756
759
  '(not an Array). For the legacy array shape, call: Git.git_version.to_a. ' \
757
760
  'The optional binary_path argument is preserved: Git.git_version(binary_path).'
@@ -0,0 +1,163 @@
1
+ # Plan: Fix Issue #919 — BranchInfo slash-remote parsing (PR series)
2
+
3
+ ## Goal
4
+ Fix `BranchInfo#remote_name` and `#short_name` for remote names containing `/`
5
+ (e.g. `team/upstream`). Root cause: `BRANCH_REFNAME_REGEXP` uses `[^/]+` which
6
+ can only capture one path segment. Fix: resolve at parse time using the configured
7
+ remote list.
8
+
9
+ ## PR sequence overview
10
+ - **PR 1** — Add `remote_names` facade method. Independent. No deps.
11
+ - **PR 2** — `BranchInfo` stored fields + remote-aware parser. Independent. No deps.
12
+ - **PR 3** — Wire `branch_list` end-to-end (closes #919). Depends on PR 1 + PR 2.
13
+
14
+ PR 1 and PR 2 can be developed in parallel. PR 3 merges last.
15
+
16
+ ```
17
+ PR1 ─┐
18
+ ├─> PR3 (closes #919)
19
+ PR2 ─┘
20
+ ```
21
+
22
+ ---
23
+
24
+ ## PR 1 — Add `remote_names` to RemoteOperations
25
+ **Branch:** `feat/remote-names`
26
+ **Depends on:** nothing
27
+ **Value:** new public API `repo.remote_names` → `Array<String>` (lightweight; no RemoteInfo).
28
+
29
+ ### Changes
30
+ - `lib/git/repository/remote_operations.rb`: add `remote_names` near `remotes` (~line 592).
31
+ - Calls `Git::Commands::Remote::List.new(@execution_context).call`, splits stdout on `"\n"`.
32
+ - Returns `Array<String>`. Empty array when no remotes.
33
+ - Full YARD with `@api public`, `@example`, `@return`, `@raise [Git::FailedError]`.
34
+
35
+ ### Tests
36
+ - `spec/unit/git/repository/remote_operations_spec.rb`: stub `Commands::Remote::List` →
37
+ stdout `"origin\nteam/upstream\n"`, assert `["origin", "team/upstream"]`; empty-stdout → `[]`.
38
+ - `spec/integration/git/repository/remote_operations_spec.rb`: real repo with two remotes
39
+ (one slash name), assert both names are returned.
40
+
41
+ ### Done when
42
+ - Unit + integration green; RuboCop clean; YARD coverage clean.
43
+
44
+ ---
45
+
46
+ ## PR 2 — BranchInfo stored fields + remote-aware parser
47
+ **Branch:** `feat/branchinfo-slash-remote-parsing`
48
+ **Depends on:** nothing (backward compatible; default behavior unchanged)
49
+ **Value:** `Git::Parsers::Branch.parse_list(stdout, remote_names: [...])` resolves slash
50
+ remotes correctly; `BranchInfo` can carry an explicit `remote_name` (with `short_name` derived).
51
+
52
+ ### Changes — `lib/git/branch_info.rb`
53
+ - **Alt 1-lite: store ONLY `:remote_name` as a new `Data.define` member; keep `short_name`
54
+ DERIVED (not a member).**
55
+ - Add `:remote_name` to the `Data.define` member list (NOT `:short_name`).
56
+ - Add `initialize` override inside the block using private `UNSET = Object.new.freeze`
57
+ sentinel: when `remote_name:` not supplied, derive via `BRANCH_REFNAME_REGEXP`
58
+ (preserves all existing `BranchInfo.new` callsites); when supplied, store as given.
59
+ - Keep `short_name` as a COMPUTED method: derive from `refname` given the (now reliable)
60
+ `remote_name` — strip `(refs/)?remotes/<remote_name>/` for remote branches, or
61
+ `refs/heads/` for local branches. Deterministic from `refname` + `remote_name`, so it
62
+ never needs storing and stays out of `Data` equality/hash/with.
63
+ - Remove only the `remote_name` computed method body (now a field accessor); `short_name`
64
+ stays a method (rewritten to use `remote_name`).
65
+ - Keep `detached?`, `unborn?`, `remote?`, `current?`, `other_worktree?`, `symref?`, `to_s`.
66
+ - YARD: add `@!attribute` doc for the single new stored field `remote_name`; keep `short_name`
67
+ documented as a derived method; soften the `BRANCH_REFNAME_REGEXP` `@note` (now a fallback
68
+ used inside `initialize`, not the sole path).
69
+
70
+ ### Changes — `lib/git/parsers/branch.rb`
71
+ - Add private `resolve_remote_name(refname, remote_names)`:
72
+ - `nil` if refname is not under `(refs/)?remotes/`.
73
+ - Strip `(refs/)?remotes/` prefix; select configured names that are a prefix of `path + "/"`;
74
+ pick the LONGEST match.
75
+ - When `remote_names` empty OR no match (stale ref): fall back to single-segment regex.
76
+ - `build_branch_info(fields, remote_names: [])`: call `resolve_remote_name`; pass only
77
+ `:remote_name` explicitly to `BranchInfo.new` (`short_name` is derived by BranchInfo).
78
+ - Thread `remote_names:` through `parse_branch_line(line, remote_names: [])` and
79
+ `parse_list(stdout, remote_names: [])`. Default `[]` = current behavior (regex fallback).
80
+ - YARD `@param remote_names` on the three methods.
81
+ - Note: no separate `resolve_short_name` helper needed — BranchInfo derives `short_name`
82
+ from `refname` + the resolved `remote_name`.
83
+
84
+ ### Tests
85
+ - `spec/unit/git/branch_info_spec.rb`: existing callsites unchanged (fallback); add examples
86
+ that an explicit `remote_name:` is stored and that `short_name` is correctly derived from it
87
+ (incl. slash-remote case); equality note (`Data#==` now includes ONE new member,
88
+ `remote_name` — for non-slash remotes regex==explicit so still equal). Audit for `.with` usage.
89
+ - `spec/unit/git/parsers/branch_spec.rb`: update stubs to new signature; add
90
+ `parse_list(stdout, remote_names: ['team/upstream'])` correctness (both `remote_name` and the
91
+ derived `short_name`); longest-prefix example (both `team` and `team/upstream` configured →
92
+ `team/upstream` wins); stale-ref fallback example.
93
+ - `spec/integration/git/parsers/branch_spec.rb`: repo with `team/upstream` remote,
94
+ `remote_names: ['team/upstream']` → correct `remote_name`/`short_name`.
95
+
96
+ ### Done when
97
+ - All existing branch specs still green (no behavior change at default); new specs green;
98
+ RuboCop + YARD clean.
99
+
100
+ ---
101
+
102
+ ## PR 3 — Wire `branch_list` end-to-end (closes #919)
103
+ **Branch:** `fix/919-branch-list-slash-remote`
104
+ **Depends on:** PR 1 (`remote_names`) + PR 2 (parser accepts `remote_names:`)
105
+ **Value:** `repo.branch_list`, `repo.branches`, `branches_all`, and `Git::Branches#[]` all
106
+ report correct remote/branch split for slash remotes. Closes #919.
107
+
108
+ ### Changes — `lib/git/repository/branching.rb`
109
+ - `branch_list(*patterns, remote_names: nil)`:
110
+ - When `remote_names` nil, call `self.remote_names` to fetch automatically.
111
+ - Pass `remote_names:` to `Git::Parsers::Branch.parse_list`.
112
+
113
+ ### Changes — docs / scope note
114
+ - `lib/git/branch.rb`: update the `@note` on `BRANCH_NAME_REGEXP` (the string-constructor path)
115
+ to state slash remotes are not resolved there and reference #919 + the follow-up issue.
116
+
117
+ ### Tests
118
+ - `spec/unit/git/repository/branching_spec.rb` `#branch_list`: update `parse_list` stub
119
+ expectations to `(stdout, remote_names: [...])`; add test that `remote_names` is auto-called
120
+ when not given; add test that explicit `remote_names:` bypasses the auto call.
121
+ - `spec/integration/git/repository/branching_spec.rb`: repo with `team/upstream` remote →
122
+ `branch_list` returns `BranchInfo` with `remote_name == 'team/upstream'`, `short_name == 'main'`.
123
+ - `spec/unit/git/branches_spec.rb` (or integration): `Git::Branches#[]` lookup with a slash
124
+ remote resolves correctly (`branch.full` = `remotes/team/upstream/main`, and the
125
+ `remotes/`-stripped key works).
126
+
127
+ ### Finalize
128
+ - File a follow-up issue for the `Git::Branch` string-constructor path
129
+ (`BRANCH_NAME_REGEXP`) and link it from the `@note` and from #919.
130
+ - Confirm #919 acceptance examples (`remote_name` → `'team/upstream'`, `short_name` →
131
+ `'feature/foo'`) are covered by integration tests.
132
+
133
+ ### Done when
134
+ - Full suite green; RuboCop + YARD clean; #919 examples demonstrably fixed; follow-up issue filed.
135
+
136
+ ---
137
+
138
+ ## Accepted decisions (finalized)
139
+ 1. String path (`Git::Branch::BRANCH_NAME_REGEXP`): leave as documented limitation (PR 3 note +
140
+ follow-up issue). Not fixed in this series.
141
+ 2. Design is **Alt 1-lite**: store ONLY `remote_name` as a new `Data.define` member; keep
142
+ `short_name` derived from `refname` + `remote_name`. One new member → smaller
143
+ equality/hash/with footprint than storing both.
144
+ 3. Stale `refs/remotes/` ref matching no configured remote: fall back to single-segment regex.
145
+ 4. Keyword arg named `remote_names:` everywhere (parser + facade).
146
+ 5. Add BOTH unit and integration tests for `remote_names`.
147
+ 6. Defer the two-pass "only fetch remotes if refs/remotes present" optimization.
148
+ 7. Full YARD updates in each PR.
149
+ 8. Add a `Git::Branches#[]` slash-remote lookup test (PR 3).
150
+ 9. #919 is satisfied by PR 3 (issue is scoped to `BranchInfo#remote_name`/`#short_name`).
151
+
152
+ ## Relevant files
153
+ - `lib/git/repository/remote_operations.rb` — PR 1 (`remote_names`)
154
+ - `lib/git/branch_info.rb` — PR 2 (fields, `initialize`, YARD)
155
+ - `lib/git/parsers/branch.rb` — PR 2 (`remote_names:` threading, resolve helpers)
156
+ - `lib/git/repository/branching.rb` — PR 3 (`branch_list`)
157
+ - `lib/git/branch.rb` — PR 3 (string-path `@note` only; no code fix)
158
+ - specs: remote_operations (PR1), branch_info + parsers/branch (PR2), branching + branches (PR3)
159
+
160
+ ## Not changing (auto-correct after PR 3)
161
+ - `branches_all` — uses `info.remote_name`/`#short_name`; values become correct.
162
+ - `Git::Branch#initialize_from_branch_info` — uses these; values become correct.
163
+ - All existing `BranchInfo.new` callsites — `initialize` fallback keeps them working.
@@ -0,0 +1,126 @@
1
+ # Plan: Migrate ActiveRecord-style classes to `*Info` value objects
2
+
3
+ This document is the durable summary and issue index for the effort to migrate
4
+ ruby-git's "ActiveRecord-style" domain classes (rich objects constructed with
5
+ `def initialize(base, ...)` that both hold data **and** perform git operations)
6
+ toward immutable `*Info` value objects (DAOs), following the pattern established
7
+ by `Git::BranchInfo` and `Git::RemoteInfo`.
8
+
9
+ It captures analysis and decisions so they are not re-derived in future sessions.
10
+ It is a planning/index document, not a specification — each tracked issue owns the
11
+ detailed design for its area.
12
+
13
+ ## Contents
14
+
15
+ - [Motivation](#motivation)
16
+ - [The pattern](#the-pattern)
17
+ - [Status of each class](#status-of-each-class)
18
+ - [Issue index](#issue-index)
19
+ - [Versioning and sequencing](#versioning-and-sequencing)
20
+ - [Key decisions](#key-decisions)
21
+ - [Open threads / not yet tracked](#open-threads--not-yet-tracked)
22
+
23
+ ## Motivation
24
+
25
+ The modern facade design returns immutable, parser-built value objects
26
+ (`Git::BranchInfo`, `Git::RemoteInfo`, `Git::StashInfo`, `Git::TagInfo`,
27
+ `Git::ConfigEntryInfo`, the `Diff*Info` family, …). Several older classes predate
28
+ this and mix data with operations while holding a repository reference (`@base`).
29
+ They are harder to test, encourage stateful usage, and (in some cases) carry
30
+ latent bugs. Migrating them to `*Info` value objects plus name-based facade
31
+ operations aligns the whole API and unblocks cleaner deprecations.
32
+
33
+ The recurring "tell" for a migration candidate is a `def initialize(base, ...)`
34
+ constructor combined with operation methods — the object both *is* data and
35
+ *does* work.
36
+
37
+ ## The pattern
38
+
39
+ For each domain:
40
+
41
+ 1. **Value object** — an immutable `Data.define` `*Info` at the top-level `Git::`
42
+ namespace holding only parsed metadata, with `#to_s` returning the string a
43
+ caller can pass back to git-facing methods (e.g. a refname or stash selector).
44
+ 2. **Parser** — a `Git::Parsers::*` that builds `Array<*Info>` from raw git output.
45
+ 3. **Facade** — `Git::Repository::*` methods that return the value objects and that
46
+ accept names / `*Info` / any `#to_s` for operations.
47
+ 4. **Deprecation** — the legacy AR class, its collection, and the facade methods
48
+ that return them are deprecated (additive, non-breaking) in one major and
49
+ removed in the next.
50
+
51
+ ## Status of each class
52
+
53
+ | Domain | AR class(es) | Value object | State |
54
+ | --- | --- | --- | --- |
55
+ | Branch | `Git::Branch` / `Git::Branches` | `Git::BranchInfo`, `Git::DetachedHeadInfo` | Value object + `branch_list` shipped; deprecation of AR classes tracked (#1639) |
56
+ | Remote | `Git::Remote` | `Git::RemoteInfo` | `RemoteInfo` + parser shipped; `remotes` deprecation tracked (#1640); `Git::Remote` class deprecation tracked (#1643) |
57
+ | Stash | `Git::Stash` / `Git::Stashes` | `Git::StashInfo` (exists) | Redesign tracked (#1634); `Git::Branch#stashes` cleanup (#1637) |
58
+ | Worktree | `Git::Worktree` / `Git::Worktrees` | *(needs `Git::WorktreeInfo`)* | Redesign tracked (#1635) — value object + parser do not exist yet |
59
+ | Tag | `Git::Object::Tag` | `Git::TagInfo` (exists) | Half-migrated; finish tracked under umbrella (#1636) |
60
+ | Commit / Tree / Blob | `Git::Object::Commit/Tree/Blob` | *(none)* | Deferred, umbrella (#1636) |
61
+ | Status | `Git::Status` / `Git::Status::StatusFile` | *(none)* | Deferred, umbrella (#1636) |
62
+ | Author | `Git::Author` (mutable) | *(none)* | Deferred, umbrella (#1636) |
63
+ | Diff / Log | `Git::Diff`, `Git::Log` | many `Diff*Info`, `Git::Log::Result` | Value layer largely exists; return-type audit deferred (#1636) |
64
+
65
+ ## Issue index
66
+
67
+ - **#1631** — bug: slash-containing remote names in the `Git::Branch` string
68
+ constructor path. Kept open; resolution is to route users to the `BranchInfo`
69
+ path (folds into #1639).
70
+ - **#1634** — redesign `Git::Repository::Stashing` around `Git::StashInfo`.
71
+ - **#1635** — redesign `Git::Repository::WorktreeOperations` around a new
72
+ `Git::WorktreeInfo` (needs value object + parser first).
73
+ - **#1636** — umbrella tracker for post-7.x AR→`*Info` migrations
74
+ (Commit/Tree/Blob, finish Tag, Status, Author, Diff/Log audit).
75
+ - **#1637** — deprecate `Git::Branch#stashes` (ignores the branch, returns all
76
+ repo stashes) in favor of `Git::Repository#stashes_all`.
77
+ - **#1639** — deprecate `Git::Branch` / `Git::Branches` and
78
+ `Git::Repository#branch` / `#branches` in favor of the `BranchInfo` API.
79
+ - **#1640** — deprecate `Git::Repository#remotes` in favor of `#remote_list`
80
+ (from `remote_refactor_plan.md` PR 3, Step 5).
81
+ - **#1641** — add `Git::Repository#in_branch` and a merge-into-branch facade path
82
+ (preconditions for #1639).
83
+ - **#1643** — deprecate `Git::Remote` (and `Git::Repository#remote`) in favor of
84
+ the `RemoteInfo` API (remote analog of #1639; depends on #1640).
85
+
86
+ ## Versioning and sequencing
87
+
88
+ - Each migration is **additive + deprecate** in one major, **remove** in the next
89
+ (e.g. deprecate in 5.x → remove in 6.x, or the analogous later pair).
90
+ - Stash (#1634) and Worktree (#1635) are the in-flight redesigns; the remaining
91
+ candidates in #1636 are intentionally deferred until after the 7.x cycle.
92
+ - **Guardrail during any major:** do not add *new* public APIs that return
93
+ AR-style objects that are already destined for deprecation. When touching these
94
+ areas, prefer adding the value-object-returning method. The `Git::Object::Tag`
95
+ vs `Git::TagInfo` split is the most important to watch (already half-migrated).
96
+
97
+ ## Key decisions
98
+
99
+ - **`Git::WorktreeInfo` should capture the full porcelain record** — `path`,
100
+ `head`, `branch`, `bare`, `detached`, `locked` (+reason), `prunable` (+reason) —
101
+ not just the `[dir, sha]` tuple `worktrees_all` returns today.
102
+ - **Branch has no capability gap.** An audit confirmed every `Git::Branch`
103
+ operation already delegates to an existing `Git::Repository` facade method that
104
+ accepts a name/string. The only missing pieces are two composites — `in_branch`
105
+ and the `merge(branch)` overload — tracked in #1641 as deprecation preconditions.
106
+ - **`checkout` semantic wrinkle:** `Git::Branch#checkout` auto-creates the branch
107
+ first; `Git::Repository#checkout(name)` does not. Deprecation notes must call
108
+ this out.
109
+ - **`Git::Branch#stashes` is effectively a latent bug** — it ignores the branch
110
+ receiver and returns all repository stashes — so it is "deprecate and delete,"
111
+ not "migrate" (#1637).
112
+ - **`Git::Repository::Branching` / `RemoteOperations` / `WorktreeOperations` are
113
+ the replacements, not deprecation targets.** Only the specific methods that
114
+ return AR objects (`#branch`, `#branches`, `#remotes`, `#worktree`,
115
+ `#worktrees`) are deprecated; the operation methods stay.
116
+ - **Stashes are not branch-scoped in git**, so no branch-scoped stash API is
117
+ warranted.
118
+
119
+ ## Open threads
120
+
121
+ All previously loose threads are now tracked within the issues above:
122
+
123
+ - **`Git::Repository#worktree` / `#worktrees` factory deprecation** — in scope of
124
+ #1635 (confirmed in its transition plan).
125
+ - **`config_remote` relationship to `remote_list`** — folded into #1640 as an open
126
+ question to resolve during that work.