gitflash 0.5.0 → 0.5.1
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 +9 -0
- data/lib/gitflash/branch.rb +10 -1
- data/lib/gitflash/commit.rb +1 -1
- data/lib/gitflash/repo.rb +34 -14
- data/lib/gitflash/terminal_text.rb +19 -0
- data/lib/gitflash/ui.rb +2 -2
- data/lib/gitflash/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 86ac16bd9cd3c64cd98305a6423e67c05187040d8caba798d132a4f985ee28df
|
|
4
|
+
data.tar.gz: 8da24e90611e20296f70ca3cadab5a77f697c97ff43cf535b9f51b1bd40a66be
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c573a9265e4558a9d502840cb7b65b0c07c6e6f640c74bde499ed1b4ff160fd8aeb101a9ecd95b692a2170c812d0a6afd14d8abe8629d46b8e65732a7622384c
|
|
7
|
+
data.tar.gz: 53e55b8afcc5f32cd1b0fa238e0f7db356b4e919d57f606ebec37ee3a86539fff2dd0afaef3b5bf6f295f235e9f4452ec1b5bb3dee5dc7fd1df964cb3e6f3d85
|
data/CHANGELOG.md
CHANGED
|
@@ -53,3 +53,12 @@
|
|
|
53
53
|
- Results include what is needed to revert: deleted branch SHAs, the previous branch after `checkout`, the previous commit after `reset`
|
|
54
54
|
- Add `schema` command that prints the JSON Schema
|
|
55
55
|
- Internal: new `Repo` service layer with `Branch` and `Commit` records; integration specs run against real temporary git repositories
|
|
56
|
+
|
|
57
|
+
## [0.5.1] - 2026-10-04
|
|
58
|
+
|
|
59
|
+
Security fixes for names and text that come from the repository:
|
|
60
|
+
|
|
61
|
+
- `checkout` uses `git switch --` and refuses a branch name that starts with a dash. A local ref named like `--force` (for example from a cloned repository) used to become a `git checkout --force` option and discard uncommitted changes
|
|
62
|
+
- Branch and default-branch detection use full ref names. A tag named like `origin/<default>` could change the short name of `origin/HEAD`, so the default branch lost its deletion protection and its merge status; `origin/HEAD` must now point inside `refs/remotes/origin/`
|
|
63
|
+
- `delete` passes `--` before the branch name; `resolve_commit` refuses references that start with a dash
|
|
64
|
+
- Text output shows terminal control characters of repository-controlled text (commit subjects, git errors) as escapes such as `\x1B`, so a crafted commit subject cannot move the cursor, rewrite output or set the clipboard (OSC 52). JSON output keeps the original text
|
data/lib/gitflash/branch.rb
CHANGED
|
@@ -11,7 +11,7 @@ module Gitflash
|
|
|
11
11
|
# Parses one line produced with Repo::BRANCH_FORMAT.
|
|
12
12
|
# `merged_names` is nil when merge status is unknown.
|
|
13
13
|
def self.parse(line, default_branch: nil, merged_names: nil)
|
|
14
|
-
name, sha, head, upstream, track, date, author, subject = line
|
|
14
|
+
name, sha, head, upstream, track, date, author, subject = fields(line)
|
|
15
15
|
|
|
16
16
|
new(
|
|
17
17
|
name: name,
|
|
@@ -29,6 +29,15 @@ module Gitflash
|
|
|
29
29
|
)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# The record's fields; the full ref name is shortened by removing refs/heads/ only, because
|
|
33
|
+
# `refname:short` gives another name when a tag has the same name.
|
|
34
|
+
def self.fields(line)
|
|
35
|
+
fields = line.split(Repo::SEPARATOR, 8)
|
|
36
|
+
fields[0] = fields[0].delete_prefix(Repo::HEADS)
|
|
37
|
+
fields
|
|
38
|
+
end
|
|
39
|
+
private_class_method :fields
|
|
40
|
+
|
|
32
41
|
alias_method :current?, :current
|
|
33
42
|
alias_method :default?, :default
|
|
34
43
|
alias_method :upstream_gone?, :upstream_gone
|
data/lib/gitflash/commit.rb
CHANGED
data/lib/gitflash/repo.rb
CHANGED
|
@@ -6,12 +6,14 @@ module Gitflash
|
|
|
6
6
|
class Repo
|
|
7
7
|
SEPARATOR = "\x1f"
|
|
8
8
|
BRANCH_FORMAT = %w[
|
|
9
|
-
refname
|
|
9
|
+
refname objectname:short HEAD upstream:short upstream:track,nobracket
|
|
10
10
|
committerdate:iso-strict authorname subject
|
|
11
11
|
].map { |field| "%(#{field})" }.join('%1f')
|
|
12
12
|
COMMIT_FORMAT = %w[%h %s %an %cI].join('%x1f')
|
|
13
13
|
COMMITS_LIMIT = 100
|
|
14
14
|
FALLBACK_DEFAULT_BRANCHES = %w[main master].freeze
|
|
15
|
+
HEADS = 'refs/heads/'
|
|
16
|
+
REMOTE_HEAD = 'refs/remotes/origin/'
|
|
15
17
|
|
|
16
18
|
def initialize(bash: Git::BashCommand)
|
|
17
19
|
@bash = bash
|
|
@@ -41,7 +43,7 @@ module Gitflash
|
|
|
41
43
|
def default_branch
|
|
42
44
|
return @default_branch if defined?(@default_branch)
|
|
43
45
|
|
|
44
|
-
@default_branch = default_ref
|
|
46
|
+
@default_branch = branch_name(default_ref)
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
# Commits of the current branch, newest first. Empty for a branch without commits.
|
|
@@ -54,17 +56,22 @@ module Gitflash
|
|
|
54
56
|
|
|
55
57
|
# Full sha of a commit reference, or nil when it does not name a commit
|
|
56
58
|
def resolve_commit(ref)
|
|
59
|
+
return nil if ref.start_with?('-')
|
|
60
|
+
|
|
57
61
|
stdout, _stderr, success = @bash.capture('git', 'rev-parse', '--verify', '--quiet',
|
|
58
62
|
"#{ref}^{commit}")
|
|
59
63
|
success ? stdout.strip : nil
|
|
60
64
|
end
|
|
61
65
|
|
|
66
|
+
# Uses `git switch`, which never discards changes or treats the name as a file path.
|
|
67
|
+
# A name that starts with a dash is refused: a ref named like an option must not become one.
|
|
62
68
|
def checkout(branch)
|
|
63
|
-
|
|
69
|
+
refuse_option_like!(branch)
|
|
70
|
+
run('git', 'switch', '--', branch)
|
|
64
71
|
end
|
|
65
72
|
|
|
66
73
|
def delete_branch(branch, force: false)
|
|
67
|
-
run('git', 'branch', force ? '-D' : '-d', branch)
|
|
74
|
+
run('git', 'branch', force ? '-D' : '-d', '--', branch)
|
|
68
75
|
end
|
|
69
76
|
|
|
70
77
|
def reset(commit, mode:)
|
|
@@ -73,13 +80,27 @@ module Gitflash
|
|
|
73
80
|
|
|
74
81
|
private
|
|
75
82
|
|
|
83
|
+
# Removes only the prefix the ref starts with, so a branch named `refs/heads/x` keeps its name
|
|
84
|
+
def branch_name(ref)
|
|
85
|
+
return nil unless ref
|
|
86
|
+
|
|
87
|
+
ref.delete_prefix(ref.start_with?(REMOTE_HEAD) ? REMOTE_HEAD : HEADS)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def refuse_option_like!(name)
|
|
91
|
+
return unless name.start_with?('-')
|
|
92
|
+
|
|
93
|
+
raise UsageError.new("Invalid branch name '#{name}'", code: 'invalid_usage')
|
|
94
|
+
end
|
|
95
|
+
|
|
76
96
|
def branch_lines
|
|
77
97
|
@branch_lines ||= @bash.exec('git', 'for-each-ref', "--format=#{BRANCH_FORMAT}",
|
|
78
|
-
|
|
98
|
+
HEADS)
|
|
79
99
|
.each_line(chomp: true).reject(&:empty?)
|
|
80
100
|
end
|
|
81
101
|
|
|
82
|
-
#
|
|
102
|
+
# Full ref used to check merge status: origin/HEAD's target, else a local main/master.
|
|
103
|
+
# Full names are used throughout because short names change when a tag has the same name.
|
|
83
104
|
def default_ref
|
|
84
105
|
return @default_ref if defined?(@default_ref)
|
|
85
106
|
|
|
@@ -87,24 +108,23 @@ module Gitflash
|
|
|
87
108
|
end
|
|
88
109
|
|
|
89
110
|
def remote_default_ref
|
|
90
|
-
ref = @bash.exec('git', 'symbolic-ref', '--quiet',
|
|
91
|
-
|
|
92
|
-
ref.empty? ? nil : ref
|
|
111
|
+
ref = @bash.exec('git', 'symbolic-ref', '--quiet', "#{REMOTE_HEAD}HEAD").strip
|
|
112
|
+
ref.start_with?(REMOTE_HEAD) ? ref : nil
|
|
93
113
|
rescue Git::CommandError
|
|
94
114
|
nil
|
|
95
115
|
end
|
|
96
116
|
|
|
97
117
|
def local_default_branch
|
|
98
|
-
names = branch_lines.map { |line| line.split(SEPARATOR, 2).first }
|
|
99
|
-
FALLBACK_DEFAULT_BRANCHES.find { |
|
|
118
|
+
names = branch_lines.map { |line| line.split(SEPARATOR, 2).first.delete_prefix(HEADS) }
|
|
119
|
+
name = FALLBACK_DEFAULT_BRANCHES.find { |candidate| names.include?(candidate) }
|
|
120
|
+
name && "#{HEADS}#{name}"
|
|
100
121
|
end
|
|
101
122
|
|
|
102
123
|
def merged_branch_names
|
|
103
124
|
return nil unless default_ref
|
|
104
125
|
|
|
105
|
-
@bash.exec('git', 'for-each-ref', "--merged=#{default_ref}", '--format=%(refname
|
|
106
|
-
|
|
107
|
-
.each_line(chomp: true).to_set
|
|
126
|
+
@bash.exec('git', 'for-each-ref', "--merged=#{default_ref}", '--format=%(refname)', HEADS)
|
|
127
|
+
.each_line(chomp: true).to_set { |ref| ref.delete_prefix(HEADS) }
|
|
108
128
|
end
|
|
109
129
|
|
|
110
130
|
def run(*)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gitflash
|
|
4
|
+
# Makes text from the repository safe to print in a terminal. Commit subjects, author names
|
|
5
|
+
# and git messages are written by other people; control characters in them could move the
|
|
6
|
+
# cursor, rewrite earlier output or set the clipboard (OSC 52). They are shown as escapes.
|
|
7
|
+
# Newlines and tabs stay.
|
|
8
|
+
module TerminalText
|
|
9
|
+
UNSAFE = /[\u0000-\u0008\u000B-\u001F\u007F-\u009F\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def safe(text)
|
|
14
|
+
text.to_s.scrub.gsub(UNSAFE) do |char|
|
|
15
|
+
format(char.ord > 0xFF ? '\\u%04X' : '\\x%02X', char.ord)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/gitflash/ui.rb
CHANGED
|
@@ -38,7 +38,7 @@ module Gitflash
|
|
|
38
38
|
document = envelope(status: status, plan: plan, result: result, error: error)
|
|
39
39
|
$stdout.puts JSON.generate(document)
|
|
40
40
|
else
|
|
41
|
-
$stdout.puts text
|
|
41
|
+
$stdout.puts TerminalText.safe(text)
|
|
42
42
|
end
|
|
43
43
|
OK_STATUSES.include?(status) ? 0 : 1
|
|
44
44
|
end
|
|
@@ -48,7 +48,7 @@ module Gitflash
|
|
|
48
48
|
details = { code: error.code, message: error.message, exit_code: error.exit_code }
|
|
49
49
|
$stdout.puts JSON.generate(envelope(status: error.status, plan: error.plan, error: details))
|
|
50
50
|
else
|
|
51
|
-
warn error.message
|
|
51
|
+
warn TerminalText.safe(error.message)
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
|
data/lib/gitflash/version.rb
CHANGED
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.5.
|
|
4
|
+
version: 0.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kleanthis Mitsioulis
|
|
@@ -89,6 +89,7 @@ files:
|
|
|
89
89
|
- lib/gitflash/prompt.rb
|
|
90
90
|
- lib/gitflash/repo.rb
|
|
91
91
|
- lib/gitflash/result.rb
|
|
92
|
+
- lib/gitflash/terminal_text.rb
|
|
92
93
|
- lib/gitflash/ui.rb
|
|
93
94
|
- lib/gitflash/usage_error.rb
|
|
94
95
|
- lib/gitflash/version.rb
|
|
@@ -114,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
114
115
|
- !ruby/object:Gem::Version
|
|
115
116
|
version: '0'
|
|
116
117
|
requirements: []
|
|
117
|
-
rubygems_version:
|
|
118
|
+
rubygems_version: 4.0.22
|
|
118
119
|
specification_version: 4
|
|
119
120
|
summary: Simplified usage of some git cli commands
|
|
120
121
|
test_files: []
|