File osc-plugin-collab-0.104+30.obscpio of Package osc-plugin-collab
07070100000000000081A40000000000000000000000016548EB8C00000010000000000000000000000000000000000000002600000000osc-plugin-collab-0.104+30/.gitignore*.pyc
*.sw[nop]
07070100000001000081A40000000000000000000000016548EB8C0000058B000000000000000000000000000000000000002000000000osc-plugin-collab-0.104+30/NEWSVersion 0.90
============
This is the first version of the osc collab plugin. It has been renamed from
osc gnome. Here are a list of changes since the last release of osc gnome.
+ Features:
- Rename to osc collab and do not refer to anything GNOME specific anywhere
- Support delta in non-link packages
- Remove potential trailing slash from packages passed as args for
convenience when used with autocompletion
- Make the config options work per apiurl
- Make it possible to use more than one repo at the same time
- Display against which repo the build is done
- Make setup/update branch from the devel project
- Take into account the version in devel project for todo/update
- Use openSUSE:Factory by default instead of GNOME:Factory
- Autodetect default repository for builds
- Add --nodevelproject option for the relevant commands
- Add --version command
+ Fixes:
- Improve upstream tarball basename detection in when the basename of the
upstream tarball is not in the URL, but in the query fields
- Fix warning about tag in Source always appearing
- Do not crash with osc from trunk
- Better handling of update when package is already updated
- Fix listreserved to not list reservations from all projects
- Substitute macros in %define lines too
- Remove old cache files
- Fix parsing of empty list options in ~/.oscrc
- Improve help message
- Code cleanups
07070100000002000081A40000000000000000000000016548EB8C0000009D000000000000000000000000000000000000002200000000osc-plugin-collab-0.104+30/READMEThe osc collab plugin aims to make it easier to collaborate within the
Build Service.
See https://en.opensuse.org/openSUSE:Osc_Collab for more information.
07070100000003000081A40000000000000000000000016548EB8C000004BE000000000000000000000000000000000000002000000000osc-plugin-collab-0.104+30/TODO+ Make --xs the default for 'osc gnome t'
For t: maybe check the version in the home project?
+ add 'osc gnome validate/check': make sure that committing is fine
+ osc collab todo:
- list build failures (with the status api)
+ 'osc gnome update':
- print information about rpmlint of this package
- prefill the .changes with "Updated translations"?
- check integrity with md5sum/sha1sum
- integrate with spec-cleaner
+ 'osc gnome todoadmin':
- use get_obs_build_results
- handle the case where there's already a submission to oS:F but the
submission is not from the latest revision of the package in G:F
+ 'osc gnome forward':
- cancel old requests (automatically? ask?)
- use 'osc meta prj GNOME:Factory' to see how to check for permissions.
+ 'osc gnome build/buildsubmit':
- if the package has no explicit enable for architecture, we don't look
at the project metadata and enable the build in the package. We should
look at the project metadata.
+ 'osc gnome buildsubmit':
- do nothing with --forward option when the user has not enough privilege to
forward
+ Kill usage of http_GET, http_PUT (?)
+ Output an error if the project passed with --project does not exist (?)
07070100000004000081ED0000000000000000000000016548EB8C000011BA000000000000000000000000000000000000003100000000osc-plugin-collab-0.104+30/build-compare-analyze#!/usr/bin/env python3
# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2009, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sys
import optparse
import re
def compare_build_output(filepath):
compare_re = re.compile('^compare /.build.oldpackages/\S+-([^-]+)-(\d+).\d+.\S+.rpm /usr/src/packages/S?RPMS/\S+-([^-]+)-(\d+).\d+.\S+.rpm$')
file = open(filepath)
# read everything until we see the build-compare report header
while True:
line = file.readline()
if line == '':
break
# this is not the beginning of the header
if line[:-1] != '... comparing built packages with the former built':
continue
# we've found the beginning of the header, so let's read the whole
# header
line = file.readline()
if line[:-1] != '/usr/lib/build/rpm-check.sh':
# oops, this is not what we expected, so go back.
file.seek(-len(line), os.SEEK_CUR)
break
different = False
version_different = False
output = ''
# now let's analyze the real important lines
while True:
line = file.readline()
if line == '':
break
# this is the end of build-compare
if line[:-1] in ['... build is finished', 'compare validated built as indentical !']:
break
output = output + line
match = compare_re.match(line[:-1])
if match:
oldver = match.group(1)
oldrel = match.group(2)
newver = match.group(3)
newrel = match.group(4)
if (oldver != newver) or (oldrel != newrel):
version_different = True
else:
# this means we have output showing the difference
different = True
file.close()
return (version_different, different, output)
def main(args):
parser = optparse.OptionParser()
parser.add_option("-f", "--file", dest="file",
help="build log file to read")
parser.add_option("-o", "--output", dest="output",
default=False, help="output file to create if build-compare detected a non-version difference")
(options, args) = parser.parse_args()
if not options.file:
print('No build log file.', file=sys.stderr)
sys.exit(1)
if not os.path.exists(options.file):
print('Build log file "%s" does not exist.' % options.file, file=sys.stderr)
sys.exit(1)
if options.output and os.path.exists(options.output):
os.unlink(options.output)
(version_different, different, output) = compare_build_output(options.file)
if not version_different and different:
if options.output:
out = open(options.output, 'w')
out.write(output[:-1])
else:
print(output[:-1])
if __name__ == '__main__':
try:
main(sys.argv)
except KeyboardInterrupt:
pass
07070100000005000081A40000000000000000000000016548EB8C000244F4000000000000000000000000000000000000002900000000osc-plugin-collab-0.104+30/osc-collab.py# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2008-2009, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
from __future__ import print_function
import difflib
import locale
import re
import select
import shutil
import subprocess
import tarfile
import tempfile
import time
import urllib
from osc import core
from osc import conf
try:
import configparser
from http.client import BadStatusLine
from urllib.error import HTTPError
from urllib.parse import quote, urlparse
from urllib.request import urlopen
except ImportError:
# python 2.x
import ConfigParser
from httplib import BadStatusLine
from urllib import quote
from urllib2 import HTTPError, urlopen
from urlparse import urlparse
try:
import rpm
have_rpm = True
except ImportError:
have_rpm = False
from osc import cmdln
from osc import conf
OSC_COLLAB_VERSION = '0.104'
# This is a hack to have osc ignore the file we create in a package directory.
_osc_collab_helper_prefixes = [ 'osc-collab.', 'osc-gnome.' ]
_osc_collab_helpers = []
for suffix in [ 'NEWS', 'ChangeLog', 'configure', 'meson', 'meson_options' ]:
for prefix in _osc_collab_helper_prefixes:
_osc_collab_helpers.append(prefix + suffix)
for helper in _osc_collab_helpers:
conf.DEFAULTS['exclude_glob'] += ' %s' % helper
_osc_collab_alias = 'collab'
_osc_collab_config_parser = None
_osc_collab_osc_conffile = None
def filedir_to_pac(f, progress_obj=None):
"""Takes a working copy path, or a path to a file inside a working copy,
and returns a Package object instance
If the argument was a filename, add it onto the "todo" list of the Package """
if os.path.isdir(f):
wd = f
p = Package(wd, progress_obj=progress_obj)
else:
wd = os.path.dirname(f) or os.curdir
p = Package(wd, progress_obj=progress_obj)
p.todo = [ os.path.basename(f) ]
return p
class OscCollabError(Exception):
def __init__(self, value):
self.msg = value
def __str__(self):
return repr(self.msg)
class OscCollabWebError(OscCollabError):
pass
class OscCollabDownloadError(OscCollabError):
pass
class OscCollabDiffError(OscCollabError):
pass
class OscCollabCompressError(OscCollabError):
pass
def _collab_exception_print(e, message = ''):
if message == None:
message = ''
if hasattr(e, 'msg'):
print(message + e.msg, file=sys.stderr)
elif str(e) != '':
print(message + str(e), file=sys.stderr)
else:
print(message + e.__class__.__name__, file=sys.stderr)
#######################################################################
class OscCollabReservation:
project = None
package = None
user = None
def __init__(self, project = None, package = None, user = None, node = None):
if node is None:
self.project = project
self.package = package
self.user = user
else:
self.project = node.get('project')
self.package = node.get('package')
self.user = node.get('user')
def __len__(self):
return 3
def __getitem__(self, key):
if not type(key) == int:
raise TypeError
if key == 0:
return self.project
elif key == 1:
return self.package
elif key == 2:
return self.user
else:
raise IndexError
def is_relevant(self, projects, package):
if self.project not in projects:
return False
if self.package != package:
return False
return True
#######################################################################
class OscCollabComment:
project = None
package = None
user = None
comment = None
firstline = None
def __init__(self, project = None, package = None, date = None, user = None, comment = None, node = None):
if node is None:
self.project = project
self.package = package
self.date = date
self.user = user
self.comment = comment
else:
self.project = node.get('project')
self.package = node.get('package')
self.date = node.get('date')
self.user = node.get('user')
self.comment = node.text
if self.comment is None:
self.firstline = None
else:
lines = self.comment.split('\n')
self.firstline = lines[0]
if len(lines) > 1:
self.firstline += ' [...]'
def __len__(self):
return 4
def __getitem__(self, key):
if not type(key) == int:
raise TypeError
if key == 0:
return self.project
elif key == 1:
return self.package
elif key == 2:
return self.user
elif key == 3:
return self.firstline
else:
raise IndexError
def is_relevant(self, projects, package):
if self.project not in projects:
return False
if self.package != package:
return False
return True
def indent(self, spaces = ' '):
lines = self.comment.split('\n')
lines = [ spaces + line for line in lines ]
return '\n'.join(lines)
#######################################################################
class OscCollabRequest():
req_id = -1
type = None
source_project = None
source_package = None
source_rev = None
dest_project = None
dest_package = None
state = None
by = None
at = None
description = None
def __init__(self, node):
self.req_id = int(node.get('id'))
# we only care about the first action here
action = node.find('action')
if action is None:
action = node.find('submit') # for old style requests
type = action.get('type', 'submit')
subnode = action.find('source')
if subnode is not None:
self.source_project = subnode.get('project')
self.source_package = subnode.get('package')
self.source_rev = subnode.get('rev')
subnode = action.find('target')
if subnode is not None:
self.target_project = subnode.get('project')
self.target_package = subnode.get('package')
subnode = node.find('state')
if subnode is not None:
self.state = subnode.get('name')
self.by = subnode.get('who')
self.at = subnode.get('when')
subnode = node.find('description')
if subnode is not None:
self.description = subnode.text
#######################################################################
class OscCollabProject(dict):
def __init__(self, node):
self.name = node.get('name')
self.parent = node.get('parent')
self.ignore_upstream = node.get('ignore_upstream') == 'true'
self.missing_packages = []
def strip_internal_links(self):
to_rm = []
for package in self.values():
if package.parent_project == self.name:
to_rm.append(package.name)
for name in to_rm:
del self[name]
def is_toplevel(self):
return self.parent in [ None, '' ]
def __eq__(self, other):
return self.name == other.name
def __ne__(self, other):
return not self.__eq__(other)
def __lt__(self, other):
return self.name < other.name
def __le__(self, other):
return self.__eq__(other) or self.__lt__(other)
def __gt__(self, other):
return other.__lt__(self)
def __ge__(self, other):
return other.__eq__(self) or other.__lt__(self)
#######################################################################
class OscCollabPackage:
def __init__(self, node, project):
self.name = None
self.version = None
self.parent_project = None
self.parent_package = None
self.parent_version = None
self.devel_project = None
self.devel_package = None
self.devel_version = None
self.upstream_version = None
self.upstream_url = None
self.is_link = False
self.has_delta = False
self.error = None
self.error_details = None
self.project = project
if node is not None:
self.name = node.get('name')
parent = node.find('parent')
if parent is not None:
self.parent_project = parent.get('project')
self.parent_package = parent.get('package')
devel = node.find('devel')
if devel is not None:
self.devel_project = devel.get('project')
self.devel_package = devel.get('package')
version = node.find('version')
if version is not None:
self.version = version.get('current')
if not project or not project.ignore_upstream:
self.upstream_version = version.get('upstream')
self.parent_version = version.get('parent')
self.devel_version = version.get('devel')
if not project or not project.ignore_upstream:
upstream = node.find('upstream')
if upstream is not None:
url = upstream.find('url')
if url is not None:
self.upstream_url = url.text
link = node.find('link')
if link is not None:
self.is_link = True
if link.get('delta') == 'true':
self.has_delta = True
delta = node.find('delta')
if delta is not None:
self.has_delta = True
error = node.find('error')
if error is not None:
self.error = error.get('type')
self.error_details = error.text
# Reconstruct some data that we can deduce from the XML
if project is not None and self.is_link and not self.parent_project:
self.parent_project = project.parent
if self.parent_project and not self.parent_package:
self.parent_package = self.name
if self.devel_project and not self.devel_package:
self.devel_package = self.name
def _compare_versions_a_gt_b(self, a, b):
if have_rpm:
# We're not really interested in the epoch or release parts of the
# complete version because they're not relevant when comparing to
# upstream version
return rpm.labelCompare((None, a, '1'), (None, b, '1')) > 0
split_a = a.split('.')
split_b = b.split('.')
# the two versions don't have the same format; we don't know how to
# handle this
if len(split_a) != len(split_b):
return a > b
for i in range(len(split_a)):
try:
int_a = int(split_a[i])
int_b = int(split_b[i])
if int_a > int_b:
return True
if int_b > int_a:
return False
except ValueError:
if split_a[i] > split_b[i]:
return True
if split_b[i] > split_a[i]:
return False
return False
def parent_more_recent(self):
if not self.parent_version:
return False
return self._compare_versions_a_gt_b(self.parent_version, self.version)
def needs_update(self):
# empty upstream version, or upstream version meaning openSUSE is
# upstream
if self.upstream_version in [ None, '', '--' ]:
return False
if self.parent_version in [ None, '', '--' ]:
return True
if self.version in [ None, '', '--' ]:
return True
return self._compare_versions_a_gt_b(self.upstream_version, self.parent_version) and self._compare_versions_a_gt_b(self.upstream_version, self.version)
def devel_needs_update(self):
# if there's no devel project, then it's as if it were needing an update
if not self.devel_project:
return True
# empty upstream version, or upstream version meaning openSUSE is
# upstream
if self.upstream_version in [ None, '', '--' ]:
return False
return self._compare_versions_a_gt_b(self.upstream_version, self.devel_version)
def is_broken_link(self):
return self.error in [ 'not-in-parent', 'need-merge-with-parent' ]
def __eq__(self, other):
return self.name == other.name and self.project and other.project and self.project.name == other.project.name
def __ne__(self, other):
return not self.__eq__(other)
def __lt__(self, other):
if not self.project or not self.project.name:
if other.project and other.project.name:
return True
else:
return self.name < other.name
if self.project.name == other.project.name:
return self.name < other.name
return self.project.name < other.project.name
def __le__(self, other):
return self.__eq__(other) or self.__lt__(other)
def __gt__(self, other):
return other.__lt__(self)
def __ge__(self, other):
return other.__eq__(self) or other.__lt__(self)
#######################################################################
class OscCollabObs:
apiurl = None
@classmethod
def init(cls, apiurl):
cls.apiurl = apiurl
@classmethod
def get_meta(cls, project):
what = 'metadata of packages in %s' % project
# download the data (cache for 2 days)
url = makeurl(cls.apiurl, ['search', 'package'], ['match=%s' % quote('@project=\'%s\'' % project)])
filename = '%s-meta.obs' % project
max_age_minutes = 3600 * 24 * 2
return OscCollabCache.get_from_obs(url, filename, max_age_minutes, what)
@classmethod
def get_build_results(cls, project):
what = 'build results of packages in %s' % project
# download the data (cache for 2 hours)
url = makeurl(cls.apiurl, ['build', project, '_result'])
filename = '%s-build-results.obs' % project
max_age_minutes = 3600 * 2
return OscCollabCache.get_from_obs(url, filename, max_age_minutes, what)
@classmethod
def _get_request_list_url(cls, project, package, type, what):
match = '(state/@name=\'new\'%20or%20state/@name=\'review\')'
match += '%20and%20'
match += 'action/%s/@project=\'%s\'' % (type, quote(project))
if package:
match += '%20and%20'
match += 'action/%s/@package=\'%s\'' % (type, quote(package))
return makeurl(cls.apiurl, ['search', 'request'], ['match=%s' % match])
@classmethod
def _parse_request_list_internal(cls, f, what):
requests = []
try:
collection = ET.parse(f).getroot()
except SyntaxError as e:
print('Cannot parse %s: %s' % (what, e.msg), file=sys.stderr)
return requests
for node in collection.findall('request'):
requests.append(OscCollabRequest(node))
return requests
@classmethod
def _get_request_list_no_cache(cls, project, package, type, what):
url = cls._get_request_list_url(project, package, type, what)
try:
fin = http_GET(url)
except HTTPError as e:
print('Cannot get %s: %s' % (what, e.msg), file=sys.stderr)
return []
requests = cls._parse_request_list_internal(fin, what)
fin.close()
return requests
@classmethod
def _get_request_list_with_cache(cls, project, package, type, what):
url = cls._get_request_list_url(project, package, type, what)
if url is None:
return []
# download the data (cache for 10 minutes)
if package:
filename = '%s-%s-requests-%s.obs' % (project, package, type)
else:
filename = '%s-requests-%s.obs' % (project, type)
max_age_minutes = 60 * 10
file = OscCollabCache.get_from_obs(url, filename, max_age_minutes, what)
if not file or not os.path.exists(file):
return []
return cls._parse_request_list_internal(file, what)
@classmethod
def _get_request_list(cls, project, package, type, use_cache):
if package:
what_helper = '%s/%s' % (project, package)
else:
what_helper = project
if type == 'source':
what = 'list of requests from %s' % what_helper
elif type == 'target':
what = 'list of requests to %s' % what_helper
else:
print('Internal error when getting request list: unknown type \"%s\".' % type, file=sys.stderr)
return None
if use_cache:
return cls._get_request_list_with_cache(project, package, type, what)
else:
return cls._get_request_list_no_cache(project, package, type, what)
@classmethod
def get_request_list_from(cls, project, package=None, use_cache=True):
return cls._get_request_list(project, package, 'source', use_cache)
@classmethod
def get_request_list_to(cls, project, package=None, use_cache=True):
return cls._get_request_list(project, package, 'target', use_cache)
@classmethod
def get_request(cls, id):
url = makeurl(cls.apiurl, ['request', id])
try:
fin = http_GET(url)
except HTTPError as e:
print('Cannot get request %s: %s' % (id, e.msg), file=sys.stderr)
return None
try:
node = ET.parse(fin).getroot()
except SyntaxError as e:
fin.close()
print('Cannot parse request %s: %s' % (id, e.msg), file=sys.stderr)
return None
fin.close()
return OscCollabRequest(node)
@classmethod
def change_request_state(cls, id, new_state, message, superseded_by=None):
if new_state != 'superseded':
result = change_request_state(cls.apiurl, id, new_state, message)
else:
result = change_request_state(cls.apiurl, id, new_state, message, supersed=superseded_by)
return result == 'ok'
@classmethod
def supersede_old_requests(cls, user, project, package, new_request_id):
requests_to = cls.get_request_list_to(project, package, use_cache=False)
old_ids = [ request.req_id for request in requests_to if request.by == user and str(request.req_id) != new_request_id ]
for old_id in old_ids:
cls.change_request_state(str(old_id), 'superseded', 'superseded by %s' % new_request_id, superseded_by=new_request_id)
return old_ids
@classmethod
def branch_package(cls, project, package, no_devel_project = False):
query = { 'cmd': 'branch' }
if no_devel_project:
query['ignoredevel'] = '1'
url = makeurl(cls.apiurl, ['source', project, package], query = query)
try:
fin = http_POST(url)
except HTTPError as e:
print('Cannot branch package %s: %s' % (package, e.msg), file=sys.stderr)
return (None, None)
try:
node = ET.parse(fin).getroot()
except SyntaxError as e:
fin.close()
print('Cannot branch package %s: %s' % (package, e.msg), file=sys.stderr)
return (None, None)
fin.close()
branch_project = None
branch_package = None
for data in node.findall('data'):
name = data.get('name')
if not name:
continue
if name == 'targetproject' and data.text:
branch_project = data.text
elif name == 'targetpackage' and data.text:
branch_package = data.text
return (branch_project, branch_package)
#######################################################################
class OscCollabApi:
_api_url = 'https://osc-collab.opensuse.org/api'
_supported_api = '0.2'
_supported_api_major = '0'
@classmethod
def init(cls, collapiurl = None):
if collapiurl:
cls._api_url = collapiurl
@classmethod
def _append_data_to_url(cls, url, data):
if url.find('?') != -1:
return '%s&%s' % (url, data)
else:
return '%s?%s' % (url, data)
@classmethod
def _get_api_url_for(cls, api, project = None, projects = None, package = None, need_package_for_multiple_projects = True):
if not project and len(projects) == 1:
project = projects[0]
items = [ cls._api_url, api ]
if project:
items.append(project)
if package:
items.append(package)
url = '/'.join(items)
if not project and (not need_package_for_multiple_projects or package) and projects:
data = urlencode({'version': cls._supported_api, 'project': projects}, True)
url = cls._append_data_to_url(url, data)
else:
data = urlencode({'version': cls._supported_api})
url = cls._append_data_to_url(url, data)
return url
@classmethod
def _get_info_url(cls, project = None, projects = None, package = None):
return cls._get_api_url_for('info', project, projects, package, True)
@classmethod
def _get_reserve_url(cls, project = None, projects = None, package = None):
return cls._get_api_url_for('reserve', project, projects, package, False)
@classmethod
def _get_comment_url(cls, project = None, projects = None, package = None):
return cls._get_api_url_for('comment', project, projects, package, False)
@classmethod
def _get_root_for_url(cls, url, error_prefix, post_data = None, cache_file = None, cache_age = 10):
if post_data and type(post_data) != dict:
raise OscCollabWebError('%s: Internal error when posting data' % error_prefix)
try:
if cache_file and not post_data:
fd = OscCollabCache.get_url_fd_with_cache(url, cache_file, cache_age)
else:
if post_data:
data = urlencode(post_data).encode('utf-8')
else:
data = None
fd = urlopen(url, data)
except HTTPError as e:
raise OscCollabWebError('%s: %s' % (error_prefix, e.msg))
try:
root = ET.parse(fd).getroot()
except SyntaxError:
raise OscCollabWebError('%s: malformed reply from server.' % error_prefix)
if root.tag != 'api' or not root.get('version'):
raise OscCollabWebError('%s: invalid reply from server.' % error_prefix)
version = root.get('version')
version_items = version.split('.')
for item in version_items:
try:
int(item)
except ValueError:
raise OscCollabWebError('%s: unknown protocol used by server.' % error_prefix)
protocol = int(version_items[0])
if int(version_items[0]) != int(cls._supported_api_major):
raise OscCollabWebError('%s: unknown protocol used by server.' % error_prefix)
result = root.find('result')
if result is None or not result.get('ok'):
raise OscCollabWebError('%s: reply from server with no result summary.' % error_prefix)
if result.get('ok') != 'true':
if result.text:
raise OscCollabWebError('%s: %s' % (error_prefix, result.text))
else:
raise OscCollabWebError('%s: unknown error in the request.' % error_prefix)
return root
@classmethod
def _meta_append_no_devel_project(cls, url, no_devel_project):
if not no_devel_project:
return url
data = urlencode({'ignoredevel': 'true'})
return cls._append_data_to_url(url, data)
@classmethod
def _parse_reservation_node(cls, node):
reservation = OscCollabReservation(node = node)
if not reservation.project or not reservation.package:
return None
return reservation
@classmethod
def get_reserved_packages(cls, projects):
url = cls._get_reserve_url(projects = projects)
root = cls._get_root_for_url(url, 'Cannot get list of reserved packages')
reserved_packages = []
for reservation in root.findall('reservation'):
item = cls._parse_reservation_node(reservation)
if item is None or not item.user:
continue
reserved_packages.append(item)
return reserved_packages
@classmethod
def is_package_reserved(cls, projects, package, no_devel_project = False):
'''
Only returns something if the package is really reserved.
'''
url = cls._get_reserve_url(projects = projects, package = package)
url = cls._meta_append_no_devel_project(url, no_devel_project)
root = cls._get_root_for_url(url, 'Cannot look if package %s is reserved' % package)
for reservation in root.findall('reservation'):
item = cls._parse_reservation_node(reservation)
if item is None or (no_devel_project and not item.is_relevant(projects, package)):
continue
if not item.user:
# We continue to make sure there are no other relevant entries
continue
return item
return None
@classmethod
def reserve_package(cls, projects, package, username, no_devel_project = False):
url = cls._get_reserve_url(projects = projects, package = package)
data = urlencode({'cmd': 'set', 'user': username})
url = cls._append_data_to_url(url, data)
url = cls._meta_append_no_devel_project(url, no_devel_project)
root = cls._get_root_for_url(url, 'Cannot reserve package %s' % package)
for reservation in root.findall('reservation'):
item = cls._parse_reservation_node(reservation)
if not item or (no_devel_project and not item.is_relevant(projects, package)):
continue
if not item.user:
raise OscCollabWebError('Cannot reserve package %s: unknown error' % package)
if item.user != username:
raise OscCollabWebError('Cannot reserve package %s: already reserved by %s' % (package, item.user))
return item
@classmethod
def unreserve_package(cls, projects, package, username, no_devel_project = False):
url = cls._get_reserve_url(projects = projects, package = package)
data = urlencode({'cmd': 'unset', 'user': username})
url = cls._append_data_to_url(url, data)
url = cls._meta_append_no_devel_project(url, no_devel_project)
root = cls._get_root_for_url(url, 'Cannot unreserve package %s' % package)
for reservation in root.findall('reservation'):
item = cls._parse_reservation_node(reservation)
if not item or (no_devel_project and not item.is_relevant(projects, package)):
continue
if item.user:
raise OscCollabWebError('Cannot unreserve package %s: reserved by %s' % (package, item.user))
return item
@classmethod
def _parse_comment_node(cls, node):
comment = OscCollabComment(node = node)
if not comment.project or not comment.package:
return None
return comment
@classmethod
def get_commented_packages(cls, projects):
url = cls._get_comment_url(projects = projects)
root = cls._get_root_for_url(url, 'Cannot get list of commented packages')
commented_packages = []
for comment in root.findall('comment'):
item = cls._parse_comment_node(comment)
if item is None or not item.user or not item.comment:
continue
commented_packages.append(item)
return commented_packages
@classmethod
def get_package_comment(cls, projects, package, no_devel_project = False):
'''
Only returns something if the package is really commented.
'''
url = cls._get_comment_url(projects = projects, package = package)
url = cls._meta_append_no_devel_project(url, no_devel_project)
root = cls._get_root_for_url(url, 'Cannot look if package %s is commented' % package)
for comment in root.findall('comment'):
item = cls._parse_comment_node(comment)
if item is None or (no_devel_project and not item.is_relevant(projects, package)):
continue
if not item.user or not item.comment:
# We continue to make sure there are no other relevant entries
continue
return item
return None
@classmethod
def set_package_comment(cls, projects, package, username, comment, no_devel_project = False):
if not comment:
raise OscCollabWebError('Cannot set comment on package %s: empty comment' % package)
url = cls._get_comment_url(projects = projects, package = package)
data = urlencode({'cmd': 'set', 'user': username})
url = cls._append_data_to_url(url, data)
url = cls._meta_append_no_devel_project(url, no_devel_project)
root = cls._get_root_for_url(url, 'Cannot set comment on package %s' % package, post_data = {'comment': comment})
for comment in root.findall('comment'):
item = cls._parse_comment_node(comment)
if not item or (no_devel_project and not item.is_relevant(projects, package)):
continue
if not item.user:
raise OscCollabWebError('Cannot set comment on package %s: unknown error' % package)
if item.user != username:
raise OscCollabWebError('Cannot set comment on package %s: already commented by %s' % (package, item.user))
return item
@classmethod
def unset_package_comment(cls, projects, package, username, no_devel_project = False):
url = cls._get_comment_url(projects = projects, package = package)
data = urlencode({'cmd': 'unset', 'user': username})
url = cls._append_data_to_url(url, data)
url = cls._meta_append_no_devel_project(url, no_devel_project)
root = cls._get_root_for_url(url, 'Cannot unset comment on package %s' % package)
for comment in root.findall('comment'):
item = cls._parse_comment_node(comment)
if not item or (no_devel_project and not item.is_relevant(projects, package)):
continue
if item.user:
raise OscCollabWebError('Cannot unset comment on package %s: commented by %s' % (package, item.user))
return item
@classmethod
def _parse_package_node(cls, node, project):
package = OscCollabPackage(node, project)
if not package.name:
return None
if project is not None:
project[package.name] = package
return package
@classmethod
def _parse_missing_package_node(cls, node, project):
name = node.get('name')
parent_project = node.get('parent_project')
parent_package = node.get('parent_package') or name
if not name or not parent_project:
return
project.missing_packages.append((name, parent_project, parent_package))
@classmethod
def _parse_project_node(cls, node):
project = OscCollabProject(node)
if not project.name:
return None
for package in node.findall('package'):
cls._parse_package_node(package, project)
missing = node.find('missing')
if missing is not None:
for package in missing.findall('package'):
cls._parse_missing_package_node(package, project)
return project
@classmethod
def get_project_details(cls, project):
url = cls._get_info_url(project = project)
root = cls._get_root_for_url(url, 'Cannot get information of project %s' % project, cache_file = project + '.xml')
for node in root.findall('project'):
item = cls._parse_project_node(node)
if item is None or item.name != project:
continue
return item
return None
@classmethod
def get_package_details(cls, projects, package):
url = cls._get_info_url(projects = projects, package = package)
root = cls._get_root_for_url(url, 'Cannot get information of package %s' % package)
for node in root.findall('project'):
item = cls._parse_project_node(node)
if item is None or item.name not in projects:
continue
pkgitem = item[package]
if pkgitem:
return pkgitem
return None
#######################################################################
class OscCollabCache:
_cache_dir = None
_ignore_cache = False
_printed = False
@classmethod
def init(cls, ignore_cache):
cls._ignore_cache = ignore_cache
cls._cleanup_old_cache()
@classmethod
def _print_message(cls):
if not cls._printed:
cls._printed = True
print('Downloading data in a cache. It might take a few seconds...')
@classmethod
def _get_xdg_cache_home(cls):
dir = None
if 'XDG_CACHE_HOME' in os.environ:
dir = os.environ['XDG_CACHE_HOME']
if dir == '':
dir = None
if not dir:
dir = '~/.cache'
return os.path.expanduser(dir)
@classmethod
def _get_xdg_cache_dir(cls):
if not cls._cache_dir:
cls._cache_dir = os.path.join(cls._get_xdg_cache_home(), 'osc', 'collab')
return cls._cache_dir
@classmethod
def _cleanup_old_cache(cls):
'''
Remove old cache files, when they're old (and therefore obsolete
anyway).
'''
gnome_cache_dir = os.path.join(cls._get_xdg_cache_home(), 'osc', 'gnome')
if os.path.exists(gnome_cache_dir):
shutil.rmtree(gnome_cache_dir)
cache_dir = cls._get_xdg_cache_dir()
if not os.path.exists(cache_dir):
return
for file in os.listdir(cache_dir):
# remove if it's more than 5 days old
if cls._need_update(file, 60 * 60 * 24 * 5):
cache = os.path.join(cache_dir, file)
os.unlink(cache)
@classmethod
def _need_update(cls, filename, maxage):
if cls._ignore_cache:
return True
cache = os.path.join(cls._get_xdg_cache_dir(), filename)
if not os.path.exists(cache):
return True
if not os.path.isfile(cache):
return True
stats = os.stat(cache)
now = time.time()
if now - stats.st_mtime > maxage:
return True
# Back to the future?
elif now < stats.st_mtime:
return True
return False
@classmethod
def get_url_fd_with_cache(cls, url, filename, max_age_minutes):
if cls._need_update(filename, max_age_minutes * 60):
# no cache available
cls._print_message()
fd = urlopen(url)
cls._write(filename, fin = fd)
return open(os.path.join(cls._get_xdg_cache_dir(), filename))
@classmethod
def get_from_obs(cls, url, filename, max_age_minutes, what):
cache = os.path.join(cls._get_xdg_cache_dir(), filename)
if not cls._need_update(cache, max_age_minutes):
return cache
# no cache available
cls._print_message()
try:
fin = http_GET(url)
except HTTPError as e:
print('Cannot get %s: %s' % (what, e.msg), file=sys.stderr)
return None
fout = open(cache, 'w')
while True:
try:
bytes = fin.read(500 * 1024)
if len(bytes) == 0:
break
fout.write(bytes.decode('utf-8'))
except HTTPError as e:
fin.close()
fout.close()
os.unlink(cache)
print('Error while downloading %s: %s' % (what, e.msg), file=sys.stderr)
return None
fin.close()
fout.close()
return cache
@classmethod
def _write(cls, filename, fin = None):
if not fin:
print('Internal error when saving a cache: no data.', file=sys.stderr)
return False
cachedir = cls._get_xdg_cache_dir()
if not os.path.exists(cachedir):
os.makedirs(cachedir)
if not os.path.isdir(cachedir):
print('Cache directory %s is not a directory.' % cachedir, file=sys.stderr)
return False
cache = os.path.join(cachedir, filename)
if os.path.exists(cache):
os.unlink(cache)
fout = open(cache, 'w')
if fin:
while True:
try:
bytes = fin.read(500 * 1024)
if len(bytes) == 0:
break
fout.write(bytes.decode('utf-8'))
except HTTPError as e:
fout.close()
os.unlink(cache)
raise e
fout.close()
return True
#######################################################################
def _collab_is_program_in_path(program):
if not 'PATH' in os.environ:
return False
for path in os.environ['PATH'].split(':'):
if os.path.exists(os.path.join(path, program)):
return True
return False
#######################################################################
def _collab_find_request_to(package, requests):
for request in requests:
if request.target_package == package:
return request
return None
def _collab_has_request_from(package, requests):
for request in requests:
if request.source_package == package:
return True
return False
#######################################################################
def _collab_table_get_maxs(init, list):
if len(list) == 0:
return ()
nb_maxs = len(init)
maxs = []
for i in range(nb_maxs):
maxs.append(len(init[i]))
for item in list:
for i in range(nb_maxs):
maxs[i] = max(maxs[i], len(item[i]))
return tuple(maxs)
def _collab_table_get_template(*args):
if len(args) == 0:
return ''
template = '%%-%d.%ds' % (args[0], args[0])
index = 1
while index < len(args):
template = template + (' | %%-%d.%ds' % (args[index], args[index]))
index = index + 1
return template
def _collab_table_print_header(template, title):
if len(title) == 0:
return
dash_template = template.replace(' | ', '-+-')
very_long_dash = ('--------------------------------------------------------------------------------',)
dashes = ()
for i in range(len(title)):
dashes = dashes + very_long_dash
print(template % title)
print(dash_template % dashes)
#######################################################################
def _collab_todo_internal(apiurl, all_reserved, all_commented, project, show_details, exclude_commented, exclude_reserved, exclude_submitted, exclude_devel):
# get all versions of packages
try:
prj = OscCollabApi.get_project_details(project)
prj.strip_internal_links()
except OscCollabWebError as e:
print(e.msg, file=sys.stderr)
return (None, None)
# get the list of reserved packages for this project
reserved_packages = [ reservation.package for reservation in all_reserved if reservation.project == project ]
# get the list of commented packages for this project
firstline_comments = {}
for comment in all_commented:
if comment.project == project:
firstline_comments[comment.package] = comment.firstline
commented_packages = firstline_comments.keys()
# get the packages submitted
requests_to = OscCollabObs.get_request_list_to(project)
parent_project = None
packages = []
for package in prj.values():
if not package.needs_update() and package.name not in commented_packages:
continue
broken_link = package.is_broken_link()
if package.parent_version or package.is_link:
package.parent_version_print = package.parent_version or ''
elif broken_link:
# this can happen if the link is to a project that doesn't exist
# anymore
package.parent_version_print = '??'
else:
package.parent_version_print = '--'
if package.version:
package.version_print = package.version
elif broken_link:
package.version_print = '(broken)'
else:
package.version_print = '??'
package.upstream_version_print = package.upstream_version or ''
package.comment = ''
if package.name in commented_packages:
if exclude_commented:
continue
if not show_details:
package.version_print += ' (c)'
package.upstream_version_print += ' (c)'
package.comment = firstline_comments[package.name]
if not package.devel_needs_update():
if exclude_devel:
continue
package.version_print += ' (d)'
package.upstream_version_print += ' (d)'
if _collab_find_request_to(package.name, requests_to) != None:
if exclude_submitted:
continue
package.version_print += ' (s)'
package.upstream_version_print += ' (s)'
if package.name in reserved_packages:
if exclude_reserved:
continue
package.upstream_version_print += ' (r)'
package.upstream_version_print = package.upstream_version_print.strip()
if package.parent_project:
if parent_project == None:
parent_project = package.parent_project
elif parent_project != package.parent_project:
parent_project = 'Parent Project'
packages.append(package)
return (parent_project, packages)
#######################################################################
def _collab_todo(apiurl, projects, show_details, ignore_comments, exclude_commented, exclude_reserved, exclude_submitted, exclude_devel):
packages = []
parent_project = None
# get the list of reserved packages
try:
reserved = OscCollabApi.get_reserved_packages(projects)
except OscCollabWebError as e:
reserved = []
print(e.msg, file=sys.stderr)
# get the list of commented packages
commented = []
if not ignore_comments:
try:
commented = OscCollabApi.get_commented_packages(projects)
except OscCollabWebError as e:
print(e.msg, file=sys.stderr)
for project in projects:
(new_parent_project, project_packages) = _collab_todo_internal(apiurl, reserved, commented, project, show_details, exclude_commented, exclude_reserved, exclude_submitted, exclude_devel)
if not project_packages:
continue
packages.extend(project_packages)
if parent_project == None:
parent_project = new_parent_project
elif parent_project != new_parent_project:
parent_project = 'Parent Project'
if len(packages) == 0:
print('Nothing to do.')
return
show_comments = not (ignore_comments or exclude_commented) and show_details
if show_comments:
lines = [ (package.name, package.parent_version_print, package.version_print, package.upstream_version_print, package.comment) for package in packages ]
else:
lines = [ (package.name, package.parent_version_print, package.version_print, package.upstream_version_print) for package in packages ]
# the first element in the tuples is the package name, so it will sort
# the lines the right way for what we want
lines.sort()
if len(projects) == 1:
project_header = projects[0]
else:
project_header = "Devel Project"
# print headers
if show_comments:
if parent_project:
title = ('Package', parent_project, project_header, 'Upstream', 'Comment')
(max_package, max_parent, max_devel, max_upstream, max_comment) = _collab_table_get_maxs(title, lines)
else:
title = ('Package', project_header, 'Upstream', 'Comment')
(max_package, max_devel, max_upstream, max_comment) = _collab_table_get_maxs(title, lines)
max_parent = 0
else:
if parent_project:
title = ('Package', parent_project, project_header, 'Upstream')
(max_package, max_parent, max_devel, max_upstream) = _collab_table_get_maxs(title, lines)
else:
title = ('Package', project_header, 'Upstream')
(max_package, max_devel, max_upstream) = _collab_table_get_maxs(title, lines)
max_parent = 0
max_comment = 0
# trim to a reasonable max
max_package = min(max_package, 48)
max_version = min(max(max(max_parent, max_devel), max_upstream), 20)
max_comment = min(max_comment, 48)
if show_comments:
if parent_project:
print_line = _collab_table_get_template(max_package, max_version, max_version, max_version, max_comment)
else:
print_line = _collab_table_get_template(max_package, max_version, max_version, max_comment)
else:
if parent_project:
print_line = _collab_table_get_template(max_package, max_version, max_version, max_version)
else:
print_line = _collab_table_get_template(max_package, max_version, max_version)
_collab_table_print_header(print_line, title)
for line in lines:
if not parent_project:
if show_comments:
(package, parent_version, devel_version, upstream_version, comment) = line
line = (package, devel_version, upstream_version, comment)
else:
(package, parent_version, devel_version, upstream_version) = line
line = (package, devel_version, upstream_version)
print(print_line % line)
#######################################################################
def _collab_todoadmin_internal(apiurl, project, include_upstream):
try:
prj = OscCollabApi.get_project_details(project)
prj.strip_internal_links()
except OscCollabWebError as e:
print(e.msg, file=sys.stderr)
return []
# get the packages submitted to/from
requests_to = OscCollabObs.get_request_list_to(project)
requests_from = OscCollabObs.get_request_list_from(project)
lines = []
for package in prj.values():
message = None
# We look for all possible messages. The last message overwrite the
# first, so we start with the less important ones.
if include_upstream:
if not package.upstream_version:
message = 'No upstream data available'
elif not package.upstream_url:
message = 'No URL for upstream tarball available'
if package.has_delta:
# FIXME: we should check the request is to the parent project
if not _collab_has_request_from(package.name, requests_from):
if not package.is_link:
message = 'Is not a link to %s and has a delta (synchronize the packages)' % package.project.parent
elif not package.project.is_toplevel():
message = 'Needs to be submitted to %s' % package.parent_project
else:
# packages in a toplevel project don't necessarily have to
# be submitted
message = 'Is a link with delta (maybe submit changes to %s)' % package.parent_project
request = _collab_find_request_to(package.name, requests_to)
if request is not None:
message = 'Needs to be reviewed (request id: %s)' % request.req_id
if package.error:
if package.error == 'not-link':
# if package has a delta, then we already set a message above
if not package.has_delta:
message = 'Is not a link to %s (make link)' % package.project.parent
elif package.error == 'not-link-not-in-parent':
message = 'Is not a link, and is not in %s (maybe submit it)' % package.project.parent
elif package.error == 'not-in-parent':
message = 'Broken link: does not exist in %s' % package.parent_project
elif package.error == 'need-merge-with-parent':
message = 'Broken link: requires a manual merge with %s' % package.parent_project
elif package.error == 'not-real-devel':
message = 'Should not exist: %s' % package.error_details
elif package.error == 'parent-without-devel':
message = 'No devel project set for parent (%s/%s)' % (package.parent_project, package.parent_package)
else:
if package.error_details:
message = 'Unknown error (%s): %s' % (package.error, package.error_details)
else:
message = 'Unknown error (%s)' % package.error
if message:
lines.append((project, package.name, message))
for (package, parent_project, parent_package) in prj.missing_packages:
message = 'Does not exist, but is devel package for %s/%s' % (parent_project, parent_package)
lines.append((project, package, message))
lines.sort()
return lines
#######################################################################
def _collab_todoadmin(apiurl, projects, include_upstream):
lines = []
for project in projects:
project_lines = _collab_todoadmin_internal(apiurl, project, include_upstream)
lines.extend(project_lines)
if len(lines) == 0:
print('Nothing to do.')
return
# the first element in the tuples is the package name, so it will sort
# the lines the right way for what we want
lines.sort()
# print headers
title = ('Project', 'Package', 'Details')
(max_project, max_package, max_details) = _collab_table_get_maxs(title, lines)
# trim to a reasonable max
max_project = min(max_project, 28)
max_package = min(max_package, 48)
max_details = min(max_details, 65)
print_line = _collab_table_get_template(max_project, max_package, max_details)
_collab_table_print_header(print_line, title)
for line in lines:
print(print_line % line)
#######################################################################
def _collab_listreserved(projects):
try:
reserved_packages = OscCollabApi.get_reserved_packages(projects)
except OscCollabWebError as e:
print(e.msg, file=sys.stderr)
return
if len(reserved_packages) == 0:
print('No package reserved.')
return
# print headers
# if changing the order here, then we need to change __getitem__ of
# Reservation in the same way
title = ('Project', 'Package', 'Reserved by')
(max_project, max_package, max_username) = _collab_table_get_maxs(title, reserved_packages)
# trim to a reasonable max
max_project = min(max_project, 28)
max_package = min(max_package, 48)
max_username = min(max_username, 28)
print_line = _collab_table_get_template(max_project, max_package, max_username)
_collab_table_print_header(print_line, title)
for reservation in reserved_packages:
if reservation.user:
print(print_line % (reservation.project, reservation.package, reservation.user))
#######################################################################
def _collab_isreserved(projects, packages, no_devel_project = False):
for package in packages:
try:
reservation = OscCollabApi.is_package_reserved(projects, package, no_devel_project = no_devel_project)
except OscCollabWebError as e:
print(e.msg, file=sys.stderr)
continue
if not reservation:
print('Package %s is not reserved.' % package)
else:
if reservation.project not in projects or reservation.package != package:
print('Package %s in %s (devel package for %s) is reserved by %s.' % (reservation.package, reservation.project, package, reservation.user))
else:
print('Package %s in %s is reserved by %s.' % (package, reservation.project, reservation.user))
#######################################################################
def _collab_reserve(projects, packages, username, no_devel_project = False):
for package in packages:
try:
reservation = OscCollabApi.reserve_package(projects, package, username, no_devel_project = no_devel_project)
except OscCollabWebError as e:
print(e.msg, file=sys.stderr)
continue
if reservation.project not in projects or reservation.package != package:
print('Package %s in %s (devel package for %s) reserved for 36 hours.' % (reservation.package, reservation.project, package))
else:
print('Package %s reserved for 36 hours.' % package)
print('Do not forget to unreserve the package when done with it:')
print(' osc %s unreserve %s' % (_osc_collab_alias, package))
#######################################################################
def _collab_unreserve(projects, packages, username, no_devel_project = False):
for package in packages:
try:
OscCollabApi.unreserve_package(projects, package, username, no_devel_project = no_devel_project)
except OscCollabWebError as e:
print(e.msg, file=sys.stderr)
continue
print('Package %s unreserved.' % package)
#######################################################################
def _collab_listcommented(projects):
try:
commented_packages = OscCollabApi.get_commented_packages(projects)
except OscCollabWebError as e:
print(e.msg, file=sys.stderr)
return
if len(commented_packages) == 0:
print('No package commented.')
return
# print headers
# if changing the order here, then we need to change __getitem__ of
# Comment in the same way
title = ('Project', 'Package', 'Commented by', 'Comment')
(max_project, max_package, max_username, max_comment) = _collab_table_get_maxs(title, commented_packages)
# trim to a reasonable max
max_project = min(max_project, 28)
max_package = min(max_package, 48)
max_username = min(max_username, 28)
max_comment = min(max_comment, 65)
print_line = _collab_table_get_template(max_project, max_package, max_username, max_comment)
_collab_table_print_header(print_line, title)
for comment in commented_packages:
if comment.user:
print(print_line % (comment.project, comment.package, comment.user, comment.firstline))
#######################################################################
def _collab_comment(projects, packages, no_devel_project = False):
for package in packages:
try:
comment = OscCollabApi.get_package_comment(projects, package, no_devel_project = no_devel_project)
except OscCollabWebError as e:
print(e.msg, file=sys.stderr)
continue
if not comment:
print('Package %s is not commented.' % package)
else:
if comment.date:
date_str = ' on %s' % comment.date
else:
date_str = ''
if comment.project not in projects or comment.package != package:
print('Package %s in %s (devel package for %s) is commented by %s%s:' % (comment.package, comment.project, package, comment.user, date_str))
print(comment.indent())
else:
print('Package %s in %s is commented by %s%s:' % (package, comment.project, comment.user, date_str))
print(comment.indent())
#######################################################################
def _collab_commentset(projects, package, username, comment, no_devel_project = False):
try:
comment = OscCollabApi.set_package_comment(projects, package, username, comment, no_devel_project = no_devel_project)
except OscCollabWebError as e:
print(e.msg, file=sys.stderr)
return
if comment.project not in projects or comment.package != package:
print('Comment on package %s in %s (devel package for %s) set.' % (comment.package, comment.project, package))
else:
print('Comment on package %s set.' % package)
print('Do not forget to unset comment on the package when done with it:')
print(' osc %s commentunset %s' % (_osc_collab_alias, package))
#######################################################################
def _collab_commentunset(projects, packages, username, no_devel_project = False):
for package in packages:
try:
OscCollabApi.unset_package_comment(projects, package, username, no_devel_project = no_devel_project)
except OscCollabWebError as e:
print(e.msg, file=sys.stderr)
continue
print('Comment on package %s unset.' % package)
#######################################################################
def _collab_setup_internal(apiurl, username, pkg, ignore_reserved = False, no_reserve = False, no_devel_project = False, no_branch = False):
if not no_devel_project:
initial_pkg = pkg
while pkg.devel_project:
previous_pkg = pkg
try:
pkg = OscCollabApi.get_package_details(pkg.devel_project, pkg.devel_package or pkg.name)
except OscCollabWebError as e:
pkg = None
if not pkg:
print('Cannot find information on %s/%s (development package for %s/%s). You can use --nodevelproject to ignore the development package.' % (previous_pkg.project.name, previous_pkg.name, initial_pkg.project.name, initial_pkg.name), file=sys.stderr)
break
if not pkg:
return (False, None, None)
if initial_pkg != pkg:
print('Using development package %s/%s for %s/%s.' % (pkg.project.name, pkg.name, initial_pkg.project.name, initial_pkg.name))
project = pkg.project.name
package = pkg.name
checkout_dir = package
# Is it reserved? Note that we have already looked for the devel project,
# so we force the project/package here.
try:
reservation = OscCollabApi.is_package_reserved((project,), package, no_devel_project = True)
if reservation:
reserved_by = reservation.user
else:
reserved_by = None
except OscCollabWebError as e:
print(e.msg, file=sys.stderr)
return (False, None, None)
# package already reserved, but not by us
if reserved_by and reserved_by != username:
if not ignore_reserved:
print('Package %s is already reserved by %s.' % (package, reserved_by))
return (False, None, None)
else:
print('WARNING: package %s is already reserved by %s.' % (package, reserved_by))
# package not reserved
elif not reserved_by and not no_reserve:
try:
# Note that we have already looked for the devel project, so we
# force the project/package here.
OscCollabApi.reserve_package((project,), package, username, no_devel_project = True)
print('Package %s has been reserved for 36 hours.' % package)
print('Do not forget to unreserve the package when done with it:')
print(' osc %s unreserve %s' % (_osc_collab_alias, package))
except OscCollabWebError as e:
print(e.msg, file=sys.stderr)
if not ignore_reserved:
return (False, None, None)
if not no_branch:
# look if we already have a branch, and if not branch the package
try:
expected_branch_project = 'home:%s:branches:%s' % (username, project)
show_package_meta(apiurl, expected_branch_project, package)
branch_project = expected_branch_project
branch_package = package
# it worked, we already have the branch
except HTTPError as e:
if e.code != 404:
print('Error while checking if package %s was already branched: %s' % (package, e.msg), file=sys.stderr)
return (False, None, None)
# We had a 404: it means the branched package doesn't exist yet
(branch_project, branch_package) = OscCollabObs.branch_package(project, package, no_devel_project)
if not branch_project or not branch_package:
print('Error while branching package %s: incomplete reply from build service' % (package,), file=sys.stderr)
return (False, None, None)
if package != branch_package:
print('Package %s has been branched in %s/%s.' % (package, branch_project, branch_package))
else:
print('Package %s has been branched in project %s.' % (branch_package, branch_project))
else:
branch_project = project
branch_package = package
checkout_dir = branch_package
# check out the branched package
if os.path.exists(checkout_dir):
# maybe we already checked it out before?
if not os.path.isdir(checkout_dir):
print('File %s already exists but is not a directory.' % checkout_dir, file=sys.stderr)
return (False, None, None)
elif not is_package_dir(checkout_dir):
print('Directory %s already exists but is not a checkout of a Build Service package.' % checkout_dir, file=sys.stderr)
return (False, None, None)
obs_package = filedir_to_pac(checkout_dir)
if obs_package.name != branch_package or obs_package.prjname != branch_project:
print('Directory %s already exists but is a checkout of package %s from project %s.' % (checkout_dir, obs_package.name, obs_package.prjname), file=sys.stderr)
return (False, None, None)
if _collab_osc_package_pending_commit(obs_package):
print('Directory %s contains some uncommitted changes.' % (checkout_dir,), file=sys.stderr)
return (False, None, None)
# update the package
try:
# we specify the revision so that it gets expanded
# the logic comes from do_update in commandline.py
rev = None
if obs_package.islink() and not obs_package.isexpanded():
rev = obs_package.linkinfo.xsrcmd5
elif obs_package.islink() and obs_package.isexpanded():
rev = show_upstream_xsrcmd5(apiurl, branch_project, branch_package)
obs_package.update(rev)
print('Package %s has been updated.' % branch_package)
except Exception as e:
message = 'Error while updating package %s: ' % branch_package
_collab_exception_print(e, message)
return (False, None, None)
else:
# check out the branched package
try:
# disable package tracking: the current directory might not be a
# project directory
# Rationale: for new contributors, checking out in the current
# directory is easier as it hides some complexity. However, this
# results in possibly mixing packages from different projects,
# which makes package tracking not work at all.
old_tracking = conf.config['do_package_tracking']
conf.config['do_package_tracking'] = _collab_get_config_bool(apiurl, 'collab_do_package_tracking', default = False)
checkout_package(apiurl, branch_project, branch_package, expand_link=True)
conf.config['do_package_tracking'] = old_tracking
print('Package %s has been checked out.' % branch_package)
except Exception as e:
message = 'Error while checking out package %s: ' % branch_package
_collab_exception_print(e, message)
return (False, None, None)
# remove old helper files
for file in os.listdir(checkout_dir):
for helper in _osc_collab_helpers:
if file == helper:
path = os.path.join(checkout_dir, file)
os.unlink(path)
break
return (True, branch_project, branch_package)
#######################################################################
def _collab_get_package_with_valid_project(projects, package):
try:
pkg = OscCollabApi.get_package_details(projects, package)
except OscCollabWebError as e:
pkg = None
if pkg is None or pkg.project is None or not pkg.project.name:
print('Cannot find an appropriate project containing %s. You can use --project to override your project settings.' % package, file=sys.stderr)
return None
return pkg
#######################################################################
def _print_comment_after_setup(pkg, no_devel_project):
comment = OscCollabApi.get_package_comment(pkg.project.name, pkg.name, no_devel_project = no_devel_project)
if comment:
if comment.date:
date_str = ' on %s' % comment.date
else:
date_str = ''
print('Note the comment from %s%s on this package:' % (comment.user, date_str))
print(comment.indent())
#######################################################################
def _collab_setup(apiurl, username, projects, package, ignore_reserved = False, ignore_comment = False, no_reserve = False, no_devel_project = False, no_branch = False):
pkg = _collab_get_package_with_valid_project(projects, package)
if not pkg:
return
project = pkg.project.name
(setup, branch_project, branch_package) = _collab_setup_internal(apiurl, username, pkg, ignore_reserved, no_reserve, no_devel_project, no_branch)
if not setup:
return
print('Package %s has been prepared for work.' % branch_package)
if not ignore_comment:
_print_comment_after_setup(pkg, no_devel_project)
#######################################################################
def _collab_download_internal(url, dest_dir):
if not os.path.exists(dest_dir):
os.makedirs(dest_dir)
parsed_url = urlparse(url)
basename = os.path.basename(parsed_url.path)
if not basename:
# FIXME: workaround until we get a upstream_basename property for each
# package (would be needed for upstream hosted on sf, anyway).
# Currently needed for mkvtoolnix.
for field in parsed_url.query.split('&'):
try:
(key, value) = field.split('=', 1)
except ValueError:
value = field
if value.endswith('.gz') or value.endswith('.tgz') or value.endswith('.bz2'):
basename = os.path.basename(value)
if not basename:
raise OscCollabDownloadError('Cannot download %s: no basename in URL.' % url)
dest_file = os.path.join(dest_dir, basename)
# we download the file again if it already exists. Maybe the upstream
# tarball changed, eg. We could add an option to avoid this, but I feel
# like it won't happen a lot anyway.
if os.path.exists(dest_file):
os.unlink(dest_file)
try:
fin = urlopen(url)
except HTTPError as e:
raise OscCollabDownloadError('Cannot download %s: %s' % (url, e.msg))
fout = open(dest_file, 'wb')
while True:
try:
bytes = fin.read(500 * 1024)
if len(bytes) == 0:
break
fout.write(bytes)
except HTTPError as e:
fin.close()
fout.close()
os.unlink(dest_file)
raise OscCollabDownloadError('Error while downloading %s: %s' % (url, e.msg))
fin.close()
fout.close()
return dest_file
#######################################################################
def _collab_extract_diff_internal(directory, old_tarball, new_tarball):
def _cleanup(old, new, tmpdir):
if old:
old.close()
if new:
new.close()
shutil.rmtree(tmpdir)
def _lzma_hack(filename, tmpdir):
if not filename.endswith('.xz'):
return filename
dest = os.path.join(tmpdir, os.path.basename(filename))
shutil.copyfile(filename, dest)
subprocess.call(['xz', '-d', dest])
return dest[:-3]
# we need to make sure it's safe to extract the file
# see the warning in http://www.python.org/doc/lib/tarfile-objects.html
def _can_extract_with_trust(name):
if not name:
return False
if name[0] == '/':
return False
if name[0] == '.':
# only accept ./ if the first character is a dot
if len(name) == 1 or name[1] != '/':
return False
return True
def _extract_files(tar, path, whitelist):
if not tar or not path or not whitelist:
return
for tarinfo in tar:
if not _can_extract_with_trust(tarinfo.name):
continue
# we won't accept symlinks or hard links. It sounds weird to have
# this for the files we're interested in.
if not tarinfo.isfile():
continue
basename = os.path.basename(tarinfo.name)
if not basename in whitelist:
continue
tar.extract(tarinfo, path)
def _diff_files(old, new, dest):
if not new:
return (False, False)
if not old:
shutil.copyfile(new, dest)
return (True, False)
old_f = open(old)
old_lines = old_f.readlines()
old_f.close()
new_f = open(new)
new_lines = new_f.readlines()
new_f.close()
diff = difflib.unified_diff(old_lines, new_lines)
# diff is a generator, so we can't know if it's empty or not until we
# iterate over it. So if it's empty, we'll create an empty file, and
# remove it afterwards.
dest_f = open(dest, 'w')
# We first write what we consider useful and then write the complete
# diff for reference.
# This works because a well-formed NEWS/ChangeLog will only have new
# items added at the top, and therefore the useful diff is the addition
# at the top. This doesn't work for configure.{ac,in}, but that's fine.
# We need to cache the first lines, though, since diff is a generator
# and we don't have direct access to lines.
i = 0
pass_one_done = False
cached = []
for line in diff:
# we skip the first three lines of the diff
if not pass_one_done and i == 0 and line[:3] == '---':
cached.append(line)
i = 1
elif not pass_one_done and i == 1 and line[:3] == '+++':
cached.append(line)
i = 2
elif not pass_one_done and i == 2 and line[:2] == '@@':
cached.append(line)
i = 3
elif not pass_one_done and i == 3 and line[0] == '+':
cached.append(line)
dest_f.write(line[1:])
elif not pass_one_done:
# end of pass one: we write a note to help the user, and then
# write the cache
pass_one_done = True
note = '# Note by osc %s: here is the complete diff for reference.' % _osc_collab_alias
header = ''
for i in range(len(note)):
header += '#'
dest_f.write('\n')
dest_f.write('%s\n' % header)
dest_f.write('%s\n' % note)
dest_f.write('%s\n' % header)
dest_f.write('\n')
for cached_line in cached:
dest_f.write(cached_line)
dest_f.write(line)
else:
dest_f.write(line)
dest_f.close()
if not cached:
os.unlink(dest)
return (False, False)
return (True, True)
# FIXME: only needed until we switch to python >= 3.3
lzma_hack = not hasattr(tarfile.TarFile, 'xzopen')
tmpdir = tempfile.mkdtemp(prefix = 'osc-collab-')
old = None
new = None
if old_tarball and os.path.exists(old_tarball):
try:
if lzma_hack:
old_tarball = _lzma_hack(old_tarball, tmpdir)
old = tarfile.open(old_tarball)
except tarfile.TarError:
pass
else:
# this is not fatal: we can provide the
# NEWS/ChangeLog/configure.{ac,in} from the new tarball without a diff
pass
if new_tarball and os.path.exists(new_tarball):
new_tarball_basename = os.path.basename(new_tarball)
try:
if lzma_hack:
new_tarball = _lzma_hack(new_tarball, tmpdir)
new = tarfile.open(new_tarball)
except tarfile.TarError as e:
_cleanup(old, new, tmpdir)
raise OscCollabDiffError('Error when opening %s: %s' % (new_tarball_basename, e))
else:
_cleanup(old, new, tmpdir)
raise OscCollabDiffError('Cannot extract useful diff between tarballs: no new tarball.')
# make sure we have at least a subdirectory in tmpdir, since we'll extract
# files from two tarballs that might conflict
old_dir = os.path.join(tmpdir, 'old')
new_dir = os.path.join(tmpdir, 'new')
try:
if old:
err_tarball = os.path.basename(old_tarball)
_extract_files (old, old_dir, ['NEWS', 'ChangeLog', 'configure.ac', 'configure.in', 'meson.build', 'meson_options.txt'])
err_tarball = new_tarball_basename
_extract_files (new, new_dir, ['NEWS', 'ChangeLog', 'configure.ac', 'configure.in', 'meson.build', 'meson_options.txt'])
except (tarfile.ReadError, EOFError):
_cleanup(old, new, tmpdir)
raise OscCollabDiffError('Cannot extract useful diff between tarballs: %s is not a valid tarball.' % err_tarball)
if old:
old.close()
old = None
if new:
new.close()
new = None
# find toplevel NEWS/ChangeLog/configure.{ac,in} in the new tarball
if not os.path.exists(new_dir):
_cleanup(old, new, tmpdir)
raise OscCollabDiffError('Cannot extract useful diff between tarballs: no relevant files found in %s.' % new_tarball_basename)
new_dir_files = os.listdir(new_dir)
if len(new_dir_files) != 1:
_cleanup(old, new, tmpdir)
raise OscCollabDiffError('Cannot extract useful diff between tarballs: unexpected file hierarchy in %s.' % new_tarball_basename)
new_subdir = os.path.join(new_dir, new_dir_files[0])
if not os.path.isdir(new_subdir):
_cleanup(old, new, tmpdir)
raise OscCollabDiffError('Cannot extract useful diff between tarballs: unexpected file hierarchy in %s.' % new_tarball_basename)
new_news = os.path.join(new_subdir, 'NEWS')
if not os.path.exists(new_news) or not os.path.isfile(new_news):
new_news = None
new_changelog = os.path.join(new_subdir, 'ChangeLog')
if not os.path.exists(new_changelog) or not os.path.isfile(new_changelog):
new_changelog = None
new_configure = os.path.join(new_subdir, 'configure.ac')
if not os.path.exists(new_configure) or not os.path.isfile(new_configure):
new_configure = os.path.join(new_subdir, 'configure.in')
if not os.path.exists(new_configure) or not os.path.isfile(new_configure):
new_configure = None
new_meson = os.path.join(new_subdir, 'meson.build')
if not os.path.exists(new_meson) or not os.path.isfile(new_meson):
new_meson = None
new_mesonopt = os.path.join(new_subdir, 'meson_options.txt')
if not os.path.exists(new_mesonopt) or not os.path.isfile(new_mesonopt):
new_mesonopt = None
if not new_news and not new_changelog and not new_configure and not new_meson and not new_mesonopt:
_cleanup(old, new, tmpdir)
raise OscCollabDiffError('Cannot extract useful diff between tarballs: no relevant files found in %s.' % new_tarball_basename)
# find toplevel NEWS/ChangeLog/configure.{ac,in} in the old tarball
# not fatal
old_news = None
old_changelog = None
old_configure = None
old_meson = None
old_mesonopt = None
if os.path.exists(old_dir):
old_dir_files = os.listdir(old_dir)
else:
old_dir_files = []
if len(old_dir_files) == 1:
old_subdir = os.path.join(old_dir, old_dir_files[0])
if os.path.isdir(old_subdir):
old_news = os.path.join(old_subdir, 'NEWS')
if not os.path.exists(old_news) or not os.path.isfile(old_news):
old_news = None
old_changelog = os.path.join(old_subdir, 'ChangeLog')
if not os.path.exists(old_changelog) or not os.path.isfile(old_changelog):
old_changelog = None
old_configure = os.path.join(old_subdir, 'configure.ac')
if not os.path.exists(old_configure) or not os.path.isfile(old_configure):
old_configure = os.path.join(old_subdir, 'configure.in')
if not os.path.exists(old_configure) or not os.path.isfile(old_configure):
old_configure = None
old_meson = os.path.join(old_subdir, 'meson.build')
if not os.path.exists(old_meson) or not os.path.isfile(old_meson):
old_meson = None
old_mesonopt = os.path.join(old_subdir, 'meson_options.txt')
if not os.path.exists(old_mesonopt) or not os.path.isfile(old_mesonopt):
old_mesonopt = None
# Choose the most appropriate prefix for helper files, based on the alias
# that was used by the user
helper_prefix = _osc_collab_helper_prefixes[0]
for prefix in _osc_collab_helper_prefixes:
if _osc_collab_alias in prefix:
helper_prefix = prefix
break
# do the diff
news = os.path.join(directory, helper_prefix + 'NEWS')
(news_created, news_is_diff) = _diff_files(old_news, new_news, news)
changelog = os.path.join(directory, helper_prefix + 'ChangeLog')
(changelog_created, changelog_is_diff) = _diff_files(old_changelog, new_changelog, changelog)
configure = os.path.join(directory, helper_prefix + 'configure')
(configure_created, configure_is_diff) = _diff_files(old_configure, new_configure, configure)
meson = os.path.join(directory, helper_prefix + 'meson')
(meson_created, meson_is_diff) = _diff_files(old_meson, new_meson, meson)
mesonopt = os.path.join(directory, helper_prefix + 'meson_options')
(mesonopt_created, mesonopt_is_diff) = _diff_files(old_mesonopt, new_mesonopt, mesonopt)
# Note: we make osc ignore those helper file we created by modifying
# the exclude list of osc.core. See the top of this file.
_cleanup(old, new, tmpdir)
return (news, news_created, news_is_diff, changelog, changelog_created, changelog_is_diff, configure, configure_created, configure_is_diff, meson, meson_created, meson_is_diff, mesonopt, mesonopt_created, mesonopt_is_diff)
#######################################################################
def _collab_subst_defines(s, defines):
'''Replace macros like %{version} and %{name} in strings. Useful
for sources and patches '''
for key in defines.keys():
if s.find(key) != -1:
value = defines[key]
s = s.replace('%%{%s}' % key, value)
s = s.replace('%%%s' % key, value)
return s
def _collab_update_spec(spec_file, upstream_url, upstream_version):
if not os.path.exists(spec_file):
print('Cannot update %s: no such file.' % os.path.basename(spec_file), file=sys.stderr)
return (False, None, None, False)
elif not os.path.isfile(spec_file):
print('Cannot update %s: not a regular file.' % os.path.basename(spec_file), file=sys.stderr)
return (False, None, None, False)
re_spec_header_with_version = re.compile('^(# spec file for package \S*) \(Version \S*\)(.*)', re.IGNORECASE)
re_spec_define = re.compile('^%define\s+(\S*)\s+(\S*)', re.IGNORECASE)
re_spec_name = re.compile('^Name:\s*(\S*)', re.IGNORECASE)
re_spec_version = re.compile('^(Version:\s*)(\S*)', re.IGNORECASE)
re_spec_release = re.compile('^(Release:\s*)\S*', re.IGNORECASE)
re_spec_source = re.compile('^(Source0?:\s*)(\S*)', re.IGNORECASE)
re_spec_prep = re.compile('^%prep', re.IGNORECASE)
defines = {}
old_source = None
old_version = None
define_in_source = False
fin = open(spec_file, 'r')
(fdout, tmp) = tempfile.mkstemp(dir = os.path.dirname(spec_file))
# replace version and reset release
while True:
line = fin.readline()
if len(line) == 0:
break
match = re_spec_prep.match(line)
if match:
os.write(fdout, line.encode('utf-8'))
break
match = re_spec_header_with_version.match(line)
if match:
# We drop the "(Version XYZ)" part of the header
write_line = '%s%s\n' % (match.group(1), match.group(2))
os.write(fdout, write_line.encode('utf-8'))
continue
match = re_spec_define.match(line)
if match:
defines[match.group(1)] = _collab_subst_defines(match.group(2), defines)
os.write(fdout, line.encode('utf-8'))
continue
match = re_spec_name.match(line)
if match:
defines['name'] = match.group(1)
os.write(fdout, line.encode('utf-8'))
continue
match = re_spec_version.match(line)
if match:
defines['version'] = match.group(2)
old_version = _collab_subst_defines(match.group(2), defines)
write_line = '%s%s\n' % (match.group(1), upstream_version)
os.write(fdout, write_line.encode('utf-8'))
continue
match = re_spec_release.match(line)
if match:
write_line = '%s0\n' % match.group(1)
os.write(fdout, write_line.encode('utf-8'))
continue
match = re_spec_source.match(line)
if match:
old_source = os.path.basename(match.group(2))
if upstream_url:
non_basename = os.path.dirname(upstream_url)
new_source = os.path.basename(upstream_url)
# Use _name in favor of name, as if _name exists, it's for a
# good reason
for key in [ '_name', 'name', 'version' ]:
if key in defines:
if key == 'version':
new_source = new_source.replace(upstream_version, '%%{%s}' % key)
else:
new_source = new_source.replace(defines[key], '%%{%s}' % key)
# Only use the URL as source if the basename looks like the
# real name of the file.
# If '?' is in the basename, then we likely have some dynamic
# page to download the file, which means the wrong basename.
# For instance:
# download.php?package=01&release=61&file=01&dummy=gwenhywfar-4.1.0.tar.gz
if '?' not in new_source:
write_line = '%s%s/%s\n' % (match.group(1), non_basename, new_source)
os.write(fdout, write_line.encode('utf-8'))
continue
os.write(fdout, line.encode('utf-8'))
continue
os.write(fdout, line.encode('utf-8'))
# wild read/write to finish quickly
while True:
bytes = fin.read(10 * 1024)
if len(bytes) == 0:
break
os.write(fdout, bytes.encode('utf-8'))
fin.close()
os.close(fdout)
os.rename(tmp, spec_file)
if old_source and old_source.find('%') != -1:
for key in defines.keys():
if old_source.find(key) != -1:
old_source = old_source.replace('%%{%s}' % key, defines[key])
old_source = old_source.replace('%%%s' % key, defines[key])
if key not in [ 'name', '_name', 'version' ]:
define_in_source = True
return (True, old_source, old_version, define_in_source)
#######################################################################
def _collab_update_changes(changes_file, upstream_version, realname, email):
if not os.path.exists(changes_file):
print('Cannot update %s: no such file.' % os.path.basename(changes_file), file=sys.stderr)
return False
elif not os.path.isfile(changes_file):
print('Cannot update %s: not a regular file.' % os.path.basename(changes_file), file=sys.stderr)
return False
(fdout, tmp) = tempfile.mkstemp(dir = os.path.dirname(changes_file))
old_lc_time = locale.setlocale(locale.LC_TIME)
old_tz = os.getenv('TZ')
locale.setlocale(locale.LC_TIME, 'C')
os.putenv('TZ', 'UTC')
time.tzset()
os.write(fdout, b'-------------------------------------------------------------------\n')
write_line = '%s - %s <%s>\n' % (time.strftime("%a %b %e %H:%M:%S %Z %Y"), realname, email)
os.write(fdout, write_line.encode('utf-8'))
os.write(fdout, b'\n')
write_line = '- Update to version %s:\n' % upstream_version
os.write(fdout, write_line.encode('utf-8'))
os.write(fdout, b' + \n')
os.write(fdout, b'\n')
locale.setlocale(locale.LC_TIME, old_lc_time)
if old_tz:
os.putenv('TZ', old_tz)
else:
os.unsetenv('TZ')
time.tzset()
fin = open(changes_file, 'r')
while True:
bytes = fin.read(10 * 1024)
if len(bytes) == 0:
break
os.write(fdout, bytes.encode('utf-8'))
fin.close()
os.close(fdout)
os.rename(tmp, changes_file)
return True
#######################################################################
def _collab_quilt_package(spec_file):
def _cleanup(null, tmpdir):
null.close()
shutil.rmtree(tmpdir)
null = open('/dev/null', 'w')
tmpdir = tempfile.mkdtemp(prefix = 'osc-collab-')
# setup with quilt
sourcedir = os.path.dirname(os.path.realpath(spec_file))
popen = subprocess.Popen(['quilt', 'setup', '-d', tmpdir, '--sourcedir', sourcedir, spec_file], stdout = null, stderr = null)
retval = popen.wait()
if retval != 0:
_cleanup(null, tmpdir)
print('Cannot apply patches: \'quilt setup\' failed.', file=sys.stderr)
return False
# apply patches for all subdirectories
for directory in os.listdir(tmpdir):
dir = os.path.join(tmpdir, directory)
if not os.path.isdir(dir):
continue
# there's no patch, so just continue
if not os.path.exists(os.path.join(dir, 'patches')):
continue
popen = subprocess.Popen(['quilt', 'push', '-a', '-q'], cwd = dir, stdout = null, stderr = null)
retval = popen.wait()
if retval != 0:
_cleanup(null, tmpdir)
print('Cannot apply patches: \'quilt push -a\' failed.', file=sys.stderr)
return False
_cleanup(null, tmpdir)
return True
#######################################################################
def _collab_update(apiurl, username, realname, email, projects, package, ignore_reserved = False, ignore_comment = False, no_reserve = False, no_devel_project = False, no_branch = False):
if len(projects) == 1:
project = projects[0]
try:
pkg = OscCollabApi.get_package_details(project, package)
except OscCollabWebError as e:
print(e.msg, file=sys.stderr)
return
else:
pkg = _collab_get_package_with_valid_project(projects, package)
if not pkg:
return
project = pkg.project.name
# check that the project is up-to-date wrt parent project
if pkg.parent_more_recent():
print('Package %s is more recent in %s (%s) than in %s (%s). Please synchronize %s first.' % (package, pkg.parent_project, pkg.parent_version, project, pkg.version, project))
return
# check that an update is really needed
if not pkg.upstream_version:
print('No information about upstream version of package %s is available. Assuming it is not up-to-date.' % package)
elif pkg.upstream_version == '--':
print('Package %s has no upstream.' % package)
return
elif pkg.devel_project and pkg.needs_update() and not no_devel_project and not pkg.devel_needs_update():
if not pkg.devel_package or pkg.devel_package == package:
print('Package %s is already up-to-date in its development project (%s).' % (package, pkg.devel_project))
else:
print('Package %s is already up-to-date in its development project (%s/%s).' % (package, pkg.devel_project, pkg.devel_package))
return
elif not pkg.needs_update():
print('Package %s is already up-to-date.' % package)
return
(setup, branch_project, branch_package) = _collab_setup_internal(apiurl, username, pkg, ignore_reserved, no_reserve, no_devel_project, no_branch)
if not setup:
return
package_dir = branch_package
# edit the version tag in the .spec files
# not fatal if fails
spec_file = os.path.join(package_dir, package + '.spec')
if not os.path.exists(spec_file) and package != branch_package:
spec_file = os.path.join(package_dir, branch_package + '.spec')
(updated, old_tarball, old_version, define_in_source) = _collab_update_spec(spec_file, pkg.upstream_url, pkg.upstream_version)
if old_tarball:
old_tarball_with_dir = os.path.join(package_dir, old_tarball)
else:
old_tarball_with_dir = None
if old_version and old_version == pkg.upstream_version:
if no_branch:
print('Package %s is already up-to-date (the database might not be up-to-date).' % branch_package)
else:
print('Package %s is already up-to-date (in your branch only, or the database is not up-to-date).' % branch_package)
return
if define_in_source:
print('WARNING: the Source tag in %s is using some define that might not be valid anymore.' % spec_file)
if updated:
print('%s has been prepared.' % os.path.basename(spec_file))
# warn if there are other spec files which might need an update
for file in os.listdir(package_dir):
if file.endswith('.spec') and file != os.path.basename(spec_file):
print('WARNING: %s might need a manual update.' % file)
# start adding an entry to .changes
# not fatal if fails
changes_file = os.path.join(package_dir, package + '.changes')
if not os.path.exists(changes_file) and package != branch_package:
changes_file = os.path.join(package_dir, branch_package + '.changes')
if _collab_update_changes(changes_file, pkg.upstream_version, realname, email):
print('%s has been prepared.' % os.path.basename(changes_file))
# warn if there are other spec files which might need an update
for file in os.listdir(package_dir):
if file.endswith('.changes') and file != os.path.basename(changes_file):
print('WARNING: %s might need a manual update.' % file)
# download the upstream tarball
# fatal if fails
if not pkg.upstream_url:
print('Cannot download latest upstream tarball for %s: no URL defined.' % package, file=sys.stderr)
return
print('Looking for the upstream tarball...')
try:
upstream_tarball = _collab_download_internal(pkg.upstream_url, package_dir)
except OscCollabDownloadError as e:
print(e.msg, file=sys.stderr)
return
if not upstream_tarball:
print('No upstream tarball downloaded for %s.' % package, file=sys.stderr)
return
else:
upstream_tarball_basename = os.path.basename(upstream_tarball)
# same file as the old one: oops, we don't want to do anything weird
# there
if upstream_tarball_basename == old_tarball:
old_tarball = None
old_tarball_with_dir = None
print('%s has been downloaded.' % upstream_tarball_basename)
# check integrity of the downloaded file
# fatal if fails (only if md5 exists)
# TODO
# extract NEWS & ChangeLog from the old + new tarballs, and do a diff
# not fatal if fails
print('Extracting useful diff between tarballs (NEWS, ChangeLog, configure.{ac,in})...')
try:
(news, news_created, news_is_diff, changelog, changelog_created, changelog_is_diff, configure, configure_created, configure_is_diff, meson, meson_created, meson_is_diff, mesonopt, mesonopt_created, mesonopt_is_diff) = _collab_extract_diff_internal(package_dir, old_tarball_with_dir, upstream_tarball)
except OscCollabDiffError as e:
print(e.msg, file=sys.stderr)
else:
if news_created:
news_basename = os.path.basename(news)
if news_is_diff:
print('NEWS between %s and %s is available in %s' % (old_tarball, upstream_tarball_basename, news_basename))
else:
print('Complete NEWS of %s is available in %s' % (upstream_tarball_basename, news_basename))
else:
print('No NEWS information found.')
if changelog_created:
changelog_basename = os.path.basename(changelog)
if changelog_is_diff:
print('ChangeLog between %s and %s is available in %s' % (old_tarball, upstream_tarball_basename, changelog_basename))
else:
print('Complete ChangeLog of %s is available in %s' % (upstream_tarball_basename, changelog_basename))
else:
print('No ChangeLog information found.')
if configure_created:
configure_basename = os.path.basename(configure)
if configure_is_diff:
print('Diff in configure.{ac,in} between %s and %s is available in %s' % (old_tarball, upstream_tarball_basename, configure_basename))
else:
print('Complete configure.{ac,in} of %s is available in %s' % (upstream_tarball_basename, configure_basename))
else:
print('No configure.{ac,in} information found (tarball is probably not using autotools).')
if meson_created:
meson_basename = os.path.basename(meson)
if meson_is_diff:
print('Diff in meson.build between %s and %s is available in %s' % (old_tarball, upstream_tarball_basename, meson_basename))
else:
print('Complete meson.build of %s is available in %s' % (upstream_tarball_basename, meson_basename))
else:
print('No meson.build information found (tarball is probably not using meson).')
if mesonopt_created:
mesonopt_basename = os.path.basename(mesonopt)
if mesonopt_is_diff:
print('Diff in meson_options.txt between %s and %s is available in %s' % (old_tarball, upstream_tarball_basename, mesonopt_basename))
else:
print('Complete meson_options.txt of %s is available in %s' % (upstream_tarball_basename, mesonopt_basename))
else:
print('No meson_options.txt information found (tarball is probably not using meson or the build has no options).')
# try applying the patches with rpm quilt
# not fatal if fails
if _collab_is_program_in_path('quilt'):
print('Running quilt...')
if _collab_quilt_package(spec_file):
print('Patches still apply.')
else:
print('WARNING: make sure that all patches apply before submitting.')
else:
print('quilt is not available.')
print('WARNING: make sure that all patches apply before submitting.')
# 'osc add newfile.tar.bz2' and 'osc del oldfile.tar.bz2'
# not fatal if fails
osc_package = filedir_to_pac(package_dir)
if old_tarball_with_dir:
if os.path.exists(old_tarball_with_dir):
osc_package.delete_file(old_tarball, force=True)
print('%s has been removed from the package.' % old_tarball)
else:
print('WARNING: the previous tarball could not be found. Please manually remove it.')
else:
print('WARNING: the previous tarball could not be found. Please manually remove it.')
osc_package.addfile(upstream_tarball_basename)
print('%s has been added to the package.' % upstream_tarball_basename)
print('Package %s has been prepared for the update.' % branch_package)
print('After having updated %s, you can use \'osc build\' to start a local build or \'osc %s build\' to start a build on the build service.' % (os.path.basename(changes_file), _osc_collab_alias))
if not ignore_comment:
_print_comment_after_setup(pkg, no_devel_project)
# TODO add a note about checking if patches are still needed, buildrequires
# & requires
#######################################################################
def _collab_forward(apiurl, user, projects, request_id, no_supersede = False):
try:
int_request_id = int(request_id)
except ValueError:
print('%s is not a valid request id.' % (request_id), file=sys.stderr)
return
request = OscCollabObs.get_request(request_id)
if request is None:
return
dest_package = request.target_package
dest_project = request.target_project
if dest_project not in projects:
if len(projects) == 1:
print('Submission request %s is for %s and not %s. You can use --project to override your project settings.' % (request_id, dest_project, projects[0]), file=sys.stderr)
else:
print('Submission request %s is for %s. You can use --project to override your project settings.' % (request_id, dest_project), file=sys.stderr)
return
if request.state != 'new':
print('Submission request %s is not new.' % request_id, file=sys.stderr)
return
try:
pkg = OscCollabApi.get_package_details((dest_project,), dest_package)
if not pkg or not pkg.parent_project:
print('No parent project for %s/%s.' % (dest_project, dest_package), file=sys.stderr)
return
except OscCollabWebError as e:
print('Cannot get parent project of %s/%s.' % (dest_project, dest_package), file=sys.stderr)
return
try:
devel_project = show_develproject(apiurl, pkg.parent_project, pkg.parent_package)
except HTTPError as e:
print('Cannot get development project for %s/%s: %s' % (pkg.parent_project, pkg.parent_package, e.msg), file=sys.stderr)
return
if devel_project != dest_project:
print('Development project for %s/%s is %s, but package has been submitted to %s.' % (pkg.parent_project, pkg.parent_package, devel_project, dest_project), file=sys.stderr)
return
if not OscCollabObs.change_request_state(request_id, 'accepted', 'Forwarding to %s' % pkg.parent_project):
return
# TODO: cancel old requests from request.dst_project to parent project
result = create_submit_request(apiurl,
dest_project, dest_package,
pkg.parent_project, pkg.parent_package,
request.description + ' (forwarded request %s from %s)' % (request_id, request.by))
print('Submission request %s has been forwarded to %s (request id: %s).' % (request_id, pkg.parent_project, result))
if not no_supersede:
for old_id in OscCollabObs.supersede_old_requests(user, pkg.parent_project, pkg.parent_package, result):
print('Previous submission request %s has been superseded.' % old_id)
#######################################################################
def _collab_osc_package_pending_commit(osc_package):
# ideally, we could use osc_package.todo, but it's not set by default.
# So we just look at all files.
for filename in osc_package.filenamelist + osc_package.filenamelist_unvers:
status = osc_package.status(filename)
if status in ['A', 'M', 'D']:
return True
return False
def _collab_osc_package_commit(osc_package, msg):
osc_package.commit(msg)
# See bug #436932: Package.commit() leads to outdated internal data.
osc_package.update_datastructs()
#######################################################################
def _collab_package_set_meta(apiurl, project, package, meta, error_msg_prefix = ''):
if error_msg_prefix:
error_str = error_msg_prefix + ': %s'
else:
error_str = 'Cannot set metadata for %s in %s: %%s' % (package, project)
(fdout, tmp) = tempfile.mkstemp()
os.write(fdout, meta)
os.close(fdout)
meta_url = make_meta_url('pkg', (quote_plus(project), quote_plus(package)), apiurl)
failed = False
try:
http_PUT(meta_url, file=tmp)
except HTTPError as e:
print(error_str % e.msg, file=sys.stderr)
failed = True
os.unlink(tmp)
return not failed
def _collab_enable_build(apiurl, project, package, meta, repos, archs):
if len(archs) == 0:
return (True, False)
package_node = ET.XML(meta)
meta_xml = ET.ElementTree(package_node)
build_node = package_node.find('build')
if not build_node:
build_node = ET.Element('build')
package_node.append(build_node)
enable_found = {}
for repo in repos:
enable_found[repo] = {}
for arch in archs:
enable_found[repo][arch] = False
# remove disable before adding enable
for node in build_node.findall('disable'):
repo = node.get('repository')
arch = node.get('arch')
if repo and repo not in repos:
continue
if arch and arch not in archs:
continue
build_node.remove(node)
for node in build_node.findall('enable'):
repo = node.get('repository')
arch = node.get('arch')
if repo and repo not in repos:
continue
if arch and arch not in archs:
continue
if repo and arch:
enable_found[repo][arch] = True
elif repo:
for arch in enable_found[repo].keys():
enable_found[repo][arch] = True
elif arch:
for repo in enable_found.keys():
enable_found[repo][arch] = True
else:
for repo in enable_found.keys():
for arch in enable_found[repo].keys():
enable_found[repo][arch] = True
for repo in repos:
for arch in archs:
if not enable_found[repo][arch]:
node = ET.Element('enable', { 'repository': repo, 'arch': arch})
build_node.append(node)
all_true = True
for repo in enable_found.keys():
for value in enable_found[repo].values():
if not value:
all_true = False
break
if all_true:
return (True, False)
meta = ET.tostring(package_node)
if _collab_package_set_meta(apiurl, project, package, meta, 'Error while enabling build of package on the build service'):
return (True, True)
else:
return (False, False)
def _collab_get_latest_package_rev_built(apiurl, project, repo, arch, package, verbose_error = True):
url = makeurl(apiurl, ['build', project, repo, arch, package, '_history'])
try:
history = http_GET(url)
except HTTPError as e:
if verbose_error:
print('Cannot get build history: %s' % e.msg, file=sys.stderr)
return (False, None, None)
try:
root = ET.parse(history).getroot()
except SyntaxError:
history.close ()
return (False, None, None)
max_time = 0
rev = None
srcmd5 = None
for node in root.findall('entry'):
t = int(node.get('time'))
if t <= max_time:
continue
srcmd5 = node.get('srcmd5')
rev = node.get('rev')
history.close ()
return (True, srcmd5, rev)
def _collab_print_build_status(build_state, header, error_line, hint = False):
def get_str_repo_arch(repo, arch, show_repos):
if show_repos:
return '%s/%s' % (repo, arch)
else:
return arch
print('%s:' % header)
repos = build_state.keys()
if not repos or len(repos) == 0:
print(' %s' % error_line)
return
repos_archs = []
for repo in repos:
archs = build_state[repo].keys()
for arch in archs:
repos_archs.append((repo, arch))
one_result = True
if len(repos_archs) == 0:
print(' %s' % error_line)
return
show_hint = False
show_repos = len(repos) > 1
repos_archs.sort()
max_len = 0
for (repo, arch) in repos_archs:
l = len(get_str_repo_arch(repo, arch, show_repos))
if l > max_len:
max_len = l
# 4: because we also have a few other characters (see left variable)
format = '%-' + str(max_len + 4) + 's%s'
for (repo, arch) in repos_archs:
if not build_state[repo][arch]['scheduler'] and build_state[repo][arch]['result'] in ['failed']:
show_hint = True
left = ' %s: ' % get_str_repo_arch(repo, arch, show_repos)
if build_state[repo][arch]['result'] in ['unresolved', 'broken', 'blocked', 'finished', 'signing'] and build_state[repo][arch]['details']:
status = '%s (%s)' % (build_state[repo][arch]['result'], build_state[repo][arch]['details'])
else:
status = build_state[repo][arch]['result']
if build_state[repo][arch]['scheduler']:
status = '%s (was: %s)' % (build_state[repo][arch]['scheduler'], status)
print(format % (left, status))
if show_hint and hint:
for (repo, arch) in repos_archs:
if build_state[repo][arch]['result'] == 'failed':
print('You can see the log of the failed build with: osc buildlog %s %s' % (repo, arch))
def _collab_build_get_results(apiurl, project, repos, package, archs, srcmd5, rev, state, error_counter, verbose_error):
try:
results = show_results_meta(apiurl, project, package=package)
if len(results) == 0:
if verbose_error:
print('Error while getting build results of package on the build service: empty results', file=sys.stderr)
error_counter += 1
return (True, False, error_counter, state)
# reset the error counter
error_counter = 0
except (HTTPError, BadStatusLine) as e:
if verbose_error:
print('Error while getting build results of package on the build service: %s' % e, file=sys.stderr)
error_counter += 1
return (True, False, error_counter, state)
res_root = ET.XML(b''.join(results))
detailed_results = {}
repos_archs = []
for node in res_root.findall('result'):
repo = node.get('repository')
# ignore the repo if it's not one we explicitly use
if not repo in repos:
continue
arch = node.get('arch')
# ignore the archs we didn't explicitly enabled: this ensures we care
# only about what is really important to us
if not arch in archs:
continue
scheduler_state = node.get('state')
scheduler_dirty = node.get('dirty') == 'true'
status_node = node.find('status')
try:
status = status_node.get('code')
except:
# code can be missing when package is too new:
status = 'unknown'
try:
details = status_node.find('details').text
except:
details = None
if not repo in detailed_results:
detailed_results[repo] = {}
detailed_results[repo][arch] = {}
detailed_results[repo][arch]['status'] = status
detailed_results[repo][arch]['details'] = details
detailed_results[repo][arch]['scheduler_state'] = scheduler_state
detailed_results[repo][arch]['scheduler_dirty'] = scheduler_dirty
repos_archs.append((repo, arch))
# evaluate the status: do we need to give more time to the build service?
# Was the build successful?
bs_not_ready = False
do_not_wait_for_bs = False
build_successful = True
# A bit paranoid, but it seems it happened to me once...
if len(repos_archs) == 0:
bs_not_ready = True
build_successful = False
if verbose_error:
print('Build service did not return any information.', file=sys.stderr)
error_counter += 1
for (repo, arch) in repos_archs:
# first look at the state of the scheduler
scheduler_state = detailed_results[repo][arch]['scheduler_state']
scheduler_dirty = detailed_results[repo][arch]['scheduler_dirty']
if detailed_results[repo][arch]['scheduler_dirty']:
scheduler_active = 'waiting for scheduler'
elif scheduler_state in ['unknown', 'scheduling']:
scheduler_active = 'waiting for scheduler'
elif scheduler_state in ['blocked']:
scheduler_active = 'blocked by scheduler'
else:
# we don't care about the scheduler state
scheduler_active = ''
need_rebuild = False
value = detailed_results[repo][arch]['status']
# the scheduler is working, or the result has changed since last time,
# so we won't trigger a rebuild
if scheduler_active or state[repo][arch]['result'] != value:
state[repo][arch]['rebuild'] = -1
# build is done, but not successful
if scheduler_active or value not in ['succeeded', 'excluded']:
build_successful = False
# we just ignore the status if the scheduler is active, since we know
# we need to wait for the build service
if scheduler_active:
bs_not_ready = True
# build is happening or will happen soon
elif value in ['scheduled', 'building', 'dispatching', 'finished', 'signing']:
bs_not_ready = True
# sometimes, the scheduler forgets about a package in 'blocked' state,
# so we have to force a rebuild
elif value in ['blocked']:
bs_not_ready = True
need_rebuild = True
# build has failed for an architecture: no need to wait for other
# architectures to know that there's a problem
elif value in ['failed', 'unresolved', 'broken']:
do_not_wait_for_bs = True
# 'disabled' => the build service didn't take into account
# the change we did to the meta yet (eg).
elif value in ['unknown', 'disabled']:
bs_not_ready = True
need_rebuild = True
# build is done, but is it for the latest version?
elif value in ['succeeded']:
# check that the build is for the version we have
(success, built_srcmd5, built_rev) = _collab_get_latest_package_rev_built(apiurl, project, repo, arch, package, verbose_error)
if not success:
detailed_results[repo][arch]['status'] = 'succeeded, but maybe not up-to-date'
error_counter += 1
# we don't know what's going on, so we'll contact the build
# service again
bs_not_ready = True
else:
# reset the error counter
error_counter = 0
#FIXME: "revision" seems to not have the same meaning for the
# build history and for the local package. See bug #436781
# (bnc). So, we just ignore the revision for now.
#if (built_srcmd5, built_rev) != (srcmd5, rev):
if built_srcmd5 != srcmd5:
need_rebuild = True
detailed_results[repo][arch]['status'] = 'rebuild needed'
if not scheduler_active and need_rebuild and state[repo][arch]['rebuild'] == 0:
bs_not_ready = True
print('Triggering rebuild for %s as of %s' % (arch, time.strftime('%X (%x)', time.localtime())))
try:
rebuild(apiurl, project, package, repo, arch)
# reset the error counter
error_counter = 0
except (HTTPError, BadStatusLine) as e:
if verbose_error:
print('Cannot trigger rebuild for %s: %s' % (arch, e), file=sys.stderr)
error_counter += 1
state[repo][arch]['scheduler'] = scheduler_active
state[repo][arch]['result'] = detailed_results[repo][arch]['status']
state[repo][arch]['details'] = detailed_results[repo][arch]['details']
# Update the timeout data
if scheduler_active:
pass
if state[repo][arch]['result'] in ['blocked']:
# if we're blocked, maybe the scheduler forgot about us, so
# schedule a rebuild every 60 minutes. The main use case is when
# you leave the plugin running for a whole night.
if state[repo][arch]['rebuild'] <= 0:
state[repo][arch]['rebuild-timeout'] = 60
state[repo][arch]['rebuild'] = state[repo][arch]['rebuild-timeout']
# note: it's correct to decrement even if we start with a new value
# of timeout, since if we don't, it adds 1 minute (ie, 5 minutes
# instead of 4, eg)
state[repo][arch]['rebuild'] = state[repo][arch]['rebuild'] - 1
elif state[repo][arch]['result'] in ['unknown', 'disabled', 'rebuild needed']:
# if we're in this unexpected state, force the scheduler to do
# something
if state[repo][arch]['rebuild'] <= 0:
# we do some exponential timeout until 60 minutes. We skip
# timeouts of 1 and 2 minutes since they're quite short.
if state[repo][arch]['rebuild-timeout'] > 0:
state[repo][arch]['rebuild-timeout'] = min(60, state[repo][arch]['rebuild-timeout'] * 2)
else:
state[repo][arch]['rebuild-timeout'] = 4
state[repo][arch]['rebuild'] = state[repo][arch]['rebuild-timeout']
# note: it's correct to decrement even if we start with a new value
# of timeout, since if we don't, it adds 1 minute (ie, 5 minutes
# instead of 4, eg)
state[repo][arch]['rebuild'] = state[repo][arch]['rebuild'] - 1
else:
# else, we make sure we won't manually trigger a rebuild
state[repo][arch]['rebuild'] = -1
state[repo][arch]['rebuild-timeout'] = -1
if do_not_wait_for_bs:
bs_not_ready = False
return (bs_not_ready, build_successful, error_counter, state)
def _collab_build_wait_loop(apiurl, project, repos, package, archs, srcmd5, rev):
# seconds we wait before looking at the results on the build service
check_frequency = 60
max_errors = 10
build_successful = False
print_status = False
error_counter = 0
last_check = 0
state = {}
# When we want to trigger a rebuild for this repo/arch.
# The initial value is 1 since we don't want to trigger a rebuild the first
# time when the state is 'disabled' since the state might have changed very
# recently (if we updated the metadata ourselves), and the build service
# might have an old build that it can re-use instead of building again.
for repo in repos:
state[repo] = {}
for arch in archs:
state[repo][arch] = {}
state[repo][arch]['rebuild'] = -1
state[repo][arch]['rebuild-timeout'] = -1
state[repo][arch]['scheduler'] = ''
state[repo][arch]['result'] = 'unknown'
state[repo][arch]['details'] = ''
print("Building on %s..." % ', '.join(repos))
print("You can press enter to get the current status of the build.")
# It's important to start the loop by downloading results since we might
# already have successful builds, and we don't want to wait to know that.
try:
while True:
# get build status if we don't have a recent status
now = time.time()
if now - last_check >= 58:
# 58s since sleep() is not 100% precise and we don't want to miss
# one turn
last_check = now
(need_to_continue, build_successful, error_counter, state) = _collab_build_get_results(apiurl, project, repos, package, archs, srcmd5, rev, state, error_counter, print_status)
# just stop if there are too many errors
if error_counter > max_errors:
print('Giving up: too many consecutive errors when contacting the build service.', file=sys.stderr)
break
else:
# we didn't download the results, so we want to continue anyway
need_to_continue = True
if print_status:
header = 'Status as of %s [checking the status every %d seconds]' % (time.strftime('%X (%x)', time.localtime(last_check)), check_frequency)
_collab_print_build_status(state, header, 'no results returned by the build service')
if not need_to_continue:
break
# and now wait for input/timeout
print_status = False
# wait check_frequency seconds or for user input
now = time.time()
if now - last_check < check_frequency:
wait = check_frequency - (now - last_check)
else:
wait = check_frequency
res = select.select([sys.stdin], [], [], wait)
# we have user input
if len(res[0]) > 0:
print_status = True
# empty sys.stdin
sys.stdin.readline()
# we catch this exception here since we might need to revert some metadata
except KeyboardInterrupt:
print('')
print('Interrupted: not waiting for the build to finish. Cleaning up...')
return (build_successful, state)
#######################################################################
def _collab_autodetect_repo(apiurl, project):
try:
meta_lines = show_project_meta(apiurl, project)
meta = b''.join(meta_lines)
except HTTPError:
return None
try:
root = ET.XML(meta)
except SyntaxError:
return None
repos = []
for node in root.findall('repository'):
name = node.get('name')
if name:
repos.append(name)
if not repos:
return None
# This is the list of repositories we prefer, the first one being the
# preferred one.
# + snapshot/standard is what openSUSE:Factory uses, and some other
# projects might use this too (snapshot is like standard, except that
# packages won't block before getting built).
# + openSUSE_Factory is the usual repository for devel projects.
# + openSUSE-Factory is a variant of the one above (typo in some project
# config?)
for repo in [ 'snapshot', 'standard', 'openSUSE_Factory', 'openSUSE-Factory' ]:
if repo in repos:
return repo
# No known repository? We try to use the last one named openSUSE* (last
# one because we want the most recent version of openSUSE).
opensuse_repos = [ repo for repo in repos if repo.startswith('openSUSE') ]
if len(opensuse_repos) > 0:
opensuse_repos.sort(reverse = True)
return opensuse_repos[0]
# Still no solution? Let's just take the first one...
return repos[0]
#######################################################################
def _collab_build_internal(apiurl, osc_package, repos, archs):
project = osc_package.prjname
package = osc_package.name
if '!autodetect!' in repos:
print('Autodetecting the most appropriate repository for the build...')
repos.remove('!autodetect!')
repo = _collab_autodetect_repo(apiurl, project)
if repo:
repos.append(repo)
if len(repos) == 0:
print('Error while setting up the build: no usable repository.', file=sys.stderr)
return False
repos.sort()
archs.sort()
# check that build is enabled for this package in this project, and if this
# is not the case, enable it
try:
meta_lines = show_package_meta(apiurl, project, package)
except HTTPError as e:
print('Error while checking if package is set to build: %s' % e.msg, file=sys.stderr)
return False
meta = b''.join(meta_lines)
(success, changed_meta) = _collab_enable_build(apiurl, project, package, meta, repos, archs)
if not success:
return False
# loop to periodically check the status of the build (and eventually
# trigger rebuilds if necessary)
(build_success, build_state) = _collab_build_wait_loop(apiurl, project, repos, package, archs, osc_package.srcmd5, osc_package.rev)
if not build_success:
_collab_print_build_status(build_state, 'Status', 'no status known: osc got interrupted?', hint=True)
# disable build for package in this project if we manually enabled it
# (we just reset to the old settings)
if changed_meta:
_collab_package_set_meta(apiurl, project, package, meta, 'Error while resetting build settings of package on the build service')
return build_success
#######################################################################
def _collab_build(apiurl, user, projects, msg, repos, archs):
try:
osc_package = filedir_to_pac('.')
except oscerr.NoWorkingCopy as e:
print(e, file=sys.stderr)
return
project = osc_package.prjname
package = osc_package.name
committed = False
# commit if there are local changes
if _collab_osc_package_pending_commit(osc_package):
if not msg:
msg = edit_message()
_collab_osc_package_commit(osc_package, msg)
committed = True
build_success = _collab_build_internal(apiurl, osc_package, repos, archs)
if build_success:
print('Package successfully built on the build service.')
#######################################################################
def _collab_build_submit(apiurl, user, projects, msg, repos, archs, forward = False, no_unreserve = False, no_supersede = False):
try:
osc_package = filedir_to_pac('.')
except oscerr.NoWorkingCopy as e:
print(e, file=sys.stderr)
return
project = osc_package.prjname
package = osc_package.name
# do some preliminary checks on the package/project: it has to be
# a branch of a development project
if not osc_package.islink():
print('Package is not a link.', file=sys.stderr)
return
parent_project = osc_package.linkinfo.project
if not parent_project in projects:
if len(projects) == 1:
print('Package links to project %s and not %s. You can use --project to override your project settings.' % (parent_project, projects[0]), file=sys.stderr)
else:
print('Package links to project %s. You can use --project to override your project settings.' % parent_project, file=sys.stderr)
return
if not project.startswith('home:%s:branches' % user):
print('Package belongs to project %s which does not look like a branch project.' % project, file=sys.stderr)
return
if project != 'home:%s:branches:%s' % (user, parent_project):
print('Package belongs to project %s which does not look like a branch project for %s.' % (project, parent_project), file=sys.stderr)
return
# get the message that will be used for commit & request
if not msg:
msg = edit_message(footer='This message will be used for the commit (if necessary) and the request.\n')
committed = False
# commit if there are local changes
if _collab_osc_package_pending_commit(osc_package):
_collab_osc_package_commit(osc_package, msg)
committed = True
build_success = _collab_build_internal(apiurl, osc_package, repos, archs)
# if build successful, submit
if build_success:
result = create_submit_request(apiurl,
project, package,
parent_project, package,
msg)
print('Package submitted to %s (request id: %s).' % (parent_project, result))
if not no_supersede:
for old_id in OscCollabObs.supersede_old_requests(user, parent_project, package, result):
print('Previous submission request %s has been superseded.' % old_id)
if forward:
# we volunteerly restrict the project list to parent_project for
# self-consistency and more safety
_collab_forward(apiurl, user, [ parent_project ], result, no_supersede = no_supersede)
if not no_unreserve:
try:
reservation = OscCollabApi.is_package_reserved((parent_project,), package, no_devel_project = True)
if reservation and reservation.user == user:
_collab_unreserve((parent_project,), (package,), user, no_devel_project = True)
except OscCollabWebError as e:
print(e.msg, file=sys.stderr)
else:
print('Package was not submitted to %s' % parent_project)
#######################################################################
# TODO
# Add a commit method.
# This will make some additional checks:
# + if we used update, we can initialize a list of patches/sources
# before any change. This way, on the commit, we can look if the
# remaining files are still referenced in the .spec, and if not
# complain if the file hasn't been removed from the directory.
# We can also complain if a file hasn't been added with osc add,
# while it's referenced.
# + complain if the lines in .changes are too long
#######################################################################
# Unfortunately, as of Python 2.5, ConfigParser does not know how to
# preserve a config file: it removes comments and reorders stuff.
# This is a dumb function to append a value to a section in a config file.
def _collab_add_config_option(section, key, value):
global _osc_collab_osc_conffile
conffile = _osc_collab_osc_conffile
if not os.path.exists(conffile):
lines = [ ]
else:
fin = open(conffile, 'r')
lines = fin.readlines()
fin.close()
(fdout, tmp) = tempfile.mkstemp(prefix = os.path.basename(conffile), dir = os.path.dirname(conffile))
in_section = False
added = False
empty_line = False
valid_sections = [ '[' + section + ']' ]
if section.startswith('http'):
if section.endswith('/'):
valid_sections.append('[' + section[:-1] + ']')
else:
valid_sections.append('[' + section + '/]')
for line in lines:
if line.rstrip() in valid_sections:
in_section = True
# key was not in the section: let's add it
elif line[0] == '[' and in_section and not added:
if not empty_line:
os.write(fdout, b'\n')
write_line = '%s = %s\n\n' % (key, value)
os.write(fdout, write_line.encode('utf-8'))
added = True
in_section = False
elif line[0] == '[' and in_section:
in_section = False
# the section/key already exists: we replace
# 'not added': in case there are multiple sections with the same name
elif in_section and not added and line.startswith(key):
index = line.find('=')
if line[:index].rstrip() == key:
line = '%s= %s\n' % (line[:index], value)
added = True
os.write(fdout, line.encode('utf-8'))
empty_line = line.strip() == ''
if not added:
if not empty_line:
os.write(fdout, b'\n')
if not in_section:
write_line = '[%s]\n' % (section,)
os.write(fdout, write_line.encode('utf-8'))
write_line = '%s = %s\n' % (key, value)
os.write(fdout, write_line.encode('utf-8'))
os.close(fdout)
os.rename(tmp, conffile)
#######################################################################
def _collab_get_compatible_apiurl_for_config(config, apiurl):
if apiurl is None:
return None
if config.has_section(apiurl):
return apiurl
# first try adding/removing a trailing slash to the API url
if apiurl.endswith('/'):
apiurl = apiurl[:-1]
else:
apiurl = apiurl + '/'
if config.has_section(apiurl):
return apiurl
# old osc (0.110) was adding the host to the tuple without the http
# part, ie just the host
apiurl = urlparse(apiurl).netloc
if apiurl and config.has_section(apiurl):
return apiurl
return None
def _collab_get_config_parser():
global _osc_collab_config_parser
global _osc_collab_osc_conffile
if _osc_collab_config_parser is not None:
return _osc_collab_config_parser
conffile = _osc_collab_osc_conffile
_osc_collab_config_parser = configparser.SafeConfigParser()
_osc_collab_config_parser.read(conffile)
return _osc_collab_config_parser
def _collab_get_config(apiurl, key, default = None):
config = _collab_get_config_parser()
if not config:
return default
apiurl = _collab_get_compatible_apiurl_for_config(config, apiurl)
if apiurl and config.has_option(apiurl, key):
return config.get(apiurl, key)
elif config.has_option('general', key):
return config.get('general', key)
else:
return default
def _collab_get_config_bool(apiurl, key, default = None):
value = _collab_get_config(apiurl, key, default)
if type(value) == bool:
return value
if value.lower() in [ 'true', 'yes' ]:
return True
try:
return int(value) != 0
except:
pass
return False
def _collab_get_config_list(apiurl, key, default = None):
def split_items(line):
items = line.split(';')
# remove all empty items
while True:
try:
items.remove('')
except ValueError:
break
return items
line = _collab_get_config(apiurl, key, default)
items = split_items(line)
if not items and default:
if type(default) == str:
items = split_items(default)
else:
items = default
return items
#######################################################################
def _collab_migrate_gnome_config(apiurl):
for key in [ 'archs', 'apiurl', 'email', 'projects' ]:
if _collab_get_config(apiurl, 'collab_' + key) is not None:
continue
elif not ('gnome_' + key) in conf.config:
continue
_collab_add_config_option(apiurl, 'collab_' + key, conf.config['gnome_' + key])
# migrate repo to repos
if _collab_get_config(apiurl, 'collab_repos') is None and 'gnome_repo' in conf.config:
_collab_add_config_option(apiurl, 'collab_repos', conf.config['gnome_repo'] + ';')
#######################################################################
def _collab_ensure_email(apiurl):
email = _collab_get_config(apiurl, 'email')
if email:
return email
email = _collab_get_config(apiurl, 'collab_email')
if email:
return email
email = raw_input('E-mail address to use for .changes entries: ')
if email == '':
return 'EMAIL@DOMAIN'
_collab_add_config_option(apiurl, 'collab_email', email)
return email
#######################################################################
def _collab_parse_arg_packages(packages):
def remove_trailing_slash(s):
if s.endswith('/'):
return s[:-1]
return s
if type(packages) == str:
return remove_trailing_slash(packages)
elif type(packages) in [ list, tuple ]:
return [ remove_trailing_slash(package) for package in packages ]
else:
return packages
#######################################################################
@cmdln.alias('gnome')
@cmdln.option('-A', '--apiurl', metavar='URL',
dest='collapiurl',
help='url to use to connect to the database (different from the build service server)')
@cmdln.option('--xs', '--exclude-submitted', action='store_true',
dest='exclude_submitted',
help='do not show submitted packages in the output')
@cmdln.option('--xr', '--exclude-reserved', action='store_true',
dest='exclude_reserved',
help='do not show reserved packages in the output')
@cmdln.option('--xc', '--exclude-commented', action='store_true',
dest='exclude_commented',
help='do not show commented packages in the output')
@cmdln.option('--xd', '--exclude-devel', action='store_true',
dest='exclude_devel',
help='do not show packages that are up-to-date in their development project in the output')
@cmdln.option('--ic', '--ignore-comments', action='store_true',
dest='ignore_comments',
help='ignore the comments')
@cmdln.option('--ir', '--ignore-reserved', action='store_true',
dest='ignore_reserved',
help='ignore the reservation state of the package if necessary')
@cmdln.option('--iu', '--include-upstream', action='store_true',
dest='include_upstream',
help='include reports about missing upstream data')
@cmdln.option('--nr', '--no-reserve', action='store_true',
dest='no_reserve',
help='do not reserve the package')
@cmdln.option('--ns', '--no-supersede', action='store_true',
dest='no_supersede',
help='do not supersede requests to the same package')
@cmdln.option('--nu', '--no-unreserve', action='store_true',
dest='no_unreserve',
help='do not unreserve the package')
@cmdln.option('--nodevelproject', action='store_true',
dest='no_devel_project',
help='do not use development project of the packages')
@cmdln.option('--nobranch', action='store_true',
dest='no_branch',
help='do not branch the package in your home project')
@cmdln.option('-m', '--message', metavar='TEXT',
dest='msg',
help='specify log message TEXT')
@cmdln.option('-f', '--forward', action='store_true',
dest='forward',
help='automatically forward to parent project if successful')
@cmdln.option('--no-details', action='store_true',
dest='no_details',
help='do not show more details')
@cmdln.option('--details', action='store_true',
dest='details',
help='show more details')
@cmdln.option('--project', metavar='PROJECT', action='append',
dest='projects', default=[],
help='project to work on (default: openSUSE:Factory)')
@cmdln.option('--repo', metavar='REPOSITORY', action='append',
dest='repos', default=[],
help='build repositories to build on (default: automatic detection)')
@cmdln.option('--arch', metavar='ARCH', action='append',
dest='archs', default=[],
help='architectures to build on (default: i586 and x86_64)')
@cmdln.option('--nc', '--no-cache', action='store_true',
dest='no_cache',
help='ignore data from the cache')
@cmdln.option('-v', '--version', action='store_true',
dest='version',
help='show version of the plugin')
def do_collab(self, subcmd, opts, *args):
"""${cmd_name}: Various commands to ease collaboration on the openSUSE Build Service.
A tutorial and detailed documentation are available at:
http://en.opensuse.org/openSUSE:Osc_Collab
"todo" (or "t") will list the packages that need some action.
"todoadmin" (or "ta") will list the packages from the project that need
to be submitted to the parent project, and various other errors or tasks.
"listreserved" (or "lr") will list the reserved packages.
"isreserved" (or "ir") will look if a package is reserved.
"reserve" (or "r") will reserve a package so other people know you're
working on it.
"unreserve" (or "u") will remove the reservation you had on a package.
"listcommented" (or "lc") will list the commented packages.
"comment" (or "c") will look if a package is commented.
"commentset" (or "cs") will add to a package a comment you want to share
with other people.
"commentunset" (or "cu") will remove the comment you set on a package.
"setup" (or "s") will prepare a package for work (possibly reservation,
branch, checking out, etc.). The package will be checked out in the current
directory.
"update" (or "up") will prepare a package for update (possibly reservation,
branch, checking out, download of the latest upstream tarball, .spec
edition, etc.). The package will be checked out in the current directory.
"forward" (or "f") will forward a request to the project to parent project.
This includes the step of accepting the request first.
"build" (or "b") will commit the local changes of the package in
the current directory and wait for the build to succeed on the build
service.
"buildsubmit" (or "bs") will commit the local changes of the package in
the current directory, wait for the build to succeed on the build service
and if the build succeeds, submit the package to the development project.
Usage:
osc collab todo [--exclude-submitted|--xs] [--exclude-reserved|--xr] [--exclude-commented|--xc] [--exclude-devel|--xd] [--ignore-comments|--ic] [--details|--no-details] [--project=PROJECT]
osc collab todoadmin [--include-upstream|--iu] [--project=PROJECT]
osc collab listreserved
osc collab isreserved [--nodevelproject] [--project=PROJECT] PKG [...]
osc collab reserve [--nodevelproject] [--project=PROJECT] PKG [...]
osc collab unreserve [--nodevelproject] [--project=PROJECT] PKG [...]
osc collab listcommented
osc collab comment [--nodevelproject] [--project=PROJECT] PKG [...]
osc collab commentset [--nodevelproject] [--project=PROJECT] PKG COMMENT
osc collab commentunset [--nodevelproject] [--project=PROJECT] PKG [...]
osc collab setup [--ignore-reserved|--ir] [--ignore-comments|--ic] [--no-reserve|--nr] [--nodevelproject] [--nobranch] [--project=PROJECT] PKG
osc collab update [--ignore-reserved|--ir] [--ignore-comments|--ic] [--no-reserve|--nr] [--nodevelproject] [--nobranch] [--project=PROJECT] PKG
osc collab forward [--no-supersede|--ns] [--project=PROJECT] ID
osc collab build [--message=TEXT|-m=TEXT] [--repo=REPOSITORY] [--arch=ARCH]
osc collab buildsubmit [--forward|-f] [--no-supersede|--ns] [--no-unreserve|--nu] [--message=TEXT|-m=TEXT] [--repo=REPOSITORY] [--arch=ARCH]
${cmd_option_list}
"""
# uncomment this when profiling is needed
#self.ref = time.time()
#print("%.3f - %s" % (time.time()-self.ref, 'start'))
global _osc_collab_alias
global _osc_collab_osc_conffile
_osc_collab_alias = 'collab'
if opts.version:
print(OSC_COLLAB_VERSION)
return
cmds = ['todo', 't', 'todoadmin', 'ta', 'listreserved', 'lr', 'isreserved', 'ir', 'reserve', 'r', 'unreserve', 'u', 'listcommented', 'lc', 'comment', 'c', 'commentset', 'cs', 'commentunset', 'cu', 'setup', 's', 'update', 'up', 'forward', 'f', 'build', 'b', 'buildsubmit', 'bs']
if not args or args[0] not in cmds:
raise oscerr.WrongArgs('Unknown %s action. Choose one of %s.' % (_osc_collab_alias, ', '.join(cmds)))
cmd = args[0]
# Check arguments validity
if cmd in ['listreserved', 'lr', 'listcommented', 'lc', 'todo', 't', 'todoadmin', 'ta', 'build', 'b', 'buildsubmit', 'bs']:
min_args, max_args = 0, 0
elif cmd in ['setup', 's', 'update', 'up', 'forward', 'f']:
min_args, max_args = 1, 1
elif cmd in ['commentset', 'cs']:
min_args, max_args = 1, 2
elif cmd in ['isreserved', 'ir', 'reserve', 'r', 'unreserve', 'u', 'comment', 'c', 'commentunset', 'cu']:
min_args = 1
max_args = sys.maxsize
else:
raise RuntimeError('Unknown command: %s' % cmd)
if len(args) - 1 < min_args:
raise oscerr.WrongArgs('Too few arguments.')
if len(args) - 1 > max_args:
raise oscerr.WrongArgs('Too many arguments.')
if opts.details and opts.no_details:
raise oscerr.WrongArgs('--details and --no-details cannot be used at the same time.')
apiurl = self.get_api_url()
user = conf.get_apiurl_usr(apiurl)
userdata = core.get_user_data(apiurl, user, *['realname', 'email'])
realname = userdata[0]
email = userdata[1]
# See get_config() in osc/conf.py and postoptparse() in
# osc/commandline.py
if self.options.conffile:
conffile = self.options.conffile
else:
conffile = conf.identify_conf()
_osc_collab_osc_conffile = os.path.expanduser(conffile)
if opts.collapiurl:
collab_apiurl = opts.collapiurl
else:
collab_apiurl = _collab_get_config(apiurl, 'collab_apiurl')
if len(opts.projects) != 0:
projects = opts.projects
else:
projects = _collab_get_config_list(apiurl, 'collab_projects', 'openSUSE:Factory')
if len(opts.repos) != 0:
repos = opts.repos
else:
repos = _collab_get_config_list(apiurl, 'collab_repos', '!autodetect!')
if len(opts.archs) != 0:
archs = opts.archs
else:
archs = _collab_get_config_list(apiurl, 'collab_archs', 'i586;x86_64;')
details = _collab_get_config_bool(apiurl, 'collab_details', False)
if details and opts.no_details:
details = False
elif not details and opts.details:
details = True
OscCollabApi.init(collab_apiurl)
OscCollabCache.init(opts.no_cache)
OscCollabObs.init(apiurl)
# Do the command
if cmd in ['todo', 't']:
_collab_todo(apiurl, projects, details, opts.ignore_comments, opts.exclude_commented, opts.exclude_reserved, opts.exclude_submitted, opts.exclude_devel)
elif cmd in ['todoadmin', 'ta']:
_collab_todoadmin(apiurl, projects, opts.include_upstream)
elif cmd in ['listreserved', 'lr']:
_collab_listreserved(projects)
elif cmd in ['isreserved', 'ir']:
packages = _collab_parse_arg_packages(args[1:])
_collab_isreserved(projects, packages, no_devel_project = opts.no_devel_project)
elif cmd in ['reserve', 'r']:
packages = _collab_parse_arg_packages(args[1:])
_collab_reserve(projects, packages, user, no_devel_project = opts.no_devel_project)
elif cmd in ['unreserve', 'u']:
packages = _collab_parse_arg_packages(args[1:])
_collab_unreserve(projects, packages, user, no_devel_project = opts.no_devel_project)
elif cmd in ['listcommented', 'lc']:
_collab_listcommented(projects)
elif cmd in ['comment', 'c']:
packages = _collab_parse_arg_packages(args[1:])
_collab_comment(projects, packages, no_devel_project = opts.no_devel_project)
elif cmd in ['commentset', 'cs']:
packages = _collab_parse_arg_packages(args[1])
if len(args) - 1 == 1:
comment = edit_message()
else:
comment = args[2]
_collab_commentset(projects, packages, user, comment, no_devel_project = opts.no_devel_project)
elif cmd in ['commentunset', 'cu']:
packages = _collab_parse_arg_packages(args[1:])
_collab_commentunset(projects, packages, user, no_devel_project = opts.no_devel_project)
elif cmd in ['setup', 's']:
package = _collab_parse_arg_packages(args[1])
_collab_setup(apiurl, user, projects, package, ignore_reserved = opts.ignore_reserved, ignore_comment = opts.ignore_comments, no_reserve = opts.no_reserve, no_devel_project = opts.no_devel_project, no_branch = opts.no_branch)
elif cmd in ['update', 'up']:
package = _collab_parse_arg_packages(args[1])
_collab_update(apiurl, user, realname, email, projects, package, ignore_reserved = opts.ignore_reserved, ignore_comment = opts.ignore_comments, no_reserve = opts.no_reserve, no_devel_project = opts.no_devel_project, no_branch = opts.no_branch)
elif cmd in ['forward', 'f']:
request_id = args[1]
_collab_forward(apiurl, user, projects, request_id, no_supersede = opts.no_supersede)
elif cmd in ['build', 'b']:
_collab_build(apiurl, user, projects, opts.msg, repos, archs)
elif cmd in ['buildsubmit', 'bs']:
_collab_build_submit(apiurl, user, projects, opts.msg, repos, archs, forward = opts.forward, no_unreserve = opts.no_unreserve, no_supersede = opts.no_supersede)
else:
raise RuntimeError('Unknown command: %s' % cmd)
07070100000006000041ED0000000000000000000000026548EB8C00000000000000000000000000000000000000000000002200000000osc-plugin-collab-0.104+30/server07070100000007000081A40000000000000000000000016548EB8C00001283000000000000000000000000000000000000002900000000osc-plugin-collab-0.104+30/server/READMEThis directory contains the script used to create the server-side
database used by the osc collab plugin, as well as the web API used by
the plugin.
All data created will be created in ./cache/ unless the cache-dir option
in a configuration file is set. You can choose the configuration file
via the -o command-line option, or via the OBS_OPTIONS environment
variable. The format of the file is the one described in
obs-db/data/defaults.conf.
There are runme scripts that should help people get started. Ideally,
those scripts would be run in cron jobs:
./obs-db/runme: a good period should be between every 10 minutes and
every 30 minutes (it has to be tweaked)
./obs-db/runme-attributes: a good period should be every 30 minutes
./upstream/runme: a good period should be every 30 minutes
Note: if the script seems to be hanging forever, then it's likely the
following python bug: http://bugs.python.org/issue5103
See the analysis at https://bugzilla.novell.com/show_bug.cgi?id=525295
The solution is to edit ssl.py, as mentioned in the above bugs.
#######################################################################
obs-db/
Contains a script that will checks out the relevant data from the
build service, and creates a database for osc collab. It was
designed to not be too expensive in resources for the build service,
by using hermes to know what has changed in the past.
The default configuration file data/defaults.conf is a good
introduction on how to use this script (what to check out, eg). An
example configuration, data/opensuse.conf, is available: it is the
one used to create the osc collab openSUSE database, which means it
will check out a lot of projects.
All the data will be put in a cache subdirectory, unless the
cache-dir option is set in a configuration file.
Here is a quick overview of the structure of the code:
data/defaults.conf:
Configuration file documenting the configuration format and the
defaults value.
data/opensuse.conf:
Configuration file used to create the database for the osc
collab openSUSE database. It can be used with the --opensuse
option of obs-db, or with the -s option of runme.
buildservice.py:
Checks out data from the build service.
config.py:
Handles the configuration.
database.py:
Creates a database based on the checkout and the upstream data.
hermes.py:
Looks at hermes feeds to know what has changed.
obs-db:
Executable script that will call shell.py.
obs-manual-checkout:
Executable script to manually download a project or package,
in the same way as obs-db does.
obs-upstream-attributes:
Executable script to update the openSUSE:UpstreamVersion and
openSUSE:UpstreamTarballURL attributes in the build service with the
upstream data we have.
osc_copy.py:
Some convenience functions copied from osc.
runme:
Small helper to launch the script.
runme-attributes:
Small helper to launch the obs-upstream-attributes script.
shell.py:
Shell part of the script.
shellutils.py:
Miscellaneous functions to create an application using those modules.
TODO:
List of things to do :-)
upstream.py:
Creates a database about upstream data.
util.py:
Miscellaneous functions that make life easier.
openSUSE-setup/
Contains a near real-life example of how the server is setup for
osc-collab.opensuse.org. See openSUSE-setup/README for more
information.
upstream/
download-upstream-versions:
Download information about the latest versions of various
modules that are not hosted on ftp.gnome.org.
runme:
Small helper to update the upstream data.
upstream-limits.txt:
Information about limits for upstream versions. We might not
want to look for unstable versions of a module, eg.
upstream-packages-match.txt:
Data file that matches upstream GNOME module names to openSUSE
source package names.
upstream-tarballs.txt:
Information about where to find the latest upstream tarballs.
web/
Contains the web API that the osc collab plugin connects to. To
deploy it, simply install the files on a web server, copy
libdissector/config.py.in to libdissector/config.py and edit it for
the configuration.
Note that there is a .htaccess file in libdissector/. You will want
to check it will not be ignored because of the apache configuration
before deploying.
07070100000008000041ED0000000000000000000000026548EB8C00000000000000000000000000000000000000000000002900000000osc-plugin-collab-0.104+30/server/obs-db07070100000009000081A40000000000000000000000016548EB8C00000548000000000000000000000000000000000000002E00000000osc-plugin-collab-0.104+30/server/obs-db/TODOhermes:
+ when there's a commit for a package, we should create a fake commit event
for all packages linking to this one, so that they get updated too.
buildservice:
+ if we queue something twice, then we will really do it twice. We should have
a way to queue, and then strip the useless elements to minimize data.
+ a project configuration cache can be written twice (eg, when a project is
devel project for two different projects). This should be avoided since we
probably expect the first write to win (assuming the first write matches the
first project in obs.conf).
+ in check_project(), we should probably also look at files that are checked
out for a package and remove the ones that shouldn't be there.
database:
+ only do one update call if there was a commit and meta change for a package
+ provide the API needed by infoxml so that it doesn't need to do any SQL
query?
upstream:
+ add a removed table: right now, obs-upstream-attributes has no way to know
what upstream metadata got removed, which might result in stale attributes.
general:
!+ add a --deep-check mode. It would use the buildservice.py infrastructure to
browse all packages of all projects, and check that the files all have the
right md5. If not, it would queue packages for the next run. Or create tool
called obs-offline-check.
0707010000000A000081A40000000000000000000000016548EB8C00009E60000000000000000000000000000000000000003900000000osc-plugin-collab-0.104+30/server/obs-db/buildservice.py# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2008-2009, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sys
import bisect
import errno
import hashlib
import optparse
import shutil
import socket
import tempfile
import time
from osc import core
import urllib.parse, urllib.error
import queue
import threading
try:
from lxml import etree as ET
except ImportError:
try:
from xml.etree import cElementTree as ET
except ImportError:
import cElementTree as ET
import osc_copy
import util
# Timeout for sockets
SOCKET_TIMEOUT = 30
# Debug output?
USE_DEBUG = False
DEBUG_DIR = 'debug'
#######################################################################
def debug_thread(context, state, indent = '', use_remaining = False):
global USE_DEBUG
global DEBUG_DIR
if not USE_DEBUG:
return
# compatibility with old versions of python (< 2.6)
if hasattr(threading.currentThread(), 'name'):
name = threading.currentThread().name
else:
name = threading.currentThread().getName()
if context == 'main':
print('%s%s: %s' % (indent, name, state))
return
try:
util.safe_mkdir_p(DEBUG_DIR)
fout = open(os.path.join(DEBUG_DIR, 'buildservice-' + name), 'a')
# ignore indent since we write in files
fout.write('[%s] %s %s\n' % (context, time.strftime("%H:%M:%S", time.localtime()), state))
if use_remaining:
remaining = ''
for i in threading.enumerate():
remaining += i.name + ', '
fout.write('Remaining: %s\n' % (remaining,))
fout.close()
except Exception as e:
print('Exception in debug_thread: %s' % (e,), file=sys.stderr)
def socket_closer_thread_run(obs_checkout, empty_event):
# compatibility with old versions of python (< 2.6)
if hasattr(empty_event, 'is_set'):
empty_is_set = empty_event.is_set
else:
empty_is_set = empty_event.isSet
while True:
if empty_is_set():
break
obs_checkout.socket_timeouts_acquire()
# Find the socket that is supposed to be closed the first, so we can
# monitor it
while True:
if not len(obs_checkout.socket_timeouts):
(max_time, current_socket) = (0, None)
break
(max_time, current_socket, url) = obs_checkout.socket_timeouts[0]
if time.time() + SOCKET_TIMEOUT + 1 < max_time:
debug_thread('monitor', 'closing socket for %s (too far)' % url)
# close this socket: the max time is way too high
current_socket.close()
obs_checkout.socket_timeouts.remove((max_time, current_socket, url))
else:
break
obs_checkout.socket_timeouts_release()
# There's a socket to monitor, let's just do it
if max_time > 0:
while time.time() < max_time:
time.sleep(1)
# This is not thread-safe, but it can only go from False to
# True.
# If the value is still False, then we'll just try another
# time (and worst case: we exit the loop because of the
# timeout, but then we acquire the lock so the end will be
# thread-safe: we won't close it twice).
# If the value is True, then it's really closed anyway.
if not current_socket.fp or current_socket.fp.closed:
break
obs_checkout.socket_timeouts_acquire()
if time.time() >= max_time:
debug_thread('monitor', 'closing socket for %s (timed out)' % url)
current_socket.close()
if (max_time, current_socket, url) in obs_checkout.socket_timeouts:
obs_checkout.socket_timeouts.remove((max_time, current_socket, url))
obs_checkout.socket_timeouts_release()
else:
# There's no socket to monitor at the moment, so we wait for one to
# appear or for the notification of the end of the work.
# We use less than the socket timeout value as timeout so we are
# sure to not start too late the monitoring of the next socket (so
# we don't allow a socket to stay more than its timeout).
empty_event.wait(SOCKET_TIMEOUT / 2)
def obs_checkout_thread_run(obs_checkout):
try:
while True:
debug_thread('thread_loop', 'start loop', use_remaining = True)
if obs_checkout.queue.empty():
break
debug_thread('thread_loop', 'getting work...')
# we don't want to block: the queue is filled at the beginning and
# once it's empty, then it means we're done. So we want the
# exception to happen.
(project, package, meta) = obs_checkout.queue.get(block = False)
debug_thread('main', 'starting %s/%s (meta: %d)' % (project, package, meta))
try:
debug_thread('thread_loop', 'work = %s/%s (meta: %d)' % (project, package, meta))
if not package:
if meta:
obs_checkout.checkout_project_pkgmeta(project)
else:
obs_checkout.check_project(project)
else:
if meta:
obs_checkout.checkout_package_meta(project, package)
else:
obs_checkout.checkout_package(project, package)
debug_thread('thread_loop', 'work done')
except Exception as e:
print('Exception in worker thread for %s/%s (meta: %d): %s' % (project, package, meta, e), file=sys.stderr)
obs_checkout.queue.task_done()
debug_thread('thread_loop', 'end loop', use_remaining = True)
except queue.Empty:
pass
debug_thread('thread_loop', 'exit loop', use_remaining = True)
#######################################################################
class ObsCheckout:
def __init__(self, conf, dest_dir):
global USE_DEBUG
global DEBUG_DIR
global SOCKET_TIMEOUT
USE_DEBUG = conf.debug
DEBUG_DIR = os.path.join(conf.cache_dir, 'debug')
SOCKET_TIMEOUT = conf.threads_sockettimeout
self.conf = conf
self.dest_dir = dest_dir
self.queue = queue.Queue()
self.queue2 = queue.Queue()
self.error_queue = queue.Queue()
self.errors = set()
self.socket_timeouts = []
self.socket_timeouts_lock = None
def socket_timeouts_acquire(self):
if self.socket_timeouts_lock:
debug_thread('lock', 'acquiring lock')
self.socket_timeouts_lock.acquire()
debug_thread('lock', 'acquired lock')
def socket_timeouts_release(self):
if self.socket_timeouts_lock:
self.socket_timeouts_lock.release()
debug_thread('lock', 'released lock')
def _download_url_to_file(self, url, file):
""" Download url to file.
Return the length of the downloaded file.
"""
fin = None
fout = None
timeout = 0
length = 0
try:
fin = core.http_GET(url)
fout = open(file, 'w')
bytes = fin.read()
cur_length = len(bytes)
fout.write(bytes.decode())
fout.close()
return length
except Exception as e:
debug_thread('url', 'exception: %s' % (e,), ' ')
self.socket_timeouts_acquire()
if (timeout, fin, url) in self.socket_timeouts:
self.socket_timeouts.remove((timeout, fin, url))
if fin:
fin.close()
self.socket_timeouts_release()
if fout:
fout.close()
raise e
def _get_file(self, project, package, filename, size, revision = None, try_again = True):
""" Download a file of a package. """
package_dir = os.path.join(self.dest_dir, project, package)
destfile = os.path.join(package_dir, filename)
tmpdestfile = destfile + '.new'
try:
query = None
if revision:
query = { 'rev': revision }
url = osc_copy.makeurl(self.conf.apiurl, ['public', 'source', project, package, urllib.request.pathname2url(filename)], query=query)
length = self._download_url_to_file(url, tmpdestfile)
if length != size:
if try_again:
util.safe_unlink(tmpdestfile)
return self._get_file(project, package, filename, size, revision, False)
os.rename(tmpdestfile, destfile)
except (urllib.error.HTTPError, urllib.error.URLError, socket.error) as e:
util.safe_unlink(tmpdestfile)
if type(e) == urllib.error.HTTPError and e.code == 404:
print('File in package %s of project %s doesn\'t exist.' % (filename, package, project), file=sys.stderr)
elif try_again:
self._get_file(project, package, filename, size, revision, False)
else:
print('Cannot get file %s for %s from %s: %s (queueing for next run)' % (filename, package, project, e), file=sys.stderr)
self.error_queue.put((project, package))
return
def _get_files_metadata(self, project, package, save_basename, revision = None, try_again = True):
""" Download the file list of a package. """
package_dir = os.path.join(self.dest_dir, project, package)
filename = os.path.join(package_dir, save_basename)
tmpfilename = filename + '.new'
# download files metadata
try:
query = None
if revision:
query = { 'rev': revision }
url = osc_copy.makeurl(self.conf.apiurl, ['public', 'source', project, package], query=query)
length = self._download_url_to_file(url, tmpfilename)
if length == 0:
# metadata files should never be empty
if try_again:
util.safe_unlink(tmpfilename)
return self._get_files_metadata(project, package, save_basename, revision, False)
os.rename(tmpfilename, filename)
except (urllib.error.HTTPError, urllib.error.URLError, socket.error) as e:
util.safe_unlink(tmpfilename)
if type(e) == urllib.error.HTTPError and e.code == 404:
print('Package %s doesn\'t exist in %s.' % (package, project), file=sys.stderr)
elif try_again:
return self._get_files_metadata(project, package, save_basename, revision, False)
elif revision:
print('Cannot download file list of %s from %s with specified revision: %s' % (package, project, e), file=sys.stderr)
else:
print('Cannot download file list of %s from %s: %s (queueing for next run)' % (package, project, e), file=sys.stderr)
self.error_queue.put((project, package))
return None
try:
return ET.parse(filename).getroot()
except SyntaxError as e:
if try_again:
os.unlink(filename)
return self._get_files_metadata(project, package, save_basename, revision, False)
elif revision:
print('Cannot parse file list of %s from %s with specified revision: %s' % (package, project, e), file=sys.stderr)
else:
print('Cannot parse file list of %s from %s: %s' % (package, project, e), file=sys.stderr)
return None
def _get_package_metadata_cache(self, project, package):
""" Get the (md5, mtime) metadata from currently checkout data.
We take the metadata from the expanded link first, and also loads
the metadata from the non-expanded link (which overloads the
previous one).
"""
def add_metadata_from_file(file, cache):
if not os.path.exists(file):
return
try:
root = ET.parse(file).getroot()
except SyntaxError:
return
# also get the md5 of the directory
cache[os.path.basename(file)] = (root.get('srcmd5'), '')
for node in root.findall('entry'):
cache[node.get('name')] = (node.get('md5'), node.get('mtime'))
package_dir = os.path.join(self.dest_dir, project, package)
cache = {}
files = os.path.join(package_dir, '_files-expanded')
add_metadata_from_file(files, cache)
files = os.path.join(package_dir, '_files')
add_metadata_from_file(files, cache)
return cache
def _get_hash_from_file(self, algo, path):
""" Return the hash of a file, using the specified algorithm. """
if not os.path.exists(path):
return None
if algo not in [ 'md5' ]:
print('Internal error: _get_hash_from_file called with unknown hash algorithm: %s' % algo, file=sys.stderr)
return None
hash = hashlib.new(algo)
file = open(path, 'rb')
while True:
data = file.read(32768)
if not data:
break
hash.update(data)
file.close()
return hash.hexdigest()
def _get_package_file_checked_out(self, project, package, filename, cache, md5, mtime):
""" Tells if a file of the package is already checked out. """
if filename not in cache:
return False
if cache[filename] != (md5, mtime):
return False
path = os.path.join(self.dest_dir, project, package, filename)
file_md5 = self._get_hash_from_file('md5', path)
return file_md5 != None and file_md5 == md5
def _cleanup_package_old_files(self, project, package, downloaded_files):
""" Function to remove old files that should not be in a package
checkout anymore.
This should be called before all return statements in
checkout_package.
"""
package_dir = os.path.join(self.dest_dir, project, package)
for file in os.listdir(package_dir):
if file in downloaded_files:
continue
os.unlink(os.path.join(package_dir, file))
def checkout_package(self, project, package):
""" Checks out a package.
We use the files already checked out as a cache, to avoid
downloading the same files again if possible.
This means we need to make sure to remove all files that shouldn't
be there when leaving this function. This is done with the calls to
_cleanup_package_old_files().
"""
if not package:
print('Internal error: checkout_package called instead of checkout_project_pkgmeta', file=sys.stderr)
self.checkout_project_pkgmeta(project)
return
package_dir = os.path.join(self.dest_dir, project, package)
util.safe_mkdir_p(package_dir)
# Never remove _meta files, since they're not handled by the checkout process
downloaded_files = [ '_meta' ]
metadata_cache = self._get_package_metadata_cache(project, package)
# find files we're interested in from the metadata
root = self._get_files_metadata(project, package, '_files')
downloaded_files.append('_files')
if root is None:
self._cleanup_package_old_files(project, package, downloaded_files)
return
is_link = False
link_error = False
# revision to expand a link
link_md5 = None
# detect if the package is a link package
linkinfos_nb = len(root.findall('linkinfo'))
if linkinfos_nb == 1:
link_node = root.find('linkinfo')
# The logic is taken from islink() in osc/core.py
is_link = link_node.get('xsrcmd5') not in [ None, '' ] or link_node.get('lsrcmd5') not in [ None, '' ]
link_error = link_node.get('error') not in [ None, '' ]
link_md5 = link_node.get('xsrcmd5')
elif linkinfos_nb > 1:
print('Ignoring link in %s from %s: more than one <linkinfo>' % (package, project), file=sys.stderr)
if is_link:
# download the _link file first. This makes it possible to know if
# the project has a delta compared to the target of the link
for node in root.findall('entry'):
filename = node.get('name')
md5 = node.get('md5')
mtime = node.get('mtime')
size = node.get('size')
if filename == '_link':
if not self._get_package_file_checked_out(project, package, filename, metadata_cache, md5, mtime):
self._get_file(project, package, filename, size)
downloaded_files.append(filename)
# if the link has an error, then we can't do anything else since we
# won't be able to expand
if link_error:
self._cleanup_package_old_files(project, package, downloaded_files)
return
# look if we need to download the metadata of the expanded package
if '_files-expanded' in metadata_cache and metadata_cache['_files-expanded'][0] == link_md5:
files = os.path.join(self.dest_dir, project, package, '_files-expanded')
try:
root = ET.parse(files).getroot()
except SyntaxError:
root = None
else:
root = self._get_files_metadata(project, package, '_files-expanded', link_md5)
if root is None:
self._cleanup_package_old_files(project, package, downloaded_files)
return
downloaded_files.append('_files-expanded')
# look at all files and download what might be interesting
for node in root.findall('entry'):
filename = node.get('name')
md5 = node.get('md5')
mtime = node.get('mtime')
size = node.get('size')
# download .spec files
if filename.endswith('.spec'):
if not self._get_package_file_checked_out(project, package, filename, metadata_cache, md5, mtime):
self._get_file(project, package, filename, size, link_md5)
downloaded_files.append(filename)
self._cleanup_package_old_files(project, package, downloaded_files)
def checkout_package_meta(self, project, package, try_again = True):
""" Checks out the metadata of a package.
If we're interested in devel projects of this project, and the
devel package is not in a checked out devel project, then we queue
a checkout of this devel project.
"""
package_dir = os.path.join(self.dest_dir, project, package)
util.safe_mkdir_p(package_dir)
filename = os.path.join(package_dir, '_meta')
tmpfilename = filename + '.new'
try:
url = osc_copy.makeurl(self.conf.apiurl, ['public', 'source', project, package, '_meta'])
length = self._download_url_to_file(url, tmpfilename)
if length == 0:
# metadata files should never be empty
if try_again:
util.safe_unlink(tmpfilename)
return self.checkout_package_meta(project, package, False)
os.rename(tmpfilename, filename)
except (urllib.error.HTTPError, urllib.error.URLError, socket.error) as e:
util.safe_unlink(tmpfilename)
if type(e) == urllib.error.HTTPError and e.code == 404:
print('Package %s of project %s doesn\'t exist.' % (package, project), file=sys.stderr)
elif try_again:
self.checkout_package_meta(project, package, False)
else:
print('Cannot get metadata of package %s in %s: %s (queueing for next run)' % (package, project, e), file=sys.stderr)
self.error_queue.put((project, package))
return
# Are we interested in devel projects of this project, and if yes,
# should we check out the devel project if needed?
if project not in self.conf.projects:
return
if not self.conf.projects[project].checkout_devel_projects:
return
try:
package_node = ET.parse(filename).getroot()
except SyntaxError:
return
devel_node = package_node.find('devel')
if devel_node is None:
return
devel_project = devel_node.get('project')
project_dir = os.path.join(self.dest_dir, devel_project)
if not os.path.exists(project_dir):
self.queue_checkout_project(devel_project, parent = project, primary = False)
def check_project(self, project, try_again = True):
""" Checks if the current checkout of a project is up-to-date, and queue task if necessary. """
project_dir = os.path.join(self.dest_dir, project)
util.safe_mkdir_p(project_dir)
filename = os.path.join(project_dir, '_status')
try:
url = osc_copy.makeurl(self.conf.apiurl, ['status', 'project', project])
length = self._download_url_to_file(url, filename)
if length == 0:
# metadata files should never be empty
if try_again:
util.safe_unlink(filename)
return self.check_project(project, False)
except (urllib.error.HTTPError, urllib.error.URLError, socket.error) as e:
util.safe_unlink(filename)
if type(e) == urllib.error.HTTPError:
if e.code == 404:
print('Project %s doesn\'t exist.' % (project,), file=sys.stderr)
elif e.code == 400:
# the status page doesn't always work :/
self.queue_checkout_project(project, primary = False, force_simple_checkout = True, no_config = True)
elif try_again:
self.check_project(project, False)
else:
print('Cannot get status of %s: %s' % (project, e), file=sys.stderr)
return
try:
packages_node = ET.parse(filename).getroot()
except SyntaxError as e:
util.safe_unlink(filename)
if try_again:
return self.check_project(project, False)
else:
print('Cannot parse status of %s: %s' % (project, e), file=sys.stderr)
return
# We will have to remove all subdirectories that just don't belong to
# this project anymore.
subdirs_to_remove = [ file for file in os.listdir(project_dir) if os.path.isdir(os.path.join(project_dir, file)) ]
# Here's what we check to know if a package needs to be checked out again:
# - if there's no subdir
# - if it's a link:
# - check that the md5 from the status is the xsrcmd5 from the file
# list
# - check that we have _files-expanded and that all spec files are
# checked out
# - if it's not a link: check that the md5 from the status is the
# srcmd5 from the file list
for node in packages_node.findall('package'):
name = node.get('name')
srcmd5 = node.get('srcmd5')
is_link = len(node.findall('link')) > 0
try:
subdirs_to_remove.remove(name)
except ValueError:
pass
files = os.path.join(project_dir, name, '_files')
if not os.path.exists(files):
self.queue_checkout_package(project, name, primary = False)
continue
try:
files_root = ET.parse(files).getroot()
except SyntaxError:
self.queue_checkout_package(project, name, primary = False)
continue
if is_link:
previous_srcmd5 = files_root.get('xsrcmd5')
else:
previous_srcmd5 = files_root.get('srcmd5')
if srcmd5 != previous_srcmd5:
self.queue_checkout_package(project, name, primary = False)
# make sure we have all spec files
if is_link:
# for links, we open the list of files when expanded
files = os.path.join(project_dir, name, '_files-expanded')
if not os.path.exists(files):
self.queue_checkout_package(project, name, primary = False)
continue
try:
files_root = ET.parse(files).getroot()
except SyntaxError:
self.queue_checkout_package(project, name, primary = False)
continue
cont = False
for entry in files_root.findall('entry'):
filename = entry.get('name')
if filename.endswith('.spec'):
specfile = os.path.join(project_dir, name, filename)
if not os.path.exists(specfile):
self.queue_checkout_package(project, name, primary = False)
cont = True
break
if cont:
continue
# Remove useless subdirectories
for subdir in subdirs_to_remove:
shutil.rmtree(os.path.join(project_dir, subdir))
util.safe_unlink(filename)
def checkout_project_pkgmeta(self, project, try_again = True):
""" Checks out the packages metadata of all packages in a project. """
project_dir = os.path.join(self.dest_dir, project)
util.safe_mkdir_p(project_dir)
filename = os.path.join(project_dir, '_pkgmeta')
tmpfilename = filename + '.new'
try:
url = osc_copy.makeurl(self.conf.apiurl, ['search', 'package'], ['match=%s' % urllib.parse.quote('@project=\'%s\'' % project)])
length = self._download_url_to_file(url, tmpfilename)
if length == 0:
# metadata files should never be empty
if try_again:
util.safe_unlink(tmpfilename)
return self.checkout_project_pkgmeta(project, False)
os.rename(tmpfilename, filename)
except (urllib.error.HTTPError, urllib.error.URLError, socket.error) as e:
util.safe_unlink(tmpfilename)
if type(e) == urllib.error.HTTPError and e.code == 404:
print('Project %s doesn\'t exist.' % (project,), file=sys.stderr)
elif try_again:
self.checkout_project_pkgmeta(project, False)
else:
print('Cannot get packages metadata of %s: %s' % (project, e), file=sys.stderr)
return
def _run_helper(self):
if self.socket_timeouts != []:
print('Internal error: list of socket timeouts is not empty before running', file=sys.stderr)
return
# queue is empty or does not exist: it could be that the requested
# project does not exist
if self.queue.empty():
return
debug_thread('main', 'queue has %d items' % self.queue.qsize())
if self.conf.threads > 1:
# Architecture with threads:
# + we fill a queue with all the tasks that have to be done
# + the main thread does nothing until the queue is empty
# + we create a bunch of threads that will take the tasks from the
# queue
# + we create a monitor thread that ensures that the socket
# connections from the other threads don't hang forever. The
# issue is that those threads use urllib2, and urllib2 will
# remove the timeout from the underlying socket. (see
# socket.makefile() documentation)
# + there's an event between the main thread and the monitor
# thread to announce to the monitor thread that the queue is
# empty and that it can leave.
# + once the queue is empty:
# - the helper threads all exit since there's nothing left to do
# - the main thread is waken up and sends an event to the
# monitor thread. It waits for it to exit.
# - the monitor thread receives the event and exits.
# - the main thread can continue towards the end of the process.
# this is used to signal the monitor thread it can exit
empty_event = threading.Event()
# this is the lock for the data shared between the threads
self.socket_timeouts_lock = threading.Lock()
if SOCKET_TIMEOUT > 0:
monitor = threading.Thread(target=socket_closer_thread_run, args=(self, empty_event))
monitor.start()
thread_args = (self,)
for i in range(min(self.conf.threads, self.queue.qsize())):
t = threading.Thread(target=obs_checkout_thread_run, args=thread_args)
t.start()
self.queue.join()
# tell the monitor thread to quit and wait for it
empty_event.set()
if SOCKET_TIMEOUT > 0:
monitor.join()
else:
try:
while not self.queue.empty():
(project, package, meta) = self.queue.get(block = False)
debug_thread('main', 'starting %s/%s' % (project, package))
if not package:
if meta:
obs_checkout.checkout_project_pkgmeta(project)
else:
obs_checkout.check_project(project)
else:
if meta:
obs_checkout.checkout_package_meta(project, package)
else:
obs_checkout.checkout_package(project, package)
except queue.Empty:
pass
# secondary queue is not empty, so we do a second run
if not self.queue2.empty():
debug_thread('main', 'Working on second queue')
self.queue = self.queue2
self.queue2 = queue.Queue()
self._run_helper()
def run(self):
# we need a helper since the helper can call itself, and we want to
# look at the error queue at the very end
self._run_helper()
self.errors.clear()
while not self.error_queue.empty():
(project, package) = self.error_queue.get()
self.errors.add((project, package or ''))
def _write_project_config(self, project):
""" We need to write the project config to a file, because nothing
remembers if a project is a devel project, and from which project
it is, so it's impossible to know what settings should apply
without such a file. """
if project not in self.conf.projects:
return
project_dir = os.path.join(self.dest_dir, project)
util.safe_mkdir_p(project_dir)
filename = os.path.join(project_dir, '_obs-db-options')
fout = open(filename, 'w')
fout.write('parent=%s\n' % self.conf.projects[project].parent)
fout.write('branches=%s\n' % ','.join(self.conf.projects[project].branches))
fout.write('force-project-parent=%d\n' % self.conf.projects[project].force_project_parent)
fout.write('lenient-delta=%d\n' % self.conf.projects[project].lenient_delta)
fout.close()
def _copy_project_config(self, project, copy_from):
from_file = os.path.join(self.dest_dir, copy_from, '_obs-db-options')
if not os.path.exists(from_file):
return
project_dir = os.path.join(self.dest_dir, project)
util.safe_mkdir_p(project_dir)
filename = os.path.join(project_dir, '_obs-db-options')
shutil.copy(from_file, filename)
def _get_packages_in_project(self, project, try_again = True):
project_dir = os.path.join(self.dest_dir, project)
util.safe_mkdir_p(project_dir)
filename = os.path.join(project_dir, '_pkglist')
try:
url = osc_copy.makeurl(self.conf.apiurl, ['public', 'source', project])
length = self._download_url_to_file(url, filename)
if length == 0:
# metadata files should never be empty
if try_again:
util.safe_unlink(filename)
return self._get_packages_in_project(project, False)
except (urllib.error.HTTPError, urllib.error.URLError, socket.error) as e:
util.safe_unlink(filename)
if type(e) == urllib.error.HTTPError and e.code == 404:
return (None, 'Project %s doesn\'t exist.' % (project,))
elif try_again:
return self._get_packages_in_project(project, False)
else:
return (None, str(e))
try:
root = ET.parse(filename).getroot()
except SyntaxError as e:
util.safe_unlink(filename)
if try_again:
return self._get_packages_in_project(project, False)
else:
return (None, 'Cannot parse list of packages in %s: %s' % (project, e))
packages = [ node.get('name') for node in root.findall('entry') ]
util.safe_unlink(filename)
return (packages, None)
def queue_pkgmeta_project(self, project, primary = True):
if primary:
q = self.queue
else:
q = self.queue2
q.put((project, '', True))
def queue_check_project(self, project, primary = True):
if primary:
q = self.queue
else:
q = self.queue2
q.put((project, '', False))
def queue_checkout_package_meta(self, project, package, primary = True):
if primary:
q = self.queue
else:
q = self.queue2
q.put((project, package, True))
def queue_checkout_package(self, project, package, primary = True):
if primary:
q = self.queue
else:
q = self.queue2
q.put((project, package, False))
def queue_checkout_packages(self, project, packages, primary = True):
if primary:
q = self.queue
else:
q = self.queue2
for package in packages:
q.put((project, package, False))
def queue_checkout_project(self, project, parent = None, primary = True, force_simple_checkout = False, no_config = False):
""" Queue a checkout of a project.
If there's already a checkout for this project, instead of a full
checkout, a check of what is locally on disk and what should be
there will be done to only update what is necessary.
force_simple_checkout is used when what is needed is really just a
checkout of this project, and nothing else (no metadata for all
packages, and no devel projects).
"""
project_dir = os.path.join(self.dest_dir, project)
# Check now whether the directory exists, since we might create it
# while creating the project config
exists = os.path.exists(project_dir)
if not no_config:
if parent:
self._copy_project_config(project, parent)
else:
self._write_project_config(project)
if exists and not force_simple_checkout:
debug_thread('main', 'Queuing check for %s' % (project,))
self.queue_check_project(project, primary)
else:
debug_thread('main', 'Queuing packages of %s' % (project,))
(packages, error) = self._get_packages_in_project(project)
if error is not None:
print('Ignoring project %s: %s' % (project, error), file=sys.stderr)
return
self.queue_checkout_packages(project, packages, primary)
if not force_simple_checkout:
if (project not in self.conf.projects or
not self.conf.projects[project].checkout_devel_projects):
# the pkgmeta of the project is automatically downloaded when
# looking for devel projects
self.queue_pkgmeta_project(project, primary)
else:
self._queue_checkout_devel_projects(project, primary)
def _queue_checkout_devel_projects(self, project, primary = True):
self.checkout_project_pkgmeta(project)
pkgmeta_file = os.path.join(self.dest_dir, project, '_pkgmeta')
if not os.path.exists(pkgmeta_file):
print('Ignoring devel projects for project %s: no packages metadata' % (project,), file=sys.stderr)
return
devel_projects = set()
try:
collection = ET.parse(pkgmeta_file).getroot()
package = collection.find('package')
if package == None:
print('Project %s doesn\'t exist.' % (project,), file=sys.stderr)
return
for package in collection.findall('package'):
devel = package.find('devel')
# "not devel" won't work (probably checks if devel.text is
# empty)
if devel == None:
continue
devel_project = devel.get('project')
if devel_project and devel_project != project:
devel_projects.add(devel_project)
except SyntaxError as e:
print('Ignoring devel projects for project %s: %s' % (project, e), file=sys.stderr)
return
for devel_project in devel_projects:
self.queue_checkout_project(devel_project, parent = project, primary = primary)
def remove_checkout_package(self, project, package):
""" Remove the checkout of a package. """
path = os.path.join(self.dest_dir, project, package)
if os.path.exists(path):
shutil.rmtree(path)
def remove_checkout_project(self, project):
""" Remove the checkout of a project. """
path = os.path.join(self.dest_dir, project)
if os.path.exists(path):
shutil.rmtree(path)
0707010000000B000081A40000000000000000000000016548EB8C00002E5A000000000000000000000000000000000000003300000000osc-plugin-collab-0.104+30/server/obs-db/config.py# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2009, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sys
import configparser
import io as StringIO
from osc import conf as oscconf
from osc import oscerr
""" Example:
[General]
threads = 5
[Defaults]
branches = latest, fallback
[Project openSUSE:Factory]
[Project GNOME:STABLE:2.32]
branches = gnome-2-32
"""
#######################################################################
class ConfigException(Exception):
pass
#######################################################################
class EasyConfigParser(configparser.SafeConfigParser):
def safe_get(self, section, option, default):
try:
return self.get(section, option)
except:
return default
def safe_getint(self, section, option, default):
try:
return self.getint(section, option)
except:
return default
def safe_getboolean(self, section, option, default):
try:
return self.getboolean(section, option)
except:
return default
#######################################################################
class ConfigProject:
default_checkout_devel_projects = False
default_parent = ''
default_branches = ''
_default_branches_helper = []
default_force_project_parent = False
default_lenient_delta = False
@classmethod
def set_defaults(cls, cp, section):
""" Set new default settings for projects. """
cls.default_checkout_devel_projects = cp.safe_getboolean(section, 'checkout-devel-projects', cls.default_checkout_devel_projects)
cls.default_parent = cp.safe_get(section, 'parent', cls.default_parent)
cls._default_branches_helper = cp.safe_get(section, 'branches', cls._default_branches_helper)
cls.default_force_project_parent = cp.safe_getboolean(section, 'force-project-parent', cls.default_force_project_parent)
cls.default_lenient_delta = cp.safe_getboolean(section, 'lenient-delta', cls.default_lenient_delta)
def __init__(self, cp, section, name):
self.name = name
self.checkout_devel_projects = cp.safe_getboolean(section, 'checkout-devel-projects', self.default_checkout_devel_projects)
self.parent = cp.safe_get(section, 'parent', self.default_parent)
self._branches_helper = cp.safe_get(section, 'branches', self._default_branches_helper)
self.force_project_parent = cp.safe_getboolean(section, 'force-project-parent', self.default_force_project_parent)
self.lenient_delta = cp.safe_getboolean(section, 'lenient-delta', self.default_lenient_delta)
if self._branches_helper:
self.branches = [ branch.strip() for branch in self._branches_helper.split(',') if branch ]
#######################################################################
class Config:
def __init__(self, file = '', use_opensuse = False):
""" Arguments:
file -- configuration file to use
"""
self.filename = file
self.use_opensuse = use_opensuse
self.apiurl = None
self.hermes_baseurl = ''
self.hermes_feeds = ''
self._hermes_feeds_helper = []
self.cache_dir = os.path.realpath('cache')
self.ignore_conf_mtime = False
self.no_full_check = False
self.allow_project_catchup = False
self.threads = 10
self.sockettimeout = 30
self.threads_sockettimeout = 30
self.debug = False
self.mirror_only_new = False
self.force_hermes = False
self.force_upstream = False
self.force_db = False
self.force_xml = False
self.skip_hermes = False
self.skip_mirror = False
self.skip_upstream = False
self.skip_db = False
self.skip_xml = False
self.projects = {}
if use_opensuse:
self._parse_opensuse()
self._parse()
# Workaround to remove warning coming from osc.conf when we don't use
# SSL checks
buffer = StringIO.StringIO()
oldstderr = sys.stderr
sys.stderr = buffer
try:
oscconf.get_config(override_apiurl = self.apiurl)
except oscerr.NoConfigfile as e:
sys.stderr = oldstderr
buffer.close()
raise ConfigException(e)
except Exception as e:
sys.stderr = oldstderr
buffer.close()
raise e
# Workaround to remove warning coming from osc.conf when we don't use
# SSL checks
sys.stderr = oldstderr
self._copy_stderr_without_ssl(buffer)
buffer.close()
# Make sure apiurl points to the right value
self.apiurl = oscconf.config['apiurl']
# M2Crypto and socket timeout are not friends. See
# https://bugzilla.osafoundation.org/show_bug.cgi?id=2341
if ('sslcertck' in oscconf.config['api_host_options'][self.apiurl] and
oscconf.config['api_host_options'][self.apiurl]['sslcertck']):
self.sockettimeout = 0
# obviously has to be done after self.sockettimeout has been set to its
# final value
if self.threads_sockettimeout <= 0:
self.threads_sockettimeout = self.sockettimeout
def _copy_stderr_without_ssl(self, buffer):
""" Copy the content of a string io to stderr, except for the SSL warning. """
buffer.seek(0)
ignore_empty = False
while True:
line = buffer.readline()
if len(line) == 0:
break
if line == 'WARNING: SSL certificate checks disabled. Connection is insecure!\n':
ignore_empty = True
continue
if line == '\n' and ignore_empty:
ignore_empty = False
continue
ignore_empty = False
print(line[:-1], file=sys.stderr)
def _get_opensuse_conf_path(self):
""" Return the path to the openSUSE configuration file. """
return os.path.join(os.path.dirname(globals()['__file__']), 'data', 'opensuse.conf')
def _parse_opensuse(self):
""" Parse the openSUSE configuration file. """
opensuse_conf = self._get_opensuse_conf_path()
if os.path.exists(opensuse_conf):
self._parse_file(opensuse_conf)
else:
raise ConfigException('openSUSE configuration file does not exist.')
def _parse(self):
""" Parse the configuration file. """
if not self.filename:
return
if not os.path.exists(self.filename):
raise ConfigException('Configuration file %s does not exist.' % self.filename)
self._parse_file(self.filename)
def _parse_file(self, filename):
cp = EasyConfigParser()
cp.read(filename)
self._parse_general(cp)
self._parse_debug(cp)
self._parse_default_project(cp)
self._parse_projects(cp)
def _parse_general(self, cp):
""" Parses the section about general settings. """
if not cp.has_section('General'):
return
self.apiurl = cp.safe_get('General', 'apiurl', self.apiurl)
self.hermes_baseurl = cp.safe_get('General', 'hermes-baseurl', self.hermes_baseurl)
self._hermes_feeds_helper = cp.safe_get('General', 'hermes-feeds', self._hermes_feeds_helper)
self.cache_dir = os.path.realpath(cp.safe_get('General', 'cache-dir', self.cache_dir))
self.ignore_conf_mtime = cp.safe_getboolean('General', 'ignore-conf-mtime', self.ignore_conf_mtime)
self.no_full_check = cp.safe_getboolean('General', 'no-full-check', self.no_full_check)
self.allow_project_catchup = cp.safe_getboolean('General', 'allow-project-catchup', self.allow_project_catchup)
self.threads = cp.safe_getint('General', 'threads', self.threads)
self.sockettimeout = cp.safe_getint('General', 'sockettimeout', self.sockettimeout)
self.threads_sockettimeout = cp.safe_getint('General', 'threads-sockettimeout', self.threads_sockettimeout)
if self._hermes_feeds_helper:
self.hermes_feeds = [ feed.strip() for feed in self._hermes_feeds_helper.split(',') ]
def _parse_debug(self, cp):
""" Parses the section about debug settings. """
if not cp.has_section('Debug'):
return
self.debug = cp.safe_getboolean('Debug', 'debug', self.debug)
self.mirror_only_new = cp.safe_getboolean('Debug', 'mirror-only-new', self.mirror_only_new)
self.force_hermes = cp.safe_getboolean('Debug', 'force-hermes', self.force_hermes)
self.force_upstream = cp.safe_getboolean('Debug', 'force-upstream', self.force_upstream)
self.force_db = cp.safe_getboolean('Debug', 'force-db', self.force_db)
self.force_xml = cp.safe_getboolean('Debug', 'force-xml', self.force_xml)
self.skip_hermes = cp.safe_getboolean('Debug', 'skip-hermes', self.skip_hermes)
self.skip_mirror = cp.safe_getboolean('Debug', 'skip-mirror', self.skip_mirror)
self.skip_upstream = cp.safe_getboolean('Debug', 'skip-upstream', self.skip_upstream)
self.skip_db = cp.safe_getboolean('Debug', 'skip-db', self.skip_db)
self.skip_xml = cp.safe_getboolean('Debug', 'skip-xml', self.skip_xml)
def _parse_default_project(self, cp):
""" Parses the section about default settings for projects. """
if not cp.has_section('Defaults'):
return
ConfigProject.set_defaults(cp, 'Defaults')
def _parse_projects(self, cp):
""" Parses the project sections. """
for section in cp.sections():
if not section.startswith('Project '):
continue
name = section[len('Project '):]
if name in self.projects:
raise ConfigException('More than one section for project %s in %s.' % (name, self.filename))
project = ConfigProject(cp, section, name)
self.projects[name] = project
def get_opensuse_mtime(self):
""" Return the mtime of the openSUSE configuration file. """
stats = os.stat(self._get_opensuse_conf_path())
return stats.st_mtime
0707010000000C000041ED0000000000000000000000026548EB8C00000000000000000000000000000000000000000000002E00000000osc-plugin-collab-0.104+30/server/obs-db/data0707010000000D000081A40000000000000000000000016548EB8C00001537000000000000000000000000000000000000003C00000000osc-plugin-collab-0.104+30/server/obs-db/data/defaults.conf####
# Configuration file.
# All the options are documented here.
####
[General]
####
# General settings, with default values
####
## API URL for the build service. Defaults to the default osc API URL.
# apiurl =
#
## Base URL for Hermes installation matching the build service instance used.
## If no base URL is provided, then more requests will have to be done to the
## server to update the data.
# hermes-baseurl =
#
## List of Hermes feeds id to monitor to know about the changes in the build
## service. Use a ',' to separate the different ids. If no id is provided, then
## more requests will have to be done to the server to update the data.
# hermes-feeds =
#
## Where to store all the data that will be created.
# cache-dir = ./cache
#
## Ignore the mtime changes for the configuration file. Usually, when the
## configuration file has a different mtime, we recheck everything. This might
## not be the intended behavior. Note that this does not apply to mtime changes
## to non-user configuration file (like opensuse.conf).
# ignore-conf-mtime = False
#
## Do not do any full check of the mirror checkout. This can be needed if you
## want to always manually handle the full check (instead of having this done
## automatically).
# no-full-check = False
#
## Allow the catchup mechanism to checkout full projects. This is disabled by
## default as this is quite expensive in terms of requests to the build
## service.
# allow-project-catchup = False
#
## Maximum number of threads to use. Set to 1 to disable threads.
# threads = 10
#
## Timeout for sockets (in seconds). Putting a long timeout can slow down
## things, especially as the build service sometimes keeps hanging connections
## without any reason. Use 0 to not change anything.
## Note: because of https://bugzilla.osafoundation.org/show_bug.cgi?id=2341 and
## the fact that osc uses M2Crypto, we can't set a low timeout without
## affecting security. To use this setting, you'll have to set sslcertck=0 for
## the appropriate build server in ~/.oscrc. Else, this setting will be
## ignored.
# sockettimeout = 30
#
## Timeout for sockets used by threads. For technical reasons, we can work
## around the above issue with M2Crypto for the threads checking out the files
## from the build service. Since the timeouts are most likely to happen there,
## having an easy to use workaround makes sense.
## Set to 0 to use sockettimeout.
# threads-sockettimeout = 30
[Debug]
####
# Debug settings, with default values
####
## Provide debug output.
# debug = False
#
## If the mirror step will check/checkout all projects, only process the ones
## that have no checkout at the moment. This is useful after changing the
## configuration to add new projects, if you want a fast update (instead of
## triggering a check for all projects).
# mirror-only-new = False
#
## If the mirror step would check/checkout all projects because of a
## configuration change, then make it use the hermes update. This also applies
## to the db step that would rebuild. This is useful after changing the
## configuration without changing projects, if you want a fast update (instead
## of triggering a check for all projects).
# force-hermes = False
#
## Force full rebuild of the upstream db.
# force-upstream = False
#
## Force full rebuild of the main db.
# force-db = False
#
## Force creation of the xml.
# force-xml = False
#
## Whether to pretend there's no change in hermes or not.
# skip-hermes = False
#
## Whether to skip the mirror step.
# skip-mirror = False
#
## Whether to skip the upstream db step.
# skip-upstream = False
#
## Whether to skip the main db step.
# skip-db = False
#
## Whether to skip the main xml step.
# skip-xml = False
[Defaults]
####
# Settings that will apply to all projects, unless overloaded
####
## Whether or not to also check out devel projects of this project. Note that
## this is not inherited by the devel projects.
# checkout-devel-projects = False
#
## Sets a default parent project, to know where packages should end. For
## example, the parent project of GNOME:Factory is openSUSE:Factory (even though
## not all packages in GNOME:Factory really exists in openSUSE:Factory). This is
## generally used to know when a link is not set to the right parent, or to
## compare packages to parent packages, even if they're not links.
# parent =
#
## Which branches to use for upstream versions. Use a ',' to separate the
## different branches.
# branches =
#
## Whether to ignore the project/package a link points to and always use the
## configured parent project of this project as parent for the packages?
## This is useful for projects that are kept in sync with copypac instead of
## linkpac (and when the devel project links to another parent project).
## For example: parent is openSUSE:Published, but package is
## openSUSE:Devel/test and links to openSUSE:11.1/test
# force-project-parent = False
#
## Whether to ignore changes in .changes, or useless changes in .spec, when
## comparing non-link packages to find a delta.
# lenient-delta = False
####
# To specify a project to analyze (like home:vuntz), create a new section named
# 'Project home:vuntz'. Settings will be inherited from the Defaults section,
# but can be overloaded.
# Note that those settings are inherited by devel projects that will be checked
# out via the checkout-devel-projects option.
# Example:
# [Project home:vuntz]
# lenient-delta = True
####
0707010000000E000081A40000000000000000000000016548EB8C00000828000000000000000000000000000000000000003C00000000osc-plugin-collab-0.104+30/server/obs-db/data/opensuse.conf###
# See defaults.conf for documentation.
###
[General]
# Will result in one feed fetched: https://hermes.opensuse.org/feeds/25545,25547,55386,55387,55388.rdf
hermes-baseurl = https://hermes.opensuse.org/
hermes-feeds = 25545, 25547, 55386, 55387, 55388
[Defaults]
# Warning: when changing this, you'll have to manually update all the
# _obs-db-options files in the cache too to activate the change (unless you
# start a full check of the cache).
branches = latest, cpan, pypi, fallback
[Project openSUSE:Factory]
parent = openSUSE:Factory
checkout-devel-projects = True
[Project GNOME:Factory]
branches = gnome-stable, gnome-stable-extras
# For Factory + 1
[Project GNOME:Next]
branches = gnome-unstable, gnome-unstable-extras
# For Leap 15.4 / SLE15SP4
[Project GNOME:STABLE:41]
branches = gnome-41, gnome-41-extras
# For Leap 15.2 / SLE15SP2
[Project GNOME:STABLE:3.34]
branches = gnome-3.34, gnome-3.34-extras
# For the OpenStack Cloud project
[Project Cloud:OpenStack:Master]
branches = pypi
# For devel:cloverleaf:testing
[Project devel:cloverleaf:testing]
## Discontinued
##
## For 11.1
# [Project GNOME:STABLE:2.26]
# branches = gnome-2.26
#
## For 11.1 and 11.2
#[Project GNOME:STABLE:2.28]
#branches = gnome-2.28
#
## For 11.2 and 11.3
#[Project GNOME:STABLE:2.30]
#branches = gnome-2.30
#
## For 11.3
#[Project GNOME:STABLE:2.32]
#branches = gnome-2.32
#
## Contrib disappeared in June 2012
#[Project openSUSE:Factory:Contrib]
#parent = openSUSE:Factory:Contrib
#
#[Project GNOME:Contrib]
#parent = openSUSE:Factory:Contrib
#
## For 11.4
#[Project GNOME:STABLE:3.0]
#branches = gnome-3.0
#
## For 11.4 (and 12.1?)
#[Project GNOME:STABLE:3.2]
#branches = gnome-3.2
#
## For 12.1
#[Project GNOME:STABLE:3.4]
#branches = gnome-3.4
#
## For 12.2
#[Project GNOME:STABLE:3.6]
#branches = gnome-3.6, gnome-3.6-extras
#
## For 12.3
#[Project GNOME:STABLE:3.8]
#branches = gnome-3.8, gnome-3.8-extras
## For 13.1
#[Project GNOME:STABLE:3.12]
#branches = gnome-3.12, gnome-3.12-extras
## For Leap 42.2
#[Project GNOME:STABLE:3.20]
#branches = gnome-3.20, gnome-3.20-extras
0707010000000F000081A40000000000000000000000016548EB8C00015A0F000000000000000000000000000000000000003500000000osc-plugin-collab-0.104+30/server/obs-db/database.py# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2008-2009, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sys
import operator
import re
import sqlite3
try:
from lxml import etree as ET
except ImportError:
try:
from xml.etree import cElementTree as ET
except ImportError:
import cElementTree as ET
import upstream
import util
# This script was originally written for an usage with autobuild. It has been
# adapted for the build service, but a few features might still need to be
# ported. TODO-BS or FIXME-BS might indicate them.
# Files to just ignore in the file list of a package.
IGNORE_FILES = [ 'ready', 'MD5SUMS', 'MD5SUMS.meta' ]
# Would be nice to get the list of failed package builds. In autobuild, look
# at /work/built/info/failed/ TODO-BS
# In autobuild, it's easy to get access to the rpmlint reports. Keep this empty
# until we find an easy way to do the same for the build service (maybe just
# parse the output of the build log?)
RPMLINT_ERRORS_PATH = ''
#RPMLINT_ERRORS_PATH = os.path.join(OBS_DISSECTOR_DIR, 'tmp', 'rpmlint')
# Changing this means breaking compatibility with previous db
DB_MAJOR = 4
# Changing this means changing the db while keeping compatibility
# Increase when changing the db. Reset to 0 when changing DB_MAJOR.
DB_MINOR = 0
#######################################################################
class ObsDbException(Exception):
pass
#######################################################################
class Base:
sql_table = 'undefined'
sql_lastid = -1
@classmethod
def sql_setup(cls, cursor):
pass
def _sql_update_last_id(self, cursor):
cursor.execute('''SELECT last_insert_rowid();''')
self.sql_id = cursor.fetchone()[0]
self.__class__.sql_lastid = self.sql_id
#######################################################################
class File(Base):
sql_table = 'file'
@classmethod
def sql_setup(cls, cursor):
cursor.execute('''CREATE TABLE %s (
id INTEGER PRIMARY KEY,
filename TEXT,
mtime INTEGER,
srcpackage INTEGER
);''' % cls.sql_table)
@classmethod
def sql_get_all(cls, cursor, srcpackage):
files = []
cursor.execute('''SELECT * FROM %s WHERE
srcpackage = ?
;''' % cls.sql_table,
(srcpackage.sql_id,))
for row in cursor.fetchall():
file = File(srcpackage, row['filename'], row['mtime'])
file.sql_id = row['id']
files.append(file)
return files
@classmethod
def sql_remove_all(cls, cursor, ids):
if type(ids) == list:
where = ' OR '.join([ 'srcpackage = ?' for i in range(len(ids)) ])
cursor.execute('''DELETE FROM %s WHERE
%s;''' % (cls.sql_table, where),
ids)
else:
cursor.execute('''DELETE FROM %s WHERE
srcpackage = ?
;''' % cls.sql_table,
(ids,))
def __init__(self, src, name, mtime):
self.sql_id = -1
self.filename = name
self.src_package = src
try:
self.mtime = int(mtime)
except SyntaxError as e:
print('Cannot parse %s as mtime for %s/%s: %s' % (mtime, src, name, e), file=sys.stderr)
self.mtime = -1
def sql_add(self, cursor):
if self.src_package.sql_id == -1:
raise ObsDbException('No SQL id for %s when adding file %s.' % (self.src_package.name, self.filename))
cursor.execute('''INSERT INTO %s VALUES (
NULL, ?, ?, ?
);''' % self.sql_table,
(self.filename, self.mtime, self.src_package.sql_id))
self._sql_update_last_id(cursor)
def sql_update_from(self, cursor, new_file):
if self.sql_id < 0:
raise ObsDbException('File %s of %s used for update does not have a SQL id.' % (self.filename, self.src_package.name))
cursor.execute('''UPDATE %s SET
mtime = ?
WHERE id = ?
;''' % self.sql_table,
(new_file.mtime, self.sql_id))
def sql_remove(self, cursor):
if self.src_package.sql_id == -1:
raise ObsDbException('No SQL id for %s when removing file %s.' % (self.src_package.name, self.filename))
cursor.execute('''DELETE FROM %s WHERE
filename = ? AND
srcpackage = ?
;''' % self.sql_table,
(self.filename, self.src_package.sql_id))
def __ne__(self, other):
if (self.filename != other.filename or
self.mtime != other.mtime or
self.src_package.name != other.src_package.name or
self.src_package.project.name != other.src_package.project.name):
return True
return False
def __eq__(self, other):
return not self.__ne__(other)
#######################################################################
class Source(Base):
sql_table = 'source'
@classmethod
def sql_setup(cls, cursor):
cursor.execute('''CREATE TABLE %s (
id INTEGER PRIMARY KEY,
filename TEXT,
srcpackage INTEGER,
nb_in_pack INTEGER
);''' % cls.sql_table)
@classmethod
def sql_get_all(cls, cursor, srcpackage):
sources = []
cursor.execute('''SELECT * FROM %s WHERE
srcpackage = ?
;''' % cls.sql_table,
(srcpackage.sql_id,))
for row in cursor.fetchall():
source = Source(srcpackage, row['filename'], row['nb_in_pack'])
source.sql_id = row['id']
sources.append(source)
return sources
@classmethod
def sql_remove_all(cls, cursor, ids):
if type(ids) == list:
where = ' OR '.join([ 'srcpackage = ?' for i in range(len(ids)) ])
cursor.execute('''DELETE FROM %s WHERE
%s;''' % (cls.sql_table, where),
ids)
else:
cursor.execute('''DELETE FROM %s WHERE
srcpackage = ?
;''' % cls.sql_table,
(ids,))
def __init__(self, src, name, i):
self.sql_id = -1
self.filename = name
self.src_package = src
self.number = i
def sql_add(self, cursor):
if self.src_package.sql_id == -1:
raise ObsDbException('No SQL id for %s when adding source %s.' % (self.src_package.name, self.filename))
cursor.execute('''INSERT INTO %s VALUES (
NULL, ?, ?, ?
);''' % self.sql_table,
(self.filename, self.src_package.sql_id, self.number))
self._sql_update_last_id(cursor)
def sql_update_from(self, cursor, new_source):
if self.sql_id < 0:
raise ObsDbException('Source %s of %s used for update does not have a SQL id.' % (self.filename, self.src_package.name))
cursor.execute('''UPDATE %s SET
nb_in_pack = ?
WHERE id = ?
;''' % self.sql_table,
(new_source.number, self.sql_id))
def sql_remove(self, cursor):
if self.src_package.sql_id == -1:
raise ObsDbException('No SQL id for %s when removing source %s.' % (self.src_package.name, self.filename))
cursor.execute('''DELETE FROM %s WHERE
filename = ? AND
srcpackage = ? AND
nb_in_pack = ?
;''' % self.sql_table,
(self.filename, self.src_package.sql_id, self.number))
def __ne__(self, other):
if (self.filename != other.filename or
self.number != other.number or
self.src_package.name != other.src_package.name or
self.src_package.project.name != other.src_package.project.name):
return True
return False
def __eq__(self, other):
return not self.__ne__(other)
#######################################################################
class Patch(Base):
sql_table = 'patch'
# Format of tag is: "# PATCH-{FIX|FEATURE}-{OPENSUSE|SLED|UPSTREAM} name-of-file.patch bncb.novell.com_bug_number bgob.gnome.org_bug_number [email protected] -- this patch..."
# PATCH-NEEDS-REBASE is also a known tag
# We remove trailing ':' for tags too...
re_strip_comment = re.compile('^#[#\s]*([\S]*[^:\s]):?\s*(.*)$', re.UNICODE)
# anything that looks like something.diff or something.patch
re_get_filename = re.compile('^\s*(\S+\.(?:diff|patch))\s*(.*)$')
# anything that looks like word123 or word#123
re_get_bug_number = re.compile('^\s*([a-zA-Z]+)#?(\d+)\s*(.*)$')
# anything that looks like a@a
re_get_email = re.compile('^\s*(\S+@\S+)\s*(.*)$')
# remove "--" if it's leading the string
re_get_short_descr = re.compile('^\s*(?:--\s*)?(.*)$')
@classmethod
def sql_setup(cls, cursor):
cursor.execute('''CREATE TABLE %s (
id INTEGER PRIMARY KEY,
filename TEXT,
srcpackage INTEGER,
nb_in_pack INTEGER,
apply_order INTEGER,
disabled INTEGER,
tag TEXT,
tag_filename TEXT,
short_descr TEXT,
descr TEXT,
bnc INTEGER,
bgo INTEGER,
bmo INTEGER,
bln INTEGER,
brc INTEGER,
fate INTEGER,
cve INTEGER
);''' % cls.sql_table)
@classmethod
def sql_get_all(cls, cursor, srcpackage):
patches = []
cursor.execute('''SELECT * FROM %s WHERE
srcpackage = ?
;''' % cls.sql_table,
(srcpackage.sql_id,))
for row in cursor.fetchall():
patch = Patch(srcpackage, row['filename'], row['nb_in_pack'], row['disabled'])
patch.sql_id = row['id']
patch.apply_order = row['apply_order']
patch.tag = row['tag']
patch.tag_filename = row['tag_filename']
patch.bnc = row['bnc']
patch.bgo = row['bgo']
patch.bmo = row['bmo']
patch.bln = row['bln']
patch.brc = row['brc']
patch.fate = row['fate']
patch.cve = row['cve']
patch.short_descr = row['short_descr']
patch.descr = row['descr']
patches.append(patch)
return patches
@classmethod
def sql_remove_all(cls, cursor, ids):
if type(ids) == list:
where = ' OR '.join([ 'srcpackage = ?' for i in range(len(ids)) ])
cursor.execute('''DELETE FROM %s WHERE
%s;''' % (cls.sql_table, where),
ids)
else:
cursor.execute('''DELETE FROM %s WHERE
srcpackage = ?
;''' % cls.sql_table,
(ids,))
def __init__(self, src, name, i, disabled=True):
self.sql_id = -1
self.filename = name
self.number = i
self.apply_order = -1
if disabled:
self.disabled = 1
else:
self.disabled = 0
self.src_package = src
self.tag = ''
self.tag_filename = ''
self.bnc = 0
self.bgo = 0
self.bmo = 0
self.bln = 0
self.brc = 0
self.fate = 0
self.cve = 0
self.short_descr = ''
#FIXME read the header from the patch itself
self.descr = ''
def set_tag(self, tag_line):
match = Patch.re_strip_comment.match(tag_line)
if not match:
return
self.tag = match.group(1)
buf = match.group(2)
match = Patch.re_get_filename.match(buf)
if match:
self.tag_filename = match.group(1)
buf = match.group(2)
while True:
match = Patch.re_get_bug_number.match(buf)
if not match:
break
buf = match.group(3)
if match.group(1) == 'bnc':
self.bnc = int(match.group(2))
elif match.group(1) == 'bgo':
self.bgo = int(match.group(2))
elif match.group(1) == 'bmo':
self.bmo = int(match.group(2))
elif match.group(1) == 'bln':
self.bln = int(match.group(2))
elif match.group(1) == 'brc':
self.brc = int(match.group(2))
elif match.group(1) == 'fate':
self.fate = int(match.group(2))
elif match.group(1) == 'cve':
self.cve = int(match.group(2))
match = Patch.re_get_email.match(buf)
if match:
#FIXME what to do with match.group(1)
buf = match.group(2)
match = Patch.re_get_short_descr.match(buf)
if match:
self.short_descr = match.group(1)
else:
print('Weird error with patch tag analysis on %s: ' % tag_line, file=sys.stderr)
self.short_descr = buf
def set_apply_order(self, order):
self.apply_order = order
def set_disabled(self, disabled):
if disabled:
self.disabled = 1
else:
self.disabled = 0
def sql_add(self, cursor):
if self.src_package.sql_id == -1:
raise ObsDbException('No SQL id for %s when adding patch %s.' % (self.src_package.name, self.filename))
cursor.execute('''INSERT INTO %s VALUES (
NULL, ?, ?, ?, ?, ?,
?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?
);''' % self.sql_table,
(self.filename, self.src_package.sql_id, self.number, self.apply_order, self.disabled,
self.tag, self.tag_filename, self.short_descr, self.descr,
self.bnc, self.bgo, self.bmo, self.bln, self.brc, self.fate, self.cve))
self._sql_update_last_id(cursor)
def sql_update_from(self, cursor, new_patch):
if self.sql_id < 0:
raise ObsDbException('Patch %s of %s used for update does not have a SQL id.' % (self.filename, self.src_package.name))
cursor.execute('''UPDATE %s SET
nb_in_pack = ?,
apply_order = ?,
disabled = ?,
tag = ?,
tag_filename = ?,
short_descr = ?,
descr = ?,
bnc = ?,
bgo = ?,
bmo = ?,
bln = ?,
brc = ?,
fate = ?,
cve = ?
WHERE id = ?
;''' % self.sql_table,
(new_patch.number, new_patch.apply_order, new_patch.disabled,
new_patch.tag, new_patch.tag_filename, new_patch.short_descr, new_patch.descr,
new_patch.bnc, new_patch.bgo, new_patch.bmo, new_patch.bln, new_patch.brc, new_patch.fate, new_patch.cve,
self.sql_id))
def sql_remove(self, cursor):
if self.src_package.sql_id == -1:
raise ObsDbException('No SQL id for %s when removing patch %s.' % (self.src_package.name, self.filename))
cursor.execute('''DELETE FROM %s WHERE
filename = ? AND
srcpackage = ? AND
nb_in_pack = ?
;''' % self.sql_table,
(self.filename, self.src_package.sql_id, self.number))
def __ne__(self, other):
if (self.filename != other.filename or
self.number != other.number or
self.apply_order != other.apply_order or
self.disabled != other.disabled or
self.tag != other.tag or
self.tag_filename != other.tag_filename or
self.bnc != other.bnc or
self.bgo != other.bgo or
self.bmo != other.bmo or
self.bln != other.bln or
self.brc != other.brc or
self.fate != other.fate or
self.cve != other.cve or
self.short_descr != other.short_descr or
self.descr != other.descr or
self.src_package.name != other.src_package.name or
self.src_package.project.name != other.src_package.project.name):
return True
return False
def __eq__(self, other):
return not self.__ne__(other)
#######################################################################
class RpmlintReport(Base):
sql_table = 'rpmlint'
re_rpmlint = re.compile('\s*(.+):\s+(.):\s+(\S+)\s+(\S*)(?:\s+.*)?')
re_rpmlint_summary = re.compile('\s*\d+\s+packages\s+and\s+\d+\s+spec\s*files\s+checked\s*;')
@classmethod
def sql_setup(cls, cursor):
cursor.execute('''CREATE TABLE %s (
id INTEGER PRIMARY KEY,
srcpackage INTEGER,
level TEXT,
type TEXT,
detail TEXT,
descr TEXT
);''' % cls.sql_table)
@classmethod
def sql_get_all(cls, cursor, srcpackage):
rpmlints = []
cursor.execute('''SELECT * FROM %s WHERE
srcpackage = ?
;''' % cls.sql_table,
(srcpackage.sql_id,))
for row in cursor.fetchall():
rpmlint = RpmlintReport(srcpackage, row['level'], row['type'], row['detail'])
rpmlint.sql_id = row['id']
rpmlint.descr = row['descr']
rpmlints.append(rpmlint)
return rpmlints
@classmethod
def sql_remove_all(cls, cursor, ids):
if type(ids) == list:
where = ' OR '.join([ 'srcpackage = ?' for i in range(len(ids)) ])
cursor.execute('''DELETE FROM %s WHERE
%s;''' % (cls.sql_table, where),
ids)
else:
cursor.execute('''DELETE FROM %s WHERE
srcpackage = ?
;''' % cls.sql_table,
(ids,))
@classmethod
def analyze(cls, srcpackage, filepath):
rpmlints = []
file = open(filepath)
# read everything until we see the rpmlint report header
while True:
line = file.readline()
if line == '':
break
# this is not the beginning of the header
if line[:-1] != 'RPMLINT report:':
continue
# we've found the beginning of the header, so let's read the whole
# header
line = file.readline()
# note: we remove spurious spaces because the build service sometimes add some
if line[:-1].replace(' ', '') != '===============':
# oops, this is not what we expected, so go back.
file.seek(-len(line), os.SEEK_CUR)
break
rpmlints_without_descr = []
descr = None
separator = True
# now let's analyze the real important lines
while True:
line = file.readline()
if line == '':
break
# empty line: this is either the separator between two series of
# entries of the same type, or just an empty line.
# in the former case, this means we'll be able to set the
# description of the former series and save the series; we just
# need to be sure we're starting a new series
if line[:-1] == '':
separator = True
continue
# let's see if this is the end of the rpmlint report, and stop
# reading if this is the case
match = cls.re_rpmlint_summary.match(line)
if match:
break
# is this a new entry?
match = cls.re_rpmlint.match(line)
if match:
# we had an old series, so save it
if separator:
if len(rpmlints_without_descr) > 0:
for rpmlint in rpmlints_without_descr:
rpmlint.descr = descr
rpmlints.extend(rpmlints_without_descr)
# reset state
rpmlints_without_descr = []
descr = None
separator = False
package = match.group(1)
src = package.find('.src:')
if src > 0:
line = package.rstrip()[src + len('.src:'):]
try:
line = int(line)
except:
print('Cannot parse source package line in rpmlint line from %s (%s): %s' % (srcpackage.name, srcpackage.project.name, package), file=sys.stderr)
line = None
else:
line = None
level = match.group(2)
type = match.group(3)
detail = match.group(4).strip()
if line != None:
if detail == '':
detail = 'line %d' % line
else:
detail = detail + ' (line %d)' % line
rpmlints_without_descr.append(RpmlintReport(srcpackage, level, type, detail))
continue
# this is not a new entry and not an empty line, so this is the
# description for the past few rpmlint entries. This is only
# expected if we had some entries before
if len(rpmlints_without_descr) == 0:
print('Unexpected rpmlint line from %s (%s): %s' % (srcpackage.name, srcpackage.project.name, line[:-1]), file=sys.stderr)
continue
if descr:
descr = descr + ' ' + line[:-1]
else:
descr = line[:-1]
if len(rpmlints_without_descr) > 0:
rpmlints.extend(rpmlints_without_descr)
file.close()
return rpmlints
def __init__(self, src_package, level, type, detail):
self.sql_id = -1
self.src_package = src_package
self.level = level
self.type = type
self.detail = detail
self.descr = None
def sql_add(self, cursor):
if self.src_package.sql_id == -1:
raise ObsDbException('No SQL id for %s when adding rpmlint report.' % (self.src_package.name,))
cursor.execute('''INSERT INTO %s VALUES (
NULL, ?,
?, ?, ?, ?
);''' % self.sql_table,
(self.src_package.sql_id,
self.level, self.type, self.detail, self.descr))
self._sql_update_last_id(cursor)
def sql_update_from(self, cursor, new_report):
raise ObsDbException('Rpmlint reports cannot be updated since they do not change with time (they get added or removed).')
def sql_remove(self, cursor):
if self.src_package.sql_id == -1:
raise ObsDbException('No SQL id for %s when removing rpmlint report.' % (self.src_package.name,))
cursor.execute('''DELETE FROM %s WHERE
srcpackage = ? AND
level = ? AND
type = ? AND
detail = ? AND
descr = ?
;''' % self.sql_table,
(self.src_package.sql_id, self.level, self.type, self.detail, self.descr))
def __ne__(self, other):
if (self.level != other.level or
self.type != other.type or
self.detail != other.detail or
self.descr != other.descr or
self.src_package.name != other.src_package.name or
self.src_package.project.name != other.src_package.project.name):
return True
return False
def __eq__(self, other):
return not self.__ne__(other)
#######################################################################
class Package(Base):
sql_table = 'package'
@classmethod
def sql_setup(cls, cursor):
cursor.execute('''CREATE TABLE %s (
id INTEGER PRIMARY KEY,
name TEXT,
srcpackage INTEGER,
summary TEXT,
description TEXT
);''' % cls.sql_table)
@classmethod
def sql_get_all(cls, cursor, srcpackage):
packages = []
cursor.execute('''SELECT * FROM %s WHERE
srcpackage = ?
;''' % cls.sql_table,
(srcpackage.sql_id,))
for row in cursor.fetchall():
package = Package(srcpackage, row['name'])
package.sql_id = row['id']
package.summary = row['summary']
package.description = row['description']
packages.append(package)
return packages
@classmethod
def sql_remove_all(cls, cursor, ids):
if type(ids) == list:
where = ' OR '.join([ 'srcpackage = ?' for i in range(len(ids)) ])
cursor.execute('''DELETE FROM %s WHERE
%s;''' % (cls.sql_table, where),
ids)
else:
cursor.execute('''DELETE FROM %s WHERE
srcpackage = ?
;''' % cls.sql_table,
(ids,))
def __init__(self, src, name):
self.sql_id = -1
self.name = name
self.src_package = src
self.summary = ''
#FIXME we don't parse the descriptions right now
self.description = ''
def sql_add(self, cursor):
if self.src_package.sql_id == -1:
raise ObsDbException('No SQL id for %s when adding package %s.' % (self.src_package.name, self.name))
cursor.execute('''INSERT INTO %s VALUES (
NULL, ?, ?,
?, ?
);''' % self.sql_table,
(self.name, self.src_package.sql_id,
self.summary, self.description))
self._sql_update_last_id(cursor)
def sql_update_from(self, cursor, new_package):
if self.sql_id < 0:
raise ObsDbException('Package %s of %s used for update does not have a SQL id.' % (self.name, self.src_package.name))
cursor.execute('''UPDATE %s SET
summary = ?,
description = ?
WHERE id = ?
;''' % self.sql_table,
(new_package.summary, new_package.description, self.sql_id))
def sql_remove(self, cursor):
if self.src_package.sql_id == -1:
raise ObsDbException('No SQL id for %s when removing package %s.' % (self.src_package.name, self.name))
cursor.execute('''DELETE FROM %s WHERE
name = ? AND
srcpackage = ?
;''' % self.sql_table,
(self.name, self.src_package.sql_id))
def set_summary(self, summary):
# make sure we have utf-8 for sqlite3, else we get
# sqlite3.ProgrammingError
try:
self.summary = summary.encode('utf8')
except UnicodeDecodeError:
# we couldn't convert to utf-8: it's likely because we had latin1
self.summary = summary.decode('latin1')
def set_description(self, description):
# see comments in set_summary()
try:
self.description = description.encode('utf8')
except UnicodeDecodeError:
self.description = description.decode('latin1')
def __ne__(self, other):
if (self.name != other.name or
self.summary != other.summary or
self.description != other.description or
self.src_package.name != other.src_package.name or
self.src_package.project.name != other.src_package.project.name):
return True
return False
def __eq__(self, other):
return not self.__ne__(other)
#######################################################################
class SrcPackage(Base):
sql_table = 'srcpackage'
re_spec_define = re.compile('^%define\s+(\S*)\s+(\S*)', re.IGNORECASE)
re_spec_name = re.compile('^Name:\s*(\S*)', re.IGNORECASE)
re_spec_version = re.compile('^Version:\s*(\S*)', re.IGNORECASE)
re_spec_summary = re.compile('^Summary:\s*(.*)', re.IGNORECASE)
re_spec_source = re.compile('^Source(\d*):\s*(\S*)', re.IGNORECASE)
re_spec_patch = re.compile('^((?:#[#\s]*)?)Patch(\d*):\s*(\S*)', re.IGNORECASE)
re_spec_package = re.compile('^%package\s*(\S.*)', re.IGNORECASE)
re_spec_package2 = re.compile('^-n\s*(\S*)', re.IGNORECASE)
re_spec_lang_package = re.compile('^%lang_package', re.IGNORECASE)
re_spec_prep = re.compile('^%prep', re.IGNORECASE)
re_spec_build = re.compile('^%build', re.IGNORECASE)
re_spec_apply_patch = re.compile('^((?:#[#\s]*)?)%patch(\d*)', re.IGNORECASE)
@classmethod
def sql_setup(cls, cursor):
cursor.execute('''CREATE TABLE %s (
id INTEGER PRIMARY KEY,
name TEXT,
project INTEGER,
srcmd5 TEXT,
version TEXT,
link_project TEXT,
link_package TEXT,
devel_project TEXT,
devel_package TEXT,
upstream_name TEXT,
upstream_version TEXT,
upstream_url TEXT,
is_obs_link INTEGER,
obs_link_has_delta INTEGER,
obs_error TEXT,
obs_error_details TEXT
);''' % cls.sql_table)
def _sql_fill(self, cursor):
self.files = File.sql_get_all(cursor, self)
self.sources = Source.sql_get_all(cursor, self)
self.patches = Patch.sql_get_all(cursor, self)
self.rpmlint_reports = RpmlintReport.sql_get_all(cursor, self)
self.packages = Package.sql_get_all(cursor, self)
@classmethod
def _sql_get_from_row(cls, cursor, project, row, recursive = False):
pkg_object = SrcPackage(row['name'], project)
pkg_object.sql_id = row['id']
pkg_object.project = project
pkg_object.srcmd5 = row['srcmd5']
pkg_object.version = row['version']
pkg_object.link_project = row['link_project']
pkg_object.link_package = row['link_package']
pkg_object.devel_project = row['devel_project']
pkg_object.devel_package = row['devel_package']
pkg_object.upstream_name = row['upstream_name']
pkg_object.upstream_version = row['upstream_version']
pkg_object.upstream_url = row['upstream_url']
pkg_object.is_link = row['is_obs_link'] != 0
pkg_object.has_delta = row['obs_link_has_delta'] != 0
pkg_object.error = row['obs_error']
pkg_object.error_details = row['obs_error_details']
if recursive:
pkg_object._sql_fill(cursor)
return pkg_object
@classmethod
def sql_get(cls, cursor, project, name, recursive = False):
cursor.execute('''SELECT * FROM %s WHERE
name = ? AND
project = ?
;''' % cls.sql_table,
(name, project.sql_id))
rows = cursor.fetchall()
length = len(rows)
if length == 0:
return None
elif length > 1:
raise ObsDbException('More than one source package named %s for project %s in database.' % (name, project.name))
return cls._sql_get_from_row(cursor, project, rows[0], recursive)
@classmethod
def sql_get_all(cls, cursor, project, recursive = False):
srcpackages = []
cursor.execute('''SELECT * FROM %s WHERE
project = ?
;''' % cls.sql_table,
(project.sql_id,))
for row in cursor.fetchall():
srcpackage = cls._sql_get_from_row(cursor, project, row, False)
srcpackages.append(srcpackage)
if recursive:
# we do a second loop so we can use only one cursor, that shouldn't
# matter much since the loop is not the slow part
for srcpackage in srcpackages:
srcpackage._sql_fill(cursor)
return srcpackages
@classmethod
def sql_remove_all(cls, cursor, project_ids):
if type(project_ids) == list:
where = ' OR '.join([ 'project = ?' for i in range(len(project_ids)) ])
cursor.execute('''SELECT id FROM %s WHERE
%s;''' % (cls.sql_table, where),
project_ids)
else:
cursor.execute('''SELECT id FROM %s WHERE
project = ?
;''' % cls.sql_table,
(project_ids,))
ids = [ id for (id,) in cursor.fetchall() ]
if not ids:
return
Package.sql_remove_all(cursor, ids)
RpmlintReport.sql_remove_all(cursor, ids)
Source.sql_remove_all(cursor, ids)
Patch.sql_remove_all(cursor, ids)
File.sql_remove_all(cursor, ids)
if type(project_ids) == list:
where = ' OR '.join([ 'project = ?' for i in range(len(project_ids)) ])
cursor.execute('''DELETE FROM %s WHERE
%s;''' % (cls.sql_table, where),
project_ids)
else:
cursor.execute('''DELETE FROM %s WHERE
project = ?
;''' % cls.sql_table,
(project_ids,))
@classmethod
def sql_simple_remove(cls, cursor, project, package):
cursor.execute('''SELECT A.id FROM %s as A, %s as B WHERE
A.project = B.id AND
B.name = ? AND
A.name = ?
;''' % (cls.sql_table, Project.sql_table),
(project, package))
ids = [ id for (id,) in cursor.fetchall() ]
if not ids:
return
Package.sql_remove_all(cursor, ids)
RpmlintReport.sql_remove_all(cursor, ids)
Source.sql_remove_all(cursor, ids)
Patch.sql_remove_all(cursor, ids)
File.sql_remove_all(cursor, ids)
where = ' OR '.join([ 'id = ?' for i in range(len(ids)) ])
cursor.execute('''DELETE FROM %s WHERE
%s;''' % (cls.sql_table, where),
ids)
def __init__(self, name, project):
self.sql_id = -1
self.name = name
self.project = project
self.srcmd5 = ''
self.version = ''
self.upstream_name = ''
self.upstream_version = ''
self.upstream_url = ''
self.packages = []
self.sources = []
self.patches = []
self.files = []
self.rpmlint_reports = []
self.link_project = ''
self.link_package = ''
self.devel_project = ''
self.devel_package = ''
# not booleans, since sqlite doesn't support this
self.is_link = 0
# 1 means link delta, 2 means delta but without link so a human being
# has to look on how to synchronize this
self.has_delta = 0
self.error = ''
self.error_details = ''
# the package is a link using the branch mechanism
self.has_branch = 0
# there's a local _meta file for this package
self.has_meta = False
self._ready_for_sql = False
def sql_add(self, cursor):
if not self._ready_for_sql:
raise ObsDbException('Source package %s is a shim object, not to be put in database.' % (self.name,))
if self.project.sql_id == -1:
raise ObsDbException('No SQL id for %s when adding source package %s.' % (self.project.name, self.name))
cursor.execute('''INSERT INTO %s VALUES (
NULL,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
);''' % self.sql_table,
(self.name, self.project.sql_id, self.srcmd5, self.version, self.link_project, self.link_package, self.devel_project, self.devel_package, self.upstream_name, self.upstream_version, self.upstream_url, self.is_link, self.has_delta, self.error, self.error_details))
self._sql_update_last_id(cursor)
for package in self.packages:
package.sql_add(cursor)
for rpmlint in self.rpmlint_reports:
rpmlint.sql_add(cursor)
for source in self.sources:
source.sql_add(cursor)
for patch in self.patches:
patch.sql_add(cursor)
for file in self.files:
file.sql_add(cursor)
def sql_update_from(self, cursor, new_srcpackage):
if not new_srcpackage._ready_for_sql:
raise ObsDbException('Source package %s used for update is a shim object, not to be put in database.' % (new_srcpackage.name,))
if self.sql_id < 0:
raise ObsDbException('Source package %s used for update does not have a SQL id.' % (self.name,))
# might be needed by objects like files that we'll add to the database
# if they were not present before
new_srcpackage.sql_id = self.sql_id
# we obviously don't need to update the id, the name or the project
cursor.execute('''UPDATE %s SET
srcmd5 = ?,
version = ?,
link_project = ?,
link_package = ?,
devel_project = ?,
devel_package = ?,
upstream_name = ?,
upstream_version = ?,
upstream_url = ?,
is_obs_link = ?,
obs_link_has_delta = ?,
obs_error = ?,
obs_error_details = ?
WHERE id = ?
;''' % self.sql_table,
(new_srcpackage.srcmd5, new_srcpackage.version, new_srcpackage.link_project, new_srcpackage.link_package, new_srcpackage.devel_project, new_srcpackage.devel_package, new_srcpackage.upstream_name, new_srcpackage.upstream_version, new_srcpackage.upstream_url, new_srcpackage.is_link, new_srcpackage.has_delta, new_srcpackage.error, new_srcpackage.error_details, self.sql_id))
def pop_first(list):
try:
return list.pop(0)
except IndexError:
return None
def update_list(cursor, oldlist, newlist, attr):
""" Generic function to update list of objects like files, patches, etc.
This requires that the lists are sortable by an attribute
(attr) and that __ne__ and sql_update_from methods exists for
the objects.
"""
oldlist.sort(key=operator.attrgetter(attr))
newlist.sort(key=operator.attrgetter(attr))
# copy the new list to not edit it
copylist = list(newlist)
newitem = pop_first(copylist)
for olditem in oldlist:
if not newitem:
olditem.sql_remove(cursor)
continue
oldattr = getattr(olditem, attr)
newattr = getattr(newitem, attr)
if oldattr < newattr:
olditem.sql_remove(cursor)
else:
if oldattr > newattr:
while newitem and oldattr > newattr:
newitem.sql_add(cursor)
newitem = pop_first(copylist)
if newitem:
newattr = getattr(newitem, attr)
# not an 'else' since we do another loop above that
# can change newattr
if oldattr == newattr:
if olditem != newitem:
olditem.sql_update_from(cursor, newitem)
newitem = pop_first(copylist)
# add remaining items
while newitem:
newitem.sql_add(cursor)
newitem = pop_first(copylist)
update_list(cursor, self.packages, new_srcpackage.packages, 'name')
update_list(cursor, self.sources, new_srcpackage.sources, 'filename')
update_list(cursor, self.patches, new_srcpackage.patches, 'filename')
update_list(cursor, self.files, new_srcpackage.files, 'filename')
# Rpmlint warnings can only get added/removed, not updated
for rpmlint in self.rpmlint_reports:
if not rpmlint in new_srcpackage.rpmlint_reports:
rpmlint.sql_remove(cursor)
for rpmlint in new_srcpackage.rpmlint_reports:
if not rpmlint in self.rpmlint_reports:
rpmlint.sql_add(cursor)
def sql_remove(self, cursor):
if self.project.sql_id == -1:
raise ObsDbException('No SQL id for %s when removing source package %s.' % (self.project.name, self.name))
if self.sql_id == -1:
cursor.execute('''SELECT id FROM %s WHERE
name = ? AND
project = ?
;''' % self.sql_table,
(self.name, self.project.sql_id))
self.sql_id = cursor.fetchone()[0]
Package.sql_remove_all(cursor, self.sql_id)
RpmlintReport.sql_remove_all(cursor, self.sql_id)
Source.sql_remove_all(cursor, self.sql_id)
Patch.sql_remove_all(cursor, self.sql_id)
File.sql_remove_all(cursor, self.sql_id)
cursor.execute('''DELETE FROM %s WHERE
id = ?
;''' % self.sql_table,
(self.sql_id,))
def read_from_disk(self, project_directory, upstream_db):
srcpackage_dir = os.path.join(project_directory, self.name)
self._analyze_files(srcpackage_dir)
self._analyze_specs(srcpackage_dir)
self._analyze_meta(srcpackage_dir)
self._get_rpmlint_errors()
if upstream_db and self.project.branches:
(self.upstream_name, self.upstream_version, self.upstream_url) = upstream_db.get_upstream_data(self.project.branches, self.name)
if self.project.parent and self.project.parent != self.project.name and not self.is_link and not self.error:
self.error = 'not-link'
self._ready_for_sql = True
def _analyze_files(self, srcpackage_dir):
linkfile = os.path.join(srcpackage_dir, '_link')
if os.path.exists(linkfile):
self.is_link = 1
try:
root = ET.parse(linkfile).getroot()
except SyntaxError as e:
print('Cannot parse %s: %s' % (linkfile, e), file=sys.stderr)
else:
node = root.find('patches')
if node is not None:
if node.find('delete') != None or node.find('apply') != None:
self.has_delta = 1
if node.find('branch') != None:
self.has_branch = 1
root = None
files = os.path.join(srcpackage_dir, '_files-expanded')
if not os.path.exists(files):
files = os.path.join(srcpackage_dir, '_files')
if os.path.exists(files):
try:
root = ET.parse(files).getroot()
except SyntaxError as e:
print('Cannot parse %s: %s' % (files, e), file=sys.stderr)
else:
self.srcmd5 = root.get('srcmd5')
linkinfo = root.find('linkinfo')
if linkinfo != None:
link_project = linkinfo.get('project')
if link_project:
self.link_project = link_project
link_package = linkinfo.get('package')
if link_package:
self.link_package = link_package
error = linkinfo.get('error')
if error:
if error.find('does not exist in project') != -1:
self.error = 'not-in-parent'
elif error.find('could not apply patch') != -1:
self.error = 'need-merge-with-parent'
elif error.find('conflict in file') != -1:
self.error = 'need-merge-with-parent'
else:
self.error = 'unknown-error'
self.error_details = error
if self.error:
self.has_delta = 1
for node in root.findall('entry'):
filename = node.get('name')
if filename in IGNORE_FILES:
continue
mtime = node.get('mtime')
self.files.append(File(self, filename, mtime))
# if we want to force the parent to the project parent, then we do it
# only if the package is a link and there's no error in the link
# package
if self.project.force_project_parent and self.is_link and not self.error and (self.link_project not in [ self.project.parent, self.project.name ] or self.link_package != self.name):
self.is_link = 0
self.has_delta = 0
self.link_project = None
self.link_package = None
if not self.is_link and root is not None:
self._compare_raw_files_with_parent(srcpackage_dir, root)
def _compare_raw_files_with_parent(self, srcpackage_dir, root):
'''
Compare the content of two source packages by looking at the
present files, and their md5sum.
'''
if root is None:
return
if not self.project.parent or self.project.parent == self.project.name:
return
parent_package_dir = os.path.join(srcpackage_dir, '..', '..', self.project.parent, self.name)
files = os.path.join(parent_package_dir, '_files-expanded')
if not os.path.exists(files):
files = os.path.join(parent_package_dir, '_files')
if not os.path.exists(files):
return
try:
parent_root = ET.parse(files).getroot()
except SyntaxError as e:
print('Cannot parse %s: %s' % (files, e), file=sys.stderr)
return
parent_files = {}
for node in parent_root.findall('entry'):
filename = node.get('name')
if filename in IGNORE_FILES:
continue
md5 = node.get('md5')
parent_files[filename] = md5
for node in root.findall('entry'):
filename = node.get('name')
if filename in IGNORE_FILES:
continue
md5 = node.get('md5')
if filename not in parent_files:
self.has_delta = 2
break
elif md5 != parent_files[filename]:
if self.project.lenient_delta:
# we don't really care about .changes here
if filename[-8:] == '.changes':
continue
# for spec files, we try to ignore the irrelevant stuff
elif filename[-5:] == '.spec':
spec = os.path.join(srcpackage_dir, filename)
parent_spec = os.path.join(parent_package_dir, filename)
if self._specs_are_different_lenient(spec, parent_spec):
self.has_delta = 2
break
else:
self.has_delta = 2
break
del parent_files[filename]
def _specs_are_different_lenient(self, spec_a, spec_b):
'''
Compare two spec files, but ignore some useless changes:
- ignore space changes
- ignore blank lines
- ignore comments
- ignore Release tag
- ignore %changelog
'''
def strip_useless_spaces(s):
return ' '.join(s.split())
def get_next_line(file):
while True:
line = file.readline()
if len(line) == 0:
return None
line = line[:-1]
line = strip_useless_spaces(line)
if not line:
continue
if line[0] == '#':
continue
if line.startswith('Release:'):
continue
if line == '%changelog':
return None
return line
if not os.path.exists(spec_a) or not os.path.exists(spec_b):
return True
file_a = open(spec_a)
file_b = open(spec_b)
diff = False
while True:
line_a = get_next_line(file_a)
line_b = get_next_line(file_b)
if line_a is None:
if line_b is not None:
diff = True
break
if line_b is None:
diff = True
break
if line_a != line_b:
diff = True
break
file_a.close()
file_b.close()
return diff
def _analyze_specs(self, srcpackage_dir):
# If there's an error, then nothing to do: the package is broken anyway
if self.is_link and self.error:
return
# Only look at one spec file, since the build service works this way.
# By default, we take the spec file with the same name as the source
# package; if it doesn't exist, we take the first one.
bestfile = None
specname = self.name + '.spec'
def _name_is_perfect_match(specname, filename):
# if the file has a prefix, it has to be '_service:.*' to be
# considered as perfect candidate
return filename == specname or (filename.startswith('_service:') and filename.endswith(':' + specname))
for file in self.files:
if file.filename[-5:] == '.spec':
if _name_is_perfect_match(specname, file.filename):
if not bestfile or bestfile.mtime < file.mtime:
bestfile = file
else:
if not bestfile:
bestfile = file
elif not _name_is_perfect_match(specname, bestfile.filename) and bestfile.mtime < file.mtime:
# the current best file has not a perfect name, so we
# just take the best one based on the mtime
bestfile = file
if bestfile:
self._analyze_spec(os.path.join(srcpackage_dir, bestfile.filename))
def _analyze_spec(self, filename):
'''Analyze a spec file and extract the relevant data from there'''
if not os.path.exists(filename):
print('Spec file %s of %s/%s does not exist' % (os.path.basename(filename), self.project.name, self.name), file=sys.stderr)
return
spec = open(filename)
current_package = None
defines = {}
defines['name'] = self.name
def subst_defines(s, defines):
'''Replace macros like %{version} and %{name} in strings. Useful
for sources and patches '''
for key in list(defines.keys()):
if s.find(key) != -1:
value = defines[key]
s = s.replace('%%{%s}' % key, value)
s = s.replace('%%%s' % key, value)
return s
# to help if Summary is defined before Name
early_summary = False
line = 'empty'
while True:
# we need to remember the previous line for patch tags
#FIXME: some packages have comments on two lines...
previous_line = line
line = spec.readline()
if line == '':
break
match = SrcPackage.re_spec_prep.match(line)
if match:
break
match = SrcPackage.re_spec_define.match(line)
if match:
value = subst_defines(match.group(2), defines)
defines[match.group(1)] = value
continue
match = SrcPackage.re_spec_name.match(line)
if match:
name = match.group(1)
defines['name'] = name
current_package = Package(self, match.group(1))
if early_summary:
# if we had a summary before the name, then use it now
current_package.set_summary(early_summary)
early_summary = None
self.packages.append(current_package)
continue
match = SrcPackage.re_spec_lang_package.match(line)
if match:
current_package = Package(self, defines['name'] + '-lang')
self.packages.append(current_package)
continue
match = SrcPackage.re_spec_package.match(line)
if match:
pack_line = subst_defines(match.group(1), defines)
match = SrcPackage.re_spec_package2.match(pack_line)
if match:
current_package = Package(self, match.group(1))
else:
current_package = Package(self, defines['name'] + '-' + pack_line)
self.packages.append(current_package)
continue
match = SrcPackage.re_spec_version.match(line)
if match:
# Ignore version if it's redefined for a second package.
# Test case: MozillaThunderbird.spec, where the main package
# has a version, and the enigmail subpackage has another
# version.
if self.version and len(self.packages) > 1:
continue
self.version = subst_defines(match.group(1), defines)
defines['version'] = self.version
continue
match = SrcPackage.re_spec_summary.match(line)
if match:
if not current_package:
# save the summary for later
early_summary = match.group(1)
continue
current_package.set_summary(match.group(1))
continue
match = SrcPackage.re_spec_source.match(line)
if match:
if match.group(1) == '':
nb = '0'
else:
nb = match.group(1)
buf = subst_defines(match.group(2), defines)
source = Source(self, buf, nb)
self.sources.append(source)
continue
match = SrcPackage.re_spec_patch.match(line)
if match:
# we don't need it here: we'll explicitly mark the patches as
# applied later
disabled = (match.group(1) != '')
if match.group(2) == '':
nb = '0'
else:
nb = match.group(2)
buf = subst_defines(match.group(3), defines)
patch = Patch(self, buf, nb)
patch.set_tag(previous_line)
self.patches.append(patch)
continue
order = 0
while True:
line = spec.readline()
if line == '':
break
match = SrcPackage.re_spec_build.match(line)
if match:
break
match = SrcPackage.re_spec_apply_patch.match(line)
if match:
disabled = (match.group(1) != '')
if match.group(2) == '':
nb = '0'
else:
nb = match.group(2)
for patch in self.patches:
if patch.number == nb:
patch.set_disabled(disabled)
patch.set_apply_order(order)
break
order = order + 1
continue
spec.close()
def _analyze_meta(self, srcpackage_dir):
meta_file = os.path.join(srcpackage_dir, '_meta')
if not os.path.exists(meta_file):
return
try:
package = ET.parse(meta_file).getroot()
except SyntaxError as e:
print('Cannot parse %s: %s' % (meta_file, e), file=sys.stderr)
return
self.has_meta = True
devel = package.find('devel')
# "not devel" won't work (probably checks if devel.text is empty)
if devel == None:
return
self.devel_project = devel.get('project', '')
if not self.devel_project:
return
self.devel_package = devel.get('package', '')
def _get_rpmlint_errors(self):
if not RPMLINT_ERRORS_PATH or RPMLINT_ERRORS_PATH == '':
return
filepath = os.path.join(os.sep, RPMLINT_ERRORS_PATH, self.project.name, self.name + '.log')
if not os.path.exists(filepath):
return
self.rpmlint_reports = RpmlintReport.analyze(self, filepath)
#######################################################################
class Project(Base):
sql_table = 'project'
@classmethod
def sql_setup(cls, cursor):
cursor.execute('''CREATE TABLE %s (
id INTEGER PRIMARY KEY,
name TEXT,
parent TEXT,
ignore_upstream INTEGER
);''' % cls.sql_table)
@classmethod
def _sql_get_from_row(cls, cursor, row):
prj_object = Project(row['name'])
prj_object.sql_id = row['id']
prj_object.parent = row['parent']
prj_object.ignore_upstream = row['ignore_upstream'] != 0
return prj_object
@classmethod
def sql_get(cls, cursor, name, recursive = False):
cursor.execute('''SELECT * FROM %s WHERE
name = ?
;''' % cls.sql_table,
(name,))
rows = cursor.fetchall()
length = len(rows)
if length == 0:
return None
elif length > 1:
raise ObsDbException('More than one project named %s in database.' % name)
row = rows[0]
prj_object = cls._sql_get_from_row(cursor, row)
if recursive:
prj_object.srcpackages = SrcPackage.sql_get_all(cursor, prj_object, recursive)
return prj_object
@classmethod
def sql_get_all(cls, cursor, recursive = False):
projects = []
cursor.execute('''SELECT * FROM %s;''' % cls.sql_table)
for row in cursor.fetchall():
project = cls._sql_get_from_row(cursor, row)
projects.append(project)
if recursive:
# we do a second loop so we can use only one cursor, that shouldn't
# matter much since the loop is not the slow part
prj_object.srcpackages = SrcPackage.sql_get_all(cursor, prj_object, recursive)
return projects
@classmethod
def sql_simple_remove(cls, cursor, project):
cursor.execute('''SELECT id FROM %s WHERE
name = ?
;''' % cls.sql_table,
(project,))
ids = [ id for (id,) in cursor.fetchall() ]
if not ids:
return
SrcPackage.sql_remove_all(cursor, ids)
where = ' OR '.join([ 'id = ?' for i in range(len(ids)) ])
cursor.execute('''DELETE FROM %s WHERE
%s;''' % (cls.sql_table, where),
ids)
def __init__(self, name):
self.sql_id = -1
self.name = name
self.srcpackages = []
# Various options set for this project
self.parent = ''
self.branches = []
# Should we ignore the project/package a link points to and always use
# the configured parent project of this project as parent for the
# packages?
# This is useful for projects that are kept in sync with copypac
# instead of linkpac (and when the devel project links to another
# parent project). Eg: parent is openSUSE:Published, but package is
# openSUSE:Devel/test and links to openSUSE:11.1/test
self.force_project_parent = False
# When comparing non-link packages to find a delta, should we ignore
# changes in .changes or useless changes in .spec?
self.lenient_delta = False
self._ready_for_sql = False
def sql_add(self, cursor):
if not self._ready_for_sql:
raise ObsDbException('Project %s is a shim object, not to be put in database.' % (self.name,))
cursor.execute('''INSERT INTO %s VALUES (
NULL, ?, ?, ?
);''' % self.sql_table,
(self.name, self.parent, not self.branches))
self._sql_update_last_id(cursor)
for srcpackage in self.srcpackages:
srcpackage.sql_add(cursor)
def sql_remove(self, cursor):
if self.sql_id == -1:
cursor.execute('''SELECT id FROM %s WHERE
name = ?
;''' % self.sql_table,
(self.name,))
self.sql_id = cursor.fetchone()[0]
SrcPackage.sql_remove_all(cursor, self.sql_id)
cursor.execute('''DELETE FROM %s WHERE
id = ?
;''' % self.sql_table,
(self.sql_id,))
def _sync_config(self, projects_config, override_project_name = None):
"""
When override_project_name is not None, then it means we are using
the parent configuration.
"""
if not projects_config:
return False
name = override_project_name or self.name
if name not in projects_config:
if not override_project_name and self.parent:
return self._sync_config(projects_config, override_project_name = self.parent)
return False
project_config = projects_config[name]
if not override_project_name and project_config.parent != self.name:
self.parent = project_config.parent
self.branches = project_config.branches
self.force_project_parent = project_config.force_project_parent
self.lenient_delta = project_config.lenient_delta
return True
def read_config(self, projects_config, parent_directory):
""" Gets the config option for this project, saved in the _obs-db-options file. """
# We first try to get the project configuration from the global
# configuration
if self._sync_config(projects_config):
return
# We failed, so let's use the special configuration cache
config_file = os.path.join(parent_directory, self.name, '_obs-db-options')
if not os.path.exists(config_file):
return
file = open(config_file)
lines = file.readlines()
file.close()
for line in lines:
line = line[:-1].strip()
if not line or line.startswith('#'):
continue
elif line.startswith('parent='):
parent = line[len('parent='):]
if parent == self.name:
parent = ''
self.parent = parent
elif line.startswith('branches='):
branches = line[len('branches='):]
if not branches:
self.branches = []
continue
self.branches = [ branch for branch in branches.split(',') if branch ]
elif line.startswith('force-project-parent='):
force_project_parent = line[len('force-project-parent='):]
self.force_project_parent = force_project_parent.lower() in [ '1', 'true' ]
elif line.startswith('lenient-delta='):
lenient_delta = line[len('lenient-delta='):]
self.lenient_delta = lenient_delta.lower() in [ '1', 'true' ]
else:
raise ObsDbException('Unknown project config option for %s: %s' % (self.name, line))
def get_meta(self, parent_directory, package_name):
""" Get the devel package for a specific package. """
meta_file = os.path.join(parent_directory, self.name, '_pkgmeta')
if not os.path.exists(meta_file):
return ('', '')
try:
collection = ET.parse(meta_file).getroot()
except SyntaxError as e:
print('Cannot parse %s: %s' % (meta_file, e), file=sys.stderr)
return ('', '')
for package in collection.findall('package'):
name = package.get('name')
if name != package_name:
continue
devel = package.find('devel')
# "not devel" won't work (probably checks if devel.text is empty)
if devel == None:
return ('', '')
devel_project = devel.get('project', '')
if not devel_project:
return ('', '')
devel_package = devel.get('package', '')
return (devel_project, devel_package)
return ('', '')
def _read_meta(self, project_dir):
meta_devel = {}
meta_file = os.path.join(project_dir, '_pkgmeta')
if not os.path.exists(meta_file):
return meta_devel
try:
collection = ET.parse(meta_file).getroot()
except SyntaxError as e:
print('Cannot parse %s: %s' % (meta_file, e), file=sys.stderr)
return meta_devel
for package in collection.findall('package'):
name = package.get('name')
if not name:
continue
devel = package.find('devel')
# "not devel" won't work (probably checks if devel.text is empty)
if devel == None:
continue
devel_project = devel.get('project', '')
if not devel_project:
continue
devel_package = devel.get('package', '')
meta_devel[name] = (devel_project, devel_package)
return meta_devel
def read_from_disk(self, parent_directory, upstream_db):
"""
Note: read_config() has to be called before.
"""
project_dir = os.path.join(parent_directory, self.name)
if not os.path.exists(project_dir):
return
meta_devel = self._read_meta(project_dir)
for file in os.listdir(project_dir):
if file in ['_pkgmeta']:
continue
if not os.path.isdir(os.path.join(project_dir, file)):
continue
srcpackage = SrcPackage(file, self)
srcpackage.read_from_disk(project_dir, upstream_db)
if not srcpackage.has_meta and srcpackage.name in meta_devel:
(srcpackage.devel_project, srcpackage.devel_package) = meta_devel[srcpackage.name]
self.srcpackages.append(srcpackage)
self._ready_for_sql = True
#######################################################################
class ObsDb:
def __init__(self, conf, db_dir, mirror_dir, upstream):
self.conf = conf
self.db_dir = db_dir
self.mirror_dir = mirror_dir
self.upstream = upstream
self._filename = os.path.join(self.db_dir, 'obs.db')
self._dbconn = None
self._cursor = None
def _debug_print(self, s):
""" Print s if debug is enabled. """
if self.conf.debug:
print('ObsDb: %s' % s)
def __del__(self):
# needed for the commit
self._close_db()
def get_cursor(self):
""" Return a cursor to the database. """
self._open_existing_db_if_necessary()
return self._dbconn.cursor()
def exists(self):
""" Return True if a database already exists. """
if not os.path.exists(self._filename):
return False
try:
self._open_existing_db_if_necessary()
# make sure we have the same version of the format, else it's
# better to start from scratch
self._cursor.execute('''SELECT major, minor FROM db_version;''')
(major, minor) = self._cursor.fetchone()
if major != DB_MAJOR or minor != DB_MINOR:
return False
# just check there are some projects there, to be sure it's valid
self._cursor.execute('''SELECT id FROM %s;''' % Project.sql_table)
if len(self._cursor.fetchall()) <= 0:
return False
except:
return False
return True
def _open_db(self, filename):
""" Open a database file, and sets up everything. """
if self._dbconn:
self._close_db()
self._dbconn = sqlite3.connect(filename)
self._dbconn.row_factory = sqlite3.Row
self._dbconn.text_factory = sqlite3.OptimizedUnicode
self._cursor = self._dbconn.cursor()
def _close_db(self):
""" Closes the currently open database. """
if self._cursor:
self._cursor.close()
self._cursor = None
if self._dbconn:
self._dbconn.commit()
self._dbconn.close()
self._dbconn = None
def _open_existing_db_if_necessary(self):
""" Opens the database if it's not already opened. """
if self._dbconn:
return
if not os.path.exists(self._filename):
raise ObsDbException('Database file %s does not exist.' % self._filename)
self._open_db(self._filename)
def _create_tables(self):
self._cursor.execute('''CREATE TABLE db_version (
major INTEGER,
minor INTEGER
);''')
self._cursor.execute('''INSERT INTO db_version VALUES (
?, ?
);''', (DB_MAJOR, DB_MINOR))
Project.sql_setup(self._cursor)
SrcPackage.sql_setup(self._cursor)
Package.sql_setup(self._cursor)
Source.sql_setup(self._cursor)
Patch.sql_setup(self._cursor)
File.sql_setup(self._cursor)
RpmlintReport.sql_setup(self._cursor)
self._dbconn.commit()
def rebuild(self):
""" Rebuild the database from scratch. """
# We rebuild in a temporary file in case there's a bug in the script :-)
tmpfilename = self._filename + '.new'
if os.path.exists(tmpfilename):
os.unlink(tmpfilename)
util.safe_mkdir_p(self.db_dir)
self._debug_print('Rebuilding the database')
try:
self._open_db(tmpfilename)
self._create_tables()
for file in os.listdir(self.mirror_dir):
if not os.path.isdir(os.path.join(self.mirror_dir, file)):
continue
self.add_project(file)
self._close_db()
os.rename(tmpfilename, self._filename)
except Exception as e:
if os.path.exists(tmpfilename):
os.unlink(tmpfilename)
raise e
def add_project(self, project):
""" Add data of all packages from project in the database. """
self._open_existing_db_if_necessary()
self._debug_print('Adding project %s' % project)
prj_object = Project(project)
prj_object.read_config(self.conf.projects, self.mirror_dir)
prj_object.read_from_disk(self.mirror_dir, self.upstream)
prj_object.sql_add(self._cursor)
# It's apparently not needed to commit each time to keep a low-memory
# profile, and committing is slowing things down.
# self._dbconn.commit()
def update_project(self, project):
""" Update data of all packages from project in the database. """
self._open_existing_db_if_necessary()
# It's simpler to just remove all packages and add them again
self.remove_project(project)
self.add_project(project)
def remove_project(self, project):
""" Remove the project from the database. """
self._open_existing_db_if_necessary()
self._debug_print('Removing project %s' % project)
Project.sql_simple_remove(self._cursor, project)
def _add_package_internal(self, prj_object, package):
""" Internal helper to add a package. """
self._debug_print('Adding %s/%s' % (prj_object.name, package))
project_dir = os.path.join(self.mirror_dir, prj_object.name)
srcpackage_dir = os.path.join(project_dir, package)
if not os.path.exists(srcpackage_dir):
print('Added package %s in %s does not exist in mirror.' % (package, prj_object.name), file=sys.stderr)
return
pkg_object = SrcPackage(package, prj_object)
pkg_object.read_from_disk(project_dir, self.upstream)
if not pkg_object.has_meta:
# In theory, this shouldn't be needed since added packages
# should have a _meta file. Since it's unlikely to happen, it's
# okay to parse a big project-wide file.
self._debug_print('No meta during addition of %s/%s' % (prj_object.name, package))
(pkg_object.devel_project, pkg_object.devel_package) = prj_object.get_meta(self.mirror_dir, package)
pkg_object.sql_add(self._cursor)
# Make sure we also have the devel project if we're interested in that
if pkg_object.has_meta and pkg_object.devel_project and prj_object.name in self.conf.projects and self.conf.projects[prj_object.name].checkout_devel_projects:
devel_prj_object = Project.sql_get(self._cursor, pkg_object.devel_project)
if not devel_prj_object:
self.add_project(pkg_object.devel_project)
def _update_package_internal(self, prj_object, package, oldpkg_object):
""" Internal helper to update a package. """
self._debug_print('Updating %s/%s' % (prj_object.name, package))
project_dir = os.path.join(self.mirror_dir, prj_object.name)
srcpackage_dir = os.path.join(project_dir, package)
if not os.path.exists(srcpackage_dir):
print('Updated package %s in %s does not exist in mirror.' % (package, prj_object.name), file=sys.stderr)
return
update_children = False
pkg_object = SrcPackage(package, prj_object)
pkg_object.read_from_disk(project_dir, self.upstream)
if not pkg_object.has_meta:
# If the metadata was updated, we should have a _meta file for the
# package. If this is not the case, then the metadata was not
# updated, and then it's okay to keep the old metadata (instead of
# parsing a big project-wide file).
pkg_object.devel_project = oldpkg_object.devel_project
pkg_object.devel_package = oldpkg_object.devel_package
else:
if (pkg_object.devel_project != oldpkg_object.devel_project or
pkg_object.devel_package != oldpkg_object.devel_package):
update_children = True
oldpkg_object.sql_update_from(self._cursor, pkg_object)
# If the devel package has changed, then "children" packages might have
# a different error now. See _not_real_devel_package().
if update_children:
self._cursor.execute('''SELECT A.name, B.name
FROM %s AS A, %s AS B
WHERE B.project = A.id AND B.link_project = ? AND (B.link_package = ? OR B.name = ?)
;''' % (Project.sql_table, SrcPackage.sql_table),
(prj_object.name, package, package))
children = [ (child_project, child_package) for (child_project, child_package) in self._cursor ]
for (child_project, child_package) in children:
self.update_package(child_project, child_package)
# Make sure we also have the devel project if we're interested in that
if pkg_object.has_meta and pkg_object.devel_project and prj_object.name in self.conf.projects and self.conf.projects[prj_object.name].checkout_devel_projects:
self._debug_print('Looking at meta during update of %s/%s' % (prj_object.name, package))
devel_prj_object = Project.sql_get(self._cursor, pkg_object.devel_project)
if not devel_prj_object:
self.add_project(pkg_object.devel_project)
def add_package(self, project, package):
""" Add the package data in the database from the mirror. """
self._open_existing_db_if_necessary()
self._debug_print('Trying to add/update %s/%s' % (project, package))
prj_object = Project.sql_get(self._cursor, project)
if not prj_object:
self.add_project(project)
return
prj_object.read_config(self.conf.projects, self.mirror_dir)
pkg_object = SrcPackage.sql_get(self._cursor, prj_object, package, True)
if pkg_object:
self._update_package_internal(prj_object, package, pkg_object)
else:
self._add_package_internal(prj_object, package)
def update_package(self, project, package):
""" Update the package data in the database from the mirror. """
# We actually share the code to be more robust
self.add_package(project, package)
def remove_package(self, project, package):
""" Remove the package from the database. """
self._open_existing_db_if_necessary()
self._debug_print('Removing %s/%s' % (project, package))
SrcPackage.sql_simple_remove(self._cursor, project, package)
def get_devel_projects(self, project):
""" Return the list of devel projects used by packages in project. """
self._open_existing_db_if_necessary()
self._cursor.execute('''SELECT A.devel_project FROM %s as A, %s AS B
WHERE A.project = B.id AND B.name = ?
GROUP BY devel_project
;''' % (SrcPackage.sql_table, Project.sql_table),
(project,))
return [ devel_project for (devel_project,) in self._cursor.fetchall() if devel_project ]
def get_projects(self):
""" Return the list of projects in the database. """
self._open_existing_db_if_necessary()
self._cursor.execute('''SELECT name FROM %s;''' % Project.sql_table)
return [ name for (name,) in self._cursor.fetchall() ]
def upstream_changes(self, upstream_mtime):
""" Updates the upstream data that has changed since last time.
Return a list of projects that have been updated.
"""
branches = self.upstream.get_changed_packages(upstream_mtime)
if not branches:
return []
self._open_existing_db_if_necessary()
# Get all projects, with their config, and update the necessary
# packages if needed
projects = Project.sql_get_all(self._cursor, recursive = False)
for project in projects:
project.read_config(self.conf.projects, self.mirror_dir)
updated_projects = set()
for project in projects:
for branch in list(branches.keys()):
if branch != upstream.MATCH_CHANGE_NAME and not branch in project.branches:
continue
branches_before = []
if branch in project.branches:
branches_before = project.branches[:project.branches.index(branch)]
self._cursor.execute('''SELECT name FROM %s WHERE project = ?;''' % SrcPackage.sql_table, (project.sql_id,))
srcpackages = [ name for (name,) in self._cursor ]
# so we're only interested in the intersection of the two sets
# (in the project, and in the changed entries)
affected_srcpackages = set(branches[branch]).intersection(srcpackages)
if not affected_srcpackages:
continue
updated_projects.add(project.name)
self._debug_print('Upstream changes: %s -- %s' % (project.name, affected_srcpackages))
for srcpackage in affected_srcpackages:
if self.upstream.exists_in_branches(branches_before, srcpackage):
continue
(upstream_name, upstream_version, upstream_url) = self.upstream.get_upstream_data(project.branches, srcpackage)
self._cursor.execute('''UPDATE %s SET
upstream_name = ?, upstream_version = ?, upstream_url = ?
WHERE name = ? AND project = ?;''' % SrcPackage.sql_table,
(upstream_name, upstream_version, upstream_url, srcpackage, project.sql_id))
return list(updated_projects)
def get_packages_with_upstream_change(self, upstream_mtime):
""" Get the list of packages that are affected by upstream changes.
Return a list of projects, each containing a list of packages, each
one containing a tuple (upstream_version, upstream_url).
"""
branches = self.upstream.get_changed_packages(upstream_mtime)
if not branches:
return {}
self._open_existing_db_if_necessary()
# Get all projects, with their config, and update the necessary
# packages if needed
projects = Project.sql_get_all(self._cursor, recursive = False)
for project in projects:
project.read_config(self.conf.projects, self.mirror_dir)
result = {}
for project in projects:
for branch in list(branches.keys()):
if branch != upstream.MATCH_CHANGE_NAME and not branch in project.branches:
continue
branches_before = []
if branch in project.branches:
branches_before = project.branches[:project.branches.index(branch)]
self._cursor.execute('''SELECT name FROM %s WHERE project = ?;''' % SrcPackage.sql_table, (project.sql_id,))
srcpackages = [ name for (name,) in self._cursor ]
# so we're only interested in the intersection of the two sets
# (in the project, and in the changed entries)
affected_srcpackages = set(branches[branch]).intersection(srcpackages)
if not affected_srcpackages:
continue
if project.name not in result:
result[project.name] = {}
self._debug_print('Upstream changes: %s -- %s' % (project.name, affected_srcpackages))
for srcpackage in affected_srcpackages:
if self.upstream.exists_in_branches(branches_before, srcpackage):
continue
(upstream_name, upstream_version, upstream_url) = self.upstream.get_upstream_data(project.branches, srcpackage)
result[project.name][srcpackage] = (upstream_version, upstream_url)
return result
def post_analyze(self):
"""
Do some post-commit analysis on the db, to find new errors now that
we have all the data.
"""
self._open_existing_db_if_necessary()
self._debug_print('Post analysis')
def _not_link_and_not_in_parent(devel_package_cache, cursor_helper, row):
"""
Check if this is not a link and if it doesn't exist in the
potential parent. In that case, the error is that maybe it
should exist there
"""
# Note: if the package was changed in any way, we won't have
# the 'not-link-not-in-parent' error (since it's added only here).
# So if we have it, it means the package hasn't been updated and is
# therefore still a link. But the parent might have been created in
# the meantime, so it's possible to go back to 'not-link'.
if row['obs_error'] not in [ 'not-link', 'not-link-not-in-parent' ]:
return False
project_parent = row['project_parent']
if not project_parent:
return False
try:
devel_package_cache[project_parent][row['name']]
error = 'not-link'
except KeyError:
error = 'not-link-not-in-parent'
if row['obs_error'] != error:
details = ''
cursor_helper.execute('''UPDATE %s SET obs_error = ?, obs_error_details = ? WHERE id = ?;''' % SrcPackage.sql_table, (error, details, row['id']))
return True
return False
def _not_real_devel_package(devel_package_cache, cursor_helper, row):
"""
Look if the link package should really exist there (ie, is it
the devel package of the parent?)
"""
# Note: the errors created here can disappear when the devel
# package of the link package changes, without the current package
# changing. This is handled in _update_package_internal().
# the errors here are not relevant to toplevel projects (ie,
# projects without a parent)
if row['project_parent'] == '':
return False
link_project = row['link_project']
link_package = row['link_package'] or row['name']
# internal link inside a project (to build another spec file)
if link_project == row['project']:
return False
try:
(devel_project, devel_package) = devel_package_cache[link_project][link_package]
if devel_project != row['project'] or devel_package != row['name']:
if devel_project:
error = 'not-real-devel'
details = 'development project is %s' % devel_project
else:
error = 'parent-without-devel'
details = ''
cursor_helper.execute('''UPDATE %s SET obs_error = ?, obs_error_details = ? WHERE id = ?;''' % SrcPackage.sql_table, (error, details, row['id']))
return True
except KeyError:
# this happens when the parent package doesn't exist; link will
# be broken, so we already have an error
pass
return False
devel_package_cache = {}
cursor_helper = self._dbconn.cursor()
self._cursor.execute('''SELECT name FROM %s;''' % Project.sql_table)
for row in self._cursor:
devel_package_cache[row['name']] = {}
self._cursor.execute('''SELECT A.name, A.devel_project, A.devel_package, B.name AS project FROM %s AS A, %s AS B WHERE A.project = B.id;''' % (SrcPackage.sql_table, Project.sql_table))
for row in self._cursor:
devel_package = row['devel_package'] or row['name']
devel_package_cache[row['project']][row['name']] = (row['devel_project'], devel_package)
self._cursor.execute('''SELECT A.id, A.name, A.obs_error, A.link_project, A.link_package, B.name AS project, B.parent AS project_parent FROM %s AS A, %s AS B WHERE A.project = B.id;''' % (SrcPackage.sql_table, Project.sql_table))
for row in self._cursor:
if _not_link_and_not_in_parent(devel_package_cache, cursor_helper, row):
continue
if _not_real_devel_package(devel_package_cache, cursor_helper, row):
continue
cursor_helper.close()
07070100000010000081A40000000000000000000000016548EB8C0000482B000000000000000000000000000000000000003300000000osc-plugin-collab-0.104+30/server/obs-db/hermes.py# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2009, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sys
import re
import urllib.parse
import feedparser
#######################################################################
class HermesException(Exception):
pass
#######################################################################
# Note: we subclass object because we need super
class HermesEvent(object):
regexp = None
raw_type = None
def __init__(self, id, title, summary):
self.id = id
self.project = None
self.package = None
self.raw = False
if self.raw_type:
if title == 'Notification %s arrived!' % self.raw_type:
self.raw = True
for line in summary.split('\n'):
if not self.project and line.startswith(' project = '):
self.project = line[len(' project = '):]
elif not self.package and line.startswith(' package = '):
self.package = line[len(' package = '):]
@classmethod
def is_type_for_title(cls, title):
""" Determines if a feed entry belongs to the event class.
The match is based on the title of the feed entry, that is passed
through a regular expression.
"""
if cls.raw_type:
if title == 'Notification %s arrived!' % cls.raw_type:
return True
if not cls.regexp:
return False
match = cls.regexp.match(title)
return match != None
def is_project_event(self):
""" Return True if the event is for a project and not a package. """
return False
def is_package_event(self):
""" Return True if the event is for a package and not a project. """
return False
#######################################################################
class HermesEventCommit(HermesEvent):
regexp = re.compile('OBS ([^/\s]*)/([^/\s]*) r\d* commited')
raw_type = 'obs_srcsrv_commit'
def __init__(self, id, title, summary):
HermesEvent.__init__(self, id, title, summary)
if self.raw:
return
match = self.regexp.match(title)
# for some reason, not using str() sometimes make our requests to the
# build service using those variables fail. I have absolutely no reason
# why. It fails with "X-Error-Info: Request Line did not contain
# request URI. The request that was received does not appear to be a
# valid HTTP request. Please verify that your application uses HTTP"
self.project = str(match.group(1))
self.package = str(match.group(2))
def is_package_event(self):
return True
#######################################################################
class HermesEventProjectDeleted(HermesEvent):
regexp = re.compile('\[obs del\] Project ([^/\s]*) deleted')
raw_type = 'OBS_SRCSRV_DELETE_PROJECT'
def __init__(self, id, title, summary):
HermesEvent.__init__(self, id, title, summary)
if self.raw:
return
match = self.regexp.match(title)
self.project = str(match.group(1))
def is_project_event(self):
return True
#######################################################################
class HermesEventPackageMeta(HermesEvent):
regexp = re.compile('\[obs update\] Package ([^/\s]*) in ([^/\s]*) updated')
raw_type = 'OBS_SRCSRV_UPDATE_PACKAGE'
def __init__(self, id, title, summary):
HermesEvent.__init__(self, id, title, summary)
if self.raw:
return
match = self.regexp.match(title)
self.project = str(match.group(2))
self.package = str(match.group(1))
def is_package_event(self):
return True
#######################################################################
class HermesEventPackageAdded(HermesEvent):
regexp = re.compile('\[obs new\] New Package ([^/\s]*) ([^/\s]*)')
# Workaround again buggy messages
workaround_regexp = re.compile('\[obs new\] New Package\s*$')
raw_type = 'OBS_SRCSRV_CREATE_PACKAGE'
@classmethod
def is_type_for_title(cls, title):
if super(HermesEventPackageAdded, cls).is_type_for_title(title):
return True
else:
match = cls.workaround_regexp.match(title)
return match != None
def __init__(self, id, title, summary):
HermesEvent.__init__(self, id, title, summary)
if self.raw:
return
match = self.regexp.match(title)
if match:
# Hermes previously said "Package $PGK in $PRJ"
if str(match.group(2)) == 'in':
raise HermesException('Old format of hermes message detected: %s' % title)
self.project = str(match.group(2))
self.package = str(match.group(1))
else:
match = self.workaround_regexp.match(title)
if match != None:
self.project = ''
self.package = ''
else:
raise HermesException('Event should not be in PackagedAdded: %s' % title)
def is_package_event(self):
return True
#######################################################################
class HermesEventPackageDeleted(HermesEvent):
regexp = re.compile('\[obs del\] Package ([^/\s]*) from ([^/\s]*) deleted')
raw_type = 'OBS_SRCSRV_DELETE_PACKAGE'
def __init__(self, id, title, summary):
HermesEvent.__init__(self, id, title, summary)
if self.raw:
return
match = self.regexp.match(title)
self.project = str(match.group(2))
self.package = str(match.group(1))
def is_package_event(self):
return True
#######################################################################
class HermesReader:
types = [ HermesEventCommit, HermesEventProjectDeleted, HermesEventPackageMeta, HermesEventPackageAdded, HermesEventPackageDeleted ]
def __init__(self, last_known_id, base_url, feeds, conf):
""" Arguments:
last_known_id -- id of the last known event, so the hermes reader
can know where to stop.
base_url -- the base url for the hermes server.
feeds -- a list of feed ids. They will be used to get a merged feed
from the hermes server.
conf -- configuration object
"""
self._events = []
self.last_known_id = last_known_id
self._previous_last_known_id = int(last_known_id)
self._conf = conf
if not base_url or not feeds:
self._feed = None
self._debug_print('No defined feed')
else:
resource = '/feeds/' + ','.join(feeds) + '.rdf'
self._feed = urllib.parse.urljoin(base_url, resource)
self._debug_print('Feed to be used: %s' % self._feed)
self._last_parsed_id = -1
def _debug_print(self, s):
""" Print s if debug is enabled. """
if self._conf.debug:
print('HermesReader: %s' % s)
def _get_entry_id(self, entry):
""" Gets the hermes id of the event.
This is an integer that we can compare with other ids.
"""
entry_id = entry['id']
id = os.path.basename(entry_id)
try:
return int(id)
except ValueError:
raise HermesException('Cannot get event id from: %s' % entry_id)
def _parse_entry(self, id, entry):
""" Return an event object based on the entry. """
title = entry['title']
for type in self.types:
if type.is_type_for_title(title):
return type(id, title, entry['summary'])
# work around some weird hermes bug
if title in [ 'Notification arrived!', 'Notification unknown type arrived!' ]:
return None
raise HermesException('Cannot get event type from message %d: "%s"' % (id, title))
def _parse_feed(self, url):
""" Parses the feed to get events that are somehow relevant.
This function ignores entries older than the previous last known id.
Return True if the feed was empty.
"""
feed = feedparser.parse(url)
if len(feed['entries']) == 0:
return True
for entry in feed['entries']:
error_encoded = False
id = self._get_entry_id(entry)
if id <= self._previous_last_known_id:
continue
if id > self._last_parsed_id:
self._last_parsed_id = id
try:
event = self._parse_entry(id, entry)
except UnicodeEncodeError as e:
error_encoded = True
event = None
print('Cannot convert hermes message %d to str: %s' % (id, e), file=sys.stderr)
# Note that hermes can be buggy and give events without the proper
# project/package. If it's '' and not None, then it means it has
# been changed to something empty (and therefore it's a bug from
# hermes).
if (event and
event.project != '' and
not (event.is_package_event() and event.package == '')):
# put the id in the tuple so we can sort the list later
self._events.append((id, event))
# in case of UnicodeEncodeError, we already output a message
elif not error_encoded:
print('Buggy hermes message %d (%s): "%s".' % (id, entry['updated'], entry['title']), file=sys.stderr)
print('----------', file=sys.stderr)
for line in entry['summary'].split('\n'):
print('> %s' % line, file=sys.stderr)
print('----------', file=sys.stderr)
if id > self.last_known_id:
self.last_known_id = id
return False
def _append_data_to_url(self, url, data):
""" Append data to the query arguments passed to url. """
if url.find('?') != -1:
return '%s&%s' % (url, data)
else:
return '%s?%s' % (url, data)
def fetch_last_known_id(self):
""" Read the first feed just to get a last known id. """
self._debug_print('Fetching new last known id')
# we don't ignore self._conf.skip_hermes if we don't have a last known
# id, since it can only harm by creating a later check for all projects
# on the build service, which is expensive
if self._conf.skip_hermes and self.last_known_id != -1:
return
if not self._feed:
return
feed = feedparser.parse(self._feed)
for entry in feed['entries']:
id = self._get_entry_id(entry)
if id > self.last_known_id:
self.last_known_id = id
def _read_feed(self, feed_url):
""" Read events from hermes, and populates the events item. """
self._last_parsed_id = -1
page = 1
if self._previous_last_known_id > 0:
url = self._append_data_to_url(feed_url, 'last_id=%d' % self._previous_last_known_id)
else:
raise HermesException('Internal error: trying to parse feeds while there is no last known id')
if self._conf.skip_hermes:
return
while True:
if page > 100:
raise HermesException('Parsing too many pages: last parsed id is %d, last known id is %d' % (self._last_parsed_id, self._previous_last_known_id))
self._debug_print('Parsing %s' % url)
old_last_parsed_id = self._last_parsed_id
empty_feed = self._parse_feed(url)
if empty_feed:
break
elif old_last_parsed_id >= self._last_parsed_id:
# this should never happen, as if we don't have an empty feeed, it
# means we progress
raise HermesException('No progress when parsing pages: last parsed id is %d, last known id is %d' % (self._last_parsed_id, self._previous_last_known_id))
page += 1
url = self._append_data_to_url(feed_url, 'last_id=%d' % self._last_parsed_id)
def read(self):
""" Read events from hermes, and populates the events item. """
# Make sure we don't append events to some old values
self._events = []
if self._feed:
self._read_feed(self._feed)
# Sort to make sure events are in the reverse chronological order
self._events.sort(reverse = True)
self._debug_print('Number of events: %d' % len(self._events))
if len(self._events) == 0:
return
self._debug_print('Events (reverse sorted): %s' % [ id for (id, event) in self._events ])
self._strip()
self._debug_print('Number of events after strip: %d' % len(self._events))
def _strip(self):
""" Strips events that we can safely ignore.
For example, we can ignore multiple commits, or commits that were
done before a deletion.
"""
meta_changed = []
changed = []
deleted = []
new_events = []
# Note: the event list has the most recent event first
# FIXME: we should do a first pass in the reverse order to know which
# packages were added, and then later removed, so we can also strip the
# remove event below.
for (id, event) in self._events:
# Ignore event if the project was deleted after this event
if (event.project, None) in deleted:
continue
# Ignore event if the package was deleted after this event
if event.package and (event.project, event.package) in deleted:
continue
if isinstance(event, HermesEventCommit):
# Ignore commit event if the package was re-committed
# afterwards
if (event.project, event.package) in changed:
continue
changed.append((event.project, event.package))
new_events.append((id, event))
elif isinstance(event, HermesEventProjectDeleted):
deleted.append((event.project, None))
new_events.append((id, event))
elif isinstance(event, HermesEventPackageMeta):
# Ignore meta event if the meta of the package was changed
# afterwards
if (event.project, event.package) in meta_changed:
continue
meta_changed.append((event.project, event.package))
new_events.append((id, event))
elif isinstance(event, HermesEventPackageAdded):
# Ignore added event if the package was re-committed
# afterwards and meta was changed
if (event.project, event.package) in meta_changed and (event.project, event.package) in changed:
continue
changed.append((event.project, event.package))
meta_changed.append((event.project, event.package))
new_events.append((id, event))
elif isinstance(event, HermesEventPackageDeleted):
# Ignore deleted event if the package was re-committed
# afterwards (or meta was changed)
if (event.project, event.package) in meta_changed:
continue
if (event.project, event.package) in changed:
continue
deleted.append((event.project, event.package))
new_events.append((id, event))
self._events = new_events
def get_events(self, last_known_id = -1, reverse = False):
""" Return the list of events that are more recent than last_known_id. """
result = []
for (id, event) in self._events:
if id <= last_known_id:
break
result.append(event)
if reverse:
result.reverse()
return result
#######################################################################
def main(args):
class Conf:
def __init__(self):
self.debug = True
self.skip_hermes = False
feeds = [ '25545', '25547', '55386', '55387', '55388' ]
last_known_id = 10011643
reader = HermesReader(last_known_id, 'https://hermes.opensuse.org/', feeds, Conf())
reader.read()
print('Number of events: %d' % len(reader.get_events(2094133)))
print('Last known event: %d' % reader.last_known_id)
if __name__ == '__main__':
try:
main(sys.argv)
except KeyboardInterrupt:
pass
07070100000011000081A40000000000000000000000016548EB8C00003745000000000000000000000000000000000000003400000000osc-plugin-collab-0.104+30/server/obs-db/infoxml.py# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2009, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sys
import errno
import filecmp
try:
from lxml import etree as ET
except ImportError:
try:
from xml.etree import cElementTree as ET
except ImportError:
import cElementTree as ET
import database
import util
#######################################################################
# Create a global dictionary that will contain the name of the SQL tables, for
# easier use
SQL_TABLES = {}
for attrname in list(database.__dict__.keys()):
attr = database.__getattribute__(attrname)
if hasattr(attr, 'sql_table'):
SQL_TABLES[attrname] = attr.sql_table
#######################################################################
class InfoXmlException(Exception):
pass
#######################################################################
class InfoXml:
def __init__(self, dest_dir, debug = False):
self.dest_dir = dest_dir
self._debug = debug
self._version_cache = None
def _debug_print(self, s):
""" Print s if debug is enabled. """
if self._debug:
print('XML: %s' % s)
def _get_version(self, project, package):
""" Gets the version of a package, in a safe way. """
try:
return self._version_cache[project][package]
except KeyError as e:
return None
def _get_package_node_from_row(self, row, ignore_upstream, default_parent_project):
""" Get the XML node for the package defined in row. """
name = row['name']
version = row['version']
link_project = row['link_project']
link_package = row['link_package']
devel_project = row['devel_project']
devel_package = row['devel_package']
upstream_version = row['upstream_version']
upstream_url = row['upstream_url']
is_link = row['is_obs_link']
has_delta = row['obs_link_has_delta']
error = row['obs_error']
error_details = row['obs_error_details']
parent_version = None
devel_version = None
package = ET.Element('package')
package.set('name', name)
if link_project:
if (link_project != default_parent_project) or (link_package and link_package != name):
node = ET.SubElement(package, 'parent')
node.set('project', link_project)
if link_package and link_package != name:
node.set('package', link_package)
parent_version = self._get_version(link_project, link_package or name)
elif default_parent_project:
parent_version = self._get_version(default_parent_project, name)
if devel_project:
node = ET.SubElement(package, 'devel')
node.set('project', devel_project)
if devel_package and devel_package != name:
node.set('package', devel_package)
devel_version = self._get_version(devel_project, devel_package or name)
if version or upstream_version or parent_version or devel_version:
node = ET.SubElement(package, 'version')
if version:
node.set('current', version)
if upstream_version:
node.set('upstream', upstream_version)
if parent_version:
node.set('parent', parent_version)
if devel_version:
node.set('devel', devel_version)
if upstream_url:
upstream = ET.SubElement(package, 'upstream')
if upstream_url:
node = ET.SubElement(upstream, 'url')
node.text = upstream_url
if is_link:
node = ET.SubElement(package, 'link')
if has_delta:
node.set('delta', 'true')
else:
node.set('delta', 'false')
# deep delta (ie, delta in non-link packages)
elif has_delta:
node = ET.SubElement(package, 'delta')
if error:
node = ET.SubElement(package, 'error')
node.set('type', error)
if error_details:
node.text = error_details
return package
def _get_project_node(self, cursor, project):
""" Get the XML node for project. """
cursor.execute('''SELECT * FROM %(Project)s WHERE name = ?;''' % SQL_TABLES, (project,))
row = cursor.fetchone()
if not row:
raise InfoXmlException('Non-existing project: %s' % project)
if project not in self._version_cache:
raise InfoXmlException('Version cache was not created correctly: %s is not in the cache' % project)
project_id = row['id']
parent_project = row['parent']
ignore_upstream = row['ignore_upstream']
prj_node = ET.Element('project')
prj_node.set('name', project)
if parent_project:
prj_node.set('parent', parent_project)
if ignore_upstream:
prj_node.set('ignore_upstream', 'true')
should_exist = {}
cursor.execute('''SELECT A.name AS parent_project, B.name AS parent_package, B.devel_package
FROM %(Project)s AS A, %(SrcPackage)s AS B
WHERE A.id = B.project AND devel_project = ?
ORDER BY A.name, B.name;''' % SQL_TABLES, (project,))
for row in cursor:
should_parent_project = row['parent_project']
should_parent_package = row['parent_package']
should_devel_package = row['devel_package'] or should_parent_package
should_exist[should_devel_package] = (should_parent_project, should_parent_package)
cursor.execute('''SELECT * FROM %(SrcPackage)s
WHERE project = ?
ORDER BY name;''' % SQL_TABLES, (project_id,))
for row in cursor:
pkg_node = self._get_package_node_from_row(row, ignore_upstream, parent_project)
prj_node.append(pkg_node)
try:
del should_exist[row['name']]
except KeyError:
pass
if len(should_exist) > 0:
missing_node = ET.Element('missing')
for (should_package_name, (should_parent_project, should_parent_package)) in should_exist.items():
missing_pkg_node = ET.Element('package')
missing_pkg_node.set('name', should_package_name)
missing_pkg_node.set('parent_project', should_parent_project)
if should_package_name != should_parent_package:
missing_pkg_node.set('parent_package', should_parent_package)
missing_node.append(missing_pkg_node)
prj_node.append(missing_node)
return prj_node
def _create_version_cache(self, cursor, projects = None):
""" Creates a cache containing version of all packages. """
# This helps us avoid doing many small SQL queries, which is really
# slow.
#
# The main difference is that we do one SQL query + many hash accesses,
# vs 2*(total number of packages in the database) SQL queries. On a
# test run, the difference results in ~1min15s vs ~5s. That's a 15x
# time win.
self._version_cache = {}
if not projects:
cursor.execute('''SELECT name FROM %(Project)s;''' % SQL_TABLES)
projects = [ row['name'] for row in cursor ]
for project in projects:
self._version_cache[project] = {}
cursor.execute('''SELECT A.name, A.version, B.name AS project
FROM %(SrcPackage)s AS A, %(Project)s AS B
WHERE A.project = B.id;''' % SQL_TABLES)
for row in cursor:
self._version_cache[row['project']][row['name']] = row['version']
def _write_xml_for_project(self, cursor, project):
""" Writes the XML file for a project.
Note that we don't touch the old file if the result is the same.
This can be useful for browser cache.
"""
node = self._get_project_node(cursor, project)
filename = os.path.join(self.dest_dir, project + '.xml')
tmpfilename = filename + '.tmp'
tree = ET.ElementTree(node)
try:
tree.write(tmpfilename)
# keep the old file if there's no change (useful when downloaded
# from the web to not re-download again the file)
if os.path.exists(filename):
if filecmp.cmp(filename, tmpfilename, shallow = False):
self._debug_print('XML for %s did not change' % project)
os.unlink(tmpfilename)
return
os.rename(tmpfilename, filename)
except Exception as e:
if os.path.exists(tmpfilename):
os.unlink(tmpfilename)
raise e
def run(self, cursor, changed_projects = None):
""" Creates the XML files for all projects.
changed_projects -- The list of projects for which we need to
generate a XML file. "None" means all projects.
"""
if not cursor:
raise InfoXmlException('Database needed to create XML files is not available.')
util.safe_mkdir_p(self.dest_dir)
cursor.execute('''SELECT name FROM %(Project)s;''' % SQL_TABLES)
projects = [ row['name'] for row in cursor ]
self._create_version_cache(cursor, projects)
if changed_projects is not None:
# We have a specific list of projects for which we need to create
# the XML. Note that None and [] don't have the same meaning.
if not changed_projects:
return
# Get the list of projects containing a package which links to a
# changed project, or which has a a devel project that has changed
where = ' OR '.join([ 'B.link_project = ? OR B.devel_project = ?' for i in range(len(changed_projects)) ])
where_args = []
for changed_project in changed_projects:
where_args.append(changed_project)
where_args.append(changed_project)
mapping = SQL_TABLES.copy()
mapping['where'] = where
cursor.execute('''SELECT A.name FROM %(Project)s as A, %(SrcPackage)s as B
WHERE A.id = B.project AND (%(where)s)
GROUP BY A.name
;''' % mapping, where_args)
changed_projects = set(changed_projects)
for (project,) in cursor:
changed_projects.add(project)
projects = changed_projects
for project in projects:
self._debug_print('Writing XML for %s' % project)
self._write_xml_for_project(cursor, project)
def remove_project(self, project):
filename = os.path.join(self.dest_dir, project + '.xml')
if os.path.exists(filename):
os.unlink(filename)
#######################################################################
def main(args):
import sqlite3
if len(args) != 3:
print('Usage: %s dbfile project' % args[0], file=sys.stderr)
sys.exit(1)
filename = args[1]
project = args[2]
if not os.path.exists(filename):
print('%s does not exist.' % filename, file=sys.stderr)
sys.exit(1)
try:
db = sqlite3.connect(filename)
except sqlite3.OperationalError as e:
print('Error while opening %s: %s' % (filename, e), file=sys.stderr)
sys.exit(1)
db.row_factory = sqlite3.Row
db.text_factory = sqlite3.OptimizedUnicode
cursor = db.cursor()
info = InfoXml('.', True)
try:
info._create_version_cache(cursor)
node = info._get_project_node(cursor, project)
except InfoXmlException as e:
print('Error while creating the XML for %s: %s' % (project, e), file=sys.stderr)
sys.exit(1)
tree = ET.ElementTree(node)
try:
print(ET.tostring(tree, pretty_print = True))
except TypeError:
# pretty_print only works with lxml
tree.write(sys.stdout)
cursor.close()
db.close()
if __name__ == '__main__':
try:
main(sys.argv)
except KeyboardInterrupt:
pass
except IOError as e:
if e.errno == errno.EPIPE:
pass
07070100000012000081ED0000000000000000000000016548EB8C0000071D000000000000000000000000000000000000003000000000osc-plugin-collab-0.104+30/server/obs-db/obs-db#!/usr/bin/env python3
# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2009, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import sys
import shell
try:
ret = shell.main(sys.argv)
sys.exit(ret)
except KeyboardInterrupt:
pass
07070100000013000081ED0000000000000000000000016548EB8C00000AF1000000000000000000000000000000000000003D00000000osc-plugin-collab-0.104+30/server/obs-db/obs-manual-checkout#!/usr/bin/env python3
# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2009, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sys
import traceback
import buildservice
import shellutils
def main(args):
(args, options, conf) = shellutils.get_conf(args)
if not conf:
return 1
if len(args) not in [1, 2]:
print("Please specify a project or a package.", file=sys.stderr)
return 1
project = args[0]
if len(args) == 2:
package = args[1]
else:
package = None
if not shellutils.lock_run(conf):
return 1
retval = 1
try:
mirror_dir = os.path.join(conf.cache_dir, 'obs-mirror')
obs = buildservice.ObsCheckout(conf, mirror_dir)
if not package:
obs.queue_checkout_project(project)
else:
obs.queue_checkout_package(project, package)
obs.queue_checkout_package_meta(project, package)
obs.run()
retval = 0
except Exception as e:
traceback.print_exc()
shellutils.unlock_run(conf)
return retval
if __name__ == '__main__':
try:
ret = main(sys.argv)
sys.exit(ret)
except KeyboardInterrupt:
pass
07070100000014000081ED0000000000000000000000016548EB8C00003783000000000000000000000000000000000000004100000000osc-plugin-collab-0.104+30/server/obs-db/obs-upstream-attributes#!/usr/bin/env python3
# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2010, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sys
import optparse
import socket
import traceback
import urllib.request, urllib.error, urllib.parse
try:
from lxml import etree as ET
except ImportError:
try:
from xml.etree import cElementTree as ET
except ImportError:
import cElementTree as ET
from osc import core
import config
import database
import shellutils
import upstream
#######################################################################
class UpstreamAttributesException(Exception):
pass
#######################################################################
def package_check_attribute_valid_arguments(project, package, namespace, name):
if not project:
raise UpstreamAttributesException('Internal error: no project defined')
if not package:
raise UpstreamAttributesException('Internal error: no package defined')
if not namespace:
raise UpstreamAttributesException('Internal error: no namespace defined')
if not name:
raise UpstreamAttributesException('Internal error: no name defined')
#######################################################################
def package_get_attribute(apiurl, project, package, namespace, name, try_again = True):
package_check_attribute_valid_arguments(project, package, namespace, name)
attribute_name = '%s:%s' % (namespace, name)
url = core.makeurl(apiurl, ['source', project, package, '_attribute', attribute_name])
try:
fin = core.http_GET(url)
except (urllib.error.HTTPError, urllib.error.URLError, socket.error) as e:
if type(e) == urllib.error.HTTPError and e.code == 404:
print('Package %s in project %s doesn\'t exist.' % (package, project), file=sys.stderr)
elif try_again:
return package_get_attribute(apiurl, project, package, namespace, name, False)
else:
raise UpstreamAttributesException('Cannot look for attribute %s for package %s in project %s: %s' % (attribute_name, package, project, e))
return None
try:
attributes_node = ET.parse(fin).getroot()
except SyntaxError as e:
fin.close()
raise UpstreamAttributesException('Cannot look for attribute %s for package %s in project %s: %s' % (attribute_name, package, project, e))
fin.close()
for attribute_node in attributes_node.findall('attribute'):
if attribute_node.get('namespace') == namespace and attribute_node.get('name') == name:
value = []
for value_node in attribute_node.findall('value'):
value.append(value_node.text)
if not value:
return ''
elif len(value) == 1:
return value[0]
else:
return value
return None
#######################################################################
def package_has_attribute(apiurl, project, package, namespace, name):
return package_get_attribute(apiurl, project, package, namespace, name) is not None
#######################################################################
def package_set_attribute_handle_reply(fin, error_str):
try:
node = ET.parse(fin).getroot()
except SyntaxError as e:
fin.close()
raise UpstreamAttributesException('s: %s' % (error_str, e))
fin.close()
if node.get('code') != 'ok':
try:
summary = node.find('summary').text
except:
summary = 'Unknown error'
try:
details = node.find('details').text
except:
details = ''
if details:
raise UpstreamAttributesException('%s: %s (%s)' % (error_str, summary, details))
else:
raise UpstreamAttributesException('%s: %s' % (error_str, summary))
def get_xml_for_attributes(attributes_values):
if len(attributes_values) == 0:
return None
attributes_node = ET.Element('attributes')
for (namespace, name, value) in attributes_values:
attribute_node = ET.SubElement(attributes_node, 'attribute')
attribute_node.set('namespace', namespace)
attribute_node.set('name', name)
value_node = ET.SubElement(attribute_node, 'value')
value_node.text = value
return ET.tostring(attributes_node)
#######################################################################
def package_unset_attribute(apiurl, project, package, namespace, name, try_again = True):
package_check_attribute_valid_arguments(project, package, namespace, name)
attribute_name = '%s:%s' % (namespace, name)
error_str = 'Cannot unset attribute %s for package %s in project %s' % (attribute_name, package, project)
if not package_has_attribute(apiurl, project, package, namespace, name):
return
url = core.makeurl(apiurl, ['source', project, package, '_attribute', attribute_name])
try:
fin = core.http_DELETE(url)
except (urllib.error.HTTPError, urllib.error.URLError, socket.error) as e:
if type(e) == urllib.error.HTTPError and e.code == 404:
print('Package %s in project %s doesn\'t exist.' % (package, project), file=sys.stderr)
elif try_again:
package_unset_attribute(apiurl, project, package, namespace, name, False)
else:
raise UpstreamAttributesException('%s: %s' % (error_str, e))
return
package_set_attribute_handle_reply(fin, error_str)
#######################################################################
def package_set_attributes(apiurl, project, package, attributes, try_again = True):
for (namespace, name, value) in attributes:
package_check_attribute_valid_arguments(project, package, namespace, name)
if value == None:
raise UpstreamAttributesException('Internal error: no value defined')
if len(attributes) == 1:
# namespace/name are set because of the above loop
attribute_name = '%s:%s' % (namespace, name)
error_str = 'Cannot set attribute %s for package %s in project %s' % (attribute_name, package, project)
else:
error_str = 'Cannot set attributes for package %s in project %s' % (package, project)
xml = get_xml_for_attributes(attributes)
url = core.makeurl(apiurl, ['source', project, package, '_attribute'])
try:
fin = core.http_POST(url, data = xml)
except (urllib.error.HTTPError, urllib.error.URLError, socket.error) as e:
if type(e) == urllib.error.HTTPError and e.code == 404:
print('Package %s in project %s doesn\'t exist.' % (package, project), file=sys.stderr)
elif try_again:
package_set_attributes(apiurl, project, package, attributes, False)
else:
raise UpstreamAttributesException('%s: %s' % (error_str, e))
return
package_set_attribute_handle_reply(fin, error_str)
def package_set_attribute(apiurl, project, package, namespace, name, value):
attributes = [ (namespace, name, value) ]
package_set_attributes(apiurl, project, package, attributes)
#######################################################################
def package_set_upstream_attributes(apiurl, project, package, upstream_version, upstream_url, ignore_empty = False):
if upstream_version and upstream_url:
attributes = [ ('openSUSE', 'UpstreamVersion', upstream_version), ('openSUSE', 'UpstreamTarballURL', upstream_url) ]
package_set_attributes(apiurl, project, package, attributes)
return
if upstream_version:
package_set_attribute(apiurl, project, package, 'openSUSE', 'UpstreamVersion', upstream_version)
elif not ignore_empty:
package_unset_attribute(apiurl, project, package, 'openSUSE', 'UpstreamVersion')
if upstream_url:
package_set_attribute(apiurl, project, package, 'openSUSE', 'UpstreamTarballURL', upstream_url)
elif not ignore_empty:
package_unset_attribute(apiurl, project, package, 'openSUSE', 'UpstreamTarballURL')
#######################################################################
def run(conf, do_projects = None, initial = False):
status_file = os.path.join(conf.cache_dir, 'status', 'attributes')
failed_file = os.path.join(conf.cache_dir, 'status', 'attributes-failed')
db_dir = os.path.join(conf.cache_dir, 'db')
mirror_dir = os.path.join(conf.cache_dir, 'obs-mirror')
status = {}
status['upstream-mtime'] = -1
status = shellutils.read_status(status_file, status)
# Get packages that we had to update before, but where we failed
old_failed = []
if os.path.exists(failed_file):
failed_f = open(failed_file)
lines = failed_f.readlines()
failed_f.close()
for line in lines:
line = line[:-1]
try:
(project, package, upstream_version, upstream_url) = line.split('|', 3)
except ValueError:
raise UpstreamAttributesException('Invalid failed attribute line: %s' % line)
old_failed.append((project, package, upstream_version, upstream_url))
# Get packages we need to update
upstreamdb = upstream.UpstreamDb(None, db_dir, conf.debug)
new_upstream_mtime = upstreamdb.get_mtime()
db = database.ObsDb(conf, db_dir, mirror_dir, upstreamdb)
projects = db.get_packages_with_upstream_change(status['upstream-mtime'])
# close the database as soon as we don't need them anymore
del db
del upstreamdb
failed = []
for (project, package, upstream_version, upstream_url) in old_failed:
try:
if conf.debug:
print('UpstreamAttributes: %s/%s' % (project, package))
package_set_upstream_attributes(conf.apiurl, project, package, upstream_version, upstream_url)
except UpstreamAttributesException as e:
print(e, file=sys.stderr)
failed.append((project, package, upstream_version, upstream_url))
# Remove the failed file as soon as we know it was handled
if os.path.exists(failed_file):
os.unlink(failed_file)
for (project, packages) in list(projects.items()):
if do_projects and project not in do_projects:
continue
for (package, (upstream_version, upstream_url)) in list(packages.items()):
try:
if conf.debug:
print('UpstreamAttributes: %s/%s' % (project, package))
package_set_upstream_attributes(conf.apiurl, project, package, upstream_version, upstream_url, ignore_empty = initial)
except UpstreamAttributesException as e:
print(e, file=sys.stderr)
failed.append((project, package, upstream_version, upstream_url))
# Save the failed packages for next run
if len(failed) > 0:
failed_f = open(failed_file, 'w')
for (project, package, upstream_version, upstream_url) in failed:
failed_f.write('|'.join((project, package, upstream_version, upstream_url)) + '\n')
failed_f.close()
# Save the status time last (saving it last ensures that everything has
# been really handled)
status['upstream-mtime'] = new_upstream_mtime
shellutils.write_status(status_file, status)
#######################################################################
def main(args):
parser = optparse.OptionParser()
parser.add_option('--initial', dest='initial',
action='store_true',
help='initial setting of attributes (will ignore empty data, instead of deleting attributes)')
parser.add_option('--project', dest='projects',
action='append', default = [],
metavar='PROJECT',
help='project to work on (default: all)')
(args, options, conf) = shellutils.get_conf(args, parser)
if not conf:
return 1
if not shellutils.lock_run(conf, 'attributes'):
return 1
retval = 1
try:
run(conf, options.projects, options.initial)
retval = 0
except Exception as e:
if isinstance(e, (UpstreamAttributesException, shellutils.ShellException, config.ConfigException, database.ObsDbException)):
print(e, file=sys.stderr)
else:
traceback.print_exc()
shellutils.unlock_run(conf, 'attributes')
return retval
if __name__ == '__main__':
try:
ret = main(sys.argv)
sys.exit(ret)
except KeyboardInterrupt:
pass
07070100000015000081A40000000000000000000000016548EB8C00000491000000000000000000000000000000000000003500000000osc-plugin-collab-0.104+30/server/obs-db/osc_copy.py# vim: sw=4 et
# Copyright (C) 2006 Novell Inc. All rights reserved.
# This program is free software; it may be used, copied, modified
# and distributed under the terms of the GNU General Public Licence,
# either version 2, or version 3 (at your option).
# This file contains copy of some trivial functions from osc that we want to
# use. It is copied here to avoid importing large python modules.
from urllib.parse import urlencode
from urllib.parse import urlsplit, urlunsplit
def makeurl(baseurl, l, query=[]):
"""Given a list of path compoments, construct a complete URL.
Optional parameters for a query string can be given as a list, as a
dictionary, or as an already assembled string.
In case of a dictionary, the parameters will be urlencoded by this
function. In case of a list not -- this is to be backwards compatible.
"""
#print 'makeurl:', baseurl, l, query
if type(query) == type(list()):
query = '&'.join(query)
elif type(query) == type(dict()):
query = urlencode(query)
scheme, netloc = urlsplit(baseurl)[0:2]
return urlunsplit((scheme, netloc, '/'.join(l), query, ''))
07070100000016000081ED0000000000000000000000016548EB8C0000117D000000000000000000000000000000000000002F00000000osc-plugin-collab-0.104+30/server/obs-db/runme#!/bin/sh
# vim: set ts=4 sw=4 et:
#
# Copyright (c) 2008-2009, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
basedir=`dirname $0`
## Options
# Do the rpmlint stuff
OBS_DO_RPMLINT=0
## Basic setup
CACHE_DIR=./cache
USE_OPENSUSE=
CONFIG_FILE=
LOG_FILE=
usage() {
echo "Usage: $0 [-o CONF-FILE] [-l LOG-FILE] [-s]"
echo ""
echo "Options:"
echo " -o CONF-FILE Use CONF-FILE as configuration file"
echo " -l LOG-FILE Use LOG-FILE to log errors"
echo " -s Use the openSUSE configuration file as a basis"
}
while getopts o:l:sh option; do
case $option in
o) CONFIG_FILE=$OPTARG;;
l) LOG_FILE=$OPTARG;;
s) USE_OPENSUSE=--opensuse;;
h|help) usage; exit 0;;
*) usage; exit 1;;
esac
done
if test "x$CONFIG_FILE" != "x"; then
if test ! -f $CONFIG_FILE; then
echo >&2 "Configuration file $CONFIG_FILE does not exit."
exit 1
else
OBS_OPTIONS_CACHE_DIR=`grep "^ *cache-dir =" $CONFIG_FILE | sed "s/.*= *\(.*\) *$/\1/g" | tail -n 1`
test "x$OBS_OPTIONS_CACHE_DIR" != "x" && CACHE_DIR=$OBS_OPTIONS_CACHE_DIR
fi
fi
mkdir -p $CACHE_DIR
##############################################################
# Copy the upstream name / package name match database
mkdir -p $CACHE_DIR/upstream
cmp --quiet $basedir/../upstream/upstream-packages-match.txt $CACHE_DIR/upstream/upstream-packages-match.txt
if test $? -ne 0; then
cp -a $basedir/../upstream/upstream-packages-match.txt $CACHE_DIR/upstream/
fi
##############################################################
# Get the rpmlint data
# GNOME:Factory only
# We download the rpmlint data. We keep the old version around until we're sure
# the new version is fine.
get_rpmlint () {
PROJECT=$1
if test "x$1" = "x"; then
return
fi
if test -f rpmlint.tar.bz2; then
rm -f rpmlint.tar.bz2
fi
wget -q ftp://ftp.coolice.org/rpmlint/$PROJECT/rpmlint.tar.bz2
if test $? -eq 0; then
if test -d $PROJECT; then
mv $PROJECT $PROJECT.old
fi
tar jxf rpmlint.tar.bz2
if test $? -ne 0 -a -d $PROJECT.old; then
mv $PROJECT.old $PROJECT
fi
if test -d $PROJECT.old; then
rm -rf $PROJECT.old
fi
rm -f rpmlint.tar.bz2
fi
}
if test "x$OBS_DO_RPMLINT" = "x1"; then
mkdir -p $CACHE_DIR/rpmlint
pushd $CACHE_DIR/rpmlint > /dev/null
get_rpmlint openSUSE:Factory
get_rpmlint GNOME:Factory
get_rpmlint GNOME:Contrib
popd > /dev/null
fi
##############################################################
# Check out everything and create the databases
CONFIG_OPTION=
if test "x$CONFIG_FILE" != "x"; then
CONFIG_OPTION="--config $CONFIG_FILE"
fi
LOG_OPTION=
if test "x$LOG_FILE" != "x"; then
LOG_OPTION="--log $LOG_FILE"
fi
$basedir/obs-db $CONFIG_OPTION $LOG_OPTION $USE_OPENSUSE
07070100000017000081ED0000000000000000000000016548EB8C00000BDB000000000000000000000000000000000000003A00000000osc-plugin-collab-0.104+30/server/obs-db/runme-attributes#!/bin/sh
# vim: set ts=4 sw=4 et:
#
# Copyright (c) 2008-2010, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
basedir=`dirname $0`
## Basic setup
CACHE_DIR=./cache
USE_OPENSUSE=
CONFIG_FILE=
LOG_FILE=
usage() {
echo "Usage: $0 [-o CONF-FILE] [-l LOG-FILE] [-s]"
echo ""
echo "Options:"
echo " -o CONF-FILE Use CONF-FILE as configuration file"
echo " -l LOG-FILE Use LOG-FILE to log errors"
echo " -s Use the openSUSE configuration file as a basis"
}
while getopts o:l:sh option; do
case $option in
o) CONFIG_FILE=$OPTARG;;
l) LOG_FILE=$OPTARG;;
s) USE_OPENSUSE=--opensuse;;
h|help) usage; exit 0;;
*) usage; exit 1;;
esac
done
if test "x$CONFIG_FILE" != "x"; then
if test ! -f $CONFIG_FILE; then
echo >&2 "Configuration file $CONFIG_FILE does not exit."
exit 1
else
OBS_OPTIONS_CACHE_DIR=`grep "^ *cache-dir =" $CONFIG_FILE | sed "s/.*= *\(.*\) *$/\1/g" | tail -n 1`
test "x$OBS_OPTIONS_CACHE_DIR" != "x" && CACHE_DIR=$OBS_OPTIONS_CACHE_DIR
fi
fi
mkdir -p $CACHE_DIR
##############################################################
# Update attributes in the build service
CONFIG_OPTION=
if test "x$CONFIG_FILE" != "x"; then
CONFIG_OPTION="--config $CONFIG_FILE"
fi
LOG_OPTION=
if test "x$LOG_FILE" != "x"; then
LOG_OPTION="--log $LOG_FILE"
fi
$basedir/obs-upstream-attributes $CONFIG_OPTION $LOG_OPTION $USE_OPENSUSE
07070100000018000081A40000000000000000000000016548EB8C00005E55000000000000000000000000000000000000003200000000osc-plugin-collab-0.104+30/server/obs-db/shell.py# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2009, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sys
import errno
import optparse
import socket
import traceback
import buildservice
import config
import database
import hermes
import infoxml
import shellutils
import upstream
import util
#######################################################################
class RunnerException(Exception):
pass
#######################################################################
class Runner:
def __init__(self, conf):
""" Arguments:
config -- a config object
"""
self.conf = conf
self.hermes = None
self.obs = None
self.upstream = None
self.db = None
self.xml = None
self._status_file = os.path.join(self.conf.cache_dir, 'status', 'last')
self._status_catchup = os.path.join(self.conf.cache_dir, 'status', 'catchup')
self._mirror_error = os.path.join(self.conf.cache_dir, 'status', 'mirror-error')
self._mirror_dir = os.path.join(self.conf.cache_dir, 'obs-mirror')
self._upstream_dir = os.path.join(self.conf.cache_dir, 'upstream')
self._db_dir = os.path.join(self.conf.cache_dir, 'db')
self._xml_dir = os.path.join(self.conf.cache_dir, 'xml')
self._status = {}
# Last hermes event handled by mirror
self._status['mirror'] = -1
# Last hermes event handled by db
self._status['db'] = -1
# Last hermes event recorded in xml (it cannot be greater than the db one)
self._status['xml'] = -1
# mtime of the configuration that was last known
self._status['conf-mtime'] = -1
# mtime of the openSUSE configuration that was last known
self._status['opensuse-mtime'] = -1
# mtime of the upstream database
self._status['upstream-mtime'] = -1
self._catchup = []
def _debug_print(self, s):
""" Print s if debug is enabled. """
if self.conf.debug:
print('Main: %s' % s)
def _read_status(self):
""" Read the last known status of the script. """
self._status = shellutils.read_status(self._status_file, self._status)
def _write_status(self):
""" Save the last known status of the script. """
shellutils.write_status(self._status_file, self._status)
def _setup_catchup(self):
""" Gets a list of packages that we need to update because of some
reason: missing hermes message, bug somewhere, etc."""
if not os.path.exists(self._status_catchup):
return
file = open(self._status_catchup)
lines = file.readlines()
catchup = set()
for line in lines:
line = line[:-1]
s = line.split('/')
if len(s) not in [1, 2]:
print('Cannot handle catchup line: %s' % line, file=sys.stderr)
continue
if len(s) == 1:
if not self.conf.allow_project_catchup:
print('Cannot handle catchup line: %s (per config, projects are ignored)' % line, file=sys.stderr)
continue
(project,) = s
catchup.add((project, None))
elif len(s) == 2:
(project, package) = s
if not project:
print('Cannot handle catchup line: %s' % line, file=sys.stderr)
continue
if not package and not self.conf.allow_project_catchup:
print('Cannot handle catchup line: %s (per config, projects are ignored)' % line, file=sys.stderr)
continue
catchup.add((project, package))
self._catchup = list(catchup)
file.close()
def _empty_catchup(self):
# do not remove the catchup file if something still needs it on next
# run
if self.conf.skip_mirror or self.conf.skip_db or self.conf.skip_xml:
return
if os.path.exists(self._status_catchup):
try:
os.unlink(self._status_catchup)
except Exception as e:
print('Cannot remove catchup file: %s' % e, file=sys.stderr)
def _had_mirror_error(self, project, package):
""" Check if we had an error on mirror for this package. """
if (project, package) in self.obs.errors:
return True
# just to be on the safe side, for project checks, we check with both
# None and '' as package.
if package is None and (project, '') in self.obs.errors:
return True
return False
def _write_mirror_error(self):
if len(self.obs.errors) == 0:
return
# note that we append
fout = open(self._mirror_error, 'a')
for (project, package) in self.obs.errors:
if package:
fout.write('%s/%s\n' % (project, package))
else:
fout.write('%s\n' % project)
fout.close()
def _move_mirror_error_to_catchup(self):
if not os.path.exists(self._mirror_error):
return
# we don't do a simple cp, but copy the content since we want to append
# if the catchup file already exists
fin = open(self._mirror_error)
lines = fin.readlines()
fin.close
fout = open(self._status_catchup, 'a')
fout.writelines(lines)
fout.close()
try:
os.unlink(self._mirror_error)
except Exception as e:
print('Cannot remove mirror-error file: %s' % e, file=sys.stderr)
def _run_mirror(self, conf_changed):
if not os.path.exists(self._mirror_dir):
os.makedirs(self._mirror_dir)
if self.conf.skip_mirror:
return
# keep in sync this boolean expression and the one used for no_full_check
if not self.conf.force_hermes and (self._status['mirror'] == -1 or conf_changed):
# we don't know how old our mirror is, or the configuration has
# changed
# get a max id from hermes feeds
self.hermes.fetch_last_known_id()
# checkout the projects (or look if we need to update them)
for name in list(self.conf.projects.keys()):
if self.conf.mirror_only_new:
if os.path.exists(os.path.join(self._mirror_dir, name)):
continue
self.obs.queue_checkout_project(name)
else:
# update the relevant part of the mirror
# get events from hermes
self.hermes.read()
# reverse to have chronological order
events = self.hermes.get_events(self._status['mirror'], reverse = True)
for event in events:
# ignore events that belong to a project we do not monitor
# (ie, there's no checkout)
project_dir = os.path.join(self._mirror_dir, event.project)
if not os.path.exists(project_dir):
continue
if isinstance(event, hermes.HermesEventCommit):
self.obs.queue_checkout_package(event.project, event.package)
elif isinstance(event, hermes.HermesEventProjectDeleted):
# Even if there's a later commit to the same project (which
# is unlikely), we wouldn't know which packages are still
# relevant, so it's better to remove the project to not
# have unexisting packages in the database. The unlikely
# case will eat a bit more resources, but it's really
# unlikely to happen anyway.
self.obs.remove_checkout_project(event.project)
elif isinstance(event, hermes.HermesEventPackageMeta):
# Note that the ObsCheckout object will automatically check out
# devel projects that have appeared via metadata change, if
# necessary.
self.obs.queue_checkout_package_meta(event.project, event.package)
elif isinstance(event, hermes.HermesEventPackageAdded):
# The pkgmeta file of the project won't have anything about
# this package, so we need to download the metadata too.
self.obs.queue_checkout_package(event.project, event.package)
self.obs.queue_checkout_package_meta(event.project, event.package)
elif isinstance(event, hermes.HermesEventPackageDeleted):
self.obs.remove_checkout_package(event.project, event.package)
else:
raise RunnerException('Unhandled Hermes event type by mirror: %s' % event.__class__.__name__)
for (project, package) in self._catchup:
if package:
self.obs.queue_checkout_package(project, package)
self.obs.queue_checkout_package_meta(project, package)
elif self.conf.allow_project_catchup:
self.obs.queue_checkout_project(project, force_simple_checkout=True)
self.obs.run()
def _run_db(self, conf_changed):
""" Return if a full rebuild was done, and if anything has been updated. """
if self.conf.skip_db:
return (False, False)
if (self.conf.force_db or not self.db.exists() or
(not self.conf.force_hermes and (conf_changed or self._status['db'] == -1))):
# The database doesn't exist, the configuration has changed, or
# we don't have the whole list of events that have happened since
# the last database update. So we just rebuild it from scratch.
self.db.rebuild()
return (True, True)
else:
# update the relevant parts of the db
# reverse to have chronological order
events = self.hermes.get_events(self._status['db'], reverse = True)
if len(events) == 0 and len(self._catchup) == 0:
return (False, False)
changed = False
for event in events:
# ignore events that belong to a project we do not monitor
# (ie, there's no checkout)
project_dir = os.path.join(self._mirror_dir, event.project)
if not os.path.exists(project_dir):
continue
# do not handle packages that had an issue while mirroring
if self._had_mirror_error(event.project, event.package):
continue
changed = True
if isinstance(event, hermes.HermesEventCommit):
self.db.update_package(event.project, event.package)
elif isinstance(event, hermes.HermesEventProjectDeleted):
self.db.remove_project(event.project)
elif isinstance(event, hermes.HermesEventPackageMeta):
# Note that the ObsDb object will automatically add the
# devel projects to the database, if necessary.
self.db.update_package(event.project, event.package)
elif isinstance(event, hermes.HermesEventPackageAdded):
self.db.add_package(event.project, event.package)
elif isinstance(event, hermes.HermesEventPackageDeleted):
self.db.remove_package(event.project, event.package)
else:
raise RunnerException('Unhandled Hermes event type by database: %s' % event.__class__.__name__)
db_projects = set(self.db.get_projects())
for (project, package) in self._catchup:
# do not handle packages that had an issue while mirroring
if self._had_mirror_error(project, package):
continue
if package:
if project not in db_projects:
print('Cannot handle %s/%s catchup: project is not part of our analysis' % (project, package), file=sys.stderr)
continue
changed = True
self.db.add_package(project, package)
elif self.conf.allow_project_catchup:
self.db.update_project(project)
return (False, changed)
def _run_xml(self, changed_projects = None):
""" Update XML files.
changed_projects -- List of projects that we know will need an
update
"""
if self.conf.skip_xml:
return
if self.conf.force_xml or self._status['xml'] == -1:
changed_projects = None
else:
# adds projects that have changed, according to hermes
if changed_projects is None:
changed_projects = set()
else:
changed_projects = set(changed_projects)
# Order of events does not matter here
events = self.hermes.get_events(self._status['xml'])
for event in events:
# ignore events that belong to a project we do not monitor
# (ie, there's no checkout)
project_dir = os.path.join(self._mirror_dir, event.project)
if not os.path.exists(project_dir):
continue
# do not handle packages that had an issue while mirroring
if self._had_mirror_error(event.project, event.package):
continue
if isinstance(event, hermes.HermesEventCommit):
changed_projects.add(event.project)
elif isinstance(event, hermes.HermesEventProjectDeleted):
# this will have been removed already, as stale data
pass
elif isinstance(event, hermes.HermesEventPackageMeta):
changed_projects.add(event.project)
elif isinstance(event, hermes.HermesEventPackageAdded):
changed_projects.add(event.project)
elif isinstance(event, hermes.HermesEventPackageDeleted):
changed_projects.add(event.project)
else:
raise RunnerException('Unhandled Hermes event type by XML generator: %s' % event.__class__.__name__)
for (project, package) in self._catchup:
# do not handle packages that had an issue while mirroring
if self._had_mirror_error(project, package):
continue
if package:
changed_projects.add(project)
elif self.conf.allow_project_catchup:
changed_projects.add(project)
self.xml.run(self.db.get_cursor(), changed_projects)
def _remove_stale_data(self):
if self.conf.skip_mirror and self.conf.skip_db and self.conf.skip_xml:
return
if self.conf.skip_db and not self.db.exists():
# If there's no database, but we skip its creation, it's not a bug
return
# If one project exists in the database, but it's not an explicitly
# requested project, nor a devel project that we should have, then we
# can safely remove it from the mirror and from the database
requested_projects = list(self.conf.projects.keys())
needed = []
for project in requested_projects:
needed.append(project)
if self.conf.projects[project].checkout_devel_projects:
needed.extend(self.db.get_devel_projects(project))
needed = set(needed)
db_projects = set(self.db.get_projects())
unneeded = db_projects.difference(needed)
for project in unneeded:
if not self.conf.skip_xml:
self.xml.remove_project(project)
if not self.conf.skip_db:
self.db.remove_project(project)
if not self.conf.skip_mirror:
self.obs.remove_checkout_project(project)
if self.conf.skip_mirror and self.conf.skip_xml:
return
# We now have "projects in the db" = needed
db_projects = needed
if not self.conf.skip_mirror and os.path.exists(self._mirror_dir):
# If one project exists in the mirror but not in the db, then it's
# stale data from the mirror that we can remove.
mirror_projects = set([ subdir for subdir in os.listdir(self._mirror_dir) if os.path.isdir(subdir) ])
unneeded = mirror_projects.difference(db_projects)
for project in unneeded:
self.obs.remove_checkout_project(project)
if not self.conf.skip_xml and os.path.exists(self._xml_dir):
# If one project exists in the xml but not in the db, then it's
# stale data that we can remove.
xml_projects = set([ file for file in os.listdir(self._xml_dir) if file.endswith('.xml') ])
unneeded = xml_projects.difference(db_projects)
for project in unneeded:
self.xml.remove_project(project)
def run(self):
""" Run the various steps of the script."""
# Get the previous status, and some info about what will be the new one
self._read_status()
if self.conf.filename:
stats = os.stat(self.conf.filename)
new_conf_mtime = stats.st_mtime
else:
new_conf_mtime = -1
if self.conf.use_opensuse:
new_opensuse_mtime = self.conf.get_opensuse_mtime()
else:
new_opensuse_mtime = -1
conf_changed = (not self.conf.ignore_conf_mtime and
(self._status['conf-mtime'] != new_conf_mtime or
self._status['opensuse-mtime'] != new_opensuse_mtime))
# keep in sync this boolean expression and the one used in _run_mirror
if self.conf.no_full_check and (self._status['mirror'] == -1 or conf_changed):
print('Full checkout check needed, but disabled by config.')
return
# Setup hermes, it will be call before the mirror update, depending on
# what we need
# We need at least what the mirror have, and we might need something a
# bit older for the database or the xml (note that if we have no status
# for them, we will just rebuild everything anyway)
ids = [ self._status['mirror'] ]
if self._status['db'] != -1:
ids.append(self._status['db'])
if self._status['xml'] != -1:
ids.append(self._status['xml'])
min_last_known_id = min(ids)
self.hermes = hermes.HermesReader(min_last_known_id, self.conf.hermes_baseurl, self.conf.hermes_feeds, self.conf)
self._setup_catchup()
# Run the mirror update, and make sure to update the status afterwards
# in case we crash later
self.obs = buildservice.ObsCheckout(self.conf, self._mirror_dir)
self._run_mirror(conf_changed)
if not self.conf.mirror_only_new and not self.conf.skip_mirror:
# we don't want to lose events if we went to fast mode once
self._status['mirror'] = self.hermes.last_known_id
self._write_mirror_error()
self._write_status()
# Update/create the upstream database
self.upstream = upstream.UpstreamDb(self._upstream_dir, self._db_dir, self.conf.debug)
if not self.conf.skip_upstream:
self.upstream.update(self.conf.projects, self.conf.force_upstream)
new_upstream_mtime = self.upstream.get_mtime()
# Update/create the package database
self.db = database.ObsDb(self.conf, self._db_dir, self._mirror_dir, self.upstream)
(db_full_rebuild, db_changed) = self._run_db(conf_changed)
if not self.conf.mirror_only_new and not self.conf.skip_db:
# we don't want to lose events if we went to fast mode once
self._status['db'] = self.hermes.last_known_id
if not self.conf.skip_db and not self.conf.skip_upstream and not db_full_rebuild:
# There's no point a looking at the upstream changes if we did a
# full rebuild anyway
projects_changed_upstream = self.db.upstream_changes(self._status['upstream-mtime'])
self._status['upstream-mtime'] = new_upstream_mtime
else:
projects_changed_upstream = []
# Prepare the creation of xml files
self.xml = infoxml.InfoXml(self._xml_dir, self.conf.debug)
# Post-analysis to remove stale data, or enhance the database
self._remove_stale_data()
if not self.conf.skip_db:
if db_changed or projects_changed_upstream:
self.db.post_analyze()
else:
self._debug_print('No need to run the post-analysis')
# Create xml last, after we have all the right data
if db_full_rebuild:
# we want to generate all XML files for full rebuilds
self._run_xml()
else:
self._run_xml(projects_changed_upstream)
if not self.conf.skip_xml:
# if we didn't skip the xml step, then we are at the same point as
# the db
self._status['xml'] = self._status['db']
self._status['conf-mtime'] = new_conf_mtime
self._status['opensuse-mtime'] = new_opensuse_mtime
self._empty_catchup()
self._move_mirror_error_to_catchup()
self._write_status()
#######################################################################
def main(args):
(args, options, conf) = shellutils.get_conf(args)
if not conf:
return 1
if not shellutils.lock_run(conf):
return 1
runner = Runner(conf)
retval = 1
try:
runner.run()
retval = 0
except Exception as e:
if isinstance(e, (RunnerException, shellutils.ShellException, config.ConfigException, hermes.HermesException, database.ObsDbException, infoxml.InfoXmlException)):
print(e, file=sys.stderr)
else:
traceback.print_exc()
shellutils.unlock_run(conf)
return retval
if __name__ == '__main__':
try:
ret = main(sys.argv)
sys.exit(ret)
except KeyboardInterrupt:
pass
07070100000019000081A40000000000000000000000016548EB8C000015CA000000000000000000000000000000000000003700000000osc-plugin-collab-0.104+30/server/obs-db/shellutils.py# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2009-2010, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sys
import errno
import optparse
import socket
import config
import util
#######################################################################
class ShellException(Exception):
pass
#######################################################################
def get_conf(args, parser = None):
if not parser:
parser = optparse.OptionParser()
parser.add_option('--config', dest='config',
help='configuration file to use')
parser.add_option('--opensuse', dest='opensuse',
action='store_true', default=False,
help='use the openSUSE config as a basis')
parser.add_option('--log', dest='log',
help='log file to use (default: stderr)')
(options, args) = parser.parse_args()
if options.log:
path = os.path.realpath(options.log)
util.safe_mkdir_p(os.path.dirname(path))
sys.stderr = open(options.log, 'a')
try:
conf = config.Config(options.config, use_opensuse = options.opensuse)
except config.ConfigException as e:
print(e, file=sys.stderr)
return (args, options, None)
if conf.sockettimeout > 0:
# we have a setting for the default socket timeout to not hang forever
socket.setdefaulttimeout(conf.sockettimeout)
try:
os.makedirs(conf.cache_dir)
except OSError as e:
if e.errno != errno.EEXIST:
print('Cannot create cache directory.', file=sys.stderr)
return (args, options, None)
return (args, options, conf)
#######################################################################
def read_status(filename, template):
""" Read the last known status of the script. """
result = template.copy()
if not os.path.exists(filename):
return result
file = open(filename)
lines = file.readlines()
file.close()
for line in lines:
line = line[:-1]
handled = False
for key in list(result.keys()):
if line.startswith(key + '='):
value = line[len(key + '='):]
try:
result[key] = int(value)
except ValueError:
raise ShellException('Cannot parse status value for %s: %s' % (key, value))
handled = True
if not handled:
raise ShellException('Unknown status line: %s' % (line,))
return result
def write_status(filename, status_dict):
""" Save the last known status of the script. """
dirname = os.path.dirname(filename)
if not os.path.exists(dirname):
os.makedirs(dirname)
tmpfilename = filename + '.new'
# it's always better to have things sorted, since it'll be predictable
# (so better for human eyes ;-))
items = list(status_dict.items())
items.sort()
file = open(tmpfilename, 'w')
for (key, value) in items:
file.write('%s=%d\n' % (key, value))
file.close()
os.rename(tmpfilename, filename)
#######################################################################
def lock_run(conf, name = None):
# FIXME: this is racy, we need a real lock file. Or use an atomic operation
# like mkdir instead
if name:
running_file = os.path.join(conf.cache_dir, 'running-' + name)
else:
running_file = os.path.join(conf.cache_dir, 'running')
if os.path.exists(running_file):
print('Another instance of the script is running.', file=sys.stderr)
return False
open(running_file, 'w').write('')
return True
def unlock_run(conf, name = None):
if name:
running_file = os.path.join(conf.cache_dir, 'running-' + name)
else:
running_file = os.path.join(conf.cache_dir, 'running')
os.unlink(running_file)
#######################################################################
0707010000001A000081A40000000000000000000000016548EB8C000055D6000000000000000000000000000000000000003500000000osc-plugin-collab-0.104+30/server/obs-db/upstream.py# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2008-2009, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sys
import re
import sqlite3
import time
from posixpath import join as posixjoin
import util
MATCH_CHANGE_NAME = ''
# FIXME: we hardcode this list of branches since, well, there's no better way to do that :/
BRANCHES_WITHOUT_PKG_MATCH = [ 'fallback', 'cpan', 'pypi' ]
#######################################################################
class UpstreamDb:
def __init__(self, dest_dir, db_dir, debug = False):
self.dest_dir = dest_dir
self._debug = debug
# we'll store an int, so let's use an int right now
self._now = int(time.time())
# keep in memory what we removed
self._removed_matches = []
# this is by branch
self._removed_upstream = {}
self._dbfile = os.path.join(db_dir, 'upstream.db')
self.db = None
self.cursor = None
def _debug_print(self, s):
""" Print s if debug is enabled. """
if self._debug:
print('UpstreamDb: %s' % s)
def __del__(self):
# needed for the commit
self._close_db()
def _open_db(self, create_if_needed = False):
""" Open a database file, and sets up everything. """
if self.db:
return True
create = False
if not os.path.exists(self._dbfile):
if not create_if_needed:
return False
else:
util.safe_mkdir_p(os.path.dirname(self._dbfile))
create = True
self.db = sqlite3.connect(self._dbfile)
self.db.row_factory = sqlite3.Row
self.cursor = self.db.cursor()
if create:
self._sql_setup()
return True
def _close_db(self):
""" Closes the currently open database. """
if self.cursor:
self.cursor.close()
self.cursor = None
if self.db:
self.db.commit()
self.db.close()
self.db = None
def _sql_setup(self):
self.cursor.execute('''CREATE TABLE upstream_pkg_name_match (
id INTEGER PRIMARY KEY,
srcpackage TEXT,
upstream TEXT,
updated INTEGER
);''')
self.cursor.execute('''CREATE TABLE upstream (
id INTEGER PRIMARY KEY,
branch INTEGER,
name TEXT,
version TEXT,
url TEXT,
updated INTEGER
);''')
self.cursor.execute('''CREATE TABLE branches (
id INTEGER PRIMARY KEY,
branch TEXT,
mtime INTEGER
);''')
def _is_line_comment(self, line):
return line[0] == '#' or line.strip() == ''
def _update_upstream_pkg_name_match(self, matchfile):
matchpath = os.path.join(self.dest_dir, matchfile)
self.cursor.execute('''SELECT * FROM upstream_pkg_name_match;''')
oldmatches = {}
for row in self.cursor:
oldmatches[row['srcpackage']] = (row['id'], row['upstream'])
if not os.path.exists(matchpath):
print('No upstream/package name match database available, keeping previous data.', file=sys.stderr)
return
handled = []
file = open(matchpath)
re_names = re.compile('^(.+):(.*)$')
while True:
line = file.readline()
if len(line) == 0:
break
if self._is_line_comment(line):
continue
match = re_names.match(line)
if not match:
continue
upstream = match.group(1)
if match.group(2) != '':
srcpackage = match.group(2)
else:
srcpackage = upstream
if srcpackage in handled:
print('Source package %s defined more than once in %s.' % (srcpackage, matchfile), file=sys.stderr)
elif srcpackage in oldmatches:
# Update the entry if it has changed
(id, oldupstream) = oldmatches[srcpackage]
if oldupstream != upstream:
# Note: we don't put the mtime here, since we use the
# updated time in get_changed_packages
self.cursor.execute('''UPDATE upstream_pkg_name_match SET
upstream = ?, updated = ?
WHERE id = ?
;''',
(upstream, self._now, id))
del oldmatches[srcpackage]
handled.append(srcpackage)
else:
# Add the entry
self.cursor.execute('''INSERT INTO upstream_pkg_name_match VALUES (
NULL, ?, ?, ?
);''',
(srcpackage, upstream, self._now))
handled.append(srcpackage)
file.close()
# Remove matches that were removed in the source file
if len(oldmatches) > 0:
ids = [ id for (id, oldupstream) in list(oldmatches.values()) ]
where = ' OR '.join([ 'id = ?' for i in range(len(ids)) ])
self.cursor.execute('''DELETE FROM upstream_pkg_name_match WHERE %s;''' % where, ids)
# will be used in get_changed_packages()
self._removed_matches = list(oldmatches.keys())
else:
self._removed_matches = []
def _get_upstream_name_branches(self):
result = {}
self.cursor.execute('''SELECT upstream FROM upstream_pkg_name_match WHERE upstream LIKE "%|%"''')
for row in self.cursor:
name_branch = row['upstream']
index = name_branch.find('|')
name = name_branch[:index]
limit = name_branch[index + 1:]
item = (name_branch, limit)
if name in result:
name_branches = result[name]
name_branches.append(item)
result[name] = name_branches
else:
result[name] = [ item ]
return result
def _get_branch_data(self, branch):
self.cursor.execute('''SELECT id, mtime FROM branches WHERE
branch = ?;''', (branch,))
row = self.cursor.fetchone()
if row:
return (row['id'], row['mtime'])
else:
return ('', '')
def _update_upstream_data(self, branch, upstream_name_branches):
branch_path = os.path.join(self.dest_dir, branch)
if not os.path.exists(branch_path):
print('No file %s available for requested branch %s, keeping previous data if available.' % (branch_path, branch), file=sys.stderr)
return
(branch_id, branch_mtime) = self._get_branch_data(branch)
stats = os.stat(branch_path)
new_mtime = stats.st_mtime
if not branch_id:
# the branch does not exist, add it
self.cursor.execute('''INSERT INTO branches VALUES (
NULL, ?, ?
);''',
(branch, new_mtime))
self.cursor.execute('''SELECT last_insert_rowid();''')
branch_id = self.cursor.fetchone()[0]
else:
# do not update anything if the file has not changed
if branch_mtime >= new_mtime:
return
# else update the mtime
self.cursor.execute('''UPDATE branches SET
mtime = ? WHERE id = ?;''',
(new_mtime, branch_id))
self.cursor.execute('''SELECT * FROM upstream WHERE branch = ?;''', (branch_id,))
olddata = {}
for row in self.cursor:
olddata[row['name']] = (row['id'], row['version'], row['url'])
# upstream data, after we've converted the names to branch names if
# needed. For instance, glib:1.2.10 will translate to the "glib|1.3"
# name but also to the "glib" name if it doesn't exist yet or if the
# version there is lower than 1.2.10.
real_upstream_data = {}
re_upstream_data = re.compile('^([^:]*):([^:]+):([^:]+):(.*)$')
file = open(branch_path)
while True:
line = file.readline()
if len(line) == 0:
break
if self._is_line_comment(line):
continue
line = line[:-1]
match = re_upstream_data.match(line)
if not match:
continue
name = match.group(2)
version = match.group(3)
if match.group(1) == 'fallback':
url = ''
elif match.group(1) == 'nonfgo':
url = match.group(4)
elif match.group(1) == 'upstream':
url = ''
elif match.group(1) == 'cpan':
url = posixjoin('http://cpan.perl.org/CPAN/authors/id/', match.group(4))
elif match.group(1) == 'pypi':
url = match.group(4)
elif match.group(1) == 'fgo':
versions = version.split('.')
if len(versions) == 1:
majmin = version
elif int(versions[0]) >= 40:
majmin = versions[0]
else:
majmin = versions[0] + '.' + versions[1]
url = 'https://download.gnome.org/sources/%s/%s/%s-%s.tar.xz' % (name, majmin, name, version)
else:
print('Unknown upstream group for metadata: %s (full line: \'%s\').' % (match.group(1), line), file=sys.stderr)
url = ''
ignore = False
if name in real_upstream_data:
(current_version, current_url) = real_upstream_data[name]
if util.version_ge(current_version, version):
ignore = True
if not ignore:
real_upstream_data[name] = (version, url)
# Now also fill data for 'glib|1.2.10' if it fits
if name in upstream_name_branches:
# name = 'glib', upstream_name_branch = 'glib|1.2.10'
# and limit = '1.2.10'
for (upstream_name_branch, limit) in upstream_name_branches[name]:
if upstream_name_branch in real_upstream_data:
(current_version, current_url) = real_upstream_data[upstream_name_branch]
if util.version_ge(current_version, version):
continue
if util.version_ge(version, limit):
continue
real_upstream_data[upstream_name_branch] = (version, url)
for (name, (version, url)) in list(real_upstream_data.items()):
if name in olddata:
# Update the entry if it has changed
(id, oldversion, oldurl) = olddata[name]
if oldversion != version or oldurl != url:
# Note: we don't put the mtime here, since we use the
# updated time in get_changed_packages
self.cursor.execute('''UPDATE upstream SET
version = ?, url = ?, updated = ?
WHERE id = ?
;''',
(version, url, self._now, id))
del olddata[name]
else:
# Add the entry
self.cursor.execute('''INSERT INTO upstream VALUES (
NULL, ?, ?, ?, ?, ?
);''',
(branch_id, name, version, url, self._now))
file.close()
# Remove data that was removed in the source file
if len(olddata) > 0:
ids = [ id for (id, version, url) in list(olddata.values()) ]
# Delete by group of 50, since it once had to remove ~1800 items
# and it didn't work fine
chunk_size = 50
ids_len = len(ids)
for index in range(int(ids_len / chunk_size)):
chunk_ids = ids[index * chunk_size : (index + 1) * chunk_size]
where = ' OR '.join([ 'id = ?' for i in range(len(chunk_ids)) ])
self.cursor.execute('''DELETE FROM upstream WHERE %s;''' % where, chunk_ids)
remainder = ids_len % chunk_size
if remainder > 0:
chunk_ids = ids[- remainder:]
where = ' OR '.join([ 'id = ?' for i in range(len(chunk_ids)) ])
self.cursor.execute('''DELETE FROM upstream WHERE %s;''' % where, chunk_ids)
self._removed_upstream[branch] = list(olddata.keys())
else:
self._removed_upstream[branch] = []
def _remove_old_branches(self, branches):
self.cursor.execute('''SELECT * FROM branches;''')
for row in self.cursor:
branch = row['branch']
if not branch in branches:
id = row['id']
self.cursor.execute('''DELETE FROM upstream WHERE branch = ?;''', (id,))
self.cursor.execute('''DELETE FROM branches WHERE id = ?;''', (id,))
def _is_without_upstream(self, name):
index = name.rfind('branding')
if index > 0:
return name[index:] in ['branding-openSUSE', 'branding-SLED', 'branding-SLES']
return False
def _get_upstream_name(self, srcpackage):
self.cursor.execute('''SELECT upstream FROM upstream_pkg_name_match WHERE
srcpackage = ?;''', (srcpackage,))
row = self.cursor.fetchone()
if row:
return row[0]
else:
return ''
def _exist_in_branch_from_db(self, branch, name):
(branch_id, branch_mtime) = self._get_branch_data(branch)
if not branch_id:
return False
self.cursor.execute('''SELECT name FROM upstream WHERE
name = ? AND branch = ?;''', (name, branch_id))
row = self.cursor.fetchone()
if row:
return True
else:
return False
def exists_in_branches(self, branches, srcpackage):
if not self._open_db():
return False
name = self._get_upstream_name(srcpackage)
for branch in branches:
if branch in BRANCHES_WITHOUT_PKG_MATCH:
query_name = srcpackage
else:
query_name = name
if query_name and self._exist_in_branch_from_db(branch, query_name):
return True
return False
def _get_data_from_db(self, branch, name):
(branch_id, branch_mtime) = self._get_branch_data(branch)
if not branch_id:
return ('', '')
self.cursor.execute('''SELECT version, url FROM upstream WHERE
name = ? AND branch = ?;''', (name, branch_id))
row = self.cursor.fetchone()
if row:
return (row[0], row[1])
else:
return ('', '')
def get_upstream_data(self, branches, srcpackage):
if not self._open_db():
return ('', '', '')
name = self._get_upstream_name(srcpackage)
(version, url) = ('', '')
for branch in branches:
if branch in BRANCHES_WITHOUT_PKG_MATCH:
query_name = srcpackage
else:
query_name = name
if query_name:
(version, url) = self._get_data_from_db(branch, query_name)
if version:
break
if not version:
if self._is_without_upstream(srcpackage):
version = '--'
else:
version = ''
return (name, version, url)
def get_mtime(self):
if not self._open_db():
return -1
self.cursor.execute('''SELECT MAX(updated) FROM upstream_pkg_name_match;''')
max_match = self.cursor.fetchone()[0]
self.cursor.execute('''SELECT MAX(updated) FROM upstream;''')
max_data = self.cursor.fetchone()[0]
if not isinstance(max_data, int):
max_data = 0
return max(max_match, max_data)
def get_changed_packages(self, old_mtime):
if not self._open_db():
return {}
changed = {}
self.cursor.execute('''SELECT srcpackage FROM upstream_pkg_name_match
WHERE updated > ?;''', (old_mtime,))
changed[MATCH_CHANGE_NAME] = [ row['srcpackage'] for row in self.cursor ]
changed[MATCH_CHANGE_NAME].extend(self._removed_matches)
self.cursor.execute('''SELECT id, branch FROM branches;''')
branches = []
for (id, branch) in self.cursor:
branches.append((id, branch))
if branch in self._removed_upstream:
changed[branch] = self._removed_upstream[branch]
else:
changed[branch] = []
# Doing a joint query is slow, so we do a cache first
match_cache = {}
self.cursor.execute('''SELECT srcpackage, upstream FROM upstream_pkg_name_match;''')
for (srcpackage, upstream) in self.cursor:
if upstream in match_cache:
match_cache[upstream].append(srcpackage)
else:
match_cache[upstream] = [ srcpackage ]
for (id, branch) in branches:
# Joint query that is slow
#self.cursor.execute('''SELECT A.srcpackage
# FROM upstream_pkg_name_match as A, upstream as B
# WHERE B.updated > ? AND B.name = A.upstream AND B.branch = ?;''', (old_mtime, id))
#changed[branch].extend([ row['srcpackage'] for row in self.cursor ])
self.cursor.execute('''SELECT name FROM upstream
WHERE updated > ? AND branch = ?;''', (old_mtime, id))
if branch in BRANCHES_WITHOUT_PKG_MATCH:
for (name,) in self.cursor:
changed[branch].append(name)
else:
for (name,) in self.cursor:
if name in match_cache:
changed[branch].extend(match_cache[name])
self._debug_print('%d upstream(s) changed' % sum([ len(i) for i in list(changed.values()) ]))
for branch in list(changed.keys()):
if not changed[branch]:
del changed[branch]
return changed
def update(self, project_configs, rebuild = False):
if rebuild:
self._close_db()
if os.path.exists(self._dbfile):
os.unlink(self._dbfile)
self._open_db(create_if_needed = True)
self._update_upstream_pkg_name_match('upstream-packages-match.txt')
upstream_name_branches = self._get_upstream_name_branches()
branches = []
for project in list(project_configs.keys()):
branches.extend(project_configs[project].branches)
branches = set(branches)
for branch in branches:
if branch:
self._update_upstream_data(branch, upstream_name_branches)
self._remove_old_branches(branches)
self.db.commit()
#######################################################################
def main(args):
class ProjectConfig:
def __init__(self, branch):
self.branch = branch
configs = {}
configs['gnome-2.32'] = ProjectConfig('gnome-2.32')
configs['latest'] = ProjectConfig('latest')
upstream = UpstreamDb('/tmp/obs-dissector/cache/upstream', '/tmp/obs-dissector/tmp')
upstream.update(configs)
print('glib (latest): %s' % (upstream.get_upstream_data('latest', 'glib', True),))
print('glib2 (latest): %s' % (upstream.get_upstream_data('latest', 'glib2', True),))
print('gtk2 (2.32): %s' % (upstream.get_upstream_data('gnome-2.32', 'gtk2', True),))
print('gtk2 (latest): %s' % (upstream.get_upstream_data('latest', 'gtk2', True),))
print('gtk3 (latest): %s' % (upstream.get_upstream_data('latest', 'gtk3', True),))
print('gobby04 (latest): %s' % (upstream.get_upstream_data('latest', 'gobby04', True),))
print('gobby (latest): %s' % (upstream.get_upstream_data('latest', 'gobby', True),))
print('OpenOffice_org (latest, fallback): %s' % (upstream.get_upstream_data('latest', 'OpenOffice_org', False),))
if __name__ == '__main__':
try:
main(sys.argv)
except KeyboardInterrupt:
pass
0707010000001B000081A40000000000000000000000016548EB8C00000E8E000000000000000000000000000000000000003100000000osc-plugin-collab-0.104+30/server/obs-db/util.py# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2008-2009, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import errno
def safe_mkdir(dir):
if not dir:
return
try:
os.mkdir(dir)
except OSError as e:
if e.errno != errno.EEXIST:
raise e
def safe_mkdir_p(dir):
if not dir:
return
try:
os.makedirs(dir)
except OSError as e:
if e.errno != errno.EEXIST:
raise e
def safe_unlink(filename):
""" Unlink a file, but ignores the exception if the file doesn't exist. """
try:
os.unlink(filename)
except OSError as e:
if e.errno != errno.ENOENT:
raise e
########################################################
# comes from convert-to-tarball.py
def _strict_bigger_version(a, b):
a_nums = a.split('.')
b_nums = b.split('.')
num_fields = min(len(a_nums), len(b_nums))
for i in range(0,num_fields):
if int(a_nums[i]) > int(b_nums[i]):
return a
elif int(a_nums[i]) < int(b_nums[i]):
return b
if len(a_nums) > len(b_nums):
return a
elif len(a_nums) < len(b_nums):
return b
else:
return None
def bigger_version(a, b):
# We compare versions this way (with examples):
# + 0.3 and 0.3.1:
# 0.3.1 wins: 0.3 == 0.3 and 0.3.1 has another digit
# + 0.3-1 and 0.3-2:
# 0.3-2 wins: 0.3 == 0.3 and 1 < 2
# + 0.3.1-1 and 0.3-2:
# 0.3.1-1 wins: 0.3.1 > 0.3
a_nums = a.split('-')
b_nums = b.split('-')
num_fields = min(len(a_nums), len(b_nums))
for i in range(0,num_fields):
bigger = _strict_bigger_version(a_nums[i], b_nums[i])
if bigger == a_nums[i]:
return a
elif bigger == b_nums[i]:
return b
if len(a_nums) > len(b_nums):
return a
else:
return b
def version_gt(a, b):
if a == b:
return False
bigger = bigger_version(a, b)
return a == bigger
def version_ge(a, b):
if a == b:
return True
bigger = bigger_version(a, b)
return a == bigger
0707010000001C000041ED0000000000000000000000026548EB8C00000000000000000000000000000000000000000000003100000000osc-plugin-collab-0.104+30/server/openSUSE-setup0707010000001D000081A40000000000000000000000016548EB8C00000CE9000000000000000000000000000000000000003800000000osc-plugin-collab-0.104+30/server/openSUSE-setup/READMESetup of osc-collab server for the openSUSE project
===================================================
The server runs on the osc-collab virtual machine.
When updating the code or data in git, you will need to update the files on
the virtual machine too. We could automatically update the code from git, but
it wouldn't be very safe to do unattended updates without looking at the
changes in git.
Just ask one of the osc-collab admins to do the following on the osc-collab
virtual machine:
# check changes in git are fine, if yes then update the code
su - opensuse-scripts
cd ~/src/osc-plugin-collab; git pull
High-level overview
===================
The runme scripts are run by wrapper scripts that cron will execute every 20
minutes. The order in which these wrapper scripts are run matters, as the data
generated by one script can be used by another. The recommended order is:
1. getting upstream data
2. update the database
3. modify OBS attributes
See the crontab file for a potential setup.
The wrapper scripts live in the cron-scripts/ subdirectory. There is one wrapper
script for each task, and a common file for shared functions/settings.
The wrapper scripts will make use of a obs.conf configuration file. See the
collab-data/ subdirectory for an example.
Log files from the log wrappers will be located in the cron-scripts/logs/
subdirectory.
Configuration that has to be done
=================================
Obviously, osc must be installed and configured for the specific user that will
be used by the scripts. It is also recommended to have the python-lxml module
installed, for performance reasons. Other required packages include: bc, curl,
dotlockfile or withlock.
Then, several files need to be updated:
crontab:
The MAILTO variable contains the mail address that will receive errors.
collab-data/obs.conf:
The cache-dir variable should be updated to reflect where the cache directory
will be.
cron-scripts/common:
Several variables control the configuration of the wrapper scripts:
WATCHDOG_CMD: command for a script that will kill processes that are stuck.
This can be ignored if needed.
COLLAB_DATA_DIR: directory that contains obs.conf and the cache
subdirectory. Must be set.
OBS_CONF: can be used to override the location of obs.conf. Must be set, but
suggested value should be correct.
OSC_PLUGIN_COLLAB_DIR: directory that contains the osc-plugin-collab code.
Must be set.
OBS_UPLOAD_URL: URL to obs-upload.py from the web API. If left empty, the
obs.db database won't get uploaded. This is generally fine
for a setup where the web server lives on the same machine
as the cron scripts.
Note for osc-collab admins
==========================
The major task for osc-collab admins is to make sure the server is correctly
configured, and the osc-collab server code runs fine.
The infrastructure team will handle maintenance of the virtual machine. Non
interactive updates are usually automatically installed, and kernel updates are
manually dealt with.
A new osc-collab admin should contact the openSUSE infrastructure team so that
the team knows about the new osc-collab admin.
0707010000001E000041ED0000000000000000000000026548EB8C00000000000000000000000000000000000000000000003D00000000osc-plugin-collab-0.104+30/server/openSUSE-setup/collab-data0707010000001F000081A40000000000000000000000016548EB8C0000015A000000000000000000000000000000000000004D00000000osc-plugin-collab-0.104+30/server/openSUSE-setup/collab-data/osc-collab.conf[General]
cache-dir = /var/lib/osc-collab/.cache
ignore-conf-mtime = True
no-full-check = True
allow-project-catchup = False
[Debug]
debug = False
#mirror-only-new = True
#force-hermes = True
#force-upstream = True
#force-db = True
#force-xml = True
skip-hermes = True
#skip-mirror = True
#skip-upstream = True
#skip-db = True
#skip-xml = True
07070100000020000041ED0000000000000000000000026548EB8C00000000000000000000000000000000000000000000003E00000000osc-plugin-collab-0.104+30/server/openSUSE-setup/cron-scripts07070100000021000081A40000000000000000000000016548EB8C000008A6000000000000000000000000000000000000004500000000osc-plugin-collab-0.104+30/server/openSUSE-setup/cron-scripts/commonsetup() {
MAXTIME=$1
if test "x$MAXTIME" = "x"; then
echo "Maximum execution time for $0 is not set."
exit 1
fi
# the PATH is set to the strict minimum in cronjobs
export PATH=${HOME}/bin:$PATH
NAME=`basename $0`
TOP_DIR=`dirname $0`
LOGFILE="${HOME}/.local/osc-collab/logs/$NAME.log"
LOCKFILE="${HOME}/.local/osc-collab/$NAME.lock"
### Values to setup
## A helper that will kill the process it launches after $MAXTIME, to
## avoid stuck processes.
#WATCHDOG_CMD="${TOP_DIR}/excubitor -d $MAXTIME --"
## Common directories / files
COLLAB_DATA_DIR="${HOME}/.cache"
COLLAB_INSTALL_DIR="/usr/share/osc-collab-server"
OBS_CONF="/etc/osc-collab.conf"
OSC_PLUGIN_COLLAB_DIR="${COLLAB_INSTALL_DIR}"
## OBS_UPLOAD_URL should stay empty if there is no need to upload the
## resulting obs.db anywhere
#OBS_UPLOAD_URL=
if test -z "$COLLAB_DATA_DIR"; then
echo "COLLAB_DATA_DIR is not set."
exit 1
fi
if test -z "$OBS_CONF"; then
echo "OBS_CONF is not set."
exit 1
fi
if test -z "$OBS_CONF"; then
echo "OSC_PLUGIN_COLLAB_DIR is not set."
exit 1
fi
LOCK_CMD=
HAVE_DOTLOCKFILE=0
HAVE_WITHLOCK=0
which dotlockfile &> /dev/null && HAVE_DOTLOCKFILE=1
which withlock &> /dev/null && HAVE_WITHLOCK=1
if test "$HAVE_DOTLOCKFILE" -eq 1; then
# we need a lock to avoid concurrent instances
# Note that with -p, we won't lock if the process that created the lock
# file does not exist anymore
dotlockfile -p -l -r 0 $LOCKFILE
if test $? -ne 0; then
exit
fi
elif test "$HAVE_WITHLOCK" -eq 1; then
LOCK_CMD="withlock $LOCKFILE"
else
echo "No lock program available; dotlockfile or withlock must be installed."
exit 1
fi
if test -f $LOGFILE; then
SIZE=`du -s $LOGFILE | cut -f 1`
if test $SIZE -gt 200000; then
today=`date +%Y%m%d`
mv $LOGFILE $LOGFILE.$today
gzip $LOGFILE.$today
fi
else
mkdir -p `dirname $LOGFILE`
fi
PRE_CMD="${LOCK_CMD} ${WATCHDOG_CMD}"
echo "=== Start (`date`) ===" >> $LOGFILE
}
cleanup() {
echo "=== End (`date`) ===" >> $LOGFILE
if test "$HAVE_DOTLOCKFILE" -eq 1; then
if test "x$LOCKFILE" = "x"; then
echo "Internal error: LOCKFILE is not set."
exit 1
fi
dotlockfile -u $LOCKFILE
fi
}
07070100000022000081ED0000000000000000000000016548EB8C00000169000000000000000000000000000000000000004D00000000osc-plugin-collab-0.104+30/server/openSUSE-setup/cron-scripts/run-attributes#!/bin/sh
TOP_DIR=`dirname $0`
if test ! -f "${TOP_DIR}/common"; then
echo "No common infrastructure available."
exit 1
fi
. "${TOP_DIR}/common"
# 30 minutes max
setup 1800
${PRE_CMD} "${OSC_PLUGIN_COLLAB_DIR}/server/obs-db/runme-attributes" -o "${OBS_CONF}" -s -l $LOGFILE
if test $? -ne 0; then
echo "Error during the attributes update."
fi
cleanup
07070100000023000081ED0000000000000000000000016548EB8C0000012F000000000000000000000000000000000000005100000000osc-plugin-collab-0.104+30/server/openSUSE-setup/cron-scripts/run-gnome-versions#!/bin/sh
TOP_DIR=`dirname $0`
if test ! -f "${TOP_DIR}/common"; then
echo "No common infrastructure available."
exit 1
fi
. "${TOP_DIR}/common"
# 20 minutes max
setup 1200
${PRE_CMD} "${OSC_PLUGIN_COLLAB_DIR}/server/upstream/gnome-versions/update-versions ${COLLAB_DATA_DIR}/upstream/"
cleanup
07070100000024000081ED0000000000000000000000016548EB8C000002AD000000000000000000000000000000000000004600000000osc-plugin-collab-0.104+30/server/openSUSE-setup/cron-scripts/run-obs#!/bin/sh
TOP_DIR=`dirname $0`
if test ! -f "${TOP_DIR}/common"; then
echo "No common infrastructure available."
exit 1
fi
. "${TOP_DIR}/common"
# 10 minutes max -- for an update with hermes, that should be more than enough
setup 600
${PRE_CMD} "${OSC_PLUGIN_COLLAB_DIR}/server/obs-db/runme" -o "${OBS_CONF}" -s -l $LOGFILE
if test $? -eq 0; then
if test -n "${OBS_UPLOAD_URL}"; then
curl --silent --show-error -F destfile=obs.db -F dbfile="@${COLLAB_DATA_DIR}/cache/db/obs.db" ${OBS_UPLOAD_URL}
fi
else
if test -n "${OBS_UPLOAD_URL}"; then
echo "Error during the database update, database not uploaded."
else
echo "Error during the database update."
fi
fi
cleanup
07070100000025000081ED0000000000000000000000016548EB8C00000115000000000000000000000000000000000000004B00000000osc-plugin-collab-0.104+30/server/openSUSE-setup/cron-scripts/run-upstream#!/bin/sh
TOP_DIR=`dirname $0`
if test ! -f "${TOP_DIR}/common"; then
echo "No common infrastructure available."
exit 1
fi
. "${TOP_DIR}/common"
# 10 minutes max
setup 600
${PRE_CMD} "${OSC_PLUGIN_COLLAB_DIR}/server/upstream/runme" -o "${OBS_CONF}" -l $LOGFILE
cleanup
07070100000026000081A40000000000000000000000016548EB8C000002B8000000000000000000000000000000000000003900000000osc-plugin-collab-0.104+30/server/openSUSE-setup/crontabMAILTO="[email protected]"
## Note: we don't use */20 to control our timing, especially as the order in
## which scripts are run matters: run-upstream should be run before run-obs,
## which should be run before run-attributes.
# Update upstream data every twenty minutes
1,21,41 * * * * ${HOME}/src/cron-scripts/run-upstream
# Update obs db every twenty minutes
6,26,46 * * * * ${HOME}/src/cron-scripts/run-obs
# Update obs attributes every twenty minutes
9,29,49 * * * * ${HOME}/src/cron-scripts/run-attributes
## This can be used to automatically update from git
##@daily ${HOME}/src/cron-scripts/run-updategit
07070100000027000081ED0000000000000000000000016548EB8C000001E2000000000000000000000000000000000000004300000000osc-plugin-collab-0.104+30/server/openSUSE-setup/osc-collab-runner#!/bin/sh
LIBEXEC=/usr/lib/osc-collab-server
# self-heal if runaway task detected: none can run longer than a day
find /var/lib/osc-collab/.cache -name running -mtime +1 -delete -print
# Find out what the latest gnome-versions are
${LIBEXEC}/run-gnome-versions
# Merge gnome-versions and find other upstream versions; update upstream.db
${LIBEXEC}/run-upstream
# Update obs.db: sync with changes in OBS
${LIBEXEC}/run-obs
# Update attributes in OBS
${LIBEXEC}/run-attributes
07070100000028000041ED0000000000000000000000026548EB8C00000000000000000000000000000000000000000000002B00000000osc-plugin-collab-0.104+30/server/upstream07070100000029000081ED0000000000000000000000016548EB8C00001515000000000000000000000000000000000000004200000000osc-plugin-collab-0.104+30/server/upstream/download-cpan-versions#!/usr/bin/env python3
# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2012, Novell, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
# USA
#
# (Licensed under the LGPLv2.1 or later)
#
#
# Authors: Vincent Untz <[email protected]>
#
import os
import socket
import sys
import time
import io
import gzip
import optparse
import urllib.request, urllib.error, urllib.parse
from util import *
PACKAGE_DETAILS = 'http://www.cpan.org/modules/02packages.details.txt.gz'
#######################################################################
def parse_cpan_details():
tarballs = {}
last_updated = ''
stream = urllib.request.urlopen(PACKAGE_DETAILS)
gzipper = gzip.GzipFile(fileobj=stream)
in_classes_data = False
while True:
line = gzipper.readline()
if not line:
break
line = line.strip()
if not line:
# An empty line is what separate the global metadata from the details
# about all classes
if not in_classes_data:
in_classes_data = True
continue
# Skip comments
if line.startswith(b'#'):
continue
# Global metadata about the details
if not in_classes_data:
if line.startswith(b'Last-Updated:'):
last_updated = line[len('Last-Updated:'):].strip()
continue
## Parse data about classes
# We only keep the first class for a given tarball (it's the more generic one)
# We ignore data when there's no version
data = line.split()
if len(data) != 3:
print('Cannot parse line: %s' % line, file=sys.stderr)
continue
(perl_class, version, tarball) = data
if version == 'undef':
continue
if tarball in tarballs:
continue
tarballs[tarball] = (perl_class, version)
gzipper.close()
return (last_updated, tarballs)
def perl_class_to_package(perl_class):
return b'perl-' + perl_class.replace(b'::', b'-')
#######################################################################
def main(args):
parser = optparse.OptionParser()
parser.add_option('--debug', dest='debug',
help='only handle the argument as input and output the result')
parser.add_option('--log', dest='log',
help='log file to use (default: stderr)')
parser.add_option('--directory', dest='dir', default='.',
help='directory where to find data and save data')
parser.add_option('--save-file', dest='save',
help='path to the file where the results will be written')
parser.add_option('--only-if-old', action='store_true',
default=False, dest='only_if_old',
help='execute only if the pre-existing result file is older than 10 hours')
(options, args) = parser.parse_args()
directory = options.dir
if options.log:
path = os.path.realpath(options.log)
safe_mkdir_p(os.path.dirname(path))
sys.stderr = open(options.log, 'a')
if options.debug:
lines = [ options.debug + '\n' ]
out = sys.stdout
else:
if options.save:
save_file = options.save
else:
save_file = os.path.join(directory, 'versions-cpan')
if os.path.exists(save_file):
if not os.path.isfile(save_file):
print('Save file %s is not a regular file.' % save_file, file=sys.stderr)
return 1
if options.only_if_old:
stats = os.stat(save_file)
# Quit if it's less than 12-hours old
if time.time() - stats.st_mtime < 3600 * 12:
return 2
else:
safe_mkdir_p(os.path.dirname(save_file))
out = open(save_file, 'w')
# The default timeout is just too long. Use 10 seconds instead.
socket.setdefaulttimeout(10)
ret = 1
try:
(last_updated, tarballs) = parse_cpan_details()
except urllib.error.URLError as e:
print('Error when downloading CPAN metadata: %s' % e, file=sys.stderr)
except urllib.error.HTTPError as e:
print('Error when downloading CPAN metadata: server sent %s' % e, file=sys.stderr)
else:
for (tarball, (perl_class, version)) in tarballs.items():
out.write('cpan:%s:%s:%s\n' % (perl_class_to_package(perl_class), version, tarball))
ret = 0
if not options.debug:
out.close()
return ret
if __name__ == '__main__':
try:
ret = main(sys.argv)
sys.exit(ret)
except KeyboardInterrupt:
pass
0707010000002A000081ED0000000000000000000000016548EB8C000011A7000000000000000000000000000000000000004600000000osc-plugin-collab-0.104+30/server/upstream/download-fallback-versions#!/usr/bin/env python3
# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2012, Novell, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
# USA
#
# (Licensed under the LGPLv2.1 or later)
#
#
# Authors: Vincent Untz <[email protected]>
#
import os
import socket
import sys
import time
import optparse
import urllib.request, urllib.error, urllib.parse
from util import *
FALLBACK_URL = 'http://users.suse.com/~meissner/factory.upstream.lst'
#######################################################################
def parse_fallback_data():
metadatas = []
fd = urllib.request.urlopen(FALLBACK_URL)
while True:
line = fd.readline()
if not line:
break
line = line.strip()
if not line or line.startswith(b'#'):
continue
data = line.split(b',')
if len(data) < 2:
print('Cannot parse fallback line: %s' % line, file=sys.stderr)
continue
name = data[0]
version = data[1]
if not name or not version:
print('Fallback line with nothing useful: %s' % line, file=sys.stderr)
continue
metadatas.append((name, version))
fd.close()
metadatas.sort()
return metadatas
#######################################################################
def main(args):
parser = optparse.OptionParser()
parser.add_option('--debug', dest='debug',
help='only handle the argument as input and output the result')
parser.add_option('--log', dest='log',
help='log file to use (default: stderr)')
parser.add_option('--directory', dest='dir', default='.',
help='directory where to find data and save data')
parser.add_option('--save-file', dest='save',
help='path to the file where the results will be written')
parser.add_option('--only-if-old', action='store_true',
default=False, dest='only_if_old',
help='execute only if the pre-existing result file is older than 10 hours')
(options, args) = parser.parse_args()
directory = options.dir
if options.log:
path = os.path.realpath(options.log)
safe_mkdir_p(os.path.dirname(path))
sys.stderr = open(options.log, 'a')
if options.debug:
lines = [ options.debug + '\n' ]
out = sys.stdout
else:
if options.save:
save_file = options.save
else:
save_file = os.path.join(directory, 'versions-fallback')
if os.path.exists(save_file):
if not os.path.isfile(save_file):
print('Save file %s is not a regular file.' % save_file, file=sys.stderr)
return 1
if options.only_if_old:
stats = os.stat(save_file)
# Quit if it's less than 2-hours old
if time.time() - stats.st_mtime < 3600 * 2:
return 2
else:
safe_mkdir_p(os.path.dirname(save_file))
out = open(save_file, 'w')
# The default timeout is just too long. Use 10 seconds instead.
socket.setdefaulttimeout(10)
ret = 1
try:
metadatas = parse_fallback_data()
except urllib.error.URLError as e:
print('Error when downloading fallback metadata: %s' % e, file=sys.stderr)
except urllib.error.HTTPError as e:
print('Error when downloading fallback metadata: server sent %s' % e, file=sys.stderr)
else:
for (name, version) in metadatas:
out.write('fallback:%s:%s:\n' % (name, version))
ret = 0
if not options.debug:
out.close()
return ret
if __name__ == '__main__':
try:
ret = main(sys.argv)
sys.exit(ret)
except KeyboardInterrupt:
pass
0707010000002B000081ED0000000000000000000000016548EB8C00002DAE000000000000000000000000000000000000004200000000osc-plugin-collab-0.104+30/server/upstream/download-pypi-versions#!/usr/bin/env python3
#
# Copyright (c) 2014, SUSE LINUX Products GmbH
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
# USA
#
# (Licensed under the LGPLv2.1 or later)
#
#
# Authors: Thomas Bechtold <[email protected]>
#
import argparse
import os
import sys
import time
from multiprocessing import Pool
try:
import xmlrpc.client
except ImportError:
# Python3 support
import xmlrpc.client as xmlrpclib
try:
from functools import total_ordering
except ImportError:
def total_ordering(cls):
"""Class decorator that fills in missing ordering methods"""
convert = {
'__lt__': [('__gt__', lambda self, other: not (self < other or self == other)),
('__le__', lambda self, other: self < other or self == other),
('__ge__', lambda self, other: not self < other)],
'__le__': [('__ge__', lambda self, other: not self <= other or self == other),
('__lt__', lambda self, other: self <= other and not self == other),
('__gt__', lambda self, other: not self <= other)],
'__gt__': [('__lt__', lambda self, other: not (self > other or self == other)),
('__ge__', lambda self, other: self > other or self == other),
('__le__', lambda self, other: not self > other)],
'__ge__': [('__le__', lambda self, other: (not self >= other) or self == other),
('__gt__', lambda self, other: self >= other and not self == other),
('__lt__', lambda self, other: not self >= other)]
}
roots = set(dir(cls)) & set(convert)
if not roots:
raise ValueError('must define at least one ordering operation: < > <= >=')
root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__
for opname, opfunc in convert[root]:
if opname not in roots:
opfunc.__name__ = opname
opfunc.__doc__ = getattr(int, opname).__doc__
setattr(cls, opname, opfunc)
return cls
XMLRPC_SERVER_PROXY = 'https://pypi.python.org/pypi'
@total_ordering
class PackageInfo(object):
"""Represents a package info"""
def __init__(self, type_, name, version, url):
self.type = type_.strip()
self.name = name.strip() # the original name from pypi (without python3- or python- prefix)
self.version = version.strip()
self.url = url.strip()
def __repr__(self):
return "PackageInfo obj <%s:%s:%s:%s>" % (self.type, self.name, self.version, self.url)
def __eq__(self, other):
if hasattr(other, 'name'):
return self.name == other.name
def __lt__(self, other):
if hasattr(other, 'name'):
return self.name < other.name
def __get_save_file_serial_name(save_file):
"""filename to use to store the latest used changelog serial from pypi"""
return save_file + "-changelog-serial"
def __get_cached_changelog_last_serial(save_file):
"""try to read the latest changelog serial and return
the number or None if not available"""
serial_file = __get_save_file_serial_name(save_file)
if os.path.exists(serial_file):
with open(serial_file, 'r') as f:
return int(f.readline())
# no changelog serial available
return None
def __write_cached_changelog_last_serial(save_file, changelog_serial=None):
"""update the cached changelog serial with the current serial from pypi"""
if not changelog_serial:
client = xmlrpc.client.ServerProxy(XMLRPC_SERVER_PROXY)
changelog_serial = str(client.changelog_last_serial())
serial_file = __get_save_file_serial_name(save_file)
with open(serial_file, 'w') as sf:
sf.write("%s\n" % changelog_serial)
def __read_pypi_package_file(save_file):
"""read the given pypi file into a list of PackageInfo objs"""
packages = list()
with open(save_file, "r") as f:
for line in f.readlines():
if line.startswith('#'):
continue
pack_info = PackageInfo(*line.split(':', 3))
# skip python3-* lines
if pack_info.name.startswith('python3-'):
continue
# remove python- prefix
if pack_info.name.startswith('python-'):
pack_info.name = pack_info.name.replace('python-', '', 1)
# now pack_info.name should have the original name from pypi
packages.append(pack_info)
return packages
def __write_pypi_package_file(save_file, packages):
"""write the pypi file. packages is a list of PackageInfo objs"""
with open(save_file, "w") as o:
for pi in sorted(packages):
if pi:
# FIXME(toabctl): check somehow if py2 and py3 versions are
# available currently just write python- and
# python3- names so both versions can be checked
o.write("%s:python-%s:%s:%s\n" %
(pi.type, pi.name, pi.version, pi.url))
o.write("%s:python3-%s:%s:%s\n" %
(pi.type, pi.name, pi.version, pi.url))
def __create_pypi_package_file(save_file):
"""create a new file with version information for pypi packages.
This step is expensive because it fetches the package list
from pypi (> 50000 packages) and then does a request for
every package to get the version information."""
# get current changelog serial and store in file before doing anything
# else so we can't lose some changes while we create the file
__write_cached_changelog_last_serial(save_file)
try:
pack_list = __get_package_list()
sys.stderr.write(
"Found %s packages. Getting packages details...\n" %
(len(pack_list)))
p = Pool(50)
packages = p.map(__get_package_info, pack_list)
__write_pypi_package_file(save_file, packages)
except Exception as e:
sys.stderr.write("Error while creating the initial pypi file: %s\n" % e)
# something wrong - delete the serial file so in the next run the
# the file will be recreated
serial_file = __get_save_file_serial_name(save_file)
os.remove(serial_file)
else:
sys.stderr.write("Initial pypi file '%s' created\n" % save_file)
def __find_package_name_index(packages, name):
"""find the given name in the packages list or None if not found"""
for i, pack in enumerate(packages):
if pack.name == name:
return i
return None
def __update_pypi_package_file(save_file, current_changelog):
"""update information of an exisiting file"""
try:
if os.path.exists(save_file):
packages = __read_pypi_package_file(save_file)
client = xmlrpc.client.ServerProxy(XMLRPC_SERVER_PROXY)
changelog_serial = str(client.changelog_last_serial())
changes = client.changelog_since_serial(current_changelog)
handled_changes = 0
sys.stderr.write("Started processing %s change requests...\n" % len(changes))
for (name, version, timestamp, action, serial) in changes:
# TODO(toabctl): do it in parallel to improve speed
if action == 'remove':
handled_changes += 1
index = __find_package_name_index(packages, name)
if index:
del packages[index]
elif action == 'new release':
handled_changes += 1
updated_pack_info = __get_package_info(name)
if updated_pack_info:
index = __find_package_name_index(packages, name)
if index:
packages[index] = updated_pack_info
else:
packages.append(updated_pack_info)
else:
pass
# write the new file with the updated package list
__write_pypi_package_file(save_file, packages)
__write_cached_changelog_last_serial(save_file, changelog_serial=changelog_serial)
else:
raise Exception("Can not update '%s'. File does not exist" % save_file)
except Exception as e:
sys.stderr.write("pypi file update for '%s' failed: %s.\n"
% (save_file, e))
else:
sys.stderr.write("pypi file update for '%s' successful. Handled %s changes\n" % (save_file, handled_changes))
def __get_package_list():
"""get a list with packages available on pypi"""
client = xmlrpc.client.ServerProxy(XMLRPC_SERVER_PROXY)
packages_list = client.list_packages()
return packages_list
def __get_package_info(package):
"""get highest sdist package version for the given package name"""
try:
client = xmlrpc.client.ServerProxy(XMLRPC_SERVER_PROXY)
releases = client.package_releases(package)
if len(releases) > 0:
for data in client.release_urls(package, releases[0]):
if data['packagetype'] == 'sdist':
return PackageInfo('pypi', package, releases[0], data['url'])
except Exception as e:
sys.stderr.write("can not get information for package '%s': %s\n" % (package, e))
# no sdist package version found.
return None
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description='Get package version from pypi')
parser.add_argument(
'--save-file', default='versions-pypi',
help='path to the file where the results will be written')
parser.add_argument(
'--log', default=sys.stderr,
help='log file to use (default: stderr)')
parser.add_argument(
'--only-if-old', action='store_true', default=False,
help='execute only if the pre-existing result file is older than 12 hours')
args = vars(parser.parse_args())
# check file age
if os.path.exists(args['save_file']):
if not os.path.isfile(args['save_file']):
sys.stderr.write('Save file %s is not a regular file.\n' % args['save_file'])
sys.exit(1)
if args['only_if_old']:
stats = os.stat(args['save_file'])
# Quit if it's less than 12-hours old
if time.time() - stats.st_mtime < 3600 * 12:
sys.exit(2)
try:
if args['log'] != sys.stderr:
sys_stderr = sys.stderr
sys.stderr = open(args['log'], 'a')
current_changelog = __get_cached_changelog_last_serial(args['save_file'])
if current_changelog:
__update_pypi_package_file(args['save_file'], current_changelog)
else:
__create_pypi_package_file(args['save_file'])
finally:
if args['log'] != sys.stderr:
sys.stderr.close()
sys.stderr = sys_stderr
sys.exit(0)
0707010000002C000081ED0000000000000000000000016548EB8C000092C1000000000000000000000000000000000000004600000000osc-plugin-collab-0.104+30/server/upstream/download-upstream-versions#!/usr/bin/env python3
# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2008-2009, Novell, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
# USA
#
# (Licensed under the LGPLv2.1 or later)
#
# Parts of this code comes from convert-to-tarball.py (in the releng GNOME
# svn module), which has the same license.
#
#
# Authors: Vincent Untz <[email protected]>
#
import os
import socket
import sys
import time
import ftplib
import hashlib
import optparse
from posixpath import join as posixjoin # Handy for URLs
import re
from sgmllib import SGMLParser
import shutil
import tempfile
import urllib.request, urllib.parse, urllib.error
import urllib.request, urllib.error, urllib.parse
import urllib.parse
import queue
import threading
try:
from lxml import etree as ET
except ImportError:
try:
from xml.etree import cElementTree as ET
except ImportError:
import cElementTree as ET
import feedparser
from util import *
USE_DEBUG = False
MAX_THREADS = 10
URL_HASH_ALGO = 'md5'
#######################################################################
def _line_is_comment(line):
return line.strip() == '' or line[0] == '#'
#######################################################################
# Fix some locations to point to what are really downloads.
def _location_fix(location):
return location
#######################################################################
class UpstreamRawDownloadError(Exception):
def __init__(self, value):
self.msg = value
def __str__(self):
return repr(self.msg)
class UpstreamDownloadError(Exception):
def __init__(self, value):
self.msg = value
def __str__(self):
return repr(self.msg)
#######################################################################
# comes from convert-to-tarball.py
class urllister(SGMLParser):
def reset(self):
SGMLParser.reset(self)
self.urls = []
def start_a(self, attrs):
href = [v for k, v in attrs if k=='href']
if href:
self.urls.extend(href)
#######################################################################
class svnurllister(SGMLParser):
def reset(self):
SGMLParser.reset(self)
self.urls = []
def start_file(self, attrs):
href = [v for k, v in attrs if k=='href']
if href:
self.urls.extend(href)
#######################################################################
def _get_cache_paths_from_url(url, cache_dir):
if cache_dir:
hash = hashlib.new(URL_HASH_ALGO)
hash.update(url)
digest = hash.hexdigest()
cache = os.path.join(cache_dir, digest)
cache_error = cache + '.error'
return (cache, cache_error)
else:
return (None, None)
#######################################################################
# based on code from convert-to-tarball.py
def _get_files_from_http(url, cache_dir):
(cache, cache_error) = _get_cache_paths_from_url (url, cache_dir)
if cache_error and os.path.exists(cache_error):
raise UpstreamRawDownloadError(open(cache_error).read())
elif cache and os.path.exists(cache):
fin = open(cache)
else:
obj = urllib.request.build_opener()
fin = obj.open(url)
# Get the files
parser = urllister()
parser.feed(fin.read())
fin.close()
parser.close()
files = parser.urls
return (url, files)
#######################################################################
# based on code from convert-to-tarball.py
def _get_files_from_ftp(url):
parsed_url = urllib.parse.urlparse(url)
ftp = ftplib.FTP(parsed_url.hostname)
ftp.login(parsed_url.username or 'anonymous', parsed_url.password or '')
ftp.cwd(parsed_url.path)
files = ftp.nlst()
ftp.quit()
return (url, files)
#######################################################################
# based on code from convert-to-tarball.py
def _get_files_from_subdir_http(url, limit):
obj = urllib.request.build_opener()
# Note that we accept directories called 1.x.X
good_dir = re.compile('^(([0-9]+|[xX])\.)*([0-9]+|[xX])/?$')
def hasdirs(x): return good_dir.search(x)
def fixdirs(x): return re.sub(r'^((?:(?:[0-9]+|[xX])\.)*)([0-9]+|[xX])/?$', r'\1\2', x)
location = url
# Follow 302 codes when retrieving URLs, speeds up conversion by 60sec
redirect_location = location
while True:
# Get the files
usock = obj.open(redirect_location)
parser = urllister()
parser.feed(usock.read())
usock.close()
parser.close()
files = parser.urls
# Check to see if we need to descend to a subdirectory
newdirs = list(filter(hasdirs, files))
newdirs = list(map(fixdirs, newdirs))
if newdirs:
newdir = _get_latest_version(newdirs, limit)
# This is a weird case, that we handled for compiz-fusion:
# if the dir contains a subdir with the same name, then we stop
# FIXME: make this an option in the metadata?
if newdir == os.path.basename(redirect_location):
break
if not newdir:
break
# Add trailing slash since we work on directories, and some servers
# don't work if we don't add the trailing slash (like lighttpd on
# http://download.banshee.fm/banshee-community-extensions/)
if newdir[-1] != '/':
newdir += '/'
redirect_location = posixjoin(usock.url, newdir)
location = posixjoin(location, newdir)
else:
break
return (location, files)
#######################################################################
def _get_files_from_svn(url):
obj = urllib.request.build_opener()
# Get the files
usock = obj.open(url)
parser = svnurllister()
parser.feed(usock.read())
usock.close()
parser.close()
files = parser.urls
return (url, files)
#######################################################################
def _setup_limit(limit):
limit_data = None
if not limit:
pass
elif limit == 'no-odd-unstable':
pass
elif limit[:4] == 'max|':
limit_data = limit[4:]
limit = 'max'
elif limit[:5] == 'skip|':
limit_data = limit[5:]
limit = 'skip'
else:
print('Unsupported limit: %s' % limit, file=sys.stderr)
limit = None
return (limit, limit_data)
def _respect_limit(version, limit, limit_data):
if not limit:
return True
elif limit == 'no-odd-unstable':
# remove the part after dashes. Eg, in 3.3-1, we're only interested in
# the 3.3 part.
version = version.split('-')[0]
split_version = version.split('.')
if len(split_version) <= 1:
# not enough version data, so let's just say yes
return True
try:
return int(split_version[1]) % 2 == 0
except:
# second element is not an int. Let's just say yes
return True
elif limit == 'max':
return version_gt(limit_data, version)
elif limit == 'skip':
skip_versions = [ x for x in limit_data.split(';') if x ]
return not (version in skip_versions)
else:
return False
def _get_latest_version(versions, limit):
(limit, limit_data) = _setup_limit(limit)
biggest = None
for version in versions:
if _respect_limit(version, limit, limit_data):
biggest = version
break
if not biggest:
return None
for version in versions[versions.index(biggest) + 1:]:
if _respect_limit(version, limit, limit_data) and version_gt(version, biggest):
biggest = version
return biggest
#######################################################################
def _all_indexes(list, item, shift = 0):
try:
i = list.index(item)
real_index = i + shift
except ValueError:
return []
subresult = _all_indexes(list[i+1:], item, real_index + 1)
subresult.append(real_index)
return subresult
# based on code from convert-to-tarball.py
def _get_version_from_files(modulename, location, files, limit):
def is_of_interest(modulename, file):
''' The file is of interest if it contains the module name, and if
either the basename of the URI matches a tarball for this module,
or there's a query (like /download.cgi?filename=module-x.y.tar.gz)
'''
if not modulename in file:
return False
parsed = urllib.parse.urlparse(file)
if os.path.basename(parsed.path).startswith(modulename):
return True
if parsed.query:
return True
return False
# Only include tarballs for the given module
tarballs = [file for file in files if is_of_interest(modulename, file)]
# Remove fragment identifiers (anchors)
tarballs_new = []
for tarball in tarballs:
index = tarball.rfind('#')
if index != -1:
tarball = tarball[:index]
tarballs_new.append(tarball)
tarballs = tarballs_new
re_tarball = r'^.*'+modulename+'[_-]v?(([0-9]+[\.\-])*[0-9]+)\.(?:tar.*|t[bg]z2?)$'
# Don't include -beta -installer -stub-installer and all kinds of
# other weird-named tarballs
tarballs = [t for t in tarballs if re.search(re_tarball, t)]
versions = [re.sub(re_tarball, r'\1', t) for t in tarballs]
if not len(versions):
raise UpstreamDownloadError('No versions found')
version = _get_latest_version(versions, limit)
if not version:
raise UpstreamDownloadError('No version found respecting the limits')
indexes = _all_indexes(versions, version)
# the list is not in the right order, because of the way we build the list
indexes.reverse()
latest = [tarballs[index] for index in indexes]
tarballs = None
if not tarballs:
tarballs = [file for file in latest if file.endswith('.tar.xz')]
if not tarballs:
tarballs = [file for file in latest if file.endswith('.tar.bz2')]
if not tarballs:
tarballs = [file for file in latest if file.endswith('.tar.gz')]
if not tarballs:
tarballs = [file for file in latest if file.endswith('.tbz2')]
if not tarballs:
tarballs = [file for file in latest if file.endswith('.tgz')]
if not tarballs:
raise UpstreamDownloadError('No tarball found for version %s' % version)
# at this point, all the tarballs we have are relevant, so just take the
# first one
tarball = tarballs[0]
if urllib.parse.urlparse(tarball).scheme != '':
# full URI
location = tarball
else:
# remove files from location when we know it's not a directory
if len(location) > 5 and location[-5:] in [ '.html' ]:
last_slash = location.rfind('/')
if last_slash != -1:
location = location[:last_slash + 1]
# add potentially missing slash to the directory
if location[-1:] != '/':
location = location + '/'
location = urllib.parse.urljoin(location, tarball)
return (location, version)
#######################################################################
def _fix_sf_location(url):
if url and url.startswith('http://sourceforge.net/projects/') and url.endswith('/download'):
# We want to move from:
# http://sourceforge.net/projects/gtkpod/files%2Fgtkpod%2Fgtkpod-2.0.2%2Fgtkpod-2.0.2.tar.gz/download
# to:
# http://downloads.sourceforge.net/project/gtkpod/gtkpod/gtkpod-2.0.2/gtkpod-2.0.2.tar.gz
# strip leading 'http://sourceforge.net/projects/' and trailing '/download'
stripped = url[len('http://sourceforge.net/projects/'):-len('/download')]
# find project name
prjname = stripped[:stripped.find('/')]
# find path to file
files_index = stripped.find('/files%2F')
if files_index != -1:
path = stripped[files_index + len('/files%2F'):]
path = path.replace('%2F', '/')
else:
files_index = stripped.find('/files/')
path = stripped[files_index + len('/files/'):]
return 'http://downloads.sourceforge.net/project/%s/%s' % (prjname, path)
return url
def _get_version_from_sf_rss(modulename, id, limit):
(limit, limit_data) = _setup_limit(limit)
ids = id.split('|')
url = 'http://sourceforge.net/api/file/index/project-id/%s/rss' % ids[0]
if len(ids) > 1:
# we do not want urlencode since spaces are %20 and not +
url += '?path=/%s' % urllib.parse.quote(ids[1])
feed = feedparser.parse(url)
re_tarball = re.compile(r'^.*(?:/|%2F)'+re.escape(modulename)+'[_-](([0-9]+[\.\-])*[0-9]+)\.(tar.*|t[bg]z2?)/')
biggest = '0'
location = None
best_ext = None
for entry in feed['entries']:
unquoted_link = urllib.parse.unquote(entry.link)
match = re_tarball.match(unquoted_link)
if not match:
continue
version = match.group(1)
ext = match.group(2)
if not version_gt(version, biggest):
continue
if not _respect_limit(version, limit, limit_data):
continue
if biggest == version:
if best_ext in [ '.tar.xz' ]:
continue
elif ext in [ '.tar.xz' ]:
pass
if best_ext in [ '.tar.bz2', '.tbz2' ]:
continue
elif ext in [ '.tar.bz2', '.tbz2' ]:
pass
elif best_ext in [ '.tar.gz', '.tgz' ]:
continue
elif ext in [ '.tar.gz', '.tgz' ]:
pass
else:
continue
biggest = version
location = entry.link
best_ext = ext
if biggest == '0' and location == None:
biggest = None
location = _fix_sf_location(location)
return (location, biggest)
#######################################################################
def _fix_sf_jp_location(location, project):
sf_jp = re.compile('^http://sourceforge.jp/projects/%s/downloads/([^/]+)/([^/]+)$' % project)
match = sf_jp.match(location)
if match:
return 'http://sourceforge.jp/frs/redir.php?m=jaist&f=%%2F%s%%2F%s%%2F%s' % (project, match.group(1), match.group(2))
return location
def _get_version_from_sf_jp(cache_dir, modulename, project, limit):
url = 'http://sourceforge.jp/projects/%s/releases/' % project
(location, files) = _get_files_from_http(url, cache_dir)
# there's a trailing slash that will break _get_version_from_files(). For instance:
# http://sourceforge.jp/projects/scim-imengine/downloads/29155/scim-canna-1.0.1.tar.gz/
files = [ f[:-1] for f in files ]
(location, version) = _get_version_from_files(modulename, location, files, limit)
location = _fix_sf_jp_location(location, project)
return (location, version)
#######################################################################
def _get_version_from_google_atom(name, limit):
(limit, limit_data) = _setup_limit(limit)
names = name.split('|')
project = names[0]
if len(names) > 1:
tarball = names[1]
else:
tarball = project
# See http://code.google.com/p/support/issues/detail?id=2926
#url = 'http://code.google.com/feeds/p/%s/downloads/basic?%s' % (project, urllib.urlencode({'q': tarball}))
url = 'http://code.google.com/feeds/p/%s/downloads/basic' % (project, )
feed = feedparser.parse(url)
version_re = re.compile('^\s*'+re.escape(tarball)+'[_-]((?:[0-9]+\.)*[0-9]+)\.(tar.*|t[bg]z2?)')
download_re = re.compile('<a href="([^"]*)">Download</a>')
biggest = '0'
location = None
for entry in feed['entries']:
match = version_re.match(entry.title)
if not match:
continue
version = match.group(1)
if not version_gt(version, biggest):
continue
if not _respect_limit(version, limit, limit_data):
continue
match = download_re.search(entry.content[0]['value'])
if match:
download_url = match.group(1)
else:
download_url = 'http://code.google.com/p/%s/downloads/list' % project
biggest = version
location = download_url
if biggest == '0' and location == None:
raise UpstreamDownloadError('No versions found')
return (location, biggest)
#######################################################################
LP_NS = '{https://launchpad.net/rdf/launchpad#}'
RDF_NS = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}'
def _get_version_from_launchpad_series(project, limit, limit_data, series):
url = 'https://launchpad.net/%s/%s/+rdf' % (project, series)
release_re = re.compile('^/%s/%s/((?:[0-9]+\.)*[0-9]+)/\+rdf$' % (re.escape(project), re.escape(series)))
biggest = '0'
fd = urllib.request.urlopen(url)
root = ET.parse(fd).getroot().find(LP_NS + 'ProductSeries')
fd.close()
for node in root.findall(LP_NS + 'release'):
productrelease = node.find(LP_NS + 'ProductRelease')
if productrelease is None:
continue
specified = productrelease.find(LP_NS + 'specifiedAt')
release = specified.get(RDF_NS + 'resource')
match = release_re.match(release)
if not match:
continue
version = match.group(1)
if not _respect_limit(version, limit, limit_data):
continue
if version_gt(version, biggest):
biggest = version
# TODO: this is blocked by https://bugs.launchpad.net/bugs/268359
location = None
return (location, biggest)
def _get_version_from_launchpad(project, limit):
(limit, limit_data) = _setup_limit(limit)
url = 'https://launchpad.net/%s/+rdf' % project
series_re = re.compile('^/%s/((?:[0-9]+\.)*[0-9]+)/\+rdf$' % re.escape(project))
fd = urllib.request.urlopen(url)
root = ET.parse(fd).getroot().find(LP_NS + 'Product')
fd.close()
# always finish with trunk
(location, biggest) = (None, '0')
for node in root.findall(LP_NS + 'series'):
product = node.find(LP_NS + 'ProductSeries')
if product is None:
continue
specified = product.find(LP_NS + 'specifiedAt')
series = specified.get(RDF_NS + 'resource')
match = series_re.match(series)
if not match:
continue
series_version = match.group(1)
if not _respect_limit(series_version, limit, limit_data):
continue
if version_ge(biggest, series_version):
continue
(series_location, series_biggest) = _get_version_from_launchpad_series (project, limit, limit_data, series_version)
if version_gt(series_biggest, biggest):
(location, biggest) = (series_location, series_biggest)
# try trunk, in case it exists
try:
(trunk_location, trunk_biggest) = _get_version_from_launchpad_series (project, limit, limit_data, 'trunk')
if version_gt(trunk_biggest, biggest):
(location, biggest) = (trunk_location, trunk_biggest)
except UpstreamDownloadError:
pass
except urllib.error.HTTPError as e:
if e.code != 404:
raise e
if location is None and biggest == '0':
raise UpstreamDownloadError('No versions found')
return (location, biggest)
#######################################################################
class trac_urllister(SGMLParser):
def __init__(self, modulename):
SGMLParser.__init__(self)
self.modulename = modulename
def reset(self):
SGMLParser.reset(self)
self.in_a = False
self.current_url = None
self.files = []
def start_a(self, attrs):
self.in_a = True
href = [v for k, v in attrs if k=='href']
if href:
self.current_url = href[0]
def handle_data(self, data):
data = data.strip()
if self.in_a and self.modulename in data:
self.files.append([self.current_url, data])
def end_a(self):
self.in_a = False
def _get_version_from_trac(modulename, url, limit):
# this is clearly based on _get_version_from_files, so read comments there
obj = urllib.request.build_opener()
# Get the files
usock = obj.open(url)
parser = trac_urllister(modulename)
parser.feed(usock.read())
usock.close()
parser.close()
files = parser.files
(limit, limit_data) = _setup_limit(limit)
re_tarball = r'^.*'+modulename+'[_-](([0-9]+[\.\-])*[0-9]+)\.(?:tar.*|t[bg]z2?)$'
tarballs = [t for t in files if re.search(re_tarball, t[1])]
versions = [re.sub(re_tarball, r'\1', t[1]) for t in tarballs]
version = _get_latest_version(versions, limit)
indexes = _all_indexes(versions, version)
# the list is not in the right order, because of the way we build the list
indexes.reverse()
latest = [tarballs[index] for index in indexes]
tarballs = None
if not tarballs:
tarballs = [file for file in latest if file[1].endswith('.tar.xz')]
if not tarballs:
tarballs = [file for file in latest if file[1].endswith('.tar.bz2')]
if not tarballs:
tarballs = [file for file in latest if file[1].endswith('.tar.gz')]
if not tarballs:
tarballs = [file for file in latest if file[1].endswith('.tbz2')]
if not tarballs:
tarballs = [file for file in latest if file[1].endswith('.tgz')]
if not tarballs:
raise UpstreamDownloadError('No tarball found for version %s' % version)
# first tarball is fine
tarball = tarballs[0]
semi_url = tarball[0]
if urllib.parse.urlparse(semi_url).scheme != '':
# full URI
location = semi_url
else:
location = urllib.parse.urljoin(url, semi_url)
return (location, version)
#######################################################################
def get_upstream_version(cache_dir, modulename, method, additional_info, limit):
# for branches, get the real modulename
modulename = modulename.split('|')[0]
if method not in [ 'upstream', 'ftpls', 'httpls', 'dualhttpls', 'subdirhttpls', 'svnls', 'sf', 'sf_jp', 'google', 'lp', 'trac' ]:
print('Unsupported method: %s' % method, file=sys.stderr)
return (None, None)
if method == 'upstream':
return (None, '--')
elif method == 'ftpls':
(location, files) = _get_files_from_ftp(additional_info)
return _get_version_from_files(modulename, location, files, limit)
elif method == 'httpls':
(location, files) = _get_files_from_http(additional_info, cache_dir)
return _get_version_from_files(modulename, location, files, limit)
elif method == 'dualhttpls':
(url1, url2) = additional_info.split('|')
(location1, files1) = _get_files_from_http(url1, cache_dir)
(location2, files2) = _get_files_from_http(url2, cache_dir)
try:
(location1, version1) = _get_version_from_files(modulename, location1, files1, limit)
except UpstreamDownloadError:
(location1, version1) = (None, None)
try:
(location2, version2) = _get_version_from_files(modulename, location2, files2, limit)
except UpstreamDownloadError:
(location2, version2) = (None, None)
if version1 and version2 and version_ge(version1, version2):
return (location1, version1)
elif version1 and version2:
return (location2, version2)
elif version1:
return (location1, version1)
elif version2:
return (location2, version2)
else:
raise UpstreamDownloadError('No versions found')
elif method == 'subdirhttpls':
(location, files) = _get_files_from_subdir_http(additional_info, limit)
return _get_version_from_files(modulename, location, files, limit)
elif method == 'svnls':
(location, files) = _get_files_from_svn(additional_info)
return _get_version_from_files(modulename, location, files, limit)
elif method == 'sf':
return _get_version_from_sf_rss(modulename, additional_info, limit)
elif method == 'sf_jp':
return _get_version_from_sf_jp(cache_dir, modulename, additional_info, limit)
elif method == 'google':
return _get_version_from_google_atom(additional_info, limit)
elif method == 'lp':
return _get_version_from_launchpad(additional_info, limit)
elif method == 'trac':
return _get_version_from_trac(modulename, additional_info, limit)
#######################################################################
def parse_limits(limits_file):
retval = {}
if not os.path.exists(limits_file) or not os.path.isfile(limits_file):
return retval
file = open(limits_file)
lines = file.readlines()
file.close()
for line in lines:
if _line_is_comment(line):
continue
data = line[:-1].split(':', 2)
retval[data[0]] = data[1]
return retval
#######################################################################
def parse_data(data_file):
retval = {}
if not os.path.exists(data_file) or not os.path.isfile(data_file):
return retval
file = open(data_file)
lines = file.readlines()
file.close()
for line in lines:
if _line_is_comment(line):
continue
data = line[:-1].split(':', 3)
if data[0] != 'nonfgo':
continue
if data[2] != '':
version = data[2]
else:
version = None
if data[3] != '':
location = data[3]
else:
location = None
retval[data[1]] = (version, location)
return retval
#######################################################################
def do_task(cache_dir, modulename, method, additional_info, fast_update, limits, fallback_data):
(location, version) = (None, None)
if fast_update and modulename in fallback_data and fallback_data[modulename][0]:
# fast update: we don't download data if we have something in cache
pass
else:
if modulename in limits:
limit = limits[modulename]
else:
limit = None
try:
(location, version) = get_upstream_version(cache_dir, modulename, method, additional_info, limit)
except urllib.error.URLError as e:
print('Error when downloading information about %s: %s' % (modulename, e), file=sys.stderr)
except urllib.error.HTTPError as e:
print('Error when downloading information about %s: server sent %s' % (modulename, e.code), file=sys.stderr)
except ftplib.all_errors as e:
print('Error when downloading information about %s: %s' % (modulename, e), file=sys.stderr)
except socket.timeout as e:
print('Error when downloading information about %s: %s' % (modulename, e), file=sys.stderr)
except UpstreamRawDownloadError as e:
print('Error when downloading information about %s: %s' % (modulename, e.msg), file=sys.stderr)
except UpstreamDownloadError as e:
print('No matching tarball found for %s: %s' % (modulename, e.msg), file=sys.stderr)
if modulename in fallback_data:
fallback_version = fallback_data[modulename][0]
fallback_location = fallback_data[modulename][1]
if not version and not location:
version = fallback_version
location = fallback_location
elif not version and location == fallback_location:
version = fallback_version
elif not location and version == fallback_version:
location = fallback_location
if version == '--':
cat = 'upstream'
else:
cat = 'nonfgo'
if location:
location = _location_fix(location)
return (cat, version, location)
#######################################################################
def do_cache(cache_dir, url):
(cache, cache_error) = _get_cache_paths_from_url (url, cache_dir)
fin = None
fout = None
error = None
try:
obj = urllib.request.build_opener()
fin = obj.open(url)
fout = open(cache, 'w')
fout.write(fin.read())
except urllib.error.URLError as e:
error = str(e)
except urllib.error.HTTPError as e:
error = 'server sent %s' % e.code
except socket.timeout as e:
error = str(e)
if fin:
fin.close()
if fout:
fout.close()
if error:
fout = open(cache_error, 'w')
fout.write(error)
fout.close
#######################################################################
def debug_thread(s):
global USE_DEBUG
if not USE_DEBUG:
return
# compatibility with old versions of python (< 2.6)
if hasattr(threading.currentThread(), 'name'):
name = threading.currentThread().name
else:
name = threading.currentThread().getName()
print('%s: %s' % (name, s))
#######################################################################
def thread_cache(task_cache, cache_dir):
try:
while True:
if task_cache.empty():
break
url = task_cache.get()
debug_thread('starting caching %s' % url)
try:
do_cache(cache_dir, url)
except Exception as e:
print('Exception in worker thread for caching %s: %s' % (url, e), file=sys.stderr)
task_cache.task_done()
except queue.Empty:
pass
#######################################################################
def thread_upstream(cache_dir, task, result, fast_update, limits, fallback_data):
try:
while True:
if task.empty():
break
(modulename, method, additional_info) = task.get()
debug_thread('starting %s' % modulename)
try:
(cat, version, location) = do_task(cache_dir, modulename, method, additional_info, fast_update, limits, fallback_data)
result.put((cat, modulename, version, location))
except Exception as e:
print('Exception in worker thread for %s: %s' % (modulename, e), file=sys.stderr)
task.task_done()
except queue.Empty:
pass
#######################################################################
def start_threads(task_cache, cache_dir, task, result, fast_update, limits, fallback_data):
if cache_dir:
# First do all the cache tasks
for i in range(min(MAX_THREADS, task.qsize())):
t = threading.Thread(target=thread_cache, args=(task_cache, cache_dir))
t.start()
task_cache.join()
# Then do all the remaining tasks
for i in range(min(MAX_THREADS, task.qsize())):
t = threading.Thread(target=thread_upstream, args=(cache_dir, task, result, fast_update, limits, fallback_data))
t.start()
task.join()
#######################################################################
def main(args):
parser = optparse.OptionParser()
parser.add_option('--debug', dest='debug',
help='only handle the argument as input and output the result')
parser.add_option('--log', dest='log',
help='log file to use (default: stderr)')
parser.add_option('--directory', dest='dir', default='.',
help='directory where to find data and save data')
parser.add_option('--save-file', dest='save',
help='path to the file where the results will be written')
parser.add_option('--upstream-limits', dest='upstream_limits',
help='path to the upstream limits data file')
parser.add_option('--upstream-tarballs', dest='upstream_tarballs',
help='path to the upstream tarballs data file')
parser.add_option('--fast-update', action='store_true',
default=False, dest='fast_update',
help='when available, use old saved data instead of looking for new data (limits will be ignored)')
parser.add_option('--use-old-as-fallback', action='store_true',
default=False, dest='fallback',
help='if available, use old saved data as a fallback for when we cannot find new data (limits will be ignored for the fallback case)')
parser.add_option('--only-if-old', action='store_true',
default=False, dest='only_if_old',
help='execute only if the pre-existing result file is older than 10 hours')
(options, args) = parser.parse_args()
fallback_data = {}
directory = options.dir
if options.log:
path = os.path.realpath(options.log)
safe_mkdir_p(os.path.dirname(path))
sys.stderr = open(options.log, 'a')
if options.upstream_limits:
limit_file = options.upstream_limits
else:
limit_file = os.path.join(directory, 'upstream-limits.txt')
limits = parse_limits(limit_file)
if options.debug:
lines = [ options.debug + '\n' ]
out = sys.stdout
else:
if options.upstream_tarballs:
upstream_file = options.upstream_tarballs
else:
upstream_file = os.path.join(directory, 'upstream-tarballs.txt')
if options.save:
save_file = options.save
else:
save_file = os.path.join(directory, 'versions-upstream')
if not os.path.exists(upstream_file):
print('Upstream data file %s does not exist.' % upstream_file, file=sys.stderr)
return 1
elif not os.path.isfile(upstream_file):
print('Upstream data file %s is not a regular file.' % upstream_file, file=sys.stderr)
return 1
if os.path.exists(save_file):
if not os.path.isfile(save_file):
print('Save file %s is not a regular file.' % save_file, file=sys.stderr)
return 1
if options.only_if_old:
stats = os.stat(save_file)
# Quit if it's less than 10-hours old
if time.time() - stats.st_mtime < 3600 * 10:
return 2
if options.fallback or options.fast_update:
fallback_data = parse_data(save_file)
else:
safe_mkdir_p(os.path.dirname(save_file))
file = open(upstream_file)
lines = file.readlines()
file.close()
out = open(save_file, 'w')
# The default timeout is just too long. Use 10 seconds instead.
socket.setdefaulttimeout(10)
to_cache = set()
task_cache = queue.Queue()
task = queue.Queue()
result = queue.Queue()
for line in lines:
if _line_is_comment(line):
continue
(modulename, method, additional_info) = line[:-1].split(':', 2)
# We will do a cache prefetch of all http url, so that we don't have to
# open the same url several times
if method in [ 'httpls', 'dualhttpls' ]:
if method == 'httpls':
to_cache.add(additional_info)
elif method == 'dualhttpls':
(url1, url2) = additional_info.split('|')
to_cache.add(url1)
to_cache.add(url2)
task.put((modulename, method, additional_info))
# We'll have to remove this temporary cache
if not options.debug:
cache_dir = tempfile.mkdtemp(prefix='upstream-cache-', dir=os.path.dirname(save_file))
else:
cache_dir = None
for url in to_cache:
task_cache.put(url)
start_threads(task_cache, cache_dir, task, result, options.fast_update, limits, fallback_data)
while not result.empty():
(cat, modulename, version, location) = result.get()
if version and location:
out.write('%s:%s:%s:%s\n' % (cat, modulename, version, location))
elif version:
out.write('%s:%s:%s:\n' % (cat, modulename, version))
elif location:
out.write('%s:%s::%s\n' % (cat, modulename, location))
else:
out.write('%s:%s::\n' % (cat, modulename))
result.task_done()
# Remove temporary cache
if cache_dir:
shutil.rmtree(cache_dir)
if not options.debug:
out.close()
return 0
if __name__ == '__main__':
try:
ret = main(sys.argv)
sys.exit(ret)
except KeyboardInterrupt:
pass
0707010000002D000041ED0000000000000000000000026548EB8C00000000000000000000000000000000000000000000003A00000000osc-plugin-collab-0.104+30/server/upstream/gnome-versions0707010000002E000081ED0000000000000000000000016548EB8C000043E9000000000000000000000000000000000000004C00000000osc-plugin-collab-0.104+30/server/upstream/gnome-versions/generate-versions#!/usr/bin/python3
# vim: set sw=4 ts=4 et:
import errno
import os
import sys
import json
import optparse
from operator import attrgetter
import urllib.parse
import rpm
try:
from lxml import etree as ET
except ImportError:
try:
from xml.etree import cElementTree as ET
except ImportError:
import cElementTree as ET
JSON_FORMAT = 4
BRANCH_LIMITS = {
'glib': '1.3',
'gnome-desktop': '2.90',
'gnome-menus': '3.1',
'goffice': '0.9',
'goocanvas': '1.90',
'gtk+': '1.3',
'gtk-engines': '2.90',
'gtkmm': '2.90',
'gtkmm-documentation': '2.90',
'gtksourceview': ('1.9', '2.11'),
'gtksourceviewmm': '2.90',
'libgda': ('3.99', '4.99'),
'libgnomedb': '3.99',
'libsigc++': ('1.3', '2.99'),
'libunique': '2',
'libwnck': '2.90',
'pygobject': '2.29',
'vala': '0.13',
'vala': '0.15',
'vte': '0.29',
# modules with an unstable branch as current branch
'gmime': '2.5'
}
STABLE_BRANCH_SAME_LIMITS = {
# Modules with the same branch as something in the modulesets
'anjuta-extras': 'anjuta',
'eog-plugins': 'eog',
'epiphany-extensions': 'epiphany',
'evolution': 'evolution-data-server',
'evolution-ews': 'evolution-data-server',
'evolution-exchange': 'evolution-data-server',
'evolution-groupwise': 'evolution-data-server',
'evolution-kolab': 'evolution-data-server',
'evolution-mapi': 'evolution-data-server',
'gdl': 'anjuta',
# Gone in 3.10:
#'gnome-applets': 'gnome-panel',
'gnome-shell-extensions': 'gnome-shell'
}
STABLE_BRANCHES_LIMITS = {
'3.4': {
'NetworkManager-openconnect': '0.9.5.0',
'NetworkManager-openswan': '0.9.5.0',
'NetworkManager-openvpn': '0.9.5.0',
'NetworkManager-pptp': '0.9.5.0',
'NetworkManager-vpnc': '0.9.5.0',
'ghex': '3.5',
'gtkhtml': '4.5',
'libgda': '5.1',
'libgdata': '0.13',
'pyatspi': '2.5',
'tomboy': '1.11'
},
'3.6': {
'NetworkManager-openconnect': '0.9.7.0',
'NetworkManager-openswan': '0.9.7.0',
'NetworkManager-openvpn': '0.9.7.0',
'NetworkManager-pptp': '0.9.7.0',
'NetworkManager-vpnc': '0.9.7.0',
'alacarte': '3.7',
'ghex': '3.7',
'glom': '1.23',
'gnote': '3.7',
'gtkhtml': '4.7',
'libgda': '5.3',
'libgdata': '0.15',
'pyatspi': '2.7',
'tomboy': '1.13'
},
'3.8': {
'NetworkManager-openconnect': '0.9.9.0',
'NetworkManager-openswan': '0.9.9.0',
'NetworkManager-openvpn': '0.9.9.0',
'NetworkManager-pptp': '0.9.9.0',
'NetworkManager-vpnc': '0.9.9.0',
'alacarte': '3.9',
'ghex': '3.9',
'glom': '1.25',
'gnome-applets': '3.9',
'gnome-panel': '3.9',
'gnote': '3.9',
'gtkhtml': '4.7',
'libgda': '5.3',
'pyatspi': '2.9',
'tomboy': '1.15'
},
'3.10': {
'gnome-applets': '3.11',
'gnome-panel': '3.11'
},
'3.12': {
'gnome-applets': '3.13',
'gnome-panel': '3.13'
},
'3.14': {
'gnome-applets': '3.15',
'gnome-panel': '3.15'
}
}
BLACKLISTED_SOURCES = [
# Sources not using ftpadmin
#Seems to use it now: 'banshee',
# Sources that are now hosted elsewhere (and listing them from
# ftp.gnome.org can be an issue).
'abiword',
'balsa'
'clutter-gst',
'gimp',
'gnucash',
'gst-python',
'g-wrap',
'intltool',
'libgnomesu',
'librep',
'pkg-config',
'rep-gtk',
'sawfish',
'startup-notification',
'xchat',
# Sources that we know have no cache.json
'librep-2002-03',
'rep-gtk-2002-03',
'sawfish-2002-03',
'xpenguins_applet',
'labyrinth_0.4.0',
'labyrinth_0.4.0rc3',
'delme'
]
##################################################################
# All this code is taken from osc-plugin-collab
##################################################################
def safe_mkdir_p(dir):
if not dir:
return
try:
os.makedirs(dir)
except OSError as e:
if e.errno != errno.EEXIST:
raise e
##################################################################
# End of code taken from osc-plugin-collab
##################################################################
##################################################################
# All this code is taken from convert-to-tarballs.py
##################################################################
def _bigger_version(a, b):
rc = rpm.labelCompare(("0", a, "0"), ("0", b, "0"))
if rc > 0:
return a
else:
return b
# This is nearly the same as _bigger_version, except that
# - It returns a boolean value
# - If max_version is None, it just returns False
# - It treats 2.13 as == 2.13.0 instead of 2.13 as < 2.13.0
# The second property is particularly important with directory hierarchies
def _version_greater_or_equal_to_max(a, max_version):
if not max_version:
return False
rc = rpm.labelCompare(("0", a, "0"), ("0", max_version , "0"))
if rc >= 0:
return True
else:
return False
def _get_latest_version(versions, max_version):
biggest = versions[0]
for version in versions[1:]:
# Just ignore '-' in versions
if version.find('-') >= 0:
version = version[:version.find('-')]
if (version == _bigger_version(biggest, version) and \
not _version_greater_or_equal_to_max(version, max_version)):
biggest = version
return biggest
##################################################################
# End of code taken from convert-to-tarballs.py
##################################################################
class Module:
''' Object representing a module '''
def __init__(self, name, limit):
self.name = name
self.limit = limit
self.version = ''
def fetch_version(self, all_versions):
if self.name not in all_versions:
return
versions = all_versions[self.name]
latest = _get_latest_version(versions, self.limit)
if latest:
self.version = latest
def get_str(self, release_set, subdir = None):
if not self.version:
prefix = '#'
else:
prefix = ''
release_set = 'fgo'
if subdir:
return '%s%s:%s:%s:%s\n' % (prefix, release_set, self.name, self.version, subdir)
else:
return '%s%s:%s:%s:\n' % (prefix, release_set, self.name, self.version)
class SubReleaseSet:
''' Object representing a sub-release set (like the bindings) (made of
modules)
'''
def __init__(self, name):
self.name = name
self.modules = []
def add(self, module):
self.modules.append(module)
def fetch_versions(self, all_versions):
for module in self.modules:
module.fetch_version(all_versions)
def get_str(self, release_set):
# Sort by name, then version (sorts are stable)
self.modules.sort(key=attrgetter('version'))
self.modules.sort(key=attrgetter('name'))
res = '# %s\n' % self.name.title()
for module in self.modules:
res += module.get_str(release_set, self.name)
res += '\n'
return res
class ReleaseSet:
''' Object representing a release set (made of modules, and sub-release
sets, like the bindings ones)
'''
def __init__(self, name):
self.name = name
self.subrelease_sets = []
self.modules = []
def add(self, module, subdir):
if subdir:
sub = self.find_subrelease_set(subdir)
if sub is None:
sub = SubReleaseSet(subdir)
self.subrelease_sets.append(sub)
sub.add(module)
else:
self.modules.append(module)
def find_subrelease_set(self, subrelease_set):
for sub in self.subrelease_sets:
if sub.name == subrelease_set:
return sub
return None
def fetch_versions(self, all_versions):
for module in self.modules:
module.fetch_version(all_versions)
for sub in self.subrelease_sets:
sub.fetch_versions(all_versions)
def get_all_modules(self):
res = []
res.extend(self.modules)
for sub in self.subrelease_sets:
res.extend(sub.modules)
return res
def get_str(self):
# Sort by name, then version (sorts are stable)
self.modules.sort(key=attrgetter('version'))
self.modules.sort(key=attrgetter('name'))
self.subrelease_sets.sort(key=attrgetter('name'))
res = '## %s\n' % self.name.upper()
for module in self.modules:
res += module.get_str(self.name)
res += '\n'
for sub in self.subrelease_sets:
res += sub.get_str(self.name)
return res
class Release:
''' Object representing a release (made of release sets) '''
def __init__(self):
self.release_sets = []
def add(self, release_set, module, subdir):
rel_set = self.find_release_set(release_set)
if rel_set is None:
rel_set = ReleaseSet(release_set)
self.release_sets.append(rel_set)
rel_set.add(module, subdir)
def find_release_set(self, release_set):
for rel_set in self.release_sets:
if rel_set.name == release_set:
return rel_set
return None
def fetch_versions(self, all_versions):
for rel_set in self.release_sets:
rel_set.fetch_versions(all_versions)
def get_all_modules(self):
res = []
for rel_set in self.release_sets:
res.extend(rel_set.get_all_modules())
return res
def get_str(self):
res = ''
for rel_set in self.release_sets:
res += rel_set.get_str()
return res
def get_release(tarball_conversion):
''' We take all packages under <whitelist> that have a non-empty 'set'
attribute.
Interesting examples:
<package name="libwnck-2" module="libwnck" limit="2.90" set="core"/>
<package name="seed" subdir="js" set="bindings" limit="2.33"/>
'''
rel = Release()
root = ET.parse(tarball_conversion).getroot()
for whitelist in root.findall('whitelist'):
for package in whitelist.findall('package'):
release_set = package.get('set')
if not release_set:
continue
module = package.get('module') or package.get('name')
limit = package.get('limit') or None
subdir = package.get('subdir')
mod = Module(module, limit)
rel.add(release_set, mod, subdir)
return rel
def fetch_all_versions(json_dir):
''' Get all versions for all modules installed on ftp.gnome.org, based on
the json file.
'''
all_versions = {}
for child in os.listdir(json_dir):
if not os.path.isfile(os.path.join(json_dir, child)):
continue
if not child[-5:] == '.json':
continue
module = child[:-5]
json_file = os.path.join(json_dir, child)
if module in BLACKLISTED_SOURCES:
continue
j = json.load(open(json_file, 'rb'))
json_format = j[0]
if json_format != JSON_FORMAT:
print('Format of cache.json for \'%s\' is %s while we support \'%s\'.' % (module, json_format, JSON_FORMAT), file=sys.stderr)
continue
json_format, json_info, json_versions, json_ignored = j
versions = json_versions[urllib.parse.unquote(module)]
versions.sort()
if not versions:
continue
all_versions[urllib.parse.unquote(module)] = versions
return all_versions
def get_extras_limit(module, release, stable_version):
# Workaround https://bugzilla.gnome.org/show_bug.cgi?id=649331
if module == 'dia':
return '1'
if not stable_version:
return None
if stable_version in STABLE_BRANCHES_LIMITS:
limits = STABLE_BRANCHES_LIMITS[stable_version]
if module in limits:
return limits[module]
if not release:
return None
if module not in STABLE_BRANCH_SAME_LIMITS:
return None
stable_module = STABLE_BRANCH_SAME_LIMITS[module]
modules = release.get_all_modules()
for m in modules:
if m.name == stable_module:
return m.limit
print('Cannot find limit for \'%s\': no module \'%s\' in moduleset.' % (module, stable_module), file=sys.stderr)
return None
def get_extras_versions(all_versions, release, stable_version):
''' Get the latest version of all modules (except the ones already in
release), as well as the latest versions for all limits configured in
the BRANCH_LIMITS variable for those modules. '''
if release:
modules_in_release = [ x.name for x in release.get_all_modules() ]
else:
modules_in_release = []
res = []
for (module, versions) in list(all_versions.items()):
if module not in modules_in_release:
limit = get_extras_limit(module, release, stable_version)
latest = _get_latest_version(versions, limit)
if latest:
res.append((module, latest))
if module in BRANCH_LIMITS:
limits_module = BRANCH_LIMITS[module]
if type(limits_module) == str:
latest = _get_latest_version(versions, limits_module)
if latest:
res.append((module, latest))
elif type(limits_module) == tuple:
for limit in limits_module:
latest = _get_latest_version(versions, limit)
if latest:
res.append((module, latest))
else:
print('Unknown limit format \'%s\' for \'%s\'.' % (limits_module, module), file=sys.stderr)
return res
def main(args):
parser = optparse.OptionParser()
parser.add_option("-s", "--stable-version", dest="stable_version",
help="stable branch to consider", metavar="VERSION")
parser.add_option("-c", "--conversion-config", dest="conversion_config",
help="tarball-conversion config file", metavar="FILE")
parser.add_option("-d", "--output-dir", dest="output_dir",
help="output dir", metavar="DIR")
parser.add_option("-j", "--json-dir", dest="json_dir",
help="JSON cache dir", metavar="DIR")
(options, args) = parser.parse_args()
versions_all = []
packages_in_conversion_config = []
release = None
all_versions = None
if options.conversion_config is not None:
if not os.path.exists(options.conversion_config):
print('tarball-conversion config file \'%s\' does not exist.' % options.conversion_config, file=sys.stderr)
return 1
try:
release = get_release(options.conversion_config)
except SyntaxError as e:
print('Cannot parse tarball-conversion config file \'%s\': %s' % (options.conversion_config, e), file=sys.stderr)
return 1
if len(release.get_all_modules()) == 0:
print('Parsing tarball-conversion config file \'%s\' resulted in no module in release sets.' % options.conversion_config, file=sys.stderr)
return 1
if options.stable_version and options.stable_version not in STABLE_BRANCHES_LIMITS:
print('No defined limits for stable version \'%s\'.' % options.stable_version, file=sys.stderr)
if options.json_dir is None:
print('JSON cache directory must be specified.' % options.json_dir, file=sys.stderr)
return 1
if not os.path.exists(options.json_dir) or not os.path.isdir(options.json_dir):
print('JSON cache directory \'%s\' is not a directory.' % options.json_dir, file=sys.stderr)
return 1
all_versions = fetch_all_versions(options.json_dir)
if release is not None:
release.fetch_versions(all_versions)
extras_versions = get_extras_versions(all_versions, release, options.stable_version)
extras_versions.sort()
if options.output_dir is None:
output_dir = '.'
else:
output_dir = options.output_dir
if os.path.exists(output_dir):
if not os.path.isdir(output_dir):
print('Output directory \'%s\' is not a directory.' % output_dir, file=sys.stderr)
return 1
else:
safe_mkdir_p(output_dir)
if release is not None:
out = open(os.path.join(output_dir, 'versions'), 'w')
out.write(release.get_str())
out.close()
out = open(os.path.join(output_dir, 'versions-extras'), 'w')
out.write('## EXTRAS\n')
for (module, version) in extras_versions:
#out.write('%s:%s:%s:\n' % ('extras', module, version))
out.write('%s:%s:%s:\n' % ('fgo', module, version))
out.close()
return 0
if __name__ == '__main__':
try:
ret = main(sys.argv)
sys.exit(ret)
except KeyboardInterrupt:
pass
0707010000002F000081ED0000000000000000000000016548EB8C0000131C000000000000000000000000000000000000004A00000000osc-plugin-collab-0.104+30/server/upstream/gnome-versions/update-versions#!/bin/sh
VERBOSE=0
TMPDIR=$(mktemp -d)
if test $# -ne 1; then
echo "Usage: $(basename $0) DEST-DIR"
exit 1
fi
DESTDIR=$1
GENERATEVERSIONS=$(readlink -f $(dirname $0))/generate-versions
die_if_error () {
if test $? -ne 0; then
if test "x$1" != "x"; then
echo $1
else
echo "Unknown error"
fi
rm -rf $TMPDIR
exit 1
fi
}
echo_verbose () {
if test $VERBOSE -ne 0; then
echo "$*"
fi
}
DOWNLOAD_STABLE="`curl --tlsv1 --silent --fail https://download.gnome.org/core/ | grep 'a href=".*/"' | sed 's/.*href="//g;s/\/".*//g' | grep -P "^(3\.|4)" | sort -g | tail -n 1`"
#TEMPORARY_STABLE="41"
if test -z "$DOWNLOAD_STABLE"; then
echo "Cannot find stable release from download.gnome.org."
exit 1
fi
if test -n "$TEMPORARY_STABLE" -a "x$DOWNLOAD_STABLE" = "x$TEMPORARY_STABLE"; then
echo "TEMPORARY_STABLE hack can be removed"
fi
if test -n "$TEMPORARY_STABLE"; then
STABLE="$TEMPORARY_STABLE"
else
STABLE="$DOWNLOAD_STABLE"
fi
STABLE_MAJOR=`echo $STABLE | sed "s/\(^[0-9]\+\.\).*/\1/g"`
UNSTABLE="$(echo $STABLE_MAJOR +1 | bc)"
echo_verbose "Stable: $STABLE - Unstable: $UNSTABLE"
mkdir -p $DESTDIR
die_if_error "Error while creating destination directory"
cd $TMPDIR
die_if_error "Cannot change directory to $TMPDIR"
if test -z "$GNOME_OFFLINE"; then
curl --tlsv1 --silent --show-error --output $TMPDIR/sources.html https://download.gnome.org/sources/
die_if_error "Error while downloading list of sources"
if test -d $TMPDIR/json-cache; then
rm -f $TMPDIR/json-cache/*
rmdir $TMPDIR/json-cache
fi
if test -e $TMPDIR/json-cache; then
echo "JSON cache directory still exists."
exit 1
fi
mkdir $TMPDIR/json-cache
die_if_error "Error while creating JSON cache directory"
for dir in $(cat $TMPDIR/sources.html | grep 'a href=".*/"' | sed 's/.*href="//g;s/".*//g'); do
module=${dir%%/}
if test "$dir" == "$module" -o "$dir" == "../"; then
continue
fi
for try in 1 2 3; do
# --fail/-f: do not ouput HTTP 40x error pages
# --location/-L: follow redirects
curl --tlsv1 --silent --fail --location https://download.gnome.org/sources/$module/cache.json > $TMPDIR/json-cache/$module.json
test $? -eq 0 -o $? -eq 22 && break
if test $try -eq 3; then
echo "Cannot download cache.json for $module"
exit 1
fi
sleep 3
done
done
curl --tlsv1 --silent --show-error --output $TMPDIR/tarball-conversion.config https://gitlab.gnome.org/GNOME/releng/raw/master/tools/smoketesting/tarball-conversion.config
die_if_error "Error while downloading tarball-conversion.config"
curl --tlsv1 --silent --show-error --output $TMPDIR/tarball-conversion-stable.config https://gitlab.gnome.org/GNOME/releng/raw/master/tools/smoketesting/tarball-conversion-${STABLE/./-}.config
die_if_error "Error while downloading tarball-conversion-stable.config"
fi
echo_verbose "Generating stable versions..."
$GENERATEVERSIONS --json-dir=$TMPDIR/json-cache --output-dir=$TMPDIR --conversion-config=$TMPDIR/tarball-conversion-stable.config --stable-version=$STABLE
die_if_error "Error while creating stable versions"
mv $TMPDIR/versions $DESTDIR/gnome-$STABLE
die_if_error "Error while moving stable versions"
cp -f $DESTDIR/gnome-$STABLE $DESTDIR/gnome-stable
die_if_error "Error while copying the stable versions"
mv $TMPDIR/versions-extras $DESTDIR/gnome-$STABLE-extras
die_if_error "Error while moving stable extras versions"
cp -f $DESTDIR/gnome-$STABLE-extras $DESTDIR/gnome-stable-extras
die_if_error "Error while copying the stable extras versions"
echo_verbose "Generating unstable versions..."
$GENERATEVERSIONS --json-dir=$TMPDIR/json-cache --output-dir=$TMPDIR --conversion-config=$TMPDIR/tarball-conversion.config
die_if_error "Error while creating unstable versions"
mv $TMPDIR/versions $DESTDIR/gnome-$UNSTABLE
die_if_error "Error while moving unstable versions"
cp -f $DESTDIR/gnome-$UNSTABLE $DESTDIR/gnome-unstable
die_if_error "Error while copying the unstable versions"
mv $TMPDIR/versions-extras $DESTDIR/gnome-$UNSTABLE-extras
die_if_error "Error while moving unstable extras versions"
cp -f $DESTDIR/gnome-$UNSTABLE-extras $DESTDIR/gnome-unstable-extras
die_if_error "Error while copying the unstable extras versions"
rm -rf $TMPDIR
# To update a versions file for an old stable version:
# - Get the tarball-conversion-stable.config from git, when the stable version
# was still the old stable version and put it in ~/local/share/
# - Then:
# cd ~/local/tmp
# export VERSION=2.28
# ~/local/bin/generate-versions --output-dir=/home/users/vuntz/local/tmp/ --conversion-config=/home/users/vuntz/local/share/tarball-conversion-$VERSION.config
# mv versions ~/public_html/tmp/versions/versions-$VERSION
# mv versions-extras ~/public_html/tmp/versions/versions-$VERSION-extras
07070100000030000081ED0000000000000000000000016548EB8C00002789000000000000000000000000000000000000003100000000osc-plugin-collab-0.104+30/server/upstream/runme#!/bin/sh
# vim: set ts=4 sw=4 et:
#
# Copyright (c) 2008-2009, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
basedir=`dirname $0`
## Options
# What's the current GNOME version in Factory
# Note: when moving to unstable, also remove the unneeded limits in upstream-limits.txt
GNOME_FACTORY_VERSION=stable
## Basic setup
CACHE_DIR=./cache
CONFIG_FILE=
LOG_FILE=
usage() {
echo "Usage: $0 [-o CONF-FILE] [-l LOG-FILE]"
echo ""
echo "Options:"
echo " -o CONF-FILE Use CONF-FILE as configuration file"
echo " -l LOG-FILE Use LOG-FILE to log errors"
}
while getopts o:l:h option; do
case $option in
o) CONFIG_FILE=$OPTARG;;
l) LOG_FILE=$OPTARG;;
h|help) usage; exit 0;;
*) usage; exit 1;;
esac
done
if test "x$CONFIG_FILE" != "x"; then
if test ! -f $CONFIG_FILE; then
echo >&2 "Configuration file $CONFIG_FILE does not exit."
exit 1
else
OBS_OPTIONS_CACHE_DIR=`grep "^ *cache-dir =" $CONFIG_FILE | sed "s/.*= *\(.*\) *$/\1/g" | tail -n 1`
test "x$OBS_OPTIONS_CACHE_DIR" != "x" && CACHE_DIR=$OBS_OPTIONS_CACHE_DIR
fi
fi
mkdir -p $CACHE_DIR
##############################################################
# Download latest upstream versions
# For non-GNOME:Factory, we only care about the official GNOME modules.
concatenate_all_versions () {
DESTFILE=$CACHE_DIR/upstream/latest
rm -f $DESTFILE.new
for file in $CACHE_DIR/upstream/gnome-$GNOME_FACTORY_VERSION \
$CACHE_DIR/upstream/gnome-$GNOME_FACTORY_VERSION-extras \
$CACHE_DIR/upstream/upstream; do
if test -f $file; then
cat $file >> $DESTFILE.new
fi
done
if test $? -ne 0; then
echo "Error while creating the merged latest upstream versions file"
return 1
fi
# we do everything above in a temporary file so that errors are safely
# ignored, and so that we can compare the result (and keep the old file
# with the old mtime if there's no change)
cmp --quiet $DESTFILE.new $DESTFILE
if test $? -ne 0; then
mv $DESTFILE.new $DESTFILE
else
rm -f $DESTFILE.new
fi
}
download_gnome_version () {
VERSION=$1
if test "x$1" = "x"; then
return 1
fi
DESTFILE=$CACHE_DIR/upstream/gnome-$VERSION
rm -f $DESTFILE.new
wget -q -nc -O $DESTFILE.new http://www.gnome.org/~vuntz/tmp/versions/versions-$VERSION
if test $? -ne 0; then
echo "Error while checking for new GNOME upstream versions ($VERSION)"
return 1
fi
# Don't use gstreamer from ftp.gnome.org -- it can be outdated
sed -i "s/^\(desktop:gst-plugins.*\)$/# \1/g;s/^\(desktop:gstreamer:.*\)$/# \1/g" $DESTFILE.new
# We don't care about mobile stuff
sed -i "s/^\(mobile:.*\)$/# \1/g" $DESTFILE.new
# Let's name the group fgo, instead of core, apps, extras, etc.
sed -i "s/^[^#:][^:]*:/fgo:/g" $DESTFILE.new
cmp --quiet $DESTFILE.new $DESTFILE
if test $? -ne 0; then
mv $DESTFILE.new $DESTFILE
else
rm -f $DESTFILE.new
fi
}
download_cpan_version () {
DESTFILE=$CACHE_DIR/upstream/cpan
rm -f $DESTFILE.new
# -a will keep the mtime
test -f $DESTFILE && cp -a $DESTFILE $DESTFILE.new
LOG_OPTION=
if test "x$LOG_FILE" != "x"; then
LOG_OPTION="--log $LOG_FILE"
fi
$basedir/download-cpan-versions $LOG_OPTION \
--save-file=$DESTFILE.new \
--only-if-old
RETVAL=$?
if test $RETVAL -eq 2; then
# No update was done (old file was not old enough)
rm -f $DESTFILE.new
return 2
fi
if test $RETVAL -ne 0; then
echo "Error while checking for new upstream versions on CPAN"
rm -f $DESTFILE.new
return 1
fi
sort -u $DESTFILE.new > $DESTFILE.new.sorted
mv $DESTFILE.new.sorted $DESTFILE.new
cmp --quiet $DESTFILE.new $DESTFILE
if test $? -ne 0; then
mv $DESTFILE.new $DESTFILE
else
rm -f $DESTFILE.new
fi
}
download_pypi_version () {
DESTFILE=$CACHE_DIR/upstream/pypi
rm -f $DESTFILE.new
# -a will keep the mtime
test -f $DESTFILE && cp -a $DESTFILE $DESTFILE.new
LOG_OPTION=
if test "x$LOG_FILE" != "x"; then
LOG_OPTION="--log $LOG_FILE"
fi
$basedir/download-pypi-versions $LOG_OPTION \
--save-file=$DESTFILE.new \
--only-if-old
RETVAL=$?
if test $RETVAL -eq 2; then
# No update was done (old file was not old enough)
rm -f $DESTFILE.new
return 2
fi
if test $RETVAL -ne 0; then
echo "Error while checking for new upstream versions on pypi"
rm -f $DESTFILE.new
return 1
fi
sort -u $DESTFILE.new > $DESTFILE.new.sorted
mv $DESTFILE.new.sorted $DESTFILE.new
cmp --quiet $DESTFILE.new $DESTFILE
if test $? -ne 0; then
mv $DESTFILE.new $DESTFILE
else
rm -f $DESTFILE.new
fi
}
download_fallback_version () {
DESTFILE=$CACHE_DIR/upstream/fallback
rm -f $DESTFILE.new
# -a will keep the mtime
test -f $DESTFILE && cp -a $DESTFILE $DESTFILE.new
LOG_OPTION=
if test "x$LOG_FILE" != "x"; then
LOG_OPTION="--log $LOG_FILE"
fi
$basedir/download-fallback-versions $LOG_OPTION \
--save-file=$DESTFILE.new
RETVAL=$?
if test $RETVAL -eq 2; then
# No update was done (old file was not old enough)
rm -f $DESTFILE.new
return 2
fi
if test $RETVAL -ne 0; then
echo "Error while checking for fallback of new upstream versions"
rm -f $DESTFILE.new
return 1
fi
cmp --quiet $DESTFILE.new $DESTFILE
if test $? -ne 0; then
mv $DESTFILE.new $DESTFILE
else
rm -f $DESTFILE.new
fi
}
download_upstream_version () {
DESTFILE=$CACHE_DIR/upstream/upstream
# -a will keep the mtime
test -f $DESTFILE && cp -a $DESTFILE $DESTFILE.new
LOG_OPTION=
if test "x$LOG_FILE" != "x"; then
LOG_OPTION="--log $LOG_FILE"
fi
$basedir/download-upstream-versions $LOG_OPTION \
--upstream-tarballs=$basedir/upstream-tarballs.txt \
--upstream-limits=$basedir/upstream-limits.txt \
--save-file=$DESTFILE.new \
--only-if-old --use-old-as-fallback
RETVAL=$?
if test $RETVAL -eq 2; then
# No update was done (old file was not old enough)
rm -f $DESTFILE.new
return 2
fi
if test $RETVAL -ne 0; then
echo "Error while checking for new upstream versions"
rm -f $DESTFILE.new
return 1
fi
cmp --quiet $DESTFILE.new $DESTFILE
if test $? -ne 0; then
mv $DESTFILE.new $DESTFILE
else
rm -f $DESTFILE.new
fi
}
mkdir -p $CACHE_DIR/status
mkdir -p $CACHE_DIR/upstream
## Discontinued
# download_gnome_version 2.26
# download_gnome_version 2.28
# download_gnome_version 2.30
# download_gnome_version 2.32
# download_gnome_version 3.0
# download_gnome_version 3.2
# download_gnome_version 3.4
# download_gnome_version 3.6
# download_gnome_version 3.6-extras
# download_gnome_version 3.8
# download_gnome_version 3.8-extras
#download_gnome_version 3.12
#download_gnome_version 3.12-extras
# Disabled because of infrastructure change on GNOME servers
#download_gnome_version stable
#download_gnome_version unstable
#download_gnome_version stable-extras
#download_gnome_version unstable-extras
# Do this once, before the slow step
concatenate_all_versions
download_cpan_version
download_pypi_version
download_fallback_version
download_upstream_version
if test $? -eq 0; then
concatenate_all_versions
fi
# Check that we have everything in the match database; we only do this once per
# day to avoid sending mails every X minutes.
MATCH_CHECK_TIMESTAMP=0
MATCH_CHECK_FILE="$CACHE_DIR/status/upstream-match-check"
if test -f "$MATCH_CHECK_FILE"; then
MATCH_CHECK_TIMESTAMP=`stat --format="%Y" "$MATCH_CHECK_FILE"`
MATCH_CHECK_TIMESTAMP=`echo "$MATCH_CHECK_TIMESTAMP + 24 * 3600" | bc`
fi
if test "$MATCH_CHECK_TIMESTAMP" -lt "`date +%s`"; then
for i in `grep -v '^#' $CACHE_DIR/upstream/latest | grep ':' | cut -d ':' -f 2`; do
re_i=`echo $i | sed 's/\+/\\\\\\+/g'`
grep -q -E "^(# ?)?$re_i[:|]" $basedir/upstream-packages-match.txt
if test $? -ne 0; then
echo $i not in $basedir/upstream-packages-match.txt
fi
done
echo "Last check for upstream match database completeness: `date --rfc-3339=seconds`" > "$MATCH_CHECK_FILE"
fi
07070100000031000081A40000000000000000000000016548EB8C000007E9000000000000000000000000000000000000003F00000000osc-plugin-collab-0.104+30/server/upstream/upstream-limits.txt# This file is used to express limits we want to have on new upstream tarballs.
#
# It's possible to decide to:
#
# + not have unstable versions (for modules following the x.y.z release
# scheme, where it's unstable when y is odd)
# Use the no-odd-unstable instruction for this behavior.
#
# + not have a version greater or equal than a specified version.
# Use the "max|x.y" instruction for this behavior.
#
# + not have specific versions.
# Use the "skip|x.y;a.b;..." instruction for this behavior
#
# LaTeXPlugin has some tarballs with a date instead of version
LaTeXPlugin:max|2000
# bash-completion has some tarballs with a date instead of version
bash-completion:max|2008
# ht has some tarballs with a date instead of version
ht:max|2000
# iptables has a 030513 tarball...
iptables:max|10000
# libflashsupport has some tarballs with a date instead of version
libflashsupport:max|2000
# libofx used 0.11 as 0.1.1...
libofx:max|0.10
xf86-video-mga:skip|1.9.99;1.9.100
# we don't want unstable versions of the following modules
gq:no-odd-unstable
swfdec:no-odd-unstable
swfdec-mozilla:no-odd-unstable
# branches
geoclue|1.0:max|1.90
gobby|0.4:max|0.4.90
gst-plugins-bad|0.10:max|0.11.0
gst-plugins-base|0.10:max|0.11.0
gst-plugins-good|0.10:max|0.11.0
gst-plugins-ugly|0.10:max|0.11.0
gst-python|0.10:max|0.11.0
gstreamer|0.10:max|0.11.0
udisks|1.90:max|1.90
webkitgtk|2.4:max|2.5
## At least for next release
#PackageKit:no-odd-unstable
#dbus:no-odd-unstable
#libdmapsharing:no-odd-unstable
#liferea:no-odd-unstable
## Modules that follow a six-months cycle, and will be released too late for
## next release
#clutter:no-odd-unstable
#deja-dup:no-odd-unstable
#folks:no-odd-unstable
#gssdp:no-odd-unstable
#gupnp:no-odd-unstable
#gupnp-av:no-odd-unstable
#gwibber:no-odd-unstable
#libgdata:no-odd-unstable
#pixman:no-odd-unstable
#telepathy-gabble:no-odd-unstable
#telepathy-glib:no-odd-unstable
#telepathy-mission-control:no-odd-unstable
#telepathy-sofiasip:no-odd-unstable
#webkitgtk:no-odd-unstable
07070100000032000081A40000000000000000000000016548EB8C00005CA7000000000000000000000000000000000000004700000000osc-plugin-collab-0.104+30/server/upstream/upstream-packages-match.txt# Format of this file:
# upstreamname:packagename
# If packagename is the same as upstreamname, then you can leave it empty
# If tracking a specific branch of upstream, then upstreamname should look like
# this: "realupstreamname|branchname". For example: gobby|0.4
# Please keep the list alphabetically sorted
BuildStream:buildstream
Coherence:python-coherence
ConsoleKit:
CouchDB:python-couchdb
DeviceKit-disks:
DeviceKit-power:
DeviceKit:
GConf:gconf2
Glib:perl-Glib
Gtk2:perl-Gtk2
LaTeXPlugin:gedit-latex-plugin
LibRaw:libraw
ModemManager:
NetworkManager-iodine:
NetworkManager-openconnect:
NetworkManager-openswan:
NetworkManager-openvpn:
NetworkManager-pptp:
NetworkManager-strongswan:
NetworkManager-vpnc:
NetworkManager:
ORBit2:orbit2
ORBit:orbit
PackageKit:
PackageKit-Qt:
PackageKit-Qt:PackageKit-Qt5
Pango:perl-Pango
PolicyKit-gnome:
QtCurve-Gtk2:qtcurve-gtk2
QtCurve-Gtk3:qtcurve-gtk3
UPnP-Inspector:upnp-inspector
abiword-docs:
abiword:
accerciser:
accountsservice:
acpid:
adwaita-icon-theme:
aegisub:
aisleriot:
alacarte:
alarm-clock-applet:
almanah:
anjuta-extras:
anjuta:
anthy:
apache-couchdb:couchdb
appdata-tools:
appres:
appstream-glib:
aqbanking:
arista:
arping:arping2
asio:
at-spi2-atk:
at-spi2-core:
at-spi:
atheme-services:atheme
atk:
atkmm:
atomix:
audiofile:
autocutsel:
autofs:
avahi:
avahi:avahi-glib2
avahi:avahi-mono
avahi:avahi-qt4
babl:
bakefile:
bakery:
balsa:
banshee-community-extensions:
banshee:
baobab:
bash-completion:
bdftopcf:
beagle-xesam:
beagle:
beforelight:
bfsync:
bindfs:
bigboard:
bijiben:
bitmap:
blktrace:
blueproximity:
bombermaze:
bot-sentry:
brasero:
brltty:
btrfs-progs:btrfsprogs
bug-buddy:
byzanz:
c++-gtk-utils:
c-ares:libcares2
cachefilesd:
cairo-clock:
cairo-compmgr:
cairo:
cairomm:
california:
calls:
cantarell-fonts:
caribou:
ccgfs:
ccsm:compizconfig-settings-manager
cdecl:
cdfs:
check:
cheese:
cherrytree:
chrome-gnome-shell:
chmlib:
chmsee:
claws-mail-extra-plugins:
claws-mail:
cloop:
clutter-gst:
clutter-gtk:
clutter:
cmsfs:
cmuclmtk:
cogl:
colorblind:
colord:
colord-gtk:
comix:
compiz-bcop:
compiz-fusion-plugins-extra:
compiz-fusion-plugins-main:
compiz-fusion-plugins-unsupported:
compiz:
compizconfig-backend-gconf:libcompizconfig-backend-gconf
compizconfig-backend-kconfig:libcompizconfig-backend-kconfig
compizconfig-python:python-compizconfig
computertemp:
conduit:
conglomerate:
conntrack-tools:
cromfs:
csmash:
cups-pk-helper:
davfs2:
d-feet:
dasher:
dbus-glib:dbus-1-glib
dbus:dbus-1
dconf:
dconf-editor:
dd_rescue:
ddrescue:gnu_ddrescue
decibel-audio-player:
dee:
deja-dup:
deskbar-applet:
desktop-data-model:
desktop-file-utils:
desktop-translations:
desktopcouch:python-desktopcouch
devhelp:
devilspie:
dia:
diffutils:
ding:
djvulibre:
dleyna-server:
dmapi:
dmlangsel:
docky:
dogtail:
dosfstools:
drwright:
dwarves:
e2fsprogs:
easytag:
ed:
editres:
eds-feed:evolution-galago
eel:
efax-gtk:
eiciel:
ekiga:
emacs:
emerald:compiz-emerald
emerillon:
empathy:
enchant:
eog-plugins:
eog:
epiphany-extensions:
epiphany:
esound:
espeak-gui:
evince:
evolution-data-server:
evolution-ews:
evolution-exchange:
evolution-groupwise:
evolution-mapi:
evolution-rss:
evolution-sharp:
evolution-tray:
evolution-webcal:
evolution:
exempi:
exfat-utils:
exfatprogs:
exiv2:
f-spot:
farsight2:
farsight:
farstream:
fcitx:
fcitx-anthy:
fcitx-chewing:
fcitx-cloudpinyin:
fcitx-configtool:
fcitx-fbterm:
fcitx-googlepinyin:
fcitx-hangul:
fcitx-libpinyin:
fcitx-m17n:
fcitx-rime:
fcitx-sayura:
fcitx-sunpinyin:
fcitx-table-extra:
fcitx-table-other:
fcitx-ui-light:
fcitx-unikey:
file-roller:
file-shrimp:
fillmore-lombard:
five-or-more:
flickrapi:python-flickrapi
folks:
font-util:
fonttosfnt:
four-in-a-row:
freerdp:FreeRDP
freetype:freetype2
frei0r-plugins:
frogr:
fslsfonts:
fstobdf:
fuse:fuse3
fusefs:exfat:fuse-exfat
fyre:
g-wrap:
gail:
gaim-galago:
galago-daemon:
galago-gtk-sharp:
galago-sharp:
gbrainy:
gcab:
gcalctool:
gccmakedep:
gcin:
gconf-editor:
gconfmm:
gcr:
gcstar:
gdata.py:python-gdata
gdesklets:gDesklets
gdk-pixbuf:
gdl:
gdlmm:
gdm:
gdome2:
geany-plugins:
geany:
geary:
gedit-code-assistance:
gedit-collaboration:
gedit-cossa:
gedit-latex:
gedit-plugins:
gedit:
geeqie:
gegl:
genius:
geoclue:geoclue2
geoclue|1.0:geoclue
geocode-glib:
gexiv2:
gfbgraph:
gftp:
ggz-client-libs:
ghex:
gi-docgen:python-gi-docgen
giggle:
gimp-dds:
gimp-gap:
gimp-help:
gimp-lqr-plugin:
gimp-save-for-web:
gimp:
gir-repository:
girl:
git:
gitg:
giver:
gjs:
gkrellm:
glabels:
glade3:
glade:
glew:
glib-networking:
glib-openssl:
glib:glib2
glib|1.3:glib
glibmm:glibmm2
glipper:
glitz:
glom:
gmetadom:
gmime:
gnac:
gnet:
gnokii:
gnome-2048:
gnome-activity-journal:
gnome-applets:
gnome-audio:
gnome-autoar:
gnome-backgrounds:
gnome-battery-bench:
gnome-blog:
gnome-bluetooth:
gnome-boxes:
gnome-build:
gnome-builder:
gnome-calculator:
gnome-calendar:
gnome-characters:
gnome-chess:
gnome-clocks:
gnome-code-assistance:
gnome-color-chooser:
gnome-color-manager:
gnome-colors:gnome-colors-icon-theme
gnome-commander:
gnome-common:
gnome-connections:
gnome-contacts:
gnome-control-center:
gnome-desktop:gnome-desktop
gnome-desktop|2.90:gnome-desktop2
gnome-devel-docs:
gnome-dictionary:
gnome-directory-thumbnailer:
gnome-disk-utility:
gnome-do-plugins:
gnome-do:
gnome-doc-utils:
gnome-documents:
gnome-dvb-daemon:
gnome-epub-thumbnailer:
gnome-font-viewer:
gnome-games-extra-data:
gnome-games:
gnome-getting-started-docs:
gnome-gmail-notifier:
gnome-gmail:
gnome-icon-theme-extras:
gnome-icon-theme-symbolic:
gnome-icon-theme:
gnome-initial-setup:
gnome-internet-radio-locator:
gnome-js-common:
gnome-keyring-sharp:
gnome-keyring:
gnome-kiosk:
gnome-klotski:
gnome-libs:
gnome-logs:
gnome-mag:
gnome-mahjongg:
gnome-main-menu:
gnome-maps:
gnome-media:
gnome-menus:
gnome-menus|3.1:gnome-menus-legacy
gnome-mime-data:
gnome-mines:
gnome-mount:
gnome-multi-writer:
gnome-music:
gnome-netstatus:
gnome-nettool:
gnome-news:
gnome-nibbles:
gnome-online-accounts:
gnome-online-miners:
gnome-packagekit:
gnome-panel:
gnome-phone-manager:
gnome-photos:
gnome-pilot-conduits:
gnome-pilot:
gnome-power-manager:
gnome-presence-applet:
gnome-python-desktop:
gnome-python-extras:python-gnome-extras
gnome-python:python-gnome
gnome-radio:
gnome-recipes:
gnome-remote-desktop:
gnome-reset:
gnome-robots:
gnome-schedule:
gnome-screensaver:
gnome-screenshot:
gnome-search-tool:
gnome-session:
gnome-settings-daemon:
gnome-sharp:gnome-sharp2
gnome-shell-extensions:
gnome-shell:
gnome-software:
gnome-sound-recorder:
gnome-speech:
gnome-spell:gnome-spell2
gnome-subtitles:
gnome-sudoku:
gnome-system-log:
gnome-system-monitor:
gnome-taquin:
gnome-terminal:
gnome-tetravex:
gnome-text-editor:
gnome-themes-extras:
gnome-themes-standard:
gnome-themes:
gnome-todo:
gnome-tour:
gnome-tweak-tool:
gnome-tweaks:
gnome-usage:
gnome-user-docs:
gnome-user-share:
gnome-utils:
gnome-vfs-monikers:
gnome-vfs-obexftp:
gnome-vfs:gnome-vfs2
gnome-vfsmm:
gnome-video-effects:
gnome-weather:
gnome-web-photo:
gnomeicu:
gnonlin:
gnopernicus:
gnote:
gnucash-docs:
gnucash:
gnumeric:
gnupg:gpg2
gob2:
gobby:
gobby|0.4:gobby04
gobject-introspection:
gocr:
goffice:
goffice|0.9:goffice-0_8
gok:
gom:
gonvert:
goobox:
goocanvas:
goocanvas|1.90:goocanvas1
goocanvasmm:
google-gadgets-for-linux:google-gadgets
gourmet:
gpa:
gparted:
gpaste:
gpgme:
gpick:
gpodder:
gq:
gqview:
gramps:
grilo-plugins:
grilo:
grisbi:
gromit:
gsettings-desktop-schemas:
gsound:
gspell:
gssdp:
gst-plugins-bad:gstreamer-plugins-bad
gst-plugins-bad|0.10:gstreamer-0_10-plugins-bad
gst-plugins-base:gstreamer-plugins-base
gst-plugins-base|0.10:gstreamer-0_10-plugins-base
gst-plugins-farsight:gstreamer-0_10-plugins-farsight
gst-plugins-gl:gstreamer-0_10-plugins-gl
gst-plugins-good:gstreamer-plugins-good
gst-plugins-good|0.10:gstreamer-0_10-plugins-good
gst-plugins-ugly:gstreamer-plugins-ugly
gst-plugins-ugly|0.10:gstreamer-0_10-plugins-ugly
gst-python:python-gstreamer
gst-python:python3-gstreamer
gst-python|0.10:python-gstreamer-0_10
gst-rtsp:
gstreamer:
gstreamer:gstreamer-doc
gstreamer|0.10:gstreamer-0_10
gstreamer|0.10:gstreamer-0_10-doc
gstreamer-editing-services:
gsynaptics:
gtef:
gtetrinet:
gtg:
gthumb:
gtk+|1.3:gtk
gtk+|2.90:gtk2
gtk+|3.89:gtk3
gtk:gtk4
gtk-doc:
gtk-engines-cleanice:gtk2-engine-cleanice
gtk-engines|2.90:gtk2-engines
gtk-recordmydesktop:gtk-recordMyDesktop
gtk-sharp:gtk-sharp2
gtk-vnc:
gtk-vnc:gtk-vnc2
gtkglext:
gtkhotkey:
gtkhtml:
gtkimageview:
gtkmathview:
gtkmm-documentation:
gtkmm-documentation|2.90:gtkmm2-documentation
gtkmm:gtkmm4
gtkmm|3.89:gtkmm3
gtkmm|2.90:gtkmm2
gtkpbbuttons:
gtkpod:
gtksourceview|1.9:gtksourceview18
gtksourceview|2.90:gtksourceview2
gtksourceview|3.90:gtksourceview
gtksourceview|4.90:gtksourceview4
gtksourceview:gtksourceview5
gtksourceviewmm:
gtksourceviewmm|2.90:gtksourceviewmm2
gtkspell:
gtranslator:
guake:
gucharmap:
gupnp-av:
gupnp-dlna:
gupnp-igd:
gupnp-tools:
gupnp-ui:
gupnp:
gurlchecker:
gvfs:
gwenhywfar:
gwget:
gwibber:
gypsy:
gzip:
harfbuzz:
hamster-applet:
hamster-time-tracker:
hicolor-icon-theme:
hippo-canvas:
hitori:
hotssh:
ht:
hxtools:
hyena:
iagno:
ibus:
ibus-anthy:
ibus-chewing:
ibus-gjs:
ibus-googlepinyin:
ibus-hangul:
ibus-input-pad:
ibus-m17n:
ibus-pinyin:
ibus-qt:
ibus-rime:
ibus-sunpinyin:
ibus-table:
ibus-table-chinese:
ibus-table-extraphrase:
ibus-table-jyutping:
ibus-table-others:
ibus-table-zhengma:
ibus-table-zhuyin:
ibus-table-ziranma:
ibus-unikey:
iceauth:
ico:
icon-naming-utils:
iio-sensor-proxy:
ima-evm-utils:
imake:
inkscape:
input-pad:
intel-gpu-tools:
intltool:
json-glib:
ipod-sharp:
iputils:
iproute2:
iptables:
iptraf-ng:iptraf
irda-utils:irda
iso-codes:
istanbul:
itstool:
iwatch:
jhbuild:
json-glib:
jsonrpc-glib:
kcm-fcitx:
kimtoy:
krb5-auth-dialog:
kye:
kyotocabinet:
lasem:
latexila:
lbxproxy:
ldtp:
libFS:
libHX:
libICE:
libIDL:libidl
libSM:
libWindowsWM:
libX11:
libXScrnSaver:
libXTrap:
libXau:
libXaw:
libXcomposite:
libXcursor:
libXdamage:
libXdmcp:
libXevie:
libXext:
libXfixes:
libXfont:
libXfontcache:
libXft:
libXi:
libXinerama:
libXmu:
libXp:
libXpm:
libXprintAppUtil:
libXprintUtil:
libXrandr:
libXrender:
libXres:
libXt:
libXtst:
libXv:
libXvMC:
libXxf86dga:
libXxf86misc:
libXxf86vm:
libadwaita:
libao-pulse:
libart_lgpl:
libassuan:
libatasmart:
libatomic_ops:
libbeagle:
libbonobo:
libbonoboui:
libbraille:
libbs2b:
libbtctl:
libcanberra:
libchamplain:
libchewing:
libcompizconfig:
libcroco:
libcryptui:
libdaemon:
libdatrie:
libdmapsharing:
libdmx:
libdrm:
libdv:
libebml:
libedit:
libepc:
libepoxy:
libesmtp:
libfontenc:
libgadu:
libgail-gnome:
libgalago-gtk:
libgalago:
libgames-support:
libgcrypt:
libgda:
libgda|3.99:libgda3
libgdamm:
libgdata:
libdazzle:
libgee:
libgexiv2:
libggz:ggz
libghttp:
libgit2:
libgit2-glib:
libglade:libglade2
libglademm:
libgnome-keyring:
libgnome-media-profiles:
libgnome:
libgnomecanvas:
libgnomecanvasmm:
libgnomecups:
libgnomedb:
libgnomedb|3.99:libgnomedb3
libgnomekbd:
libgnomemm:
libgnomeprint:
libgnomeprintui:
libgnomesu:
libgnomeui:
libgnomeuimm:
libgooglepinyin:
libgovirt:
libgpg-error:
libgpod:
libgrss:
libgsasl:
libgsf:
libgssh:
libgsystem:
libgtkhtml:
libgtksourceviewmm:
libgtop:
libgudev:
libgusb:
libgweather:
libgxps:
libhandy:
libhangul:
libical:
libical-glib:
libinfinity:
libiptcdata:
libjingle:
libksba:
liblbxutil:
liblouis:
liblouis:python-louis
libmatroska:
libmbim:
libmcs:
libmediaart:
libmnl:
libmodman:
libmowgli:
libnma:
libnetfilter_conntrack:
libnetfilter_log:
libnetfilter_queue:
libnfnetlink:
libnice:
libnjb:
libnotify:
libofetion:
libofx:
liboil:
liboldX:
libopenraw:
libosinfo:
libpanelappletmm:
libpciaccess:
libpeas:
libpinyin:
libplist:
libproxy:
libproxy:libproxy-plugins
libpst:
libpwquality:
libqmi:
librep:
librime:
librsvg:
libsecret:
libsexy:
libsigc++:libsigc++3
libsigc++|2.99:libsigc++2
libsigc++|1.3:libsigc++12
libslab:
libsocialweb:
libsoup:
libspectre:
libtasn1:
libtelepathy:
libthai:
libturpial:
libunique:
libunique|2:libunique1
libvirt-cim:
libvirt-glib:
libvirt:
libvpx:
libwacom:
libwebp:
libwnck:
libwnck|2.90:libwnck2
libxcb:
libxkbcommon:
libxkbfile:
libxkbui:
libxklavier:
libxml++:
libxml:
libzapojit:
libzeitgeist:
liferea:
lightsoff:
link-grammar:
listres:
lmms:
lndir:
loudmouth:
m4:
m17n-contrib:
m17n-db:
m17n-lib:
mail-notification:
makedepend:
mangler:
md5deep:
media-explorer:
media-player-info:
meld:
memphis:
memprof:
mergeant:
metacity:
metatheme-Sonar:gtk2-metatheme-sonar
metatheme-gilouche:gtk2-metatheme-gilouche
mercurial:
mkcomposecache:
mkfontdir:
mkfontscale:
mkvtoolnix:
moc:
mobile-broadband-provider-info:
mod_dnssd:apache2-mod_dnssd
monsoon:
moserial:
mousetweaks:
mozilla-bonobo:
mbpurple:
mrim-prpl:pidgin-mrim
mtr:
muine:
murrine:gtk2-engine-murrine
mutter:
mutter-wayland:
mx:
nautilus-actions:
nautilus-cd-burner:
nautilus-open-terminal:
nautilus-python:python-nautilus
nautilus-search-tool:
nautilus-sendto:
nautilus-share:
nautilus-terminal:
nautilus:
nemiver:
neon:
net6:
netspeed_applet:gnome-netspeed-applet
network-manager-applet:NetworkManager-applet
nfs-utils:
nimbus:gtk2-metatheme-nimbus
njb-sharp:
notification-daemon:
notify-osd:
notify-python:python-notify
npth:
nspluginwrapper:
nss-mdns:
ntfs-3g_ntfsprogs:ntfs-3g
ntfs-3g_ntfsprogs:ntfs-3g_ntfsprogs
ntfs-config:
nuntius:
obby:
obex-data-server:
oclock:
onboard:
online-desktop:
opal:
opencc:
openfetion:
openobex:
opensuse-font-fifth-leg:fifth-leg-font
opus:
orc:
orca:
ori:
osm-gps-map:
ostree:
p11-kit:
padevchooser:
pam_mount:
paman:
pango:
pangomm:
pangomm|2.47:pangomm1_4
pangox-compat:
paprefs:
papyon:
pavucontrol:
pavuk:
pavumeter:
pcre:
pdfmod:
pessulus:
phodav:
pidgin-advanced-sound-notification:
pidgin-birthday-reminder:
pidgin-embeddedvideo:
pidgin-facebookchat:
pidgin-guifications:
pidgin-openfetion:
pidgin-otr:
pidgin-sipe:
pidgin:
pinentry:
pino:
pinpoint:
pipewire:
pithos:
pitivi:
pixman:
pkg-config:
planner:
polari:
polkit-gnome:
polkit:
poppler-data:
poppler:
posixovl:
postr:
powerpc-utils:
ppc64-diag:
presage:
procmeter3:procmeter
proxymngr:
psiconv:
psmisc:
ptlib:libpt2
pulseaudio:
purple-plugin-pack:
py2cairo:python-cairo
pyatspi:python-atspi
pyatspi:python3-atspi
pycairo:python3-cairo
pycups:python-cups
pygobject:python-gobject
pygobject:python3-gobject
pygobject|2.29:python-gobject2
pygobject|2.29:python3-gobject2
pygoocanvas:python-goocanvas
pygtk:python-gtk
pygtkglext:python-gtkglext
pygtksourceview:python-gtksourceview
pymetar:python-pymetar
pymsn:python-msn
pyorbit:python-orbit
pypoppler:
pysmbc:python-smbc
python-distutils-extra:
python-espeak:
python-xlib:
pywebkitgtk:python-webkitgtk
pyxdg:python-xdg
qiv:
qmmp:
quadrapassel:
quilt:
radiotray:
raptor:
rarian:
rasqal:
raw-thumbnailer:
recordmydesktop:
redland:
rednotebook:
remmina:Remmina
rendercheck:
rep-gtk:
rest:librest
retro-gtk:
rgb:
rhythmbox:
rlwrap:
rstart:
rygel:
sabayon:
sawfish:
schismtracker:
schroedinger:
scim:
scim-anthy:
scim-bridge:
scim-canna:
scim-chewing:
scim-hangul:
scim-input-pad:
scim-m17n:
scim-pinyin:
scim-qtimm:
scim-skk:
scim-sunpinyin:
scim-tables:
scim-tomoe:
scim-uim:
scim-unikey:
scrollkeeper:
scummvm:
seahorse-nautilus:
seahorse-plugins:
seahorse-sharing:
seahorse:
seed:
seed:seed2
scripts|xorg:xorg-scripts
sessreg:
setxkbmap:
shared-color-profiles:
shared-color-targets:
shared-desktop-ontologies:
shared-mime-info:
shotwell:
showfont:
simple-ccsm:
simple-scan:
smproxy:
smuxi:
snappy:snappy-player
sobby:
sofia-sip:
solang:
sound-juicer:
sound-theme-freedesktop:
sparkleshare:
specto:
speech-dispatcher:
spheres-and-crystals:gtk2-metatheme-spheres-and-crystals
spice-gtk:
spice-protocol:
spice:
ssh-contact:
sshfp:
startup-notification:
stk:
sunpinyin:
sushi:
swell-foop:
swfdec-gnome:
swfdec-mozilla:
swfdec:
swig:
synapse:
sysprof:
system-config-printer:
tali:
tangerine:
tango-icon-theme:
tasks:
tasque:
telegnome:
telepathy-butterfly:
telepathy-farsight:
telepathy-farstream:
telepathy-gabble:
telepathy-glib:
telepathy-haze:
telepathy-idle:
telepathy-logger:
telepathy-mission-control:
telepathy-python:python-telepathy
telepathy-rakia:
telepathy-salut:
telepathy-sofiasip:
telepathy-stream-engine:
template-glib:
tepl:
the-board:
tig:
tilda:
tinyproxy:
tokyocabinet:
tomboy:
totem-pl-parser:
totem:
tracker:
tracker-miners:
traffic-vis:
transmageddon:
transmission:
tsclient:
turpial:
twitux:
twm:
udisks:udisks2
udisks|1.90:udisks
uget:
uhttpmock:
ulogd:ulogd2
unico:
update-desktop-files:
upower:
usbredir:
usbview:
vala:
vala:vala-unstable
vala|0.13:vala-0_12
valencia:
varnish:
vboxgtk:
viewres:
vim:
vinagre:
vino:
virtkey:python-virtkey
vmfs-tools:
vobject:
vte:
vte|0.29:vte2
wadptr:
weather-wallpaper:
webkitgtk|2.4:libwebkit
webkitgtk|2.4:libwebkit3
webkitgtk|2.4:webkitgtk
webkitgtk|2.4:webkitgtk3
webkitgtk:webkit2gtk3
wmakerconf:
x-tile:
x11perf:
x11vnc:
xauth:
xbacklight:
xbiff:
xbitmaps:
xcalc:
xcb-util-image:
xcb-util-keysyms:
xcb-util-renderutil:
xcb-util-wm:
xcb-util:
xchat-gnome:
xchat:
xclipboard:
xclock:
xcmsdb:
xcompmgr:
xconsole:
xcursor-themes:
xcursorgen:
xdbedizzy:
xdelta:
xdg-app:
xdg-desktop-portal:
xdg-desktop-portal-gnome:
xdg-desktop-portal-gtk:
xdg-user-dirs-gtk:
xdg-user-dirs:
xdg-utils:
xditview:
xdm:
xdpyinfo:
xedit:
xev:
xeyes:
xf86dga:
xf86-input-evdev:
xf86-input-joystick:
xf86-input-keyboard:
xf86-input-libinput:
xf86-input-mouse:
xf86-input-synaptics:
xf86-input-vmmouse:
xf86-input-void:
xf86-input-wacom:
xf86-video-ark:
xf86-video-ast:
xf86-video-ati:
xf86-video-cirrus:
xf86-video-dummy:
xf86-video-fbdev:
xf86-video-geode:
xf86-video-glint:
xf86-video-i128:
xf86-video-intel:
xf86-video-ivtv:xorg-x11-driver-video-ivtv
xf86-video-mach64:
xf86-video-mga:
xf86-video-neomagic:
xf86-video-newport:
xf86-video-nv:
xf86-video-qxl:
xf86-video-r128:
xf86-video-radeonhd:xorg-x11-driver-video-radeonhd
xf86-video-savage:
xf86-video-siliconmotion:
xf86-video-sis:
xf86-video-tdfx:
xf86-video-tga:
xf86-video-trident:
xf86-video-v4l:
xf86-video-vesa:
xf86-video-vmware:
xf86-video-voodoo:
xfd:
xfindproxy:
xfontsel:
xfs:
xfsdump:
xfsinfo:
xfsprogs:
xfwp:
xgamma:
xgc:
xhost:
xinit:
xinput:
xkbcomp:
xkbevd:
xkbprint:
xkbutils:
xkeyboard-config:
xkill:
xload:
xlogo:
xlsatoms:
xlsclients:
xlsfonts:
xmag:
xman:
xmessage:
xmh:
xmodmap:
xmore:
xorgxrdp:
xorg-cf-files:
xorg-docs:
xorg-sgml-doctools:
xosd:
xplsprinters:
xpr:
xprehashprinterlist:
xprop:
xrandr:
xrdb:
xrdp:
xrefresh:
xrestop:
xrx:
xsane:
xscope:
xset:
xsetmode:
xsetpointer:
xsetroot:
xsm:
xstdcmap:
xtables-addons:
xtrans:
xtrap:
xvidtune:
xvinfo:
xwd:
xwininfo:
xwud:
xzgv:
yaml-cpp:
yelp-tools:
yelp-xsl:
yelp:
zeitgeist-datahub:
zeitgeist:
zenity:
zim:
zlib:
##
## Packages with issues when tracking
##
## The way libnl is packaged is a bit too complex since it depends on the version
# libnl:
##
## Package with no upstream page anymore
##
# dopi:
##
## Packages where we are upstream
## It's not required to list -branding-{openSUSE,SLED} packages.
##
beagle-index:
build-compare:
bundle-lang-common:
bundle-lang-gnome:
bundle-lang-gnome-extras:
bundle-lang-kde:
bundle-lang-other:
desktop-data-openSUSE:
desktop-data-SLED:
dynamic-wallpapers-11x:
ggreeter:
gnome-patch-translation:
gnome-shell-search-provider-openSUSE-packages:
gos-wallpapers:
gtk2-themes:
libsolv:
libzypp:
libzypp-bindings:
libzypp-testsuite-tools:
metacity-themes:
opt_gnome-compat:
tennebon-dynamic-wallpaper:
translation-update:
yast2:
yast2-control-center-gnome:
zypp-plugin:
zypper:
##
## Packages that we removed
##
# clutter-cairo:
# fast-user-switch-applet:
# gnome-cups-manager:
# gnome-volume-manager:
# gst-pulse:gstreamer-0_10-pulse
# last-exit:
# libssui:
# libsvg:
# libsvg-cairo:
## This is the old mission-control. Not needed anymore (unless we want to package the 4.x branch)
# mission-control:telepathy-mission-control
# pysqlite:python-sqlite2
## TODO (should get packaged):
#librsvgmm:
## Stuff on ftp.gnome.org we don't handle
#GConf-dbus:
#GSAPI:
#GnomeHello:
#Guppi:
#abi:
#abispell:
#accountsdialog:
#acme:
#alleyoop:
#ammonite:
#anjal:
#aravis:
#at-poke:
#banter:
#battfink:
#billreminder:
#blam:
#bonobo:
#bonobo-activation:
#bonobo-conf:
#bonobo-config:
#cairo-java:
#camorama:
#capuchin:
#capuchin-glib:
#chronojump:
#clutter-box2dmm:
#clutter-cairomm:
#clutter-gtkmm:
#cluttermm:
#cluttermm_tutorial:
#contacts:
#control-center:
#control-center-plus:
#couchdb-glib:
#crux:
#dates:
#deskscribe:
#discident-glib:
#dots:
#dryad:
#ease:
#easytag:
#ee:
#eggcups:
#evolution-activesync:
#evolution-caldav:
#evolution-couchdb:
#evolution-data-server-dbus:
#evolution-jescs:
#evolution-kolab:
#evolution-scalix:
#firestarter:
#fontilus:
#fpm:
#g-print:
#gASQL:
#gDesklets:
#gabber:
#gal:
#galeon:
#gamin:
#garnome:
#gazpacho:
#gb:
#gedit2:
#gegl-vala:
#geglmm:
#gevice:
#gfax:
#gfloppy:
#gget:
#ggv:
#gide:
#gio-standalone:
#glibwww:
#glimmer:
#glom-postgresql-setup:
#gmdns:
#gmf:
#gmime:
#gnome-admin:
#gnome-boxes-nonfree:
#gnome-braille:
#gnome-chart:
#gnome-core:
#gnome-crash:
#gnome-db:
#gnome-debug:
#gnome-desktop-testing:
#gnome-file-selector:
#gnome-getting-started-docs:
#gnome-gpg:
#gnome-guile:
#gnome-hello:
#gnome-initial-setup:
#gnome-jabber:
#gnome-keyring-manager:
#gnome-launch-box:
#gnome-linuxconf:
#gnome-lirc-properties:
#gnome-lokkit:
#gnome-mud:
#gnome-nds-thumbnailer:
#gnome-netinfo:
#gnome-network:
#gnome-objc:
#gnome-perfmeter:
#gnome-pim:
#gnome-print:
#gnome-specimen:
#gnome-vfs-extras:
#gnome-video-arcade:
#gnome-xcf-thumbnailer:
#gnome2-user-docs:
#gnome_js_common:
#gnome_speech:
#gnomemeeting:
#gnomemm:
#gnomemm-all:
#gnomemm_hello:
#gnomoku:
#gnorpm:
#gnotepad+:
#gob:
#googlizer:
#gopersist:
#gpdf:
#gst-plugins:
#gstreamermm:
#gswitchit-plugins:
#gswitchit_plugins:
#gtk-css-engine:
#gtk-mac-bundler:
#gtk-mac-integration:
#gtk-theme-engine-clearlooks:
#gtk-thinice-engine:
#gtkglarea:
#gtkglextmm:
#gtkmm_hello:
#gtkmozedit:
#gtkmozembedmm:
#gtm:
#gtop:
#gturing:
#guile-gobject:
#gupnp-vala:
#guppi:
#gwt-glom:
#gxml:
#gyrus:
#hipo:
#imlib:
#iogrind:
#jamboree:
#java-access-bridge:
#java-atk-wrapper:
#java-gnome:
#java-libglom:
#kbdraw:
#kiwi:
#libPropList:
#libbonobomm:
#libbonobouimm:
#libcapplet:
#libccc:
#libcm:
#libeds-java:
#libgda-uimm:
#libgnetwork:
#libgnome2:
#libgnomecanvas2:
#libgnomecompat2:
#libgnomedbmm:
#libgnomefilesel:
#libgnomeprintmm:
#libgnomeprintuimm:
#libgnomeui2:
#libgtcpsocket:
#libgtkhtml-java:
#libgtkmozembed-java:
#libgtkmusic:
#libmrproject:
#libnotifymm:
#libole2:
#libqmi:
#libunicode:
#libvte-java:
#libvtemm:
#libxml2:
#libxslt:
#libzvt:
#libzvt2:
#linc:
#linux-user-chroot:
#lock-service:
#longomatch:
#loudmouth-ruby:
#lsr:
#magicdev:
#mc:
#medusa:
#mess-desktop-entries:
#metatheme:
#mlview:
#model:
#mrproject:
#msitools:
#nanny:
#nautilus-gtkhtml:
#nautilus-image-converter:
#nautilus-media:
#nautilus-mozilla:
#nautilus-rpm:
#network-manager-netbook:
#oaf:
#ocrfeeder:
#office-runner:
#ontv:
#opengl-glib:
#orbit-python:
#orbitcpp:
#pan:
#panelmm:
#paperbox:
#passepartout:
#pkgconfig:
#pong:
#postr:
#prefixsuffix:
#present:
#printman:
#procman:
#pwlib:
#pybliographer:
#pygda:
#pygi:
#pygtk2reference:
#pyphany:
#quick-lounge-applet:
#radioactive:
#regexxer:
#rep-gtk-gnome2:
#rygel-gst-0-10-fullscreen-renderer:
#rygel-gst-0-10-media-engine:
#rygel-gst-0-10-plugins:
#sapwood:
#sawfish-gnome2:
#scaffold:
#siobhan:
#snowy:
#sodipodi:
#soup:
#straw:
#strongwind:
#system-tools-backends:
#system-tray-applet:
#telegnome:
#themus:
#toutdoux:
#trilobite:
#ttf-bitstream-vera:
#update-manager:
#users-guide:
#xalf:
#ximian-connector:
#ximian-setup-tools:
#xml-i18n-tools:
## Added to ftp.gnome.org recently
#glick2:
# Other gnome-related upstreams with no package in openSUSE
#gnome-desktop-sharp:
#gnome-system-tools:
#liboobs:
#mm-common:
#glib-java:
#libgconf-java:
#libglade-java:
#libgnome-java:
#libgtk-java:
#atomix:
#gnome-scan:
#gossip:
#labyrinth:
## perl bindings
#Gnome2-Canvas:
#Gnome2-GConf:
#Gnome2-VFS:
#Gnome2:
#Gtk2-GladeXML:
07070100000033000081A40000000000000000000000016548EB8C0000C871000000000000000000000000000000000000004100000000osc-plugin-collab-0.104+30/server/upstream/upstream-tarballs.txt# Format of this file:
# name:method:info
#
# where:
# + name is the upstream module name found in tarball names
# (it can contain a branch name after the '|' character, for example:
# 'gobby|0.4')
# + method is one of 'upstream', 'ftpls', 'httpls', 'dualhttpls',
# 'subdirhttpls', 'sf', 'google', 'lp'
# + info depends on method
#
# upstream:
# + use this when openSUSE is upstream for the package
# + the info field is ignored
#
# ftpls:
# + use this when you only have a ftp directory listing all tarballs
# + the info field should be the URL of the ftp directory
#
# httpls:
# + use this when you only have a single web page listing all tarballs
# Note that we support real HTML pages (and not just 'listing' pages)
# and that a link to the last tarball on this page is generally enough.
# + the info field should be the URL of the web page
#
# dualhttpls:
# + use this when there are two web pages listing all tarballs (usually
# happens when there's a releases directory and a snapshots directory)
# + the info field should be both URL separated by a pipe
#
# subdirhttpls:
# + use this when there are subdirectories to browse find the latest version
# The subdirectories should always be made of version numbers only
# + the info field should be the root URL containing all those subdirectories
#
# svnls:
# + use this when you only have a svn server listening on port 80 listing all
# tarballs. An example is:
# https://svn.revolutionlinux.com/MILLE/XTERM/trunk/libflashsupport/Tarballs/
# + the info field should be the URL of the svn directory
#
# sf:
# + use this when the upstream tarballs are hosted on sourceforge
# + the info field should be the project id in sourceforge
# It can also contain the path for sourceforge projects using
# packages. In this case, the ids should be separated by a pipe:
# project_id|path
# + Project ID can be found by going to http://sourceforge.net/rest/p/<project_name>?doap
# (i.e. http://sourceforge.net/rest/p/swig?doap), and searching for the <sf:id> field
#
# sf_jp:
# + use this when the upstream tarballs are hosted on sourceforge.jp
# + the info field should be the project in sourceforge.jp
#
# google:
# + use this when the upstream tarballs are hosted on code.google.com
# + the info field should be the project name on code.google.com. It can
# also contain the name of tarballs if it's different from the project
# name. In this case, the names should be separated by a pipe:
# project_name|tarball_name
#
# lp:
# + use this when the upstream tarballs are hosted on launchpad
# + the info field should be the project name on launchpad
#
# trac:
# + use this when the upstream tarballs are hosted on trac, with a download page
# + the info field should be the URL of the trac download page
#
# Note that you need to add a line in upstream-packages-match.txt for each
# module you add here. The line should go in the "Non ftp.gnome.org stuff"
# section or in the "Packages where we are upstream" section.
# Please keep the list alphabetically sorted
Coherence:httpls:http://coherence.beebits.net/download/
ConsoleKit:httpls:http://www.freedesktop.org/software/ConsoleKit/dist/
CouchDB:httpls:http://pypi.python.org/pypi/CouchDB
DeviceKit-disks:httpls:http://hal.freedesktop.org/releases/
DeviceKit-power:httpls:http://upower.freedesktop.org/releases/
DeviceKit:httpls:http://hal.freedesktop.org/releases/
LaTeXPlugin:sf:204144
LibRaw:httpls:http://www.libraw.org/download
NetworkManager-strongswan:httpls:http://download.strongswan.org/NetworkManager/
PackageKit:httpls:http://www.freedesktop.org/software/PackageKit/releases/
PackageKit-Qt:httpls:http://www.freedesktop.org/software/PackageKit/releases/
PolicyKit-gnome:httpls:http://hal.freedesktop.org/releases/
QtCurve-Gtk2:httpls:http://www.kde-look.org/content/download.php?content=40492&id=3
UPnP-Inspector:httpls:http://coherence.beebits.net/download/
abiword-docs:httpls:http://abisource.com/downloads/abiword/latest/source/
abiword:httpls:http://abisource.com/downloads/abiword/latest/source/
accountsservice:httpls:http://www.freedesktop.org/software/accountsservice/
acpid:httpls:http://tedfelix.com/linux/acpid-netlink.html
aegisub:httpls:http://ftp.aegisub.org/pub/releases/
alarm-clock-applet:lp:alarm-clock
anthy:sf_jp:anthy
apache-couchdb:httpls:http://couchdb.apache.org/downloads.html
appdata-tools:httpls:http://people.freedesktop.org/~hughsient/releases/
appres:httpls:http://xorg.freedesktop.org/releases/individual/app/
appstream-glib:httpls:http://people.freedesktop.org/~hughsient/appstream-glib/releases/
aqbanking:httpls:http://www.aquamaniac.de/sites/download/packages.php?showall=1
arista:httpls:http://www.transcoder.org/downloads/
arping:httpls:http://www.habets.pp.se/synscan/files/
asio:sf:122478|asio
atheme-services:httpls:http://atheme.net/downloads/
autocutsel:httpls:http://download.savannah.gnu.org/releases/autocutsel/
autofs:httpls:https://kernel.org/pub/linux/daemons/autofs/v5/
avahi:httpls:http://avahi.org/download/
babl:subdirhttpls:http://ftp.gtk.org/pub/babl/
bakefile:sf:83016|bakefile
balsa:httpls:http://pawsa.fedorapeople.org/balsa/download.html
banshee-community-extensions:subdirhttpls:http://download.banshee.fm/banshee-community-extensions/
bash-completion:httpls:http://bash-completion.alioth.debian.org/files/
bdftopcf:httpls:http://xorg.freedesktop.org/releases/individual/app/
beforelight:httpls:http://xorg.freedesktop.org/releases/individual/app/
bfsync:httpls:http://space.twc.de/~stefan/bfsync/
bindfs:httpls:http://bindfs.org/downloads/
bitmap:httpls:http://xorg.freedesktop.org/releases/individual/app/
blktrace:httpls:http://brick.kernel.dk/snaps/
blueproximity:sf:203022
bombermaze:sf:8614|bombermaze
bot-sentry:sf:156021|bot-sentry
brltty:httpls:http://mielke.cc/brltty/releases/
c++-gtk-utils:sf:277143|cxx-gtk-utils
c-ares:httpls:http://c-ares.haxx.se/download/
cachefilesd:httpls:http://people.redhat.com/~dhowells/fscache/
cairo-clock:httpls:http://macslow.net/?page_id=23
cairo-compmgr:httpls:http://download.tuxfamily.org/ccm/cairo-compmgr/
cairo:dualhttpls:http://cairographics.org/snapshots/|http://cairographics.org/releases/
cairomm:dualhttpls:http://cairographics.org/snapshots/|http://cairographics.org/releases/
ccgfs:sf:207310
ccsm:subdirhttpls:http://releases.compiz.org/components/ccsm/
cdecl:httpls:http://www.gtlib.cc.gatech.edu/pub/Linux/devel/lang/c/
cdfs:httpls:https://users.elis.ugent.be/~mronsse/cdfs/download/
check:sf:28255|check
cherrytree:httpls:http://www.giuspen.com/software/
chmlib:httpls:http://www.jedrea.com/chmlib/
chmsee:google:chmsee
claws-mail-extra-plugins:sf:25528|extra plugins
claws-mail:sf:25528|Claws Mail
cloop:httpls:http://debian-knoppix.alioth.debian.org/packages/cloop/
cmsfs:httpls:http://www.linuxvm.org/Patches
cmuclmtk:sf:1904|cmuclmtk
colorblind:httpls:https://alioth.debian.org/frs/?group_id=31117
colord:httpls:http://www.freedesktop.org/software/colord/releases/
colord-gtk:httpls:http://www.freedesktop.org/software/colord/releases/
comix:sf:146377
compiz-bcop:subdirhttpls:http://releases.compiz.org/components/compiz-bcop/
compiz-fusion-plugins-extra:subdirhttpls:http://releases.compiz.org/components/plugins-extra/
compiz-fusion-plugins-main:subdirhttpls:http://releases.compiz.org/components/plugins-main/
compiz-fusion-plugins-unsupported:subdirhttpls:http://releases.compiz.org/components/plugins-unsupported/
compiz:subdirhttpls:http://releases.compiz.org/core/
compizconfig-backend-gconf:subdirhttpls:http://releases.compiz.org/components/compizconfig-backend-gconf/
compizconfig-backend-kconfig:subdirhttpls:http://releases.compiz.org/components/compizconfig-backend-kconfig/
compizconfig-python:subdirhttpls:http://releases.compiz.org/components/compizconfig-python/
computertemp:httpls:http://computertemp.berlios.de/download.php
conglomerate:sf:82766|Conglomerate XML Editor
conntrack-tools:httpls:http://ftp.netfilter.org/pub/conntrack-tools/
cromfs:httpls:http://bisqwit.iki.fi/source/cromfs.html
csmash:sf:4179|CannonSmash
cups-pk-helper:httpls:http://www.freedesktop.org/software/cups-pk-helper/releases/
davfs2:httpls:http://download.savannah.gnu.org/releases/davfs2/
dbus-glib:httpls:http://dbus.freedesktop.org/releases/dbus-glib/
dbus:httpls:http://dbus.freedesktop.org/releases/dbus/
dd_rescue:httpls:http://garloff.de/kurt/linux/ddrescue/
ddrescue:httpls:http://ftp.gnu.org/gnu/ddrescue/
decibel-audio-player:httpls:http://decibel.silent-blade.org/index.php?n=Main.Download
dee:lp:dee
deja-dup:lp:deja-dup
desktop-file-utils:httpls:http://www.freedesktop.org/software/desktop-file-utils/releases/
desktopcouch:lp:desktopcouch
devilspie:httpls:http://www.burtonini.com/computing/
diffutils:httpls:http://ftp.gnu.org/gnu/diffutils/
ding:httpls:http://ftp.tu-chemnitz.de/pub/Local/urz/ding/
djvulibre:sf:32953|DjVuLibre
dmapi:ftpls:ftp://oss.sgi.com/projects/xfs/cmd_tars/
dmlangsel:google:loolixbodes|dmlangsel
docky:lp:docky
dwarves:httpls:http://fedorapeople.org/~acme/dwarves/
ed:httpls:http://ftp.gnu.org/gnu/ed/
editres:httpls:http://xorg.freedesktop.org/releases/individual/app/
eds-feed:httpls:http://www.galago-project.org/files/releases/source/eds-feed/
efax-gtk:sf:109982|efax-gtk
eiciel:httpls:http://rofi.roger-ferrer.org/eiciel/download/
emacs:ftpls:ftp://ftp.gnu.org/gnu/emacs/
emerald:subdirhttpls:http://releases.compiz.org/components/emerald/
enchant:subdirhttpls:http://www.abisource.com/downloads/enchant/
espeak-gui:lp:espeak-gui
evolution-rss:httpls:http://gnome.eu.org/index.php/Evolution_RSS_Reader_Plugin
evolution-tray:httpls:http://gnome.eu.org/index.php/Evolution_Tray
exempi:httpls:http://libopenraw.freedesktop.org/download/
exiv2:httpls:http://www.exiv2.org/download.html
farsight2:httpls:http://farsight.freedesktop.org/releases/farsight2/
farsight:httpls:http://farsight.freedesktop.org/releases/obsolete/farsight/
farstream:httpls:http://freedesktop.org/software/farstream/releases/farstream/
fcitx:google:fcitx
fcitx-anthy:google:fcitx|fcitx-anthy
fcitx-chewing:google:fcitx|fcitx-chewing
fcitx-cloudpinyin:google:fcitx|fcitx-cloudpinyin
fcitx-configtool:google:fcitx|fcitx-configtool
fcitx-fbterm:google:fcitx|fcitx-fbterm
fcitx-googlepinyin:google:fcitx|fcitx-googlepinyin
fcitx-hangul:google:fcitx|fcitx-hangul
fcitx-libpinyin:google:fcitx|fcitx-libpinyin
fcitx-m17n:google:fcitx|fcitx-m17n
fcitx-rime:google:fcitx|fcitx-rime
fcitx-sayura:google:fcitx|fcitx-sayura
fcitx-sunpinyin:google:fcitx|fcitx-sunpinyin
fcitx-table-extra:google:fcitx|fcitx-table-extra
fcitx-table-other:google:fcitx|fcitx-table-other
fcitx-ui-light:google:fcitx|fcitx-ui-light
fcitx-unikey:google:fcitx|fcitx-unikey
file-shrimp:google:loolixbodes|file-shrimp
fillmore-lombard:subdirhttpls:http://yorba.org/download/media/
flickrapi:httpls:http://pypi.python.org/pypi/flickrapi
font-util:httpls:http://xorg.freedesktop.org/releases/individual/font/
fonttosfnt:httpls:http://xorg.freedesktop.org/releases/individual/app/
freerdp:httpls:https://github.com/FreeRDP/FreeRDP/releases
freetype:httpls:http://download.savannah.gnu.org/releases/freetype/
frei0r-plugins:httpls:http://www.piksel.no/frei0r/releases/
fslsfonts:httpls:http://xorg.freedesktop.org/releases/individual/app/
fstobdf:httpls:http://xorg.freedesktop.org/releases/individual/app/
fyre:httpls:http://releases.navi.cx/fyre/
g-wrap:httpls:http://download.savannah.gnu.org/releases/g-wrap/
gaim-galago:httpls:http://www.galago-project.org/files/releases/source/gaim-galago/
galago-daemon:httpls:http://www.galago-project.org/files/releases/source/galago-daemon/
galago-gtk-sharp:httpls:http://www.galago-project.org/files/releases/source/galago-gtk-sharp/
galago-sharp:httpls:http://www.galago-project.org/files/releases/source/galago-sharp/
gbrainy:httpls:http://live.gnome.org/gbrainy
gccmakedep:httpls:http://xorg.freedesktop.org/releases/individual/util/
gcin:httpls:http://www.csie.nctu.edu.tw/~cp76/gcin/download/
gcstar:httpls:http://download.gna.org/gcstar/
gdata.py:google:gdata-python-client|gdata
gdesklets:httpls:http://gdesklets.de/
gdome2:httpls:http://gdome2.cs.unibo.it/tarball/
geany-plugins:httpls:http://plugins.geany.org/geany-plugins/
geany:httpls:http://download.geany.org/
geeqie:sf:222125
gegl:subdirhttpls:http://ftp.gtk.org/pub/gegl/
geoclue:httpls:http://www.freedesktop.org/wiki/Software/GeoClue
geoclue|1.0:httpls:http://people.freedesktop.org/~hadess/
gftp:httpls:http://gftp.seul.org/
ggz-client-libs:subdirhttpls:http://mirrors.ibiblio.org/ggzgamingzone/ggz/
gimp-dds:google:gimp-dds
gimp-help:ftpls:ftp://ftp.gimp.org/pub/gimp/help/
gimp-lqr-plugin:httpls:http://liquidrescale.wikidot.com/en:download-page-sources
gimp-save-for-web:httpls:http://registry.gimp.org/node/33
gimp:ftpls:ftp://ftp.gimp.org/pub/gimp/stable
git:google:git-core|git
giver:google:giver
gkrellm:httpls:http://members.dslextreme.com/users/billw/gkrellm/gkrellm.html
glabels:sf:46122|glabels
glew:sf:67586|glew
glipper:lp:glipper
glitz:dualhttpls:http://cairographics.org/snapshots/|http://cairographics.org/releases/
gmetadom:sf:40627|gmetadom
gnac:sf:193628|gnac
gnokii:httpls:http://www.gnokii.org/download/gnokii/
gnome-activity-journal:lp:gnome-activity-journal
gnome-color-chooser:sf:211146|gnome-color-chooser
gnome-colors:google:gnome-colors
gnome-do-plugins:lp:do-plugins
gnome-do:lp:do
gnome-gmail-notifier:google:gnome-gmail-notifier
gnome-gmail:sf:277145
gnome-keyring-sharp:httpls:http://download.mono-project.com/sources/gnome-keyring-sharp/
gnome-mount:httpls:http://hal.freedesktop.org/releases/
gnome-presence-applet:httpls:http://www.galago-project.org/files/releases/source/gnome-presence-applet/
gnome-schedule:sf:112183
gnome-subtitles:sf:129996
gnomeicu:sf:237
gnonlin:httpls:http://gstreamer.freedesktop.org/src/gnonlin/
# gnucash: Not updated anymore: gnucash:httpls:http://www.gnucash.org/pub/gnucash/sources/stable/
gnucash:sf:192|gnucash (stable)
# gnucash-docs: Not updated anymore: gnucash-docs:httpls:http://www.gnucash.org/pub/gnucash/sources/stable/
gnucash-docs:sf:192|gnucash-docs
gnupg:ftpls:ftp://ftp.gnupg.org/gcrypt/gnupg/
gobby:httpls:http://releases.0x539.de/gobby/
gobby|0.4:httpls:http://releases.0x539.de/gobby/
gocr:httpls:http://www-e.uni-magdeburg.de/jschulen/ocr/download.html
gonvert:httpls:http://www.unihedron.com/projects/gonvert/downloads/
google-gadgets-for-linux:google:google-gadgets-for-linux
gourmet:sf:108118
gpa:ftpls:ftp://ftp.gnupg.org/gcrypt/gpa/
gparted:sf:115843
gpaste:httpls:https://github.com/Keruspe/GPaste/downloads
gpgme:ftpls:ftp://ftp.gnupg.org/gcrypt/gpgme/
gpick:google:gpick
gpodder:httpls:http://gpodder.org/src/
gq:sf:3805|GQ LDAP Client
# gqview: note that we don't care about stable vs unstable
gqview:sf:4050
gramps:sf:25770
# grisbi: we really only want stable versions
grisbi:sf:93867|grisbi stable
gromit:httpls:http://www.home.unix-ag.org/simon/gromit/
gst-plugins-bad:httpls:http://gstreamer.freedesktop.org/src/gst-plugins-bad/
gst-plugins-bad|0.10:httpls:http://gstreamer.freedesktop.org/src/gst-plugins-bad/
gst-plugins-base:httpls:http://gstreamer.freedesktop.org/src/gst-plugins-base/
gst-plugins-base|0.10:httpls:http://gstreamer.freedesktop.org/src/gst-plugins-base/
gst-plugins-farsight:httpls:http://farsight.freedesktop.org/releases/obsolete/gst-plugins-farsight/
gst-plugins-gl:httpls:http://gstreamer.freedesktop.org/src/gst-plugins-gl/
gst-plugins-good:httpls:http://gstreamer.freedesktop.org/src/gst-plugins-good/
gst-plugins-good|0.10:httpls:http://gstreamer.freedesktop.org/src/gst-plugins-good/
gst-plugins-ugly:httpls:http://gstreamer.freedesktop.org/src/gst-plugins-ugly/
gst-plugins-ugly|0.10:httpls:http://gstreamer.freedesktop.org/src/gst-plugins-ugly/
gst-python:httpls:http://gstreamer.freedesktop.org/src/gst-python/
gst-python|0.10:httpls:http://gstreamer.freedesktop.org/src/gst-python/
gst-rtsp:httpls:http://gstreamer.freedesktop.org/src/gst-rtsp/
gstreamer:httpls:http://gstreamer.freedesktop.org/src/gstreamer/
gstreamer|0.10:httpls:http://gstreamer.freedesktop.org/src/gstreamer/
gstreamer-editing-services:httpls:http://gstreamer.freedesktop.org/src/gstreamer-editing-services/
gsynaptics:sf_jp:gsynaptics
gtg:lp:gtg
gtk-engines-cleanice:sf:57808|gtk-engines-cleanice
gtk-recordmydesktop:sf:172357|gtk-recordMyDesktop
gtkglext:sf:54333|gtkglext
gtkhotkey:lp:gtkhotkey
gtkimageview:httpls:http://trac.bjourne.webfactional.com/chrome/common/releases/
gtkmathview:httpls:http://helm.cs.unibo.it/mml-widget/sources/
gtkpbbuttons:sf:47862|gtkpbbuttons
gtkpod:sf:67873|gtkpod
gtkspell:sf:7896
guake:trac:http://guake.org/downloads
gurlchecker:httpls:http://labs.libre-entreprise.org/frs/?group_id=7
gwenhywfar:httpls:http://www.aquamaniac.de/sites/download/packages.php?showall=1
gwibber:lp:gwibber
gypsy:httpls:http://gypsy.freedesktop.org/releases/
gzip:httpls:http://ftp.gnu.org/gnu/gzip/
harfbuzz:httpls:http://www.freedesktop.org/software/harfbuzz/release/
hamster-time-tracker:httpls:https://github.com/projecthamster/hamster/tags
hicolor-icon-theme:httpls:http://icon-theme.freedesktop.org/releases/
ht:sf:1066
hxtools:httpls:http://jftp.inai.de/hxtools/
ibus:google:ibus
ibus-anthy:google:ibus|ibus-anthy
ibus-chewing:google:ibus|ibus-chewing
ibus-gjs:google:ibus|ibus-gjs
ibus-googlepinyin:google:libgooglepinyin|ibus-googlepinyin
ibus-hangul:google:ibus|ibus-hangul
ibus-input-pad:google:input-pad|ibus-input-pad
ibus-m17n:google:ibus|ibus-m17n
ibus-pinyin:google:ibus|ibus-pinyin
ibus-qt:google:ibus|ibus-qt
ibus-rime:google:rimeime|ibus-rime
ibus-sunpinyin:google:sunpinyin|ibus-sunpinyin
ibus-table:google:ibus|ibus-table
ibus-table-chinese:google:ibus|ibus-table-chinese
ibus-table-extraphrase:google:ibus|ibus-table-extraphrase
ibus-table-jyutping:google:ibus|ibus-table-jyutping
ibus-table-others:google:ibus|ibus-table-others
ibus-table-zhengma:google:ibus|ibus-table-zhengma
ibus-table-zhuyin:google:ibus|ibus-table-zhuyin
ibus-table-ziranma:google:ibus|ibus-table-ziranma
ibus-unikey:google:ibus-unikey
iceauth:httpls:http://xorg.freedesktop.org/releases/individual/app/
ico:httpls:http://xorg.freedesktop.org/releases/individual/app/
icon-naming-utils:httpls:http://tango.freedesktop.org/releases/
iio-sensor-proxy:httpls:https://github.com/hadess/iio-sensor-proxy/releases
imake:httpls:http://xorg.freedesktop.org/releases/individual/util/
inkscape:sf:93438|inkscape
input-pad:google:input-pad
intel-gpu-tools:httpls:http://xorg.freedesktop.org/releases/individual/app/
intltool:lp:intltool
ipod-sharp:httpls:http://download.banshee-project.org/ipod-sharp/
iproute2:httpls:http://kernel.org/pub/linux/utils/net/iproute2/
iptables:httpls:http://ftp.netfilter.org/pub/iptables/
iptraf-ng:httpls:https://fedorahosted.org/iptraf-ng/wiki/Download
irda-utils:sf:5616|irda-utils
istanbul:httpls:http://live.gnome.org/Istanbul
itstool:httpls:http://files.itstool.org/itstool/
iwatch:sf:174218|iwatch
kcm-fcitx:google:fcitx|kcm-fcitx
kimtoy:httpls:http://kde-apps.org/content/download.php?content=140967&id=1
kye:httpls:http://games.moria.org.uk/kye/download-install
kyotocabinet:httpls:http://fallabs.com/kyotocabinet/pkg/
lbxproxy:httpls:http://xorg.freedesktop.org/releases/individual/app/
ldtp:subdirhttpls:http://download.freedesktop.org/ldtp/
libFS:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libHX:sf:254041
libICE:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libSM:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libWindowsWM:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libX11:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXScrnSaver:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXTrap:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXau:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXaw:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXcomposite:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXcursor:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXdamage:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXdmcp:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXevie:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXext:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXfixes:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXfont:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXfontcache:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXft:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXi:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXinerama:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXmu:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXp:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXpm:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXprintAppUtil:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXprintUtil:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXrandr:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXrender:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXres:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXt:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXtst:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXv:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXvMC:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXxf86dga:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXxf86misc:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libXxf86vm:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libao-pulse:httpls:http://0pointer.de/lennart/projects/libao-pulse/
libassuan:ftpls:ftp://ftp.gnupg.org/gcrypt/libassuan/
libatasmart:httpls:http://0pointer.de/public/
libatomic_ops:httpls:http://www.ivmaisoft.com/_bin/atomic_ops/
libbraille:sf:17127|libbraille
libbs2b:sf:151236
libcanberra:httpls:http://0pointer.de/lennart/projects/libcanberra/
libchewing:google:chewing|libchewing
libcompizconfig:subdirhttpls:http://releases.compiz.org/components/libcompizconfig/
libdaemon:httpls:http://0pointer.de/lennart/projects/libdaemon/
libdatrie:httpls:http://linux.thai.net/~thep/datrie/datrie.html
libdmapsharing:httpls:http://flyn.org/projects/libdmapsharing/download.html
libdmx:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libdrm:httpls:http://dri.freedesktop.org/libdrm/
libdv:sf:4393|libdv
libebml:httpls:http://dl.matroska.org/downloads/libebml/
libedit:httpls:http://thrysoee.dk/editline/
libesmtp:httpls:http://www.stafford.uklinux.net/libesmtp/download.html
libfontenc:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libgadu:httpls:http://toxygen.net/libgadu/files/
libgalago-gtk:httpls:http://www.galago-project.org/files/releases/source/libgalago-gtk/
libgalago:httpls:http://www.galago-project.org/files/releases/source/libgalago/
libgcrypt:ftpls:ftp://ftp.gnupg.org/gcrypt/libgcrypt/
libgexiv2:subdirhttpls:http://yorba.org/download/gexiv2/
libggz:subdirhttpls:http://mirrors.ibiblio.org/ggzgamingzone/ggz/
libgit2:htpls:https://github.com/libgit2/libgit2/releases/
libgnomesu:httpls:http://members.chello.nl/~h.lai/libgnomesu/
libgooglepinyin:google:libgooglepinyin
libgpg-error:ftpls:ftp://ftp.gnupg.org/gcrypt/libgpg-error/
libgpod:sf:67873|libgpod
libgrss:httpls:http://gtk.mplat.es/libgrss/tarballs/
libgsasl:httpls:http://ftp.gnu.org/pub/gnu/gsasl/
libgusb:httpls:http://people.freedesktop.org/~hughsient/releases/
libhangul:google:libhangul
libical:sf:16077
libinfinity:httpls:http://releases.0x539.de/libinfinity/
libiptcdata:sf:130582|libiptcdata
libjingle:httpls:http://farsight.freedesktop.org/releases/obsolete/libjingle/
libksba:ftpls:ftp://ftp.gnupg.org/gcrypt/libksba/
liblbxutil:httpls:http://xorg.freedesktop.org/releases/individual/lib/
liblouis:google:liblouis
libmatroska:httpls:http://dl.matroska.org/downloads/libmatroska/
libmbim:httpls:http://www.freedesktop.org/software/libmbim/
libmcs:httpls:http://distfiles.atheme.org/
libmnl:httpls:http://ftp.netfilter.org/pub/libmnl/
libmodman:google:libmodman
libmowgli:httpls:http://distfiles.atheme.org/
libnetfilter_conntrack:httpls:http://ftp.netfilter.org/pub/libnetfilter_conntrack/
libnetfilter_log:httpls:http://ftp.netfilter.org/pub/libnetfilter_log/
libnetfilter_queue:httpls:http://ftp.netfilter.org/pub/libnetfilter_queue/
libnfnetlink:httpls:http://ftp.netfilter.org/pub/libnfnetlink/
libnice:httpls:http://nice.freedesktop.org/releases/
libnjb:sf:32528|libnjb
libnl:httpls:http://www.infradead.org/~tgr/libnl/
libofetion:google:ofetion|libofetion
libofx:sf:61170|libofx
liboil:httpls:http://liboil.freedesktop.org/download/
liboldX:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libopenraw:httpls:http://libopenraw.freedesktop.org/download/
libosinfo:httpls:https://fedorahosted.org/releases/l/i/libosinfo/
libpciaccess:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libpinyin:httpls:http://github.com/libpinyin/libpinyin/downloads
libplist:httpls:http://github.com/JonathanBeck/libplist/downloads
libproxy:google:libproxy
libpst:httpls:http://www.five-ten-sg.com/libpst/packages/
libpwquality:httpls:https://fedorahosted.org/releases/l/i/libpwquality/
libqmi:httpls:http://www.freedesktop.org/software/libqmi/
librep:httpls:http://download.tuxfamily.org/librep/
librime:google:rimeime|librime
libsexy:httpls:http://releases.chipx86.com/libsexy/libsexy/
libspectre:httpls:http://libspectre.freedesktop.org/releases/
libssui:google:libssui
libtasn1:httpls:http://ftp.gnu.org/gnu/libtasn1/
libtelepathy:httpls:http://telepathy.freedesktop.org/releases/libtelepathy/
libthai:httpls:http://linux.thai.net/pub/thailinux/software/libthai/
libturpial:httpls:http://files.turpial.org.ve/sources/stable/
libvirt-cim:httpls:http://libvirt.org/sources/CIM/
libvirt-glib:httpls:http://libvirt.org/sources/glib/
libvirt:httpls:http://libvirt.org/sources/
libvpx:google:webm|libvpx
libwacom:sf:69596|libwacom
libwebp:google:webp|libwebp
libxcb:httpls:http://xorg.freedesktop.org/releases/individual/xcb/
libxkbcommon:httpls:http://xkbcommon.org/download/
libxkbfile:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libxkbui:httpls:http://xorg.freedesktop.org/releases/individual/lib/
libxklavier:sf:319|libxklavier
libzeitgeist:lp:libzeitgeist
liferea:httpls:https://github.com/lwindolf/liferea/releases/
link-grammar:subdirhttpls:http://www.abisource.com/downloads/link-grammar/
listres:httpls:http://xorg.freedesktop.org/releases/individual/app/
lmms:sf:105168
lndir:httpls:http://xorg.freedesktop.org/releases/individual/util/
m4:httpls:http://ftp.gnu.org/gnu/m4/
m17n-contrib:httpls:http://download.savannah.gnu.org/releases/m17n/
m17n-db:httpls:http://download.savannah.gnu.org/releases/m17n/
m17n-lib:httpls:http://download.savannah.gnu.org/releases/m17n/
mail-notification:httpls:http://www.nongnu.org/mailnotify/
mangler:httpls:http://www.mangler.org/downloads/
makedepend:httpls:http://xorg.freedesktop.org/releases/individual/util/
md5deep:sf:67079|md5deep
media-explorer:httpls:https://github.com/media-explorer/media-explorer/downloads
media-player-info:httpls:http://www.freedesktop.org/software/media-player-info/
mercurial:httpls:https://www.mercurial-scm.org/release/
mkcomposecache:httpls:http://xorg.freedesktop.org/releases/individual/app/
mkfontdir:httpls:http://xorg.freedesktop.org/releases/individual/app/
mkfontscale:httpls:http://xorg.freedesktop.org/releases/individual/app/
mkvtoolnix:httpls:http://www.bunkus.org/videotools/mkvtoolnix/sources/
moc:httpls:http://moc.daper.net/download
mod_dnssd:httpls:http://0pointer.de/lennart/projects/mod_dnssd/
monsoon:httpls:http://www.monsoon-project.org/jaws/index.php?page/Download
mozilla-bonobo:httpls:http://download.savannah.gnu.org/releases/moz-bonobo/
mbpurple:google:microblog-purple|mbpurple
mrim-prpl:google:mrim-prpl|mrim-prpl
mtr:ftpls:ftp://ftp.bitwizard.nl/mtr/
mx:httpls:https://github.com/clutter-project/mx/downloads
nautilus-search-tool:sf:149158|nautilus-search-tool
nautilus-terminal:lp:nautilus-terminal
neon:httpls:http://www.webdav.org/neon/
net6:httpls:http://releases.0x539.de/net6/
netspeed_applet:lp:netspeed
nimbus:httpls:http://dlc.sun.com/osol/jds/downloads/extras/nimbus/
njb-sharp:httpls:http://download.banshee.fm/legacy/njb-sharp/
notify-osd:lp:notify-osd
notify-python:httpls:http://www.galago-project.org/files/releases/source/notify-python/
npth:ftpls:ftp://ftp.gnupg.org/gcrypt/npth/
nspluginwrapper:httpls:http://nspluginwrapper.org/download/
nss-mdns:httpls:http://0pointer.de/lennart/projects/nss-mdns/
ntfs-3g_ntfsprogs:httpls:http://www.tuxera.com/community/ntfs-3g-download/
ntfs-config:httpls:http://flomertens.free.fr/ntfs-config/download.html
obby:httpls:http://releases.0x539.de/obby/
obex-data-server:httpls:http://tadas.dailyda.com/software/
oclock:httpls:http://xorg.freedesktop.org/releases/individual/app/
onboard:lp:onboard
opencc:google:opencc
openfetion:google:ofetion|openfetion
openobex:httpls:http://www.kernel.org/pub/linux/bluetooth/
opus:httpls:http://downloads.xiph.org/releases/opus/
orc:httpls:http://code.entropywave.com/download/orc/
ori:httpls:https://bitbucket.org/orifs/ori/downloads/
osm-gps-map:httpls:https://github.com/nzjrs/osm-gps-map/releases/
p11-kit:httpls:http://p11-glue.freedesktop.org/releases/
padevchooser:httpls:http://0pointer.de/lennart/projects/padevchooser/
pam_mount:sf:41452
paman:httpls:http://0pointer.de/lennart/projects/paman/
paprefs:httpls:http://freedesktop.org/software/pulseaudio/paprefs/
papyon:httpls:http://www.freedesktop.org/software/papyon/releases/
pavucontrol:httpls:http://freedesktop.org/software/pulseaudio/pavucontrol/
pavuk:sf:81012|pavuk
pavumeter:httpls:http://0pointer.de/lennart/projects/pavumeter/
pcre:ftpls:ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/
pidgin-advanced-sound-notification:lp:pidgin-advanced-sound-notification
pidgin-birthday-reminder:lp:pidgin-birthday-reminder
pidgin-embeddedvideo:google:pidgin-embeddedvideo|pidgin-embeddedvideo
pidgin-facebookchat:google:pidgin-facebookchat|pidgin-facebookchat-source
pidgin-guifications:httpls:https://www.guifications.org/projects/gf2/files
pidgin-openfetion:google:ofetion|pidgin-openfetion
pidgin-otr:httpls:http://www.cypherpunks.ca/otr/
pidgin-sipe:sf:194563|sipe
pidgin:sf:235|Pidgin
pinentry:ftpls:ftp://ftp.gnupg.org/gcrypt/pinentry/
pino:google:pino-twitter|pino
pithos:httpls:http://kevinmehall.net/p/pithos/release/
pixman:dualhttpls:http://cairographics.org/snapshots/|http://cairographics.org/releases/
pkg-config:httpls:http://pkgconfig.freedesktop.org/releases/
polkit-gnome:httpls:http://hal.freedesktop.org/releases/
polkit:httpls:http://www.freedesktop.org/software/polkit/releases/
poppler-data:httpls:http://poppler.freedesktop.org/
poppler:httpls:http://poppler.freedesktop.org/releases.html
posixovl:sf:255236
powerpc-utils:sf:261744
ppc64-diag:sf:44427
presage:sf:172950|presage
procmeter3:httpls:http://www.gedanken.demon.co.uk/download-procmeter/
proxymngr:httpls:http://xorg.freedesktop.org/releases/individual/app/
psiconv:httpls:http://software.frodo.looijaard.name/psiconv/download.php
psmisc:sf:15273|psmisc
pulseaudio:httpls:http://www.freedesktop.org/software/pulseaudio/releases/
purple-plugin-pack:httpls:https://www.guifications.org/projects/purple-plugin-pack/files
py2cairo:dualhttpls:http://cairographics.org/snapshots/|http://cairographics.org/releases/
pycairo:dualhttpls:http://cairographics.org/snapshots/|http://cairographics.org/releases/
pycups:httpls:http://cyberelk.net/tim/data/pycups/
pygtkglext:sf:54333|pygtkglext
pymetar:httpls:http://www.schwarzvogel.de/pkgs/
pymsn:httpls:http://telepathy.freedesktop.org/releases/pymsn/
pysmbc:httpls:http://cyberelk.net/tim/data/pysmbc/
python-distutils-extra:lp:python-distutils-extra
python-espeak:lp:python-espeak
virtkey:lp:virtkey
python-xlib:sf:10350|python-xlib
pywebkitgtk:google:pywebkitgtk
pyxdg:httpls:http://www.freedesktop.org/wiki/Software/pyxdg
qiv:httpls:http://spiegl.de/qiv/download/
qmmp:httpls:http://qmmp.ylsoftware.com/files/
quilt:httpls:http://download.savannah.gnu.org/releases/quilt/
radiotray:sf:295096
raptor:httpls:http://download.librdf.org/source/
rarian:httpls:http://rarian.freedesktop.org/Releases/
rasqal:httpls:http://download.librdf.org/source/
raw-thumbnailer:httpls:http://libopenraw.freedesktop.org/download/
recordmydesktop:sf:172357|recordmydesktop
redland:httpls:http://download.librdf.org/source/
rednotebook:sf:238077
remmina:httpls:https://github.com/FreeRDP/Remmina/releases
rendercheck:httpls:http://xorg.freedesktop.org/releases/individual/app/
rep-gtk:httpls:http://download.tuxfamily.org/librep/rep-gtk/
rgb:httpls:http://xorg.freedesktop.org/releases/individual/app/
rlwrap:httpls:http://utopia.knoware.nl/~hlub/rlwrap/
rstart:httpls:http://xorg.freedesktop.org/releases/individual/app/
sawfish:httpls:http://download.tuxfamily.org/sawfish/
schismtracker:httpls:http://schismtracker.org/dl/
schroedinger:httpls:http://diracvideo.org/download/schroedinger/
scim:sf:108454|scim
scim-anthy:sf_jp:scim-imengine
scim-bridge:sf:108454|scim-bridge
scim-canna:sf_jp:scim-imengine
scim-chewing:google:chewing|scim-chewing
scim-hangul:sf:108454|scim-hangul
scim-input-pad:sf:108454|scim-input-pad
scim-m17n:sf:108454|scim-m17n
scim-pinyin:sf:108454|scim-pinyin
scim-qtimm:sf:108454|scim-qtimm
scim-skk:sf_jp:scim-imengine
scim-sunpinyin:google:sunpinyin|scim-sunpinyin
scim-tables:sf:108454|scim-tables
scim-tomoe:sf_jp:scim-imengine
scim-uim:sf:108454|scim-uim
scim-unikey:google:scim-unikey
# Use "|xorg" because scripts is a really generic name
scripts|xorg:httpls:http://xorg.freedesktop.org/releases/individual/app/
scummvm:sf:37116
sessreg:httpls:http://xorg.freedesktop.org/releases/individual/app/
setxkbmap:httpls:http://xorg.freedesktop.org/releases/individual/app/
shared-color-profiles:httpls:http://people.freedesktop.org/~hughsient/releases/
shared-color-targets:httpls:http://people.freedesktop.org/~hughsient/releases/
shared-desktop-ontologies:sf:254113|shared-desktop-ontologies
shared-mime-info:httpls:http://people.freedesktop.org/~hadess/
shotwell:subdirhttpls:http://yorba.org/download/shotwell/
showfont:httpls:http://xorg.freedesktop.org/releases/individual/app/
simple-ccsm:subdirhttpls:http://releases.compiz.org/components/simple-ccsm/
simple-scan:lp:simple-scan
smproxy:httpls:http://xorg.freedesktop.org/releases/individual/app/
smuxi:httpls:http://www.smuxi.org/jaws/data/files/
sobby:httpls:http://releases.0x539.de/sobby/
sofia-sip:sf:143636|sofia-sip
solang:httpls:http://projects.gnome.org/solang/download
sound-theme-freedesktop:dualhttpls:http://people.freedesktop.org/~mccann/dist/|http://www.freedesktop.org/wiki/Specifications/sound-theme-spec
sparkleshare:httpls:http://sparkleshare.org/
specto:google:specto
speech-dispatcher:httpls:http://www.freebsoft.org/pub/projects/speechd/
spheres-and-crystals:sf:64400|spherecrystal
spice-gtk:httpls:http://spice-space.org/download/gtk/
spice-protocol:httpls:http://spice-space.org/download/releases/
spice:httpls:http://spice-space.org/download/releases/
ssh-contact:httpls:http://telepathy.freedesktop.org/releases/ssh-contact/
sshfp:ftpls:ftp://ftp.xelerance.com/sshfp/
startup-notification:httpls:http://www.freedesktop.org/software/startup-notification/releases/
stk:httpls:https://ccrma.stanford.edu/software/stk/download.html
sunpinyin:google:sunpinyin
swfdec-mozilla:subdirhttpls:http://swfdec.freedesktop.org/download/swfdec-mozilla/
swfdec:subdirhttpls:http://swfdec.freedesktop.org/download/swfdec/
swig:sf:1645
synapse:lp:synapse-project
system-config-printer:subdirhttpls:http://cyberelk.net/tim/data/system-config-printer/
tangerine:lp:tangerine
tango-icon-theme:httpls:http://tango.freedesktop.org/releases/
telepathy-butterfly:httpls:http://telepathy.freedesktop.org/releases/telepathy-butterfly/
telepathy-farsight:httpls:http://telepathy.freedesktop.org/releases/telepathy-farsight/
telepathy-farstream:httpls:http://telepathy.freedesktop.org/releases/telepathy-farstream/
telepathy-gabble:httpls:http://telepathy.freedesktop.org/releases/telepathy-gabble/
telepathy-glib:httpls:http://telepathy.freedesktop.org/releases/telepathy-glib/
telepathy-haze:httpls:http://telepathy.freedesktop.org/releases/telepathy-haze/
telepathy-idle:httpls:http://telepathy.freedesktop.org/releases/telepathy-idle/
telepathy-logger:httpls:http://telepathy.freedesktop.org/releases/telepathy-logger/
# telepathy-mission-control: for the old 4.x branch: telepathy-mission-control:sf:190214|mission-control
telepathy-mission-control:httpls:http://telepathy.freedesktop.org/releases/telepathy-mission-control/
telepathy-python:httpls:http://telepathy.freedesktop.org/releases/telepathy-python/
telepathy-rakia:httpls:http://telepathy.freedesktop.org/releases/telepathy-rakia/
telepathy-salut:httpls:http://telepathy.freedesktop.org/releases/telepathy-salut/
# telepathy-sofiasip: used to live at: telepathy-sofiasip:sf:191149
telepathy-sofiasip:httpls:http://telepathy.freedesktop.org/releases/telepathy-sofiasip/
telepathy-stream-engine:httpls:http://telepathy.freedesktop.org/releases/stream-engine/
tig:httpls:http://jonas.nitro.dk/tig/releases/
tilda:sf:126081|tilda
tinyproxy:httpls:https://banu.com/tinyproxy/
tokyocabinet:httpls:http://fallabs.com/tokyocabinet/
traffic-vis:httpls:http://www.mindrot.org/traffic-vis.html
transmageddon:httpls:http://www.linuxrising.org/files/
transmission:httpls:http://download.m0k.org/transmission/files/
tsclient:sf:192483|tsclient
turpial:httpls:http://turpial.org.ve/files/sources/stable/
twitux:sf:198704|twitux
twm:httpls:http://xorg.freedesktop.org/releases/individual/app/
udisks:httpls:http://udisks.freedesktop.org/releases/
udisks|1.90:dualhttpls:http://hal.freedesktop.org/releases/|http://udisks.freedesktop.org/releases/
uget:sf:72252|Uget (stable)
uhttpmock:httpls:https://tecnocode.co.uk/downloads/uhttpmock/
ulogd:httpls:http://ftp.netfilter.org/pub/ulogd/
unico:lp:unico
upower:httpls:http://upower.freedesktop.org/releases/
usbredir:httpls:http://spice-space.org/download/usbredir/
usbview:httpls:http://www.kroah.com/linux-usb/
valencia:subdirhttpls:http://yorba.org/download/valencia/
varnish:httpls:http://repo.varnish-cache.org/source/
# vboxgtk: used to live at: vboxgtk:sf:263334|vboxgtk
vboxgtk:google:vboxgtk
viewres:httpls:http://xorg.freedesktop.org/releases/individual/app/
vim:ftpls:ftp://ftp.vim.org/pub/vim/unix/
vmfs-tools:httpls:http://glandium.org/projects/vmfs-tools/
vobject:httpls:http://vobject.skyhouseconsulting.com/
wadptr:httpls:http://soulsphere.org/projects/wadptr/
weather-wallpaper:httpls:http://mundogeek.net/weather-wallpaper/
webkitgtk|2.4:httpls:http://webkitgtk.org/releases/
webkitgtk:httpls:http://webkitgtk.org/releases/
wmakerconf:sf:196469|wmakerconf
x-tile:httpls:http://www.giuspen.com/software/
x11perf:httpls:http://xorg.freedesktop.org/releases/individual/app/
x11vnc:sf:32584|x11vnc
xauth:httpls:http://xorg.freedesktop.org/releases/individual/app/
xbacklight:httpls:http://xorg.freedesktop.org/releases/individual/app/
xbiff:httpls:http://xorg.freedesktop.org/releases/individual/app/
xbitmaps:httpls:http://xorg.freedesktop.org/releases/individual/data/
xcalc:httpls:http://xorg.freedesktop.org/releases/individual/app/
xcb-util-image:httpls:http://xorg.freedesktop.org/releases/individual/xcb/
xcb-util-keysyms:httpls:http://xorg.freedesktop.org/releases/individual/xcb/
xcb-util-renderutil:httpls:http://xorg.freedesktop.org/releases/individual/xcb/
xcb-util-wm:httpls:http://xorg.freedesktop.org/releases/individual/xcb/
xcb-util:httpls:http://xorg.freedesktop.org/releases/individual/xcb/
xchat:subdirhttpls:http://www.xchat.org/files/source/
xclipboard:httpls:http://xorg.freedesktop.org/releases/individual/app/
xclock:httpls:http://xorg.freedesktop.org/releases/individual/app/
xcmsdb:httpls:http://xorg.freedesktop.org/releases/individual/app/
xcompmgr:httpls:http://xorg.freedesktop.org/releases/individual/app/
xconsole:httpls:http://xorg.freedesktop.org/releases/individual/app/
xcursorgen:httpls:http://xorg.freedesktop.org/releases/individual/app/
xcursor-themes:httpls:http://xorg.freedesktop.org/releases/individual/data/
xdbedizzy:httpls:http://xorg.freedesktop.org/releases/individual/app/
xdg-user-dirs:httpls:http://user-dirs.freedesktop.org/releases/
xdg-utils:httpls:http://portland.freedesktop.org/download/
xditview:httpls:http://xorg.freedesktop.org/releases/individual/app/
xdm:httpls:http://xorg.freedesktop.org/releases/individual/app/
xdpyinfo:httpls:http://xorg.freedesktop.org/releases/individual/app/
xedit:httpls:http://xorg.freedesktop.org/releases/individual/app/
xev:httpls:http://xorg.freedesktop.org/releases/individual/app/
xeyes:httpls:http://xorg.freedesktop.org/releases/individual/app/
xf86dga:httpls:http://xorg.freedesktop.org/releases/individual/app/
xf86-input-evdev:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-input-joystick:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-input-keyboard:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-input-libinput:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-input-mouse:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-input-synaptics:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-input-vmmouse:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-input-void:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-input-wacom:sf:69596|xf86-input-wacom
xf86-video-ark:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-ast:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-ati:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-cirrus:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-dummy:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-fbdev:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-geode:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-glint:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-i128:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-intel:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-ivtv:httpls:http://dl.ivtvdriver.org/xf86-video-ivtv/
xf86-video-mach64:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-mga:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-neomagic:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-newport:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-nv:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-qxl:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-r128:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-radeonhd:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-savage:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-siliconmotion:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-sis:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-tdfx:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-tga:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-trident:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-v4l:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-vesa:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-vmware:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xf86-video-voodoo:httpls:http://xorg.freedesktop.org/releases/individual/driver/
xfd:httpls:http://xorg.freedesktop.org/releases/individual/app/
xfindproxy:httpls:http://xorg.freedesktop.org/releases/individual/app/
xfontsel:httpls:http://xorg.freedesktop.org/releases/individual/app/
xfs:httpls:http://xorg.freedesktop.org/releases/individual/app/
xfsdump:ftpls:ftp://oss.sgi.com/projects/xfs/cmd_tars/
xfsinfo:httpls:http://xorg.freedesktop.org/releases/individual/app/
xfsprogs:ftpls:ftp://oss.sgi.com/projects/xfs/cmd_tars/
xfwp:httpls:http://xorg.freedesktop.org/releases/individual/app/
xgamma:httpls:http://xorg.freedesktop.org/releases/individual/app/
xgc:httpls:http://xorg.freedesktop.org/releases/individual/app/
xhost:httpls:http://xorg.freedesktop.org/releases/individual/app/
xinit:httpls:http://xorg.freedesktop.org/releases/individual/app/
xinput:httpls:http://xorg.freedesktop.org/releases/individual/app/
xkbcomp:httpls:http://xorg.freedesktop.org/releases/individual/app/
xkbevd:httpls:http://xorg.freedesktop.org/releases/individual/app/
xkbprint:httpls:http://xorg.freedesktop.org/releases/individual/app/
xkbutils:httpls:http://xorg.freedesktop.org/releases/individual/app/
xkeyboard-config:httpls:http://xorg.freedesktop.org/releases/individual/data/
xkill:httpls:http://xorg.freedesktop.org/releases/individual/app/
xload:httpls:http://xorg.freedesktop.org/releases/individual/app/
xlogo:httpls:http://xorg.freedesktop.org/releases/individual/app/
xlsatoms:httpls:http://xorg.freedesktop.org/releases/individual/app/
xlsclients:httpls:http://xorg.freedesktop.org/releases/individual/app/
xlsfonts:httpls:http://xorg.freedesktop.org/releases/individual/app/
xmag:httpls:http://xorg.freedesktop.org/releases/individual/app/
xman:httpls:http://xorg.freedesktop.org/releases/individual/app/
xmessage:httpls:http://xorg.freedesktop.org/releases/individual/app/
xmh:httpls:http://xorg.freedesktop.org/releases/individual/app/
xmodmap:httpls:http://xorg.freedesktop.org/releases/individual/app/
xorgxrdp:httpls:https://github.com/neutrinolabs/xorgxrdp/releases
xmore:httpls:http://xorg.freedesktop.org/releases/individual/app/
xorg-cf-files:httpls:http://xorg.freedesktop.org/releases/individual/util/
xorg-docs:httpls:http://xorg.freedesktop.org/releases/individual/doc/
xorg-sgml-doctools:httpls:http://xorg.freedesktop.org/releases/individual/doc/
xosd:sf:124390|libxosd
xplsprinters:httpls:http://xorg.freedesktop.org/releases/individual/app/
xprehashprinterlist:httpls:http://xorg.freedesktop.org/releases/individual/app/
xpr:httpls:http://xorg.freedesktop.org/releases/individual/app/
xprop:httpls:http://xorg.freedesktop.org/releases/individual/app/
xrandr:httpls:http://xorg.freedesktop.org/releases/individual/app/
xrdb:httpls:http://xorg.freedesktop.org/releases/individual/app/
xrdp:httpls:https://github.com/neutrinolabs/xrdp/releases
xrefresh:httpls:http://xorg.freedesktop.org/releases/individual/app/
xrestop:httpls:http://downloads.yoctoproject.org/releases/xrestop/
xrx:httpls:http://xorg.freedesktop.org/releases/individual/app/
xsane:httpls:http://www.xsane.org/cgi-bin/sitexplorer.cgi?/download/
xscope:httpls:http://xorg.freedesktop.org/releases/individual/app/
xset:httpls:http://xorg.freedesktop.org/releases/individual/app/
xsetmode:httpls:http://xorg.freedesktop.org/releases/individual/app/
xsetpointer:httpls:http://xorg.freedesktop.org/releases/individual/app/
xsetroot:httpls:http://xorg.freedesktop.org/releases/individual/app/
xsm:httpls:http://xorg.freedesktop.org/releases/individual/app/
xstdcmap:httpls:http://xorg.freedesktop.org/releases/individual/app/
xtables-addons:sf:254159
xtrans:httpls:http://xorg.freedesktop.org/releases/individual/lib/
xtrap:httpls:http://xorg.freedesktop.org/releases/individual/app/
xvidtune:httpls:http://xorg.freedesktop.org/releases/individual/app/
xvinfo:httpls:http://xorg.freedesktop.org/releases/individual/app/
xwd:httpls:http://xorg.freedesktop.org/releases/individual/app/
xwininfo:httpls:http://xorg.freedesktop.org/releases/individual/app/
xwud:httpls:http://xorg.freedesktop.org/releases/individual/app/
xzgv:sf:203093|xzgv
yaml-cpp:google:yaml-cpp
zeitgeist-datahub:lp:zeitgeist-datahub
zeitgeist:lp:zeitgeist
zim:httpls:http://zim-wiki.org/downloads/
zlib:httpls:http://www.zlib.net/
# Moved to ftp.gnome.org
#byzanz:httpls:http://people.freedesktop.org/~company/byzanz/
## We are upstream, with no tarball
beagle-index:upstream:
build-compare:upstream:
bundle-lang-common:upstream:
bundle-lang-gnome-extras:upstream:
bundle-lang-gnome-extras:upstream:
bundle-lang-gnome:upstream:
bundle-lang-gnome:upstream:
bundle-lang-kde:upstream:
bundle-lang-other:upstream:
desktop-data-SLED:upstream:
desktop-data-openSUSE:upstream:
desktop-translations:upstream:
dynamic-wallpapers-11x:upstream:
ggreeter:upstream:
gnome-patch-translation:upstream:
gnome-shell-search-provider-openSUSE-packages:upstream:
gos-wallpapers:upstream:
gtk2-themes:upstream:
libsolv:upstream:
libzypp:upstream:
libzypp-bindings:upstream:
libzypp-testsuite-tools:upstream:
metacity-themes:upstream:
opt_gnome-compat:upstream:
tennebon-dynamic-wallpaper:upstream:
translation-update:upstream:
update-desktop-files:upstream:
yast2:upstream:
yast2-control-center-gnome:upstream:
zypp-plugin:upstream:
zypper:upstream:
##
## Upstream where our script doesn't work (with potential workarounds)
##
# gimp-gap: not good: we have the version in the URL...
gimp-gap:ftpls:ftp://ftp.gimp.org/pub/gimp/plug-ins/v2.6/gap/
# iso-codes: Ugly workaround as the ftp is not accessibly from the server :/ Should be: iso-codes:ftpls:ftp://pkg-isocodes.alioth.debian.org/pub/pkg-isocodes/
iso-codes:httpls:http://translationproject.org/domain/iso_4217.html
# memphis: trac doesn't work. Should be: memphis:trac:http://trac.openstreetmap.ch/trac/memphis/downloads
memphis:httpls:http://trac.openstreetmap.ch/trac/memphis/
# pypoppler: unfortunately, it doesn't work: the rdf has no information, so we use httpls instead for now: pypoppler:lp:poppler-python
pypoppler:httpls:https://launchpad.net/poppler-python/+download
# xdelta: tarball names are not standard
xdelta:google:xdelta|xdelta
#FIXME opal http://sourceforge.net/project/showfiles.php?group_id=204472
#FIXME ptlib http://sourceforge.net/project/showfiles.php?group_id=204472
##
## Upstream where the old page is dead
##
## QtCurve-Gtk3: Doesn't exist anymore?
#QtCurve-Gtk3:httpls:http://www.kde-look.org/content/download.php?content=40492&id=4
## dopi is dead: no upstream homepage anymore
#dopi:httpls:http://www.snorp.net/files/dopi/
## Server is down
#metatheme-Sonar:httpls:http://forgeftp.novell.com/opensuse-art/openSUSE11.2/metatheme/
#metatheme-gilouche:httpls:http://forgeftp.novell.com/opensuse-art/openSUSE11.1/metatheme/
#opensuse-font-fifth-leg:httpls:http://forgeftp.novell.com/opensuse-art/openSUSE11.2/fonts/
##
## Packages that we removed
##
## clutter-cairo: also has no real homepage anymore
# clutter-cairo:subdirhttpls:http://clutter-project.org/sources/clutter-cairo/
# gst-pulse:httpls:http://0pointer.de/lennart/projects/gst-pulse/
## last-exist is dead: no upstream homepage anymore
# last-exit:httpls:http://lastexit-player.org/releases/
# libsvg:dualhttpls:http://cairographics.org/snapshots/|http://cairographics.org/releases/
# libsvg-cairo:dualhttpls:http://cairographics.org/snapshots/|http://cairographics.org/releases/
# pysqlite:subdirhttpls:http://oss.itsystementwicklung.de/download/pysqlite/
070701000000340000A1FF000000000000000000000001654A0F6A00000011000000000000000000000000000000000000003300000000osc-plugin-collab-0.104+30/server/upstream/util.py../obs-db/util.py07070100000035000041ED0000000000000000000000026548EB8C00000000000000000000000000000000000000000000002600000000osc-plugin-collab-0.104+30/server/web07070100000036000081ED0000000000000000000000016548EB8C00005D1A000000000000000000000000000000000000002D00000000osc-plugin-collab-0.104+30/server/web/api.py#!/usr/bin/env python
# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2008-2010, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sys
import cStringIO
import gzip
import re
import sqlite3
import cgi
try:
from lxml import etree as ET
except ImportError:
try:
from xml.etree import cElementTree as ET
except ImportError:
import cElementTree as ET
from libdissector import config
from libdissector import libdbcore
from libdissector import libinfoxml
if config.cgitb:
import cgitb; cgitb.enable()
# Database containing metadata. Can be created if needed.
METADATA_DBFILE = os.path.join(config.datadir, 'metadata.db')
# Protocol version is X.Y.
# + when breaking compatibility in the XML, then increase X and reset Y to 0.
# + when adding a new feature in a compatible way, increase Y.
PROTOCOL_MAJOR = 0
PROTOCOL_MINOR = 2
#######################################################################
class ApiOutput:
def __init__(self):
self.root = ET.Element('api')
self.root.set('version', '%d.%d' % (PROTOCOL_MAJOR, PROTOCOL_MINOR))
self.result = None
self.compress = False
def set_compress(self, compress):
can_compress = False
if os.environ.has_key('HTTP_ACCEPT_ENCODING'):
accepted = os.environ['HTTP_ACCEPT_ENCODING'].split(',')
accepted = [ item.strip() for item in accepted ]
can_compress = 'gzip' in accepted
self.compress = can_compress and compress
def set_result(self, ok = True, detail = ''):
if self.result is None:
self.result = ET.SubElement(self.root, 'result')
if ok:
self.result.set('ok', 'true')
else:
self.result.set('ok', 'false')
if detail:
self.result.text = detail
def add_node(self, node):
self.root.append(node)
def _output(self):
print 'Content-type: text/xml'
print
ET.ElementTree(self.root).write(sys.stdout)
def _output_compressed(self):
# Thanks to http://www.xhaus.com/alan/python/httpcomp.html
zbuf = cStringIO.StringIO()
zfile = gzip.GzipFile(mode = 'wb', fileobj = zbuf)
ET.ElementTree(self.root).write(zfile)
zfile.close()
compressed = zbuf.getvalue()
print 'Content-type: text/xml'
print 'Content-Encoding: gzip'
print 'Content-Length: %d' % len(compressed)
print
sys.stdout.write(compressed)
def output(self):
if self.compress and False:
self._output_compressed()
else:
self._output()
#######################################################################
class ApiGeneric:
def __init__(self, output, protocol, args, form):
self.output = output
self.protocol = protocol
self.args = args
self.form = form
self.db = libdbcore.ObsDb()
def __del__(self):
del self.db
def _find_project_for_package(self, package, projects):
'''
Find the first project in the list of projects containing the
specified package.
'''
query = 'SELECT COUNT(*) FROM %s, %s WHERE %s.name = ? AND %s.name = ? AND %s.project = %s.id;' % (libdbcore.table_project, libdbcore.table_srcpackage, libdbcore.table_project, libdbcore.table_srcpackage, libdbcore.table_srcpackage, libdbcore.table_project)
for project in projects:
self.db.cursor.execute(query, (project, package))
row = self.db.cursor.fetchone()
if row[0] != 0:
return project
return None
def _package_exists(self, project, package):
'''
Checks a package exists.
'''
query = 'SELECT version FROM %s, %s WHERE %s.name = ? AND %s.name = ? AND %s.project = %s.id;' % (libdbcore.table_project, libdbcore.table_srcpackage, libdbcore.table_project, libdbcore.table_srcpackage, libdbcore.table_srcpackage, libdbcore.table_project)
self.db.cursor.execute(query, (project, package))
row = self.db.cursor.fetchone()
return row is not None
def _find_devel_package(self, project, package):
'''
Find the end devel package of a specified package.
'''
query = 'SELECT devel_project, devel_package FROM %s, %s WHERE %s.name = ? AND %s.name = ? AND %s.project = %s.id;' % (libdbcore.table_project, libdbcore.table_srcpackage, libdbcore.table_project, libdbcore.table_srcpackage, libdbcore.table_srcpackage, libdbcore.table_project)
while True:
self.db.cursor.execute(query, (project, package))
row = self.db.cursor.fetchone()
if not row:
return (project, package)
devel_project = row['devel_project']
devel_package = row['devel_package']
if not devel_project:
return (project, package)
project = devel_project
package = devel_package or package
return (project, package)
def _parse_standard_args(self, paths):
'''
Parse a path that is in the form of either of the following:
+ <project>
+ <project>/<package>
+ <package>?project=aa&project=...
'''
if len(paths) == 1 and not paths[0]:
return (True, None, None)
elif len(paths) == 1 or (len(paths) == 2 and not paths[1]):
projects = self.form.getlist('project')
if projects:
package = paths[0]
project = self._find_project_for_package(package, projects)
if project:
return (True, project, package)
else:
self.output.set_result(False, 'Non existing package: %s' % package)
return (False, None, None)
else:
project = paths[0]
return (True, project, None)
else:
project = paths[0]
package = paths[1]
return (True, project, package)
def run(self):
pass
#######################################################################
class ApiInfo(ApiGeneric):
'''
api/info
api/info/<project>
api/info/<project>/<package>
api/info/<package>?project=aa&project=...
'''
def _list_projects(self):
self.db.cursor.execute('''SELECT name FROM %s ORDER BY name;''' % libdbcore.table_project)
for row in self.db.cursor:
node = ET.Element('project')
node.set('name', row['name'])
self.output.add_node(node)
def _list_project(self, project):
info = libinfoxml.InfoXml(self.db)
try:
node = info.get_project_node(project)
self.output.add_node(node)
output.set_compress(True)
except libinfoxml.InfoXmlException, e:
self.output.set_result(False, e.msg)
def _list_package(self, project, package):
info = libinfoxml.InfoXml(self.db)
try:
prj_node = info.get_project_node(project, False)
pkg_node = info.get_package_node(project, package)
prj_node.append(pkg_node)
self.output.add_node(prj_node)
except libinfoxml.InfoXmlException, e:
self.output.set_result(False, e.msg)
def run(self):
paths = self.args.split('/')
if len(paths) > 2:
self.output.set_result(False, 'Too many arguments to "info" command')
return
(ok, project, package) = self._parse_standard_args(paths)
if not ok:
return
if not project:
self._list_projects()
elif not package:
self._list_project(project)
else:
self._list_package(project, package)
#######################################################################
class ApiPackageMetadata(ApiGeneric):
'''
api/<meta> (list)
api/<meta>/<project> (list)
api/<meta>/<project>/<package> (list)
api/<meta>/<project>/<package>?cmd=list
api/<meta>/<project>/<package>?cmd=set&user=<user>
api/<meta>/<project>/<package>?cmd=unset&user=<user>
api/<meta>?project=aa&project=... (list)
api/<meta>/<package>?project=aa&project=...&cmd=...
For all package-related commands, ignoredevel=1 or ignoredevel=true can
be used to not make the metadata request work on the development
package of the package, but to force the commands on this package in
this project.
Subclasses should:
- set self.dbtable and self.command in __init__
- override self._create_node()
- override self._run_project_package_helper()
'''
def __init__(self, output, protocol, args, form):
ApiGeneric.__init__(self, output, protocol, args, form)
self.dbmeta = None
self.cursor = None
# Should be overridden by subclass
self.dbtable = ''
self.command = ''
def __del__(self):
if self.cursor:
self.cursor.close()
if self.dbmeta:
self.dbmeta.close()
def _get_metadata_database(self):
create = True
if os.path.exists(METADATA_DBFILE):
create = False
if not os.access(METADATA_DBFILE, os.W_OK):
self.output.set_result(False, 'Read-only database')
return False
else:
dirname = os.path.dirname(METADATA_DBFILE)
if not os.path.exists(dirname):
os.makedirs(dirname)
self.dbmeta = sqlite3.connect(METADATA_DBFILE)
if not self.dbmeta:
self.output.set_result(False, 'No access to database')
return False
self.dbmeta.row_factory = sqlite3.Row
self.cursor = self.dbmeta.cursor()
if create:
# When adding a table here, update _prune_old_metadata() and
# _check_no_abuse() to deal with them too.
self.cursor.execute('''CREATE TABLE reserve (date TEXT, user TEXT, project TEXT, package TEXT);''')
self.cursor.execute('''CREATE TABLE comment (date TEXT, user TEXT, project TEXT, package TEXT, comment TEXT);''')
return True
def _prune_old_metadata(self):
# do not touch comments, since they might stay for good reasons
self.cursor.execute('''DELETE FROM reserve WHERE datetime(date, '+36 hours') < datetime('now');''')
def _check_no_abuse(self):
# just don't do anything if we have more than 200 entries in a table
# (we're getting spammed)
for table in [ 'reserve', 'comment' ]:
self.cursor.execute('''SELECT COUNT(*) FROM %s;''' % table)
row = self.cursor.fetchone()
if not row or row[0] > 200:
self.output.set_result(False, 'Database currently unavailable')
return False
return True
def _create_node(self, row):
''' Should be overridden by subclass. '''
# Note: row can be a sqlite3.Row or a tuple
return None
def _list_all(self, projects = None):
if projects:
projects_where = ' OR '.join(['project = ?' for project in projects])
self.cursor.execute('''SELECT * FROM %s WHERE %s ORDER BY project, package;''' % (self.dbtable, projects_where), projects)
else:
self.cursor.execute('''SELECT * FROM %s ORDER BY project, package;''' % self.dbtable)
for row in self.cursor:
node = self._create_node(row)
if node is None:
self.output.set_result(False, 'Internal server error')
return
self.output.add_node(node)
def _run_project_package_helper(self, user, subcommand, project, package):
''' Should be overridden by subclass. '''
return None
def _run_project_package(self, project, package):
ignore_devel = False
if form.has_key('ignoredevel'):
if form.getfirst('ignoredevel').lower() in [ '1', 'true' ]:
ignore_devel = True
if not ignore_devel:
(project, package) = self._find_devel_package(project, package)
if not self._package_exists(project, package):
self.output.set_result(False, 'Non existing package: %s/%s' % (project, package))
return
if form.has_key('cmd'):
cmd = form.getfirst('cmd')
else:
cmd = 'list'
if cmd not in [ 'list', 'set', 'unset' ]:
self.output.set_result(False, 'Unknown "%s" subcommand: %s' % (self.command, cmd))
return
if form.has_key('user'):
user = form.getfirst('user')
else:
user = None
if cmd in [ 'set', 'unset' ] and not user:
self.output.set_result(False, 'No user specified')
return
pseudorow = self._run_project_package_helper(user, cmd, project, package)
self.dbmeta.commit()
node = self._create_node(pseudorow)
if node is None:
self.output.set_result(False, 'Internal server error')
return
self.output.add_node(node)
def run(self):
if not self._get_metadata_database():
return
# automatically remove old metadata
self._prune_old_metadata()
if not self._check_no_abuse():
return
paths = self.args.split('/')
if len(paths) > 2:
self.output.set_result(False, 'Too many arguments to "%s" command' % self.command)
return
(ok, project, package) = self._parse_standard_args(paths)
if not ok:
return
if not project:
projects = self.form.getlist('project')
self._list_all(projects)
elif not package:
self._list_all((project,))
else:
self._run_project_package(project, package)
#######################################################################
class ApiReserve(ApiPackageMetadata):
'''
See ApiPackageMetadata comment, with <meta> == reserve
'''
def __init__(self, output, protocol, args, form):
ApiPackageMetadata.__init__(self, output, protocol, args, form)
self.dbtable = 'reserve'
self.command = 'reserve'
def _create_node(self, row):
# Note: row can be a sqlite3.Row or a tuple
keys = row.keys()
if not ('project' in keys and 'package' in keys):
return None
project = row['project']
package = row['package']
if 'user' in keys:
user = row['user']
else:
user = None
node = ET.Element('reservation')
node.set('project', project)
node.set('package', package)
if user:
node.set('user', user)
return node
def _run_project_package_helper(self, user, subcommand, project, package):
self.cursor.execute('''SELECT user FROM reserve WHERE project = ? AND package = ?;''', (project, package,))
row = self.cursor.fetchone()
if row:
reserved_by = row['user']
else:
reserved_by = None
if subcommand == 'list':
# we just want the reservation node
pass
elif subcommand == 'set':
if reserved_by:
self.output.set_result(False, 'Package already reserved by %s' % reserved_by)
else:
self.cursor.execute('''INSERT INTO reserve VALUES (datetime('now'), ?, ?, ?);''', (user, project, package))
reserved_by = user
elif subcommand == 'unset':
if not reserved_by:
self.output.set_result(False, 'Package not reserved')
elif reserved_by != user:
self.output.set_result(False, 'Package reserved by %s' % reserved_by)
else:
self.cursor.execute('''DELETE FROM reserve WHERE user = ? AND project = ? AND package = ?''', (user, project, package))
reserved_by = None
pseudorow = {}
pseudorow['project'] = project
pseudorow['package'] = package
if reserved_by:
pseudorow['user'] = reserved_by
return pseudorow
#######################################################################
class ApiComment(ApiPackageMetadata):
'''
See ApiPackageMetadata comment, with <meta> == comment
'''
def __init__(self, output, protocol, args, form):
ApiPackageMetadata.__init__(self, output, protocol, args, form)
self.dbtable = 'comment'
self.command = 'comment'
def _create_node(self, row):
# Note: row can be a sqlite3.Row or a tuple
keys = row.keys()
if not ('project' in keys and 'package' in keys):
return None
project = row['project']
package = row['package']
date = None
user = None
comment = None
if 'date' in keys:
date = row['date']
if 'user' in keys:
user = row['user']
if 'comment' in keys:
comment = row['comment']
node = ET.Element('comment')
node.set('project', project)
node.set('package', package)
if date:
node.set('date', date)
if user:
node.set('user', user)
if comment:
node.text = comment
return node
def _run_project_package_helper(self, user, subcommand, project, package):
if form.has_key('comment'):
form_comment = form.getfirst('comment')
else:
form_comment = None
self.cursor.execute('''SELECT user, comment, date FROM comment WHERE project = ? AND package = ?;''', (project, package,))
row = self.cursor.fetchone()
if row:
commented_by = row['user']
comment = row['comment']
date = row['date']
else:
commented_by = None
comment = None
date = None
if subcommand == 'list':
# we just want the comment node
pass
elif subcommand == 'set':
if commented_by:
self.output.set_result(False, 'Package already commented by %s' % commented_by)
else:
if not form_comment:
self.output.set_result(False, 'No comment provided')
elif len(form_comment) > 1000:
self.output.set_result(False, 'Provided comment is too long')
else:
self.cursor.execute('''INSERT INTO comment VALUES (datetime('now'), ?, ?, ?, ?);''', (user, project, package, form_comment))
commented_by = user
comment = form_comment
# we do a query to get the right format for the date
self.cursor.execute('''SELECT datetime('now');''')
row = self.cursor.fetchone()
date = row[0]
elif subcommand == 'unset':
if not commented_by:
self.output.set_result(False, 'Package not commented')
elif commented_by != user:
self.output.set_result(False, 'Package commented_by by %s' % commented_by)
else:
self.cursor.execute('''DELETE FROM comment WHERE user = ? AND project = ? AND package = ?''', (user, project, package))
commented_by = None
pseudorow = {}
pseudorow['project'] = project
pseudorow['package'] = package
if date:
pseudorow['date'] = date
if commented_by:
pseudorow['user'] = commented_by
if comment:
pseudorow['comment'] = comment
return pseudorow
#######################################################################
def handle_args(output, path, form):
paths = path.split('/', 1)
if len(paths) == 1:
command = paths[0]
args = ''
else:
(command, args) = paths
if form.has_key('version'):
client_version = form.getfirst('version')
else:
client_version = '0.1'
client_version_items = client_version.split('.')
for item in client_version_items:
try:
int(item)
except ValueError:
output.set_result(False, 'Invalid protocol version')
return
if len(client_version_items) != 2:
output.set_result(False, 'Invalid format for protocol version')
return
protocol = (int(client_version_items[0]), int(client_version_items[1]))
if protocol[0] > PROTOCOL_MAJOR or (protocol[0] == PROTOCOL_MAJOR and protocol[1] > PROTOCOL_MINOR):
output.set_result(False, 'Protocol version requested is unknown')
return
# assume the result is successful at first :-)
output.set_result()
if not command:
output.set_result(False, 'No command specified')
elif command == 'info':
try:
info = ApiInfo(output, protocol, args, form)
info.run()
except libdbcore.ObsDbException, e:
output.set_result(False, str(e))
elif command == 'reserve':
try:
reserve = ApiReserve(output, protocol, args, form)
reserve.run()
except libdbcore.ObsDbException, e:
output.set_result(False, str(e))
elif command == 'comment':
try:
comment = ApiComment(output, protocol, args, form)
comment.run()
except libdbcore.ObsDbException, e:
output.set_result(False, str(e))
else:
output.set_result(False, 'Unknown command "%s"' % command)
#######################################################################
if os.environ.has_key('PATH_INFO'):
path = os.environ['PATH_INFO']
# remove consecutive slashes
path = re.sub('//+', '/', path)
# remove first slash
path = path[1:]
else:
path = ''
output = ApiOutput()
form = cgi.FieldStorage()
handle_args(output, path, form)
output.output()
07070100000037000081A40000000000000000000000016548EB8C00000000000000000000000000000000000000000000003100000000osc-plugin-collab-0.104+30/server/web/index.html07070100000038000081ED0000000000000000000000016548EB8C00000C3F000000000000000000000000000000000000002F00000000osc-plugin-collab-0.104+30/server/web/index.py#!/usr/bin/env python
# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2008-2010, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
from libdissector import config
from libdissector import libhttp
if config.cgitb:
import cgitb; cgitb.enable()
#######################################################################
libhttp.print_html_header()
libhttp.print_header("Our playground for openSUSE")
print '''
<h2>Our playground for openSUSE</h2>
<p>If you're simply interested in browsing the openSUSE source, head to the <a href="https://build.opensuse.org/">openSUSE instance</a> of the <a href="http://open-build-service.org/">Open Build Service</a>! No need to login, just browse the packages; the most interesting projects are the ones starting with "openSUSE:", like <a href="https://build.opensuse.org/project/show?project=openSUSE%3AFactory">openSUSE:Factory</a>.</p>
<p>The original idea behind this playground was to analyze the packages of the GNOME team to know what kind of work is needed, but it has evolved and it's now a good place to get some data about all packages in openSUSE.</a>
<p>This playground also hosts a service that makes it easier to collaborate within the openSUSE community, via <a href="http://en.opensuse.org/openSUSE:Osc_Collab">osc collab</a>. This service works for all packages part of <a href="https://build.opensuse.org/project/show?project=openSUSE%3AFactory">openSUSE:Factory</a>!</p>
<p>On the right side, you can find a few links that are, hopefully, self-explanatory :-)</p>
'''
libhttp.print_foot()
07070100000039000041ED0000000000000000000000026548EB8C00000000000000000000000000000000000000000000003300000000osc-plugin-collab-0.104+30/server/web/libdissector0707010000003A000081A40000000000000000000000016548EB8C0000000E000000000000000000000000000000000000003D00000000osc-plugin-collab-0.104+30/server/web/libdissector/.htaccessDeny from all
0707010000003B000081A40000000000000000000000016548EB8C00000000000000000000000000000000000000000000003F00000000osc-plugin-collab-0.104+30/server/web/libdissector/__init__.py0707010000003C000081A40000000000000000000000016548EB8C00001065000000000000000000000000000000000000004300000000osc-plugin-collab-0.104+30/server/web/libdissector/buildservice.py# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2009, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import urllib2
from cgi import escape
from urllib import urlencode
from urlparse import urlsplit, urlunsplit
try:
from lxml import etree as ET
except ImportError:
try:
from xml.etree import cElementTree as ET
except ImportError:
import cElementTree as ET
import config
#######################################################################
class BuildServiceException(Exception):
def __init__(self, value):
self.msg = value
def __str__(self):
return self.msg
#######################################################################
def get_source_url(project, package, file = None, rev = None, do_escape = False):
if do_escape:
project = escape(project)
package = escape(package)
if file:
file = escape(file)
(scheme, netloc) = urlsplit(config.apiurl)[0:2]
path = '/'.join(('public', 'source', project, package))
if file:
path = '/'.join((path, file))
if rev:
query = urlencode({'rev': rev})
else:
query = None
url = urlunsplit((scheme, netloc, path, query, ''))
return url
def get_source_link(project, package, file = None, rev = None, do_escape = False, text = None, title = None):
url = get_source_url(project, package, file, rev, do_escape)
if title:
title_attr = ' title="%s"' % escape(title)
else:
title_attr = ''
if not text:
text = file or package
text = escape(text)
return '<a href="%s"%s>%s</a>' % (url, title_attr, text)
#######################################################################
def fetch_package_content(project, package):
url = get_source_url(project, package)
url += '?expand=1'
try:
fd = urllib2.urlopen(url)
directory = ET.parse(fd).getroot()
linkinfo = directory.find('linkinfo')
if linkinfo != None:
srcmd5 = directory.get('srcmd5')
else:
srcmd5 = ''
files = []
for node in directory.findall('entry'):
files.append(node.get('name'))
fd.close()
return (files, srcmd5)
except urllib2.HTTPError, e:
raise BuildServiceException('Error while fetching the content: %s' % (e.msg,))
except urllib2.URLError, e:
raise BuildServiceException('Error while fetching the content: %s' % (e,))
except SyntaxError, e:
raise BuildServiceException('Error while fetching the content: %s' % (e.msg,))
return (None, None)
0707010000003D000081A40000000000000000000000016548EB8C000008C5000000000000000000000000000000000000004000000000osc-plugin-collab-0.104+30/server/web/libdissector/config.py.in# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2009, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
### Settings to change ###
# Where to store files?
## Example: '/tmp/obs-dissector'
datadir = ''
# IPs that are authorized to upload files
## Example: [ '10.0.0.1', '127.0.0.1' ]
upload_authorized_ips = [ ]
# Hosts that are authorized to upload files
## Example: [ 'magic.opensuse.org' ]
upload_authorized_hosts = [ ]
### Settings that should be fine by default ###
# Use cgitb?
cgitb = False
# API URL of the build service to use
apiurl = 'https://api.opensuse.org/'
# Default project to use when no project is specified
default_project = 'openSUSE:Factory'
0707010000003E000081A40000000000000000000000016548EB8C00000F08000000000000000000000000000000000000004000000000osc-plugin-collab-0.104+30/server/web/libdissector/libdbcore.py# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2008-2010, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sqlite3
import time
from stat import *
import config
#######################################################################
_db_file = os.path.join(config.datadir, 'obs.db')
table_file = 'file'
table_source = 'source'
table_patch = 'patch'
table_rpmlint = 'rpmlint'
table_package = 'package'
table_srcpackage = 'srcpackage'
table_project = 'project'
#######################################################################
pkg_query = 'SELECT %s.* FROM %s, %s WHERE %s.name = ? AND %s.name = ? AND %s.project = %s.id;' % (table_srcpackage, table_project, table_srcpackage, table_project, table_srcpackage, table_srcpackage, table_project)
#######################################################################
def get_db_mtime(raw = False):
mtime = time.gmtime(os.stat(_db_file)[ST_MTIME])
if raw:
return mtime
else:
return time.strftime('%d/%m/%Y (%H:%M UTC)', mtime)
#######################################################################
class ObsDbException(Exception):
def __init__(self, value):
self.msg = value
def __str__(self):
return self.msg
#######################################################################
class ObsDb:
def __init__(self):
if not os.path.exists(_db_file):
raise ObsDbException('Database %s unavailable' % (os.path.abspath(_db_file)))
self.conn = sqlite3.connect(_db_file)
if not self.conn:
raise ObsDbException('Database unavailable')
self.conn.row_factory = sqlite3.Row
self.cursor = self.conn.cursor()
self.cursor.execute('''SELECT * FROM %s;''' % 'db_version')
row = self.cursor.fetchone()
if row:
self.db_major = row['major']
self.db_minor = row['minor']
else:
self.db_major = -1
self.db_minor = -1
def __del__(self):
if self.cursor:
self.cursor.close()
if self.conn:
self.conn.close()
def get_db_version(self):
return (self.db_major, self.db_minor)
def cursor_new(self):
return self.conn.cursor()
0707010000003F000081A40000000000000000000000016548EB8C000009C7000000000000000000000000000000000000004000000000osc-plugin-collab-0.104+30/server/web/libdissector/libdbhtml.py# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2009-2010, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
from cgi import escape
import libdbcore
def get_project_selector(current_project = None, db = None):
if not db:
db = libdbcore.ObsDb()
db.cursor.execute('''SELECT name FROM %s ORDER BY UPPER(name);''' % libdbcore.table_project)
s = ''
s += '<form action="%s">\n' % escape(os.environ['SCRIPT_NAME'])
s += '<p>\n'
s += 'Project:\n'
s += '<select name="project">\n'
for row in db.cursor:
escaped_name = escape(row['name'])
if row['name'] == current_project:
selected = ' selected'
else:
selected = ''
s += '<option value="%s"%s>%s</option>\n' % (escaped_name, selected, escaped_name)
s += '</select>\n'
s += '<input type="submit" value="choose">\n'
s += '</p>\n'
s += '</form>\n'
return s
07070100000040000081A40000000000000000000000016548EB8C00001E08000000000000000000000000000000000000003E00000000osc-plugin-collab-0.104+30/server/web/libdissector/libhttp.py# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2008-2010, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
#
# The included HTML code here is the design from the openSUSE project.
# FIXME: find the right copyright/license for it.
#
import config
import libdbcore
def print_text_header():
print 'Content-type: text/plain'
print
def print_xml_header():
print 'Content-type: text/xml'
print
def print_html_header():
print 'Content-type: text/html'
print
def print_header_raw(title):
print '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="MSSmartTagsPreventParsing" content="TRUE" />
<title>%s</title>
</head>
<body>
''' % title
def print_foot_raw():
print '''
</body>
</html>'''
def print_header(title=''):
print '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="robots" content="index,follow" />
<link rel="stylesheet" href="http://static.opensuse.org/themes/bento/css/style.css" type="text/css" media="screen" title="All" charset="utf-8" />
<link rel="stylesheet" href="http://static.opensuse.org/themes/bento/css/print.css" type="text/css" media="print" charset="utf-8">
<script src="http://static.opensuse.org/themes/bento/js/jquery.js" type="text/javascript" charset="utf-8"></script>
<script src="http://static.opensuse.org/themes/bento/js/l10n/global-navigation-data-en.js" type="text/javascript" charset="utf-8"></script>
<script src="http://static.opensuse.org/themes/bento/js/global-navigation.js" type="text/javascript" charset="utf-8"></script>
<link rel="icon" type="image/png" href="http://static.opensuse.org/themes/bento/images/favicon.png" />
<title>%s</title>
</head>
<body>
<!-- Start: Header -->
<div id="header">
<div id="header-content" class="container_12">
<a id="header-logo" href="./"><img src="http://static.opensuse.org/themes/bento/images/header-logo.png" width="46" height="26" alt="Header Logo" /></a>
<ul id="global-navigation">
<li id="item-downloads"><a href="http://en.opensuse.org/openSUSE:Browse#downloads">Downloads</a></li>
<li id="item-support"><a href="http://en.opensuse.org/openSUSE:Browse#support">Support</a></li>
<li id="item-community"><a href="http://en.opensuse.org/openSUSE:Browse#community">Community</a></li>
<li id="item-development"><a href="http://en.opensuse.org/openSUSE:Browse#development">Development</a></li>
</ul>
</div>
</div>
<!-- End: Header -->
<!-- Start: Main Content Area -->
<div id="content" class="container_16 content-wrapper">
<div class="box box-shadow grid_12 alpha">
<!-- Begin Content Area -->
''' % title
def print_foot(additional_box = ''):
timestr = libdbcore.get_db_mtime()
print '''
<!-- End Content Area -->
</div>
<div class="column grid_4 omega">
<div id="some_other_content" class=" box box-shadow alpha clear-both navigation">
<h2 class="box-header">Navigation</h2>
<ul class="navigation">
<li><a href="./obs">Packages Status</a></li>
<li><a href="./patch">Patches Status</a></li>
<!--<li><a href="./rpmlint">Rpmlint Status</a></li>-->
</ul>
</div>
%s
</div>
</div>
<!-- Start: included footer part -->
<div id="footer" class="container_12">
<!-- TODO: add content -->
<div id="footer-legal" class="border-top grid_12">
<p>
This is still a prototype and is not officially endorsed by the openSUSE project.
<br />
Database last updated on %s
</p>
</div>
</div>
</body>
</html>''' % (additional_box, timestr)
# At some point we wanted to have this too:
'''
<div class="green_box">
<div class="box_top_row">
<div class="box_left"></div>
<div class="box_right"></div>
</div>
<div class="box_title_row">
<div class="box_title">
Statistics
</div>
</div>
<div class="box_content">
<ul class="navlist">
<li>General stats</li>
<li>Graphes</li>
</ul>
</div>
<div class="box_bottom_row">
<div class="box_left"></div>
<div class="box_right"></div>
</div>
</div>
<br />
'''
'''
<div class="green_box">
<div class="box_top_row">
<div class="box_left"></div>
<div class="box_right"></div>
</div>
<div class="box_title_row">
<div class="box_title">
Reports
</div>
</div>
<div class="box_content">
<ul class="navlist">
<li>Build Failures</li>
<li>Tagging Progress</li>
<li>Rpmlint Progress</li>
<li>Bug Filing Status</li>
<li>Patch Rebase Status</li>
<li>Patch SLE Status</li>
</ul>
</div>
<div class="box_bottom_row">
<div class="box_left"></div>
<div class="box_right"></div>
</div>
</div>
<br />
'''
def get_arg(form, name, default_value = None):
if form.has_key(name):
return form.getfirst(name)
else:
return default_value
def get_arg_bool(form, name, default_value = False):
if default_value:
default = '1'
else:
default = '0'
value = get_arg(form, name, default)
try:
return (int(value) == 1)
except ValueError:
return default_value
def get_project(form):
return get_arg(form, 'project', config.default_project)
def get_srcpackage(form):
ret = get_arg(form, 'srcpackage')
if not ret:
ret = get_arg(form, 'package')
return ret
07070100000041000081A40000000000000000000000016548EB8C0000305D000000000000000000000000000000000000004100000000osc-plugin-collab-0.104+30/server/web/libdissector/libinfoxml.py# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2009-2010, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sys
import sqlite3
import tempfile
try:
from lxml import etree as ET
except ImportError:
try:
from xml.etree import cElementTree as ET
except ImportError:
import cElementTree as ET
import config
import libdbcore
# Directory containing XML caches for projects data
XML_CACHE_DIR = os.path.join(config.datadir, 'xml')
#######################################################################
class InfoXmlException(Exception):
def __init__(self, value):
self.msg = value
def __str__(self):
return self.msg
#######################################################################
class InfoXml:
version_query = 'SELECT %s.version FROM %s, %s WHERE %s.name = ? AND %s.name = ? AND %s.project = %s.id ;' % (libdbcore.table_srcpackage, libdbcore.table_project, libdbcore.table_srcpackage, libdbcore.table_project, libdbcore.table_srcpackage, libdbcore.table_srcpackage, libdbcore.table_project)
def __init__(self, obsdb = None):
if not obsdb:
self.obsdb = libdbcore.ObsDb()
else:
if not isinstance(obsdb, libdbcore.ObsDb):
raise TypeError, 'obsdb must be a ObsDb instance'
self.obsdb = obsdb
self.cursor = self.obsdb.cursor_new()
self.cursor_helper = self.obsdb.cursor_new()
self.cache_dir = XML_CACHE_DIR
self.version_cache = None
def _find_version_for_sql(self, project, package):
self.cursor_helper.execute(self.version_query, (project, package))
row = self.cursor_helper.fetchone()
if row:
return row['version']
else:
return None
def _find_version_for(self, project, package):
# We have a cache here because we want to avoid doing SQL queries.
# See also comment in create_cache()
if self.version_cache is None:
return self._find_version_for_sql(project, package)
try:
return self.version_cache[project][package]
except KeyError:
return None
def _get_package_node_from_row(self, row, ignore_upstream, default_parent_project):
name = row['name']
version = row['version']
link_project = row['link_project']
link_package = row['link_package']
devel_project = row['devel_project']
devel_package = row['devel_package']
upstream_version = row['upstream_version']
upstream_url = row['upstream_url']
is_link = row['is_obs_link']
has_delta = row['obs_link_has_delta']
error = row['obs_error']
error_details = row['obs_error_details']
parent_version = None
devel_version = None
package = ET.Element('package')
package.set('name', name)
if link_project:
if (link_project != default_parent_project) or (link_package and link_package != name):
node = ET.SubElement(package, 'parent')
node.set('project', link_project)
if link_package and link_package != name:
node.set('package', link_package)
parent_version = self._find_version_for(link_project, link_package or name)
elif default_parent_project:
parent_version = self._find_version_for(default_parent_project, name)
if devel_project:
node = ET.SubElement(package, 'devel')
node.set('project', devel_project)
if devel_package and devel_package != name:
node.set('package', devel_package)
devel_version = self._find_version_for(devel_project, devel_package or name)
if version or upstream_version or parent_version or devel_version:
node = ET.SubElement(package, 'version')
if version:
node.set('current', version)
if upstream_version:
node.set('upstream', upstream_version)
if parent_version:
node.set('parent', parent_version)
if devel_version:
node.set('devel', devel_version)
if upstream_url:
upstream = ET.SubElement(package, 'upstream')
if upstream_url:
node = ET.SubElement(upstream, 'url')
node.text = upstream_url
if is_link:
node = ET.SubElement(package, 'link')
if has_delta:
node.set('delta', 'true')
else:
node.set('delta', 'false')
# deep delta (ie, delta in non-link packages)
elif has_delta:
node = ET.SubElement(package, 'delta')
if error:
node = ET.SubElement(package, 'error')
node.set('type', error)
if error_details:
node.text = error_details
return package
def get_package_node(self, project, package):
self.cursor.execute(libdbcore.pkg_query, (project, package))
row = self.cursor.fetchone()
if not row:
raise InfoXmlException('Non existing package in project %s: %s' % (project, package))
self.cursor_helper.execute('''SELECT * FROM %s WHERE name = ?;''' % libdbcore.table_project, (project,))
row_helper = self.cursor_helper.fetchone()
parent_project = row_helper['parent']
ignore_upstream = row_helper['ignore_upstream']
return self._get_package_node_from_row(row, ignore_upstream, parent_project)
def get_project_node(self, project, filled = True, write_cache = False):
if filled:
prj_node = self._read_cache(project)
if prj_node is not None:
return prj_node
self.cursor.execute('''SELECT * FROM %s WHERE name = ?;''' % libdbcore.table_project, (project,))
row = self.cursor.fetchone()
if not row:
raise InfoXmlException('Non existing project: %s' % project)
project_id = row['id']
parent_project = row['parent']
ignore_upstream = row['ignore_upstream']
prj_node = ET.Element('project')
prj_node.set('name', project)
if parent_project:
prj_node.set('parent', parent_project)
if ignore_upstream:
prj_node.set('ignore_upstream', 'true')
if not filled:
return prj_node
should_exist = {}
self.cursor.execute('''SELECT A.name AS parent_project, B.name AS parent_package, B.devel_package FROM %s AS A, %s AS B WHERE A.id = B.project AND devel_project = ? ORDER BY A.name, B.name;''' % (libdbcore.table_project, libdbcore.table_srcpackage), (project,))
for row in self.cursor:
should_parent_project = row['parent_project']
should_parent_package = row['parent_package']
should_devel_package = row['devel_package'] or should_parent_package
should_exist[should_devel_package] = (should_parent_project, should_parent_package)
self.cursor.execute('''SELECT * FROM %s WHERE project = ? ORDER BY name;''' % libdbcore.table_srcpackage, (project_id,))
for row in self.cursor:
pkg_node = self._get_package_node_from_row(row, ignore_upstream, parent_project)
prj_node.append(pkg_node)
try:
del should_exist[row['name']]
except KeyError:
pass
if len(should_exist) > 0:
missing_node = ET.Element('missing')
for (should_package_name, (should_parent_project, should_parent_package)) in should_exist.iteritems():
missing_pkg_node = ET.Element('package')
missing_pkg_node.set('name', should_package_name)
missing_pkg_node.set('parent_project', should_parent_project)
if should_package_name != should_parent_package:
missing_pkg_node.set('parent_package', should_parent_package)
missing_node.append(missing_pkg_node)
prj_node.append(missing_node)
if write_cache:
self._write_cache(project, prj_node)
return prj_node
def _get_cache_path(self, project):
return os.path.join(self.cache_dir, project + '.xml')
def _read_cache(self, project):
cache = self._get_cache_path(project)
try:
if os.path.exists(cache):
return ET.parse(cache).getroot()
except:
pass
return None
def _write_cache(self, project, node):
cache = self._get_cache_path(project)
try:
if os.path.exists(cache):
return
dirname = os.path.dirname(cache)
if not os.path.exists(dirname):
os.makedirs(dirname)
if not os.access(dirname, os.W_OK):
return
tree = ET.ElementTree(node)
tree.write(cache)
except:
pass
def create_cache(self, verbose = False):
try:
if not os.path.exists(self.cache_dir):
os.makedirs(self.cache_dir)
except Exception, e:
raise InfoXmlException('Cannot create cache directory (%s).' % e)
if not os.access(self.cache_dir, os.W_OK):
raise InfoXmlException('No write access.')
self.cursor.execute('''SELECT name FROM %s;''' % libdbcore.table_project)
# We need to first take all names because cursor will be re-used
projects = [ row['name'] for row in self.cursor ]
# Create the cache containing version of all packages. This will help
# us avoid doing many small SQL queries, which is really slow.
#
# The main difference is that we do one SQL query + many hash accesses,
# vs 2*(total number of packages in the database) SQL queries. On a
# test run, the difference results in ~1min15s vs ~5s. That's a 15x
# time win.
self.version_cache = {}
for project in projects:
self.version_cache[project] = {}
self.cursor.execute('''SELECT A.name, A.version, B.name AS project FROM %s AS A, %s AS B WHERE A.project = B.id;''' % (libdbcore.table_srcpackage, libdbcore.table_project))
for row in self.cursor:
self.version_cache[row['project']][row['name']] = row['version']
for project in projects:
self.get_project_node(project, write_cache = True)
if verbose:
print 'Wrote cache for %s.' % project
#if __name__ == '__main__':
# try:
# info = InfoXml()
# info.cache_dir = XML_CACHE_DIR + '-test'
# info.create_cache()
# except KeyboardInterrupt:
# pass
07070100000042000081ED0000000000000000000000016548EB8C00001BD7000000000000000000000000000000000000003400000000osc-plugin-collab-0.104+30/server/web/obs-upload.py#!/usr/bin/env python
# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2008-2010, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sys
import base64
import cgi
import socket
import shutil
from libdissector import config
from libdissector import libinfoxml
# Upload with:
# # Upload the db
# curl --silent --show-error -F dbfile=@/path/to/obs.db http://server/path/obs-upload.py
UPLOAD_DIR = config.datadir
AUTHORIZED_IPS = config.upload_authorized_ips
AUTHORIZED_HOSTS = config.upload_authorized_hosts
def log_error (s):
print >>sys.stderr, 'obs-upload: %s' % s,
def save_uploaded_file_internal (filename, fileitem, tmppath, destpath):
fout = file (tmppath, 'wb')
size = 0
complete = False
while 1:
chunk = fileitem.file.read(100000)
if not chunk:
complete = True
break
size = size + len(chunk)
# if bigger than 15MB, we give up. This way, it's not possible to fill
# the disk
# FWIW: file size was 2683904 on 2009-03-28
# file size was 12480512 on 2009-07-25
# file size was 10097664 on 2009-08-28
# file size was 9393152 on 2009-08-31
if size > 1024*1024*15:
break
fout.write (chunk)
fout.close()
if not complete:
print 'File upload cancelled: file is too big'
log_error ('File upload cancelled: file is too big')
return False
if filename == 'obs.db':
if size < 1024*1024*8:
print 'File upload cancelled: file is not as expected'
log_error ('File upload cancelled: obs.db too small (%d)' % size)
return False
try:
os.rename(tmppath, destpath)
return True
except:
print 'File upload cancelled: cannot rename file'
log_error ('File upload cancelled: cannot rename file')
return False
def save_uploaded_file (form, form_field, upload_dir, filename):
if not form.has_key(form_field):
return False
fileitem = form[form_field]
if not fileitem.file:
return False
# force the filename where we save, so we're not remote exploitable
tmppath = os.path.join(upload_dir, filename + '.tmp')
destpath = os.path.join(upload_dir, filename)
try:
if not os.path.exists(upload_dir):
os.makedirs(upload_dir)
ret = save_uploaded_file_internal (filename, fileitem, tmppath, destpath)
except Exception, e:
print 'Unknown error'
log_error ('Unknown error: %s' % str(e))
ret = False
if os.path.exists(tmppath):
os.unlink(tmppath)
return ret
def create_cache(filename):
if filename != 'obs.db':
return
try:
info = libinfoxml.InfoXml()
except Exception, e:
print 'Unknown error when accessing the database'
log_error ('Unknown error when accessing the database: %s' % str(e))
return
if os.path.exists(info.cache_dir) and not os.access(info.cache_dir, os.W_OK):
print 'Cannot verify database: no access'
log_error ('Cannot verify database: no access')
return
# We'll first write to a temporary directory since it's a long operation
# and we don't want to make data unavailable
cache_dir = info.cache_dir
tmp_cache_dir = info.cache_dir + '.tmp'
bak_cache_dir = info.cache_dir + '.bak'
info.cache_dir = tmp_cache_dir
# Remove this check: worst case, we'll have data about a project that
# doesn't exist anymore or we'll overwrite a cache file that was just
# created. In both cases, it's not a big deal -- especially since this
# shouldn't stay long in time.
## This is racy (check for existence and then creation), but it should be
## okay in the end since there is only one client
#if os.path.exists(info.cache_dir):
# print 'Cannot verify database: already verifying'
# return
try:
info.create_cache()
# First move the old cache away before installing the new one (fast
# operation), and then nuke the old cache
if os.path.exists(bak_cache_dir):
shutil.rmtree(bak_cache_dir)
if os.path.exists(cache_dir):
os.rename(cache_dir, bak_cache_dir)
os.rename(tmp_cache_dir, cache_dir)
if os.path.exists(bak_cache_dir):
shutil.rmtree(bak_cache_dir)
except Exception, e:
print 'Cannot verify database'
log_error ('Cannot verify database: no access (%s)' % str(e))
try:
if os.path.exists(tmp_cache_dir):
shutil.rmtree(tmp_cache_dir)
if os.path.exists(bak_cache_dir):
if not os.path.exists(cache_dir):
os.rename(bak_cache_dir, cache_dir)
else:
shutil.rmtree(bak_cache_dir)
except:
pass
print 'content-type: text/html\n'
form = cgi.FieldStorage()
if form.has_key('destfile'):
dest = form.getfirst('destfile')
if not dest in ['obs.db']:
print 'Unknown file'
sys.exit(0)
else:
# Just assume it's the standard database
dest = 'obs.db'
authorized_ips = AUTHORIZED_IPS[:]
for host in AUTHORIZED_HOSTS:
try:
ip = socket.gethostbyname(host)
authorized_ips.append(ip)
except:
pass
if os.environ['REMOTE_ADDR'] in authorized_ips:
ret = save_uploaded_file (form, 'dbfile', UPLOAD_DIR, dest)
if ret and dest in ['obs.db']:
create_cache(dest)
else:
print 'Unauthorized access'
07070100000043000081ED0000000000000000000000016548EB8C0000274E000000000000000000000000000000000000002D00000000osc-plugin-collab-0.104+30/server/web/obs.py#!/usr/bin/env python
# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2008-2010, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sys
import cgi
from cgi import escape
from libdissector import config
from libdissector import libdbhtml
from libdissector import libhttp
from libdissector import libinfoxml
if config.cgitb:
import cgitb; cgitb.enable()
#######################################################################
def compare_versions_a_gt_b (a, b):
split_a = a.split('.')
split_b = b.split('.')
if len(split_a) != len(split_b):
return a > b
for i in range(len(split_a)):
try:
int_a = int(split_a[i])
int_b = int(split_b[i])
if int_a > int_b:
return True
if int_b > int_a:
return False
except ValueError:
if split_a[i] > split_b[i]:
return True
if split_b[i] > split_a[i]:
return False
return False
#######################################################################
def colortype_to_style(colortype):
if colortype is None:
return ''
colors = {
'not-in-parent': ('#75507b', 'white'),
'delta': ('#3465a4', 'white'),
'no-upstream': ('#fce94f', None),
'new-upstream': ('#a40000', 'white')
}
(bg, text) = colors[colortype]
if bg or text:
style = ' style="'
if bg:
style += 'background: %s;' % bg
if text:
style += 'color: %s;' % text
style += '"'
else:
style = ''
return style
#######################################################################
def get_legend_box():
s = ''
s += '<div id="some_other_content" class="box box-shadow alpha clear-both">\n'
s += '<h2 class="box-header">Legend</h2>\n'
s += '<table>\n'
s += '<tr><td>Package is perfect!</td></tr>\n'
s += '<tr><td%s>Does not exist in parent</td></tr>\n' % colortype_to_style('not-in-parent')
s += '<tr><td%s>Has delta with parent</td></tr>\n' % colortype_to_style('delta')
s += '<tr><td%s>No upstream data</td></tr>\n' % colortype_to_style('no-upstream')
s += '<tr><td%s>Upstream has a new version</td></tr>\n' % colortype_to_style('new-upstream')
s += '</table>\n'
s += '</div>\n'
return s
#######################################################################
class Package:
def __init__(self, node):
self.name = None
self.parent_project = None
self.version = None
self.upstream_version = None
self.parent_version = None
self.upstream_url = None
self.has_delta = False
self.error = None
self.error_details = None
if node is not None:
self.name = node.get('name')
parent = node.find('parent')
if parent is not None:
self.parent_project = parent.get('project')
version = node.find('version')
if version is not None:
self.version = version.get('current')
self.upstream_version = version.get('upstream')
self.parent_version = version.get('parent')
upstream = node.find('upstream')
if upstream is not None:
url = upstream.find('url')
if url is not None:
self.upstream_url = url.text
link = node.find('link')
if link is not None:
if link.get('delta') == 'true':
self.has_delta = True
delta = node.find('delta')
if delta is not None:
self.has_delta = True
error = node.find('error')
if error is not None:
self.error = error.get('type')
self.error_details = error.text or ''
else:
self.error = ''
self.error_details = ''
if not self.version:
self.version = ''
if not self.upstream_version:
self.upstream_version = ''
if not self.parent_version:
self.parent_version = '--'
if not self.upstream_url:
self.upstream_url = ''
#######################################################################
def get_colortype(package, parent, use_upstream):
color = None
if parent and package.has_delta:
color = 'delta'
elif parent and package.parent_version == '--':
color = 'not-in-parent'
if use_upstream:
if package.upstream_version not in [ '', '--' ]:
newer_than_parent = package.parent_version == '--' or compare_versions_a_gt_b(package.upstream_version, package.parent_version)
newer = compare_versions_a_gt_b(package.upstream_version, package.version)
if newer and newer_than_parent:
color = 'new-upstream'
elif color is None and package.upstream_version != '--':
color = 'no-upstream'
return color
#######################################################################
def get_table_for_project(project, only_missing_upstream, only_missing_parent):
info = libinfoxml.InfoXml()
try:
node = info.get_project_node(project)
except libinfoxml.InfoXmlException, e:
return 'Error: %s' % e.msg
parent = node.get('parent')
use_upstream = node.get('ignore_upstream') != 'true'
packages = []
for subnode in node.findall('package'):
package = Package(subnode)
if only_missing_upstream and use_upstream:
if package.upstream_url:
continue
if package.upstream_version == '--':
continue
elif only_missing_parent and parent:
if package.parent_version != '--':
continue
packages.append(package)
if len(packages) == 0:
return 'No package in %s.' % escape(project)
if parent:
same_parent = True
for package in packages:
if package.parent_project and package.parent_project != project and package.parent_project != parent:
same_parent = False
break
s = ''
s += '<h2>%s source packages in %s</h2>\n' % (len(packages), escape(project))
s += '<table>\n'
s += '<tr>\n'
s += '<th>Package</th>\n'
if parent:
if same_parent:
s += '<th>%s</th>\n' % escape(parent)
else:
s += '<th>%s</th>\n' % 'Parent project'
s += '<th>%s</th>\n' % escape(project)
if use_upstream:
s += '<th>Upstream</th>\n'
s += '</tr>\n'
for package in packages:
colortype = get_colortype(package, parent, use_upstream)
style = colortype_to_style(colortype)
row = '<tr><td%s>%s</td>' % (style, escape(package.name))
if parent:
row += '<td>%s</td>' % (escape(package.parent_version),)
if package.error in ['not-in-parent', 'need-merge-with-parent']:
if package.error_details:
row += '<td title="%s">%s</td>' % (escape(package.error_details), '(broken)')
else:
row += '<td title="%s">%s</td>' % (escape(package.error), '(broken)')
else:
row += '<td>%s</td>' % escape(package.version)
if use_upstream:
if package.upstream_url and package.upstream_url != '':
version_cell = '<a href="' + escape(package.upstream_url) + '">' + escape(package.upstream_version) + '</a>'
else:
version_cell = escape(package.upstream_version)
row += '<td>%s</td>' % version_cell
row += '</tr>'
s += row
s += '\n'
s += '</table>\n'
return s
#######################################################################
form = cgi.FieldStorage()
only_missing_upstream = libhttp.get_arg_bool(form, 'missing-upstream', False)
only_missing_parent = libhttp.get_arg_bool(form, 'missing-parent', False)
libhttp.print_html_header()
project = libhttp.get_project(form)
table = get_table_for_project(project, only_missing_upstream, only_missing_parent)
libhttp.print_header('Versions of packages in the Build Service for project %s' % escape(project))
print libdbhtml.get_project_selector(current_project = project)
print table
libhttp.print_foot(additional_box = get_legend_box())
07070100000044000081ED0000000000000000000000016548EB8C00001FD5000000000000000000000000000000000000002F00000000osc-plugin-collab-0.104+30/server/web/patch.py#!/usr/bin/env python
# vim: set ts=4 sw=4 et: coding=UTF-8
#
# Copyright (c) 2008-2010, Novell, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# (Licensed under the simplified BSD license)
#
# Authors: Vincent Untz <[email protected]>
#
import os
import sys
import cgi
from cgi import escape
from libdissector import buildservice
from libdissector import config
from libdissector import libdbcore
from libdissector import libdbhtml
from libdissector import libhttp
if config.cgitb:
import cgitb; cgitb.enable()
#######################################################################
def get_page_title(project, srcpackage, tag):
if project and srcpackage and tag:
return 'Patches tagged %s for package %s in project %s' % (escape(tag), escape(srcpackage), escape(project))
elif project and srcpackage:
return 'Patches for package %s in project %s' % (escape(srcpackage), escape(project))
elif project and tag:
return 'Patches tagged %s in project %s' % (escape(tag), escape(project))
elif project:
return 'Patches in project %s' % (escape(project))
else:
return 'Patches'
#######################################################################
def get_package(db, project, srcpackage, tag):
db.cursor.execute(libdbcore.pkg_query, (project, srcpackage))
row = db.cursor.fetchone()
if not row:
return 'Error: package %s does not exist in project %s' % (escape(project), escape(srcpackage))
if row['is_obs_link'] and row['srcmd5']:
rev = row['srcmd5']
else:
rev = None
if tag:
db.cursor.execute('''SELECT * FROM %s WHERE srcpackage = ? AND tag = ? ORDER BY nb_in_pack;''' % libdbcore.table_patch, (row['id'], tag))
else:
db.cursor.execute('''SELECT * FROM %s WHERE srcpackage = ? ORDER BY nb_in_pack;''' % libdbcore.table_patch, (row['id'],))
s = ''
s += '<pre>\n'
count = 0
for row in db.cursor:
count += 1
url = buildservice.get_source_url(project, srcpackage, row['filename'], rev, True)
s += '%s: <a href=\"%s\">%s</a>' % (row['nb_in_pack'], url, row['filename'])
if row['disabled'] != 0:
s += ' (not applied)'
s += '\n'
s += '</pre>\n'
if tag:
s = '<h2>%d patches tagged %s for package %s in project %s</h2>\n' % (count, escape(tag), escape(srcpackage), escape(project)) + s
else:
s = '<h2>%d patches for package %s in project %s</h2>\n' % (count, escape(srcpackage), escape(project)) + s
return s
#######################################################################
def get_project(db, project, tag):
db.cursor.execute('''SELECT id FROM %s WHERE name = ?;''' % libdbcore.table_project, (project,))
row = db.cursor.fetchone()
if not row:
return 'Error: project %s does not exist' % escape(project)
project_id = row['id']
if tag == 'None':
tag_sql = ''
else:
tag_sql = tag
if tag:
db.cursor.execute('''SELECT COUNT(*) FROM %s, %s WHERE %s.srcpackage = %s.id AND %s.project = ? AND tag = ?;''' % (libdbcore.table_patch, libdbcore.table_srcpackage, libdbcore.table_patch, libdbcore.table_srcpackage, libdbcore.table_srcpackage) , (project_id, tag_sql))
else:
db.cursor.execute('''SELECT COUNT(*) FROM %s, %s WHERE %s.srcpackage = %s.id AND %s.project = ?;''' % (libdbcore.table_patch, libdbcore.table_srcpackage, libdbcore.table_patch, libdbcore.table_srcpackage, libdbcore.table_srcpackage) , (project_id,))
row = db.cursor.fetchone()
count = escape(str(row[0]))
s = ''
if tag:
s += '<h2>%s patches tagged %s in project %s</h2>\n<p>\n' % (count, escape(tag), escape(project))
db.cursor.execute('''SELECT COUNT(*) AS c, %s.name AS n FROM %s, %s WHERE %s.srcpackage = %s.id AND %s.project = ? AND tag = ? GROUP BY srcpackage ORDER BY c DESC;''' % (libdbcore.table_srcpackage, libdbcore.table_patch, libdbcore.table_srcpackage, libdbcore.table_patch, libdbcore.table_srcpackage, libdbcore.table_srcpackage), (project_id, tag_sql))
for row in db.cursor:
s += '<a href="%s?project=%s&srcpackage=%s&tag=%s">%s</a>: %s<br />\n' % (escape(os.environ['SCRIPT_NAME']), escape(project), escape(row['n']), escape(tag or ''), escape(row['n']), escape(str(row['c'])))
s += '</p>\n'
else:
s += '<h2>%s patches in project %s</h2>\n' % (count, escape(project))
s += '<h3>Order by tag</h3>\n<p>\n'
db.cursor.execute('''SELECT COUNT(*) AS c, tag FROM %s, %s WHERE %s.srcpackage = %s.id AND %s.project = ? GROUP BY tag ORDER BY c DESC;''' % (libdbcore.table_patch, libdbcore.table_srcpackage, libdbcore.table_patch, libdbcore.table_srcpackage, libdbcore.table_srcpackage), (project_id,))
for row in db.cursor:
if row['tag'] == '':
row_tag = 'None'
else:
row_tag = escape(row['tag'])
s += '<a href="%s?project=%s&tag=%s">%s</a>: %s<br />\n' % (escape(os.environ['SCRIPT_NAME']), escape(project), row_tag, row_tag, escape(str(row['c'])))
s += '</p>\n<h3>Order by source package</h3>\n<p>\n'
db.cursor.execute('''SELECT COUNT(*) AS c, %s.name AS n FROM %s, %s WHERE %s.srcpackage = %s.id AND %s.project = ? GROUP BY srcpackage ORDER BY c DESC;''' % (libdbcore.table_srcpackage, libdbcore.table_patch, libdbcore.table_srcpackage, libdbcore.table_patch, libdbcore.table_srcpackage, libdbcore.table_srcpackage), (project_id,))
for row in db.cursor:
s += '<a href="%s?project=%s&srcpackage=%s">%s</a>: %s<br />\n' % (escape(os.environ['SCRIPT_NAME']), escape(project), escape(row['n']), escape(row['n']), escape(str(row['c'])))
s += '</p>\n'
return s
#######################################################################
def get_page_content(db, project, srcpackage, tag):
if not project:
return 'Error: no project specified'
if srcpackage:
return get_package(db, project, srcpackage, tag)
else:
return get_project(db, project, tag)
#######################################################################
form = cgi.FieldStorage()
libhttp.print_html_header()
project = libhttp.get_project(form)
srcpackage = libhttp.get_srcpackage(form)
tag = libhttp.get_arg(form, 'tag')
db = libdbcore.ObsDb()
title = get_page_title(project, srcpackage, tag)
content = get_page_content(db, project, srcpackage, tag)
libhttp.print_header(title)
if not srcpackage:
print libdbhtml.get_project_selector(current_project = project, db = db)
print content
libhttp.print_foot()
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!1369 blocks