There are a has been a number of version control systems en vogue over time, CVS, SVN, Git etc.
I try to keep up with them, use them where possible, but don’t put EVERYTHING I do in version control. Since I am on a Mac running Leopard, I do have, and use Time Machine and so wanted to see if it would be pretty easy to use that to do some quick diffs between some source files.
The result is a quick and dirty python script. In the unlikely event that I ever have time, this would be a cool pyObjC project, a file browser panel, date versions picker, and a webkit view (with some better css).
The source to the script is below the fold – it will look for the first attached volume that has time machine backups for the current machine. It will not work with network based time machine backups that are on disk images. This script will run on a stock Leopard install without any extra python modules needed.
#!/usr/bin/env python
# encoding: utf-8
"""
Created by Preston Holmes on 2009-02-23.
Copyright (c) 2009 __MyCompanyName__. All rights reserved.
"""
import sys
import os
import getopt
import difflib
import time
import pdb
from subprocess import Popen, PIPE
# if you set time_machine_path, it should be to the full path of this machines backup drive:
# ie '/Volumes/TM_Drive/Backups.backupsdb/Joes-Mac/'
# if not set explicitly - the script will use the first TM drive it finds,
# and the first host folder it finds - which should work for most cases
# Will Not work with network or disk image based time machine backups
time_machine_path = None
cmd = 'tell application "Finder" to name of (path to startup disk)'
boot_volume = Popen('osascript -e \'%s\'' % cmd,shell=True,stdout=PIPE,stderr=PIPE).communicate()[0][0:-1]
help_message = '''
Call this script with the path to one or more text based files as arguments
'''
header = '''
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=ISO-8859-1" />
<title></title>
<style type="text/css">
table.diff {font-family:Courier; border:medium;}
.diff_header {background-color:#e0e0e0}
td.diff_header {text-align:right}
.diff_next {background-color:#c0c0c0}
.diff_add {background-color:#aaffaa}
.diff_chg {background-color:#ffff77}
.diff_sub {background-color:#ffaaaa}
</style>
</head>
<body>
'''
footer = '''
<table class="diff" summary="Legends">
<tr> <th colspan="2"> Legends </th> </tr>
<tr> <td> <table border="" summary="Colors">
<tr><th> Colors </th> </tr>
<tr><td class="diff_add"> Added </td></tr>
<tr><td class="diff_chg">Changed</td> </tr>
<tr><td class="diff_sub">Deleted</td> </tr>
</table></td>
<td> <table border="" summary="Links">
<tr><th colspan="2"> Links </th> </tr>
<tr><td>(f)irst change</td> </tr>
<tr><td>(n)ext change</td> </tr>
<tr><td>(t)op</td> </tr>
</table></td> </tr>
</table>
</body>
</html>
'''
class Usage(Exception):
def __init__(self, msg):
self.msg = msg
def find_versions(path):
#pdb.set_trace()
print 'looking for versions of %s' % path
#print time.strftime("%m/%d/%Y %I:%M:%S %p",time.localtime(os.path.getmtime(fname)))
backups = os.listdir(time_machine_path)
backups.sort()
pathlist = [os.path.join(time_machine_path,b,boot_volume,path[1:]) for b in backups]
pathlist.append(path)
versions = []
mod_times = []
for f in pathlist:
if os.path.exists(f):
mod_time = time.localtime(os.path.getmtime(f))
if not mod_time in mod_times:
mod_times.append(mod_time)
versions.append({mod_time:f})
return versions
def getTMLocation():
global time_machine_path
if time_machine_path and os.path.exists(time_machine_path):
return True
volumes = os.listdir('/Volumes')
#hostname = os.uname()[1].split('.')[0]
#cmd = 'scutil --get ComputerName'
#machine_name = Popen('osascript -e \'%s\'' % cmd,shell=True,stdout=PIPE,stderr=PIPE).communicate()[0][0:-1]
for v in volumes:
if os.path.exists(os.path.join('/Volumes',v,'Backups.backupdb')):
backupsdb = (os.path.join('/Volumes',v,'Backups.backupdb'))
time_machine_path = os.path.join(backupsdb,os.listdir(backupsdb)[0])
return True
# candidate_path = os.path.join('/Volumes',v,'Backups.backupdb',machine_name)
# if os.path.exists(candidate_path):
# time_machine_path = candidate_path
# return True
return False
def main(argv=None):
if not getTMLocation():
print 'No Time Macine Backup Found'
sys.exit()
print 'time machine path: ' + time_machine_path
if argv is None:
argv = sys.argv
try:
try:
opts, args = getopt.getopt(argv[1:], "ho:v", ["help", "output="])
except getopt.error, msg:
raise Usage(msg)
# option processing
for option, value in opts:
if option == "-v":
verbose = True
if option in ("-h", "–help"):
raise Usage(help_message)
if option in ("-o", "–output"):
output = value
if not args:
raise Usage(help_message)
differ = difflib.HtmlDiff(tabsize=4)
html = header
for path in args:
path = os.path.join(os.getcwd(),path)
html += '<h1>Changes for %s</h1' % os.path.basename(path)
versions = find_versions(path)
if len(versions) < 2:
html += '<h2>Less than 2 Versions found on Time Machine Backup</h2>'
else:
html += '<h2>%s versions found</h2>' % len(versions)
for i in range(0,len(versions)):
if i: #skip the first
d1 = time.strftime("%m/%d/%Y %I:%M:%S %p",versions[i-1].keys()[0])
d2 = time.strftime("%m/%d/%Y %I:%M:%S %p",versions[i].keys()[0])
html = html + '<h2>changes from %s to %s</h2>' % (d1,d2)
l1 = open(versions[i-1].values()[0]).readlines()
l2 = open(versions[i].values()[0]).readlines()
table = differ.make_table(l1,l2,context=True,numlines=3)
html += table
html += footer
o = open('/tmp/diff.html','w')
o.write(html)
o.close()
import webbrowser
webbrowser.open('/tmp/diff.html')
except Usage, err:
print >> sys.stderr, sys.argv[0].split("/")[-1] + ": " + str(err.msg)
print >> sys.stderr, "\t for help use –help"
return 2
if __name__ == "__main__":
sys.exit(main())
1 comment so far ↓
I really like the idea of this. Could post the code as an attachment or in a pre section so it’s easier to paste?
If you don’t mind it would also be nice to have an open source or CC license instead of “All rights reserved.”
Leave a Comment