format-staged 0.0.1
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 +7 -0
- data/bin/git-format-staged +54 -0
- data/lib/format-staged/entry.rb +35 -0
- data/lib/format-staged/io.rb +40 -0
- data/lib/format-staged/version.rb +3 -0
- data/lib/format-staged.rb +109 -0
- metadata +48 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f8fd81e745a1b6710724e4834252786c1775928cb3ca8281b4acb94f87bf0946
|
4
|
+
data.tar.gz: 05f8dfa70f6405d390bf19e6c9c515ebed6647b714ecc83122e28b420f5934bf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6d04e7ef3312f160d22796fa6c0b9f923405b7c65540cd107641c2c668d312f66859d9c661b00a3e43fbddec56fc4b888d8823057bd64d652147d8aa90ff57bf
|
7
|
+
data.tar.gz: 61873bda257b0fd6d89abb4bb191f32694ea4dfb6e7f36b138d84f45b77091ba64b458195debdf498f3503b55800e3f685f170a8e0b3054b5895c324ebf8f4ec
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'format-staged'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
parameters = {
|
6
|
+
:update => true,
|
7
|
+
:write => true,
|
8
|
+
:verbose => false,
|
9
|
+
}
|
10
|
+
|
11
|
+
parser = OptionParser.new do |opt|
|
12
|
+
opt.banner = "Usage: #{opt.program_name} [options] [patterns]"
|
13
|
+
opt.separator ""
|
14
|
+
opt.on('-f', '--formatter COMMAND', 'Shell command to format files, will run once per file. Occurrences of the placeholder `{}` will be replaced with a path to the file being formatted. (Example: "prettier --stdin-filepath \'{}\'")') do |o|
|
15
|
+
parameters[:formatter] = o
|
16
|
+
end
|
17
|
+
|
18
|
+
opt.on('--[no-]update-working-tree', 'By default formatting changes made to staged file content will also be applied to working tree files via a patch. This option disables that behavior, leaving working tree files untouched.') do |value|
|
19
|
+
parameters[:update] = value
|
20
|
+
end
|
21
|
+
|
22
|
+
opt.on('--[no-]write', "Prevents #{opt.program_name} from modifying staged or working tree files. You can use this option to check staged changes with a linter instead of formatting. With this option stdout from the formatter command is ignored.") do |value|
|
23
|
+
parameters[:write] = value
|
24
|
+
end
|
25
|
+
|
26
|
+
opt.on("-v", "--[no-]verbose", 'Shows commands being run') do |value|
|
27
|
+
parameters[:verbose] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
opt.separator ""
|
31
|
+
|
32
|
+
opt.on_tail('-h', '--help', 'Prints this help') do
|
33
|
+
puts opt
|
34
|
+
exit
|
35
|
+
end
|
36
|
+
|
37
|
+
opt.on_tail('--version', "Prints the version number and exits") do
|
38
|
+
puts FormatStaged::VERSION
|
39
|
+
exit
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
parser.parse!
|
44
|
+
parameters[:patterns] = ARGV
|
45
|
+
|
46
|
+
if !parameters[:formatter] or parameters[:patterns].empty?
|
47
|
+
puts "Missing formatter or file patterns!"
|
48
|
+
|
49
|
+
puts parser
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
|
53
|
+
formatter = FormatStaged.new(**parameters)
|
54
|
+
formatter.run
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class FormatStaged
|
2
|
+
class Entry
|
3
|
+
PATTERN = /^:(?<src_mode>\d+) (?<dst_mode>\d+) (?<src_hash>[a-f0-9]+) (?<dst_hash>[a-f0-9]+) (?<status>[A-Z])(?<score>\d+)?\t(?<src_path>[^\t]+)(?:\t(?<dst_path>[^\t]+))?$/
|
4
|
+
|
5
|
+
attr_reader :src_mode, :dst_mode, :src_hash, :dst_hash, :status, :score, :src_path, :dst_path, :path, :root
|
6
|
+
|
7
|
+
def initialize(line, root:)
|
8
|
+
matches = line.match(PATTERN) or raise "Cannot parse output #{line}"
|
9
|
+
@src_mode = matches[:src_mode]
|
10
|
+
@dst_mode = matches[:dst_mode]
|
11
|
+
@src_hash = matches[:src_hash]
|
12
|
+
@dst_hash = matches[:dst_hash]
|
13
|
+
@status = matches[:status]
|
14
|
+
@score = matches[:score]&.to_i
|
15
|
+
@src_path = matches[:src_path]
|
16
|
+
@dst_path = matches[:dst_path]
|
17
|
+
@path = File.expand_path(@src_path, root)
|
18
|
+
@root = root
|
19
|
+
end
|
20
|
+
|
21
|
+
def symlink?
|
22
|
+
@dst_mode == '120000'
|
23
|
+
end
|
24
|
+
|
25
|
+
def matches?(patterns)
|
26
|
+
result = false
|
27
|
+
patterns.each do |pattern|
|
28
|
+
if File.fnmatch? pattern, path, File::FNM_EXTGLOB
|
29
|
+
result = true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class FormatStaged
|
2
|
+
def get_output(*args, lines: true)
|
3
|
+
puts '> ' + args.join(' ') if @verbose
|
4
|
+
|
5
|
+
output = IO.popen(args, err: :err) do |io|
|
6
|
+
if lines
|
7
|
+
io.readlines.map { |l| l.chomp }
|
8
|
+
else
|
9
|
+
io.read
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
if @verbose and lines
|
14
|
+
output.each do |line|
|
15
|
+
puts "< #{line}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
raise "Failed to run command" unless $?.success?
|
20
|
+
|
21
|
+
output
|
22
|
+
end
|
23
|
+
|
24
|
+
def pipe_command(*args, source: nil)
|
25
|
+
puts (source.nil? ? '> ' : '| ') + args.join(' ') if @verbose
|
26
|
+
r, w = IO.pipe
|
27
|
+
|
28
|
+
opts = {}
|
29
|
+
opts[:in] = source unless source.nil?
|
30
|
+
opts[:out] = w
|
31
|
+
opts[:err] = :err
|
32
|
+
|
33
|
+
pid = spawn(*args, **opts)
|
34
|
+
|
35
|
+
w.close
|
36
|
+
source &.close
|
37
|
+
|
38
|
+
return [pid, r]
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'format-staged/version'
|
2
|
+
require 'format-staged/entry'
|
3
|
+
require 'format-staged/io'
|
4
|
+
require 'shellwords'
|
5
|
+
|
6
|
+
class FormatStaged
|
7
|
+
attr_reader :formatter, :patterns, :update, :write, :verbose
|
8
|
+
|
9
|
+
def initialize(formatter:, patterns:, update: true, write: true, verbose: true)
|
10
|
+
@formatter = formatter
|
11
|
+
@patterns = patterns
|
12
|
+
@update = update
|
13
|
+
@write = write
|
14
|
+
@verbose = verbose
|
15
|
+
end
|
16
|
+
|
17
|
+
def run()
|
18
|
+
root = get_output('git', 'rev-parse', '--show-toplevel').first
|
19
|
+
|
20
|
+
files = get_output('git', 'diff-index', '--cached', '--diff-filter=AM', '--no-renames', 'HEAD')
|
21
|
+
.map { |line| Entry.new(line, root: root) }
|
22
|
+
.reject { |entry| entry.symlink? }
|
23
|
+
.filter { |entry| entry.matches?(@patterns) }
|
24
|
+
|
25
|
+
files.each do |file|
|
26
|
+
format_file(file)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def format_file(file)
|
31
|
+
new_hash = format_object file
|
32
|
+
|
33
|
+
return true if not write
|
34
|
+
|
35
|
+
if new_hash == file.dst_hash
|
36
|
+
puts "Unchanged #{file.src_path}"
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
|
40
|
+
if object_is_empty new_hash
|
41
|
+
puts "Skipping #{file.src_path}, formatted file is empty"
|
42
|
+
return false
|
43
|
+
end
|
44
|
+
|
45
|
+
replace_file_in_index file, new_hash
|
46
|
+
|
47
|
+
if update
|
48
|
+
begin
|
49
|
+
patch_working_file file, new_hash
|
50
|
+
rescue => error
|
51
|
+
puts "Warning: failed updating #{file.src_path} in working copy: #{error}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
def format_object(file)
|
59
|
+
puts "Formatting #{file.src_path}"
|
60
|
+
|
61
|
+
format_command = formatter.sub("{}", file.src_path.shellescape)
|
62
|
+
|
63
|
+
pid1, r = pipe_command "git", "cat-file", "-p", file.dst_hash
|
64
|
+
pid2, r = pipe_command format_command, source: r
|
65
|
+
pid3, r = pipe_command "git", "hash-object", "-w", "--stdin", source: r
|
66
|
+
|
67
|
+
result = r.readlines.map { |it| it.chomp }
|
68
|
+
if @verbose
|
69
|
+
result.each do |line|
|
70
|
+
puts "< #{line}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
Process.wait pid1
|
75
|
+
raise "Cannot read #{file.dst_hash} from object database" unless $?.success?
|
76
|
+
|
77
|
+
Process.wait pid2
|
78
|
+
raise "Error formatting #{file.src_path}" unless $?.success?
|
79
|
+
|
80
|
+
Process.wait pid3
|
81
|
+
raise "Error writing formatted file back to object database" unless $?.success? && !result.empty?
|
82
|
+
|
83
|
+
result.first
|
84
|
+
end
|
85
|
+
|
86
|
+
def object_is_empty(hash)
|
87
|
+
size = get_output("git", "cat-file", "-s", hash).first.to_i
|
88
|
+
size == 0
|
89
|
+
end
|
90
|
+
|
91
|
+
def patch_working_file(file, new_hash)
|
92
|
+
patch = get_output "git", "diff", file.dst_hash, new_hash, lines: false
|
93
|
+
patch.gsub! "a/#{file.dst_hash}", "a/#{file.src_path}"
|
94
|
+
patch.gsub! "b/#{new_hash}", "b/#{file.src_path}"
|
95
|
+
|
96
|
+
input, patch_out = IO.pipe
|
97
|
+
pid, r = pipe_command "git", "apply", "-", source: input
|
98
|
+
|
99
|
+
patch_out.write patch
|
100
|
+
patch_out.close
|
101
|
+
|
102
|
+
Process.wait pid
|
103
|
+
raise "Error applying patch" unless $?.success?
|
104
|
+
end
|
105
|
+
|
106
|
+
def replace_file_in_index(file, new_hash)
|
107
|
+
get_output "git", "update-index", "--cacheinfo", "#{file.dst_mode},#{new_hash},#{file.src_path}"
|
108
|
+
end
|
109
|
+
end
|
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: format-staged
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sven Weidauer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-05-22 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: git format staged
|
14
|
+
email: sven@5sw.de
|
15
|
+
executables:
|
16
|
+
- git-format-staged
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- bin/git-format-staged
|
21
|
+
- lib/format-staged.rb
|
22
|
+
- lib/format-staged/entry.rb
|
23
|
+
- lib/format-staged/io.rb
|
24
|
+
- lib/format-staged/version.rb
|
25
|
+
homepage: https://github.com/5sw/format-staged
|
26
|
+
licenses:
|
27
|
+
- MIT
|
28
|
+
metadata: {}
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubygems_version: 3.1.6
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: git format staged!
|
48
|
+
test_files: []
|