Skip to content

Commit 525ac7a

Browse files
piscisaureusdscho
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 <[email protected]> Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 2bf8e61 commit 525ac7a

File tree

2 files changed

+87
-1
lines changed

2 files changed

+87
-1
lines changed

Documentation/gitattributes.txt

+30
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,36 @@ sign `$` upon checkout. Any byte sequence that begins with
385385
with `$Id$` upon check-in.
386386

387387

388+
`symlink`
389+
^^^^^^^^^
390+
391+
On Windows, symbolic links have a type: a "file symlink" must point at
392+
a file, and a "directory symlink" must point at a directory. If the
393+
type of symlink does not match its target, it doesn't work.
394+
395+
Git does not record the type of symlink in the index or in a tree. On
396+
checkout it'll guess the type, which only works if the target exists
397+
at the time the symlink is created. This may often not be the case,
398+
for example when the link points at a directory inside a submodule.
399+
400+
The `symlink` attribute allows you to explicitly set the type of symlink
401+
to `file` or `dir`, so Git doesn't have to guess. If you have a set of
402+
symlinks that point at other files, you can do:
403+
404+
------------------------
405+
*.gif symlink=file
406+
------------------------
407+
408+
To tell Git that a symlink points at a directory, use:
409+
410+
------------------------
411+
tools_folder symlink=dir
412+
------------------------
413+
414+
The `symlink` attribute is ignored on platforms other than Windows,
415+
since they don't distinguish between different types of symlinks.
416+
417+
388418
`filter`
389419
^^^^^^^^
390420

compat/mingw.c

+57-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#define SECURITY_WIN32
1515
#include <sspi.h>
1616
#include "win32/fscache.h"
17+
#include "../attr.h"
1718

1819
#define HCAST(type, handle) ((type)(intptr_t)handle)
1920

@@ -2902,6 +2903,37 @@ int link(const char *oldpath, const char *newpath)
29022903
return 0;
29032904
}
29042905

2906+
enum symlink_type {
2907+
SYMLINK_TYPE_UNSPECIFIED = 0,
2908+
SYMLINK_TYPE_FILE,
2909+
SYMLINK_TYPE_DIRECTORY,
2910+
};
2911+
2912+
static enum symlink_type check_symlink_attr(struct index_state *index, const char *link)
2913+
{
2914+
static struct attr_check *check;
2915+
const char *value;
2916+
2917+
if (!index)
2918+
return SYMLINK_TYPE_UNSPECIFIED;
2919+
2920+
if (!check)
2921+
check = attr_check_initl("symlink", NULL);
2922+
2923+
git_check_attr(index, link, check);
2924+
2925+
value = check->items[0].value;
2926+
if (ATTR_UNSET(value))
2927+
return SYMLINK_TYPE_UNSPECIFIED;
2928+
if (!strcmp(value, "file"))
2929+
return SYMLINK_TYPE_FILE;
2930+
if (!strcmp(value, "dir") || !strcmp(value, "directory"))
2931+
return SYMLINK_TYPE_DIRECTORY;
2932+
2933+
warning(_("ignoring invalid symlink type '%s' for '%s'"), value, link);
2934+
return SYMLINK_TYPE_UNSPECIFIED;
2935+
}
2936+
29052937
int mingw_create_symlink(struct index_state *index, const char *target, const char *link)
29062938
{
29072939
wchar_t wtarget[MAX_LONG_PATH], wlink[MAX_LONG_PATH];
@@ -2922,7 +2954,31 @@ int mingw_create_symlink(struct index_state *index, const char *target, const ch
29222954
if (wtarget[len] == '/')
29232955
wtarget[len] = '\\';
29242956

2925-
return create_phantom_symlink(wtarget, wlink);
2957+
switch (check_symlink_attr(index, link)) {
2958+
case SYMLINK_TYPE_UNSPECIFIED:
2959+
/* Create a phantom symlink: it is initially created as a file
2960+
* symlink, but may change to a directory symlink later if/when
2961+
* the target exists. */
2962+
return create_phantom_symlink(wtarget, wlink);
2963+
case SYMLINK_TYPE_FILE:
2964+
if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags))
2965+
break;
2966+
return 0;
2967+
case SYMLINK_TYPE_DIRECTORY:
2968+
if (!CreateSymbolicLinkW(wlink, wtarget,
2969+
symlink_directory_flags))
2970+
break;
2971+
/* There may be dangling phantom symlinks that point at this
2972+
* one, which should now morph into directory symlinks. */
2973+
process_phantom_symlinks();
2974+
return 0;
2975+
default:
2976+
BUG("unhandled symlink type");
2977+
}
2978+
2979+
/* CreateSymbolicLinkW failed. */
2980+
errno = err_win_to_posix(GetLastError());
2981+
return -1;
29262982
}
29272983

29282984
#ifndef _WINNT_H

0 commit comments

Comments
 (0)