Export the built tree as git repo #9

Merged
Ghost merged 24 commits from add_export into main 2022-11-02 14:17:24 +01:00
34 changed files with 20683 additions and 4901 deletions

View File

@@ -3,20 +3,20 @@
import argparse
import logging
import pathlib
import shutil
import sys
import osc.core
from lib.exporter import Exporter
from lib.git_exporter import GitExporter
from lib.importer import Importer
from lib.test_exporter import TestExporter
URL_OBS = "https://api.opensuse.org"
URL_IBS = "https://api.suse.de"
# The order is relevant (from older to newer initial codebase)
# TODO: make something with these, for now we look purely at openSUSE:Factory
PROJECTS = [
("openSUSE:Factory", "factory", URL_OBS),
# ("SUSE:SLE-12:GA", "SLE_12", URL_IBS),
# ("SUSE:SLE-12:Update", "SLE_12", URL_IBS),
# ("SUSE:SLE-12-SP1:GA", "SLE_12_SP1", URL_IBS),
@@ -52,24 +52,6 @@ def main():
type=pathlib.Path,
help="Local git repository directory",
)
parser.add_argument(
"-f",
"--force",
action="store_true",
help="If the repository directory exists, remove it",
)
parser.add_argument(
"-a",
"--search-ancestor",
action="store_true",
help="Search closest ancestor candidate for initial commit",
)
parser.add_argument(
"-d",
"--rebase-devel",
action="store_true",
help="The devel project with be rebased after a merge",
)
parser.add_argument(
"-g",
"--gc",
@@ -84,15 +66,10 @@ def main():
default="INFO",
help="logging level",
)
parser.add_argument(
"--db",
action="store_true",
help="Import revisions into database only",
)
parser.add_argument(
"--export",
action="store_true",
help="Export database fields for the given package as JSON",
help="Export database fields for the given package as YAML",
)
args = parser.parse_args()
@@ -110,28 +87,17 @@ def main():
requests_log.propagate = True
if args.export:
Exporter(args.package).run()
TestExporter(args.package).run()
return
if not args.repodir:
args.repodir = pathlib.Path(args.package)
args.repodir = pathlib.Path("repos/" + args.package)
Ghost marked this conversation as resolved
Review

if in L47 use a type pathlib.Path, like in L52, you will be sure that a path is given, and this line can be args.repodir = "repos" / args.package

if in L47 use a type pathlib.Path, like in L52, you will be sure that a path is given, and this line can be `args.repodir = "repos" / args.package`
Review

But we need package for more than that

But we need package for more than that
if args.repodir.exists() and not args.force:
print(f"Repository {args.repodir} already present")
sys.exit(-1)
elif args.repodir.exists() and args.force:
logging.info(f"Removing old repository {args.repodir}")
shutil.rmtree(args.repodir)
# TODO: use a CLI parameter to describe the projects
importer = Importer(
PROJECTS, args.package, args.repodir, args.search_ancestor, args.rebase_devel
)
if args.db:
importer.import_into_db()
return
importer.import_all_revisions(args.gc)
importer = Importer(URL_OBS, "openSUSE:Factory", args.package)
importer.import_into_db()
exporter = GitExporter(URL_OBS, "openSUSE:Factory", args.package, args.repodir)
exporter.set_gc_interval(args.gc)
exporter.export_as_git()
if __name__ == "__main__":

6
lib/abstract_walker.py Normal file
View File

@@ -0,0 +1,6 @@
class AbstractWalker:
"""Just a duck type, most likely not needed by python, but I
find interface classes preferable (java school)"""
def call(self, node, is_source):
pass
Ghost marked this conversation as resolved
Review
https://docs.python.org/3/library/abc.html
Review

That confuses me more than it helps, but I'll dig into it :)

That confuses me more than it helps, but I'll dig into it :)

View File

@@ -1,5 +1,10 @@
from hashlib import md5
from __future__ import annotations
import logging
from hashlib import md5
from typing import Optional
from lib.db import DB
from lib.request import Request
@@ -25,7 +30,7 @@ class DBRevision:
self._files = None
def short_string(self):
return f"Rev {self.project}/{self.package}/{self.rev}"
return f"{self.project}/{self.package}/{self.rev}"
def __str__(self):
return f"Rev {self.project}/{self.package}/{self.rev} Md5 {self.unexpanded_srcmd5} {self.commit_time} {self.userid} {self.request_number}"
@@ -202,6 +207,29 @@ class DBRevision:
self._files.sort(key=lambda x: x["name"])
Ghost marked this conversation as resolved Outdated

Reading the code seems that current_rev is not optional.

Reading the code seems that `current_rev` is not optional.
Outdated
Review

There is a if current_rev - and it's treated as empty dict if there is nothing before.

There is a `if current_rev` - and it's treated as empty dict if there is nothing before.

but not in the call?

but not in the call?
Outdated
Review

well, the =None is not needed as there is always a variable passed. I guess nowadays I can make it current_rev: Optional[DbRevision]. Would that be more of your liking?

well, the `=None` is not needed as there is always a variable passed. I guess nowadays I can make it `current_rev: Optional[DbRevision]`. Would that be more of your liking?

What I am wondering is the semantic of the function. calc_delta seems to be from some status to self. When the other status is not None the old_files is empty making all to download, when I would expect that none would be listed to download.

That makes me wonder if current_rev can be optional.

What I am wondering is the semantic of the function. `calc_delta` seems to be from some status to `self`. When the other status is not `None` the `old_files` is empty making all to download, when I would expect that none would be listed to download. That makes me wonder if `current_rev` can be optional.
Outdated
Review

I even added documentation! :)

self is the revision to download and current_rev is the state of the repository. If it's None, there is no state and everything needs to be downloaded.

I even added documentation! :) `self` is the revision to download and `current_rev` is the state of the repository. If it's `None`, there is no state and everything needs to be downloaded.
return self._files
def calc_delta(self, db: DB, current_rev: Optional[DBRevision]):
"""Calculate the list of files to download and to delete.
Param current_rev is the revision that's currently checked out.
Ghost marked this conversation as resolved Outdated

(no optional)

(no optional)
If it's None, the repository is empty.
"""
Ghost marked this conversation as resolved Outdated

The last two lines: old_files = {e["name"]: f"{e['md5']}-{e['size']}" for e in current_rev.files_list(db)}

The last two lines: `old_files = {e["name"]: f"{e['md5']}-{e['size']}" for e in current_rev.files_list(db)}`
Outdated
Review

But current_rev is optional - no matter what you make out of it :)

But current_rev is optional - no matter what you make out of it :)

still the for two lines can be replaced

still the `for` two lines can be replaced
Outdated
Review

ah

ah
to_download = []
to_delete = []
Ghost marked this conversation as resolved
Review

old_files.get(entry["name"]) != ..., will return None if missing.

`old_files.get(entry["name"]) != ...`, will return `None` if missing.
Review

true, but an old database afine developer doesn't like comparing None with strings :)

true, but an old database afine developer doesn't like comparing None with strings :)
if current_rev:
old_files = {
Ghost marked this conversation as resolved Outdated
to_download = [e["name"] for e in self.files_list(db) if old_files.get(e["name"]) != f"{e['md5']}-{e['size']}"]
logging.debug(f"Downloading {to_download}")
```python to_download = [e["name"] for e in self.files_list(db) if old_files.get(e["name"]) != f"{e['md5']}-{e['size']}"] logging.debug(f"Downloading {to_download}") ```
Outdated
Review

That's outputting an array, which I don't want.

That's outputting an array, which I don't want.

right, the last line can be replaced with a for, but this change is for the for and if lines

right, the last line can be replaced with a `for`, but this change is for the `for` and `if` lines
Outdated
Review

What would be the benefit? I don't see it as more readable

What would be the benefit? I don't see it as more readable
e["name"]: f"{e['md5']}-{e['size']}" for e in current_rev.files_list(db)
}
else:
old_files = dict()

same

same
for entry in self.files_list(db):
if old_files.get(entry["name"]) != f"{entry['md5']}-{entry['size']}":
logging.debug(f"Download {entry['name']}")
to_download.append((entry["name"], entry["md5"]))
old_files.pop(entry["name"], None)
for entry in old_files.keys():
logging.debug(f"Delete {entry}")
to_delete.append(entry)
return to_download, to_delete
@staticmethod
def requests_to_fetch(db):
with db.cursor() as cur:

57
lib/flat_walker.py Normal file
View File

@@ -0,0 +1,57 @@
from lib.abstract_walker import AbstractWalker
class FlatNode:
def __init__(self, branch, commit, parent1=None, parent2=None) -> None:
self.branch = branch
self.commit = commit
self.parent1 = parent1
self.parent2 = parent2
def __str__(self) -> str:
p1_str = ""
if self.parent1:
p1_str = f" p1:{self.parent1.short_string()}"
Ghost marked this conversation as resolved
Review

p1_str = f" p1:{self.parent1.short_string()}" if self.parent1 else ""

`p1_str = f" p1:{self.parent1.short_string()}" if self.parent1 else ""`
p2_str = ""
if self.parent2:
p2_str = f" p2:{self.parent2.short_string()}"
return f"{self.branch} c:{self.commit.short_string()}{p1_str}{p2_str}"
class FlatTreeWalker(AbstractWalker):
"""While walking the tree, record the commits to do one after the other. These
FlatNodes are in the end in the flats array."""
def __init__(self, rebase_devel=False) -> None:
super().__init__()
self.flats = []
# the rebase_devel won't work as such as rebasing the branch needs an explicit action
self.rebase_devel = rebase_devel
# remember the last merge point so we can know the parent of it for the root of the sources
self.last_merge = None
def add(self, branch, commit, parent1=None, parent2=None):
self.flats.append(FlatNode(branch, commit, parent1, parent2))
def handle_source_node(self, node) -> None:
if self.rebase_devel and node.parent and node.parent.merged_into:
self.add("devel", node.revision, node.parent.merged_into.revision)
return
if node.parent:
Ghost marked this conversation as resolved
Review

making it elif can remove the return

making it `elif` can remove the `return`
self.add("devel", node.revision, node.parent.revision)
elif self.last_merge:
self.add("devel", node.revision, self.last_merge.parent.revision)
def call(self, node, is_source) -> None:
if is_source:
self.handle_source_node(node)
return
if not node.parent:
self.add("factory", node.revision)
return
if not node.merged:
self.add("factory", node.revision, node.parent.revision)
return
self.add("factory", node.revision, node.parent.revision, node.merged.revision)
self.last_merge = node

View File

@@ -181,8 +181,11 @@ class Git:
except:
return None
def branch_head(self, branch):
return self.repo.references["refs/heads/" + branch].target
def gc(self):
logging.info(f"Garbage recollec and repackage {self.path}")
logging.info(f"Garbage recollect and repackage {self.path}")
subprocess.run(
["git", "gc", "--auto"],
cwd=self.path,

171
lib/git_exporter.py Normal file
View File

@@ -0,0 +1,171 @@
import logging
import os
import yaml
from lib.binary import is_binary_or_large
from lib.db import DB
from lib.git import Git
from lib.obs import OBS
from lib.proxy_sha256 import ProxySHA256, md5
from lib.tree_builder import TreeBuilder
class GitExporter:
def __init__(self, api_url, project, package, repodir):
self.obs = OBS()
self.project = project
self.package = package
# TODO: Store the api url in the revision
self.obs.change_url(api_url)
self.proxy_sha256 = ProxySHA256(self.obs, enabled=True)
self.git = Git(
repodir,
committer="Git OBS Bridge",
committer_email="obsbridge@suse.de",
).create()
self.state_file = os.path.join(self.git.path, ".git", "_flat_state.yaml")
self.gc_interval = 200
def download(self, revision):
obs_files = self.obs.files(revision.project, revision.package, revision.srcmd5)
git_files = {
(f.name, f.stat().st_size, md5(f))
for f in self.git.path.iterdir()
if f.is_file() and f.name not in (".gitattributes")
}
# Overwrite ".gitattributes" with the
self.git.add_default_lfs_gitattributes(force=True)
# Download each file in OBS if it is not a binary (or large)
# file
for (name, size, file_md5) in obs_files:
# this file creates easily 100k commits and is just useless data :(
# unfortunately it's stored in the same meta package as the project config
if revision.package == "_project" and name == "_staging_workflow":
continue
# have such files been detected as text mimetype before?
is_text = self.proxy_sha256.is_text(name)
if not is_text and is_binary_or_large(name, size):
file_sha256 = self.proxy_sha256.get_or_put(
revision.project,
revision.package,
name,
revision.srcmd5,
file_md5,
size,
)
self.git.add_lfs(name, file_sha256["sha256"], size)
else:
if (name, size, file_md5) not in git_files:
logging.debug(f"Download {name}")
self.obs.download(
revision.project,
revision.package,
name,
revision.srcmd5,
self.git.path,
file_md5=file_md5,
)
# Validate the MD5 of the downloaded file
if md5(self.git.path / name) != file_md5:
raise Exception(f"Download error in {name}")
self.git.add(name)
# Remove extra files
obs_names = {n for (n, _, _) in obs_files}
git_names = {n for (n, _, _) in git_files}
for name in git_names - obs_names:
logging.debug(f"Remove {name}")
self.git.remove(name)
def set_gc_interval(self, gc):
self.gc_interval = gc
def export_as_git(self):
db = DB()
tree = TreeBuilder(db).build(self.project, self.package)
flats = tree.as_flat_list()
branch_state = {"factory": None, "devel": None}
state_data = dict()
if os.path.exists(self.state_file):
with open(self.state_file, "r") as f:
state_data = yaml.safe_load(f)
if type(state_data) != dict:
state_data = {}
left_to_commit = []
for flat in reversed(flats):
found_state = False
for branch in ["factory", "devel"]:
if flat.commit.dbid == state_data.get(branch):
branch_state[branch] = flat.commit
flat.commit.git_commit = self.git.branch_head(branch)
logging.debug(
f"Found {self.git.path}'s {branch} branch in state {flat}"
)
left_to_commit = []
found_state = True
if not found_state:
left_to_commit.append(flat)
gc_cnt = self.gc_interval
if len(left_to_commit) > 0:
self.git.gc()
for flat in left_to_commit:
gc_cnt -= 1
if gc_cnt <= 0 and self.gc_interval:
self.git.gc()
gc_cnt = self.gc_interval
logging.debug(f"Committing {flat}")
self.commit_flat(db, flat, branch_state)
def limit_download(self, file):
if file.endswith(".spec") or file.endswith(".changes"):
return True
Ghost marked this conversation as resolved
Review

return file.endswith((".spec", ".changes"))

But today it is better if all files are pathlib.Path objects, so you can do return file.suffix in (".spec", ".changes")

`return file.endswith((".spec", ".changes"))` But today it is better if all files are pathlib.Path objects, so you can do `return file.suffix in (".spec", ".changes")`
return False
def commit_flat(self, db, flat, branch_state):
parents = []
self.git.checkout(flat.branch)
if flat.parent1:
parents.append(flat.parent1.git_commit)
if flat.parent2:
parents.append(flat.parent2.git_commit)
to_download, to_delete = flat.commit.calc_delta(db, branch_state[flat.branch])
for file in to_delete:
if not self.limit_download(file):
continue
self.git.remove(file)
for file, md5 in to_download:
if not self.limit_download(file):
continue
self.obs.download(
flat.commit.project,
flat.commit.package,
file,
flat.commit.expanded_srcmd5,
self.git.path,
file_md5=md5,
)
self.git.add(file)
commit = self.git.commit(
f"OBS User {flat.commit.userid}",
"null@suse.de",
flat.commit.commit_time,
# TODO: Normalize better the commit message
f"{flat.commit.comment}\n\n{flat.commit}",
allow_empty=True,
parents=parents,
)
flat.commit.git_commit = commit
branch_state[flat.branch] = flat.commit
with open(self.state_file, "w") as f:
data = {}
for branch in ["factory", "devel"]:
commit = branch_state[branch]
if commit:
data[branch] = commit.dbid
yaml.dump(data, f)

View File

@@ -1,106 +0,0 @@
import itertools
import logging
import re
from lib.obs_revision import OBSRevision
class History:
"""Store the history of revisions of a package in different
projects.
"""
def __init__(self, obs, package):
self.obs = obs
self.package = package
self.revisions = {}
def __contains__(self, project):
return project in self.revisions
def __getitem__(self, project):
return self.revisions[project]
def _extract_copypac(self, comment):
original_project = re.findall(
r"osc copypac from project:(.*) package:", comment
)
return original_project[0] if original_project else None
def _fetch_revisions(self, project, **params):
root = self.obs._history(project, self.package, **params)
if root is not None:
return [
OBSRevision(self.obs, self, project, self.package).parse(r)
for r in root.findall("revision")
]
def fetch_revisions(self, project, follow_copypac=False):
"""Get the revision history of a package"""
if project in self:
return
revs = self._fetch_revisions(project)
self.revisions[project] = revs
# while (
# revs
# and follow_copypac
# and (copypac_project := self._extract_copypac(revs[0].comment))
# ):
# # Add the history pre-copypac
# # TODO: missing the old project name
# revs = self._fetch_revisions(copypac_project, deleted=1)
# self.revisions[project] = (
# revs + self.revisions[project]
# )
def fetch_all_revisions(self, projects):
"""Pre-populate the history"""
for project, _, api_url in projects:
self.obs.change_url(api_url)
self.fetch_revisions(project)
def sort_all_revisions(self):
"""Sort revisions for all projects, from older to newer"""
return sorted(itertools.chain(*self.revisions.values()), key=lambda x: x.time)
def find_revision(self, project, revisionid, accepted_at):
last_commited_revision = None
for r in self.revisions.get(project, []):
logging.debug(f"Find revision {revisionid} [{accepted_at}]: {r}")
if str(r.rev) == str(revisionid) or r.srcmd5 == revisionid:
if r.ignored:
logging.debug(
f"{r} fits but is ignored, returning {last_commited_revision}"
)
return last_commited_revision
else:
logging.debug(f"{r} fits")
return r
if r.time > accepted_at:
# if we can't find the right revision, we take the last
# commit. Before ~2012 the data was tracked really loosely
# (e.g. using different timezones and the state field was
# only introduced in 2016...)
logging.warning(
f"Deploying workaround for missing request revision - returning {last_commited_revision}"
)
return last_commited_revision
if r.commit:
last_commited_revision = r
logging.info("No commited revision found, returning None")
return None
def find_last_rev_after_time(self, project, time):
# revs = self.projects.get(project, [])
# return next((r for r in reversed(revs) if r.time <= time), None)
prev = None
for rev in self.revisions.get(project, []):
if rev.time > time:
return prev
if rev.time == time:
return rev
prev = rev
return prev

View File

@@ -1,132 +1,23 @@
import functools
import logging
import xml.etree.ElementTree as ET
import psycopg2
from lib.binary import is_binary_or_large
from lib.db import DB
from lib.db_revision import DBRevision
from lib.git import Git
from lib.history import History
from lib.obs import OBS
from lib.obs_revision import OBSRevision
from lib.proxy_sha256 import ProxySHA256, md5, sha256
from lib.tree_builder import TreeBuilder
from lib.user import User
def _files_hash(hash_alg, dirpath):
"""List of (filepath, md5) for a directory"""
# TODO: do it async or multythread
files = [f for f in dirpath.iterdir() if f.is_file()]
return [(f.parts[-1], hash_alg(f)) for f in files]
files_md5 = functools.partial(_files_hash, md5)
files_sha256 = functools.partial(_files_hash, sha256)
class Importer:
def __init__(self, projects, package, repodir, search_ancestor, rebase_devel):
# The idea is to create each commit in order, and draw the
# same graph described by the revisions timeline. For that we
# need first to fetch all the revisions and sort them
# linearly, based on the timestamp.
#
# After that we recreate the commits, and if one revision is a
# request that contains a target inside the projects in the
# "history", we create a merge commit.
#
# Optionally, if a flag is set, we will try to find a common
# "Initial commit" from a reference branch (the first one in
# "projects", that is safe to assume to be "openSUSE:Factory".
# This is not always a good idea. For example, in a normal
# situation the "devel" project history is older than
# "factory", and we can root the tree on it. But for some
# other projects we lost partially the "devel" history project
# (could be moved), and "factory" is not the root.
def __init__(self, api_url, project, package):
# Import a Factory package into the database
self.package = package
self.search_ancestor = search_ancestor
self.rebase_devel = rebase_devel
self.project = project
self.obs = OBS()
self.git = Git(
repodir,
committer="Git OBS Bridge",
committer_email="obsbridge@suse.de",
).create()
self.proxy_sha256 = ProxySHA256(self.obs, enabled=True)
self.history = History(self.obs, self.package)
# Add the "devel" project
(project, branch, api_url) = projects[0]
assert project == "openSUSE:Factory"
self.obs.change_url(api_url)
devel_project = self.obs.devel_project(project, package)
if devel_project:
self.projects = [(devel_project, "devel", api_url)] + projects
else:
self.projects = projects
# Associate the branch and api_url information per project
self.projects_info = {
project: (branch, api_url) for (project, branch, api_url) in self.projects
}
def download(self, revision):
obs_files = self.obs.files(revision.project, revision.package, revision.srcmd5)
git_files = {
(f.name, f.stat().st_size, md5(f))
for f in self.git.path.iterdir()
if f.is_file() and f.name not in (".gitattributes")
}
# Overwrite ".gitattributes" with the
self.git.add_default_lfs_gitattributes(force=True)
# Download each file in OBS if it is not a binary (or large)
# file
for (name, size, file_md5) in obs_files:
# this file creates easily 100k commits and is just useless data :(
# unfortunately it's stored in the same meta package as the project config
if revision.package == "_project" and name == "_staging_workflow":
continue
# have such files been detected as text mimetype before?
is_text = self.proxy_sha256.is_text(name)
if not is_text and is_binary_or_large(name, size):
file_sha256 = self.proxy_sha256.get_or_put(
revision.project,
revision.package,
name,
revision.srcmd5,
file_md5,
size,
)
self.git.add_lfs(name, file_sha256["sha256"], size)
else:
if (name, size, file_md5) not in git_files:
print(f"Download {name}")
self.obs.download(
revision.project,
revision.package,
name,
revision.srcmd5,
self.git.path,
)
# Validate the MD5 of the downloaded file
if md5(self.git.path / name) != file_md5:
raise Exception(f"Download error in {name}")
self.git.add(name)
# Remove extra files
obs_names = {n for (n, _, _) in obs_files}
git_names = {n for (n, _, _) in git_files}
for name in git_names - obs_names:
print(f"Remove {name}")
self.git.remove(name)
self.refreshed_packages = set()
def update_db_package(self, db, project, package):
root = self.obs._history(project, package)
@@ -134,7 +25,7 @@ class Importer:
return
latest = DBRevision.latest_revision(db, project, package)
for r in root.findall("revision"):
rev = OBSRevision(self.obs, self, project, package).parse(r)
rev = OBSRevision(self.obs, project, package).parse(r)
if not latest or rev.rev > latest.rev:
dbrev = DBRevision.import_obs_rev(db, rev)
try:
@@ -175,7 +66,8 @@ class Importer:
)
for row in cur.fetchall():
(lproject, lpackage) = row
self.update_db_package(db, lproject, lpackage)
# recurse
self.refresh_package(db, lproject, lpackage)
def find_fake_revisions(self, db):
with db.cursor() as cur:
@@ -259,20 +151,8 @@ class Importer:
)
return [DBRevision(row) for row in cur.fetchall()]
def import_into_db(self):
db = DB()
for project, _, api_url in self.projects:
self.obs.change_url(api_url)
self.update_db_package(db, project, self.package)
self.fetch_all_linked_packages(db, project, self.package)
# all remaining, no filtering here
self.find_linked_revs(db)
missing_users = User.missing_users(db)
for userid in missing_users:
missing_user = self.obs.user(userid)
if missing_user:
missing_user.import_into_db(db)
def fill_file_lists(self, db):
self.find_linked_revs(db)
self.find_fake_revisions(db)
for rev in self.revisions_without_files(db):
@@ -299,241 +179,35 @@ class Importer:
else:
rev.set_broken(db)
def refresh_package(self, db, project, package):
key = f"{project}/{package}"
if key in self.refreshed_packages:
# refreshing once is good enough
return
self.refreshed_packages.add(key)
self.update_db_package(db, project, package)
self.fetch_all_linked_packages(db, project, package)
def import_into_db(self):
db = DB()
self.refresh_package(db, self.project, self.package)
for number in DBRevision.requests_to_fetch(db):
self.obs.request(number).import_into_db(db)
with db.cursor() as cur:
cur.execute(
"""SELECT DISTINCT source_project,source_package FROM requests
WHERE id IN (SELECT request_id FROM revisions WHERE project=%s and package=%s);""",
(self.project, self.package),
)
for project, package in cur.fetchall():
self.refresh_package(db, project, package)
missing_users = User.missing_users(db)
for userid in missing_users:
missing_user = self.obs.user(userid)
if missing_user:
missing_user.import_into_db(db)
self.fill_file_lists(db)
db.conn.commit()
TreeBuilder(db).build(self.package).print()
def import_all_revisions(self, gc):
# Fetch all the requests and sort them. Ideally we should
# build the graph here, to avoid new commits before the merge.
# For now we will sort them and invalidate the commits if
# "rebase_devel" is set.
self.history.fetch_all_revisions(self.projects)
revisions = self.history.sort_all_revisions()
logging.debug(f"Selected import order for {self.package}")
for revision in revisions:
logging.debug(revision)
gc_cnt = gc
for revision in revisions:
gc_cnt -= 1
if gc_cnt <= 0 and gc:
self.git.gc()
gc_cnt = gc
self.import_revision(revision)
def import_new_revision_with_request(self, revision, request):
"""Create a new branch as a result of a merge"""
submitted_revision = self.history.find_revision(
request.source, request.revisionid, revision.time
)
if not submitted_revision:
logging.warning(f"Request {request} does not connect to a known revision")
return False
if not submitted_revision.commit:
# If the revision appointed by the request is not part of
# the git history, we can have an ordering problem. One
# example is "premake4".
self.import_revision(submitted_revision)
assert submitted_revision.commit is not None
project = revision.project
branch, _ = self.projects_info[project]
# TODO: add an empty commit marking the acceptenace of the request (see discussion in PR 2858)
self.git.branch(branch, submitted_revision.commit)
self.git.clean()
self.git.checkout(branch)
logging.info(f"Create new branch based on {submitted_revision.commit}")
revision.commit = submitted_revision.commit
def _rebase_branch_history(self, project, revision):
branch, _ = self.projects_info[project]
history = self.history[project]
revision_index = history.index(revision)
for index in range(revision_index + 1, len(history)):
revision = history[index]
# We are done when we have one non-commited revision
if not revision.commit:
return
logging.info(f"Rebasing {revision} from {branch}")
revision.commit = None
self.import_revision(revision)
def import_revision_with_request(self, revision, request):
"""Import a single revision via a merge"""
submitted_revision = self.history.find_revision(
request.source, request.revisionid, revision.time
)
if not submitted_revision:
logging.warning(f"Request {request} does not connect to a known revision")
return False
assert submitted_revision.commit is not None
# TODO: detect a revision, case in point
# Base:System/bash/284 -> rq683701 -> accept O:F/151
# -> autocommit Base:System/bash/285
# Revert lead to openSUSE:Factory/bash/152
# Base:System/286 restored the reverted code in devel project
# rq684575 was created and accepted as O:F/153
# But the 284-285 and the 285-286 changeset is seen as empty
# as the revert was never in Base:System, so the
# submitted_revision of 684575 has no commit
if submitted_revision.commit == "EMPTY":
logging.warning("Empty commit submitted?!")
return False
message = (
f"Accepting request {revision.requestid}: {revision.comment}\n\n{revision}"
)
commit = self.git.merge(
# TODO: revision.userid or request.creator?
f"OBS User {revision.userid}",
"null@suse.de",
revision.time,
message,
submitted_revision.commit,
)
if commit == "EMPTY":
logging.warning("Empty merge. Ignoring the revision and the request")
self.git.merge_abort()
revision.commit = commit
return False
if commit == "CONFLICT":
logging.info("Merge conflict. Downloading revision")
self.download(revision)
message = f"CONFLICT {message}"
commit = self.git.merge(
f"OBS User {revision.userid}",
"null@suse.de",
revision.time,
message,
submitted_revision.commit,
merged=True,
)
assert commit and commit != "CONFLICT"
logging.info(f"Merge with {submitted_revision.commit} into {commit}")
revision.commit = commit
# TODO: There are more checks to do, like for example, the
# last commit into the non-devel branch should be a merge from
# the devel branch
if self.rebase_devel:
branch, _ = self.projects_info.get(request.source, (None, None))
if branch == "devel":
self.git.repo.references[f"refs/heads/{branch}"].set_target(commit)
self._rebase_branch_history(request.source, submitted_revision)
return True
def matching_request(self, revision):
request = self.obs.request(revision.requestid)
if not request:
return None
# to be handled by the caller
if request.type() != "submit":
return request
if request.source not in self.projects_info:
logging.info("Request from a non exported project")
return None
if request.target != revision.project:
# This seems to happen when the devel project gets
# reinitialized (for example, SR#943593 in 7zip, or
# SR#437901 in ColorFull)
logging.info("Request target different from current project")
return None
if request.source == request.target:
# this is not a merge, but a different way to do a
# contribution to the (devel) project - see bindfs's rev 1
logging.info("Request within the same project")
return None
return request
def import_revision(self, revision):
"""Import a single revision into git"""
project = revision.project
branch, api_url = self.projects_info[project]
logging.info(f"Importing [{revision}] to {branch}")
self.obs.change_url(api_url)
# Populate linkrev and replace srcmd5 from the linked
# revision. If the expansion fails, the revision will be ignored
# and not imported.
if not revision.check_expanded():
logging.warning(f"Broken revision")
revision.ignored = True
return
# When doing a SR, we see also a revision in the origin
# project with the outgoing request, but without changes in
# the project. We can ignore them.
#
# If there is a request ID, it will be filtered out later,
# when the target project is different from itself.
if revision.userid == "autobuild" and not revision.requestid:
logging.info("Ignoring autocommit")
revision.ignored = True
return
if revision.userid == "buildservice-autocommit":
logging.info("Ignoring autocommit")
revision.ignored = True
return
# Create the reference if the branch is new. If so return
# True.
new_branch = self.git.checkout(branch)
if revision.requestid:
request = self.matching_request(revision)
if request:
if request.type() == "delete":
# TODO: after this comes a restore, this should be collapsed
# before even hitting git
logging.info("Delete request ignored")
revision.ignored = True
return
logging.debug(f"Found matching request: #{revision.project} #{request}")
if new_branch:
self.import_new_revision_with_request(revision, request)
return
if self.import_revision_with_request(revision, request):
return
# Import revision as a single commit (without merging)
self.download(revision)
if new_branch or self.git.is_dirty():
commit = self.git.commit(
f"OBS User {revision.userid}",
"null@suse.de",
revision.time,
# TODO: Normalize better the commit message
f"{revision.comment}\n\n{revision}",
# Create an empty commit only if is a new branch
allow_empty=new_branch,
)
revision.commit = commit
logging.info(f"Commit {commit}")
else:
logging.info("Skip empty commit")
revision.ignored = True

View File

@@ -151,7 +151,15 @@ class OBS:
)
return osc.core.http_GET(url)
def download(self, project, package, name, revision, dirpath):
def download(
self,
project: str,
package: str,
name: str,
revision: str,
dirpath: str,
file_md5: str,
) -> None:
with (dirpath / name).open("wb") as f:
f.write(self._download(project, package, name, revision).read())

View File

@@ -1,14 +1,12 @@
import datetime
import logging
import re
import xml.etree.ElementTree as ET
from urllib.error import HTTPError
class OBSRevision:
def __init__(self, obs, history, project, package):
def __init__(self, obs, project, package):
self.obs = obs
self.history = history
self.project = project
self.package = package
@@ -72,46 +70,3 @@ class OBSRevision:
logging.debug("No _link for the revision")
return None
raise e
def check_link(self):
"""Add 'linkrev' attribute into the revision. Returns False if the link is invalid"""
try:
root = self.read_link()
if root is None:
return True
target_project = root.get("project")
except ET.ParseError:
logging.error(
f"_link can't be parsed [{self.project}/{self.package} rev={self.unexpanded_srcmd5}]"
)
return False
rev = self.history.find_last_rev_after_time(target_project, self.time)
if rev:
logging.debug(f"Linkrev found: {rev}")
self.linkrev = rev.srcmd5
return True
def check_expanded(self):
# Even if it's not a link we still need to check the expanded
# srcmd5 as it's possible used in submit requests
if not self.check_link():
return False
# If there is a "linkrev", "rev" is ignored
params = {"rev": self.srcmd5, "expand": "1"}
if self.linkrev:
params["linkrev"] = self.linkrev
try:
root = self.obs._xml(f"source/{self.project}/{self.package}", **params)
except HTTPError as e:
if e.code == 400:
logging.error(
f"Package [{self.project}/{self.package} {params}] can't be expanded: {e}"
)
return False
raise e
self.srcmd5 = root.get("srcmd5")
return True

View File

@@ -61,21 +61,22 @@ class Request:
with db.cursor() as cur:
cur.execute("""SELECT * from requests WHERE id=%s""", (request_id,))
row = cur.fetchone()
ret = Request()
ret._from_db(row)
return ret
return Request.from_db(row)
def _from_db(self, row):
@staticmethod
def from_db(row):
ret = Request()
(
self.dbid,
self.number,
self.creator,
self.type_,
self.state,
self.source_package,
self.source_project,
self.source_rev,
ret.dbid,
ret.number,
ret.creator,
ret.type_,
ret.state,
ret.source_package,
ret.source_project,
ret.source_rev,
) = row
return ret
def as_dict(self):
return {

View File

@@ -6,7 +6,9 @@ from lib.db import DB
from lib.db_revision import DBRevision
class Exporter:
class TestExporter:
""" "Helper class to export data from production DB for tests"""
def __init__(self, package):
self.package = package

View File

@@ -1,7 +1,27 @@
from typing import Dict
from xmlrpc.client import Boolean
from lib.db_revision import DBRevision
from lib.flat_walker import FlatTreeWalker
from lib.request import Request
class AbstractWalker:
def call(self, node, is_source):
pass
class PrintWalker(AbstractWalker):
def call(self, node, is_source):
if is_source:
print(" ", node.revision.short_string(), node.revision.files_hash)
else:
merge_str = ""
if node.merged:
merge_str = f"merged:{node.merged.revision.short_string()}"
print(node.revision.short_string(), node.revision.files_hash, merge_str)
class TreeNode:
"""
Nodes in this "tree" have either no parent (root), one parent (in a chain)
@@ -13,20 +33,30 @@ class TreeNode:
self.merged = None
self.revision = rev
self.merged_into = None
self.git_commit = None
def print(self):
def walk(self, walker: AbstractWalker):
node = self
while node:
print(node.revision, node.revision.files_hash)
walker.call(node, False)
if node.merged:
source_node = node.merged
while source_node:
print(" ", source_node.revision, source_node.revision.files_hash)
walker.call(source_node, True)
source_node = source_node.parent
if source_node and source_node.merged_into:
break
node = node.parent
def print(self):
self.walk(PrintWalker())
def as_flat_list(self):
"""Return the tree as git commits to do"""
ftw = FlatTreeWalker()
self.walk(ftw)
return ftw.flats
def as_list(self):
"""Return a list for test cases"""
node = self
@@ -51,6 +81,8 @@ class TreeBuilder:
self.db = db
def revisions_chain(self, project, package):
"""Build a tree without branches (chain) from a project's
history ignoring empty and broken revisions"""
revisions = DBRevision.all_revisions(self.db, project, package)
revisions.sort()
prev = None
@@ -69,6 +101,8 @@ class TreeBuilder:
return tree
def find_merge(self, revision, source_chain):
"""For a given revision in the target, find the node in the source chain
that matches the files"""
node = source_chain
while node:
# exclude reverts happening after the merge
@@ -80,48 +114,101 @@ class TreeBuilder:
node = node.parent
def add_merge_points(self, factory_revisions):
source_revisions = dict()
factory_node = factory_revisions
while factory_node:
if factory_node.revision.request_id:
req = Request.find(self.db, factory_node.revision.request_id)
"""For all target revisions that accepted a request, look up the merge
points in the source chains (ignoring the actual revision submitted for now)"""
class FindRequestsWalker(AbstractWalker):
def __init__(self) -> None:
super().__init__()
self.requests = set()
def call(self, node: TreeNode, _: Boolean) -> None:
if not node.revision.request_id:
return
self.requests.add(node.revision.request_id)
class FindMergeWalker(AbstractWalker):
def __init__(self, builder: TreeBuilder, requests: Dict) -> None:
super().__init__()
self.source_revisions = dict()
self.builder = builder
self.requests = requests
def call(self, node, is_source) -> None:
# not going to happen, but better safe
if is_source:
return
if not node.revision.request_id:
return
req = self.requests.get(node.revision.request_id)
key = f"{req.source_project}/{req.source_package}"
if key not in source_revisions:
source_revisions[key] = self.revisions_chain(
if key not in self.source_revisions:
self.source_revisions[key] = self.builder.revisions_chain(
req.source_project, req.source_package
)
factory_node.merged = self.find_merge(
factory_node.revision, source_revisions[key]
node.merged = self.builder.find_merge(
node.revision, self.source_revisions[key]
)
# add a reverse lookup
if factory_node.merged:
factory_node.merged.merged_into = factory_node
if node.merged:
node.merged.merged_into = node
factory_node = factory_node.parent
# walk the tree twice. First we collect all requests to be looked up
# to avoid going into the DB a thousand times
frqs = FindRequestsWalker()
factory_revisions.walk(frqs)
requests = dict()
with self.db.cursor() as cur:
cur.execute(
"SELECT * from requests WHERE id = ANY(%s)", (list(frqs.requests),)
)
for row in cur.fetchall():
req = Request.from_db(row)
requests[req.dbid] = req
sw = FindMergeWalker(self, requests)
factory_revisions.walk(sw)
def prune_loose_end(self, factory_node):
"""Look for source revisions that end in a new root and prune them"""
merge_before_last = None
last_merge = None
while factory_node:
if factory_node.merged:
source_node = factory_node.merged
ended_without_merge = False
while source_node:
source_node = source_node.parent
if source_node and source_node.merged_into:
ended_without_merge = True
break
if not ended_without_merge:
factory_node.merged = None
if last_merge:
last_merge.parent = None
else:
last_merge = factory_node.merged
merge_before_last = last_merge
last_merge = factory_node
factory_node = factory_node.parent
def build(self, package):
factory_revisions = self.revisions_chain("openSUSE:Factory", package)
self.add_merge_points(factory_revisions)
# a package without requests
if not last_merge:
return
if merge_before_last:
# we need to find the last merged_into that didn't end nowhere
# and cut the rope there
node = merge_before_last.merged
last_node = None
while node:
last_node = node
node = node.parent
if node and node.merged_into:
break
if last_node:
last_node.parent = None
if not last_merge.parent:
last_merge.parent = last_merge.merged
last_merge.merged.merged_into = None
last_merge.merged = None
def build(self, project, package):
"""Create a Factory tree (returning the top)"""
factory_revisions = self.revisions_chain(project, package)
self.add_merge_points(factory_revisions)
# factory_revisions.print()
self.prune_loose_end(factory_revisions)
# factory_revisions.print()
return factory_revisions

View File

@@ -1,10 +1,25 @@
FAKE_ACCOUNTS = ("unknown", "buildservice-autocommit", "autobuild", "_service")
FAKE_ACCOUNTS = (
"unknown",
"buildservice-autocommit",
"autobuild",
"_service",
"admin",
"jg",
"mrdocs",
"AdrianSuSE",
"$user",
"embar-",
"michel_mno",
"guinux",
)
class User:
def parse(self, xml, userid):
self.userid = userid
self.realname = xml.find("realname").text
if self.realname is None:
self.realname = ""
self.email = xml.find("email").text
if self.email is None:
self.email = ""
@@ -40,13 +55,24 @@ class User:
@staticmethod
def missing_users(db):
missing_users = set()
with db.cursor() as cur:
cur.execute(
"""SELECT DISTINCT revisions.userid
FROM revisions LEFT JOIN users ON revisions.userid = users.userid
"""SELECT DISTINCT revisions.userid
FROM revisions LEFT JOIN users ON revisions.userid = users.userid
WHERE users.userid IS NULL AND revisions.userid NOT IN {}""".format(
FAKE_ACCOUNTS
)
)
missing_users = [row[0] for row in cur.fetchall()]
return missing_users
for row in cur.fetchall():
missing_users.add(row[0])
cur.execute(
"""SELECT DISTINCT requests.creator
FROM requests LEFT JOIN users ON requests.creator=users.userid
WHERE users.userid IS NULL AND requests.creator NOT IN {}""".format(
FAKE_ACCOUNTS
)
)
for row in cur.fetchall():
missing_users.add(row[0])
return sorted(missing_users)

1
repos/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*

View File

@@ -3,7 +3,6 @@ import xml.etree.ElementTree as ET
from lib.db import DB
from lib.db_revision import DBRevision
from lib.history import History
from lib.obs import OBS
from lib.obs_revision import OBSRevision
@@ -12,10 +11,9 @@ class TestDBMethods(unittest.TestCase):
def setUp(self):
self.db = DB(section="test")
self.obs = OBS()
self.history = History(self.obs, "xz")
def test_import(self):
test_rev = OBSRevision(self.obs, self.history, "openSUSE:Factory", "xz")
test_rev = OBSRevision(self.obs, "openSUSE:Factory", "xz")
test_rev.parse(
ET.fromstring(
"""<revision rev="70" vrev="1">

8120
tests/fixtures/000update-repos-data.yaml vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
- factory c:openSUSE:Factory/000update-repos/12.0 p1:openSUSE:Factory/000update-repos/11.0
- factory c:openSUSE:Factory/000update-repos/11.0 p1:openSUSE:Factory/000update-repos/10.0
- factory c:openSUSE:Factory/000update-repos/10.0 p1:openSUSE:Factory/000update-repos/9.0
- factory c:openSUSE:Factory/000update-repos/9.0 p1:openSUSE:Factory/000update-repos/8.0
- factory c:openSUSE:Factory/000update-repos/8.0 p1:openSUSE:Factory/000update-repos/7.0
- factory c:openSUSE:Factory/000update-repos/7.0 p1:openSUSE:Factory/000update-repos/6.0
- factory c:openSUSE:Factory/000update-repos/6.0 p1:openSUSE:Factory/000update-repos/5.0
- factory c:openSUSE:Factory/000update-repos/5.0 p1:openSUSE:Factory/000update-repos/4.0
- factory c:openSUSE:Factory/000update-repos/4.0 p1:openSUSE:Factory/000update-repos/3.0
- factory c:openSUSE:Factory/000update-repos/3.0 p1:openSUSE:Factory/000update-repos/2.0
- factory c:openSUSE:Factory/000update-repos/2.0 p1:openSUSE:Factory/000update-repos/1.0
- factory c:openSUSE:Factory/000update-repos/1.0

View File

@@ -0,0 +1,12 @@
- commit: openSUSE:Factory/000update-repos/12.0
- commit: openSUSE:Factory/000update-repos/11.0
- commit: openSUSE:Factory/000update-repos/10.0
- commit: openSUSE:Factory/000update-repos/9.0
- commit: openSUSE:Factory/000update-repos/8.0
- commit: openSUSE:Factory/000update-repos/7.0
- commit: openSUSE:Factory/000update-repos/6.0
- commit: openSUSE:Factory/000update-repos/5.0
- commit: openSUSE:Factory/000update-repos/4.0
- commit: openSUSE:Factory/000update-repos/3.0
- commit: openSUSE:Factory/000update-repos/2.0
- commit: openSUSE:Factory/000update-repos/1.0

189
tests/fixtures/2048-cli-data.yaml vendored Normal file
View File

@@ -0,0 +1,189 @@
revisions:
- broken: false
comment: Initial package for 2048-cli
commit_time: 2019-01-17 13:24:10
expanded_srcmd5: bf00cfcbc357fc8a22140cb976cb1831
files:
- md5: fc83599e3bee1ca6e801c0345972e0db
mtime: 1547497445
name: 2048-cli-0.9.1+git.20181118.tar.xz
size: 11264
- md5: 0e55db8b846331b0419de91b9b4adeed
mtime: 1547497445
name: 2048-cli-link-against-correct-curses-lib.patch
size: 599
- md5: f5163dd8a1eeba70ab548a94abe5c2e8
mtime: 1547497445
name: 2048-cli-use-proper-gettext-header.patch
size: 295
- md5: 66545f46846337395fef1a73ac8adc4b
mtime: 1547497445
name: 2048-cli.changes
size: 160
- md5: 664c5dde51c1aed237647f5ee3f33eb1
mtime: 1547497446
name: 2048-cli.spec
size: 1912
- md5: 19fd04edbcba9d5dd847987cb6a252f9
mtime: 1547497446
name: _service
size: 576
- md5: 17e4e2499ed0511542a3f1119f0e7f73
mtime: 1547497446
name: _servicedata
size: 237
files_hash: 43709daf58d28446064513690987c5bb
package: 2048-cli
project: games
request:
creator: mnhauke
number: 666053
source_package: 2048-cli
source_project: home:mnhauke:games
source_rev: '1'
state: accepted
type: submit
rev: 1.0
unexpanded_srcmd5: d5865fc419f6c88f7e53304ad653a5fe
userid: dstoecker
- broken: false
comment: '- Generalize description, and adjust summary grammar.'
commit_time: 2019-01-17 16:47:56
expanded_srcmd5: 9403af9dd3f07b99bee5230c36d73c55
files:
- md5: fc83599e3bee1ca6e801c0345972e0db
mtime: 1547497445
name: 2048-cli-0.9.1+git.20181118.tar.xz
size: 11264
- md5: 0e55db8b846331b0419de91b9b4adeed
mtime: 1547497445
name: 2048-cli-link-against-correct-curses-lib.patch
size: 599
- md5: f5163dd8a1eeba70ab548a94abe5c2e8
mtime: 1547497445
name: 2048-cli-use-proper-gettext-header.patch
size: 295
- md5: a84bd74cb2fff9e88a53763a90e5dac3
mtime: 1547737525
name: 2048-cli.changes
size: 348
- md5: ae75b0d87d1636964684ec9312b5e5a4
mtime: 1547737525
name: 2048-cli.spec
size: 1980
- md5: 19fd04edbcba9d5dd847987cb6a252f9
mtime: 1547497446
name: _service
size: 576
- md5: 17e4e2499ed0511542a3f1119f0e7f73
mtime: 1547497446
name: _servicedata
size: 237
files_hash: 376a74bc69d432aa5d9880a23bb1b1a3
package: 2048-cli
project: games
request:
creator: jengelh
number: 666761
source_package: 2048-cli
source_project: home:jengelh:branches:games
source_rev: '2'
state: accepted
type: submit
rev: 2.0
unexpanded_srcmd5: f523f3b5b7fbb3f1d27139eaed4d89d3
userid: mnhauke
- broken: false
comment: initialized devel package after accepting 666812
commit_time: 2019-01-24 14:11:01
expanded_srcmd5: f29bf05261449d7e67e13ad64c4e2c03
files:
- md5: fc83599e3bee1ca6e801c0345972e0db
mtime: 1547497445
name: 2048-cli-0.9.1+git.20181118.tar.xz
size: 11264
- md5: 0e55db8b846331b0419de91b9b4adeed
mtime: 1547497445
name: 2048-cli-link-against-correct-curses-lib.patch
size: 599
- md5: f5163dd8a1eeba70ab548a94abe5c2e8
mtime: 1547497445
name: 2048-cli-use-proper-gettext-header.patch
size: 295
- md5: a84bd74cb2fff9e88a53763a90e5dac3
mtime: 1547737525
name: 2048-cli.changes
size: 348
- md5: ae75b0d87d1636964684ec9312b5e5a4
mtime: 1547737525
name: 2048-cli.spec
size: 1980
- md5: 19fd04edbcba9d5dd847987cb6a252f9
mtime: 1547497446
name: _service
size: 576
- md5: 17e4e2499ed0511542a3f1119f0e7f73
mtime: 1547497446
name: _servicedata
size: 237
files_hash: 376a74bc69d432aa5d9880a23bb1b1a3
package: 2048-cli
project: games
request:
creator: jengelh
number: 666812
source_package: 2048-cli
source_project: games
source_rev: '2'
state: accepted
type: submit
rev: 3.0
unexpanded_srcmd5: 6caa3b7070b6f2c5c7772d00c6ac8712
userid: dimstar_suse
- broken: false
comment: ''
commit_time: 2019-01-24 14:11:01
expanded_srcmd5: 00f1a7cd03864ac8f34c66c4a028d175
files:
- md5: fc83599e3bee1ca6e801c0345972e0db
mtime: 1547497445
name: 2048-cli-0.9.1+git.20181118.tar.xz
size: 11264
- md5: 0e55db8b846331b0419de91b9b4adeed
mtime: 1547497445
name: 2048-cli-link-against-correct-curses-lib.patch
size: 599
- md5: f5163dd8a1eeba70ab548a94abe5c2e8
mtime: 1547497445
name: 2048-cli-use-proper-gettext-header.patch
size: 295
- md5: a84bd74cb2fff9e88a53763a90e5dac3
mtime: 1547737525
name: 2048-cli.changes
size: 348
- md5: ae75b0d87d1636964684ec9312b5e5a4
mtime: 1547737525
name: 2048-cli.spec
size: 1980
- md5: 19fd04edbcba9d5dd847987cb6a252f9
mtime: 1547497446
name: _service
size: 576
- md5: 17e4e2499ed0511542a3f1119f0e7f73
mtime: 1547497446
name: _servicedata
size: 237
files_hash: 376a74bc69d432aa5d9880a23bb1b1a3
package: 2048-cli
project: openSUSE:Factory
request:
creator: jengelh
number: 666812
source_package: 2048-cli
source_project: games
source_rev: '2'
state: accepted
type: submit
rev: 1.0
unexpanded_srcmd5: 63aac027654e164c683fa5c6684a2e00
userid: dimstar_suse

View File

@@ -0,0 +1,3 @@
- factory c:openSUSE:Factory/2048-cli/1.0 p1:games/2048-cli/2.0
- factory c:games/2048-cli/2.0 p1:games/2048-cli/1.0
- factory c:games/2048-cli/1.0

View File

@@ -0,0 +1,3 @@
- commit: openSUSE:Factory/2048-cli/1.0
- commit: games/2048-cli/2.0
- commit: games/2048-cli/1.0

2167
tests/fixtures/CoreFreq-data.yaml vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
- factory c:openSUSE:Factory/CoreFreq/15.0 p1:openSUSE:Factory/CoreFreq/14.0 p2:hardware/CoreFreq/32.0
- devel c:hardware/CoreFreq/32.0 p1:hardware/CoreFreq/31.0
- devel c:hardware/CoreFreq/31.0 p1:hardware/CoreFreq/29.0
- factory c:openSUSE:Factory/CoreFreq/14.0 p1:openSUSE:Factory/CoreFreq/13.0 p2:hardware/CoreFreq/29.0
- devel c:hardware/CoreFreq/29.0 p1:hardware/CoreFreq/27.0
- factory c:openSUSE:Factory/CoreFreq/13.0 p1:openSUSE:Factory/CoreFreq/12.0 p2:hardware/CoreFreq/27.0
- devel c:hardware/CoreFreq/27.0 p1:hardware/CoreFreq/26.0
- devel c:hardware/CoreFreq/26.0 p1:hardware/CoreFreq/25.0
- factory c:openSUSE:Factory/CoreFreq/12.0 p1:openSUSE:Factory/CoreFreq/11.0 p2:hardware/CoreFreq/25.0
- devel c:hardware/CoreFreq/25.0 p1:hardware/CoreFreq/24.0
- devel c:hardware/CoreFreq/24.0 p1:hardware/CoreFreq/23.0
- factory c:openSUSE:Factory/CoreFreq/11.0 p1:openSUSE:Factory/CoreFreq/10.0 p2:hardware/CoreFreq/23.0
- devel c:hardware/CoreFreq/23.0 p1:hardware/CoreFreq/22.0
- factory c:openSUSE:Factory/CoreFreq/10.0 p1:openSUSE:Factory/CoreFreq/9.0 p2:hardware/CoreFreq/22.0
- devel c:hardware/CoreFreq/22.0 p1:hardware/CoreFreq/21.0
- factory c:openSUSE:Factory/CoreFreq/9.0 p1:openSUSE:Factory/CoreFreq/8.0 p2:hardware/CoreFreq/21.0
- devel c:hardware/CoreFreq/21.0 p1:hardware/CoreFreq/20.0
- factory c:openSUSE:Factory/CoreFreq/8.0 p1:openSUSE:Factory/CoreFreq/7.0 p2:hardware/CoreFreq/20.0
- devel c:hardware/CoreFreq/20.0 p1:hardware/CoreFreq/18.0
- factory c:openSUSE:Factory/CoreFreq/7.0 p1:openSUSE:Factory/CoreFreq/6.0 p2:hardware/CoreFreq/18.0
- devel c:hardware/CoreFreq/18.0 p1:hardware/CoreFreq/16.0
- factory c:openSUSE:Factory/CoreFreq/6.0 p1:openSUSE:Factory/CoreFreq/5.0 p2:hardware/CoreFreq/16.0
- devel c:hardware/CoreFreq/16.0 p1:hardware/CoreFreq/15.0
- devel c:hardware/CoreFreq/15.0 p1:hardware/CoreFreq/13.0
- factory c:openSUSE:Factory/CoreFreq/5.0 p1:openSUSE:Factory/CoreFreq/4.0 p2:hardware/CoreFreq/13.0
- devel c:hardware/CoreFreq/13.0 p1:hardware/CoreFreq/11.0
- factory c:openSUSE:Factory/CoreFreq/4.0 p1:openSUSE:Factory/CoreFreq/3.0 p2:hardware/CoreFreq/11.0
- devel c:hardware/CoreFreq/11.0 p1:hardware/CoreFreq/9.0
- factory c:openSUSE:Factory/CoreFreq/3.0 p1:openSUSE:Factory/CoreFreq/2.0 p2:hardware/CoreFreq/9.0
- devel c:hardware/CoreFreq/9.0 p1:hardware/CoreFreq/8.0
- devel c:hardware/CoreFreq/8.0 p1:hardware/CoreFreq/7.0
- devel c:hardware/CoreFreq/7.0 p1:hardware/CoreFreq/6.0
- devel c:hardware/CoreFreq/6.0 p1:hardware/CoreFreq/4.0
- factory c:openSUSE:Factory/CoreFreq/2.0 p1:openSUSE:Factory/CoreFreq/1.0 p2:hardware/CoreFreq/4.0
- devel c:hardware/CoreFreq/4.0 p1:openSUSE:Factory/CoreFreq/1.0
- factory c:openSUSE:Factory/CoreFreq/1.0 p1:hardware/CoreFreq/2.0
- factory c:hardware/CoreFreq/2.0 p1:hardware/CoreFreq/1.0
- factory c:hardware/CoreFreq/1.0

View File

@@ -0,0 +1,52 @@
- commit: openSUSE:Factory/CoreFreq/15.0
merged:
- hardware/CoreFreq/32.0
- hardware/CoreFreq/31.0
- commit: openSUSE:Factory/CoreFreq/14.0
merged:
- hardware/CoreFreq/29.0
- commit: openSUSE:Factory/CoreFreq/13.0
merged:
- hardware/CoreFreq/27.0
- hardware/CoreFreq/26.0
- commit: openSUSE:Factory/CoreFreq/12.0
merged:
- hardware/CoreFreq/25.0
- hardware/CoreFreq/24.0
- commit: openSUSE:Factory/CoreFreq/11.0
merged:
- hardware/CoreFreq/23.0
- commit: openSUSE:Factory/CoreFreq/10.0
merged:
- hardware/CoreFreq/22.0
- commit: openSUSE:Factory/CoreFreq/9.0
merged:
- hardware/CoreFreq/21.0
- commit: openSUSE:Factory/CoreFreq/8.0
merged:
- hardware/CoreFreq/20.0
- commit: openSUSE:Factory/CoreFreq/7.0
merged:
- hardware/CoreFreq/18.0
- commit: openSUSE:Factory/CoreFreq/6.0
merged:
- hardware/CoreFreq/16.0
- hardware/CoreFreq/15.0
- commit: openSUSE:Factory/CoreFreq/5.0
merged:
- hardware/CoreFreq/13.0
- commit: openSUSE:Factory/CoreFreq/4.0
merged:
- hardware/CoreFreq/11.0
- commit: openSUSE:Factory/CoreFreq/3.0
merged:
- hardware/CoreFreq/9.0
- hardware/CoreFreq/8.0
- hardware/CoreFreq/7.0
- hardware/CoreFreq/6.0
- commit: openSUSE:Factory/CoreFreq/2.0
merged:
- hardware/CoreFreq/4.0
- commit: openSUSE:Factory/CoreFreq/1.0
- commit: hardware/CoreFreq/2.0
- commit: hardware/CoreFreq/1.0

View File

@@ -0,0 +1,10 @@
- factory c:openSUSE:Factory/clapper/3.0 p1:openSUSE:Factory/clapper/2.0 p2:multimedia:apps/clapper/9.0
- devel c:multimedia:apps/clapper/9.0 p1:multimedia:apps/clapper/7.0
- factory c:openSUSE:Factory/clapper/2.0 p1:openSUSE:Factory/clapper/1.0 p2:multimedia:apps/clapper/7.0
- devel c:multimedia:apps/clapper/7.0 p1:multimedia:apps/clapper/6.0
- devel c:multimedia:apps/clapper/6.0 p1:multimedia:apps/clapper/5.0
- devel c:multimedia:apps/clapper/5.0 p1:openSUSE:Factory/clapper/1.0
- factory c:openSUSE:Factory/clapper/1.0 p1:multimedia:apps/clapper/3.0
- factory c:multimedia:apps/clapper/3.0 p1:multimedia:apps/clapper/2.0
- factory c:multimedia:apps/clapper/2.0 p1:multimedia:apps/clapper/1.0
- factory c:multimedia:apps/clapper/1.0

View File

@@ -1,7 +1,12 @@
- commit: Rev openSUSE:Factory/clapper/3.0
- commit: openSUSE:Factory/clapper/3.0
merged:
- Rev multimedia:apps/clapper/9.0
- commit: Rev openSUSE:Factory/clapper/2.0
- multimedia:apps/clapper/9.0
- commit: openSUSE:Factory/clapper/2.0
merged:
- Rev multimedia:apps/clapper/7.0
- commit: Rev openSUSE:Factory/clapper/1.0
- multimedia:apps/clapper/7.0
- multimedia:apps/clapper/6.0
- multimedia:apps/clapper/5.0
- commit: openSUSE:Factory/clapper/1.0
- commit: multimedia:apps/clapper/3.0
- commit: multimedia:apps/clapper/2.0
- commit: multimedia:apps/clapper/1.0

9030
tests/fixtures/llvm13-data.yaml vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
- factory c:openSUSE:Factory/llvm13/12.0 p1:openSUSE:Factory/llvm13/11.0 p2:devel:tools:compiler/llvm13/30.0
- devel c:devel:tools:compiler/llvm13/30.0 p1:devel:tools:compiler/llvm13/28.0
- factory c:openSUSE:Factory/llvm13/11.0 p1:openSUSE:Factory/llvm13/10.0 p2:devel:tools:compiler/llvm13/28.0
- devel c:devel:tools:compiler/llvm13/28.0 p1:devel:tools:compiler/llvm13/26.0
- factory c:openSUSE:Factory/llvm13/10.0 p1:openSUSE:Factory/llvm13/9.0 p2:devel:tools:compiler/llvm13/26.0
- devel c:devel:tools:compiler/llvm13/26.0 p1:devel:tools:compiler/llvm13/25.0
- devel c:devel:tools:compiler/llvm13/25.0 p1:devel:tools:compiler/llvm13/24.0
- devel c:devel:tools:compiler/llvm13/24.0 p1:devel:tools:compiler/llvm13/22.0
- factory c:openSUSE:Factory/llvm13/9.0 p1:openSUSE:Factory/llvm13/8.0 p2:devel:tools:compiler/llvm13/22.0
- devel c:devel:tools:compiler/llvm13/22.0 p1:devel:tools:compiler/llvm13/21.0
- devel c:devel:tools:compiler/llvm13/21.0 p1:devel:tools:compiler/llvm13/20.0
- devel c:devel:tools:compiler/llvm13/20.0 p1:devel:tools:compiler/llvm13/19.0
- devel c:devel:tools:compiler/llvm13/19.0 p1:devel:tools:compiler/llvm13/18.0
- devel c:devel:tools:compiler/llvm13/18.0 p1:devel:tools:compiler/llvm13/17.0
- devel c:devel:tools:compiler/llvm13/17.0 p1:devel:tools:compiler/llvm13/16.0
- devel c:devel:tools:compiler/llvm13/16.0 p1:devel:tools:compiler/llvm13/15.0
- devel c:devel:tools:compiler/llvm13/15.0 p1:devel:tools:compiler/llvm13/14.0
- devel c:devel:tools:compiler/llvm13/14.0 p1:devel:tools:compiler/llvm13/13.0
- factory c:openSUSE:Factory/llvm13/8.0 p1:openSUSE:Factory/llvm13/7.0 p2:devel:tools:compiler/llvm13/13.0
- devel c:devel:tools:compiler/llvm13/13.0 p1:devel:tools:compiler/llvm13/12.0
- devel c:devel:tools:compiler/llvm13/12.0 p1:devel:tools:compiler/llvm13/11.0
- devel c:devel:tools:compiler/llvm13/11.0 p1:devel:tools:compiler/llvm13/10.0
- factory c:openSUSE:Factory/llvm13/7.0 p1:openSUSE:Factory/llvm13/6.0 p2:devel:tools:compiler/llvm13/10.0
- devel c:devel:tools:compiler/llvm13/10.0 p1:devel:tools:compiler/llvm13/9.0
- factory c:openSUSE:Factory/llvm13/6.0 p1:openSUSE:Factory/llvm13/5.0 p2:devel:tools:compiler/llvm13/9.0
- devel c:devel:tools:compiler/llvm13/9.0 p1:devel:tools:compiler/llvm13/8.0
- devel c:devel:tools:compiler/llvm13/8.0 p1:devel:tools:compiler/llvm13/7.0
- factory c:openSUSE:Factory/llvm13/5.0 p1:openSUSE:Factory/llvm13/4.0 p2:devel:tools:compiler/llvm13/7.0
- devel c:devel:tools:compiler/llvm13/7.0 p1:devel:tools:compiler/llvm13/6.0
- factory c:openSUSE:Factory/llvm13/4.0 p1:openSUSE:Factory/llvm13/3.0 p2:devel:tools:compiler/llvm13/6.0
- devel c:devel:tools:compiler/llvm13/6.0 p1:devel:tools:compiler/llvm13/5.0
- factory c:openSUSE:Factory/llvm13/3.0 p1:openSUSE:Factory/llvm13/2.0 p2:devel:tools:compiler/llvm13/5.0
- devel c:devel:tools:compiler/llvm13/5.0 p1:devel:tools:compiler/llvm13/4.0
- factory c:openSUSE:Factory/llvm13/2.0 p1:openSUSE:Factory/llvm13/1.0 p2:devel:tools:compiler/llvm13/4.0
- devel c:devel:tools:compiler/llvm13/4.0 p1:devel:tools:compiler/llvm13/3.0
- devel c:devel:tools:compiler/llvm13/3.0 p1:openSUSE:Factory/llvm13/1.0
- factory c:openSUSE:Factory/llvm13/1.0 p1:devel:tools:compiler/llvm13/1.0
- factory c:devel:tools:compiler/llvm13/1.0

View File

@@ -0,0 +1,49 @@
- commit: openSUSE:Factory/llvm13/12.0
merged:
- devel:tools:compiler/llvm13/30.0
- commit: openSUSE:Factory/llvm13/11.0
merged:
- devel:tools:compiler/llvm13/28.0
- commit: openSUSE:Factory/llvm13/10.0
merged:
- devel:tools:compiler/llvm13/26.0
- devel:tools:compiler/llvm13/25.0
- devel:tools:compiler/llvm13/24.0
- commit: openSUSE:Factory/llvm13/9.0
merged:
- devel:tools:compiler/llvm13/22.0
- devel:tools:compiler/llvm13/21.0
- devel:tools:compiler/llvm13/20.0
- devel:tools:compiler/llvm13/19.0
- devel:tools:compiler/llvm13/18.0
- devel:tools:compiler/llvm13/17.0
- devel:tools:compiler/llvm13/16.0
- devel:tools:compiler/llvm13/15.0
- devel:tools:compiler/llvm13/14.0
- commit: openSUSE:Factory/llvm13/8.0
merged:
- devel:tools:compiler/llvm13/13.0
- devel:tools:compiler/llvm13/12.0
- devel:tools:compiler/llvm13/11.0
- commit: openSUSE:Factory/llvm13/7.0
merged:
- devel:tools:compiler/llvm13/10.0
- commit: openSUSE:Factory/llvm13/6.0
merged:
- devel:tools:compiler/llvm13/9.0
- devel:tools:compiler/llvm13/8.0
- commit: openSUSE:Factory/llvm13/5.0
merged:
- devel:tools:compiler/llvm13/7.0
- commit: openSUSE:Factory/llvm13/4.0
merged:
- devel:tools:compiler/llvm13/6.0
- commit: openSUSE:Factory/llvm13/3.0
merged:
- devel:tools:compiler/llvm13/5.0
- commit: openSUSE:Factory/llvm13/2.0
merged:
- devel:tools:compiler/llvm13/4.0
- devel:tools:compiler/llvm13/3.0
- commit: openSUSE:Factory/llvm13/1.0
- commit: devel:tools:compiler/llvm13/1.0

File diff suppressed because it is too large Load Diff

206
tests/fixtures/zsh-expected-list.yaml vendored Normal file
View File

@@ -0,0 +1,206 @@
- factory c:openSUSE:Factory/zsh/87.0 p1:openSUSE:Factory/zsh/86.0 p2:shells/zsh/210.0
- devel c:shells/zsh/210.0 p1:shells/zsh/209.0
- devel c:shells/zsh/209.0 p1:shells/zsh/208.0
- devel c:shells/zsh/208.0 p1:shells/zsh/207.0
- devel c:shells/zsh/207.0 p1:shells/zsh/205.0
- factory c:openSUSE:Factory/zsh/86.0 p1:openSUSE:Factory/zsh/85.0 p2:shells/zsh/205.0
- devel c:shells/zsh/205.0 p1:shells/zsh/204.0
- devel c:shells/zsh/204.0 p1:shells/zsh/202.0
- factory c:openSUSE:Factory/zsh/85.0 p1:openSUSE:Factory/zsh/84.0 p2:shells/zsh/202.0
- devel c:shells/zsh/202.0 p1:shells/zsh/201.0
- devel c:shells/zsh/201.0 p1:shells/zsh/199.0
- factory c:openSUSE:Factory/zsh/84.0 p1:openSUSE:Factory/zsh/83.0 p2:shells/zsh/199.0
- devel c:shells/zsh/199.0 p1:shells/zsh/198.0
- devel c:shells/zsh/198.0 p1:shells/zsh/197.0
- devel c:shells/zsh/197.0 p1:shells/zsh/195.0
- factory c:openSUSE:Factory/zsh/83.0 p1:openSUSE:Factory/zsh/82.0 p2:shells/zsh/195.0
- devel c:shells/zsh/195.0 p1:shells/zsh/194.0
- devel c:shells/zsh/194.0 p1:shells/zsh/193.0
- devel c:shells/zsh/193.0 p1:shells/zsh/192.0
- devel c:shells/zsh/192.0 p1:shells/zsh/191.0
- devel c:shells/zsh/191.0 p1:shells/zsh/189.0
- factory c:openSUSE:Factory/zsh/82.0 p1:openSUSE:Factory/zsh/81.0 p2:shells/zsh/189.0
- devel c:shells/zsh/189.0 p1:shells/zsh/187.0
- factory c:openSUSE:Factory/zsh/81.0 p1:openSUSE:Factory/zsh/80.0 p2:shells/zsh/187.0
- devel c:shells/zsh/187.0 p1:shells/zsh/186.0
- devel c:shells/zsh/186.0 p1:shells/zsh/184.0
- factory c:openSUSE:Factory/zsh/80.0 p1:openSUSE:Factory/zsh/79.0 p2:shells/zsh/184.0
- devel c:shells/zsh/184.0 p1:shells/zsh/182.0
- factory c:openSUSE:Factory/zsh/79.0 p1:openSUSE:Factory/zsh/78.0 p2:shells/zsh/182.0
- devel c:shells/zsh/182.0 p1:shells/zsh/180.0
- factory c:openSUSE:Factory/zsh/78.0 p1:openSUSE:Factory/zsh/77.0 p2:shells/zsh/180.0
- devel c:shells/zsh/180.0 p1:shells/zsh/178.0
- factory c:openSUSE:Factory/zsh/77.0 p1:openSUSE:Factory/zsh/76.0 p2:shells/zsh/178.0
- devel c:shells/zsh/178.0 p1:shells/zsh/177.0
- devel c:shells/zsh/177.0 p1:shells/zsh/175.0
- factory c:openSUSE:Factory/zsh/76.0 p1:openSUSE:Factory/zsh/75.0 p2:shells/zsh/175.0
- devel c:shells/zsh/175.0 p1:shells/zsh/173.0
- factory c:openSUSE:Factory/zsh/75.0 p1:openSUSE:Factory/zsh/74.0 p2:shells/zsh/173.0
- devel c:shells/zsh/173.0 p1:shells/zsh/171.0
- factory c:openSUSE:Factory/zsh/74.0 p1:openSUSE:Factory/zsh/73.0 p2:shells/zsh/171.0
- devel c:shells/zsh/171.0 p1:shells/zsh/170.0
- devel c:shells/zsh/170.0 p1:shells/zsh/168.0
- factory c:openSUSE:Factory/zsh/73.0 p1:openSUSE:Factory/zsh/72.0 p2:shells/zsh/168.0
- devel c:shells/zsh/168.0 p1:shells/zsh/166.0
- factory c:openSUSE:Factory/zsh/72.0 p1:openSUSE:Factory/zsh/71.0 p2:shells/zsh/166.0
- devel c:shells/zsh/166.0 p1:shells/zsh/164.0
- factory c:openSUSE:Factory/zsh/71.0 p1:openSUSE:Factory/zsh/70.0 p2:shells/zsh/164.0
- devel c:shells/zsh/164.0 p1:shells/zsh/162.0
- factory c:openSUSE:Factory/zsh/70.0 p1:openSUSE:Factory/zsh/69.0 p2:shells/zsh/162.0
- devel c:shells/zsh/162.0 p1:shells/zsh/160.0
- factory c:openSUSE:Factory/zsh/69.0 p1:openSUSE:Factory/zsh/68.0 p2:shells/zsh/160.0
- devel c:shells/zsh/160.0 p1:shells/zsh/159.0
- devel c:shells/zsh/159.0 p1:shells/zsh/158.0
- devel c:shells/zsh/158.0 p1:shells/zsh/156.0
- factory c:openSUSE:Factory/zsh/68.0 p1:openSUSE:Factory/zsh/67.0 p2:shells/zsh/156.0
- devel c:shells/zsh/156.0 p1:shells/zsh/154.0
- factory c:openSUSE:Factory/zsh/67.0 p1:openSUSE:Factory/zsh/66.0 p2:shells/zsh/154.0
- devel c:shells/zsh/154.0 p1:shells/zsh/153.0
- devel c:shells/zsh/153.0 p1:shells/zsh/151.0
- factory c:openSUSE:Factory/zsh/66.0 p1:openSUSE:Factory/zsh/65.0 p2:shells/zsh/151.0
- devel c:shells/zsh/151.0 p1:shells/zsh/150.0
- devel c:shells/zsh/150.0 p1:shells/zsh/148.0
- factory c:openSUSE:Factory/zsh/65.0 p1:openSUSE:Factory/zsh/64.0 p2:shells/zsh/148.0
- devel c:shells/zsh/148.0 p1:shells/zsh/146.0
- factory c:openSUSE:Factory/zsh/64.0 p1:openSUSE:Factory/zsh/63.0 p2:shells/zsh/146.0
- devel c:shells/zsh/146.0 p1:shells/zsh/145.0
- devel c:shells/zsh/145.0 p1:shells/zsh/144.0
- devel c:shells/zsh/144.0 p1:shells/zsh/142.0
- factory c:openSUSE:Factory/zsh/63.0 p1:openSUSE:Factory/zsh/62.0 p2:shells/zsh/142.0
- devel c:shells/zsh/142.0 p1:shells/zsh/140.0
- factory c:openSUSE:Factory/zsh/62.0 p1:openSUSE:Factory/zsh/61.0 p2:shells/zsh/140.0
- devel c:shells/zsh/140.0 p1:shells/zsh/138.0
- factory c:openSUSE:Factory/zsh/61.0 p1:openSUSE:Factory/zsh/60.0 p2:shells/zsh/138.0
- devel c:shells/zsh/138.0 p1:shells/zsh/137.0
- devel c:shells/zsh/137.0 p1:shells/zsh/135.0
- factory c:openSUSE:Factory/zsh/60.0 p1:openSUSE:Factory/zsh/59.0 p2:shells/zsh/135.0
- devel c:shells/zsh/135.0 p1:shells/zsh/133.0
- factory c:openSUSE:Factory/zsh/59.0 p1:openSUSE:Factory/zsh/57.0 p2:shells/zsh/133.0
- devel c:shells/zsh/133.0 p1:shells/zsh/131.0
- factory c:openSUSE:Factory/zsh/57.0 p1:openSUSE:Factory/zsh/56.0 p2:shells/zsh/131.0
- devel c:shells/zsh/131.0 p1:shells/zsh/129.0
- factory c:openSUSE:Factory/zsh/56.0 p1:openSUSE:Factory/zsh/55.0 p2:shells/zsh/129.0
- devel c:shells/zsh/129.0 p1:shells/zsh/127.0
- factory c:openSUSE:Factory/zsh/55.0 p1:openSUSE:Factory/zsh/54.0 p2:shells/zsh/127.0
- devel c:shells/zsh/127.0 p1:shells/zsh/125.0
- factory c:openSUSE:Factory/zsh/54.0 p1:openSUSE:Factory/zsh/53.0 p2:shells/zsh/125.0
- devel c:shells/zsh/125.0 p1:shells/zsh/123.0
- factory c:openSUSE:Factory/zsh/53.0 p1:openSUSE:Factory/zsh/51.0 p2:shells/zsh/123.0
- devel c:shells/zsh/123.0 p1:shells/zsh/121.0
- factory c:openSUSE:Factory/zsh/51.0 p1:openSUSE:Factory/zsh/50.0 p2:shells/zsh/121.0
- devel c:shells/zsh/121.0 p1:shells/zsh/119.0
- factory c:openSUSE:Factory/zsh/50.0 p1:openSUSE:Factory/zsh/49.0 p2:shells/zsh/119.0
- devel c:shells/zsh/119.0 p1:shells/zsh/117.0
- factory c:openSUSE:Factory/zsh/49.0 p1:openSUSE:Factory/zsh/48.0 p2:shells/zsh/117.0
- devel c:shells/zsh/117.0 p1:shells/zsh/115.0
- factory c:openSUSE:Factory/zsh/48.0 p1:openSUSE:Factory/zsh/46.0 p2:shells/zsh/115.0
- devel c:shells/zsh/115.0 p1:shells/zsh/113.0
- factory c:openSUSE:Factory/zsh/46.0 p1:openSUSE:Factory/zsh/45.0 p2:shells/zsh/113.0
- devel c:shells/zsh/113.0 p1:shells/zsh/111.0
- factory c:openSUSE:Factory/zsh/45.0 p1:openSUSE:Factory/zsh/44.0 p2:shells/zsh/111.0
- devel c:shells/zsh/111.0 p1:shells/zsh/109.0
- factory c:openSUSE:Factory/zsh/44.0 p1:openSUSE:Factory/zsh/43.0 p2:shells/zsh/109.0
- devel c:shells/zsh/109.0 p1:shells/zsh/107.0
- factory c:openSUSE:Factory/zsh/43.0 p1:openSUSE:Factory/zsh/42.0 p2:shells/zsh/107.0
- devel c:shells/zsh/107.0 p1:shells/zsh/105.0
- factory c:openSUSE:Factory/zsh/42.0 p1:openSUSE:Factory/zsh/41.0 p2:shells/zsh/105.0
- devel c:shells/zsh/105.0 p1:shells/zsh/103.0
- factory c:openSUSE:Factory/zsh/41.0 p1:openSUSE:Factory/zsh/39.0 p2:shells/zsh/103.0
- devel c:shells/zsh/103.0 p1:shells/zsh/102.0
- devel c:shells/zsh/102.0 p1:shells/zsh/100.0
- factory c:openSUSE:Factory/zsh/39.0 p1:openSUSE:Factory/zsh/38.0 p2:shells/zsh/100.0
- devel c:shells/zsh/100.0 p1:shells/zsh/98.0
- factory c:openSUSE:Factory/zsh/38.0 p1:openSUSE:Factory/zsh/37.0 p2:shells/zsh/98.0
- devel c:shells/zsh/98.0 p1:shells/zsh/95.0
- factory c:openSUSE:Factory/zsh/37.0 p1:openSUSE:Factory/zsh/36.0 p2:shells/zsh/95.0
- devel c:shells/zsh/95.0 p1:shells/zsh/94.0
- devel c:shells/zsh/94.0 p1:shells/zsh/91.0
- devel c:shells/zsh/91.0 p1:shells/zsh/90.0
- factory c:openSUSE:Factory/zsh/36.0 p1:openSUSE:Factory/zsh/35.0 p2:shells/zsh/90.0
- devel c:shells/zsh/90.0 p1:shells/zsh/89.0
- devel c:shells/zsh/89.0 p1:shells/zsh/88.0
- devel c:shells/zsh/88.0 p1:shells/zsh/87.0
- devel c:shells/zsh/87.0 p1:shells/zsh/84.0
- factory c:openSUSE:Factory/zsh/35.0 p1:openSUSE:Factory/zsh/34.0 p2:shells/zsh/84.0
- devel c:shells/zsh/84.0 p1:shells/zsh/83.0
- devel c:shells/zsh/83.0 p1:shells/zsh/82.0
- devel c:shells/zsh/82.0 p1:shells/zsh/81.0
- devel c:shells/zsh/81.0 p1:shells/zsh/80.0
- devel c:shells/zsh/80.0 p1:shells/zsh/77.0
- factory c:openSUSE:Factory/zsh/34.0 p1:openSUSE:Factory/zsh/32.0 p2:shells/zsh/77.0
- devel c:shells/zsh/77.0 p1:shells/zsh/76.032
- devel c:shells/zsh/76.032 p1:shells/zsh/75.0
- factory c:openSUSE:Factory/zsh/32.0 p1:openSUSE:Factory/zsh/31.0
- factory c:openSUSE:Factory/zsh/31.0 p1:openSUSE:Factory/zsh/30.0 p2:shells/zsh/75.0
- devel c:shells/zsh/75.0 p1:shells/zsh/74.0
- devel c:shells/zsh/74.0 p1:shells/zsh/73.0
- devel c:shells/zsh/73.0 p1:shells/zsh/72.03
- devel c:shells/zsh/72.03 p1:shells/zsh/71.0
- factory c:openSUSE:Factory/zsh/30.0 p1:openSUSE:Factory/zsh/29.0
- factory c:openSUSE:Factory/zsh/29.0 p1:openSUSE:Factory/zsh/28.0 p2:shells/zsh/71.0
- devel c:shells/zsh/71.0 p1:shells/zsh/69.0
- factory c:openSUSE:Factory/zsh/28.0 p1:openSUSE:Factory/zsh/27.0 p2:shells/zsh/69.0
- devel c:shells/zsh/69.0 p1:shells/zsh/68.0
- devel c:shells/zsh/68.0 p1:shells/zsh/67.027
- devel c:shells/zsh/67.027 p1:shells/zsh/66.0
- factory c:openSUSE:Factory/zsh/27.0 p1:openSUSE:Factory/zsh/26.0
- factory c:openSUSE:Factory/zsh/26.0 p1:openSUSE:Factory/zsh/25.0 p2:shells/zsh/66.0
- devel c:shells/zsh/66.0 p1:shells/zsh/65.0
- devel c:shells/zsh/65.0 p1:shells/zsh/64.0
- devel c:shells/zsh/64.0 p1:shells/zsh/63.0
- devel c:shells/zsh/63.0 p1:shells/zsh/62.0
- devel c:shells/zsh/62.0 p1:shells/zsh/61.0
- devel c:shells/zsh/61.0 p1:shells/zsh/60.0
- devel c:shells/zsh/60.0 p1:shells/zsh/59.0
- devel c:shells/zsh/59.0 p1:shells/zsh/58.0
- devel c:shells/zsh/58.0 p1:shells/zsh/57.0
- devel c:shells/zsh/57.0 p1:shells/zsh/56.0
- devel c:shells/zsh/56.0 p1:shells/zsh/55.0
- devel c:shells/zsh/55.0 p1:shells/zsh/54.0
- devel c:shells/zsh/54.0 p1:shells/zsh/53.0
- devel c:shells/zsh/53.0 p1:shells/zsh/52.0
- devel c:shells/zsh/52.0 p1:shells/zsh/51.0
- devel c:shells/zsh/51.0 p1:shells/zsh/50.0
- devel c:shells/zsh/50.0 p1:shells/zsh/49.0
- devel c:shells/zsh/49.0 p1:shells/zsh/48.0
- devel c:shells/zsh/48.0 p1:shells/zsh/47.0
- devel c:shells/zsh/47.0 p1:shells/zsh/46.025
- devel c:shells/zsh/46.025 p1:shells/zsh/45.0
- factory c:openSUSE:Factory/zsh/25.0 p1:openSUSE:Factory/zsh/24.0
- factory c:openSUSE:Factory/zsh/24.0 p1:openSUSE:Factory/zsh/23.0 p2:shells/zsh/45.0
- devel c:shells/zsh/45.0 p1:shells/zsh/44.0
- devel c:shells/zsh/44.0 p1:shells/zsh/43.023
- devel c:shells/zsh/43.023 p1:shells/zsh/42.0
- factory c:openSUSE:Factory/zsh/23.0 p1:openSUSE:Factory/zsh/22.0
- factory c:openSUSE:Factory/zsh/22.0 p1:openSUSE:Factory/zsh/18.0 p2:shells/zsh/42.0
- devel c:shells/zsh/42.0 p1:shells/zsh/41.035
- devel c:shells/zsh/41.035 p1:shells/zsh/41.017
- factory c:openSUSE:Factory/zsh/18.0 p1:openSUSE:Factory/zsh/17.0
- factory c:openSUSE:Factory/zsh/17.0 p1:openSUSE:Factory/zsh/16.0 p2:shells/zsh/41.017
- devel c:shells/zsh/41.017 p1:shells/zsh/41.0
- devel c:shells/zsh/41.0 p1:shells/zsh/40.0
- devel c:shells/zsh/40.0 p1:shells/zsh/39.0
- devel c:shells/zsh/39.0 p1:shells/zsh/38.0
- devel c:shells/zsh/38.0 p1:shells/zsh/37.0
- devel c:shells/zsh/37.0 p1:shells/zsh/36.014
- factory c:openSUSE:Factory/zsh/16.0 p1:openSUSE:Factory/zsh/14.0
- factory c:openSUSE:Factory/zsh/14.0 p1:openSUSE:Factory/zsh/13.0 p2:shells/zsh/36.014
- devel c:shells/zsh/36.014 p1:shells/zsh/36.0
- devel c:shells/zsh/36.0 p1:shells/zsh/35.0
- devel c:shells/zsh/35.0 p1:shells/zsh/34.0
- factory c:openSUSE:Factory/zsh/13.0 p1:openSUSE:Factory/zsh/12.0 p2:shells/zsh/34.0
- devel c:shells/zsh/34.0 p1:shells/zsh/33.0
- devel c:shells/zsh/33.0 p1:shells/zsh/32.0
- devel c:shells/zsh/32.0 p1:openSUSE:Factory/zsh/12.0
- factory c:openSUSE:Factory/zsh/12.0 p1:openSUSE:Factory/zsh/11.0
- factory c:openSUSE:Factory/zsh/11.0 p1:openSUSE:Factory/zsh/10.0
- factory c:openSUSE:Factory/zsh/10.0 p1:openSUSE:Factory/zsh/9.0
- factory c:openSUSE:Factory/zsh/9.0 p1:openSUSE:Factory/zsh/8.0
- factory c:openSUSE:Factory/zsh/8.0 p1:openSUSE:Factory/zsh/7.0
- factory c:openSUSE:Factory/zsh/7.0 p1:openSUSE:Factory/zsh/6.0
- factory c:openSUSE:Factory/zsh/6.0 p1:openSUSE:Factory/zsh/5.0
- factory c:openSUSE:Factory/zsh/5.0 p1:openSUSE:Factory/zsh/4.0
- factory c:openSUSE:Factory/zsh/4.0 p1:openSUSE:Factory/zsh/3.0
- factory c:openSUSE:Factory/zsh/3.0 p1:openSUSE:Factory/zsh/2.0
- factory c:openSUSE:Factory/zsh/2.0 p1:openSUSE:Factory/zsh/1.0
- factory c:openSUSE:Factory/zsh/1.0

View File

@@ -1,300 +1,265 @@
- commit: Rev openSUSE:Factory/zsh/98.0
- commit: openSUSE:Factory/zsh/87.0
merged:
- Rev shells/zsh/236.0
- commit: Rev openSUSE:Factory/zsh/97.0
- shells/zsh/210.0
- shells/zsh/209.0
- shells/zsh/208.0
- shells/zsh/207.0
- commit: openSUSE:Factory/zsh/86.0
merged:
- Rev shells/zsh/234.0
- commit: Rev openSUSE:Factory/zsh/96.0
- shells/zsh/205.0
- shells/zsh/204.0
- commit: openSUSE:Factory/zsh/85.0
merged:
- Rev shells/zsh/232.0
- commit: Rev openSUSE:Factory/zsh/95.0
- shells/zsh/202.0
- shells/zsh/201.0
- commit: openSUSE:Factory/zsh/84.0
merged:
- Rev shells/zsh/230.0
- commit: Rev openSUSE:Factory/zsh/94.0
- shells/zsh/199.0
- shells/zsh/198.0
- shells/zsh/197.0
- commit: openSUSE:Factory/zsh/83.0
merged:
- Rev shells/zsh/228.0
- Rev shells/zsh/227.0
- commit: Rev openSUSE:Factory/zsh/93.0
- shells/zsh/195.0
- shells/zsh/194.0
- shells/zsh/193.0
- shells/zsh/192.0
- shells/zsh/191.0
- commit: openSUSE:Factory/zsh/82.0
merged:
- Rev shells/zsh/225.0
- Rev shells/zsh/224.0
- Rev shells/zsh/223.0
- commit: Rev openSUSE:Factory/zsh/92.0
- shells/zsh/189.0
- commit: openSUSE:Factory/zsh/81.0
merged:
- Rev shells/zsh/221.0
- commit: Rev openSUSE:Factory/zsh/91.0
- shells/zsh/187.0
- shells/zsh/186.0
- commit: openSUSE:Factory/zsh/80.0
merged:
- Rev shells/zsh/219.0
- commit: Rev openSUSE:Factory/zsh/90.0
- shells/zsh/184.0
- commit: openSUSE:Factory/zsh/79.0
merged:
- Rev shells/zsh/217.0
- commit: Rev openSUSE:Factory/zsh/89.0
- shells/zsh/182.0
- commit: openSUSE:Factory/zsh/78.0
merged:
- Rev shells/zsh/215.0
- Rev shells/zsh/214.0
- commit: Rev openSUSE:Factory/zsh/88.0
- shells/zsh/180.0
- commit: openSUSE:Factory/zsh/77.0
merged:
- Rev shells/zsh/212.0
- commit: Rev openSUSE:Factory/zsh/87.0
- shells/zsh/178.0
- shells/zsh/177.0
- commit: openSUSE:Factory/zsh/76.0
merged:
- Rev shells/zsh/210.0
- Rev shells/zsh/209.0
- Rev shells/zsh/208.0
- Rev shells/zsh/207.0
- commit: Rev openSUSE:Factory/zsh/86.0
- shells/zsh/175.0
- commit: openSUSE:Factory/zsh/75.0
merged:
- Rev shells/zsh/205.0
- Rev shells/zsh/204.0
- commit: Rev openSUSE:Factory/zsh/85.0
- shells/zsh/173.0
- commit: openSUSE:Factory/zsh/74.0
merged:
- Rev shells/zsh/202.0
- Rev shells/zsh/201.0
- commit: Rev openSUSE:Factory/zsh/84.0
- shells/zsh/171.0
- shells/zsh/170.0
- commit: openSUSE:Factory/zsh/73.0
merged:
- Rev shells/zsh/199.0
- Rev shells/zsh/198.0
- Rev shells/zsh/197.0
- commit: Rev openSUSE:Factory/zsh/83.0
- shells/zsh/168.0
- commit: openSUSE:Factory/zsh/72.0
merged:
- Rev shells/zsh/195.0
- Rev shells/zsh/194.0
- Rev shells/zsh/193.0
- Rev shells/zsh/192.0
- Rev shells/zsh/191.0
- commit: Rev openSUSE:Factory/zsh/82.0
- shells/zsh/166.0
- commit: openSUSE:Factory/zsh/71.0
merged:
- Rev shells/zsh/189.0
- commit: Rev openSUSE:Factory/zsh/81.0
- shells/zsh/164.0
- commit: openSUSE:Factory/zsh/70.0
merged:
- Rev shells/zsh/187.0
- Rev shells/zsh/186.0
- commit: Rev openSUSE:Factory/zsh/80.0
- shells/zsh/162.0
- commit: openSUSE:Factory/zsh/69.0
merged:
- Rev shells/zsh/184.0
- commit: Rev openSUSE:Factory/zsh/79.0
- shells/zsh/160.0
- shells/zsh/159.0
- shells/zsh/158.0
- commit: openSUSE:Factory/zsh/68.0
merged:
- Rev shells/zsh/182.0
- commit: Rev openSUSE:Factory/zsh/78.0
- shells/zsh/156.0
- commit: openSUSE:Factory/zsh/67.0
merged:
- Rev shells/zsh/180.0
- commit: Rev openSUSE:Factory/zsh/77.0
- shells/zsh/154.0
- shells/zsh/153.0
- commit: openSUSE:Factory/zsh/66.0
merged:
- Rev shells/zsh/178.0
- Rev shells/zsh/177.0
- commit: Rev openSUSE:Factory/zsh/76.0
- shells/zsh/151.0
- shells/zsh/150.0
- commit: openSUSE:Factory/zsh/65.0
merged:
- Rev shells/zsh/175.0
- commit: Rev openSUSE:Factory/zsh/75.0
- shells/zsh/148.0
- commit: openSUSE:Factory/zsh/64.0
merged:
- Rev shells/zsh/173.0
- commit: Rev openSUSE:Factory/zsh/74.0
- shells/zsh/146.0
- shells/zsh/145.0
- shells/zsh/144.0
- commit: openSUSE:Factory/zsh/63.0
merged:
- Rev shells/zsh/171.0
- Rev shells/zsh/170.0
- commit: Rev openSUSE:Factory/zsh/73.0
- shells/zsh/142.0
- commit: openSUSE:Factory/zsh/62.0
merged:
- Rev shells/zsh/168.0
- commit: Rev openSUSE:Factory/zsh/72.0
- shells/zsh/140.0
- commit: openSUSE:Factory/zsh/61.0
merged:
- Rev shells/zsh/166.0
- commit: Rev openSUSE:Factory/zsh/71.0
- shells/zsh/138.0
- shells/zsh/137.0
- commit: openSUSE:Factory/zsh/60.0
merged:
- Rev shells/zsh/164.0
- commit: Rev openSUSE:Factory/zsh/70.0
- shells/zsh/135.0
- commit: openSUSE:Factory/zsh/59.0
merged:
- Rev shells/zsh/162.0
- commit: Rev openSUSE:Factory/zsh/69.0
- shells/zsh/133.0
- commit: openSUSE:Factory/zsh/57.0
merged:
- Rev shells/zsh/160.0
- Rev shells/zsh/159.0
- Rev shells/zsh/158.0
- commit: Rev openSUSE:Factory/zsh/68.0
- shells/zsh/131.0
- commit: openSUSE:Factory/zsh/56.0
merged:
- Rev shells/zsh/156.0
- commit: Rev openSUSE:Factory/zsh/67.0
- shells/zsh/129.0
- commit: openSUSE:Factory/zsh/55.0
merged:
- Rev shells/zsh/154.0
- Rev shells/zsh/153.0
- commit: Rev openSUSE:Factory/zsh/66.0
- shells/zsh/127.0
- commit: openSUSE:Factory/zsh/54.0
merged:
- Rev shells/zsh/151.0
- Rev shells/zsh/150.0
- commit: Rev openSUSE:Factory/zsh/65.0
- shells/zsh/125.0
- commit: openSUSE:Factory/zsh/53.0
merged:
- shells/zsh/123.0
- commit: openSUSE:Factory/zsh/51.0
merged:
- Rev shells/zsh/148.0
- commit: Rev openSUSE:Factory/zsh/64.0
- shells/zsh/121.0
- commit: openSUSE:Factory/zsh/50.0
merged:
- Rev shells/zsh/146.0
- Rev shells/zsh/145.0
- Rev shells/zsh/144.0
- commit: Rev openSUSE:Factory/zsh/63.0
- shells/zsh/119.0
- commit: openSUSE:Factory/zsh/49.0
merged:
- Rev shells/zsh/142.0
- commit: Rev openSUSE:Factory/zsh/62.0
merged:
- Rev shells/zsh/140.0
- commit: Rev openSUSE:Factory/zsh/61.0
merged:
- Rev shells/zsh/138.0
- Rev shells/zsh/137.0
- commit: Rev openSUSE:Factory/zsh/60.0
merged:
- Rev shells/zsh/135.0
- commit: Rev openSUSE:Factory/zsh/59.0
merged:
- Rev shells/zsh/133.0
- commit: Rev openSUSE:Factory/zsh/57.0
merged:
- Rev shells/zsh/131.0
- commit: Rev openSUSE:Factory/zsh/56.0
merged:
- Rev shells/zsh/129.0
- commit: Rev openSUSE:Factory/zsh/55.0
merged:
- Rev shells/zsh/127.0
- commit: Rev openSUSE:Factory/zsh/54.0
merged:
- Rev shells/zsh/125.0
- commit: Rev openSUSE:Factory/zsh/53.0
merged:
- Rev shells/zsh/123.0
- commit: Rev openSUSE:Factory/zsh/51.0
merged:
- Rev shells/zsh/121.0
- commit: Rev openSUSE:Factory/zsh/50.0
merged:
- Rev shells/zsh/119.0
- commit: Rev openSUSE:Factory/zsh/49.0
merged:
- Rev shells/zsh/117.0
- commit: Rev openSUSE:Factory/zsh/48.0
merged:
- Rev shells/zsh/115.0
- commit: Rev openSUSE:Factory/zsh/46.0
merged:
- Rev shells/zsh/113.0
- commit: Rev openSUSE:Factory/zsh/45.0
merged:
- Rev shells/zsh/111.0
- commit: Rev openSUSE:Factory/zsh/44.0
merged:
- Rev shells/zsh/109.0
- commit: Rev openSUSE:Factory/zsh/43.0
merged:
- Rev shells/zsh/107.0
- commit: Rev openSUSE:Factory/zsh/42.0
merged:
- Rev shells/zsh/105.0
- commit: Rev openSUSE:Factory/zsh/41.0
merged:
- Rev shells/zsh/103.0
- Rev shells/zsh/102.0
- commit: Rev openSUSE:Factory/zsh/39.0
merged:
- Rev shells/zsh/100.0
- commit: Rev openSUSE:Factory/zsh/38.0
merged:
- Rev shells/zsh/98.0
- commit: Rev openSUSE:Factory/zsh/37.0
merged:
- Rev shells/zsh/95.0
- Rev shells/zsh/94.0
- Rev shells/zsh/91.0
- commit: Rev openSUSE:Factory/zsh/36.0
merged:
- Rev shells/zsh/90.0
- Rev shells/zsh/89.0
- Rev shells/zsh/88.0
- Rev shells/zsh/87.0
- commit: Rev openSUSE:Factory/zsh/35.0
merged:
- Rev shells/zsh/84.0
- Rev shells/zsh/83.0
- Rev shells/zsh/82.0
- Rev shells/zsh/81.0
- Rev shells/zsh/80.0
- commit: Rev openSUSE:Factory/zsh/34.0
merged:
- Rev shells/zsh/77.0
- Rev shells/zsh/76.032
- commit: Rev openSUSE:Factory/zsh/32.0
- commit: Rev openSUSE:Factory/zsh/31.0
merged:
- Rev shells/zsh/75.0
- Rev shells/zsh/74.0
- Rev shells/zsh/73.0
- Rev shells/zsh/72.03
- commit: Rev openSUSE:Factory/zsh/30.0
- commit: Rev openSUSE:Factory/zsh/29.0
merged:
- Rev shells/zsh/71.0
- commit: Rev openSUSE:Factory/zsh/28.0
merged:
- Rev shells/zsh/69.0
- Rev shells/zsh/68.0
- Rev shells/zsh/67.027
- commit: Rev openSUSE:Factory/zsh/27.0
- commit: Rev openSUSE:Factory/zsh/26.0
merged:
- Rev shells/zsh/66.0
- Rev shells/zsh/65.0
- Rev shells/zsh/64.0
- Rev shells/zsh/63.0
- Rev shells/zsh/62.0
- Rev shells/zsh/61.0
- Rev shells/zsh/60.0
- Rev shells/zsh/59.0
- Rev shells/zsh/58.0
- Rev shells/zsh/57.0
- Rev shells/zsh/56.0
- Rev shells/zsh/55.0
- Rev shells/zsh/54.0
- Rev shells/zsh/53.0
- Rev shells/zsh/52.0
- Rev shells/zsh/51.0
- Rev shells/zsh/50.0
- Rev shells/zsh/49.0
- Rev shells/zsh/48.0
- Rev shells/zsh/47.0
- Rev shells/zsh/46.025
- commit: Rev openSUSE:Factory/zsh/25.0
- commit: Rev openSUSE:Factory/zsh/24.0
merged:
- Rev shells/zsh/45.0
- Rev shells/zsh/44.0
- Rev shells/zsh/43.023
- commit: Rev openSUSE:Factory/zsh/23.0
- commit: Rev openSUSE:Factory/zsh/22.0
merged:
- Rev shells/zsh/42.0
- Rev shells/zsh/41.035
- commit: Rev openSUSE:Factory/zsh/18.0
- commit: Rev openSUSE:Factory/zsh/17.0
merged:
- Rev shells/zsh/41.017
- Rev shells/zsh/41.0
- Rev shells/zsh/40.0
- Rev shells/zsh/39.0
- Rev shells/zsh/38.0
- Rev shells/zsh/37.0
- commit: Rev openSUSE:Factory/zsh/16.0
- commit: Rev openSUSE:Factory/zsh/14.0
merged:
- Rev shells/zsh/36.014
- Rev shells/zsh/36.0
- Rev shells/zsh/35.0
- commit: Rev openSUSE:Factory/zsh/13.0
merged:
- Rev shells/zsh/34.0
- commit: Rev openSUSE:Factory/zsh/12.0
- commit: Rev openSUSE:Factory/zsh/11.0
- commit: Rev openSUSE:Factory/zsh/10.0
- commit: Rev openSUSE:Factory/zsh/9.0
- commit: Rev openSUSE:Factory/zsh/8.0
- commit: Rev openSUSE:Factory/zsh/7.0
- commit: Rev openSUSE:Factory/zsh/6.0
- commit: Rev openSUSE:Factory/zsh/5.0
- commit: Rev openSUSE:Factory/zsh/4.0
- commit: Rev openSUSE:Factory/zsh/3.0
- commit: Rev openSUSE:Factory/zsh/2.0
- commit: Rev openSUSE:Factory/zsh/1.0
- shells/zsh/117.0
- commit: openSUSE:Factory/zsh/48.0
merged:
- shells/zsh/115.0
- commit: openSUSE:Factory/zsh/46.0
merged:
- shells/zsh/113.0
- commit: openSUSE:Factory/zsh/45.0
merged:
- shells/zsh/111.0
- commit: openSUSE:Factory/zsh/44.0
merged:
- shells/zsh/109.0
- commit: openSUSE:Factory/zsh/43.0
merged:
- shells/zsh/107.0
- commit: openSUSE:Factory/zsh/42.0
merged:
- shells/zsh/105.0
- commit: openSUSE:Factory/zsh/41.0
merged:
- shells/zsh/103.0
- shells/zsh/102.0
- commit: openSUSE:Factory/zsh/39.0
merged:
- shells/zsh/100.0
- commit: openSUSE:Factory/zsh/38.0
merged:
- shells/zsh/98.0
- commit: openSUSE:Factory/zsh/37.0
merged:
- shells/zsh/95.0
- shells/zsh/94.0
- shells/zsh/91.0
- commit: openSUSE:Factory/zsh/36.0
merged:
- shells/zsh/90.0
- shells/zsh/89.0
- shells/zsh/88.0
- shells/zsh/87.0
- commit: openSUSE:Factory/zsh/35.0
merged:
- shells/zsh/84.0
- shells/zsh/83.0
- shells/zsh/82.0
- shells/zsh/81.0
- shells/zsh/80.0
- commit: openSUSE:Factory/zsh/34.0
merged:
- shells/zsh/77.0
- shells/zsh/76.032
- commit: openSUSE:Factory/zsh/32.0
- commit: openSUSE:Factory/zsh/31.0
merged:
- shells/zsh/75.0
- shells/zsh/74.0
- shells/zsh/73.0
- shells/zsh/72.03
- commit: openSUSE:Factory/zsh/30.0
- commit: openSUSE:Factory/zsh/29.0
merged:
- shells/zsh/71.0
- commit: openSUSE:Factory/zsh/28.0
merged:
- shells/zsh/69.0
- shells/zsh/68.0
- shells/zsh/67.027
- commit: openSUSE:Factory/zsh/27.0
- commit: openSUSE:Factory/zsh/26.0
merged:
- shells/zsh/66.0
- shells/zsh/65.0
- shells/zsh/64.0
- shells/zsh/63.0
- shells/zsh/62.0
- shells/zsh/61.0
- shells/zsh/60.0
- shells/zsh/59.0
- shells/zsh/58.0
- shells/zsh/57.0
- shells/zsh/56.0
- shells/zsh/55.0
- shells/zsh/54.0
- shells/zsh/53.0
- shells/zsh/52.0
- shells/zsh/51.0
- shells/zsh/50.0
- shells/zsh/49.0
- shells/zsh/48.0
- shells/zsh/47.0
- shells/zsh/46.025
- commit: openSUSE:Factory/zsh/25.0
- commit: openSUSE:Factory/zsh/24.0
merged:
- shells/zsh/45.0
- shells/zsh/44.0
- shells/zsh/43.023
- commit: openSUSE:Factory/zsh/23.0
- commit: openSUSE:Factory/zsh/22.0
merged:
- shells/zsh/42.0
- shells/zsh/41.035
- commit: openSUSE:Factory/zsh/18.0
- commit: openSUSE:Factory/zsh/17.0
merged:
- shells/zsh/41.017
- shells/zsh/41.0
- shells/zsh/40.0
- shells/zsh/39.0
- shells/zsh/38.0
- shells/zsh/37.0
- commit: openSUSE:Factory/zsh/16.0
- commit: openSUSE:Factory/zsh/14.0
merged:
- shells/zsh/36.014
- shells/zsh/36.0
- shells/zsh/35.0
- commit: openSUSE:Factory/zsh/13.0
merged:
- shells/zsh/34.0
- shells/zsh/33.0
- shells/zsh/32.0
- commit: openSUSE:Factory/zsh/12.0
- commit: openSUSE:Factory/zsh/11.0
- commit: openSUSE:Factory/zsh/10.0
- commit: openSUSE:Factory/zsh/9.0
- commit: openSUSE:Factory/zsh/8.0
- commit: openSUSE:Factory/zsh/7.0
- commit: openSUSE:Factory/zsh/6.0
- commit: openSUSE:Factory/zsh/5.0
- commit: openSUSE:Factory/zsh/4.0
- commit: openSUSE:Factory/zsh/3.0
- commit: openSUSE:Factory/zsh/2.0
- commit: openSUSE:Factory/zsh/1.0

View File

@@ -19,7 +19,7 @@ class TestTreeMethods(unittest.TestCase):
for rev in data["revisions"]:
DBRevision.import_fixture_dict(self.db, rev)
revisions = TreeBuilder(self.db).build(package)
revisions = TreeBuilder(self.db).build("openSUSE:Factory", package)
path = os.path.join(
os.path.dirname(__file__), f"fixtures/{package}-expected-tree.yaml"
)
@@ -31,6 +31,20 @@ class TestTreeMethods(unittest.TestCase):
with open(path, "r") as f:
data = yaml.safe_load(f)
self.assertEqual(data, revisions.as_list())
# verify flat lists
path = os.path.join(
os.path.dirname(__file__), f"fixtures/{package}-expected-list.yaml"
)
flat_list = [str(x) for x in revisions.as_flat_list()]
if os.getenv("REGENERATE_DATA"):
with open(path, "w") as f:
yaml.dump(flat_list, f)
with open(path, "r") as f:
data = yaml.safe_load(f)
self.assertEqual(data, flat_list)
self.db.conn.rollback()
def test_zsh_tree(self):
@@ -39,6 +53,18 @@ class TestTreeMethods(unittest.TestCase):
def test_clapper_tree(self):
self.verify_package("clapper")
def test_llvm13_tree(self):
self.verify_package("llvm13")
def test_2048_cli_tree(self):
self.verify_package("2048-cli")
def test_corefreq_tree(self):
self.verify_package("CoreFreq")
def test_000update_repos_tree(self):
self.verify_package("000update-repos")
if __name__ == "__main__":
unittest.main()