git-crecord 1.0.8 → 1.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 +5 -5
- data/.rubocop.yml +8 -15
- data/Gemfile +2 -0
- data/README.md +2 -2
- data/Rakefile +6 -1
- data/bin/git-crecord +2 -0
- data/git-crecord.gemspec +9 -7
- data/lib/git_crecord/diff/difference.rb +18 -3
- data/lib/git_crecord/diff/file.rb +10 -5
- data/lib/git_crecord/diff/hunk.rb +8 -3
- data/lib/git_crecord/diff/line.rb +6 -2
- data/lib/git_crecord/diff.rb +14 -7
- data/lib/git_crecord/git.rb +25 -7
- data/lib/git_crecord/logger.rb +3 -1
- data/lib/git_crecord/quit_action.rb +1 -0
- data/lib/git_crecord/ui/color.rb +3 -1
- data/lib/git_crecord/ui/help_window.rb +4 -2
- data/lib/git_crecord/ui/hunks_window.rb +16 -9
- data/lib/git_crecord/ui.rb +4 -1
- data/lib/git_crecord/version.rb +3 -1
- data/lib/git_crecord.rb +24 -12
- data/test/git_crecord/diff/hunk_test.rb +3 -1
- data/test/system-test.sh +31 -11
- data/test/test_helper.rb +2 -0
- metadata +42 -16
- data/ext/mkrf_conf.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9faaad49aba11b3aad863f3923f1249cd6d83ae88148405bcb0799f6019cb6c5
|
4
|
+
data.tar.gz: 5ee14973f732a9117c37ea52cbc3040cdb4c4331a4342efb8624143121f84d87
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23c240bc80cda76c5aa13bd2d296b1a8f65a40a0364ea5eea0098f51b8af5f69a40f189a67abe8be19ac98f240e6bfd14f432aa051268e33352067305661c7c8
|
7
|
+
data.tar.gz: 8259cb00118d75e7ee7aa49c57ae949197fae66a6cc20e24a1644890fc4691a8049d88834dc91778d9689cd388566526337698e01884b9929314f5772353110d
|
data/.rubocop.yml
CHANGED
@@ -1,20 +1,13 @@
|
|
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
1
|
Documentation:
|
17
2
|
Enabled: false
|
18
3
|
|
4
|
+
Metrics/ClassLength:
|
5
|
+
Enabled: false
|
6
|
+
|
7
|
+
Metrics/BlockLength:
|
8
|
+
Max: 30
|
9
|
+
|
19
10
|
AllCops:
|
11
|
+
DisplayCopNames: true
|
20
12
|
DisplayStyleGuide: true
|
13
|
+
TargetRubyVersion: 2.3
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -15,6 +15,7 @@ $ gem install git-crecord
|
|
15
15
|
```shell
|
16
16
|
$ git crecord
|
17
17
|
$ git crecord --untracked-files # show untracked files
|
18
|
+
$ git crecord --reverse # unstage hunks
|
18
19
|
```
|
19
20
|
|
20
21
|
Key-bindings:
|
@@ -55,6 +56,5 @@ $ ln -s bin/git-crecord /usr/bin/git-crecord
|
|
55
56
|
|
56
57
|
Tests:
|
57
58
|
```shell
|
58
|
-
$ bundle exec rake
|
59
|
-
$ bundle exec rake systemtest
|
59
|
+
$ bundle exec rake
|
60
60
|
```
|
data/Rakefile
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rake'
|
2
4
|
require 'rake/testtask'
|
3
5
|
require 'bundler/gem_tasks'
|
6
|
+
require 'rubocop/rake_task'
|
4
7
|
|
5
8
|
Rake::TestTask.new do |t|
|
6
9
|
t.test_files = FileList['test/git_crecord/**/*test.rb']
|
@@ -11,4 +14,6 @@ task :systemtest do
|
|
11
14
|
sh(File.join(__dir__, 'test/system-test.sh'))
|
12
15
|
end
|
13
16
|
|
14
|
-
|
17
|
+
RuboCop::RakeTask.new
|
18
|
+
|
19
|
+
task default: %i[test systemtest rubocop]
|
data/bin/git-crecord
CHANGED
data/git-crecord.gemspec
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'lib/git_crecord/version'
|
2
4
|
|
3
5
|
GemSpec = Gem::Specification.new do |spec|
|
4
6
|
spec.required_rubygems_version = Gem::Requirement.new('>= 1.3.6')
|
5
7
|
spec.platform = Gem::Platform::RUBY
|
6
|
-
spec.required_ruby_version = '>= 2.
|
8
|
+
spec.required_ruby_version = '>= 2.3.0'
|
7
9
|
spec.name = 'git-crecord'
|
8
10
|
spec.version = GitCrecord::VERSION
|
9
11
|
spec.authors = 'Maik Brendler'
|
@@ -19,14 +21,14 @@ GemSpec = Gem::Specification.new do |spec|
|
|
19
21
|
'issue_tracker' => 'https://github.com/mbrendler/git-crecord/issues'
|
20
22
|
}
|
21
23
|
spec.require_paths = %w[lib]
|
22
|
-
spec.files = `git ls-files`.split($RS).delete_if
|
24
|
+
spec.files = `git ls-files`.split($RS).delete_if do |f|
|
25
|
+
%r{^(spec|test)/} =~ f
|
26
|
+
end
|
23
27
|
spec.test_files = `git ls-files`.split($RS).grep(%r{^(spec|test)/})
|
24
28
|
spec.executables = %w[git-crecord]
|
25
29
|
spec.has_rdoc = false
|
26
|
-
|
27
|
-
# ruby 2.0 includes curses and can't install curses-gem
|
28
|
-
# spec.add_dependency 'curses', '~> 1.0'
|
29
|
-
spec.extensions << 'ext/mkrf_conf.rb'
|
30
|
-
spec.add_development_dependency 'rake', '~> 10.1', '>= 10.1.1'
|
30
|
+
spec.add_dependency 'curses', '~>1.0'
|
31
31
|
spec.add_development_dependency 'minitest', '~> 5.8', '>= 5.8.4'
|
32
|
+
spec.add_development_dependency 'rake', '~> 10.1', '>= 10.1.1'
|
33
|
+
spec.add_development_dependency 'rubocop', '>= 0.56.0'
|
32
34
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative '../ui/color'
|
2
4
|
|
3
5
|
module GitCrecord
|
@@ -12,9 +14,18 @@ module GitCrecord
|
|
12
14
|
false => '[ ] ',
|
13
15
|
:partly => '[~] '
|
14
16
|
}.freeze
|
17
|
+
|
18
|
+
REVERSE_SELECTED_MAP = {
|
19
|
+
true => '[R] ',
|
20
|
+
false => '[X] ',
|
21
|
+
:partly => '[~] '
|
22
|
+
}.freeze
|
23
|
+
|
15
24
|
SELECTION_MARKER_WIDTH = SELECTED_MAP[true].size
|
16
25
|
|
17
|
-
def initialize
|
26
|
+
def initialize(reverse: false)
|
27
|
+
@reverse = reverse
|
28
|
+
@selection_marker_map = reverse ? REVERSE_SELECTED_MAP : SELECTED_MAP
|
18
29
|
@subs = []
|
19
30
|
end
|
20
31
|
|
@@ -44,15 +55,17 @@ module GitCrecord
|
|
44
55
|
def selected
|
45
56
|
s = selectable_subs.map(&:selected).uniq
|
46
57
|
return s[0] if s.size == 1
|
58
|
+
|
47
59
|
:partly
|
48
60
|
end
|
49
61
|
|
50
62
|
def selected=(value)
|
51
|
-
selectable_subs.each{ |sub| sub.selected = value }
|
63
|
+
selectable_subs.each { |sub| sub.selected = value }
|
52
64
|
end
|
53
65
|
|
54
66
|
def style(is_highlighted)
|
55
67
|
return Curses::A_BOLD | UI::Color.hl if is_highlighted
|
68
|
+
|
56
69
|
Curses::A_BOLD | UI::Color.normal
|
57
70
|
end
|
58
71
|
|
@@ -61,7 +74,9 @@ module GitCrecord
|
|
61
74
|
end
|
62
75
|
|
63
76
|
def prefix(line_number)
|
64
|
-
|
77
|
+
show_selection_marker = line_number.zero? && selectable?
|
78
|
+
return @selection_marker_map.fetch(selected) if show_selection_marker
|
79
|
+
|
65
80
|
' ' * SELECTION_MARKER_WIDTH
|
66
81
|
end
|
67
82
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'difference'
|
2
4
|
require_relative 'hunk'
|
3
5
|
require_relative '../ui/color'
|
@@ -8,28 +10,30 @@ module GitCrecord
|
|
8
10
|
attr_reader :filename_a
|
9
11
|
attr_reader :type
|
10
12
|
|
11
|
-
def initialize(filename_a, filename_b, type: :modified)
|
13
|
+
def initialize(filename_a, filename_b, type: :modified, reverse: false)
|
12
14
|
@filename_a = filename_a
|
13
15
|
@filename_b = filename_b
|
14
16
|
@type = type
|
15
17
|
@expanded = false
|
16
|
-
super()
|
18
|
+
super(reverse: reverse)
|
17
19
|
end
|
18
20
|
|
19
21
|
def to_s
|
20
|
-
prefix = {modified: 'M', untracked: '?'}.fetch(type)
|
22
|
+
prefix = { modified: 'M', untracked: '?' }.fetch(type)
|
21
23
|
return "#{prefix} #{@filename_a}" if @filename_a == @filename_b
|
24
|
+
|
22
25
|
"#{prefix} #{filename_a} -> #{filename_b}"
|
23
26
|
end
|
24
27
|
|
25
28
|
def info_string
|
26
|
-
line_count = subs.reduce(0){ |a, e| e.selectable_subs.size + a }
|
29
|
+
line_count = subs.reduce(0) { |a, e| e.selectable_subs.size + a }
|
27
30
|
" #{subs.size} hunk(s), #{line_count} line(s) changed"
|
28
31
|
end
|
29
32
|
|
30
33
|
def strings(width)
|
31
34
|
result = super
|
32
35
|
return result unless expanded
|
36
|
+
|
33
37
|
result += info_string.scan(/.{1,#{content_width(width)}}/)
|
34
38
|
result << ''
|
35
39
|
end
|
@@ -43,7 +47,7 @@ module GitCrecord
|
|
43
47
|
end
|
44
48
|
|
45
49
|
def <<(hunk)
|
46
|
-
subs << Hunk.new(hunk)
|
50
|
+
subs << Hunk.new(hunk, reverse: @reverse)
|
47
51
|
self
|
48
52
|
end
|
49
53
|
|
@@ -53,6 +57,7 @@ module GitCrecord
|
|
53
57
|
|
54
58
|
def generate_diff
|
55
59
|
return unless selected
|
60
|
+
|
56
61
|
[
|
57
62
|
"diff --git a/#{@filename_a} b/#{@filename_b}",
|
58
63
|
"--- a/#{@filename_a}",
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'difference'
|
2
4
|
require_relative 'line'
|
3
5
|
require_relative '../ui/color'
|
@@ -5,10 +7,10 @@ require_relative '../ui/color'
|
|
5
7
|
module GitCrecord
|
6
8
|
module Diff
|
7
9
|
class Hunk < Difference
|
8
|
-
def initialize(head)
|
10
|
+
def initialize(head, reverse: false)
|
9
11
|
@head = head
|
10
12
|
@expanded = true
|
11
|
-
super()
|
13
|
+
super(reverse: reverse)
|
12
14
|
end
|
13
15
|
|
14
16
|
def to_s
|
@@ -20,12 +22,13 @@ module GitCrecord
|
|
20
22
|
end
|
21
23
|
|
22
24
|
def <<(line)
|
23
|
-
subs << Line.new(line)
|
25
|
+
subs << Line.new(line, reverse: @reverse)
|
24
26
|
self
|
25
27
|
end
|
26
28
|
|
27
29
|
def generate_diff
|
28
30
|
return nil unless selected
|
31
|
+
|
29
32
|
[generate_header, *subs.map(&:generate_diff).compact].join("\n")
|
30
33
|
end
|
31
34
|
|
@@ -33,6 +36,7 @@ module GitCrecord
|
|
33
36
|
old_start, old_count, new_start, new_count = parse_header
|
34
37
|
selectable_subs.each do |sub|
|
35
38
|
next if sub.selected
|
39
|
+
|
36
40
|
new_count -= 1 if sub.add?
|
37
41
|
new_count += 1 if sub.del?
|
38
42
|
end
|
@@ -42,6 +46,7 @@ module GitCrecord
|
|
42
46
|
def parse_header
|
43
47
|
match = @head.match(/@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@/)
|
44
48
|
raise "mismatching hunk-header - '#{@head}'" if match.nil?
|
49
|
+
|
45
50
|
[match[1], match[3] || 1, match[4], match[6] || 1].map(&:to_i)
|
46
51
|
end
|
47
52
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'difference'
|
2
4
|
require_relative '../ui/color'
|
3
5
|
|
@@ -40,10 +42,10 @@ module GitCrecord
|
|
40
42
|
class Line < Difference
|
41
43
|
attr_reader :selected
|
42
44
|
|
43
|
-
def initialize(line)
|
45
|
+
def initialize(line, reverse: false)
|
44
46
|
@line = line
|
45
47
|
@selected = true
|
46
|
-
super()
|
48
|
+
super(reverse: reverse)
|
47
49
|
end
|
48
50
|
|
49
51
|
def to_s
|
@@ -77,6 +79,7 @@ module GitCrecord
|
|
77
79
|
def generate_diff
|
78
80
|
return " #{@line[1..-1]}" if !selected && del?
|
79
81
|
return @line if selected
|
82
|
+
|
80
83
|
nil
|
81
84
|
end
|
82
85
|
|
@@ -84,6 +87,7 @@ module GitCrecord
|
|
84
87
|
return UI::Color.hl if is_highlighted
|
85
88
|
return UI::Color.green if add?
|
86
89
|
return UI::Color.red if del?
|
90
|
+
|
87
91
|
UI::Color.normal
|
88
92
|
end
|
89
93
|
end
|
data/lib/git_crecord/diff.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'diff/file'
|
2
4
|
|
3
5
|
module GitCrecord
|
4
6
|
module Diff
|
5
|
-
def self.parse(diff)
|
7
|
+
def self.parse(diff, reverse = false)
|
6
8
|
files = []
|
7
9
|
enum = diff.lines.each
|
8
10
|
loop do
|
9
11
|
line = enum.next
|
10
12
|
line.chomp!
|
11
|
-
next files <<
|
13
|
+
next files << parse_file_head(line, enum, reverse) if file_start?(line)
|
12
14
|
next files[-1] << line if hunk_start?(line)
|
15
|
+
|
13
16
|
files[-1].add_hunk_line(line)
|
14
17
|
end
|
15
18
|
files
|
@@ -23,11 +26,14 @@ module GitCrecord
|
|
23
26
|
line.start_with?('@@')
|
24
27
|
end
|
25
28
|
|
26
|
-
def self.
|
27
|
-
enum.next # index ...
|
29
|
+
def self.parse_file_head(line, enum, reverse)
|
30
|
+
index_line = enum.next # index ... or new ...
|
31
|
+
is_new_file = index_line.start_with?('new')
|
32
|
+
enum.next if is_new_file
|
28
33
|
enum.next # --- ...
|
29
34
|
enum.next # +++ ...
|
30
|
-
|
35
|
+
type = is_new_file ? :untracked : :modified
|
36
|
+
File.new(*parse_filenames(line), type: type, reverse: reverse)
|
31
37
|
end
|
32
38
|
|
33
39
|
def self.parse_filenames(line)
|
@@ -35,7 +41,7 @@ module GitCrecord
|
|
35
41
|
end
|
36
42
|
|
37
43
|
def self.untracked_files(git_status)
|
38
|
-
git_status.lines.select{ |l| l.start_with?('??') }.flat_map do |path|
|
44
|
+
git_status.lines.select { |l| l.start_with?('??') }.flat_map do |path|
|
39
45
|
path = path.chomp[3..-1]
|
40
46
|
::File.directory?(path) ? untracked_dir(path) : untracked_file(path)
|
41
47
|
end.compact
|
@@ -46,7 +52,7 @@ module GitCrecord
|
|
46
52
|
lines, err = file_lines(filename)
|
47
53
|
file << "@@ -0,0 +1,#{lines.size} @@"
|
48
54
|
file.subs[0].subs << PseudoLine.new(err) if lines.empty?
|
49
|
-
lines.each{ |line| file.add_hunk_line("+#{line.chomp}") }
|
55
|
+
lines.each { |line| file.add_hunk_line("+#{line.chomp}") }
|
50
56
|
file.selected = false
|
51
57
|
end
|
52
58
|
end
|
@@ -64,6 +70,7 @@ module GitCrecord
|
|
64
70
|
def self.file_lines(filename)
|
65
71
|
encoding = file_encoding(filename)
|
66
72
|
return [[], 'binary'] if encoding == 'binary'
|
73
|
+
|
67
74
|
[::File.open(filename, "r:#{encoding}", &:readlines), nil]
|
68
75
|
end
|
69
76
|
end
|
data/lib/git_crecord/git.rb
CHANGED
@@ -1,17 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'logger'
|
2
4
|
require 'open3'
|
3
5
|
|
4
6
|
module GitCrecord
|
5
7
|
module Git
|
6
|
-
def self.stage(files)
|
8
|
+
def self.stage(files, reverse = false)
|
7
9
|
selected_files = files.select(&:selected)
|
8
|
-
|
10
|
+
untracked_files = selected_files.select { |file| file.type == :untracked }
|
11
|
+
add_files(untracked_files) unless reverse
|
9
12
|
diff = selected_files.map(&:generate_diff).join("\n")
|
10
|
-
_stage(diff).success?
|
13
|
+
status = _stage(diff, reverse).success?
|
14
|
+
return status unless reverse
|
15
|
+
|
16
|
+
reset_files(untracked_files.select { |file| file.selected == true })
|
17
|
+
true
|
11
18
|
end
|
12
19
|
|
13
|
-
def self._stage(diff)
|
14
|
-
cmd =
|
20
|
+
def self._stage(diff, reverse = false)
|
21
|
+
cmd = "git apply --cached --unidiff-zero #{reverse ? '-R' : ''} - "
|
15
22
|
content, status = Open3.capture2e(cmd, stdin_data: diff)
|
16
23
|
LOGGER.info(cmd)
|
17
24
|
LOGGER.info(diff)
|
@@ -33,6 +40,17 @@ module GitCrecord
|
|
33
40
|
system("git add -N #{filename}")
|
34
41
|
end
|
35
42
|
|
43
|
+
def self.reset_files(files)
|
44
|
+
files.each do |file|
|
45
|
+
success = reset_file(file.filename_a)
|
46
|
+
raise "could not reset file #{file.filename_a}" unless success
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.reset_file(filename)
|
51
|
+
system("git reset -q #{filename}")
|
52
|
+
end
|
53
|
+
|
36
54
|
def self.status
|
37
55
|
`git status --porcelain`
|
38
56
|
end
|
@@ -41,8 +59,8 @@ module GitCrecord
|
|
41
59
|
exec('git commit')
|
42
60
|
end
|
43
61
|
|
44
|
-
def self.diff
|
45
|
-
`git diff --no-ext-diff --no-color`
|
62
|
+
def self.diff(staged: false)
|
63
|
+
`git diff --no-ext-diff --no-color #{staged ? '--staged' : ''}`
|
46
64
|
end
|
47
65
|
|
48
66
|
def self.toplevel_dir
|
data/lib/git_crecord/logger.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'logger'
|
2
4
|
|
3
5
|
module GitCrecord
|
4
6
|
LOGGER = Logger.new(File.new(File.join(ENV['HOME'], '.git-crecord.log'), 'w'))
|
5
|
-
LOGGER.formatter = proc{ |_severity, _datetime, _progname, msg| "#{msg}\n" }
|
7
|
+
LOGGER.formatter = proc { |_severity, _datetime, _progname, msg| "#{msg}\n" }
|
6
8
|
LOGGER.level = Logger::INFO
|
7
9
|
end
|
data/lib/git_crecord/ui/color.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'curses'
|
2
4
|
|
3
5
|
module GitCrecord
|
@@ -20,7 +22,7 @@ module GitCrecord
|
|
20
22
|
end
|
21
23
|
|
22
24
|
MAP.each_pair do |name, number|
|
23
|
-
define_singleton_method(name){ Curses.color_pair(number) }
|
25
|
+
define_singleton_method(name) { Curses.color_pair(number) }
|
24
26
|
end
|
25
27
|
end
|
26
28
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'curses'
|
2
4
|
|
3
5
|
module GitCrecord
|
4
6
|
module UI
|
5
7
|
module HelpWindow
|
6
|
-
CONTENT = <<
|
8
|
+
CONTENT = <<HELP
|
7
9
|
q - quit
|
8
10
|
s - stage selection and quit
|
9
11
|
c - commit selection and quit
|
@@ -20,7 +22,7 @@ module GitCrecord
|
|
20
22
|
A - toggle all selections
|
21
23
|
? - display help
|
22
24
|
R - force redraw
|
23
|
-
|
25
|
+
HELP
|
24
26
|
|
25
27
|
def self.show
|
26
28
|
win = Curses::Window.new(height, width, 0, 0)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'curses'
|
2
4
|
require_relative 'color'
|
3
5
|
require_relative 'help_window'
|
@@ -39,12 +41,13 @@ module GitCrecord
|
|
39
41
|
new_width = Curses.cols
|
40
42
|
new_height = [Curses.lines, content_height(new_width)].max
|
41
43
|
return if width == new_width && @win.maxy == new_height
|
44
|
+
|
42
45
|
@win.resize(new_height, new_width)
|
43
46
|
redraw
|
44
47
|
end
|
45
48
|
|
46
49
|
def content_height(width)
|
47
|
-
@files.reduce(@files.size){ |a, e| a + e.max_height(width) }
|
50
|
+
@files.reduce(@files.size) { |a, e| a + e.max_height(width) }
|
48
51
|
end
|
49
52
|
|
50
53
|
def scroll_position
|
@@ -59,6 +62,7 @@ module GitCrecord
|
|
59
62
|
|
60
63
|
def move_highlight(to)
|
61
64
|
return if to == @highlighted || to.nil?
|
65
|
+
|
62
66
|
from = @highlighted
|
63
67
|
@highlighted = to
|
64
68
|
from.print(self, from.y1 - 1, false)
|
@@ -66,12 +70,13 @@ module GitCrecord
|
|
66
70
|
refresh
|
67
71
|
end
|
68
72
|
|
69
|
-
def addstr(str,
|
70
|
-
@win.setpos(
|
73
|
+
def addstr(str, y_pos = nil, x_pos = 0, attr: 0, fill: false)
|
74
|
+
@win.setpos(y_pos, x_pos) unless y_pos.nil?
|
71
75
|
@win.attrset(attr)
|
72
76
|
@win.addstr(str)
|
73
77
|
fill_size = width - @win.curx
|
74
|
-
return unless fill && fill_size
|
78
|
+
return unless fill && fill_size.positive?
|
79
|
+
|
75
80
|
@win.addstr((fill * fill_size)[0..fill_size])
|
76
81
|
end
|
77
82
|
|
@@ -79,6 +84,7 @@ module GitCrecord
|
|
79
84
|
list.each do |entry|
|
80
85
|
line_number = entry.print(self, line_number, entry == @highlighted)
|
81
86
|
next unless entry.expanded
|
87
|
+
|
82
88
|
line_number = print_list(entry.subs, line_number)
|
83
89
|
addstr('', line_number += 1, fill: '_') if entry.is_a?(Diff::File)
|
84
90
|
end
|
@@ -89,6 +95,7 @@ module GitCrecord
|
|
89
95
|
@visibles = @files.each_with_object([]) do |entry, vs|
|
90
96
|
vs << entry
|
91
97
|
next unless entry.expanded
|
98
|
+
|
92
99
|
entry.selectable_subs.each do |entryy|
|
93
100
|
vs << entryy
|
94
101
|
vs.concat(entryy.selectable_subs) if entryy.expanded
|
@@ -101,11 +108,11 @@ module GitCrecord
|
|
101
108
|
end
|
102
109
|
|
103
110
|
def stage
|
104
|
-
QuitAction.new{ Git.stage(@files) }
|
111
|
+
QuitAction.new { |reverse| Git.stage(@files, reverse) }
|
105
112
|
end
|
106
113
|
|
107
114
|
def commit
|
108
|
-
QuitAction.new{ Git.stage(@files) && Git.commit }
|
115
|
+
QuitAction.new { |reverse| Git.stage(@files, reverse) && Git.commit }
|
109
116
|
end
|
110
117
|
|
111
118
|
def highlight_next
|
@@ -127,14 +134,14 @@ module GitCrecord
|
|
127
134
|
def highlight_next_hunk
|
128
135
|
index = @visibles.index(@highlighted)
|
129
136
|
move_highlight(
|
130
|
-
@visibles[(index + 1)..-1].find{ |entry| !entry.subs.empty? }
|
137
|
+
@visibles[(index + 1)..-1].find { |entry| !entry.subs.empty? }
|
131
138
|
)
|
132
139
|
end
|
133
140
|
|
134
141
|
def highlight_previous_hunk
|
135
142
|
index = @visibles.index(@highlighted)
|
136
143
|
move_highlight(
|
137
|
-
@visibles[0...index].reverse_each.find{ |entry| !entry.subs.empty? }
|
144
|
+
@visibles[0...index].reverse_each.find { |entry| !entry.subs.empty? }
|
138
145
|
)
|
139
146
|
end
|
140
147
|
|
@@ -159,7 +166,7 @@ module GitCrecord
|
|
159
166
|
|
160
167
|
def toggle_all_selections
|
161
168
|
new_selected = @files[0].selected == false
|
162
|
-
@files.each{ |file| file.selected = new_selected }
|
169
|
+
@files.each { |file| file.selected = new_selected }
|
163
170
|
redraw
|
164
171
|
end
|
165
172
|
|
data/lib/git_crecord/ui.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'curses'
|
2
4
|
require_relative 'ui/color'
|
3
5
|
require_relative 'ui/hunks_window'
|
@@ -34,7 +36,7 @@ module GitCrecord
|
|
34
36
|
Curses.clear
|
35
37
|
Curses.noecho
|
36
38
|
Curses.curs_set(0)
|
37
|
-
pad = Curses::Pad.new(1, 1).tap{ |p| p.keypad = true }
|
39
|
+
pad = Curses::Pad.new(1, 1).tap { |p| p.keypad = true }
|
38
40
|
run_loop(HunksWindow.new(pad, files))
|
39
41
|
ensure
|
40
42
|
Curses.close_screen
|
@@ -44,6 +46,7 @@ module GitCrecord
|
|
44
46
|
loop do
|
45
47
|
c = win.getch
|
46
48
|
next if ACTIONS[c].nil?
|
49
|
+
|
47
50
|
quit = win.send(ACTIONS[c])
|
48
51
|
break quit if quit == :quit
|
49
52
|
end
|
data/lib/git_crecord/version.rb
CHANGED
data/lib/git_crecord.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'git_crecord/diff'
|
2
4
|
require_relative 'git_crecord/git'
|
3
5
|
require_relative 'git_crecord/ui'
|
@@ -13,37 +15,47 @@ module GitCrecord
|
|
13
15
|
help
|
14
16
|
true
|
15
17
|
else
|
16
|
-
run(with_untracked_files: untracked_files?(argv))
|
18
|
+
run(with_untracked_files: untracked_files?(argv), reverse: reverse?(argv))
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
20
22
|
def self.untracked_files?(argv)
|
23
|
+
return false if reverse?(argv)
|
24
|
+
|
21
25
|
argv.include?('--untracked-files') || argv.include?('-u')
|
22
26
|
end
|
23
27
|
|
24
|
-
def self.
|
28
|
+
def self.reverse?(argv)
|
29
|
+
argv.include?('--reverse') || argv.include?('-R')
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.run(with_untracked_files: false, reverse: false)
|
25
33
|
toplevel_dir = Git.toplevel_dir
|
26
34
|
return false if toplevel_dir.empty?
|
35
|
+
|
27
36
|
Dir.chdir(toplevel_dir) do
|
28
|
-
files = Diff.parse(Git.diff)
|
37
|
+
files = Diff.parse(Git.diff(staged: reverse), reverse)
|
29
38
|
files.concat(Diff.untracked_files(Git.status)) if with_untracked_files
|
30
39
|
return false if files.empty?
|
40
|
+
|
31
41
|
result = UI.run(files)
|
32
|
-
return result.call == true if result.respond_to?(:call)
|
42
|
+
return result.call(reverse) == true if result.respond_to?(:call)
|
43
|
+
|
33
44
|
true
|
34
45
|
end
|
35
46
|
end
|
36
47
|
|
37
48
|
def self.help
|
38
|
-
puts
|
39
|
-
usage: git crecord [<options>]
|
49
|
+
puts <<~HELP
|
50
|
+
usage: git crecord [<options>]
|
40
51
|
|
41
|
-
|
42
|
-
|
43
|
-
|
52
|
+
-u, --untracked-files -- show untracked files
|
53
|
+
-R, --reverse -- unstage hunks
|
54
|
+
--version -- show version information
|
55
|
+
-h -- this help message
|
44
56
|
|
45
|
-
|
46
|
-
#{UI::HelpWindow::CONTENT.gsub(/^/, ' ')}
|
47
|
-
|
57
|
+
in-program commands:
|
58
|
+
#{UI::HelpWindow::CONTENT.gsub(/^/, ' ')}
|
59
|
+
HELP
|
48
60
|
end
|
49
61
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative '../../test_helper'
|
2
4
|
|
3
5
|
class HunkTest < Minitest::Test
|
@@ -23,6 +25,6 @@ class HunkTest < Minitest::Test
|
|
23
25
|
|
24
26
|
def test_parse_header_failure
|
25
27
|
hunk = Hunk.new('ugly header')
|
26
|
-
assert_raises(RuntimeError){ hunk.parse_header }
|
28
|
+
assert_raises(RuntimeError) { hunk.parse_header }
|
27
29
|
end
|
28
30
|
end
|
data/test/system-test.sh
CHANGED
@@ -4,13 +4,15 @@ set -euo pipefail
|
|
4
4
|
|
5
5
|
function assert-equal() {
|
6
6
|
local expected=$1
|
7
|
-
local actual=$
|
7
|
+
local actual=$2
|
8
8
|
if test "$expected" != "$actual" ; then
|
9
|
-
cat <<
|
9
|
+
cat << EOF
|
10
10
|
expect:
|
11
|
-
$
|
11
|
+
$expected
|
12
12
|
but got:
|
13
13
|
$actual
|
14
|
+
EOF
|
15
|
+
cat << 'EOF'
|
14
16
|
____ _
|
15
17
|
| _ \ __ _ _ __ (_) ___
|
16
18
|
| |_) / _` | '_ \| |/ __|
|
@@ -31,9 +33,14 @@ function assert-status() {
|
|
31
33
|
assert-equal "$expected" "$(git status -s)"
|
32
34
|
}
|
33
35
|
|
34
|
-
function run-git-crecord(){
|
36
|
+
function run-git-crecord() {
|
35
37
|
local keys=$1
|
36
|
-
"$EXECUTABLE" -u <<<"$keys"
|
38
|
+
"$EXECUTABLE" -u "$@" <<<"$keys"
|
39
|
+
}
|
40
|
+
|
41
|
+
function run-git-crecord-reverse() {
|
42
|
+
local keys=$1
|
43
|
+
"$EXECUTABLE" -R <<<"$keys"
|
37
44
|
}
|
38
45
|
|
39
46
|
readonly HERE="$(dirname "$(readlink -m "${BASH_SOURCE[0]}")")"
|
@@ -68,7 +75,7 @@ assert-diff ""
|
|
68
75
|
git reset > /dev/null
|
69
76
|
|
70
77
|
echo "add first line ----------------------------------------------------------"
|
71
|
-
run-git-crecord "
|
78
|
+
run-git-crecord " ljj s"
|
72
79
|
assert-diff "+This is the second line.
|
73
80
|
+This is line 3.
|
74
81
|
+This is line 4."
|
@@ -76,7 +83,7 @@ assert-diff "+This is the second line.
|
|
76
83
|
git reset > /dev/null
|
77
84
|
|
78
85
|
echo "add another line --------------------------------------------------------"
|
79
|
-
run-git-crecord "
|
86
|
+
run-git-crecord " ljjjj s"
|
80
87
|
assert-diff "+This is line 1.
|
81
88
|
+This is the second line.
|
82
89
|
+This is line 4."
|
@@ -92,8 +99,8 @@ assert-diff ""
|
|
92
99
|
|
93
100
|
git reset > /dev/null
|
94
101
|
|
95
|
-
echo "delete one
|
96
|
-
run-git-crecord "
|
102
|
+
echo "delete one line ---------------------------------------------------------"
|
103
|
+
run-git-crecord " ljjj s"
|
97
104
|
assert-diff "-This is line 1.
|
98
105
|
-This is line 3."
|
99
106
|
|
@@ -125,14 +132,14 @@ assert-diff ""
|
|
125
132
|
git reset > /dev/null
|
126
133
|
|
127
134
|
echo "add lines of second hunk ------------------------------------------------"
|
128
|
-
run-git-crecord "
|
135
|
+
run-git-crecord " ljjjj sq "
|
129
136
|
assert-diff "-This is the second line.
|
130
137
|
+This is line 2."
|
131
138
|
|
132
139
|
git reset > /dev/null
|
133
140
|
|
134
141
|
echo "add some lines of all hunks ---------------------------------------------"
|
135
|
-
run-git-crecord "
|
142
|
+
run-git-crecord " ljjj jj jj j s"
|
136
143
|
assert-diff "-This is the second line.
|
137
144
|
-This is line 11."
|
138
145
|
|
@@ -176,6 +183,19 @@ echo "++++" >> b_file.txt
|
|
176
183
|
run-git-crecord "s"
|
177
184
|
assert-diff ""
|
178
185
|
|
186
|
+
echo "test unstage ------------------------------------------------------------"
|
187
|
+
echo new line1 > new.txt
|
188
|
+
echo new line2 >> new.txt
|
189
|
+
git add new.txt
|
190
|
+
run-git-crecord-reverse 'j k lj Gkljj s'
|
191
|
+
assert-status 'MM a_file.txt
|
192
|
+
M b_file.txt
|
193
|
+
AM new.txt
|
194
|
+
?? sub/'
|
195
|
+
assert-diff '-This is the second line.
|
196
|
+
+This is line 2.
|
197
|
+
+new line2'
|
198
|
+
|
179
199
|
popd > /dev/null # $REPO_DIR
|
180
200
|
|
181
201
|
cat << 'EOF'
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,49 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: git-crecord
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maik Brendler
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-11-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: curses
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.8'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 5.8.4
|
37
|
+
type: :development
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '5.8'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 5.8.4
|
13
47
|
- !ruby/object:Gem::Dependency
|
14
48
|
name: rake
|
15
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -31,32 +65,25 @@ dependencies:
|
|
31
65
|
- !ruby/object:Gem::Version
|
32
66
|
version: 10.1.1
|
33
67
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
68
|
+
name: rubocop
|
35
69
|
requirement: !ruby/object:Gem::Requirement
|
36
70
|
requirements:
|
37
|
-
- - "~>"
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: '5.8'
|
40
71
|
- - ">="
|
41
72
|
- !ruby/object:Gem::Version
|
42
|
-
version:
|
73
|
+
version: 0.56.0
|
43
74
|
type: :development
|
44
75
|
prerelease: false
|
45
76
|
version_requirements: !ruby/object:Gem::Requirement
|
46
77
|
requirements:
|
47
|
-
- - "~>"
|
48
|
-
- !ruby/object:Gem::Version
|
49
|
-
version: '5.8'
|
50
78
|
- - ">="
|
51
79
|
- !ruby/object:Gem::Version
|
52
|
-
version:
|
80
|
+
version: 0.56.0
|
53
81
|
description: This gem adds the git-crecord command. It provides a curses UI to stage/commit
|
54
82
|
git-hunks.
|
55
83
|
email: maik.brendler@invision.de
|
56
84
|
executables:
|
57
85
|
- git-crecord
|
58
|
-
extensions:
|
59
|
-
- ext/mkrf_conf.rb
|
86
|
+
extensions: []
|
60
87
|
extra_rdoc_files: []
|
61
88
|
files:
|
62
89
|
- ".gitignore"
|
@@ -66,7 +93,6 @@ files:
|
|
66
93
|
- README.md
|
67
94
|
- Rakefile
|
68
95
|
- bin/git-crecord
|
69
|
-
- ext/mkrf_conf.rb
|
70
96
|
- git-crecord.gemspec
|
71
97
|
- lib/git_crecord.rb
|
72
98
|
- lib/git_crecord/diff.rb
|
@@ -99,7 +125,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
99
125
|
requirements:
|
100
126
|
- - ">="
|
101
127
|
- !ruby/object:Gem::Version
|
102
|
-
version: 2.
|
128
|
+
version: 2.3.0
|
103
129
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
130
|
requirements:
|
105
131
|
- - ">="
|
@@ -107,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
133
|
version: 1.3.6
|
108
134
|
requirements: []
|
109
135
|
rubyforge_project:
|
110
|
-
rubygems_version: 2.6
|
136
|
+
rubygems_version: 2.7.6
|
111
137
|
signing_key:
|
112
138
|
specification_version: 4
|
113
139
|
summary: Git command to stage/commit hunks the simple way.
|
data/ext/mkrf_conf.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rubygems/dependency_installer'
|
3
|
-
|
4
|
-
# This is a hack to not install curses for ruby-2.0.
|
5
|
-
|
6
|
-
di = Gem::DependencyInstaller.new
|
7
|
-
|
8
|
-
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.1.0')
|
9
|
-
puts 'install curses'
|
10
|
-
di.install 'curses', '~>1.0'
|
11
|
-
end
|
12
|
-
|
13
|
-
File.open(File.join(__dir__, 'Rakefile'), 'w') do |f|
|
14
|
-
f.write("task :default#{$/}")
|
15
|
-
end
|