carson 2.10.0 → 2.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9b99975ce01b36b30e333553f7c491ebbf92591a3bc7dbc492633984b80a807a
4
- data.tar.gz: 55c64697792a9400be1fef45a68731ac7eab6be3be517c79ae2caebd5f5edc9c
3
+ metadata.gz: ef5d5bfa64c8a8b7f4d2b4893dd5a3cab05ab2f154bd12ab926837ae396c2fe2
4
+ data.tar.gz: 106a0dc1a54c05e59ba9fe4eb0d01f647c420ac305d92a5d70cbc3c606300006
5
5
  SHA512:
6
- metadata.gz: c716497d2db2c235dc5d8fa90c65fda8002722cabd7e8425305ce9c26917ebd48e905aed20564393d01178bd8d12257606097fdbf3ab1a77be71d1e48dc08681
7
- data.tar.gz: 7f5358b84901463fbd1b211c3094df9c7c46566f9decc3a99d4d643264fc76e79de8b3c298d642c6efac41ecc16aa07647b52e66b6a822f7fff842345dfd9ab6
6
+ metadata.gz: 1c1d53528fa822f8d7e4850234a039459f56053762ed8ed1982e7a27c2b1f7fd7820f6565afc2a8cda596971ce4dfab2e2288aeabf8b92e54e2cc5e9266ab213
7
+ data.tar.gz: 04755a262052a630f8200089d03ff1f5a7c48ab8e68f50097fcdd206a8672da988d51c5f041384752a7d97712854b054aa4fd85b2b7268989190ea248d33bf1f
data/RELEASE.md CHANGED
@@ -5,6 +5,18 @@ Release-note scope rule:
5
5
  - `RELEASE.md` records only version deltas, breaking changes, and migration actions.
6
6
  - Operational usage guides live in `MANUAL.md` and `API.md`.
7
7
 
8
+ ## 2.11.0 — Self-Diagnosing Audit and Duplicate-Remote Prevention
9
+
10
+ ### What changed
11
+
12
+ - **Audit concise output now names the remote and suggests recovery actions.** "Main sync (origin): ahead by 1 — git fetch origin, or carson setup to switch remote." instead of the opaque "Main sync: ahead by 1 — reset local drift." The remote name is visible; the fix is embedded.
13
+ - **Setup warns when multiple remotes share the same URL.** Interactive mode annotates duplicates with `[duplicate]` and prints a warning. Silent mode logs a `duplicate_remotes:` verbose line. URL normalisation treats SSH and HTTPS variants as equal (`git@github.com:user/repo.git` matches `https://github.com/user/repo`).
14
+
15
+ ### What users must do now
16
+
17
+ 1. Upgrade Carson to `2.11.0`.
18
+ 2. If you have duplicate remotes (e.g. both `origin` and `github` pointing to the same URL), remove the stale one with `git remote remove <name>`.
19
+
8
20
  ## 2.10.0 — Lower Ruby Requirement to 3.4
9
21
 
10
22
  ### What changed
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.10.0
1
+ 2.11.0
@@ -46,13 +46,13 @@ module Carson
46
46
  puts_verbose "main_vs_remote_main_behind: #{behind_count}"
47
47
  puts_verbose "ACTION: local #{config.main_branch} is ahead of #{config.git_remote}/#{config.main_branch} by #{ahead_count} commit#{plural_suffix( count: ahead_count )}; reset local drift before commit/push workflows."
48
48
  audit_state = "block"
49
- audit_concise_problems << "Main sync: ahead by #{ahead_count} — reset local drift."
49
+ audit_concise_problems << "Main sync (#{config.git_remote}): ahead by #{ahead_count} — git fetch #{config.git_remote}, or carson setup to switch remote."
50
50
  elsif behind_count.positive?
51
51
  puts_verbose "main_vs_remote_main_ahead: #{ahead_count}"
52
52
  puts_verbose "main_vs_remote_main_behind: #{behind_count}"
53
53
  puts_verbose "ACTION: local #{config.main_branch} is behind #{config.git_remote}/#{config.main_branch} by #{behind_count} commit#{plural_suffix( count: behind_count )}; run carson sync."
54
54
  audit_state = "attention" if audit_state == "ok"
55
- audit_concise_problems << "Main sync: behind by #{behind_count} — run carson sync."
55
+ audit_concise_problems << "Main sync (#{config.git_remote}): behind by #{behind_count} — run carson sync."
56
56
  else
57
57
  puts_verbose "main_vs_remote_main_ahead: 0"
58
58
  puts_verbose "main_vs_remote_main_behind: 0"
@@ -1,3 +1,6 @@
1
+ require "set"
2
+ require "uri"
3
+
1
4
  module Carson
2
5
  class Runtime
3
6
  module Setup
@@ -51,6 +54,15 @@ module Carson
51
54
  puts_verbose "detected_remote: none"
52
55
  end
53
56
 
57
+ remotes = list_git_remotes
58
+ duplicates = duplicate_remote_groups( remotes: remotes )
59
+ unless duplicates.empty?
60
+ duplicates.each_value do |group|
61
+ names = group.map { it.fetch( :name ) }.join( " and " )
62
+ puts_verbose "duplicate_remotes: #{names} share the same URL"
63
+ end
64
+ end
65
+
54
66
  branch = detect_main_branch
55
67
  if branch && branch != config.main_branch
56
68
  choices[ "git.main_branch" ] = branch
@@ -69,9 +81,18 @@ module Carson
69
81
  return nil
70
82
  end
71
83
 
84
+ duplicates = duplicate_remote_groups( remotes: remotes )
85
+ duplicate_names = duplicates.values.flatten.map { it.fetch( :name ) }.to_set
86
+ unless duplicates.empty?
87
+ duplicates.each_value do |group|
88
+ names = group.map { it.fetch( :name ) }.join( " and " )
89
+ puts_line "Remotes #{names} share the same URL. Consider removing the duplicate."
90
+ end
91
+ end
92
+
72
93
  puts_line ""
73
94
  puts_line "Git remote"
74
- options = build_remote_options( remotes: remotes )
95
+ options = build_remote_options( remotes: remotes, duplicate_names: duplicate_names )
75
96
  options << { label: "Other (enter name)", value: :other }
76
97
 
77
98
  default_index = 0
@@ -151,12 +172,13 @@ module Carson
151
172
  value.empty? ? nil : value
152
173
  end
153
174
 
154
- def build_remote_options( remotes: )
175
+ def build_remote_options( remotes:, duplicate_names: Set.new )
155
176
  sorted = sort_remotes( remotes: remotes )
156
177
  sorted.map do |entry|
157
178
  name = entry.fetch( :name )
158
179
  url = entry.fetch( :url )
159
- { label: "#{name} (#{url})", value: name }
180
+ tag = duplicate_names.include?( name ) ? " [duplicate]" : ""
181
+ { label: "#{name} (#{url})#{tag}", value: name }
160
182
  end
161
183
  end
162
184
 
@@ -173,6 +195,35 @@ module Carson
173
195
  well_known.sort_by { |e| WELL_KNOWN_REMOTES.index( e.fetch( :name ) ) || 999 } + others.sort_by { |e| e.fetch( :name ) }
174
196
  end
175
197
 
198
+ # Normalises a remote URL so SSH and HTTPS variants of the same host/path compare equal.
199
+ # Strips trailing .git, lowercases, converts git@host:path to https://host/path.
200
+ def normalise_remote_url( url: )
201
+ text = url.to_s.strip
202
+ return "" if text.empty?
203
+
204
+ # Convert SSH shorthand (git@host:owner/repo) to HTTPS form.
205
+ if text.match?( /\A[\w.-]+@[\w.-]+:/ )
206
+ text = text.sub( /\A[\w.-]+@([\w.-]+):/, 'https://\1/' )
207
+ end
208
+
209
+ text = text.delete_suffix( ".git" )
210
+ text = text.chomp( "/" )
211
+ text.downcase
212
+ end
213
+
214
+ # Groups remotes that share the same normalised URL. Returns a hash of
215
+ # normalised_url => [remote entries] for groups with more than one member.
216
+ def duplicate_remote_groups( remotes: )
217
+ by_url = {}
218
+ remotes.each do |entry|
219
+ key = normalise_remote_url( url: entry.fetch( :url ) )
220
+ next if key.empty?
221
+
222
+ ( by_url[ key ] ||= [] ) << entry
223
+ end
224
+ by_url.select { |_url, entries| entries.length > 1 }
225
+ end
226
+
176
227
  def build_main_branch_options
177
228
  options = []
178
229
  main_exists = branch_exists_locally_or_remote?( branch: "main" )
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carson
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.10.0
4
+ version: 2.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hailei Wang