From patchwork Tue Apr 3 19:19:05 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Legner X-Patchwork-Id: 485 Return-Path: Delivered-To: patchwork@archlinux.org Received: from apollo.archlinux.org (localhost [127.0.0.1]) by apollo.archlinux.org (Postfix) with ESMTP id B99902F479D5 for ; Tue, 3 Apr 2018 19:19:21 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.1 (2015-04-28) on apollo.archlinux.org X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=DKIM_SIGNED=0.1, DKIM_VALID=-0.1,DKIM_VALID_AU=-0.1,FREEMAIL_FROM=0.5,RCVD_IN_DNSWL_MED=-2.3 autolearn=ham autolearn_force=no version=3.4.1 X-Spam-BL-Results: [127.0.9.2] Received: from orion.archlinux.org (orion.archlinux.org [88.198.91.70]) by apollo.archlinux.org (Postfix) with ESMTPS for ; Tue, 3 Apr 2018 19:19:21 +0000 (UTC) Received: from orion.archlinux.org (localhost [127.0.0.1]) by orion.archlinux.org (Postfix) with ESMTP id 483489B91BC0D; Tue, 3 Apr 2018 19:19:16 +0000 (UTC) Received: from luna.archlinux.org (luna.archlinux.org [5.9.250.164]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by orion.archlinux.org (Postfix) with ESMTPS; Tue, 3 Apr 2018 19:19:16 +0000 (UTC) Received: from luna.archlinux.org (luna.archlinux.org [127.0.0.1]) by luna.archlinux.org (Postfix) with ESMTP id 3554D2D248; Tue, 3 Apr 2018 19:19:16 +0000 (UTC) Authentication-Results: luna.archlinux.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=hOBfBDX3 Received: from luna.archlinux.org (luna.archlinux.org [127.0.0.1]) by luna.archlinux.org (Postfix) with ESMTP id CCAE92BEAE for ; Tue, 3 Apr 2018 19:19:13 +0000 (UTC) Received: from orion.archlinux.org (orion.archlinux.org [88.198.91.70]) by luna.archlinux.org (Postfix) with ESMTPS for ; Tue, 3 Apr 2018 19:19:13 +0000 (UTC) Received: from orion.archlinux.org (localhost [127.0.0.1]) by orion.archlinux.org (Postfix) with ESMTP id 168D69B91BBFE for ; Tue, 3 Apr 2018 19:19:08 +0000 (UTC) Received: from mail-oi0-x22b.google.com (mail-oi0-x22b.google.com [IPv6:2607:f8b0:4003:c06::22b]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by orion.archlinux.org (Postfix) with ESMTPS for ; Tue, 3 Apr 2018 19:19:07 +0000 (UTC) Received: by mail-oi0-x22b.google.com with SMTP id c3-v6so17042322oib.5 for ; Tue, 03 Apr 2018 12:19:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:from:date:message-id:subject:to; bh=4nh1KFSpZktv9Gdm5wcPU+8CRFC8V5AKeS0Lyjf2EfM=; b=hOBfBDX3IMXcObUK40aPNu3fIu2Eb6l70lgXcjRPQ0/ErzaKsxHyR9pflCg1GIsY4L z1zU+5ORjOTZYL68LKYgOEmZPhjrnmFJOmSZ5J62AmNn9RhvIGYaTo/d3y1UXtC/QDbI 5nmfnNY212lxXSbxrK0SUBcO3PfzmcssBSiXDmnY7IQ6e6VcfZKYZeAJZx4dvtfQ9fEb BML94o/E/qrw9hXZuSmVPbX/bRuJP3GzxG6nG8l1Rrg/9Iam40U9cwlJdNxrDB45sfxr MckiGSgudNcdTApdjDMJ4YRIXdkHZamCCjPkWgv6kqBGIzJUQeMdoc+Pa5HswIdl+OER /Siw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=4nh1KFSpZktv9Gdm5wcPU+8CRFC8V5AKeS0Lyjf2EfM=; b=qUbSwnf2ERebiFnToP4zOeOcB24RGyPtcJsxMSnknxhdPn2jeiUplaOiFlz5Zd0Bcm 0XA0sh9awk4W+9sLW/nZ78ZSHSeincZhkNoCRfLhS3ZOq+ducpVGEJ/qR5KckXBldMiG bgb3+Vpudn3u/nyHx4FwQpUPepy12N3NT7URjbnktezRQ6A6yKhhMwCiUubgO5QQ2Z08 zwIckVTHE4BmNVvSudg6rIRDR7iTpHiSFuTHPJAD6NGt5b1nbp8jcikgLHsS+vI6oxrh vhg1f57wjic/g0UeYzL0+jj3MlRzySQyrh3hOLx9UtZZhAMhLFo6e1uczMTBsZuGUU+A nJgw== X-Gm-Message-State: ALQs6tBSnGtEWsx187TkGudZJuhBXiZ/E/Xy3CY+LqYHCqBvu7L8Jfbq QbTtJotha+15ZeQFnMjStzCPrrBHKiw8M+R62Pyahw== X-Google-Smtp-Source: AIpwx4/JApC3E1kGDjQl0+pSBkh71ihiVzWuAKd39su2QiTB4Fv93dDqN+RKxjEgCQ6yHmshrzpg5ff21N71EuOO2tw= X-Received: by 2002:aca:ad6:: with SMTP id k83-v6mr8833259oiy.147.1522783146345; Tue, 03 Apr 2018 12:19:06 -0700 (PDT) MIME-Version: 1.0 Received: by 2002:a9d:4281:0:0:0:0:0 with HTTP; Tue, 3 Apr 2018 12:19:05 -0700 (PDT) From: Simon Legner Date: Tue, 3 Apr 2018 21:19:05 +0200 Message-ID: Subject: [PATCH 1/2] db.py: extract commonly used fetch_userid function To: aur-dev@archlinux.org X-BeenThere: aur-dev@archlinux.org X-Mailman-Version: 2.1.26 Precedence: list List-Id: "Arch User Repository \(AUR\) Development" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: aur-dev-bounces@archlinux.org Sender: "aur-dev" --- aurweb/db.py | 7 ++++++ aurweb/git/serve.py | 68 ++++++++++------------------------------------------- 2 files changed, 19 insertions(+), 56 deletions(-) uids_rem = uids_old - uids_new @@ -209,10 +185,7 @@ def pkgreq_close(reqid, user, reason, comments, autoclose=False): if autoclose: userid = 0 else: - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) - userid = cur.fetchone()[0] - if userid == 0: - raise aurweb.exceptions.InvalidUserException(user) + userid = conn.fetch_userid(user) conn.execute("UPDATE PackageRequests SET Status = ?, ClosureComment = ? " + "WHERE ID = ?", [status, comments, reqid]) @@ -250,9 +223,7 @@ def pkgbase_disown(pkgbase, user, privileged): 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] + new_maintainer_userid = conn.fetch_userid(new_maintainer) comaintainers.remove(new_maintainer) pkgbase_set_comaintainers(pkgbase, comaintainers, user, privileged) @@ -261,10 +232,7 @@ def pkgbase_disown(pkgbase, user, privileged): conn.commit() - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) - userid = cur.fetchone()[0] - if userid == 0: - raise aurweb.exceptions.InvalidUserException(user) + userid = conn.fetch_userid(user) subprocess.Popen((notify_cmd, 'disown', str(pkgbase_id), str(userid))) @@ -280,10 +248,7 @@ def pkgbase_flag(pkgbase, user, comment): conn = aurweb.db.Connection() - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) - userid = cur.fetchone()[0] - if userid == 0: - raise aurweb.exceptions.InvalidUserException(user) + userid = conn.fetch_userid(user) now = int(time.time()) conn.execute("UPDATE PackageBases SET " + @@ -303,10 +268,7 @@ def pkgbase_unflag(pkgbase, user): conn = aurweb.db.Connection() - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) - userid = cur.fetchone()[0] - if userid == 0: - raise aurweb.exceptions.InvalidUserException(user) + userid = conn.fetch_userid(user) if user in pkgbase_get_comaintainers(pkgbase): conn.execute("UPDATE PackageBases SET OutOfDateTS = NULL " + @@ -326,10 +288,7 @@ def pkgbase_vote(pkgbase, user): conn = aurweb.db.Connection() - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) - userid = cur.fetchone()[0] - if userid == 0: - raise aurweb.exceptions.InvalidUserException(user) + userid = conn.fetch_userid(user) cur = conn.execute("SELECT COUNT(*) FROM PackageVotes " + "WHERE UsersID = ? AND PackageBaseID = ?", @@ -352,10 +311,7 @@ def pkgbase_unvote(pkgbase, user): conn = aurweb.db.Connection() - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) - userid = cur.fetchone()[0] - if userid == 0: - raise aurweb.exceptions.InvalidUserException(user) + userid = conn.fetch_userid(user) cur = conn.execute("SELECT COUNT(*) FROM PackageVotes " + "WHERE UsersID = ? AND PackageBaseID = ?", diff --git a/aurweb/db.py b/aurweb/db.py index 0b58197..bbc674b 100644 --- a/aurweb/db.py +++ b/aurweb/db.py @@ -49,3 +49,10 @@ class Connection: def close(self): self._conn.close() + + def fetch_userid(self, user): + cur = self.execute("SELECT ID FROM Users WHERE Username = ?", [user]) + userid = cur.fetchone()[0] + if userid == 0: + raise aurweb.exceptions.InvalidUserException(user) + return userid diff --git a/aurweb/git/serve.py b/aurweb/git/serve.py index 93ff34c..01aea20 100755 --- a/aurweb/git/serve.py +++ b/aurweb/git/serve.py @@ -38,10 +38,7 @@ def pkgbase_exists(pkgbase): 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: - raise aurweb.exceptions.InvalidUserException(user) + userid = conn.fetch_userid(user) cur = conn.execute("SELECT Name, PackagerUID FROM PackageBases " + "WHERE MaintainerUID = ?", [userid]) @@ -58,10 +55,7 @@ def create_pkgbase(pkgbase, user): conn = aurweb.db.Connection() - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) - userid = cur.fetchone()[0] - if userid == 0: - raise aurweb.exceptions.InvalidUserException(user) + userid = conn.fetch_userid(user) now = int(time.time()) cur = conn.execute("INSERT INTO PackageBases (Name, SubmittedTS, " + @@ -90,10 +84,7 @@ def pkgbase_adopt(pkgbase, user, privileged): if not privileged and not cur.fetchone(): raise aurweb.exceptions.PermissionDeniedException(user) - cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) - userid = cur.fetchone()[0] - if userid == 0: - raise aurweb.exceptions.InvalidUserException(user) + userid = conn.fetch_userid(user) cur = conn.execute("UPDATE PackageBases SET MaintainerUID = ? " + "WHERE ID = ?", [userid, pkgbase_id]) @@ -138,23 +129,8 @@ def pkgbase_set_comaintainers(pkgbase, userlist, user, privileged): 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: - raise aurweb.exceptions.InvalidUserException(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: - raise aurweb.exceptions.InvalidUserException(user) - uids_new.add(userid) + uids_old = set([conn.fetch_userid(olduser) for olduser in userlist_old]) + uids_new = set([conn.fetch_userid(newuser) for newuser in userlist]) uids_add = uids_new - uids_old From patchwork Tue Apr 3 19:19:16 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Legner X-Patchwork-Id: 486 Return-Path: Delivered-To: patchwork@archlinux.org Received: from apollo.archlinux.org (localhost [127.0.0.1]) by apollo.archlinux.org (Postfix) with ESMTP id 30A0E2F47A17 for ; Tue, 3 Apr 2018 19:19:33 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.1 (2015-04-28) on apollo.archlinux.org X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=DKIM_SIGNED=0.1, DKIM_VALID=-0.1,DKIM_VALID_AU=-0.1,FREEMAIL_FROM=0.5,RCVD_IN_DNSWL_MED=-2.3 autolearn=ham autolearn_force=no version=3.4.1 X-Spam-BL-Results: [127.0.9.2] Received: from orion.archlinux.org (orion.archlinux.org [88.198.91.70]) by apollo.archlinux.org (Postfix) with ESMTPS for ; Tue, 3 Apr 2018 19:19:33 +0000 (UTC) Received: from orion.archlinux.org (localhost [127.0.0.1]) by orion.archlinux.org (Postfix) with ESMTP id 7BE1C9B91BC20; Tue, 3 Apr 2018 19:19:28 +0000 (UTC) Received: from luna.archlinux.org (luna.archlinux.org [IPv6:2a01:4f8:160:3033::2]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by orion.archlinux.org (Postfix) with ESMTPS; Tue, 3 Apr 2018 19:19:28 +0000 (UTC) Received: from luna.archlinux.org (luna.archlinux.org [127.0.0.1]) by luna.archlinux.org (Postfix) with ESMTP id 6A92D2C71A; Tue, 3 Apr 2018 19:19:28 +0000 (UTC) Authentication-Results: luna.archlinux.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=GPWjz1cf Received: from luna.archlinux.org (luna.archlinux.org [127.0.0.1]) by luna.archlinux.org (Postfix) with ESMTP id 7EA8720771 for ; Tue, 3 Apr 2018 19:19:26 +0000 (UTC) Received: from orion.archlinux.org (orion.archlinux.org [88.198.91.70]) by luna.archlinux.org (Postfix) with ESMTPS for ; Tue, 3 Apr 2018 19:19:26 +0000 (UTC) Received: from orion.archlinux.org (localhost [127.0.0.1]) by orion.archlinux.org (Postfix) with ESMTP id 7C8929B91BC0E for ; Tue, 3 Apr 2018 19:19:18 +0000 (UTC) Received: from mail-oi0-x230.google.com (mail-oi0-x230.google.com [IPv6:2607:f8b0:4003:c06::230]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by orion.archlinux.org (Postfix) with ESMTPS for ; Tue, 3 Apr 2018 19:19:18 +0000 (UTC) Received: by mail-oi0-x230.google.com with SMTP id 188-v6so17020706oih.8 for ; Tue, 03 Apr 2018 12:19:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:from:date:message-id:subject:to; bh=6ljtW6IpzUapxGUpQJSG3Ql9TLsBdFnTqbcHVDBy2e8=; b=GPWjz1cftAf7yclVQ0i3UiabGKr/EmP0yVt2WjYvg98yntsXpl27AyriUL8Fz77g82 SmDZe5gHefwCgQ5+jRe9BwBE9RIVTgAQkdTZngmYQAp9LNAonmsx2hGD4cm+S5t7XKnH kmeMUfQLyKc7AmaMZsG3iUAztvL7+BduX72Z96w3o9ENxcF3KZo9ssKtvCPIUuNukk2a DGvcE5cmtEsSzZFu4RtSWUWxV2vZ+D/eU2APikzkbTFKUwCM+odqr3JVtw2ZZwPUel82 RKmVvSl/CRFshFYBQNtupaRdqtElTHyOdIBp/o7+cXN3omeZ0KG8lh7fsmEQPschtIw6 FvAA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=6ljtW6IpzUapxGUpQJSG3Ql9TLsBdFnTqbcHVDBy2e8=; b=VGk98LeJql7dWQ3NAirBDKzE1x0whG4u1dXCZWkIrY8vFJ6wFhTHse3Xq+x8dQ5+Q9 dEoj6zRqJdvMVXMoAOwICy+fnvJ7Q6cS1BdeqDzU1pFpMSLJWH9XCb6AYXuRfOGFk4uL 2gc/5NlTA5hDE7zDzRP9wYbk3JqF6GNpCvyy0FXKsSHiBgTX9mw4cpDirXzkVMk9MNMq mAb9I3kecpsOWaBnoB3yGtvzRqmkYZEcofSYUTe3DdgisvY6Mxp6qK8YzqgHZ2aRCZhy bOhdqm++RCAQXz8u6iR7n7ixlI1SkatjVU/Uh8bK8PfPJbKdHQkactxP5MzTCchrPLAR bCxw== X-Gm-Message-State: ALQs6tBPNz2v35oUNJma1gyld3d8yrCh5q+joqf4GSml2BwonZqOE5wI SfaA/MplJPFLVbrZFvw1Jp/0gtW4jHCjWr2RXxGpzQ== X-Google-Smtp-Source: AIpwx4+wh0G2FYiiQ+Co0+8zCo6jYp3m7z3EF80bKdxTOgfkaX/wXJ3z/B2oWouqkCS9EW/1tJVZAbtDCNo78v6GNwU= X-Received: by 2002:aca:3407:: with SMTP id b7-v6mr8586968oia.183.1522783156937; Tue, 03 Apr 2018 12:19:16 -0700 (PDT) MIME-Version: 1.0 Received: by 2002:a9d:4281:0:0:0:0:0 with HTTP; Tue, 3 Apr 2018 12:19:16 -0700 (PDT) From: Simon Legner Date: Tue, 3 Apr 2018 21:19:16 +0200 Message-ID: Subject: [PATCH 2/2] serve.py: add support for 'list-voted' To: aur-dev@archlinux.org X-BeenThere: aur-dev@archlinux.org X-Mailman-Version: 2.1.26 Precedence: list List-Id: "Arch User Repository \(AUR\) Development" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: aur-dev-bounces@archlinux.org Sender: "aur-dev" This command lists voted packages. --- aurweb/git/serve.py | 18 ++++++++++++++++++ test/t1200-git-serve.sh | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/aurweb/git/serve.py b/aurweb/git/serve.py index 01aea20..3b3967a 100755 --- a/aurweb/git/serve.py +++ b/aurweb/git/serve.py @@ -326,6 +326,19 @@ def pkgbase_unvote(pkgbase, user): conn.commit() +def list_voted(user): + conn = aurweb.db.Connection() + + userid = conn.fetch_userid(user) + + cur = conn.execute("SELECT Name FROM PackageBases JOIN PackageVotes " + + "ON ID = PackageBaseID " + + "WHERE UsersID = ?", [userid]) + for row in cur: + print(row[0]) + conn.close() + + def pkgbase_set_keywords(pkgbase, keywords): pkgbase_id = pkgbase_from_name(pkgbase) if not pkgbase_id: @@ -508,6 +521,9 @@ def serve(action, cmdargv, user, privileged, remote_addr): pkgbase = cmdargv[1] pkgbase_unvote(pkgbase, user) + elif action == 'list-voted': + checkarg(cmdargv) + list_voted(user) elif action == 'set-comaintainers': checkarg_atleast(cmdargv, 'repository name') @@ -515,12 +531,14 @@ def serve(action, cmdargv, user, privileged, remote_addr): userlist = cmdargv[2:] pkgbase_set_comaintainers(pkgbase, userlist, user, privileged) elif action == 'help': + # commands in alphabetical order, git-* at the end cmds = { "adopt ": "Adopt a package base.", "disown ": "Disown a package base.", "flag ": "Flag a package base out-of-date.", "help": "Show this help message and exit.", "list-repos": "List all your repositories.", + "list-voted": "List voted packages.", "restore ": "Restore a deleted package base.", "set-comaintainers [...]": "Set package base co-maintainers.", "set-keywords [...]": "Change package base keywords.", diff --git a/test/t1200-git-serve.sh b/test/t1200-git-serve.sh index 94a5ff6..51db399 100755 --- a/test/t1200-git-serve.sh +++ b/test/t1200-git-serve.sh @@ -466,6 +466,12 @@ test_expect_success "Vote for a package base." ' EOF echo "SELECT NumVotes FROM PackageBases WHERE Name = \"foobar\";" | \ sqlite3 aur.db >actual && + test_cmp expected actual && + SSH_ORIGINAL_COMMAND="list-voted" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + cat >expected <<-EOF && + foobar + EOF test_cmp expected actual ' @@ -484,6 +490,12 @@ test_expect_success "Vote for a package base twice." ' EOF echo "SELECT NumVotes FROM PackageBases WHERE Name = \"foobar\";" | \ sqlite3 aur.db >actual && + test_cmp expected actual && + SSH_ORIGINAL_COMMAND="list-voted" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + cat >expected <<-EOF && + foobar + EOF test_cmp expected actual ' @@ -500,6 +512,11 @@ test_expect_success "Remove vote from a package base." ' EOF echo "SELECT NumVotes FROM PackageBases WHERE Name = \"foobar\";" | \ sqlite3 aur.db >actual && + test_cmp expected actual && + SSH_ORIGINAL_COMMAND="list-voted" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + cat >expected <<-EOF && + EOF test_cmp expected actual ' @@ -518,6 +535,11 @@ test_expect_success "Try to remove the vote again." ' EOF echo "SELECT NumVotes FROM PackageBases WHERE Name = \"foobar\";" | \ sqlite3 aur.db >actual && + test_cmp expected actual && + SSH_ORIGINAL_COMMAND="list-voted" AUR_USER=user AUR_PRIVILEGED=0 \ + "$GIT_SERVE" 2>&1 >actual && + cat >expected <<-EOF && + EOF test_cmp expected actual '