Skip to content

Commit 466192f

Browse files
piscisaureusGit for Windows Build Agent
authored andcommitted
mingw: allow to specify the symlink type in .gitattributes
On Windows, symbolic links have a type: a "file symlink" must point at a file, and a "directory symlink" must point at a directory. If the type of symlink does not match its target, it doesn't work. Git does not record the type of symlink in the index or in a tree. On checkout it'll guess the type, which only works if the target exists at the time the symlink is created. This may often not be the case, for example when the link points at a directory inside a submodule. By specifying `symlink=file` or `symlink=dir` the user can specify what type of symlink Git should create, so Git doesn't have to rely on unreliable heuristics. Signed-off-by: Bert Belder <bertbelder@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1 parent 2f2338f commit 466192f

2 files changed

Lines changed: 88 additions & 2 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

compat/mingw.c

Lines changed: 58 additions & 2 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"
@@ -3274,7 +3275,38 @@ int link(const char *oldpath, const char *newpath)
32743275
return 0;
32753276
}
32763277

3277-
int mingw_create_symlink(struct index_state *index UNUSED, const char *target, const char *link)
3278+
enum symlink_type {
3279+
SYMLINK_TYPE_UNSPECIFIED = 0,
3280+
SYMLINK_TYPE_FILE,
3281+
SYMLINK_TYPE_DIRECTORY,
3282+
};
3283+
3284+
static enum symlink_type check_symlink_attr(struct index_state *index, const char *link)
3285+
{
3286+
static struct attr_check *check;
3287+
const char *value;
3288+
3289+
if (!index)
3290+
return SYMLINK_TYPE_UNSPECIFIED;
3291+
3292+
if (!check)
3293+
check = attr_check_initl("symlink", NULL);
3294+
3295+
git_check_attr(index, link, check);
3296+
3297+
value = check->items[0].value;
3298+
if (ATTR_UNSET(value))
3299+
return SYMLINK_TYPE_UNSPECIFIED;
3300+
if (!strcmp(value, "file"))
3301+
return SYMLINK_TYPE_FILE;
3302+
if (!strcmp(value, "dir") || !strcmp(value, "directory"))
3303+
return SYMLINK_TYPE_DIRECTORY;
3304+
3305+
warning(_("ignoring invalid symlink type '%s' for '%s'"), value, link);
3306+
return SYMLINK_TYPE_UNSPECIFIED;
3307+
}
3308+
3309+
int mingw_create_symlink(struct index_state *index, const char *target, const char *link)
32783310
{
32793311
wchar_t wtarget[MAX_LONG_PATH], wlink[MAX_LONG_PATH];
32803312
int len;
@@ -3294,7 +3326,31 @@ int mingw_create_symlink(struct index_state *index UNUSED, const char *target, c
32943326
if (wtarget[len] == '/')
32953327
wtarget[len] = '\\';
32963328

3297-
return create_phantom_symlink(wtarget, wlink);
3329+
switch (check_symlink_attr(index, link)) {
3330+
case SYMLINK_TYPE_UNSPECIFIED:
3331+
/* Create a phantom symlink: it is initially created as a file
3332+
* symlink, but may change to a directory symlink later if/when
3333+
* the target exists. */
3334+
return create_phantom_symlink(wtarget, wlink);
3335+
case SYMLINK_TYPE_FILE:
3336+
if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags))
3337+
break;
3338+
return 0;
3339+
case SYMLINK_TYPE_DIRECTORY:
3340+
if (!CreateSymbolicLinkW(wlink, wtarget,
3341+
symlink_directory_flags))
3342+
break;
3343+
/* There may be dangling phantom symlinks that point at this
3344+
* one, which should now morph into directory symlinks. */
3345+
process_phantom_symlinks();
3346+
return 0;
3347+
default:
3348+
BUG("unhandled symlink type");
3349+
}
3350+
3351+
/* CreateSymbolicLinkW failed. */
3352+
errno = err_win_to_posix(GetLastError());
3353+
return -1;
32983354
}
32993355

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

0 commit comments

Comments
 (0)