49 Commits

Author SHA1 Message Date
Stephan Kulow
716db10adf Scan old imports 2022-11-24 10:24:14 +01:00
Stephan Kulow
4692d47120 Make the refresh a debug output, not info 2022-11-16 09:05:36 +01:00
coolo
d311d54f26 Merge pull request 'Also treat some more mimetypes as text' (#20) from add_further_mimetypes into main
Reviewed-on: https://gitea.opensuse.org/importers/git-importer/pulls/20
2022-11-15 07:28:05 +01:00
Stephan Kulow
dddc54ab1c Remove ProcessPool from exporting
It's ignoring exceptions and makes debugging way too hard to justify
what's happening
2022-11-11 16:33:44 +01:00
Stephan Kulow
4d1ca8d882 Also treat some more mimetypes as text 2022-11-11 16:22:18 +01:00
Stephan Kulow
7861a7e9b0 Fix LFS register (it needs json not data)
Refactored the LFS Oid handling in its class of its own and
add a way to recheck all LFS handles (or re-register)
2022-11-09 08:32:18 +01:00
coolo
f5b29886ae Merge pull request 'No longer rely on external service for LFS tracking' (#18) from add_lfs into main
Reviewed-on: https://gitea.opensuse.org/importers/git-importer/pulls/18
2022-11-08 11:00:34 +01:00
coolo
d1a8a3288d Merge pull request 'Push to the remote when the repo changed' (#19) from push_it_baby into main
Reviewed-on: https://gitea.opensuse.org/importers/git-importer/pulls/19
2022-11-08 09:48:56 +01:00
Stephan Kulow
9f6c8f62e7 Push to the remote when the repo changed 2022-11-08 09:32:03 +01:00
Stephan Kulow
3e1fbaa1c3 Migrate the ProxySHA256 data into postgresql DB
The calculation of the sha256 and the mimetype is local due to that
2022-11-07 21:50:31 +01:00
Stephan Kulow
e1b32999f0 Fix confusion about User constructor 2022-11-07 16:04:44 +01:00
coolo
86490b51dd Merge pull request 'Fix the maintenance of .gitattributes file' (#17) from fix_lfs_attributes into main
Reviewed-on: https://gitea.opensuse.org/importers/git-importer/pulls/17
2022-11-07 13:26:36 +01:00
Stephan Kulow
da5de04171 Add packages to consider 2022-11-07 07:29:20 +01:00
Stephan Kulow
be8fb2ab94 Fix fake revision creation 2022-11-06 12:27:36 +01:00
Stephan Kulow
9e895e34b6 Adding a gitea remote when creating the git repo 2022-11-06 12:18:16 +01:00
Stephan Kulow
5e495dbd95 Fancy up the git commit message 2022-11-06 11:46:04 +01:00
Stephan Kulow
5ae02a413d Store API URL in the revision table
Will be important once we get into SLE
2022-11-06 10:57:32 +01:00
Stephan Kulow
f1457e8f8e Move git commit message creation into class 2022-11-06 10:16:42 +01:00
Stephan Kulow
9114c2fff8 Change debug output for downloading files 2022-11-06 10:16:42 +01:00
Stephan Kulow
834cf61634 Use proper user info in commits 2022-11-06 09:53:52 +01:00
Stephan Kulow
a294c0f670 Readd the skipping of _staging_workflow file
A repository with 150k commits is just very hard to work with -
especially if 99% of them are worthless
2022-11-06 08:29:17 +01:00
Stephan Kulow
7bc4d6c8b1 Make downloading a little more careful for races
As we're downloading packages in parallel, it could happen that we
copy a file that isn't fully copied yet
2022-11-06 08:24:11 +01:00
Stephan Kulow
bd5bd5a444 Don't reset the .gitattributes file
Just change it if it existed before
2022-11-04 21:02:18 +01:00
Stephan Kulow
4e1d5b42ca Only validate the MD5 if we downloaded - trust the file system 2022-11-04 21:02:18 +01:00
Stephan Kulow
0bcc0183c9 Load the proxy data for is_text as well
Otherwise the text state changes over time
2022-11-04 21:02:18 +01:00
Stephan Kulow
7f88e0cc5c Run in the same process if there is only package
Debugging is much easier without Process Pool
2022-11-04 21:02:18 +01:00
coolo
4cc0a23d4e Merge pull request 'Run many packages in parallel to avoid overhead and make use of CPUS' (#16) from parallize_packages into main
Reviewed-on: https://gitea.opensuse.org/importers/git-importer/pulls/16
2022-11-04 10:04:15 +01:00
Stephan Kulow
a457a16a50 Limit the workers to 8
This is hard coding the limit, we may want to make this configurable
but for now the machines supposed to run this code are very similiar
2022-11-04 10:00:28 +01:00
Stephan Kulow
60ced896a3 Fix condition for export 2022-11-04 09:58:36 +01:00
Stephan Kulow
33a5733cb9 Create the git repos in multiple processes
Threads appear to be too dangerous for this
2022-11-04 07:48:17 +01:00
Stephan Kulow
d21ce571f5 Refresh the packages in multiple threads 2022-11-03 22:04:45 +01:00
Stephan Kulow
ab38332642 Allow to import multiple packages in one go
This way we avoid duplicating all startup and SQL queries
2022-11-03 20:14:56 +01:00
coolo
651bd94771 Merge pull request 'Fix merge points creating a cross' (#15) from debug_firewalld into main
Reviewed-on: https://gitea.opensuse.org/importers/git-importer/pulls/15
2022-11-03 15:35:52 +01:00
Stephan Kulow
dd5e26b779 Clarify which of the candidates is the right one - removing assert 2022-11-03 15:29:58 +01:00
Stephan Kulow
f2019db8ff Ignore merge point candidates that create crosses
In OBS you can create submit requests for revisions that are behind
the last merge point, in git you can't - so we ignore them.

Fixes #14
2022-11-03 15:19:51 +01:00
Stephan Kulow
ef7755c771 Add firewalld test showing a broken tree 2022-11-03 15:19:21 +01:00
coolo
6b26f8ff96 Merge pull request 'Fix the import of breeze and other packages' (#13) from add_export into main
Reviewed-on: https://gitea.opensuse.org/importers/git-importer/pulls/13
2022-11-03 15:19:06 +01:00
Stephan Kulow
ed4b7367eb Reset branch if the devel branch is based on Factory
This happens in packages that change their devel project over time. Then
the commit in the devel project no longer has the parent in the devel branch
but is based on factory
2022-11-03 15:12:07 +01:00
Stephan Kulow
f5b3e42165 Add a test case that switches devel project in its life time 2022-11-03 15:06:12 +01:00
6dd3cf3eba Merge pull request 'implement file caching' (#11) from file-cache into main
Reviewed-on: https://gitea.opensuse.org/importers/git-importer/pulls/11
2022-11-03 14:24:22 +01:00
8aed76e52a change cached file naming pattern 2022-11-03 14:22:19 +01:00
639096b548 optimize cached file locations and add option for cache directory 2022-11-03 14:12:32 +01:00
7678967ae0 implement file caching
to prevent having to download files multiple times
2022-11-03 14:05:11 +01:00
coolo
74f5cd901e Merge pull request 'Keep a reference to the database in DBRevision' (#12) from add_export into main
Reviewed-on: https://gitea.opensuse.org/importers/git-importer/pulls/12
2022-11-03 08:16:30 +01:00
Stephan Kulow
1c54a74ecd Download the full revision 2022-11-02 20:55:09 +01:00
Stephan Kulow
c2294d6200 Add a default LFS .gitattributes for now
Otherwise some packages will break to import
2022-11-02 18:27:17 +01:00
Stephan Kulow
ba7436f10c Keep a reference to the database in DBRevision
To avoid passing the db to all actions
2022-11-02 18:27:09 +01:00
coolo
75f9f56a57 Merge pull request 'Fix up some code after aplanas' continued review' (#10) from add_export into main
Reviewed-on: https://gitea.opensuse.org/importers/git-importer/pulls/10
2022-11-02 18:05:04 +01:00
Stephan Kulow
172242891d Fix up some code after aplanas' continued review 2022-11-02 15:22:24 +01:00
29 changed files with 40088 additions and 428 deletions

View File

@@ -1,7 +1,13 @@
all:
isort -rc .
autoflake -r --in-place --remove-unused-variables .
black .
isort *.py lib/*py tests/*py
autoflake --in-place --remove-unused-variables *.py lib/*py tests/*py
black *.py lib/*py tests/*py
test:
python3 -m unittest -v tests/*.py
update-packages:
f=$$(mktemp) ;\
osc api /source/openSUSE:Factory?view=info | grep -v lsrcmd5 | grep srcmd5= | sed -e 's,.*package=",,; s,".*,,' | grep -v : > $$f ;\
echo _project >> $$f ;\
mv $$f packages

View File

@@ -7,6 +7,8 @@ import sys
import osc.core
from lib.db import DB
from lib.db_revision import DBRevision
from lib.git_exporter import GitExporter
from lib.importer import Importer
from lib.test_exporter import TestExporter
@@ -42,16 +44,30 @@ PROJECTS = [
]
def export_package(package, repodir, cachedir, gc):
exporter = GitExporter(URL_OBS, "openSUSE:Factory", package, repodir, cachedir)
exporter.set_gc_interval(gc)
exporter.export_as_git()
def main():
parser = argparse.ArgumentParser(description="OBS history importer into git")
parser.add_argument("package", help="OBS package name")
parser.add_argument("packages", help="OBS package names", nargs="*")
parser.add_argument(
"-r",
"--repodir",
required=False,
default=pathlib.Path("repos"),
type=pathlib.Path,
help="Local git repository directory",
)
parser.add_argument(
"-c",
"--cachedir",
required=False,
type=pathlib.Path,
help="Local cache directory",
)
parser.add_argument(
"-g",
"--gc",
@@ -86,18 +102,70 @@ def main():
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
def check_old_package(db: DB, dir: pathlib.Path):
md5file = dir / "MD5SUMS"
print(md5file)
valid_revisions = None
with open(md5file, "rb") as f:
for line in f.readlines():
try:
md5, file = line.decode("utf-8").strip().split(" ")
except UnicodeDecodeError:
logging.error(f"Corrupt MD5 file: {md5file}")
return
if file == "ready":
continue
if len(md5) != 32:
logging.error(f"Corrupt MD5 file: {md5file}")
return
with db.cursor() as cur:
cur.execute(
"SELECT revision_id FROM files WHERE md5=%s AND name=%s",
(md5, file),
)
nrevs = set([row[0] for row in cur.fetchall()])
if valid_revisions is None:
valid_revisions = nrevs
else:
valid_revisions = valid_revisions & nrevs
if not valid_revisions:
break
with db.cursor() as cur:
cur.execute(
"SELECT * FROM revisions WHERE id = ANY(%s) AND project=%s",
(list(valid_revisions), "openSUSE:Factory"),
)
for row in cur.fetchall():
r = DBRevision(db, row)
print("Valid", r, r.files_hash)
return True
if False:
import os
db = DB()
basedir = pathlib.Path(
f"/mounts/work/SAVE/oldpackages/stable/{args.packages[0]}"
)
for subdir in sorted(os.listdir(basedir)):
if check_old_package(db, basedir / subdir):
break
if args.export:
TestExporter(args.package).run()
if len(args.packages) != 1:
print("Can only export one package")
sys.exit(1)
TestExporter(args.packages[0]).run()
return
if not args.repodir:
args.repodir = pathlib.Path("repos/" + args.package)
if not args.cachedir:
args.cachedir = pathlib.Path("~/.cache/git-import/").expanduser()
importer = Importer(URL_OBS, "openSUSE:Factory", args.package)
importer = Importer(URL_OBS, "openSUSE:Factory", args.packages)
importer.import_into_db()
exporter = GitExporter(URL_OBS, "openSUSE:Factory", args.package, args.repodir)
exporter.set_gc_interval(args.gc)
exporter.export_as_git()
for package in args.packages:
export_package(package, args.repodir, args.cachedir, args.gc)
if __name__ == "__main__":

View File

@@ -1,6 +1,10 @@
class AbstractWalker:
from abc import ABC, abstractmethod
class AbstractWalker(ABC):
"""Just a duck type, most likely not needed by python, but I
find interface classes preferable (java school)"""
@abstractmethod
def call(self, node, is_source):
pass

View File

@@ -25,18 +25,28 @@ BINARY = {
".zst",
}
TEXT_MIMETYPES = {
"message/rfc822",
"application/pgp-keys",
"application/x-gnupg-keyring",
}
def is_text_mimetype(mimetype):
if mimetype.startswith("text/"):
return True
return mimetype.split(";")[0] in TEXT_MIMETYPES
def is_binary_or_large(filename, size):
"""Decide if is a binary file based on the extension or size"""
binary_suffix = BINARY
non_binary_suffix = {
".1",
".8",
".SUSE",
".asc",
".c",
".cabal",
".cfg",
".changes",
".conf",
".desktop",

View File

@@ -215,6 +215,51 @@ class DB:
"CREATE INDEX ON linked_revs(considered)",
"UPDATE scheme SET version=20",
)
schemes[21] = (
"ALTER TABLE revisions ADD COLUMN api_url VARCHAR(40)",
"UPDATE revisions SET api_url='https://api.opensuse.org'",
"ALTER TABLE revisions ALTER COLUMN api_url SET NOT NULL",
"UPDATE scheme SET version=21",
)
schemes[22] = (
"""DROP TABLE IF EXISTS lfs_oids""",
"""
CREATE TABLE lfs_oids (
id SERIAL PRIMARY KEY,
project VARCHAR(255) NOT NULL,
package VARCHAR(255) NOT NULL,
filename VARCHAR(255) NOT NULL,
rev VARCHAR(40) NOT NULL,
sha256 VARCHAR(70) NOT NULL,
size INTEGER NOT NULL,
mimetype VARCHAR(255) NOT NULL,
file_md5 VARCHAR(40) NOT NULL
)
""",
"CREATE UNIQUE INDEX ON lfs_oids (sha256,size)",
"CREATE INDEX ON revisions(package)",
"""DROP TABLE IF EXISTS text_files""",
"""
CREATE TABLE text_files (
id SERIAL PRIMARY KEY,
package VARCHAR(255) NOT NULL,
filename VARCHAR(255) NOT NULL
)
""",
"CREATE UNIQUE INDEX ON text_files (package,filename)",
"""DROP TABLE IF EXISTS lfs_oid_in_package""",
"""
CREATE TABLE lfs_oid_in_package (
id SERIAL PRIMARY KEY,
lfs_oid_id INTEGER NOT NULL,
package VARCHAR(255) NOT NULL,
filename VARCHAR(255) NOT NULL
)
""",
"CREATE INDEX ON text_files(package)",
"CREATE INDEX ON lfs_oid_in_package(package)",
"UPDATE scheme SET version=22",
)
schema_version = self.schema_version()
if (schema_version + 1) not in schemes:
return

View File

@@ -1,15 +1,16 @@
from __future__ import annotations
import logging
from hashlib import md5
from pathlib import Path
from typing import Optional
from lib.db import DB
from lib.obs_revision import OBSRevision
from lib.request import Request
class DBRevision:
def __init__(self, row):
def __init__(self, db: DB, row: tuple):
# need to stay in sync with the schema creation in db.py
(
self.dbid,
@@ -25,9 +26,12 @@ class DBRevision:
self.request_number,
self.request_id,
self.files_hash,
self.api_url,
) = row
self.rev = float(self.rev)
self._files = None
self.db = db
self.git_commit = None
def short_string(self):
return f"{self.project}/{self.package}/{self.rev}"
@@ -48,7 +52,29 @@ class DBRevision:
return self.package < other.package
return self.rev < other.rev
def as_dict(self, db):
def request_accept_message(self):
request = Request.find(self.db, self.request_id)
msg = f"Accepting request {request.number} from {request.source_project}\n\n"
msg += self.comment.strip()
url = self.api_url.replace("api.", "build.")
msg += f"\n\nOBS-URL: {url}/request/show/{self.request_number}"
return msg
def git_commit_message(self):
msg = ""
if self.request_id:
msg = self.request_accept_message()
else:
msg = self.comment.strip() + "\n"
url = self.api_url.replace("api.", "build.")
if self.rev == int(self.rev):
# do not link to fake revisions
msg += f"\nOBS-URL: {url}/package/show/{self.project}/{self.package}?expand=0&rev={int(self.rev)}"
else:
msg += f"\nOBS-URL: {url}/package/show/{self.project}/{self.package}?expand=0&rev={self.expanded_srcmd5}"
return msg
def as_dict(self):
"""Return a dict we can put into YAML for test cases"""
ret = {
"project": self.project,
@@ -60,26 +86,27 @@ class DBRevision:
"comment": self.comment,
"broken": self.broken,
"expanded_srcmd5": self.expanded_srcmd5,
"api_url": self.api_url,
"files_hash": self.files_hash,
"files": self.files_list(db),
"files": self.files_list(),
}
if self.request_id:
ret["request"] = Request.find(db, self.request_id).as_dict()
ret["request"] = Request.find(self.db, self.request_id).as_dict()
return ret
def links_to(self, db, project, package):
with db.cursor() as cur:
def links_to(self, project: str, package: str) -> None:
with self.db.cursor() as cur:
cur.execute(
"INSERT INTO links (revision_id, project, package) VALUES (%s,%s,%s)",
(self.dbid, project, package),
)
@classmethod
def import_obs_rev(cls, db, revision):
@staticmethod
def import_obs_rev(db: DB, revision: OBSRevision):
with db.cursor() as cur:
cur.execute(
"""INSERT INTO revisions (project, package, rev, unexpanded_srcmd5, commit_time, userid, comment, request_number)
VALUES(%s, %s, %s, %s, %s, %s, %s, %s)""",
"""INSERT INTO revisions (project, package, rev, unexpanded_srcmd5, commit_time, userid, comment, request_number, api_url)
VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s)""",
(
revision.project,
revision.package,
@@ -89,12 +116,17 @@ class DBRevision:
revision.userid,
revision.comment,
revision.request_number,
revision.obs.url,
),
)
return cls.fetch_revision(db, revision.project, revision.package, revision.rev)
return DBRevision.fetch_revision(
db, revision.project, revision.package, revision.rev
)
@staticmethod
def fetch_revision(db, project, package, rev):
"""Technically we would need the api_url as well, but we assume projects are unique
(e.g. not importing SLE from obs)"""
with db.cursor() as cur:
cur.execute(
"SELECT * FROM revisions where project=%s and package=%s and rev=%s",
@@ -102,16 +134,21 @@ class DBRevision:
)
row = cur.fetchone()
if row:
return DBRevision(row)
return DBRevision(db, row)
@staticmethod
def latest_revision(db, project, package):
def max_rev(db, project, package):
with db.cursor() as cur:
cur.execute(
"SELECT MAX(rev) FROM revisions where project=%s and package=%s",
(project, package),
)
max = cur.fetchone()[0]
return cur.fetchone()[0]
return None
@staticmethod
def latest_revision(db, project, package):
max = DBRevision.max_rev(db, project, package)
if max:
return DBRevision.fetch_revision(db, project, package, max)
return None
@@ -125,13 +162,13 @@ class DBRevision:
)
ret = []
for row in cur.fetchall():
ret.append(DBRevision(row))
ret.append(DBRevision(db, row))
return ret
def linked_rev(self, db):
def linked_rev(self):
if self.broken:
return None
with db.cursor() as cur:
with self.db.cursor() as cur:
cur.execute(
"SELECT project,package FROM links where revision_id=%s", (self.dbid,)
)
@@ -143,24 +180,31 @@ class DBRevision:
"SELECT * FROM revisions where project=%s and package=%s and commit_time <= %s ORDER BY commit_time DESC LIMIT 1",
(project, package, self.commit_time),
)
revisions = [DBRevision(row) for row in cur.fetchall()]
revisions = [DBRevision(self.db, row) for row in cur.fetchall()]
if revisions:
return revisions[0]
else:
self.set_broken(db)
self.set_broken()
return None
def set_broken(self, db):
with db.cursor() as cur:
def set_broken(self):
with self.db.cursor() as cur:
cur.execute("UPDATE revisions SET broken=TRUE where id=%s", (self.dbid,))
def import_dir_list(self, db, xml):
with db.cursor() as cur:
def import_dir_list(self, xml):
with self.db.cursor() as cur:
cur.execute(
"UPDATE revisions SET expanded_srcmd5=%s where id=%s",
(xml.get("srcmd5"), self.dbid),
)
for entry in xml.findall("entry"):
# 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 (
entry.get("name") == "_staging_workflow"
and self.package == "_project"
):
continue
cur.execute(
"""INSERT INTO files (name, md5, size, mtime, revision_id)
VALUES (%s,%s,%s,%s,%s)""",
@@ -173,15 +217,19 @@ class DBRevision:
),
)
def previous_commit(self, db):
return self.fetch_revision(db, self.project, self.package, int(self.rev) - 1)
def previous_commit(self):
return DBRevision.fetch_revision(
self.db, self.project, self.package, int(self.rev) - 1
)
def next_commit(self, db):
return self.fetch_revision(db, self.project, self.package, int(self.rev) + 1)
def next_commit(self):
return DBRevision.fetch_revision(
self.db, self.project, self.package, int(self.rev) + 1
)
def calculate_files_hash(self, db):
def calculate_files_hash(self):
m = md5()
for file_dict in self.files_list(db):
for file_dict in self.files_list():
m.update(
(
file_dict["name"]
@@ -193,10 +241,10 @@ class DBRevision:
)
return m.hexdigest()
def files_list(self, db):
def files_list(self):
if self._files:
return self._files
with db.cursor() as cur:
with self.db.cursor() as cur:
cur.execute("SELECT * from files where revision_id=%s", (self.dbid,))
self._files = []
for row in cur.fetchall():
@@ -207,27 +255,23 @@ class DBRevision:
self._files.sort(key=lambda x: x["name"])
return self._files
def calc_delta(self, db: DB, current_rev: Optional[DBRevision]):
def calc_delta(self, 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.
If it's None, the repository is empty.
"""
to_download = []
to_delete = []
if current_rev:
old_files = {
e["name"]: f"{e['md5']}-{e['size']}" for e in current_rev.files_list(db)
e["name"]: f"{e['md5']}-{e['size']}" for e in current_rev.files_list()
}
else:
old_files = dict()
for entry in self.files_list(db):
for entry in self.files_list():
if old_files.get(entry["name"]) != f"{entry['md5']}-{entry['size']}":
logging.debug(f"Download {entry['name']}")
to_download.append((entry["name"], entry["md5"]))
to_download.append((Path(entry["name"]), entry["size"], entry["md5"]))
old_files.pop(entry["name"], None)
for entry in old_files.keys():
logging.debug(f"Delete {entry}")
to_delete.append(entry)
to_delete = [Path(e) for e in old_files.keys()]
return to_download, to_delete
@staticmethod
@@ -245,9 +289,9 @@ class DBRevision:
"""Used in test cases to read a revision from fixtures into the test database"""
with db.cursor() as cur:
cur.execute(
"""INSERT INTO revisions (project, package, rev, unexpanded_srcmd5, expanded_srcmd5,
commit_time, userid, comment, broken, files_hash)
VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id""",
"""INSERT INTO revisions (project, package, rev, unexpanded_srcmd5, expanded_srcmd5,
commit_time, userid, comment, broken, files_hash, api_url)
VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id""",
(
rev_dict["project"],
rev_dict["package"],
@@ -259,6 +303,7 @@ class DBRevision:
rev_dict["comment"],
rev_dict["broken"],
rev_dict["files_hash"],
rev_dict.get("api_url", "https://api.opensuse.org"),
),
)
rev_id = cur.fetchone()[0]

View File

@@ -9,12 +9,8 @@ class FlatNode:
self.parent2 = parent2
def __str__(self) -> str:
p1_str = ""
if self.parent1:
p1_str = f" p1:{self.parent1.short_string()}"
p2_str = ""
if self.parent2:
p2_str = f" p2:{self.parent2.short_string()}"
p1_str = f" p1:{self.parent1.short_string()}" if self.parent1 else ""
p2_str = f" p2:{self.parent2.short_string()}" if self.parent2 else ""
return f"{self.branch} c:{self.commit.short_string()}{p1_str}{p2_str}"
@@ -36,8 +32,7 @@ class FlatTreeWalker(AbstractWalker):
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:
elif node.parent:
self.add("devel", node.revision, node.parent.revision)
elif self.last_merge:
self.add("devel", node.revision, self.last_merge.parent.revision)

View File

@@ -1,9 +1,11 @@
import fnmatch
import logging
import os
import pathlib
import subprocess
import pygit2
import requests
from lib.binary import BINARY
@@ -23,7 +25,6 @@ class Git:
def is_open(self):
return self.repo is not None
# TODO: Extend it to packages and files
def exists(self):
"""Check if the path is a valid git repository"""
return (self.path / ".git").exists()
@@ -31,10 +32,12 @@ class Git:
def create(self):
"""Create a local git repository"""
self.path.mkdir(parents=True, exist_ok=True)
self.open()
def open(self):
# Convert the path to string, to avoid some limitations in
# older pygit2
self.repo = pygit2.init_repository(str(self.path))
return self
def is_dirty(self):
"""Check if there is something to commit"""
@@ -73,10 +76,8 @@ class Git:
committer=None,
committer_email=None,
committer_time=None,
allow_empty=False,
):
"""Add all the files and create a new commit in the current HEAD"""
assert allow_empty or self.is_dirty()
if not committer:
committer = self.committer if self.committer else self.user
@@ -85,96 +86,20 @@ class Git:
)
committer_time = committer_time if committer_time else user_time
try:
if self.is_dirty():
self.repo.index.add_all()
except pygit2.GitError as e:
if not allow_empty:
raise e
self.repo.index.write()
author = pygit2.Signature(user, user_email, int(user_time.timestamp()))
committer = pygit2.Signature(
committer, committer_email, int(committer_time.timestamp())
)
if not parents:
try:
parents = [self.repo.head.target]
except pygit2.GitError as e:
parents = []
if not allow_empty:
raise e
tree = self.repo.index.write_tree()
return self.repo.create_commit(
"HEAD", author, committer, message, tree, parents
)
def merge(
self,
user,
user_email,
user_time,
message,
commit,
committer=None,
committer_email=None,
committer_time=None,
clean_on_conflict=True,
merged=False,
allow_empty=False,
):
new_branch = False
if not merged:
try:
self.repo.merge(commit)
except KeyError:
# If it is the first commit, we will have a missing
# "HEAD", but the files will be there. We can proceed
# to the commit directly.
new_branch = True
if not merged and self.repo.index.conflicts:
for conflict in self.repo.index.conflicts:
conflict = [c for c in conflict if c]
if conflict:
logging.info(f"CONFLICT {conflict[0].path}")
if clean_on_conflict:
self.clean()
# Now I miss Rust enums
return "CONFLICT"
# Some merges are empty in OBS (no changes, not sure
# why), for now we signal them
if not allow_empty and not self.is_dirty():
# I really really do miss Rust enums
return "EMPTY"
if new_branch:
parents = [commit]
else:
parents = [
self.repo.head.target,
commit,
]
commit = self.commit(
user,
user_email,
user_time,
message,
parents,
committer,
committer_email,
committer_time,
allow_empty=allow_empty,
)
return commit
def merge_abort(self):
self.repo.state_cleanup()
def last_commit(self):
try:
return self.repo.head.target
@@ -184,8 +109,11 @@ class Git:
def branch_head(self, branch):
return self.repo.references["refs/heads/" + branch].target
def set_branch_head(self, branch, commit):
self.repo.references["refs/heads/" + branch].set_target(commit)
def gc(self):
logging.info(f"Garbage recollect and repackage {self.path}")
logging.debug(f"Garbage recollect and repackage {self.path}")
subprocess.run(
["git", "gc", "--auto"],
cwd=self.path,
@@ -256,11 +184,44 @@ class Git:
)
return any(fnmatch.fnmatch(filename, line) for line in patterns)
def remove(self, filename):
self.repo.index.remove(filename)
(self.path / filename).unlink()
def remove(self, file: pathlib.Path):
self.repo.index.remove(file.name)
(self.path / file).unlink()
patterns = self.get_specific_lfs_gitattributes()
if filename in patterns:
patterns.remove(filename)
if file.name in patterns:
patterns.remove(file.name)
self.add_specific_lfs_gitattributes(patterns)
def add_gitea_remote(self, package):
repo_name = package.replace("+", "_")
org_name = "rpm"
if not os.getenv("GITEA_TOKEN"):
logging.warning("Not adding a remote due to missing $GITEA_TOKEN")
return
url = f"https://gitea.opensuse.org/api/v1/org/{org_name}/repos"
response = requests.post(
url,
data={"name": repo_name},
headers={"Authorization": f"token {os.getenv('GITEA_TOKEN')}"},
timeout=10,
)
# 409 Conflict (Already existing)
# 201 Created
if response.status_code not in (201, 409):
print(response.data)
url = f"gitea@gitea.opensuse.org:{org_name}/{repo_name}.git"
self.repo.remotes.create("origin", url)
def push(self):
remo = self.repo.remotes["origin"]
keypair = pygit2.KeypairFromAgent("gitea")
callbacks = pygit2.RemoteCallbacks(credentials=keypair)
refspecs = ["refs/heads/factory"]
if "refs/heads/devel" in self.repo.references:
refspecs.append("refs/heads/devel")
remo.push(refspecs, callbacks=callbacks)

View File

@@ -6,89 +6,38 @@ import yaml
from lib.binary import is_binary_or_large
from lib.db import DB
from lib.git import Git
from lib.lfs_oid import LFSOid
from lib.obs import OBS
from lib.proxy_sha256 import ProxySHA256, md5
from lib.proxy_sha256 import ProxySHA256
from lib.tree_builder import TreeBuilder
from lib.user import User
class GitExporter:
def __init__(self, api_url, project, package, repodir):
self.obs = OBS()
def __init__(self, api_url, project, package, repodir, cachedir):
self.obs = OBS(api_url)
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.db = DB()
self.proxy_sha256 = ProxySHA256(self.obs, self.db)
self.git = Git(
repodir,
repodir / package,
committer="Git OBS Bridge",
committer_email="obsbridge@suse.de",
).create()
)
if self.git.exists():
self.git.open()
else:
self.git.create()
self.git.add_gitea_remote(package)
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)
self.cachedir = cachedir
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}
def check_repo_state(self, flats, branch_state):
state_data = dict()
if os.path.exists(self.state_file):
with open(self.state_file, "r") as f:
@@ -109,55 +58,110 @@ class GitExporter:
found_state = True
if not found_state:
left_to_commit.append(flat)
return left_to_commit
def export_as_git(self):
if os.getenv("CHECK_ALL_LFS"):
LFSOid.check_all(self.db, self.package)
tree = TreeBuilder(self.db).build(self.project, self.package)
flats = tree.as_flat_list()
branch_state = {"factory": None, "devel": None}
left_to_commit = self.check_repo_state(flats, branch_state)
if not left_to_commit:
return
logging.info(f"Commiting into {self.git.path}")
self.run_gc()
users = dict()
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
if flat.commit.userid not in users:
users[flat.commit.userid] = User.find(self.db, flat.commit.userid)
flat.user = users[flat.commit.userid]
self.gc_cnt -= 1
if self.gc_cnt <= 0 and self.gc_interval:
self.run_gc()
logging.debug(f"Committing {flat}")
self.commit_flat(db, flat, branch_state)
self.commit_flat(flat, branch_state)
def limit_download(self, file):
if file.endswith(".spec") or file.endswith(".changes"):
return True
return False
self.git.push()
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(
def run_gc(self):
self.gc_cnt = self.gc_interval
self.git.gc()
def is_lfs_file(self, package, filename, size):
if not is_binary_or_large(filename, size):
return False
return not self.proxy_sha256.is_text(package, filename)
def commit_file(self, flat, file, size, md5):
# have such files been detected as text mimetype before?
if self.is_lfs_file(flat.commit.package, file.name, size):
file_sha256 = self.proxy_sha256.get_or_put(
flat.commit.project,
flat.commit.package,
file,
file.name,
flat.commit.expanded_srcmd5,
self.git.path,
file_md5=md5,
md5,
size,
)
self.git.add(file)
# as it's newly registered, it might be a text file now, so double check
if not self.proxy_sha256.is_text(flat.commit.package, file.name):
self.git.add_lfs(file.name, file_sha256, size)
return
self.commit_non_lfs_file(flat, file, md5)
def commit_non_lfs_file(self, flat, file, md5):
self.obs.change_url(flat.commit.api_url)
self.obs.download(
flat.commit.project,
flat.commit.package,
file.name,
flat.commit.expanded_srcmd5,
self.git.path,
self.cachedir,
file_md5=md5,
)
self.git.add(file)
def branch_fits_parent1(self, flat, branch_state):
if branch_state[flat.branch] is None:
# everything fits nothing
return True
return flat.parent1 == branch_state[flat.branch]
def commit_flat(self, flat, branch_state):
parents = []
self.git.checkout(flat.branch)
if flat.parent1:
if not self.branch_fits_parent1(flat, branch_state):
logging.debug(f"Reset {flat.branch} onto {flat.parent1.short_string()}")
assert flat.parent1.git_commit
self.git.set_branch_head(flat.branch, flat.parent1.git_commit)
self.git.checkout(flat.branch)
parents.append(flat.parent1.git_commit)
if flat.parent2:
assert flat.parent2.git_commit
parents.append(flat.parent2.git_commit)
# create file if not existant
self.git.add_default_lfs_gitattributes(force=False)
to_download, to_delete = flat.commit.calc_delta(branch_state[flat.branch])
for file in to_delete:
self.git.remove(file)
for file, size, md5 in to_download:
self.commit_file(flat, file, size, md5)
commit = self.git.commit(
f"OBS User {flat.commit.userid}",
"null@suse.de",
flat.user.realname,
flat.user.email,
flat.commit.commit_time,
# TODO: Normalize better the commit message
f"{flat.commit.comment}\n\n{flat.commit}",
allow_empty=True,
flat.commit.git_commit_message(),
parents=parents,
)
flat.commit.git_commit = commit

20
lib/hash.py Normal file
View File

@@ -0,0 +1,20 @@
import functools
import hashlib
def _hash(hash_alg, file_or_path):
h = hash_alg()
def __hash(f):
while chunk := f.read(1024 * 4):
h.update(chunk)
if hasattr(file_or_path, "read"):
__hash(file_or_path)
else:
with file_or_path.open("rb") as f:
__hash(f)
return h.hexdigest()
md5 = functools.partial(_hash, hashlib.md5)

View File

@@ -1,3 +1,4 @@
import concurrent.futures
import logging
import xml.etree.ElementTree as ET
@@ -8,46 +9,61 @@ from lib.obs_revision import OBSRevision
from lib.user import User
def refresh_package(importer, project, package):
importer.refresh_package(project, package)
def import_request(importer, number):
importer.import_request(number)
def import_rev(importer, rev):
importer.import_rev(rev)
class Importer:
def __init__(self, api_url, project, package):
# Import a Factory package into the database
self.package = package
def __init__(self, api_url, project, packages):
# Import multiple Factory packages into the database
self.packages = packages
self.project = project
self.obs = OBS()
self.db = DB()
self.obs = OBS(api_url)
assert project == "openSUSE:Factory"
self.obs.change_url(api_url)
self.refreshed_packages = set()
def update_db_package(self, db, project, package):
def import_request(self, number):
self.obs.request(number).import_into_db(self.db)
def update_db_package(self, project, package):
root = self.obs._history(project, package)
if root is None:
return
latest = DBRevision.latest_revision(db, project, package)
latest = DBRevision.max_rev(self.db, project, package)
for r in root.findall("revision"):
rev = OBSRevision(self.obs, project, package).parse(r)
if not latest or rev.rev > latest.rev:
dbrev = DBRevision.import_obs_rev(db, rev)
if not latest or rev.rev > latest:
dbrev = DBRevision.import_obs_rev(self.db, rev)
try:
root = rev.read_link()
except ET.ParseError:
dbrev.set_broken(db)
dbrev.set_broken()
continue
if root is not None:
tprj = root.get("project") or project
tpkg = root.get("package") or package
dbrev.links_to(db, tprj, tpkg)
dbrev.links_to(tprj, tpkg)
def find_linked_revs(self, db):
with db.cursor() as cur:
def find_linked_revs(self):
with self.db.cursor() as cur:
cur.execute(
"""SELECT * from revisions WHERE id in (SELECT l.revision_id FROM links l
LEFT JOIN linked_revs lrevs ON lrevs.revision_id=l.revision_id
WHERE lrevs.id IS NULL) and broken is FALSE;"""
)
for row in cur.fetchall():
rev = DBRevision(row)
linked_rev = rev.linked_rev(db)
rev = DBRevision(self.db, row)
linked_rev = rev.linked_rev()
if not linked_rev:
logging.debug(f"No link {rev}")
continue
@@ -57,8 +73,8 @@ class Importer:
(rev.dbid, linked_rev.dbid),
)
def fetch_all_linked_packages(self, db, project, package):
with db.cursor() as cur:
def fetch_all_linked_packages(self, project, package):
with self.db.cursor() as cur:
cur.execute(
"""SELECT DISTINCT l.project, l.package from links l JOIN revisions r
on r.id=l.revision_id WHERE r.project=%s AND r.package=%s""",
@@ -67,26 +83,26 @@ class Importer:
for row in cur.fetchall():
(lproject, lpackage) = row
# recurse
self.refresh_package(db, lproject, lpackage)
self.refresh_package(lproject, lpackage)
def find_fake_revisions(self, db):
with db.cursor() as cur:
def find_fake_revisions(self):
with self.db.cursor() as cur:
cur.execute(
"SELECT * from revisions WHERE id in (SELECT linked_id from linked_revs WHERE considered=FALSE)"
)
for row in cur.fetchall():
self._find_fake_revision(db, DBRevision(row))
self._find_fake_revision(DBRevision(self.db, row))
def _find_fake_revision(self, db, rev):
prev = rev.previous_commit(db)
def _find_fake_revision(self, rev):
prev = rev.previous_commit()
if not prev:
with db.cursor() as cur:
with self.db.cursor() as cur:
cur.execute(
"UPDATE linked_revs SET considered=TRUE where linked_id=%s",
(rev.dbid,),
)
return
with db.cursor() as cur:
with self.db.cursor() as cur:
cur.execute(
"""SELECT * FROM revisions WHERE id IN
(SELECT revision_id from linked_revs WHERE linked_id=%s)
@@ -95,8 +111,8 @@ class Importer:
)
last_linked = None
for linked in cur.fetchall():
linked = DBRevision(linked)
nextrev = linked.next_commit(db)
linked = DBRevision(self.db, linked)
nextrev = linked.next_commit()
if nextrev and nextrev.commit_time < rev.commit_time:
continue
last_linked = linked
@@ -107,7 +123,7 @@ class Importer:
if not last_linked:
return
with db.cursor() as cur:
with self.db.cursor() as cur:
linked = last_linked
cur.execute(
"SELECT 1 FROM fake_revs where revision_id=%s AND linked_id=%s",
@@ -120,10 +136,10 @@ class Importer:
)
return
fake_rev = linked.rev + rev.rev / 1000.0
comment = f"Updating link to change in {rev.project}/{rev.package} revision {rev.rev}"
comment = f"Updating link to change in {rev.project}/{rev.package} revision {int(rev.rev)}"
cur.execute(
"""INSERT INTO revisions (project,package,rev,unexpanded_srcmd5,
commit_time, userid, comment) VALUES(%s,%s,%s,%s,%s,%s,%s) RETURNING id""",
commit_time, userid, comment, api_url) VALUES(%s,%s,%s,%s,%s,%s,%s,%s) RETURNING id""",
(
linked.project,
linked.package,
@@ -132,6 +148,7 @@ class Importer:
rev.commit_time,
"buildservice-autocommit",
comment,
linked.api_url,
),
)
new_id = cur.fetchone()[0]
@@ -144,70 +161,96 @@ class Importer:
(rev.dbid, linked.dbid),
)
def revisions_without_files(self, db):
with db.cursor() as cur:
def revisions_without_files(self):
with self.db.cursor() as cur:
cur.execute(
"SELECT * FROM revisions WHERE broken=FALSE AND expanded_srcmd5 IS NULL"
)
return [DBRevision(row) for row in cur.fetchall()]
return [DBRevision(self.db, row) for row in cur.fetchall()]
def fill_file_lists(self, db):
self.find_linked_revs(db)
self.find_fake_revisions(db)
for rev in self.revisions_without_files(db):
with db.cursor() as cur:
cur.execute(
"""SELECT unexpanded_srcmd5 from revisions WHERE
id=(SELECT linked_id FROM linked_revs WHERE revision_id=%s)""",
(rev.dbid,),
)
linked_rev = cur.fetchone()
if linked_rev:
linked_rev = linked_rev[0]
list = self.obs.list(
rev.project, rev.package, rev.unexpanded_srcmd5, linked_rev
def import_rev(self, rev):
with self.db.cursor() as cur:
cur.execute(
"""SELECT unexpanded_srcmd5 from revisions WHERE
id=(SELECT linked_id FROM linked_revs WHERE revision_id=%s)""",
(rev.dbid,),
)
if list:
rev.import_dir_list(db, list)
md5 = rev.calculate_files_hash(db)
with db.cursor() as cur:
cur.execute(
"UPDATE revisions SET files_hash=%s WHERE id=%s",
(md5, rev.dbid),
)
else:
rev.set_broken(db)
linked_rev = cur.fetchone()
if linked_rev:
linked_rev = linked_rev[0]
list = self.obs.list(
rev.project, rev.package, rev.unexpanded_srcmd5, linked_rev
)
if list:
rev.import_dir_list(list)
md5 = rev.calculate_files_hash()
with self.db.cursor() as cur:
cur.execute(
"UPDATE revisions SET files_hash=%s WHERE id=%s",
(md5, rev.dbid),
)
else:
rev.set_broken()
def refresh_package(self, db, project, package):
def fill_file_lists(self):
self.find_linked_revs()
self.find_fake_revisions()
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
fs = [
executor.submit(import_rev, self, rev)
for rev in self.revisions_without_files()
]
concurrent.futures.wait(fs)
def refresh_package(self, project, package):
key = f"{project}/{package}"
if key in self.refreshed_packages:
# refreshing once is good enough
return
logging.debug(f"Refresh {project}/{package}")
self.refreshed_packages.add(key)
self.update_db_package(db, project, package)
self.fetch_all_linked_packages(db, project, package)
self.update_db_package(project, package)
self.fetch_all_linked_packages(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)
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
fs = [
executor.submit(refresh_package, self, self.project, package)
for package in self.packages
]
concurrent.futures.wait(fs)
missing_users = User.missing_users(db)
self.db.conn.commit()
fs = [
executor.submit(import_request, self, number)
for number in DBRevision.requests_to_fetch(self.db)
]
concurrent.futures.wait(fs)
self.db.conn.commit()
with self.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 = ANY(%s));""",
(self.project, self.packages),
)
fs = [
executor.submit(refresh_package, self, project, package)
for project, package in cur.fetchall()
]
concurrent.futures.wait(fs)
self.db.conn.commit()
missing_users = User.missing_users(self.db)
for userid in missing_users:
missing_user = self.obs.user(userid)
if missing_user:
missing_user.import_into_db(db)
missing_user.import_into_db(self.db)
self.db.conn.commit()
self.fill_file_lists(db)
db.conn.commit()
self.fill_file_lists()
self.db.conn.commit()

193
lib/lfs_oid.py Normal file
View File

@@ -0,0 +1,193 @@
from __future__ import annotations
import logging
import os
import sys
import requests
from lib.binary import is_text_mimetype
from lib.db import DB
# no need for this class yet, so just leave the migration code here
class LFSOid:
def __init__(self, db: DB) -> None:
self.db = db
self.dbid = None
self.project = None
self.package = None
self.filename = None
self.revision = None
self.sha = None
self.size = None
self.mimetype = None
self.file_md5 = None
@staticmethod
def check_all(db, package):
with db.cursor() as cur:
cur.execute(
"SELECT lfs_oid_id FROM lfs_oid_in_package WHERE package=%s ORDER BY lfs_oid_id DESC limit 10 ",
(package,),
)
for row in cur.fetchall():
oid = LFSOid(db).set_from_dbid(row[0])
if not oid.check():
oid.register()
def add(
self,
project: str,
package: str,
filename: str,
revision: str,
sha256: str,
size: int,
mimetype: str,
file_md5: str,
) -> None:
with self.db.cursor() as cur:
# we UPDATE here so the return functions. conflicts are likely as we look for filename/md5 but conflict on sha256
cur.execute(
"""INSERT INTO lfs_oids (project,package,filename,rev,sha256,size,mimetype,file_md5)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s)
ON CONFLICT (sha256,size) DO UPDATE SET mimetype=EXCLUDED.mimetype
RETURNING id""",
(
project,
package,
filename,
revision,
sha256,
size,
mimetype,
file_md5,
),
)
row = cur.fetchone()
lfs_oid_id = row[0]
cur.execute(
"""INSERT INTO lfs_oid_in_package (package,filename,lfs_oid_id)
VALUES (%s,%s,%s)""",
(package, filename, lfs_oid_id),
)
if is_text_mimetype(mimetype):
cur.execute(
"INSERT INTO text_files (package,filename) VALUES (%s,%s)",
(package, filename),
)
self.db.conn.commit()
self.set_from_dbid(lfs_oid_id)
if not self.check():
self.register()
def check(self):
url = f"http://gitea.opensuse.org:9999/check/{self.sha256}/{self.size}"
response = requests.get(
url,
timeout=10,
)
return response.status_code == 200
def set_from_dbid(self, dbid: int) -> LFSOid:
with self.db.cursor() as cur:
cur.execute("SELECT * from lfs_oids where id=%s", (dbid,))
row = cur.fetchone()
self.set_from_row(row)
assert self.dbid == dbid
return self
def set_from_row(self, row: list) -> LFSOid:
(
self.dbid,
self.project,
self.package,
self.filename,
self.revision,
self.sha256,
self.size,
self.mimetype,
self.file_md5,
) = row
return self
def register(self):
if not os.getenv("GITEA_REGISTER_SECRET"):
logging.info("Not registering LFS due to missing secret")
return
data = {
"secret": os.getenv("GITEA_REGISTER_SECRET"),
"project": self.project,
"package": self.package,
"filename": self.filename,
"rev": self.revision,
"sha256": self.sha256,
"size": self.size,
}
url = "http://gitea.opensuse.org:9999/register"
response = requests.post(
url,
json=data,
timeout=10,
)
logging.info(f"Register LFS returned {response.status_code}")
if __name__ == "__main__":
"""
Import the old data - it only makes sense on a DB with previously scanned revisions
curl -s https://stephan.kulow.org/git_lfs.csv.xz | xz -cd | PYTHONPATH=$PWD /usr/bin/python3 lib/lfs_oid.py
"""
db = DB()
logging.basicConfig(level=logging.DEBUG)
with db.cursor() as cur:
while True:
line = sys.stdin.readline()
if not line:
break
(
project,
package,
filename,
rev,
sha256,
size,
mimetype,
md5,
) = line.strip().split("\t")
cur.execute(
"""INSERT INTO lfs_oids (project,package,filename,rev,sha256,size,mimetype,file_md5)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s) ON CONFLICT DO NOTHING""",
(project, package, filename, rev, sha256, size, mimetype, md5),
)
cur.execute(
"""
CREATE TEMPORARY TABLE lfs_oid_in_revision (
revision_id INTEGER,
lfs_oid_id INTEGER NOT NULL,
name VARCHAR(255) NOT NULL
)
"""
)
cur.execute(
"""INSERT INTO lfs_oid_in_revision (revision_id, lfs_oid_id, name)
SELECT revision_id,lfs_oids.id,files.name FROM lfs_oids JOIN files ON files.md5=lfs_oids.file_md5"""
)
cur.execute(
"""INSERT INTO text_files (package,filename)
SELECT DISTINCT r.package, lfs_oid_in_revision.name FROM lfs_oids
JOIN lfs_oid_in_revision on lfs_oid_in_revision.lfs_oid_id=lfs_oids.id
JOIN revisions r ON r.id=lfs_oid_in_revision.revision_id
WHERE lfs_oids.mimetype like 'text/%' ON CONFLICT DO NOTHING"""
)
cur.execute(
"""INSERT INTO lfs_oid_in_package (lfs_oid_id, package, filename)
SELECT DISTINCT lfs_oids.id,r.package, lfs_oid_in_revision.name FROM lfs_oids
JOIN lfs_oid_in_revision on lfs_oid_in_revision.lfs_oid_id=lfs_oids.id
JOIN revisions r ON r.id=lfs_oid_in_revision.revision_id"""
)
db.conn.commit()

View File

@@ -1,5 +1,7 @@
import errno
import logging
import os
import shutil
import time
import urllib.parse
import xml.etree.ElementTree as ET
@@ -7,6 +9,7 @@ from urllib.error import HTTPError
import osc.core
from lib.hash import md5
from lib.request import Request
from lib.user import User
@@ -56,13 +59,14 @@ osc.core.http_GET = retry(osc.core.http_GET)
class OBS:
def __init__(self, url=None):
if url:
self.change_url(url)
def __init__(self, url):
self.url = None
self.change_url(url)
def change_url(self, url):
self.url = url
osc.conf.get_config(override_apiurl=url)
if url != self.url:
self.url = url
osc.conf.get_config(override_apiurl=url)
def _xml(self, url_path, **params):
url = osc.core.makeurl(self.url, [url_path], params)
@@ -158,10 +162,25 @@ class OBS:
name: str,
revision: str,
dirpath: str,
cachedir: str,
file_md5: str,
) -> None:
with (dirpath / name).open("wb") as f:
f.write(self._download(project, package, name, revision).read())
cached_file = self._path_from_md5(name, cachedir, file_md5)
if not self.in_cache(name, cachedir, file_md5):
with (dirpath / name).open("wb") as f:
logging.debug(f"Download {project}/{package}/{name}")
f.write(self._download(project, package, name, revision).read())
# Validate the MD5 of the downloaded file
if md5(dirpath / name) != file_md5:
raise Exception(f"Download error in {name}")
shutil.copy(dirpath / name, cached_file.with_suffix(".new"))
os.rename(cached_file.with_suffix(".new"), cached_file)
else:
shutil.copy(cached_file, dirpath / name)
logging.debug(f"Use cached {project}/{package}/{name}")
def list(self, project, package, srcmd5, linkrev):
params = {"rev": srcmd5, "expand": "1"}
@@ -179,3 +198,11 @@ class OBS:
raise e
return root
def _path_from_md5(self, name, cachedir, md5):
filepath = cachedir / md5[:3]
filepath.mkdir(parents=True, exist_ok=True)
return filepath / md5[3:]
def in_cache(self, name, cachedir, md5):
return self._path_from_md5(name, cachedir, md5).exists()

View File

@@ -1,106 +1,92 @@
import functools
import hashlib
import logging
import urllib
try:
import magic
except:
print("Install python3-python-magic, not python3-magic")
raise
import requests
def _hash(hash_alg, file_or_path):
h = hash_alg()
def __hash(f):
while chunk := f.read(1024 * 4):
h.update(chunk)
if hasattr(file_or_path, "read"):
__hash(file_or_path)
else:
with file_or_path.open("rb") as f:
__hash(f)
return h.hexdigest()
md5 = functools.partial(_hash, hashlib.md5)
sha256 = functools.partial(_hash, hashlib.sha256)
from lib.db import DB
from lib.lfs_oid import LFSOid
from lib.obs import OBS
class ProxySHA256:
def __init__(self, obs, url=None, enabled=True):
def __init__(self, obs: OBS, db: DB):
self.obs = obs
self.url = url if url else "http://source.dyn.cloud.suse.de"
self.enabled = enabled
self.db = db
self.hashes = None
self.texts = set()
def load_package(self, package):
# _project is unreachable for the proxy - due to being a fake package
if package == "_project":
self.enabled = False
self.texts = set(["_config", "_service"])
self.hashes = dict()
return
logging.info("Retrieve all previously defined SHA256")
response = requests.get(f"http://source.dyn.cloud.suse.de/package/{package}")
if response.status_code == 200:
json = response.json()
self.hashes = json["shas"]
self.texts = set(json["texts"])
self.texts = None
self.mime = None
def get(self, package, name, file_md5):
key = f"{file_md5}-{name}"
if self.hashes is None:
if self.enabled:
self.load_package(package)
else:
self.hashes = {}
return self.hashes.get(key, None)
self.load_hashes(package)
key = f"{file_md5}-{name}"
ret = self.hashes.get(key)
return ret
def _proxy_put(self, project, package, name, revision, file_md5, size):
quoted_name = urllib.parse.quote(name)
url = f"{self.obs.url}/public/source/{project}/{package}/{quoted_name}?rev={revision}"
response = requests.put(
self.url,
data={
"hash": file_md5,
"filename": name,
"url": url,
"package": package,
},
)
if response.status_code != 200:
raise Exception(f"Redirector error on {self.url} for {url}")
key = (file_md5, name)
self.hashes[key] = {
"sha256": response.content.decode("utf-8"),
"fsize": size,
}
return self.hashes[key]
def _obs_put(self, project, package, name, revision, file_md5, size):
key = (file_md5, name)
self.hashes[key] = {
"sha256": sha256(self.obs._download(project, package, name, revision)),
"fsize": size,
}
return self.hashes[key]
def load_hashes(self, package):
with self.db.cursor() as cur:
cur.execute(
"""SELECT lfs_oids.file_md5,lop.filename,lfs_oids.sha256,lfs_oids.size
FROM lfs_oid_in_package lop
JOIN lfs_oids ON lfs_oids.id=lop.lfs_oid_id
WHERE lop.package=%s""",
(package,),
)
self.hashes = {
f"{row[0]}-{row[1]}": (row[2], row[3]) for row in cur.fetchall()
}
def put(self, project, package, name, revision, file_md5, size):
if not self.enabled:
return self._obs_put(project, package, name, revision, file_md5, size)
return self._proxy_put(project, package, name, revision, file_md5, size)
def is_text(self, filename):
if not self.mime:
self.mime = magic.Magic(mime=True)
mimetype = None
logging.debug(f"Add LFS for {project}/{package}/{name}")
fin = self.obs._download(project, package, name, revision)
sha = hashlib.sha256()
while True:
buffer = fin.read(10000)
if not buffer:
break
sha.update(buffer)
# only guess from the first 10K
if not mimetype:
mimetype = self.mime.from_buffer(buffer)
fin.close()
LFSOid(self.db).add(
project, package, name, revision, sha.hexdigest(), size, mimetype, file_md5
)
# reset
self.hashes = None
self.texts = None
return self.get(package, name, file_md5)
def is_text(self, package, filename):
if self.texts is None:
self.load_texts(package)
return filename in self.texts
def load_texts(self, package):
self.texts = set()
with self.db.cursor() as cur:
cur.execute("SELECT filename from text_files where package=%s", (package,))
for row in cur.fetchall():
self.texts.add(row[0])
def get_or_put(self, project, package, name, revision, file_md5, size):
result = self.get(package, name, file_md5)
if not result:
result = self.put(project, package, name, revision, file_md5, size)
# Sanity check
if result["fsize"] != size:
raise Exception(f"Redirector has different size for {name}")
sha256, db_size = result
assert db_size == size
return result
return sha256

View File

@@ -16,11 +16,11 @@ class TestExporter:
db = DB()
with db.cursor() as cur:
cur.execute(
"SELECT * from revisions where package=%s ORDER BY project,rev",
"SELECT * from revisions where package=%s ORDER BY commit_time",
(self.package,),
)
data = {"revisions": []}
for row in cur.fetchall():
data["revisions"].append(DBRevision(row).as_dict(db))
data["revisions"].append(DBRevision(db, row).as_dict())
yaml.dump(data, sys.stdout, default_flow_style=False)

View File

@@ -104,14 +104,24 @@ class TreeBuilder:
"""For a given revision in the target, find the node in the source chain
that matches the files"""
node = source_chain
candidates = []
while node:
# exclude reverts happening after the merge
if (
node.revision.commit_time <= revision.commit_time
and node.revision.files_hash == revision.files_hash
):
return node
candidates.append(node)
if node.merged_into:
# we can't have candidates that are crossing previous merges
# see https://gitea.opensuse.org/importers/git-importer/issues/14
candidates = []
node = node.parent
if candidates:
# the first candidate is the youngest one that matches the check. That's
# good enough. See FastCGI test case for rev 36 and 38: 37 reverted 36 and
# then 38 reverting the revert before it was submitted.
return candidates[0]
def add_merge_points(self, factory_revisions):
"""For all target revisions that accepted a request, look up the merge

View File

@@ -1,3 +1,7 @@
from __future__ import annotations
from lib.db import DB
FAKE_ACCOUNTS = (
"unknown",
"buildservice-autocommit",
@@ -15,6 +19,22 @@ FAKE_ACCOUNTS = (
class User:
@staticmethod
def find(db: DB, userid: str) -> User:
row = User.lookup(db, userid)
self = User()
self.userid = userid
if row:
(_, _, self.email, self.realname) = row
else:
self.email = ""
self.realname = ""
if not self.email:
self.email = "null@suse.de"
if not self.realname:
self.realname = f"OBS User {userid}"
return self
def parse(self, xml, userid):
self.userid = userid
self.realname = xml.find("realname").text

14502
packages Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -6,11 +6,14 @@ from lib.db_revision import DBRevision
from lib.obs import OBS
from lib.obs_revision import OBSRevision
# needs to exist in local oscrc (little tricky)
API_URL = "https://api.opensuse.org"
class TestDBMethods(unittest.TestCase):
def setUp(self):
self.db = DB(section="test")
self.obs = OBS()
self.obs = OBS(API_URL)
def test_import(self):
test_rev = OBSRevision(self.obs, "openSUSE:Factory", "xz")
@@ -30,6 +33,7 @@ class TestDBMethods(unittest.TestCase):
db_rev = DBRevision.fetch_revision(
self.db, project="openSUSE:Factory", package="xz", rev="70"
)
self.assertEqual(db_rev.api_url, API_URL)
self.assertEqual(str(test_rev), str(db_rev))

4528
tests/fixtures/FastCGI-data.yaml vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
- factory c:openSUSE:Factory/FastCGI/29.0 p1:openSUSE:Factory/FastCGI/28.0 p2:devel:libraries:c_c++/FastCGI/40.0
- devel c:devel:libraries:c_c++/FastCGI/40.0 p1:devel:libraries:c_c++/FastCGI/38.0
- factory c:openSUSE:Factory/FastCGI/28.0 p1:openSUSE:Factory/FastCGI/27.0 p2:devel:libraries:c_c++/FastCGI/38.0
- devel c:devel:libraries:c_c++/FastCGI/38.0 p1:devel:libraries:c_c++/FastCGI/37.0
- devel c:devel:libraries:c_c++/FastCGI/37.0 p1:devel:libraries:c_c++/FastCGI/36.0
- devel c:devel:libraries:c_c++/FastCGI/36.0 p1:devel:libraries:c_c++/FastCGI/34.0
- factory c:openSUSE:Factory/FastCGI/27.0 p1:openSUSE:Factory/FastCGI/26.0 p2:devel:libraries:c_c++/FastCGI/34.0
- devel c:devel:libraries:c_c++/FastCGI/34.0 p1:devel:libraries:c_c++/FastCGI/32.0
- factory c:openSUSE:Factory/FastCGI/26.0 p1:openSUSE:Factory/FastCGI/23.0 p2:devel:libraries:c_c++/FastCGI/32.0
- devel c:devel:libraries:c_c++/FastCGI/32.0 p1:devel:libraries:c_c++/FastCGI/30.0
- factory c:openSUSE:Factory/FastCGI/23.0 p1:openSUSE:Factory/FastCGI/20.0 p2:devel:libraries:c_c++/FastCGI/30.0
- devel c:devel:libraries:c_c++/FastCGI/30.0 p1:devel:libraries:c_c++/FastCGI/28.0
- factory c:openSUSE:Factory/FastCGI/20.0 p1:openSUSE:Factory/FastCGI/19.0 p2:devel:libraries:c_c++/FastCGI/28.0
- devel c:devel:libraries:c_c++/FastCGI/28.0 p1:devel:libraries:c_c++/FastCGI/26.0
- factory c:openSUSE:Factory/FastCGI/19.0 p1:openSUSE:Factory/FastCGI/18.0 p2:devel:libraries:c_c++/FastCGI/26.0
- devel c:devel:libraries:c_c++/FastCGI/26.0 p1:devel:libraries:c_c++/FastCGI/24.0
- factory c:openSUSE:Factory/FastCGI/18.0 p1:openSUSE:Factory/FastCGI/16.0 p2:devel:libraries:c_c++/FastCGI/24.0
- devel c:devel:libraries:c_c++/FastCGI/24.0 p1:devel:libraries:c_c++/FastCGI/22.0
- factory c:openSUSE:Factory/FastCGI/16.0 p1:openSUSE:Factory/FastCGI/15.0 p2:devel:libraries:c_c++/FastCGI/22.0
- devel c:devel:libraries:c_c++/FastCGI/22.0 p1:devel:libraries:c_c++/FastCGI/20.0
- factory c:openSUSE:Factory/FastCGI/15.0 p1:openSUSE:Factory/FastCGI/14.0 p2:devel:libraries:c_c++/FastCGI/20.0
- devel c:devel:libraries:c_c++/FastCGI/20.0 p1:devel:libraries:c_c++/FastCGI/19.014
- devel c:devel:libraries:c_c++/FastCGI/19.014 p1:devel:libraries:c_c++/FastCGI/18.0
- factory c:openSUSE:Factory/FastCGI/14.0 p1:openSUSE:Factory/FastCGI/13.0
- factory c:openSUSE:Factory/FastCGI/13.0 p1:openSUSE:Factory/FastCGI/11.0 p2:devel:libraries:c_c++/FastCGI/18.0
- devel c:devel:libraries:c_c++/FastCGI/18.0 p1:openSUSE:Factory/FastCGI/11.0
- factory c:openSUSE:Factory/FastCGI/11.0 p1:openSUSE:Factory/FastCGI/10.0
- factory c:openSUSE:Factory/FastCGI/10.0 p1:openSUSE:Factory/FastCGI/7.0
- factory c:openSUSE:Factory/FastCGI/7.0 p1:openSUSE:Factory/FastCGI/6.0
- factory c:openSUSE:Factory/FastCGI/6.0 p1:openSUSE:Factory/FastCGI/4.0
- factory c:openSUSE:Factory/FastCGI/4.0 p1:openSUSE:Factory/FastCGI/3.0
- factory c:openSUSE:Factory/FastCGI/3.0 p1:openSUSE:Factory/FastCGI/1.0
- factory c:openSUSE:Factory/FastCGI/1.0

View File

@@ -0,0 +1,44 @@
- commit: openSUSE:Factory/FastCGI/29.0
merged:
- devel:libraries:c_c++/FastCGI/40.0
- commit: openSUSE:Factory/FastCGI/28.0
merged:
- devel:libraries:c_c++/FastCGI/38.0
- devel:libraries:c_c++/FastCGI/37.0
- devel:libraries:c_c++/FastCGI/36.0
- commit: openSUSE:Factory/FastCGI/27.0
merged:
- devel:libraries:c_c++/FastCGI/34.0
- commit: openSUSE:Factory/FastCGI/26.0
merged:
- devel:libraries:c_c++/FastCGI/32.0
- commit: openSUSE:Factory/FastCGI/23.0
merged:
- devel:libraries:c_c++/FastCGI/30.0
- commit: openSUSE:Factory/FastCGI/20.0
merged:
- devel:libraries:c_c++/FastCGI/28.0
- commit: openSUSE:Factory/FastCGI/19.0
merged:
- devel:libraries:c_c++/FastCGI/26.0
- commit: openSUSE:Factory/FastCGI/18.0
merged:
- devel:libraries:c_c++/FastCGI/24.0
- commit: openSUSE:Factory/FastCGI/16.0
merged:
- devel:libraries:c_c++/FastCGI/22.0
- commit: openSUSE:Factory/FastCGI/15.0
merged:
- devel:libraries:c_c++/FastCGI/20.0
- devel:libraries:c_c++/FastCGI/19.014
- commit: openSUSE:Factory/FastCGI/14.0
- commit: openSUSE:Factory/FastCGI/13.0
merged:
- devel:libraries:c_c++/FastCGI/18.0
- commit: openSUSE:Factory/FastCGI/11.0
- commit: openSUSE:Factory/FastCGI/10.0
- commit: openSUSE:Factory/FastCGI/7.0
- commit: openSUSE:Factory/FastCGI/6.0
- commit: openSUSE:Factory/FastCGI/4.0
- commit: openSUSE:Factory/FastCGI/3.0
- commit: openSUSE:Factory/FastCGI/1.0

9756
tests/fixtures/breeze-data.yaml vendored Normal file

File diff suppressed because it is too large Load Diff

171
tests/fixtures/breeze-expected-list.yaml vendored Normal file
View File

@@ -0,0 +1,171 @@
- factory c:openSUSE:Factory/breeze/43.0 p1:openSUSE:Factory/breeze/42.0 p2:KDE:Frameworks5/breeze/150.0
- devel c:KDE:Frameworks5/breeze/150.0 p1:KDE:Frameworks5/breeze/148.0
- factory c:openSUSE:Factory/breeze/42.0 p1:openSUSE:Factory/breeze/41.0 p2:KDE:Frameworks5/breeze/148.0
- devel c:KDE:Frameworks5/breeze/148.0 p1:KDE:Frameworks5/breeze/147.0
- devel c:KDE:Frameworks5/breeze/147.0 p1:KDE:Frameworks5/breeze/145.0
- factory c:openSUSE:Factory/breeze/41.0 p1:openSUSE:Factory/breeze/40.0 p2:KDE:Frameworks5/breeze/145.0
- devel c:KDE:Frameworks5/breeze/145.0 p1:KDE:Frameworks5/breeze/143.0
- factory c:openSUSE:Factory/breeze/40.0 p1:openSUSE:Factory/breeze/39.0 p2:KDE:Frameworks5/breeze/143.0
- devel c:KDE:Frameworks5/breeze/143.0 p1:KDE:Frameworks5/breeze/142.0
- devel c:KDE:Frameworks5/breeze/142.0 p1:KDE:Frameworks5/breeze/141.0
- devel c:KDE:Frameworks5/breeze/141.0 p1:KDE:Frameworks5/breeze/139.0
- factory c:openSUSE:Factory/breeze/39.0 p1:openSUSE:Factory/breeze/38.0 p2:KDE:Frameworks5/breeze/139.0
- devel c:KDE:Frameworks5/breeze/139.0 p1:KDE:Frameworks5/breeze/137.0
- factory c:openSUSE:Factory/breeze/38.0 p1:openSUSE:Factory/breeze/37.0 p2:KDE:Frameworks5/breeze/137.0
- devel c:KDE:Frameworks5/breeze/137.0 p1:KDE:Frameworks5/breeze/136.0
- devel c:KDE:Frameworks5/breeze/136.0 p1:KDE:Frameworks5/breeze/135.0
- devel c:KDE:Frameworks5/breeze/135.0 p1:KDE:Frameworks5/breeze/134.0
- devel c:KDE:Frameworks5/breeze/134.0 p1:KDE:Frameworks5/breeze/132.0
- factory c:openSUSE:Factory/breeze/37.0 p1:openSUSE:Factory/breeze/36.0 p2:KDE:Frameworks5/breeze/132.0
- devel c:KDE:Frameworks5/breeze/132.0 p1:KDE:Frameworks5/breeze/130.0
- factory c:openSUSE:Factory/breeze/36.0 p1:openSUSE:Factory/breeze/35.0 p2:KDE:Frameworks5/breeze/130.0
- devel c:KDE:Frameworks5/breeze/130.0 p1:KDE:Frameworks5/breeze/128.0
- factory c:openSUSE:Factory/breeze/35.0 p1:openSUSE:Factory/breeze/34.0 p2:KDE:Frameworks5/breeze/128.0
- devel c:KDE:Frameworks5/breeze/128.0 p1:KDE:Frameworks5/breeze/127.0
- devel c:KDE:Frameworks5/breeze/127.0 p1:KDE:Frameworks5/breeze/126.034
- devel c:KDE:Frameworks5/breeze/126.034 p1:KDE:Frameworks5/breeze/126.0
- devel c:KDE:Frameworks5/breeze/126.0 p1:KDE:Frameworks5/breeze/125.0
- devel c:KDE:Frameworks5/breeze/125.0 p1:KDE:Frameworks5/breeze/124.0
- devel c:KDE:Frameworks5/breeze/124.0 p1:KDE:Frameworks5/breeze/123.0
- devel c:KDE:Frameworks5/breeze/123.0 p1:KDE:Frameworks5/breeze/122.0
- devel c:KDE:Frameworks5/breeze/122.0 p1:KDE:Frameworks5/breeze/120.0
- factory c:openSUSE:Factory/breeze/34.0 p1:openSUSE:Factory/breeze/33.0 p2:KDE:Frameworks5:LTS/breeze/14.0
- devel c:KDE:Frameworks5:LTS/breeze/14.0 p1:KDE:Frameworks5:LTS/breeze/13.0
- devel c:KDE:Frameworks5:LTS/breeze/13.0 p1:KDE:Frameworks5:LTS/breeze/12.0
- devel c:KDE:Frameworks5:LTS/breeze/12.0 p1:KDE:Frameworks5:LTS/breeze/11.0
- factory c:openSUSE:Factory/breeze/33.0 p1:openSUSE:Factory/breeze/32.0 p2:KDE:Frameworks5:LTS/breeze/11.0
- devel c:KDE:Frameworks5:LTS/breeze/11.0 p1:KDE:Frameworks5:LTS/breeze/10.0
- devel c:KDE:Frameworks5:LTS/breeze/10.0 p1:KDE:Frameworks5:LTS/breeze/9.0
- devel c:KDE:Frameworks5:LTS/breeze/9.0 p1:KDE:Frameworks5:LTS/breeze/8.0
- devel c:KDE:Frameworks5:LTS/breeze/8.0 p1:KDE:Frameworks5:LTS/breeze/7.0
- devel c:KDE:Frameworks5:LTS/breeze/7.0 p1:KDE:Frameworks5:LTS/breeze/6.0
- devel c:KDE:Frameworks5:LTS/breeze/6.0 p1:KDE:Frameworks5:LTS/breeze/5.0
- devel c:KDE:Frameworks5:LTS/breeze/5.0 p1:KDE:Frameworks5:LTS/breeze/4.0
- devel c:KDE:Frameworks5:LTS/breeze/4.0 p1:KDE:Frameworks5:LTS/breeze/3.0
- devel c:KDE:Frameworks5:LTS/breeze/3.0 p1:KDE:Frameworks5:LTS/breeze/2.0
- devel c:KDE:Frameworks5:LTS/breeze/2.0 p1:KDE:Frameworks5:LTS/breeze/1.0
- devel c:KDE:Frameworks5:LTS/breeze/1.0 p1:openSUSE:Factory/breeze/32.0
- factory c:openSUSE:Factory/breeze/32.0 p1:openSUSE:Factory/breeze/31.0 p2:KDE:Frameworks5/breeze/120.0
- devel c:KDE:Frameworks5/breeze/120.0 p1:KDE:Frameworks5/breeze/117.0
- factory c:openSUSE:Factory/breeze/31.0 p1:openSUSE:Factory/breeze/30.0 p2:KDE:Frameworks5/breeze/117.0
- devel c:KDE:Frameworks5/breeze/117.0 p1:KDE:Frameworks5/breeze/116.0
- factory c:openSUSE:Factory/breeze/30.0 p1:openSUSE:Factory/breeze/29.0 p2:KDE:Frameworks5/breeze/116.0
- devel c:KDE:Frameworks5/breeze/116.0 p1:KDE:Frameworks5/breeze/115.0
- devel c:KDE:Frameworks5/breeze/115.0 p1:KDE:Frameworks5/breeze/113.0
- devel c:KDE:Frameworks5/breeze/113.0 p1:KDE:Frameworks5/breeze/112.0
- devel c:KDE:Frameworks5/breeze/112.0 p1:KDE:Frameworks5/breeze/111.0
- factory c:openSUSE:Factory/breeze/29.0 p1:openSUSE:Factory/breeze/28.0 p2:KDE:Frameworks5/breeze/111.0
- devel c:KDE:Frameworks5/breeze/111.0 p1:KDE:Frameworks5/breeze/110.0
- devel c:KDE:Frameworks5/breeze/110.0 p1:KDE:Frameworks5/breeze/109.0
- devel c:KDE:Frameworks5/breeze/109.0 p1:KDE:Frameworks5/breeze/108.0
- devel c:KDE:Frameworks5/breeze/108.0 p1:KDE:Frameworks5/breeze/107.0
- devel c:KDE:Frameworks5/breeze/107.0 p1:KDE:Frameworks5/breeze/105.0
- factory c:openSUSE:Factory/breeze/28.0 p1:openSUSE:Factory/breeze/27.0 p2:KDE:Frameworks5/breeze/105.0
- devel c:KDE:Frameworks5/breeze/105.0 p1:KDE:Frameworks5/breeze/103.0
- factory c:openSUSE:Factory/breeze/27.0 p1:openSUSE:Factory/breeze/26.0 p2:KDE:Frameworks5/breeze/103.0
- devel c:KDE:Frameworks5/breeze/103.0 p1:KDE:Frameworks5/breeze/100.0
- factory c:openSUSE:Factory/breeze/26.0 p1:openSUSE:Factory/breeze/25.0 p2:KDE:Frameworks5/breeze/100.0
- devel c:KDE:Frameworks5/breeze/100.0 p1:KDE:Frameworks5/breeze/99.0
- factory c:openSUSE:Factory/breeze/25.0 p1:openSUSE:Factory/breeze/24.0 p2:KDE:Frameworks5/breeze/99.0
- devel c:KDE:Frameworks5/breeze/99.0 p1:KDE:Frameworks5/breeze/98.0
- devel c:KDE:Frameworks5/breeze/98.0 p1:KDE:Frameworks5/breeze/97.0
- devel c:KDE:Frameworks5/breeze/97.0 p1:KDE:Frameworks5/breeze/95.0
- factory c:openSUSE:Factory/breeze/24.0 p1:openSUSE:Factory/breeze/23.0 p2:KDE:Frameworks5/breeze/95.0
- devel c:KDE:Frameworks5/breeze/95.0 p1:KDE:Frameworks5/breeze/93.0
- factory c:openSUSE:Factory/breeze/23.0 p1:openSUSE:Factory/breeze/22.0 p2:KDE:Frameworks5/breeze/93.0
- devel c:KDE:Frameworks5/breeze/93.0 p1:KDE:Frameworks5/breeze/91.0
- factory c:openSUSE:Factory/breeze/22.0 p1:openSUSE:Factory/breeze/21.0 p2:KDE:Frameworks5/breeze/91.0
- devel c:KDE:Frameworks5/breeze/91.0 p1:KDE:Frameworks5/breeze/88.0
- factory c:openSUSE:Factory/breeze/21.0 p1:openSUSE:Factory/breeze/20.0 p2:KDE:Frameworks5/breeze/88.0
- devel c:KDE:Frameworks5/breeze/88.0 p1:KDE:Frameworks5/breeze/87.0
- factory c:openSUSE:Factory/breeze/20.0 p1:openSUSE:Factory/breeze/19.0 p2:KDE:Frameworks5/breeze/87.0
- devel c:KDE:Frameworks5/breeze/87.0 p1:KDE:Frameworks5/breeze/86.0
- devel c:KDE:Frameworks5/breeze/86.0 p1:KDE:Frameworks5/breeze/85.0
- devel c:KDE:Frameworks5/breeze/85.0 p1:KDE:Frameworks5/breeze/84.0
- devel c:KDE:Frameworks5/breeze/84.0 p1:KDE:Frameworks5/breeze/83.0
- devel c:KDE:Frameworks5/breeze/83.0 p1:KDE:Frameworks5/breeze/82.0
- devel c:KDE:Frameworks5/breeze/82.0 p1:KDE:Frameworks5/breeze/81.0
- devel c:KDE:Frameworks5/breeze/81.0 p1:KDE:Frameworks5/breeze/80.0
- devel c:KDE:Frameworks5/breeze/80.0 p1:KDE:Frameworks5/breeze/79.0
- devel c:KDE:Frameworks5/breeze/79.0 p1:KDE:Frameworks5/breeze/78.0
- devel c:KDE:Frameworks5/breeze/78.0 p1:KDE:Frameworks5/breeze/76.0
- devel c:KDE:Frameworks5/breeze/76.0 p1:KDE:Frameworks5/breeze/75.0
- factory c:openSUSE:Factory/breeze/19.0 p1:openSUSE:Factory/breeze/18.0 p2:KDE:Frameworks5/breeze/75.0
- devel c:KDE:Frameworks5/breeze/75.0 p1:KDE:Frameworks5/breeze/74.0
- devel c:KDE:Frameworks5/breeze/74.0 p1:KDE:Frameworks5/breeze/73.0
- devel c:KDE:Frameworks5/breeze/73.0 p1:KDE:Frameworks5/breeze/71.0
- factory c:openSUSE:Factory/breeze/18.0 p1:openSUSE:Factory/breeze/17.0 p2:KDE:Frameworks5/breeze/71.0
- devel c:KDE:Frameworks5/breeze/71.0 p1:KDE:Frameworks5/breeze/70.0
- devel c:KDE:Frameworks5/breeze/70.0 p1:KDE:Frameworks5/breeze/69.0
- devel c:KDE:Frameworks5/breeze/69.0 p1:KDE:Frameworks5/breeze/68.0
- devel c:KDE:Frameworks5/breeze/68.0 p1:KDE:Frameworks5/breeze/67.0
- devel c:KDE:Frameworks5/breeze/67.0 p1:KDE:Frameworks5/breeze/65.0
- factory c:openSUSE:Factory/breeze/17.0 p1:openSUSE:Factory/breeze/16.0 p2:KDE:Frameworks5/breeze/65.0
- devel c:KDE:Frameworks5/breeze/65.0 p1:KDE:Frameworks5/breeze/64.0
- devel c:KDE:Frameworks5/breeze/64.0 p1:KDE:Frameworks5/breeze/62.0
- factory c:openSUSE:Factory/breeze/16.0 p1:openSUSE:Factory/breeze/15.0 p2:KDE:Frameworks5/breeze/62.0
- devel c:KDE:Frameworks5/breeze/62.0 p1:KDE:Frameworks5/breeze/61.0
- devel c:KDE:Frameworks5/breeze/61.0 p1:KDE:Frameworks5/breeze/60.0
- devel c:KDE:Frameworks5/breeze/60.0 p1:KDE:Frameworks5/breeze/59.0
- devel c:KDE:Frameworks5/breeze/59.0 p1:KDE:Frameworks5/breeze/58.0
- devel c:KDE:Frameworks5/breeze/58.0 p1:KDE:Frameworks5/breeze/57.0
- devel c:KDE:Frameworks5/breeze/57.0 p1:KDE:Frameworks5/breeze/55.0
- factory c:openSUSE:Factory/breeze/15.0 p1:openSUSE:Factory/breeze/14.0 p2:KDE:Frameworks5/breeze/55.0
- devel c:KDE:Frameworks5/breeze/55.0 p1:KDE:Frameworks5/breeze/53.0
- factory c:openSUSE:Factory/breeze/14.0 p1:openSUSE:Factory/breeze/13.0 p2:KDE:Frameworks5/breeze/53.0
- devel c:KDE:Frameworks5/breeze/53.0 p1:KDE:Frameworks5/breeze/51.0
- factory c:openSUSE:Factory/breeze/13.0 p1:openSUSE:Factory/breeze/12.0 p2:KDE:Frameworks5/breeze/51.0
- devel c:KDE:Frameworks5/breeze/51.0 p1:KDE:Frameworks5/breeze/50.0
- devel c:KDE:Frameworks5/breeze/50.0 p1:KDE:Frameworks5/breeze/49.0
- devel c:KDE:Frameworks5/breeze/49.0 p1:KDE:Frameworks5/breeze/48.0
- devel c:KDE:Frameworks5/breeze/48.0 p1:KDE:Frameworks5/breeze/47.0
- devel c:KDE:Frameworks5/breeze/47.0 p1:KDE:Frameworks5/breeze/46.0
- devel c:KDE:Frameworks5/breeze/46.0 p1:KDE:Frameworks5/breeze/45.0
- devel c:KDE:Frameworks5/breeze/45.0 p1:KDE:Frameworks5/breeze/44.0
- devel c:KDE:Frameworks5/breeze/44.0 p1:KDE:Frameworks5/breeze/43.0
- devel c:KDE:Frameworks5/breeze/43.0 p1:KDE:Frameworks5/breeze/41.0
- factory c:openSUSE:Factory/breeze/12.0 p1:openSUSE:Factory/breeze/11.0 p2:KDE:Frameworks5/breeze/41.0
- devel c:KDE:Frameworks5/breeze/41.0 p1:KDE:Frameworks5/breeze/40.0
- devel c:KDE:Frameworks5/breeze/40.0 p1:KDE:Frameworks5/breeze/39.0
- devel c:KDE:Frameworks5/breeze/39.0 p1:KDE:Frameworks5/breeze/38.0
- factory c:openSUSE:Factory/breeze/11.0 p1:openSUSE:Factory/breeze/10.0 p2:KDE:Frameworks5/breeze/38.0
- devel c:KDE:Frameworks5/breeze/38.0 p1:KDE:Frameworks5/breeze/36.0
- factory c:openSUSE:Factory/breeze/10.0 p1:openSUSE:Factory/breeze/9.0 p2:KDE:Frameworks5/breeze/36.0
- devel c:KDE:Frameworks5/breeze/36.0 p1:KDE:Frameworks5/breeze/35.0
- devel c:KDE:Frameworks5/breeze/35.0 p1:KDE:Frameworks5/breeze/33.0
- factory c:openSUSE:Factory/breeze/9.0 p1:openSUSE:Factory/breeze/8.0 p2:KDE:Frameworks5/breeze/33.0
- devel c:KDE:Frameworks5/breeze/33.0 p1:KDE:Frameworks5/breeze/32.0
- devel c:KDE:Frameworks5/breeze/32.0 p1:KDE:Frameworks5/breeze/31.0
- devel c:KDE:Frameworks5/breeze/31.0 p1:KDE:Frameworks5/breeze/30.0
- devel c:KDE:Frameworks5/breeze/30.0 p1:KDE:Frameworks5/breeze/28.0
- factory c:openSUSE:Factory/breeze/8.0 p1:openSUSE:Factory/breeze/7.0 p2:KDE:Frameworks5/breeze/28.0
- devel c:KDE:Frameworks5/breeze/28.0 p1:KDE:Frameworks5/breeze/27.0
- devel c:KDE:Frameworks5/breeze/27.0 p1:KDE:Frameworks5/breeze/25.0
- factory c:openSUSE:Factory/breeze/7.0 p1:openSUSE:Factory/breeze/6.0 p2:KDE:Frameworks5/breeze/25.0
- devel c:KDE:Frameworks5/breeze/25.0 p1:KDE:Frameworks5/breeze/24.0
- devel c:KDE:Frameworks5/breeze/24.0 p1:KDE:Frameworks5/breeze/22.0
- factory c:openSUSE:Factory/breeze/6.0 p1:openSUSE:Factory/breeze/5.0 p2:KDE:Frameworks5/breeze/22.0
- devel c:KDE:Frameworks5/breeze/22.0 p1:KDE:Frameworks5/breeze/21.0
- devel c:KDE:Frameworks5/breeze/21.0 p1:KDE:Frameworks5/breeze/20.0
- devel c:KDE:Frameworks5/breeze/20.0 p1:KDE:Frameworks5/breeze/19.0
- devel c:KDE:Frameworks5/breeze/19.0 p1:KDE:Frameworks5/breeze/18.0
- devel c:KDE:Frameworks5/breeze/18.0 p1:KDE:Frameworks5/breeze/17.0
- factory c:openSUSE:Factory/breeze/5.0 p1:openSUSE:Factory/breeze/4.0 p2:KDE:Frameworks5/breeze/17.0
- devel c:KDE:Frameworks5/breeze/17.0 p1:KDE:Frameworks5/breeze/16.0
- devel c:KDE:Frameworks5/breeze/16.0 p1:KDE:Frameworks5/breeze/15.0
- devel c:KDE:Frameworks5/breeze/15.0 p1:KDE:Frameworks5/breeze/14.0
- devel c:KDE:Frameworks5/breeze/14.0 p1:KDE:Frameworks5/breeze/13.0
- devel c:KDE:Frameworks5/breeze/13.0 p1:KDE:Frameworks5/breeze/12.0
- devel c:KDE:Frameworks5/breeze/12.0 p1:KDE:Frameworks5/breeze/11.0
- factory c:openSUSE:Factory/breeze/4.0 p1:openSUSE:Factory/breeze/2.0 p2:KDE:Frameworks5/breeze/11.0
- devel c:KDE:Frameworks5/breeze/11.0 p1:KDE:Frameworks5/breeze/10.0
- devel c:KDE:Frameworks5/breeze/10.0 p1:KDE:Frameworks5/breeze/9.0
- devel c:KDE:Frameworks5/breeze/9.0 p1:KDE:Frameworks5/breeze/8.0
- devel c:KDE:Frameworks5/breeze/8.0 p1:KDE:Frameworks5/breeze/6.0
- factory c:openSUSE:Factory/breeze/2.0 p1:openSUSE:Factory/breeze/1.0 p2:KDE:Frameworks5/breeze/6.0
- devel c:KDE:Frameworks5/breeze/6.0 p1:openSUSE:Factory/breeze/1.0
- factory c:openSUSE:Factory/breeze/1.0 p1:KDE:Frameworks5/breeze/4.0
- factory c:KDE:Frameworks5/breeze/4.0 p1:KDE:Frameworks5/breeze/3.0
- factory c:KDE:Frameworks5/breeze/3.0 p1:KDE:Frameworks5/breeze/2.0
- factory c:KDE:Frameworks5/breeze/2.0 p1:KDE:Frameworks5/breeze/1.0
- factory c:KDE:Frameworks5/breeze/1.0

212
tests/fixtures/breeze-expected-tree.yaml vendored Normal file
View File

@@ -0,0 +1,212 @@
- commit: openSUSE:Factory/breeze/43.0
merged:
- KDE:Frameworks5/breeze/150.0
- commit: openSUSE:Factory/breeze/42.0
merged:
- KDE:Frameworks5/breeze/148.0
- KDE:Frameworks5/breeze/147.0
- commit: openSUSE:Factory/breeze/41.0
merged:
- KDE:Frameworks5/breeze/145.0
- commit: openSUSE:Factory/breeze/40.0
merged:
- KDE:Frameworks5/breeze/143.0
- KDE:Frameworks5/breeze/142.0
- KDE:Frameworks5/breeze/141.0
- commit: openSUSE:Factory/breeze/39.0
merged:
- KDE:Frameworks5/breeze/139.0
- commit: openSUSE:Factory/breeze/38.0
merged:
- KDE:Frameworks5/breeze/137.0
- KDE:Frameworks5/breeze/136.0
- KDE:Frameworks5/breeze/135.0
- KDE:Frameworks5/breeze/134.0
- commit: openSUSE:Factory/breeze/37.0
merged:
- KDE:Frameworks5/breeze/132.0
- commit: openSUSE:Factory/breeze/36.0
merged:
- KDE:Frameworks5/breeze/130.0
- commit: openSUSE:Factory/breeze/35.0
merged:
- KDE:Frameworks5/breeze/128.0
- KDE:Frameworks5/breeze/127.0
- KDE:Frameworks5/breeze/126.034
- KDE:Frameworks5/breeze/126.0
- KDE:Frameworks5/breeze/125.0
- KDE:Frameworks5/breeze/124.0
- KDE:Frameworks5/breeze/123.0
- KDE:Frameworks5/breeze/122.0
- commit: openSUSE:Factory/breeze/34.0
merged:
- KDE:Frameworks5:LTS/breeze/14.0
- KDE:Frameworks5:LTS/breeze/13.0
- KDE:Frameworks5:LTS/breeze/12.0
- commit: openSUSE:Factory/breeze/33.0
merged:
- KDE:Frameworks5:LTS/breeze/11.0
- KDE:Frameworks5:LTS/breeze/10.0
- KDE:Frameworks5:LTS/breeze/9.0
- KDE:Frameworks5:LTS/breeze/8.0
- KDE:Frameworks5:LTS/breeze/7.0
- KDE:Frameworks5:LTS/breeze/6.0
- KDE:Frameworks5:LTS/breeze/5.0
- KDE:Frameworks5:LTS/breeze/4.0
- KDE:Frameworks5:LTS/breeze/3.0
- KDE:Frameworks5:LTS/breeze/2.0
- KDE:Frameworks5:LTS/breeze/1.0
- commit: openSUSE:Factory/breeze/32.0
merged:
- KDE:Frameworks5/breeze/120.0
- commit: openSUSE:Factory/breeze/31.0
merged:
- KDE:Frameworks5/breeze/117.0
- commit: openSUSE:Factory/breeze/30.0
merged:
- KDE:Frameworks5/breeze/116.0
- KDE:Frameworks5/breeze/115.0
- KDE:Frameworks5/breeze/113.0
- KDE:Frameworks5/breeze/112.0
- commit: openSUSE:Factory/breeze/29.0
merged:
- KDE:Frameworks5/breeze/111.0
- KDE:Frameworks5/breeze/110.0
- KDE:Frameworks5/breeze/109.0
- KDE:Frameworks5/breeze/108.0
- KDE:Frameworks5/breeze/107.0
- commit: openSUSE:Factory/breeze/28.0
merged:
- KDE:Frameworks5/breeze/105.0
- commit: openSUSE:Factory/breeze/27.0
merged:
- KDE:Frameworks5/breeze/103.0
- commit: openSUSE:Factory/breeze/26.0
merged:
- KDE:Frameworks5/breeze/100.0
- commit: openSUSE:Factory/breeze/25.0
merged:
- KDE:Frameworks5/breeze/99.0
- KDE:Frameworks5/breeze/98.0
- KDE:Frameworks5/breeze/97.0
- commit: openSUSE:Factory/breeze/24.0
merged:
- KDE:Frameworks5/breeze/95.0
- commit: openSUSE:Factory/breeze/23.0
merged:
- KDE:Frameworks5/breeze/93.0
- commit: openSUSE:Factory/breeze/22.0
merged:
- KDE:Frameworks5/breeze/91.0
- commit: openSUSE:Factory/breeze/21.0
merged:
- KDE:Frameworks5/breeze/88.0
- commit: openSUSE:Factory/breeze/20.0
merged:
- KDE:Frameworks5/breeze/87.0
- KDE:Frameworks5/breeze/86.0
- KDE:Frameworks5/breeze/85.0
- KDE:Frameworks5/breeze/84.0
- KDE:Frameworks5/breeze/83.0
- KDE:Frameworks5/breeze/82.0
- KDE:Frameworks5/breeze/81.0
- KDE:Frameworks5/breeze/80.0
- KDE:Frameworks5/breeze/79.0
- KDE:Frameworks5/breeze/78.0
- KDE:Frameworks5/breeze/76.0
- commit: openSUSE:Factory/breeze/19.0
merged:
- KDE:Frameworks5/breeze/75.0
- KDE:Frameworks5/breeze/74.0
- KDE:Frameworks5/breeze/73.0
- commit: openSUSE:Factory/breeze/18.0
merged:
- KDE:Frameworks5/breeze/71.0
- KDE:Frameworks5/breeze/70.0
- KDE:Frameworks5/breeze/69.0
- KDE:Frameworks5/breeze/68.0
- KDE:Frameworks5/breeze/67.0
- commit: openSUSE:Factory/breeze/17.0
merged:
- KDE:Frameworks5/breeze/65.0
- KDE:Frameworks5/breeze/64.0
- commit: openSUSE:Factory/breeze/16.0
merged:
- KDE:Frameworks5/breeze/62.0
- KDE:Frameworks5/breeze/61.0
- KDE:Frameworks5/breeze/60.0
- KDE:Frameworks5/breeze/59.0
- KDE:Frameworks5/breeze/58.0
- KDE:Frameworks5/breeze/57.0
- commit: openSUSE:Factory/breeze/15.0
merged:
- KDE:Frameworks5/breeze/55.0
- commit: openSUSE:Factory/breeze/14.0
merged:
- KDE:Frameworks5/breeze/53.0
- commit: openSUSE:Factory/breeze/13.0
merged:
- KDE:Frameworks5/breeze/51.0
- KDE:Frameworks5/breeze/50.0
- KDE:Frameworks5/breeze/49.0
- KDE:Frameworks5/breeze/48.0
- KDE:Frameworks5/breeze/47.0
- KDE:Frameworks5/breeze/46.0
- KDE:Frameworks5/breeze/45.0
- KDE:Frameworks5/breeze/44.0
- KDE:Frameworks5/breeze/43.0
- commit: openSUSE:Factory/breeze/12.0
merged:
- KDE:Frameworks5/breeze/41.0
- KDE:Frameworks5/breeze/40.0
- KDE:Frameworks5/breeze/39.0
- commit: openSUSE:Factory/breeze/11.0
merged:
- KDE:Frameworks5/breeze/38.0
- commit: openSUSE:Factory/breeze/10.0
merged:
- KDE:Frameworks5/breeze/36.0
- KDE:Frameworks5/breeze/35.0
- commit: openSUSE:Factory/breeze/9.0
merged:
- KDE:Frameworks5/breeze/33.0
- KDE:Frameworks5/breeze/32.0
- KDE:Frameworks5/breeze/31.0
- KDE:Frameworks5/breeze/30.0
- commit: openSUSE:Factory/breeze/8.0
merged:
- KDE:Frameworks5/breeze/28.0
- KDE:Frameworks5/breeze/27.0
- commit: openSUSE:Factory/breeze/7.0
merged:
- KDE:Frameworks5/breeze/25.0
- KDE:Frameworks5/breeze/24.0
- commit: openSUSE:Factory/breeze/6.0
merged:
- KDE:Frameworks5/breeze/22.0
- KDE:Frameworks5/breeze/21.0
- KDE:Frameworks5/breeze/20.0
- KDE:Frameworks5/breeze/19.0
- KDE:Frameworks5/breeze/18.0
- commit: openSUSE:Factory/breeze/5.0
merged:
- KDE:Frameworks5/breeze/17.0
- KDE:Frameworks5/breeze/16.0
- KDE:Frameworks5/breeze/15.0
- KDE:Frameworks5/breeze/14.0
- KDE:Frameworks5/breeze/13.0
- KDE:Frameworks5/breeze/12.0
- commit: openSUSE:Factory/breeze/4.0
merged:
- KDE:Frameworks5/breeze/11.0
- KDE:Frameworks5/breeze/10.0
- KDE:Frameworks5/breeze/9.0
- KDE:Frameworks5/breeze/8.0
- commit: openSUSE:Factory/breeze/2.0
merged:
- KDE:Frameworks5/breeze/6.0
- commit: openSUSE:Factory/breeze/1.0
- commit: KDE:Frameworks5/breeze/4.0
- commit: KDE:Frameworks5/breeze/3.0
- commit: KDE:Frameworks5/breeze/2.0
- commit: KDE:Frameworks5/breeze/1.0

9551
tests/fixtures/firewalld-data.yaml vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,173 @@
- factory c:openSUSE:Factory/firewalld/73.0 p1:openSUSE:Factory/firewalld/72.0 p2:security:netfilter/firewalld/131.0
- devel c:security:netfilter/firewalld/131.0 p1:security:netfilter/firewalld/129.0
- factory c:openSUSE:Factory/firewalld/72.0 p1:openSUSE:Factory/firewalld/71.0 p2:security:netfilter/firewalld/129.0
- devel c:security:netfilter/firewalld/129.0 p1:security:netfilter/firewalld/128.0
- devel c:security:netfilter/firewalld/128.0 p1:security:netfilter/firewalld/127.0
- factory c:openSUSE:Factory/firewalld/71.0 p1:openSUSE:Factory/firewalld/70.0 p2:security:netfilter/firewalld/127.0
- devel c:security:netfilter/firewalld/127.0 p1:security:netfilter/firewalld/126.0
- factory c:openSUSE:Factory/firewalld/70.0 p1:openSUSE:Factory/firewalld/69.0
- factory c:openSUSE:Factory/firewalld/69.0 p1:openSUSE:Factory/firewalld/68.0 p2:security:netfilter/firewalld/126.0
- devel c:security:netfilter/firewalld/126.0 p1:security:netfilter/firewalld/125.0
- factory c:openSUSE:Factory/firewalld/68.0 p1:openSUSE:Factory/firewalld/67.0 p2:security:netfilter/firewalld/125.0
- devel c:security:netfilter/firewalld/125.0 p1:security:netfilter/firewalld/124.0
- factory c:openSUSE:Factory/firewalld/67.0 p1:openSUSE:Factory/firewalld/66.0 p2:security:netfilter/firewalld/124.0
- devel c:security:netfilter/firewalld/124.0 p1:security:netfilter/firewalld/123.0
- factory c:openSUSE:Factory/firewalld/66.0 p1:openSUSE:Factory/firewalld/65.0 p2:security:netfilter/firewalld/123.0
- devel c:security:netfilter/firewalld/123.0 p1:security:netfilter/firewalld/122.0
- factory c:openSUSE:Factory/firewalld/65.0 p1:openSUSE:Factory/firewalld/64.0 p2:security:netfilter/firewalld/122.0
- devel c:security:netfilter/firewalld/122.0 p1:security:netfilter/firewalld/121.0
- factory c:openSUSE:Factory/firewalld/64.0 p1:openSUSE:Factory/firewalld/63.0 p2:security:netfilter/firewalld/121.0
- devel c:security:netfilter/firewalld/121.0 p1:security:netfilter/firewalld/120.0
- factory c:openSUSE:Factory/firewalld/63.0 p1:openSUSE:Factory/firewalld/62.0 p2:security:netfilter/firewalld/120.0
- devel c:security:netfilter/firewalld/120.0 p1:security:netfilter/firewalld/119.0
- factory c:openSUSE:Factory/firewalld/62.0 p1:openSUSE:Factory/firewalld/61.0 p2:security:netfilter/firewalld/119.0
- devel c:security:netfilter/firewalld/119.0 p1:security:netfilter/firewalld/118.0
- factory c:openSUSE:Factory/firewalld/61.0 p1:openSUSE:Factory/firewalld/60.0 p2:security:netfilter/firewalld/118.0
- devel c:security:netfilter/firewalld/118.0 p1:security:netfilter/firewalld/117.0
- factory c:openSUSE:Factory/firewalld/60.0 p1:openSUSE:Factory/firewalld/59.0 p2:security:netfilter/firewalld/117.0
- devel c:security:netfilter/firewalld/117.0 p1:security:netfilter/firewalld/116.0
- factory c:openSUSE:Factory/firewalld/59.0 p1:openSUSE:Factory/firewalld/58.0 p2:security:netfilter/firewalld/116.0
- devel c:security:netfilter/firewalld/116.0 p1:security:netfilter/firewalld/115.0
- factory c:openSUSE:Factory/firewalld/58.0 p1:openSUSE:Factory/firewalld/57.0 p2:security:netfilter/firewalld/115.0
- devel c:security:netfilter/firewalld/115.0 p1:security:netfilter/firewalld/114.0
- factory c:openSUSE:Factory/firewalld/57.0 p1:openSUSE:Factory/firewalld/56.0 p2:security:netfilter/firewalld/114.0
- devel c:security:netfilter/firewalld/114.0 p1:security:netfilter/firewalld/113.0
- factory c:openSUSE:Factory/firewalld/56.0 p1:openSUSE:Factory/firewalld/55.0 p2:security:netfilter/firewalld/113.0
- devel c:security:netfilter/firewalld/113.0 p1:security:netfilter/firewalld/112.0
- factory c:openSUSE:Factory/firewalld/55.0 p1:openSUSE:Factory/firewalld/54.0 p2:security:netfilter/firewalld/112.0
- devel c:security:netfilter/firewalld/112.0 p1:security:netfilter/firewalld/111.0
- devel c:security:netfilter/firewalld/111.0 p1:security:netfilter/firewalld/110.0
- devel c:security:netfilter/firewalld/110.0 p1:security:netfilter/firewalld/109.0
- devel c:security:netfilter/firewalld/109.0 p1:security:netfilter/firewalld/108.0
- factory c:openSUSE:Factory/firewalld/54.0 p1:openSUSE:Factory/firewalld/53.0 p2:security:netfilter/firewalld/108.0
- devel c:security:netfilter/firewalld/108.0 p1:security:netfilter/firewalld/107.0
- factory c:openSUSE:Factory/firewalld/53.0 p1:openSUSE:Factory/firewalld/52.0 p2:security:netfilter/firewalld/107.0
- devel c:security:netfilter/firewalld/107.0 p1:security:netfilter/firewalld/106.0
- factory c:openSUSE:Factory/firewalld/52.0 p1:openSUSE:Factory/firewalld/51.0
- factory c:openSUSE:Factory/firewalld/51.0 p1:openSUSE:Factory/firewalld/50.0
- factory c:openSUSE:Factory/firewalld/50.0 p1:openSUSE:Factory/firewalld/49.0 p2:security:netfilter/firewalld/106.0
- devel c:security:netfilter/firewalld/106.0 p1:security:netfilter/firewalld/105.0
- factory c:openSUSE:Factory/firewalld/49.0 p1:openSUSE:Factory/firewalld/48.0 p2:security:netfilter/firewalld/105.0
- devel c:security:netfilter/firewalld/105.0 p1:security:netfilter/firewalld/104.0
- devel c:security:netfilter/firewalld/104.0 p1:security:netfilter/firewalld/103.0
- devel c:security:netfilter/firewalld/103.0 p1:security:netfilter/firewalld/102.0
- factory c:openSUSE:Factory/firewalld/48.0 p1:openSUSE:Factory/firewalld/47.0 p2:security:netfilter/firewalld/102.0
- devel c:security:netfilter/firewalld/102.0 p1:security:netfilter/firewalld/101.0
- factory c:openSUSE:Factory/firewalld/47.0 p1:openSUSE:Factory/firewalld/46.0 p2:security:netfilter/firewalld/101.0
- devel c:security:netfilter/firewalld/101.0 p1:security:netfilter/firewalld/100.0
- factory c:openSUSE:Factory/firewalld/46.0 p1:openSUSE:Factory/firewalld/45.0 p2:security:netfilter/firewalld/100.0
- devel c:security:netfilter/firewalld/100.0 p1:security:netfilter/firewalld/99.0
- factory c:openSUSE:Factory/firewalld/45.0 p1:openSUSE:Factory/firewalld/44.0 p2:security:netfilter/firewalld/99.0
- devel c:security:netfilter/firewalld/99.0 p1:security:netfilter/firewalld/98.0
- factory c:openSUSE:Factory/firewalld/44.0 p1:openSUSE:Factory/firewalld/43.0 p2:security:netfilter/firewalld/98.0
- devel c:security:netfilter/firewalld/98.0 p1:security:netfilter/firewalld/97.0
- factory c:openSUSE:Factory/firewalld/43.0 p1:openSUSE:Factory/firewalld/42.0 p2:security:netfilter/firewalld/97.0
- devel c:security:netfilter/firewalld/97.0 p1:security:netfilter/firewalld/96.0
- devel c:security:netfilter/firewalld/96.0 p1:security:netfilter/firewalld/95.0
- devel c:security:netfilter/firewalld/95.0 p1:security:netfilter/firewalld/94.0
- devel c:security:netfilter/firewalld/94.0 p1:security:netfilter/firewalld/93.0
- factory c:openSUSE:Factory/firewalld/42.0 p1:openSUSE:Factory/firewalld/41.0 p2:security:netfilter/firewalld/93.0
- devel c:security:netfilter/firewalld/93.0 p1:security:netfilter/firewalld/92.0
- factory c:openSUSE:Factory/firewalld/41.0 p1:openSUSE:Factory/firewalld/40.0 p2:security:netfilter/firewalld/92.0
- devel c:security:netfilter/firewalld/92.0 p1:security:netfilter/firewalld/91.0
- factory c:openSUSE:Factory/firewalld/40.0 p1:openSUSE:Factory/firewalld/39.0 p2:security:netfilter/firewalld/91.0
- devel c:security:netfilter/firewalld/91.0 p1:security:netfilter/firewalld/90.0
- factory c:openSUSE:Factory/firewalld/39.0 p1:openSUSE:Factory/firewalld/38.0 p2:security:netfilter/firewalld/90.0
- devel c:security:netfilter/firewalld/90.0 p1:security:netfilter/firewalld/89.0
- factory c:openSUSE:Factory/firewalld/38.0 p1:openSUSE:Factory/firewalld/37.0 p2:security:netfilter/firewalld/89.0
- devel c:security:netfilter/firewalld/89.0 p1:security:netfilter/firewalld/88.0
- factory c:openSUSE:Factory/firewalld/37.0 p1:openSUSE:Factory/firewalld/36.0 p2:security:netfilter/firewalld/88.0
- devel c:security:netfilter/firewalld/88.0 p1:security:netfilter/firewalld/87.0
- devel c:security:netfilter/firewalld/87.0 p1:security:netfilter/firewalld/86.0
- devel c:security:netfilter/firewalld/86.0 p1:security:netfilter/firewalld/85.0
- devel c:security:netfilter/firewalld/85.0 p1:security:netfilter/firewalld/84.0
- factory c:openSUSE:Factory/firewalld/36.0 p1:openSUSE:Factory/firewalld/35.0 p2:security:netfilter/firewalld/84.0
- devel c:security:netfilter/firewalld/84.0 p1:security:netfilter/firewalld/83.0
- devel c:security:netfilter/firewalld/83.0 p1:security:netfilter/firewalld/82.0
- factory c:openSUSE:Factory/firewalld/35.0 p1:openSUSE:Factory/firewalld/34.0 p2:security:netfilter/firewalld/82.0
- devel c:security:netfilter/firewalld/82.0 p1:security:netfilter/firewalld/81.0
- devel c:security:netfilter/firewalld/81.0 p1:security:netfilter/firewalld/80.0
- devel c:security:netfilter/firewalld/80.0 p1:security:netfilter/firewalld/79.0
- devel c:security:netfilter/firewalld/79.0 p1:security:netfilter/firewalld/78.0
- devel c:security:netfilter/firewalld/78.0 p1:security:netfilter/firewalld/77.0
- factory c:openSUSE:Factory/firewalld/34.0 p1:openSUSE:Factory/firewalld/33.0 p2:security:netfilter/firewalld/77.0
- devel c:security:netfilter/firewalld/77.0 p1:security:netfilter/firewalld/76.0
- devel c:security:netfilter/firewalld/76.0 p1:security:netfilter/firewalld/75.0
- devel c:security:netfilter/firewalld/75.0 p1:security:netfilter/firewalld/74.0
- factory c:openSUSE:Factory/firewalld/33.0 p1:openSUSE:Factory/firewalld/32.0
- factory c:openSUSE:Factory/firewalld/32.0 p1:openSUSE:Factory/firewalld/31.0 p2:security:netfilter/firewalld/74.0
- devel c:security:netfilter/firewalld/74.0 p1:security:netfilter/firewalld/71.0
- factory c:openSUSE:Factory/firewalld/31.0 p1:openSUSE:Factory/firewalld/30.0
- factory c:openSUSE:Factory/firewalld/30.0 p1:openSUSE:Factory/firewalld/29.0 p2:security:netfilter/firewalld/71.0
- devel c:security:netfilter/firewalld/71.0 p1:security:netfilter/firewalld/69.0
- factory c:openSUSE:Factory/firewalld/29.0 p1:openSUSE:Factory/firewalld/28.0 p2:security:netfilter/firewalld/69.0
- devel c:security:netfilter/firewalld/69.0 p1:security:netfilter/firewalld/68.0
- factory c:openSUSE:Factory/firewalld/28.0 p1:openSUSE:Factory/firewalld/27.0 p2:security:netfilter/firewalld/68.0
- devel c:security:netfilter/firewalld/68.0 p1:security:netfilter/firewalld/67.0
- devel c:security:netfilter/firewalld/67.0 p1:security:netfilter/firewalld/65.0
- factory c:openSUSE:Factory/firewalld/27.0 p1:openSUSE:Factory/firewalld/26.0 p2:security:netfilter/firewalld/65.0
- devel c:security:netfilter/firewalld/65.0 p1:security:netfilter/firewalld/63.0
- factory c:openSUSE:Factory/firewalld/26.0 p1:openSUSE:Factory/firewalld/25.0 p2:security:netfilter/firewalld/63.0
- devel c:security:netfilter/firewalld/63.0 p1:security:netfilter/firewalld/61.0
- factory c:openSUSE:Factory/firewalld/25.0 p1:openSUSE:Factory/firewalld/24.0 p2:security:netfilter/firewalld/61.0
- devel c:security:netfilter/firewalld/61.0 p1:security:netfilter/firewalld/60.0
- devel c:security:netfilter/firewalld/60.0 p1:security:netfilter/firewalld/59.0
- devel c:security:netfilter/firewalld/59.0 p1:security:netfilter/firewalld/57.0
- factory c:openSUSE:Factory/firewalld/24.0 p1:openSUSE:Factory/firewalld/23.0 p2:security:netfilter/firewalld/57.0
- devel c:security:netfilter/firewalld/57.0 p1:security:netfilter/firewalld/55.0
- factory c:openSUSE:Factory/firewalld/23.0 p1:openSUSE:Factory/firewalld/22.0 p2:security:netfilter/firewalld/55.0
- devel c:security:netfilter/firewalld/55.0 p1:security:netfilter/firewalld/54.0
- devel c:security:netfilter/firewalld/54.0 p1:security:netfilter/firewalld/53.0
- devel c:security:netfilter/firewalld/53.0 p1:security:netfilter/firewalld/51.0
- factory c:openSUSE:Factory/firewalld/22.0 p1:openSUSE:Factory/firewalld/21.0 p2:security:netfilter/firewalld/51.0
- devel c:security:netfilter/firewalld/51.0 p1:security:netfilter/firewalld/50.0
- devel c:security:netfilter/firewalld/50.0 p1:security:netfilter/firewalld/48.0
- factory c:openSUSE:Factory/firewalld/21.0 p1:openSUSE:Factory/firewalld/20.0 p2:security:netfilter/firewalld/48.0
- devel c:security:netfilter/firewalld/48.0 p1:security:netfilter/firewalld/47.0
- devel c:security:netfilter/firewalld/47.0 p1:security:netfilter/firewalld/45.0
- factory c:openSUSE:Factory/firewalld/20.0 p1:openSUSE:Factory/firewalld/19.0 p2:security:netfilter/firewalld/45.0
- devel c:security:netfilter/firewalld/45.0 p1:security:netfilter/firewalld/43.0
- factory c:openSUSE:Factory/firewalld/19.0 p1:openSUSE:Factory/firewalld/18.0 p2:security:netfilter/firewalld/43.0
- devel c:security:netfilter/firewalld/43.0 p1:security:netfilter/firewalld/41.0
- factory c:openSUSE:Factory/firewalld/18.0 p1:openSUSE:Factory/firewalld/17.0 p2:security:netfilter/firewalld/41.0
- devel c:security:netfilter/firewalld/41.0 p1:security:netfilter/firewalld/39.0
- factory c:openSUSE:Factory/firewalld/17.0 p1:openSUSE:Factory/firewalld/16.0 p2:security:netfilter/firewalld/39.0
- devel c:security:netfilter/firewalld/39.0 p1:security:netfilter/firewalld/38.0
- devel c:security:netfilter/firewalld/38.0 p1:security:netfilter/firewalld/36.0
- factory c:openSUSE:Factory/firewalld/16.0 p1:openSUSE:Factory/firewalld/15.0 p2:security:netfilter/firewalld/36.0
- devel c:security:netfilter/firewalld/36.0 p1:security:netfilter/firewalld/34.0
- factory c:openSUSE:Factory/firewalld/15.0 p1:openSUSE:Factory/firewalld/14.0 p2:security:netfilter/firewalld/34.0
- devel c:security:netfilter/firewalld/34.0 p1:security:netfilter/firewalld/32.0
- factory c:openSUSE:Factory/firewalld/14.0 p1:openSUSE:Factory/firewalld/13.0 p2:security:netfilter/firewalld/32.0
- devel c:security:netfilter/firewalld/32.0 p1:security:netfilter/firewalld/30.0
- factory c:openSUSE:Factory/firewalld/13.0 p1:openSUSE:Factory/firewalld/12.0 p2:security:netfilter/firewalld/30.0
- devel c:security:netfilter/firewalld/30.0 p1:security:netfilter/firewalld/28.0
- factory c:openSUSE:Factory/firewalld/12.0 p1:openSUSE:Factory/firewalld/11.0 p2:security:netfilter/firewalld/28.0
- devel c:security:netfilter/firewalld/28.0 p1:security:netfilter/firewalld/26.0
- factory c:openSUSE:Factory/firewalld/11.0 p1:openSUSE:Factory/firewalld/10.0 p2:security:netfilter/firewalld/26.0
- devel c:security:netfilter/firewalld/26.0 p1:security:netfilter/firewalld/24.0
- factory c:openSUSE:Factory/firewalld/10.0 p1:openSUSE:Factory/firewalld/9.0 p2:security:netfilter/firewalld/24.0
- devel c:security:netfilter/firewalld/24.0 p1:security:netfilter/firewalld/22.0
- factory c:openSUSE:Factory/firewalld/9.0 p1:openSUSE:Factory/firewalld/8.0 p2:security:netfilter/firewalld/22.0
- devel c:security:netfilter/firewalld/22.0 p1:security:netfilter/firewalld/21.0
- devel c:security:netfilter/firewalld/21.0 p1:security:netfilter/firewalld/19.0
- factory c:openSUSE:Factory/firewalld/8.0 p1:openSUSE:Factory/firewalld/7.0 p2:security:netfilter/firewalld/19.0
- devel c:security:netfilter/firewalld/19.0 p1:security:netfilter/firewalld/17.0
- factory c:openSUSE:Factory/firewalld/7.0 p1:openSUSE:Factory/firewalld/6.0 p2:security:netfilter/firewalld/17.0
- devel c:security:netfilter/firewalld/17.0 p1:security:netfilter/firewalld/15.0
- factory c:openSUSE:Factory/firewalld/6.0 p1:openSUSE:Factory/firewalld/5.0 p2:security:netfilter/firewalld/15.0
- devel c:security:netfilter/firewalld/15.0 p1:security:netfilter/firewalld/13.0
- factory c:openSUSE:Factory/firewalld/5.0 p1:openSUSE:Factory/firewalld/4.0 p2:security:netfilter/firewalld/13.0
- devel c:security:netfilter/firewalld/13.0 p1:security:netfilter/firewalld/11.0
- factory c:openSUSE:Factory/firewalld/4.0 p1:openSUSE:Factory/firewalld/3.0 p2:security:netfilter/firewalld/11.0
- devel c:security:netfilter/firewalld/11.0 p1:security:netfilter/firewalld/9.0
- factory c:openSUSE:Factory/firewalld/3.0 p1:openSUSE:Factory/firewalld/2.0 p2:security:netfilter/firewalld/9.0
- devel c:security:netfilter/firewalld/9.0 p1:security:netfilter/firewalld/8.0
- devel c:security:netfilter/firewalld/8.0 p1:security:netfilter/firewalld/6.0
- factory c:openSUSE:Factory/firewalld/2.0 p1:openSUSE:Factory/firewalld/1.0 p2:security:netfilter/firewalld/6.0
- devel c:security:netfilter/firewalld/6.0 p1:security:netfilter/firewalld/5.0
- devel c:security:netfilter/firewalld/5.0 p1:security:netfilter/firewalld/4.0
- devel c:security:netfilter/firewalld/4.0 p1:openSUSE:Factory/firewalld/1.0
- factory c:openSUSE:Factory/firewalld/1.0 p1:security:netfilter/firewalld/2.0
- factory c:security:netfilter/firewalld/2.0 p1:security:netfilter/firewalld/1.0
- factory c:security:netfilter/firewalld/1.0

View File

@@ -0,0 +1,240 @@
- commit: openSUSE:Factory/firewalld/73.0
merged:
- security:netfilter/firewalld/131.0
- commit: openSUSE:Factory/firewalld/72.0
merged:
- security:netfilter/firewalld/129.0
- security:netfilter/firewalld/128.0
- commit: openSUSE:Factory/firewalld/71.0
merged:
- security:netfilter/firewalld/127.0
- commit: openSUSE:Factory/firewalld/70.0
- commit: openSUSE:Factory/firewalld/69.0
merged:
- security:netfilter/firewalld/126.0
- commit: openSUSE:Factory/firewalld/68.0
merged:
- security:netfilter/firewalld/125.0
- commit: openSUSE:Factory/firewalld/67.0
merged:
- security:netfilter/firewalld/124.0
- commit: openSUSE:Factory/firewalld/66.0
merged:
- security:netfilter/firewalld/123.0
- commit: openSUSE:Factory/firewalld/65.0
merged:
- security:netfilter/firewalld/122.0
- commit: openSUSE:Factory/firewalld/64.0
merged:
- security:netfilter/firewalld/121.0
- commit: openSUSE:Factory/firewalld/63.0
merged:
- security:netfilter/firewalld/120.0
- commit: openSUSE:Factory/firewalld/62.0
merged:
- security:netfilter/firewalld/119.0
- commit: openSUSE:Factory/firewalld/61.0
merged:
- security:netfilter/firewalld/118.0
- commit: openSUSE:Factory/firewalld/60.0
merged:
- security:netfilter/firewalld/117.0
- commit: openSUSE:Factory/firewalld/59.0
merged:
- security:netfilter/firewalld/116.0
- commit: openSUSE:Factory/firewalld/58.0
merged:
- security:netfilter/firewalld/115.0
- commit: openSUSE:Factory/firewalld/57.0
merged:
- security:netfilter/firewalld/114.0
- commit: openSUSE:Factory/firewalld/56.0
merged:
- security:netfilter/firewalld/113.0
- commit: openSUSE:Factory/firewalld/55.0
merged:
- security:netfilter/firewalld/112.0
- security:netfilter/firewalld/111.0
- security:netfilter/firewalld/110.0
- security:netfilter/firewalld/109.0
- commit: openSUSE:Factory/firewalld/54.0
merged:
- security:netfilter/firewalld/108.0
- commit: openSUSE:Factory/firewalld/53.0
merged:
- security:netfilter/firewalld/107.0
- commit: openSUSE:Factory/firewalld/52.0
- commit: openSUSE:Factory/firewalld/51.0
- commit: openSUSE:Factory/firewalld/50.0
merged:
- security:netfilter/firewalld/106.0
- commit: openSUSE:Factory/firewalld/49.0
merged:
- security:netfilter/firewalld/105.0
- security:netfilter/firewalld/104.0
- security:netfilter/firewalld/103.0
- commit: openSUSE:Factory/firewalld/48.0
merged:
- security:netfilter/firewalld/102.0
- commit: openSUSE:Factory/firewalld/47.0
merged:
- security:netfilter/firewalld/101.0
- commit: openSUSE:Factory/firewalld/46.0
merged:
- security:netfilter/firewalld/100.0
- commit: openSUSE:Factory/firewalld/45.0
merged:
- security:netfilter/firewalld/99.0
- commit: openSUSE:Factory/firewalld/44.0
merged:
- security:netfilter/firewalld/98.0
- commit: openSUSE:Factory/firewalld/43.0
merged:
- security:netfilter/firewalld/97.0
- security:netfilter/firewalld/96.0
- security:netfilter/firewalld/95.0
- security:netfilter/firewalld/94.0
- commit: openSUSE:Factory/firewalld/42.0
merged:
- security:netfilter/firewalld/93.0
- commit: openSUSE:Factory/firewalld/41.0
merged:
- security:netfilter/firewalld/92.0
- commit: openSUSE:Factory/firewalld/40.0
merged:
- security:netfilter/firewalld/91.0
- commit: openSUSE:Factory/firewalld/39.0
merged:
- security:netfilter/firewalld/90.0
- commit: openSUSE:Factory/firewalld/38.0
merged:
- security:netfilter/firewalld/89.0
- commit: openSUSE:Factory/firewalld/37.0
merged:
- security:netfilter/firewalld/88.0
- security:netfilter/firewalld/87.0
- security:netfilter/firewalld/86.0
- security:netfilter/firewalld/85.0
- commit: openSUSE:Factory/firewalld/36.0
merged:
- security:netfilter/firewalld/84.0
- security:netfilter/firewalld/83.0
- commit: openSUSE:Factory/firewalld/35.0
merged:
- security:netfilter/firewalld/82.0
- security:netfilter/firewalld/81.0
- security:netfilter/firewalld/80.0
- security:netfilter/firewalld/79.0
- security:netfilter/firewalld/78.0
- commit: openSUSE:Factory/firewalld/34.0
merged:
- security:netfilter/firewalld/77.0
- security:netfilter/firewalld/76.0
- security:netfilter/firewalld/75.0
- commit: openSUSE:Factory/firewalld/33.0
- commit: openSUSE:Factory/firewalld/32.0
merged:
- security:netfilter/firewalld/74.0
- commit: openSUSE:Factory/firewalld/31.0
- commit: openSUSE:Factory/firewalld/30.0
merged:
- security:netfilter/firewalld/71.0
- commit: openSUSE:Factory/firewalld/29.0
merged:
- security:netfilter/firewalld/69.0
- commit: openSUSE:Factory/firewalld/28.0
merged:
- security:netfilter/firewalld/68.0
- security:netfilter/firewalld/67.0
- commit: openSUSE:Factory/firewalld/27.0
merged:
- security:netfilter/firewalld/65.0
- commit: openSUSE:Factory/firewalld/26.0
merged:
- security:netfilter/firewalld/63.0
- commit: openSUSE:Factory/firewalld/25.0
merged:
- security:netfilter/firewalld/61.0
- security:netfilter/firewalld/60.0
- security:netfilter/firewalld/59.0
- commit: openSUSE:Factory/firewalld/24.0
merged:
- security:netfilter/firewalld/57.0
- commit: openSUSE:Factory/firewalld/23.0
merged:
- security:netfilter/firewalld/55.0
- security:netfilter/firewalld/54.0
- security:netfilter/firewalld/53.0
- commit: openSUSE:Factory/firewalld/22.0
merged:
- security:netfilter/firewalld/51.0
- security:netfilter/firewalld/50.0
- commit: openSUSE:Factory/firewalld/21.0
merged:
- security:netfilter/firewalld/48.0
- security:netfilter/firewalld/47.0
- commit: openSUSE:Factory/firewalld/20.0
merged:
- security:netfilter/firewalld/45.0
- commit: openSUSE:Factory/firewalld/19.0
merged:
- security:netfilter/firewalld/43.0
- commit: openSUSE:Factory/firewalld/18.0
merged:
- security:netfilter/firewalld/41.0
- commit: openSUSE:Factory/firewalld/17.0
merged:
- security:netfilter/firewalld/39.0
- security:netfilter/firewalld/38.0
- commit: openSUSE:Factory/firewalld/16.0
merged:
- security:netfilter/firewalld/36.0
- commit: openSUSE:Factory/firewalld/15.0
merged:
- security:netfilter/firewalld/34.0
- commit: openSUSE:Factory/firewalld/14.0
merged:
- security:netfilter/firewalld/32.0
- commit: openSUSE:Factory/firewalld/13.0
merged:
- security:netfilter/firewalld/30.0
- commit: openSUSE:Factory/firewalld/12.0
merged:
- security:netfilter/firewalld/28.0
- commit: openSUSE:Factory/firewalld/11.0
merged:
- security:netfilter/firewalld/26.0
- commit: openSUSE:Factory/firewalld/10.0
merged:
- security:netfilter/firewalld/24.0
- commit: openSUSE:Factory/firewalld/9.0
merged:
- security:netfilter/firewalld/22.0
- security:netfilter/firewalld/21.0
- commit: openSUSE:Factory/firewalld/8.0
merged:
- security:netfilter/firewalld/19.0
- commit: openSUSE:Factory/firewalld/7.0
merged:
- security:netfilter/firewalld/17.0
- commit: openSUSE:Factory/firewalld/6.0
merged:
- security:netfilter/firewalld/15.0
- commit: openSUSE:Factory/firewalld/5.0
merged:
- security:netfilter/firewalld/13.0
- commit: openSUSE:Factory/firewalld/4.0
merged:
- security:netfilter/firewalld/11.0
- commit: openSUSE:Factory/firewalld/3.0
merged:
- security:netfilter/firewalld/9.0
- security:netfilter/firewalld/8.0
- commit: openSUSE:Factory/firewalld/2.0
merged:
- security:netfilter/firewalld/6.0
- security:netfilter/firewalld/5.0
- security:netfilter/firewalld/4.0
- commit: openSUSE:Factory/firewalld/1.0
- commit: security:netfilter/firewalld/2.0
- commit: security:netfilter/firewalld/1.0

View File

@@ -65,6 +65,15 @@ class TestTreeMethods(unittest.TestCase):
def test_000update_repos_tree(self):
self.verify_package("000update-repos")
def test_breeze_tree(self):
self.verify_package("breeze")
def test_firewalld_tree(self):
self.verify_package("firewalld")
def test_FastCGI_tree(self):
self.verify_package("FastCGI")
if __name__ == "__main__":
unittest.main()