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