#!/usr/bin/env python
#
#  svn-incremental-backup.py: incremental backups of Subversion
#    repository. Works with hot-backup.py to produce incremental
#    backups since the latest full backup produced by hot-backup
#
#  Dan R. K. Ports <drkp@mit.edu
#
#  Based on hot-backup.py 
#
#  Subversion is a tool for revision control. 
#  See http://subversion.tigris.org for more information.
#    
# ====================================================================
# Copyright (c) 2000-2004 CollabNet.  All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution.  The terms
# are also available at http://subversion.tigris.org/license-1.html.
# If newer versions of this license are posted there, you may use a
# newer version instead, at your option.
#
# This software consists of voluntary contributions made by many
# individuals.  For exact contribution history, see the revision
# history and logs, available at http://subversion.tigris.org/.
# ====================================================================

######################################################################

import sys, os, shutil, string, re

######################################################################
# Global Settings

# Path to svnlook utility
svnlook = "/usr/local/bin/svnlook"

# Path to svnadmin utility
svnadmin = "/usr/local/bin/svnadmin"


######################################################################
# Command line arguments

if len(sys.argv) != 4:
  print "Usage: ", os.path.basename(sys.argv[0]), " <repos_path> <backup_path> <incremental_path>"
  sys.exit(1)

# Path to repository
repo_dir = sys.argv[1]
repo = os.path.basename(os.path.abspath(repo_dir))

# Where to store the repository backup.  The backup will be placed in
# a *subdirectory* of this location, named after the youngest
# revision.
backup_dir = sys.argv[2]

# Where to store the incremental dumps.
incremental_dir = sys.argv[3]

######################################################################
# Helper functions

def comparator(a, b):
  # We pass in filenames so there is never a case where they are equal.
  regexp = re.compile("-(?P<revision>[0-9]+)(-(?P<increment>[0-9]+))?$")
  matcha = regexp.search(a)
  matchb = regexp.search(b)
  reva = int(matcha.groupdict()['revision'])
  revb = int(matchb.groupdict()['revision'])
  if (reva < revb):
    return -1
  elif (reva > revb):
    return 1
  else:
    inca = matcha.groupdict()['increment']
    incb = matchb.groupdict()['increment']
    if not inca:
      return -1
    elif not incb:
      return 1;
    elif (int(inca) < int(incb)):
      return -1
    else:
      return 1

######################################################################
# Main

print "Beginning incremental backup of '"+ repo_dir + "'."


### Step 1: get the youngest revision.

infile, outfile, errfile = os.popen3(svnlook + " youngest " + repo_dir)
stdout_lines = outfile.readlines()
stderr_lines = errfile.readlines()
outfile.close()
infile.close()
errfile.close()

youngest = string.strip(stdout_lines[0])
print "Youngest revision in repository is", youngest


### Step 2: Find youngest full backup

backup_subdir = os.path.join(backup_dir, repo + "-" + youngest)

directory_list = os.listdir(backup_dir)
directory_list.sort(comparator)
youngest_full = directory_list.pop();
regexp = re.compile("repos-(?P<revision>[0-9]+)(-(?P<increment>[0-9]+))?$")
youngest_full_revision = int(regexp.match(youngest_full).group('revision'))
print "Youngest full backup is " + str(youngest_full_revision)


### Step 3: Find youngest incremental backup
directory_list = os.listdir(incremental_dir)
regexp = re.compile("^[0-9]+" + 
                    "(-(?P<increment>[0-9]+))$")
incremental_list = filter(lambda x: regexp.search(x), directory_list)
if incremental_list:
  increments = [int(regexp.search(x).group('increment'))
                for x in incremental_list]
  increments.sort()
  youngest_increment = increments.pop()
  print "Youngest incremental is " + str(youngest_increment)
else:
  youngest_increment = youngest_full_revision
  print "No incrementals"
  

if youngest_increment == int(youngest):
  print "No new revisions to backup"
else:

### Step 4: Ask subversion to make an incremental copy

  outfilename = (incremental_dir + '/' + str(youngest_increment) + '-' +
                 str(youngest))
  print ("Running incremental backup from " + str(youngest_increment)
          + ", to " + outfilename)

  command = (svnadmin + " dump " + repo_dir + " --revision " +
             str(youngest_increment) + ":" + str(youngest) +
             " --incremental >" + outfilename)

  err_code = os.system(command)

  if(err_code != 0):
    print "Unable to backup the repository."
    sys.exit(err_code)
  else:
    print "Done."


### Step 5: finally, remove all incremental backups older than the
###         oldest full backup.

directory_list = os.listdir(backup_dir)
directory_list.sort(comparator)
oldest = directory_list[0];
regexp = re.compile("repos-(?P<revision>[0-9]+)(-(?P<increment>[0-9]+))?$")
oldest_full_revision = int(regexp.match(oldest).group('revision'))

directory_list = os.listdir(incremental_dir)
for incremental in directory_list:
  incremental_base = int(incremental.split("-")[0])
  if incremental_base < oldest_full_revision:
    path = incremental_dir + "/" + incremental
    print "Removing old incremental: " + path
    os.remove(path)
