Skip to content

Commit 96b1646

Browse files
jgelensxvello
andcommitted
Show a diff when a file will be or gets modified
Co-authored-by: Xavier Vello <[email protected]>
1 parent 28c7fa7 commit 96b1646

File tree

3 files changed

+62
-2
lines changed

3 files changed

+62
-2
lines changed

pyinfra/operations/files.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
import traceback
1111
from datetime import timedelta
1212
from fnmatch import fnmatch
13-
from io import StringIO
13+
from io import BytesIO, StringIO
1414
from pathlib import Path
1515
from typing import IO, Any, Union
1616

17+
import click
1718
from jinja2 import TemplateRuntimeError, TemplateSyntaxError, UndefinedError
1819

1920
from pyinfra import host, logger, state
@@ -61,6 +62,7 @@
6162
from .util.files import (
6263
adjust_regex,
6364
ensure_mode_int,
65+
generate_color_diff,
6466
get_timestamp,
6567
sed_delete,
6668
sed_replace,
@@ -940,6 +942,20 @@ def put(
940942
# File exists, check sum and check user/group/mode if supplied
941943
else:
942944
if not _file_equal(local_sum_path, dest):
945+
current_contents = BytesIO()
946+
947+
# Generate diff when contents change
948+
host.get_file(dest, current_contents)
949+
current_lines = current_contents.getvalue().decode("utf-8").splitlines(keepends=True)
950+
logger.info(f"\n Will modify {click.style(dest, bold=True)}")
951+
952+
with get_file_io(src, "r") as f:
953+
desired_lines = f.readlines()
954+
955+
for line in generate_color_diff(current_lines, desired_lines):
956+
logger.info(f" {line}")
957+
logger.info("")
958+
943959
yield FileUploadCommand(
944960
local_file,
945961
dest,

pyinfra/operations/util/files.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from __future__ import annotations
22

3+
import difflib
34
import re
45
from datetime import datetime
5-
from typing import Callable
6+
from typing import Callable, Generator
7+
8+
import click
69

710
from pyinfra.api import QuoteString, StringCommand
811

@@ -173,3 +176,34 @@ def adjust_regex(line: str, escape_regex_characters: bool) -> str:
173176
match_line = "{0}.*$".format(match_line)
174177

175178
return match_line
179+
180+
181+
def generate_color_diff(
182+
current_lines: list[str], desired_lines: list[str]
183+
) -> Generator[str, None, None]:
184+
def _format_range_unified(start: int, stop: int) -> str:
185+
beginning = start + 1 # lines start numbering with one
186+
length = stop - start
187+
if length == 1:
188+
return "{}".format(beginning)
189+
if not length:
190+
beginning -= 1 # empty ranges begin at line just before the range
191+
return "{},{}".format(beginning, length)
192+
193+
for group in difflib.SequenceMatcher(None, current_lines, desired_lines).get_grouped_opcodes(2):
194+
first, last = group[0], group[-1]
195+
file1_range = _format_range_unified(first[1], last[2])
196+
file2_range = _format_range_unified(first[3], last[4])
197+
yield "@@ -{} +{} @@".format(file1_range, file2_range)
198+
199+
for tag, i1, i2, j1, j2 in group:
200+
if tag == "equal":
201+
for line in current_lines[i1:i2]:
202+
yield " " + line.rstrip()
203+
continue
204+
if tag in {"replace", "delete"}:
205+
for line in current_lines[i1:i2]:
206+
yield click.style("- " + line.rstrip(), "red")
207+
if tag in {"replace", "insert"}:
208+
for line in desired_lines[j1:j2]:
209+
yield click.style("+ " + line.rstrip(), "green")

tests/util.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,16 @@ def noop(self, description):
188188
def get_temp_filename(*args, **kwargs):
189189
return "_tempfile_"
190190

191+
def get_file(
192+
self,
193+
remote_filename,
194+
filename_or_io,
195+
remote_temp_filename=None,
196+
print_output=False,
197+
*arguments,
198+
):
199+
return True
200+
191201
@staticmethod
192202
def _get_fact_key(fact_cls):
193203
return "{0}.{1}".format(fact_cls.__module__.split(".")[-1], fact_cls.__name__)

0 commit comments

Comments
 (0)