Patchwork [2,of,2,V2] commands: add --browser argument to `hg serve`

login
register
mail settings
Submitter Gregory Szorc
Date July 17, 2016, 8:01 p.m.
Message ID <61e63f5a6922f693c7ca.1468785676@ubuntu-vm-main>
Download mbox | patch
Permalink /patch/15921/
State Deferred
Headers show

Comments

Gregory Szorc - July 17, 2016, 8:01 p.m.
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1468785660 25200
#      Sun Jul 17 13:01:00 2016 -0700
# Node ID 61e63f5a6922f693c7ca4e7ed8c5c8e8a45aba2a
# Parent  a6ed7328eac4500637021b9afa065225fe1b90ed
commands: add --browser argument to `hg serve`

When D. Richard Hipp visited the Mozilla office several months ago
and was showing me all the awesomeness in his Fossil VCS, one of the
features that stood out to me was `fossil ui` starting a fully-featured
HTTP/HTML server including the web browser. It was so... elegant
to just type a command and get a web browser pointed to a local
HTTP server where you could interact with your VCS.

From my own experience, when I run `hg serve` I frequently open a web
browser to navigate the started server. Today, I have to click on the
printed URL or copy and paste it into my browser to open it. While it
only takes a few seconds, this wastes my precious time.

This patch adds a --browser argument to `hg serve` to streamline the
process of opening a browser when starting a local HTTP/HTML server.

The argument only works when starting servers that run the HTTP/HTML
server.

I wish "--browser" could exist as a boolean with an optional value so
bareword "--browser" would open the default browser (instead of
requiring an argument). However, it doesn't appear our option parser
allows this. This does undermine the utility of the argument a bit
(now you have to type extra characters). But, users can always install
an [alias] entry to type fewer characters, so this is only a minor
issue.
Augie Fackler - July 18, 2016, 1:33 p.m.
On Sun, Jul 17, 2016 at 01:01:16PM -0700, Gregory Szorc wrote:
> # HG changeset patch
> # User Gregory Szorc <gregory.szorc@gmail.com>
> # Date 1468785660 25200
> #      Sun Jul 17 13:01:00 2016 -0700
> # Node ID 61e63f5a6922f693c7ca4e7ed8c5c8e8a45aba2a
> # Parent  a6ed7328eac4500637021b9afa065225fe1b90ed
> commands: add --browser argument to `hg serve`

Seems fine. I suspect there will be some weirdness around showing the
address we bound to, since we're showing the (IIRC) reverse-DNS
hostname, which might not forward resolve back to the user's machine
(I think that might be true on most home networks?). I'd like someone
else to look at it though, and I'd love to know your thoughts on my
question below.

>
> When D. Richard Hipp visited the Mozilla office several months ago
> and was showing me all the awesomeness in his Fossil VCS, one of the
> features that stood out to me was `fossil ui` starting a fully-featured
> HTTP/HTML server including the web browser. It was so... elegant
> to just type a command and get a web browser pointed to a local
> HTTP server where you could interact with your VCS.
>
> From my own experience, when I run `hg serve` I frequently open a web
> browser to navigate the started server. Today, I have to click on the
> printed URL or copy and paste it into my browser to open it. While it
> only takes a few seconds, this wastes my precious time.
>
> This patch adds a --browser argument to `hg serve` to streamline the
> process of opening a browser when starting a local HTTP/HTML server.
>
> The argument only works when starting servers that run the HTTP/HTML
> server.
>
> I wish "--browser" could exist as a boolean with an optional value so
> bareword "--browser" would open the default browser (instead of
> requiring an argument). However, it doesn't appear our option parser
> allows this. This does undermine the utility of the argument a bit
> (now you have to type extra characters). But, users can always install
> an [alias] entry to type fewer characters, so this is only a minor
> issue.

So, I've been thinking about this some, and I've been meaning to see
if I can hack together some sort of true-or-value flag that'd work
thus:

[not set]: false
--flag: true
--flag=value: value

Which would then let us do things like have:

hg diff --git=no

as a quick (and mostly obvious) way to disable git-diffs. What do you
think? Even if you like that idea (I'll admit it's a bit wonky), I
think we can still land this patch and then at a later date we can
change --browser to work with no argument.

>
> diff --git a/mercurial/commands.py b/mercurial/commands.py
> --- a/mercurial/commands.py
> +++ b/mercurial/commands.py
> @@ -6439,16 +6439,17 @@ def root(ui, repo):
>
>      Returns 0 on success.
>      """
>      ui.write(repo.root + "\n")
>
>  @command('^serve',
>      [('A', 'accesslog', '', _('name of access log file to write to'),
>       _('FILE')),
> +    ('', 'browser', '', _('web browser to run')),
>      ('d', 'daemon', None, _('run server in background')),
>      ('', 'daemon-postexec', [], _('used internally by daemon mode')),
>      ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
>      # use string type, then we can check if something was passed
>      ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
>      ('a', 'address', '', _('address to listen on (default: all interfaces)'),
>       _('ADDR')),
>      ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
> @@ -6485,22 +6486,31 @@ def serve(ui, repo, **opts):
>      By default, the server logs accesses to stdout and errors to
>      stderr. Use the -A/--accesslog and -E/--errorlog options to log to
>      files.
>
>      To have the server choose a free port number to listen on, specify
>      a port number of 0; in this case, the server will print the port
>      number it uses.
>
> +    A web browser will be opened at the started server if the ``--browser``
> +    argument contains the name of a browser to start. The special value
> +    ``default`` will open the default browser as configured by your
> +    operating system.
> +
>      Returns 0 on success.
>      """
>
>      if opts["stdio"] and opts["cmdserver"]:
>          raise error.Abort(_("cannot use --stdio with --cmdserver"))
>
> +    if (opts['stdio'] or opts['cmdserver']) and opts['browser']:
> +        raise error.Abort(_('cannot use --browser with --cmdserver or '
> +                            '--stdio'))
> +
>      if opts["stdio"]:
>          if repo is None:
>              raise error.RepoError(_("there is no Mercurial repository here"
>                                      " (.hg not found)"))
>          s = sshserver.sshserver(ui, repo)
>          s.serve_forever()
>
>      if opts["cmdserver"]:
> diff --git a/mercurial/hgweb/__init__.py b/mercurial/hgweb/__init__.py
> --- a/mercurial/hgweb/__init__.py
> +++ b/mercurial/hgweb/__init__.py
> @@ -4,16 +4,17 @@
>  # Copyright 2005 Matt Mackall <mpm@selenic.com>
>  #
>  # This software may be used and distributed according to the terms of the
>  # GNU General Public License version 2 or any later version.
>
>  from __future__ import absolute_import
>
>  import os
> +import webbrowser
>
>  from ..i18n import _
>
>  from .. import (
>      error,
>      util,
>  )
>
> @@ -79,18 +80,41 @@ class httpservice(object):
>          else:
>              write = self.ui.write
>          self.url = 'http://%s%s/%s' % (fqaddr, port, prefix)
>          write(_('listening at %s (bound to %s:%d)\n') %
>                (self.url, bindaddr, self.httpd.port))
>          self.ui.flush()  # avoid buffering of status message
>
>      def run(self):
> +        self._maybestartbrowser()
>          self.httpd.serve_forever()
>
> +    def _maybestartbrowser(self):
> +        browser = self.opts.get('browser')
> +        if not browser:
> +            return
> +
> +        # webbrowser.get() accepts empty value to indicate default browser.
> +        if browser == 'default':
> +            browser = None
> +
> +        try:
> +            browser = webbrowser.get(browser)
> +            # Text-mode browsers block the calling process, which interferes
> +            # with our server.
> +            if (not isinstance(browser, webbrowser.BackgroundBrowser) and
> +                not getattr(browser, 'background', None)):
> +                self.ui.warn(_('(cannot start text-mode browsers; '
> +                               'continuing to start server)\n'))
> +                return
> +            browser.open_new_tab(self.url)
> +        except webbrowser.Error as e:
> +            self.ui.warn(_('(unable to start web browser: %s)\n') % e.args[0])
> +
>  def createservice(ui, repo, opts):
>      # this way we can check if something was given in the command-line
>      if opts.get('port'):
>          opts['port'] = util.getport(opts.get('port'))
>
>      alluis = set([ui])
>      if repo:
>          baseui = repo.baseui
> diff --git a/tests/test-completion.t b/tests/test-completion.t
> --- a/tests/test-completion.t
> +++ b/tests/test-completion.t
> @@ -149,16 +149,17 @@ Show the global options
>    -q
>    -v
>    -y
>
>  Show the options for the "serve" command
>    $ hg debugcomplete --options serve | sort
>    --accesslog
>    --address
> +  --browser
>    --certificate
>    --cmdserver
>    --config
>    --cwd
>    --daemon
>    --daemon-postexec
>    --debug
>    --debugger
> @@ -214,17 +215,17 @@ Show all commands + options
>    export: output, switch-parent, rev, text, git, nodates
>    forget: include, exclude
>    init: ssh, remotecmd, insecure
>    log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
>    merge: force, rev, preview, tool
>    pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
>    push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
>    remove: after, force, subrepos, include, exclude
> -  serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate
> +  serve: accesslog, browser, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate
>    status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos, template
>    summary: remote
>    update: clean, check, date, rev, tool
>    addremove: similarity, subrepos, include, exclude, dry-run
>    archive: no-decode, prefix, rev, type, subrepos, include, exclude
>    backout: merge, commit, no-commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user
>    bisect: reset, good, bad, skip, extend, command, noupdate
>    bookmarks: force, rev, delete, rename, inactive, template
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Sean Farley - July 19, 2016, 6:24 p.m.
Augie Fackler <raf@durin42.com> writes:

> On Sun, Jul 17, 2016 at 01:01:16PM -0700, Gregory Szorc wrote:
>> # HG changeset patch
>> # User Gregory Szorc <gregory.szorc@gmail.com>
>> # Date 1468785660 25200
>> #      Sun Jul 17 13:01:00 2016 -0700
>> # Node ID 61e63f5a6922f693c7ca4e7ed8c5c8e8a45aba2a
>> # Parent  a6ed7328eac4500637021b9afa065225fe1b90ed
>> commands: add --browser argument to `hg serve`
>
> Seems fine. I suspect there will be some weirdness around showing the
> address we bound to, since we're showing the (IIRC) reverse-DNS
> hostname, which might not forward resolve back to the user's machine
> (I think that might be true on most home networks?). I'd like someone
> else to look at it though, and I'd love to know your thoughts on my
> question below.
>
>>
>> When D. Richard Hipp visited the Mozilla office several months ago
>> and was showing me all the awesomeness in his Fossil VCS, one of the
>> features that stood out to me was `fossil ui` starting a fully-featured
>> HTTP/HTML server including the web browser. It was so... elegant
>> to just type a command and get a web browser pointed to a local
>> HTTP server where you could interact with your VCS.
>>
>> From my own experience, when I run `hg serve` I frequently open a web
>> browser to navigate the started server. Today, I have to click on the
>> printed URL or copy and paste it into my browser to open it. While it
>> only takes a few seconds, this wastes my precious time.
>>
>> This patch adds a --browser argument to `hg serve` to streamline the
>> process of opening a browser when starting a local HTTP/HTML server.
>>
>> The argument only works when starting servers that run the HTTP/HTML
>> server.
>>
>> I wish "--browser" could exist as a boolean with an optional value so
>> bareword "--browser" would open the default browser (instead of
>> requiring an argument). However, it doesn't appear our option parser
>> allows this. This does undermine the utility of the argument a bit
>> (now you have to type extra characters). But, users can always install
>> an [alias] entry to type fewer characters, so this is only a minor
>> issue.
>
> So, I've been thinking about this some, and I've been meaning to see
> if I can hack together some sort of true-or-value flag that'd work
> thus:
>
> [not set]: false
> --flag: true
> --flag=value: value
>
> Which would then let us do things like have:
>
> hg diff --git=no
>
> as a quick (and mostly obvious) way to disable git-diffs. What do you
> think? Even if you like that idea (I'll admit it's a bit wonky), I
> think we can still land this patch and then at a later date we can
> change --browser to work with no argument.

Last time I dabbled in this, the problem was with the way we parsed some
of the options in dispatch. mpm was the one that pointed out my error,
so hopefully he could chime in here.

Patch

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -6439,16 +6439,17 @@  def root(ui, repo):
 
     Returns 0 on success.
     """
     ui.write(repo.root + "\n")
 
 @command('^serve',
     [('A', 'accesslog', '', _('name of access log file to write to'),
      _('FILE')),
+    ('', 'browser', '', _('web browser to run')),
     ('d', 'daemon', None, _('run server in background')),
     ('', 'daemon-postexec', [], _('used internally by daemon mode')),
     ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
     # use string type, then we can check if something was passed
     ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
     ('a', 'address', '', _('address to listen on (default: all interfaces)'),
      _('ADDR')),
     ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
@@ -6485,22 +6486,31 @@  def serve(ui, repo, **opts):
     By default, the server logs accesses to stdout and errors to
     stderr. Use the -A/--accesslog and -E/--errorlog options to log to
     files.
 
     To have the server choose a free port number to listen on, specify
     a port number of 0; in this case, the server will print the port
     number it uses.
 
+    A web browser will be opened at the started server if the ``--browser``
+    argument contains the name of a browser to start. The special value
+    ``default`` will open the default browser as configured by your
+    operating system.
+
     Returns 0 on success.
     """
 
     if opts["stdio"] and opts["cmdserver"]:
         raise error.Abort(_("cannot use --stdio with --cmdserver"))
 
+    if (opts['stdio'] or opts['cmdserver']) and opts['browser']:
+        raise error.Abort(_('cannot use --browser with --cmdserver or '
+                            '--stdio'))
+
     if opts["stdio"]:
         if repo is None:
             raise error.RepoError(_("there is no Mercurial repository here"
                                     " (.hg not found)"))
         s = sshserver.sshserver(ui, repo)
         s.serve_forever()
 
     if opts["cmdserver"]:
diff --git a/mercurial/hgweb/__init__.py b/mercurial/hgweb/__init__.py
--- a/mercurial/hgweb/__init__.py
+++ b/mercurial/hgweb/__init__.py
@@ -4,16 +4,17 @@ 
 # Copyright 2005 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
 from __future__ import absolute_import
 
 import os
+import webbrowser
 
 from ..i18n import _
 
 from .. import (
     error,
     util,
 )
 
@@ -79,18 +80,41 @@  class httpservice(object):
         else:
             write = self.ui.write
         self.url = 'http://%s%s/%s' % (fqaddr, port, prefix)
         write(_('listening at %s (bound to %s:%d)\n') %
               (self.url, bindaddr, self.httpd.port))
         self.ui.flush()  # avoid buffering of status message
 
     def run(self):
+        self._maybestartbrowser()
         self.httpd.serve_forever()
 
+    def _maybestartbrowser(self):
+        browser = self.opts.get('browser')
+        if not browser:
+            return
+
+        # webbrowser.get() accepts empty value to indicate default browser.
+        if browser == 'default':
+            browser = None
+
+        try:
+            browser = webbrowser.get(browser)
+            # Text-mode browsers block the calling process, which interferes
+            # with our server.
+            if (not isinstance(browser, webbrowser.BackgroundBrowser) and
+                not getattr(browser, 'background', None)):
+                self.ui.warn(_('(cannot start text-mode browsers; '
+                               'continuing to start server)\n'))
+                return
+            browser.open_new_tab(self.url)
+        except webbrowser.Error as e:
+            self.ui.warn(_('(unable to start web browser: %s)\n') % e.args[0])
+
 def createservice(ui, repo, opts):
     # this way we can check if something was given in the command-line
     if opts.get('port'):
         opts['port'] = util.getport(opts.get('port'))
 
     alluis = set([ui])
     if repo:
         baseui = repo.baseui
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -149,16 +149,17 @@  Show the global options
   -q
   -v
   -y
 
 Show the options for the "serve" command
   $ hg debugcomplete --options serve | sort
   --accesslog
   --address
+  --browser
   --certificate
   --cmdserver
   --config
   --cwd
   --daemon
   --daemon-postexec
   --debug
   --debugger
@@ -214,17 +215,17 @@  Show all commands + options
   export: output, switch-parent, rev, text, git, nodates
   forget: include, exclude
   init: ssh, remotecmd, insecure
   log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
   merge: force, rev, preview, tool
   pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
   push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
   remove: after, force, subrepos, include, exclude
-  serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate
+  serve: accesslog, browser, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate
   status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos, template
   summary: remote
   update: clean, check, date, rev, tool
   addremove: similarity, subrepos, include, exclude, dry-run
   archive: no-decode, prefix, rev, type, subrepos, include, exclude
   backout: merge, commit, no-commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user
   bisect: reset, good, bad, skip, extend, command, noupdate
   bookmarks: force, rev, delete, rename, inactive, template