kk-git 0.2.4 → 0.2.6

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: b8dfe02f8577125ba5e85afa70e08b8d7dff806a5d87378aabc4ce4a57c32857
4
- data.tar.gz: dba6e8237452a835805971aa4d9c49d1b84d1af8da53adb53c1c1c32dd6be7f4
3
+ metadata.gz: ae39abac1e3638de958917ebc2fcbbffdace00c23e13fdd53929cec3e416930f
4
+ data.tar.gz: eb7191f382ae86ff2c25aac1ce6f7af7a1288be5a25c503641416bcb321ba234
5
5
  SHA512:
6
- metadata.gz: 94af0ecb6c2baf572aa9d757d4be1ed7f9f2a992c86b44914db30ce6e96808c21ce40af86efe6ddd11f07e62871853bf3505e3c7a2995330c2f7d2d017555e55
7
- data.tar.gz: f9dec745963b9b20ff32232b087c58ed5f9259443b52f707ec60cdebe4f5638df182fab95f8b65676774671019d478d04eb0b8ffe14ea392481c164776755363
6
+ metadata.gz: df09df41cb4383f3654dc161b33703798e069cd16be48dd160162076eca1d03417c7272bb6dc80cdfb7a06c2a46e8a8029597e94c40e6fe5d79e5be9a2f1c6a4
7
+ data.tar.gz: a52d189d3bf6606506e9fb06f3ff8289926d9f62864e9a2e3628a0657813a009e32840cfda0b2d4e414f3a8b768e940211566f247c06a998db4c9732d29471a9
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
@@ -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,14 +12,16 @@ module KKGit
10
12
  class Error < StandardError; end
11
13
 
12
14
  SENSITIVE_PATH_PATTERNS = [
13
- %r{\A\.env(\.|$)}i,
15
+ /\A\.env(\.|$)/i,
14
16
  /credentials/i,
15
- %r{\.pem\z}i,
17
+ /\.pem\z/i,
16
18
  /id_rsa/i,
17
- %r{\.key\z}i,
18
- %r{\Asecrets?\.}i
19
+ /\.key\z/i,
20
+ /\Asecrets?\./i
19
21
  ].freeze
20
22
 
23
+ SAFE_ENV_TEMPLATE_SUFFIXES = %w[example sample template dist].freeze
24
+
21
25
  # 仓库同步状态快照
22
26
  Status = Struct.new(
23
27
  :branch, :remote, :clean, :ahead, :behind,
@@ -58,10 +62,14 @@ module KKGit
58
62
  ENV.fetch('KK_GIT_REMOTE', 'origin')
59
63
  end
60
64
 
61
- def branch(explicit: ENV['KK_GIT_BRANCH'], repo_dir: '.')
65
+ def branch(explicit: ENV.fetch('KK_GIT_BRANCH', nil), repo_dir: '.')
62
66
  explicit.to_s.strip.empty? ? current_branch(repo_dir: repo_dir) : explicit.to_s.strip
63
67
  end
64
68
 
69
+ def skip_format?
70
+ ENV['KK_GIT_SKIP_FORMAT'] == '1'
71
+ end
72
+
65
73
  def add_paths
66
74
  ENV.fetch('KK_GIT_ADD_PATHS', '.').split(/\s+/).reject(&:empty?)
67
75
  end
@@ -113,7 +121,7 @@ module KKGit
113
121
  def ensure_ok!(ok, title, stdout: nil, stderr: nil, repo_dir: '.')
114
122
  return if ok
115
123
 
116
- msg = +"#{title} failed"
124
+ msg = "#{title} failed"
117
125
  msg << "\n#{stderr}" unless stderr.to_s.strip.empty?
118
126
  msg << "\n#{stdout}" unless stdout.to_s.strip.empty?
119
127
 
@@ -252,9 +260,17 @@ module KKGit
252
260
  end
253
261
 
254
262
  def sensitive_path?(path)
263
+ return false if safe_env_template?(path)
264
+
255
265
  SENSITIVE_PATH_PATTERNS.any? { |pattern| path.match?(pattern) }
256
266
  end
257
267
 
268
+ def safe_env_template?(path)
269
+ SAFE_ENV_TEMPLATE_SUFFIXES.any? do |suffix|
270
+ path.match?(/\A\.env\.#{suffix}\z/i)
271
+ end
272
+ end
273
+
258
274
  def sensitive_staged_paths(repo_dir: '.')
259
275
  out, _err, ok = run_cmd('git', 'diff', '--cached', '--name-only', chdir: repo_dir)
260
276
  return [] unless ok
@@ -318,6 +334,8 @@ module KKGit
318
334
  return :noop
319
335
  end
320
336
 
337
+ AutoFormat.run!(repo_dir: repo_dir) unless skip_format?
338
+
321
339
  add_all!(repo_dir: repo_dir)
322
340
  ensure_no_sensitive_staged!(repo_dir: repo_dir)
323
341
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KKGit
4
- VERSION = '0.2.4'
4
+ VERSION = '0.2.6'
5
5
  end
data/lib/kk/git.rb CHANGED
@@ -5,8 +5,8 @@ require_relative 'git/ruby_warnings'
5
5
  require_relative 'git/git_runner'
6
6
  require_relative 'git/commit_message'
7
7
  require_relative 'git/git_ops'
8
+ require_relative 'git/auto_format'
8
9
  require_relative 'git/release'
9
10
 
10
11
  module KKGit
11
12
  end
12
-
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
4
+ version: 0.2.6
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