diff --git a/debian/changelog b/debian/changelog
index b0db2d1..0bcc159 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -18,6 +18,7 @@ ubuntu-dev-tools (0.137) UNRELEASED; urgency=low
   * mk-sbuild, pbuilder-dist, ubuntu-build: Add armhf.
   * pull-debian-source, pull-lp-source: Resolve the source package (via DDE),
     if a binary package was requested (LP: #617349)
+  * New Tool: who-can-upload (LP: #876554)
 
   [ Andreas Moog ]
   * sponsor-patch: Check permission to unsubscribe sponsors-team (LP: #896884)
diff --git a/ubuntutools/lp/lpapicache.py b/ubuntutools/lp/lpapicache.py
index f284d09..dedda24 100644
--- a/ubuntutools/lp/lpapicache.py
+++ b/ubuntutools/lp/lpapicache.py
@@ -278,11 +278,11 @@ class Archive(BaseWrapper):
     resource_type = 'archive'
 
     def __init__(self, *args):
-        # Don't share between different Archives
-        if '_binpkgs' not in self.__dict__:
-            self._binpkgs = dict()
-        if '_srcpkgs' not in self.__dict__:
-            self._srcpkgs = dict()
+        self._binpkgs = {}
+        self._srcpkgs = {}
+        self._pkg_uploaders = {}
+        self._pkgset_uploaders = {}
+        self._component_uploaders = {}
 
     def getSourcePackage(self, name, series=None, pocket=None):
         '''
@@ -400,6 +400,40 @@ class Archive(BaseWrapper):
             include_binaries=include_binaries
             )
 
+    def getUploadersForComponent(self, component_name):
+        '''Get the list of PersonTeams who can upload packages in the
+        specified component.
+        '''
+        if component_name not in self._component_uploaders:
+            self._component_uploaders[component_name] = sorted(set(
+                    PersonTeam(permission.person_link)
+                    for permission in self._lpobject.getUploadersForComponent(
+                        component_name=component_name
+                    )))
+        return self._component_uploaders[component_name]
+
+    def getUploadersForPackage(self, source_package_name):
+        '''Get the list of PersonTeams who can upload source_package_name)'''
+        if source_package_name not in self._pkg_uploaders:
+            self._pkg_uploaders[source_package_name] = sorted(set(
+                    PersonTeam(permission.person_link)
+                    for permission in self._lpobject.getUploadersForPackage(
+                        source_package_name=source_package_name
+                    )))
+        return self._pkg_uploaders[source_package_name]
+
+    def getUploadersForPackageset(self, packageset, direct_permissions=False):
+        '''Get the list of PersonTeams who can upload packages in packageset'''
+        key = (packageset, direct_permissions)
+        if key not in self._pkgset_uploaders:
+            self._pkgset_uploaders[key] = sorted(set(
+                    PersonTeam(permission.person_link)
+                    for permission in self._lpobject.getUploadersForPackageset(
+                        packageset=packageset._lpobject,
+                        direct_permissions=direct_permissions,
+                    )))
+        return self._pkgset_uploaders[key]
+
 
 class SourcePackagePublishingHistory(BaseWrapper):
     '''
@@ -703,3 +737,36 @@ class DistributionSourcePackage(BaseWrapper):
     Caching class for distribution_source_package objects.
     '''
     resource_type = 'distribution_source_package'
+
+
+class Packageset(BaseWrapper):
+    '''
+    Caching class for packageset objects.
+    '''
+    resource_type = 'packageset'
+    _lp_packagesets = None
+    _source_sets = {}
+
+    @classmethod
+    def setsIncludingSource(cls, sourcepackagename, distroseries=None,
+                            direct_inclusion=False):
+        '''Get the package sets including sourcepackagename'''
+
+        if cls._lp_packagesets is None:
+            cls._lp_packagesets = Launchpad.packagesets
+
+        key = (sourcepackagename, distroseries, direct_inclusion)
+        if key not in cls._source_sets:
+            params = {
+                'sourcepackagename': sourcepackagename,
+                'direct_inclusion': direct_inclusion,
+            }
+            if distroseries is not None:
+                params['distroseries'] = distroseries._lpobject
+
+            cls._source_sets[key] = [
+                    Packageset(packageset) for packageset
+                    in cls._lp_packagesets.setsIncludingSource(**params)
+            ]
+
+        return cls._source_sets[key]
diff --git a/who-can-upload b/who-can-upload
new file mode 100755
index 0000000..04ea9f5
--- /dev/null
+++ b/who-can-upload
@@ -0,0 +1,97 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2011, Stefano Rivera <stefanor@ubuntu.com>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import optparse
+
+from ubuntutools.lp.lpapicache import (Launchpad, Distribution, PersonTeam,
+                                       Packageset,
+                                       SeriesNotFoundException)
+
+
+def main():
+    parser = optparse.OptionParser('%prog [options] package')
+    parser.add_option('-r', '--release', default=None, metavar='RELEASE',
+                      help='Use RELEASE, rather than the current development '
+                           'release')
+    parser.add_option('-t', '--list-team-members',
+                      default=False, action='store_true',
+                      help='List all team members of teams with upload rights')
+    options, args = parser.parse_args()
+
+    if len(args) != 1:
+        parser.error("One (and only one) package must be specified")
+    package = args[0]
+
+    # Need to be logged in to see uploaders:
+    Launchpad.login()
+
+    ubuntu = Distribution('ubuntu')
+    archive = ubuntu.getArchive()
+    if options.release is None:
+        series = ubuntu.getDevelopmentSeries()
+    else:
+        try:
+            series = ubuntu.getSeries(options.release)
+        except SeriesNotFoundException, e:
+            parser.error(str(e))
+
+    spph = archive.getSourcePackage(package)
+    component = spph.getComponent()
+    component_uploader = archive.getUploadersForComponent(
+            component_name=component)[0]
+    print "Component (%s)" % component
+    print "============" + ("=" * len(component))
+    print_uploaders([component_uploader], options.list_team_members)
+
+    packagesets = sorted(Packageset.setsIncludingSource(distroseries=series,
+                                                    sourcepackagename=package))
+    if packagesets:
+        print
+        print "Packagesets"
+        print "==========="
+        for packageset in packagesets:
+            print
+            print "%s:" % packageset.name
+            print_uploaders(archive.getUploadersForPackageset(
+                packageset=packageset), options.list_team_members)
+
+    ppu_uploaders = archive.getUploadersForPackage(source_package_name=package)
+    if ppu_uploaders:
+        print
+        print "Per-Package-Uploaders"
+        print "====================="
+        print
+        print_uploaders(ppu_uploaders, options.list_team_members)
+
+    print
+    if PersonTeam.me.canUploadPackage(archive, series, package, component):
+        print "You can upload this package"
+    else:
+        print "You can not upload this package, yourself."
+        print ("But you can still contribute to it via the sponsorship "
+               "process: https://wiki.ubuntu.com/SponsorshipProcess")
+
+
+def print_uploaders(uploaders, expand_teams=False, prefix=''):
+    for uploader in sorted(uploaders, key=lambda p: p.display_name):
+        print ("%s* %s (%s)%s"
+               % (prefix, uploader.display_name, uploader.name,
+                  ' [team]' if uploader.is_team else ''))
+        if expand_teams and uploader.is_team:
+            print_uploaders(uploader.participants, True, prefix=prefix + '  ')
+
+if __name__ == '__main__':
+    main()