oxidized 0.30.1 → 0.31.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +2 -2
  3. data/.github/workflows/stale.yml +4 -2
  4. data/.rubocop.yml +18 -2
  5. data/.rubocop_todo.yml +5 -12
  6. data/CHANGELOG.md +61 -1
  7. data/CONTRIBUTING.md +5 -0
  8. data/Dockerfile +82 -21
  9. data/README.md +5 -21
  10. data/Rakefile +3 -2
  11. data/docs/Configuration.md +36 -12
  12. data/docs/Creating-Models.md +45 -4
  13. data/docs/Hooks.md +34 -0
  14. data/docs/Issues.md +91 -0
  15. data/docs/Model-Notes/Cumulus.md +5 -0
  16. data/docs/Model-Notes/FSOS.md +5 -0
  17. data/docs/Model-Notes/FortiOS.md +21 -5
  18. data/docs/Model-Notes/HPEAruba.md +31 -0
  19. data/docs/Model-Notes/OS6.md +10 -0
  20. data/docs/Model-Notes/RouterOS.md +15 -0
  21. data/docs/Model-Notes/SikluMHTG.md +7 -0
  22. data/docs/Outputs.md +2 -0
  23. data/docs/Release.md +18 -15
  24. data/docs/Sources.md +21 -0
  25. data/docs/Supported-OS-Types.md +11 -5
  26. data/docs/Troubleshooting.md +35 -0
  27. data/examples/device-simulation/README.md +173 -0
  28. data/examples/device-simulation/cmdsets/aoscx +9 -0
  29. data/examples/device-simulation/cmdsets/arubainstant +5 -0
  30. data/examples/device-simulation/cmdsets/asa +7 -0
  31. data/examples/device-simulation/cmdsets/ios +7 -0
  32. data/examples/device-simulation/cmdsets/nxos +5 -0
  33. data/examples/device-simulation/cmdsets/routeros +5 -0
  34. data/examples/device-simulation/cmdsets/srosmd +11 -0
  35. data/examples/device-simulation/device2yaml.rb +225 -0
  36. data/examples/device-simulation/yaml/aoscx_R0X25A-6410_FL.10.10.1100.yaml +2281 -0
  37. data/examples/device-simulation/yaml/aoscx_R8N85A-C6000-48G-CL4_PL.10.08.1010.yaml +451 -0
  38. data/examples/device-simulation/yaml/arubainstant_IAP515_8.10.0.6_VWLC.yaml +213 -0
  39. data/examples/device-simulation/yaml/asa_5512_9.12-4-67_single-context.yaml +531 -0
  40. data/examples/device-simulation/yaml/asr920_16.8.1b.yaml +1122 -0
  41. data/examples/device-simulation/yaml/garderos_R7709_003_006_068.yaml +101 -0
  42. data/examples/device-simulation/yaml/iosxe_C9200L-24P-4G_17.09.04a.yaml +514 -0
  43. data/examples/device-simulation/yaml/iosxe_C9800-L-F-K9_17.06.05.yaml +417 -0
  44. data/examples/device-simulation/yaml/riverbed_915.yaml +123 -0
  45. data/examples/device-simulation/yaml/routeros_CHR_7.10.1.yaml +145 -0
  46. data/examples/device-simulation/yaml/routeros_CHR_7.16.yaml +79 -0
  47. data/examples/device-simulation/yaml/routeros_L009UiGS_7.15.2.yaml +353 -0
  48. data/examples/podman-compose/Makefile +60 -17
  49. data/examples/podman-compose/README.md +63 -27
  50. data/examples/podman-compose/docker-compose.yml +11 -2
  51. data/examples/podman-compose/gitserver/.gitignore +1 -0
  52. data/examples/podman-compose/gitserver/Dockerfile +14 -0
  53. data/examples/podman-compose/model-simulation/Dockerfile-model +1 -1
  54. data/examples/podman-compose/model-simulation/asternos.sh +2 -0
  55. data/examples/podman-compose/oxidized-config/.gitignore +2 -0
  56. data/examples/podman-compose/oxidized-config/config +1 -1
  57. data/examples/podman-compose/oxidized-config/config_csv-file +46 -0
  58. data/examples/podman-compose/oxidized-config/config_csv-gitserver +56 -0
  59. data/examples/podman-compose/oxidized-ssh/.gitignore +1 -0
  60. data/lib/oxidized/config.rb +7 -1
  61. data/lib/oxidized/hook/githubrepo.rb +37 -7
  62. data/lib/oxidized/hook/slackdiff.rb +29 -7
  63. data/lib/oxidized/input/http.rb +1 -0
  64. data/lib/oxidized/input/telnet.rb +1 -1
  65. data/lib/oxidized/manager.rb +17 -16
  66. data/lib/oxidized/model/aoscx.rb +16 -2
  67. data/lib/oxidized/model/aosw.rb +7 -1
  68. data/lib/oxidized/model/arubainstant.rb +90 -0
  69. data/lib/oxidized/model/audiocodes.rb +2 -2
  70. data/lib/oxidized/model/cnos.rb +13 -10
  71. data/lib/oxidized/model/cumulus.rb +3 -0
  72. data/lib/oxidized/model/dlink.rb +1 -0
  73. data/lib/oxidized/model/dlinknextgen.rb +3 -0
  74. data/lib/oxidized/model/edgecos.rb +2 -1
  75. data/lib/oxidized/model/eos.rb +2 -0
  76. data/lib/oxidized/model/f5os.rb +17 -0
  77. data/lib/oxidized/model/firewareos.rb +10 -1
  78. data/lib/oxidized/model/fortios.rb +24 -1
  79. data/lib/oxidized/model/garderos.rb +43 -0
  80. data/lib/oxidized/model/h3c.rb +1 -1
  81. data/lib/oxidized/model/ibos.rb +1 -0
  82. data/lib/oxidized/model/ios.rb +20 -12
  83. data/lib/oxidized/model/iosxr.rb +1 -1
  84. data/lib/oxidized/model/lenovonos.rb +2 -0
  85. data/lib/oxidized/model/linuxgeneric.rb +1 -1
  86. data/lib/oxidized/model/netgear.rb +1 -1
  87. data/lib/oxidized/model/nodegrid.rb +1 -1
  88. data/lib/oxidized/model/nsxdfw.rb +30 -0
  89. data/lib/oxidized/model/nxos.rb +2 -1
  90. data/lib/oxidized/model/os6.rb +48 -0
  91. data/lib/oxidized/model/rgos.rb +1 -1
  92. data/lib/oxidized/model/riverbed.rb +104 -0
  93. data/lib/oxidized/model/routeros.rb +2 -2
  94. data/lib/oxidized/model/saos.rb +18 -1
  95. data/lib/oxidized/model/siklumhtg.rb +22 -0
  96. data/lib/oxidized/model/uplinkolt.rb +46 -0
  97. data/lib/oxidized/model/vyatta.rb +2 -2
  98. data/lib/oxidized/model/xos.rb +7 -0
  99. data/lib/oxidized/node.rb +30 -18
  100. data/lib/oxidized/nodes.rb +13 -5
  101. data/lib/oxidized/output/file.rb +45 -42
  102. data/lib/oxidized/output/git.rb +185 -160
  103. data/lib/oxidized/output/gitcrypt.rb +188 -186
  104. data/lib/oxidized/output/http.rb +53 -51
  105. data/lib/oxidized/output/output.rb +6 -4
  106. data/lib/oxidized/source/csv.rb +44 -49
  107. data/lib/oxidized/source/http.rb +63 -81
  108. data/lib/oxidized/source/jsonfile.rb +63 -0
  109. data/lib/oxidized/source/source.rb +43 -18
  110. data/lib/oxidized/source/sql.rb +66 -59
  111. data/lib/oxidized/version.rb +2 -2
  112. data/oxidized.gemspec +22 -16
  113. metadata +111 -15
data/lib/oxidized/node.rb CHANGED
@@ -80,6 +80,8 @@ module Oxidized
80
80
  @err_reason = err.message.to_s
81
81
  false
82
82
  rescue StandardError => e
83
+ # Send a message in debug mode in case we are not able to create a crashfile
84
+ Oxidized.logger.send(:debug, '%s raised %s with msg "%s", creating crashfile' % [ip, e.class, e.message])
83
85
  crashdir = Oxidized.config.crash.directory
84
86
  crashfile = Oxidized.config.crash.hostnames? ? name : ip.to_s
85
87
  FileUtils.mkdir_p(crashdir) unless File.directory?(crashdir)
@@ -198,33 +200,43 @@ module Oxidized
198
200
  end
199
201
 
200
202
  def resolve_key(key, opt, global = nil)
201
- # resolve key, first get global, then get group then get node config
203
+ # resolve key: the priority is as follows: node -> group specific model -> group -> model -> global passed -> global
204
+ # where node has the highest priority (= if defined, overwrites other values)
202
205
  key_sym = key.to_sym
203
206
  key_str = key.to_s
204
- value = global
205
- Oxidized.logger.debug "node.rb: resolving node key '#{key}', with passed global value of '#{value}' and node value '#{opt[key_sym]}'"
207
+ model_name = @model.class.name.to_s.downcase
208
+ Oxidized.logger.debug "node.rb: resolving node key '#{key}', with passed global value of '#{global}' and node value '#{opt[key_sym]}'"
206
209
 
207
- # global
208
- if (not value) && Oxidized.config.has_key?(key_str)
209
- value = Oxidized.config[key_str]
210
- Oxidized.logger.debug "node.rb: setting node key '#{key}' to value '#{value}' from global"
211
- end
210
+ # Node
211
+ if opt[key_sym]
212
+ value = opt[key_sym]
213
+ Oxidized.logger.debug "node.rb: setting node key '#{key}' to value '#{value}' from node"
212
214
 
213
- # group
214
- if Oxidized.config.groups.has_key?(@group) && Oxidized.config.groups[@group].has_key?(key_str)
215
+ # Group specific model
216
+ elsif Oxidized.config.groups.has_key?(@group) && Oxidized.config.groups[@group].models.has_key?(model_name) && Oxidized.config.groups[@group].models[model_name].has_key?(key_str)
217
+ value = Oxidized.config.groups[@group].models[model_name][key_str]
218
+ Oxidized.logger.debug "node.rb: setting node key '#{key}' to value '#{value}' from model in group"
219
+
220
+ # Group
221
+ elsif Oxidized.config.groups.has_key?(@group) && Oxidized.config.groups[@group].has_key?(key_str)
215
222
  value = Oxidized.config.groups[@group][key_str]
216
223
  Oxidized.logger.debug "node.rb: setting node key '#{key}' to value '#{value}' from group"
217
- end
218
224
 
219
- # model
220
- if Oxidized.config.models.has_key?(@model.class.name.to_s.downcase) && Oxidized.config.models[@model.class.name.to_s.downcase].has_key?(key_str)
221
- value = Oxidized.config.models[@model.class.name.to_s.downcase][key_str]
225
+ # Model
226
+ elsif Oxidized.config.models.has_key?(model_name) && Oxidized.config.models[model_name].has_key?(key_str)
227
+ value = Oxidized.config.models[model_name][key_str]
222
228
  Oxidized.logger.debug "node.rb: setting node key '#{key}' to value '#{value}' from model"
223
- end
224
229
 
225
- # node
226
- value = opt[key_sym] || value
227
- Oxidized.logger.debug "node.rb: returning node key '#{key}' with value '#{value}'"
230
+ # Global passed
231
+ elsif global
232
+ value = global
233
+ Oxidized.logger.debug "node.rb: setting node key '#{key}' to value '#{value}' from passed global value"
234
+
235
+ # Global
236
+ elsif Oxidized.config.has_key?(key_str)
237
+ value = Oxidized.config[key_str]
238
+ Oxidized.logger.debug "node.rb: setting node key '#{key}' to value '#{value}' from global"
239
+ end
228
240
  value
229
241
  end
230
242
 
@@ -61,6 +61,9 @@ module Oxidized
61
61
  end
62
62
  end
63
63
 
64
+ # Returns the configuration of group/node_name
65
+ #
66
+ # #fetch is called by oxidzed-web
64
67
  def fetch(node_name, group)
65
68
  yield_node_output(node_name) do |node, output|
66
69
  output.fetch node, group
@@ -69,7 +72,7 @@ module Oxidized
69
72
 
70
73
  # @param node [String] name of the node moved into the head of array
71
74
  def next(node, opt = {})
72
- return unless waiting.find_node_index(node)
75
+ return if running.find_index(node)
73
76
 
74
77
  with_lock do
75
78
  n = del node
@@ -98,6 +101,9 @@ module Oxidized
98
101
  find_index(node) || raise(NodeNotFound, "unable to find '#{node}'")
99
102
  end
100
103
 
104
+ # Returns all stored versions of group/node_name
105
+ #
106
+ # Called by oxidized-web
101
107
  def version(node_name, group)
102
108
  yield_node_output(node_name) do |node, output|
103
109
  output.version node, group
@@ -116,6 +122,10 @@ module Oxidized
116
122
  end
117
123
  end
118
124
 
125
+ def find_index(node)
126
+ index { |e| [e.name, e.ip].include? node }
127
+ end
128
+
119
129
  private
120
130
 
121
131
  def initialize(opts = {})
@@ -133,10 +143,6 @@ module Oxidized
133
143
  @mutex.synchronize(...)
134
144
  end
135
145
 
136
- def find_index(node)
137
- index { |e| [e.name, e.ip].include? node }
138
- end
139
-
140
146
  # @param node node which is removed from nodes list
141
147
  # @return [Node] deleted node
142
148
  def del(node)
@@ -179,6 +185,8 @@ module Oxidized
179
185
  def yield_node_output(node_name)
180
186
  with_lock do
181
187
  node = find { |n| n.name == node_name }
188
+ raise(NodeNotFound, "unable to find '#{node_name}'") if node.nil?
189
+
182
190
  output = node.output.new
183
191
  raise NotSupported unless output.respond_to? :fetch
184
192
 
@@ -1,55 +1,58 @@
1
1
  module Oxidized
2
- class OxidizedFile < Output
3
- require 'fileutils'
2
+ module Output
3
+ # ruby's File class must be accessed with ::File to avoid name conflicts
4
+ class File < Output
5
+ require 'fileutils'
4
6
 
5
- attr_reader :commitref
7
+ attr_reader :commitref
6
8
 
7
- def initialize
8
- super
9
- @cfg = Oxidized.config.output.file
10
- end
9
+ def initialize
10
+ super
11
+ @cfg = Oxidized.config.output.file
12
+ end
11
13
 
12
- def setup
13
- return unless @cfg.empty?
14
+ def setup
15
+ return unless @cfg.empty?
14
16
 
15
- Oxidized.asetus.user.output.file.directory = File.join(Config::ROOT, 'configs')
16
- Oxidized.asetus.save :user
17
- raise NoConfig, 'no output file config, edit ~/.config/oxidized/config'
18
- end
17
+ Oxidized.asetus.user.output.file.directory = ::File.join(Config::ROOT, 'configs')
18
+ Oxidized.asetus.save :user
19
+ raise NoConfig, "no output file config, edit #{Oxidized::Config.configfile}"
20
+ end
19
21
 
20
- def store(node, outputs, opt = {})
21
- file = File.expand_path @cfg.directory
22
- file = File.join File.dirname(file), opt[:group] if opt[:group]
23
- FileUtils.mkdir_p file
24
- file = File.join file, node
25
- File.write(file, outputs.to_cfg)
26
- @commitref = file
27
- end
22
+ def store(node, outputs, opt = {})
23
+ file = ::File.expand_path @cfg.directory
24
+ file = ::File.join ::File.dirname(file), opt[:group] if opt[:group]
25
+ FileUtils.mkdir_p file
26
+ file = ::File.join file, node
27
+ ::File.write(file, outputs.to_cfg)
28
+ @commitref = file
29
+ end
28
30
 
29
- def fetch(node, group)
30
- cfg_dir = File.expand_path @cfg.directory
31
- node_name = node.name
32
-
33
- if group # group is explicitly defined by user
34
- cfg_dir = File.join File.dirname(cfg_dir), group
35
- File.read File.join(cfg_dir, node_name)
36
- elsif File.exist? File.join(cfg_dir, node_name) # node configuration file is stored on base directory
37
- File.read File.join(cfg_dir, node_name)
38
- else
39
- path = Dir.glob(File.join(File.dirname(cfg_dir), '**', node_name)).first # fetch node in all groups
40
- File.read path
31
+ def fetch(node, group)
32
+ cfg_dir = ::File.expand_path @cfg.directory
33
+ node_name = node.name
34
+
35
+ if group # group is explicitly defined by user
36
+ cfg_dir = ::File.join ::File.dirname(cfg_dir), group
37
+ ::File.read ::File.join(cfg_dir, node_name)
38
+ elsif ::File.exist? ::File.join(cfg_dir, node_name) # node configuration file is stored on base directory
39
+ ::File.read ::File.join(cfg_dir, node_name)
40
+ else
41
+ path = Dir.glob(::File.join(::File.dirname(cfg_dir), '**', node_name)).first # fetch node in all groups
42
+ ::File.read path
43
+ end
44
+ rescue Errno::ENOENT
45
+ nil
41
46
  end
42
- rescue Errno::ENOENT
43
- nil
44
- end
45
47
 
46
- def version(_node, _group)
47
- # not supported
48
- []
49
- end
48
+ def version(_node, _group)
49
+ # not supported
50
+ []
51
+ end
50
52
 
51
- def get_version(_node, _group, _oid)
52
- 'not supported'
53
+ def get_version(_node, _group, _oid)
54
+ 'not supported'
55
+ end
53
56
  end
54
57
  end
55
58
  end
@@ -1,195 +1,220 @@
1
1
  module Oxidized
2
- class Git < Output
3
- using Refinements
4
-
5
- class GitError < OxidizedError; end
6
- begin
7
- require 'rugged'
8
- rescue LoadError
9
- raise OxidizedError, 'rugged not found: sudo gem install rugged'
10
- end
2
+ module Output
3
+ class Git < Output
4
+ using Refinements
11
5
 
12
- attr_reader :commitref
6
+ class GitError < OxidizedError; end
7
+ begin
8
+ require 'rugged'
9
+ rescue LoadError
10
+ raise OxidizedError, 'rugged not found: sudo gem install rugged'
11
+ end
13
12
 
14
- def initialize
15
- super
16
- @cfg = Oxidized.config.output.git
17
- end
13
+ attr_reader :commitref
18
14
 
19
- def setup
20
- if @cfg.empty?
21
- Oxidized.asetus.user.output.git.user = 'Oxidized'
22
- Oxidized.asetus.user.output.git.email = 'o@example.com'
23
- Oxidized.asetus.user.output.git.repo = File.join(Config::ROOT, 'oxidized.git')
24
- Oxidized.asetus.save :user
25
- raise NoConfig, 'no output git config, edit ~/.config/oxidized/config'
15
+ def initialize
16
+ super
17
+ @cfg = Oxidized.config.output.git
26
18
  end
27
19
 
28
- if @cfg.repo.respond_to?(:each)
29
- @cfg.repo.each do |group, repo|
30
- @cfg.repo["#{group}="] = File.expand_path repo
20
+ def setup
21
+ if @cfg.empty?
22
+ Oxidized.asetus.user.output.git.user = 'Oxidized'
23
+ Oxidized.asetus.user.output.git.email = 'o@example.com'
24
+ Oxidized.asetus.user.output.git.repo = File.join(Config::ROOT, 'oxidized.git')
25
+ Oxidized.asetus.save :user
26
+ raise NoConfig, "no output git config, edit #{Oxidized::Config.configfile}"
31
27
  end
32
- else
33
- @cfg.repo = File.expand_path @cfg.repo
34
- end
35
- end
36
28
 
37
- def store(file, outputs, opt = {})
38
- @msg = opt[:msg]
39
- @user = opt[:user] || @cfg.user
40
- @email = opt[:email] || @cfg.email
41
- @opt = opt
42
- @commitref = nil
43
- repo = @cfg.repo
44
-
45
- outputs.types.each do |type|
46
- type_cfg = ''
47
- type_repo = File.join(File.dirname(repo), type + '.git')
48
- outputs.type(type).each do |output|
49
- (type_cfg << output; next) unless output.name # rubocop:disable Style/Semicolon
50
- type_file = file + '--' + output.name
51
- if @cfg.type_as_directory?
52
- type_file = type + '/' + type_file
53
- type_repo = repo
29
+ if @cfg.repo.respond_to?(:each)
30
+ @cfg.repo.each do |group, repo|
31
+ @cfg.repo["#{group}="] = File.expand_path repo
54
32
  end
55
- update type_repo, type_file, output
33
+ else
34
+ @cfg.repo = File.expand_path @cfg.repo
56
35
  end
57
- update type_repo, file, type_cfg
58
36
  end
59
37
 
60
- update repo, file, outputs.to_cfg
61
- end
38
+ def store(file, outputs, opt = {})
39
+ @msg = opt[:msg]
40
+ @user = opt[:user] || @cfg.user
41
+ @email = opt[:email] || @cfg.email
42
+ @opt = opt
43
+ @commitref = nil
44
+ repo = @cfg.repo
45
+
46
+ outputs.types.each do |type|
47
+ type_cfg = ''
48
+ type_repo = File.join(File.dirname(repo), type + '.git')
49
+ outputs.type(type).each do |output|
50
+ (type_cfg << output; next) unless output.name # rubocop:disable Style/Semicolon
51
+ type_file = file + '--' + output.name
52
+ if @cfg.type_as_directory?
53
+ type_file = type + '/' + type_file
54
+ type_repo = repo
55
+ end
56
+ update type_repo, type_file, output
57
+ end
58
+ update type_repo, file, type_cfg
59
+ end
62
60
 
63
- def fetch(node, group)
64
- repo, path = yield_repo_and_path(node, group)
65
- repo = Rugged::Repository.new repo
66
- index = repo.index
67
- index.read_tree repo.head.target.tree unless repo.empty?
68
- repo.read(index.get(path)[:oid]).data
69
- rescue StandardError
70
- 'node not found'
71
- end
61
+ update repo, file, outputs.to_cfg
62
+ end
72
63
 
73
- # give a hash of all oid revision for the given node, and the date of the commit
74
- def version(node, group)
75
- repo, path = yield_repo_and_path(node, group)
76
-
77
- repo = Rugged::Repository.new repo
78
- walker = Rugged::Walker.new(repo)
79
- walker.sorting(Rugged::SORT_DATE)
80
- walker.push(repo.head.target.oid)
81
- i = -1
82
- tab = []
83
- walker.each do |commit|
84
- # Diabled rubocop because the suggested .empty? does not work here.
85
- next if commit.diff(paths: [path]).size.zero? # rubocop:disable Style/ZeroLengthPredicate
86
-
87
- hash = {}
88
- hash[:date] = commit.time.to_s
89
- hash[:oid] = commit.oid
90
- hash[:author] = commit.author
91
- hash[:message] = commit.message
92
- tab[i += 1] = hash
64
+ # Returns the configuration of group/node_name
65
+ #
66
+ # #fetch is called by Nodes#fetch
67
+ # Nodes#fetch creates a new Output object each time, so we cannot
68
+ # store the repo index in memory. But as we keep the repo index up
69
+ # to date on disk in #update_repo, we can read it from disk instead of
70
+ # rebuilding it each time.
71
+ def fetch(node, group)
72
+ repo, path = yield_repo_and_path(node, group)
73
+ repo = Rugged::Repository.new repo
74
+ # Read the index from disk
75
+ index = repo.index
76
+
77
+ repo.read(index.get(path)[:oid]).data
78
+ rescue StandardError
79
+ 'node not found'
93
80
  end
94
- walker.reset
95
- tab
96
- rescue StandardError
97
- 'node not found'
98
- end
99
81
 
100
- # give the blob of a specific revision
101
- def get_version(node, group, oid)
102
- repo, path = yield_repo_and_path(node, group)
103
- repo = Rugged::Repository.new repo
104
- repo.blob_at(oid, path).content
105
- rescue StandardError
106
- 'version not found'
107
- end
82
+ # give a hash of all oid revision for the given node, and the date of the commit
83
+ def version(node, group)
84
+ repo, path = yield_repo_and_path(node, group)
108
85
 
109
- # give a hash with the patch of a diff between 2 revision and the stats (added and deleted lines)
110
- def get_diff(node, group, oid1, oid2)
111
- diff_commits = nil
112
- repo, = yield_repo_and_path(node, group)
113
- repo = Rugged::Repository.new repo
114
- commit = repo.lookup(oid1)
115
-
116
- if oid2
117
- commit_old = repo.lookup(oid2)
118
- diff = repo.diff(commit_old, commit)
119
- diff.each do |patch|
120
- if /#{node.name}\s+/ =~ patch.to_s.lines.first
121
- diff_commits = { patch: patch.to_s, stat: patch.stat }
122
- break
123
- end
86
+ repo = Rugged::Repository.new repo
87
+ walker = Rugged::Walker.new(repo)
88
+ walker.sorting(Rugged::SORT_DATE)
89
+ walker.push(repo.head.target.oid)
90
+ i = -1
91
+ tab = []
92
+ walker.each do |commit|
93
+ # Diabled rubocop because the suggested .empty? does not work here.
94
+ next if commit.diff(paths: [path]).size.zero? # rubocop:disable Style/ZeroLengthPredicate
95
+
96
+ hash = {}
97
+ hash[:date] = commit.time.to_s
98
+ hash[:oid] = commit.oid
99
+ hash[:author] = commit.author
100
+ hash[:message] = commit.message
101
+ tab[i += 1] = hash
124
102
  end
125
- else
126
- stat = commit.parents[0].diff(commit).stat
127
- stat = [stat[1], stat[2]]
128
- patch = commit.parents[0].diff(commit).patch
129
- diff_commits = { patch: patch, stat: stat }
103
+ walker.reset
104
+ tab
105
+ rescue StandardError
106
+ 'node not found'
130
107
  end
131
108
 
132
- diff_commits
133
- rescue StandardError
134
- 'no diffs'
135
- end
109
+ # give the blob of a specific revision
110
+ def get_version(node, group, oid)
111
+ repo, path = yield_repo_and_path(node, group)
112
+ repo = Rugged::Repository.new repo
113
+ repo.blob_at(oid, path).content
114
+ rescue StandardError
115
+ 'version not found'
116
+ end
136
117
 
137
- private
118
+ # give a hash with the patch of a diff between 2 revision and the stats (added and deleted lines)
119
+ def get_diff(node, group, oid1, oid2)
120
+ diff_commits = nil
121
+ repo, = yield_repo_and_path(node, group)
122
+ repo = Rugged::Repository.new repo
123
+ commit = repo.lookup(oid1)
124
+
125
+ if oid2
126
+ commit_old = repo.lookup(oid2)
127
+ diff = repo.diff(commit_old, commit)
128
+ diff.each do |patch|
129
+ if /#{node.name}\s+/ =~ patch.to_s.lines.first
130
+ diff_commits = { patch: patch.to_s, stat: patch.stat }
131
+ break
132
+ end
133
+ end
134
+ else
135
+ stat = commit.parents[0].diff(commit).stat
136
+ stat = [stat[1], stat[2]]
137
+ patch = commit.parents[0].diff(commit).patch
138
+ diff_commits = { patch: patch, stat: stat }
139
+ end
138
140
 
139
- def yield_repo_and_path(node, group)
140
- repo, path = node.repo, node.name
141
+ diff_commits
142
+ rescue StandardError
143
+ 'no diffs'
144
+ end
141
145
 
142
- path = "#{group}/#{node.name}" if group && @cfg.single_repo?
146
+ private
143
147
 
144
- [repo, path]
145
- end
148
+ def yield_repo_and_path(node, group)
149
+ repo, path = node.repo, node.name
146
150
 
147
- def update(repo, file, data)
148
- return if data.empty?
151
+ path = "#{group}/#{node.name}" if group && !group.empty? && @cfg.single_repo?
149
152
 
150
- if @opt[:group]
151
- if @cfg.single_repo?
152
- file = File.join @opt[:group], file
153
- else
154
- repo = if repo.is_a?(::String)
155
- File.join File.dirname(repo), @opt[:group] + '.git'
156
- else
157
- repo[@opt[:group]]
158
- end
159
- end
153
+ [repo, path]
160
154
  end
161
155
 
162
- begin
163
- repo = Rugged::Repository.new repo
164
- update_repo repo, file, data
165
- rescue Rugged::OSError, Rugged::RepositoryError => e
156
+ def update(repo, file, data)
157
+ return if data.empty?
158
+
159
+ if @opt[:group]
160
+ if @cfg.single_repo?
161
+ file = File.join @opt[:group], file
162
+ else
163
+ repo = if repo.is_a?(::String)
164
+ File.join File.dirname(repo), @opt[:group] + '.git'
165
+ else
166
+ repo[@opt[:group]]
167
+ end
168
+ end
169
+ end
170
+
166
171
  begin
167
- Rugged::Repository.init_at repo, :bare
168
- rescue StandardError => create_error
169
- raise GitError, "first '#{e.message}' was raised while opening git repo, then '#{create_error.message}' was while trying to create git repo"
172
+ repo = Rugged::Repository.new repo
173
+ update_repo repo, file, data
174
+ rescue Rugged::OSError, Rugged::RepositoryError => e
175
+ begin
176
+ Rugged::Repository.init_at repo, :bare
177
+ rescue StandardError => create_error
178
+ raise GitError, "first '#{e.message}' was raised while opening git repo, then '#{create_error.message}' was while trying to create git repo"
179
+ end
180
+ retry
170
181
  end
171
- retry
172
182
  end
173
- end
174
-
175
- def update_repo(repo, file, data)
176
- oid_old = repo.blob_at(repo.head.target_id, file) rescue nil
177
- return false if oid_old && (oid_old.content.b == data.b)
178
-
179
- oid = repo.write data, :blob
180
- index = repo.index
181
- index.add path: file, oid: oid, mode: 0o100644
182
183
 
183
- repo.config['user.name'] = @user
184
- repo.config['user.email'] = @email
185
- @commitref = Rugged::Commit.create(repo,
186
- tree: index.write_tree(repo),
187
- message: @msg,
188
- parents: repo.empty? ? [] : [repo.head.target].compact,
189
- update_ref: 'HEAD')
190
-
191
- index.write
192
- true
184
+ # Uploads data into file in the repo
185
+ #
186
+ # @param [String] file: the file to save the configuration to
187
+ # @param [String] data: the configuration to save
188
+ # @param [Rugged::Repository] repo: the git repository to use
189
+ #
190
+ # If Oxidized.config.output.git.single_repo = false (which is the default),
191
+ # there will one repository for each group.
192
+ #
193
+ # update_repo caches the index on disk. An index is usually used in a
194
+ # working directory and not in a bare repository, which confuses users.
195
+ # The alternative would be to rebuild the index each time, which a little
196
+ # time consuming. Caching the index in memory is difficult because a new
197
+ # Output object is created each time #store is called.
198
+ def update_repo(repo, file, data)
199
+ oid_old = repo.blob_at(repo.head.target_id, file) rescue nil
200
+ return false if oid_old && (oid_old.content.b == data.b)
201
+
202
+ oid = repo.write data, :blob
203
+ # Read the index from disk
204
+ index = repo.index
205
+ index.add path: file, oid: oid, mode: 0o100644
206
+
207
+ repo.config['user.name'] = @user
208
+ repo.config['user.email'] = @email
209
+ @commitref = Rugged::Commit.create(repo,
210
+ tree: index.write_tree(repo),
211
+ message: @msg,
212
+ parents: repo.empty? ? [] : [repo.head.target].compact,
213
+ update_ref: 'HEAD')
214
+
215
+ index.write
216
+ true
217
+ end
193
218
  end
194
219
  end
195
220
  end