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
data/lib/git/lib.rb DELETED
@@ -1,1328 +0,0 @@
1
- require 'git/failed_error'
2
- require 'logger'
3
- require 'tempfile'
4
- require 'zlib'
5
- require 'open3'
6
-
7
- module Git
8
- class Lib
9
-
10
- @@semaphore = Mutex.new
11
-
12
- # The path to the Git working copy. The default is '"./.git"'.
13
- #
14
- # @return [Pathname] the path to the Git working copy.
15
- #
16
- # @see [Git working tree](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefworkingtreeaworkingtree)
17
- #
18
- attr_reader :git_work_dir
19
-
20
- # The path to the Git repository directory. The default is
21
- # `"#{git_work_dir}/.git"`.
22
- #
23
- # @return [Pathname] the Git repository directory.
24
- #
25
- # @see [Git repository](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefrepositoryarepository)
26
- #
27
- attr_reader :git_dir
28
-
29
- # The Git index file used to stage changes (using `git add`) before they
30
- # are committed.
31
- #
32
- # @return [Pathname] the Git index file
33
- #
34
- # @see [Git index file](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefindexaindex)
35
- #
36
- attr_reader :git_index_file
37
-
38
- # Create a new Git::Lib object
39
- #
40
- # @param [Git::Base, Hash] base An object that passes in values for
41
- # @git_work_dir, @git_dir, and @git_index_file
42
- #
43
- # @param [Logger] logger
44
- #
45
- # @option base [Pathname] :working_directory
46
- # @option base [Pathname] :repository
47
- # @option base [Pathname] :index
48
- #
49
- def initialize(base = nil, logger = nil)
50
- @git_dir = nil
51
- @git_index_file = nil
52
- @git_work_dir = nil
53
- @path = nil
54
- @logger = logger || Logger.new(nil)
55
-
56
- if base.is_a?(Git::Base)
57
- @git_dir = base.repo.path
58
- @git_index_file = base.index.path if base.index
59
- @git_work_dir = base.dir.path if base.dir
60
- elsif base.is_a?(Hash)
61
- @git_dir = base[:repository]
62
- @git_index_file = base[:index]
63
- @git_work_dir = base[:working_directory]
64
- end
65
- end
66
-
67
- # creates or reinitializes the repository
68
- #
69
- # options:
70
- # :bare
71
- # :working_directory
72
- # :initial_branch
73
- #
74
- def init(opts={})
75
- arr_opts = []
76
- arr_opts << '--bare' if opts[:bare]
77
- arr_opts << "--initial-branch=#{opts[:initial_branch]}" if opts[:initial_branch]
78
-
79
- command('init', *arr_opts)
80
- end
81
-
82
- # tries to clone the given repo
83
- #
84
- # accepts options:
85
- # :bare:: no working directory
86
- # :branch:: name of branch to track (rather than 'master')
87
- # :depth:: the number of commits back to pull
88
- # :filter:: specify partial clone
89
- # :origin:: name of remote (same as remote)
90
- # :path:: directory where the repo will be cloned
91
- # :remote:: name of remote (rather than 'origin')
92
- # :recursive:: after the clone is created, initialize all submodules within, using their default settings.
93
- #
94
- # TODO - make this work with SSH password or auth_key
95
- #
96
- # @return [Hash] the options to pass to {Git::Base.new}
97
- #
98
- def clone(repository_url, directory, opts = {})
99
- @path = opts[:path] || '.'
100
- clone_dir = opts[:path] ? File.join(@path, directory) : directory
101
-
102
- arr_opts = []
103
- arr_opts << '--bare' if opts[:bare]
104
- arr_opts << '--branch' << opts[:branch] if opts[:branch]
105
- arr_opts << '--depth' << opts[:depth].to_i if opts[:depth] && opts[:depth].to_i > 0
106
- arr_opts << '--filter' << opts[:filter] if opts[:filter]
107
- Array(opts[:config]).each { |c| arr_opts << '--config' << c }
108
- arr_opts << '--origin' << opts[:remote] || opts[:origin] if opts[:remote] || opts[:origin]
109
- arr_opts << '--recursive' if opts[:recursive]
110
- arr_opts << '--mirror' if opts[:mirror]
111
-
112
- arr_opts << '--'
113
-
114
- arr_opts << repository_url
115
- arr_opts << clone_dir
116
-
117
- command('clone', *arr_opts)
118
-
119
- return_base_opts_from_clone(clone_dir, opts)
120
- end
121
-
122
- def return_base_opts_from_clone(clone_dir, opts)
123
- base_opts = {}
124
- base_opts[:repository] = clone_dir if (opts[:bare] || opts[:mirror])
125
- base_opts[:working_directory] = clone_dir unless (opts[:bare] || opts[:mirror])
126
- base_opts[:log] = opts[:log] if opts[:log]
127
- base_opts
128
- end
129
-
130
- # Returns the name of the default branch of the given repository
131
- #
132
- # @param repository [URI, Pathname, String] The (possibly remote) repository to clone from
133
- #
134
- # @return [String] the name of the default branch
135
- #
136
- def repository_default_branch(repository)
137
- output = command('ls-remote', '--symref', '--', repository, 'HEAD')
138
-
139
- match_data = output.match(%r{^ref: refs/remotes/origin/(?<default_branch>[^\t]+)\trefs/remotes/origin/HEAD$})
140
- return match_data[:default_branch] if match_data
141
-
142
- match_data = output.match(%r{^ref: refs/heads/(?<default_branch>[^\t]+)\tHEAD$})
143
- return match_data[:default_branch] if match_data
144
-
145
- raise 'Unable to determine the default branch'
146
- end
147
-
148
- ## READ COMMANDS ##
149
-
150
- #
151
- # Returns most recent tag that is reachable from a commit
152
- #
153
- # accepts options:
154
- # :all
155
- # :tags
156
- # :contains
157
- # :debug
158
- # :exact_match
159
- # :dirty
160
- # :abbrev
161
- # :candidates
162
- # :long
163
- # :always
164
- # :math
165
- #
166
- # @param [String|NilClass] committish target commit sha or object name
167
- # @param [{Symbol=>Object}] opts the given options
168
- # @return [String] the tag name
169
- #
170
- def describe(committish=nil, opts={})
171
- arr_opts = []
172
-
173
- arr_opts << '--all' if opts[:all]
174
- arr_opts << '--tags' if opts[:tags]
175
- arr_opts << '--contains' if opts[:contains]
176
- arr_opts << '--debug' if opts[:debug]
177
- arr_opts << '--long' if opts[:long]
178
- arr_opts << '--always' if opts[:always]
179
- arr_opts << '--exact-match' if opts[:exact_match] || opts[:"exact-match"]
180
-
181
- arr_opts << '--dirty' if opts[:dirty] == true
182
- arr_opts << "--dirty=#{opts[:dirty]}" if opts[:dirty].is_a?(String)
183
-
184
- arr_opts << "--abbrev=#{opts[:abbrev]}" if opts[:abbrev]
185
- arr_opts << "--candidates=#{opts[:candidates]}" if opts[:candidates]
186
- arr_opts << "--match=#{opts[:match]}" if opts[:match]
187
-
188
- arr_opts << committish if committish
189
-
190
- return command('describe', *arr_opts)
191
- end
192
-
193
- def log_commits(opts={})
194
- arr_opts = log_common_options(opts)
195
-
196
- arr_opts << '--pretty=oneline'
197
-
198
- arr_opts += log_path_options(opts)
199
-
200
- command_lines('log', *arr_opts).map { |l| l.split.first }
201
- end
202
-
203
- def full_log_commits(opts={})
204
- arr_opts = log_common_options(opts)
205
-
206
- arr_opts << '--pretty=raw'
207
- arr_opts << "--skip=#{opts[:skip]}" if opts[:skip]
208
-
209
- arr_opts += log_path_options(opts)
210
-
211
- full_log = command_lines('log', *arr_opts)
212
-
213
- process_commit_log_data(full_log)
214
- end
215
-
216
- def revparse(string)
217
- return string if string =~ /^[A-Fa-f0-9]{40}$/ # passing in a sha - just no-op it
218
- rev = ['head', 'remotes', 'tags'].map do |d|
219
- File.join(@git_dir, 'refs', d, string)
220
- end.find do |path|
221
- File.file?(path)
222
- end
223
- return File.read(rev).chomp if rev
224
- command('rev-parse', string)
225
- end
226
-
227
- def namerev(string)
228
- command('name-rev', string).split[1]
229
- end
230
-
231
- def object_type(sha)
232
- command('cat-file', '-t', sha)
233
- end
234
-
235
- def object_size(sha)
236
- command('cat-file', '-s', sha).to_i
237
- end
238
-
239
- # returns useful array of raw commit object data
240
- def commit_data(sha)
241
- sha = sha.to_s
242
- cdata = command_lines('cat-file', 'commit', sha)
243
- process_commit_data(cdata, sha)
244
- end
245
-
246
- def process_commit_data(data, sha)
247
- hsh = {
248
- 'sha' => sha,
249
- 'parent' => []
250
- }
251
-
252
- each_cat_file_header(data) do |key, value|
253
- if key == 'parent'
254
- hsh['parent'] << value
255
- else
256
- hsh[key] = value
257
- end
258
- end
259
-
260
- hsh['message'] = data.join("\n") + "\n"
261
-
262
- return hsh
263
- end
264
-
265
- CAT_FILE_HEADER_LINE = /\A(?<key>\w+) (?<value>.*)\z/
266
-
267
- def each_cat_file_header(data)
268
- while (match = CAT_FILE_HEADER_LINE.match(data.shift))
269
- key = match[:key]
270
- value_lines = [match[:value]]
271
-
272
- while data.first.start_with?(' ')
273
- value_lines << data.shift.lstrip
274
- end
275
-
276
- yield key, value_lines.join("\n")
277
- end
278
- end
279
-
280
- def tag_data(name)
281
- sha = sha.to_s
282
- tdata = command_lines('cat-file', 'tag', name)
283
- process_tag_data(tdata, name)
284
- end
285
-
286
- def process_tag_data(data, name)
287
- hsh = { 'name' => name }
288
-
289
- each_cat_file_header(data) do |key, value|
290
- hsh[key] = value
291
- end
292
-
293
- hsh['message'] = data.join("\n") + "\n"
294
-
295
- return hsh
296
- end
297
-
298
- def process_commit_log_data(data)
299
- in_message = false
300
-
301
- hsh_array = []
302
-
303
- hsh = nil
304
-
305
- data.each do |line|
306
- line = line.chomp
307
-
308
- if line[0].nil?
309
- in_message = !in_message
310
- next
311
- end
312
-
313
- in_message = false if in_message && line[0..3] != " "
314
-
315
- if in_message
316
- hsh['message'] << "#{line[4..-1]}\n"
317
- next
318
- end
319
-
320
- key, *value = line.split
321
- value = value.join(' ')
322
-
323
- case key
324
- when 'commit'
325
- hsh_array << hsh if hsh
326
- hsh = {'sha' => value, 'message' => '', 'parent' => []}
327
- when 'parent'
328
- hsh['parent'] << value
329
- else
330
- hsh[key] = value
331
- end
332
- end
333
-
334
- hsh_array << hsh if hsh
335
-
336
- return hsh_array
337
- end
338
-
339
- def object_contents(sha, &block)
340
- command('cat-file', '-p', sha, &block)
341
- end
342
-
343
- def ls_tree(sha)
344
- data = { 'blob' => {}, 'tree' => {}, 'commit' => {} }
345
-
346
- command_lines('ls-tree', sha).each do |line|
347
- (info, filenm) = line.split("\t")
348
- (mode, type, sha) = info.split
349
- data[type][filenm] = {:mode => mode, :sha => sha}
350
- end
351
-
352
- data
353
- end
354
-
355
- def mv(file1, file2)
356
- command_lines('mv', '--', file1, file2)
357
- end
358
-
359
- def full_tree(sha)
360
- command_lines('ls-tree', '-r', sha)
361
- end
362
-
363
- def tree_depth(sha)
364
- full_tree(sha).size
365
- end
366
-
367
- def change_head_branch(branch_name)
368
- command('symbolic-ref', 'HEAD', "refs/heads/#{branch_name}")
369
- end
370
-
371
- BRANCH_LINE_REGEXP = /
372
- ^
373
- # Prefix indicates if this branch is checked out. The prefix is one of:
374
- (?:
375
- (?<current>\*[[:blank:]]) | # Current branch (checked out in the current worktree)
376
- (?<worktree>\+[[:blank:]]) | # Branch checked out in a different worktree
377
- [[:blank:]]{2} # Branch not checked out
378
- )
379
-
380
- # The branch's full refname
381
- (?:
382
- (?<not_a_branch>\(not[[:blank:]]a[[:blank:]]branch\)) |
383
- (?:\(HEAD[[:blank:]]detached[[:blank:]]at[[:blank:]](?<detached_ref>[^\)]+)\)) |
384
- (?<refname>[^[[:blank:]]]+)
385
- )
386
-
387
- # Optional symref
388
- # If this ref is a symbolic reference, this is the ref referenced
389
- (?:
390
- [[:blank:]]->[[:blank:]](?<symref>.*)
391
- )?
392
- $
393
- /x
394
-
395
- def branches_all
396
- command_lines('branch', '-a').map do |line|
397
- match_data = line.match(BRANCH_LINE_REGEXP)
398
- raise GitExecuteError, 'Unexpected branch line format' unless match_data
399
- next nil if match_data[:not_a_branch] || match_data[:detached_ref]
400
- [
401
- match_data[:refname],
402
- !match_data[:current].nil?,
403
- !match_data[:worktree].nil?,
404
- match_data[:symref]
405
- ]
406
- end.compact
407
- end
408
-
409
- def worktrees_all
410
- arr = []
411
- directory = ''
412
- # Output example for `worktree list --porcelain`:
413
- # worktree /code/public/ruby-git
414
- # HEAD 4bef5abbba073c77b4d0ccc1ffcd0ed7d48be5d4
415
- # branch refs/heads/master
416
- #
417
- # worktree /tmp/worktree-1
418
- # HEAD b8c63206f8d10f57892060375a86ae911fad356e
419
- # detached
420
- #
421
- command_lines('worktree', 'list', '--porcelain').each do |w|
422
- s = w.split("\s")
423
- directory = s[1] if s[0] == 'worktree'
424
- arr << [directory, s[1]] if s[0] == 'HEAD'
425
- end
426
- arr
427
- end
428
-
429
- def worktree_add(dir, commitish = nil)
430
- return command('worktree', 'add', dir, commitish) if !commitish.nil?
431
- command('worktree', 'add', dir)
432
- end
433
-
434
- def worktree_remove(dir)
435
- command('worktree', 'remove', dir)
436
- end
437
-
438
- def worktree_prune
439
- command('worktree', 'prune')
440
- end
441
-
442
- def list_files(ref_dir)
443
- dir = File.join(@git_dir, 'refs', ref_dir)
444
- files = []
445
- begin
446
- files = Dir.glob('**/*', base: dir).select { |f| File.file?(File.join(dir, f)) }
447
- rescue
448
- end
449
- files
450
- end
451
-
452
- def branch_current
453
- branches_all.select { |b| b[1] }.first[0] rescue nil
454
- end
455
-
456
- def branch_contains(commit, branch_name="")
457
- command("branch", branch_name, "--contains", commit)
458
- end
459
-
460
- # returns hash
461
- # [tree-ish] = [[line_no, match], [line_no, match2]]
462
- # [tree-ish] = [[line_no, match], [line_no, match2]]
463
- def grep(string, opts = {})
464
- opts[:object] ||= 'HEAD'
465
-
466
- grep_opts = ['-n']
467
- grep_opts << '-i' if opts[:ignore_case]
468
- grep_opts << '-v' if opts[:invert_match]
469
- grep_opts << '-E' if opts[:extended_regexp]
470
- grep_opts << '-e'
471
- grep_opts << string
472
- grep_opts << opts[:object] if opts[:object].is_a?(String)
473
- grep_opts.push('--', opts[:path_limiter]) if opts[:path_limiter].is_a?(String)
474
- grep_opts.push('--', *opts[:path_limiter]) if opts[:path_limiter].is_a?(Array)
475
-
476
- hsh = {}
477
- command_lines('grep', *grep_opts).each do |line|
478
- if m = /(.*?)\:(\d+)\:(.*)/.match(line)
479
- hsh[m[1]] ||= []
480
- hsh[m[1]] << [m[2].to_i, m[3]]
481
- end
482
- end
483
- hsh
484
- end
485
-
486
- def diff_full(obj1 = 'HEAD', obj2 = nil, opts = {})
487
- diff_opts = ['-p']
488
- diff_opts << obj1
489
- diff_opts << obj2 if obj2.is_a?(String)
490
- diff_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
491
-
492
- command('diff', *diff_opts)
493
- end
494
-
495
- def diff_stats(obj1 = 'HEAD', obj2 = nil, opts = {})
496
- diff_opts = ['--numstat']
497
- diff_opts << obj1
498
- diff_opts << obj2 if obj2.is_a?(String)
499
- diff_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
500
-
501
- hsh = {:total => {:insertions => 0, :deletions => 0, :lines => 0, :files => 0}, :files => {}}
502
-
503
- command_lines('diff', *diff_opts).each do |file|
504
- (insertions, deletions, filename) = file.split("\t")
505
- hsh[:total][:insertions] += insertions.to_i
506
- hsh[:total][:deletions] += deletions.to_i
507
- hsh[:total][:lines] = (hsh[:total][:deletions] + hsh[:total][:insertions])
508
- hsh[:total][:files] += 1
509
- hsh[:files][filename] = {:insertions => insertions.to_i, :deletions => deletions.to_i}
510
- end
511
-
512
- hsh
513
- end
514
-
515
- def diff_name_status(reference1 = nil, reference2 = nil, opts = {})
516
- opts_arr = ['--name-status']
517
- opts_arr << reference1 if reference1
518
- opts_arr << reference2 if reference2
519
-
520
- opts_arr << '--' << opts[:path] if opts[:path]
521
-
522
- command_lines('diff', *opts_arr).inject({}) do |memo, line|
523
- status, path = line.split("\t")
524
- memo[path] = status
525
- memo
526
- end
527
- end
528
-
529
- # compares the index and the working directory
530
- def diff_files
531
- diff_as_hash('diff-files')
532
- end
533
-
534
- # compares the index and the repository
535
- def diff_index(treeish)
536
- diff_as_hash('diff-index', treeish)
537
- end
538
-
539
- def ls_files(location=nil)
540
- location ||= '.'
541
- hsh = {}
542
- command_lines('ls-files', '--stage', location).each do |line|
543
- (info, file) = line.split("\t")
544
- (mode, sha, stage) = info.split
545
- if file.start_with?('"') && file.end_with?('"')
546
- file = Git::EscapedPath.new(file[1..-2]).unescape
547
- end
548
- hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
549
- end
550
- hsh
551
- end
552
-
553
- def ls_remote(location=nil, opts={})
554
- arr_opts = []
555
- arr_opts << '--refs' if opts[:refs]
556
- arr_opts << (location || '.')
557
-
558
- Hash.new{ |h,k| h[k] = {} }.tap do |hsh|
559
- command_lines('ls-remote', *arr_opts).each do |line|
560
- (sha, info) = line.split("\t")
561
- (ref, type, name) = info.split('/', 3)
562
- type ||= 'head'
563
- type = 'branches' if type == 'heads'
564
- value = {:ref => ref, :sha => sha}
565
- hsh[type].update( name.nil? ? value : { name => value })
566
- end
567
- end
568
- end
569
-
570
- def ignored_files
571
- command_lines('ls-files', '--others', '-i', '--exclude-standard')
572
- end
573
-
574
-
575
- def config_remote(name)
576
- hsh = {}
577
- config_list.each do |key, value|
578
- if /remote.#{name}/.match(key)
579
- hsh[key.gsub("remote.#{name}.", '')] = value
580
- end
581
- end
582
- hsh
583
- end
584
-
585
- def config_get(name)
586
- command('config', '--get', name, chdir: @git_dir)
587
- end
588
-
589
- def global_config_get(name)
590
- command('config', '--global', '--get', name)
591
- end
592
-
593
- def config_list
594
- parse_config_list command_lines('config', '--list', chdir: @git_dir)
595
- end
596
-
597
- def global_config_list
598
- parse_config_list command_lines('config', '--global', '--list')
599
- end
600
-
601
- def parse_config_list(lines)
602
- hsh = {}
603
- lines.each do |line|
604
- (key, *values) = line.split('=')
605
- hsh[key] = values.join('=')
606
- end
607
- hsh
608
- end
609
-
610
- def parse_config(file)
611
- parse_config_list command_lines('config', '--list', '--file', file)
612
- end
613
-
614
- # Shows objects
615
- #
616
- # @param [String|NilClass] objectish the target object reference (nil == HEAD)
617
- # @param [String|NilClass] path the path of the file to be shown
618
- # @return [String] the object information
619
- def show(objectish=nil, path=nil)
620
- arr_opts = []
621
-
622
- arr_opts << (path ? "#{objectish}:#{path}" : objectish)
623
-
624
- command('show', *arr_opts.compact, chomp: false)
625
- end
626
-
627
- ## WRITE COMMANDS ##
628
-
629
- def config_set(name, value, options = {})
630
- if options[:file].to_s.empty?
631
- command('config', name, value)
632
- else
633
- command('config', '--file', options[:file], name, value)
634
- end
635
- end
636
-
637
- def global_config_set(name, value)
638
- command('config', '--global', name, value)
639
- end
640
-
641
- # updates the repository index using the working directory content
642
- #
643
- # lib.add('path/to/file')
644
- # lib.add(['path/to/file1','path/to/file2'])
645
- # lib.add(:all => true)
646
- #
647
- # options:
648
- # :all => true
649
- # :force => true
650
- #
651
- # @param [String,Array] paths files paths to be added to the repository
652
- # @param [Hash] options
653
- def add(paths='.',options={})
654
- arr_opts = []
655
-
656
- arr_opts << '--all' if options[:all]
657
- arr_opts << '--force' if options[:force]
658
-
659
- arr_opts << '--'
660
-
661
- arr_opts << paths
662
-
663
- arr_opts.flatten!
664
-
665
- command('add', *arr_opts)
666
- end
667
-
668
- def rm(path = '.', opts = {})
669
- arr_opts = ['-f'] # overrides the up-to-date check by default
670
- arr_opts << '-r' if opts[:recursive]
671
- arr_opts << '--cached' if opts[:cached]
672
- arr_opts << '--'
673
- arr_opts += Array(path)
674
-
675
- command('rm', *arr_opts)
676
- end
677
-
678
- # Takes the commit message with the options and executes the commit command
679
- #
680
- # accepts options:
681
- # :amend
682
- # :all
683
- # :allow_empty
684
- # :author
685
- # :date
686
- # :no_verify
687
- # :allow_empty_message
688
- # :gpg_sign (accepts true or a gpg key ID as a String)
689
- # :no_gpg_sign (conflicts with :gpg_sign)
690
- #
691
- # @param [String] message the commit message to be used
692
- # @param [Hash] opts the commit options to be used
693
- def commit(message, opts = {})
694
- arr_opts = []
695
- arr_opts << "--message=#{message}" if message
696
- arr_opts << '--amend' << '--no-edit' if opts[:amend]
697
- arr_opts << '--all' if opts[:add_all] || opts[:all]
698
- arr_opts << '--allow-empty' if opts[:allow_empty]
699
- arr_opts << "--author=#{opts[:author]}" if opts[:author]
700
- arr_opts << "--date=#{opts[:date]}" if opts[:date].is_a? String
701
- arr_opts << '--no-verify' if opts[:no_verify]
702
- arr_opts << '--allow-empty-message' if opts[:allow_empty_message]
703
-
704
- if opts[:gpg_sign] && opts[:no_gpg_sign]
705
- raise ArgumentError, 'cannot specify :gpg_sign and :no_gpg_sign'
706
- elsif opts[:gpg_sign]
707
- arr_opts <<
708
- if opts[:gpg_sign] == true
709
- '--gpg-sign'
710
- else
711
- "--gpg-sign=#{opts[:gpg_sign]}"
712
- end
713
- elsif opts[:no_gpg_sign]
714
- arr_opts << '--no-gpg-sign'
715
- end
716
-
717
- command('commit', *arr_opts)
718
- end
719
-
720
- def reset(commit, opts = {})
721
- arr_opts = []
722
- arr_opts << '--hard' if opts[:hard]
723
- arr_opts << commit if commit
724
- command('reset', *arr_opts)
725
- end
726
-
727
- def clean(opts = {})
728
- arr_opts = []
729
- arr_opts << '--force' if opts[:force]
730
- arr_opts << '-ff' if opts[:ff]
731
- arr_opts << '-d' if opts[:d]
732
- arr_opts << '-x' if opts[:x]
733
-
734
- command('clean', *arr_opts)
735
- end
736
-
737
- def revert(commitish, opts = {})
738
- # Forcing --no-edit as default since it's not an interactive session.
739
- opts = {:no_edit => true}.merge(opts)
740
-
741
- arr_opts = []
742
- arr_opts << '--no-edit' if opts[:no_edit]
743
- arr_opts << commitish
744
-
745
- command('revert', *arr_opts)
746
- end
747
-
748
- def apply(patch_file)
749
- arr_opts = []
750
- arr_opts << '--' << patch_file if patch_file
751
- command('apply', *arr_opts)
752
- end
753
-
754
- def apply_mail(patch_file)
755
- arr_opts = []
756
- arr_opts << '--' << patch_file if patch_file
757
- command('am', *arr_opts)
758
- end
759
-
760
- def stashes_all
761
- arr = []
762
- filename = File.join(@git_dir, 'logs/refs/stash')
763
- if File.exist?(filename)
764
- File.open(filename) do |f|
765
- f.each_with_index do |line, i|
766
- m = line.match(/:(.*)$/)
767
- arr << [i, m[1].strip]
768
- end
769
- end
770
- end
771
- arr
772
- end
773
-
774
- def stash_save(message)
775
- output = command('stash', 'save', message)
776
- output =~ /HEAD is now at/
777
- end
778
-
779
- def stash_apply(id = nil)
780
- if id
781
- command('stash', 'apply', id)
782
- else
783
- command('stash', 'apply')
784
- end
785
- end
786
-
787
- def stash_clear
788
- command('stash', 'clear')
789
- end
790
-
791
- def stash_list
792
- command('stash', 'list')
793
- end
794
-
795
- def branch_new(branch)
796
- command('branch', branch)
797
- end
798
-
799
- def branch_delete(branch)
800
- command('branch', '-D', branch)
801
- end
802
-
803
- # Runs checkout command to checkout or create branch
804
- #
805
- # accepts options:
806
- # :new_branch
807
- # :force
808
- # :start_point
809
- #
810
- # @param [String] branch
811
- # @param [Hash] opts
812
- def checkout(branch = nil, opts = {})
813
- if branch.is_a?(Hash) && opts == {}
814
- opts = branch
815
- branch = nil
816
- end
817
-
818
- arr_opts = []
819
- arr_opts << '-b' if opts[:new_branch] || opts[:b]
820
- arr_opts << '--force' if opts[:force] || opts[:f]
821
- arr_opts << branch if branch
822
- arr_opts << opts[:start_point] if opts[:start_point] && arr_opts.include?('-b')
823
-
824
- command('checkout', *arr_opts)
825
- end
826
-
827
- def checkout_file(version, file)
828
- arr_opts = []
829
- arr_opts << version
830
- arr_opts << file
831
- command('checkout', *arr_opts)
832
- end
833
-
834
- def merge(branch, message = nil, opts = {})
835
- arr_opts = []
836
- arr_opts << '--no-commit' if opts[:no_commit]
837
- arr_opts << '--no-ff' if opts[:no_ff]
838
- arr_opts << '-m' << message if message
839
- arr_opts += Array(branch)
840
- command('merge', *arr_opts)
841
- end
842
-
843
- def merge_base(*args)
844
- opts = args.last.is_a?(Hash) ? args.pop : {}
845
-
846
- arg_opts = []
847
-
848
- arg_opts << '--octopus' if opts[:octopus]
849
- arg_opts << '--independent' if opts[:independent]
850
- arg_opts << '--fork-point' if opts[:fork_point]
851
- arg_opts << '--all' if opts[:all]
852
-
853
- arg_opts += args
854
-
855
- command('merge-base', *arg_opts).lines.map(&:strip)
856
- end
857
-
858
- def unmerged
859
- unmerged = []
860
- command_lines('diff', "--cached").each do |line|
861
- unmerged << $1 if line =~ /^\* Unmerged path (.*)/
862
- end
863
- unmerged
864
- end
865
-
866
- def conflicts # :yields: file, your, their
867
- self.unmerged.each do |f|
868
- your_tempfile = Tempfile.new("YOUR-#{File.basename(f)}")
869
- your = your_tempfile.path
870
- your_tempfile.close # free up file for git command process
871
- command('show', ":2:#{f}", redirect: "> #{escape your}")
872
-
873
- their_tempfile = Tempfile.new("THEIR-#{File.basename(f)}")
874
- their = their_tempfile.path
875
- their_tempfile.close # free up file for git command process
876
- command('show', ":3:#{f}", redirect: "> #{escape their}")
877
- yield(f, your, their)
878
- end
879
- end
880
-
881
- def remote_add(name, url, opts = {})
882
- arr_opts = ['add']
883
- arr_opts << '-f' if opts[:with_fetch] || opts[:fetch]
884
- arr_opts << '-t' << opts[:track] if opts[:track]
885
- arr_opts << '--'
886
- arr_opts << name
887
- arr_opts << url
888
-
889
- command('remote', *arr_opts)
890
- end
891
-
892
- def remote_set_url(name, url)
893
- arr_opts = ['set-url']
894
- arr_opts << name
895
- arr_opts << url
896
-
897
- command('remote', *arr_opts)
898
- end
899
-
900
- def remote_remove(name)
901
- command('remote', 'rm', name)
902
- end
903
-
904
- def remotes
905
- command_lines('remote')
906
- end
907
-
908
- def tags
909
- command_lines('tag')
910
- end
911
-
912
- def tag(name, *opts)
913
- target = opts[0].instance_of?(String) ? opts[0] : nil
914
-
915
- opts = opts.last.instance_of?(Hash) ? opts.last : {}
916
-
917
- if (opts[:a] || opts[:annotate]) && !(opts[:m] || opts[:message])
918
- raise "Can not create an [:a|:annotate] tag without the precense of [:m|:message]."
919
- end
920
-
921
- arr_opts = []
922
-
923
- arr_opts << '-f' if opts[:force] || opts[:f]
924
- arr_opts << '-a' if opts[:a] || opts[:annotate]
925
- arr_opts << '-s' if opts[:s] || opts[:sign]
926
- arr_opts << '-d' if opts[:d] || opts[:delete]
927
- arr_opts << name
928
- arr_opts << target if target
929
-
930
- if opts[:m] || opts[:message]
931
- arr_opts << '-m' << (opts[:m] || opts[:message])
932
- end
933
-
934
- command('tag', *arr_opts)
935
- end
936
-
937
- def fetch(remote, opts)
938
- arr_opts = []
939
- arr_opts << '--all' if opts[:all]
940
- arr_opts << '--tags' if opts[:t] || opts[:tags]
941
- arr_opts << '--prune' if opts[:p] || opts[:prune]
942
- arr_opts << '--prune-tags' if opts[:P] || opts[:'prune-tags']
943
- arr_opts << '--force' if opts[:f] || opts[:force]
944
- arr_opts << '--update-head-ok' if opts[:u] || opts[:'update-head-ok']
945
- arr_opts << '--unshallow' if opts[:unshallow]
946
- arr_opts << '--depth' << opts[:depth] if opts[:depth]
947
- arr_opts << '--' if remote || opts[:ref]
948
- arr_opts << remote if remote
949
- arr_opts << opts[:ref] if opts[:ref]
950
-
951
- command('fetch', *arr_opts)
952
- end
953
-
954
- def push(remote = nil, branch = nil, opts = nil)
955
- if opts.nil? && branch.instance_of?(Hash)
956
- opts = branch
957
- branch = nil
958
- end
959
-
960
- if opts.nil? && remote.instance_of?(Hash)
961
- opts = remote
962
- remote = nil
963
- end
964
-
965
- opts ||= {}
966
-
967
- # Small hack to keep backwards compatibility with the 'push(remote, branch, tags)' method signature.
968
- opts = {:tags => opts} if [true, false].include?(opts)
969
-
970
- raise ArgumentError, "You must specify a remote if a branch is specified" if remote.nil? && !branch.nil?
971
-
972
- arr_opts = []
973
- arr_opts << '--mirror' if opts[:mirror]
974
- arr_opts << '--delete' if opts[:delete]
975
- arr_opts << '--force' if opts[:force] || opts[:f]
976
- arr_opts << '--all' if opts[:all] && remote
977
-
978
- Array(opts[:push_option]).each { |o| arr_opts << '--push-option' << o } if opts[:push_option]
979
- arr_opts << remote if remote
980
- arr_opts_with_branch = arr_opts.dup
981
- arr_opts_with_branch << branch if branch
982
-
983
- if opts[:mirror]
984
- command('push', *arr_opts_with_branch)
985
- else
986
- command('push', *arr_opts_with_branch)
987
- command('push', '--tags', *arr_opts) if opts[:tags]
988
- end
989
- end
990
-
991
- def pull(remote = nil, branch = nil)
992
- raise ArgumentError, "You must specify a remote if a branch is specified" if remote.nil? && !branch.nil?
993
-
994
- arr_opts = []
995
- arr_opts << remote if remote
996
- arr_opts << branch if branch
997
- command('pull', *arr_opts)
998
- end
999
-
1000
- def tag_sha(tag_name)
1001
- head = File.join(@git_dir, 'refs', 'tags', tag_name)
1002
- return File.read(head).chomp if File.exist?(head)
1003
-
1004
- command('show-ref', '--tags', '-s', tag_name)
1005
- end
1006
-
1007
- def repack
1008
- command('repack', '-a', '-d')
1009
- end
1010
-
1011
- def gc
1012
- command('gc', '--prune', '--aggressive', '--auto')
1013
- end
1014
-
1015
- # reads a tree into the current index file
1016
- def read_tree(treeish, opts = {})
1017
- arr_opts = []
1018
- arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
1019
- arr_opts += [treeish]
1020
- command('read-tree', *arr_opts)
1021
- end
1022
-
1023
- def write_tree
1024
- command('write-tree')
1025
- end
1026
-
1027
- def commit_tree(tree, opts = {})
1028
- opts[:message] ||= "commit tree #{tree}"
1029
- t = Tempfile.new('commit-message')
1030
- t.write(opts[:message])
1031
- t.close
1032
-
1033
- arr_opts = []
1034
- arr_opts << tree
1035
- arr_opts << '-p' << opts[:parent] if opts[:parent]
1036
- arr_opts += Array(opts[:parents]).map { |p| ['-p', p] }.flatten if opts[:parents]
1037
- command('commit-tree', *arr_opts, redirect: "< #{escape t.path}")
1038
- end
1039
-
1040
- def update_ref(ref, commit)
1041
- command('update-ref', ref, commit)
1042
- end
1043
-
1044
- def checkout_index(opts = {})
1045
- arr_opts = []
1046
- arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
1047
- arr_opts << "--force" if opts[:force]
1048
- arr_opts << "--all" if opts[:all]
1049
- arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
1050
-
1051
- command('checkout-index', *arr_opts)
1052
- end
1053
-
1054
- # creates an archive file
1055
- #
1056
- # options
1057
- # :format (zip, tar)
1058
- # :prefix
1059
- # :remote
1060
- # :path
1061
- def archive(sha, file = nil, opts = {})
1062
- opts[:format] ||= 'zip'
1063
-
1064
- if opts[:format] == 'tgz'
1065
- opts[:format] = 'tar'
1066
- opts[:add_gzip] = true
1067
- end
1068
-
1069
- if !file
1070
- tempfile = Tempfile.new('archive')
1071
- file = tempfile.path
1072
- # delete it now, before we write to it, so that Ruby doesn't delete it
1073
- # when it finalizes the Tempfile.
1074
- tempfile.close!
1075
- end
1076
-
1077
- arr_opts = []
1078
- arr_opts << "--format=#{opts[:format]}" if opts[:format]
1079
- arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
1080
- arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
1081
- arr_opts << sha
1082
- arr_opts << '--' << opts[:path] if opts[:path]
1083
- command('archive', *arr_opts, redirect: " > #{escape file}")
1084
- if opts[:add_gzip]
1085
- file_content = File.read(file)
1086
- Zlib::GzipWriter.open(file) do |gz|
1087
- gz.write(file_content)
1088
- end
1089
- end
1090
- return file
1091
- end
1092
-
1093
- # returns the current version of git, as an Array of Fixnums.
1094
- def current_command_version
1095
- output = command('version')
1096
- version = output[/\d+(\.\d+)+/]
1097
- version_parts = version.split('.').collect { |i| i.to_i }
1098
- version_parts.fill(0, version_parts.length...3)
1099
- end
1100
-
1101
- # Returns current_command_version <=> other_version
1102
- #
1103
- # @example
1104
- # lib.current_command_version #=> [2, 42, 0]
1105
- #
1106
- # lib.compare_version_to(2, 41, 0) #=> 1
1107
- # lib.compare_version_to(2, 42, 0) #=> 0
1108
- # lib.compare_version_to(2, 43, 0) #=> -1
1109
- #
1110
- # @param other_version [Array<Object>] the other version to compare to
1111
- # @return [Integer] -1 if this version is less than other_version, 0 if equal, or 1 if greater than
1112
- #
1113
- def compare_version_to(*other_version)
1114
- current_command_version <=> other_version
1115
- end
1116
-
1117
- def required_command_version
1118
- [1, 6]
1119
- end
1120
-
1121
- def meets_required_version?
1122
- (self.current_command_version <=> self.required_command_version) >= 0
1123
- end
1124
-
1125
- def self.warn_if_old_command(lib)
1126
- return true if @version_checked
1127
- @version_checked = true
1128
- unless lib.meets_required_version?
1129
- $stderr.puts "[WARNING] The git gem requires git #{lib.required_command_version.join('.')} or later, but only found #{lib.current_command_version.join('.')}. You should probably upgrade."
1130
- end
1131
- true
1132
- end
1133
-
1134
- private
1135
-
1136
- # Systen ENV variables involved in the git commands.
1137
- #
1138
- # @return [<String>] the names of the EVN variables involved in the git commands
1139
- ENV_VARIABLE_NAMES = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_SSH']
1140
-
1141
- def command_lines(cmd, *opts, chdir: nil)
1142
- cmd_op = command(cmd, *opts, chdir: chdir)
1143
- if cmd_op.encoding.name != "UTF-8"
1144
- op = cmd_op.encode("UTF-8", "binary", :invalid => :replace, :undef => :replace)
1145
- else
1146
- op = cmd_op
1147
- end
1148
- op.split("\n")
1149
- end
1150
-
1151
- # Takes the current git's system ENV variables and store them.
1152
- def store_git_system_env_variables
1153
- @git_system_env_variables = {}
1154
- ENV_VARIABLE_NAMES.each do |env_variable_name|
1155
- @git_system_env_variables[env_variable_name] = ENV[env_variable_name]
1156
- end
1157
- end
1158
-
1159
- # Takes the previously stored git's ENV variables and set them again on ENV.
1160
- def restore_git_system_env_variables
1161
- ENV_VARIABLE_NAMES.each do |env_variable_name|
1162
- ENV[env_variable_name] = @git_system_env_variables[env_variable_name]
1163
- end
1164
- end
1165
-
1166
- # Sets git's ENV variables to the custom values for the current instance.
1167
- def set_custom_git_env_variables
1168
- ENV['GIT_DIR'] = @git_dir
1169
- ENV['GIT_WORK_TREE'] = @git_work_dir
1170
- ENV['GIT_INDEX_FILE'] = @git_index_file
1171
- ENV['GIT_SSH'] = Git::Base.config.git_ssh
1172
- end
1173
-
1174
- # Runs a block inside an environment with customized ENV variables.
1175
- # It restores the ENV after execution.
1176
- #
1177
- # @param [Proc] block block to be executed within the customized environment
1178
- def with_custom_env_variables(&block)
1179
- @@semaphore.synchronize do
1180
- store_git_system_env_variables()
1181
- set_custom_git_env_variables()
1182
- return block.call()
1183
- end
1184
- ensure
1185
- restore_git_system_env_variables()
1186
- end
1187
-
1188
- def command(*cmd, redirect: '', chomp: true, chdir: nil, &block)
1189
- Git::Lib.warn_if_old_command(self)
1190
-
1191
- raise 'cmd can not include a nested array' if cmd.any? { |o| o.is_a? Array }
1192
-
1193
- global_opts = []
1194
- global_opts << "--git-dir=#{@git_dir}" if !@git_dir.nil?
1195
- global_opts << "--work-tree=#{@git_work_dir}" if !@git_work_dir.nil?
1196
- global_opts << '-c' << 'core.quotePath=true'
1197
- global_opts << '-c' << 'color.ui=false'
1198
-
1199
- escaped_cmd = cmd.map { |part| escape(part) }.join(' ')
1200
-
1201
- global_opts = global_opts.map { |s| escape(s) }.join(' ')
1202
-
1203
- git_cmd = "#{Git::Base.config.binary_path} #{global_opts} #{escaped_cmd} #{redirect} 2>&1"
1204
-
1205
- output = nil
1206
-
1207
- command_thread = nil;
1208
-
1209
- status = nil
1210
-
1211
- with_custom_env_variables do
1212
- command_thread = Thread.new do
1213
- output, status = run_command(git_cmd, chdir, &block)
1214
- end
1215
- command_thread.join
1216
- end
1217
-
1218
- @logger.info(git_cmd)
1219
- @logger.debug(output)
1220
-
1221
- if status.exitstatus > 1 || (status.exitstatus == 1 && output != '')
1222
- result = Git::CommandLineResult.new(git_cmd, status, output, '')
1223
- raise Git::FailedError.new(result)
1224
- end
1225
-
1226
- output.chomp! if output && chomp && !block_given?
1227
-
1228
- output
1229
- end
1230
-
1231
- # Takes the diff command line output (as Array) and parse it into a Hash
1232
- #
1233
- # @param [String] diff_command the diff commadn to be used
1234
- # @param [Array] opts the diff options to be used
1235
- # @return [Hash] the diff as Hash
1236
- def diff_as_hash(diff_command, opts=[])
1237
- # update index before diffing to avoid spurious diffs
1238
- command('status')
1239
- command_lines(diff_command, *opts).inject({}) do |memo, line|
1240
- info, file = line.split("\t")
1241
- mode_src, mode_dest, sha_src, sha_dest, type = info.split
1242
-
1243
- memo[file] = {
1244
- :mode_index => mode_dest,
1245
- :mode_repo => mode_src.to_s[1, 7],
1246
- :path => file,
1247
- :sha_repo => sha_src,
1248
- :sha_index => sha_dest,
1249
- :type => type
1250
- }
1251
-
1252
- memo
1253
- end
1254
- end
1255
-
1256
- # Returns an array holding the common options for the log commands
1257
- #
1258
- # @param [Hash] opts the given options
1259
- # @return [Array] the set of common options that the log command will use
1260
- def log_common_options(opts)
1261
- arr_opts = []
1262
-
1263
- if opts[:count] && !opts[:count].is_a?(Integer)
1264
- raise ArgumentError, "The log count option must be an Integer but was #{opts[:count].inspect}"
1265
- end
1266
-
1267
- arr_opts << "--max-count=#{opts[:count]}" if opts[:count]
1268
- arr_opts << "--all" if opts[:all]
1269
- arr_opts << "--no-color"
1270
- arr_opts << "--cherry" if opts[:cherry]
1271
- arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
1272
- arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
1273
- arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
1274
- arr_opts << "--author=#{opts[:author]}" if opts[:author].is_a? String
1275
- arr_opts << "#{opts[:between][0].to_s}..#{opts[:between][1].to_s}" if (opts[:between] && opts[:between].size == 2)
1276
-
1277
- arr_opts
1278
- end
1279
-
1280
- # Retrurns an array holding path options for the log commands
1281
- #
1282
- # @param [Hash] opts the given options
1283
- # @return [Array] the set of path options that the log command will use
1284
- def log_path_options(opts)
1285
- arr_opts = []
1286
-
1287
- arr_opts << opts[:object] if opts[:object].is_a? String
1288
- if opts[:path_limiter]
1289
- arr_opts << '--'
1290
- arr_opts += Array(opts[:path_limiter])
1291
- end
1292
- arr_opts
1293
- end
1294
-
1295
- def run_command(git_cmd, chdir=nil, &block)
1296
- block ||= Proc.new do |io|
1297
- io.readlines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join
1298
- end
1299
-
1300
- opts = {}
1301
- opts[:chdir] = File.expand_path(chdir) if chdir
1302
-
1303
- Open3.popen2(git_cmd, opts) do |stdin, stdout, wait_thr|
1304
- [block.call(stdout), wait_thr.value]
1305
- end
1306
- end
1307
-
1308
- def escape(s)
1309
- windows_platform? ? escape_for_windows(s) : escape_for_sh(s)
1310
- end
1311
-
1312
- def escape_for_sh(s)
1313
- "'#{s && s.to_s.gsub('\'','\'"\'"\'')}'"
1314
- end
1315
-
1316
- def escape_for_windows(s)
1317
- # Escape existing double quotes in s and then wrap the result with double quotes
1318
- escaped_string = s.to_s.gsub('"','\\"')
1319
- %Q{"#{escaped_string}"}
1320
- end
1321
-
1322
- def windows_platform?
1323
- # Check if on Windows via RUBY_PLATFORM (CRuby) and RUBY_DESCRIPTION (JRuby)
1324
- win_platform_regex = /mingw|mswin/
1325
- RUBY_PLATFORM =~ win_platform_regex || RUBY_DESCRIPTION =~ win_platform_regex
1326
- end
1327
- end
1328
- end