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