commit 64ae8493022d713e2b5f69d829d7b26213f01da4 from: Stefan Sperling date: Sun Jul 13 10:11:54 2025 UTC do not clobber changes staged via stage -p during 'got revert' While we must install staged blob contents during revert if there are staged changes, the base blob ID recorded in the file index must always reflect the actual base blob from the base commit. Setting the base blob ID to that of a staged blob causes problems. The staged blob's ID is tracked in a separate field. ok op@ commit - 2f46ecd27c3d285e0a82670ff8dc042db5e58d5d commit + 64ae8493022d713e2b5f69d829d7b26213f01da4 blob - 42e673a56bd12ea603762da85d77d62677e35aaf blob + e229bb2a3ce1e8dedaa1b6d9cbb7ee90751a6673 --- lib/worktree.c +++ lib/worktree.c @@ -5389,11 +5389,21 @@ revert_file(void *arg, unsigned char status, unsigned goto done; } } - err = got_fileindex_entry_update(ie, - a->worktree->root_fd, relpath, - &blob->id, &ie->commit, 0); - if (err) - goto done; + if (staged_status == GOT_STATUS_ADD || + staged_status == GOT_STATUS_MODIFY) { + got_fileindex_entry_get_blob_id(&id, ie); + err = got_fileindex_entry_update(ie, + a->worktree->root_fd, relpath, &id, + &ie->commit, 0); + if (err) + goto done; + } else { + err = got_fileindex_entry_update(ie, + a->worktree->root_fd, relpath, &blob->id, + &ie->commit, 0); + if (err) + goto done; + } } else { int is_bad_symlink = 0; if (te && S_ISLNK(te->mode)) { @@ -5411,11 +5421,22 @@ revert_file(void *arg, unsigned char status, unsigned } if (err) goto done; - err = got_fileindex_entry_update(ie, - a->worktree->root_fd, relpath, - &blob->id, &ie->commit, 0); - if (err) - goto done; + + if (staged_status == GOT_STATUS_ADD || + staged_status == GOT_STATUS_MODIFY) { + got_fileindex_entry_get_blob_id(&id, ie); + err = got_fileindex_entry_update(ie, + a->worktree->root_fd, relpath, &id, + &ie->commit, 0); + if (err) + goto done; + } else { + err = got_fileindex_entry_update(ie, + a->worktree->root_fd, relpath, &blob->id, + &ie->commit, 0); + if (err) + goto done; + } if (is_bad_symlink) { got_fileindex_entry_filetype_set(ie, GOT_FILEIDX_MODE_BAD_SYMLINK); blob - cc12859f70df97800a252ad662cc906397252208 blob + 80353942463567fdee376f1cbfd018b1513b4539 --- regress/cmdline/revert.sh +++ regress/cmdline/revert.sh @@ -1864,7 +1864,214 @@ test_revert_patch_binary() { test_done "$testroot" 0 } + +test_revert_staged_file() { + local testroot=`test_init revert_staged_file` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done "$testroot" "$ret" + return 1 + fi + + echo "line 0" > $testroot/wt/epsilon/zeta + for i in `seq 1 10`; do + echo "line $i" >> $testroot/wt/epsilon/zeta + done + + (cd $testroot/wt && got commit -m 'make zeta a multi-line file' \ + > /dev/null) + local commit_id=`git_show_head $testroot/repo` + + sed -i -e 's/line 0/line 0a/' $testroot/wt/epsilon/zeta + sed -i -e 's/line 4/line 4a/' $testroot/wt/epsilon/zeta + sed -i -e 's/line 6/line 6a/' $testroot/wt/epsilon/zeta + + # stage line 0 and line 6 + printf "y\n" > $testroot/patchscript + for i in `seq 1 5`; do + printf "n\n" >> $testroot/patchscript + done + printf "y\n" >> $testroot/patchscript + for i in `seq 7 10`; do + printf "n\n" >> $testroot/patchscript + done + + (cd $testroot/wt && got stage > /dev/null) + + echo ' M epsilon/zeta' > $testroot/stdout.expected + + (cd $testroot/wt && got status > $testroot/stdout) + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + blobid_zeta=$(get_blob_id $testroot/repo epsilon zeta) + stageid_zeta=$(cd $testroot/wt && got stage -l epsilon/zeta | cut -d' ' -f 1) + + (cd $testroot/wt && got revert epsilon/zeta > $testroot/stdout) + + echo -n '' > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo ' M epsilon/zeta' > $testroot/stdout.expected + (cd $testroot/wt && got status > $testroot/stdout) + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + cat <<-eof >$testroot/stdout.expected + diff -s $testroot/wt + path + $testroot/wt (staged changes) + commit - $commit_id + blob - $blobid_zeta + blob + $stageid_zeta + --- epsilon/zeta + +++ epsilon/zeta + @@ -1,10 +1,10 @@ + -line 0 + +line 0a + line 1 + line 2 + line 3 + -line 4 + +line 4a + line 5 + -line 6 + +line 6a + line 7 + line 8 + line 9 + eof + + (cd $testroot/wt && got diff -s > $testroot/stdout) + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + test_done "$testroot" "$ret" +} + +test_revert_partially_staged_file() { + local testroot=`test_init revert_partially_staged_file` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done "$testroot" "$ret" + return 1 + fi + + echo "line 0" > $testroot/wt/epsilon/zeta + for i in `seq 1 10`; do + echo "line $i" >> $testroot/wt/epsilon/zeta + done + + (cd $testroot/wt && got commit -m 'make zeta a multi-line file' \ + > /dev/null) + local commit_id=`git_show_head $testroot/repo` + + sed -i -e 's/line 0/line 0a/' $testroot/wt/epsilon/zeta + sed -i -e 's/line 4/line 4a/' $testroot/wt/epsilon/zeta + sed -i -e 's/line 6/line 6a/' $testroot/wt/epsilon/zeta + + # stage line 0 and line 6 + printf "y\n" > $testroot/patchscript + for i in `seq 1 5`; do + printf "n\n" >> $testroot/patchscript + done + printf "y\n" >> $testroot/patchscript + for i in `seq 7 10`; do + printf "n\n" >> $testroot/patchscript + done + + (cd $testroot/wt && got stage -F $testroot/patchscript -p > /dev/null) + + echo 'MM epsilon/zeta' > $testroot/stdout.expected + + (cd $testroot/wt && got status > $testroot/stdout) + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + blobid_zeta=$(get_blob_id $testroot/repo epsilon zeta) + stageid_zeta=$(cd $testroot/wt && got stage -l epsilon/zeta | \ + cut -d' ' -f 1) + + (cd $testroot/wt && got revert epsilon/zeta > $testroot/stdout) + + echo 'R epsilon/zeta' > $testroot/stdout.expected + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo ' M epsilon/zeta' > $testroot/stdout.expected + (cd $testroot/wt && got status > $testroot/stdout) + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + cat <<-eof >$testroot/stdout.expected + diff -s $testroot/wt + path + $testroot/wt (staged changes) + commit - $commit_id + blob - $blobid_zeta + blob + $stageid_zeta + --- epsilon/zeta + +++ epsilon/zeta + @@ -1,4 +1,4 @@ + -line 0 + +line 0a + line 1 + line 2 + line 3 + eof + + (cd $testroot/wt && got diff -s > $testroot/stdout) + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + test_done "$testroot" "$ret" +} + test_parseargs "$@" run_test test_revert_basic run_test test_revert_rm @@ -1885,3 +2092,5 @@ run_test test_revert_symlink run_test test_revert_patch_symlink run_test test_revert_umask run_test test_revert_patch_binary +run_test test_revert_staged_file +run_test test_revert_partially_staged_file