]> arthur.barton.de Git - bup.git/blob - lib/bup/rm.py
Replace lresolve with resolve(..., follow=False)
[bup.git] / lib / bup / rm.py
1
2 from __future__ import absolute_import
3 import sys
4
5 from bup import compat, git, vfs
6 from bup.client import ClientError
7 from bup.git import get_commit_items
8 from bup.helpers import add_error, die_if_errors, log, saved_errors
9
10
11 def append_commit(hash, parent, cp, writer):
12     ci = get_commit_items(hash, cp)
13     tree = ci.tree.decode('hex')
14     author = '%s <%s>' % (ci.author_name, ci.author_mail)
15     committer = '%s <%s>' % (ci.committer_name, ci.committer_mail)
16     c = writer.new_commit(tree, parent,
17                           author, ci.author_sec, ci.author_offset,
18                           committer, ci.committer_sec, ci.committer_offset,
19                           ci.message)
20     return c, tree
21
22
23 def filter_branch(tip_commit_hex, exclude, writer):
24     # May return None if everything is excluded.
25     commits = [x.decode('hex') for x in git.rev_list(tip_commit_hex)]
26     commits.reverse()
27     last_c, tree = None, None
28     # Rather than assert that we always find an exclusion here, we'll
29     # just let the StopIteration signal the error.
30     first_exclusion = next(i for i, c in enumerate(commits) if exclude(c))
31     if first_exclusion != 0:
32         last_c = commits[first_exclusion - 1]
33         tree = get_commit_items(last_c.encode('hex'),
34                                 git.cp()).tree.decode('hex')
35         commits = commits[first_exclusion:]
36     for c in commits:
37         if exclude(c):
38             continue
39         last_c, tree = append_commit(c.encode('hex'), last_c, git.cp(), writer)
40     return last_c
41
42 def commit_oid(item):
43     if isinstance(item, vfs.Commit):
44         return item.coid
45     assert isinstance(item, vfs.RevList)
46     return item.oid
47
48 def rm_saves(saves, writer):
49     assert(saves)
50     first_branch_item = saves[0][1]
51     for save, branch in saves: # Be certain they're all on the same branch
52         assert(branch == first_branch_item)
53     rm_commits = frozenset([commit_oid(save) for save, branch in saves])
54     orig_tip = commit_oid(first_branch_item)
55     new_tip = filter_branch(orig_tip.encode('hex'),
56                             lambda x: x in rm_commits,
57                             writer)
58     assert(orig_tip)
59     assert(new_tip != orig_tip)
60     return orig_tip, new_tip
61
62
63 def dead_items(repo, paths):
64     """Return an optimized set of removals, reporting errors via
65     add_error, and if there are any errors, return None, None."""
66     dead_branches = {}
67     dead_saves = {}
68     # Scan for bad requests, and opportunities to optimize
69     for path in paths:
70         try:
71             resolved = vfs.resolve(repo, path, follow=False)
72         except vfs.IOError as e:
73             add_error(e)
74             continue
75         else:
76             leaf_name, leaf_item = resolved[-1]
77             if not leaf_item:
78                 add_error('error: cannot access %r in %r'
79                           % ('/'.join(name for name, item in resolved),
80                              path))
81                 continue
82             if isinstance(leaf_item, vfs.RevList):  # rm /foo
83                 branchname = leaf_name
84                 dead_branches[branchname] = leaf_item
85                 dead_saves.pop(branchname, None)  # rm /foo obviates rm /foo/bar
86             elif isinstance(leaf_item, vfs.Commit):  # rm /foo/bar
87                 if leaf_name == 'latest':
88                     add_error("error: cannot delete 'latest' symlink")
89                 else:
90                     branchname, branchitem = resolved[-2]
91                     if branchname not in dead_branches:
92                         dead = leaf_item, branchitem
93                         dead_saves.setdefault(branchname, []).append(dead)
94             else:
95                 add_error("don't know how to remove %r yet" % path)
96     if saved_errors:
97         return None, None
98     return dead_branches, dead_saves
99
100
101 def bup_rm(repo, paths, compression=6, verbosity=None):
102     dead_branches, dead_saves = dead_items(repo, paths)
103     die_if_errors('not proceeding with any removals\n')
104
105     updated_refs = {}  # ref_name -> (original_ref, tip_commit(bin))
106
107     for branchname, branchitem in compat.items(dead_branches):
108         ref = 'refs/heads/' + branchname
109         assert(not ref in updated_refs)
110         updated_refs[ref] = (branchitem.oid, None)
111
112     if dead_saves:
113         writer = git.PackWriter(compression_level=compression)
114         try:
115             for branch, saves in compat.items(dead_saves):
116                 assert(saves)
117                 updated_refs['refs/heads/' + branch] = rm_saves(saves, writer)
118         except:
119             if writer:
120                 writer.abort()
121             raise
122         else:
123             if writer:
124                 # Must close before we can update the ref(s) below.
125                 writer.close()
126
127     # Only update the refs here, at the very end, so that if something
128     # goes wrong above, the old refs will be undisturbed.  Make an attempt
129     # to update each ref.
130     for ref_name, info in compat.items(updated_refs):
131         orig_ref, new_ref = info
132         try:
133             if not new_ref:
134                 git.delete_ref(ref_name, orig_ref.encode('hex'))
135             else:
136                 git.update_ref(ref_name, new_ref, orig_ref)
137                 if verbosity:
138                     new_hex = new_ref.encode('hex')
139                     if orig_ref:
140                         orig_hex = orig_ref.encode('hex')
141                         log('updated %r (%s -> %s)\n'
142                             % (ref_name, orig_hex, new_hex))
143                     else:
144                         log('updated %r (%s)\n' % (ref_name, new_hex))
145         except (git.GitError, ClientError) as ex:
146             if new_ref:
147                 add_error('while trying to update %r (%s -> %s): %s'
148                           % (ref_name, orig_ref, new_ref, ex))
149             else:
150                 add_error('while trying to delete %r (%s): %s'
151                           % (ref_name, orig_ref, ex))