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,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
# Value object representing a detached HEAD state
|
|
5
|
+
#
|
|
6
|
+
# When HEAD points directly to a commit rather than a branch reference,
|
|
7
|
+
# the repository is in a "detached HEAD" state. This object captures
|
|
8
|
+
# that state along with the commit SHA that HEAD points to.
|
|
9
|
+
#
|
|
10
|
+
# This class shares a minimal interface with {Git::BranchInfo} to allow
|
|
11
|
+
# polymorphic usage where appropriate:
|
|
12
|
+
# - `short_name` - returns 'HEAD'
|
|
13
|
+
# - `target_oid` - returns the commit SHA
|
|
14
|
+
# - `to_s` - returns 'HEAD'
|
|
15
|
+
# - `detached?` - returns true
|
|
16
|
+
#
|
|
17
|
+
# @example Detecting detached HEAD state
|
|
18
|
+
# head = repo.show_current
|
|
19
|
+
# if head.detached?
|
|
20
|
+
# puts "HEAD detached at #{head.target_oid[0, 7]}"
|
|
21
|
+
# else
|
|
22
|
+
# puts "On branch #{head.short_name}"
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# @example Polymorphic usage
|
|
26
|
+
# head = repo.show_current
|
|
27
|
+
# puts "Checked out: #{head.short_name}" # Works for both types
|
|
28
|
+
# system("git log #{head.short_name}") # Works for both types
|
|
29
|
+
#
|
|
30
|
+
# @see Git::BranchInfo for the branch counterpart
|
|
31
|
+
#
|
|
32
|
+
# @see Git::Commands::Branch::ShowCurrent for the command that produces this
|
|
33
|
+
#
|
|
34
|
+
# @api private
|
|
35
|
+
#
|
|
36
|
+
# Work in progress; this class is internal for now and may be made public in a future release.
|
|
37
|
+
#
|
|
38
|
+
# @!attribute [r] target_oid
|
|
39
|
+
#
|
|
40
|
+
# The commit object ID (SHA) that HEAD points to
|
|
41
|
+
#
|
|
42
|
+
# @return [String] the full 40-character object ID
|
|
43
|
+
#
|
|
44
|
+
DetachedHeadInfo = Data.define(:target_oid) do
|
|
45
|
+
# @return [Boolean] always true for DetachedHeadInfo
|
|
46
|
+
def detached? = true
|
|
47
|
+
|
|
48
|
+
# @return [Boolean] always false for DetachedHeadInfo (detached HEAD always has a commit)
|
|
49
|
+
def unborn? = false
|
|
50
|
+
|
|
51
|
+
# @return [String] always 'HEAD'
|
|
52
|
+
def short_name = 'HEAD'
|
|
53
|
+
|
|
54
|
+
# @return [String] always 'HEAD'
|
|
55
|
+
def to_s = 'HEAD'
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/git/diff.rb
CHANGED
|
@@ -1,79 +1,294 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'diff_path_status'
|
|
4
|
+
require_relative 'diff_stats'
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
module Git
|
|
7
|
+
# Diff between two commits or between a commit and the working tree
|
|
8
|
+
#
|
|
9
|
+
# @example Diff between two commits
|
|
10
|
+
# diff = repo.diff('HEAD~1', 'HEAD')
|
|
11
|
+
# diff.size # => 3
|
|
12
|
+
# diff.insertions # => 20
|
|
13
|
+
# diff.deletions # => 5
|
|
14
|
+
#
|
|
15
|
+
# @example Limit diff to a specific path
|
|
16
|
+
# diff = repo.diff('HEAD~1', 'HEAD').path('lib/')
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
#
|
|
4
20
|
class Diff
|
|
5
21
|
include Enumerable
|
|
6
22
|
|
|
23
|
+
# Creates a new Diff
|
|
24
|
+
#
|
|
25
|
+
# @example
|
|
26
|
+
# diff = Git::Diff.new(base, 'HEAD~1', 'HEAD')
|
|
27
|
+
#
|
|
28
|
+
# @param base [Git::Repository] the git repository
|
|
29
|
+
#
|
|
30
|
+
# @param from [String, nil] the starting commit ref, or `nil` to compare
|
|
31
|
+
# from the index
|
|
32
|
+
#
|
|
33
|
+
# @param to [String, nil] the ending commit ref, or `nil` to compare to
|
|
34
|
+
# the working tree
|
|
35
|
+
#
|
|
36
|
+
# @return [void]
|
|
37
|
+
#
|
|
7
38
|
def initialize(base, from = nil, to = nil)
|
|
8
39
|
@base = base
|
|
9
|
-
@from = from
|
|
10
|
-
@to = to
|
|
40
|
+
@from = from&.to_s
|
|
41
|
+
@to = to&.to_s
|
|
11
42
|
|
|
12
43
|
@path = nil
|
|
13
|
-
@full_diff = nil
|
|
14
44
|
@full_diff_files = nil
|
|
15
|
-
@stats = nil
|
|
16
45
|
end
|
|
17
|
-
attr_reader :from, :to
|
|
18
46
|
|
|
19
|
-
|
|
20
|
-
|
|
47
|
+
# The starting commit ref
|
|
48
|
+
#
|
|
49
|
+
# @return [String, nil] the starting commit ref, or `nil` if not set
|
|
50
|
+
#
|
|
51
|
+
attr_reader :from
|
|
52
|
+
|
|
53
|
+
# The ending commit ref
|
|
54
|
+
#
|
|
55
|
+
# @return [String, nil] the ending commit ref, or `nil` if not set
|
|
56
|
+
#
|
|
57
|
+
attr_reader :to
|
|
58
|
+
|
|
59
|
+
# Limits the diff to the specified path(s)
|
|
60
|
+
#
|
|
61
|
+
# When called with no arguments (or only nil arguments), removes any existing
|
|
62
|
+
# path filter, showing all files in the diff. Internally stores a single path
|
|
63
|
+
# as a String and multiple paths as an Array for efficiency.
|
|
64
|
+
#
|
|
65
|
+
# @example Limit diff to a single path
|
|
66
|
+
# git.diff('HEAD~3', 'HEAD').path('lib/')
|
|
67
|
+
#
|
|
68
|
+
# @example Limit diff to multiple paths
|
|
69
|
+
# git.diff('HEAD~3', 'HEAD').path('src/', 'docs/', 'README.md')
|
|
70
|
+
#
|
|
71
|
+
# @example Remove path filtering (show all files)
|
|
72
|
+
# diff.path # or diff.path(nil)
|
|
73
|
+
#
|
|
74
|
+
# @param paths [String, Pathname] one or more paths to filter the diff;
|
|
75
|
+
# pass no arguments to remove filtering
|
|
76
|
+
#
|
|
77
|
+
# @return [self] returns self for method chaining
|
|
78
|
+
#
|
|
79
|
+
# @raise [ArgumentError] if any path is an Array (use splatted arguments instead)
|
|
80
|
+
#
|
|
81
|
+
def path(*paths)
|
|
82
|
+
validate_paths_not_arrays(paths)
|
|
83
|
+
|
|
84
|
+
cleaned_paths = paths.compact
|
|
85
|
+
|
|
86
|
+
@path = if cleaned_paths.empty?
|
|
87
|
+
nil
|
|
88
|
+
elsif cleaned_paths.length == 1
|
|
89
|
+
cleaned_paths.first
|
|
90
|
+
else
|
|
91
|
+
cleaned_paths
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
self
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Returns the full diff output as a string
|
|
98
|
+
#
|
|
99
|
+
# @example
|
|
100
|
+
# diff.patch # => "diff --git a/file.rb b/file.rb\n..."
|
|
101
|
+
#
|
|
102
|
+
# @return [String] the raw output of `git diff`
|
|
103
|
+
#
|
|
104
|
+
def patch
|
|
105
|
+
@base.diff_full(@from, @to, path_limiter: @path)
|
|
106
|
+
end
|
|
107
|
+
alias to_s patch
|
|
108
|
+
|
|
109
|
+
# Returns the diff file info for the given path
|
|
110
|
+
#
|
|
111
|
+
# @example
|
|
112
|
+
# diff['lib/git.rb'] # => #<Git::Diff::DiffFile ...>
|
|
113
|
+
#
|
|
114
|
+
# @param key [String] the file path to look up
|
|
115
|
+
#
|
|
116
|
+
# @return [Git::Diff::DiffFile] the diff file object for the given path
|
|
117
|
+
#
|
|
118
|
+
def [](key)
|
|
119
|
+
process_full
|
|
120
|
+
@full_diff_files.assoc(key)[1]
|
|
21
121
|
end
|
|
22
122
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
123
|
+
# Iterates over each changed file in the diff
|
|
124
|
+
#
|
|
125
|
+
# @overload each
|
|
126
|
+
# @example Get an enumerator
|
|
127
|
+
# diff.each.map(&:path) # => ["lib/git.rb", "README.md"]
|
|
128
|
+
#
|
|
129
|
+
# @return [Enumerator<Git::Diff::DiffFile>] an enumerator over the
|
|
130
|
+
# changed files
|
|
131
|
+
#
|
|
132
|
+
# @overload each(&block)
|
|
133
|
+
# @example Iterate with a block
|
|
134
|
+
# diff.each { |file| puts file.path }
|
|
135
|
+
#
|
|
136
|
+
# @yield [file] each changed file in the diff
|
|
137
|
+
#
|
|
138
|
+
# @yieldparam file [Git::Diff::DiffFile] a changed file
|
|
139
|
+
#
|
|
140
|
+
# @yieldreturn [void]
|
|
141
|
+
#
|
|
142
|
+
# @return [Array<Git::Diff::DiffFile>] the array of changed files
|
|
143
|
+
#
|
|
144
|
+
def each(&)
|
|
145
|
+
process_full
|
|
146
|
+
@full_diff_files.map { |file| file[1] }.each(&)
|
|
26
147
|
end
|
|
27
148
|
|
|
149
|
+
# Returns the number of changed files in the diff
|
|
150
|
+
#
|
|
151
|
+
# @example
|
|
152
|
+
# diff.size # => 3
|
|
153
|
+
#
|
|
154
|
+
# @return [Integer] the number of changed files
|
|
155
|
+
#
|
|
28
156
|
def size
|
|
29
|
-
|
|
30
|
-
|
|
157
|
+
stats_provider.total[:files]
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
#
|
|
161
|
+
# DEPRECATED METHODS
|
|
162
|
+
#
|
|
163
|
+
|
|
164
|
+
# Returns the path-to-status hash for all changed files in the diff
|
|
165
|
+
#
|
|
166
|
+
# @example
|
|
167
|
+
# diff.name_status # => { "lib/git.rb" => "M", "README.md" => "A" }
|
|
168
|
+
#
|
|
169
|
+
# @return [Hash<String, String>] map of file path to git status letter
|
|
170
|
+
#
|
|
171
|
+
def name_status
|
|
172
|
+
path_status_provider.to_h
|
|
31
173
|
end
|
|
32
174
|
|
|
175
|
+
# Returns the total number of changed lines in the diff
|
|
176
|
+
#
|
|
177
|
+
# @example
|
|
178
|
+
# diff.lines # => 42
|
|
179
|
+
#
|
|
180
|
+
# @return [Integer] the total number of inserted and deleted lines
|
|
181
|
+
#
|
|
33
182
|
def lines
|
|
34
|
-
|
|
35
|
-
@stats[:total][:lines]
|
|
183
|
+
stats_provider.lines
|
|
36
184
|
end
|
|
37
185
|
|
|
186
|
+
# Returns the total number of deleted lines in the diff
|
|
187
|
+
#
|
|
188
|
+
# @example
|
|
189
|
+
# diff.deletions # => 10
|
|
190
|
+
#
|
|
191
|
+
# @return [Integer] the number of deleted lines
|
|
192
|
+
#
|
|
38
193
|
def deletions
|
|
39
|
-
|
|
40
|
-
@stats[:total][:deletions]
|
|
194
|
+
stats_provider.deletions
|
|
41
195
|
end
|
|
42
196
|
|
|
197
|
+
# Returns the total number of inserted lines in the diff
|
|
198
|
+
#
|
|
199
|
+
# @example
|
|
200
|
+
# diff.insertions # => 32
|
|
201
|
+
#
|
|
202
|
+
# @return [Integer] the number of inserted lines
|
|
203
|
+
#
|
|
43
204
|
def insertions
|
|
44
|
-
|
|
45
|
-
@stats[:total][:insertions]
|
|
205
|
+
stats_provider.insertions
|
|
46
206
|
end
|
|
47
207
|
|
|
208
|
+
# Returns a statistics hash for the diff
|
|
209
|
+
#
|
|
210
|
+
# @example
|
|
211
|
+
# diff.stats
|
|
212
|
+
# # => {
|
|
213
|
+
# # files: { "lib/git.rb" => { insertions: 5, deletions: 2 } },
|
|
214
|
+
# # total: { insertions: 5, deletions: 2, lines: 7 }
|
|
215
|
+
# # }
|
|
216
|
+
#
|
|
217
|
+
# @return [Hash] statistics including per-file and total insert/delete counts
|
|
218
|
+
#
|
|
48
219
|
def stats
|
|
49
|
-
|
|
50
|
-
|
|
220
|
+
{
|
|
221
|
+
files: stats_provider.files,
|
|
222
|
+
total: stats_provider.total
|
|
223
|
+
}
|
|
51
224
|
end
|
|
52
225
|
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
226
|
+
# Information about a single changed file within a {Git::Diff}
|
|
227
|
+
#
|
|
228
|
+
# @example Access diff file information
|
|
229
|
+
# diff.each do |file|
|
|
230
|
+
# puts file.path
|
|
231
|
+
# puts file.binary? ? 'binary' : file.patch
|
|
232
|
+
# end
|
|
233
|
+
#
|
|
234
|
+
# @api public
|
|
235
|
+
#
|
|
236
|
+
class DiffFile
|
|
237
|
+
# The raw diff patch text for this file
|
|
238
|
+
#
|
|
239
|
+
# @return [String, nil] the patch text
|
|
240
|
+
#
|
|
241
|
+
attr_accessor :patch
|
|
59
242
|
|
|
60
|
-
|
|
243
|
+
# The file path relative to the repository root
|
|
244
|
+
#
|
|
245
|
+
# @return [String, nil] the file path
|
|
246
|
+
#
|
|
247
|
+
attr_accessor :path
|
|
61
248
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
@
|
|
65
|
-
|
|
249
|
+
# The file mode
|
|
250
|
+
#
|
|
251
|
+
# @return [String] the octal file mode (e.g. `"100644"`)
|
|
252
|
+
#
|
|
253
|
+
attr_accessor :mode
|
|
66
254
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@
|
|
70
|
-
|
|
255
|
+
# The source (pre-change) blob SHA
|
|
256
|
+
#
|
|
257
|
+
# @return [String] the source blob SHA
|
|
258
|
+
#
|
|
259
|
+
attr_accessor :src
|
|
260
|
+
|
|
261
|
+
# The destination (post-change) blob SHA
|
|
262
|
+
#
|
|
263
|
+
# @return [String] the destination blob SHA
|
|
264
|
+
#
|
|
265
|
+
attr_accessor :dst
|
|
266
|
+
|
|
267
|
+
# The type of change
|
|
268
|
+
#
|
|
269
|
+
# @return [String] the change type (e.g. `"modified"`, `"new"`, `"deleted"`)
|
|
270
|
+
#
|
|
271
|
+
attr_accessor :type
|
|
71
272
|
|
|
72
|
-
class DiffFile
|
|
73
|
-
attr_accessor :patch, :path, :mode, :src, :dst, :type
|
|
74
273
|
@base = nil
|
|
75
|
-
NIL_BLOB_REGEXP = /\A0{4,40}\z/.freeze
|
|
76
274
|
|
|
275
|
+
# Regexp matching a nil blob SHA (all-zero hash of 4 to 40 hex digits)
|
|
276
|
+
NIL_BLOB_REGEXP = /\A0{4,40}\z/
|
|
277
|
+
|
|
278
|
+
# Creates a new DiffFile from parsed diff data
|
|
279
|
+
#
|
|
280
|
+
# @example
|
|
281
|
+
# file = Git::Diff::DiffFile.new(base,
|
|
282
|
+
# patch: "diff --git ...", path: 'lib/git.rb',
|
|
283
|
+
# mode: '100644', src: 'abc123', dst: 'def456',
|
|
284
|
+
# type: 'modified', binary: false)
|
|
285
|
+
#
|
|
286
|
+
# @param base [Git::Repository] the git repository
|
|
287
|
+
#
|
|
288
|
+
# @param hash [Hash] the parsed diff attributes
|
|
289
|
+
#
|
|
290
|
+
# @return [void]
|
|
291
|
+
#
|
|
77
292
|
def initialize(base, hash)
|
|
78
293
|
@base = base
|
|
79
294
|
@patch = hash[:patch]
|
|
@@ -85,71 +300,207 @@ module Git
|
|
|
85
300
|
@binary = hash[:binary]
|
|
86
301
|
end
|
|
87
302
|
|
|
303
|
+
# Returns true if this file is a binary file
|
|
304
|
+
#
|
|
305
|
+
# @example
|
|
306
|
+
# diff['path/to/image.png'].binary? # => true
|
|
307
|
+
#
|
|
308
|
+
# @return [Boolean] `true` if the file is binary, `false` otherwise
|
|
309
|
+
#
|
|
88
310
|
def binary?
|
|
89
311
|
!!@binary
|
|
90
312
|
end
|
|
91
313
|
|
|
314
|
+
# Returns the blob object for this file
|
|
315
|
+
#
|
|
316
|
+
# @example Retrieve the destination blob
|
|
317
|
+
# file.blob # => #<Git::Object::Blob ...>
|
|
318
|
+
#
|
|
319
|
+
# @example Retrieve the source blob
|
|
320
|
+
# file.blob(:src) # => #<Git::Object::Blob ...>
|
|
321
|
+
#
|
|
322
|
+
# @param type [Symbol] `:src` to retrieve the source blob, or `:dst`
|
|
323
|
+
# (default) for the destination blob
|
|
324
|
+
#
|
|
325
|
+
# @return [Git::Object::Blob, nil] the blob object, or `nil` if the blob
|
|
326
|
+
# SHA is the null SHA
|
|
327
|
+
#
|
|
92
328
|
def blob(type = :dst)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
elsif !NIL_BLOB_REGEXP.match(@dst)
|
|
96
|
-
@base.object(@dst)
|
|
97
|
-
end
|
|
329
|
+
sha = type == :src ? @src : @dst
|
|
330
|
+
@base.object(sha) unless NIL_BLOB_REGEXP.match(sha)
|
|
98
331
|
end
|
|
99
332
|
end
|
|
100
333
|
|
|
101
334
|
private
|
|
102
335
|
|
|
103
|
-
|
|
104
|
-
|
|
336
|
+
# Validates that no path argument is an Array
|
|
337
|
+
#
|
|
338
|
+
# @param paths [Array] the raw paths array passed to {#path}
|
|
339
|
+
#
|
|
340
|
+
# @return [void]
|
|
341
|
+
#
|
|
342
|
+
# @raise [ArgumentError] if any element of paths is an Array
|
|
343
|
+
#
|
|
344
|
+
def validate_paths_not_arrays(paths)
|
|
345
|
+
return unless paths.any?(Array)
|
|
346
|
+
|
|
347
|
+
raise ArgumentError,
|
|
348
|
+
'path expects individual arguments, not arrays. ' \
|
|
349
|
+
"Use path('lib/', 'docs/') not path(['lib/', 'docs/'])"
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# Triggers full diff processing if not yet done
|
|
353
|
+
#
|
|
354
|
+
# @return [void]
|
|
355
|
+
#
|
|
356
|
+
def process_full
|
|
357
|
+
return if @full_diff_files
|
|
358
|
+
|
|
359
|
+
@full_diff_files = process_full_diff
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Returns a memoized DiffPathStatus provider for this diff
|
|
363
|
+
#
|
|
364
|
+
# @return [Git::DiffPathStatus] the path status provider
|
|
365
|
+
#
|
|
366
|
+
def path_status_provider
|
|
367
|
+
@path_status_provider ||= Git::DiffPathStatus.new(@base, @from, @to, @path)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Returns a memoized DiffStats provider for this diff
|
|
371
|
+
#
|
|
372
|
+
# @return [Git::DiffStats] the stats provider
|
|
373
|
+
#
|
|
374
|
+
def stats_provider
|
|
375
|
+
@stats_provider ||= Git::DiffStats.new(@base, @from, @to, @path)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# Parses the full diff output into DiffFile objects
|
|
379
|
+
#
|
|
380
|
+
# @return [Array<Array(String, Git::Diff::DiffFile)>] list of
|
|
381
|
+
# `[filename, DiffFile]` pairs
|
|
382
|
+
#
|
|
383
|
+
def process_full_diff
|
|
384
|
+
FullDiffParser.new(@base, patch).parse
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# Private parser for `git diff` output
|
|
388
|
+
#
|
|
389
|
+
# @example Parse a diff patch
|
|
390
|
+
# parser = Git::Diff::FullDiffParser.new(base, patch_text)
|
|
391
|
+
# files = parser.parse
|
|
392
|
+
#
|
|
393
|
+
# @api private
|
|
394
|
+
#
|
|
395
|
+
class FullDiffParser
|
|
396
|
+
# Creates a new FullDiffParser
|
|
397
|
+
#
|
|
398
|
+
# @param base [Git::Repository] the git repository
|
|
399
|
+
#
|
|
400
|
+
# @param patch_text [String] the raw `git diff` output to parse
|
|
401
|
+
#
|
|
402
|
+
# @return [void]
|
|
403
|
+
#
|
|
404
|
+
def initialize(base, patch_text)
|
|
405
|
+
@base = base
|
|
406
|
+
@patch_text = patch_text
|
|
407
|
+
@final_files = {}
|
|
408
|
+
@current_file_data = nil
|
|
409
|
+
@defaults = { mode: '', src: '', dst: '', type: 'modified', binary: false }
|
|
105
410
|
end
|
|
106
411
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
412
|
+
# Parses the diff text into a list of filename/DiffFile pairs
|
|
413
|
+
#
|
|
414
|
+
# @return [Array<Array(String, Git::Diff::DiffFile)>] list of
|
|
415
|
+
# `[filename, DiffFile]` pairs
|
|
416
|
+
#
|
|
417
|
+
def parse
|
|
418
|
+
@patch_text.split("\n").each { |line| process_line(line) }
|
|
419
|
+
@final_files.map { |filename, data| [filename, DiffFile.new(@base, data)] }
|
|
111
420
|
end
|
|
112
421
|
|
|
113
|
-
|
|
114
|
-
|
|
422
|
+
private
|
|
423
|
+
|
|
424
|
+
# Dispatches a single diff line to the appropriate handler
|
|
425
|
+
#
|
|
426
|
+
# @param line [String] a line from the diff output
|
|
427
|
+
#
|
|
428
|
+
# @return [void]
|
|
429
|
+
#
|
|
430
|
+
def process_line(line)
|
|
431
|
+
if (new_file_match = line.match(%r{\Adiff --git ("?)a/(.+?)\1 ("?)b/(.+?)\3\z}))
|
|
432
|
+
start_new_file(new_file_match, line)
|
|
433
|
+
else
|
|
434
|
+
append_to_current_file(line)
|
|
435
|
+
end
|
|
115
436
|
end
|
|
116
437
|
|
|
117
|
-
|
|
118
|
-
|
|
438
|
+
# Starts tracking a new file from a diff header line
|
|
439
|
+
#
|
|
440
|
+
# @param match [MatchData] the regex match from the diff header line
|
|
441
|
+
#
|
|
442
|
+
# @param line [String] the original diff header line
|
|
443
|
+
#
|
|
444
|
+
# @return [void]
|
|
445
|
+
#
|
|
446
|
+
def start_new_file(match, line)
|
|
447
|
+
filename = Git::EscapedPath.new(match[2]).unescape
|
|
448
|
+
@current_file_data = @defaults.merge({ patch: line, path: filename })
|
|
449
|
+
@final_files[filename] = @current_file_data
|
|
119
450
|
end
|
|
120
451
|
|
|
121
|
-
#
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
else
|
|
136
|
-
if m = /^index ([0-9a-f]{4,40})\.\.([0-9a-f]{4,40})( ......)*/.match(line)
|
|
137
|
-
final[current_file][:src] = m[1]
|
|
138
|
-
final[current_file][:dst] = m[2]
|
|
139
|
-
final[current_file][:mode] = m[3].strip if m[3]
|
|
140
|
-
end
|
|
141
|
-
if m = /^([[:alpha:]]*?) file mode (......)/.match(line)
|
|
142
|
-
final[current_file][:type] = m[1]
|
|
143
|
-
final[current_file][:mode] = m[2]
|
|
144
|
-
end
|
|
145
|
-
if m = /^Binary files /.match(line)
|
|
146
|
-
final[current_file][:binary] = true
|
|
147
|
-
end
|
|
148
|
-
final[current_file][:patch] << "\n" + line
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
final.map { |e| [e[0], DiffFile.new(@base, e[1])] }
|
|
452
|
+
# Appends a diff line to the current file's accumulated data
|
|
453
|
+
#
|
|
454
|
+
# @param line [String] a diff line to append
|
|
455
|
+
#
|
|
456
|
+
# @return [void]
|
|
457
|
+
#
|
|
458
|
+
def append_to_current_file(line)
|
|
459
|
+
return unless @current_file_data
|
|
460
|
+
|
|
461
|
+
parse_index_line(line)
|
|
462
|
+
parse_file_mode_line(line)
|
|
463
|
+
check_for_binary(line)
|
|
464
|
+
|
|
465
|
+
@current_file_data[:patch] << "\n#{line}"
|
|
152
466
|
end
|
|
153
467
|
|
|
468
|
+
# Parses an index line to extract source and destination blob SHAs
|
|
469
|
+
#
|
|
470
|
+
# @param line [String] a diff line that may be an index line
|
|
471
|
+
#
|
|
472
|
+
# @return [void]
|
|
473
|
+
#
|
|
474
|
+
def parse_index_line(line)
|
|
475
|
+
return unless (match = line.match(/^index ([0-9a-f]{4,40})\.\.([0-9a-f]{4,40})( ......)*/))
|
|
476
|
+
|
|
477
|
+
@current_file_data[:src] = match[1]
|
|
478
|
+
@current_file_data[:dst] = match[2]
|
|
479
|
+
@current_file_data[:mode] = match[3].strip if match[3]
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
# Parses a file mode line to extract the change type and file mode
|
|
483
|
+
#
|
|
484
|
+
# @param line [String] a diff line that may be a file mode line
|
|
485
|
+
#
|
|
486
|
+
# @return [void]
|
|
487
|
+
#
|
|
488
|
+
def parse_file_mode_line(line)
|
|
489
|
+
return unless (match = line.match(/^([[:alpha:]]*?) file mode (......)/))
|
|
490
|
+
|
|
491
|
+
@current_file_data[:type] = match[1]
|
|
492
|
+
@current_file_data[:mode] = match[2]
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Marks the current file as binary if this is a binary diff line
|
|
496
|
+
#
|
|
497
|
+
# @param line [String] a diff line to check for the binary file marker
|
|
498
|
+
#
|
|
499
|
+
# @return [void]
|
|
500
|
+
#
|
|
501
|
+
def check_for_binary(line)
|
|
502
|
+
@current_file_data[:binary] = true if line.match?(/^Binary files /)
|
|
503
|
+
end
|
|
504
|
+
end
|
|
154
505
|
end
|
|
155
506
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
# Immutable value object representing stats for a single file from git numstat output
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
7
|
+
#
|
|
8
|
+
# Work in progress; this class is internal for now and may be made public in a future release.
|
|
9
|
+
#
|
|
10
|
+
# @!attribute [r] path
|
|
11
|
+
# @return [String] the file path (destination path for renames)
|
|
12
|
+
#
|
|
13
|
+
# @!attribute [r] src_path
|
|
14
|
+
# @return [String, nil] the source path for renamed files, nil otherwise
|
|
15
|
+
#
|
|
16
|
+
# @!attribute [r] insertions
|
|
17
|
+
# @return [Integer] number of lines inserted
|
|
18
|
+
#
|
|
19
|
+
# @!attribute [r] deletions
|
|
20
|
+
# @return [Integer] number of lines deleted
|
|
21
|
+
#
|
|
22
|
+
DiffFileNumstatInfo = Data.define(:path, :src_path, :insertions, :deletions) do
|
|
23
|
+
# Check if this file was renamed
|
|
24
|
+
#
|
|
25
|
+
# @return [Boolean] true if the file was renamed
|
|
26
|
+
#
|
|
27
|
+
def renamed?
|
|
28
|
+
!src_path.nil?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|