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