git 5.0.5 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7e6fda47311cb6c004daa4c42830658e97f5c95a37f325da3dee7bee64f8222
4
- data.tar.gz: 7ce4dd95073b4419f883ca79fc2985352575ac909c22739be7d3c43ae4d734b8
3
+ metadata.gz: 10e1f676a8b4122760435d18757f8dbb0f67e8e0f59f2e85463c0c7c93add807
4
+ data.tar.gz: 47687fd4a08c52945fb1eb8aa668429e33c7e4e9ce06f6a8664dc2f83e0372bc
5
5
  SHA512:
6
- metadata.gz: 3e3420fd700a1440102c3a8b186841ce3cfc3c442f7f80afb9691e1a337b35e947a8ffdf668916889218c839393e5971706667f4df492a04ead0afb27ecd1925
7
- data.tar.gz: d0a7ea7e2150e56bcd0da3cd4c86645b1430eb4193f8610709bd2d07394fbb320ec3d9b7b77e2eafb42324ba58740437ac5b433c2575058d7e01a72bc23998d5
6
+ metadata.gz: 3073e1e644e4d927de4e78bb6b0a8f082e6c757353a68b53c0ac2035c8ebee978e8d4a0a4c3a5fcaccece86ce5139f433af86716193ae2f8b0a0d26d6d9119c0
7
+ data.tar.gz: dcc552a33be697647b62e780492d454d3da93b1c9c8de218363f5118125abc59c7aa331ed15c1b69eb87e2b60b205db466b9e5da7cc59022d0a1ae9f55d0384a
@@ -207,10 +207,25 @@ jobs:
207
207
  # `update-locale` is deliberately not run: it edits /etc/default/locale, which is
208
208
  # only consulted for logins that do not already set LANG and LC_ALL, and this job
209
209
  # sets both explicitly.
210
+ #
211
+ # The two apt settings below exist because this step reaches out to the Ubuntu
212
+ # archive, which is the only part of this job that can fail for reasons that have
213
+ # nothing to do with the code under test:
214
+ #
215
+ # * `Acquire::Retries` retries an individual download that fails outright,
216
+ # rather than failing the job on one unlucky mirror.
217
+ # * `timeout-minutes` bounds a mirror that accepts the connection and then
218
+ # stops responding, which retries cannot help with. Without it the step
219
+ # inherits the 6 hour job default: run 32087601711 sat here for 12 minutes
220
+ # before it was cancelled by hand, and would otherwise have held a runner
221
+ # for the rest of the day. The whole job normally finishes in 53-118
222
+ # seconds, so 5 minutes is generous while still failing the same morning.
223
+ #
210
224
  - name: Install the German locale and message catalogs
225
+ timeout-minutes: 5
211
226
  run: |
212
- sudo apt-get update -qq
213
- sudo apt-get install -y --no-install-recommends language-pack-de
227
+ sudo apt-get -o Acquire::Retries=3 update -qq
228
+ sudo apt-get -o Acquire::Retries=3 install -y --no-install-recommends language-pack-de
214
229
  sudo locale-gen de_DE.UTF-8
215
230
 
216
231
  # Ruby 3.4 to share the bundler cache the lint job already warms. Nothing about
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "5.0.5"
2
+ ".": "5.1.0"
3
3
  }
data/.rubocop.yml CHANGED
@@ -26,6 +26,13 @@ Layout/LineLength:
26
26
  Style/OneClassPerFile:
27
27
  Enabled: false
28
28
 
29
+ # The repository is stored with LF line endings, but this cop defaults to `native`,
30
+ # which expects CRLF on Windows and so reports every file in the project as an
31
+ # offense there. Pinning it to `lf` makes the lint verdict identical on every
32
+ # platform, which is the same reason CI runs RuboCop on one runtime only.
33
+ Layout/EndOfLine:
34
+ EnforcedStyle: lf
35
+
29
36
  AllCops:
30
37
  # Must match the floor of required_ruby_version in the gemspec: TargetRubyVersion
31
38
  # decides which syntax RuboCop permits and suggests, so a value above the floor
data/CHANGELOG.md CHANGED
@@ -5,6 +5,30 @@
5
5
 
6
6
  # Change Log
7
7
 
8
+ ## [5.1.0](https://github.com/ruby-git/ruby-git/compare/v5.0.5...v5.1.0) (2026-08-18)
9
+
10
+
11
+ ### Features
12
+
13
+ * **grep:** Expose perl_regexp on the Git::Repository#grep facade ([759436f](https://github.com/ruby-git/ruby-git/commit/759436f7b59ac5028e38a2158a9a354720640415))
14
+ * **log:** Expose perl_regexp on the log path ([df8ea33](https://github.com/ruby-git/ruby-git/commit/df8ea33bb7d019d1a52fbb99370d9ffd67bf279f))
15
+
16
+
17
+ ### Other Changes
18
+
19
+ * Add fiddle as a Windows development dependency ([1847989](https://github.com/ruby-git/ruby-git/commit/18479892529bd1f992596cbc055a9a42be326680))
20
+ * Bound the locale job's apt step so a stalled mirror fails fast ([3a5d83b](https://github.com/ruby-git/ruby-git/commit/3a5d83b7685091a4abbde2c84c0136214fa66e5d))
21
+ * Cover the perl_regexp escape hatch for non-ASCII regex matching ([55e2ded](https://github.com/ruby-git/ruby-git/commit/55e2ded4563a39a0389dbbd52f8425da9a1e3aea))
22
+ * Document the Git for Windows byte-matching regex limitation ([9aaedbd](https://github.com/ruby-git/ruby-git/commit/9aaedbd94488bcfeef05cc56dfce61ce69f529fc))
23
+ * Document the windows symlink privilege requirement ([696dff3](https://github.com/ruby-git/ruby-git/commit/696dff3cbf09647bbfa51279f7e35fc8a85eaab4))
24
+ * Express development dependency conditions as named predicates ([acca20a](https://github.com/ruby-git/ruby-git/commit/acca20a43a6fb79f6b36de900ac8aa8c2488d87f))
25
+ * Fix a broken section link in the pre-review checklist ([719f595](https://github.com/ruby-git/ruby-git/commit/719f595af69c1afcf6b025d1a2bb920963738f80))
26
+ * Make integration fixtures independent of git and platform settings ([c2a1592](https://github.com/ruby-git/ruby-git/commit/c2a159287ec007793c125929029303707669c8ad))
27
+ * Pin rubocop to lf line endings ([7f40413](https://github.com/ruby-git/ruby-git/commit/7f40413c3eded1af7a3dede02e1d94d7dc9c829b))
28
+ * Pin test repository config from a single source ([6e227b7](https://github.com/ruby-git/ruby-git/commit/6e227b70f59f4bbaf2bbc1d30a4b1b86ffbe6afc))
29
+ * Scope the symlink rescue to the symlink call ([c2bed8f](https://github.com/ruby-git/ruby-git/commit/c2bed8fefdd8bdf2efa84432c318b2b39ba223d2))
30
+ * Skip the symlink specs when the host cannot create symlinks ([8e9b667](https://github.com/ruby-git/ruby-git/commit/8e9b667daeb6a179a02df7c946a495c7b7e6fce5))
31
+
8
32
  ## [5.0.5](https://github.com/ruby-git/ruby-git/compare/v5.0.4...v5.0.5) (2026-08-09)
9
33
 
10
34
 
data/CONTRIBUTING.md CHANGED
@@ -10,6 +10,7 @@
10
10
  - [How to report an issue or request a feature](#how-to-report-an-issue-or-request-a-feature)
11
11
  - [Local development setup](#local-development-setup)
12
12
  - [Prerequisites](#prerequisites)
13
+ - [A note for Windows contributors](#a-note-for-windows-contributors)
13
14
  - [Bootstrap the project](#bootstrap-the-project)
14
15
  - [Verify the toolchain](#verify-the-toolchain)
15
16
  - [Contributor validation policy](#contributor-validation-policy)
@@ -97,6 +98,18 @@ prerequisite is missing.
97
98
  | git | `>= 2.28.0` (matches `git.gemspec` `requirements`) | Older git versions are not supported and the test suite will not pass against them. |
98
99
  | Node.js / npm | Optional | Required only to install the local Conventional Commit `commit-msg` hook (Husky + commitlint). If npm is missing, `bin/setup` will warn and continue — CI will still validate commit messages. |
99
100
 
101
+ #### A note for Windows contributors
102
+
103
+ A few unit specs create real symlinks, which on Windows requires
104
+ `SeCreateSymbolicLinkPrivilege`. A non-elevated process only holds that privilege
105
+ when Developer Mode is enabled (Settings → System → For developers). Without it
106
+ those specs skip rather than fail, so `bundle exec rake` still passes — but the
107
+ behavior they cover goes unverified locally.
108
+
109
+ The same privilege decides whether Git for Windows materializes the committed
110
+ `.claude/skills` symlink, so enabling Developer Mode fixes both at once. See
111
+ [Agent configuration](#agent-configuration).
112
+
100
113
  ### Bootstrap the project
101
114
 
102
115
  From the project root, run:
@@ -181,7 +194,7 @@ Before moving a pull request out of draft or requesting a review, confirm:
181
194
  - [ ] `bundle exec rake` passes locally on your branch (see
182
195
  [Local development setup](#local-development-setup)).
183
196
  - [ ] New or changed code has accompanying tests under `spec/`
184
- (see [Unit tests](#unit-tests)).
197
+ (see [Unit tests vs Integration tests](#unit-tests-vs-integration-tests)).
185
198
  - [ ] Every commit message follows [Conventional Commits](#commit-message-guidelines).
186
199
  - [ ] User-facing changes are documented in `README.md` and/or YARD as appropriate.
187
200
 
@@ -967,9 +980,21 @@ below either threshold.
967
980
 
968
981
  This is enforceable without being onerous because unit coverage in this project is
969
982
  deterministic: `lib/` has no Ruby-version, Ruby-engine, or platform conditionals, and
970
- no unit spec is conditionally skipped. Every supported MRI runtime measures exactly
971
- the same lines and branches, so a coverage failure is always something the pull
972
- request introduced.
983
+ the handful of unit specs that are conditionally skipped are redundant for coverage
984
+ every `lib/` line and branch they reach is also reached by a spec that always runs.
985
+ Every supported MRI runtime therefore measures exactly the same lines and branches, so
986
+ a coverage failure is always something the pull request introduced.
987
+
988
+ A new conditional skip in `spec/unit/` must preserve that property. Verify it on a
989
+ host where the guard actually skips: run the full unit suite there and confirm it
990
+ still reports 100% line and branch coverage. A conditionally skipped unit spec that
991
+ is the only thing covering a line would turn this gate into a platform-dependent
992
+ failure, which is exactly what the policy exists to prevent.
993
+
994
+ Write the guard the same way the rest of the suite does: a reusable predicate in
995
+ `spec/spec_helper.rb` (`unless_git`, `unless_command`, `unless_pcre`,
996
+ `unless_ci_build`) used as `skip:` metadata, or — for a one-off capability that the
997
+ `before` block is already exercising — a `rescue` in that block that calls `skip`.
973
998
 
974
999
  What the policy does and does not cover:
975
1000
 
data/README.md CHANGED
@@ -30,6 +30,8 @@ Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?log
30
30
  - [Errors Raised by This Gem](#errors-raised-by-this-gem)
31
31
  - [Specifying and Handling Timeouts](#specifying-and-handling-timeouts)
32
32
  - [Deprecations](#deprecations)
33
+ - [Platform Limitations](#platform-limitations)
34
+ - [Regex Metacharacters on Git for Windows](#regex-metacharacters-on-git-for-windows)
33
35
  - [Project Policies](#project-policies)
34
36
  - [Ruby Version Support Policy](#ruby-version-support-policy)
35
37
  - [Git Version Support Policy](#git-version-support-policy)
@@ -302,6 +304,71 @@ needed for the upgrade.
302
304
  For the full list of deprecated methods and their replacements, see
303
305
  [UPGRADING.md](UPGRADING.md).
304
306
 
307
+ ## Platform Limitations
308
+
309
+ ### Regex Metacharacters on Git for Windows
310
+
311
+ On Git for Windows, git's regex engine matches **bytes** rather than characters. A
312
+ metacharacter such as `.`, or a POSIX character class such as `[[:alpha:]]`, therefore
313
+ never matches a whole multi-byte character. The same call matches on Linux and macOS.
314
+
315
+ The failure is silent — nothing raises, and the result is indistinguishable from a
316
+ pattern that genuinely does not occur:
317
+
318
+ ```ruby
319
+ # File content, commit message, and config value are all 'ÄPFEL sind gut'.
320
+ # 'Ä' is two bytes in UTF-8 (C3 84), so '.' has to match both to match the character.
321
+
322
+ repo.grep('^.PFEL') # => {} on Windows, matches elsewhere
323
+ repo.log.grep('^.PFEL').execute.size # => 0 on Windows, 1 elsewhere
324
+ repo.config_get_all('test.desc', '^.PFEL') # => [] on Windows, matches elsewhere
325
+ ```
326
+
327
+ This is a property of the regex engine git bundles on that platform, not something the
328
+ gem sets. It is unaffected by the locale: the behavior is identical under `en_US.UTF-8`,
329
+ `C.UTF-8`, `C`, and with no `LC_ALL` set at all. Literal (metacharacter-free) patterns
330
+ and case-insensitive matching are unaffected on every platform.
331
+
332
+ **Workaround.** Perl-compatible regular expressions do match characters on Git for
333
+ Windows, so the surfaces that can reach a PCRE engine accept an opt-in selector:
334
+
335
+ ```ruby
336
+ repo.grep('^.PFEL', nil, perl_regexp: true) # matches on every platform
337
+ repo.log.perl_regexp.grep('^.PFEL').execute # matches on every platform
338
+ repo.full_log_commits(grep: '^.PFEL', perl_regexp: true)
339
+ ```
340
+
341
+ Two caveats:
342
+
343
+ - **PCRE is a different dialect** than git's default POSIX basic/extended regular
344
+ expressions. Selecting it is a deliberate choice by the caller, so the gem does not
345
+ substitute it automatically based on the host.
346
+ - **PCRE must be compiled in.** Git for Windows and the mainstream Linux and macOS
347
+ packages ship it, but git built without `USE_LIBPCRE` fails with `cannot use
348
+ Perl-compatible regexes...`.
349
+
350
+ **There is no workaround for `git config` value patterns.** They are POSIX extended
351
+ regular expressions with no PCRE mode, so `config_get`, `config_get_all`,
352
+ `config_get_regexp`, `config_replace_all`, `config_unset`, and `config_unset_all` cannot
353
+ match a metacharacter against a non-ASCII character on Git for Windows. Match on ASCII
354
+ text or an exact value instead.
355
+
356
+ `config_replace_all` deserves particular care, because there the failure is not merely an
357
+ empty result. When the value pattern selects nothing, `git config --replace-all` *adds*
358
+ the new value as an additional entry rather than replacing one, and exits zero:
359
+
360
+ ```ruby
361
+ # Existing value of test.desc is 'ÄPFEL sind gut'
362
+ repo.config_replace_all('test.desc', 'NEW', '^.PFEL')
363
+
364
+ repo.config_get_all('test.desc').map(&:value)
365
+ # => ["NEW"] elsewhere — replaced, as intended
366
+ # => ["ÄPFEL sind gut", "NEW"] on Windows — original kept, duplicate added
367
+ ```
368
+
369
+ So a replace can silently leave the original value in place and add a second entry beside
370
+ it. Confirm with `config_get_all` when the key must end up single-valued.
371
+
305
372
  ## Project Policies
306
373
 
307
374
  These documents set expectations for behavior, contribution workflows, AI-assisted
data/git.gemspec CHANGED
@@ -46,31 +46,59 @@ Gem::Specification.new do |spec|
46
46
  spec.add_dependency 'process_executer', '~> 4.0'
47
47
  spec.add_dependency 'rchardet', '~> 1.9'
48
48
 
49
+ # Not every development dependency is installed on every runtime. Each predicate
50
+ # below names one reason for holding a gem back, which keeps the dependency list
51
+ # itself a flat, readable list. Deriving one predicate from another also keeps
52
+ # coupled gems from drifting apart when a condition changes.
53
+ #
54
+ # These are local variables rather than methods deliberately. The Gemfile uses
55
+ # `gemspec`, so Bundler evaluates this file on every `bundle exec`, and a top-level
56
+ # `def` -- including one written inside this block, since a block is not a definition
57
+ # scope -- would define a private method on Object in every one of those processes.
58
+
59
+ # JRuby (which reports RUBY_PLATFORM as 'java') and TruffleRuby build no C extensions
60
+ # and do not run the docs build.
61
+ mri = !(RUBY_PLATFORM == 'java' || RUBY_ENGINE == 'truffleruby')
62
+
63
+ # irb stopped being a default gem in Ruby 3.4. JRuby and TruffleRuby ship their own.
64
+ install_irb = mri
65
+
66
+ # Ruby 4.0.0 dropped fiddle from the default gems. On Windows, irb loads
67
+ # reline/io/windows.rb, which requires fiddle/import for the Win32 console API, so
68
+ # bin/console cannot start without it; every other platform takes reline's ANSI IO
69
+ # gate and never loads that file. Derived from install_irb because fiddle exists only
70
+ # to serve irb and, being a C extension, could not install where irb is not.
71
+ install_fiddle = install_irb && Gem.win_platform?
72
+
73
+ # The docs toolchain is supported on MRI only. redcarpet, YARD's Markdown renderer,
74
+ # is also a C extension and so could not install on JRuby regardless.
75
+ install_docs = mri
76
+
77
+ # yard-lint requires Ruby >= 3.3.
78
+ install_yard_lint = install_docs && Gem.ruby_version >= Gem::Version.new('3.3.0')
79
+
80
+ # i18n 1.15+ uses Fiber.[] (Ruby 3.2 Fiber storage), which TruffleRuby < 34.0.0 does
81
+ # not implement, so those runtimes hold at the last release that works there.
82
+ pin_old_i18n = RUBY_ENGINE == 'truffleruby' &&
83
+ Gem::Version.new(RUBY_ENGINE_VERSION) < Gem::Version.new('34.0.0')
84
+
49
85
  spec.add_development_dependency 'create_github_release', '~> 2.1'
86
+ spec.add_development_dependency 'fiddle', '~> 1.1' if install_fiddle
50
87
  spec.add_development_dependency 'fuubar', '~> 2.5'
88
+ spec.add_development_dependency 'i18n', '< 1.15' if pin_old_i18n
89
+ spec.add_development_dependency 'irb', '~> 1.16' if install_irb
51
90
  spec.add_development_dependency 'main_branch_shared_rubocop_config', '~> 0.1'
52
91
  spec.add_development_dependency 'parallel_tests', '~> 5.6'
53
92
  spec.add_development_dependency 'rake', '~> 13.3'
93
+ spec.add_development_dependency 'redcarpet', '~> 3.6' if install_docs
54
94
  spec.add_development_dependency 'rspec', '~> 3.13'
55
95
  spec.add_development_dependency 'rubocop', '~> 1.82'
56
96
  spec.add_development_dependency 'simplecov', '~> 1.0'
57
97
  spec.add_development_dependency 'simplecov-lcov', '~> 0.9'
58
98
  spec.add_development_dependency 'simplecov-rspec', '~> 1.1'
59
-
60
- if RUBY_ENGINE == 'truffleruby' && Gem::Version.new(RUBY_ENGINE_VERSION) < Gem::Version.new('34.0.0')
61
- # i18n 1.15+ uses Fiber.[] (Ruby 3.2 Fiber storage) which TruffleRuby < 34.0.0 does not implement
62
- spec.add_development_dependency 'i18n', '< 1.15'
63
- end
64
-
65
- unless RUBY_PLATFORM == 'java' || RUBY_ENGINE == 'truffleruby'
66
- spec.add_development_dependency 'irb', '~> 1.16'
67
- spec.add_development_dependency 'redcarpet', '~> 3.6'
68
- spec.add_development_dependency 'yard', '~> 0.9', '>= 0.9.28'
69
- spec.add_development_dependency 'yard_example_test', '~> 0.2', '>= 0.2.1'
70
-
71
- # yard-lint requires Ruby >= 3.3, so it is only installed on Ruby 3.3+.
72
- spec.add_development_dependency 'yard-lint', '~> 1.8' if Gem.ruby_version >= Gem::Version.new('3.3.0')
73
- end
99
+ spec.add_development_dependency 'yard', '~> 0.9', '>= 0.9.28' if install_docs
100
+ spec.add_development_dependency 'yard_example_test', '~> 0.2', '>= 0.2.1' if install_docs
101
+ spec.add_development_dependency 'yard-lint', '~> 1.8' if install_yard_lint
74
102
 
75
103
  # Specify which files should be added to the gem when it is released.
76
104
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -88,6 +88,14 @@ module Git
88
88
  #
89
89
  # @raise [Git::FailedError] if git exits with an unexpected non-zero status
90
90
  #
91
+ # @note On Git for Windows, git's default regex engine matches *bytes* rather than
92
+ # characters, so a metacharacter such as `.` or a POSIX class such as
93
+ # `[[:alpha:]]` in `value_regex` never matches a whole multi-byte character. The
94
+ # failure is silent: nothing raises, and the outcome is indistinguishable from a
95
+ # `value_regex` that genuinely matches nothing. `git config` value patterns are
96
+ # POSIX extended regular expressions with no PCRE mode, so unlike
97
+ # {Git::Repository#grep} there is no alternate regex engine to select here.
98
+ #
91
99
  def config_get(name, value_regex = nil, **options)
92
100
  Private.assert_valid_opts!(CONFIG_GET_ALLOWED_OPTS, **options)
93
101
  assert_valid_scope!(**options)
@@ -139,6 +147,14 @@ module Git
139
147
  #
140
148
  # @raise [Git::FailedError] if git exits with an unexpected non-zero status
141
149
  #
150
+ # @note On Git for Windows, git's default regex engine matches *bytes* rather than
151
+ # characters, so a metacharacter such as `.` or a POSIX class such as
152
+ # `[[:alpha:]]` in `value_regex` never matches a whole multi-byte character. The
153
+ # failure is silent: nothing raises, and the outcome is indistinguishable from a
154
+ # `value_regex` that genuinely matches nothing. `git config` value patterns are
155
+ # POSIX extended regular expressions with no PCRE mode, so unlike
156
+ # {Git::Repository#grep} there is no alternate regex engine to select here.
157
+ #
142
158
  def config_get_all(name, value_regex = nil, **options)
143
159
  Private.assert_valid_opts!(CONFIG_GET_ALL_ALLOWED_OPTS, **options)
144
160
  assert_valid_scope!(**options)
@@ -238,6 +254,14 @@ module Git
238
254
  #
239
255
  # @raise [Git::FailedError] if git exits with an unexpected non-zero status
240
256
  #
257
+ # @note On Git for Windows, git's default regex engine matches *bytes* rather than
258
+ # characters, so a metacharacter such as `.` or a POSIX class such as
259
+ # `[[:alpha:]]` in `value_regex` never matches a whole multi-byte character. The
260
+ # failure is silent: nothing raises, and the outcome is indistinguishable from a
261
+ # `value_regex` that genuinely matches nothing. `git config` value patterns are
262
+ # POSIX extended regular expressions with no PCRE mode, so unlike
263
+ # {Git::Repository#grep} there is no alternate regex engine to select here.
264
+ #
241
265
  def config_get_regexp(name_regex, value_regex = nil, **options)
242
266
  Private.assert_valid_opts!(CONFIG_GET_REGEXP_ALLOWED_OPTS, **options)
243
267
  assert_valid_scope!(**options)
@@ -529,6 +553,22 @@ module Git
529
553
  #
530
554
  # @raise [Git::FailedError] if git exits with a non-zero exit status
531
555
  #
556
+ # @note On Git for Windows, git's default regex engine matches *bytes* rather than
557
+ # characters, so a metacharacter such as `.` or a POSIX class such as
558
+ # `[[:alpha:]]` in `value_regex` never matches a whole multi-byte character. The
559
+ # failure is silent: nothing raises, and the outcome is indistinguishable from a
560
+ # `value_regex` that genuinely matches nothing. `git config` value patterns are
561
+ # POSIX extended regular expressions with no PCRE mode, so unlike
562
+ # {Git::Repository#grep} there is no alternate regex engine to select here.
563
+ #
564
+ # @note A `value_regex` that selects nothing does not make this method a no-op.
565
+ # `git config --replace-all` *adds* `value` as an additional entry when no
566
+ # existing value matches, and exits zero. On Git for Windows, a `value_regex`
567
+ # whose metacharacters span non-ASCII text therefore leaves the value it was
568
+ # meant to replace in place and silently creates a duplicate entry beside it.
569
+ # Confirm the result with {#config_get_all}, or match on ASCII text, when the
570
+ # key must end up single-valued.
571
+ #
532
572
  def config_replace_all(name, value, value_regex = nil, **)
533
573
  Private.assert_valid_opts!(CONFIG_REPLACE_ALL_ALLOWED_OPTS, **)
534
574
  assert_valid_scope!(**)
@@ -621,6 +661,14 @@ module Git
621
661
  #
622
662
  # @raise [Git::FailedError] if git exits with a non-zero exit status
623
663
  #
664
+ # @note On Git for Windows, git's default regex engine matches *bytes* rather than
665
+ # characters, so a metacharacter such as `.` or a POSIX class such as
666
+ # `[[:alpha:]]` in `value_regex` never matches a whole multi-byte character. The
667
+ # failure is silent: nothing raises, and the outcome is indistinguishable from a
668
+ # `value_regex` that genuinely matches nothing. `git config` value patterns are
669
+ # POSIX extended regular expressions with no PCRE mode, so unlike
670
+ # {Git::Repository#grep} there is no alternate regex engine to select here.
671
+ #
624
672
  def config_unset(name, value_regex = nil, **)
625
673
  Private.assert_valid_opts!(CONFIG_UNSET_ALLOWED_OPTS, **)
626
674
  assert_valid_scope!(**)
@@ -666,6 +714,14 @@ module Git
666
714
  #
667
715
  # @raise [Git::FailedError] if git exits with a non-zero exit status
668
716
  #
717
+ # @note On Git for Windows, git's default regex engine matches *bytes* rather than
718
+ # characters, so a metacharacter such as `.` or a POSIX class such as
719
+ # `[[:alpha:]]` in `value_regex` never matches a whole multi-byte character. The
720
+ # failure is silent: nothing raises, and the outcome is indistinguishable from a
721
+ # `value_regex` that genuinely matches nothing. `git config` value patterns are
722
+ # POSIX extended regular expressions with no PCRE mode, so unlike
723
+ # {Git::Repository#grep} there is no alternate regex engine to select here.
724
+ #
669
725
  def config_unset_all(name, value_regex = nil, **)
670
726
  Private.assert_valid_opts!(CONFIG_UNSET_ALL_ALLOWED_OPTS, **)
671
727
  assert_valid_scope!(**)
data/lib/git/log.rb CHANGED
@@ -142,6 +142,25 @@ module Git
142
142
  #
143
143
  def grep(regex) = set_option(:grep, regex)
144
144
 
145
+ # Interprets {#grep} and {#author} patterns as Perl-compatible regular expressions
146
+ #
147
+ # Selects PCRE instead of git's default POSIX basic regular expressions for
148
+ # every pattern in the query. Requires a git built with PCRE support.
149
+ #
150
+ # @example Match a metacharacter against a non-ASCII character on Git for Windows
151
+ # repo.log.perl_regexp.grep('^.PFEL').execute
152
+ #
153
+ # @return [Git::Log] the current query builder
154
+ #
155
+ # @note On Git for Windows, git's default regex engine matches *bytes* rather
156
+ # than characters, so a metacharacter such as `.` or a POSIX class such as
157
+ # `[[:alpha:]]` never matches a whole multi-byte character. The match fails
158
+ # silently: git exits zero and the result is empty. PCRE does match characters
159
+ # on that platform, but it is a different dialect than git's default, so
160
+ # selecting it is a deliberate choice by the caller.
161
+ #
162
+ def perl_regexp = set_option(:perl_regexp, true)
163
+
145
164
  # Limits commits to those that touch the given path or paths
146
165
  #
147
166
  # @param path [String, Pathname, Array<String, Pathname>] path limiter input
@@ -18,7 +18,7 @@ module Git
18
18
  # @return [Array<Symbol>] the supported option keys
19
19
  #
20
20
  FULL_LOG_COMMITS_ALLOWED_OPTS = %i[
21
- count all cherry since until grep author between object path_limiter skip merges
21
+ count all cherry since until grep author between object path_limiter skip merges perl_regexp
22
22
  ].freeze
23
23
  private_constant :FULL_LOG_COMMITS_ALLOWED_OPTS
24
24
 
@@ -68,6 +68,13 @@ module Git
68
68
  #
69
69
  # @option opts [Boolean, nil] :merges (nil) include only merge commits
70
70
  #
71
+ # @option opts [Boolean, nil] :perl_regexp (nil) interpret the `:grep` and
72
+ # `:author` patterns as Perl-compatible regular expressions (PCRE) instead of
73
+ # git's default POSIX basic regular expressions
74
+ #
75
+ # Requires a git built with PCRE support; git otherwise fails with
76
+ # "cannot use Perl-compatible regexes...".
77
+ #
71
78
  # @return [Array<Hash>] the parsed raw log output for each commit
72
79
  #
73
80
  # @raise [ArgumentError] if unsupported options are provided
@@ -76,6 +83,14 @@ module Git
76
83
  #
77
84
  # @raise [Git::FailedError] if git exits with a non-zero exit status
78
85
  #
86
+ # @note On Git for Windows, git's default regex engine matches *bytes* rather
87
+ # than characters, so a metacharacter such as `.` or a POSIX class such as
88
+ # `[[:alpha:]]` never matches a whole multi-byte character in a `:grep` or
89
+ # `:author` pattern. The match fails silently: git exits zero and the result
90
+ # is empty. Pass `perl_regexp: true` to select PCRE, which does match
91
+ # characters. PCRE is a different dialect than git's default, so this is a
92
+ # deliberate choice by the caller rather than a transparent substitution.
93
+ #
79
94
  # @see https://git-scm.com/docs/git-log git-log
80
95
  #
81
96
  def full_log_commits(opts = {})
@@ -113,6 +128,14 @@ module Git
113
128
  module Private
114
129
  module_function
115
130
 
131
+ # Log option keys forwarded to {Git::Commands::Log#call} under the same name
132
+ #
133
+ # The remaining options are renamed or reshaped by {#log_base_call_options}.
134
+ #
135
+ # @return [Array<Symbol>] the pass-through option keys
136
+ #
137
+ PASSTHROUGH_LOG_OPTS = %i[all cherry since until grep author perl_regexp].freeze
138
+
116
139
  # Validates the :count log option
117
140
  #
118
141
  # @param opts [Hash] the log options
@@ -195,6 +218,9 @@ module Git
195
218
  # @option opts [String, nil] :author (nil) only include commits whose author
196
219
  # matches this pattern
197
220
  #
221
+ # @option opts [Boolean, nil] :perl_regexp (nil) interpret the `:grep` and
222
+ # `:author` patterns as Perl-compatible regular expressions
223
+ #
198
224
  # @option opts [Integer, nil] :count (nil) maximum number of commits to return
199
225
  #
200
226
  # @option opts [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
@@ -207,16 +233,14 @@ module Git
207
233
  # @return [Hash] keyword options for {Git::Commands::Log#call}
208
234
  #
209
235
  def log_base_call_options(opts, extra = {})
210
- {
211
- all: opts[:all],
212
- cherry: opts[:cherry],
213
- since: opts[:since],
214
- until: opts[:until],
215
- grep: opts[:grep],
216
- author: opts[:author],
217
- max_count: opts[:count],
218
- path: opts[:path_limiter] ? Array(opts[:path_limiter]) : nil
219
- }.merge(extra).compact
236
+ opts
237
+ .slice(*PASSTHROUGH_LOG_OPTS)
238
+ .merge(
239
+ max_count: opts[:count],
240
+ path: opts[:path_limiter] ? Array(opts[:path_limiter]) : nil
241
+ )
242
+ .merge(extra)
243
+ .compact
220
244
  end
221
245
 
222
246
  # Executes git log and parses the raw output
@@ -454,7 +454,7 @@ module Git
454
454
  end
455
455
 
456
456
  # Option keys accepted by {#grep}
457
- GREP_ALLOWED_OPTS = %i[ignore_case i invert_match v extended_regexp E object].freeze
457
+ GREP_ALLOWED_OPTS = %i[ignore_case i invert_match v extended_regexp E perl_regexp P object].freeze
458
458
  private_constant :GREP_ALLOWED_OPTS
459
459
 
460
460
  # Search tracked file contents in a git tree for a pattern
@@ -475,6 +475,9 @@ module Git
475
475
  # @example Case-insensitive search
476
476
  # repo.grep('todo', nil, ignore_case: true)
477
477
  #
478
+ # @example Match a metacharacter against a non-ASCII character on Git for Windows
479
+ # repo.grep('^.PFEL', nil, perl_regexp: true)
480
+ #
478
481
  # @param pattern [String] the pattern to search for
479
482
  #
480
483
  # @param path_limiter [String, Pathname, Array<String, Pathname>, nil]
@@ -499,6 +502,14 @@ module Git
499
502
  #
500
503
  # Alias: :E
501
504
  #
505
+ # @option opts [Boolean, nil] :perl_regexp (nil) use Perl-compatible regular
506
+ # expressions (PCRE) for the pattern
507
+ #
508
+ # Requires a git built with PCRE support; git otherwise fails with
509
+ # "cannot use Perl-compatible regexes...".
510
+ #
511
+ # Alias: :P
512
+ #
502
513
  # @return [Hash<String, Array<Array(Integer, String)>>] a hash mapping
503
514
  # each `"treeish:filename"` key to an array of `[line_number, text]`
504
515
  # pairs; returns an empty hash when no lines match
@@ -508,6 +519,16 @@ module Git
508
519
  # @raise [Git::FailedError] if git exits with a non-zero status and
509
520
  # stderr is non-empty (e.g. bad object reference)
510
521
  #
522
+ # @note On Git for Windows, git's default regex engine matches *bytes* rather
523
+ # than characters, so a metacharacter such as `.` or a POSIX class such as
524
+ # `[[:alpha:]]` never matches a whole multi-byte character. The failure is
525
+ # silent: nothing raises, and the empty hash returned is indistinguishable
526
+ # from a pattern that genuinely does not occur in the tree. Pass
527
+ # `perl_regexp: true` to select PCRE, which does match characters. PCRE is a
528
+ # different dialect than git's default POSIX basic/extended regular
529
+ # expressions, so this is a deliberate choice by the caller rather than a
530
+ # transparent substitution, and it requires a git built with PCRE support.
531
+ #
511
532
  # @see https://git-scm.com/docs/git-grep git-grep documentation
512
533
  #
513
534
  def grep(pattern, path_limiter = nil, opts = {})
data/lib/git/version.rb CHANGED
@@ -4,7 +4,7 @@ module Git
4
4
  # The current gem version
5
5
  #
6
6
  # @return [String] the current gem version
7
- VERSION = '5.0.5'
7
+ VERSION = '5.1.0'
8
8
 
9
9
  # Represents a git version with major, minor, and patch components
10
10
  #
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: 5.0.5
4
+ version: 5.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Chacon and others
@@ -93,6 +93,20 @@ dependencies:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
95
  version: '2.5'
96
+ - !ruby/object:Gem::Dependency
97
+ name: irb
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '1.16'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.16'
96
110
  - !ruby/object:Gem::Dependency
97
111
  name: main_branch_shared_rubocop_config
98
112
  requirement: !ruby/object:Gem::Requirement
@@ -135,6 +149,20 @@ dependencies:
135
149
  - - "~>"
136
150
  - !ruby/object:Gem::Version
137
151
  version: '13.3'
152
+ - !ruby/object:Gem::Dependency
153
+ name: redcarpet
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '3.6'
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: '3.6'
138
166
  - !ruby/object:Gem::Dependency
139
167
  name: rspec
140
168
  requirement: !ruby/object:Gem::Requirement
@@ -205,34 +233,6 @@ dependencies:
205
233
  - - "~>"
206
234
  - !ruby/object:Gem::Version
207
235
  version: '1.1'
208
- - !ruby/object:Gem::Dependency
209
- name: irb
210
- requirement: !ruby/object:Gem::Requirement
211
- requirements:
212
- - - "~>"
213
- - !ruby/object:Gem::Version
214
- version: '1.16'
215
- type: :development
216
- prerelease: false
217
- version_requirements: !ruby/object:Gem::Requirement
218
- requirements:
219
- - - "~>"
220
- - !ruby/object:Gem::Version
221
- version: '1.16'
222
- - !ruby/object:Gem::Dependency
223
- name: redcarpet
224
- requirement: !ruby/object:Gem::Requirement
225
- requirements:
226
- - - "~>"
227
- - !ruby/object:Gem::Version
228
- version: '3.6'
229
- type: :development
230
- prerelease: false
231
- version_requirements: !ruby/object:Gem::Requirement
232
- requirements:
233
- - - "~>"
234
- - !ruby/object:Gem::Version
235
- version: '3.6'
236
236
  - !ruby/object:Gem::Dependency
237
237
  name: yard
238
238
  requirement: !ruby/object:Gem::Requirement
@@ -631,8 +631,8 @@ licenses:
631
631
  metadata:
632
632
  homepage_uri: http://github.com/ruby-git/ruby-git
633
633
  source_code_uri: http://github.com/ruby-git/ruby-git
634
- changelog_uri: https://rubydoc.info/gems/git/5.0.5/file/CHANGELOG.md
635
- documentation_uri: https://rubydoc.info/gems/git/5.0.5
634
+ changelog_uri: https://rubydoc.info/gems/git/5.1.0/file/CHANGELOG.md
635
+ documentation_uri: https://rubydoc.info/gems/git/5.1.0
636
636
  rubygems_mfa_required: 'true'
637
637
  rdoc_options: []
638
638
  require_paths: