git-prepare-branch 0.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 +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: []
|