git-status-tree 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []