By BoLOBOOLNE payday loans

Automation – saving 15 mins/day at a time

Posted: October 25th, 2011 | Filed under: Programming, Python
Author:
1 Comment →

So my wife laughs at me quite a bit, but I’m a little obsessed with efficiency and automation. I hate wasting my time on non-engaging tasks. Which is, as it happens, most of life. So I try to find ways to automate things. The less I have to invest my time in tasks with a clearly defined process the more time I can spend working on things I actually care about. At work I put this to the test constantly. Working as a software engineer there is just a pile of repetitive tasks that are ripe for automation. Fortunately a number of tools have entered the battlefield against wasting our time on processes that can be automated.

I love the growth and ever increasing adoption of continuous integration. At my new job I was thrilled to see how much they had already begun to move in that direction. It’s not perfect, that’s for sure, but we’ve got a solid start and we always keep our eyes out for ways to improve that process. That said, there’s still a lot of room for grabbing the low hanging fruit and Python/IronPython (two tools I’ve mentioned a number of times) come in to save the day for me.

One place I decided to tackle recently was our revision control. We use Hg which is a Python based tool itself, but they expressly discourage people from importing their libraries and using them, simply because they don’t guarantee they won’t change and therefore break everything. I had a short internal debate about if I thought it was worth it and eventually I opted against it, simply because it was just too easy to work around that issue (for my needs at least).

The problem came up that we have a pile of repositories that we all work in frequently. Keeping these repositories up to date was kind of a pain. That said the more familiar I get with the command line for Hg the easier, however I opted to write something with python to handle the task at hand. I came across this script which really accomplished what I needed, but I just wasn’t in love with the implementation. I wanted something a bit more flexible, I had in mind that I could use some of this for some tasks with our automated build too.

I started off by writing a simple “repo” class.

import os
PENDING_STATUS = ['A','M']
ERRORS = ['access denied', 'abort']

PULL = 'hg pull'
UPDATE = 'hg update'
STATUS = 'hg status'
TIP = 'hg tip'
NUDGE = 'hg push --rev .'

class repo:
    def __init__(self, path=''):
        self.path = path
        self.validdir = False

        if path:
            self.__validate_dir__()

    def __validate_dir__(self):
        if not os.path.exists(os.path.join(self.path,'.hg')):
            print self.path, 'is not an hg directory.'
            self.validdir = False
            return
        
	self.validdir = True

    def __haspending__(self, show=False):
        if not self.__prep__(): return False
        out = os.popen(STATUS)
        changes = [ln for ln in out if ln[0] in PENDING_STATUS]
        if show:
            for chg in changes: print chg

        return len(changes) > 0

    def __indir__(self):
        return os.getcwd() == self.path

    def __gotorepo__(self):
        if not self.validdir: return False
        if not self.__indir__():
            os.chdir(self.path)

        return True

    def __prep__(self):
        if not self.validdir: return False
        return self.__gotorepo__()

    def __checkerror__(self, output):
        for l in output:
            err = [e for e in ERRORS if e in l]
            if err:
                print err
                return True

        return False

    def setpath(self, path):
        self.path = path
        self.__validate_dir__()
    
    def issafe(self):
        if not self.__prep__(): return False
        if self.__haspending__(True):
            print 'Will not update. Pending changes found.'
            return False
        
        print 'No pending changes found... safe to update.'
        return True

    def getchg(self):
        if not self.validdir: return False
        changeset = [ln for ln in os.popen(TIP) if 'changeset:' in ln]
        return ln.split(':')[2]

    def anypending(self):
        return __safe_to_update__()

    def pull(self):
        if not self.__prep__(): return
	out = [ln for ln in os.popen(PULL)]
	if self.__checkerror__(out): return
	
	for ln in out:            
            if 'no changes found' in ln:
                print ln
                return False

        print 'pulled latest'
        return True
			
    def update(self, show=True):
	if not self.__prep__(): return
	out = [ln for ln in os.popen(UPDATE)]
	if self.__checkerror__(out): return
        for ln in out:
            if show: print ln
            
    def sync(self, update_if_safe = True):
        if not self.__prep__(): return
        
        print 'Getting latest for ' + os.getcwd()
        #only try to update if there were changes...
        if self.pull():
            if update_if_safe and self.issafe():
                print 'Updating...'
                self.update(False)
                
        print 'done'

    def nudge(self, show=True):
        if not self.__prep__(): return
        out = [ln for ln in os.popen(NUDGE)]
	if self.__checkerror__(out): return
        for ln in out:
            if show: print ln

It’s not the prettiest, in fact I’ll change it soon to have a ‘verbose’ property or something so I can ditch the “show=True” params from a number of those functions. The idea for the “nudge” function came from an hgtip.com post where they address what is honestly an annoying part of mercurial to me.

Once I had this repo class then I just needed a simple script to wrap around it. This is a bit closer to the script I mentioned earlier, but it’s got some options for command line params.

import os, hgutil, config, sys

def main(argv=None):
    if argv == None:
        argv = sys.argv

    arg_path = None
    # start at 1, 0 is the command
    for i in range(1, len(argv)):
        if argv[i] == '-p':
            arg_path = argv[i+1]
        if argv[i] == '-h':
            show_help()
            return True
        
    path = get_path(arg_path)

    if has_connection():
        for f in os.listdir(path):
            p = os.path.join(path,f)
            if os.path.isdir(p):
                repo = hgutil.repo(p)
                repo.sync()
    else:
        print 'No connection to hg found. Please ensure pageant is running.'
        return False

    return True

def has_connection():
    return len([ln for ln in os.popen('tasklist') if 'pageant' in ln]) > 0

def get_path(path=None):
    if path != None and os.path.exists(path):
        print 'starting from path:', path
        return path

    ##default
    print 'No valid path found... using default.'
    return config.get_home()

def show_help():
    print '--- sync.py ---'
    print 'Attempts to sync all hg repositories under a path.'
    print 'Returns errors if pageant is not running.'
    print 'Args:'
    print '-p : path to work from'
    print 'Example: sync.py -p c:\dev'
    print 'Done.'

if __name__ == "__main__":
    sys.exit(main())

Since we use pageant for our connections to mercurial it’s nice to have this be able to check and let me know in case I’ve forgotten to enable it.

Now that I had this full script, I create a windows scheduled task to run it behind the scenes for me every 10 mins. Now if I’m not actually working with a specific repository it just gets updated for me, and if I am, I pull the changes, but don’t update until I’m ready. This needs more work before it’s terribly useful on my automated build server, but the ground work is there and I’ve been really happy with it so far. It might not seem like it but this script saves me a solid 10-15 mins a day manually doing pulls and updates on my repos and a lot of help where I run into issues from running an older version of some code.

Now, it is possible that I’ll be working on something I don’t want automatically updated. Then I’ll just change it over to default to not update. The automated pull is still useful.

Tags: , , , , ,

One Comment on “Automation – saving 15 mins/day at a time”

  1. 1 Troy said at 2:33 pm on October 28th, 2011:

    I like your tack! I was toying with creating a repo class to deal with the changes. I really like the idea of running it as a background task :)

    Any how, I ended up completely rewriting it so that it checks the commit status of the repo before attempting to pull/push changes.

    http://bluebill.wordpress.com/2011/10/22/mercurial-pushpull-script-with-status-checking/

    cheers,
    Troy


Leave a Reply