[PATCH] hgext: add smb extension

Sean Farley sean at farley.io
Wed Apr 1 18:28:55 UTC 2015


# HG changeset patch
# User Sean Farley <sean at farley.io>
# Date 1427912211 25200
#      Wed Apr 01 11:16:51 2015 -0700
# Node ID 35cf765458f06a7651d0e9ba9340d46369741616
# Parent  1b97cc5d2272c272961cc3e1d738e521af012a40
hgext: add smb extension

This extension brings the missing component of DVCS to Mercurial: sound
effects. Who doesn't want the sound of a vine growing when they 'hg log' or the
sound of a warping down a pipe when updating to an ancestor? I'll tell you.
Nobody.

For reference and a screencast, the extension is hosted at:

https://bitbucket.org/seanfarley/smb

diff --git a/hgext/smb.py b/hgext/smb.py
new file mode 100644
--- /dev/null
+++ b/hgext/smb.py
@@ -0,0 +1,209 @@
+import inspect
+import os
+import subprocess
+
+from mercurial import _
+from mercurial import commands
+from mercurial import error
+from mercurial import extensions
+from mercurial import dispatch
+from mercurial import hg
+from mercurial import obsolete
+from mercurial import scmutil
+from mercurial import util
+
+######################
+# audio player methods
+######################
+
+def is_exe(fpath):
+    return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
+
+def which(program):
+    fpath, fname = os.path.split(program)
+    if fpath:
+        if is_exe(program):
+            return program
+    else:
+        for path in os.environ["PATH"].split(os.pathsep):
+            path = path.strip('"')
+            exe_file = os.path.join(path, program)
+            if is_exe(exe_file):
+                return exe_file
+
+    return None
+
+def audioplayer():
+    for p in ('afplay', 'aplay', 'mpg123'):
+        found = which(p)
+        if found is not None:
+            return found
+    return None
+
+def play(repo, sound):
+    if repo is None or not repo.ui.interactive():
+        return
+    player = audioplayer()
+    hasplayed = False
+    if os.path.exists(repo.join('smbrebasehack')):
+        os.remove(repo.join('smbrebasehack'))
+        hasplayed = True
+
+    if player and not hasplayed:
+        datapath = os.path.dirname(os.path.realpath(__file__))
+        datapath = os.path.join(datapath, 'data')
+        if not sound.endswith('.wav'):
+            sound = sound + '.wav'
+        subprocess.Popen([player, os.path.join(datapath, sound)],
+                         stdout=open(os.devnull, 'w'),
+                         stderr=subprocess.STDOUT)
+
+##############################
+# mercurial extension wrappers
+##############################
+
+def extsetup(ui):
+    extensions.wrapfunction(dispatch, 'runcommand', wrapruncommand)
+    extensions.wrapfunction(hg, 'updaterepo', wrapupdate)
+
+    extensions.wrapcommand(commands.table, 'log', wraplog)
+    extensions.wrapcommand(commands.table, 'commit', wrapcommit)
+
+    try:
+        rebase = extensions.find('rebase')
+        if rebase:
+            extensions.wrapcommand(rebase.cmdtable, 'rebase', wraprebase)
+    except:
+        pass
+
+    try:
+        histedit = extensions.find('histedit')
+        if histedit:
+            extensions.wrapfunction(histedit, 'finishfold', wrapfold)
+    except:
+        pass
+
+    try:
+        purge = extensions.find('purge')
+        if purge:
+            extensions.wrapcommand(purge.cmdtable, 'purge', wrappurge)
+    except:
+        pass
+
+    try:
+        evolve = extensions.find('evolve')
+        if evolve:
+            extensions.wrapcommand(evolve.cmdtable, 'prune', wrapprune)
+            extensions.wrapcommand(evolve.cmdtable, 'amend', wrapamend)
+    except:
+        pass
+
+def wrapruncommand(orig, lui, repo, cmd, fullargs, ui, options, d, cmdpats,
+                   cmdoptions):
+    try:
+        ret = orig(lui, repo, cmd, fullargs, ui, options, d, cmdpats,
+                   cmdoptions)
+        return ret
+    except Exception as e:
+        # things to skip:
+        # - skip if broken pipe (such as quiting from a pager)
+        # - histedit in progress
+        if ((not util.safehasattr(e, 'errno') or e.errno != 32)
+            and e.message != 'histedit in progress'
+            and e.message != 'rebase in progress'):
+            if isinstance(e, error.InterventionRequired):
+                play(repo, 'smb_pause')
+            else:
+                play(repo, 'smb_gameover')
+        raise
+
+def wrapupdate(orig, repo, node, overwrite):
+    # copied from merge.update which should be extracted
+    wc = repo[None]
+    p1 = wc.parents()[0]
+    if node is None:
+            try:
+                node = repo.branchtip(wc.branch())
+            except error.RepoLookupError:
+                if wc.branch() == 'default':
+                    node = repo.lookup('tip')
+                else:
+                    raise util.Abort(_("branch %s not found") % wc.branch())
+
+            if p1.obsolete() and not p1.children():
+                # allow updating to successors
+                successors = obsolete.successorssets(repo, p1.node())
+                if successors:
+                    successors = [n for sub in successors for n in sub]
+                    node = repo.revs('max(%ln)', successors).first()
+
+    current = repo['.']
+    dest = repo[node]
+
+    # things to skip:
+    # - if current == dest
+    # - if we are in histedit
+    # - if we are in a rebase
+    skip = False
+    for frame in inspect.stack():
+        for s in ['rebase', 'hist', 'prune']:
+            if s in frame[3]:
+                skip = s
+        if skip:
+            break
+
+    if current != dest and not skip:
+        # are we moving from an ancestor to a descendant?
+        if current.descendant(dest):
+            play(repo, 'smb_flagpole')
+        # or from a descendat to ancestor?
+        elif dest.descendant(current):
+            play(repo, 'smb_pipe')
+        # or jumping across branches?
+        else:
+            play(repo, 'smb_jump')
+    return orig(repo, node, overwrite)
+
+def wraprebase(orig, ui, repo, **opts):
+    ret = orig(ui, repo, **opts)
+    play(repo, 'smb_die')
+    f = repo.vfs('smbrebasehack', 'w')
+    f.write("True")
+    f.close()
+    return ret
+
+def wraplog(orig, ui, repo, *args, **opts):
+    # double check rev exists so we don't play two sounds
+    rev = opts.get('rev')
+    skip = False
+    if rev and not scmutil.revrange(repo, opts.get('rev')):
+        skip = True
+    if not skip:
+        play(repo, 'smb_vine')
+    return orig(ui, repo, *args, **opts)
+
+def wrapcommit(orig, ui, repo, *args, **opts):
+    if opts.get('amend'):
+        play(repo, 'smb_kick')
+    else:
+        play(repo, 'smb_1up')
+    return orig(ui, repo, *args, **opts)
+
+def wrapfold(orig, ui, repo, ctx, oldctx, newnode, opts, internalchanges):
+    play(repo, 'smb_stomp')
+    return orig(ui, repo, ctx, oldctx, newnode, opts, internalchanges)
+
+def wrappurge(orig, ui, repo, *dirs, **opts):
+    play(repo, 'smb_bowserfire')
+    return orig(ui, repo, *dirs, **opts)
+
+def wrapprune(orig, ui, repo, *revs, **opts):
+    if opts.get('succ'):
+        play(repo, 'smb_kick')
+    else:
+        play(repo, 'smb_fireball')
+    return orig(ui, repo, *revs, **opts)
+
+def wrapamend(orig, ui, repo, *pats, **opts):
+    play(repo, 'smb_kick')
+    return orig(ui, repo, *pats, **opts)


More information about the Mercurial-devel mailing list