public inbox for cygwin-apps-cvs@sourceware.org
help / color / mirror / Atom feed
* [calm - Cygwin server-side packaging maintenance script] branch master, updated. 20190530-43-g4a623bb
@ 2019-07-04 12:08 jturney
  0 siblings, 0 replies; only message in thread
From: jturney @ 2019-07-04 12:08 UTC (permalink / raw)
  To: cygwin-apps-cvs

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain, Size: 171676 bytes --]




https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=4a623bb4dd963b2945c36b8e3b5b7e57e982eb89

commit 4a623bb4dd963b2945c36b8e3b5b7e57e982eb89
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Thu Jul 4 13:03:32 2019 +0100

    Fix over-indented read_one_package()

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=a2a297230bc714d49bb06624bbb7d57115b97045

commit a2a297230bc714d49bb06624bbb7d57115b97045
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sun Jun 30 18:11:05 2019 +0100

    Write lines for source-only versions in setup.ini
    
    Preserve the historical behaviour by writing lines for source-only
    versions in setup.ini.

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=7ccbc27b01d00623b632f700fa08dbd183f18b8b

commit 7ccbc27b01d00623b632f700fa08dbd183f18b8b
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Wed Jun 26 19:45:39 2019 +0100

    Also write a source package index page

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=84e5f407c66832a0d0bdbf6e6bd3c8f6e661be28

commit 84e5f407c66832a0d0bdbf6e6bd3c8f6e661be28
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Tue Jun 25 14:42:52 2019 +0100

    Identify x86 or x86_64 only depends in package summary page

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=f0cf563288d3b0e17bb53b570395a4fd23b13f19

commit f0cf563288d3b0e17bb53b570395a4fd23b13f19
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Fri Jun 7 17:36:20 2019 +0100

    Create a separate package object for the source package
    
    Create separate package objects for the binary and source packages. This
    is generally a simplification to the data model.
    
    One slight wrinkle is that stale file detection needs to be aware that
    .hint files may now be used by both a binary and source packages, and so
    should only be removed if both packages are stale. (Since the source
    package cannot be stale if the binary package isn't, we solve this by
    making the source package control the staleness of the hint, in that
    case [except for archives which are sourceless for permitted reasons,
    which we now explicitly annotate as such]).
    
    'external-source' is now always followed, rather than checking for the
    source for a given version in the current package first. This exposes a
    handful of (migrated) hints which contain an unnecessary
    external-source.
    
    Write separate package summary pages for source packages
    
    Always create binary packages when uploading to allow replacement hints,
    otherwise, only create a package if there's archives for it to contain
    to avoid creating empty packages from the relarea.
    
    Note: since we don't put source packages into a separate namespace,
    there is a potential collision if both 'foo' (source package 'foo-src'),
    and 'foo-src' (source package 'foo-src-src') exist. That would be daft,
    and there aren't currently any examples of this. Forbid package names
    ending in '-src' to avoid such problems.

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=dec704d7feb89f1a173c03f82369adc0256d72c8

commit dec704d7feb89f1a173c03f82369adc0256d72c8
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sun Jun 9 18:51:10 2019 +0100

    Factor out arch_package

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=c634590a67bfa6ab944d5f3cbc31c398b5168980

commit c634590a67bfa6ab944d5f3cbc31c398b5168980
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Thu Jun 27 14:02:19 2019 +0100

    Add a check that dirtree produced matches expected in test_html_writer

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=ca46f039f156bae73658b29e17c9ec962fa0de02

commit ca46f039f156bae73658b29e17c9ec962fa0de02
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Wed Jun 19 18:26:23 2019 +0100

    Don't look up hints multiple times in write_setup_ini

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=2957a3320a877e79c10c4482a7d13b56fdaab99f

commit 2957a3320a877e79c10c4482a7d13b56fdaab99f
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sun Jun 30 14:58:53 2019 +0100

    Refine wording of a piece of logging

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=7bc5411e21cc2090b4ecbe2502c9aec192704689

commit 7bc5411e21cc2090b4ecbe2502c9aec192704689
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Tue Jul 2 13:04:36 2019 +0100

    Don't use defaultdict for vermap
    
    Written as it is, this needs more care to avoid accidentally creating
    version of packages.
    
    Note that after this, we have two different definitions of the set of
    versions for a package: vermap.keys() and version_hints.keys().  One is
    the set of versions for which we currently have archives (after
    vaulting), the other, the set of versions for which we've ever seen
    hints.


Diff:
---
 TODO                                               |    1 +
 calm/package.py                                    |  628 ++++++++++++--------
 calm/past_mistakes.py                              |    4 +
 calm/pkg2html.py                                   |  185 ++++--
 calm/uploads.py                                    |    2 +-
 test/test_calm.py                                  |    8 +
 test/testdata/htdocs.expected/dirtree.expected     |  102 ++++
 test/testdata/htdocs.expected/packages.inc         |    1 +
 test/testdata/htdocs.expected/src_packages.inc     |   31 +
 test/testdata/htdocs.expected/summary/arc-src.html |   35 ++
 test/testdata/htdocs.expected/summary/arc.html     |    3 +-
 .../htdocs.expected/summary/base-cygwin.html       |    2 +-
 .../htdocs.expected/summary/corrupt-src.html       |   30 +
 test/testdata/htdocs.expected/summary/corrupt.html |    3 +-
 .../htdocs.expected/summary/cygwin-debuginfo.html  |    2 +-
 .../htdocs.expected/summary/cygwin-devel.html      |    2 +-
 .../htdocs.expected/summary/cygwin-src.html        |   32 +
 test/testdata/htdocs.expected/summary/cygwin.html  |    5 +-
 .../htdocs.expected/summary/keychain-src.html      |   39 ++
 .../testdata/htdocs.expected/summary/keychain.html |    4 +-
 .../htdocs.expected/summary/libdns_sd-devel.html   |    2 +-
 .../htdocs.expected/summary/libdns_sd1.html        |    2 +-
 .../htdocs.expected/summary/mDNSResponder-src.html |   32 +
 .../htdocs.expected/summary/mDNSResponder.html     |    3 +-
 .../htdocs.expected/summary/obs-a-src.html         |   30 +
 test/testdata/htdocs.expected/summary/obs-a.html   |    3 +-
 .../htdocs.expected/summary/obs-b-src.html         |   30 +
 test/testdata/htdocs.expected/summary/obs-b.html   |    3 +-
 .../htdocs.expected/summary/openssh-src.html       |   32 +
 test/testdata/htdocs.expected/summary/openssh.html |    3 +-
 .../per-version-replacement-hint-only-src.html     |   30 +
 .../summary/per-version-replacement-hint-only.html |    3 +-
 .../htdocs.expected/summary/per-version-src.html   |   31 +
 .../htdocs.expected/summary/per-version.html       |    4 +-
 .../summary/perl-Net-SMTP-SSL-src.html             |   33 +
 .../htdocs.expected/summary/perl-Net-SMTP-SSL.html |    5 +-
 .../htdocs.expected/summary/rpm-doc-src.html       |   26 +
 test/testdata/htdocs.expected/summary/rpm-doc.html |    3 +-
 .../htdocs.expected/summary/staleversion-src.html  |   35 ++
 .../htdocs.expected/summary/staleversion.html      |    8 +-
 .../htdocs.expected/summary/test-c-src.html        |   30 +
 test/testdata/htdocs.expected/summary/test-c.html  |    3 +-
 .../htdocs.expected/summary/test-d-src.html        |   30 +
 test/testdata/htdocs.expected/summary/test-d.html  |    3 +-
 .../htdocs.expected/summary/test-e-src.html        |   31 +
 test/testdata/htdocs.expected/summary/test-e.html  |    4 +-
 .../htdocs.expected/summary/testpackage-src.html   |   30 +
 .../htdocs.expected/summary/testpackage.html       |    3 +-
 .../testdata/htdocs.expected/x86/arc-src/.htaccess |    2 +
 .../htdocs.expected/x86/arc-src/arc-4.32.7-10-src  |   13 +
 .../htdocs.expected/x86/corrupt-src/.htaccess      |    2 +
 .../x86/corrupt-src/corrupt-2.0.0-1-src            |   14 +
 .../htdocs.expected/x86/cygwin-src/.htaccess       |    2 +
 .../x86/cygwin-src/cygwin-2.2.0-1-src              |   13 +
 .../x86/cygwin-src/cygwin-2.2.1-1-src              |   13 +
 .../x86/cygwin-src/cygwin-2.3.0-0.3-src            |   13 +
 .../htdocs.expected/x86/keychain-src/.htaccess     |    2 +
 .../x86/keychain-src/keychain-2.6.8-1-src          |   14 +
 .../x86/keychain-src/keychain-2.7.1-1-src          |   16 +
 .../x86/mDNSResponder-src/.htaccess                |    2 +
 .../mDNSResponder-src/mDNSResponder-379.32.1-1-src |   13 +
 .../htdocs.expected/x86/obs-a-src/.htaccess        |    2 +
 .../htdocs.expected/x86/obs-a-src/obs-a-1.0-1-src  |   12 +
 .../htdocs.expected/x86/obs-b-src/.htaccess        |    2 +
 .../htdocs.expected/x86/obs-b-src/obs-b-1.0-1-src  |   12 +
 .../htdocs.expected/x86/openssh-src/.htaccess      |    2 +
 .../x86/openssh-src/openssh-7.2p2-1-src            |   13 +
 .../.htaccess                                      |    2 +
 .../per-version-replacement-hint-only-1.0-1-src    |   13 +
 .../htdocs.expected/x86/per-version-src/.htaccess  |    2 +
 .../x86/per-version-src/per-version-4.0-1-src      |   13 +
 .../x86/per-version-src/per-version-4.8-1-src      |   13 +
 .../x86/perl-Net-SMTP-SSL-src/.htaccess            |    2 +
 .../perl-Net-SMTP-SSL-1.01-1-src                   |   14 +
 .../perl-Net-SMTP-SSL-1.02-1-src                   |   14 +
 .../perl-Net-SMTP-SSL-1.03-1-src                   |   14 +
 .../htdocs.expected/x86/rpm-doc-src/.htaccess      |    2 +
 .../x86/rpm-doc-src/rpm-doc-4.1-2-src              |   11 +
 .../htdocs.expected/x86/staleversion-src/.htaccess |    2 +
 .../x86/staleversion-src/staleversion-240-1-src    |   13 +
 .../x86/staleversion-src/staleversion-242-0-src    |   13 +
 .../x86/staleversion-src/staleversion-243-0-src    |   13 +
 .../x86/staleversion-src/staleversion-250-0-src    |   13 +
 .../x86/staleversion-src/staleversion-251-0-src    |   13 +
 .../x86/staleversion-src/staleversion-260-0-src    |   13 +
 .../htdocs.expected/x86/test-c-src/.htaccess       |    2 +
 .../x86/test-c-src/test-c-1.0-1-src                |   12 +
 .../htdocs.expected/x86/test-d-src/.htaccess       |    2 +
 .../x86/test-d-src/test-d-1.0-1-src                |   12 +
 .../htdocs.expected/x86/test-e-src/.htaccess       |    2 +
 .../x86/test-e-src/test-e-1.0-1-src                |   12 +
 .../htdocs.expected/x86/testpackage-src/.htaccess  |    2 +
 .../x86/testpackage-src/testpackage-0.1-1-src      |   13 +
 test/testdata/htdocs.expected/x86_64/.htaccess     |    1 +
 test/testdata/process_arch/htdocs.expected         |  122 +++--
 test/testdata/uploads/pkglist.expected             |   13 +-
 96 files changed, 1690 insertions(+), 416 deletions(-)

diff --git a/TODO b/TODO
index 9d1ddd1..c7c6f26 100644
--- a/TODO
+++ b/TODO
@@ -10,3 +10,4 @@
 * atomically update .ini/.sig (rename of containing directory, if we put release/ was somewhere else?)
 * report changes in override.hint like we used to for setup.hint
 * maintainers.py should only re-read cygwin-pkg-maint if it's changed
+* empty install packages should only come in two variants: no dependencies and in _obsolete category, or with dependencies and in 'meta' category
diff --git a/calm/package.py b/calm/package.py
index 4ce1660..f73eb28 100755
--- a/calm/package.py
+++ b/calm/package.py
@@ -26,6 +26,7 @@
 #
 
 from collections import defaultdict
+from enum import Enum, unique
 import copy
 import difflib
 import hashlib
@@ -46,6 +47,12 @@ from . import maintainers
 from . import past_mistakes
 
 
+# kinds of packages @unique
+class Kind(Enum):
+    binary = 1  # aka 'install'
+    source = 2
+
+
 # information we keep about a package
 class Package(object):
     def __init__(self):
@@ -56,7 +63,7 @@ class Package(object):
         self.version_hints = {}
         self.override_hints = {}
         self.skip = False
-        self.vermap = defaultdict(defaultdict)
+        self.vermap = {}
 
     def __repr__(self):
         return "Package('%s', %s, %s, %s, %s)" % (
@@ -106,9 +113,9 @@ def read_packages(rel_area, arch):
         logging.debug('reading packages from %s' % releasedir)
 
         for (dirpath, subdirs, files) in os.walk(releasedir, followlinks=True):
-            read_package(packages[root], rel_area, dirpath, files)
+            read_package_dir(packages[root], rel_area, dirpath, files)
 
-        logging.debug("%d packages read" % len(packages[root]))
+        logging.debug("%d packages read from %s" % (len(packages[root]), releasedir))
 
     return merge({}, *packages.values())
 
@@ -173,188 +180,241 @@ def clean_hints(p, hints, warnings):
 
 
 #
-# read a single package
+# read a single package directory
 #
-def read_package(packages, basedir, dirpath, files, remove=[]):
+# (may contain at most one source package and one binary package)
+# (return True if problems, False otherwise)
+#
+
+def read_package_dir(packages, basedir, dirpath, files, remove=[], upload=False):
     relpath = os.path.relpath(dirpath, basedir)
-    warnings = False
 
-    if any([f.endswith('.hint') for f in files]):
-        # the package name is always the directory name
-        p = os.path.basename(dirpath)
+    # the package name is always the directory name
+    p = os.path.basename(dirpath)
 
-        if not re.match(r'^[\w\-._+]*$', p):
-            logging.error("package '%s' name contains illegal characters" % p)
-            return True
+    # no .hint files
+    if not any([f.endswith('.hint') for f in files]):
+        if (relpath.count(os.path.sep) > 1):
+            for s in ['md5.sum', 'sha512.sum']:
+                if s in files:
+                    files.remove(s)
 
-        # check for duplicate package names at different paths
-        (_, _, pkgpath) = relpath.split(os.sep, 2)
-        if p in packages:
-            logging.error("duplicate package name at paths %s and %s" %
-                          (relpath, packages[p].pkgpath))
-            return True
+            if len(files) > 0:
+                logging.error("no .hint files in %s but has files: %s" % (dirpath, ', '.join(files)))
+                return True
 
-        # determine version overrides
-        note_absent = ('override.hint' in remove) or ('override.hint' in files)
+        return False
 
-        if 'override.hint' in files:
-            # read override.hint
-            override_hints = read_hints(p, os.path.join(dirpath, 'override.hint'), hint.override)
-            if override_hints is None:
-                logging.error("error parsing %s" % (os.path.join(dirpath, 'override.hint')))
-                return True
-            files.remove('override.hint')
-        else:
-            override_hints = {}
-
-        # if override.hint exists or is being removed, explicitly note absent
-        # stability level hints
-        if note_absent:
-            for level in ['test', 'curr', 'prev']:
-                if level not in override_hints:
-                    override_hints[level] = None
-
-        # read sha512.sum
-        sha512 = {}
-        if 'sha512.sum' not in files:
-            logging.debug("no sha512.sum for package '%s'" % p)
-        else:
-            files.remove('sha512.sum')
+    # discard obsolete md5.sum
+    if 'md5.sum' in files:
+        files.remove('md5.sum')
+
+    # ignore dotfiles and backup files
+    for f in files[:]:
+        if f.startswith('.') or f.endswith('.bak'):
+            files.remove(f)
+
+    # classify files for which kind of package they belong to
+    fl = {}
+    for kind in list(Kind) + ['all']:
+        fl[kind] = []
+
+    for f in files[:]:
+        if f == 'sha512.sum' or f == 'override.hint':
+            fl['all'].append(f)
+            files.remove(f)
+        elif re.match(r'^' + re.escape(p) + r'.*\.hint$', f):
+            fl['all'].append(f)
+            files.remove(f)
+        elif re.match(r'^' + re.escape(p) + r'.*\.tar\.(bz2|gz|lzma|xz)$', f):
+            if '-src.tar' in f:
+                fl[Kind.source].append(f)
+            else:
+                fl[Kind.binary].append(f)
+            files.remove(f)
 
-            with open(os.path.join(dirpath, 'sha512.sum')) as fo:
-                for l in fo:
-                    match = re.match(r'^(\S+)\s+(?:\*|)(\S+)$', l)
-                    if match:
-                        sha512[match.group(2)] = match.group(1)
-                    else:
-                        logging.warning("bad line '%s' in sha512.sum for package '%s'" % (l.strip(), p))
+    # read package
+    result = False
+    for kind in Kind:
+        # always create binary packages when uploading to allow replacement
+        # hints, otherwise, only create a package if there's archives for it to
+        # contain
+        if fl[kind] or (upload and kind == Kind.binary):
+            result = read_one_package(packages, p, relpath, dirpath, fl[kind] + fl['all'], remove, kind) or result
 
-        # discard obsolete md5.sum
-        if 'md5.sum' in files:
-            files.remove('md5.sum')
+    # warn about unexpected files, including tarfiles which don't match the
+    # package name
+    if files:
+        logging.error("unexpected files in %s: %s" % (p, ', '.join(files)))
+        result = True
 
-        # build a list of version-releases (since replacement pvr.hint files are
-        # allowed to be uploaded, we must consider both .tar and .hint files for
-        # that), and collect the attributes for each tar file
-        tars = defaultdict(dict)
-        vr_list = set()
+    return result
 
-        for f in list(files):
-            match = re.match(r'^' + re.escape(p) + r'.*\.(tar\.(bz2|gz|lzma|xz)|hint)$', f)
-            if not match:
-                continue
 
-            if not f.endswith('.hint'):
-                files.remove(f)
+#
+# read a single package
+#
+def read_one_package(packages, p, relpath, dirpath, files, remove, kind):
+    warnings = False
 
-            # warn if filename doesn't follow P-V-R naming convention
-            #
-            # P must match the package name, V can contain anything, R must
-            # start with a number
-            match = re.match(r'^' + re.escape(p) + r'-(.+)-(\d[0-9a-zA-Z.]*)(-src|)\.' + match.group(1) + '$', f)
-            if not match:
-                logging.error("file '%s' in package '%s' doesn't follow naming convention" % (f, p))
-                return True
-            else:
-                v = match.group(1)
-                r = match.group(2)
-
-                # historically, V can contain a '-' (since we can use the fact
-                # we already know P to split unambiguously), but this is a bad
-                # idea.
-                if '-' in v:
-                    if v in past_mistakes.hyphen_in_version.get(p, []):
-                        lvl = logging.INFO
-                    else:
-                        lvl = logging.ERROR
-                        warnings = True
-                    logging.log(lvl, "file '%s' in package '%s' contains '-' in version" % (f, p))
+    if not re.match(r'^[\w\-._+]*$', p):
+        logging.error("package '%s' name contains illegal characters" % p)
+        return True
 
-                if not v[0].isdigit():
-                    logging.error("file '%s' in package '%s' has a version which doesn't start with a digit" % (f, p))
-                    warnings = True
+    # assumption: no real package names end with '-src'
+    #
+    # enforce this, because source and install package names exist in a
+    # single namespace currently, and otherwise there could be a collision
+    if p.endswith('-src'):
+        logging.error("package '%s' name ends with '-src'" % p)
+        return True
+
+    # check for duplicate package names at different paths
+    (_, _, pkgpath) = relpath.split(os.sep, 2)
+    pn = p + ('-src' if kind == Kind.source else '')
+
+    if pn in packages:
+        logging.error("duplicate package name at paths %s and %s" %
+                      (relpath, packages[p].pkgpath))
+        return True
+
+    # determine version overrides
+    note_absent = ('override.hint' in remove) or ('override.hint' in files)
 
-                # if not there already, add to version-release list
-                vr = '%s-%s' % (v, r)
-                vr_list.add(vr)
-
-            if not f.endswith('.hint'):
-                # collect the attributes for each tar file
-                t = Tar()
-                t.path = relpath
-                t.fn = f
-                t.size = os.path.getsize(os.path.join(dirpath, f))
-                t.is_empty = tarfile_is_empty(os.path.join(dirpath, f))
-                t.mtime = os.path.getmtime(os.path.join(dirpath, f))
-
-                if f in sha512:
-                    t.sha512 = sha512[f]
+    if 'override.hint' in files:
+        # read override.hint
+        override_hints = read_hints(p, os.path.join(dirpath, 'override.hint'), hint.override)
+        if override_hints is None:
+            logging.error("error parsing %s" % (os.path.join(dirpath, 'override.hint')))
+            return True
+        files.remove('override.hint')
+    else:
+        override_hints = {}
+
+    # if override.hint exists or is being removed, explicitly note absent
+    # stability level hints
+    if note_absent:
+        for level in ['test', 'curr', 'prev']:
+            if level not in override_hints:
+                override_hints[level] = None
+
+    # read sha512.sum
+    sha512 = {}
+    if 'sha512.sum' not in files:
+        logging.debug("no sha512.sum for package '%s'" % p)
+    else:
+        files.remove('sha512.sum')
+
+        with open(os.path.join(dirpath, 'sha512.sum')) as fo:
+            for l in fo:
+                match = re.match(r'^(\S+)\s+(?:\*|)(\S+)$', l)
+                if match:
+                    sha512[match.group(2)] = match.group(1)
+                else:
+                    logging.warning("bad line '%s' in sha512.sum for package '%s'" % (l.strip(), p))
+
+    # build a list of version-releases (since replacement pvr.hint files are
+    # allowed to be uploaded, we must consider both .tar and .hint files for
+    # that), and collect the attributes for each tar file
+    tars = defaultdict(dict)
+    vr_list = set()
+
+    for f in list(files):
+        # warn if filename doesn't follow P-V-R naming convention
+        #
+        # P must match the package name, V can contain anything, R must
+        # start with a number
+        match = re.match(r'^' + re.escape(p) + r'-(.+)-(\d[0-9a-zA-Z.]*)(-src|)\.(tar\.(bz2|gz|lzma|xz)|hint)$', f)
+        if not match:
+            logging.error("file '%s' in package '%s' doesn't follow naming convention" % (f, p))
+            return True
+        else:
+            v = match.group(1)
+            r = match.group(2)
+
+            # historically, V can contain a '-' (since we can use the fact
+            # we already know P to split unambiguously), but this is a bad
+            # idea.
+            if '-' in v:
+                if v in past_mistakes.hyphen_in_version.get(p, []):
+                    lvl = logging.INFO
                 else:
-                    t.sha512 = sha512_file(os.path.join(dirpath, f))
-                    logging.debug("no sha512.sum line for file %s in package '%s', computed sha512 hash is %s" % (f, p, t.sha512))
-
-                tars[vr][f] = t
-
-        # determine hints for each version we've encountered
-        version_hints = {}
-        hints = {}
-        actual_tars = {}
-        for vr in vr_list:
-            hint_fn = '%s-%s.hint' % (p, vr)
-            if hint_fn in files:
-                # is there a PVR.hint file?
-                pvr_hint = read_hints(p, os.path.join(dirpath, hint_fn), hint.pvr)
-                if not pvr_hint:
-                    logging.error("error parsing %s" % (os.path.join(dirpath, hint_fn)))
-                    return True
-                warnings = clean_hints(p, pvr_hint, warnings)
-                files.remove(hint_fn)
+                    lvl = logging.ERROR
+                    warnings = True
+                logging.log(lvl, "file '%s' in package '%s' contains '-' in version" % (f, p))
+
+            if not v[0].isdigit():
+                logging.error("file '%s' in package '%s' has a version which doesn't start with a digit" % (f, p))
+                warnings = True
+
+            # if not there already, add to version-release list
+            vr = '%s-%s' % (v, r)
+            vr_list.add(vr)
+
+        if not f.endswith('.hint'):
+            # collect the attributes for each tar file
+            t = Tar()
+            t.path = relpath
+            t.fn = f
+            t.size = os.path.getsize(os.path.join(dirpath, f))
+            t.is_empty = tarfile_is_empty(os.path.join(dirpath, f))
+            t.mtime = os.path.getmtime(os.path.join(dirpath, f))
+
+            if f in sha512:
+                t.sha512 = sha512[f]
             else:
-                # it's an error to not have a pvr.hint
-                logging.error("package %s has packages for version %s, but no %s" % (p, vr, hint_fn))
+                t.sha512 = sha512_file(os.path.join(dirpath, f))
+                logging.debug("no sha512.sum line for file %s in package '%s', computed sha512 hash is %s" % (f, p, t.sha512))
+
+            tars[vr][f] = t
+
+    # determine hints for each version we've encountered
+    version_hints = {}
+    hints = {}
+    actual_tars = {}
+    for vr in vr_list:
+        hint_fn = '%s-%s.hint' % (p, vr)
+        if hint_fn in files:
+            # is there a PVR.hint file?
+            pvr_hint = read_hints(p, os.path.join(dirpath, hint_fn), hint.pvr)
+            if not pvr_hint:
+                logging.error("error parsing %s" % (os.path.join(dirpath, hint_fn)))
                 return True
+            warnings = clean_hints(p, pvr_hint, warnings)
+        else:
+            # it's an error to not have a pvr.hint
+            logging.error("package %s has packages for version %s, but no %s" % (p, vr, hint_fn))
+            return True
 
-            # apply a version override
-            if 'version' in pvr_hint:
-                ovr = pvr_hint['version']
-            else:
-                ovr = vr
-
-            hintobj = Hint()
-            hintobj.path = relpath
-            hintobj.fn = hint_fn
-            hintobj.hints = pvr_hint
-
-            version_hints[ovr] = pvr_hint
-            hints[ovr] = hintobj
-            actual_tars[ovr] = tars[vr]
-
-        # ignore dotfiles and backup files
-        for f in files[:]:
-            if f.startswith('.') or f.endswith('.bak'):
-                files.remove(f)
-
-        # warn about unexpected files, including tarfiles which don't match the
-        # package name
-        if files:
-            logging.error("unexpected files in %s: %s" % (p, ', '.join(files)))
-            warnings = True
-
-        packages[p].version_hints = version_hints
-        packages[p].override_hints = override_hints
-        packages[p].tars = actual_tars
-        packages[p].hints = hints
-        packages[p].pkgpath = pkgpath
-        packages[p].skip = any(['skip' in version_hints[vr] for vr in version_hints])
-
-    elif (relpath.count(os.path.sep) > 1):
-        for s in ['md5.sum', 'sha512.sum']:
-            if s in files:
-                files.remove(s)
-
-        if len(files) > 0:
-            logging.error("no .hint files in %s but has files: %s" % (dirpath, ', '.join(files)))
-            warnings = True
+        # apply a version override
+        if 'version' in pvr_hint:
+            ovr = pvr_hint['version']
+        else:
+            ovr = vr
+
+        # external source will always point to a source package
+        if 'external-source' in pvr_hint:
+            pvr_hint['external-source'] += '-src'
+
+        hintobj = Hint()
+        hintobj.path = relpath
+        hintobj.fn = hint_fn
+        hintobj.hints = pvr_hint
+
+        version_hints[ovr] = pvr_hint
+        hints[ovr] = hintobj
+        actual_tars[ovr] = tars[vr]
+
+    packages[pn].version_hints = version_hints
+    packages[pn].override_hints = override_hints
+    packages[pn].tars = actual_tars
+    packages[pn].hints = hints
+    packages[pn].pkgpath = pkgpath
+    packages[pn].skip = any(['skip' in version_hints[vr] for vr in version_hints])
+    packages[pn].kind = kind
+    # since we are kind of inventing the source package names, and don't
+    # want to report them, keep track of the real name
+    packages[pn].orig_name = p
 
     return warnings
 
@@ -485,8 +545,9 @@ def validate_packages(args, packages):
                             packages[o].version_hints[ov]['depends'] = ','.join(depends)
                             logging.debug("removed obsoleting '%s' from the depends: of package '%s'" % (p, o))
 
-        packages[p].vermap = defaultdict(defaultdict)
+        packages[p].vermap = {}
         is_empty = {}
+        has_source = False
         has_install = False
         has_nonempty_install = False
 
@@ -495,6 +556,7 @@ def validate_packages(args, packages):
                 # categorize each tarfile as either 'source' or 'install'
                 if re.search(r'-src.*\.tar', t):
                     category = 'source'
+                    has_source = True
                 else:
                     category = 'install'
                     has_install = True
@@ -502,6 +564,9 @@ def validate_packages(args, packages):
                     if not is_empty[t]:
                         has_nonempty_install = True
 
+                if vr not in packages[p].vermap:
+                    packages[p].vermap[vr] = {}
+
                 # for each version, a package can contain at most one source tar
                 # file and at most one install tar file.  warn if we have too many
                 # (for e.g. both a .xz and .bz2 install tar file)
@@ -513,6 +578,16 @@ def validate_packages(args, packages):
                 packages[p].vermap[vr][category] = t
                 packages[p].vermap[vr]['mtime'] = tar.mtime
 
+        # check that package only contains tar archives of the appropriate type
+        if packages[p].kind == Kind.source:
+            if has_install:
+                logging.error("source package '%s' has install archives" % (p))
+                error = True
+        elif packages[p].kind == Kind.binary:
+            if has_source:
+                logging.error("package '%s' has source archives" % (p))
+                error = True
+
         obsolete = any(['_obsolete' in packages[p].version_hints[vr].get('category', '') for vr in packages[p].version_hints])
 
         # if the package has no install tarfiles (i.e. is source only), make
@@ -684,7 +759,7 @@ def validate_packages(args, packages):
         # should probably be marked obsolete
         if not packages[p].skip:
             for vr in packages[p].version_hints:
-                if '_obsolete' not in packages[p].version_hints[vr].get('category', ''):
+                if '_obsolete' not in packages[p].version_hints[vr].get('category', '') and vr in packages[p].vermap:
                     if ('source' not in packages[p].vermap[vr]) and ('external-source' not in packages[p].version_hints[vr]):
                         if 'install' in packages[p].vermap[vr]:
                             if packages[p].tar(vr, 'install').is_empty:
@@ -692,15 +767,21 @@ def validate_packages(args, packages):
                                     ('empty-obsolete' in packages[p].version_hints[vr].get('disable-check', ''))):
                                     lvl = logging.DEBUG
                                 else:
-                                    lvl = logging.ERROR
-                                    error = True
-                                logging.log(lvl, "package '%s' version '%s' has empty install tar file and no source, but it's not in the _obsolete category" % (p, vr))
-
-        for vr in packages[p].version_hints:
-            if 'build-depends' in packages[p].version_hints[vr]:
-                if 'source' not in packages[p].vermap[vr]:
-                    logging.error("package '%s' version '%s' has build-depends but no source" % (p, vr))
-                    error = True
+                                    if 'external-source' in packages[p].version_hints[vr]:
+                                        lvl = logging.ERROR
+                                        error = True
+                                    else:
+                                        lvl = logging.WARNING
+                                logging.log(lvl, "package '%s' version '%s' has empty install tar file, but it's not in the _obsolete category" % (p, vr))
+
+        # the presence of build-depends only makes sense if this is a source
+        # package, or it has a sibling source package, with which is shares hints
+        if packages[p].kind != Kind.source:
+            for vr in packages[p].version_hints:
+                if 'build-depends' in packages[p].version_hints[vr]:
+                    if 'external-source' in packages[p].version_hints[vr]:
+                        logging.error("package '%s' version '%s' has build-depends but isn't a source package" % (p, vr))
+                        error = True
 
     # make another pass to verify a source tarfile exists for every install
     # tarfile version
@@ -709,43 +790,39 @@ def validate_packages(args, packages):
             if 'install' not in packages[p].vermap[v]:
                 continue
 
-            # unless the install tarfile is empty
-            if packages[p].tar(v, 'install').is_empty:
-                continue
-
-            # source tarfile may be either in this package or in the
-            # external-source package
-            #
-            # mark the source tarfile as being used by an install tarfile
-            if 'source' in packages[p].vermap[v]:
-                packages[p].tar(v, 'source').is_used = True
-                packages[p].is_used_by.add(p)
-                continue
+            sourceless = False
+            missing_source = True
 
+            # source tarfile is in the external-source package, if specified,
+            # otherwise it's in the sibling source package
             if 'external-source' in packages[p].version_hints[v]:
                 es_p = packages[p].version_hints[v]['external-source']
-                if es_p in packages:
-                    if 'source' in packages[es_p].vermap[v]:
-                        packages[es_p].tar(v, 'source').is_used = True
-                        packages[es_p].is_used_by.add(p)
-                        continue
-
-                # this is a bodge to follow external-source: which hasn't been
-                # updated following a source package de-duplication
-                es_p = es_p + '-src'
-                if es_p in packages:
-                    if 'source' in packages[es_p].vermap[v]:
-                        logging.warning("package '%s' version '%s' external-source: should be %s" % (p, v, es_p))
-                        packages[es_p].tar(v, 'source').is_used = True
-                        packages[es_p].is_used_by.add(p)
-                        continue
-
-            # unless this package is marked as 'self-source'
-            if p in past_mistakes.self_source:
-                continue
+            else:
+                es_p = p + '-src'
 
-            logging.error("package '%s' version '%s' is missing source" % (p, v))
-            error = True
+            # mark the source tarfile as being used by an install tarfile
+            if es_p in packages:
+                if v in packages[es_p].vermap and 'source' in packages[es_p].vermap[v]:
+                    packages[es_p].tar(v, 'source').is_used = True
+                    packages[es_p].is_used_by.add(p)
+                    missing_source = False
+
+            if missing_source:
+                # unless the install tarfile is empty
+                if packages[p].tar(v, 'install').is_empty:
+                    sourceless = True
+                    missing_source = False
+
+                # unless this package is marked as 'self-source'
+                if p in past_mistakes.self_source:
+                    sourceless = True
+                    missing_source = False
+
+            # ... it's an error for this package to be missing source
+            packages[p].tar(v, 'install').sourceless = sourceless
+            if missing_source:
+                logging.error("package '%s' version '%s' is missing source" % (p, v))
+                error = True
 
     # make another pass to verify that each non-empty source tarfile version has
     # at least one corresponding non-empty install tarfile, in some package.
@@ -805,7 +882,7 @@ def validate_packages(args, packages):
                 most_common = False
 
             error = True
-            logging.error("install packages from source package '%s' have non-unique current versions %s" % (source_p, ', '.join(reversed(out))))
+            logging.error("install packages from source package '%s' have non-unique current versions %s" % (packages[source_p].orig_name, ', '.join(reversed(out))))
 
     # validate that all packages are in the package maintainers list
     error = validate_package_maintainers(args, packages) or error
@@ -835,13 +912,19 @@ def validate_package_maintainers(args, packages):
         if not is_in_package_list(packages[p].pkgpath, all_packages):
             logging.error("package '%s' on path '%s', which doesn't start with a package in the package list" % (p, packages[p].pkgpath))
             error = True
+        # source which is superseded by a different package, but retained due to
+        # old install versions can be unmaintained and non-obsolete
+        if packages[p].kind == Kind.source:
+            continue
         # validate that the source package has a maintainer
         bv = packages[p].best_version
-        es = packages[p].version_hints[bv].get('external-source', p)
-        if es not in all_packages and p not in all_packages:
-            if bv not in past_mistakes.maint_anomalies.get(p, []):
-                logging.error("package '%s' is not obsolete, but has no maintainer" % (p))
-                error = True
+        if bv:
+            es = packages[p].version_hints[bv].get('external-source', p)
+            es_pn = packages[es].orig_name
+            if es_pn not in all_packages and p not in all_packages:
+                if bv not in past_mistakes.maint_anomalies.get(p, []):
+                    logging.error("package '%s' is not obsolete, but has no maintainer" % (p))
+                    error = True
 
     return error
 
@@ -886,7 +969,7 @@ def write_setup_ini(args, packages, arch):
         # for each package
         for p in sorted(packages.keys(), key=sort_key):
             # do nothing if 'skip'
-            if packages[p].skip and not p.endswith('-src'):
+            if packages[p].skip:
                 continue
 
             # write package data
@@ -936,12 +1019,16 @@ def write_setup_ini(args, packages, arch):
             # next put any other versions
             #
             # these [prev] or [test] sections are superseded by the final ones.
-            for version in sorted(packages[p].vermap.keys(), key=lambda v: SetupVersion(v), reverse=True):
-                # ignore versions which should have been removed by stale
-                # package removal
-                if not (set(['install', 'source']) & set(packages[p].vermap[version])):
-                    continue
+            #
+            # (to maintain historical behaviour, include versions which only
+            # exist as a source package)
+            #
+            versions = set(packages[p].vermap.keys())
+            sibling_src = p + '-src'
+            if sibling_src in packages:
+                versions.update(packages[sibling_src].vermap.keys())
 
+            for version in sorted(versions, key=lambda v: SetupVersion(v), reverse=True):
                 # skip over versions assigned to stability level: 'curr' has
                 # already be done, and 'prev' and 'test' will be done later
                 skip = False
@@ -955,7 +1042,7 @@ def write_setup_ini(args, packages, arch):
                     continue
 
                 # test versions receive the test label
-                if 'test' in packages[p].version_hints[version]:
+                if 'test' in packages[p].version_hints.get(version, {}):
                     level = "test"
                 else:
                     level = "prev"
@@ -979,33 +1066,42 @@ def write_setup_ini(args, packages, arch):
                     print("[%s]" % tag, file=f)
                 print("version: %s" % version, file=f)
 
-                if 'install' in packages[p].vermap[version]:
+                is_empty = False
+                if 'install' in packages[p].vermap.get(version, {}):
                     tar_line(packages[p], 'install', version, f)
+                    is_empty = packages[p].tar(version, 'install').is_empty
 
-                # look for corresponding source in this package first
-                if 'source' in packages[p].vermap[version]:
-                    tar_line(packages[p], 'source', version, f)
-                # if that doesn't exist, follow external-source
-                elif 'external-source' in packages[p].version_hints[version]:
-                    s = packages[p].version_hints[version]['external-source']
-                    # external-source points to a real source package (-src)
-                    if s.endswith('-src'):
-                        print("Source: %s" % (s), file=f)
-                    # external-source points to a source file in another package
+                hints = packages[p].version_hints.get(version, {})
+
+                # follow external-source
+                if 'external-source' in hints:
+                    s = hints['external-source']
+                else:
+                    s = sibling_src
+                    if s not in packages:
+                        s = None
+
+                # external-source points to a source file in another package
+                if s:
+                    if 'source' in packages[s].vermap.get(version, {}):
+                        tar_line(packages[s], 'source', version, f)
                     else:
-                        if 'source' in packages[s].vermap[version]:
-                            tar_line(packages[s], 'source', version, f)
-                        else:
-                            logging.warning("package '%s' version '%s' has no source in external-source '%s'" % (p, version, s))
+                        if not (is_empty or packages[s].orig_name in past_mistakes.self_source):
+                            logging.warning("package '%s' version '%s' has no source in '%s'" % (p, version, packages[s].orig_name))
 
-                if packages[p].version_hints[version].get('depends', '') or requires:
-                    print("depends2: %s" % packages[p].version_hints[version].get('depends', ''), file=f)
+                # external-source should also be capable of pointing to a 'real'
+                # source package (if cygport could generate such a thing), in
+                # which case we should emit a 'Source:' line, and the package is
+                # also itself emitted.
 
-                if packages[p].version_hints[version].get('obsoletes', ''):
-                    print("obsoletes: %s" % packages[p].version_hints[version]['obsoletes'], file=f)
+                if hints.get('depends', '') or requires:
+                    print("depends2: %s" % hints.get('depends', ''), file=f)
 
-                if packages[p].version_hints[version].get('build-depends', ''):
-                    bd = packages[p].version_hints[version]['build-depends']
+                if hints.get('obsoletes', ''):
+                    print("obsoletes: %s" % hints['obsoletes'], file=f)
+
+                if hints.get('build-depends', ''):
+                    bd = hints['build-depends']
 
                     # Ideally, we'd transform dependency atoms which aren't
                     # cygwin package names into package names. For the moment,
@@ -1016,11 +1112,11 @@ def write_setup_ini(args, packages, arch):
                     if bd:
                         print("build-depends: %s" % ', '.join(bd), file=f)
 
-                if packages[p].version_hints[version].get('provides', ''):
-                    print("provides: %s" % packages[p].version_hints[version]['provides'], file=f)
+                if hints.get('provides', ''):
+                    print("provides: %s" % hints['provides'], file=f)
 
-                if packages[p].version_hints[version].get('conflicts', ''):
-                    print("conflicts: %s" % packages[p].version_hints[version]['conflicts'], file=f)
+                if hints.get('conflicts', ''):
+                    print("conflicts: %s" % hints['conflicts'], file=f)
 
 
 # helper function to output details for a particular tar file
@@ -1066,7 +1162,7 @@ def write_repo_json(args, packages, f):
 
         bv = po.best_version
 
-        if po.version_hints[bv].get('external-source', None):
+        if po.kind != Kind.source:
             continue
 
         versions = {}
@@ -1269,8 +1365,10 @@ def stale_packages(packages):
     # build a move list of stale versions
     stale = MoveList()
     for pn, po in packages.items():
+        all_stale = {}
+
         for v in sorted(po.vermap.keys(), key=lambda v: SetupVersion(v)):
-            all_stale = True
+            all_stale[v] = True
             for category in ['source', 'install']:
                 if category in po.vermap[v]:
                     if not getattr(po.tar(v, category), 'fresh', False):
@@ -1278,14 +1376,32 @@ def stale_packages(packages):
                         stale.add(to.path, to.fn)
                         logging.debug("package '%s' version '%s' %s is stale" % (pn, v, category))
                     else:
-                        all_stale = False
+                        all_stale[v] = False
 
+        for v in po.hints:
             # if there's a pvr.hint without a fresh source or install of the
-            # same version, move it as well
-            if all_stale:
-                if v in po.hints:
-                    stale.add(po.hints[v].path, po.hints[v].fn)
-                    logging.debug("package '%s' version '%s' hint is stale" % (pn, v))
+            # same version, move it as well (this is complicated as the hint may
+            # be used by both a binary and source package; give the source
+            # package ownership, if it exists)
+
+            if po.kind == Kind.source:
+                if ((po.orig_name in packages) and
+                    (v in packages[po.orig_name].vermap) and
+                    ('install' in packages[po.orig_name].vermap[v])):
+                    sourceless = packages[po.orig_name].tar(v, 'install').sourceless
+                else:
+                    sourceless = False
+            else:
+                if 'install' in po.vermap.get(v, {}):
+                    sourceless = po.tar(v, 'install').sourceless
+                else:
+                    sourceless = False
+
+            binary_owns_hint = ('external-source' in po.hints[v].hints) or sourceless
+
+            if all_stale.get(v, True) and ((po.kind == Kind.binary) == binary_owns_hint):
+                stale.add(po.hints[v].path, po.hints[v].fn)
+                logging.debug("package '%s' version '%s' hint is stale" % (pn, v))
 
         # clean up freshness mark
         for v in po.vermap:
diff --git a/calm/past_mistakes.py b/calm/past_mistakes.py
index 31bcb0d..0eceb76 100644
--- a/calm/past_mistakes.py
+++ b/calm/past_mistakes.py
@@ -155,6 +155,7 @@ empty_but_not_obsolete = [
 mtime_anomalies = [
     'gcc-java',
     'gcc-tools-epoch2-autoconf',
+    'gcc-tools-epoch2-autoconf-src',
     'gv-debuginfo',
     'libgcj-common',
     'libgcj16',
@@ -167,10 +168,13 @@ mtime_anomalies = [
     'subversion-perl',
     'subversion-python',
     'subversion-ruby',
+    'subversion-src',
     'subversion-tools',
 ]
 
 # packages with maintainer anomalies
+#
+# don't add to this list, fix the package (e.g. by obsoleting it)
 maint_anomalies = {
     'libelf0': ['0.8.13-2'],  # libelf is called libelf0 in x86 arch
     'libelf0-devel': ['0.8.13-2'],
diff --git a/calm/pkg2html.py b/calm/pkg2html.py
index e5c3c06..d61d5db 100755
--- a/calm/pkg2html.py
+++ b/calm/pkg2html.py
@@ -63,8 +63,8 @@ from . import utils
 #
 # get sdesc for a package
 #
-def sdesc(packages, p, bv):
-    header = packages[p].version_hints[bv]['sdesc']
+def sdesc(po, bv):
+    header = po.version_hints[bv]['sdesc']
     header = header.strip('"')
     return html.escape(header, quote=False)
 
@@ -72,11 +72,11 @@ def sdesc(packages, p, bv):
 #
 # ditto for ldesc
 #
-def ldesc(packages, p, bv):
-    if 'ldesc' in packages[p].version_hints[bv]:
-        header = packages[p].version_hints[bv]['ldesc']
+def ldesc(po, bv):
+    if 'ldesc' in po.version_hints[bv]:
+        header = po.version_hints[bv]['ldesc']
     else:
-        return sdesc(packages, p, bv)
+        return sdesc(po, bv)
 
     header = header.strip('"')
     # escape html entities
@@ -91,6 +91,27 @@ def ldesc(packages, p, bv):
 
 
 #
+# try hard to find a package object for package p
+#
+def arch_package(packages, p):
+    for arch in common_constants.ARCHES:
+        if p in packages[arch]:
+            return packages[arch][p]
+    return None
+
+
+#
+# build a dict of the arches which contain package p
+#
+def arch_packages(packages, p):
+    result = {}
+    for arch in common_constants.ARCHES:
+        if p in packages[arch]:
+            result[arch] = packages[arch][p]
+    return result
+
+
+#
 # ensure a directory exists
 #
 def ensure_dir_exists(args, path):
@@ -121,7 +142,8 @@ def update_package_listings(args, packages):
 
     def linkify_package(p):
         if p in package_list:
-            return '<a href="%s.html">%s</a>' % (p, p)
+            pn = arch_package(packages, p).orig_name
+            return '<a href="%s.html">%s</a>' % (p, pn)
         logging.debug('package linkification failed for %s' % p)
         return p
 
@@ -146,17 +168,21 @@ def update_package_listings(args, packages):
                 with open(summary, 'w') as f:
                     os.fchmod(f.fileno(), 0o755)
 
-                    arch_packages = None
-                    for arch in common_constants.ARCHES:
-                        if p in packages[arch]:
-                            arch_packages = packages[arch]
-                            break
-
-                    if not arch_packages:
+                    pos = arch_packages(packages, p)
+                    if not pos:
                         continue
 
-                    bv = arch_packages[p].best_version
-                    title = "Cygwin Package Summary for %s" % p
+                    po = next(iter(pos.values()))
+                    bv = po.best_version
+
+                    if po.kind == package.Kind.source:
+                        pn = po.orig_name
+                        title = "Cygwin Package Summary for %s (source)" % pn
+                        kind = "Source Package"
+                    else:
+                        pn = p
+                        title = "Cygwin Package Summary for %s" % p
+                        kind = "Package"
 
                     print(textwrap.dedent('''\
                     <!DOCTYPE html>
@@ -170,31 +196,60 @@ def update_package_listings(args, packages):
                     <!--#include virtual="/navbar.html" -->
                     <div id="main">
                     <!--#include virtual="/top.html" -->
-                    <h1>Package: %s</h1>''' % (title, p)), file=f)
-
-                    print('<span class="detail">summary</span>: %s<br><br>' % sdesc(arch_packages, p, bv), file=f)
-                    print('<span class="detail">description</span>: %s<br><br>' % ldesc(arch_packages, p, bv), file=f)
-                    print('<span class="detail">categories</span>: %s<br><br>' % arch_packages[p].version_hints[bv].get('category', ''), file=f)
+                    <h1>%s: %s</h1>''' % (title, kind, pn)), file=f)
 
-                    for key in ['depends', 'obsoletes', 'provides', 'conflicts', 'build-depends']:
-                        value = arch_packages[p].version_hints[bv].get(key, None)
-                        if value:
-                            print('<span class="detail">%s</span>: %s<br><br>' % (key, ', '.join([linkify_package(p) for p in value.split(', ')])), file=f)
+                    print('<span class="detail">summary</span>: %s<br><br>' % sdesc(po, bv), file=f)
+                    print('<span class="detail">description</span>: %s<br><br>' % ldesc(po, bv), file=f)
+                    print('<span class="detail">categories</span>: %s<br><br>' % po.version_hints[bv].get('category', ''), file=f)
 
-                    es = arch_packages[p].version_hints[bv].get('external-source', None)
-                    if es:
-                        print('<span class="detail">source</span>: %s<br><br>' % linkify_package(es), file=f)
+                    if po.kind == package.Kind.source:
+                        details = ['build-depends']
                     else:
-                        print('<span class="detail">binaries</span>: %s<br><br>' % ', '.join([linkify_package(p) for p in sorted(arch_packages[p].is_used_by)]), file=f)
+                        details = ['depends', 'obsoletes', 'provides', 'conflicts']
+
+                    for key in details:
+                        # make the union of the package list for this detail
+                        # across arches, and then annotate any items which don't
+                        # appear for all arches
+                        value = {}
+                        values = set()
+                        for arch in pos:
+                            t = pos[arch].version_hints[pos[arch].best_version].get(key, None)
+                            if t:
+                                value[arch] = set(t.split(', '))
+                            else:
+                                value[arch] = set()
+                            values.update(value[arch])
+
+                        if values:
+                            detail = []
+                            for detail_pkg in sorted(values):
+                                if all(detail_pkg in value[arch] for arch in pos):
+                                    detail.append(linkify_package(detail_pkg))
+                                else:
+                                    detail.append(linkify_package(detail_pkg) + ' (%s)' % (','.join([arch for arch in pos if detail_pkg in value[arch]])))
+
+                            print('<span class="detail">%s</span>: %s<br><br>' % (key, ', '.join(detail)), file=f)
+
+                    if po.kind == package.Kind.source:
                         es = p
-
-                    if 'ORPHANED' in pkg_maintainers[es]:
+                        print('<span class="detail">install package(s)</span>: %s<br><br>' % ', '.join([linkify_package(p) for p in sorted(po.is_used_by)]), file=f)
+                    else:
+                        es = po.version_hints[bv].get('external-source', p + '-src')
+                        print('<span class="detail">source package</span>: %s<br><br>' % linkify_package(es), file=f)
+
+                    es_po = arch_package(packages, es)
+                    if not es_po:
+                        es_po = po
+                    m_pn = es_po.orig_name
+                    if 'ORPHANED' in pkg_maintainers[m_pn]:
                         m = 'ORPHANED'
                     else:
-                        m = ', '.join(sorted(pkg_maintainers[es]))
+                        m = ', '.join(sorted(pkg_maintainers[m_pn]))
 
                     if m:
                         print('<span class="detail">maintainer(s)</span>: %s ' % m, file=f)
+
                         print(textwrap.dedent('''\
                         <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
                         <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>'''), file=f)
@@ -215,7 +270,7 @@ def update_package_listings(args, packages):
                                 t = p.vermap[v][category]
                                 size = int(math.ceil(p.tar(v, category).size / 1024))
                                 name = v if category == 'install' else v + ' (source)'
-                                target = "%s-%s" % (pn, v) + ('' if category == 'install' else '-src')
+                                target = "%s-%s" % (p.orig_name, v) + ('' if category == 'install' else '-src')
                                 test = 'test' if 'test' in p.version_hints[v] else 'stable'
                                 print('<tr><td>%s</td><td class="right">%d kB</td><td>[<a href="../%s/%s/%s">list of files</a>]</td><td>%s</td></tr>' % (name, size, arch, pn, target, test), file=f)
 
@@ -236,23 +291,44 @@ def update_package_listings(args, packages):
         if not args.dryrun:
             os.unlink(r)
 
-    #
-    # write packages.inc
-    #
+    write_packages_inc(args, packages, 'packages.inc', package.Kind.binary, 'package_list.html')
+    write_packages_inc(args, packages, 'src_packages.inc', package.Kind.source, 'src_package_list.html')
 
-    packages_inc = os.path.join(args.htdocs, 'packages.inc')
+
+#
+# write package index page fragment for inclusion
+#
+def write_packages_inc(args, packages, name, kind, includer):
+    packages_inc = os.path.join(args.htdocs, name)
     logging.debug('writing %s' % packages_inc)
     if not args.dryrun:
         with open(packages_inc, 'w') as index:
             os.fchmod(index.fileno(), 0o644)
 
+            # This list contains all packages in any arch. Source packages
+            # appear under their original package name.
+            package_list = {}
+            for arch in packages:
+                for p in packages[arch]:
+                    if p.endswith('-debuginfo'):
+                        continue
+
+                    if packages[arch][p].kind == package.Kind.binary:
+                        if packages[arch][p].skip:
+                            continue
+
+                    if packages[arch][p].kind == kind:
+                        package_list[packages[arch][p].orig_name] = p
+
             jumplist = set()
-            for p in package_list:
+            for k in package_list:
+                p = package_list[k]
                 c = p[0].lower()
                 if c in string.ascii_lowercase:
                     jumplist.add(c)
 
             print('<p class="center">', file=index)
+            print('%d packages : ' % len(package_list), file=index)
             print(' - \n'.join(['<a href="#%s">%s</a>' % (c, c) for c in sorted(jumplist)]), file=index)
             print('</p>', file=index)
 
@@ -260,21 +336,22 @@ def update_package_listings(args, packages):
 
             first = ' class="pkgname"'
             jump = ''
-            for p in sorted(package_list, key=package.sort_key):
-                if p.endswith('-debuginfo'):
-                    continue
-
-                arch_packages = None
-                for arch in common_constants.ARCHES:
-                    if p in packages[arch]:
-                        arch_packages = packages[arch]
-                        break
+            for k in sorted(package_list, key=package.sort_key):
+                p = package_list[k]
 
-                if not arch_packages:
+                po = arch_package(packages, p)
+                if not po:
                     continue
 
-                bv = arch_packages[p].best_version
-                header = sdesc(arch_packages, p, bv)
+                bv = po.best_version
+                header = sdesc(po, bv)
+
+                if po.kind == package.Kind.source:
+                    pn = po.orig_name
+                    if 'source' not in header:
+                        header += ' (source)'
+                else:
+                    pn = p
 
                 anchor = ''
                 if jump != p[0].lower():
@@ -283,14 +360,14 @@ def update_package_listings(args, packages):
                         anchor = ' id="%s"' % (jump)
 
                 print('<tr%s><td%s><a href="summary/%s.html">%s</a></td><td>%s</td></tr>' %
-                      (anchor, first, p, p, header),
+                      (anchor, first, p, pn, header),
                       file=index)
                 first = ''
 
             print('</table>', file=index)
 
         # touch the including file for the benefit of 'XBitHack full'
-        package_list = os.path.join(args.htdocs, 'package_list.html')
+        package_list = os.path.join(args.htdocs, includer)
         if os.path.exists(package_list):
             utils.touch(package_list)
 
@@ -370,10 +447,10 @@ def write_arch_listing(args, packages, arch):
 
                     with open(listing, 'w') as f:
                         bv = packages[p].best_version
-                        header = p + ": " + sdesc(packages, p, bv)
+                        header = p + ": " + sdesc(packages[p], bv)
 
                         if fver.endswith('-src'):
-                            header = header + " (source code)"
+                            header = header + " (source)"
 
                         print(textwrap.dedent('''\
                                                  <!DOCTYPE html>
diff --git a/calm/uploads.py b/calm/uploads.py
index 587aa9b..9914a16 100644
--- a/calm/uploads.py
+++ b/calm/uploads.py
@@ -257,7 +257,7 @@ def scan(m, all_packages, arch, args):
 
         # read and validate package
         if files:
-            if package.read_package(packages, m.homedir(), dirpath, files, remove=removed_files):
+            if package.read_package_dir(packages, m.homedir(), dirpath, files, remove=removed_files, upload=True):
                 error = True
 
     # always consider timestamp as checked during a dry-run, so it is never
diff --git a/test/test_calm.py b/test/test_calm.py
index a360fe0..ef4f79f 100755
--- a/test/test_calm.py
+++ b/test/test_calm.py
@@ -39,6 +39,7 @@ import unittest
 
 from calm.version import SetupVersion
 import calm.calm
+import calm.common_constants as common_constants
 import calm.hint as hint
 import calm.maintainers as maintainers
 import calm.package as package
@@ -165,10 +166,17 @@ class CalmTest(unittest.TestCase):
         setattr(args, 'pkglist', 'testdata/pkglist/cygwin-pkg-maint')
 
         packages = {}
+        for arch in common_constants.ARCHES:
+            packages[arch] = {}
         packages[args.arch] = package.read_packages(args.rel_area, args.arch)
         package.validate_packages(args, packages[args.arch])
         pkg2html.update_package_listings(args, packages)
 
+        # compare the output dirtree with expected
+        with self.subTest('dirtree'):
+            dirlist = capture_dirtree(htdocs)
+            compare_with_expected_file(self, 'testdata/htdocs.expected', dirlist, 'dirtree')
+
         # compare the output files with expected
         for (dirpath, subdirs, files) in os.walk(htdocs):
             relpath = os.path.relpath(dirpath, htdocs)
diff --git a/test/testdata/htdocs.expected/dirtree.expected b/test/testdata/htdocs.expected/dirtree.expected
new file mode 100644
index 0000000..5908ca9
--- /dev/null
+++ b/test/testdata/htdocs.expected/dirtree.expected
@@ -0,0 +1,102 @@
+{'.': ['packages.inc', 'src_packages.inc'],
+ 'summary': ['arc-src.html',
+             'arc.html',
+             'base-cygwin.html',
+             'corrupt-src.html',
+             'corrupt.html',
+             'cygwin-debuginfo.html',
+             'cygwin-devel.html',
+             'cygwin-src.html',
+             'cygwin.html',
+             'keychain-src.html',
+             'keychain.html',
+             'libdns_sd-devel.html',
+             'libdns_sd1.html',
+             'mDNSResponder-src.html',
+             'mDNSResponder.html',
+             'obs-a-src.html',
+             'obs-a.html',
+             'obs-b-src.html',
+             'obs-b.html',
+             'openssh-src.html',
+             'openssh.html',
+             'per-version-replacement-hint-only-src.html',
+             'per-version-replacement-hint-only.html',
+             'per-version-src.html',
+             'per-version.html',
+             'perl-Net-SMTP-SSL-src.html',
+             'perl-Net-SMTP-SSL.html',
+             'rpm-doc-src.html',
+             'rpm-doc.html',
+             'staleversion-src.html',
+             'staleversion.html',
+             'test-c-src.html',
+             'test-c.html',
+             'test-d-src.html',
+             'test-d.html',
+             'test-e-src.html',
+             'test-e.html',
+             'testpackage-src.html',
+             'testpackage.html'],
+ 'x86': ['.htaccess'],
+ 'x86/arc': ['.htaccess', 'arc-4.32.7-10'],
+ 'x86/arc-src': ['.htaccess', 'arc-4.32.7-10-src'],
+ 'x86/base-cygwin': ['.htaccess', 'base-cygwin-3.6-1', 'base-cygwin-3.8-1'],
+ 'x86/corrupt': ['.htaccess', 'corrupt-2.0.0-1'],
+ 'x86/corrupt-src': ['.htaccess', 'corrupt-2.0.0-1-src'],
+ 'x86/cygwin': ['.htaccess', 'cygwin-2.2.0-1', 'cygwin-2.2.1-1', 'cygwin-2.3.0-0.3'],
+ 'x86/cygwin-debuginfo': ['.htaccess',
+                          'cygwin-debuginfo-2.2.0-1',
+                          'cygwin-debuginfo-2.2.1-1',
+                          'cygwin-debuginfo-2.3.0-0.3'],
+ 'x86/cygwin-devel': ['.htaccess', 'cygwin-devel-2.2.0-1', 'cygwin-devel-2.2.1-1', 'cygwin-devel-2.3.0-0.3'],
+ 'x86/cygwin-src': ['.htaccess', 'cygwin-2.2.0-1-src', 'cygwin-2.2.1-1-src', 'cygwin-2.3.0-0.3-src'],
+ 'x86/keychain': ['.htaccess', 'keychain-2.6.8-1', 'keychain-2.7.1-1'],
+ 'x86/keychain-src': ['.htaccess', 'keychain-2.6.8-1-src', 'keychain-2.7.1-1-src'],
+ 'x86/libdns_sd-devel': ['.htaccess', 'libdns_sd-devel-379.32.1-1'],
+ 'x86/libdns_sd1': ['.htaccess', 'libdns_sd1-379.32.1-1'],
+ 'x86/mDNSResponder': ['.htaccess', 'mDNSResponder-379.32.1-1'],
+ 'x86/mDNSResponder-src': ['.htaccess', 'mDNSResponder-379.32.1-1-src'],
+ 'x86/obs-a': ['.htaccess', 'obs-a-1.0-1'],
+ 'x86/obs-a-src': ['.htaccess', 'obs-a-1.0-1-src'],
+ 'x86/obs-b': ['.htaccess', 'obs-b-1.0-1'],
+ 'x86/obs-b-src': ['.htaccess', 'obs-b-1.0-1-src'],
+ 'x86/openssh': ['.htaccess', 'openssh-7.2p2-1'],
+ 'x86/openssh-src': ['.htaccess', 'openssh-7.2p2-1-src'],
+ 'x86/per-version': ['.htaccess', 'per-version-4.0-1', 'per-version-4.8-1'],
+ 'x86/per-version-replacement-hint-only': ['.htaccess', 'per-version-replacement-hint-only-1.0-1'],
+ 'x86/per-version-replacement-hint-only-src': ['.htaccess', 'per-version-replacement-hint-only-1.0-1-src'],
+ 'x86/per-version-src': ['.htaccess', 'per-version-4.0-1-src', 'per-version-4.8-1-src'],
+ 'x86/perl-Net-SMTP-SSL': ['.htaccess',
+                           'perl-Net-SMTP-SSL-1.01-1',
+                           'perl-Net-SMTP-SSL-1.02-1',
+                           'perl-Net-SMTP-SSL-1.03-1'],
+ 'x86/perl-Net-SMTP-SSL-src': ['.htaccess',
+                               'perl-Net-SMTP-SSL-1.01-1-src',
+                               'perl-Net-SMTP-SSL-1.02-1-src',
+                               'perl-Net-SMTP-SSL-1.03-1-src'],
+ 'x86/rpm-doc': ['.htaccess', 'rpm-doc-4.1-2', 'rpm-doc-999-1'],
+ 'x86/rpm-doc-src': ['.htaccess', 'rpm-doc-4.1-2-src'],
+ 'x86/staleversion': ['.htaccess',
+                      'staleversion-240-1',
+                      'staleversion-242-0',
+                      'staleversion-243-0',
+                      'staleversion-250-0',
+                      'staleversion-251-0',
+                      'staleversion-260-0'],
+ 'x86/staleversion-src': ['.htaccess',
+                          'staleversion-240-1-src',
+                          'staleversion-242-0-src',
+                          'staleversion-243-0-src',
+                          'staleversion-250-0-src',
+                          'staleversion-251-0-src',
+                          'staleversion-260-0-src'],
+ 'x86/test-c': ['.htaccess', 'test-c-1.0-1'],
+ 'x86/test-c-src': ['.htaccess', 'test-c-1.0-1-src'],
+ 'x86/test-d': ['.htaccess', 'test-d-1.0-1'],
+ 'x86/test-d-src': ['.htaccess', 'test-d-1.0-1-src'],
+ 'x86/test-e': ['.htaccess', 'test-e-1.0-1'],
+ 'x86/test-e-src': ['.htaccess', 'test-e-1.0-1-src'],
+ 'x86/testpackage': ['.htaccess', 'testpackage-0.1-1'],
+ 'x86/testpackage-src': ['.htaccess', 'testpackage-0.1-1-src'],
+ 'x86_64': ['.htaccess']}
diff --git a/test/testdata/htdocs.expected/packages.inc b/test/testdata/htdocs.expected/packages.inc
old mode 100755
new mode 100644
index 25a317f..e9eaf6e
--- a/test/testdata/htdocs.expected/packages.inc
+++ b/test/testdata/htdocs.expected/packages.inc
@@ -1,4 +1,5 @@
 <p class="center">
+21 packages : 
 <a href="#a">a</a> - 
 <a href="#b">b</a> - 
 <a href="#c">c</a> - 
diff --git a/test/testdata/htdocs.expected/src_packages.inc b/test/testdata/htdocs.expected/src_packages.inc
new file mode 100644
index 0000000..7dd26bc
--- /dev/null
+++ b/test/testdata/htdocs.expected/src_packages.inc
@@ -0,0 +1,31 @@
+<p class="center">
+17 packages : 
+<a href="#a">a</a> - 
+<a href="#c">c</a> - 
+<a href="#k">k</a> - 
+<a href="#m">m</a> - 
+<a href="#o">o</a> - 
+<a href="#p">p</a> - 
+<a href="#r">r</a> - 
+<a href="#s">s</a> - 
+<a href="#t">t</a>
+</p>
+<table class="pkglist">
+<tr id="a"><td class="pkgname"><a href="summary/arc-src.html">arc</a></td><td>The ARC archive utility (source)</td></tr>
+<tr id="c"><td><a href="summary/corrupt-src.html">corrupt</a></td><td>A corrupt package (source)</td></tr>
+<tr><td><a href="summary/cygwin-src.html">cygwin</a></td><td>The UNIX emulation engine (source)</td></tr>
+<tr id="k"><td><a href="summary/keychain-src.html">keychain</a></td><td>Key manager for OpenSSH (source)</td></tr>
+<tr id="m"><td><a href="summary/mDNSResponder-src.html">mDNSResponder</a></td><td>Bonjour Zeroconf implementation (source)</td></tr>
+<tr id="o"><td><a href="summary/obs-a-src.html">obs-a</a></td><td>obsolete package A (source)</td></tr>
+<tr><td><a href="summary/obs-b-src.html">obs-b</a></td><td>obsolete package B (source)</td></tr>
+<tr><td><a href="summary/openssh-src.html">openssh</a></td><td>The OpenSSH server and client programs (source)</td></tr>
+<tr id="p"><td><a href="summary/per-version-src.html">per-version</a></td><td>Per-version hint test package (source)</td></tr>
+<tr><td><a href="summary/per-version-replacement-hint-only-src.html">per-version-replacement-hint-only</a></td><td>Per-version hint test package (source)</td></tr>
+<tr><td><a href="summary/perl-Net-SMTP-SSL-src.html">perl-Net-SMTP-SSL</a></td><td>Perl distribution Net-SMTP-SSL (source)</td></tr>
+<tr id="r"><td><a href="summary/rpm-doc-src.html">rpm-doc</a></td><td>Obsolete package for RPM package management system manual pages (extra text to so repr is not one line) (source)</td></tr>
+<tr id="s"><td><a href="summary/staleversion-src.html">staleversion</a></td><td>Test package for stale version removal (source)</td></tr>
+<tr id="t"><td><a href="summary/test-c-src.html">test-c</a></td><td>test package C (source)</td></tr>
+<tr><td><a href="summary/test-d-src.html">test-d</a></td><td>test package D (source)</td></tr>
+<tr><td><a href="summary/test-e-src.html">test-e</a></td><td>test package E (source)</td></tr>
+<tr><td><a href="summary/testpackage-src.html">testpackage</a></td><td>A test package (stuff &amp; other stuff) (source)</td></tr>
+</table>
diff --git a/test/testdata/htdocs.expected/summary/arc-src.html b/test/testdata/htdocs.expected/summary/arc-src.html
new file mode 100755
index 0000000..3623e01
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/arc-src.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for arc (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: arc</h1>
+<span class="detail">summary</span>: The ARC archive utility<br><br>
+<span class="detail">description</span>: This program is based on the MSDOS ARC program, version 5.21, plus a
+few enhancements. ARC performs Huffman Squeezing on data. The Huffman
+Squeeze algorithm was removed from MSDOS ARC after version 5.12. It
+turns out to be more efficient than Lempel-Ziv style compression when
+compressing graphic images. Squeeze analysis is always done now, and
+the best of packing, squeezing, or crunching is used.<br><br>
+<span class="detail">categories</span>: Archive<br><br>
+<span class="detail">install package(s)</span>: <a href="arc.html">arc</a><br><br>
+<span class="detail">maintainer(s)</span>: Jari Aalto 
+<span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
+<a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
+<br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>4.32.7-10 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/arc-src/arc-4.32.7-10-src">list of files</a>]</td><td>stable</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/arc.html b/test/testdata/htdocs.expected/summary/arc.html
index 009b8f5..0cfcb9e 100644
--- a/test/testdata/htdocs.expected/summary/arc.html
+++ b/test/testdata/htdocs.expected/summary/arc.html
@@ -18,7 +18,7 @@ turns out to be more efficient than Lempel-Ziv style compression when
 compressing graphic images. Squeeze analysis is always done now, and
 the best of packing, squeezing, or crunching is used.<br><br>
 <span class="detail">categories</span>: Archive<br><br>
-<span class="detail">binaries</span>: <a href="arc.html">arc</a><br><br>
+<span class="detail">source package</span>: <a href="arc-src.html">arc</a><br><br>
 <span class="detail">maintainer(s)</span>: Jari Aalto 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
@@ -28,7 +28,6 @@ the best of packing, squeezing, or crunching is used.<br><br>
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>4.32.7-10</td><td class="right">1 kB</td><td>[<a href="../x86/arc/arc-4.32.7-10">list of files</a>]</td><td>stable</td></tr>
-<tr><td>4.32.7-10 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/arc/arc-4.32.7-10-src">list of files</a>]</td><td>stable</td></tr>
 </table><br>
 </ul>
 </div>
diff --git a/test/testdata/htdocs.expected/summary/base-cygwin.html b/test/testdata/htdocs.expected/summary/base-cygwin.html
index 0ce7e9e..9dbd8fa 100644
--- a/test/testdata/htdocs.expected/summary/base-cygwin.html
+++ b/test/testdata/htdocs.expected/summary/base-cygwin.html
@@ -13,7 +13,7 @@
 <span class="detail">summary</span>: Initial base installation helper script<br><br>
 <span class="detail">description</span>: Initial base installation helper script.<br><br>
 <span class="detail">categories</span>: Base<br><br>
-<span class="detail">binaries</span>: <br><br>
+<span class="detail">source package</span>: base-cygwin-src<br><br>
 <span class="detail">maintainer(s)</span>: Corinna Vinschen 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
diff --git a/test/testdata/htdocs.expected/summary/corrupt-src.html b/test/testdata/htdocs.expected/summary/corrupt-src.html
new file mode 100755
index 0000000..167c266
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/corrupt-src.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for corrupt (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: corrupt</h1>
+<span class="detail">summary</span>: A corrupt package<br><br>
+<span class="detail">description</span>: A package containing corrupt archives<br><br>
+<span class="detail">categories</span>: Devel<br><br>
+<span class="detail">install package(s)</span>: <a href="corrupt.html">corrupt</a><br><br>
+<span class="detail">maintainer(s)</span>: Blooey McFooey 
+<span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
+<a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
+<br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>2.0.0-1 (source)</td><td class="right">354 kB</td><td>[<a href="../x86/corrupt-src/corrupt-2.0.0-1-src">list of files</a>]</td><td>stable</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/corrupt.html b/test/testdata/htdocs.expected/summary/corrupt.html
index d7016dc..f12513c 100644
--- a/test/testdata/htdocs.expected/summary/corrupt.html
+++ b/test/testdata/htdocs.expected/summary/corrupt.html
@@ -13,7 +13,7 @@
 <span class="detail">summary</span>: A corrupt package<br><br>
 <span class="detail">description</span>: A package containing corrupt archives<br><br>
 <span class="detail">categories</span>: Devel<br><br>
-<span class="detail">binaries</span>: <a href="corrupt.html">corrupt</a><br><br>
+<span class="detail">source package</span>: <a href="corrupt-src.html">corrupt</a><br><br>
 <span class="detail">maintainer(s)</span>: Blooey McFooey 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
@@ -23,7 +23,6 @@
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>2.0.0-1</td><td class="right">1 kB</td><td>[<a href="../x86/corrupt/corrupt-2.0.0-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>2.0.0-1 (source)</td><td class="right">354 kB</td><td>[<a href="../x86/corrupt/corrupt-2.0.0-1-src">list of files</a>]</td><td>stable</td></tr>
 </table><br>
 </ul>
 </div>
diff --git a/test/testdata/htdocs.expected/summary/cygwin-debuginfo.html b/test/testdata/htdocs.expected/summary/cygwin-debuginfo.html
index 0d613d6..2cab7d3 100644
--- a/test/testdata/htdocs.expected/summary/cygwin-debuginfo.html
+++ b/test/testdata/htdocs.expected/summary/cygwin-debuginfo.html
@@ -15,7 +15,7 @@
 cygwin package with gdb.<br><br>
 <span class="detail">categories</span>: Debug<br><br>
 <span class="detail">depends</span>: <a href="cygwin-debuginfo.html">cygwin-debuginfo</a><br><br>
-<span class="detail">source</span>: <a href="cygwin.html">cygwin</a><br><br>
+<span class="detail">source package</span>: <a href="cygwin-src.html">cygwin</a><br><br>
 <span class="detail">maintainer(s)</span>: Corinna Vinschen, Yaakov Selkowitz 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
diff --git a/test/testdata/htdocs.expected/summary/cygwin-devel.html b/test/testdata/htdocs.expected/summary/cygwin-devel.html
index 2435a15..f9f3c8a 100644
--- a/test/testdata/htdocs.expected/summary/cygwin-devel.html
+++ b/test/testdata/htdocs.expected/summary/cygwin-devel.html
@@ -13,7 +13,7 @@
 <span class="detail">summary</span>: Core development files<br><br>
 <span class="detail">description</span>: Core development files required to build Cygwin packages<br><br>
 <span class="detail">categories</span>: Devel<br><br>
-<span class="detail">source</span>: <a href="cygwin.html">cygwin</a><br><br>
+<span class="detail">source package</span>: <a href="cygwin-src.html">cygwin</a><br><br>
 <span class="detail">maintainer(s)</span>: Corinna Vinschen, Yaakov Selkowitz 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
diff --git a/test/testdata/htdocs.expected/summary/cygwin-src.html b/test/testdata/htdocs.expected/summary/cygwin-src.html
new file mode 100755
index 0000000..23bf0be
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/cygwin-src.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for cygwin (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: cygwin</h1>
+<span class="detail">summary</span>: The UNIX emulation engine<br><br>
+<span class="detail">description</span>: The UNIX emulation engine<br><br>
+<span class="detail">categories</span>: Base<br><br>
+<span class="detail">install package(s)</span>: <a href="cygwin.html">cygwin</a>, <a href="cygwin-debuginfo.html">cygwin-debuginfo</a>, <a href="cygwin-devel.html">cygwin-devel</a><br><br>
+<span class="detail">maintainer(s)</span>: Corinna Vinschen, Yaakov Selkowitz 
+<span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
+<a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
+<br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>2.2.0-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/cygwin-src/cygwin-2.2.0-1-src">list of files</a>]</td><td>stable</td></tr>
+<tr><td>2.2.1-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/cygwin-src/cygwin-2.2.1-1-src">list of files</a>]</td><td>stable</td></tr>
+<tr><td>2.3.0-0.3 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/cygwin-src/cygwin-2.3.0-0.3-src">list of files</a>]</td><td>test</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/cygwin.html b/test/testdata/htdocs.expected/summary/cygwin.html
index c7c16de..14bd73a 100644
--- a/test/testdata/htdocs.expected/summary/cygwin.html
+++ b/test/testdata/htdocs.expected/summary/cygwin.html
@@ -14,7 +14,7 @@
 <span class="detail">description</span>: The UNIX emulation engine<br><br>
 <span class="detail">categories</span>: Base<br><br>
 <span class="detail">depends</span>: <a href="base-cygwin.html">base-cygwin</a><br><br>
-<span class="detail">binaries</span>: <a href="cygwin.html">cygwin</a>, <a href="cygwin-debuginfo.html">cygwin-debuginfo</a>, <a href="cygwin-devel.html">cygwin-devel</a><br><br>
+<span class="detail">source package</span>: <a href="cygwin-src.html">cygwin</a><br><br>
 <span class="detail">maintainer(s)</span>: Corinna Vinschen, Yaakov Selkowitz 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
@@ -24,11 +24,8 @@
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>2.2.0-1</td><td class="right">1 kB</td><td>[<a href="../x86/cygwin/cygwin-2.2.0-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>2.2.0-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/cygwin/cygwin-2.2.0-1-src">list of files</a>]</td><td>stable</td></tr>
 <tr><td>2.2.1-1</td><td class="right">1 kB</td><td>[<a href="../x86/cygwin/cygwin-2.2.1-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>2.2.1-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/cygwin/cygwin-2.2.1-1-src">list of files</a>]</td><td>stable</td></tr>
 <tr><td>2.3.0-0.3</td><td class="right">1 kB</td><td>[<a href="../x86/cygwin/cygwin-2.3.0-0.3">list of files</a>]</td><td>test</td></tr>
-<tr><td>2.3.0-0.3 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/cygwin/cygwin-2.3.0-0.3-src">list of files</a>]</td><td>test</td></tr>
 </table><br>
 </ul>
 </div>
diff --git a/test/testdata/htdocs.expected/summary/keychain-src.html b/test/testdata/htdocs.expected/summary/keychain-src.html
new file mode 100755
index 0000000..52e97d4
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/keychain-src.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for keychain (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: keychain</h1>
+<span class="detail">summary</span>: Key manager for OpenSSH<br><br>
+<span class="detail">description</span>: Keychain is an OpenSSH key manager, typically run from
+~/.bash_profile. When keychain is run, it checks for a running
+ssh-agent, otherwise it starts one. It saves the ssh-agent environment
+variables to ~/.keychain/$HOSTNAME-sh, so that subsequent logins
+and non-interactive shells such as cron jobs can source the file and
+make passwordless ssh connections. In addition, when keychain runs, it
+verifies that the key files specified on the command-line are known to
+ssh-agent, otherwise it loads them, prompting you for a password if
+necessary<br><br>
+<span class="detail">categories</span>: Utils<br><br>
+<span class="detail">install package(s)</span>: <a href="keychain.html">keychain</a><br><br>
+<span class="detail">maintainer(s)</span>: Jari Aalto 
+<span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
+<a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
+<br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>2.6.8-1 (source)</td><td class="right">36 kB</td><td>[<a href="../x86/keychain-src/keychain-2.6.8-1-src">list of files</a>]</td><td>stable</td></tr>
+<tr><td>2.7.1-1 (source)</td><td class="right">132 kB</td><td>[<a href="../x86/keychain-src/keychain-2.7.1-1-src">list of files</a>]</td><td>stable</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/keychain.html b/test/testdata/htdocs.expected/summary/keychain.html
index 96f30f7..8daabe0 100644
--- a/test/testdata/htdocs.expected/summary/keychain.html
+++ b/test/testdata/htdocs.expected/summary/keychain.html
@@ -22,7 +22,7 @@ ssh-agent, otherwise it loads them, prompting you for a password if
 necessary<br><br>
 <span class="detail">categories</span>: Utils<br><br>
 <span class="detail">depends</span>: <a href="openssh.html">openssh</a><br><br>
-<span class="detail">binaries</span>: <a href="keychain.html">keychain</a><br><br>
+<span class="detail">source package</span>: <a href="keychain-src.html">keychain</a><br><br>
 <span class="detail">maintainer(s)</span>: Jari Aalto 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
@@ -32,9 +32,7 @@ necessary<br><br>
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>2.6.8-1</td><td class="right">30 kB</td><td>[<a href="../x86/keychain/keychain-2.6.8-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>2.6.8-1 (source)</td><td class="right">36 kB</td><td>[<a href="../x86/keychain/keychain-2.6.8-1-src">list of files</a>]</td><td>stable</td></tr>
 <tr><td>2.7.1-1</td><td class="right">32 kB</td><td>[<a href="../x86/keychain/keychain-2.7.1-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>2.7.1-1 (source)</td><td class="right">132 kB</td><td>[<a href="../x86/keychain/keychain-2.7.1-1-src">list of files</a>]</td><td>stable</td></tr>
 </table><br>
 </ul>
 </div>
diff --git a/test/testdata/htdocs.expected/summary/libdns_sd-devel.html b/test/testdata/htdocs.expected/summary/libdns_sd-devel.html
index e28d111..58b060e 100644
--- a/test/testdata/htdocs.expected/summary/libdns_sd-devel.html
+++ b/test/testdata/htdocs.expected/summary/libdns_sd-devel.html
@@ -16,7 +16,7 @@ automatic discovery of computers, devices, and services on IP networks using
 industry standard IP protocols.<br><br>
 <span class="detail">categories</span>: Net<br><br>
 <span class="detail">depends</span>: <a href="libdns_sd1.html">libdns_sd1</a><br><br>
-<span class="detail">source</span>: <a href="mDNSResponder.html">mDNSResponder</a><br><br>
+<span class="detail">source package</span>: <a href="mDNSResponder-src.html">mDNSResponder</a><br><br>
 <span class="detail">maintainer(s)</span>: Yaakov Selkowitz 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
diff --git a/test/testdata/htdocs.expected/summary/libdns_sd1.html b/test/testdata/htdocs.expected/summary/libdns_sd1.html
index 8fe51cd..10de4bf 100644
--- a/test/testdata/htdocs.expected/summary/libdns_sd1.html
+++ b/test/testdata/htdocs.expected/summary/libdns_sd1.html
@@ -15,7 +15,7 @@
 automatic discovery of computers, devices, and services on IP networks using
 industry standard IP protocols.<br><br>
 <span class="detail">categories</span>: Net<br><br>
-<span class="detail">source</span>: <a href="mDNSResponder.html">mDNSResponder</a><br><br>
+<span class="detail">source package</span>: <a href="mDNSResponder-src.html">mDNSResponder</a><br><br>
 <span class="detail">maintainer(s)</span>: Yaakov Selkowitz 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
diff --git a/test/testdata/htdocs.expected/summary/mDNSResponder-src.html b/test/testdata/htdocs.expected/summary/mDNSResponder-src.html
new file mode 100755
index 0000000..744f519
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/mDNSResponder-src.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for mDNSResponder (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: mDNSResponder</h1>
+<span class="detail">summary</span>: Bonjour Zeroconf implementation<br><br>
+<span class="detail">description</span>: Bonjour, also known as zero-configuration networking, enables
+automatic discovery of computers, devices, and services on IP networks using
+industry standard IP protocols.<br><br>
+<span class="detail">categories</span>: Net<br><br>
+<span class="detail">install package(s)</span>: <a href="libdns_sd-devel.html">libdns_sd-devel</a>, <a href="libdns_sd1.html">libdns_sd1</a>, <a href="mDNSResponder.html">mDNSResponder</a><br><br>
+<span class="detail">maintainer(s)</span>: Yaakov Selkowitz 
+<span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
+<a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
+<br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>379.32.1-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/mDNSResponder-src/mDNSResponder-379.32.1-1-src">list of files</a>]</td><td>stable</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/mDNSResponder.html b/test/testdata/htdocs.expected/summary/mDNSResponder.html
index eac93f9..fee2fd7 100644
--- a/test/testdata/htdocs.expected/summary/mDNSResponder.html
+++ b/test/testdata/htdocs.expected/summary/mDNSResponder.html
@@ -16,7 +16,7 @@ automatic discovery of computers, devices, and services on IP networks using
 industry standard IP protocols.<br><br>
 <span class="detail">categories</span>: Net<br><br>
 <span class="detail">depends</span>: <a href="libdns_sd1.html">libdns_sd1</a><br><br>
-<span class="detail">binaries</span>: <a href="libdns_sd-devel.html">libdns_sd-devel</a>, <a href="libdns_sd1.html">libdns_sd1</a>, <a href="mDNSResponder.html">mDNSResponder</a><br><br>
+<span class="detail">source package</span>: <a href="mDNSResponder-src.html">mDNSResponder</a><br><br>
 <span class="detail">maintainer(s)</span>: Yaakov Selkowitz 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
@@ -26,7 +26,6 @@ industry standard IP protocols.<br><br>
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>379.32.1-1</td><td class="right">1 kB</td><td>[<a href="../x86/mDNSResponder/mDNSResponder-379.32.1-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>379.32.1-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/mDNSResponder/mDNSResponder-379.32.1-1-src">list of files</a>]</td><td>stable</td></tr>
 </table><br>
 </ul>
 </div>
diff --git a/test/testdata/htdocs.expected/summary/obs-a-src.html b/test/testdata/htdocs.expected/summary/obs-a-src.html
new file mode 100755
index 0000000..03847a9
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/obs-a-src.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for obs-a (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: obs-a</h1>
+<span class="detail">summary</span>: obsolete package A<br><br>
+<span class="detail">description</span>: obsolete package A<br><br>
+<span class="detail">categories</span>: Devel<br><br>
+<span class="detail">install package(s)</span>: <a href="obs-a.html">obs-a</a><br><br>
+<span class="detail">maintainer(s)</span>: ORPHANED 
+<span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
+<a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
+<br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>1.0-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/obs-a-src/obs-a-1.0-1-src">list of files</a>]</td><td>stable</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/obs-a.html b/test/testdata/htdocs.expected/summary/obs-a.html
index b3f5ac7..e769792 100644
--- a/test/testdata/htdocs.expected/summary/obs-a.html
+++ b/test/testdata/htdocs.expected/summary/obs-a.html
@@ -13,7 +13,7 @@
 <span class="detail">summary</span>: obsolete package A<br><br>
 <span class="detail">description</span>: obsolete package A<br><br>
 <span class="detail">categories</span>: Devel<br><br>
-<span class="detail">binaries</span>: <a href="obs-a.html">obs-a</a><br><br>
+<span class="detail">source package</span>: <a href="obs-a-src.html">obs-a</a><br><br>
 <span class="detail">maintainer(s)</span>: ORPHANED 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
@@ -23,7 +23,6 @@
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>1.0-1</td><td class="right">1 kB</td><td>[<a href="../x86/obs-a/obs-a-1.0-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>1.0-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/obs-a/obs-a-1.0-1-src">list of files</a>]</td><td>stable</td></tr>
 </table><br>
 </ul>
 </div>
diff --git a/test/testdata/htdocs.expected/summary/obs-b-src.html b/test/testdata/htdocs.expected/summary/obs-b-src.html
new file mode 100755
index 0000000..c55f882
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/obs-b-src.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for obs-b (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: obs-b</h1>
+<span class="detail">summary</span>: obsolete package B<br><br>
+<span class="detail">description</span>: obsolete package B<br><br>
+<span class="detail">categories</span>: Devel<br><br>
+<span class="detail">install package(s)</span>: <a href="obs-b.html">obs-b</a><br><br>
+<span class="detail">maintainer(s)</span>: ORPHANED 
+<span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
+<a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
+<br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>1.0-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/obs-b-src/obs-b-1.0-1-src">list of files</a>]</td><td>stable</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/obs-b.html b/test/testdata/htdocs.expected/summary/obs-b.html
index 436fa94..3eea36d 100644
--- a/test/testdata/htdocs.expected/summary/obs-b.html
+++ b/test/testdata/htdocs.expected/summary/obs-b.html
@@ -13,7 +13,7 @@
 <span class="detail">summary</span>: obsolete package B<br><br>
 <span class="detail">description</span>: obsolete package B<br><br>
 <span class="detail">categories</span>: Devel<br><br>
-<span class="detail">binaries</span>: <a href="obs-b.html">obs-b</a><br><br>
+<span class="detail">source package</span>: <a href="obs-b-src.html">obs-b</a><br><br>
 <span class="detail">maintainer(s)</span>: ORPHANED 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
@@ -23,7 +23,6 @@
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>1.0-1</td><td class="right">1 kB</td><td>[<a href="../x86/obs-b/obs-b-1.0-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>1.0-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/obs-b/obs-b-1.0-1-src">list of files</a>]</td><td>stable</td></tr>
 </table><br>
 </ul>
 </div>
diff --git a/test/testdata/htdocs.expected/summary/openssh-src.html b/test/testdata/htdocs.expected/summary/openssh-src.html
new file mode 100755
index 0000000..ddac058
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/openssh-src.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for openssh (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: openssh</h1>
+<span class="detail">summary</span>: The OpenSSH server and client programs<br><br>
+<span class="detail">description</span>: OpenSSH is a program for logging into a remote machine and for
+	executing commands on a remote machine.  It can replace rlogin and rsh,
+	providing encrypted communication between two machines.<br><br>
+<span class="detail">categories</span>: Net<br><br>
+<span class="detail">install package(s)</span>: <a href="openssh.html">openssh</a><br><br>
+<span class="detail">maintainer(s)</span>: Corinna Vinschen 
+<span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
+<a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
+<br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>7.2p2-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/openssh-src/openssh-7.2p2-1-src">list of files</a>]</td><td>stable</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/openssh.html b/test/testdata/htdocs.expected/summary/openssh.html
index a2f3b78..de87c5a 100644
--- a/test/testdata/htdocs.expected/summary/openssh.html
+++ b/test/testdata/htdocs.expected/summary/openssh.html
@@ -15,7 +15,7 @@
 	executing commands on a remote machine.  It can replace rlogin and rsh,
 	providing encrypted communication between two machines.<br><br>
 <span class="detail">categories</span>: Net<br><br>
-<span class="detail">binaries</span>: <a href="openssh.html">openssh</a><br><br>
+<span class="detail">source package</span>: <a href="openssh-src.html">openssh</a><br><br>
 <span class="detail">maintainer(s)</span>: Corinna Vinschen 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
@@ -25,7 +25,6 @@
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>7.2p2-1</td><td class="right">1 kB</td><td>[<a href="../x86/openssh/openssh-7.2p2-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>7.2p2-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/openssh/openssh-7.2p2-1-src">list of files</a>]</td><td>stable</td></tr>
 </table><br>
 </ul>
 </div>
diff --git a/test/testdata/htdocs.expected/summary/per-version-replacement-hint-only-src.html b/test/testdata/htdocs.expected/summary/per-version-replacement-hint-only-src.html
new file mode 100755
index 0000000..d4f7ab6
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/per-version-replacement-hint-only-src.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for per-version-replacement-hint-only (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: per-version-replacement-hint-only</h1>
+<span class="detail">summary</span>: Per-version hint test package<br><br>
+<span class="detail">description</span>: Per-version hint test package<br><br>
+<span class="detail">categories</span>: Base<br><br>
+<span class="detail">install package(s)</span>: <a href="per-version-replacement-hint-only.html">per-version-replacement-hint-only</a><br><br>
+<span class="detail">maintainer(s)</span>: Blooey McFooey 
+<span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
+<a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
+<br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>1.0-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/per-version-replacement-hint-only-src/per-version-replacement-hint-only-1.0-1-src">list of files</a>]</td><td>stable</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/per-version-replacement-hint-only.html b/test/testdata/htdocs.expected/summary/per-version-replacement-hint-only.html
index 8ab2a7f..ed019b7 100644
--- a/test/testdata/htdocs.expected/summary/per-version-replacement-hint-only.html
+++ b/test/testdata/htdocs.expected/summary/per-version-replacement-hint-only.html
@@ -14,7 +14,7 @@
 <span class="detail">description</span>: Per-version hint test package<br><br>
 <span class="detail">categories</span>: Base<br><br>
 <span class="detail">depends</span>: <a href="cygwin.html">cygwin</a><br><br>
-<span class="detail">binaries</span>: <a href="per-version-replacement-hint-only.html">per-version-replacement-hint-only</a><br><br>
+<span class="detail">source package</span>: <a href="per-version-replacement-hint-only-src.html">per-version-replacement-hint-only</a><br><br>
 <span class="detail">maintainer(s)</span>: Blooey McFooey 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
@@ -24,7 +24,6 @@
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>1.0-1</td><td class="right">1 kB</td><td>[<a href="../x86/per-version-replacement-hint-only/per-version-replacement-hint-only-1.0-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>1.0-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/per-version-replacement-hint-only/per-version-replacement-hint-only-1.0-1-src">list of files</a>]</td><td>stable</td></tr>
 </table><br>
 </ul>
 </div>
diff --git a/test/testdata/htdocs.expected/summary/per-version-src.html b/test/testdata/htdocs.expected/summary/per-version-src.html
new file mode 100755
index 0000000..5e5d61e
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/per-version-src.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for per-version (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: per-version</h1>
+<span class="detail">summary</span>: Per-version hint test package<br><br>
+<span class="detail">description</span>: Per-version hint test package<br><br>
+<span class="detail">categories</span>: Base<br><br>
+<span class="detail">install package(s)</span>: <a href="per-version.html">per-version</a><br><br>
+<span class="detail">maintainer(s)</span>: Blooey McFooey 
+<span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
+<a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
+<br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>4.0-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/per-version-src/per-version-4.0-1-src">list of files</a>]</td><td>stable</td></tr>
+<tr><td>4.8-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/per-version-src/per-version-4.8-1-src">list of files</a>]</td><td>stable</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/per-version.html b/test/testdata/htdocs.expected/summary/per-version.html
index 3fef454..cb8d1b4 100644
--- a/test/testdata/htdocs.expected/summary/per-version.html
+++ b/test/testdata/htdocs.expected/summary/per-version.html
@@ -14,7 +14,7 @@
 <span class="detail">description</span>: Per-version hint test package<br><br>
 <span class="detail">categories</span>: Base<br><br>
 <span class="detail">depends</span>: <a href="base-cygwin.html">base-cygwin</a><br><br>
-<span class="detail">binaries</span>: <a href="per-version.html">per-version</a><br><br>
+<span class="detail">source package</span>: <a href="per-version-src.html">per-version</a><br><br>
 <span class="detail">maintainer(s)</span>: Blooey McFooey 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
@@ -24,9 +24,7 @@
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>4.0-1</td><td class="right">1 kB</td><td>[<a href="../x86/per-version/per-version-4.0-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>4.0-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/per-version/per-version-4.0-1-src">list of files</a>]</td><td>stable</td></tr>
 <tr><td>4.8-1</td><td class="right">1 kB</td><td>[<a href="../x86/per-version/per-version-4.8-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>4.8-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/per-version/per-version-4.8-1-src">list of files</a>]</td><td>stable</td></tr>
 </table><br>
 </ul>
 </div>
diff --git a/test/testdata/htdocs.expected/summary/perl-Net-SMTP-SSL-src.html b/test/testdata/htdocs.expected/summary/perl-Net-SMTP-SSL-src.html
new file mode 100755
index 0000000..512831d
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/perl-Net-SMTP-SSL-src.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for perl-Net-SMTP-SSL (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: perl-Net-SMTP-SSL</h1>
+<span class="detail">summary</span>: Perl distribution Net-SMTP-SSL<br><br>
+<span class="detail">description</span>: Implements the same API as Net::SMTP, but uses IO::Socket::SSL for
+its network operations in order to support encrypted connections.<br><br>
+<span class="detail">categories</span>: Perl<br><br>
+<span class="detail">install package(s)</span>: <a href="perl-Net-SMTP-SSL.html">perl-Net-SMTP-SSL</a><br><br>
+<span class="detail">maintainer(s)</span>: Blooey McFooey, Yaakov Selkowitz 
+<span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
+<a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
+<br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>1.01-1 (source)</td><td class="right">3 kB</td><td>[<a href="../x86/perl-Net-SMTP-SSL-src/perl-Net-SMTP-SSL-1.01-1-src">list of files</a>]</td><td>stable</td></tr>
+<tr><td>1.02-1 (source)</td><td class="right">3 kB</td><td>[<a href="../x86/perl-Net-SMTP-SSL-src/perl-Net-SMTP-SSL-1.02-1-src">list of files</a>]</td><td>stable</td></tr>
+<tr><td>1.03-1 (source)</td><td class="right">3 kB</td><td>[<a href="../x86/perl-Net-SMTP-SSL-src/perl-Net-SMTP-SSL-1.03-1-src">list of files</a>]</td><td>stable</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/perl-Net-SMTP-SSL.html b/test/testdata/htdocs.expected/summary/perl-Net-SMTP-SSL.html
index 68f5b81..83f27cb 100644
--- a/test/testdata/htdocs.expected/summary/perl-Net-SMTP-SSL.html
+++ b/test/testdata/htdocs.expected/summary/perl-Net-SMTP-SSL.html
@@ -14,7 +14,7 @@
 <span class="detail">description</span>: Implements the same API as Net::SMTP, but uses IO::Socket::SSL for
 its network operations in order to support encrypted connections.<br><br>
 <span class="detail">categories</span>: Perl<br><br>
-<span class="detail">binaries</span>: <a href="perl-Net-SMTP-SSL.html">perl-Net-SMTP-SSL</a><br><br>
+<span class="detail">source package</span>: <a href="perl-Net-SMTP-SSL-src.html">perl-Net-SMTP-SSL</a><br><br>
 <span class="detail">maintainer(s)</span>: Blooey McFooey, Yaakov Selkowitz 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
@@ -24,11 +24,8 @@ its network operations in order to support encrypted connections.<br><br>
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>1.01-1</td><td class="right">4 kB</td><td>[<a href="../x86/perl-Net-SMTP-SSL/perl-Net-SMTP-SSL-1.01-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>1.01-1 (source)</td><td class="right">3 kB</td><td>[<a href="../x86/perl-Net-SMTP-SSL/perl-Net-SMTP-SSL-1.01-1-src">list of files</a>]</td><td>stable</td></tr>
 <tr><td>1.02-1</td><td class="right">4 kB</td><td>[<a href="../x86/perl-Net-SMTP-SSL/perl-Net-SMTP-SSL-1.02-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>1.02-1 (source)</td><td class="right">3 kB</td><td>[<a href="../x86/perl-Net-SMTP-SSL/perl-Net-SMTP-SSL-1.02-1-src">list of files</a>]</td><td>stable</td></tr>
 <tr><td>1.03-1</td><td class="right">4 kB</td><td>[<a href="../x86/perl-Net-SMTP-SSL/perl-Net-SMTP-SSL-1.03-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>1.03-1 (source)</td><td class="right">3 kB</td><td>[<a href="../x86/perl-Net-SMTP-SSL/perl-Net-SMTP-SSL-1.03-1-src">list of files</a>]</td><td>stable</td></tr>
 </table><br>
 </ul>
 </div>
diff --git a/test/testdata/htdocs.expected/summary/rpm-doc-src.html b/test/testdata/htdocs.expected/summary/rpm-doc-src.html
new file mode 100755
index 0000000..733c6e8
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/rpm-doc-src.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for rpm-doc (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: rpm-doc</h1>
+<span class="detail">summary</span>: Obsolete package for RPM package management system manual pages (extra text to so repr is not one line)<br><br>
+<span class="detail">description</span>: Obsolete package for RPM package management system manual pages (extra text to so repr is not one line)<br><br>
+<span class="detail">categories</span>: _obsolete<br><br>
+<span class="detail">install package(s)</span>: <a href="rpm-doc.html">rpm-doc</a><br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>4.1-2 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/rpm-doc-src/rpm-doc-4.1-2-src">list of files</a>]</td><td>stable</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/rpm-doc.html b/test/testdata/htdocs.expected/summary/rpm-doc.html
index f56be56..0da1339 100644
--- a/test/testdata/htdocs.expected/summary/rpm-doc.html
+++ b/test/testdata/htdocs.expected/summary/rpm-doc.html
@@ -13,13 +13,12 @@
 <span class="detail">summary</span>: Obsolete package for RPM package management system manual pages (extra text to so repr is not one line)<br><br>
 <span class="detail">description</span>: Obsolete package for RPM package management system manual pages (extra text to so repr is not one line)<br><br>
 <span class="detail">categories</span>: _obsolete<br><br>
-<span class="detail">binaries</span>: <a href="rpm-doc.html">rpm-doc</a><br><br>
+<span class="detail">source package</span>: <a href="rpm-doc-src.html">rpm-doc</a><br><br>
 <ul>
 <li><span class="detail">x86</span></li>
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>4.1-2</td><td class="right">50 kB</td><td>[<a href="../x86/rpm-doc/rpm-doc-4.1-2">list of files</a>]</td><td>stable</td></tr>
-<tr><td>4.1-2 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/rpm-doc/rpm-doc-4.1-2-src">list of files</a>]</td><td>stable</td></tr>
 <tr><td>999-1</td><td class="right">1 kB</td><td>[<a href="../x86/rpm-doc/rpm-doc-999-1">list of files</a>]</td><td>stable</td></tr>
 </table><br>
 </ul>
diff --git a/test/testdata/htdocs.expected/summary/staleversion-src.html b/test/testdata/htdocs.expected/summary/staleversion-src.html
new file mode 100755
index 0000000..9f1006d
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/staleversion-src.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for staleversion (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: staleversion</h1>
+<span class="detail">summary</span>: Test package for stale version removal<br><br>
+<span class="detail">description</span>: Test package for stale version removal<br><br>
+<span class="detail">categories</span>: Shells Base<br><br>
+<span class="detail">install package(s)</span>: <a href="staleversion.html">staleversion</a><br><br>
+<span class="detail">maintainer(s)</span>: Blooey McFooey 
+<span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
+<a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
+<br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>240-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion-src/staleversion-240-1-src">list of files</a>]</td><td>stable</td></tr>
+<tr><td>242-0 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion-src/staleversion-242-0-src">list of files</a>]</td><td>stable</td></tr>
+<tr><td>243-0 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion-src/staleversion-243-0-src">list of files</a>]</td><td>stable</td></tr>
+<tr><td>250-0 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion-src/staleversion-250-0-src">list of files</a>]</td><td>stable</td></tr>
+<tr><td>251-0 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion-src/staleversion-251-0-src">list of files</a>]</td><td>stable</td></tr>
+<tr><td>260-0 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion-src/staleversion-260-0-src">list of files</a>]</td><td>test</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/staleversion.html b/test/testdata/htdocs.expected/summary/staleversion.html
index 0f59ea2..e75eb27 100644
--- a/test/testdata/htdocs.expected/summary/staleversion.html
+++ b/test/testdata/htdocs.expected/summary/staleversion.html
@@ -13,7 +13,7 @@
 <span class="detail">summary</span>: Test package for stale version removal<br><br>
 <span class="detail">description</span>: Test package for stale version removal<br><br>
 <span class="detail">categories</span>: Shells Base<br><br>
-<span class="detail">binaries</span>: <a href="staleversion.html">staleversion</a><br><br>
+<span class="detail">source package</span>: <a href="staleversion-src.html">staleversion</a><br><br>
 <span class="detail">maintainer(s)</span>: Blooey McFooey 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
@@ -23,17 +23,11 @@
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>240-1</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion/staleversion-240-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>240-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion/staleversion-240-1-src">list of files</a>]</td><td>stable</td></tr>
 <tr><td>242-0</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion/staleversion-242-0">list of files</a>]</td><td>stable</td></tr>
-<tr><td>242-0 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion/staleversion-242-0-src">list of files</a>]</td><td>stable</td></tr>
 <tr><td>243-0</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion/staleversion-243-0">list of files</a>]</td><td>stable</td></tr>
-<tr><td>243-0 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion/staleversion-243-0-src">list of files</a>]</td><td>stable</td></tr>
 <tr><td>250-0</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion/staleversion-250-0">list of files</a>]</td><td>stable</td></tr>
-<tr><td>250-0 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion/staleversion-250-0-src">list of files</a>]</td><td>stable</td></tr>
 <tr><td>251-0</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion/staleversion-251-0">list of files</a>]</td><td>stable</td></tr>
-<tr><td>251-0 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion/staleversion-251-0-src">list of files</a>]</td><td>stable</td></tr>
 <tr><td>260-0</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion/staleversion-260-0">list of files</a>]</td><td>test</td></tr>
-<tr><td>260-0 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/staleversion/staleversion-260-0-src">list of files</a>]</td><td>test</td></tr>
 </table><br>
 </ul>
 </div>
diff --git a/test/testdata/htdocs.expected/summary/test-c-src.html b/test/testdata/htdocs.expected/summary/test-c-src.html
new file mode 100755
index 0000000..3218aee
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/test-c-src.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for test-c (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: test-c</h1>
+<span class="detail">summary</span>: test package C<br><br>
+<span class="detail">description</span>: test package C<br><br>
+<span class="detail">categories</span>: Devel<br><br>
+<span class="detail">install package(s)</span>: <a href="test-c.html">test-c</a><br><br>
+<span class="detail">maintainer(s)</span>: ORPHANED 
+<span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
+<a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
+<br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>1.0-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/test-c-src/test-c-1.0-1-src">list of files</a>]</td><td>stable</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/test-c.html b/test/testdata/htdocs.expected/summary/test-c.html
index 188f4b0..f779106 100644
--- a/test/testdata/htdocs.expected/summary/test-c.html
+++ b/test/testdata/htdocs.expected/summary/test-c.html
@@ -15,7 +15,7 @@
 <span class="detail">categories</span>: Devel<br><br>
 <span class="detail">depends</span>: test-d (>= 1.0), <a href="test-e.html">test-e</a><br><br>
 <span class="detail">obsoletes</span>: <a href="obs-a.html">obs-a</a>, <a href="obs-b.html">obs-b</a><br><br>
-<span class="detail">binaries</span>: <a href="test-c.html">test-c</a><br><br>
+<span class="detail">source package</span>: <a href="test-c-src.html">test-c</a><br><br>
 <span class="detail">maintainer(s)</span>: ORPHANED 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
@@ -25,7 +25,6 @@
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>1.0-1</td><td class="right">1 kB</td><td>[<a href="../x86/test-c/test-c-1.0-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>1.0-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/test-c/test-c-1.0-1-src">list of files</a>]</td><td>stable</td></tr>
 </table><br>
 </ul>
 </div>
diff --git a/test/testdata/htdocs.expected/summary/test-d-src.html b/test/testdata/htdocs.expected/summary/test-d-src.html
new file mode 100755
index 0000000..a80cc86
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/test-d-src.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for test-d (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: test-d</h1>
+<span class="detail">summary</span>: test package D<br><br>
+<span class="detail">description</span>: test package D<br><br>
+<span class="detail">categories</span>: Devel<br><br>
+<span class="detail">install package(s)</span>: <a href="test-d.html">test-d</a><br><br>
+<span class="detail">maintainer(s)</span>: ORPHANED 
+<span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
+<a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
+<br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>1.0.42590-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/test-d-src/test-d-1.0.42590-1-src">list of files</a>]</td><td>stable</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/test-d.html b/test/testdata/htdocs.expected/summary/test-d.html
index ba3ee0e..1aa2f6b 100644
--- a/test/testdata/htdocs.expected/summary/test-d.html
+++ b/test/testdata/htdocs.expected/summary/test-d.html
@@ -13,7 +13,7 @@
 <span class="detail">summary</span>: test package D<br><br>
 <span class="detail">description</span>: test package D<br><br>
 <span class="detail">categories</span>: Devel<br><br>
-<span class="detail">binaries</span>: <a href="test-d.html">test-d</a><br><br>
+<span class="detail">source package</span>: <a href="test-d-src.html">test-d</a><br><br>
 <span class="detail">maintainer(s)</span>: ORPHANED 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
@@ -23,7 +23,6 @@
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>1.0.42590-1</td><td class="right">1 kB</td><td>[<a href="../x86/test-d/test-d-1.0.42590-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>1.0.42590-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/test-d/test-d-1.0.42590-1-src">list of files</a>]</td><td>stable</td></tr>
 </table><br>
 </ul>
 </div>
diff --git a/test/testdata/htdocs.expected/summary/test-e-src.html b/test/testdata/htdocs.expected/summary/test-e-src.html
new file mode 100755
index 0000000..84da327
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/test-e-src.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for test-e (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: test-e</h1>
+<span class="detail">summary</span>: test package E<br><br>
+<span class="detail">description</span>: test package E<br><br>
+<span class="detail">categories</span>: Devel<br><br>
+<span class="detail">build-depends</span>: libtextcat-devel<br><br>
+<span class="detail">install package(s)</span>: <a href="test-e.html">test-e</a><br><br>
+<span class="detail">maintainer(s)</span>: ORPHANED 
+<span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
+<a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
+<br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>1.0-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/test-e-src/test-e-1.0-1-src">list of files</a>]</td><td>stable</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/test-e.html b/test/testdata/htdocs.expected/summary/test-e.html
index 1360f7d..c71f753 100644
--- a/test/testdata/htdocs.expected/summary/test-e.html
+++ b/test/testdata/htdocs.expected/summary/test-e.html
@@ -13,8 +13,7 @@
 <span class="detail">summary</span>: test package E<br><br>
 <span class="detail">description</span>: test package E<br><br>
 <span class="detail">categories</span>: Devel<br><br>
-<span class="detail">build-depends</span>: libtextcat-devel<br><br>
-<span class="detail">binaries</span>: <a href="test-e.html">test-e</a><br><br>
+<span class="detail">source package</span>: <a href="test-e-src.html">test-e</a><br><br>
 <span class="detail">maintainer(s)</span>: ORPHANED 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
@@ -24,7 +23,6 @@
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>1.0-1</td><td class="right">1 kB</td><td>[<a href="../x86/test-e/test-e-1.0-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>1.0-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/test-e/test-e-1.0-1-src">list of files</a>]</td><td>stable</td></tr>
 </table><br>
 </ul>
 </div>
diff --git a/test/testdata/htdocs.expected/summary/testpackage-src.html b/test/testdata/htdocs.expected/summary/testpackage-src.html
new file mode 100755
index 0000000..8d132db
--- /dev/null
+++ b/test/testdata/htdocs.expected/summary/testpackage-src.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="../../style.css"/>
+<title>Cygwin Package Summary for testpackage (source)</title>
+</head>
+<body>
+<!--#include virtual="/navbar.html" -->
+<div id="main">
+<!--#include virtual="/top.html" -->
+<h1>Source Package: testpackage</h1>
+<span class="detail">summary</span>: A test package (stuff &amp; other stuff)<br><br>
+<span class="detail">description</span>: A test package (stuff &amp; other stuff)<br><br>
+<span class="detail">categories</span>: Devel<br><br>
+<span class="detail">install package(s)</span>: <a href="testpackage.html">testpackage</a><br><br>
+<span class="detail">maintainer(s)</span>: Blooey McFooey 
+<span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
+<a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
+<br><br>
+<ul>
+<li><span class="detail">x86</span></li>
+<table class="pkgtable">
+<tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
+<tr><td>0.1-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/testpackage-src/testpackage-0.1-1-src">list of files</a>]</td><td>stable</td></tr>
+</table><br>
+</ul>
+</div>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/summary/testpackage.html b/test/testdata/htdocs.expected/summary/testpackage.html
index 6f6daff..6d18cd8 100644
--- a/test/testdata/htdocs.expected/summary/testpackage.html
+++ b/test/testdata/htdocs.expected/summary/testpackage.html
@@ -13,7 +13,7 @@
 <span class="detail">summary</span>: A test package (stuff &amp; other stuff)<br><br>
 <span class="detail">description</span>: A test package (stuff &amp; other stuff)<br><br>
 <span class="detail">categories</span>: Devel<br><br>
-<span class="detail">binaries</span>: <a href="testpackage.html">testpackage</a><br><br>
+<span class="detail">source package</span>: <a href="testpackage-src.html">testpackage</a><br><br>
 <span class="detail">maintainer(s)</span>: Blooey McFooey 
 <span class="smaller">(Use <a href="/lists.html#cygwin">the mailing list</a> to report bugs or ask questions.
 <a href="/problems.html#personal-email">Do not contact the maintainer(s) directly</a>.)</span>
@@ -23,7 +23,6 @@
 <table class="pkgtable">
 <tr><th>Version</th><th>Package Size</th><th>Files</th><th>Status</th></tr>
 <tr><td>0.1-1</td><td class="right">1 kB</td><td>[<a href="../x86/testpackage/testpackage-0.1-1">list of files</a>]</td><td>stable</td></tr>
-<tr><td>0.1-1 (source)</td><td class="right">1 kB</td><td>[<a href="../x86/testpackage/testpackage-0.1-1-src">list of files</a>]</td><td>stable</td></tr>
 </table><br>
 </ul>
 </div>
diff --git a/test/testdata/htdocs.expected/x86/arc-src/.htaccess b/test/testdata/htdocs.expected/x86/arc-src/.htaccess
new file mode 100644
index 0000000..c846ada
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/arc-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/arc-src/$ /packages/summary/arc-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/arc-src/arc-4.32.7-10-src b/test/testdata/htdocs.expected/x86/arc-src/arc-4.32.7-10-src
new file mode 100644
index 0000000..583e28d
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/arc-src/arc-4.32.7-10-src
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>arc-src: The ARC archive utility (source)</title>
+</head>
+<body>
+<h1>arc-src: The ARC archive utility (source)</h1>
+<pre>
+    2016-11-24 22:47        1786 cc1BOI4Z
+    2016-11-24 18:14        1786 ccbdRcNC
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/corrupt-src/.htaccess b/test/testdata/htdocs.expected/x86/corrupt-src/.htaccess
new file mode 100644
index 0000000..7389214
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/corrupt-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/corrupt-src/$ /packages/summary/corrupt-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/corrupt-src/corrupt-2.0.0-1-src b/test/testdata/htdocs.expected/x86/corrupt-src/corrupt-2.0.0-1-src
new file mode 100644
index 0000000..46032cf
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/corrupt-src/corrupt-2.0.0-1-src
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>corrupt-src: A corrupt package (source)</title>
+</head>
+<body>
+<h1>corrupt-src: A corrupt package (source)</h1>
+<pre>
+    2016-06-11 10:34           0 perl-Business-ISBN-2.011-1.src/
+    2016-06-11 10:34      364175 perl-Business-ISBN-2.011-1.src/Business-ISBN-2.011.tar.gz
+package is corrupted
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/cygwin-src/.htaccess b/test/testdata/htdocs.expected/x86/cygwin-src/.htaccess
new file mode 100644
index 0000000..0f3a4c0
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/cygwin-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/cygwin-src/$ /packages/summary/cygwin-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/cygwin-src/cygwin-2.2.0-1-src b/test/testdata/htdocs.expected/x86/cygwin-src/cygwin-2.2.0-1-src
new file mode 100644
index 0000000..9a42632
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/cygwin-src/cygwin-2.2.0-1-src
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>cygwin-src: The UNIX emulation engine (source)</title>
+</head>
+<body>
+<h1>cygwin-src: The UNIX emulation engine (source)</h1>
+<pre>
+    2015-10-11 14:45          26 test/test.1
+    2015-10-11 14:45          31 test/test.2
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/cygwin-src/cygwin-2.2.1-1-src b/test/testdata/htdocs.expected/x86/cygwin-src/cygwin-2.2.1-1-src
new file mode 100644
index 0000000..9a42632
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/cygwin-src/cygwin-2.2.1-1-src
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>cygwin-src: The UNIX emulation engine (source)</title>
+</head>
+<body>
+<h1>cygwin-src: The UNIX emulation engine (source)</h1>
+<pre>
+    2015-10-11 14:45          26 test/test.1
+    2015-10-11 14:45          31 test/test.2
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/cygwin-src/cygwin-2.3.0-0.3-src b/test/testdata/htdocs.expected/x86/cygwin-src/cygwin-2.3.0-0.3-src
new file mode 100644
index 0000000..9a42632
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/cygwin-src/cygwin-2.3.0-0.3-src
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>cygwin-src: The UNIX emulation engine (source)</title>
+</head>
+<body>
+<h1>cygwin-src: The UNIX emulation engine (source)</h1>
+<pre>
+    2015-10-11 14:45          26 test/test.1
+    2015-10-11 14:45          31 test/test.2
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/keychain-src/.htaccess b/test/testdata/htdocs.expected/x86/keychain-src/.htaccess
new file mode 100644
index 0000000..b1467ae
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/keychain-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/keychain-src/$ /packages/summary/keychain-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/keychain-src/keychain-2.6.8-1-src b/test/testdata/htdocs.expected/x86/keychain-src/keychain-2.6.8-1-src
new file mode 100644
index 0000000..bdc08ce
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/keychain-src/keychain-2.6.8-1-src
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>keychain-src: Key manager for OpenSSH (source)</title>
+</head>
+<body>
+<h1>keychain-src: Key manager for OpenSSH (source)</h1>
+<pre>
+    2006-11-09 04:59        3051 keychain-2.6.8-1.patch
+    2006-11-09 04:59        5621 keychain-2.6.8-1.sh
+    2006-11-09 04:59       31531 keychain-2.6.8.tar.bz2
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/keychain-src/keychain-2.7.1-1-src b/test/testdata/htdocs.expected/x86/keychain-src/keychain-2.7.1-1-src
new file mode 100644
index 0000000..239531c
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/keychain-src/keychain-2.7.1-1-src
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>keychain-src: Key manager for OpenSSH (source)</title>
+</head>
+<body>
+<h1>keychain-src: Key manager for OpenSSH (source)</h1>
+<pre>
+    2012-10-10 05:40       24455 keychain-2.7.1-1-cygwin.patch
+    2012-10-10 05:40         270 keychain-2.7.1-1-cygwin.patch.sig
+    2012-10-10 05:40      320093 keychain-2.7.1-1.sh
+    2012-10-10 05:40         260 keychain-2.7.1-1.sh.sig
+    2012-10-10 05:40       65211 keychain_2.7.1.orig.tar.gz
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/mDNSResponder-src/.htaccess b/test/testdata/htdocs.expected/x86/mDNSResponder-src/.htaccess
new file mode 100644
index 0000000..a47ee41
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/mDNSResponder-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/mDNSResponder-src/$ /packages/summary/mDNSResponder-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/mDNSResponder-src/mDNSResponder-379.32.1-1-src b/test/testdata/htdocs.expected/x86/mDNSResponder-src/mDNSResponder-379.32.1-1-src
new file mode 100644
index 0000000..6b9e6ce
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/mDNSResponder-src/mDNSResponder-379.32.1-1-src
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>mDNSResponder-src: Bonjour Zeroconf implementation (source)</title>
+</head>
+<body>
+<h1>mDNSResponder-src: Bonjour Zeroconf implementation (source)</h1>
+<pre>
+    2015-10-11 14:45          26 test/test.1
+    2015-10-11 14:45          31 test/test.2
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/obs-a-src/.htaccess b/test/testdata/htdocs.expected/x86/obs-a-src/.htaccess
new file mode 100644
index 0000000..8728151
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/obs-a-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/obs-a-src/$ /packages/summary/obs-a-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/obs-a-src/obs-a-1.0-1-src b/test/testdata/htdocs.expected/x86/obs-a-src/obs-a-1.0-1-src
new file mode 100644
index 0000000..e33509d
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/obs-a-src/obs-a-1.0-1-src
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>obs-a-src: obsolete package A (source)</title>
+</head>
+<body>
+<h1>obs-a-src: obsolete package A (source)</h1>
+<pre>
+    2017-05-22 13:04          82 test-a-1.0-1.hint
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/obs-b-src/.htaccess b/test/testdata/htdocs.expected/x86/obs-b-src/.htaccess
new file mode 100644
index 0000000..d8e7f90
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/obs-b-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/obs-b-src/$ /packages/summary/obs-b-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/obs-b-src/obs-b-1.0-1-src b/test/testdata/htdocs.expected/x86/obs-b-src/obs-b-1.0-1-src
new file mode 100644
index 0000000..ad1d017
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/obs-b-src/obs-b-1.0-1-src
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>obs-b-src: obsolete package B (source)</title>
+</head>
+<body>
+<h1>obs-b-src: obsolete package B (source)</h1>
+<pre>
+    2017-05-22 13:04          82 test-a-1.0-1.hint
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/openssh-src/.htaccess b/test/testdata/htdocs.expected/x86/openssh-src/.htaccess
new file mode 100644
index 0000000..efcc95b
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/openssh-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/openssh-src/$ /packages/summary/openssh-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/openssh-src/openssh-7.2p2-1-src b/test/testdata/htdocs.expected/x86/openssh-src/openssh-7.2p2-1-src
new file mode 100644
index 0000000..11ab8f7
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/openssh-src/openssh-7.2p2-1-src
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>openssh-src: The OpenSSH server and client programs (source)</title>
+</head>
+<body>
+<h1>openssh-src: The OpenSSH server and client programs (source)</h1>
+<pre>
+    2015-10-11 14:45          26 test/test.1
+    2015-10-11 14:45          31 test/test.2
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/per-version-replacement-hint-only-src/.htaccess b/test/testdata/htdocs.expected/x86/per-version-replacement-hint-only-src/.htaccess
new file mode 100644
index 0000000..146cc84
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/per-version-replacement-hint-only-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/per-version-replacement-hint-only-src/$ /packages/summary/per-version-replacement-hint-only-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/per-version-replacement-hint-only-src/per-version-replacement-hint-only-1.0-1-src b/test/testdata/htdocs.expected/x86/per-version-replacement-hint-only-src/per-version-replacement-hint-only-1.0-1-src
new file mode 100644
index 0000000..bef865b
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/per-version-replacement-hint-only-src/per-version-replacement-hint-only-1.0-1-src
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>per-version-replacement-hint-only-src: Per-version hint test package (source)</title>
+</head>
+<body>
+<h1>per-version-replacement-hint-only-src: Per-version hint test package (source)</h1>
+<pre>
+    2015-10-11 14:45          26 test/test.1
+    2015-10-11 14:45          31 test/test.2
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/per-version-src/.htaccess b/test/testdata/htdocs.expected/x86/per-version-src/.htaccess
new file mode 100644
index 0000000..dc4da58
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/per-version-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/per-version-src/$ /packages/summary/per-version-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/per-version-src/per-version-4.0-1-src b/test/testdata/htdocs.expected/x86/per-version-src/per-version-4.0-1-src
new file mode 100644
index 0000000..a43c063
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/per-version-src/per-version-4.0-1-src
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>per-version-src: Per-version hint test package (source)</title>
+</head>
+<body>
+<h1>per-version-src: Per-version hint test package (source)</h1>
+<pre>
+    2015-10-11 14:45          26 test/test.1
+    2015-10-11 14:45          31 test/test.2
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/per-version-src/per-version-4.8-1-src b/test/testdata/htdocs.expected/x86/per-version-src/per-version-4.8-1-src
new file mode 100644
index 0000000..a43c063
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/per-version-src/per-version-4.8-1-src
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>per-version-src: Per-version hint test package (source)</title>
+</head>
+<body>
+<h1>per-version-src: Per-version hint test package (source)</h1>
+<pre>
+    2015-10-11 14:45          26 test/test.1
+    2015-10-11 14:45          31 test/test.2
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/perl-Net-SMTP-SSL-src/.htaccess b/test/testdata/htdocs.expected/x86/perl-Net-SMTP-SSL-src/.htaccess
new file mode 100644
index 0000000..7934f37
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/perl-Net-SMTP-SSL-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/perl-Net-SMTP-SSL-src/$ /packages/summary/perl-Net-SMTP-SSL-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/perl-Net-SMTP-SSL-src/perl-Net-SMTP-SSL-1.01-1-src b/test/testdata/htdocs.expected/x86/perl-Net-SMTP-SSL-src/perl-Net-SMTP-SSL-1.01-1-src
new file mode 100644
index 0000000..806be54
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/perl-Net-SMTP-SSL-src/perl-Net-SMTP-SSL-1.01-1-src
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>perl-Net-SMTP-SSL-src: Perl distribution Net-SMTP-SSL (source)</title>
+</head>
+<body>
+<h1>perl-Net-SMTP-SSL-src: Perl distribution Net-SMTP-SSL (source)</h1>
+<pre>
+    2015-06-26 19:10           0 perl-Net-SMTP-SSL-1.03-1.src/
+    2015-06-26 19:10        2271 perl-Net-SMTP-SSL-1.03-1.src/Net-SMTP-SSL-1.03.tar.gz
+    2015-06-26 19:10         296 perl-Net-SMTP-SSL-1.03-1.src/perl-Net-SMTP-SSL.cygport
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/perl-Net-SMTP-SSL-src/perl-Net-SMTP-SSL-1.02-1-src b/test/testdata/htdocs.expected/x86/perl-Net-SMTP-SSL-src/perl-Net-SMTP-SSL-1.02-1-src
new file mode 100644
index 0000000..806be54
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/perl-Net-SMTP-SSL-src/perl-Net-SMTP-SSL-1.02-1-src
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>perl-Net-SMTP-SSL-src: Perl distribution Net-SMTP-SSL (source)</title>
+</head>
+<body>
+<h1>perl-Net-SMTP-SSL-src: Perl distribution Net-SMTP-SSL (source)</h1>
+<pre>
+    2015-06-26 19:10           0 perl-Net-SMTP-SSL-1.03-1.src/
+    2015-06-26 19:10        2271 perl-Net-SMTP-SSL-1.03-1.src/Net-SMTP-SSL-1.03.tar.gz
+    2015-06-26 19:10         296 perl-Net-SMTP-SSL-1.03-1.src/perl-Net-SMTP-SSL.cygport
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/perl-Net-SMTP-SSL-src/perl-Net-SMTP-SSL-1.03-1-src b/test/testdata/htdocs.expected/x86/perl-Net-SMTP-SSL-src/perl-Net-SMTP-SSL-1.03-1-src
new file mode 100644
index 0000000..806be54
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/perl-Net-SMTP-SSL-src/perl-Net-SMTP-SSL-1.03-1-src
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>perl-Net-SMTP-SSL-src: Perl distribution Net-SMTP-SSL (source)</title>
+</head>
+<body>
+<h1>perl-Net-SMTP-SSL-src: Perl distribution Net-SMTP-SSL (source)</h1>
+<pre>
+    2015-06-26 19:10           0 perl-Net-SMTP-SSL-1.03-1.src/
+    2015-06-26 19:10        2271 perl-Net-SMTP-SSL-1.03-1.src/Net-SMTP-SSL-1.03.tar.gz
+    2015-06-26 19:10         296 perl-Net-SMTP-SSL-1.03-1.src/perl-Net-SMTP-SSL.cygport
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/rpm-doc-src/.htaccess b/test/testdata/htdocs.expected/x86/rpm-doc-src/.htaccess
new file mode 100644
index 0000000..c588cef
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/rpm-doc-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/rpm-doc-src/$ /packages/summary/rpm-doc-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/rpm-doc-src/rpm-doc-4.1-2-src b/test/testdata/htdocs.expected/x86/rpm-doc-src/rpm-doc-4.1-2-src
new file mode 100644
index 0000000..fb29610
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/rpm-doc-src/rpm-doc-4.1-2-src
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>rpm-doc-src: Obsolete package for RPM package management system manual pages (extra text to so repr is not one line) (source)</title>
+</head>
+<body>
+<h1>rpm-doc-src: Obsolete package for RPM package management system manual pages (extra text to so repr is not one line) (source)</h1>
+<pre>
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/staleversion-src/.htaccess b/test/testdata/htdocs.expected/x86/staleversion-src/.htaccess
new file mode 100644
index 0000000..b6ffc09
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/staleversion-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/staleversion-src/$ /packages/summary/staleversion-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-240-1-src b/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-240-1-src
new file mode 100644
index 0000000..c7e0926
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-240-1-src
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>staleversion-src: Test package for stale version removal (source)</title>
+</head>
+<body>
+<h1>staleversion-src: Test package for stale version removal (source)</h1>
+<pre>
+    2015-10-11 14:45          26 test/test.1
+    2015-10-11 14:45          31 test/test.2
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-242-0-src b/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-242-0-src
new file mode 100644
index 0000000..c7e0926
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-242-0-src
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>staleversion-src: Test package for stale version removal (source)</title>
+</head>
+<body>
+<h1>staleversion-src: Test package for stale version removal (source)</h1>
+<pre>
+    2015-10-11 14:45          26 test/test.1
+    2015-10-11 14:45          31 test/test.2
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-243-0-src b/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-243-0-src
new file mode 100644
index 0000000..c7e0926
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-243-0-src
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>staleversion-src: Test package for stale version removal (source)</title>
+</head>
+<body>
+<h1>staleversion-src: Test package for stale version removal (source)</h1>
+<pre>
+    2015-10-11 14:45          26 test/test.1
+    2015-10-11 14:45          31 test/test.2
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-250-0-src b/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-250-0-src
new file mode 100644
index 0000000..c7e0926
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-250-0-src
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>staleversion-src: Test package for stale version removal (source)</title>
+</head>
+<body>
+<h1>staleversion-src: Test package for stale version removal (source)</h1>
+<pre>
+    2015-10-11 14:45          26 test/test.1
+    2015-10-11 14:45          31 test/test.2
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-251-0-src b/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-251-0-src
new file mode 100644
index 0000000..c7e0926
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-251-0-src
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>staleversion-src: Test package for stale version removal (source)</title>
+</head>
+<body>
+<h1>staleversion-src: Test package for stale version removal (source)</h1>
+<pre>
+    2015-10-11 14:45          26 test/test.1
+    2015-10-11 14:45          31 test/test.2
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-260-0-src b/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-260-0-src
new file mode 100644
index 0000000..c7e0926
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/staleversion-src/staleversion-260-0-src
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>staleversion-src: Test package for stale version removal (source)</title>
+</head>
+<body>
+<h1>staleversion-src: Test package for stale version removal (source)</h1>
+<pre>
+    2015-10-11 14:45          26 test/test.1
+    2015-10-11 14:45          31 test/test.2
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/test-c-src/.htaccess b/test/testdata/htdocs.expected/x86/test-c-src/.htaccess
new file mode 100644
index 0000000..a134807
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/test-c-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/test-c-src/$ /packages/summary/test-c-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/test-c-src/test-c-1.0-1-src b/test/testdata/htdocs.expected/x86/test-c-src/test-c-1.0-1-src
new file mode 100644
index 0000000..e3bd985
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/test-c-src/test-c-1.0-1-src
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test-c-src: test package C (source)</title>
+</head>
+<body>
+<h1>test-c-src: test package C (source)</h1>
+<pre>
+    2017-05-22 13:27          82 test-c-0.5-1.hint
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/test-d-src/.htaccess b/test/testdata/htdocs.expected/x86/test-d-src/.htaccess
new file mode 100644
index 0000000..b58f463
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/test-d-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/test-d-src/$ /packages/summary/test-d-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/test-d-src/test-d-1.0-1-src b/test/testdata/htdocs.expected/x86/test-d-src/test-d-1.0-1-src
new file mode 100644
index 0000000..1551c3f
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/test-d-src/test-d-1.0-1-src
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test-d-src: test package D (source)</title>
+</head>
+<body>
+<h1>test-d-src: test package D (source)</h1>
+<pre>
+    2017-05-22 18:17          40 test-d-1.0-1.hint
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/test-e-src/.htaccess b/test/testdata/htdocs.expected/x86/test-e-src/.htaccess
new file mode 100644
index 0000000..ac45df5
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/test-e-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/test-e-src/$ /packages/summary/test-e-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/test-e-src/test-e-1.0-1-src b/test/testdata/htdocs.expected/x86/test-e-src/test-e-1.0-1-src
new file mode 100644
index 0000000..a1b7a2a
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/test-e-src/test-e-1.0-1-src
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test-e-src: test package E (source)</title>
+</head>
+<body>
+<h1>test-e-src: test package E (source)</h1>
+<pre>
+    2017-05-22 18:17          40 test-d-1.0-1.hint
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/testpackage-src/.htaccess b/test/testdata/htdocs.expected/x86/testpackage-src/.htaccess
new file mode 100644
index 0000000..def9360
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/testpackage-src/.htaccess
@@ -0,0 +1,2 @@
+RedirectMatch temp /packages/x86/testpackage-src/$ /packages/summary/testpackage-src.html
+ForceType text/html
diff --git a/test/testdata/htdocs.expected/x86/testpackage-src/testpackage-0.1-1-src b/test/testdata/htdocs.expected/x86/testpackage-src/testpackage-0.1-1-src
new file mode 100644
index 0000000..ea726da
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/testpackage-src/testpackage-0.1-1-src
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>testpackage-src: A test package (stuff &amp; other stuff) (source)</title>
+</head>
+<body>
+<h1>testpackage-src: A test package (stuff &amp; other stuff) (source)</h1>
+<pre>
+    2016-11-24 22:47        1786 cc1BOI4Z
+    2016-11-24 18:14        1786 ccbdRcNC
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86_64/.htaccess b/test/testdata/htdocs.expected/x86_64/.htaccess
new file mode 100644
index 0000000..dce07bf
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86_64/.htaccess
@@ -0,0 +1 @@
+Redirect temp /packages/x86_64/index.html https://cygwin.com/packages/package_list.html
diff --git a/test/testdata/process_arch/htdocs.expected b/test/testdata/process_arch/htdocs.expected
index bc09e7d..947fb4c 100644
--- a/test/testdata/process_arch/htdocs.expected
+++ b/test/testdata/process_arch/htdocs.expected
@@ -1,90 +1,110 @@
-{'.': ['packages.inc'],
- 'summary': ['arc.html',
+{'.': ['packages.inc', 'src_packages.inc'],
+ 'summary': ['arc-src.html',
+             'arc.html',
              'base-cygwin.html',
+             'corrupt-src.html',
              'corrupt.html',
              'cygwin-debuginfo.html',
              'cygwin-devel.html',
+             'cygwin-src.html',
              'cygwin.html',
+             'keychain-src.html',
              'keychain.html',
              'libdns_sd-devel.html',
              'libdns_sd1.html',
+             'mDNSResponder-src.html',
              'mDNSResponder.html',
+             'obs-a-src.html',
              'obs-a.html',
+             'obs-b-src.html',
              'obs-b.html',
+             'openssh-src.html',
              'openssh.html',
+             'per-version-replacement-hint-only-src.html',
              'per-version-replacement-hint-only.html',
+             'per-version-src.html',
              'per-version.html',
+             'perl-Net-SMTP-SSL-src.html',
              'perl-Net-SMTP-SSL.html',
+             'rpm-doc-src.html',
              'rpm-doc.html',
+             'staleversion-src.html',
              'staleversion.html',
+             'test-c-src.html',
              'test-c.html',
+             'test-d-src.html',
              'test-d.html',
+             'test-e-src.html',
              'test-e.html',
+             'testpackage-src.html',
              'testpackage-subpackage.html',
              'testpackage.html'],
  'x86': ['.htaccess'],
- 'x86/arc': ['.htaccess', 'arc-4.32.7-10', 'arc-4.32.7-10-src'],
+ 'x86/arc': ['.htaccess', 'arc-4.32.7-10'],
+ 'x86/arc-src': ['.htaccess', 'arc-4.32.7-10-src'],
  'x86/base-cygwin': ['.htaccess', 'base-cygwin-3.6-1', 'base-cygwin-3.8-1'],
- 'x86/corrupt': ['.htaccess', 'corrupt-2.0.0-1', 'corrupt-2.0.0-1-src'],
- 'x86/cygwin': ['.htaccess',
-                'cygwin-2.2.0-1',
-                'cygwin-2.2.0-1-src',
-                'cygwin-2.2.1-1',
-                'cygwin-2.2.1-1-src',
-                'cygwin-2.3.0-0.3',
-                'cygwin-2.3.0-0.3-src'],
+ 'x86/corrupt': ['.htaccess', 'corrupt-2.0.0-1'],
+ 'x86/corrupt-src': ['.htaccess', 'corrupt-2.0.0-1-src'],
+ 'x86/cygwin': ['.htaccess', 'cygwin-2.2.0-1', 'cygwin-2.2.1-1', 'cygwin-2.3.0-0.3'],
  'x86/cygwin-debuginfo': ['.htaccess',
                           'cygwin-debuginfo-2.2.0-1',
                           'cygwin-debuginfo-2.2.1-1',
                           'cygwin-debuginfo-2.3.0-0.3'],
  'x86/cygwin-devel': ['.htaccess', 'cygwin-devel-2.2.0-1', 'cygwin-devel-2.2.1-1', 'cygwin-devel-2.3.0-0.3'],
- 'x86/keychain': ['.htaccess', 'keychain-2.6.8-1', 'keychain-2.6.8-1-src', 'keychain-2.7.1-1', 'keychain-2.7.1-1-src'],
+ 'x86/cygwin-src': ['.htaccess', 'cygwin-2.2.0-1-src', 'cygwin-2.2.1-1-src', 'cygwin-2.3.0-0.3-src'],
+ 'x86/keychain': ['.htaccess', 'keychain-2.6.8-1', 'keychain-2.7.1-1'],
+ 'x86/keychain-src': ['.htaccess', 'keychain-2.6.8-1-src', 'keychain-2.7.1-1-src'],
  'x86/libdns_sd-devel': ['.htaccess', 'libdns_sd-devel-379.32.1-1'],
  'x86/libdns_sd1': ['.htaccess', 'libdns_sd1-379.32.1-1'],
- 'x86/mDNSResponder': ['.htaccess', 'mDNSResponder-379.32.1-1', 'mDNSResponder-379.32.1-1-src'],
- 'x86/obs-a': ['.htaccess', 'obs-a-1.0-1', 'obs-a-1.0-1-src'],
- 'x86/obs-b': ['.htaccess', 'obs-b-1.0-1', 'obs-b-1.0-1-src'],
- 'x86/openssh': ['.htaccess', 'openssh-7.2p2-1', 'openssh-7.2p2-1-src'],
- 'x86/per-version': ['.htaccess',
-                     'per-version-4.0-1',
-                     'per-version-4.0-1-src',
-                     'per-version-4.8-1',
-                     'per-version-4.8-1-src',
-                     'per-version-5.0-1',
-                     'per-version-5.0-1-src'],
- 'x86/per-version-replacement-hint-only': ['.htaccess',
-                                           'per-version-replacement-hint-only-1.0-1',
-                                           'per-version-replacement-hint-only-1.0-1-src'],
+ 'x86/mDNSResponder': ['.htaccess', 'mDNSResponder-379.32.1-1'],
+ 'x86/mDNSResponder-src': ['.htaccess', 'mDNSResponder-379.32.1-1-src'],
+ 'x86/obs-a': ['.htaccess', 'obs-a-1.0-1'],
+ 'x86/obs-a-src': ['.htaccess', 'obs-a-1.0-1-src'],
+ 'x86/obs-b': ['.htaccess', 'obs-b-1.0-1'],
+ 'x86/obs-b-src': ['.htaccess', 'obs-b-1.0-1-src'],
+ 'x86/openssh': ['.htaccess', 'openssh-7.2p2-1'],
+ 'x86/openssh-src': ['.htaccess', 'openssh-7.2p2-1-src'],
+ 'x86/per-version': ['.htaccess', 'per-version-4.0-1', 'per-version-4.8-1', 'per-version-5.0-1'],
+ 'x86/per-version-replacement-hint-only': ['.htaccess', 'per-version-replacement-hint-only-1.0-1'],
+ 'x86/per-version-replacement-hint-only-src': ['.htaccess', 'per-version-replacement-hint-only-1.0-1-src'],
+ 'x86/per-version-src': ['.htaccess', 'per-version-4.0-1-src', 'per-version-4.8-1-src', 'per-version-5.0-1-src'],
  'x86/perl-Net-SMTP-SSL': ['.htaccess',
                            'perl-Net-SMTP-SSL-1.02-1',
-                           'perl-Net-SMTP-SSL-1.02-1-src',
                            'perl-Net-SMTP-SSL-1.03-1',
-                           'perl-Net-SMTP-SSL-1.03-1-src',
-                           'perl-Net-SMTP-SSL-1.03-2',
-                           'perl-Net-SMTP-SSL-1.03-2-src'],
- 'x86/rpm-doc': ['.htaccess', 'rpm-doc-4.1-2', 'rpm-doc-4.1-2-src', 'rpm-doc-999-1'],
- 'x86/staleversion': ['.htaccess',
-                      'staleversion-243-0',
-                      'staleversion-243-0-src',
-                      'staleversion-250-0',
-                      'staleversion-250-0-src',
-                      'staleversion-260-0',
-                      'staleversion-260-0-src'],
- 'x86/test-c': ['.htaccess', 'test-c-1.0-1', 'test-c-1.0-1-src'],
- 'x86/test-d': ['.htaccess', 'test-d-1.0-1', 'test-d-1.0-1-src'],
- 'x86/test-e': ['.htaccess', 'test-e-1.0-1', 'test-e-1.0-1-src'],
- 'x86/testpackage': ['.htaccess', 'testpackage-1.0-1', 'testpackage-1.0-1-src'],
+                           'perl-Net-SMTP-SSL-1.03-2'],
+ 'x86/perl-Net-SMTP-SSL-src': ['.htaccess',
+                               'perl-Net-SMTP-SSL-1.02-1-src',
+                               'perl-Net-SMTP-SSL-1.03-1-src',
+                               'perl-Net-SMTP-SSL-1.03-2-src'],
+ 'x86/rpm-doc': ['.htaccess', 'rpm-doc-4.1-2', 'rpm-doc-999-1'],
+ 'x86/rpm-doc-src': ['.htaccess', 'rpm-doc-4.1-2-src'],
+ 'x86/staleversion': ['.htaccess', 'staleversion-243-0', 'staleversion-250-0', 'staleversion-260-0'],
+ 'x86/staleversion-src': ['.htaccess', 'staleversion-243-0-src', 'staleversion-250-0-src', 'staleversion-260-0-src'],
+ 'x86/test-c': ['.htaccess', 'test-c-1.0-1'],
+ 'x86/test-c-src': ['.htaccess', 'test-c-1.0-1-src'],
+ 'x86/test-d': ['.htaccess', 'test-d-1.0-1'],
+ 'x86/test-d-src': ['.htaccess', 'test-d-1.0-1-src'],
+ 'x86/test-e': ['.htaccess', 'test-e-1.0-1'],
+ 'x86/test-e-src': ['.htaccess', 'test-e-1.0-1-src'],
+ 'x86/testpackage': ['.htaccess', 'testpackage-1.0-1'],
+ 'x86/testpackage-src': ['.htaccess', 'testpackage-1.0-1-src'],
  'x86/testpackage-subpackage': ['.htaccess', 'testpackage-subpackage-1.0-1'],
  'x86_64': ['.htaccess'],
- 'x86_64/obs-a': ['.htaccess', 'obs-a-1.0-1', 'obs-a-1.0-1-src'],
- 'x86_64/obs-b': ['.htaccess', 'obs-b-1.0-1', 'obs-b-1.0-1-src'],
+ 'x86_64/obs-a': ['.htaccess', 'obs-a-1.0-1'],
+ 'x86_64/obs-a-src': ['.htaccess', 'obs-a-1.0-1-src'],
+ 'x86_64/obs-b': ['.htaccess', 'obs-b-1.0-1'],
+ 'x86_64/obs-b-src': ['.htaccess', 'obs-b-1.0-1-src'],
  'x86_64/perl-Net-SMTP-SSL': ['.htaccess',
                               'perl-Net-SMTP-SSL-1.02-1',
-                              'perl-Net-SMTP-SSL-1.02-1-src',
                               'perl-Net-SMTP-SSL-1.03-1',
-                              'perl-Net-SMTP-SSL-1.03-1-src',
-                              'perl-Net-SMTP-SSL-1.03-2',
-                              'perl-Net-SMTP-SSL-1.03-2-src'],
- 'x86_64/test-c': ['.htaccess', 'test-c-1.0-1', 'test-c-1.0-1-src'],
- 'x86_64/test-d': ['.htaccess', 'test-d-1.0-1', 'test-d-1.0-1-src'],
- 'x86_64/test-e': ['.htaccess', 'test-e-1.0-1', 'test-e-1.0-1-src']}
+                              'perl-Net-SMTP-SSL-1.03-2'],
+ 'x86_64/perl-Net-SMTP-SSL-src': ['.htaccess',
+                                  'perl-Net-SMTP-SSL-1.02-1-src',
+                                  'perl-Net-SMTP-SSL-1.03-1-src',
+                                  'perl-Net-SMTP-SSL-1.03-2-src'],
+ 'x86_64/test-c': ['.htaccess', 'test-c-1.0-1'],
+ 'x86_64/test-c-src': ['.htaccess', 'test-c-1.0-1-src'],
+ 'x86_64/test-d': ['.htaccess', 'test-d-1.0-1'],
+ 'x86_64/test-d-src': ['.htaccess', 'test-d-1.0-1-src'],
+ 'x86_64/test-e': ['.htaccess', 'test-e-1.0-1'],
+ 'x86_64/test-e-src': ['.htaccess', 'test-e-1.0-1-src']}
diff --git a/test/testdata/uploads/pkglist.expected b/test/testdata/uploads/pkglist.expected
index a9d9bfd..5595a50 100644
--- a/test/testdata/uploads/pkglist.expected
+++ b/test/testdata/uploads/pkglist.expected
@@ -1,5 +1,12 @@
-{'testpackage': Package('testpackage', {'1.0-1': {'testpackage-1.0-1-src.tar.bz2': Tar('testpackage-1.0-1-src.tar.bz2', 'x86/release/testpackage', 'aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83', 195, False),
-           'testpackage-1.0-1.tar.bz2': Tar('testpackage-1.0-1.tar.bz2', 'x86/release/testpackage', 'aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83', 195, False)}}, {'1.0-1': {'sdesc': '"A test package"',
+{'testpackage': Package('testpackage', {'1.0-1': {'testpackage-1.0-1.tar.bz2': Tar('testpackage-1.0-1.tar.bz2', 'x86/release/testpackage', 'aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83', 195, False)}}, {'1.0-1': {'sdesc': '"A test package"',
+           'ldesc': '"A test package\n'
+                    "It's description might contains some unicode junk\n"
+                    'Like it’s you’re Markup Language™ Nokogiri’s tool―that '
+                    'Bézier."',
+           'category': 'Devel',
+           'requires': 'cygwin',
+           'depends': 'cygwin'}}, {}, False),
+ 'testpackage-src': Package('testpackage', {'1.0-1': {'testpackage-1.0-1-src.tar.bz2': Tar('testpackage-1.0-1-src.tar.bz2', 'x86/release/testpackage', 'aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83', 195, False)}}, {'1.0-1': {'sdesc': '"A test package"',
            'ldesc': '"A test package\n'
                     "It's description might contains some unicode junk\n"
                     'Like it’s you’re Markup Language™ Nokogiri’s tool―that '
@@ -10,7 +17,7 @@
  'testpackage-subpackage': Package('testpackage/testpackage-subpackage', {'1.0-1': {'testpackage-subpackage-1.0-1.tar.bz2': Tar('testpackage-subpackage-1.0-1.tar.bz2', 'x86/release/testpackage/testpackage-subpackage', 'aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83', 195, False)}}, {'1.0-1': {'sdesc': '"A test subpackage"',
            'ldesc': '"A test subpackage"',
            'category': 'Devel',
-           'external-source': 'testpackage'}}, {}, False),
+           'external-source': 'testpackage-src'}}, {}, False),
  'testpackage2-subpackage': Package('testpackage2/testpackage2-subpackage', {'1.0-1': {'testpackage2-subpackage-1.0-1.tar.bz2': Tar('testpackage2-subpackage-1.0-1.tar.bz2', 'x86/release/testpackage2/testpackage2-subpackage', '6de201dfed1d45412509c65deb34690dc2d09c6aafccfe491fd2f440f92842b9c755b61dc7bcdd4cc0c9f18cf46c2b3a1241e99c4c2a33fff5555e7b2f0b6348', 14, True)}}, {'1.0-1': {'sdesc': '"A test subpackage 2"',
            'ldesc': '"A test subpackage 2"',
            'category': 'Devel'}}, {}, False)}


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2019-07-04 12:08 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-07-04 12:08 [calm - Cygwin server-side packaging maintenance script] branch master, updated. 20190530-43-g4a623bb jturney

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).