git-status-tree 1.0.1 → 3.0.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/bin/git-status-tree +27 -3
- data/ext/git_tree/extconf.rb +5 -3
- data/lib/bash_color.rb +1 -1
- data/lib/node.rb +223 -208
- data/lib/nodes_collection.rb +194 -179
- data/lib/rubygems_plugin.rb +2 -0
- data/lib/version.rb +7 -0
- data/src/git_status_tree.rb +25 -9
- metadata +5 -8
- data/src/git-status-tree.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 261d0c82a5a74d301f58b8524456d4c2fbe29feac0f120e459f2ebd154a0ca57
|
4
|
+
data.tar.gz: dbe0de28748947459d8a6d1be0a535f69c7c72abb445565a4d05d667d99925dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0283802e22db622c566a797487849add4b5d08d607e8bed7bdc406be7da782f353a464894e0b45f62434c78d314785e98e674cb0bbc56b3dba3087250ce200e
|
7
|
+
data.tar.gz: 1282ceceb1e13cfacec5292c9c705b4c2ad2981559bf705b6dc3d1513987b2d54c8a4c3d85142df870c245ebedd290f69c2ca7a1f9dfe07a63754cbe1eaf05af
|
data/bin/git-status-tree
CHANGED
@@ -1,6 +1,30 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
#
|
2
|
+
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
4
|
+
require 'optparse'
|
5
|
+
require File.join File.dirname(__FILE__), '../src/git_status_tree'
|
6
|
+
require File.join File.dirname(__FILE__), '../lib/version'
|
5
7
|
|
6
|
-
|
8
|
+
parser = OptionParser.new do |opts|
|
9
|
+
opts.banner = 'Usage: git-status-tree [options]'
|
10
|
+
|
11
|
+
opts.on('-v', '--version', 'Show version') do
|
12
|
+
puts "git-status-tree #{GitStatusTree::VERSION}"
|
13
|
+
exit 0
|
14
|
+
end
|
15
|
+
|
16
|
+
opts.on('-h', '--help', 'Show this help message') do
|
17
|
+
puts opts
|
18
|
+
exit 0
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
begin
|
23
|
+
parser.parse!
|
24
|
+
rescue OptionParser::InvalidOption => e
|
25
|
+
puts "Error: #{e.message}"
|
26
|
+
puts parser
|
27
|
+
exit 1
|
28
|
+
end
|
29
|
+
|
30
|
+
puts GitStatusTree.new
|
data/ext/git_tree/extconf.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'mkmf'
|
2
4
|
|
3
5
|
find_executable('bash')
|
@@ -5,11 +7,11 @@ find_executable('git')
|
|
5
7
|
find_executable('make')
|
6
8
|
|
7
9
|
# Trick Rubygems into thinking the generated Makefile was executed
|
8
|
-
compile = File.join(Dir.pwd,
|
9
|
-
File.open(compile,
|
10
|
+
compile = File.join(Dir.pwd, "git_tree.#{RbConfig::CONFIG['DLEXT']}")
|
11
|
+
File.open(compile, 'w') {}
|
10
12
|
|
11
13
|
# Install "git tree"
|
12
14
|
puts `../../bin/git_add_alias_tree`
|
13
15
|
|
14
16
|
# Trick Rubygems into thinking the Makefile was executed
|
15
|
-
$makefile_created = true
|
17
|
+
$makefile_created = true # rubocop:disable Style/GlobalVars
|
data/lib/bash_color.rb
CHANGED
data/lib/node.rb
CHANGED
@@ -1,208 +1,223 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
class NodeNameError < StandardError; end
|
4
|
-
class NodeChildrenError < StandardError; end
|
5
|
-
class NodeTypeError < StandardError; end
|
6
|
-
|
7
|
-
# A Node represents a file or directory in the git-status-tree
|
8
|
-
class Node
|
9
|
-
class << self
|
10
|
-
attr_accessor :indent
|
11
|
-
end
|
12
|
-
|
13
|
-
attr_accessor :status, :name, :children
|
14
|
-
|
15
|
-
def initialize(name, children = nil, status = nil)
|
16
|
-
self.class.indent ||= 4
|
17
|
-
|
18
|
-
|
19
|
-
msg = '"
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
ary_nodes =
|
43
|
-
|
44
|
-
|
45
|
-
if ary_nodes.any?
|
46
|
-
children = NodesCollection.create_from_array(ary_nodes, status)
|
47
|
-
|
48
|
-
else
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
def self.
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
end
|
178
|
-
|
179
|
-
#
|
180
|
-
def
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class NodeNameError < StandardError; end
|
4
|
+
class NodeChildrenError < StandardError; end
|
5
|
+
class NodeTypeError < StandardError; end
|
6
|
+
|
7
|
+
# A Node represents a file or directory in the git-status-tree
|
8
|
+
class Node # rubocop:disable Metrics/ClassLength
|
9
|
+
class << self
|
10
|
+
attr_accessor :indent
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :status, :name, :children
|
14
|
+
|
15
|
+
def initialize(name, children = nil, status = nil)
|
16
|
+
self.class.indent ||= 4
|
17
|
+
validate_name!(name)
|
18
|
+
|
19
|
+
msg = '"children" must be a NodesCollection or nil.'
|
20
|
+
valid_nodes_collection = children.nil? || children.is_a?(NodesCollection)
|
21
|
+
raise NodeChildrenError, msg unless valid_nodes_collection
|
22
|
+
|
23
|
+
@name = name
|
24
|
+
@children = children
|
25
|
+
@status = status || '??'
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.create_from_string(gs_porcelain)
|
29
|
+
msg = '"str_node" must be String.'
|
30
|
+
raise NodeTypeError, msg unless gs_porcelain.is_a? String
|
31
|
+
raise NodeNameError, '"str_node" too short.' if gs_porcelain.length < 4
|
32
|
+
|
33
|
+
node_from_gs(gs_porcelain)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.instances?
|
37
|
+
->(node) { node.is_a?(Node) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.node_from_gs(gs_porcelain)
|
41
|
+
status = status(gs_porcelain)
|
42
|
+
ary_nodes = "./#{gs_porcelain[3..]}".split(%r{/})
|
43
|
+
|
44
|
+
name = ary_nodes.shift
|
45
|
+
if ary_nodes.any?
|
46
|
+
children = NodesCollection.create_from_array(ary_nodes, status)
|
47
|
+
new(name, children)
|
48
|
+
else
|
49
|
+
new(name, nil, status)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private_class_method :node_from_gs
|
54
|
+
|
55
|
+
def self.status(gs_porcelain)
|
56
|
+
status = "#{gs_porcelain[0]}+" if gs_porcelain[1] == ' '
|
57
|
+
status = gs_porcelain[1] unless gs_porcelain[1] == ' '
|
58
|
+
status
|
59
|
+
end
|
60
|
+
private_class_method :status
|
61
|
+
|
62
|
+
def to_primitive
|
63
|
+
if dir?
|
64
|
+
{ name => children.to_primitive }
|
65
|
+
else
|
66
|
+
name
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def file?
|
71
|
+
children.nil?
|
72
|
+
end
|
73
|
+
|
74
|
+
def dir?
|
75
|
+
!file?
|
76
|
+
end
|
77
|
+
|
78
|
+
def valid?
|
79
|
+
return valid_dir? if dir?
|
80
|
+
return valid_file? if file?
|
81
|
+
|
82
|
+
false
|
83
|
+
end
|
84
|
+
|
85
|
+
# @return [NodesCollection]
|
86
|
+
def +(other)
|
87
|
+
raise 'not valid' unless valid? && other.valid?
|
88
|
+
raise "not a #{self.class}" unless other.is_a?(self.class)
|
89
|
+
|
90
|
+
tmp_children = [children, other.children].compact.inject(&:+)
|
91
|
+
|
92
|
+
NodesCollection.new([self.class.new(name, tmp_children)])
|
93
|
+
end
|
94
|
+
|
95
|
+
def <=>(other)
|
96
|
+
return (name <=> other.name) if file? == other.file?
|
97
|
+
return -1 if dir? && other.file?
|
98
|
+
return 1 if file? && other.dir?
|
99
|
+
|
100
|
+
0
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_tree_s(depth = 0, open_parents = [0], last: true)
|
104
|
+
open_parents << depth
|
105
|
+
|
106
|
+
pre = pre_tree(depth, open_parents, last)
|
107
|
+
|
108
|
+
str_tree = "#{pre}#{color_name}\n"
|
109
|
+
str_tree += children.to_tree_s(depth + 1, open_parents) if children
|
110
|
+
|
111
|
+
str_tree
|
112
|
+
end
|
113
|
+
|
114
|
+
# 'M' modified
|
115
|
+
def modified?
|
116
|
+
status.include?('M')
|
117
|
+
end
|
118
|
+
|
119
|
+
# 'A' added
|
120
|
+
def added?
|
121
|
+
status.include?('A')
|
122
|
+
end
|
123
|
+
|
124
|
+
# 'D' deleted
|
125
|
+
def deleted?
|
126
|
+
status.include?('D')
|
127
|
+
end
|
128
|
+
|
129
|
+
# 'R' renamed
|
130
|
+
def renamed?
|
131
|
+
status.include?('R')
|
132
|
+
end
|
133
|
+
|
134
|
+
# 'C' copied
|
135
|
+
def copied?
|
136
|
+
status.include?('C')
|
137
|
+
end
|
138
|
+
|
139
|
+
# 'U' updated but unmerged
|
140
|
+
def unmerged?
|
141
|
+
status.include?('U')
|
142
|
+
end
|
143
|
+
|
144
|
+
# '?' new
|
145
|
+
def new?
|
146
|
+
status.include?('?')
|
147
|
+
end
|
148
|
+
|
149
|
+
# '+' staged
|
150
|
+
def staged?
|
151
|
+
status.include?('+')
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def validate_name!(name)
|
157
|
+
msg = '"name" must be a String.'
|
158
|
+
raise NodeNameError, msg unless name.is_a? String
|
159
|
+
|
160
|
+
msg = '"name" must have at least one character.'
|
161
|
+
raise NodeNameError, msg if name.empty?
|
162
|
+
|
163
|
+
msg = '"name" must not contain "/", use create_from_string.'
|
164
|
+
raise NodeNameError, msg if name =~ %r{/}
|
165
|
+
end
|
166
|
+
|
167
|
+
def color_name
|
168
|
+
color_name = ''
|
169
|
+
if dir?
|
170
|
+
color_name += BashColor::EMB + name
|
171
|
+
else
|
172
|
+
color_name += BashColor::G if staged?
|
173
|
+
color_name += BashColor::R unless staged?
|
174
|
+
color_name += "#{name} (#{status})"
|
175
|
+
end
|
176
|
+
color_name + BashColor::NONE
|
177
|
+
end
|
178
|
+
|
179
|
+
# Has a valid name?
|
180
|
+
def name_valid?
|
181
|
+
name.is_a?(String) &&
|
182
|
+
name.length.positive? &&
|
183
|
+
name.match(%r{/}).nil?
|
184
|
+
end
|
185
|
+
|
186
|
+
# Is a valid dir?
|
187
|
+
def valid_dir?
|
188
|
+
name_valid? &&
|
189
|
+
children.is_a?(NodesCollection) &&
|
190
|
+
children.valid?
|
191
|
+
end
|
192
|
+
|
193
|
+
# Is a valid file?
|
194
|
+
def valid_file?
|
195
|
+
name_valid? &&
|
196
|
+
children.nil?
|
197
|
+
end
|
198
|
+
|
199
|
+
def pre_tree(depth, open_parents, last)
|
200
|
+
if depth.zero?
|
201
|
+
''
|
202
|
+
elsif depth.positive?
|
203
|
+
pre_ary = Array.new(depth).fill(' ')
|
204
|
+
indent = self.class.indent - 2
|
205
|
+
|
206
|
+
open_parents.each { |idx| pre_ary[idx] = "│#{' ' * indent} " if pre_ary[idx] == ' ' }
|
207
|
+
|
208
|
+
sibling(depth, indent, last, open_parents, pre_ary)
|
209
|
+
|
210
|
+
pre_ary * ''
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def sibling(depth, indent, last, open_parents, pre_ary)
|
215
|
+
if last
|
216
|
+
pre_ary[-1] = '└'
|
217
|
+
open_parents.delete(depth - 1)
|
218
|
+
else
|
219
|
+
pre_ary[-1] = '├'
|
220
|
+
end
|
221
|
+
pre_ary[-1] += "#{'─' * indent} "
|
222
|
+
end
|
223
|
+
end
|
data/lib/nodes_collection.rb
CHANGED
@@ -1,179 +1,194 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# error class for invalid NodeCollection types
|
4
|
-
class NodesCollectionTypeError < StandardError
|
5
|
-
end
|
6
|
-
|
7
|
-
# collection of nodes
|
8
|
-
class NodesCollection
|
9
|
-
attr_accessor :nodes
|
10
|
-
|
11
|
-
def self.create_from_string(str_nodes, status = ' ')
|
12
|
-
msg = '"str_nodes" must be String.'
|
13
|
-
raise NodesCollectionTypeError, msg unless str_nodes.is_a? String
|
14
|
-
|
15
|
-
ary_nodes = str_nodes.split(
|
16
|
-
create_from_valid_array(ary_nodes, status)
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.create_from_array(ary_nodes, status)
|
20
|
-
ary_nodes = [ary_nodes].flatten(1)
|
21
|
-
|
22
|
-
msg = '"ary_nodes" must only contain Strings.'
|
23
|
-
are_strings =
|
24
|
-
raise NodesCollectionTypeError, msg unless ary_nodes.all?
|
25
|
-
|
26
|
-
create_from_valid_array(ary_nodes, status)
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.new_from_nodes_array(
|
30
|
-
raise msg unless
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
plain_nodes
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
private_class_method :
|
60
|
-
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
end
|
150
|
-
|
151
|
-
def
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
end
|
162
|
-
|
163
|
-
def
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
end
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# error class for invalid NodeCollection types
|
4
|
+
class NodesCollectionTypeError < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
# collection of nodes
|
8
|
+
class NodesCollection # rubocop:disable Metrics/ClassLength
|
9
|
+
attr_accessor :nodes
|
10
|
+
|
11
|
+
def self.create_from_string(str_nodes, status = ' ')
|
12
|
+
msg = '"str_nodes" must be String.'
|
13
|
+
raise NodesCollectionTypeError, msg unless str_nodes.is_a? String
|
14
|
+
|
15
|
+
ary_nodes = str_nodes.split(%r{/})
|
16
|
+
create_from_valid_array(ary_nodes, status)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.create_from_array(ary_nodes, status)
|
20
|
+
ary_nodes = [ary_nodes].flatten(1)
|
21
|
+
|
22
|
+
msg = '"ary_nodes" must only contain Strings.'
|
23
|
+
are_strings = ->(node) { node.is_a?(String) }
|
24
|
+
raise NodesCollectionTypeError, msg unless ary_nodes.all?(&are_strings)
|
25
|
+
|
26
|
+
create_from_valid_array(ary_nodes, status)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.new_from_nodes_array(all_nodes)
|
30
|
+
raise msg unless all_nodes.all?(&Node.instances?)
|
31
|
+
|
32
|
+
grouped_nodes = all_nodes.group_by(&:name)
|
33
|
+
plain_nodes = plain_nodes(grouped_nodes)
|
34
|
+
merged_nodes = merged_nodes(grouped_nodes)
|
35
|
+
|
36
|
+
new(plain_nodes + merged_nodes)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.create_from_valid_array(ary_nodes, status)
|
40
|
+
name = ary_nodes.shift
|
41
|
+
node = if ary_nodes.any?
|
42
|
+
children = create_from_valid_array(ary_nodes, status)
|
43
|
+
Node.new(name, children)
|
44
|
+
else
|
45
|
+
Node.new(name, nil, status)
|
46
|
+
end
|
47
|
+
new([node])
|
48
|
+
end
|
49
|
+
|
50
|
+
private_class_method :create_from_valid_array
|
51
|
+
|
52
|
+
def self.merged_nodes(grouped_nodes)
|
53
|
+
merged_nodes = []
|
54
|
+
grouped_nodes.each_value do |nodes|
|
55
|
+
merged_nodes << (nodes[0] + nodes[1]).nodes[0] if nodes.length == 2
|
56
|
+
end
|
57
|
+
merged_nodes
|
58
|
+
end
|
59
|
+
private_class_method :merged_nodes
|
60
|
+
|
61
|
+
def self.plain_nodes(grouped_nodes)
|
62
|
+
grouped_nodes.filter { |_, nodes| nodes.length == 1 }.values.flatten(1)
|
63
|
+
end
|
64
|
+
private_class_method :plain_nodes
|
65
|
+
|
66
|
+
def initialize(nodes = [])
|
67
|
+
nodes = [nodes].flatten(1)
|
68
|
+
|
69
|
+
msg = '"nodes" must only contain Nodes.'
|
70
|
+
are_nodes = ->(node) { node.is_a?(Node) }
|
71
|
+
raise NodesCollectionTypeError, msg unless nodes.all?(&are_nodes)
|
72
|
+
|
73
|
+
@nodes = nodes
|
74
|
+
end
|
75
|
+
|
76
|
+
# @return [NodesCollection]
|
77
|
+
def +(other)
|
78
|
+
raise 'not a Node or NodesCollection' unless other.is_a?(Node) || other.is_a?(self.class)
|
79
|
+
|
80
|
+
all_nodes = merge_nodes_with other
|
81
|
+
|
82
|
+
dir_nodes = dir_nodes(all_nodes)
|
83
|
+
file_nodes = file_nodes(all_nodes)
|
84
|
+
|
85
|
+
self.class.new(dir_nodes + file_nodes)
|
86
|
+
end
|
87
|
+
|
88
|
+
# @return [Integer]
|
89
|
+
def <=>(other)
|
90
|
+
to_primitive <=> other.to_primitive
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [Array<Node>]
|
94
|
+
def nodes_not_in(other)
|
95
|
+
self_names = nodes.map(&:name)
|
96
|
+
other_names = other.nodes.map(&:name)
|
97
|
+
|
98
|
+
self_only_names = self_names - (self_names & other_names)
|
99
|
+
|
100
|
+
nodes.select { |node| self_only_names.include?(node.name) }
|
101
|
+
end
|
102
|
+
|
103
|
+
# @return [Array<Node>]
|
104
|
+
def merge_common_nodes_with(other)
|
105
|
+
self_names = nodes.map(&:name)
|
106
|
+
other_names = other.nodes.map(&:name)
|
107
|
+
|
108
|
+
common_names = self_names & other_names
|
109
|
+
all_nodes = nodes + other.nodes
|
110
|
+
|
111
|
+
common_names.map do |name|
|
112
|
+
all_nodes.select { |node| node.name == name }.reduce(&:+).nodes[0]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# @return [Array<Node>]
|
117
|
+
def merge_nodes_with(other)
|
118
|
+
case other
|
119
|
+
when Node
|
120
|
+
nodes_merged = merge_nodes_with_node(other)
|
121
|
+
when NodesCollection
|
122
|
+
nodes_merged = merge_nodes_with_collection(other)
|
123
|
+
end
|
124
|
+
|
125
|
+
nodes_merged
|
126
|
+
end
|
127
|
+
|
128
|
+
def files
|
129
|
+
nodes.select(&:file?)
|
130
|
+
end
|
131
|
+
|
132
|
+
def dirs
|
133
|
+
nodes.select(&:dir?)
|
134
|
+
end
|
135
|
+
|
136
|
+
def sort!
|
137
|
+
nodes.sort!
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_primitive
|
141
|
+
nodes.map(&:to_primitive)
|
142
|
+
end
|
143
|
+
|
144
|
+
def valid?
|
145
|
+
nodes.is_a?(Array) &&
|
146
|
+
nodes.all? { |node| node.is_a?(Node) } &&
|
147
|
+
nodes&.all?(&:valid?)
|
148
|
+
# TODO: compare uniqueness of file and dir names.
|
149
|
+
end
|
150
|
+
|
151
|
+
def to_tree_s(depth = 0, open_parents = [])
|
152
|
+
tree_s = ''
|
153
|
+
|
154
|
+
if nodes.length > 1
|
155
|
+
to_tree_s = ->(node) { node.to_tree_s(depth, open_parents, last: false) }
|
156
|
+
tree_s += nodes[0..-2].map(&to_tree_s) * ''
|
157
|
+
end
|
158
|
+
tree_s += nodes.last.to_tree_s(depth, open_parents)
|
159
|
+
|
160
|
+
tree_s
|
161
|
+
end
|
162
|
+
|
163
|
+
def file_nodes(all_nodes)
|
164
|
+
all_files = all_nodes.select(&:file?)
|
165
|
+
files_collection = self.class.new all_files
|
166
|
+
files_collection.sort!
|
167
|
+
end
|
168
|
+
|
169
|
+
def dir_nodes(all_nodes)
|
170
|
+
all_dirs = all_nodes.select(&:dir?)
|
171
|
+
dirs_collection = self.class.new_from_nodes_array all_dirs
|
172
|
+
dirs_collection.sort!
|
173
|
+
end
|
174
|
+
|
175
|
+
def merge_nodes_with_collection(other)
|
176
|
+
self_dedicated_nodes = nodes_not_in other
|
177
|
+
other_dedicated_nodes = other.nodes_not_in self
|
178
|
+
common_nodes = merge_common_nodes_with other
|
179
|
+
|
180
|
+
self_dedicated_nodes + common_nodes + other_dedicated_nodes
|
181
|
+
end
|
182
|
+
|
183
|
+
def merge_nodes_with_node(other)
|
184
|
+
if nodes.map(&:name).include?(other.name)
|
185
|
+
equal_names = ->(node) { node.name == other.name }
|
186
|
+
collection_merged = nodes.select(&equal_names)[0] + other
|
187
|
+
other = collection_merged.nodes[0]
|
188
|
+
end
|
189
|
+
|
190
|
+
nodes + [other]
|
191
|
+
end
|
192
|
+
|
193
|
+
def add_node(other); end
|
194
|
+
end
|
data/lib/rubygems_plugin.rb
CHANGED
data/lib/version.rb
ADDED
data/src/git_status_tree.rb
CHANGED
@@ -1,18 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.join File.dirname(__FILE__), '../lib/node'
|
4
|
+
require File.join File.dirname(__FILE__), '../lib/nodes_collection'
|
5
|
+
require File.join File.dirname(__FILE__), '../lib/bash_color'
|
6
|
+
require File.join File.dirname(__FILE__), '../src/git_status_tree'
|
7
|
+
|
1
8
|
# GitStatusTree
|
2
9
|
# use GitStatusTree.new.to_s to print the current git-status-tree
|
3
10
|
class GitStatusTree
|
4
11
|
attr_reader :files, :nodes, :tree
|
5
12
|
|
6
13
|
def initialize(options = {})
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
indent = 10 if indent > 10
|
12
|
-
Node.indent = indent
|
13
|
-
@files = (%x(git status --porcelain)).split(/\n/)
|
14
|
-
@nodes = files.map{|file| Node.create_from_string file}
|
15
|
-
@tree = nodes.reduce{|a, i| (a+i).nodes[0]}
|
14
|
+
Node.indent = indent(options)
|
15
|
+
@files = `git status --porcelain`.split(/\n/)
|
16
|
+
@nodes = files.map { |file| Node.create_from_string file }
|
17
|
+
@tree = nodes.reduce { |a, i| (a + i).nodes[0] }
|
16
18
|
end
|
17
19
|
|
18
20
|
def to_s
|
@@ -22,4 +24,18 @@ class GitStatusTree
|
|
22
24
|
tree.to_tree_s
|
23
25
|
end
|
24
26
|
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def indent(options)
|
31
|
+
indent = options[:indent] || config || 4
|
32
|
+
indent = 2 if indent < 2
|
33
|
+
indent = 10 if indent > 10
|
34
|
+
indent
|
35
|
+
end
|
36
|
+
|
37
|
+
def config
|
38
|
+
config = `git config --global status-tree.indent`.strip
|
39
|
+
config =~ /\A\d+\z/ ? config.to_i : nil
|
40
|
+
end
|
25
41
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: git-status-tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wolfgang Teuber
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-06-25 00:00:00.000000000 Z
|
12
11
|
dependencies: []
|
13
12
|
description: git-status-tree is a command line tool that shows git repository changes
|
14
13
|
in a file tree.
|
@@ -28,7 +27,7 @@ files:
|
|
28
27
|
- lib/node.rb
|
29
28
|
- lib/nodes_collection.rb
|
30
29
|
- lib/rubygems_plugin.rb
|
31
|
-
-
|
30
|
+
- lib/version.rb
|
32
31
|
- src/git_status_tree.rb
|
33
32
|
homepage: https://github.com/knugie/git-status-tree
|
34
33
|
licenses:
|
@@ -36,7 +35,6 @@ licenses:
|
|
36
35
|
- GPL-2.0
|
37
36
|
metadata:
|
38
37
|
source_code_uri: https://github.com/knugie/git-status-tree
|
39
|
-
post_install_message:
|
40
38
|
rdoc_options: []
|
41
39
|
require_paths:
|
42
40
|
- lib
|
@@ -44,15 +42,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
44
42
|
requirements:
|
45
43
|
- - ">="
|
46
44
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
45
|
+
version: '3.3'
|
48
46
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
47
|
requirements:
|
50
48
|
- - ">="
|
51
49
|
- !ruby/object:Gem::Version
|
52
50
|
version: '0'
|
53
51
|
requirements: []
|
54
|
-
rubygems_version: 3.2
|
55
|
-
signing_key:
|
52
|
+
rubygems_version: 3.6.2
|
56
53
|
specification_version: 4
|
57
54
|
summary: git status in file tree format
|
58
55
|
test_files: []
|
data/src/git-status-tree.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require File.join File.dirname(__FILE__), '../lib/node'
|
4
|
-
require File.join File.dirname(__FILE__), '../lib/nodes_collection'
|
5
|
-
require File.join File.dirname(__FILE__), '../lib/bash_color'
|
6
|
-
require File.join File.dirname(__FILE__), '../src/git_status_tree'
|
7
|
-
|
8
|
-
# GIT STATUS
|
9
|
-
# ' ' unmodified
|
10
|
-
# 'M' modified
|
11
|
-
# 'A' added
|
12
|
-
# 'D' deleted
|
13
|
-
# 'R' renamed
|
14
|
-
# 'C' copied
|
15
|
-
# 'U' updated but unmerged
|
16
|
-
# '??' other
|