]> arthur.barton.de Git - bup.git/blob - lib/bup/rm.py
Remove vfs (replaced by vfs2)
[bup.git] / lib / bup / rm.py
1
2 import sys
3
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
8
9
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,
18                           ci.message)
19     return c, tree
20
21
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)]
25     commits.reverse()
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:]
35     for c in commits:
36         if exclude(c):
37             continue
38         last_c, tree = append_commit(c.encode('hex'), last_c, git.cp(), writer)
39     return last_c
40
41 def commit_oid(item):
42     if isinstance(item, vfs.Commit):
43         return item.coid
44     assert isinstance(item, vfs.RevList)
45     return item.oid
46
47 def rm_saves(saves, writer):
48     assert(saves)
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,
56                             writer)
57     assert(orig_tip)
58     assert(new_tip != orig_tip)
59     return orig_tip, new_tip
60
61
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."""
65     dead_branches = {}
66     dead_saves = {}
67     # Scan for bad requests, and opportunities to optimize
68     for path in paths:
69         try:
70             resolved = vfs.lresolve(repo, path)
71         except vfs.IOError as e:
72             add_error(e)
73             continue
74         else:
75             leaf_name, leaf_item = resolved[-1]
76             if not leaf_item:
77                 add_error('error: cannot access %r in %r'
78                           % ('/'.join(name for name, item in resolved),
79                              path))
80                 continue
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")
88                 else:
89                     branchname, branchitem = resolved[-2]
90                     if branchname not in dead_branches:
91                         dead = leaf_item, branchitem
92                         dead_saves.setdefault(branchname, []).append(dead)
93             else:
94                 add_error("don't know how to remove %r yet" % path)
95     if saved_errors:
96         return None, None
97     return dead_branches, dead_saves
98
99
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')
103
104     updated_refs = {}  # ref_name -> (original_ref, tip_commit(bin))
105
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)
110
111     if dead_saves:
112         writer = git.PackWriter(compression_level=compression)
113         try:
114             for branch, saves in dead_saves.iteritems():
115                 assert(saves)
116                 updated_refs['refs/heads/' + branch] = rm_saves(saves, writer)
117         except:
118             if writer:
119                 writer.abort()
120             raise
121         else:
122             if writer:
123                 # Must close before we can update the ref(s) below.
124                 writer.close()
125
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
131         try:
132             if not new_ref:
133                 git.delete_ref(ref_name, orig_ref.encode('hex'))
134             else:
135                 git.update_ref(ref_name, new_ref, orig_ref)
136                 if verbosity:
137                     new_hex = new_ref.encode('hex')
138                     if orig_ref:
139                         orig_hex = orig_ref.encode('hex')
140                         log('updated %r (%s -> %s)\n'
141                             % (ref_name, orig_hex, new_hex))
142                     else:
143                         log('updated %r (%s)\n' % (ref_name, new_hex))
144         except (git.GitError, ClientError) as ex:
145             if new_ref:
146                 add_error('while trying to update %r (%s -> %s): %s'
147                           % (ref_name, orig_ref, new_ref, ex))
148             else:
149                 add_error('while trying to delete %r (%s): %s'
150                           % (ref_name, orig_ref, ex))