-#!/usr/bin/env python
-import sys, re, errno
+#!/usr/bin/env python2.5
+import sys, re, errno, stat
import hashsplit, git, options
from helpers import *
-saved_errors = []
+saved_errors = []
def add_error(e):
saved_errors.append(e)
- log('%s\n' % e)
+ log('\n%s\n' % e)
+
+
+def _direxpand(name):
+ st = os.lstat(name)
+ try:
+ if stat.S_ISDIR(st.st_mode):
+ for sub in os.listdir(name):
+ subfull = os.path.join(name, sub)
+ for fn_st in _direxpand(subfull):
+ yield fn_st
+ else:
+ yield (name,st)
+ except OSError, e:
+ if e.errno in [errno.ENOENT, errno.EPERM, errno.EACCES]:
+ add_error(e)
+ else:
+ raise
def direxpand(names):
for n in names:
- log('%s\n' % n)
- try:
- for sub in os.listdir(n):
- subfull = os.path.join(n, sub)
- for sub2 in direxpand([subfull]):
- yield sub2
- except OSError, e:
- if e.errno == errno.ENOTDIR:
- yield n
- elif e.errno in [errno.ENOENT, errno.EPERM, errno.EACCES]:
- add_error(e)
- else:
- raise
+ for fn_st in _direxpand(n):
+ yield fn_st
+
+
+def _normpath(dir):
+ p = os.path.normpath(dir)
+ return (p != '.') and p or ''
+
+
+class Tree:
+ def __init__(self, parent, name):
+ assert(name != '.')
+ assert(not (parent and not name))
+ self.parent = parent
+ self.name = name
+ self.sha = None
+ self.children = {}
+ if self.parent:
+ self.parent.children[self.name] = self
+
+ def fullpath(self):
+ if self.parent:
+ return os.path.join(self.parent.fullpath(), self.name)
+ else:
+ return self.name
+
+ def gettop(self):
+ p = self
+ while p.parent:
+ p = p.parent
+ return p
+
+ def getdir(self, dir):
+ # FIXME: deal with '..' somehow (look at how tar does it)
+ dir = _normpath(dir)
+ if dir.startswith('/'):
+ dir = dir[1:]
+ top = self.gettop()
+ if not dir:
+ return top
+ for part in dir.split('/'):
+ sub = top.children.get(part)
+ if not sub:
+ sub = top.children[part] = Tree(top, part)
+ top = sub
+ return top
+
+ def addfile(self, mode, fullname, id):
+ (dir, name) = os.path.split(fullname)
+ self.getdir(dir).children[name] = (mode, name, id)
+
+ def shalist(self, w):
+ for c in self.children.values():
+ if isinstance(c, tuple): # sha1 entry for a file
+ yield c
+ else: # tree
+ t = ('40000', c.name, c.gen_tree(w))
+ yield t
+
+ def gen_tree(self, w):
+ if not self.sha:
+ self.sha = w.new_tree(self.shalist(w))
+ return self.sha
optspec = """
t,tree output a tree id
c,commit output a commit id
n,name= name of backup set to update (if any)
+v,verbose increase log output (can be used more than once)
"""
o = options.Options('bup save', optspec)
(opt, flags, extra) = o.parse(sys.argv[1:])
+git.check_repo_or_die()
if not (opt.tree or opt.commit or opt.name):
log("bup save: use one or more of -t, -c, -n\n")
o.usage()
-shalist = []
-for fn in direxpand(extra):
+if opt.verbose >= 2:
+ git.verbose = opt.verbose - 1
+ hashsplit.split_verbosely = opt.verbose - 1
+
+w = git.PackWriter()
+root = Tree(None, '')
+for (fn,st) in direxpand(extra):
+ if opt.verbose:
+ log('\n%s ' % fn)
try:
- # FIXME: symlinks etc.
- f = open(fn)
+ if stat.S_ISREG(st.st_mode): # regular file
+ f = open(fn)
+ (mode, id) = hashsplit.split_to_blob_or_tree(w, [f])
+ elif stat.S_ISLNK(st.st_mode): # symlink
+ (mode, id) = ('120000', w.new_blob(os.readlink(fn)))
+ else:
+ add_error(Exception('skipping special file "%s"' % fn))
except IOError, e:
add_error(e)
- continue
except OSError, e:
add_error(e)
- continue
- (mode, id) = hashsplit.split_to_blob_or_tree([f])
- shalist.append((mode, re.sub(r'/', '_', fn), id))
-tree = git.gen_tree(shalist)
+ else:
+ root.addfile(mode, fn, id)
+tree = root.gen_tree(w)
+if opt.verbose:
+ log('\n')
if opt.tree:
- print tree
+ print tree.encode('hex')
if opt.commit or opt.name:
- msg = 'Generated by command:\n%r' % sys.argv
+ msg = 'bup save\n\nGenerated by command:\n%r' % sys.argv
ref = opt.name and ('refs/heads/%s' % opt.name) or None
- commit = git.gen_commit_easy(ref, tree, msg)
+ commit = w.new_commit(ref, tree, msg)
if opt.commit:
- print commit
+ if opt.verbose:
+ log('\n')
+ print commit.encode('hex')
+
+w.close()
if saved_errors:
log('WARNING: %d errors encountered while saving.\n' % len(saved_errors))