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 | let out = if is_symlink && self.core_symlinks { | 570 | let out = if is_symlink && self.core_symlinks { |
| 571 | // conversion to bstr can never fail because symlinks are only used | 571 | // conversion to bstr can never fail because symlinks are only used |
| 572 | // on unix (by git) so no reason to use the try version here | 572 | // on unix (by git) so no reason to use the try version here |
| 573 | let symlink_path = | 573 | let symlink_path = gix_path::to_unix_separators_on_windows(gix_path::into_bstr( |
| 574 | gix_path::to_unix_separators_on_windows(gix_path::into_bstr(std::fs::read_link(self.path).unwrap())); | 574 | std::fs::read_link(self.path).map_err(gix_hash::io::Error::from)?, |
| 575 | )); | ||
| 575 | self.buf.extend_from_slice(&symlink_path); | 576 | self.buf.extend_from_slice(&symlink_path); |
| 576 | self.worktree_bytes.fetch_add(self.buf.len() as u64, Ordering::Relaxed); | 577 | self.worktree_bytes.fetch_add(self.buf.len() as u64, Ordering::Relaxed); |
| 577 | Stream { | 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 | fn default_pathspec() -> gix_pathspec::Search { | 1128 | fn default_pathspec() -> gix_pathspec::Search { |
| 1129 | gix_pathspec::Search::from_specs(to_pathspecs(&[]), None, std::path::Path::new("")).expect("empty is always valid") | 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 | } | ||