2 from bup import options, drecurse
3 from bup.helpers import *
8 x,xdev,one-file-system don't cross filesystem boundaries
9 q,quiet don't actually print filenames
10 profile run under the python profiler
12 o = options.Options('bup drecurse', optspec)
13 (opt, flags, extra) = o.parse(sys.argv[1:])
16 o.fatal("exactly one filename expected")
18 it = drecurse.recursive_dirlist(extra, opt.xdev)
24 cProfile.run('do_it()')
34 log('WARNING: %d errors encountered.\n' % len(saved_errors))
37 import sys, time, struct
38 from bup import hashsplit, git, options, client
39 from bup.helpers import *
40 from subprocess import PIPE
44 bup split [-tcb] [-n name] [--bench] [filenames...]
46 r,remote= remote repository path
47 b,blobs output a series of blob ids
48 t,tree output a tree id
49 c,commit output a commit id
50 n,name= name of backup set to update (if any)
51 N,noop don't actually save the data anywhere
52 q,quiet don't print progress messages
53 v,verbose increase log output (can be used more than once)
54 copy just copy input to output, hashsplitting along the way
55 bench print benchmark timings to stderr
56 max-pack-size= maximum bytes in a single pack
57 max-pack-objects= maximum number of objects in a single pack
58 fanout= maximum number of blobs in a single tree
60 o = options.Options('bup split', optspec)
61 (opt, flags, extra) = o.parse(sys.argv[1:])
63 git.check_repo_or_die()
64 if not (opt.blobs or opt.tree or opt.commit or opt.name or
65 opt.noop or opt.copy):
66 o.fatal("use one or more of -b, -t, -c, -n, -N, --copy")
67 if (opt.noop or opt.copy) and (opt.blobs or opt.tree or
68 opt.commit or opt.name):
69 o.fatal('-N is incompatible with -b, -t, -c, -n')
72 git.verbose = opt.verbose - 1
75 hashsplit.max_pack_size = parse_num(opt.max_pack_size)
76 if opt.max_pack_objects:
77 hashsplit.max_pack_objects = parse_num(opt.max_pack_objects)
79 hashsplit.fanout = parse_num(opt.fanout)
83 is_reverse = os.environ.get('BUP_SERVER_REVERSE')
84 if is_reverse and opt.remote:
85 o.fatal("don't use -r in reverse mode; it's automatic")
86 start_time = time.time()
88 refname = opt.name and 'refs/heads/%s' % opt.name or None
89 if opt.noop or opt.copy:
90 cli = w = oldref = None
91 elif opt.remote or is_reverse:
92 cli = client.Client(opt.remote)
93 oldref = refname and cli.read_ref(refname) or None
94 w = cli.new_packwriter()
97 oldref = refname and git.read_ref(refname) or None
100 files = extra and (open(fn) for fn in extra) or [sys.stdin]
102 shalist = hashsplit.split_to_shalist(w, files)
103 tree = w.new_tree(shalist)
106 for (blob, bits) in hashsplit.hashsplit_iter(files):
107 hashsplit.total_split += len(blob)
109 sys.stdout.write(str(blob))
110 megs = hashsplit.total_split/1024/1024
111 if not opt.quiet and last != megs:
112 progress('%d Mbytes read\r' % megs)
114 progress('%d Mbytes read, done.\n' % megs)
119 for (mode,name,bin) in shalist:
120 print bin.encode('hex')
122 print tree.encode('hex')
123 if opt.commit or opt.name:
124 msg = 'bup split\n\nGenerated by command:\n%r' % sys.argv
125 ref = opt.name and ('refs/heads/%s' % opt.name) or None
126 commit = w.new_commit(oldref, tree, msg)
128 print commit.encode('hex')
131 w.cjon is changing some random bytes here and theref
135 cli.update_ref(refname, commit, oldref)
137 git.update_ref(refname, commit, oldref)
142 secs = time.time() - start_time
143 size = hashsplit.total_split
145 log('\nbup: %.2fkbytes in %.2f secs = %.2f kbytes/sec\n'
146 % (size/1024., secs, size/1024./secs))
147 #!/usr/bin/env python
148 import sys, re, struct, mmap
149 from bup import git, options
150 from bup.helpers import *
153 def s_from_bytes(bytes):
154 clist = [chr(b) for b in bytes]
155 return ''.join(clist)
159 fields = ['VmSize', 'VmRSS', 'VmData', 'VmStk']
161 for line in open('/proc/self/status').readlines():
162 l = re.split(r':\s*', line.strip(), 1)
166 fields = [d[k] for k in fields]
169 print ('%9s ' + ('%10s ' * len(fields))) % tuple([e1] + fields)
174 bup memtest [-n elements] [-c cycles]
176 n,number= number of objects per cycle
177 c,cycles= number of cycles to run
178 ignore-midx ignore .midx files, use only .idx files
180 o = options.Options('bup memtest', optspec)
181 (opt, flags, extra) = o.parse(sys.argv[1:])
184 o.fatal('no arguments expected')
186 git.ignore_midx = opt.ignore_midx
188 git.check_repo_or_die()
189 m = git.PackIdxList(git.repo('objects/pack'))
191 cycles = opt.cycles or 100
192 number = opt.number or 10000
195 f = open('/dev/urandom')
196 a = mmap.mmap(-1, 20)
198 for c in xrange(cycles):
199 for n in xrange(number):
202 bytes = list(struct.unpack('!BBB', b)) + [0]*17
204 bin = struct.pack('!20s', s_from_bytes(bytes))
207 a[2] = chr(ord(b[2]) & 0xf0)
209 #print bin.encode('hex')
212 #!/usr/bin/env python
214 from bup import options, git, vfs
215 from bup.helpers import *
217 def print_node(text, n):
220 prefix += "%s " % n.hash.encode('hex')
221 if stat.S_ISDIR(n.mode):
222 print '%s%s/' % (prefix, text)
223 elif stat.S_ISLNK(n.mode):
224 print '%s%s@' % (prefix, text)
226 print '%s%s' % (prefix, text)
232 s,hash show hash for each file
234 o = options.Options('bup ls', optspec)
235 (opt, flags, extra) = o.parse(sys.argv[1:])
237 git.check_repo_or_die()
238 top = vfs.RefList(None)
247 if stat.S_ISDIR(n.mode):
249 print_node(sub.name, sub)
252 except vfs.NodeError, e:
253 log('error: %s\n' % e)
257 #!/usr/bin/env python
258 import sys, os, re, stat, readline, fnmatch
259 from bup import options, git, shquote, vfs
260 from bup.helpers import *
262 def node_name(text, n):
263 if stat.S_ISDIR(n.mode):
265 elif stat.S_ISLNK(n.mode):
273 if stat.S_ISDIR(n.mode):
275 l.append(node_name(sub.name, sub))
277 l.append(node_name(path, n))
278 print columnate(l, '')
281 def write_to_file(inf, outf):
282 for blob in chunkyreader(inf):
287 if os.isatty(sys.stdin.fileno()):
290 yield raw_input('bup> ')
294 for line in sys.stdin:
298 def _completer_get_subs(line):
299 (qtype, lastword) = shquote.unfinished_word(line)
300 (dir,name) = os.path.split(lastword)
301 #log('\ncompleter: %r %r %r\n' % (qtype, lastword, text))
303 subs = list(filter(lambda x: x.name.startswith(name),
305 return (dir, name, qtype, lastword, subs)
310 def completer(text, state):
314 line = readline.get_line_buffer()[:readline.get_endidx()]
315 if _last_line != line:
316 _last_res = _completer_get_subs(line)
318 (dir, name, qtype, lastword, subs) = _last_res
319 if state < len(subs):
321 sn1 = sn.resolve('') # deref symlinks
322 fullname = os.path.join(dir, sn.name)
323 if stat.S_ISDIR(sn1.mode):
324 ret = shquote.what_to_add(qtype, lastword, fullname+'/',
327 ret = shquote.what_to_add(qtype, lastword, fullname,
328 terminate=True) + ' '
331 log('\nerror in completion: %s\n' % e)
337 o = options.Options('bup ftp', optspec)
338 (opt, flags, extra) = o.parse(sys.argv[1:])
340 git.check_repo_or_die()
342 top = vfs.RefList(None)
348 readline.set_completer_delims(' \t\n\r/')
349 readline.set_completer(completer)
350 readline.parse_and_bind("tab: complete")
356 words = [word for (wordstart,word) in shquote.quotesplit(line)]
357 cmd = words[0].lower()
358 #log('execute: %r %r\n' % (cmd, parm))
361 for parm in (words[1:] or ['.']):
362 do_ls(parm, pwd.resolve(parm))
364 for parm in words[1:]:
365 pwd = pwd.resolve(parm)
369 for parm in words[1:]:
370 write_to_file(pwd.resolve(parm).open(), sys.stdout)
372 if len(words) not in [2,3]:
373 raise Exception('Usage: get <filename> [localname]')
375 (dir,base) = os.path.split(rname)
376 lname = len(words)>2 and words[2] or base
377 inf = pwd.resolve(rname).open()
378 log('Saving %r\n' % lname)
379 write_to_file(inf, open(lname, 'wb'))
381 for parm in words[1:]:
382 (dir,base) = os.path.split(parm)
383 for n in pwd.resolve(dir).subs():
384 if fnmatch.fnmatch(n.name, base):
386 log('Saving %r\n' % n.name)
388 outf = open(n.name, 'wb')
389 write_to_file(inf, outf)
392 log(' error: %s\n' % e)
393 elif cmd == 'help' or cmd == '?':
394 log('Commands: ls cd pwd cat get mget help quit\n')
395 elif cmd == 'quit' or cmd == 'exit' or cmd == 'bye':
398 raise Exception('no such command %r' % cmd)
400 log('error: %s\n' % e)
402 #!/usr/bin/env python
404 from bup import options, _hashsplit
405 from bup.helpers import *
408 bup random [-S seed] <numbytes>
410 S,seed= optional random number seed (default 1)
411 f,force print random data to stdout even if it's a tty
413 o = options.Options('bup random', optspec)
414 (opt, flags, extra) = o.parse(sys.argv[1:])
417 o.fatal("exactly one argument expected")
419 total = parse_num(extra[0])
421 if opt.force or (not os.isatty(1) and
422 not atoi(os.environ.get('BUP_FORCE_TTY')) & 1):
423 _hashsplit.write_random(sys.stdout.fileno(), total, opt.seed or 0)
425 log('error: not writing binary data to a terminal. Use -f to force.\n')
427 #!/usr/bin/env python
429 from bup import options
434 o = options.Options('bup help', optspec)
435 (opt, flags, extra) = o.parse(sys.argv[1:])
438 # the wrapper program provides the default usage string
439 os.execvp(os.environ['BUP_MAIN_EXE'], ['bup'])
440 elif len(extra) == 1:
441 docname = (extra[0]=='bup' and 'bup' or ('bup-%s' % extra[0]))
443 (exepath, exefile) = os.path.split(exe)
444 manpath = os.path.join(exepath, '../Documentation/' + docname + '.[1-9]')
445 g = glob.glob(manpath)
447 os.execvp('man', ['man', '-l', g[0]])
449 os.execvp('man', ['man', docname])
451 o.fatal("exactly one command name expected")
452 #!/usr/bin/env python
453 import sys, os, stat, errno, fuse, re, time, tempfile
454 from bup import options, git, vfs
455 from bup.helpers import *
458 class Stat(fuse.Stat):
476 def cache_get(top, path):
477 parts = path.split('/')
481 #log('cache: %r\n' % cache.keys())
484 #log('cache trying: %r\n' % pre)
485 c = cache.get(tuple(pre))
489 #log('resolving %r from %r\n' % (r, c.fullname()))
491 key = tuple(pre + [r])
492 #log('saving: %r\n' % (key,))
500 class BupFs(fuse.Fuse):
501 def __init__(self, top):
502 fuse.Fuse.__init__(self)
505 def getattr(self, path):
506 log('--getattr(%r)\n' % path)
508 node = cache_get(self.top, path)
510 st.st_mode = node.mode
511 st.st_nlink = node.nlinks()
512 st.st_size = node.size()
513 st.st_mtime = node.mtime
514 st.st_ctime = node.ctime
515 st.st_atime = node.atime
517 except vfs.NoSuchFile:
520 def readdir(self, path, offset):
521 log('--readdir(%r)\n' % path)
522 node = cache_get(self.top, path)
523 yield fuse.Direntry('.')
524 yield fuse.Direntry('..')
525 for sub in node.subs():
526 yield fuse.Direntry(sub.name)
528 def readlink(self, path):
529 log('--readlink(%r)\n' % path)
530 node = cache_get(self.top, path)
531 return node.readlink()
533 def open(self, path, flags):
534 log('--open(%r)\n' % path)
535 node = cache_get(self.top, path)
536 accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
537 if (flags & accmode) != os.O_RDONLY:
541 def release(self, path, flags):
542 log('--release(%r)\n' % path)
544 def read(self, path, size, offset):
545 log('--read(%r)\n' % path)
546 n = cache_get(self.top, path)
552 if not hasattr(fuse, '__version__'):
553 raise RuntimeError, "your fuse module is too old for fuse.__version__"
554 fuse.fuse_python_api = (0, 2)
558 bup fuse [-d] [-f] <mountpoint>
560 d,debug increase debug level
561 f,foreground run in foreground
563 o = options.Options('bup fuse', optspec)
564 (opt, flags, extra) = o.parse(sys.argv[1:])
567 o.fatal("exactly one argument expected")
569 git.check_repo_or_die()
570 top = vfs.RefList(None)
572 f.fuse_args.mountpoint = extra[0]
574 f.fuse_args.add('debug')
576 f.fuse_args.setmod('foreground')
577 print f.multithreaded
578 f.multithreaded = False
581 #!/usr/bin/env python
582 from bup import git, options, client
583 from bup.helpers import *
586 [BUP_DIR=...] bup init [-r host:path]
588 r,remote= remote repository path
590 o = options.Options('bup init', optspec)
591 (opt, flags, extra) = o.parse(sys.argv[1:])
594 o.fatal("no arguments expected")
598 git.init_repo() # local repo
599 git.check_repo_or_die()
600 cli = client.Client(opt.remote, create=True)
604 #!/usr/bin/env python
605 import sys, math, struct, glob
606 from bup import options, git
607 from bup.helpers import *
610 SHA_PER_PAGE=PAGE_SIZE/200.
613 def merge(idxlist, bits, table):
615 for e in git.idxmerge(idxlist):
617 prefix = git.extract_bits(e, bits)
618 table[prefix] = count
622 def do_midx(outdir, outfilename, infilenames):
625 sum = Sha1('\0'.join(infilenames)).hexdigest()
626 outfilename = '%s/midx-%s.midx' % (outdir, sum)
630 for name in infilenames:
631 ix = git.PackIdx(name)
635 log('Merging %d indexes (%d objects).\n' % (len(infilenames), total))
636 if (not opt.force and (total < 1024 and len(infilenames) < 3)) \
637 or (opt.force and not total):
638 log('midx: nothing to do.\n')
641 pages = int(total/SHA_PER_PAGE) or 1
642 bits = int(math.ceil(math.log(pages, 2)))
644 log('Table size: %d (%d bits)\n' % (entries*4, bits))
649 os.unlink(outfilename)
652 f = open(outfilename + '.tmp', 'w+')
653 f.write('MIDX\0\0\0\2')
654 f.write(struct.pack('!I', bits))
655 assert(f.tell() == 12)
656 f.write('\0'*4*entries)
658 for e in merge(inp, bits, table):
661 f.write('\0'.join(os.path.basename(p) for p in infilenames))
664 f.write(struct.pack('!%dI' % entries, *table))
666 os.rename(outfilename + '.tmp', outfilename)
668 # this is just for testing
670 p = git.PackMidx(outfilename)
671 assert(len(p.idxnames) == len(infilenames))
673 assert(len(p) == total)
675 for i in merge(inp, total, bits, table):
676 assert(i == pi.next())
682 bup midx [options...] <idxnames...>
684 o,output= output midx filename (default: auto-generated)
685 a,auto automatically create .midx from any unindexed .idx files
686 f,force automatically create .midx from *all* .idx files
688 o = options.Options('bup midx', optspec)
689 (opt, flags, extra) = o.parse(sys.argv[1:])
691 if extra and (opt.auto or opt.force):
692 o.fatal("you can't use -f/-a and also provide filenames")
694 git.check_repo_or_die()
697 do_midx(git.repo('objects/pack'), opt.output, extra)
698 elif opt.auto or opt.force:
699 paths = [git.repo('objects/pack')]
700 paths += glob.glob(git.repo('index-cache/*/.'))
702 log('midx: scanning %s\n' % path)
704 do_midx(path, opt.output, glob.glob('%s/*.idx' % path))
706 m = git.PackIdxList(path)
708 for pack in m.packs: # only .idx files without a .midx are open
709 if pack.name.endswith('.idx'):
710 needed[pack.name] = 1
712 do_midx(path, opt.output, needed.keys())
715 o.fatal("you must use -f or -a or provide input filenames")
716 #!/usr/bin/env python
717 import sys, os, random
718 from bup import options
719 from bup.helpers import *
725 l.append(chr(random.randrange(0,256)))
730 bup damage [-n count] [-s maxsize] [-S seed] <filenames...>
732 WARNING: THIS COMMAND IS EXTREMELY DANGEROUS
733 n,num= number of blocks to damage
734 s,size= maximum size of each damaged block
735 percent= maximum size of each damaged block (as a percent of entire file)
736 equal spread damage evenly throughout the file
737 S,seed= random number seed (for repeatable tests)
739 o = options.Options('bup damage', optspec)
740 (opt, flags, extra) = o.parse(sys.argv[1:])
743 o.fatal('filenames expected')
746 random.seed(opt.seed)
749 log('Damaging "%s"...\n' % name)
750 f = open(name, 'r+b')
751 st = os.fstat(f.fileno())
753 if opt.percent or opt.size:
754 ms1 = int(float(opt.percent or 0)/100.0*size) or size
755 ms2 = opt.size or size
756 maxsize = min(ms1, ms2)
759 chunks = opt.num or 10
760 chunksize = size/chunks
761 for r in range(chunks):
762 sz = random.randrange(1, maxsize+1)
768 ofs = random.randrange(0, size - sz + 1)
769 log(' %6d bytes at %d\n' % (sz, ofs))
771 f.write(randblock(sz))
773 #!/usr/bin/env python
774 import sys, struct, mmap
775 from bup import options, git
776 from bup.helpers import *
781 def init_dir(conn, arg):
783 log('bup server: bupdir initialized: %r\n' % git.repodir)
787 def set_dir(conn, arg):
788 git.check_repo_or_die(arg)
789 log('bup server: bupdir is %r\n' % git.repodir)
793 def list_indexes(conn, junk):
794 git.check_repo_or_die()
795 for f in os.listdir(git.repo('objects/pack')):
796 if f.endswith('.idx'):
797 conn.write('%s\n' % f)
801 def send_index(conn, name):
802 git.check_repo_or_die()
803 assert(name.find('/') < 0)
804 assert(name.endswith('.idx'))
805 idx = git.PackIdx(git.repo('objects/pack/%s' % name))
806 conn.write(struct.pack('!I', len(idx.map)))
811 def receive_objects(conn, junk):
813 git.check_repo_or_die()
824 raise Exception('object read: expected length header, got EOF\n')
825 n = struct.unpack('!I', ns)[0]
826 #log('expecting %d bytes\n' % n)
828 log('bup server: received %d object%s.\n'
829 % (w.count, w.count!=1 and "s" or ''))
832 (dir, name) = os.path.split(fullpath)
833 conn.write('%s.idx\n' % name)
836 elif n == 0xffffffff:
837 log('bup server: receive-objects suspended.\n')
842 buf = conn.read(n) # object sizes in bup are reasonably small
843 #log('read %d bytes\n' % n)
846 raise Exception('object read: expected %d bytes, got %d\n'
848 (type, content) = git._decode_packobj(buf)
849 sha = git.calc_hash(type, content)
850 oldpack = w.exists(sha)
851 # FIXME: we only suggest a single index per cycle, because the client
852 # is currently dumb to download more than one per cycle anyway.
853 # Actually we should fix the client, but this is a minor optimization
854 # on the server side.
855 if not suggested and \
856 oldpack and (oldpack == True or oldpack.endswith('.midx')):
857 # FIXME: we shouldn't really have to know about midx files
858 # at this layer. But exists() on a midx doesn't return the
859 # packname (since it doesn't know)... probably we should just
860 # fix that deficiency of midx files eventually, although it'll
861 # make the files bigger. This method is certainly not very
863 w.objcache.refresh(skip_midx = True)
864 oldpack = w.objcache.exists(sha)
865 log('new suggestion: %r\n' % oldpack)
867 assert(oldpack != True)
868 assert(not oldpack.endswith('.midx'))
869 w.objcache.refresh(skip_midx = False)
870 if not suggested and oldpack:
871 assert(oldpack.endswith('.idx'))
872 (dir,name) = os.path.split(oldpack)
873 if not (name in suggested):
874 log("bup server: suggesting index %s\n" % name)
875 conn.write('index %s\n' % name)
882 def read_ref(conn, refname):
883 git.check_repo_or_die()
884 r = git.read_ref(refname)
885 conn.write('%s\n' % (r or '').encode('hex'))
889 def update_ref(conn, refname):
890 git.check_repo_or_die()
891 newval = conn.readline().strip()
892 oldval = conn.readline().strip()
893 git.update_ref(refname, newval.decode('hex'), oldval.decode('hex'))
898 git.check_repo_or_die()
900 for blob in git.cat(id):
901 conn.write(struct.pack('!I', len(blob)))
904 log('server: error: %s\n' % e)
905 conn.write('\0\0\0\0')
908 conn.write('\0\0\0\0')
915 o = options.Options('bup server', optspec)
916 (opt, flags, extra) = o.parse(sys.argv[1:])
919 o.fatal('no arguments expected')
921 log('bup server: reading from stdin.\n')
924 'init-dir': init_dir,
926 'list-indexes': list_indexes,
927 'send-index': send_index,
928 'receive-objects': receive_objects,
929 'read-ref': read_ref,
930 'update-ref': update_ref,
934 # FIXME: this protocol is totally lame and not at all future-proof.
935 # (Especially since we abort completely as soon as *anything* bad happens)
936 conn = Conn(sys.stdin, sys.stdout)
937 lr = linereader(conn)
942 log('bup server: command: %r\n' % line)
943 words = line.split(' ', 1)
945 rest = len(words)>1 and words[1] or ''
949 cmd = commands.get(cmd)
953 raise Exception('unknown server command: %r\n' % line)
955 log('bup server: done\n')
956 #!/usr/bin/env python
957 import sys, time, struct
958 from bup import hashsplit, git, options, client
959 from bup.helpers import *
960 from subprocess import PIPE
964 bup join [-r host:path] [refs or hashes...]
966 r,remote= remote repository path
968 o = options.Options('bup join', optspec)
969 (opt, flags, extra) = o.parse(sys.argv[1:])
971 git.check_repo_or_die()
974 extra = linereader(sys.stdin)
979 cli = client.Client(opt.remote)
988 sys.stdout.write(blob)
991 log('error: %s\n' % e)
995 #!/usr/bin/env python
996 import sys, re, errno, stat, time, math
997 from bup import hashsplit, git, options, index, client
998 from bup.helpers import *
1002 bup save [-tc] [-n name] <filenames...>
1004 r,remote= remote repository path
1005 t,tree output a tree id
1006 c,commit output a commit id
1007 n,name= name of backup set to update (if any)
1008 v,verbose increase log output (can be used more than once)
1009 q,quiet don't show progress meter
1010 smaller= only back up files smaller than n bytes
1012 o = options.Options('bup save', optspec)
1013 (opt, flags, extra) = o.parse(sys.argv[1:])
1015 git.check_repo_or_die()
1016 if not (opt.tree or opt.commit or opt.name):
1017 o.fatal("use one or more of -t, -c, -n")
1019 o.fatal("no filenames given")
1021 opt.progress = (istty and not opt.quiet)
1022 opt.smaller = parse_num(opt.smaller or 0)
1024 is_reverse = os.environ.get('BUP_SERVER_REVERSE')
1025 if is_reverse and opt.remote:
1026 o.fatal("don't use -r in reverse mode; it's automatic")
1028 refname = opt.name and 'refs/heads/%s' % opt.name or None
1029 if opt.remote or is_reverse:
1030 cli = client.Client(opt.remote)
1031 oldref = refname and cli.read_ref(refname) or None
1032 w = cli.new_packwriter()
1035 oldref = refname and git.read_ref(refname) or None
1036 w = git.PackWriter()
1042 if dir.endswith('/'):
1056 def _pop(force_tree):
1057 assert(len(parts) >= 1)
1059 shalist = shalists.pop()
1060 tree = force_tree or w.new_tree(shalist)
1062 shalists[-1].append(('40000', part, tree))
1063 else: # this was the toplevel, so put it back for sanity
1064 shalists.append(shalist)
1068 def progress_report(n):
1069 global count, subcount, lastremain
1071 cc = count + subcount
1072 pct = total and (cc*100.0/total) or 0
1074 elapsed = now - tstart
1075 kps = elapsed and int(cc/1024./elapsed)
1076 kps_frac = 10 ** int(math.log(kps+1, 10) - 1)
1077 kps = int(kps/kps_frac)*kps_frac
1079 remain = elapsed*1.0/cc * (total-cc)
1082 if (lastremain and (remain > lastremain)
1083 and ((remain - lastremain)/lastremain < 0.05)):
1087 hours = int(remain/60/60)
1088 mins = int(remain/60 - hours*60)
1089 secs = int(remain - hours*60*60 - mins*60)
1094 kpsstr = '%dk/s' % kps
1096 remainstr = '%dh%dm' % (hours, mins)
1098 remainstr = '%dm%d' % (mins, secs)
1100 remainstr = '%ds' % secs
1101 progress('Saving: %.2f%% (%d/%dk, %d/%d files) %s %s\r'
1102 % (pct, cc/1024, total/1024, fcount, ftotal,
1106 r = index.Reader(git.repo('bupindex'))
1108 def already_saved(ent):
1109 return ent.is_valid() and w.exists(ent.sha) and ent.sha
1111 def wantrecurse_pre(ent):
1112 return not already_saved(ent)
1114 def wantrecurse_during(ent):
1115 return not already_saved(ent) or ent.sha_missing()
1119 for (transname,ent) in r.filter(extra, wantrecurse=wantrecurse_pre):
1120 if not (ftotal % 10024):
1121 progress('Reading index: %d\r' % ftotal)
1122 exists = ent.exists()
1123 hashvalid = already_saved(ent)
1124 ent.set_sha_missing(not hashvalid)
1125 if not opt.smaller or ent.size < opt.smaller:
1126 if exists and not hashvalid:
1129 progress('Reading index: %d, done.\n' % ftotal)
1130 hashsplit.progress_callback = progress_report
1132 tstart = time.time()
1133 count = subcount = fcount = 0
1134 lastskip_name = None
1136 for (transname,ent) in r.filter(extra, wantrecurse=wantrecurse_during):
1137 (dir, file) = os.path.split(ent.name)
1138 exists = (ent.flags & index.IX_EXISTS)
1139 hashvalid = already_saved(ent)
1140 wasmissing = ent.sha_missing()
1146 if ent.sha == index.EMPTY_SHA:
1152 if opt.verbose >= 2:
1153 log('%s %-70s\n' % (status, ent.name))
1154 elif not stat.S_ISDIR(ent.mode) and lastdir != dir:
1155 if not lastdir.startswith(dir):
1156 log('%s %-70s\n' % (status, os.path.join(dir, '')))
1165 if opt.smaller and ent.size >= opt.smaller:
1166 if exists and not hashvalid:
1167 add_error('skipping large file "%s"' % ent.name)
1168 lastskip_name = ent.name
1171 assert(dir.startswith('/'))
1172 dirp = dir.split('/')
1174 _pop(force_tree = None)
1176 for part in dirp[len(parts):]:
1180 # no filename portion means this is a subdir. But
1181 # sub/parentdirectories already handled in the pop/push() part above.
1182 oldtree = already_saved(ent) # may be None
1183 newtree = _pop(force_tree = oldtree)
1185 if lastskip_name and lastskip_name.startswith(ent.name):
1188 ent.validate(040000, newtree)
1190 if exists and wasmissing:
1194 # it's not a directory
1197 mode = '%o' % ent.gitmode
1199 shalists[-1].append((mode,
1200 git.mangle_name(file, ent.mode, ent.gitmode),
1203 if stat.S_ISREG(ent.mode):
1205 f = hashsplit.open_noatime(ent.name)
1208 lastskip_name = ent.name
1211 lastskip_name = ent.name
1213 (mode, id) = hashsplit.split_to_blob_or_tree(w, [f])
1215 if stat.S_ISDIR(ent.mode):
1216 assert(0) # handled above
1217 elif stat.S_ISLNK(ent.mode):
1219 rl = os.readlink(ent.name)
1222 lastskip_name = ent.name
1225 lastskip_name = ent.name
1227 (mode, id) = ('120000', w.new_blob(rl))
1229 add_error(Exception('skipping special file "%s"' % ent.name))
1230 lastskip_name = ent.name
1232 ent.validate(int(mode, 8), id)
1234 shalists[-1].append((mode,
1235 git.mangle_name(file, ent.mode, ent.gitmode),
1237 if exists and wasmissing:
1243 pct = total and count*100.0/total or 100
1244 progress('Saving: %.2f%% (%d/%dk, %d/%d files), done. \n'
1245 % (pct, count/1024, total/1024, fcount, ftotal))
1247 while len(parts) > 1:
1248 _pop(force_tree = None)
1249 assert(len(shalists) == 1)
1250 tree = w.new_tree(shalists[-1])
1252 print tree.encode('hex')
1253 if opt.commit or opt.name:
1254 msg = 'bup save\n\nGenerated by command:\n%r' % sys.argv
1255 ref = opt.name and ('refs/heads/%s' % opt.name) or None
1256 commit = w.new_commit(oldref, tree, msg)
1258 print commit.encode('hex')
1260 w.close() # must close before we can update the ref
1264 cli.update_ref(refname, commit, oldref)
1266 git.update_ref(refname, commit, oldref)
1272 log('WARNING: %d errors encountered while saving.\n' % len(saved_errors))
1274 #!/usr/bin/env python
1276 from bup import options
1281 o = options.Options('bup tick', optspec)
1282 (opt, flags, extra) = o.parse(sys.argv[1:])
1285 o.fatal("no arguments expected")
1288 tleft = 1 - (t - int(t))
1290 #!/usr/bin/env python
1291 import os, sys, stat, time
1292 from bup import options, git, index, drecurse
1293 from bup.helpers import *
1296 def merge_indexes(out, r1, r2):
1297 for e in index.MergeIter([r1, r2]):
1298 # FIXME: shouldn't we remove deleted entries eventually? When?
1303 def __init__(self, l):
1310 self.cur = self.i.next()
1311 except StopIteration:
1316 def check_index(reader):
1318 log('check: checking forward iteration...\n')
1321 for e in reader.forward_iter():
1324 log('%08x+%-4d %r\n' % (e.children_ofs, e.children_n,
1326 assert(e.children_ofs)
1327 assert(e.name.endswith('/'))
1328 assert(not d.get(e.children_ofs))
1329 d[e.children_ofs] = 1
1330 if e.flags & index.IX_HASHVALID:
1331 assert(e.sha != index.EMPTY_SHA)
1333 assert(not e or e.name == '/') # last entry is *always* /
1334 log('check: checking normal iteration...\n')
1338 assert(last > e.name)
1341 log('index error! at %r\n' % e)
1343 log('check: passed.\n')
1346 def update_index(top):
1347 ri = index.Reader(indexfile)
1348 wi = index.Writer(indexfile)
1349 rig = IterHelper(ri.iter(name=top))
1350 tstart = int(time.time())
1355 return (0100644, index.FAKE_SHA)
1358 for (path,pst) in drecurse.recursive_dirlist([top], xdev=opt.xdev):
1359 if opt.verbose>=2 or (opt.verbose==1 and stat.S_ISDIR(pst.st_mode)):
1360 sys.stdout.write('%s\n' % path)
1362 progress('Indexing: %d\r' % total)
1363 elif not (total % 128):
1364 progress('Indexing: %d\r' % total)
1366 while rig.cur and rig.cur.name > path: # deleted paths
1367 if rig.cur.exists():
1368 rig.cur.set_deleted()
1371 if rig.cur and rig.cur.name == path: # paths that already existed
1373 rig.cur.from_stat(pst, tstart)
1374 if not (rig.cur.flags & index.IX_HASHVALID):
1376 (rig.cur.gitmode, rig.cur.sha) = hashgen(path)
1377 rig.cur.flags |= index.IX_HASHVALID
1378 if opt.fake_invalid:
1379 rig.cur.invalidate()
1383 wi.add(path, pst, hashgen = hashgen)
1384 progress('Indexing: %d, done.\n' % total)
1390 wr = wi.new_reader()
1392 log('check: before merging: oldfile\n')
1394 log('check: before merging: newfile\n')
1396 mi = index.Writer(indexfile)
1397 merge_indexes(mi, ri, wr)
1407 bup index <-p|m|u> [options...] <filenames...>
1409 p,print print the index entries for the given names (also works with -u)
1410 m,modified print only added/deleted/modified files (implies -p)
1411 s,status print each filename with a status char (A/M/D) (implies -p)
1412 H,hash print the hash for each object next to its name (implies -p)
1413 l,long print more information about each file
1414 u,update (recursively) update the index entries for the given filenames
1415 x,xdev,one-file-system don't cross filesystem boundaries
1416 fake-valid mark all index entries as up-to-date even if they aren't
1417 fake-invalid mark all index entries as invalid
1418 check carefully check index file integrity
1419 f,indexfile= the name of the index file (default 'index')
1420 v,verbose increase log output (can be used more than once)
1422 o = options.Options('bup index', optspec)
1423 (opt, flags, extra) = o.parse(sys.argv[1:])
1425 if not (opt.modified or opt['print'] or opt.status or opt.update or opt.check):
1426 o.fatal('supply one or more of -p, -s, -m, -u, or --check')
1427 if (opt.fake_valid or opt.fake_invalid) and not opt.update:
1428 o.fatal('--fake-{in,}valid are meaningless without -u')
1429 if opt.fake_valid and opt.fake_invalid:
1430 o.fatal('--fake-valid is incompatible with --fake-invalid')
1432 git.check_repo_or_die()
1433 indexfile = opt.indexfile or git.repo('bupindex')
1438 log('check: starting initial check.\n')
1439 check_index(index.Reader(indexfile))
1441 paths = index.reduce_paths(extra)
1445 o.fatal('update (-u) requested but no paths given')
1446 for (rp,path) in paths:
1449 if opt['print'] or opt.status or opt.modified:
1450 for (name, ent) in index.Reader(indexfile).filter(extra or ['']):
1452 and (ent.is_valid() or ent.is_deleted() or not ent.mode)):
1456 if ent.is_deleted():
1458 elif not ent.is_valid():
1459 if ent.sha == index.EMPTY_SHA:
1466 line += ent.sha.encode('hex') + ' '
1468 line += "%7s %7s " % (oct(ent.mode), oct(ent.gitmode))
1469 print line + (name or './')
1471 if opt.check and (opt['print'] or opt.status or opt.modified or opt.update):
1472 log('check: starting final check.\n')
1473 check_index(index.Reader(indexfile))
1476 log('WARNING: %d errors encountered.\n' % len(saved_errors))
1478 #!/usr/bin/env python
1479 import sys, os, struct
1480 from bup import options, helpers
1485 This command is not intended to be run manually.
1487 o = options.Options('bup rbackup-server', optspec)
1488 (opt, flags, extra) = o.parse(sys.argv[1:])
1490 o.fatal('no arguments expected')
1492 # get the subcommand's argv.
1493 # Normally we could just pass this on the command line, but since we'll often
1494 # be getting called on the other end of an ssh pipe, which tends to mangle
1495 # argv (by sending it via the shell), this way is much safer.
1496 buf = sys.stdin.read(4)
1497 sz = struct.unpack('!I', buf)[0]
1499 assert(sz < 1000000)
1500 buf = sys.stdin.read(sz)
1501 assert(len(buf) == sz)
1502 argv = buf.split('\0')
1504 # stdin/stdout are supposedly connected to 'bup server' that the caller
1505 # started for us (often on the other end of an ssh tunnel), so we don't want
1506 # to misuse them. Move them out of the way, then replace stdout with
1507 # a pointer to stderr in case our subcommand wants to do something with it.
1509 # It might be nice to do the same with stdin, but my experiments showed that
1510 # ssh seems to make its child's stderr a readable-but-never-reads-anything
1511 # socket. They really should have used shutdown(SHUT_WR) on the other end
1512 # of it, but probably didn't. Anyway, it's too messy, so let's just make sure
1513 # anyone reading from stdin is disappointed.
1515 # (You can't just leave stdin/stdout "not open" by closing the file
1516 # descriptors. Then the next file that opens is automatically assigned 0 or 1,
1517 # and people *trying* to read/write stdin/stdout get screwed.)
1521 in approximately the same placeRDONLY)
1525 in the original test filesERSE'] = helpers.hostname()
1526 os.execvp(argv[0], argv)
1528 #!/usr/bin/env python
1529 import sys, os, glob, subprocess, time
1530 from bup import options, git
1531 from bup.helpers import *
1534 nullf = open('/dev/null')
1541 # at least in python 2.5, using "stdout=2" or "stdout=sys.stderr" below
1542 # doesn't actually work, because subprocess closes fd #2 right before
1543 # execing for some reason. So we work around it by duplicating the fd
1545 fd = os.dup(2) # copy stderr
1547 p = subprocess.Popen(argv, stdout=fd, close_fds=False)
1556 p = subprocess.Popen(['par2', '--help'],
1557 stdout=nullf, stderr=nullf, stdin=nullf)
1560 log('fsck: warning: par2 not found; disabling recovery features.\n')
1565 if opt.verbose >= lvl:
1573 def par2_generate(base):
1574 return run(['par2', 'create', '-n1', '-c200'] + parv(2)
1575 + ['--', base, base+'.pack', base+'.idx'])
1577 def par2_verify(base):
1578 return run(['par2', 'verify'] + parv(3) + ['--', base])
1580 def par2_repair(base):
1581 return run(['par2', 'repair'] + parv(2) + ['--', base])
1583 def quick_verify(base):
1584 f = open(base + '.pack', 'rb')
1586 wantsum = f.read(20)
1587 assert(len(wantsum) == 20)
1590 for b in chunkyreader(f, os.fstat(f.fileno()).st_size - 20):
1592 if sum.digest() != wantsum:
1593 raise ValueError('expected %r, got %r' % (wantsum.encode('hex'),
1597 def git_verify(base):
1601 except Exception, e:
1602 debug('error: %s\n' % e)
1606 return run(['git', 'verify-pack', '--', base])
1609 def do_pack(base, last):
1611 if par2_ok and par2_exists and (opt.repair or not opt.generate):
1612 vresult = par2_verify(base)
1615 rresult = par2_repair(base)
1617 print '%s par2 repair: failed (%d)' % (last, rresult)
1620 print '%s par2 repair: succeeded (0)' % last
1623 print '%s par2 verify: failed (%d)' % (last, vresult)
1626 print '%s ok' % last
1627 elif not opt.generate or (par2_ok and not par2_exists):
1628 gresult = git_verify(base)
1630 print '%s git verify: failed (%d)' % (last, gresult)
1633 if par2_ok and opt.generate:
1634 presult = par2_generate(base)
1636 print '%s par2 create: failed (%d)' % (last, presult)
1639 print '%s ok' % last
1641 print '%s ok' % last
1643 assert(opt.generate and (not par2_ok or par2_exists))
1644 debug(' skipped: par2 file already generated.\n')
1649 bup fsck [options...] [filenames...]
1651 r,repair attempt to repair errors using par2 (dangerous!)
1652 g,generate generate auto-repair information using par2
1653 v,verbose increase verbosity (can be used more than once)
1654 quick just check pack sha1sum, don't use git verify-pack
1655 j,jobs= run 'n' jobs in parallel
1656 par2-ok immediately return 0 if par2 is ok, 1 if not
1657 disable-par2 ignore par2 even if it is available
1659 o = options.Options('bup fsck', optspec)
1660 (opt, flags, extra) = o.parse(sys.argv[1:])
1665 sys.exit(0) # 'true' in sh
1668 if opt.disable_par2:
1671 git.check_repo_or_die()
1674 debug('fsck: No filenames given: checking all packs.\n')
1675 extra = glob.glob(git.repo('objects/pack/*.pack'))
1681 if name.endswith('.pack'):
1683 elif name.endswith('.idx'):
1685 elif name.endswith('.par2'):
1687 elif os.path.exists(name + '.pack'):
1690 raise Exception('%s is not a pack file!' % name)
1691 (dir,last) = os.path.split(base)
1692 par2_exists = os.path.exists(base + '.par2')
1693 if par2_exists and os.stat(base + '.par2').st_size == 0:
1696 debug('fsck: checking %s (%s)\n'
1697 % (last, par2_ok and par2_exists and 'par2' or 'git'))
1699 progress('fsck (%d/%d)\r' % (count, len(extra)))
1702 nc = do_pack(base, last)
1706 while len(outstanding) >= opt.jobs:
1707 (pid,nc) = os.wait()
1709 if pid in outstanding:
1710 del outstanding[pid]
1715 outstanding[pid] = 1
1718 sys.exit(do_pack(base, last))
1719 except Exception, e:
1720 log('exception: %r\n' % e)
1723 while len(outstanding):
1724 (pid,nc) = os.wait()
1726 if pid in outstanding:
1727 del outstanding[pid]
1731 progress('fsck (%d/%d)\r' % (count, len(extra)))
1733 if not opt.verbose and istty:
1734 log('fsck done. \n')
1736 #!/usr/bin/env python
1737 import sys, os, struct, getopt, subprocess, signal
1738 from bup import options, ssh
1739 from bup.helpers import *
1742 bup rbackup <hostname> index ...
1743 bup rbackup <hostname> save ...
1744 bup rbackup <hostname> split ...
1746 o = options.Options('bup rbackup', optspec, optfunc=getopt.getopt)
1747 (opt, flags, extra) = o.parse(sys.argv[1:])
1749 o.fatal('arguments expected')
1751 class SigException(Exception):
1752 def __init__(self, signum):
1753 self.signum = signum
1754 Exception.__init__(self, 'signal %d received' % signum)
1755 def handler(signum, frame):
1756 raise SigException(signum)
1758 signal.signal(signal.SIGTERM, handler)
1759 signal.signal(signal.SIGINT, handler)
1768 p = ssh.connect(hostname, 'rbackup-server')
1770 argvs = '\0'.join(['bup'] + argv)
1771 p.stdin.write(struct.pack('!I', len(argvs)) + argvs)
1774 main_exe = os.environ.get('BUP_MAIN_EXE') or sys.argv[0]
1775 sp = subprocess.Popen([main_exe, 'server'], stdin=p.stdout, stdout=p.stdin)
1782 # if we get a signal while waiting, we have to keep waiting, just
1783 # in case our child doesn't die.
1788 except SigException, e:
1789 log('\nbup rbackup: %s\n' % e)
1790 os.kill(p.pid, e.signum)
1793 #!/usr/bin/env python
1795 from bup import options
1800 o = options.Options('bup newliner', optspec)
1801 (opt, flags, extra) = o.parse(sys.argv[1:])
1804 o.fatal("no arguments expected")
1806 r = re.compile(r'([\r\n])')
1813 b = os.read(sys.stdin.fileno(), 4096)
1814 except KeyboardInterrupt:
1821 (line, splitchar, all) = l
1823 sys.stdout.write('%-*s%s' % (lastlen, line, splitchar))
1824 if splitchar == '\r':
1831 sys.stdout.write('%-*s\n' % (lastlen, all))
1832 #!/usr/bin/env python
1834 from bup import options, git, _hashsplit
1835 from bup.helpers import *
1841 o = options.Options('bup margin', optspec)
1842 (opt, flags, extra) = o.parse(sys.argv[1:])
1845 o.fatal("no arguments expected")
1847 git.check_repo_or_die()
1848 #git.ignore_midx = 1
1850 mi = git.PackIdxList(git.repo('objects/pack'))
1856 #assert(str(i) >= last)
1857 pm = _hashsplit.bitmatch(last, i)
1858 longmatch = max(longmatch, pm)
1861 #!/usr/bin/env python
1862 from bup import options, drecurse
1863 from bup.helpers import *
1868 x,xdev,one-file-system don't cross filesystem boundaries
1869 q,quiet don't actually print filenames
1870 profile run under the python profiler
1872 o = options.Options('bup drecurse', optspec)
1873 (opt, flags, extra) = o.parse(sys.argv[1:])
1876 o.fatal("exactly one filename expected")
1878 it = drecurse.recursive_dirlist(extra, opt.xdev)
1884 cProfile.run('do_it()')
1890 for (name,st) in it:
1894 log('WARNING: %d errors encountered.\n' % len(saved_errors))
1896 #!/usr/bin/env python
1897 import sys, time, struct
1898 from bup import hashsplit, git, options, client
1899 from bup.helpers import *
1900 from subprocess import PIPE
1904 bup split [-tcb] [-n name] [--bench] [filenames...]
1906 r,remote= remote repository path
1907 b,blobs output a series of blob ids
1908 t,tree output a tree id
1909 c,commit output a commit id
1910 n,name= name of backup set to update (if any)
1911 N,noop don't actually save the data anywhere
1912 q,quiet don't print progress messages
1913 v,verbose increase log output (can be used more than once)
1914 copy just copy input to output, hashsplitting along the way
1915 bench print benchmark timings to stderr
1916 max-pack-size= maximum bytes in a single pack
1917 max-pack-objects= maximum number of objects in a single pack
1918 fanout= maximum number of blobs in a single tree
1920 o = options.Options('bup split', optspec)
1921 (opt, flags, extra) = o.parse(sys.argv[1:])
1923 git.check_repo_or_die()
1924 if not (opt.blobs or opt.tree or opt.commit or opt.name or
1925 opt.noop or opt.copy):
1926 o.fatal("use one or more of -b, -t, -c, -n, -N, --copy")
1927 if (opt.noop or opt.copy) and (opt.blobs or opt.tree or
1928 opt.commit or opt.name):
1929 o.fatal('-N is incompatible with -b, -t, -c, -n')
1931 if opt.verbose >= 2:
1932 git.verbose = opt.verbose - 1
1934 if opt.max_pack_size:
1935 hashsplit.max_pack_size = parse_num(opt.max_pack_size)
1936 if opt.max_pack_objects:
1937 hashsplit.max_pack_objects = parse_num(opt.max_pack_objects)
1939 hashsplit.fanout = parse_num(opt.fanout)
1941 hashsplit.fanout = 0
1943 is_reverse = os.environ.get('BUP_SERVER_REVERSE')
1944 if is_reverse and opt.remote:
1945 o.fatal("don't use -r in reverse mode; it's automatic")
1946 start_time = time.time()
1948 refname = opt.name and 'refs/heads/%s' % opt.name or None
1949 if opt.noop or opt.copy:
1950 cli = w = oldref = None
1951 elif opt.remote or is_reverse:
1952 cli = client.Client(opt.remote)
1953 oldref = refname and cli.read_ref(refname) or None
1954 w = cli.new_packwriter()
1957 oldref = refname and git.read_ref(refname) or None
1958 w = git.PackWriter()
1960 files = extra and (open(fn) for fn in extra) or [sys.stdin]
1962 shalist = hashsplit.split_to_shalist(w, files)
1963 tree = w.new_tree(shalist)
1966 for (blob, bits) in hashsplit.hashsplit_iter(files):
1967 hashsplit.total_split += len(blob)
1969 sys.stdout.write(str(blob))
1970 megs = hashsplit.total_split/1024/1024
1971 if not opt.quiet and last != megs:
1972 progress('%d Mbytes read\r' % megs)
1974 progress('%d Mbytes read, done.\n' % megs)
1979 for (mode,name,bin) in shalist:
1980 print bin.encode('hex')
1982 print tree.encode('hex')
1983 if opt.commit or opt.name:
1984 msg = 'bup split\n\nGenerated by command:\n%r' % sys.argv
1985 ref = opt.name and ('refs/heads/%s' % opt.name) or None
1986 commit = w.new_commit(oldref, tree, msg)
1988 print commit.encode('hex')
1991 w.close() # must close before we can update the ref
1995 cli.update_ref(refname, commit, oldref)
1997 git.update_ref(refname, commit, oldref)
2002 secs = time.time() - start_time
2003 size = hashsplit.total_split
2005 log('\nbup: %.2fkbytes in %.2f secs = %.2f kbytes/sec\n'
2006 % (size/1024., secs, size/1024./secs))
2007 #!/usr/bin/env python
2008 import sys, re, struct, mmap
2009 from bup import git, options
2010 from bup.helpers import *
2013 def s_from_bytes(bytes):
2014 clist = [chr(b) for b in bytes]
2015 return ''.join(clist)
2019 fields = ['VmSize', 'VmRSS', 'VmData', 'VmStk']
2021 for line in open('/proc/self/status').readlines():
2022 l = re.split(r':\s*', line.strip(), 1)
2026 fields = [d[k] for k in fields]
2029 print ('%9s ' + ('%10s ' * len(fields))) % tuple([e1] + fields)
2034 bup memtest [-n elements] [-c cycles]
2036 n,number= number of objects per cycle
2037 c,cycles= number of cycles to run
2038 ignore-midx ignore .midx files, use only .idx files
2040 o = options.Options('bup memtest', optspec)
2041 (opt, flags, extra) = o.parse(sys.argv[1:])
2044 o.fatal('no arguments expected')
2046 git.ignore_midx = opt.ignore_midx
2048 git.check_repo_or_die()
2049 m = git.PackIdxList(git.repo('objects/pack'))
2051 cycles = opt.cycles or 100
2052 number = opt.number or 10000
2055 f = open('/dev/urandom')
2056 a = mmap.mmap(-1, 20)
2058 for c in xrange(cycles):
2059 for n in xrange(number):
2062 bytes = list(struct.unpack('!BBB', b)) + [0]*17
2064 bin = struct.pack('!20s', s_from_bytes(bytes))
2067 a[2] = chr(ord(b[2]) & 0xf0)
2069 #print bin.encode('hex')
2071 report((c+1)*number)
2072 #!/usr/bin/env python
2073 import sys, os, stat
2074 from bup import options, git, vfs
2075 from bup.helpers import *
2077 def print_node(text, n):
2080 prefix += "%s " % n.hash.encode('hex')
2081 if stat.S_ISDIR(n.mode):
2082 print '%s%s/' % (prefix, text)
2083 elif stat.S_ISLNK(n.mode):
2084 print '%s%s@' % (prefix, text)
2086 print '%s%s' % (prefix, text)
2092 s,hash show hash for each file
2094 o = options.Options('bup ls', optspec)
2095 (opt, flags, extra) = o.parse(sys.argv[1:])
2097 git.check_repo_or_die()
2098 top = vfs.RefList(None)
2107 if stat.S_ISDIR(n.mode):
2109 print_node(sub.name, sub)
2112 except vfs.NodeError, e:
2113 log('error: %s\n' % e)
2117 #!/usr/bin/env python
2118 import sys, os, re, stat, readline, fnmatch
2119 from bup import options, git, shquote, vfs
2120 from bup.helpers import *
2122 def node_name(text, n):
2123 if stat.S_ISDIR(n.mode):
2125 elif stat.S_ISLNK(n.mode):
2133 if stat.S_ISDIR(n.mode):
2135 l.append(node_name(sub.name, sub))
2137 l.append(node_name(path, n))
2138 print columnate(l, '')
2141 def write_to_file(inf, outf):
2142 for blob in chunkyreader(inf):
2147 if os.isatty(sys.stdin.fileno()):
2150 yield raw_input('bup> ')
2154 for line in sys.stdin:
2158 def _completer_get_subs(line):
2159 (qtype, lastword) = shquote.unfinished_word(line)
2160 (dir,name) = os.path.split(lastword)
2161 #log('\ncompleter: %r %r %r\n' % (qtype, lastword, text))
2162 n = pwd.resolve(dir)
2163 subs = list(filter(lambda x: x.name.startswith(name),
2165 return (dir, name, qtype, lastword, subs)
2170 def completer(text, state):
2174 line = readline.get_line_buffer()[:readline.get_endidx()]
2175 if _last_line != line:
2176 _last_res = _completer_get_subs(line)
2178 (dir, name, qtype, lastword, subs) = _last_res
2179 if state < len(subs):
2181 sn1 = sn.resolve('') # deref symlinks
2182 fullname = os.path.join(dir, sn.name)
2183 if stat.S_ISDIR(sn1.mode):
2184 ret = shquote.what_to_add(qtype, lastword, fullname+'/',
2187 ret = shquote.what_to_add(qtype, lastword, fullname,
2188 terminate=True) + ' '
2190 except Exception, e:
2191 log('\nerror in completion: %s\n' % e)
2197 o = options.Options('bup ftp', optspec)
2198 (opt, flags, extra) = o.parse(sys.argv[1:])
2200 git.check_repo_or_die()
2202 top = vfs.RefList(None)
2208 readline.set_completer_delims(' \t\n\r/')
2209 readline.set_completer(completer)
2210 readline.parse_and_bind("tab: complete")
2214 if not line.strip():
2216 words = [word for (wordstart,word) in shquote.quotesplit(line)]
2217 cmd = words[0].lower()
2218 #log('execute: %r %r\n' % (cmd, parm))
2221 for parm in (words[1:] or ['.']):
2222 do_ls(parm, pwd.resolve(parm))
2224 for parm in words[1:]:
2225 pwd = pwd.resolve(parm)
2227 print pwd.fullname()
2229 for parm in words[1:]:
2230 give or take a bitresolve(parm).open(), sys.stdout)
2232 if len(words) not in [2,3]:
2233 raise Exception('Usage: get <filename> [localname]')
2235 (dir,base) = os.path.split(rname)
2236 lname = len(words)>2 and words[2] or base
2237 inf = pwd.resolve(rname).open()
2238 log('Saving %r\n' % lname)
2239 write_to_file(inf, open(lname, 'wb'))
2241 for parm in words[1:]:
2242 (dir,base) = os.path.split(parm)
2243 for n in pwd.resolve(dir).subs():
2244 if fnmatch.fnmatch(n.name, base):
2246 log('Saving %r\n' % n.name)
2248 outf = open(n.name, 'wb')
2249 write_to_file(inf, outf)
2251 except Exception, e:
2252 log(' error: %s\n' % e)
2253 elif cmd == 'help' or cmd == '?':
2254 log('Commands: ls cd pwd cat get mget help quit\n')
2255 elif cmd == 'quit' or cmd == 'exit' or cmd == 'bye':
2258 raise Exception('no such command %r' % cmd)
2259 except Exception, e:
2260 log('error: %s\n' % e)
2262 #!/usr/bin/env python
2264 from bup import options, _hashsplit
2265 from bup.helpers import *
2268 bup random [-S seed] <numbytes>
2270 S,seed= optional random number seed (default 1)
2271 f,force print random data to stdout even if it's a tty
2273 o = options.Options('bup random', optspec)
2274 (opt, flags, extra) = o.parse(sys.argv[1:])
2277 o.fatal("exactly one argument expected")
2279 total = parse_num(extra[0])
2281 if opt.force or (not os.isatty(1) and
2282 not atoi(os.environ.get('BUP_FORCE_TTY')) & 1):
2283 _hashsplit.write_random(sys.stdout.fileno(), total, opt.seed or 0)
2285 log('error: not writing binary data to a terminal. Use -f to force.\n')
2287 #!/usr/bin/env python
2288 import sys, os, glob
2289 from bup import options
2294 o = options.Options('bup help', optspec)
2295 (opt, flags, extra) = o.parse(sys.argv[1:])
2298 # the wrapper program provides the default usage string
2299 os.execvp(os.environ['BUP_MAIN_EXE'], ['bup'])
2300 elif len(extra) == 1:
2301 docname = (extra[0]=='bup' and 'bup' or ('bup-%s' % extra[0]))
2303 (exepath, exefile) = os.path.split(exe)
2304 manpath = os.path.join(exepath, '../Documentation/' + docname + '.[1-9]')
2305 g = glob.glob(manpath)
2307 os.execvp('man', ['man', '-l', g[0]])
2309 os.execvp('man', ['man', docname])
2311 o.fatal("exactly one command name expected")
2312 #!/usr/bin/env python
2313 import sys, os, stat, errno, fuse, re, time, tempfile
2314 from bup import options, git, vfs
2315 from bup.helpers import *
2318 class Stat(fuse.Stat):
2336 def cache_get(top, path):
2337 parts = path.split('/')
2341 #log('cache: %r\n' % cache.keys())
2342 for i in range(max):
2344 #log('cache trying: %r\n' % pre)
2345 c = cache.get(tuple(pre))
2347 rest = parts[max-i:]
2349 #log('resolving %r from %r\n' % (r, c.fullname()))
2351 key = tuple(pre + [r])
2352 #log('saving: %r\n' % (key,))
2360 class BupFs(fuse.Fuse):
2361 def __init__(self, top):
2362 fuse.Fuse.__init__(self)
2365 def getattr(self, path):
2366 log('--getattr(%r)\n' % path)
2368 node = cache_get(self.top, path)
2370 st.st_mode = node.mode
2371 st.st_nlink = node.nlinks()
2372 st.st_size = node.size()
2373 st.st_mtime = node.mtime
2374 st.st_ctime = node.ctime
2375 st.st_atime = node.atime
2377 except vfs.NoSuchFile:
2378 return -errno.ENOENT
2380 def readdir(self, path, offset):
2381 log('--readdir(%r)\n' % path)
2382 node = cache_get(self.top, path)
2383 yield fuse.Direntry('.')
2384 yield fuse.Direntry('..')
2385 for sub in node.subs():
2386 yield fuse.Direntry(sub.name)
2388 def readlink(self, path):
2389 log('--readlink(%r)\n' % path)
2390 node = cache_get(self.top, path)
2391 return node.readlink()
2393 def open(self, path, flags):
2394 log('--open(%r)\n' % path)
2395 node = cache_get(self.top, path)
2396 accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
2397 if (flags & accmode) != os.O_RDONLY:
2398 return -errno.EACCES
2401 def release(self, path, flags):
2402 log('--release(%r)\n' % path)
2404 def read(self, path, size, offset):
2405 log('--read(%r)\n' % path)
2406 n = cache_get(self.top, path)
2412 if not hasattr(fuse, '__version__'):
2413 raise RuntimeError, "your fuse module is too old for fuse.__version__"
2414 fuse.fuse_python_api = (0, 2)
2418 bup fuse [-d] [-f] <mountpoint>
2420 d,debug increase debug level
2421 f,foreground run in foreground
2423 o = options.Options('bup fuse', optspec)
2424 (opt, flags, extra) = o.parse(sys.argv[1:])
2427 o.fatal("exactly one argument expected")
2429 git.check_repo_or_die()
2430 top = vfs.RefList(None)
2432 f.fuse_args.mountpoint = extra[0]
2434 f.fuse_args.add('debug')
2436 f.fuse_args.setmod('foreground')
2437 print f.multithreaded
2438 f.multithreaded = False
2441 #!/usr/bin/env python
2442 from bup import git, options, client
2443 from bup.helpers import *
2446 [BUP_DIR=...] bup init [-r host:path]
2448 r,remote= remote repository path
2450 o = options.Options('bup init', optspec)
2451 (opt, flags, extra) = o.parse(sys.argv[1:])
2454 o.fatal("no arguments expected")
2458 git.init_repo() # local repo
2459 git.check_repo_or_die()
2460 cli = client.Client(opt.remote, create=True)
2464 #!/usr/bin/env python
2465 import sys, math, struct, glob
2466 from bup import options, git
2467 from bup.helpers import *
2470 SHA_PER_PAGE=PAGE_SIZE/200.
2473 def merge(idxlist, bits, table):
2475 for e in git.idxmerge(idxlist):
2477 prefix = git.extract_bits(e, bits)
2478 table[prefix] = count
2482 def do_midx(outdir, outfilename, infilenames):
2485 sum = Sha1('\0'.join(infilenames)).hexdigest()
2486 outfilename = '%s/midx-%s.midx' % (outdir, sum)
2490 for name in infilenames:
2491 ix = git.PackIdx(name)
2495 log('Merging %d indexes (%d objects).\n' % (len(infilenames), total))
2496 if (not opt.force and (total < 1024 and len(infilenames) < 3)) \
2497 or (opt.force and not total):
2498 log('midx: nothing to do.\n')
2501 pages = int(total/SHA_PER_PAGE) or 1
2502 bits = int(math.ceil(math.log(pages, 2)))
2504 log('Table size: %d (%d bits)\n' % (entries*4, bits))
2509 os.unlink(outfilename)
2512 f = open(outfilename + '.tmp', 'w+')
2513 f.write('MIDX\0\0\0\2')
2514 f.write(struct.pack('!I', bits))
2515 assert(f.tell() == 12)
2516 f.write('\0'*4*entries)
2518 for e in merge(inp, bits, table):
2521 f.write('\0'.join(os.path.basename(p) for p in infilenames))
2524 f.write(struct.pack('!%dI' % entries, *table))
2526 os.rename(outfilename + '.tmp', outfilename)
2528 # this is just for testing
2530 p = git.PackMidx(outfilename)
2531 assert(len(p.idxnames) == len(infilenames))
2533 assert(len(p) == total)
2535 for i in merge(inp, total, bits, table):
2536 assert(i == pi.next())
2542 bup midx [options...] <idxnames...>
2544 o,output= output midx filename (default: auto-generated)
2545 a,auto automatically create .midx from any unindexed .idx files
2546 f,force automatically create .midx from *all* .idx files
2548 o = options.Options('bup midx', optspec)
2549 (opt, flags, extra) = o.parse(sys.argv[1:])
2551 if extra and (opt.auto or opt.force):
2552 o.fatal("you can't use -f/-a and also provide filenames")
2554 git.check_repo_or_die()
2557 do_midx(git.repo('objects/pack'), opt.output, extra)
2558 elif opt.auto or opt.force:
2559 paths = [git.repo('objects/pack')]
2560 paths += glob.glob(git.repo('index-cache/*/.'))
2562 log('midx: scanning %s\n' % path)
2564 do_midx(path, opt.output, glob.glob('%s/*.idx' % path))
2566 m = git.PackIdxList(path)
2568 for pack in m.packs: # only .idx files without a .midx are open
2569 if pack.name.endswith('.idx'):
2570 needed[pack.name] = 1
2572 do_midx(path, opt.output, needed.keys())
2575 o.fatal("you must use -f or -a or provide input filenames")
2576 #!/usr/bin/env python
2577 import sys, os, random
2578 from bup import options
2579 from bup.helpers import *
2585 l.append(chr(random.randrange(0,256)))
2590 bup damage [-n count] [-s maxsize] [-S seed] <filenames...>
2592 WARNING: THIS COMMAND IS EXTREMELY DANGEROUS
2593 n,num= number of blocks to damage
2594 s,size= maximum size of each damaged block
2595 percent= maximum size of each damaged block (as a percent of entire file)
2596 equal spread damage evenly throughout the file
2597 S,seed= random number seed (for repeatable tests)
2599 o = options.Options('bup damage', optspec)
2600 (opt, flags, extra) = o.parse(sys.argv[1:])
2603 o.fatal('filenames expected')
2605 if opt.seed != None:
2606 random.seed(opt.seed)
2609 log('Damaging "%s"...\n' % name)
2610 f = open(name, 'r+b')
2611 st = os.fstat(f.fileno())
2613 if opt.percent or opt.size:
2614 ms1 = int(float(opt.percent or 0)/100.0*size) or size
2615 ms2 = opt.size or size
2616 maxsize = min(ms1, ms2)
2619 chunks = opt.num or 10
2620 chunksize = size/chunks
2621 for r in range(chunks):
2622 sz = random.randrange(1, maxsize+1)
2628 ofs = random.randrange(0, size - sz + 1)
2629 log(' %6d bytes at %d\n' % (sz, ofs))
2631 f.write(randblock(sz))
2633 #!/usr/bin/env python
2634 import sys, struct, mmap
2635 from bup import options, git
2636 from bup.helpers import *
2641 def init_dir(conn, arg):
2643 log('bup server: bupdir initialized: %r\n' % git.repodir)
2647 def set_dir(conn, arg):
2648 git.check_repo_or_die(arg)
2649 log('bup server: bupdir is %r\n' % git.repodir)
2653 def list_indexes(conn, junk):
2654 git.check_repo_or_die()
2655 for f in os.listdir(git.repo('objects/pack')):
2656 if f.endswith('.idx'):
2657 conn.write('%s\n' % f)
2661 def send_index(conn, name):
2662 git.check_repo_or_die()
2663 assert(name.find('/') < 0)
2664 assert(name.endswith('.idx'))
2665 idx = git.PackIdx(git.repo('objects/pack/%s' % name))
2666 conn.write(struct.pack('!I', len(idx.map)))
2671 def receive_objects(conn, junk):
2673 git.check_repo_or_die()
2679 w = git.PackWriter()
2684 raise Exception('object read: expected length header, got EOF\n')
2685 n = struct.unpack('!I', ns)[0]
2686 #log('expecting %d bytes\n' % n)
2688 log('bup server: received %d object%s.\n'
2689 % (w.count, w.count!=1 and "s" or ''))
2690 fullpath = w.close()
2692 (dir, name) = os.path.split(fullpath)
2693 conn.write('%s.idx\n' % name)
2696 elif n == 0xffffffff:
2697 log('bup server: receive-objects suspended.\n')
2702 buf = conn.read(n) # object sizes in bup are reasonably small
2703 #log('read %d bytes\n' % n)
2706 raise Exception('object read: expected %d bytes, got %d\n'
2708 (type, content) = git._decode_packobj(buf)
2709 sha = git.calc_hash(type, content)
2710 oldpack = w.exists(sha)
2711 # FIXME: we only suggest a single index per cycle, because the client
2712 # is currently dumb to download more than one per cycle anyway.
2713 # Actually we should fix the client, but this is a minor optimization
2714 # on the server side.
2715 if not suggested and \
2716 oldpack and (oldpack == True or oldpack.endswith('.midx')):
2717 # FIXME: we shouldn't really have to know about midx files
2718 # at this layer. But exists() on a midx doesn't return the
2719 # packname (since it doesn't know)... probably we should just
2720 # fix that deficiency of midx files eventually, although it'll
2721 # make the files bigger. This method is certainly not very
2723 w.objcache.refresh(skip_midx = True)
2724 oldpack = w.objcache.exists(sha)
2725 log('new suggestion: %r\n' % oldpack)
2727 assert(oldpack != True)
2728 assert(not oldpack.endswith('.midx'))
2729 w.objcache.refresh(skip_midx = False)
2730 if not suggested and oldpack:
2731 assert(oldpack.endswith('.idx'))
2732 (dir,name) = os.path.split(oldpack)
2733 if not (name in suggested):
2734 log("bup server: suggesting index %s\n" % name)
2735 conn.write('index %s\n' % name)
2742 def read_ref(conn, refname):
2743 git.check_repo_or_die()
2744 r = git.read_ref(refname)
2745 conn.write('%s\n' % (r or '').encode('hex'))
2749 def update_ref(conn, refname):
2750 git.check_repo_or_die()
2751 newval = conn.readline().strip()
2752 oldval = conn.readline().strip()
2753 git.update_ref(refname, newval.decode('hex'), oldval.decode('hex'))
2758 git.check_repo_or_die()
2760 for blob in git.cat(id):
2761 conn.write(struct.pack('!I', len(blob)))
2764 log('server: error: %s\n' % e)
2765 conn.write('\0\0\0\0')
2768 conn.write('\0\0\0\0')
2775 o = options.Options('bup server', optspec)
2776 (opt, flags, extra) = o.parse(sys.argv[1:])
2779 o.fatal('no arguments expected')
2781 log('bup server: reading from stdin.\n')
2784 'init-dir': init_dir,
2786 'list-indexes': list_indexes,
2787 'send-index': send_index,
2788 'receive-objects': receive_objects,
2789 'read-ref': read_ref,
2790 'update-ref': update_ref,
2794 # FIXME: this protocol is totally lame and not at all future-proof.
2795 # (Especially since we abort completely as soon as *anything* bad happens)
2796 conn = Conn(sys.stdin, sys.stdout)
2797 lr = linereader(conn)
2799 line = _line.strip()
2802 log('bup server: command: %r\n' % line)
2803 words = line.split(' ', 1)
2805 rest = len(words)>1 and words[1] or ''
2809 cmd = commands.get(cmd)
2813 raise Exception('unknown server command: %r\n' % line)
2815 log('bup server: done\n')
2816 #!/usr/bin/env python
2817 import sys, time, struct
2818 from bup import hashsplit, git, options, client
2819 from bup.helpers import *
2820 from subprocess import PIPE
2824 bup join [-r host:path] [refs or hashes...]
2826 r,remote= remote repository path
2828 o = options.Options('bup join', optspec)
2829 (opt, flags, extra) = o.parse(sys.argv[1:])
2831 git.check_repo_or_die()
2834 extra = linereader(sys.stdin)
2839 cli = client.Client(opt.remote)
2847 for blob in cat(id):
2848 sys.stdout.write(blob)
2851 log('error: %s\n' % e)
2855 #!/usr/bin/env python
2856 import sys, re, errno, stat, time, math
2857 from bup import hashsplit, git, options, index, client
2858 from bup.helpers import *
2862 bup save [-tc] [-n name] <filenames...>
2864 r,remote= remote repository path
2865 t,tree output a tree id
2866 c,commit output a commit id
2867 n,name= name of backup set to update (if any)
2868 v,verbose increase log output (can be used more than once)
2869 q,quiet don't show progress meter
2870 smaller= only back up files smaller than n bytes
2872 o = options.Options('bup save', optspec)
2873 (opt, flags, extra) = o.parse(sys.argv[1:])
2875 git.check_repo_or_die()
2876 if not (opt.tree or opt.commit or opt.name):
2877 o.fatal("use one or more of -t, -c, -n")
2879 o.fatal("no filenames given")
2881 opt.progress = (istty and not opt.quiet)
2882 opt.smaller = parse_num(opt.smaller or 0)
2884 is_reverse = os.environ.get('BUP_SERVER_REVERSE')
2885 if is_reverse and opt.remote:
2886 o.fatal("don't use -r in reverse mode; it's automatic")
2888 refname = opt.name and 'refs/heads/%s' % opt.name or None
2889 if opt.remote or is_reverse:
2890 cli = client.Client(opt.remote)
2891 oldref = refname and cli.read_ref(refname) or None
2892 w = cli.new_packwriter()
2895 oldref = refname and git.read_ref(refname) or None
2896 w = git.PackWriter()
2902 if dir.endswith('/'):
2916 def _pop(force_tree):
2917 assert(len(parts) >= 1)
2919 shalist = shalists.pop()
2920 tree = force_tree or w.new_tree(shalist)
2922 shalists[-1].append(('40000', part, tree))
2923 else: # this was the toplevel, so put it back for sanity
2924 shalists.append(shalist)
2928 def progress_report(n):
2929 global count, subcount, lastremain
2931 cc = count + subcount
2932 pct = total and (cc*100.0/total) or 0
2934 elapsed = now - tstart
2935 kps = elapsed and int(cc/1024./elapsed)
2936 kps_frac = 10 ** int(math.log(kps+1, 10) - 1)
2937 kps = int(kps/kps_frac)*kps_frac
2939 remain = elapsed*1.0/cc * (total-cc)
2942 if (lastremain and (remain > lastremain)
2943 and ((remain - lastremain)/lastremain < 0.05)):
2947 hours = int(remain/60/60)
2948 mins = int(remain/60 - hours*60)
2949 secs = int(remain - hours*60*60 - mins*60)
2954 kpsstr = '%dk/s' % kps
2956 remainstr = '%dh%dm' % (hours, mins)
2958 remainstr = '%dm%d' % (mins, secs)
2960 remainstr = '%ds' % secs
2961 progress('Saving: %.2f%% (%d/%dk, %d/%d files) %s %s\r'
2962 % (pct, cc/1024, total/1024, fcount, ftotal,
2966 r = index.Reader(git.repo('bupindex'))
2968 def already_saved(ent):
2969 return ent.is_valid() and w.exists(ent.sha) and ent.sha
2971 def wantrecurse_pre(ent):
2972 return not already_saved(ent)
2974 def wantrecurse_during(ent):
2975 return not already_saved(ent) or ent.sha_missing()
2979 for (transname,ent) in r.filter(extra, wantrecurse=wantrecurse_pre):
2980 if not (ftotal % 10024):
2981 progress('Reading index: %d\r' % ftotal)
2982 exists = ent.exists()
2983 hashvalid = already_saved(ent)
2984 ent.set_sha_missing(not hashvalid)
2985 if not opt.smaller or ent.size < opt.smaller:
2986 if exists and not hashvalid:
2989 progress('Reading index: %d, done.\n' % ftotal)
2990 hashsplit.progress_callback = progress_report
2992 tstart = time.time()
2993 count = subcount = fcount = 0
2994 lastskip_name = None
2996 for (transname,ent) in r.filter(extra, wantrecurse=wantrecurse_during):
2997 (dir, file) = os.path.split(ent.name)
2998 exists = (ent.flags & index.IX_EXISTS)
2999 hashvalid = already_saved(ent)
3000 wasmissing = ent.sha_missing()
3006 if ent.sha == index.EMPTY_SHA:
3012 if opt.verbose >= 2:
3013 log('%s %-70s\n' % (status, ent.name))
3014 elif not stat.S_ISDIR(ent.mode) and lastdir != dir:
3015 if not lastdir.startswith(dir):
3016 log('%s %-70s\n' % (status, os.path.join(dir, '')))
3025 if opt.smaller and ent.size >= opt.smaller:
3026 if exists and not hashvalid:
3027 add_error('skipping large file "%s"' % ent.name)
3028 lastskip_name = ent.name
3031 assert(dir.startswith('/'))
3032 dirp = dir.split('/')
3034 _pop(force_tree = None)
3036 for part in dirp[len(parts):]:
3040 # no filename portion means this is a subdir. But
3041 # sub/parentdirectories already handled in the pop/push() part above.
3042 oldtree = already_saved(ent) # may be None
3043 newtree = _pop(force_tree = oldtree)
3045 if lastskip_name and lastskip_name.startswith(ent.name):
3048 ent.validate(040000, newtree)
3050 if exists and wasmissing:
3054 # it's not a directory
3057 mode = '%o' % ent.gitmode
3059 shalists[-1].append((mode,
3060 git.mangle_name(file, ent.mode, ent.gitmode),
3063 if stat.S_ISREG(ent.mode):
3065 f = hashsplit.open_noatime(ent.name)
3068 lastskip_name = ent.name
3071 lastskip_name = ent.name
3073 (mode, id) = hashsplit.split_to_blob_or_tree(w, [f])
3075 if stat.S_ISDIR(ent.mode):
3076 assert(0) # handled above
3077 elif stat.S_ISLNK(ent.mode):
3079 rl = os.readlink(ent.name)
3082 lastskip_name = ent.name
3085 lastskip_name = ent.name
3087 (mode, id) = ('120000', w.new_blob(rl))
3089 add_error(Exception('skipping special file "%s"' % ent.name))
3090 lastskip_name = ent.name
3092 ent.validate(int(mode, 8), id)
3094 shalists[-1].append((mode,
3095 git.mangle_name(file, ent.mode, ent.gitmode),
3097 if exists and wasmissing:
3103 pct = total and count*100.0/total or 100
3104 progress('Saving: %.2f%% (%d/%dk, %d/%d files), done. \n'
3105 % (pct, count/1024, total/1024, fcount, ftotal))
3107 while len(parts) > 1:
3108 _pop(force_tree = None)
3109 assert(len(shalists) == 1)
3110 tree = w.new_tree(shalists[-1])
3112 print tree.encode('hex')
3113 if opt.commit or opt.name:
3114 msg = 'bup save\n\nGenerated by command:\n%r' % sys.argv
3115 ref = opt.name and ('refs/heads/%s' % opt.name) or None
3116 commit = w.new_commit(oldref, tree, msg)
3118 print commit.encode('hex')
3120 w.close() # must close before we can update the ref
3124 cli.update_ref(refname, commit, oldref)
3126 git.update_ref(refname, commit, oldref)
3132 log('WARNING: %d errors encountered while saving.\n' % len(saved_errors))
3134 #!/usr/bin/env python
3136 from bup import options
3141 o = options.Options('bup tick', optspec)
3142 (opt, flags, extra) = o.parse(sys.argv[1:])
3145 o.fatal("no arguments expected")
3148 tleft = 1 - (t - int(t))
3150 #!/usr/bin/env python
3151 import os, sys, stat, time
3152 from bup import options, git, index, drecurse
3153 from bup.helpers import *
3156 def merge_indexes(out, r1, r2):
3157 for e in index.MergeIter([r1, r2]):
3158 # FIXME: shouldn't we remove deleted entries eventually? When?
3163 def __init__(self, l):
3170 self.cur = self.i.next()
3171 except StopIteration:
3176 def check_index(reader):
3178 log('check: checking forward iteration...\n')
3181 for e in reader.forward_iter():
3184 log('%08x+%-4d %r\n' % (e.children_ofs, e.children_n,
3186 assert(e.children_ofs)
3187 assert(e.name.endswith('/'))
3188 assert(not d.get(e.children_ofs))
3189 d[e.children_ofs] = 1
3190 if e.flags & index.IX_HASHVALID:
3191 assert(e.sha != index.EMPTY_SHA)
3193 assert(not e or e.name == '/') # last entry is *always* /
3194 log('check: checking normal iteration...\n')
3198 assert(last > e.name)
3201 log('index error! at %r\n' % e)
3203 log('check: passed.\n')
3206 def update_index(top):
3207 ri = index.Reader(indexfile)
3208 wi = index.Writer(indexfile)
3209 rig = IterHelper(ri.iter(name=top))
3210 tstart = int(time.time())
3215 return (0100644, index.FAKE_SHA)
3218 for (path,pst) in drecurse.recursive_dirlist([top], xdev=opt.xdev):
3219 if opt.verbose>=2 or (opt.verbose==1 and stat.S_ISDIR(pst.st_mode)):
3220 sys.stdout.write('%s\n' % path)
3222 progress('Indexing: %d\r' % total)
3223 elif not (total % 128):
3224 progress('Indexing: %d\r' % total)
3226 while rig.cur and rig.cur.name > path: # deleted paths
3227 if rig.cur.exists():
3228 rig.cur.set_deleted()
3231 if rig.cur and rig.cur.name == path: # paths that already existed
3233 rig.cur.from_stat(pst, tstart)
3234 if not (rig.cur.flags & index.IX_HASHVALID):
3236 (rig.cur.gitmode, rig.cur.sha) = hashgen(path)
3237 rig.cur.flags |= index.IX_HASHVALID
3238 if opt.fake_invalid:
3239 rig.cur.invalidate()
3243 wi.add(path, pst, hashgen = hashgen)
3244 progress('Indexing: %d, done.\n' % total)
3250 wr = wi.new_reader()
3252 log('check: before merging: oldfile\n')
3254 log('check: before merging: newfile\n')
3256 mi = index.Writer(indexfile)
3257 merge_indexes(mi, ri, wr)
3267 bup index <-p|m|u> [options...] <filenames...>
3269 p,print print the index entries for the given names (also works with -u)
3270 m,modified print only added/deleted/modified files (implies -p)
3271 s,status print each filename with a status char (A/M/D) (implies -p)
3272 H,hash print the hash for each object next to its name (implies -p)
3273 l,long print more information about each file
3274 u,update (recursively) update the index entries for the given filenames
3275 x,xdev,one-file-system don't cross filesystem boundaries
3276 fake-valid mark all index entries as up-to-date even if they aren't
3277 fake-invalid mark all index entries as invalid
3278 check carefully check index file integrity
3279 f,indexfile= the name of the index file (default 'index')
3280 v,verbose increase log output (can be used more than once)
3282 o = options.Options('bup index', optspec)
3283 (opt, flags, extra) = o.parse(sys.argv[1:])
3285 if not (opt.modified or opt['print'] or opt.status or opt.update or opt.check):
3286 o.fatal('supply one or more of -p, -s, -m, -u, or --check')
3287 if (opt.fake_valid or opt.fake_invalid) and not opt.update:
3288 o.fatal('--fake-{in,}valid are meaningless without -u')
3289 if opt.fake_valid and opt.fake_invalid:
3290 o.fatal('--fake-valid is incompatible with --fake-invalid')
3292 git.check_repo_or_die()
3293 indexfile = opt.indexfile or git.repo('bupindex')
3298 log('check: starting initial check.\n')
3299 check_index(index.Reader(indexfile))
3301 paths = index.reduce_paths(extra)
3305 o.fatal('update (-u) requested but no paths given')
3306 for (rp,path) in paths:
3309 if opt['print'] or opt.status or opt.modified:
3310 for (name, ent) in index.Reader(indexfile).filter(extra or ['']):
3312 and (ent.is_valid() or ent.is_deleted() or not ent.mode)):
3316 if ent.is_deleted():
3318 elif not ent.is_valid():
3319 if ent.sha == index.EMPTY_SHA:
3326 line += ent.sha.encode('hex') + ' '
3328 line += "%7s %7s " % (oct(ent.mode), oct(ent.gitmode))
3329 print line + (name or './')
3331 if opt.check and (opt['print'] or opt.status or opt.modified or opt.update):
3332 log('check: starting final check.\n')
3333 check_index(index.Reader(indexfile))
3336 log('WARNING: %d errors encountered.\n' % len(saved_errors))
3338 #!/usr/bin/env python
3339 import sys, os, struct
3340 from bup import options, helpers
3345 This command is not intended to be run manually.
3347 o = options.Options('bup rbackup-server', optspec)
3348 (opt, flags, extra) = o.parse(sys.argv[1:])
3350 o.fatal('no arguments expected')
3352 # get the subcommand's argv.
3353 # Normally we could just pass this on the command line, but since we'll often
3354 # be getting called on the other end of an ssh pipe, which tends to mangle
3355 # argv (by sending it via the shell), this way is much safer.
3356 buf = sys.stdin.read(4)
3357 sz = struct.unpack('!I', buf)[0]
3359 assert(sz < 1000000)
3360 buf = sys.stdin.read(sz)
3361 assert(len(buf) == sz)
3362 argv = buf.split('\0')
3364 # stdin/stdout are supposedly connected to 'bup server' that the caller
3365 # started for us (often on the other end of an ssh tunnel), so we don't want
3366 # to misuse them. Move them out of the way, then replace stdout with
3367 # a pointer to stderr in case our subcommand wants to do something with it.
3369 # It might be nice to do the same with stdin, but my experiments showed that
3370 # ssh seems to make its child's stderr a readable-but-never-reads-anything
3371 # socket. They really should have used shutdown(SHUT_WR) on the other end
3372 # of it, but probably didn't. Anyway, it's too messy, so let's just make sure
3373 # anyone reading from stdin is disappointed.
3375 # (You can't just leave stdin/stdout "not open" by closing the file
3376 # descriptors. Then the next file that opens is automatically assigned 0 or 1,
3377 # and people *trying* to read/write stdin/stdout get screwed.)
3381 fd = os.open('/dev/null', os.O_RDONLY)
3385 os.environ['BUP_SERVER_REVERSE'] = helpers.hostname()
3386 os.execvp(argv[0], argv)
3388 #!/usr/bin/env python
3389 import sys, os, glob, subprocess, time
3390 from bup import options, git
3391 from bup.helpers import *
3394 nullf = open('/dev/null')
3401 # at least in python 2.5, using "stdout=2" or "stdout=sys.stderr" below
3402 # doesn't actually work, because subprocess closes fd #2 right before
3403 # execing for some reason. So we work around it by duplicating the fd
3405 fd = os.dup(2) # copy stderr
3407 p = subprocess.Popen(argv, stdout=fd, close_fds=False)
3416 p = subprocess.Popen(['par2', '--help'],
3417 stdout=nullf, stderr=nullf, stdin=nullf)
3420 log('fsck: warning: par2 not found; disabling recovery features.\n')
3425 if opt.verbose >= lvl:
3433 def par2_generate(base):
3434 return run(['par2', 'create', '-n1', '-c200'] + parv(2)
3435 + ['--', base, base+'.pack', base+'.idx'])
3437 def par2_verify(base):
3438 return run(['par2', 'verify'] + parv(3) + ['--', base])
3440 def par2_repair(base):
3441 return run(['par2', 'repair'] + parv(2) + ['--', base])
3443 def quick_verify(base):
3444 f = open(base + '.pack', 'rb')
3446 wantsum = f.read(20)
3447 assert(len(wantsum) == 20)
3450 for b in chunkyreader(f, os.fstat(f.fileno()).st_size - 20):
3452 if sum.digest() != wantsum:
3453 raise ValueError('expected %r, got %r' % (wantsum.encode('hex'),
3457 def git_verify(base):
3461 except Exception, e:
3462 debug('error: %s\n' % e)
3466 return run(['git', 'verify-pack', '--', base])
3469 def do_pack(base, last):
3471 if par2_ok and par2_exists and (opt.repair or not opt.generate):
3472 vresult = par2_verify(base)
3475 rresult = par2_repair(base)
3477 print '%s par2 repair: failed (%d)' % (last, rresult)
3480 print '%s par2 repair: succeeded (0)' % last
3483 print '%s par2 verify: failed (%d)' % (last, vresult)
3486 print '%s ok' % last
3487 elif not opt.generate or (par2_ok and not par2_exists):
3488 gresult = git_verify(base)
3490 print '%s git verify: failed (%d)' % (last, gresult)
3493 if par2_ok and opt.generate:
3494 presult = par2_generate(base)
3496 print '%s par2 create: failed (%d)' % (last, presult)
3499 print '%s ok' % last
3501 print '%s ok' % last
3503 assert(opt.generate and (not par2_ok or par2_exists))
3504 debug(' skipped: par2 file already generated.\n')
3509 bup fsck [options...] [filenames...]
3511 r,repair attempt to repair errors using par2 (dangerous!)
3512 g,generate generate auto-repair information using par2
3513 v,verbose increase verbosity (can be used more than once)
3514 quick just check pack sha1sum, don't use git verify-pack
3515 j,jobs= run 'n' jobs in parallel
3516 par2-ok immediately return 0 if par2 is ok, 1 if not
3517 disable-par2 ignore par2 even if it is available
3519 o = options.Options('bup fsck', optspec)
3520 (opt, flags, extra) = o.parse(sys.argv[1:])
3525 sys.exit(0) # 'true' in sh
3528 if opt.disable_par2:
3531 git.check_repo_or_die()
3534 debug('fsck: No filenames given: checking all packs.\n')
3535 extra = glob.glob(git.repo('objects/pack/*.pack'))
3541 if name.endswith('.pack'):
3543 elif name.endswith('.idx'):
3545 elif name.endswith('.par2'):
3547 elif os.path.exists(name + '.pack'):
3550 raise Exception('%s is not a pack file!' % name)
3551 (dir,last) = os.path.split(base)
3552 par2_exists = os.path.exists(base + '.par2')
3553 if par2_exists and os.stat(base + '.par2').st_size == 0:
3556 debug('fsck: checking %s (%s)\n'
3557 % (last, par2_ok and par2_exists and 'par2' or 'git'))
3559 progress('fsck (%d/%d)\r' % (count, len(extra)))
3562 nc = do_pack(base, last)
3566 while len(outstanding) >= opt.jobs:
3567 (pid,nc) = os.wait()
3569 if pid in outstanding:
3570 del outstanding[pid]
3575 outstanding[pid] = 1
3578 sys.exit(do_pack(base, last))
3579 except Exception, e:
3580 log('exception: %r\n' % e)
3583 while len(outstanding):
3584 (pid,nc) = os.wait()
3586 if pid in outstanding:
3587 del outstanding[pid]
3591 progress('fsck (%d/%d)\r' % (count, len(extra)))
3593 if not opt.verbose and istty:
3594 log('fsck done. \n')
3596 #!/usr/bin/env python
3597 import sys, os, struct, getopt, subprocess, signal
3598 from bup import options, ssh
3599 from bup.helpers import *
3602 bup rbackup <hostname> index ...
3603 bup rbackup <hostname> save ...
3604 bup rbackup <hostname> split ...
3606 o = options.Options('bup rbackup', optspec, optfunc=getopt.getopt)
3607 (opt, flags, extra) = o.parse(sys.argv[1:])
3609 o.fatal('arguments expected')
3611 class SigException(Exception):
3612 def __init__(self, signum):
3613 self.signum = signum
3614 Exception.__init__(self, 'signal %d received' % signum)
3615 def handler(signum, frame):
3616 raise SigException(signum)
3618 signal.signal(signal.SIGTERM, handler)
3619 signal.signal(signal.SIGINT, handler)
3628 p = ssh.connect(hostname, 'rbackup-server')
3630 argvs = '\0'.join(['bup'] + argv)
3631 p.stdin.write(struct.pack('!I', len(argvs)) + argvs)
3634 main_exe = os.environ.get('BUP_MAIN_EXE') or sys.argv[0]
3635 sp = subprocess.Popen([main_exe, 'server'], stdin=p.stdout, stdout=p.stdin)
3642 # if we get a signal while waiting, we have to keep waiting, just
3643 # in case our child doesn't die.
3648 except SigException, e:
3649 log('\nbup rbackup: %s\n' % e)
3650 os.kill(p.pid, e.signum)
3653 #!/usr/bin/env python
3655 from bup import options
3660 o = options.Options('bup newliner', optspec)
3661 (opt, flags, extra) = o.parse(sys.argv[1:])
3664 o.fatal("no arguments expected")
3666 r = re.compile(r'([\r\n])')
3673 b = os.read(sys.stdin.fileno(), 4096)
3674 except KeyboardInterrupt:
3681 (line, splitchar, all) = l
3683 sys.stdout.write('%-*s%s' % (lastlen, line, splitchar))
3684 if splitchar == '\r':
3691 sys.stdout.write('%-*s\n' % (lastlen, all))
3692 #!/usr/bin/env python
3694 from bup import options, git, _hashsplit
3695 from bup.helpers import *
3701 o = options.Options('bup margin', optspec)
3702 (opt, flags, extra) = o.parse(sys.argv[1:])
3705 o.fatal("no arguments expected")
3707 git.check_repo_or_die()
3708 #git.ignore_midx = 1
3710 mi = git.PackIdxList(git.repo('objects/pack'))
3716 #assert(str(i) >= last)
3717 pm = _hashsplit.bitmatch(last, i)
3718 longmatch = max(longmatch, pm)
3721 #!/usr/bin/env python
3722 from bup import options, drecurse
3723 from bup.helpers import *
3728 x,xdev,one-file-system don't cross filesystem boundaries
3729 q,quiet don't actually print filenames
3730 profile run under the python profiler
3732 o = options.Options('bup drecurse', optspec)
3733 (opt, flags, extra) = o.parse(sys.argv[1:])
3736 o.fatal("exactly one filename expected")
3738 it = drecurse.recursive_dirlist(extra, opt.xdev)
3744 cProfile.run('do_it()')
3750 for (name,st) in it:
3754 log('WARNING: %d errors encountered.\n' % len(saved_errors))
3756 #!/usr/bin/env python
3757 import sys, time, struct
3758 from bup import hashsplit, git, options, client
3759 from bup.helpers import *
3760 from subprocess import PIPE
3764 bup split [-tcb] [-n name] [--bench] [filenames...]
3766 r,remote= remote repository path
3767 b,blobs output a series of blob ids
3768 t,tree output a tree id
3769 c,commit output a commit id
3770 n,name= name of backup set to update (if any)
3771 N,noop don't actually save the data anywhere
3772 q,quiet don't print progress messages
3773 v,verbose increase log output (can be used more than once)
3774 copy just copy input to output, hashsplitting along the way
3775 bench print benchmark timings to stderr
3776 max-pack-size= maximum bytes in a single pack
3777 max-pack-objects= maximum number of objects in a single pack
3778 fanout= maximum number of blobs in a single tree
3780 o = options.Options('bup split', optspec)
3781 (opt, flags, extra) = o.parse(sys.argv[1:])
3783 git.check_repo_or_die()
3784 if not (opt.blobs or opt.tree or opt.commit or opt.name or
3785 opt.noop or opt.copy):
3786 o.fatal("use one or more of -b, -t, -c, -n, -N, --copy")
3787 if (opt.noop or opt.copy) and (opt.blobs or opt.tree or
3788 opt.commit or opt.name):
3789 o.fatal('-N is incompatible with -b, -t, -c, -n')
3791 if opt.verbose >= 2:
3792 git.verbose = opt.verbose - 1
3794 if opt.max_pack_size:
3795 hashsplit.max_pack_size = parse_num(opt.max_pack_size)
3796 if opt.max_pack_objects:
3797 hashsplit.max_pack_objects = parse_num(opt.max_pack_objects)
3799 hashsplit.fanout = parse_num(opt.fanout)
3801 hashsplit.fanout = 0
3803 is_reverse = os.environ.get('BUP_SERVER_REVERSE')
3804 if is_reverse and opt.remote:
3805 o.fatal("don't use -r in reverse mode; it's automatic")
3806 start_time = time.time()
3808 refname = opt.name and 'refs/heads/%s' % opt.name or None
3809 if opt.noop or opt.copy:
3810 cli = w = oldref = None
3811 elif opt.remote or is_reverse:
3812 cli = client.Client(opt.remote)
3813 oldref = refname and cli.read_ref(refname) or None
3814 w = cli.new_packwriter()
3817 oldref = refname and git.read_ref(refname) or None
3818 w = git.PackWriter()
3820 files = extra and (open(fn) for fn in extra) or [sys.stdin]
3822 shalist = hashsplit.split_to_shalist(w, files)
3823 tree = w.new_tree(shalist)
3826 for (blob, bits) in hashsplit.hashsplit_iter(files):
3827 hashsplit.total_split += len(blob)
3829 sys.stdout.write(str(blob))
3830 megs = hashsplit.total_split/1024/1024
3831 if not opt.quiet and last != megs:
3832 progress('%d Mbytes read\r' % megs)
3834 progress('%d Mbytes read, done.\n' % megs)
3839 for (mode,name,bin) in shalist:
3840 print bin.encode('hex')
3842 print tree.encode('hex')
3843 if opt.commit or opt.name:
3844 msg = 'bup split\n\nGenerated by command:\n%r' % sys.argv
3845 ref = opt.name and ('refs/heads/%s' % opt.name) or None
3846 commit = w.new_commit(oldref, tree, msg)
3848 print commit.encode('hex')
3851 w.close() # must close before we can update the ref
3855 cli.update_ref(refname, commit, oldref)
3857 git.update_ref(refname, commit, oldref)
3862 secs = time.time() - start_time
3863 size = hashsplit.total_split
3865 log('\nbup: %.2fkbytes in %.2f secs = %.2f kbytes/sec\n'
3866 % (size/1024., secs, size/1024./secs))
3867 #!/usr/bin/env python
3868 import sys, re, struct, mmap
3869 from bup import git, options
3870 from bup.helpers import *
3873 def s_from_bytes(bytes):
3874 clist = [chr(b) for b in bytes]
3875 return ''.join(clist)
3879 fields = ['VmSize', 'VmRSS', 'VmData', 'VmStk']
3881 for line in open('/proc/self/status').readlines():
3882 l = re.split(r':\s*', line.strip(), 1)
3886 fields = [d[k] for k in fields]
3889 print ('%9s ' + ('%10s ' * len(fields))) % tuple([e1] + fields)
3894 bup memtest [-n elements] [-c cycles]
3896 n,number= number of objects per cycle
3897 c,cycles= number of cycles to run
3898 ignore-midx ignore .midx files, use only .idx files
3900 o = options.Options('bup memtest', optspec)
3901 (opt, flags, extra) = o.parse(sys.argv[1:])
3904 o.fatal('no arguments expected')
3906 git.ignore_midx = opt.ignore_midx
3908 git.check_repo_or_die()
3909 m = git.PackIdxList(git.repo('objects/pack'))
3911 cycles = opt.cycles or 100
3912 number = opt.number or 10000
3915 f = open('/dev/urandom')
3916 a = mmap.mmap(-1, 20)
3918 for c in xrange(cycles):
3919 for n in xrange(number):
3922 bytes = list(struct.unpack('!BBB', b)) + [0]*17
3924 bin = struct.pack('!20s', s_from_bytes(bytes))
3927 a[2] = chr(ord(b[2]) & 0xf0)
3929 #print bin.encode('hex')
3931 report((c+1)*number)
3932 #!/usr/bin/env python
3933 import sys, os, stat
3934 from bup import options, git, vfs
3935 from bup.helpers import *
3937 def print_node(text, n):
3940 prefix += "%s " % n.hash.encode('hex')
3941 if stat.S_ISDIR(n.mode):
3942 print '%s%s/' % (prefix, text)
3943 elif stat.S_ISLNK(n.mode):
3944 print '%s%s@' % (prefix, text)
3946 print '%s%s' % (prefix, text)
3952 s,hash show hash for each file
3954 o = options.Options('bup ls', optspec)
3955 (opt, flags, extra) = o.parse(sys.argv[1:])
3957 git.check_repo_or_die()
3958 top = vfs.RefList(None)
3967 if stat.S_ISDIR(n.mode):
3969 print_node(sub.name, sub)
3972 except vfs.NodeError, e:
3973 log('error: %s\n' % e)
3977 #!/usr/bin/env python
3978 import sys, os, re, stat, readline, fnmatch
3979 from bup import options, git, shquote, vfs
3980 from bup.helpers import *
3982 def node_name(text, n):
3983 if stat.S_ISDIR(n.mode):
3985 elif stat.S_ISLNK(n.mode):
3993 if stat.S_ISDIR(n.mode):
3995 l.append(node_name(sub.name, sub))
3997 l.append(node_name(path, n))
3998 print columnate(l, '')
4001 def write_to_file(inf, outf):
4002 for blob in chunkyreader(inf):
4007 if os.isatty(sys.stdin.fileno()):
4010 yield raw_input('bup> ')
4014 for line in sys.stdin:
4018 def _completer_get_subs(line):
4019 (qtype, lastword) = shquote.unfinished_word(line)
4020 (dir,name) = os.path.split(lastword)
4021 #log('\ncompleter: %r %r %r\n' % (qtype, lastword, text))
4022 n = pwd.resolve(dir)
4023 subs = list(filter(lambda x: x.name.startswith(name),
4025 return (dir, name, qtype, lastword, subs)
4030 def completer(text, state):
4034 line = readline.get_line_buffer()[:readline.get_endidx()]
4035 if _last_line != line:
4036 _last_res = _completer_get_subs(line)
4038 (dir, name, qtype, lastword, subs) = _last_res
4039 if state < len(subs):
4041 sn1 = sn.resolve('') # deref symlinks
4042 fullname = os.path.join(dir, sn.name)
4043 if stat.S_ISDIR(sn1.mode):
4044 ret = shquote.what_to_add(qtype, lastword, fullname+'/',
4047 ret = shquote.what_to_add(qtype, lastword, fullname,
4048 terminate=True) + ' '
4050 except Exception, e:
4051 log('\nerror in completion: %s\n' % e)
4057 o = options.Options('bup ftp', optspec)
4058 (opt, flags, extra) = o.parse(sys.argv[1:])
4060 git.check_repo_or_die()
4062 top = vfs.RefList(None)
4068 readline.set_completer_delims(' \t\n\r/')
4069 readline.set_completer(completer)
4070 readline.parse_and_bind("tab: complete")
4074 if not line.strip():
4076 words = [word for (wordstart,word) in shquote.quotesplit(line)]
4077 cmd = words[0].lower()
4078 #log('execute: %r %r\n' % (cmd, parm))
4081 for parm in (words[1:] or ['.']):
4082 do_ls(parm, pwd.resolve(parm))
4084 for parm in words[1:]:
4085 pwd = pwd.resolve(parm)
4087 print pwd.fullname()
4089 for parm in words[1:]:
4090 write_to_file(pwd.resolve(parm).open(), sys.stdout)
4092 if len(words) not in [2,3]:
4093 raise Exception('Usage: get <filename> [localname]')
4095 (dir,base) = os.path.split(rname)
4096 lname = len(words)>2 and words[2] or base
4097 inf = pwd.resolve(rname).open()
4098 log('Saving %r\n' % lname)
4099 write_to_file(inf, open(lname, 'wb'))
4101 for parm in words[1:]:
4102 (dir,base) = os.path.split(parm)
4103 for n in pwd.resolve(dir).subs():
4104 if fnmatch.fnmatch(n.name, base):
4106 log('Saving %r\n' % n.name)
4108 outf = open(n.name, 'wb')
4109 write_to_file(inf, outf)
4111 except Exception, e:
4112 log(' error: %s\n' % e)
4113 elif cmd == 'help' or cmd == '?':
4114 log('Commands: ls cd pwd cat get mget help quit\n')
4115 elif cmd == 'quit' or cmd == 'exit' or cmd == 'bye':
4118 raise Exception('no such command %r' % cmd)
4119 except Exception, e:
4120 log('error: %s\n' % e)
4122 #!/usr/bin/env python
4124 from bup import options, _hashsplit
4125 from bup.helpers import *
4128 bup random [-S seed] <numbytes>
4130 S,seed= optional random number seed (default 1)
4131 f,force print random data to stdout even if it's a tty
4133 o = options.Options('bup random', optspec)
4134 (opt, flags, extra) = o.parse(sys.argv[1:])
4137 o.fatal("exactly one argument expected")
4139 total = parse_num(extra[0])
4141 if opt.force or (not os.isatty(1) and
4142 not atoi(os.environ.get('BUP_FORCE_TTY')) & 1):
4143 _hashsplit.write_random(sys.stdout.fileno(), total, opt.seed or 0)
4145 log('error: not writing binary data to a terminal. Use -f to force.\n')
4147 #!/usr/bin/env python
4148 import sys, os, glob
4149 from bup import options
4154 o = options.Options('bup help', optspec)
4155 (opt, flags, extra) = o.parse(sys.argv[1:])
4158 # the wrapper program provides the default usage string
4159 os.execvp(os.environ['BUP_MAIN_EXE'], ['bup'])
4160 elif len(extra) == 1:
4161 docname = (extra[0]=='bup' and 'bup' or ('bup-%s' % extra[0]))
4163 (exepath, exefile) = os.path.split(exe)
4164 manpath = os.path.join(exepath, '../Documentation/' + docname + '.[1-9]')
4165 g = glob.glob(manpath)
4167 os.execvp('man', ['man', '-l', g[0]])
4169 os.execvp('man', ['man', docname])
4171 o.fatal("exactly one command name expected")
4172 #!/usr/bin/env python
4173 import sys, os, stat, errno, fuse, re, time, tempfile
4174 from bup import options, git, vfs
4175 from bup.helpers import *
4178 class Stat(fuse.Stat):
4196 def cache_get(top, path):
4197 parts = path.split('/')
4201 #log('cache: %r\n' % cache.keys())
4202 for i in range(max):
4204 #log('cache trying: %r\n' % pre)
4205 c = cache.get(tuple(pre))
4207 rest = parts[max-i:]
4209 #log('resolving %r from %r\n' % (r, c.fullname()))
4211 key = tuple(pre + [r])
4212 #log('saving: %r\n' % (key,))
4220 class BupFs(fuse.Fuse):
4221 def __init__(self, top):
4222 fuse.Fuse.__init__(self)
4225 def getattr(self, path):
4226 log('--getattr(%r)\n' % path)
4228 node = cache_get(self.top, path)
4230 st.st_mode = node.mode
4231 st.st_nlink = node.nlinks()
4232 st.st_size = node.size()
4233 st.st_mtime = node.mtime
4234 st.st_ctime = node.ctime
4235 st.st_atime = node.atime
4237 except vfs.NoSuchFile:
4238 return -errno.ENOENT
4240 def readdir(self, path, offset):
4241 log('--readdir(%r)\n' % path)
4242 node = cache_get(self.top, path)
4243 yield fuse.Direntry('.')
4244 yield fuse.Direntry('..')
4245 for sub in node.subs():
4246 yield fuse.Direntry(sub.name)
4248 def readlink(self, path):
4249 log('--readlink(%r)\n' % path)
4250 node = cache_get(self.top, path)
4251 return node.readlink()
4253 def open(self, path, flags):
4254 log('--open(%r)\n' % path)
4255 node = cache_get(self.top, path)
4256 accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
4257 if (flags & accmode) != os.O_RDONLY:
4258 return -errno.EACCES
4261 def release(self, path, flags):
4262 log('--release(%r)\n' % path)
4264 def read(self, path, size, offset):
4265 log('--read(%r)\n' % path)
4266 n = cache_get(self.top, path)
4272 if not hasattr(fuse, '__version__'):
4273 raise RuntimeError, "your fuse module is too old for fuse.__version__"
4274 fuse.fuse_python_api = (0, 2)
4278 bup fuse [-d] [-f] <mountpoint>
4280 d,debug increase debug level
4281 f,foreground run in foreground
4283 o = options.Options('bup fuse', optspec)
4284 (opt, flags, extra) = o.parse(sys.argv[1:])
4287 o.fatal("exactly one argument expected")
4289 git.check_repo_or_die()
4290 top = vfs.RefList(None)
4292 f.fuse_args.mountpoint = extra[0]
4294 f.fuse_args.add('debug')
4296 f.fuse_args.setmod('foreground')
4297 print f.multithreaded
4298 f.multithreaded = False
4301 #!/usr/bin/env python
4302 from bup import git, options, client
4303 from bup.helpers import *
4306 [BUP_DIR=...] bup init [-r host:path]
4308 r,remote= remote repository path
4310 o = options.Options('bup init', optspec)
4311 (opt, flags, extra) = o.parse(sys.argv[1:])
4314 o.fatal("no arguments expected")
4318 git.init_repo() # local repo
4319 git.check_repo_or_die()
4320 cli = client.Client(opt.remote, create=True)
4324 #!/usr/bin/env python
4325 import sys, math, struct, glob
4326 from bup import options, git
4327 from bup.helpers import *
4330 SHA_PER_PAGE=PAGE_SIZE/200.
4333 def merge(idxlist, bits, table):
4335 for e in git.idxmerge(idxlist):
4337 prefix = git.extract_bits(e, bits)
4338 table[prefix] = count
4342 def do_midx(outdir, outfilename, infilenames):
4345 sum = Sha1('\0'.join(infilenames)).hexdigest()
4346 outfilename = '%s/midx-%s.midx' % (outdir, sum)
4350 for name in infilenames:
4351 ix = git.PackIdx(name)
4355 log('Merging %d indexes (%d objects).\n' % (len(infilenames), total))
4356 if (not opt.force and (total < 1024 and len(infilenames) < 3)) \
4357 or (opt.force and not total):
4358 log('midx: nothing to do.\n')
4361 pages = int(total/SHA_PER_PAGE) or 1
4362 bits = int(math.ceil(math.log(pages, 2)))
4364 log('Table size: %d (%d bits)\n' % (entries*4, bits))
4369 os.unlink(outfilename)
4372 f = open(outfilename + '.tmp', 'w+')
4373 f.write('MIDX\0\0\0\2')
4374 f.write(struct.pack('!I', bits))
4375 assert(f.tell() == 12)
4376 f.write('\0'*4*entries)
4378 for e in merge(inp, bits, table):
4381 f.write('\0'.join(os.path.basename(p) for p in infilenames))
4384 f.write(struct.pack('!%dI' % entries, *table))
4386 os.rename(outfilename + '.tmp', outfilename)
4388 # this is just for testing
4390 p = git.PackMidx(outfilename)
4391 assert(len(p.idxnames) == len(infilenames))
4393 assert(len(p) == total)
4395 for i in merge(inp, total, bits, table):
4396 assert(i == pi.next())
4402 bup midx [options...] <idxnames...>
4404 o,output= output midx filename (default: auto-generated)
4405 a,auto automatically create .midx from any unindexed .idx files
4406 f,force automatically create .midx from *all* .idx files
4408 o = options.Options('bup midx', optspec)
4409 (opt, flags, extra) = o.parse(sys.argv[1:])
4411 if extra and (opt.auto or opt.force):
4412 o.fatal("you can't use -f/-a and also provide filenames")
4414 git.check_repo_or_die()
4417 do_midx(git.repo('objects/pack'), opt.output, extra)
4418 elif opt.auto or opt.force:
4419 paths = [git.repo('objects/pack')]
4420 paths += glob.glob(git.repo('index-cache/*/.'))
4422 log('midx: scanning %s\n' % path)
4424 do_midx(path, opt.output, glob.glob('%s/*.idx' % path))
4426 m = git.PackIdxList(path)
4428 for pack in m.packs: # only .idx files without a .midx are open
4429 if pack.name.endswith('.idx'):
4430 needed[pack.name] = 1
4432 do_midx(path, opt.output, needed.keys())
4435 o.fatal("you must use -f or -a or provide input filenames")
4436 #!/usr/bin/env python
4437 import sys, os, random
4438 from bup import options
4439 from bup.helpers import *
4445 l.append(chr(random.randrange(0,256)))
4450 bup damage [-n count] [-s maxsize] [-S seed] <filenames...>
4452 WARNING: THIS COMMAND IS EXTREMELY DANGEROUS
4453 n,num= number of blocks to damage
4454 s,size= maximum size of each damaged block
4455 percent= maximum size of each damaged block (as a percent of entire file)
4456 equal spread damage evenly throughout the file
4457 S,seed= random number seed (for repeatable tests)
4459 o = options.Options('bup damage', optspec)
4460 (opt, flags, extra) = o.parse(sys.argv[1:])
4463 o.fatal('filenames expected')
4465 if opt.seed != None:
4466 random.seed(opt.seed)
4469 log('Damaging "%s"...\n' % name)
4470 f = open(name, 'r+b')
4471 st = os.fstat(f.fileno())
4473 if opt.percent or opt.size:
4474 ms1 = int(float(opt.percent or 0)/100.0*size) or size
4475 ms2 = opt.size or size
4476 maxsize = min(ms1, ms2)
4479 chunks = opt.num or 10
4480 chunksize = size/chunks
4481 for r in range(chunks):
4482 sz = random.randrange(1, maxsize+1)
4488 ofs = random.randrange(0, size - sz + 1)
4489 log(' %6d bytes at %d\n' % (sz, ofs))
4491 f.write(randblock(sz))
4493 #!/usr/bin/env python
4494 import sys, struct, mmap
4495 from bup import options, git
4496 from bup.helpers import *
4501 def init_dir(conn, arg):
4503 log('bup server: bupdir initialized: %r\n' % git.repodir)
4507 def set_dir(conn, arg):
4508 git.check_repo_or_die(arg)
4509 log('bup server: bupdir is %r\n' % git.repodir)
4513 def list_indexes(conn, junk):
4514 git.check_repo_or_die()
4515 for f in os.listdir(git.repo('objects/pack')):
4516 if f.endswith('.idx'):
4517 conn.write('%s\n' % f)
4521 def send_index(conn, name):
4522 git.check_repo_or_die()
4523 assert(name.find('/') < 0)
4524 assert(name.endswith('.idx'))
4525 idx = git.PackIdx(git.repo('objects/pack/%s' % name))
4526 conn.write(struct.pack('!I', len(idx.map)))
4531 def receive_objects(conn, junk):
4533 git.check_repo_or_die()
4539 w = git.PackWriter()
4544 raise Exception('object read: expected length header, got EOF\n')
4545 n = struct.unpack('!I', ns)[0]
4546 #log('expecting %d bytes\n' % n)
4548 log('bup server: received %d object%s.\n'
4549 % (w.count, w.count!=1 and "s" or ''))
4550 fullpath = w.close()
4552 (dir, name) = os.path.split(fullpath)
4553 conn.write('%s.idx\n' % name)
4556 elif n == 0xffffffff:
4557 log('bup server: receive-objects suspended.\n')
4562 buf = conn.read(n) # object sizes in bup are reasonably small
4563 #log('read %d bytes\n' % n)
4566 raise Exception('object read: expected %d bytes, got %d\n'
4568 (type, content) = git._decode_packobj(buf)
4569 sha = git.calc_hash(type, content)
4570 oldpack = w.exists(sha)
4571 # FIXME: we only suggest a single index per cycle, because the client
4572 # is currently dumb to download more than one per cycle anyway.
4573 # Actually we should fix the client, but this is a minor optimization
4574 # on the server side.
4575 if not suggested and \
4576 oldpack and (oldpack == True or oldpack.endswith('.midx')):
4577 # FIXME: we shouldn't really have to know about midx files
4578 # at this layer. But exists() on a midx doesn't return the
4579 # packname (since it doesn't know)... probably we should just
4580 # fix that deficiency of midx files eventually, although it'll
4581 # make the files bigger. This method is certainly not very
4583 w.objcache.refresh(skip_midx = True)
4584 oldpack = w.objcache.exists(sha)
4585 log('new suggestion: %r\n' % oldpack)
4587 assert(oldpack != True)
4588 assert(not oldpack.endswith('.midx'))
4589 w.objcache.refresh(skip_midx = False)
4590 if not suggested and oldpack:
4591 assert(oldpack.endswith('.idx'))
4592 (dir,name) = os.path.split(oldpack)
4593 if not (name in suggested):
4594 log("bup server: suggesting index %s\n" % name)
4595 conn.write('index %s\n' % name)
4602 def read_ref(conn, refname):
4603 git.check_repo_or_die()
4604 r = git.read_ref(refname)
4605 conn.write('%s\n' % (r or '').encode('hex'))
4609 def update_ref(conn, refname):
4610 git.check_repo_or_die()
4611 newval = conn.readline().strip()
4612 oldval = conn.readline().strip()
4613 git.update_ref(refname, newval.decode('hex'), oldval.decode('hex'))
4618 git.check_repo_or_die()
4620 for blob in git.cat(id):
4621 conn.write(struct.pack('!I', len(blob)))
4624 log('server: error: %s\n' % e)
4625 conn.write('\0\0\0\0')
4628 conn.write('\0\0\0\0')
4635 o = options.Options('bup server', optspec)
4636 (opt, flags, extra) = o.parse(sys.argv[1:])
4639 o.fatal('no arguments expected')
4641 log('bup server: reading from stdin.\n')
4644 'init-dir': init_dir,
4646 'list-indexes': list_indexes,
4647 'send-index': send_index,
4648 'receive-objects': receive_objects,
4649 'read-ref': read_ref,
4650 'update-ref': update_ref,
4654 # FIXME: this protocol is totally lame and not at all future-proof.
4655 # (Especially since we abort completely as soon as *anything* bad happens)
4656 conn = Conn(sys.stdin, sys.stdout)
4657 lr = linereader(conn)
4659 line = _line.strip()
4662 log('bup server: command: %r\n' % line)
4663 words = line.split(' ', 1)
4665 rest = len(words)>1 and words[1] or ''
4669 cmd = commands.get(cmd)
4673 raise Exception('unknown server command: %r\n' % line)
4675 log('bup server: done\n')
4676 #!/usr/bin/env python
4677 import sys, time, struct
4678 from bup import hashsplit, git, options, client
4679 from bup.helpers import *
4680 from subprocess import PIPE
4684 bup join [-r host:path] [refs or hashes...]
4686 r,remote= remote repository path
4688 o = options.Options('bup join', optspec)
4689 (opt, flags, extra) = o.parse(sys.argv[1:])
4691 git.check_repo_or_die()
4694 extra = linereader(sys.stdin)
4699 cli = client.Client(opt.remote)
4707 for blob in cat(id):
4708 sys.stdout.write(blob)
4711 log('error: %s\n' % e)
4715 #!/usr/bin/env python
4716 import sys, re, errno, stat, time, math
4717 from bup import hashsplit, git, options, index, client
4718 from bup.helpers import *
4722 bup save [-tc] [-n name] <filenames...>
4724 r,remote= remote repository path
4725 t,tree output a tree id
4726 c,commit output a commit id
4727 n,name= name of backup set to update (if any)
4728 v,verbose increase log output (can be used more than once)
4729 q,quiet don't show progress meter
4730 smaller= only back up files smaller than n bytes
4732 o = options.Options('bup save', optspec)
4733 (opt, flags, extra) = o.parse(sys.argv[1:])
4735 git.check_repo_or_die()
4736 if not (opt.tree or opt.commit or opt.name):
4737 o.fatal("use one or more of -t, -c, -n")
4739 o.fatal("no filenames given")
4741 opt.progress = (istty and not opt.quiet)
4742 opt.smaller = parse_num(opt.smaller or 0)
4744 is_reverse = os.environ.get('BUP_SERVER_REVERSE')
4745 if is_reverse and opt.remote:
4746 o.fatal("don't use -r in reverse mode; it's automatic")
4748 refname = opt.name and 'refs/heads/%s' % opt.name or None
4749 if opt.remote or is_reverse:
4750 cli = client.Client(opt.remote)
4751 oldref = refname and cli.read_ref(refname) or None
4752 w = cli.new_packwriter()
4755 oldref = refname and git.read_ref(refname) or None
4756 w = git.PackWriter()
4762 if dir.endswith('/'):
4776 def _pop(force_tree):
4777 assert(len(parts) >= 1)
4779 shalist = shalists.pop()
4780 tree = force_tree or w.new_tree(shalist)
4782 shalists[-1].append(('40000', part, tree))
4783 else: # this was the toplevel, so put it back for sanity
4784 shalists.append(shalist)
4788 def progress_report(n):
4789 global count, subcount, lastremain
4791 cc = count + subcount
4792 pct = total and (cc*100.0/total) or 0
4794 elapsed = now - tstart
4795 kps = elapsed and int(cc/1024./elapsed)
4796 kps_frac = 10 ** int(math.log(kps+1, 10) - 1)
4797 kps = int(kps/kps_frac)*kps_frac
4799 remain = elapsed*1.0/cc * (total-cc)
4802 if (lastremain and (remain > lastremain)
4803 and ((remain - lastremain)/lastremain < 0.05)):
4807 hours = int(remain/60/60)
4808 mins = int(remain/60 - hours*60)
4809 secs = int(remain - hours*60*60 - mins*60)
4814 kpsstr = '%dk/s' % kps
4816 remainstr = '%dh%dm' % (hours, mins)
4818 remainstr = '%dm%d' % (mins, secs)
4820 remainstr = '%ds' % secs
4821 progress('Saving: %.2f%% (%d/%dk, %d/%d files) %s %s\r'
4822 % (pct, cc/1024, total/1024, fcount, ftotal,
4826 r = index.Reader(git.repo('bupindex'))
4828 def already_saved(ent):
4829 return ent.is_valid() and w.exists(ent.sha) and ent.sha
4831 def wantrecurse_pre(ent):
4832 return not already_saved(ent)
4834 def wantrecurse_during(ent):
4835 return not already_saved(ent) or ent.sha_missing()
4839 for (transname,ent) in r.filter(extra, wantrecurse=wantrecurse_pre):
4840 if not (ftotal % 10024):
4841 progress('Reading index: %d\r' % ftotal)
4842 exists = ent.exists()
4843 hashvalid = already_saved(ent)
4844 ent.set_sha_missing(not hashvalid)
4845 if not opt.smaller or ent.size < opt.smaller:
4846 if exists and not hashvalid:
4849 progress('Reading index: %d, done.\n' % ftotal)
4850 hashsplit.progress_callback = progress_report
4852 tstart = time.time()
4853 count = subcount = fcount = 0
4854 lastskip_name = None
4856 for (transname,ent) in r.filter(extra, wantrecurse=wantrecurse_during):
4857 (dir, file) = os.path.split(ent.name)
4858 exists = (ent.flags & index.IX_EXISTS)
4859 hashvalid = already_saved(ent)
4860 wasmissing = ent.sha_missing()
4866 if ent.sha == index.EMPTY_SHA:
4872 if opt.verbose >= 2:
4873 log('%s %-70s\n' % (status, ent.name))
4874 elif not stat.S_ISDIR(ent.mode) and lastdir != dir:
4875 if not lastdir.startswith(dir):
4876 log('%s %-70s\n' % (status, os.path.join(dir, '')))
4885 if opt.smaller and ent.size >= opt.smaller:
4886 if exists and not hashvalid:
4887 add_error('skipping large file "%s"' % ent.name)
4888 lastskip_name = ent.name
4891 assert(dir.startswith('/'))
4892 dirp = dir.split('/')
4894 _pop(force_tree = None)
4896 for part in dirp[len(parts):]:
4900 # no filename portion means this is a subdir. But
4901 # sub/parentdirectories already handled in the pop/push() part above.
4902 oldtree = already_saved(ent) # may be None
4903 newtree = _pop(force_tree = oldtree)
4905 if lastskip_name and lastskip_name.startswith(ent.name):
4908 ent.validate(040000, newtree)
4910 if exists and wasmissing:
4914 # it's not a directory
4917 mode = '%o' % ent.gitmode
4919 shalists[-1].append((mode,
4920 git.mangle_name(file, ent.mode, ent.gitmode),
4923 if stat.S_ISREG(ent.mode):
4925 f = hashsplit.open_noatime(ent.name)
4928 lastskip_name = ent.name
4931 lastskip_name = ent.name
4933 (mode, id) = hashsplit.split_to_blob_or_tree(w, [f])
4935 if stat.S_ISDIR(ent.mode):
4936 assert(0) # handled above
4937 elif stat.S_ISLNK(ent.mode):
4939 rl = os.readlink(ent.name)
4942 lastskip_name = ent.name
4945 lastskip_name = ent.name
4947 (mode, id) = ('120000', w.new_blob(rl))
4949 add_error(Exception('skipping special file "%s"' % ent.name))
4950 lastskip_name = ent.name
4952 ent.validate(int(mode, 8), id)
4954 shalists[-1].append((mode,
4955 git.mangle_name(file, ent.mode, ent.gitmode),
4957 if exists and wasmissing:
4963 pct = total and count*100.0/total or 100
4964 progress('Saving: %.2f%% (%d/%dk, %d/%d files), done. \n'
4965 % (pct, count/1024, total/1024, fcount, ftotal))
4967 while len(parts) > 1:
4968 _pop(force_tree = None)
4969 assert(len(shalists) == 1)
4970 tree = w.new_tree(shalists[-1])
4972 print tree.encode('hex')
4973 if opt.commit or opt.name:
4974 msg = 'bup save\n\nGenerated by command:\n%r' % sys.argv
4975 ref = opt.name and ('refs/heads/%s' % opt.name) or None
4976 commit = w.new_commit(oldref, tree, msg)
4978 print commit.encode('hex')
4980 w.close() # must close before we can update the ref
4984 cli.update_ref(refname, commit, oldref)
4986 git.update_ref(refname, commit, oldref)
4992 log('WARNING: %d errors encountered while saving.\n' % len(saved_errors))
4994 #!/usr/bin/env python
4996 from bup import options
5001 o = options.Options('bup tick', optspec)
5002 (opt, flags, extra) = o.parse(sys.argv[1:])
5005 o.fatal("no arguments expected")
5008 tleft = 1 - (t - int(t))
5010 #!/usr/bin/env python
5011 import os, sys, stat, time
5012 from bup import options, git, index, drecurse
5013 from bup.helpers import *
5016 def merge_indexes(out, r1, r2):
5017 for e in index.MergeIter([r1, r2]):
5018 # FIXME: shouldn't we remove deleted entries eventually? When?
5023 def __init__(self, l):
5030 self.cur = self.i.next()
5031 except StopIteration:
5036 def check_index(reader):
5038 log('check: checking forward iteration...\n')
5041 for e in reader.forward_iter():
5044 log('%08x+%-4d %r\n' % (e.children_ofs, e.children_n,
5046 assert(e.children_ofs)
5047 assert(e.name.endswith('/'))
5048 assert(not d.get(e.children_ofs))
5049 d[e.children_ofs] = 1
5050 if e.flags & index.IX_HASHVALID:
5051 assert(e.sha != index.EMPTY_SHA)
5053 assert(not e or e.name == '/') # last entry is *always* /
5054 log('check: checking normal iteration...\n')
5058 assert(last > e.name)
5061 log('index error! at %r\n' % e)
5063 log('check: passed.\n')
5066 def update_index(top):
5067 ri = index.Reader(indexfile)
5068 wi = index.Writer(indexfile)
5069 rig = IterHelper(ri.iter(name=top))
5070 tstart = int(time.time())
5075 return (0100644, index.FAKE_SHA)
5078 for (path,pst) in drecurse.recursive_dirlist([top], xdev=opt.xdev):
5079 if opt.verbose>=2 or (opt.verbose==1 and stat.S_ISDIR(pst.st_mode)):
5080 sys.stdout.write('%s\n' % path)
5082 progress('Indexing: %d\r' % total)
5083 elif not (total % 128):
5084 progress('Indexing: %d\r' % total)
5086 while rig.cur and rig.cur.name > path: # deleted paths
5087 if rig.cur.exists():
5088 rig.cur.set_deleted()
5091 if rig.cur and rig.cur.name == path: # paths that already existed
5093 rig.cur.from_stat(pst, tstart)
5094 if not (rig.cur.flags & index.IX_HASHVALID):
5096 (rig.cur.gitmode, rig.cur.sha) = hashgen(path)
5097 rig.cur.flags |= index.IX_HASHVALID
5098 if opt.fake_invalid:
5099 rig.cur.invalidate()
5103 wi.add(path, pst, hashgen = hashgen)
5104 progress('Indexing: %d, done.\n' % total)
5110 wr = wi.new_reader()
5112 log('check: before merging: oldfile\n')
5114 log('check: before merging: newfile\n')
5116 mi = index.Writer(indexfile)
5117 merge_indexes(mi, ri, wr)
5127 bup index <-p|m|u> [options...] <filenames...>
5129 p,print print the index entries for the given names (also works with -u)
5130 m,modified print only added/deleted/modified files (implies -p)
5131 s,status print each filename with a status char (A/M/D) (implies -p)
5132 H,hash print the hash for each object next to its name (implies -p)
5133 l,long print more information about each file
5134 u,update (recursively) update the index entries for the given filenames
5135 x,xdev,one-file-system don't cross filesystem boundaries
5136 fake-valid mark all index entries as up-to-date even if they aren't
5137 fake-invalid mark all index entries as invalid
5138 check carefully check index file integrity
5139 f,indexfile= the name of the index file (default 'index')
5140 v,verbose increase log output (can be used more than once)
5142 o = options.Options('bup index', optspec)
5143 (opt, flags, extra) = o.parse(sys.argv[1:])
5145 if not (opt.modified or opt['print'] or opt.status or opt.update or opt.check):
5146 o.fatal('supply one or more of -p, -s, -m, -u, or --check')
5147 if (opt.fake_valid or opt.fake_invalid) and not opt.update:
5148 o.fatal('--fake-{in,}valid are meaningless without -u')
5149 if opt.fake_valid and opt.fake_invalid:
5150 o.fatal('--fake-valid is incompatible with --fake-invalid')
5152 git.check_repo_or_die()
5153 indexfile = opt.indexfile or git.repo('bupindex')
5158 log('check: starting initial check.\n')
5159 check_index(index.Reader(indexfile))
5161 paths = index.reduce_paths(extra)
5165 o.fatal('update (-u) requested but no paths given')
5166 for (rp,path) in paths:
5169 if opt['print'] or opt.status or opt.modified:
5170 for (name, ent) in index.Reader(indexfile).filter(extra or ['']):
5172 and (ent.is_valid() or ent.is_deleted() or not ent.mode)):
5176 if ent.is_deleted():
5178 elif not ent.is_valid():
5179 if ent.sha == index.EMPTY_SHA:
5186 line += ent.sha.encode('hex') + ' '
5188 line += "%7s %7s " % (oct(ent.mode), oct(ent.gitmode))
5189 print line + (name or './')
5191 if opt.check and (opt['print'] or opt.status or opt.modified or opt.update):
5192 log('check: starting final check.\n')
5193 check_index(index.Reader(indexfile))
5196 log('WARNING: %d errors encountered.\n' % len(saved_errors))
5198 #!/usr/bin/env python
5199 import sys, os, struct
5200 from bup import options, helpers
5205 This command is not intended to be run manually.
5207 o = options.Options('bup rbackup-server', optspec)
5208 (opt, flags, extra) = o.parse(sys.argv[1:])
5210 o.fatal('no arguments expected')
5212 # get the subcommand's argv.
5213 # Normally we could just pass this on the command line, but since we'll often
5214 # be getting called on the other end of an ssh pipe, which tends to mangle
5215 # argv (by sending it via the shell), this way is much safer.
5216 buf = sys.stdin.read(4)
5217 sz = struct.unpack('!I', buf)[0]
5219 assert(sz < 1000000)
5220 buf = sys.stdin.read(sz)
5221 assert(len(buf) == sz)
5222 argv = buf.split('\0')
5224 # stdin/stdout are supposedly connected to 'bup server' that the caller
5225 # started for us (often on the other end of an ssh tunnel), so we don't want
5226 # to misuse them. Move them out of the way, then replace stdout with
5227 # a pointer to stderr in case our subcommand wants to do something with it.
5229 # It might be nice to do the same with stdin, but my experiments showed that
5230 # ssh seems to make its child's stderr a readable-but-never-reads-anything
5231 # socket. They really should have used shutdown(SHUT_WR) on the other end
5232 # of it, but probably didn't. Anyway, it's too messy, so let's just make sure
5233 # anyone reading from stdin is disappointed.
5235 # (You can't just leave stdin/stdout "not open" by closing the file
5236 # descriptors. Then the next file that opens is automatically assigned 0 or 1,
5237 # and people *trying* to read/write stdin/stdout get screwed.)
5241 fd = os.open('/dev/null', os.O_RDONLY)
5245 os.environ['BUP_SERVER_REVERSE'] = helpers.hostname()
5246 os.execvp(argv[0], argv)
5248 #!/usr/bin/env python
5249 import sys, os, glob, subprocess, time
5250 from bup import options, git
5251 from bup.helpers import *
5254 nullf = open('/dev/null')
5261 # at least in python 2.5, using "stdout=2" or "stdout=sys.stderr" below
5262 # doesn't actually work, because subprocess closes fd #2 right before
5263 # execing for some reason. So we work around it by duplicating the fd
5265 fd = os.dup(2) # copy stderr
5267 p = subprocess.Popen(argv, stdout=fd, close_fds=False)
5276 p = subprocess.Popen(['par2', '--help'],
5277 stdout=nullf, stderr=nullf, stdin=nullf)
5280 log('fsck: warning: par2 not found; disabling recovery features.\n')
5285 if opt.verbose >= lvl:
5293 def par2_generate(base):
5294 return run(['par2', 'create', '-n1', '-c200'] + parv(2)
5295 + ['--', base, base+'.pack', base+'.idx'])
5297 def par2_verify(base):
5298 return run(['par2', 'verify'] + parv(3) + ['--', base])
5300 def par2_repair(base):
5301 return run(['par2', 'repair'] + parv(2) + ['--', base])
5303 def quick_verify(base):
5304 f = open(base + '.pack', 'rb')
5306 wantsum = f.read(20)
5307 assert(len(wantsum) == 20)
5310 for b in chunkyreader(f, os.fstat(f.fileno()).st_size - 20):
5312 if sum.digest() != wantsum:
5313 raise ValueError('expected %r, got %r' % (wantsum.encode('hex'),
5317 def git_verify(base):
5321 except Exception, e:
5322 debug('error: %s\n' % e)
5326 return run(['git', 'verify-pack', '--', base])
5329 def do_pack(base, last):
5331 if par2_ok and par2_exists and (opt.repair or not opt.generate):
5332 vresult = par2_verify(base)
5335 rresult = par2_repair(base)
5337 print '%s par2 repair: failed (%d)' % (last, rresult)
5340 print '%s par2 repair: succeeded (0)' % last
5343 print '%s par2 verify: failed (%d)' % (last, vresult)
5346 print '%s ok' % last
5347 elif not opt.generate or (par2_ok and not par2_exists):
5348 gresult = git_verify(base)
5350 print '%s git verify: failed (%d)' % (last, gresult)
5353 if par2_ok and opt.generate:
5354 presult = par2_generate(base)
5356 print '%s par2 create: failed (%d)' % (last, presult)
5359 print '%s ok' % last
5361 print '%s ok' % last
5363 assert(opt.generate and (not par2_ok or par2_exists))
5364 debug(' skipped: par2 file already generated.\n')
5369 bup fsck [options...] [filenames...]
5371 r,repair attempt to repair errors using par2 (dangerous!)
5372 g,generate generate auto-repair information using par2
5373 v,verbose increase verbosity (can be used more than once)
5374 quick just check pack sha1sum, don't use git verify-pack
5375 j,jobs= run 'n' jobs in parallel
5376 par2-ok immediately return 0 if par2 is ok, 1 if not
5377 disable-par2 ignore par2 even if it is available
5379 o = options.Options('bup fsck', optspec)
5380 (opt, flags, extra) = o.parse(sys.argv[1:])
5385 sys.exit(0) # 'true' in sh
5388 if opt.disable_par2:
5391 git.check_repo_or_die()
5394 debug('fsck: No filenames given: checking all packs.\n')
5395 extra = glob.glob(git.repo('objects/pack/*.pack'))
5401 if name.endswith('.pack'):
5403 elif name.endswith('.idx'):
5405 elif name.endswith('.par2'):
5407 elif os.path.exists(name + '.pack'):
5410 raise Exception('%s is not a pack file!' % name)
5411 (dir,last) = os.path.split(base)
5412 par2_exists = os.path.exists(base + '.par2')
5413 if par2_exists and os.stat(base + '.par2').st_size == 0:
5416 debug('fsck: checking %s (%s)\n'
5417 % (last, par2_ok and par2_exists and 'par2' or 'git'))
5419 progress('fsck (%d/%d)\r' % (count, len(extra)))
5422 nc = do_pack(base, last)
5426 while len(outstanding) >= opt.jobs:
5427 (pid,nc) = os.wait()
5429 if pid in outstanding:
5430 del outstanding[pid]
5435 outstanding[pid] = 1
5438 sys.exit(do_pack(base, last))
5439 except Exception, e:
5440 log('exception: %r\n' % e)
5443 while len(outstanding):
5444 (pid,nc) = os.wait()
5446 if pid in outstanding:
5447 del outstanding[pid]
5451 progress('fsck (%d/%d)\r' % (count, len(extra)))
5453 if not opt.verbose and istty:
5454 log('fsck done. \n')
5456 #!/usr/bin/env python
5457 import sys, os, struct, getopt, subprocess, signal
5458 from bup import options, ssh
5459 from bup.helpers import *
5462 bup rbackup <hostname> index ...
5463 bup rbackup <hostname> save ...
5464 bup rbackup <hostname> split ...
5466 o = options.Options('bup rbackup', optspec, optfunc=getopt.getopt)
5467 (opt, flags, extra) = o.parse(sys.argv[1:])
5469 o.fatal('arguments expected')
5471 class SigException(Exception):
5472 def __init__(self, signum):
5473 self.signum = signum
5474 Exception.__init__(self, 'signal %d received' % signum)
5475 def handler(signum, frame):
5476 raise SigException(signum)
5478 signal.signal(signal.SIGTERM, handler)
5479 signal.signal(signal.SIGINT, handler)
5488 p = ssh.connect(hostname, 'rbackup-server')
5490 argvs = '\0'.join(['bup'] + argv)
5491 p.stdin.write(struct.pack('!I', len(argvs)) + argvs)
5494 main_exe = os.environ.get('BUP_MAIN_EXE') or sys.argv[0]
5495 sp = subprocess.Popen([main_exe, 'server'], stdin=p.stdout, stdout=p.stdin)
5502 # if we get a signal while waiting, we have to keep waiting, just
5503 # in case our child doesn't die.
5508 except SigException, e:
5509 log('\nbup rbackup: %s\n' % e)
5510 os.kill(p.pid, e.signum)
5513 #!/usr/bin/env python
5515 from bup import options
5520 o = options.Options('bup newliner', optspec)
5521 (opt, flags, extra) = o.parse(sys.argv[1:])
5524 o.fatal("no arguments expected")
5526 r = re.compile(r'([\r\n])')
5533 b = os.read(sys.stdin.fileno(), 4096)
5534 except KeyboardInterrupt:
5541 (line, splitchar, all) = l
5543 sys.stdout.write('%-*s%s' % (lastlen, line, splitchar))
5544 if splitchar == '\r':
5551 sys.stdout.write('%-*s\n' % (lastlen, all))
5552 #!/usr/bin/env python
5554 from bup import options, git, _hashsplit
5555 from bup.helpers import *
5561 o = options.Options('bup margin', optspec)
5562 (opt, flags, extra) = o.parse(sys.argv[1:])
5565 o.fatal("no arguments expected")
5567 git.check_repo_or_die()
5568 #git.ignore_midx = 1
5570 mi = git.PackIdxList(git.repo('objects/pack'))
5576 #assert(str(i) >= last)
5577 pm = _hashsplit.bitmatch(last, i)
5578 longmatch = max(longmatch, pm)