git 4.0.2 → 4.0.5

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: edf8c0ca68f2c4a5e2ecd4a72cfc49bf30edf8406bd7016ebd8e96e8bb016065
4
- data.tar.gz: 76c74e062482a50b780c3af13767e5bbd9d361f7fe4aad44c593d11de53a3acf
3
+ metadata.gz: aa18499d23585a949951d7e9cec7ffcda07eb1085deb3f43bb1396da678b7ba2
4
+ data.tar.gz: aa41f2fbc6558e54549ad49381492f0ec952d31b56256f1cb72f0e845df3eb55
5
5
  SHA512:
6
- metadata.gz: 49d11d3ae7d6a26365c594381f94d426130fa3d62c819d575b1b8868fa9af3f83b42ba81b0a9404e3aeedc422962f13d6f10203bdfd2074e9e9fbc36c1fc3422
7
- data.tar.gz: bf6e926d267120715fbfef1d2008dc3f4951130cdfe6266203caceb9fe63bd55c02282b0798a39c3917ea927af6215da805253eb85210a35a531bc066e62ed78
6
+ metadata.gz: 2d9bffd83ac45d0fc88b5f9f83ca8bd06b447989f74c68b279ea7ad8b35618ec059709d71f391ad1ee75ff3bbc2b40e1eabe7718446b588c68bb77fab4d5e10d
7
+ data.tar.gz: ba02db38b890b206c68bf8f7b09f3d13758beddd057977ebaf9b0259f20bcc4125f124713c15a230c7822a8c521801e9dfec4181582b58ac82ca873563721798
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "4.0.2"
2
+ ".": "4.0.5"
3
3
  }
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2025-07-06 21:08:14 UTC using RuboCop version 1.77.0.
3
+ # on 2025-08-17 04:52:22 UTC using RuboCop version 1.79.2.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -9,4 +9,4 @@
9
9
  # Offense count: 2
10
10
  # Configuration parameters: CountComments, CountAsOne.
11
11
  Metrics/ClassLength:
12
- Max: 1032
12
+ Max: 1039
data/CHANGELOG.md CHANGED
@@ -5,6 +5,54 @@
5
5
 
6
6
  # Change Log
7
7
 
8
+ ## [4.0.5](https://github.com/ruby-git/ruby-git/compare/v4.0.4...v4.0.5) (2025-08-20)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Properly parse UTF-8(multibyte) file paths in git output ([8e6a11e](https://github.com/ruby-git/ruby-git/commit/8e6a11e5f3749a25e1d56ffbc0332a98846a395b))
14
+
15
+
16
+ ### Other Changes
17
+
18
+ * Document and announce the proposed architectural redesign ([e27255a](https://github.com/ruby-git/ruby-git/commit/e27255ad6d06fbf84c1bc32efc2e0f8eb48290a7))
19
+ * Minor change to the architecture redesign document ([b4634b5](https://github.com/ruby-git/ruby-git/commit/b4634b596d71bd59857b7723d20f393eb5024faa))
20
+ * Rearrange README so that Summary is at the top ([3d2c473](https://github.com/ruby-git/ruby-git/commit/3d2c47388b9d4dc730964fc316afb2fc0fb7c90a))
21
+ * Update ClassLength max in .rubocop_todo.yml for CI passing ([4430478](https://github.com/ruby-git/ruby-git/commit/4430478e087b33839d1a3b307a418b806197f279))
22
+
23
+ ## [4.0.4](https://github.com/ruby-git/ruby-git/compare/v4.0.3...v4.0.4) (2025-07-09)
24
+
25
+
26
+ ### Bug Fixes
27
+
28
+ * Remove deprecation from Git::Path ([ab1e207](https://github.com/ruby-git/ruby-git/commit/ab1e20773c6a300b546841f79adf8dd6e707250e))
29
+ * Remove deprecation from Git::Stash ([9da1e91](https://github.com/ruby-git/ruby-git/commit/9da1e9112e38c0e964dd2bc638bda7aebe45ba91))
30
+
31
+
32
+ ### Other Changes
33
+
34
+ * Add tests for Git::Base#set_index including deprecation ([e6ccb11](https://github.com/ruby-git/ruby-git/commit/e6ccb11830a794f12235e47032235c3284c84cf6))
35
+ * Add tests for Git::Base#set_working including deprecation ([ee11137](https://github.com/ruby-git/ruby-git/commit/ee1113706a8e34e9631f0e2d89bd602bca87f05f))
36
+ * Add tests to verify Git::Object.new creates the right type of object ([ab17621](https://github.com/ruby-git/ruby-git/commit/ab17621d65a02b70844fde3127c9cbb219add7f5))
37
+ * Verify deprecated Git::Log methods emit a deprecation warning ([abb0efb](https://github.com/ruby-git/ruby-git/commit/abb0efbdb3b6bb49352d097b1fece708477d4362))
38
+
39
+ ## [4.0.3](https://github.com/ruby-git/ruby-git/compare/v4.0.2...v4.0.3) (2025-07-08)
40
+
41
+
42
+ ### Bug Fixes
43
+
44
+ * Correct the deprecation horizon for Git deprecations ([b7b7f38](https://github.com/ruby-git/ruby-git/commit/b7b7f38ccb88ba719e8ea7cb3fea14474b19a00c))
45
+ * Fix Rubocop Layout/EmptyLinesAroundClassBody offense ([1de27da](https://github.com/ruby-git/ruby-git/commit/1de27daabed18b47a42539fe69b735d8ee90cbbb))
46
+ * Internally create a Stash with non-deprecated initializer args ([8b9b9e2](https://github.com/ruby-git/ruby-git/commit/8b9b9e2f3b3fa525973785f642331317ade35936))
47
+ * Report correct line number in deprecation warnings ([cca0deb](https://github.com/ruby-git/ruby-git/commit/cca0debb4166c809af76f9dc586e4fd06e142d44))
48
+ * Un-deprecate Git::Diff methods ([761b6ff](https://github.com/ruby-git/ruby-git/commit/761b6ffcd363f4329a9cbafbf1379513a19ff174))
49
+
50
+
51
+ ### Other Changes
52
+
53
+ * Make tests that emit a deprecation warning fail ([7e211d7](https://github.com/ruby-git/ruby-git/commit/7e211d7b2b7cc8d9da4a860361bef52280a5e73b))
54
+ * Update all tests to not use deprecated features ([33ab0e2](https://github.com/ruby-git/ruby-git/commit/33ab0e255e229e22d84b14a4d4f5fb829c1fe37c))
55
+
8
56
  ## [4.0.2](https://github.com/ruby-git/ruby-git/compare/v4.0.1...v4.0.2) (2025-07-08)
9
57
 
10
58
 
data/README.md CHANGED
@@ -11,9 +11,6 @@
11
11
  [![Build Status](https://github.com/ruby-git/ruby-git/workflows/CI/badge.svg?branch=main)](https://github.com/ruby-git/ruby-git/actions?query=workflow%3ACI)
12
12
  [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)
13
13
 
14
- - [📢 We Now Use RuboCop 📢](#-we-now-use-rubocop-)
15
- - [📢 Default Branch Rename 📢](#-default-branch-rename-)
16
- - [📢 We've Switched to Conventional Commits 📢](#-weve-switched-to-conventional-commits-)
17
14
  - [Summary](#summary)
18
15
  - [Install](#install)
19
16
  - [Major Objects](#major-objects)
@@ -23,55 +20,11 @@
23
20
  - [Examples](#examples)
24
21
  - [Ruby version support policy](#ruby-version-support-policy)
25
22
  - [License](#license)
26
-
27
- ## 📢 We Now Use RuboCop 📢
28
-
29
- To improve code consistency and maintainability, the `ruby-git` project has now
30
- adopted [RuboCop](https://rubocop.org/) as our static code analyzer and formatter.
31
-
32
- This integration is a key part of our ongoing commitment to making `ruby-git` a
33
- high-quality, stable, and easy-to-contribute-to project. All new contributions will
34
- be expected to adhere to the style guidelines enforced by our RuboCop configuration.
35
-
36
- RuboCop can be run from the project's Rakefile:
37
-
38
- ```shell
39
- rake rubocop
40
- ```
41
-
42
- RuboCop is also run as part of the default rake task (by running `rake`) that is run
43
- in our Continuous Integration workflow.
44
-
45
- Going forward, any PRs that have any Robocop offenses will not be merged. In
46
- certain rare cases, it might be acceptable to disable a RuboCop check for the most
47
- limited scope possible.
48
-
49
- If you have a problem fixing a RuboCop offense, don't be afraid to ask a contributor.
50
-
51
- ## 📢 Default Branch Rename 📢
52
-
53
- On June 6th, 2025, the default branch was renamed from 'master' to 'main'.
54
-
55
- Instructions for renaming your local or forked branch to match can be found in the
56
- gist [Default Branch Name
57
- Change](https://gist.github.com/jcouball/580a10e395f7fdfaaa4297bbe816cc7d).
58
-
59
- ## 📢 We've Switched to Conventional Commits 📢
60
-
61
- To enhance our development workflow, enable automated changelog generation, and pave
62
- the way for Continuous Delivery, the `ruby-git` project has adopted the [Conventional
63
- Commits standard](https://www.conventionalcommits.org/en/v1.0.0/) for all commit
64
- messages.
65
-
66
- Going forward, all commits to this repository **MUST** adhere to the Conventional
67
- Commits standard. Commits not adhering to this standard will cause the CI build to
68
- fail. PRs will not be merged if they include non-conventional commits.
69
-
70
- A git pre-commit hook may be installed to validate your conventional commit messages
71
- before pushing them to GitHub by running `bin/setup` in the project root.
72
-
73
- Read more about this change in the [Commit Message Guidelines section of
74
- CONTRIBUTING.md](CONTRIBUTING.md#commit-message-guidelines)
23
+ - [📢 Project Announcements 📢](#-project-announcements-)
24
+ - [2025-07-09: Architectural Redesign](#2025-07-09-architectural-redesign)
25
+ - [2025-07-07: We Now Use RuboCop](#2025-07-07-we-now-use-rubocop)
26
+ - [2025-06-06: Default Branch Rename](#2025-06-06-default-branch-rename)
27
+ - [2025-05-15: We've Switched to Conventional Commits](#2025-05-15-weve-switched-to-conventional-commits)
75
28
 
76
29
  ## Summary
77
30
 
@@ -574,9 +527,9 @@ end
574
527
 
575
528
  This gem will be expected to function correctly on:
576
529
 
577
- * All non-EOL versions of the MRI Ruby on Mac, Linux, and Windows
578
- * The latest version of JRuby on Linux
579
- * The latest version of Truffle Ruby on Linus
530
+ - All non-EOL versions of the MRI Ruby on Mac, Linux, and Windows
531
+ - The latest version of JRuby on Linux
532
+ - The latest version of Truffle Ruby on Linus
580
533
 
581
534
  It is this project's intent to support the latest version of JRuby on Windows
582
535
  once the following JRuby bug is fixed:
@@ -587,3 +540,87 @@ jruby/jruby#7515
587
540
 
588
541
  Licensed under MIT License Copyright (c) 2008 Scott Chacon. See LICENSE for further
589
542
  details.
543
+
544
+ ## 📢 Project Announcements 📢
545
+
546
+ ### 2025-07-09: Architectural Redesign
547
+
548
+ The git gem is undergoing a significant architectural redesign for the upcoming
549
+ v5.0.0 release. The current architecture has several design challenges that make it
550
+ difficult to maintain and evolve. This redesign aims to address these issues by
551
+ introducing a clearer, more robust, and more testable structure.
552
+
553
+ We have prepared detailed documents outlining the analysis of the current
554
+ architecture and the proposed changes. We encourage our community and contributors to
555
+ review them:
556
+
557
+ 1. [Analysis of the Current Architecture](redesign/1_architecture_existing.md): A
558
+ breakdown of the existing design and its challenges.
559
+ 2. [The Proposed Redesign](redesign/2_architecture_redesign.md): An overview of the
560
+ new three-layered architecture.
561
+ 3. [Implementation Plan](redesign/3_architecture_implementation.md): The step-by-step
562
+ plan for implementing the redesign.
563
+
564
+ Your feedback is welcome! Please feel free to open an issue to discuss the proposed
565
+ changes.
566
+
567
+ > **DON'T PANIC!**
568
+ >
569
+ > While this is a major internal refactoring, our goal is to keep the primary public
570
+ API on the main repository object as stable as possible. Most users who rely on
571
+ documented methods like `g.commit`, `g.add`, and `g.status` should find the
572
+ transition to v5.0.0 straightforward.
573
+ >
574
+ > The breaking changes will primarily affect users who have been relying on the
575
+ internal g.lib accessor, which will be removed as part of this cleanup. For more
576
+ details, please see the "Impact on Users" section in [the redesign
577
+ document](redesign/2_architecture_redesign.md).
578
+
579
+ ### 2025-07-07: We Now Use RuboCop
580
+
581
+ To improve code consistency and maintainability, the `ruby-git` project has now
582
+ adopted [RuboCop](https://rubocop.org/) as our static code analyzer and formatter.
583
+
584
+ This integration is a key part of our ongoing commitment to making `ruby-git` a
585
+ high-quality, stable, and easy-to-contribute-to project. All new contributions will
586
+ be expected to adhere to the style guidelines enforced by our RuboCop configuration.
587
+
588
+ RuboCop can be run from the project's Rakefile:
589
+
590
+ ```shell
591
+ rake rubocop
592
+ ```
593
+
594
+ RuboCop is also run as part of the default rake task (by running `rake`) that is run
595
+ in our Continuous Integration workflow.
596
+
597
+ Going forward, any PRs that have any Robocop offenses will not be merged. In
598
+ certain rare cases, it might be acceptable to disable a RuboCop check for the most
599
+ limited scope possible.
600
+
601
+ If you have a problem fixing a RuboCop offense, don't be afraid to ask a contributor.
602
+
603
+ ### 2025-06-06: Default Branch Rename
604
+
605
+ On June 6th, 2025, the default branch was renamed from 'master' to 'main'.
606
+
607
+ Instructions for renaming your local or forked branch to match can be found in the
608
+ gist [Default Branch Name
609
+ Change](https://gist.github.com/jcouball/580a10e395f7fdfaaa4297bbe816cc7d).
610
+
611
+ ### 2025-05-15: We've Switched to Conventional Commits
612
+
613
+ To enhance our development workflow, enable automated changelog generation, and pave
614
+ the way for Continuous Delivery, the `ruby-git` project has adopted the [Conventional
615
+ Commits standard](https://www.conventionalcommits.org/en/v1.0.0/) for all commit
616
+ messages.
617
+
618
+ Going forward, all commits to this repository **MUST** adhere to the Conventional
619
+ Commits standard. Commits not adhering to this standard will cause the CI build to
620
+ fail. PRs will not be merged if they include non-conventional commits.
621
+
622
+ A git pre-commit hook may be installed to validate your conventional commit messages
623
+ before pushing them to GitHub by running `bin/setup` in the project root.
624
+
625
+ Read more about this change in the [Commit Message Guidelines section of
626
+ CONTRIBUTING.md](CONTRIBUTING.md#commit-message-guidelines)
data/lib/git/diff.rb CHANGED
@@ -38,38 +38,31 @@ module Git
38
38
  @full_diff_files.map { |file| file[1] }.each(&)
39
39
  end
40
40
 
41
+ def size
42
+ stats_provider.total[:files]
43
+ end
44
+
41
45
  #
42
46
  # DEPRECATED METHODS
43
47
  #
44
48
 
45
49
  def name_status
46
- Git::Deprecation.warn('Git::Diff#name_status is deprecated. Use Git::Base#diff_path_status instead.')
47
50
  path_status_provider.to_h
48
51
  end
49
52
 
50
- def size
51
- Git::Deprecation.warn('Git::Diff#size is deprecated. Use Git::Base#diff_stats(...).total[:files] instead.')
52
- stats_provider.total[:files]
53
- end
54
-
55
53
  def lines
56
- Git::Deprecation.warn('Git::Diff#lines is deprecated. Use Git::Base#diff_stats(...).lines instead.')
57
54
  stats_provider.lines
58
55
  end
59
56
 
60
57
  def deletions
61
- Git::Deprecation.warn('Git::Diff#deletions is deprecated. Use Git::Base#diff_stats(...).deletions instead.')
62
58
  stats_provider.deletions
63
59
  end
64
60
 
65
61
  def insertions
66
- Git::Deprecation.warn('Git::Diff#insertions is deprecated. Use Git::Base#diff_stats(...).insertions instead.')
67
62
  stats_provider.insertions
68
63
  end
69
64
 
70
65
  def stats
71
- Git::Deprecation.warn('Git::Diff#stats is deprecated. Use Git::Base#diff_stats instead.')
72
- # CORRECTED: Re-create the original hash structure for backward compatibility
73
66
  {
74
67
  files: stats_provider.files,
75
68
  total: stats_provider.total
data/lib/git/lib.rb CHANGED
@@ -643,7 +643,7 @@ module Git
643
643
  args << opts[:path] if opts[:path]
644
644
 
645
645
  command_lines('ls-tree', *args).each do |line|
646
- (info, filenm) = line.split("\t")
646
+ (info, filenm) = split_status_line(line)
647
647
  (mode, type, sha) = info.split
648
648
  data[type][filenm] = { mode: mode, sha: sha }
649
649
  end
@@ -905,9 +905,9 @@ module Git
905
905
  location ||= '.'
906
906
  {}.tap do |files|
907
907
  command_lines('ls-files', '--stage', location).each do |line|
908
- (info, file) = line.split("\t")
908
+ (info, file) = split_status_line(line)
909
909
  (mode, sha, stage) = info.split
910
- files[unescape_quoted_path(file)] = {
910
+ files[file] = {
911
911
  path: file, mode_index: mode, sha_index: sha, stage: stage
912
912
  }
913
913
  end
@@ -956,7 +956,9 @@ module Git
956
956
  end
957
957
 
958
958
  def untracked_files
959
- command_lines('ls-files', '--others', '--exclude-standard', chdir: @git_work_dir)
959
+ command_lines('ls-files', '--others', '--exclude-standard', chdir: @git_work_dir).map do |f|
960
+ unescape_quoted_path(f)
961
+ end
960
962
  end
961
963
 
962
964
  def config_remote(name)
@@ -1602,7 +1604,7 @@ module Git
1602
1604
 
1603
1605
  def parse_diff_path_status(args)
1604
1606
  command_lines('diff', *args).each_with_object({}) do |line, memo|
1605
- status, path = line.split("\t")
1607
+ status, path = split_status_line(line)
1606
1608
  memo[path] = status
1607
1609
  end
1608
1610
  end
@@ -1727,7 +1729,7 @@ module Git
1727
1729
 
1728
1730
  def parse_stat_lines(lines)
1729
1731
  lines.map do |line|
1730
- insertions_s, deletions_s, filename = line.split("\t")
1732
+ insertions_s, deletions_s, filename = split_status_line(line)
1731
1733
  {
1732
1734
  filename: filename,
1733
1735
  insertions: insertions_s.to_i,
@@ -1736,6 +1738,12 @@ module Git
1736
1738
  end
1737
1739
  end
1738
1740
 
1741
+ def split_status_line(line)
1742
+ parts = line.split("\t")
1743
+ parts[-1] = unescape_quoted_path(parts[-1]) if parts.any?
1744
+ parts
1745
+ end
1746
+
1739
1747
  def build_final_stats_hash(file_stats)
1740
1748
  {
1741
1749
  total: build_total_stats(file_stats),
@@ -1965,7 +1973,7 @@ module Git
1965
1973
  # update index before diffing to avoid spurious diffs
1966
1974
  command('status')
1967
1975
  command_lines(diff_command, *opts).each_with_object({}) do |line, memo|
1968
- info, file = line.split("\t")
1976
+ info, file = split_status_line(line)
1969
1977
  mode_src, mode_dest, sha_src, sha_dest, type = info.split
1970
1978
 
1971
1979
  memo[file] = {
data/lib/git/log.rb CHANGED
@@ -90,28 +90,56 @@ module Git
90
90
 
91
91
  # @deprecated Use {#execute} and call `each` on the result.
92
92
  def each(&)
93
- deprecate_and_run
93
+ Git::Deprecation.warn(
94
+ 'Calling Git::Log#each is deprecated. Call #execute and then #each on the result object.'
95
+ )
96
+ run_log_if_dirty
94
97
  @commits.each(&)
95
98
  end
96
99
 
97
100
  # @deprecated Use {#execute} and call `size` on the result.
98
101
  def size
99
- deprecate_and_run
102
+ Git::Deprecation.warn(
103
+ 'Calling Git::Log#size is deprecated. Call #execute and then #size on the result object.'
104
+ )
105
+ run_log_if_dirty
100
106
  @commits&.size
101
107
  end
102
108
 
103
109
  # @deprecated Use {#execute} and call `to_s` on the result.
104
110
  def to_s
105
- deprecate_and_run
111
+ Git::Deprecation.warn(
112
+ 'Calling Git::Log#to_s is deprecated. Call #execute and then #to_s on the result object.'
113
+ )
114
+ run_log_if_dirty
106
115
  @commits&.map(&:to_s)&.join("\n")
107
116
  end
108
117
 
109
118
  # @deprecated Use {#execute} and call the method on the result.
110
- %i[first last []].each do |method_name|
111
- define_method(method_name) do |*args|
112
- deprecate_and_run
113
- @commits&.public_send(method_name, *args)
114
- end
119
+ def first
120
+ Git::Deprecation.warn(
121
+ 'Calling Git::Log#first is deprecated. Call #execute and then #first on the result object.'
122
+ )
123
+ run_log_if_dirty
124
+ @commits&.first
125
+ end
126
+
127
+ # @deprecated Use {#execute} and call the method on the result.
128
+ def last
129
+ Git::Deprecation.warn(
130
+ 'Calling Git::Log#last is deprecated. Call #execute and then #last on the result object.'
131
+ )
132
+ run_log_if_dirty
133
+ @commits&.last
134
+ end
135
+
136
+ # @deprecated Use {#execute} and call the method on the result.
137
+ def [](index)
138
+ Git::Deprecation.warn(
139
+ 'Calling Git::Log#[] is deprecated. Call #execute and then #[] on the result object.'
140
+ )
141
+ run_log_if_dirty
142
+ @commits&.[](index)
115
143
  end
116
144
 
117
145
  # @!endgroup
@@ -131,13 +159,5 @@ module Git
131
159
  @commits = log_data.map { |c| Git::Object::Commit.new(@base, c['sha'], c) }
132
160
  @dirty = false
133
161
  end
134
-
135
- def deprecate_and_run(method = caller_locations(1, 1)[0].label)
136
- Git::Deprecation.warn(
137
- "Calling Git::Log##{method} is deprecated. " \
138
- "Call #execute and then ##{method} on the result object."
139
- )
140
- run_log_if_dirty
141
- end
142
162
  end
143
163
  end
data/lib/git/path.rb CHANGED
@@ -9,17 +9,7 @@ module Git
9
9
  class Path
10
10
  attr_accessor :path
11
11
 
12
- def initialize(path, check_path = nil, must_exist: nil)
13
- unless check_path.nil?
14
- Git::Deprecation.warn(
15
- 'The "check_path" argument is deprecated and ' \
16
- 'will be removed in a future version. Use "must_exist:" instead.'
17
- )
18
- end
19
-
20
- # default is true
21
- must_exist = must_exist.nil? && check_path.nil? ? true : must_exist || check_path
22
-
12
+ def initialize(path, must_exist: true)
23
13
  path = File.expand_path(path)
24
14
 
25
15
  raise ArgumentError, 'path does not exist', [path] if must_exist && !File.exist?(path)
data/lib/git/stash.rb CHANGED
@@ -3,16 +3,7 @@
3
3
  module Git
4
4
  # A stash in a Git repository
5
5
  class Stash
6
- def initialize(base, message, existing = nil, save: nil)
7
- unless existing.nil?
8
- Git::Deprecation.warn(
9
- 'The "existing" argument is deprecated and will be removed in a future version. Use "save:" instead.'
10
- )
11
- end
12
-
13
- # default is false
14
- save = existing.nil? && save.nil? ? false : save | existing
15
-
6
+ def initialize(base, message, save: false)
16
7
  @base = base
17
8
  @message = message
18
9
  self.save unless save
data/lib/git/stashes.rb CHANGED
@@ -12,7 +12,7 @@ module Git
12
12
 
13
13
  @base.lib.stashes_all.each do |indexed_message|
14
14
  _index, message = indexed_message
15
- @stashes.unshift(Git::Stash.new(@base, message, true))
15
+ @stashes.unshift(Git::Stash.new(@base, message, save: true))
16
16
  end
17
17
  end
18
18
 
data/lib/git/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  module Git
4
4
  # The current gem version
5
5
  # @return [String] the current gem version.
6
- VERSION = '4.0.2'
6
+ VERSION = '4.0.5'
7
7
  end
data/lib/git.rb CHANGED
@@ -4,7 +4,7 @@ require 'active_support'
4
4
  require 'active_support/deprecation'
5
5
 
6
6
  module Git
7
- Deprecation = ActiveSupport::Deprecation.new('3.0', 'Git')
7
+ Deprecation = ActiveSupport::Deprecation.new('5.0.0', 'Git')
8
8
  end
9
9
 
10
10
  require 'git/author'
@@ -0,0 +1,66 @@
1
+ # Analysis of the Current Git Gem Architecture and Its Challenges
2
+
3
+ This document provides an in-depth look at the current architecture of the `git` gem, outlining its primary components and the design challenges that have emerged over time. Understanding these challenges is the key motivation for the proposed architectural redesign.
4
+
5
+ - [1. Overview of the Current Architecture](#1-overview-of-the-current-architecture)
6
+ - [2. Key Architectural Challenges](#2-key-architectural-challenges)
7
+ - [A. Unclear Separation of Concerns](#a-unclear-separation-of-concerns)
8
+ - [B. Circular Dependency](#b-circular-dependency)
9
+ - [C. Undefined Public API Boundary](#c-undefined-public-api-boundary)
10
+ - [D. Slow and Brittle Test Suite](#d-slow-and-brittle-test-suite)
11
+
12
+ ## 1. Overview of the Current Architecture
13
+
14
+ The gem's current design is centered around three main classes: `Git`, `Git::Base`, and `Git::Lib`.
15
+
16
+ - **`Git` (Top-Level Module)**: This module serves as the primary public entry point for creating repository objects. It contains class-level factory methods like `Git.open`, `Git.clone`, and `Git.init`. It also provides an interface for accessing global git configuration settings.
17
+
18
+ **`Git::Base`**: This is the main object that users interact with after creating or opening a repository. It holds the high-level public API for most git operations (e.g., `g.commit`, `g.add`, `g.status`). It is responsible for managing the repository's state, such as the paths to the working directory and the `.git` directory.
19
+
20
+ **`Git::Lib`**: This class is intended to be the low-level wrapper around the `git` command-line tool. It contains the methods that build the specific command-line arguments and execute the `git` binary. In practice, it also contains a significant amount of logic for parsing the output of these commands.
21
+
22
+ ## 2. Key Architectural Challenges
23
+
24
+ While this structure has been functional, several significant design challenges make the codebase difficult to maintain, test, and evolve.
25
+
26
+ ### A. Unclear Separation of Concerns
27
+
28
+ The responsibilities between Git::Base and Git::Lib are "muddy" and overlap significantly.
29
+
30
+ - `Git::Base` sometimes contains logic that feels like it should be lower-level.
31
+
32
+ - `Git::Lib`, which should ideally only be concerned with command execution, is filled with high-level logic for parsing command output into specific Ruby objects (e.g., parsing log output, diff stats, and branch lists).
33
+
34
+ This blending of responsibilities makes it hard to determine where a specific piece of logic should reside, leading to an inconsistent and confusing internal structure.
35
+
36
+ ### B. Circular Dependency
37
+
38
+ This is the most critical architectural flaw in the current design.
39
+
40
+ - A `Git::Base` instance is created.
41
+
42
+ - The first time a command is run, `Git::Base` lazily initializes a `Git::Lib` instance via its `.lib` accessor method.
43
+
44
+ - The `Git::Lib` constructor is passed the `Git::Base` instance (`self`) so that it can read the repository's path configuration back from the object that is creating it.
45
+
46
+ This creates a tight, circular coupling: `Git::Base` depends on `Git::Lib` to execute commands, but `Git::Lib` depends on `Git::Base` for its own configuration. This pattern makes the classes difficula to instantiate or test in isolation and creates a fragile system where changes in one class can have unexpected side effects in the other.
47
+
48
+ ### C. Undefined Public API Boundary
49
+
50
+ The gem lacks a formally defined public interface. Because `Git::Base` exposes its internal `Git::Lib` instance via the public `g.lib` accessor, many users have come to rely on `Git::Lib` and its methods as if they were part of the public API.
51
+
52
+ This has two negative consequences:
53
+
54
+ 1. It prevents the gem's maintainers from refactoring or changing the internal implementation of `Git::Lib` without causing breaking changes for users.
55
+
56
+ 2. It exposes complex, internal methods to users, creating a confusing and inconsistent user experience.
57
+
58
+ ### D. Slow and Brittle Test Suite
59
+
60
+ The current testing strategy, built on `TestUnit`, suffers from two major issues:
61
+
62
+ - **Over-reliance on Fixtures**: Most tests depend on having a complete, physical git repository on the filesystem to run against. Managing these fixtures is cumbersome.
63
+
64
+ - **Excessive Shelling Out**: Because the logic for command execution and output parsing are tightly coupled, nearly every test must shell out to the actual `git` command-line tool.
65
+
66
+ This makes the test suite extremely slow, especially on non-UNIX platforms like Windows where process creation is more expensive. The slow feedback loop discourages frequent testing and makes development more difficult. The brittleness of filesystem-dependent tests also leads to flickering or unreliable test runs.
@@ -0,0 +1,130 @@
1
+ # Proposed Redesigned Architecture for the Git Gem
2
+
3
+ This document outlines a proposal for a major redesign of the git gem, targeted for version 5.0.0. The goal of this redesign is to modernize the gem's architecture, making it more robust, maintainable, testable, and easier for new contributors to understand.
4
+
5
+ - [1. Motivation](#1-motivation)
6
+ - [2. The New Architecture: A Three-Layered Approach](#2-the-new-architecture-a-three-layered-approach)
7
+ - [3. Key Design Principles](#3-key-design-principles)
8
+ - [A. Clear Public vs. Private API](#a-clear-public-vs-private-api)
9
+ - [B. Dependency Injection](#b-dependency-injection)
10
+ - [C. Immutable Return Values](#c-immutable-return-values)
11
+ - [D. Clear Naming for Path Objects](#d-clear-naming-for-path-objects)
12
+ - [4. Testing Strategy Overhaul](#4-testing-strategy-overhaul)
13
+ - [5. Impact on Users: Breaking Changes for v5.0.0](#5-impact-on-users-breaking-changes-for-v500)
14
+
15
+ ## 1. Motivation
16
+
17
+ The current architecture, while functional, has several design issues that have accrued over time, making it difficult to extend and maintain.
18
+
19
+ - **Unclear Separation of Concerns**: The responsibilities of the `Git`, `Git::Base`, and `Git::Lib` classes are "muddy." `Git::Base` acts as both a high-level API and a factory, while `Git::Lib` contains a mix of low-level command execution and high-level output parsing.
20
+
21
+ - **Circular Dependency**: A key architectural flaw is the circular dependency between `Git::Base` and `Git::Lib`. `Git::Base` creates and depends on `Git::Lib`, but `Git::Lib`'s constructor requires an instance of Git::Base to access configuration. This tight coupling makes the classes difficult to reason about and test in isolation.
22
+
23
+ - **Undefined Public API**: The boundary between the gem's public API and its internal implementation is not clearly defined. This has led some users to rely on internal classes like `Git::Lib`, making it difficult to refactor the internals without introducing breaking changes.
24
+
25
+ - **Slow and Brittle Test Suite**: The current tests rely heavily on filesystem fixtures and shelling out to the git command line for almost every test case. This makes the test suite slow and difficult to maintain, especially on non-UNIX platforms.
26
+
27
+ ## 2. The New Architecture: A Three-Layered Approach
28
+
29
+ The new design is built on a clear separation of concerns, dividing responsibilities into three distinct layers: a Facade, an Execution Context, and Command Objects.
30
+
31
+ 1. The Facade Layer: Git::Repository
32
+
33
+ This is the primary public interface that users will interact with.
34
+
35
+ **Renaming**: `Git::Base` will be renamed to `Git::Repository`. This name is more descriptive and intuitive.
36
+
37
+ **Responsibility**: It will serve as a clean, high-level facade for all common git operations. Its methods will be simple, one-line calls that delegate the actual work to an appropriate command object.
38
+
39
+ **Scalability**: To prevent this class from growing too large, its methods will be organized into logical modules (e.g., `Git::Repository::Branching`, `Git::Repository::History`) which are then included in the main class. This keeps the core class definition small and the features well-organized. These categories will be inspired by (but not slavishly follow) the git command line reference in [this page](https://git-scm.com/docs).
40
+
41
+ 2. The Execution Layer: Git::ExecutionContext
42
+
43
+ This is the low-level, private engine for running commands.
44
+
45
+ **Renaming**: `Git::Lib` will be renamed to `Git::ExecutionContext`.
46
+
47
+ **Responsibility**: Its sole purpose is to execute raw git commands. It will manage the repository's environment (working directory, .git path, logger) and use the existing `Git::CommandLine` class to interact with the system's git binary. It will have no knowledge of any specific git command's arguments or output.
48
+
49
+ 3. The Logic Layer: Git::Commands
50
+
51
+ This is where all the command-specific implementation details will live.
52
+
53
+ **New Classes**: For each git operation, a new command class will be created within the `Git::Commands` namespace (e.g., `Git::Commands::Commit`, `Git::Commands::Diff`).
54
+
55
+ **Dual Responsibility**: Each command class will be responsible for:
56
+
57
+ 1. **Building Arguments**: Translating high-level Ruby options into the specific command-line flags and arguments that git expects.
58
+
59
+ 2. **Parsing Output**: Taking the raw string output from the ExecutionContext and converting it into rich, structured Ruby objects.
60
+
61
+ **Handling Complexity**: For commands with multiple behaviors (like git diff), we can use specialized subclasses (e.g., Git::Commands::Diff::NameStatus, Git::Commands::Diff::Stats) to keep each class focused on a single responsibility.
62
+
63
+ ## 3. Key Design Principles
64
+
65
+ The new architecture will be guided by the following modern design principles.
66
+
67
+ ### A. Clear Public vs. Private API
68
+
69
+ A primary goal of this redesign is to establish a crisp boundary between the public API and internal implementation details.
70
+
71
+ - **Public Interface**: The public API will consist of the `Git` module (for factories), the `Git::Repository` class, and the specialized data/query objects it returns (e.g., `Git::Log`, `Git::Status`, `Git::Object::Commit`).
72
+
73
+ - **Private Implementation**: All other components, including `Git::ExecutionContext` and all classes within the `Git::Commands` namespace, will be considered internal. They will be explicitly marked with the `@api private` YARD tag to discourage external use.
74
+
75
+ ### B. Dependency Injection
76
+
77
+ The circular dependency will be resolved by implementing a clear, one-way dependency flow.
78
+
79
+ 1. The factory methods (`Git.open`, `Git.clone`) will create and configure an instance of `Git::ExecutionContext`.
80
+
81
+ 2. This `ExecutionContext` instance will then be injected into the constructor of the `Git::Repository` object.
82
+
83
+ This decouples the `Repository` from its execution environment, making the system more modular and easier to test.
84
+
85
+ ### C. Immutable Return Values
86
+
87
+ To create a more predictable and robust API, methods will return structured, immutable data objects instead of raw strings or hashes.
88
+
89
+ This will be implemented using `Data.define` or simple, frozen `Struct`s.
90
+
91
+ For example, instead of returning a raw string, `repo.config('user.name')` will return a `Git::Config::Value` object containing the key, value, scope, and source path.
92
+
93
+ ### D. Clear Naming for Path Objects
94
+
95
+ To improve clarity, all classes that represent filesystem paths will be renamed to follow a consistent `...Path` suffix convention.
96
+
97
+ - `Git::WorkingDirectory` -> `Git::WorkingTreePath`
98
+
99
+ - `Git::Index` -> `Git::IndexPath`
100
+
101
+ - The old `Git::Repository` (representing the .git directory/file) -> `Git::RepositoryPath`
102
+
103
+ ## 4. Testing Strategy Overhaul
104
+
105
+ The test suite will be modernized to be faster, more reliable, and easier to work with.
106
+
107
+ - **Migration to RSpec**: The entire test suite will be migrated from TestUnit to RSpec to leverage its modern tooling and expressive DSL.
108
+
109
+ - **Layered Testing**: A three-layered testing strategy will be adopted:
110
+
111
+ 1. **Unit Tests**: The majority of tests will be fast, isolated unit tests for the `Command` classes, using mock `ExecutionContexts`.
112
+
113
+ 2. **Integration Tests**: A small number of integration tests will verify that `ExecutionContext` correctly interacts with the system's `git` binary.
114
+
115
+ 3. **Feature Tests**: A minimal set of high-level tests will ensure the public facade on `Git::Repository` works end-to-end.
116
+
117
+ - **Reduced Filesystem Dependency**: This new structure will dramatically reduce the suite's reliance on slow and brittle filesystem fixtures.
118
+
119
+ ## 5. Impact on Users: Breaking Changes for v5.0.0
120
+
121
+ This redesign is a significant undertaking and will be released as version 5.0.0. It includes several breaking changes that users will need to be aware of when upgrading.
122
+
123
+ - **`Git::Lib` is Removed**: Any code directly referencing `Git::Lib` will break.
124
+
125
+ - **g.lib Accessor is Removed**: The `.lib` accessor on repository objects will be removed.
126
+
127
+ - **Internal Methods Relocated**: Methods that were previously accessible via g.lib will now be private implementation details of the new command classes and will not be directly reachable.
128
+
129
+ Users should only rely on the newly defined public interface.
130
+
@@ -0,0 +1,138 @@
1
+ # Implementation Plan for Git Gem Redesign (v5.0.0)
2
+
3
+ This document outlines a step-by-step plan to implement the proposed architectural redesign. The plan is structured to be incremental, ensuring that the gem remains functional and passes its test suite after each major step. This approach minimizes risk and allows for a gradual, controlled migration to the new architecture.
4
+
5
+ - [Phase 1: Foundation and Scaffolding](#phase-1-foundation-and-scaffolding)
6
+ - [Phase 2: The Strangler Fig Pattern - Migrating Commands](#phase-2-the-strangler-fig-pattern---migrating-commands)
7
+ - [Phase 3: Refactoring the Public Interface](#phase-3-refactoring-the-public-interface)
8
+ - [Phase 4: Final Cleanup and Release Preparation](#phase-4-final-cleanup-and-release-preparation)
9
+
10
+ ## Phase 1: Foundation and Scaffolding
11
+
12
+ ***Goal**: Set up the new file structure and class names without altering existing logic. The gem will be fully functional after this phase.*
13
+
14
+ 1. **Create New Directory Structure**:
15
+
16
+ - Create the new directories that will house the refactored components:
17
+
18
+ - `lib/git/commands/`
19
+
20
+ - `lib/git/repository/` (for the facade modules)
21
+
22
+ 2. **Rename Path Classes**:
23
+
24
+ - Perform a project-wide, safe rename of the existing path-related classes. This is a low-risk mechanical change.
25
+
26
+ - `Git::WorkingDirectory` -> `Git::WorkingTreePath`
27
+
28
+ - `Git::Index` -> `Git::IndexPath`
29
+
30
+ - `Git::Repository` -> `Git::RepositoryPath`
31
+
32
+ - Run the test suite to ensure everything still works as expected.
33
+
34
+ 3. **Introduce New Core Classes (Empty Shells)**:
35
+
36
+ - Create the new `Git::ExecutionContext` class in `lib/git/execution_context.rb`. For now, its implementation can be a simple shell or a thin wrapper around the existing `Git::Lib`.
37
+
38
+ - Create the new `Git::Repository` class in `lib/git/repository.rb`. This will initially be an empty class.
39
+
40
+ 4. **Set Up RSpec Environment**:
41
+
42
+ - Add rspec dependencies to the `Gemfile` as a development dependency.
43
+
44
+ - Configure the test setup to allow both TestUnit and RSpec tests to run concurrently.
45
+
46
+ ## Phase 2: The Strangler Fig Pattern - Migrating Commands
47
+
48
+ ***Goal**: Incrementally move the implementation of each git command from `Git::Lib` to a new `Command` class, strangling the old implementation one piece at a time using a Test-Driven Development workflow.*
49
+
50
+ - **1. Migrate the First Command (`config`)**:
51
+
52
+ - **Write Unit Tests First**: Write comprehensive RSpec unit tests for the *proposed* `Git::Commands::Config` class. These tests will fail initially because the class doesn't exist yet. The tests should be fast and mock the `ExecutionContext`.
53
+
54
+ - **Create Command Class**: Implement `Git::Commands::Config` to make the tests pass. This class will contain all the logic for building git config arguments and parsing its output. It will accept an `ExecutionContext` instance in its constructor.
55
+
56
+ - **Delegate from `Git::Lib`**: Modify the `config_*` methods within the existing `Git::Lib` class. Instead of containing the implementation, they will now instantiate and call the new `Git::Commands::Config` object.
57
+
58
+ - **Verify**: Run the full test suite (both TestUnit and RSpec). The existing tests for `g.config` should still pass, but they will now be executing the new, refactored code.
59
+
60
+ - **2. Incrementally Migrate Remaining Commands:**
61
+
62
+ - Repeat the process from the previous step for all other commands, one by one or in logical groups (e.g., all `diff` related commands, then all `log` commands).
63
+
64
+ - For each command (`add`, `commit`, `log`, `diff`, `status`, etc.):
65
+
66
+ 1. Create the corresponding Git::Commands::* class.
67
+
68
+ 2. Write isolated RSpec unit tests for the new class.
69
+
70
+ 3. Change the method in Git::Lib to delegate to the new command object.
71
+
72
+ 4. Run the full test suite to ensure no regressions have been introduced.
73
+
74
+ ## Phase 3: Refactoring the Public Interface
75
+
76
+ ***Goal**: Switch the public-facing classes to use the new architecture directly, breaking the final ties to the old implementation.*
77
+
78
+ 1. **Refactor Factory Methods**:
79
+
80
+ - Modify the factory methods in the top-level `Git` module (`.open`, `.clone`, etc.).
81
+
82
+ - These methods will now be responsible for creating an instance of `Git::ExecutionContext` and injecting it into the constructor of a `Git::Repository` object.
83
+
84
+ The return value of these factories will now be a `Git::Repository` instance, not a `Git::Base` instance.
85
+
86
+ 2. **Implement the Facade**:
87
+
88
+ - Populate the `Git::Repository` class with the simple, one-line facade methods that delegate to the `Command` objects. For example:
89
+
90
+ ```ruby
91
+ def commit(msg)
92
+ Git::Commands::Commit.new(@execution_context, msg).run
93
+ end
94
+ ```
95
+
96
+ - Organize these facade methods into modules as planned (`lib/git/repository/branching.rb`, etc.) and include them in the main `Git::Repository` class.
97
+
98
+ 3. **Deprecate and Alias `Git::Base`**:
99
+
100
+ - To maintain a degree of backward compatibility through the transition, make `Git::Base` a deprecated constant that points to `Git::Repository`.
101
+
102
+ ```ruby
103
+ Git::Base = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(
104
+ 'Git::Base',
105
+ 'Git::Repository',
106
+ Git::Deprecation
107
+ )
108
+ ```
109
+
110
+ - This ensures that any user code checking `is_a?(Git::Base)` will not immediately break.
111
+
112
+ ## Phase 4: Final Cleanup and Release Preparation
113
+
114
+ ***Goal**: Remove all old code, finalize the test suite, and prepare for the v5.0.0 release.*
115
+
116
+ 1. **Remove Old Code**:
117
+
118
+ - Delete the `Git::Lib` class entirely.
119
+
120
+ - Delete the `Git::Base` class file and remove the deprecation proxy.
121
+
122
+ - Remove any other dead code that was part of the old implementation.
123
+
124
+ 2. **Finalize Test Suite**:
125
+
126
+ - Convert any remaining, relevant TestUnit tests to RSpec.
127
+
128
+ - Remove the `test-unit` dependency from the `Gemfile`.
129
+
130
+ - Ensure the RSpec suite has comprehensive coverage for the new architecture.
131
+
132
+ 3. **Update Documentation**:
133
+
134
+ - Thoroughly document the new public API (`Git`, `Git::Repository`, etc.).
135
+
136
+ - Mark all internal classes (`ExecutionContext`, `Commands`, `*Path`) with `@api private` in the YARD documentation.
137
+
138
+ - Update the `README.md` and create a `UPGRADING.md` guide explaining the breaking changes for v5.0.0.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.2
4
+ version: 4.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Chacon and others
@@ -274,6 +274,9 @@ files:
274
274
  - lib/git/worktree.rb
275
275
  - lib/git/worktrees.rb
276
276
  - package.json
277
+ - redesign/1_architecture_existing.md
278
+ - redesign/2_architecture_redesign.md
279
+ - redesign/3_architecture_implementation.md
277
280
  - release-please-config.json
278
281
  homepage: http://github.com/ruby-git/ruby-git
279
282
  licenses:
@@ -281,8 +284,8 @@ licenses:
281
284
  metadata:
282
285
  homepage_uri: http://github.com/ruby-git/ruby-git
283
286
  source_code_uri: http://github.com/ruby-git/ruby-git
284
- changelog_uri: https://rubydoc.info/gems/git/4.0.2/file/CHANGELOG.md
285
- documentation_uri: https://rubydoc.info/gems/git/4.0.2
287
+ changelog_uri: https://rubydoc.info/gems/git/4.0.5/file/CHANGELOG.md
288
+ documentation_uri: https://rubydoc.info/gems/git/4.0.5
286
289
  rubygems_mfa_required: 'true'
287
290
  rdoc_options: []
288
291
  require_paths:
@@ -299,7 +302,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
299
302
  version: '0'
300
303
  requirements:
301
304
  - git 2.28.0 or greater
302
- rubygems_version: 3.6.7
305
+ rubygems_version: 3.6.9
303
306
  specification_version: 4
304
307
  summary: An API to create, read, and manipulate Git repositories
305
308
  test_files: []