addons/shared/patch/stringify.tsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {Hunk, ParsedDiff} from './types';
b69ab319import {DiffType} from './types';
b69ab3110
b69ab3111/**
b69ab3112 * Convert a parsed diff back to git diff format string.
b69ab3113 *
b69ab3114 * This is the reverse operation of parsePatch in parse.ts.
b69ab3115 */
b69ab3116export function stringifyPatch(parsedDiffs: ParsedDiff[]): string {
b69ab3117 return parsedDiffs.map(stringifyDiff).join('');
b69ab3118}
b69ab3119
b69ab3120function stringifyDiff(diff: ParsedDiff): string {
b69ab3121 const parts: string[] = [];
b69ab3122
b69ab3123 // diff header
b69ab3124 if (diff.oldFileName && diff.newFileName) {
b69ab3125 parts.push(`diff --git ${diff.oldFileName} ${diff.newFileName}\n`);
b69ab3126 }
b69ab3127
b69ab3128 // extended header lines
b69ab3129 if (diff.type === DiffType.Renamed) {
b69ab3130 const oldName = diff.oldFileName?.replace(/^a\//, '') ?? '';
b69ab3131 const newName = diff.newFileName?.replace(/^b\//, '') ?? '';
b69ab3132 parts.push(`rename from ${oldName}\n`);
b69ab3133 parts.push(`rename to ${newName}\n`);
b69ab3134 }
b69ab3135
b69ab3136 if (diff.type === DiffType.Copied) {
b69ab3137 const oldName = diff.oldFileName?.replace(/^a\//, '') ?? '';
b69ab3138 const newName = diff.newFileName?.replace(/^b\//, '') ?? '';
b69ab3139 parts.push(`copy from ${oldName}\n`);
b69ab3140 parts.push(`copy to ${newName}\n`);
b69ab3141 }
b69ab3142
b69ab3143 if (diff.oldMode && diff.newMode && diff.oldMode !== diff.newMode) {
b69ab3144 parts.push(`old mode ${diff.oldMode}\n`);
b69ab3145 parts.push(`new mode ${diff.newMode}\n`);
b69ab3146 }
b69ab3147
b69ab3148 if (diff.type === DiffType.Added && diff.newMode) {
b69ab3149 parts.push(`new file mode ${diff.newMode}\n`);
b69ab3150 }
b69ab3151
b69ab3152 if (diff.type === DiffType.Removed && diff.newMode) {
b69ab3153 parts.push(`deleted file mode ${diff.newMode}\n`);
b69ab3154 }
b69ab3155
b69ab3156 // file headers
b69ab3157 if (diff.hunks.length > 0) {
b69ab3158 const oldFile = diff.type === DiffType.Added ? '/dev/null' : (diff.oldFileName ?? '/dev/null');
b69ab3159 const newFile =
b69ab3160 diff.type === DiffType.Removed ? '/dev/null' : (diff.newFileName ?? '/dev/null');
b69ab3161 parts.push(`--- ${oldFile}\n`);
b69ab3162 parts.push(`+++ ${newFile}\n`);
b69ab3163 }
b69ab3164
b69ab3165 // hunks
b69ab3166 diff.hunks.forEach(hunk => {
b69ab3167 parts.push(stringifyHunk(hunk));
b69ab3168 });
b69ab3169
b69ab3170 return parts.join('');
b69ab3171}
b69ab3172
b69ab3173function stringifyHunk(hunk: Hunk): string {
b69ab3174 const parts: string[] = [];
b69ab3175
b69ab3176 // Handle the Unified Diff Format quirk:
b69ab3177 // If the hunk size is 0, the start line is one higher than stored
b69ab3178 let oldStart = hunk.oldStart;
b69ab3179 let newStart = hunk.newStart;
b69ab3180
b69ab3181 if (hunk.oldLines === 0) {
b69ab3182 oldStart -= 1;
b69ab3183 }
b69ab3184 if (hunk.newLines === 0) {
b69ab3185 newStart -= 1;
b69ab3186 }
b69ab3187
b69ab3188 // hunk header - always include line count
b69ab3189 const oldRange = `${oldStart},${hunk.oldLines}`;
b69ab3190 const newRange = `${newStart},${hunk.newLines}`;
b69ab3191 parts.push(`@@ -${oldRange} +${newRange} @@\n`);
b69ab3192
b69ab3193 // hunk lines
b69ab3194 hunk.lines.forEach((line, index) => {
b69ab3195 const delimiter = hunk.linedelimiters[index] ?? '\n';
b69ab3196 parts.push(line + delimiter);
b69ab3197 });
b69ab3198
b69ab3199 return parts.join('');
b69ab31100}