Replace unwrap() with proper error handling when reading symlink targets in stream_worktree_file. This prevents panics when read_link fails due to permissions, race conditions, or I/O errors.
Unified · Split
| @@ -570,8 +570,9 @@ where · 569 unmodified lines | ||
| 570 | 570 | let out = if is_symlink && self.core_symlinks { |
| 571 | 571 | // conversion to bstr can never fail because symlinks are only used |
| 572 | 572 | // on unix (by git) so no reason to use the try version here |
| 573 | − let symlink_path = | |
| 574 | − gix_path::to_unix_separators_on_windows(gix_path::into_bstr(std::fs::read_link(self.path).unwrap())); | |
| 573 | + let symlink_path = gix_path::to_unix_separators_on_windows(gix_path::into_bstr( | |
| 574 | + std::fs::read_link(self.path).map_err(gix_hash::io::Error::from)?, | |
| 575 | + )); | |
| 575 | 576 | self.buf.extend_from_slice(&symlink_path); |
| 576 | 577 | self.worktree_bytes.fetch_add(self.buf.len() as u64, Ordering::Relaxed); |
| 577 | 578 | Stream { |
| @@ -0,0 +1,17 @@ | ||
| 1 | +#!/usr/bin/env bash | |
| 2 | +set -eu -o pipefail | |
| 3 | + | |
| 4 | +# Initialize main git repo | |
| 5 | +git init -q | |
| 6 | +git config --local commit.gpgsign false | |
| 7 | +git config user.email "test@example.com" | |
| 8 | +git config user.name "Test User" | |
| 9 | + | |
| 10 | +# Create a symlink and add it to git | |
| 11 | +ln -s target broken_link | |
| 12 | +git add broken_link | |
| 13 | +git commit -q -m "Add symlink" | |
| 14 | + | |
| 15 | +# Now break the symlink by removing the target | |
| 16 | +# The symlink exists but points to nothing | |
| 17 | +# Note: the symlink was stored in the index, so git knows about it | |
| @@ -0,0 +1,26 @@ | ||
| 1 | +#!/usr/bin/env bash | |
| 2 | +set -eu -o pipefail | |
| 3 | + | |
| 4 | +# Initialize main git repo | |
| 5 | +git init -q | |
| 6 | +git config --local commit.gpgsign false | |
| 7 | +git config user.email "test@example.com" | |
| 8 | +git config user.name "Test User" | |
| 9 | + | |
| 10 | +# Create a nested directory structure | |
| 11 | +mkdir -p some/path | |
| 12 | +TF=$(pwd)/some/path | |
| 13 | + | |
| 14 | +# Create files in the nested path | |
| 15 | +touch "$TF/foo" "$TF/bar" "$TF/blah" | |
| 16 | +echo "merry" > "$TF/foo" | |
| 17 | +echo "christmas" > "$TF/bar" | |
| 18 | +git add some/path/foo some/path/bar | |
| 19 | +git commit -q -m "Initial commit" | |
| 20 | + | |
| 21 | +# Modify a file to create a change | |
| 22 | +echo "new year" > "$TF/bar" | |
| 23 | + | |
| 24 | +# Create a symlink pointing to the nested path | |
| 25 | +mkdir -p config | |
| 26 | +ln -s "$TF" config/repro_link | |
| @@ -1128,3 +1128,31 @@ fn racy_git() { · 1127 unmodified lines | ||
| 1128 | 1128 | fn default_pathspec() -> gix_pathspec::Search { |
| 1129 | 1129 | gix_pathspec::Search::from_specs(to_pathspecs(&[]), None, std::path::Path::new("")).expect("empty is always valid") |
| 1130 | 1130 | } |
| 1131 | + | |
| 1132 | +#[test] | |
| 1133 | +fn symlink_to_nested_path() { | |
| 1134 | + // Reproduce issue from https://github.com/gitui-org/gitui/issues/2820 | |
| 1135 | + // Tests handling of symlinks pointing to nested directories with modifications | |
| 1136 | + let expected_outcome = fixture( | |
| 1137 | + "status_symlink_submodule", | |
| 1138 | + &[( | |
| 1139 | + BStr::new(b"some/path/bar"), | |
| 1140 | + 0, | |
| 1141 | + Change::Modification { | |
| 1142 | + executable_bit_changed: false, | |
| 1143 | + content_change: Some(()), | |
| 1144 | + set_entry_stat_size_zero: false, | |
| 1145 | + } | |
| 1146 | + .into(), | |
| 1147 | + )], | |
| 1148 | + ); | |
| 1149 | + assert_eq!( | |
| 1150 | + expected_outcome, | |
| 1151 | + Outcome { | |
| 1152 | + entries_to_process: 2, | |
| 1153 | + entries_processed: 2, | |
| 1154 | + symlink_metadata_calls: 2, | |
| 1155 | + ..Default::default() | |
| 1156 | + } | |
| 1157 | + ); | |
| 1158 | +} | |