]> arthur.barton.de Git - bup.git/blobdiff - cmd-save.py
executable files: don't assume python2.5.
[bup.git] / cmd-save.py
index 8b32e5e18b121cd70b57e76229fc186940bbfa41..8c7ec9ef7ca60c8c6f239d41c8ca4ad98e9a6648 100755 (executable)
 #!/usr/bin/env python
-import sys, re, errno
-import hashsplit, git, options
+import sys, re, errno, stat, client
+import hashsplit, git, options, index
 from helpers import *
 
-saved_errors = []
 
+saved_errors = []
 def add_error(e):
     saved_errors.append(e)
-    log('%s\n' % e)
-
-
-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
+    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()
 
-shalist = []
-for fn in direxpand(extra):
-    try:
-        # FIXME: symlinks etc.
-        f = open(fn)
-    except IOError, e:
-        add_error(e)
-        continue
-    except OSError, e:
-        add_error(e)
+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
-    (mode, id) = hashsplit.split_to_blob_or_tree([f])
-    shalist.append((mode, re.sub(r'/', '_', fn), id))
-tree = git.gen_tree(shalist)
+
+    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
+    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(oldref, tree, msg)
     if opt.commit:
-        print 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))