5 from bup import client, git, options, vfs
6 from bup.git import get_commit_items
7 from bup.helpers import add_error, handle_ctrl_c, log, saved_errors
10 bup rm <branch|save...>
12 #,compress= set compression level to # (0-9, 9 is highest) [6]
13 v,verbose increase verbosity (can be specified multiple times)
14 unsafe use the command even though it may be DANGEROUS
17 def append_commit(hash, parent, cp, writer):
18 ci = get_commit_items(hash, cp)
19 tree = ci.tree.decode('hex')
20 author = '%s <%s>' % (ci.author_name, ci.author_mail)
21 committer = '%s <%s>' % (ci.committer_name, ci.committer_mail)
22 c = writer.new_commit(tree, parent,
23 author, ci.author_sec, ci.author_offset,
24 committer, ci.committer_sec, ci.committer_offset,
29 def filter_branch(tip_commit_hex, exclude, writer):
30 # May return None if everything is excluded.
31 commits = [c for _, c in git.rev_list(tip_commit_hex)]
33 last_c, tree = None, None
34 # Rather than assert that we always find an exclusion here, we'll
35 # just let the StopIteration signal the error.
36 first_exclusion = next(i for i, c in enumerate(commits) if exclude(c))
37 if first_exclusion != 0:
38 last_c = commits[first_exclusion - 1]
39 tree = get_commit_items(last_c.encode('hex'),
40 git.cp()).tree.decode('hex')
41 commits = commits[first_exclusion:]
45 last_c, tree = append_commit(c.encode('hex'), last_c, git.cp(), writer)
49 def rm_saves(saves, writer):
51 branch_node = saves[0].parent
52 for save in saves: # Be certain they're all on the same branch
53 assert(save.parent == branch_node)
54 rm_commits = frozenset([x.dereference().hash for x in saves])
55 orig_tip = branch_node.hash
56 new_tip = filter_branch(orig_tip.encode('hex'),
57 lambda x: x in rm_commits,
60 assert(new_tip != orig_tip)
61 return orig_tip, new_tip
64 def dead_items(vfs_top, 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 n = vfs_top.lresolve(path)
73 except vfs.NodeError as e:
74 add_error('unable to resolve %s: %s' % (path, e))
76 if isinstance(n, vfs.BranchList): # rm /foo
78 dead_branches[branchname] = n
79 dead_saves.pop(branchname, None) # rm /foo obviates rm /foo/bar
80 elif isinstance(n, vfs.FakeSymlink) and isinstance(n.parent,
82 if n.name == 'latest':
83 add_error("error: cannot delete 'latest' symlink")
85 branchname = n.parent.name
86 if branchname not in dead_branches:
87 dead_saves.setdefault(branchname, []).append(n)
89 add_error("don't know how to remove %r yet" % n.fullname())
92 return dead_branches, dead_saves
97 o = options.Options(optspec)
98 opt, flags, extra = o.parse(sys.argv[1:])
101 o.fatal('refusing to run dangerous, experimental command without --unsafe')
104 o.fatal('no paths specified')
108 git.check_repo_or_die()
109 top = vfs.RefList(None)
111 dead_branches, dead_saves = dead_items(top, paths)
113 log('not proceeding with any removals\n')
116 updated_refs = {} # ref_name -> (original_ref, tip_commit(bin))
120 writer = git.PackWriter(compression_level=opt.compress)
122 for branch, saves in dead_saves.iteritems():
124 updated_refs['refs/heads/' + branch] = rm_saves(saves, writer)
126 for branch, node in dead_branches.iteritems():
127 ref = 'refs/heads/' + branch
128 assert(not ref in updated_refs)
129 updated_refs[ref] = (node.hash, None)
132 # Must close before we can update the ref(s) below.
135 # Only update the refs here, at the very end, so that if something
136 # goes wrong above, the old refs will be undisturbed. Make an attempt
137 # to update each ref.
138 for ref_name, info in updated_refs.iteritems():
139 orig_ref, new_ref = info
142 git.delete_ref(ref_name, orig_ref.encode('hex'))
144 git.update_ref(ref_name, new_ref, orig_ref)
146 new_hex = new_ref.encode('hex')
148 orig_hex = orig_ref.encode('hex')
149 log('updated %r (%s -> %s)\n'
150 % (ref_name, orig_hex, new_hex))
152 log('updated %r (%s)\n' % (ref_name, new_hex))
153 except (git.GitError, client.ClientError) as ex:
155 add_error('while trying to update %r (%s -> %s): %s'
156 % (ref_name, orig_ref, new_ref, ex))
158 add_error('while trying to delete %r (%s): %s'
159 % (ref_name, orig_ref, ex))
162 log('warning: %d errors encountered\n' % len(saved_errors))