Skip to content

Commit fab78a0

Browse files
matheustavaresgitster
authored andcommitted
checkout: don't follow symlinks when removing entries
At 1d718a5 ("do not overwrite untracked symlinks", 2011-02-20), symlink.c:check_leading_path() started returning different codes for FL_ENOENT and FL_SYMLINK. But one of its callers, unlink_entry(), was not adjusted for this change, so it started to follow symlinks on the leading path of to-be-removed entries. Fix that and add a regression test. Note that since 1d718a5 check_leading_path() no longer differentiates the case where it found a symlink in the path's leading components from the cases where it found a regular file or failed to lstat() the component. So, a side effect of this current patch is that unlink_entry() now returns early in all of these three cases. And because we no longer try to unlink such paths, we also don't get the warning from remove_or_warn(). For the regular file and symlink cases, it's questionable whether the warning was useful in the first place: unlink_entry() removes tracked paths that should no longer be present in the state we are checking out to. If the path had its leading dir replaced by another file, it means that the basename already doesn't exist, so there is no need for a warning. Sure, we are leaving a regular file or symlink behind at the path's dirname, but this file is either untracked now (so again, no need to warn), or it will be replaced by a tracked file during the next phase of this checkout operation. As for failing to lstat() one of the leading components, the basename might still exist only we cannot unlink it (e.g. due to the lack of the required permissions). Since the user expect it to be removed (especially with checkout's --no-overlay option), add back the warning in this more relevant case. Signed-off-by: Matheus Tavares <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 462b4e8 commit fab78a0

File tree

5 files changed

+37
-11
lines changed

5 files changed

+37
-11
lines changed

cache.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1744,7 +1744,7 @@ static inline void cache_def_clear(struct cache_def *cache)
17441744

17451745
int has_symlink_leading_path(const char *name, int len);
17461746
int threaded_has_symlink_leading_path(struct cache_def *, const char *, int);
1747-
int check_leading_path(const char *name, int len);
1747+
int check_leading_path(const char *name, int len, int warn_on_lstat_err);
17481748
int has_dirs_only_path(const char *name, int len, int prefix_len);
17491749
void invalidate_lstat_cache(void);
17501750
void schedule_dir_for_removal(const char *name, int len);

entry.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ void unlink_entry(const struct cache_entry *ce)
530530
submodule_move_head(ce->name, "HEAD", NULL,
531531
SUBMODULE_MOVE_HEAD_FORCE);
532532
}
533-
if (!check_leading_path(ce->name, ce_namelen(ce)))
533+
if (check_leading_path(ce->name, ce_namelen(ce), 1) >= 0)
534534
return;
535535
if (remove_or_warn(ce->ce_mode, ce->name))
536536
return;

symlinks.c

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "cache.h"
22

3-
static int threaded_check_leading_path(struct cache_def *cache, const char *name, int len);
3+
static int threaded_check_leading_path(struct cache_def *cache, const char *name,
4+
int len, int warn_on_lstat_err);
45
static int threaded_has_dirs_only_path(struct cache_def *cache, const char *name, int len, int prefix_len);
56

67
/*
@@ -72,7 +73,7 @@ static int lstat_cache_matchlen(struct cache_def *cache,
7273
int prefix_len_stat_func)
7374
{
7475
int match_len, last_slash, last_slash_dir, previous_slash;
75-
int save_flags, ret;
76+
int save_flags, ret, saved_errno = 0;
7677
struct stat st;
7778

7879
if (cache->track_flags != track_flags ||
@@ -139,6 +140,7 @@ static int lstat_cache_matchlen(struct cache_def *cache,
139140

140141
if (ret) {
141142
*ret_flags = FL_LSTATERR;
143+
saved_errno = errno;
142144
if (errno == ENOENT)
143145
*ret_flags |= FL_NOENT;
144146
} else if (S_ISDIR(st.st_mode)) {
@@ -180,6 +182,8 @@ static int lstat_cache_matchlen(struct cache_def *cache,
180182
} else {
181183
reset_lstat_cache(cache);
182184
}
185+
if (saved_errno)
186+
errno = saved_errno;
183187
return match_len;
184188
}
185189

@@ -207,9 +211,10 @@ int has_symlink_leading_path(const char *name, int len)
207211
return threaded_has_symlink_leading_path(&default_cache, name, len);
208212
}
209213

210-
int check_leading_path(const char *name, int len)
214+
int check_leading_path(const char *name, int len, int warn_on_lstat_err)
211215
{
212-
return threaded_check_leading_path(&default_cache, name, len);
216+
return threaded_check_leading_path(&default_cache, name, len,
217+
warn_on_lstat_err);
213218
}
214219

215220
/*
@@ -218,19 +223,28 @@ int check_leading_path(const char *name, int len)
218223
* Return -1 if leading path exists and is a directory.
219224
*
220225
* Return the length of a leading component if it either exists but it's not a
221-
* directory, or if we were unable to lstat() it.
226+
* directory, or if we were unable to lstat() it. If warn_on_lstat_err is true,
227+
* also emit a warning for this error.
222228
*/
223-
static int threaded_check_leading_path(struct cache_def *cache, const char *name, int len)
229+
static int threaded_check_leading_path(struct cache_def *cache, const char *name,
230+
int len, int warn_on_lstat_err)
224231
{
225232
int flags;
226233
int match_len = lstat_cache_matchlen(cache, name, len, &flags,
227234
FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT);
235+
int saved_errno = errno;
236+
228237
if (flags & FL_NOENT)
229238
return 0;
230239
else if (flags & FL_DIR)
231240
return -1;
232-
else
233-
return match_len;
241+
if (warn_on_lstat_err && (flags & FL_LSTATERR)) {
242+
char *path = xmemdupz(name, match_len);
243+
errno = saved_errno;
244+
warning_errno(_("failed to lstat '%s'"), path);
245+
free(path);
246+
}
247+
return match_len;
234248
}
235249

236250
int has_dirs_only_path(const char *name, int len, int prefix_len)

t/t2021-checkout-overwrite.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,16 @@ test_expect_success SYMLINKS 'the symlink remained' '
5151
test -h a/b
5252
'
5353

54+
test_expect_success SYMLINKS 'checkout -f must not follow symlinks when removing entries' '
55+
git checkout -f start &&
56+
mkdir dir &&
57+
>dir/f &&
58+
git add dir/f &&
59+
git commit -m "add dir/f" &&
60+
mv dir untracked &&
61+
ln -s untracked dir &&
62+
git checkout -f HEAD~ &&
63+
test_path_is_file untracked/f
64+
'
65+
5466
test_done

unpack-trees.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2098,7 +2098,7 @@ static int verify_absent_1(const struct cache_entry *ce,
20982098
if (o->index_only || o->reset || !o->update)
20992099
return 0;
21002100

2101-
len = check_leading_path(ce->name, ce_namelen(ce));
2101+
len = check_leading_path(ce->name, ce_namelen(ce), 0);
21022102
if (!len)
21032103
return 0;
21042104
else if (len > 0) {

0 commit comments

Comments
 (0)