(dir, name) = os.path.split(fullname)
self.getdir(dir).children[name] = (mode, name, id)
- def shalist(self):
+ def shalist(self, w):
for c in self.children.values():
if isinstance(c, tuple): # sha1 entry for a file
yield c
else: # tree
- t = ('40000', c.name, c.gen_tree())
+ t = ('40000', c.name, c.gen_tree(w))
yield t
- def gen_tree(self):
+ def gen_tree(self, w):
if not self.sha:
- self.sha = git.gen_tree(self.shalist())
+ self.sha = w.new_tree(self.shalist(w))
return self.sha
git.verbose = opt.verbose - 1
hashsplit.split_verbosely = opt.verbose - 1
+w = git.PackWriter()
root = Tree(None, '')
for fn in direxpand(extra):
if opt.verbose:
except OSError, e:
add_error(e)
continue
- (mode, id) = hashsplit.split_to_blob_or_tree([f])
+ (mode, id) = hashsplit.split_to_blob_or_tree(w, [f])
root.addfile(mode, fn, id)
-tree = root.gen_tree()
+tree = root.gen_tree(w)
if opt.verbose:
log('\n')
if opt.tree:
if opt.commit or opt.name:
msg = 'bup save\n\nGenerated by command:\n%r' % sys.argv
ref = opt.name and ('refs/heads/%s' % opt.name) or None
- commit = git.gen_commit_easy(ref, tree, msg)
+ commit = w.new_commit(ref, tree, msg)
if opt.commit:
print commit.encode('hex')
-git.flush_pack()
+w.close()
if saved_errors:
log('WARNING: %d errors encountered while saving.\n' % len(saved_errors))
verbose = 0
+def repodir(sub = ''):
+ return os.path.join(os.environ.get('BUP_DIR', '.git'), sub)
+
class PackIndex:
def __init__(self, filename):
def add(self, hash):
self.also[hash] = 1
+ def zap_also(self):
+ self.also = {}
+
def calc_hash(type, content):
header = '%s %d\0' % (type, len(content))
def __init__(self):
self.count = 0
self.binlist = []
- self.filename = '.git/objects/bup%d' % os.getpid()
+ self.objcache = MultiPackIndex(repodir('objects/pack'))
+ self.filename = None
+ self.file = None
+
+ def __del__(self):
+ self.close()
+
+ def _open(self):
+ assert(not self.file)
+ self.objcache.zap_also()
+ self.filename = repodir('objects/bup%d' % os.getpid())
self.file = open(self.filename + '.pack', 'w+')
self.file.write('PACK\0\0\0\2\0\0\0\0')
- def write(self, bin, type, content):
- global _typemap
+ def _write(self, bin, type, content):
+ if not self.file:
+ self._open()
f = self.file
if verbose:
self.binlist.append(bin)
return bin
- def easy_write(self, type, content):
- return self.write(calc_hash(type, content), type, content)
+ def write(self, type, content):
+ return self._write(calc_hash(type, content), type, content)
+
+ def maybe_write(self, type, content):
+ bin = calc_hash(type, content)
+ if not self.objcache.exists(bin):
+ self._write(bin, type, content)
+ self.objcache.add(bin)
+ return bin
+
+ def new_blob(self, blob):
+ return self.maybe_write('blob', blob)
+
+ def new_tree(self, shalist):
+ shalist = sorted(shalist, key = lambda x: x[1])
+ l = ['%s %s\0%s' % (mode,name,bin)
+ for (mode,name,bin) in shalist]
+ return self.maybe_write('tree', ''.join(l))
+
+ def _new_commit(self, tree, parent, author, adate, committer, cdate, msg):
+ l = []
+ if tree: l.append('tree %s' % tree.encode('hex'))
+ if parent: l.append('parent %s' % parent)
+ if author: l.append('author %s %s' % (author, _git_date(adate)))
+ if committer: l.append('committer %s %s' % (committer, _git_date(cdate)))
+ l.append('')
+ l.append(msg)
+ return self.maybe_write('commit', '\n'.join(l))
+
+ def new_commit(self, ref, tree, msg):
+ now = time.time()
+ userline = '%s <%s@%s>' % (userfullname(), username(), hostname())
+ oldref = ref and _read_ref(ref) or None
+ commit = self._new_commit(tree, oldref,
+ userline, now, userline, now,
+ msg)
+ self.close() # UGLY: needed so _update_ref can see the new objects
+ if ref:
+ _update_ref(ref, commit.encode('hex'), oldref)
+ return commit
def abort(self):
- self.file.close()
- os.unlink(self.filename + '.pack')
+ f = self.file
+ if f:
+ self.file = None
+ f.close()
+ os.unlink(self.filename + '.pack')
def close(self):
f = self.file
+ if not f: return None
+ self.file = None
# update object count
f.seek(8)
p = subprocess.Popen(['git', 'index-pack', '-v',
self.filename + '.pack'],
- preexec_fn = lambda: _gitenv('.git'),
+ preexec_fn = _gitenv,
stdout = subprocess.PIPE)
out = p.stdout.read().strip()
if p.wait() or not out:
raise Exception('git index-pack returned an error')
- nameprefix = '.git/objects/pack/%s' % out
+ nameprefix = repodir('objects/pack/%s' % out)
os.rename(self.filename + '.pack', nameprefix + '.pack')
os.rename(self.filename + '.idx', nameprefix + '.idx')
return nameprefix
-_packout = None
-def _write_object(bin, type, content):
- global _packout
- if not _packout:
- _packout = PackWriter()
- _packout.write(bin, type, content)
-
-
-def flush_pack():
- global _packout
- if _packout:
- _packout.close()
- _packout = None
-
-
-def abort_pack():
- global _packout
- if _packout:
- _packout.abort()
- _packout = None
-
-
-_objcache = None
-def hash_raw(type, s):
- global _objcache
- if not _objcache:
- _objcache = MultiPackIndex('.git/objects/pack')
- bin = calc_hash(type, s)
- if _objcache.exists(bin):
- return bin
- else:
- _write_object(bin, type, s)
- _objcache.add(bin)
- return bin
-
-
-def hash_blob(blob):
- return hash_raw('blob', blob)
-
-
-def gen_tree(shalist):
- shalist = sorted(shalist, key = lambda x: x[1])
- l = ['%s %s\0%s' % (mode,name,bin)
- for (mode,name,bin) in shalist]
- return hash_raw('tree', ''.join(l))
-
-
def _git_date(date):
return time.strftime('%s %z', time.localtime(date))
-def _gitenv(repo):
- os.environ['GIT_DIR'] = os.path.abspath(repo)
+def _gitenv():
+ os.environ['GIT_DIR'] = os.path.abspath(repodir())
-def _read_ref(repo, refname):
+def _read_ref(refname):
p = subprocess.Popen(['git', 'show-ref', '--', refname],
- preexec_fn = lambda: _gitenv(repo),
+ preexec_fn = _gitenv,
stdout = subprocess.PIPE)
out = p.stdout.read().strip()
p.wait()
return None
-def _update_ref(repo, refname, newval, oldval):
+def _update_ref(refname, newval, oldval):
if not oldval:
oldval = ''
p = subprocess.Popen(['git', 'update-ref', '--', refname, newval, oldval],
- preexec_fn = lambda: _gitenv(repo))
+ preexec_fn = _gitenv)
p.wait()
return newval
-
-
-def gen_commit(tree, parent, author, adate, committer, cdate, msg):
- l = []
- if tree: l.append('tree %s' % tree.encode('hex'))
- if parent: l.append('parent %s' % parent)
- if author: l.append('author %s %s' % (author, _git_date(adate)))
- if committer: l.append('committer %s %s' % (committer, _git_date(cdate)))
- l.append('')
- l.append(msg)
- return hash_raw('commit', '\n'.join(l))
-
-
-def gen_commit_easy(ref, tree, msg):
- now = time.time()
- userline = '%s <%s@%s>' % (userfullname(), username(), hostname())
- oldref = ref and _read_ref('.git', ref) or None
- commit = gen_commit(tree, oldref, userline, now, userline, now, msg)
- flush_pack()
- if ref:
- _update_ref('.git', ref, commit.encode('hex'), oldref)
- return commit
yield open(n)
-def hashsplit_iter(files):
+def hashsplit_iter(w, files):
global split_verbosely
ofs = 0
buf = Buf()
continue
if blob:
- yield (ofs, len(blob), git.hash_blob(blob))
+ yield (ofs, len(blob), w.new_blob(blob))
ofs += len(blob)
nv = (ofs + buf.used())/1000000
total_split = 0
-def split_to_shalist(files):
+def split_to_shalist(w, files):
global total_split
ofs = 0
last_ofs = 0
- for (ofs, size, sha) in hashsplit_iter(files):
+ for (ofs, size, sha) in hashsplit_iter(w, files):
#log('SPLIT @ %-8d size=%-8d\n' % (ofs, size))
# this silliness keeps chunk filenames "similar" when a file changes
# slightly.
yield ('100644', 'bup.chunk.%016x' % cn, sha)
-def split_to_tree(files):
- shalist = list(split_to_shalist(files))
- tree = git.gen_tree(shalist)
+def split_to_tree(w, files):
+ shalist = list(split_to_shalist(w, files))
+ tree = w.new_tree(shalist)
return (shalist, tree)
-def split_to_blob_or_tree(files):
- (shalist, tree) = split_to_tree(files)
+def split_to_blob_or_tree(w, files):
+ (shalist, tree) = split_to_tree(w, files)
if len(shalist) == 1:
return (shalist[0][0], shalist[0][2])
elif len(shalist) == 0:
- return ('100644', git.hash_blob(''))
+ return ('100644', w.new_blob(''))
else:
return ('40000', tree)