def __init__(self, cwd, quilt_pc, quilt_patches): super(Delete, self).__init__(cwd) self.quilt_pc = Directory(quilt_pc) self.quilt_patches = Directory(quilt_patches) self.db = Db(quilt_pc) self.series = Series(quilt_patches) self.pop = Pop(cwd, quilt_pc)
def revert_file(self, filename, patch_name=None): """ Revert not added changes of filename. If patch_name is None or empty the topmost patch will be used. """ file = File(filename) if patch_name: patch = Patch(patch_name) else: patch = self.db.top_patch() if not patch: raise QuiltError("No patch available. Nothing to revert.") self._file_in_patch(filename, patch) self._file_in_next_patches(filename, patch) pc_dir = self.quilt_pc + patch.get_name() pc_file = pc_dir + file if not file.exists() and pc_file.is_empty(): # new and empty file will be reverted pc_file.delete() self.file_reverted(file, patch) return with TmpDirectory(prefix="pquilt-") as tmpdir: # apply current patch in temporary directory to revert changes of # file that aren't committed in the patch tmp_file = self._apply_patch_temporary(tmpdir, pc_file, patch) if tmp_file and tmp_file.exists() and not tmp_file.is_empty(): diff = Diff(file, tmp_file) if diff.equal(self.cwd): self.file_unchanged(file, patch) return dir = file.get_directory() if not dir: dir = Directory(os.getcwd()) else: dir.create() tmp_file.copy(dir) self.file_reverted(file, patch) else: self.file_unchanged(file, patch)
class PushTest(QuiltTest): data_dir = Directory(os.path.join(test_dir, "data", "push")) def test_apply_all(self): patch2 = Patch("p2.patch") test_dir = self.data_dir + "test1" with TmpDirectory(dir=self.data_dir.get_name()) as tmp_dir: tmp_test_dir = tmp_dir + "test1" test_dir.copy(tmp_test_dir) pc_dir = tmp_test_dir + "pc" patches_dir = tmp_test_dir + "patches" f1 = tmp_test_dir + File("f1") self.assertFalse(f1.exists()) f2 = tmp_test_dir + File("f2") self.assertFalse(f2.exists()) push = Push(tmp_test_dir.get_name(), pc_dir.get_name(), patches_dir.get_name()) self.assertEqual(None, push.db.top_patch()) push.apply_all(quiet=True) self.assertEqual(patch2, push.db.top_patch()) self.assertTrue(f1.exists()) self.assertTrue(f2.exists()) def test_apply_next(self): patch1 = Patch("p1.patch") patch2 = Patch("p2.patch") test_dir = self.data_dir + "test2" with TmpDirectory(dir=self.data_dir.get_name()) as tmp_dir: tmp_test_dir = tmp_dir + "test2" test_dir.copy(tmp_test_dir) pc_dir = tmp_test_dir + "pc" patches_dir = tmp_test_dir + "patches" f1 = tmp_test_dir + File("f1") self.assertFalse(f1.exists()) f2 = tmp_test_dir + File("f2") self.assertFalse(f2.exists()) push = Push(tmp_test_dir.get_name(), pc_dir.get_name(), patches_dir.get_name()) self.assertEqual(None, push.db.top_patch()) push.apply_next_patch(quiet=True) self.assertEqual(patch1, push.db.top_patch()) self.assertTrue(f1.exists()) self.assertFalse(f2.exists()) push.apply_next_patch(quiet=True) self.assertEqual(patch2, push.db.top_patch()) self.assertTrue(f1.exists()) self.assertTrue(f2.exists()) def test_upto_applied(self): """ Push up to a specified patch when a patch is already applied """ top = os.path.join(test_dir, "data", "pop", "test1") pc = os.path.join(top, "pc") patches = os.path.join(top, "patches") cmd = Push(top, pc, patches) self.assertRaises(AllPatchesApplied, cmd.apply_patch, "p1.patch") def test_force(self): with tmp_series() as [dir, series]: self._make_conflict(dir, series) series.save() cmd = Push(dir, quilt_pc=dir, quilt_patches=series.dirname) with six.assertRaisesRegex( self, QuiltError, r"does not apply"), \ self._suppress_output(): cmd.apply_next_patch(quiet=True) with six.assertRaisesRegex(self, QuiltError, r"Applied patch.*needs refresh"), \ self._suppress_output(): cmd.apply_next_patch(quiet=True, force=True) def test_without_refresh(self): with tmp_series() as [dir, series]: self._make_conflict(dir, series) series.add_patch("p2") series.save() cmd = Push(dir, quilt_pc=dir, quilt_patches=series.dirname) with six.assertRaisesRegex(self, QuiltError, r"Applied patch.*needs refresh"), \ self._suppress_output(): cmd.apply_next_patch(quiet=True, force=True) with six.assertRaisesRegex(self, QuiltError, r"needs to be refreshed"): cmd.apply_next_patch() def test_fail_after_success(self): """ Test where the first patch applies but a later patch fails """ with tmp_series() as [dir, series]: make_file( b"--- /dev/null\n" b"+++ dir/new-file\n" b"@@ -0,0 +1,1 @@\n" b"+new file\n", series.dirname, "good.patch") series.add_patch(Patch("good.patch")) self._make_conflict(dir, series) series.save() cmd = Push(dir, quilt_pc=dir, quilt_patches=series.dirname) with six.assertRaisesRegex(self, QuiltError, r"conflict\.patch does not apply"), \ self._suppress_output(): cmd.apply_all() [applied] = Db(dir).patches() self.assertEqual(applied.get_name(), "good.patch") with open(os.path.join(dir, "new-file"), "rb") as file: self.assertEqual(file.read(), b"new file\n") with open(os.path.join(dir, "file"), "rb") as file: self.assertEqual(file.read(), b"conflict\n") def _make_conflict(self, dir, series): series.add_patch(Patch("conflict.patch")) make_file( b"--- orig/file\n" b"+++ new/file\n" b"@@ -1 +1 @@\n" b"-old\n" b"+new\n", series.dirname, "conflict.patch") make_file(b"conflict\n", dir, "file") @contextmanager def _suppress_output(self): """ Silence error messages from the "patch" command """ STDOUT_FILENO = 1 STDERR_FILENO = 2 with open(os.devnull, "w") as null: stdout = os.dup(STDOUT_FILENO) stderr = os.dup(STDERR_FILENO) os.dup2(null.fileno(), STDOUT_FILENO) os.dup2(null.fileno(), STDERR_FILENO) try: yield finally: os.dup2(stdout, STDOUT_FILENO) os.dup2(stderr, STDERR_FILENO) os.close(stdout) os.close(stderr)
def __init__(self, cwd, quilt_pc): super(Pop, self).__init__(cwd) self.quilt_pc = Directory(quilt_pc) self.db = Db(quilt_pc)
def __init__(self, cwd, quilt_pc, quilt_patches): super(Revert, self).__init__(cwd) self.quilt_pc = Directory(quilt_pc) self.quilt_patches = Directory(quilt_patches) self.db = Db(quilt_pc) self.series = Series(quilt_patches)
class Revert(Command): """Command class to remove files from the current patch """ file_reverted = Signal() file_unchanged = Signal() def __init__(self, cwd, quilt_pc, quilt_patches): super(Revert, self).__init__(cwd) self.quilt_pc = Directory(quilt_pc) self.quilt_patches = Directory(quilt_patches) self.db = Db(quilt_pc) self.series = Series(quilt_patches) def _file_in_patch(self, filename, patch): """ Checks if a backup file of the filename in the current patch exists and raises a QuiltError if not. """ pc_dir = self.quilt_pc + patch.get_name() file = pc_dir + File(filename) if not file.exists(): raise QuiltError("File %s is not in patch %s" % (filename, patch.get_name())) def _file_in_next_patches(self, filename, patch): """ Checks if a backup file of the filename in the applied patches after patch exists """ if not self.db.is_patch(patch): # no patches applied return patches = self.db.patches_after(patch) for patch in patches: file = self.quilt_pc + File(os.path.join(patch.get_name(), filename)) if file.exists(): raise QuiltError("File %s is modified by patch %s" % (filename, patch.get_name())) def _apply_patch_temporary(self, tmpdir, file, patch): backup = Backup() backup_file = backup.backup_file(file, tmpdir) patch_file = self.quilt_patches + File(patch.get_name()) if patch_file.exists() and not patch_file.is_empty(): try: patch.run(self.cwd, self.quilt_patches.get_absdir(), work_dir=tmpdir, no_backup_if_mismatch=True, remove_empty_files=True, force=True, quiet=True, suppress_output=True, ) except SubprocessError: pass # Expected to fail if there are other files in patch return backup_file def revert_file(self, filename, patch_name=None): """ Revert not added changes of filename. If patch_name is None or empty the topmost patch will be used. """ file = File(filename) if patch_name: patch = Patch(patch_name) else: patch = self.db.top_patch() if not patch: raise QuiltError("No patch available. Nothing to revert.") self._file_in_patch(filename, patch) self._file_in_next_patches(filename, patch) pc_dir = self.quilt_pc + patch.get_name() pc_file = pc_dir + file if not file.exists() and pc_file.is_empty(): # new and empty file will be reverted pc_file.delete() self.file_reverted(file, patch) return with TmpDirectory(prefix="pquilt-") as tmpdir: # apply current patch in temporary directory to revert changes of # file that aren't committed in the patch tmp_file = self._apply_patch_temporary(tmpdir, pc_file, patch) if tmp_file and tmp_file.exists() and not tmp_file.is_empty(): diff = Diff(file, tmp_file) if diff.equal(self.cwd): self.file_unchanged(file, patch) return dir = file.get_directory() if not dir: dir = Directory(os.getcwd()) else: dir.create() tmp_file.copy(dir) self.file_reverted(file, patch) else: self.file_unchanged(file, patch) def revert_files(self, filenames, patch_name=None): for filename in filenames: self.revert_file(filename, patch_name)
class Revert(Command): """Command class to remove files from the current patch """ file_reverted = Signal() file_unchanged = Signal() def __init__(self, cwd, quilt_pc, quilt_patches): super(Revert, self).__init__(cwd) self.quilt_pc = Directory(quilt_pc) self.quilt_patches = Directory(quilt_patches) self.db = Db(quilt_pc) self.series = Series(quilt_patches) def _file_in_patch(self, filename, patch): """ Checks if a backup file of the filename in the current patch exists and raises a QuiltError if not. """ pc_dir = self.quilt_pc + patch.get_name() file = pc_dir + File(filename) if not file.exists(): raise QuiltError("File %s is not in patch %s" % (filename, patch.get_name())) def _file_in_next_patches(self, filename, patch): """ Checks if a backup file of the filename in the applied patches after patch exists """ if not self.db.is_patch(patch): # no patches applied return patches = self.db.patches_after(patch) for patch in patches: file = self.quilt_pc + File( os.path.join(patch.get_name(), filename)) if file.exists(): raise QuiltError("File %s is modified by patch %s" % (filename, patch.get_name())) def _apply_patch_temporary(self, tmpdir, file, patch): backup = Backup() backup_file = backup.backup_file(file, tmpdir) patch_file = self.quilt_patches + File(patch.get_name()) if patch_file.exists() and not patch_file.is_empty(): try: patch.run( self.cwd, self.quilt_patches.get_absdir(), work_dir=tmpdir, no_backup_if_mismatch=True, remove_empty_files=True, force=True, quiet=True, suppress_output=True, ) except SubprocessError: pass # Expected to fail if there are other files in patch return backup_file def revert_file(self, filename, patch_name=None): """ Revert not added changes of filename. If patch_name is None or empty the topmost patch will be used. """ file = File(filename) if patch_name: patch = Patch(patch_name) else: patch = self.db.top_patch() if not patch: raise QuiltError("No patch available. Nothing to revert.") self._file_in_patch(filename, patch) self._file_in_next_patches(filename, patch) pc_dir = self.quilt_pc + patch.get_name() pc_file = pc_dir + file if not file.exists() and pc_file.is_empty(): # new and empty file will be reverted pc_file.delete() self.file_reverted(file, patch) return with TmpDirectory(prefix="pquilt-") as tmpdir: # apply current patch in temporary directory to revert changes of # file that aren't committed in the patch tmp_file = self._apply_patch_temporary(tmpdir, pc_file, patch) if tmp_file and tmp_file.exists() and not tmp_file.is_empty(): diff = Diff(file, tmp_file) if diff.equal(self.cwd): self.file_unchanged(file, patch) return dir = file.get_directory() if not dir: dir = Directory(os.getcwd()) else: dir.create() tmp_file.copy(dir) self.file_reverted(file, patch) else: self.file_unchanged(file, patch) def revert_files(self, filenames, patch_name=None): for filename in filenames: self.revert_file(filename, patch_name)
class Push(Command): applying = Signal() applying_patch = Signal() applied = Signal() applied_patch = Signal() applied_empty_patch = Signal() def __init__(self, cwd, quilt_pc, quilt_patches): super(Push, self).__init__(cwd) self.quilt_pc = Directory(quilt_pc) self.quilt_patches = Directory(quilt_patches) self.db = Db(quilt_pc) self.series = Series(quilt_patches) def _apply_patch(self, patch, force=False, quiet=False): patch_name = patch.get_name() pc_dir = self.quilt_pc + patch_name patch_file = self.quilt_patches + File(patch_name) refresh = File(pc_dir.get_name() + "~refresh") forced = False self.applying_patch(patch) if patch_file.exists(): try: patch.run(self.cwd, patch_dir=self.quilt_patches, backup=True, prefix=pc_dir.get_name(), quiet=quiet) except SubprocessError as e: if not force: patch = RollbackPatch(self.cwd, pc_dir) patch.rollback() patch.delete_backup() raise QuiltError("Patch %s does not apply" % patch_name) else: refresh.touch() forced = True self.db.add_patch(patch) if pc_dir.exists(): timestamp = pc_dir + File(".timestamp") timestamp.touch() else: pc_dir.create() if not patch_file.exists(): self.applied_empty_patch(patch, False) elif pc_dir.is_empty(): self.applied_empty_patch(patch, True) elif forced: raise QuiltError("Applied patch %s (forced; needs refresh)" % patch.get_name()) else: self.applied_patch(patch) def _check(self): if not self.series.exists() or not self.series.patches(): raise NoPatchesInSeries(self.series) top = self.db.top_patch() if top is not None: refresh = top.get_name() + "~refresh" refresh = os.path.join(self.quilt_pc.get_name(), refresh) if os.path.exists(refresh): raise QuiltError("Patch %s needs to be refreshed" % \ top.get_name()) def apply_patch(self, patch_name, force=False, quiet=False): """ Apply all patches up to patch_name """ self._check() patch = Patch(patch_name) patches = self.series.patches_until(patch)[:] applied = self.db.applied_patches() for patch in applied: if patch in patches: patches.remove(patch) if not patches: raise AllPatchesApplied(self.series, self.db.top_patch()) self.applying(patch) try: for cur_patch in patches: self._apply_patch(cur_patch, force, quiet) finally: self.db.save() self.applied(self.db.top_patch()) def apply_next_patch(self, force=False, quiet=False): """ Apply next patch in series file """ self._check() top = self.db.top_patch() if not top: patch = self.series.first_patch() else: patch = self.series.patch_after(top) if not patch: raise AllPatchesApplied(self.series, top) self.applying(patch) self._apply_patch(patch, force, quiet) self.db.save() self.applied(self.db.top_patch()) def apply_all(self, force=False, quiet=False): """ Apply all patches in series file """ self._check() top = self.db.top_patch() if top: patches = self.series.patches_after(top) else: patches = self.series.patches() if not patches: raise AllPatchesApplied(self.series, top) try: for patch in patches: self.applying(patch) self._apply_patch(patch, force, quiet) finally: self.db.save() self.applied(self.db.top_patch())
class PopTest(QuiltTest): data_dir = Directory(os.path.join(test_dir, "data", "pop")) def test_unapply_all(self): patch2 = Patch("p2.patch") test_dir = self.data_dir + "test1" with TmpDirectory(dir=self.data_dir.get_name()) as tmp_dir: tmp_test_dir = tmp_dir + "test1" test_dir.copy(tmp_test_dir) pc_dir = tmp_test_dir + "pc" f1 = tmp_test_dir + File("f1") self.assertTrue(f1.exists()) f2 = tmp_test_dir + File("f2") self.assertTrue(f2.exists()) pop = Pop(tmp_test_dir.get_name(), pc_dir.get_name()) self.assertEqual(patch2, pop.db.top_patch()) pop.unapply_all() self.assertEqual(None, pop.db.top_patch()) self.assertFalse(f1.exists()) self.assertFalse(f2.exists()) def test_apply_next(self): patch1 = Patch("p1.patch") patch2 = Patch("p2.patch") test_dir = self.data_dir + "test1" with TmpDirectory(dir=self.data_dir.get_name()) as tmp_dir: tmp_test_dir = tmp_dir + "test2" test_dir.copy(tmp_test_dir) pc_dir = tmp_test_dir + "pc" f1 = tmp_test_dir + File("f1") self.assertTrue(f1.exists()) f2 = tmp_test_dir + File("f2") self.assertTrue(f2.exists()) pop = Pop(tmp_test_dir.get_name(), pc_dir.get_name()) self.assertEqual(patch2, pop.db.top_patch()) pop.unapply_top_patch() self.assertEqual(patch1, pop.db.top_patch()) self.assertTrue(f1.exists()) self.assertFalse(f2.exists()) pop.unapply_top_patch() self.assertEqual(None, pop.db.top_patch()) self.assertFalse(f1.exists()) self.assertFalse(f2.exists()) def test_unrefreshed(self): with TmpDirectory() as dir: db = Db(dir.get_name()) db.add_patch(Patch("unrefreshed.patch")) db.save() make_file(b"", db.dirname, "unrefreshed.patch~refresh") cmd = Pop(dir.get_name(), db.dirname) with six.assertRaisesRegex(self, QuiltError, r"needs to be refreshed"): cmd.unapply_top_patch()