]> arthur.barton.de Git - bup.git/blob - cmd/split-cmd.py
Merge initial bup rm command
[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 (handle_ctrl_c, hostname, log, parse_num, qprogress,
12                          reprogress, saved_errors, userfullname, username)
13
14
15 optspec = """
16 bup split [-t] [-c] [-n name] OPTIONS [--git-ids | filenames...]
17 bup split -b OPTIONS [--git-ids | filenames...]
18 bup split <--noop [--copy]|--copy>  OPTIONS [--git-ids | filenames...]
19 --
20  Modes:
21 b,blobs    output a series of blob ids.  Implies --fanout=0.
22 t,tree     output a tree id
23 c,commit   output a commit id
24 n,name=    save the result under the given name
25 noop       split the input, but throw away the result
26 copy       split the input, copy it to stdout, don't save to repo
27  Options:
28 r,remote=  remote repository path
29 d,date=    date for the commit (seconds since the epoch)
30 q,quiet    don't print progress messages
31 v,verbose  increase log output (can be used more than once)
32 git-ids    read a list of git object ids from stdin and split their contents
33 keep-boundaries  don't let one chunk span two input files
34 bench      print benchmark timings to stderr
35 max-pack-size=  maximum bytes in a single pack
36 max-pack-objects=  maximum number of objects in a single pack
37 fanout=    average number of blobs in a single tree
38 bwlimit=   maximum bytes/sec to transmit to server
39 #,compress=  set compression level to # (0-9, 9 is highest) [1]
40 """
41 o = options.Options(optspec)
42 (opt, flags, extra) = o.parse(sys.argv[1:])
43
44 handle_ctrl_c()
45 git.check_repo_or_die()
46 if not (opt.blobs or opt.tree or opt.commit or opt.name or
47         opt.noop or opt.copy):
48     o.fatal("use one or more of -b, -t, -c, -n, --noop, --copy")
49 if (opt.noop or opt.copy) and (opt.blobs or opt.tree or
50                                opt.commit or opt.name):
51     o.fatal('--noop and --copy are incompatible with -b, -t, -c, -n')
52 if opt.blobs and (opt.tree or opt.commit or opt.name):
53     o.fatal('-b is incompatible with -t, -c, -n')
54 if extra and opt.git_ids:
55     o.fatal("don't provide filenames when using --git-ids")
56
57 if opt.verbose >= 2:
58     git.verbose = opt.verbose - 1
59     opt.bench = 1
60 if opt.max_pack_size:
61     git.max_pack_size = parse_num(opt.max_pack_size)
62 if opt.max_pack_objects:
63     git.max_pack_objects = parse_num(opt.max_pack_objects)
64 if opt.fanout:
65     hashsplit.fanout = parse_num(opt.fanout)
66 if opt.blobs:
67     hashsplit.fanout = 0
68 if opt.bwlimit:
69     client.bwlimit = parse_num(opt.bwlimit)
70 if opt.date:
71     date = parse_date_or_fatal(opt.date, o.fatal)
72 else:
73     date = time.time()
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)
98     oldref = refname and cli.read_ref(refname) or None
99     pack_writer = cli.new_packwriter(compression_level=opt.compress)
100 else:
101     cli = None
102     oldref = refname and git.read_ref(refname) or None
103     pack_writer = git.PackWriter(compression_level=opt.compress)
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, None)
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, None)  # skip the file type
131             except KeyError as 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     if opt.name: # insert dummy_name which may be used as a restore target
149         mode, sha = \
150             hashsplit.split_to_blob_or_tree(pack_writer.new_blob,
151                                             pack_writer.new_tree,
152                                             files,
153                                             keep_boundaries=opt.keep_boundaries,
154                                             progress=prog)
155         splitfile_name = git.mangle_name('data', hashsplit.GIT_MODE_FILE, mode)
156         shalist = [(mode, splitfile_name, sha)]
157     else:
158         shalist = hashsplit.split_to_shalist(
159                       pack_writer.new_blob, pack_writer.new_tree, files,
160                       keep_boundaries=opt.keep_boundaries, progress=prog)
161     tree = pack_writer.new_tree(shalist)
162 else:
163     last = 0
164     it = hashsplit.hashsplit_iter(files,
165                                   keep_boundaries=opt.keep_boundaries,
166                                   progress=prog)
167     for (blob, level) in it:
168         hashsplit.total_split += len(blob)
169         if opt.copy:
170             sys.stdout.write(str(blob))
171         megs = hashsplit.total_split/1024/1024
172         if not opt.quiet and last != megs:
173             last = megs
174
175 if opt.verbose:
176     log('\n')
177 if opt.tree:
178     print tree.encode('hex')
179 if opt.commit or opt.name:
180     msg = 'bup split\n\nGenerated by command:\n%r\n' % sys.argv
181     ref = opt.name and ('refs/heads/%s' % opt.name) or None
182     userline = '%s <%s@%s>' % (userfullname(), username(), hostname())
183     commit = pack_writer.new_commit(tree, oldref, userline, date, None,
184                                     userline, date, None, msg)
185     if opt.commit:
186         print commit.encode('hex')
187
188 if pack_writer:
189     pack_writer.close()  # must close before we can update the ref
190
191 if opt.name:
192     if cli:
193         cli.update_ref(refname, commit, oldref)
194     else:
195         git.update_ref(refname, commit, oldref)
196
197 if cli:
198     cli.close()
199
200 secs = time.time() - start_time
201 size = hashsplit.total_split
202 if opt.bench:
203     log('bup: %.2fkbytes in %.2f secs = %.2f kbytes/sec\n'
204         % (size/1024., secs, size/1024./secs))
205
206 if saved_errors:
207     log('WARNING: %d errors encountered while saving.\n' % len(saved_errors))
208     sys.exit(1)