]> arthur.barton.de Git - bup.git/blob - cmd-save.py
cmd-margin: work correctly in python 2.4 when a midx is present.
[bup.git] / cmd-save.py
1 #!/usr/bin/env python
2 import sys, re, errno, stat, time, math
3 import hashsplit, git, options, index, client
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 lastremain = None
65 def progress_report(n):
66     global count, lastremain
67     count += n
68     pct = count*100.0/total
69     now = time.time()
70     elapsed = now - tstart
71     kps = elapsed and int(count/1024./elapsed)
72     kps_frac = 10 ** int(math.log(kps+1, 10) - 1)
73     kps = int(kps/kps_frac)*kps_frac
74     if count:
75         remain = elapsed*1.0/count * (total-count)
76     else:
77         remain = 0.0
78     if (lastremain and (remain > lastremain)
79           and ((remain - lastremain)/lastremain < 0.05)):
80         remain = lastremain
81     else:
82         lastremain = remain
83     hours = int(remain/60/60)
84     mins = int(remain/60 - hours*60)
85     secs = int(remain - hours*60*60 - mins*60)
86     if elapsed < 30:
87         remainstr = ''
88         kpsstr = ''
89     else:
90         kpsstr = '%dk/s' % kps
91         if hours:
92             remainstr = '%dh%dm' % (hours, mins)
93         elif mins:
94             remainstr = '%dm%d' % (mins, secs)
95         else:
96             remainstr = '%ds' % secs
97     progress('Saving: %.2f%% (%d/%dk, %d/%d files) %s %s     \r'
98              % (pct, count/1024, total/1024, fcount, ftotal,
99                 remainstr, kpsstr))
100
101
102 r = index.Reader(git.repo('bupindex'))
103
104 total = ftotal = 0
105 if opt.progress:
106     for (transname,ent) in r.filter(extra):
107         if not (ftotal % 10024):
108             progress('Reading index: %d\r' % ftotal)
109         exists = (ent.flags & index.IX_EXISTS)
110         hashvalid = (ent.flags & index.IX_HASHVALID) and w.exists(ent.sha)
111         if exists and not hashvalid:
112             total += ent.size
113         ftotal += 1
114     progress('Reading index: %d, done.\n' % ftotal)
115     hashsplit.progress_callback = progress_report
116
117 tstart = time.time()
118 count = fcount = 0
119 for (transname,ent) in r.filter(extra):
120     (dir, file) = os.path.split(ent.name)
121     exists = (ent.flags & index.IX_EXISTS)
122     hashvalid = (ent.flags & index.IX_HASHVALID) and w.exists(ent.sha)
123     if opt.verbose:
124         if not exists:
125             status = 'D'
126         elif not hashvalid:
127             if ent.sha == index.EMPTY_SHA:
128                 status = 'A'
129             else:
130                 status = 'M'
131         else:
132             status = ' '
133         if opt.verbose >= 2 or stat.S_ISDIR(ent.mode):
134             log('%s %-70s\n' % (status, ent.name))
135
136     if opt.progress:
137         progress_report(0)
138     fcount += 1
139     
140     if not exists:
141         continue
142
143     assert(dir.startswith('/'))
144     dirp = dir.split('/')
145     while parts > dirp:
146         _pop()
147     if dir != '/':
148         for part in dirp[len(parts):]:
149             _push(part)
150
151     if not file:
152         # directory already handled.
153         # FIXME: not using the indexed tree sha1's for anything, which is
154         # a waste.  That's a potential optimization...
155         count += ent.size
156         continue  
157
158     id = None
159     if hashvalid:
160         mode = '%o' % ent.mode
161         id = ent.sha
162         shalists[-1].append((mode, file, id))
163     elif opt.smaller and ent.size >= opt.smaller:
164         add_error('skipping large file "%s"' % ent.name)
165     else:
166         if stat.S_ISREG(ent.mode):
167             try:
168                 f = open(ent.name)
169             except IOError, e:
170                 add_error(e)
171             except OSError, e:
172                 add_error(e)
173             else:
174                 (mode, id) = hashsplit.split_to_blob_or_tree(w, [f])
175         else:
176             if stat.S_ISDIR(ent.mode):
177                 assert(0)  # handled above
178             elif stat.S_ISLNK(ent.mode):
179                 try:
180                     rl = os.readlink(ent.name)
181                 except OSError, e:
182                     add_error(e)
183                 except IOError, e:
184                     add_error(e)
185                 else:
186                     (mode, id) = ('120000', w.new_blob(rl))
187             else:
188                 add_error(Exception('skipping special file "%s"' % ent.name))
189             count += ent.size
190         if id:
191             ent.validate(id)
192             ent.repack()
193             shalists[-1].append((mode, file, id))
194
195 if opt.progress:
196     pct = total and count*100.0/total or 100
197     progress('Saving: %.2f%% (%d/%dk, %d/%d files), done.    \n'
198              % (pct, count/1024, total/1024, fcount, ftotal))
199
200 #log('parts out: %r\n' % parts)
201 #log('stk out: %r\n' % shalists)
202 while len(parts) > 1:
203     _pop()
204 #log('parts out: %r\n' % parts)
205 #log('stk out: %r\n' % shalists)
206 assert(len(shalists) == 1)
207 tree = w.new_tree(shalists[-1])
208 if opt.tree:
209     print tree.encode('hex')
210 if opt.commit or opt.name:
211     msg = 'bup save\n\nGenerated by command:\n%r' % sys.argv
212     ref = opt.name and ('refs/heads/%s' % opt.name) or None
213     commit = w.new_commit(oldref, tree, msg)
214     if opt.commit:
215         print commit.encode('hex')
216
217 w.close()  # must close before we can update the ref
218         
219 if opt.name:
220     if cli:
221         cli.update_ref(refname, commit, oldref)
222     else:
223         git.update_ref(refname, commit, oldref)
224
225 if cli:
226     cli.close()
227
228 if saved_errors:
229     log('WARNING: %d errors encountered while saving.\n' % len(saved_errors))