tadhg.com
tadhg.com
 

Python Script for Subversion Status

23:48 Sun 02 Aug 2009. Updated: 11:33 06 Apr 2010
[, , , , ]

I find in my use of Subversion that I often want to see a side-by-side list of files that aren’t under version control and files that have some other status. I also want these lists to be sorted alphabetically. Naturally, I ended up writing a Python script for this.

I suspect it could be cleaner, but it works and I’m trying to keep in mind that “the perfect is the enemy of the good”.

(Script updated 06 Apr 2010, and it now has a home at bitbucket).

#! /usr/bin/python2.6
# -*- coding: utf-8 -*-
# Displays added and modified files in two columns (terminal space permitting)

import subprocess

class SubversionSortedStatus(object):

    STATUS = "svn st"

    def cli_main(self):
        self.get_raw_st()
        self.list_unversioned()
        self.list_versioned()
        self.get_widths()
        print self.output_string()

    def get_raw_st(self):
        cmd_proc = subprocess.Popen(
            [self.STATUS], shell=True, stdout=subprocess.PIPE)
        self.raw_st = cmd_proc.communicate()[0] or u""

    def list_unversioned(self):
        self.unversioned = sorted([
            l[7:] for l in self.raw_st.split(u"\n") if l.startswith("?")
        ]) or ["No new files"]

    def list_versioned(self):
        def namesort(x, y): return cmp(x[6:], y[6:])
        self.versioned = sorted([
            l for l in self.raw_st.split(u"\n") if l and not l.startswith("?")
        ], cmp=namesort) or ["No modified files"]

    def get_widths(self):
        self.unversioned_width = self.get_files_width(self.unversioned)
        self.versioned_width = self.get_files_width(self.versioned)
        self.terminal_width = self.terminal_size()[0]
        self.terminal_midpoint = self.get_terminal_midpoint()

    def get_files_width(self, files):
        return max([len(line) for line in files]) if files else 0

    def get_terminal_midpoint(self):
        return self.unversioned_width + 1 if (self.terminal_width > (
                self.unversioned_width + self.versioned_width + 1
            )) else 0

    def output_string(self):
        if not self.terminal_midpoint:
            return u"{0}\n-----\n{1}".format(u"\n".join(self.unversioned),
                                             u"\n".join(self.versioned))

        ver, unv = self.versioned, self.unversioned
        def pad(l, maxlength): return l + ([""] * (maxlength - len(l)))
        def sep(mid, col_a): return u" " * (mid - len(col_a))
        def line(cols, mid):
            return u"{0}{1}{2}".format(cols[0], sep(mid, cols[0]), cols[1])
        maxlength = max(len(unv), len(ver))
        ver, unv = pad(ver, maxlength), pad(unv, maxlength)
        return u"\n".join(
            [line(cols, self.terminal_midpoint) for cols in zip(unv,ver)])

    """
    The next two functions are from Chuck Blake's categorized ls,
    found at http://pdos.csail.mit.edu/~cblake/cls/cls.py
    """

    def ioctl_GWINSZ(self, fd):                  #### TABULATION FUNCTIONS
        try:                                ### Discover terminal width
            import fcntl, termios, struct, os
            cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
        except:
            return None
        return cr

    def terminal_size(self):                    ### decide on *some* terminal size
        import os
        env = os.environ
        cr = self.ioctl_GWINSZ(0) or self.ioctl_GWINSZ(1) or self.ioctl_GWINSZ(2)  # try open fds
        if not cr:                                                  # ...then ctty
            try:
                fd = os.open(os.ctermid(), os.O_RDONLY)
                cr = self.ioctl_GWINSZ(fd)
                os.close(fd)
            except:
                pass
        if not cr:                            # env vars or finally defaults
            try:
                cr = (env['LINES'], env['COLUMNS'])
            except:
                cr = (25, 80)
        return int(cr[1]), int(cr[0])         # reverse rows, cols

if __name__ == '__main__':
    svnstatus = SubversionSortedStatus()
    svnstatus.cli_main()

Leave a Reply