spoom 1.1.16 → 1.2.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.
@@ -0,0 +1,136 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ class Context
6
+ # Sorbet features for a context
7
+ module Sorbet
8
+ extend T::Sig
9
+ extend T::Helpers
10
+
11
+ requires_ancestor { Context }
12
+
13
+ # Run `bundle exec srb` in this context directory
14
+ sig { params(arg: String, sorbet_bin: T.nilable(String), capture_err: T::Boolean).returns(ExecResult) }
15
+ def srb(*arg, sorbet_bin: nil, capture_err: true)
16
+ res = if sorbet_bin
17
+ exec("#{sorbet_bin} #{arg.join(" ")}", capture_err: capture_err)
18
+ else
19
+ bundle_exec("srb #{arg.join(" ")}", capture_err: capture_err)
20
+ end
21
+
22
+ case res.exit_code
23
+ when Spoom::Sorbet::KILLED_CODE
24
+ raise Spoom::Sorbet::Error::Killed.new("Sorbet was killed.", res)
25
+ when Spoom::Sorbet::SEGFAULT_CODE
26
+ raise Spoom::Sorbet::Error::Segfault.new("Sorbet segfaulted.", res)
27
+ end
28
+
29
+ res
30
+ end
31
+
32
+ sig { params(arg: String, sorbet_bin: T.nilable(String), capture_err: T::Boolean).returns(ExecResult) }
33
+ def srb_tc(*arg, sorbet_bin: nil, capture_err: true)
34
+ arg.prepend("tc") unless sorbet_bin
35
+ T.unsafe(self).srb(*arg, sorbet_bin: sorbet_bin, capture_err: capture_err)
36
+ end
37
+
38
+ sig do
39
+ params(
40
+ arg: String,
41
+ sorbet_bin: T.nilable(String),
42
+ capture_err: T::Boolean,
43
+ ).returns(T.nilable(T::Hash[String, Integer]))
44
+ end
45
+ def srb_metrics(*arg, sorbet_bin: nil, capture_err: true)
46
+ metrics_file = "metrics.tmp"
47
+
48
+ T.unsafe(self).srb_tc(
49
+ "--metrics-file",
50
+ metrics_file,
51
+ *arg,
52
+ sorbet_bin: sorbet_bin,
53
+ capture_err: capture_err,
54
+ )
55
+ return nil unless file?(metrics_file)
56
+
57
+ metrics_path = absolute_path_to(metrics_file)
58
+ metrics = Spoom::Sorbet::MetricsParser.parse_file(metrics_path)
59
+ remove!(metrics_file)
60
+ metrics
61
+ end
62
+
63
+ # List all files typechecked by Sorbet from its `config`
64
+ sig { params(with_config: T.nilable(Spoom::Sorbet::Config)).returns(T::Array[String]) }
65
+ def srb_files(with_config: nil)
66
+ config = with_config || sorbet_config
67
+ regs = config.ignore.map { |string| Regexp.new(Regexp.escape(string)) }
68
+ exts = config.allowed_extensions.empty? ? [".rb", ".rbi"] : config.allowed_extensions
69
+ glob("**/*{#{exts.join(",")}}").reject do |f|
70
+ regs.any? { |re| re.match?(f) }
71
+ end.sort
72
+ end
73
+
74
+ sig { params(arg: String, sorbet_bin: T.nilable(String), capture_err: T::Boolean).returns(T.nilable(String)) }
75
+ def srb_version(*arg, sorbet_bin: nil, capture_err: true)
76
+ res = T.unsafe(self).srb_tc("--no-config", "--version", *arg, sorbet_bin: sorbet_bin, capture_err: capture_err)
77
+ return nil unless res.status
78
+
79
+ res.out.split(" ")[2]
80
+ end
81
+
82
+ # Does this context has a `sorbet/config` file?
83
+ sig { returns(T::Boolean) }
84
+ def has_sorbet_config?
85
+ file?(Spoom::Sorbet::CONFIG_PATH)
86
+ end
87
+
88
+ sig { returns(Spoom::Sorbet::Config) }
89
+ def sorbet_config
90
+ Spoom::Sorbet::Config.parse_string(read_sorbet_config)
91
+ end
92
+
93
+ # Read the contents of `sorbet/config` in this context directory
94
+ sig { returns(String) }
95
+ def read_sorbet_config
96
+ read(Spoom::Sorbet::CONFIG_PATH)
97
+ end
98
+
99
+ # Set the `contents` of `sorbet/config` in this context directory
100
+ sig { params(contents: String, append: T::Boolean).void }
101
+ def write_sorbet_config!(contents, append: false)
102
+ write!(Spoom::Sorbet::CONFIG_PATH, contents, append: append)
103
+ end
104
+
105
+ # Read the strictness sigil from the file at `relative_path` (returns `nil` if no sigil)
106
+ sig { params(relative_path: String).returns(T.nilable(String)) }
107
+ def read_file_strictness(relative_path)
108
+ Spoom::Sorbet::Sigils.file_strictness(absolute_path_to(relative_path))
109
+ end
110
+
111
+ # Get the commit introducing the `sorbet/config` file
112
+ sig { returns(T.nilable(Spoom::Git::Commit)) }
113
+ def sorbet_intro_commit
114
+ res = git_log("--diff-filter=A --format='%h %at' -1 -- sorbet/config")
115
+ return nil unless res.status
116
+
117
+ out = res.out.strip
118
+ return nil if out.empty?
119
+
120
+ Spoom::Git::Commit.parse_line(out)
121
+ end
122
+
123
+ # Get the commit removing the `sorbet/config` file
124
+ sig { returns(T.nilable(Spoom::Git::Commit)) }
125
+ def sorbet_removal_commit
126
+ res = git_log("--diff-filter=D --format='%h %at' -1 -- sorbet/config")
127
+ return nil unless res.status
128
+
129
+ out = res.out.strip
130
+ return nil if out.empty?
131
+
132
+ Spoom::Git::Commit.parse_line(out)
133
+ end
134
+ end
135
+ end
136
+ end
data/lib/spoom/context.rb CHANGED
@@ -3,8 +3,15 @@
3
3
 
4
4
  require "fileutils"
5
5
  require "open3"
6
+ require "time"
6
7
  require "tmpdir"
7
8
 
9
+ require_relative "context/bundle"
10
+ require_relative "context/exec"
11
+ require_relative "context/file_system"
12
+ require_relative "context/git"
13
+ require_relative "context/sorbet"
14
+
8
15
  module Spoom
9
16
  # An abstraction to a Ruby project context
10
17
  #
@@ -13,9 +20,11 @@ module Spoom
13
20
  class Context
14
21
  extend T::Sig
15
22
 
16
- # The absolute path to the directory this context is about
17
- sig { returns(String) }
18
- attr_reader :absolute_path
23
+ include Bundle
24
+ include Exec
25
+ include FileSystem
26
+ include Git
27
+ include Sorbet
19
28
 
20
29
  class << self
21
30
  extend T::Sig
@@ -30,6 +39,10 @@ module Spoom
30
39
  end
31
40
  end
32
41
 
42
+ # The absolute path to the directory this context is about
43
+ sig { returns(String) }
44
+ attr_reader :absolute_path
45
+
33
46
  # Create a new context about `absolute_path`
34
47
  #
35
48
  # The directory will not be created if it doesn't exist.
@@ -38,202 +51,5 @@ module Spoom
38
51
  def initialize(absolute_path)
39
52
  @absolute_path = T.let(::File.expand_path(absolute_path), String)
40
53
  end
41
-
42
- # Returns the absolute path to `relative_path` in the context's directory
43
- sig { params(relative_path: String).returns(String) }
44
- def absolute_path_to(relative_path)
45
- File.join(@absolute_path, relative_path)
46
- end
47
-
48
- # File System
49
-
50
- # Does the context directory at `absolute_path` exist and is a directory?
51
- sig { returns(T::Boolean) }
52
- def exist?
53
- File.directory?(@absolute_path)
54
- end
55
-
56
- # Create the context directory at `absolute_path`
57
- sig { void }
58
- def mkdir!
59
- FileUtils.rm_rf(@absolute_path)
60
- FileUtils.mkdir_p(@absolute_path)
61
- end
62
-
63
- # List all files in this context matching `pattern`
64
- sig { params(pattern: String).returns(T::Array[String]) }
65
- def glob(pattern = "**/*")
66
- Dir.glob(absolute_path_to(pattern)).map do |path|
67
- Pathname.new(path).relative_path_from(@absolute_path).to_s
68
- end.sort
69
- end
70
-
71
- # List all files at the top level of this context directory
72
- sig { returns(T::Array[String]) }
73
- def list
74
- glob("*")
75
- end
76
-
77
- # Does `relative_path` point to an existing file in this context directory?
78
- sig { params(relative_path: String).returns(T::Boolean) }
79
- def file?(relative_path)
80
- File.file?(absolute_path_to(relative_path))
81
- end
82
-
83
- # Return the contents of the file at `relative_path` in this context directory
84
- #
85
- # Will raise if the file doesn't exist.
86
- sig { params(relative_path: String).returns(String) }
87
- def read(relative_path)
88
- File.read(absolute_path_to(relative_path))
89
- end
90
-
91
- # Write `contents` in the file at `relative_path` in this context directory
92
- #
93
- # Append to the file if `append` is true.
94
- sig { params(relative_path: String, contents: String, append: T::Boolean).void }
95
- def write!(relative_path, contents = "", append: false)
96
- absolute_path = absolute_path_to(relative_path)
97
- FileUtils.mkdir_p(File.dirname(absolute_path))
98
- File.write(absolute_path, contents, mode: append ? "a" : "w")
99
- end
100
-
101
- # Remove the path at `relative_path` (recursive + force) in this context directory
102
- sig { params(relative_path: String).void }
103
- def remove!(relative_path)
104
- FileUtils.rm_rf(absolute_path_to(relative_path))
105
- end
106
-
107
- # Move the file or directory from `from_relative_path` to `to_relative_path`
108
- sig { params(from_relative_path: String, to_relative_path: String).void }
109
- def move!(from_relative_path, to_relative_path)
110
- destination_path = absolute_path_to(to_relative_path)
111
- FileUtils.mkdir_p(File.dirname(destination_path))
112
- FileUtils.mv(absolute_path_to(from_relative_path), destination_path)
113
- end
114
-
115
- # Delete this context and its content
116
- #
117
- # Warning: it will `rm -rf` the context directory on the file system.
118
- sig { void }
119
- def destroy!
120
- FileUtils.rm_rf(@absolute_path)
121
- end
122
-
123
- # Execution
124
-
125
- # Run a command in this context directory
126
- sig { params(command: String, capture_err: T::Boolean).returns(ExecResult) }
127
- def exec(command, capture_err: true)
128
- Bundler.with_unbundled_env do
129
- opts = T.let({ chdir: @absolute_path }, T::Hash[Symbol, T.untyped])
130
-
131
- if capture_err
132
- out, err, status = Open3.capture3(command, opts)
133
- ExecResult.new(out: out, err: err, status: T.must(status.success?), exit_code: T.must(status.exitstatus))
134
- else
135
- out, status = Open3.capture2(command, opts)
136
- ExecResult.new(out: out, err: nil, status: T.must(status.success?), exit_code: T.must(status.exitstatus))
137
- end
138
- end
139
- end
140
-
141
- # Bundle
142
-
143
- # Read the `contents` of the Gemfile in this context directory
144
- sig { returns(T.nilable(String)) }
145
- def read_gemfile
146
- read("Gemfile")
147
- end
148
-
149
- # Set the `contents` of the Gemfile in this context directory
150
- sig { params(contents: String, append: T::Boolean).void }
151
- def write_gemfile!(contents, append: false)
152
- write!("Gemfile", contents, append: append)
153
- end
154
-
155
- # Run a command with `bundle` in this context directory
156
- sig { params(command: String, version: T.nilable(String)).returns(ExecResult) }
157
- def bundle(command, version: nil)
158
- command = "_#{version}_ #{command}" if version
159
- exec("bundle #{command}")
160
- end
161
-
162
- # Run `bundle install` in this context directory
163
- sig { params(version: T.nilable(String)).returns(ExecResult) }
164
- def bundle_install!(version: nil)
165
- bundle("install", version: version)
166
- end
167
-
168
- # Run a command `bundle exec` in this context directory
169
- sig { params(command: String, version: T.nilable(String)).returns(ExecResult) }
170
- def bundle_exec(command, version: nil)
171
- bundle("exec #{command}", version: version)
172
- end
173
-
174
- # Git
175
-
176
- # Run a command prefixed by `git` in this context directory
177
- sig { params(command: String).returns(ExecResult) }
178
- def git(command)
179
- exec("git #{command}")
180
- end
181
-
182
- # Run `git init` in this context directory
183
- #
184
- # Warning: passing a branch will run `git init -b <branch>` which is only available in git 2.28+.
185
- # In older versions, use `git_init!` followed by `git("checkout -b <branch>")`.
186
- sig { params(branch: T.nilable(String)).returns(ExecResult) }
187
- def git_init!(branch: nil)
188
- if branch
189
- git("init -b #{branch}")
190
- else
191
- git("init")
192
- end
193
- end
194
-
195
- # Run `git checkout` in this context directory
196
- sig { params(ref: String).returns(ExecResult) }
197
- def git_checkout!(ref: "main")
198
- git("checkout #{ref}")
199
- end
200
-
201
- # Get the current git branch in this context directory
202
- sig { returns(T.nilable(String)) }
203
- def git_current_branch
204
- Spoom::Git.current_branch(path: @absolute_path)
205
- end
206
-
207
- # Get the last commit in the currently checked out branch
208
- sig { params(short_sha: T::Boolean).returns(T.nilable(Git::Commit)) }
209
- def git_last_commit(short_sha: true)
210
- Spoom::Git.last_commit(path: @absolute_path, short_sha: short_sha)
211
- end
212
-
213
- # Sorbet
214
-
215
- # Run `bundle exec srb` in this context directory
216
- sig { params(command: String).returns(ExecResult) }
217
- def srb(command)
218
- bundle_exec("srb #{command}")
219
- end
220
-
221
- # Read the contents of `sorbet/config` in this context directory
222
- sig { returns(String) }
223
- def read_sorbet_config
224
- read(Spoom::Sorbet::CONFIG_PATH)
225
- end
226
-
227
- # Set the `contents` of `sorbet/config` in this context directory
228
- sig { params(contents: String, append: T::Boolean).void }
229
- def write_sorbet_config!(contents, append: false)
230
- write!(Spoom::Sorbet::CONFIG_PATH, contents, append: append)
231
- end
232
-
233
- # Read the strictness sigil from the file at `relative_path` (returns `nil` if no sigil)
234
- sig { params(relative_path: String).returns(T.nilable(String)) }
235
- def read_file_strictness(relative_path)
236
- Spoom::Sorbet::Sigils.file_strictness(absolute_path_to(relative_path))
237
- end
238
54
  end
239
55
  end
@@ -12,9 +12,9 @@ module Spoom
12
12
  class << self
13
13
  extend T::Sig
14
14
 
15
- sig { params(path: String, rbi: T::Boolean, sorbet_bin: T.nilable(String)).returns(Snapshot) }
16
- def snapshot(path: ".", rbi: true, sorbet_bin: nil)
17
- config = sorbet_config(path: path)
15
+ sig { params(context: Context, rbi: T::Boolean, sorbet_bin: T.nilable(String)).returns(Snapshot) }
16
+ def snapshot(context, rbi: true, sorbet_bin: nil)
17
+ config = context.sorbet_config
18
18
  config.allowed_extensions.push(".rb", ".rbi") if config.allowed_extensions.empty?
19
19
 
20
20
  new_config = config.copy
@@ -27,25 +27,16 @@ module Spoom
27
27
  new_config.options_string,
28
28
  ]
29
29
 
30
- metrics = Spoom::Sorbet.srb_metrics(
31
- *flags,
32
- path: path,
33
- capture_err: true,
34
- sorbet_bin: sorbet_bin,
35
- )
30
+ metrics = context.srb_metrics(*flags, sorbet_bin: sorbet_bin)
31
+
36
32
  # Collect extra information using a different configuration
37
33
  flags << "--ignore sorbet/rbi/"
38
- metrics_without_rbis = Spoom::Sorbet.srb_metrics(
39
- *flags,
40
- path: path,
41
- capture_err: true,
42
- sorbet_bin: sorbet_bin,
43
- )
34
+ metrics_without_rbis = context.srb_metrics(*flags, sorbet_bin: sorbet_bin)
44
35
 
45
36
  snapshot = Snapshot.new
46
37
  return snapshot unless metrics
47
38
 
48
- last_commit = Spoom::Git.last_commit(path: path)
39
+ last_commit = context.git_last_commit
49
40
  snapshot.commit_sha = last_commit&.sha
50
41
  snapshot.commit_timestamp = last_commit&.timestamp
51
42
 
@@ -79,40 +70,34 @@ module Spoom
79
70
  end
80
71
  end
81
72
 
82
- snapshot.version_static = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-static", path: path)
83
- snapshot.version_runtime = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-runtime", path: path)
73
+ snapshot.version_static = context.gem_version_from_gemfile_lock("sorbet-static")
74
+ snapshot.version_runtime = context.gem_version_from_gemfile_lock("sorbet-runtime")
84
75
 
85
- files = Spoom::Sorbet.srb_files(new_config, path: path)
76
+ files = context.srb_files(with_config: new_config)
86
77
  snapshot.rbi_files = files.count { |file| file.end_with?(".rbi") }
87
78
 
88
79
  snapshot
89
80
  end
90
81
 
91
- sig { params(snapshots: T::Array[Snapshot], palette: D3::ColorPalette, path: String).returns(Report) }
92
- def report(snapshots, palette:, path: ".")
93
- intro_commit = Git.sorbet_intro_commit(path: path)
82
+ sig { params(context: Context, snapshots: T::Array[Snapshot], palette: D3::ColorPalette).returns(Report) }
83
+ def report(context, snapshots, palette:)
84
+ intro_commit = context.sorbet_intro_commit
94
85
 
95
86
  Report.new(
96
- project_name: File.basename(File.expand_path(path)),
87
+ project_name: File.basename(context.absolute_path),
97
88
  palette: palette,
98
89
  snapshots: snapshots,
99
- sigils_tree: sigils_tree(path: path),
90
+ sigils_tree: sigils_tree(context),
100
91
  sorbet_intro_commit: intro_commit&.sha,
101
92
  sorbet_intro_date: intro_commit&.time,
102
93
  )
103
94
  end
104
95
 
105
- sig { params(path: String).returns(Sorbet::Config) }
106
- def sorbet_config(path: ".")
107
- Sorbet::Config.parse_file("#{path}/#{Spoom::Sorbet::CONFIG_PATH}")
108
- end
109
-
110
- sig { params(path: String).returns(FileTree) }
111
- def sigils_tree(path: ".")
112
- config = sorbet_config(path: path)
113
- files = Sorbet.srb_files(config, path: path)
96
+ sig { params(context: Context).returns(FileTree) }
97
+ def sigils_tree(context)
98
+ files = context.srb_files
114
99
 
115
- extensions = config.allowed_extensions
100
+ extensions = context.sorbet_config.allowed_extensions
116
101
  extensions = [".rb"] if extensions.empty?
117
102
  extensions -= [".rbi"]
118
103
 
@@ -120,7 +105,7 @@ module Spoom
120
105
  files.select! { |file| file =~ pattern }
121
106
  files.reject! { |file| file =~ %r{/test/} }
122
107
 
123
- FileTree.new(files, strip_prefix: path)
108
+ FileTree.new(files, strip_prefix: context.absolute_path)
124
109
  end
125
110
  end
126
111
  end
data/lib/spoom/sorbet.rb CHANGED
@@ -39,124 +39,5 @@ module Spoom
39
39
 
40
40
  KILLED_CODE = 137
41
41
  SEGFAULT_CODE = 139
42
-
43
- class << self
44
- extend T::Sig
45
-
46
- sig do
47
- params(
48
- arg: String,
49
- path: String,
50
- capture_err: T::Boolean,
51
- sorbet_bin: T.nilable(String),
52
- ).returns(ExecResult)
53
- end
54
- def srb(*arg, path: ".", capture_err: false, sorbet_bin: nil)
55
- if sorbet_bin
56
- arg.prepend(sorbet_bin)
57
- else
58
- arg.prepend("bundle", "exec", "srb")
59
- end
60
- result = Spoom.exec(*T.unsafe(arg), path: path, capture_err: capture_err)
61
-
62
- case result.exit_code
63
- when KILLED_CODE
64
- raise Error::Killed.new("Sorbet was killed.", result)
65
- when SEGFAULT_CODE
66
- raise Error::Segfault.new("Sorbet segfaulted.", result)
67
- end
68
-
69
- result
70
- end
71
-
72
- sig do
73
- params(
74
- arg: String,
75
- path: String,
76
- capture_err: T::Boolean,
77
- sorbet_bin: T.nilable(String),
78
- ).returns(ExecResult)
79
- end
80
- def srb_tc(*arg, path: ".", capture_err: false, sorbet_bin: nil)
81
- arg.prepend("tc") unless sorbet_bin
82
- srb(*T.unsafe(arg), path: path, capture_err: capture_err, sorbet_bin: sorbet_bin)
83
- end
84
-
85
- # List all files typechecked by Sorbet from its `config`
86
- sig { params(config: Config, path: String).returns(T::Array[String]) }
87
- def srb_files(config, path: ".")
88
- regs = config.ignore.map { |string| Regexp.new(Regexp.escape(string)) }
89
- exts = config.allowed_extensions.empty? ? [".rb", ".rbi"] : config.allowed_extensions
90
- Dir.glob((Pathname.new(path) / "**/*{#{exts.join(",")}}").to_s).reject do |f|
91
- regs.any? { |re| re.match?(f) }
92
- end.sort
93
- end
94
-
95
- sig do
96
- params(
97
- arg: String,
98
- path: String,
99
- capture_err: T::Boolean,
100
- sorbet_bin: T.nilable(String),
101
- ).returns(T.nilable(String))
102
- end
103
- def srb_version(*arg, path: ".", capture_err: false, sorbet_bin: nil)
104
- result = T.let(
105
- T.unsafe(self).srb_tc(
106
- "--no-config",
107
- "--version",
108
- *arg,
109
- path: path,
110
- capture_err: capture_err,
111
- sorbet_bin: sorbet_bin,
112
- ),
113
- ExecResult,
114
- )
115
- return nil unless result.status
116
-
117
- result.out.split(" ")[2]
118
- end
119
-
120
- sig do
121
- params(
122
- arg: String,
123
- path: String,
124
- capture_err: T::Boolean,
125
- sorbet_bin: T.nilable(String),
126
- ).returns(T.nilable(T::Hash[String, Integer]))
127
- end
128
- def srb_metrics(*arg, path: ".", capture_err: false, sorbet_bin: nil)
129
- metrics_file = "metrics.tmp"
130
- metrics_path = "#{path}/#{metrics_file}"
131
- T.unsafe(self).srb_tc(
132
- "--metrics-file",
133
- metrics_file,
134
- *arg,
135
- path: path,
136
- capture_err: capture_err,
137
- sorbet_bin: sorbet_bin,
138
- )
139
- if File.exist?(metrics_path)
140
- metrics = Spoom::Sorbet::MetricsParser.parse_file(metrics_path)
141
- File.delete(metrics_path)
142
- return metrics
143
- end
144
- nil
145
- end
146
-
147
- # Get `gem` version from the `Gemfile.lock` content
148
- #
149
- # Returns `nil` if `gem` cannot be found in the Gemfile.
150
- sig { params(gem: String, path: String).returns(T.nilable(String)) }
151
- def version_from_gemfile_lock(gem: "sorbet", path: ".")
152
- gemfile_path = "#{path}/Gemfile.lock"
153
- return nil unless File.exist?(gemfile_path)
154
-
155
- content = File.read(gemfile_path).match(/^ #{gem} \(.*(\d+\.\d+\.\d+).*\)/)
156
- return nil unless content
157
-
158
- content[1]
159
- end
160
- end
161
42
  end
162
43
  end
@@ -1,17 +1,15 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative "git"
5
-
6
4
  module Spoom
7
5
  class Timeline
8
6
  extend T::Sig
9
7
 
10
- sig { params(from: Time, to: Time, path: String).void }
11
- def initialize(from, to, path: ".")
8
+ sig { params(context: Context, from: Time, to: Time).void }
9
+ def initialize(context, from, to)
10
+ @context = context
12
11
  @from = from
13
12
  @to = to
14
- @path = path
15
13
  end
16
14
 
17
15
  # Return one commit for each month between `from` and `to`
@@ -37,17 +35,16 @@ module Spoom
37
35
  sig { params(dates: T::Array[Time]).returns(T::Array[Git::Commit]) }
38
36
  def commits_for_dates(dates)
39
37
  dates.map do |t|
40
- result = Spoom::Git.log(
38
+ result = @context.git_log(
41
39
  "--since='#{t}'",
42
40
  "--until='#{t.to_date.next_month}'",
43
41
  "--format='format:%h %at'",
44
42
  "--author-date-order",
45
43
  "-1",
46
- path: @path,
47
44
  )
48
45
  next if result.out.empty?
49
46
 
50
- Git.parse_commit(result.out.strip)
47
+ Spoom::Git::Commit.parse_line(result.out.strip)
51
48
  end.compact.uniq(&:sha)
52
49
  end
53
50
  end
data/lib/spoom/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Spoom
5
- VERSION = "1.1.16"
5
+ VERSION = "1.2.0"
6
6
  end