Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions git/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# This module is part of GitPython and is released under the
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/

__all__ = ["DiffConstants", "NULL_TREE", "INDEX", "Diffable", "DiffIndex", "Diff"]
__all__ = ["DiffConstants", "NULL_TREE", "NULL_TREE_SHA", "INDEX", "Diffable", "DiffIndex", "Diff"]

import enum
import re
Expand Down Expand Up @@ -84,6 +84,9 @@ class DiffConstants(enum.Enum):
:const:`git.NULL_TREE` and :const:`Diffable.NULL_TREE`.
"""

NULL_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
"""SHA of Git's canonical empty tree object."""

INDEX: Literal[DiffConstants.INDEX] = DiffConstants.INDEX
"""Stand-in indicating you want to diff against the index.

Expand Down Expand Up @@ -599,7 +602,14 @@ def _index_from_patch_format(cls, repo: "Repo", proc: Union["Popen", "Git.AutoIn

# FIXME: Here SLURPING raw, need to re-phrase header-regexes linewise.
text_list: List[bytes] = []
handle_process_output(proc, text_list.append, None, finalize_process, decode_streams=False)
stderr_list: List[bytes] = []

def finalize_process_with_stderr(proc: Union["Popen", "Git.AutoInterrupt"]) -> None:
finalize_process(proc, stderr=b"".join(stderr_list))

handle_process_output(
proc, text_list.append, stderr_list.append, finalize_process_with_stderr, decode_streams=False
)

# For now, we have to bake the stream.
text = b"".join(text_list)
Expand Down Expand Up @@ -765,11 +775,16 @@ def _index_from_raw_format(cls, repo: "Repo", proc: "Popen") -> "DiffIndex[Diff]
# :100644 100644 687099101... 37c5e30c8... M .gitignore

index: "DiffIndex" = DiffIndex()
stderr_list: List[bytes] = []

def finalize_process_with_stderr(proc: Union["Popen", "Git.AutoInterrupt"]) -> None:
finalize_process(proc, stderr=b"".join(stderr_list))

handle_process_output(
proc,
lambda byt: cls._handle_diff_line(byt, repo, index),
None,
finalize_process,
stderr_list.append,
finalize_process_with_stderr,
decode_streams=False,
)

Expand Down
43 changes: 40 additions & 3 deletions git/index/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1480,12 +1480,11 @@ def reset(

return self

# FIXME: This is documented to accept the same parameters as Diffable.diff, but this
# does not handle NULL_TREE for `other`. (The suppressed mypy error is about this.)
def diff(
self,
other: Union[ # type: ignore[override]
other: Union[
Literal[git_diff.DiffConstants.INDEX],
Literal[git_diff.DiffConstants.NULL_TREE],
"Tree",
"Commit",
str,
Expand All @@ -1512,6 +1511,44 @@ def diff(
if other is self.INDEX:
return git_diff.DiffIndex()

if other == git_diff.NULL_TREE or other == git_diff.NULL_TREE_SHA:
args: List[Union[PathLike, str]] = [
"--cached",
git_diff.NULL_TREE_SHA,
"--abbrev=40",
"--full-index",
]
Comment thread
puneetdixit200 marked this conversation as resolved.

if not any(x in kwargs for x in ("find_renames", "no_renames", "M")):
args.append("-M")

if create_patch:
args.append("-p")
args.append("--no-ext-diff")
else:
args.append("--raw")
args.append("-z")

args.append("--no-color")

if paths is not None and not isinstance(paths, (tuple, list)):
paths = [paths]

if paths:
args.append("--")
args.extend(paths)

kwargs["as_process"] = True
proc = self.repo.git.diff(*args, **kwargs)

diff_method = (
git_diff.Diff._index_from_patch_format if create_patch else git_diff.Diff._index_from_raw_format
)
index = diff_method(self.repo, proc)

proc.wait()
Comment thread
puneetdixit200 marked this conversation as resolved.
return index

# Index against anything but None is a reverse diff with the respective item.
# Handle existing -R flags properly.
# Transform strings to the object so that we can call diff on it.
Expand Down
2 changes: 1 addition & 1 deletion test/lib/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def __init__(self, input_string):
self.stdout = io.BytesIO(input_string)
self.stderr = io.BytesIO()

def wait(self):
def wait(self, stderr=None):
return 0

poll = wait
Expand Down
36 changes: 35 additions & 1 deletion test/test_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
import ddt
import pytest

from git import BlobFilter, Diff, Git, IndexFile, Object, Repo, Tree
from git import BlobFilter, Diff, Git, IndexFile, NULL_TREE, Object, Repo, Tree
from git.diff import NULL_TREE_SHA
from git.exc import (
CheckoutError,
GitCommandError,
Expand Down Expand Up @@ -555,6 +556,39 @@ def test_index_file_diffing(self, rw_repo):
rval = index.checkout("lib")
assert len(list(rval)) > 1

@with_rw_directory
def test_index_file_diff_null_tree_with_initial_index(self, rw_dir):
repo = Repo.init(rw_dir)
filename = ".gitkeep"
file_path = osp.join(repo.working_tree_dir, filename)
with open(file_path, "w") as fp:
fp.write("# Initial file\n")

index = repo.index
index.add([filename])
index.write()

index = IndexFile(repo)
self.assertEqual(len(index.diff(None)), 0)

diff = index.diff(NULL_TREE)
self.assertEqual(len(diff), 1)
self.assertEqual(diff[0].change_type, "A")
assert diff[0].new_file
self.assertEqual(diff[0].b_path, filename)

self.assertEqual(len(index.diff(NULL_TREE, paths=filename)), 1)
self.assertEqual(len(index.diff(NULL_TREE_SHA, paths=filename)), 1)
self.assertEqual(len(index.diff(NULL_TREE, paths="missing")), 0)

patch = index.diff(NULL_TREE, create_patch=True)
self.assertEqual(len(patch), 1)
self.assertIn(b"+# Initial file", patch[0].diff)

with self.assertRaises(GitCommandError) as exc_info:
index.diff(NULL_TREE, bogus_option=True)
self.assertIn("usage: git diff", exc_info.exception.stderr)

def _count_existing(self, repo, files):
"""Return count of files that actually exist in the repository directory."""
existing = 0
Expand Down
7 changes: 6 additions & 1 deletion test/test_remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,12 @@ def test_multiple_urls(self, rw_repo):

def test_fetch_error(self):
rem = self.rorepo.remote("origin")
with self.assertRaisesRegex(GitCommandError, "[Cc]ouldn't find remote ref __BAD_REF__"):
msg = (
r"[Cc]ouldn't find remote ref __BAD_REF__|"
r"could not read Username|"
r"expected flush after ref listing"
)
with self.assertRaisesRegex(GitCommandError, msg):
rem.fetch("__BAD_REF__")

@with_rw_repo("0.1.6", bare=False)
Expand Down
Loading