| b69ab31 | | | 1 | /** |
| b69ab31 | | | 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
| b69ab31 | | | 3 | * |
| b69ab31 | | | 4 | * This source code is licensed under the MIT license found in the |
| b69ab31 | | | 5 | * LICENSE file in the root directory of this source tree. |
| b69ab31 | | | 6 | */ |
| b69ab31 | | | 7 | |
| b69ab31 | | | 8 | import type {Map as ImMap} from 'immutable'; |
| b69ab31 | | | 9 | import type {AbsorbEdit, AbsorbEditId} from '../absorb'; |
| b69ab31 | | | 10 | import type {FileRev} from '../fileStackState'; |
| b69ab31 | | | 11 | |
| b69ab31 | | | 12 | import {splitLines} from 'shared/diff'; |
| b69ab31 | | | 13 | import { |
| b69ab31 | | | 14 | analyseFileStack, |
| b69ab31 | | | 15 | applyFileStackEditsWithAbsorbId, |
| b69ab31 | | | 16 | calculateAbsorbEditsForFileStack, |
| b69ab31 | | | 17 | embedAbsorbId, |
| b69ab31 | | | 18 | extractRevAbsorbId, |
| b69ab31 | | | 19 | revWithAbsorb, |
| b69ab31 | | | 20 | } from '../absorb'; |
| b69ab31 | | | 21 | import {FileStackState} from '../fileStackState'; |
| b69ab31 | | | 22 | |
| b69ab31 | | | 23 | // See also [test-fb-ext-absorb-filefixupstate.py](https://github.com/facebook/sapling/blob/eb3d35d/eden/scm/tests/test-fb-ext-absorb-filefixupstate.py#L75) |
| b69ab31 | | | 24 | describe('analyseFileStack', () => { |
| b69ab31 | | | 25 | it('edits an empty file', () => { |
| b69ab31 | | | 26 | // Public: empty. |
| b69ab31 | | | 27 | const stack = createStack(['']); |
| b69ab31 | | | 28 | // No Selectedion - cannot edit the public (rev 0) content. |
| b69ab31 | | | 29 | expect(analyseFile(stack, 'a')).toMatchInlineSnapshot(`"0:0=>'a': Rev 0+ Selected null"`); |
| b69ab31 | | | 30 | }); |
| b69ab31 | | | 31 | |
| b69ab31 | | | 32 | it('edits 2 lines by one insertion', () => { |
| b69ab31 | | | 33 | // Public: empty. Rev 1: "1\n1\n". |
| b69ab31 | | | 34 | const stack = createStack(['', '1↵1↵']); |
| b69ab31 | | | 35 | // Delete the chunk. |
| b69ab31 | | | 36 | expect(analyseFile(stack, '')).toMatchInlineSnapshot(`"0:2=>'': Rev 1+ Selected 1"`); |
| b69ab31 | | | 37 | // Replace to 1 line. |
| b69ab31 | | | 38 | expect(analyseFile(stack, '2')).toMatchInlineSnapshot(`"0:2=>'2': Rev 1+ Selected 1"`); |
| b69ab31 | | | 39 | // Replace to 2 lines. |
| b69ab31 | | | 40 | expect(analyseFile(stack, '22')).toMatchInlineSnapshot(`"0:2=>'22': Rev 1+ Selected 1"`); |
| b69ab31 | | | 41 | // Replace to 3 lines. |
| b69ab31 | | | 42 | expect(analyseFile(stack, '222')).toMatchInlineSnapshot(`"0:2=>'222': Rev 1+ Selected 1"`); |
| b69ab31 | | | 43 | }); |
| b69ab31 | | | 44 | |
| b69ab31 | | | 45 | it('edits 3 lines by 3 insertions', () => { |
| b69ab31 | | | 46 | // Public: empty. Rev 1: "1". Rev 2: "1", "2". Rev 3: "1", "2", "3". |
| b69ab31 | | | 47 | const stack = createStack(['', '1↵', '1↵2↵', '1↵2↵3↵']); |
| b69ab31 | | | 48 | // No change. |
| b69ab31 | | | 49 | expect(analyseFile(stack, '1↵2↵3↵')).toMatchInlineSnapshot(`""`); |
| b69ab31 | | | 50 | // Replave the last line. |
| b69ab31 | | | 51 | expect(analyseFile(stack, '1↵2↵c↵')).toMatchInlineSnapshot(`"2:3=>'c': Rev 3+ Selected 3"`); |
| b69ab31 | | | 52 | // Replave the 2nd line. |
| b69ab31 | | | 53 | expect(analyseFile(stack, '1↵b↵3↵')).toMatchInlineSnapshot(`"1:2=>'b': Rev 2+ Selected 2"`); |
| b69ab31 | | | 54 | // Replace the last 2 lines. |
| b69ab31 | | | 55 | expect(analyseFile(stack, '1↵b↵c↵')).toMatchInlineSnapshot(` |
| b69ab31 | | | 56 | "1:2=>'b': Rev 2+ Selected 2 |
| b69ab31 | | | 57 | 2:3=>'c': Rev 3+ Selected 3" |
| b69ab31 | | | 58 | `); |
| b69ab31 | | | 59 | // Replace the first line. |
| b69ab31 | | | 60 | expect(analyseFile(stack, 'a↵2↵3↵')).toMatchInlineSnapshot(`"0:1=>'a': Rev 1+ Selected 1"`); |
| b69ab31 | | | 61 | // Replace the first and the last lines. |
| b69ab31 | | | 62 | expect(analyseFile(stack, 'a↵2↵c↵')).toMatchInlineSnapshot(` |
| b69ab31 | | | 63 | "0:1=>'a': Rev 1+ Selected 1 |
| b69ab31 | | | 64 | 2:3=>'c': Rev 3+ Selected 3" |
| b69ab31 | | | 65 | `); |
| b69ab31 | | | 66 | // Replace the first 2 lines. |
| b69ab31 | | | 67 | expect(analyseFile(stack, 'a↵b↵3↵')).toMatchInlineSnapshot(` |
| b69ab31 | | | 68 | "0:1=>'a': Rev 1+ Selected 1 |
| b69ab31 | | | 69 | 1:2=>'b': Rev 2+ Selected 2" |
| b69ab31 | | | 70 | `); |
| b69ab31 | | | 71 | // Replace all 3 lines. |
| b69ab31 | | | 72 | expect(analyseFile(stack, 'a↵b↵c↵')).toMatchInlineSnapshot(` |
| b69ab31 | | | 73 | "0:1=>'a': Rev 1+ Selected 1 |
| b69ab31 | | | 74 | 1:2=>'b': Rev 2+ Selected 2 |
| b69ab31 | | | 75 | 2:3=>'c': Rev 3+ Selected 3" |
| b69ab31 | | | 76 | `); |
| b69ab31 | | | 77 | // Non 1:1 line mapping. |
| b69ab31 | | | 78 | expect(analyseFile(stack, 'a↵b↵c↵d↵')).toMatchInlineSnapshot( |
| b69ab31 | | | 79 | `"0:3=>'abcd': Rev 3+ Selected null"`, |
| b69ab31 | | | 80 | ); |
| b69ab31 | | | 81 | expect(analyseFile(stack, 'a↵b↵')).toMatchInlineSnapshot(`"0:3=>'ab': Rev 3+ Selected null"`); |
| b69ab31 | | | 82 | // Deletion. |
| b69ab31 | | | 83 | expect(analyseFile(stack, '')).toMatchInlineSnapshot(` |
| b69ab31 | | | 84 | "0:1=>'': Rev 1+ Selected 1 |
| b69ab31 | | | 85 | 1:2=>'': Rev 2+ Selected 2 |
| b69ab31 | | | 86 | 2:3=>'': Rev 3+ Selected 3" |
| b69ab31 | | | 87 | `); |
| b69ab31 | | | 88 | expect(analyseFile(stack, '1↵')).toMatchInlineSnapshot(` |
| b69ab31 | | | 89 | "1:2=>'': Rev 2+ Selected 2 |
| b69ab31 | | | 90 | 2:3=>'': Rev 3+ Selected 3" |
| b69ab31 | | | 91 | `); |
| b69ab31 | | | 92 | expect(analyseFile(stack, '2↵')).toMatchInlineSnapshot(` |
| b69ab31 | | | 93 | "0:1=>'': Rev 1+ Selected 1 |
| b69ab31 | | | 94 | 2:3=>'': Rev 3+ Selected 3" |
| b69ab31 | | | 95 | `); |
| b69ab31 | | | 96 | expect(analyseFile(stack, '3↵')).toMatchInlineSnapshot(` |
| b69ab31 | | | 97 | "0:1=>'': Rev 1+ Selected 1 |
| b69ab31 | | | 98 | 1:2=>'': Rev 2+ Selected 2" |
| b69ab31 | | | 99 | `); |
| b69ab31 | | | 100 | expect(analyseFile(stack, '1↵3↵')).toMatchInlineSnapshot(`"1:2=>'': Rev 2+ Selected 2"`); |
| b69ab31 | | | 101 | // Replace the 2nd line with multiple lines. |
| b69ab31 | | | 102 | expect(analyseFile(stack, '1↵b↵b↵3↵')).toMatchInlineSnapshot(`"1:2=>'bb': Rev 2+ Selected 2"`); |
| b69ab31 | | | 103 | // "Confusing" replaces. |
| b69ab31 | | | 104 | expect(analyseFile(stack, '1↵b↵b↵b↵')).toMatchInlineSnapshot( |
| b69ab31 | | | 105 | `"1:3=>'bbb': Rev 3+ Selected null"`, |
| b69ab31 | | | 106 | ); |
| b69ab31 | | | 107 | expect(analyseFile(stack, 'b↵b↵b↵3↵')).toMatchInlineSnapshot( |
| b69ab31 | | | 108 | `"0:2=>'bbb': Rev 2+ Selected null"`, |
| b69ab31 | | | 109 | ); |
| b69ab31 | | | 110 | expect(analyseFile(stack, '1↵b↵')).toMatchInlineSnapshot(`"1:3=>'b': Rev 3+ Selected null"`); |
| b69ab31 | | | 111 | expect(analyseFile(stack, 'b↵3↵')).toMatchInlineSnapshot(`"0:2=>'b': Rev 2+ Selected null"`); |
| b69ab31 | | | 112 | // Insertion at the beginning and the end. |
| b69ab31 | | | 113 | expect(analyseFile(stack, '1↵2↵3↵c↵')).toMatchInlineSnapshot(`"3:3=>'c': Rev 3+ Selected 3"`); |
| b69ab31 | | | 114 | expect(analyseFile(stack, 'a↵1↵2↵3↵')).toMatchInlineSnapshot(`"0:0=>'a': Rev 1+ Selected 1"`); |
| b69ab31 | | | 115 | // "Confusing" insertions. |
| b69ab31 | | | 116 | expect(analyseFile(stack, '1↵a↵2↵3↵')).toMatchInlineSnapshot( |
| b69ab31 | | | 117 | `"1:1=>'a': Rev 2+ Selected null"`, |
| b69ab31 | | | 118 | ); |
| b69ab31 | | | 119 | expect(analyseFile(stack, '1↵2↵b↵3↵')).toMatchInlineSnapshot( |
| b69ab31 | | | 120 | `"2:2=>'b': Rev 3+ Selected null"`, |
| b69ab31 | | | 121 | ); |
| b69ab31 | | | 122 | }); |
| b69ab31 | | | 123 | |
| b69ab31 | | | 124 | it('does not edit the public commit', () => { |
| b69ab31 | | | 125 | const stack = createStack(['1↵3↵5↵7↵', '0↵1↵2↵5↵6↵7↵8↵']); |
| b69ab31 | | | 126 | // Nothing changed. |
| b69ab31 | | | 127 | expect(analyseFile(stack, '0↵1↵2↵5↵6↵7↵8↵')).toMatchInlineSnapshot(`""`); |
| b69ab31 | | | 128 | // No Selectedion. "1" (from public) is changed to "a". |
| b69ab31 | | | 129 | expect(analyseFile(stack, '0↵a↵2↵5↵6↵7↵8↵')).toMatchInlineSnapshot( |
| b69ab31 | | | 130 | `"1:2=>'a': Rev 0+ Selected null"`, |
| b69ab31 | | | 131 | ); |
| b69ab31 | | | 132 | // Whole block changed. NOTE: This is different from the Python behavior. |
| b69ab31 | | | 133 | expect(analyseFile(stack, 'a↵b↵c↵d↵e↵f↵g↵')).toMatchInlineSnapshot( |
| b69ab31 | | | 134 | `"0:7=>'abcdefg': Rev 1+ Selected 1"`, |
| b69ab31 | | | 135 | ); |
| b69ab31 | | | 136 | expect(analyseFile(stack, 'a↵b↵c↵d↵e↵f↵')).toMatchInlineSnapshot( |
| b69ab31 | | | 137 | `"0:7=>'abcdef': Rev 1+ Selected 1"`, |
| b69ab31 | | | 138 | ); |
| b69ab31 | | | 139 | expect(analyseFile(stack, '')).toMatchInlineSnapshot(`"0:7=>'': Rev 1+ Selected 1"`); |
| b69ab31 | | | 140 | // Insert 2 lines. |
| b69ab31 | | | 141 | expect(analyseFile(stack, '0↵1↵2↵3↵4↵5↵6↵7↵8↵9↵')).toMatchInlineSnapshot(` |
| b69ab31 | | | 142 | "3:3=>'34': Rev 1+ Selected 1 |
| b69ab31 | | | 143 | 7:7=>'9': Rev 1+ Selected 1" |
| b69ab31 | | | 144 | `); |
| b69ab31 | | | 145 | }); |
| b69ab31 | | | 146 | |
| b69ab31 | | | 147 | describe('applyFileStackEdits', () => { |
| b69ab31 | | | 148 | it('edits 3 lines by 3 insertions', () => { |
| b69ab31 | | | 149 | // Replace ['1','2','3'] to ['a','b','c'], 1->a, 2->b, 3->c. |
| b69ab31 | | | 150 | const fullStack = createStack(['', '1↵', '1↵2↵', '1↵2↵3↵', 'a↵b↵c↵']); |
| b69ab31 | | | 151 | const edits = calculateAbsorbEditsForFileStack(fullStack)[1]; |
| b69ab31 | | | 152 | const stack = fullStack.truncate((fullStack.revLength - 1) as FileRev); |
| b69ab31 | | | 153 | expect(applyEdits(stack, edits.values())).toMatchInlineSnapshot(`" a↵ a↵b↵ a↵b↵c↵"`); |
| b69ab31 | | | 154 | // Tweak the `selectedRev` so the 1->a, 2->b changes happen at the last rev. |
| b69ab31 | | | 155 | const edits2 = edits.map(c => c.set('selectedRev', 3 as FileRev)); |
| b69ab31 | | | 156 | expect(applyEdits(stack, edits2.values())).toMatchInlineSnapshot(`" 1↵ 1↵2↵ a↵b↵c↵"`); |
| b69ab31 | | | 157 | // Drop the "2->b" change by setting selectedRev to `null`. |
| b69ab31 | | | 158 | const edits3 = edits.map(c => (c.oldStart === 1 ? c.set('selectedRev', null) : c)); |
| b69ab31 | | | 159 | expect(applyEdits(stack, edits3.values())).toMatchInlineSnapshot(`" a↵ a↵2↵ a↵2↵c↵ a↵b↵c↵"`); |
| b69ab31 | | | 160 | }); |
| b69ab31 | | | 161 | |
| b69ab31 | | | 162 | it('edits do not need to be 1:1 line mapping', () => { |
| b69ab31 | | | 163 | // Replace ['111','2','333'] to ['aaaa','2','cc']. 111->aaaa. 333->cc. |
| b69ab31 | | | 164 | const fullStack = createStack(['', '2↵', '1↵1↵1↵2↵3↵3↵3↵', 'a↵a↵a↵a↵2↵c↵c↵']); |
| b69ab31 | | | 165 | const edits = calculateAbsorbEditsForFileStack(fullStack)[1]; |
| b69ab31 | | | 166 | const stack = fullStack.truncate((fullStack.revLength - 1) as FileRev); |
| b69ab31 | | | 167 | expect(applyEdits(stack, edits.values())).toMatchInlineSnapshot(`" 2↵ a↵a↵a↵a↵2↵c↵c↵"`); |
| b69ab31 | | | 168 | // Drop the "1->aaa" change by setting selectedRev to `null`. |
| b69ab31 | | | 169 | const edits2 = edits.map(c => (c.oldStart === 0 ? c.set('selectedRev', null) : c)); |
| b69ab31 | | | 170 | expect(applyEdits(stack, edits2.values())).toMatchInlineSnapshot( |
| b69ab31 | | | 171 | `" 2↵ 1↵1↵1↵2↵c↵c↵ a↵a↵a↵a↵2↵c↵c↵"`, |
| b69ab31 | | | 172 | ); |
| b69ab31 | | | 173 | }); |
| b69ab31 | | | 174 | }); |
| b69ab31 | | | 175 | |
| b69ab31 | | | 176 | describe('absorbId', () => { |
| b69ab31 | | | 177 | it('can be embedded into rev, and extracted out', () => { |
| b69ab31 | | | 178 | const plainRev = 567; |
| b69ab31 | | | 179 | const absorbEditId = 890; |
| b69ab31 | | | 180 | const rev = embedAbsorbId(plainRev as FileRev, absorbEditId); |
| b69ab31 | | | 181 | expect(extractRevAbsorbId(rev)).toEqual([plainRev, absorbEditId]); |
| b69ab31 | | | 182 | }); |
| b69ab31 | | | 183 | }); |
| b69ab31 | | | 184 | |
| b69ab31 | | | 185 | describe('calculateAbsorbEditsForFileStack', () => { |
| b69ab31 | | | 186 | it('analyses a stack', () => { |
| b69ab31 | | | 187 | const stack = createStack([ |
| b69ab31 | | | 188 | 'p↵u↵b↵', |
| b69ab31 | | | 189 | 'p↵u↵b↵1↵2↵3↵4↵', |
| b69ab31 | | | 190 | 'p↵u↵b↵2↵3↵4↵5↵6↵', |
| b69ab31 | | | 191 | 'p↵U↵b↵x↵3↵4↵6↵y↵', |
| b69ab31 | | | 192 | ]); |
| b69ab31 | | | 193 | const [analysedStack, absorbMap] = calculateAbsorbEditsForFileStack(stack); |
| b69ab31 | | | 194 | expect(describeAbsorbIdChunkMap(absorbMap)).toMatchInlineSnapshot(` |
| b69ab31 | | | 195 | [ |
| b69ab31 | | | 196 | "0: -u↵ +U↵ Introduced=0", |
| b69ab31 | | | 197 | "1: -2↵ +x↵ Selected=1 Introduced=1", |
| b69ab31 | | | 198 | "2: -5↵ Selected=2 Introduced=2", |
| b69ab31 | | | 199 | "3: +y↵ Selected=2 Introduced=2", |
| b69ab31 | | | 200 | ] |
| b69ab31 | | | 201 | `); |
| b69ab31 | | | 202 | const show = (rev: number) => compactText(analysedStack.getRev(rev as FileRev)); |
| b69ab31 | | | 203 | // Rev 1 original. |
| b69ab31 | | | 204 | expect(show(1)).toMatchInlineSnapshot(`"p↵u↵b↵1↵2↵3↵4↵"`); |
| b69ab31 | | | 205 | // Rev 1.99 is Rev 1 with the absorb "-2 -x" chunk applied. |
| b69ab31 | | | 206 | expect(show(1.99)).toMatchInlineSnapshot(`"p↵u↵b↵1↵x↵3↵4↵"`); |
| b69ab31 | | | 207 | // Rev 2 original. |
| b69ab31 | | | 208 | expect(show(2)).toMatchInlineSnapshot(`"p↵u↵b↵x↵3↵4↵5↵6↵"`); |
| b69ab31 | | | 209 | // Rev 2.99 is Rev 2 with the absorb "-5 +y" applied. |
| b69ab31 | | | 210 | const rev299 = revWithAbsorb(2 as FileRev); |
| b69ab31 | | | 211 | expect(show(rev299)).toMatchInlineSnapshot(`"p↵u↵b↵x↵3↵4↵6↵y↵"`); |
| b69ab31 | | | 212 | // Rev 3 "wdir()" is dropped - no changes from 2.99. |
| b69ab31 | | | 213 | expect(show(3)).toMatchInlineSnapshot(`"p↵u↵b↵x↵3↵4↵6↵y↵"`); |
| b69ab31 | | | 214 | // Rev 3.99 includes changes left in "wdir()": "pub" -> "pUb". |
| b69ab31 | | | 215 | // This edit changes the "public" portion so it wasn't absorbed by default. |
| b69ab31 | | | 216 | expect(show(3.99)).toMatchInlineSnapshot(`"p↵U↵b↵x↵3↵4↵6↵y↵"`); |
| b69ab31 | | | 217 | expect(analysedStack.convertToLineLog().code.describeHumanReadableInstructions()) |
| b69ab31 | | | 218 | .toMatchInlineSnapshot(` |
| b69ab31 | | | 219 | [ |
| b69ab31 | | | 220 | "0: J 1", |
| b69ab31 | | | 221 | "1: JL 0 5", |
| b69ab31 | | | 222 | "2: LINE 0 "p"", |
| b69ab31 | | | 223 | "3: J 30", |
| b69ab31 | | | 224 | "4: LINE 0 "b"", |
| b69ab31 | | | 225 | "5: J 6", |
| b69ab31 | | | 226 | "6: JL 1 11", |
| b69ab31 | | | 227 | "7: J 16", |
| b69ab31 | | | 228 | "8: J 25", |
| b69ab31 | | | 229 | "9: LINE 1 "3"", |
| b69ab31 | | | 230 | "10: LINE 1 "4"", |
| b69ab31 | | | 231 | "11: J 12", |
| b69ab31 | | | 232 | "12: JL 2 15", |
| b69ab31 | | | 233 | "13: J 22", |
| b69ab31 | | | 234 | "14: LINE 2 "6"", |
| b69ab31 | | | 235 | "15: J 19", |
| b69ab31 | | | 236 | "16: JGE 2 8", |
| b69ab31 | | | 237 | "17: LINE 1 "1"", |
| b69ab31 | | | 238 | "18: J 8", |
| b69ab31 | | | 239 | "19: J 21", |
| b69ab31 | | | 240 | "20: J 21", |
| b69ab31 | | | 241 | "21: J 35", |
| b69ab31 | | | 242 | "22: J 23", |
| b69ab31 | | | 243 | "23: J 38", |
| b69ab31 | | | 244 | "24: J 14", |
| b69ab31 | | | 245 | "25: J 27", |
| b69ab31 | | | 246 | "26: J 27", |
| b69ab31 | | | 247 | "27: J 28", |
| b69ab31 | | | 248 | "28: J 41", |
| b69ab31 | | | 249 | "29: J 9", |
| b69ab31 | | | 250 | "30: J 32", |
| b69ab31 | | | 251 | "31: J 32", |
| b69ab31 | | | 252 | "32: J 33", |
| b69ab31 | | | 253 | "33: J 46", |
| b69ab31 | | | 254 | "34: J 4", |
| b69ab31 | | | 255 | "35: JL 2.0000038146972656 37", |
| b69ab31 | | | 256 | "36: LINE 2.0000038146972656 "y"", |
| b69ab31 | | | 257 | "37: END", |
| b69ab31 | | | 258 | "38: JGE 2.000002861022949 24", |
| b69ab31 | | | 259 | "39: LINE 2 "5"", |
| b69ab31 | | | 260 | "40: J 24", |
| b69ab31 | | | 261 | "41: JL 1.0000019073486328 43", |
| b69ab31 | | | 262 | "42: LINE 1.0000019073486328 "x"", |
| b69ab31 | | | 263 | "43: JGE 1.0000019073486328 29", |
| b69ab31 | | | 264 | "44: LINE 1 "2"", |
| b69ab31 | | | 265 | "45: J 29", |
| b69ab31 | | | 266 | "46: JL 3.0000009536743164 48", |
| b69ab31 | | | 267 | "47: LINE 3.0000009536743164 "U"", |
| b69ab31 | | | 268 | "48: JGE 3.0000009536743164 34", |
| b69ab31 | | | 269 | "49: LINE 0 "u"", |
| b69ab31 | | | 270 | "50: J 34", |
| b69ab31 | | | 271 | ] |
| b69ab31 | | | 272 | `); |
| b69ab31 | | | 273 | }); |
| b69ab31 | | | 274 | |
| b69ab31 | | | 275 | it('1:1 line mapping edit can include immutable lines', () => { |
| b69ab31 | | | 276 | const stack = createStack(['p↵', 'p↵1↵', 'p↵1↵2↵', 'P↵X↵Y↵']); |
| b69ab31 | | | 277 | const [, absorbMap] = calculateAbsorbEditsForFileStack(stack); |
| b69ab31 | | | 278 | // Absorbed edits: "1 => X"; "2 => Y" (selected is set) |
| b69ab31 | | | 279 | // "p => P" is left in the working copy, since "p" is considered immutable. |
| b69ab31 | | | 280 | expect(describeAbsorbIdChunkMap(absorbMap)).toMatchInlineSnapshot(` |
| b69ab31 | | | 281 | [ |
| b69ab31 | | | 282 | "0: -p↵ +P↵ Introduced=0", |
| b69ab31 | | | 283 | "1: -1↵ +X↵ Selected=1 Introduced=1", |
| b69ab31 | | | 284 | "2: -2↵ +Y↵ Selected=2 Introduced=2", |
| b69ab31 | | | 285 | ] |
| b69ab31 | | | 286 | `); |
| b69ab31 | | | 287 | }); |
| b69ab31 | | | 288 | }); |
| b69ab31 | | | 289 | |
| b69ab31 | | | 290 | function createStack(texts: string[]): FileStackState { |
| b69ab31 | | | 291 | return new FileStackState(texts.map(t => injectNewLines(t))); |
| b69ab31 | | | 292 | } |
| b69ab31 | | | 293 | |
| b69ab31 | | | 294 | function analyseFile(stack: FileStackState, newText: string): string { |
| b69ab31 | | | 295 | const text = injectNewLines(newText); |
| b69ab31 | | | 296 | const oldLines = splitLines(stack.getRev((stack.revLength - 1) as FileRev)); |
| b69ab31 | | | 297 | const newLines = splitLines(text); |
| b69ab31 | | | 298 | const chunks = analyseFileStack(stack, text); |
| b69ab31 | | | 299 | return chunks |
| b69ab31 | | | 300 | .map(c => { |
| b69ab31 | | | 301 | // Check the old and new line numbers and content match. |
| b69ab31 | | | 302 | expect(oldLines.slice(c.oldStart, c.oldEnd)).toEqual(c.oldLines.toArray()); |
| b69ab31 | | | 303 | expect(newLines.slice(c.newStart, c.newEnd)).toEqual(c.newLines.toArray()); |
| b69ab31 | | | 304 | return `${c.oldStart}:${c.oldEnd}=>'${c.newLines |
| b69ab31 | | | 305 | .map(l => l.replace('\n', '')) |
| b69ab31 | | | 306 | .join('')}': Rev ${c.introductionRev}+ Selected ${c.selectedRev}`; |
| b69ab31 | | | 307 | }) |
| b69ab31 | | | 308 | .join('\n'); |
| b69ab31 | | | 309 | } |
| b69ab31 | | | 310 | |
| b69ab31 | | | 311 | function applyEdits(stack: FileStackState, edits: Iterable<AbsorbEdit>): string { |
| b69ab31 | | | 312 | const editedStack = applyFileStackEditsWithAbsorbId(stack, edits); |
| b69ab31 | | | 313 | return compactTexts(editedStack.revs().map(rev => editedStack.getRev(revWithAbsorb(rev)))); |
| b69ab31 | | | 314 | } |
| b69ab31 | | | 315 | |
| b69ab31 | | | 316 | /** Replace "↵" with "\n" */ |
| b69ab31 | | | 317 | function injectNewLines(text: string): string { |
| b69ab31 | | | 318 | return text.replaceAll('↵', '\n'); |
| b69ab31 | | | 319 | } |
| b69ab31 | | | 320 | |
| b69ab31 | | | 321 | function compactText(text: string): string { |
| b69ab31 | | | 322 | return text.replaceAll('\n', '↵'); |
| b69ab31 | | | 323 | } |
| b69ab31 | | | 324 | }); |
| b69ab31 | | | 325 | |
| b69ab31 | | | 326 | /** Turn ["a\n", "a\nb\n"] to "a↵ ab↵". */ |
| b69ab31 | | | 327 | function compactTexts(texts: Iterable<string>): string { |
| b69ab31 | | | 328 | return [...texts].map(t => t.replaceAll('\n', '↵')).join(' '); |
| b69ab31 | | | 329 | } |
| b69ab31 | | | 330 | |
| b69ab31 | | | 331 | export function describeAbsorbIdChunkMap(map: ImMap<AbsorbEditId, AbsorbEdit>): string[] { |
| b69ab31 | | | 332 | const result: string[] = []; |
| b69ab31 | | | 333 | map.forEach((chunk, id) => { |
| b69ab31 | | | 334 | const words: string[] = [`${id}:`]; |
| b69ab31 | | | 335 | if (!chunk.oldLines.isEmpty()) { |
| b69ab31 | | | 336 | words.push(`-${compactTexts(chunk.oldLines)}`); |
| b69ab31 | | | 337 | } |
| b69ab31 | | | 338 | if (!chunk.newLines.isEmpty()) { |
| b69ab31 | | | 339 | words.push(`+${compactTexts(chunk.newLines)}`); |
| b69ab31 | | | 340 | } |
| b69ab31 | | | 341 | if (chunk.selectedRev != null) { |
| b69ab31 | | | 342 | words.push(`Selected=${chunk.selectedRev}`); |
| b69ab31 | | | 343 | } |
| b69ab31 | | | 344 | words.push(`Introduced=${chunk.introductionRev}`); |
| b69ab31 | | | 345 | result.push(words.join(' ')); |
| b69ab31 | | | 346 | }); |
| b69ab31 | | | 347 | return result; |
| b69ab31 | | | 348 | } |