From b9b734c9c47d526f7bfd8cabc688dae2d0ba045b Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Thu, 25 Mar 2021 01:49:53 -0400 Subject: [PATCH] fix: export patches not retaining CRLF line endings (#28360) When a patch targets a file using CRLF line endings, they need to be retained in the patch file. Otherwise the patch will fail to apply due to being unable to find surrounding lines with matching whitespace. --- script/lib/git.py | 43 ++++++++++++++++++++++++++++--------------- script/lint.js | 2 +- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/script/lib/git.py b/script/lib/git.py index cc82ec9c1076..0edcd98ea025 100644 --- a/script/lib/git.py +++ b/script/lib/git.py @@ -47,7 +47,7 @@ def get_repo_root(path): def am(repo, patch_data, threeway=False, directory=None, exclude=None, - committer_name=None, committer_email=None): + committer_name=None, committer_email=None, keep_cr=True): args = [] if threeway: args += ['--3way'] @@ -56,6 +56,10 @@ def am(repo, patch_data, threeway=False, directory=None, exclude=None, if exclude is not None: for path_pattern in exclude: args += ['--exclude', path_pattern] + if keep_cr is True: + # Keep the CR of CRLF in case any patches target files with Windows line + # endings. + args += ['--keep-cr'] root_args = ['-C', repo] if committer_name is not None: @@ -230,7 +234,9 @@ def split_patches(patch_data): """Split a concatenated series of patches into N separate patches""" patches = [] patch_start = re.compile('^From [0-9a-f]+ ') - for line in patch_data.splitlines(): + # Keep line endings in case any patches target files with CRLF. + keep_line_endings = True + for line in patch_data.splitlines(keep_line_endings): if patch_start.match(line): patches.append([]) patches[-1].append(line) @@ -246,13 +252,23 @@ def munge_subject_to_filename(subject): def get_file_name(patch): """Return the name of the file to which the patch should be written""" + file_name = None for line in patch: if line.startswith('Patch-Filename: '): - return line[len('Patch-Filename: '):] + file_name = line[len('Patch-Filename: '):] + break # If no patch-filename header, munge the subject. - for line in patch: - if line.startswith('Subject: '): - return munge_subject_to_filename(line[len('Subject: '):]) + if not file_name: + for line in patch: + if line.startswith('Subject: '): + file_name = munge_subject_to_filename(line[len('Subject: '):]) + break + return file_name.rstrip('\n') + + +def join_patch(patch): + """Joins and formats patch contents""" + return ''.join(remove_patch_filename(patch)).rstrip('\n') + '\n' def remove_patch_filename(patch): @@ -294,10 +310,8 @@ def export_patches(repo, out_dir, patch_range=None, dry_run=False): for patch in patches: filename = get_file_name(patch) filepath = posixpath.join(out_dir, filename) - existing_patch = io.open(filepath, 'r', encoding='utf-8').read() - formatted_patch = ( - '\n'.join(remove_patch_filename(patch)).rstrip('\n') + '\n' - ) + existing_patch = io.open(filepath, 'rb').read() + formatted_patch = join_patch(patch) if formatted_patch != existing_patch: patch_count += 1 if patch_count > 0: @@ -322,12 +336,11 @@ def export_patches(repo, out_dir, patch_range=None, dry_run=False): for patch in patches: filename = get_file_name(patch) file_path = posixpath.join(out_dir, filename) - formatted_patch = ( - '\n'.join(remove_patch_filename(patch)).rstrip('\n') + '\n' - ) + formatted_patch = join_patch(patch) + # Write in binary mode to retain mixed line endings on write. with io.open( - file_path, 'w', newline='\n', encoding='utf-8' + file_path, 'wb' ) as f: - f.write(formatted_patch) + f.write(formatted_patch.encode('utf-8')) pl.write(filename + '\n') diff --git a/script/lint.js b/script/lint.js index 70f6a51693f7..a194aacb7b76 100755 --- a/script/lint.js +++ b/script/lint.js @@ -207,7 +207,7 @@ const LINTERS = [{ console.warn(`Patch file '${f}' has no description. Every patch must contain a justification for why the patch exists and the plan for its removal.`); return false; } - const trailingWhitespace = patchText.split('\n').filter(line => line.startsWith('+')).some(line => /\s+$/.test(line)); + const trailingWhitespace = patchText.split(/\r?\n/).some(line => line.startsWith('+') && /\s+$/.test(line)); if (trailingWhitespace) { console.warn(`Patch file '${f}' has trailing whitespace on some lines.`); return false;