From a887ade78fb6fcf615679555cc6196ba1ce8a6a4 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Tue, 25 Jun 2024 20:58:53 +0200 Subject: [PATCH] Fix possibility to overwrite special files in .osc (CVE-2024-22034 boo#1225911) Source files are now stored in the 'sources' subdirectory which prevents name collisons. This requires changing version of '.osc' store to 2.0. --- osc/commandline.py | 10 ++--- osc/core.py | 2 +- osc/obs_scm/package.py | 62 ++++++++++++--------------- osc/obs_scm/store.py | 90 ++++++++++++++++++++++++++++++++++++--- tests/common.py | 8 ++-- tests/test_addfiles.py | 10 ++--- tests/test_commit.py | 16 +++---- tests/test_deletefiles.py | 20 ++++----- tests/test_repairwc.py | 18 ++++---- tests/test_revertfiles.py | 2 +- tests/test_update.py | 26 +++++------ 11 files changed, 166 insertions(+), 98 deletions(-) diff --git a/osc/commandline.py b/osc/commandline.py index 1cba3715..8a657470 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -9535,14 +9535,12 @@ Please submit there instead, or use --nodevelproject to force direct submission. store_write_string(destdir, '_linkrepair', '') pac = Package(destdir) - storedir = os.path.join(destdir, store) - for name in sorted(entries.keys()): md5_old = entries_old.get(name, '') md5_new = entries_new.get(name, '') md5_oldpatched = entries_oldpatched.get(name, '') if md5_new != '': - self.download(name, md5_new, dir_new, os.path.join(storedir, name)) + self.download(name, md5_new, dir_new, pac.store.sources_get_path(name)) if md5_old == md5_new: if md5_oldpatched == '': pac.put_on_deletelist(name) @@ -9554,17 +9552,17 @@ Please submit there instead, or use --nodevelproject to force direct submission. if md5_new == '': continue print(statfrmt('U', name)) - shutil.copy2(os.path.join(storedir, name), os.path.join(destdir, name)) + shutil.copy2(pac.store.sources_get_path(name), os.path.join(destdir, name)) continue if md5_new == md5_oldpatched: if md5_new == '': continue print(statfrmt('G', name)) - shutil.copy2(os.path.join(storedir, name), os.path.join(destdir, name)) + shutil.copy2(pac.store.sources_get_path(name), os.path.join(destdir, name)) continue self.download(name, md5_oldpatched, dir_oldpatched, os.path.join(destdir, name + '.mine')) if md5_new != '': - shutil.copy2(os.path.join(storedir, name), os.path.join(destdir, name + '.new')) + shutil.copy2(pac.store.sources_get_path(name), os.path.join(destdir, name + '.new')) else: self.download(name, md5_new, dir_new, os.path.join(destdir, name + '.new')) self.download(name, md5_old, dir_old, os.path.join(destdir, name + '.old')) diff --git a/osc/core.py b/osc/core.py index b4304ba1..0b213488 100644 --- a/osc/core.py +++ b/osc/core.py @@ -2820,7 +2820,7 @@ def get_source_file_diff(dir, filename, rev, oldfilename=None, olddir=None, orig oldfilename = filename if not olddir: - olddir = os.path.join(dir, store) + olddir = os.path.join(dir, store, "sources") if not origfilename: origfilename = filename diff --git a/osc/obs_scm/package.py b/osc/obs_scm/package.py index 35c65633..6fd1a0ec 100644 --- a/osc/obs_scm/package.py +++ b/osc/obs_scm/package.py @@ -16,6 +16,7 @@ from .linkinfo import Linkinfo from .serviceinfo import Serviceinfo from .store import __store_version__ from .store import Store +from .store import check_store_version from .store import read_inconflict from .store import read_filemeta from .store import read_sizelimit @@ -154,16 +155,13 @@ class Package: if self.scm_url: return dirty_files for fname in self.filenamelist: - if not os.path.exists(os.path.join(self.storedir, fname)) and fname not in self.skipped: + if not self.store.sources_is_file(fname) and fname not in self.skipped: dirty_files.append(fname) for fname in Package.REQ_STOREFILES: if not os.path.isfile(os.path.join(self.storedir, fname)): dirty_files.append(fname) - for fname in os.listdir(self.storedir): - if fname in Package.REQ_STOREFILES or fname in Package.OPT_STOREFILES or \ - fname.startswith('_build'): - continue - elif fname in self.filenamelist and fname in self.skipped: + for fname in self.store.sources_list_files(): + if fname in self.filenamelist and fname in self.skipped: dirty_files.append(fname) elif fname not in self.filenamelist: dirty_files.append(fname) @@ -182,6 +180,8 @@ class Package: store = Store(self.dir, check=False) store.assert_is_package() + # check_store_version() does the metadata migration that was disabled due to Store(..., check=False) + check_store_version(self.absdir) # there was a time when osc did not write _osclib_version file; let's assume these checkouts have version 1.0 if not store.exists("_osclib_version"): @@ -202,10 +202,10 @@ class Package: # all files which are present in the filelist have to exist in the storedir for f in self.filelist: # XXX: should we also check the md5? - if not os.path.exists(os.path.join(self.storedir, f.name)) and f.name not in self.skipped: + if not self.store.sources_is_file(f.name) and f.name not in self.skipped: # if get_source_file fails we're screwed up... get_source_file(self.apiurl, self.prjname, self.name, f.name, - targetfilename=os.path.join(self.storedir, f.name), revision=self.rev, + targetfilename=self.store.sources_get_path(f.name), revision=self.rev, mtime=f.mtime) repaired = True @@ -213,9 +213,11 @@ class Package: if fname in Package.REQ_STOREFILES or fname in Package.OPT_STOREFILES or \ fname.startswith('_build'): continue - elif fname not in self.filenamelist or fname in self.skipped: + + for fname in self.store.sources_list_files(): + if fname not in self.filenamelist or fname in self.skipped: # this file does not belong to the storedir so remove it - store.unlink(fname) + os.unlink(self.store.sources_get_path(fname)) repaired = True for fname in self.to_be_deleted[:]: @@ -247,11 +249,9 @@ class Package: raise oscerr.OscIOError(None, f'error: file \'{n}\' does not exist') if n in self.to_be_deleted: self.to_be_deleted.remove(n) -# self.delete_storefile(n) self.write_deletelist() elif n in self.filenamelist or n in self.to_be_added: raise oscerr.PackageFileConflict(self.prjname, self.name, n, f'osc: warning: \'{n}\' is already under version control') -# shutil.copyfile(os.path.join(self.dir, n), os.path.join(self.storedir, n)) if self.dir != '.': pathname = os.path.join(self.dir, n) else: @@ -291,12 +291,6 @@ class Package: self.write_deletelist() return (True, state) - def delete_storefile(self, n): - try: - os.unlink(os.path.join(self.storedir, n)) - except: - pass - def delete_localfile(self, n): try: os.unlink(os.path.join(self.dir, n)) @@ -320,7 +314,7 @@ class Package: if n in self.in_conflict: filename = os.path.join(self.dir, n) - storefilename = os.path.join(self.storedir, n) + storefilename = self.store.sources_get_path(n) myfilename = os.path.join(self.dir, n + '.mine') upfilename = os.path.join(self.dir, n + '.new') @@ -364,7 +358,7 @@ class Package: def delete_source_file(self, n): """delete local a source file""" self.delete_localfile(n) - self.delete_storefile(n) + self.store.sources_delete_file(n) def delete_remote_source_file(self, n): """delete a remote source file (e.g. from the server)""" @@ -393,7 +387,7 @@ class Package: def __commit_update_store(self, tdir): """move files from transaction directory into the store""" for filename in os.listdir(tdir): - os.rename(os.path.join(tdir, filename), os.path.join(self.storedir, filename)) + os.rename(os.path.join(tdir, filename), self.store.sources_get_path(filename)) def __generate_commitlist(self, todo_send): root = ET.Element('directory') @@ -543,7 +537,7 @@ class Package: # in sha256sums. # The storefile is guaranteed to exist (since we have a # pulled/linkrepair wc, the file cannot have state 'S') - storefile = os.path.join(self.storedir, filename) + storefile = self.store.sources_get_path(filename) sha256sums[filename] = sha256_dgst(storefile) if not force and not real_send and not todo_delete and not self.islinkrepair() and not self.ispulled(): @@ -623,7 +617,7 @@ class Package: store_write_string(self.absdir, '_files', ET.tostring(sfilelist, encoding=ET_ENCODING) + '\n') for filename in todo_delete: self.to_be_deleted.remove(filename) - self.delete_storefile(filename) + self.store.sources_delete_file(filename) self.write_deletelist() self.write_addlist() self.update_datastructs() @@ -667,7 +661,7 @@ class Package: from ..core import utime filename = os.path.join(self.dir, n) - storefilename = os.path.join(self.storedir, n) + storefilename = self.store.sources_get_path(n) origfile_tmp = os.path.join(self.storedir, '_in_update', f'{n}.copy') origfile = os.path.join(self.storedir, '_in_update', n) if os.path.isfile(filename): @@ -691,7 +685,7 @@ class Package: from ..core import run_external filename = os.path.join(self.dir, n) - storefilename = os.path.join(self.storedir, n) + storefilename = self.store.sources_get_path(n) myfilename = os.path.join(self.dir, n + '.mine') upfilename = os.path.join(self.dir, n + '.new') origfile_tmp = os.path.join(self.storedir, '_in_update', f'{n}.copy') @@ -996,7 +990,7 @@ class Package: known_by_meta = True if os.path.exists(localfile): exists = True - if os.path.exists(os.path.join(self.storedir, n)): + if self.store.sources_is_file(n): exists_in_store = True if n in self.to_be_deleted: @@ -1074,7 +1068,7 @@ class Package: b_revision = self.rev.encode() diff.append(b'--- %s\t(revision %s)\n' % (fname.encode(), b_revision)) diff.append(b'+++ %s\t(working copy)\n' % fname.encode()) - fname = os.path.join(self.storedir, fname) + fname = self.store.sources_get_path(fname) fd = None tmpfile = None @@ -1427,8 +1421,8 @@ rev: %s raise oscerr.PackageInternalError(self.prjname, self.name, 'too many files in \'_in_update\' dir') tmp = rfiles[:] for f in tmp: - if os.path.exists(os.path.join(self.storedir, f.name)): - if dgst(os.path.join(self.storedir, f.name)) == f.md5: + if self.store.sources_is_file(f.name): + if dgst(self.store.sources_get_path(f.name)) == f.md5: if f in kept: kept.remove(f) elif f in added: @@ -1474,10 +1468,10 @@ rev: %s # if the storefile doesn't exist we're resuming an aborted update: # the file was already deleted but we cannot know this # OR we're processing a _service: file (simply keep the file) - if os.path.isfile(os.path.join(self.storedir, f.name)) and self.status(f.name) not in ('M', 'C'): + if self.store.sources_is_file(f.name) and self.status(f.name) not in ('M', 'C'): # if self.status(f.name) != 'M': self.delete_localfile(f.name) - self.delete_storefile(f.name) + self.store.sources_delete_file(f.name) print(statfrmt('D', os.path.join(pathn, f.name))) if f.name in self.to_be_deleted: self.to_be_deleted.remove(f.name) @@ -1501,7 +1495,7 @@ rev: %s print(f'Restored \'{os.path.join(pathn, f.name)}\'') elif state == 'C': get_source_file(self.apiurl, self.prjname, self.name, f.name, - targetfilename=os.path.join(self.storedir, f.name), revision=rev, + targetfilename=self.store.sources_get_path(f.name), revision=rev, progress_obj=self.progress_obj, mtime=f.mtime, meta=self.meta) print(f'skipping \'{f.name}\' (this is due to conflicts)') elif state == 'D' and self.findfilebyname(f.name).md5 != f.md5: @@ -1560,12 +1554,12 @@ rev: %s raise oscerr.OscIOError(None, f'file \'{filename}\' is not under version control') elif filename in self.skipped: raise oscerr.OscIOError(None, f'file \'{filename}\' is marked as skipped and cannot be reverted') - if filename in self.filenamelist and not os.path.exists(os.path.join(self.storedir, filename)): + if filename in self.filenamelist and not self.store.sources_is_file(filename): msg = f"file '{filename}' is listed in filenamelist but no storefile exists" raise oscerr.PackageInternalError(self.prjname, self.name, msg) state = self.status(filename) if not (state == 'A' or state == '!' and filename in self.to_be_added): - shutil.copyfile(os.path.join(self.storedir, filename), os.path.join(self.absdir, filename)) + shutil.copyfile(self.store.sources_get_path(filename), os.path.join(self.absdir, filename)) if state == 'D': self.to_be_deleted.remove(filename) self.write_deletelist() diff --git a/osc/obs_scm/store.py b/osc/obs_scm/store.py index 4e008d4d..2701ce0e 100644 --- a/osc/obs_scm/store.py +++ b/osc/obs_scm/store.py @@ -11,16 +11,17 @@ from .. import oscerr from .._private import api from ..util.xml import ET +from typing import List # __store_version__ is to be incremented when the format of the working copy # "store" changes in an incompatible way. Please add any needed migration # functionality to check_store_version(). -__store_version__ = '1.0' +__store_version__ = '2.0' class Store: STORE_DIR = ".osc" - STORE_VERSION = "1.0" + STORE_VERSION = "2.0" @classmethod def is_project_dir(cls, path): @@ -57,7 +58,11 @@ class Store: def __iter__(self): path = os.path.join(self.abspath, self.STORE_DIR) - yield from os.listdir(path) + for fn in os.listdir(path): + full_path = os.path.join(path, fn) + if os.path.isdir(full_path): + continue + yield fn def assert_is_project(self): if not self.is_project: @@ -327,6 +332,43 @@ class Store: root = self.read_xml_node("_meta", "project").getroot() return root + def sources_get_path(self, file_name: str) -> str: + if "/" in file_name: + raise ValueError(f"Plain file name expected: {file_name}") + result = os.path.join(self.abspath, self.STORE_DIR, "sources", file_name) + os.makedirs(os.path.dirname(result), exist_ok=True) + return result + + def sources_list_files(self) -> List[str]: + result = [] + invalid = [] + + topdir = os.path.join(self.abspath, self.STORE_DIR, "sources") + + if not os.path.isdir(topdir): + return [] + + for fn in os.listdir(topdir): + if self.sources_is_file(fn): + result.append(fn) + else: + invalid.append(fn) + + if invalid: + msg = ".osc/sources contains entries other than regular files" + raise oscerr.WorkingCopyInconsistent(self.project, self.package, invalid, msg) + + return result + + def sources_is_file(self, file_name: str) -> bool: + return os.path.isfile(self.sources_get_path(file_name)) + + def sources_delete_file(self, file_name: str): + try: + os.unlink(self.sources_get_path(file_name)) + except: + pass + store = '.osc' @@ -351,12 +393,46 @@ def check_store_version(dir): raise oscerr.NoWorkingCopy(msg) if v != __store_version__: + migrated = False + if v in ['0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '0.95', '0.96', '0.97', '0.98', '0.99']: - # version is fine, no migration needed - f = open(versionfile, 'w') - f.write(__store_version__ + '\n') - f.close() + # no migration needed, only change metadata version to 1.0 + s = Store(dir, check=False) + v = "1.0" + s.write_string("_osclib_version", v) + migrated = True + + if v == "1.0": + store_dir = os.path.join(dir, store) + sources_dir = os.path.join(dir, store, "sources") + os.makedirs(sources_dir, exist_ok=True) + + s = Store(dir, check=False) + if s.is_package and not s.scmurl: + from .package import Package + from .project import Project + + scm_files = [i.name for i in s.files] + + for fn in os.listdir(store_dir): + old_path = os.path.join(store_dir, fn) + new_path = os.path.join(sources_dir, fn) + if not os.path.isfile(old_path): + continue + if fn in Package.REQ_STOREFILES or fn in Package.OPT_STOREFILES: + continue + if fn.startswith("_") and fn not in scm_files: + continue + if os.path.isfile(old_path): + os.rename(old_path, new_path) + + v = "2.0" + s.write_string("_osclib_version", v) + migrated = True + + if migrated: return + msg = f'The osc metadata of your working copy "{dir}"' msg += f'\nhas __store_version__ = {v}, but it should be {__store_version__}' msg += '\nPlease do a fresh checkout or update your client. Sorry about the inconvenience.' diff --git a/tests/common.py b/tests/common.py index 092d69a6..db826a85 100644 --- a/tests/common.py +++ b/tests/common.py @@ -73,7 +73,7 @@ class RequestWrongOrder(Exception): self.exp_method = exp_method def __str__(self): - return f'{self.url}, {self.exp_url}, {self.method}, {self.exp_method}' + return '%s, %s, %s, %s' % (self.url, self.exp_url, self.method, self.exp_method) class RequestDataMismatch(Exception): @@ -85,7 +85,7 @@ class RequestDataMismatch(Exception): self.exp = exp def __str__(self): - return f'{self.url}, {self.got}, {self.exp}' + return '%s, %s, %s' % (self.url, self.got, self.exp) EXPECTED_REQUESTS = [] @@ -261,8 +261,8 @@ class OscTestCase(unittest.TestCase): for i in root.findall('entry'): if i.get('name') in skipfiles: continue - self.assertTrue(os.path.exists(os.path.join('.osc', i.get('name')))) - self.assertEqual(osc.core.dgst(os.path.join('.osc', i.get('name'))), i.get('md5')) + self.assertTrue(os.path.exists(os.path.join('.osc', 'sources', i.get('name')))) + self.assertEqual(osc.core.dgst(os.path.join('.osc', 'sources', i.get('name'))), i.get('md5')) def assertFilesEqual(self, first, second): self.assertTrue(os.path.exists(first)) diff --git a/tests/test_addfiles.py b/tests/test_addfiles.py index 1bb5c92e..87ff2320 100644 --- a/tests/test_addfiles.py +++ b/tests/test_addfiles.py @@ -26,7 +26,7 @@ class TestAddFiles(OscTestCase): p.addfile('toadd1') exp = 'A toadd1\n' self.assertEqual(sys.stdout.getvalue(), exp) - self.assertFalse(os.path.exists(os.path.join('.osc', 'toadd1'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'toadd1'))) self._check_status(p, 'toadd1', 'A') self._check_addlist('toadd1\n') @@ -38,8 +38,8 @@ class TestAddFiles(OscTestCase): p.addfile('toadd2') exp = 'A toadd1\nA toadd2\n' self.assertEqual(sys.stdout.getvalue(), exp) - self.assertFalse(os.path.exists(os.path.join('.osc', 'toadd1'))) - self.assertFalse(os.path.exists(os.path.join('.osc', 'toadd2'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'toadd1'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'toadd2'))) self._check_status(p, 'toadd1', 'A') self._check_status(p, 'toadd2', 'A') self._check_addlist('toadd1\ntoadd2\n') @@ -60,7 +60,7 @@ class TestAddFiles(OscTestCase): self.assertRaises(osc.oscerr.PackageFileConflict, p.addfile, 'toadd1') exp = 'A toadd1\n' self.assertEqual(sys.stdout.getvalue(), exp) - self.assertFalse(os.path.exists(os.path.join('.osc', 'toadd1'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'toadd1'))) self._check_status(p, 'toadd1', 'A') self._check_addlist('toadd1\n') @@ -73,7 +73,7 @@ class TestAddFiles(OscTestCase): p.addfile('foo') exp = 'A foo\n' self.assertEqual(sys.stdout.getvalue(), exp) - self.assertFileContentNotEqual(os.path.join('.osc', 'foo'), 'replaced file\n') + self.assertFileContentNotEqual(os.path.join('.osc', 'sources', 'foo'), 'replaced file\n') self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) self._check_status(p, 'foo', 'R') self._check_addlist('foo\n') diff --git a/tests/test_commit.py b/tests/test_commit.py index c77ddaa5..42215857 100644 --- a/tests/test_commit.py +++ b/tests/test_commit.py @@ -43,7 +43,7 @@ class TestCommit(OscTestCase): self.assertEqual(sys.stdout.getvalue(), exp) self._check_digests('testSimple_cfilesremote') self.assertTrue(os.path.exists('nochange')) - self.assertFilesEqual('nochange', os.path.join('.osc', 'nochange')) + self.assertFilesEqual('nochange', os.path.join('.osc', 'sources', 'nochange')) self._check_status(p, 'nochange', ' ') self._check_status(p, 'foo', ' ') self._check_status(p, 'merge', ' ') @@ -67,7 +67,7 @@ class TestCommit(OscTestCase): self.assertEqual(sys.stdout.getvalue(), exp) self._check_digests('testAddfile_cfilesremote') self.assertTrue(os.path.exists('add')) - self.assertFilesEqual('add', os.path.join('.osc', 'add')) + self.assertFilesEqual('add', os.path.join('.osc', 'sources', 'add')) self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_added'))) self._check_status(p, 'add', ' ') self._check_status(p, 'foo', ' ') @@ -89,7 +89,7 @@ class TestCommit(OscTestCase): self.assertEqual(sys.stdout.getvalue(), exp) self._check_digests('testDeletefile_cfilesremote') self.assertFalse(os.path.exists('nochange')) - self.assertFalse(os.path.exists(os.path.join('.osc', 'nochange'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'nochange'))) self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) self._check_status(p, 'foo', ' ') self._check_status(p, 'merge', ' ') @@ -144,8 +144,8 @@ class TestCommit(OscTestCase): self._check_digests('testMultiple_cfilesremote') self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_added'))) self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) - self.assertFalse(os.path.exists(os.path.join('.osc', 'foo'))) - self.assertFalse(os.path.exists(os.path.join('.osc', 'merge'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'foo'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'merge'))) self.assertRaises(osc.oscerr.OscIOError, p.status, 'foo') self.assertRaises(osc.oscerr.OscIOError, p.status, 'merge') self._check_status(p, 'add', ' ') @@ -221,7 +221,7 @@ class TestCommit(OscTestCase): self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_added'))) self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) self.assertFalse(os.path.exists('foo')) - self.assertFalse(os.path.exists(os.path.join('.osc', 'foo'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'foo'))) self._check_status(p, 'add', ' ') self._check_status(p, 'nochange', ' ') self._check_status(p, 'merge', '!') @@ -244,7 +244,7 @@ class TestCommit(OscTestCase): self.assertEqual(sys.stdout.getvalue(), exp) self._check_digests('testAddfile_cfilesremote') self.assertTrue(os.path.exists('add')) - self.assertFilesEqual('add', os.path.join('.osc', 'add')) + self.assertFilesEqual('add', os.path.join('.osc', 'sources', 'add')) self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_added'))) self._check_status(p, 'add', ' ') self._check_status(p, 'foo', ' ') @@ -344,7 +344,7 @@ class TestCommit(OscTestCase): self.assertEqual(sys.stdout.getvalue(), exp) self._check_digests('testSimple_cfilesremote') self.assertTrue(os.path.exists('nochange')) - self.assertFilesEqual('nochange', os.path.join('.osc', 'nochange')) + self.assertFilesEqual('nochange', os.path.join('.osc', 'sources', 'nochange')) self._check_status(p, 'nochange', ' ') self._check_status(p, 'foo', ' ') self._check_status(p, 'merge', ' ') diff --git a/tests/test_deletefiles.py b/tests/test_deletefiles.py index 56685063..0ad928ac 100644 --- a/tests/test_deletefiles.py +++ b/tests/test_deletefiles.py @@ -25,7 +25,7 @@ class TestDeleteFiles(OscTestCase): ret = p.delete_file('foo') self.__check_ret(ret, True, ' ') self.assertFalse(os.path.exists('foo')) - self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) + self.assertTrue(os.path.exists(os.path.join('.osc', 'sources', 'foo'))) self._check_deletelist('foo\n') self._check_status(p, 'foo', 'D') @@ -36,7 +36,7 @@ class TestDeleteFiles(OscTestCase): ret = p.delete_file('nochange') self.__check_ret(ret, False, 'M') self.assertTrue(os.path.exists('nochange')) - self.assertTrue(os.path.exists(os.path.join('.osc', 'nochange'))) + self.assertTrue(os.path.exists(os.path.join('.osc', 'sources', 'nochange'))) self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) self._check_status(p, 'nochange', 'M') @@ -78,7 +78,7 @@ class TestDeleteFiles(OscTestCase): ret = p.delete_file('foo') self.__check_ret(ret, False, 'C') self.assertTrue(os.path.exists('foo')) - self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) + self.assertTrue(os.path.exists(os.path.join('.osc', 'sources', 'foo'))) self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) self._check_conflictlist('foo\n') self._check_status(p, 'foo', 'C') @@ -90,7 +90,7 @@ class TestDeleteFiles(OscTestCase): ret = p.delete_file('nochange', force=True) self.__check_ret(ret, True, 'M') self.assertFalse(os.path.exists('nochange')) - self.assertTrue(os.path.exists(os.path.join('.osc', 'nochange'))) + self.assertTrue(os.path.exists(os.path.join('.osc', 'sources', 'nochange'))) self._check_deletelist('nochange\n') self._check_status(p, 'nochange', 'D') @@ -122,7 +122,7 @@ class TestDeleteFiles(OscTestCase): ret = p.delete_file('merge', force=True) self.__check_ret(ret, True, 'R') self.assertFalse(os.path.exists('merge')) - self.assertTrue(os.path.exists(os.path.join('.osc', 'merge'))) + self.assertTrue(os.path.exists(os.path.join('.osc', 'sources', 'merge'))) self._check_deletelist('merge\n') self._check_addlist('toadd1\n') self._check_status(p, 'merge', 'D') @@ -136,7 +136,7 @@ class TestDeleteFiles(OscTestCase): self.assertFalse(os.path.exists('foo')) self.assertTrue(os.path.exists('foo.r2')) self.assertTrue(os.path.exists('foo.mine')) - self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) + self.assertTrue(os.path.exists(os.path.join('.osc', 'sources', 'foo'))) self._check_deletelist('foo\n') self.assertFalse(os.path.exists(os.path.join('.osc', '_in_conflict'))) self._check_status(p, 'foo', 'D') @@ -151,8 +151,8 @@ class TestDeleteFiles(OscTestCase): self.__check_ret(ret, True, ' ') self.assertFalse(os.path.exists('foo')) self.assertFalse(os.path.exists('merge')) - self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) - self.assertTrue(os.path.exists(os.path.join('.osc', 'merge'))) + self.assertTrue(os.path.exists(os.path.join('.osc', 'sources', 'foo'))) + self.assertTrue(os.path.exists(os.path.join('.osc', 'sources', 'merge'))) self._check_deletelist('foo\nmerge\n') def testDeleteAlreadyDeleted(self): @@ -162,7 +162,7 @@ class TestDeleteFiles(OscTestCase): ret = p.delete_file('foo') self.__check_ret(ret, True, 'D') self.assertFalse(os.path.exists('foo')) - self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) + self.assertTrue(os.path.exists(os.path.join('.osc', 'sources', 'foo'))) self._check_deletelist('foo\n') self._check_status(p, 'foo', 'D') @@ -176,7 +176,7 @@ class TestDeleteFiles(OscTestCase): ret = p.delete_file('toadd1') self.__check_ret(ret, True, '!') self.assertFalse(os.path.exists('toadd1')) - self.assertFalse(os.path.exists(os.path.join('.osc', 'toadd1'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'toadd1'))) self._check_deletelist('foo\n') self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_added'))) diff --git a/tests/test_repairwc.py b/tests/test_repairwc.py index c69725ce..2d6c6b10 100644 --- a/tests/test_repairwc.py +++ b/tests/test_repairwc.py @@ -25,7 +25,7 @@ class TestRepairWC(OscTestCase): try: meth(*args, **kwargs) except exception: - self.fail(f'{exception.__name__} raised') + self.fail('%s raised' % exception.__name__) def test_working_empty(self): """consistent, empty working copy""" @@ -54,7 +54,7 @@ class TestRepairWC(OscTestCase): self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') p = osc.core.Package('.', wc_check=False) p.wc_repair() - self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) + self.assertTrue(os.path.exists(os.path.join('.osc', 'sources', 'foo'))) self._check_deletelist('foo\n') self._check_status(p, 'foo', 'D') self._check_status(p, 'nochange', 'M') @@ -69,7 +69,7 @@ class TestRepairWC(OscTestCase): self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') p = osc.core.Package('.', wc_check=False) p.wc_repair() - self.assertFalse(os.path.exists(os.path.join('.osc', 'somefile'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'somefile'))) self._check_deletelist('foo\n') self._check_status(p, 'foo', 'D') self._check_status(p, 'nochange', 'M') @@ -79,12 +79,12 @@ class TestRepairWC(OscTestCase): self.__assertNotRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') def test_simple3(self): - """toadd1 has state 'A' and a file .osc/toadd1 exists""" + """toadd1 has state 'A' and a file .osc/sources/toadd1 exists""" self._change_to_pkg('simple3') self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') p = osc.core.Package('.', wc_check=False) p.wc_repair() - self.assertFalse(os.path.exists(os.path.join('.osc', 'toadd1'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'toadd1'))) self._check_deletelist('foo\n') self._check_status(p, 'foo', 'D') self._check_status(p, 'nochange', 'M') @@ -133,7 +133,7 @@ class TestRepairWC(OscTestCase): self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') p = osc.core.Package('.', wc_check=False) p.wc_repair() - self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) + self.assertTrue(os.path.exists(os.path.join('.osc', 'sources', 'foo'))) self._check_deletelist('foo\n') self._check_status(p, 'foo', 'D') self._check_status(p, 'nochange', 'M') @@ -155,7 +155,7 @@ class TestRepairWC(OscTestCase): self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') p = osc.core.Package('.', wc_check=False) p.wc_repair() - self.assertFalse(os.path.exists(os.path.join('.osc', 'skipped'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'skipped'))) self._check_deletelist('foo\n') self._check_status(p, 'foo', 'D') self._check_status(p, 'nochange', 'M') @@ -178,8 +178,8 @@ class TestRepairWC(OscTestCase): self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') p = osc.core.Package('.', wc_check=False) p.wc_repair() - self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) - self.assertFalse(os.path.exists(os.path.join('.osc', 'unknown_file'))) + self.assertTrue(os.path.exists(os.path.join('.osc', 'sources', 'foo'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'unknown_file'))) self._check_deletelist('foo\n') self._check_status(p, 'foo', 'D') self._check_status(p, 'nochange', 'C') diff --git a/tests/test_revertfiles.py b/tests/test_revertfiles.py index fd0fba13..f9e10e88 100644 --- a/tests/test_revertfiles.py +++ b/tests/test_revertfiles.py @@ -92,7 +92,7 @@ class TestRevertFiles(OscTestCase): self.assertRaises(osc.oscerr.OscIOError, p.revert, 'skipped') def __check_file(self, fname): - storefile = os.path.join('.osc', fname) + storefile = os.path.join('.osc', 'sources', fname) self.assertTrue(os.path.exists(fname)) self.assertTrue(os.path.exists(storefile)) self.assertFilesEqual(fname, storefile) diff --git a/tests/test_update.py b/tests/test_update.py index 1c64e330..01090086 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -57,7 +57,7 @@ class TestUpdate(OscTestCase): self.assertEqual(sys.stdout.getvalue(), exp) self._check_digests('testUpdateDeletedFile_files') self.assertFalse(os.path.exists('foo')) - self.assertFalse(os.path.exists(os.path.join('.osc', 'foo'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'foo'))) @GET('http://localhost/source/osctest/simple?rev=2', file='testUpdateUpstreamModifiedFile_files') @GET('http://localhost/source/osctest/simple/foo?rev=2', file='testUpdateUpstreamModifiedFile_foo') @@ -117,7 +117,7 @@ class TestUpdate(OscTestCase): self.assertEqual(sys.stdout.getvalue(), exp) self._check_deletelist('foo\n') self._check_conflictlist('merge\n') - self.assertFilesEqual('foo', os.path.join('.osc', 'foo')) + self.assertFilesEqual('foo', os.path.join('.osc', 'sources', 'foo')) self._check_digests('testUpdateLocalDeletions_files') @GET('http://localhost/source/osctest/restore?rev=latest', file='testUpdateRestore_files') @@ -142,7 +142,7 @@ class TestUpdate(OscTestCase): osc.core.Package('.').update(size_limit=50) exp = 'D bigfile\nAt revision 2.\n' self.assertEqual(sys.stdout.getvalue(), exp) - self.assertFalse(os.path.exists(os.path.join('.osc', 'bigfile'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'bigfile'))) self.assertFalse(os.path.exists('bigfile')) self._check_digests('testUpdateLimitSizeNoChange_files', 'bigfile') @@ -158,8 +158,8 @@ class TestUpdate(OscTestCase): p.update() exp = 'D bigfile\nD merge\nAt revision 2.\n' self.assertEqual(sys.stdout.getvalue(), exp) - self.assertFalse(os.path.exists(os.path.join('.osc', 'bigfile'))) - self.assertFalse(os.path.exists(os.path.join('.osc', 'merge'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'bigfile'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'merge'))) self.assertFalse(os.path.exists('bigfile')) self._check_digests('testUpdateLocalLimitSizeNoChange_files', 'bigfile', 'merge') self._check_status(p, 'bigfile', 'S') @@ -180,11 +180,11 @@ class TestUpdate(OscTestCase): osc.core.Package('.').update(size_limit=10) exp = 'A exists\nD bigfile\nD foo\nD merge\nD nochange\nAt revision 2.\n' self.assertEqual(sys.stdout.getvalue(), exp) - self.assertFalse(os.path.exists(os.path.join('.osc', 'bigfile'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'bigfile'))) self.assertFalse(os.path.exists('bigfile')) - self.assertFalse(os.path.exists(os.path.join('.osc', 'foo'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'foo'))) self.assertFalse(os.path.exists('foo')) - self.assertFalse(os.path.exists(os.path.join('.osc', 'merge'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'merge'))) self.assertFalse(os.path.exists('merge')) # exists because local version is modified self.assertTrue(os.path.exists('nochange')) @@ -202,9 +202,9 @@ class TestUpdate(OscTestCase): osc.core.Package('.').update(service_files=True) exp = 'A bigfile\nD _service:exists\nA _service:bar\nA _service:foo\nAt revision 2.\n' self.assertEqual(sys.stdout.getvalue(), exp) - self.assertFalse(os.path.exists(os.path.join('.osc', '_service:bar'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', '_service:bar'))) self.assertFileContentEqual('_service:bar', 'another service\n') - self.assertFalse(os.path.exists(os.path.join('.osc', '_service:foo'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', '_service:foo'))) self.assertFileContentEqual('_service:foo', 'small\n') self.assertTrue(os.path.exists('_service:exists')) self._check_digests('testUpdateServiceFilesAddDelete_files', '_service:foo', '_service:bar') @@ -218,9 +218,9 @@ class TestUpdate(OscTestCase): osc.core.Package('.').update() exp = 'A bigfile\nD _service:exists\nAt revision 2.\n' self.assertEqual(sys.stdout.getvalue(), exp) - self.assertFalse(os.path.exists(os.path.join('.osc', '_service:bar'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', '_service:bar'))) self.assertFalse(os.path.exists('_service:bar')) - self.assertFalse(os.path.exists(os.path.join('.osc', '_service:foo'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', '_service:foo'))) self.assertFalse(os.path.exists('_service:foo')) self.assertTrue(os.path.exists('_service:exists')) self._check_digests('testUpdateServiceFilesAddDelete_files', '_service:foo', '_service:bar') @@ -284,7 +284,7 @@ class TestUpdate(OscTestCase): self.assertEqual(sys.stdout.getvalue(), exp) self.assertFalse(os.path.exists(os.path.join('.osc', '_in_update'))) self.assertFalse(os.path.exists('added')) - self.assertFalse(os.path.exists(os.path.join('.osc', 'added'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'sources', 'added'))) self._check_digests('testUpdateResumeDeletedFile_files')