the working copy are ignored; useful on broken filesystems like FAT.
See gitlink:git-update-index[1]. True by default.
+core.symlinks::
+ If false, symbolic links are checked out as small plain files that
+ contain the link text. gitlink:git-update-index[1] and
+ gitlink:git-add[1] will not change the recorded type to regular
+ file. Useful on filesystems like FAT that do not support
+ symbolic links. True by default.
+
core.gitProxy::
A "proxy command" to execute (as 'command host port') instead
of establishing direct connection to the remote server when
executable bit. On such an unfortunate filesystem, you may
need to use `git-update-index --chmod=`.
+Quite similarly, if `core.symlinks` configuration variable is set
+to 'false' (see gitlink:git-config[1]), symbolic links are checked out
+as plain files, and this command does not modify a recorded file mode
+from symbolic link to regular file.
+
The command looks at `core.ignorestat` configuration variable. See
'Using "assume unchanged" bit' section above.
char *nbuf;
unsigned long nsize;
- if (S_ISLNK(mode))
+ if (has_symlinks && S_ISLNK(mode))
/* Although buf:size is counted string, it also is NUL
* terminated.
*/
ce->ce_flags = htons(namelen);
fill_stat_cache_info(ce, &st);
- if (trust_executable_bit)
+ if (trust_executable_bit && has_symlinks)
ce->ce_mode = create_ce_mode(st.st_mode);
else {
- /* If there is an existing entry, pick the mode bits
- * from it, otherwise assume unexecutable.
+ /* If there is an existing entry, pick the mode bits and type
+ * from it, otherwise assume unexecutable regular file.
*/
struct cache_entry *ent;
int pos = cache_name_pos(path, namelen);
}
static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
{
- extern int trust_executable_bit;
+ extern int trust_executable_bit, has_symlinks;
+ if (!has_symlinks && S_ISREG(mode) &&
+ ce && S_ISLNK(ntohl(ce->ce_mode)))
+ return ce->ce_mode;
if (!trust_executable_bit && S_ISREG(mode)) {
if (ce && S_ISREG(ntohl(ce->ce_mode)))
return ce->ce_mode;
/* Environment bits from configuration mechanism */
extern int use_legacy_headers;
extern int trust_executable_bit;
+extern int has_symlinks;
extern int assume_unchanged;
extern int prefer_symlink_refs;
extern int log_all_ref_updates;
!fstat(fd, &st)) {
size_t len = st.st_size;
size_t sz = 0;
+ int is_file, i;
elem->mode = canon_mode(st.st_mode);
+ /* if symlinks don't work, assume symlink if all parents
+ * are symlinks
+ */
+ is_file = has_symlinks;
+ for (i = 0; !is_file && i < num_parent; i++)
+ is_file = !S_ISLNK(elem->parent[i].mode);
+ if (!is_file)
+ elem->mode = canon_mode(S_IFLNK);
+
result_size = len;
result = xmalloc(len + 1);
while (sz < len) {
return 0;
}
+ if (!strcmp(var, "core.symlinks")) {
+ has_symlinks = git_config_bool(var, value);
+ return 0;
+ }
+
if (!strcmp(var, "core.bare")) {
is_bare_repository_cfg = git_config_bool(var, value);
return 0;
S_ISREG(newmode) && S_ISREG(oldmode) &&
((newmode ^ oldmode) == 0111))
newmode = oldmode;
+ else if (!has_symlinks &&
+ S_ISREG(newmode) && S_ISLNK(oldmode))
+ newmode = oldmode;
diff_change(&revs->diffopt, oldmode, newmode,
ce->sha1, (changed ? null_sha1 : ce->sha1),
ce->name, NULL);
return error("git-checkout-index: unable to write file %s", path);
break;
case S_IFLNK:
- if (to_tempfile) {
- strcpy(path, ".merge_link_XXXXXX");
- fd = mkstemp(path);
+ if (to_tempfile || !has_symlinks) {
+ if (to_tempfile) {
+ strcpy(path, ".merge_link_XXXXXX");
+ fd = mkstemp(path);
+ } else
+ fd = create_file(path, 0666);
if (fd < 0) {
free(new);
return error("git-checkout-index: unable to create "
char git_default_name[MAX_GITNAME];
int use_legacy_headers = 1;
int trust_executable_bit = 1;
+int has_symlinks = 1;
int assume_unchanged;
int prefer_symlink_refs;
int is_bare_repository_cfg = -1; /* unspecified */
if (type != OBJ_BLOB)
die("blob expected for %s '%s'", sha1_to_hex(sha), path);
- if (S_ISREG(mode)) {
+ if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
int fd;
if (mkdir_p(path, 0777))
die("failed to create path %s: %s", path, strerror(errno));
mkdir_p(path, 0777);
unlink(path);
symlink(lnk, path);
+ free(lnk);
} else
die("do not know what to do with %06o %s '%s'",
mode, sha1_to_hex(sha), path);
changed |= MODE_CHANGED;
break;
case S_IFLNK:
- changed |= !S_ISLNK(st->st_mode) ? TYPE_CHANGED : 0;
+ if (!S_ISLNK(st->st_mode) &&
+ (has_symlinks || !S_ISREG(st->st_mode)))
+ changed |= TYPE_CHANGED;
break;
default:
die("internal error: ce_mode is %o", ntohl(ce->ce_mode));
ce->ce_flags = htons(namelen);
fill_stat_cache_info(ce, &st);
- if (trust_executable_bit)
+ if (trust_executable_bit && has_symlinks)
ce->ce_mode = create_ce_mode(st.st_mode);
else {
- /* If there is an existing entry, pick the mode bits
- * from it, otherwise assume unexecutable.
+ /* If there is an existing entry, pick the mode bits and type
+ * from it, otherwise assume unexecutable regular file.
*/
struct cache_entry *ent;
int pos = cache_name_pos(path, namelen);
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes Sixt
+#
+
+test_description='git-checkout-index on filesystem w/o symlinks test.
+
+This tests that git-checkout-index creates a symbolic link as a plain
+file if core.symlinks is false.'
+
+. ./test-lib.sh
+
+test_expect_success \
+'preparation' '
+git-config core.symlinks false &&
+l=$(echo -n file | git-hash-object -t blob -w --stdin) &&
+echo "120000 $l symlink" | git-update-index --index-info'
+
+test_expect_success \
+'the checked-out symlink must be a file' '
+git-checkout-index symlink &&
+test -f symlink'
+
+test_expect_success \
+'the file must be the blob we added during the setup' '
+test "$(git-hash-object -t blob symlink)" = $l'
+
+test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes Sixt
+#
+
+test_description='git-update-index on filesystem w/o symlinks test.
+
+This tests that git-update-index keeps the symbolic link property
+even if a plain file is in the working tree if core.symlinks is false.'
+
+. ./test-lib.sh
+
+test_expect_success \
+'preparation' '
+git-config core.symlinks false &&
+l=$(echo -n file | git-hash-object -t blob -w --stdin) &&
+echo "120000 $l symlink" | git-update-index --index-info'
+
+test_expect_success \
+'modify the symbolic link' '
+echo -n new-file > symlink &&
+git-update-index symlink'
+
+test_expect_success \
+'the index entry must still be a symbolic link' '
+case "`git-ls-files --stage --cached symlink`" in
+120000" "*symlink) echo ok;;
+*) echo fail; git-ls-files --stage --cached symlink; (exit 1);;
+esac'
+
+test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes Sixt
+#
+
+test_description='merging symlinks on filesystem w/o symlink support.
+
+This tests that git-merge-recursive writes merge results as plain files
+if core.symlinks is false.'
+
+. ./test-lib.sh
+
+test_expect_success \
+'setup' '
+git-config core.symlinks false &&
+> file &&
+git-add file &&
+git-commit -m initial &&
+git-branch b-symlink &&
+git-branch b-file &&
+l=$(echo -n file | git-hash-object -t blob -w --stdin) &&
+echo "120000 $l symlink" | git-update-index --index-info &&
+git-commit -m master &&
+git-checkout b-symlink &&
+l=$(echo -n file-different | git-hash-object -t blob -w --stdin) &&
+echo "120000 $l symlink" | git-update-index --index-info &&
+git-commit -m b-symlink &&
+git-checkout b-file &&
+echo plain-file > symlink &&
+git-add symlink &&
+git-commit -m b-file'
+
+test_expect_failure \
+'merge master into b-symlink, which has a different symbolic link' '
+! git-checkout b-symlink ||
+git-merge master'
+
+test_expect_success \
+'the merge result must be a file' '
+test -f symlink'
+
+test_expect_failure \
+'merge master into b-file, which has a file instead of a symbolic link' '
+! (git-reset --hard &&
+git-checkout b-file) ||
+git-merge master'
+
+test_expect_success \
+'the merge result must be a file' '
+test -f symlink'
+
+test_expect_failure \
+'merge b-file, which has a file instead of a symbolic link, into master' '
+! (git-reset --hard &&
+git-checkout master) ||
+git-merge b-file'
+
+test_expect_success \
+'the merge result must be a file' '
+test -f symlink'
+
+test_done