git 1.19.1 → 5.5.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.
Files changed (265) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -1
  3. data/AI_POLICY.md +24 -0
  4. data/CHANGELOG.md +689 -0
  5. data/CODE_OF_CONDUCT.md +25 -0
  6. data/CONTRIBUTING.md +1175 -97
  7. data/GOVERNANCE.md +106 -0
  8. data/LICENSE +1 -1
  9. data/MAINTAINERS.md +17 -4
  10. data/README.md +476 -320
  11. data/UPGRADING.md +1138 -0
  12. data/git.gemspec +124 -36
  13. data/lib/git/author.rb +39 -7
  14. data/lib/git/author_info.rb +66 -0
  15. data/lib/git/branch.rb +615 -65
  16. data/lib/git/branch_delete_failure.rb +34 -0
  17. data/lib/git/branch_delete_result.rb +66 -0
  18. data/lib/git/branch_info.rb +237 -0
  19. data/lib/git/branches.rb +167 -44
  20. data/lib/git/command_line/base.rb +247 -0
  21. data/lib/git/command_line/capturing.rb +308 -0
  22. data/lib/git/command_line/result.rb +88 -0
  23. data/lib/git/command_line/streaming.rb +236 -0
  24. data/lib/git/command_line.rb +52 -0
  25. data/lib/git/commands/add.rb +139 -0
  26. data/lib/git/commands/am/abort.rb +43 -0
  27. data/lib/git/commands/am/apply.rb +263 -0
  28. data/lib/git/commands/am/continue.rb +43 -0
  29. data/lib/git/commands/am/quit.rb +43 -0
  30. data/lib/git/commands/am/retry.rb +49 -0
  31. data/lib/git/commands/am/show_current_patch.rb +64 -0
  32. data/lib/git/commands/am/skip.rb +42 -0
  33. data/lib/git/commands/am.rb +33 -0
  34. data/lib/git/commands/apply.rb +242 -0
  35. data/lib/git/commands/archive/list_formats.rb +46 -0
  36. data/lib/git/commands/archive.rb +145 -0
  37. data/lib/git/commands/arguments.rb +4521 -0
  38. data/lib/git/commands/base.rb +502 -0
  39. data/lib/git/commands/branch/copy.rb +102 -0
  40. data/lib/git/commands/branch/create.rb +177 -0
  41. data/lib/git/commands/branch/delete.rb +88 -0
  42. data/lib/git/commands/branch/list.rb +178 -0
  43. data/lib/git/commands/branch/move.rb +102 -0
  44. data/lib/git/commands/branch/set_upstream.rb +86 -0
  45. data/lib/git/commands/branch/show_current.rb +49 -0
  46. data/lib/git/commands/branch/unset_upstream.rb +53 -0
  47. data/lib/git/commands/branch.rb +34 -0
  48. data/lib/git/commands/cat_file/batch.rb +385 -0
  49. data/lib/git/commands/cat_file/filtered.rb +105 -0
  50. data/lib/git/commands/cat_file/raw.rb +271 -0
  51. data/lib/git/commands/cat_file.rb +49 -0
  52. data/lib/git/commands/checkout/branch.rb +153 -0
  53. data/lib/git/commands/checkout/files.rb +116 -0
  54. data/lib/git/commands/checkout.rb +38 -0
  55. data/lib/git/commands/checkout_index.rb +106 -0
  56. data/lib/git/commands/clean.rb +102 -0
  57. data/lib/git/commands/clone.rb +241 -0
  58. data/lib/git/commands/commit.rb +273 -0
  59. data/lib/git/commands/commit_tree.rb +101 -0
  60. data/lib/git/commands/config_option_syntax/add.rb +86 -0
  61. data/lib/git/commands/config_option_syntax/get.rb +121 -0
  62. data/lib/git/commands/config_option_syntax/get_all.rb +118 -0
  63. data/lib/git/commands/config_option_syntax/get_color.rb +95 -0
  64. data/lib/git/commands/config_option_syntax/get_color_bool.rb +96 -0
  65. data/lib/git/commands/config_option_syntax/get_regexp.rb +119 -0
  66. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +111 -0
  67. data/lib/git/commands/config_option_syntax/list.rb +111 -0
  68. data/lib/git/commands/config_option_syntax/remove_section.rb +79 -0
  69. data/lib/git/commands/config_option_syntax/rename_section.rb +83 -0
  70. data/lib/git/commands/config_option_syntax/replace_all.rb +109 -0
  71. data/lib/git/commands/config_option_syntax/set.rb +119 -0
  72. data/lib/git/commands/config_option_syntax/unset.rb +92 -0
  73. data/lib/git/commands/config_option_syntax/unset_all.rb +94 -0
  74. data/lib/git/commands/config_option_syntax.rb +56 -0
  75. data/lib/git/commands/describe.rb +156 -0
  76. data/lib/git/commands/diff.rb +657 -0
  77. data/lib/git/commands/diff_files.rb +519 -0
  78. data/lib/git/commands/diff_index.rb +499 -0
  79. data/lib/git/commands/fetch.rb +354 -0
  80. data/lib/git/commands/fsck.rb +138 -0
  81. data/lib/git/commands/gc.rb +134 -0
  82. data/lib/git/commands/grep.rb +339 -0
  83. data/lib/git/commands/init.rb +101 -0
  84. data/lib/git/commands/log.rb +634 -0
  85. data/lib/git/commands/ls_files.rb +195 -0
  86. data/lib/git/commands/ls_remote.rb +161 -0
  87. data/lib/git/commands/ls_tree.rb +135 -0
  88. data/lib/git/commands/maintenance/register.rb +77 -0
  89. data/lib/git/commands/maintenance/run.rb +109 -0
  90. data/lib/git/commands/maintenance/start.rb +71 -0
  91. data/lib/git/commands/maintenance/stop.rb +60 -0
  92. data/lib/git/commands/maintenance/unregister.rb +84 -0
  93. data/lib/git/commands/maintenance.rb +31 -0
  94. data/lib/git/commands/merge/abort.rb +44 -0
  95. data/lib/git/commands/merge/continue.rb +44 -0
  96. data/lib/git/commands/merge/quit.rb +46 -0
  97. data/lib/git/commands/merge/start.rb +250 -0
  98. data/lib/git/commands/merge.rb +28 -0
  99. data/lib/git/commands/merge_base.rb +91 -0
  100. data/lib/git/commands/mv.rb +82 -0
  101. data/lib/git/commands/name_rev.rb +119 -0
  102. data/lib/git/commands/pull.rb +382 -0
  103. data/lib/git/commands/push.rb +251 -0
  104. data/lib/git/commands/read_tree.rb +154 -0
  105. data/lib/git/commands/remote/add.rb +96 -0
  106. data/lib/git/commands/remote/get_url.rb +68 -0
  107. data/lib/git/commands/remote/list.rb +56 -0
  108. data/lib/git/commands/remote/prune.rb +63 -0
  109. data/lib/git/commands/remote/remove.rb +52 -0
  110. data/lib/git/commands/remote/rename.rb +76 -0
  111. data/lib/git/commands/remote/set_branches.rb +70 -0
  112. data/lib/git/commands/remote/set_head.rb +89 -0
  113. data/lib/git/commands/remote/set_url.rb +78 -0
  114. data/lib/git/commands/remote/set_url_add.rb +70 -0
  115. data/lib/git/commands/remote/set_url_delete.rb +71 -0
  116. data/lib/git/commands/remote/show.rb +77 -0
  117. data/lib/git/commands/remote/update.rb +79 -0
  118. data/lib/git/commands/remote.rb +42 -0
  119. data/lib/git/commands/repack.rb +281 -0
  120. data/lib/git/commands/reset.rb +154 -0
  121. data/lib/git/commands/rev_parse.rb +304 -0
  122. data/lib/git/commands/revert/abort.rb +45 -0
  123. data/lib/git/commands/revert/continue.rb +62 -0
  124. data/lib/git/commands/revert/quit.rb +47 -0
  125. data/lib/git/commands/revert/skip.rb +44 -0
  126. data/lib/git/commands/revert/start.rb +158 -0
  127. data/lib/git/commands/revert.rb +29 -0
  128. data/lib/git/commands/rm.rb +113 -0
  129. data/lib/git/commands/show.rb +632 -0
  130. data/lib/git/commands/show_ref/exclude_existing.rb +119 -0
  131. data/lib/git/commands/show_ref/exists.rb +80 -0
  132. data/lib/git/commands/show_ref/list.rb +149 -0
  133. data/lib/git/commands/show_ref/verify.rb +122 -0
  134. data/lib/git/commands/show_ref.rb +42 -0
  135. data/lib/git/commands/stash/apply.rb +81 -0
  136. data/lib/git/commands/stash/branch.rb +67 -0
  137. data/lib/git/commands/stash/clear.rb +43 -0
  138. data/lib/git/commands/stash/create.rb +60 -0
  139. data/lib/git/commands/stash/drop.rb +73 -0
  140. data/lib/git/commands/stash/list.rb +43 -0
  141. data/lib/git/commands/stash/pop.rb +87 -0
  142. data/lib/git/commands/stash/push.rb +112 -0
  143. data/lib/git/commands/stash/show.rb +158 -0
  144. data/lib/git/commands/stash/store.rb +72 -0
  145. data/lib/git/commands/stash.rb +38 -0
  146. data/lib/git/commands/status.rb +174 -0
  147. data/lib/git/commands/symbolic_ref/delete.rb +72 -0
  148. data/lib/git/commands/symbolic_ref/read.rb +99 -0
  149. data/lib/git/commands/symbolic_ref/update.rb +79 -0
  150. data/lib/git/commands/symbolic_ref.rb +38 -0
  151. data/lib/git/commands/tag/create.rb +142 -0
  152. data/lib/git/commands/tag/delete.rb +57 -0
  153. data/lib/git/commands/tag/list.rb +146 -0
  154. data/lib/git/commands/tag/verify.rb +71 -0
  155. data/lib/git/commands/tag.rb +26 -0
  156. data/lib/git/commands/update_ref/batch.rb +145 -0
  157. data/lib/git/commands/update_ref/delete.rb +90 -0
  158. data/lib/git/commands/update_ref/update.rb +103 -0
  159. data/lib/git/commands/update_ref.rb +42 -0
  160. data/lib/git/commands/version.rb +60 -0
  161. data/lib/git/commands/worktree/add.rb +139 -0
  162. data/lib/git/commands/worktree/list.rb +64 -0
  163. data/lib/git/commands/worktree/lock.rb +58 -0
  164. data/lib/git/commands/worktree/management_base.rb +51 -0
  165. data/lib/git/commands/worktree/move.rb +66 -0
  166. data/lib/git/commands/worktree/prune.rb +67 -0
  167. data/lib/git/commands/worktree/remove.rb +63 -0
  168. data/lib/git/commands/worktree/repair.rb +76 -0
  169. data/lib/git/commands/worktree/unlock.rb +47 -0
  170. data/lib/git/commands/worktree.rb +43 -0
  171. data/lib/git/commands/write_tree.rb +68 -0
  172. data/lib/git/commands.rb +88 -0
  173. data/lib/git/config.rb +72 -5
  174. data/lib/git/config_entry_info.rb +106 -0
  175. data/lib/git/configuring.rb +795 -0
  176. data/lib/git/detached_head_info.rb +57 -0
  177. data/lib/git/diff.rb +437 -86
  178. data/lib/git/diff_file_numstat_info.rb +31 -0
  179. data/lib/git/diff_file_patch_info.rb +136 -0
  180. data/lib/git/diff_file_raw_info.rb +129 -0
  181. data/lib/git/diff_info.rb +162 -0
  182. data/lib/git/diff_path_status.rb +107 -0
  183. data/lib/git/diff_result.rb +34 -0
  184. data/lib/git/diff_stats.rb +111 -0
  185. data/lib/git/dirstat_info.rb +102 -0
  186. data/lib/git/encoding_utils.rb +32 -1
  187. data/lib/git/errors.rb +285 -0
  188. data/lib/git/escaped_path.rb +57 -5
  189. data/lib/git/execution_context/global.rb +31 -0
  190. data/lib/git/execution_context/repository.rb +151 -0
  191. data/lib/git/execution_context.rb +559 -0
  192. data/lib/git/factories.rb +813 -0
  193. data/lib/git/file_ref.rb +77 -0
  194. data/lib/git/fsck_object.rb +56 -0
  195. data/lib/git/fsck_result.rb +132 -0
  196. data/lib/git/log.rb +306 -90
  197. data/lib/git/object.rb +563 -141
  198. data/lib/git/parsers/branch.rb +240 -0
  199. data/lib/git/parsers/cat_file.rb +111 -0
  200. data/lib/git/parsers/config_entry.rb +110 -0
  201. data/lib/git/parsers/diff.rb +792 -0
  202. data/lib/git/parsers/fsck.rb +144 -0
  203. data/lib/git/parsers/grep.rb +42 -0
  204. data/lib/git/parsers/ls_remote.rb +79 -0
  205. data/lib/git/parsers/ls_tree.rb +58 -0
  206. data/lib/git/parsers/remote.rb +162 -0
  207. data/lib/git/parsers/stash.rb +292 -0
  208. data/lib/git/parsers/status.rb +251 -0
  209. data/lib/git/parsers/tag.rb +341 -0
  210. data/lib/git/parsers/worktree.rb +185 -0
  211. data/lib/git/path_resolver.rb +206 -0
  212. data/lib/git/remote.rb +165 -12
  213. data/lib/git/remote_info.rb +203 -0
  214. data/lib/git/repository/branching.rb +964 -0
  215. data/lib/git/repository/committing.rb +246 -0
  216. data/lib/git/repository/context_helpers.rb +293 -0
  217. data/lib/git/repository/diffing.rb +785 -0
  218. data/lib/git/repository/inspecting.rb +252 -0
  219. data/lib/git/repository/logging.rb +410 -0
  220. data/lib/git/repository/maintenance.rb +65 -0
  221. data/lib/git/repository/merging.rb +451 -0
  222. data/lib/git/repository/object_operations.rb +1551 -0
  223. data/lib/git/repository/remote_operations.rb +984 -0
  224. data/lib/git/repository/shared_private.rb +120 -0
  225. data/lib/git/repository/staging.rb +587 -0
  226. data/lib/git/repository/stashing.rb +623 -0
  227. data/lib/git/repository/status_operations.rb +249 -0
  228. data/lib/git/repository/worktree_operations.rb +339 -0
  229. data/lib/git/repository.rb +484 -2
  230. data/lib/git/stash.rb +109 -12
  231. data/lib/git/stash_info.rb +102 -0
  232. data/lib/git/stashes.rb +169 -26
  233. data/lib/git/status.rb +308 -122
  234. data/lib/git/status_file_info.rb +258 -0
  235. data/lib/git/status_info.rb +189 -0
  236. data/lib/git/tag_delete_failure.rb +34 -0
  237. data/lib/git/tag_delete_result.rb +66 -0
  238. data/lib/git/tag_info.rb +99 -0
  239. data/lib/git/url.rb +15 -8
  240. data/lib/git/version.rb +113 -2
  241. data/lib/git/version_constraint.rb +85 -0
  242. data/lib/git/worktree.rb +150 -8
  243. data/lib/git/worktree_info.rb +128 -0
  244. data/lib/git/worktrees.rb +118 -13
  245. data/lib/git.rb +632 -234
  246. metadata +369 -54
  247. data/.github/stale.yml +0 -25
  248. data/.github/workflows/continuous_integration.yml +0 -49
  249. data/.gitignore +0 -10
  250. data/Dockerfile.changelog-rs +0 -12
  251. data/Gemfile +0 -5
  252. data/ISSUE_TEMPLATE.md +0 -15
  253. data/PULL_REQUEST_TEMPLATE.md +0 -9
  254. data/RELEASING.md +0 -70
  255. data/Rakefile +0 -60
  256. data/lib/git/base/factory.rb +0 -99
  257. data/lib/git/base.rb +0 -711
  258. data/lib/git/command_line_result.rb +0 -86
  259. data/lib/git/failed_error.rb +0 -53
  260. data/lib/git/git_execute_error.rb +0 -7
  261. data/lib/git/index.rb +0 -5
  262. data/lib/git/lib.rb +0 -1328
  263. data/lib/git/path.rb +0 -31
  264. data/lib/git/signaled_error.rb +0 -50
  265. data/lib/git/working_directory.rb +0 -4
@@ -0,0 +1,252 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/commands/describe'
4
+ require 'git/commands/fsck'
5
+ require 'git/commands/show'
6
+ require 'git/parsers/fsck'
7
+ require 'git/repository/shared_private'
8
+
9
+ module Git
10
+ class Repository
11
+ # Facade methods for read-only repository inspection operations
12
+ #
13
+ # These methods report on the contents and integrity of the repository.
14
+ #
15
+ # Included by {Git::Repository}.
16
+ #
17
+ # @api private
18
+ #
19
+ module Inspecting
20
+ # Give a human-readable name to a commit based on the most recent reachable tag
21
+ #
22
+ # Runs `git describe` to find the nearest tag reachable from `committish` and
23
+ # formats a version string. When the tag points directly at the commit, only the
24
+ # tag name is shown. Otherwise, the tag name is suffixed with the number of
25
+ # additional commits and the abbreviated commit SHA (e.g. `v1.0.0-3-gabcdef1`).
26
+ #
27
+ # @example Describe HEAD
28
+ # repo.describe #=> "v1.0.0"
29
+ #
30
+ # @example Describe a specific commit
31
+ # repo.describe('abc123') #=> "v1.0.0-3-gabcdef1"
32
+ #
33
+ # @example Describe using any tag (not just annotated tags)
34
+ # repo.describe(nil, tags: true) #=> "v1.0.0-lightweight"
35
+ #
36
+ # @example Require an exact tag match
37
+ # repo.describe(nil, exact_match: true)
38
+ #
39
+ # @example Use the legacy hyphenated key (still accepted)
40
+ # repo.describe(nil, :'exact-match' => true)
41
+ #
42
+ # @param committish [String, nil] the commit-ish to describe; defaults to HEAD
43
+ # when `nil`
44
+ #
45
+ # @param opts [Hash] options forwarded to `git describe`
46
+ #
47
+ # @option opts [Boolean, nil] :all (nil) use any ref in `refs/`, not just tags
48
+ #
49
+ # @option opts [Boolean, nil] :tags (nil) use lightweight tags as well as
50
+ # annotated ones
51
+ #
52
+ # @option opts [Boolean, nil] :contains (nil) describe the tag that contains the
53
+ # commit, rather than the nearest reachable one
54
+ #
55
+ # @option opts [Boolean, String, nil] :abbrev (nil) number of hex digits for the
56
+ # abbreviated object name; `true` uses git's default length
57
+ #
58
+ # @option opts [Boolean, String, nil] :dirty (nil) append a dirty-state mark to
59
+ # the description; `true` appends `-dirty`, a String appends that string
60
+ #
61
+ # @option opts [Boolean, String, nil] :broken (nil) like `:dirty` but treats
62
+ # broken repository links as dirty
63
+ #
64
+ # @option opts [Integer, String, nil] :candidates (nil) number of candidate tags
65
+ # to consider; increasing above 10 may yield a more accurate result
66
+ #
67
+ # @option opts [Boolean, nil] :exact_match (nil) only succeed when the commit is
68
+ # pointed to by a tag directly (no suffix)
69
+ #
70
+ # The legacy hyphenated key `:"exact-match"` is also accepted and is
71
+ # automatically translated to `:exact_match`.
72
+ #
73
+ # @option opts [Boolean, nil] :debug (nil) verbosely display the search strategy
74
+ #
75
+ # @option opts [Boolean, nil] :long (nil) always output the long format even when
76
+ # the commit matches a tag exactly
77
+ #
78
+ # @option opts [String, Array<String>, nil] :match (nil) only consider tags
79
+ # matching the given `glob(7)` pattern; pass an array for multiple patterns
80
+ #
81
+ # @option opts [String, Array<String>, nil] :exclude (nil) do not consider tags
82
+ # matching the given `glob(7)` pattern; pass an array for multiple patterns
83
+ #
84
+ # @option opts [Boolean, nil] :always (nil) show the abbreviated commit SHA as
85
+ # fallback when the commit cannot be described
86
+ #
87
+ # @option opts [Boolean, nil] :first_parent (nil) follow only the first parent of
88
+ # merge commits when searching for the nearest tag
89
+ #
90
+ # @return [String] the human-readable description of the commit, with trailing
91
+ # newlines preserved
92
+ #
93
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
94
+ #
95
+ # @raise [ArgumentError] when `committish` looks like a command-line flag (starts
96
+ # with `-`), or when `opts` contains any key not in the documented option list
97
+ #
98
+ def describe(committish = nil, opts = {})
99
+ raise ArgumentError, "Invalid commit-ish object: '#{committish}'" if committish&.start_with?('-')
100
+
101
+ opts = opts.dup
102
+ if opts.key?(:'exact-match')
103
+ opts[:exact_match] ||= opts[:'exact-match']
104
+ opts.delete(:'exact-match')
105
+ end
106
+ SharedPrivate.assert_valid_opts!(DESCRIBE_ALLOWED_OPTS, **opts)
107
+ commit_ishes = Array(committish).compact
108
+ Git::Commands::Describe.new(@execution_context).call(*commit_ishes, **opts).stdout
109
+ end
110
+
111
+ # Show a single git object (a commit, tag, tree, or blob)
112
+ #
113
+ # @example Show the HEAD commit
114
+ # repo.show
115
+ #
116
+ # @example Show a specific commit
117
+ # repo.show('HEAD~1')
118
+ #
119
+ # @example Show the contents of a file at a revision
120
+ # repo.show('HEAD', 'README.md')
121
+ #
122
+ # @param objectish [String, nil] the object to show; a ref, SHA, or
123
+ # `objectish:path` expression
124
+ #
125
+ # Defaults to `HEAD` when `nil`.
126
+ #
127
+ # @param path [String, nil] the file whose contents to show at `objectish`,
128
+ # when given
129
+ #
130
+ # Combined with `objectish` as `objectish:path`. When `objectish` is `nil`
131
+ # and `path` is given, `HEAD` is used as the objectish, so
132
+ # `show(nil, 'README.md')` resolves to `HEAD:README.md`.
133
+ #
134
+ # @return [String] git's stdout from the show, with trailing newlines
135
+ # preserved
136
+ #
137
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
138
+ #
139
+ def show(objectish = nil, path = nil)
140
+ object = path ? "#{objectish || 'HEAD'}:#{path}" : objectish
141
+ Git::Commands::Show.new(@execution_context).call(*[object].compact).stdout
142
+ end
143
+
144
+ # Option keys accepted by {#describe}
145
+ DESCRIBE_ALLOWED_OPTS = %i[
146
+ all tags contains abbrev dirty broken candidates
147
+ exact_match debug long match exclude always first_parent
148
+ ].freeze
149
+ private_constant :DESCRIBE_ALLOWED_OPTS
150
+
151
+ # Option keys accepted by {#fsck}
152
+ #
153
+ # `:progress`/`:no_progress` are intentionally excluded: progress output is
154
+ # always suppressed (see {#fsck}), so callers may not toggle it.
155
+ FSCK_ALLOWED_OPTS = %i[
156
+ tags root unreachable cache no_reflogs
157
+ full no_full strict verbose lost_found dangling no_dangling
158
+ connectivity_only name_objects no_name_objects references no_references
159
+ ].freeze
160
+ private_constant :FSCK_ALLOWED_OPTS
161
+
162
+ # Verify the connectivity and validity of the objects in the database
163
+ #
164
+ # Runs `git fsck` and returns the categorized objects it flags. Progress
165
+ # output is always suppressed (`--no-progress`) so that stdout contains only
166
+ # the machine-parsable findings.
167
+ #
168
+ # @overload fsck(*objects, **options)
169
+ #
170
+ # @example Check repository integrity
171
+ # result = repo.fsck
172
+ # result.dangling.each { |obj| puts "#{obj.type}: #{obj.oid}" }
173
+ #
174
+ # @example Check if the repository is clean
175
+ # repo.fsck.empty? #=> true
176
+ #
177
+ # @example List root commits
178
+ # repo.fsck(root: true).root.each { |obj| puts obj.oid }
179
+ #
180
+ # @example Check specific objects
181
+ # repo.fsck('abc1234', 'def5678')
182
+ #
183
+ # @param objects [Array<String>] specific objects to treat as heads for the
184
+ # unreachability trace
185
+ #
186
+ # When none are given, git fsck defaults to the index file, all refs, and
187
+ # all reflogs.
188
+ #
189
+ # @param options [Hash] options for the fsck command
190
+ #
191
+ # @option options [Boolean, nil] :tags (nil) report tags
192
+ #
193
+ # @option options [Boolean, nil] :root (nil) report root nodes
194
+ #
195
+ # @option options [Boolean, nil] :unreachable (nil) print objects that exist
196
+ # but are not reachable from any reference node
197
+ #
198
+ # @option options [Boolean, nil] :cache (nil) consider objects recorded in the
199
+ # index as head nodes for reachability
200
+ #
201
+ # @option options [Boolean, nil] :no_reflogs (nil) do not consider commits
202
+ # referenced only by reflogs to be reachable
203
+ #
204
+ # @option options [Boolean, nil] :full (nil) also check alternate object
205
+ # pools and packed archives, not just the local store
206
+ #
207
+ # @option options [Boolean, nil] :no_full (nil) skip alternate object pools and
208
+ # packed archives
209
+ #
210
+ # @option options [Boolean, nil] :strict (nil) enable stricter checking
211
+ #
212
+ # @option options [Boolean, nil] :verbose (nil) be chatty
213
+ #
214
+ # @option options [Boolean, nil] :lost_found (nil) write dangling objects
215
+ # into `.git/lost-found`
216
+ #
217
+ # This modifies the repository by creating files.
218
+ #
219
+ # @option options [Boolean, nil] :dangling (nil) print dangling objects
220
+ #
221
+ # @option options [Boolean, nil] :no_dangling (nil) suppress dangling object
222
+ # reporting
223
+ #
224
+ # @option options [Boolean, nil] :connectivity_only (nil) check only
225
+ # connectivity; faster but does not validate blob content
226
+ #
227
+ # @option options [Boolean, nil] :name_objects (nil) show the name of each
228
+ # reachable object alongside its identifier
229
+ #
230
+ # @option options [Boolean, nil] :no_name_objects (nil) suppress object name
231
+ # display
232
+ #
233
+ # @option options [Boolean, nil] :references (nil) check reference database
234
+ # consistency
235
+ #
236
+ # @option options [Boolean, nil] :no_references (nil) skip reference checking
237
+ #
238
+ # @return [Git::FsckResult] the objects flagged by fsck, categorized by status
239
+ #
240
+ # @raise [ArgumentError] when unsupported options are provided
241
+ #
242
+ # @raise [Git::FailedError] when git exits outside the allowed range (exit
243
+ # code > 7)
244
+ #
245
+ def fsck(*objects, **)
246
+ SharedPrivate.assert_valid_opts!(FSCK_ALLOWED_OPTS, **)
247
+ result = Git::Commands::Fsck.new(@execution_context).call(*objects, **, no_progress: true)
248
+ Git::Parsers::Fsck.parse(result.stdout)
249
+ end
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,410 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/commands/log'
4
+ require 'git/log'
5
+ require 'git/repository/shared_private'
6
+
7
+ module Git
8
+ class Repository
9
+ # Facade methods for querying commit history
10
+ #
11
+ # Included by {Git::Repository}.
12
+ #
13
+ # @api private
14
+ #
15
+ module Logging
16
+ # Allowed option keys for {#full_log_commits}
17
+ #
18
+ # @return [Array<Symbol>] the supported option keys
19
+ #
20
+ FULL_LOG_COMMITS_ALLOWED_OPTS = %i[
21
+ count all cherry since until grep author between object path_limiter skip merges perl_regexp
22
+ ].freeze
23
+ private_constant :FULL_LOG_COMMITS_ALLOWED_OPTS
24
+
25
+ # Returns commits within the given revision range
26
+ #
27
+ # @example Return commits from all refs
28
+ # repo.full_log_commits(all: true).first['sha']
29
+ # #=> "a1b2c3d4..."
30
+ #
31
+ # @example Return commits between two revisions
32
+ # repo.full_log_commits(between: ['v1.0.0', 'HEAD']).map { |c| c['sha'] }
33
+ # #=> ["d4e5f6...", "a1b2c3..."]
34
+ #
35
+ # @param opts [Hash] options for the log query
36
+ #
37
+ # @option opts [Integer, nil] :count (nil) maximum number of commits to return
38
+ #
39
+ # @option opts [Boolean, nil] :all (nil) include commits reachable from any ref
40
+ #
41
+ # @option opts [Boolean, nil] :cherry (nil) omit commits equivalent to
42
+ # cherry-picked commits
43
+ #
44
+ # @option opts [String] :since (nil) include commits newer than this date expression
45
+ #
46
+ # @option opts [String] :until (nil) include commits older than this date expression
47
+ #
48
+ # @option opts [String] :grep (nil) only include commits whose message matches
49
+ # this pattern
50
+ #
51
+ # @option opts [String] :author (nil) only include commits whose author matches
52
+ # this pattern
53
+ #
54
+ # @option opts [Array(String, String), nil] :between (nil) revision range as
55
+ # two commit-ish values
56
+ #
57
+ # When both `:between` and `:object` are provided, `:between` takes precedence.
58
+ #
59
+ # @option opts [String] :object (nil) single revision range expression for
60
+ # `git log`
61
+ #
62
+ # Ignored when `:between` is provided.
63
+ #
64
+ # @option opts [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
65
+ # only include commits that impact files from the specified path(s)
66
+ #
67
+ # @option opts [Integer, nil] :skip (nil) skip this many commits before output
68
+ #
69
+ # @option opts [Boolean, nil] :merges (nil) include only merge commits
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
+ #
78
+ # @return [Array<Hash>] the parsed raw log output for each commit
79
+ #
80
+ # @raise [ArgumentError] if unsupported options are provided
81
+ #
82
+ # @raise [ArgumentError] if `:count` is not an Integer
83
+ #
84
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
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
+ #
94
+ # @see https://git-scm.com/docs/git-log git-log
95
+ #
96
+ def full_log_commits(opts = {})
97
+ SharedPrivate.assert_valid_opts!(FULL_LOG_COMMITS_ALLOWED_OPTS, **opts)
98
+ Private.validate_log_count_option!(opts)
99
+ Private.validate_log_between_option!(opts)
100
+
101
+ call_opts = Private.log_base_call_options(opts, skip: opts[:skip], merges: opts[:merges])
102
+ revision_range_args = Private.log_revision_range_args(opts)
103
+ Private.run_log_command(@execution_context, revision_range_args, call_opts)
104
+ end
105
+
106
+ # Returns a new {Git::Log} query builder scoped to this repository
107
+ #
108
+ # @example Build a log query and execute it
109
+ # results = repo.log(50).author('Alice').since('2 weeks ago').execute
110
+ # results.each { |commit| puts commit.sha }
111
+ #
112
+ # @param count [Integer, Symbol, nil] the maximum number of commits to return,
113
+ # or `:all` / `nil` to return all commits; passed directly to {Git::Log#initialize}
114
+ #
115
+ # @return [Git::Log] a new log query builder
116
+ #
117
+ # @see Git::Log
118
+ #
119
+ def log(count = 30)
120
+ Git::Log.new(self, count)
121
+ end
122
+
123
+ # Internal helpers for {Logging} that should not be mixed into
124
+ # {Git::Repository} instances
125
+ #
126
+ # @api private
127
+ #
128
+ module Private
129
+ module_function
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
+
139
+ # Validates the :count log option
140
+ #
141
+ # @param opts [Hash] the log options
142
+ #
143
+ # @option opts [Integer, nil] :count (nil) the maximum number of commits to
144
+ # return
145
+ #
146
+ # @return [void]
147
+ #
148
+ # @raise [ArgumentError] if the log count option is not an Integer
149
+ #
150
+ def validate_log_count_option!(opts)
151
+ return if opts[:count].nil? || opts[:count].is_a?(Integer)
152
+
153
+ raise ArgumentError, "The log count option must be an Integer but was #{opts[:count].inspect}"
154
+ end
155
+
156
+ # Validates the :between log option
157
+ #
158
+ # @param opts [Hash] the log options
159
+ #
160
+ # @option opts [Array(String, String), nil] :between (nil) the two-commit
161
+ # revision range to validate
162
+ #
163
+ # @return [void]
164
+ #
165
+ # @raise [ArgumentError] if the :between option is not an Array with exactly
166
+ # two non-nil values
167
+ #
168
+ def validate_log_between_option!(opts)
169
+ between = opts[:between]
170
+ return if between.nil?
171
+ return if between.is_a?(Array) && between.length == 2 && between.none?(&:nil?)
172
+
173
+ raise ArgumentError,
174
+ "The log between option must be an Array with exactly two non-nil values but was #{between.inspect}"
175
+ end
176
+
177
+ # Builds positional revision arguments for `git log`
178
+ #
179
+ # @param opts [Hash] the log options
180
+ #
181
+ # @option opts [Array(String, String), nil] :between (nil) a two-revision
182
+ # range where index `0` is the start and index `1` is the end
183
+ #
184
+ # @option opts [String, nil] :object (nil) a single revision range expression
185
+ #
186
+ # @return [Array<String>] zero or one positional revision arguments
187
+ #
188
+ def log_revision_range_args(opts)
189
+ if opts[:between]
190
+ ["#{opts[:between][0]}..#{opts[:between][1]}"]
191
+ elsif opts[:object].is_a?(String)
192
+ [opts[:object]]
193
+ else
194
+ []
195
+ end
196
+ end
197
+
198
+ # Builds keyword options passed to {Git::Commands::Log#call}
199
+ #
200
+ # @param opts [Hash] the log options
201
+ #
202
+ # @param extra [Hash] additional keyword options merged into the call options
203
+ #
204
+ # @option opts [Boolean, nil] :all (nil) include commits reachable from any ref
205
+ #
206
+ # @option opts [Boolean, nil] :cherry (nil) omit commits equivalent to
207
+ # cherry-picked commits
208
+ #
209
+ # @option opts [String, nil] :since (nil) include commits newer than this date
210
+ # expression
211
+ #
212
+ # @option opts [String, nil] :until (nil) include commits older than this date
213
+ # expression
214
+ #
215
+ # @option opts [String, nil] :grep (nil) only include commits whose message
216
+ # matches this pattern
217
+ #
218
+ # @option opts [String, nil] :author (nil) only include commits whose author
219
+ # matches this pattern
220
+ #
221
+ # @option opts [Boolean, nil] :perl_regexp (nil) interpret the `:grep` and
222
+ # `:author` patterns as Perl-compatible regular expressions
223
+ #
224
+ # @option opts [Integer, nil] :count (nil) maximum number of commits to return
225
+ #
226
+ # @option opts [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
227
+ # only include commits that impact files from the specified path(s)
228
+ #
229
+ # @option extra [Integer, nil] :skip (nil) skip this many commits before output
230
+ #
231
+ # @option extra [Boolean, nil] :merges (nil) include only merge commits
232
+ #
233
+ # @return [Hash] keyword options for {Git::Commands::Log#call}
234
+ #
235
+ def log_base_call_options(opts, extra = {})
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
244
+ end
245
+
246
+ # Executes git log and parses the raw output
247
+ #
248
+ # @param execution_context [Git::ExecutionContext] the execution context
249
+ #
250
+ # @param revision_range_args [Array<String>] positional revision range arguments
251
+ #
252
+ # @param call_opts [Hash] keyword options for {Git::Commands::Log#call}
253
+ #
254
+ # @return [Array<Hash>] parsed commits from the command output
255
+ #
256
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
257
+ #
258
+ def run_log_command(execution_context, revision_range_args, call_opts)
259
+ log_or_empty_on_unborn do
260
+ result = Git::Commands::Log.new(execution_context).call(
261
+ *revision_range_args,
262
+ no_color: true,
263
+ pretty: 'raw',
264
+ **call_opts
265
+ )
266
+ RawLogParser.new(result.stdout.split("\n")).parse
267
+ end
268
+ end
269
+
270
+ # Returns an empty result when the repository has no commits yet
271
+ #
272
+ # @return [Array<Hash>] parsed commits or an empty array for unborn repositories
273
+ #
274
+ # @raise [Git::FailedError] if git fails for a reason other than unborn history
275
+ #
276
+ # @yield [] runs the wrapped log command
277
+ #
278
+ # @yieldreturn [Array<Hash>] the parsed commits from the wrapped command
279
+ #
280
+ def log_or_empty_on_unborn
281
+ yield
282
+ rescue Git::FailedError => e
283
+ raise unless e.result.status.exitstatus == 128 &&
284
+ e.result.stderr =~ /does not have any commits yet/
285
+
286
+ []
287
+ end
288
+
289
+ # Parser for `git log --pretty=raw` output into commit hashes
290
+ #
291
+ # @api private
292
+ #
293
+ class RawLogParser
294
+ # Initializes a parser for raw git log output lines
295
+ #
296
+ # @param lines [Array<String>] raw output lines from `git log --pretty=raw`
297
+ #
298
+ # @return [void]
299
+ #
300
+ def initialize(lines)
301
+ @lines = lines
302
+ @commits = []
303
+ @current_commit = nil
304
+ @in_message = false
305
+ @last_metadata_key = nil
306
+ end
307
+
308
+ # Parse raw `git log --pretty=raw` lines into commit hashes
309
+ #
310
+ # @return [Array<Hash>] the parsed commits in command output order
311
+ #
312
+ def parse
313
+ @lines.each { |line| process_line(line.chomp) }
314
+ finalize_commit
315
+ @commits
316
+ end
317
+
318
+ private
319
+
320
+ # Routes a raw line to message or metadata parsing
321
+ #
322
+ # @param line [String] the current raw log output line
323
+ #
324
+ # @return [void]
325
+ #
326
+ def process_line(line)
327
+ if line.empty?
328
+ @in_message = !@in_message
329
+ return
330
+ end
331
+
332
+ @in_message = false if @in_message && !line.start_with?(' ')
333
+
334
+ @in_message ? process_message_line(line) : process_metadata_line(line)
335
+ end
336
+
337
+ # Appends a commit message line to the current commit buffer
338
+ #
339
+ # @param line [String] an indented message line from raw output
340
+ #
341
+ # @return [void]
342
+ #
343
+ def process_message_line(line)
344
+ @current_commit['message'] << "#{line[4..]}\n"
345
+ end
346
+
347
+ # Parses metadata lines and multi-line metadata continuations
348
+ #
349
+ # @param line [String] a metadata line from raw output
350
+ #
351
+ # @return [void]
352
+ #
353
+ def process_metadata_line(line)
354
+ if line.start_with?(' ') && @last_metadata_key
355
+ @current_commit[@last_metadata_key] << "\n#{line[1..]}"
356
+ return
357
+ end
358
+
359
+ key, *value = line.split
360
+ value = value.join(' ')
361
+ @last_metadata_key = nil
362
+ dispatch_metadata_key(key, value)
363
+ end
364
+
365
+ # Applies a metadata key/value pair to the current commit
366
+ #
367
+ # @param key [String] the metadata key from the raw log line
368
+ #
369
+ # @param value [String] the parsed metadata value
370
+ #
371
+ # @return [void]
372
+ #
373
+ def dispatch_metadata_key(key, value)
374
+ case key
375
+ when 'commit'
376
+ start_new_commit(value)
377
+ when 'parent'
378
+ @current_commit['parent'] << value
379
+ else
380
+ @current_commit[key] = value
381
+ @last_metadata_key = key
382
+ end
383
+ end
384
+
385
+ # Starts a new commit record in the parser state
386
+ #
387
+ # @param sha [String] the commit SHA for the new record
388
+ #
389
+ # @return [void]
390
+ #
391
+ def start_new_commit(sha)
392
+ finalize_commit
393
+ @current_commit = { 'sha' => sha, 'message' => +'', 'parent' => [] }
394
+ @last_metadata_key = nil
395
+ end
396
+
397
+ # Appends the current commit to the parsed results when present
398
+ #
399
+ # @return [void]
400
+ #
401
+ def finalize_commit
402
+ @commits << @current_commit if @current_commit
403
+ end
404
+ end
405
+ private_constant :RawLogParser
406
+ end
407
+ private_constant :Private
408
+ end
409
+ end
410
+ end