Skip to content

Commit 007169f

Browse files
piscisaureusdscho
authored andcommitted
Win32: symlink: specify 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 3d6e7d6 commit 007169f

File tree

2 files changed

+83
-1
lines changed

2 files changed

+83
-1
lines changed

Documentation/gitattributes.txt

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

384384

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

compat/mingw.c

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "win32/lazyload.h"
1010
#include "../config.h"
1111
#include "dir.h"
12+
#include "../attr.h"
1213

1314
#define HCAST(type, handle) ((type)(intptr_t)handle)
1415

@@ -2381,6 +2382,33 @@ int link(const char *oldpath, const char *newpath)
23812382
return 0;
23822383
}
23832384

2385+
enum symlink_type {
2386+
SYMLINK_TYPE_UNSPECIFIED = 0,
2387+
SYMLINK_TYPE_FILE,
2388+
SYMLINK_TYPE_DIRECTORY,
2389+
};
2390+
2391+
static enum symlink_type check_symlink_attr(const char *link)
2392+
{
2393+
static struct attr_check *check;
2394+
const char *value;
2395+
2396+
if (!check)
2397+
check = attr_check_initl("symlink", NULL);
2398+
2399+
git_check_attr(the_repository->index, link, check);
2400+
2401+
value = check->items[0].value;
2402+
if (value == NULL)
2403+
;
2404+
else if (!strcmp(value, "file"))
2405+
return SYMLINK_TYPE_FILE;
2406+
else if (!strcmp(value, "dir"))
2407+
return SYMLINK_TYPE_DIRECTORY;
2408+
2409+
return SYMLINK_TYPE_UNSPECIFIED;
2410+
}
2411+
23842412
int symlink(const char *target, const char *link)
23852413
{
23862414
wchar_t wtarget[MAX_LONG_PATH], wlink[MAX_LONG_PATH];
@@ -2401,7 +2429,31 @@ int symlink(const char *target, const char *link)
24012429
if (wtarget[len] == '/')
24022430
wtarget[len] = '\\';
24032431

2404-
return create_phantom_symlink(wtarget, wlink);
2432+
switch (check_symlink_attr(link)) {
2433+
case SYMLINK_TYPE_UNSPECIFIED:
2434+
/* Create a phantom symlink: it is initially created as a file
2435+
* symlink, but may change to a directory symlink later if/when
2436+
* the target exists. */
2437+
return create_phantom_symlink(wtarget, wlink);
2438+
case SYMLINK_TYPE_FILE:
2439+
if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags))
2440+
break;
2441+
return 0;
2442+
case SYMLINK_TYPE_DIRECTORY:
2443+
if (!CreateSymbolicLinkW(wlink, wtarget,
2444+
symlink_directory_flags))
2445+
break;
2446+
/* There may be dangling phantom symlinks that point at this
2447+
* one, which should now morph into directory symlinks. */
2448+
process_phantom_symlinks();
2449+
return 0;
2450+
default:
2451+
BUG("unhandled symlink type");
2452+
}
2453+
2454+
/* CreateSymbolicLinkW failed. */
2455+
errno = err_win_to_posix(GetLastError());
2456+
return -1;
24052457
}
24062458

24072459
#ifndef _WINNT_H

0 commit comments

Comments
 (0)