git 4.3.2 → 5.0.0.beta.2
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 +73 -2705
- data/.github/prompts/iteratively-address-copilot-reviews.prompt.md +188 -0
- 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/KEYWORD_ARG_REMEDIATION.md +22 -0
- data/.github/skills/extract-facade-from-base-lib/SKILL.md +600 -0
- data/.github/skills/facade-implementation/REFERENCE.md +840 -0
- data/.github/skills/facade-implementation/SKILL.md +274 -0
- data/.github/skills/facade-test-conventions/SKILL.md +394 -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 +12 -3
- data/.rubocop_todo.yml +23 -5
- data/.yardopts +1 -0
- data/CHANGELOG.md +0 -40
- data/CONTRIBUTING.md +694 -53
- data/README.md +67 -15
- data/Rakefile +61 -9
- data/UPGRADING.md +141 -0
- data/commitlint.test +4 -0
- data/git.gemspec +18 -7
- data/lib/git/args_builder.rb +0 -8
- data/lib/git/branch.rb +369 -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 +122 -24
- data/lib/git/command_line/base.rb +255 -0
- data/lib/git/command_line/capturing.rb +251 -0
- data/lib/git/command_line/result.rb +96 -0
- data/lib/git/command_line/streaming.rb +196 -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 +404 -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 +369 -0
- data/lib/git/commands/cat_file/filtered.rb +105 -0
- data/lib/git/commands/cat_file/raw.rb +216 -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 +107 -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 +57 -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 +87 -0
- data/lib/git/config.rb +17 -0
- data/lib/git/config_entry_info.rb +106 -0
- data/lib/git/configuring.rb +665 -0
- data/lib/git/deprecation.rb +9 -0
- data/lib/git/detached_head_info.rb +54 -0
- data/lib/git/diff.rb +293 -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 +67 -19
- data/lib/git/diff_result.rb +32 -0
- data/lib/git/diff_stats.rb +51 -14
- data/lib/git/dirstat_info.rb +86 -0
- data/lib/git/errors.rb +65 -2
- data/lib/git/execution_context/global.rb +31 -0
- data/lib/git/execution_context/repository.rb +136 -0
- data/lib/git/execution_context.rb +501 -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/log.rb +9 -2
- data/lib/git/object.rb +90 -27
- data/lib/git/parsers/branch.rb +224 -0
- data/lib/git/parsers/cat_file.rb +111 -0
- data/lib/git/parsers/config_entry.rb +110 -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_remote.rb +79 -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 +120 -9
- data/lib/git/repository/branching.rb +743 -0
- data/lib/git/repository/committing.rb +187 -0
- data/lib/git/repository/configuring.rb +351 -0
- data/lib/git/repository/context_helpers.rb +264 -0
- data/lib/git/repository/diffing.rb +775 -0
- data/lib/git/repository/factories.rb +682 -0
- data/lib/git/repository/inspecting.rb +252 -0
- data/lib/git/repository/logging.rb +247 -0
- data/lib/git/repository/maintenance.rb +65 -0
- data/lib/git/repository/merging.rb +357 -0
- data/lib/git/repository/object_operations.rb +1199 -0
- data/lib/git/repository/path_resolver.rb +207 -0
- data/lib/git/repository/remote_operations.rb +898 -0
- data/lib/git/repository/shared_private.rb +51 -0
- data/lib/git/repository/staging.rb +554 -0
- data/lib/git/repository/stashing.rb +143 -0
- data/lib/git/repository/status_operations.rb +201 -0
- data/lib/git/repository/worktree_operations.rb +159 -0
- data/lib/git/repository.rb +203 -1
- data/lib/git/stash.rb +78 -4
- data/lib/git/stash_info.rb +104 -0
- data/lib/git/stashes.rb +125 -13
- data/lib/git/status.rb +215 -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 +108 -6
- data/lib/git/worktrees.rb +94 -7
- data/lib/git.rb +260 -61
- data/redesign/1_architecture_existing.md +54 -18
- data/redesign/2_architecture_redesign.md +365 -46
- data/redesign/3_architecture_implementation.md +1497 -63
- data/redesign/Phase 4 - Step A.md +360 -0
- data/redesign/beta_release.md +107 -0
- data/redesign/c1c2_audit.md +566 -0
- data/redesign/c1c2_bucket6_lib_orphans.md +626 -0
- data/redesign/config_design.rb +501 -0
- 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 +365 -22
- data/lib/git/base.rb +0 -1128
- data/lib/git/index.rb +0 -6
- data/lib/git/lib.rb +0 -2277
- 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
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'git/fsck_object'
|
|
4
|
+
require 'git/fsck_result'
|
|
5
|
+
|
|
6
|
+
module Git
|
|
7
|
+
module Parsers
|
|
8
|
+
# Parser for git fsck command output
|
|
9
|
+
#
|
|
10
|
+
# Handles parsing of `git fsck` output into structured data objects
|
|
11
|
+
# for dangling, missing, unreachable objects, warnings, roots, and tagged objects.
|
|
12
|
+
#
|
|
13
|
+
# ## Design Note: Namespace Organization
|
|
14
|
+
#
|
|
15
|
+
# This parser creates and returns {Git::FsckObject} and {Git::FsckResult}
|
|
16
|
+
# objects, which live at the top-level `Git::` namespace rather than within
|
|
17
|
+
# `Git::Parsers::`. This is intentional:
|
|
18
|
+
#
|
|
19
|
+
# - **Parsers are infrastructure** - marked `@api private`, users shouldn't
|
|
20
|
+
# interact with them directly
|
|
21
|
+
# - **Result classes are public API** - returned by commands and used
|
|
22
|
+
# throughout the codebase
|
|
23
|
+
# - **FsckObject and FsckResult represent operation outcomes** - they describe
|
|
24
|
+
# repository integrity status, not parsing details
|
|
25
|
+
#
|
|
26
|
+
# Keeping these classes at `Git::` improves discoverability and correctly
|
|
27
|
+
# reflects their role as public types rather than parser internals.
|
|
28
|
+
#
|
|
29
|
+
# @api private
|
|
30
|
+
#
|
|
31
|
+
module Fsck
|
|
32
|
+
# Pattern matcher for dangling/missing/unreachable object lines
|
|
33
|
+
# Matches lines like:
|
|
34
|
+
# dangling commit abc123...
|
|
35
|
+
# missing blob def456...
|
|
36
|
+
# unreachable tree 789abc... (name)
|
|
37
|
+
OBJECT_PATTERN = /\A(dangling|missing|unreachable) (\w+) ([0-9a-f]{40})(?: \((.+)\))?\z/
|
|
38
|
+
|
|
39
|
+
# Pattern matcher for warning lines
|
|
40
|
+
# Matches lines like:
|
|
41
|
+
# warning in commit abc123...: message here
|
|
42
|
+
WARNING_PATTERN = /\Awarning in (\w+) ([0-9a-f]{40}): (.+)\z/
|
|
43
|
+
|
|
44
|
+
# Pattern matcher for root commit lines
|
|
45
|
+
# Matches lines like:
|
|
46
|
+
# root abc123...
|
|
47
|
+
ROOT_PATTERN = /\Aroot ([0-9a-f]{40})\z/
|
|
48
|
+
|
|
49
|
+
# Pattern matcher for tagged object lines
|
|
50
|
+
# Matches lines like:
|
|
51
|
+
# tagged commit abc123... (tagname) in def456...
|
|
52
|
+
TAGGED_PATTERN = /\Atagged (\w+) ([0-9a-f]{40}) \((.+)\) in ([0-9a-f]{40})\z/
|
|
53
|
+
|
|
54
|
+
module_function
|
|
55
|
+
|
|
56
|
+
# Parse git fsck output into a FsckResult object
|
|
57
|
+
#
|
|
58
|
+
# @example
|
|
59
|
+
# FsckParser.parse("dangling commit abc123...\nmissing blob def456...\n")
|
|
60
|
+
# # => #<Git::FsckResult dangling: [...], missing: [...]>
|
|
61
|
+
#
|
|
62
|
+
# @param stdout [String] output from git fsck command
|
|
63
|
+
# @return [Git::FsckResult] the parsed result
|
|
64
|
+
#
|
|
65
|
+
def parse(stdout)
|
|
66
|
+
result = { dangling: [], missing: [], unreachable: [], warnings: [], root: [], tagged: [] }
|
|
67
|
+
stdout.each_line { |line| parse_line(line.strip, result) }
|
|
68
|
+
Git::FsckResult.new(**result)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Parse a single line of fsck output
|
|
72
|
+
#
|
|
73
|
+
# @param line [String] a line of output
|
|
74
|
+
# @param result [Hash] the result hash to populate
|
|
75
|
+
# @return [Boolean] true if the line was parsed
|
|
76
|
+
#
|
|
77
|
+
def parse_line(line, result)
|
|
78
|
+
parse_object_line(line, result) ||
|
|
79
|
+
parse_warning_line(line, result) ||
|
|
80
|
+
parse_root_line(line, result) ||
|
|
81
|
+
parse_tagged_line(line, result)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Parse a dangling/missing/unreachable object line
|
|
85
|
+
#
|
|
86
|
+
# @param line [String] a line of output
|
|
87
|
+
# @param result [Hash] the result hash to populate
|
|
88
|
+
# @return [Boolean] true if the line was parsed
|
|
89
|
+
#
|
|
90
|
+
def parse_object_line(line, result)
|
|
91
|
+
return unless (match = OBJECT_PATTERN.match(line))
|
|
92
|
+
|
|
93
|
+
result[match[1].to_sym] << Git::FsckObject.new(type: match[2].to_sym, oid: match[3], name: match[4])
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Parse a warning line
|
|
97
|
+
#
|
|
98
|
+
# @param line [String] a line of output
|
|
99
|
+
# @param result [Hash] the result hash to populate
|
|
100
|
+
# @return [Boolean] true if the line was parsed
|
|
101
|
+
#
|
|
102
|
+
def parse_warning_line(line, result)
|
|
103
|
+
return unless (match = WARNING_PATTERN.match(line))
|
|
104
|
+
|
|
105
|
+
result[:warnings] << Git::FsckObject.new(type: match[1].to_sym, oid: match[2], message: match[3])
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Parse a root line
|
|
109
|
+
#
|
|
110
|
+
# @param line [String] a line of output
|
|
111
|
+
# @param result [Hash] the result hash to populate
|
|
112
|
+
# @return [Boolean] true if the line was parsed
|
|
113
|
+
#
|
|
114
|
+
def parse_root_line(line, result)
|
|
115
|
+
return unless (match = ROOT_PATTERN.match(line))
|
|
116
|
+
|
|
117
|
+
result[:root] << Git::FsckObject.new(type: :commit, oid: match[1])
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Parse a tagged line
|
|
121
|
+
#
|
|
122
|
+
# @param line [String] a line of output
|
|
123
|
+
# @param result [Hash] the result hash to populate
|
|
124
|
+
# @return [Boolean] true if the line was parsed
|
|
125
|
+
#
|
|
126
|
+
def parse_tagged_line(line, result)
|
|
127
|
+
return unless (match = TAGGED_PATTERN.match(line))
|
|
128
|
+
|
|
129
|
+
result[:tagged] << Git::FsckObject.new(type: match[1].to_sym, oid: match[2], name: match[3])
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
module Parsers
|
|
5
|
+
# Parser for `git grep` output
|
|
6
|
+
#
|
|
7
|
+
# Provides a class method that transforms raw `git grep --null` output into a
|
|
8
|
+
# structured Hash consumed by the `Git::Repository::ObjectOperations` facade.
|
|
9
|
+
#
|
|
10
|
+
# This parser is a pure text transformer with no exit-status logic. The
|
|
11
|
+
# calling facade is responsible for interpreting the command's exit status
|
|
12
|
+
# before delegating output parsing to this class.
|
|
13
|
+
#
|
|
14
|
+
# @api private
|
|
15
|
+
#
|
|
16
|
+
module Grep
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# Parse `git grep --line-number --null --no-color` output into a match hash
|
|
20
|
+
#
|
|
21
|
+
# With `--null`, git separates the path and line number fields with NUL
|
|
22
|
+
# bytes: `treeish:filename\0linenum\0text\n`. This keeps filenames that
|
|
23
|
+
# contain `:<digits>:` from being confused with the line-number delimiter.
|
|
24
|
+
#
|
|
25
|
+
# @param output [String] raw output from `git grep --null --line-number`
|
|
26
|
+
#
|
|
27
|
+
# @return [Hash<String, Array<Array(Integer, String)>>] hash mapping
|
|
28
|
+
# `"treeish:filename"` keys to arrays of `[line_number, text]` pairs
|
|
29
|
+
#
|
|
30
|
+
# @api private
|
|
31
|
+
#
|
|
32
|
+
def parse(output)
|
|
33
|
+
output.each_line.with_object(Hash.new { |h, k| h[k] = [] }) do |line, hsh|
|
|
34
|
+
filename, line_num, text = line.chomp.split("\0", 3)
|
|
35
|
+
next unless text && line_num&.match?(/\A\d+\z/)
|
|
36
|
+
|
|
37
|
+
hsh[filename] << [line_num.to_i, text]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
module Parsers
|
|
5
|
+
# Parser for `git ls-remote` command output
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
#
|
|
9
|
+
module LsRemote
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# Parse `git ls-remote` stdout lines into a structured Hash
|
|
13
|
+
#
|
|
14
|
+
# @param lines [Array<String>] the individual stdout lines
|
|
15
|
+
#
|
|
16
|
+
# @return [Hash{String => Hash}] a map of ref type to ref data
|
|
17
|
+
#
|
|
18
|
+
# The structure differs by key:
|
|
19
|
+
#
|
|
20
|
+
# - `"head"` maps directly to `{ ref: String, sha: String }`
|
|
21
|
+
# - Other keys (e.g. `"branches"`, `"tags"`) map to
|
|
22
|
+
# `{ name => { ref: String, sha: String } }`
|
|
23
|
+
#
|
|
24
|
+
def parse_output(lines)
|
|
25
|
+
lines.each_with_object(Hash.new { |h, k| h[k] = {} }) do |line, hsh|
|
|
26
|
+
type, name, value = parse_line(line)
|
|
27
|
+
if name
|
|
28
|
+
hsh[type][name] = value
|
|
29
|
+
else
|
|
30
|
+
hsh[type].update(value)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Parse a single `git ls-remote` output line
|
|
36
|
+
#
|
|
37
|
+
# @param line [String] a single line from ls-remote stdout
|
|
38
|
+
#
|
|
39
|
+
# @return [Array(String, String|nil, Hash)] `[type, name, value]` where
|
|
40
|
+
# `value` is `{ ref: String, sha: String }`
|
|
41
|
+
#
|
|
42
|
+
# @raise [Git::UnexpectedResultError] if the line is not in `<sha>\t<ref>` format
|
|
43
|
+
#
|
|
44
|
+
def parse_line(line)
|
|
45
|
+
unless line.include?("\t") && line.match?(/\A[0-9a-f]{4,}\t/)
|
|
46
|
+
raise Git::UnexpectedResultError, "Unexpected ls-remote output line: #{line.inspect}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
sha, info = line.split("\t", 2)
|
|
50
|
+
ref, type, name = info.split('/', 3)
|
|
51
|
+
|
|
52
|
+
type ||= 'head'
|
|
53
|
+
type = 'branches' if type == 'heads'
|
|
54
|
+
|
|
55
|
+
value = { ref: ref, sha: sha }
|
|
56
|
+
|
|
57
|
+
[type, name, value]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Parse `git ls-remote --symref <repo> HEAD` output into a branch name
|
|
61
|
+
#
|
|
62
|
+
# @param output [String] command stdout
|
|
63
|
+
#
|
|
64
|
+
# @return [String] the default branch name
|
|
65
|
+
#
|
|
66
|
+
# @raise [Git::UnexpectedResultError] when the branch cannot be determined
|
|
67
|
+
#
|
|
68
|
+
def parse_default_branch(output)
|
|
69
|
+
match_data = output.match(%r{^ref: refs/remotes/[^/]+/(?<default_branch>[^\t]+)\t})
|
|
70
|
+
return match_data[:default_branch] if match_data
|
|
71
|
+
|
|
72
|
+
match_data = output.match(%r{^ref: refs/heads/(?<default_branch>[^\t]+)\tHEAD$})
|
|
73
|
+
return match_data[:default_branch] if match_data
|
|
74
|
+
|
|
75
|
+
raise Git::UnexpectedResultError, 'Unable to determine the default branch'
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'git/escaped_path'
|
|
4
|
+
|
|
5
|
+
module Git
|
|
6
|
+
module Parsers
|
|
7
|
+
# Parser for `git ls-tree` output
|
|
8
|
+
#
|
|
9
|
+
# Provides a class method that transforms raw `git ls-tree` output into a
|
|
10
|
+
# structured Hash consumed by the `Git::Repository::ObjectOperations` facade.
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
13
|
+
#
|
|
14
|
+
module LsTree
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# Parse `git ls-tree` output into a type-keyed hash of entries
|
|
18
|
+
#
|
|
19
|
+
# Each line of output is expected in the format produced by
|
|
20
|
+
# `git ls-tree`: `<mode> <type> <sha>\t<file>`.
|
|
21
|
+
#
|
|
22
|
+
# @param output [String] raw stdout from `git ls-tree`
|
|
23
|
+
#
|
|
24
|
+
# @return [Hash<String, Hash<String, Hash>>] hash keyed by object type
|
|
25
|
+
# (`'blob'`, `'tree'`, `'commit'`), then by filename, holding
|
|
26
|
+
# `:mode` and `:sha` values
|
|
27
|
+
#
|
|
28
|
+
# @api private
|
|
29
|
+
#
|
|
30
|
+
def parse(output)
|
|
31
|
+
data = { 'blob' => {}, 'tree' => {}, 'commit' => {} }
|
|
32
|
+
output.split("\n").each do |line|
|
|
33
|
+
info, filenm = line.split("\t", 2)
|
|
34
|
+
filenm = unescape_path(filenm) if filenm
|
|
35
|
+
mode, type, entry_sha = info.split
|
|
36
|
+
data[type][filenm] = { mode: mode, sha: entry_sha }
|
|
37
|
+
end
|
|
38
|
+
data
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Converts a git-quoted path back to its original form
|
|
42
|
+
#
|
|
43
|
+
# @param path [String] the path, possibly git-quoted
|
|
44
|
+
#
|
|
45
|
+
# @return [String] the unquoted path
|
|
46
|
+
#
|
|
47
|
+
# @api private
|
|
48
|
+
#
|
|
49
|
+
def unescape_path(path)
|
|
50
|
+
if path.start_with?('"') && path.end_with?('"')
|
|
51
|
+
Git::EscapedPath.new(path[1..-2]).unescape
|
|
52
|
+
else
|
|
53
|
+
path
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'git/stash_info'
|
|
4
|
+
|
|
5
|
+
module Git
|
|
6
|
+
module Parsers
|
|
7
|
+
# Parser for git stash command output
|
|
8
|
+
#
|
|
9
|
+
# Handles parsing of `git stash list` output into structured data objects.
|
|
10
|
+
#
|
|
11
|
+
# @note Known limitation: If a stash message contains the field separator
|
|
12
|
+
# character (\x1f, ASCII unit separator), parsing will fail or produce
|
|
13
|
+
# incorrect results. This is extremely rare in practice since \x1f is a
|
|
14
|
+
# non-printable control character.
|
|
15
|
+
#
|
|
16
|
+
# ## Design Note: Namespace Organization
|
|
17
|
+
#
|
|
18
|
+
# This parser creates and returns {Git::StashInfo} objects, which live at
|
|
19
|
+
# the top-level `Git::` namespace rather than within `Git::Parsers::`. This
|
|
20
|
+
# is intentional:
|
|
21
|
+
#
|
|
22
|
+
# - **Parsers are infrastructure** - marked `@api private`, users shouldn't
|
|
23
|
+
# interact with them directly
|
|
24
|
+
# - **Info classes are public API** - returned by commands and used throughout
|
|
25
|
+
# the codebase
|
|
26
|
+
# - **Info classes are domain entities** - represent core git concepts
|
|
27
|
+
# (stashes as data)
|
|
28
|
+
#
|
|
29
|
+
# Keeping Info 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 Stash
|
|
35
|
+
# Field separator used in custom format output
|
|
36
|
+
# Using a non-printable unit separator (US, 0x1F) to avoid collisions with
|
|
37
|
+
# stash messages and author/committer fields, while still working with
|
|
38
|
+
# Process.spawn (which doesn't allow NUL bytes in arguments)
|
|
39
|
+
FIELD_SEPARATOR = "\x1f"
|
|
40
|
+
|
|
41
|
+
# Custom format for git stash list that extracts all available metadata
|
|
42
|
+
# %H = full commit SHA
|
|
43
|
+
# %h = abbreviated commit SHA
|
|
44
|
+
# %gd = reflog selector (stash@\\{n})
|
|
45
|
+
# %gs = reflog subject (the stash message)
|
|
46
|
+
# %an = author name
|
|
47
|
+
# %ae = author email
|
|
48
|
+
# %aI = author date (ISO 8601 format)
|
|
49
|
+
# %cn = committer name
|
|
50
|
+
# %ce = committer email
|
|
51
|
+
# %cI = committer date (ISO 8601 format)
|
|
52
|
+
STASH_FORMAT = [
|
|
53
|
+
'%H', # 0: full SHA
|
|
54
|
+
'%h', # 1: short SHA
|
|
55
|
+
'%gd', # 2: reflog selector
|
|
56
|
+
'%gs', # 3: reflog subject (message)
|
|
57
|
+
'%an', # 4: author name
|
|
58
|
+
'%ae', # 5: author email
|
|
59
|
+
'%aI', # 6: author date
|
|
60
|
+
'%cn', # 7: committer name
|
|
61
|
+
'%ce', # 8: committer email
|
|
62
|
+
'%cI' # 9: committer date
|
|
63
|
+
].join(FIELD_SEPARATOR)
|
|
64
|
+
|
|
65
|
+
# Number of fields expected in the parsed output
|
|
66
|
+
FIELD_COUNT = 10
|
|
67
|
+
|
|
68
|
+
# Field indices for parsed output
|
|
69
|
+
module Fields
|
|
70
|
+
OID = 0
|
|
71
|
+
SHORT_OID = 1
|
|
72
|
+
REFLOG = 2
|
|
73
|
+
MESSAGE = 3
|
|
74
|
+
AUTHOR_NAME = 4
|
|
75
|
+
AUTHOR_EMAIL = 5
|
|
76
|
+
AUTHOR_DATE = 6
|
|
77
|
+
COMMITTER_NAME = 7
|
|
78
|
+
COMMITTER_EMAIL = 8
|
|
79
|
+
COMMITTER_DATE = 9
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Pattern to extract branch from standard stash messages
|
|
83
|
+
# Matches "WIP on <branch>:" or "On <branch>:" at the start
|
|
84
|
+
BRANCH_PATTERN = /^(?:WIP on|On)\s+([^:]+):/
|
|
85
|
+
|
|
86
|
+
module_function
|
|
87
|
+
|
|
88
|
+
# Parse git stash list output into StashInfo objects
|
|
89
|
+
#
|
|
90
|
+
# @example
|
|
91
|
+
# StashParser.parse_list("abc123\x1fabc\x1fstash@\\{0}\x1fWIP on main: msg\x1f...\n")
|
|
92
|
+
# # => [#<Git::StashInfo index: 0, ...>]
|
|
93
|
+
#
|
|
94
|
+
# @param stdout [String] output from git stash list --format=...
|
|
95
|
+
# @return [Array<Git::StashInfo>] parsed stash information
|
|
96
|
+
#
|
|
97
|
+
# @raise [Git::UnexpectedResultError] if stash output cannot be parsed
|
|
98
|
+
#
|
|
99
|
+
def parse_list(stdout)
|
|
100
|
+
lines = stdout.split("\n")
|
|
101
|
+
lines.each_with_index.map { |line, idx| parse_stash_line(line, idx, lines) }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Parse a single stash list line into a StashInfo object
|
|
105
|
+
#
|
|
106
|
+
# @param line [String] a line from git stash list output (custom format)
|
|
107
|
+
# @param expected_index [Integer] the expected stash index for validation
|
|
108
|
+
# @param all_lines [Array<String>] all output lines (for error messages)
|
|
109
|
+
#
|
|
110
|
+
# @return [Git::StashInfo] parsed stash info
|
|
111
|
+
#
|
|
112
|
+
# @raise [Git::UnexpectedResultError] if line format is unexpected
|
|
113
|
+
#
|
|
114
|
+
def parse_stash_line(line, expected_index, all_lines)
|
|
115
|
+
parts = line.split(FIELD_SEPARATOR, FIELD_COUNT)
|
|
116
|
+
return build_stash_info(parts, expected_index) if parts.length == FIELD_COUNT
|
|
117
|
+
|
|
118
|
+
raise Git::UnexpectedResultError, unexpected_stash_line_error(all_lines, line, expected_index)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Build a StashInfo from parsed format parts
|
|
122
|
+
#
|
|
123
|
+
# @param parts [Array<String>] the parsed format fields
|
|
124
|
+
# @param expected_index [Integer] fallback index if not parseable from reflog
|
|
125
|
+
# @return [Git::StashInfo]
|
|
126
|
+
#
|
|
127
|
+
def build_stash_info(parts, expected_index)
|
|
128
|
+
index = extract_index(parts[Fields::REFLOG]) || expected_index
|
|
129
|
+
|
|
130
|
+
Git::StashInfo.new(**stash_info_attrs(parts, index))
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Build StashInfo attributes hash from parsed parts
|
|
134
|
+
#
|
|
135
|
+
# @param parts [Array<String>] the parsed format fields
|
|
136
|
+
# @param index [Integer] the resolved stash index
|
|
137
|
+
# @return [Hash] attributes for StashInfo.new
|
|
138
|
+
#
|
|
139
|
+
def stash_info_attrs(parts, index)
|
|
140
|
+
core_attrs(parts, index).merge(author_attrs(parts)).merge(committer_attrs(parts))
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def core_attrs(parts, index)
|
|
144
|
+
{
|
|
145
|
+
index: index, name: parts[Fields::REFLOG], oid: parts[Fields::OID],
|
|
146
|
+
short_oid: parts[Fields::SHORT_OID], branch: extract_branch(parts[Fields::MESSAGE]),
|
|
147
|
+
message: parts[Fields::MESSAGE]
|
|
148
|
+
}
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def author_attrs(parts)
|
|
152
|
+
{
|
|
153
|
+
author_name: parts[Fields::AUTHOR_NAME], author_email: parts[Fields::AUTHOR_EMAIL],
|
|
154
|
+
author_date: parts[Fields::AUTHOR_DATE]
|
|
155
|
+
}
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def committer_attrs(parts)
|
|
159
|
+
{
|
|
160
|
+
committer_name: parts[Fields::COMMITTER_NAME], committer_email: parts[Fields::COMMITTER_EMAIL],
|
|
161
|
+
committer_date: parts[Fields::COMMITTER_DATE]
|
|
162
|
+
}
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Extract the stash index from a reflog selector
|
|
166
|
+
#
|
|
167
|
+
# @param reflog_selector [String] e.g., "stash@\\{0}"
|
|
168
|
+
# @return [Integer, nil] the index or nil if not found
|
|
169
|
+
#
|
|
170
|
+
def extract_index(reflog_selector)
|
|
171
|
+
match = reflog_selector&.match(/stash@\{(\d+)\}/)
|
|
172
|
+
match ? match[1].to_i : nil
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Extract the branch name from a stash message
|
|
176
|
+
#
|
|
177
|
+
# @param message [String] the stash message
|
|
178
|
+
# @return [String, nil] the branch name or nil for custom messages
|
|
179
|
+
#
|
|
180
|
+
def extract_branch(message)
|
|
181
|
+
match = BRANCH_PATTERN.match(message)
|
|
182
|
+
match ? match[1] : nil
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Generate error message for unexpected stash line format
|
|
186
|
+
#
|
|
187
|
+
# @param lines [Array<String>] all output lines
|
|
188
|
+
# @param line [String] the problematic line
|
|
189
|
+
# @param index [Integer] the stash index
|
|
190
|
+
# @return [String] formatted error message
|
|
191
|
+
#
|
|
192
|
+
def unexpected_stash_line_error(lines, line, index)
|
|
193
|
+
format_str = STASH_FORMAT.gsub(FIELD_SEPARATOR, '<FS>')
|
|
194
|
+
<<~ERROR
|
|
195
|
+
Unexpected line in output from `git stash list --format=#{format_str}`, at index #{index}
|
|
196
|
+
|
|
197
|
+
Expected #{FIELD_COUNT} fields separated by '\\x1f' (unit separator), got #{line.split(FIELD_SEPARATOR, -1).length}
|
|
198
|
+
|
|
199
|
+
Full output:
|
|
200
|
+
#{lines.join("\n ")}
|
|
201
|
+
|
|
202
|
+
Line at index #{index}:
|
|
203
|
+
"#{line}"
|
|
204
|
+
ERROR
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|