1 from stgit
import exception
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 stack
= property(lambda self
: self
.__stack
)
45 patches
= property(lambda self
: self
.__patches
)
46 def __set_applied(self
, val
):
47 self
.__applied
= list(val
)
48 applied
= property(lambda self
: self
.__applied
, __set_applied
)
49 def __set_unapplied(self
, val
):
50 self
.__unapplied
= list(val
)
51 unapplied
= property(lambda self
: self
.__unapplied
, __set_unapplied
)
52 def __checkout(self
, tree
, iw
):
53 if not self
.__stack
.head_top_equal():
55 'HEAD and top are not the same.',
56 'This can happen if you modify a branch with git.',
57 '"stg repair --help" explains more about what to do next.')
59 if self
.__current_tree
!= tree
:
61 iw
.checkout(self
.__current_tree
, tree
)
62 self
.__current_tree
= tree
65 raise TransactionException(
66 'Command aborted (all changes rolled back)')
67 def __check_consistency(self
):
68 remaining
= set(self
.__applied
+ self
.__unapplied
)
69 for pn
, commit
in self
.__patches
.iteritems():
71 assert self
.__stack
.patches
.exists(pn
)
73 assert pn
in remaining
77 return self
.__patches
[self
.__applied
[-1]]
79 return self
.__stack
.base
80 def run(self
, iw
= None):
81 self
.__check_consistency()
82 new_head
= self
.__head
86 self
.__checkout(new_head
.data
.tree
, iw
)
87 except git
.CheckoutException
:
88 # We have to abort the transaction. The only state we need
89 # to restore is index+worktree.
90 self
.__checkout(self
.__stack
.head
.data
.tree
, iw
)
92 self
.__stack
.set_head(new_head
, self
.__msg
)
95 out
.error(self
.__error
)
98 for pn
, commit
in self
.__patches
.iteritems():
99 if self
.__stack
.patches
.exists(pn
):
100 p
= self
.__stack
.patches
.get(pn
)
104 p
.set_commit(commit
, self
.__msg
)
106 self
.__stack
.patches
.new(pn
, commit
, self
.__msg
)
107 _print_current_patch(self
.__stack
.patchorder
.applied
, self
.__applied
)
108 self
.__stack
.patchorder
.applied
= self
.__applied
109 self
.__stack
.patchorder
.unapplied
= self
.__unapplied
111 def __halt(self
, msg
):
113 raise TransactionHalted(msg
)
116 def __print_popped(popped
):
119 elif len(popped
) == 1:
120 out
.info('Popped %s' % popped
[0])
122 out
.info('Popped %s -- %s' %
(popped
[-1], popped
[0]))
124 def pop_patches(self
, p
):
125 """Pop all patches pn for which p(pn) is true. Return the list of
126 other patches that had to be popped to accomplish this."""
128 for i
in xrange(len(self
.applied
)):
129 if p(self
.applied
[i
]):
130 popped
= self
.applied
[i
:]
133 popped1
= [pn
for pn
in popped
if not p(pn
)]
134 popped2
= [pn
for pn
in popped
if p(pn
)]
135 self
.unapplied
= popped1
+ popped2
+ self
.unapplied
136 self
.__print_popped(popped
)
139 def delete_patches(self
, p
):
140 """Delete all patches pn for which p(pn) is true. Return the list of
141 other patches that had to be popped to accomplish this."""
143 all_patches
= self
.applied
+ self
.unapplied
144 for i
in xrange(len(self
.applied
)):
145 if p(self
.applied
[i
]):
146 popped
= self
.applied
[i
:]
149 popped
= [pn
for pn
in popped
if not p(pn
)]
150 self
.unapplied
= popped
+ [pn
for pn
in self
.unapplied
if not p(pn
)]
151 self
.__print_popped(popped
)
152 for pn
in all_patches
:
154 s
= ['', ' (empty)'][self
.patches
[pn
].data
.is_nochange()]
155 self
.patches
[pn
] = None
156 out
.info('Deleted %s%s' %
(pn
, s
))
159 def push_patch(self
, pn
, iw
= None):
160 """Attempt to push the named patch. If this results in conflicts,
161 halts the transaction. If index+worktree are given, spill any
162 conflicts to them."""
163 i
= self
.unapplied
.index(pn
)
164 cd
= self
.patches
[pn
].data
165 s
= ['', ' (empty)'][cd
.is_nochange()]
166 oldparent
= cd
.parent
167 cd
= cd
.set_parent(self
.__head
)
168 base
= oldparent
.data
.tree
169 ours
= cd
.parent
.data
.tree
171 tree
= self
.__stack
.repository
.simple_merge(base
, ours
, theirs
)
172 merge_conflict
= False
175 self
.__halt('%s does not apply cleanly' % pn
)
177 self
.__checkout(ours
, iw
)
178 except git
.CheckoutException
:
179 self
.__halt('Index/worktree dirty')
181 iw
.merge(base
, ours
, theirs
)
182 tree
= iw
.index
.write_tree()
183 self
.__current_tree
= tree
185 except git
.MergeException
:
187 merge_conflict
= True
189 cd
= cd
.set_tree(tree
)
190 self
.patches
[pn
] = self
.__stack
.repository
.commit(cd
)
191 del self
.unapplied
[i
]
192 self
.applied
.append(pn
)
193 out
.info('Pushed %s%s' %
(pn
, s
))
195 self
.__halt('Merge conflict')