git-prepare-branch 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +70 -0
- data/bin/git-prepare-branch +161 -0
- data/lib/git-prepare-branch.rb +12 -0
- data/lib/git-prepare-branch/app.rb +104 -0
- data/lib/git-prepare-branch/command.rb +45 -0
- data/lib/git-prepare-branch/command_keys.rb +6 -0
- data/lib/git-prepare-branch/configurator.rb +72 -0
- data/lib/git-prepare-branch/context.rb +12 -0
- data/lib/git-prepare-branch/logger.rb +20 -0
- data/lib/git-prepare-branch/null_logger.rb +6 -0
- data/lib/git-prepare-branch/screen.rb +54 -0
- data/lib/git-prepare-branch/styles.rb +48 -0
- data/lib/git-prepare-branch/terminal.rb +132 -0
- data/lib/git-prepare-branch/variable.rb +26 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2d820e9fda828430f381da9a0173811a060e2c73
|
4
|
+
data.tar.gz: 27d972cc3a0993b61e6479a312eebf0f8d628e94
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b2ffc9dd90b096e11080164a6f31db67b9fa4f6bcb8023ae69b38d1c7241108857dafc46248163a195480626ab1c7a2c9cb44614e9195c5537e1a929519d7421
|
7
|
+
data.tar.gz: 3565a768eae5d3387864727057cdcc3925eace47b57f34f8db568a1729fb58b3da6528f8de2b4e0a0def4ca36adbb0de50eb4cc00c7970a16bf680be3694b61a
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# git-prepare-branch
|
2
|
+
|
3
|
+
This tool is for developers who prefer to use interactive rebasing to prepare
|
4
|
+
their branches for code review and merging. It provides a wrapper around some
|
5
|
+
git commands along with shortcut keys.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Currently installation is via rubygems
|
10
|
+
|
11
|
+
```
|
12
|
+
gem install git-prepare-branch
|
13
|
+
```
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
With your feature branch checked out and assuming you are merging into master
|
18
|
+
|
19
|
+
```
|
20
|
+
git prepare-branch
|
21
|
+
```
|
22
|
+
|
23
|
+
If you are merging into another branch run
|
24
|
+
|
25
|
+
```
|
26
|
+
git prepare-branch some-other-branch
|
27
|
+
```
|
28
|
+
|
29
|
+
You will then see a log of all the commits to be merged.
|
30
|
+
|
31
|
+
Pressing `?` will bring up a list of the available command keys. They array_to_sentence_string
|
32
|
+
|
33
|
+
```
|
34
|
+
f filter files Filters commits to just those affected files that match the specified filter
|
35
|
+
|
36
|
+
r begin rebase Start an interactive rebase
|
37
|
+
|
38
|
+
s show Show a specific commit
|
39
|
+
|
40
|
+
d sum diff Show the combined diff from one SHA to another (inclusive)
|
41
|
+
|
42
|
+
v cycle view Cycles through applying different view options to the list of commits
|
43
|
+
```
|
44
|
+
|
45
|
+
If you pause mid rebase - for example if you have chosen to edit a commit - there are a different set of commands available
|
46
|
+
|
47
|
+
```
|
48
|
+
a abort rebase Abort the current rebase
|
49
|
+
|
50
|
+
c continue rebase Continue with the current rebase
|
51
|
+
```
|
52
|
+
|
53
|
+
If you pause mid rebase and there are conflicts detected, another set of commands are available
|
54
|
+
|
55
|
+
```
|
56
|
+
a abort rebase Abort the current rebase
|
57
|
+
|
58
|
+
m show my changes Show the diff of the content in this branch causing the conflict
|
59
|
+
|
60
|
+
t show other commits Show the commits that may have introduced the conflicting changes
|
61
|
+
|
62
|
+
o show other diff Show the combined diff of the commits that may have introduced the change
|
63
|
+
|
64
|
+
d show diff Show the diff of the conflicts
|
65
|
+
```
|
66
|
+
|
67
|
+
## What are the actual git commands being run?
|
68
|
+
|
69
|
+
The entire set of commands are defined in [bin/git-prepare-branch](bin/git-prepare-branch).
|
70
|
+
The rest of the code in the application is to allow for the interface and the declarative DSL.
|
@@ -0,0 +1,161 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/git-prepare-branch'
|
4
|
+
|
5
|
+
LOG_FILE = ENV['LOG']
|
6
|
+
|
7
|
+
if LOG_FILE
|
8
|
+
logger = GitPrepareBranch::Logger.new(
|
9
|
+
stream: IO.new(IO.sysopen(LOG_FILE, 'a+'))
|
10
|
+
)
|
11
|
+
else
|
12
|
+
logger = GitPrepareBranch::NullLogger.new
|
13
|
+
end
|
14
|
+
|
15
|
+
onto = ARGV[0] || 'master'
|
16
|
+
|
17
|
+
VIEWS = [
|
18
|
+
'log --oneline --decorate --reverse ',
|
19
|
+
'log --oneline --decorate --reverse --stat --name-only',
|
20
|
+
'log --oneline --decorate --reverse --stat',
|
21
|
+
'diff --stat',
|
22
|
+
'diff --dirstat'
|
23
|
+
]
|
24
|
+
|
25
|
+
GitPrepareBranch::App.configure(logger: logger) do
|
26
|
+
on :load do
|
27
|
+
variable :view, value: VIEWS[0]
|
28
|
+
variable :file_filter
|
29
|
+
variable :current_branch, capture: 'git rev-parse --abbrev-ref HEAD'
|
30
|
+
variable :prefix, capture: 'git rev-parse --show-prefix'
|
31
|
+
variable :num_commits, capture: 'git rev-list --count %{onto}..'
|
32
|
+
variable :num_files, capture: 'git diff --name-only %{onto}.. | wc -l'
|
33
|
+
end
|
34
|
+
|
35
|
+
on :display do
|
36
|
+
variable :mid_rebase?,
|
37
|
+
value: ->(context) {
|
38
|
+
context.terminal.capture(
|
39
|
+
'if test -d "$(git rev-parse --git-path rebase-merge)" || test -d "$(git rev-parse --git-path rebase-apply)"; then echo 1; fi'
|
40
|
+
).strip == '1'
|
41
|
+
}
|
42
|
+
|
43
|
+
variable :conflicts?,
|
44
|
+
value: ->(context) {
|
45
|
+
!context.terminal.capture("git status -s | grep -e '^UU'").strip.empty?
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
screen :default do
|
50
|
+
heading '❯ %{current_branch} => %{onto} - %{num_commits} commits, %{num_files} files', style: :header
|
51
|
+
display 'git %{view} %{onto}.. *%{file_filter}*'
|
52
|
+
description "prepare-branch\n\nPrepare a branch for rebasing"
|
53
|
+
|
54
|
+
command 'f', 'filter files',
|
55
|
+
-> (context, inputs) {
|
56
|
+
context.variables[:file_filter] = inputs[:file_filter]
|
57
|
+
},
|
58
|
+
description: 'Filters commits to just those affected files that match the specified filter',
|
59
|
+
input: {
|
60
|
+
file_filter: {
|
61
|
+
prompt: 'Enter a file pattern',
|
62
|
+
autocomplete_strategy: :file
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
command 'r', 'begin rebase',
|
67
|
+
'git rebase -i %{onto}',
|
68
|
+
description: 'Start an interactive rebase'
|
69
|
+
|
70
|
+
command 's', 'show',
|
71
|
+
'git show %{sha}',
|
72
|
+
input: {
|
73
|
+
sha: {
|
74
|
+
prompt: 'Enter a sha',
|
75
|
+
autocomplete_strategy: :sha
|
76
|
+
}
|
77
|
+
},
|
78
|
+
description: 'Show a specific commit',
|
79
|
+
prompt_to_continue: true
|
80
|
+
|
81
|
+
command 'd', 'sum diff',
|
82
|
+
'git diff -w --find-renames --find-copies --patience %{start_sha}~...%{end_sha}',
|
83
|
+
input: {
|
84
|
+
start_sha: {
|
85
|
+
prompt: 'Enter the starting sha',
|
86
|
+
autocomplete_strategy: :sha
|
87
|
+
},
|
88
|
+
end_sha: {
|
89
|
+
prompt: 'Enter the end sha',
|
90
|
+
autocomplete_strategy: :sha
|
91
|
+
}
|
92
|
+
},
|
93
|
+
description: 'Show the combined diff from one sha to another (inclusive)',
|
94
|
+
prompt_to_continue: true
|
95
|
+
|
96
|
+
command 'D', 'entire diff',
|
97
|
+
'git diff -w --find-renames --find-copies --patience %{onto}..',
|
98
|
+
description: 'Show the entire combined diff as if the branch was squashed into a single commit',
|
99
|
+
prompt_to_continue: true
|
100
|
+
|
101
|
+
command 'v', 'cycle view',
|
102
|
+
-> (context, _) {
|
103
|
+
context.variables[:view] = VIEWS[(VIEWS.index(context.variables[:view]) + 1) % VIEWS.length]
|
104
|
+
},
|
105
|
+
description: 'Cycles through applying different view options to the list of commits'
|
106
|
+
|
107
|
+
command 'V', 'cycle view (reverse)',
|
108
|
+
-> (context, _) {
|
109
|
+
context.variables[:view] = VIEWS[(VIEWS.index(context.variables[:view]) - 1) % VIEWS.length]
|
110
|
+
},
|
111
|
+
description: 'Cycles back through applying different view options to the list of commits (reverse of v)'
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
screen :mid_rebase do
|
116
|
+
heading '❯ Rebasing %{current_branch} onto %{onto} (paused)', style: :header_ok
|
117
|
+
display 'git show --stat'
|
118
|
+
description "prepare-branch - mid rebase"
|
119
|
+
|
120
|
+
command 'a', 'abort rebase', 'git rebase --abort', description: 'Abort the current rebase'
|
121
|
+
command 'c', 'continue rebase', 'git rebase --continue', description: 'Continue with the current rebase'
|
122
|
+
end
|
123
|
+
|
124
|
+
screen :conflicts do
|
125
|
+
heading '❯ Conflicts rebasing %{current_branch} onto %{onto}', style: :header_warning
|
126
|
+
display 'git status -s'
|
127
|
+
description "prepare-branch - conflicts"
|
128
|
+
|
129
|
+
command 'a', 'abort rebase', 'git rebase --abort', description: 'Abort the current rebase'
|
130
|
+
|
131
|
+
command 'm', 'show my changes',
|
132
|
+
'git diff --name-only --diff-filter=U --relative=%{prefix} | xargs git show $(< $(git rev-parse --git-path rebase-merge/stopped-sha)) --oneline',
|
133
|
+
description: 'Show the diff of the content in this branch causing the conflict',
|
134
|
+
prompt_to_continue: true
|
135
|
+
|
136
|
+
command 't', 'show other commits',
|
137
|
+
'git diff --name-only --relative=%{prefix} --diff-filter=U | xargs git log $(git merge-base HEAD $(< $(git rev-parse --git-path rebase-merge/stopped-sha)))... --oneline',
|
138
|
+
description: 'Show the commits that may have introduced the conflicting changes',
|
139
|
+
prompt_to_continue: true
|
140
|
+
|
141
|
+
command 'o', 'show other diff',
|
142
|
+
'git diff --name-only --relative=%{prefix} --diff-filter=U | xargs git diff $(git merge-base HEAD $(< $(git rev-parse --git-path rebase-merge/stopped-sha)))...',
|
143
|
+
description: 'Show the combined diff of the commits that may have introduced the change',
|
144
|
+
prompt_to_continue: true
|
145
|
+
|
146
|
+
command 'd', 'show diff',
|
147
|
+
'git diff',
|
148
|
+
description: 'Show the diff of the conflicts',
|
149
|
+
prompt_to_continue: true
|
150
|
+
end
|
151
|
+
|
152
|
+
routing -> (context) {
|
153
|
+
if context.variables.mid_rebase?
|
154
|
+
return :conflicts if context.variables.conflicts?
|
155
|
+
return :mid_rebase
|
156
|
+
end
|
157
|
+
:default
|
158
|
+
}
|
159
|
+
end.start(
|
160
|
+
onto: { value: onto }
|
161
|
+
)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'git-prepare-branch/app'
|
4
|
+
require_relative 'git-prepare-branch/command'
|
5
|
+
require_relative 'git-prepare-branch/configurator'
|
6
|
+
require_relative 'git-prepare-branch/context'
|
7
|
+
require_relative 'git-prepare-branch/logger'
|
8
|
+
require_relative 'git-prepare-branch/null_logger'
|
9
|
+
require_relative 'git-prepare-branch/screen'
|
10
|
+
require_relative 'git-prepare-branch/styles'
|
11
|
+
require_relative 'git-prepare-branch/terminal'
|
12
|
+
require_relative 'git-prepare-branch/variable'
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
require_relative 'logger'
|
6
|
+
require_relative 'configurator'
|
7
|
+
require_relative 'context'
|
8
|
+
require_relative 'terminal'
|
9
|
+
require_relative 'variable'
|
10
|
+
|
11
|
+
module GitPrepareBranch
|
12
|
+
class App
|
13
|
+
attr_reader :logger
|
14
|
+
|
15
|
+
attr_accessor :title, :router
|
16
|
+
|
17
|
+
def initialize(logger: nil)
|
18
|
+
@logger = logger || Logger.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_event_handler(event, &block)
|
22
|
+
event_handlers[event] ||= []
|
23
|
+
event_handlers[event] << block
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_screen screen
|
27
|
+
screen.context = context
|
28
|
+
screens[screen.name] = screen
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_variable name, capture: nil, value: nil
|
32
|
+
context.variables[name] = Variable.new(name, context: context, capture: capture, value: value).value
|
33
|
+
end
|
34
|
+
|
35
|
+
def configure(&block)
|
36
|
+
Configurator.new(self).apply(&block)
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def context
|
41
|
+
@context ||= Context.new(
|
42
|
+
terminal: terminal,
|
43
|
+
variables: {}
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def current_screen
|
48
|
+
screens[current_screen_name]
|
49
|
+
end
|
50
|
+
|
51
|
+
def current_screen_name
|
52
|
+
router.call(context)
|
53
|
+
end
|
54
|
+
|
55
|
+
def screens
|
56
|
+
@screens ||= {}
|
57
|
+
end
|
58
|
+
|
59
|
+
def start(variables = [])
|
60
|
+
variables.each { |name, args| add_variable(name, args) }
|
61
|
+
trigger :load
|
62
|
+
while true do
|
63
|
+
begin
|
64
|
+
terminal.clear
|
65
|
+
trigger :display
|
66
|
+
terminal.write_line format(current_screen.heading, context.variables.to_h), current_screen.heading_style
|
67
|
+
terminal.call current_screen.display, context.variables.to_h
|
68
|
+
terminal.say 'Press a command key or ? for help', :hint
|
69
|
+
begin
|
70
|
+
result = terminal.wait_for_keypress
|
71
|
+
current_screen.handle_keypress result, context
|
72
|
+
rescue Interrupt
|
73
|
+
end
|
74
|
+
rescue Interrupt
|
75
|
+
exit
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def terminal
|
81
|
+
@terminal ||= Terminal.new(logger: logger)
|
82
|
+
end
|
83
|
+
|
84
|
+
def trigger event
|
85
|
+
return unless event_handlers[event]
|
86
|
+
|
87
|
+
event_handlers[event].each do |block|
|
88
|
+
block.call(context)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class << self
|
93
|
+
def configure(*options, &block)
|
94
|
+
self.new(*options).configure(&block)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def event_handlers
|
101
|
+
@event_handlers ||= {}
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitPrepareBranch
|
4
|
+
class Command
|
5
|
+
attr_reader :key, :name, :command, :options
|
6
|
+
|
7
|
+
def initialize(key, name, command, options={})
|
8
|
+
@key = key
|
9
|
+
@name = name
|
10
|
+
@command = command
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(context)
|
15
|
+
inputs = capture_inputs(context)
|
16
|
+
run_proc(context, inputs) if command.is_a?(Proc)
|
17
|
+
run_as_shell_command(context, inputs) if command.is_a?(String)
|
18
|
+
context.terminal.prompt_to_continue if options[:prompt_to_continue]
|
19
|
+
end
|
20
|
+
|
21
|
+
def description
|
22
|
+
options[:description]
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def capture_inputs(context)
|
28
|
+
return {} unless options[:input]
|
29
|
+
options[:input].each_with_object({}) do |(key, value), object|
|
30
|
+
object[key] = context.terminal.ask value[:prompt], autocomplete_strategy: [value[:autocomplete_strategy], context.variables]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def run_as_shell_command(context, inputs)
|
35
|
+
context.terminal.call format(
|
36
|
+
command,
|
37
|
+
context.variables.to_h.merge(inputs)
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def run_proc(context, inputs)
|
42
|
+
command.call(context, inputs)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'screen'
|
4
|
+
|
5
|
+
module GitPrepareBranch
|
6
|
+
class Configurator
|
7
|
+
attr_reader :app
|
8
|
+
|
9
|
+
def initialize(app)
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply(&block)
|
14
|
+
instance_exec(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def on(event, &block)
|
18
|
+
app.add_event_handler event, &block
|
19
|
+
end
|
20
|
+
|
21
|
+
def routing(routes)
|
22
|
+
@app.router = routes
|
23
|
+
end
|
24
|
+
|
25
|
+
def screen(name, &block)
|
26
|
+
app.add_screen ScreenDSL.new(Screen.new(name)).apply(&block).screen
|
27
|
+
end
|
28
|
+
|
29
|
+
def title(value)
|
30
|
+
app.title = value
|
31
|
+
end
|
32
|
+
|
33
|
+
def variable(name, capture: nil, value: nil)
|
34
|
+
app.add_variable name, capture: capture, value: value
|
35
|
+
end
|
36
|
+
|
37
|
+
class ScreenDSL
|
38
|
+
attr_reader :screen
|
39
|
+
|
40
|
+
def initialize(screen)
|
41
|
+
@screen = screen
|
42
|
+
end
|
43
|
+
|
44
|
+
def apply(&block)
|
45
|
+
self.tap do |s|
|
46
|
+
instance_exec(&block)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def command(*args)
|
51
|
+
screen.add_command(*args)
|
52
|
+
end
|
53
|
+
|
54
|
+
def description(value)
|
55
|
+
screen.description = value
|
56
|
+
end
|
57
|
+
|
58
|
+
def display(command)
|
59
|
+
screen.display = command
|
60
|
+
end
|
61
|
+
|
62
|
+
def heading(message, options={})
|
63
|
+
screen.heading = message
|
64
|
+
screen.heading_style = options[:style]
|
65
|
+
end
|
66
|
+
|
67
|
+
def title(value)
|
68
|
+
screen.title = value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module GitPrepareBranch
|
2
|
+
class Logger
|
3
|
+
def initialize(stream: $stdout)
|
4
|
+
@stream = stream
|
5
|
+
end
|
6
|
+
|
7
|
+
def log message
|
8
|
+
stream << format_message(message)
|
9
|
+
stream.flush
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
attr_reader :stream
|
15
|
+
|
16
|
+
def format_message(message)
|
17
|
+
"#{Time.now}: #{message}\n"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'command'
|
4
|
+
require_relative 'command_keys'
|
5
|
+
|
6
|
+
module GitPrepareBranch
|
7
|
+
class Screen
|
8
|
+
attr_reader :name, :context
|
9
|
+
attr_accessor :context, :title, :heading, :heading_style, :description, :display
|
10
|
+
|
11
|
+
def initialize(name)
|
12
|
+
@name = name
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_command(key, name, command, options={})
|
16
|
+
commands[key] = Command.new(key, name, command, options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def commands
|
20
|
+
@commands ||= {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def handle_keypress key, context
|
24
|
+
help if ['h', '?'].include?(key)
|
25
|
+
exit if ['q', CommandKeys::CTRL_C, CommandKeys::CTRL_D].include?(key)
|
26
|
+
return unless commands.keys.include?(key)
|
27
|
+
commands[key].call(context)
|
28
|
+
end
|
29
|
+
|
30
|
+
def help
|
31
|
+
terminal.clear
|
32
|
+
terminal.say "DESCRIPTION"
|
33
|
+
terminal.hr
|
34
|
+
terminal.say "#{description}\n\n"
|
35
|
+
terminal.say "COMMANDS"
|
36
|
+
terminal.hr
|
37
|
+
commands.each_with_object('') do |(key, command), result|
|
38
|
+
terminal.say "#{key} #{command.name.ljust(longest_command_name_length)} #{command.description}"
|
39
|
+
terminal.say "\n"
|
40
|
+
end
|
41
|
+
terminal.prompt_to_continue
|
42
|
+
end
|
43
|
+
|
44
|
+
def longest_command_name_length
|
45
|
+
@longest_command_name_length ||= commands.collect{|(_, c)| c.name.length}.max
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def terminal
|
51
|
+
context.terminal
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rainbow'
|
2
|
+
|
3
|
+
module GitPrepareBranch
|
4
|
+
class Styles
|
5
|
+
def initialize(styler: nil)
|
6
|
+
@styler = styler || Rainbow.global.method(:wrap)
|
7
|
+
end
|
8
|
+
|
9
|
+
def apply(text, style = :default)
|
10
|
+
return text if style == :default
|
11
|
+
send(style, text)
|
12
|
+
end
|
13
|
+
|
14
|
+
def bold(text)
|
15
|
+
style(text).bright
|
16
|
+
end
|
17
|
+
|
18
|
+
def footer(text)
|
19
|
+
style(text).color(:dimgray)
|
20
|
+
end
|
21
|
+
|
22
|
+
def header(text)
|
23
|
+
style(text).color(:white).bg(:darkslateblue)
|
24
|
+
end
|
25
|
+
|
26
|
+
def header_ok(text)
|
27
|
+
style(text).color(:white).bg(:darkgreen)
|
28
|
+
end
|
29
|
+
|
30
|
+
def header_warning(text)
|
31
|
+
style(text).color(:white).bg(:red)
|
32
|
+
end
|
33
|
+
|
34
|
+
def hint(text)
|
35
|
+
style(text).color(:dimgray)
|
36
|
+
end
|
37
|
+
|
38
|
+
alias hr footer
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :styler
|
43
|
+
|
44
|
+
def style(text)
|
45
|
+
styler.call(text)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'readline'
|
2
|
+
require 'io/console'
|
3
|
+
|
4
|
+
require_relative 'styles'
|
5
|
+
|
6
|
+
module GitPrepareBranch
|
7
|
+
class Terminal
|
8
|
+
CLEAR = 'clear'
|
9
|
+
DEFAULT_PROMPT = ' ❯ '
|
10
|
+
DEFAULT_WIDTH = 20
|
11
|
+
HR_CHARACTER = '─'
|
12
|
+
BLANK_CHARACTER = ' '
|
13
|
+
NUM_BLANK_LINES_BEFORE_PROMPT = 3
|
14
|
+
|
15
|
+
AUTOCOMPLETE_STRATEGIES = {
|
16
|
+
default: -> (_, _) {},
|
17
|
+
sha: -> (variables, s) {
|
18
|
+
`git rev-list #{variables[:onto]}...`
|
19
|
+
.split(/\n/)
|
20
|
+
.grep(/^#{s}/)
|
21
|
+
},
|
22
|
+
file: ->(variables, s) {
|
23
|
+
`git diff --name-only --relative=#{variables[:prefix]} #{variables[:onto]}...`
|
24
|
+
.split(/\n/)
|
25
|
+
.grep(/#{s}/)
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
def initialize(out: $stdout, err: $stderr, prompt: DEFAULT_PROMPT, logger: nil, styles: nil)
|
30
|
+
@out = out
|
31
|
+
@err = err
|
32
|
+
@prompt = prompt
|
33
|
+
@logger = logger || Logger.new
|
34
|
+
@styles = styles || Styles.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def ask(question, autocomplete_strategy: :default)
|
38
|
+
set_autocomplete_strategy autocomplete_strategy
|
39
|
+
Readline.readline("#{question}#{prompt}", true).chomp.strip
|
40
|
+
end
|
41
|
+
|
42
|
+
def blank_lines(quantity = 1)
|
43
|
+
quantity.times { out.puts }
|
44
|
+
end
|
45
|
+
|
46
|
+
def call(command, values = {})
|
47
|
+
command = normalise_command(command, values)
|
48
|
+
logger.log "CMD: #{command}"
|
49
|
+
result = system command, out: out, err: err
|
50
|
+
logger.log "OUT: #{out}"
|
51
|
+
logger.log "ERR: #{err}"
|
52
|
+
result
|
53
|
+
end
|
54
|
+
|
55
|
+
def capture(command, values = {})
|
56
|
+
command = normalise_command(command, values)
|
57
|
+
logger.log "CAP: #{command}"
|
58
|
+
result = `#{command}`.chomp.strip
|
59
|
+
logger.log "OUT: #{result}"
|
60
|
+
result
|
61
|
+
end
|
62
|
+
|
63
|
+
def clear
|
64
|
+
call CLEAR
|
65
|
+
end
|
66
|
+
|
67
|
+
def enable_raw
|
68
|
+
system("stty raw -echo")
|
69
|
+
end
|
70
|
+
|
71
|
+
def hr
|
72
|
+
out.puts styles.hr(HR_CHARACTER * width)
|
73
|
+
end
|
74
|
+
|
75
|
+
def prompt_to_continue
|
76
|
+
blank_lines NUM_BLANK_LINES_BEFORE_PROMPT
|
77
|
+
hr
|
78
|
+
say styles.footer('Press any key to continue')
|
79
|
+
wait_for_keypress {}
|
80
|
+
end
|
81
|
+
|
82
|
+
def reset_raw
|
83
|
+
system("stty -raw echo")
|
84
|
+
end
|
85
|
+
|
86
|
+
def say(output, style = :default)
|
87
|
+
puts styles.apply(output, style)
|
88
|
+
end
|
89
|
+
|
90
|
+
def wait_for_keypress
|
91
|
+
while true do
|
92
|
+
begin
|
93
|
+
enable_raw
|
94
|
+
char = STDIN.getc
|
95
|
+
return char
|
96
|
+
ensure
|
97
|
+
reset_raw
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def write_line(output, style = :default)
|
103
|
+
puts styles.apply(make_full_width(output), style)
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
attr_reader :out, :err, :prompt, :logger, :styles
|
109
|
+
|
110
|
+
def normalise_command(command, values = {})
|
111
|
+
format(command, values)
|
112
|
+
end
|
113
|
+
|
114
|
+
def set_autocomplete_strategy strategy
|
115
|
+
Readline.completion_append_character = " "
|
116
|
+
strategy = Array(strategy)
|
117
|
+
Readline.completion_proc = AUTOCOMPLETE_STRATEGIES[strategy[0]].curry.call(strategy[1])
|
118
|
+
end
|
119
|
+
|
120
|
+
def width
|
121
|
+
begin
|
122
|
+
IO.console.winsize[1]
|
123
|
+
rescue NotImplementedError
|
124
|
+
DEFAULT_WIDTH
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def make_full_width(text)
|
129
|
+
text + (BLANK_CHARACTER * [width - text.length, 0].max)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitPrepareBranch
|
4
|
+
class Variable
|
5
|
+
attr_reader :name, :context, :value, :capture
|
6
|
+
|
7
|
+
def initialize(name, context:, value: nil, capture: nil)
|
8
|
+
@name = name
|
9
|
+
@context = context
|
10
|
+
@capture = capture
|
11
|
+
@value = calculate_value(value)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def calculate_value(value)
|
17
|
+
return context.terminal.capture(capture_with_variables_injected) unless capture.nil?
|
18
|
+
return value.call(context) if value.kind_of? Proc
|
19
|
+
value
|
20
|
+
end
|
21
|
+
|
22
|
+
def capture_with_variables_injected
|
23
|
+
format(capture, context.variables.to_h)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: git-prepare-branch
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Phillips
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-04-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rainbow
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry-byebug
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest-reporters
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.2'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.2'
|
55
|
+
description: Command to assist in preparing Git branches for review
|
56
|
+
email: adam@29ways.co.uk
|
57
|
+
executables:
|
58
|
+
- git-prepare-branch
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- README.md
|
63
|
+
- bin/git-prepare-branch
|
64
|
+
- lib/git-prepare-branch.rb
|
65
|
+
- lib/git-prepare-branch/app.rb
|
66
|
+
- lib/git-prepare-branch/command.rb
|
67
|
+
- lib/git-prepare-branch/command_keys.rb
|
68
|
+
- lib/git-prepare-branch/configurator.rb
|
69
|
+
- lib/git-prepare-branch/context.rb
|
70
|
+
- lib/git-prepare-branch/logger.rb
|
71
|
+
- lib/git-prepare-branch/null_logger.rb
|
72
|
+
- lib/git-prepare-branch/screen.rb
|
73
|
+
- lib/git-prepare-branch/styles.rb
|
74
|
+
- lib/git-prepare-branch/terminal.rb
|
75
|
+
- lib/git-prepare-branch/variable.rb
|
76
|
+
homepage: https://github.com/adamphillips/git-prepare-branch
|
77
|
+
licenses:
|
78
|
+
- MIT
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 2.6.13
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: Command to assist in preparing Git branches for review
|
100
|
+
test_files: []
|