kk-git 0.2.3 → 0.2.5
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 +2 -0
- data/lib/kk/git/auto_format.rb +193 -0
- data/lib/kk/git/git_ops.rb +14 -6
- data/lib/kk/git/version.rb +1 -1
- data/lib/kk/git.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4b432c2d8b24c8a15fe3185d0f8d9c26bc07816a788a051933b7eaeb7b2a5409
|
|
4
|
+
data.tar.gz: f7666db73c822b09c1918e656437fc165f88ef13a6f23e1fd32fd0d3eb53c0f4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a08f91f62c4252dee11fc5be7a414f75c7f1c96de5151b83c25a7c78bce8d33419623c35f9768c02f5e16c81485d8622e645f1dbf8af4298cc8bd80d2b93ccd7
|
|
7
|
+
data.tar.gz: 2167ef28d0b789fda42ac855834340e968ccaf973d0858281b7dbb8d8059128624bbb2b997100ee8d4ac6720c9356dc7d76b8989feb7011c36380d8046619944
|
data/exe/kk-git
CHANGED
|
@@ -169,6 +169,8 @@ def main_help
|
|
|
169
169
|
KK_GIT_DEFAULT_TYPE default type for code edits (default: chore)
|
|
170
170
|
KK_GIT_PUSH_URL push to explicit URL (bypass SSH remote)
|
|
171
171
|
KK_GIT_PULL_URL pull from explicit URL (bypass SSH remote)
|
|
172
|
+
KK_GIT_SKIP_FORMAT=1 skip pre-commit auto-format
|
|
173
|
+
KK_GIT_FORMAT_ALL=1 format all tracked files (default: changed only)
|
|
172
174
|
|
|
173
175
|
Run `kk-git <command> --help` for command-specific options.
|
|
174
176
|
HELP
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
5
|
+
module KKGit
|
|
6
|
+
# Best-effort format changed files before commit; skip missing tools.
|
|
7
|
+
module AutoFormat
|
|
8
|
+
Language = Struct.new(:id, :label, :extensions, :tools, keyword_init: true)
|
|
9
|
+
|
|
10
|
+
Tool = Struct.new(:name, :bin, :argv, keyword_init: true) do
|
|
11
|
+
def build(files, repo_dir:)
|
|
12
|
+
bin_path = AutoFormat.resolve_bin(bin, repo_dir: repo_dir)
|
|
13
|
+
return nil unless bin_path
|
|
14
|
+
|
|
15
|
+
args = argv.flat_map { |part| part == '%{files}' ? files : [part] }
|
|
16
|
+
[bin_path, *args]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
LANGUAGES = [
|
|
21
|
+
Language.new(
|
|
22
|
+
id: :ruby,
|
|
23
|
+
label: 'Ruby',
|
|
24
|
+
extensions: %w[.rb],
|
|
25
|
+
tools: [
|
|
26
|
+
Tool.new(name: 'rubocop', bin: 'rubocop', argv: ['-A', '--force-exclusion', '%{files}'])
|
|
27
|
+
]
|
|
28
|
+
),
|
|
29
|
+
Language.new(
|
|
30
|
+
id: :python,
|
|
31
|
+
label: 'Python',
|
|
32
|
+
extensions: %w[.py],
|
|
33
|
+
tools: [
|
|
34
|
+
Tool.new(name: 'ruff', bin: 'ruff', argv: ['format', '%{files}']),
|
|
35
|
+
Tool.new(name: 'black', bin: 'black', argv: ['%{files}'])
|
|
36
|
+
]
|
|
37
|
+
),
|
|
38
|
+
Language.new(
|
|
39
|
+
id: :go,
|
|
40
|
+
label: 'Go',
|
|
41
|
+
extensions: %w[.go],
|
|
42
|
+
tools: [
|
|
43
|
+
Tool.new(name: 'gofmt', bin: 'gofmt', argv: ['-w', '%{files}'])
|
|
44
|
+
]
|
|
45
|
+
),
|
|
46
|
+
Language.new(
|
|
47
|
+
id: :bash,
|
|
48
|
+
label: 'Bash',
|
|
49
|
+
extensions: %w[.sh .bash],
|
|
50
|
+
tools: [
|
|
51
|
+
Tool.new(name: 'shfmt', bin: 'shfmt', argv: ['-w', '%{files}'])
|
|
52
|
+
]
|
|
53
|
+
),
|
|
54
|
+
Language.new(
|
|
55
|
+
id: :prettier,
|
|
56
|
+
label: 'JS/TS/Node',
|
|
57
|
+
extensions: %w[.js .jsx .mjs .cjs .ts .tsx .json .css .scss .less .html .md .yaml .yml],
|
|
58
|
+
tools: [
|
|
59
|
+
Tool.new(name: 'prettier', bin: 'prettier', argv: ['--write', '%{files}'])
|
|
60
|
+
]
|
|
61
|
+
)
|
|
62
|
+
].freeze
|
|
63
|
+
|
|
64
|
+
module_function
|
|
65
|
+
|
|
66
|
+
def enabled?
|
|
67
|
+
ENV['KK_GIT_SKIP_FORMAT'] != '1'
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def format_all?
|
|
71
|
+
ENV['KK_GIT_FORMAT_ALL'] == '1'
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def run!(repo_dir: '.', dry_run: GitOps.dry_run?)
|
|
75
|
+
return { ran: [], skipped: [], failed: [] } unless enabled?
|
|
76
|
+
|
|
77
|
+
files = target_files(repo_dir: repo_dir)
|
|
78
|
+
grouped = group_files(files)
|
|
79
|
+
return { ran: [], skipped: [], failed: [] } if grouped.empty?
|
|
80
|
+
|
|
81
|
+
ran = []
|
|
82
|
+
skipped = []
|
|
83
|
+
failed = []
|
|
84
|
+
|
|
85
|
+
grouped.each do |lang_id, paths|
|
|
86
|
+
language = LANGUAGES.find { |l| l.id == lang_id }
|
|
87
|
+
next unless language
|
|
88
|
+
|
|
89
|
+
tool = pick_tool(language, paths, repo_dir: repo_dir)
|
|
90
|
+
unless tool
|
|
91
|
+
skipped << "#{language.label}: no formatter (#{language.tools.map(&:name).join(' / ')})"
|
|
92
|
+
next
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
cmd = tool.build(paths, repo_dir: repo_dir)
|
|
96
|
+
label = "#{language.label} (#{tool.name})"
|
|
97
|
+
if dry_run
|
|
98
|
+
puts "[dry-run] format #{label}: #{cmd.join(' ')}"
|
|
99
|
+
ran << label
|
|
100
|
+
next
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
unless executable?(cmd&.first)
|
|
104
|
+
skipped << "#{label}: not found (#{tool.name})"
|
|
105
|
+
next
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
out, err, ok = run_command(cmd, repo_dir: repo_dir)
|
|
109
|
+
if ok
|
|
110
|
+
puts "Formatted #{label}: #{paths.length} file(s)"
|
|
111
|
+
ran << label
|
|
112
|
+
else
|
|
113
|
+
msg = err.to_s.strip.empty? ? out.to_s.strip : err.to_s.strip
|
|
114
|
+
warn "Format failed #{label} (continuing): #{msg.lines.first}"
|
|
115
|
+
failed << label
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
skipped.each { |line| puts "Format skip: #{line}" }
|
|
120
|
+
|
|
121
|
+
{ ran: ran, skipped: skipped, failed: failed }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def target_files(repo_dir:)
|
|
125
|
+
format_all? ? all_tracked_files(repo_dir: repo_dir) : changed_files(repo_dir: repo_dir)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def changed_files(repo_dir:)
|
|
129
|
+
out, _err, ok = GitOps.run_cmd('git', 'status', '--porcelain', '-z', chdir: repo_dir)
|
|
130
|
+
return [] unless ok
|
|
131
|
+
|
|
132
|
+
out.split("\0").filter_map do |entry|
|
|
133
|
+
next if entry.nil? || entry.empty?
|
|
134
|
+
|
|
135
|
+
path = entry[3..]
|
|
136
|
+
next if path.nil? || path.empty? || path.end_with?('/')
|
|
137
|
+
|
|
138
|
+
path
|
|
139
|
+
end.uniq
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def all_tracked_files(repo_dir:)
|
|
143
|
+
out, _err, ok = GitOps.run_cmd('git', 'ls-files', '-z', chdir: repo_dir)
|
|
144
|
+
return [] unless ok
|
|
145
|
+
|
|
146
|
+
out.split("\0").reject(&:empty?)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def group_files(files)
|
|
150
|
+
grouped = Hash.new { |h, k| h[k] = [] }
|
|
151
|
+
files.each do |path|
|
|
152
|
+
ext = File.extname(path)
|
|
153
|
+
language = LANGUAGES.find { |lang| lang.extensions.include?(ext) }
|
|
154
|
+
grouped[language.id] << path if language
|
|
155
|
+
end
|
|
156
|
+
grouped.transform_values { |paths| paths.uniq.sort }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def pick_tool(language, _files, repo_dir:)
|
|
160
|
+
language.tools.find do |tool|
|
|
161
|
+
resolve_bin(tool.bin, repo_dir: repo_dir)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def resolve_bin(name, repo_dir:)
|
|
166
|
+
path = which(name)
|
|
167
|
+
return path if executable?(path)
|
|
168
|
+
|
|
169
|
+
local = File.expand_path(File.join(repo_dir, 'node_modules', '.bin', name))
|
|
170
|
+
return local if executable?(local)
|
|
171
|
+
|
|
172
|
+
nil
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def which(cmd)
|
|
176
|
+
out, _, ok = Open3.capture3('command', '-v', cmd)
|
|
177
|
+
return nil unless ok
|
|
178
|
+
|
|
179
|
+
path = out.strip
|
|
180
|
+
path.empty? ? nil : path
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def executable?(path)
|
|
184
|
+
path && File.file?(path) && File.executable?(path)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def run_command(cmd, repo_dir:)
|
|
188
|
+
Open3.capture3(*cmd, chdir: repo_dir)
|
|
189
|
+
rescue Errno::ENOENT, Errno::EACCES => e
|
|
190
|
+
['', e.message, false]
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
data/lib/kk/git/git_ops.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'open3'
|
|
3
4
|
require 'tempfile'
|
|
4
5
|
require_relative 'git_runner'
|
|
5
6
|
require_relative 'network_hints'
|
|
7
|
+
require_relative 'auto_format'
|
|
6
8
|
|
|
7
9
|
module KKGit
|
|
8
10
|
# Git 仓库操作:供 Rake task 与 CLI 复用。
|
|
@@ -10,12 +12,12 @@ module KKGit
|
|
|
10
12
|
class Error < StandardError; end
|
|
11
13
|
|
|
12
14
|
SENSITIVE_PATH_PATTERNS = [
|
|
13
|
-
|
|
15
|
+
/\A\.env(\.|$)/i,
|
|
14
16
|
/credentials/i,
|
|
15
|
-
|
|
17
|
+
/\.pem\z/i,
|
|
16
18
|
/id_rsa/i,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
/\.key\z/i,
|
|
20
|
+
/\Asecrets?\./i
|
|
19
21
|
].freeze
|
|
20
22
|
|
|
21
23
|
# 仓库同步状态快照
|
|
@@ -58,10 +60,14 @@ module KKGit
|
|
|
58
60
|
ENV.fetch('KK_GIT_REMOTE', 'origin')
|
|
59
61
|
end
|
|
60
62
|
|
|
61
|
-
def branch(explicit: ENV
|
|
63
|
+
def branch(explicit: ENV.fetch('KK_GIT_BRANCH', nil), repo_dir: '.')
|
|
62
64
|
explicit.to_s.strip.empty? ? current_branch(repo_dir: repo_dir) : explicit.to_s.strip
|
|
63
65
|
end
|
|
64
66
|
|
|
67
|
+
def skip_format?
|
|
68
|
+
ENV['KK_GIT_SKIP_FORMAT'] == '1'
|
|
69
|
+
end
|
|
70
|
+
|
|
65
71
|
def add_paths
|
|
66
72
|
ENV.fetch('KK_GIT_ADD_PATHS', '.').split(/\s+/).reject(&:empty?)
|
|
67
73
|
end
|
|
@@ -113,7 +119,7 @@ module KKGit
|
|
|
113
119
|
def ensure_ok!(ok, title, stdout: nil, stderr: nil, repo_dir: '.')
|
|
114
120
|
return if ok
|
|
115
121
|
|
|
116
|
-
msg =
|
|
122
|
+
msg = "#{title} failed"
|
|
117
123
|
msg << "\n#{stderr}" unless stderr.to_s.strip.empty?
|
|
118
124
|
msg << "\n#{stdout}" unless stdout.to_s.strip.empty?
|
|
119
125
|
|
|
@@ -318,6 +324,8 @@ module KKGit
|
|
|
318
324
|
return :noop
|
|
319
325
|
end
|
|
320
326
|
|
|
327
|
+
AutoFormat.run!(repo_dir: repo_dir) unless skip_format?
|
|
328
|
+
|
|
321
329
|
add_all!(repo_dir: repo_dir)
|
|
322
330
|
ensure_no_sensitive_staged!(repo_dir: repo_dir)
|
|
323
331
|
|
data/lib/kk/git/version.rb
CHANGED
data/lib/kk/git.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kk
|
|
@@ -21,6 +21,7 @@ extra_rdoc_files: []
|
|
|
21
21
|
files:
|
|
22
22
|
- exe/kk-git
|
|
23
23
|
- lib/kk/git.rb
|
|
24
|
+
- lib/kk/git/auto_format.rb
|
|
24
25
|
- lib/kk/git/commit_message.rb
|
|
25
26
|
- lib/kk/git/git_ops.rb
|
|
26
27
|
- lib/kk/git/git_runner.rb
|