Skip to content

Commit

Permalink
mingw: allow to specify the symlink type in .gitattributes
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
piscisaureus authored and dscho committed Sep 24, 2024
1 parent 9e51a9a commit 38f7ed5
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 1 deletion.
30 changes: 30 additions & 0 deletions Documentation/gitattributes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,36 @@ sign `$` upon checkout. Any byte sequence that begins with
with `$Id$` upon check-in.


`symlink`
^^^^^^^^^

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.

The `symlink` attribute allows you to explicitly set the type of symlink
to `file` or `dir`, so Git doesn't have to guess. If you have a set of
symlinks that point at other files, you can do:

------------------------
*.gif symlink=file
------------------------

To tell Git that a symlink points at a directory, use:

------------------------
tools_folder symlink=dir
------------------------

The `symlink` attribute is ignored on platforms other than Windows,
since they don't distinguish between different types of symlinks.


`filter`
^^^^^^^^

Expand Down
58 changes: 57 additions & 1 deletion compat/mingw.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "../write-or-die.h"
#include "../repository.h"
#include "win32/fscache.h"
#include "../attr.h"

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

Expand Down Expand Up @@ -2943,6 +2944,37 @@ int link(const char *oldpath, const char *newpath)
return 0;
}

enum symlink_type {
SYMLINK_TYPE_UNSPECIFIED = 0,
SYMLINK_TYPE_FILE,
SYMLINK_TYPE_DIRECTORY,
};

static enum symlink_type check_symlink_attr(struct index_state *index, const char *link)
{
static struct attr_check *check;
const char *value;

if (!index)
return SYMLINK_TYPE_UNSPECIFIED;

if (!check)
check = attr_check_initl("symlink", NULL);

git_check_attr(index, link, check);

value = check->items[0].value;
if (ATTR_UNSET(value))
return SYMLINK_TYPE_UNSPECIFIED;
if (!strcmp(value, "file"))
return SYMLINK_TYPE_FILE;
if (!strcmp(value, "dir") || !strcmp(value, "directory"))
return SYMLINK_TYPE_DIRECTORY;

warning(_("ignoring invalid symlink type '%s' for '%s'"), value, link);
return SYMLINK_TYPE_UNSPECIFIED;
}

int mingw_create_symlink(struct index_state *index, const char *target, const char *link)
{
wchar_t wtarget[MAX_LONG_PATH], wlink[MAX_LONG_PATH];
Expand All @@ -2963,7 +2995,31 @@ int mingw_create_symlink(struct index_state *index, const char *target, const ch
if (wtarget[len] == '/')
wtarget[len] = '\\';

return create_phantom_symlink(wtarget, wlink);
switch (check_symlink_attr(index, link)) {
case SYMLINK_TYPE_UNSPECIFIED:
/* Create a phantom symlink: it is initially created as a file
* symlink, but may change to a directory symlink later if/when
* the target exists. */
return create_phantom_symlink(wtarget, wlink);
case SYMLINK_TYPE_FILE:
if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags))
break;
return 0;
case SYMLINK_TYPE_DIRECTORY:
if (!CreateSymbolicLinkW(wlink, wtarget,
symlink_directory_flags))
break;
/* There may be dangling phantom symlinks that point at this
* one, which should now morph into directory symlinks. */
process_phantom_symlinks();
return 0;
default:
BUG("unhandled symlink type");
}

/* CreateSymbolicLinkW failed. */
errno = err_win_to_posix(GetLastError());
return -1;
}

#ifndef _WINNT_H
Expand Down

0 comments on commit 38f7ed5

Please sign in to comment.