Download raw body.
Got publishes local unversioned bytes during merge and rebase
Got publishes local unversioned bytes during merge and rebase
Got publishes local unversioned bytes during merge and rebase
Hi Stefan,
First, I sincerely apologize for sending such a long report. You were right
that a concise shell regression test would have been much more useful. I was
trying to provide detailed source and impact information, but I should have
kept the initial report focused. Although some of the report text was
LLM-assisted, I manually reviewed the report and all attachment materials
carefully, checked the relevant source, and reproduced the reported behavior
myself.
Thank you for acknowledging the issue and for preparing the proposed test
and
fix. I replayed it against Got 0.128. It blocks the regular-file and ignored
regular-file cases, but I found two residual cases:
- With the patch as written, a local symlink to a directory is followed by
`stat()`, reported as obstructed, and `got merge` still advances
`refs/heads/main` while omitting the incoming path from the merge tree.
- A local unversioned directory produces the same result.
Changing `stat()` to `lstat()` blocks the symlink case, but the directory
case
remains because merge, rebase, and histedit do not treat `upa.obstructed`
as a
stop condition. I suggest using `lstat()` and adding `upa.obstructed > 0` to
those postpone guards. I also recommend regression tests for symlink-to-
directory and directory collisions that assert that the target ref does not
move and that the incoming tree entry is not silently omitted.
So I believe the original issue is addressed for regular files, but the
current patch still has a history-integrity residual for obstructing
directories and symlinks. I have recorded the replay details and exact
outcomes, and I can provide a shorter regression-test-oriented patch if that
would be useful.
Best regards,
Yann
On Sun, Sep 20, 2026 at 2:53 AM Stefan Sperling <stsp@stsp.name> wrote:
> On Sat, Sep 19, 2026 at 10:30:45AM +0200, Stefan Sperling wrote:
> > On Fri, Sep 18, 2026 at 10:40:23AM +0800, Yann Lorwyn wrote:
> > > Hello,
> > >
> > > I am writing to report a potential bug int Got 0.128.
> > >
> > > Got 0.128 can publish a local unversioned path when an incoming merge
> or
> > > rebase adds the same pathname. The operation succeeds and can leave a
> clean
> > > work tree, but the resulting tree contains the local file, ignored
> file, or
> > > symlink rather than the incoming entry. The ignored-file case can
> publish
> > > local generated or sensitive bytes without an explicit add or conflict
> > > resolution.
> > >
> > > The attached report contains the source analysis and Git-core
> comparison.
> > > attachments.zip contains a shell reproducer that starts from fresh Got
> > > repositories and builds the pinned 0.128 source.
> > >
> > > Best regards,
> > > Yann
> >
> > Thanks, this sounds like a valid issue.
> >
> > Your report is much too detailed, you could have simply written about
> > 50 lines of shell script instead. Could you please do that by adding
> > a regression test for this problem to our test suite?
>
> Yann, I am sorry about my slightly annoyed initial response.
>
> The summary of the problem you wrote above is in fact excellent.
> It's just that being asked to look at walls of LLM generated text irritates
> me. I could have ignored those parts since you already provided a good
> summary of everything that needed to be said.
>
> If you end up finding and reporting more issues like this, feel free to
> omit
> the LLM output. I prefer to only receive a description of the problem you
> have
> written yourself. If you are able to include a new test for our test suite,
> similar to what I am adding in the patch below, that would be perfect.
> But I would rather receive a report without a test than no report at all,
> of course. In this case, it took me about 10 minutes to write a test for
> our test suite myself, which is not too bad. Sometimes it takes more time.
>
> With that out of the way, below are the test changes and a proposed fix.
> Does this work for you?
>
> Apart from the new test I've added, it turns out there is an existing test
> in histedit.sh which deliberately triggers the unversioned-added file merge
> situation while testing for an unrelated bug we have fixed. This test has
> been adjusted to expect the new behaviour.
>
>
> do not add unversioned files to version control if a file addition is
> merged
>
> Problem reported by Yann Lorwyn
>
> M lib/worktree.c | 15+ 0-
> M regress/cmdline/histedit.sh | 17+ 6-
> M regress/cmdline/merge.sh | 91+ 0-
>
> 3 files changed, 123 insertions(+), 6 deletions(-)
>
> commit - cb8fe661ec8c00388527dbc4d2744bc511c3cc62
> commit + 9d912e77fc9355855259a24e44c4fcb3a146faad
> blob - b3c50d7a852f627a1de9da592bcf0d73dcdd5ac4
> blob + 46fbbdf95999e8f764ffb85ca71d9f85375c7143
> --- lib/worktree.c
> +++ lib/worktree.c
> @@ -3345,6 +3345,21 @@ merge_file_cb(void *arg, struct got_blob_object
> *blob1
> goto done;
> }
> } else {
> + if (stat(ondisk_path, &sb) == -1) {
> + if (errno != ENOENT && errno != ENOTDIR) {
> + err = got_error_from_errno2("stat",
> + ondisk_path);
> + goto done;
> + }
> + } else {
> + if (S_ISREG(sb.st_mode) ||
> S_ISLNK(sb.st_mode))
> + status = GOT_STATUS_UNVERSIONED;
> + else
> + status = GOT_STATUS_OBSTRUCTED;
> + err = (*a->progress_cb)(a->progress_arg,
> + status, path2);
> + goto done;
> + }
> err = add_file(a->worktree, a->fileindex, NULL,
> ondisk_path, path2, blob2, mode2,
> 0, 0, 1, a->allow_bad_symlinks,
> blob - 1d993e2f9340895b63648eeed66a23d0497eaa8a
> blob + c936996937241a7920b5ca639a7275d88af88a1c
> --- regress/cmdline/histedit.sh
> +++ regress/cmdline/histedit.sh
> @@ -3030,17 +3030,17 @@ test_histedit_added_file() {
> # +2f81c7988242 -> no-op change: add new file
> echo new > $testroot/wt/new
>
> - (cd $testroot/wt && got histedit -f > $testroot/stdout)
> + (cd $testroot/wt && got histedit -f > $testroot/stdout \
> + 2> $testroot/stderr)
> local new_commit=`git_show_head $testroot/repo`
>
> local short_commit2=`trim_obj_id 12 $commit2`
> local short_new_commit=`trim_obj_id 12 $new_commit`
>
> - echo "A new" > $testroot/stdout.expected
> - echo "$short_commit2 -> $short_new_commit: add new file" \
> - >> $testroot/stdout.expected
> - echo "Switching work tree to refs/heads/master" \
> - >> $testroot/stdout.expected
> + cat > $testroot/stdout.expected <<EOF
> +? new
> +Files not merged because an unversioned file was found in the work tree: 1
> +EOF
>
> cmp -s $testroot/stdout.expected $testroot/stdout
> ret=$?
> @@ -3050,6 +3050,17 @@ test_histedit_added_file() {
> return 1
> fi
>
> + cat > $testroot/stderr.expected <<EOF
> +got: changes destined for some files were not yet merged and should be
> merged manually if required before the histedit operation is continued
> +EOF
> + cmp -s $testroot/stderr.expected $testroot/stderr
> + ret=$?
> + if [ $ret -ne 0 ]; then
> + diff -u $testroot/stderr.expected $testroot/stderr
> + test_done "$testroot" "$ret"
> + return 1
> + fi
> +
> test_done "$testroot" "$ret"
> }
>
> blob - 576400c021a6d57ecea5c86d8e2478d39b7be3b9
> blob + 2721a8cce3d6dc8ef3aaab0b48f987cd7f67e2de
> --- regress/cmdline/merge.sh
> +++ regress/cmdline/merge.sh
> @@ -2331,6 +2331,96 @@ EOF
> test_done "$testroot" "$ret"
> }
>
> +test_merge_adds_ignored_path() {
> + local testroot=`test_init merge_adds_ignored_path`
> + local commit0=`git_show_head $testroot/repo`
> + local commit0_author_time=`git_show_author_time $testroot/repo`
> +
> + git -C $testroot/repo checkout -q -b newbranch
> + echo "new file on branch" > $testroot/repo/gamma/new
> + git -C $testroot/repo add gamma/new > /dev/null
> + git_commit $testroot/repo -m "committing to new file on newbranch"
> + local branch_commit=`git_show_branch_head $testroot/repo newbranch`
> +
> + git -C $testroot/repo checkout -q master
> + echo gamma/new > $testroot/repo/.gitignore
> + git -C $testroot/repo add .gitignore > /dev/null
> + echo "ignoring new file on master branch" >
> $testroot/repo/epsilon/zeta
> + git_commit $testroot/repo -m "committing to zeta on master"
> + local master_commit=`git_show_head $testroot/repo`
> +
> + got checkout -b master $testroot/repo $testroot/wt > /dev/null
> + ret=$?
> + if [ $ret -ne 0 ]; then
> + echo "got checkout failed unexpectedly" >&2
> + test_done "$testroot" "$ret"
> + return 1
> + fi
> +
> + echo 'this file is being ignored' > $testroot/wt/gamma/new
> +
> + (cd $testroot/wt && got status > $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
> +
> + (cd $testroot/wt && got status -I > $testroot/stdout)
> +
> + cat > $testroot/stdout.expected <<EOF
> +? gamma/new
> +EOF
> + 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 > $testroot/stderr.expected <<EOF
> +got: changes destined for some files were not yet merged and should be
> merged manually if required before the merge operation is continued
> +EOF
> + (cd $testroot/wt && got merge newbranch \
> + > $testroot/stdout 2> $testroot/stderr)
> + ret=$?
> + if [ $ret -eq 0 ]; then
> + echo "got merge succeeded unexpectedly" >&2
> + test_done "$testroot" "1"
> + return 1
> + fi
> +
> + local merge_commit=`git_show_head $testroot/repo`
> +
> + cat > $testroot/stdout.expected <<EOF
> +? gamma/new
> +Files not merged because an unversioned file was found in the work tree: 1
> +EOF
> + 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
> +
> + cmp -s $testroot/stderr.expected $testroot/stderr
> + ret=$?
> + if [ $ret -ne 0 ]; then
> + diff -u $testroot/stderr.expected $testroot/stderr
> + test_done "$testroot" "$ret"
> + return 1
> + fi
> +
> + test_done "$testroot" "$ret"
> +}
> +
> test_parseargs "$@"
> run_test test_merge_basic
> run_test test_merge_forward
> @@ -2352,3 +2442,4 @@ run_test test_merge_fetched_branch
> run_test test_merge_fetched_branch_remote
> run_test test_merge_tag
> run_test test_merge_tag_abort
> +run_test test_merge_adds_ignored_path
>
Got publishes local unversioned bytes during merge and rebase
Got publishes local unversioned bytes during merge and rebase
Got publishes local unversioned bytes during merge and rebase