Rename "stg coalesce" to "stg squash"
[stgit] / stgit / commands / refresh.py
1 # -*- coding: utf-8 -*-
2
3 __copyright__ = """
4 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
5 Copyright (C) 2008, Karl Hasselström <kha@treskal.com>
6
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.
10
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.
15
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
19 """
20
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
26
27 help = 'Generate a new commit for the current patch'
28 kind = 'patch'
29 usage = ['[options] [<files or dirs>]']
30 description = """
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.
34
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
37 updated.
38
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
44 squash.
45
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."""
50
51 args = [argparse.dirty_files]
52 options = [
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 ] + (argparse.message_options(save_template = False) +
65 argparse.sign_options() + argparse.author_options())
66
67 directory = common.DirectoryHasRepositoryLib()
68
69 def get_patch(stack, given_patch):
70 """Get the name of the patch we are to refresh."""
71 if given_patch:
72 patch_name = given_patch
73 if not stack.patches.exists(patch_name):
74 raise common.CmdException('%s: no such patch' % patch_name)
75 return patch_name
76 else:
77 if not stack.patchorder.applied:
78 raise common.CmdException(
79 'Cannot refresh top patch, because no patches are applied')
80 return stack.patchorder.applied[-1]
81
82 def list_files(stack, patch_name, args, index, update):
83 """Figure out which files to update."""
84 if index:
85 # --index: Don't update the index.
86 return set()
87 paths = stack.repository.default_iw.changed_files(
88 stack.head.data.tree, args or [])
89 if update:
90 # --update: Restrict update to the paths that were already
91 # part of the patch.
92 paths &= stack.patches.get(patch_name).files()
93 return paths
94
95 def write_tree(stack, paths, temp_index):
96 """Possibly update the index, and then write its tree.
97 @return: The written tree.
98 @rtype: L{Tree<stgit.git.Tree>}"""
99 def go(index):
100 if paths:
101 iw = git.IndexAndWorktree(index, stack.repository.default_worktree)
102 iw.update_index(paths)
103 return index.write_tree()
104 if temp_index:
105 index = stack.repository.temp_index()
106 try:
107 index.read_tree(stack.head)
108 return go(index)
109 finally:
110 index.delete()
111 stack.repository.default_iw.update_index(paths)
112 else:
113 return go(stack.repository.default_index)
114
115 def make_temp_patch(stack, patch_name, paths, temp_index):
116 """Commit index to temp patch, in a complete transaction. If any path
117 limiting is in effect, use a temp index."""
118 tree = write_tree(stack, paths, temp_index)
119 commit = stack.repository.commit(git.CommitData(
120 tree = tree, parents = [stack.head],
121 message = 'Refresh of %s' % patch_name))
122 temp_name = utils.make_patch_name('refresh-temp', stack.patches.exists)
123 trans = transaction.StackTransaction(stack,
124 'refresh (create temporary patch)')
125 trans.patches[temp_name] = commit
126 trans.applied.append(temp_name)
127 return trans.run(stack.repository.default_iw,
128 print_current_patch = False), temp_name
129
130 def absorb_applied(trans, iw, patch_name, temp_name, edit_fun):
131 """Absorb the temp patch (C{temp_name}) into the given patch
132 (C{patch_name}), which must be applied. If the absorption
133 succeeds, call C{edit_fun} on the resulting
134 L{CommitData<stgit.lib.git.CommitData>} before committing it and
135 commit the return value.
136
137 @return: C{True} if we managed to absorb the temp patch, C{False}
138 if we had to leave it for the user to deal with."""
139 temp_absorbed = False
140 try:
141 # Pop any patch on top of the patch we're refreshing.
142 to_pop = trans.applied[trans.applied.index(patch_name)+1:]
143 if len(to_pop) > 1:
144 popped = trans.pop_patches(lambda pn: pn in to_pop)
145 assert not popped # no other patches were popped
146 trans.push_patch(temp_name, iw)
147 assert to_pop.pop() == temp_name
148
149 # Absorb the temp patch.
150 temp_cd = trans.patches[temp_name].data
151 assert trans.patches[patch_name] == temp_cd.parent
152 trans.patches[patch_name] = trans.stack.repository.commit(
153 edit_fun(trans.patches[patch_name].data.set_tree(temp_cd.tree)))
154 popped = trans.delete_patches(lambda pn: pn == temp_name, quiet = True)
155 assert not popped # the temp patch was topmost
156 temp_absorbed = True
157
158 # Push back any patch we were forced to pop earlier.
159 for pn in to_pop:
160 trans.push_patch(pn, iw)
161 except transaction.TransactionHalted:
162 pass
163 return temp_absorbed
164
165 def absorb_unapplied(trans, iw, patch_name, temp_name, edit_fun):
166 """Absorb the temp patch (C{temp_name}) into the given patch
167 (C{patch_name}), which must be unapplied. If the absorption
168 succeeds, call C{edit_fun} on the resulting
169 L{CommitData<stgit.lib.git.CommitData>} before committing it and
170 commit the return value.
171
172 @param iw: Not used.
173 @return: C{True} if we managed to absorb the temp patch, C{False}
174 if we had to leave it for the user to deal with."""
175
176 # Pop the temp patch.
177 popped = trans.pop_patches(lambda pn: pn == temp_name)
178 assert not popped # the temp patch was topmost
179
180 # Try to create the new tree of the refreshed patch. (This is the
181 # same operation as pushing the temp patch onto the patch we're
182 # trying to refresh -- but we don't have a worktree to spill
183 # conflicts to, so if the simple merge doesn't succeed, we have to
184 # give up.)
185 patch_cd = trans.patches[patch_name].data
186 temp_cd = trans.patches[temp_name].data
187 new_tree = trans.stack.repository.simple_merge(
188 base = temp_cd.parent.data.tree,
189 ours = patch_cd.tree, theirs = temp_cd.tree)
190 if new_tree:
191 # It worked. Refresh the patch with the new tree, and delete
192 # the temp patch.
193 trans.patches[patch_name] = trans.stack.repository.commit(
194 edit_fun(patch_cd.set_tree(new_tree)))
195 popped = trans.delete_patches(lambda pn: pn == temp_name, quiet = True)
196 assert not popped # the temp patch was not applied
197 return True
198 else:
199 # Nope, we couldn't create the new tree, so we'll just have to
200 # leave the temp patch for the user.
201 return False
202
203 def absorb(stack, patch_name, temp_name, edit_fun):
204 """Absorb the temp patch into the target patch."""
205 trans = transaction.StackTransaction(stack, 'refresh')
206 iw = stack.repository.default_iw
207 f = { True: absorb_applied, False: absorb_unapplied
208 }[patch_name in trans.applied]
209 if f(trans, iw, patch_name, temp_name, edit_fun):
210 def info_msg(): pass
211 else:
212 def info_msg():
213 out.warn('The new changes did not apply cleanly to %s.'
214 % patch_name, 'They were saved in %s.' % temp_name)
215 r = trans.run(iw)
216 info_msg()
217 return r
218
219 def func(parser, options, args):
220 """Generate a new commit for the current or given patch."""
221
222 # Catch illegal argument combinations.
223 path_limiting = bool(args or options.update)
224 if options.index and path_limiting:
225 raise common.CmdException(
226 'Only full refresh is available with the --index option')
227
228 stack = directory.repository.current_stack
229 patch_name = get_patch(stack, options.patch)
230 paths = list_files(stack, patch_name, args, options.index, options.update)
231
232 # Make sure there are no conflicts in the files we want to
233 # refresh.
234 if stack.repository.default_index.conflicts() & paths:
235 raise common.CmdException(
236 'Cannot refresh -- resolve conflicts first')
237
238 # Commit index to temp patch, and absorb it into the target patch.
239 retval, temp_name = make_temp_patch(
240 stack, patch_name, paths, temp_index = path_limiting)
241 if retval != utils.STGIT_SUCCESS:
242 return retval
243 def edit_fun(cd):
244 cd, failed_diff = edit.auto_edit_patch(
245 stack.repository, cd, msg = options.message, contains_diff = False,
246 author = options.author, committer = lambda p: p,
247 sign_str = options.sign_str)
248 assert not failed_diff
249 if options.edit:
250 cd, failed_diff = edit.interactive_edit_patch(
251 stack.repository, cd, edit_diff = False,
252 diff_flags = [], replacement_diff = None)
253 assert not failed_diff
254 return cd
255 return absorb(stack, patch_name, temp_name, edit_fun)