1 from stgit
import exception
, utils
2 from stgit
.utils
import any
, all
3 from stgit
.out
import *
4 from stgit
.lib
import git
6 class TransactionException(exception
.StgException
):
9 class TransactionHalted(TransactionException
):
12 def _print_current_patch(old_applied
, new_applied
):
14 out
.info('Now at patch "%s"' % pn
)
15 if not old_applied
and not new_applied
:
18 now_at(new_applied
[-1])
20 out
.info('No patch applied')
21 elif old_applied
[-1] == new_applied
[-1]:
24 now_at(new_applied
[-1])
26 class _TransPatchMap(dict):
27 def __init__(self
, stack
):
30 def __getitem__(self
, pn
):
32 return dict.__getitem__(self
, pn
)
34 return self
.__stack
.patches
.get(pn
).commit
36 class StackTransaction(object):
37 def __init__(self
, stack
, msg
):
40 self
.__patches
= _TransPatchMap(stack
)
41 self
.__applied
= list(self
.__stack
.patchorder
.applied
)
42 self
.__unapplied
= list(self
.__stack
.patchorder
.unapplied
)
44 self
.__current_tree
= self
.__stack
.head
.data
.tree
45 self
.__base
= self
.__stack
.base
46 stack
= property(lambda self
: self
.__stack
)
47 patches
= property(lambda self
: self
.__patches
)
48 def __set_applied(self
, val
):
49 self
.__applied
= list(val
)
50 applied
= property(lambda self
: self
.__applied
, __set_applied
)
51 def __set_unapplied(self
, val
):
52 self
.__unapplied
= list(val
)
53 unapplied
= property(lambda self
: self
.__unapplied
, __set_unapplied
)
54 def __set_base(self
, val
):
55 assert (not self
.__applied
56 or self
.patches
[self
.applied
[0]].data
.parent
== val
)
58 base
= property(lambda self
: self
.__base
, __set_base
)
59 def __checkout(self
, tree
, iw
):
60 if not self
.__stack
.head_top_equal():
62 'HEAD and top are not the same.',
63 'This can happen if you modify a branch with git.',
64 '"stg repair --help" explains more about what to do next.')
66 if self
.__current_tree
!= tree
:
68 iw
.checkout(self
.__current_tree
, tree
)
69 self
.__current_tree
= tree
72 raise TransactionException(
73 'Command aborted (all changes rolled back)')
74 def __check_consistency(self
):
75 remaining
= set(self
.__applied
+ self
.__unapplied
)
76 for pn
, commit
in self
.__patches
.iteritems():
78 assert self
.__stack
.patches
.exists(pn
)
80 assert pn
in remaining
84 return self
.__patches
[self
.__applied
[-1]]
87 def abort(self
, iw
= None):
88 # The only state we need to restore is index+worktree.
90 self
.__checkout(self
.__stack
.head
.data
.tree
, iw
)
91 def run(self
, iw
= None):
92 self
.__check_consistency()
93 new_head
= self
.__head
98 self
.__checkout(new_head
.data
.tree
, iw
)
99 except git
.CheckoutException
:
100 # We have to abort the transaction.
103 self
.__stack
.set_head(new_head
, self
.__msg
)
106 out
.error(self
.__error
)
109 for pn
, commit
in self
.__patches
.iteritems():
110 if self
.__stack
.patches
.exists(pn
):
111 p
= self
.__stack
.patches
.get(pn
)
115 p
.set_commit(commit
, self
.__msg
)
117 self
.__stack
.patches
.new(pn
, commit
, self
.__msg
)
118 _print_current_patch(self
.__stack
.patchorder
.applied
, self
.__applied
)
119 self
.__stack
.patchorder
.applied
= self
.__applied
120 self
.__stack
.patchorder
.unapplied
= self
.__unapplied
123 return utils
.STGIT_CONFLICT
125 return utils
.STGIT_SUCCESS
127 def __halt(self
, msg
):
129 raise TransactionHalted(msg
)
132 def __print_popped(popped
):
135 elif len(popped
) == 1:
136 out
.info('Popped %s' % popped
[0])
138 out
.info('Popped %s -- %s' %
(popped
[-1], popped
[0]))
140 def pop_patches(self
, p
):
141 """Pop all patches pn for which p(pn) is true. Return the list of
142 other patches that had to be popped to accomplish this."""
144 for i
in xrange(len(self
.applied
)):
145 if p(self
.applied
[i
]):
146 popped
= self
.applied
[i
:]
149 popped1
= [pn
for pn
in popped
if not p(pn
)]
150 popped2
= [pn
for pn
in popped
if p(pn
)]
151 self
.unapplied
= popped1
+ popped2
+ self
.unapplied
152 self
.__print_popped(popped
)
155 def delete_patches(self
, p
):
156 """Delete all patches pn for which p(pn) is true. Return the list of
157 other patches that had to be popped to accomplish this."""
159 all_patches
= self
.applied
+ self
.unapplied
160 for i
in xrange(len(self
.applied
)):
161 if p(self
.applied
[i
]):
162 popped
= self
.applied
[i
:]
165 popped
= [pn
for pn
in popped
if not p(pn
)]
166 self
.unapplied
= popped
+ [pn
for pn
in self
.unapplied
if not p(pn
)]
167 self
.__print_popped(popped
)
168 for pn
in all_patches
:
170 s
= ['', ' (empty)'][self
.patches
[pn
].data
.is_nochange()]
171 self
.patches
[pn
] = None
172 out
.info('Deleted %s%s' %
(pn
, s
))
175 def push_patch(self
, pn
, iw
= None):
176 """Attempt to push the named patch. If this results in conflicts,
177 halts the transaction. If index+worktree are given, spill any
178 conflicts to them."""
179 orig_cd
= self
.patches
[pn
].data
180 cd
= orig_cd
.set_committer(None)
181 s
= ['', ' (empty)'][cd
.is_nochange()]
182 oldparent
= cd
.parent
183 cd
= cd
.set_parent(self
.__head
)
184 base
= oldparent
.data
.tree
185 ours
= cd
.parent
.data
.tree
187 tree
= self
.__stack
.repository
.simple_merge(base
, ours
, theirs
)
188 merge_conflict
= False
191 self
.__halt('%s does not apply cleanly' % pn
)
193 self
.__checkout(ours
, iw
)
194 except git
.CheckoutException
:
195 self
.__halt('Index/worktree dirty')
197 iw
.merge(base
, ours
, theirs
)
198 tree
= iw
.index
.write_tree()
199 self
.__current_tree
= tree
201 except git
.MergeConflictException
:
203 merge_conflict
= True
205 except git
.MergeException
, e
:
207 cd
= cd
.set_tree(tree
)
208 if any(getattr(cd
, a
) != getattr(orig_cd
, a
) for a
in
209 ['parent', 'tree', 'author', 'message']):
210 self
.patches
[pn
] = self
.__stack
.repository
.commit(cd
)
213 del self
.unapplied
[self
.unapplied
.index(pn
)]
214 self
.applied
.append(pn
)
215 out
.info('Pushed %s%s' %
(pn
, s
))
217 self
.__halt('Merge conflict')