1 """This module contains utility functions for patch editing."""
5 from stgit
import utils
6 from stgit
.commands
import common
7 from stgit
.config
import config
8 from stgit
.lib
import transaction
9 from stgit
.lib
.git
import Date
, Person
10 from stgit
.lib
.log
import log_stack_state
11 from stgit
.out
import out
13 EDIT_MESSAGE_INSTRUCTIONS
= """# Everything here is editable! You can modify the patch name, author,
14 # date, commit message, and the diff (if --diff was given).
15 # Lines starting with '#' will be ignored, and an empty message
20 def _update_patch_description(repo
, cd
, text
, contains_diff
):
21 """Create commit with updated description.
23 The given :class:`stgit.lib.git.CommitData` is updated with the
24 given description, which may contain author name and time
25 stamp in addition to a new commit message. If ``contains_diff`` is
26 true, it may also contain a replacement diff.
29 - the new :class:`CommitData<stgit.lib.git.CommitData>`
30 - the patch name if given or None otherwise
31 - the diff text if it did not apply or None otherwise
33 (message
, patch_name
, authname
, authemail
, authdate
, diff
) = common
.parse_patch(
37 if authname
is not None:
38 author
= author
.set_name(authname
)
39 if authemail
is not None:
40 author
= author
.set_email(authemail
)
41 if authdate
is not None:
42 author
= author
.set_date(Date(authdate
))
43 cd
= cd
.set_message(message
).set_author(author
)
45 if diff
and not re
.match(br
'---\s*\Z', diff
, re
.MULTILINE
):
46 tree
= repo
.apply(cd
.parent
.data
.tree
, diff
, quiet
=False)
50 cd
= cd
.set_tree(tree
)
51 return cd
, patch_name
, failed_diff
54 def get_patch_description(repo
, cd
, patch_name
, append_diff
, diff_flags
):
55 """Return a description text for the patch.
57 The returned description is suitable for editing and/or reimporting with
58 :func:`_update_patch_description()`.
60 :param cd: the :class:`stgit.lib.git.CommitData` to generate a description of
61 :param append_diff: whether to append the patch diff to the description
62 :type append_diff: bool
63 :param diff_flags: extra parameters to pass to `git diff`
66 commit_encoding
= config
.get('i18n.commitencoding')
69 'Patch: %s' % patch_name
,
70 'From: %s' % cd
.author
.name_email
,
71 'Date: %s' % cd
.author
.date
.isoformat(),
74 EDIT_MESSAGE_INSTRUCTIONS
,
76 ).encode(commit_encoding
)
78 parts
= [desc
.rstrip(), b
'---', b
'']
79 diff
= repo
.diff_tree(
80 cd
.parent
.data
.tree
, cd
.tree
, diff_flags
, binary
=False, full_index
=True
83 diffstat
= repo
.default_iw
.diffstat(diff
).encode(commit_encoding
)
84 parts
.extend([diffstat
, diff
])
85 desc
= b
'\n'.join(parts
)
89 def interactive_edit_patch(repo
, cd
, patch_name
, edit_diff
, diff_flags
):
90 """Edit the patch interactively.
92 If ``edit_diff`` is true, edit the diff as well.
95 - the new :class:`commitdata<stgit.lib.git.commitdata>`
96 - the patch name if given or none otherwise
97 - the diff text if it did not apply or none otherwise
99 patch_desc
= get_patch_description(repo
, cd
, patch_name
, edit_diff
, diff_flags
)
100 cd
, patch_name
, failed_diff
= _update_patch_description(
104 patch_desc
, '.stgit-edit.' + ['txt', 'patch'][bool(edit_diff
)]
110 note_patch_application_failure(patch_desc
)
112 return cd
, patch_name
, failed_diff
115 def auto_edit_patch(repo
, cd
, msg
, author
, trailers
):
116 """Edit the patch noninteractively in a couple of ways:
118 * If ``msg`` is not None, parse it to find a replacement message, and possibly also
119 replacement author and timestamp.
121 * ``author`` is a function that takes the original :class:`stgit.lib.git.Person`
122 value as argument, and returns the new one.
124 * ``trailers`` is a list of trailer strings to append to the message.
127 - the new :class:`commitdata<stgit.lib.git.commitdata>`
128 - the patch name if given or none otherwise
129 - the diff text if it did not apply or none otherwise
133 cd
, _
, failed_diff
= _update_patch_description(
134 repo
, cd
, msg
, contains_diff
=False
136 assert not failed_diff
137 a
= author(cd
.author
)
139 cd
= cd
.set_author(a
)
145 Person
.committer().name
,
146 Person
.committer().email
,
152 def note_patch_application_failure(patch_desc
, reason
='Edited patch did not apply.'):
153 """Call when edit fails. Logs to stderr and saves a patch to filesystem."""
154 fn
= '.stgit-failed.patch'
155 with
open(fn
, 'wb') as f
:
157 out
.error(reason
, 'The patch has been saved to "%s".' % fn
)
169 """Given instructions, performs required the edit.
172 - the result of the transaction
173 - the new patch name, whether changed or not.
175 # Refresh the committer information
176 cd
= cd
.set_committer(None)
178 # Rewrite the StGit patch with the given diff (and any patches on top of
180 iw
= stack
.repository
.default_iw
181 trans
= transaction
.StackTransaction(stack
, 'edit', allow_conflicts
=True)
182 if orig_patchname
in trans
.applied
:
183 popped
= trans
.applied
[trans
.applied
.index(orig_patchname
) + 1 :]
184 popped_extra
= trans
.pop_patches(lambda pn
: pn
in popped
)
185 assert not popped_extra
188 trans
.patches
[orig_patchname
] = stack
.repository
.commit(cd
)
189 if new_patchname
== "":
190 new_patchname
= stack
.patches
.make_name(cd
.message_str
, allow
=orig_patchname
)
191 if new_patchname
is not None and orig_patchname
!= new_patchname
:
192 out
.start('Renaming patch "%s" to "%s"' % (orig_patchname
, new_patchname
))
193 trans
.rename_patch(orig_patchname
, new_patchname
)
195 log_stack_state(stack
, 'rename %s to %s' % (orig_patchname
, new_patchname
))
197 new_patchname
= orig_patchname
203 trans
.push_patch(pn
, iw
, allow_interactive
=True)
204 except transaction
.TransactionHalted
:
207 # Either a complete success, or a conflict during push. But in
208 # either case, we've successfully effected the edits the user
210 return trans
.run(iw
), new_patchname
211 except transaction
.TransactionException
:
212 # Transaction aborted -- we couldn't check out files due to
213 # dirty index/worktree. The edits were not carried out.
214 note_patch_application_failure(
215 get_patch_description(
216 stack
.repository
, cd
, orig_patchname
, edit_diff
, diff_flags
219 return utils
.STGIT_COMMAND_ERROR
, new_patchname