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: []
         |