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