git-crecord 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +20 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +50 -0
- data/Rakefile +14 -0
- data/bin/git-crecord +4 -0
- data/ext/mkrf_conf.rb +10 -0
- data/git-crecord.gemspec +29 -0
- data/lib/git_crecord/diff/difference.rb +81 -0
- data/lib/git_crecord/diff/file.rb +68 -0
- data/lib/git_crecord/diff/hunk.rb +49 -0
- data/lib/git_crecord/diff/line.rb +57 -0
- data/lib/git_crecord/diff.rb +59 -0
- data/lib/git_crecord/git.rb +52 -0
- data/lib/git_crecord/logger.rb +7 -0
- data/lib/git_crecord/quit_action.rb +8 -0
- data/lib/git_crecord/ui/color.rb +27 -0
- data/lib/git_crecord/ui/help_window.rb +45 -0
- data/lib/git_crecord/ui/hunks_window.rb +172 -0
- data/lib/git_crecord/ui.rb +51 -0
- data/lib/git_crecord/version.rb +3 -0
- data/lib/git_crecord.rb +45 -0
- data/screenshot.jpg +0 -0
- data/test/git_crecord/diff/hunk_test.rb +28 -0
- data/test/system-test.sh +187 -0
- data/test/test_helper.rb +3 -0
- metadata +117 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fffb4cbf34ec7e9c44ff7c17f598c2a2164adf03
|
4
|
+
data.tar.gz: 396608cc59e9a52e8315c4a35f68827693030d0f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f80d28b331abbb5dfe6387718fc37032f4329dab9dc3a6547108aeb8453139c8139b017891c60b858c41e091bb52bd18bb447531a5a0ea0df84244d9023e1458
|
7
|
+
data.tar.gz: 3377110378bbe79cbb61b9d3707171a0b0eb56847bdfcbb6fa92df4679fbd543ce89124d87dd42648d0075c8c301d6fe4f4fc2beaab1eadb8aa257500d9b3023
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Metrics/LineLength:
|
2
|
+
Max: 80
|
3
|
+
|
4
|
+
Style/AlignParameters:
|
5
|
+
EnforcedStyle: with_fixed_indentation
|
6
|
+
|
7
|
+
Style/SpaceInsideHashLiteralBraces:
|
8
|
+
EnforcedStyle: no_space
|
9
|
+
|
10
|
+
Style/SpaceBeforeBlockBraces:
|
11
|
+
EnforcedStyle: no_space
|
12
|
+
|
13
|
+
Style/NumericLiterals:
|
14
|
+
MinDigits: 666
|
15
|
+
|
16
|
+
Documentation:
|
17
|
+
Enabled: false
|
18
|
+
|
19
|
+
AllCops:
|
20
|
+
DisplayStyleGuide: true
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2016 Maik Brendler
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# git-crecord
|
2
|
+
|
3
|
+
Inspred by [crecord mercurial extension](https://bitbucket.org/edgimar/crecord/wiki/Home), git-crecord is an easy way for partially committing/staging of git changes.
|
4
|
+
|
5
|
+
![Screenshot](/screenshot.jpg?raw=true)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
TODO
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
```shell
|
14
|
+
$ git crecord
|
15
|
+
```
|
16
|
+
|
17
|
+
Key-bindings:
|
18
|
+
```
|
19
|
+
q - quit
|
20
|
+
s - stage selection and quit
|
21
|
+
c - commit selection and quit
|
22
|
+
j / ↓ - down
|
23
|
+
k / ↑ - up
|
24
|
+
h / ← - collapse hunk
|
25
|
+
l / → - expand
|
26
|
+
f - toggle fold
|
27
|
+
g - go to first line
|
28
|
+
G - go to last line
|
29
|
+
C-P - up to previous hunk / file
|
30
|
+
C-N - down to previous hunk / file
|
31
|
+
SPACE - toggle selection
|
32
|
+
A - toggle all selections
|
33
|
+
? - display help
|
34
|
+
R - force redraw
|
35
|
+
```
|
36
|
+
|
37
|
+
## Development
|
38
|
+
|
39
|
+
```shell
|
40
|
+
$ git clone https://github.com/mbrendler/git-crecord
|
41
|
+
$ cd git-crecord
|
42
|
+
$ bundle install
|
43
|
+
$ ln -s bin/git-crecord /usr/bin/git-crecord
|
44
|
+
```
|
45
|
+
|
46
|
+
Tests:
|
47
|
+
```shell
|
48
|
+
$ bundle exec rake test
|
49
|
+
$ bundle exec rake systemtest
|
50
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.test_files = FileList['test/git_crecord/**/*test.rb']
|
7
|
+
end
|
8
|
+
|
9
|
+
desc 'run system tests'
|
10
|
+
task :systemtest do
|
11
|
+
sh(File.join(__dir__, 'test/system-test.sh'))
|
12
|
+
end
|
13
|
+
|
14
|
+
task :default # dummy task to build native extension (install curses)
|
data/bin/git-crecord
ADDED
data/ext/mkrf_conf.rb
ADDED
data/git-crecord.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'lib/git_crecord/version'
|
2
|
+
|
3
|
+
GemSpec = Gem::Specification.new do |spec|
|
4
|
+
spec.required_rubygems_version = Gem::Requirement.new('>= 1.3.6')
|
5
|
+
spec.platform = Gem::Platform::RUBY
|
6
|
+
spec.required_ruby_version = '>= 2.0.0'
|
7
|
+
spec.name = 'git-crecord'
|
8
|
+
spec.version = GitCrecord::VERSION
|
9
|
+
spec.authors = 'Maik Brendler'
|
10
|
+
spec.email = 'maik.brendler@invision.de'
|
11
|
+
spec.summary = 'Git command to stage/commit hunks the simple way.'
|
12
|
+
spec.description = %w(
|
13
|
+
This gem adds the git-crecord command.
|
14
|
+
It provides a curses UI to stage/commit git-hunks.
|
15
|
+
).join(' ')
|
16
|
+
spec.license = 'MIT'
|
17
|
+
spec.homepage = 'https://github.com/mbrendler/git-crecord'
|
18
|
+
spec.metadata = {
|
19
|
+
'issue_tracker' => 'https://github.com/mbrendler/git-crecord/issues'
|
20
|
+
}
|
21
|
+
spec.require_paths = %w(lib)
|
22
|
+
spec.files = `git ls-files`.split($RS).delete_if{ |f| %r{^(spec|test)/} =~ f }
|
23
|
+
spec.test_files = `git ls-files`.split($RS).grep(%r{^(spec|test)/})
|
24
|
+
spec.executables = %w(git-crecord)
|
25
|
+
spec.has_rdoc = false
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.1', '>= 10.1.1'
|
27
|
+
spec.add_development_dependency 'minitest', '~> 5.8', '>= 5.8.4'
|
28
|
+
spec.extensions << 'ext/mkrf_conf.rb' # install curses dependency
|
29
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require_relative '../ui/color'
|
2
|
+
|
3
|
+
module GitCrecord
|
4
|
+
module Diff
|
5
|
+
class Difference
|
6
|
+
attr_accessor :expanded
|
7
|
+
attr_accessor :y1, :y2
|
8
|
+
attr_reader :subs
|
9
|
+
|
10
|
+
SELECTED_MAP = {
|
11
|
+
true => '[X] ',
|
12
|
+
false => '[ ] ',
|
13
|
+
:partly => '[~] '
|
14
|
+
}.freeze
|
15
|
+
SELECTION_MARKER_WIDTH = SELECTED_MAP[true].size
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@subs = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def strings(width)
|
22
|
+
to_s.scan(/.{1,#{content_width(width)}}/)
|
23
|
+
end
|
24
|
+
|
25
|
+
def max_height(width)
|
26
|
+
width = content_width(width)
|
27
|
+
((to_s.size - 1).abs / width) + 1 + subs.reduce(0) do |a, e|
|
28
|
+
a + e.max_height(width)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def content_width(width)
|
33
|
+
[1, width - x_offset - SELECTION_MARKER_WIDTH].max
|
34
|
+
end
|
35
|
+
|
36
|
+
def selectable?
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def selectable_subs
|
41
|
+
@selectable_subs ||= subs.select(&:selectable?)
|
42
|
+
end
|
43
|
+
|
44
|
+
def selected
|
45
|
+
s = selectable_subs.map(&:selected).uniq
|
46
|
+
return s[0] if s.size == 1
|
47
|
+
:partly
|
48
|
+
end
|
49
|
+
|
50
|
+
def selected=(value)
|
51
|
+
selectable_subs.each{ |sub| sub.selected = value }
|
52
|
+
end
|
53
|
+
|
54
|
+
def style(is_highlighted)
|
55
|
+
return Curses::A_BOLD | UI::Color.hl if is_highlighted
|
56
|
+
Curses::A_BOLD | UI::Color.normal
|
57
|
+
end
|
58
|
+
|
59
|
+
def prefix_style(_is_highlighted)
|
60
|
+
UI::Color.normal
|
61
|
+
end
|
62
|
+
|
63
|
+
def prefix(line_number)
|
64
|
+
return SELECTED_MAP.fetch(selected) if line_number == 0 && selectable?
|
65
|
+
' ' * SELECTION_MARKER_WIDTH
|
66
|
+
end
|
67
|
+
|
68
|
+
def print(win, line_number, is_highlighted)
|
69
|
+
@y1 = line_number + 1
|
70
|
+
prefix_style = prefix_style(is_highlighted)
|
71
|
+
style = style(is_highlighted)
|
72
|
+
strings(win.width).each_with_index do |string, index|
|
73
|
+
win.addstr(' ' * x_offset, line_number += 1, attr: prefix_style)
|
74
|
+
win.addstr(prefix(index), attr: prefix_style)
|
75
|
+
win.addstr(string, attr: style, fill: ' ')
|
76
|
+
end
|
77
|
+
@y2 = line_number
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require_relative 'difference'
|
2
|
+
require_relative 'hunk'
|
3
|
+
require_relative '../ui/color'
|
4
|
+
|
5
|
+
module GitCrecord
|
6
|
+
module Diff
|
7
|
+
class File < Difference
|
8
|
+
attr_reader :filename_a
|
9
|
+
attr_reader :type
|
10
|
+
|
11
|
+
def initialize(filename_a, filename_b, type: :modified)
|
12
|
+
@filename_a = filename_a
|
13
|
+
@filename_b = filename_b
|
14
|
+
@type = type
|
15
|
+
@expanded = false
|
16
|
+
super()
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
prefix = {modified: 'M', untracked: '?'}.fetch(type)
|
21
|
+
return "#{prefix} #{@filename_a}" if @filename_a == @filename_b
|
22
|
+
"#{prefix} #{filename_a} -> #{filename_b}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def info_string
|
26
|
+
line_count = subs.reduce(0){ |a, e| e.selectable_subs.size + a }
|
27
|
+
" #{subs.size} hunk(s), #{line_count} line(s) changed"
|
28
|
+
end
|
29
|
+
|
30
|
+
def strings(width)
|
31
|
+
result = super
|
32
|
+
return result unless expanded
|
33
|
+
result += info_string.scan(/.{1,#{content_width(width)}}/)
|
34
|
+
result << ''
|
35
|
+
end
|
36
|
+
|
37
|
+
def max_height(width)
|
38
|
+
super + ((info_string.size - 1).abs / content_width(width)) + 2
|
39
|
+
end
|
40
|
+
|
41
|
+
def x_offset
|
42
|
+
0
|
43
|
+
end
|
44
|
+
|
45
|
+
def <<(hunk)
|
46
|
+
subs << Hunk.new(hunk)
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_hunk_line(line)
|
51
|
+
subs.last << line
|
52
|
+
end
|
53
|
+
|
54
|
+
def generate_diff
|
55
|
+
return unless selected
|
56
|
+
[
|
57
|
+
"diff --git a/#{@filename_a} b/#{@filename_b}",
|
58
|
+
"--- a/#{@filename_a}",
|
59
|
+
"+++ b/#{@filename_b}",
|
60
|
+
*subs.map(&:generate_diff).compact,
|
61
|
+
''
|
62
|
+
].join("\n")
|
63
|
+
end
|
64
|
+
|
65
|
+
alias prefix_style style
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require_relative 'difference'
|
2
|
+
require_relative 'line'
|
3
|
+
require_relative '../ui/color'
|
4
|
+
|
5
|
+
module GitCrecord
|
6
|
+
module Diff
|
7
|
+
class Hunk < Difference
|
8
|
+
def initialize(head)
|
9
|
+
@head = head
|
10
|
+
@expanded = true
|
11
|
+
super()
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
@head
|
16
|
+
end
|
17
|
+
|
18
|
+
def x_offset
|
19
|
+
3
|
20
|
+
end
|
21
|
+
|
22
|
+
def <<(line)
|
23
|
+
subs << Line.new(line)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def generate_diff
|
28
|
+
return nil unless selected
|
29
|
+
[generate_header, *subs.map(&:generate_diff).compact].join("\n")
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate_header
|
33
|
+
old_start, old_count, new_start, new_count = parse_header
|
34
|
+
selectable_subs.each do |sub|
|
35
|
+
next if sub.selected
|
36
|
+
new_count -= 1 if sub.add?
|
37
|
+
new_count += 1 if sub.del?
|
38
|
+
end
|
39
|
+
"@@ -#{old_start},#{old_count} +#{new_start},#{new_count} @@"
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_header
|
43
|
+
match = @head.match(/@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@/)
|
44
|
+
raise "mismatching hunk-header - '#{@head}'" if match.nil?
|
45
|
+
[match[1], match[3] || 1, match[4], match[6] || 1].map(&:to_i)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require_relative 'difference'
|
2
|
+
require_relative '../ui/color'
|
3
|
+
|
4
|
+
module GitCrecord
|
5
|
+
module Diff
|
6
|
+
class Line < Difference
|
7
|
+
attr_reader :selected
|
8
|
+
|
9
|
+
def initialize(line)
|
10
|
+
@line = line
|
11
|
+
@selected = true
|
12
|
+
super()
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
@line
|
17
|
+
end
|
18
|
+
|
19
|
+
def x_offset
|
20
|
+
6
|
21
|
+
end
|
22
|
+
|
23
|
+
def add?
|
24
|
+
@line.start_with?('+')
|
25
|
+
end
|
26
|
+
|
27
|
+
def del?
|
28
|
+
@line.start_with?('-')
|
29
|
+
end
|
30
|
+
|
31
|
+
def selectable?
|
32
|
+
add? || del?
|
33
|
+
end
|
34
|
+
|
35
|
+
def selected=(value)
|
36
|
+
@selected = selectable? ? value : selected
|
37
|
+
end
|
38
|
+
|
39
|
+
def expanded
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_diff
|
44
|
+
return " #{@line[1..-1]}" if !selected && del?
|
45
|
+
return @line if selected
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def style(is_highlighted)
|
50
|
+
return UI::Color.hl if is_highlighted
|
51
|
+
return UI::Color.green if add?
|
52
|
+
return UI::Color.red if del?
|
53
|
+
UI::Color.normal
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative 'diff/file'
|
2
|
+
|
3
|
+
module GitCrecord
|
4
|
+
module Diff
|
5
|
+
def self.parse(diff)
|
6
|
+
files = []
|
7
|
+
enum = diff.lines.each
|
8
|
+
loop do
|
9
|
+
line = enum.next
|
10
|
+
line.chomp!
|
11
|
+
next files << parse_file_header(line, enum) if file_start?(line)
|
12
|
+
next files[-1] << line if hunk_start?(line)
|
13
|
+
files[-1].add_hunk_line(line)
|
14
|
+
end
|
15
|
+
files
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.file_start?(line)
|
19
|
+
line.start_with?('diff')
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.hunk_start?(line)
|
23
|
+
line.start_with?('@@')
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.parse_file_header(line, enum)
|
27
|
+
enum.next # index ...
|
28
|
+
enum.next # --- ...
|
29
|
+
enum.next # +++ ...
|
30
|
+
File.new(*parse_filenames(line))
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.parse_filenames(line)
|
34
|
+
line.match(%r{a/(.*) b/(.*)$})[1..2]
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.untracked_files(git_status)
|
38
|
+
git_status.lines.select{ |l| l.start_with?('??') }.flat_map do |path|
|
39
|
+
path = path.chomp[3..-1]
|
40
|
+
::File.directory?(path) ? untracked_dir(path) : untracked_file(path)
|
41
|
+
end.compact
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.untracked_file(filename)
|
45
|
+
File.new(filename, filename, type: :untracked).tap do |file|
|
46
|
+
file_lines = ::File.readlines(filename)
|
47
|
+
file << "@@ -0,0 +1,#{file_lines.size} @@"
|
48
|
+
file_lines.each{ |line| file.add_hunk_line("+#{line.chomp}") }
|
49
|
+
file.selected = false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.untracked_dir(path)
|
54
|
+
Dir.glob(::File.join(path, '**/*')).map do |filename|
|
55
|
+
untracked_file(filename)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative 'logger'
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
module GitCrecord
|
5
|
+
module Git
|
6
|
+
def self.stage(files)
|
7
|
+
selected_files = files.select(&:selected)
|
8
|
+
add_files(selected_files.select{ |file| file.type == :untracked })
|
9
|
+
diff = selected_files.map(&:generate_diff).join("\n")
|
10
|
+
_stage(diff).success?
|
11
|
+
end
|
12
|
+
|
13
|
+
def self._stage(diff)
|
14
|
+
cmd = 'git apply --cached --unidiff-zero - '
|
15
|
+
content, status = Open3.capture2e(cmd, stdin_data: diff)
|
16
|
+
LOGGER.info(cmd)
|
17
|
+
LOGGER.info(diff)
|
18
|
+
LOGGER.info(diff.lines.size)
|
19
|
+
LOGGER.info('stdout/stderr:')
|
20
|
+
LOGGER.info(content)
|
21
|
+
LOGGER.info("return code: #{status}")
|
22
|
+
status
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.add_files(files)
|
26
|
+
files.each do |file|
|
27
|
+
success = add_file(file.filename_a)
|
28
|
+
raise "could not add file #{file.filename_a}" unless success
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.add_file(filename)
|
33
|
+
system("git add -N #{filename}")
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.status
|
37
|
+
`git status --porcelain`
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.commit
|
41
|
+
exec('git commit')
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.diff
|
45
|
+
`git diff --no-ext-diff --no-color`
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.toplevel_dir
|
49
|
+
`git rev-parse --show-toplevel`.chomp
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
module GitCrecord
|
4
|
+
module UI
|
5
|
+
module Color
|
6
|
+
MAP = {
|
7
|
+
normal: 1,
|
8
|
+
green: 2,
|
9
|
+
red: 3,
|
10
|
+
hl: 4
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
def self.init
|
14
|
+
Curses.start_color
|
15
|
+
Curses.use_default_colors
|
16
|
+
Curses.init_pair(MAP[:normal], -1, -1)
|
17
|
+
Curses.init_pair(MAP[:green], Curses::COLOR_GREEN, -1)
|
18
|
+
Curses.init_pair(MAP[:red], Curses::COLOR_RED, -1)
|
19
|
+
Curses.init_pair(MAP[:hl], Curses::COLOR_WHITE, Curses::COLOR_GREEN)
|
20
|
+
end
|
21
|
+
|
22
|
+
MAP.each_pair do |name, number|
|
23
|
+
define_singleton_method(name){ Curses.color_pair(number) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
module GitCrecord
|
4
|
+
module UI
|
5
|
+
module HelpWindow
|
6
|
+
CONTENT = <<EOS.freeze
|
7
|
+
q - quit
|
8
|
+
s - stage selection and quit
|
9
|
+
c - commit selection and quit
|
10
|
+
j / ↓ - down
|
11
|
+
k / ↑ - up
|
12
|
+
h / ← - collapse hunk
|
13
|
+
l / → - expand
|
14
|
+
f - toggle fold
|
15
|
+
g - go to first line
|
16
|
+
G - go to last line
|
17
|
+
C-P - up to previous hunk / file
|
18
|
+
C-N - down to previous hunk / file
|
19
|
+
SPACE - toggle selection
|
20
|
+
A - toggle all selections
|
21
|
+
? - display help
|
22
|
+
R - force redraw
|
23
|
+
EOS
|
24
|
+
|
25
|
+
def self.show
|
26
|
+
win = Curses::Window.new(height, width, 0, 0)
|
27
|
+
win.box('|', '-')
|
28
|
+
CONTENT.split("\n").each_with_index do |line, index|
|
29
|
+
win.setpos(index + 1, 1)
|
30
|
+
win.addstr(line)
|
31
|
+
end
|
32
|
+
win.getch
|
33
|
+
win.close
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.width
|
37
|
+
CONTENT.lines.map(&:size).max + 3
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.height
|
41
|
+
CONTENT.lines.size + 2
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'curses'
|
2
|
+
require_relative 'color'
|
3
|
+
require_relative 'help_window'
|
4
|
+
require_relative '../git'
|
5
|
+
require_relative '../quit_action'
|
6
|
+
|
7
|
+
module GitCrecord
|
8
|
+
module UI
|
9
|
+
class HunksWindow
|
10
|
+
def initialize(win, files)
|
11
|
+
@win = win
|
12
|
+
@files = files
|
13
|
+
@visibles = @files
|
14
|
+
@highlighted = @files[0]
|
15
|
+
@scroll_position = 0
|
16
|
+
|
17
|
+
resize
|
18
|
+
end
|
19
|
+
|
20
|
+
def getch
|
21
|
+
@win.getch
|
22
|
+
end
|
23
|
+
|
24
|
+
def width
|
25
|
+
@win.maxx
|
26
|
+
end
|
27
|
+
|
28
|
+
def refresh
|
29
|
+
@win.refresh(scroll_position, 0, 0, 0, Curses.lines - 1, width)
|
30
|
+
end
|
31
|
+
|
32
|
+
def redraw
|
33
|
+
@win.clear
|
34
|
+
print_list(@files)
|
35
|
+
refresh
|
36
|
+
end
|
37
|
+
|
38
|
+
def resize
|
39
|
+
new_width = Curses.cols
|
40
|
+
new_height = [Curses.lines, content_height(new_width)].max
|
41
|
+
return if width == new_width && @win.maxy == new_height
|
42
|
+
@win.resize(new_height, new_width)
|
43
|
+
redraw
|
44
|
+
end
|
45
|
+
|
46
|
+
def content_height(width)
|
47
|
+
@files.reduce(@files.size){ |a, e| a + e.max_height(width) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def scroll_position
|
51
|
+
upper_position = @highlighted.y1 - 3
|
52
|
+
if @scroll_position > upper_position
|
53
|
+
@scroll_position = upper_position
|
54
|
+
elsif @scroll_position <= @highlighted.y2 + 4 - Curses.lines
|
55
|
+
@scroll_position = [@highlighted.y2 + 4, @win.maxy].min - Curses.lines
|
56
|
+
end
|
57
|
+
@scroll_position
|
58
|
+
end
|
59
|
+
|
60
|
+
def move_highlight(to)
|
61
|
+
return if to == @highlighted || to.nil?
|
62
|
+
from = @highlighted
|
63
|
+
@highlighted = to
|
64
|
+
from.print(self, from.y1 - 1, false)
|
65
|
+
to.print(self, to.y1 - 1, true)
|
66
|
+
refresh
|
67
|
+
end
|
68
|
+
|
69
|
+
def addstr(str, y = nil, x = 0, attr: 0, fill: false)
|
70
|
+
@win.setpos(y, x) unless y.nil?
|
71
|
+
@win.attrset(attr)
|
72
|
+
@win.addstr(str)
|
73
|
+
fill_size = width - @win.curx
|
74
|
+
return unless fill && fill_size > 0
|
75
|
+
@win.addstr((fill * fill_size)[0..fill_size])
|
76
|
+
end
|
77
|
+
|
78
|
+
def print_list(list, line_number = -1)
|
79
|
+
list.each do |entry|
|
80
|
+
line_number = entry.print(self, line_number, entry == @highlighted)
|
81
|
+
next unless entry.expanded
|
82
|
+
line_number = print_list(entry.subs, line_number)
|
83
|
+
addstr('', line_number += 1, fill: '_') if entry.is_a?(Diff::File)
|
84
|
+
end
|
85
|
+
line_number
|
86
|
+
end
|
87
|
+
|
88
|
+
def update_visibles
|
89
|
+
@visibles = @files.each_with_object([]) do |entry, vs|
|
90
|
+
vs << entry
|
91
|
+
next unless entry.expanded
|
92
|
+
entry.selectable_subs.each do |entryy|
|
93
|
+
vs << entryy
|
94
|
+
vs.concat(entryy.selectable_subs) if entryy.expanded
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def quit
|
100
|
+
:quit
|
101
|
+
end
|
102
|
+
|
103
|
+
def stage
|
104
|
+
QuitAction.new{ Git.stage(@files) }
|
105
|
+
end
|
106
|
+
|
107
|
+
def commit
|
108
|
+
QuitAction.new{ Git.stage(@files) && Git.commit }
|
109
|
+
end
|
110
|
+
|
111
|
+
def highlight_next
|
112
|
+
move_highlight(@visibles[@visibles.index(@highlighted) + 1])
|
113
|
+
end
|
114
|
+
|
115
|
+
def highlight_previous
|
116
|
+
move_highlight(@visibles[[@visibles.index(@highlighted) - 1, 0].max])
|
117
|
+
end
|
118
|
+
|
119
|
+
def highlight_first
|
120
|
+
move_highlight(@visibles[0])
|
121
|
+
end
|
122
|
+
|
123
|
+
def highlight_last
|
124
|
+
move_highlight(@visibles[-1])
|
125
|
+
end
|
126
|
+
|
127
|
+
def highlight_next_hunk
|
128
|
+
index = @visibles.index(@highlighted)
|
129
|
+
move_highlight(
|
130
|
+
@visibles[(index + 1)..-1].find{ |entry| !entry.subs.empty? }
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
def highlight_previous_hunk
|
135
|
+
index = @visibles.index(@highlighted)
|
136
|
+
move_highlight(
|
137
|
+
@visibles[0...index].reverse_each.find{ |entry| !entry.subs.empty? }
|
138
|
+
)
|
139
|
+
end
|
140
|
+
|
141
|
+
def collapse
|
142
|
+
toggle_fold if !@highlighted.subs.empty? && @highlighted.expanded
|
143
|
+
end
|
144
|
+
|
145
|
+
def expand
|
146
|
+
toggle_fold if !@highlighted.subs.empty? && !@highlighted.expanded
|
147
|
+
end
|
148
|
+
|
149
|
+
def toggle_fold
|
150
|
+
@highlighted.expanded = !@highlighted.expanded
|
151
|
+
update_visibles
|
152
|
+
redraw
|
153
|
+
end
|
154
|
+
|
155
|
+
def toggle_selection
|
156
|
+
@highlighted.selected = !@highlighted.selected
|
157
|
+
redraw
|
158
|
+
end
|
159
|
+
|
160
|
+
def toggle_all_selections
|
161
|
+
new_selected = @files[0].selected == false
|
162
|
+
@files.each{ |file| file.selected = new_selected }
|
163
|
+
redraw
|
164
|
+
end
|
165
|
+
|
166
|
+
def help_window
|
167
|
+
HelpWindow.show
|
168
|
+
refresh
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'curses'
|
2
|
+
require_relative 'ui/color'
|
3
|
+
require_relative 'ui/hunks_window'
|
4
|
+
|
5
|
+
module GitCrecord
|
6
|
+
module UI
|
7
|
+
ACTIONS = {
|
8
|
+
'q' => :quit,
|
9
|
+
's' => :stage,
|
10
|
+
'c' => :commit,
|
11
|
+
'j' => :highlight_next,
|
12
|
+
Curses::KEY_DOWN => :highlight_next,
|
13
|
+
'k' => :highlight_previous,
|
14
|
+
Curses::KEY_UP => :highlight_previous,
|
15
|
+
'h' => :collapse,
|
16
|
+
Curses::KEY_LEFT => :collapse,
|
17
|
+
'l' => :expand,
|
18
|
+
Curses::KEY_RIGHT => :expand,
|
19
|
+
'f' => :toggle_fold,
|
20
|
+
'g' => :highlight_first,
|
21
|
+
'G' => :highlight_last,
|
22
|
+
''.ord => :highlight_next_hunk,
|
23
|
+
''.ord => :highlight_previous_hunk,
|
24
|
+
' ' => :toggle_selection,
|
25
|
+
'A' => :toggle_all_selections,
|
26
|
+
'?' => :help_window,
|
27
|
+
'R' => :redraw,
|
28
|
+
Curses::KEY_RESIZE => :resize
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
def self.run(files)
|
32
|
+
Curses.init_screen.keypad = true
|
33
|
+
Color.init
|
34
|
+
Curses.clear
|
35
|
+
Curses.noecho
|
36
|
+
Curses.curs_set(0)
|
37
|
+
run_loop(HunksWindow.new(Curses::Pad.new(1, 1), files))
|
38
|
+
ensure
|
39
|
+
Curses.close_screen
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.run_loop(win)
|
43
|
+
loop do
|
44
|
+
c = win.getch
|
45
|
+
next if ACTIONS[c].nil?
|
46
|
+
quit = win.send(ACTIONS[c])
|
47
|
+
break quit if quit == :quit
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/git_crecord.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative 'git_crecord/git'
|
2
|
+
require_relative 'git_crecord/diff'
|
3
|
+
require_relative 'git_crecord/ui'
|
4
|
+
require_relative 'git_crecord/version'
|
5
|
+
require_relative 'git_crecord/ui/help_window'
|
6
|
+
|
7
|
+
module GitCrecord
|
8
|
+
def self.main(argv)
|
9
|
+
if argv.include?('--version')
|
10
|
+
puts VERSION
|
11
|
+
true
|
12
|
+
elsif argv.include?('--help') || argv.include?('-h')
|
13
|
+
help
|
14
|
+
true
|
15
|
+
else
|
16
|
+
run(with_untracked_files: !argv.include?('--no-untracked-files'))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.run(with_untracked_files: false)
|
21
|
+
toplevel_dir = Git.toplevel_dir
|
22
|
+
return false if toplevel_dir.empty?
|
23
|
+
Dir.chdir(toplevel_dir) do
|
24
|
+
files = Diff.parse(Git.diff)
|
25
|
+
files.concat(Diff.untracked_files(Git.status)) if with_untracked_files
|
26
|
+
return false if files.empty?
|
27
|
+
result = UI.run(files)
|
28
|
+
return result.call == true if result.respond_to?(:call)
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.help
|
34
|
+
puts <<EOS
|
35
|
+
usage: git crecord [<options>]'
|
36
|
+
|
37
|
+
--no-untracked-files -- ignore untracked files
|
38
|
+
--version -- show version information'
|
39
|
+
-h -- this help message'
|
40
|
+
|
41
|
+
in-program commands:'
|
42
|
+
#{UI::HelpWindow::CONTENT.gsub(/^/, ' ')}
|
43
|
+
EOS
|
44
|
+
end
|
45
|
+
end
|
data/screenshot.jpg
ADDED
Binary file
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
class HunkTest < Minitest::Test
|
4
|
+
include GitCrecord::Diff
|
5
|
+
|
6
|
+
def test_strings
|
7
|
+
hunk = Hunk.new('1234567890' * 5)
|
8
|
+
expected = %w(12345678901 23456789012 34567890123 45678901234 567890)
|
9
|
+
assert_equal(expected, hunk.strings(19))
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_max_height
|
13
|
+
assert_equal(1, Hunk.new('').max_height(10))
|
14
|
+
assert_equal(1, Hunk.new('1234567890').max_height(18))
|
15
|
+
assert_equal(2, Hunk.new('12345678901').max_height(18))
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_parse_header
|
19
|
+
assert_equal([1, 2, 3, 4], Hunk.new('@@ -1,2 +3,4 @@').parse_header)
|
20
|
+
assert_equal([1, 1, 3, 4], Hunk.new('@@ -1 +3,4 @@').parse_header)
|
21
|
+
assert_equal([1, 2, 3, 1], Hunk.new('@@ -1,2 +3 @@').parse_header)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_parse_header_failure
|
25
|
+
hunk = Hunk.new('ugly header')
|
26
|
+
assert_raises(RuntimeError){ hunk.parse_header }
|
27
|
+
end
|
28
|
+
end
|
data/test/system-test.sh
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
#! /usr/bin/env bash
|
2
|
+
|
3
|
+
set -euo pipefail
|
4
|
+
|
5
|
+
function assert-equal() {
|
6
|
+
local expected=$1
|
7
|
+
local actual=$1
|
8
|
+
if test "$expected" != "$actual" ; then
|
9
|
+
cat << 'EOF'
|
10
|
+
expect:
|
11
|
+
$expect
|
12
|
+
but got:
|
13
|
+
$actual
|
14
|
+
____ _
|
15
|
+
| _ \ __ _ _ __ (_) ___
|
16
|
+
| |_) / _` | '_ \| |/ __|
|
17
|
+
| __/ (_| | | | | | (__
|
18
|
+
|_| \__,_|_| |_|_|\___|
|
19
|
+
EOF
|
20
|
+
exit 1
|
21
|
+
fi
|
22
|
+
}
|
23
|
+
|
24
|
+
function assert-diff(){
|
25
|
+
local expected=$1
|
26
|
+
assert-equal "$expected" "$(git diff | grep '^[+-][^+-]')"
|
27
|
+
}
|
28
|
+
|
29
|
+
function assert-status() {
|
30
|
+
local expected=$1
|
31
|
+
assert-equal "$expected" "$(git status -s)"
|
32
|
+
}
|
33
|
+
|
34
|
+
function run-git-crecord(){
|
35
|
+
local keys=$1
|
36
|
+
"$EXECUTABLE" <<<"$keys"
|
37
|
+
}
|
38
|
+
|
39
|
+
readonly HERE="$(dirname "$(readlink -m "${BASH_SOURCE[0]}")")"
|
40
|
+
readonly TEST_DIR=$HERE/../tmp/__test__
|
41
|
+
readonly EXECUTABLE=$HERE/../bin/git-crecord
|
42
|
+
readonly REPO_DIR=$TEST_DIR/repo
|
43
|
+
|
44
|
+
rm -rf "$TEST_DIR"
|
45
|
+
mkdir -p "$TEST_DIR"
|
46
|
+
|
47
|
+
git init "$REPO_DIR" > /dev/null
|
48
|
+
|
49
|
+
pushd "$REPO_DIR" > /dev/null
|
50
|
+
|
51
|
+
touch a_file.txt
|
52
|
+
|
53
|
+
git add a_file.txt
|
54
|
+
git ci -m 'add a_file.txt' > /dev/null
|
55
|
+
|
56
|
+
cat > a_file.txt << 'EOF'
|
57
|
+
This is line 1.
|
58
|
+
This is the second line.
|
59
|
+
This is line 3.
|
60
|
+
This is line 4.
|
61
|
+
EOF
|
62
|
+
|
63
|
+
|
64
|
+
echo "add all -----------------------------------------------------------------"
|
65
|
+
run-git-crecord "s"
|
66
|
+
assert-diff ""
|
67
|
+
|
68
|
+
git reset > /dev/null
|
69
|
+
|
70
|
+
echo "add first line ----------------------------------------------------------"
|
71
|
+
run-git-crecord " lj s"
|
72
|
+
assert-diff "+This is the second line.
|
73
|
+
+This is line 3.
|
74
|
+
+This is line 4."
|
75
|
+
|
76
|
+
git reset > /dev/null
|
77
|
+
|
78
|
+
echo "add another line --------------------------------------------------------"
|
79
|
+
run-git-crecord " ljjj s"
|
80
|
+
assert-diff "+This is line 1.
|
81
|
+
+This is the second line.
|
82
|
+
+This is line 4."
|
83
|
+
|
84
|
+
|
85
|
+
git ci -a -m "add some lines" > /dev/null
|
86
|
+
|
87
|
+
sed -i '' '1,3d' a_file.txt
|
88
|
+
|
89
|
+
echo "delete all lines --------------------------------------------------------"
|
90
|
+
run-git-crecord "s"
|
91
|
+
assert-diff ""
|
92
|
+
|
93
|
+
git reset > /dev/null
|
94
|
+
|
95
|
+
echo "delete one lines --------------------------------------------------------"
|
96
|
+
run-git-crecord " ljj s"
|
97
|
+
assert-diff "-This is line 1.
|
98
|
+
-This is line 3."
|
99
|
+
|
100
|
+
git reset --hard > /dev/null
|
101
|
+
|
102
|
+
# add some more lines:
|
103
|
+
cat >> a_file.txt << 'EOF'
|
104
|
+
|
105
|
+
This is line 5.
|
106
|
+
This is line 6.
|
107
|
+
This is line 7.
|
108
|
+
This is line 8.
|
109
|
+
This is line 9.
|
110
|
+
|
111
|
+
This is line 10.
|
112
|
+
This is line 11.
|
113
|
+
This is line 12.
|
114
|
+
EOF
|
115
|
+
git ci -a -m "add some more lines" > /dev/null
|
116
|
+
|
117
|
+
sed -i '' '2s/.*/This is line 2./' a_file.txt
|
118
|
+
sed -i '' '12s/.*/This is the tenth line./' a_file.txt
|
119
|
+
sed -i '' '13s/.*/This is the eleventh line./' a_file.txt
|
120
|
+
|
121
|
+
echo "multiple hunks ----------------------------------------------------------"
|
122
|
+
run-git-crecord "s"
|
123
|
+
assert-diff ""
|
124
|
+
|
125
|
+
git reset > /dev/null
|
126
|
+
|
127
|
+
echo "add lines of second hunk ------------------------------------------------"
|
128
|
+
run-git-crecord " ljjj sq "
|
129
|
+
assert-diff "-This is the second line.
|
130
|
+
+This is line 2."
|
131
|
+
|
132
|
+
git reset > /dev/null
|
133
|
+
|
134
|
+
echo "add some lines of all hunks ---------------------------------------------"
|
135
|
+
run-git-crecord " ljj jj jj j s"
|
136
|
+
assert-diff "-This is the second line.
|
137
|
+
-This is line 11."
|
138
|
+
|
139
|
+
git reset > /dev/null
|
140
|
+
|
141
|
+
echo "run git-crecord in a subdirectory directory -----------------------------"
|
142
|
+
mkdir -p "$REPO_DIR"/sub
|
143
|
+
pushd "$REPO_DIR"/sub > /dev/null
|
144
|
+
run-git-crecord "s"
|
145
|
+
assert-diff ""
|
146
|
+
popd > /dev/null # "$REPO_DIR"/sub
|
147
|
+
|
148
|
+
git reset > /dev/null
|
149
|
+
|
150
|
+
echo "add a untracked file ----------------------------------------------------"
|
151
|
+
echo "b_file line 1" > b_file.txt
|
152
|
+
run-git-crecord 'AG s'
|
153
|
+
git commit -m "add b_file" > /dev/null
|
154
|
+
assert-diff '-This is the second line.
|
155
|
+
+This is line 2.
|
156
|
+
-This is line 10.
|
157
|
+
-This is line 11.
|
158
|
+
+This is the tenth line.
|
159
|
+
+This is the eleventh line.'
|
160
|
+
|
161
|
+
echo "a not selected file -----------------------------------------------------"
|
162
|
+
echo "b_file line 2" >> b_file.txt
|
163
|
+
run-git-crecord "j s"
|
164
|
+
assert-diff "+b_file line 2"
|
165
|
+
|
166
|
+
echo "add untracked file from untracked directory -----------------------------"
|
167
|
+
echo "a line" > "$REPO_DIR/sub/sub-file.txt"
|
168
|
+
run-git-crecord "AG s"
|
169
|
+
assert-diff "+b_file line 2"
|
170
|
+
assert-status 'M a_file.txt
|
171
|
+
M b_file.txt
|
172
|
+
A sub/sub-file.txt'
|
173
|
+
|
174
|
+
echo "test with +++ line ------------------------------------------------------"
|
175
|
+
echo "++++" >> b_file.txt
|
176
|
+
run-git-crecord "s"
|
177
|
+
assert-diff ""
|
178
|
+
|
179
|
+
popd > /dev/null # $REPO_DIR
|
180
|
+
|
181
|
+
cat << 'EOF'
|
182
|
+
___ _ __
|
183
|
+
/ _ \| |/ /
|
184
|
+
| | | | ' /
|
185
|
+
| |_| | . \
|
186
|
+
\___/|_|\_\
|
187
|
+
EOF
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: git-crecord
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Maik Brendler
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '10.1'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 10.1.1
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '10.1'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 10.1.1
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: minitest
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '5.8'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 5.8.4
|
43
|
+
type: :development
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '5.8'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 5.8.4
|
53
|
+
description: This gem adds the git-crecord command. It provides a curses UI to stage/commit
|
54
|
+
git-hunks.
|
55
|
+
email: maik.brendler@invision.de
|
56
|
+
executables:
|
57
|
+
- git-crecord
|
58
|
+
extensions:
|
59
|
+
- ext/mkrf_conf.rb
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rubocop.yml"
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- bin/git-crecord
|
69
|
+
- ext/mkrf_conf.rb
|
70
|
+
- git-crecord.gemspec
|
71
|
+
- lib/git_crecord.rb
|
72
|
+
- lib/git_crecord/diff.rb
|
73
|
+
- lib/git_crecord/diff/difference.rb
|
74
|
+
- lib/git_crecord/diff/file.rb
|
75
|
+
- lib/git_crecord/diff/hunk.rb
|
76
|
+
- lib/git_crecord/diff/line.rb
|
77
|
+
- lib/git_crecord/git.rb
|
78
|
+
- lib/git_crecord/logger.rb
|
79
|
+
- lib/git_crecord/quit_action.rb
|
80
|
+
- lib/git_crecord/ui.rb
|
81
|
+
- lib/git_crecord/ui/color.rb
|
82
|
+
- lib/git_crecord/ui/help_window.rb
|
83
|
+
- lib/git_crecord/ui/hunks_window.rb
|
84
|
+
- lib/git_crecord/version.rb
|
85
|
+
- screenshot.jpg
|
86
|
+
- test/git_crecord/diff/hunk_test.rb
|
87
|
+
- test/system-test.sh
|
88
|
+
- test/test_helper.rb
|
89
|
+
homepage: https://github.com/mbrendler/git-crecord
|
90
|
+
licenses:
|
91
|
+
- MIT
|
92
|
+
metadata:
|
93
|
+
issue_tracker: https://github.com/mbrendler/git-crecord/issues
|
94
|
+
post_install_message:
|
95
|
+
rdoc_options: []
|
96
|
+
require_paths:
|
97
|
+
- lib
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 2.0.0
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: 1.3.6
|
108
|
+
requirements: []
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 2.5.1
|
111
|
+
signing_key:
|
112
|
+
specification_version: 4
|
113
|
+
summary: Git command to stage/commit hunks the simple way.
|
114
|
+
test_files:
|
115
|
+
- test/git_crecord/diff/hunk_test.rb
|
116
|
+
- test/system-test.sh
|
117
|
+
- test/test_helper.rb
|