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