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
570570 let out = if is_symlink && self.core_symlinks {
571571 // conversion to bstr can never fail because symlinks are only used
572572 // 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+ ));
575576 self.buf.extend_from_slice(&symlink_path);
576577 self.worktree_bytes.fetch_add(self.buf.len() as u64, Ordering::Relaxed);
577578 Stream {
gix-status/tests/fixtures/status_broken_symlink.sh+17−0
@@ -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
gix-status/tests/fixtures/status_symlink_submodule.sh+26−0
@@ -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
gix-status/tests/status/index_as_worktree.rs+28−0
@@ -1128,3 +1128,31 @@ fn racy_git() { · 1127 unmodified lines
11281128 fn default_pathspec() -> gix_pathspec::Search {
11291129 gix_pathspec::Search::from_specs(to_pathspecs(&[]), None, std::path::Path::new("")).expect("empty is always valid")
11301130 }
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+}