From d273ee5eb20ecb6e97d5d6cd8a1f493e3652b584 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Tue, 17 May 2016 19:07:41 +0200 Subject: Use the official provider list to detect duplicates Instead of automatically adding packages from the official binary repositories to the package blacklist, use the official provider list to prevent users from uploading duplicates. This does not only result in reduced disk usage but also has a nice visible side effect. The error messages printed by the update hook now look like error: package already provided by [community]: powerline-fonts instead of error: package is blacklisted: powerline-fonts which was confusing to most end users. Signed-off-by: Lukas Fleischer --- git-interface/git-update.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'git-interface') diff --git a/git-interface/git-update.py b/git-interface/git-update.py index e54e0e6..2c24e72 100755 --- a/git-interface/git-update.py +++ b/git-interface/git-update.py @@ -331,12 +331,18 @@ pkgbase_id = cur.fetchone()[0] if cur.rowcount == 1 else 0 cur.execute("SELECT Name FROM PackageBlacklist") blacklist = [row[0] for row in cur.fetchall()] +cur.execute("SELECT Name, Repo FROM OfficialProviders") +providers = dict(cur.fetchall()) + for pkgname in srcinfo.utils.get_package_names(metadata): pkginfo = srcinfo.utils.get_merged_package(pkgname, metadata) pkgname = pkginfo['pkgname'] if pkgname in blacklist and not privileged: die('package is blacklisted: {:s}'.format(pkgname)) + if pkgname in providers and not privileged: + repo = providers[pkgname] + die('package already provided by [{:s}]: {:s}'.format(repo, pkgname)) cur.execute("SELECT COUNT(*) FROM Packages WHERE Name = %s AND " + "PackageBaseID <> %s", [pkgname, pkgbase_id]) -- cgit v1.2.3-54-g00ecf From 573715afd9f7e56e34be07f983055f938351d990 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Mon, 1 Aug 2016 19:48:02 +0200 Subject: git-serve: Refactor environment variable access Read all environment variables at the beginning of the script and immediately pre-process their values. Signed-off-by: Lukas Fleischer --- git-interface/git-serve.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'git-interface') diff --git a/git-interface/git-serve.py b/git-interface/git-serve.py index 35c6b3a..aa5f1c9 100755 --- a/git-interface/git-serve.py +++ b/git-interface/git-serve.py @@ -108,15 +108,12 @@ def pkgbase_set_keywords(pkgbase, keywords): db.close() -def check_permissions(pkgbase, user): +def pkgbase_has_write_access(pkgbase, user): db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, passwd=aur_db_pass, db=aur_db_name, unix_socket=aur_db_socket, buffered=True) cur = db.cursor() - if os.environ.get('AUR_PRIVILEGED', '0') == '1': - return True - cur.execute("SELECT COUNT(*) FROM PackageBases " + "LEFT JOIN PackageComaintainers " + "ON PackageComaintainers.PackageBaseID = PackageBases.ID " + @@ -136,15 +133,18 @@ def die_with_help(msg): die(msg + "\nTry `{:s} help` for a list of commands.".format(ssh_cmdline)) -user = os.environ.get("AUR_USER") -cmd = os.environ.get("SSH_ORIGINAL_COMMAND") -if not cmd: +user = os.environ.get('AUR_USER') +privileged = (os.environ.get('AUR_PRIVILEGED', '0') == '1') +ssh_cmd = os.environ.get('SSH_ORIGINAL_COMMAND') +ssh_client = os.environ.get('SSH_CLIENT') + +if not ssh_cmd: die_with_help("Interactive shell is disabled.") -cmdargv = shlex.split(cmd) +cmdargv = shlex.split(ssh_cmd) action = cmdargv[0] +remote_addr = ssh_client.split(' ')[0] if ssh_client else None if enable_maintenance: - remote_addr = os.environ["SSH_CLIENT"].split(" ")[0] if remote_addr not in maintenance_exc: die("The AUR is down due to maintenance. We will be back soon.") @@ -165,7 +165,7 @@ if action == 'git-upload-pack' or action == 'git-receive-pack': create_pkgbase(pkgbase, user) if action == 'git-receive-pack': - if not check_permissions(pkgbase, user): + if not privileged and not pkgbase_has_write_access(pkgbase, user): die('{:s}: permission denied: {:s}'.format(action, user)) os.environ["AUR_USER"] = user -- cgit v1.2.3-54-g00ecf From 415a2c836df9094ddac555d5ed967ac11e48907e Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Mon, 1 Aug 2016 20:14:25 +0200 Subject: git-update: Notify privileged users of forced uploads Show a warning when a Trusted User or a developer creates a package that is blacklisted or already provided by an official package. Signed-off-by: Lukas Fleischer --- git-interface/git-update.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'git-interface') diff --git a/git-interface/git-update.py b/git-interface/git-update.py index 2c24e72..41b38a6 100755 --- a/git-interface/git-update.py +++ b/git-interface/git-update.py @@ -202,6 +202,7 @@ repo = pygit2.Repository(repo_path) user = os.environ.get("AUR_USER") pkgbase = os.environ.get("AUR_PKGBASE") privileged = (os.environ.get("AUR_PRIVILEGED", '0') == '1') +warn_or_die = warn if privileged else die if len(sys.argv) == 2 and sys.argv[1] == "restore": if 'refs/heads/' + pkgbase not in repo.listall_references(): @@ -338,11 +339,11 @@ for pkgname in srcinfo.utils.get_package_names(metadata): pkginfo = srcinfo.utils.get_merged_package(pkgname, metadata) pkgname = pkginfo['pkgname'] - if pkgname in blacklist and not privileged: - die('package is blacklisted: {:s}'.format(pkgname)) - if pkgname in providers and not privileged: + if pkgname in blacklist: + warn_or_die('package is blacklisted: {:s}'.format(pkgname)) + if pkgname in providers: repo = providers[pkgname] - die('package already provided by [{:s}]: {:s}'.format(repo, pkgname)) + warn_or_die('package already provided by [{:s}]: {:s}'.format(repo, pkgname)) cur.execute("SELECT COUNT(*) FROM Packages WHERE Name = %s AND " + "PackageBaseID <> %s", [pkgname, pkgbase_id]) -- cgit v1.2.3-54-g00ecf From 0c1187caa46ba2b5d159a88c9301356b56ef0637 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Sun, 24 Jul 2016 19:22:18 +0200 Subject: git-serve: Deprecate setup-repo Since 02dd9c5 (git-serve.py: Automatically create repositories, 2015-01-06), one can create new package bases by running `git push`. It is no longer necessary to run setup-repo manually. Signed-off-by: Lukas Fleischer --- git-interface/git-serve.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'git-interface') diff --git a/git-interface/git-serve.py b/git-interface/git-serve.py index aa5f1c9..e0ebd0e 100755 --- a/git-interface/git-serve.py +++ b/git-interface/git-serve.py @@ -133,6 +133,10 @@ def die_with_help(msg): die(msg + "\nTry `{:s} help` for a list of commands.".format(ssh_cmdline)) +def warn(msg): + sys.stderr.write("warning: {:s}\n".format(msg)) + + user = os.environ.get('AUR_USER') privileged = (os.environ.get('AUR_PRIVILEGED', '0') == '1') ssh_cmd = os.environ.get('SSH_ORIGINAL_COMMAND') @@ -186,6 +190,7 @@ elif action == 'setup-repo': die_with_help("{:s}: missing repository name".format(action)) if len(cmdargv) > 2: die_with_help("{:s}: too many arguments".format(action)) + warn('{:s} is deprecated. Use `git push` to create new repositories.'.format(action)) create_pkgbase(cmdargv[1], user) elif action == 'restore': if len(cmdargv) < 2: -- cgit v1.2.3-54-g00ecf From 7a53ded5fea71cc89d0530d5c087e452e89c9d3f Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Tue, 2 Aug 2016 00:47:17 +0200 Subject: git-update: Fix some issues reported by pyflakes Signed-off-by: Lukas Fleischer --- git-interface/git-update.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'git-interface') diff --git a/git-interface/git-update.py b/git-interface/git-update.py index 41b38a6..5fc5562 100755 --- a/git-interface/git-update.py +++ b/git-interface/git-update.py @@ -167,11 +167,13 @@ def save_metadata(metadata, db, cur, user): "PackageBaseID = %s AND UserID = %s", [pkgbase_id, user_id]) if cur.fetchone()[0] == 0: - cur.execute("INSERT INTO PackageNotifications (PackageBaseID, UserID) " + - "VALUES (%s, %s)", [pkgbase_id, user_id]) + cur.execute("INSERT INTO PackageNotifications " + + "(PackageBaseID, UserID) VALUES (%s, %s)", + [pkgbase_id, user_id]) db.commit() + def update_notify(db, cur, user, pkgbase_id): # Obtain the user ID of the new maintainer. cur.execute("SELECT ID FROM Users WHERE Username = %s", [user]) @@ -180,6 +182,7 @@ def update_notify(db, cur, user, pkgbase_id): # Execute the notification script. subprocess.Popen((notify_cmd, 'update', str(user_id), str(pkgbase_id))) + def die(msg): sys.stderr.write("error: {:s}\n".format(msg)) exit(1) -- cgit v1.2.3-54-g00ecf From 87f5f1b407fee29412c761e6f1484f51fae86eda Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Tue, 2 Aug 2016 23:21:06 +0200 Subject: git-update: Use AUR_PRIVILEGED for forced pushes Instead of looking up the account type of the current user again, use the AUR_PRIVILEGED environment variable to check whether the user is allowed to perform non-fast-forward ref updates. Signed-off-by: Lukas Fleischer --- git-interface/git-update.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'git-interface') diff --git a/git-interface/git-update.py b/git-interface/git-update.py index 5fc5562..68f7387 100755 --- a/git-interface/git-update.py +++ b/git-interface/git-update.py @@ -226,14 +226,11 @@ db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, cur = db.cursor() # Detect and deny non-fast-forwards. -if sha1_old != "0000000000000000000000000000000000000000": +if sha1_old != "0000000000000000000000000000000000000000" and not privileged: walker = repo.walk(sha1_old, pygit2.GIT_SORT_TOPOLOGICAL) walker.hide(sha1_new) if next(walker, None) is not None: - cur.execute("SELECT AccountTypeID FROM Users WHERE UserName = %s ", - [user]) - if cur.fetchone()[0] == 1: - die("denying non-fast-forward (you should pull first)") + die("denying non-fast-forward (you should pull first)") # Prepare the walker that validates new commits. walker = repo.walk(sha1_new, pygit2.GIT_SORT_TOPOLOGICAL) -- cgit v1.2.3-54-g00ecf From 2cd69bf66d244c7d438992aff1a03ea52cdba819 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Wed, 3 Aug 2016 01:59:37 +0200 Subject: git-update: Make maximum blob size configurable Support setting the maximum blob size in the configuration file. Signed-off-by: Lukas Fleischer --- conf/config.proto | 3 +++ git-interface/git-update.py | 17 +++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) (limited to 'git-interface') diff --git a/conf/config.proto b/conf/config.proto index d5778a0..543c3ca 100644 --- a/conf/config.proto +++ b/conf/config.proto @@ -56,6 +56,9 @@ git-shell-cmd = /usr/bin/git-shell git-update-cmd = /srv/http/aurweb/git-interface/git-update.py ssh-cmdline = ssh aur@aur.archlinux.org +[update] +max-blob-size = 256000 + [aurblup] db-path = /srv/http/aurweb/aurblup/ sync-dbs = core extra community multilib testing community-testing diff --git a/git-interface/git-update.py b/git-interface/git-update.py index 68f7387..7aace9b 100755 --- a/git-interface/git-update.py +++ b/git-interface/git-update.py @@ -25,6 +25,19 @@ notify_cmd = config.get('notifications', 'notify-cmd') repo_path = config.get('serve', 'repo-path') repo_regex = config.get('serve', 'repo-regex') +max_blob_size = config.getint('update', 'max-blob-size') + + +def size_humanize(num): + for unit in ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB']: + if abs(num) < 2048.0: + if isinstance(num, int): + return "{}{}".format(num, unit) + else: + return "{:.2f}{}".format(num, unit) + num /= 1024.0 + return "{:.2f}{}".format(num, 'YiB') + def extract_arch_fields(pkginfo, field): values = [] @@ -254,8 +267,8 @@ for commit in walker: die_commit("not a blob object: {:s}".format(treeobj), str(commit.id)) - if blob.size > 250000: - die_commit("maximum blob size (250kB) exceeded", str(commit.id)) + if blob.size > max_blob_size: + die_commit("maximum blob size ({:s}) exceeded".format(size_humanize(max_blob_size)), str(commit.id)) metadata_raw = repo[commit.tree['.SRCINFO'].id].data.decode() (metadata, errors) = srcinfo.parse.parse_srcinfo(metadata_raw) -- cgit v1.2.3-54-g00ecf From 2915abb9d35308150ec107c5f4664e116daaf1de Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Wed, 3 Aug 2016 02:20:40 +0200 Subject: git-interface: Add database abstraction layer Add a new class that connects to the database specified in the configuration file and provides an interface to execute SQL queries. Prepared statements with qmark ("?") placeholders are supported. Replace all direct database accesses with calls to the new abstraction layer. Signed-off-by: Lukas Fleischer --- git-interface/db.py | 38 ++++++++++++ git-interface/git-auth.py | 22 +++---- git-interface/git-serve.py | 92 ++++++++++++---------------- git-interface/git-update.py | 144 +++++++++++++++++++++----------------------- 4 files changed, 150 insertions(+), 146 deletions(-) create mode 100644 git-interface/db.py (limited to 'git-interface') diff --git a/git-interface/db.py b/git-interface/db.py new file mode 100644 index 0000000..d3e1e69 --- /dev/null +++ b/git-interface/db.py @@ -0,0 +1,38 @@ +import configparser +import mysql.connector +import os + + +class Connection: + _conn = None + + def __init__(self): + config = configparser.RawConfigParser() + config.read(os.path.dirname(os.path.realpath(__file__)) + "/../conf/config") + + aur_db_host = config.get('database', 'host') + aur_db_name = config.get('database', 'name') + aur_db_user = config.get('database', 'user') + aur_db_pass = config.get('database', 'password') + aur_db_socket = config.get('database', 'socket') + + self._conn = mysql.connector.connect(host=aur_db_host, + user=aur_db_user, + passwd=aur_db_pass, + db=aur_db_name, + unix_socket=aur_db_socket, + buffered=True) + + def execute(self, query, params=()): + query = query.replace('%', '%%').replace('?', '%s') + + cur = self._conn.cursor() + cur.execute(query, params) + + return cur + + def commit(self): + self._conn.commit() + + def close(self): + self._conn.close() diff --git a/git-interface/git-auth.py b/git-interface/git-auth.py index 83bd20c..7cd033c 100755 --- a/git-interface/git-auth.py +++ b/git-interface/git-auth.py @@ -1,12 +1,13 @@ #!/usr/bin/python3 import configparser -import mysql.connector import shlex import os import re import sys +import db + def format_command(env_vars, command, ssh_opts, ssh_key): environment = '' @@ -26,12 +27,6 @@ def format_command(env_vars, command, ssh_opts, ssh_key): config = configparser.RawConfigParser() config.read(os.path.dirname(os.path.realpath(__file__)) + "/../conf/config") -aur_db_host = config.get('database', 'host') -aur_db_name = config.get('database', 'name') -aur_db_user = config.get('database', 'user') -aur_db_pass = config.get('database', 'password') -aur_db_socket = config.get('database', 'socket') - valid_keytypes = config.get('auth', 'valid-keytypes').split() username_regex = config.get('auth', 'username-regex') git_serve_cmd = config.get('auth', 'git-serve-cmd') @@ -42,15 +37,12 @@ keytext = sys.argv[2] if keytype not in valid_keytypes: exit(1) -db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, - passwd=aur_db_pass, db=aur_db_name, - unix_socket=aur_db_socket, buffered=True) +conn = db.Connection() -cur = db.cursor() -cur.execute("SELECT Users.Username, Users.AccountTypeID FROM Users " + - "INNER JOIN SSHPubKeys ON SSHPubKeys.UserID = Users.ID " - "WHERE SSHPubKeys.PubKey = %s AND Users.Suspended = 0", - (keytype + " " + keytext,)) +cur = conn.execute("SELECT Users.Username, Users.AccountTypeID FROM Users " + + "INNER JOIN SSHPubKeys ON SSHPubKeys.UserID = Users.ID " + "WHERE SSHPubKeys.PubKey = ? AND Users.Suspended = 0", + (keytype + " " + keytext,)) if cur.rowcount != 1: exit(1) diff --git a/git-interface/git-serve.py b/git-interface/git-serve.py index e0ebd0e..ab612f0 100755 --- a/git-interface/git-serve.py +++ b/git-interface/git-serve.py @@ -1,21 +1,16 @@ #!/usr/bin/python3 import configparser -import mysql.connector import os import re import shlex import sys +import db + config = configparser.RawConfigParser() config.read(os.path.dirname(os.path.realpath(__file__)) + "/../conf/config") -aur_db_host = config.get('database', 'host') -aur_db_name = config.get('database', 'name') -aur_db_user = config.get('database', 'user') -aur_db_pass = config.get('database', 'password') -aur_db_socket = config.get('database', 'socket') - repo_path = config.get('serve', 'repo-path') repo_regex = config.get('serve', 'repo-regex') git_shell_cmd = config.get('serve', 'git-shell-cmd') @@ -27,12 +22,8 @@ maintenance_exc = config.get('options', 'maintenance-exceptions').split() def pkgbase_from_name(pkgbase): - db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, - passwd=aur_db_pass, db=aur_db_name, - unix_socket=aur_db_socket) - cur = db.cursor() - cur.execute("SELECT ID FROM PackageBases WHERE Name = %s", [pkgbase]) - db.close() + conn = db.Connection() + cur = conn.execute("SELECT ID FROM PackageBases WHERE Name = ?", [pkgbase]) row = cur.fetchone() return row[0] if row else None @@ -43,21 +34,18 @@ def pkgbase_exists(pkgbase): def list_repos(user): - db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, - passwd=aur_db_pass, db=aur_db_name, - unix_socket=aur_db_socket) - cur = db.cursor() + conn = db.Connection() - cur.execute("SELECT ID FROM Users WHERE Username = %s ", [user]) + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) userid = cur.fetchone()[0] if userid == 0: die('{:s}: unknown user: {:s}'.format(action, user)) - cur.execute("SELECT Name, PackagerUID FROM PackageBases " + - "WHERE MaintainerUID = %s ", [userid]) + cur = conn.execute("SELECT Name, PackagerUID FROM PackageBases " + + "WHERE MaintainerUID = ?", [userid]) for row in cur: print((' ' if row[1] else '*') + row[0]) - db.close() + conn.close() def create_pkgbase(pkgbase, user): @@ -66,26 +54,25 @@ def create_pkgbase(pkgbase, user): if pkgbase_exists(pkgbase): die('{:s}: package base already exists: {:s}'.format(action, pkgbase)) - db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, - passwd=aur_db_pass, db=aur_db_name, - unix_socket=aur_db_socket) - cur = db.cursor() + conn = db.Connection() - cur.execute("SELECT ID FROM Users WHERE Username = %s ", [user]) + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) userid = cur.fetchone()[0] if userid == 0: die('{:s}: unknown user: {:s}'.format(action, user)) - cur.execute("INSERT INTO PackageBases (Name, SubmittedTS, ModifiedTS, " + - "SubmitterUID, MaintainerUID) VALUES (%s, UNIX_TIMESTAMP(), " + - "UNIX_TIMESTAMP(), %s, %s)", [pkgbase, userid, userid]) + cur = conn.execute("INSERT INTO PackageBases (Name, SubmittedTS, " + + "ModifiedTS, SubmitterUID, MaintainerUID) VALUES " + + "(?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), ?, ?)", + [pkgbase, userid, userid]) pkgbase_id = cur.lastrowid - cur.execute("INSERT INTO PackageNotifications (PackageBaseID, UserID) " + - "VALUES (%s, %s)", [pkgbase_id, userid]) + cur = conn.execute("INSERT INTO PackageNotifications " + + "(PackageBaseID, UserID) VALUES (?, ?)", + [pkgbase_id, userid]) - db.commit() - db.close() + conn.commit() + conn.close() def pkgbase_set_keywords(pkgbase, keywords): @@ -93,34 +80,29 @@ def pkgbase_set_keywords(pkgbase, keywords): if not pkgbase_id: die('{:s}: package base not found: {:s}'.format(action, pkgbase)) - db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, - passwd=aur_db_pass, db=aur_db_name, - unix_socket=aur_db_socket) - cur = db.cursor() + conn = db.Connection() - cur.execute("DELETE FROM PackageKeywords WHERE PackageBaseID = %s", - [pkgbase_id]) + conn.execute("DELETE FROM PackageKeywords WHERE PackageBaseID = ?", + [pkgbase_id]) for keyword in keywords: - cur.execute("INSERT INTO PackageKeywords (PackageBaseID, Keyword) " - "VALUES (%s, %s)", [pkgbase_id, keyword]) + conn.execute("INSERT INTO PackageKeywords (PackageBaseID, Keyword) " + + "VALUES (?, ?)", [pkgbase_id, keyword]) - db.commit() - db.close() + conn.commit() + conn.close() def pkgbase_has_write_access(pkgbase, user): - db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, - passwd=aur_db_pass, db=aur_db_name, - unix_socket=aur_db_socket, buffered=True) - cur = db.cursor() - - cur.execute("SELECT COUNT(*) FROM PackageBases " + - "LEFT JOIN PackageComaintainers " + - "ON PackageComaintainers.PackageBaseID = PackageBases.ID " + - "INNER JOIN Users ON Users.ID = PackageBases.MaintainerUID " + - "OR PackageBases.MaintainerUID IS NULL " + - "OR Users.ID = PackageComaintainers.UsersID " + - "WHERE Name = %s AND Username = %s", [pkgbase, user]) + conn = db.Connection() + + cur = conn.execute("SELECT COUNT(*) FROM PackageBases " + + "LEFT JOIN PackageComaintainers " + + "ON PackageComaintainers.PackageBaseID = PackageBases.ID " + + "INNER JOIN Users " + + "ON Users.ID = PackageBases.MaintainerUID " + + "OR PackageBases.MaintainerUID IS NULL " + + "OR Users.ID = PackageComaintainers.UsersID " + + "WHERE Name = ? AND Username = ?", [pkgbase, user]) return cur.fetchone()[0] > 0 diff --git a/git-interface/git-update.py b/git-interface/git-update.py index 7aace9b..b7199e6 100755 --- a/git-interface/git-update.py +++ b/git-interface/git-update.py @@ -1,7 +1,6 @@ #!/usr/bin/python3 import configparser -import mysql.connector import os import pygit2 import re @@ -11,15 +10,11 @@ import sys import srcinfo.parse import srcinfo.utils +import db + config = configparser.RawConfigParser() config.read(os.path.dirname(os.path.realpath(__file__)) + "/../conf/config") -aur_db_host = config.get('database', 'host') -aur_db_name = config.get('database', 'name') -aur_db_user = config.get('database', 'user') -aur_db_pass = config.get('database', 'password') -aur_db_socket = config.get('database', 'socket') - notify_cmd = config.get('notifications', 'notify-cmd') repo_path = config.get('serve', 'repo-path') @@ -65,27 +60,27 @@ def parse_dep(depstring): return (depname, depcond) -def save_metadata(metadata, db, cur, user): +def save_metadata(metadata, conn, user): # Obtain package base ID and previous maintainer. pkgbase = metadata['pkgbase'] - cur.execute("SELECT ID, MaintainerUID FROM PackageBases " - "WHERE Name = %s", [pkgbase]) + cur = conn.execute("SELECT ID, MaintainerUID FROM PackageBases " + "WHERE Name = ?", [pkgbase]) (pkgbase_id, maintainer_uid) = cur.fetchone() was_orphan = not maintainer_uid # Obtain the user ID of the new maintainer. - cur.execute("SELECT ID FROM Users WHERE Username = %s", [user]) + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) user_id = int(cur.fetchone()[0]) # Update package base details and delete current packages. - cur.execute("UPDATE PackageBases SET ModifiedTS = UNIX_TIMESTAMP(), " + - "PackagerUID = %s, OutOfDateTS = NULL WHERE ID = %s", - [user_id, pkgbase_id]) - cur.execute("UPDATE PackageBases SET MaintainerUID = %s " + - "WHERE ID = %s AND MaintainerUID IS NULL", - [user_id, pkgbase_id]) - cur.execute("DELETE FROM Packages WHERE PackageBaseID = %s", - [pkgbase_id]) + conn.execute("UPDATE PackageBases SET ModifiedTS = UNIX_TIMESTAMP(), " + + "PackagerUID = ?, OutOfDateTS = NULL WHERE ID = ?", + [user_id, pkgbase_id]) + conn.execute("UPDATE PackageBases SET MaintainerUID = ? " + + "WHERE ID = ? AND MaintainerUID IS NULL", + [user_id, pkgbase_id]) + conn.execute("DELETE FROM Packages WHERE PackageBaseID = ?", + [pkgbase_id]) for pkgname in srcinfo.utils.get_package_names(metadata): pkginfo = srcinfo.utils.get_merged_package(pkgname, metadata) @@ -102,94 +97,94 @@ def save_metadata(metadata, db, cur, user): pkginfo[field] = None # Create a new package. - cur.execute("INSERT INTO Packages (PackageBaseID, Name, " + - "Version, Description, URL) " + - "VALUES (%s, %s, %s, %s, %s)", - [pkgbase_id, pkginfo['pkgname'], ver, - pkginfo['pkgdesc'], pkginfo['url']]) - db.commit() + cur = conn.execute("INSERT INTO Packages (PackageBaseID, Name, " + + "Version, Description, URL) " + + "VALUES (?, ?, ?, ?, ?)", + [pkgbase_id, pkginfo['pkgname'], ver, + pkginfo['pkgdesc'], pkginfo['url']]) + conn.commit() pkgid = cur.lastrowid # Add package sources. for source_info in extract_arch_fields(pkginfo, 'source'): - cur.execute("INSERT INTO PackageSources (PackageID, Source, " + - "SourceArch) VALUES (%s, %s, %s)", - [pkgid, source_info['value'], source_info['arch']]) + conn.execute("INSERT INTO PackageSources (PackageID, Source, " + + "SourceArch) VALUES (?, ?, ?)", + [pkgid, source_info['value'], source_info['arch']]) # Add package dependencies. for deptype in ('depends', 'makedepends', 'checkdepends', 'optdepends'): - cur.execute("SELECT ID FROM DependencyTypes WHERE Name = %s", - [deptype]) + cur = conn.execute("SELECT ID FROM DependencyTypes WHERE Name = ?", + [deptype]) deptypeid = cur.fetchone()[0] for dep_info in extract_arch_fields(pkginfo, deptype): depname, depcond = parse_dep(dep_info['value']) deparch = dep_info['arch'] - cur.execute("INSERT INTO PackageDepends (PackageID, " + - "DepTypeID, DepName, DepCondition, DepArch) " + - "VALUES (%s, %s, %s, %s, %s)", - [pkgid, deptypeid, depname, depcond, deparch]) + conn.execute("INSERT INTO PackageDepends (PackageID, " + + "DepTypeID, DepName, DepCondition, DepArch) " + + "VALUES (?, ?, ?, ?, ?)", + [pkgid, deptypeid, depname, depcond, deparch]) # Add package relations (conflicts, provides, replaces). for reltype in ('conflicts', 'provides', 'replaces'): - cur.execute("SELECT ID FROM RelationTypes WHERE Name = %s", - [reltype]) + cur = conn.execute("SELECT ID FROM RelationTypes WHERE Name = ?", + [reltype]) reltypeid = cur.fetchone()[0] for rel_info in extract_arch_fields(pkginfo, reltype): relname, relcond = parse_dep(rel_info['value']) relarch = rel_info['arch'] - cur.execute("INSERT INTO PackageRelations (PackageID, " + - "RelTypeID, RelName, RelCondition, RelArch) " + - "VALUES (%s, %s, %s, %s, %s)", - [pkgid, reltypeid, relname, relcond, relarch]) + conn.execute("INSERT INTO PackageRelations (PackageID, " + + "RelTypeID, RelName, RelCondition, RelArch) " + + "VALUES (?, ?, ?, ?, ?)", + [pkgid, reltypeid, relname, relcond, relarch]) # Add package licenses. if 'license' in pkginfo: for license in pkginfo['license']: - cur.execute("SELECT ID FROM Licenses WHERE Name = %s", - [license]) + cur = conn.execute("SELECT ID FROM Licenses WHERE Name = ?", + [license]) if cur.rowcount == 1: licenseid = cur.fetchone()[0] else: - cur.execute("INSERT INTO Licenses (Name) VALUES (%s)", - [license]) - db.commit() + cur = conn.execute("INSERT INTO Licenses (Name) " + + "VALUES (?)", [license]) + conn.commit() licenseid = cur.lastrowid - cur.execute("INSERT INTO PackageLicenses (PackageID, " + - "LicenseID) VALUES (%s, %s)", - [pkgid, licenseid]) + conn.execute("INSERT INTO PackageLicenses (PackageID, " + + "LicenseID) VALUES (?, ?)", + [pkgid, licenseid]) # Add package groups. if 'groups' in pkginfo: for group in pkginfo['groups']: - cur.execute("SELECT ID FROM Groups WHERE Name = %s", - [group]) + cur = conn.execute("SELECT ID FROM Groups WHERE Name = ?", + [group]) if cur.rowcount == 1: groupid = cur.fetchone()[0] else: - cur.execute("INSERT INTO Groups (Name) VALUES (%s)", - [group]) - db.commit() + cur = conn.execute("INSERT INTO Groups (Name) VALUES (?)", + [group]) + conn.commit() groupid = cur.lastrowid - cur.execute("INSERT INTO PackageGroups (PackageID, " - "GroupID) VALUES (%s, %s)", [pkgid, groupid]) + conn.execute("INSERT INTO PackageGroups (PackageID, " + "GroupID) VALUES (?, ?)", [pkgid, groupid]) # Add user to notification list on adoption. if was_orphan: - cur.execute("SELECT COUNT(*) FROM PackageNotifications WHERE " + - "PackageBaseID = %s AND UserID = %s", - [pkgbase_id, user_id]) + cur = conn.execute("SELECT COUNT(*) FROM PackageNotifications WHERE " + + "PackageBaseID = ? AND UserID = ?", + [pkgbase_id, user_id]) if cur.fetchone()[0] == 0: - cur.execute("INSERT INTO PackageNotifications " + - "(PackageBaseID, UserID) VALUES (%s, %s)", - [pkgbase_id, user_id]) + conn.execute("INSERT INTO PackageNotifications " + + "(PackageBaseID, UserID) VALUES (?, ?)", + [pkgbase_id, user_id]) - db.commit() + conn.commit() -def update_notify(db, cur, user, pkgbase_id): +def update_notify(conn, user, pkgbase_id): # Obtain the user ID of the new maintainer. - cur.execute("SELECT ID FROM Users WHERE Username = %s", [user]) + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) user_id = int(cur.fetchone()[0]) # Execute the notification script. @@ -233,10 +228,7 @@ else: if refname != "refs/heads/master": die("pushing to a branch other than master is restricted") -db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, - passwd=aur_db_pass, db=aur_db_name, - unix_socket=aur_db_socket, buffered=True) -cur = db.cursor() +conn = db.Connection() # Detect and deny non-fast-forwards. if sha1_old != "0000000000000000000000000000000000000000" and not privileged: @@ -339,13 +331,13 @@ if metadata_pkgbase != pkgbase: # Ensure that packages are neither blacklisted nor overwritten. pkgbase = metadata['pkgbase'] -cur.execute("SELECT ID FROM PackageBases WHERE Name = %s", [pkgbase]) +cur = conn.execute("SELECT ID FROM PackageBases WHERE Name = ?", [pkgbase]) pkgbase_id = cur.fetchone()[0] if cur.rowcount == 1 else 0 -cur.execute("SELECT Name FROM PackageBlacklist") +cur = conn.execute("SELECT Name FROM PackageBlacklist") blacklist = [row[0] for row in cur.fetchall()] -cur.execute("SELECT Name, Repo FROM OfficialProviders") +cur = conn.execute("SELECT Name, Repo FROM OfficialProviders") providers = dict(cur.fetchall()) for pkgname in srcinfo.utils.get_package_names(metadata): @@ -358,13 +350,13 @@ for pkgname in srcinfo.utils.get_package_names(metadata): repo = providers[pkgname] warn_or_die('package already provided by [{:s}]: {:s}'.format(repo, pkgname)) - cur.execute("SELECT COUNT(*) FROM Packages WHERE Name = %s AND " + - "PackageBaseID <> %s", [pkgname, pkgbase_id]) + cur = conn.execute("SELECT COUNT(*) FROM Packages WHERE Name = ? AND " + + "PackageBaseID <> ?", [pkgname, pkgbase_id]) if cur.fetchone()[0] > 0: die('cannot overwrite package: {:s}'.format(pkgname)) # Store package base details in the database. -save_metadata(metadata, db, cur, user) +save_metadata(metadata, conn, user) # Create (or update) a branch with the name of the package base for better # accessibility. @@ -377,7 +369,7 @@ repo.create_reference('refs/heads/' + pkgbase, sha1_new, True) repo.create_reference('refs/namespaces/' + pkgbase + '/HEAD', sha1_new, True) # Send package update notifications. -update_notify(db, cur, user, pkgbase_id) +update_notify(conn, user, pkgbase_id) # Close the database. -db.close() +conn.close() -- cgit v1.2.3-54-g00ecf From 2f5f5583bec2a0a04424d6bedd763855f308bce6 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Wed, 3 Aug 2016 20:21:40 +0200 Subject: git-interface: Factor out configuration file parsing Add a new module that automatically locates the configuration file and provides methods to obtain the values of configuration options. Use the new module instead of ConfigParser everywhere. Signed-off-by: Lukas Fleischer --- git-interface/config.py | 27 +++++++++++++++++++++++++++ git-interface/db.py | 7 ++----- git-interface/git-auth.py | 6 +----- git-interface/git-serve.py | 5 +---- git-interface/git-update.py | 5 +---- 5 files changed, 32 insertions(+), 18 deletions(-) create mode 100644 git-interface/config.py (limited to 'git-interface') diff --git a/git-interface/config.py b/git-interface/config.py new file mode 100644 index 0000000..cd6495b --- /dev/null +++ b/git-interface/config.py @@ -0,0 +1,27 @@ +import configparser +import os + +_parser = None + + +def _get_parser(): + global _parser + + if not _parser: + _parser = configparser.RawConfigParser() + path = os.path.dirname(os.path.realpath(__file__)) + "/../conf/config" + _parser.read(path) + + return _parser + + +def get(section, option): + return _get_parser().get(section, option) + + +def getboolean(section, option): + return _get_parser().getboolean(section, option) + + +def getint(section, option): + return _get_parser().getint(section, option) diff --git a/git-interface/db.py b/git-interface/db.py index d3e1e69..c4c7d31 100644 --- a/git-interface/db.py +++ b/git-interface/db.py @@ -1,15 +1,12 @@ -import configparser import mysql.connector -import os + +import config class Connection: _conn = None def __init__(self): - config = configparser.RawConfigParser() - config.read(os.path.dirname(os.path.realpath(__file__)) + "/../conf/config") - aur_db_host = config.get('database', 'host') aur_db_name = config.get('database', 'name') aur_db_user = config.get('database', 'user') diff --git a/git-interface/git-auth.py b/git-interface/git-auth.py index 7cd033c..ebdc75c 100755 --- a/git-interface/git-auth.py +++ b/git-interface/git-auth.py @@ -1,11 +1,10 @@ #!/usr/bin/python3 -import configparser import shlex -import os import re import sys +import config import db @@ -24,9 +23,6 @@ def format_command(env_vars, command, ssh_opts, ssh_key): return msg -config = configparser.RawConfigParser() -config.read(os.path.dirname(os.path.realpath(__file__)) + "/../conf/config") - valid_keytypes = config.get('auth', 'valid-keytypes').split() username_regex = config.get('auth', 'username-regex') git_serve_cmd = config.get('auth', 'git-serve-cmd') diff --git a/git-interface/git-serve.py b/git-interface/git-serve.py index ab612f0..6377ffc 100755 --- a/git-interface/git-serve.py +++ b/git-interface/git-serve.py @@ -1,16 +1,13 @@ #!/usr/bin/python3 -import configparser import os import re import shlex import sys +import config import db -config = configparser.RawConfigParser() -config.read(os.path.dirname(os.path.realpath(__file__)) + "/../conf/config") - repo_path = config.get('serve', 'repo-path') repo_regex = config.get('serve', 'repo-regex') git_shell_cmd = config.get('serve', 'git-shell-cmd') diff --git a/git-interface/git-update.py b/git-interface/git-update.py index b7199e6..9a127a9 100755 --- a/git-interface/git-update.py +++ b/git-interface/git-update.py @@ -1,6 +1,5 @@ #!/usr/bin/python3 -import configparser import os import pygit2 import re @@ -10,11 +9,9 @@ import sys import srcinfo.parse import srcinfo.utils +import config import db -config = configparser.RawConfigParser() -config.read(os.path.dirname(os.path.realpath(__file__)) + "/../conf/config") - notify_cmd = config.get('notifications', 'notify-cmd') repo_path = config.get('serve', 'repo-path') -- cgit v1.2.3-54-g00ecf From ecbf32f0cc4673c56380a97a0097187924d47624 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Wed, 3 Aug 2016 20:25:29 +0200 Subject: git-interface: Add AUR_CONFIG environment variable Introduce a new environment variable that can be used to specify the path to an aurweb configuration file. If the environment variable is unset, the default search path is used. Signed-off-by: Lukas Fleischer --- git-interface/config.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'git-interface') diff --git a/git-interface/config.py b/git-interface/config.py index cd6495b..aac188b 100644 --- a/git-interface/config.py +++ b/git-interface/config.py @@ -9,7 +9,11 @@ def _get_parser(): if not _parser: _parser = configparser.RawConfigParser() - path = os.path.dirname(os.path.realpath(__file__)) + "/../conf/config" + if 'AUR_CONFIG' in os.environ: + path = os.environ.get('AUR_CONFIG') + else: + relpath = "/../conf/config" + path = os.path.dirname(os.path.realpath(__file__)) + relpath _parser.read(path) return _parser -- cgit v1.2.3-54-g00ecf From 27631f1157226bd9ca4d0dbfb6a59c7656e7e361 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Thu, 4 Aug 2016 21:00:50 +0200 Subject: git-interface: Do not use rowcount Avoid using Cursor.rowcount to obtain the number of rows returned by a SELECT statement as this is not guaranteed to be supported by every database engine. Signed-off-by: Lukas Fleischer --- git-interface/git-auth.py | 5 +++-- git-interface/git-update.py | 13 ++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) (limited to 'git-interface') diff --git a/git-interface/git-auth.py b/git-interface/git-auth.py index ebdc75c..45fd577 100755 --- a/git-interface/git-auth.py +++ b/git-interface/git-auth.py @@ -40,10 +40,11 @@ cur = conn.execute("SELECT Users.Username, Users.AccountTypeID FROM Users " + "WHERE SSHPubKeys.PubKey = ? AND Users.Suspended = 0", (keytype + " " + keytext,)) -if cur.rowcount != 1: +row = cur.fetchone() +if not row or cur.fetchone(): exit(1) -user, account_type = cur.fetchone() +user, account_type = row if not re.match(username_regex, user): exit(1) diff --git a/git-interface/git-update.py b/git-interface/git-update.py index 9a127a9..d6c9f10 100755 --- a/git-interface/git-update.py +++ b/git-interface/git-update.py @@ -140,8 +140,9 @@ def save_metadata(metadata, conn, user): for license in pkginfo['license']: cur = conn.execute("SELECT ID FROM Licenses WHERE Name = ?", [license]) - if cur.rowcount == 1: - licenseid = cur.fetchone()[0] + row = cur.fetchone() + if row: + licenseid = row[0] else: cur = conn.execute("INSERT INTO Licenses (Name) " + "VALUES (?)", [license]) @@ -156,8 +157,9 @@ def save_metadata(metadata, conn, user): for group in pkginfo['groups']: cur = conn.execute("SELECT ID FROM Groups WHERE Name = ?", [group]) - if cur.rowcount == 1: - groupid = cur.fetchone()[0] + row = cur.fetchone() + if row: + groupid = row[0] else: cur = conn.execute("INSERT INTO Groups (Name) VALUES (?)", [group]) @@ -329,7 +331,8 @@ if metadata_pkgbase != pkgbase: # Ensure that packages are neither blacklisted nor overwritten. pkgbase = metadata['pkgbase'] cur = conn.execute("SELECT ID FROM PackageBases WHERE Name = ?", [pkgbase]) -pkgbase_id = cur.fetchone()[0] if cur.rowcount == 1 else 0 +row = cur.fetchone() +pkgbase_id = row[0] if row else 0 cur = conn.execute("SELECT Name FROM PackageBlacklist") blacklist = [row[0] for row in cur.fetchall()] -- cgit v1.2.3-54-g00ecf From f2a6bd207d5c3400e304c53f0b6eafb4bc5b7ece Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Fri, 5 Aug 2016 11:36:19 +0200 Subject: git-interface: Do not use UNIX_TIMESTAMP Avoid using UNIX_TIMESTAMP which is not part of the SQL standard. Retrieve the current UNIX time in Python and substitute it into the SQL queries instead. Signed-off-by: Lukas Fleischer --- git-interface/git-serve.py | 5 +++-- git-interface/git-update.py | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) (limited to 'git-interface') diff --git a/git-interface/git-serve.py b/git-interface/git-serve.py index 6377ffc..d3a32c3 100755 --- a/git-interface/git-serve.py +++ b/git-interface/git-serve.py @@ -4,6 +4,7 @@ import os import re import shlex import sys +import time import config import db @@ -58,10 +59,10 @@ def create_pkgbase(pkgbase, user): if userid == 0: die('{:s}: unknown user: {:s}'.format(action, user)) + now = int(time.time()) cur = conn.execute("INSERT INTO PackageBases (Name, SubmittedTS, " + "ModifiedTS, SubmitterUID, MaintainerUID) VALUES " + - "(?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), ?, ?)", - [pkgbase, userid, userid]) + "(?, ?, ?, ?, ?)", [pkgbase, now, now, userid, userid]) pkgbase_id = cur.lastrowid cur = conn.execute("INSERT INTO PackageNotifications " + diff --git a/git-interface/git-update.py b/git-interface/git-update.py index d6c9f10..2820720 100755 --- a/git-interface/git-update.py +++ b/git-interface/git-update.py @@ -5,6 +5,7 @@ import pygit2 import re import subprocess import sys +import time import srcinfo.parse import srcinfo.utils @@ -70,9 +71,10 @@ def save_metadata(metadata, conn, user): user_id = int(cur.fetchone()[0]) # Update package base details and delete current packages. - conn.execute("UPDATE PackageBases SET ModifiedTS = UNIX_TIMESTAMP(), " + + now = int(time.time()) + conn.execute("UPDATE PackageBases SET ModifiedTS = ?, " + "PackagerUID = ?, OutOfDateTS = NULL WHERE ID = ?", - [user_id, pkgbase_id]) + [now, user_id, pkgbase_id]) conn.execute("UPDATE PackageBases SET MaintainerUID = ? " + "WHERE ID = ? AND MaintainerUID IS NULL", [user_id, pkgbase_id]) -- cgit v1.2.3-54-g00ecf From baf8a220ab923371cf19c742d2a7805e2276a037 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Wed, 3 Aug 2016 20:28:22 +0200 Subject: git-interface: Support SQLite as database backend In addition to MySQL, add support for SQLite to the database abstraction layer. Also, add a new configuration option to select the DBMS. Signed-off-by: Lukas Fleischer --- conf/config.proto | 2 +- git-interface/db.py | 42 +++++++++++++++++++++++++++++------------- 2 files changed, 30 insertions(+), 14 deletions(-) (limited to 'git-interface') diff --git a/conf/config.proto b/conf/config.proto index 543c3ca..c56141c 100644 --- a/conf/config.proto +++ b/conf/config.proto @@ -1,5 +1,5 @@ [database] -dsn_prefix = mysql +backend = mysql host = localhost socket = /var/run/mysqld/mysqld.sock name = AUR diff --git a/git-interface/db.py b/git-interface/db.py index c4c7d31..060689b 100644 --- a/git-interface/db.py +++ b/git-interface/db.py @@ -1,27 +1,43 @@ import mysql.connector +import sqlite3 import config class Connection: _conn = None + _paramstyle = None def __init__(self): - aur_db_host = config.get('database', 'host') - aur_db_name = config.get('database', 'name') - aur_db_user = config.get('database', 'user') - aur_db_pass = config.get('database', 'password') - aur_db_socket = config.get('database', 'socket') - - self._conn = mysql.connector.connect(host=aur_db_host, - user=aur_db_user, - passwd=aur_db_pass, - db=aur_db_name, - unix_socket=aur_db_socket, - buffered=True) + aur_db_backend = config.get('database', 'backend') + + if aur_db_backend == 'mysql': + aur_db_host = config.get('database', 'host') + aur_db_name = config.get('database', 'name') + aur_db_user = config.get('database', 'user') + aur_db_pass = config.get('database', 'password') + aur_db_socket = config.get('database', 'socket') + self._conn = mysql.connector.connect(host=aur_db_host, + user=aur_db_user, + passwd=aur_db_pass, + db=aur_db_name, + unix_socket=aur_db_socket, + buffered=True) + self._paramstyle = mysql.connector.paramstyle + elif aur_db_backend == 'sqlite': + aur_db_name = config.get('database', 'name') + self._conn = sqlite3.connect(aur_db_name) + self._paramstyle = sqlite3.paramstyle + else: + raise ValueError('unsupported database backend') def execute(self, query, params=()): - query = query.replace('%', '%%').replace('?', '%s') + if self._paramstyle == 'format': + query = query.replace('%', '%%').replace('?', '%s') + elif self._paramstyle == 'qmark': + pass + else: + raise ValueError('unsupported paramstyle') cur = self._conn.cursor() cur.execute(query, params) -- cgit v1.2.3-54-g00ecf From 6e38309c194d860cccd26f901ee5687502331779 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Thu, 4 Aug 2016 20:36:31 +0200 Subject: git-interface: Add test suite and basic tests Add basic tests for the Git interface. The test suite is based on sharness. Signed-off-by: Lukas Fleischer --- git-interface/test/Makefile | 11 + git-interface/test/setup.sh | 121 ++++++ git-interface/test/sharness.sh | 851 +++++++++++++++++++++++++++++++++++++ git-interface/test/t0001-auth.sh | 19 + git-interface/test/t0002-serve.sh | 26 ++ git-interface/test/t0003-update.sh | 20 + 6 files changed, 1048 insertions(+) create mode 100644 git-interface/test/Makefile create mode 100644 git-interface/test/setup.sh create mode 100644 git-interface/test/sharness.sh create mode 100755 git-interface/test/t0001-auth.sh create mode 100755 git-interface/test/t0002-serve.sh create mode 100755 git-interface/test/t0003-update.sh (limited to 'git-interface') diff --git a/git-interface/test/Makefile b/git-interface/test/Makefile new file mode 100644 index 0000000..d6f0f74 --- /dev/null +++ b/git-interface/test/Makefile @@ -0,0 +1,11 @@ +T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)) + +check: $(T) + +clean: + $(RM) -r test-results/ + +$(T): + @echo "*** $@ ***"; $(SHELL) $@ + +.PHONY: check clean $(T) diff --git a/git-interface/test/setup.sh b/git-interface/test/setup.sh new file mode 100644 index 0000000..eecb4d5 --- /dev/null +++ b/git-interface/test/setup.sh @@ -0,0 +1,121 @@ +TEST_DIRECTORY="$(pwd)" + +. ./sharness.sh + +# Configure paths to the Git interface scripts. +GIT_AUTH="$TEST_DIRECTORY/../git-auth.py" +GIT_SERVE="$TEST_DIRECTORY/../git-serve.py" +GIT_UPDATE="$TEST_DIRECTORY/../git-update.py" + +# Create the configuration file and a dummy notification script. +cat >config <<-EOF +[database] +backend = sqlite +name = aur.db + +[options] +enable-maintenance = 0 +maintenance-exceptions = 127.0.0.1 + +[notifications] +notify-cmd = ./notify.sh + +[auth] +valid-keytypes = ssh-rsa ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 +username-regex = [a-zA-Z0-9]+[.\-_]?[a-zA-Z0-9]+$ +git-serve-cmd = /srv/http/aurweb/git-interface/git-serve.py +ssh-options = restrict + +[serve] +repo-path = ./aur.git/ +repo-regex = [a-z0-9][a-z0-9.+_-]*$ +git-shell-cmd = /usr/bin/git-shell +git-update-cmd = /srv/http/aurweb/git-interface/git-update.py +ssh-cmdline = ssh aur@aur.archlinux.org + +[update] +max-blob-size = 256000 +EOF + +cat >notify.sh <<-EOF +#!/bin/sh +EOF +chmod +x notify.sh + +AUR_CONFIG=config +export AUR_CONFIG + +# Create SSH public keys which will be used by the test users later. +AUTH_KEYTYPE_USER=ssh-rsa +AUTH_KEYTEXT_USER=AAAAB3NzaC1yc2EAAAADAQABAAABAQCeUafDK4jqUiRHNQfwHcYjBKLZ4Rc1sNUofHApBP6j91nIvDHZe2VUqeBmFUhBz7kXK4VbXD9nlHMun2HeshL8hXnMzymZ8Wk7+IKefj61pajJkIdttw9Tnayfg7uhg5RbFy9zpEjmGjnIVjSzOXKCwppNT+CNujpKM5FD8gso/Z+l3fD+IwrPwS1SzF1Z99nqI9n2FM/JWZqluvTqnW9WdAvBDfutXxp0R5ZiLI5TAKL2Ssp5rpL70pkLXhv+9sK545zKKlXUFmw6Pi2iVBdqdRsk9ocl49dLiNIh8CYDCO3CRQn+8EnpBhTor2TKQxGJI3mzoBwWJJxoKhD/XlYJ +AUTH_FINGERPRINT_USER=SHA256:F/OFtYAy0JCytAGUi4RUZnOsThhQtFMK7fH1YvFBCpo + +AUTH_KEYTYPE_TU=ssh-rsa +AUTH_KEYTEXT_TU=AAAAB3NzaC1yc2EAAAADAQABAAABAQC4Q2Beg6jf2r1LZ4vwT5y10dK8+/c5RaNyTwv77wF2OSLXh32xW0ovhE2lW2gqoakdGsxgM2fTtqMTl29WOsAxlGF7x9XbWhFXFUT88Daq1fAeuihkiRjfBbInSW/WcrFZ+biLBch67addtfkkd4PmAafDeeCtszAXqza+ltBG1oxAGiTXgI3LOhA1/GtLLxsi5sPUO3ZlhvwDn4Sy0aXYx8l9hop/PU4Cjn82hyRa9r+SRxQ3KtjKxcVMnZ8IyXOrBwXTukgSBR/6nSdEmO0JPkYUFuNwh3UGFKuNkrPguL5T+4YDym6czYmZJzQ7NNl2pLKYmYgBwBe5rORlWfN5 +AUTH_FINGERPRINT_TU=SHA256:xQGC6j/U1Q3NDXLl04pm+Shr1mjYUXbGMUzlm9vby4k + +# Initialize the test database. +rm -f aur.db +sed \ + -e '/^DROP DATABASE /d' \ + -e '/^CREATE DATABASE /d' \ + -e '/^USE /d' \ + -e 's/ ENGINE = InnoDB//' \ + -e 's/ [A-Z]* UNSIGNED NOT NULL AUTO_INCREMENT/ INTEGER NOT NULL/' \ + -e 's/([0-9, ]*) UNSIGNED / UNSIGNED /' \ + "$TEST_DIRECTORY/../../schema/aur-schema.sql" | sqlite3 aur.db + +echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (1, 'user', '!', 'user@localhost', 1);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (2, 'tu', '!', 'tu@localhost', 2);" | sqlite3 aur.db + +echo "INSERT INTO SSHPubKeys (UserID, Fingerprint, PubKey) VALUES (1, '$AUTH_FINGERPRINT_USER', '$AUTH_KEYTYPE_USER $AUTH_KEYTEXT_USER');" | sqlite3 aur.db +echo "INSERT INTO SSHPubKeys (UserID, Fingerprint, PubKey) VALUES (2, '$AUTH_FINGERPRINT_TU', '$AUTH_KEYTYPE_TU $AUTH_KEYTEXT_TU');" | sqlite3 aur.db + +# Initialize a Git repository to store test packages in. +( + GIT_AUTHOR_EMAIL=author@example.com + GIT_AUTHOR_NAME='A U Thor' + GIT_COMMITTER_EMAIL=committer@example.com + GIT_COMMITTER_NAME='C O Mitter' + export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME + export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME + + mkdir aur.git + cd aur.git + + git init -q + git checkout -q -b refs/namespaces/foobar/refs/heads/master + + cat >PKGBUILD <<-EOF + pkgname=foobar + pkgver=1 + pkgrel=1 + pkgdesc='aurweb test package.' + url='https://aur.archlinux.org/' + license=('GPL') + arch=('any') + depends=('python-pygit2') + source=() + md5sums=() + + package() { + echo 'Hello world!' + } + EOF + + cat >.SRCINFO <<-EOF + pkgbase = foobar + pkgdesc = aurweb test package. + pkgver = 1 + pkgrel = 1 + url = https://aur.archlinux.org/ + arch = any + license = GPL + depends = python-pygit2 + + pkgname = foobar + EOF + + git add PKGBUILD .SRCINFO + git commit -q -am 'Initial import' +) diff --git a/git-interface/test/sharness.sh b/git-interface/test/sharness.sh new file mode 100644 index 0000000..1d57ce9 --- /dev/null +++ b/git-interface/test/sharness.sh @@ -0,0 +1,851 @@ +#!/bin/sh +# +# Copyright (c) 2011-2012 Mathias Lafeldt +# Copyright (c) 2005-2012 Git project +# Copyright (c) 2005-2012 Junio C Hamano +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/ . + +# Public: Current version of Sharness. +SHARNESS_VERSION="1.0.0" +export SHARNESS_VERSION + +# Public: The file extension for tests. By default, it is set to "t". +: ${SHARNESS_TEST_EXTENSION:=t} +export SHARNESS_TEST_EXTENSION + +# Reset TERM to original terminal if found, otherwise save orignal TERM +[ "x" = "x$SHARNESS_ORIG_TERM" ] && + SHARNESS_ORIG_TERM="$TERM" || + TERM="$SHARNESS_ORIG_TERM" +# Public: The unsanitized TERM under which sharness is originally run +export SHARNESS_ORIG_TERM + +# Export SHELL_PATH +: ${SHELL_PATH:=$SHELL} +export SHELL_PATH + +# For repeatability, reset the environment to a known state. +# TERM is sanitized below, after saving color control sequences. +LANG=C +LC_ALL=C +PAGER=cat +TZ=UTC +EDITOR=: +export LANG LC_ALL PAGER TZ EDITOR +unset VISUAL CDPATH GREP_OPTIONS + +# Line feed +LF=' +' + +[ "x$TERM" != "xdumb" ] && ( + [ -t 1 ] && + tput bold >/dev/null 2>&1 && + tput setaf 1 >/dev/null 2>&1 && + tput sgr0 >/dev/null 2>&1 + ) && + color=t + +while test "$#" -ne 0; do + case "$1" in + -d|--d|--de|--deb|--debu|--debug) + debug=t; shift ;; + -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) + immediate=t; shift ;; + -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) + TEST_LONG=t; export TEST_LONG; shift ;; + --in|--int|--inte|--inter|--intera|--interac|--interact|--interacti|--interactiv|--interactive|--interactive-|--interactive-t|--interactive-te|--interactive-tes|--interactive-test|--interactive-tests): + TEST_INTERACTIVE=t; export TEST_INTERACTIVE; verbose=t; shift ;; + -h|--h|--he|--hel|--help) + help=t; shift ;; + -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) + verbose=t; shift ;; + -q|--q|--qu|--qui|--quie|--quiet) + # Ignore --quiet under a TAP::Harness. Saying how many tests + # passed without the ok/not ok details is always an error. + test -z "$HARNESS_ACTIVE" && quiet=t; shift ;; + --chain-lint) + chain_lint=t; shift ;; + --no-chain-lint) + chain_lint=; shift ;; + --no-color) + color=; shift ;; + --root=*) + root=$(expr "z$1" : 'z[^=]*=\(.*\)') + shift ;; + *) + echo "error: unknown test option '$1'" >&2; exit 1 ;; + esac +done + +if test -n "$color"; then + # Save the color control sequences now rather than run tput + # each time say_color() is called. This is done for two + # reasons: + # * TERM will be changed to dumb + # * HOME will be changed to a temporary directory and tput + # might need to read ~/.terminfo from the original HOME + # directory to get the control sequences + # Note: This approach assumes the control sequences don't end + # in a newline for any terminal of interest (command + # substitutions strip trailing newlines). Given that most + # (all?) terminals in common use are related to ECMA-48, this + # shouldn't be a problem. + say_color_error=$(tput bold; tput setaf 1) # bold red + say_color_skip=$(tput setaf 4) # blue + say_color_warn=$(tput setaf 3) # brown/yellow + say_color_pass=$(tput setaf 2) # green + say_color_info=$(tput setaf 6) # cyan + say_color_reset=$(tput sgr0) + say_color_="" # no formatting for normal text + say_color() { + test -z "$1" && test -n "$quiet" && return + eval "say_color_color=\$say_color_$1" + shift + printf "%s\\n" "$say_color_color$*$say_color_reset" + } +else + say_color() { + test -z "$1" && test -n "$quiet" && return + shift + printf "%s\n" "$*" + } +fi + +TERM=dumb +export TERM + +error() { + say_color error "error: $*" + EXIT_OK=t + exit 1 +} + +say() { + say_color info "$*" +} + +test -n "$test_description" || error "Test script did not set test_description." + +if test "$help" = "t"; then + echo "$test_description" + exit 0 +fi + +exec 5>&1 +exec 6<&0 +if test "$verbose" = "t"; then + exec 4>&2 3>&1 +else + exec 4>/dev/null 3>/dev/null +fi + +test_failure=0 +test_count=0 +test_fixed=0 +test_broken=0 +test_success=0 + +die() { + code=$? + if test -n "$EXIT_OK"; then + exit $code + else + echo >&5 "FATAL: Unexpected exit with code $code" + exit 1 + fi +} + +EXIT_OK= +trap 'die' EXIT + +# Public: Define that a test prerequisite is available. +# +# The prerequisite can later be checked explicitly using test_have_prereq or +# implicitly by specifying the prerequisite name in calls to test_expect_success +# or test_expect_failure. +# +# $1 - Name of prerequiste (a simple word, in all capital letters by convention) +# +# Examples +# +# # Set PYTHON prerequisite if interpreter is available. +# command -v python >/dev/null && test_set_prereq PYTHON +# +# # Set prerequisite depending on some variable. +# test -z "$NO_GETTEXT" && test_set_prereq GETTEXT +# +# Returns nothing. +test_set_prereq() { + satisfied_prereq="$satisfied_prereq$1 " +} +satisfied_prereq=" " + +# Public: Check if one or more test prerequisites are defined. +# +# The prerequisites must have previously been set with test_set_prereq. +# The most common use of this is to skip all the tests if some essential +# prerequisite is missing. +# +# $1 - Comma-separated list of test prerequisites. +# +# Examples +# +# # Skip all remaining tests if prerequisite is not set. +# if ! test_have_prereq PERL; then +# skip_all='skipping perl interface tests, perl not available' +# test_done +# fi +# +# Returns 0 if all prerequisites are defined or 1 otherwise. +test_have_prereq() { + # prerequisites can be concatenated with ',' + save_IFS=$IFS + IFS=, + set -- $* + IFS=$save_IFS + + total_prereq=0 + ok_prereq=0 + missing_prereq= + + for prerequisite; do + case "$prerequisite" in + !*) + negative_prereq=t + prerequisite=${prerequisite#!} + ;; + *) + negative_prereq= + esac + + total_prereq=$(($total_prereq + 1)) + case "$satisfied_prereq" in + *" $prerequisite "*) + satisfied_this_prereq=t + ;; + *) + satisfied_this_prereq= + esac + + case "$satisfied_this_prereq,$negative_prereq" in + t,|,t) + ok_prereq=$(($ok_prereq + 1)) + ;; + *) + # Keep a list of missing prerequisites; restore + # the negative marker if necessary. + prerequisite=${negative_prereq:+!}$prerequisite + if test -z "$missing_prereq"; then + missing_prereq=$prerequisite + else + missing_prereq="$prerequisite,$missing_prereq" + fi + esac + done + + test $total_prereq = $ok_prereq +} + +# You are not expected to call test_ok_ and test_failure_ directly, use +# the text_expect_* functions instead. + +test_ok_() { + test_success=$(($test_success + 1)) + say_color "" "ok $test_count - $@" +} + +test_failure_() { + test_failure=$(($test_failure + 1)) + say_color error "not ok $test_count - $1" + shift + echo "$@" | sed -e 's/^/# /' + test "$immediate" = "" || { EXIT_OK=t; exit 1; } +} + +test_known_broken_ok_() { + test_fixed=$(($test_fixed + 1)) + say_color error "ok $test_count - $@ # TODO known breakage vanished" +} + +test_known_broken_failure_() { + test_broken=$(($test_broken + 1)) + say_color warn "not ok $test_count - $@ # TODO known breakage" +} + +# Public: Execute commands in debug mode. +# +# Takes a single argument and evaluates it only when the test script is started +# with --debug. This is primarily meant for use during the development of test +# scripts. +# +# $1 - Commands to be executed. +# +# Examples +# +# test_debug "cat some_log_file" +# +# Returns the exit code of the last command executed in debug mode or 0 +# otherwise. +test_debug() { + test "$debug" = "" || eval "$1" +} + +# Public: Stop execution and start a shell. +# +# This is useful for debugging tests and only makes sense together with "-v". +# Be sure to remove all invocations of this command before submitting. +test_pause() { + if test "$verbose" = t; then + "$SHELL_PATH" <&6 >&3 2>&4 + else + error >&5 "test_pause requires --verbose" + fi +} + +test_eval_() { + # This is a separate function because some tests use + # "return" to end a test_expect_success block early. + case ",$test_prereq," in + *,INTERACTIVE,*) + eval "$*" + ;; + *) + eval &3 2>&4 "$*" + ;; + esac +} + +test_run_() { + test_cleanup=: + expecting_failure=$2 + test_eval_ "$1" + eval_ret=$? + + if test "$chain_lint" = "t"; then + test_eval_ "(exit 117) && $1" + if test "$?" != 117; then + error "bug in the test script: broken &&-chain: $1" + fi + fi + + if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure"; then + test_eval_ "$test_cleanup" + fi + if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then + echo "" + fi + return "$eval_ret" +} + +test_skip_() { + test_count=$(($test_count + 1)) + to_skip= + for skp in $SKIP_TESTS; do + case $this_test.$test_count in + $skp) + to_skip=t + break + esac + done + if test -z "$to_skip" && test -n "$test_prereq" && ! test_have_prereq "$test_prereq"; then + to_skip=t + fi + case "$to_skip" in + t) + of_prereq= + if test "$missing_prereq" != "$test_prereq"; then + of_prereq=" of $test_prereq" + fi + + say_color skip >&3 "skipping test: $@" + say_color skip "ok $test_count # skip $1 (missing $missing_prereq${of_prereq})" + : true + ;; + *) + false + ;; + esac +} + +# Public: Run test commands and expect them to succeed. +# +# When the test passed, an "ok" message is printed and the number of successful +# tests is incremented. When it failed, a "not ok" message is printed and the +# number of failed tests is incremented. +# +# With --immediate, exit test immediately upon the first failed test. +# +# Usually takes two arguments: +# $1 - Test description +# $2 - Commands to be executed. +# +# With three arguments, the first will be taken to be a prerequisite: +# $1 - Comma-separated list of test prerequisites. The test will be skipped if +# not all of the given prerequisites are set. To negate a prerequisite, +# put a "!" in front of it. +# $2 - Test description +# $3 - Commands to be executed. +# +# Examples +# +# test_expect_success \ +# 'git-write-tree should be able to write an empty tree.' \ +# 'tree=$(git-write-tree)' +# +# # Test depending on one prerequisite. +# test_expect_success TTY 'git --paginate rev-list uses a pager' \ +# ' ... ' +# +# # Multiple prerequisites are separated by a comma. +# test_expect_success PERL,PYTHON 'yo dawg' \ +# ' test $(perl -E 'print eval "1 +" . qx[python -c "print 2"]') == "4" ' +# +# Returns nothing. +test_expect_success() { + test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= + test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test_expect_success" + export test_prereq + if ! test_skip_ "$@"; then + say >&3 "expecting success: $2" + if test_run_ "$2"; then + test_ok_ "$1" + else + test_failure_ "$@" + fi + fi + echo >&3 "" +} + +# Public: Run test commands and expect them to fail. Used to demonstrate a known +# breakage. +# +# This is NOT the opposite of test_expect_success, but rather used to mark a +# test that demonstrates a known breakage. +# +# When the test passed, an "ok" message is printed and the number of fixed tests +# is incremented. When it failed, a "not ok" message is printed and the number +# of tests still broken is incremented. +# +# Failures from these tests won't cause --immediate to stop. +# +# Usually takes two arguments: +# $1 - Test description +# $2 - Commands to be executed. +# +# With three arguments, the first will be taken to be a prerequisite: +# $1 - Comma-separated list of test prerequisites. The test will be skipped if +# not all of the given prerequisites are set. To negate a prerequisite, +# put a "!" in front of it. +# $2 - Test description +# $3 - Commands to be executed. +# +# Returns nothing. +test_expect_failure() { + test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= + test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test_expect_failure" + export test_prereq + if ! test_skip_ "$@"; then + say >&3 "checking known breakage: $2" + if test_run_ "$2" expecting_failure; then + test_known_broken_ok_ "$1" + else + test_known_broken_failure_ "$1" + fi + fi + echo >&3 "" +} + +# Public: Run command and ensure that it fails in a controlled way. +# +# Use it instead of "! ". For example, when dies due to a +# segfault, test_must_fail diagnoses it as an error, while "! " would +# mistakenly be treated as just another expected failure. +# +# This is one of the prefix functions to be used inside test_expect_success or +# test_expect_failure. +# +# $1.. - Command to be executed. +# +# Examples +# +# test_expect_success 'complain and die' ' +# do something && +# do something else && +# test_must_fail git checkout ../outerspace +# ' +# +# Returns 1 if the command succeeded (exit code 0). +# Returns 1 if the command died by signal (exit codes 130-192) +# Returns 1 if the command could not be found (exit code 127). +# Returns 0 otherwise. +test_must_fail() { + "$@" + exit_code=$? + if test $exit_code = 0; then + echo >&2 "test_must_fail: command succeeded: $*" + return 1 + elif test $exit_code -gt 129 -a $exit_code -le 192; then + echo >&2 "test_must_fail: died by signal: $*" + return 1 + elif test $exit_code = 127; then + echo >&2 "test_must_fail: command not found: $*" + return 1 + fi + return 0 +} + +# Public: Run command and ensure that it succeeds or fails in a controlled way. +# +# Similar to test_must_fail, but tolerates success too. Use it instead of +# " || :" to catch failures caused by a segfault, for instance. +# +# This is one of the prefix functions to be used inside test_expect_success or +# test_expect_failure. +# +# $1.. - Command to be executed. +# +# Examples +# +# test_expect_success 'some command works without configuration' ' +# test_might_fail git config --unset all.configuration && +# do something +# ' +# +# Returns 1 if the command died by signal (exit codes 130-192) +# Returns 1 if the command could not be found (exit code 127). +# Returns 0 otherwise. +test_might_fail() { + "$@" + exit_code=$? + if test $exit_code -gt 129 -a $exit_code -le 192; then + echo >&2 "test_might_fail: died by signal: $*" + return 1 + elif test $exit_code = 127; then + echo >&2 "test_might_fail: command not found: $*" + return 1 + fi + return 0 +} + +# Public: Run command and ensure it exits with a given exit code. +# +# This is one of the prefix functions to be used inside test_expect_success or +# test_expect_failure. +# +# $1 - Expected exit code. +# $2.. - Command to be executed. +# +# Examples +# +# test_expect_success 'Merge with d/f conflicts' ' +# test_expect_code 1 git merge "merge msg" B master +# ' +# +# Returns 0 if the expected exit code is returned or 1 otherwise. +test_expect_code() { + want_code=$1 + shift + "$@" + exit_code=$? + if test $exit_code = $want_code; then + return 0 + fi + + echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*" + return 1 +} + +# Public: Compare two files to see if expected output matches actual output. +# +# The TEST_CMP variable defines the command used for the comparision; it +# defaults to "diff -u". Only when the test script was started with --verbose, +# will the command's output, the diff, be printed to the standard output. +# +# This is one of the prefix functions to be used inside test_expect_success or +# test_expect_failure. +# +# $1 - Path to file with expected output. +# $2 - Path to file with actual output. +# +# Examples +# +# test_expect_success 'foo works' ' +# echo expected >expected && +# foo >actual && +# test_cmp expected actual +# ' +# +# Returns the exit code of the command set by TEST_CMP. +test_cmp() { + ${TEST_CMP:-diff -u} "$@" +} + +# Public: portably print a sequence of numbers. +# +# seq is not in POSIX and GNU seq might not be available everywhere, +# so it is nice to have a seq implementation, even a very simple one. +# +# $1 - Starting number. +# $2 - Ending number. +# +# Examples +# +# test_expect_success 'foo works 10 times' ' +# for i in $(test_seq 1 10) +# do +# foo || return +# done +# ' +# +# Returns 0 if all the specified numbers can be displayed. +test_seq() { + i="$1" + j="$2" + while test "$i" -le "$j" + do + echo "$i" || return + i=$(expr "$i" + 1) + done +} + +# Public: Check if the file expected to be empty is indeed empty, and barfs +# otherwise. +# +# $1 - File to check for emptyness. +# +# Returns 0 if file is empty, 1 otherwise. +test_must_be_empty() { + if test -s "$1" + then + echo "'$1' is not empty, it contains:" + cat "$1" + return 1 + fi +} + +# Public: Schedule cleanup commands to be run unconditionally at the end of a +# test. +# +# If some cleanup command fails, the test will not pass. With --immediate, no +# cleanup is done to help diagnose what went wrong. +# +# This is one of the prefix functions to be used inside test_expect_success or +# test_expect_failure. +# +# $1.. - Commands to prepend to the list of cleanup commands. +# +# Examples +# +# test_expect_success 'test core.capslock' ' +# git config core.capslock true && +# test_when_finished "git config --unset core.capslock" && +# do_something +# ' +# +# Returns the exit code of the last cleanup command executed. +test_when_finished() { + test_cleanup="{ $* + } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup" +} + +# Public: Schedule cleanup commands to be run unconditionally when all tests +# have run. +# +# This can be used to clean up things like test databases. It is not needed to +# clean up temporary files, as test_done already does that. +# +# Examples: +# +# cleanup mysql -e "DROP DATABASE mytest" +# +# Returns the exit code of the last cleanup command executed. +final_cleanup= +cleanup() { + final_cleanup="{ $* + } && (exit \"\$eval_ret\"); eval_ret=\$?; $final_cleanup" +} + +# Public: Summarize test results and exit with an appropriate error code. +# +# Must be called at the end of each test script. +# +# Can also be used to stop tests early and skip all remaining tests. For this, +# set skip_all to a string explaining why the tests were skipped before calling +# test_done. +# +# Examples +# +# # Each test script must call test_done at the end. +# test_done +# +# # Skip all remaining tests if prerequisite is not set. +# if ! test_have_prereq PERL; then +# skip_all='skipping perl interface tests, perl not available' +# test_done +# fi +# +# Returns 0 if all tests passed or 1 if there was a failure. +test_done() { + EXIT_OK=t + + if test -z "$HARNESS_ACTIVE"; then + test_results_dir="$SHARNESS_TEST_DIRECTORY/test-results" + mkdir -p "$test_results_dir" + test_results_path="$test_results_dir/$this_test.$$.counts" + + cat >>"$test_results_path" <<-EOF + total $test_count + success $test_success + fixed $test_fixed + broken $test_broken + failed $test_failure + + EOF + fi + + if test "$test_fixed" != 0; then + say_color error "# $test_fixed known breakage(s) vanished; please update test(s)" + fi + if test "$test_broken" != 0; then + say_color warn "# still have $test_broken known breakage(s)" + fi + if test "$test_broken" != 0 || test "$test_fixed" != 0; then + test_remaining=$(( $test_count - $test_broken - $test_fixed )) + msg="remaining $test_remaining test(s)" + else + test_remaining=$test_count + msg="$test_count test(s)" + fi + + case "$test_failure" in + 0) + # Maybe print SKIP message + if test -n "$skip_all" && test $test_count -gt 0; then + error "Can't use skip_all after running some tests" + fi + [ -z "$skip_all" ] || skip_all=" # SKIP $skip_all" + + if test $test_remaining -gt 0; then + say_color pass "# passed all $msg" + fi + say "1..$test_count$skip_all" + + test_eval_ "$final_cleanup" + + test -d "$remove_trash" && + cd "$(dirname "$remove_trash")" && + rm -rf "$(basename "$remove_trash")" + + exit 0 ;; + + *) + say_color error "# failed $test_failure among $msg" + say "1..$test_count" + + exit 1 ;; + + esac +} + +# Public: Root directory containing tests. Tests can override this variable, +# e.g. for testing Sharness itself. +: ${SHARNESS_TEST_DIRECTORY:=$(pwd)} +export SHARNESS_TEST_DIRECTORY + +# Public: Source directory of test code and sharness library. +# This directory may be different from the directory in which tests are +# being run. +: ${SHARNESS_TEST_SRCDIR:=$(cd $(dirname $0) && pwd)} +export SHARNESS_TEST_SRCDIR + +# Public: Build directory that will be added to PATH. By default, it is set to +# the parent directory of SHARNESS_TEST_DIRECTORY. +: ${SHARNESS_BUILD_DIRECTORY:="$SHARNESS_TEST_DIRECTORY/.."} +PATH="$SHARNESS_BUILD_DIRECTORY:$PATH" +export PATH SHARNESS_BUILD_DIRECTORY + +# Public: Path to test script currently executed. +SHARNESS_TEST_FILE="$0" +export SHARNESS_TEST_FILE + +# Prepare test area. +SHARNESS_TRASH_DIRECTORY="trash directory.$(basename "$SHARNESS_TEST_FILE" ".$SHARNESS_TEST_EXTENSION")" +test -n "$root" && SHARNESS_TRASH_DIRECTORY="$root/$SHARNESS_TRASH_DIRECTORY" +case "$SHARNESS_TRASH_DIRECTORY" in +/*) ;; # absolute path is good + *) SHARNESS_TRASH_DIRECTORY="$SHARNESS_TEST_DIRECTORY/$SHARNESS_TRASH_DIRECTORY" ;; +esac +test "$debug" = "t" || remove_trash="$SHARNESS_TRASH_DIRECTORY" +rm -rf "$SHARNESS_TRASH_DIRECTORY" || { + EXIT_OK=t + echo >&5 "FATAL: Cannot prepare test area" + exit 1 +} + + +# +# Load any extensions in $srcdir/sharness.d/*.sh +# +if test -d "${SHARNESS_TEST_SRCDIR}/sharness.d" +then + for file in "${SHARNESS_TEST_SRCDIR}"/sharness.d/*.sh + do + # Ensure glob was not an empty match: + test -e "${file}" || break + + if test -n "$debug" + then + echo >&5 "sharness: loading extensions from ${file}" + fi + . "${file}" + if test $? != 0 + then + echo >&5 "sharness: Error loading ${file}. Aborting." + exit 1 + fi + done +fi + +# Public: Empty trash directory, the test area, provided for each test. The HOME +# variable is set to that directory too. +export SHARNESS_TRASH_DIRECTORY + +HOME="$SHARNESS_TRASH_DIRECTORY" +export HOME + +mkdir -p "$SHARNESS_TRASH_DIRECTORY" || exit 1 +# Use -P to resolve symlinks in our working directory so that the cwd +# in subprocesses like git equals our $PWD (for pathname comparisons). +cd -P "$SHARNESS_TRASH_DIRECTORY" || exit 1 + +this_test=${SHARNESS_TEST_FILE##*/} +this_test=${this_test%.$SHARNESS_TEST_EXTENSION} +for skp in $SKIP_TESTS; do + case "$this_test" in + $skp) + say_color info >&3 "skipping test $this_test altogether" + skip_all="skip all tests in $this_test" + test_done + esac +done + +test -n "$TEST_LONG" && test_set_prereq EXPENSIVE +test -n "$TEST_INTERACTIVE" && test_set_prereq INTERACTIVE + +# Make sure this script ends with code 0 +: + +# vi: set ts=4 sw=4 noet : diff --git a/git-interface/test/t0001-auth.sh b/git-interface/test/t0001-auth.sh new file mode 100755 index 0000000..50ef510 --- /dev/null +++ b/git-interface/test/t0001-auth.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +test_description='git-auth tests' + +. ./setup.sh + +test_expect_success 'Test basic authentication.' ' + "$GIT_AUTH" "$AUTH_KEYTYPE_USER" "$AUTH_KEYTEXT_USER" >out && + grep -q AUR_USER=user out && + grep -q AUR_PRIVILEGED=0 out +' + +test_expect_success 'Test Trusted User authentication.' ' + "$GIT_AUTH" "$AUTH_KEYTYPE_TU" "$AUTH_KEYTEXT_TU" >out && + grep -q AUR_USER=tu out && + grep -q AUR_PRIVILEGED=1 out +' + +test_done diff --git a/git-interface/test/t0002-serve.sh b/git-interface/test/t0002-serve.sh new file mode 100755 index 0000000..7e17bcb --- /dev/null +++ b/git-interface/test/t0002-serve.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +test_description='git-serve tests' + +. ./setup.sh + +test_expect_success 'Test interactive shell.' ' + "$GIT_SERVE" 2>&1 | grep -q "Interactive shell is disabled." +' + +test_expect_success 'Test help.' ' + SSH_ORIGINAL_COMMAND=help "$GIT_SERVE" 2>&1 | grep -q "^Commands:$" +' + +test_expect_success 'Test setup-repo and list-repos.' ' + SSH_ORIGINAL_COMMAND="setup-repo foobar" AUR_USER=user \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + *foobar + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_done diff --git a/git-interface/test/t0003-update.sh b/git-interface/test/t0003-update.sh new file mode 100755 index 0000000..0e8962c --- /dev/null +++ b/git-interface/test/t0003-update.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +test_description='git-update tests' + +. ./setup.sh + +test_expect_success 'Test update hook.' ' + old=0000000000000000000000000000000000000000 && + new=$(git -C aur.git rev-parse HEAD) && + SSH_ORIGINAL_COMMAND="setup-repo foobar" AUR_USER=user "$GIT_SERVE" && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 && + cat >expected <<-EOF && + 1|1|foobar|1-1|aurweb test package.|https://aur.archlinux.org/ + EOF + echo "SELECT * FROM Packages;" | sqlite3 aur.db >actual && + test_cmp expected actual +' + +test_done -- cgit v1.2.3-54-g00ecf From 3a41f8d56495e469396455b7d95e94e21e26f9b0 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Fri, 5 Aug 2016 14:01:06 +0200 Subject: git-update: Remove package details before updating Explicitly remove all package sources, dependencies, relations, licenses and groups before inserting new ones. Signed-off-by: Lukas Fleischer --- git-interface/git-update.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'git-interface') diff --git a/git-interface/git-update.py b/git-interface/git-update.py index 2820720..e6f6410 100755 --- a/git-interface/git-update.py +++ b/git-interface/git-update.py @@ -78,8 +78,13 @@ def save_metadata(metadata, conn, user): conn.execute("UPDATE PackageBases SET MaintainerUID = ? " + "WHERE ID = ? AND MaintainerUID IS NULL", [user_id, pkgbase_id]) - conn.execute("DELETE FROM Packages WHERE PackageBaseID = ?", - [pkgbase_id]) + for table in ('Sources', 'Depends', 'Relations', 'Licenses', 'Groups'): + conn.execute("DELETE FROM Package" + table + " WHERE EXISTS (" + + "SELECT * FROM Packages " + + "WHERE Packages.PackageBaseID = ? AND " + + "Package" + table + ".PackageID = Packages.ID)", + [pkgbase_id]) + conn.execute("DELETE FROM Packages WHERE PackageBaseID = ?", [pkgbase_id]) for pkgname in srcinfo.utils.get_package_names(metadata): pkginfo = srcinfo.utils.get_merged_package(pkgname, metadata) -- cgit v1.2.3-54-g00ecf From 008eace8dbeb0343ae3908cb8373d669efcb8e63 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Fri, 5 Aug 2016 21:00:02 +0200 Subject: t0001: Add more git-auth tests Test the authentication script with an invalid key type and with a key that does not exist in the database. Signed-off-by: Lukas Fleischer --- git-interface/test/setup.sh | 4 ++++ git-interface/test/t0001-auth.sh | 9 +++++++++ 2 files changed, 13 insertions(+) (limited to 'git-interface') diff --git a/git-interface/test/setup.sh b/git-interface/test/setup.sh index eecb4d5..4512978 100644 --- a/git-interface/test/setup.sh +++ b/git-interface/test/setup.sh @@ -54,6 +54,10 @@ AUTH_KEYTYPE_TU=ssh-rsa AUTH_KEYTEXT_TU=AAAAB3NzaC1yc2EAAAADAQABAAABAQC4Q2Beg6jf2r1LZ4vwT5y10dK8+/c5RaNyTwv77wF2OSLXh32xW0ovhE2lW2gqoakdGsxgM2fTtqMTl29WOsAxlGF7x9XbWhFXFUT88Daq1fAeuihkiRjfBbInSW/WcrFZ+biLBch67addtfkkd4PmAafDeeCtszAXqza+ltBG1oxAGiTXgI3LOhA1/GtLLxsi5sPUO3ZlhvwDn4Sy0aXYx8l9hop/PU4Cjn82hyRa9r+SRxQ3KtjKxcVMnZ8IyXOrBwXTukgSBR/6nSdEmO0JPkYUFuNwh3UGFKuNkrPguL5T+4YDym6czYmZJzQ7NNl2pLKYmYgBwBe5rORlWfN5 AUTH_FINGERPRINT_TU=SHA256:xQGC6j/U1Q3NDXLl04pm+Shr1mjYUXbGMUzlm9vby4k +AUTH_KEYTYPE_MISSING=sha-rsa +AUTH_KEYTEXT_MISSING=AAAAB3NzaC1yc2EAAAADAQABAAABAQC9UTpssBunuTBCT3KFtv+yb+cN0VmI2C9O9U7wHlkEZWxNBK8is6tnDHXBxRuvRk0LHILkTidLLFX22ZF0+TFgSz7uuEvGZVNpa2Fn2+vKJJYMvZEvb/f8VHF5/Jddt21VOyu23royTN/duiT7WIZdCtEmq5C9Y43NPfsB8FbUc+FVSYT2Lq7g1/bzvFF+CZxwCrGjC3qC7p3pshICfFR8bbWgRN33ClxIQ7MvkcDtfNu38dLotJqdfEa7NdQgba5/S586f1A4OWKc/mQJFyTaGhRBxw/cBSjqonvO0442VYLHFxlrTHoUunKyOJ8+BJfKgjWmfENC9ESY3mL/IEn5 +AUTH_FINGERPRINT_MISSING=SHA256:uB0B+30r2WA1TDMUmFcaEBjosjnFGzn33XFhiyvTL9w + # Initialize the test database. rm -f aur.db sed \ diff --git a/git-interface/test/t0001-auth.sh b/git-interface/test/t0001-auth.sh index 50ef510..71d526f 100755 --- a/git-interface/test/t0001-auth.sh +++ b/git-interface/test/t0001-auth.sh @@ -16,4 +16,13 @@ test_expect_success 'Test Trusted User authentication.' ' grep -q AUR_PRIVILEGED=1 out ' +test_expect_success 'Test authentication with an unsupported key type.' ' + test_must_fail "$GIT_AUTH" ssh-xxx "$AUTH_KEYTEXT_USER" +' + +test_expect_success 'Test authentication with a wrong key.' ' + "$GIT_AUTH" "$AUTH_KEYTYPE_MISSING" "$AUTH_KEYTEXT_MISSING" >out + test_must_be_empty out +' + test_done -- cgit v1.2.3-54-g00ecf From 9a03c7fbdd9a1eff197d9c14301ab23aabc88109 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Fri, 5 Aug 2016 21:22:36 +0200 Subject: t0002: Add more git-serve tests Add tests for common scenarios that should be detected/handled by the git-serve script. Signed-off-by: Lukas Fleischer --- git-interface/test/setup.sh | 19 ++++++++++-- git-interface/test/t0002-serve.sh | 62 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) (limited to 'git-interface') diff --git a/git-interface/test/setup.sh b/git-interface/test/setup.sh index 4512978..cb1e250 100644 --- a/git-interface/test/setup.sh +++ b/git-interface/test/setup.sh @@ -29,8 +29,8 @@ ssh-options = restrict [serve] repo-path = ./aur.git/ repo-regex = [a-z0-9][a-z0-9.+_-]*$ -git-shell-cmd = /usr/bin/git-shell -git-update-cmd = /srv/http/aurweb/git-interface/git-update.py +git-shell-cmd = ./git-shell.sh +git-update-cmd = ./update.sh ssh-cmdline = ssh aur@aur.archlinux.org [update] @@ -42,6 +42,21 @@ cat >notify.sh <<-EOF EOF chmod +x notify.sh +cat >git-shell.sh <<-\EOF +#!/bin/sh +echo $AUR_USER +echo $AUR_PKGBASE +echo $GIT_NAMESPACE +EOF +chmod +x git-shell.sh + +cat >update.sh <<-\EOF +#!/bin/sh +echo $AUR_USER +echo $AUR_PKGBASE +EOF +chmod +x update.sh + AUR_CONFIG=config export AUR_CONFIG diff --git a/git-interface/test/t0002-serve.sh b/git-interface/test/t0002-serve.sh index 7e17bcb..f36f1d8 100755 --- a/git-interface/test/t0002-serve.sh +++ b/git-interface/test/t0002-serve.sh @@ -15,6 +15,8 @@ test_expect_success 'Test help.' ' test_expect_success 'Test setup-repo and list-repos.' ' SSH_ORIGINAL_COMMAND="setup-repo foobar" AUR_USER=user \ "$GIT_SERVE" 2>&1 && + SSH_ORIGINAL_COMMAND="setup-repo foobar2" AUR_USER=tu \ + "$GIT_SERVE" 2>&1 && cat >expected <<-EOF && *foobar EOF @@ -23,4 +25,64 @@ test_expect_success 'Test setup-repo and list-repos.' ' test_cmp expected actual ' +test_expect_success 'Test git-receive-pack.' ' + cat >expected <<-EOF && + user + foobar + foobar + EOF + SSH_ORIGINAL_COMMAND="git-receive-pack /foobar.git/" \ + AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success 'Test git-receive-pack with an invalid repository name.' ' + SSH_ORIGINAL_COMMAND="git-receive-pack /!.git/" \ + AUR_USER=user AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_SERVE" 2>&1 >actual +' + +test_expect_success "Test git-upload-pack." ' + cat >expected <<-EOF && + user + foobar + foobar + EOF + SSH_ORIGINAL_COMMAND="git-upload-pack /foobar.git/" \ + AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success "Try to pull from someone else's repository." ' + cat >expected <<-EOF && + user + foobar2 + foobar2 + EOF + SSH_ORIGINAL_COMMAND="git-upload-pack /foobar2.git/" \ + AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success "Try to push to someone else's repository." ' + SSH_ORIGINAL_COMMAND="git-receive-pack /foobar2.git/" \ + AUR_USER=user AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_SERVE" 2>&1 +' + +test_expect_success "Try to push to someone else's repository as Trusted User." ' + cat >expected <<-EOF && + tu + foobar + foobar + EOF + SSH_ORIGINAL_COMMAND="git-receive-pack /foobar.git/" \ + AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + test_done -- cgit v1.2.3-54-g00ecf From dd9c6f3ddca37479ce14425c0a82a4b46d7a727a Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Fri, 5 Aug 2016 14:01:22 +0200 Subject: t0003: Add more git-update tests Add tests for common scenarios that should be detected/handled by the update hook. Signed-off-by: Lukas Fleischer --- git-interface/test/setup.sh | 68 +++++-- git-interface/test/t0003-update.sh | 350 ++++++++++++++++++++++++++++++++++++- 2 files changed, 404 insertions(+), 14 deletions(-) (limited to 'git-interface') diff --git a/git-interface/test/setup.sh b/git-interface/test/setup.sh index cb1e250..7f3d45a 100644 --- a/git-interface/test/setup.sh +++ b/git-interface/test/setup.sh @@ -90,20 +90,23 @@ echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (2, echo "INSERT INTO SSHPubKeys (UserID, Fingerprint, PubKey) VALUES (1, '$AUTH_FINGERPRINT_USER', '$AUTH_KEYTYPE_USER $AUTH_KEYTEXT_USER');" | sqlite3 aur.db echo "INSERT INTO SSHPubKeys (UserID, Fingerprint, PubKey) VALUES (2, '$AUTH_FINGERPRINT_TU', '$AUTH_KEYTYPE_TU $AUTH_KEYTEXT_TU');" | sqlite3 aur.db -# Initialize a Git repository to store test packages in. -( - GIT_AUTHOR_EMAIL=author@example.com - GIT_AUTHOR_NAME='A U Thor' - GIT_COMMITTER_EMAIL=committer@example.com - GIT_COMMITTER_NAME='C O Mitter' - export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME - export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME +echo "INSERT INTO PackageBlacklist (Name) VALUES ('forbidden');" | sqlite3 aur.db +echo "INSERT INTO OfficialProviders (Name, Repo, Provides) VALUES ('official', 'core', 'official');" | sqlite3 aur.db + +# Initialize a Git repository and test packages. +GIT_AUTHOR_EMAIL=author@example.com +GIT_AUTHOR_NAME='A U Thor' +GIT_COMMITTER_EMAIL=committer@example.com +GIT_COMMITTER_NAME='C O Mitter' +export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME +export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME +( mkdir aur.git cd aur.git - git init -q - git checkout -q -b refs/namespaces/foobar/refs/heads/master + + git checkout -q --orphan refs/namespaces/foobar/refs/heads/master cat >PKGBUILD <<-EOF pkgname=foobar @@ -136,5 +139,48 @@ echo "INSERT INTO SSHPubKeys (UserID, Fingerprint, PubKey) VALUES (2, '$AUTH_FIN EOF git add PKGBUILD .SRCINFO - git commit -q -am 'Initial import' + git commit -q -m 'Initial import' + + sed 's/\(pkgrel.*\)1/\12/' PKGBUILD >PKGBUILD.new + sed 's/\(pkgrel.*\)1/\12/' .SRCINFO >.SRCINFO.new + mv PKGBUILD.new PKGBUILD + mv .SRCINFO.new .SRCINFO + git commit -q -am 'Bump pkgrel' + + git checkout -q --orphan refs/namespaces/foobar2/refs/heads/master + + cat >PKGBUILD <<-EOF + pkgname=foobar2 + pkgver=1 + pkgrel=1 + pkgdesc='aurweb test package.' + url='https://aur.archlinux.org/' + license=('MIT') + arch=('any') + depends=('python-pygit2') + source=() + md5sums=() + + package() { + echo 'Hello world!' + } + EOF + + cat >.SRCINFO <<-EOF + pkgbase = foobar2 + pkgdesc = aurweb test package. + pkgver = 1 + pkgrel = 1 + url = https://aur.archlinux.org/ + arch = any + license = MIT + depends = python-pygit2 + + pkgname = foobar2 + EOF + + git add PKGBUILD .SRCINFO + git commit -q -m 'Initial import' + + git checkout -q refs/namespaces/foobar/refs/heads/master ) diff --git a/git-interface/test/t0003-update.sh b/git-interface/test/t0003-update.sh index 0e8962c..810b860 100755 --- a/git-interface/test/t0003-update.sh +++ b/git-interface/test/t0003-update.sh @@ -4,17 +4,361 @@ test_description='git-update tests' . ./setup.sh -test_expect_success 'Test update hook.' ' +test_expect_success 'Setup repositories and create package bases.' ' + SSH_ORIGINAL_COMMAND="setup-repo foobar" AUR_USER=user "$GIT_SERVE" + SSH_ORIGINAL_COMMAND="setup-repo foobar2" AUR_USER=user "$GIT_SERVE" +' + +test_expect_success 'Test update hook on a fresh repository.' ' old=0000000000000000000000000000000000000000 && - new=$(git -C aur.git rev-parse HEAD) && - SSH_ORIGINAL_COMMAND="setup-repo foobar" AUR_USER=user "$GIT_SERVE" && + new=$(git -C aur.git rev-parse HEAD^) && AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 && cat >expected <<-EOF && 1|1|foobar|1-1|aurweb test package.|https://aur.archlinux.org/ + 1|GPL + 1|1 + 1|1|python-pygit2|| + 1|1 + 2|1 + EOF + >actual && + for t in Packages Licenses PackageLicenses Groups PackageGroups \ + PackageDepends PackageRelations PackageSources \ + PackageNotifications; do + echo "SELECT * FROM $t;" | sqlite3 aur.db >>actual + done && + test_cmp expected actual +' + +test_expect_success 'Test update hook on another fresh repository.' ' + old=0000000000000000000000000000000000000000 && + test_when_finished "git -C aur.git checkout refs/namespaces/foobar/refs/heads/master" && + git -C aur.git checkout -q refs/namespaces/foobar2/refs/heads/master && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar2 AUR_PRIVILEGED=0 \ + "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 && + cat >expected <<-EOF && + 1|1|foobar|1-1|aurweb test package.|https://aur.archlinux.org/ + 2|2|foobar2|1-1|aurweb test package.|https://aur.archlinux.org/ + 1|GPL + 2|MIT + 1|1 + 2|2 + 1|1|python-pygit2|| + 2|1|python-pygit2|| + 1|1 + 2|1 + EOF + >actual && + for t in Packages Licenses PackageLicenses Groups PackageGroups \ + PackageDepends PackageRelations PackageSources \ + PackageNotifications; do + echo "SELECT * FROM $t;" | sqlite3 aur.db >>actual + done && + test_cmp expected actual +' + +test_expect_success 'Test update hook on an updated repository.' ' + old=$(git -C aur.git rev-parse HEAD^) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 && + cat >expected <<-EOF && + 2|2|foobar2|1-1|aurweb test package.|https://aur.archlinux.org/ + 3|1|foobar|1-2|aurweb test package.|https://aur.archlinux.org/ + 1|GPL + 2|MIT + 2|2 + 3|1 + 2|1|python-pygit2|| + 3|1|python-pygit2|| + 1|1 + 2|1 + EOF + >actual && + for t in Packages Licenses PackageLicenses Groups PackageGroups \ + PackageDepends PackageRelations PackageSources \ + PackageNotifications; do + echo "SELECT * FROM $t;" | sqlite3 aur.db >>actual + done && + test_cmp expected actual +' + +test_expect_success 'Pushing to a branch other than master.' ' + old=0000000000000000000000000000000000000000 && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/pu "$old" "$new" 2>&1 +' + +test_expect_success 'Performing a non-fast-forward ref update.' ' + old=$(git -C aur.git rev-parse HEAD) && + new=$(git -C aur.git rev-parse HEAD^) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Performing a non-fast-forward ref update as Trusted User.' ' + old=$(git -C aur.git rev-parse HEAD) && + new=$(git -C aur.git rev-parse HEAD^) && + AUR_USER=tu AUR_PKGBASE=foobar AUR_PRIVILEGED=1 \ + "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Removing .SRCINFO.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + git -C aur.git rm -q .SRCINFO && + git -C aur.git commit -q -m "Remove .SRCINFO" && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Removing .SRCINFO with a follow-up fix.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + git -C aur.git rm -q .SRCINFO && + git -C aur.git commit -q -m "Remove .SRCINFO" && + git -C aur.git revert --no-edit HEAD && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Removing PKGBUILD.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + git -C aur.git rm -q PKGBUILD && + git -C aur.git commit -q -m "Remove PKGBUILD" && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Pushing a tree with a subdirectory.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + mkdir aur.git/subdir && + touch aur.git/subdir/file && + git -C aur.git add subdir/file && + git -C aur.git commit -q -m "Add subdirectory" && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Pushing a tree with a large blob.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + printf "%256001s" x >aur.git/file && + git -C aur.git add file && + git -C aur.git commit -q -m "Add large blob" && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Pushing .SRCINFO with a non-matching package base.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "s/\(pkgbase.*\)foobar/\1foobar2/" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Change package base" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Pushing .SRCINFO with invalid syntax.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "s/=//" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Break .SRCINFO" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Pushing .SRCINFO without pkgver.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "/pkgver/d" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Remove pkgver" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Pushing .SRCINFO without pkgrel.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "/pkgrel/d" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Remove pkgrel" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Pushing .SRCINFO with epoch.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "s/.*pkgrel.*/\\0\\nepoch = 1/" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Add epoch" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 && + cat >expected <<-EOF && + 2|2|foobar2|1-1|aurweb test package.|https://aur.archlinux.org/ + 3|1|foobar|1:1-2|aurweb test package.|https://aur.archlinux.org/ EOF echo "SELECT * FROM Packages;" | sqlite3 aur.db >actual && test_cmp expected actual ' +test_expect_success 'Pushing .SRCINFO with invalid pkgname.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "s/\(pkgname.*\)foobar/\1!/" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Change pkgname" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Pushing .SRCINFO with invalid epoch.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "s/.*pkgrel.*/\\0\\nepoch = !/" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Change epoch" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Missing install file.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "s/.*depends.*/\\0\\ninstall = install/" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Add install field" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Missing changelog file.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "s/.*depends.*/\\0\\nchangelog = changelog/" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Add changelog field" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Missing source file.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "s/.*depends.*/\\0\\nsource = file/" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Add file to the source array" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Pushing a blacklisted package.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + echo "pkgname = forbidden" >>aur.git/.SRCINFO && + git -C aur.git commit -q -am "Add blacklisted package" && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Pushing a blacklisted package as Trusted User.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + echo "pkgname = forbidden" >>aur.git/.SRCINFO && + git -C aur.git commit -q -am "Add blacklisted package" && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=tu AUR_PKGBASE=foobar AUR_PRIVILEGED=1 \ + "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 | grep ^warning: +' + +test_expect_success 'Pushing a package already in the official repositories.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + echo "pkgname = official" >>aur.git/.SRCINFO && + git -C aur.git commit -q -am "Add official package" && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Pushing a package already in the official repositories as Trusted User.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + echo "pkgname = official" >>aur.git/.SRCINFO && + git -C aur.git commit -q -am "Add official package" && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=tu AUR_PKGBASE=foobar AUR_PRIVILEGED=1 \ + "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 | grep ^warning: +' + +test_expect_success 'Trying to hijack a package.' ' + old=0000000000000000000000000000000000000000 && + test_when_finished "git -C aur.git checkout refs/namespaces/foobar/refs/heads/master" && + ( + cd aur.git && + git checkout -q refs/namespaces/foobar2/refs/heads/master && + sed "s/\\(.*pkgname.*\\)2/\\1/" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Change package name" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar2 AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + test_done -- cgit v1.2.3-54-g00ecf From 83df9808b4d533473da51c819c88214a406c5cb9 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Sat, 6 Aug 2016 01:45:15 +0200 Subject: Add tests for the restore command Test the restore mode of git-serve and git-update. Signed-off-by: Lukas Fleischer --- git-interface/test/t0002-serve.sh | 17 +++++++++++++++++ git-interface/test/t0003-update.sh | 29 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) (limited to 'git-interface') diff --git a/git-interface/test/t0002-serve.sh b/git-interface/test/t0002-serve.sh index f36f1d8..52fdcd1 100755 --- a/git-interface/test/t0002-serve.sh +++ b/git-interface/test/t0002-serve.sh @@ -85,4 +85,21 @@ test_expect_success "Try to push to someone else's repository as Trusted User." test_cmp expected actual ' +test_expect_success "Test restore." ' + echo "DELETE FROM PackageBases WHERE Name = \"foobar\";" | \ + sqlite3 aur.db && + cat >expected <<-EOF && + user + foobar + EOF + SSH_ORIGINAL_COMMAND="restore foobar" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual + test_cmp expected actual +' + +test_expect_success "Try to restore an existing package base." ' + SSH_ORIGINAL_COMMAND="restore foobar2" AUR_USER=user AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_SERVE" 2>&1 +' + test_done diff --git a/git-interface/test/t0003-update.sh b/git-interface/test/t0003-update.sh index 810b860..81c5687 100755 --- a/git-interface/test/t0003-update.sh +++ b/git-interface/test/t0003-update.sh @@ -85,6 +85,35 @@ test_expect_success 'Test update hook on an updated repository.' ' test_cmp expected actual ' +test_expect_success 'Test restore mode.' ' + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + "$GIT_UPDATE" restore 2>&1 && + cat >expected <<-EOF && + 2|2|foobar2|1-1|aurweb test package.|https://aur.archlinux.org/ + 3|1|foobar|1-2|aurweb test package.|https://aur.archlinux.org/ + 1|GPL + 2|MIT + 2|2 + 3|1 + 2|1|python-pygit2|| + 3|1|python-pygit2|| + 1|1 + 2|1 + EOF + >actual && + for t in Packages Licenses PackageLicenses Groups PackageGroups \ + PackageDepends PackageRelations PackageSources \ + PackageNotifications; do + echo "SELECT * FROM $t;" | sqlite3 aur.db >>actual + done && + test_cmp expected actual +' + +test_expect_success 'Test restore mode on a non-existent repository.' ' + AUR_USER=user AUR_PKGBASE=foobar3 AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" restore 2>&1 +' + test_expect_success 'Pushing to a branch other than master.' ' old=0000000000000000000000000000000000000000 && new=$(git -C aur.git rev-parse HEAD) && -- cgit v1.2.3-54-g00ecf From 936ee66f1e3e387d24f9bb4f5d00071c15c9f3bd Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Tue, 9 Aug 2016 14:46:08 +0200 Subject: Lazy-add new package bases Create new package bases just before saving package metadata. This protects from stray package bases left behind when new packages are rejected, e.g. when the user tries to push a package that is available from the official repositories already. Signed-off-by: Lukas Fleischer --- git-interface/git-serve.py | 5 +---- git-interface/git-update.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) (limited to 'git-interface') diff --git a/git-interface/git-serve.py b/git-interface/git-serve.py index d3a32c3..19c3ab2 100755 --- a/git-interface/git-serve.py +++ b/git-interface/git-serve.py @@ -145,10 +145,7 @@ if action == 'git-upload-pack' or action == 'git-receive-pack': if not re.match(repo_regex, pkgbase): die('{:s}: invalid repository name: {:s}'.format(action, pkgbase)) - if not pkgbase_exists(pkgbase): - create_pkgbase(pkgbase, user) - - if action == 'git-receive-pack': + if action == 'git-receive-pack' and pkgbase_exists(pkgbase): if not privileged and not pkgbase_has_write_access(pkgbase, user): die('{:s}: permission denied: {:s}'.format(action, user)) diff --git a/git-interface/git-update.py b/git-interface/git-update.py index e6f6410..40d834d 100755 --- a/git-interface/git-update.py +++ b/git-interface/git-update.py @@ -58,6 +58,25 @@ def parse_dep(depstring): return (depname, depcond) +def create_pkgbase(conn, pkgbase, user): + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) + userid = cur.fetchone()[0] + + now = int(time.time()) + cur = conn.execute("INSERT INTO PackageBases (Name, SubmittedTS, " + + "ModifiedTS, SubmitterUID, MaintainerUID) VALUES " + + "(?, ?, ?, ?, ?)", [pkgbase, now, now, userid, userid]) + pkgbase_id = cur.lastrowid + + cur = conn.execute("INSERT INTO PackageNotifications " + + "(PackageBaseID, UserID) VALUES (?, ?)", + [pkgbase_id, userid]) + + conn.commit() + + return pkgbase_id + + def save_metadata(metadata, conn, user): # Obtain package base ID and previous maintainer. pkgbase = metadata['pkgbase'] @@ -362,6 +381,10 @@ for pkgname in srcinfo.utils.get_package_names(metadata): if cur.fetchone()[0] > 0: die('cannot overwrite package: {:s}'.format(pkgname)) +# Create a new package base if it does not exist yet. +if pkgbase_id == 0: + pkgbase_id = create_pkgbase(conn, pkgbase, user) + # Store package base details in the database. save_metadata(metadata, conn, user) -- cgit v1.2.3-54-g00ecf From 435b5fc9024bff261d18456cd6628966eab8452f Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Tue, 9 Aug 2016 20:04:05 +0200 Subject: t0003: Do not initialize package bases explicitly Package bases are created by git-update automatically when the repository receives a ref update for the first time. Signed-off-by: Lukas Fleischer --- git-interface/test/t0003-update.sh | 6 ------ 1 file changed, 6 deletions(-) (limited to 'git-interface') diff --git a/git-interface/test/t0003-update.sh b/git-interface/test/t0003-update.sh index 81c5687..aeb223e 100755 --- a/git-interface/test/t0003-update.sh +++ b/git-interface/test/t0003-update.sh @@ -4,11 +4,6 @@ test_description='git-update tests' . ./setup.sh -test_expect_success 'Setup repositories and create package bases.' ' - SSH_ORIGINAL_COMMAND="setup-repo foobar" AUR_USER=user "$GIT_SERVE" - SSH_ORIGINAL_COMMAND="setup-repo foobar2" AUR_USER=user "$GIT_SERVE" -' - test_expect_success 'Test update hook on a fresh repository.' ' old=0000000000000000000000000000000000000000 && new=$(git -C aur.git rev-parse HEAD^) && @@ -20,7 +15,6 @@ test_expect_success 'Test update hook on a fresh repository.' ' 1|1 1|1|python-pygit2|| 1|1 - 2|1 EOF >actual && for t in Packages Licenses PackageLicenses Groups PackageGroups \ -- cgit v1.2.3-54-g00ecf From 2c36c17a18b0d7b54072cb85898b2795567fbf99 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Sun, 14 Aug 2016 21:56:41 +0200 Subject: db.py: Support pyformat paramstyle This is used by the MySQL database backend. Signed-off-by: Lukas Fleischer --- git-interface/db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'git-interface') diff --git a/git-interface/db.py b/git-interface/db.py index 060689b..75d2283 100644 --- a/git-interface/db.py +++ b/git-interface/db.py @@ -32,7 +32,7 @@ class Connection: raise ValueError('unsupported database backend') def execute(self, query, params=()): - if self._paramstyle == 'format': + if self._paramstyle in ('format', 'pyformat'): query = query.replace('%', '%%').replace('?', '%s') elif self._paramstyle == 'qmark': pass -- cgit v1.2.3-54-g00ecf From c7616311810ae4817fc4b0585b5ef335e6273dbb Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Mon, 22 Aug 2016 07:54:52 +0200 Subject: t0003: Make tests more strict Instead of testing the exit code only, also check the error output. This reveals two bugs. The corresponding tests are marked as known breakages. Signed-off-by: Lukas Fleischer --- git-interface/test/t0003-update.sh | 91 ++++++++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 23 deletions(-) (limited to 'git-interface') diff --git a/git-interface/test/t0003-update.sh b/git-interface/test/t0003-update.sh index aeb223e..ec830ba 100755 --- a/git-interface/test/t0003-update.sh +++ b/git-interface/test/t0003-update.sh @@ -104,22 +104,34 @@ test_expect_success 'Test restore mode.' ' ' test_expect_success 'Test restore mode on a non-existent repository.' ' + cat >expected <<-EOD && + error: restore: repository not found: foobar3 + EOD AUR_USER=user AUR_PKGBASE=foobar3 AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" restore 2>&1 + test_must_fail "$GIT_UPDATE" restore >actual 2>&1 && + test_cmp expected actual ' test_expect_success 'Pushing to a branch other than master.' ' old=0000000000000000000000000000000000000000 && new=$(git -C aur.git rev-parse HEAD) && + cat >expected <<-EOD && + error: pushing to a branch other than master is restricted + EOD AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/pu "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/pu "$old" "$new" >actual 2>&1 && + test_cmp expected actual ' test_expect_success 'Performing a non-fast-forward ref update.' ' old=$(git -C aur.git rev-parse HEAD) && new=$(git -C aur.git rev-parse HEAD^) && + cat >expected <<-EOD && + error: denying non-fast-forward (you should pull first) + EOD AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + test_cmp expected actual ' test_expect_success 'Performing a non-fast-forward ref update as Trusted User.' ' @@ -136,7 +148,8 @@ test_expect_success 'Removing .SRCINFO.' ' git -C aur.git commit -q -m "Remove .SRCINFO" && new=$(git -C aur.git rev-parse HEAD) && AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: missing .SRCINFO$" actual ' test_expect_success 'Removing .SRCINFO with a follow-up fix.' ' @@ -147,7 +160,8 @@ test_expect_success 'Removing .SRCINFO with a follow-up fix.' ' git -C aur.git revert --no-edit HEAD && new=$(git -C aur.git rev-parse HEAD) && AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: missing .SRCINFO$" actual ' test_expect_success 'Removing PKGBUILD.' ' @@ -157,7 +171,8 @@ test_expect_success 'Removing PKGBUILD.' ' git -C aur.git commit -q -m "Remove PKGBUILD" && new=$(git -C aur.git rev-parse HEAD) && AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: missing PKGBUILD$" actual ' test_expect_success 'Pushing a tree with a subdirectory.' ' @@ -169,7 +184,8 @@ test_expect_success 'Pushing a tree with a subdirectory.' ' git -C aur.git commit -q -m "Add subdirectory" && new=$(git -C aur.git rev-parse HEAD) && AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: the repository must not contain subdirectories$" actual ' test_expect_success 'Pushing a tree with a large blob.' ' @@ -180,7 +196,8 @@ test_expect_success 'Pushing a tree with a large blob.' ' git -C aur.git commit -q -m "Add large blob" && new=$(git -C aur.git rev-parse HEAD) && AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: maximum blob size (250.00KiB) exceeded$" actual ' test_expect_success 'Pushing .SRCINFO with a non-matching package base.' ' @@ -194,7 +211,8 @@ test_expect_success 'Pushing .SRCINFO with a non-matching package base.' ' ) && new=$(git -C aur.git rev-parse HEAD) && AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: invalid pkgbase: foobar2, expected foobar$" actual ' test_expect_success 'Pushing .SRCINFO with invalid syntax.' ' @@ -222,7 +240,8 @@ test_expect_success 'Pushing .SRCINFO without pkgver.' ' ) && new=$(git -C aur.git rev-parse HEAD) && AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: missing mandatory field: pkgver$" actual ' test_expect_success 'Pushing .SRCINFO without pkgrel.' ' @@ -236,7 +255,8 @@ test_expect_success 'Pushing .SRCINFO without pkgrel.' ' ) && new=$(git -C aur.git rev-parse HEAD) && AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: missing mandatory field: pkgrel$" actual ' test_expect_success 'Pushing .SRCINFO with epoch.' ' @@ -270,7 +290,8 @@ test_expect_success 'Pushing .SRCINFO with invalid pkgname.' ' ) && new=$(git -C aur.git rev-parse HEAD) && AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: invalid package name: !$" actual ' test_expect_success 'Pushing .SRCINFO with invalid epoch.' ' @@ -284,7 +305,8 @@ test_expect_success 'Pushing .SRCINFO with invalid epoch.' ' ) && new=$(git -C aur.git rev-parse HEAD) && AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: invalid epoch: !$" actual ' test_expect_success 'Missing install file.' ' @@ -298,7 +320,8 @@ test_expect_success 'Missing install file.' ' ) && new=$(git -C aur.git rev-parse HEAD) && AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: missing install file: install$" actual ' test_expect_success 'Missing changelog file.' ' @@ -312,7 +335,8 @@ test_expect_success 'Missing changelog file.' ' ) && new=$(git -C aur.git rev-parse HEAD) && AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: missing changelog file: changelog$" actual ' test_expect_success 'Missing source file.' ' @@ -326,7 +350,8 @@ test_expect_success 'Missing source file.' ' ) && new=$(git -C aur.git rev-parse HEAD) && AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: missing source file: file$" actual ' test_expect_success 'Pushing a blacklisted package.' ' @@ -335,18 +360,26 @@ test_expect_success 'Pushing a blacklisted package.' ' echo "pkgname = forbidden" >>aur.git/.SRCINFO && git -C aur.git commit -q -am "Add blacklisted package" && new=$(git -C aur.git rev-parse HEAD) && + cat >expected <<-EOD && + error: package is blacklisted: forbidden + EOD AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + test_cmp expected actual ' -test_expect_success 'Pushing a blacklisted package as Trusted User.' ' +test_expect_failure 'Pushing a blacklisted package as Trusted User.' ' old=$(git -C aur.git rev-parse HEAD) && test_when_finished "git -C aur.git reset --hard $old" && echo "pkgname = forbidden" >>aur.git/.SRCINFO && git -C aur.git commit -q -am "Add blacklisted package" && new=$(git -C aur.git rev-parse HEAD) && + cat >expected <<-EOD && + warning: package is blacklisted: forbidden + EOD AUR_USER=tu AUR_PKGBASE=foobar AUR_PRIVILEGED=1 \ - "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 | grep ^warning: + "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + test_cmp expected actual ' test_expect_success 'Pushing a package already in the official repositories.' ' @@ -355,18 +388,26 @@ test_expect_success 'Pushing a package already in the official repositories.' ' echo "pkgname = official" >>aur.git/.SRCINFO && git -C aur.git commit -q -am "Add official package" && new=$(git -C aur.git rev-parse HEAD) && + cat >expected <<-EOD && + error: package already provided by [core]: official + EOD AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + test_cmp expected actual ' -test_expect_success 'Pushing a package already in the official repositories as Trusted User.' ' +test_expect_failure 'Pushing a package already in the official repositories as Trusted User.' ' old=$(git -C aur.git rev-parse HEAD) && test_when_finished "git -C aur.git reset --hard $old" && echo "pkgname = official" >>aur.git/.SRCINFO && git -C aur.git commit -q -am "Add official package" && new=$(git -C aur.git rev-parse HEAD) && + cat >expected <<-EOD && + warning: package already provided by [core]: official + EOD AUR_USER=tu AUR_PKGBASE=foobar AUR_PRIVILEGED=1 \ - "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 | grep ^warning: + "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + test_cmp expected actual ' test_expect_success 'Trying to hijack a package.' ' @@ -380,8 +421,12 @@ test_expect_success 'Trying to hijack a package.' ' git commit -q -am "Change package name" ) && new=$(git -C aur.git rev-parse HEAD) && + cat >expected <<-EOD && + error: cannot overwrite package: foobar + EOD AUR_USER=user AUR_PKGBASE=foobar2 AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + test_cmp expected actual ' test_done -- cgit v1.2.3-54-g00ecf From 29625e074472b6546e7433e9b72a3b907abd06ff Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Mon, 22 Aug 2016 08:12:07 +0200 Subject: git-update: Close cursor before closing database When using SQLite as backend, we need to close the cursor before closing the database to avoid the following error: sqlite3.OperationalError: unable to close due to unfinalized statements or unfinished backups Signed-off-by: Lukas Fleischer --- git-interface/git-update.py | 1 + git-interface/test/t0003-update.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'git-interface') diff --git a/git-interface/git-update.py b/git-interface/git-update.py index 40d834d..50938c3 100755 --- a/git-interface/git-update.py +++ b/git-interface/git-update.py @@ -402,4 +402,5 @@ repo.create_reference('refs/namespaces/' + pkgbase + '/HEAD', sha1_new, True) update_notify(conn, user, pkgbase_id) # Close the database. +cur.close() conn.close() diff --git a/git-interface/test/t0003-update.sh b/git-interface/test/t0003-update.sh index ec830ba..4a45779 100755 --- a/git-interface/test/t0003-update.sh +++ b/git-interface/test/t0003-update.sh @@ -368,7 +368,7 @@ test_expect_success 'Pushing a blacklisted package.' ' test_cmp expected actual ' -test_expect_failure 'Pushing a blacklisted package as Trusted User.' ' +test_expect_success 'Pushing a blacklisted package as Trusted User.' ' old=$(git -C aur.git rev-parse HEAD) && test_when_finished "git -C aur.git reset --hard $old" && echo "pkgname = forbidden" >>aur.git/.SRCINFO && -- cgit v1.2.3-54-g00ecf From e3bcf83feb2dd68352c6c47aa16d64c025e1fb5b Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Mon, 22 Aug 2016 08:16:47 +0200 Subject: git-update: Do not overwrite the repo variable The repo variable is already used to store the pygit2.Repository. Fixes a regression introduced in d273ee5 (Use the official provider list to detect duplicates, 2016-05-17). Signed-off-by: Lukas Fleischer --- git-interface/git-update.py | 3 +-- git-interface/test/t0003-update.sh | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'git-interface') diff --git a/git-interface/git-update.py b/git-interface/git-update.py index 50938c3..be4c9be 100755 --- a/git-interface/git-update.py +++ b/git-interface/git-update.py @@ -373,8 +373,7 @@ for pkgname in srcinfo.utils.get_package_names(metadata): if pkgname in blacklist: warn_or_die('package is blacklisted: {:s}'.format(pkgname)) if pkgname in providers: - repo = providers[pkgname] - warn_or_die('package already provided by [{:s}]: {:s}'.format(repo, pkgname)) + warn_or_die('package already provided by [{:s}]: {:s}'.format(providers[pkgname], pkgname)) cur = conn.execute("SELECT COUNT(*) FROM Packages WHERE Name = ? AND " + "PackageBaseID <> ?", [pkgname, pkgbase_id]) diff --git a/git-interface/test/t0003-update.sh b/git-interface/test/t0003-update.sh index 4a45779..b642089 100755 --- a/git-interface/test/t0003-update.sh +++ b/git-interface/test/t0003-update.sh @@ -396,7 +396,7 @@ test_expect_success 'Pushing a package already in the official repositories.' ' test_cmp expected actual ' -test_expect_failure 'Pushing a package already in the official repositories as Trusted User.' ' +test_expect_success 'Pushing a package already in the official repositories as Trusted User.' ' old=$(git -C aur.git rev-parse HEAD) && test_when_finished "git -C aur.git reset --hard $old" && echo "pkgname = official" >>aur.git/.SRCINFO && -- cgit v1.2.3-54-g00ecf From ab228fd3c3124d4fc4d927c91245072d8a0ec32d Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Mon, 12 Sep 2016 08:44:54 +0200 Subject: git-serve: Mark setup-repo as deprecated Since 0c1187c (git-serve: Deprecate setup-repo, 2016-07-24), it is no longer recommended to use setup-repo. Mark the command as deprecated in the usage/help text. Signed-off-by: Lukas Fleischer --- git-interface/git-serve.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'git-interface') diff --git a/git-interface/git-serve.py b/git-interface/git-serve.py index 19c3ab2..38048c9 100755 --- a/git-interface/git-serve.py +++ b/git-interface/git-serve.py @@ -192,7 +192,7 @@ elif action == 'help': " list-repos List all your repositories.\n" + " restore Restore a deleted package base.\n" + " set-keywords [...] Change package base keywords.\n" + - " setup-repo Create an empty repository.\n" + + " setup-repo Create a repository (deprecated).\n" + " git-receive-pack Internal command used with Git.\n" + " git-upload-pack Internal command used with Git.") else: -- cgit v1.2.3-54-g00ecf From e0450694216e5e88df6f48aaa9f7adc91e3f23f3 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Sat, 17 Sep 2016 16:02:42 +0200 Subject: git-serve: Format usage text automatically Remove the formatting of the usage text and add code to columnize it automatically instead. Also, add more strict tests for the usage output. These new tests ensure that the usage header is printed, commands are indented properly and no overly long lines are produced. Signed-off-by: Lukas Fleischer --- git-interface/git-serve.py | 26 ++++++++++++++++++-------- git-interface/test/t0002-serve.sh | 10 +++++++++- 2 files changed, 27 insertions(+), 9 deletions(-) (limited to 'git-interface') diff --git a/git-interface/git-serve.py b/git-interface/git-serve.py index 38048c9..0187de9 100755 --- a/git-interface/git-serve.py +++ b/git-interface/git-serve.py @@ -117,6 +117,14 @@ def warn(msg): sys.stderr.write("warning: {:s}\n".format(msg)) +def usage(cmds): + sys.stderr.write("Commands:\n") + colwidth = max([len(cmd) for cmd in cmds.keys()]) + 4 + for key in sorted(cmds): + sys.stderr.write(" " + key.ljust(colwidth) + cmds[key] + "\n") + exit(0) + + user = os.environ.get('AUR_USER') privileged = (os.environ.get('AUR_PRIVILEGED', '0') == '1') ssh_cmd = os.environ.get('SSH_ORIGINAL_COMMAND') @@ -187,13 +195,15 @@ elif action == 'restore': os.environ["AUR_PKGBASE"] = pkgbase os.execl(git_update_cmd, git_update_cmd, 'restore') elif action == 'help': - die("Commands:\n" + - " help Show this help message and exit.\n" + - " list-repos List all your repositories.\n" + - " restore Restore a deleted package base.\n" + - " set-keywords [...] Change package base keywords.\n" + - " setup-repo Create a repository (deprecated).\n" + - " git-receive-pack Internal command used with Git.\n" + - " git-upload-pack Internal command used with Git.") + cmds = { + "help": "Show this help message and exit.", + "list-repos": "List all your repositories.", + "restore ": "Restore a deleted package base.", + "set-keywords [...]": "Change package base keywords.", + "setup-repo ": "Create a repository (deprecated).", + "git-receive-pack": "Internal command used with Git.", + "git-upload-pack": "Internal command used with Git.", + } + usage(cmds) else: die_with_help("invalid command: {:s}".format(action)) diff --git a/git-interface/test/t0002-serve.sh b/git-interface/test/t0002-serve.sh index 52fdcd1..ce8340e 100755 --- a/git-interface/test/t0002-serve.sh +++ b/git-interface/test/t0002-serve.sh @@ -9,7 +9,15 @@ test_expect_success 'Test interactive shell.' ' ' test_expect_success 'Test help.' ' - SSH_ORIGINAL_COMMAND=help "$GIT_SERVE" 2>&1 | grep -q "^Commands:$" + SSH_ORIGINAL_COMMAND=help "$GIT_SERVE" 2>actual && + save_IFS=$IFS + IFS= + while read -r line; do + echo $line | grep -q "^Commands:$" && continue + echo $line | grep -q "^ [a-z]" || return 1 + [ ${#line} -le 80 ] || return 1 + done Date: Mon, 12 Sep 2016 19:56:12 +0200 Subject: git-serve: Add support for adopting package bases Add support for adopting packages from the SSH interface. The syntax is `adopt `. Signed-off-by: Lukas Fleischer --- git-interface/git-serve.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) (limited to 'git-interface') diff --git a/git-interface/git-serve.py b/git-interface/git-serve.py index 0187de9..353771c 100755 --- a/git-interface/git-serve.py +++ b/git-interface/git-serve.py @@ -3,12 +3,15 @@ import os import re import shlex +import subprocess import sys import time import config import db +notify_cmd = config.get('notifications', 'notify-cmd') + repo_path = config.get('serve', 'repo-path') repo_regex = config.get('serve', 'repo-regex') git_shell_cmd = config.get('serve', 'git-shell-cmd') @@ -73,6 +76,40 @@ def create_pkgbase(pkgbase, user): conn.close() +def pkgbase_adopt(pkgbase): + pkgbase_id = pkgbase_from_name(pkgbase) + if not pkgbase_id: + die('{:s}: package base not found: {:s}'.format(action, pkgbase)) + + conn = db.Connection() + + cur = conn.execute("SELECT ID FROM PackageBases WHERE ID = ? AND " + + "MaintainerUID IS NULL", [pkgbase_id]) + if not privileged and not cur.fetchone(): + die('{:s}: permission denied: {:s}'.format(action, user)) + + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) + userid = cur.fetchone()[0] + if userid == 0: + die('{:s}: unknown user: {:s}'.format(action, user)) + + cur = conn.execute("UPDATE PackageBases SET MaintainerUID = ? " + + "WHERE ID = ?", [userid, pkgbase_id]) + + cur = conn.execute("SELECT COUNT(*) FROM PackageNotifications WHERE " + + "PackageBaseID = ? AND UserID = ?", + [pkgbase_id, userid]) + if cur.fetchone()[0] == 0: + cur = conn.execute("INSERT INTO PackageNotifications " + + "(PackageBaseID, UserID) VALUES (?, ?)", + [pkgbase_id, userid]) + conn.commit() + + subprocess.Popen((notify_cmd, 'adopt', str(pkgbase_id), str(userid))) + + conn.close() + + def pkgbase_set_keywords(pkgbase, keywords): pkgbase_id = pkgbase_from_name(pkgbase) if not pkgbase_id: @@ -194,8 +231,17 @@ elif action == 'restore': os.environ["AUR_USER"] = user os.environ["AUR_PKGBASE"] = pkgbase os.execl(git_update_cmd, git_update_cmd, 'restore') +elif action == 'adopt': + if len(cmdargv) < 2: + die_with_help("{:s}: missing repository name".format(action)) + if len(cmdargv) > 2: + die_with_help("{:s}: too many arguments".format(action)) + + pkgbase = cmdargv[1] + pkgbase_adopt(pkgbase) elif action == 'help': cmds = { + "adopt ": "Adopt a package base.", "help": "Show this help message and exit.", "list-repos": "List all your repositories.", "restore ": "Restore a deleted package base.", -- cgit v1.2.3-54-g00ecf From 9b983ac03e127160693441d6754f129a44f80870 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Tue, 13 Sep 2016 08:57:00 +0200 Subject: git-serve: Add support for disowning package bases Add support for disowning packages from the SSH interface. The syntax is `disown `. Signed-off-by: Lukas Fleischer --- git-interface/git-serve.py | 136 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) (limited to 'git-interface') diff --git a/git-interface/git-serve.py b/git-interface/git-serve.py index 353771c..9959934 100755 --- a/git-interface/git-serve.py +++ b/git-interface/git-serve.py @@ -110,6 +110,123 @@ def pkgbase_adopt(pkgbase): conn.close() +def pkgbase_get_comaintainers(pkgbase): + conn = db.Connection() + + cur = conn.execute("SELECT UserName FROM PackageComaintainers " + + "INNER JOIN Users " + + "ON Users.ID = PackageComaintainers.UsersID " + + "INNER JOIN PackageBases " + + "ON PackageBases.ID = PackageComaintainers.PackageBaseID " + + "WHERE PackageBases.Name = ? " + + "ORDER BY Priority ASC", [pkgbase]) + + return [row[0] for row in cur.fetchall()] + + +def pkgbase_set_comaintainers(pkgbase, userlist): + pkgbase_id = pkgbase_from_name(pkgbase) + if not pkgbase_id: + die('{:s}: package base not found: {:s}'.format(action, pkgbase)) + + if not privileged and not pkgbase_has_full_access(pkgbase, user): + die('{:s}: permission denied: {:s}'.format(action, user)) + + conn = db.Connection() + + userlist_old = set(pkgbase_get_comaintainers(pkgbase)) + + uids_old = set() + for olduser in userlist_old: + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", + [olduser]) + userid = cur.fetchone()[0] + if userid == 0: + die('{:s}: unknown user: {:s}'.format(action, user)) + uids_old.add(userid) + + uids_new = set() + for newuser in userlist: + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", + [newuser]) + userid = cur.fetchone()[0] + if userid == 0: + die('{:s}: unknown user: {:s}'.format(action, user)) + uids_new.add(userid) + + uids_add = uids_new - uids_old + uids_rem = uids_old - uids_new + + i = 1 + for userid in uids_new: + if userid in uids_add: + cur = conn.execute("INSERT INTO PackageComaintainers " + + "(PackageBaseID, UsersID, Priority) " + + "VALUES (?, ?, ?)", [pkgbase_id, userid, i]) + subprocess.Popen((notify_cmd, 'comaintainer-add', str(pkgbase_id), + str(userid))) + else: + cur = conn.execute("UPDATE PackageComaintainers " + + "SET Priority = ? " + + "WHERE PackageBaseID = ? AND UsersID = ?", + [i, pkgbase_id, userid]) + i += 1 + + for userid in uids_rem: + cur = conn.execute("DELETE FROM PackageComaintainers " + + "WHERE PackageBaseID = ? AND UsersID = ?", + [pkgbase_id, userid]) + subprocess.Popen((notify_cmd, 'comaintainer-remove', + str(pkgbase_id), str(userid))) + + conn.commit() + conn.close() + + +def pkgbase_disown(pkgbase): + pkgbase_id = pkgbase_from_name(pkgbase) + if not pkgbase_id: + die('{:s}: package base not found: {:s}'.format(action, pkgbase)) + + initialized_by_owner = pkgbase_has_full_access(pkgbase, user) + if not privileged and not initialized_by_owner: + die('{:s}: permission denied: {:s}'.format(action, user)) + + # TODO: Support disowning package bases via package request. + # TODO: Scan through pending orphan requests and close them. + + comaintainers = [] + new_maintainer_userid = None + + conn = db.Connection() + + # Make the first co-maintainer the new maintainer, unless the action was + # enforced by a Trusted User. + if initialized_by_owner: + comaintainers = pkgbase_get_comaintainers(pkgbase) + if len(comaintainers) > 0: + new_maintainer = comaintainers[0] + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", + [new_maintainer]) + new_maintainer_userid = cur.fetchone()[0] + comaintainers.remove(new_maintainer) + + pkgbase_set_comaintainers(pkgbase, comaintainers) + cur = conn.execute("UPDATE PackageBases SET MaintainerUID = ? " + + "WHERE ID = ?", [new_maintainer_userid, pkgbase_id]) + + conn.commit() + + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) + userid = cur.fetchone()[0] + if userid == 0: + die('{:s}: unknown user: {:s}'.format(action, user)) + + subprocess.Popen((notify_cmd, 'disown', str(pkgbase_id), str(userid))) + + conn.close() + + def pkgbase_set_keywords(pkgbase, keywords): pkgbase_id = pkgbase_from_name(pkgbase) if not pkgbase_id: @@ -141,6 +258,16 @@ def pkgbase_has_write_access(pkgbase, user): return cur.fetchone()[0] > 0 +def pkgbase_has_full_access(pkgbase, user): + conn = db.Connection() + + cur = conn.execute("SELECT COUNT(*) FROM PackageBases " + + "INNER JOIN Users " + + "ON Users.ID = PackageBases.MaintainerUID " + + "WHERE Name = ? AND Username = ?", [pkgbase, user]) + return cur.fetchone()[0] > 0 + + def die(msg): sys.stderr.write("{:s}\n".format(msg)) exit(1) @@ -239,9 +366,18 @@ elif action == 'adopt': pkgbase = cmdargv[1] pkgbase_adopt(pkgbase) +elif action == 'disown': + if len(cmdargv) < 2: + die_with_help("{:s}: missing repository name".format(action)) + if len(cmdargv) > 2: + die_with_help("{:s}: too many arguments".format(action)) + + pkgbase = cmdargv[1] + pkgbase_disown(pkgbase) elif action == 'help': cmds = { "adopt ": "Adopt a package base.", + "disown ": "Disown a package base.", "help": "Show this help message and exit.", "list-repos": "List all your repositories.", "restore ": "Restore a deleted package base.", -- cgit v1.2.3-54-g00ecf From 94ac084d9dc130d9bbee2b9abfe4ea154a6a8439 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Sat, 17 Sep 2016 20:14:35 +0200 Subject: git-serve: Add support for setting co-maintainers Add support for changing co-maintainers from the SSH interface. The syntax is `set-comaintainers ...`. Signed-off-by: Lukas Fleischer --- git-interface/git-serve.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'git-interface') diff --git a/git-interface/git-serve.py b/git-interface/git-serve.py index 9959934..47e7df9 100755 --- a/git-interface/git-serve.py +++ b/git-interface/git-serve.py @@ -374,6 +374,13 @@ elif action == 'disown': pkgbase = cmdargv[1] pkgbase_disown(pkgbase) +elif action == 'set-comaintainers': + if len(cmdargv) < 2: + die_with_help("{:s}: missing repository name".format(action)) + + pkgbase = cmdargv[1] + userlist = cmdargv[2:] + pkgbase_set_comaintainers(pkgbase, userlist) elif action == 'help': cmds = { "adopt ": "Adopt a package base.", @@ -381,6 +388,7 @@ elif action == 'help': "help": "Show this help message and exit.", "list-repos": "List all your repositories.", "restore ": "Restore a deleted package base.", + "set-comaintainers [...]": "Set package base co-maintainers.", "set-keywords [...]": "Change package base keywords.", "setup-repo ": "Create a repository (deprecated).", "git-receive-pack": "Internal command used with Git.", -- cgit v1.2.3-54-g00ecf From 5f43e2aaa9bcd233a310d4f6bf65640976457d85 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Sun, 18 Sep 2016 13:20:39 +0200 Subject: t0002: Add tests for adopt/disown/set-comaintainers Signed-off-by: Lukas Fleischer --- git-interface/test/setup.sh | 4 + git-interface/test/t0002-serve.sh | 207 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+) (limited to 'git-interface') diff --git a/git-interface/test/setup.sh b/git-interface/test/setup.sh index 7f3d45a..f9c1616 100644 --- a/git-interface/test/setup.sh +++ b/git-interface/test/setup.sh @@ -86,6 +86,10 @@ sed \ echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (1, 'user', '!', 'user@localhost', 1);" | sqlite3 aur.db echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (2, 'tu', '!', 'tu@localhost', 2);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (3, 'dev', '!', 'dev@localhost', 3);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (4, 'user2', '!', 'user2@localhost', 1);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (5, 'user3', '!', 'user3@localhost', 1);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (6, 'user4', '!', 'user4@localhost', 1);" | sqlite3 aur.db echo "INSERT INTO SSHPubKeys (UserID, Fingerprint, PubKey) VALUES (1, '$AUTH_FINGERPRINT_USER', '$AUTH_KEYTYPE_USER $AUTH_KEYTEXT_USER');" | sqlite3 aur.db echo "INSERT INTO SSHPubKeys (UserID, Fingerprint, PubKey) VALUES (2, '$AUTH_FINGERPRINT_TU', '$AUTH_KEYTYPE_TU $AUTH_KEYTEXT_TU');" | sqlite3 aur.db diff --git a/git-interface/test/t0002-serve.sh b/git-interface/test/t0002-serve.sh index ce8340e..2f1926e 100755 --- a/git-interface/test/t0002-serve.sh +++ b/git-interface/test/t0002-serve.sh @@ -110,4 +110,211 @@ test_expect_success "Try to restore an existing package base." ' test_must_fail "$GIT_SERVE" 2>&1 ' +test_expect_success "Disown all package bases." ' + SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + SSH_ORIGINAL_COMMAND="disown foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success "Adopt a package base as a regular user." ' + SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + *foobar + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success "Adopt an already adopted package base." ' + SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=user AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_SERVE" 2>&1 +' + +test_expect_success "Adopt a package base as a Trusted User." ' + SSH_ORIGINAL_COMMAND="adopt foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + *foobar2 + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success "Disown one's own package base as a regular user." ' + SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success "Disown one's own package base as a Trusted User." ' + SSH_ORIGINAL_COMMAND="disown foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success "Try to steal another user's package as a regular user." ' + SSH_ORIGINAL_COMMAND="adopt foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + SSH_ORIGINAL_COMMAND="adopt foobar2" AUR_USER=user AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + cat >expected <<-EOF && + *foobar2 + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + SSH_ORIGINAL_COMMAND="disown foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 +' + +test_expect_success "Try to steal another user's package as a Trusted User." ' + SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 && + SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + cat >expected <<-EOF && + *foobar + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 +' + +test_expect_success "Try to disown another user's package as a regular user." ' + SSH_ORIGINAL_COMMAND="adopt foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + SSH_ORIGINAL_COMMAND="disown foobar2" AUR_USER=user AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + *foobar2 + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + SSH_ORIGINAL_COMMAND="disown foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 +' + +test_expect_success "Try to disown another user's package as a Trusted User." ' + SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 && + SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 +' + +test_expect_success "Adopt a package base and add co-maintainers." ' + SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 && + SSH_ORIGINAL_COMMAND="set-comaintainers foobar user3 user4" \ + AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + 5|3|1 + 6|3|2 + EOF + echo "SELECT * FROM PackageComaintainers ORDER BY Priority;" | \ + sqlite3 aur.db >actual && + test_cmp expected actual +' + +test_expect_success "Update package base co-maintainers." ' + SSH_ORIGINAL_COMMAND="set-comaintainers foobar user2 user3 user4" \ + AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + 4|3|1 + 5|3|2 + 6|3|3 + EOF + echo "SELECT * FROM PackageComaintainers ORDER BY Priority;" | \ + sqlite3 aur.db >actual && + test_cmp expected actual +' + +test_expect_success "Try to add co-maintainers to an orphan package base." ' + SSH_ORIGINAL_COMMAND="set-comaintainers foobar2 user2 user3 user4" \ + AUR_USER=user AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + 4|3|1 + 5|3|2 + 6|3|3 + EOF + echo "SELECT * FROM PackageComaintainers ORDER BY Priority;" | \ + sqlite3 aur.db >actual && + test_cmp expected actual +' + +test_expect_success "Disown a package base and check (co-)maintainer list." ' + SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + *foobar + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user2 AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + cat >expected <<-EOF && + 5|3|1 + 6|3|2 + EOF + echo "SELECT * FROM PackageComaintainers ORDER BY Priority;" | \ + sqlite3 aur.db >actual && + test_cmp expected actual +' + +test_expect_success "Force-disown a package base and check (co-)maintainer list." ' + SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user3 AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + cat >expected <<-EOF && + EOF + echo "SELECT * FROM PackageComaintainers ORDER BY Priority;" | \ + sqlite3 aur.db >actual && + test_cmp expected actual +' + test_done -- cgit v1.2.3-54-g00ecf From 3a352435e95207fd395a9dbd19227da57f243047 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Tue, 20 Sep 2016 08:45:43 +0200 Subject: git-auth: Move entry point to a main() method Move the main program logic of git-auth to a main() method such that it can be used as a module and easily be invoked by setuptools wrapper scripts. Signed-off-by: Lukas Fleischer --- git-interface/git-auth.py | 54 +++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 25 deletions(-) (limited to 'git-interface') diff --git a/git-interface/git-auth.py b/git-interface/git-auth.py index 45fd577..d3b0188 100755 --- a/git-interface/git-auth.py +++ b/git-interface/git-auth.py @@ -23,36 +23,40 @@ def format_command(env_vars, command, ssh_opts, ssh_key): return msg -valid_keytypes = config.get('auth', 'valid-keytypes').split() -username_regex = config.get('auth', 'username-regex') -git_serve_cmd = config.get('auth', 'git-serve-cmd') -ssh_opts = config.get('auth', 'ssh-options') +def main(): + valid_keytypes = config.get('auth', 'valid-keytypes').split() + username_regex = config.get('auth', 'username-regex') + git_serve_cmd = config.get('auth', 'git-serve-cmd') + ssh_opts = config.get('auth', 'ssh-options') -keytype = sys.argv[1] -keytext = sys.argv[2] -if keytype not in valid_keytypes: - exit(1) + keytype = sys.argv[1] + keytext = sys.argv[2] + if keytype not in valid_keytypes: + exit(1) -conn = db.Connection() + conn = db.Connection() -cur = conn.execute("SELECT Users.Username, Users.AccountTypeID FROM Users " + - "INNER JOIN SSHPubKeys ON SSHPubKeys.UserID = Users.ID " - "WHERE SSHPubKeys.PubKey = ? AND Users.Suspended = 0", - (keytype + " " + keytext,)) + cur = conn.execute("SELECT Users.Username, Users.AccountTypeID FROM Users " + "INNER JOIN SSHPubKeys ON SSHPubKeys.UserID = Users.ID " + "WHERE SSHPubKeys.PubKey = ? AND Users.Suspended = 0", + (keytype + " " + keytext,)) -row = cur.fetchone() -if not row or cur.fetchone(): - exit(1) + row = cur.fetchone() + if not row or cur.fetchone(): + exit(1) -user, account_type = row -if not re.match(username_regex, user): - exit(1) + user, account_type = row + if not re.match(username_regex, user): + exit(1) + env_vars = { + 'AUR_USER': user, + 'AUR_PRIVILEGED': '1' if account_type > 1 else '0', + } + key = keytype + ' ' + keytext -env_vars = { - 'AUR_USER': user, - 'AUR_PRIVILEGED': '1' if account_type > 1 else '0', -} -key = keytype + ' ' + keytext + print(format_command(env_vars, git_serve_cmd, ssh_opts, key)) -print(format_command(env_vars, git_serve_cmd, ssh_opts, key)) + +if __name__ == '__main__': + main() -- cgit v1.2.3-54-g00ecf From b8318d25873f2294fc04bb98f14fbcbd55a4dc8a Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Tue, 20 Sep 2016 08:48:34 +0200 Subject: git-serve: Pass user and privileges as parameters Move the main program logic of git-server to a main() method such that it can be used as a module and easily be invoked by setuptools wrapper scripts. Signed-off-by: Lukas Fleischer --- git-interface/git-serve.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'git-interface') diff --git a/git-interface/git-serve.py b/git-interface/git-serve.py index 47e7df9..ddec144 100755 --- a/git-interface/git-serve.py +++ b/git-interface/git-serve.py @@ -76,7 +76,7 @@ def create_pkgbase(pkgbase, user): conn.close() -def pkgbase_adopt(pkgbase): +def pkgbase_adopt(pkgbase, user, privileged): pkgbase_id = pkgbase_from_name(pkgbase) if not pkgbase_id: die('{:s}: package base not found: {:s}'.format(action, pkgbase)) @@ -124,7 +124,7 @@ def pkgbase_get_comaintainers(pkgbase): return [row[0] for row in cur.fetchall()] -def pkgbase_set_comaintainers(pkgbase, userlist): +def pkgbase_set_comaintainers(pkgbase, userlist, user, privileged): pkgbase_id = pkgbase_from_name(pkgbase) if not pkgbase_id: die('{:s}: package base not found: {:s}'.format(action, pkgbase)) @@ -183,7 +183,7 @@ def pkgbase_set_comaintainers(pkgbase, userlist): conn.close() -def pkgbase_disown(pkgbase): +def pkgbase_disown(pkgbase, user, privileged): pkgbase_id = pkgbase_from_name(pkgbase) if not pkgbase_id: die('{:s}: package base not found: {:s}'.format(action, pkgbase)) @@ -211,7 +211,7 @@ def pkgbase_disown(pkgbase): new_maintainer_userid = cur.fetchone()[0] comaintainers.remove(new_maintainer) - pkgbase_set_comaintainers(pkgbase, comaintainers) + pkgbase_set_comaintainers(pkgbase, comaintainers, user, privileged) cur = conn.execute("UPDATE PackageBases SET MaintainerUID = ? " + "WHERE ID = ?", [new_maintainer_userid, pkgbase_id]) @@ -365,7 +365,7 @@ elif action == 'adopt': die_with_help("{:s}: too many arguments".format(action)) pkgbase = cmdargv[1] - pkgbase_adopt(pkgbase) + pkgbase_adopt(pkgbase, user, privileged) elif action == 'disown': if len(cmdargv) < 2: die_with_help("{:s}: missing repository name".format(action)) @@ -373,14 +373,14 @@ elif action == 'disown': die_with_help("{:s}: too many arguments".format(action)) pkgbase = cmdargv[1] - pkgbase_disown(pkgbase) + pkgbase_disown(pkgbase, user, privileged) elif action == 'set-comaintainers': if len(cmdargv) < 2: die_with_help("{:s}: missing repository name".format(action)) pkgbase = cmdargv[1] userlist = cmdargv[2:] - pkgbase_set_comaintainers(pkgbase, userlist) + pkgbase_set_comaintainers(pkgbase, userlist, user, privileged) elif action == 'help': cmds = { "adopt ": "Adopt a package base.", -- cgit v1.2.3-54-g00ecf From 8468b6be4bada275f1fb8836f2fd9c161c103985 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Tue, 20 Sep 2016 08:53:41 +0200 Subject: git-serve: Move entry point to a main() method Move the main program logic of git-serve to a main() method such that it can be used as a module and easily be invoked by setuptools wrapper scripts. Signed-off-by: Lukas Fleischer --- git-interface/git-serve.py | 222 +++++++++++++++++++++++---------------------- 1 file changed, 114 insertions(+), 108 deletions(-) (limited to 'git-interface') diff --git a/git-interface/git-serve.py b/git-interface/git-serve.py index ddec144..8bcecd2 100755 --- a/git-interface/git-serve.py +++ b/git-interface/git-serve.py @@ -289,111 +289,117 @@ def usage(cmds): exit(0) -user = os.environ.get('AUR_USER') -privileged = (os.environ.get('AUR_PRIVILEGED', '0') == '1') -ssh_cmd = os.environ.get('SSH_ORIGINAL_COMMAND') -ssh_client = os.environ.get('SSH_CLIENT') - -if not ssh_cmd: - die_with_help("Interactive shell is disabled.") -cmdargv = shlex.split(ssh_cmd) -action = cmdargv[0] -remote_addr = ssh_client.split(' ')[0] if ssh_client else None - -if enable_maintenance: - if remote_addr not in maintenance_exc: - die("The AUR is down due to maintenance. We will be back soon.") - -if action == 'git-upload-pack' or action == 'git-receive-pack': - if len(cmdargv) < 2: - die_with_help("{:s}: missing path".format(action)) - - path = cmdargv[1].rstrip('/') - if not path.startswith('/'): - path = '/' + path - if not path.endswith('.git'): - path = path + '.git' - pkgbase = path[1:-4] - if not re.match(repo_regex, pkgbase): - die('{:s}: invalid repository name: {:s}'.format(action, pkgbase)) - - if action == 'git-receive-pack' and pkgbase_exists(pkgbase): - if not privileged and not pkgbase_has_write_access(pkgbase, user): - die('{:s}: permission denied: {:s}'.format(action, user)) - - os.environ["AUR_USER"] = user - os.environ["AUR_PKGBASE"] = pkgbase - os.environ["GIT_NAMESPACE"] = pkgbase - cmd = action + " '" + repo_path + "'" - os.execl(git_shell_cmd, git_shell_cmd, '-c', cmd) -elif action == 'set-keywords': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) - pkgbase_set_keywords(cmdargv[1], cmdargv[2:]) -elif action == 'list-repos': - if len(cmdargv) > 1: - die_with_help("{:s}: too many arguments".format(action)) - list_repos(user) -elif action == 'setup-repo': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) - if len(cmdargv) > 2: - die_with_help("{:s}: too many arguments".format(action)) - warn('{:s} is deprecated. Use `git push` to create new repositories.'.format(action)) - create_pkgbase(cmdargv[1], user) -elif action == 'restore': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) - if len(cmdargv) > 2: - die_with_help("{:s}: too many arguments".format(action)) - - pkgbase = cmdargv[1] - if not re.match(repo_regex, pkgbase): - die('{:s}: invalid repository name: {:s}'.format(action, pkgbase)) - - if pkgbase_exists(pkgbase): - die('{:s}: package base exists: {:s}'.format(action, pkgbase)) - create_pkgbase(pkgbase, user) - - os.environ["AUR_USER"] = user - os.environ["AUR_PKGBASE"] = pkgbase - os.execl(git_update_cmd, git_update_cmd, 'restore') -elif action == 'adopt': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) - if len(cmdargv) > 2: - die_with_help("{:s}: too many arguments".format(action)) - - pkgbase = cmdargv[1] - pkgbase_adopt(pkgbase, user, privileged) -elif action == 'disown': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) - if len(cmdargv) > 2: - die_with_help("{:s}: too many arguments".format(action)) - - pkgbase = cmdargv[1] - pkgbase_disown(pkgbase, user, privileged) -elif action == 'set-comaintainers': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) - - pkgbase = cmdargv[1] - userlist = cmdargv[2:] - pkgbase_set_comaintainers(pkgbase, userlist, user, privileged) -elif action == 'help': - cmds = { - "adopt ": "Adopt a package base.", - "disown ": "Disown a package base.", - "help": "Show this help message and exit.", - "list-repos": "List all your repositories.", - "restore ": "Restore a deleted package base.", - "set-comaintainers [...]": "Set package base co-maintainers.", - "set-keywords [...]": "Change package base keywords.", - "setup-repo ": "Create a repository (deprecated).", - "git-receive-pack": "Internal command used with Git.", - "git-upload-pack": "Internal command used with Git.", - } - usage(cmds) -else: - die_with_help("invalid command: {:s}".format(action)) +def main(): + user = os.environ.get('AUR_USER') + privileged = (os.environ.get('AUR_PRIVILEGED', '0') == '1') + ssh_cmd = os.environ.get('SSH_ORIGINAL_COMMAND') + ssh_client = os.environ.get('SSH_CLIENT') + + if not ssh_cmd: + die_with_help("Interactive shell is disabled.") + cmdargv = shlex.split(ssh_cmd) + action = cmdargv[0] + remote_addr = ssh_client.split(' ')[0] if ssh_client else None + + if enable_maintenance: + if remote_addr not in maintenance_exc: + die("The AUR is down due to maintenance. We will be back soon.") + + if action == 'git-upload-pack' or action == 'git-receive-pack': + if len(cmdargv) < 2: + die_with_help("{:s}: missing path".format(action)) + + path = cmdargv[1].rstrip('/') + if not path.startswith('/'): + path = '/' + path + if not path.endswith('.git'): + path = path + '.git' + pkgbase = path[1:-4] + if not re.match(repo_regex, pkgbase): + die('{:s}: invalid repository name: {:s}'.format(action, pkgbase)) + + if action == 'git-receive-pack' and pkgbase_exists(pkgbase): + if not privileged and not pkgbase_has_write_access(pkgbase, user): + die('{:s}: permission denied: {:s}'.format(action, user)) + + os.environ["AUR_USER"] = user + os.environ["AUR_PKGBASE"] = pkgbase + os.environ["GIT_NAMESPACE"] = pkgbase + cmd = action + " '" + repo_path + "'" + os.execl(git_shell_cmd, git_shell_cmd, '-c', cmd) + elif action == 'set-keywords': + if len(cmdargv) < 2: + die_with_help("{:s}: missing repository name".format(action)) + pkgbase_set_keywords(cmdargv[1], cmdargv[2:]) + elif action == 'list-repos': + if len(cmdargv) > 1: + die_with_help("{:s}: too many arguments".format(action)) + list_repos(user) + elif action == 'setup-repo': + if len(cmdargv) < 2: + die_with_help("{:s}: missing repository name".format(action)) + if len(cmdargv) > 2: + die_with_help("{:s}: too many arguments".format(action)) + warn('{:s} is deprecated. ' + 'Use `git push` to create new repositories.'.format(action)) + create_pkgbase(cmdargv[1], user) + elif action == 'restore': + if len(cmdargv) < 2: + die_with_help("{:s}: missing repository name".format(action)) + if len(cmdargv) > 2: + die_with_help("{:s}: too many arguments".format(action)) + + pkgbase = cmdargv[1] + if not re.match(repo_regex, pkgbase): + die('{:s}: invalid repository name: {:s}'.format(action, pkgbase)) + + if pkgbase_exists(pkgbase): + die('{:s}: package base exists: {:s}'.format(action, pkgbase)) + create_pkgbase(pkgbase, user) + + os.environ["AUR_USER"] = user + os.environ["AUR_PKGBASE"] = pkgbase + os.execl(git_update_cmd, git_update_cmd, 'restore') + elif action == 'adopt': + if len(cmdargv) < 2: + die_with_help("{:s}: missing repository name".format(action)) + if len(cmdargv) > 2: + die_with_help("{:s}: too many arguments".format(action)) + + pkgbase = cmdargv[1] + pkgbase_adopt(pkgbase, user, privileged) + elif action == 'disown': + if len(cmdargv) < 2: + die_with_help("{:s}: missing repository name".format(action)) + if len(cmdargv) > 2: + die_with_help("{:s}: too many arguments".format(action)) + + pkgbase = cmdargv[1] + pkgbase_disown(pkgbase, user, privileged) + elif action == 'set-comaintainers': + if len(cmdargv) < 2: + die_with_help("{:s}: missing repository name".format(action)) + + pkgbase = cmdargv[1] + userlist = cmdargv[2:] + pkgbase_set_comaintainers(pkgbase, userlist, user, privileged) + elif action == 'help': + cmds = { + "adopt ": "Adopt a package base.", + "disown ": "Disown a package base.", + "help": "Show this help message and exit.", + "list-repos": "List all your repositories.", + "restore ": "Restore a deleted package base.", + "set-comaintainers [...]": "Set package base co-maintainers.", + "set-keywords [...]": "Change package base keywords.", + "setup-repo ": "Create a repository (deprecated).", + "git-receive-pack": "Internal command used with Git.", + "git-upload-pack": "Internal command used with Git.", + } + usage(cmds) + else: + die_with_help("invalid command: {:s}".format(action)) + + +if __name__ == '__main__': + main() -- cgit v1.2.3-54-g00ecf From 1946486a67d6085318e00c753d341ab05d12904c Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Tue, 20 Sep 2016 09:08:33 +0200 Subject: git-update: Move entry point to a main() method Move the main program logic of git-update to a main() method such that it can be used as a module and easily be invoked by setuptools wrapper scripts. Signed-off-by: Lukas Fleischer --- git-interface/git-update.py | 294 +++++++++++++++++++++++--------------------- 1 file changed, 154 insertions(+), 140 deletions(-) (limited to 'git-interface') diff --git a/git-interface/git-update.py b/git-interface/git-update.py index be4c9be..36c38ae 100755 --- a/git-interface/git-update.py +++ b/git-interface/git-update.py @@ -233,173 +233,187 @@ def die_commit(msg, commit): exit(1) -repo = pygit2.Repository(repo_path) - -user = os.environ.get("AUR_USER") -pkgbase = os.environ.get("AUR_PKGBASE") -privileged = (os.environ.get("AUR_PRIVILEGED", '0') == '1') -warn_or_die = warn if privileged else die - -if len(sys.argv) == 2 and sys.argv[1] == "restore": - if 'refs/heads/' + pkgbase not in repo.listall_references(): - die('{:s}: repository not found: {:s}'.format(sys.argv[1], pkgbase)) - refname = "refs/heads/master" - sha1_old = sha1_new = repo.lookup_reference('refs/heads/' + pkgbase).target -elif len(sys.argv) == 4: - refname, sha1_old, sha1_new = sys.argv[1:4] -else: - die("invalid arguments") - -if refname != "refs/heads/master": - die("pushing to a branch other than master is restricted") - -conn = db.Connection() - -# Detect and deny non-fast-forwards. -if sha1_old != "0000000000000000000000000000000000000000" and not privileged: - walker = repo.walk(sha1_old, pygit2.GIT_SORT_TOPOLOGICAL) - walker.hide(sha1_new) - if next(walker, None) is not None: - die("denying non-fast-forward (you should pull first)") - -# Prepare the walker that validates new commits. -walker = repo.walk(sha1_new, pygit2.GIT_SORT_TOPOLOGICAL) -if sha1_old != "0000000000000000000000000000000000000000": - walker.hide(sha1_old) - -# Validate all new commits. -for commit in walker: - for fname in ('.SRCINFO', 'PKGBUILD'): - if fname not in commit.tree: - die_commit("missing {:s}".format(fname), str(commit.id)) - - for treeobj in commit.tree: - blob = repo[treeobj.id] - - if isinstance(blob, pygit2.Tree): - die_commit("the repository must not contain subdirectories", - str(commit.id)) +def main(): + repo = pygit2.Repository(repo_path) + + user = os.environ.get("AUR_USER") + pkgbase = os.environ.get("AUR_PKGBASE") + privileged = (os.environ.get("AUR_PRIVILEGED", '0') == '1') + warn_or_die = warn if privileged else die + + if len(sys.argv) == 2 and sys.argv[1] == "restore": + if 'refs/heads/' + pkgbase not in repo.listall_references(): + die('{:s}: repository not found: {:s}'.format(sys.argv[1], + pkgbase)) + refname = "refs/heads/master" + branchref = 'refs/heads/' + pkgbase + sha1_old = sha1_new = repo.lookup_reference(branchref).target + elif len(sys.argv) == 4: + refname, sha1_old, sha1_new = sys.argv[1:4] + else: + die("invalid arguments") - if not isinstance(blob, pygit2.Blob): - die_commit("not a blob object: {:s}".format(treeobj), - str(commit.id)) + if refname != "refs/heads/master": + die("pushing to a branch other than master is restricted") - if blob.size > max_blob_size: - die_commit("maximum blob size ({:s}) exceeded".format(size_humanize(max_blob_size)), str(commit.id)) + conn = db.Connection() - metadata_raw = repo[commit.tree['.SRCINFO'].id].data.decode() - (metadata, errors) = srcinfo.parse.parse_srcinfo(metadata_raw) - if errors: - sys.stderr.write("error: The following errors occurred " - "when parsing .SRCINFO in commit\n") - sys.stderr.write("error: {:s}:\n".format(str(commit.id))) - for error in errors: - for err in error['error']: - sys.stderr.write("error: line {:d}: {:s}\n".format(error['line'], err)) - exit(1) + # Detect and deny non-fast-forwards. + if sha1_old != "0" * 40 and not privileged: + walker = repo.walk(sha1_old, pygit2.GIT_SORT_TOPOLOGICAL) + walker.hide(sha1_new) + if next(walker, None) is not None: + die("denying non-fast-forward (you should pull first)") - metadata_pkgbase = metadata['pkgbase'] - if not re.match(repo_regex, metadata_pkgbase): - die_commit('invalid pkgbase: {:s}'.format(metadata_pkgbase), - str(commit.id)) + # Prepare the walker that validates new commits. + walker = repo.walk(sha1_new, pygit2.GIT_SORT_TOPOLOGICAL) + if sha1_old != "0" * 40: + walker.hide(sha1_old) - for pkgname in set(metadata['packages'].keys()): - pkginfo = srcinfo.utils.get_merged_package(pkgname, metadata) + # Validate all new commits. + for commit in walker: + for fname in ('.SRCINFO', 'PKGBUILD'): + if fname not in commit.tree: + die_commit("missing {:s}".format(fname), str(commit.id)) - for field in ('pkgver', 'pkgrel', 'pkgname'): - if field not in pkginfo: - die_commit('missing mandatory field: {:s}'.format(field), + for treeobj in commit.tree: + blob = repo[treeobj.id] + + if isinstance(blob, pygit2.Tree): + die_commit("the repository must not contain subdirectories", str(commit.id)) - if 'epoch' in pkginfo and not pkginfo['epoch'].isdigit(): - die_commit('invalid epoch: {:s}'.format(pkginfo['epoch']), - str(commit.id)) + if not isinstance(blob, pygit2.Blob): + die_commit("not a blob object: {:s}".format(treeobj), + str(commit.id)) - if not re.match(r'[a-z0-9][a-z0-9\.+_-]*$', pkginfo['pkgname']): - die_commit('invalid package name: {:s}'.format(pkginfo['pkgname']), + if blob.size > max_blob_size: + die_commit("maximum blob size ({:s}) exceeded".format( + size_humanize(max_blob_size)), str(commit.id)) + + metadata_raw = repo[commit.tree['.SRCINFO'].id].data.decode() + (metadata, errors) = srcinfo.parse.parse_srcinfo(metadata_raw) + if errors: + sys.stderr.write("error: The following errors occurred " + "when parsing .SRCINFO in commit\n") + sys.stderr.write("error: {:s}:\n".format(str(commit.id))) + for error in errors: + for err in error['error']: + sys.stderr.write("error: line {:d}: {:s}\n".format( + error['line'], err)) + exit(1) + + metadata_pkgbase = metadata['pkgbase'] + if not re.match(repo_regex, metadata_pkgbase): + die_commit('invalid pkgbase: {:s}'.format(metadata_pkgbase), str(commit.id)) - for field in ('pkgname', 'pkgdesc', 'url'): - if field in pkginfo and len(pkginfo[field]) > 255: - die_commit('{:s} field too long: {:s}'.format(field, pkginfo[field]), - str(commit.id)) + for pkgname in set(metadata['packages'].keys()): + pkginfo = srcinfo.utils.get_merged_package(pkgname, metadata) - for field in ('install', 'changelog'): - if field in pkginfo and not pkginfo[field] in commit.tree: - die_commit('missing {:s} file: {:s}'.format(field, pkginfo[field]), - str(commit.id)) + for field in ('pkgver', 'pkgrel', 'pkgname'): + if field not in pkginfo: + die_commit('missing mandatory field: {:s}'.format(field), + str(commit.id)) - for field in extract_arch_fields(pkginfo, 'source'): - fname = field['value'] - if "://" in fname or "lp:" in fname: - continue - if fname not in commit.tree: - die_commit('missing source file: {:s}'.format(fname), + if 'epoch' in pkginfo and not pkginfo['epoch'].isdigit(): + die_commit('invalid epoch: {:s}'.format(pkginfo['epoch']), str(commit.id)) + if not re.match(r'[a-z0-9][a-z0-9\.+_-]*$', pkginfo['pkgname']): + die_commit('invalid package name: {:s}'.format( + pkginfo['pkgname']), str(commit.id)) + + for field in ('pkgname', 'pkgdesc', 'url'): + if field in pkginfo and len(pkginfo[field]) > 255: + die_commit('{:s} field too long: {:s}'.format(field, + pkginfo[field]), str(commit.id)) + + for field in ('install', 'changelog'): + if field in pkginfo and not pkginfo[field] in commit.tree: + die_commit('missing {:s} file: {:s}'.format(field, + pkginfo[field]), str(commit.id)) + + for field in extract_arch_fields(pkginfo, 'source'): + fname = field['value'] + if "://" in fname or "lp:" in fname: + continue + if fname not in commit.tree: + die_commit('missing source file: {:s}'.format(fname), + str(commit.id)) + + # Display a warning if .SRCINFO is unchanged. + if sha1_old not in ("0000000000000000000000000000000000000000", sha1_new): + srcinfo_id_old = repo[sha1_old].tree['.SRCINFO'].id + srcinfo_id_new = repo[sha1_new].tree['.SRCINFO'].id + if srcinfo_id_old == srcinfo_id_new: + warn(".SRCINFO unchanged. " + "The package database will not be updated!") + + # Read .SRCINFO from the HEAD commit. + metadata_raw = repo[repo[sha1_new].tree['.SRCINFO'].id].data.decode() + (metadata, errors) = srcinfo.parse.parse_srcinfo(metadata_raw) -# Display a warning if .SRCINFO is unchanged. -if sha1_old not in ("0000000000000000000000000000000000000000", sha1_new): - srcinfo_id_old = repo[sha1_old].tree['.SRCINFO'].id - srcinfo_id_new = repo[sha1_new].tree['.SRCINFO'].id - if srcinfo_id_old == srcinfo_id_new: - warn(".SRCINFO unchanged. The package database will not be updated!") + # Ensure that the package base name matches the repository name. + metadata_pkgbase = metadata['pkgbase'] + if metadata_pkgbase != pkgbase: + die('invalid pkgbase: {:s}, expected {:s}'.format(metadata_pkgbase, + pkgbase)) -# Read .SRCINFO from the HEAD commit. -metadata_raw = repo[repo[sha1_new].tree['.SRCINFO'].id].data.decode() -(metadata, errors) = srcinfo.parse.parse_srcinfo(metadata_raw) + # Ensure that packages are neither blacklisted nor overwritten. + pkgbase = metadata['pkgbase'] + cur = conn.execute("SELECT ID FROM PackageBases WHERE Name = ?", [pkgbase]) + row = cur.fetchone() + pkgbase_id = row[0] if row else 0 -# Ensure that the package base name matches the repository name. -metadata_pkgbase = metadata['pkgbase'] -if metadata_pkgbase != pkgbase: - die('invalid pkgbase: {:s}, expected {:s}'.format(metadata_pkgbase, pkgbase)) + cur = conn.execute("SELECT Name FROM PackageBlacklist") + blacklist = [row[0] for row in cur.fetchall()] -# Ensure that packages are neither blacklisted nor overwritten. -pkgbase = metadata['pkgbase'] -cur = conn.execute("SELECT ID FROM PackageBases WHERE Name = ?", [pkgbase]) -row = cur.fetchone() -pkgbase_id = row[0] if row else 0 + cur = conn.execute("SELECT Name, Repo FROM OfficialProviders") + providers = dict(cur.fetchall()) -cur = conn.execute("SELECT Name FROM PackageBlacklist") -blacklist = [row[0] for row in cur.fetchall()] + for pkgname in srcinfo.utils.get_package_names(metadata): + pkginfo = srcinfo.utils.get_merged_package(pkgname, metadata) + pkgname = pkginfo['pkgname'] -cur = conn.execute("SELECT Name, Repo FROM OfficialProviders") -providers = dict(cur.fetchall()) + if pkgname in blacklist: + warn_or_die('package is blacklisted: {:s}'.format(pkgname)) + if pkgname in providers: + warn_or_die('package already provided by [{:s}]: {:s}'.format( + providers[pkgname], pkgname)) -for pkgname in srcinfo.utils.get_package_names(metadata): - pkginfo = srcinfo.utils.get_merged_package(pkgname, metadata) - pkgname = pkginfo['pkgname'] + cur = conn.execute("SELECT COUNT(*) FROM Packages WHERE Name = ? " + + "AND PackageBaseID <> ?", [pkgname, pkgbase_id]) + if cur.fetchone()[0] > 0: + die('cannot overwrite package: {:s}'.format(pkgname)) - if pkgname in blacklist: - warn_or_die('package is blacklisted: {:s}'.format(pkgname)) - if pkgname in providers: - warn_or_die('package already provided by [{:s}]: {:s}'.format(providers[pkgname], pkgname)) + # Create a new package base if it does not exist yet. + if pkgbase_id == 0: + pkgbase_id = create_pkgbase(conn, pkgbase, user) - cur = conn.execute("SELECT COUNT(*) FROM Packages WHERE Name = ? AND " + - "PackageBaseID <> ?", [pkgname, pkgbase_id]) - if cur.fetchone()[0] > 0: - die('cannot overwrite package: {:s}'.format(pkgname)) + # Store package base details in the database. + save_metadata(metadata, conn, user) -# Create a new package base if it does not exist yet. -if pkgbase_id == 0: - pkgbase_id = create_pkgbase(conn, pkgbase, user) + # Create (or update) a branch with the name of the package base for better + # accessibility. + branchref = 'refs/heads/' + pkgbase + repo.create_reference(branchref, sha1_new, True) -# Store package base details in the database. -save_metadata(metadata, conn, user) + # Work around a Git bug: The HEAD ref is not updated when using + # gitnamespaces. This can be removed once the bug fix is included in Git + # mainline. See + # http://git.661346.n2.nabble.com/PATCH-receive-pack-Create-a-HEAD-ref-for-ref-namespace-td7632149.html + # for details. + headref = 'refs/namespaces/' + pkgbase + '/HEAD' + repo.create_reference(headref, sha1_new, True) -# Create (or update) a branch with the name of the package base for better -# accessibility. -repo.create_reference('refs/heads/' + pkgbase, sha1_new, True) + # Send package update notifications. + update_notify(conn, user, pkgbase_id) -# Work around a Git bug: The HEAD ref is not updated when using gitnamespaces. -# This can be removed once the bug fix is included in Git mainline. See -# http://git.661346.n2.nabble.com/PATCH-receive-pack-Create-a-HEAD-ref-for-ref-namespace-td7632149.html -# for details. -repo.create_reference('refs/namespaces/' + pkgbase + '/HEAD', sha1_new, True) + # Close the database. + cur.close() + conn.close() -# Send package update notifications. -update_notify(conn, user, pkgbase_id) -# Close the database. -cur.close() -conn.close() +if __name__ == '__main__': + main() -- cgit v1.2.3-54-g00ecf From dc3fd60715a5b17b9542ec888c6eaeb14c284e2b Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Tue, 20 Sep 2016 20:18:24 +0200 Subject: Use setuptools to install Python modules Instead of using relative imports, add support for installing the config and db Python modules to a proper location using setuptools. Change all git-interface scripts to access those modules from the search path. Signed-off-by: Lukas Fleischer --- aurweb/__init__.py | 0 aurweb/config.py | 31 +++++++++++++++++++++++++++ aurweb/db.py | 51 +++++++++++++++++++++++++++++++++++++++++++++ git-interface/__init__.py | 0 git-interface/config.py | 31 --------------------------- git-interface/db.py | 51 --------------------------------------------- git-interface/git-auth.py | 14 ++++++------- git-interface/git-serve.py | 40 +++++++++++++++++------------------ git-interface/git-update.py | 14 ++++++------- git-interface/test/setup.sh | 4 ++++ scripts/__init__.py | 0 setup.py | 20 ++++++++++++++++++ 12 files changed, 140 insertions(+), 116 deletions(-) create mode 100644 aurweb/__init__.py create mode 100644 aurweb/config.py create mode 100644 aurweb/db.py create mode 100644 git-interface/__init__.py delete mode 100644 git-interface/config.py delete mode 100644 git-interface/db.py create mode 100644 scripts/__init__.py create mode 100644 setup.py (limited to 'git-interface') diff --git a/aurweb/__init__.py b/aurweb/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aurweb/config.py b/aurweb/config.py new file mode 100644 index 0000000..aac188b --- /dev/null +++ b/aurweb/config.py @@ -0,0 +1,31 @@ +import configparser +import os + +_parser = None + + +def _get_parser(): + global _parser + + if not _parser: + _parser = configparser.RawConfigParser() + if 'AUR_CONFIG' in os.environ: + path = os.environ.get('AUR_CONFIG') + else: + relpath = "/../conf/config" + path = os.path.dirname(os.path.realpath(__file__)) + relpath + _parser.read(path) + + return _parser + + +def get(section, option): + return _get_parser().get(section, option) + + +def getboolean(section, option): + return _get_parser().getboolean(section, option) + + +def getint(section, option): + return _get_parser().getint(section, option) diff --git a/aurweb/db.py b/aurweb/db.py new file mode 100644 index 0000000..0b58197 --- /dev/null +++ b/aurweb/db.py @@ -0,0 +1,51 @@ +import mysql.connector +import sqlite3 + +import aurweb.config + + +class Connection: + _conn = None + _paramstyle = None + + def __init__(self): + aur_db_backend = aurweb.config.get('database', 'backend') + + if aur_db_backend == 'mysql': + aur_db_host = aurweb.config.get('database', 'host') + aur_db_name = aurweb.config.get('database', 'name') + aur_db_user = aurweb.config.get('database', 'user') + aur_db_pass = aurweb.config.get('database', 'password') + aur_db_socket = aurweb.config.get('database', 'socket') + self._conn = mysql.connector.connect(host=aur_db_host, + user=aur_db_user, + passwd=aur_db_pass, + db=aur_db_name, + unix_socket=aur_db_socket, + buffered=True) + self._paramstyle = mysql.connector.paramstyle + elif aur_db_backend == 'sqlite': + aur_db_name = aurweb.config.get('database', 'name') + self._conn = sqlite3.connect(aur_db_name) + self._paramstyle = sqlite3.paramstyle + else: + raise ValueError('unsupported database backend') + + def execute(self, query, params=()): + if self._paramstyle in ('format', 'pyformat'): + query = query.replace('%', '%%').replace('?', '%s') + elif self._paramstyle == 'qmark': + pass + else: + raise ValueError('unsupported paramstyle') + + cur = self._conn.cursor() + cur.execute(query, params) + + return cur + + def commit(self): + self._conn.commit() + + def close(self): + self._conn.close() diff --git a/git-interface/__init__.py b/git-interface/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/git-interface/config.py b/git-interface/config.py deleted file mode 100644 index aac188b..0000000 --- a/git-interface/config.py +++ /dev/null @@ -1,31 +0,0 @@ -import configparser -import os - -_parser = None - - -def _get_parser(): - global _parser - - if not _parser: - _parser = configparser.RawConfigParser() - if 'AUR_CONFIG' in os.environ: - path = os.environ.get('AUR_CONFIG') - else: - relpath = "/../conf/config" - path = os.path.dirname(os.path.realpath(__file__)) + relpath - _parser.read(path) - - return _parser - - -def get(section, option): - return _get_parser().get(section, option) - - -def getboolean(section, option): - return _get_parser().getboolean(section, option) - - -def getint(section, option): - return _get_parser().getint(section, option) diff --git a/git-interface/db.py b/git-interface/db.py deleted file mode 100644 index 75d2283..0000000 --- a/git-interface/db.py +++ /dev/null @@ -1,51 +0,0 @@ -import mysql.connector -import sqlite3 - -import config - - -class Connection: - _conn = None - _paramstyle = None - - def __init__(self): - aur_db_backend = config.get('database', 'backend') - - if aur_db_backend == 'mysql': - aur_db_host = config.get('database', 'host') - aur_db_name = config.get('database', 'name') - aur_db_user = config.get('database', 'user') - aur_db_pass = config.get('database', 'password') - aur_db_socket = config.get('database', 'socket') - self._conn = mysql.connector.connect(host=aur_db_host, - user=aur_db_user, - passwd=aur_db_pass, - db=aur_db_name, - unix_socket=aur_db_socket, - buffered=True) - self._paramstyle = mysql.connector.paramstyle - elif aur_db_backend == 'sqlite': - aur_db_name = config.get('database', 'name') - self._conn = sqlite3.connect(aur_db_name) - self._paramstyle = sqlite3.paramstyle - else: - raise ValueError('unsupported database backend') - - def execute(self, query, params=()): - if self._paramstyle in ('format', 'pyformat'): - query = query.replace('%', '%%').replace('?', '%s') - elif self._paramstyle == 'qmark': - pass - else: - raise ValueError('unsupported paramstyle') - - cur = self._conn.cursor() - cur.execute(query, params) - - return cur - - def commit(self): - self._conn.commit() - - def close(self): - self._conn.close() diff --git a/git-interface/git-auth.py b/git-interface/git-auth.py index d3b0188..022b0ff 100755 --- a/git-interface/git-auth.py +++ b/git-interface/git-auth.py @@ -4,8 +4,8 @@ import shlex import re import sys -import config -import db +import aurweb.config +import aurweb.db def format_command(env_vars, command, ssh_opts, ssh_key): @@ -24,17 +24,17 @@ def format_command(env_vars, command, ssh_opts, ssh_key): def main(): - valid_keytypes = config.get('auth', 'valid-keytypes').split() - username_regex = config.get('auth', 'username-regex') - git_serve_cmd = config.get('auth', 'git-serve-cmd') - ssh_opts = config.get('auth', 'ssh-options') + valid_keytypes = aurweb.config.get('auth', 'valid-keytypes').split() + username_regex = aurweb.config.get('auth', 'username-regex') + git_serve_cmd = aurweb.config.get('auth', 'git-serve-cmd') + ssh_opts = aurweb.config.get('auth', 'ssh-options') keytype = sys.argv[1] keytext = sys.argv[2] if keytype not in valid_keytypes: exit(1) - conn = db.Connection() + conn = aurweb.db.Connection() cur = conn.execute("SELECT Users.Username, Users.AccountTypeID FROM Users " "INNER JOIN SSHPubKeys ON SSHPubKeys.UserID = Users.ID " diff --git a/git-interface/git-serve.py b/git-interface/git-serve.py index 8bcecd2..5f3b26d 100755 --- a/git-interface/git-serve.py +++ b/git-interface/git-serve.py @@ -7,23 +7,23 @@ import subprocess import sys import time -import config -import db +import aurweb.config +import aurweb.db -notify_cmd = config.get('notifications', 'notify-cmd') +notify_cmd = aurweb.config.get('notifications', 'notify-cmd') -repo_path = config.get('serve', 'repo-path') -repo_regex = config.get('serve', 'repo-regex') -git_shell_cmd = config.get('serve', 'git-shell-cmd') -git_update_cmd = config.get('serve', 'git-update-cmd') -ssh_cmdline = config.get('serve', 'ssh-cmdline') +repo_path = aurweb.config.get('serve', 'repo-path') +repo_regex = aurweb.config.get('serve', 'repo-regex') +git_shell_cmd = aurweb.config.get('serve', 'git-shell-cmd') +git_update_cmd = aurweb.config.get('serve', 'git-update-cmd') +ssh_cmdline = aurweb.config.get('serve', 'ssh-cmdline') -enable_maintenance = config.getboolean('options', 'enable-maintenance') -maintenance_exc = config.get('options', 'maintenance-exceptions').split() +enable_maintenance = aurweb.config.getboolean('options', 'enable-maintenance') +maintenance_exc = aurweb.config.get('options', 'maintenance-exceptions').split() def pkgbase_from_name(pkgbase): - conn = db.Connection() + conn = aurweb.db.Connection() cur = conn.execute("SELECT ID FROM PackageBases WHERE Name = ?", [pkgbase]) row = cur.fetchone() @@ -35,7 +35,7 @@ def pkgbase_exists(pkgbase): def list_repos(user): - conn = db.Connection() + conn = aurweb.db.Connection() cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) userid = cur.fetchone()[0] @@ -55,7 +55,7 @@ def create_pkgbase(pkgbase, user): if pkgbase_exists(pkgbase): die('{:s}: package base already exists: {:s}'.format(action, pkgbase)) - conn = db.Connection() + conn = aurweb.db.Connection() cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) userid = cur.fetchone()[0] @@ -81,7 +81,7 @@ def pkgbase_adopt(pkgbase, user, privileged): if not pkgbase_id: die('{:s}: package base not found: {:s}'.format(action, pkgbase)) - conn = db.Connection() + conn = aurweb.db.Connection() cur = conn.execute("SELECT ID FROM PackageBases WHERE ID = ? AND " + "MaintainerUID IS NULL", [pkgbase_id]) @@ -111,7 +111,7 @@ def pkgbase_adopt(pkgbase, user, privileged): def pkgbase_get_comaintainers(pkgbase): - conn = db.Connection() + conn = aurweb.db.Connection() cur = conn.execute("SELECT UserName FROM PackageComaintainers " + "INNER JOIN Users " + @@ -132,7 +132,7 @@ def pkgbase_set_comaintainers(pkgbase, userlist, user, privileged): if not privileged and not pkgbase_has_full_access(pkgbase, user): die('{:s}: permission denied: {:s}'.format(action, user)) - conn = db.Connection() + conn = aurweb.db.Connection() userlist_old = set(pkgbase_get_comaintainers(pkgbase)) @@ -198,7 +198,7 @@ def pkgbase_disown(pkgbase, user, privileged): comaintainers = [] new_maintainer_userid = None - conn = db.Connection() + conn = aurweb.db.Connection() # Make the first co-maintainer the new maintainer, unless the action was # enforced by a Trusted User. @@ -232,7 +232,7 @@ def pkgbase_set_keywords(pkgbase, keywords): if not pkgbase_id: die('{:s}: package base not found: {:s}'.format(action, pkgbase)) - conn = db.Connection() + conn = aurweb.db.Connection() conn.execute("DELETE FROM PackageKeywords WHERE PackageBaseID = ?", [pkgbase_id]) @@ -245,7 +245,7 @@ def pkgbase_set_keywords(pkgbase, keywords): def pkgbase_has_write_access(pkgbase, user): - conn = db.Connection() + conn = aurweb.db.Connection() cur = conn.execute("SELECT COUNT(*) FROM PackageBases " + "LEFT JOIN PackageComaintainers " + @@ -259,7 +259,7 @@ def pkgbase_has_write_access(pkgbase, user): def pkgbase_has_full_access(pkgbase, user): - conn = db.Connection() + conn = aurweb.db.Connection() cur = conn.execute("SELECT COUNT(*) FROM PackageBases " + "INNER JOIN Users " + diff --git a/git-interface/git-update.py b/git-interface/git-update.py index 36c38ae..7337341 100755 --- a/git-interface/git-update.py +++ b/git-interface/git-update.py @@ -10,15 +10,15 @@ import time import srcinfo.parse import srcinfo.utils -import config -import db +import aurweb.config +import aurweb.db -notify_cmd = config.get('notifications', 'notify-cmd') +notify_cmd = aurweb.config.get('notifications', 'notify-cmd') -repo_path = config.get('serve', 'repo-path') -repo_regex = config.get('serve', 'repo-regex') +repo_path = aurweb.config.get('serve', 'repo-path') +repo_regex = aurweb.config.get('serve', 'repo-regex') -max_blob_size = config.getint('update', 'max-blob-size') +max_blob_size = aurweb.config.getint('update', 'max-blob-size') def size_humanize(num): @@ -256,7 +256,7 @@ def main(): if refname != "refs/heads/master": die("pushing to a branch other than master is restricted") - conn = db.Connection() + conn = aurweb.db.Connection() # Detect and deny non-fast-forwards. if sha1_old != "0" * 40 and not privileged: diff --git a/git-interface/test/setup.sh b/git-interface/test/setup.sh index f9c1616..d269af6 100644 --- a/git-interface/test/setup.sh +++ b/git-interface/test/setup.sh @@ -2,6 +2,10 @@ TEST_DIRECTORY="$(pwd)" . ./sharness.sh +# Configure python search path. +PYTHONPATH="$TEST_DIRECTORY/../../" +export PYTHONPATH + # Configure paths to the Git interface scripts. GIT_AUTH="$TEST_DIRECTORY/../git-auth.py" GIT_SERVE="$TEST_DIRECTORY/../git-serve.py" diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..48eb176 --- /dev/null +++ b/setup.py @@ -0,0 +1,20 @@ +import re +from setuptools import setup, find_packages +import sys + +version = None +with open('web/lib/version.inc.php', 'r') as f: + for line in f.readlines(): + match = re.match(r'^define\("AURWEB_VERSION", "v([0-9.]+)"\);$', line) + if match: + version = match.group(1) + +if not version: + sys.stderr.write('error: Failed to parse version file!') + sys.exit(1) + +setup( + name="aurweb", + version=version, + packages=find_packages(), +) -- cgit v1.2.3-54-g00ecf From 1a999810e3a6da21181c91f6b383f69db399cf1c Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Tue, 20 Sep 2016 20:52:34 +0200 Subject: Make test suite paths top-level directory relative Determine the top-level directory before running tests and make all script paths relative to that directory. Signed-off-by: Lukas Fleischer --- git-interface/test/setup.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'git-interface') diff --git a/git-interface/test/setup.sh b/git-interface/test/setup.sh index d269af6..0995f05 100644 --- a/git-interface/test/setup.sh +++ b/git-interface/test/setup.sh @@ -1,15 +1,16 @@ TEST_DIRECTORY="$(pwd)" +TOPLEVEL="$(cd ../.. && pwd)" . ./sharness.sh # Configure python search path. -PYTHONPATH="$TEST_DIRECTORY/../../" +PYTHONPATH="$TOPLEVEL" export PYTHONPATH # Configure paths to the Git interface scripts. -GIT_AUTH="$TEST_DIRECTORY/../git-auth.py" -GIT_SERVE="$TEST_DIRECTORY/../git-serve.py" -GIT_UPDATE="$TEST_DIRECTORY/../git-update.py" +GIT_AUTH="$TOPLEVEL/git-interface/git-auth.py" +GIT_SERVE="$TOPLEVEL/git-interface/git-serve.py" +GIT_UPDATE="$TOPLEVEL/git-interface/git-update.py" # Create the configuration file and a dummy notification script. cat >config <<-EOF @@ -86,7 +87,7 @@ sed \ -e 's/ ENGINE = InnoDB//' \ -e 's/ [A-Z]* UNSIGNED NOT NULL AUTO_INCREMENT/ INTEGER NOT NULL/' \ -e 's/([0-9, ]*) UNSIGNED / UNSIGNED /' \ - "$TEST_DIRECTORY/../../schema/aur-schema.sql" | sqlite3 aur.db + "$TOPLEVEL/schema/aur-schema.sql" | sqlite3 aur.db echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (1, 'user', '!', 'user@localhost', 1);" | sqlite3 aur.db echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (2, 'tu', '!', 'tu@localhost', 2);" | sqlite3 aur.db -- cgit v1.2.3-54-g00ecf From 49c7e53572be8be7848ff99aa5e60ffd4dc316eb Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Tue, 20 Sep 2016 20:53:44 +0200 Subject: Reorganize tests Move and rename the existing git-interface tests such that tests for other scripts can be added easily. In particular, the following changes are made: * Move the existing tests from git-interface/test/ to test/. * Rename t0001-auth.sh to t1100-git-auth.sh. * Rename t0002-serve.sh to t1200-git-serve.sh. * Rename t0003-update.sh to t1300-git-update.sh. Signed-off-by: Lukas Fleischer --- git-interface/test/Makefile | 11 - git-interface/test/setup.sh | 195 --------- git-interface/test/sharness.sh | 851 ------------------------------------- git-interface/test/t0001-auth.sh | 28 -- git-interface/test/t0002-serve.sh | 320 -------------- git-interface/test/t0003-update.sh | 432 ------------------- test/Makefile | 11 + test/setup.sh | 195 +++++++++ test/sharness.sh | 851 +++++++++++++++++++++++++++++++++++++ test/t1100-git-auth.sh | 28 ++ test/t1200-git-serve.sh | 320 ++++++++++++++ test/t1300-git-update.sh | 432 +++++++++++++++++++ 12 files changed, 1837 insertions(+), 1837 deletions(-) delete mode 100644 git-interface/test/Makefile delete mode 100644 git-interface/test/setup.sh delete mode 100644 git-interface/test/sharness.sh delete mode 100755 git-interface/test/t0001-auth.sh delete mode 100755 git-interface/test/t0002-serve.sh delete mode 100755 git-interface/test/t0003-update.sh create mode 100644 test/Makefile create mode 100644 test/setup.sh create mode 100644 test/sharness.sh create mode 100755 test/t1100-git-auth.sh create mode 100755 test/t1200-git-serve.sh create mode 100755 test/t1300-git-update.sh (limited to 'git-interface') diff --git a/git-interface/test/Makefile b/git-interface/test/Makefile deleted file mode 100644 index d6f0f74..0000000 --- a/git-interface/test/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)) - -check: $(T) - -clean: - $(RM) -r test-results/ - -$(T): - @echo "*** $@ ***"; $(SHELL) $@ - -.PHONY: check clean $(T) diff --git a/git-interface/test/setup.sh b/git-interface/test/setup.sh deleted file mode 100644 index 0995f05..0000000 --- a/git-interface/test/setup.sh +++ /dev/null @@ -1,195 +0,0 @@ -TEST_DIRECTORY="$(pwd)" -TOPLEVEL="$(cd ../.. && pwd)" - -. ./sharness.sh - -# Configure python search path. -PYTHONPATH="$TOPLEVEL" -export PYTHONPATH - -# Configure paths to the Git interface scripts. -GIT_AUTH="$TOPLEVEL/git-interface/git-auth.py" -GIT_SERVE="$TOPLEVEL/git-interface/git-serve.py" -GIT_UPDATE="$TOPLEVEL/git-interface/git-update.py" - -# Create the configuration file and a dummy notification script. -cat >config <<-EOF -[database] -backend = sqlite -name = aur.db - -[options] -enable-maintenance = 0 -maintenance-exceptions = 127.0.0.1 - -[notifications] -notify-cmd = ./notify.sh - -[auth] -valid-keytypes = ssh-rsa ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 -username-regex = [a-zA-Z0-9]+[.\-_]?[a-zA-Z0-9]+$ -git-serve-cmd = /srv/http/aurweb/git-interface/git-serve.py -ssh-options = restrict - -[serve] -repo-path = ./aur.git/ -repo-regex = [a-z0-9][a-z0-9.+_-]*$ -git-shell-cmd = ./git-shell.sh -git-update-cmd = ./update.sh -ssh-cmdline = ssh aur@aur.archlinux.org - -[update] -max-blob-size = 256000 -EOF - -cat >notify.sh <<-EOF -#!/bin/sh -EOF -chmod +x notify.sh - -cat >git-shell.sh <<-\EOF -#!/bin/sh -echo $AUR_USER -echo $AUR_PKGBASE -echo $GIT_NAMESPACE -EOF -chmod +x git-shell.sh - -cat >update.sh <<-\EOF -#!/bin/sh -echo $AUR_USER -echo $AUR_PKGBASE -EOF -chmod +x update.sh - -AUR_CONFIG=config -export AUR_CONFIG - -# Create SSH public keys which will be used by the test users later. -AUTH_KEYTYPE_USER=ssh-rsa -AUTH_KEYTEXT_USER=AAAAB3NzaC1yc2EAAAADAQABAAABAQCeUafDK4jqUiRHNQfwHcYjBKLZ4Rc1sNUofHApBP6j91nIvDHZe2VUqeBmFUhBz7kXK4VbXD9nlHMun2HeshL8hXnMzymZ8Wk7+IKefj61pajJkIdttw9Tnayfg7uhg5RbFy9zpEjmGjnIVjSzOXKCwppNT+CNujpKM5FD8gso/Z+l3fD+IwrPwS1SzF1Z99nqI9n2FM/JWZqluvTqnW9WdAvBDfutXxp0R5ZiLI5TAKL2Ssp5rpL70pkLXhv+9sK545zKKlXUFmw6Pi2iVBdqdRsk9ocl49dLiNIh8CYDCO3CRQn+8EnpBhTor2TKQxGJI3mzoBwWJJxoKhD/XlYJ -AUTH_FINGERPRINT_USER=SHA256:F/OFtYAy0JCytAGUi4RUZnOsThhQtFMK7fH1YvFBCpo - -AUTH_KEYTYPE_TU=ssh-rsa -AUTH_KEYTEXT_TU=AAAAB3NzaC1yc2EAAAADAQABAAABAQC4Q2Beg6jf2r1LZ4vwT5y10dK8+/c5RaNyTwv77wF2OSLXh32xW0ovhE2lW2gqoakdGsxgM2fTtqMTl29WOsAxlGF7x9XbWhFXFUT88Daq1fAeuihkiRjfBbInSW/WcrFZ+biLBch67addtfkkd4PmAafDeeCtszAXqza+ltBG1oxAGiTXgI3LOhA1/GtLLxsi5sPUO3ZlhvwDn4Sy0aXYx8l9hop/PU4Cjn82hyRa9r+SRxQ3KtjKxcVMnZ8IyXOrBwXTukgSBR/6nSdEmO0JPkYUFuNwh3UGFKuNkrPguL5T+4YDym6czYmZJzQ7NNl2pLKYmYgBwBe5rORlWfN5 -AUTH_FINGERPRINT_TU=SHA256:xQGC6j/U1Q3NDXLl04pm+Shr1mjYUXbGMUzlm9vby4k - -AUTH_KEYTYPE_MISSING=sha-rsa -AUTH_KEYTEXT_MISSING=AAAAB3NzaC1yc2EAAAADAQABAAABAQC9UTpssBunuTBCT3KFtv+yb+cN0VmI2C9O9U7wHlkEZWxNBK8is6tnDHXBxRuvRk0LHILkTidLLFX22ZF0+TFgSz7uuEvGZVNpa2Fn2+vKJJYMvZEvb/f8VHF5/Jddt21VOyu23royTN/duiT7WIZdCtEmq5C9Y43NPfsB8FbUc+FVSYT2Lq7g1/bzvFF+CZxwCrGjC3qC7p3pshICfFR8bbWgRN33ClxIQ7MvkcDtfNu38dLotJqdfEa7NdQgba5/S586f1A4OWKc/mQJFyTaGhRBxw/cBSjqonvO0442VYLHFxlrTHoUunKyOJ8+BJfKgjWmfENC9ESY3mL/IEn5 -AUTH_FINGERPRINT_MISSING=SHA256:uB0B+30r2WA1TDMUmFcaEBjosjnFGzn33XFhiyvTL9w - -# Initialize the test database. -rm -f aur.db -sed \ - -e '/^DROP DATABASE /d' \ - -e '/^CREATE DATABASE /d' \ - -e '/^USE /d' \ - -e 's/ ENGINE = InnoDB//' \ - -e 's/ [A-Z]* UNSIGNED NOT NULL AUTO_INCREMENT/ INTEGER NOT NULL/' \ - -e 's/([0-9, ]*) UNSIGNED / UNSIGNED /' \ - "$TOPLEVEL/schema/aur-schema.sql" | sqlite3 aur.db - -echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (1, 'user', '!', 'user@localhost', 1);" | sqlite3 aur.db -echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (2, 'tu', '!', 'tu@localhost', 2);" | sqlite3 aur.db -echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (3, 'dev', '!', 'dev@localhost', 3);" | sqlite3 aur.db -echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (4, 'user2', '!', 'user2@localhost', 1);" | sqlite3 aur.db -echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (5, 'user3', '!', 'user3@localhost', 1);" | sqlite3 aur.db -echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (6, 'user4', '!', 'user4@localhost', 1);" | sqlite3 aur.db - -echo "INSERT INTO SSHPubKeys (UserID, Fingerprint, PubKey) VALUES (1, '$AUTH_FINGERPRINT_USER', '$AUTH_KEYTYPE_USER $AUTH_KEYTEXT_USER');" | sqlite3 aur.db -echo "INSERT INTO SSHPubKeys (UserID, Fingerprint, PubKey) VALUES (2, '$AUTH_FINGERPRINT_TU', '$AUTH_KEYTYPE_TU $AUTH_KEYTEXT_TU');" | sqlite3 aur.db - -echo "INSERT INTO PackageBlacklist (Name) VALUES ('forbidden');" | sqlite3 aur.db -echo "INSERT INTO OfficialProviders (Name, Repo, Provides) VALUES ('official', 'core', 'official');" | sqlite3 aur.db - -# Initialize a Git repository and test packages. -GIT_AUTHOR_EMAIL=author@example.com -GIT_AUTHOR_NAME='A U Thor' -GIT_COMMITTER_EMAIL=committer@example.com -GIT_COMMITTER_NAME='C O Mitter' -export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME -export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME - -( - mkdir aur.git - cd aur.git - git init -q - - git checkout -q --orphan refs/namespaces/foobar/refs/heads/master - - cat >PKGBUILD <<-EOF - pkgname=foobar - pkgver=1 - pkgrel=1 - pkgdesc='aurweb test package.' - url='https://aur.archlinux.org/' - license=('GPL') - arch=('any') - depends=('python-pygit2') - source=() - md5sums=() - - package() { - echo 'Hello world!' - } - EOF - - cat >.SRCINFO <<-EOF - pkgbase = foobar - pkgdesc = aurweb test package. - pkgver = 1 - pkgrel = 1 - url = https://aur.archlinux.org/ - arch = any - license = GPL - depends = python-pygit2 - - pkgname = foobar - EOF - - git add PKGBUILD .SRCINFO - git commit -q -m 'Initial import' - - sed 's/\(pkgrel.*\)1/\12/' PKGBUILD >PKGBUILD.new - sed 's/\(pkgrel.*\)1/\12/' .SRCINFO >.SRCINFO.new - mv PKGBUILD.new PKGBUILD - mv .SRCINFO.new .SRCINFO - git commit -q -am 'Bump pkgrel' - - git checkout -q --orphan refs/namespaces/foobar2/refs/heads/master - - cat >PKGBUILD <<-EOF - pkgname=foobar2 - pkgver=1 - pkgrel=1 - pkgdesc='aurweb test package.' - url='https://aur.archlinux.org/' - license=('MIT') - arch=('any') - depends=('python-pygit2') - source=() - md5sums=() - - package() { - echo 'Hello world!' - } - EOF - - cat >.SRCINFO <<-EOF - pkgbase = foobar2 - pkgdesc = aurweb test package. - pkgver = 1 - pkgrel = 1 - url = https://aur.archlinux.org/ - arch = any - license = MIT - depends = python-pygit2 - - pkgname = foobar2 - EOF - - git add PKGBUILD .SRCINFO - git commit -q -m 'Initial import' - - git checkout -q refs/namespaces/foobar/refs/heads/master -) diff --git a/git-interface/test/sharness.sh b/git-interface/test/sharness.sh deleted file mode 100644 index 1d57ce9..0000000 --- a/git-interface/test/sharness.sh +++ /dev/null @@ -1,851 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2011-2012 Mathias Lafeldt -# Copyright (c) 2005-2012 Git project -# Copyright (c) 2005-2012 Junio C Hamano -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/ . - -# Public: Current version of Sharness. -SHARNESS_VERSION="1.0.0" -export SHARNESS_VERSION - -# Public: The file extension for tests. By default, it is set to "t". -: ${SHARNESS_TEST_EXTENSION:=t} -export SHARNESS_TEST_EXTENSION - -# Reset TERM to original terminal if found, otherwise save orignal TERM -[ "x" = "x$SHARNESS_ORIG_TERM" ] && - SHARNESS_ORIG_TERM="$TERM" || - TERM="$SHARNESS_ORIG_TERM" -# Public: The unsanitized TERM under which sharness is originally run -export SHARNESS_ORIG_TERM - -# Export SHELL_PATH -: ${SHELL_PATH:=$SHELL} -export SHELL_PATH - -# For repeatability, reset the environment to a known state. -# TERM is sanitized below, after saving color control sequences. -LANG=C -LC_ALL=C -PAGER=cat -TZ=UTC -EDITOR=: -export LANG LC_ALL PAGER TZ EDITOR -unset VISUAL CDPATH GREP_OPTIONS - -# Line feed -LF=' -' - -[ "x$TERM" != "xdumb" ] && ( - [ -t 1 ] && - tput bold >/dev/null 2>&1 && - tput setaf 1 >/dev/null 2>&1 && - tput sgr0 >/dev/null 2>&1 - ) && - color=t - -while test "$#" -ne 0; do - case "$1" in - -d|--d|--de|--deb|--debu|--debug) - debug=t; shift ;; - -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) - immediate=t; shift ;; - -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) - TEST_LONG=t; export TEST_LONG; shift ;; - --in|--int|--inte|--inter|--intera|--interac|--interact|--interacti|--interactiv|--interactive|--interactive-|--interactive-t|--interactive-te|--interactive-tes|--interactive-test|--interactive-tests): - TEST_INTERACTIVE=t; export TEST_INTERACTIVE; verbose=t; shift ;; - -h|--h|--he|--hel|--help) - help=t; shift ;; - -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) - verbose=t; shift ;; - -q|--q|--qu|--qui|--quie|--quiet) - # Ignore --quiet under a TAP::Harness. Saying how many tests - # passed without the ok/not ok details is always an error. - test -z "$HARNESS_ACTIVE" && quiet=t; shift ;; - --chain-lint) - chain_lint=t; shift ;; - --no-chain-lint) - chain_lint=; shift ;; - --no-color) - color=; shift ;; - --root=*) - root=$(expr "z$1" : 'z[^=]*=\(.*\)') - shift ;; - *) - echo "error: unknown test option '$1'" >&2; exit 1 ;; - esac -done - -if test -n "$color"; then - # Save the color control sequences now rather than run tput - # each time say_color() is called. This is done for two - # reasons: - # * TERM will be changed to dumb - # * HOME will be changed to a temporary directory and tput - # might need to read ~/.terminfo from the original HOME - # directory to get the control sequences - # Note: This approach assumes the control sequences don't end - # in a newline for any terminal of interest (command - # substitutions strip trailing newlines). Given that most - # (all?) terminals in common use are related to ECMA-48, this - # shouldn't be a problem. - say_color_error=$(tput bold; tput setaf 1) # bold red - say_color_skip=$(tput setaf 4) # blue - say_color_warn=$(tput setaf 3) # brown/yellow - say_color_pass=$(tput setaf 2) # green - say_color_info=$(tput setaf 6) # cyan - say_color_reset=$(tput sgr0) - say_color_="" # no formatting for normal text - say_color() { - test -z "$1" && test -n "$quiet" && return - eval "say_color_color=\$say_color_$1" - shift - printf "%s\\n" "$say_color_color$*$say_color_reset" - } -else - say_color() { - test -z "$1" && test -n "$quiet" && return - shift - printf "%s\n" "$*" - } -fi - -TERM=dumb -export TERM - -error() { - say_color error "error: $*" - EXIT_OK=t - exit 1 -} - -say() { - say_color info "$*" -} - -test -n "$test_description" || error "Test script did not set test_description." - -if test "$help" = "t"; then - echo "$test_description" - exit 0 -fi - -exec 5>&1 -exec 6<&0 -if test "$verbose" = "t"; then - exec 4>&2 3>&1 -else - exec 4>/dev/null 3>/dev/null -fi - -test_failure=0 -test_count=0 -test_fixed=0 -test_broken=0 -test_success=0 - -die() { - code=$? - if test -n "$EXIT_OK"; then - exit $code - else - echo >&5 "FATAL: Unexpected exit with code $code" - exit 1 - fi -} - -EXIT_OK= -trap 'die' EXIT - -# Public: Define that a test prerequisite is available. -# -# The prerequisite can later be checked explicitly using test_have_prereq or -# implicitly by specifying the prerequisite name in calls to test_expect_success -# or test_expect_failure. -# -# $1 - Name of prerequiste (a simple word, in all capital letters by convention) -# -# Examples -# -# # Set PYTHON prerequisite if interpreter is available. -# command -v python >/dev/null && test_set_prereq PYTHON -# -# # Set prerequisite depending on some variable. -# test -z "$NO_GETTEXT" && test_set_prereq GETTEXT -# -# Returns nothing. -test_set_prereq() { - satisfied_prereq="$satisfied_prereq$1 " -} -satisfied_prereq=" " - -# Public: Check if one or more test prerequisites are defined. -# -# The prerequisites must have previously been set with test_set_prereq. -# The most common use of this is to skip all the tests if some essential -# prerequisite is missing. -# -# $1 - Comma-separated list of test prerequisites. -# -# Examples -# -# # Skip all remaining tests if prerequisite is not set. -# if ! test_have_prereq PERL; then -# skip_all='skipping perl interface tests, perl not available' -# test_done -# fi -# -# Returns 0 if all prerequisites are defined or 1 otherwise. -test_have_prereq() { - # prerequisites can be concatenated with ',' - save_IFS=$IFS - IFS=, - set -- $* - IFS=$save_IFS - - total_prereq=0 - ok_prereq=0 - missing_prereq= - - for prerequisite; do - case "$prerequisite" in - !*) - negative_prereq=t - prerequisite=${prerequisite#!} - ;; - *) - negative_prereq= - esac - - total_prereq=$(($total_prereq + 1)) - case "$satisfied_prereq" in - *" $prerequisite "*) - satisfied_this_prereq=t - ;; - *) - satisfied_this_prereq= - esac - - case "$satisfied_this_prereq,$negative_prereq" in - t,|,t) - ok_prereq=$(($ok_prereq + 1)) - ;; - *) - # Keep a list of missing prerequisites; restore - # the negative marker if necessary. - prerequisite=${negative_prereq:+!}$prerequisite - if test -z "$missing_prereq"; then - missing_prereq=$prerequisite - else - missing_prereq="$prerequisite,$missing_prereq" - fi - esac - done - - test $total_prereq = $ok_prereq -} - -# You are not expected to call test_ok_ and test_failure_ directly, use -# the text_expect_* functions instead. - -test_ok_() { - test_success=$(($test_success + 1)) - say_color "" "ok $test_count - $@" -} - -test_failure_() { - test_failure=$(($test_failure + 1)) - say_color error "not ok $test_count - $1" - shift - echo "$@" | sed -e 's/^/# /' - test "$immediate" = "" || { EXIT_OK=t; exit 1; } -} - -test_known_broken_ok_() { - test_fixed=$(($test_fixed + 1)) - say_color error "ok $test_count - $@ # TODO known breakage vanished" -} - -test_known_broken_failure_() { - test_broken=$(($test_broken + 1)) - say_color warn "not ok $test_count - $@ # TODO known breakage" -} - -# Public: Execute commands in debug mode. -# -# Takes a single argument and evaluates it only when the test script is started -# with --debug. This is primarily meant for use during the development of test -# scripts. -# -# $1 - Commands to be executed. -# -# Examples -# -# test_debug "cat some_log_file" -# -# Returns the exit code of the last command executed in debug mode or 0 -# otherwise. -test_debug() { - test "$debug" = "" || eval "$1" -} - -# Public: Stop execution and start a shell. -# -# This is useful for debugging tests and only makes sense together with "-v". -# Be sure to remove all invocations of this command before submitting. -test_pause() { - if test "$verbose" = t; then - "$SHELL_PATH" <&6 >&3 2>&4 - else - error >&5 "test_pause requires --verbose" - fi -} - -test_eval_() { - # This is a separate function because some tests use - # "return" to end a test_expect_success block early. - case ",$test_prereq," in - *,INTERACTIVE,*) - eval "$*" - ;; - *) - eval &3 2>&4 "$*" - ;; - esac -} - -test_run_() { - test_cleanup=: - expecting_failure=$2 - test_eval_ "$1" - eval_ret=$? - - if test "$chain_lint" = "t"; then - test_eval_ "(exit 117) && $1" - if test "$?" != 117; then - error "bug in the test script: broken &&-chain: $1" - fi - fi - - if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure"; then - test_eval_ "$test_cleanup" - fi - if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then - echo "" - fi - return "$eval_ret" -} - -test_skip_() { - test_count=$(($test_count + 1)) - to_skip= - for skp in $SKIP_TESTS; do - case $this_test.$test_count in - $skp) - to_skip=t - break - esac - done - if test -z "$to_skip" && test -n "$test_prereq" && ! test_have_prereq "$test_prereq"; then - to_skip=t - fi - case "$to_skip" in - t) - of_prereq= - if test "$missing_prereq" != "$test_prereq"; then - of_prereq=" of $test_prereq" - fi - - say_color skip >&3 "skipping test: $@" - say_color skip "ok $test_count # skip $1 (missing $missing_prereq${of_prereq})" - : true - ;; - *) - false - ;; - esac -} - -# Public: Run test commands and expect them to succeed. -# -# When the test passed, an "ok" message is printed and the number of successful -# tests is incremented. When it failed, a "not ok" message is printed and the -# number of failed tests is incremented. -# -# With --immediate, exit test immediately upon the first failed test. -# -# Usually takes two arguments: -# $1 - Test description -# $2 - Commands to be executed. -# -# With three arguments, the first will be taken to be a prerequisite: -# $1 - Comma-separated list of test prerequisites. The test will be skipped if -# not all of the given prerequisites are set. To negate a prerequisite, -# put a "!" in front of it. -# $2 - Test description -# $3 - Commands to be executed. -# -# Examples -# -# test_expect_success \ -# 'git-write-tree should be able to write an empty tree.' \ -# 'tree=$(git-write-tree)' -# -# # Test depending on one prerequisite. -# test_expect_success TTY 'git --paginate rev-list uses a pager' \ -# ' ... ' -# -# # Multiple prerequisites are separated by a comma. -# test_expect_success PERL,PYTHON 'yo dawg' \ -# ' test $(perl -E 'print eval "1 +" . qx[python -c "print 2"]') == "4" ' -# -# Returns nothing. -test_expect_success() { - test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= - test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test_expect_success" - export test_prereq - if ! test_skip_ "$@"; then - say >&3 "expecting success: $2" - if test_run_ "$2"; then - test_ok_ "$1" - else - test_failure_ "$@" - fi - fi - echo >&3 "" -} - -# Public: Run test commands and expect them to fail. Used to demonstrate a known -# breakage. -# -# This is NOT the opposite of test_expect_success, but rather used to mark a -# test that demonstrates a known breakage. -# -# When the test passed, an "ok" message is printed and the number of fixed tests -# is incremented. When it failed, a "not ok" message is printed and the number -# of tests still broken is incremented. -# -# Failures from these tests won't cause --immediate to stop. -# -# Usually takes two arguments: -# $1 - Test description -# $2 - Commands to be executed. -# -# With three arguments, the first will be taken to be a prerequisite: -# $1 - Comma-separated list of test prerequisites. The test will be skipped if -# not all of the given prerequisites are set. To negate a prerequisite, -# put a "!" in front of it. -# $2 - Test description -# $3 - Commands to be executed. -# -# Returns nothing. -test_expect_failure() { - test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= - test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test_expect_failure" - export test_prereq - if ! test_skip_ "$@"; then - say >&3 "checking known breakage: $2" - if test_run_ "$2" expecting_failure; then - test_known_broken_ok_ "$1" - else - test_known_broken_failure_ "$1" - fi - fi - echo >&3 "" -} - -# Public: Run command and ensure that it fails in a controlled way. -# -# Use it instead of "! ". For example, when dies due to a -# segfault, test_must_fail diagnoses it as an error, while "! " would -# mistakenly be treated as just another expected failure. -# -# This is one of the prefix functions to be used inside test_expect_success or -# test_expect_failure. -# -# $1.. - Command to be executed. -# -# Examples -# -# test_expect_success 'complain and die' ' -# do something && -# do something else && -# test_must_fail git checkout ../outerspace -# ' -# -# Returns 1 if the command succeeded (exit code 0). -# Returns 1 if the command died by signal (exit codes 130-192) -# Returns 1 if the command could not be found (exit code 127). -# Returns 0 otherwise. -test_must_fail() { - "$@" - exit_code=$? - if test $exit_code = 0; then - echo >&2 "test_must_fail: command succeeded: $*" - return 1 - elif test $exit_code -gt 129 -a $exit_code -le 192; then - echo >&2 "test_must_fail: died by signal: $*" - return 1 - elif test $exit_code = 127; then - echo >&2 "test_must_fail: command not found: $*" - return 1 - fi - return 0 -} - -# Public: Run command and ensure that it succeeds or fails in a controlled way. -# -# Similar to test_must_fail, but tolerates success too. Use it instead of -# " || :" to catch failures caused by a segfault, for instance. -# -# This is one of the prefix functions to be used inside test_expect_success or -# test_expect_failure. -# -# $1.. - Command to be executed. -# -# Examples -# -# test_expect_success 'some command works without configuration' ' -# test_might_fail git config --unset all.configuration && -# do something -# ' -# -# Returns 1 if the command died by signal (exit codes 130-192) -# Returns 1 if the command could not be found (exit code 127). -# Returns 0 otherwise. -test_might_fail() { - "$@" - exit_code=$? - if test $exit_code -gt 129 -a $exit_code -le 192; then - echo >&2 "test_might_fail: died by signal: $*" - return 1 - elif test $exit_code = 127; then - echo >&2 "test_might_fail: command not found: $*" - return 1 - fi - return 0 -} - -# Public: Run command and ensure it exits with a given exit code. -# -# This is one of the prefix functions to be used inside test_expect_success or -# test_expect_failure. -# -# $1 - Expected exit code. -# $2.. - Command to be executed. -# -# Examples -# -# test_expect_success 'Merge with d/f conflicts' ' -# test_expect_code 1 git merge "merge msg" B master -# ' -# -# Returns 0 if the expected exit code is returned or 1 otherwise. -test_expect_code() { - want_code=$1 - shift - "$@" - exit_code=$? - if test $exit_code = $want_code; then - return 0 - fi - - echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*" - return 1 -} - -# Public: Compare two files to see if expected output matches actual output. -# -# The TEST_CMP variable defines the command used for the comparision; it -# defaults to "diff -u". Only when the test script was started with --verbose, -# will the command's output, the diff, be printed to the standard output. -# -# This is one of the prefix functions to be used inside test_expect_success or -# test_expect_failure. -# -# $1 - Path to file with expected output. -# $2 - Path to file with actual output. -# -# Examples -# -# test_expect_success 'foo works' ' -# echo expected >expected && -# foo >actual && -# test_cmp expected actual -# ' -# -# Returns the exit code of the command set by TEST_CMP. -test_cmp() { - ${TEST_CMP:-diff -u} "$@" -} - -# Public: portably print a sequence of numbers. -# -# seq is not in POSIX and GNU seq might not be available everywhere, -# so it is nice to have a seq implementation, even a very simple one. -# -# $1 - Starting number. -# $2 - Ending number. -# -# Examples -# -# test_expect_success 'foo works 10 times' ' -# for i in $(test_seq 1 10) -# do -# foo || return -# done -# ' -# -# Returns 0 if all the specified numbers can be displayed. -test_seq() { - i="$1" - j="$2" - while test "$i" -le "$j" - do - echo "$i" || return - i=$(expr "$i" + 1) - done -} - -# Public: Check if the file expected to be empty is indeed empty, and barfs -# otherwise. -# -# $1 - File to check for emptyness. -# -# Returns 0 if file is empty, 1 otherwise. -test_must_be_empty() { - if test -s "$1" - then - echo "'$1' is not empty, it contains:" - cat "$1" - return 1 - fi -} - -# Public: Schedule cleanup commands to be run unconditionally at the end of a -# test. -# -# If some cleanup command fails, the test will not pass. With --immediate, no -# cleanup is done to help diagnose what went wrong. -# -# This is one of the prefix functions to be used inside test_expect_success or -# test_expect_failure. -# -# $1.. - Commands to prepend to the list of cleanup commands. -# -# Examples -# -# test_expect_success 'test core.capslock' ' -# git config core.capslock true && -# test_when_finished "git config --unset core.capslock" && -# do_something -# ' -# -# Returns the exit code of the last cleanup command executed. -test_when_finished() { - test_cleanup="{ $* - } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup" -} - -# Public: Schedule cleanup commands to be run unconditionally when all tests -# have run. -# -# This can be used to clean up things like test databases. It is not needed to -# clean up temporary files, as test_done already does that. -# -# Examples: -# -# cleanup mysql -e "DROP DATABASE mytest" -# -# Returns the exit code of the last cleanup command executed. -final_cleanup= -cleanup() { - final_cleanup="{ $* - } && (exit \"\$eval_ret\"); eval_ret=\$?; $final_cleanup" -} - -# Public: Summarize test results and exit with an appropriate error code. -# -# Must be called at the end of each test script. -# -# Can also be used to stop tests early and skip all remaining tests. For this, -# set skip_all to a string explaining why the tests were skipped before calling -# test_done. -# -# Examples -# -# # Each test script must call test_done at the end. -# test_done -# -# # Skip all remaining tests if prerequisite is not set. -# if ! test_have_prereq PERL; then -# skip_all='skipping perl interface tests, perl not available' -# test_done -# fi -# -# Returns 0 if all tests passed or 1 if there was a failure. -test_done() { - EXIT_OK=t - - if test -z "$HARNESS_ACTIVE"; then - test_results_dir="$SHARNESS_TEST_DIRECTORY/test-results" - mkdir -p "$test_results_dir" - test_results_path="$test_results_dir/$this_test.$$.counts" - - cat >>"$test_results_path" <<-EOF - total $test_count - success $test_success - fixed $test_fixed - broken $test_broken - failed $test_failure - - EOF - fi - - if test "$test_fixed" != 0; then - say_color error "# $test_fixed known breakage(s) vanished; please update test(s)" - fi - if test "$test_broken" != 0; then - say_color warn "# still have $test_broken known breakage(s)" - fi - if test "$test_broken" != 0 || test "$test_fixed" != 0; then - test_remaining=$(( $test_count - $test_broken - $test_fixed )) - msg="remaining $test_remaining test(s)" - else - test_remaining=$test_count - msg="$test_count test(s)" - fi - - case "$test_failure" in - 0) - # Maybe print SKIP message - if test -n "$skip_all" && test $test_count -gt 0; then - error "Can't use skip_all after running some tests" - fi - [ -z "$skip_all" ] || skip_all=" # SKIP $skip_all" - - if test $test_remaining -gt 0; then - say_color pass "# passed all $msg" - fi - say "1..$test_count$skip_all" - - test_eval_ "$final_cleanup" - - test -d "$remove_trash" && - cd "$(dirname "$remove_trash")" && - rm -rf "$(basename "$remove_trash")" - - exit 0 ;; - - *) - say_color error "# failed $test_failure among $msg" - say "1..$test_count" - - exit 1 ;; - - esac -} - -# Public: Root directory containing tests. Tests can override this variable, -# e.g. for testing Sharness itself. -: ${SHARNESS_TEST_DIRECTORY:=$(pwd)} -export SHARNESS_TEST_DIRECTORY - -# Public: Source directory of test code and sharness library. -# This directory may be different from the directory in which tests are -# being run. -: ${SHARNESS_TEST_SRCDIR:=$(cd $(dirname $0) && pwd)} -export SHARNESS_TEST_SRCDIR - -# Public: Build directory that will be added to PATH. By default, it is set to -# the parent directory of SHARNESS_TEST_DIRECTORY. -: ${SHARNESS_BUILD_DIRECTORY:="$SHARNESS_TEST_DIRECTORY/.."} -PATH="$SHARNESS_BUILD_DIRECTORY:$PATH" -export PATH SHARNESS_BUILD_DIRECTORY - -# Public: Path to test script currently executed. -SHARNESS_TEST_FILE="$0" -export SHARNESS_TEST_FILE - -# Prepare test area. -SHARNESS_TRASH_DIRECTORY="trash directory.$(basename "$SHARNESS_TEST_FILE" ".$SHARNESS_TEST_EXTENSION")" -test -n "$root" && SHARNESS_TRASH_DIRECTORY="$root/$SHARNESS_TRASH_DIRECTORY" -case "$SHARNESS_TRASH_DIRECTORY" in -/*) ;; # absolute path is good - *) SHARNESS_TRASH_DIRECTORY="$SHARNESS_TEST_DIRECTORY/$SHARNESS_TRASH_DIRECTORY" ;; -esac -test "$debug" = "t" || remove_trash="$SHARNESS_TRASH_DIRECTORY" -rm -rf "$SHARNESS_TRASH_DIRECTORY" || { - EXIT_OK=t - echo >&5 "FATAL: Cannot prepare test area" - exit 1 -} - - -# -# Load any extensions in $srcdir/sharness.d/*.sh -# -if test -d "${SHARNESS_TEST_SRCDIR}/sharness.d" -then - for file in "${SHARNESS_TEST_SRCDIR}"/sharness.d/*.sh - do - # Ensure glob was not an empty match: - test -e "${file}" || break - - if test -n "$debug" - then - echo >&5 "sharness: loading extensions from ${file}" - fi - . "${file}" - if test $? != 0 - then - echo >&5 "sharness: Error loading ${file}. Aborting." - exit 1 - fi - done -fi - -# Public: Empty trash directory, the test area, provided for each test. The HOME -# variable is set to that directory too. -export SHARNESS_TRASH_DIRECTORY - -HOME="$SHARNESS_TRASH_DIRECTORY" -export HOME - -mkdir -p "$SHARNESS_TRASH_DIRECTORY" || exit 1 -# Use -P to resolve symlinks in our working directory so that the cwd -# in subprocesses like git equals our $PWD (for pathname comparisons). -cd -P "$SHARNESS_TRASH_DIRECTORY" || exit 1 - -this_test=${SHARNESS_TEST_FILE##*/} -this_test=${this_test%.$SHARNESS_TEST_EXTENSION} -for skp in $SKIP_TESTS; do - case "$this_test" in - $skp) - say_color info >&3 "skipping test $this_test altogether" - skip_all="skip all tests in $this_test" - test_done - esac -done - -test -n "$TEST_LONG" && test_set_prereq EXPENSIVE -test -n "$TEST_INTERACTIVE" && test_set_prereq INTERACTIVE - -# Make sure this script ends with code 0 -: - -# vi: set ts=4 sw=4 noet : diff --git a/git-interface/test/t0001-auth.sh b/git-interface/test/t0001-auth.sh deleted file mode 100755 index 71d526f..0000000 --- a/git-interface/test/t0001-auth.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh - -test_description='git-auth tests' - -. ./setup.sh - -test_expect_success 'Test basic authentication.' ' - "$GIT_AUTH" "$AUTH_KEYTYPE_USER" "$AUTH_KEYTEXT_USER" >out && - grep -q AUR_USER=user out && - grep -q AUR_PRIVILEGED=0 out -' - -test_expect_success 'Test Trusted User authentication.' ' - "$GIT_AUTH" "$AUTH_KEYTYPE_TU" "$AUTH_KEYTEXT_TU" >out && - grep -q AUR_USER=tu out && - grep -q AUR_PRIVILEGED=1 out -' - -test_expect_success 'Test authentication with an unsupported key type.' ' - test_must_fail "$GIT_AUTH" ssh-xxx "$AUTH_KEYTEXT_USER" -' - -test_expect_success 'Test authentication with a wrong key.' ' - "$GIT_AUTH" "$AUTH_KEYTYPE_MISSING" "$AUTH_KEYTEXT_MISSING" >out - test_must_be_empty out -' - -test_done diff --git a/git-interface/test/t0002-serve.sh b/git-interface/test/t0002-serve.sh deleted file mode 100755 index 2f1926e..0000000 --- a/git-interface/test/t0002-serve.sh +++ /dev/null @@ -1,320 +0,0 @@ -#!/bin/sh - -test_description='git-serve tests' - -. ./setup.sh - -test_expect_success 'Test interactive shell.' ' - "$GIT_SERVE" 2>&1 | grep -q "Interactive shell is disabled." -' - -test_expect_success 'Test help.' ' - SSH_ORIGINAL_COMMAND=help "$GIT_SERVE" 2>actual && - save_IFS=$IFS - IFS= - while read -r line; do - echo $line | grep -q "^Commands:$" && continue - echo $line | grep -q "^ [a-z]" || return 1 - [ ${#line} -le 80 ] || return 1 - done &1 && - SSH_ORIGINAL_COMMAND="setup-repo foobar2" AUR_USER=tu \ - "$GIT_SERVE" 2>&1 && - cat >expected <<-EOF && - *foobar - EOF - SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual -' - -test_expect_success 'Test git-receive-pack.' ' - cat >expected <<-EOF && - user - foobar - foobar - EOF - SSH_ORIGINAL_COMMAND="git-receive-pack /foobar.git/" \ - AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual -' - -test_expect_success 'Test git-receive-pack with an invalid repository name.' ' - SSH_ORIGINAL_COMMAND="git-receive-pack /!.git/" \ - AUR_USER=user AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_SERVE" 2>&1 >actual -' - -test_expect_success "Test git-upload-pack." ' - cat >expected <<-EOF && - user - foobar - foobar - EOF - SSH_ORIGINAL_COMMAND="git-upload-pack /foobar.git/" \ - AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual -' - -test_expect_success "Try to pull from someone else's repository." ' - cat >expected <<-EOF && - user - foobar2 - foobar2 - EOF - SSH_ORIGINAL_COMMAND="git-upload-pack /foobar2.git/" \ - AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual -' - -test_expect_success "Try to push to someone else's repository." ' - SSH_ORIGINAL_COMMAND="git-receive-pack /foobar2.git/" \ - AUR_USER=user AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_SERVE" 2>&1 -' - -test_expect_success "Try to push to someone else's repository as Trusted User." ' - cat >expected <<-EOF && - tu - foobar - foobar - EOF - SSH_ORIGINAL_COMMAND="git-receive-pack /foobar.git/" \ - AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual -' - -test_expect_success "Test restore." ' - echo "DELETE FROM PackageBases WHERE Name = \"foobar\";" | \ - sqlite3 aur.db && - cat >expected <<-EOF && - user - foobar - EOF - SSH_ORIGINAL_COMMAND="restore foobar" AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 >actual - test_cmp expected actual -' - -test_expect_success "Try to restore an existing package base." ' - SSH_ORIGINAL_COMMAND="restore foobar2" AUR_USER=user AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_SERVE" 2>&1 -' - -test_expect_success "Disown all package bases." ' - SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 && - SSH_ORIGINAL_COMMAND="disown foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 && - cat >expected <<-EOF && - EOF - SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual && - SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual -' - -test_expect_success "Adopt a package base as a regular user." ' - SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 && - cat >expected <<-EOF && - *foobar - EOF - SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual -' - -test_expect_success "Adopt an already adopted package base." ' - SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=user AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_SERVE" 2>&1 -' - -test_expect_success "Adopt a package base as a Trusted User." ' - SSH_ORIGINAL_COMMAND="adopt foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 && - cat >expected <<-EOF && - *foobar2 - EOF - SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual -' - -test_expect_success "Disown one's own package base as a regular user." ' - SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 && - cat >expected <<-EOF && - EOF - SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual -' - -test_expect_success "Disown one's own package base as a Trusted User." ' - SSH_ORIGINAL_COMMAND="disown foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 && - cat >expected <<-EOF && - EOF - SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual -' - -test_expect_success "Try to steal another user's package as a regular user." ' - SSH_ORIGINAL_COMMAND="adopt foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 && - SSH_ORIGINAL_COMMAND="adopt foobar2" AUR_USER=user AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_SERVE" 2>&1 && - cat >expected <<-EOF && - EOF - SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual && - cat >expected <<-EOF && - *foobar2 - EOF - SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual && - SSH_ORIGINAL_COMMAND="disown foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 -' - -test_expect_success "Try to steal another user's package as a Trusted User." ' - SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 && - SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 && - cat >expected <<-EOF && - EOF - SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual && - cat >expected <<-EOF && - *foobar - EOF - SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual && - SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 -' - -test_expect_success "Try to disown another user's package as a regular user." ' - SSH_ORIGINAL_COMMAND="adopt foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 && - SSH_ORIGINAL_COMMAND="disown foobar2" AUR_USER=user AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_SERVE" 2>&1 && - cat >expected <<-EOF && - *foobar2 - EOF - SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual && - SSH_ORIGINAL_COMMAND="disown foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 -' - -test_expect_success "Try to disown another user's package as a Trusted User." ' - SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 && - SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 && - cat >expected <<-EOF && - EOF - SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual && - SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 -' - -test_expect_success "Adopt a package base and add co-maintainers." ' - SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 && - SSH_ORIGINAL_COMMAND="set-comaintainers foobar user3 user4" \ - AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 && - cat >expected <<-EOF && - 5|3|1 - 6|3|2 - EOF - echo "SELECT * FROM PackageComaintainers ORDER BY Priority;" | \ - sqlite3 aur.db >actual && - test_cmp expected actual -' - -test_expect_success "Update package base co-maintainers." ' - SSH_ORIGINAL_COMMAND="set-comaintainers foobar user2 user3 user4" \ - AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 && - cat >expected <<-EOF && - 4|3|1 - 5|3|2 - 6|3|3 - EOF - echo "SELECT * FROM PackageComaintainers ORDER BY Priority;" | \ - sqlite3 aur.db >actual && - test_cmp expected actual -' - -test_expect_success "Try to add co-maintainers to an orphan package base." ' - SSH_ORIGINAL_COMMAND="set-comaintainers foobar2 user2 user3 user4" \ - AUR_USER=user AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_SERVE" 2>&1 && - cat >expected <<-EOF && - 4|3|1 - 5|3|2 - 6|3|3 - EOF - echo "SELECT * FROM PackageComaintainers ORDER BY Priority;" | \ - sqlite3 aur.db >actual && - test_cmp expected actual -' - -test_expect_success "Disown a package base and check (co-)maintainer list." ' - SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=user AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 && - cat >expected <<-EOF && - *foobar - EOF - SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user2 AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual && - cat >expected <<-EOF && - 5|3|1 - 6|3|2 - EOF - echo "SELECT * FROM PackageComaintainers ORDER BY Priority;" | \ - sqlite3 aur.db >actual && - test_cmp expected actual -' - -test_expect_success "Force-disown a package base and check (co-)maintainer list." ' - SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ - "$GIT_SERVE" 2>&1 && - cat >expected <<-EOF && - EOF - SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user3 AUR_PRIVILEGED=0 \ - "$GIT_SERVE" 2>&1 >actual && - test_cmp expected actual && - cat >expected <<-EOF && - EOF - echo "SELECT * FROM PackageComaintainers ORDER BY Priority;" | \ - sqlite3 aur.db >actual && - test_cmp expected actual -' - -test_done diff --git a/git-interface/test/t0003-update.sh b/git-interface/test/t0003-update.sh deleted file mode 100755 index b642089..0000000 --- a/git-interface/test/t0003-update.sh +++ /dev/null @@ -1,432 +0,0 @@ -#!/bin/sh - -test_description='git-update tests' - -. ./setup.sh - -test_expect_success 'Test update hook on a fresh repository.' ' - old=0000000000000000000000000000000000000000 && - new=$(git -C aur.git rev-parse HEAD^) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 && - cat >expected <<-EOF && - 1|1|foobar|1-1|aurweb test package.|https://aur.archlinux.org/ - 1|GPL - 1|1 - 1|1|python-pygit2|| - 1|1 - EOF - >actual && - for t in Packages Licenses PackageLicenses Groups PackageGroups \ - PackageDepends PackageRelations PackageSources \ - PackageNotifications; do - echo "SELECT * FROM $t;" | sqlite3 aur.db >>actual - done && - test_cmp expected actual -' - -test_expect_success 'Test update hook on another fresh repository.' ' - old=0000000000000000000000000000000000000000 && - test_when_finished "git -C aur.git checkout refs/namespaces/foobar/refs/heads/master" && - git -C aur.git checkout -q refs/namespaces/foobar2/refs/heads/master && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar2 AUR_PRIVILEGED=0 \ - "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 && - cat >expected <<-EOF && - 1|1|foobar|1-1|aurweb test package.|https://aur.archlinux.org/ - 2|2|foobar2|1-1|aurweb test package.|https://aur.archlinux.org/ - 1|GPL - 2|MIT - 1|1 - 2|2 - 1|1|python-pygit2|| - 2|1|python-pygit2|| - 1|1 - 2|1 - EOF - >actual && - for t in Packages Licenses PackageLicenses Groups PackageGroups \ - PackageDepends PackageRelations PackageSources \ - PackageNotifications; do - echo "SELECT * FROM $t;" | sqlite3 aur.db >>actual - done && - test_cmp expected actual -' - -test_expect_success 'Test update hook on an updated repository.' ' - old=$(git -C aur.git rev-parse HEAD^) && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 && - cat >expected <<-EOF && - 2|2|foobar2|1-1|aurweb test package.|https://aur.archlinux.org/ - 3|1|foobar|1-2|aurweb test package.|https://aur.archlinux.org/ - 1|GPL - 2|MIT - 2|2 - 3|1 - 2|1|python-pygit2|| - 3|1|python-pygit2|| - 1|1 - 2|1 - EOF - >actual && - for t in Packages Licenses PackageLicenses Groups PackageGroups \ - PackageDepends PackageRelations PackageSources \ - PackageNotifications; do - echo "SELECT * FROM $t;" | sqlite3 aur.db >>actual - done && - test_cmp expected actual -' - -test_expect_success 'Test restore mode.' ' - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - "$GIT_UPDATE" restore 2>&1 && - cat >expected <<-EOF && - 2|2|foobar2|1-1|aurweb test package.|https://aur.archlinux.org/ - 3|1|foobar|1-2|aurweb test package.|https://aur.archlinux.org/ - 1|GPL - 2|MIT - 2|2 - 3|1 - 2|1|python-pygit2|| - 3|1|python-pygit2|| - 1|1 - 2|1 - EOF - >actual && - for t in Packages Licenses PackageLicenses Groups PackageGroups \ - PackageDepends PackageRelations PackageSources \ - PackageNotifications; do - echo "SELECT * FROM $t;" | sqlite3 aur.db >>actual - done && - test_cmp expected actual -' - -test_expect_success 'Test restore mode on a non-existent repository.' ' - cat >expected <<-EOD && - error: restore: repository not found: foobar3 - EOD - AUR_USER=user AUR_PKGBASE=foobar3 AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" restore >actual 2>&1 && - test_cmp expected actual -' - -test_expect_success 'Pushing to a branch other than master.' ' - old=0000000000000000000000000000000000000000 && - new=$(git -C aur.git rev-parse HEAD) && - cat >expected <<-EOD && - error: pushing to a branch other than master is restricted - EOD - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/pu "$old" "$new" >actual 2>&1 && - test_cmp expected actual -' - -test_expect_success 'Performing a non-fast-forward ref update.' ' - old=$(git -C aur.git rev-parse HEAD) && - new=$(git -C aur.git rev-parse HEAD^) && - cat >expected <<-EOD && - error: denying non-fast-forward (you should pull first) - EOD - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - test_cmp expected actual -' - -test_expect_success 'Performing a non-fast-forward ref update as Trusted User.' ' - old=$(git -C aur.git rev-parse HEAD) && - new=$(git -C aur.git rev-parse HEAD^) && - AUR_USER=tu AUR_PKGBASE=foobar AUR_PRIVILEGED=1 \ - "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 -' - -test_expect_success 'Removing .SRCINFO.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - git -C aur.git rm -q .SRCINFO && - git -C aur.git commit -q -m "Remove .SRCINFO" && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - grep -q "^error: missing .SRCINFO$" actual -' - -test_expect_success 'Removing .SRCINFO with a follow-up fix.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - git -C aur.git rm -q .SRCINFO && - git -C aur.git commit -q -m "Remove .SRCINFO" && - git -C aur.git revert --no-edit HEAD && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - grep -q "^error: missing .SRCINFO$" actual -' - -test_expect_success 'Removing PKGBUILD.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - git -C aur.git rm -q PKGBUILD && - git -C aur.git commit -q -m "Remove PKGBUILD" && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - grep -q "^error: missing PKGBUILD$" actual -' - -test_expect_success 'Pushing a tree with a subdirectory.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - mkdir aur.git/subdir && - touch aur.git/subdir/file && - git -C aur.git add subdir/file && - git -C aur.git commit -q -m "Add subdirectory" && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - grep -q "^error: the repository must not contain subdirectories$" actual -' - -test_expect_success 'Pushing a tree with a large blob.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - printf "%256001s" x >aur.git/file && - git -C aur.git add file && - git -C aur.git commit -q -m "Add large blob" && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - grep -q "^error: maximum blob size (250.00KiB) exceeded$" actual -' - -test_expect_success 'Pushing .SRCINFO with a non-matching package base.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - ( - cd aur.git && - sed "s/\(pkgbase.*\)foobar/\1foobar2/" .SRCINFO >.SRCINFO.new - mv .SRCINFO.new .SRCINFO - git commit -q -am "Change package base" - ) && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - grep -q "^error: invalid pkgbase: foobar2, expected foobar$" actual -' - -test_expect_success 'Pushing .SRCINFO with invalid syntax.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - ( - cd aur.git && - sed "s/=//" .SRCINFO >.SRCINFO.new - mv .SRCINFO.new .SRCINFO - git commit -q -am "Break .SRCINFO" - ) && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 -' - -test_expect_success 'Pushing .SRCINFO without pkgver.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - ( - cd aur.git && - sed "/pkgver/d" .SRCINFO >.SRCINFO.new - mv .SRCINFO.new .SRCINFO - git commit -q -am "Remove pkgver" - ) && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - grep -q "^error: missing mandatory field: pkgver$" actual -' - -test_expect_success 'Pushing .SRCINFO without pkgrel.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - ( - cd aur.git && - sed "/pkgrel/d" .SRCINFO >.SRCINFO.new - mv .SRCINFO.new .SRCINFO - git commit -q -am "Remove pkgrel" - ) && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - grep -q "^error: missing mandatory field: pkgrel$" actual -' - -test_expect_success 'Pushing .SRCINFO with epoch.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - ( - cd aur.git && - sed "s/.*pkgrel.*/\\0\\nepoch = 1/" .SRCINFO >.SRCINFO.new - mv .SRCINFO.new .SRCINFO - git commit -q -am "Add epoch" - ) && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 && - cat >expected <<-EOF && - 2|2|foobar2|1-1|aurweb test package.|https://aur.archlinux.org/ - 3|1|foobar|1:1-2|aurweb test package.|https://aur.archlinux.org/ - EOF - echo "SELECT * FROM Packages;" | sqlite3 aur.db >actual && - test_cmp expected actual -' - -test_expect_success 'Pushing .SRCINFO with invalid pkgname.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - ( - cd aur.git && - sed "s/\(pkgname.*\)foobar/\1!/" .SRCINFO >.SRCINFO.new - mv .SRCINFO.new .SRCINFO - git commit -q -am "Change pkgname" - ) && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - grep -q "^error: invalid package name: !$" actual -' - -test_expect_success 'Pushing .SRCINFO with invalid epoch.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - ( - cd aur.git && - sed "s/.*pkgrel.*/\\0\\nepoch = !/" .SRCINFO >.SRCINFO.new - mv .SRCINFO.new .SRCINFO - git commit -q -am "Change epoch" - ) && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - grep -q "^error: invalid epoch: !$" actual -' - -test_expect_success 'Missing install file.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - ( - cd aur.git && - sed "s/.*depends.*/\\0\\ninstall = install/" .SRCINFO >.SRCINFO.new - mv .SRCINFO.new .SRCINFO - git commit -q -am "Add install field" - ) && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - grep -q "^error: missing install file: install$" actual -' - -test_expect_success 'Missing changelog file.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - ( - cd aur.git && - sed "s/.*depends.*/\\0\\nchangelog = changelog/" .SRCINFO >.SRCINFO.new - mv .SRCINFO.new .SRCINFO - git commit -q -am "Add changelog field" - ) && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - grep -q "^error: missing changelog file: changelog$" actual -' - -test_expect_success 'Missing source file.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - ( - cd aur.git && - sed "s/.*depends.*/\\0\\nsource = file/" .SRCINFO >.SRCINFO.new - mv .SRCINFO.new .SRCINFO - git commit -q -am "Add file to the source array" - ) && - new=$(git -C aur.git rev-parse HEAD) && - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - grep -q "^error: missing source file: file$" actual -' - -test_expect_success 'Pushing a blacklisted package.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - echo "pkgname = forbidden" >>aur.git/.SRCINFO && - git -C aur.git commit -q -am "Add blacklisted package" && - new=$(git -C aur.git rev-parse HEAD) && - cat >expected <<-EOD && - error: package is blacklisted: forbidden - EOD - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - test_cmp expected actual -' - -test_expect_success 'Pushing a blacklisted package as Trusted User.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - echo "pkgname = forbidden" >>aur.git/.SRCINFO && - git -C aur.git commit -q -am "Add blacklisted package" && - new=$(git -C aur.git rev-parse HEAD) && - cat >expected <<-EOD && - warning: package is blacklisted: forbidden - EOD - AUR_USER=tu AUR_PKGBASE=foobar AUR_PRIVILEGED=1 \ - "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - test_cmp expected actual -' - -test_expect_success 'Pushing a package already in the official repositories.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - echo "pkgname = official" >>aur.git/.SRCINFO && - git -C aur.git commit -q -am "Add official package" && - new=$(git -C aur.git rev-parse HEAD) && - cat >expected <<-EOD && - error: package already provided by [core]: official - EOD - AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - test_cmp expected actual -' - -test_expect_success 'Pushing a package already in the official repositories as Trusted User.' ' - old=$(git -C aur.git rev-parse HEAD) && - test_when_finished "git -C aur.git reset --hard $old" && - echo "pkgname = official" >>aur.git/.SRCINFO && - git -C aur.git commit -q -am "Add official package" && - new=$(git -C aur.git rev-parse HEAD) && - cat >expected <<-EOD && - warning: package already provided by [core]: official - EOD - AUR_USER=tu AUR_PKGBASE=foobar AUR_PRIVILEGED=1 \ - "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - test_cmp expected actual -' - -test_expect_success 'Trying to hijack a package.' ' - old=0000000000000000000000000000000000000000 && - test_when_finished "git -C aur.git checkout refs/namespaces/foobar/refs/heads/master" && - ( - cd aur.git && - git checkout -q refs/namespaces/foobar2/refs/heads/master && - sed "s/\\(.*pkgname.*\\)2/\\1/" .SRCINFO >.SRCINFO.new - mv .SRCINFO.new .SRCINFO - git commit -q -am "Change package name" - ) && - new=$(git -C aur.git rev-parse HEAD) && - cat >expected <<-EOD && - error: cannot overwrite package: foobar - EOD - AUR_USER=user AUR_PKGBASE=foobar2 AUR_PRIVILEGED=0 \ - test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && - test_cmp expected actual -' - -test_done diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..d6f0f74 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,11 @@ +T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)) + +check: $(T) + +clean: + $(RM) -r test-results/ + +$(T): + @echo "*** $@ ***"; $(SHELL) $@ + +.PHONY: check clean $(T) diff --git a/test/setup.sh b/test/setup.sh new file mode 100644 index 0000000..232b14d --- /dev/null +++ b/test/setup.sh @@ -0,0 +1,195 @@ +TEST_DIRECTORY="$(pwd)" +TOPLEVEL="$(cd .. && pwd)" + +. ./sharness.sh + +# Configure python search path. +PYTHONPATH="$TOPLEVEL" +export PYTHONPATH + +# Configure paths to the Git interface scripts. +GIT_AUTH="$TOPLEVEL/git-interface/git-auth.py" +GIT_SERVE="$TOPLEVEL/git-interface/git-serve.py" +GIT_UPDATE="$TOPLEVEL/git-interface/git-update.py" + +# Create the configuration file and a dummy notification script. +cat >config <<-EOF +[database] +backend = sqlite +name = aur.db + +[options] +enable-maintenance = 0 +maintenance-exceptions = 127.0.0.1 + +[notifications] +notify-cmd = ./notify.sh + +[auth] +valid-keytypes = ssh-rsa ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 +username-regex = [a-zA-Z0-9]+[.\-_]?[a-zA-Z0-9]+$ +git-serve-cmd = /srv/http/aurweb/git-interface/git-serve.py +ssh-options = restrict + +[serve] +repo-path = ./aur.git/ +repo-regex = [a-z0-9][a-z0-9.+_-]*$ +git-shell-cmd = ./git-shell.sh +git-update-cmd = ./update.sh +ssh-cmdline = ssh aur@aur.archlinux.org + +[update] +max-blob-size = 256000 +EOF + +cat >notify.sh <<-EOF +#!/bin/sh +EOF +chmod +x notify.sh + +cat >git-shell.sh <<-\EOF +#!/bin/sh +echo $AUR_USER +echo $AUR_PKGBASE +echo $GIT_NAMESPACE +EOF +chmod +x git-shell.sh + +cat >update.sh <<-\EOF +#!/bin/sh +echo $AUR_USER +echo $AUR_PKGBASE +EOF +chmod +x update.sh + +AUR_CONFIG=config +export AUR_CONFIG + +# Create SSH public keys which will be used by the test users later. +AUTH_KEYTYPE_USER=ssh-rsa +AUTH_KEYTEXT_USER=AAAAB3NzaC1yc2EAAAADAQABAAABAQCeUafDK4jqUiRHNQfwHcYjBKLZ4Rc1sNUofHApBP6j91nIvDHZe2VUqeBmFUhBz7kXK4VbXD9nlHMun2HeshL8hXnMzymZ8Wk7+IKefj61pajJkIdttw9Tnayfg7uhg5RbFy9zpEjmGjnIVjSzOXKCwppNT+CNujpKM5FD8gso/Z+l3fD+IwrPwS1SzF1Z99nqI9n2FM/JWZqluvTqnW9WdAvBDfutXxp0R5ZiLI5TAKL2Ssp5rpL70pkLXhv+9sK545zKKlXUFmw6Pi2iVBdqdRsk9ocl49dLiNIh8CYDCO3CRQn+8EnpBhTor2TKQxGJI3mzoBwWJJxoKhD/XlYJ +AUTH_FINGERPRINT_USER=SHA256:F/OFtYAy0JCytAGUi4RUZnOsThhQtFMK7fH1YvFBCpo + +AUTH_KEYTYPE_TU=ssh-rsa +AUTH_KEYTEXT_TU=AAAAB3NzaC1yc2EAAAADAQABAAABAQC4Q2Beg6jf2r1LZ4vwT5y10dK8+/c5RaNyTwv77wF2OSLXh32xW0ovhE2lW2gqoakdGsxgM2fTtqMTl29WOsAxlGF7x9XbWhFXFUT88Daq1fAeuihkiRjfBbInSW/WcrFZ+biLBch67addtfkkd4PmAafDeeCtszAXqza+ltBG1oxAGiTXgI3LOhA1/GtLLxsi5sPUO3ZlhvwDn4Sy0aXYx8l9hop/PU4Cjn82hyRa9r+SRxQ3KtjKxcVMnZ8IyXOrBwXTukgSBR/6nSdEmO0JPkYUFuNwh3UGFKuNkrPguL5T+4YDym6czYmZJzQ7NNl2pLKYmYgBwBe5rORlWfN5 +AUTH_FINGERPRINT_TU=SHA256:xQGC6j/U1Q3NDXLl04pm+Shr1mjYUXbGMUzlm9vby4k + +AUTH_KEYTYPE_MISSING=sha-rsa +AUTH_KEYTEXT_MISSING=AAAAB3NzaC1yc2EAAAADAQABAAABAQC9UTpssBunuTBCT3KFtv+yb+cN0VmI2C9O9U7wHlkEZWxNBK8is6tnDHXBxRuvRk0LHILkTidLLFX22ZF0+TFgSz7uuEvGZVNpa2Fn2+vKJJYMvZEvb/f8VHF5/Jddt21VOyu23royTN/duiT7WIZdCtEmq5C9Y43NPfsB8FbUc+FVSYT2Lq7g1/bzvFF+CZxwCrGjC3qC7p3pshICfFR8bbWgRN33ClxIQ7MvkcDtfNu38dLotJqdfEa7NdQgba5/S586f1A4OWKc/mQJFyTaGhRBxw/cBSjqonvO0442VYLHFxlrTHoUunKyOJ8+BJfKgjWmfENC9ESY3mL/IEn5 +AUTH_FINGERPRINT_MISSING=SHA256:uB0B+30r2WA1TDMUmFcaEBjosjnFGzn33XFhiyvTL9w + +# Initialize the test database. +rm -f aur.db +sed \ + -e '/^DROP DATABASE /d' \ + -e '/^CREATE DATABASE /d' \ + -e '/^USE /d' \ + -e 's/ ENGINE = InnoDB//' \ + -e 's/ [A-Z]* UNSIGNED NOT NULL AUTO_INCREMENT/ INTEGER NOT NULL/' \ + -e 's/([0-9, ]*) UNSIGNED / UNSIGNED /' \ + "$TOPLEVEL/schema/aur-schema.sql" | sqlite3 aur.db + +echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (1, 'user', '!', 'user@localhost', 1);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (2, 'tu', '!', 'tu@localhost', 2);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (3, 'dev', '!', 'dev@localhost', 3);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (4, 'user2', '!', 'user2@localhost', 1);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (5, 'user3', '!', 'user3@localhost', 1);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (6, 'user4', '!', 'user4@localhost', 1);" | sqlite3 aur.db + +echo "INSERT INTO SSHPubKeys (UserID, Fingerprint, PubKey) VALUES (1, '$AUTH_FINGERPRINT_USER', '$AUTH_KEYTYPE_USER $AUTH_KEYTEXT_USER');" | sqlite3 aur.db +echo "INSERT INTO SSHPubKeys (UserID, Fingerprint, PubKey) VALUES (2, '$AUTH_FINGERPRINT_TU', '$AUTH_KEYTYPE_TU $AUTH_KEYTEXT_TU');" | sqlite3 aur.db + +echo "INSERT INTO PackageBlacklist (Name) VALUES ('forbidden');" | sqlite3 aur.db +echo "INSERT INTO OfficialProviders (Name, Repo, Provides) VALUES ('official', 'core', 'official');" | sqlite3 aur.db + +# Initialize a Git repository and test packages. +GIT_AUTHOR_EMAIL=author@example.com +GIT_AUTHOR_NAME='A U Thor' +GIT_COMMITTER_EMAIL=committer@example.com +GIT_COMMITTER_NAME='C O Mitter' +export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME +export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME + +( + mkdir aur.git + cd aur.git + git init -q + + git checkout -q --orphan refs/namespaces/foobar/refs/heads/master + + cat >PKGBUILD <<-EOF + pkgname=foobar + pkgver=1 + pkgrel=1 + pkgdesc='aurweb test package.' + url='https://aur.archlinux.org/' + license=('GPL') + arch=('any') + depends=('python-pygit2') + source=() + md5sums=() + + package() { + echo 'Hello world!' + } + EOF + + cat >.SRCINFO <<-EOF + pkgbase = foobar + pkgdesc = aurweb test package. + pkgver = 1 + pkgrel = 1 + url = https://aur.archlinux.org/ + arch = any + license = GPL + depends = python-pygit2 + + pkgname = foobar + EOF + + git add PKGBUILD .SRCINFO + git commit -q -m 'Initial import' + + sed 's/\(pkgrel.*\)1/\12/' PKGBUILD >PKGBUILD.new + sed 's/\(pkgrel.*\)1/\12/' .SRCINFO >.SRCINFO.new + mv PKGBUILD.new PKGBUILD + mv .SRCINFO.new .SRCINFO + git commit -q -am 'Bump pkgrel' + + git checkout -q --orphan refs/namespaces/foobar2/refs/heads/master + + cat >PKGBUILD <<-EOF + pkgname=foobar2 + pkgver=1 + pkgrel=1 + pkgdesc='aurweb test package.' + url='https://aur.archlinux.org/' + license=('MIT') + arch=('any') + depends=('python-pygit2') + source=() + md5sums=() + + package() { + echo 'Hello world!' + } + EOF + + cat >.SRCINFO <<-EOF + pkgbase = foobar2 + pkgdesc = aurweb test package. + pkgver = 1 + pkgrel = 1 + url = https://aur.archlinux.org/ + arch = any + license = MIT + depends = python-pygit2 + + pkgname = foobar2 + EOF + + git add PKGBUILD .SRCINFO + git commit -q -m 'Initial import' + + git checkout -q refs/namespaces/foobar/refs/heads/master +) diff --git a/test/sharness.sh b/test/sharness.sh new file mode 100644 index 0000000..1d57ce9 --- /dev/null +++ b/test/sharness.sh @@ -0,0 +1,851 @@ +#!/bin/sh +# +# Copyright (c) 2011-2012 Mathias Lafeldt +# Copyright (c) 2005-2012 Git project +# Copyright (c) 2005-2012 Junio C Hamano +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/ . + +# Public: Current version of Sharness. +SHARNESS_VERSION="1.0.0" +export SHARNESS_VERSION + +# Public: The file extension for tests. By default, it is set to "t". +: ${SHARNESS_TEST_EXTENSION:=t} +export SHARNESS_TEST_EXTENSION + +# Reset TERM to original terminal if found, otherwise save orignal TERM +[ "x" = "x$SHARNESS_ORIG_TERM" ] && + SHARNESS_ORIG_TERM="$TERM" || + TERM="$SHARNESS_ORIG_TERM" +# Public: The unsanitized TERM under which sharness is originally run +export SHARNESS_ORIG_TERM + +# Export SHELL_PATH +: ${SHELL_PATH:=$SHELL} +export SHELL_PATH + +# For repeatability, reset the environment to a known state. +# TERM is sanitized below, after saving color control sequences. +LANG=C +LC_ALL=C +PAGER=cat +TZ=UTC +EDITOR=: +export LANG LC_ALL PAGER TZ EDITOR +unset VISUAL CDPATH GREP_OPTIONS + +# Line feed +LF=' +' + +[ "x$TERM" != "xdumb" ] && ( + [ -t 1 ] && + tput bold >/dev/null 2>&1 && + tput setaf 1 >/dev/null 2>&1 && + tput sgr0 >/dev/null 2>&1 + ) && + color=t + +while test "$#" -ne 0; do + case "$1" in + -d|--d|--de|--deb|--debu|--debug) + debug=t; shift ;; + -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) + immediate=t; shift ;; + -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) + TEST_LONG=t; export TEST_LONG; shift ;; + --in|--int|--inte|--inter|--intera|--interac|--interact|--interacti|--interactiv|--interactive|--interactive-|--interactive-t|--interactive-te|--interactive-tes|--interactive-test|--interactive-tests): + TEST_INTERACTIVE=t; export TEST_INTERACTIVE; verbose=t; shift ;; + -h|--h|--he|--hel|--help) + help=t; shift ;; + -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) + verbose=t; shift ;; + -q|--q|--qu|--qui|--quie|--quiet) + # Ignore --quiet under a TAP::Harness. Saying how many tests + # passed without the ok/not ok details is always an error. + test -z "$HARNESS_ACTIVE" && quiet=t; shift ;; + --chain-lint) + chain_lint=t; shift ;; + --no-chain-lint) + chain_lint=; shift ;; + --no-color) + color=; shift ;; + --root=*) + root=$(expr "z$1" : 'z[^=]*=\(.*\)') + shift ;; + *) + echo "error: unknown test option '$1'" >&2; exit 1 ;; + esac +done + +if test -n "$color"; then + # Save the color control sequences now rather than run tput + # each time say_color() is called. This is done for two + # reasons: + # * TERM will be changed to dumb + # * HOME will be changed to a temporary directory and tput + # might need to read ~/.terminfo from the original HOME + # directory to get the control sequences + # Note: This approach assumes the control sequences don't end + # in a newline for any terminal of interest (command + # substitutions strip trailing newlines). Given that most + # (all?) terminals in common use are related to ECMA-48, this + # shouldn't be a problem. + say_color_error=$(tput bold; tput setaf 1) # bold red + say_color_skip=$(tput setaf 4) # blue + say_color_warn=$(tput setaf 3) # brown/yellow + say_color_pass=$(tput setaf 2) # green + say_color_info=$(tput setaf 6) # cyan + say_color_reset=$(tput sgr0) + say_color_="" # no formatting for normal text + say_color() { + test -z "$1" && test -n "$quiet" && return + eval "say_color_color=\$say_color_$1" + shift + printf "%s\\n" "$say_color_color$*$say_color_reset" + } +else + say_color() { + test -z "$1" && test -n "$quiet" && return + shift + printf "%s\n" "$*" + } +fi + +TERM=dumb +export TERM + +error() { + say_color error "error: $*" + EXIT_OK=t + exit 1 +} + +say() { + say_color info "$*" +} + +test -n "$test_description" || error "Test script did not set test_description." + +if test "$help" = "t"; then + echo "$test_description" + exit 0 +fi + +exec 5>&1 +exec 6<&0 +if test "$verbose" = "t"; then + exec 4>&2 3>&1 +else + exec 4>/dev/null 3>/dev/null +fi + +test_failure=0 +test_count=0 +test_fixed=0 +test_broken=0 +test_success=0 + +die() { + code=$? + if test -n "$EXIT_OK"; then + exit $code + else + echo >&5 "FATAL: Unexpected exit with code $code" + exit 1 + fi +} + +EXIT_OK= +trap 'die' EXIT + +# Public: Define that a test prerequisite is available. +# +# The prerequisite can later be checked explicitly using test_have_prereq or +# implicitly by specifying the prerequisite name in calls to test_expect_success +# or test_expect_failure. +# +# $1 - Name of prerequiste (a simple word, in all capital letters by convention) +# +# Examples +# +# # Set PYTHON prerequisite if interpreter is available. +# command -v python >/dev/null && test_set_prereq PYTHON +# +# # Set prerequisite depending on some variable. +# test -z "$NO_GETTEXT" && test_set_prereq GETTEXT +# +# Returns nothing. +test_set_prereq() { + satisfied_prereq="$satisfied_prereq$1 " +} +satisfied_prereq=" " + +# Public: Check if one or more test prerequisites are defined. +# +# The prerequisites must have previously been set with test_set_prereq. +# The most common use of this is to skip all the tests if some essential +# prerequisite is missing. +# +# $1 - Comma-separated list of test prerequisites. +# +# Examples +# +# # Skip all remaining tests if prerequisite is not set. +# if ! test_have_prereq PERL; then +# skip_all='skipping perl interface tests, perl not available' +# test_done +# fi +# +# Returns 0 if all prerequisites are defined or 1 otherwise. +test_have_prereq() { + # prerequisites can be concatenated with ',' + save_IFS=$IFS + IFS=, + set -- $* + IFS=$save_IFS + + total_prereq=0 + ok_prereq=0 + missing_prereq= + + for prerequisite; do + case "$prerequisite" in + !*) + negative_prereq=t + prerequisite=${prerequisite#!} + ;; + *) + negative_prereq= + esac + + total_prereq=$(($total_prereq + 1)) + case "$satisfied_prereq" in + *" $prerequisite "*) + satisfied_this_prereq=t + ;; + *) + satisfied_this_prereq= + esac + + case "$satisfied_this_prereq,$negative_prereq" in + t,|,t) + ok_prereq=$(($ok_prereq + 1)) + ;; + *) + # Keep a list of missing prerequisites; restore + # the negative marker if necessary. + prerequisite=${negative_prereq:+!}$prerequisite + if test -z "$missing_prereq"; then + missing_prereq=$prerequisite + else + missing_prereq="$prerequisite,$missing_prereq" + fi + esac + done + + test $total_prereq = $ok_prereq +} + +# You are not expected to call test_ok_ and test_failure_ directly, use +# the text_expect_* functions instead. + +test_ok_() { + test_success=$(($test_success + 1)) + say_color "" "ok $test_count - $@" +} + +test_failure_() { + test_failure=$(($test_failure + 1)) + say_color error "not ok $test_count - $1" + shift + echo "$@" | sed -e 's/^/# /' + test "$immediate" = "" || { EXIT_OK=t; exit 1; } +} + +test_known_broken_ok_() { + test_fixed=$(($test_fixed + 1)) + say_color error "ok $test_count - $@ # TODO known breakage vanished" +} + +test_known_broken_failure_() { + test_broken=$(($test_broken + 1)) + say_color warn "not ok $test_count - $@ # TODO known breakage" +} + +# Public: Execute commands in debug mode. +# +# Takes a single argument and evaluates it only when the test script is started +# with --debug. This is primarily meant for use during the development of test +# scripts. +# +# $1 - Commands to be executed. +# +# Examples +# +# test_debug "cat some_log_file" +# +# Returns the exit code of the last command executed in debug mode or 0 +# otherwise. +test_debug() { + test "$debug" = "" || eval "$1" +} + +# Public: Stop execution and start a shell. +# +# This is useful for debugging tests and only makes sense together with "-v". +# Be sure to remove all invocations of this command before submitting. +test_pause() { + if test "$verbose" = t; then + "$SHELL_PATH" <&6 >&3 2>&4 + else + error >&5 "test_pause requires --verbose" + fi +} + +test_eval_() { + # This is a separate function because some tests use + # "return" to end a test_expect_success block early. + case ",$test_prereq," in + *,INTERACTIVE,*) + eval "$*" + ;; + *) + eval &3 2>&4 "$*" + ;; + esac +} + +test_run_() { + test_cleanup=: + expecting_failure=$2 + test_eval_ "$1" + eval_ret=$? + + if test "$chain_lint" = "t"; then + test_eval_ "(exit 117) && $1" + if test "$?" != 117; then + error "bug in the test script: broken &&-chain: $1" + fi + fi + + if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure"; then + test_eval_ "$test_cleanup" + fi + if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then + echo "" + fi + return "$eval_ret" +} + +test_skip_() { + test_count=$(($test_count + 1)) + to_skip= + for skp in $SKIP_TESTS; do + case $this_test.$test_count in + $skp) + to_skip=t + break + esac + done + if test -z "$to_skip" && test -n "$test_prereq" && ! test_have_prereq "$test_prereq"; then + to_skip=t + fi + case "$to_skip" in + t) + of_prereq= + if test "$missing_prereq" != "$test_prereq"; then + of_prereq=" of $test_prereq" + fi + + say_color skip >&3 "skipping test: $@" + say_color skip "ok $test_count # skip $1 (missing $missing_prereq${of_prereq})" + : true + ;; + *) + false + ;; + esac +} + +# Public: Run test commands and expect them to succeed. +# +# When the test passed, an "ok" message is printed and the number of successful +# tests is incremented. When it failed, a "not ok" message is printed and the +# number of failed tests is incremented. +# +# With --immediate, exit test immediately upon the first failed test. +# +# Usually takes two arguments: +# $1 - Test description +# $2 - Commands to be executed. +# +# With three arguments, the first will be taken to be a prerequisite: +# $1 - Comma-separated list of test prerequisites. The test will be skipped if +# not all of the given prerequisites are set. To negate a prerequisite, +# put a "!" in front of it. +# $2 - Test description +# $3 - Commands to be executed. +# +# Examples +# +# test_expect_success \ +# 'git-write-tree should be able to write an empty tree.' \ +# 'tree=$(git-write-tree)' +# +# # Test depending on one prerequisite. +# test_expect_success TTY 'git --paginate rev-list uses a pager' \ +# ' ... ' +# +# # Multiple prerequisites are separated by a comma. +# test_expect_success PERL,PYTHON 'yo dawg' \ +# ' test $(perl -E 'print eval "1 +" . qx[python -c "print 2"]') == "4" ' +# +# Returns nothing. +test_expect_success() { + test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= + test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test_expect_success" + export test_prereq + if ! test_skip_ "$@"; then + say >&3 "expecting success: $2" + if test_run_ "$2"; then + test_ok_ "$1" + else + test_failure_ "$@" + fi + fi + echo >&3 "" +} + +# Public: Run test commands and expect them to fail. Used to demonstrate a known +# breakage. +# +# This is NOT the opposite of test_expect_success, but rather used to mark a +# test that demonstrates a known breakage. +# +# When the test passed, an "ok" message is printed and the number of fixed tests +# is incremented. When it failed, a "not ok" message is printed and the number +# of tests still broken is incremented. +# +# Failures from these tests won't cause --immediate to stop. +# +# Usually takes two arguments: +# $1 - Test description +# $2 - Commands to be executed. +# +# With three arguments, the first will be taken to be a prerequisite: +# $1 - Comma-separated list of test prerequisites. The test will be skipped if +# not all of the given prerequisites are set. To negate a prerequisite, +# put a "!" in front of it. +# $2 - Test description +# $3 - Commands to be executed. +# +# Returns nothing. +test_expect_failure() { + test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= + test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test_expect_failure" + export test_prereq + if ! test_skip_ "$@"; then + say >&3 "checking known breakage: $2" + if test_run_ "$2" expecting_failure; then + test_known_broken_ok_ "$1" + else + test_known_broken_failure_ "$1" + fi + fi + echo >&3 "" +} + +# Public: Run command and ensure that it fails in a controlled way. +# +# Use it instead of "! ". For example, when dies due to a +# segfault, test_must_fail diagnoses it as an error, while "! " would +# mistakenly be treated as just another expected failure. +# +# This is one of the prefix functions to be used inside test_expect_success or +# test_expect_failure. +# +# $1.. - Command to be executed. +# +# Examples +# +# test_expect_success 'complain and die' ' +# do something && +# do something else && +# test_must_fail git checkout ../outerspace +# ' +# +# Returns 1 if the command succeeded (exit code 0). +# Returns 1 if the command died by signal (exit codes 130-192) +# Returns 1 if the command could not be found (exit code 127). +# Returns 0 otherwise. +test_must_fail() { + "$@" + exit_code=$? + if test $exit_code = 0; then + echo >&2 "test_must_fail: command succeeded: $*" + return 1 + elif test $exit_code -gt 129 -a $exit_code -le 192; then + echo >&2 "test_must_fail: died by signal: $*" + return 1 + elif test $exit_code = 127; then + echo >&2 "test_must_fail: command not found: $*" + return 1 + fi + return 0 +} + +# Public: Run command and ensure that it succeeds or fails in a controlled way. +# +# Similar to test_must_fail, but tolerates success too. Use it instead of +# " || :" to catch failures caused by a segfault, for instance. +# +# This is one of the prefix functions to be used inside test_expect_success or +# test_expect_failure. +# +# $1.. - Command to be executed. +# +# Examples +# +# test_expect_success 'some command works without configuration' ' +# test_might_fail git config --unset all.configuration && +# do something +# ' +# +# Returns 1 if the command died by signal (exit codes 130-192) +# Returns 1 if the command could not be found (exit code 127). +# Returns 0 otherwise. +test_might_fail() { + "$@" + exit_code=$? + if test $exit_code -gt 129 -a $exit_code -le 192; then + echo >&2 "test_might_fail: died by signal: $*" + return 1 + elif test $exit_code = 127; then + echo >&2 "test_might_fail: command not found: $*" + return 1 + fi + return 0 +} + +# Public: Run command and ensure it exits with a given exit code. +# +# This is one of the prefix functions to be used inside test_expect_success or +# test_expect_failure. +# +# $1 - Expected exit code. +# $2.. - Command to be executed. +# +# Examples +# +# test_expect_success 'Merge with d/f conflicts' ' +# test_expect_code 1 git merge "merge msg" B master +# ' +# +# Returns 0 if the expected exit code is returned or 1 otherwise. +test_expect_code() { + want_code=$1 + shift + "$@" + exit_code=$? + if test $exit_code = $want_code; then + return 0 + fi + + echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*" + return 1 +} + +# Public: Compare two files to see if expected output matches actual output. +# +# The TEST_CMP variable defines the command used for the comparision; it +# defaults to "diff -u". Only when the test script was started with --verbose, +# will the command's output, the diff, be printed to the standard output. +# +# This is one of the prefix functions to be used inside test_expect_success or +# test_expect_failure. +# +# $1 - Path to file with expected output. +# $2 - Path to file with actual output. +# +# Examples +# +# test_expect_success 'foo works' ' +# echo expected >expected && +# foo >actual && +# test_cmp expected actual +# ' +# +# Returns the exit code of the command set by TEST_CMP. +test_cmp() { + ${TEST_CMP:-diff -u} "$@" +} + +# Public: portably print a sequence of numbers. +# +# seq is not in POSIX and GNU seq might not be available everywhere, +# so it is nice to have a seq implementation, even a very simple one. +# +# $1 - Starting number. +# $2 - Ending number. +# +# Examples +# +# test_expect_success 'foo works 10 times' ' +# for i in $(test_seq 1 10) +# do +# foo || return +# done +# ' +# +# Returns 0 if all the specified numbers can be displayed. +test_seq() { + i="$1" + j="$2" + while test "$i" -le "$j" + do + echo "$i" || return + i=$(expr "$i" + 1) + done +} + +# Public: Check if the file expected to be empty is indeed empty, and barfs +# otherwise. +# +# $1 - File to check for emptyness. +# +# Returns 0 if file is empty, 1 otherwise. +test_must_be_empty() { + if test -s "$1" + then + echo "'$1' is not empty, it contains:" + cat "$1" + return 1 + fi +} + +# Public: Schedule cleanup commands to be run unconditionally at the end of a +# test. +# +# If some cleanup command fails, the test will not pass. With --immediate, no +# cleanup is done to help diagnose what went wrong. +# +# This is one of the prefix functions to be used inside test_expect_success or +# test_expect_failure. +# +# $1.. - Commands to prepend to the list of cleanup commands. +# +# Examples +# +# test_expect_success 'test core.capslock' ' +# git config core.capslock true && +# test_when_finished "git config --unset core.capslock" && +# do_something +# ' +# +# Returns the exit code of the last cleanup command executed. +test_when_finished() { + test_cleanup="{ $* + } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup" +} + +# Public: Schedule cleanup commands to be run unconditionally when all tests +# have run. +# +# This can be used to clean up things like test databases. It is not needed to +# clean up temporary files, as test_done already does that. +# +# Examples: +# +# cleanup mysql -e "DROP DATABASE mytest" +# +# Returns the exit code of the last cleanup command executed. +final_cleanup= +cleanup() { + final_cleanup="{ $* + } && (exit \"\$eval_ret\"); eval_ret=\$?; $final_cleanup" +} + +# Public: Summarize test results and exit with an appropriate error code. +# +# Must be called at the end of each test script. +# +# Can also be used to stop tests early and skip all remaining tests. For this, +# set skip_all to a string explaining why the tests were skipped before calling +# test_done. +# +# Examples +# +# # Each test script must call test_done at the end. +# test_done +# +# # Skip all remaining tests if prerequisite is not set. +# if ! test_have_prereq PERL; then +# skip_all='skipping perl interface tests, perl not available' +# test_done +# fi +# +# Returns 0 if all tests passed or 1 if there was a failure. +test_done() { + EXIT_OK=t + + if test -z "$HARNESS_ACTIVE"; then + test_results_dir="$SHARNESS_TEST_DIRECTORY/test-results" + mkdir -p "$test_results_dir" + test_results_path="$test_results_dir/$this_test.$$.counts" + + cat >>"$test_results_path" <<-EOF + total $test_count + success $test_success + fixed $test_fixed + broken $test_broken + failed $test_failure + + EOF + fi + + if test "$test_fixed" != 0; then + say_color error "# $test_fixed known breakage(s) vanished; please update test(s)" + fi + if test "$test_broken" != 0; then + say_color warn "# still have $test_broken known breakage(s)" + fi + if test "$test_broken" != 0 || test "$test_fixed" != 0; then + test_remaining=$(( $test_count - $test_broken - $test_fixed )) + msg="remaining $test_remaining test(s)" + else + test_remaining=$test_count + msg="$test_count test(s)" + fi + + case "$test_failure" in + 0) + # Maybe print SKIP message + if test -n "$skip_all" && test $test_count -gt 0; then + error "Can't use skip_all after running some tests" + fi + [ -z "$skip_all" ] || skip_all=" # SKIP $skip_all" + + if test $test_remaining -gt 0; then + say_color pass "# passed all $msg" + fi + say "1..$test_count$skip_all" + + test_eval_ "$final_cleanup" + + test -d "$remove_trash" && + cd "$(dirname "$remove_trash")" && + rm -rf "$(basename "$remove_trash")" + + exit 0 ;; + + *) + say_color error "# failed $test_failure among $msg" + say "1..$test_count" + + exit 1 ;; + + esac +} + +# Public: Root directory containing tests. Tests can override this variable, +# e.g. for testing Sharness itself. +: ${SHARNESS_TEST_DIRECTORY:=$(pwd)} +export SHARNESS_TEST_DIRECTORY + +# Public: Source directory of test code and sharness library. +# This directory may be different from the directory in which tests are +# being run. +: ${SHARNESS_TEST_SRCDIR:=$(cd $(dirname $0) && pwd)} +export SHARNESS_TEST_SRCDIR + +# Public: Build directory that will be added to PATH. By default, it is set to +# the parent directory of SHARNESS_TEST_DIRECTORY. +: ${SHARNESS_BUILD_DIRECTORY:="$SHARNESS_TEST_DIRECTORY/.."} +PATH="$SHARNESS_BUILD_DIRECTORY:$PATH" +export PATH SHARNESS_BUILD_DIRECTORY + +# Public: Path to test script currently executed. +SHARNESS_TEST_FILE="$0" +export SHARNESS_TEST_FILE + +# Prepare test area. +SHARNESS_TRASH_DIRECTORY="trash directory.$(basename "$SHARNESS_TEST_FILE" ".$SHARNESS_TEST_EXTENSION")" +test -n "$root" && SHARNESS_TRASH_DIRECTORY="$root/$SHARNESS_TRASH_DIRECTORY" +case "$SHARNESS_TRASH_DIRECTORY" in +/*) ;; # absolute path is good + *) SHARNESS_TRASH_DIRECTORY="$SHARNESS_TEST_DIRECTORY/$SHARNESS_TRASH_DIRECTORY" ;; +esac +test "$debug" = "t" || remove_trash="$SHARNESS_TRASH_DIRECTORY" +rm -rf "$SHARNESS_TRASH_DIRECTORY" || { + EXIT_OK=t + echo >&5 "FATAL: Cannot prepare test area" + exit 1 +} + + +# +# Load any extensions in $srcdir/sharness.d/*.sh +# +if test -d "${SHARNESS_TEST_SRCDIR}/sharness.d" +then + for file in "${SHARNESS_TEST_SRCDIR}"/sharness.d/*.sh + do + # Ensure glob was not an empty match: + test -e "${file}" || break + + if test -n "$debug" + then + echo >&5 "sharness: loading extensions from ${file}" + fi + . "${file}" + if test $? != 0 + then + echo >&5 "sharness: Error loading ${file}. Aborting." + exit 1 + fi + done +fi + +# Public: Empty trash directory, the test area, provided for each test. The HOME +# variable is set to that directory too. +export SHARNESS_TRASH_DIRECTORY + +HOME="$SHARNESS_TRASH_DIRECTORY" +export HOME + +mkdir -p "$SHARNESS_TRASH_DIRECTORY" || exit 1 +# Use -P to resolve symlinks in our working directory so that the cwd +# in subprocesses like git equals our $PWD (for pathname comparisons). +cd -P "$SHARNESS_TRASH_DIRECTORY" || exit 1 + +this_test=${SHARNESS_TEST_FILE##*/} +this_test=${this_test%.$SHARNESS_TEST_EXTENSION} +for skp in $SKIP_TESTS; do + case "$this_test" in + $skp) + say_color info >&3 "skipping test $this_test altogether" + skip_all="skip all tests in $this_test" + test_done + esac +done + +test -n "$TEST_LONG" && test_set_prereq EXPENSIVE +test -n "$TEST_INTERACTIVE" && test_set_prereq INTERACTIVE + +# Make sure this script ends with code 0 +: + +# vi: set ts=4 sw=4 noet : diff --git a/test/t1100-git-auth.sh b/test/t1100-git-auth.sh new file mode 100755 index 0000000..71d526f --- /dev/null +++ b/test/t1100-git-auth.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +test_description='git-auth tests' + +. ./setup.sh + +test_expect_success 'Test basic authentication.' ' + "$GIT_AUTH" "$AUTH_KEYTYPE_USER" "$AUTH_KEYTEXT_USER" >out && + grep -q AUR_USER=user out && + grep -q AUR_PRIVILEGED=0 out +' + +test_expect_success 'Test Trusted User authentication.' ' + "$GIT_AUTH" "$AUTH_KEYTYPE_TU" "$AUTH_KEYTEXT_TU" >out && + grep -q AUR_USER=tu out && + grep -q AUR_PRIVILEGED=1 out +' + +test_expect_success 'Test authentication with an unsupported key type.' ' + test_must_fail "$GIT_AUTH" ssh-xxx "$AUTH_KEYTEXT_USER" +' + +test_expect_success 'Test authentication with a wrong key.' ' + "$GIT_AUTH" "$AUTH_KEYTYPE_MISSING" "$AUTH_KEYTEXT_MISSING" >out + test_must_be_empty out +' + +test_done diff --git a/test/t1200-git-serve.sh b/test/t1200-git-serve.sh new file mode 100755 index 0000000..2f1926e --- /dev/null +++ b/test/t1200-git-serve.sh @@ -0,0 +1,320 @@ +#!/bin/sh + +test_description='git-serve tests' + +. ./setup.sh + +test_expect_success 'Test interactive shell.' ' + "$GIT_SERVE" 2>&1 | grep -q "Interactive shell is disabled." +' + +test_expect_success 'Test help.' ' + SSH_ORIGINAL_COMMAND=help "$GIT_SERVE" 2>actual && + save_IFS=$IFS + IFS= + while read -r line; do + echo $line | grep -q "^Commands:$" && continue + echo $line | grep -q "^ [a-z]" || return 1 + [ ${#line} -le 80 ] || return 1 + done &1 && + SSH_ORIGINAL_COMMAND="setup-repo foobar2" AUR_USER=tu \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + *foobar + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success 'Test git-receive-pack.' ' + cat >expected <<-EOF && + user + foobar + foobar + EOF + SSH_ORIGINAL_COMMAND="git-receive-pack /foobar.git/" \ + AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success 'Test git-receive-pack with an invalid repository name.' ' + SSH_ORIGINAL_COMMAND="git-receive-pack /!.git/" \ + AUR_USER=user AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_SERVE" 2>&1 >actual +' + +test_expect_success "Test git-upload-pack." ' + cat >expected <<-EOF && + user + foobar + foobar + EOF + SSH_ORIGINAL_COMMAND="git-upload-pack /foobar.git/" \ + AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success "Try to pull from someone else's repository." ' + cat >expected <<-EOF && + user + foobar2 + foobar2 + EOF + SSH_ORIGINAL_COMMAND="git-upload-pack /foobar2.git/" \ + AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success "Try to push to someone else's repository." ' + SSH_ORIGINAL_COMMAND="git-receive-pack /foobar2.git/" \ + AUR_USER=user AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_SERVE" 2>&1 +' + +test_expect_success "Try to push to someone else's repository as Trusted User." ' + cat >expected <<-EOF && + tu + foobar + foobar + EOF + SSH_ORIGINAL_COMMAND="git-receive-pack /foobar.git/" \ + AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success "Test restore." ' + echo "DELETE FROM PackageBases WHERE Name = \"foobar\";" | \ + sqlite3 aur.db && + cat >expected <<-EOF && + user + foobar + EOF + SSH_ORIGINAL_COMMAND="restore foobar" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual + test_cmp expected actual +' + +test_expect_success "Try to restore an existing package base." ' + SSH_ORIGINAL_COMMAND="restore foobar2" AUR_USER=user AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_SERVE" 2>&1 +' + +test_expect_success "Disown all package bases." ' + SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + SSH_ORIGINAL_COMMAND="disown foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success "Adopt a package base as a regular user." ' + SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + *foobar + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success "Adopt an already adopted package base." ' + SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=user AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_SERVE" 2>&1 +' + +test_expect_success "Adopt a package base as a Trusted User." ' + SSH_ORIGINAL_COMMAND="adopt foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + *foobar2 + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success "Disown one's own package base as a regular user." ' + SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success "Disown one's own package base as a Trusted User." ' + SSH_ORIGINAL_COMMAND="disown foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual +' + +test_expect_success "Try to steal another user's package as a regular user." ' + SSH_ORIGINAL_COMMAND="adopt foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + SSH_ORIGINAL_COMMAND="adopt foobar2" AUR_USER=user AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + cat >expected <<-EOF && + *foobar2 + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + SSH_ORIGINAL_COMMAND="disown foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 +' + +test_expect_success "Try to steal another user's package as a Trusted User." ' + SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 && + SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + cat >expected <<-EOF && + *foobar + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 +' + +test_expect_success "Try to disown another user's package as a regular user." ' + SSH_ORIGINAL_COMMAND="adopt foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + SSH_ORIGINAL_COMMAND="disown foobar2" AUR_USER=user AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + *foobar2 + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + SSH_ORIGINAL_COMMAND="disown foobar2" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 +' + +test_expect_success "Try to disown another user's package as a Trusted User." ' + SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 && + SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 +' + +test_expect_success "Adopt a package base and add co-maintainers." ' + SSH_ORIGINAL_COMMAND="adopt foobar" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 && + SSH_ORIGINAL_COMMAND="set-comaintainers foobar user3 user4" \ + AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + 5|3|1 + 6|3|2 + EOF + echo "SELECT * FROM PackageComaintainers ORDER BY Priority;" | \ + sqlite3 aur.db >actual && + test_cmp expected actual +' + +test_expect_success "Update package base co-maintainers." ' + SSH_ORIGINAL_COMMAND="set-comaintainers foobar user2 user3 user4" \ + AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + 4|3|1 + 5|3|2 + 6|3|3 + EOF + echo "SELECT * FROM PackageComaintainers ORDER BY Priority;" | \ + sqlite3 aur.db >actual && + test_cmp expected actual +' + +test_expect_success "Try to add co-maintainers to an orphan package base." ' + SSH_ORIGINAL_COMMAND="set-comaintainers foobar2 user2 user3 user4" \ + AUR_USER=user AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + 4|3|1 + 5|3|2 + 6|3|3 + EOF + echo "SELECT * FROM PackageComaintainers ORDER BY Priority;" | \ + sqlite3 aur.db >actual && + test_cmp expected actual +' + +test_expect_success "Disown a package base and check (co-)maintainer list." ' + SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + *foobar + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user2 AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + cat >expected <<-EOF && + 5|3|1 + 6|3|2 + EOF + echo "SELECT * FROM PackageComaintainers ORDER BY Priority;" | \ + sqlite3 aur.db >actual && + test_cmp expected actual +' + +test_expect_success "Force-disown a package base and check (co-)maintainer list." ' + SSH_ORIGINAL_COMMAND="disown foobar" AUR_USER=tu AUR_PRIVILEGED=1 \ + "$GIT_SERVE" 2>&1 && + cat >expected <<-EOF && + EOF + SSH_ORIGINAL_COMMAND="list-repos" AUR_USER=user3 AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + test_cmp expected actual && + cat >expected <<-EOF && + EOF + echo "SELECT * FROM PackageComaintainers ORDER BY Priority;" | \ + sqlite3 aur.db >actual && + test_cmp expected actual +' + +test_done diff --git a/test/t1300-git-update.sh b/test/t1300-git-update.sh new file mode 100755 index 0000000..b642089 --- /dev/null +++ b/test/t1300-git-update.sh @@ -0,0 +1,432 @@ +#!/bin/sh + +test_description='git-update tests' + +. ./setup.sh + +test_expect_success 'Test update hook on a fresh repository.' ' + old=0000000000000000000000000000000000000000 && + new=$(git -C aur.git rev-parse HEAD^) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 && + cat >expected <<-EOF && + 1|1|foobar|1-1|aurweb test package.|https://aur.archlinux.org/ + 1|GPL + 1|1 + 1|1|python-pygit2|| + 1|1 + EOF + >actual && + for t in Packages Licenses PackageLicenses Groups PackageGroups \ + PackageDepends PackageRelations PackageSources \ + PackageNotifications; do + echo "SELECT * FROM $t;" | sqlite3 aur.db >>actual + done && + test_cmp expected actual +' + +test_expect_success 'Test update hook on another fresh repository.' ' + old=0000000000000000000000000000000000000000 && + test_when_finished "git -C aur.git checkout refs/namespaces/foobar/refs/heads/master" && + git -C aur.git checkout -q refs/namespaces/foobar2/refs/heads/master && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar2 AUR_PRIVILEGED=0 \ + "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 && + cat >expected <<-EOF && + 1|1|foobar|1-1|aurweb test package.|https://aur.archlinux.org/ + 2|2|foobar2|1-1|aurweb test package.|https://aur.archlinux.org/ + 1|GPL + 2|MIT + 1|1 + 2|2 + 1|1|python-pygit2|| + 2|1|python-pygit2|| + 1|1 + 2|1 + EOF + >actual && + for t in Packages Licenses PackageLicenses Groups PackageGroups \ + PackageDepends PackageRelations PackageSources \ + PackageNotifications; do + echo "SELECT * FROM $t;" | sqlite3 aur.db >>actual + done && + test_cmp expected actual +' + +test_expect_success 'Test update hook on an updated repository.' ' + old=$(git -C aur.git rev-parse HEAD^) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 && + cat >expected <<-EOF && + 2|2|foobar2|1-1|aurweb test package.|https://aur.archlinux.org/ + 3|1|foobar|1-2|aurweb test package.|https://aur.archlinux.org/ + 1|GPL + 2|MIT + 2|2 + 3|1 + 2|1|python-pygit2|| + 3|1|python-pygit2|| + 1|1 + 2|1 + EOF + >actual && + for t in Packages Licenses PackageLicenses Groups PackageGroups \ + PackageDepends PackageRelations PackageSources \ + PackageNotifications; do + echo "SELECT * FROM $t;" | sqlite3 aur.db >>actual + done && + test_cmp expected actual +' + +test_expect_success 'Test restore mode.' ' + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + "$GIT_UPDATE" restore 2>&1 && + cat >expected <<-EOF && + 2|2|foobar2|1-1|aurweb test package.|https://aur.archlinux.org/ + 3|1|foobar|1-2|aurweb test package.|https://aur.archlinux.org/ + 1|GPL + 2|MIT + 2|2 + 3|1 + 2|1|python-pygit2|| + 3|1|python-pygit2|| + 1|1 + 2|1 + EOF + >actual && + for t in Packages Licenses PackageLicenses Groups PackageGroups \ + PackageDepends PackageRelations PackageSources \ + PackageNotifications; do + echo "SELECT * FROM $t;" | sqlite3 aur.db >>actual + done && + test_cmp expected actual +' + +test_expect_success 'Test restore mode on a non-existent repository.' ' + cat >expected <<-EOD && + error: restore: repository not found: foobar3 + EOD + AUR_USER=user AUR_PKGBASE=foobar3 AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" restore >actual 2>&1 && + test_cmp expected actual +' + +test_expect_success 'Pushing to a branch other than master.' ' + old=0000000000000000000000000000000000000000 && + new=$(git -C aur.git rev-parse HEAD) && + cat >expected <<-EOD && + error: pushing to a branch other than master is restricted + EOD + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/pu "$old" "$new" >actual 2>&1 && + test_cmp expected actual +' + +test_expect_success 'Performing a non-fast-forward ref update.' ' + old=$(git -C aur.git rev-parse HEAD) && + new=$(git -C aur.git rev-parse HEAD^) && + cat >expected <<-EOD && + error: denying non-fast-forward (you should pull first) + EOD + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + test_cmp expected actual +' + +test_expect_success 'Performing a non-fast-forward ref update as Trusted User.' ' + old=$(git -C aur.git rev-parse HEAD) && + new=$(git -C aur.git rev-parse HEAD^) && + AUR_USER=tu AUR_PKGBASE=foobar AUR_PRIVILEGED=1 \ + "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Removing .SRCINFO.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + git -C aur.git rm -q .SRCINFO && + git -C aur.git commit -q -m "Remove .SRCINFO" && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: missing .SRCINFO$" actual +' + +test_expect_success 'Removing .SRCINFO with a follow-up fix.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + git -C aur.git rm -q .SRCINFO && + git -C aur.git commit -q -m "Remove .SRCINFO" && + git -C aur.git revert --no-edit HEAD && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: missing .SRCINFO$" actual +' + +test_expect_success 'Removing PKGBUILD.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + git -C aur.git rm -q PKGBUILD && + git -C aur.git commit -q -m "Remove PKGBUILD" && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: missing PKGBUILD$" actual +' + +test_expect_success 'Pushing a tree with a subdirectory.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + mkdir aur.git/subdir && + touch aur.git/subdir/file && + git -C aur.git add subdir/file && + git -C aur.git commit -q -m "Add subdirectory" && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: the repository must not contain subdirectories$" actual +' + +test_expect_success 'Pushing a tree with a large blob.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + printf "%256001s" x >aur.git/file && + git -C aur.git add file && + git -C aur.git commit -q -m "Add large blob" && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: maximum blob size (250.00KiB) exceeded$" actual +' + +test_expect_success 'Pushing .SRCINFO with a non-matching package base.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "s/\(pkgbase.*\)foobar/\1foobar2/" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Change package base" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: invalid pkgbase: foobar2, expected foobar$" actual +' + +test_expect_success 'Pushing .SRCINFO with invalid syntax.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "s/=//" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Break .SRCINFO" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 +' + +test_expect_success 'Pushing .SRCINFO without pkgver.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "/pkgver/d" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Remove pkgver" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: missing mandatory field: pkgver$" actual +' + +test_expect_success 'Pushing .SRCINFO without pkgrel.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "/pkgrel/d" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Remove pkgrel" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: missing mandatory field: pkgrel$" actual +' + +test_expect_success 'Pushing .SRCINFO with epoch.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "s/.*pkgrel.*/\\0\\nepoch = 1/" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Add epoch" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + "$GIT_UPDATE" refs/heads/master "$old" "$new" 2>&1 && + cat >expected <<-EOF && + 2|2|foobar2|1-1|aurweb test package.|https://aur.archlinux.org/ + 3|1|foobar|1:1-2|aurweb test package.|https://aur.archlinux.org/ + EOF + echo "SELECT * FROM Packages;" | sqlite3 aur.db >actual && + test_cmp expected actual +' + +test_expect_success 'Pushing .SRCINFO with invalid pkgname.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "s/\(pkgname.*\)foobar/\1!/" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Change pkgname" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: invalid package name: !$" actual +' + +test_expect_success 'Pushing .SRCINFO with invalid epoch.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "s/.*pkgrel.*/\\0\\nepoch = !/" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Change epoch" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: invalid epoch: !$" actual +' + +test_expect_success 'Missing install file.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "s/.*depends.*/\\0\\ninstall = install/" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Add install field" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: missing install file: install$" actual +' + +test_expect_success 'Missing changelog file.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "s/.*depends.*/\\0\\nchangelog = changelog/" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Add changelog field" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: missing changelog file: changelog$" actual +' + +test_expect_success 'Missing source file.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + ( + cd aur.git && + sed "s/.*depends.*/\\0\\nsource = file/" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Add file to the source array" + ) && + new=$(git -C aur.git rev-parse HEAD) && + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + grep -q "^error: missing source file: file$" actual +' + +test_expect_success 'Pushing a blacklisted package.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + echo "pkgname = forbidden" >>aur.git/.SRCINFO && + git -C aur.git commit -q -am "Add blacklisted package" && + new=$(git -C aur.git rev-parse HEAD) && + cat >expected <<-EOD && + error: package is blacklisted: forbidden + EOD + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + test_cmp expected actual +' + +test_expect_success 'Pushing a blacklisted package as Trusted User.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + echo "pkgname = forbidden" >>aur.git/.SRCINFO && + git -C aur.git commit -q -am "Add blacklisted package" && + new=$(git -C aur.git rev-parse HEAD) && + cat >expected <<-EOD && + warning: package is blacklisted: forbidden + EOD + AUR_USER=tu AUR_PKGBASE=foobar AUR_PRIVILEGED=1 \ + "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + test_cmp expected actual +' + +test_expect_success 'Pushing a package already in the official repositories.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + echo "pkgname = official" >>aur.git/.SRCINFO && + git -C aur.git commit -q -am "Add official package" && + new=$(git -C aur.git rev-parse HEAD) && + cat >expected <<-EOD && + error: package already provided by [core]: official + EOD + AUR_USER=user AUR_PKGBASE=foobar AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + test_cmp expected actual +' + +test_expect_success 'Pushing a package already in the official repositories as Trusted User.' ' + old=$(git -C aur.git rev-parse HEAD) && + test_when_finished "git -C aur.git reset --hard $old" && + echo "pkgname = official" >>aur.git/.SRCINFO && + git -C aur.git commit -q -am "Add official package" && + new=$(git -C aur.git rev-parse HEAD) && + cat >expected <<-EOD && + warning: package already provided by [core]: official + EOD + AUR_USER=tu AUR_PKGBASE=foobar AUR_PRIVILEGED=1 \ + "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + test_cmp expected actual +' + +test_expect_success 'Trying to hijack a package.' ' + old=0000000000000000000000000000000000000000 && + test_when_finished "git -C aur.git checkout refs/namespaces/foobar/refs/heads/master" && + ( + cd aur.git && + git checkout -q refs/namespaces/foobar2/refs/heads/master && + sed "s/\\(.*pkgname.*\\)2/\\1/" .SRCINFO >.SRCINFO.new + mv .SRCINFO.new .SRCINFO + git commit -q -am "Change package name" + ) && + new=$(git -C aur.git rev-parse HEAD) && + cat >expected <<-EOD && + error: cannot overwrite package: foobar + EOD + AUR_USER=user AUR_PKGBASE=foobar2 AUR_PRIVILEGED=0 \ + test_must_fail "$GIT_UPDATE" refs/heads/master "$old" "$new" >actual 2>&1 && + test_cmp expected actual +' + +test_done -- cgit v1.2.3-54-g00ecf From df6bb72807408bc0eab59e729ea8c0b69fe39388 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Sat, 1 Oct 2016 21:48:14 +0200 Subject: git-serve: Support `git {receive,upload}-pack` Add support for the `git receive-pack` and `git upload-pack` commands which are aliases for git-receive-pack and git-upload-pack, respectively. Signed-off-by: Lukas Fleischer --- git-interface/git-serve.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'git-interface') diff --git a/git-interface/git-serve.py b/git-interface/git-serve.py index 5f3b26d..ebfef94 100755 --- a/git-interface/git-serve.py +++ b/git-interface/git-serve.py @@ -305,6 +305,10 @@ def main(): if remote_addr not in maintenance_exc: die("The AUR is down due to maintenance. We will be back soon.") + if action == 'git' and cmdargv[1] in ('upload-pack', 'receive-pack'): + action = action + '-' + cmdargv[1] + del cmdargv[1] + if action == 'git-upload-pack' or action == 'git-receive-pack': if len(cmdargv) < 2: die_with_help("{:s}: missing path".format(action)) -- cgit v1.2.3-54-g00ecf From d4fe77ac572ef0e60c9ffa5f987c9cda448cf9f2 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Sat, 8 Oct 2016 14:19:11 +0200 Subject: Reorganize Git interface scripts Move the Git interface scripts from git-interface/ to aurweb/git/. Use setuptools to automatically create wrappers which can be installed using `python3 setup.py install`. Update the configuration files, the test suite as well as the INSTALL and README files to reflect these changes. Signed-off-by: Lukas Fleischer --- INSTALL | 28 ++- README | 5 +- aurweb/git/auth.py | 62 +++++++ aurweb/git/serve.py | 409 +++++++++++++++++++++++++++++++++++++++++ aurweb/git/update.py | 419 +++++++++++++++++++++++++++++++++++++++++++ conf/config.proto | 4 +- git-interface/Makefile | 18 -- git-interface/__init__.py | 0 git-interface/config.mk | 1 - git-interface/git-auth.py | 62 ------- git-interface/git-auth.sh.in | 3 - git-interface/git-serve.py | 409 ----------------------------------------- git-interface/git-update.py | 419 ------------------------------------------- setup.py | 7 + test/setup.sh | 8 +- 15 files changed, 916 insertions(+), 938 deletions(-) create mode 100755 aurweb/git/auth.py create mode 100755 aurweb/git/serve.py create mode 100755 aurweb/git/update.py delete mode 100644 git-interface/Makefile delete mode 100644 git-interface/__init__.py delete mode 100644 git-interface/config.mk delete mode 100755 git-interface/git-auth.py delete mode 100644 git-interface/git-auth.sh.in delete mode 100755 git-interface/git-serve.py delete mode 100755 git-interface/git-update.py (limited to 'git-interface') diff --git a/INSTALL b/INSTALL index dab48cc..395915a 100644 --- a/INSTALL +++ b/INSTALL @@ -37,11 +37,16 @@ Setup on Arch Linux $ mysql -uaur -p AUR 1 else '0', + } + key = keytype + ' ' + keytext + + print(format_command(env_vars, git_serve_cmd, ssh_opts, key)) + + +if __name__ == '__main__': + main() diff --git a/aurweb/git/serve.py b/aurweb/git/serve.py new file mode 100755 index 0000000..ebfef94 --- /dev/null +++ b/aurweb/git/serve.py @@ -0,0 +1,409 @@ +#!/usr/bin/python3 + +import os +import re +import shlex +import subprocess +import sys +import time + +import aurweb.config +import aurweb.db + +notify_cmd = aurweb.config.get('notifications', 'notify-cmd') + +repo_path = aurweb.config.get('serve', 'repo-path') +repo_regex = aurweb.config.get('serve', 'repo-regex') +git_shell_cmd = aurweb.config.get('serve', 'git-shell-cmd') +git_update_cmd = aurweb.config.get('serve', 'git-update-cmd') +ssh_cmdline = aurweb.config.get('serve', 'ssh-cmdline') + +enable_maintenance = aurweb.config.getboolean('options', 'enable-maintenance') +maintenance_exc = aurweb.config.get('options', 'maintenance-exceptions').split() + + +def pkgbase_from_name(pkgbase): + conn = aurweb.db.Connection() + cur = conn.execute("SELECT ID FROM PackageBases WHERE Name = ?", [pkgbase]) + + row = cur.fetchone() + return row[0] if row else None + + +def pkgbase_exists(pkgbase): + return pkgbase_from_name(pkgbase) is not None + + +def list_repos(user): + conn = aurweb.db.Connection() + + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) + userid = cur.fetchone()[0] + if userid == 0: + die('{:s}: unknown user: {:s}'.format(action, user)) + + cur = conn.execute("SELECT Name, PackagerUID FROM PackageBases " + + "WHERE MaintainerUID = ?", [userid]) + for row in cur: + print((' ' if row[1] else '*') + row[0]) + conn.close() + + +def create_pkgbase(pkgbase, user): + if not re.match(repo_regex, pkgbase): + die('{:s}: invalid repository name: {:s}'.format(action, pkgbase)) + if pkgbase_exists(pkgbase): + die('{:s}: package base already exists: {:s}'.format(action, pkgbase)) + + conn = aurweb.db.Connection() + + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) + userid = cur.fetchone()[0] + if userid == 0: + die('{:s}: unknown user: {:s}'.format(action, user)) + + now = int(time.time()) + cur = conn.execute("INSERT INTO PackageBases (Name, SubmittedTS, " + + "ModifiedTS, SubmitterUID, MaintainerUID) VALUES " + + "(?, ?, ?, ?, ?)", [pkgbase, now, now, userid, userid]) + pkgbase_id = cur.lastrowid + + cur = conn.execute("INSERT INTO PackageNotifications " + + "(PackageBaseID, UserID) VALUES (?, ?)", + [pkgbase_id, userid]) + + conn.commit() + conn.close() + + +def pkgbase_adopt(pkgbase, user, privileged): + pkgbase_id = pkgbase_from_name(pkgbase) + if not pkgbase_id: + die('{:s}: package base not found: {:s}'.format(action, pkgbase)) + + conn = aurweb.db.Connection() + + cur = conn.execute("SELECT ID FROM PackageBases WHERE ID = ? AND " + + "MaintainerUID IS NULL", [pkgbase_id]) + if not privileged and not cur.fetchone(): + die('{:s}: permission denied: {:s}'.format(action, user)) + + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) + userid = cur.fetchone()[0] + if userid == 0: + die('{:s}: unknown user: {:s}'.format(action, user)) + + cur = conn.execute("UPDATE PackageBases SET MaintainerUID = ? " + + "WHERE ID = ?", [userid, pkgbase_id]) + + cur = conn.execute("SELECT COUNT(*) FROM PackageNotifications WHERE " + + "PackageBaseID = ? AND UserID = ?", + [pkgbase_id, userid]) + if cur.fetchone()[0] == 0: + cur = conn.execute("INSERT INTO PackageNotifications " + + "(PackageBaseID, UserID) VALUES (?, ?)", + [pkgbase_id, userid]) + conn.commit() + + subprocess.Popen((notify_cmd, 'adopt', str(pkgbase_id), str(userid))) + + conn.close() + + +def pkgbase_get_comaintainers(pkgbase): + conn = aurweb.db.Connection() + + cur = conn.execute("SELECT UserName FROM PackageComaintainers " + + "INNER JOIN Users " + + "ON Users.ID = PackageComaintainers.UsersID " + + "INNER JOIN PackageBases " + + "ON PackageBases.ID = PackageComaintainers.PackageBaseID " + + "WHERE PackageBases.Name = ? " + + "ORDER BY Priority ASC", [pkgbase]) + + return [row[0] for row in cur.fetchall()] + + +def pkgbase_set_comaintainers(pkgbase, userlist, user, privileged): + pkgbase_id = pkgbase_from_name(pkgbase) + if not pkgbase_id: + die('{:s}: package base not found: {:s}'.format(action, pkgbase)) + + if not privileged and not pkgbase_has_full_access(pkgbase, user): + die('{:s}: permission denied: {:s}'.format(action, user)) + + conn = aurweb.db.Connection() + + userlist_old = set(pkgbase_get_comaintainers(pkgbase)) + + uids_old = set() + for olduser in userlist_old: + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", + [olduser]) + userid = cur.fetchone()[0] + if userid == 0: + die('{:s}: unknown user: {:s}'.format(action, user)) + uids_old.add(userid) + + uids_new = set() + for newuser in userlist: + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", + [newuser]) + userid = cur.fetchone()[0] + if userid == 0: + die('{:s}: unknown user: {:s}'.format(action, user)) + uids_new.add(userid) + + uids_add = uids_new - uids_old + uids_rem = uids_old - uids_new + + i = 1 + for userid in uids_new: + if userid in uids_add: + cur = conn.execute("INSERT INTO PackageComaintainers " + + "(PackageBaseID, UsersID, Priority) " + + "VALUES (?, ?, ?)", [pkgbase_id, userid, i]) + subprocess.Popen((notify_cmd, 'comaintainer-add', str(pkgbase_id), + str(userid))) + else: + cur = conn.execute("UPDATE PackageComaintainers " + + "SET Priority = ? " + + "WHERE PackageBaseID = ? AND UsersID = ?", + [i, pkgbase_id, userid]) + i += 1 + + for userid in uids_rem: + cur = conn.execute("DELETE FROM PackageComaintainers " + + "WHERE PackageBaseID = ? AND UsersID = ?", + [pkgbase_id, userid]) + subprocess.Popen((notify_cmd, 'comaintainer-remove', + str(pkgbase_id), str(userid))) + + conn.commit() + conn.close() + + +def pkgbase_disown(pkgbase, user, privileged): + pkgbase_id = pkgbase_from_name(pkgbase) + if not pkgbase_id: + die('{:s}: package base not found: {:s}'.format(action, pkgbase)) + + initialized_by_owner = pkgbase_has_full_access(pkgbase, user) + if not privileged and not initialized_by_owner: + die('{:s}: permission denied: {:s}'.format(action, user)) + + # TODO: Support disowning package bases via package request. + # TODO: Scan through pending orphan requests and close them. + + comaintainers = [] + new_maintainer_userid = None + + conn = aurweb.db.Connection() + + # Make the first co-maintainer the new maintainer, unless the action was + # enforced by a Trusted User. + if initialized_by_owner: + comaintainers = pkgbase_get_comaintainers(pkgbase) + if len(comaintainers) > 0: + new_maintainer = comaintainers[0] + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", + [new_maintainer]) + new_maintainer_userid = cur.fetchone()[0] + comaintainers.remove(new_maintainer) + + pkgbase_set_comaintainers(pkgbase, comaintainers, user, privileged) + cur = conn.execute("UPDATE PackageBases SET MaintainerUID = ? " + + "WHERE ID = ?", [new_maintainer_userid, pkgbase_id]) + + conn.commit() + + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) + userid = cur.fetchone()[0] + if userid == 0: + die('{:s}: unknown user: {:s}'.format(action, user)) + + subprocess.Popen((notify_cmd, 'disown', str(pkgbase_id), str(userid))) + + conn.close() + + +def pkgbase_set_keywords(pkgbase, keywords): + pkgbase_id = pkgbase_from_name(pkgbase) + if not pkgbase_id: + die('{:s}: package base not found: {:s}'.format(action, pkgbase)) + + conn = aurweb.db.Connection() + + conn.execute("DELETE FROM PackageKeywords WHERE PackageBaseID = ?", + [pkgbase_id]) + for keyword in keywords: + conn.execute("INSERT INTO PackageKeywords (PackageBaseID, Keyword) " + + "VALUES (?, ?)", [pkgbase_id, keyword]) + + conn.commit() + conn.close() + + +def pkgbase_has_write_access(pkgbase, user): + conn = aurweb.db.Connection() + + cur = conn.execute("SELECT COUNT(*) FROM PackageBases " + + "LEFT JOIN PackageComaintainers " + + "ON PackageComaintainers.PackageBaseID = PackageBases.ID " + + "INNER JOIN Users " + + "ON Users.ID = PackageBases.MaintainerUID " + + "OR PackageBases.MaintainerUID IS NULL " + + "OR Users.ID = PackageComaintainers.UsersID " + + "WHERE Name = ? AND Username = ?", [pkgbase, user]) + return cur.fetchone()[0] > 0 + + +def pkgbase_has_full_access(pkgbase, user): + conn = aurweb.db.Connection() + + cur = conn.execute("SELECT COUNT(*) FROM PackageBases " + + "INNER JOIN Users " + + "ON Users.ID = PackageBases.MaintainerUID " + + "WHERE Name = ? AND Username = ?", [pkgbase, user]) + return cur.fetchone()[0] > 0 + + +def die(msg): + sys.stderr.write("{:s}\n".format(msg)) + exit(1) + + +def die_with_help(msg): + die(msg + "\nTry `{:s} help` for a list of commands.".format(ssh_cmdline)) + + +def warn(msg): + sys.stderr.write("warning: {:s}\n".format(msg)) + + +def usage(cmds): + sys.stderr.write("Commands:\n") + colwidth = max([len(cmd) for cmd in cmds.keys()]) + 4 + for key in sorted(cmds): + sys.stderr.write(" " + key.ljust(colwidth) + cmds[key] + "\n") + exit(0) + + +def main(): + user = os.environ.get('AUR_USER') + privileged = (os.environ.get('AUR_PRIVILEGED', '0') == '1') + ssh_cmd = os.environ.get('SSH_ORIGINAL_COMMAND') + ssh_client = os.environ.get('SSH_CLIENT') + + if not ssh_cmd: + die_with_help("Interactive shell is disabled.") + cmdargv = shlex.split(ssh_cmd) + action = cmdargv[0] + remote_addr = ssh_client.split(' ')[0] if ssh_client else None + + if enable_maintenance: + if remote_addr not in maintenance_exc: + die("The AUR is down due to maintenance. We will be back soon.") + + if action == 'git' and cmdargv[1] in ('upload-pack', 'receive-pack'): + action = action + '-' + cmdargv[1] + del cmdargv[1] + + if action == 'git-upload-pack' or action == 'git-receive-pack': + if len(cmdargv) < 2: + die_with_help("{:s}: missing path".format(action)) + + path = cmdargv[1].rstrip('/') + if not path.startswith('/'): + path = '/' + path + if not path.endswith('.git'): + path = path + '.git' + pkgbase = path[1:-4] + if not re.match(repo_regex, pkgbase): + die('{:s}: invalid repository name: {:s}'.format(action, pkgbase)) + + if action == 'git-receive-pack' and pkgbase_exists(pkgbase): + if not privileged and not pkgbase_has_write_access(pkgbase, user): + die('{:s}: permission denied: {:s}'.format(action, user)) + + os.environ["AUR_USER"] = user + os.environ["AUR_PKGBASE"] = pkgbase + os.environ["GIT_NAMESPACE"] = pkgbase + cmd = action + " '" + repo_path + "'" + os.execl(git_shell_cmd, git_shell_cmd, '-c', cmd) + elif action == 'set-keywords': + if len(cmdargv) < 2: + die_with_help("{:s}: missing repository name".format(action)) + pkgbase_set_keywords(cmdargv[1], cmdargv[2:]) + elif action == 'list-repos': + if len(cmdargv) > 1: + die_with_help("{:s}: too many arguments".format(action)) + list_repos(user) + elif action == 'setup-repo': + if len(cmdargv) < 2: + die_with_help("{:s}: missing repository name".format(action)) + if len(cmdargv) > 2: + die_with_help("{:s}: too many arguments".format(action)) + warn('{:s} is deprecated. ' + 'Use `git push` to create new repositories.'.format(action)) + create_pkgbase(cmdargv[1], user) + elif action == 'restore': + if len(cmdargv) < 2: + die_with_help("{:s}: missing repository name".format(action)) + if len(cmdargv) > 2: + die_with_help("{:s}: too many arguments".format(action)) + + pkgbase = cmdargv[1] + if not re.match(repo_regex, pkgbase): + die('{:s}: invalid repository name: {:s}'.format(action, pkgbase)) + + if pkgbase_exists(pkgbase): + die('{:s}: package base exists: {:s}'.format(action, pkgbase)) + create_pkgbase(pkgbase, user) + + os.environ["AUR_USER"] = user + os.environ["AUR_PKGBASE"] = pkgbase + os.execl(git_update_cmd, git_update_cmd, 'restore') + elif action == 'adopt': + if len(cmdargv) < 2: + die_with_help("{:s}: missing repository name".format(action)) + if len(cmdargv) > 2: + die_with_help("{:s}: too many arguments".format(action)) + + pkgbase = cmdargv[1] + pkgbase_adopt(pkgbase, user, privileged) + elif action == 'disown': + if len(cmdargv) < 2: + die_with_help("{:s}: missing repository name".format(action)) + if len(cmdargv) > 2: + die_with_help("{:s}: too many arguments".format(action)) + + pkgbase = cmdargv[1] + pkgbase_disown(pkgbase, user, privileged) + elif action == 'set-comaintainers': + if len(cmdargv) < 2: + die_with_help("{:s}: missing repository name".format(action)) + + pkgbase = cmdargv[1] + userlist = cmdargv[2:] + pkgbase_set_comaintainers(pkgbase, userlist, user, privileged) + elif action == 'help': + cmds = { + "adopt ": "Adopt a package base.", + "disown ": "Disown a package base.", + "help": "Show this help message and exit.", + "list-repos": "List all your repositories.", + "restore ": "Restore a deleted package base.", + "set-comaintainers [...]": "Set package base co-maintainers.", + "set-keywords [...]": "Change package base keywords.", + "setup-repo ": "Create a repository (deprecated).", + "git-receive-pack": "Internal command used with Git.", + "git-upload-pack": "Internal command used with Git.", + } + usage(cmds) + else: + die_with_help("invalid command: {:s}".format(action)) + + +if __name__ == '__main__': + main() diff --git a/aurweb/git/update.py b/aurweb/git/update.py new file mode 100755 index 0000000..7337341 --- /dev/null +++ b/aurweb/git/update.py @@ -0,0 +1,419 @@ +#!/usr/bin/python3 + +import os +import pygit2 +import re +import subprocess +import sys +import time + +import srcinfo.parse +import srcinfo.utils + +import aurweb.config +import aurweb.db + +notify_cmd = aurweb.config.get('notifications', 'notify-cmd') + +repo_path = aurweb.config.get('serve', 'repo-path') +repo_regex = aurweb.config.get('serve', 'repo-regex') + +max_blob_size = aurweb.config.getint('update', 'max-blob-size') + + +def size_humanize(num): + for unit in ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB']: + if abs(num) < 2048.0: + if isinstance(num, int): + return "{}{}".format(num, unit) + else: + return "{:.2f}{}".format(num, unit) + num /= 1024.0 + return "{:.2f}{}".format(num, 'YiB') + + +def extract_arch_fields(pkginfo, field): + values = [] + + if field in pkginfo: + for val in pkginfo[field]: + values.append({"value": val, "arch": None}) + + for arch in ['i686', 'x86_64']: + if field + '_' + arch in pkginfo: + for val in pkginfo[field + '_' + arch]: + values.append({"value": val, "arch": arch}) + + return values + + +def parse_dep(depstring): + dep, _, desc = depstring.partition(': ') + depname = re.sub(r'(<|=|>).*', '', dep) + depcond = dep[len(depname):] + + if (desc): + return (depname + ': ' + desc, depcond) + else: + return (depname, depcond) + + +def create_pkgbase(conn, pkgbase, user): + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) + userid = cur.fetchone()[0] + + now = int(time.time()) + cur = conn.execute("INSERT INTO PackageBases (Name, SubmittedTS, " + + "ModifiedTS, SubmitterUID, MaintainerUID) VALUES " + + "(?, ?, ?, ?, ?)", [pkgbase, now, now, userid, userid]) + pkgbase_id = cur.lastrowid + + cur = conn.execute("INSERT INTO PackageNotifications " + + "(PackageBaseID, UserID) VALUES (?, ?)", + [pkgbase_id, userid]) + + conn.commit() + + return pkgbase_id + + +def save_metadata(metadata, conn, user): + # Obtain package base ID and previous maintainer. + pkgbase = metadata['pkgbase'] + cur = conn.execute("SELECT ID, MaintainerUID FROM PackageBases " + "WHERE Name = ?", [pkgbase]) + (pkgbase_id, maintainer_uid) = cur.fetchone() + was_orphan = not maintainer_uid + + # Obtain the user ID of the new maintainer. + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) + user_id = int(cur.fetchone()[0]) + + # Update package base details and delete current packages. + now = int(time.time()) + conn.execute("UPDATE PackageBases SET ModifiedTS = ?, " + + "PackagerUID = ?, OutOfDateTS = NULL WHERE ID = ?", + [now, user_id, pkgbase_id]) + conn.execute("UPDATE PackageBases SET MaintainerUID = ? " + + "WHERE ID = ? AND MaintainerUID IS NULL", + [user_id, pkgbase_id]) + for table in ('Sources', 'Depends', 'Relations', 'Licenses', 'Groups'): + conn.execute("DELETE FROM Package" + table + " WHERE EXISTS (" + + "SELECT * FROM Packages " + + "WHERE Packages.PackageBaseID = ? AND " + + "Package" + table + ".PackageID = Packages.ID)", + [pkgbase_id]) + conn.execute("DELETE FROM Packages WHERE PackageBaseID = ?", [pkgbase_id]) + + for pkgname in srcinfo.utils.get_package_names(metadata): + pkginfo = srcinfo.utils.get_merged_package(pkgname, metadata) + + if 'epoch' in pkginfo and int(pkginfo['epoch']) > 0: + ver = '{:d}:{:s}-{:s}'.format(int(pkginfo['epoch']), + pkginfo['pkgver'], + pkginfo['pkgrel']) + else: + ver = '{:s}-{:s}'.format(pkginfo['pkgver'], pkginfo['pkgrel']) + + for field in ('pkgdesc', 'url'): + if field not in pkginfo: + pkginfo[field] = None + + # Create a new package. + cur = conn.execute("INSERT INTO Packages (PackageBaseID, Name, " + + "Version, Description, URL) " + + "VALUES (?, ?, ?, ?, ?)", + [pkgbase_id, pkginfo['pkgname'], ver, + pkginfo['pkgdesc'], pkginfo['url']]) + conn.commit() + pkgid = cur.lastrowid + + # Add package sources. + for source_info in extract_arch_fields(pkginfo, 'source'): + conn.execute("INSERT INTO PackageSources (PackageID, Source, " + + "SourceArch) VALUES (?, ?, ?)", + [pkgid, source_info['value'], source_info['arch']]) + + # Add package dependencies. + for deptype in ('depends', 'makedepends', + 'checkdepends', 'optdepends'): + cur = conn.execute("SELECT ID FROM DependencyTypes WHERE Name = ?", + [deptype]) + deptypeid = cur.fetchone()[0] + for dep_info in extract_arch_fields(pkginfo, deptype): + depname, depcond = parse_dep(dep_info['value']) + deparch = dep_info['arch'] + conn.execute("INSERT INTO PackageDepends (PackageID, " + + "DepTypeID, DepName, DepCondition, DepArch) " + + "VALUES (?, ?, ?, ?, ?)", + [pkgid, deptypeid, depname, depcond, deparch]) + + # Add package relations (conflicts, provides, replaces). + for reltype in ('conflicts', 'provides', 'replaces'): + cur = conn.execute("SELECT ID FROM RelationTypes WHERE Name = ?", + [reltype]) + reltypeid = cur.fetchone()[0] + for rel_info in extract_arch_fields(pkginfo, reltype): + relname, relcond = parse_dep(rel_info['value']) + relarch = rel_info['arch'] + conn.execute("INSERT INTO PackageRelations (PackageID, " + + "RelTypeID, RelName, RelCondition, RelArch) " + + "VALUES (?, ?, ?, ?, ?)", + [pkgid, reltypeid, relname, relcond, relarch]) + + # Add package licenses. + if 'license' in pkginfo: + for license in pkginfo['license']: + cur = conn.execute("SELECT ID FROM Licenses WHERE Name = ?", + [license]) + row = cur.fetchone() + if row: + licenseid = row[0] + else: + cur = conn.execute("INSERT INTO Licenses (Name) " + + "VALUES (?)", [license]) + conn.commit() + licenseid = cur.lastrowid + conn.execute("INSERT INTO PackageLicenses (PackageID, " + + "LicenseID) VALUES (?, ?)", + [pkgid, licenseid]) + + # Add package groups. + if 'groups' in pkginfo: + for group in pkginfo['groups']: + cur = conn.execute("SELECT ID FROM Groups WHERE Name = ?", + [group]) + row = cur.fetchone() + if row: + groupid = row[0] + else: + cur = conn.execute("INSERT INTO Groups (Name) VALUES (?)", + [group]) + conn.commit() + groupid = cur.lastrowid + conn.execute("INSERT INTO PackageGroups (PackageID, " + "GroupID) VALUES (?, ?)", [pkgid, groupid]) + + # Add user to notification list on adoption. + if was_orphan: + cur = conn.execute("SELECT COUNT(*) FROM PackageNotifications WHERE " + + "PackageBaseID = ? AND UserID = ?", + [pkgbase_id, user_id]) + if cur.fetchone()[0] == 0: + conn.execute("INSERT INTO PackageNotifications " + + "(PackageBaseID, UserID) VALUES (?, ?)", + [pkgbase_id, user_id]) + + conn.commit() + + +def update_notify(conn, user, pkgbase_id): + # Obtain the user ID of the new maintainer. + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) + user_id = int(cur.fetchone()[0]) + + # Execute the notification script. + subprocess.Popen((notify_cmd, 'update', str(user_id), str(pkgbase_id))) + + +def die(msg): + sys.stderr.write("error: {:s}\n".format(msg)) + exit(1) + + +def warn(msg): + sys.stderr.write("warning: {:s}\n".format(msg)) + + +def die_commit(msg, commit): + sys.stderr.write("error: The following error " + + "occurred when parsing commit\n") + sys.stderr.write("error: {:s}:\n".format(commit)) + sys.stderr.write("error: {:s}\n".format(msg)) + exit(1) + + +def main(): + repo = pygit2.Repository(repo_path) + + user = os.environ.get("AUR_USER") + pkgbase = os.environ.get("AUR_PKGBASE") + privileged = (os.environ.get("AUR_PRIVILEGED", '0') == '1') + warn_or_die = warn if privileged else die + + if len(sys.argv) == 2 and sys.argv[1] == "restore": + if 'refs/heads/' + pkgbase not in repo.listall_references(): + die('{:s}: repository not found: {:s}'.format(sys.argv[1], + pkgbase)) + refname = "refs/heads/master" + branchref = 'refs/heads/' + pkgbase + sha1_old = sha1_new = repo.lookup_reference(branchref).target + elif len(sys.argv) == 4: + refname, sha1_old, sha1_new = sys.argv[1:4] + else: + die("invalid arguments") + + if refname != "refs/heads/master": + die("pushing to a branch other than master is restricted") + + conn = aurweb.db.Connection() + + # Detect and deny non-fast-forwards. + if sha1_old != "0" * 40 and not privileged: + walker = repo.walk(sha1_old, pygit2.GIT_SORT_TOPOLOGICAL) + walker.hide(sha1_new) + if next(walker, None) is not None: + die("denying non-fast-forward (you should pull first)") + + # Prepare the walker that validates new commits. + walker = repo.walk(sha1_new, pygit2.GIT_SORT_TOPOLOGICAL) + if sha1_old != "0" * 40: + walker.hide(sha1_old) + + # Validate all new commits. + for commit in walker: + for fname in ('.SRCINFO', 'PKGBUILD'): + if fname not in commit.tree: + die_commit("missing {:s}".format(fname), str(commit.id)) + + for treeobj in commit.tree: + blob = repo[treeobj.id] + + if isinstance(blob, pygit2.Tree): + die_commit("the repository must not contain subdirectories", + str(commit.id)) + + if not isinstance(blob, pygit2.Blob): + die_commit("not a blob object: {:s}".format(treeobj), + str(commit.id)) + + if blob.size > max_blob_size: + die_commit("maximum blob size ({:s}) exceeded".format( + size_humanize(max_blob_size)), str(commit.id)) + + metadata_raw = repo[commit.tree['.SRCINFO'].id].data.decode() + (metadata, errors) = srcinfo.parse.parse_srcinfo(metadata_raw) + if errors: + sys.stderr.write("error: The following errors occurred " + "when parsing .SRCINFO in commit\n") + sys.stderr.write("error: {:s}:\n".format(str(commit.id))) + for error in errors: + for err in error['error']: + sys.stderr.write("error: line {:d}: {:s}\n".format( + error['line'], err)) + exit(1) + + metadata_pkgbase = metadata['pkgbase'] + if not re.match(repo_regex, metadata_pkgbase): + die_commit('invalid pkgbase: {:s}'.format(metadata_pkgbase), + str(commit.id)) + + for pkgname in set(metadata['packages'].keys()): + pkginfo = srcinfo.utils.get_merged_package(pkgname, metadata) + + for field in ('pkgver', 'pkgrel', 'pkgname'): + if field not in pkginfo: + die_commit('missing mandatory field: {:s}'.format(field), + str(commit.id)) + + if 'epoch' in pkginfo and not pkginfo['epoch'].isdigit(): + die_commit('invalid epoch: {:s}'.format(pkginfo['epoch']), + str(commit.id)) + + if not re.match(r'[a-z0-9][a-z0-9\.+_-]*$', pkginfo['pkgname']): + die_commit('invalid package name: {:s}'.format( + pkginfo['pkgname']), str(commit.id)) + + for field in ('pkgname', 'pkgdesc', 'url'): + if field in pkginfo and len(pkginfo[field]) > 255: + die_commit('{:s} field too long: {:s}'.format(field, + pkginfo[field]), str(commit.id)) + + for field in ('install', 'changelog'): + if field in pkginfo and not pkginfo[field] in commit.tree: + die_commit('missing {:s} file: {:s}'.format(field, + pkginfo[field]), str(commit.id)) + + for field in extract_arch_fields(pkginfo, 'source'): + fname = field['value'] + if "://" in fname or "lp:" in fname: + continue + if fname not in commit.tree: + die_commit('missing source file: {:s}'.format(fname), + str(commit.id)) + + # Display a warning if .SRCINFO is unchanged. + if sha1_old not in ("0000000000000000000000000000000000000000", sha1_new): + srcinfo_id_old = repo[sha1_old].tree['.SRCINFO'].id + srcinfo_id_new = repo[sha1_new].tree['.SRCINFO'].id + if srcinfo_id_old == srcinfo_id_new: + warn(".SRCINFO unchanged. " + "The package database will not be updated!") + + # Read .SRCINFO from the HEAD commit. + metadata_raw = repo[repo[sha1_new].tree['.SRCINFO'].id].data.decode() + (metadata, errors) = srcinfo.parse.parse_srcinfo(metadata_raw) + + # Ensure that the package base name matches the repository name. + metadata_pkgbase = metadata['pkgbase'] + if metadata_pkgbase != pkgbase: + die('invalid pkgbase: {:s}, expected {:s}'.format(metadata_pkgbase, + pkgbase)) + + # Ensure that packages are neither blacklisted nor overwritten. + pkgbase = metadata['pkgbase'] + cur = conn.execute("SELECT ID FROM PackageBases WHERE Name = ?", [pkgbase]) + row = cur.fetchone() + pkgbase_id = row[0] if row else 0 + + cur = conn.execute("SELECT Name FROM PackageBlacklist") + blacklist = [row[0] for row in cur.fetchall()] + + cur = conn.execute("SELECT Name, Repo FROM OfficialProviders") + providers = dict(cur.fetchall()) + + for pkgname in srcinfo.utils.get_package_names(metadata): + pkginfo = srcinfo.utils.get_merged_package(pkgname, metadata) + pkgname = pkginfo['pkgname'] + + if pkgname in blacklist: + warn_or_die('package is blacklisted: {:s}'.format(pkgname)) + if pkgname in providers: + warn_or_die('package already provided by [{:s}]: {:s}'.format( + providers[pkgname], pkgname)) + + cur = conn.execute("SELECT COUNT(*) FROM Packages WHERE Name = ? " + + "AND PackageBaseID <> ?", [pkgname, pkgbase_id]) + if cur.fetchone()[0] > 0: + die('cannot overwrite package: {:s}'.format(pkgname)) + + # Create a new package base if it does not exist yet. + if pkgbase_id == 0: + pkgbase_id = create_pkgbase(conn, pkgbase, user) + + # Store package base details in the database. + save_metadata(metadata, conn, user) + + # Create (or update) a branch with the name of the package base for better + # accessibility. + branchref = 'refs/heads/' + pkgbase + repo.create_reference(branchref, sha1_new, True) + + # Work around a Git bug: The HEAD ref is not updated when using + # gitnamespaces. This can be removed once the bug fix is included in Git + # mainline. See + # http://git.661346.n2.nabble.com/PATCH-receive-pack-Create-a-HEAD-ref-for-ref-namespace-td7632149.html + # for details. + headref = 'refs/namespaces/' + pkgbase + '/HEAD' + repo.create_reference(headref, sha1_new, True) + + # Send package update notifications. + update_notify(conn, user, pkgbase_id) + + # Close the database. + cur.close() + conn.close() + + +if __name__ == '__main__': + main() diff --git a/conf/config.proto b/conf/config.proto index 21441a9..96fad80 100644 --- a/conf/config.proto +++ b/conf/config.proto @@ -46,14 +46,14 @@ RSA = SHA256:Ju+yWiMb/2O+gKQ9RJCDqvRg7l+Q95KFAeqM5sr6l2s [auth] valid-keytypes = ssh-rsa ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 username-regex = [a-zA-Z0-9]+[.\-_]?[a-zA-Z0-9]+$ -git-serve-cmd = /srv/http/aurweb/git-interface/git-serve.py +git-serve-cmd = /usr/local/bin/aurweb-git-serve ssh-options = restrict [serve] repo-path = /srv/http/aurweb/aur.git/ repo-regex = [a-z0-9][a-z0-9.+_-]*$ git-shell-cmd = /usr/bin/git-shell -git-update-cmd = /srv/http/aurweb/git-interface/git-update.py +git-update-cmd = /usr/local/bin/aurweb-git-update ssh-cmdline = ssh aur@aur.archlinux.org [update] diff --git a/git-interface/Makefile b/git-interface/Makefile deleted file mode 100644 index 8865790..0000000 --- a/git-interface/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -GIT_INTERFACE_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) - -include config.mk - -git-auth.sh: - sed 's#%GIT_INTERFACE_DIR%#$(GIT_INTERFACE_DIR)#' git-auth.sh - chmod +x git-auth.sh - -install: git-auth.sh - install -Dm0755 git-auth.sh "$(DESTDIR)$(PREFIX)/bin/aur-git-auth" - -uninstall: - rm -f "$(DESTDIR)$(PREFIX)/bin/aur-git-auth" - -clean: - rm -f git-auth.sh - -.PHONY: install uninstall clean diff --git a/git-interface/__init__.py b/git-interface/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/git-interface/config.mk b/git-interface/config.mk deleted file mode 100644 index 4d794a1..0000000 --- a/git-interface/config.mk +++ /dev/null @@ -1 +0,0 @@ -PREFIX = /usr/local diff --git a/git-interface/git-auth.py b/git-interface/git-auth.py deleted file mode 100755 index 022b0ff..0000000 --- a/git-interface/git-auth.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/python3 - -import shlex -import re -import sys - -import aurweb.config -import aurweb.db - - -def format_command(env_vars, command, ssh_opts, ssh_key): - environment = '' - for key, var in env_vars.items(): - environment += '{}={} '.format(key, shlex.quote(var)) - - command = shlex.quote(command) - command = '{}{}'.format(environment, command) - - # The command is being substituted into an authorized_keys line below, - # so we need to escape the double quotes. - command = command.replace('"', '\\"') - msg = 'command="{}",{} {}'.format(command, ssh_opts, ssh_key) - return msg - - -def main(): - valid_keytypes = aurweb.config.get('auth', 'valid-keytypes').split() - username_regex = aurweb.config.get('auth', 'username-regex') - git_serve_cmd = aurweb.config.get('auth', 'git-serve-cmd') - ssh_opts = aurweb.config.get('auth', 'ssh-options') - - keytype = sys.argv[1] - keytext = sys.argv[2] - if keytype not in valid_keytypes: - exit(1) - - conn = aurweb.db.Connection() - - cur = conn.execute("SELECT Users.Username, Users.AccountTypeID FROM Users " - "INNER JOIN SSHPubKeys ON SSHPubKeys.UserID = Users.ID " - "WHERE SSHPubKeys.PubKey = ? AND Users.Suspended = 0", - (keytype + " " + keytext,)) - - row = cur.fetchone() - if not row or cur.fetchone(): - exit(1) - - user, account_type = row - if not re.match(username_regex, user): - exit(1) - - env_vars = { - 'AUR_USER': user, - 'AUR_PRIVILEGED': '1' if account_type > 1 else '0', - } - key = keytype + ' ' + keytext - - print(format_command(env_vars, git_serve_cmd, ssh_opts, key)) - - -if __name__ == '__main__': - main() diff --git a/git-interface/git-auth.sh.in b/git-interface/git-auth.sh.in deleted file mode 100644 index 223816a..0000000 --- a/git-interface/git-auth.sh.in +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -%GIT_INTERFACE_DIR%/git-auth.py "$1" "$2" diff --git a/git-interface/git-serve.py b/git-interface/git-serve.py deleted file mode 100755 index ebfef94..0000000 --- a/git-interface/git-serve.py +++ /dev/null @@ -1,409 +0,0 @@ -#!/usr/bin/python3 - -import os -import re -import shlex -import subprocess -import sys -import time - -import aurweb.config -import aurweb.db - -notify_cmd = aurweb.config.get('notifications', 'notify-cmd') - -repo_path = aurweb.config.get('serve', 'repo-path') -repo_regex = aurweb.config.get('serve', 'repo-regex') -git_shell_cmd = aurweb.config.get('serve', 'git-shell-cmd') -git_update_cmd = aurweb.config.get('serve', 'git-update-cmd') -ssh_cmdline = aurweb.config.get('serve', 'ssh-cmdline') - -enable_maintenance = aurweb.config.getboolean('options', 'enable-maintenance') -maintenance_exc = aurweb.config.get('options', 'maintenance-exceptions').split() - - -def pkgbase_from_name(pkgbase): - conn = aurweb.db.Connection() - cur = conn.execute("SELECT ID FROM PackageBases WHERE Name = ?", [pkgbase]) - - row = cur.fetchone() - return row[0] if row else None - - -def pkgbase_exists(pkgbase): - return pkgbase_from_name(pkgbase) is not None - - -def list_repos(user): - conn = aurweb.db.Connection() - - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) - userid = cur.fetchone()[0] - if userid == 0: - die('{:s}: unknown user: {:s}'.format(action, user)) - - cur = conn.execute("SELECT Name, PackagerUID FROM PackageBases " + - "WHERE MaintainerUID = ?", [userid]) - for row in cur: - print((' ' if row[1] else '*') + row[0]) - conn.close() - - -def create_pkgbase(pkgbase, user): - if not re.match(repo_regex, pkgbase): - die('{:s}: invalid repository name: {:s}'.format(action, pkgbase)) - if pkgbase_exists(pkgbase): - die('{:s}: package base already exists: {:s}'.format(action, pkgbase)) - - conn = aurweb.db.Connection() - - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) - userid = cur.fetchone()[0] - if userid == 0: - die('{:s}: unknown user: {:s}'.format(action, user)) - - now = int(time.time()) - cur = conn.execute("INSERT INTO PackageBases (Name, SubmittedTS, " + - "ModifiedTS, SubmitterUID, MaintainerUID) VALUES " + - "(?, ?, ?, ?, ?)", [pkgbase, now, now, userid, userid]) - pkgbase_id = cur.lastrowid - - cur = conn.execute("INSERT INTO PackageNotifications " + - "(PackageBaseID, UserID) VALUES (?, ?)", - [pkgbase_id, userid]) - - conn.commit() - conn.close() - - -def pkgbase_adopt(pkgbase, user, privileged): - pkgbase_id = pkgbase_from_name(pkgbase) - if not pkgbase_id: - die('{:s}: package base not found: {:s}'.format(action, pkgbase)) - - conn = aurweb.db.Connection() - - cur = conn.execute("SELECT ID FROM PackageBases WHERE ID = ? AND " + - "MaintainerUID IS NULL", [pkgbase_id]) - if not privileged and not cur.fetchone(): - die('{:s}: permission denied: {:s}'.format(action, user)) - - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) - userid = cur.fetchone()[0] - if userid == 0: - die('{:s}: unknown user: {:s}'.format(action, user)) - - cur = conn.execute("UPDATE PackageBases SET MaintainerUID = ? " + - "WHERE ID = ?", [userid, pkgbase_id]) - - cur = conn.execute("SELECT COUNT(*) FROM PackageNotifications WHERE " + - "PackageBaseID = ? AND UserID = ?", - [pkgbase_id, userid]) - if cur.fetchone()[0] == 0: - cur = conn.execute("INSERT INTO PackageNotifications " + - "(PackageBaseID, UserID) VALUES (?, ?)", - [pkgbase_id, userid]) - conn.commit() - - subprocess.Popen((notify_cmd, 'adopt', str(pkgbase_id), str(userid))) - - conn.close() - - -def pkgbase_get_comaintainers(pkgbase): - conn = aurweb.db.Connection() - - cur = conn.execute("SELECT UserName FROM PackageComaintainers " + - "INNER JOIN Users " + - "ON Users.ID = PackageComaintainers.UsersID " + - "INNER JOIN PackageBases " + - "ON PackageBases.ID = PackageComaintainers.PackageBaseID " + - "WHERE PackageBases.Name = ? " + - "ORDER BY Priority ASC", [pkgbase]) - - return [row[0] for row in cur.fetchall()] - - -def pkgbase_set_comaintainers(pkgbase, userlist, user, privileged): - pkgbase_id = pkgbase_from_name(pkgbase) - if not pkgbase_id: - die('{:s}: package base not found: {:s}'.format(action, pkgbase)) - - if not privileged and not pkgbase_has_full_access(pkgbase, user): - die('{:s}: permission denied: {:s}'.format(action, user)) - - conn = aurweb.db.Connection() - - userlist_old = set(pkgbase_get_comaintainers(pkgbase)) - - uids_old = set() - for olduser in userlist_old: - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", - [olduser]) - userid = cur.fetchone()[0] - if userid == 0: - die('{:s}: unknown user: {:s}'.format(action, user)) - uids_old.add(userid) - - uids_new = set() - for newuser in userlist: - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", - [newuser]) - userid = cur.fetchone()[0] - if userid == 0: - die('{:s}: unknown user: {:s}'.format(action, user)) - uids_new.add(userid) - - uids_add = uids_new - uids_old - uids_rem = uids_old - uids_new - - i = 1 - for userid in uids_new: - if userid in uids_add: - cur = conn.execute("INSERT INTO PackageComaintainers " + - "(PackageBaseID, UsersID, Priority) " + - "VALUES (?, ?, ?)", [pkgbase_id, userid, i]) - subprocess.Popen((notify_cmd, 'comaintainer-add', str(pkgbase_id), - str(userid))) - else: - cur = conn.execute("UPDATE PackageComaintainers " + - "SET Priority = ? " + - "WHERE PackageBaseID = ? AND UsersID = ?", - [i, pkgbase_id, userid]) - i += 1 - - for userid in uids_rem: - cur = conn.execute("DELETE FROM PackageComaintainers " + - "WHERE PackageBaseID = ? AND UsersID = ?", - [pkgbase_id, userid]) - subprocess.Popen((notify_cmd, 'comaintainer-remove', - str(pkgbase_id), str(userid))) - - conn.commit() - conn.close() - - -def pkgbase_disown(pkgbase, user, privileged): - pkgbase_id = pkgbase_from_name(pkgbase) - if not pkgbase_id: - die('{:s}: package base not found: {:s}'.format(action, pkgbase)) - - initialized_by_owner = pkgbase_has_full_access(pkgbase, user) - if not privileged and not initialized_by_owner: - die('{:s}: permission denied: {:s}'.format(action, user)) - - # TODO: Support disowning package bases via package request. - # TODO: Scan through pending orphan requests and close them. - - comaintainers = [] - new_maintainer_userid = None - - conn = aurweb.db.Connection() - - # Make the first co-maintainer the new maintainer, unless the action was - # enforced by a Trusted User. - if initialized_by_owner: - comaintainers = pkgbase_get_comaintainers(pkgbase) - if len(comaintainers) > 0: - new_maintainer = comaintainers[0] - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", - [new_maintainer]) - new_maintainer_userid = cur.fetchone()[0] - comaintainers.remove(new_maintainer) - - pkgbase_set_comaintainers(pkgbase, comaintainers, user, privileged) - cur = conn.execute("UPDATE PackageBases SET MaintainerUID = ? " + - "WHERE ID = ?", [new_maintainer_userid, pkgbase_id]) - - conn.commit() - - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) - userid = cur.fetchone()[0] - if userid == 0: - die('{:s}: unknown user: {:s}'.format(action, user)) - - subprocess.Popen((notify_cmd, 'disown', str(pkgbase_id), str(userid))) - - conn.close() - - -def pkgbase_set_keywords(pkgbase, keywords): - pkgbase_id = pkgbase_from_name(pkgbase) - if not pkgbase_id: - die('{:s}: package base not found: {:s}'.format(action, pkgbase)) - - conn = aurweb.db.Connection() - - conn.execute("DELETE FROM PackageKeywords WHERE PackageBaseID = ?", - [pkgbase_id]) - for keyword in keywords: - conn.execute("INSERT INTO PackageKeywords (PackageBaseID, Keyword) " + - "VALUES (?, ?)", [pkgbase_id, keyword]) - - conn.commit() - conn.close() - - -def pkgbase_has_write_access(pkgbase, user): - conn = aurweb.db.Connection() - - cur = conn.execute("SELECT COUNT(*) FROM PackageBases " + - "LEFT JOIN PackageComaintainers " + - "ON PackageComaintainers.PackageBaseID = PackageBases.ID " + - "INNER JOIN Users " + - "ON Users.ID = PackageBases.MaintainerUID " + - "OR PackageBases.MaintainerUID IS NULL " + - "OR Users.ID = PackageComaintainers.UsersID " + - "WHERE Name = ? AND Username = ?", [pkgbase, user]) - return cur.fetchone()[0] > 0 - - -def pkgbase_has_full_access(pkgbase, user): - conn = aurweb.db.Connection() - - cur = conn.execute("SELECT COUNT(*) FROM PackageBases " + - "INNER JOIN Users " + - "ON Users.ID = PackageBases.MaintainerUID " + - "WHERE Name = ? AND Username = ?", [pkgbase, user]) - return cur.fetchone()[0] > 0 - - -def die(msg): - sys.stderr.write("{:s}\n".format(msg)) - exit(1) - - -def die_with_help(msg): - die(msg + "\nTry `{:s} help` for a list of commands.".format(ssh_cmdline)) - - -def warn(msg): - sys.stderr.write("warning: {:s}\n".format(msg)) - - -def usage(cmds): - sys.stderr.write("Commands:\n") - colwidth = max([len(cmd) for cmd in cmds.keys()]) + 4 - for key in sorted(cmds): - sys.stderr.write(" " + key.ljust(colwidth) + cmds[key] + "\n") - exit(0) - - -def main(): - user = os.environ.get('AUR_USER') - privileged = (os.environ.get('AUR_PRIVILEGED', '0') == '1') - ssh_cmd = os.environ.get('SSH_ORIGINAL_COMMAND') - ssh_client = os.environ.get('SSH_CLIENT') - - if not ssh_cmd: - die_with_help("Interactive shell is disabled.") - cmdargv = shlex.split(ssh_cmd) - action = cmdargv[0] - remote_addr = ssh_client.split(' ')[0] if ssh_client else None - - if enable_maintenance: - if remote_addr not in maintenance_exc: - die("The AUR is down due to maintenance. We will be back soon.") - - if action == 'git' and cmdargv[1] in ('upload-pack', 'receive-pack'): - action = action + '-' + cmdargv[1] - del cmdargv[1] - - if action == 'git-upload-pack' or action == 'git-receive-pack': - if len(cmdargv) < 2: - die_with_help("{:s}: missing path".format(action)) - - path = cmdargv[1].rstrip('/') - if not path.startswith('/'): - path = '/' + path - if not path.endswith('.git'): - path = path + '.git' - pkgbase = path[1:-4] - if not re.match(repo_regex, pkgbase): - die('{:s}: invalid repository name: {:s}'.format(action, pkgbase)) - - if action == 'git-receive-pack' and pkgbase_exists(pkgbase): - if not privileged and not pkgbase_has_write_access(pkgbase, user): - die('{:s}: permission denied: {:s}'.format(action, user)) - - os.environ["AUR_USER"] = user - os.environ["AUR_PKGBASE"] = pkgbase - os.environ["GIT_NAMESPACE"] = pkgbase - cmd = action + " '" + repo_path + "'" - os.execl(git_shell_cmd, git_shell_cmd, '-c', cmd) - elif action == 'set-keywords': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) - pkgbase_set_keywords(cmdargv[1], cmdargv[2:]) - elif action == 'list-repos': - if len(cmdargv) > 1: - die_with_help("{:s}: too many arguments".format(action)) - list_repos(user) - elif action == 'setup-repo': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) - if len(cmdargv) > 2: - die_with_help("{:s}: too many arguments".format(action)) - warn('{:s} is deprecated. ' - 'Use `git push` to create new repositories.'.format(action)) - create_pkgbase(cmdargv[1], user) - elif action == 'restore': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) - if len(cmdargv) > 2: - die_with_help("{:s}: too many arguments".format(action)) - - pkgbase = cmdargv[1] - if not re.match(repo_regex, pkgbase): - die('{:s}: invalid repository name: {:s}'.format(action, pkgbase)) - - if pkgbase_exists(pkgbase): - die('{:s}: package base exists: {:s}'.format(action, pkgbase)) - create_pkgbase(pkgbase, user) - - os.environ["AUR_USER"] = user - os.environ["AUR_PKGBASE"] = pkgbase - os.execl(git_update_cmd, git_update_cmd, 'restore') - elif action == 'adopt': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) - if len(cmdargv) > 2: - die_with_help("{:s}: too many arguments".format(action)) - - pkgbase = cmdargv[1] - pkgbase_adopt(pkgbase, user, privileged) - elif action == 'disown': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) - if len(cmdargv) > 2: - die_with_help("{:s}: too many arguments".format(action)) - - pkgbase = cmdargv[1] - pkgbase_disown(pkgbase, user, privileged) - elif action == 'set-comaintainers': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) - - pkgbase = cmdargv[1] - userlist = cmdargv[2:] - pkgbase_set_comaintainers(pkgbase, userlist, user, privileged) - elif action == 'help': - cmds = { - "adopt ": "Adopt a package base.", - "disown ": "Disown a package base.", - "help": "Show this help message and exit.", - "list-repos": "List all your repositories.", - "restore ": "Restore a deleted package base.", - "set-comaintainers [...]": "Set package base co-maintainers.", - "set-keywords [...]": "Change package base keywords.", - "setup-repo ": "Create a repository (deprecated).", - "git-receive-pack": "Internal command used with Git.", - "git-upload-pack": "Internal command used with Git.", - } - usage(cmds) - else: - die_with_help("invalid command: {:s}".format(action)) - - -if __name__ == '__main__': - main() diff --git a/git-interface/git-update.py b/git-interface/git-update.py deleted file mode 100755 index 7337341..0000000 --- a/git-interface/git-update.py +++ /dev/null @@ -1,419 +0,0 @@ -#!/usr/bin/python3 - -import os -import pygit2 -import re -import subprocess -import sys -import time - -import srcinfo.parse -import srcinfo.utils - -import aurweb.config -import aurweb.db - -notify_cmd = aurweb.config.get('notifications', 'notify-cmd') - -repo_path = aurweb.config.get('serve', 'repo-path') -repo_regex = aurweb.config.get('serve', 'repo-regex') - -max_blob_size = aurweb.config.getint('update', 'max-blob-size') - - -def size_humanize(num): - for unit in ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB']: - if abs(num) < 2048.0: - if isinstance(num, int): - return "{}{}".format(num, unit) - else: - return "{:.2f}{}".format(num, unit) - num /= 1024.0 - return "{:.2f}{}".format(num, 'YiB') - - -def extract_arch_fields(pkginfo, field): - values = [] - - if field in pkginfo: - for val in pkginfo[field]: - values.append({"value": val, "arch": None}) - - for arch in ['i686', 'x86_64']: - if field + '_' + arch in pkginfo: - for val in pkginfo[field + '_' + arch]: - values.append({"value": val, "arch": arch}) - - return values - - -def parse_dep(depstring): - dep, _, desc = depstring.partition(': ') - depname = re.sub(r'(<|=|>).*', '', dep) - depcond = dep[len(depname):] - - if (desc): - return (depname + ': ' + desc, depcond) - else: - return (depname, depcond) - - -def create_pkgbase(conn, pkgbase, user): - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) - userid = cur.fetchone()[0] - - now = int(time.time()) - cur = conn.execute("INSERT INTO PackageBases (Name, SubmittedTS, " + - "ModifiedTS, SubmitterUID, MaintainerUID) VALUES " + - "(?, ?, ?, ?, ?)", [pkgbase, now, now, userid, userid]) - pkgbase_id = cur.lastrowid - - cur = conn.execute("INSERT INTO PackageNotifications " + - "(PackageBaseID, UserID) VALUES (?, ?)", - [pkgbase_id, userid]) - - conn.commit() - - return pkgbase_id - - -def save_metadata(metadata, conn, user): - # Obtain package base ID and previous maintainer. - pkgbase = metadata['pkgbase'] - cur = conn.execute("SELECT ID, MaintainerUID FROM PackageBases " - "WHERE Name = ?", [pkgbase]) - (pkgbase_id, maintainer_uid) = cur.fetchone() - was_orphan = not maintainer_uid - - # Obtain the user ID of the new maintainer. - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) - user_id = int(cur.fetchone()[0]) - - # Update package base details and delete current packages. - now = int(time.time()) - conn.execute("UPDATE PackageBases SET ModifiedTS = ?, " + - "PackagerUID = ?, OutOfDateTS = NULL WHERE ID = ?", - [now, user_id, pkgbase_id]) - conn.execute("UPDATE PackageBases SET MaintainerUID = ? " + - "WHERE ID = ? AND MaintainerUID IS NULL", - [user_id, pkgbase_id]) - for table in ('Sources', 'Depends', 'Relations', 'Licenses', 'Groups'): - conn.execute("DELETE FROM Package" + table + " WHERE EXISTS (" + - "SELECT * FROM Packages " + - "WHERE Packages.PackageBaseID = ? AND " + - "Package" + table + ".PackageID = Packages.ID)", - [pkgbase_id]) - conn.execute("DELETE FROM Packages WHERE PackageBaseID = ?", [pkgbase_id]) - - for pkgname in srcinfo.utils.get_package_names(metadata): - pkginfo = srcinfo.utils.get_merged_package(pkgname, metadata) - - if 'epoch' in pkginfo and int(pkginfo['epoch']) > 0: - ver = '{:d}:{:s}-{:s}'.format(int(pkginfo['epoch']), - pkginfo['pkgver'], - pkginfo['pkgrel']) - else: - ver = '{:s}-{:s}'.format(pkginfo['pkgver'], pkginfo['pkgrel']) - - for field in ('pkgdesc', 'url'): - if field not in pkginfo: - pkginfo[field] = None - - # Create a new package. - cur = conn.execute("INSERT INTO Packages (PackageBaseID, Name, " + - "Version, Description, URL) " + - "VALUES (?, ?, ?, ?, ?)", - [pkgbase_id, pkginfo['pkgname'], ver, - pkginfo['pkgdesc'], pkginfo['url']]) - conn.commit() - pkgid = cur.lastrowid - - # Add package sources. - for source_info in extract_arch_fields(pkginfo, 'source'): - conn.execute("INSERT INTO PackageSources (PackageID, Source, " + - "SourceArch) VALUES (?, ?, ?)", - [pkgid, source_info['value'], source_info['arch']]) - - # Add package dependencies. - for deptype in ('depends', 'makedepends', - 'checkdepends', 'optdepends'): - cur = conn.execute("SELECT ID FROM DependencyTypes WHERE Name = ?", - [deptype]) - deptypeid = cur.fetchone()[0] - for dep_info in extract_arch_fields(pkginfo, deptype): - depname, depcond = parse_dep(dep_info['value']) - deparch = dep_info['arch'] - conn.execute("INSERT INTO PackageDepends (PackageID, " + - "DepTypeID, DepName, DepCondition, DepArch) " + - "VALUES (?, ?, ?, ?, ?)", - [pkgid, deptypeid, depname, depcond, deparch]) - - # Add package relations (conflicts, provides, replaces). - for reltype in ('conflicts', 'provides', 'replaces'): - cur = conn.execute("SELECT ID FROM RelationTypes WHERE Name = ?", - [reltype]) - reltypeid = cur.fetchone()[0] - for rel_info in extract_arch_fields(pkginfo, reltype): - relname, relcond = parse_dep(rel_info['value']) - relarch = rel_info['arch'] - conn.execute("INSERT INTO PackageRelations (PackageID, " + - "RelTypeID, RelName, RelCondition, RelArch) " + - "VALUES (?, ?, ?, ?, ?)", - [pkgid, reltypeid, relname, relcond, relarch]) - - # Add package licenses. - if 'license' in pkginfo: - for license in pkginfo['license']: - cur = conn.execute("SELECT ID FROM Licenses WHERE Name = ?", - [license]) - row = cur.fetchone() - if row: - licenseid = row[0] - else: - cur = conn.execute("INSERT INTO Licenses (Name) " + - "VALUES (?)", [license]) - conn.commit() - licenseid = cur.lastrowid - conn.execute("INSERT INTO PackageLicenses (PackageID, " + - "LicenseID) VALUES (?, ?)", - [pkgid, licenseid]) - - # Add package groups. - if 'groups' in pkginfo: - for group in pkginfo['groups']: - cur = conn.execute("SELECT ID FROM Groups WHERE Name = ?", - [group]) - row = cur.fetchone() - if row: - groupid = row[0] - else: - cur = conn.execute("INSERT INTO Groups (Name) VALUES (?)", - [group]) - conn.commit() - groupid = cur.lastrowid - conn.execute("INSERT INTO PackageGroups (PackageID, " - "GroupID) VALUES (?, ?)", [pkgid, groupid]) - - # Add user to notification list on adoption. - if was_orphan: - cur = conn.execute("SELECT COUNT(*) FROM PackageNotifications WHERE " + - "PackageBaseID = ? AND UserID = ?", - [pkgbase_id, user_id]) - if cur.fetchone()[0] == 0: - conn.execute("INSERT INTO PackageNotifications " + - "(PackageBaseID, UserID) VALUES (?, ?)", - [pkgbase_id, user_id]) - - conn.commit() - - -def update_notify(conn, user, pkgbase_id): - # Obtain the user ID of the new maintainer. - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) - user_id = int(cur.fetchone()[0]) - - # Execute the notification script. - subprocess.Popen((notify_cmd, 'update', str(user_id), str(pkgbase_id))) - - -def die(msg): - sys.stderr.write("error: {:s}\n".format(msg)) - exit(1) - - -def warn(msg): - sys.stderr.write("warning: {:s}\n".format(msg)) - - -def die_commit(msg, commit): - sys.stderr.write("error: The following error " + - "occurred when parsing commit\n") - sys.stderr.write("error: {:s}:\n".format(commit)) - sys.stderr.write("error: {:s}\n".format(msg)) - exit(1) - - -def main(): - repo = pygit2.Repository(repo_path) - - user = os.environ.get("AUR_USER") - pkgbase = os.environ.get("AUR_PKGBASE") - privileged = (os.environ.get("AUR_PRIVILEGED", '0') == '1') - warn_or_die = warn if privileged else die - - if len(sys.argv) == 2 and sys.argv[1] == "restore": - if 'refs/heads/' + pkgbase not in repo.listall_references(): - die('{:s}: repository not found: {:s}'.format(sys.argv[1], - pkgbase)) - refname = "refs/heads/master" - branchref = 'refs/heads/' + pkgbase - sha1_old = sha1_new = repo.lookup_reference(branchref).target - elif len(sys.argv) == 4: - refname, sha1_old, sha1_new = sys.argv[1:4] - else: - die("invalid arguments") - - if refname != "refs/heads/master": - die("pushing to a branch other than master is restricted") - - conn = aurweb.db.Connection() - - # Detect and deny non-fast-forwards. - if sha1_old != "0" * 40 and not privileged: - walker = repo.walk(sha1_old, pygit2.GIT_SORT_TOPOLOGICAL) - walker.hide(sha1_new) - if next(walker, None) is not None: - die("denying non-fast-forward (you should pull first)") - - # Prepare the walker that validates new commits. - walker = repo.walk(sha1_new, pygit2.GIT_SORT_TOPOLOGICAL) - if sha1_old != "0" * 40: - walker.hide(sha1_old) - - # Validate all new commits. - for commit in walker: - for fname in ('.SRCINFO', 'PKGBUILD'): - if fname not in commit.tree: - die_commit("missing {:s}".format(fname), str(commit.id)) - - for treeobj in commit.tree: - blob = repo[treeobj.id] - - if isinstance(blob, pygit2.Tree): - die_commit("the repository must not contain subdirectories", - str(commit.id)) - - if not isinstance(blob, pygit2.Blob): - die_commit("not a blob object: {:s}".format(treeobj), - str(commit.id)) - - if blob.size > max_blob_size: - die_commit("maximum blob size ({:s}) exceeded".format( - size_humanize(max_blob_size)), str(commit.id)) - - metadata_raw = repo[commit.tree['.SRCINFO'].id].data.decode() - (metadata, errors) = srcinfo.parse.parse_srcinfo(metadata_raw) - if errors: - sys.stderr.write("error: The following errors occurred " - "when parsing .SRCINFO in commit\n") - sys.stderr.write("error: {:s}:\n".format(str(commit.id))) - for error in errors: - for err in error['error']: - sys.stderr.write("error: line {:d}: {:s}\n".format( - error['line'], err)) - exit(1) - - metadata_pkgbase = metadata['pkgbase'] - if not re.match(repo_regex, metadata_pkgbase): - die_commit('invalid pkgbase: {:s}'.format(metadata_pkgbase), - str(commit.id)) - - for pkgname in set(metadata['packages'].keys()): - pkginfo = srcinfo.utils.get_merged_package(pkgname, metadata) - - for field in ('pkgver', 'pkgrel', 'pkgname'): - if field not in pkginfo: - die_commit('missing mandatory field: {:s}'.format(field), - str(commit.id)) - - if 'epoch' in pkginfo and not pkginfo['epoch'].isdigit(): - die_commit('invalid epoch: {:s}'.format(pkginfo['epoch']), - str(commit.id)) - - if not re.match(r'[a-z0-9][a-z0-9\.+_-]*$', pkginfo['pkgname']): - die_commit('invalid package name: {:s}'.format( - pkginfo['pkgname']), str(commit.id)) - - for field in ('pkgname', 'pkgdesc', 'url'): - if field in pkginfo and len(pkginfo[field]) > 255: - die_commit('{:s} field too long: {:s}'.format(field, - pkginfo[field]), str(commit.id)) - - for field in ('install', 'changelog'): - if field in pkginfo and not pkginfo[field] in commit.tree: - die_commit('missing {:s} file: {:s}'.format(field, - pkginfo[field]), str(commit.id)) - - for field in extract_arch_fields(pkginfo, 'source'): - fname = field['value'] - if "://" in fname or "lp:" in fname: - continue - if fname not in commit.tree: - die_commit('missing source file: {:s}'.format(fname), - str(commit.id)) - - # Display a warning if .SRCINFO is unchanged. - if sha1_old not in ("0000000000000000000000000000000000000000", sha1_new): - srcinfo_id_old = repo[sha1_old].tree['.SRCINFO'].id - srcinfo_id_new = repo[sha1_new].tree['.SRCINFO'].id - if srcinfo_id_old == srcinfo_id_new: - warn(".SRCINFO unchanged. " - "The package database will not be updated!") - - # Read .SRCINFO from the HEAD commit. - metadata_raw = repo[repo[sha1_new].tree['.SRCINFO'].id].data.decode() - (metadata, errors) = srcinfo.parse.parse_srcinfo(metadata_raw) - - # Ensure that the package base name matches the repository name. - metadata_pkgbase = metadata['pkgbase'] - if metadata_pkgbase != pkgbase: - die('invalid pkgbase: {:s}, expected {:s}'.format(metadata_pkgbase, - pkgbase)) - - # Ensure that packages are neither blacklisted nor overwritten. - pkgbase = metadata['pkgbase'] - cur = conn.execute("SELECT ID FROM PackageBases WHERE Name = ?", [pkgbase]) - row = cur.fetchone() - pkgbase_id = row[0] if row else 0 - - cur = conn.execute("SELECT Name FROM PackageBlacklist") - blacklist = [row[0] for row in cur.fetchall()] - - cur = conn.execute("SELECT Name, Repo FROM OfficialProviders") - providers = dict(cur.fetchall()) - - for pkgname in srcinfo.utils.get_package_names(metadata): - pkginfo = srcinfo.utils.get_merged_package(pkgname, metadata) - pkgname = pkginfo['pkgname'] - - if pkgname in blacklist: - warn_or_die('package is blacklisted: {:s}'.format(pkgname)) - if pkgname in providers: - warn_or_die('package already provided by [{:s}]: {:s}'.format( - providers[pkgname], pkgname)) - - cur = conn.execute("SELECT COUNT(*) FROM Packages WHERE Name = ? " + - "AND PackageBaseID <> ?", [pkgname, pkgbase_id]) - if cur.fetchone()[0] > 0: - die('cannot overwrite package: {:s}'.format(pkgname)) - - # Create a new package base if it does not exist yet. - if pkgbase_id == 0: - pkgbase_id = create_pkgbase(conn, pkgbase, user) - - # Store package base details in the database. - save_metadata(metadata, conn, user) - - # Create (or update) a branch with the name of the package base for better - # accessibility. - branchref = 'refs/heads/' + pkgbase - repo.create_reference(branchref, sha1_new, True) - - # Work around a Git bug: The HEAD ref is not updated when using - # gitnamespaces. This can be removed once the bug fix is included in Git - # mainline. See - # http://git.661346.n2.nabble.com/PATCH-receive-pack-Create-a-HEAD-ref-for-ref-namespace-td7632149.html - # for details. - headref = 'refs/namespaces/' + pkgbase + '/HEAD' - repo.create_reference(headref, sha1_new, True) - - # Send package update notifications. - update_notify(conn, user, pkgbase_id) - - # Close the database. - cur.close() - conn.close() - - -if __name__ == '__main__': - main() diff --git a/setup.py b/setup.py index 48eb176..b64e71c 100644 --- a/setup.py +++ b/setup.py @@ -17,4 +17,11 @@ setup( name="aurweb", version=version, packages=find_packages(), + entry_points={ + 'console_scripts': [ + 'aurweb-git-auth = aurweb.git.auth:main', + 'aurweb-git-serve = aurweb.git.serve:main', + 'aurweb-git-update = aurweb.git.update:main', + ], + }, ) diff --git a/test/setup.sh b/test/setup.sh index dc9cff2..d02d298 100644 --- a/test/setup.sh +++ b/test/setup.sh @@ -8,9 +8,9 @@ PYTHONPATH="$TOPLEVEL" export PYTHONPATH # Configure paths to the Git interface scripts. -GIT_AUTH="$TOPLEVEL/git-interface/git-auth.py" -GIT_SERVE="$TOPLEVEL/git-interface/git-serve.py" -GIT_UPDATE="$TOPLEVEL/git-interface/git-update.py" +GIT_AUTH="$TOPLEVEL/aurweb/git/auth.py" +GIT_SERVE="$TOPLEVEL/aurweb/git/serve.py" +GIT_UPDATE="$TOPLEVEL/aurweb/git/update.py" MKPKGLISTS="$TOPLEVEL/scripts/mkpkglists.py" TUVOTEREMINDER="$TOPLEVEL/scripts/tuvotereminder.py" PKGMAINT="$TOPLEVEL/scripts/pkgmaint.py" @@ -38,7 +38,7 @@ reply-to = noreply@aur.archlinux.org [auth] valid-keytypes = ssh-rsa ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 username-regex = [a-zA-Z0-9]+[.\-_]?[a-zA-Z0-9]+$ -git-serve-cmd = /srv/http/aurweb/git-interface/git-serve.py +git-serve-cmd = $GIT_SERVE ssh-options = restrict [serve] -- cgit v1.2.3-54-g00ecf