]> arthur.barton.de Git - bup.git/blob - cmd/split-cmd.py
hashsplit.py: convert from 'bits' to 'level' earlier in the sequence.
[bup.git] / cmd / split-cmd.py
1 #!/usr/bin/env python
2 import sys, time
3 from bup import hashsplit, git, options, client
4 from bup.helpers import *
5
6
7 optspec = """
8 bup split [-tcb] [-n name] [--bench] [filenames...]
9 --
10 r,remote=  remote repository path
11 b,blobs    output a series of blob ids
12 t,tree     output a tree id
13 c,commit   output a commit id
14 n,name=    name of backup set to update (if any)
15 d,date=    date for the commit (seconds since the epoch)
16 q,quiet    don't print progress messages
17 v,verbose  increase log output (can be used more than once)
18 git-ids    read a list of git object ids from stdin and split their contents
19 keep-boundaries  don't let one chunk span two input files
20 noop       don't actually save the data anywhere
21 copy       just copy input to output, hashsplitting along the way
22 bench      print benchmark timings to stderr
23 max-pack-size=  maximum bytes in a single pack
24 max-pack-objects=  maximum number of objects in a single pack
25 fanout=    maximum number of blobs in a single tree
26 bwlimit=   maximum bytes/sec to transmit to server
27 """
28 o = options.Options(optspec)
29 (opt, flags, extra) = o.parse(sys.argv[1:])
30
31 handle_ctrl_c()
32 git.check_repo_or_die()
33 if not (opt.blobs or opt.tree or opt.commit or opt.name or
34         opt.noop or opt.copy):
35     o.fatal("use one or more of -b, -t, -c, -n, -N, --copy")
36 if (opt.noop or opt.copy) and (opt.blobs or opt.tree or 
37                                opt.commit or opt.name):
38     o.fatal('-N and --copy are incompatible with -b, -t, -c, -n')
39 if extra and opt.git_ids:
40     o.fatal("don't provide filenames when using --git-ids")
41
42 if opt.verbose >= 2:
43     git.verbose = opt.verbose - 1
44     opt.bench = 1
45 if opt.max_pack_size:
46     hashsplit.max_pack_size = parse_num(opt.max_pack_size)
47 if opt.max_pack_objects:
48     hashsplit.max_pack_objects = parse_num(opt.max_pack_objects)
49 if opt.fanout:
50     hashsplit.fanout = parse_num(opt.fanout)
51 if opt.blobs:
52     hashsplit.fanout = 0
53 if opt.bwlimit:
54     client.bwlimit = parse_num(opt.bwlimit)
55 if opt.date:
56     date = parse_date_or_fatal(opt.date, o.fatal)
57 else:
58     date = time.time()
59
60
61 total_bytes = 0
62 def prog(filenum, nbytes):
63     global total_bytes
64     total_bytes += nbytes
65     if filenum > 0:
66         qprogress('Splitting: file #%d, %d kbytes\r'
67                   % (filenum+1, total_bytes/1024))
68     else:
69         qprogress('Splitting: %d kbytes\r' % (total_bytes/1024))
70
71
72 is_reverse = os.environ.get('BUP_SERVER_REVERSE')
73 if is_reverse and opt.remote:
74     o.fatal("don't use -r in reverse mode; it's automatic")
75 start_time = time.time()
76
77 if opt.name and opt.name.startswith('.'):
78     o.fatal("'%s' is not a valid branch name." % opt.name)
79 refname = opt.name and 'refs/heads/%s' % opt.name or None
80 if opt.noop or opt.copy:
81     cli = pack_writer = oldref = None
82 elif opt.remote or is_reverse:
83     cli = client.Client(opt.remote)
84     oldref = refname and cli.read_ref(refname) or None
85     pack_writer = cli.new_packwriter()
86 else:
87     cli = None
88     oldref = refname and git.read_ref(refname) or None
89     pack_writer = git.PackWriter()
90
91 if opt.git_ids:
92     # the input is actually a series of git object ids that we should retrieve
93     # and split.
94     #
95     # This is a bit messy, but basically it converts from a series of
96     # CatPipe.get() iterators into a series of file-type objects.
97     # It would be less ugly if either CatPipe.get() returned a file-like object
98     # (not very efficient), or split_to_shalist() expected an iterator instead
99     # of a file.
100     cp = git.CatPipe()
101     class IterToFile:
102         def __init__(self, it):
103             self.it = iter(it)
104         def read(self, size):
105             v = next(self.it)
106             return v or ''
107     def read_ids():
108         while 1:
109             line = sys.stdin.readline()
110             if not line:
111                 break
112             if line:
113                 line = line.strip()
114             try:
115                 it = cp.get(line.strip())
116                 next(it)  # skip the file type
117             except KeyError, e:
118                 add_error('error: %s' % e)
119                 continue
120             yield IterToFile(it)
121     files = read_ids()
122 else:
123     # the input either comes from a series of files or from stdin.
124     files = extra and (open(fn) for fn in extra) or [sys.stdin]
125
126 if pack_writer:
127     shalist = hashsplit.split_to_shalist(pack_writer, files,
128                                          keep_boundaries=opt.keep_boundaries,
129                                          progress=prog)
130     tree = pack_writer.new_tree(shalist)
131 else:
132     last = 0
133     it = hashsplit.hashsplit_iter(files,
134                                   keep_boundaries=opt.keep_boundaries,
135                                   progress=prog)
136     for (blob, level) in it:
137         hashsplit.total_split += len(blob)
138         if opt.copy:
139             sys.stdout.write(str(blob))
140         megs = hashsplit.total_split/1024/1024
141         if not opt.quiet and last != megs:
142             progress('%d Mbytes read\r' % megs)
143             last = megs
144     progress('%d Mbytes read, done.\n' % megs)
145
146 if opt.verbose:
147     log('\n')
148 if opt.blobs:
149     for (mode,name,bin) in shalist:
150         print bin.encode('hex')
151 if opt.tree:
152     print tree.encode('hex')
153 if opt.commit or opt.name:
154     msg = 'bup split\n\nGenerated by command:\n%r' % sys.argv
155     ref = opt.name and ('refs/heads/%s' % opt.name) or None
156     commit = pack_writer.new_commit(oldref, tree, date, msg)
157     if opt.commit:
158         print commit.encode('hex')
159
160 if pack_writer:
161     pack_writer.close()  # must close before we can update the ref
162
163 if opt.name:
164     if cli:
165         cli.update_ref(refname, commit, oldref)
166     else:
167         git.update_ref(refname, commit, oldref)
168
169 if cli:
170     cli.close()
171
172 secs = time.time() - start_time
173 size = hashsplit.total_split
174 if opt.bench:
175     log('\nbup: %.2fkbytes in %.2f secs = %.2f kbytes/sec\n'
176         % (size/1024., secs, size/1024./secs))
177
178 if saved_errors:
179     log('WARNING: %d errors encountered while saving.\n' % len(saved_errors))
180     sys.exit(1)