]> arthur.barton.de Git - bup.git/blob - lib/bup/rm.py
Update base_version to 0.34~ for 0.34 development
[bup.git] / lib / bup / rm.py
1
2 from __future__ import absolute_import
3 from binascii import hexlify, unhexlify
4
5 from bup import git, vfs
6 from bup.client import ClientError
7 from bup.compat import hexstr, pending_raise
8 from bup.git import get_commit_items
9 from bup.helpers import add_error, die_if_errors, log, saved_errors
10 from bup.io import path_msg
11
12 def append_commit(hash, parent, cp, writer):
13     ci = get_commit_items(hash, cp)
14     tree = unhexlify(ci.tree)
15     author = b'%s <%s>' % (ci.author_name, ci.author_mail)
16     committer = b'%s <%s>' % (ci.committer_name, ci.committer_mail)
17     c = writer.new_commit(tree, parent,
18                           author, ci.author_sec, ci.author_offset,
19                           committer, ci.committer_sec, ci.committer_offset,
20                           ci.message)
21     return c, tree
22
23
24 def filter_branch(tip_commit_hex, exclude, writer):
25     # May return None if everything is excluded.
26     commits = [unhexlify(x) for x in git.rev_list(tip_commit_hex)]
27     commits.reverse()
28     last_c, tree = None, None
29     # Rather than assert that we always find an exclusion here, we'll
30     # just let the StopIteration signal the error.
31     first_exclusion = next(i for i, c in enumerate(commits) if exclude(c))
32     if first_exclusion != 0:
33         last_c = commits[first_exclusion - 1]
34         tree = unhexlify(get_commit_items(hexlify(last_c), git.cp()).tree)
35         commits = commits[first_exclusion:]
36     for c in commits:
37         if exclude(c):
38             continue
39         last_c, tree = append_commit(hexlify(c), 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(hexlify(orig_tip),
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 %s in %s'
79                           % (path_msg(b'/'.join(name for name, item in resolved)),
80                              path_msg(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 == b'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 %s yet" % path_msg(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 dead_branches.items():
108         ref = b'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 dead_saves.items():
116                 assert(saves)
117                 updated_refs[b'refs/heads/' + branch] = rm_saves(saves, writer)
118         except BaseException as ex:
119             with pending_raise(ex):
120                 writer.abort()
121         finally:
122             writer.close()
123
124     # Only update the refs here, at the very end, so that if something
125     # goes wrong above, the old refs will be undisturbed.  Make an attempt
126     # to update each ref.
127     for ref_name, info in updated_refs.items():
128         orig_ref, new_ref = info
129         try:
130             if not new_ref:
131                 git.delete_ref(ref_name, hexlify(orig_ref))
132             else:
133                 git.update_ref(ref_name, new_ref, orig_ref)
134                 if verbosity:
135                     log('updated %s (%s%s)\n'
136                         % (path_msg(ref_name),
137                            hexstr(orig_ref) + ' -> ' if orig_ref else '',
138                            hexstr(new_ref)))
139         except (git.GitError, ClientError) as ex:
140             if new_ref:
141                 add_error('while trying to update %s (%s%s): %s'
142                           % (path_msg(ref_name),
143                              hexstr(orig_ref) + ' -> ' if orig_ref else '',
144                              hexstr(new_ref),
145                              ex))
146             else:
147                 add_error('while trying to delete %r (%s): %s'
148                           % (ref_name, hexstr(orig_ref), ex))