1 """This module contains utility functions for patch editing."""
6 from stgit
import utils
7 from stgit
.commands
import common
8 from stgit
.config
import config
9 from stgit
.lib
import transaction
10 from stgit
.lib
.git
import Date
, Person
11 from stgit
.lib
.log
import log_stack_state
12 from stgit
.out
import out
14 EDIT_MESSAGE_INSTRUCTIONS
= """# Everything here is editable! You can modify the patch name, author,
15 # date, commit message, and the diff (if --diff was given).
16 # Lines starting with '#' will be ignored, and an empty message
21 def update_patch_description(repo
, cd
, text
, contains_diff
):
22 """Create commit with updated description.
24 The given :class:`stgit.lib.git.CommitData` is updated with the
25 given description, which may contain author name and time
26 stamp in addition to a new commit message. If ``contains_diff`` is
27 true, it may also contain a replacement diff.
30 - the new :class:`CommitData<stgit.lib.git.CommitData>`
31 - the patch name if given or None otherwise
32 - the diff text if it did not apply or None otherwise
34 (message
, patch_name
, authname
, authemail
, authdate
, diff
) = common
.parse_patch(
38 if authname
is not None:
39 author
= author
.set_name(authname
)
40 if authemail
is not None:
41 author
= author
.set_email(authemail
)
42 if authdate
is not None:
43 author
= author
.set_date(Date(authdate
))
44 cd
= cd
.set_message(message
).set_author(author
)
46 if diff
and not re
.match(br
'---\s*\Z', diff
, re
.MULTILINE
):
47 tree
= repo
.apply(cd
.parent
.data
.tree
, diff
, quiet
=False)
51 cd
= cd
.set_tree(tree
)
52 return cd
, patch_name
, failed_diff
55 def patch_desc(repo
, cd
, patch_name
, append_diff
, diff_flags
, replacement_diff
):
56 """Return a description text for the patch.
58 The returned description is suitable for editing and/or reimporting with
59 :func:`update_patch_description()`.
61 :param cd: the :class:`stgit.lib.git.CommitData` to generate a description of
62 :param append_diff: whether to append the patch diff to the description
63 :type append_diff: bool
64 :param diff_flags: extra parameters to pass to `git diff`
65 :param replacement_diff: diff text to use; or None if it should be computed from cd
66 :type replacement_diff: str or None
69 commit_encoding
= config
.get('i18n.commitencoding')
72 'Patch: %s' % patch_name
,
73 'From: %s' % cd
.author
.name_email
,
74 'Date: %s' % cd
.author
.date
.isoformat(),
77 EDIT_MESSAGE_INSTRUCTIONS
,
79 ).encode(commit_encoding
)
81 parts
= [desc
.rstrip(), b
'---', b
'']
83 parts
.append(replacement_diff
)
85 diff
= repo
.diff_tree(cd
.parent
.data
.tree
, cd
.tree
, diff_flags
)
87 diffstat
= repo
.default_iw
.diffstat(diff
).encode(commit_encoding
)
88 parts
.extend([diffstat
, diff
])
89 desc
= b
'\n'.join(parts
)
93 def interactive_edit_patch(repo
, cd
, patch_name
, edit_diff
, diff_flags
):
94 """Edit the patch interactively.
96 If ``edit_diff`` is true, edit the diff as well. If ``replacement_diff`` is not
97 None, it contains a diff to edit instead of the patch's real diff.
100 - the new :class:`commitdata<stgit.lib.git.commitdata>`
101 - the patch name if given or none otherwise
102 - the diff text if it did not apply or none otherwise
104 cd
, patch_name
, failed_diff
= update_patch_description(
109 repo
, cd
, patch_name
, edit_diff
, diff_flags
, replacement_diff
=None
111 '.stgit-edit.' + ['txt', 'patch'][bool(edit_diff
)],
116 # If we couldn't apply the patch, fail without even trying to
117 # effect any of the changes.
119 return failed(repo
, cd
, patch_name
, edit_diff
, diff_flags
, failed_diff
)
121 return cd
, patch_name
, failed_diff
124 def auto_edit_patch(repo
, cd
, msg
, author
, sign_str
):
125 """Edit the patch noninteractively in a couple of ways:
127 * If ``msg`` is not None, parse it to find a replacement message, and possibly also
128 replacement author and timestamp.
130 * ``author`` is a function that takes the original :class:`stgit.lib.git.Person`
131 value as argument, and returns the new one.
133 * ``sign_str, if not None, is a trailer string to append to the message.
136 - the new :class:`commitdata<stgit.lib.git.commitdata>`
137 - the patch name if given or none otherwise
138 - the diff text if it did not apply or none otherwise
142 cd
, _
, failed_diff
= update_patch_description(
143 repo
, cd
, msg
, contains_diff
=False
145 assert not failed_diff
146 a
= author(cd
.author
)
148 cd
= cd
.set_author(a
)
149 if sign_str
is not None:
154 Person
.committer().name
,
155 Person
.committer().email
,
168 reason
='Edited patch did not apply.',
170 """Call when edit fails. Logs to stderr and saves a patch to filesystem."""
171 fn
= '.stgit-failed.patch'
172 with io
.open(fn
, 'wb') as f
:
180 replacement_diff
=replacement_diff
,
183 out
.error(reason
, 'The patch has been saved to "%s".' % fn
)
184 return utils
.STGIT_COMMAND_ERROR
197 """Given instructions, performs required the edit.
200 - the result of the transaction
201 - the new patch name, whether changed or not.
203 # Refresh the committer information
204 cd
= cd
.set_committer(None)
206 # Rewrite the StGit patch with the given diff (and any patches on top of
208 iw
= stack
.repository
.default_iw
209 trans
= transaction
.StackTransaction(stack
, 'edit', allow_conflicts
=True)
210 if orig_patchname
in trans
.applied
:
211 popped
= trans
.applied
[trans
.applied
.index(orig_patchname
) + 1 :]
212 popped_extra
= trans
.pop_patches(lambda pn
: pn
in popped
)
213 assert not popped_extra
216 trans
.patches
[orig_patchname
] = stack
.repository
.commit(cd
)
217 if new_patchname
== "":
218 new_patchname
= stack
.patches
.make_name(cd
.message_str
, allow
=orig_patchname
)
219 if new_patchname
is not None and orig_patchname
!= new_patchname
:
220 out
.start('Renaming patch "%s" to "%s"' % (orig_patchname
, new_patchname
))
221 trans
.rename_patch(orig_patchname
, new_patchname
)
223 log_stack_state(stack
, 'rename %s to %s' % (orig_patchname
, new_patchname
))
225 new_patchname
= orig_patchname
231 trans
.push_patch(pn
, iw
, allow_interactive
=True)
232 except transaction
.TransactionHalted
:
235 # Either a complete success, or a conflict during push. But in
236 # either case, we've successfully effected the edits the user
238 return trans
.run(iw
), new_patchname
239 except transaction
.TransactionException
:
240 # Transaction aborted -- we couldn't check out files due to
241 # dirty index/worktree. The edits were not carried out.
243 failed(stack
, cd
, new_patchname
, edit_diff
, diff_flags
, replacement_diff
),