module Git::Repository::Diffing
Facade methods for comparing commits and trees using ‘git diff`
Included by {Git::Repository}.
@api private
Constants
- DIFF_FULL_ALLOWED_OPTS
-
Option keys accepted by {#diff_full}
@return [Array<Symbol>]
@api private
- DIFF_NUMSTAT_ALLOWED_OPTS
-
Option keys accepted by {#diff_numstat}
@return [Array<Symbol>]
@api private
- DIFF_PATH_STATUS_ALLOWED_OPTS
-
Option keys accepted by {#diff_path_status}
@return [Array<Symbol>]
@api private
- DIFF_STATS_ALLOWED_OPTS
-
Option keys accepted by {#diff_stats}
@return [Array<Symbol>]
@api private
Public Instance Methods
Source
# File lib/git/repository/diffing.rb, line 292 def diff(obj1 = 'HEAD', obj2 = nil) Git::Diff.new(self, obj1, obj2) end
Returns a lazy {Git::Diff} object for the comparison between two trees
Compares (1) two commits, (2) a commit against the working tree, or (3) the index against the working tree. The returned {Git::Diff} is lazy — it does not run any git commands until an accessor method (e.g., {Git::Diff#patch}, {Git::Diff#each}) is called.
Use {Git::Diff#path} to limit the diff to a sub-path after construction.
@example Get the diff since HEAD
diff = repo.diff diff.patch #=> "diff --git a/lib/foo.rb ..."
@example Compare two specific commits
repo.diff('abc1234', 'def5678').patch
@example Limit to a sub-path
repo.diff('HEAD~1', 'HEAD').path('lib/').patch
@example Get unstaged changes (index vs. working tree)
repo.diff(nil).patch
@param obj1 [String, nil] the first commit or object to compare; defaults to
`'HEAD'`
@param obj2 [String, nil] the second commit or object to compare
@return [Git::Diff] a lazy diff object for the comparison
@see git-scm.com/docs/git-diff git-diff documentation
Source
# File lib/git/repository/diffing.rb, line 424 def diff_files Git::Commands::Status.new(@execution_context).call Private.parse_diff_files_output( Git::Commands::DiffFiles.new(@execution_context).call.stdout ) end
Compares the index and the working directory
Runs ‘git diff-files` to list files that differ between the index (staging area) and the working directory. These are changes that have been made to tracked files but not yet staged.
@example List all files with unstaged changes
repo.diff_files #=> { # "lib/foo.rb" => { # mode_index: "100644", mode_repo: "100644", # path: "lib/foo.rb", sha_repo: "abc1234", # sha_index: "0000000000000000000000000000000000000000", # type: "M" # } # }
@return [Hash{String => Hash}] a hash keyed by file path
Each value is a hash with the following keys (note the legacy naming where `:*_repo` holds index data and `:*_index` holds working tree data): * `:mode_index` [String] the working tree file mode (legacy name) * `:mode_repo` [String] the index (staging area) file mode (legacy name) * `:path` [String] the file path * `:sha_repo` [String] the SHA of the object in the index (staging area) (legacy name) * `:sha_index` [String] the SHA of the object in the working tree; all zeros when git has not computed the working tree blob SHA (legacy name) * `:type` [String] the status code (e.g. `"M"`, `"A"`, `"D"`)
@raise [Git::FailedError] if git exits outside the allowed range (exit code > 1)
@note The field names in the returned hash are **legacy names** inherited
from `Git::Lib#diff_files` and appear counterintuitive: `:mode_repo` and `:sha_repo` hold **index (staging area)** values, while `:mode_index` and `:sha_index` hold **working tree** values.
@see git-scm.com/docs/git-diff-files git-diff-files documentation
Source
# File lib/git/repository/diffing.rb, line 86 def diff_full(obj1 = 'HEAD', obj2 = nil, opts = {}) SharedPrivate.assert_valid_opts!(DIFF_FULL_ALLOWED_OPTS, **opts) raise ArgumentError, 'Invalid arguments: obj1 is nil but obj2 is not' if obj1.nil? && !obj2.nil? pathspecs = Private.normalize_pathspecs(opts[:path_limiter], 'path limiter') result = Git::Commands::Diff.new(@execution_context).call( *[obj1, obj2].compact, patch: true, numstat: true, shortstat: true, src_prefix: 'a/', dst_prefix: 'b/', path: pathspecs ) Private.extract_patch_text(result.stdout) end
Returns the full unified diff patch text between two trees
Compares (1) two commits, (2) a commit against the working tree, or (3) the index against the working tree using ‘git diff -p`, and returns the raw unified diff patch output.
**Comparing two commits**
When both obj1 and obj2 are provided, the comparison is between those two refs (commits, tags, branches, etc.).
**Comparing a commit against the working tree**
When only obj1 is provided (and isn’t nil), the comparison is between obj1 and the working tree; the patch reflects all changes since obj1.
**Comparing the index against the working tree**
When obj1 is explicitly ‘nil` then obj2 must be omitted or `nil`. In this case, the comparison is between the index and the working tree; the patch reflects unstaged changes.
@example Get the working tree patch since HEAD
repo.diff_full #=> "diff --git a/lib/foo.rb b/lib/foo.rb\n..."
@example Compare two specific commits
repo.diff_full('abc1234', 'def5678')
@example Get unstaged changes (index vs. working tree)
repo.diff_full(nil)
@example Limit the diff to a sub-path
repo.diff_full('HEAD~1', 'HEAD', path_limiter: 'lib/')
@param obj1 [String, nil] the first commit or object to compare; defaults to
`'HEAD'`
@param obj2 [String, nil] the second commit or object to compare
@param opts [Hash] options to filter the diff
@option opts [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
limit the diff to the given path(s)
@return [String] the unified diff patch output
@raise [ArgumentError] if unsupported options are provided
@raise [ArgumentError] if ‘obj1` is `nil` but `obj2` is not OR if `obj1` or `obj2` starts with `“-”`
@raise [Git::FailedError] if git exits outside the allowed range (exit code > 2)
@see git-scm.com/docs/git-diff git-diff documentation
Source
# File lib/git/repository/diffing.rb, line 484 def diff_index(treeish) Git::Commands::Status.new(@execution_context).call Private.parse_diff_files_output( Git::Commands::DiffIndex.new(@execution_context).call(treeish).stdout ) end
Compares the working tree against the given tree object
Runs ‘git diff-index <treeish>` (without `–cached`) to list files that differ between the given tree object (e.g. a commit or `“HEAD”`) and the working tree. The index is refreshed via `git status` first so that cached stat information is up to date.
This is equivalent to the 4.x ‘Git::Lib#diff_index` behavior, which also ran `git diff-index` without `–cached`.
@example List all working-tree files that differ from HEAD
repo.diff_index('HEAD') #=> { # "lib/foo.rb" => { # mode_index: "100644", mode_repo: "100644", # path: "lib/foo.rb", sha_repo: "abc1234", # sha_index: "0000000000000000000000000000000000000000", # type: "M" # } # }
@param treeish [String] the tree object to compare against (e.g. ‘’HEAD’‘,
a commit SHA, or a tag name)
@return [Hash{String => Hash}] a hash keyed by file path
Each value is a hash with the following keys (note the legacy naming where `:*_repo` holds tree data and `:*_index` holds working tree data): * `:mode_index` [String] the working tree file mode (legacy name) * `:mode_repo` [String] the tree (treeish) file mode (legacy name) * `:path` [String] the file path * `:sha_repo` [String] the SHA of the object in the tree (treeish) (legacy name) * `:sha_index` [String] the SHA of the object in the working tree; all zeros when git has not yet computed the working tree blob SHA (legacy name) * `:type` [String] the status code (e.g. `"M"`, `"A"`, `"D"`)
@raise [Git::FailedError] if git exits outside the allowed range (exit code > 1)
@note ‘git diff-index` without `–cached` uses the index as a stat cache:
any file whose index entry differs from the tree is reported as changed, even when the on-disk working-tree content is byte-for-byte identical to the tree. A staged change that has been reverted in the working tree will therefore still appear in the result (because the index still differs from the tree).
@note The field names in the returned hash are **legacy names** inherited
from `Git::Lib#diff_index` and appear counterintuitive: `:mode_repo` and `:sha_repo` hold **tree (treeish)** values, while `:mode_index` and `:sha_index` hold **working tree** values.
@see git-scm.com/docs/git-diff-index git-diff-index documentation
Alias for {#diff_path_status}; provided for backward compatibility
@return [Git::DiffPathStatus] the name-status report for the comparison
@deprecated Use {#diff_path_status} instead
@see diff_path_status
Source
# File lib/git/repository/diffing.rb, line 175 def diff_numstat(obj1 = 'HEAD', obj2 = nil, opts = {}) SharedPrivate.assert_valid_opts!(DIFF_NUMSTAT_ALLOWED_OPTS, **opts) raise ArgumentError, 'Invalid arguments: obj1 is nil but obj2 is not' if obj1.nil? && !obj2.nil? pathspecs = Private.normalize_pathspecs(opts[:path_limiter], 'path limiter') result = Git::Commands::Diff.new(@execution_context).call( *[obj1, obj2].compact, numstat: true, shortstat: true, src_prefix: 'a/', dst_prefix: 'b/', path: pathspecs ) Private.parse_numstat_output(result.stdout) end
Returns per-file insertion/deletion counts and totals between two trees
Compares (1) two commits, (2) a commit against the working tree, or (3) the index against the working tree using ‘git diff –numstat`, and returns a structured hash of per-file insertion and deletion line counts together with aggregate totals.
**Comparing two commits**
When both obj1 and obj2 are provided, the comparison is between those two refs (commits, tags, branches, etc.).
**Comparing a commit against the working tree**
When only obj1 is provided (and isn’t nil), the comparison is between obj1 and the working tree; the stats reflect all changes since obj1.
**Comparing the index against the working tree**
When obj1 is explicitly ‘nil` then obj2 must be omitted or `nil`. In this case, the comparison is between the index and the working tree; the stats reflect unstaged changes.
@example Compare two specific commits
repo.diff_numstat('abc1234', 'def5678')
@example Get working tree changes since HEAD
repo.diff_numstat #=> { # total: { insertions: 5, deletions: 2, lines: 7, files: 1 }, # files: { "lib/foo.rb" => { insertions: 5, deletions: 2 } } # }
@example Get unstaged changes (index vs. working tree)
repo.diff_numstat(nil) #=> { ... }
@example Limit the stats to a sub-path
repo.diff_numstat('HEAD~1', 'HEAD', path_limiter: 'lib/')
@param obj1 [String, nil] the first commit or object to compare; defaults to
`'HEAD'`
@param obj2 [String, nil] the second commit or object to compare
@param opts [Hash] options to filter the diff
@option opts [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
limit the stats to the given path(s)
@return [Hash] per-file insertion and deletion counts plus aggregate totals
```
{
total: { insertions: Integer, deletions: Integer, lines: Integer, files: Integer },
files: { "path/to/file" => { insertions: Integer, deletions: Integer } }
}
```
@raise [ArgumentError] if unsupported options are provided
@raise [ArgumentError] if ‘obj1` is `nil` but `obj2` is not OR if `obj1` or `obj2` starts with `“-”`
@raise [Git::FailedError] if git exits outside the allowed range (exit code > 2)
@see git-scm.com/docs/git-diff git-diff documentation
Source
# File lib/git/repository/diffing.rb, line 365 def diff_path_status(from = 'HEAD', to = nil, opts = {}) SharedPrivate.assert_valid_opts!(DIFF_PATH_STATUS_ALLOWED_OPTS, **opts) raise ArgumentError, 'Invalid arguments: `from` is nil but `to` is not' if from.nil? && !to.nil? path_limiter = Private.resolve_path_limiter(opts) pathspecs = Private.normalize_pathspecs(path_limiter, 'path limiter') result = Private.call_diff_command(@execution_context, from, to, pathspecs) Git::DiffPathStatus.new(Private.extract_name_status_from_raw(result.stdout)) end
Returns the file path status between two trees
Compares (1) two commits, (2) a commit against the working tree, or (3) the index against the working tree and returns a {Git::DiffPathStatus} enumerating each changed file together with its status code (e.g. ‘“M”` for modified, `“A”` for added, `“D”` for deleted, `“R100”` for a rename with 100% similarity, etc.).
**Comparing two commits**
When both from and to are provided, the comparison is between those two refs (commits, tags, branches, etc.).
**Comparing a commit against the working tree**
When only from is provided (and isn’t nil), the comparison is between from and the working tree; the status reflects all changes since from.
**Comparing the index against the working tree**
When from is explicitly ‘nil` then to must be omitted or `nil`. In this case, the comparison is between the index and the working tree; the status reflects unstaged changes.
@example Get working tree path changes since HEAD
repo.diff_path_status #=> #<Git::DiffPathStatus ...> repo.diff_path_status.to_h #=> { "README.md" => "M", "lib/foo.rb" => "A" }
@example Compare two specific commits
repo.diff_path_status('abc1234', 'def5678').to_h
@example Get unstaged path changes (index vs. working tree)
repo.diff_path_status(nil).to_h
@example Limit the comparison to a sub-path
repo.diff_path_status('HEAD~1', 'HEAD', path_limiter: 'lib/')
@param from [String, nil] the first commit or object to compare; defaults to
`'HEAD'`
@param to [String, nil] the second commit or object to compare
@param opts [Hash] options to filter the diff
@option opts [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
limit the status report to the given path(s)
@option opts [String, Pathname, Array<String, Pathname>, nil] :path (nil)
**deprecated** — use `:path_limiter` instead
@return [Git::DiffPathStatus] the name-status report for the comparison
@raise [ArgumentError] if unsupported options are provided
@raise [ArgumentError] if ‘from` is `nil` but `to` is not OR if `from` or `to` starts with `“-”`
@raise [Git::FailedError] if git exits outside the allowed range (exit code > 2)
@see git-scm.com/docs/git-diff git-diff documentation
Source
# File lib/git/repository/diffing.rb, line 254 def diff_stats(obj1 = 'HEAD', obj2 = nil, opts = {}) SharedPrivate.assert_valid_opts!(DIFF_STATS_ALLOWED_OPTS, **opts) raise ArgumentError, 'Invalid arguments: obj1 is nil but obj2 is not' if obj1.nil? && !obj2.nil? Git::DiffStats.new(self, obj1, obj2, opts[:path_limiter]) end
Returns the stats between two trees as a {Git::DiffStats} object
Compares (1) two commits, (2) a commit against the working tree, or (3) the index against the working tree and constructs a lazy {Git::DiffStats} that computes per-file insertion and deletion counts on demand when its accessor methods are called.
**Comparing two commits**
When both obj1 and obj2 are provided, the comparison is between those two refs (commits, tags, branches, etc.).
**Comparing a commit against the working tree**
When only obj1 is provided (and isn’t nil), the comparison is between obj1 and the working tree; the stats reflect all changes since obj1.
**Comparing the index against the working tree**
When obj1 is explicitly ‘nil` then obj2 must be omitted or `nil`. In this case, the comparison is between the index and the working tree; the stats reflect unstaged changes.
@example Get working tree stats since HEAD
stats = repo.diff_stats stats.insertions #=> 3 stats.deletions #=> 1
@example Compare two specific commits
repo.diff_stats('abc1234', 'def5678')
@example Get unstaged stats (index vs. working tree)
repo.diff_stats(nil).insertions
@example Limit stats to a sub-path
repo.diff_stats('HEAD~1', 'HEAD', path_limiter: 'lib/')
@param obj1 [String, nil] the first commit or object to compare; defaults to
`'HEAD'`
@param obj2 [String, nil] the second commit or object to compare
@param opts [Hash] options to filter the diff
@option opts [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
limit the stats to the given path(s)
@return [Git::DiffStats] a lazy stats object for the comparison
@raise [ArgumentError] if unsupported options are provided
@raise [ArgumentError] if ‘obj1` is `nil` but `obj2` is not OR if `obj1` or `obj2` starts with `“-”`
@see diff_numstat
@see git-scm.com/docs/git-diff git-diff documentation