format-staged 0.0.1 → 0.1.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 +4 -4
- data/bin/git-format-staged +49 -21
- data/lib/format-staged/entry.rb +20 -8
- data/lib/format-staged/io.rb +72 -35
- data/lib/format-staged/job.rb +157 -0
- data/lib/format-staged/version.rb +5 -3
- data/lib/format_staged.rb +11 -0
- metadata +93 -7
- data/lib/format-staged.rb +0 -109
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f07500eeef0c1c28b74bbd28b5fd935b733d8c5b444e714db45b18ca450fdfce
|
4
|
+
data.tar.gz: 1cac2a6e5e80f10a0fd0df2c0349e49b7411f632709193902ba1eb9c3b646d8a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba546faa5200d31c2f7c4faa413196d31c6b45314fecd583a2905b1f0c52b7135a4e0f847384a6c5fb27b41e8a66e6813a2329b1cb65af1659a6c9250fa40daf
|
7
|
+
data.tar.gz: 82dd60bd9f48843a67a72c65bb4d455ea818502b6e6f34bc6c66b94a2aa6497bbc9981909ed1d266519d544cf48a95dd1ada6c8d73531eab07f49522c7083f3a
|
data/bin/git-format-staged
CHANGED
@@ -1,54 +1,82 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'format_staged'
|
3
5
|
require 'optparse'
|
4
6
|
|
5
7
|
parameters = {
|
6
|
-
:
|
7
|
-
:
|
8
|
-
:
|
8
|
+
update: true,
|
9
|
+
write: true,
|
10
|
+
verbose: false
|
9
11
|
}
|
10
12
|
|
11
13
|
parser = OptionParser.new do |opt|
|
12
14
|
opt.banner = "Usage: #{opt.program_name} [options] [patterns]"
|
13
|
-
opt.separator
|
14
|
-
opt.on('-f', '--formatter COMMAND',
|
15
|
+
opt.separator ''
|
16
|
+
opt.on('-f', '--formatter COMMAND',
|
17
|
+
<<~DOC
|
18
|
+
Shell command to format files, will run once per file. Occurrences of the placeholder `{}` will \
|
19
|
+
be replaced with a path to the file being formatted. \
|
20
|
+
(Example: "prettier --stdin-filepath \'{}\'")
|
21
|
+
DOC
|
22
|
+
) do |o|
|
15
23
|
parameters[:formatter] = o
|
16
24
|
end
|
17
|
-
|
18
|
-
opt.on('--[no-]update-working-tree',
|
25
|
+
|
26
|
+
opt.on('--[no-]update-working-tree',
|
27
|
+
<<~DOC
|
28
|
+
By default formatting changes made to staged file content will also be applied to working tree \
|
29
|
+
files via a patch. This option disables that behavior, leaving working tree files untouched.#{' '}
|
30
|
+
DOC
|
31
|
+
) do |value|
|
19
32
|
parameters[:update] = value
|
20
33
|
end
|
21
|
-
|
22
|
-
opt.on('--[no-]write',
|
34
|
+
|
35
|
+
opt.on('--[no-]write',
|
36
|
+
<<~DOC
|
37
|
+
"Prevents #{opt.program_name} from modifying staged or working tree files. You can use this option \
|
38
|
+
to check staged changes with a linter instead of formatting. With this option stdout from the \
|
39
|
+
formatter command is ignored."
|
40
|
+
DOC
|
41
|
+
) do |value|
|
23
42
|
parameters[:write] = value
|
24
43
|
end
|
25
|
-
|
26
|
-
opt.on(
|
44
|
+
|
45
|
+
opt.on('-v', '--[no-]verbose', 'Shows commands being run') do |value|
|
27
46
|
parameters[:verbose] = value
|
28
47
|
end
|
29
48
|
|
30
|
-
opt.separator
|
31
|
-
|
49
|
+
opt.separator ''
|
50
|
+
|
32
51
|
opt.on_tail('-h', '--help', 'Prints this help') do
|
33
52
|
puts opt
|
34
53
|
exit
|
35
54
|
end
|
36
|
-
|
37
|
-
opt.on_tail('--version',
|
55
|
+
|
56
|
+
opt.on_tail('--version', 'Prints the version number and exits') do
|
38
57
|
puts FormatStaged::VERSION
|
39
58
|
exit
|
40
59
|
end
|
60
|
+
|
61
|
+
opt.on('--[no-]color', 'Colorizes output') do |value|
|
62
|
+
parameters[:color_output] = value
|
63
|
+
end
|
41
64
|
end
|
42
65
|
|
43
66
|
parser.parse!
|
44
67
|
parameters[:patterns] = ARGV
|
45
68
|
|
46
|
-
if !parameters[:formatter]
|
47
|
-
puts
|
48
|
-
|
69
|
+
if !parameters[:formatter] || parameters[:patterns].empty?
|
70
|
+
puts 'Missing formatter or file patterns!'
|
71
|
+
|
49
72
|
puts parser
|
50
73
|
exit
|
51
74
|
end
|
52
75
|
|
53
|
-
|
54
|
-
|
76
|
+
begin
|
77
|
+
success = FormatStaged.run(**parameters)
|
78
|
+
exit success ? 0 : 1
|
79
|
+
rescue RuntimeError => e
|
80
|
+
puts "💣 Error: #{e.message}".red
|
81
|
+
exit 1
|
82
|
+
end
|
data/lib/format-staged/entry.rb
CHANGED
@@ -1,9 +1,23 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FormatStaged
|
4
|
+
##
|
5
|
+
# Entry in the git index.
|
6
|
+
#
|
7
|
+
# Data as produced by `git diff-index`
|
2
8
|
class Entry
|
3
|
-
PATTERN =
|
9
|
+
PATTERN = /^
|
10
|
+
:(?<src_mode>\d+)\s
|
11
|
+
(?<dst_mode>\d+)\s
|
12
|
+
(?<src_hash>[a-f0-9]+)\s
|
13
|
+
(?<dst_hash>[a-f0-9]+)\s
|
14
|
+
(?<status>[A-Z])(?<score>\d+)?\t
|
15
|
+
(?<src_path>[^\t]+)
|
16
|
+
(?:\t(?<dst_path>[^\t]+))?
|
17
|
+
$/x.freeze
|
4
18
|
|
5
19
|
attr_reader :src_mode, :dst_mode, :src_hash, :dst_hash, :status, :score, :src_path, :dst_path, :path, :root
|
6
|
-
|
20
|
+
|
7
21
|
def initialize(line, root:)
|
8
22
|
matches = line.match(PATTERN) or raise "Cannot parse output #{line}"
|
9
23
|
@src_mode = matches[:src_mode]
|
@@ -17,17 +31,15 @@ class FormatStaged
|
|
17
31
|
@path = File.expand_path(@src_path, root)
|
18
32
|
@root = root
|
19
33
|
end
|
20
|
-
|
34
|
+
|
21
35
|
def symlink?
|
22
36
|
@dst_mode == '120000'
|
23
37
|
end
|
24
|
-
|
38
|
+
|
25
39
|
def matches?(patterns)
|
26
40
|
result = false
|
27
41
|
patterns.each do |pattern|
|
28
|
-
if File.fnmatch? pattern, path, File::FNM_EXTGLOB
|
29
|
-
result = true
|
30
|
-
end
|
42
|
+
result = true if File.fnmatch? pattern, path, File::FNM_EXTGLOB
|
31
43
|
end
|
32
44
|
result
|
33
45
|
end
|
data/lib/format-staged/io.rb
CHANGED
@@ -1,40 +1,77 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
|
5
|
+
module FormatStaged
|
6
|
+
##
|
7
|
+
# Mixin that provides IO methods
|
8
|
+
module IOMixin
|
9
|
+
def get_output(*args, lines: true, silent: false)
|
10
|
+
puts "> #{args.join(' ')}" if @verbose
|
11
|
+
|
12
|
+
r = IO.popen(args, err: :err)
|
13
|
+
output = read_output(r, lines: lines, silent: silent)
|
14
|
+
|
15
|
+
raise 'Failed to run command' unless $CHILD_STATUS.success?
|
16
|
+
|
17
|
+
output
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_status(*args)
|
21
|
+
puts "> #{args.join(' ')}" if @verbose
|
22
|
+
result = system(*args)
|
23
|
+
|
24
|
+
raise 'Failed to run command' if result.nil?
|
25
|
+
|
26
|
+
puts "? #{$CHILD_STATUS.exitstatus}" if @verbose
|
27
|
+
|
28
|
+
$CHILD_STATUS.exitstatus
|
11
29
|
end
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
30
|
+
|
31
|
+
def pipe_command(*args, source: nil)
|
32
|
+
puts (source.nil? ? '> ' : '| ') + args.join(' ') if @verbose
|
33
|
+
r, w = IO.pipe
|
34
|
+
|
35
|
+
opts = {}
|
36
|
+
opts[:in] = source unless source.nil?
|
37
|
+
opts[:out] = w
|
38
|
+
opts[:err] = :err
|
39
|
+
|
40
|
+
pid = spawn(*args, **opts)
|
41
|
+
|
42
|
+
w.close
|
43
|
+
source&.close
|
44
|
+
|
45
|
+
[pid, r]
|
46
|
+
end
|
47
|
+
|
48
|
+
def read_output(output, lines: true, silent: false)
|
49
|
+
result = output.read
|
50
|
+
splits = result.split("\n")
|
51
|
+
if @verbose && !silent
|
52
|
+
splits.each do |line|
|
53
|
+
puts "< #{line}"
|
54
|
+
end
|
16
55
|
end
|
56
|
+
output.close
|
57
|
+
|
58
|
+
lines ? splits : result
|
59
|
+
end
|
60
|
+
|
61
|
+
def fail!(message)
|
62
|
+
abort "💣 #{message.red}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def warning(message)
|
66
|
+
warn "⚠️ #{message.yellow}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def info(message)
|
70
|
+
puts message.blue
|
71
|
+
end
|
72
|
+
|
73
|
+
def verbose_info(message)
|
74
|
+
puts "ℹ️ #{message}" if verbose
|
17
75
|
end
|
18
|
-
|
19
|
-
raise "Failed to run command" unless $?.success?
|
20
|
-
|
21
|
-
output
|
22
76
|
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
77
|
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'entry'
|
4
|
+
require_relative 'io'
|
5
|
+
|
6
|
+
require 'shellwords'
|
7
|
+
require 'colorize'
|
8
|
+
require 'English'
|
9
|
+
|
10
|
+
module FormatStaged
|
11
|
+
##
|
12
|
+
# Runs staged changes through a formatting tool
|
13
|
+
class Job
|
14
|
+
include IOMixin
|
15
|
+
|
16
|
+
attr_reader :formatter, :patterns, :update, :write, :verbose
|
17
|
+
|
18
|
+
def initialize(formatter:, patterns:, **options)
|
19
|
+
validate_patterns patterns
|
20
|
+
|
21
|
+
@formatter = formatter
|
22
|
+
@patterns = patterns
|
23
|
+
@update = options.fetch(:update, true)
|
24
|
+
@write = options.fetch(:write, true)
|
25
|
+
@verbose = options.fetch(:verbose, true)
|
26
|
+
|
27
|
+
String.disable_colorization = !options.fetch(:color_output, $stdout.isatty)
|
28
|
+
end
|
29
|
+
|
30
|
+
def run
|
31
|
+
files = matching_files(repo_root)
|
32
|
+
if files.empty?
|
33
|
+
info 'No staged file matching pattern. Done'
|
34
|
+
return true
|
35
|
+
end
|
36
|
+
|
37
|
+
formatted = files.filter { |file| format_file file }
|
38
|
+
|
39
|
+
return false unless formatted.size == files.size
|
40
|
+
|
41
|
+
quiet = @verbose ? [] : ['--quiet']
|
42
|
+
return !write || get_status('git', 'diff-index', '--cached', '--exit-code', *quiet, 'HEAD') != 0
|
43
|
+
end
|
44
|
+
|
45
|
+
def repo_root
|
46
|
+
verbose_info 'Finding repository root'
|
47
|
+
root = get_output('git', 'rev-parse', '--show-toplevel', lines: false).chomp
|
48
|
+
verbose_info "Repo at #{root}"
|
49
|
+
|
50
|
+
root
|
51
|
+
end
|
52
|
+
|
53
|
+
def matching_files(root)
|
54
|
+
verbose_info 'Listing staged files'
|
55
|
+
|
56
|
+
get_output('git', 'diff-index', '--cached', '--diff-filter=AM', '--no-renames', 'HEAD')
|
57
|
+
.map { |line| Entry.new(line, root: root) }
|
58
|
+
.reject(&:symlink?)
|
59
|
+
.filter { |entry| entry.matches?(@patterns) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def format_file(file)
|
63
|
+
new_hash = format_object file
|
64
|
+
|
65
|
+
return true unless write
|
66
|
+
|
67
|
+
if new_hash == file.dst_hash
|
68
|
+
info "Unchanged #{file.src_path}"
|
69
|
+
return true
|
70
|
+
end
|
71
|
+
|
72
|
+
if object_is_empty new_hash
|
73
|
+
info "Skipping #{file.src_path}, formatted file is empty"
|
74
|
+
return false
|
75
|
+
end
|
76
|
+
|
77
|
+
replace_file_in_index file, new_hash
|
78
|
+
update_working_copy file, new_hash
|
79
|
+
|
80
|
+
if new_hash == file.src_hash
|
81
|
+
info "File #{file.src_path} equal to comitted"
|
82
|
+
return true
|
83
|
+
end
|
84
|
+
|
85
|
+
true
|
86
|
+
end
|
87
|
+
|
88
|
+
def update_working_copy(file, new_hash)
|
89
|
+
return unless update
|
90
|
+
|
91
|
+
begin
|
92
|
+
patch_working_file file, new_hash
|
93
|
+
rescue StandardError => e
|
94
|
+
warning "failed updating #{file.src_path} in working copy: #{e}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def format_object(file)
|
99
|
+
info "Formatting #{file.src_path}"
|
100
|
+
|
101
|
+
format_command = formatter.sub('{}', file.src_path.shellescape)
|
102
|
+
|
103
|
+
pid1, r = pipe_command 'git', 'cat-file', '-p', file.dst_hash
|
104
|
+
pid2, r = pipe_command format_command, source: r
|
105
|
+
|
106
|
+
write_args = write ? ['-w'] : []
|
107
|
+
pid3, r = pipe_command 'git', 'hash-object', *write_args, '--stdin', source: r
|
108
|
+
|
109
|
+
result = read_output(r, lines: false).chomp
|
110
|
+
|
111
|
+
Process.wait pid1
|
112
|
+
raise "Cannot read #{file.dst_hash} from object database" unless $CHILD_STATUS.success?
|
113
|
+
|
114
|
+
Process.wait pid2
|
115
|
+
raise "Error formatting #{file.src_path}" unless $CHILD_STATUS.success?
|
116
|
+
|
117
|
+
Process.wait pid3
|
118
|
+
raise 'Error writing formatted file back to object database' unless $CHILD_STATUS.success? && !result.empty?
|
119
|
+
|
120
|
+
result
|
121
|
+
end
|
122
|
+
|
123
|
+
def object_is_empty(hash)
|
124
|
+
size = get_output('git', 'cat-file', '-s', hash).first.to_i
|
125
|
+
size.zero?
|
126
|
+
end
|
127
|
+
|
128
|
+
def patch_working_file(file, new_hash)
|
129
|
+
info 'Updating working copy'
|
130
|
+
|
131
|
+
patch = get_output 'git', 'diff', file.dst_hash, new_hash, lines: false, silent: true
|
132
|
+
patch.gsub! "a/#{file.dst_hash}", "a/#{file.src_path}"
|
133
|
+
patch.gsub! "b/#{new_hash}", "b/#{file.src_path}"
|
134
|
+
|
135
|
+
input, patch_out = IO.pipe
|
136
|
+
pid, r = pipe_command 'git', 'apply', '-', source: input
|
137
|
+
|
138
|
+
patch_out.write patch
|
139
|
+
patch_out.close
|
140
|
+
|
141
|
+
read_output r
|
142
|
+
|
143
|
+
Process.wait pid
|
144
|
+
raise 'Error applying patch' unless $CHILD_STATUS.success?
|
145
|
+
end
|
146
|
+
|
147
|
+
def replace_file_in_index(file, new_hash)
|
148
|
+
get_output 'git', 'update-index', '--cacheinfo', "#{file.dst_mode},#{new_hash},#{file.src_path}"
|
149
|
+
end
|
150
|
+
|
151
|
+
def validate_patterns(patterns)
|
152
|
+
patterns.each do |pattern|
|
153
|
+
fail! "Negative pattern '#{pattern}' is not yet supported" if pattern.start_with? '!'
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,99 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: format-staged
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sven Weidauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
12
|
-
dependencies:
|
11
|
+
date: 2022-06-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colorize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '13.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '13.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.29'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.29'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop-rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.6'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.6'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop-rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
13
97
|
description: git format staged
|
14
98
|
email: sven@5sw.de
|
15
99
|
executables:
|
@@ -18,23 +102,25 @@ extensions: []
|
|
18
102
|
extra_rdoc_files: []
|
19
103
|
files:
|
20
104
|
- bin/git-format-staged
|
21
|
-
- lib/format-staged.rb
|
22
105
|
- lib/format-staged/entry.rb
|
23
106
|
- lib/format-staged/io.rb
|
107
|
+
- lib/format-staged/job.rb
|
24
108
|
- lib/format-staged/version.rb
|
109
|
+
- lib/format_staged.rb
|
25
110
|
homepage: https://github.com/5sw/format-staged
|
26
111
|
licenses:
|
27
112
|
- MIT
|
28
|
-
metadata:
|
113
|
+
metadata:
|
114
|
+
rubygems_mfa_required: 'true'
|
29
115
|
post_install_message:
|
30
116
|
rdoc_options: []
|
31
117
|
require_paths:
|
32
118
|
- lib
|
33
119
|
required_ruby_version: !ruby/object:Gem::Requirement
|
34
120
|
requirements:
|
35
|
-
- - "
|
121
|
+
- - "~>"
|
36
122
|
- !ruby/object:Gem::Version
|
37
|
-
version: '
|
123
|
+
version: '2.7'
|
38
124
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
125
|
requirements:
|
40
126
|
- - ">="
|
data/lib/format-staged.rb
DELETED
@@ -1,109 +0,0 @@
|
|
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
|