#!/usr/bin/env python
import sys, re, errno, stat, client
import hashsplit, git, options, index
from helpers import *


saved_errors = []
def add_error(e):
    saved_errors.append(e)
    log('\n%s\n' % e)


optspec = """
bup save [-tc] [-n name] <filenames...>
--
r,remote=  remote repository path
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()
if not extra:
    log("bup save: no filenames given.\n")
    o.usage()

if opt.verbose >= 2:
    git.verbose = opt.verbose - 1
    hashsplit.split_verbosely = opt.verbose - 1

refname = opt.name and 'refs/heads/%s' % opt.name or None
if opt.remote:
    cli = client.Client(opt.remote)
    oldref = refname and cli.read_ref(refname) or None
    w = cli.new_packwriter()
else:
    cli = None
    oldref = refname and git.read_ref(refname) or None
    w = git.PackWriter()


def eatslash(dir):
    if dir.endswith('/'):
        return dir[:-1]
    else:
        return dir


parts = ['']
shalists = [[]]

def _push(part):
    assert(part)
    parts.append(part)
    shalists.append([])

def _pop():
    assert(len(parts) > 1)
    part = parts.pop()
    shalist = shalists.pop()
    tree = w.new_tree(shalist)
    shalists[-1].append(('40000', part, tree))


for (transname,ent) in index.Reader(git.repo('bupindex')).filter(extra):
    (dir, file) = os.path.split(ent.name)
    exists = (ent.flags & index.IX_EXISTS)
    hashvalid = (ent.flags & index.IX_HASHVALID) and w.exists(ent.sha)
    if opt.verbose:
        if not exists:
            status = 'D'
        elif not hashvalid:
            if ent.sha == index.EMPTY_SHA:
                status = 'A'
            else:
                status = 'M'
        else:
            status = ' '
        if opt.verbose >= 2 or (status in ['A','M'] 
                                and not stat.S_ISDIR(ent.mode)):
            log('\n%s %s ' % (status, ent.name))

    if not exists:
        continue

    assert(dir.startswith('/'))
    dirp = dir.split('/')
    while parts > dirp:
        _pop()
    if dir != '/':
        for part in dirp[len(parts):]:
            _push(part)

    if not file:
        # directory already handled.
        # FIXME: not using the indexed tree sha1's for anything, which is
        # a waste.  That's a potential optimization...
        continue  

    id = None
    if hashvalid:
        mode = '%o' % ent.mode
        id = ent.sha
        shalists[-1].append((mode, file, id))
    else:
        try:
            if stat.S_ISREG(ent.mode):
                f = open(ent.name)
                (mode, id) = hashsplit.split_to_blob_or_tree(w, [f])
            elif stat.S_ISDIR(ent.mode):
                assert(0)  # handled above
            elif stat.S_ISLNK(ent.mode):
                (mode, id) = ('120000', w.new_blob(os.readlink(ent.name)))
            else:
                add_error(Exception('skipping special file "%s"' % ent.name))
        except IOError, e:
            add_error(e)
        except OSError, e:
            add_error(e)
        if id:
            ent.validate(id)
            ent.repack()
            shalists[-1].append((mode, file, id))
#log('parts out: %r\n' % parts)
#log('stk out: %r\n' % shalists)
while len(parts) > 1:
    _pop()
#log('parts out: %r\n' % parts)
#log('stk out: %r\n' % shalists)
assert(len(shalists) == 1)
tree = w.new_tree(shalists[-1])
if opt.verbose:
    log('\n')
if opt.tree:
    print tree.encode('hex')
if opt.commit or opt.name:
    msg = 'bup save\n\nGenerated by command:\n%r' % sys.argv
    ref = opt.name and ('refs/heads/%s' % opt.name) or None
    commit = w.new_commit(oldref, tree, msg)
    if opt.commit:
        if opt.verbose:
            log('\n')
        print commit.encode('hex')

w.close()  # must close before we can update the ref
        
if opt.name:
    if cli:
        cli.update_ref(refname, commit, oldref)
    else:
        git.update_ref(refname, commit, oldref)

if cli:
    cli.close()

if saved_errors:
    log('WARNING: %d errors encountered while saving.\n' % len(saved_errors))

@@ -458,16 +458,67 @@
 n once)%0A
+smaller=   only back up files smaller than n bytes%0A
 %22%22%22%0Ao = 
@@ -2791,16 +2791,123 @@
 e, id))%0A
+    elif opt.smaller and ent.size %3E= opt.smaller:%0A        add_error('skipping large file %22%25s%22' %25 ent.name)%0A
     else

add --smaller option.