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
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
class Repository
|
|
5
|
+
# Internal helpers shared by `Git::Repository::*` topic modules
|
|
6
|
+
#
|
|
7
|
+
# Methods defined here use `module_function` so they are callable as
|
|
8
|
+
# `SharedPrivate.foo(...)` from any topic module within `Git::Repository`
|
|
9
|
+
# without being added to `Git::Repository`'s instance namespace via `include`.
|
|
10
|
+
#
|
|
11
|
+
# The constant is declared `private_constant` so it is inaccessible from
|
|
12
|
+
# outside the `Git::Repository` class body; callers inside topic modules use
|
|
13
|
+
# the short unqualified form `SharedPrivate.foo(...)`.
|
|
14
|
+
#
|
|
15
|
+
# @api private
|
|
16
|
+
#
|
|
17
|
+
module SharedPrivate
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# Validate that `options` contains only keys listed in `allowed`
|
|
21
|
+
#
|
|
22
|
+
# Used by facade methods to enforce that only documented options (those
|
|
23
|
+
# named in `@option` tags) are accepted, even when the underlying command
|
|
24
|
+
# class would accept more keys. This prevents silent expansion of the
|
|
25
|
+
# facade's public contract.
|
|
26
|
+
#
|
|
27
|
+
# @example Reject an undocumented option
|
|
28
|
+
# ADD_ALLOWED_OPTS = %i[all force].freeze
|
|
29
|
+
#
|
|
30
|
+
# SharedPrivate.assert_valid_opts!(ADD_ALLOWED_OPTS, bogus: true)
|
|
31
|
+
# #=> raises ArgumentError: Unknown options: bogus
|
|
32
|
+
#
|
|
33
|
+
# @param allowed [Array<Symbol>] the keys permitted by the facade method
|
|
34
|
+
#
|
|
35
|
+
# @param options [Hash] the options hash provided by the caller
|
|
36
|
+
#
|
|
37
|
+
# @return [void]
|
|
38
|
+
#
|
|
39
|
+
# @raise [ArgumentError] when `options` contains any key not in `allowed`
|
|
40
|
+
#
|
|
41
|
+
def assert_valid_opts!(allowed, **options)
|
|
42
|
+
unknown = options.keys - allowed
|
|
43
|
+
return if unknown.empty?
|
|
44
|
+
|
|
45
|
+
raise ArgumentError, "Unknown options: #{unknown.join(', ')}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private_constant :SharedPrivate
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'git/commands/add'
|
|
4
|
+
require 'git/commands/clean'
|
|
5
|
+
require 'git/commands/ls_files'
|
|
6
|
+
require 'git/commands/reset'
|
|
7
|
+
require 'git/commands/rm'
|
|
8
|
+
require 'git/escaped_path'
|
|
9
|
+
require 'git/repository/shared_private'
|
|
10
|
+
|
|
11
|
+
module Git
|
|
12
|
+
class Repository
|
|
13
|
+
# Facade methods for staging-area operations: adding, resetting, removing, and
|
|
14
|
+
# cleaning files
|
|
15
|
+
#
|
|
16
|
+
# Included by {Git::Repository}.
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
#
|
|
20
|
+
module Staging
|
|
21
|
+
# Option keys accepted by {#add}
|
|
22
|
+
ADD_ALLOWED_OPTS = %i[all force].freeze
|
|
23
|
+
private_constant :ADD_ALLOWED_OPTS
|
|
24
|
+
|
|
25
|
+
# Update the index with the current content found in the working tree
|
|
26
|
+
#
|
|
27
|
+
# @overload add(paths = '.', **options)
|
|
28
|
+
#
|
|
29
|
+
# @example Stage all changed files
|
|
30
|
+
# repo.add
|
|
31
|
+
#
|
|
32
|
+
# @example Stage a specific file
|
|
33
|
+
# repo.add('README.md')
|
|
34
|
+
#
|
|
35
|
+
# @example Stage all changes including deletions
|
|
36
|
+
# repo.add(all: true)
|
|
37
|
+
#
|
|
38
|
+
# @param paths [String, Array<String>] a file or files to add (relative to
|
|
39
|
+
# the worktree root); defaults to `'.'` (all files)
|
|
40
|
+
#
|
|
41
|
+
# @param options [Hash] options for the add command
|
|
42
|
+
#
|
|
43
|
+
# @option options [Boolean, nil] :all (nil) add, modify, and remove index
|
|
44
|
+
# entries to match the worktree
|
|
45
|
+
#
|
|
46
|
+
# @option options [Boolean, nil] :force (nil) allow adding otherwise ignored
|
|
47
|
+
# files
|
|
48
|
+
#
|
|
49
|
+
# @return [String] git's stdout from the add
|
|
50
|
+
#
|
|
51
|
+
# @raise [ArgumentError] when unsupported options are provided
|
|
52
|
+
#
|
|
53
|
+
# @raise [Git::FailedError] when git exits with a non-zero exit status
|
|
54
|
+
#
|
|
55
|
+
def add(paths = '.', **)
|
|
56
|
+
SharedPrivate.assert_valid_opts!(ADD_ALLOWED_OPTS, **)
|
|
57
|
+
Git::Commands::Add.new(@execution_context).call(*Array(paths), **).stdout
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Option keys accepted by {#reset}
|
|
61
|
+
RESET_ALLOWED_OPTS = %i[hard].freeze
|
|
62
|
+
private_constant :RESET_ALLOWED_OPTS
|
|
63
|
+
|
|
64
|
+
# Reset the current HEAD to a specified state
|
|
65
|
+
#
|
|
66
|
+
# @overload reset(commitish = nil, **options)
|
|
67
|
+
#
|
|
68
|
+
# @example Reset the index and working tree to HEAD
|
|
69
|
+
# repo.reset
|
|
70
|
+
#
|
|
71
|
+
# @example Hard reset to a specific commit
|
|
72
|
+
# repo.reset('HEAD~1', hard: true)
|
|
73
|
+
#
|
|
74
|
+
# @param commitish [String, nil] the commit or tree-ish to reset to;
|
|
75
|
+
# defaults to HEAD when `nil`
|
|
76
|
+
#
|
|
77
|
+
# @param options [Hash] options for the reset command
|
|
78
|
+
#
|
|
79
|
+
# @option options [Boolean, nil] :hard (nil) reset the index and working
|
|
80
|
+
# tree; discards all tracked changes
|
|
81
|
+
#
|
|
82
|
+
# @return [String] git's stdout from the reset
|
|
83
|
+
#
|
|
84
|
+
# @raise [ArgumentError] when unsupported options are provided
|
|
85
|
+
#
|
|
86
|
+
# @raise [Git::FailedError] when git exits with a non-zero exit status
|
|
87
|
+
#
|
|
88
|
+
def reset(commitish = nil, **)
|
|
89
|
+
SharedPrivate.assert_valid_opts!(RESET_ALLOWED_OPTS, **)
|
|
90
|
+
Git::Commands::Reset.new(@execution_context).call(commitish, **).stdout
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Option keys accepted by {#rm}
|
|
94
|
+
RM_ALLOWED_OPTS = %i[
|
|
95
|
+
force f dry_run n r cached ignore_unmatch sparse quiet q
|
|
96
|
+
pathspec_from_file pathspec_file_nul
|
|
97
|
+
].freeze
|
|
98
|
+
private_constant :RM_ALLOWED_OPTS
|
|
99
|
+
|
|
100
|
+
# Remove file(s) from the working tree and the index
|
|
101
|
+
#
|
|
102
|
+
# @example Remove a single file
|
|
103
|
+
# repo.rm('obsolete.txt', force: true)
|
|
104
|
+
#
|
|
105
|
+
# @example Remove a directory recursively
|
|
106
|
+
# repo.rm('build', r: true)
|
|
107
|
+
#
|
|
108
|
+
# @example Remove from the index only, keeping the working tree copy
|
|
109
|
+
# repo.rm('keep_me.txt', cached: true)
|
|
110
|
+
#
|
|
111
|
+
# @param path [String, Array<String>] a file or files to remove (relative to
|
|
112
|
+
# the worktree root); defaults to `'.'` (all files)
|
|
113
|
+
#
|
|
114
|
+
# @param opts [Hash] options for the rm command
|
|
115
|
+
#
|
|
116
|
+
# @option opts [Boolean, nil] :force (nil) override the up-to-date check and
|
|
117
|
+
# remove files with local modifications (alias: `:f`)
|
|
118
|
+
#
|
|
119
|
+
# @option opts [Boolean, nil] :f (nil) alias for `:force`
|
|
120
|
+
#
|
|
121
|
+
# @option opts [Boolean, nil] :dry_run (nil) do not actually remove any files;
|
|
122
|
+
# only show what would be removed (alias: `:n`)
|
|
123
|
+
#
|
|
124
|
+
# @option opts [Boolean, nil] :n (nil) alias for `:dry_run`
|
|
125
|
+
#
|
|
126
|
+
# @option opts [Boolean, nil] :r (nil) allow recursive removal when a leading
|
|
127
|
+
# directory name is given
|
|
128
|
+
#
|
|
129
|
+
# @option opts [Boolean, nil] :cached (nil) only remove from the index, keeping
|
|
130
|
+
# the working tree files
|
|
131
|
+
#
|
|
132
|
+
# @option opts [Boolean, nil] :ignore_unmatch (nil) exit with a zero status even
|
|
133
|
+
# if no files matched
|
|
134
|
+
#
|
|
135
|
+
# @option opts [Boolean, nil] :sparse (nil) allow updating index entries outside
|
|
136
|
+
# of the sparse-checkout cone
|
|
137
|
+
#
|
|
138
|
+
# @option opts [Boolean, nil] :quiet (nil) suppress the one-line-per-file output
|
|
139
|
+
# (alias: `:q`)
|
|
140
|
+
#
|
|
141
|
+
# @option opts [Boolean, nil] :q (nil) alias for `:quiet`
|
|
142
|
+
#
|
|
143
|
+
# @option opts [String] :pathspec_from_file (nil) read pathspec from the given
|
|
144
|
+
# file, one pathspec element per line; pass `-` to read from standard input
|
|
145
|
+
#
|
|
146
|
+
# @option opts [Boolean, nil] :pathspec_file_nul (nil) when used with
|
|
147
|
+
# `:pathspec_from_file`, separate pathspec elements with NUL instead of newlines
|
|
148
|
+
#
|
|
149
|
+
# @return [String] git's stdout from the rm
|
|
150
|
+
#
|
|
151
|
+
# @raise [ArgumentError] when unsupported options are provided
|
|
152
|
+
#
|
|
153
|
+
# @raise [Git::FailedError] when git exits with a non-zero exit status
|
|
154
|
+
#
|
|
155
|
+
def rm(path = '.', opts = {})
|
|
156
|
+
SharedPrivate.assert_valid_opts!(RM_ALLOWED_OPTS, **opts)
|
|
157
|
+
Git::Commands::Rm.new(@execution_context).call(*Array(path), **opts).stdout
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Option keys accepted by {#clean}
|
|
161
|
+
#
|
|
162
|
+
# The deprecated `:ff` and `:force_force` keys are handled by
|
|
163
|
+
# {Git::Repository::Staging::Private.migrate_clean_legacy_options} before this
|
|
164
|
+
# whitelist is enforced, so they are intentionally absent here.
|
|
165
|
+
CLEAN_ALLOWED_OPTS = %i[d force f dry_run n quiet q exclude e x X pathspec].freeze
|
|
166
|
+
private_constant :CLEAN_ALLOWED_OPTS
|
|
167
|
+
|
|
168
|
+
# Remove untracked files from the working tree
|
|
169
|
+
#
|
|
170
|
+
# @example Remove untracked files
|
|
171
|
+
# repo.clean(force: true)
|
|
172
|
+
#
|
|
173
|
+
# @example Remove untracked files and directories
|
|
174
|
+
# repo.clean(force: true, d: true)
|
|
175
|
+
#
|
|
176
|
+
# @example Remove untracked and ignored files
|
|
177
|
+
# repo.clean(force: true, x: true)
|
|
178
|
+
#
|
|
179
|
+
# @param opts [Hash] options for the clean command
|
|
180
|
+
#
|
|
181
|
+
# @option opts [Boolean, nil] :d (nil) recurse into untracked directories
|
|
182
|
+
#
|
|
183
|
+
# @option opts [Boolean, Integer, nil] :force (nil) force the removal of
|
|
184
|
+
# untracked files; pass `2` to also remove untracked nested git repositories
|
|
185
|
+
# (alias: `:f`)
|
|
186
|
+
#
|
|
187
|
+
# @option opts [Boolean, Integer, nil] :f (nil) alias for `:force`
|
|
188
|
+
#
|
|
189
|
+
# @option opts [Boolean, nil] :dry_run (nil) do not actually remove anything,
|
|
190
|
+
# just show what would be done (alias: `:n`)
|
|
191
|
+
#
|
|
192
|
+
# @option opts [Boolean, nil] :n (nil) alias for `:dry_run`
|
|
193
|
+
#
|
|
194
|
+
# @option opts [Boolean, nil] :quiet (nil) be quiet, only report errors
|
|
195
|
+
# (alias: `:q`)
|
|
196
|
+
#
|
|
197
|
+
# @option opts [Boolean, nil] :q (nil) alias for `:quiet`
|
|
198
|
+
#
|
|
199
|
+
# @option opts [String, Array<String>] :exclude (nil) use the given exclude
|
|
200
|
+
# pattern in addition to the standard ignore rules (alias: `:e`)
|
|
201
|
+
#
|
|
202
|
+
# @option opts [String, Array<String>] :e (nil) alias for `:exclude`
|
|
203
|
+
#
|
|
204
|
+
# @option opts [Boolean, nil] :x (nil) don't use the standard ignore rules
|
|
205
|
+
#
|
|
206
|
+
# @option opts [Boolean, nil] :X (nil) remove only files ignored by git
|
|
207
|
+
#
|
|
208
|
+
# @option opts [String, Array<String>] :pathspec (nil) limit cleaning to files
|
|
209
|
+
# matching the given pathspec(s)
|
|
210
|
+
#
|
|
211
|
+
# @return [String] git's stdout from the clean
|
|
212
|
+
#
|
|
213
|
+
# @raise [ArgumentError] when unsupported options are provided, or when a
|
|
214
|
+
# deprecated `:ff`/`:force_force` value is not `true`, `false`, or `nil`
|
|
215
|
+
#
|
|
216
|
+
# @raise [Git::FailedError] when git exits with a non-zero exit status
|
|
217
|
+
#
|
|
218
|
+
def clean(opts = {})
|
|
219
|
+
opts = Private.migrate_clean_legacy_options(opts)
|
|
220
|
+
SharedPrivate.assert_valid_opts!(CLEAN_ALLOWED_OPTS, **opts)
|
|
221
|
+
Git::Commands::Clean.new(@execution_context).call(**opts).stdout
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# List the files in the working tree that are ignored by git
|
|
225
|
+
#
|
|
226
|
+
# Runs `git ls-files --others --ignored --exclude-standard` and returns the
|
|
227
|
+
# ignored files as repository-relative paths.
|
|
228
|
+
#
|
|
229
|
+
# @example List ignored files
|
|
230
|
+
# repo.ignored_files #=> ["coverage/index.html", "tmp/cache.db"]
|
|
231
|
+
#
|
|
232
|
+
# @example No ignored files
|
|
233
|
+
# repo.ignored_files #=> []
|
|
234
|
+
#
|
|
235
|
+
# @return [Array<String>] repository-relative paths of ignored files; empty
|
|
236
|
+
# when there are none
|
|
237
|
+
#
|
|
238
|
+
# @raise [Git::FailedError] when git exits with a non-zero exit status
|
|
239
|
+
#
|
|
240
|
+
def ignored_files
|
|
241
|
+
Git::Commands::LsFiles.new(@execution_context).call(
|
|
242
|
+
others: true, ignored: true, exclude_standard: true
|
|
243
|
+
).stdout.split("\n").map { |f| Private.unescape_quoted_path(f) }
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Private helpers local to {Git::Repository::Staging}
|
|
247
|
+
#
|
|
248
|
+
# @api private
|
|
249
|
+
#
|
|
250
|
+
module Private
|
|
251
|
+
module_function
|
|
252
|
+
|
|
253
|
+
# Translate deprecated `git clean` option keys into their modern form
|
|
254
|
+
#
|
|
255
|
+
# Maps the legacy `:ff` and `:force_force` boolean options onto the
|
|
256
|
+
# `:force` option, emitting a deprecation warning for each.
|
|
257
|
+
#
|
|
258
|
+
# @param opts [Hash] the caller-provided clean options
|
|
259
|
+
#
|
|
260
|
+
# @return [Hash] a new options hash with deprecated keys translated
|
|
261
|
+
#
|
|
262
|
+
# @raise [ArgumentError] when a deprecated value is not `true`, `false`, or `nil`
|
|
263
|
+
#
|
|
264
|
+
def migrate_clean_legacy_options(opts)
|
|
265
|
+
opts = deprecate_clean_option(opts, :ff, ':ff option is deprecated. Use force: 2 instead.')
|
|
266
|
+
deprecate_clean_option(opts, :force_force, ':force_force option is deprecated. Use force: 2 instead.')
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Translate a single deprecated clean option key onto `:force`
|
|
270
|
+
#
|
|
271
|
+
# @param opts [Hash] the clean options
|
|
272
|
+
#
|
|
273
|
+
# @param key [Symbol] the deprecated option key (`:ff` or `:force_force`)
|
|
274
|
+
#
|
|
275
|
+
# @param message [String] the deprecation message to emit
|
|
276
|
+
#
|
|
277
|
+
# @return [Hash] a new options hash with the deprecated key removed
|
|
278
|
+
#
|
|
279
|
+
# @raise [ArgumentError] when the deprecated value is not `true`, `false`,
|
|
280
|
+
# or `nil`
|
|
281
|
+
#
|
|
282
|
+
def deprecate_clean_option(opts, key, message)
|
|
283
|
+
return opts unless opts.key?(key)
|
|
284
|
+
|
|
285
|
+
opts = opts.dup
|
|
286
|
+
deprecated_value = opts.delete(key)
|
|
287
|
+
validate_deprecated_clean_option_value!(key, deprecated_value)
|
|
288
|
+
|
|
289
|
+
Git::Deprecation.warn(message)
|
|
290
|
+
return opts unless deprecated_value
|
|
291
|
+
|
|
292
|
+
opts[:force] = merge_clean_force_option(opts[:force], force_specified: force_option_specified?(opts))
|
|
293
|
+
opts
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Whether the caller explicitly set a non-nil `:force` value
|
|
297
|
+
#
|
|
298
|
+
# @param opts [Hash] the clean options
|
|
299
|
+
#
|
|
300
|
+
# @return [Boolean] true if `:force` was set to a non-nil value, false
|
|
301
|
+
# otherwise
|
|
302
|
+
#
|
|
303
|
+
def force_option_specified?(opts)
|
|
304
|
+
opts.key?(:force) && !opts[:force].nil?
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Validate the value passed to a deprecated clean option
|
|
308
|
+
#
|
|
309
|
+
# @param key [Symbol] the deprecated option key
|
|
310
|
+
#
|
|
311
|
+
# @param value [Object] the value provided for the deprecated key
|
|
312
|
+
#
|
|
313
|
+
# @return [void]
|
|
314
|
+
#
|
|
315
|
+
# @raise [ArgumentError] when `value` is not `true`, `false`, or `nil`
|
|
316
|
+
#
|
|
317
|
+
def validate_deprecated_clean_option_value!(key, value)
|
|
318
|
+
return if value.nil? || value == true || value == false
|
|
319
|
+
|
|
320
|
+
raise ArgumentError, "#{key} option only accepts true, false, or nil"
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Merge a deprecated force request into the existing `:force` value
|
|
324
|
+
#
|
|
325
|
+
# @param existing_force [Boolean, Integer, nil] the caller's `:force` value
|
|
326
|
+
#
|
|
327
|
+
# @param force_specified [Boolean] whether the caller explicitly set `:force`
|
|
328
|
+
#
|
|
329
|
+
# @return [Integer] the resolved `:force` value
|
|
330
|
+
#
|
|
331
|
+
def merge_clean_force_option(existing_force, force_specified: false)
|
|
332
|
+
return 2 unless force_specified
|
|
333
|
+
|
|
334
|
+
normalized_force = normalize_clean_force_option(existing_force)
|
|
335
|
+
|
|
336
|
+
case normalized_force
|
|
337
|
+
when Integer then merge_integer_clean_force_option(normalized_force)
|
|
338
|
+
when false then 2
|
|
339
|
+
else normalized_force
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Merge an integer `:force` value with the deprecated force request
|
|
344
|
+
#
|
|
345
|
+
# @param normalized_force [Integer] the caller's normalized `:force` value
|
|
346
|
+
#
|
|
347
|
+
# @return [Integer] the resolved `:force` value
|
|
348
|
+
#
|
|
349
|
+
def merge_integer_clean_force_option(normalized_force)
|
|
350
|
+
return normalized_force if normalized_force < 1
|
|
351
|
+
|
|
352
|
+
[normalized_force, 2].max
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# Normalize a `:force` value, coercing `true` to the integer `1`
|
|
356
|
+
#
|
|
357
|
+
# @param value [Boolean, Integer, nil] the `:force` value
|
|
358
|
+
#
|
|
359
|
+
# @return [Integer, Boolean, nil] the normalized value
|
|
360
|
+
#
|
|
361
|
+
def normalize_clean_force_option(value)
|
|
362
|
+
case value
|
|
363
|
+
when true then 1
|
|
364
|
+
else value
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# Unescape a git-quoted path
|
|
369
|
+
#
|
|
370
|
+
# Git wraps paths containing non-ASCII or special characters in
|
|
371
|
+
# double-quotes and octal-escapes each byte. This method strips the
|
|
372
|
+
# surrounding quotes and delegates unescaping to {Git::EscapedPath}.
|
|
373
|
+
#
|
|
374
|
+
# @param path [String] the path as it appears in git output
|
|
375
|
+
#
|
|
376
|
+
# @return [String] the unescaped path
|
|
377
|
+
#
|
|
378
|
+
def unescape_quoted_path(path)
|
|
379
|
+
if path.start_with?('"') && path.end_with?('"')
|
|
380
|
+
Git::EscapedPath.new(path[1..-2]).unescape
|
|
381
|
+
else
|
|
382
|
+
path
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
private_constant :Private
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'git/commands/stash'
|
|
4
|
+
require 'git/parsers/stash'
|
|
5
|
+
|
|
6
|
+
module Git
|
|
7
|
+
class Repository
|
|
8
|
+
# Facade methods for stash operations
|
|
9
|
+
#
|
|
10
|
+
# Included by {Git::Repository}.
|
|
11
|
+
#
|
|
12
|
+
# @api public
|
|
13
|
+
#
|
|
14
|
+
module Stashing
|
|
15
|
+
# Returns all stash entries as an array of index and message pairs
|
|
16
|
+
#
|
|
17
|
+
# Lists all stash entries in the repository ordered from oldest to newest.
|
|
18
|
+
# The index is a sequential number starting from 0 for the oldest stash. The
|
|
19
|
+
# message is the stash description with the leading branch prefix (e.g.
|
|
20
|
+
# `"On main:"` or `"WIP on main:"`) stripped.
|
|
21
|
+
#
|
|
22
|
+
# @example List all stashes (oldest first)
|
|
23
|
+
# repo.stashes_all #=> [[0, "Fix bug"], [1, "Add feature"]]
|
|
24
|
+
#
|
|
25
|
+
# @return [Array<Array(Integer, String)>] array of `[index, message]` pairs
|
|
26
|
+
# where index is the sequential position (0 is oldest) and message is the
|
|
27
|
+
# stash description with the branch prefix stripped
|
|
28
|
+
#
|
|
29
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
30
|
+
#
|
|
31
|
+
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
32
|
+
#
|
|
33
|
+
def stashes_all
|
|
34
|
+
result = Git::Commands::Stash::List.new(@execution_context).call
|
|
35
|
+
stashes = Git::Parsers::Stash.parse_list(result.stdout)
|
|
36
|
+
stashes.reverse.each_with_index.map do |info, i|
|
|
37
|
+
match_data = info.message.match(/^[^:]+:(.*)$/)
|
|
38
|
+
message = match_data ? match_data[1].strip : info.message
|
|
39
|
+
[i, message]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Save the current working directory and index state to a new stash
|
|
44
|
+
#
|
|
45
|
+
# @param message [String] the stash message
|
|
46
|
+
#
|
|
47
|
+
# @return [Boolean] true if changes were stashed, false if there were no
|
|
48
|
+
# local changes to save
|
|
49
|
+
#
|
|
50
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
51
|
+
#
|
|
52
|
+
# @example Save current changes
|
|
53
|
+
# repo.stash_save('WIP: feature work')
|
|
54
|
+
#
|
|
55
|
+
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
56
|
+
#
|
|
57
|
+
def stash_save(message) # rubocop:disable Naming/PredicateMethod
|
|
58
|
+
result = Git::Commands::Stash::Push.new(@execution_context).call(message: message)
|
|
59
|
+
!result.stdout.include?('No local changes to save')
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Apply a stash to the working directory
|
|
63
|
+
#
|
|
64
|
+
# Applies the changes recorded in a stash entry to the working directory
|
|
65
|
+
# without removing the entry from the stash list. Unlike `git stash pop`,
|
|
66
|
+
# the stash entry is preserved after applying.
|
|
67
|
+
#
|
|
68
|
+
# @example Apply the most recent stash
|
|
69
|
+
# repo.stash_apply #=> "HEAD is now at abc1234 Initial commit"
|
|
70
|
+
#
|
|
71
|
+
# @example Apply a specific stash entry by reference
|
|
72
|
+
# repo.stash_apply('stash@{1}') #=> "HEAD is now at abc1234 Initial commit"
|
|
73
|
+
#
|
|
74
|
+
# @param id [String, Integer, nil] the stash identifier (e.g., `'stash@{0}'`,
|
|
75
|
+
# `0`) or `nil` to apply the most recent stash entry
|
|
76
|
+
#
|
|
77
|
+
# @return [String] the output from the git stash apply command
|
|
78
|
+
#
|
|
79
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
80
|
+
#
|
|
81
|
+
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
82
|
+
#
|
|
83
|
+
def stash_apply(id = nil)
|
|
84
|
+
Git::Commands::Stash::Apply.new(@execution_context).call(id).stdout
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Remove all stash entries
|
|
88
|
+
#
|
|
89
|
+
# Removes all entries from the stash list. Use with caution as this
|
|
90
|
+
# operation cannot be undone.
|
|
91
|
+
#
|
|
92
|
+
# @return [String] the output from the git stash clear command
|
|
93
|
+
# (typically empty)
|
|
94
|
+
#
|
|
95
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
96
|
+
#
|
|
97
|
+
# @example Clear all stashes
|
|
98
|
+
# repo.stash_clear #=> ""
|
|
99
|
+
#
|
|
100
|
+
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
101
|
+
#
|
|
102
|
+
def stash_clear
|
|
103
|
+
Git::Commands::Stash::Clear.new(@execution_context).call.stdout
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|