git_spelunk 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|