gitt 2.1.1 → 3.0.0

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