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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8fd81e745a1b6710724e4834252786c1775928cb3ca8281b4acb94f87bf0946
4
- data.tar.gz: 05f8dfa70f6405d390bf19e6c9c515ebed6647b714ecc83122e28b420f5934bf
3
+ metadata.gz: f07500eeef0c1c28b74bbd28b5fd935b733d8c5b444e714db45b18ca450fdfce
4
+ data.tar.gz: 1cac2a6e5e80f10a0fd0df2c0349e49b7411f632709193902ba1eb9c3b646d8a
5
5
  SHA512:
6
- metadata.gz: 6d04e7ef3312f160d22796fa6c0b9f923405b7c65540cd107641c2c668d312f66859d9c661b00a3e43fbddec56fc4b888d8823057bd64d652147d8aa90ff57bf
7
- data.tar.gz: 61873bda257b0fd6d89abb4bb191f32694ea4dfb6e7f36b138d84f45b77091ba64b458195debdf498f3503b55800e3f685f170a8e0b3054b5895c324ebf8f4ec
6
+ metadata.gz: ba546faa5200d31c2f7c4faa413196d31c6b45314fecd583a2905b1f0c52b7135a4e0f847384a6c5fb27b41e8a66e6813a2329b1cb65af1659a6c9250fa40daf
7
+ data.tar.gz: 82dd60bd9f48843a67a72c65bb4d455ea818502b6e6f34bc6c66b94a2aa6497bbc9981909ed1d266519d544cf48a95dd1ada6c8d73531eab07f49522c7083f3a
@@ -1,54 +1,82 @@
1
1
  #!/usr/bin/env ruby
2
- require 'format-staged'
2
+ # frozen_string_literal: true
3
+
4
+ require 'format_staged'
3
5
  require 'optparse'
4
6
 
5
7
  parameters = {
6
- :update => true,
7
- :write => true,
8
- :verbose => false,
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', '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
+ 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', '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|
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', "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|
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("-v", "--[no-]verbose", 'Shows commands being run') do |value|
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', "Prints the version number and exits") do
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] or parameters[:patterns].empty?
47
- puts "Missing formatter or file patterns!"
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
- formatter = FormatStaged.new(**parameters)
54
- formatter.run
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
@@ -1,9 +1,23 @@
1
- class FormatStaged
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 = /^:(?<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]+))?$/
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
@@ -1,40 +1,77 @@
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
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
- if @verbose and lines
14
- output.each do |line|
15
- puts "< #{line}"
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
@@ -1,3 +1,5 @@
1
- class FormatStaged
2
- VERSION = "0.0.1"
3
- end
1
+ # frozen_string_literal: true
2
+
3
+ module FormatStaged
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'format-staged/job'
4
+
5
+ ##
6
+ # FormatStaged module
7
+ module FormatStaged
8
+ def self.run(**options)
9
+ Job.new(**options).run
10
+ end
11
+ 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.1
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-05-22 00:00:00.000000000 Z
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: '0'
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