Skip to content

Commit

Permalink
patches: support git format-patch --no-prefix
Browse files Browse the repository at this point in the history
some patches in dist-git are applied with -p0 which implies --no-prefix
option for format-patch

this commit enables that: packit is able generate a patch file with
format-patch without leading a/ and b/ in the patch diff

by default format-patch generates patches for -p1 application

🤦‍♂️

packit/dist-git-to-source-git#85 (comment)

Signed-off-by: Tomas Tomecek <[email protected]>
  • Loading branch information
TomasTomecek committed Sep 25, 2020
1 parent e7c970d commit 777f971
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 19 deletions.
100 changes: 81 additions & 19 deletions packit/patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def __init__(
present_in_specfile: bool = False,
ignore: bool = False,
squash_commits: bool = False,
no_prefix: bool = False,
) -> None:
"""
Metadata about patch files and relation to the respective commit.
Expand All @@ -70,6 +71,7 @@ def __init__(
:param squash_commits: squash commits into a single patch file
until next commits with squash_commits=True
(git-am patches do this)
:param no_prefix: do not prepend a/ and b/ when generating the patch file
"""
self.name = name
self.path = path
Expand All @@ -79,6 +81,7 @@ def __init__(
self.present_in_specfile = present_in_specfile
self.ignore = ignore
self.squash_commits = squash_commits
self.no_prefix = no_prefix

@property
def specfile_comment(self) -> str:
Expand Down Expand Up @@ -112,6 +115,9 @@ def commit_message(self) -> str:
if self.squash_commits:
msg += "\nsquash_commits: true"

if self.no_prefix:
msg += "\nno_prefix: true"

return msg

@staticmethod
Expand Down Expand Up @@ -153,6 +159,7 @@ def from_commit(
ignore=metadata.get("ignore"),
commit=commit,
squash_commits=metadata.get("squash_commits"),
no_prefix=metadata.get("no_prefix"),
)

def __repr__(self):
Expand Down Expand Up @@ -254,15 +261,62 @@ def linearize_history(git_ref: str):
env={"FILTER_BRANCH_SQUELCH_WARNING": "1"},
)

@staticmethod
def process_patches(patches: Dict[str, bytes], commits: List[git.Commit]):
def run_git_format_patch(
self,
destination: str,
files_to_ignore: List[str],
ref_or_range: str,
no_prefix: bool = False,
):
"""
run `git format-patch $ref_or_range` in self.local_project.working_dir
:param destination: place the patches here
:param files_to_ignore: ignore changes in these files
:param ref_or_range: [ <since> | <revision range> ]:
1. A single commit, <since>, specifies that the commits leading to the tip of the
current branch that are not in the history that leads to the <since> to be
output.
2. Generic <revision range> expression (see "SPECIFYING REVISIONS" section in
gitrevisions(7)) means the commits in the specified range.
:param no_prefix: prefix is the leading a/ and b/ - format-patch does this by default
:return: str, git format-patch output: new-line separated list of patch names
"""
git_f_p_cmd = ["git", "format-patch", "--output-directory", f"{destination}"]
if no_prefix:
git_f_p_cmd.append("--no-prefix")
git_f_p_cmd += [
ref_or_range,
"--",
".",
] + [f":(exclude){file_to_ignore}" for file_to_ignore in files_to_ignore]
return run_command(
cmd=git_f_p_cmd,
cwd=self.lp.working_dir,
output=True,
decode=True,
).strip()

def process_patches(
self,
patches: Dict[str, bytes],
commits: List[git.Commit],
destination: str,
files_to_ignore: List[str] = None,
):
"""
Pair commits (in a source-git repo) with a list patches generated with git-format-patch.
Pairing is done using commit.hexsha (which is always present in the patch file).
patch_list (provided List) is then mutated by appending PatchMetadata using
the paired information: commit and a path to the patch file.
:param patches: Dict: commit hexsha -> patch content
:param commits: list of commits we created the patches from
:param destination: place the patch files here
:param files_to_ignore: list of files to ignore when creating patches
"""
patch_list: List[PatchMetadata] = []
for commit in commits:
Expand All @@ -281,7 +335,24 @@ def process_patches(patches: Dict[str, bytes], commits: List[git.Commit]):
)
else:
logger.debug(f"[{patch_metadata.name}] {commit.summary}")
patch_list.append(patch_metadata)
if patch_metadata.no_prefix:
# sadly, we have work to do, the original patch is no good:
# format-patch by default generates patches with prefixes a/ and b/
# no-prefix means we don't want those: we need create the patch, again
# https://github.com/packit/dist-git-to-source-git/issues/85#issuecomment-698827925
git_f_p_out = self.run_git_format_patch(
destination,
files_to_ignore,
f"{commit}^..{commit}",
no_prefix=True,
)
patch_list.append(
PatchMetadata.from_commit(
commit=commit, patch_path=Path(git_f_p_out)
)
)
else:
patch_list.append(patch_metadata)
break
return patch_list

Expand Down Expand Up @@ -338,6 +409,7 @@ def create_patches(
:param files_to_ignore: list of files to ignore when creating patches
:return: [PatchMetadata, ...] list of patches
"""
files_to_ignore = files_to_ignore or []
contained = self.are_child_commits_contained(git_ref)
if not contained:
self.linearize_history(git_ref)
Expand All @@ -348,29 +420,19 @@ def create_patches(
commits = self.get_commits_since_ref(
git_ref, add_upstream_head_commit=False
)
git_f_p_cmd = [
"git",
"format-patch",
"--output-directory",
f"{destination}",
git_ref,
"--",
".",
] + [f":(exclude){file_to_ignore}" for file_to_ignore in files_to_ignore]
git_format_patch_out = run_command(
cmd=git_f_p_cmd,
cwd=self.lp.working_dir,
output=True,
decode=True,
).strip()
git_format_patch_out = self.run_git_format_patch(
destination, files_to_ignore, git_ref
)

if git_format_patch_out:
patches: Dict[str, bytes] = {
# we need to read bytes since we cannot decode whatever is inside patches
patch_name: Path(patch_name).read_bytes()
for patch_name in git_format_patch_out.split("\n")
}
patch_list = self.process_patches(patches, commits)
patch_list = self.process_patches(
patches, commits, destination, files_to_ignore
)
patch_list = self.process_git_am_style_patches(patch_list)
else:
logger.warning(f"No patches between {git_ref!r} and {self.lp.ref!r}")
Expand Down
37 changes: 37 additions & 0 deletions tests/integration/test_source_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
build_srpm,
create_merge_commit_in_source_git,
create_git_am_style_history,
create_patch_mixed_history,
)


Expand Down Expand Up @@ -471,3 +472,39 @@ def test_srpm_git_am(mock_remote_functionality_sourcegit, api_instance_source_gi
"0001-m04r-malt.patch",
"malt.patch",
}


@pytest.mark.parametrize("ref", ["0.1.0", "0.1*", "0.*"])
def test_srpm_git_no_prefix_patches(
mock_remote_functionality_sourcegit, api_instance_source_git, ref
):
sg_path = Path(api_instance_source_git.upstream_local_project.working_dir)
mock_spec_download_remote_s(sg_path, sg_path / "fedora", "0.1.0")

api_instance_source_git.up.specfile.spec_content.section("%package")[10:10] = (
"Patch1: amarillo.patch",
"Patch2: citra.patch",
"Patch8: malt.patch",
)
api_instance_source_git.up.specfile.spec_content.section("%prep")[0:2] = [
"%setup -n %{upstream_name}-%{version}",
"%patch1 -p1",
"%patch2 -p0",
"%patch8 -p1",
]
api_instance_source_git.up.specfile.save()

create_patch_mixed_history(sg_path)

with cwd(sg_path):
api_instance_source_git.create_srpm(upstream_ref=ref)

srpm_path = list(sg_path.glob("beer-0.1.0-2.*.src.rpm"))[0]
assert srpm_path.is_file()
build_srpm(srpm_path)

assert {x.name for x in sg_path.joinpath("fedora").glob("*.patch")} == {
"amarillo.patch",
"citra.patch",
"malt.patch",
}
21 changes: 21 additions & 0 deletions tests/spellbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,27 @@ def create_git_am_style_history(sg: Path):
git_add_and_commit(directory=sg, message=meta.commit_message)


def create_patch_mixed_history(sg: Path):
"""
create a git history where we mix prefix and no-prefix
:param sg: the repo
"""
hops = sg.joinpath("hops")
hops.write_text("Amarillo\n")
meta = PatchMetadata(name="amarillo.patch", present_in_specfile=True)
git_add_and_commit(directory=sg, message=meta.commit_message)

hops.write_text("Citra\n")
meta = PatchMetadata(name="citra.patch", present_in_specfile=True, no_prefix=True)
git_add_and_commit(directory=sg, message=meta.commit_message)

malt = sg.joinpath("malt")
malt.write_text("Munich\n")
meta = PatchMetadata(name="malt.patch", present_in_specfile=True)
git_add_and_commit(directory=sg, message=meta.commit_message)


def prepare_dist_git_repo(directory, push=True):
subprocess.check_call(["git", "branch", "f30"], cwd=directory)
if push:
Expand Down

0 comments on commit 777f971

Please sign in to comment.