git 4.3.2 → 5.0.0.beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/copilot-instructions.md +67 -2705
- data/.github/pull_request_template.md +3 -1
- data/.github/skills/breaking-change-analysis/SKILL.md +102 -0
- data/.github/skills/ci-cd-troubleshooting/SKILL.md +264 -0
- data/.github/skills/command-implementation/REFERENCE.md +993 -0
- data/.github/skills/command-implementation/SKILL.md +229 -0
- data/.github/skills/command-test-conventions/SKILL.md +660 -0
- data/.github/skills/command-yard-documentation/SKILL.md +426 -0
- data/.github/skills/dependency-management/SKILL.md +72 -0
- data/.github/skills/development-workflow/SKILL.md +506 -0
- data/.github/skills/extract-command-from-lib/SKILL.md +487 -0
- data/.github/skills/extract-facade-from-base-lib/SKILL.md +586 -0
- data/.github/skills/facade-implementation/REFERENCE.md +840 -0
- data/.github/skills/facade-implementation/SKILL.md +260 -0
- data/.github/skills/facade-test-conventions/SKILL.md +380 -0
- data/.github/skills/facade-yard-documentation/SKILL.md +429 -0
- data/.github/skills/make-skill-template/SKILL.md +176 -0
- data/.github/skills/pr-readiness-review/SKILL.md +185 -0
- data/.github/skills/project-context/SKILL.md +313 -0
- data/.github/skills/pull-request-review/SKILL.md +168 -0
- data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +131 -0
- data/.github/skills/release-management/SKILL.md +125 -0
- data/.github/skills/review-arguments-dsl/CHECKLIST.md +788 -0
- data/.github/skills/review-arguments-dsl/SKILL.md +214 -0
- data/.github/skills/review-backward-compatibility/SKILL.md +275 -0
- data/.github/skills/review-cross-command-consistency/SKILL.md +139 -0
- data/.github/skills/reviewing-skills/SKILL.md +189 -0
- data/.github/skills/rspec-unit-testing-standards/SKILL.md +639 -0
- data/.github/skills/tdd-refactor-step/SKILL.md +236 -0
- data/.github/skills/test-debugging/SKILL.md +160 -0
- data/.github/skills/yard-documentation/SKILL.md +793 -0
- data/.github/workflows/continuous_integration.yml +3 -2
- data/.github/workflows/enforce_conventional_commits.yml +1 -1
- data/.github/workflows/experimental_continuous_integration.yml +2 -2
- data/.github/workflows/release.yml +3 -4
- data/.gitignore +8 -0
- data/.husky/pre-commit +13 -0
- data/.release-please-manifest.json +1 -1
- data/.rspec +3 -0
- data/.rubocop.yml +7 -3
- data/.rubocop_todo.yml +23 -5
- data/.yardopts +1 -0
- data/CHANGELOG.md +0 -40
- data/CONTRIBUTING.md +694 -53
- data/README.md +17 -5
- data/Rakefile +61 -9
- data/commitlint.test +4 -0
- data/git.gemspec +14 -8
- data/lib/git/args_builder.rb +0 -8
- data/lib/git/base.rb +486 -410
- data/lib/git/branch.rb +380 -43
- data/lib/git/branch_delete_failure.rb +31 -0
- data/lib/git/branch_delete_result.rb +63 -0
- data/lib/git/branch_info.rb +178 -0
- data/lib/git/branches.rb +130 -24
- data/lib/git/command_line/base.rb +245 -0
- data/lib/git/command_line/capturing.rb +249 -0
- data/lib/git/command_line/result.rb +96 -0
- data/lib/git/command_line/streaming.rb +194 -0
- data/lib/git/command_line.rb +43 -322
- data/lib/git/command_line_result.rb +4 -88
- data/lib/git/commands/add.rb +131 -0
- data/lib/git/commands/am/abort.rb +43 -0
- data/lib/git/commands/am/apply.rb +252 -0
- data/lib/git/commands/am/continue.rb +43 -0
- data/lib/git/commands/am/quit.rb +43 -0
- data/lib/git/commands/am/retry.rb +47 -0
- data/lib/git/commands/am/show_current_patch.rb +64 -0
- data/lib/git/commands/am/skip.rb +42 -0
- data/lib/git/commands/am.rb +33 -0
- data/lib/git/commands/apply.rb +237 -0
- data/lib/git/commands/archive/list_formats.rb +46 -0
- data/lib/git/commands/archive.rb +140 -0
- data/lib/git/commands/arguments.rb +3510 -0
- data/lib/git/commands/base.rb +403 -0
- data/lib/git/commands/branch/copy.rb +94 -0
- data/lib/git/commands/branch/create.rb +173 -0
- data/lib/git/commands/branch/delete.rb +80 -0
- data/lib/git/commands/branch/list.rb +162 -0
- data/lib/git/commands/branch/move.rb +94 -0
- data/lib/git/commands/branch/set_upstream.rb +86 -0
- data/lib/git/commands/branch/show_current.rb +49 -0
- data/lib/git/commands/branch/unset_upstream.rb +57 -0
- data/lib/git/commands/branch.rb +34 -0
- data/lib/git/commands/cat_file/batch.rb +364 -0
- data/lib/git/commands/cat_file/filtered.rb +105 -0
- data/lib/git/commands/cat_file/raw.rb +210 -0
- data/lib/git/commands/cat_file.rb +49 -0
- data/lib/git/commands/checkout/branch.rb +151 -0
- data/lib/git/commands/checkout/files.rb +115 -0
- data/lib/git/commands/checkout.rb +38 -0
- data/lib/git/commands/checkout_index.rb +105 -0
- data/lib/git/commands/clean.rb +100 -0
- data/lib/git/commands/clone.rb +240 -0
- data/lib/git/commands/commit.rb +272 -0
- data/lib/git/commands/commit_tree.rb +100 -0
- data/lib/git/commands/config_option_syntax/add.rb +83 -0
- data/lib/git/commands/config_option_syntax/get.rb +117 -0
- data/lib/git/commands/config_option_syntax/get_all.rb +115 -0
- data/lib/git/commands/config_option_syntax/get_color.rb +91 -0
- data/lib/git/commands/config_option_syntax/get_color_bool.rb +93 -0
- data/lib/git/commands/config_option_syntax/get_regexp.rb +115 -0
- data/lib/git/commands/config_option_syntax/get_urlmatch.rb +102 -0
- data/lib/git/commands/config_option_syntax/list.rb +107 -0
- data/lib/git/commands/config_option_syntax/remove_section.rb +74 -0
- data/lib/git/commands/config_option_syntax/rename_section.rb +78 -0
- data/lib/git/commands/config_option_syntax/replace_all.rb +104 -0
- data/lib/git/commands/config_option_syntax/set.rb +114 -0
- data/lib/git/commands/config_option_syntax/unset.rb +89 -0
- data/lib/git/commands/config_option_syntax/unset_all.rb +89 -0
- data/lib/git/commands/config_option_syntax.rb +56 -0
- data/lib/git/commands/describe.rb +155 -0
- data/lib/git/commands/diff.rb +656 -0
- data/lib/git/commands/diff_files.rb +518 -0
- data/lib/git/commands/diff_index.rb +496 -0
- data/lib/git/commands/fetch.rb +352 -0
- data/lib/git/commands/fsck.rb +136 -0
- data/lib/git/commands/gc.rb +132 -0
- data/lib/git/commands/grep.rb +338 -0
- data/lib/git/commands/init.rb +99 -0
- data/lib/git/commands/log.rb +632 -0
- data/lib/git/commands/ls_files.rb +191 -0
- data/lib/git/commands/ls_remote.rb +155 -0
- data/lib/git/commands/ls_tree.rb +131 -0
- data/lib/git/commands/maintenance/register.rb +75 -0
- data/lib/git/commands/maintenance/run.rb +104 -0
- data/lib/git/commands/maintenance/start.rb +66 -0
- data/lib/git/commands/maintenance/stop.rb +55 -0
- data/lib/git/commands/maintenance/unregister.rb +79 -0
- data/lib/git/commands/maintenance.rb +31 -0
- data/lib/git/commands/merge/abort.rb +44 -0
- data/lib/git/commands/merge/continue.rb +44 -0
- data/lib/git/commands/merge/quit.rb +46 -0
- data/lib/git/commands/merge/start.rb +245 -0
- data/lib/git/commands/merge.rb +28 -0
- data/lib/git/commands/merge_base.rb +86 -0
- data/lib/git/commands/mv.rb +77 -0
- data/lib/git/commands/name_rev.rb +114 -0
- data/lib/git/commands/pull.rb +377 -0
- data/lib/git/commands/push.rb +246 -0
- data/lib/git/commands/read_tree.rb +149 -0
- data/lib/git/commands/remote/add.rb +91 -0
- data/lib/git/commands/remote/get_url.rb +66 -0
- data/lib/git/commands/remote/list.rb +54 -0
- data/lib/git/commands/remote/prune.rb +61 -0
- data/lib/git/commands/remote/remove.rb +52 -0
- data/lib/git/commands/remote/rename.rb +69 -0
- data/lib/git/commands/remote/set_branches.rb +63 -0
- data/lib/git/commands/remote/set_head.rb +82 -0
- data/lib/git/commands/remote/set_url.rb +71 -0
- data/lib/git/commands/remote/set_url_add.rb +61 -0
- data/lib/git/commands/remote/set_url_delete.rb +64 -0
- data/lib/git/commands/remote/show.rb +71 -0
- data/lib/git/commands/remote/update.rb +72 -0
- data/lib/git/commands/remote.rb +42 -0
- data/lib/git/commands/repack.rb +277 -0
- data/lib/git/commands/reset.rb +147 -0
- data/lib/git/commands/rev_parse.rb +297 -0
- data/lib/git/commands/revert/abort.rb +45 -0
- data/lib/git/commands/revert/continue.rb +57 -0
- data/lib/git/commands/revert/quit.rb +47 -0
- data/lib/git/commands/revert/skip.rb +44 -0
- data/lib/git/commands/revert/start.rb +153 -0
- data/lib/git/commands/revert.rb +29 -0
- data/lib/git/commands/rm.rb +114 -0
- data/lib/git/commands/show.rb +632 -0
- data/lib/git/commands/show_ref/exclude_existing.rb +120 -0
- data/lib/git/commands/show_ref/exists.rb +78 -0
- data/lib/git/commands/show_ref/list.rb +145 -0
- data/lib/git/commands/show_ref/verify.rb +120 -0
- data/lib/git/commands/show_ref.rb +42 -0
- data/lib/git/commands/stash/apply.rb +75 -0
- data/lib/git/commands/stash/branch.rb +65 -0
- data/lib/git/commands/stash/clear.rb +41 -0
- data/lib/git/commands/stash/create.rb +58 -0
- data/lib/git/commands/stash/drop.rb +67 -0
- data/lib/git/commands/stash/list.rb +39 -0
- data/lib/git/commands/stash/pop.rb +78 -0
- data/lib/git/commands/stash/push.rb +103 -0
- data/lib/git/commands/stash/show.rb +149 -0
- data/lib/git/commands/stash/store.rb +63 -0
- data/lib/git/commands/stash.rb +38 -0
- data/lib/git/commands/status.rb +169 -0
- data/lib/git/commands/symbolic_ref/delete.rb +68 -0
- data/lib/git/commands/symbolic_ref/read.rb +95 -0
- data/lib/git/commands/symbolic_ref/update.rb +76 -0
- data/lib/git/commands/symbolic_ref.rb +38 -0
- data/lib/git/commands/tag/create.rb +139 -0
- data/lib/git/commands/tag/delete.rb +55 -0
- data/lib/git/commands/tag/list.rb +143 -0
- data/lib/git/commands/tag/verify.rb +71 -0
- data/lib/git/commands/tag.rb +26 -0
- data/lib/git/commands/update_ref/batch.rb +140 -0
- data/lib/git/commands/update_ref/delete.rb +92 -0
- data/lib/git/commands/update_ref/update.rb +106 -0
- data/lib/git/commands/update_ref.rb +42 -0
- data/lib/git/commands/version.rb +52 -0
- data/lib/git/commands/worktree/add.rb +140 -0
- data/lib/git/commands/worktree/list.rb +64 -0
- data/lib/git/commands/worktree/lock.rb +58 -0
- data/lib/git/commands/worktree/management_base.rb +51 -0
- data/lib/git/commands/worktree/move.rb +66 -0
- data/lib/git/commands/worktree/prune.rb +67 -0
- data/lib/git/commands/worktree/remove.rb +63 -0
- data/lib/git/commands/worktree/repair.rb +76 -0
- data/lib/git/commands/worktree/unlock.rb +47 -0
- data/lib/git/commands/worktree.rb +43 -0
- data/lib/git/commands/write_tree.rb +68 -0
- data/lib/git/commands.rb +89 -0
- data/lib/git/detached_head_info.rb +54 -0
- data/lib/git/diff.rb +297 -7
- data/lib/git/diff_file_numstat_info.rb +29 -0
- data/lib/git/diff_file_patch_info.rb +134 -0
- data/lib/git/diff_file_raw_info.rb +127 -0
- data/lib/git/diff_info.rb +169 -0
- data/lib/git/diff_path_status.rb +78 -19
- data/lib/git/diff_result.rb +32 -0
- data/lib/git/diff_stats.rb +59 -14
- data/lib/git/dirstat_info.rb +86 -0
- data/lib/git/errors.rb +65 -2
- data/lib/git/execution_context/global.rb +56 -0
- data/lib/git/execution_context/repository.rb +147 -0
- data/lib/git/execution_context.rb +482 -0
- data/lib/git/file_ref.rb +74 -0
- data/lib/git/fsck_object.rb +9 -9
- data/lib/git/fsck_result.rb +1 -1
- data/lib/git/lib.rb +1606 -1028
- data/lib/git/log.rb +15 -2
- data/lib/git/object.rb +92 -22
- data/lib/git/parsers/branch.rb +224 -0
- data/lib/git/parsers/cat_file.rb +111 -0
- data/lib/git/parsers/diff.rb +585 -0
- data/lib/git/parsers/fsck.rb +133 -0
- data/lib/git/parsers/grep.rb +42 -0
- data/lib/git/parsers/ls_tree.rb +58 -0
- data/lib/git/parsers/stash.rb +208 -0
- data/lib/git/parsers/tag.rb +257 -0
- data/lib/git/remote.rb +133 -9
- data/lib/git/repository/branching.rb +572 -0
- data/lib/git/repository/committing.rb +191 -0
- data/lib/git/repository/configuring.rb +156 -0
- data/lib/git/repository/diffing.rb +775 -0
- data/lib/git/repository/inspecting.rb +153 -0
- data/lib/git/repository/logging.rb +247 -0
- data/lib/git/repository/merging.rb +295 -0
- data/lib/git/repository/object_operations.rb +1101 -0
- data/lib/git/repository/path_resolver.rb +207 -0
- data/lib/git/repository/remote_operations.rb +753 -0
- data/lib/git/repository/shared_private.rb +51 -0
- data/lib/git/repository/staging.rb +390 -0
- data/lib/git/repository/stashing.rb +107 -0
- data/lib/git/repository/status_operations.rb +180 -0
- data/lib/git/repository/worktree_operations.rb +159 -0
- data/lib/git/repository.rb +264 -1
- data/lib/git/stash.rb +85 -4
- data/lib/git/stash_info.rb +104 -0
- data/lib/git/stashes.rb +130 -13
- data/lib/git/status.rb +224 -18
- data/lib/git/tag_delete_failure.rb +31 -0
- data/lib/git/tag_delete_result.rb +63 -0
- data/lib/git/tag_info.rb +105 -0
- data/lib/git/version.rb +109 -2
- data/lib/git/version_constraint.rb +81 -0
- data/lib/git/worktree.rb +120 -5
- data/lib/git/worktrees.rb +107 -7
- data/lib/git.rb +114 -18
- data/redesign/1_architecture_existing.md +54 -18
- data/redesign/2_architecture_redesign.md +365 -46
- data/redesign/3_architecture_implementation.md +1451 -54
- data/tasks/gem_tasks.rake +4 -0
- data/tasks/npm_tasks.rake +7 -0
- data/tasks/rspec.rake +48 -0
- data/tasks/test.rake +13 -1
- data/tasks/yard.rake +34 -7
- metadata +349 -20
- data/lib/git/index.rb +0 -6
- data/lib/git/path.rb +0 -38
- data/lib/git/working_directory.rb +0 -6
- /data/{release-please-config.json → .release-please-config.json} +0 -0
data/lib/git/log.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'git/base'
|
|
4
|
+
|
|
3
5
|
module Git
|
|
4
6
|
# Builds and executes a `git log` query
|
|
5
7
|
#
|
|
@@ -42,7 +44,7 @@ module Git
|
|
|
42
44
|
# git = Git.open('.')
|
|
43
45
|
# Git::Log.new(git)
|
|
44
46
|
#
|
|
45
|
-
# @param base [Git::Base] the git repository object
|
|
47
|
+
# @param base [Git::Repository, Git::Base] the git repository object
|
|
46
48
|
# @param max_count [Integer, Symbol, nil] the number of commits to return, or
|
|
47
49
|
# `:all` or `nil` to return all
|
|
48
50
|
#
|
|
@@ -157,10 +159,21 @@ module Git
|
|
|
157
159
|
self
|
|
158
160
|
end
|
|
159
161
|
|
|
162
|
+
# Returns the facade interface for log operations.
|
|
163
|
+
#
|
|
164
|
+
# Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy).
|
|
165
|
+
# The `is_a?` guard will be removed when {Git::Base} is deleted in Phase 4.
|
|
166
|
+
#
|
|
167
|
+
# @return [Git::Repository]
|
|
168
|
+
#
|
|
169
|
+
def log_repository
|
|
170
|
+
@base.is_a?(Git::Base) ? @base.facade_repository : @base
|
|
171
|
+
end
|
|
172
|
+
|
|
160
173
|
def run_log_if_dirty
|
|
161
174
|
return unless @dirty
|
|
162
175
|
|
|
163
|
-
log_data =
|
|
176
|
+
log_data = log_repository.full_log_commits(@options)
|
|
164
177
|
@commits = log_data.map { |c| Git::Object::Commit.new(@base, c['sha'], c) }
|
|
165
178
|
@dirty = false
|
|
166
179
|
end
|
data/lib/git/object.rb
CHANGED
|
@@ -4,6 +4,7 @@ require 'git/author'
|
|
|
4
4
|
require 'git/diff'
|
|
5
5
|
require 'git/errors'
|
|
6
6
|
require 'git/log'
|
|
7
|
+
require 'git/base'
|
|
7
8
|
|
|
8
9
|
module Git
|
|
9
10
|
# represents a git object
|
|
@@ -24,24 +25,55 @@ module Git
|
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
def sha
|
|
27
|
-
@sha ||=
|
|
28
|
+
@sha ||= object_repository.rev_parse(@objectish)
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def size
|
|
31
|
-
@size ||=
|
|
32
|
+
@size ||= object_repository.cat_file_size(@objectish)
|
|
32
33
|
end
|
|
33
34
|
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
35
|
+
# Returns the raw content of this git object or streams it into a temporary file
|
|
36
|
+
#
|
|
37
|
+
# Without a block, the full content is buffered in memory and cached, then
|
|
38
|
+
# returned as a `String`. With a block, git output is streamed directly to a
|
|
39
|
+
# temporary file on disk — suitable for large objects.
|
|
40
|
+
#
|
|
41
|
+
# @api public
|
|
42
|
+
#
|
|
43
|
+
# @overload contents
|
|
44
|
+
# Returns the cached content as a string.
|
|
45
|
+
#
|
|
46
|
+
# @return [String] the raw content of the object, cached after first call
|
|
47
|
+
#
|
|
48
|
+
# @raise [Git::FailedError] if the object does not exist or the command fails
|
|
49
|
+
#
|
|
50
|
+
# @example Get the contents of a blob
|
|
51
|
+
# git.object('HEAD:README.md').contents # => "This is a README file\n"
|
|
52
|
+
#
|
|
53
|
+
# @overload contents(&block)
|
|
54
|
+
# Streams the content to a temporary file and yields it.
|
|
55
|
+
#
|
|
56
|
+
# Git output is written directly to a file without buffering in
|
|
57
|
+
# memory. Use this form for large blobs to avoid memory pressure.
|
|
58
|
+
#
|
|
59
|
+
# @yield [file] the temporary file, positioned at the start of the content
|
|
60
|
+
#
|
|
61
|
+
# @yieldparam file [File] readable `IO` object positioned at the beginning
|
|
62
|
+
#
|
|
63
|
+
# @yieldreturn [Object] the value to return from this method
|
|
64
|
+
#
|
|
65
|
+
# @return [Object] the value returned by the block
|
|
66
|
+
#
|
|
67
|
+
# @raise [Git::FailedError] if the object does not exist or the command fails
|
|
68
|
+
#
|
|
69
|
+
# @example Read a large blob without loading it into memory
|
|
70
|
+
# git.object('HEAD:large_file.bin').contents { |f| upload(f) }
|
|
38
71
|
#
|
|
39
|
-
# Use this for large files so that they are not held in memory.
|
|
40
72
|
def contents(&)
|
|
41
73
|
if block_given?
|
|
42
|
-
|
|
74
|
+
object_repository.cat_file_contents(@objectish, &)
|
|
43
75
|
else
|
|
44
|
-
@contents ||=
|
|
76
|
+
@contents ||= object_repository.cat_file_contents(@objectish)
|
|
45
77
|
end
|
|
46
78
|
end
|
|
47
79
|
|
|
@@ -54,8 +86,7 @@ module Git
|
|
|
54
86
|
end
|
|
55
87
|
|
|
56
88
|
def grep(string, path_limiter = nil, opts = {})
|
|
57
|
-
|
|
58
|
-
@base.lib.grep(string, opts)
|
|
89
|
+
object_repository.grep(string, path_limiter, opts.merge(object: sha))
|
|
59
90
|
end
|
|
60
91
|
|
|
61
92
|
def diff(objectish)
|
|
@@ -66,9 +97,23 @@ module Git
|
|
|
66
97
|
Git::Log.new(@base, count).object(@objectish)
|
|
67
98
|
end
|
|
68
99
|
|
|
69
|
-
#
|
|
100
|
+
# Creates an archive of this object and writes it to a file
|
|
101
|
+
#
|
|
102
|
+
# @api public
|
|
103
|
+
#
|
|
104
|
+
# @param file [String, nil] destination file path; a temp file is created if `nil`
|
|
105
|
+
#
|
|
106
|
+
# @param opts [Hash] archive options (see {Git::Lib#archive})
|
|
107
|
+
#
|
|
108
|
+
# @return [String] the path to the written archive file
|
|
109
|
+
#
|
|
110
|
+
# @raise [Git::FailedError] if `git archive` fails
|
|
111
|
+
#
|
|
112
|
+
# @example Archive a tree to a zip file
|
|
113
|
+
# git.object('v1.0').archive('/tmp/release.zip', format: 'zip')
|
|
114
|
+
#
|
|
70
115
|
def archive(file = nil, opts = {})
|
|
71
|
-
|
|
116
|
+
object_repository.archive(@objectish, file, opts)
|
|
72
117
|
end
|
|
73
118
|
|
|
74
119
|
def tree? = false
|
|
@@ -78,6 +123,19 @@ module Git
|
|
|
78
123
|
def commit? = false
|
|
79
124
|
|
|
80
125
|
def tag? = false
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
# Returns the facade interface for git object queries.
|
|
130
|
+
#
|
|
131
|
+
# Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy).
|
|
132
|
+
# The `is_a?` guard will be removed when {Git::Base} is deleted in Phase 4.
|
|
133
|
+
#
|
|
134
|
+
# @return [Git::Repository]
|
|
135
|
+
#
|
|
136
|
+
def object_repository
|
|
137
|
+
@base.is_a?(Git::Base) ? @base.facade_repository : @base
|
|
138
|
+
end
|
|
81
139
|
end
|
|
82
140
|
|
|
83
141
|
# A Git blob object
|
|
@@ -117,11 +175,11 @@ module Git
|
|
|
117
175
|
alias subdirectories trees
|
|
118
176
|
|
|
119
177
|
def full_tree
|
|
120
|
-
|
|
178
|
+
object_repository.full_tree(@objectish)
|
|
121
179
|
end
|
|
122
180
|
|
|
123
181
|
def depth
|
|
124
|
-
|
|
182
|
+
object_repository.tree_depth(@objectish)
|
|
125
183
|
end
|
|
126
184
|
|
|
127
185
|
def tree?
|
|
@@ -135,7 +193,7 @@ module Git
|
|
|
135
193
|
@trees = {}
|
|
136
194
|
@blobs = {}
|
|
137
195
|
|
|
138
|
-
data =
|
|
196
|
+
data = object_repository.ls_tree(@objectish)
|
|
139
197
|
|
|
140
198
|
data['tree'].each do |key, tree|
|
|
141
199
|
@trees[key] = Git::Object::Tree.new(@base, tree[:sha], tree[:mode])
|
|
@@ -169,7 +227,7 @@ module Git
|
|
|
169
227
|
end
|
|
170
228
|
|
|
171
229
|
def name
|
|
172
|
-
|
|
230
|
+
object_repository.name_rev(sha)
|
|
173
231
|
end
|
|
174
232
|
|
|
175
233
|
def gtree
|
|
@@ -239,7 +297,7 @@ module Git
|
|
|
239
297
|
def check_commit
|
|
240
298
|
return if @tree
|
|
241
299
|
|
|
242
|
-
data =
|
|
300
|
+
data = object_repository.cat_file_commit(@objectish)
|
|
243
301
|
from_data(data)
|
|
244
302
|
end
|
|
245
303
|
end
|
|
@@ -268,7 +326,8 @@ module Git
|
|
|
268
326
|
def initialize(base, sha, name = nil)
|
|
269
327
|
if name.nil?
|
|
270
328
|
name = sha
|
|
271
|
-
|
|
329
|
+
repo = base.is_a?(Git::Base) ? base.facade_repository : base
|
|
330
|
+
sha = repo.tag_sha(name)
|
|
272
331
|
raise Git::UnexpectedResultError, "Tag '#{name}' does not exist." if sha == ''
|
|
273
332
|
end
|
|
274
333
|
|
|
@@ -280,7 +339,7 @@ module Git
|
|
|
280
339
|
end
|
|
281
340
|
|
|
282
341
|
def annotated?
|
|
283
|
-
@annotated = @annotated.nil? ? (
|
|
342
|
+
@annotated = @annotated.nil? ? (object_repository.cat_file_type(name) == 'tag') : @annotated
|
|
284
343
|
end
|
|
285
344
|
|
|
286
345
|
def message
|
|
@@ -303,7 +362,7 @@ module Git
|
|
|
303
362
|
return if @loaded
|
|
304
363
|
|
|
305
364
|
if annotated?
|
|
306
|
-
tdata =
|
|
365
|
+
tdata = object_repository.cat_file_tag(@name)
|
|
307
366
|
@message = tdata['message'].chomp
|
|
308
367
|
@tagger = Git::Author.new(tdata['tagger'])
|
|
309
368
|
else
|
|
@@ -319,7 +378,7 @@ module Git
|
|
|
319
378
|
def self.new(base, objectish, type = nil, is_tag = false) # rubocop:disable Style/OptionalBooleanParameter
|
|
320
379
|
return new_tag(base, objectish) if is_tag
|
|
321
380
|
|
|
322
|
-
type ||= base.
|
|
381
|
+
type ||= object_repository_for(base).cat_file_type(objectish)
|
|
323
382
|
# TODO: why not handle tag case here too?
|
|
324
383
|
klass =
|
|
325
384
|
case type
|
|
@@ -334,5 +393,16 @@ module Git
|
|
|
334
393
|
Git::Deprecation.warn('Git::Object.new with is_tag argument is deprecated. Use Git::Object::Tag.new instead.')
|
|
335
394
|
Git::Object::Tag.new(base, objectish)
|
|
336
395
|
end
|
|
396
|
+
|
|
397
|
+
# Returns the facade interface for git object queries.
|
|
398
|
+
#
|
|
399
|
+
# Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy).
|
|
400
|
+
# The `is_a?` guard will be removed when {Git::Base} is deleted in Phase 4.
|
|
401
|
+
#
|
|
402
|
+
# @return [Git::Repository]
|
|
403
|
+
#
|
|
404
|
+
private_class_method def self.object_repository_for(base)
|
|
405
|
+
base.is_a?(Git::Base) ? base.facade_repository : base
|
|
406
|
+
end
|
|
337
407
|
end
|
|
338
408
|
end
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'git/branch_info'
|
|
4
|
+
require 'git/branch_delete_result'
|
|
5
|
+
require 'git/branch_delete_failure'
|
|
6
|
+
|
|
7
|
+
module Git
|
|
8
|
+
module Parsers
|
|
9
|
+
# Parser for git branch command output
|
|
10
|
+
#
|
|
11
|
+
# Handles parsing of `git branch --list` and `git branch --delete` output
|
|
12
|
+
# into structured data objects.
|
|
13
|
+
#
|
|
14
|
+
# ## Design Note: Namespace Organization
|
|
15
|
+
#
|
|
16
|
+
# This parser creates and returns {Git::BranchInfo} and {Git::BranchDeleteResult}
|
|
17
|
+
# objects, which live at the top-level `Git::` namespace rather than within
|
|
18
|
+
# `Git::Parsers::`. This is intentional:
|
|
19
|
+
#
|
|
20
|
+
# - **Parsers are infrastructure** - marked `@api private`, users shouldn't
|
|
21
|
+
# interact with them directly
|
|
22
|
+
# - **Info/Result classes are public API** - returned by commands and used
|
|
23
|
+
# throughout the codebase
|
|
24
|
+
# - **Info classes are domain entities** - represent core git concepts
|
|
25
|
+
# (branches as data)
|
|
26
|
+
# - **Result classes are operation outcomes** - represent command results,
|
|
27
|
+
# not parsing details
|
|
28
|
+
#
|
|
29
|
+
# Keeping Info/Result classes at `Git::` improves discoverability and correctly
|
|
30
|
+
# reflects their role as public types rather than parser internals.
|
|
31
|
+
#
|
|
32
|
+
# @api private
|
|
33
|
+
#
|
|
34
|
+
module Branch
|
|
35
|
+
# Format string for git branch --format
|
|
36
|
+
#
|
|
37
|
+
# Fields (pipe-delimited):
|
|
38
|
+
# 1. refname - full ref name (e.g., refs/heads/main, refs/remotes/origin/main)
|
|
39
|
+
# 2. objectname - full SHA of the commit the branch points to
|
|
40
|
+
# 3. HEAD - '*' if current branch, empty otherwise
|
|
41
|
+
# 4. worktreepath - path if checked out in another worktree, empty otherwise
|
|
42
|
+
# 5. symref - target ref if symbolic reference, empty otherwise
|
|
43
|
+
# 6. upstream - full upstream ref (e.g., refs/remotes/origin/main), empty if none
|
|
44
|
+
#
|
|
45
|
+
FORMAT_STRING = '%(refname)|%(objectname)|%(HEAD)|%(worktreepath)|%(symref)|%(upstream)'
|
|
46
|
+
|
|
47
|
+
# Delimiter used in format output
|
|
48
|
+
FIELD_DELIMITER = '|'
|
|
49
|
+
|
|
50
|
+
# Regex to parse successful deletion lines from stdout
|
|
51
|
+
# Matches: Deleted branch branchname (was abc123).
|
|
52
|
+
# Matches: Deleted remote-tracking branch origin/branchname (was abc123).
|
|
53
|
+
# Uses non-greedy match to capture branch names containing spaces
|
|
54
|
+
DELETED_BRANCH_REGEX = /^Deleted (?:remote-tracking )?branch (.+?) \(was/
|
|
55
|
+
|
|
56
|
+
# Regex to parse error messages from stderr
|
|
57
|
+
# Matches: error: branch 'branchname' not found.
|
|
58
|
+
ERROR_BRANCH_REGEX = /^error: branch '([^']+)'(.*)$/
|
|
59
|
+
|
|
60
|
+
module_function
|
|
61
|
+
|
|
62
|
+
# Parse git branch --list output into BranchInfo objects
|
|
63
|
+
#
|
|
64
|
+
# @example
|
|
65
|
+
# Git::Parsers::Branch.parse_list("refs/heads/main|abc1234|*|||\nrefs/heads/feature|def5678||||\n")
|
|
66
|
+
# # => [#<data Git::BranchInfo refname="main", ...>, #<data Git::BranchInfo refname="feature", ...>]
|
|
67
|
+
#
|
|
68
|
+
# @param stdout [String] output from git branch --list --format=...
|
|
69
|
+
#
|
|
70
|
+
# @return [Array<Git::BranchInfo>] parsed branch information
|
|
71
|
+
#
|
|
72
|
+
def parse_list(stdout)
|
|
73
|
+
stdout.split("\n").filter_map { |line| parse_branch_line(line) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Parse a single formatted branch line
|
|
77
|
+
#
|
|
78
|
+
# @param line [String] the line to parse (pipe-delimited fields)
|
|
79
|
+
# @return [Git::BranchInfo, nil] branch info object, or nil if line should be skipped
|
|
80
|
+
#
|
|
81
|
+
def parse_branch_line(line)
|
|
82
|
+
fields = line.split(FIELD_DELIMITER, 6)
|
|
83
|
+
|
|
84
|
+
return nil if non_branch_entry?(fields[0])
|
|
85
|
+
|
|
86
|
+
build_branch_info(fields)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Build a BranchInfo from parsed fields
|
|
90
|
+
#
|
|
91
|
+
# @param fields [Array<String>] the parsed fields: [refname, objectname, head, worktreepath, symref, upstream]
|
|
92
|
+
# @return [Git::BranchInfo] the branch info object
|
|
93
|
+
#
|
|
94
|
+
def build_branch_info(fields)
|
|
95
|
+
raw_refname, objectname, head, worktreepath, symref, upstream = fields
|
|
96
|
+
current = head == '*'
|
|
97
|
+
|
|
98
|
+
Git::BranchInfo.new(
|
|
99
|
+
refname: normalize_refname(raw_refname),
|
|
100
|
+
target_oid: presence(objectname),
|
|
101
|
+
current: current,
|
|
102
|
+
worktree: in_other_worktree?(worktreepath, current),
|
|
103
|
+
symref: presence(symref),
|
|
104
|
+
upstream: build_upstream_info(upstream)
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Check if the refname represents a detached HEAD state or non-branch entry
|
|
109
|
+
#
|
|
110
|
+
# Git outputs special entries for detached HEAD and non-branch states:
|
|
111
|
+
# - "(HEAD detached at <ref>)" when in detached HEAD state
|
|
112
|
+
# - "(not a branch)" for non-branch entries
|
|
113
|
+
#
|
|
114
|
+
# @param refname [String] the refname to check
|
|
115
|
+
# @return [Boolean] true if this is a non-branch entry
|
|
116
|
+
#
|
|
117
|
+
def non_branch_entry?(refname)
|
|
118
|
+
refname.match?(/^\(HEAD detached/) || refname.match?(/^\(not a branch\)/)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Normalize a full refname to the expected format
|
|
122
|
+
#
|
|
123
|
+
# Converts:
|
|
124
|
+
# - refs/heads/main -> main
|
|
125
|
+
# - refs/remotes/origin/main -> remotes/origin/main
|
|
126
|
+
#
|
|
127
|
+
# @param refname [String] the full refname from git
|
|
128
|
+
# @return [String] normalized refname
|
|
129
|
+
#
|
|
130
|
+
def normalize_refname(refname)
|
|
131
|
+
refname.sub(%r{^refs/heads/}, '').sub(%r{^refs/}, '')
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Check if the branch is checked out in another worktree
|
|
135
|
+
#
|
|
136
|
+
# worktree is true when the branch is checked out in ANOTHER worktree
|
|
137
|
+
# (worktreepath is non-empty AND it's not the current branch)
|
|
138
|
+
#
|
|
139
|
+
# @param worktreepath [String, nil] the worktree path from git output
|
|
140
|
+
# @param current [Boolean] whether this is the current branch
|
|
141
|
+
# @return [Boolean] true if checked out in another worktree
|
|
142
|
+
#
|
|
143
|
+
def in_other_worktree?(worktreepath, current)
|
|
144
|
+
has_worktree = !worktreepath.nil? && !worktreepath.empty?
|
|
145
|
+
has_worktree && !current
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Build upstream BranchInfo from upstream refname
|
|
149
|
+
#
|
|
150
|
+
# @param upstream_ref [String, nil] the upstream ref (e.g., 'refs/remotes/origin/main')
|
|
151
|
+
# @return [Git::BranchInfo, nil] upstream branch info or nil
|
|
152
|
+
#
|
|
153
|
+
def build_upstream_info(upstream_ref)
|
|
154
|
+
return nil if upstream_ref.nil? || upstream_ref.empty?
|
|
155
|
+
|
|
156
|
+
Git::BranchInfo.new(
|
|
157
|
+
refname: normalize_refname(upstream_ref),
|
|
158
|
+
target_oid: nil, # We don't have upstream's OID from this format
|
|
159
|
+
current: false,
|
|
160
|
+
worktree: false,
|
|
161
|
+
symref: nil,
|
|
162
|
+
upstream: nil # Upstream branches don't have their own upstream in this context
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Return value if non-empty, nil otherwise
|
|
167
|
+
#
|
|
168
|
+
# @param value [String, nil] the value to check
|
|
169
|
+
# @return [String, nil] the value or nil
|
|
170
|
+
#
|
|
171
|
+
def presence(value)
|
|
172
|
+
value.nil? || value.empty? ? nil : value
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Parse deleted branch names from stdout
|
|
176
|
+
#
|
|
177
|
+
# @example
|
|
178
|
+
# BranchParser.parse_deleted_branches("Deleted branch feature (was abc123).\n")
|
|
179
|
+
# # => ["feature"]
|
|
180
|
+
#
|
|
181
|
+
# @param stdout [String] command stdout
|
|
182
|
+
# @return [Array<String>] names of successfully deleted branches
|
|
183
|
+
#
|
|
184
|
+
def parse_deleted_branches(stdout)
|
|
185
|
+
stdout.scan(DELETED_BRANCH_REGEX).flatten
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Parse error messages from stderr into a map
|
|
189
|
+
#
|
|
190
|
+
# @example
|
|
191
|
+
# BranchParser.parse_error_messages("error: branch 'missing' not found.\n")
|
|
192
|
+
# # => {"missing" => "error: branch 'missing' not found."}
|
|
193
|
+
#
|
|
194
|
+
# @param stderr [String] command stderr
|
|
195
|
+
# @return [Hash<String, String>] map of branch name to error message
|
|
196
|
+
#
|
|
197
|
+
def parse_error_messages(stderr)
|
|
198
|
+
stderr.each_line.with_object({}) do |line, hash|
|
|
199
|
+
match = line.match(ERROR_BRANCH_REGEX)
|
|
200
|
+
hash[match[1]] = line.strip if match
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Build the BranchDeleteResult from parsed data
|
|
205
|
+
#
|
|
206
|
+
# @param requested_names [Array<String>] originally requested branch names
|
|
207
|
+
# @param existing_branches [Hash<String, Git::BranchInfo>] branches that existed before delete
|
|
208
|
+
# @param deleted_names [Array<String>] names confirmed deleted in stdout
|
|
209
|
+
# @param error_map [Hash<String, String>] map of branch name to error message
|
|
210
|
+
# @return [Git::BranchDeleteResult] the result object
|
|
211
|
+
#
|
|
212
|
+
def build_delete_result(requested_names, existing_branches, deleted_names, error_map)
|
|
213
|
+
deleted = deleted_names.filter_map { |name| existing_branches[name] }
|
|
214
|
+
|
|
215
|
+
not_deleted = (requested_names - deleted_names).map do |name|
|
|
216
|
+
error_message = error_map[name] || "branch '#{name}' could not be deleted"
|
|
217
|
+
Git::BranchDeleteFailure.new(name: name, error_message: error_message)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
Git::BranchDeleteResult.new(deleted: deleted, not_deleted: not_deleted)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
module Parsers
|
|
5
|
+
# Parser for `git cat-file` commit and tag output
|
|
6
|
+
#
|
|
7
|
+
# Provides class methods that transform raw `git cat-file` output lines into
|
|
8
|
+
# structured Hash objects consumed by the `Git::Repository::ObjectOperations`
|
|
9
|
+
# facade.
|
|
10
|
+
#
|
|
11
|
+
# @api private
|
|
12
|
+
#
|
|
13
|
+
module CatFile
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# Matches a single `git cat-file` header line
|
|
17
|
+
#
|
|
18
|
+
# @api private
|
|
19
|
+
#
|
|
20
|
+
CAT_FILE_HEADER_LINE = /\A(?<key>\w+) (?<value>.*)\z/
|
|
21
|
+
|
|
22
|
+
# Parse `git cat-file commit` output into a structured Hash
|
|
23
|
+
#
|
|
24
|
+
# @param lines [Array<String>] mutable cat-file output lines, consumed
|
|
25
|
+
# in place during header parsing
|
|
26
|
+
#
|
|
27
|
+
# @param sha [String] the object name passed by the caller
|
|
28
|
+
#
|
|
29
|
+
# @return [Hash] commit data hash with string keys
|
|
30
|
+
#
|
|
31
|
+
# @api private
|
|
32
|
+
#
|
|
33
|
+
def parse_commit(lines, sha)
|
|
34
|
+
headers = parse_commit_headers(lines)
|
|
35
|
+
message = "#{lines.join("\n")}\n"
|
|
36
|
+
{ 'sha' => sha, 'message' => message }.merge(headers)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Parse `git cat-file tag` output into a structured Hash
|
|
40
|
+
#
|
|
41
|
+
# @param lines [Array<String>] mutable cat-file output lines, consumed
|
|
42
|
+
# in place during header parsing; remaining lines become the message
|
|
43
|
+
#
|
|
44
|
+
# @param name [String] the tag name passed by the caller
|
|
45
|
+
#
|
|
46
|
+
# @return [Hash] tag data hash with string keys
|
|
47
|
+
#
|
|
48
|
+
# @api private
|
|
49
|
+
#
|
|
50
|
+
def parse_tag(lines, name)
|
|
51
|
+
hsh = { 'name' => name }
|
|
52
|
+
each_header(lines) { |key, value| hsh[key] = value }
|
|
53
|
+
hsh['message'] = "#{lines.join("\n")}\n"
|
|
54
|
+
hsh
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Extracts and returns commit headers from the front of `lines`
|
|
58
|
+
#
|
|
59
|
+
# Mutates `lines` in place, consuming header lines and the blank
|
|
60
|
+
# separator line. After the call `lines` contains only message lines.
|
|
61
|
+
#
|
|
62
|
+
# @param lines [Array<String>] mutable cat-file output lines
|
|
63
|
+
#
|
|
64
|
+
# @return [Hash] parsed header key/value pairs; `parent` is always
|
|
65
|
+
# an Array
|
|
66
|
+
#
|
|
67
|
+
# @api private
|
|
68
|
+
#
|
|
69
|
+
def parse_commit_headers(lines)
|
|
70
|
+
headers = { 'parent' => [] }
|
|
71
|
+
each_header(lines) do |key, value|
|
|
72
|
+
if key == 'parent'
|
|
73
|
+
headers['parent'] << value
|
|
74
|
+
else
|
|
75
|
+
headers[key] = value
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
headers
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Yields parsed header key/value pairs from `git cat-file` output lines
|
|
82
|
+
#
|
|
83
|
+
# Consumes header lines from the front of `lines` until a blank line is
|
|
84
|
+
# encountered. Continuation lines that begin with a space are folded
|
|
85
|
+
# into the previous header value using newline separators.
|
|
86
|
+
#
|
|
87
|
+
# @param lines [Array<String>] mutable output lines from a cat-file response
|
|
88
|
+
#
|
|
89
|
+
# @yield [key, value] each parsed header pair
|
|
90
|
+
#
|
|
91
|
+
# @yieldparam key [String] header field name
|
|
92
|
+
#
|
|
93
|
+
# @yieldparam value [String] unfolded header value text
|
|
94
|
+
#
|
|
95
|
+
# @yieldreturn [void]
|
|
96
|
+
#
|
|
97
|
+
# @return [void]
|
|
98
|
+
#
|
|
99
|
+
# @api private
|
|
100
|
+
#
|
|
101
|
+
def each_header(lines)
|
|
102
|
+
while (line = lines.shift) && (match = CAT_FILE_HEADER_LINE.match(line))
|
|
103
|
+
key = match[:key]
|
|
104
|
+
value_lines = [match[:value]]
|
|
105
|
+
value_lines << lines.shift.lstrip while lines.first&.start_with?(' ')
|
|
106
|
+
yield key, value_lines.join("\n")
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|