Commit | Line | Data |
---|---|---|
f9cc5e69 | 1 | from stgit import exception, utils |
cbe4567e | 2 | from stgit.out import * |
dcd32afa | 3 | from stgit.lib import git |
cbe4567e KH |
4 | |
5 | class TransactionException(exception.StgException): | |
6 | pass | |
7 | ||
dcd32afa KH |
8 | class TransactionHalted(TransactionException): |
9 | pass | |
10 | ||
11 | def _print_current_patch(old_applied, new_applied): | |
cbe4567e KH |
12 | def now_at(pn): |
13 | out.info('Now at patch "%s"' % pn) | |
14 | if not old_applied and not new_applied: | |
15 | pass | |
16 | elif not old_applied: | |
17 | now_at(new_applied[-1]) | |
18 | elif not new_applied: | |
19 | out.info('No patch applied') | |
20 | elif old_applied[-1] == new_applied[-1]: | |
21 | pass | |
22 | else: | |
23 | now_at(new_applied[-1]) | |
24 | ||
dcd32afa KH |
25 | class _TransPatchMap(dict): |
26 | def __init__(self, stack): | |
27 | dict.__init__(self) | |
28 | self.__stack = stack | |
29 | def __getitem__(self, pn): | |
30 | try: | |
31 | return dict.__getitem__(self, pn) | |
32 | except KeyError: | |
33 | return self.__stack.patches.get(pn).commit | |
34 | ||
cbe4567e KH |
35 | class StackTransaction(object): |
36 | def __init__(self, stack, msg): | |
37 | self.__stack = stack | |
38 | self.__msg = msg | |
dcd32afa | 39 | self.__patches = _TransPatchMap(stack) |
cbe4567e KH |
40 | self.__applied = list(self.__stack.patchorder.applied) |
41 | self.__unapplied = list(self.__stack.patchorder.unapplied) | |
dcd32afa KH |
42 | self.__error = None |
43 | self.__current_tree = self.__stack.head.data.tree | |
3b0552a7 | 44 | self.__base = self.__stack.base |
dcd32afa KH |
45 | stack = property(lambda self: self.__stack) |
46 | patches = property(lambda self: self.__patches) | |
cbe4567e KH |
47 | def __set_applied(self, val): |
48 | self.__applied = list(val) | |
49 | applied = property(lambda self: self.__applied, __set_applied) | |
50 | def __set_unapplied(self, val): | |
51 | self.__unapplied = list(val) | |
52 | unapplied = property(lambda self: self.__unapplied, __set_unapplied) | |
3b0552a7 | 53 | def __set_base(self, val): |
980bde6a KH |
54 | assert (not self.__applied |
55 | or self.patches[self.applied[0]].data.parent == val) | |
3b0552a7 KH |
56 | self.__base = val |
57 | base = property(lambda self: self.__base, __set_base) | |
dcd32afa KH |
58 | def __checkout(self, tree, iw): |
59 | if not self.__stack.head_top_equal(): | |
60 | out.error( | |
61 | 'HEAD and top are not the same.', | |
62 | 'This can happen if you modify a branch with git.', | |
63 | '"stg repair --help" explains more about what to do next.') | |
64 | self.__abort() | |
65 | if self.__current_tree != tree: | |
66 | assert iw != None | |
67 | iw.checkout(self.__current_tree, tree) | |
68 | self.__current_tree = tree | |
69 | @staticmethod | |
70 | def __abort(): | |
71 | raise TransactionException( | |
72 | 'Command aborted (all changes rolled back)') | |
cbe4567e KH |
73 | def __check_consistency(self): |
74 | remaining = set(self.__applied + self.__unapplied) | |
75 | for pn, commit in self.__patches.iteritems(): | |
76 | if commit == None: | |
77 | assert self.__stack.patches.exists(pn) | |
78 | else: | |
79 | assert pn in remaining | |
dcd32afa KH |
80 | @property |
81 | def __head(self): | |
cbe4567e | 82 | if self.__applied: |
dcd32afa | 83 | return self.__patches[self.__applied[-1]] |
cbe4567e | 84 | else: |
3b0552a7 | 85 | return self.__base |
59032ccd KH |
86 | def abort(self, iw = None): |
87 | # The only state we need to restore is index+worktree. | |
88 | if iw: | |
89 | self.__checkout(self.__stack.head.data.tree, iw) | |
dcd32afa KH |
90 | def run(self, iw = None): |
91 | self.__check_consistency() | |
92 | new_head = self.__head | |
cbe4567e KH |
93 | |
94 | # Set branch head. | |
24d417c4 KH |
95 | if iw: |
96 | try: | |
97 | self.__checkout(new_head.data.tree, iw) | |
98 | except git.CheckoutException: | |
99 | # We have to abort the transaction. | |
100 | self.abort(iw) | |
101 | self.__abort() | |
cbe4567e KH |
102 | self.__stack.set_head(new_head, self.__msg) |
103 | ||
dcd32afa KH |
104 | if self.__error: |
105 | out.error(self.__error) | |
106 | ||
cbe4567e KH |
107 | # Write patches. |
108 | for pn, commit in self.__patches.iteritems(): | |
109 | if self.__stack.patches.exists(pn): | |
110 | p = self.__stack.patches.get(pn) | |
111 | if commit == None: | |
112 | p.delete() | |
113 | else: | |
114 | p.set_commit(commit, self.__msg) | |
115 | else: | |
116 | self.__stack.patches.new(pn, commit, self.__msg) | |
dcd32afa | 117 | _print_current_patch(self.__stack.patchorder.applied, self.__applied) |
cbe4567e KH |
118 | self.__stack.patchorder.applied = self.__applied |
119 | self.__stack.patchorder.unapplied = self.__unapplied | |
dcd32afa | 120 | |
f9cc5e69 KH |
121 | if self.__error: |
122 | return utils.STGIT_CONFLICT | |
123 | else: | |
124 | return utils.STGIT_SUCCESS | |
125 | ||
dcd32afa KH |
126 | def __halt(self, msg): |
127 | self.__error = msg | |
128 | raise TransactionHalted(msg) | |
129 | ||
130 | @staticmethod | |
131 | def __print_popped(popped): | |
132 | if len(popped) == 0: | |
133 | pass | |
134 | elif len(popped) == 1: | |
135 | out.info('Popped %s' % popped[0]) | |
136 | else: | |
137 | out.info('Popped %s -- %s' % (popped[-1], popped[0])) | |
138 | ||
139 | def pop_patches(self, p): | |
140 | """Pop all patches pn for which p(pn) is true. Return the list of | |
141 | other patches that had to be popped to accomplish this.""" | |
142 | popped = [] | |
143 | for i in xrange(len(self.applied)): | |
144 | if p(self.applied[i]): | |
145 | popped = self.applied[i:] | |
146 | del self.applied[i:] | |
147 | break | |
148 | popped1 = [pn for pn in popped if not p(pn)] | |
149 | popped2 = [pn for pn in popped if p(pn)] | |
150 | self.unapplied = popped1 + popped2 + self.unapplied | |
151 | self.__print_popped(popped) | |
152 | return popped1 | |
153 | ||
154 | def delete_patches(self, p): | |
155 | """Delete all patches pn for which p(pn) is true. Return the list of | |
156 | other patches that had to be popped to accomplish this.""" | |
157 | popped = [] | |
158 | all_patches = self.applied + self.unapplied | |
159 | for i in xrange(len(self.applied)): | |
160 | if p(self.applied[i]): | |
161 | popped = self.applied[i:] | |
162 | del self.applied[i:] | |
163 | break | |
164 | popped = [pn for pn in popped if not p(pn)] | |
165 | self.unapplied = popped + [pn for pn in self.unapplied if not p(pn)] | |
166 | self.__print_popped(popped) | |
167 | for pn in all_patches: | |
168 | if p(pn): | |
169 | s = ['', ' (empty)'][self.patches[pn].data.is_nochange()] | |
170 | self.patches[pn] = None | |
171 | out.info('Deleted %s%s' % (pn, s)) | |
172 | return popped | |
173 | ||
174 | def push_patch(self, pn, iw = None): | |
175 | """Attempt to push the named patch. If this results in conflicts, | |
176 | halts the transaction. If index+worktree are given, spill any | |
177 | conflicts to them.""" | |
dcd32afa | 178 | cd = self.patches[pn].data |
a0c727a4 | 179 | cd = cd.set_committer(None) |
dcd32afa KH |
180 | s = ['', ' (empty)'][cd.is_nochange()] |
181 | oldparent = cd.parent | |
182 | cd = cd.set_parent(self.__head) | |
183 | base = oldparent.data.tree | |
184 | ours = cd.parent.data.tree | |
185 | theirs = cd.tree | |
186 | tree = self.__stack.repository.simple_merge(base, ours, theirs) | |
187 | merge_conflict = False | |
188 | if not tree: | |
189 | if iw == None: | |
190 | self.__halt('%s does not apply cleanly' % pn) | |
191 | try: | |
192 | self.__checkout(ours, iw) | |
193 | except git.CheckoutException: | |
194 | self.__halt('Index/worktree dirty') | |
195 | try: | |
196 | iw.merge(base, ours, theirs) | |
197 | tree = iw.index.write_tree() | |
198 | self.__current_tree = tree | |
199 | s = ' (modified)' | |
363d432f | 200 | except git.MergeConflictException: |
dcd32afa KH |
201 | tree = ours |
202 | merge_conflict = True | |
203 | s = ' (conflict)' | |
363d432f KH |
204 | except git.MergeException, e: |
205 | self.__halt(str(e)) | |
dcd32afa KH |
206 | cd = cd.set_tree(tree) |
207 | self.patches[pn] = self.__stack.repository.commit(cd) | |
9ee0285b | 208 | del self.unapplied[self.unapplied.index(pn)] |
dcd32afa KH |
209 | self.applied.append(pn) |
210 | out.info('Pushed %s%s' % (pn, s)) | |
211 | if merge_conflict: | |
212 | self.__halt('Merge conflict') |