git_spelunk 0.1.2
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.
- data/bin/git-spelunk +6 -0
- data/lib/git_spelunk/file.rb +25 -0
- data/lib/git_spelunk/file_context.rb +83 -0
- data/lib/git_spelunk/offset.rb +142 -0
- data/lib/git_spelunk/ui/pager.rb +153 -0
- data/lib/git_spelunk/ui/repo.rb +56 -0
- data/lib/git_spelunk/ui/window.rb +19 -0
- data/lib/git_spelunk/ui.rb +158 -0
- data/lib/git_spelunk.rb +4 -0
- metadata +74 -0
data/bin/git-spelunk
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'grit'
|
2
|
+
|
3
|
+
module GitSpelunk
|
4
|
+
class File
|
5
|
+
def initialize(filename, sha, line=1)
|
6
|
+
@filename = filename
|
7
|
+
@sha = sha
|
8
|
+
@line = line
|
9
|
+
end
|
10
|
+
|
11
|
+
def blame
|
12
|
+
[
|
13
|
+
["abcdef", "content"],
|
14
|
+
["abcdef", "content"],
|
15
|
+
["abcdef", "content"],
|
16
|
+
["abcdef", "content"],
|
17
|
+
["abcdef", "content"]
|
18
|
+
["abcdef", "content"]
|
19
|
+
["abcdef", "content"]
|
20
|
+
["abcdef", "content"]
|
21
|
+
["abcdef", "content"]
|
22
|
+
]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'grit'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module GitSpelunk
|
5
|
+
class FileContext
|
6
|
+
attr_accessor :line_number
|
7
|
+
attr_reader :repo
|
8
|
+
|
9
|
+
def initialize(file, options = {})
|
10
|
+
@sha = options[:sha] || 'HEAD'
|
11
|
+
@line_number = options[:line_number] || 1
|
12
|
+
|
13
|
+
@repo = options.fetch(:repo) do
|
14
|
+
repo_directory = find_repo_from_file(file)
|
15
|
+
@file = file.sub(%r{^#{repo_directory}/}, '')
|
16
|
+
Grit::Repo.new(repo_directory)
|
17
|
+
end
|
18
|
+
|
19
|
+
@file ||= options.fetch(:file)
|
20
|
+
@commit_cache = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def clone_for_parent_sha(line_number)
|
25
|
+
new_sha = sha_for_line(line_number) + "~1"
|
26
|
+
GitSpelunk::FileContext.new(@file, {:sha => new_sha, :repo => @repo, :file => @file})
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_line_for_sha_parent(line_number)
|
30
|
+
o = GitSpelunk::Offset.new(@repo, @file, sha_for_line(line_number))
|
31
|
+
o.line_number_to_parent(@new_to_old[line_number])
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_repo_from_file(file)
|
35
|
+
file = './' + file unless file.start_with?('/')
|
36
|
+
targets = file.split('/')
|
37
|
+
targets.pop
|
38
|
+
while !File.directory?(targets.join("/") + "/.git")
|
39
|
+
targets.pop
|
40
|
+
end
|
41
|
+
|
42
|
+
if targets.empty?
|
43
|
+
nil
|
44
|
+
else
|
45
|
+
targets.join("/")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_blame
|
50
|
+
@blame_data ||= begin
|
51
|
+
@new_to_old = {}
|
52
|
+
@line_to_sha = {}
|
53
|
+
blame = Grit::Blame.new(@repo, @file, @sha)
|
54
|
+
blame.lines.map do |line|
|
55
|
+
@new_to_old[line.lineno] = line.oldlineno
|
56
|
+
[line.commit.id_abbrev, line.line]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
@blame_data
|
60
|
+
end
|
61
|
+
|
62
|
+
def sha_for_line(line)
|
63
|
+
@blame_data[line - 1][0]
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_line_commit_info(line)
|
67
|
+
get_blame
|
68
|
+
abbrev = sha_for_line(line)
|
69
|
+
commit = (@commit_cache[abbrev] ||= @repo.commit(abbrev))
|
70
|
+
return nil unless commit
|
71
|
+
|
72
|
+
author_info = commit.author_string.split(" ")
|
73
|
+
tz = author_info.pop
|
74
|
+
utc = Time.at(author_info.pop.to_i)
|
75
|
+
[
|
76
|
+
"commit " + commit.id,
|
77
|
+
"Author: " + author_info.join(" "),
|
78
|
+
"Date: " + utc.to_s
|
79
|
+
].join("\n") + "\n\n" + " " + commit.short_message
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# Given a sha and a line_number in this sha, this module calculates the corresponding line number in sha's parent.
|
2
|
+
# 1. It uses git diff for the sha & its parent to get all the diff chunks.
|
3
|
+
# 2. It then calculates which chunk given line_number belongs to.
|
4
|
+
# 3. Once found the target chunk, it then goes to the sha's line_number in the diff
|
5
|
+
# 4. It then calculate parent's line number by ignoring changes for sha in the diff
|
6
|
+
#
|
7
|
+
# git diff 6d405155..379120f
|
8
|
+
# --- a/app/assets/javascripts/lib/user_assume/chat_extension.module.js
|
9
|
+
# +++ b/app/assets/javascripts/lib/user_assume/chat_extension.module.js
|
10
|
+
# @@ -1,7 +1,6 @@
|
11
|
+
# -/*globals ChatLotus*/
|
12
|
+
# module.exports = Em.Object.extend({
|
13
|
+
# - chatService: ChatLotus.Service,
|
14
|
+
# - hasChatEnabled: Em.computed.oneWay('chatService.hasChatEnabled'),
|
15
|
+
# + ChatService: ChatLotus.Service,
|
16
|
+
# + hasChatEnabled: Em.computed.oneWay('ChatService.hasChatEnabled'),
|
17
|
+
# previousAvailablity: false,
|
18
|
+
#
|
19
|
+
# detach: function() {
|
20
|
+
# @@ -19,10 +18,10 @@ module.exports = Em.Object.extend({
|
21
|
+
# },
|
22
|
+
#
|
23
|
+
# _isChatAvailable: function() {
|
24
|
+
# - return this.get('chatService.Availability.available');
|
25
|
+
# + return this.getPath('ChatService.Availability.available');
|
26
|
+
# },
|
27
|
+
#
|
28
|
+
# _toggleChatAvailability: function() {
|
29
|
+
# - this.get('chatService.Availability').toggleAvailability();
|
30
|
+
# + this.getPath('ChatService.Availability').toggleAvailability();
|
31
|
+
# }
|
32
|
+
# });
|
33
|
+
|
34
|
+
|
35
|
+
module GitSpelunk
|
36
|
+
require 'grit'
|
37
|
+
|
38
|
+
class Offset
|
39
|
+
attr_reader :repo, :file_name, :sha, :chunks
|
40
|
+
|
41
|
+
def initialize(repo, file_name, sha)
|
42
|
+
@repo = repo
|
43
|
+
@file_name = file_name
|
44
|
+
@sha = sha
|
45
|
+
parent_sha = @repo.commits(@sha)[0].parents[0].id
|
46
|
+
@chunks = diff_chunks(@repo.diff(parent_sha, @sha, @file_name))
|
47
|
+
end
|
48
|
+
|
49
|
+
def diff_chunks(diffs)
|
50
|
+
return nil if diffs.empty?
|
51
|
+
# split it into chunks: [["@@ -10,13 +10,18 @@", diffs], ["@@ -20,13 +20,18 @@", diffs, diff]]
|
52
|
+
multiple_chunks = diffs[0].diff.split(/(@@.*?@@.*?\n)/)
|
53
|
+
# Discard file name line
|
54
|
+
multiple_chunks[1..multiple_chunks.length].each_slice(2).to_a
|
55
|
+
end
|
56
|
+
|
57
|
+
def line_number_to_parent(src_line_number)
|
58
|
+
return nil unless @chunks
|
59
|
+
chunk = target_chunk(src_line_number)
|
60
|
+
chunk_starting_line, chunk_total_lines = src_start_and_total(stats_line(chunk))
|
61
|
+
parent_starting_line = parent_start_and_total(stats_line(chunk))[0]
|
62
|
+
parent_line_offset = find_parent_line_number(diff_lines(chunk), src_line_number, chunk_starting_line, chunk_total_lines)
|
63
|
+
parent_starting_line + parent_line_offset
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def target_chunk(line_number)
|
69
|
+
chunks.select {|chunk| has_line?(chunk, line_number)}[0]
|
70
|
+
end
|
71
|
+
|
72
|
+
def has_line?(chunk, line_number)
|
73
|
+
starting_line, total_lines = src_start_and_total(stats_line(chunk))
|
74
|
+
starting_line + total_lines >= line_number
|
75
|
+
end
|
76
|
+
|
77
|
+
def src_start_and_total(line)
|
78
|
+
# Get the offset and line number where lines were added
|
79
|
+
# @@ -3,10 +3,17 @@ optionally a line\n unchnaged_line_1\n- deleted_line_1\n+ new_line_1"
|
80
|
+
line.scan(/\+(.*)@@/)[0][0].split(",").map {|str| str.to_i}
|
81
|
+
end
|
82
|
+
|
83
|
+
def parent_start_and_total(line)
|
84
|
+
line.scan(/\-(.*)\+/)[0][0].split(",").map {|str| str.to_i}
|
85
|
+
end
|
86
|
+
|
87
|
+
def find_parent_line_number(lines, src_line_number, src_starting_line, src_number_of_lines)
|
88
|
+
target_line_offset = src_line_number - src_starting_line
|
89
|
+
current_line_offset = parent_line_offset = diff_index = 0
|
90
|
+
|
91
|
+
lines.each do |line|
|
92
|
+
break if current_line_offset == target_line_offset
|
93
|
+
|
94
|
+
if src_line?(line)
|
95
|
+
current_line_offset += 1
|
96
|
+
end
|
97
|
+
|
98
|
+
if parent_line?(line)
|
99
|
+
parent_line_offset += 1
|
100
|
+
end
|
101
|
+
|
102
|
+
diff_index += 1
|
103
|
+
end
|
104
|
+
# find last contiguous bit of diff
|
105
|
+
line = lines[diff_index]
|
106
|
+
removals = additions = 0
|
107
|
+
|
108
|
+
while ["-", "+"].include?(line[0])
|
109
|
+
if parent_line?(line)
|
110
|
+
removals += 1
|
111
|
+
else
|
112
|
+
additions += 1
|
113
|
+
end
|
114
|
+
|
115
|
+
diff_index -= 1
|
116
|
+
line = lines[diff_index]
|
117
|
+
end
|
118
|
+
|
119
|
+
forward_push = current_line_offset - additions
|
120
|
+
forward_push = removals if forward_push > removals # clamp line matching
|
121
|
+
parent_line_offset - removals + forward_push
|
122
|
+
end
|
123
|
+
|
124
|
+
def src_line?(line)
|
125
|
+
# Src line will either have a "+" or will be an unchanged line
|
126
|
+
line[0] != '-'
|
127
|
+
end
|
128
|
+
|
129
|
+
def parent_line?(line)
|
130
|
+
# Src line will either have a "-" or will be an unchanged line
|
131
|
+
line[0] != '+'
|
132
|
+
end
|
133
|
+
|
134
|
+
def stats_line(chunk)
|
135
|
+
chunk[0]
|
136
|
+
end
|
137
|
+
|
138
|
+
def diff_lines(chunk)
|
139
|
+
chunk[1].split("\n")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
ACTIVE_SHA_COLOR=1
|
4
|
+
module GitSpelunk
|
5
|
+
class UI
|
6
|
+
class PagerWindow < Window
|
7
|
+
def initialize(height)
|
8
|
+
@window = Curses::Window.new(height, Curses.cols, 0, 0)
|
9
|
+
@height = height
|
10
|
+
@cursor = 1
|
11
|
+
@top = 1
|
12
|
+
@highlight_sha = true
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :data, :highlight_sha
|
16
|
+
attr_reader :cursor, :top
|
17
|
+
|
18
|
+
def draw
|
19
|
+
@window.clear
|
20
|
+
@window.setpos(0,0)
|
21
|
+
line_number_width = (data.size + 1).to_s.size
|
22
|
+
|
23
|
+
active_sha = data[@cursor - 1][0]
|
24
|
+
|
25
|
+
data[@top - 1,@height].each_with_index do |b, i|
|
26
|
+
sha, content = *b
|
27
|
+
line_number = i + @top
|
28
|
+
|
29
|
+
if sha == active_sha && highlight_sha
|
30
|
+
@window.attron(Curses::color_pair(ACTIVE_SHA_COLOR))
|
31
|
+
end
|
32
|
+
|
33
|
+
if @cursor == line_number
|
34
|
+
with_highlighting { @window.addstr(sha) }
|
35
|
+
else
|
36
|
+
@window.addstr(sha)
|
37
|
+
end
|
38
|
+
|
39
|
+
@window.addstr(" %*s " % [line_number_width, line_number])
|
40
|
+
if @search_term
|
41
|
+
content.split(/(#{@search_term})/).each do |t|
|
42
|
+
if t == @search_term
|
43
|
+
@window.attron(Curses::A_STANDOUT)
|
44
|
+
end
|
45
|
+
@window.addstr(t[0,line_remainder])
|
46
|
+
@window.attroff(Curses::A_STANDOUT)
|
47
|
+
end
|
48
|
+
else
|
49
|
+
@window.addstr(content[0,line_remainder])
|
50
|
+
end
|
51
|
+
@window.addstr("\n")
|
52
|
+
@window.attroff(Curses::color_pair(ACTIVE_SHA_COLOR))
|
53
|
+
end
|
54
|
+
@window.refresh
|
55
|
+
@window.setpos(0,0)
|
56
|
+
end
|
57
|
+
|
58
|
+
attr_accessor :top
|
59
|
+
|
60
|
+
def search(term, skip_current_line)
|
61
|
+
@search_term = term
|
62
|
+
return unless term
|
63
|
+
save_cursor = @cursor
|
64
|
+
search_data = data.map { |d| d[1] }
|
65
|
+
initial_position = save_cursor - (skip_current_line ? 0 : 1)
|
66
|
+
search_data[initial_position..-1].each_with_index do |d, i|
|
67
|
+
if d =~ /#{term}/
|
68
|
+
go_to(initial_position + i + 1)
|
69
|
+
return
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
search_data[0..initial_position].each_with_index do |d, i|
|
74
|
+
if d =~ /#{term}/
|
75
|
+
go_to(i + 1)
|
76
|
+
return
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def bufbottom
|
82
|
+
@top + (@height - 1)
|
83
|
+
end
|
84
|
+
|
85
|
+
def cursorup
|
86
|
+
return if @cursor == 1
|
87
|
+
@cursor -= 1
|
88
|
+
adjust_top!
|
89
|
+
end
|
90
|
+
|
91
|
+
def cursordown
|
92
|
+
return if @cursor >= data.size
|
93
|
+
@cursor += 1
|
94
|
+
adjust_top!
|
95
|
+
end
|
96
|
+
|
97
|
+
def pageup
|
98
|
+
previous_offset = @cursor - @top
|
99
|
+
@cursor -= @height / 2
|
100
|
+
if @cursor < 1
|
101
|
+
@cursor = 1
|
102
|
+
end
|
103
|
+
|
104
|
+
@top = @cursor - previous_offset
|
105
|
+
adjust_top!
|
106
|
+
end
|
107
|
+
|
108
|
+
def pagedown
|
109
|
+
previous_offset = @cursor - @top
|
110
|
+
@cursor += @height / 2
|
111
|
+
if @cursor > data.size
|
112
|
+
@cursor = data.size
|
113
|
+
end
|
114
|
+
|
115
|
+
@top = @cursor - previous_offset
|
116
|
+
adjust_top!
|
117
|
+
end
|
118
|
+
|
119
|
+
def go_top
|
120
|
+
@top = @cursor = 1
|
121
|
+
end
|
122
|
+
|
123
|
+
def go_to(l)
|
124
|
+
previous_offset = @cursor - @top
|
125
|
+
@cursor = l
|
126
|
+
@top = @cursor - previous_offset
|
127
|
+
adjust_top!
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
def go_bottom
|
132
|
+
@cursor = data.size
|
133
|
+
@top = data.size - (@height - 1)
|
134
|
+
end
|
135
|
+
|
136
|
+
def adjust_top!
|
137
|
+
if @top < 1
|
138
|
+
@top = 1
|
139
|
+
end
|
140
|
+
|
141
|
+
if @top > @cursor
|
142
|
+
@top = @cursor
|
143
|
+
end
|
144
|
+
|
145
|
+
while @cursor > bufbottom
|
146
|
+
@top += 1
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module GitSpelunk
|
2
|
+
class UI
|
3
|
+
class RepoWindow < Window
|
4
|
+
def initialize(height, offset)
|
5
|
+
@window = Curses::Window.new(height, Curses.cols, offset, 0)
|
6
|
+
@offset = offset
|
7
|
+
@height = height
|
8
|
+
@command_mode = false
|
9
|
+
@command_buffer = ""
|
10
|
+
@content = ""
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :content, :command_mode, :command_buffer
|
14
|
+
|
15
|
+
def exit_command_mode!
|
16
|
+
self.command_buffer = ""
|
17
|
+
self.command_mode = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def draw
|
21
|
+
@window.setpos(0,0)
|
22
|
+
draw_status_line
|
23
|
+
@window.addstr(@content + "\n") if content
|
24
|
+
@window.addstr("\n" * (@height - @content.split("\n").size - 2))
|
25
|
+
|
26
|
+
draw_bottom_line
|
27
|
+
@window.refresh
|
28
|
+
set_cursor
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_cursor
|
32
|
+
Curses::stdscr.setpos(@offset + @height - 1, command_buffer.size + 1)
|
33
|
+
end
|
34
|
+
|
35
|
+
def draw_status_line
|
36
|
+
with_highlighting do
|
37
|
+
@window.addstr("navigation: j k CTRL-D CTRL-U ")
|
38
|
+
@window.addstr("history: [ ] ")
|
39
|
+
@window.addstr("search: / ")
|
40
|
+
@window.addstr(" " * line_remainder + "\n")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def draw_bottom_line
|
45
|
+
if command_mode
|
46
|
+
@window.addstr(":" + command_buffer)
|
47
|
+
@window.addstr(" " * line_remainder)
|
48
|
+
else
|
49
|
+
with_highlighting do
|
50
|
+
@window.addstr(" " * line_remainder + "\n")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
module GitSpelunk
|
4
|
+
class UI
|
5
|
+
class Window
|
6
|
+
def with_highlighting
|
7
|
+
@window.attron(Curses::A_STANDOUT)
|
8
|
+
yield
|
9
|
+
ensure
|
10
|
+
@window.attroff(Curses::A_STANDOUT)
|
11
|
+
end
|
12
|
+
|
13
|
+
def line_remainder
|
14
|
+
Curses.cols - @window.curx - 1
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'git_spelunk/ui/window'
|
2
|
+
require 'git_spelunk/ui/pager'
|
3
|
+
require 'git_spelunk/ui/repo'
|
4
|
+
require 'curses'
|
5
|
+
|
6
|
+
module GitSpelunk
|
7
|
+
class UI
|
8
|
+
|
9
|
+
def initialize(file_context)
|
10
|
+
Curses.init_screen
|
11
|
+
Curses.start_color
|
12
|
+
Curses.raw
|
13
|
+
Curses.nonl
|
14
|
+
Curses.curs_set(2)
|
15
|
+
screen = Curses.stdscr
|
16
|
+
screen.refresh
|
17
|
+
screen.keypad(1)
|
18
|
+
Curses.init_pair(ACTIVE_SHA_COLOR, Curses::COLOR_GREEN, Curses::COLOR_BLACK)
|
19
|
+
|
20
|
+
calculate_heights!
|
21
|
+
@file_context = file_context
|
22
|
+
@history = [file_context]
|
23
|
+
@pager = PagerWindow.new(@pager_height)
|
24
|
+
@pager.data = @file_context.get_blame
|
25
|
+
|
26
|
+
@repo = RepoWindow.new(@repo_height, @pager_height)
|
27
|
+
end
|
28
|
+
|
29
|
+
def calculate_heights!
|
30
|
+
@repo_height = (Curses.lines.to_f * 0.20).to_i
|
31
|
+
@pager_height = Curses.lines - @repo_height
|
32
|
+
end
|
33
|
+
|
34
|
+
def run
|
35
|
+
@repo.content = @file_context.get_line_commit_info(@pager.cursor)
|
36
|
+
pause_thread
|
37
|
+
begin
|
38
|
+
@pager.draw
|
39
|
+
@repo.draw
|
40
|
+
@repo.set_cursor
|
41
|
+
handle_key(Curses.getch)
|
42
|
+
end while true
|
43
|
+
end
|
44
|
+
|
45
|
+
def pause_thread
|
46
|
+
Thread.abort_on_exception = true
|
47
|
+
Thread.new do
|
48
|
+
while true
|
49
|
+
if heartbeat_expired? && @last_line != @pager.cursor
|
50
|
+
current_line = @pager.cursor
|
51
|
+
content = @file_context.get_line_commit_info(current_line)
|
52
|
+
if heartbeat_expired? && @pager.cursor == current_line
|
53
|
+
@repo.content = content
|
54
|
+
@repo.draw
|
55
|
+
@last_line = current_line
|
56
|
+
else
|
57
|
+
@heartbeat = Time.now
|
58
|
+
end
|
59
|
+
end
|
60
|
+
sleep 0.05
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def heartbeat_expired?
|
66
|
+
@heartbeat && (Time.now - @heartbeat).to_f > 0.30
|
67
|
+
end
|
68
|
+
|
69
|
+
def after_navigation
|
70
|
+
@pager.highlight_sha = true
|
71
|
+
@repo.exit_command_mode!
|
72
|
+
end
|
73
|
+
|
74
|
+
def handle_key(key)
|
75
|
+
@heartbeat = Time.now
|
76
|
+
case key
|
77
|
+
when Curses::KEY_DOWN, 'j'
|
78
|
+
@pager.cursordown
|
79
|
+
after_navigation
|
80
|
+
when Curses::KEY_UP, '-', 'k'
|
81
|
+
@pager.cursorup
|
82
|
+
after_navigation
|
83
|
+
when Curses::KEY_CTRL_D, ' '
|
84
|
+
@pager.pagedown
|
85
|
+
after_navigation
|
86
|
+
when Curses::KEY_CTRL_U
|
87
|
+
@pager.pageup
|
88
|
+
after_navigation
|
89
|
+
when *(0..9).to_a.map(&:to_s)
|
90
|
+
@repo.command_mode = true
|
91
|
+
@repo.command_buffer += key
|
92
|
+
when Curses::KEY_CTRL_M
|
93
|
+
if @repo.command_buffer != ''
|
94
|
+
@pager.go_to(@repo.command_buffer.to_i)
|
95
|
+
end
|
96
|
+
after_navigation
|
97
|
+
when 'G'
|
98
|
+
if @repo.command_buffer != ''
|
99
|
+
@pager.go_to(@repo.command_buffer.to_i)
|
100
|
+
else
|
101
|
+
@pager.go_bottom
|
102
|
+
end
|
103
|
+
after_navigation
|
104
|
+
when '['
|
105
|
+
goto = @file_context.get_line_for_sha_parent(@pager.cursor)
|
106
|
+
if goto
|
107
|
+
@file_context.line_number = @pager.cursor
|
108
|
+
@history.push(@file_context)
|
109
|
+
|
110
|
+
@file_context = @file_context.clone_for_parent_sha(@pager.cursor)
|
111
|
+
@pager.data = @file_context.get_blame
|
112
|
+
@pager.go_to(goto)
|
113
|
+
|
114
|
+
# force commit info update
|
115
|
+
@last_line = nil
|
116
|
+
end
|
117
|
+
when ']'
|
118
|
+
if @history.last
|
119
|
+
@file_context = @history.pop
|
120
|
+
@pager.data = @file_context.get_blame
|
121
|
+
@pager.go_to(@file_context.line_number)
|
122
|
+
@pager.draw
|
123
|
+
|
124
|
+
# force commit info update
|
125
|
+
@last_line = nil
|
126
|
+
end
|
127
|
+
when 's'
|
128
|
+
@heartbeat = nil
|
129
|
+
sha = @file_context.sha_for_line(@pager.cursor)
|
130
|
+
Curses.close_screen
|
131
|
+
system("git -p --git-dir='#{@file_context.repo.path}' show #{sha} | less")
|
132
|
+
Curses.stdscr.refresh
|
133
|
+
@pager.draw
|
134
|
+
@repo.draw
|
135
|
+
@pager.highlight_sha = true
|
136
|
+
when '/'
|
137
|
+
@repo.command_mode = true
|
138
|
+
@repo.command_buffer = '/'
|
139
|
+
@repo.draw
|
140
|
+
@repo.set_cursor
|
141
|
+
begin
|
142
|
+
line = Curses.getstr
|
143
|
+
rescue Interrupt
|
144
|
+
@repo.exit_command_mode!
|
145
|
+
end
|
146
|
+
@search_string = line
|
147
|
+
@pager.search(@search_string, false)
|
148
|
+
@repo.exit_command_mode!
|
149
|
+
when 'n'
|
150
|
+
@pager.search(@search_string, true)
|
151
|
+
after_navigation
|
152
|
+
when 'q'
|
153
|
+
exit
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
data/lib/git_spelunk.rb
ADDED
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: git_spelunk
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ben Osheroff
|
9
|
+
- Saroj Yadav
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2013-11-15 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: grit
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
31
|
+
description: ''
|
32
|
+
email:
|
33
|
+
- ben@zendesk.com
|
34
|
+
- saroj@zendesk.com
|
35
|
+
executables:
|
36
|
+
- git-spelunk
|
37
|
+
extensions: []
|
38
|
+
extra_rdoc_files: []
|
39
|
+
files:
|
40
|
+
- lib/git_spelunk/file.rb
|
41
|
+
- lib/git_spelunk/file_context.rb
|
42
|
+
- lib/git_spelunk/offset.rb
|
43
|
+
- lib/git_spelunk/ui/pager.rb
|
44
|
+
- lib/git_spelunk/ui/repo.rb
|
45
|
+
- lib/git_spelunk/ui/window.rb
|
46
|
+
- lib/git_spelunk/ui.rb
|
47
|
+
- lib/git_spelunk.rb
|
48
|
+
- bin/git-spelunk
|
49
|
+
homepage: ''
|
50
|
+
licenses: []
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 1.3.6
|
67
|
+
requirements: []
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.8.25
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: ''
|
73
|
+
test_files: []
|
74
|
+
has_rdoc:
|