Commit 52dfc56
committed
📝 Add implementation plan for Windows junction support
📝 Document junction handling in --allow-unsafe-links
The extract path gates Windows junction creation on --allow-unsafe-links
(see `cli/src/command/extract.rs` junction arm), but the flag's help text
still advertised only "symbolic links and hard links". A user reading
`pna extract --help` or `pna compat bsdtar --help` today would not know
the flag also covers NTFS junctions.
Rewrite the four help strings (positive/negative x extract/bsdtar) to
list symbolic links, hard links, and Windows junctions together, and
regenerate the committed CLI reference so docs stay in sync.
🐛 Pass --unstable alongside --keep-permission on Windows
Windows CI surfaced that `extract_junction_does_not_mutate_external_target`
panics with `MissingRequiredArgument: --unstable` on the
`--no-default-features` build of the `extract` subcommand. On Windows,
`--keep-permission` (and related metadata-restoration flags) is gated
behind `--unstable` per `cli/src/command/extract.rs:149-150`
(`#[cfg_attr(windows, arg(requires = "unstable", ...))]`).
Add `#[cfg(windows)] "--unstable"` to the test's CLI args, matching
the peer pattern in `cli/tests/cli/keep_all.rs` and
`cli/tests/cli/diff/metadata.rs`. Plan mirrored.
🐛 Load-bearing I2 fence: stamp Permission chunk in junction fixture
The Task 4.4 spec-compliance review empirically proved the I2 security
fence was decorative: a regression that swaps
`restore_link_timestamps_no_follow` for the default `restore_metadata`
call slipped through the `extract_junction_does_not_mutate_external_target`
assertion. Root cause: `build_junction_fixture` produced a junction entry
with no Permission chunk, so `restore_metadata`'s chmod/chown branch
(gated on `item.metadata().permission().is_some()`) never entered — no
follow-link syscall ever touched the target, no matter what the extract
code did.
Extend the fixture helper surface:
- `build_junction_fixture_with_permission(target, mode)` stamps a
Permission chunk with the given mode so extract's `restore_mode` actually
fires when `--keep-permission` is passed.
- `build_junction_fixture` continues to produce a Permission-less entry
for the other two tests that don't exercise the mode path.
- Both delegate to a shared
`build_junction_fixture_with_optional_permission` to avoid duplication.
Retarget the I2 test to `build_junction_fixture_with_permission(..., 0o755)`
while still pre-setting the external directory to 0o700. Under a regression
that removes Task 4.1's early return, extract now calls `chmod(link, 0o755)`
which follows the symlink and changes the external target's mode to 0o755,
firing `baseline_perms != after_perms` with the "I2 violation" message.
Empirically verified by reverting Task 4.1's early return in a throwaway
experiment: the fence fires with `left 0o040700, right 0o040755`. With
the early return restored, the fence passes.
Plan Task 4.4 Step 1 code block mirrors the new helper surface.
✅ Pin junction extract does-not-mutate-target invariant
🎨 Hoist file_type() call in junction round-trip test
Match the neighboring `extract_junction_with_allow_unsafe_links_creates_link`
test's `let ft = meta.file_type();` hoist, avoiding three separate
`file_type()` calls inside a single `assert!`. Pure style cleanup flagged
by the Task 4.3 code-quality review.
✅ End-to-end junction round trip via CLI
🐛 Gate junction round-trip test off WASM
CI surfaced that `extract_junction_with_allow_unsafe_links_creates_link`
panics under `wasm32-wasip1` with "Operation not permitted (os error 63)"
because wasi-preview1 does not expose symlink creation. The fallback
path `pna::fs::symlink` therefore fails when the test tries to
materialize the non-Windows junction-fallback symlink.
Add `#[cfg(not(target_family = "wasm"))]` to the test, matching the
pattern established by `cli/tests/cli/extract/hardlink.rs:95` and
peers which gate off WASM for the same reason. The skip-case test
(`extract_junction_without_allow_unsafe_links_skips`) is not gated
because it never reaches the symlink-creation path.
Plan Task 4.2 Step 1 code block mirrors the gate.
✅ Cover junction extract round-trip across platforms
🎨 Polish Task 4.1 extract branch per code-quality review
Address all four Minor nits flagged by the Task 4.1 code-quality
review; none affect Invariant I2 correctness.
- Junction skip warning: drop the internal encoding term
"HardLink+fLTP=Directory" and match the peer symlink/hardlink voice
("If you need to extract it, use `--allow-unsafe-links`.").
- Non-Windows fallback warning: demote to `log::debug!`. The per-entry
`warn!` would spam on large Windows-origin archives extracted on
Unix; the degradation (junction → symlink) is a known platform
limitation, best surfaced at debug level.
- `create_junction_or_fallback` canonicalize: replace `unwrap_or` with
an explicit match that logs the underlying error at `debug!` level
before falling back to the raw join. Prevents permission-denied /
ELOOP failures from being hidden behind `create_junction`'s generic
"target must be absolute" message.
- Test pattern: switch `extract_junction_without_allow_unsafe_links_skips`
to the peer CLI test convention (`utils::setup()` + relative
`"{base}/{base}.pna"` paths + in-process `cli::Cli::try_parse_from`).
Drops the subprocess invocation and the macOS `canonicalize`
workaround, and aligns with existing peer tests in
`cli/tests/cli/extract/`. The stderr assertion is dropped because
the filesystem assertion ("link_dir does not exist") is the
load-bearing check.
Plan Task 4.1 Step 2 and Step 4 mirror the final code.
✨ Extract HardLink+fLTP=Directory as junction or symlink fallback
♻️ Route junction target through PathnameEditor::edit_junction
The Task 3.1 code-quality review flagged that the `StoreAs::Junction`
arm in `create_entry` built the `EntryReference` directly via
`EntryReference::from_utf8_preserve_root(target.to_str().unwrap())`,
bypassing the `PathnameEditor::edit_junction` method added in Task 2.1.
The consequence was that user `-s` / `--transform` substitutions
applied to symlink targets but silently NOT to junction targets, and
`edit_junction` itself was dead code on every platform.
Call `pathname_editor.edit_junction(target)` instead. The method
applies substitutions and returns an `EntryReference` via the shared
helper that `edit_symlink` also uses, so junction and symlink targets
behave consistently under user transforms. Invariant I1 (spec §7)
guarantees the target is valid UTF-8, so the helper's internal
`to_string_lossy` is effectively lossless for real inputs.
Updates spec §6 / §8 so the "load-bearing unwrap" paragraph is
replaced with the `edit_junction` rationale, and plan Task 3.1 Step 7
mirrors the final code. Remove the now-unused `EntryReference` import
from `cli/src/command/core.rs`.
✨ Detect and archive Windows junctions as HardLink entries
🐛 Terminate junction reparse names with UTF-16 NUL
Windows CI revealed that every `create_junction_round_trip` run was
hitting `ERROR_INVALID_REPARSE_DATA` (4392) — the kernel rejected our
IO_REPARSE_TAG_MOUNT_POINT buffer. Microsoft's REPARSE_DATA_BUFFER
contract requires both SubstituteName and PrintName inside PathBuffer
to be UTF-16 NUL-terminated, with the length fields excluding the NULs
and PrintNameOffset set after SubstituteNameLength plus the NUL.
Our writer omitted both terminators and pointed PrintNameOffset at
`subst_bytes_len` (should be `subst_bytes_len + 2`). The parser never
noticed because it slices by offset/length and never reads the NULs,
so `read_reparse_point_on_junction` (which reads a `mklink /J`-built
junction) passed all along while `create_junction_round_trip` failed.
Append a u16 0x0000 after each name, bump `PrintNameOffset` by 2, and
make `ReparseDataLength` account for the four extra NUL bytes. The
u16-overflow guard now also checks the two extra NUL bytes so
oversized targets are still caught before any filesystem mutation.
Plan Task 1.3 Step 4 mirrors the fix.
🎨 Align edit_junction test names and doc comment with conventions
The Task 2.1 code-quality review flagged two stylistic gaps against the
file's existing conventions:
- The four new unit tests used an `edit_junction_*` prefix while every
other test in `cli/src/command/core/path.rs` uses an `editor_*`
prefix. Rename to `editor_junction_*` so the test list stays
consistently grep-able.
- The `edit_junction` doc comment captured the design rationale (why a
separate method exists) but not the contract (substitutions applied;
leading `/` and --strip-components NOT applied). Mirror the contract
lines from `edit_symlink` so the two methods read as siblings, not
as rationale + cross-reference.
✨ Add PathnameEditor::edit_junction delegating to shared helper
✨ Add detect_junction helper for CLI
🔒 Reject oversized junction targets before mutating filesystem
The Task 1.3 code-quality review flagged a silent `u16` truncation
pitfall: the reparse-buffer header stores SubstituteName and PrintName
byte lengths as `u16`, and `create_junction` was casting
`(wide.len() * 2) as u16` plus computing `data_len = 8 + subst + print`
as `u16` — a path exceeding ~32 KB of UTF-16 bytes would silently wrap
and emit a malformed reparse buffer. The 16 KB reparse-buffer kernel
cap acts as a backstop, but relying on that to backstop silent
corruption is weaker than the helper's contract should be.
Move the SubstituteName / PrintName construction and the u16 overflow
check ahead of `std::fs::create_dir`, so an oversized target now
returns `InvalidInput` at the helper boundary without ever leaving a
half-created junction directory behind. Use `checked_add` for the
8-byte offset-header total so the check also catches the
32_760-plus-offset-header wrap case.
Plan Task 1.3 Step 4 mirrors the reorder.
✨ Create NTFS junctions via FSCTL_SET_REPARSE_POINT
♻️ Align read_reparse_point with CLI FFI conventions
The Task 1.2 code-quality review flagged three consistency gaps against
the rest of the crate's Windows FFI:
- Reuse `crate::utils::str::encode_wide`, which rejects embedded NUL
bytes before null-terminating. The previous inline
`encode_wide().collect() + push(0)` would let a path with an embedded
NUL reach CreateFileW and open a silently-truncated different file.
All nine sibling call sites in `cli/src/utils/os/windows/` already use
this helper.
- Use `PCWSTR::from_raw` instead of the tuple-struct constructor, again
matching the nine sibling call sites.
- Downgrade `read_reparse_point` from `pub` to `pub(crate)` to match the
spec §5 commitment that these primitives stay CLI-private. Promotion
into the `pna` crate behind a `windows-fs` feature is captured as an
explicit follow-up in spec §11.
Plan Task 1.2 Step 4 and spec §5 are updated to mirror the source so
they don't drift again.
✨ Read NTFS reparse points via DeviceIoControl
✅ Cover symlink reparse buffer parsing
The Task 1.1 code-quality review flagged that the IO_REPARSE_TAG_SYMLINK
arm of parse_reparse_buffer shipped without direct unit coverage. Add
three tests and the sample_symlink_buffer helper that exercises the
symlink layout (with the extra 4-byte Flags word): one for an absolute
target where \\??\\ is stripped, one for a relative target where the
raw string is preserved, and one for a truncated buffer that should
error with InvalidData before the flags word is read.
Updates the plan's Task 1.1 Step 3 test module to mirror the final
reparse.rs file so plan and implementation stay in sync.
🎨 Sort windows feature list alphabetically
ASCII lexicographic order places Win32_System_IO before Win32_System_Ioctl
('O' 0x4F < 'o' 0x6F). The plan originally placed them in the opposite
order, which the Task 1.1 code-quality review flagged as the only
sortable-by-ASCII anomaly in the list. Swap the two entries in both
Cargo.toml and the plan so the list is strictly sorted — the stable-diff
rationale the plan cites now actually holds.
No functional impact; feature ordering does not affect compilation.
✨ Add Windows reparse buffer parser in CLI
📝 Slim junction plan to reference the spec
The spec `docs/superpowers/specs/2026-04-19-windows-junction-support-design.md`
now owns the WHY — problem, architecture, interfaces, invariants,
rejected alternatives, follow-ups. The plan keeps the HOW — Task 1.1
through Task 6.2 with concrete code snippets, commit commands, and
verification steps.
Removed duplicated sections from the plan (Goal, Architecture, Tech
Stack narrative, full In-scope/Out-of-scope list, Risk Summary table,
Testing Matrix detailed rows, File Structure tables) and replaced them
with short cross-references plus plan-specific anchors: where each of
T1-T19 lands, which task enforces which safety invariant, which files
are touched. The Task List and its embedded implementation details are
untouched.
📝 Formalize Windows junction support design spec
Writing-plans skipped the brainstorming→spec step and produced a plan
directly, with all design decisions iterated inside the plan through
grill and Codex adversarial review rounds. This commits the spec
retroactively so architectural intent lives separately from task-level
steps.
Captures locked decisions (HardLink+fLTP=Directory encoding, cli-only
module placement, PathnameEditor::edit_junction with shared helper,
Option A metadata safety with early return, HRESULT→Win32 error
translation, non-UTF-8 invariant from UTF-16 gate) alongside the
rejected alternatives (cases B'/C', pna-crate module placement, option
B/C metadata approaches, direct edit_symlink reuse, HRESULT-form error
matching) and the deferred follow-ups (relative-path optimization,
full junction-aware no-follow metadata, public reparse-point API).
📝 Translate HRESULT to Win32 code in reparse error wrapping
Codex adversarial review flagged that `detect_junction`'s
`raw_os_error() == Some(4390)` check against ERROR_NOT_A_REPARSE_POINT
would never fire because `read_reparse_point` and `create_junction`
round-trip `windows::core::Error` through
`io::Error::from_raw_os_error(e.code().0)`. The windows crate encodes
Win32 failures as HRESULT via `HRESULT_FROM_WIN32` (0x80070000 | code),
so the stored raw_os_error is the HRESULT form (e.g. 0x80071126), not
the canonical Win32 code (4390).
Introduce `io_error_from_win32` in the reparse module. It inspects the
HRESULT facility bits and, when FACILITY_WIN32 is set, extracts the low
16 bits as the Win32 code; other HRESULTs pass through as raw i32. Every
`CreateFileW` / `DeviceIoControl` error path in read_reparse_point and
create_junction now goes through this helper, so downstream consumers
such as `detect_junction` see Microsoft-documented Win32 codes.
Adds two pure-Rust unit tests that verify the FACILITY_WIN32 extraction
and the non-Win32 pass-through, so the helper is regression-fenced
without needing a Windows CI round-trip.
📝 Close junction external-target mutation hole in plan
Codex adversarial review flagged that `DataKind::HardLink + fLTP=Directory`
(junction or non-Windows fallback symlink) still went through the default
`restore_metadata()` path in extract.rs, which would chmod/chown/ACL/xattr/
fflag the junction via follow-link syscalls and therefore mutate the
external directory outside the extraction root. Option (A) from the
review discussion closes the hole by bypassing `restore_metadata()` for
junction entries and applying only no-follow timestamp restoration via a
new `restore_link_timestamps_no_follow` helper. A `TODO:
junction-aware no-follow metadata (deferred follow-up)` block on that
helper and a new Out-of-Scope entry document the full option (C) path
for a follow-up plan.
Task 4.4 locks the invariant with a security regression test (T19) that
pre-sets a distinctive mode on the external target, runs extract with
`--keep-permission --allow-unsafe-links`, and asserts the external mode
is unchanged. Self-review, risk summary, testing matrix and
type-consistency sections are updated accordingly.
📝 Align junction plan with existing Windows module tree
Codex adversarial review flagged that the plan instructed implementers to
create cli/src/utils/os/windows/fs/mod.rs and edit
cli/src/utils/os/windows/mod.rs, but the repository uses the sibling
layout: cli/src/utils/os/windows.rs declares `pub(crate) mod fs;` and
cli/src/utils/os/windows/fs.rs already holds FileHandle, chmod, lchown,
open_read_metadata, and pub(crate) mod owner. Creating a new fs/mod.rs
would conflict with fs.rs and strand those exports.
Reparse and junction now live as additional submodules of the existing
fs module: Task 1.1 adds `pub(crate) mod reparse;` to windows/fs.rs and
creates windows/fs/reparse.rs; Task 1.4 adds `pub(crate) mod junction;`
and creates windows/fs/junction.rs. The existing contents of
windows/fs.rs are left untouched.
📝 Rework junction plan after grill review
Move the Windows reparse-point FFI out of pna into cli so pna stays
platform-dependency-free. Add PathnameEditor::edit_junction delegating
to a helper shared with edit_symlink. Declare StoreAs::Junction as
unconditional for exhaustive matching. Accept both absolute and relative
stored targets at extract for forward compatibility. Correct
detect_junction's error-kind mapping, switch to OsStr::encode_wide for
non-lossy path encoding in create_junction, and fix the push branch name.1 parent 3f7aa7c commit 52dfc56
14 files changed
Lines changed: 3554 additions & 19 deletions
File tree
- cli
- src
- command
- core
- utils/os/windows
- fs
- tests/cli
- docs
- superpowers
- plans
- specs
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
65 | 65 | | |
66 | 66 | | |
67 | 67 | | |
68 | | - | |
| 68 | + | |
| 69 | + | |
69 | 70 | | |
70 | | - | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
71 | 74 | | |
| 75 | + | |
72 | 76 | | |
73 | 77 | | |
74 | 78 | | |
| |||
77 | 81 | | |
78 | 82 | | |
79 | 83 | | |
| 84 | + | |
80 | 85 | | |
81 | 86 | | |
82 | 87 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
601 | 601 | | |
602 | 602 | | |
603 | 603 | | |
604 | | - | |
| 604 | + | |
605 | 605 | | |
606 | 606 | | |
607 | 607 | | |
608 | 608 | | |
609 | | - | |
| 609 | + | |
610 | 610 | | |
611 | 611 | | |
612 | 612 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
383 | 383 | | |
384 | 384 | | |
385 | 385 | | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
386 | 391 | | |
387 | 392 | | |
388 | 393 | | |
| |||
781 | 786 | | |
782 | 787 | | |
783 | 788 | | |
784 | | - | |
785 | | - | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
786 | 795 | | |
787 | 796 | | |
788 | 797 | | |
| |||
883 | 892 | | |
884 | 893 | | |
885 | 894 | | |
| 895 | + | |
| 896 | + | |
| 897 | + | |
| 898 | + | |
| 899 | + | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
| 905 | + | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
| 910 | + | |
| 911 | + | |
| 912 | + | |
| 913 | + | |
| 914 | + | |
886 | 915 | | |
887 | 916 | | |
888 | 917 | | |
| |||
980 | 1009 | | |
981 | 1010 | | |
982 | 1011 | | |
| 1012 | + | |
| 1013 | + | |
| 1014 | + | |
| 1015 | + | |
| 1016 | + | |
| 1017 | + | |
| 1018 | + | |
| 1019 | + | |
| 1020 | + | |
| 1021 | + | |
| 1022 | + | |
| 1023 | + | |
983 | 1024 | | |
984 | 1025 | | |
985 | 1026 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
77 | 77 | | |
78 | 78 | | |
79 | 79 | | |
80 | | - | |
81 | | - | |
82 | | - | |
83 | | - | |
84 | | - | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
| |||
94 | 94 | | |
95 | 95 | | |
96 | 96 | | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
97 | 119 | | |
98 | 120 | | |
99 | 121 | | |
| |||
1156 | 1178 | | |
1157 | 1179 | | |
1158 | 1180 | | |
| 1181 | + | |
| 1182 | + | |
| 1183 | + | |
| 1184 | + | |
| 1185 | + | |
| 1186 | + | |
| 1187 | + | |
| 1188 | + | |
| 1189 | + | |
| 1190 | + | |
| 1191 | + | |
| 1192 | + | |
| 1193 | + | |
| 1194 | + | |
| 1195 | + | |
| 1196 | + | |
| 1197 | + | |
| 1198 | + | |
| 1199 | + | |
| 1200 | + | |
| 1201 | + | |
| 1202 | + | |
| 1203 | + | |
| 1204 | + | |
| 1205 | + | |
| 1206 | + | |
| 1207 | + | |
| 1208 | + | |
| 1209 | + | |
1159 | 1210 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
366 | 366 | | |
367 | 367 | | |
368 | 368 | | |
369 | | - | |
| 369 | + | |
370 | 370 | | |
371 | 371 | | |
372 | 372 | | |
373 | 373 | | |
374 | | - | |
| 374 | + | |
375 | 375 | | |
376 | 376 | | |
377 | 377 | | |
| |||
1421 | 1421 | | |
1422 | 1422 | | |
1423 | 1423 | | |
| 1424 | + | |
| 1425 | + | |
| 1426 | + | |
| 1427 | + | |
| 1428 | + | |
| 1429 | + | |
| 1430 | + | |
| 1431 | + | |
| 1432 | + | |
| 1433 | + | |
| 1434 | + | |
| 1435 | + | |
| 1436 | + | |
| 1437 | + | |
| 1438 | + | |
| 1439 | + | |
| 1440 | + | |
| 1441 | + | |
| 1442 | + | |
| 1443 | + | |
| 1444 | + | |
| 1445 | + | |
| 1446 | + | |
| 1447 | + | |
| 1448 | + | |
| 1449 | + | |
| 1450 | + | |
| 1451 | + | |
| 1452 | + | |
| 1453 | + | |
| 1454 | + | |
| 1455 | + | |
| 1456 | + | |
| 1457 | + | |
| 1458 | + | |
1424 | 1459 | | |
1425 | 1460 | | |
1426 | 1461 | | |
| |||
2016 | 2051 | | |
2017 | 2052 | | |
2018 | 2053 | | |
| 2054 | + | |
| 2055 | + | |
| 2056 | + | |
| 2057 | + | |
| 2058 | + | |
| 2059 | + | |
| 2060 | + | |
| 2061 | + | |
| 2062 | + | |
| 2063 | + | |
| 2064 | + | |
| 2065 | + | |
| 2066 | + | |
| 2067 | + | |
| 2068 | + | |
| 2069 | + | |
| 2070 | + | |
| 2071 | + | |
| 2072 | + | |
| 2073 | + | |
| 2074 | + | |
| 2075 | + | |
| 2076 | + | |
| 2077 | + | |
| 2078 | + | |
| 2079 | + | |
| 2080 | + | |
| 2081 | + | |
| 2082 | + | |
| 2083 | + | |
| 2084 | + | |
| 2085 | + | |
| 2086 | + | |
| 2087 | + | |
| 2088 | + | |
| 2089 | + | |
| 2090 | + | |
| 2091 | + | |
| 2092 | + | |
| 2093 | + | |
| 2094 | + | |
| 2095 | + | |
| 2096 | + | |
| 2097 | + | |
| 2098 | + | |
| 2099 | + | |
| 2100 | + | |
| 2101 | + | |
| 2102 | + | |
| 2103 | + | |
| 2104 | + | |
| 2105 | + | |
| 2106 | + | |
| 2107 | + | |
| 2108 | + | |
| 2109 | + | |
| 2110 | + | |
| 2111 | + | |
| 2112 | + | |
| 2113 | + | |
| 2114 | + | |
| 2115 | + | |
| 2116 | + | |
| 2117 | + | |
| 2118 | + | |
| 2119 | + | |
| 2120 | + | |
| 2121 | + | |
| 2122 | + | |
| 2123 | + | |
| 2124 | + | |
| 2125 | + | |
| 2126 | + | |
| 2127 | + | |
| 2128 | + | |
| 2129 | + | |
| 2130 | + | |
| 2131 | + | |
| 2132 | + | |
| 2133 | + | |
| 2134 | + | |
| 2135 | + | |
| 2136 | + | |
| 2137 | + | |
| 2138 | + | |
| 2139 | + | |
| 2140 | + | |
| 2141 | + | |
| 2142 | + | |
| 2143 | + | |
| 2144 | + | |
| 2145 | + | |
| 2146 | + | |
| 2147 | + | |
| 2148 | + | |
| 2149 | + | |
| 2150 | + | |
| 2151 | + | |
| 2152 | + | |
| 2153 | + | |
| 2154 | + | |
2019 | 2155 | | |
2020 | 2156 | | |
2021 | 2157 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
1 | 2 | | |
| 3 | + | |
2 | 4 | | |
3 | 5 | | |
4 | 6 | | |
| |||
0 commit comments