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,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Immutable value object representing a reference to a file at a specific point in time
5
+ #
6
+ # FileRef encapsulates the mode, SHA, and path of a file as it exists on one side
7
+ # of a diff. This is used to represent either the source (before) or destination
8
+ # (after) state of a file in a diff operation.
9
+ #
10
+ # When a file doesn't exist on a side of the diff (e.g., src for new files,
11
+ # dst for deleted files), the entire FileRef should be nil rather than having
12
+ # a FileRef with nil attributes.
13
+ #
14
+ #
15
+ # @example A modified file's source reference
16
+ # src = Git::FileRef.new(mode: '100644', sha: 'abc1234', path: 'lib/foo.rb')
17
+ #
18
+ # @example A new file (src would be nil, not a FileRef)
19
+ # # src = nil
20
+ # dst = Git::FileRef.new(mode: '100644', sha: 'def5678', path: 'lib/new_file.rb')
21
+ #
22
+ # @api private
23
+ #
24
+ # Work in progress; this class is internal for now and may be made public in a future release.
25
+ #
26
+ # @!attribute [r] mode
27
+ # @return [String] the file mode (e.g., '100644' for regular file, '100755' for executable,
28
+ # '120000' for symlink)
29
+ #
30
+ # @!attribute [r] sha
31
+ # @return [String] the blob SHA (object identifier)
32
+ #
33
+ # @!attribute [r] path
34
+ # @return [String] the file path relative to repository root
35
+ #
36
+ FileRef = Data.define(:mode, :sha, :path) do
37
+ # Check if this is a regular file (not executable, symlink, etc.)
38
+ #
39
+ # @return [Boolean] true if mode is 100644
40
+ #
41
+ def regular_file?
42
+ mode == '100644'
43
+ end
44
+
45
+ # Check if this is an executable file
46
+ #
47
+ # @return [Boolean] true if mode is 100755
48
+ #
49
+ def executable?
50
+ mode == '100755'
51
+ end
52
+
53
+ # Check if this is a symbolic link
54
+ #
55
+ # @return [Boolean] true if mode is 120000
56
+ #
57
+ def symlink?
58
+ mode == '120000'
59
+ end
60
+
61
+ # Return the mode as an integer (parsed as octal)
62
+ #
63
+ # Useful for bit operations on file permissions.
64
+ #
65
+ # @example Check file permissions
66
+ # ref.mode_bits & 0o777 # => 0o644 (420 decimal)
67
+ #
68
+ # @example Check if group writable
69
+ # (ref.mode_bits & 0o020) != 0
70
+ #
71
+ # @return [Integer] the mode as an integer
72
+ #
73
+ def mode_bits
74
+ mode.to_i(8)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Represents an object returned by `git fsck`
5
+ #
6
+ # This class provides information about dangling, missing, unreachable, or
7
+ # problematic Git objects found during repository integrity checks.
8
+ #
9
+ # @api public
10
+ #
11
+ class FsckObject
12
+ # The type of the Git object
13
+ #
14
+ # @return [Symbol] one of :commit, :tree, :blob, or :tag
15
+ attr_reader :type
16
+
17
+ # The object identifier (OID) of the object
18
+ #
19
+ # @return [String] the 40-character object identifier
20
+ attr_reader :oid
21
+
22
+ # A warning or error message associated with this object
23
+ #
24
+ # @return [String, nil] the message, or nil if no message
25
+ attr_reader :message
26
+
27
+ # A name describing how the object is reachable (from --name-objects)
28
+ #
29
+ # @return [String, nil] the name, or nil if not provided
30
+ attr_reader :name
31
+
32
+ # Create a new FsckObject
33
+ #
34
+ # @param type [Symbol] the object type (:commit, :tree, :blob, or :tag)
35
+ #
36
+ # @param oid [String] the 40-character object identifier
37
+ #
38
+ # @param message [String, nil] optional warning/error message
39
+ #
40
+ # @param name [String, nil] optional name from --name-objects (e.g., "HEAD~2^2:src/")
41
+ #
42
+ def initialize(type:, oid:, message: nil, name: nil)
43
+ @type = type
44
+ @oid = oid
45
+ @message = message
46
+ @name = name
47
+ end
48
+
49
+ # Returns the OID as the string representation
50
+ #
51
+ # @return [String] the object identifier
52
+ def to_s
53
+ oid
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Represents the result of running `git fsck`
5
+ #
6
+ # This class provides structured access to the objects found during a
7
+ # repository integrity check, categorized by their status.
8
+ #
9
+ # @api public
10
+ #
11
+ class FsckResult
12
+ # Objects not referenced by any other object
13
+ #
14
+ # @return [Array<Git::FsckObject>]
15
+ attr_reader :dangling
16
+
17
+ # Objects that are referenced but not present in the repository
18
+ #
19
+ # @return [Array<Git::FsckObject>]
20
+ attr_reader :missing
21
+
22
+ # Objects not reachable from any ref
23
+ #
24
+ # @return [Array<Git::FsckObject>]
25
+ attr_reader :unreachable
26
+
27
+ # Objects with warnings (each includes a message)
28
+ #
29
+ # @return [Array<Git::FsckObject>]
30
+ attr_reader :warnings
31
+
32
+ # Root nodes (commits with no parents) when --root is used
33
+ #
34
+ # @return [Array<Git::FsckObject>]
35
+ attr_reader :root
36
+
37
+ # Tagged objects when --tags is used
38
+ #
39
+ # @return [Array<Git::FsckObject>]
40
+ attr_reader :tagged
41
+
42
+ # rubocop:disable Metrics/ParameterLists
43
+
44
+ # Create a new FsckResult
45
+ #
46
+ # @param dangling [Array<Git::FsckObject>] dangling objects
47
+ #
48
+ # @param missing [Array<Git::FsckObject>] missing objects
49
+ #
50
+ # @param unreachable [Array<Git::FsckObject>] unreachable objects
51
+ #
52
+ # @param warnings [Array<Git::FsckObject>] objects with warnings
53
+ #
54
+ # @param root [Array<Git::FsckObject>] root nodes
55
+ #
56
+ # @param tagged [Array<Git::FsckObject>] tagged objects
57
+ #
58
+ def initialize(dangling: [], missing: [], unreachable: [], warnings: [], root: [], tagged: [])
59
+ @dangling = dangling
60
+ @missing = missing
61
+ @unreachable = unreachable
62
+ @warnings = warnings
63
+ @root = root
64
+ @tagged = tagged
65
+ end
66
+
67
+ # rubocop:enable Metrics/ParameterLists
68
+
69
+ # Returns true if any issues were found
70
+ #
71
+ # @example Check for repository issues
72
+ # result = git.fsck
73
+ # puts "Repository has issues!" if result.any_issues?
74
+ #
75
+ # @return [Boolean]
76
+ #
77
+ def any_issues?
78
+ [dangling, missing, unreachable, warnings].any?(&:any?)
79
+ end
80
+
81
+ # Returns true if no issues were found
82
+ #
83
+ # @example Check if the result is empty
84
+ # result = git.fsck
85
+ # puts "Repository is clean" if result.empty?
86
+ #
87
+ # @return [Boolean]
88
+ #
89
+ def empty?
90
+ !any_issues?
91
+ end
92
+
93
+ # Returns all objects from all categories (excluding informational root/tagged)
94
+ #
95
+ # @example Iterate over all objects
96
+ # result = git.fsck
97
+ # result.all_objects.each { |obj| puts obj.oid }
98
+ #
99
+ # @return [Array<Git::FsckObject>]
100
+ #
101
+ def all_objects
102
+ dangling + missing + unreachable + warnings
103
+ end
104
+
105
+ # Returns the total number of issues found
106
+ #
107
+ # @example Count all issues
108
+ # result = git.fsck
109
+ # puts "Found #{result.count} issues"
110
+ #
111
+ # @return [Integer]
112
+ #
113
+ def count
114
+ all_objects.size
115
+ end
116
+
117
+ # Returns a hash representation of the result
118
+ #
119
+ # @example Convert to a hash
120
+ # result = git.fsck
121
+ # result.to_h # => { dangling: [...], missing: [...], ... }
122
+ #
123
+ # @return [Hash{Symbol => Array<Git::FsckObject>}]
124
+ #
125
+ def to_h
126
+ {
127
+ dangling: dangling, missing: missing, unreachable: unreachable,
128
+ warnings: warnings, root: root, tagged: tagged
129
+ }
130
+ end
131
+ end
132
+ end
data/lib/git/log.rb CHANGED
@@ -1,131 +1,347 @@
1
- module Git
1
+ # frozen_string_literal: true
2
2
 
3
- # object that holds the last X commits on given branch
3
+ module Git
4
+ # Builds and executes a `git log` query
5
+ #
6
+ # This class provides a fluent interface for building complex `git log` queries.
7
+ #
8
+ # Queries default to returning 30 commits; call {#max_count} with `:all` to
9
+ # return every matching commit. Calling {#all} adds the `--all` flag to include
10
+ # all refs in the search but does not change the number of commits returned.
11
+ #
12
+ # The query is lazily executed when results are requested either via the modern
13
+ # `#execute` method or the deprecated Enumerable methods.
14
+ #
15
+ # @example Using the modern `execute` API
16
+ # log = git.log.max_count(50).between('v1.0', 'v1.1').author('Scott')
17
+ # results = log.execute
18
+ # puts "Found #{results.size} commits."
19
+ # results.each { |commit| puts commit.sha }
20
+ #
21
+ # @api public
22
+ #
4
23
  class Log
5
24
  include Enumerable
6
25
 
7
- def initialize(base, count = 30)
8
- dirty_log
9
- @base = base
10
- @count = count
11
- end
26
+ # An immutable, Enumerable collection of `Git::Object::Commit` objects.
27
+ # Returned by `Git::Log#execute`.
28
+ #
29
+ # @api public
30
+ Result = Data.define(:commits) do
31
+ include Enumerable
12
32
 
13
- def all
14
- dirty_log
15
- @all = true
16
- self
17
- end
33
+ # Iterates over each commit in query order
34
+ #
35
+ # @overload each
36
+ # @example Get an enumerator
37
+ # results.each.map(&:sha)
38
+ #
39
+ # @return [Enumerator<Git::Object::Commit>] an enumerator over commits
40
+ #
41
+ # @overload each(&block)
42
+ # @example Iterate with a block
43
+ # results.each { |commit| puts commit.sha }
44
+ #
45
+ # @yield [commit] each commit from the result
46
+ #
47
+ # @yieldparam commit [Git::Object::Commit] a commit in query order
48
+ #
49
+ # @yieldreturn [void]
50
+ #
51
+ # @return [Array<Git::Object::Commit>] the commit array
52
+ #
53
+ def each(&block) = commits.each(&block)
18
54
 
19
- def object(objectish)
20
- dirty_log
21
- @object = objectish
22
- return self
23
- end
55
+ # Returns the last commit in the result
56
+ #
57
+ # @return [Git::Object::Commit, nil] the last commit, or `nil` when empty
58
+ #
59
+ def last = commits.last
24
60
 
25
- def author(regex)
26
- dirty_log
27
- @author = regex
28
- return self
29
- end
61
+ # Returns a commit by index or a slice of commits by range
62
+ #
63
+ # @param index [Integer, Range] the commit index or range to retrieve
64
+ #
65
+ # @return [Git::Object::Commit, Array<Git::Object::Commit>, nil] the selected
66
+ # commit or commits
67
+ #
68
+ def [](index) = commits[index]
30
69
 
31
- def grep(regex)
32
- dirty_log
33
- @grep = regex
34
- return self
35
- end
70
+ # Returns the commits joined with newlines
71
+ #
72
+ # @return [String] newline-separated commits
73
+ #
74
+ def to_s = commits.join("\n")
36
75
 
37
- def path(path)
38
- dirty_log
39
- @path = path
40
- return self
76
+ # Returns the number of commits in the result
77
+ #
78
+ # @return [Integer] the commit count
79
+ #
80
+ def size = commits.size
41
81
  end
42
82
 
43
- def skip(num)
44
- dirty_log
45
- @skip = num
46
- return self
83
+ # Create a new Git::Log object
84
+ #
85
+ # @example
86
+ # git = Git.open('.')
87
+ # Git::Log.new(git)
88
+ #
89
+ # @param base [Git::Repository] the git repository object
90
+ #
91
+ # @param max_count [Integer, Symbol, nil] the number of commits to return, or
92
+ # `:all` or `nil` to return all
93
+ #
94
+ # Passing max_count to {#initialize} is equivalent to calling {#max_count} on the object.
95
+ #
96
+ def initialize(base, max_count = 30)
97
+ @base = base
98
+ @options = {}
99
+ @dirty = true
100
+ self.max_count(max_count)
47
101
  end
48
102
 
49
- def since(date)
50
- dirty_log
51
- @since = date
52
- return self
53
- end
103
+ # Set query options using a fluent interface.
104
+ # Each method returns `self` to allow for chaining.
105
+ #
106
+ # Sets the maximum number of commits to return
107
+ #
108
+ # @param num [Integer, Symbol, nil] the maximum commit count, or `:all` / `nil`
109
+ # for no limit
110
+ #
111
+ # @return [Git::Log] the current query builder
112
+ #
113
+ def max_count(num) = set_option(:count, num == :all ? nil : num)
54
114
 
55
- def until(date)
56
- dirty_log
57
- @until = date
58
- return self
59
- end
115
+ # Includes commits reachable from all refs
116
+ #
117
+ # @return [Git::Log] the current query builder
118
+ #
119
+ def all = set_option(:all, true)
60
120
 
61
- def between(sha1, sha2 = nil)
62
- dirty_log
63
- @between = [sha1, sha2]
64
- return self
65
- end
121
+ # Sets the revision range expression for the log query
122
+ #
123
+ # @param objectish [String] a git revision expression to pass to `git log`
124
+ #
125
+ # @return [Git::Log] the current query builder
126
+ #
127
+ def object(objectish) = set_option(:object, objectish)
66
128
 
67
- def cherry
68
- dirty_log
69
- @cherry = true
70
- return self
71
- end
129
+ # Filters commits by author pattern
130
+ #
131
+ # @param regex [String] a pattern matched against author names
132
+ #
133
+ # @return [Git::Log] the current query builder
134
+ #
135
+ def author(regex) = set_option(:author, regex)
72
136
 
73
- def to_s
74
- self.map { |c| c.to_s }.join("\n")
137
+ # Filters commits by commit message pattern
138
+ #
139
+ # @param regex [String] a pattern matched against commit messages
140
+ #
141
+ # @return [Git::Log] the current query builder
142
+ #
143
+ def grep(regex) = set_option(:grep, regex)
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
+
164
+ # Limits commits to those that touch the given path or paths
165
+ #
166
+ # @param path [String, Pathname, Array<String, Pathname>] path limiter input
167
+ #
168
+ # @return [Git::Log] the current query builder
169
+ #
170
+ def path(path) = set_option(:path_limiter, path)
171
+
172
+ # Skips a number of commits before returning results
173
+ #
174
+ # @param num [Integer] the number of commits to skip
175
+ #
176
+ # @return [Git::Log] the current query builder
177
+ #
178
+ def skip(num) = set_option(:skip, num)
179
+
180
+ # Includes only commits newer than the given date expression
181
+ #
182
+ # @param date [String] a git-compatible date expression
183
+ #
184
+ # @return [Git::Log] the current query builder
185
+ #
186
+ def since(date) = set_option(:since, date)
187
+
188
+ # Includes only commits older than the given date expression
189
+ #
190
+ # @param date [String] a git-compatible date expression
191
+ #
192
+ # @return [Git::Log] the current query builder
193
+ #
194
+ def until(date) = set_option(:until, date)
195
+
196
+ # Limits commits to the given revision range
197
+ #
198
+ # @param val1 [String] the first revision
199
+ #
200
+ # @param val2 [String, nil] the second revision; when `nil`, validation fails
201
+ # at execution time
202
+ #
203
+ # @return [Git::Log] the current query builder
204
+ #
205
+ def between(val1, val2 = nil) = set_option(:between, [val1, val2])
206
+
207
+ # Omits commits equivalent to cherry-picked commits
208
+ #
209
+ # @return [Git::Log] the current query builder
210
+ #
211
+ def cherry = set_option(:cherry, true)
212
+
213
+ # Includes only merge commits
214
+ #
215
+ # @return [Git::Log] the current query builder
216
+ #
217
+ def merges = set_option(:merges, true)
218
+
219
+ # Executes the git log command and returns an immutable result object
220
+ #
221
+ # This is the preferred way to get log data. It separates the query
222
+ # building from the execution, making the API more predictable.
223
+ #
224
+ # @example
225
+ # query = g.log.since('2 weeks ago').author('Scott')
226
+ # results = query.execute
227
+ # puts "Found #{results.size} commits"
228
+ # results.each do |commit|
229
+ # # ...
230
+ # end
231
+ #
232
+ # @return [Git::Log::Result] an object containing the log results
233
+ #
234
+ def execute
235
+ run_log_if_dirty
236
+ Result.new(@commits)
75
237
  end
76
238
 
239
+ # @!group Deprecated Enumerable Interface
77
240
 
78
- # forces git log to run
241
+ # @deprecated Use {#execute} and call `each` on the result.
242
+ def each(&)
243
+ Git::Deprecation.warn(
244
+ 'Calling Git::Log#each is deprecated and will be removed in v6.0.0. ' \
245
+ 'Call #execute and then #each on the result object.'
246
+ )
247
+ run_log_if_dirty
248
+ @commits.each(&)
249
+ end
79
250
 
251
+ # @deprecated Use {#execute} and call `size` on the result.
80
252
  def size
81
- check_log
82
- @commits.size rescue nil
253
+ Git::Deprecation.warn(
254
+ 'Calling Git::Log#size is deprecated and will be removed in v6.0.0. ' \
255
+ 'Call #execute and then #size on the result object.'
256
+ )
257
+ run_log_if_dirty
258
+ @commits.size
83
259
  end
84
260
 
85
- def each(&block)
86
- check_log
87
- @commits.each(&block)
261
+ # @deprecated Use {#execute} and call `to_s` on the result.
262
+ def to_s
263
+ Git::Deprecation.warn(
264
+ 'Calling Git::Log#to_s is deprecated and will be removed in v6.0.0. ' \
265
+ 'Call #execute and then #to_s on the result object.'
266
+ )
267
+ run_log_if_dirty
268
+ @commits.join("\n")
88
269
  end
89
270
 
271
+ # @deprecated Use {#execute} and call the method on the result.
90
272
  def first
91
- check_log
92
- @commits.first rescue nil
273
+ Git::Deprecation.warn(
274
+ 'Calling Git::Log#first is deprecated and will be removed in v6.0.0. ' \
275
+ 'Call #execute and then #first on the result object.'
276
+ )
277
+ run_log_if_dirty
278
+ @commits.first
93
279
  end
94
280
 
281
+ # @deprecated Use {#execute} and call the method on the result.
95
282
  def last
96
- check_log
97
- @commits.last rescue nil
283
+ Git::Deprecation.warn(
284
+ 'Calling Git::Log#last is deprecated and will be removed in v6.0.0. ' \
285
+ 'Call #execute and then #last on the result object.'
286
+ )
287
+ run_log_if_dirty
288
+ @commits.last
98
289
  end
99
290
 
291
+ # @param index [Integer, Range] the commit index or range to retrieve
292
+ #
293
+ # @return [Git::Object::Commit, Array<Git::Object::Commit>, nil] the selected
294
+ # commit or commits
295
+ #
296
+ # @deprecated Use {#execute} and call the method on the result.
297
+ #
100
298
  def [](index)
101
- check_log
102
- @commits[index] rescue nil
299
+ Git::Deprecation.warn(
300
+ 'Calling Git::Log#[] is deprecated and will be removed in v6.0.0. ' \
301
+ 'Call #execute and then #[] on the result object.'
302
+ )
303
+ run_log_if_dirty
304
+ @commits[index]
103
305
  end
104
306
 
307
+ # @!endgroup
105
308
 
106
309
  private
107
310
 
108
- def dirty_log
109
- @dirty_flag = true
110
- end
111
-
112
- def check_log
113
- if @dirty_flag
114
- run_log
115
- @dirty_flag = false
116
- end
117
- end
118
-
119
- # actually run the 'git log' command
120
- def run_log
121
- log = @base.lib.full_log_commits(
122
- count: @count, all: @all, object: @object, path_limiter: @path, since: @since,
123
- author: @author, grep: @grep, skip: @skip, until: @until, between: @between,
124
- cherry: @cherry
125
- )
126
- @commits = log.map { |c| Git::Object::Commit.new(@base, c['sha'], c) }
127
- end
311
+ # Sets a log query option and marks cached results dirty
312
+ #
313
+ # @param key [Symbol] the option key
314
+ #
315
+ # @param value [Object] the option value
316
+ #
317
+ # @return [Git::Log] the current query builder
318
+ #
319
+ def set_option(key, value)
320
+ @dirty = true
321
+ @options[key] = value
322
+ self
323
+ end
128
324
 
129
- end
325
+ # @return [Git::Repository]
326
+ #
327
+ def log_repository
328
+ @base
329
+ end
330
+
331
+ # Refreshes cached commits when query options have changed
332
+ #
333
+ # @return [void]
334
+ #
335
+ # @raise [ArgumentError] if configured query options are invalid
336
+ #
337
+ # @raise [Git::FailedError] if the underlying `git log` command fails
338
+ #
339
+ def run_log_if_dirty
340
+ return unless @dirty
130
341
 
342
+ log_data = log_repository.full_log_commits(@options)
343
+ @commits = log_data.map { |c| Git::Object::Commit.new(@base, c['sha'], c) }
344
+ @dirty = false
345
+ end
346
+ end
131
347
  end