gitflash 0.4.0 → 0.5.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 +4 -4
- data/CHANGELOG.md +14 -2
- data/README.md +53 -5
- data/command_descriptions.yml +36 -21
- data/lib/gitflash/branch.rb +44 -0
- data/lib/gitflash/cli.rb +46 -104
- data/lib/gitflash/commands/base.rb +38 -0
- data/lib/gitflash/commands/branches.rb +68 -0
- data/lib/gitflash/commands/checkout.rb +47 -0
- data/lib/gitflash/commands/delete.rb +106 -0
- data/lib/gitflash/commands/reset.rb +67 -0
- data/lib/gitflash/commit.rb +22 -0
- data/lib/gitflash/confirmation_required.rb +19 -0
- data/lib/gitflash/error.rb +23 -0
- data/lib/gitflash/git/bash_command.rb +8 -6
- data/lib/gitflash/git/command_error.rb +5 -1
- data/lib/gitflash/repo.rb +116 -0
- data/lib/gitflash/result.rb +8 -0
- data/lib/gitflash/ui.rb +93 -0
- data/lib/gitflash/usage_error.rb +14 -0
- data/lib/gitflash/version.rb +1 -1
- data/schema/v1.json +172 -0
- metadata +15 -2
- data/lib/gitflash/git/wrapper.rb +0 -86
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gitflash
|
|
4
|
+
module Commands
|
|
5
|
+
# Resets the current branch to a commit given as an argument or picked from a menu.
|
|
6
|
+
# Default mode is mixed; --soft and --hard are supported. Only --hard asks for confirmation.
|
|
7
|
+
# The result includes the previous HEAD, so the reset can be reverted.
|
|
8
|
+
class Reset < Base
|
|
9
|
+
HARD_RESET_WARNING = 'You are about to reset your branch and lose all your current changes'
|
|
10
|
+
|
|
11
|
+
def call(ref = nil)
|
|
12
|
+
mode = reset_mode
|
|
13
|
+
|
|
14
|
+
if ref.nil?
|
|
15
|
+
require_interactive!('Pass the commit to reset to: gitflash reset COMMIT')
|
|
16
|
+
commits = repo.commits
|
|
17
|
+
return report_single_commit if commits.size < 2
|
|
18
|
+
|
|
19
|
+
ref = ui.select('Select a commit to reset to', commits.to_h { |c| [c.label, c.sha] })
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
reset(ref, mode)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def reset_mode
|
|
28
|
+
if options[:soft] && options[:hard]
|
|
29
|
+
usage_error!('invalid_options', 'Use either --soft or --hard, not both')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
return 'hard' if options[:hard]
|
|
33
|
+
|
|
34
|
+
options[:soft] ? 'soft' : 'mixed'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def reset(ref, mode)
|
|
38
|
+
commit = repo.resolve_commit(ref)
|
|
39
|
+
usage_error!('unknown_commit', "Unknown commit '#{ref}'") if commit.nil?
|
|
40
|
+
|
|
41
|
+
plan = { commit: commit, mode: mode }
|
|
42
|
+
text = "reset to #{commit[0, 7]} (#{mode})"
|
|
43
|
+
return planned(plan, "Would #{text}") if ui.dry_run?
|
|
44
|
+
return cancelled(plan) unless confirmed?(plan)
|
|
45
|
+
|
|
46
|
+
execute(plan, text)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def execute(plan, text)
|
|
50
|
+
previous = repo.resolve_commit('HEAD')
|
|
51
|
+
result = repo.reset(plan[:commit], mode: plan[:mode])
|
|
52
|
+
git_error!('reset', result) unless result.success?
|
|
53
|
+
|
|
54
|
+
ui.report(status: 'done', plan: plan, result: plan.merge(previous_commit: previous),
|
|
55
|
+
text: text.capitalize)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def confirmed?(plan)
|
|
59
|
+
plan[:mode] != 'hard' || ui.confirm?(HARD_RESET_WARNING, plan: plan)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def report_single_commit
|
|
63
|
+
ui.report(status: 'noop', result: nil, text: 'You only have one commit!')
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
5
|
+
module Gitflash
|
|
6
|
+
# A commit as reported by `git log`
|
|
7
|
+
Commit = Data.define(:sha, :subject, :author, :committed_at) do
|
|
8
|
+
# Parses one line produced with Repo::COMMIT_FORMAT
|
|
9
|
+
def self.parse(line)
|
|
10
|
+
sha, subject, author, date = line.split(Repo::SEPARATOR, 4)
|
|
11
|
+
new(sha: sha, subject: subject.to_s, author: author, committed_at: Time.iso8601(date))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def label
|
|
15
|
+
"#{sha} - #{subject}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_h
|
|
19
|
+
super.merge(committed_at: committed_at.iso8601)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gitflash
|
|
4
|
+
# Raised when a destructive command runs without a terminal and without --yes.
|
|
5
|
+
# Carries the plan so the caller can review it and re-run with --yes.
|
|
6
|
+
class ConfirmationRequired < Error
|
|
7
|
+
def initialize(message = nil, plan: nil)
|
|
8
|
+
super(message, code: 'confirmation_required', plan: plan)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def exit_code
|
|
12
|
+
2
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def status
|
|
16
|
+
'confirmation_required'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gitflash
|
|
4
|
+
# Base class for errors reported to the user.
|
|
5
|
+
# `code` is a stable machine-readable identifier; `plan` is the planned change, if any.
|
|
6
|
+
class Error < StandardError
|
|
7
|
+
attr_reader :code, :plan
|
|
8
|
+
|
|
9
|
+
def initialize(message = nil, code: 'failed', plan: nil)
|
|
10
|
+
super(message)
|
|
11
|
+
@code = code
|
|
12
|
+
@plan = plan
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def exit_code
|
|
16
|
+
1
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def status
|
|
20
|
+
'error'
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -9,16 +9,18 @@ module Gitflash
|
|
|
9
9
|
# Runs a command without a shell and returns its stdout.
|
|
10
10
|
# Raises CommandError when the command exits with a non-zero status.
|
|
11
11
|
def exec(*args)
|
|
12
|
-
stdout, stderr,
|
|
13
|
-
raise CommandError, "#{args.join(' ')} failed: #{stderr.strip}" unless
|
|
12
|
+
stdout, stderr, success = capture(*args)
|
|
13
|
+
raise CommandError, "#{args.join(' ')} failed: #{stderr.strip}" unless success
|
|
14
14
|
|
|
15
15
|
stdout
|
|
16
|
-
rescue SystemCallError => e
|
|
17
|
-
raise CommandError, "#{args.join(' ')} failed: #{e.message}"
|
|
18
16
|
end
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
# Runs a command without a shell and returns [stdout, stderr, success].
|
|
19
|
+
def capture(*args)
|
|
20
|
+
stdout, stderr, status = Open3.capture3(*args)
|
|
21
|
+
[stdout, stderr, status.success?]
|
|
22
|
+
rescue SystemCallError => e
|
|
23
|
+
raise CommandError, "#{args.join(' ')} failed: #{e.message}"
|
|
22
24
|
end
|
|
23
25
|
end
|
|
24
26
|
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gitflash
|
|
4
|
+
# Reads and changes the git repository in the current working directory.
|
|
5
|
+
# All git access goes through this class; it never prompts or prints.
|
|
6
|
+
class Repo
|
|
7
|
+
SEPARATOR = "\x1f"
|
|
8
|
+
BRANCH_FORMAT = %w[
|
|
9
|
+
refname:short objectname:short HEAD upstream:short upstream:track,nobracket
|
|
10
|
+
committerdate:iso-strict authorname subject
|
|
11
|
+
].map { |field| "%(#{field})" }.join('%1f')
|
|
12
|
+
COMMIT_FORMAT = %w[%h %s %an %cI].join('%x1f')
|
|
13
|
+
COMMITS_LIMIT = 100
|
|
14
|
+
FALLBACK_DEFAULT_BRANCHES = %w[main master].freeze
|
|
15
|
+
|
|
16
|
+
def initialize(bash: Git::BashCommand)
|
|
17
|
+
@bash = bash
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def ensure_work_tree!
|
|
21
|
+
inside = @bash.exec('git', 'rev-parse', '--is-inside-work-tree').strip == 'true'
|
|
22
|
+
raise Error.new('Not a git repository', code: 'not_a_repository') unless inside
|
|
23
|
+
rescue Git::CommandError
|
|
24
|
+
raise Error.new('Not a git repository', code: 'not_a_repository')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Local branches. With `merged_status: true` each branch also reports whether it is
|
|
28
|
+
# merged into the default branch (one extra git call).
|
|
29
|
+
def branches(merged_status: false)
|
|
30
|
+
merged_names = merged_status ? merged_branch_names : nil
|
|
31
|
+
branch_lines.map do |line|
|
|
32
|
+
Branch.parse(line, default_branch: default_branch, merged_names: merged_names)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def current_branch
|
|
37
|
+
branches.find(&:current?)&.name
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The branch that origin/HEAD points to, else main or master when present locally
|
|
41
|
+
def default_branch
|
|
42
|
+
return @default_branch if defined?(@default_branch)
|
|
43
|
+
|
|
44
|
+
@default_branch = default_ref&.delete_prefix('origin/')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Commits of the current branch, newest first. Empty for a branch without commits.
|
|
48
|
+
def commits(limit: COMMITS_LIMIT)
|
|
49
|
+
@bash.exec('git', 'log', "--max-count=#{limit}", "--format=#{COMMIT_FORMAT}")
|
|
50
|
+
.each_line(chomp: true).reject(&:empty?).map { |line| Commit.parse(line) }
|
|
51
|
+
rescue Git::CommandError
|
|
52
|
+
[]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Full sha of a commit reference, or nil when it does not name a commit
|
|
56
|
+
def resolve_commit(ref)
|
|
57
|
+
stdout, _stderr, success = @bash.capture('git', 'rev-parse', '--verify', '--quiet',
|
|
58
|
+
"#{ref}^{commit}")
|
|
59
|
+
success ? stdout.strip : nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def checkout(branch)
|
|
63
|
+
run('git', 'checkout', branch, '--')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def delete_branch(branch, force: false)
|
|
67
|
+
run('git', 'branch', force ? '-D' : '-d', branch)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def reset(commit, mode:)
|
|
71
|
+
run('git', 'reset', "--#{mode}", commit, '--')
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def branch_lines
|
|
77
|
+
@branch_lines ||= @bash.exec('git', 'for-each-ref', "--format=#{BRANCH_FORMAT}",
|
|
78
|
+
'refs/heads/')
|
|
79
|
+
.each_line(chomp: true).reject(&:empty?)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Ref used to check merge status: origin/HEAD's target, else a local main/master
|
|
83
|
+
def default_ref
|
|
84
|
+
return @default_ref if defined?(@default_ref)
|
|
85
|
+
|
|
86
|
+
@default_ref = remote_default_ref || local_default_branch
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def remote_default_ref
|
|
90
|
+
ref = @bash.exec('git', 'symbolic-ref', '--quiet', '--short',
|
|
91
|
+
'refs/remotes/origin/HEAD').strip
|
|
92
|
+
ref.empty? ? nil : ref
|
|
93
|
+
rescue Git::CommandError
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def local_default_branch
|
|
98
|
+
names = branch_lines.map { |line| line.split(SEPARATOR, 2).first }
|
|
99
|
+
FALLBACK_DEFAULT_BRANCHES.find { |name| names.include?(name) }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def merged_branch_names
|
|
103
|
+
return nil unless default_ref
|
|
104
|
+
|
|
105
|
+
@bash.exec('git', 'for-each-ref', "--merged=#{default_ref}", '--format=%(refname:short)',
|
|
106
|
+
'refs/heads/')
|
|
107
|
+
.each_line(chomp: true).to_set
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def run(*)
|
|
111
|
+
stdout, stderr, success = @bash.capture(*)
|
|
112
|
+
Result.new(success: success,
|
|
113
|
+
output: [stdout, stderr].map(&:strip).reject(&:empty?).join("\n"))
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
data/lib/gitflash/ui.rb
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Gitflash
|
|
6
|
+
# Everything the user sees: human text or JSON, menus and confirmations.
|
|
7
|
+
# Menus and confirmations are only shown in a terminal and never in JSON mode.
|
|
8
|
+
#
|
|
9
|
+
# JSON output always uses the same envelope (see schema/v1.json):
|
|
10
|
+
# { schema, command, ok, status, dry_run, plan?, result?, error? }
|
|
11
|
+
class Ui
|
|
12
|
+
JSON_SCHEMA_VERSION = 1
|
|
13
|
+
OK_STATUSES = %w[done planned noop cancelled].freeze
|
|
14
|
+
|
|
15
|
+
def initialize(command:, json: false, yes: false, dry_run: false)
|
|
16
|
+
@command = command
|
|
17
|
+
@json = json
|
|
18
|
+
@yes = yes
|
|
19
|
+
@dry_run = dry_run
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def json?
|
|
23
|
+
@json
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def dry_run?
|
|
27
|
+
@dry_run
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def interactive?
|
|
31
|
+
!json? && $stdin.tty?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Reports the outcome of a command and returns its exit status.
|
|
35
|
+
# `status` is one of done, planned, noop, cancelled or failed.
|
|
36
|
+
def report(status:, text:, plan: nil, result: nil, error: nil)
|
|
37
|
+
if json?
|
|
38
|
+
document = envelope(status: status, plan: plan, result: result, error: error)
|
|
39
|
+
$stdout.puts JSON.generate(document)
|
|
40
|
+
else
|
|
41
|
+
$stdout.puts text
|
|
42
|
+
end
|
|
43
|
+
OK_STATUSES.include?(status) ? 0 : 1
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def error(error)
|
|
47
|
+
if json?
|
|
48
|
+
details = { code: error.code, message: error.message, exit_code: error.exit_code }
|
|
49
|
+
$stdout.puts JSON.generate(envelope(status: error.status, plan: error.plan, error: details))
|
|
50
|
+
else
|
|
51
|
+
warn error.message
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def select(message, choices, **)
|
|
56
|
+
prompt.select(message, choices, **)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def multi_select(message, choices, **)
|
|
60
|
+
prompt.multi_select(message, choices, **)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# True when the action may proceed: --yes was given or the user confirmed in a terminal.
|
|
64
|
+
# Raises ConfirmationRequired, carrying the plan, when nobody can confirm.
|
|
65
|
+
def confirm?(summary, plan:)
|
|
66
|
+
return true if @yes
|
|
67
|
+
|
|
68
|
+
message = "#{summary}\nRe-run with --yes to confirm."
|
|
69
|
+
raise ConfirmationRequired.new(message, plan: plan) unless interactive?
|
|
70
|
+
|
|
71
|
+
prompt.proceed_with_warning(summary) { true } == true
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def envelope(status:, plan: nil, result: nil, error: nil)
|
|
77
|
+
{
|
|
78
|
+
schema: JSON_SCHEMA_VERSION,
|
|
79
|
+
command: @command,
|
|
80
|
+
ok: OK_STATUSES.include?(status),
|
|
81
|
+
status: status,
|
|
82
|
+
dry_run: dry_run?,
|
|
83
|
+
plan: plan,
|
|
84
|
+
result: result,
|
|
85
|
+
error: error
|
|
86
|
+
}.compact
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def prompt
|
|
90
|
+
@prompt ||= Prompt.create
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gitflash
|
|
4
|
+
# Raised for invalid arguments or when input is needed but no terminal is available
|
|
5
|
+
class UsageError < Error
|
|
6
|
+
def initialize(message = nil, code: 'invalid_usage', plan: nil)
|
|
7
|
+
super
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def exit_code
|
|
11
|
+
2
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/gitflash/version.rb
CHANGED
data/schema/v1.json
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/Kleanthismits/git_flash/blob/main/schema/v1.json",
|
|
4
|
+
"title": "gitflash --json output, schema version 1",
|
|
5
|
+
"description": "Every gitflash command run with --json prints exactly one object in this envelope. `ok` is true for the statuses done, planned, noop and cancelled. Fields may be added in later releases of version 1; existing fields keep their meaning.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["schema", "command", "ok", "status", "dry_run"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema": { "const": 1 },
|
|
10
|
+
"command": { "enum": ["branches", "checkout", "delete", "reset"] },
|
|
11
|
+
"ok": { "type": "boolean" },
|
|
12
|
+
"status": {
|
|
13
|
+
"description": "done: the change was made. planned: --dry-run, nothing changed. noop: nothing to do. cancelled: the user declined. failed: git refused part of the change. confirmation_required: re-run with --yes after reviewing `plan`. error: invalid input or environment.",
|
|
14
|
+
"enum": ["done", "planned", "noop", "cancelled", "failed", "confirmation_required", "error"]
|
|
15
|
+
},
|
|
16
|
+
"dry_run": { "type": "boolean" },
|
|
17
|
+
"plan": { "type": "object", "description": "The change that will be, would be or was made." },
|
|
18
|
+
"result": { "type": "object", "description": "What happened, including what is needed to revert it." },
|
|
19
|
+
"error": { "$ref": "#/$defs/error" }
|
|
20
|
+
},
|
|
21
|
+
"allOf": [
|
|
22
|
+
{
|
|
23
|
+
"if": { "properties": { "status": { "enum": ["done", "planned", "noop", "cancelled"] } } },
|
|
24
|
+
"then": { "properties": { "ok": { "const": true } }, "not": { "required": ["error"] } },
|
|
25
|
+
"else": { "properties": { "ok": { "const": false } }, "required": ["error"] }
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"if": { "properties": { "status": { "const": "confirmation_required" } } },
|
|
29
|
+
"then": { "required": ["plan"] }
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"if": { "properties": { "command": { "const": "branches" } } },
|
|
33
|
+
"then": { "properties": { "result": { "$ref": "#/$defs/branches_result" } } }
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"if": { "properties": { "command": { "const": "checkout" } } },
|
|
37
|
+
"then": {
|
|
38
|
+
"properties": {
|
|
39
|
+
"plan": { "$ref": "#/$defs/checkout_plan" },
|
|
40
|
+
"result": { "$ref": "#/$defs/checkout_result" }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"if": { "properties": { "command": { "const": "delete" } } },
|
|
46
|
+
"then": {
|
|
47
|
+
"properties": {
|
|
48
|
+
"plan": { "$ref": "#/$defs/delete_plan" },
|
|
49
|
+
"result": { "$ref": "#/$defs/delete_result" }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"if": { "properties": { "command": { "const": "reset" } } },
|
|
55
|
+
"then": {
|
|
56
|
+
"properties": {
|
|
57
|
+
"plan": { "$ref": "#/$defs/reset_plan" },
|
|
58
|
+
"result": { "$ref": "#/$defs/reset_result" }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"$defs": {
|
|
64
|
+
"sha": { "type": "string", "pattern": "^[0-9a-f]{4,64}$" },
|
|
65
|
+
"error": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"required": ["code", "message", "exit_code"],
|
|
68
|
+
"properties": {
|
|
69
|
+
"code": {
|
|
70
|
+
"description": "Stable identifier to branch on; the message is for humans.",
|
|
71
|
+
"enum": [
|
|
72
|
+
"not_a_repository", "input_required", "invalid_usage", "invalid_options",
|
|
73
|
+
"unknown_branch", "unknown_commit", "protected_branch",
|
|
74
|
+
"confirmation_required", "git_failed", "failed"
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
"message": { "type": "string" },
|
|
78
|
+
"exit_code": { "enum": [1, 2] }
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"branch": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"required": [
|
|
84
|
+
"name", "sha", "current", "default", "upstream", "upstream_gone", "ahead", "behind",
|
|
85
|
+
"merged", "last_commit_at", "last_commit_author", "last_commit_subject"
|
|
86
|
+
],
|
|
87
|
+
"properties": {
|
|
88
|
+
"name": { "type": "string" },
|
|
89
|
+
"sha": { "$ref": "#/$defs/sha" },
|
|
90
|
+
"current": { "type": "boolean" },
|
|
91
|
+
"default": { "type": "boolean", "description": "The branch origin/HEAD points to, else main or master." },
|
|
92
|
+
"upstream": { "type": ["string", "null"] },
|
|
93
|
+
"upstream_gone": { "type": "boolean", "description": "The upstream branch was deleted on the remote." },
|
|
94
|
+
"ahead": { "type": "integer", "minimum": 0 },
|
|
95
|
+
"behind": { "type": "integer", "minimum": 0 },
|
|
96
|
+
"merged": { "type": ["boolean", "null"], "description": "Merged into the default branch; null when there is no default branch." },
|
|
97
|
+
"last_commit_at": { "type": "string", "format": "date-time" },
|
|
98
|
+
"last_commit_author": { "type": "string" },
|
|
99
|
+
"last_commit_subject": { "type": "string" }
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"branches_result": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"required": ["default_branch", "branches"],
|
|
105
|
+
"properties": {
|
|
106
|
+
"default_branch": { "type": ["string", "null"] },
|
|
107
|
+
"branches": { "type": "array", "items": { "$ref": "#/$defs/branch" } }
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"checkout_plan": {
|
|
111
|
+
"type": "object",
|
|
112
|
+
"required": ["branch"],
|
|
113
|
+
"properties": { "branch": { "type": "string" } }
|
|
114
|
+
},
|
|
115
|
+
"checkout_result": {
|
|
116
|
+
"type": "object",
|
|
117
|
+
"required": ["branch", "previous_branch"],
|
|
118
|
+
"properties": {
|
|
119
|
+
"branch": { "type": ["string", "null"] },
|
|
120
|
+
"previous_branch": { "type": ["string", "null"], "description": "Check this out to go back; null on a detached HEAD." }
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"delete_plan": {
|
|
124
|
+
"type": "object",
|
|
125
|
+
"required": ["branches", "force"],
|
|
126
|
+
"properties": {
|
|
127
|
+
"branches": { "type": "array", "items": { "type": "string" } },
|
|
128
|
+
"force": { "type": "boolean" }
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"delete_result": {
|
|
132
|
+
"type": "object",
|
|
133
|
+
"required": ["deleted", "failed"],
|
|
134
|
+
"properties": {
|
|
135
|
+
"deleted": {
|
|
136
|
+
"type": "array",
|
|
137
|
+
"description": "Recreate a branch with: git branch NAME SHA",
|
|
138
|
+
"items": {
|
|
139
|
+
"type": "object",
|
|
140
|
+
"required": ["branch", "sha"],
|
|
141
|
+
"properties": { "branch": { "type": "string" }, "sha": { "$ref": "#/$defs/sha" } }
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"failed": {
|
|
145
|
+
"type": "array",
|
|
146
|
+
"items": {
|
|
147
|
+
"type": "object",
|
|
148
|
+
"required": ["branch", "error"],
|
|
149
|
+
"properties": { "branch": { "type": "string" }, "error": { "type": "string" } }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"reset_plan": {
|
|
155
|
+
"type": "object",
|
|
156
|
+
"required": ["commit", "mode"],
|
|
157
|
+
"properties": {
|
|
158
|
+
"commit": { "$ref": "#/$defs/sha" },
|
|
159
|
+
"mode": { "enum": ["soft", "mixed", "hard"] }
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"reset_result": {
|
|
163
|
+
"type": "object",
|
|
164
|
+
"required": ["commit", "mode", "previous_commit"],
|
|
165
|
+
"properties": {
|
|
166
|
+
"commit": { "$ref": "#/$defs/sha" },
|
|
167
|
+
"mode": { "enum": ["soft", "mixed", "hard"] },
|
|
168
|
+
"previous_commit": { "$ref": "#/$defs/sha", "description": "Reset to this commit to go back. Uncommitted changes lost by a hard reset cannot be restored this way." }
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gitflash
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kleanthis Mitsioulis
|
|
@@ -73,13 +73,26 @@ files:
|
|
|
73
73
|
- bin/gitflash
|
|
74
74
|
- command_descriptions.yml
|
|
75
75
|
- lib/gitflash.rb
|
|
76
|
+
- lib/gitflash/branch.rb
|
|
76
77
|
- lib/gitflash/cli.rb
|
|
78
|
+
- lib/gitflash/commands/base.rb
|
|
79
|
+
- lib/gitflash/commands/branches.rb
|
|
80
|
+
- lib/gitflash/commands/checkout.rb
|
|
81
|
+
- lib/gitflash/commands/delete.rb
|
|
82
|
+
- lib/gitflash/commands/reset.rb
|
|
83
|
+
- lib/gitflash/commit.rb
|
|
77
84
|
- lib/gitflash/configuration.rb
|
|
85
|
+
- lib/gitflash/confirmation_required.rb
|
|
86
|
+
- lib/gitflash/error.rb
|
|
78
87
|
- lib/gitflash/git/bash_command.rb
|
|
79
88
|
- lib/gitflash/git/command_error.rb
|
|
80
|
-
- lib/gitflash/git/wrapper.rb
|
|
81
89
|
- lib/gitflash/prompt.rb
|
|
90
|
+
- lib/gitflash/repo.rb
|
|
91
|
+
- lib/gitflash/result.rb
|
|
92
|
+
- lib/gitflash/ui.rb
|
|
93
|
+
- lib/gitflash/usage_error.rb
|
|
82
94
|
- lib/gitflash/version.rb
|
|
95
|
+
- schema/v1.json
|
|
83
96
|
homepage: https://github.com/Kleanthismits/git_flash
|
|
84
97
|
licenses:
|
|
85
98
|
- MIT
|