aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'repoman/pym/repoman/modules/vcs')
-rw-r--r--repoman/pym/repoman/modules/vcs/None/__init__.py34
-rw-r--r--repoman/pym/repoman/modules/vcs/None/changes.py50
-rw-r--r--repoman/pym/repoman/modules/vcs/None/status.py53
-rw-r--r--repoman/pym/repoman/modules/vcs/__init__.py14
-rw-r--r--repoman/pym/repoman/modules/vcs/bzr/__init__.py34
-rw-r--r--repoman/pym/repoman/modules/vcs/bzr/changes.py68
-rw-r--r--repoman/pym/repoman/modules/vcs/bzr/status.py70
-rw-r--r--repoman/pym/repoman/modules/vcs/changes.py169
-rw-r--r--repoman/pym/repoman/modules/vcs/cvs/__init__.py34
-rw-r--r--repoman/pym/repoman/modules/vcs/cvs/changes.py118
-rw-r--r--repoman/pym/repoman/modules/vcs/cvs/status.py131
-rw-r--r--repoman/pym/repoman/modules/vcs/git/__init__.py34
-rw-r--r--repoman/pym/repoman/modules/vcs/git/changes.py120
-rw-r--r--repoman/pym/repoman/modules/vcs/git/status.py79
-rw-r--r--repoman/pym/repoman/modules/vcs/hg/__init__.py34
-rw-r--r--repoman/pym/repoman/modules/vcs/hg/changes.py105
-rw-r--r--repoman/pym/repoman/modules/vcs/hg/status.py65
-rw-r--r--repoman/pym/repoman/modules/vcs/settings.py108
-rw-r--r--repoman/pym/repoman/modules/vcs/svn/__init__.py34
-rw-r--r--repoman/pym/repoman/modules/vcs/svn/changes.py141
-rw-r--r--repoman/pym/repoman/modules/vcs/svn/status.py150
-rw-r--r--repoman/pym/repoman/modules/vcs/vcs.py149
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
+
+