]> arthur.barton.de Git - bup.git/blob - cmd/split-cmd.py
Move pwd grp functions to pwdgrp module; require binary fields
[bup.git] / cmd / split-cmd.py
1 #!/bin/sh
2 """": # -*-python-*-
3 bup_python="$(dirname "$0")/bup-python" || exit $?
4 exec "$bup_python" "$0" ${1+"$@"}
5 """
6 # end of bup preamble
7
8 from __future__ import absolute_import, division, print_function
9 import os, sys, time
10
11 from bup import hashsplit, git, options, client
12 from bup.helpers import (add_error, handle_ctrl_c, hostname, log, parse_num,
13                          qprogress, reprogress, saved_errors,
14                          valid_save_name,
15                          parse_date_or_fatal)
16 from bup.pwdgrp import userfullname, username
17
18
19 optspec = """
20 bup split [-t] [-c] [-n name] OPTIONS [--git-ids | filenames...]
21 bup split -b OPTIONS [--git-ids | filenames...]
22 bup split --copy OPTIONS [--git-ids | filenames...]
23 bup split --noop [-b|-t] OPTIONS [--git-ids | filenames...]
24 --
25  Modes:
26 b,blobs    output a series of blob ids.  Implies --fanout=0.
27 t,tree     output a tree id
28 c,commit   output a commit id
29 n,name=    save the result under the given name
30 noop       split the input, but throw away the result
31 copy       split the input, copy it to stdout, don't save to repo
32  Options:
33 r,remote=  remote repository path
34 d,date=    date for the commit (seconds since the epoch)
35 q,quiet    don't print progress messages
36 v,verbose  increase log output (can be used more than once)
37 git-ids    read a list of git object ids from stdin and split their contents
38 keep-boundaries  don't let one chunk span two input files
39 bench      print benchmark timings to stderr
40 max-pack-size=  maximum bytes in a single pack
41 max-pack-objects=  maximum number of objects in a single pack
42 fanout=    average number of blobs in a single tree
43 bwlimit=   maximum bytes/sec to transmit to server
44 #,compress=  set compression level to # (0-9, 9 is highest) [1]
45 """
46 handle_ctrl_c()
47
48 o = options.Options(optspec)
49 (opt, flags, extra) = o.parse(sys.argv[1:])
50
51 if not (opt.blobs or opt.tree or opt.commit or opt.name or
52         opt.noop or opt.copy):
53     o.fatal("use one or more of -b, -t, -c, -n, --noop, --copy")
54 if opt.copy and (opt.blobs or opt.tree):
55     o.fatal('--copy is incompatible with -b, -t')
56 if (opt.noop or opt.copy) and (opt.commit or opt.name):
57     o.fatal('--noop and --copy are incompatible with -c, -n')
58 if opt.blobs and (opt.tree or opt.commit or opt.name):
59     o.fatal('-b is incompatible with -t, -c, -n')
60 if extra and opt.git_ids:
61     o.fatal("don't provide filenames when using --git-ids")
62
63 if opt.verbose >= 2:
64     git.verbose = opt.verbose - 1
65     opt.bench = 1
66
67 max_pack_size = None
68 if opt.max_pack_size:
69     max_pack_size = parse_num(opt.max_pack_size)
70 max_pack_objects = None
71 if opt.max_pack_objects:
72     max_pack_objects = parse_num(opt.max_pack_objects)
73
74 if opt.fanout:
75     hashsplit.fanout = parse_num(opt.fanout)
76 if opt.blobs:
77     hashsplit.fanout = 0
78 if opt.bwlimit:
79     client.bwlimit = parse_num(opt.bwlimit)
80 if opt.date:
81     date = parse_date_or_fatal(opt.date, o.fatal)
82 else:
83     date = time.time()
84
85 total_bytes = 0
86 def prog(filenum, nbytes):
87     global total_bytes
88     total_bytes += nbytes
89     if filenum > 0:
90         qprogress('Splitting: file #%d, %d kbytes\r'
91                   % (filenum+1, total_bytes // 1024))
92     else:
93         qprogress('Splitting: %d kbytes\r' % (total_bytes // 1024))
94
95
96 is_reverse = os.environ.get('BUP_SERVER_REVERSE')
97 if is_reverse and opt.remote:
98     o.fatal("don't use -r in reverse mode; it's automatic")
99 start_time = time.time()
100
101 if opt.name and not valid_save_name(opt.name):
102     o.fatal("'%s' is not a valid branch name." % opt.name)
103 refname = opt.name and 'refs/heads/%s' % opt.name or None
104
105 if opt.noop or opt.copy:
106     cli = pack_writer = oldref = None
107 elif opt.remote or is_reverse:
108     git.check_repo_or_die()
109     cli = client.Client(opt.remote)
110     oldref = refname and cli.read_ref(refname) or None
111     pack_writer = cli.new_packwriter(compression_level=opt.compress,
112                                      max_pack_size=max_pack_size,
113                                      max_pack_objects=max_pack_objects)
114 else:
115     git.check_repo_or_die()
116     cli = None
117     oldref = refname and git.read_ref(refname) or None
118     pack_writer = git.PackWriter(compression_level=opt.compress,
119                                  max_pack_size=max_pack_size,
120                                  max_pack_objects=max_pack_objects)
121
122 if opt.git_ids:
123     # the input is actually a series of git object ids that we should retrieve
124     # and split.
125     #
126     # This is a bit messy, but basically it converts from a series of
127     # CatPipe.get() iterators into a series of file-type objects.
128     # It would be less ugly if either CatPipe.get() returned a file-like object
129     # (not very efficient), or split_to_shalist() expected an iterator instead
130     # of a file.
131     cp = git.CatPipe()
132     class IterToFile:
133         def __init__(self, it):
134             self.it = iter(it)
135         def read(self, size):
136             v = next(self.it, None)
137             return v or ''
138     def read_ids():
139         while 1:
140             line = sys.stdin.readline()
141             if not line:
142                 break
143             if line:
144                 line = line.strip()
145             try:
146                 it = cp.get(line.strip())
147                 next(it, None)  # skip the file info
148             except KeyError as e:
149                 add_error('error: %s' % e)
150                 continue
151             yield IterToFile(it)
152     files = read_ids()
153 else:
154     # the input either comes from a series of files or from stdin.
155     files = extra and (open(fn) for fn in extra) or [sys.stdin]
156
157 if pack_writer:
158     new_blob = pack_writer.new_blob
159     new_tree = pack_writer.new_tree
160 elif opt.blobs or opt.tree:
161     # --noop mode
162     new_blob = lambda content: git.calc_hash('blob', content)
163     new_tree = lambda shalist: git.calc_hash('tree', git.tree_encode(shalist))
164
165 if opt.blobs:
166     shalist = hashsplit.split_to_blobs(new_blob, files,
167                                        keep_boundaries=opt.keep_boundaries,
168                                        progress=prog)
169     for (sha, size, level) in shalist:
170         print(sha.encode('hex'))
171         reprogress()
172 elif opt.tree or opt.commit or opt.name:
173     if opt.name: # insert dummy_name which may be used as a restore target
174         mode, sha = \
175             hashsplit.split_to_blob_or_tree(new_blob, new_tree, files,
176                                             keep_boundaries=opt.keep_boundaries,
177                                             progress=prog)
178         splitfile_name = git.mangle_name('data', hashsplit.GIT_MODE_FILE, mode)
179         shalist = [(mode, splitfile_name, sha)]
180     else:
181         shalist = hashsplit.split_to_shalist(
182                       new_blob, new_tree, files,
183                       keep_boundaries=opt.keep_boundaries, progress=prog)
184     tree = new_tree(shalist)
185 else:
186     last = 0
187     it = hashsplit.hashsplit_iter(files,
188                                   keep_boundaries=opt.keep_boundaries,
189                                   progress=prog)
190     for (blob, level) in it:
191         hashsplit.total_split += len(blob)
192         if opt.copy:
193             sys.stdout.write(str(blob))
194         megs = hashsplit.total_split // 1024 // 1024
195         if not opt.quiet and last != megs:
196             last = megs
197
198 if opt.verbose:
199     log('\n')
200 if opt.tree:
201     print(tree.encode('hex'))
202 if opt.commit or opt.name:
203     msg = 'bup split\n\nGenerated by command:\n%r\n' % sys.argv
204     ref = opt.name and ('refs/heads/%s' % opt.name) or None
205     userline = '%s <%s@%s>' % (userfullname(), username(), hostname())
206     commit = pack_writer.new_commit(tree, oldref, userline, date, None,
207                                     userline, date, None, msg)
208     if opt.commit:
209         print(commit.encode('hex'))
210
211 if pack_writer:
212     pack_writer.close()  # must close before we can update the ref
213
214 if opt.name:
215     if cli:
216         cli.update_ref(refname, commit, oldref)
217     else:
218         git.update_ref(refname, commit, oldref)
219
220 if cli:
221     cli.close()
222
223 secs = time.time() - start_time
224 size = hashsplit.total_split
225 if opt.bench:
226     log('bup: %.2f kbytes in %.2f secs = %.2f kbytes/sec\n'
227         % (size / 1024, secs, size / 1024 / secs))
228
229 if saved_errors:
230     log('WARNING: %d errors encountered while saving.\n' % len(saved_errors))
231     sys.exit(1)