gitt 2.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3127915892090cd6023f6ff8ad9e3a221352fc4d9cfd3dd8ac4a82392ae7b22b
4
- data.tar.gz: 3c724f8d0e0befa772bfb549ccc99a1d61e42fa527a4305d05c8acd3181d9925
3
+ metadata.gz: 2a62c7af41ef6056ceb816436a8c80f1d2ee3fd792fbbcadadbc82a2b5eba97d
4
+ data.tar.gz: bc3d13fbd0296b5ca483f02033446fc79fbbd46617d0fd2b06cf07477ba06d3e
5
5
  SHA512:
6
- metadata.gz: 74a64c4b46f502ecf9193c4b31fbefd301d2b92d2a94b074819a44ceb559717003ba15fb6ee1aaf3c39b03da2027d33ebed7309131556b0ba1ba8aa9c9b2d147
7
- data.tar.gz: 2afce970af961e6ecf28a8c8e88b71d5d2405f53f249d2f875cf73bd5d87ccdc55f6eadb4ad5e8f159a06f20c9764dad6d979110aad8e7d5d8a2548b1f55b704
6
+ metadata.gz: df96432454f47a6d4fd57c741949dabd088f61c1a62af03b25fac1860a766b52ecf19e7bd3973b29ee617e1c015588953ba34ca17fd56201f894ec9279819f80
7
+ data.tar.gz: da01de46bb99ad4c59a328101772184703da241949ba9678dfb19c71c2e616a7fd6ab4d422035592ffe15473d33663e4e07e73fa9dede99c5e06660940d9ecdb
checksums.yaml.gz.sig CHANGED
Binary file
data/README.adoc CHANGED
@@ -24,7 +24,7 @@ toc::[]
24
24
  == Features
25
25
 
26
26
  * Wraps a subset of native {git_link} commands with additional enhancements to improve your working experience.
27
- * Answers link:https://dry-rb.org/gems/dry-monads[monads] you can pipe together -- or link:https://alchemists.io/projects/transactable[transact] -- for more powerful and complex workflows.
27
+ * Answers link:https://dry-rb.org/gems/dry-monads[monads] you can link:https://alchemists.io/projects/transactable[pipe] together for more complex workflows.
28
28
  * Provides _optional_ {rspec_link} shared contexts that speed up the testing of your own Git related implementations.
29
29
 
30
30
  == Requirements
@@ -65,17 +65,19 @@ git.tag? # Answers if local or remote tag exists.
65
65
  git.tag_create # Create a new tag.
66
66
  git.tag_last # Answers last tag created.
67
67
  git.tag_local? # Answers if local tag exists?
68
- git.tag_parse # Parses and answers a tag record.
69
68
  git.tag_remote? # Answers if remote tag exists?
69
+ git.tag_show # Answers information about a single tag.
70
70
  git.tagged? # Answers if the repository has any tags.
71
+ git.tags # Answers all tags.
71
72
  git.tags_push # Pushes local tags to remote git.
72
73
  git.uncommitted # Parses a file and answers an unsaved commit message.
74
+
73
75
  ----
74
76
 
75
77
  === Commands
76
78
 
77
79
  Should you want to use individual commands instead of interacting with the `Repository` object, you
78
- can leverage any of the objects in the `Commands` namespace which -- at a minimum -- use the link:https://alchemists.io/articles/interactor_pattern[Command Pattern]. Here are the _select_ commands which are enhanced further:
80
+ can leverage any of the objects in the `Commands` namespace which -- at a minimum -- use the link:https://alchemists.io/articles/interactor_pattern[Command Pattern]. Here are the specific commands which are enhanced further:
79
81
 
80
82
  ==== link:https://git-scm.com/docs/git-branch[Branch]
81
83
 
@@ -85,9 +87,12 @@ Handles branches.
85
87
  ----
86
88
  branch = Gitt::Commands::Branch.new
87
89
 
88
- # Answers branch default (via Git `init.defaultBranch` configuration).
90
+ # Answers branch default (via Git `init.defaultBranch` configuration) of if blank.
89
91
  branch.default # Success "main"
90
92
 
93
+ # Answers branch default fallback if unset or error is detected.
94
+ branch.default "source" # Success "source"
95
+
91
96
  # Accepts any argument you'd send to `git branch`. Example:
92
97
  branch.call "--list" # Success " main\n"
93
98
 
@@ -157,6 +162,9 @@ tag = Gitt::Commands::Tag.new
157
162
  # Example: tag.call "--list"
158
163
  stdout, stderr, status = tag.call
159
164
 
165
+ # Creates a new tag.
166
+ tag.create "0.0.0", "Version 0.0.0"
167
+
160
168
  # Answers true or false base on whether local and remote tag exist.
161
169
  tag.exist? "0.1.0"
162
170
 
@@ -172,6 +180,9 @@ tag.push
172
180
  # Answers if remote tag exists.
173
181
  tag.remote? "0.1.0"
174
182
 
183
+ # Answers details about a specific tag.
184
+ tag.show "1.0.0"
185
+
175
186
  # Answers true or false based on whether repository is tagged.
176
187
  tag.tagged?
177
188
  ----
@@ -221,21 +232,31 @@ An instance of `Gitt::Models::Tag` is what is answered back to when using `Gitt:
221
232
  [source,ruby]
222
233
  ----
223
234
  # #<struct Gitt::Models::Tag
224
- # author_date="Tue Dec 29 17:33:01 2020 -0700",
225
235
  # author_email="demo@example.com",
226
- # author_name="Demo User",
227
- # body="- Added gem skeleton\n- Added RSpec dependency",
228
- # sha="d041d07c29f97b5b06b3b2fd05fa1dd018c7da7c",
229
- # subject="Version 0.1.0",
230
- # version="0.1.0">
236
+ # author_name="Brooke Kuhlmann",
237
+ # authored_at="1671892451",
238
+ # authored_relative_at="1 year ago",
239
+ # body="A demo body.",
240
+ # committed_at="1671997684",
241
+ # committed_relative_at="1 year ago",
242
+ # committer_email="demo@example.com",
243
+ # committer_name="Brooke Kuhlmann",
244
+ # sha="662f32b2846c7bd4f153560478f035197f5279d5",
245
+ # subject="Version 1.0.0",
246
+ # version="1.0.0">
231
247
  ----
232
248
 
233
249
  You get a {struct_link} with the following attributes:
234
250
 
235
- * `author_date`: Stores author creation date.
236
251
  * `author_email`: Stores author email.
237
252
  * `author_name`: Store author name.
253
+ * `authored_at`: Stores author creation date.
254
+ * `authored_relative_at`: Stores author creation date relative to current time.
238
255
  * `body`: Stores body of tag which can be sentences, multiple paragraphs, and/or signature information.
256
+ * `committer_email`: Stores committer email.
257
+ * `committer_name`: Store committer name.
258
+ * `committed_at`: Stores committer creation date.
259
+ * `committed_relative_at`: Stores committer creation date relative to current time.
239
260
  * `sha`: Stores the commit SHA for which this tag labels
240
261
  * `subject`: Stores the subject.
241
262
  * `version`: Stores the version.
@@ -280,12 +301,12 @@ Provides a simple Git repository with a single commit for testing purposes. This
280
301
  [source,ruby]
281
302
  ----
282
303
  require "gitt/rspec/shared_contexts/git_repo"
283
- require "refinements/pathnames"
304
+ require "refinements/pathname"
284
305
 
285
306
  describe Demo do
286
307
  include_context "with Git repository"
287
308
 
288
- using Refinements::Pathnames
309
+ using Refinements::Pathname
289
310
 
290
311
  it "is a demo" do
291
312
  git_repo_dir.change_dir { # Your expectation goes here. }
@@ -300,12 +321,12 @@ Provides a temporary directory (i.e. `tmp/rspec`) for creating directories and o
300
321
  [source,ruby]
301
322
  ----
302
323
  require "gitt/rspec/shared_contexts/temp_dir"
303
- require "refinements/pathnames"
324
+ require "refinements/pathname"
304
325
 
305
326
  describe Demo do
306
327
  include_context "with temporary directory"
307
328
 
308
- using Refinements::Pathnames
329
+ using Refinements::Pathname
309
330
 
310
331
  it "is a demo" do
311
332
  temp_dir.change_dir { # Your expectation goes here. }
data/gitt.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "gitt"
5
- spec.version = "2.1.1"
5
+ spec.version = "3.0.0"
6
6
  spec.authors = ["Brooke Kuhlmann"]
7
7
  spec.email = ["brooke@alchemists.io"]
8
8
  spec.homepage = "https://alchemists.io/projects/gitt"
@@ -22,10 +22,10 @@ Gem::Specification.new do |spec|
22
22
  spec.signing_key = Gem.default_key_path
23
23
  spec.cert_chain = [Gem.default_cert_path]
24
24
 
25
- spec.required_ruby_version = [">= 3.2", "<= 3.3"]
26
- spec.add_dependency "core", "~> 0.1"
25
+ spec.required_ruby_version = "~> 3.3"
26
+ spec.add_dependency "core", "~> 1.0"
27
27
  spec.add_dependency "dry-monads", "~> 1.6"
28
- spec.add_dependency "refinements", "~> 11.0"
28
+ spec.add_dependency "refinements", "~> 12.0"
29
29
  spec.add_dependency "zeitwerk", "~> 2.6"
30
30
 
31
31
  spec.extra_rdoc_files = Dir["README*", "LICENSE*"]
@@ -1,20 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "dry/monads"
4
+
3
5
  module Gitt
4
6
  module Commands
5
7
  # A Git branch command wrapper.
6
8
  class Branch
9
+ include Dry::Monads[:result]
10
+
7
11
  def initialize shell: SHELL
8
12
  @shell = shell
9
13
  end
10
14
 
11
- def default
15
+ def default fallback = "main"
12
16
  shell.call("config", "init.defaultBranch")
13
17
  .fmap(&:chomp)
14
- .fmap { |name| name.empty? ? "main" : name }
18
+ .fmap { |name| name.empty? ? fallback : name }
19
+ .or(Success(fallback))
15
20
  end
16
21
 
17
- def call(*arguments) = shell.call "branch", *arguments
22
+ def call(*) = shell.call("branch", *)
18
23
 
19
24
  def name = shell.call("rev-parse", "--abbrev-ref", "HEAD").fmap(&:chomp)
20
25
 
@@ -13,18 +13,16 @@ module Gitt
13
13
  @shell = shell
14
14
  end
15
15
 
16
- def call(*arguments) = shell.call "config", *arguments
16
+ def call(*) = shell.call("config", *)
17
17
 
18
- def get key, fallback = Core::EMPTY_STRING, *arguments
19
- call(*arguments, "--get", key).fmap(&:chomp)
20
- .or do |error|
21
- block_given? ? yield(error) : Success(fallback)
22
- end
18
+ def get(key, fallback = Core::EMPTY_STRING, *)
19
+ call(*, "--get", key).fmap(&:chomp)
20
+ .or { |error| block_given? ? yield(error) : Success(fallback) }
23
21
  end
24
22
 
25
23
  def origin? = !get("remote.origin.url").value_or(Core::EMPTY_STRING).empty?
26
24
 
27
- def set(key, value, *arguments) = call(*arguments, "--add", key, value).fmap { value }
25
+ def set(key, value, *) = call(*, "--add", key, value).fmap { value }
28
26
 
29
27
  private
30
28
 
@@ -18,6 +18,8 @@ module Gitt
18
18
  committed_relative_at: "%cr",
19
19
  committer_email: "%ce",
20
20
  committer_name: "%cn",
21
+ encoding: "%e",
22
+ notes: "%N",
21
23
  raw: "%B",
22
24
  sha: "%H",
23
25
  signature: "%G?",
@@ -25,18 +27,18 @@ module Gitt
25
27
  trailers: "%(trailers)"
26
28
  }.freeze
27
29
 
28
- def initialize shell: SHELL, key_map: KEY_MAP, parser: Parsers::Commit
30
+ def initialize shell: SHELL, key_map: KEY_MAP, parser: Parsers::Commit.new
29
31
  @shell = shell
30
32
  @key_map = key_map
31
33
  @parser = parser
32
34
  end
33
35
 
34
- def call(*arguments) = shell.call "log", *arguments
36
+ def call(*) = shell.call("log", *)
35
37
 
36
38
  def index *arguments
37
- arguments.prepend(pretty_format)
39
+ arguments.prepend("--shortstat", pretty_format)
38
40
  .then { |pretty_format| call(*pretty_format) }
39
- .fmap { |content| String(content).scrub("?").split %("\n") }
41
+ .fmap { |content| String(content).scrub("?") }
40
42
  .fmap { |entries| build_records entries }
41
43
  end
42
44
 
@@ -58,7 +60,24 @@ module Gitt
58
60
  .then { |structure| %(--pretty=format:"#{structure}") }
59
61
  end
60
62
 
61
- def build_records(entries) = entries.map { |entry| parser.call(entry) }
63
+ def build_records entries
64
+ wrap_statistics entries
65
+ add_empty_statistics entries
66
+ entries.split("<break/>").map { |entry| parser.call entry }
67
+ end
68
+
69
+ # :reek:UtilityFunction
70
+ def wrap_statistics entries
71
+ entries.gsub!(/\d+\sfile.+\d+\s(insertion|deletion).+\n/) do |match|
72
+ "<statistics>#{match}</statistics><break/>"
73
+ end
74
+ end
75
+
76
+ # :reek:UtilityFunction
77
+ def add_empty_statistics entries
78
+ entries.gsub! %(</trailers>\n"\n"<author_email>),
79
+ "</trailers>\n<statistics></statistics>\n<author_email>"
80
+ end
62
81
  end
63
82
  end
64
83
  end
@@ -31,7 +31,7 @@ module Gitt
31
31
  @parser = parser
32
32
  end
33
33
 
34
- def call(*arguments) = shell.call "tag", *arguments
34
+ def call(*) = shell.call("tag", *)
35
35
 
36
36
  def create version, body = Core::EMPTY_STRING, *flags
37
37
  return Failure "Unable to create Git tag without version." unless version
@@ -53,9 +53,15 @@ module Gitt
53
53
  end
54
54
 
55
55
  def last
56
- shell.call("describe", "--abbrev=0", "--tags", "--always")
56
+ shell.call("describe", "--abbrev=0", "--tags")
57
57
  .fmap(&:strip)
58
- .or { |error| Failure error.delete_prefix("fatal: ").chomp }
58
+ .or do |error|
59
+ if error.match?(/no names found/i)
60
+ Failure "No tags found."
61
+ else
62
+ Failure error.delete_prefix("fatal: ").chomp
63
+ end
64
+ end
59
65
  end
60
66
 
61
67
  def local? version
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitt
4
+ # Provides shared behavior for objects that can act like a commit.
5
+ module Directable
6
+ def directive? = amend? || fixup? || squash?
7
+
8
+ def amend? = subject.match?(/\Aamend!\s/)
9
+
10
+ def fixup? = subject.match?(/\Afixup!\s/)
11
+
12
+ def squash? = subject.match?(/\Asquash!\s/)
13
+
14
+ def prefix = subject[/\A[\w\!]+/]
15
+ end
16
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "dry/monads"
4
+
3
5
  module Gitt
4
6
  module Models
5
7
  # Represents commit details.
@@ -15,25 +17,43 @@ module Gitt
15
17
  :committed_relative_at,
16
18
  :committer_email,
17
19
  :committer_name,
20
+ :deletions,
21
+ :encoding,
22
+ :files_changed,
23
+ :insertions,
18
24
  :lines,
25
+ :notes,
19
26
  :raw,
20
27
  :sha,
21
28
  :signature,
22
29
  :subject,
23
30
  :trailers
24
31
  ) do
32
+ include Directable
33
+ include Dry::Monads[:result]
34
+
25
35
  def initialize(**)
26
36
  super
27
37
  freeze
28
38
  end
29
39
 
30
- def amend? = subject.match?(/\Aamend!\s/)
40
+ def find_trailer key
41
+ trailers.find { |trailer| trailer.key == key }
42
+ .then do |trailer|
43
+ return Success trailer if trailer
31
44
 
32
- def fixup? = subject.match?(/\Afixup!\s/)
45
+ Failure "Unable to find trailer for key: #{key.inspect}."
46
+ end
47
+ end
48
+
49
+ def find_trailers key
50
+ trailers.select { |trailer| trailer.key == key }
51
+ .then { |trailers| Success trailers }
52
+ end
33
53
 
34
- def squash? = subject.match?(/\Asquash!\s/)
54
+ def trailer_value_for(key) = find_trailer(key).fmap(&:value)
35
55
 
36
- def prefix? = amend? || fixup? || squash?
56
+ def trailer_values_for(key) = find_trailers(key).fmap { |trailers| trailers.map(&:value) }
37
57
  end
38
58
  end
39
59
  end
@@ -1,17 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "core"
4
+
3
5
  module Gitt
4
6
  module Models
5
7
  # Represents a person within a repository.
6
- Person = Struct.new :name, :delimiter, :email do
8
+ Person = Data.define :name, :delimiter, :email do
7
9
  def self.for(string, parser: Parsers::Person.new) = parser.call string
8
10
 
9
- def initialize(**)
11
+ def initialize name: nil, delimiter: " ", email: nil
10
12
  super
11
- freeze
12
13
  end
13
14
 
14
- def to_s = values.join
15
+ def to_s
16
+ case self
17
+ in String, String, String then "#{name}#{delimiter}<#{email}>"
18
+ in String, String, nil then name
19
+ in nil, String, String then "<#{email}>"
20
+ else Core::EMPTY_STRING
21
+ end
22
+ end
15
23
  end
16
24
  end
17
25
  end
@@ -3,15 +3,14 @@
3
3
  module Gitt
4
4
  module Models
5
5
  # Represents commit trailer details.
6
- Trailer = Struct.new :key, :delimiter, :space, :value, keyword_init: true do
6
+ Trailer = Data.define :key, :delimiter, :space, :value do
7
7
  def self.for(string, parser: Parsers::Trailer.new) = parser.call string
8
8
 
9
- def initialize(**)
9
+ def initialize key:, value:, delimiter: ":", space: " "
10
10
  super
11
- freeze
12
11
  end
13
12
 
14
- def to_s = values.join
13
+ def to_s = to_h.values.join
15
14
  end
16
15
  end
17
16
  end
@@ -6,23 +6,25 @@ module Gitt
6
6
  module Parsers
7
7
  # Extracts attributes from XML formatted content.
8
8
  class Attributer
9
- def self.with(...) = new(...)
10
-
11
9
  def initialize keys = Core::EMPTY_ARRAY
12
10
  @keys = keys
13
11
  end
14
12
 
15
13
  def call content
16
- scrub = String(content).scrub "?"
17
-
18
- keys.reduce({}) do |attributes, key|
19
- attributes.merge key => scrub[%r(<#{key}>(?<value>.*?)</#{key}>)m, :value]
20
- end
14
+ build String(content)
15
+ rescue ArgumentError => error
16
+ error.message.include?("invalid byte") ? build(content.scrub("?")) : raise
21
17
  end
22
18
 
23
19
  private
24
20
 
25
21
  attr_reader :keys
22
+
23
+ def build content
24
+ keys.each.with_object({}) do |key, attributes|
25
+ attributes[key] = content[%r(<#{key}>(?<value>.*?)</#{key}>)m, :value]
26
+ end
27
+ end
26
28
  end
27
29
  end
28
30
  end
@@ -1,16 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "refinements/hashes"
3
+ require "refinements/hash"
4
4
 
5
5
  module Gitt
6
6
  module Parsers
7
7
  # Parses raw commit information to produce a commit record.
8
8
  class Commit
9
- using Refinements::Hashes
9
+ using Refinements::Hash
10
10
 
11
- def self.call(...) = new.call(...)
12
-
13
- def initialize attributer: Attributer.with(Commands::Log::KEY_MAP.keys),
11
+ def initialize attributer: Attributer.new(Commands::Log::KEY_MAP.keys.append(:statistics)),
14
12
  sanitizers: Sanitizers::CONTAINER,
15
13
  model: Models::Commit
16
14
  @attributer = attributer
@@ -18,11 +16,14 @@ module Gitt
18
16
  @model = model
19
17
  end
20
18
 
19
+ # rubocop:todo Layout/LineLength
21
20
  def call content
22
21
  attributer.call(content)
23
22
  .then { |attributes| process attributes }
23
+ .then { |attributes| attributes.merge! statistic_sanitizer.call(attributes.delete(:statistics)) }
24
24
  .then { |attributes| model[**attributes] }
25
25
  end
26
+ # rubocop:enable Layout/LineLength
26
27
 
27
28
  private
28
29
 
@@ -64,6 +65,8 @@ module Gitt
64
65
 
65
66
  def scissors_sanitizer = sanitizers.fetch :scissors
66
67
 
68
+ def statistic_sanitizer = sanitizers.fetch :statistic
69
+
67
70
  def signature_sanitizer = sanitizers.fetch :signature
68
71
 
69
72
  def trailers_sanitizer = sanitizers.fetch :trailers
@@ -2,26 +2,28 @@
2
2
 
3
3
  module Gitt
4
4
  module Parsers
5
- # Parses raw trailer data to produce a trailer record.
5
+ # Parses trailer to produce a person.
6
6
  class Person
7
- PATTERN = /
8
- \A # Start of line.
9
- (?<name>.*?) # Name (smallest possible).
10
- (?<delimiter>\s?) # Space delimiter (optional).
11
- (?<email><.+>)? # Collaborator email (optional).
12
- \Z # End of line.
13
- /x
14
-
15
- def initialize model: Models::Person, pattern: PATTERN
16
- @pattern = pattern
7
+ def initialize email_start: "<", email_end: ">", model: Models::Person
8
+ @email_start = email_start
9
+ @email_end = email_end
17
10
  @model = model
18
11
  end
19
12
 
20
- def call(content) = model[**content.match(pattern).named_captures]
13
+ def call content
14
+ if content.start_with? email_start
15
+ model[email: content.delete_prefix(email_start).delete_suffix(email_end)]
16
+ else
17
+ name, email = content.split " #{email_start}"
18
+ email.delete_suffix! email_end if email
19
+
20
+ model[name:, email:]
21
+ end
22
+ end
21
23
 
22
24
  private
23
25
 
24
- attr_reader :pattern, :model
26
+ attr_reader :email_start, :email_end, :model
25
27
  end
26
28
  end
27
29
  end
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "refinements/hashes"
3
+ require "refinements/hash"
4
4
 
5
5
  module Gitt
6
6
  module Parsers
7
7
  # Parses raw tag information to produce a tag record.
8
8
  class Tag
9
- using Refinements::Hashes
9
+ using Refinements::Hash
10
10
 
11
- def initialize attributer: Attributer.with(Commands::Tag::KEY_MAP.keys),
11
+ def initialize attributer: Attributer.new(Commands::Tag::KEY_MAP.keys),
12
12
  sanitizers: Sanitizers::CONTAINER,
13
13
  model: Models::Tag
14
14
  @attributer = attributer
@@ -12,20 +12,21 @@ module Gitt
12
12
  \Z # End of line.
13
13
  /x
14
14
 
15
- def initialize model: Models::Trailer, pattern: PATTERN
15
+ def initialize pattern: PATTERN, model: Models::Trailer
16
16
  @pattern = pattern
17
17
  @model = model
18
+ @empty = model[key: nil, value: nil].to_h
18
19
  end
19
20
 
20
21
  def call content
21
22
  content.match(pattern)
22
- .then { |data| data ? data.named_captures : {} }
23
+ .then { |data| data ? data.named_captures(symbolize_names: true) : empty }
23
24
  .then { |attributes| model[**attributes] }
24
25
  end
25
26
 
26
27
  private
27
28
 
28
- attr_reader :pattern, :model
29
+ attr_reader :pattern, :model, :empty
29
30
  end
30
31
  end
31
32
  end
@@ -20,7 +20,7 @@ module Gitt
20
20
 
21
21
  def branch(...) = commands.fetch(__method__).call(...)
22
22
 
23
- def branch_default = commands.fetch(:branch).default
23
+ def branch_default(...) = commands.fetch(:branch).default(...)
24
24
 
25
25
  def branch_name = commands.fetch(:branch).name
26
26
 
@@ -3,7 +3,7 @@
3
3
  RSpec.shared_context "with Git repository" do
4
4
  include_context "with temporary directory"
5
5
 
6
- using Refinements::Pathnames
6
+ using Refinements::Pathname
7
7
 
8
8
  let(:git_repo_dir) { temp_dir.join "test" }
9
9
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  RSpec.shared_context "with temporary directory" do
4
- using Refinements::Pathnames
4
+ using Refinements::Pathname
5
5
 
6
6
  let(:temp_dir) { Bundler.root.join "tmp", "rspec" }
7
7
 
@@ -9,6 +9,7 @@ module Gitt
9
9
  paragraphs: Paragraphs,
10
10
  scissors: Scissors,
11
11
  signature: Signature,
12
+ statistic: Statistic.new,
12
13
  trailers: Trailers.new
13
14
  }.freeze
14
15
  end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "core"
4
+
3
5
  module Gitt
4
6
  module Sanitizers
5
- Date = -> value { value.sub(/\s.+\Z/, "") if value }
7
+ Date = -> value { value.sub(/\s.+\Z/, Core::EMPTY_STRING) if value }
6
8
  end
7
9
  end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "core"
4
+
3
5
  module Gitt
4
6
  module Sanitizers
5
- Email = -> value { value.tr "<>", "" if value }
7
+ Email = -> value { value.tr "<>", Core::EMPTY_STRING if value }
6
8
  end
7
9
  end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "core"
4
+
3
5
  module Gitt
4
6
  module Sanitizers
5
- Scissors = -> value { value.sub(/^#\s-.+\s>8\s-.+/m, "") if value }
7
+ Scissors = -> value { value.sub(/^#\s-.+\s>8\s-.+/m, Core::EMPTY_STRING) if value }
6
8
  end
7
9
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitt
4
+ module Sanitizers
5
+ # Converts raw text into a statistics hash.
6
+ class Statistic
7
+ EMPTY = {files_changed: 0, insertions: 0, deletions: 0}.freeze
8
+
9
+ PATTERN = /
10
+ (?<total>\d+) # Total capture group.
11
+ \s # Space delimiter.
12
+ (?<kind>file|insertion|deletion) # Kind capture group.
13
+ /x
14
+
15
+ def initialize empty: EMPTY, pattern: PATTERN
16
+ @empty = empty
17
+ @pattern = pattern
18
+ end
19
+
20
+ # :reek:TooManyStatements
21
+ def call text
22
+ return empty unless text
23
+
24
+ text.scan(pattern).each.with_object(empty.dup) do |(number, kind), stats|
25
+ total = number.to_i
26
+
27
+ case kind
28
+ when "file" then stats[:files_changed] = total
29
+ when "insertion" then stats[:insertions] = total
30
+ when "deletion" then stats[:deletions] = total
31
+ # :nocov:
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :empty, :pattern
39
+ end
40
+ end
41
+ end
data/lib/gitt/shell.rb CHANGED
@@ -12,8 +12,8 @@ module Gitt
12
12
  @client = client
13
13
  end
14
14
 
15
- def call(...)
16
- client.capture3("git", ...).then do |stdout, stderr, status|
15
+ def call(*, **)
16
+ client.capture3("git", *, **).then do |stdout, stderr, status|
17
17
  status.success? ? Success(stdout) : Failure(stderr)
18
18
  end
19
19
  end
data/lib/gitt.rb CHANGED
@@ -17,4 +17,6 @@ module Gitt
17
17
  def self.loader registry = Zeitwerk::Registry
18
18
  @loader ||= registry.loaders.find { |loader| loader.tag == File.basename(__FILE__, ".rb") }
19
19
  end
20
+
21
+ def self.new(...) = Repository.new(...)
20
22
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitt
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brooke Kuhlmann
@@ -35,7 +35,7 @@ cert_chain:
35
35
  3n5C8/6Zh9DYTkpcwPSuIfAga6wf4nXc9m6JAw8AuMLaiWN/r/2s4zJsUHYERJEu
36
36
  gZGm4JqtuSg8pYjPeIJxS960owq+SfuC+jxqmRA54BisFCv/0VOJi7tiJVY=
37
37
  -----END CERTIFICATE-----
38
- date: 2023-11-16 00:00:00.000000000 Z
38
+ date: 2024-01-01 00:00:00.000000000 Z
39
39
  dependencies:
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: core
@@ -43,14 +43,14 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '0.1'
46
+ version: '1.0'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '0.1'
53
+ version: '1.0'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: dry-monads
56
56
  requirement: !ruby/object:Gem::Requirement
@@ -71,14 +71,14 @@ dependencies:
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '11.0'
74
+ version: '12.0'
75
75
  type: :runtime
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '11.0'
81
+ version: '12.0'
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: zeitwerk
84
84
  requirement: !ruby/object:Gem::Requirement
@@ -110,6 +110,7 @@ files:
110
110
  - lib/gitt/commands/config.rb
111
111
  - lib/gitt/commands/log.rb
112
112
  - lib/gitt/commands/tag.rb
113
+ - lib/gitt/directable.rb
113
114
  - lib/gitt/models/commit.rb
114
115
  - lib/gitt/models/person.rb
115
116
  - lib/gitt/models/tag.rb
@@ -130,6 +131,7 @@ files:
130
131
  - lib/gitt/sanitizers/paragraphs.rb
131
132
  - lib/gitt/sanitizers/scissors.rb
132
133
  - lib/gitt/sanitizers/signature.rb
134
+ - lib/gitt/sanitizers/statistic.rb
133
135
  - lib/gitt/sanitizers/trailers.rb
134
136
  - lib/gitt/shell.rb
135
137
  homepage: https://alchemists.io/projects/gitt
@@ -149,10 +151,7 @@ require_paths:
149
151
  - lib
150
152
  required_ruby_version: !ruby/object:Gem::Requirement
151
153
  requirements:
152
- - - ">="
153
- - !ruby/object:Gem::Version
154
- version: '3.2'
155
- - - "<="
154
+ - - "~>"
156
155
  - !ruby/object:Gem::Version
157
156
  version: '3.3'
158
157
  required_rubygems_version: !ruby/object:Gem::Requirement
@@ -161,7 +160,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
161
160
  - !ruby/object:Gem::Version
162
161
  version: '0'
163
162
  requirements: []
164
- rubygems_version: 3.4.22
163
+ rubygems_version: 3.5.3
165
164
  signing_key:
166
165
  specification_version: 4
167
166
  summary: A monadic Object API for the Git CLI.
metadata.gz.sig CHANGED
Binary file