diff --git a/apply_pr/fabfile.py b/apply_pr/fabfile.py index 7019bfd..750314e 100644 --- a/apply_pr/fabfile.py +++ b/apply_pr/fabfile.py @@ -34,7 +34,7 @@ from collections import OrderedDict -from apply_pr.patch_utils import filter_patch_paths +from apply_pr.patch_utils import append_pr_url, filter_patch_paths from apply_pr.exceptions import ApplyError from apply_pr.console import ( as_text, console_message, log_error, print_message as _print_message, @@ -483,7 +483,8 @@ def find_from_to_commits(pr_number, owner='gisce', repository='erp'): @task def export_patches_from_git( - from_commit, to_commit, pr_number, skip_directory_pattern=None + from_commit, to_commit, pr_number, owner='gisce', repository='erp', + skip_directory_pattern=None ): logger.info('Exporting patches from %s to %s' % (from_commit, to_commit)) deploy_path = "deploy/patches/{}".format(pr_number) @@ -497,16 +498,21 @@ def export_patches_from_git( local("git format-patch -o deploy/patches/%s %s..%s" % ( pr_number, from_commit, to_commit) ) - if skip_directory_pattern: - for patch_path in glob.glob(os.path.join(deploy_path, '*.patch')): - with io.open(patch_path, 'r', encoding='utf-8') as patch_file: - content = patch_file.read() + pr_url = 'https://github.com/{}/{}/pull/{}'.format( + owner, repository, pr_number + ) + for patch_path in glob.glob(os.path.join(deploy_path, '*.patch')): + with io.open(patch_path, 'r', encoding='utf-8') as patch_file: + content = patch_file.read() + if skip_directory_pattern: filtered = filter_patch_paths(content, skip_directory_pattern) if 'diff --git ' not in filtered: os.unlink(patch_path) continue - with io.open(patch_path, 'w', encoding='utf-8') as patch_file: - patch_file.write(filtered) + content = filtered + content = append_pr_url(content, pr_url) + with io.open(patch_path, 'w', encoding='utf-8') as patch_file: + patch_file.write(content) @task @@ -642,6 +648,12 @@ def export_patches_from_github( if 'diff --git ' not in content: logger.info('Skipping empty patch %s after path filtering.', filename) continue + content = append_pr_url( + content, + 'https://github.com/{}/{}/pull/{}'.format( + owner, repository, pr_number + ), + ) with open(os.path.join(patch_folder, filename), 'wb') as patch: logger.info('Exporting patch %s.' % filename) patch.write(content.encode('utf-8')) diff --git a/apply_pr/patch_utils.py b/apply_pr/patch_utils.py index d924271..e997bcb 100644 --- a/apply_pr/patch_utils.py +++ b/apply_pr/patch_utils.py @@ -41,3 +41,15 @@ def filter_patch_paths(content, skip_directory_pattern=None): if not any(pattern.search(path) for path in _diff_paths(header)): kept.append(section) return ''.join(kept) + + +def append_pr_url(content, pr_url): + """Append the pull request URL to the commit message in a mail patch.""" + diff_start = content.find('\ndiff --git ') + if diff_start == -1: + return content + separator = content.find('\n---\n') + if separator == -1 or separator > diff_start: + return content + message = content[:separator].rstrip('\n') + return '{}\n\n{}{}'.format(message, pr_url, content[separator:]) diff --git a/tests/test_patch_message.py b/tests/test_patch_message.py new file mode 100644 index 0000000..19125eb --- /dev/null +++ b/tests/test_patch_message.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import os +import shutil +import subprocess +import tempfile +import unittest + +from apply_pr.patch_utils import append_pr_url + + +PATCH = """From abc Mon Sep 17 00:00:00 2001 +From: Developer +Date: Tue, 6 Oct 2026 12:00:00 +0200 +Subject: [PATCH] Preserve the original title + +First paragraph of the body. + +Second paragraph of the commit message. +--- + file.txt | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/file.txt b/file.txt +index e69de29..7898192 100644 +--- a/file.txt ++++ b/file.txt +@@ -0,0 +1 @@ ++content +-- +2.39.5 +""" + + +class AppendPrUrlTest(unittest.TestCase): + def test_appends_url_after_multiline_message(self): + url = 'https://github.com/gisce/erp/pull/12345' + + result = append_pr_url(PATCH, url) + + self.assertIn( + 'Second paragraph of the commit message.\n\n{}\n---\n' + ' file.txt | 1 +'.format(url), + result, + ) + self.assertEqual(result.count(url), 1) + self.assertIn('Subject: [PATCH] Preserve the original title', result) + + def test_git_am_creates_commit_with_original_message_and_url(self): + checkout = tempfile.mkdtemp(prefix='apply-pr-message-test-') + self.addCleanup(shutil.rmtree, checkout) + subprocess.check_call(['git', 'init', '-q'], cwd=checkout) + subprocess.check_call( + ['git', 'config', 'user.name', 'Sastre Test'], cwd=checkout + ) + subprocess.check_call( + ['git', 'config', 'user.email', 'sastre@example.net'], cwd=checkout + ) + open(os.path.join(checkout, 'file.txt'), 'w').close() + subprocess.check_call(['git', 'add', 'file.txt'], cwd=checkout) + subprocess.check_call( + ['git', 'commit', '-q', '-m', 'Initial commit'], cwd=checkout + ) + url = 'https://github.com/gisce/erp/pull/12345' + patch_path = os.path.join(checkout, 'change.patch') + with open(patch_path, 'w') as patch_file: + patch_file.write(append_pr_url(PATCH, url)) + + subprocess.check_call(['git', 'am', patch_path], cwd=checkout) + + message = subprocess.check_output( + ['git', 'log', '-1', '--format=%B'], cwd=checkout + ).decode('utf-8').strip() + self.assertEqual( + message, + 'Preserve the original title\n\n' + 'First paragraph of the body.\n\n' + 'Second paragraph of the commit message.\n\n{}'.format(url), + ) + + def test_leaves_a_plain_diff_unchanged(self): + diff = 'diff --git a/file.txt b/file.txt\n' + + self.assertEqual( + append_pr_url(diff, 'https://github.com/gisce/erp/pull/12345'), + diff, + ) + + +if __name__ == '__main__': + unittest.main()