git 1.19.1 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +5 -1
- data/AI_POLICY.md +24 -0
- data/CHANGELOG.md +689 -0
- data/CODE_OF_CONDUCT.md +25 -0
- data/CONTRIBUTING.md +1175 -97
- data/GOVERNANCE.md +106 -0
- data/LICENSE +1 -1
- data/MAINTAINERS.md +17 -4
- data/README.md +476 -320
- data/UPGRADING.md +1138 -0
- data/git.gemspec +124 -36
- data/lib/git/author.rb +39 -7
- data/lib/git/author_info.rb +66 -0
- data/lib/git/branch.rb +615 -65
- data/lib/git/branch_delete_failure.rb +34 -0
- data/lib/git/branch_delete_result.rb +66 -0
- data/lib/git/branch_info.rb +237 -0
- data/lib/git/branches.rb +167 -44
- data/lib/git/command_line/base.rb +247 -0
- data/lib/git/command_line/capturing.rb +308 -0
- data/lib/git/command_line/result.rb +88 -0
- data/lib/git/command_line/streaming.rb +236 -0
- data/lib/git/command_line.rb +52 -0
- data/lib/git/commands/add.rb +139 -0
- data/lib/git/commands/am/abort.rb +43 -0
- data/lib/git/commands/am/apply.rb +263 -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 +49 -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 +242 -0
- data/lib/git/commands/archive/list_formats.rb +46 -0
- data/lib/git/commands/archive.rb +145 -0
- data/lib/git/commands/arguments.rb +4521 -0
- data/lib/git/commands/base.rb +502 -0
- data/lib/git/commands/branch/copy.rb +102 -0
- data/lib/git/commands/branch/create.rb +177 -0
- data/lib/git/commands/branch/delete.rb +88 -0
- data/lib/git/commands/branch/list.rb +178 -0
- data/lib/git/commands/branch/move.rb +102 -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 +53 -0
- data/lib/git/commands/branch.rb +34 -0
- data/lib/git/commands/cat_file/batch.rb +385 -0
- data/lib/git/commands/cat_file/filtered.rb +105 -0
- data/lib/git/commands/cat_file/raw.rb +271 -0
- data/lib/git/commands/cat_file.rb +49 -0
- data/lib/git/commands/checkout/branch.rb +153 -0
- data/lib/git/commands/checkout/files.rb +116 -0
- data/lib/git/commands/checkout.rb +38 -0
- data/lib/git/commands/checkout_index.rb +106 -0
- data/lib/git/commands/clean.rb +102 -0
- data/lib/git/commands/clone.rb +241 -0
- data/lib/git/commands/commit.rb +273 -0
- data/lib/git/commands/commit_tree.rb +101 -0
- data/lib/git/commands/config_option_syntax/add.rb +86 -0
- data/lib/git/commands/config_option_syntax/get.rb +121 -0
- data/lib/git/commands/config_option_syntax/get_all.rb +118 -0
- data/lib/git/commands/config_option_syntax/get_color.rb +95 -0
- data/lib/git/commands/config_option_syntax/get_color_bool.rb +96 -0
- data/lib/git/commands/config_option_syntax/get_regexp.rb +119 -0
- data/lib/git/commands/config_option_syntax/get_urlmatch.rb +111 -0
- data/lib/git/commands/config_option_syntax/list.rb +111 -0
- data/lib/git/commands/config_option_syntax/remove_section.rb +79 -0
- data/lib/git/commands/config_option_syntax/rename_section.rb +83 -0
- data/lib/git/commands/config_option_syntax/replace_all.rb +109 -0
- data/lib/git/commands/config_option_syntax/set.rb +119 -0
- data/lib/git/commands/config_option_syntax/unset.rb +92 -0
- data/lib/git/commands/config_option_syntax/unset_all.rb +94 -0
- data/lib/git/commands/config_option_syntax.rb +56 -0
- data/lib/git/commands/describe.rb +156 -0
- data/lib/git/commands/diff.rb +657 -0
- data/lib/git/commands/diff_files.rb +519 -0
- data/lib/git/commands/diff_index.rb +499 -0
- data/lib/git/commands/fetch.rb +354 -0
- data/lib/git/commands/fsck.rb +138 -0
- data/lib/git/commands/gc.rb +134 -0
- data/lib/git/commands/grep.rb +339 -0
- data/lib/git/commands/init.rb +101 -0
- data/lib/git/commands/log.rb +634 -0
- data/lib/git/commands/ls_files.rb +195 -0
- data/lib/git/commands/ls_remote.rb +161 -0
- data/lib/git/commands/ls_tree.rb +135 -0
- data/lib/git/commands/maintenance/register.rb +77 -0
- data/lib/git/commands/maintenance/run.rb +109 -0
- data/lib/git/commands/maintenance/start.rb +71 -0
- data/lib/git/commands/maintenance/stop.rb +60 -0
- data/lib/git/commands/maintenance/unregister.rb +84 -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 +250 -0
- data/lib/git/commands/merge.rb +28 -0
- data/lib/git/commands/merge_base.rb +91 -0
- data/lib/git/commands/mv.rb +82 -0
- data/lib/git/commands/name_rev.rb +119 -0
- data/lib/git/commands/pull.rb +382 -0
- data/lib/git/commands/push.rb +251 -0
- data/lib/git/commands/read_tree.rb +154 -0
- data/lib/git/commands/remote/add.rb +96 -0
- data/lib/git/commands/remote/get_url.rb +68 -0
- data/lib/git/commands/remote/list.rb +56 -0
- data/lib/git/commands/remote/prune.rb +63 -0
- data/lib/git/commands/remote/remove.rb +52 -0
- data/lib/git/commands/remote/rename.rb +76 -0
- data/lib/git/commands/remote/set_branches.rb +70 -0
- data/lib/git/commands/remote/set_head.rb +89 -0
- data/lib/git/commands/remote/set_url.rb +78 -0
- data/lib/git/commands/remote/set_url_add.rb +70 -0
- data/lib/git/commands/remote/set_url_delete.rb +71 -0
- data/lib/git/commands/remote/show.rb +77 -0
- data/lib/git/commands/remote/update.rb +79 -0
- data/lib/git/commands/remote.rb +42 -0
- data/lib/git/commands/repack.rb +281 -0
- data/lib/git/commands/reset.rb +154 -0
- data/lib/git/commands/rev_parse.rb +304 -0
- data/lib/git/commands/revert/abort.rb +45 -0
- data/lib/git/commands/revert/continue.rb +62 -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 +158 -0
- data/lib/git/commands/revert.rb +29 -0
- data/lib/git/commands/rm.rb +113 -0
- data/lib/git/commands/show.rb +632 -0
- data/lib/git/commands/show_ref/exclude_existing.rb +119 -0
- data/lib/git/commands/show_ref/exists.rb +80 -0
- data/lib/git/commands/show_ref/list.rb +149 -0
- data/lib/git/commands/show_ref/verify.rb +122 -0
- data/lib/git/commands/show_ref.rb +42 -0
- data/lib/git/commands/stash/apply.rb +81 -0
- data/lib/git/commands/stash/branch.rb +67 -0
- data/lib/git/commands/stash/clear.rb +43 -0
- data/lib/git/commands/stash/create.rb +60 -0
- data/lib/git/commands/stash/drop.rb +73 -0
- data/lib/git/commands/stash/list.rb +43 -0
- data/lib/git/commands/stash/pop.rb +87 -0
- data/lib/git/commands/stash/push.rb +112 -0
- data/lib/git/commands/stash/show.rb +158 -0
- data/lib/git/commands/stash/store.rb +72 -0
- data/lib/git/commands/stash.rb +38 -0
- data/lib/git/commands/status.rb +174 -0
- data/lib/git/commands/symbolic_ref/delete.rb +72 -0
- data/lib/git/commands/symbolic_ref/read.rb +99 -0
- data/lib/git/commands/symbolic_ref/update.rb +79 -0
- data/lib/git/commands/symbolic_ref.rb +38 -0
- data/lib/git/commands/tag/create.rb +142 -0
- data/lib/git/commands/tag/delete.rb +57 -0
- data/lib/git/commands/tag/list.rb +146 -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 +145 -0
- data/lib/git/commands/update_ref/delete.rb +90 -0
- data/lib/git/commands/update_ref/update.rb +103 -0
- data/lib/git/commands/update_ref.rb +42 -0
- data/lib/git/commands/version.rb +60 -0
- data/lib/git/commands/worktree/add.rb +139 -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 +88 -0
- data/lib/git/config.rb +72 -5
- data/lib/git/config_entry_info.rb +106 -0
- data/lib/git/configuring.rb +795 -0
- data/lib/git/detached_head_info.rb +57 -0
- data/lib/git/diff.rb +437 -86
- data/lib/git/diff_file_numstat_info.rb +31 -0
- data/lib/git/diff_file_patch_info.rb +136 -0
- data/lib/git/diff_file_raw_info.rb +129 -0
- data/lib/git/diff_info.rb +162 -0
- data/lib/git/diff_path_status.rb +107 -0
- data/lib/git/diff_result.rb +34 -0
- data/lib/git/diff_stats.rb +111 -0
- data/lib/git/dirstat_info.rb +102 -0
- data/lib/git/encoding_utils.rb +32 -1
- data/lib/git/errors.rb +285 -0
- data/lib/git/escaped_path.rb +57 -5
- data/lib/git/execution_context/global.rb +31 -0
- data/lib/git/execution_context/repository.rb +151 -0
- data/lib/git/execution_context.rb +559 -0
- data/lib/git/factories.rb +813 -0
- data/lib/git/file_ref.rb +77 -0
- data/lib/git/fsck_object.rb +56 -0
- data/lib/git/fsck_result.rb +132 -0
- data/lib/git/log.rb +306 -90
- data/lib/git/object.rb +563 -141
- data/lib/git/parsers/branch.rb +240 -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 +792 -0
- data/lib/git/parsers/fsck.rb +144 -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/remote.rb +162 -0
- data/lib/git/parsers/stash.rb +292 -0
- data/lib/git/parsers/status.rb +251 -0
- data/lib/git/parsers/tag.rb +341 -0
- data/lib/git/parsers/worktree.rb +185 -0
- data/lib/git/path_resolver.rb +206 -0
- data/lib/git/remote.rb +165 -12
- data/lib/git/remote_info.rb +203 -0
- data/lib/git/repository/branching.rb +964 -0
- data/lib/git/repository/committing.rb +246 -0
- data/lib/git/repository/context_helpers.rb +293 -0
- data/lib/git/repository/diffing.rb +785 -0
- data/lib/git/repository/inspecting.rb +252 -0
- data/lib/git/repository/logging.rb +410 -0
- data/lib/git/repository/maintenance.rb +65 -0
- data/lib/git/repository/merging.rb +451 -0
- data/lib/git/repository/object_operations.rb +1551 -0
- data/lib/git/repository/remote_operations.rb +984 -0
- data/lib/git/repository/shared_private.rb +120 -0
- data/lib/git/repository/staging.rb +587 -0
- data/lib/git/repository/stashing.rb +623 -0
- data/lib/git/repository/status_operations.rb +249 -0
- data/lib/git/repository/worktree_operations.rb +339 -0
- data/lib/git/repository.rb +484 -2
- data/lib/git/stash.rb +109 -12
- data/lib/git/stash_info.rb +102 -0
- data/lib/git/stashes.rb +169 -26
- data/lib/git/status.rb +308 -122
- data/lib/git/status_file_info.rb +258 -0
- data/lib/git/status_info.rb +189 -0
- data/lib/git/tag_delete_failure.rb +34 -0
- data/lib/git/tag_delete_result.rb +66 -0
- data/lib/git/tag_info.rb +99 -0
- data/lib/git/url.rb +15 -8
- data/lib/git/version.rb +113 -2
- data/lib/git/version_constraint.rb +85 -0
- data/lib/git/worktree.rb +150 -8
- data/lib/git/worktree_info.rb +128 -0
- data/lib/git/worktrees.rb +118 -13
- data/lib/git.rb +632 -234
- metadata +369 -54
- data/.github/stale.yml +0 -25
- data/.github/workflows/continuous_integration.yml +0 -49
- data/.gitignore +0 -10
- data/Dockerfile.changelog-rs +0 -12
- data/Gemfile +0 -5
- data/ISSUE_TEMPLATE.md +0 -15
- data/PULL_REQUEST_TEMPLATE.md +0 -9
- data/RELEASING.md +0 -70
- data/Rakefile +0 -60
- data/lib/git/base/factory.rb +0 -99
- data/lib/git/base.rb +0 -711
- data/lib/git/command_line_result.rb +0 -86
- data/lib/git/failed_error.rb +0 -53
- data/lib/git/git_execute_error.rb +0 -7
- data/lib/git/index.rb +0 -5
- data/lib/git/lib.rb +0 -1328
- data/lib/git/path.rb +0 -31
- data/lib/git/signaled_error.rb +0 -50
- data/lib/git/working_directory.rb +0 -4
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'git/commands/ls_files'
|
|
4
|
+
require 'git/commands/rev_parse'
|
|
5
|
+
require 'git/commands/status'
|
|
6
|
+
require 'git/escaped_path'
|
|
7
|
+
require 'git/parsers/status'
|
|
8
|
+
require 'git/status'
|
|
9
|
+
require 'git/status_info'
|
|
10
|
+
|
|
11
|
+
module Git
|
|
12
|
+
class Repository
|
|
13
|
+
# Facade methods for repository-status operations
|
|
14
|
+
#
|
|
15
|
+
# Provides methods for querying the state of the repository: checking
|
|
16
|
+
# whether any commits exist, listing untracked working-tree files, and
|
|
17
|
+
# listing files tracked in the index.
|
|
18
|
+
#
|
|
19
|
+
# Included by {Git::Repository}.
|
|
20
|
+
#
|
|
21
|
+
# @api private
|
|
22
|
+
#
|
|
23
|
+
module StatusOperations
|
|
24
|
+
# Returns `true` if the repository has no commits yet
|
|
25
|
+
#
|
|
26
|
+
# Checks whether `HEAD` can be resolved to a commit object. A brand-new
|
|
27
|
+
# repository (or one created with `git checkout --orphan`) where no commit
|
|
28
|
+
# has been made yet will have no commits.
|
|
29
|
+
#
|
|
30
|
+
# @example Check whether a repository is empty
|
|
31
|
+
# repo.no_commits? #=> true # freshly initialized, no commits yet
|
|
32
|
+
# repo.no_commits? #=> false # at least one commit exists
|
|
33
|
+
#
|
|
34
|
+
# @return [Boolean] `true` when the repository has no commits, `false` otherwise
|
|
35
|
+
#
|
|
36
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status other
|
|
37
|
+
# than when the repository has no commits
|
|
38
|
+
#
|
|
39
|
+
def no_commits?
|
|
40
|
+
Git::Commands::RevParse.new(@execution_context).call('HEAD', verify: true)
|
|
41
|
+
false
|
|
42
|
+
rescue Git::FailedError => e
|
|
43
|
+
raise unless e.result.status.exitstatus == 128 &&
|
|
44
|
+
e.result.stderr == 'fatal: Needed a single revision'
|
|
45
|
+
|
|
46
|
+
true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Returns `true` if the repository has no commits yet
|
|
50
|
+
#
|
|
51
|
+
# @example Check whether a repository is empty
|
|
52
|
+
# repo.empty? #=> true # freshly initialized, no commits yet
|
|
53
|
+
# repo.empty? #=> false # at least one commit exists
|
|
54
|
+
#
|
|
55
|
+
# @return [Boolean] `true` when the repository has no commits, `false` otherwise
|
|
56
|
+
#
|
|
57
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status other
|
|
58
|
+
# than when the repository has no commits
|
|
59
|
+
#
|
|
60
|
+
# @deprecated Use {#no_commits?} instead
|
|
61
|
+
#
|
|
62
|
+
def empty?
|
|
63
|
+
Git::Deprecation.warn(
|
|
64
|
+
'Git::Repository#empty? is deprecated and will be removed in v6.0.0. ' \
|
|
65
|
+
'Use Git::Repository#no_commits? instead.'
|
|
66
|
+
)
|
|
67
|
+
no_commits?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# List all files in the working tree that are not tracked by git
|
|
71
|
+
#
|
|
72
|
+
# Runs `git ls-files --others --exclude-standard` from the working tree
|
|
73
|
+
# root and returns an array of repository-relative file paths. Files that
|
|
74
|
+
# match `.gitignore` or other standard exclusion rules are omitted.
|
|
75
|
+
#
|
|
76
|
+
# @example Get untracked files
|
|
77
|
+
# repo.untracked_files #=> ["new_feature.rb", "tmp/debug.log"]
|
|
78
|
+
#
|
|
79
|
+
# @example No untracked files
|
|
80
|
+
# repo.untracked_files #=> []
|
|
81
|
+
#
|
|
82
|
+
# @return [Array<String>] repository-relative paths of untracked,
|
|
83
|
+
# non-ignored files; empty when there are none
|
|
84
|
+
#
|
|
85
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
86
|
+
#
|
|
87
|
+
def untracked_files
|
|
88
|
+
Git::Commands::LsFiles.new(@execution_context).call(
|
|
89
|
+
others: true, exclude_standard: true, chdir: @execution_context.git_work_dir
|
|
90
|
+
).stdout.split("\n").map { |f| Private.unescape_quoted_path(f) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns a {Git::StatusInfo} describing the index and working tree state
|
|
94
|
+
#
|
|
95
|
+
# Runs `git status` in porcelain v2 format with NUL-separated entries and
|
|
96
|
+
# every untracked file listed individually, then reads `core.ignoreCase`
|
|
97
|
+
# as a boolean so that the path predicates on the result compare paths the
|
|
98
|
+
# way git does in this repository. Every entry type git reports is
|
|
99
|
+
# represented, including renames, copies, and merge conflicts. Clean
|
|
100
|
+
# tracked paths are not reported by `git status`, so they are absent from
|
|
101
|
+
# the result; the deprecated {#status} listed them, and {#ls_files} still
|
|
102
|
+
# does.
|
|
103
|
+
#
|
|
104
|
+
# @example Check which files are modified
|
|
105
|
+
# repo.status_info.changed
|
|
106
|
+
# #=> { "lib/foo.rb" => #<data Git::StatusFileInfo path="lib/foo.rb", ...> }
|
|
107
|
+
#
|
|
108
|
+
# @example Check for untracked files
|
|
109
|
+
# repo.status_info.untracked.keys #=> ["new_file.rb"]
|
|
110
|
+
#
|
|
111
|
+
# @example Check one path
|
|
112
|
+
# repo.status_info.changed?('lib/foo.rb') #=> true
|
|
113
|
+
#
|
|
114
|
+
# @example Iterate over every entry
|
|
115
|
+
# repo.status_info.files.each do |file|
|
|
116
|
+
# puts "#{file.index_status}#{file.worktree_status} #{file.path}"
|
|
117
|
+
# end
|
|
118
|
+
#
|
|
119
|
+
# @return [Git::StatusInfo] the status of the repository
|
|
120
|
+
#
|
|
121
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
122
|
+
#
|
|
123
|
+
# @see https://git-scm.com/docs/git-status git-status
|
|
124
|
+
#
|
|
125
|
+
def status_info
|
|
126
|
+
result = Git::Commands::Status.new(@execution_context).call(
|
|
127
|
+
porcelain: 'v2', z: true, untracked_files: 'all'
|
|
128
|
+
)
|
|
129
|
+
files = Git::Parsers::Status.parse(result.stdout)
|
|
130
|
+
ignore_case = config_get('core.ignoreCase', type: 'bool')&.value == 'true'
|
|
131
|
+
Git::StatusInfo.new(files: files, ignore_case: ignore_case)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Returns a {Git::Status} object describing the working tree and index state
|
|
135
|
+
#
|
|
136
|
+
# Constructs a {Git::Status} for this repository by collecting information from
|
|
137
|
+
# `git ls-files --stage`, `git ls-files --others`, `git diff-files`, and
|
|
138
|
+
# `git diff-index HEAD` (the last only when at least one commit exists). The
|
|
139
|
+
# result identifies which files have been modified, added, deleted, or are
|
|
140
|
+
# untracked.
|
|
141
|
+
#
|
|
142
|
+
# Emits one deprecation warning per call. The {Git::Status} it constructs is
|
|
143
|
+
# built with warnings silenced so the caller does not see a second one.
|
|
144
|
+
#
|
|
145
|
+
# @example Check which files are modified (deprecated; use status_info)
|
|
146
|
+
# repo.status.changed.keys #=> ["lib/foo.rb"]
|
|
147
|
+
# repo.status_info.changed.keys #=> ["lib/foo.rb"]
|
|
148
|
+
#
|
|
149
|
+
# @return [Git::Status] the status of the repository
|
|
150
|
+
#
|
|
151
|
+
# @raise [Git::FailedError] if any underlying git command exits with a
|
|
152
|
+
# non-zero exit status
|
|
153
|
+
#
|
|
154
|
+
# @deprecated Use {#status_info} instead
|
|
155
|
+
#
|
|
156
|
+
def status
|
|
157
|
+
Git::Deprecation.warn(
|
|
158
|
+
'Git::Repository#status is deprecated and will be removed in v6.0.0. ' \
|
|
159
|
+
'Use Git::Repository#status_info instead.'
|
|
160
|
+
)
|
|
161
|
+
Git::Deprecation.silence { Git::Status.new(self) }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# List all files tracked in the index
|
|
165
|
+
#
|
|
166
|
+
# Runs `git ls-files --stage` under the given `location` and returns a
|
|
167
|
+
# hash keyed by file path with per-file index metadata.
|
|
168
|
+
#
|
|
169
|
+
# @example List all indexed files in the working tree
|
|
170
|
+
# repo.ls_files
|
|
171
|
+
# #=> { "README.md" => { path: "README.md", mode_index: "100644",
|
|
172
|
+
# #=> sha_index: "abc123...", stage: "0" }, ... }
|
|
173
|
+
#
|
|
174
|
+
# @example List indexed files under a specific directory
|
|
175
|
+
# repo.ls_files('lib/')
|
|
176
|
+
# #=> { "lib/git.rb" => { path: "lib/git.rb", ... }, ... }
|
|
177
|
+
#
|
|
178
|
+
# @param location [String, nil] the path to restrict the listing to;
|
|
179
|
+
# defaults to `'.'` (all tracked files) when `nil`
|
|
180
|
+
#
|
|
181
|
+
# @return [Hash{String => Hash}] a hash of index entries keyed by file path
|
|
182
|
+
#
|
|
183
|
+
# Each value is a Hash with the following keys:
|
|
184
|
+
# * `:path` [String] the file path
|
|
185
|
+
# * `:mode_index` [String] the file's index mode (e.g. `"100644"`)
|
|
186
|
+
# * `:sha_index` [String] the file's index SHA
|
|
187
|
+
# * `:stage` [String] the merge stage (`"0"` for normal entries)
|
|
188
|
+
#
|
|
189
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
190
|
+
#
|
|
191
|
+
def ls_files(location = nil)
|
|
192
|
+
location ||= '.'
|
|
193
|
+
{}.tap do |files|
|
|
194
|
+
Git::Commands::LsFiles.new(@execution_context).call(location, stage: true).stdout.split("\n").each do |line|
|
|
195
|
+
info, file = Private.split_status_line(line)
|
|
196
|
+
mode, sha, stage = info.split
|
|
197
|
+
files[file] = { path: file, mode_index: mode, sha_index: sha, stage: stage }
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Private helpers local to {Git::Repository::StatusOperations}
|
|
203
|
+
#
|
|
204
|
+
# @api private
|
|
205
|
+
#
|
|
206
|
+
module Private
|
|
207
|
+
module_function
|
|
208
|
+
|
|
209
|
+
# Split a tab-delimited status line from `git ls-files --stage` output
|
|
210
|
+
#
|
|
211
|
+
# The output format is `<mode> <sha> <stage>\t<file>`. Quoted file paths
|
|
212
|
+
# (which git uses when the path contains non-ASCII or special characters)
|
|
213
|
+
# are unescaped before being returned. `line` is assumed to be non-empty
|
|
214
|
+
# because `git ls-files --stage` never emits blank lines.
|
|
215
|
+
#
|
|
216
|
+
# @param line [String] a single line of git ls-files output
|
|
217
|
+
#
|
|
218
|
+
# @return [Array<String>] the tab-delimited parts with the last part
|
|
219
|
+
# unescaped when it was git-quoted
|
|
220
|
+
#
|
|
221
|
+
def split_status_line(line)
|
|
222
|
+
parts = line.split("\t")
|
|
223
|
+
parts[-1] = unescape_quoted_path(parts[-1])
|
|
224
|
+
parts
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Unescape a git-quoted path
|
|
228
|
+
#
|
|
229
|
+
# Git wraps paths containing non-ASCII or special characters in
|
|
230
|
+
# double-quotes and octal-escapes each byte. This method strips the
|
|
231
|
+
# surrounding quotes and delegates unescaping to {Git::EscapedPath}.
|
|
232
|
+
#
|
|
233
|
+
# @param path [String] the path as it appears in git output
|
|
234
|
+
#
|
|
235
|
+
# @return [String] the unescaped path
|
|
236
|
+
#
|
|
237
|
+
def unescape_quoted_path(path)
|
|
238
|
+
if path.start_with?('"') && path.end_with?('"')
|
|
239
|
+
Git::EscapedPath.new(path[1..-2]).unescape
|
|
240
|
+
else
|
|
241
|
+
path
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
private_constant :Private
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'git/commands/worktree'
|
|
4
|
+
require 'git/parsers/worktree'
|
|
5
|
+
require 'git/worktree'
|
|
6
|
+
require 'git/worktree_info'
|
|
7
|
+
require 'git/worktrees'
|
|
8
|
+
|
|
9
|
+
module Git
|
|
10
|
+
class Repository
|
|
11
|
+
# Facade methods for worktree operations: listing, adding, removing, moving,
|
|
12
|
+
# locking, repairing, and pruning worktrees
|
|
13
|
+
#
|
|
14
|
+
# Included by {Git::Repository}.
|
|
15
|
+
#
|
|
16
|
+
# @api private
|
|
17
|
+
#
|
|
18
|
+
module WorktreeOperations
|
|
19
|
+
# Returns every worktree attached to the repository
|
|
20
|
+
#
|
|
21
|
+
# Lists the main worktree first, then each linked worktree, in the order
|
|
22
|
+
# git reports them. The main worktree of a bare repository is included
|
|
23
|
+
# with {Git::WorktreeInfo#bare?} true and no head or branch.
|
|
24
|
+
#
|
|
25
|
+
# @example List all worktrees
|
|
26
|
+
# repo.worktree_list.map(&:path)
|
|
27
|
+
# #=> ["/path/to/main", "/tmp/feature"]
|
|
28
|
+
#
|
|
29
|
+
# @example Find the worktree that has a branch checked out
|
|
30
|
+
# info = repo.worktree_list.find { |w| w.branch == 'refs/heads/feature' }
|
|
31
|
+
# info.path #=> "/tmp/feature"
|
|
32
|
+
# info.head #=> "b8c63202c3c0ebd37b7e45fd0c22e6c20d5bead1"
|
|
33
|
+
# info.locked? #=> false
|
|
34
|
+
#
|
|
35
|
+
# @return [Array<Git::WorktreeInfo>] one entry per worktree
|
|
36
|
+
#
|
|
37
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
38
|
+
#
|
|
39
|
+
# @raise [Git::UnexpectedResultError] if the worktree listing cannot be
|
|
40
|
+
# parsed
|
|
41
|
+
#
|
|
42
|
+
# @see https://git-scm.com/docs/git-worktree git-worktree documentation
|
|
43
|
+
#
|
|
44
|
+
def worktree_list
|
|
45
|
+
result = Git::Commands::Worktree::List.new(@execution_context).call(porcelain: true)
|
|
46
|
+
Git::Parsers::Worktree.parse_list(result.stdout)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Returns all worktrees as an array of directory and SHA pairs
|
|
50
|
+
#
|
|
51
|
+
# Lists the main worktree and all linked worktrees. The main worktree of
|
|
52
|
+
# a bare repository has no checked-out commit and is omitted.
|
|
53
|
+
#
|
|
54
|
+
# @example List all worktrees
|
|
55
|
+
# repo.worktrees_all
|
|
56
|
+
# #=> [["/path/to/main", "4bef5ab..."], ["/tmp/worktree-1", "b8c6320..."]]
|
|
57
|
+
#
|
|
58
|
+
# @return [Array<Array(String, String)>] array of `[directory, sha]` pairs
|
|
59
|
+
#
|
|
60
|
+
# `directory` is the worktree path reported by git (absolute or relative,
|
|
61
|
+
# depending on repository configuration); `sha` is the full SHA of the
|
|
62
|
+
# checked-out HEAD commit
|
|
63
|
+
#
|
|
64
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
65
|
+
#
|
|
66
|
+
# @deprecated Use {#worktree_list} instead
|
|
67
|
+
#
|
|
68
|
+
# {#worktree_list} returns one {Git::WorktreeInfo} per worktree, with
|
|
69
|
+
# `path` and `head` in place of the pair, and includes the main worktree
|
|
70
|
+
# of a bare repository.
|
|
71
|
+
#
|
|
72
|
+
# @see #worktree_list
|
|
73
|
+
#
|
|
74
|
+
# @see https://git-scm.com/docs/git-worktree git-worktree documentation
|
|
75
|
+
#
|
|
76
|
+
def worktrees_all
|
|
77
|
+
Git::Deprecation.warn(
|
|
78
|
+
'Git::Repository#worktrees_all is deprecated and will be removed in v6.0.0. ' \
|
|
79
|
+
'Use Git::Repository#worktree_list instead.'
|
|
80
|
+
)
|
|
81
|
+
worktree_list.reject { |worktree| worktree.head.nil? }.map { |worktree| [worktree.path, worktree.head] }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Create a new linked worktree at the given directory
|
|
85
|
+
#
|
|
86
|
+
# @example Create a worktree at a path (auto-creates a branch)
|
|
87
|
+
# repo.worktree_add('/tmp/feature')
|
|
88
|
+
#
|
|
89
|
+
# @example Create a worktree and check out an existing commitish
|
|
90
|
+
# repo.worktree_add('/tmp/hotfix', 'main')
|
|
91
|
+
#
|
|
92
|
+
# @param dir [String] filesystem path for the new worktree
|
|
93
|
+
#
|
|
94
|
+
# @param commitish [String, nil] branch, tag, or commit to check out
|
|
95
|
+
#
|
|
96
|
+
# When `nil`, git creates a new branch named after the final path component
|
|
97
|
+
#
|
|
98
|
+
# @return [String] the output from the git worktree add command
|
|
99
|
+
#
|
|
100
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
101
|
+
#
|
|
102
|
+
# @see https://git-scm.com/docs/git-worktree git-worktree documentation
|
|
103
|
+
#
|
|
104
|
+
def worktree_add(dir, commitish = nil)
|
|
105
|
+
args = [dir]
|
|
106
|
+
args << commitish unless commitish.nil?
|
|
107
|
+
|
|
108
|
+
Git::Commands::Worktree::Add.new(@execution_context).call(*args).stdout
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Remove a linked worktree
|
|
112
|
+
#
|
|
113
|
+
# @example Remove a worktree by path
|
|
114
|
+
# repo.worktree_remove('/tmp/feature')
|
|
115
|
+
#
|
|
116
|
+
# @example Remove a worktree from the list
|
|
117
|
+
# info = repo.worktree_list.find { |w| w.branch == 'refs/heads/feature' }
|
|
118
|
+
# repo.worktree_remove(info)
|
|
119
|
+
#
|
|
120
|
+
# @param worktree [String, Git::WorktreeInfo] the path of the worktree to
|
|
121
|
+
# remove, or its entry from {#worktree_list}
|
|
122
|
+
#
|
|
123
|
+
# @return [String] the output from the git worktree remove command
|
|
124
|
+
# (typically empty)
|
|
125
|
+
#
|
|
126
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
127
|
+
#
|
|
128
|
+
# @see https://git-scm.com/docs/git-worktree git-worktree documentation
|
|
129
|
+
#
|
|
130
|
+
def worktree_remove(worktree)
|
|
131
|
+
Git::Commands::Worktree::Remove.new(@execution_context).call(worktree.to_s).stdout
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Move a linked worktree to a new location
|
|
135
|
+
#
|
|
136
|
+
# @example Move a worktree
|
|
137
|
+
# repo.worktree_move('/tmp/feature', '/tmp/feature-moved')
|
|
138
|
+
#
|
|
139
|
+
# @example Move a locked worktree
|
|
140
|
+
# repo.worktree_move('/tmp/feature', '/tmp/feature-moved', force: 2)
|
|
141
|
+
#
|
|
142
|
+
# @param worktree [String, Git::WorktreeInfo] the path of the worktree to
|
|
143
|
+
# move, or its entry from {#worktree_list}
|
|
144
|
+
#
|
|
145
|
+
# @param new_path [String] the destination path
|
|
146
|
+
#
|
|
147
|
+
# @param opts [Hash] options for the move
|
|
148
|
+
#
|
|
149
|
+
# @option opts [Boolean, Integer, nil] :force (nil) override git's
|
|
150
|
+
# safeguards; git refuses to move a locked worktree unless the flag is
|
|
151
|
+
# given twice, so pass `2` for that
|
|
152
|
+
#
|
|
153
|
+
# @return [String] the output from the git worktree move command
|
|
154
|
+
# (typically empty)
|
|
155
|
+
#
|
|
156
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
157
|
+
#
|
|
158
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
159
|
+
#
|
|
160
|
+
# @see https://git-scm.com/docs/git-worktree git-worktree documentation
|
|
161
|
+
#
|
|
162
|
+
def worktree_move(worktree, new_path, opts = {})
|
|
163
|
+
Git::Commands::Worktree::Move.new(@execution_context).call(worktree.to_s, new_path, **opts).stdout
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Lock a linked worktree so that `git worktree prune` leaves it alone
|
|
167
|
+
#
|
|
168
|
+
# Lock a worktree whose directory is on removable media or a network
|
|
169
|
+
# share that is not always mounted.
|
|
170
|
+
#
|
|
171
|
+
# @example Lock a worktree
|
|
172
|
+
# repo.worktree_lock('/tmp/feature')
|
|
173
|
+
#
|
|
174
|
+
# @example Lock a worktree with a reason
|
|
175
|
+
# repo.worktree_lock('/tmp/feature', reason: 'on an external drive')
|
|
176
|
+
#
|
|
177
|
+
# @param worktree [String, Git::WorktreeInfo] the path of the worktree to
|
|
178
|
+
# lock, or its entry from {#worktree_list}
|
|
179
|
+
#
|
|
180
|
+
# @param opts [Hash] options for the lock
|
|
181
|
+
#
|
|
182
|
+
# @option opts [String, nil] :reason (nil) an explanation stored with the
|
|
183
|
+
# lock and reported as {Git::WorktreeInfo#lock_reason}
|
|
184
|
+
#
|
|
185
|
+
# @return [String] the output from the git worktree lock command
|
|
186
|
+
# (typically empty)
|
|
187
|
+
#
|
|
188
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
189
|
+
#
|
|
190
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
191
|
+
#
|
|
192
|
+
# @see https://git-scm.com/docs/git-worktree git-worktree documentation
|
|
193
|
+
#
|
|
194
|
+
def worktree_lock(worktree, opts = {})
|
|
195
|
+
Git::Commands::Worktree::Lock.new(@execution_context).call(worktree.to_s, **opts).stdout
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Unlock a linked worktree
|
|
199
|
+
#
|
|
200
|
+
# @example Unlock a worktree
|
|
201
|
+
# repo.worktree_unlock('/tmp/feature')
|
|
202
|
+
#
|
|
203
|
+
# @param worktree [String, Git::WorktreeInfo] the path of the worktree to
|
|
204
|
+
# unlock, or its entry from {#worktree_list}
|
|
205
|
+
#
|
|
206
|
+
# @return [String] the output from the git worktree unlock command
|
|
207
|
+
# (typically empty)
|
|
208
|
+
#
|
|
209
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
210
|
+
#
|
|
211
|
+
# @see https://git-scm.com/docs/git-worktree git-worktree documentation
|
|
212
|
+
#
|
|
213
|
+
def worktree_unlock(worktree)
|
|
214
|
+
Git::Commands::Worktree::Unlock.new(@execution_context).call(worktree.to_s).stdout
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Repair the links between the repository and its linked worktrees
|
|
218
|
+
#
|
|
219
|
+
# With no paths, repairs the link from each linked worktree back to the
|
|
220
|
+
# repository, which is needed after the repository directory was moved.
|
|
221
|
+
# Given the current paths of linked worktrees that were moved without
|
|
222
|
+
# {#worktree_move}, also repairs the repository's links to them.
|
|
223
|
+
#
|
|
224
|
+
# @example Repair after the repository directory was moved
|
|
225
|
+
# repo.worktree_repair
|
|
226
|
+
#
|
|
227
|
+
# @example Repair after a linked worktree was moved by hand
|
|
228
|
+
# repo.worktree_repair('/new/path/to/feature')
|
|
229
|
+
#
|
|
230
|
+
# @param paths [Array<String, Git::WorktreeInfo>] the current paths of the
|
|
231
|
+
# worktrees to repair, or their entries from {#worktree_list}
|
|
232
|
+
#
|
|
233
|
+
# @return [String] the output from the git worktree repair command, which
|
|
234
|
+
# reports each repair made
|
|
235
|
+
#
|
|
236
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
237
|
+
#
|
|
238
|
+
# @raise [Git::VersionError] if the installed git is older than 2.29.0
|
|
239
|
+
#
|
|
240
|
+
# @see https://git-scm.com/docs/git-worktree git-worktree documentation
|
|
241
|
+
#
|
|
242
|
+
def worktree_repair(*paths)
|
|
243
|
+
Git::Commands::Worktree::Repair.new(@execution_context).call(*paths.map(&:to_s)).stdout
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Prune stale worktree administrative files
|
|
247
|
+
#
|
|
248
|
+
# Removes stale administrative files from `$GIT_DIR/worktrees`. A
|
|
249
|
+
# worktree becomes stale when its directory no longer exists on disk.
|
|
250
|
+
#
|
|
251
|
+
# @example Prune stale worktrees
|
|
252
|
+
# repo.worktree_prune
|
|
253
|
+
#
|
|
254
|
+
# @return [String] the output from the git worktree prune command
|
|
255
|
+
# (typically empty)
|
|
256
|
+
#
|
|
257
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
258
|
+
#
|
|
259
|
+
# @see https://git-scm.com/docs/git-worktree git-worktree documentation
|
|
260
|
+
#
|
|
261
|
+
def worktree_prune
|
|
262
|
+
Git::Commands::Worktree::Prune.new(@execution_context).call.stdout
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Return a {Git::Worktree} object for the given directory and optional commitish
|
|
266
|
+
#
|
|
267
|
+
# This is a factory method — it constructs the domain object but does not
|
|
268
|
+
# immediately execute any git commands.
|
|
269
|
+
#
|
|
270
|
+
# @example Get a worktree object for a new path
|
|
271
|
+
# wt = repo.worktree('/tmp/feature')
|
|
272
|
+
#
|
|
273
|
+
# @example Get a worktree object for a specific branch or commit
|
|
274
|
+
# wt = repo.worktree('/tmp/hotfix', 'main')
|
|
275
|
+
#
|
|
276
|
+
# @param dir [String] filesystem path for the worktree
|
|
277
|
+
#
|
|
278
|
+
# @param commitish [String, nil] branch, tag, or commit to associate with
|
|
279
|
+
# the worktree; `nil` means no commitish is specified
|
|
280
|
+
#
|
|
281
|
+
# @return [Git::Worktree] a worktree domain object for the given path
|
|
282
|
+
#
|
|
283
|
+
# @deprecated Use {#worktree_add} and {#worktree_remove} instead
|
|
284
|
+
#
|
|
285
|
+
# `repo.worktree(dir, commitish).add` becomes
|
|
286
|
+
# `repo.worktree_add(dir, commitish)` and `repo.worktree(dir).remove`
|
|
287
|
+
# becomes `repo.worktree_remove(dir)`. Read a worktree's checked-out
|
|
288
|
+
# commit from {Git::WorktreeInfo#head} via {#worktree_list}.
|
|
289
|
+
#
|
|
290
|
+
# @see #worktree_add
|
|
291
|
+
#
|
|
292
|
+
# @see #worktree_remove
|
|
293
|
+
#
|
|
294
|
+
def worktree(dir, commitish = nil)
|
|
295
|
+
Git::Deprecation.warn(
|
|
296
|
+
'Git::Repository#worktree is deprecated and will be removed in v6.0.0. ' \
|
|
297
|
+
'Use Git::Repository#worktree_add and Git::Repository#worktree_remove instead.'
|
|
298
|
+
)
|
|
299
|
+
Git::Worktree.new(self, dir, commitish)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Return a {Git::Worktrees} collection of all worktrees (main and linked)
|
|
303
|
+
#
|
|
304
|
+
# The collection is populated eagerly when this method is called (git runs
|
|
305
|
+
# at construction time). It is enumerable and supports indexed access by
|
|
306
|
+
# worktree path.
|
|
307
|
+
#
|
|
308
|
+
# @example Iterate over all worktrees
|
|
309
|
+
# repo.worktrees.each { |wt| puts wt.dir }
|
|
310
|
+
#
|
|
311
|
+
# @example Count worktrees
|
|
312
|
+
# repo.worktrees.size
|
|
313
|
+
#
|
|
314
|
+
# @example Access a specific worktree by path
|
|
315
|
+
# repo.worktrees['/tmp/feature']
|
|
316
|
+
#
|
|
317
|
+
# @return [Git::Worktrees] an enumerable collection of all worktrees
|
|
318
|
+
#
|
|
319
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
320
|
+
#
|
|
321
|
+
# @deprecated Use {#worktree_list} instead
|
|
322
|
+
#
|
|
323
|
+
# {#worktree_list} returns `Array<Git::WorktreeInfo>`. Look a worktree up
|
|
324
|
+
# by path with `worktree_list.find { |w| w.path == path }` in place of
|
|
325
|
+
# `worktrees[path]`, and call {#worktree_prune} in place of
|
|
326
|
+
# `worktrees.prune`. Calling this method emits one deprecation warning.
|
|
327
|
+
#
|
|
328
|
+
# @see #worktree_list
|
|
329
|
+
#
|
|
330
|
+
def worktrees
|
|
331
|
+
Git::Deprecation.warn(
|
|
332
|
+
'Git::Repository#worktrees is deprecated and will be removed in v6.0.0. ' \
|
|
333
|
+
'Use Git::Repository#worktree_list instead.'
|
|
334
|
+
)
|
|
335
|
+
Git::Worktrees.new(self)
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
end
|