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