kk-git 0.2.1 → 0.2.3
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/exe/kk-git +35 -15
- data/lib/kk/git/commit_message.rb +9 -23
- data/lib/kk/git/git_ops.rb +156 -89
- data/lib/kk/git/git_runner.rb +26 -0
- data/lib/kk/git/network_hints.rb +79 -0
- data/lib/kk/git/rake_runner.rb +25 -0
- data/lib/kk/git/rake_tasks.rb +3 -0
- data/lib/kk/git/release.rb +113 -0
- data/lib/kk/git/ruby_warnings.rb +33 -0
- data/lib/kk/git/version.rb +1 -2
- data/lib/kk/git.rb +3 -0
- metadata +11 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 476a578e1aeea922d2d1c7835f42066e625a9cc976f5d19d713def1f8f11aa6c
|
|
4
|
+
data.tar.gz: dac594a319b48a7a089b58f45b5739fc9070cedb74ff113e64306c1c1faa202c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 44f2c4ddbff4478b973096815179d39a28711497e3f9bc2bd3b4461e6a7c44283565eb52e93b25588aca78a6ca9a95f57b170f0af0b5fafe6b50e6ae13e283cc
|
|
7
|
+
data.tar.gz: 54679b9c73d3e181db53ba4fccab282f3ae3878d07215663af441458e9215b6d935af1e41b735d9821e78baf74f4aad0e31db6f6d4209eb4c21f7b38f430c214
|
data/exe/kk-git
CHANGED
|
@@ -15,7 +15,17 @@ def to_utf8(str)
|
|
|
15
15
|
|
|
16
16
|
s = str.to_s.dup
|
|
17
17
|
s.force_encoding(Encoding::UTF_8)
|
|
18
|
-
s.scrub(
|
|
18
|
+
s.scrub("\uFFFD")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def parse_repo_options!(argv)
|
|
22
|
+
options = { repo_dir: '.' }
|
|
23
|
+
parser = OptionParser.new do |opts|
|
|
24
|
+
opts.on('--repo DIR', 'Git repo directory (default: current dir)') { |v| options[:repo_dir] = v }
|
|
25
|
+
opts.on('-h', '--help', 'Show help') { |v| options[:help] = v }
|
|
26
|
+
end
|
|
27
|
+
parser.parse!(argv)
|
|
28
|
+
options
|
|
19
29
|
end
|
|
20
30
|
|
|
21
31
|
def run_commit_message!(argv)
|
|
@@ -81,15 +91,16 @@ def run_commit_message!(argv)
|
|
|
81
91
|
end
|
|
82
92
|
|
|
83
93
|
def run_status!(argv)
|
|
84
|
-
options = { format: :text }
|
|
94
|
+
options = { format: :text, repo_dir: '.' }
|
|
85
95
|
parser = OptionParser.new do |opts|
|
|
86
96
|
opts.banner = 'Usage: kk-git status [options]'
|
|
97
|
+
opts.on('--repo DIR', 'Git repo directory (default: current dir)') { |v| options[:repo_dir] = v }
|
|
87
98
|
opts.on('--format FORMAT', 'Output format: text/json (default: text)') { |v| options[:format] = v.to_s.downcase.to_sym }
|
|
88
99
|
opts.on('-h', '--help', 'Show help') { puts opts; exit 0 }
|
|
89
100
|
end
|
|
90
101
|
parser.parse!(argv)
|
|
91
102
|
|
|
92
|
-
hash = KKGit::GitOps.status_hash
|
|
103
|
+
hash = KKGit::GitOps.status_hash(repo_dir: options[:repo_dir])
|
|
93
104
|
if options[:format] == :json
|
|
94
105
|
puts JSON.pretty_generate(hash)
|
|
95
106
|
else
|
|
@@ -102,31 +113,35 @@ def run_status!(argv)
|
|
|
102
113
|
end
|
|
103
114
|
|
|
104
115
|
def run_sync!(argv)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
options = parse_repo_options!(argv)
|
|
117
|
+
if options[:help]
|
|
118
|
+
puts 'Usage: kk-git sync [--repo DIR]'
|
|
119
|
+
exit 0
|
|
108
120
|
end
|
|
109
|
-
parser.parse!(argv)
|
|
110
121
|
|
|
122
|
+
repo_dir = options[:repo_dir]
|
|
111
123
|
remote = KKGit::GitOps.remote
|
|
112
|
-
branch = KKGit::GitOps.branch
|
|
113
|
-
s = KKGit::GitOps.status(remote: remote, branch: branch)
|
|
124
|
+
branch = KKGit::GitOps.branch(repo_dir: repo_dir)
|
|
125
|
+
s = KKGit::GitOps.status(remote: remote, branch: branch, repo_dir: repo_dir)
|
|
114
126
|
|
|
115
127
|
if s.needs_sync?
|
|
116
|
-
KKGit::GitOps.sync_with_remote!(remote, branch)
|
|
128
|
+
KKGit::GitOps.sync_with_remote!(remote, branch, repo_dir: repo_dir)
|
|
117
129
|
else
|
|
118
130
|
puts 'Already in sync with remote'
|
|
119
131
|
end
|
|
120
132
|
end
|
|
121
133
|
|
|
122
134
|
def run_push!(argv)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
options = parse_repo_options!(argv)
|
|
136
|
+
if options[:help]
|
|
137
|
+
puts 'Usage: kk-git push [--repo DIR]'
|
|
138
|
+
exit 0
|
|
126
139
|
end
|
|
127
|
-
parser.parse!(argv)
|
|
128
140
|
|
|
129
|
-
KKGit::GitOps.auto_commit_push!
|
|
141
|
+
KKGit::GitOps.auto_commit_push!(repo_dir: options[:repo_dir])
|
|
142
|
+
rescue KKGit::GitOps::Error => e
|
|
143
|
+
warn e.message
|
|
144
|
+
exit 1
|
|
130
145
|
end
|
|
131
146
|
|
|
132
147
|
def main_help
|
|
@@ -149,6 +164,11 @@ def main_help
|
|
|
149
164
|
KK_GIT_SKIP_PULL=1 skip pull step
|
|
150
165
|
KK_GIT_SKIP_PUSH=1 skip push step
|
|
151
166
|
KK_GIT_AMEND=1 amend last commit instead of creating new one
|
|
167
|
+
KK_GIT_CONFIRM=1 print message and require KK_GIT_YES=1
|
|
168
|
+
KK_GIT_ALLOW_SENSITIVE=1 allow committing .env/credentials paths
|
|
169
|
+
KK_GIT_DEFAULT_TYPE default type for code edits (default: chore)
|
|
170
|
+
KK_GIT_PUSH_URL push to explicit URL (bypass SSH remote)
|
|
171
|
+
KK_GIT_PULL_URL pull from explicit URL (bypass SSH remote)
|
|
152
172
|
|
|
153
173
|
Run `kk-git <command> --help` for command-specific options.
|
|
154
174
|
HELP
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
|
-
|
|
4
|
+
require_relative 'git_runner'
|
|
5
5
|
|
|
6
6
|
module KKGit
|
|
7
7
|
# Generate Conventional Commits messages from git changes.
|
|
@@ -211,14 +211,7 @@ module KKGit
|
|
|
211
211
|
end
|
|
212
212
|
|
|
213
213
|
def self.run_git(args, repo_dir:)
|
|
214
|
-
|
|
215
|
-
# Open3 stdout/stderr may be ASCII-8BIT (BINARY). Normalize to UTF-8 to avoid concat errors.
|
|
216
|
-
stdout = stdout.to_s.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: '�')
|
|
217
|
-
stderr = stderr.to_s.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: '�')
|
|
218
|
-
|
|
219
|
-
raise "git #{args.join(' ')} failed: #{stderr.strip}" unless status.success?
|
|
220
|
-
|
|
221
|
-
stdout
|
|
214
|
+
GitRunner.capture!(args, repo_dir: repo_dir)
|
|
222
215
|
end
|
|
223
216
|
|
|
224
217
|
# Infer type/scope/subject (with optional breaking detection)
|
|
@@ -238,17 +231,6 @@ module KKGit
|
|
|
238
231
|
types.min_by { |t| TYPE_PRIORITY[t] || 999 } || 'chore'
|
|
239
232
|
end
|
|
240
233
|
|
|
241
|
-
def self.pick_scope(scopes, fallback_scope:)
|
|
242
|
-
uniq = scopes.compact.uniq
|
|
243
|
-
return fallback_scope if uniq.empty?
|
|
244
|
-
return uniq.first if uniq.length == 1
|
|
245
|
-
|
|
246
|
-
# When multiple scopes exist, prefer "repo"; otherwise fallback.
|
|
247
|
-
return 'repo' if uniq.include?('repo')
|
|
248
|
-
|
|
249
|
-
fallback_scope
|
|
250
|
-
end
|
|
251
|
-
|
|
252
234
|
def self.infer_scope(paths, fallback_scope:)
|
|
253
235
|
# Tooling/script/build changes: prefer a stable scope
|
|
254
236
|
if paths.any? && paths.all? { |p| tooling_path?(p) || doc_path?(p) || ci_path?(p) }
|
|
@@ -319,10 +301,14 @@ module KKGit
|
|
|
319
301
|
return 'chore' if config_path?(path)
|
|
320
302
|
return 'chore' if script_path?(path)
|
|
321
303
|
|
|
322
|
-
return
|
|
304
|
+
return default_code_type unless code_path?(path)
|
|
305
|
+
|
|
306
|
+
default_code_type
|
|
307
|
+
end
|
|
323
308
|
|
|
324
|
-
|
|
325
|
-
'
|
|
309
|
+
def self.default_code_type
|
|
310
|
+
type = ENV.fetch('KK_GIT_DEFAULT_TYPE', 'chore').to_s.strip
|
|
311
|
+
TYPE_PRIORITY.key?(type) ? type : 'chore'
|
|
326
312
|
end
|
|
327
313
|
|
|
328
314
|
def self.doc_path?(path)
|
data/lib/kk/git/git_ops.rb
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'open3'
|
|
4
3
|
require 'tempfile'
|
|
4
|
+
require_relative 'git_runner'
|
|
5
|
+
require_relative 'network_hints'
|
|
5
6
|
|
|
6
7
|
module KKGit
|
|
7
8
|
# Git 仓库操作:供 Rake task 与 CLI 复用。
|
|
8
9
|
module GitOps
|
|
9
10
|
class Error < StandardError; end
|
|
10
11
|
|
|
12
|
+
SENSITIVE_PATH_PATTERNS = [
|
|
13
|
+
%r{\A\.env(\.|$)}i,
|
|
14
|
+
/credentials/i,
|
|
15
|
+
%r{\.pem\z}i,
|
|
16
|
+
/id_rsa/i,
|
|
17
|
+
%r{\.key\z}i,
|
|
18
|
+
%r{\Asecrets?\.}i
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
11
21
|
# 仓库同步状态快照
|
|
12
22
|
Status = Struct.new(
|
|
13
23
|
:branch, :remote, :clean, :ahead, :behind,
|
|
14
24
|
:upstream_configured, :detached,
|
|
15
25
|
keyword_init: true
|
|
16
26
|
) do
|
|
17
|
-
# 是否需要与远端同步(有未推送 commit 或落后远端)
|
|
18
27
|
def needs_sync?
|
|
19
28
|
ahead.positive? || behind.positive?
|
|
20
29
|
end
|
|
@@ -29,7 +38,6 @@ module KKGit
|
|
|
29
38
|
end
|
|
30
39
|
|
|
31
40
|
class << self
|
|
32
|
-
# @return [Boolean] KK_GIT_DRY_RUN=1 时只打印命令不执行
|
|
33
41
|
def dry_run?
|
|
34
42
|
ENV['KK_GIT_DRY_RUN'] == '1'
|
|
35
43
|
end
|
|
@@ -50,127 +58,152 @@ module KKGit
|
|
|
50
58
|
ENV.fetch('KK_GIT_REMOTE', 'origin')
|
|
51
59
|
end
|
|
52
60
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
explicit.to_s.strip.empty? ? current_branch : explicit.to_s.strip
|
|
61
|
+
def branch(explicit: ENV['KK_GIT_BRANCH'], repo_dir: '.')
|
|
62
|
+
explicit.to_s.strip.empty? ? current_branch(repo_dir: repo_dir) : explicit.to_s.strip
|
|
56
63
|
end
|
|
57
64
|
|
|
58
|
-
# git add 路径,默认 `.`;可用 KK_GIT_ADD_PATHS 指定多个路径(空格分隔)
|
|
59
65
|
def add_paths
|
|
60
66
|
ENV.fetch('KK_GIT_ADD_PATHS', '.').split(/\s+/).reject(&:empty?)
|
|
61
67
|
end
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
def pull_target(remote)
|
|
70
|
+
url = ENV['KK_GIT_PULL_URL'].to_s.strip
|
|
71
|
+
url.empty? ? remote : url
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def push_target(remote)
|
|
75
|
+
url = ENV['KK_GIT_PUSH_URL'].to_s.strip
|
|
76
|
+
url.empty? ? remote : url
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def remote_fetch_url(repo_dir: '.')
|
|
80
|
+
out, _err, ok = run_cmd('git', 'remote', 'get-url', remote, chdir: repo_dir)
|
|
81
|
+
ok ? out.strip : nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def suggested_https_push_url(repo_dir: '.')
|
|
85
|
+
NetworkHints.ssh_remote_to_https(remote_fetch_url(repo_dir: repo_dir))
|
|
86
|
+
end
|
|
65
87
|
|
|
66
|
-
|
|
88
|
+
MUTATING_GIT_COMMANDS = %w[add commit push pull merge rebase checkout reset cherry-pick revert tag].freeze
|
|
89
|
+
|
|
90
|
+
def run_cmd(*cmd, chdir: '.')
|
|
67
91
|
if dry_run? && mutating_git_command?(cmd)
|
|
68
|
-
label = chdir ? "(cd #{chdir} && #{cmd.join(' ')})"
|
|
92
|
+
label = chdir == '.' ? cmd.join(' ') : "(cd #{chdir} && #{cmd.join(' ')})"
|
|
69
93
|
puts "[dry-run] #{label}"
|
|
70
94
|
return ['', '', true]
|
|
71
95
|
end
|
|
72
96
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
97
|
+
if cmd[0] == 'git'
|
|
98
|
+
stdout, stderr, ok = GitRunner.capture(cmd.drop(1), repo_dir: chdir)
|
|
99
|
+
else
|
|
100
|
+
require 'open3'
|
|
101
|
+
stdout, stderr, status = Open3.capture3(*cmd, chdir: chdir == '.' ? nil : chdir)
|
|
102
|
+
ok = status.success?
|
|
103
|
+
stdout = GitRunner.normalize_utf8(stdout)
|
|
104
|
+
stderr = GitRunner.normalize_utf8(stderr)
|
|
105
|
+
end
|
|
106
|
+
[stdout.to_s, stderr.to_s, ok]
|
|
80
107
|
end
|
|
81
108
|
|
|
82
109
|
def mutating_git_command?(cmd)
|
|
83
110
|
cmd[0] == 'git' && MUTATING_GIT_COMMANDS.include?(cmd[1])
|
|
84
111
|
end
|
|
85
112
|
|
|
86
|
-
def ensure_ok!(ok, title, stdout: nil, stderr: nil)
|
|
113
|
+
def ensure_ok!(ok, title, stdout: nil, stderr: nil, repo_dir: '.')
|
|
87
114
|
return if ok
|
|
88
115
|
|
|
89
116
|
msg = +"#{title} failed"
|
|
90
117
|
msg << "\n#{stderr}" unless stderr.to_s.strip.empty?
|
|
91
118
|
msg << "\n#{stdout}" unless stdout.to_s.strip.empty?
|
|
119
|
+
|
|
120
|
+
hints = NetworkHints.for_git_failure(
|
|
121
|
+
title: title,
|
|
122
|
+
stderr: stderr.to_s,
|
|
123
|
+
stdout: stdout.to_s,
|
|
124
|
+
suggested_https_url: suggested_https_push_url(repo_dir: repo_dir)
|
|
125
|
+
)
|
|
126
|
+
msg << "\n\n#{hints.join("\n\n")}" unless hints.empty?
|
|
127
|
+
|
|
92
128
|
raise Error, msg
|
|
93
129
|
end
|
|
94
130
|
|
|
95
|
-
def in_git_repo?
|
|
96
|
-
_, _, ok = run_cmd('git', 'rev-parse', '--git-dir')
|
|
131
|
+
def in_git_repo?(repo_dir: '.')
|
|
132
|
+
_, _, ok = run_cmd('git', 'rev-parse', '--git-dir', chdir: repo_dir)
|
|
97
133
|
ok
|
|
98
134
|
end
|
|
99
135
|
|
|
100
|
-
def ensure_in_repo!
|
|
101
|
-
raise Error, 'Not a git repository' unless in_git_repo?
|
|
136
|
+
def ensure_in_repo!(repo_dir: '.')
|
|
137
|
+
raise Error, 'Not a git repository' unless in_git_repo?(repo_dir: repo_dir)
|
|
102
138
|
end
|
|
103
139
|
|
|
104
|
-
def current_branch
|
|
105
|
-
out, err, ok = run_cmd('git', 'rev-parse', '--abbrev-ref', 'HEAD')
|
|
106
|
-
ensure_ok!(ok, 'Get current branch', stdout: out, stderr: err)
|
|
140
|
+
def current_branch(repo_dir: '.')
|
|
141
|
+
out, err, ok = run_cmd('git', 'rev-parse', '--abbrev-ref', 'HEAD', chdir: repo_dir)
|
|
142
|
+
ensure_ok!(ok, 'Get current branch', stdout: out, stderr: err, repo_dir: repo_dir)
|
|
107
143
|
out.strip
|
|
108
144
|
end
|
|
109
145
|
|
|
110
|
-
def detached_head?
|
|
111
|
-
current_branch == 'HEAD'
|
|
146
|
+
def detached_head?(repo_dir: '.')
|
|
147
|
+
current_branch(repo_dir: repo_dir) == 'HEAD'
|
|
112
148
|
end
|
|
113
149
|
|
|
114
|
-
def ensure_not_detached!
|
|
115
|
-
raise Error, 'Cannot push from detached HEAD' if detached_head?
|
|
150
|
+
def ensure_not_detached!(repo_dir: '.')
|
|
151
|
+
raise Error, 'Cannot push from detached HEAD' if detached_head?(repo_dir: repo_dir)
|
|
116
152
|
end
|
|
117
153
|
|
|
118
|
-
def working_tree_clean?
|
|
119
|
-
out, err, ok = run_cmd('git', 'status', '--porcelain')
|
|
120
|
-
ensure_ok!(ok, 'Check git status', stdout: out, stderr: err)
|
|
154
|
+
def working_tree_clean?(repo_dir: '.')
|
|
155
|
+
out, err, ok = run_cmd('git', 'status', '--porcelain', chdir: repo_dir)
|
|
156
|
+
ensure_ok!(ok, 'Check git status', stdout: out, stderr: err, repo_dir: repo_dir)
|
|
121
157
|
out.strip.empty?
|
|
122
158
|
end
|
|
123
159
|
|
|
124
|
-
def upstream_configured?
|
|
125
|
-
_, _, ok = run_cmd('git', 'rev-parse', '--abbrev-ref', '@{u}')
|
|
160
|
+
def upstream_configured?(repo_dir: '.')
|
|
161
|
+
_, _, ok = run_cmd('git', 'rev-parse', '--abbrev-ref', '@{u}', chdir: repo_dir)
|
|
126
162
|
ok
|
|
127
163
|
end
|
|
128
164
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
out, _err, ok = run_cmd('git', 'rev-list', '--count', '@{u}..HEAD')
|
|
165
|
+
def ahead_count(remote, branch, repo_dir: '.')
|
|
166
|
+
out, _err, ok = run_cmd('git', 'rev-list', '--count', '@{u}..HEAD', chdir: repo_dir)
|
|
132
167
|
return out.strip.to_i if ok
|
|
133
168
|
|
|
134
|
-
out, _err, ok = run_cmd('git', 'rev-list', '--count', "#{remote}/#{branch}..HEAD")
|
|
169
|
+
out, _err, ok = run_cmd('git', 'rev-list', '--count', "#{remote}/#{branch}..HEAD", chdir: repo_dir)
|
|
135
170
|
return out.strip.to_i if ok
|
|
136
171
|
|
|
137
172
|
0
|
|
138
173
|
end
|
|
139
174
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
out, _err, ok = run_cmd('git', 'rev-list', '--count', 'HEAD..@{u}')
|
|
175
|
+
def behind_count(remote, branch, repo_dir: '.')
|
|
176
|
+
out, _err, ok = run_cmd('git', 'rev-list', '--count', 'HEAD..@{u}', chdir: repo_dir)
|
|
143
177
|
return out.strip.to_i if ok
|
|
144
178
|
|
|
145
|
-
out, _err, ok = run_cmd('git', 'rev-list', '--count', "HEAD..#{remote}/#{branch}")
|
|
179
|
+
out, _err, ok = run_cmd('git', 'rev-list', '--count', "HEAD..#{remote}/#{branch}", chdir: repo_dir)
|
|
146
180
|
return out.strip.to_i if ok
|
|
147
181
|
|
|
148
182
|
0
|
|
149
183
|
end
|
|
150
184
|
|
|
151
|
-
def unpushed_commits?(remote, branch)
|
|
152
|
-
ahead_count(remote, branch).positive?
|
|
185
|
+
def unpushed_commits?(remote, branch, repo_dir: '.')
|
|
186
|
+
ahead_count(remote, branch, repo_dir: repo_dir).positive?
|
|
153
187
|
end
|
|
154
188
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
ensure_in_repo!
|
|
189
|
+
def status(remote: nil, branch: nil, repo_dir: '.')
|
|
190
|
+
ensure_in_repo!(repo_dir: repo_dir)
|
|
158
191
|
remote ||= self.remote
|
|
159
|
-
branch ||= self.branch
|
|
192
|
+
branch ||= self.branch(repo_dir: repo_dir)
|
|
160
193
|
|
|
161
194
|
Status.new(
|
|
162
195
|
branch: branch,
|
|
163
196
|
remote: remote,
|
|
164
|
-
clean: working_tree_clean
|
|
165
|
-
ahead: ahead_count(remote, branch),
|
|
166
|
-
behind: behind_count(remote, branch),
|
|
167
|
-
upstream_configured: upstream_configured
|
|
168
|
-
detached: detached_head?
|
|
197
|
+
clean: working_tree_clean?(repo_dir: repo_dir),
|
|
198
|
+
ahead: ahead_count(remote, branch, repo_dir: repo_dir),
|
|
199
|
+
behind: behind_count(remote, branch, repo_dir: repo_dir),
|
|
200
|
+
upstream_configured: upstream_configured?(repo_dir: repo_dir),
|
|
201
|
+
detached: detached_head?(repo_dir: repo_dir)
|
|
169
202
|
)
|
|
170
203
|
end
|
|
171
204
|
|
|
172
|
-
def status_hash(remote: nil, branch: nil)
|
|
173
|
-
s = status(remote: remote, branch: branch)
|
|
205
|
+
def status_hash(remote: nil, branch: nil, repo_dir: '.')
|
|
206
|
+
s = status(remote: remote, branch: branch, repo_dir: repo_dir)
|
|
174
207
|
{
|
|
175
208
|
branch: s.branch,
|
|
176
209
|
remote: s.remote,
|
|
@@ -183,70 +216,101 @@ module KKGit
|
|
|
183
216
|
}
|
|
184
217
|
end
|
|
185
218
|
|
|
186
|
-
def pull_remote!(remote, branch)
|
|
219
|
+
def pull_remote!(remote, branch, repo_dir: '.')
|
|
187
220
|
return if skip_pull?
|
|
188
221
|
|
|
189
222
|
pull_args = ENV.fetch('KK_GIT_PULL_ARGS', '--ff-only').split
|
|
190
|
-
|
|
191
|
-
|
|
223
|
+
target = pull_target(remote)
|
|
224
|
+
out, err, ok = run_cmd('git', 'pull', target, branch, *pull_args, chdir: repo_dir)
|
|
225
|
+
ensure_ok!(ok, 'git pull', stdout: out, stderr: err, repo_dir: repo_dir)
|
|
192
226
|
end
|
|
193
227
|
|
|
194
|
-
def push_remote!(remote, branch)
|
|
228
|
+
def push_remote!(remote, branch, repo_dir: '.')
|
|
195
229
|
return if skip_push?
|
|
196
230
|
|
|
197
|
-
ensure_not_detached! unless dry_run?
|
|
231
|
+
ensure_not_detached!(repo_dir: repo_dir) unless dry_run?
|
|
198
232
|
|
|
199
|
-
|
|
200
|
-
|
|
233
|
+
target = push_target(remote)
|
|
234
|
+
if upstream_configured?(repo_dir: repo_dir)
|
|
235
|
+
out, err, ok = run_cmd('git', 'push', target, branch, chdir: repo_dir)
|
|
201
236
|
else
|
|
202
|
-
out, err, ok = run_cmd('git', 'push', '-u',
|
|
237
|
+
out, err, ok = run_cmd('git', 'push', '-u', target, branch, chdir: repo_dir)
|
|
203
238
|
end
|
|
204
|
-
ensure_ok!(ok, 'git push', stdout: out, stderr: err)
|
|
239
|
+
ensure_ok!(ok, 'git push', stdout: out, stderr: err, repo_dir: repo_dir)
|
|
205
240
|
end
|
|
206
241
|
|
|
207
|
-
def sync_with_remote!(remote, branch)
|
|
208
|
-
pull_remote!(remote, branch)
|
|
209
|
-
push_remote!(remote, branch)
|
|
242
|
+
def sync_with_remote!(remote, branch, repo_dir: '.')
|
|
243
|
+
pull_remote!(remote, branch, repo_dir: repo_dir)
|
|
244
|
+
push_remote!(remote, branch, repo_dir: repo_dir)
|
|
210
245
|
puts "Synced: #{remote} #{branch}" unless dry_run?
|
|
211
246
|
end
|
|
212
247
|
|
|
213
|
-
def add_all!
|
|
248
|
+
def add_all!(repo_dir: '.')
|
|
214
249
|
paths = add_paths
|
|
215
|
-
out, err, ok = run_cmd('git', 'add', *paths)
|
|
216
|
-
ensure_ok!(ok, 'git add', stdout: out, stderr: err)
|
|
250
|
+
out, err, ok = run_cmd('git', 'add', *paths, chdir: repo_dir)
|
|
251
|
+
ensure_ok!(ok, 'git add', stdout: out, stderr: err, repo_dir: repo_dir)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def sensitive_path?(path)
|
|
255
|
+
SENSITIVE_PATH_PATTERNS.any? { |pattern| path.match?(pattern) }
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def sensitive_staged_paths(repo_dir: '.')
|
|
259
|
+
out, _err, ok = run_cmd('git', 'diff', '--cached', '--name-only', chdir: repo_dir)
|
|
260
|
+
return [] unless ok
|
|
261
|
+
|
|
262
|
+
out.split("\n").reject(&:empty?).select { |path| sensitive_path?(path) }
|
|
217
263
|
end
|
|
218
264
|
|
|
219
|
-
|
|
220
|
-
|
|
265
|
+
def ensure_no_sensitive_staged!(repo_dir: '.')
|
|
266
|
+
paths = sensitive_staged_paths(repo_dir: repo_dir)
|
|
267
|
+
return if paths.empty?
|
|
268
|
+
|
|
269
|
+
if ENV['KK_GIT_ALLOW_SENSITIVE'] == '1'
|
|
270
|
+
warn "Warning: committing sensitive paths: #{paths.join(', ')}"
|
|
271
|
+
else
|
|
272
|
+
raise Error,
|
|
273
|
+
"Refusing to commit sensitive paths: #{paths.join(', ')}. " \
|
|
274
|
+
'Set KK_GIT_ALLOW_SENSITIVE=1 to override.'
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def confirm_commit!(message)
|
|
279
|
+
return unless ENV['KK_GIT_CONFIRM'] == '1'
|
|
280
|
+
return if ENV['KK_GIT_YES'] == '1'
|
|
281
|
+
|
|
282
|
+
puts "Commit message:\n#{message}\n"
|
|
283
|
+
raise Error, 'Set KK_GIT_YES=1 to confirm this commit'
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def commit_with_message!(message, repo_dir: '.')
|
|
221
287
|
commit_args = amend? ? %w[commit --amend -F] : %w[commit -F]
|
|
222
288
|
|
|
223
289
|
Tempfile.create('commit_message') do |f|
|
|
224
290
|
f.write(message)
|
|
225
291
|
f.flush
|
|
226
|
-
out, err, ok = run_cmd('git', *commit_args, f.path)
|
|
292
|
+
out, err, ok = run_cmd('git', *commit_args, f.path, chdir: repo_dir)
|
|
227
293
|
if ok
|
|
228
294
|
true
|
|
229
295
|
elsif err.include?('nothing to commit') || out.include?('nothing to commit')
|
|
230
296
|
puts 'No staged changes to commit'
|
|
231
297
|
false
|
|
232
298
|
else
|
|
233
|
-
ensure_ok!(ok, 'git commit', stdout: out, stderr: err)
|
|
299
|
+
ensure_ok!(ok, 'git commit', stdout: out, stderr: err, repo_dir: repo_dir)
|
|
234
300
|
false
|
|
235
301
|
end
|
|
236
302
|
end
|
|
237
303
|
end
|
|
238
304
|
|
|
239
|
-
# 自动 add → commit → pull → push 主流程
|
|
240
|
-
#
|
|
241
305
|
# @return [Symbol] :synced | :committed_and_synced | :noop
|
|
242
|
-
def auto_commit_push!(commit_message_generator: nil)
|
|
243
|
-
ensure_in_repo!
|
|
306
|
+
def auto_commit_push!(commit_message_generator: nil, repo_dir: '.')
|
|
307
|
+
ensure_in_repo!(repo_dir: repo_dir)
|
|
244
308
|
remote_name = remote
|
|
245
|
-
branch_name = branch
|
|
309
|
+
branch_name = branch(repo_dir: repo_dir)
|
|
246
310
|
|
|
247
|
-
if working_tree_clean?
|
|
248
|
-
if status(remote: remote_name, branch: branch_name).needs_sync?
|
|
249
|
-
sync_with_remote!(remote_name, branch_name)
|
|
311
|
+
if working_tree_clean?(repo_dir: repo_dir)
|
|
312
|
+
if status(remote: remote_name, branch: branch_name, repo_dir: repo_dir).needs_sync?
|
|
313
|
+
sync_with_remote!(remote_name, branch_name, repo_dir: repo_dir)
|
|
250
314
|
return :synced
|
|
251
315
|
end
|
|
252
316
|
|
|
@@ -254,20 +318,23 @@ module KKGit
|
|
|
254
318
|
return :noop
|
|
255
319
|
end
|
|
256
320
|
|
|
257
|
-
add_all!
|
|
321
|
+
add_all!(repo_dir: repo_dir)
|
|
322
|
+
ensure_no_sensitive_staged!(repo_dir: repo_dir)
|
|
258
323
|
|
|
259
324
|
message =
|
|
260
325
|
if commit_message_generator
|
|
261
326
|
commit_message_generator.call
|
|
262
327
|
else
|
|
263
|
-
KKGit::CommitMessage.generate(mode: :all)
|
|
328
|
+
KKGit::CommitMessage.generate(repo_dir: repo_dir, mode: :all)
|
|
264
329
|
end
|
|
265
330
|
message = message.to_s.strip
|
|
266
|
-
|
|
331
|
+
raise Error, 'Could not generate commit message from staged changes' if message.empty?
|
|
332
|
+
|
|
333
|
+
confirm_commit!(message)
|
|
267
334
|
|
|
268
|
-
committed = commit_with_message!(message)
|
|
269
|
-
if committed || unpushed_commits?(remote_name, branch_name)
|
|
270
|
-
sync_with_remote!(remote_name, branch_name)
|
|
335
|
+
committed = commit_with_message!(message, repo_dir: repo_dir)
|
|
336
|
+
if committed || unpushed_commits?(remote_name, branch_name, repo_dir: repo_dir)
|
|
337
|
+
sync_with_remote!(remote_name, branch_name, repo_dir: repo_dir)
|
|
271
338
|
return committed ? :committed_and_synced : :synced
|
|
272
339
|
end
|
|
273
340
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
5
|
+
module KKGit
|
|
6
|
+
# Shared git subprocess helper for CommitMessage and GitOps.
|
|
7
|
+
module GitRunner
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def capture(args, repo_dir: '.')
|
|
11
|
+
stdout, stderr, status = Open3.capture3('git', *args, chdir: repo_dir)
|
|
12
|
+
[normalize_utf8(stdout), normalize_utf8(stderr), status.success?]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def capture!(args, repo_dir: '.')
|
|
16
|
+
stdout, stderr, ok = capture(args, repo_dir: repo_dir)
|
|
17
|
+
raise "git #{args.join(' ')} failed: #{stderr.strip}" unless ok
|
|
18
|
+
|
|
19
|
+
stdout
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def normalize_utf8(str)
|
|
23
|
+
str.to_s.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "\uFFFD")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KKGit
|
|
4
|
+
# Append actionable hints when git network operations fail.
|
|
5
|
+
module NetworkHints
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def for_git_failure(title:, stderr:, stdout: nil, suggested_https_url: nil)
|
|
9
|
+
combined = [stderr, stdout].compact.join("\n")
|
|
10
|
+
return [] if combined.strip.empty?
|
|
11
|
+
|
|
12
|
+
hints = []
|
|
13
|
+
hints << ssh_timeout_hint(suggested_https_url) if ssh_timeout?(combined)
|
|
14
|
+
hints << auth_hint if auth_failure?(combined)
|
|
15
|
+
hints << unreachable_hint if host_unreachable?(combined) && hints.empty?
|
|
16
|
+
|
|
17
|
+
hints
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def ssh_timeout?(text)
|
|
21
|
+
text.match?(/ssh:.*connect to host .+ port 22.*timed out/i) ||
|
|
22
|
+
(text.match?(/Operation timed out/i) && text.match?(/ssh:/i))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def auth_failure?(text)
|
|
26
|
+
text.match?(/Authentication failed/i) ||
|
|
27
|
+
text.match?(/Permission denied \(publickey\)/i) ||
|
|
28
|
+
text.match?(/could not read Username/i)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def host_unreachable?(text)
|
|
32
|
+
text.match?(/Could not read from remote repository/i) ||
|
|
33
|
+
text.match?(/unable to access/i) ||
|
|
34
|
+
text.match?(/Connection refused/i)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def ssh_timeout_hint(suggested_https_url)
|
|
38
|
+
lines = [
|
|
39
|
+
'Network hint: SSH to the git host on port 22 timed out (often blocked by firewalls or ISP).',
|
|
40
|
+
'Fix options:',
|
|
41
|
+
' 1) Switch remote to HTTPS:',
|
|
42
|
+
' git remote set-url origin https://github.com/USER/REPO.git',
|
|
43
|
+
' 2) Use SSH over port 443 (~/.ssh/config):',
|
|
44
|
+
' Host github.com',
|
|
45
|
+
' Hostname ssh.github.com',
|
|
46
|
+
' Port 443',
|
|
47
|
+
' 3) One-off push/pull without changing remote:',
|
|
48
|
+
' KK_GIT_PUSH_URL=https://github.com/USER/REPO.git kk-git push'
|
|
49
|
+
]
|
|
50
|
+
lines << " Suggested KK_GIT_PUSH_URL=#{suggested_https_url}" if suggested_https_url
|
|
51
|
+
lines.join("\n")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def auth_hint
|
|
55
|
+
<<~HINT.strip
|
|
56
|
+
Auth hint: remote rejected credentials.
|
|
57
|
+
- HTTPS: use a PAT and credential helper
|
|
58
|
+
- SSH: ensure ssh-agent has the right key (ssh -T git@github.com)
|
|
59
|
+
HINT
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def unreachable_hint
|
|
63
|
+
'Network hint: could not reach the remote. Check VPN, proxy, DNS, and `git remote -v`.'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# git@github.com:owner/repo.git -> https://github.com/owner/repo.git
|
|
67
|
+
def ssh_remote_to_https(url)
|
|
68
|
+
return nil if url.nil? || url.strip.empty?
|
|
69
|
+
|
|
70
|
+
if (m = url.strip.match(/\Agit@([^:]+):(.+?)(?:\.git)?\z/))
|
|
71
|
+
host = m[1]
|
|
72
|
+
path = m[2]
|
|
73
|
+
return "https://#{host}/#{path}.git"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'ruby_warnings'
|
|
4
|
+
|
|
5
|
+
module KKGit
|
|
6
|
+
# Run root Rakefile in-process (avoids subprocess `rake` and duplicate gem loads).
|
|
7
|
+
module RakeRunner
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def run!(root:, argv: [])
|
|
11
|
+
root = File.expand_path(root)
|
|
12
|
+
Dir.chdir(root)
|
|
13
|
+
|
|
14
|
+
lib = File.join(root, 'lib')
|
|
15
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
16
|
+
|
|
17
|
+
require 'rake'
|
|
18
|
+
load File.join(root, 'Rakefile')
|
|
19
|
+
|
|
20
|
+
tasks = argv.empty? ? ['push'] : argv.dup
|
|
21
|
+
failed = Rake.application.run(tasks)
|
|
22
|
+
exit(failed ? 1 : 0)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/kk/git/rake_tasks.rb
CHANGED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KKGit
|
|
4
|
+
# Semver bump and git tag helpers (maintainer release flow).
|
|
5
|
+
module Release
|
|
6
|
+
VERSION_PATH = File.join(__dir__, 'version.rb').freeze
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def latest_semver_tag(prefix: 'v', repo_dir: '.')
|
|
11
|
+
stdout, _stderr, ok = GitOps.run_cmd(
|
|
12
|
+
'git', 'tag', '--list', "#{prefix}[0-9]*.[0-9]*.[0-9]*", '--sort=-v:refname',
|
|
13
|
+
chdir: repo_dir
|
|
14
|
+
)
|
|
15
|
+
return nil unless ok
|
|
16
|
+
|
|
17
|
+
stdout.to_s.split("\n").first&.strip
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def parse_semver(str)
|
|
21
|
+
m = str.to_s.match(/\A(\d+)\.(\d+)\.(\d+)\z/)
|
|
22
|
+
return nil unless m
|
|
23
|
+
|
|
24
|
+
[m[1].to_i, m[2].to_i, m[3].to_i]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def bump_semver(version, level)
|
|
28
|
+
major, minor, patch = version
|
|
29
|
+
case level
|
|
30
|
+
when 'major' then [major + 1, 0, 0]
|
|
31
|
+
when 'minor' then [major, minor + 1, 0]
|
|
32
|
+
else [major, minor, patch + 1]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def semver_to_s(version)
|
|
37
|
+
"#{version[0]}.#{version[1]}.#{version[2]}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def semver_gt?(a, b)
|
|
41
|
+
return false if a.nil? || b.nil?
|
|
42
|
+
|
|
43
|
+
a.each_with_index do |part, i|
|
|
44
|
+
return true if part > b[i]
|
|
45
|
+
return false if part < b[i]
|
|
46
|
+
end
|
|
47
|
+
false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def semver_max(*versions)
|
|
51
|
+
versions.compact.max_by { |v| v }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def tag_exists?(tag, repo_dir: '.')
|
|
55
|
+
out, _err, ok = GitOps.run_cmd('git', 'tag', '--list', tag, chdir: repo_dir)
|
|
56
|
+
ok && !out.to_s.strip.empty?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def current_version_in_file
|
|
60
|
+
content = File.read(VERSION_PATH, mode: 'r:BOM|UTF-8')
|
|
61
|
+
m = content.match(/VERSION\s*=\s*'([^']*)'/)
|
|
62
|
+
m ? parse_semver(m[1]) : nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [Boolean] true when file was updated
|
|
66
|
+
def update_version!(new_version)
|
|
67
|
+
content = File.read(VERSION_PATH, mode: 'r:BOM|UTF-8')
|
|
68
|
+
return false if content.match?(/VERSION\s*=\s*'#{Regexp.escape(new_version)}'/)
|
|
69
|
+
|
|
70
|
+
replaced = content.sub(/VERSION\s*=\s*'[^']*'/, "VERSION = '#{new_version}'")
|
|
71
|
+
raise "Failed to update version in #{VERSION_PATH}" if replaced == content
|
|
72
|
+
|
|
73
|
+
File.write(VERSION_PATH, replaced)
|
|
74
|
+
true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @return [Array(String, String)] tag, version
|
|
78
|
+
def next_tag_and_version(prefix: nil, bump: nil, repo_dir: '.')
|
|
79
|
+
prefix ||= ENV.fetch('KK_GIT_TAG_PREFIX', 'v')
|
|
80
|
+
bump ||= ENV.fetch('KK_GIT_BUMP', 'patch')
|
|
81
|
+
|
|
82
|
+
latest_tag = latest_semver_tag(prefix: prefix, repo_dir: repo_dir)
|
|
83
|
+
tag_version = latest_tag ? parse_semver(latest_tag.delete_prefix(prefix)) : nil
|
|
84
|
+
file_version = current_version_in_file || parse_semver(KKGit::VERSION)
|
|
85
|
+
|
|
86
|
+
# version.rb already ahead of tags and tag not yet created → release that version
|
|
87
|
+
if file_version && tag_version && semver_gt?(file_version, tag_version)
|
|
88
|
+
candidate = "#{prefix}#{semver_to_s(file_version)}"
|
|
89
|
+
return [candidate, semver_to_s(file_version)] unless tag_exists?(candidate, repo_dir: repo_dir)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
base = semver_max(tag_version, file_version) || [0, 1, 0]
|
|
93
|
+
version = base
|
|
94
|
+
50.times do
|
|
95
|
+
version = bump_semver(version, bump)
|
|
96
|
+
candidate = "#{prefix}#{semver_to_s(version)}"
|
|
97
|
+
return [candidate, semver_to_s(version)] unless tag_exists?(candidate, repo_dir: repo_dir)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
raise 'Failed to generate next release tag: too many attempts'
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def create_and_push_tag!(tag, remote: nil, repo_dir: '.')
|
|
104
|
+
remote ||= GitOps.remote
|
|
105
|
+
out, err, ok = GitOps.run_cmd('git', 'tag', '-a', tag, '-m', "Release #{tag}", chdir: repo_dir)
|
|
106
|
+
GitOps.ensure_ok!(ok, 'git tag', stdout: out, stderr: err, repo_dir: repo_dir)
|
|
107
|
+
|
|
108
|
+
push_target = GitOps.push_target(remote)
|
|
109
|
+
out, err, ok = GitOps.run_cmd('git', 'push', push_target, tag, chdir: repo_dir)
|
|
110
|
+
GitOps.ensure_ok!(ok, 'git push tag', stdout: out, stderr: err, repo_dir: repo_dir)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ruby 4.0+ removed Warning.ignore; Homebrew may load two rdoc versions (harmless).
|
|
4
|
+
module KKGit
|
|
5
|
+
module RubyWarnings
|
|
6
|
+
RDOC_DUP = /already initialized constant RDoc::|previous definition of .* was here/
|
|
7
|
+
|
|
8
|
+
module WarnFilter
|
|
9
|
+
def warn(*messages, category: nil, **kwargs)
|
|
10
|
+
filtered = messages.reject { |m| KKGit::RubyWarnings.ignore?(m) }
|
|
11
|
+
return if filtered.empty?
|
|
12
|
+
|
|
13
|
+
super(*filtered, category: category, **kwargs)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
def install!
|
|
20
|
+
return if @installed
|
|
21
|
+
|
|
22
|
+
@installed = true
|
|
23
|
+
Kernel.singleton_class.prepend(WarnFilter)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ignore?(message)
|
|
27
|
+
text = message.is_a?(Exception) ? message.message : message.to_s
|
|
28
|
+
text.match?(RDOC_DUP)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
KKGit::RubyWarnings.install!
|
data/lib/kk/git/version.rb
CHANGED
data/lib/kk/git.rb
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'git/version'
|
|
4
|
+
require_relative 'git/ruby_warnings'
|
|
5
|
+
require_relative 'git/git_runner'
|
|
4
6
|
require_relative 'git/commit_message'
|
|
5
7
|
require_relative 'git/git_ops'
|
|
8
|
+
require_relative 'git/release'
|
|
6
9
|
|
|
7
10
|
module KKGit
|
|
8
11
|
end
|
metadata
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kk-git
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kk
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Generate Conventional Commits commit messages from current git changes
|
|
14
14
|
(staged/worktree), designed for Rake/script usage.
|
|
15
15
|
email:
|
|
16
|
-
-
|
|
16
|
+
- kk@users.noreply.github.com
|
|
17
17
|
executables:
|
|
18
18
|
- kk-git
|
|
19
19
|
extensions: []
|
|
@@ -23,13 +23,20 @@ files:
|
|
|
23
23
|
- lib/kk/git.rb
|
|
24
24
|
- lib/kk/git/commit_message.rb
|
|
25
25
|
- lib/kk/git/git_ops.rb
|
|
26
|
+
- lib/kk/git/git_runner.rb
|
|
27
|
+
- lib/kk/git/network_hints.rb
|
|
28
|
+
- lib/kk/git/rake_runner.rb
|
|
26
29
|
- lib/kk/git/rake_tasks.rb
|
|
30
|
+
- lib/kk/git/release.rb
|
|
31
|
+
- lib/kk/git/ruby_warnings.rb
|
|
27
32
|
- lib/kk/git/version.rb
|
|
28
|
-
homepage:
|
|
33
|
+
homepage: https://github.com/kevin197011/kk-git
|
|
29
34
|
licenses:
|
|
30
35
|
- MIT
|
|
31
36
|
metadata:
|
|
32
37
|
rubygems_mfa_required: 'true'
|
|
38
|
+
source_code_uri: https://github.com/kevin197011/kk-git
|
|
39
|
+
changelog_uri: https://github.com/kevin197011/kk-git/blob/main/CHANGELOG.md
|
|
33
40
|
post_install_message:
|
|
34
41
|
rdoc_options: []
|
|
35
42
|
require_paths:
|