33import fnmatch
44import os
55import pathlib
6+ import shutil
67from abc import ABC , abstractmethod
78from collections .abc import Sequence
89from typing import Optional
@@ -93,7 +94,10 @@ def update_is_required(self, force: bool = False) -> Optional[Version]:
9394 return wanted
9495
9596 def update (
96- self , force : bool = False , files_to_ignore : Optional [Sequence [str ]] = None
97+ self ,
98+ force : bool = False ,
99+ files_to_ignore : Optional [Sequence [str ]] = None ,
100+ no_patch : bool = True ,
97101 ) -> None :
98102 """Update this subproject if required.
99103
@@ -128,7 +132,7 @@ def update(
128132 actually_fetched = self ._fetch_impl (to_fetch )
129133 self ._log_project (f"Fetched { actually_fetched } " )
130134
131- applied_patches = self ._apply_patches ()
135+ applied_patches = [] if not no_patch else self ._apply_patches ()
132136
133137 self .__metadata .fetched (
134138 actually_fetched ,
@@ -139,6 +143,56 @@ def update(
139143 logger .debug (f"Writing repo metadata to: { self .__metadata .path } " )
140144 self .__metadata .dump ()
141145
146+ def update_patch (self , files_to_ignore : Optional [Sequence [str ]] = None ) -> None :
147+ """Update the patch."""
148+
149+ # Check if the project has a patch, maybe suggest creating one?
150+ if not self .__project .patch :
151+ self ._log_project (
152+ f'skipped - there is no patch file, use "dfetch diff { self .__project .name } " instead'
153+ )
154+ return
155+
156+ # Check if the project was ever fetched
157+ on_disk_version = self .on_disk_version ()
158+ if not on_disk_version :
159+ self ._log_project (
160+ f'skipped - the project was never fetched before, use "dfetch update { self .__project .name } " '
161+ )
162+ return
163+
164+ # Make sure no uncommitted changes (don't care about ignored files)
165+ if self ._are_there_local_changes (files_to_ignore ):
166+ self ._log_project (
167+ "skipped - local changes after last update (use --force to overwrite)"
168+ )
169+ return
170+
171+ # Select patch to overwrite & make backup
172+ patch_to_update = self .__project .patch [- 1 ]
173+ shutil .move (patch_to_update , patch_to_update + ".backup" )
174+
175+ # force update to fetched version from metadata without applying patch
176+ self .update (force = True , files_to_ignore = files_to_ignore , no_patch = True )
177+
178+ # generate reverse patch
179+ patch_text = self ._diff_impl (
180+ old_revision = self .metadata_revision (),
181+ new_revision = None ,
182+ ignore = files_to_ignore ,
183+ reverse = True ,
184+ )
185+
186+ if patch_text :
187+ patch_path = pathlib .Path (patch_to_update )
188+ self ._log_project (f"Updating patch { patch_to_update } " )
189+ patch_path .write_text (patch_text , encoding = "UTF-8" )
190+ else :
191+ self ._log_project (f"No diffs found, kept patch { patch_to_update } unchanged" )
192+
193+ # force update again to fetched version from metadata but with applying patch
194+ self .update (force = True , files_to_ignore = files_to_ignore , no_patch = False )
195+
142196 def _apply_patches (self ) -> list [str ]:
143197 """Apply the patches."""
144198 cwd = pathlib .Path ("." ).resolve ()
@@ -386,6 +440,7 @@ def _diff_impl(
386440 old_revision : str , # noqa
387441 new_revision : Optional [str ], # noqa
388442 ignore : Sequence [str ],
443+ reverse : bool = False ,
389444 ) -> str :
390445 """Get the diff of two revisions, should be implemented by the child class."""
391446
0 commit comments