chiark / gitweb /
Reinstate the --annotate option for refresh
[stgit] / stgit / commands / refresh.py
CommitLineData
85aaed81 1# -*- coding: utf-8 -*-
fcee87cf
CM
2
3__copyright__ = """
4Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
85aaed81 5Copyright (C) 2008, Karl Hasselström <kha@treskal.com>
fcee87cf
CM
6
7This program is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License version 2 as
9published by the Free Software Foundation.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with this program; if not, write to the Free Software
18Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19"""
20
575bbdae 21from stgit.argparse import opt
85aaed81 22from stgit.commands import common
f9d69fc4 23from stgit.lib import git, transaction, edit
85aaed81 24from stgit.out import out
f9d69fc4 25from stgit import argparse, utils
fcee87cf 26
575bbdae 27help = 'Generate a new commit for the current patch'
33ff9cdd 28kind = 'patch'
575bbdae
KH
29usage = ['[options] [<files or dirs>]']
30description = """
85aaed81
KH
31Include the latest work tree and index changes in the current patch.
32This command generates a new git commit object for the patch; the old
33commit is no longer visible.
34
35You may optionally list one or more files or directories relative to
36the current working directory; if you do, only matching files will be
37updated.
38
39Behind the scenes, stg refresh first creates a new temporary patch
40with your updates, and then merges that patch into the patch you asked
41to have refreshed. If you asked to refresh a patch other than the
42topmost patch, there can be conflicts; in that case, the temporary
43patch will be left for you to take care of, for example with stg
594aa463 44squash.
85aaed81
KH
45
46The creation of the temporary patch is recorded in a separate entry in
47the patch stack log; this means that one undo step will undo the merge
48between the other patch and the temp patch, and two undo steps will
49additionally get rid of the temp patch."""
fcee87cf 50
6c8a90e1 51args = [argparse.dirty_files]
575bbdae 52options = [
85aaed81 53 opt('-u', '--update', action = 'store_true',
575bbdae 54 short = 'Only update the current patch files'),
85aaed81 55 opt('-i', '--index', action = 'store_true',
575bbdae
KH
56 short = 'Refresh from index instead of worktree', long = """
57 Instead of setting the patch top to the current contents of
58 the worktree, set it to the current contents of the index."""),
6c8a90e1
KH
59 opt('-p', '--patch', args = [argparse.other_applied_patches,
60 argparse.unapplied_patches],
f9d69fc4
KH
61 short = 'Refresh (applied) PATCH instead of the top patch'),
62 opt('-e', '--edit', action = 'store_true',
63 short = 'Invoke an editor for the patch description'),
d11f4f2a
CM
64 opt('-a', '--annotate', metavar = 'NOTE',
65 short = 'Annotate the patch log entry')
f9d69fc4
KH
66 ] + (argparse.message_options(save_template = False) +
67 argparse.sign_options() + argparse.author_options())
575bbdae 68
85aaed81 69directory = common.DirectoryHasRepositoryLib()
fcee87cf 70
85aaed81
KH
71def get_patch(stack, given_patch):
72 """Get the name of the patch we are to refresh."""
73 if given_patch:
74 patch_name = given_patch
75 if not stack.patches.exists(patch_name):
76 raise common.CmdException('%s: no such patch' % patch_name)
77 return patch_name
78 else:
79 if not stack.patchorder.applied:
80 raise common.CmdException(
81 'Cannot refresh top patch, because no patches are applied')
82 return stack.patchorder.applied[-1]
83
84def list_files(stack, patch_name, args, index, update):
85 """Figure out which files to update."""
86 if index:
87 # --index: Don't update the index.
88 return set()
89 paths = stack.repository.default_iw.changed_files(
90 stack.head.data.tree, args or [])
91 if update:
92 # --update: Restrict update to the paths that were already
93 # part of the patch.
94 paths &= stack.patches.get(patch_name).files()
95 return paths
96
97def write_tree(stack, paths, temp_index):
98 """Possibly update the index, and then write its tree.
99 @return: The written tree.
100 @rtype: L{Tree<stgit.git.Tree>}"""
101 def go(index):
102 if paths:
103 iw = git.IndexAndWorktree(index, stack.repository.default_worktree)
104 iw.update_index(paths)
105 return index.write_tree()
106 if temp_index:
107 index = stack.repository.temp_index()
108 try:
109 index.read_tree(stack.head)
110 return go(index)
111 finally:
112 index.delete()
113 stack.repository.default_iw.update_index(paths)
114 else:
115 return go(stack.repository.default_index)
116
117def make_temp_patch(stack, patch_name, paths, temp_index):
118 """Commit index to temp patch, in a complete transaction. If any path
119 limiting is in effect, use a temp index."""
120 tree = write_tree(stack, paths, temp_index)
121 commit = stack.repository.commit(git.CommitData(
122 tree = tree, parents = [stack.head],
123 message = 'Refresh of %s' % patch_name))
124 temp_name = utils.make_patch_name('refresh-temp', stack.patches.exists)
125 trans = transaction.StackTransaction(stack,
126 'refresh (create temporary patch)')
127 trans.patches[temp_name] = commit
128 trans.applied.append(temp_name)
129 return trans.run(stack.repository.default_iw,
130 print_current_patch = False), temp_name
131
f9d69fc4 132def absorb_applied(trans, iw, patch_name, temp_name, edit_fun):
85aaed81 133 """Absorb the temp patch (C{temp_name}) into the given patch
f9d69fc4
KH
134 (C{patch_name}), which must be applied. If the absorption
135 succeeds, call C{edit_fun} on the resulting
136 L{CommitData<stgit.lib.git.CommitData>} before committing it and
137 commit the return value.
85aaed81
KH
138
139 @return: C{True} if we managed to absorb the temp patch, C{False}
140 if we had to leave it for the user to deal with."""
141 temp_absorbed = False
142 try:
143 # Pop any patch on top of the patch we're refreshing.
144 to_pop = trans.applied[trans.applied.index(patch_name)+1:]
145 if len(to_pop) > 1:
146 popped = trans.pop_patches(lambda pn: pn in to_pop)
147 assert not popped # no other patches were popped
148 trans.push_patch(temp_name, iw)
149 assert to_pop.pop() == temp_name
150
151 # Absorb the temp patch.
152 temp_cd = trans.patches[temp_name].data
153 assert trans.patches[patch_name] == temp_cd.parent
154 trans.patches[patch_name] = trans.stack.repository.commit(
f9d69fc4 155 edit_fun(trans.patches[patch_name].data.set_tree(temp_cd.tree)))
85aaed81
KH
156 popped = trans.delete_patches(lambda pn: pn == temp_name, quiet = True)
157 assert not popped # the temp patch was topmost
158 temp_absorbed = True
159
160 # Push back any patch we were forced to pop earlier.
161 for pn in to_pop:
162 trans.push_patch(pn, iw)
163 except transaction.TransactionHalted:
164 pass
165 return temp_absorbed
166
f9d69fc4 167def absorb_unapplied(trans, iw, patch_name, temp_name, edit_fun):
85aaed81 168 """Absorb the temp patch (C{temp_name}) into the given patch
f9d69fc4
KH
169 (C{patch_name}), which must be unapplied. If the absorption
170 succeeds, call C{edit_fun} on the resulting
171 L{CommitData<stgit.lib.git.CommitData>} before committing it and
172 commit the return value.
85aaed81
KH
173
174 @param iw: Not used.
175 @return: C{True} if we managed to absorb the temp patch, C{False}
176 if we had to leave it for the user to deal with."""
177
178 # Pop the temp patch.
179 popped = trans.pop_patches(lambda pn: pn == temp_name)
180 assert not popped # the temp patch was topmost
181
182 # Try to create the new tree of the refreshed patch. (This is the
183 # same operation as pushing the temp patch onto the patch we're
184 # trying to refresh -- but we don't have a worktree to spill
185 # conflicts to, so if the simple merge doesn't succeed, we have to
186 # give up.)
187 patch_cd = trans.patches[patch_name].data
188 temp_cd = trans.patches[temp_name].data
189 new_tree = trans.stack.repository.simple_merge(
190 base = temp_cd.parent.data.tree,
191 ours = patch_cd.tree, theirs = temp_cd.tree)
192 if new_tree:
193 # It worked. Refresh the patch with the new tree, and delete
194 # the temp patch.
195 trans.patches[patch_name] = trans.stack.repository.commit(
f9d69fc4 196 edit_fun(patch_cd.set_tree(new_tree)))
85aaed81
KH
197 popped = trans.delete_patches(lambda pn: pn == temp_name, quiet = True)
198 assert not popped # the temp patch was not applied
199 return True
42fc7623 200 else:
85aaed81
KH
201 # Nope, we couldn't create the new tree, so we'll just have to
202 # leave the temp patch for the user.
203 return False
204
d11f4f2a 205def absorb(stack, patch_name, temp_name, edit_fun, annotate = None):
85aaed81 206 """Absorb the temp patch into the target patch."""
d11f4f2a
CM
207 if annotate:
208 log_msg = 'refresh\n\n' + annotate
209 else:
210 log_msg = 'refresh'
211 trans = transaction.StackTransaction(stack, log_msg)
85aaed81
KH
212 iw = stack.repository.default_iw
213 f = { True: absorb_applied, False: absorb_unapplied
214 }[patch_name in trans.applied]
f9d69fc4 215 if f(trans, iw, patch_name, temp_name, edit_fun):
85aaed81 216 def info_msg(): pass
fcee87cf 217 else:
85aaed81
KH
218 def info_msg():
219 out.warn('The new changes did not apply cleanly to %s.'
220 % patch_name, 'They were saved in %s.' % temp_name)
221 r = trans.run(iw)
222 info_msg()
223 return r
224
225def func(parser, options, args):
226 """Generate a new commit for the current or given patch."""
227
228 # Catch illegal argument combinations.
229 path_limiting = bool(args or options.update)
230 if options.index and path_limiting:
231 raise common.CmdException(
232 'Only full refresh is available with the --index option')
233
234 stack = directory.repository.current_stack
235 patch_name = get_patch(stack, options.patch)
236 paths = list_files(stack, patch_name, args, options.index, options.update)
237
238 # Make sure there are no conflicts in the files we want to
239 # refresh.
240 if stack.repository.default_index.conflicts() & paths:
241 raise common.CmdException(
242 'Cannot refresh -- resolve conflicts first')
243
244 # Commit index to temp patch, and absorb it into the target patch.
245 retval, temp_name = make_temp_patch(
246 stack, patch_name, paths, temp_index = path_limiting)
247 if retval != utils.STGIT_SUCCESS:
248 return retval
f9d69fc4
KH
249 def edit_fun(cd):
250 cd, failed_diff = edit.auto_edit_patch(
251 stack.repository, cd, msg = options.message, contains_diff = False,
252 author = options.author, committer = lambda p: p,
253 sign_str = options.sign_str)
254 assert not failed_diff
255 if options.edit:
256 cd, failed_diff = edit.interactive_edit_patch(
257 stack.repository, cd, edit_diff = False,
258 diff_flags = [], replacement_diff = None)
259 assert not failed_diff
260 return cd
d11f4f2a
CM
261 return absorb(stack, patch_name, temp_name, edit_fun,
262 annotate = options.annotate)