]> arthur.barton.de Git - bup.git/blob - cmd-save.py
This adds the long-awaited indexfile feature, so you no longer have to feed
[bup.git] / cmd-save.py
1 #!/usr/bin/env python2.5
2 import sys, re, errno, stat, client
3 import hashsplit, git, options, index
4 from helpers import *
5
6
7 saved_errors = []
8 def add_error(e):
9     saved_errors.append(e)
10     log('\n%s\n' % e)
11
12
13 optspec = """
14 bup save [-tc] [-n name] <filenames...>
15 --
16 r,remote=  remote repository path
17 t,tree     output a tree id
18 c,commit   output a commit id
19 n,name=    name of backup set to update (if any)
20 v,verbose  increase log output (can be used more than once)
21 """
22 o = options.Options('bup save', optspec)
23 (opt, flags, extra) = o.parse(sys.argv[1:])
24
25 git.check_repo_or_die()
26 if not (opt.tree or opt.commit or opt.name):
27     log("bup save: use one or more of -t, -c, -n\n")
28     o.usage()
29 if not extra:
30     log("bup save: no filenames given.\n")
31     o.usage()
32
33 if opt.verbose >= 2:
34     git.verbose = opt.verbose - 1
35     hashsplit.split_verbosely = opt.verbose - 1
36
37 refname = opt.name and 'refs/heads/%s' % opt.name or None
38 if opt.remote:
39     cli = client.Client(opt.remote)
40     oldref = refname and cli.read_ref(refname) or None
41     w = cli.new_packwriter()
42 else:
43     cli = None
44     oldref = refname and git.read_ref(refname) or None
45     w = git.PackWriter()
46
47
48 def eatslash(dir):
49     if dir.endswith('/'):
50         return dir[:-1]
51     else:
52         return dir
53
54
55 parts = ['']
56 shalists = [[]]
57
58 def _push(part):
59     parts.append(part)
60     shalists.append([])
61
62 def _pop():
63     assert(len(parts) > 1)
64     part = parts.pop()
65     shalist = shalists.pop()
66     tree = w.new_tree(shalist)
67     shalists[-1].append(('40000', part, tree))
68
69
70 for (transname,ent) in index.Reader(git.repo('bupindex')).filter(extra):
71     (dir, file) = os.path.split(ent.name)
72     exists = (ent.flags & index.IX_EXISTS)
73     hashvalid = (ent.flags & index.IX_HASHVALID) and w.exists(ent.sha)
74     if opt.verbose:
75         if not exists:
76             status = 'D'
77         elif not hashvalid:
78             if ent.sha == index.EMPTY_SHA:
79                 status = 'A'
80             else:
81                 status = 'M'
82         else:
83             status = ' '
84         log('\n%s %s ' % (status, ent.name))
85
86     if not exists:
87         continue
88
89     assert(dir.startswith('/'))
90     dirp = dir.split('/')
91     while parts > dirp:
92         _pop()
93     for part in dirp[len(parts):]:
94         _push(part)
95
96     if not file:
97         # directory already handled.
98         # FIXME: not using the indexed tree sha1's for anything, which is
99         # a waste.  That's a potential optimization...
100         continue  
101
102     id = None
103     if hashvalid:
104         mode = '%o' % ent.mode
105         id = ent.sha
106         shalists[-1].append((mode, file, id))
107     else:
108         try:
109             if stat.S_ISREG(ent.mode):
110                 f = open(ent.name)
111                 (mode, id) = hashsplit.split_to_blob_or_tree(w, [f])
112             elif stat.S_ISDIR(ent.mode):
113                 assert(0)  # handled above
114             elif stat.S_ISLNK(ent.mode):
115                 (mode, id) = ('120000', w.new_blob(os.readlink(ent.name)))
116             else:
117                 add_error(Exception('skipping special file "%s"' % ent.name))
118         except IOError, e:
119             add_error(e)
120         except OSError, e:
121             add_error(e)
122         if id:
123             ent.validate(id)
124             ent.repack()
125             shalists[-1].append((mode, file, id))
126 #log('parts out: %r\n' % parts)
127 #log('stk out: %r\n' % shalists)
128 while len(parts) > 1:
129     _pop()
130 #log('parts out: %r\n' % parts)
131 #log('stk out: %r\n' % shalists)
132 assert(len(shalists) == 1)
133 tree = w.new_tree(shalists[-1])
134 if opt.verbose:
135     log('\n')
136 if opt.tree:
137     print tree.encode('hex')
138 if opt.commit or opt.name:
139     msg = 'bup save\n\nGenerated by command:\n%r' % sys.argv
140     ref = opt.name and ('refs/heads/%s' % opt.name) or None
141     commit = w.new_commit(oldref, tree, msg)
142     if opt.commit:
143         if opt.verbose:
144             log('\n')
145         print commit.encode('hex')
146
147 w.close()  # must close before we can update the ref
148         
149 if opt.name:
150     if cli:
151         cli.update_ref(refname, commit, oldref)
152     else:
153         git.update_ref(refname, commit, oldref)
154
155 if cli:
156     cli.close()
157
158 if saved_errors:
159     log('WARNING: %d errors encountered while saving.\n' % len(saved_errors))