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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +520 -0
- data/README.md +53 -475
- data/UPGRADING.md +26 -0
- data/lib/git/branch.rb +5 -1
- data/lib/git/branch_info.rb +135 -76
- data/lib/git/branches.rb +1 -1
- data/lib/git/log.rb +12 -6
- data/lib/git/object.rb +5 -2
- data/lib/git/parsers/branch.rb +62 -64
- data/lib/git/parsers/remote.rb +162 -0
- data/lib/git/remote.rb +1 -22
- data/lib/git/remote_info.rb +146 -0
- data/lib/git/repository/branching.rb +57 -22
- data/lib/git/repository/committing.rb +25 -6
- data/lib/git/repository/context_helpers.rb +1 -1
- data/lib/git/repository/diffing.rb +2 -1
- data/lib/git/repository/factories.rb +10 -3
- data/lib/git/repository/merging.rb +5 -5
- data/lib/git/repository/remote_operations.rb +64 -8
- data/lib/git/repository/staging.rb +13 -4
- data/lib/git/repository/stashing.rb +1 -1
- data/lib/git/repository/status_operations.rb +1 -1
- data/lib/git/version.rb +1 -1
- data/lib/git.rb +8 -5
- data/redesign/branch_parse_refactor_plan.md +163 -0
- data/redesign/info_object_migration_plan.md +126 -0
- data/redesign/integration_test_analysis.md +521 -0
- data/redesign/remote_refactor_plan.md +164 -0
- metadata +10 -4
|
@@ -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 [
|
|
334
|
+
# @return [void]
|
|
334
335
|
#
|
|
335
336
|
# @raise [ArgumentError] when unsupported option keys are provided
|
|
336
337
|
#
|
|
337
|
-
# @raise [Git::FailedError]
|
|
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
|
-
|
|
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]
|
|
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 [
|
|
436
|
+
# @return [void]
|
|
435
437
|
#
|
|
436
|
-
# @raise [Git::FailedError]
|
|
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
|
-
|
|
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]
|
|
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
|
|
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(
|
|
442
|
-
|
|
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
|
|
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
|
|
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
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
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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
|
|
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.
|