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