]> arthur.barton.de Git - bup.git/blob - cmd/bloom-cmd.py
47b4294ba7eec5fac62110b7a54a766c32886f9b
[bup.git] / cmd / bloom-cmd.py
1 #!/usr/bin/env python
2 import sys, glob, tempfile
3 from bup import options, git, bloom
4 from bup.helpers import *
5
6 optspec = """
7 bup bloom [options...]
8 --
9 f,force    ignore existing bloom file and regenerate it from scratch
10 o,output=  output bloom filename (default: auto)
11 d,dir=     input directory to look for idx files (default: auto)
12 k,hashes=  number of hash functions to use (4 or 5) (default: auto)
13 c,check=   check the given .idx file against the bloom filter
14 """
15
16 def check_bloom(path, bloomfilename, idx):
17     rbloomfilename = git.repo_rel(bloomfilename)
18     ridx = git.repo_rel(idx)
19     if not os.path.exists(bloomfilename):
20         log("bloom: %s: does not exist.\n" % rbloomfilename)
21         return
22     b = bloom.ShaBloom(bloomfilename)
23     if not b.valid():
24         add_error("bloom: %r is invalid.\n" % rbloomfilename)
25         return
26     base = os.path.basename(idx)
27     if base not in b.idxnames:
28         log("bloom: %s does not contain the idx.\n" % rbloomfilename)
29         return
30     if base == idx:
31         idx = os.path.join(path, idx)
32     log("bloom: bloom file: %s\n" % rbloomfilename)
33     log("bloom:   checking %s\n" % ridx)
34     for objsha in git.open_idx(idx):
35         if not b.exists(objsha):
36             add_error("bloom: ERROR: object %s missing" 
37                       % str(objsha).encode('hex'))
38
39
40 _first = None
41 def do_bloom(path, outfilename):
42     global _first
43     b = None
44     if os.path.exists(outfilename) and not opt.force:
45         b = bloom.ShaBloom(outfilename)
46         if not b.valid():
47             debug1("bloom: Existing invalid bloom found, regenerating.\n")
48             b = None
49
50     add = []
51     rest = []
52     add_count = 0
53     rest_count = 0
54     for i,name in enumerate(glob.glob('%s/*.idx' % path)):
55         progress('bloom: counting: %d\r' % i)
56         ix = git.open_idx(name)
57         ixbase = os.path.basename(name)
58         if b and (ixbase in b.idxnames):
59             rest.append(name)
60             rest_count += len(ix)
61         else:
62             add.append(name)
63             add_count += len(ix)
64     total = add_count + rest_count
65
66     if not add:
67         debug1("bloom: nothing to do.\n")
68         return
69
70     if b:
71         if len(b) != rest_count:
72             log("bloom: size %d != idx total %d, regenerating\n"
73                     % (len(b), rest_count))
74             b = None
75         elif (b.bits < bloom.MAX_BLOOM_BITS and
76               b.pfalse_positive(add_count) > bloom.MAX_PFALSE_POSITIVE):
77             log("bloom: regenerating: adding %d entries gives "
78                 "%.2f%% false positives.\n"
79                     % (add_count, b.pfalse_positive(add_count)))
80             b = None
81         else:
82             b = bloom.ShaBloom(outfilename, readwrite=True, expected=add_count)
83     if not b: # Need all idxs to build from scratch
84         add += rest
85         add_count += rest_count
86     del rest
87     del rest_count
88
89     msg = b is None and 'creating from' or 'adding'
90     if not _first: _first = path
91     dirprefix = (_first != path) and git.repo_rel(path)+': ' or ''
92     log('bloom: %s%s %d file%s (%d object%s).\n'
93         % (dirprefix, msg,
94            len(add), len(add)!=1 and 's' or '',
95            add_count, add_count!=1 and 's' or ''))
96
97     tfname = None
98     if b is None:
99         tfname = os.path.join(path, 'bup.tmp.bloom')
100         tf = open(tfname, 'w+')
101         b = bloom.create(tfname, f=tf, expected=add_count, k=opt.k)
102     count = 0
103     icount = 0
104     for name in add:
105         ix = git.open_idx(name)
106         qprogress('bloom: writing %.2f%% (%d/%d objects)\r' 
107                   % (icount*100.0/add_count, icount, add_count))
108         b.add_idx(ix)
109         count += 1
110         icount += len(ix)
111
112     if tfname:
113         os.rename(tfname, outfilename)
114
115
116 handle_ctrl_c()
117
118 o = options.Options(optspec)
119 (opt, flags, extra) = o.parse(sys.argv[1:])
120
121 if extra:
122     o.fatal('no positional parameters expected')
123
124 git.check_repo_or_die()
125
126 if not opt.check and opt.k and opt.k not in (4,5):
127     o.fatal('only k values of 4 and 5 are supported')
128
129 paths = opt.dir and [opt.dir] or git.all_packdirs()
130 for path in paths:
131     debug1('bloom: scanning %s\n' % path)
132     outfilename = opt.output or os.path.join(path, 'bup.bloom')
133     if opt.check:
134         check_bloom(path, outfilename, opt.check)
135     else:
136         do_bloom(path, outfilename)
137
138 if saved_errors:
139     log('WARNING: %d errors encountered during bloom.\n' % len(saved_errors))
140     sys.exit(1)
141 elif opt.check:
142     log('all tests passed.\n')