History

fix: handle `read_link` errors instead of panicking

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.

copilot-swe-agent[bot] committed 10 months ago

Commit 33ff24b1 parent a000a734 files · +74 −2Browse files

Unified · Split

4 files
  1. gix-status/src/index_as_worktree/function.rs +3 −2
  2. gix-status/tests/fixtures/status_broken_symlink.sh +17 −0
  3. gix-status/tests/fixtures/status_symlink_submodule.sh +26 −0
  4. gix-status/tests/status/index_as_worktree.rs +28 −0
gix-status/src/index_as_worktree/function.rs+3−2
@@ -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 used571 // conversion to bstr can never fail because symlinks are only used
572 // on unix (by git) so no reason to use the try version here572 // 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 {
gix-status/tests/fixtures/status_broken_symlink.sh+17−0
@@ -0,0 +1,17 @@
1#!/usr/bin/env bash
2set -eu -o pipefail
3
4# Initialize main git repo
5git init -q
6git config --local commit.gpgsign false
7git config user.email "test@example.com"
8git config user.name "Test User"
9
10# Create a symlink and add it to git
11ln -s target broken_link
12git add broken_link
13git 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
gix-status/tests/fixtures/status_symlink_submodule.sh+26−0
@@ -0,0 +1,26 @@
1#!/usr/bin/env bash
2set -eu -o pipefail
3
4# Initialize main git repo
5git init -q
6git config --local commit.gpgsign false
7git config user.email "test@example.com"
8git config user.name "Test User"
9
10# Create a nested directory structure
11mkdir -p some/path
12TF=$(pwd)/some/path
13
14# Create files in the nested path
15touch "$TF/foo" "$TF/bar" "$TF/blah"
16echo "merry" > "$TF/foo"
17echo "christmas" > "$TF/bar"
18git add some/path/foo some/path/bar
19git commit -q -m "Initial commit"
20
21# Modify a file to create a change
22echo "new year" > "$TF/bar"
23
24# Create a symlink pointing to the nested path
25mkdir -p config
26ln -s "$TF" config/repro_link
gix-status/tests/status/index_as_worktree.rs+28−0
@@ -1128,3 +1128,31 @@ fn racy_git() { · 1127 unmodified lines
1128fn default_pathspec() -> gix_pathspec::Search {1128fn 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]
1133fn 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}