ruby-maat 1.2.0 → 1.3.4

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: 6015693182fa71b7676389768560ffe75b15b8cc789878a8de2897a4c1e35535
4
- data.tar.gz: 7074ca652d6e29ef33502c92769c2ed55f74453c4478a40a401ddd36c3718492
3
+ metadata.gz: d51369f42199d48e3ad5afb4d19737b75cccb62eb8dca64a54a0ac025a05f37c
4
+ data.tar.gz: 54ed689f672b96a2825961a9d5d22772d0a5d67b7ae2ea827377b001bbe09605
5
5
  SHA512:
6
- metadata.gz: e398056d4b83287eb1e926603986243a33005856fb1755d22fadbd1ac14cbb41b9ed1238edbf47033c2164f9a526a74f47b313587abd634063dded3639918330
7
- data.tar.gz: fcc96417f9165035d40b7f584a3a9da06234b6e579a505b82678f7da0ce50cb8b74c99da151fdd52d43257f1a82395b109e9ff99fae3f937f8a91f553a313e17
6
+ metadata.gz: 8cd8d93ee615d5f9917733ef693a07cbbb9a5122944fd12210d19904aa00302d57d5d997c39eb05d112cee1a4cc2d5fc26497626d7a9257df44ef724051c6df2
7
+ data.tar.gz: 635137995c30cd76f544bde641cc969fee86b229ca2d7fa667b00b23b4f22b8b96e30066424c8f584766bfa70408c04d8d43e3c539687a452bc48884ef8c5516
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "1.2.0"
2
+ ".": "1.4.1"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -5,6 +5,84 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.4.1](https://github.com/viamin/ruby-maat/compare/v1.4.0...v1.4.1) (2026-09-21)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * 37: Can we identify merge commits to link commits to PRs? ([#44](https://github.com/viamin/ruby-maat/issues/44)) ([52ebb51](https://github.com/viamin/ruby-maat/commit/52ebb51b9f169ed1b0f28e880bbc4f876eb3cd61))
14
+
15
+ ## [1.4.0](https://github.com/viamin/ruby-maat/compare/v1.3.4...v1.4.0) (2026-09-19)
16
+
17
+
18
+ ### Features
19
+
20
+ * identify merge commits to link commits to PRs for coupling analysis ([#37](https://github.com/viamin/ruby-maat/issues/37)) ([ee16386](https://github.com/viamin/ruby-maat/commit/ee16386459d589886525d5d3b4ee7da2bef144cc))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * 37: Can we identify merge commits to link commits to PRs? ([#45](https://github.com/viamin/ruby-maat/issues/45)) ([6d57c5d](https://github.com/viamin/ruby-maat/commit/6d57c5d91bcc4f5dc6b4bb539021db327af1712d))
26
+ * address remaining review comments on merge commit grouper ([69d62a4](https://github.com/viamin/ruby-maat/commit/69d62a47205d5426d5a948ad25ac8c79fa2e9554))
27
+ * address remaining review comments on PR [#45](https://github.com/viamin/ruby-maat/issues/45) ([09f4279](https://github.com/viamin/ruby-maat/commit/09f4279bbd486ff470af3842cd2b7a0f422e1947))
28
+ * address review comments on MergeCommitGrouper ([15be4b5](https://github.com/viamin/ruby-maat/commit/15be4b54a728c4d06ff08fffedf92d0f4d257e2c))
29
+ * allow hyphens in author names in Git2Parser regexes ([dac123f](https://github.com/viamin/ruby-maat/commit/dac123f124cb5f82d3891427377d090f67fa668a))
30
+ * check for nil instead of empty parent_revisions in --group-by-merge validation ([a2c1b98](https://github.com/viamin/ruby-maat/commit/a2c1b9808d54132e5abe58ac854b3ef03879590c))
31
+ * clarify Set require comment to address review feedback ([4996924](https://github.com/viamin/ruby-maat/commit/4996924c3877ae698a3920253a39d1262402fbcb))
32
+ * improve MergeCommitGrouper correctness and performance ([cd6b500](https://github.com/viamin/ruby-maat/commit/cd6b500c413e32ee7b870be1a98faa08d0742a28))
33
+ * reject mixed-format logs in --group-by-merge validation ([21004c3](https://github.com/viamin/ruby-maat/commit/21004c31e8daa69d0876648bf3a2409f12a8248f))
34
+ * rewrite feature-branch dates to merge commit date for consistency ([a6a4cfc](https://github.com/viamin/ruby-maat/commit/a6a4cfcd417e99a58caf96376b9b6ab81a3ce9df))
35
+ * validate parent metadata for --group-by-merge and optimize octopus merge graph walks ([28742c0](https://github.com/viamin/ruby-maat/commit/28742c00443cb78cdbd02e9127d8db3d0089f0c4))
36
+
37
+ ## [1.3.4](https://github.com/viamin/ruby-maat/compare/v1.3.3...v1.3.4) (2025-09-08)
38
+
39
+
40
+ ### Improvements
41
+
42
+ * simplify CI workflow and remove redundant release task ([b37fcd1](https://github.com/viamin/ruby-maat/commit/b37fcd11a885a9a45103b74c22894aa21c5ab824))
43
+
44
+ ## [1.3.3](https://github.com/viamin/ruby-maat/compare/v1.3.2...v1.3.3) (2025-09-08)
45
+
46
+
47
+ ### Bug Fixes
48
+
49
+ * use correct task name rubygems:push instead of rubygem:push ([10d4795](https://github.com/viamin/ruby-maat/commit/10d4795b56346189cfd140dc1fa7c0aa7458b64d))
50
+
51
+ ## [1.3.2](https://github.com/viamin/ruby-maat/compare/v1.3.1...v1.3.2) (2025-09-08)
52
+
53
+
54
+ ### Bug Fixes
55
+
56
+ * add description to release task to satisfy RuboCop ([51c8bb4](https://github.com/viamin/ruby-maat/commit/51c8bb40d3057de0a71f33db2ae6bc5eac423ee0))
57
+ * override release task to skip tag creation ([7f22222](https://github.com/viamin/ruby-maat/commit/7f22222df12e90d8507be5baa584a6cce34470fa))
58
+
59
+ ## [1.3.1](https://github.com/viamin/ruby-maat/compare/v1.3.0...v1.3.1) (2025-09-08)
60
+
61
+
62
+ ### Bug Fixes
63
+
64
+ * **Gemfile.lock:** update ruby-maat version to 1.3.0 ([c5f8f1d](https://github.com/viamin/ruby-maat/commit/c5f8f1d659220d0db4015ac21fbb285d454a8a53))
65
+ * **release:** update version to 1.3.0 and enhance release-please configuration ([76c5222](https://github.com/viamin/ruby-maat/commit/76c5222720ee66c5e4238720c07f9e33edf1587e))
66
+ * **release:** update version to 1.3.0 and enhance release-please configuration ([6e96292](https://github.com/viamin/ruby-maat/commit/6e962922db3179f5b3d51c13db513ec89ea23fcb))
67
+
68
+
69
+ ### Dependencies
70
+
71
+ * **deps:** bump rexml from 3.4.2 to 3.4.3 ([10300aa](https://github.com/viamin/ruby-maat/commit/10300aa75eca1441fa7a9d80b285f0195ca5c078))
72
+
73
+ ## [1.3.0](https://github.com/viamin/ruby-maat/compare/v1.2.0...v1.3.0) (2025-09-03)
74
+
75
+
76
+ ### Features
77
+
78
+ * add command preview functionality before execution ([3801f15](https://github.com/viamin/ruby-maat/commit/3801f15cf31cca8d519082ce78890efa4b5b9c65))
79
+ * add command preview functionality before execution ([499b3d5](https://github.com/viamin/ruby-maat/commit/499b3d5c929175d2e394d2877ff0ab30e973739e))
80
+
81
+
82
+ ### Bug Fixes
83
+
84
+ * move release-type out of packages section of release-please-config ([ee87f5b](https://github.com/viamin/ruby-maat/commit/ee87f5b1a2e4d48ddc36ea516a9a968b23e75f39))
85
+
8
86
  ## [1.2.0](https://github.com/viamin/ruby-maat/compare/ruby-maat-v1.1.3...ruby-maat/v1.2.0) (2025-08-17)
9
87
 
10
88
 
data/lib/ruby_maat/app.rb CHANGED
@@ -44,6 +44,7 @@ module RubyMaat
44
44
  change_records = parser.parse
45
45
 
46
46
  # Apply data transformations
47
+ change_records = apply_merge_grouping(change_records)
47
48
  change_records = apply_grouping(change_records)
48
49
  change_records = apply_temporal_grouping(change_records)
49
50
  change_records = apply_team_mapping(change_records)
@@ -94,6 +95,30 @@ module RubyMaat
94
95
  end
95
96
  end
96
97
 
98
+ def apply_merge_grouping(change_records)
99
+ return change_records unless @options[:group_by_merge]
100
+ return change_records if change_records.nil? || change_records.empty?
101
+
102
+ # Check for non-nil parent_revisions (not non-empty) because root commits
103
+ # legitimately have parent_revisions == [] when parsed with the parents format.
104
+ records_with_parents = change_records.count { |record| !record.parent_revisions.nil? }
105
+ records_without_parents = change_records.count { |record| record.parent_revisions.nil? }
106
+
107
+ if records_with_parents.zero?
108
+ raise ArgumentError,
109
+ "--group-by-merge requires parent revision metadata in the log. " \
110
+ "Please regenerate the log using the 'pr-coupling' preset or another format that includes parent hashes."
111
+ elsif records_without_parents.positive?
112
+ raise ArgumentError,
113
+ "--group-by-merge requires parent revision metadata for all commits, " \
114
+ "but the provided log mixes records with and without parent hashes. " \
115
+ "Please regenerate the log using the 'pr-coupling' preset or another format that includes parent hashes."
116
+ end
117
+
118
+ grouper = RubyMaat::Groupers::MergeCommitGrouper.new
119
+ grouper.group(change_records)
120
+ end
121
+
97
122
  def apply_grouping(change_records)
98
123
  return change_records unless @options[:group]
99
124
 
@@ -4,9 +4,10 @@ module RubyMaat
4
4
  # Represents a single change/modification record from VCS
5
5
  # This is the fundamental data structure that flows through the entire pipeline
6
6
  class ChangeRecord
7
- attr_reader :entity, :author, :date, :revision, :message, :loc_added, :loc_deleted
7
+ attr_reader :entity, :author, :date, :revision, :message, :loc_added, :loc_deleted, :parent_revisions
8
8
 
9
- def initialize(entity:, author:, date:, revision:, message: nil, loc_added: nil, loc_deleted: nil)
9
+ def initialize(entity:, author:, date:, revision:, message: nil, loc_added: nil, loc_deleted: nil,
10
+ parent_revisions: nil)
10
11
  @entity = entity
11
12
  @author = author
12
13
  @date = date.is_a?(Date) ? date : Date.parse(date)
@@ -14,6 +15,14 @@ module RubyMaat
14
15
  @message = message
15
16
  @loc_added = loc_added.to_i if loc_added && (!loc_added.is_a?(Float) || !loc_added.nan?)
16
17
  @loc_deleted = loc_deleted.to_i if loc_deleted && (!loc_deleted.is_a?(Float) || !loc_deleted.nan?)
18
+ @parent_revisions = parent_revisions
19
+ end
20
+
21
+ # True when the commit has multiple parents (i.e., a merge commit).
22
+ # Returns false when parent metadata is unavailable (nil), since the parser
23
+ # could not determine the parent count.
24
+ def merge_commit?
25
+ parent_revisions.is_a?(Array) && parent_revisions.size >= 2
17
26
  end
18
27
 
19
28
  def to_h
@@ -24,7 +33,8 @@ module RubyMaat
24
33
  revision: revision,
25
34
  message: message,
26
35
  loc_added: loc_added,
27
- loc_deleted: loc_deleted
36
+ loc_deleted: loc_deleted,
37
+ parent_revisions: parent_revisions
28
38
  }
29
39
  end
30
40
 
data/lib/ruby_maat/cli.rb CHANGED
@@ -362,6 +362,12 @@ module RubyMaat
362
362
  raise ArgumentError, "Invalid date format for --age-time-now: #{date_str}. Use YYYY-MM-dd format."
363
363
  end
364
364
 
365
+ opts.on("--group-by-merge",
366
+ "Group commits by merge commit for PR-level coupling analysis. " \
367
+ "Requires log generated with parent hashes (use --preset pr-coupling).") do
368
+ @options[:group_by_merge] = true
369
+ end
370
+
365
371
  opts.on("--verbose-results",
366
372
  "Includes additional analysis details together with the results. Only implemented for change coupling.") do
367
373
  @options[:verbose_results] = true
@@ -101,6 +101,9 @@ module RubyMaat
101
101
  end
102
102
 
103
103
  def execute_command(command)
104
+ # Show command preview and ask for confirmation
105
+ show_command_preview(command)
106
+
104
107
  puts "Executing: #{command}" if @options[:verbose]
105
108
 
106
109
  # Use proper subprocess execution to prevent command injection
@@ -262,6 +265,25 @@ module RubyMaat
262
265
  puts "Please enter 'y' or 'n'"
263
266
  end
264
267
  end
268
+
269
+ def show_command_preview(command)
270
+ # Skip preview in non-interactive modes, when quiet is set, or during tests
271
+ return if @options[:quiet] || !$stdin.tty? || test_environment?
272
+
273
+ puts "\n" + "=" * 60
274
+ puts "COMMAND PREVIEW"
275
+ puts "=" * 60
276
+ puts "Repository: #{@repository_path}"
277
+ puts "Command: #{command}"
278
+ puts "=" * 60
279
+ puts "\nPress Enter to execute this command..."
280
+ $stdin.gets
281
+ end
282
+
283
+ def test_environment?
284
+ # Check if we're in a test environment
285
+ !!(defined?(RSpec) || ENV["RUBY_MAAT_TEST"] == "true" || ENV["RSPEC_CORE_PID"])
286
+ end
265
287
  end
266
288
  end
267
289
  end
@@ -48,6 +48,14 @@ module RubyMaat
48
48
  no_renames: true,
49
49
  all_branches: true
50
50
  }
51
+ },
52
+ "pr-coupling" => {
53
+ description: "Format with parent hashes for PR-level coupling analysis (use with --group-by-merge)",
54
+ options: {
55
+ format: "git2-parents",
56
+ no_renames: true,
57
+ all_branches: true
58
+ }
51
59
  }
52
60
  }.freeze
53
61
 
@@ -72,6 +80,8 @@ module RubyMaat
72
80
  case format
73
81
  when "git2"
74
82
  build_git2_command(options)
83
+ when "git2-parents"
84
+ build_git2_parents_command(options)
75
85
  when "legacy"
76
86
  build_legacy_command(options)
77
87
  else
@@ -87,10 +97,12 @@ module RubyMaat
87
97
  # Format selection
88
98
  puts "Output formats:"
89
99
  puts " 1. git2 (recommended, faster parsing)"
90
- puts " 2. legacy (backward compatibility)"
100
+ puts " 2. git2-parents (git2 with parent hashes, for --group-by-merge)"
101
+ puts " 3. legacy (backward compatibility)"
91
102
 
92
- format_choice = ask_integer("Choose format", 1, 2)
93
- options[:format] = (format_choice == 1) ? "git2" : "legacy"
103
+ format_choice = ask_integer("Choose format", 1, 3)
104
+ formats = {1 => "git2", 2 => "git2-parents", 3 => "legacy"}
105
+ options[:format] = formats[format_choice]
94
106
 
95
107
  # Branch selection
96
108
  options[:all_branches] = ask_yes_no("Include all branches?", options[:all_branches])
@@ -119,7 +131,7 @@ module RubyMaat
119
131
  parts << "--all" if options[:all_branches]
120
132
  parts << "--numstat"
121
133
  parts << "--date=short"
122
- parts << "--pretty=format:'--%h--%ad--%aN'"
134
+ parts << "--pretty=format:'--%h--%ad--%aN--%s'"
123
135
  parts << "--no-renames" if options[:no_renames]
124
136
 
125
137
  # Date filtering (with validation and shell escaping)
@@ -135,6 +147,25 @@ module RubyMaat
135
147
  parts.join(" ")
136
148
  end
137
149
 
150
+ def build_git2_parents_command(options)
151
+ parts = ["git", "log"]
152
+
153
+ parts << "--all" if options[:all_branches]
154
+ parts << "--numstat"
155
+ parts << "--date=short"
156
+ parts << "--pretty=format:'--%h--%p--%ad--%aN--%s'"
157
+ parts << "--no-renames" if options[:no_renames]
158
+
159
+ parts << "--after=#{shell_escape(validate_date(options[:since]))}" if options[:since]
160
+ parts << "--before=#{shell_escape(validate_date(options[:until]))}" if options[:until]
161
+
162
+ parts << "--author=#{shell_escape(options[:author])}" if options[:author]
163
+
164
+ parts << "--" << shell_escape(options[:path]) if options[:path]
165
+
166
+ parts.join(" ")
167
+ end
168
+
138
169
  def build_legacy_command(options)
139
170
  parts = ["git", "log"]
140
171
 
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ # NOTE: Set has been a built-in class (autoloaded without require) since
4
+ # Ruby 3.2, which is this project's minimum version (see gemspec:
5
+ # required_ruby_version >= "3.2.0"). An explicit `require "set"` is
6
+ # unnecessary and triggers RuboCop's Lint/RedundantRequireStatement cop.
7
+
8
+ module RubyMaat
9
+ module Groupers
10
+ # Groups commits by their merge commit to enable PR-level coupling analysis.
11
+ #
12
+ # When Git uses a merge-based workflow (e.g., GitHub PRs), individual commits
13
+ # on feature branches are combined into merge commits on the main branch.
14
+ # This grouper identifies merge commits and rewrites the revision of child
15
+ # commits so that all commits belonging to a merge share the same revision.
16
+ #
17
+ # This allows the coupling analysis to treat all files changed in a PR as
18
+ # co-changing, giving more meaningful coupling results.
19
+ #
20
+ # Requires log data generated with parent hashes:
21
+ # git log --all --numstat --date=short --pretty=format:'--%h--%p--%ad--%aN--%s' --no-renames
22
+ class MergeCommitGrouper
23
+ def group(change_records)
24
+ commit_info = build_commit_info(change_records)
25
+ merge_map = build_merge_map(commit_info)
26
+
27
+ return change_records if merge_map.empty?
28
+
29
+ merge_dates = build_merge_dates(commit_info)
30
+ rewrite_records(change_records, merge_map, merge_dates)
31
+ end
32
+
33
+ private
34
+
35
+ def build_commit_info(records)
36
+ commits = {}
37
+ records.each do |record|
38
+ rev = record.revision
39
+ commits[rev] ||= {parents: record.parent_revisions || [], merge: false, date: record.date}
40
+ commits[rev][:merge] = true if record.merge_commit?
41
+ end
42
+ commits
43
+ end
44
+
45
+ def build_merge_map(commits)
46
+ merge_map = {}
47
+ merges = commits.select { |_, info| info[:merge] }
48
+
49
+ merges.each do |merge_rev, info|
50
+ parents = info[:parents] || []
51
+ mainline_parent = parents[0]
52
+ # Skip this merge if it does not have a valid mainline parent present
53
+ # in the commit set, or if it is not a true merge (single parent).
54
+ next unless mainline_parent && parents.length > 1 && commits.key?(mainline_parent)
55
+
56
+ # Compute mainline ancestors once per merge commit and reuse across
57
+ # all feature parents, avoiding repeated graph walks for octopus merges.
58
+ mainline_ancestors = collect_ancestors(mainline_parent, commits)
59
+ # If we cannot determine any ancestors for the mainline parent (for
60
+ # example due to a filtered log), skip grouping for this merge to
61
+ # avoid incorrect rewrites with an incomplete graph.
62
+ next if mainline_ancestors.empty?
63
+
64
+ feature_parents = parents[1..].compact
65
+ feature_parents.each do |feature_parent|
66
+ feature_commits = find_feature_commits(feature_parent, mainline_ancestors, commits)
67
+ feature_commits.each { |rev| merge_map[rev] = merge_rev }
68
+ end
69
+ end
70
+
71
+ merge_map
72
+ end
73
+
74
+ # Walk backward from the feature branch tip, collecting commits that belong
75
+ # to this merge. Excludes any commits reachable from the mainline parent so
76
+ # that intermediate merges from main into the feature branch don't pull in
77
+ # unrelated mainline history.
78
+ #
79
+ # +mainline_ancestors+ is a pre-computed Set of commits reachable from the
80
+ # mainline parent, passed in to avoid redundant graph walks when a merge
81
+ # has multiple feature parents (octopus merges).
82
+ def find_feature_commits(start, mainline_ancestors, commits)
83
+ visited = Set.new
84
+ queue = [start]
85
+ head = 0
86
+
87
+ while head < queue.length
88
+ current = queue[head]
89
+ head += 1
90
+ next if mainline_ancestors.include?(current) || visited.include?(current) || !commits.key?(current)
91
+
92
+ visited << current
93
+
94
+ parents = commits[current][:parents]
95
+ parents&.each { |p| queue << p }
96
+ end
97
+
98
+ visited
99
+ end
100
+
101
+ # Collect all ancestors reachable from a given commit (inclusive).
102
+ def collect_ancestors(start, commits)
103
+ ancestors = Set.new
104
+ queue = [start]
105
+ head = 0
106
+
107
+ while head < queue.length
108
+ current = queue[head]
109
+ head += 1
110
+ next if ancestors.include?(current) || !commits.key?(current)
111
+
112
+ ancestors << current
113
+
114
+ parents = commits[current][:parents]
115
+ parents&.each { |p| queue << p }
116
+ end
117
+
118
+ ancestors
119
+ end
120
+
121
+ # Build a lookup from merge revision to its date, used to align
122
+ # rewritten feature-branch records with the merge commit's date.
123
+ def build_merge_dates(commits)
124
+ dates = {}
125
+ commits.each do |rev, info|
126
+ dates[rev] = info[:date] if info[:merge]
127
+ end
128
+ dates
129
+ end
130
+
131
+ def rewrite_records(records, merge_map, merge_dates)
132
+ records.map do |record|
133
+ merge_rev = merge_map[record.revision]
134
+ if merge_rev
135
+ ChangeRecord.new(
136
+ entity: record.entity,
137
+ author: record.author,
138
+ date: merge_dates[merge_rev] || record.date,
139
+ revision: merge_rev,
140
+ message: record.message,
141
+ loc_added: record.loc_added,
142
+ loc_deleted: record.loc_deleted,
143
+ parent_revisions: record.parent_revisions
144
+ )
145
+ else
146
+ record
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -5,17 +5,27 @@ module RubyMaat
5
5
  module Parsers
6
6
  # Git2 parser - preferred Git parser (more tolerant and faster)
7
7
  #
8
- # Input: git log --all --numstat --date=short --pretty=format:'--%h--%ad--%aN' --no-renames --after=YYYY-MM-DD
8
+ # Supports two formats:
9
9
  #
10
- # Sample format:
11
- # --586b4eb--2015-06-15--Adam Tornhill
10
+ # Standard format (without parent info):
11
+ # git log --all --numstat --date=short --pretty=format:'--%h--%ad--%aN--%s' --no-renames
12
+ #
13
+ # Format with parent hashes (for --group-by-merge / PR-level coupling):
14
+ # git log --all --numstat --date=short --pretty=format:'--%h--%p--%ad--%aN--%s' --no-renames
15
+ # This format is generated by the "pr-coupling" preset.
16
+ #
17
+ # Sample standard format:
18
+ # --586b4eb--2015-06-15--Adam Tornhill--Add new feature
12
19
  # 35 0 src/code_maat/mining/vcs.clj
13
- # 2 1 test/file.rb
14
20
  #
15
- # --abc123--2015-06-16--Jane Doe
21
+ # Sample parent-hash format (merge commit has multiple parent hashes):
22
+ # --abc123--def456 ghi789--2015-06-16--Jane Doe--Merge pull request #42
16
23
  # 10 5 lib/example.rb
17
24
  class Git2Parser < BaseParser
18
- COMMIT_SEPARATOR = /^--([a-z0-9]+)--(\d{4}-\d{2}-\d{2})--([^\r\n]+)$/
25
+ # Format with parent hashes: --hash--parent1 parent2--date--author--message
26
+ COMMIT_WITH_PARENTS = /^--([a-z0-9]+)--([a-z0-9 ]*)--(\d{4}-\d{2}-\d{2})--([^\r\n]+?)--([^\r\n]*)$/
27
+ # Standard format: --hash--date--author--message
28
+ COMMIT_SEPARATOR = /^--([a-z0-9]+)--(\d{4}-\d{2}-\d{2})--([^\r\n]+?)--([^\r\n]*)$/
19
29
  CHANGE_PATTERN = /^(-|\d+)[\t ]{1,10}(-|\d+)[\t ]{1,10}([^\r\n]*)$/
20
30
 
21
31
  protected
@@ -28,11 +38,22 @@ module RubyMaat
28
38
  line.strip!
29
39
  next if line.empty?
30
40
 
31
- if (commit_match = line.match(COMMIT_SEPARATOR))
41
+ if (commit_match = line.match(COMMIT_WITH_PARENTS))
42
+ parents_str = commit_match[2].strip
43
+ current_commit = {
44
+ revision: commit_match[1],
45
+ parent_revisions: parents_str.empty? ? [] : parents_str.split,
46
+ date: parse_date(commit_match[3]),
47
+ author: commit_match[4].strip,
48
+ message: commit_match[5].strip
49
+ }
50
+ elsif (commit_match = line.match(COMMIT_SEPARATOR))
32
51
  current_commit = {
33
52
  revision: commit_match[1],
53
+ parent_revisions: nil,
34
54
  date: parse_date(commit_match[2]),
35
- author: commit_match[3].strip
55
+ author: commit_match[3].strip,
56
+ message: commit_match[4].strip
36
57
  }
37
58
  elsif current_commit && (change_match = line.match(CHANGE_PATTERN))
38
59
  added = clean_numstat(change_match[1])
@@ -47,8 +68,10 @@ module RubyMaat
47
68
  author: current_commit[:author],
48
69
  date: current_commit[:date],
49
70
  revision: current_commit[:revision],
71
+ message: current_commit[:message],
50
72
  loc_added: added,
51
- loc_deleted: deleted
73
+ loc_deleted: deleted,
74
+ parent_revisions: current_commit[:parent_revisions]
52
75
  )
53
76
  end
54
77
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyMaat
4
- VERSION = "1.2.0"
4
+ VERSION = "1.3.4"
5
5
  end
data/lib/ruby_maat.rb CHANGED
@@ -17,6 +17,7 @@ require_relative "ruby_maat/parsers/tfs_parser"
17
17
  require_relative "ruby_maat/groupers/layer_grouper"
18
18
  require_relative "ruby_maat/groupers/time_grouper"
19
19
  require_relative "ruby_maat/groupers/team_mapper"
20
+ require_relative "ruby_maat/groupers/merge_commit_grouper"
20
21
 
21
22
  # Analysis modules (load before app.rb since app references them)
22
23
  require_relative "ruby_maat/analysis/base_analysis"
@@ -1,8 +1,57 @@
1
1
  {
2
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
2
3
  "packages": {
3
4
  ".": {
4
5
  "release-type": "ruby",
5
- "version-file": "lib/ruby_maat/version.rb"
6
+ "package-name": "ruby-maat",
7
+ "version-file": "lib/ruby_maat/version.rb",
8
+ "changelog-sections": [
9
+ {
10
+ "type": "feat",
11
+ "section": "Features",
12
+ "hidden": false
13
+ },
14
+ {
15
+ "type": "fix",
16
+ "section": "Bug Fixes",
17
+ "hidden": false
18
+ },
19
+ {
20
+ "type": "refactor",
21
+ "section": "Improvements",
22
+ "hidden": false
23
+ },
24
+ {
25
+ "type": "chore",
26
+ "section": "Maintenance",
27
+ "hidden": true
28
+ },
29
+ {
30
+ "type": "docs",
31
+ "section": "Documentation",
32
+ "hidden": false
33
+ },
34
+ {
35
+ "type": "test",
36
+ "section": "Testing",
37
+ "hidden": true
38
+ },
39
+ {
40
+ "type": "ci",
41
+ "section": "CI/CD",
42
+ "hidden": true
43
+ },
44
+ {
45
+ "type": "deps",
46
+ "section": "Dependencies",
47
+ "hidden": false
48
+ },
49
+ {
50
+ "type": "BREAKING CHANGE",
51
+ "section": "Breaking Changes",
52
+ "hidden": false
53
+ }
54
+ ]
6
55
  }
7
56
  }
8
57
  }