aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLoek Le Blansch <loek@pipeframe.xyz>2025-10-25 09:13:56 +0200
committerLoek Le Blansch <loek@pipeframe.xyz>2025-10-25 09:13:56 +0200
commit5aac30996d6f4c99a752e6d1e83573e8962d4eea (patch)
treec49acc0355b908a396f1c86cbcf795bd07295d35
parent7b703051076bdec99592a31cd78a83ac79e34fdb (diff)
add extended git dif header lines
-rw-r--r--patchtree/context.py10
-rw-r--r--patchtree/diff.py52
-rw-r--r--patchtree/patch.py19
-rw-r--r--patchtree/process.py23
4 files changed, 75 insertions, 29 deletions
diff --git a/patchtree/context.py b/patchtree/context.py
index a91fb36..57200a5 100644
--- a/patchtree/context.py
+++ b/patchtree/context.py
@@ -33,6 +33,8 @@ class Context:
)
def __del__(self):
+ # patch must have a trailing newline
+ self.output.write("\n")
self.output.flush()
if self.options.in_place:
@@ -58,6 +60,14 @@ class Context:
return None
return here.read_bytes().decode()
+ def get_mode(self, file: str) -> int:
+ # TODO
+ return 0
+ here = self.fs.joinpath(file)
+ if not here.exists():
+ return 0
+ return here.stat().st_mode
+
def get_fs(self) -> Path:
target: str = self.options.target
diff --git a/patchtree/diff.py b/patchtree/diff.py
index 9058b02..a3442ae 100644
--- a/patchtree/diff.py
+++ b/patchtree/diff.py
@@ -1,6 +1,16 @@
+from dataclasses import dataclass
from difflib import unified_diff
+@dataclass
+class DiffFile:
+ content: str | None
+ mode: int
+
+ def lines(self) -> list[str]:
+ return (self.content or "").splitlines()
+
+
class Diff:
"""
The base Diff class just produces a regular diff from the (possibly absent)
@@ -10,24 +20,44 @@ class Diff:
file: str
- content_a: str | None
- content_b: str = ""
+ a: DiffFile
+ b: DiffFile
def __init__(self, file: str):
self.file = file
def compare(self) -> str:
+ """
+ Generate patch text in "git-diff-files -p" format (see https:
+ //git-scm.com/docs/diff-format#generate_patch_text_with_p)
+ """
+ a = self.a
+ b = self.b
fromfile = f"a/{self.file}"
tofile = f"b/{self.file}"
- if self.content_a is None:
+ assert (a.content or b.content) is not None
+
+ delta = f"diff --git {fromfile} {tofile}\n"
+
+ if a.content is None:
fromfile = "/dev/null"
- self.content_a = ""
+ delta += f"new file mode {b.mode:06o}\n"
+
+ if b.content is None:
+ tofile = "/dev/null"
+ delta += f"deleted file mode {a.mode:06o}\n"
+
+ if a.content is not None and b.content is not None and a.mode != b.mode:
+ delta += f"old mode {a.mode:06o}\n"
+ delta += f"new mode {b.mode:06o}\n"
+
+ lines_a = a.lines()
+ lines_b = b.lines()
+ diff = unified_diff(lines_a, lines_b, fromfile, tofile, lineterm="")
+ delta += "".join(f"{line}\n" for line in diff)
- a = self.content_a.splitlines()
- b = self.content_b.splitlines()
- diff = unified_diff(a, b, fromfile, tofile, lineterm="")
- return "\n".join(diff) + "\n"
+ return delta
def diff(self) -> str:
return self.compare()
@@ -41,12 +71,12 @@ class IgnoreDiff(Diff):
"""
def diff(self):
- lines_a = (self.content_a or "").splitlines()
- lines_b = self.content_b.splitlines()
+ lines_a = self.a.lines()
+ lines_b = self.b.lines()
add_lines = set(lines_b) - set(lines_a)
- self.content_b = "\n".join(
+ self.b.content = "\n".join(
(
*lines_a,
*add_lines,
diff --git a/patchtree/patch.py b/patchtree/patch.py
index 415cf26..0b1f69c 100644
--- a/patchtree/patch.py
+++ b/patchtree/patch.py
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
from pathlib import Path
-from .diff import Diff
+from .diff import Diff, DiffFile
if TYPE_CHECKING:
from .process import Process
@@ -63,17 +63,18 @@ class Patch:
diff = diff_class(self.file)
- # read file A contents
- content_a = context.get_content(self.file)
+ diff.a = DiffFile(
+ content=context.get_content(self.file),
+ mode=context.get_mode(self.file),
+ )
- # read file B contents
- content_b = self.patch.read_text()
+ diff.b = DiffFile(
+ content=self.patch.read_text(),
+ mode=self.patch.stat().st_mode,
+ )
for processor_class in processor_classes:
processor = processor_class(context)
- content_b = processor.transform(content_a, content_b)
-
- diff.content_a = content_a
- diff.content_b = content_b
+ diff.b = processor.transform(diff.a, diff.b)
delta = diff.diff()
context.output.write(delta)
diff --git a/patchtree/process.py b/patchtree/process.py
index b8b87bd..a44886d 100644
--- a/patchtree/process.py
+++ b/patchtree/process.py
@@ -6,6 +6,8 @@ from jinja2 import Environment
from subprocess import Popen
from pathlib import Path
+from .diff import DiffFile
+
if TYPE_CHECKING:
from .context import Context
@@ -16,8 +18,8 @@ class Process:
def __init__(self, context: Context):
self.context = context
- def transform(self, content_a: str | None, content_b: str) -> str:
- return content_b
+ def transform(self, a: DiffFile, b: DiffFile) -> DiffFile:
+ return b
class ProcessJinja2(Process):
@@ -26,20 +28,23 @@ class ProcessJinja2(Process):
lstrip_blocks=True,
)
- def transform(self, content_a, content_b) -> str:
+ def transform(self, a, b):
template_vars = self.get_template_vars()
- return self.environment.from_string(content_b).render(**template_vars)
+ assert b.content is not None
+ b.content = self.environment.from_string(b.content).render(**template_vars)
+ return b
def get_template_vars(self) -> dict[str, Any]:
return {}
class ProcessCoccinelle(Process):
- def transform(self, content_a, content_b) -> str:
- content_a = content_a or ""
+ def transform(self, a, b):
+ content_a = a.content or ""
+ content_b = b.content or ""
if len(content_b.strip()) == 0:
- return content_a
+ return a
temp_a = Path(mkstemp()[1])
temp_b = Path(mkstemp()[1])
@@ -60,10 +65,10 @@ class ProcessCoccinelle(Process):
coccinelle = Popen(cmd)
coccinelle.wait()
- content_b = temp_b.read_text()
+ b.content = temp_b.read_text()
temp_a.unlink()
temp_b.unlink()
temp_sp.unlink()
- return content_b
+ return b