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