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,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Immutable value object representing a single directory's contribution to a diff
5
+ #
6
+ # @example Create a DirstatEntry
7
+ # info = Git::DirstatEntry.new(directory: 'lib/commands/', percentage: 45.2)
8
+ # info.directory #=> "lib/commands/"
9
+ # info.percentage #=> 45.2
10
+ #
11
+ # @api private
12
+ #
13
+ # Work in progress; this class is internal for now and may be made public in a future release.
14
+ #
15
+ # @!attribute [r] directory
16
+ # @return [String] the directory path (always ends with '/')
17
+ #
18
+ # @!attribute [r] percentage
19
+ # @return [Float] the percentage of changes in this directory (0.0-100.0)
20
+ #
21
+ DirstatEntry = Data.define(:directory, :percentage)
22
+
23
+ # Immutable result object from git --dirstat output
24
+ #
25
+ # Contains the list of directories and their contribution percentages to the diff.
26
+ #
27
+ # @example Create a DirstatInfo
28
+ # dirstat = Git::DirstatInfo.new(
29
+ # entries: [
30
+ # Git::DirstatEntry.new(directory: 'lib/commands/', percentage: 45.2),
31
+ # Git::DirstatEntry.new(directory: 'spec/unit/', percentage: 30.1)
32
+ # ]
33
+ # )
34
+ # dirstat.entries.first.directory #=> "lib/commands/"
35
+ # dirstat['lib/commands/'] #=> 45.2
36
+ # dirstat.to_h #=> { "lib/commands/" => 45.2, "spec/unit/" => 30.1 }
37
+ #
38
+ # @api private
39
+ #
40
+ # Work in progress; this class is internal for now and may be made public in a future release.
41
+ #
42
+ # @!attribute [r] entries
43
+ # @return [Array<DirstatEntry>] directory statistics in order from git output
44
+ #
45
+ DirstatInfo = Data.define(:entries) do
46
+ # Look up percentage by directory path
47
+ #
48
+ # @param directory [String] the directory path
49
+ #
50
+ # @return [Float, nil] the percentage or nil if not found
51
+ #
52
+ def [](directory)
53
+ entries.find { |e| e.directory == directory }&.percentage
54
+ end
55
+
56
+ # Convert to a Hash mapping directory to percentage
57
+ #
58
+ # @return [Hash<String, Float>]
59
+ #
60
+ def to_h
61
+ entries.to_h { |e| [e.directory, e.percentage] }
62
+ end
63
+
64
+ # Number of directories in the dirstat
65
+ #
66
+ # @return [Integer]
67
+ #
68
+ def size
69
+ entries.size
70
+ end
71
+
72
+ # Check if dirstat is empty
73
+ #
74
+ # @return [Boolean]
75
+ #
76
+ def empty?
77
+ entries.empty?
78
+ end
79
+
80
+ # Iterate over entries
81
+ #
82
+ # @overload each
83
+ #
84
+ # @return [Enumerator<Git::DirstatEntry>] an enumerator over all dirstat entries
85
+ #
86
+ # @overload each(&block)
87
+ #
88
+ # @return [Array<Git::DirstatEntry>] the full list of dirstat entries
89
+ #
90
+ # @yield [entry] each dirstat entry
91
+ #
92
+ # @yieldparam entry [Git::DirstatEntry] a single dirstat entry
93
+ #
94
+ # @yieldreturn [void]
95
+ #
96
+ def each(&block)
97
+ entries.each(&block)
98
+ end
99
+
100
+ include Enumerable
101
+ end
102
+ end
@@ -3,25 +3,56 @@
3
3
  require 'rchardet'
4
4
 
5
5
  module Git
6
- # Method that can be used to detect and normalize string encoding
6
+ # Provides helpers for detecting and normalizing string encodings
7
+ #
8
+ # `default_encoding` refers to {Git::EncodingUtils.default_encoding}, which is
9
+ # derived from this source file's encoding declaration
10
+ #
11
+ # @api private
12
+ #
7
13
  module EncodingUtils
14
+ # Returns the default encoding name used by this source file
15
+ #
16
+ # @return [String] the source file encoding name
17
+ #
8
18
  def self.default_encoding
9
19
  __ENCODING__.name
10
20
  end
11
21
 
22
+ # Returns the fallback encoding name used when detection fails
23
+ #
24
+ # @return [String] the fallback encoding name
25
+ #
12
26
  def self.best_guess_encoding
13
27
  # Encoding::ASCII_8BIT.name
14
28
  Encoding::UTF_8.name
15
29
  end
16
30
 
31
+ # Returns the detected encoding name for the given string
32
+ #
33
+ # @param str [String] the string whose encoding should be detected
34
+ #
35
+ # @return [String] the detected encoding name or the fallback encoding name
36
+ #
17
37
  def self.detected_encoding(str)
18
38
  CharDet.detect(str)['encoding'] || best_guess_encoding
19
39
  end
20
40
 
41
+ # Returns replacement options used when transcoding invalid byte sequences
42
+ #
43
+ # @return [Hash<Symbol, Symbol>] options for replacing invalid and undefined bytes
44
+ #
21
45
  def self.encoding_options
22
46
  { invalid: :replace, undef: :replace }
23
47
  end
24
48
 
49
+ # Returns the given string converted to {Git::EncodingUtils.default_encoding}
50
+ #
51
+ # @param str [String] the string to normalize
52
+ #
53
+ # @return [String] the original or transcoded string in
54
+ # {Git::EncodingUtils.default_encoding}
55
+ #
25
56
  def self.normalize_encoding(str)
26
57
  return str if str.valid_encoding? && str.encoding.name == default_encoding
27
58
 
data/lib/git/errors.rb ADDED
@@ -0,0 +1,285 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # rubocop:disable Layout/LineLength
5
+
6
+ # Base class for all custom git module errors
7
+ #
8
+ # The git gem will only raise an `ArgumentError` or an error that is a subclass of
9
+ # `Git::Error`. It does not explicitly raise any other types of errors.
10
+ #
11
+ # It is recommended to rescue `Git::Error` to catch any runtime error raised by
12
+ # this gem unless you need more specific error handling.
13
+ #
14
+ # Git's custom errors are arranged in the following class heirarchy:
15
+ #
16
+ # ```text
17
+ # StandardError
18
+ # └─> Git::Error
19
+ # ├─> Git::CommandLineError
20
+ # │ ├─> Git::FailedError
21
+ # │ └─> Git::SignaledError
22
+ # │ └─> Git::TimeoutError
23
+ # ├─> Git::ProcessIOError
24
+ # ├─> Git::UnexpectedResultError
25
+ # └─> Git::VersionError
26
+ # ```
27
+ #
28
+ # | Error Class | Description |
29
+ # | --- | --- |
30
+ # | `Error` | This catch-all error serves as the base class for other custom errors raised by the git gem. |
31
+ # | `CommandLineError` | A subclass of this error is raised when there is a problem executing the git command line. |
32
+ # | `FailedError` | This error is raised when the git command line exits with a non-zero status code that is not expected by the git gem. |
33
+ # | `SignaledError` | This error is raised when the git command line is terminated as a result of receiving a signal. This could happen if the process is forcibly terminated or if there is a serious system error. |
34
+ # | `TimeoutError` | This is a specific type of `SignaledError` that is raised when the git command line operation times out and is killed via the SIGKILL signal. This happens if the operation takes longer than the timeout duration configured in `Git.config.timeout` or via the `:timeout` parameter given in git methods that support timeouts. |
35
+ # | `ProcessIOError` | An error was encountered reading or writing to a subprocess. |
36
+ # | `UnexpectedResultError` | The command line ran without error but did not return the expected results. |
37
+ # | `VersionError` | The installed git version does not meet the requirements of the git gem or a specific command. |
38
+ #
39
+ # @example Rescuing a generic error
40
+ # begin
41
+ # # some git operation
42
+ # rescue Git::Error => e
43
+ # puts "An error occurred: #{e.message}"
44
+ # end
45
+ #
46
+ # @example Rescuing a timeout error
47
+ # begin
48
+ # timeout_duration = 0.001 # seconds
49
+ # repo = Git.clone('https://github.com/ruby-git/ruby-git', 'ruby-git-temp', timeout: timeout_duration)
50
+ # rescue Git::TimeoutError => e # Catch the more specific error first!
51
+ # puts "Git clone took too long and timed out #{e}"
52
+ # rescue Git::Error => e
53
+ # puts "Received the following error: #{e}"
54
+ # end
55
+ #
56
+ # @see Git::CommandLineError
57
+ #
58
+ # @see Git::FailedError
59
+ #
60
+ # @see Git::SignaledError
61
+ #
62
+ # @see Git::TimeoutError
63
+ #
64
+ # @see Git::ProcessIOError
65
+ #
66
+ # @see Git::UnexpectedResultError
67
+ #
68
+ # @see Git::VersionError
69
+ #
70
+ # @api public
71
+ #
72
+ class Error < StandardError; end
73
+
74
+ # rubocop:enable Layout/LineLength
75
+
76
+ # An alias for Git::Error
77
+ #
78
+ # Git::GitExecuteError error class is an alias for Git::Error for backwards
79
+ # compatibility. It is recommended to use Git::Error directly.
80
+ #
81
+ # @deprecated Use Git::Error instead
82
+ #
83
+ GitExecuteError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Git::GitExecuteError', 'Git::Error', Git::Deprecation)
84
+
85
+ # Raised when a git command fails or exits because of an uncaught signal
86
+ #
87
+ # The git command executed, status, stdout, and stderr are available from this
88
+ # object.
89
+ #
90
+ # The Gem will raise a more specific error for each type of failure:
91
+ #
92
+ # * {Git::FailedError}: when the git command exits with a non-zero status
93
+ # * {Git::SignaledError}: when the git command exits because of an uncaught signal
94
+ # * {Git::TimeoutError}: when the git command times out
95
+ #
96
+ # @api public
97
+ #
98
+ class CommandLineError < Git::Error
99
+ # Create a CommandLineError object
100
+ #
101
+ # @example
102
+ # `exit 1` # set $? appropriately for this example
103
+ # result = Git::CommandLine::Result.new(%w[git status], $?, 'stdout', 'stderr')
104
+ # error = Git::CommandLineError.new(result)
105
+ # error.to_s #=> '["git", "status"], status: pid 89784 exit 1, stderr: "stderr"'
106
+ #
107
+ # @param result [Git::CommandLine::Result] the result of the git command including
108
+ # the git command, status, stdout, and stderr
109
+ #
110
+ def initialize(result)
111
+ @result = result
112
+ super(error_message)
113
+ end
114
+
115
+ # The human readable representation of this error
116
+ #
117
+ # @example
118
+ # error.error_message #=> '["git", "status"], status: pid 89784 exit 1, stderr: "stderr"'
119
+ #
120
+ # @return [String]
121
+ #
122
+ def error_message = <<~MESSAGE.chomp
123
+ #{result.git_cmd}, status: #{result.status}, stderr: #{result.stderr.inspect}
124
+ MESSAGE
125
+
126
+ # The result of the git command including the git command and its status and output
127
+ #
128
+ # @example
129
+ # error.result #=> #<Git::CommandLine::Result:0x00000001046bd488 ...>
130
+ #
131
+ # @return [Git::CommandLine::Result]
132
+ #
133
+ attr_reader :result
134
+ end
135
+
136
+ # This error is raised when a git command returns a non-zero exitstatus
137
+ #
138
+ # The git command executed, status, stdout, and stderr are available from this
139
+ # object.
140
+ #
141
+ # @api public
142
+ #
143
+ class FailedError < Git::CommandLineError; end
144
+
145
+ # This error is raised when a git command exits because of an uncaught signal
146
+ #
147
+ # @api public
148
+ #
149
+ class SignaledError < Git::CommandLineError; end
150
+
151
+ # This error is raised when a git command takes longer than the configured timeout
152
+ #
153
+ # The git command executed, status, stdout, and stderr, and the timeout duration
154
+ # are available from this object.
155
+ #
156
+ # result.status.timed_out? will be `true`
157
+ #
158
+ # @api public
159
+ #
160
+ class TimeoutError < Git::SignaledError
161
+ # Create a TimeoutError object
162
+ #
163
+ # @example
164
+ # command = %w[sleep 10]
165
+ # timeout_duration = 1
166
+ # status = ProcessExecuter.spawn(*command, timeout: timeout_duration)
167
+ # result = Git::CommandLine::Result.new(command, status, 'stdout', 'err output')
168
+ # error = Git::TimeoutError.new(result, timeout_duration)
169
+ # error.error_message
170
+ # #=> '["sleep", "10"], status: pid 70144 SIGKILL (signal 9), stderr: "err output", timed out after 1s'
171
+ #
172
+ # @param result [Git::CommandLine::Result] the result of the git command including
173
+ # the git command, status, stdout, and stderr
174
+ #
175
+ # @param timeout_duration [Numeric] the amount of time the subprocess was allowed
176
+ # to run before being killed
177
+ #
178
+ def initialize(result, timeout_duration)
179
+ @timeout_duration = timeout_duration
180
+ super(result)
181
+ end
182
+
183
+ # The human readable representation of this error
184
+ #
185
+ # @example
186
+ # error.error_message
187
+ # #=> '["sleep", "10"], status: pid 88811 SIGKILL (signal 9), stderr: "err output", timed out after 1s'
188
+ #
189
+ # @return [String]
190
+ #
191
+ def error_message = <<~MESSAGE.chomp
192
+ #{super}, timed out after #{timeout_duration}s
193
+ MESSAGE
194
+
195
+ # The amount of time the subprocess was allowed to run before being killed
196
+ #
197
+ # @example
198
+ # `kill -9 $$` # set $? appropriately for this example
199
+ # result = Git::CommandLine::Result.new(%w[git status], $?, '', "killed")
200
+ # error = Git::TimeoutError.new(result, 10)
201
+ # error.timeout_duration #=> 10
202
+ #
203
+ # @return [Numeric]
204
+ #
205
+ attr_reader :timeout_duration
206
+ end
207
+
208
+ # Raised when the output of a git command can not be read
209
+ #
210
+ # @api public
211
+ #
212
+ class ProcessIOError < Git::Error; end
213
+
214
+ # Raised when the git command result was not as expected
215
+ #
216
+ # @api public
217
+ #
218
+ class UnexpectedResultError < Git::Error; end
219
+
220
+ # Raised when the installed git version does not meet requirements
221
+ #
222
+ # This error is raised when:
223
+ # - The installed git version is below `Git::MINIMUM_GIT_VERSION`
224
+ # - A command requires a minimum git version that isn't met
225
+ # - A command was removed in a git version older than the installed version
226
+ #
227
+ # @example Rescuing a version error
228
+ # begin
229
+ # git.some_command
230
+ # rescue Git::VersionError => e
231
+ # puts "Git version #{e.actual_version} does not meet requirements"
232
+ # puts " #{e.subject}: requires #{e.constraint}"
233
+ # end
234
+ #
235
+ # @api public
236
+ #
237
+ class VersionError < Git::Error
238
+ # Create a VersionError
239
+ #
240
+ # @param subject [#to_s] the entity with the version requirement (e.g., "The git gem", a Class)
241
+ #
242
+ # @param constraint [Git::VersionConstraint] the version constraint that was violated
243
+ #
244
+ # @param actual_version [Git::Version] the installed git version
245
+ #
246
+ def initialize(subject:, constraint:, actual_version:)
247
+ @subject = subject
248
+ @constraint = constraint
249
+ @actual_version = actual_version
250
+ super(build_message)
251
+ end
252
+
253
+ # The entity that has the version requirement
254
+ #
255
+ # @return [#to_s]
256
+ #
257
+ attr_reader :subject
258
+
259
+ # The version constraint that was violated
260
+ #
261
+ # @return [Git::VersionConstraint]
262
+ #
263
+ attr_reader :constraint
264
+
265
+ # The installed git version that caused the error
266
+ #
267
+ # @return [Git::Version]
268
+ #
269
+ attr_reader :actual_version
270
+
271
+ private
272
+
273
+ # Builds the error message for the violated version constraint.
274
+ #
275
+ # @return [String]
276
+ #
277
+ def build_message
278
+ if constraint.too_new?(actual_version)
279
+ "#{subject} requires git < #{constraint.before} (found #{actual_version})"
280
+ else
281
+ "#{subject} requires git >= #{constraint.min} (found #{actual_version})"
282
+ end
283
+ end
284
+ end
285
+ end
@@ -3,15 +3,20 @@
3
3
  module Git
4
4
  # Represents an escaped Git path string
5
5
  #
6
- # Git commands that output paths (e.g. ls-files, diff), will escape usual
6
+ # Git commands that output paths (e.g. ls-files, diff), will escape unusual
7
7
  # characters in the path with backslashes in the same way C escapes control
8
8
  # characters (e.g. \t for TAB, \n for LF, \\ for backslash) or bytes with values
9
9
  # larger than 0x80 (e.g. octal \302\265 for "micro" in UTF-8).
10
10
  #
11
- # @example
12
- # Git::GitPath.new('\302\265').unescape # => "µ"
11
+ # @example Decode octal UTF-8 bytes
12
+ # Git::EscapedPath.new('\302\265').unescape # => "µ"
13
+ #
14
+ # @api private
13
15
  #
14
16
  class EscapedPath
17
+ # Maps single-character escapes to their decoded byte values
18
+ #
19
+ # @return [Hash<String, Integer>] escape characters mapped to byte values
15
20
  UNESCAPES = {
16
21
  'a' => 0x07,
17
22
  'b' => 0x08,
@@ -26,13 +31,27 @@ module Git
26
31
  "'" => 0x27
27
32
  }.freeze
28
33
 
34
+ # Returns the escaped path as provided by git output
35
+ #
36
+ # @return [String] the escaped path string
29
37
  attr_reader :path
30
38
 
39
+ # Initializes an escaped path wrapper
40
+ #
41
+ # @param path [String] the path string with Git-style escape sequences
42
+ #
43
+ # @return [void]
31
44
  def initialize(path)
32
45
  @path = path
33
46
  end
34
47
 
35
- # Convert an escaped path to an unescaped path
48
+ # Converts an escaped path to an unescaped UTF-8 path
49
+ #
50
+ # @example Decode escaped path output
51
+ # Git::EscapedPath.new("dir/\\342\\230\\240\\n").unescape
52
+ # # => "dir/☠\n"
53
+ #
54
+ # @return [String] the decoded path string
36
55
  def unescape
37
56
  bytes = escaped_path_to_bytes(path)
38
57
  str = bytes.pack('C*')
@@ -41,18 +60,46 @@ module Git
41
60
 
42
61
  private
43
62
 
63
+ # Extracts an octal escape sequence starting at the given index
64
+ #
65
+ # @param path [String] the escaped path string
66
+ #
67
+ # @param index [Integer] the index where the sequence starts
68
+ #
69
+ # @return [Array(Integer, Integer)] decoded byte and consumed character count
44
70
  def extract_octal(path, index)
45
- [path[index + 1..index + 3].to_i(8), 4]
71
+ [path[(index + 1)..(index + 3)].to_i(8), 4]
46
72
  end
47
73
 
74
+ # Extracts a single-character escape sequence at the given index
75
+ #
76
+ # @param path [String] the escaped path string
77
+ #
78
+ # @param index [Integer] the index where the sequence starts
79
+ #
80
+ # @return [Array(Integer, Integer)] decoded byte and consumed character count
48
81
  def extract_escape(path, index)
49
82
  [UNESCAPES[path[index + 1]], 2]
50
83
  end
51
84
 
85
+ # Extracts a non-escaped character byte at the given index
86
+ #
87
+ # @param path [String] the escaped path string
88
+ #
89
+ # @param index [Integer] the index of the character to decode
90
+ #
91
+ # @return [Array(Integer, Integer)] decoded byte and consumed character count
52
92
  def extract_single_char(path, index)
53
93
  [path[index].ord, 1]
54
94
  end
55
95
 
96
+ # Decodes the next byte from the escaped path at the given index
97
+ #
98
+ # @param path [String] the escaped path string
99
+ #
100
+ # @param index [Integer] the index to decode from
101
+ #
102
+ # @return [Array(Integer, Integer)] decoded byte and consumed character count
56
103
  def next_byte(path, index)
57
104
  if path[index] == '\\' && path[index + 1] >= '0' && path[index + 1] <= '7'
58
105
  extract_octal(path, index)
@@ -63,6 +110,11 @@ module Git
63
110
  end
64
111
  end
65
112
 
113
+ # Converts an escaped path string into decoded bytes
114
+ #
115
+ # @param path [String] the escaped path string
116
+ #
117
+ # @return [Array<Integer>] decoded bytes in order
66
118
  def escaped_path_to_bytes(path)
67
119
  index = 0
68
120
  [].tap do |bytes|
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/execution_context'
4
+
5
+ module Git
6
+ class ExecutionContext
7
+ # Execution context for global git commands (no repository required)
8
+ #
9
+ # Used for commands that do not require an existing repository — such as
10
+ # `git init`, `git clone`, and `git version`. Unlike
11
+ # {Git::ExecutionContext::Repository}, this class leaves `GIT_DIR`,
12
+ # `GIT_WORK_TREE`, and `GIT_INDEX_FILE` as `nil` (which unsets them), so
13
+ # that the parent environment cannot leak an unintended repository context.
14
+ # `GIT_SSH` is still supported to allow SSH-based remote operations
15
+ # (e.g. `git clone git@github.com:...`).
16
+ #
17
+ # @example Create a context using the default git binary
18
+ # context = Git::ExecutionContext::Global.new
19
+ #
20
+ # @example Create a context targeting a specific binary
21
+ # context = Git::ExecutionContext::Global.new(binary_path: '/usr/local/bin/git2')
22
+ #
23
+ # @see Git::ExecutionContext#initialize for constructor parameters and
24
+ # their semantics
25
+ #
26
+ # @api private
27
+ #
28
+ class Global < ExecutionContext
29
+ end
30
+ end
31
+ end