diff --git a/osc/babysitter.py b/osc/babysitter.py index cf66027b..aed7357e 100644 --- a/osc/babysitter.py +++ b/osc/babysitter.py @@ -149,7 +149,7 @@ def run(prg): print >>sys.stderr, e return 1 - except (oscerr.PackageExists, oscerr.PackageMissing), e: + except (oscerr.PackageExists, oscerr.PackageMissing, oscerr.WorkingCopyInconsistent), e: print >>sys.stderr, e.msg return 1 diff --git a/osc/commandline.py b/osc/commandline.py index e647eb6e..7f4d52a7 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -5977,7 +5977,34 @@ Please submit there instead, or use --nodevelproject to force direct submission. p.todo = p.filenamelist for f in p.todo: p.revert(f) - + + def do_repairwc(self, subcmd, opts, *args): + """${cmd_name}: try to repair an inconsistent working copy + + Examples: + osc repairwc + + Note: if is omitted it defaults to '.' + + Warning: This command might delete some files in the storedir + (.osc). Please check the state of the wc afterwards (via 'osc status'). + + ${cmd_usage} + ${cmd_option_list} + """ + args = parseargs(args) + if len(args) < 1: + raise oscerr.WrongArgs('Too few arguments.') + elif len(args) > 1: + raise oscerr.WrongArgs('Too many arguments.') + try: + p = Package(args[0]) + except oscerr.WorkingCopyInconsistent: + p = Package(args[0], wc_check=False) + p.wc_repair() + print 'done. Please check the state of the wc (via \'osc status\').' + else: + print >>sys.stderr, 'osc: working copy is not inconsistent' # fini! ############################################################################### diff --git a/osc/core.py b/osc/core.py index aa8e2ba4..be4c7758 100644 --- a/osc/core.py +++ b/osc/core.py @@ -773,7 +773,13 @@ class Project: class Package: """represent a package (its directory) and read/keep/write its metadata""" - def __init__(self, workingdir, progress_obj=None, size_limit=None): + + # should _meta be a required file? + REQ_STOREFILES = ('_project', '_package', '_apiurl', '_files', '_osclib_version') + OPT_STOREFILES = ('_to_be_added', '_to_be_deleted', '_in_conflict', '_in_update', + '_in_commit', '_meta', '_meta_mode', '_frozen', '_pulled', '_linkrepair', '_size_limit') + + def __init__(self, workingdir, progress_obj=None, size_limit=None, wc_check=True): global store self.dir = workingdir @@ -791,9 +797,64 @@ class Package: self.apiurl = store_read_apiurl(self.dir) self.update_datastructs() + if wc_check and self.wc_check(): + msg = 'Your working copy is in an inconsistent state.\n' \ + 'Please run \'osc repairwc\' (Note this might _remove_\n' \ + 'files from the .osc/ dir). Please check the state\n' \ + 'of the working copy afterwards (via \'osc status\')' + raise oscerr.WorkingCopyInconsistent(self.prjname, self.name, msg) self.todo = [] + def wc_check(self): + dirty = False + for fname in self.filenamelist: + if not os.path.exists(os.path.join(self.storedir, fname)) and not fname in self.skipped: + dirty = True + for fname in Package.REQ_STOREFILES: + if not os.path.isfile(os.path.join(self.storedir, fname)): + dirty = True + 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: + dirty = True + elif not fname in self.filenamelist: + dirty = True + for fname in self.to_be_deleted[:]: + if not fname in self.filenamelist: + dirty = True + for fname in self.in_conflict[:]: + if not fname in self.filenamelist: + dirty = True + return dirty + + def wc_repair(self): + # 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 not f.name 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, + mtime=f.mtime) + 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 not fname in self.filenamelist or fname in self.skipped: + # this file does not belong to the storedir so remove it + os.unlink(os.path.join(self.storedir, fname)) + for fname in self.to_be_deleted[:]: + if not fname in self.filenamelist: + self.to_be_deleted.remove(fname) + self.write_deletelist() + for fname in self.in_conflict[:]: + if not fname in self.filenamelist: + self.in_conflict.remove(fname) + self.write_conflictlist() + def info(self): source_url = makeurl(self.apiurl, ['source', self.prjname, self.name]) r = info_templ % (self.prjname, self.name, self.absdir, self.apiurl, source_url, self.srcmd5, self.rev, self.linkinfo) @@ -1242,12 +1303,6 @@ class Package: except: # okay, a very old version of _files, which didn't contain any metadata yet... f = File(node.get('name'), '', 0, 0) - # restore storefile in case it is lost (for whatever reason) - # XXX: this is a bad idea if we do a checkout - will be fixed later - if not os.path.exists(os.path.join(self.storedir, f.name)) and not f.name in self.skipped: - get_source_file(self.apiurl, self.prjname, self.name, f.name, - targetfilename=os.path.join(self.storedir, f.name), revision=files_tree_root.get('rev'), - mtime=f.mtime) self.filelist.append(f) self.filenamelist.append(f.name) diff --git a/osc/oscerr.py b/osc/oscerr.py index 122cd548..bbfbf460 100644 --- a/osc/oscerr.py +++ b/osc/oscerr.py @@ -82,6 +82,12 @@ class PackageError(OscBaseError): self.prj = prj self.pac = pac +class WorkingCopyInconsistent(PackageError): + """Exception raised when the working copy is in an inconsistent state""" + def __init__(self, prj, pac, msg): + PackageError.__init__(self, prj, pac) + self.msg = msg + class LinkExpandError(PackageError): """Exception raised when source link expansion fails""" def __init__(self, prj, pac, msg):