1 from stgit
import exception
, utils
2 from stgit
.out
import *
3 from stgit
.lib
import git
5 class TransactionException(exception
.StgException
):
8 class TransactionHalted(TransactionException
):
11 def _print_current_patch(old_applied
, new_applied
):
13 out
.info('Now at patch "%s"' % pn
)
14 if not old_applied
and not new_applied
:
17 now_at(new_applied
[-1])
19 out
.info('No patch applied')
20 elif old_applied
[-1] == new_applied
[-1]:
23 now_at(new_applied
[-1])
25 class _TransPatchMap(dict):
26 def __init__(self
, stack
):
29 def __getitem__(self
, pn
):
31 return dict.__getitem__(self
, pn
)
33 return self
.__stack
.patches
.get(pn
).commit
35 class StackTransaction(object):
36 def __init__(self
, stack
, msg
):
39 self
.__patches
= _TransPatchMap(stack
)
40 self
.__applied
= list(self
.__stack
.patchorder
.applied
)
41 self
.__unapplied
= list(self
.__stack
.patchorder
.unapplied
)
43 self
.__current_tree
= self
.__stack
.head
.data
.tree
44 self
.__base
= self
.__stack
.base
45 stack
= property(lambda self
: self
.__stack
)
46 patches
= property(lambda self
: self
.__patches
)
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
)
53 def __set_base(self
, val
):
54 assert (not self
.__applied
55 or self
.patches
[self
.applied
[0]].data
.parent
== val
)
57 base
= property(lambda self
: self
.__base
, __set_base
)
58 def __checkout(self
, tree
, iw
):
59 if not self
.__stack
.head_top_equal():
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.')
65 if self
.__current_tree
!= tree
:
67 iw
.checkout(self
.__current_tree
, tree
)
68 self
.__current_tree
= tree
71 raise TransactionException(
72 'Command aborted (all changes rolled back)')
73 def __check_consistency(self
):
74 remaining
= set(self
.__applied
+ self
.__unapplied
)
75 for pn
, commit
in self
.__patches
.iteritems():
77 assert self
.__stack
.patches
.exists(pn
)
79 assert pn
in remaining
83 return self
.__patches
[self
.__applied
[-1]]
86 def abort(self
, iw
= None):
87 # The only state we need to restore is index+worktree.
89 self
.__checkout(self
.__stack
.head
.data
.tree
, iw
)
90 def run(self
, iw
= None):
91 self
.__check_consistency()
92 new_head
= self
.__head
96 self
.__checkout(new_head
.data
.tree
, iw
)
97 except git
.CheckoutException
:
98 # We have to abort the transaction.
101 self
.__stack
.set_head(new_head
, self
.__msg
)
104 out
.error(self
.__error
)
107 for pn
, commit
in self
.__patches
.iteritems():
108 if self
.__stack
.patches
.exists(pn
):
109 p
= self
.__stack
.patches
.get(pn
)
113 p
.set_commit(commit
, self
.__msg
)
115 self
.__stack
.patches
.new(pn
, commit
, self
.__msg
)
116 _print_current_patch(self
.__stack
.patchorder
.applied
, self
.__applied
)
117 self
.__stack
.patchorder
.applied
= self
.__applied
118 self
.__stack
.patchorder
.unapplied
= self
.__unapplied
121 return utils
.STGIT_CONFLICT
123 return utils
.STGIT_SUCCESS
125 def __halt(self
, msg
):
127 raise TransactionHalted(msg
)
130 def __print_popped(popped
):
133 elif len(popped
) == 1:
134 out
.info('Popped %s' % popped
[0])
136 out
.info('Popped %s -- %s' %
(popped
[-1], popped
[0]))
138 def pop_patches(self
, p
):
139 """Pop all patches pn for which p(pn) is true. Return the list of
140 other patches that had to be popped to accomplish this."""
142 for i
in xrange(len(self
.applied
)):
143 if p(self
.applied
[i
]):
144 popped
= self
.applied
[i
:]
147 popped1
= [pn
for pn
in popped
if not p(pn
)]
148 popped2
= [pn
for pn
in popped
if p(pn
)]
149 self
.unapplied
= popped1
+ popped2
+ self
.unapplied
150 self
.__print_popped(popped
)
153 def delete_patches(self
, p
):
154 """Delete all patches pn for which p(pn) is true. Return the list of
155 other patches that had to be popped to accomplish this."""
157 all_patches
= self
.applied
+ self
.unapplied
158 for i
in xrange(len(self
.applied
)):
159 if p(self
.applied
[i
]):
160 popped
= self
.applied
[i
:]
163 popped
= [pn
for pn
in popped
if not p(pn
)]
164 self
.unapplied
= popped
+ [pn
for pn
in self
.unapplied
if not p(pn
)]
165 self
.__print_popped(popped
)
166 for pn
in all_patches
:
168 s
= ['', ' (empty)'][self
.patches
[pn
].data
.is_nochange()]
169 self
.patches
[pn
] = None
170 out
.info('Deleted %s%s' %
(pn
, s
))
173 def push_patch(self
, pn
, iw
= None):
174 """Attempt to push the named patch. If this results in conflicts,
175 halts the transaction. If index+worktree are given, spill any
176 conflicts to them."""
177 i
= self
.unapplied
.index(pn
)
178 cd
= self
.patches
[pn
].data
179 s
= ['', ' (empty)'][cd
.is_nochange()]
180 oldparent
= cd
.parent
181 cd
= cd
.set_parent(self
.__head
)
182 base
= oldparent
.data
.tree
183 ours
= cd
.parent
.data
.tree
185 tree
= self
.__stack
.repository
.simple_merge(base
, ours
, theirs
)
186 merge_conflict
= False
189 self
.__halt('%s does not apply cleanly' % pn
)
191 self
.__checkout(ours
, iw
)
192 except git
.CheckoutException
:
193 self
.__halt('Index/worktree dirty')
195 iw
.merge(base
, ours
, theirs
)
196 tree
= iw
.index
.write_tree()
197 self
.__current_tree
= tree
199 except git
.MergeException
:
201 merge_conflict
= True
203 cd
= cd
.set_tree(tree
)
204 self
.patches
[pn
] = self
.__stack
.repository
.commit(cd
)
205 del self
.unapplied
[i
]
206 self
.applied
.append(pn
)
207 out
.info('Pushed %s%s' %
(pn
, s
))
209 self
.__halt('Merge conflict')