git-status-tree 1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4fdc77cb8d05dbec437b7ca6aaaf46bff71d2d35fa6ca2c1435c40fa1871f0d6
4
+ data.tar.gz: cfe0d6434eacb99525c1116668a6f251ef23b50253b45bdf413837dffbf609c4
5
+ SHA512:
6
+ metadata.gz: 4f15db6fa9f6bf045faeac4f86c4f5131f0323f5771182d2d1f81fcde6cf068176cf47a8d71a2f9df3a050137b64b99788a5d24f3554fab014b51a0edc311344
7
+ data.tar.gz: 5f61fbea0eaf0fc67fb82fc69421aac9c3faf498759e621b54298e1fffd560e91f5cdce7539bfca151b155b4b09bead7f8cfe629434613c9d35037cdfdde52dc
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require File.join File.dirname(__FILE__), '../src/git-status-tree'
5
+
6
+ puts GitStatusTree.new.to_s
@@ -0,0 +1,12 @@
1
+ #!/bin/bash
2
+
3
+ pushd `dirname $0` > /dev/null
4
+ BIN_DIR=`pwd`
5
+ popd > /dev/null
6
+
7
+ git config --global status-tree.indent 4
8
+ git config --global alias.tree "!sh -c \"$BIN_DIR/git-status-tree\""
9
+ echo '#############################'
10
+ echo '# "git tree" has been added #'
11
+ echo '# Run: git tree #'
12
+ echo '#############################'
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+
3
+ git config --global --unset alias.tree
4
+ git config --global --remove-section status-tree
5
+ echo '###############################'
6
+ echo '# "git tree" has been removed #'
7
+ echo '###############################'
@@ -0,0 +1,4 @@
1
+ all:
2
+ true
3
+ install:
4
+ true
@@ -0,0 +1,15 @@
1
+ require 'mkmf'
2
+
3
+ find_executable('bash')
4
+ find_executable('git')
5
+ find_executable('make')
6
+
7
+ # Trick Rubygems into thinking the generated Makefile was executed
8
+ compile = File.join(Dir.pwd, 'git_tree.' + RbConfig::CONFIG['DLEXT'])
9
+ File.open(compile, "w") {}
10
+
11
+ # Install "git tree"
12
+ puts `../../bin/git_add_alias_tree`
13
+
14
+ # Trick Rubygems into thinking the Makefile was executed
15
+ $makefile_created = true
data/lib/bash_color.rb ADDED
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ module BashColor
4
+ NONE = "\033[0m"
5
+
6
+ K = "\033[0;30m" # black
7
+ R = "\033[0;31m" # red
8
+ G = "\033[0;32m" # green
9
+ Y = "\033[0;33m" # yellow
10
+ B = "\033[0;34m" # blue
11
+ M = "\033[0;35m" # magenta
12
+ C = "\033[0;36m" # cyan
13
+ W = "\033[0;37m" # white
14
+
15
+ # emphasized (bolded) colors
16
+ EMK = "\033[1;30m"
17
+ EMR = "\033[1;31m"
18
+ EMG = "\033[1;32m"
19
+ EMY = "\033[1;33m"
20
+ EMB = "\033[1;34m"
21
+ EMM = "\033[1;35m"
22
+ EMC = "\033[1;36m"
23
+ EMW = "\033[1;37m"
24
+
25
+ # background colors
26
+ BGK = "\033[40m"
27
+ BGR = "\033[41m"
28
+ BGG = "\033[42m"
29
+ BGY = "\033[43m"
30
+ BGB = "\033[44m"
31
+ BGM = "\033[45m"
32
+ BGC = "\033[46m"
33
+ BGW = "\033[47m"
34
+ end
data/lib/node.rb ADDED
@@ -0,0 +1,208 @@
1
+ # encoding: utf-8
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
+ msg = '"name" must be a String.'
18
+ raise NodeNameError, msg unless name.is_a? String
19
+ msg = '"name" must have at least one character.'
20
+ raise NodeNameError, msg if name.empty?
21
+ msg = '"name" must not contain "/", use create_from_string.'
22
+ raise NodeNameError, msg if name =~ /\//
23
+ msg = '"children" must be a NodesCollection or nil.'
24
+ valid_nodes_collection = children.nil? || children.is_a?(NodesCollection)
25
+ raise NodeChildrenError, msg unless valid_nodes_collection
26
+
27
+ @name = name
28
+ @children = children
29
+ @status = status || []
30
+ end
31
+
32
+ def self.create_from_string(gs_porcelain)
33
+ msg = '"str_node" must be String.'
34
+ raise NodeTypeError, msg unless gs_porcelain.is_a? String
35
+ raise NodeNameError, '"str_node" too short.' if gs_porcelain.length < 4
36
+ status = if gs_porcelain[1..1] == ' '
37
+ gs_porcelain[0..0] + '+'
38
+ else
39
+ gs_porcelain[1..1]
40
+ end
41
+ path = './' + gs_porcelain[3..-1]
42
+ ary_nodes = path.split(/\//)
43
+ name = ary_nodes.shift
44
+
45
+ if ary_nodes.any?
46
+ children = NodesCollection.create_from_array(ary_nodes, status)
47
+ node = self.new(name, children)
48
+ else
49
+ node = self.new(name, nil, status)
50
+ end
51
+
52
+ node
53
+ end
54
+
55
+ def self.instances?
56
+ lambda { |node| node.is_a?(Node) }
57
+ end
58
+
59
+ def to_primitive
60
+ if dir?
61
+ {name => children.to_primitive}
62
+ else
63
+ name
64
+ end
65
+ end
66
+
67
+ def file?
68
+ children.nil?
69
+ end
70
+
71
+ def dir?
72
+ !file?
73
+ end
74
+
75
+ def valid?
76
+ return valid_dir? if dir?
77
+ return valid_file? if file?
78
+ false
79
+ end
80
+
81
+ # @return [NodesCollection]
82
+ def +(other)
83
+ raise 'not valid' unless self.valid? && other.valid?
84
+ raise 'not a ' + self.class.to_s unless other.is_a?(self.class)
85
+
86
+ tmp_children = [self.children, other.children].compact.inject(&:+)
87
+
88
+ NodesCollection.new([self.class.new(self.name, tmp_children)])
89
+ end
90
+
91
+ def <=>(other)
92
+ return (self.name <=> other.name) if self.file? == other.file?
93
+ return -1 if (self.dir? && other.file?)
94
+ return 1 if (self.file? && other.dir?)
95
+ 0
96
+ end
97
+
98
+ def to_tree_s(depth = 0, open_parents = [0], last = true)
99
+ open_parents << depth
100
+
101
+ pre = pre_tree(depth, open_parents, last)
102
+
103
+ color_name = ''
104
+ if dir?
105
+ color_name += BashColor::EMB + name
106
+ else #file?
107
+ if staged?
108
+ color_name += BashColor::G
109
+ else
110
+ color_name += BashColor::R
111
+ end
112
+ color_name += name + ' (' + status + ')'
113
+ end
114
+ color_name += BashColor::NONE
115
+
116
+
117
+ str_tree = pre + color_name + "\n"
118
+ str_tree << children.to_tree_s(depth + 1, open_parents) if children
119
+
120
+ str_tree
121
+ end
122
+
123
+ # 'M' modified
124
+ def modified?
125
+ self.status.include?('M')
126
+ end
127
+
128
+ # 'A' added
129
+ def added?
130
+ self.status.include?('A')
131
+ end
132
+
133
+ # 'D' deleted
134
+ def deleted?
135
+ self.status.include?('D')
136
+ end
137
+
138
+ # 'R' renamed
139
+ def renamed?
140
+ self.status.include?('R')
141
+ end
142
+
143
+ # 'C' copied
144
+ def copied?
145
+ self.status.include?('C')
146
+ end
147
+
148
+ # 'U' updated but unmerged
149
+ def unmerged?
150
+ self.status.include?('U')
151
+ end
152
+
153
+ # '?' new
154
+ def new?
155
+ self.status.include?('?')
156
+ end
157
+
158
+ # '+' staged
159
+ def staged?
160
+ self.status.include?('+')
161
+ end
162
+
163
+ private
164
+ # Has a valid name?
165
+ def name_valid?
166
+ name &&
167
+ name.is_a?(String) &&
168
+ (name.length > 0) &&
169
+ name.match(/\//).nil?
170
+ end
171
+
172
+ # Is a valid dir?
173
+ def valid_dir?
174
+ name_valid? &&
175
+ children.is_a?(NodesCollection) &&
176
+ children.valid?
177
+ end
178
+
179
+ # Is a valid file?
180
+ def valid_file?
181
+ name_valid? &&
182
+ children.nil?
183
+ end
184
+
185
+ def pre_tree(depth, open_parents, last)
186
+ if depth == 0
187
+ ''
188
+ elsif depth > 0
189
+ pre_ary = Array.new(depth).fill(' ')
190
+
191
+ indent = self.class.indent - 2
192
+ open_parents.each do |idx|
193
+ pre_ary[idx] = '│' + (' ' * indent) + ' ' if pre_ary[idx] == ' '
194
+ end
195
+
196
+ if last
197
+ pre_ary[-1] = '└'
198
+ open_parents.delete(depth-1)
199
+ else
200
+ pre_ary[-1] = '├'
201
+ end
202
+ pre_ary[-1] += ('─' * indent) + ' '
203
+
204
+
205
+ pre_ary * ''
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,179 @@
1
+ # encoding: utf-8
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 = lambda { |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(nodes)
30
+ raise msg unless nodes.all? &Node.instances?
31
+
32
+ all_nodes = nodes.group_by(&:name)
33
+
34
+ plain_nodes = []
35
+ all_nodes.each { |_, nodes| plain_nodes << nodes if nodes.length == 1 }
36
+ plain_nodes.flatten!(1)
37
+
38
+ merged_nodes = []
39
+ all_nodes.each do |_, nodes|
40
+ if nodes.length == 2
41
+ merged_nodes << (nodes[0] + nodes[1]).nodes[0]
42
+ end
43
+ end
44
+
45
+ self.new(plain_nodes + merged_nodes)
46
+ end
47
+
48
+ def self.create_from_valid_array(ary_nodes, status)
49
+ name = ary_nodes.shift
50
+ node = if ary_nodes.any?
51
+ children = create_from_valid_array(ary_nodes, status)
52
+ Node.new(name, children)
53
+ else
54
+ Node.new(name, nil, status)
55
+ end
56
+ self.new([node])
57
+ end
58
+
59
+ private_class_method :create_from_valid_array
60
+
61
+ def initialize(nodes = [])
62
+ nodes = [nodes].flatten(1)
63
+
64
+ msg = '"nodes" must only contain Nodes.'
65
+ are_nodes = lambda { |node| node.is_a?(Node) }
66
+ raise NodesCollectionTypeError, msg unless nodes.all? &are_nodes
67
+ @nodes = nodes
68
+ end
69
+
70
+ # @return [NodesCollection]
71
+ def +(other)
72
+ unless other.is_a?(Node) || other.is_a?(self.class)
73
+ raise 'not a Node or NodesCollection'
74
+ end
75
+
76
+ all_nodes = merge_nodes_with other
77
+ all_dirs = all_nodes.select(&:dir?)
78
+ all_files = all_nodes.select(&:file?)
79
+
80
+ dirs_collection = self.class.new_from_nodes_array all_dirs
81
+ files_collection = self.class.new all_files
82
+
83
+
84
+ dir_nodes = dirs_collection.sort!
85
+ file_nodes = files_collection.sort!
86
+
87
+ self.class.new(dir_nodes + file_nodes)
88
+ end
89
+
90
+ # @return [Integer]
91
+ def <=>(other)
92
+ self.to_primitive <=> other.to_primitive
93
+ end
94
+
95
+ # @return [Array<Node>]
96
+ def nodes_not_in(other)
97
+ self_names = self.nodes.map(&:name)
98
+ other_names = other.nodes.map(&:name)
99
+
100
+ self_only_names = self_names - (self_names & other_names)
101
+
102
+ self.nodes.select { |node| self_only_names.include?(node.name) }
103
+ end
104
+
105
+ # @return [Array<Node>]
106
+ def merge_common_nodes_with(other)
107
+ self_names = self.nodes.map(&:name)
108
+ other_names = other.nodes.map(&:name)
109
+
110
+ common_names = self_names & other_names
111
+ all_nodes = self.nodes + other.nodes
112
+
113
+ common_names.map do |name|
114
+ all_nodes.select { |node| node.name == name }.reduce(&:+).nodes[0]
115
+ end
116
+ end
117
+
118
+ # @return [Array<Node>]
119
+ def merge_nodes_with(other)
120
+ if other.is_a? Node
121
+ if self.nodes.map(&:name).include?(other.name)
122
+ equal_names = lambda { |node| node.name == other.name }
123
+ collection_merged = self.nodes.select(&equal_names)[0] + other
124
+ other = collection_merged.nodes[0]
125
+ end
126
+
127
+ nodes_merged = self.nodes + [other]
128
+ elsif other.is_a? NodesCollection
129
+ self_dedicated_nodes = self.nodes_not_in other
130
+ other_dedicated_nodes = other.nodes_not_in self
131
+ common_nodes = self.merge_common_nodes_with other
132
+
133
+ nodes_merged = self_dedicated_nodes + common_nodes + other_dedicated_nodes
134
+ end
135
+
136
+ nodes_merged
137
+ end
138
+
139
+ def files
140
+ nodes.select(&:file?)
141
+ end
142
+
143
+ def dirs
144
+ nodes.select(&:dir?)
145
+ end
146
+
147
+ def sort!
148
+ nodes.sort!
149
+ end
150
+
151
+ def to_primitive
152
+ nodes.map(&:to_primitive)
153
+ end
154
+
155
+ def valid?
156
+ nodes &&
157
+ nodes.is_a?(Array) &&
158
+ nodes.all? { |node| node.is_a?(Node) } &&
159
+ nodes.all?(&:valid?)
160
+ #TODO compare uniqueness of file and dir names.
161
+ end
162
+
163
+ def to_tree_s(depth = 0, open_parents = [])
164
+ tree_s = ''
165
+
166
+ if nodes.length > 1
167
+ to_tree_s = lambda { |node| node.to_tree_s(depth, open_parents, false) }
168
+ tree_s << nodes[0..-2].map(&to_tree_s) * ''
169
+ end
170
+ tree_s << nodes.last.to_tree_s(depth, open_parents)
171
+
172
+ tree_s
173
+ end
174
+
175
+ private
176
+ def add_node(other)
177
+ end
178
+
179
+ end
@@ -0,0 +1,5 @@
1
+ Gem.pre_uninstall do |uninstaller|
2
+ bin_dir = uninstaller.spec.bin_dir
3
+ git_remove_alias_tree = File.join(bin_dir, 'git_remove_alias_tree')
4
+ puts `#{git_remove_alias_tree}`
5
+ end
@@ -0,0 +1,16 @@
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
+ # '??' new
@@ -0,0 +1,25 @@
1
+ # GitStatusTree
2
+ # use GitStatusTree.new.to_s to print the current git-status-tree
3
+ class GitStatusTree
4
+ attr_reader :files, :nodes, :tree
5
+
6
+ def initialize(options = {})
7
+ config = %x(git config --global status-tree.indent).strip
8
+ config = (config =~ /\A\d+\z/) ? config.to_i : nil
9
+ indent = options[:indent] || config || 4
10
+ indent = 2 if indent < 2
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]}
16
+ end
17
+
18
+ def to_s
19
+ if tree.nil?
20
+ '(working directory clean)'
21
+ else
22
+ tree.to_tree_s
23
+ end
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git-status-tree
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Wolfgang Teuber
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-09-28 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: git-status-tree is a command line tool that shows git repository changes
14
+ in a file tree.
15
+ email: knugie@gmx.net
16
+ executables:
17
+ - git-status-tree
18
+ extensions:
19
+ - ext/git_tree/extconf.rb
20
+ extra_rdoc_files: []
21
+ files:
22
+ - bin/git-status-tree
23
+ - bin/git_add_alias_tree
24
+ - bin/git_remove_alias_tree
25
+ - ext/git_tree/Makefile
26
+ - ext/git_tree/extconf.rb
27
+ - lib/bash_color.rb
28
+ - lib/node.rb
29
+ - lib/nodes_collection.rb
30
+ - lib/rubygems_plugin.rb
31
+ - src/git-status-tree.rb
32
+ - src/git_status_tree.rb
33
+ homepage: https://github.com/knugie/git-status-tree
34
+ licenses:
35
+ - MIT
36
+ - GPL-2.0
37
+ metadata:
38
+ source_code_uri: https://github.com/knugie/git-status-tree
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubygems_version: 3.2.3
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: git status in file tree format
58
+ test_files: []