diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
index 2ff243c415b43440af82fa3e965105205eff9ee4_bWVyY3VyaWFsL2hlbHAvY29uZmlnLnR4dA==..c4fc33c477da9d3bfeedf504a5652c31a4a63c56_bWVyY3VyaWFsL2hlbHAvY29uZmlnLnR4dA== 100644
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -2026,6 +2026,14 @@
 ``ipv6``
     Whether to use IPv6. (default: False)
 
+``labels``
+    List of string *labels* associated with the repository.
+
+    Labels are exposed as a template keyword and can be used to customize
+    output. e.g. the ``index`` template can group or filter repositories
+    by labels and the ``summary`` template can display additional content
+    if a specific label is present.
+
 ``logoimg``
     File name of the logo image that some templates display on each page.
     The file name is relative to ``staticurl``. That is, the full path to
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
index 2ff243c415b43440af82fa3e965105205eff9ee4_bWVyY3VyaWFsL2hnd2ViL2hnd2ViZGlyX21vZC5weQ==..c4fc33c477da9d3bfeedf504a5652c31a4a63c56_bWVyY3VyaWFsL2hnd2ViL2hnd2ViZGlyX21vZC5weQ== 100644
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -366,7 +366,9 @@
                            'lastchange': d,
                            'lastchange_sort': d[1]-d[0],
                            'archives': [],
-                           'isdirectory': True}
+                           'isdirectory': True,
+                           'labels': [],
+                           }
 
                     seendirs.add(name)
                     yield row
@@ -416,6 +418,7 @@
                        'lastchange_sort': d[1]-d[0],
                        'archives': archivelist(u, "tip", url),
                        'isdirectory': None,
+                       'labels': u.configlist('web', 'labels', untrusted=True),
                        }
 
                 yield row
diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
index 2ff243c415b43440af82fa3e965105205eff9ee4_bWVyY3VyaWFsL2hnd2ViL3dlYmNvbW1hbmRzLnB5..c4fc33c477da9d3bfeedf504a5652c31a4a63c56_bWVyY3VyaWFsL2hnd2ViL3dlYmNvbW1hbmRzLnB5 100644
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -725,7 +725,8 @@
                 shortlog=changelist,
                 node=tip.hex(),
                 symrev='tip',
-                archives=web.archivelist("tip"))
+                archives=web.archivelist("tip"),
+                labels=web.configlist('web', 'labels'))
 
 @webcommand('filediff')
 def filediff(web, req, tmpl):
diff --git a/mercurial/templates/json/map b/mercurial/templates/json/map
index 2ff243c415b43440af82fa3e965105205eff9ee4_bWVyY3VyaWFsL3RlbXBsYXRlcy9qc29uL21hcA==..c4fc33c477da9d3bfeedf504a5652c31a4a63c56_bWVyY3VyaWFsL3RlbXBsYXRlcy9qc29uL21hcA== 100644
--- a/mercurial/templates/json/map
+++ b/mercurial/templates/json/map
@@ -109,7 +109,8 @@
   "branches": [{join(branches%branchentry, ", ")}],
   "shortlog": [{join(shortlog%shortlogentry, ", ")}],
   "tags": [{join(tags%tagentry, ", ")}],
-  "archives": [{join(archives%archiveentry, ", ")}]
+  "archives": [{join(archives%archiveentry, ", ")}],
+  "labels": {labels|json}
   }'
 archiveentry = '\{
   "node": {node|json},
@@ -220,5 +221,6 @@
   "name": {name|utf8|json},
   "description": {description|utf8|json},
   "contact": {contact|utf8|json},
-  "lastchange": {lastchange|json}
+  "lastchange": {lastchange|json},
+  "labels": {labels|json}
   }'
diff --git a/tests/test-hgweb-json.t b/tests/test-hgweb-json.t
index 2ff243c415b43440af82fa3e965105205eff9ee4_dGVzdHMvdGVzdC1oZ3dlYi1qc29uLnQ=..c4fc33c477da9d3bfeedf504a5652c31a4a63c56_dGVzdHMvdGVzdC1oZ3dlYi1qc29uLnQ= 100644
--- a/tests/test-hgweb-json.t
+++ b/tests/test-hgweb-json.t
@@ -848,6 +848,7 @@
         "status": "inactive"
       }
     ],
+    "labels": [],
     "lastchange": [
       0.0,
       0
diff --git a/tests/test-hgwebdir.t b/tests/test-hgwebdir.t
index 2ff243c415b43440af82fa3e965105205eff9ee4_dGVzdHMvdGVzdC1oZ3dlYmRpci50..c4fc33c477da9d3bfeedf504a5652c31a4a63c56_dGVzdHMvdGVzdC1oZ3dlYmRpci50 100644
--- a/tests/test-hgwebdir.t
+++ b/tests/test-hgwebdir.t
@@ -62,6 +62,7 @@
   $ cat >> f/.hg/hgrc << EOF
   > [web]
   > name = fancy name for repo f
+  > labels = foo, bar
   > EOF
   $ cd ..
 
@@ -108,8 +109,9 @@
   "name": "a",
   "description": "unknown",
   "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
-  "lastchange": [*, *] (glob)
+  "lastchange": [*, *], (glob)
+  "labels": []
   }, {
   "name": "b",
   "description": "unknown",
   "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
@@ -112,8 +114,9 @@
   }, {
   "name": "b",
   "description": "unknown",
   "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
-  "lastchange": [*, *] (glob)
+  "lastchange": [*, *], (glob)
+  "labels": []
   }]
   } (no-eol)
 
@@ -201,6 +204,218 @@
   /astar/
   /astar/.hg/patches/
   
+
+  $ get-with-headers.py localhost:$HGPORT1 '?style=json'
+  200 Script output follows
+  
+  {
+  "entries": [{
+  "name": "t/a",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "b",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "coll/a",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "coll/a/.hg/patches",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "coll/b",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "coll/c",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "coll/notrepo/e",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "fancy name for repo f",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": ["foo", "bar"]
+  }, {
+  "name": "rcoll/a",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "rcoll/a/.hg/patches",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "rcoll/b",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "rcoll/b/d",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "rcoll/c",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "rcoll/notrepo/e",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "rcoll/notrepo/e/e2",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "fancy name for repo f",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": ["foo", "bar"]
+  }, {
+  "name": "rcoll/notrepo/f/f2",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "star/webdir/a",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "star/webdir/a/.hg/patches",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "star/webdir/b",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "star/webdir/c",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "star/webdir/notrepo/e",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "fancy name for repo f",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": ["foo", "bar"]
+  }, {
+  "name": "starstar/webdir/a",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "starstar/webdir/a/.hg/patches",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "starstar/webdir/b",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "starstar/webdir/b/d",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "starstar/webdir/c",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "starstar/webdir/notrepo/e",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "starstar/webdir/notrepo/e/e2",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "fancy name for repo f",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": ["foo", "bar"]
+  }, {
+  "name": "starstar/webdir/notrepo/f/f2",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "astar",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }, {
+  "name": "astar/.hg/patches",
+  "description": "unknown",
+  "contact": "Foo Bar \u003cfoo.bar@example.com\u003e",
+  "lastchange": [*, *], (glob)
+  "labels": []
+  }]
+  } (no-eol)
+
   $ get-with-headers.py localhost:$HGPORT1 '?style=paper'
   200 Script output follows