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