view gut/main.py @ 7:67ec22ce347c

add a delete command
author Jeff Hammel <jhammel@mozilla.com>
date Wed, 21 Jul 2010 16:23:43 -0700
parents ef895ddba2d3
children 0c0ade65b9f9
line wrap: on
line source

#!/usr/bin/env python
"""
a workflow for git
"""

import os
import subprocess
import sys

from command import CommandParser

def call(command, **kw):
    if isinstance(command, basestring):
        kw['shell'] = True
    output = kw.pop('output', True)
    if output or kw.pop('pipe', False):
        kw['stdout'] = subprocess.PIPE
        kw['stderr'] = subprocess.PIPE
    check = kw.pop('check', True)
    process = subprocess.Popen(command, **kw)
    stdout, stderr = process.communicate()
    code = process.poll()
    if check and code:
        if isinstance(command, basestring):
            cmdstr = command
        else:
            cmdstr = ' '.join(command)
        raise SystemExit("Command `%s` exited with code %d" % (cmdstr, code))
    if output:
        print stdout
        print stderr
    return dict(stdout=stdout, stderr=stderr, code=code)

def fake_call(command, **kw):
    if isinstance(command, basestring):
        print command
    else:
        print ' '.join(command)

class gut(object):
    """
    a workflow for git
    """

    def __init__(self, remote=None, simulate=False, branches=('master',)):
        """
        - remote: name of the remote repository in .git/config
        - simulate: print what calls will be used but don't run them
        - branches: branches to apply to
        """

        # sanity check
        try:
            self.root()
        except SystemExit:
            print "Not in a git repository"
            sys.exit(1)

        self.remote = remote
        self.simulate = simulate
        self.branches = branches
        if simulate:
            globals()['call'] = fake_call


    def update(self):
        """update the master"""
        branch = self.branch()
        if self.simulate:
            branch = '<branch>'
        call(['git', 'checkout', 'master'])
        call(['git', 'pull', 'origin', 'master'])
        if self.remote:
            call(['git', 'pull', self.remote, 'master'])
        call(['git', 'checkout', branch])

    def feature(self, name):
        """make a new feature branch"""
        call(['git', 'checkout', 'master'])
        call(['git', 'checkout', '-b', name])
        call(['git', 'push', 'origin', name])

    def patch(self, output=None, logfile=None):
        """
        generate a patch for review
        - output: name of patch
        - logfile: name of file to output the log to [DEFAULT: stdout]
        """
        self.update()
        diff = call(['git', 'diff', 'master'], pipe=True, output=False)
        log = call(['git', 'log', 'master..'], pipe=True, output=False)
        if self.simulate:
            return
        if not output:
            output = self.branch() + '.diff'
        diff = diff['stdout']
        log = log['stdout']

        # format the log
        lines = []
        oldline = None
        for line in log.splitlines():
            if not line:
                continue
            if line[0].strip():
                if oldline:
                    lines.append(oldline)
                    oldline = None
                incomment = False
                continue
            line = line.strip()
            if line.startswith('*'):
                if oldline:
                    lines.append(oldline)
                    oldline = None
                lines.append(line[1:].strip())
                continue
            if oldline:
                oldline = oldline + ' ' + line
            else:
                oldline = line
        else:
            if oldline:
                lines.append(oldline)
        log = '\n\n'.join(['* %s' % line for line in lines])
                
        f = file(output, 'w') # write the output to a patch file
        print >> f, diff
        f.close()

        # output the log
        if logfile:
            f = file(logfile, 'w') # write the log to a file
            print >> f, log
        else:
            return log

    def apply(self):
        """
        apply the existing feature branch to master as a patch
        """

        # sanity check
        branch = self.branch()
        assert branch != 'master', "Can't apply master to itself!"
        assert self.branches, "No branches defined!"

        # get the patch
        self.patch(branch + '.diff', branch + '.log')
        diff = os.path.abspath(branch + '.diff')
        log = os.path.abspath(branch + '.log')

        # apply the patch
        cwd = os.getcwd()
        os.chdir(self.root())
        for b in self.branches:
            call(['git', 'checkout', b])
            call(['patch -p1 < %s' % diff])
            call(['git', 'commit', '-a', '-F', log])
            call(['git', 'push', 'origin', b])
            if self.remote:
                call(['git', 'push', self.remote, b])

        # cleanup
        call(['git', 'checkout', branch])
        os.chdir(cwd)

    def delete(self):
        """delete the current feature branch"""
        # sanity check
        branch = self.branch()
        assert branch != 'master', "You can't delete the master!"
        call(['git', 'checkout', 'master'])
        call(['git', '-D', branch])

    def branch(self):
        """print what branch you're on"""
        output = call(['git', 'branch'], output=False, pipe=True)
        if self.simulate:
            return 
        for line in output['stdout'].splitlines():
            if line.startswith('*'):
                return line[1:].strip()

    def root(self):
        """return (relative) root location of repository"""
        
        output = call(['git', 'rev-parse', '--show-cdup'], output=False, pipe=True)
        location = output['stdout'].strip()
        if not location:
            return '.'
        return location

def main(args=sys.argv[1:]):
    parser = CommandParser(gut)
    parser.invoke(args)

if __name__ == '__main__':
    main()