diff options
Diffstat (limited to 'repoman/pym/repoman/modules/vcs')
22 files changed, 1794 insertions, 0 deletions
diff --git a/repoman/pym/repoman/modules/vcs/None/__init__.py b/repoman/pym/repoman/modules/vcs/None/__init__.py new file mode 100644 index 000000000..285932541 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/None/__init__.py @@ -0,0 +1,34 @@ +# Copyright 2014-2015 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +doc = """None (non vcs type) plug-in module for portage. +Performs various git actions and checks on repositories.""" +__doc__ = doc[:] + + +module_spec = { + 'name': 'None', + 'description': doc, + 'provides':{ + 'None-module': { + 'name': "None_status", + 'sourcefile': "status", + 'class': "Status", + 'description': doc, + 'functions': ['check', 'supports_gpg_sign', 'detect_conflicts'], + 'func_desc': { + }, + 'vcs_preserves_mtime': False, + 'needs_keyword_expansion': False, + }, + 'None-changes': { + 'name': "None_changes", + 'sourcefile': "changes", + 'class': "Changes", + 'description': doc, + 'functions': ['scan'], + 'func_desc': { + }, + }, + } +} diff --git a/repoman/pym/repoman/modules/vcs/None/changes.py b/repoman/pym/repoman/modules/vcs/None/changes.py new file mode 100644 index 000000000..46c38e257 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/None/changes.py @@ -0,0 +1,50 @@ +''' +None module Changes class submodule +''' + +from repoman.modules.vcs.changes import ChangesBase + + +class Changes(ChangesBase): + '''Class object to scan and hold the resultant data + for all changes to process. + ''' + + vcs = 'None' + + def __init__(self, options, repo_settings): + '''Class init + + @param options: the run time cli options + @param repo_settings: RepoSettings instance + ''' + super(Changes, self).__init__(options, repo_settings) + + def scan(self): + '''VCS type scan function, looks for all detectable changes''' + pass + + def add_items(self, autoadd): + '''Add files to the vcs's modified or new index + + @param autoadd: the files to add to the vcs modified index''' + pass + + def commit(self, myfiles, commitmessagefile): + '''None commit function + + @param commitfiles: list of files to commit + @param commitmessagefile: file containing the commit message + @returns: The sub-command exit value or 0 + ''' + commit_cmd = [] + # substitute a bogus vcs value for pretend output + commit_cmd.append("pretend") + commit_cmd.extend(self.vcs_settings.vcs_global_opts) + commit_cmd.append("commit") + commit_cmd.extend(self.vcs_settings.vcs_local_opts) + commit_cmd.extend(["-F", commitmessagefile]) + commit_cmd.extend(f.lstrip("./") for f in myfiles) + + print("(%s)" % (" ".join(commit_cmd),)) + return 0 diff --git a/repoman/pym/repoman/modules/vcs/None/status.py b/repoman/pym/repoman/modules/vcs/None/status.py new file mode 100644 index 000000000..d6e5ca0e4 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/None/status.py @@ -0,0 +1,53 @@ +''' +None (non-VCS) module Status class submodule +''' + + +class Status(object): + '''Performs status checks on the svn repository''' + + def __init__(self, qatracker, eadded): + '''Class init + + @param qatracker: QATracker class instance + @param eadded: list + ''' + self.qatracker = qatracker + self.eadded = eadded + + def check(self, checkdir, checkdir_relative, xpkg): + '''Perform the svn status check + + @param checkdir: string of the directory being checked + @param checkdir_relative: string of the relative directory being checked + @param xpkg: string of the package being checked + @returns: boolean + ''' + return True + + @staticmethod + def detect_conflicts(options): + '''Are there any merge conflicts present in the VCS tracking system + + @param options: command line options + @returns: Boolean + ''' + return False + + @staticmethod + def supports_gpg_sign(): + '''Does this vcs system support gpg commit signatures + + @returns: Boolean + ''' + return False + + @staticmethod + def isVcsDir(dirname): + '''Is the directory belong to the vcs system + + @param dirname: string, directory name + @returns: Boolean + ''' + return False + diff --git a/repoman/pym/repoman/modules/vcs/__init__.py b/repoman/pym/repoman/modules/vcs/__init__.py new file mode 100644 index 000000000..84e837408 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/__init__.py @@ -0,0 +1,14 @@ + +import os +from portage.module import Modules + +path = os.path.dirname(__file__) +# initial development debug info +#print("module path:", path) + +module_controller = Modules(path=path, namepath="repoman.modules.vcs") + +# initial development debug info +#print(module_controller.module_names) +module_names = module_controller.module_names[:] + diff --git a/repoman/pym/repoman/modules/vcs/bzr/__init__.py b/repoman/pym/repoman/modules/vcs/bzr/__init__.py new file mode 100644 index 000000000..4490ed86c --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/bzr/__init__.py @@ -0,0 +1,34 @@ +# Copyright 2014-2015 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +doc = """Bazaar (bzr) plug-in module for portage. +Performs variaous Bazaar actions and checks on repositories.""" +__doc__ = doc[:] + + +module_spec = { + 'name': 'bzr', + 'description': doc, + 'provides':{ + 'bzr-module': { + 'name': "bzr_status", + 'sourcefile': "status", + 'class': "Status", + 'description': doc, + 'functions': ['check', 'supports_gpg_sign', 'detect_conflicts'], + 'func_desc': { + }, + 'vcs_preserves_mtime': True, + 'needs_keyword_expansion': False, + }, + 'bzr-changes': { + 'name': "bzr_changes", + 'sourcefile': "changes", + 'class': "Changes", + 'description': doc, + 'functions': ['scan'], + 'func_desc': { + }, + }, + } +} diff --git a/repoman/pym/repoman/modules/vcs/bzr/changes.py b/repoman/pym/repoman/modules/vcs/bzr/changes.py new file mode 100644 index 000000000..4d4808c08 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/bzr/changes.py @@ -0,0 +1,68 @@ +''' +Bazaar module Changes class submodule +''' + +from repoman.modules.vcs.changes import ChangesBase +from repoman._subprocess import repoman_popen +from repoman._portage import portage +from portage import os +from portage.package.ebuild.digestgen import digestgen + +class Changes(ChangesBase): + '''Class object to scan and hold the resultant data + for all changes to process. + ''' + + vcs = 'bzr' + + def __init__(self, options, repo_settings): + '''Class init + + @param options: the run time cli options + @param repo_settings: RepoSettings instance + ''' + super(Changes, self).__init__(options, repo_settings) + + def _scan(self): + '''VCS type scan function, looks for all detectable changes''' + with repoman_popen("bzr status -S .") as f: + bzrstatus = f.readlines() + self.changed = [ + "./" + elem.split()[-1:][0].split('/')[-1:][0] + for elem in bzrstatus + if elem and elem[1:2] == "M"] + self.new = [ + "./" + elem.split()[-1:][0].split('/')[-1:][0] + for elem in bzrstatus + if elem and (elem[1:2] == "NK" or elem[0:1] == "R")] + self.removed = [ + "./" + elem.split()[-3:-2][0].split('/')[-1:][0] + for elem in bzrstatus + if elem and (elem[1:2] == "K" or elem[0:1] == "R")] + self.bzrstatus = bzrstatus + # Bazaar expands nothing. + + @property + def unadded(self): + '''Bazzar method of getting the unadded files in the repository''' + if self._unadded is not None: + return self._unadded + self._unadded = [ + "./" + elem.rstrip().split()[1].split('/')[-1:][0] + for elem in self.bzrstatus + if elem.startswith("?") or elem[0:2] == " D"] + return self._unadded + + def digest_regen(self, updates, removed, manifests, scanner, broken_changelog_manifests): + '''Regenerate manifests + + @param updates: updated files + @param removed: removed files + @param manifests: Manifest files + @param scanner: The repoman.scanner.Scanner instance + @param broken_changelog_manifests: broken changelog manifests + ''' + if broken_changelog_manifests: + for x in broken_changelog_manifests: + self.repoman_settings["O"] = os.path.join(self.repo_settings.repodir, x) + digestgen(mysettings=self.repoman_settings, myportdb=self.repo_settings.portdb) diff --git a/repoman/pym/repoman/modules/vcs/bzr/status.py b/repoman/pym/repoman/modules/vcs/bzr/status.py new file mode 100644 index 000000000..199e7f399 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/bzr/status.py @@ -0,0 +1,70 @@ +''' +Bazaar module Status class submodule +''' + +from repoman._portage import portage +from portage import os +from repoman._subprocess import repoman_popen + + +class Status(object): + '''Performs status checks on the svn repository''' + + def __init__(self, qatracker, eadded): + '''Class init + + @param qatracker: QATracker class instance + @param eadded: list + ''' + self.qatracker = qatracker + self.eadded = eadded + + def check(self, checkdir, checkdir_relative, xpkg): + '''Perform the svn status check + + @param checkdir: string of the directory being checked + @param checkdir_relative: string of the relative directory being checked + @param xpkg: string of the package being checked + @returns: boolean + ''' + try: + myf = repoman_popen( + "bzr ls -v --kind=file " + + portage._shell_quote(checkdir)) + myl = myf.readlines() + myf.close() + except IOError: + raise + for l in myl: + if l[1:2] == "?": + continue + l = l.split()[-1] + if l[-7:] == ".ebuild": + self.eadded.append(os.path.basename(l[:-7])) + return True + + @staticmethod + def detect_conflicts(options): + '''Are there any merge conflicts present in the VCS tracking system + + @param options: command line options + @returns: Boolean + ''' + return False + + @staticmethod + def supports_gpg_sign(): + '''Does this vcs system support gpg commit signatures + + @returns: Boolean + ''' + return False + + @staticmethod + def isVcsDir(dirname): + '''Does the directory belong to the vcs system + + @param dirname: string, directory name + @returns: Boolean + ''' + return dirname in [".bzr"] diff --git a/repoman/pym/repoman/modules/vcs/changes.py b/repoman/pym/repoman/modules/vcs/changes.py new file mode 100644 index 000000000..aa4923f8f --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/changes.py @@ -0,0 +1,169 @@ +''' +Base Changes class +''' + +import logging +import os +import subprocess +import sys +from itertools import chain + +from repoman._portage import portage +from portage import _unicode_encode +from portage.process import spawn + + +class ChangesBase(object): + '''Base Class object to scan and hold the resultant data + for all changes to process. + ''' + + vcs = 'None' + + def __init__(self, options, repo_settings): + '''Class init function + + @param options: the run time cli options + @param repo_settings: RepoSettings instance + ''' + self.options = options + self.repo_settings = repo_settings + self.repoman_settings = repo_settings.repoman_settings + self.vcs_settings = repo_settings.vcs_settings + self._reset() + + def _reset(self): + '''Reset the class variables for a new run''' + self.new_ebuilds = set() + self.ebuilds = set() + self.changelogs = set() + self.changed = [] + self.new = [] + self.removed = [] + self.no_expansion = set() + self._expansion = None + self._deleted = None + self._unadded = None + + def scan(self): + '''Scan the vcs for detectable changes. + + base method which calls the subclassing VCS module's _scan() + then updates some classwide variables. + ''' + self._reset() + + if self.vcs: + self._scan() + self.new_ebuilds.update(x for x in self.new if x.endswith(".ebuild")) + self.ebuilds.update(x for x in self.changed if x.endswith(".ebuild")) + self.changelogs.update( + x for x in chain(self.changed, self.new) + if os.path.basename(x) == "ChangeLog") + + def _scan(self): + '''Placeholder for subclassing''' + pass + + @property + def has_deleted(self): + '''Placeholder for VCS that requires manual deletion of files''' + return self.deleted != [] + + @property + def has_changes(self): + '''Placeholder for VCS repo common has changes result''' + changed = self.changed or self.new or self.removed or self.deleted + return changed != [] + + @property + def unadded(self): + '''Override this function as needed''' + return [] + + @property + def deleted(self): + '''Override this function as needed''' + return [] + + @property + def expansion(self): + '''Override this function as needed''' + return {} + + def thick_manifest(self, updates, headers, no_expansion, expansion): + '''Create a thick manifest + + @param updates: + @param headers: + @param no_expansion: + @param expansion: + ''' + pass + + def digest_regen(self, updates, removed, manifests, scanner, + broken_changelog_manifests): + '''Regenerate manifests + + @param updates: updated files + @param removed: removed files + @param manifests: Manifest files + @param scanner: The repoman.scanner.Scanner instance + @param broken_changelog_manifests: broken changelog manifests + ''' + pass + + @staticmethod + def clear_attic(headers): + '''Old CVS leftover + + @param headers: file headers''' + pass + + def update_index(self, mymanifests, myupdates): + '''Update the vcs's modified index if it is needed + + @param mymanifests: manifest files updated + @param myupdates: other files updated''' + pass + + def add_items(self, autoadd): + '''Add files to the vcs's modified or new index + + @param autoadd: the files to add to the vcs modified index''' + add_cmd = [self.vcs, "add"] + add_cmd += autoadd + if self.options.pretend: + portage.writemsg_stdout( + "(%s)\n" % " ".join(add_cmd), + noiselevel=-1) + else: + add_cmd = [_unicode_encode(arg) for arg in add_cmd] + retcode = subprocess.call(add_cmd) + if retcode != os.EX_OK: + logging.error( + "Exiting on %s error code: %s\n", self.vcs_settings.vcs, retcode) + sys.exit(retcode) + + + def commit(self, commitfiles, commitmessagefile): + '''Common generic commit function + + @param commitfiles: list of files to commit + @param commitmessagefile: file containing the commit message + @returns: The sub-command exit value or 0 + ''' + commit_cmd = [] + commit_cmd.append(self.vcs) + commit_cmd.extend(self.vcs_settings.vcs_global_opts) + commit_cmd.append("commit") + commit_cmd.extend(self.vcs_settings.vcs_local_opts) + commit_cmd.extend(["-F", commitmessagefile]) + commit_cmd.extend(f.lstrip("./") for f in commitfiles) + + if self.options.pretend: + print("(%s)" % (" ".join(commit_cmd),)) + return 0 + else: + retval = spawn(commit_cmd, env=self.repo_settings.commit_env) + return retval diff --git a/repoman/pym/repoman/modules/vcs/cvs/__init__.py b/repoman/pym/repoman/modules/vcs/cvs/__init__.py new file mode 100644 index 000000000..0b4587bc6 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/cvs/__init__.py @@ -0,0 +1,34 @@ +# Copyright 2014-2015 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +doc = """CVS (cvs) plug-in module for portage. +Performs variaous CVS actions and checks on repositories.""" +__doc__ = doc[:] + + +module_spec = { + 'name': 'cvs', + 'description': doc, + 'provides':{ + 'cvs-status': { + 'name': "cvs_status", + 'sourcefile': "status", + 'class': "Status", + 'description': doc, + 'functions': ['check', 'supports_gpg_sign', 'detect_conflicts'], + 'func_desc': { + }, + 'vcs_preserves_mtime': True, + 'needs_keyword_expansion': True, + }, + 'cvs-changes': { + 'name': "cvs_changes", + 'sourcefile': "changes", + 'class': "Changes", + 'description': doc, + 'functions': ['scan'], + 'func_desc': { + }, + }, + } +} diff --git a/repoman/pym/repoman/modules/vcs/cvs/changes.py b/repoman/pym/repoman/modules/vcs/cvs/changes.py new file mode 100644 index 000000000..c3d880bdb --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/cvs/changes.py @@ -0,0 +1,118 @@ +''' +CVS module Changes class submodule +''' + +import re +from itertools import chain + +from repoman._portage import portage +from repoman.modules.vcs.changes import ChangesBase +from repoman.modules.vcs.vcs import vcs_files_to_cps +from repoman._subprocess import repoman_getstatusoutput + +from portage import _encodings, _unicode_encode +from portage import cvstree, os +from portage.output import green +from portage.package.ebuild.digestgen import digestgen + + +class Changes(ChangesBase): + '''Class object to scan and hold the resultant data + for all changes to process. + ''' + + vcs = 'cvs' + + def __init__(self, options, repo_settings): + '''Class init + + @param options: the run time cli options + @param repo_settings: RepoSettings instance + ''' + super(Changes, self).__init__(options, repo_settings) + self._tree = None + + def _scan(self): + '''VCS type scan function, looks for all detectable changes''' + self._tree = portage.cvstree.getentries("./", recursive=1) + self.changed = cvstree.findchanged(self._tree, recursive=1, basedir="./") + self.new = cvstree.findnew(self._tree, recursive=1, basedir="./") + self.removed = cvstree.findremoved(self._tree, recursive=1, basedir="./") + bin_blob_pattern = re.compile("^-kb$") + self.no_expansion = set(portage.cvstree.findoption( + self._tree, bin_blob_pattern, recursive=1, basedir="./")) + + @property + def unadded(self): + '''VCS method of getting the unadded files in the repository''' + if self._unadded is not None: + return self._unadded + self._unadded = portage.cvstree.findunadded(self._tree, recursive=1, basedir="./") + return self._unadded + + @staticmethod + def clear_attic(headers): + '''Clear the attic (inactive files) + + @param headers: file headers + ''' + cvs_header_re = re.compile(br'^#\s*\$Header.*\$$') + attic_str = b'/Attic/' + attic_replace = b'/' + for x in headers: + f = open( + _unicode_encode(x, encoding=_encodings['fs'], errors='strict'), + mode='rb') + mylines = f.readlines() + f.close() + modified = False + for i, line in enumerate(mylines): + if cvs_header_re.match(line) is not None and \ + attic_str in line: + mylines[i] = line.replace(attic_str, attic_replace) + modified = True + if modified: + portage.util.write_atomic(x, b''.join(mylines), mode='wb') + + def thick_manifest(self, updates, headers, no_expansion, expansion): + '''Create a thick manifest + + @param updates: + @param headers: + @param no_expansion: + @param expansion: + ''' + headerstring = "'\$(Header|Id).*\$'" + + for _file in updates: + + # for CVS, no_expansion contains files that are excluded from expansion + if _file in no_expansion: + continue + + _out = repoman_getstatusoutput( + "egrep -q %s %s" % (headerstring, portage._shell_quote(_file))) + if _out[0] == 0: + headers.append(_file) + + print("%s have headers that will change." % green(str(len(headers)))) + print( + "* Files with headers will" + " cause the manifests to be changed and committed separately.") + + def digest_regen(self, updates, removed, manifests, scanner, broken_changelog_manifests): + '''Regenerate manifests + + @param updates: updated files + @param removed: removed files + @param manifests: Manifest files + @param scanner: The repoman.scanner.Scanner instance + @param broken_changelog_manifests: broken changelog manifests + ''' + if updates or removed: + for x in sorted(vcs_files_to_cps( + chain(updates, removed, manifests), + self.repo_settings.repodir, + scanner.repolevel, scanner.reposplit, scanner.categories)): + self.repoman_settings["O"] = os.path.join(self.repo_settings.repodir, x) + digestgen(mysettings=self.repoman_settings, myportdb=self.repo_settings.portdb) diff --git a/repoman/pym/repoman/modules/vcs/cvs/status.py b/repoman/pym/repoman/modules/vcs/cvs/status.py new file mode 100644 index 000000000..b936aa7d9 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/cvs/status.py @@ -0,0 +1,131 @@ +''' +CVS module Status class submodule +''' + +import logging +import subprocess +import sys + +from repoman._portage import portage +from portage import os +from portage.const import BASH_BINARY +from portage.output import red, green +from portage import _unicode_encode, _unicode_decode + + +class Status(object): + '''Performs status checks on the svn repository''' + + def __init__(self, qatracker, eadded): + '''Class init + + @param qatracker: QATracker class instance + @param eadded: list + ''' + self.qatracker = qatracker + self.eadded = eadded + + def check(self, checkdir, checkdir_relative, xpkg): + '''Perform the svn status check + + @param checkdir: string of the directory being checked + @param checkdir_relative: string of the relative directory being checked + @param xpkg: string of the package being checked + @returns: boolean + ''' + try: + myf = open(checkdir + "/CVS/Entries", "r") + myl = myf.readlines() + myf.close() + except IOError: + self.qatracker.add_error( + "CVS/Entries.IO_error", checkdir + "/CVS/Entries") + return True + for l in myl: + if l[0] != "/": + continue + splitl = l[1:].split("/") + if not len(splitl): + continue + if splitl[0][-7:] == ".ebuild": + self.eadded.append(splitl[0][:-7]) + return True + + @staticmethod + def detect_conflicts(options): + """Determine if the checkout has cvs conflicts. + + TODO(antarus): Also this should probably not call sys.exit() as + repoman is run on >1 packages and one failure should not cause + subsequent packages to fail. + + Returns: + None (calls sys.exit on fatal problems) + """ + + cmd = ("cvs -n up 2>/dev/null | " + "egrep '^[^\?] .*' | " + "egrep -v '^. .*/digest-[^/]+|^cvs server: .* -- ignored$'") + msg = ("Performing a %s with a little magic grep to check for updates." + % green("cvs -n up")) + + logging.info(msg) + # Use Popen instead of getstatusoutput(), in order to avoid + # unicode handling problems (see bug #310789). + args = [BASH_BINARY, "-c", cmd] + args = [_unicode_encode(x) for x in args] + proc = subprocess.Popen( + args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + out = _unicode_decode(proc.communicate()[0]) + proc.wait() + mylines = out.splitlines() + myupdates = [] + for line in mylines: + if not line: + continue + + # [ ] Unmodified (SVN) [U] Updates [P] Patches + # [M] Modified [A] Added [R] Removed / Replaced + # [D] Deleted + if line[0] not in " UPMARD": + # Stray Manifest is fine, we will readd it anyway. + if line[0] == '?' and line[1:].lstrip() == 'Manifest': + continue + logging.error(red( + "!!! Please fix the following issues reported " + "from cvs: %s" % green("(U,P,M,A,R,D are ok)"))) + logging.error(red( + "!!! Note: This is a pretend/no-modify pass...")) + logging.error(out) + sys.exit(1) + elif line[0] in "UP": + myupdates.append(line[2:]) + + if myupdates: + logging.info(green("Fetching trivial updates...")) + if options.pretend: + logging.info("(cvs update " + " ".join(myupdates) + ")") + retval = os.EX_OK + else: + retval = os.system("cvs update " + " ".join(myupdates)) + if retval != os.EX_OK: + logging.fatal("!!! cvs exited with an error. Terminating.") + sys.exit(retval) + return False + + @staticmethod + def supports_gpg_sign(): + '''Does this vcs system support gpg commit signatures + + @returns: Boolean + ''' + return False + + @staticmethod + def isVcsDir(dirname): + '''Does the directory belong to the vcs system + + @param dirname: string, directory name + @returns: Boolean + ''' + return dirname in ["CVS"] diff --git a/repoman/pym/repoman/modules/vcs/git/__init__.py b/repoman/pym/repoman/modules/vcs/git/__init__.py new file mode 100644 index 000000000..eecd4a1d0 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/git/__init__.py @@ -0,0 +1,34 @@ +# Copyright 2014-2015 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +doc = """Git (git) plug-in module for portage. +Performs variaous git actions and checks on repositories.""" +__doc__ = doc[:] + + +module_spec = { + 'name': 'git', + 'description': doc, + 'provides':{ + 'git-module': { + 'name': "git_status", + 'sourcefile': "status", + 'class': "Status", + 'description': doc, + 'functions': ['check', 'supports_gpg_sign', 'detect_conflicts'], + 'func_desc': { + }, + 'vcs_preserves_mtime': False, + 'needs_keyword_expansion': False, + }, + 'git-changes': { + 'name': "git_changes", + 'sourcefile': "changes", + 'class': "Changes", + 'description': doc, + 'functions': ['scan'], + 'func_desc': { + }, + }, + } +} diff --git a/repoman/pym/repoman/modules/vcs/git/changes.py b/repoman/pym/repoman/modules/vcs/git/changes.py new file mode 100644 index 000000000..7e9ac1eb5 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/git/changes.py @@ -0,0 +1,120 @@ +''' +Git module Changes class submodule +''' + +import logging +import sys + +from repoman.modules.vcs.changes import ChangesBase +from repoman._subprocess import repoman_popen +from repoman._portage import portage +from portage import os +from portage.package.ebuild.digestgen import digestgen +from portage.process import spawn +from portage.util import writemsg_level + + +class Changes(ChangesBase): + '''Class object to scan and hold the resultant data + for all changes to process. + ''' + + vcs = 'git' + + def __init__(self, options, repo_settings): + '''Class init + + @param options: the run time cli options + @param repo_settings: RepoSettings instance + ''' + super(Changes, self).__init__(options, repo_settings) + + def _scan(self): + '''VCS type scan function, looks for all detectable changes''' + with repoman_popen( + "git diff-index --name-only " + "--relative --diff-filter=M HEAD") as f: + changed = f.readlines() + self.changed = ["./" + elem[:-1] for elem in changed] + del changed + + with repoman_popen( + "git diff-index --name-only " + "--relative --diff-filter=A HEAD") as f: + new = f.readlines() + self.new = ["./" + elem[:-1] for elem in new] + del new + + with repoman_popen( + "git diff-index --name-only " + "--relative --diff-filter=D HEAD") as f: + removed = f.readlines() + self.removed = ["./" + elem[:-1] for elem in removed] + del removed + + @property + def unadded(self): + '''VCS method of getting the unadded files in the repository''' + if self._unadded is not None: + return self._unadded + # get list of files not under version control or missing + with repoman_popen("git ls-files --others") as f: + unadded = f.readlines() + self._unadded = ["./" + elem[:-1] for elem in unadded] + del unadded + return self._unadded + + def digest_regen(self, updates, removed, manifests, scanner, broken_changelog_manifests): + '''Regenerate manifests + + @param updates: updated files + @param removed: removed files + @param manifests: Manifest files + @param scanner: The repoman.scanner.Scanner instance + @param broken_changelog_manifests: broken changelog manifests + ''' + if broken_changelog_manifests: + for x in broken_changelog_manifests: + self.repoman_settings["O"] = os.path.join(self.repo_settings.repodir, x) + digestgen(mysettings=self.repoman_settings, myportdb=self.repo_settings.portdb) + + def update_index(self, mymanifests, myupdates): + '''Update the vcs's modified index if it is needed + + @param mymanifests: manifest files updated + @param myupdates: other files updated''' + # It's not safe to use the git commit -a option since there might + # be some modified files elsewhere in the working tree that the + # user doesn't want to commit. Therefore, call git update-index + # in order to ensure that the index is updated with the latest + # versions of all new and modified files in the relevant portion + # of the working tree. + myfiles = mymanifests + myupdates + myfiles.sort() + update_index_cmd = ["git", "update-index"] + update_index_cmd.extend(f.lstrip("./") for f in myfiles) + if self.options.pretend: + print("(%s)" % (" ".join(update_index_cmd),)) + else: + retval = spawn(update_index_cmd, env=os.environ) + if retval != os.EX_OK: + writemsg_level( + "!!! Exiting on %s (shell) " + "error code: %s\n" % (self.vcs_settings.vcs, retval), + level=logging.ERROR, noiselevel=-1) + sys.exit(retval) + + def commit(self, myfiles, commitmessagefile): + '''Git commit function + + @param commitfiles: list of files to commit + @param commitmessagefile: file containing the commit message + @returns: The sub-command exit value or 0 + ''' + retval = super(Changes, self).commit(myfiles, commitmessagefile) + if retval != os.EX_OK: + if self.repo_settings.repo_config.sign_commit and not self.vcs_settings.status.supports_gpg_sign(): + # Inform user that newer git is needed (bug #403323). + logging.error( + "Git >=1.7.9 is required for signed commits!") + return retval diff --git a/repoman/pym/repoman/modules/vcs/git/status.py b/repoman/pym/repoman/modules/vcs/git/status.py new file mode 100644 index 000000000..48a73bed3 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/git/status.py @@ -0,0 +1,79 @@ +''' +Git module Status class submodule +''' + +import re + +from repoman._portage import portage +from portage import os +from repoman._subprocess import repoman_popen, repoman_getstatusoutput + + +class Status(object): + '''Performs status checks on the svn repository''' + + def __init__(self, qatracker, eadded): + '''Class init + + @param qatracker: QATracker class instance + @param eadded: list + ''' + self.qatracker = qatracker + self.eadded = eadded + + def check(self, checkdir, checkdir_relative, xpkg): + '''Perform the svn status check + + @param checkdir: string of the directory being checked + @param checkdir_relative: string of the relative directory being checked + @param xpkg: string of the package being checked + @returns: boolean + ''' + myf = repoman_popen( + "git ls-files --others %s" % + (portage._shell_quote(checkdir_relative),)) + for l in myf: + if l[:-1][-7:] == ".ebuild": + self.qatracker.add_error( + "ebuild.notadded", + os.path.join(xpkg, os.path.basename(l[:-1]))) + myf.close() + return True + + @staticmethod + def detect_conflicts(options): + '''Are there any merge conflicts present in the VCS tracking system + + @param options: command line options + @returns: Boolean + ''' + return False + + @staticmethod + def supports_gpg_sign(): + '''Does this vcs system support gpg commit signatures + + @returns: Boolean + ''' + status, cmd_output = \ + repoman_getstatusoutput("git --version") + cmd_output = cmd_output.split() + if cmd_output: + version = re.match(r'^(\d+)\.(\d+)\.(\d+)', cmd_output[-1]) + if version is not None: + version = [int(x) for x in version.groups()] + if version[0] > 1 or \ + (version[0] == 1 and version[1] > 7) or \ + (version[0] == 1 and version[1] == 7 and version[2] >= 9): + return True + return False + + @staticmethod + def isVcsDir(dirname): + '''Does the directory belong to the vcs system + + @param dirname: string, directory name + @returns: Boolean + ''' + return dirname in [".git"] + diff --git a/repoman/pym/repoman/modules/vcs/hg/__init__.py b/repoman/pym/repoman/modules/vcs/hg/__init__.py new file mode 100644 index 000000000..2e39970f7 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/hg/__init__.py @@ -0,0 +1,34 @@ +# Copyright 2014-2015 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +doc = """Mercurial (hg) plug-in module for portage. +Performs variaous mercurial actions and checks on repositories.""" +__doc__ = doc[:] + + +module_spec = { + 'name': 'hg', + 'description': doc, + 'provides':{ + 'hg-module': { + 'name': "hg_status", + 'sourcefile': "status", + 'class': "Status", + 'description': doc, + 'functions': ['check', 'supports_gpg_sign', 'detect_conflicts'], + 'func_desc': { + }, + 'vcs_preserves_mtime': False, + 'needs_keyword_expansion': False, + }, + 'hg-changes': { + 'name': "hg_changes", + 'sourcefile': "changes", + 'class': "Changes", + 'description': doc, + 'functions': ['scan'], + 'func_desc': { + }, + }, + } +} diff --git a/repoman/pym/repoman/modules/vcs/hg/changes.py b/repoman/pym/repoman/modules/vcs/hg/changes.py new file mode 100644 index 000000000..867057545 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/hg/changes.py @@ -0,0 +1,105 @@ +''' +Mercurial module Changes class submodule +''' + +from repoman.modules.vcs.changes import ChangesBase +from repoman._subprocess import repoman_popen +from repoman._portage import portage +from portage import os +from portage.package.ebuild.digestgen import digestgen +from portage.process import spawn + + +class Changes(ChangesBase): + '''Class object to scan and hold the resultant data + for all changes to process. + ''' + + vcs = 'hg' + + def __init__(self, options, repo_settings): + '''Class init + + @param options: the run time cli options + @param repo_settings: RepoSettings instance + ''' + super(Changes, self).__init__(options, repo_settings) + + def _scan(self): + '''VCS type scan function, looks for all detectable changes''' + with repoman_popen("hg status --no-status --modified .") as f: + changed = f.readlines() + self.changed = ["./" + elem.rstrip() for elem in changed] + del changed + + with repoman_popen("hg status --no-status --added .") as f: + new = f.readlines() + self.new = ["./" + elem.rstrip() for elem in new] + del new + + with repoman_popen("hg status --no-status --removed .") as f: + removed = f.readlines() + self.removed = ["./" + elem.rstrip() for elem in removed] + del removed + + @property + def unadded(self): + '''VCS method of getting the unadded files in the repository''' + if self._unadded is not None: + return self._unadded + with repoman_popen("hg status --no-status --unknown .") as f: + unadded = f.readlines() + self._unadded = ["./" + elem.rstrip() for elem in unadded] + del unadded + return self._unadded + + @property + def deleted(self): + '''VCS method of getting the deleted files in the repository''' + if self._deleted is not None: + return self._deleted + # Mercurial doesn't handle manually deleted files as removed from + # the repository, so the user need to remove them before commit, + # using "hg remove [FILES]" + with repoman_popen("hg status --no-status --deleted .") as f: + deleted = f.readlines() + self._deleted = ["./" + elem.rstrip() for elem in deleted] + del deleted + return self._deleted + + + def digest_regen(self, updates, removed, manifests, scanner, broken_changelog_manifests): + '''Regenerate manifests + + @param updates: updated files + @param removed: removed files + @param manifests: Manifest files + @param scanner: The repoman.scanner.Scanner instance + @param broken_changelog_manifests: broken changelog manifests + ''' + if broken_changelog_manifests: + for x in broken_changelog_manifests: + self.repoman_settings["O"] = os.path.join(self.repo_settings.repodir, x) + digestgen(mysettings=self.repoman_settings, myportdb=self.repo_settings.portdb) + + def commit(self, myfiles, commitmessagefile): + '''Hg commit function + + @param commitfiles: list of files to commit + @param commitmessagefile: file containing the commit message + @returns: The sub-command exit value or 0 + ''' + commit_cmd = [] + commit_cmd.append(self.vcs) + commit_cmd.extend(self.vcs_settings.vcs_global_opts) + commit_cmd.append("commit") + commit_cmd.extend(self.vcs_settings.vcs_local_opts) + commit_cmd.extend(["--logfile", commitmessagefile]) + commit_cmd.extend(myfiles) + + if self.options.pretend: + print("(%s)" % (" ".join(commit_cmd),)) + return 0 + else: + retval = spawn(commit_cmd, env=self.repo_settings.commit_env) + return retval diff --git a/repoman/pym/repoman/modules/vcs/hg/status.py b/repoman/pym/repoman/modules/vcs/hg/status.py new file mode 100644 index 000000000..8443554f5 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/hg/status.py @@ -0,0 +1,65 @@ +''' +Mercurial module Status class submodule +''' + +from repoman._portage import portage +from portage import os +from repoman._subprocess import repoman_popen + + +class Status(object): + '''Performs status checks on the svn repository''' + + def __init__(self, qatracker, eadded): + '''Class init + + @param qatracker: QATracker class instance + @param eadded: list + ''' + self.qatracker = qatracker + self.eadded = eadded + + def check(self, checkdir, checkdir_relative, xpkg): + '''Perform the svn status check + + @param checkdir: string of the directory being checked + @param checkdir_relative: string of the relative directory being checked + @param xpkg: string of the package being checked + @returns: boolean + ''' + myf = repoman_popen( + "hg status --no-status --unknown %s" % + (portage._shell_quote(checkdir_relative),)) + for l in myf: + if l[:-1][-7:] == ".ebuild": + self.qatracker.add_error( + "ebuild.notadded", + os.path.join(xpkg, os.path.basename(l[:-1]))) + myf.close() + return True + + @staticmethod + def detect_conflicts(options): + '''Are there any merge conflicts present in the VCS tracking system + + @param options: command line options + @returns: Boolean + ''' + return False + + @staticmethod + def supports_gpg_sign(): + '''Does this vcs system support gpg commit signatures + + @returns: Boolean + ''' + return False + + @staticmethod + def isVcsDir(dirname): + '''Does the directory belong to the vcs system + + @param dirname: string, directory name + @returns: Boolean + ''' + return dirname in [".hg"] diff --git a/repoman/pym/repoman/modules/vcs/settings.py b/repoman/pym/repoman/modules/vcs/settings.py new file mode 100644 index 000000000..a8e91dd27 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/settings.py @@ -0,0 +1,108 @@ +''' +Repoman VCSSettings modules +''' + +from __future__ import print_function, unicode_literals + +import logging +import sys + +from portage.output import red +from repoman.modules.vcs import module_controller, module_names +from repoman.modules.vcs.vcs import FindVCS +from repoman.qa_tracker import QATracker + + +class VCSSettings(object): + '''Holds various VCS settings''' + + def __init__(self, options=None, repoman_settings=None, repo_settings=None): + '''Class init function + + @param options: the run time cli options + @param repoman_settings: portage.config settings instance + @param repo_settings: RepoSettings instance + ''' + self.options = options + self.repoman_settings = repoman_settings + self.repo_settings = repo_settings + if options.vcs: + if options.vcs in module_names: + self.vcs = options.vcs + else: + self.vcs = None + else: + vcses = FindVCS() + if len(vcses) > 1: + print(red( + '*** Ambiguous workdir -- more than one VCS found' + ' at the same depth: %s.' % ', '.join(vcses))) + print(red( + '*** Please either clean up your workdir' + ' or specify --vcs option.')) + sys.exit(1) + elif vcses: + self.vcs = vcses[0] + else: + self.vcs = None + + if options.if_modified == "y" and self.vcs is None: + logging.info( + "Not in a version controlled repository; " + "disabling --if-modified.") + options.if_modified = "n" + + # initialize our instance placeholders + self._status = None + self._changes = None + # get our vcs plugin controller and available module names + self.module_controller = module_controller + self.module_names = module_names + + # Disable copyright/mtime check if vcs does not preserve mtime (bug #324075). + if str(self.vcs) in self.module_controller.parents: + self.vcs_preserves_mtime = module_controller.modules[ + "%s_status" % self.vcs]['vcs_preserves_mtime'] + else: + self.vcs_preserves_mtime = False + logging.error("VCSSettings: Unknown VCS type: %s", self.vcs) + logging.error("Available modules: %s", module_controller.parents) + + self.needs_keyword_expansion = module_controller.modules[ + "%s_status" % self.vcs]['needs_keyword_expansion'] + self.vcs_local_opts = repoman_settings.get( + "REPOMAN_VCS_LOCAL_OPTS", "").split() + self.vcs_global_opts = repoman_settings.get( + "REPOMAN_VCS_GLOBAL_OPTS") + if self.vcs_global_opts is None: + if self.vcs in ('cvs', 'svn'): + self.vcs_global_opts = "-q" + else: + self.vcs_global_opts = "" + self.vcs_global_opts = self.vcs_global_opts.split() + + if options.mode == 'commit' and not options.pretend and not self.vcs: + logging.info( + "Not in a version controlled repository; " + "enabling pretend mode.") + options.pretend = True + self.qatracker = QATracker() + self.eadded = [] + + @property + def status(self): + '''Initializes and returns the class instance + of the vcs's Status class''' + if not self._status: + status = self.module_controller.get_class('%s_status' % self.vcs) + self._status = status(self.qatracker, self.eadded) + return self._status + + @property + def changes(self): + '''Initializes and returns the class instance + of the vcs's Changes class''' + if not self._changes: + changes = self.module_controller.get_class('%s_changes' % self.vcs) + self._changes = changes(self.options, self.repo_settings) + return self._changes diff --git a/repoman/pym/repoman/modules/vcs/svn/__init__.py b/repoman/pym/repoman/modules/vcs/svn/__init__.py new file mode 100644 index 000000000..6bb0b9af4 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/svn/__init__.py @@ -0,0 +1,34 @@ +# Copyright 2014-2015 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +doc = """Subversion (svn) plug-in module for portage. +Performs variaous subversion actions and checks on repositories.""" +__doc__ = doc[:] + + +module_spec = { + 'name': 'svn', + 'description': doc, + 'provides':{ + 'svn-module': { + 'name': "svn_status", + 'sourcefile': "status", + 'class': "Status", + 'description': doc, + 'functions': ['check', 'supports_gpg_sign', 'detect_conflicts'], + 'func_desc': { + }, + 'vcs_preserves_mtime': False, + 'needs_keyword_expansion': True, + }, + 'svn-changes': { + 'name': "svn_changes", + 'sourcefile': "changes", + 'class': "Changes", + 'description': doc, + 'functions': ['scan'], + 'func_desc': { + }, + }, + } +} diff --git a/repoman/pym/repoman/modules/vcs/svn/changes.py b/repoman/pym/repoman/modules/vcs/svn/changes.py new file mode 100644 index 000000000..d83c7c45f --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/svn/changes.py @@ -0,0 +1,141 @@ +''' +Subversion module Changes class submodule +''' + +from itertools import chain + +from repoman.modules.vcs.changes import ChangesBase +from repoman._subprocess import repoman_popen +from repoman._subprocess import repoman_getstatusoutput +from repoman.modules.vcs.vcs import vcs_files_to_cps +from repoman._portage import portage +from portage import os +from portage.output import green +from portage.package.ebuild.digestgen import digestgen + + +class Changes(ChangesBase): + '''Class object to scan and hold the resultant data + for all changes to process. + ''' + + vcs = 'svn' + + def __init__(self, options, repo_settings): + '''Class init + + @param options: the run time cli options + @param repo_settings: RepoSettings instance + ''' + super(Changes, self).__init__(options, repo_settings) + + def _scan(self): + '''VCS type scan function, looks for all detectable changes''' + with repoman_popen("svn status") as f: + svnstatus = f.readlines() + self.changed = [ + "./" + elem.split()[-1:][0] + for elem in svnstatus + if elem and elem[:1] in "MR"] + self.new = [ + "./" + elem.split()[-1:][0] + for elem in svnstatus + if elem.startswith("A")] + self.removed = [ + "./" + elem.split()[-1:][0] + for elem in svnstatus + if elem.startswith("D")] + + @property + def expansion(self): + '''VCS method of getting the expanded keywords in the repository''' + if self._expansion is not None: + return self._expansion + # Subversion expands keywords specified in svn:keywords properties. + with repoman_popen("svn propget -R svn:keywords") as f: + props = f.readlines() + self._expansion = dict( + ("./" + prop.split(" - ")[0], prop.split(" - ")[1].split()) + for prop in props if " - " in prop) + del props + return self._expansion + + @property + def unadded(self): + '''VCS method of getting the unadded files in the repository''' + if self._unadded is not None: + return self._unadded + with repoman_popen("svn status --no-ignore") as f: + svnstatus = f.readlines() + self._unadded = [ + "./" + elem.rstrip().split()[1] + for elem in svnstatus + if elem.startswith("?") or elem.startswith("I")] + del svnstatus + return self._unadded + + def thick_manifest(self, updates, headers, no_expansion, expansion): + '''Create a thick manifest + + @param updates: + @param headers: + @param no_expansion: + @param expansion: + ''' + svn_keywords = dict((k.lower(), k) for k in [ + "Rev", + "Revision", + "LastChangedRevision", + "Date", + "LastChangedDate", + "Author", + "LastChangedBy", + "URL", + "HeadURL", + "Id", + "Header", + ]) + + for _file in updates: + # for SVN, expansion contains files that are included in expansion + if _file not in expansion: + continue + + # Subversion keywords are case-insensitive + # in svn:keywords properties, + # but case-sensitive in contents of files. + enabled_keywords = [] + for k in expansion[_file]: + keyword = svn_keywords.get(k.lower()) + if keyword is not None: + enabled_keywords.append(keyword) + + headerstring = "'\$(%s).*\$'" % "|".join(enabled_keywords) + + _out = repoman_getstatusoutput( + "egrep -q %s %s" % (headerstring, portage._shell_quote(_file))) + if _out[0] == 0: + headers.append(_file) + + print("%s have headers that will change." % green(str(len(headers)))) + print( + "* Files with headers will" + " cause the manifests to be changed and committed separately.") + + def digest_regen(self, updates, removed, manifests, scanner, broken_changelog_manifests): + '''Regenerate manifests + + @param updates: updated files + @param removed: removed files + @param manifests: Manifest files + @param scanner: The repoman.scanner.Scanner instance + @param broken_changelog_manifests: broken changelog manifests + ''' + if updates or removed: + for x in sorted(vcs_files_to_cps( + chain(updates, removed, manifests), + scanner.repolevel, scanner.reposplit, scanner.categories)): + self.repoman_settings["O"] = os.path.join(self.repo_settings.repodir, x) + digestgen(mysettings=self.repoman_settings, myportdb=self.repo_settings.portdb) + + diff --git a/repoman/pym/repoman/modules/vcs/svn/status.py b/repoman/pym/repoman/modules/vcs/svn/status.py new file mode 100644 index 000000000..6575fe0b0 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/svn/status.py @@ -0,0 +1,150 @@ +''' +Subversion module Status class submodule +''' + +import logging +import subprocess +import sys + +from repoman._portage import portage +from portage import os +from portage.const import BASH_BINARY +from portage.output import red, green +from portage import _unicode_encode, _unicode_decode + +from repoman._subprocess import repoman_popen + + +class Status(object): + '''Performs status checks on the svn repository''' + + def __init__(self, qatracker, eadded): + '''Class init + + @param qatracker: QATracker class instance + @param eadded: list + ''' + self.qatracker = qatracker + self.eadded = eadded + + def check(self, checkdir, checkdir_relative, xpkg): + '''Perform the svn status check + + @param checkdir: string of the directory being checked + @param checkdir_relative: string of the relative directory being checked + @param xpkg: string of the package being checked + @returns: boolean + ''' + try: + myf = repoman_popen( + "svn status --depth=files --verbose " + + portage._shell_quote(checkdir)) + myl = myf.readlines() + myf.close() + except IOError: + raise + for l in myl: + if l[:1] == "?": + continue + if l[:7] == ' >': + # tree conflict, new in subversion 1.6 + continue + l = l.split()[-1] + if l[-7:] == ".ebuild": + self.eadded.append(os.path.basename(l[:-7])) + try: + myf = repoman_popen( + "svn status " + + portage._shell_quote(checkdir)) + myl = myf.readlines() + myf.close() + except IOError: + raise + for l in myl: + if l[0] == "A": + l = l.rstrip().split(' ')[-1] + if l[-7:] == ".ebuild": + self.eadded.append(os.path.basename(l[:-7])) + return True + + @staticmethod + def detect_conflicts(options): + """Determine if the checkout has problems like cvs conflicts. + + If you want more vcs support here just keep adding if blocks... + This could be better. + + TODO(antarus): Also this should probably not call sys.exit() as + repoman is run on >1 packages and one failure should not cause + subsequent packages to fail. + + Args: + vcs - A string identifying the version control system in use + Returns: boolean + (calls sys.exit on fatal problems) + """ + + cmd = "svn status -u 2>&1 | egrep -v '^. +.*/digest-[^/]+' | head -n-1" + msg = ("Performing a %s with a little magic grep to check for updates." + % green("svn status -u")) + + logging.info(msg) + # Use Popen instead of getstatusoutput(), in order to avoid + # unicode handling problems (see bug #310789). + args = [BASH_BINARY, "-c", cmd] + args = [_unicode_encode(x) for x in args] + proc = subprocess.Popen( + args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + out = _unicode_decode(proc.communicate()[0]) + proc.wait() + mylines = out.splitlines() + myupdates = [] + for line in mylines: + if not line: + continue + + # [ ] Unmodified (SVN) [U] Updates [P] Patches + # [M] Modified [A] Added [R] Removed / Replaced + # [D] Deleted + if line[0] not in " UPMARD": + # Stray Manifest is fine, we will readd it anyway. + if line[0] == '?' and line[1:].lstrip() == 'Manifest': + continue + logging.error(red( + "!!! Please fix the following issues reported " + "from cvs: %s" % green("(U,P,M,A,R,D are ok)"))) + logging.error(red( + "!!! Note: This is a pretend/no-modify pass...")) + logging.error(out) + sys.exit(1) + elif line[8] == '*': + myupdates.append(line[9:].lstrip(" 1234567890")) + + if myupdates: + logging.info(green("Fetching trivial updates...")) + if options.pretend: + logging.info("(svn update " + " ".join(myupdates) + ")") + retval = os.EX_OK + else: + retval = os.system("svn update " + " ".join(myupdates)) + if retval != os.EX_OK: + logging.fatal("!!! svn exited with an error. Terminating.") + sys.exit(retval) + return False + + @staticmethod + def supports_gpg_sign(): + '''Does this vcs system support gpg commit signatures + + @returns: Boolean + ''' + return False + + @staticmethod + def isVcsDir(dirname): + '''Does the directory belong to the vcs system + + @param dirname: string, directory name + @returns: Boolean + ''' + return dirname in [".svn"] diff --git a/repoman/pym/repoman/modules/vcs/vcs.py b/repoman/pym/repoman/modules/vcs/vcs.py new file mode 100644 index 000000000..e9d45d4c8 --- /dev/null +++ b/repoman/pym/repoman/modules/vcs/vcs.py @@ -0,0 +1,149 @@ +# -*- coding:utf-8 -*- + +from __future__ import print_function, unicode_literals + +import collections +import logging +from itertools import chain + +from portage import os + + +_vcs_type = collections.namedtuple('_vcs_type', 'name dir_name') + +_FindVCS_data = ( + _vcs_type( + name='git', + dir_name='.git' + ), + _vcs_type( + name='bzr', + dir_name='.bzr' + ), + _vcs_type( + name='hg', + dir_name='.hg' + ), + _vcs_type( + name='svn', + dir_name='.svn' + ) +) + + +def FindVCS(cwd=None): + """ + Try to figure out in what VCS' working tree we are. + + @param cwd: working directory (default is os.getcwd()) + @type cwd: str + @return: list of strings describing the discovered vcs types + @rtype: list + """ + + if cwd is None: + cwd = os.getcwd() + + outvcs = [] + + def seek(depth=None): + '''Seek for VCSes that have a top-level data directory only. + + @param depth: integer + @returns: list of strings + ''' + retvcs = [] + pathprep = cwd + + while depth is None or depth > 0: + for vcs_type in _FindVCS_data: + vcs_dir = os.path.join(pathprep, vcs_type.dir_name) + if os.path.isdir(vcs_dir): + logging.debug( + 'FindVCS: found %(name)s dir: %(vcs_dir)s' % { + 'name': vcs_type.name, + 'vcs_dir': os.path.abspath(vcs_dir)}) + retvcs.append(vcs_type.name) + + if retvcs: + break + pathprep = os.path.join(pathprep, '..') + if os.path.realpath(pathprep).strip('/') == '': + break + if depth is not None: + depth = depth - 1 + + return retvcs + + # Level zero VCS-es. + if os.path.isdir(os.path.join(cwd, 'CVS')): + outvcs.append('cvs') + if os.path.isdir('.svn'): # <1.7 + outvcs.append(os.path.join(cwd, 'svn')) + + # If we already found one of 'level zeros', just take a quick look + # at the current directory. Otherwise, seek parents till we get + # something or reach root. + if outvcs: + outvcs.extend(seek(1)) + else: + outvcs = seek() + + if len(outvcs) > 1: + # eliminate duplicates, like for svn in bug #391199 + outvcs = list(set(outvcs)) + + return outvcs + + +def vcs_files_to_cps(vcs_file_iter, repodir, repolevel, reposplit, categories): + """ + Iterate over the given modified file paths returned from the vcs, + and return a frozenset containing category/pn strings for each + modified package. + """ + + modified_cps = [] + + if repolevel == 3: + if reposplit[-2] in categories and \ + next(vcs_file_iter, None) is not None: + modified_cps.append("/".join(reposplit[-2:])) + + elif repolevel == 2: + category = reposplit[-1] + if category in categories: + for filename in vcs_file_iter: + f_split = filename.split(os.sep) + # ['.', pn, ...] + if len(f_split) > 2: + modified_cps.append(category + "/" + f_split[1]) + + else: + # repolevel == 1 + for filename in vcs_file_iter: + f_split = filename.split(os.sep) + # ['.', category, pn, ...] + if len(f_split) > 3 and f_split[1] in categories: + modified_cps.append("/".join(f_split[1:3])) + + # Exclude packages that have been removed, since calling + # code assumes that the packages exist. + return frozenset(x for x in frozenset(modified_cps) + if os.path.exists(os.path.join(repodir, x))) + + +def vcs_new_changed(relative_path, mychanged, mynew): + '''Check if any vcs tracked file have been modified + + @param relative_path: + @param mychanged: iterable of changed files + @param mynew: iterable of new files + @returns boolean + ''' + for x in chain(mychanged, mynew): + if x == relative_path: + return True + return False + + |