1 # -*- coding: utf-8 -*-
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, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 from stgit.argparse import opt
22 from stgit.commands import common
23 from stgit.lib import git, transaction, edit
24 from stgit.out import out
25 from stgit import argparse, utils
27 help = 'Generate a new commit for the current patch'
29 usage = ['[options] [<files or dirs>]']
31 Include the latest work tree and index changes in the current patch.
32 This command generates a new git commit object for the patch; the old
33 commit is no longer visible.
35 You may optionally list one or more files or directories relative to
36 the current working directory; if you do, only matching files will be
39 Behind the scenes, stg refresh first creates a new temporary patch
40 with your updates, and then merges that patch into the patch you asked
41 to have refreshed. If you asked to refresh a patch other than the
42 topmost patch, there can be conflicts; in that case, the temporary
43 patch will be left for you to take care of, for example with stg
46 The creation of the temporary patch is recorded in a separate entry in
47 the patch stack log; this means that one undo step will undo the merge
48 between the other patch and the temp patch, and two undo steps will
49 additionally get rid of the temp patch."""
51 args = [argparse.dirty_files]
53 opt('-u', '--update', action = 'store_true',
54 short = 'Only update the current patch files'),
55 opt('-i', '--index', action = 'store_true',
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."""),
59 opt('-p', '--patch', args = [argparse.other_applied_patches,
60 argparse.unapplied_patches],
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'),
64 opt('-a', '--annotate', metavar = 'NOTE',
65 short = 'Annotate the patch log entry')
66 ] + (argparse.message_options(save_template = False) +
67 argparse.sign_options() + argparse.author_options())
69 directory = common.DirectoryHasRepositoryLib()
71 def get_patch(stack, given_patch):
72 """Get the name of the patch we are to refresh."""
74 patch_name = given_patch
75 if not stack.patches.exists(patch_name):
76 raise common.CmdException('%s: no such patch' % patch_name)
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]
84 def list_files(stack, patch_name, args, index, update):
85 """Figure out which files to update."""
87 # --index: Don't update the index.
89 paths = stack.repository.default_iw.changed_files(
90 stack.head.data.tree, args or [])
92 # --update: Restrict update to the paths that were already
94 paths &= stack.patches.get(patch_name).files()
97 def 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>}"""
103 iw = git.IndexAndWorktree(index, stack.repository.default_worktree)
104 iw.update_index(paths)
105 return index.write_tree()
107 index = stack.repository.temp_index()
109 index.read_tree(stack.head)
113 stack.repository.default_iw.update_index(paths)
115 return go(stack.repository.default_index)
117 def 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
132 def absorb_applied(trans, iw, patch_name, temp_name, edit_fun):
133 """Absorb the temp patch (C{temp_name}) into the given patch
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.
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
143 # Pop any patch on top of the patch we're refreshing.
144 to_pop = trans.applied[trans.applied.index(patch_name)+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
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(
155 edit_fun(trans.patches[patch_name].data.set_tree(temp_cd.tree)))
156 popped = trans.delete_patches(lambda pn: pn == temp_name, quiet = True)
157 assert not popped # the temp patch was topmost
160 # Push back any patch we were forced to pop earlier.
162 trans.push_patch(pn, iw)
163 except transaction.TransactionHalted:
167 def absorb_unapplied(trans, iw, patch_name, temp_name, edit_fun):
168 """Absorb the temp patch (C{temp_name}) into the given patch
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.
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."""
178 # Pop the temp patch.
179 popped = trans.pop_patches(lambda pn: pn == temp_name)
180 assert not popped # the temp patch was topmost
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
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)
193 # It worked. Refresh the patch with the new tree, and delete
195 trans.patches[patch_name] = trans.stack.repository.commit(
196 edit_fun(patch_cd.set_tree(new_tree)))
197 popped = trans.delete_patches(lambda pn: pn == temp_name, quiet = True)
198 assert not popped # the temp patch was not applied
201 # Nope, we couldn't create the new tree, so we'll just have to
202 # leave the temp patch for the user.
205 def absorb(stack, patch_name, temp_name, edit_fun, annotate = None):
206 """Absorb the temp patch into the target patch."""
208 log_msg = 'refresh\n\n' + annotate
211 trans = transaction.StackTransaction(stack, log_msg)
212 iw = stack.repository.default_iw
213 f = { True: absorb_applied, False: absorb_unapplied
214 }[patch_name in trans.applied]
215 if f(trans, iw, patch_name, temp_name, edit_fun):
219 out.warn('The new changes did not apply cleanly to %s.'
220 % patch_name, 'They were saved in %s.' % temp_name)
225 def func(parser, options, args):
226 """Generate a new commit for the current or given patch."""
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')
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)
238 # Make sure there are no conflicts in the files we want to
240 if stack.repository.default_index.conflicts() & paths:
241 raise common.CmdException(
242 'Cannot refresh -- resolve conflicts first')
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:
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
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
261 return absorb(stack, patch_name, temp_name, edit_fun,
262 annotate = options.annotate)