2 from __future__ import absolute_import
3 from binascii import hexlify, unhexlify
6 from bup import compat, git, vfs
7 from bup.client import ClientError
8 from bup.compat import hexstr
9 from bup.git import get_commit_items
10 from bup.helpers import add_error, die_if_errors, log, saved_errors
11 from bup.io import path_msg
13 def append_commit(hash, parent, cp, writer):
14 ci = get_commit_items(hash, cp)
15 tree = unhexlify(ci.tree)
16 author = b'%s <%s>' % (ci.author_name, ci.author_mail)
17 committer = b'%s <%s>' % (ci.committer_name, ci.committer_mail)
18 c = writer.new_commit(tree, parent,
19 author, ci.author_sec, ci.author_offset,
20 committer, ci.committer_sec, ci.committer_offset,
25 def filter_branch(tip_commit_hex, exclude, writer):
26 # May return None if everything is excluded.
27 commits = [unhexlify(x) for x in git.rev_list(tip_commit_hex)]
29 last_c, tree = None, None
30 # Rather than assert that we always find an exclusion here, we'll
31 # just let the StopIteration signal the error.
32 first_exclusion = next(i for i, c in enumerate(commits) if exclude(c))
33 if first_exclusion != 0:
34 last_c = commits[first_exclusion - 1]
35 tree = unhexlify(get_commit_items(hexlify(last_c), git.cp()).tree)
36 commits = commits[first_exclusion:]
40 last_c, tree = append_commit(hexlify(c), last_c, git.cp(), writer)
44 if isinstance(item, vfs.Commit):
46 assert isinstance(item, vfs.RevList)
49 def rm_saves(saves, writer):
51 first_branch_item = saves[0][1]
52 for save, branch in saves: # Be certain they're all on the same branch
53 assert(branch == first_branch_item)
54 rm_commits = frozenset([commit_oid(save) for save, branch in saves])
55 orig_tip = commit_oid(first_branch_item)
56 new_tip = filter_branch(hexlify(orig_tip),
57 lambda x: x in rm_commits,
60 assert(new_tip != orig_tip)
61 return orig_tip, new_tip
64 def dead_items(repo, paths):
65 """Return an optimized set of removals, reporting errors via
66 add_error, and if there are any errors, return None, None."""
69 # Scan for bad requests, and opportunities to optimize
72 resolved = vfs.resolve(repo, path, follow=False)
73 except vfs.IOError as e:
77 leaf_name, leaf_item = resolved[-1]
79 add_error('error: cannot access %s in %s'
80 % (path_msg(b'/'.join(name for name, item in resolved)),
83 if isinstance(leaf_item, vfs.RevList): # rm /foo
84 branchname = leaf_name
85 dead_branches[branchname] = leaf_item
86 dead_saves.pop(branchname, None) # rm /foo obviates rm /foo/bar
87 elif isinstance(leaf_item, vfs.Commit): # rm /foo/bar
88 if leaf_name == b'latest':
89 add_error("error: cannot delete 'latest' symlink")
91 branchname, branchitem = resolved[-2]
92 if branchname not in dead_branches:
93 dead = leaf_item, branchitem
94 dead_saves.setdefault(branchname, []).append(dead)
96 add_error("don't know how to remove %s yet" % path_msg(path))
99 return dead_branches, dead_saves
102 def bup_rm(repo, paths, compression=6, verbosity=None):
103 dead_branches, dead_saves = dead_items(repo, paths)
104 die_if_errors('not proceeding with any removals\n')
106 updated_refs = {} # ref_name -> (original_ref, tip_commit(bin))
108 for branchname, branchitem in compat.items(dead_branches):
109 ref = b'refs/heads/' + branchname
110 assert(not ref in updated_refs)
111 updated_refs[ref] = (branchitem.oid, None)
114 writer = git.PackWriter(compression_level=compression)
116 for branch, saves in compat.items(dead_saves):
118 updated_refs[b'refs/heads/' + branch] = rm_saves(saves, writer)
125 # Must close before we can update the ref(s) below.
128 # Only update the refs here, at the very end, so that if something
129 # goes wrong above, the old refs will be undisturbed. Make an attempt
130 # to update each ref.
131 for ref_name, info in compat.items(updated_refs):
132 orig_ref, new_ref = info
135 git.delete_ref(ref_name, hexlify(orig_ref))
137 git.update_ref(ref_name, new_ref, orig_ref)
139 log('updated %s (%s%s)\n'
140 % (path_msg(ref_name),
141 hexstr(orig_ref) + ' -> ' if orig_ref else '',
143 except (git.GitError, ClientError) as ex:
145 add_error('while trying to update %s (%s%s): %s'
146 % (path_msg(ref_name),
147 hexstr(orig_ref) + ' -> ' if orig_ref else '',
151 add_error('while trying to delete %r (%s): %s'
152 % (ref_name, hexstr(orig_ref), ex))