diff --git a/Makefile b/Makefile
index d500df2e8034bc5da526b130f9911e29f0ed4e6d_TWFrZWZpbGU=..8d7eaff92f9cc4c7156a2e217123b798e76fcf60_TWFrZWZpbGU= 100644
--- a/Makefile
+++ b/Makefile
@@ -235,16 +235,6 @@
         # Place a bogon .DS_Store file in the target dir so we can be
         # sure it doesn't get included in the final package.
 	touch build/mercurial/.DS_Store
-        # install zsh completions - this location appears to be
-        # searched by default as of macOS Sierra.
-	install -d build/mercurial/usr/local/share/zsh/site-functions/
-	install -m 0644 contrib/zsh_completion build/mercurial/usr/local/share/zsh/site-functions/_hg
-        # install bash completions - there doesn't appear to be a
-        # place that's searched by default for bash, so we'll follow
-        # the lead of Apple's git install and just put it in a
-        # location of our own.
-	install -d build/mercurial/usr/local/hg/contrib/
-	install -m 0644 contrib/bash_completion build/mercurial/usr/local/hg/contrib/hg-completion.bash
 	make -C contrib/chg \
 	  HGPATH=/usr/local/bin/hg \
 	  PYTHON=/usr/bin/python2.7 \
diff --git a/contrib/packaging/debian/rules b/contrib/packaging/debian/rules
index d500df2e8034bc5da526b130f9911e29f0ed4e6d_Y29udHJpYi9wYWNrYWdpbmcvZGViaWFuL3J1bGVz..8d7eaff92f9cc4c7156a2e217123b798e76fcf60_Y29udHJpYi9wYWNrYWdpbmcvZGViaWFuL3J1bGVz 100755
--- a/contrib/packaging/debian/rules
+++ b/contrib/packaging/debian/rules
@@ -92,6 +92,4 @@
 	mkdir -p "$(CURDIR)"/debian/mercurial/etc/mercurial/hgrc.d/
 	cp contrib/packaging/debian/*.rc "$(CURDIR)"/debian/mercurial/etc/mercurial/hgrc.d/
 	# completions
-	mkdir -p "$(CURDIR)"/debian/mercurial/usr/share/bash-completion/completions
-	cp contrib/bash_completion "$(CURDIR)"/debian/mercurial/usr/share/bash-completion/completions/hg
 	mkdir -p "$(CURDIR)"/debian/mercurial/usr/share/zsh/vendor-completions
@@ -97,5 +95,5 @@
 	mkdir -p "$(CURDIR)"/debian/mercurial/usr/share/zsh/vendor-completions
-	cp contrib/zsh_completion "$(CURDIR)"/debian/mercurial/usr/share/zsh/vendor-completions/_hg
+	mv "$(CURDIR)"/debian/mercurial/usr/share/zsh/site-functions/_hg "$(CURDIR)"/debian/mercurial/usr/share/zsh/vendor-completions/_hg
 	if [ "$(DEB_HG_CHG_BY_DEFAULT)" -eq 1 ]; then \
 		mkdir -p "$(CURDIR)"/debian/mercurial/usr/lib/mercurial; \
 		mv "$(CURDIR)"/debian/mercurial/usr/bin/hg "$(CURDIR)"/debian/mercurial/usr/lib/mercurial/hg; \
diff --git a/contrib/packaging/hgpackaging/pyoxidizer.py b/contrib/packaging/hgpackaging/pyoxidizer.py
index d500df2e8034bc5da526b130f9911e29f0ed4e6d_Y29udHJpYi9wYWNrYWdpbmcvaGdwYWNrYWdpbmcvcHlveGlkaXplci5weQ==..8d7eaff92f9cc4c7156a2e217123b798e76fcf60_Y29udHJpYi9wYWNrYWdpbmcvaGdwYWNrYWdpbmcvcHlveGlkaXplci5weQ== 100644
--- a/contrib/packaging/hgpackaging/pyoxidizer.py
+++ b/contrib/packaging/hgpackaging/pyoxidizer.py
@@ -23,7 +23,6 @@
 
 
 STAGING_RULES_WINDOWS = [
-    ('contrib/bash_completion', 'contrib/'),
     ('contrib/hgk', 'contrib/hgk.tcl'),
     ('contrib/hgweb.fcgi', 'contrib/'),
     ('contrib/hgweb.wsgi', 'contrib/'),
@@ -36,7 +35,6 @@
     ('contrib/win32/postinstall.txt', 'ReleaseNotes.txt'),
     ('contrib/win32/ReadMe.html', 'ReadMe.html'),
     ('contrib/xml.rnc', 'contrib/'),
-    ('contrib/zsh_completion', 'contrib/'),
     ('doc/*.html', 'doc/'),
     ('doc/style.css', 'doc/'),
     ('COPYING', 'Copying.txt'),
diff --git a/contrib/packaging/mercurial.spec b/contrib/packaging/mercurial.spec
index d500df2e8034bc5da526b130f9911e29f0ed4e6d_Y29udHJpYi9wYWNrYWdpbmcvbWVyY3VyaWFsLnNwZWM=..8d7eaff92f9cc4c7156a2e217123b798e76fcf60_Y29udHJpYi9wYWNrYWdpbmcvbWVyY3VyaWFsLnNwZWM= 100644
--- a/contrib/packaging/mercurial.spec
+++ b/contrib/packaging/mercurial.spec
@@ -126,14 +126,6 @@
 install -m 755 contrib/hgk $RPM_BUILD_ROOT%{_bindir}/
 install -m 755 contrib/hg-ssh $RPM_BUILD_ROOT%{_bindir}/
 
-bash_completion_dir=$RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d
-mkdir -p $bash_completion_dir
-install -m 644 contrib/bash_completion $bash_completion_dir/mercurial.sh
-
-zsh_completion_dir=$RPM_BUILD_ROOT%{_datadir}/zsh/site-functions
-mkdir -p $zsh_completion_dir
-install -m 644 contrib/zsh_completion $zsh_completion_dir/_mercurial
-
 mkdir -p $RPM_BUILD_ROOT%{emacs_lispdir}
 install -m 644 contrib/mercurial.el $RPM_BUILD_ROOT%{emacs_lispdir}/
 install -m 644 contrib/mq.el $RPM_BUILD_ROOT%{emacs_lispdir}/
@@ -148,5 +140,8 @@
 %doc CONTRIBUTORS COPYING doc/README doc/hg*.txt doc/hg*.html *.cgi contrib/*.fcgi contrib/*.wsgi
 %doc %attr(644,root,root) %{_mandir}/man?/hg*
 %doc %attr(644,root,root) contrib/*.svg
+%dir %{_datadir}/bash-completion/
+%dir %{_datadir}/bash-completion/completions
+%{_datadir}/bash-completion/completions/hg
 %dir %{_datadir}/zsh/
 %dir %{_datadir}/zsh/site-functions/
@@ -151,6 +146,6 @@
 %dir %{_datadir}/zsh/
 %dir %{_datadir}/zsh/site-functions/
-%{_datadir}/zsh/site-functions/_mercurial
+%{_datadir}/zsh/site-functions/_hg
 %dir %{_datadir}/emacs/site-lisp/
 %{_datadir}/emacs/site-lisp/mercurial.el
 %{_datadir}/emacs/site-lisp/mq.el
@@ -158,8 +153,6 @@
 %{_bindir}/chg
 %{_bindir}/hgk
 %{_bindir}/hg-ssh
-%dir %{_sysconfdir}/bash_completion.d/
-%config(noreplace) %{_sysconfdir}/bash_completion.d/mercurial.sh
 %dir %{_sysconfdir}/mercurial
 %dir %{_sysconfdir}/mercurial/hgrc.d
 %if "%{?withpython}"
diff --git a/setup.py b/setup.py
index d500df2e8034bc5da526b130f9911e29f0ed4e6d_c2V0dXAucHk=..8d7eaff92f9cc4c7156a2e217123b798e76fcf60_c2V0dXAucHk= 100644
--- a/setup.py
+++ b/setup.py
@@ -982,6 +982,10 @@
         ),
     ]
 
+    sub_commands = install.sub_commands + [
+        ('install_completion', lambda self: True)
+    ]
+
     # Also helps setuptools not be sad while we refuse to create eggs.
     single_version_externally_managed = True
 
@@ -1101,6 +1105,33 @@
                 fp.write(data)
 
 
+class hginstallcompletion(Command):
+    description = 'Install shell completion'
+
+    def initialize_options(self):
+        self.install_dir = None
+
+    def finalize_options(self):
+        self.set_undefined_options(
+            'install_data', ('install_dir', 'install_dir')
+        )
+
+    def run(self):
+        for src, dir_path, dest in (
+            (
+                'bash_completion',
+                ('share', 'bash-completion', 'completions'),
+                'hg',
+            ),
+            ('zsh_completion', ('share', 'zsh', 'site-functions'), '_hg'),
+        ):
+            dir = os.path.join(self.install_dir, *dir_path)
+            self.mkpath(dir)
+            self.copy_file(
+                os.path.join('contrib', src), os.path.join(dir, dest)
+            )
+
+
 # virtualenv installs custom distutils/__init__.py and
 # distutils/distutils.cfg files which essentially proxy back to the
 # "real" distutils in the main Python install. The presence of this
@@ -1191,6 +1222,7 @@
     'build_scripts': hgbuildscripts,
     'build_hgextindex': buildhgextindex,
     'install': hginstall,
+    'install_completion': hginstallcompletion,
     'install_lib': hginstalllib,
     'install_scripts': hginstallscripts,
     'build_hgexe': buildhgexe,
diff --git a/tests/test-mac-packages.t b/tests/test-mac-packages.t
index d500df2e8034bc5da526b130f9911e29f0ed4e6d_dGVzdHMvdGVzdC1tYWMtcGFja2FnZXMudA==..8d7eaff92f9cc4c7156a2e217123b798e76fcf60_dGVzdHMvdGVzdC1tYWMtcGFja2FnZXMudA== 100644
--- a/tests/test-mac-packages.t
+++ b/tests/test-mac-packages.t
@@ -39,8 +39,8 @@
   ./Library/Python/2.7/site-packages/mercurial/pure/bdiff.pyo	100644	0/0
   $ grep zsh/site-functions/_hg boms.txt | cut -d '	' -f 1,2,3
   ./usr/local/share/zsh/site-functions/_hg	100644	0/0
-  $ grep hg-completion.bash boms.txt | cut -d '	' -f 1,2,3
-  ./usr/local/hg/contrib/hg-completion.bash	100644	0/0
+  $ grep bash-completion/completions/hg boms.txt | cut -d '	' -f 1,2,3
+  ./usr/local/share/bash-completion-completions/hg	100644	0/0
   $ egrep 'man[15]' boms.txt | cut -d '	' -f 1,2,3
   ./usr/local/share/man/man1	40755	0/0
   ./usr/local/share/man/man1/chg.1	100644	0/0