diff-lib: ignore paths that are outside $cwd if --relative asked

For diff family commands, we can tell them to exclude changes outside of some directories if --relative is requested. In diff_unmerge(), NULL will be returned if the requested path is outside of the interesting directories, thus we'll run into NULL pointer dereference in run_diff_files when trying to dereference its return value. Checking for return value of diff_unmerge before dereferencing is not sufficient, though. Since, diff engine will try to work on such pathspec later. Let's not run diff on those unintesting entries, instead. As a side effect, by skipping like that, we can save some CPU cycles. Reported-by: Thomas De Zeeuw <thomas@slight.dev> Tested-by: Carlo Arenas <carenas@gmail.com> Signed-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Đoàn Trần Công Danh committed Aug 22, 2021 at 15:49 UTC 8174627b3d32dc80cb477b3fa46971955f26c6b2
2 files changed +57
diff-lib.c
+4
@@ -116,6 +116,10 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
116 if (!ce_path_match(istate, ce, &revs->prune_data, NULL))
117 continue;
118
119 + if (revs->diffopt.prefix &&
120 + strncmp(ce->name, revs->diffopt.prefix, revs->diffopt.prefix_length))
121 + continue;
122 +
123 if (ce_stage(ce)) {
124 struct combine_diff_path *dpath;
125 struct diff_filepair *pair;
t/t4045-diff-relative.sh
+53
@@ -162,4 +162,57 @@ check_diff_relative_option subdir file2 true --no-relative --relative
162 check_diff_relative_option . file2 false --no-relative --relative=subdir
163 check_diff_relative_option . file2 true --no-relative --relative=subdir
164
165 +test_expect_success 'setup diff --relative unmerged' '
166 + test_commit zero file0 &&
167 + test_commit base subdir/file0 &&
168 + git switch -c br1 &&
169 + test_commit one file0 &&
170 + test_commit sub1 subdir/file0 &&
171 + git switch -c br2 base &&
172 + test_commit two file0 &&
173 + git switch -c br3 &&
174 + test_commit sub3 subdir/file0
175 +'
176 +
177 +test_expect_success 'diff --relative without change in subdir' '
178 + git switch br2 &&
179 + test_when_finished "git merge --abort" &&
180 + test_must_fail git merge one &&
181 + git -C subdir diff --relative >out &&
182 + test_must_be_empty out &&
183 + git -C subdir diff --relative --name-only >out &&
184 + test_must_be_empty out
185 +'
186 +
187 +test_expect_success 'diff --relative --name-only with change in subdir' '
188 + git switch br3 &&
189 + test_when_finished "git merge --abort" &&
190 + test_must_fail git merge sub1 &&
191 + test_write_lines file0 file0 >expected &&
192 + git -C subdir diff --relative --name-only >out &&
193 + test_cmp expected out
194 +'
195 +
196 +test_expect_failure 'diff --relative with change in subdir' '
197 + git switch br3 &&
198 + br1_blob=$(git rev-parse --short --verify br1:subdir/file0) &&
199 + br3_blob=$(git rev-parse --short --verify br3:subdir/file0) &&
200 + test_when_finished "git merge --abort" &&
201 + test_must_fail git merge br1 &&
202 + cat >expected <<-EOF &&
203 + diff --cc file0
204 + index $br3_blob,$br1_blob..0000000
205 + --- a/file0
206 + +++ b/file0
207 + @@@ -1,1 -1,1 +1,5 @@@
208 + ++<<<<<<< HEAD
209 + +sub3
210 + ++=======
211 + + sub1
212 + ++>>>>>>> br1
213 + EOF
214 + git -C subdir diff --relative >out &&
215 + test_cmp expected out
216 +'
217 +
218 test_done