Commits

Anonymous committed c274051

created a boundmode extension

  • Participants

Comments (0)

Files changed (7)

+syntax: glob
+*.pyc

File boundmode.py

+#!/usr/bin/env python
+
+# boundmode.py - bound mode (commit automatically pushes)
+#
+# Copyright 2009 Bill Barry <after.fallout@gmail.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+"""allows a repository to auto-push when you commit
+
+Using this extension you will have two modes of operation:
+1. unbound mode - repository behaves normally
+   (commit is local; push must be done manually)
+2. bound mode - commit automatically attempts push
+
+You enter bound mode with the command 'hg bind' and can exit with 'hg unbind'.
+
+This is a proof of concept extension. You can optionally configure repositories
+to be in bound mode automatically when cloning by adding a file called
+.hgboundrc to the root of your repository. This file must be an an ini file
+and can contain:
+[paths]
+auto-push = <auto-push path>
+[ui]
+bound = <False | True>
+
+to provide defaults for boundmode.
+"""
+
+from mercurial.i18n import _
+from mercurial import commands, cmdutil, extensions, hg, util
+import iniparse
+import os, sys
+
+class boundrc:
+    def parserc(self, fn):
+        if not os.path.exists(fn):
+            return None, None, None
+        ini = iniparse.INIConfig(file(fn))
+        boundpush, autopush, isbound = None, None, False
+        try:
+            boundpush = ini['paths']['bound-push']
+        except KeyError:
+            pass;
+        try:
+            autopush = ini['paths']['auto-push']
+        except KeyError:
+            pass;
+        try:
+            isbound = ini['ui']['bound']
+        except KeyError:
+            pass;
+        return boundpush, autopush, isbound
+
+    def __init__(self, ui, repo):
+        self.userrc = os.sep.join([repo.root, '.hg', 'boundrc'])
+        self.reporc = os.sep.join([repo.root, '.hgboundrc'])
+        self.repo = repo
+        self.ui = ui
+        b, a, i = self.parserc(self.reporc)
+        self.repoautopush = a
+        self.repoisbound = i
+        b, a, i = self.parserc(self.userrc)
+        self.userboundpush = b
+        self.userautopush = a
+        self.userisbound = i
+        
+    def isbound(self):
+        if self.userisbound != None:
+            return self.userisbound == 'True'
+        return self.repoisbound == 'True'
+        
+    def unbind(self):
+        self.userisbound = 'False'
+        self.userboundpush = None
+
+    def pushloc(self):
+        path = self.userautopush or self.repoautopush
+        if path:
+            return hg.parseurl(self.ui.expandpath(path, None), None)[0]
+        return None
+
+    def clearboundpush(self):
+        self.userboundpush = None
+
+    def saveitem(self, ini, section, name, value):
+        if value == None:
+            try:
+                del ini[section][name]
+            except KeyError:
+                return
+        else:
+            if section not in list(ini):
+                ini.new_namespace(section)
+            ini[section][name] = value
+
+    def saverc(self):
+        addline = False
+        fn = self.userrc
+        if not os.path.exists(fn):
+            f = open(fn, "w")
+            #apparently there is a bug in iniparse
+            #it doesn't like an empty file
+            f.write('\n')
+            f.close()
+            addline = True
+        ini = iniparse.INIConfig(file(fn))
+        self.saveitem(ini, 'paths', 'bound-push', self.userboundpush)
+        self.saveitem(ini, 'paths', 'auto-push', self.userautopush)
+        self.saveitem(ini, 'ui', 'bound', self.userisbound)
+        try:
+            f = open(fn, "w")
+            f.write(str(ini))
+            if addline: f.write('\n')
+            f.close()
+        except IOError:
+            return 1
+            
+    def bind(self):
+        self.userisbound = True
+
+    def setloc(self, name, auto):
+        if auto or (not self.userboundpush and not self.userautopush):
+            self.userautopush = name
+            self.userboundpush = None
+        else:
+            self.userboundpush = name
+
+def bind(ui, repo, name=None, **opts):
+    """enters bound mode"""
+    b = boundrc(ui, repo)
+    force = opts.get('force')
+    if b.isbound():
+        if not force:
+            raise util.Abort(_('repository is already bound'))
+    if name == None:
+        b.clearboundpush()
+        if b.pushloc() == None:
+            raise util.Abort(_(
+                'no auto-push path available, you must specify one'))
+    else:
+        b.setloc(name, opts.get('auto'))
+    b.bind()
+    b.saverc()
+    ui.note(_('now in bound mode\n'))
+
+def unbind(ui, repo, **opts):
+    """exits bound mode"""
+    b = boundrc(ui, repo)
+    if not b.isbound():
+        raise util.Abort(_('repository is not bound'))
+    b.unbind()
+    b.saverc()
+    ui.note(_('now in unbound mode\n'))
+
+def autopushwrapper(orig, ui, repo, *args, **opts):
+    """if in bound mode this will attempt a push after calling the wrapped
+       method
+    """
+    orig(ui, repo, *args, **opts)
+    b = boundrc(ui, repo)
+    if b.isbound():
+        ui.note(_('commit succeeded; attempting push\n'))
+        pushfunc = cmdutil.findcmd('push', commands.table)[1][0]
+        dest = b.pushloc()
+        pushfunc(ui, repo, dest, **opts)
+
+def extsetup():
+    'here we will wrap every command that commits in order to auto push'
+    'some example methods are record, import, qfinish, and qremove'
+    extensions.wrapcommand(commands.table, 'commit', autopushwrapper)
+
+cmdtable = {
+    'bind': (
+        bind, [
+            ('f', 'force', None, _('force bind even if already bound')),
+            ('a', 'auto', None, _('change auto-push path when binding'))
+            ],
+        _('hg bind [options] [name]')),
+
+    'unbind': (
+        unbind, [],
+        _('hg unbind'))
+}

File runtests.bat

+@echo off
+rem runtests.bat - command line test runner
+rem
+rem Copyright 2009 Bill Barry <after.fallout@gmail.com>
+rem
+rem This software may be used and distributed according to the terms
+rem of the GNU General Public License, incorporated herein by reference.
+
+rem This program will run all the test files in a tests subdirectory.
+rem   A test is a batch script in the form "testSSSS.bat" with an output file
+rem   in the form "testSSSS.out"
+rem
+rem   A test succeeds if it generates the expected output file.
+rem   If a test fails, you can rerun with -v to get diff output.
+rem
+rem   diff is required to run this and must be available in your path
+setlocal
+pushd tests
+
+if "%1" == "-v" set verbose=1
+
+set /A numtests= 0
+set /A numfails= 0
+set /A numpass= 0
+
+for %%t in ("test*.bat") do call :run %%~nt
+
+echo %numtests% tests taken
+echo %numpass% tests passed
+if %numfails% NEQ 0 echo %numfails% tests failed
+
+goto :end
+
+:run
+set /A numtests += 1
+call %1.bat > testoutput 2>&1
+diff -u %1.out testoutput > diffresults.txt 
+for %%R in ("diffresults.txt") do call :checksize %%~zR %1 
+erase testoutput
+erase diffresults.txt
+goto :eof
+
+:checksize
+if "%1" == "0" call :testpassed %2 
+if "%1" NEQ "0" call :testfailed %2
+goto :eof
+
+:testfailed
+echo failed %1
+set /A numfails += 1
+if "%verbose%" == "1" echo diff output:
+if "%verbose%" == "1" type diffresults.txt
+goto :eof
+
+:testpassed
+echo passed %1
+set /A numpass += 1
+goto :eof
+
+:end
+popd
+endlocal
+
+:eof
+echo on

File tests/test-bind.bat

+@echo off
+
+rem setup boundmode in a temp test directory
+setlocal
+cd ..
+cd > tmpfile
+set /p boundmodedir= < tmpfile
+erase tmpfile
+cd tests
+mkdir master
+cd master
+call hg init
+echo asfd > asdf.txt
+call hg add
+call hg ci -m "creating a first commit"
+cd .hg
+echo [extensions] >tmp
+echo BoundMode=%boundmodedir%\BoundMode.py >>tmp
+move tmp hgrc
+cd ..
+
+rem replace here with test content
+echo ### cd ..
+cd ..
+echo ### call hg clone master child1
+call hg clone master child1
+echo ### cd child1\.hg
+cd child1\.hg
+echo ### echo [extensions] ^>^>hgrc
+echo [extensions] >>hgrc
+echo ### echo BoundMode=%boundmodedir%\BoundMode.py ^>^>hgrc
+echo BoundMode=%boundmodedir%\BoundMode.py >>hgrc
+echo ### cd ..
+cd ..
+echo ### call hg help bind
+call hg help bind
+echo ### call hg bind
+call hg bind
+echo ### call hg bind default
+call hg bind default
+echo ### type .hg\boundrc
+type .hg\boundrc
+echo ### echo fdsa ^> fdsa.txt
+echo fdsa > fdsa.txt
+echo ### call hg add
+call hg add
+echo ### call hg ci -m "adding fdsa"
+call hg ci -m "adding fdsa"
+echo ### call hg outgoing
+call hg outgoing --template "I see a changeset: {rev}"
+echo ### call hg bind
+call hg bind
+echo ### call hg unbind
+call hg unbind
+echo ### type .hg\boundrc
+type .hg\boundrc
+echo ### call hg bind
+call hg bind
+echo ### type .hg\boundrc
+type .hg\boundrc
+echo ### call hg bind
+call hg bind
+echo ### type .hg\boundrc
+type .hg\boundrc
+echo ### call hg unbind
+call hg unbind
+echo ### type .hg\boundrc
+type .hg\boundrc
+echo ### call hg bind default
+call hg bind default
+echo ### type .hg\boundrc
+type .hg\boundrc
+echo ### call hg unbind
+call hg unbind
+echo ### type .hg\boundrc
+type .hg\boundrc
+echo ### call hg bind
+call hg bind
+echo ### type .hg\boundrc
+type .hg\boundrc
+echo ### call hg unbind
+call hg unbind
+echo ### type .hg\boundrc
+type .hg\boundrc
+echo ### call hg bind -a "..\master"
+call hg bind -a "..\master"
+echo ### type .hg\boundrc
+type .hg\boundrc
+echo ### call hg bind -f "default"
+call hg bind -f "default"
+echo ### type .hg\boundrc
+type .hg\boundrc
+echo ### call hg bind -a -f "default"
+call hg bind -a -f "default"
+echo ### type .hg\boundrc
+type .hg\boundrc
+echo ### copy .hg\boundrc .hgboundrc
+copy .hg\boundrc .hgboundrc
+echo ### call hg add
+call hg add
+echo ### call hg ci -m "adding hgboundrc"
+call hg ci -m "adding hgboundrc"
+echo ### cd ..
+cd ..
+echo ### call hg clone master child2
+call hg clone master child2
+echo ### copy /Y child1\.hg\hgrc child2\.hg\hgrc
+copy /Y child1\.hg\hgrc child2\.hg\hgrc
+echo ### cd child2
+cd child2
+echo ### type .hg\boundrc
+type .hg\boundrc
+echo ### call hg bind
+call hg bind
+echo ### call hg unbind
+call hg unbind
+echo ### type .hg\boundrc
+type .hg\boundrc
+echo ### call hg bind
+call hg bind
+echo ### type .hg\boundrc
+type .hg\boundrc
+rem end test content
+
+rem cleanup
+cd ..
+rmdir /S /Q child1
+rmdir /S /Q child2
+rmdir /S /Q master
+endlocal

File tests/test-bind.out

+ adding asdf.txt
+### cd ..
+### call hg clone master child1
+updating working directory
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+### cd child1\.hg
+### echo [extensions] >>hgrc
+### echo BoundMode=C:\hgwork\BoundMode\BoundMode\BoundMode.py >>hgrc
+### cd ..
+### call hg help bind
+hg bind [options] [name]
+
+enters bound mode
+
+options:
+
+ -f --force  force bind even if already bound
+ -a --auto   change auto-push path when binding
+
+use "hg -v help bind" to show global options
+### call hg bind
+abort: no auto-push path available, you must specify one
+### call hg bind default
+### type .hg\boundrc
+
+
+
+[paths]
+auto-push = default
+
+[ui]
+bound = True
+### echo fdsa > fdsa.txt
+### call hg add
+adding fdsa.txt
+### call hg ci -m "adding fdsa"
+pushing to c:\hgwork\BoundMode\BoundMode\tests\master
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+### call hg outgoing
+comparing with c:\hgwork\BoundMode\BoundMode\tests\master
+searching for changes
+no changes found
+### call hg bind
+abort: repository is already bound
+### call hg unbind
+### type .hg\boundrc
+
+
+
+[paths]
+auto-push = default
+
+[ui]
+bound = False
+### call hg bind
+### type .hg\boundrc
+
+
+
+[paths]
+auto-push = default
+
+[ui]
+bound = True
+### call hg bind
+abort: repository is already bound
+### type .hg\boundrc
+
+
+
+[paths]
+auto-push = default
+
+[ui]
+bound = True
+### call hg unbind
+### type .hg\boundrc
+
+
+
+[paths]
+auto-push = default
+
+[ui]
+bound = False
+### call hg bind default
+### type .hg\boundrc
+
+
+
+[paths]
+auto-push = default
+bound-push = default
+
+[ui]
+bound = True
+### call hg unbind
+### type .hg\boundrc
+
+
+
+[paths]
+auto-push = default
+
+[ui]
+bound = False
+### call hg bind
+### type .hg\boundrc
+
+
+
+[paths]
+auto-push = default
+
+[ui]
+bound = True
+### call hg unbind
+### type .hg\boundrc
+
+
+
+[paths]
+auto-push = default
+
+[ui]
+bound = False
+### call hg bind -a "..\master"
+### type .hg\boundrc
+
+
+
+[paths]
+auto-push = ..\master
+
+[ui]
+bound = True
+### call hg bind -f "default"
+### type .hg\boundrc
+
+
+
+[paths]
+auto-push = ..\master
+bound-push = default
+
+[ui]
+bound = True
+### call hg bind -a -f "default"
+### type .hg\boundrc
+
+
+
+[paths]
+auto-push = default
+
+[ui]
+bound = True
+### copy .hg\boundrc .hgboundrc
+        1 file(s) copied.
+### call hg add
+adding .hgboundrc
+### call hg ci -m "adding hgboundrc"
+pushing to c:\hgwork\BoundMode\BoundMode\tests\master
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+### cd ..
+### call hg clone master child2
+updating working directory
+3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+### copy /Y child1\.hg\hgrc child2\.hg\hgrc
+        1 file(s) copied.
+### cd child2
+### type .hg\boundrc
+The system cannot find the file specified.
+### call hg bind
+abort: repository is already bound
+### call hg unbind
+### type .hg\boundrc
+
+
+
+[ui]
+bound = False
+### call hg bind
+### type .hg\boundrc
+
+
+
+[ui]
+bound = True

File tests/test-boundmode.bat

+@echo off
+
+rem setup boundmode in a temp test directory
+setlocal
+cd ..
+cd > tmpfile
+set /p boundmodedir= < tmpfile
+erase tmpfile
+cd tests
+mkdir master
+cd master
+call hg init
+echo asfd > asdf.txt
+call hg add
+call hg ci -m "creating a first commit"
+cd .hg
+echo [extensions] >tmp
+echo boundmode=%boundmodedir%\boundmode.py >>tmp
+echo hgext.churn=! >>tmp
+echo hgext.extdiff=! >>tmp
+echo hgext.graphlog=! >>tmp
+echo hgext.hgconfig=! >>tmp
+echo hgext.mq=! >>tmp
+echo hgext.patchbomb=! >>tmp
+echo hgext.purge=! >>tmp
+echo hgext.qct=! >>tmp
+echo hgext.rebase=! >>tmp
+move tmp hgrc
+cd ..
+
+rem replace here with test content
+call hg help boundmode
+rem end test content
+
+rem cleanup
+cd ..
+rmdir /S /Q master
+endlocal

File tests/test-boundmode.out

+ adding asdf.txt
+boundmode extension - allows a repository to auto-push when you commit
+
+Using this extension you will have two modes of operation:
+1. unbound mode - repository behaves normally
+   (commit is local; push must be done manually)
+2. bound mode - commit automatically attempts push
+
+You enter bound mode with the command 'hg bind' and can exit with 'hg unbind'.
+
+This is a proof of concept extension. You can optionally configure repositories
+to be in bound mode automatically when cloning by adding a file called
+.hgboundrc to the root of your repository. This file must be an an ini file
+and can contain:
+[paths]
+auto-push = <auto-push path>
+[ui]
+bound = <False | True>
+
+to provide defaults for boundmode.
+
+list of commands:
+
+ bind     enters bound mode
+ unbind   exits bound mode
+
+enabled extensions:
+
+ boundmode   allows a repository to auto-push when you commit
+
+use "hg -v help boundmode" to show aliases and global options