4 from bup import git, vfs
5 from bup.client import ClientError
6 from bup.git import get_commit_items
7 from bup.helpers import add_error, die_if_errors, log, saved_errors
10 def append_commit(hash, parent, cp, writer):
11 ci = get_commit_items(hash, cp)
12 tree = ci.tree.decode('hex')
13 author = '%s <%s>' % (ci.author_name, ci.author_mail)
14 committer = '%s <%s>' % (ci.committer_name, ci.committer_mail)
15 c = writer.new_commit(tree, parent,
16 author, ci.author_sec, ci.author_offset,
17 committer, ci.committer_sec, ci.committer_offset,
22 def filter_branch(tip_commit_hex, exclude, writer):
23 # May return None if everything is excluded.
24 commits = [x.decode('hex') for x in git.rev_list(tip_commit_hex)]
26 last_c, tree = None, None
27 # Rather than assert that we always find an exclusion here, we'll
28 # just let the StopIteration signal the error.
29 first_exclusion = next(i for i, c in enumerate(commits) if exclude(c))
30 if first_exclusion != 0:
31 last_c = commits[first_exclusion - 1]
32 tree = get_commit_items(last_c.encode('hex'),
33 git.cp()).tree.decode('hex')
34 commits = commits[first_exclusion:]
38 last_c, tree = append_commit(c.encode('hex'), last_c, git.cp(), writer)
42 if isinstance(item, vfs.Commit):
44 assert isinstance(item, vfs.RevList)
47 def rm_saves(saves, writer):
49 first_branch_item = saves[0][1]
50 for save, branch in saves: # Be certain they're all on the same branch
51 assert(branch == first_branch_item)
52 rm_commits = frozenset([commit_oid(save) for save, branch in saves])
53 orig_tip = commit_oid(first_branch_item)
54 new_tip = filter_branch(orig_tip.encode('hex'),
55 lambda x: x in rm_commits,
58 assert(new_tip != orig_tip)
59 return orig_tip, new_tip
62 def dead_items(repo, paths):
63 """Return an optimized set of removals, reporting errors via
64 add_error, and if there are any errors, return None, None."""
67 # Scan for bad requests, and opportunities to optimize
70 resolved = vfs.lresolve(repo, path)
71 except vfs.IOError as e:
75 leaf_name, leaf_item = resolved[-1]
77 add_error('error: cannot access %r in %r'
78 % ('/'.join(name for name, item in resolved),
81 if isinstance(leaf_item, vfs.RevList): # rm /foo
82 branchname = leaf_name
83 dead_branches[branchname] = leaf_item
84 dead_saves.pop(branchname, None) # rm /foo obviates rm /foo/bar
85 elif isinstance(leaf_item, vfs.Commit): # rm /foo/bar
86 if leaf_name == 'latest':
87 add_error("error: cannot delete 'latest' symlink")
89 branchname, branchitem = resolved[-2]
90 if branchname not in dead_branches:
91 dead = leaf_item, branchitem
92 dead_saves.setdefault(branchname, []).append(dead)
94 add_error("don't know how to remove %r yet" % path)
97 return dead_branches, dead_saves
100 def bup_rm(repo, paths, compression=6, verbosity=None):
101 dead_branches, dead_saves = dead_items(repo, paths)
102 die_if_errors('not proceeding with any removals\n')
104 updated_refs = {} # ref_name -> (original_ref, tip_commit(bin))
106 for branchname, branchitem in dead_branches.iteritems():
107 ref = 'refs/heads/' + branchname
108 assert(not ref in updated_refs)
109 updated_refs[ref] = (branchitem.oid, None)
112 writer = git.PackWriter(compression_level=compression)
114 for branch, saves in dead_saves.iteritems():
116 updated_refs['refs/heads/' + branch] = rm_saves(saves, writer)
123 # Must close before we can update the ref(s) below.
126 # Only update the refs here, at the very end, so that if something
127 # goes wrong above, the old refs will be undisturbed. Make an attempt
128 # to update each ref.
129 for ref_name, info in updated_refs.iteritems():
130 orig_ref, new_ref = info
133 git.delete_ref(ref_name, orig_ref.encode('hex'))
135 git.update_ref(ref_name, new_ref, orig_ref)
137 new_hex = new_ref.encode('hex')
139 orig_hex = orig_ref.encode('hex')
140 log('updated %r (%s -> %s)\n'
141 % (ref_name, orig_hex, new_hex))
143 log('updated %r (%s)\n' % (ref_name, new_hex))
144 except (git.GitError, ClientError) as ex:
146 add_error('while trying to update %r (%s -> %s): %s'
147 % (ref_name, orig_ref, new_ref, ex))
149 add_error('while trying to delete %r (%s): %s'
150 % (ref_name, orig_ref, ex))