Skip to content

Commit 249e5c2

Browse files
dschoGit for Windows Build Agent
authored andcommitted
Merge pull request #1897 from piscisaureus/symlink-attr
Specify symlink type in .gitattributes
2 parents 36d13bd + 076652c commit 249e5c2

11 files changed

Lines changed: 199 additions & 47 deletions

File tree

Documentation/gitattributes.adoc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,36 @@ sign `$` upon checkout. Any byte sequence that begins with
403403
with `$Id$` upon check-in.
404404

405405

406+
`symlink`
407+
^^^^^^^^^
408+
409+
On Windows, symbolic links have a type: a "file symlink" must point at
410+
a file, and a "directory symlink" must point at a directory. If the
411+
type of symlink does not match its target, it doesn't work.
412+
413+
Git does not record the type of symlink in the index or in a tree. On
414+
checkout it'll guess the type, which only works if the target exists
415+
at the time the symlink is created. This may often not be the case,
416+
for example when the link points at a directory inside a submodule.
417+
418+
The `symlink` attribute allows you to explicitly set the type of symlink
419+
to `file` or `dir`, so Git doesn't have to guess. If you have a set of
420+
symlinks that point at other files, you can do:
421+
422+
------------------------
423+
*.gif symlink=file
424+
------------------------
425+
426+
To tell Git that a symlink points at a directory, use:
427+
428+
------------------------
429+
tools_folder symlink=dir
430+
------------------------
431+
432+
The `symlink` attribute is ignored on platforms other than Windows,
433+
since they don't distinguish between different types of symlinks.
434+
435+
406436
`filter`
407437
^^^^^^^^
408438

apply.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4515,7 +4515,7 @@ static int try_create_file(struct apply_state *state, const char *path,
45154515
/* Although buf:size is counted string, it also is NUL
45164516
* terminated.
45174517
*/
4518-
return !!symlink(buf, path);
4518+
return !!create_symlink(state && state->repo ? state->repo->index : NULL, buf, path);
45194519

45204520
fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666);
45214521
if (fd < 0)

builtin/difftool.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ static int run_dir_diff(struct repository *repo,
544544
}
545545
add_path(&wtdir, wtdir_len, dst_path);
546546
if (dt_options->symlinks) {
547-
if (symlink(wtdir.buf, rdir.buf)) {
547+
if (create_symlink(lstate.istate, wtdir.buf, rdir.buf)) {
548548
ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf);
549549
goto finish;
550550
}

compat/mingw-posix.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,10 @@ int setitimer(int type, struct itimerval *in, struct itimerval *out);
193193
int sigaction(int sig, struct sigaction *in, struct sigaction *out);
194194
int link(const char *oldpath, const char *newpath);
195195
int uname(struct utsname *buf);
196-
int symlink(const char *target, const char *link);
197196
int readlink(const char *path, char *buf, size_t bufsiz);
197+
struct index_state;
198+
int mingw_create_symlink(struct index_state *index, const char *target, const char *link);
199+
#define create_symlink mingw_create_symlink
198200

199201
/*
200202
* replacements of existing functions

compat/mingw.c

Lines changed: 103 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "git-compat-util.h"
55
#include "abspath.h"
66
#include "alloc.h"
7+
#include "attr.h"
78
#include "config.h"
89
#include "dir.h"
910
#include "environment.h"
@@ -454,6 +455,54 @@ static void process_phantom_symlinks(void)
454455
LeaveCriticalSection(&phantom_symlinks_cs);
455456
}
456457

458+
static int create_phantom_symlink(wchar_t *wtarget, wchar_t *wlink)
459+
{
460+
int len;
461+
462+
/* create file symlink */
463+
if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags)) {
464+
errno = err_win_to_posix(GetLastError());
465+
return -1;
466+
}
467+
468+
/* convert to directory symlink if target exists */
469+
switch (process_phantom_symlink(wtarget, wlink)) {
470+
case PHANTOM_SYMLINK_RETRY: {
471+
/* if target doesn't exist, add to phantom symlinks list */
472+
wchar_t wfullpath[MAX_LONG_PATH];
473+
struct phantom_symlink_info *psi;
474+
475+
/* convert to absolute path to be independent of cwd */
476+
len = GetFullPathNameW(wlink, MAX_LONG_PATH, wfullpath, NULL);
477+
if (!len || len >= MAX_LONG_PATH) {
478+
errno = err_win_to_posix(GetLastError());
479+
return -1;
480+
}
481+
482+
/* over-allocate and fill phantom_symlink_info structure */
483+
psi = xmalloc(sizeof(struct phantom_symlink_info) +
484+
sizeof(wchar_t) * (len + wcslen(wtarget) + 2));
485+
psi->wlink = (wchar_t *)(psi + 1);
486+
wcscpy(psi->wlink, wfullpath);
487+
psi->wtarget = psi->wlink + len + 1;
488+
wcscpy(psi->wtarget, wtarget);
489+
490+
EnterCriticalSection(&phantom_symlinks_cs);
491+
psi->next = phantom_symlinks;
492+
phantom_symlinks = psi;
493+
LeaveCriticalSection(&phantom_symlinks_cs);
494+
break;
495+
}
496+
case PHANTOM_SYMLINK_DIRECTORY:
497+
/* if we created a dir symlink, process other phantom symlinks */
498+
process_phantom_symlinks();
499+
break;
500+
default:
501+
break;
502+
}
503+
return 0;
504+
}
505+
457506
/* Normalizes NT paths as returned by some low-level APIs. */
458507
static wchar_t *normalize_ntpath(wchar_t *wbuf)
459508
{
@@ -3223,7 +3272,38 @@ int link(const char *oldpath, const char *newpath)
32233272
return 0;
32243273
}
32253274

3226-
int symlink(const char *target, const char *link)
3275+
enum symlink_type {
3276+
SYMLINK_TYPE_UNSPECIFIED = 0,
3277+
SYMLINK_TYPE_FILE,
3278+
SYMLINK_TYPE_DIRECTORY,
3279+
};
3280+
3281+
static enum symlink_type check_symlink_attr(struct index_state *index, const char *link)
3282+
{
3283+
static struct attr_check *check;
3284+
const char *value;
3285+
3286+
if (!index)
3287+
return SYMLINK_TYPE_UNSPECIFIED;
3288+
3289+
if (!check)
3290+
check = attr_check_initl("symlink", NULL);
3291+
3292+
git_check_attr(index, link, check);
3293+
3294+
value = check->items[0].value;
3295+
if (ATTR_UNSET(value))
3296+
return SYMLINK_TYPE_UNSPECIFIED;
3297+
if (!strcmp(value, "file"))
3298+
return SYMLINK_TYPE_FILE;
3299+
if (!strcmp(value, "dir") || !strcmp(value, "directory"))
3300+
return SYMLINK_TYPE_DIRECTORY;
3301+
3302+
warning(_("ignoring invalid symlink type '%s' for '%s'"), value, link);
3303+
return SYMLINK_TYPE_UNSPECIFIED;
3304+
}
3305+
3306+
int mingw_create_symlink(struct index_state *index, const char *target, const char *link)
32273307
{
32283308
wchar_t wtarget[MAX_LONG_PATH], wlink[MAX_LONG_PATH];
32293309
int len;
@@ -3243,48 +3323,31 @@ int symlink(const char *target, const char *link)
32433323
if (wtarget[len] == '/')
32443324
wtarget[len] = '\\';
32453325

3246-
/* create file symlink */
3247-
if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags)) {
3248-
errno = err_win_to_posix(GetLastError());
3249-
return -1;
3250-
}
3251-
3252-
/* convert to directory symlink if target exists */
3253-
switch (process_phantom_symlink(wtarget, wlink)) {
3254-
case PHANTOM_SYMLINK_RETRY: {
3255-
/* if target doesn't exist, add to phantom symlinks list */
3256-
wchar_t wfullpath[MAX_PATH];
3257-
struct phantom_symlink_info *psi;
3258-
3259-
/* convert to absolute path to be independent of cwd */
3260-
len = GetFullPathNameW(wlink, MAX_PATH, wfullpath, NULL);
3261-
if (!len || len >= MAX_PATH) {
3262-
errno = err_win_to_posix(GetLastError());
3263-
return -1;
3264-
}
3265-
3266-
/* over-allocate and fill phantom_symlink_info structure */
3267-
psi = xmalloc(sizeof(struct phantom_symlink_info)
3268-
+ sizeof(wchar_t) * (len + wcslen(wtarget) + 2));
3269-
psi->wlink = (wchar_t *)(psi + 1);
3270-
wcscpy(psi->wlink, wfullpath);
3271-
psi->wtarget = psi->wlink + len + 1;
3272-
wcscpy(psi->wtarget, wtarget);
3273-
3274-
EnterCriticalSection(&phantom_symlinks_cs);
3275-
psi->next = phantom_symlinks;
3276-
phantom_symlinks = psi;
3277-
LeaveCriticalSection(&phantom_symlinks_cs);
3278-
break;
3279-
}
3280-
case PHANTOM_SYMLINK_DIRECTORY:
3281-
/* if we created a dir symlink, process other phantom symlinks */
3326+
switch (check_symlink_attr(index, link)) {
3327+
case SYMLINK_TYPE_UNSPECIFIED:
3328+
/* Create a phantom symlink: it is initially created as a file
3329+
* symlink, but may change to a directory symlink later if/when
3330+
* the target exists. */
3331+
return create_phantom_symlink(wtarget, wlink);
3332+
case SYMLINK_TYPE_FILE:
3333+
if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags))
3334+
break;
3335+
return 0;
3336+
case SYMLINK_TYPE_DIRECTORY:
3337+
if (!CreateSymbolicLinkW(wlink, wtarget,
3338+
symlink_directory_flags))
3339+
break;
3340+
/* There may be dangling phantom symlinks that point at this
3341+
* one, which should now morph into directory symlinks. */
32823342
process_phantom_symlinks();
3283-
break;
3343+
return 0;
32843344
default:
3285-
break;
3345+
BUG("unhandled symlink type");
32863346
}
3287-
return 0;
3347+
3348+
/* CreateSymbolicLinkW failed. */
3349+
errno = err_win_to_posix(GetLastError());
3350+
return -1;
32883351
}
32893352

32903353
int readlink(const char *path, char *buf, size_t bufsiz)

entry.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca
324324
if (!has_symlinks || to_tempfile)
325325
goto write_file_entry;
326326

327-
ret = symlink(new_blob, path);
327+
ret = create_symlink(state->istate, new_blob, path);
328328
free(new_blob);
329329
if (ret)
330330
return error_errno("unable to create symlink %s", path);

git-compat-util.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,16 @@ static inline int git_has_dir_sep(const char *path)
359359
#define is_mount_point is_mount_point_via_stat
360360
#endif
361361

362+
#ifndef create_symlink
363+
struct index_state;
364+
static inline int git_create_symlink(struct index_state *index UNUSED,
365+
const char *target, const char *link)
366+
{
367+
return symlink(target, link);
368+
}
369+
#define create_symlink git_create_symlink
370+
#endif
371+
362372
#ifndef query_user_email
363373
#define query_user_email() NULL
364374
#endif

refs/files-backend.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2119,7 +2119,7 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target)
21192119

21202120
ref_path = get_locked_file_path(&lock->lk);
21212121
unlink(ref_path);
2122-
ret = symlink(target, ref_path);
2122+
ret = create_symlink(NULL, target, ref_path);
21232123
free(ref_path);
21242124

21252125
if (ret)

setup.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2309,7 +2309,7 @@ static void copy_templates_1(struct repository *repo,
23092309
if (strbuf_readlink(&lnk, template_path->buf,
23102310
st_template.st_size) < 0)
23112311
die_errno(_("cannot readlink '%s'"), template_path->buf);
2312-
if (symlink(lnk.buf, path->buf))
2312+
if (create_symlink(NULL, lnk.buf, path->buf))
23132313
die_errno(_("cannot symlink '%s' '%s'"),
23142314
lnk.buf, path->buf);
23152315
strbuf_release(&lnk);
@@ -2591,7 +2591,7 @@ static int create_default_files(struct repository *repo,
25912591
repo_git_path_replace(repo, &path, "tXXXXXX");
25922592
if (!close(xmkstemp(path.buf)) &&
25932593
!unlink(path.buf) &&
2594-
!symlink("testing", path.buf) &&
2594+
!create_symlink(NULL, "testing", path.buf) &&
25952595
!lstat(path.buf, &st1) &&
25962596
S_ISLNK(st1.st_mode))
25972597
unlink(path.buf); /* good */

t/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ integration_tests = [
276276
't2027-checkout-track.sh',
277277
't2030-unresolve-info.sh',
278278
't2031-checkout-long-paths.sh',
279+
't2040-checkout-symlink-attr.sh',
279280
't2050-git-dir-relative.sh',
280281
't2060-switch.sh',
281282
't2070-restore.sh',

0 commit comments

Comments
 (0)