Python Script for Subversion Status
.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”.
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# Displays added and modified files in two columns (terminal space permitting)
import subprocess
import re
class SubversionSortedStatus(object):
STATUS_NEW = "svn st | grep '^\?'" #check for new files
STATUS_MODIFIED = "svn st | grep -v '^\?'" #check for modified files
def __init__(self):
newfiles = self.get_new_files()
newfiles_width = self.get_filename_width(newfiles)
modfiles = self.get_modified_files()
modfiles_width = self.get_filename_width(modfiles)
terminal_width = self.terminal_size()[0]
terminal_midpoint = self.get_midpoint(terminal_width, newfiles_width, modfiles_width)
self.print_status(terminal_midpoint, newfiles, modfiles)
def get_new_files(self):
#the shell command:
files = self.exec_shell_command(self.STATUS_NEW, stdout=subprocess.PIPE)
#get rid of the status character:
status_column = re.compile(r"^\?( +)(?=[^ ]*)")
lines = files.split("\n")
lines.sort()
replaced_lines = []
for line in lines:
line = status_column.sub("", line)
replaced_lines.append(line)
files = "\n".join(replaced_lines)
return files.strip()
def get_modified_files(self):
#the shell command:
files = self.exec_shell_command(self.STATUS_MODIFIED, stdout=subprocess.PIPE)
lines = files.split("\n")
#sort using only the non-status columns:
lines.sort(cmp = lambda x, y: cmp(x[6:], y[6:]))
files = "\n".join(lines)
return files.strip()
def get_filename_width(self, files):
width = 0
lines = files.split("\n")
for line in lines:
if len(line) > width:
width = len(line)
return width
def get_midpoint(self, width, newfiles_width, modfiles_width):
if width <= newfiles_width + modfiles_width + 1:
return 0
else:
return newfiles_width + 1
def print_status(self, midpoint, newfiles, modfiles):
if midpoint:
newlines = newfiles.split("\n")
modlines = modfiles.split("\n")
if len(newlines) > len(modlines):
maxlength = len(newlines)
else:
maxlength = len(modlines)
for i in range(0, maxlength):
if i < len(newlines):
newfile = newlines[i]
else:
newfile = ""
if i < len(modlines):
modfile = modlines[i]
else:
modfile = ""
separator = " " * ((midpoint - len(newfile)) + 1)
print "%s%s%s" % (newfile, separator, modfile)
else:
print newfiles
print "-----"
print modfiles
def exec_shell_command(self, command, stdout=None, wait=None, test_mode=False):
if test_mode:
print command
return command
output = ""
if stdout:
commandprocess = subprocess.Popen([command], shell=True, stdout=stdout)
output = commandprocess.communicate()[0]
else:
commandprocess = subprocess.Popen([command], shell=True)
commandprocess.wait()
if wait:
import time
time.sleep(wait)
return output
"""
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()