{"thread":{"id":"32278","subject":"[PATCH] RFC Optionally handle symbolic links as copies","startedAt":"2012-12-05T22:46:15Z","lastAt":"2012-12-12T20:22:23Z","messageCount":8,"participants":["Robin Rosenberg","Junio C Hamano","Johannes Sixt","Michael J Gruber"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"204542","messageId":"1354747575-89277-1-git-send-email-robin.rosenberg@dewire.com","threadId":"32278","inReplyTo":null,"subject":"[PATCH] RFC Optionally handle symbolic links as copies","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2012-12-05T22:46:15Z","receivedAt":"2012-12-05T22:46:15Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"If core.symlinks is set to copy then symbolic links in a git repository\nwill be checked out as copies of the file it points to. This allows repos\ncontaining symbolic links to not only be checked out, but also that the\nlinked content may be used on OS:es and filesystems that do not support\nsymbolic links.\n\nPlain files will be copied as hard links, directories will be replicated\nwith files as hard links. Stale links will not be copied and will appear\nas missing files.\n\nA git-ln utility whose usage is similar to the standard ln utilty. It will\ncreate symbolic links unless core.symlinks is set to copy.\n\nThis patch still contains debug statements and open issues:\n- git rm link-to-dir - remove without force or compare\n- git status - when do we report a diff. \n\t- After checkout we should probably not\n\t- if the \"linked\" files change?\n\t- if a change in the copied directory chsnges\n\t- if a file in the copied diretory is added/removed\n\t- update, should we update the copied structure automatically\n\t  when the link target changes\n- git add - just ignore the stat diff for links\n- git xxx -- gotchas\n- ugly code\n- debug statements\n- few test cases\n\n---\n .gitignore                          |   1 +\n Makefile                            |  10 +-\n builtin/checkout-index.c            |   1 +\n builtin/checkout.c                  |   2 +\n builtin/rm.c                        |  17 ++-\n cache.h                             |   3 +\n command-list.txt                    |   1 +\n config.c                            |   8 +-\n entry.c                             | 238 +++++++++++++++++++++++++++++++++++-\n environment.c                       |   1 +\n git-ln.sh                           | 132 ++++++++++++++++++++\n read-cache.c                        |  60 +++++----\n t/t2401-checkout-symlink-as-copy.sh | 142 +++++++++++++++++++++\n unpack-trees.c                      |  11 ++\n wrapper.c                           |  19 ++-\n 15 files changed, 611 insertions(+), 35 deletions(-)\n create mode 100755 git-ln.sh\n create mode 100755 t/t2401-checkout-symlink-as-copy.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 5778ce1..0c82778 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -72,6 +72,7 @@\n /git-init\n /git-init-db\n /git-instaweb\n+/git-ln\n /git-log\n /git-lost-found\n /git-ls-files\ndiff --git a/Makefile b/Makefile\nindex 1cff01e..3660597 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -341,10 +341,15 @@ endif\n \n # CFLAGS and LDFLAGS are for the users to override from the command line.\n \n-CFLAGS = -g -O2 -Wall -Werror \\\n+CFLAGS = -g -Wall -Werror \\\n \t-Wno-pointer-to-int-cast \\\n \t-Wold-style-definition \\\n-\t-Wdeclaration-after-statement\n+\t-Wdeclaration-after-statement \\\n+\t-Wno-deprecated-declarations \\\n+\t-Wstrict-prototypes \\\n+\t-Wdeclaration-after-statement \\\n+\t-Wno-pointer-to-int-cast \\\n+\n LDFLAGS =\n ALL_CFLAGS = $(CPPFLAGS) $(CFLAGS)\n ALL_LDFLAGS = $(LDFLAGS)\n@@ -442,6 +447,7 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-lost-found.sh\n+SCRIPT_SH += git-ln.sh\n SCRIPT_SH += git-merge-octopus.sh\n SCRIPT_SH += git-merge-one-file.sh\n SCRIPT_SH += git-merge-resolve.sh\ndiff --git a/builtin/checkout-index.c b/builtin/checkout-index.c\nindex b1feda7..aa840b7 100644\n--- a/builtin/checkout-index.c\n+++ b/builtin/checkout-index.c\n@@ -113,6 +113,7 @@ static void checkout_all(const char *prefix, int prefix_length)\n \t\t\terrs++;\n \t\tlast_ce = ce;\n \t}\n+\tcheckout_remaining_link_copies();\n \tif (last_ce && to_tempfile)\n \t\twrite_tempfile_record(last_ce->name, prefix_length);\n \tif (errs)\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 781295b..eafd3d5 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -328,6 +328,8 @@ static int checkout_paths(const struct checkout_opts *opts,\n \t\t}\n \t}\n \n+\tcheckout_remaining_link_copies();\n+\n \tif (write_cache(newfd, active_cache, active_nr) ||\n \t    commit_locked_index(lock_file))\n \t\tdie(_(\"unable to write new index file\"));\ndiff --git a/builtin/rm.c b/builtin/rm.c\nindex b384c4c..14fb399 100644\n--- a/builtin/rm.c\n+++ b/builtin/rm.c\n@@ -17,6 +17,8 @@ static const char * const builtin_rm_usage[] = {\n \n static struct {\n \tint nr, alloc;\n+\tint nrmode, allocmode;\n+\tint *mode;\n \tconst char **name;\n } list;\n \n@@ -174,7 +176,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n \t\tif (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))\n \t\t\tcontinue;\n \t\tALLOC_GROW(list.name, list.nr + 1, list.alloc);\n-\t\tlist.name[list.nr++] = ce->name;\n+\t\tALLOC_GROW(list.mode, list.nrmode + 1, list.allocmode); // ugly\n+\t\tlist.name[list.nr] = ce->name;\n+\t\tlist.mode[list.nr] = ce->ce_mode;\n+\t\tlist.nr++;\n \t}\n \n \tif (pathspec) {\n@@ -245,7 +250,13 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n \t\tint removed = 0;\n \t\tfor (i = 0; i < list.nr; i++) {\n \t\t\tconst char *path = list.name[i];\n-\t\t\tif (!remove_path(path)) {\n+\t\t\tif (S_ISLNK(list.mode[i]) && copy_symlinks) {\n+\t\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\t\tstrbuf_add(&sb, list.name[i], strlen(list.name[i]));\n+\t\t\t\tremove_dir_recursively(&sb, 0);\n+\t\t\t\tstrbuf_release(&sb);\n+\t\t\t\tremoved = 1;\n+\t\t\t} else if (!remove_path(path)) {\n \t\t\t\tremoved = 1;\n \t\t\t\tcontinue;\n \t\t\t}\ndiff --git a/cache.h b/cache.h\nindex fe0388c..125929d 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -532,6 +532,7 @@ extern int trust_executable_bit;\n extern int trust_ctime;\n extern int quote_path_fully;\n extern int has_symlinks;\n+extern int copy_symlinks;\n extern int minimum_abbrev, default_abbrev;\n extern int ignore_case;\n extern int assume_unchanged;\n@@ -954,6 +955,8 @@ extern int has_dirs_only_path(const char *name, int len, int prefix_len);\n extern void schedule_dir_for_removal(const char *name, int len);\n extern void remove_scheduled_dirs(void);\n \n+extern int checkout_remaining_link_copies(void);\n+\n extern struct alternate_object_database {\n \tstruct alternate_object_database *next;\n \tchar *name;\ndiff --git a/command-list.txt b/command-list.txt\nindex 7e8cfec..0e7e147 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -61,6 +61,7 @@ git-index-pack                          plumbingmanipulators\n git-init                                mainporcelain common\n git-instaweb                            ancillaryinterrogators\n gitk                                    mainporcelain\n+git-ln                                  mainporcelain\n git-log                                 mainporcelain common\n git-lost-found                          ancillarymanipulators\tdeprecated\n git-ls-files                            plumbinginterrogators\ndiff --git a/config.c b/config.c\nindex 5ea36a7..e0fd347 100644\n--- a/config.c\n+++ b/config.c\n@@ -576,7 +576,13 @@ static int git_default_core_config(const char *var, const char *value)\n \t}\n \n \tif (!strcmp(var, \"core.symlinks\")) {\n-\t\thas_symlinks = git_config_bool(var, value);\n+\t\tif (0 == strcasecmp(\"copy\", value)) {\n+\t\t\tcopy_symlinks = 1;\n+\t\t\thas_symlinks = 1; // TODO: reconsider\n+\t\t} else {\n+\t\t\thas_symlinks = git_config_bool(var, value);\n+\t\t\tcopy_symlinks = 0;\n+\t\t}\n \t\treturn 0;\n \t}\n \ndiff --git a/entry.c b/entry.c\nindex 17a6bcc..b46417a 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -2,6 +2,17 @@\n #include \"blob.h\"\n #include \"dir.h\"\n #include \"streaming.h\"\n+#include <string.h>\n+#include <stdio.h>\n+\n+int nnn;\n+int xlink(const char *from, const char *to)\n+{\n+\tint ret = link(from,to);\n+\tprintf(\"#%d,link %s,%s, ret=%d, errno=%d\\n\", nnn++, from, to, ret, errno);\n+\treturn ret;\n+}\n+#define link(s,e) xlink(s,e)\n \n static void create_directories(const char *path, int path_len,\n \t\t\t       const struct checkout *state)\n@@ -134,6 +145,209 @@ static int streaming_write_entry(struct cache_entry *ce, char *path,\n \treturn result;\n }\n \n+struct postlink {\n+\tstruct cache_entry *ce;\n+\tchar *from,*to;\n+};\n+static struct postlink *postlinks = NULL;\n+static int npostlinks = 0;\n+static int maxpostlinks = 0;\n+static int resolved = 0;\n+int lasttry = 0;\n+\n+void add_postlink(struct cache_entry *ce, char *from, char *to)\n+{\n+\tif (npostlinks >= maxpostlinks) {\n+\t\tif (maxpostlinks == 0)\n+\t\t\tmaxpostlinks = 64;\n+\t\telse\n+\t\t\tmaxpostlinks *= 2;\n+\t\tpostlinks = realloc(postlinks, maxpostlinks*sizeof(struct postlink));\n+\t}\n+\tpostlinks[npostlinks].ce = ce;\n+\tpostlinks[npostlinks].to = xstrdup(to);\n+\tpostlinks[npostlinks].from = xstrdup(from);\n+\tnpostlinks++;\n+}\n+\n+void release_postlink(void)\n+{\n+\tint i;\n+\tfor (i = 0; i < npostlinks; ++i) {\n+\t\tfree(postlinks[i].from);\n+\t\tfree(postlinks[i].to);\n+\t}\n+\tfree(postlinks);\n+\tpostlinks = NULL;\n+\tnpostlinks = 0;\n+\tmaxpostlinks = 0;\n+}\n+\n+static int recursive_link(const char *src, const char *dst) {\n+\t// printf(\"recursive_link %s %s\\n\", src, dst);\n+\tstruct stat buf;\n+\tstruct dirent *dp;\n+\tstruct strbuf dstb = STRBUF_INIT;\n+\tstruct strbuf srcb = STRBUF_INIT;\n+\tint err = stat(dst, &buf);\n+\tDIR *d = opendir(src);\n+\tif (!d)\n+\t\treturn ENOENT;\n+\tif (err && errno == ENOENT) {\n+\t\terr = mkdir(dst, 0777);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t\telse\n+\t\t\tresolved++;\n+\t} else if (!(buf.st_mode & S_IFDIR))\n+\t\treturn EEXIST;\n+\terr = 0;\n+\twhile ((dp = readdir(d)) != NULL) {\n+\t\tif (is_dot_or_dotdot(dp->d_name))\n+\t\t\tcontinue;\n+\t\tstrbuf_add(&dstb, dst, strlen(dst));\n+\t\tstrbuf_add(&dstb, \"/\", 1);\n+\t\tstrbuf_add(&dstb, dp->d_name, strlen(dp->d_name));\n+\t\tstrbuf_add(&srcb, src, strlen(src));\n+\t\tstrbuf_add(&srcb, \"/\", 1);\n+\t\tstrbuf_add(&srcb, dp->d_name, strlen(dp->d_name));\n+\t\tif (!stat(srcb.buf, &buf)) {\n+\t\t\tif (buf.st_mode & S_IFDIR) {\n+\t\t\t\tif (recursive_link(srcb.buf, dstb.buf)) {\n+\t\t\t\t\terr = -1;\n+\t\t\t\t}\n+\t\t\t} else {\n+\t\t\t\tint r1,r2;\n+\t\t\t\tr1 = unlink(dstb.buf);\n+\t\t\t\tif (r1)\n+\t\t\t\t\terror(\"unlink %s = %d, errno=%d\\n\", dstb.buf, r1, errno);\n+\t\t\t\tr2 = link(srcb.buf, dstb.buf);\n+\t\t\t\tif (r2) {\n+\t\t\t\t\tif (maxpostlinks >= 0) {\n+\t\t\t\t\t\terror(\"cannot link %s to %s\", srcb.buf, dstb.buf);\n+\t\t\t\t\t}\n+\t\t\t\t\terr = -1;\n+\t\t\t\t}\n+\t\t\t\tif (r1 && !r2)\n+\t\t\t\t\tresolved++;\n+\t\t\t}\n+\t\t} else {\n+\t\t\tif (maxpostlinks >= 0) {\n+\t\t\t\terror(\"cannot link %s to %s\", srcb.buf, dstb.buf);\n+\t\t\t}\n+\t\t\terr = -1;\n+\t\t}\n+\t\tstrbuf_reset(&srcb);\n+\t\tstrbuf_reset(&dstb);\n+\t}\n+\tstrbuf_release(&dstb);\n+\tstrbuf_release(&srcb);\n+\tclosedir(d);\n+\treturn err;\n+}\n+\n+static void resolvelink(struct strbuf *src, struct strbuf *dst)\n+{\n+\tif (0 == strncmp(src->buf, \"../\", 3)) {\n+\t\tchar *p = dst->buf + dst->len - 1;\n+\t\twhile (p > dst->buf && *p != '/')\n+\t\t\t--p;\n+\t\tif (*p != '/')\n+\t\t\treturn;\n+\t\t--p;\n+\t\twhile (p > dst->buf && *p != '/')\n+\t\t\t--p;\n+\t\tstrbuf_remove(src, 0, 3);\n+\t\tif (*p == '/')\n+\t\t\tstrbuf_insert(src, 0, dst->buf, p - dst->buf + 1);\n+\t\tresolvelink(src, dst);\n+\t} else if (0 == strncmp(src->buf, \"./\", 2)) {\n+\t\tstrbuf_remove(src, 0, 2);\n+\t\tresolvelink(src, dst);\n+\t}\n+}\n+\n+static int fakesymlink(const char *old, const char *new)\n+{\n+\tstruct stat buf;\n+\tint st;\n+\tint err;\n+\tchar dir[MAXPATHLEN];\n+\tchar cdir[MAXPATHLEN];\n+\tchar *dend;\n+\tstruct strbuf src = STRBUF_INIT;\n+\tstruct strbuf dst = STRBUF_INIT;\n+\tstrcpy(dir, new);\n+\tdend = strrchr(dir, '/');\n+\tif (dend) {\n+\t\t*dend = 0;\n+\t\tif (!getcwd(cdir, MAXPATHLEN))\n+\t\t\treturn -1;\n+\t}\n+\tstrbuf_add(&src, old, strlen(old));\n+\tstrbuf_add(&dst, new, strlen(new));\n+\tresolvelink(&src, &dst);\n+\tif (0 == (st = stat(src.buf, &buf))) {\n+\t\tif (buf.st_mode & (S_IFREG | S_IFLNK)) {\n+\t\t\tint r = unlink(dst.buf);\n+\t\t\terr = link(src.buf, dst.buf);\n+\t\t\tif (!err && !r)\n+\t\t\t\tresolved++;\n+\t\t\tif (err)\n+\t\t\t\terror(\"Failed to link %s %s\\n\", src.buf, dst.buf);\n+\t\t} else if (buf.st_mode & S_IFDIR) {\n+\t\t\terr = recursive_link(src.buf, dst.buf);\n+\t\t\tif (maxpostlinks < 0)\n+\t\t\t\terr = 1;\n+//\t\t\tprintf(\"%d <- recursive_link\\n\", err);\n+\t\t} else {\n+\t\t\terror(\"mode:Failed to link %s %s\\n\", src.buf, dst.buf);\n+\t\t\terr = -1;\n+\t\t}\n+\t} else {\n+\t\terror(\"stat:Failed to link %s %s\\n\", src.buf, dst.buf);\n+\t\terr = -1;\n+\t}\n+\tstrbuf_release(&src);\n+\tstrbuf_release(&dst);\n+\treturn err;\n+}\n+\n+int checkout_remaining_link_copies(void)\n+{\n+\tint j;\n+\tint err = 0;\n+\tmaxpostlinks = -1;\n+\tresolved = 1;\n+\n+\t/* stupid, each iteration should resolve at least one entry */\n+\tdo {\n+\t\tif (resolved == 0)\n+\t\t\tlasttry = 1;\n+\t\tresolved = 0;\n+\t\tfor (j = 0; j < npostlinks; ++j) {\n+\t\t\tstruct postlink *e = &postlinks[j];\n+\t\t\tif (e->from) {\n+\t\t\t\tif (0 == fakesymlink(e->from, e->to)) {\n+\t\t\t\t\tif (e->ce) {\n+\t\t\t\t\t\tstruct stat st;\n+\t\t\t\t\t\tif (0 == stat(e->to, &st))\n+\t\t\t\t\t\t\tfill_stat_cache_info(e->ce, &st);\n+\t\t\t\t\t}\n+\t\t\t\t\tfree(e->from);\n+\t\t\t\t\te->from = NULL;\n+\t\t\t\t} else {\n+\t\t\t\t\tif (lasttry) {\n+\t\t\t\t\t\terr = 1;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t} while (resolved > 0 && !lasttry);\n+\trelease_postlink();\n+\treturn err;\n+}\n+\n static int write_entry(struct cache_entry *ce, char *path, const struct checkout *state, int to_tempfile)\n {\n \tunsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;\n@@ -161,8 +375,16 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout\n \t\t\treturn error(\"unable to read sha1 file of %s (%s)\",\n \t\t\t\tpath, sha1_to_hex(ce->sha1));\n \n-\t\tif (ce_mode_s_ifmt == S_IFLNK && has_symlinks && !to_tempfile) {\n-\t\t\tret = symlink(new, path);\n+\t\tif (ce_mode_s_ifmt == S_IFLNK && (has_symlinks || copy_symlinks) && !to_tempfile) {\n+\t\t\tif (copy_symlinks) {\n+\t\t\t\tret = fakesymlink(new, path);\n+\t\t\t\tif (ret < 0) {\n+\t\t\t\t\t// Could not copy now, assume we can do it later\n+\t\t\t\t\tadd_postlink(ce, new, path);\n+\t\t\t\t\tret = 0;\n+\t\t\t\t}\n+\t\t\t} else\n+\t\t\t\tret = symlink(new, path);\n \t\t\tfree(new);\n \t\t\tif (ret)\n \t\t\t\treturn error(\"unable to create symlink %s (%s)\",\n@@ -267,8 +489,16 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t\n \t\t\tif (!state->force)\n \t\t\t\treturn error(\"%s is a directory\", path);\n \t\t\tremove_subtree(path);\n-\t\t} else if (unlink(path))\n-\t\t\treturn error(\"unable to unlink old '%s' (%s)\", path, strerror(errno));\n+\t\t} else {\n+\t\t\tif (unlink(path)) {\n+//\t\t\t\tprintf(\"has_symlinks=%d, copy_symlinks=%d\\n\", has_symlinks, copy_symlinks);\n+\t\t\t\tif (copy_symlinks) {\n+\t\t\t\t\tremove_subtree(path);\n+\t\t\t\t} else {\n+\t\t\t\t\treturn error(\"unable to unlink old '%s' (%s)\", path, strerror(errno));\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n \t} else if (state->not_new)\n \t\treturn 0;\n \tcreate_directories(path, len, state);\ndiff --git a/environment.c b/environment.c\nindex 71e438c..a1328ec 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -14,6 +14,7 @@\n int trust_executable_bit = 1;\n int trust_ctime = 1;\n int has_symlinks = 1;\n+int copy_symlinks = 0;\n int minimum_abbrev = 4, default_abbrev = 7;\n int ignore_case;\n int assume_unchanged;\ndiff --git a/git-ln.sh b/git-ln.sh\nnew file mode 100755\nindex 0000000..d8df14f\n--- /dev/null\n+++ b/git-ln.sh\n@@ -0,0 +1,132 @@\n+#!/bin/sh\n+[[ -n $LX ]] && set -x\n+#\n+# Copyright (C) 2012 Robin Rosenberg\n+# Helper for adding symbolic links when file system does not support it\n+#\n+\n+SUBDIRECTORY_OK=Yes\n+OPTIONS_KEEPDASHDASH=\n+OPTIONS_SPEC=\"\\\n+git ln -s [options] target linkname\n+--\n+f,force\tforce creating when linkname or target does not exist\n+s,symbolic create symbolic link (mandatory)\n+cached operate only on index\n+\"\n+#usage() {\n+\t#\techo >&2 \"usage: ln [-fs] target name\"\n+\t#\texit 1\n+#}\n+\n+symbolic=\n+force=\n+cached=\n+copy=\n+symlinks=$(git config --get core.symlinks|tr A-Z a-z)\n+if [[ $symlinks == copy ]];then copy=1;fi\n+\n+. git-sh-setup\n+. git-sh-i18n\n+\n+require_work_tree_exists\n+if [ $? != 0 ]; then\n+\tusage\n+fi\n+while test $# != 0;do\n+\tcase \"$1\" in\n+\t\t-f|--force)\n+\t\t\tforce=1\n+\t\t\tshift\n+\t\t\t;;\n+\t\t-s|--symbolic)\n+\t\t\tsymbolic=1\n+\t\t\tshift\n+\t\t\t;;\n+\t\t--cached)\n+\t\t\tcached=1\n+\t\t\tshift\n+\t\t\t;;\n+\t\t--)\n+\t\t\tif [[ $# == 3 ]];\n+\t\t\tthen\n+\t\t\t\ttarget=$2\n+\t\t\t\tname=$3\n+\t\t\t\tshift 3\n+\t\t\t\tbreak\n+\t\t\telse\n+\t\t\t\tusage\n+\t\t\t\texit 1\n+\t\t\tfi\n+\t\t\t;;\n+\t\t*)\n+\t\t\tusage\n+\t\t\texit 1\n+\t\t\t;;\n+\t\tesac\n+done\n+if [[ -z $symbolic ]];then\n+\tusage\n+fi\n+GIT_DIR=$(git rev-parse --git-dir) || exit 1\n+cwd=$(pwd)\n+wd=$( (cd_to_toplevel && pwd) )\n+namedir=$(dirname \"$name\")\n+namebase=$(basename \"$name\")\n+absnamedir=$( (cd \"$namedir\" && pwd) )\n+cd \"$namedir\" || exit 1\n+pwd\n+if [[ ${absnamedir:0:${#wd}} != $wd ]]\n+then\n+\techo >&2 git-ln: $name is outside of working tree\n+\texit 1\n+fi\n+reldir=${absnamedir:${#wd}}\n+reldir=${reldir:1}\n+if [[ -n $reldir ]];then\n+\treldir=\"$reldir/\"\n+fi\n+if [[ ! -e $target && ! $force ]];then\n+\techo >&2 git-ln: $target does not exist\n+\texit 1\n+fi\n+\n+if [[ -n $force ]];then\n+\tif [[ -z $cached ]];then\n+\t\tif [[ -d $name && -n $copy ]];then\n+\t\t\trm -rf \"$name\"\n+\t\telif [[ ! -d $name ]];then\n+\t\t\trm -f \"$name\"\n+\t\tfi\n+\tfi\n+\tgit rm -f --cached \"$name\" >/dev/null || exit 1\n+fi\n+sha1=$(printf %s \"$target\" | git hash-object -w -t blob --stdin)\n+if [ $? != 0 ];then exit 1;fi\n+git update-index --add --cacheinfo 120000 $sha1 $reldir$name || exit $?\n+if [[ -n $copy ]];then\n+\tcd \"$cwd\" || exit $?\n+\tif [[ -d $target ]];\n+\tthen\n+\t\t(cd \"$target\" &&\n+\t\tfind . -type d)|\n+\t\twhile read d;do\n+\t\t\tmkdir -p \"$name/$d\" || exit $?\n+\t\tdone\n+\t\t(cd \"$target\" &&\n+\t\tfind . -type f)|\n+\t\twhile read f;do\n+\t\t\tln \"$target/$f\" \"$name/$f\" || exit $?\n+\t\tdone\n+\telse\n+\t\tln \"$target\" \"$name\" || exit $?\n+\tfi\n+else\n+\tif [[ -e $name ]];then\n+\t\techo >&2 git-ln: File $name exists\n+\t\texit 1\n+\tfi\n+\tcd \"$cwd\" || exit $?\n+\tln -s $target $name || exit $?\n+fi\n+exit 0\ndiff --git a/read-cache.c b/read-cache.c\nindex fda78bc..9ca8e42 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -181,8 +181,18 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)\n \t\t\tchanged |= MODE_CHANGED;\n \t\tbreak;\n \tcase S_IFLNK:\n+\t\t//\tISLNK(st)\thassym\tISREG(st)\tISDIR(st)\tcopy\t->\n+\t\t//\ty\t\t\ty\t\ty\t\t\t\t\t\t\t\tchanged (old)\n+\t\t//\ty\t\t\tn\t\ty\t\t\t\t\t\t\t\tchanged (old)\n+\t\t//\ty\t\t\ty\t\tn\t\t\t\t\t\t\t\tchanged (old)\n+\t\t//  =\t\t\t=\t\t=\t\t\ty\t\t\ty\t\tno\n+\t\t//  =\t\t\t=\t\t=\t\t\tn\t\t\tn\t\tchanged\n+\t\t//  =\t\t\t=\t\t=\t\t\ty\t\t\tn\t\tchanged\n+\t\t//  =\t\t\t=\t\t=\t\t\tn\t\t\ty\t\tchanged\n+\n \t\tif (!S_ISLNK(st->st_mode) &&\n-\t\t    (has_symlinks || !S_ISREG(st->st_mode)))\n+\t\t    (has_symlinks || !S_ISREG(st->st_mode))\n+\t\t    && !S_ISDIR(st->st_mode) && !copy_symlinks)\n \t\t\tchanged |= TYPE_CHANGED;\n \t\tbreak;\n \tcase S_IFGITLINK:\n@@ -195,36 +205,38 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)\n \tdefault:\n \t\tdie(\"internal error: ce_mode is %o\", ce->ce_mode);\n \t}\n-\tif (ce->ce_mtime.sec != (unsigned int)st->st_mtime)\n-\t\tchanged |= MTIME_CHANGED;\n-\tif (trust_ctime && ce->ce_ctime.sec != (unsigned int)st->st_ctime)\n-\t\tchanged |= CTIME_CHANGED;\n-\n+\t/* For copy as symlink we just ignore the lowlevel stat fields */\n+\tif (!copy_symlinks || !S_ISLNK(ce->ce_mode) || !S_ISDIR(st->st_mode)) {\n+\t\tif (ce->ce_mtime.sec != (unsigned int)st->st_mtime)\n+\t\t\tchanged |= MTIME_CHANGED;\n+\t\tif (trust_ctime && ce->ce_ctime.sec != (unsigned int)st->st_ctime)\n+\t\t\tchanged |= CTIME_CHANGED;\n #ifdef USE_NSEC\n-\tif (ce->ce_mtime.nsec != ST_MTIME_NSEC(*st))\n-\t\tchanged |= MTIME_CHANGED;\n-\tif (trust_ctime && ce->ce_ctime.nsec != ST_CTIME_NSEC(*st))\n-\t\tchanged |= CTIME_CHANGED;\n+\t\tif (ce->ce_mtime.nsec != ST_MTIME_NSEC(*st))\n+\t\t\tchanged |= MTIME_CHANGED;\n+\t\tif (trust_ctime && ce->ce_ctime.nsec != ST_CTIME_NSEC(*st))\n+\t\t\tchanged |= CTIME_CHANGED;\n #endif\n \n-\tif (ce->ce_uid != (unsigned int) st->st_uid ||\n-\t    ce->ce_gid != (unsigned int) st->st_gid)\n-\t\tchanged |= OWNER_CHANGED;\n-\tif (ce->ce_ino != (unsigned int) st->st_ino)\n-\t\tchanged |= INODE_CHANGED;\n+\t\tif (ce->ce_uid != (unsigned int) st->st_uid ||\n+\t\t\tce->ce_gid != (unsigned int) st->st_gid)\n+\t\t\tchanged |= OWNER_CHANGED;\n+\t\tif (ce->ce_ino != (unsigned int) st->st_ino)\n+\t\t\tchanged |= INODE_CHANGED;\n \n #ifdef USE_STDEV\n-\t/*\n-\t * st_dev breaks on network filesystems where different\n-\t * clients will have different views of what \"device\"\n-\t * the filesystem is on\n-\t */\n-\tif (ce->ce_dev != (unsigned int) st->st_dev)\n-\t\tchanged |= INODE_CHANGED;\n+\t\t/*\n+\t\t * st_dev breaks on network filesystems where different\n+\t\t * clients will have different views of what \"device\"\n+\t\t * the filesystem is on\n+\t\t */\n+\t\tif (ce->ce_dev != (unsigned int) st->st_dev)\n+\t\t\tchanged |= INODE_CHANGED;\n #endif\n \n-\tif (ce->ce_size != (unsigned int) st->st_size)\n-\t\tchanged |= DATA_CHANGED;\n+\t\tif (ce->ce_size != (unsigned int) st->st_size)\n+\t\t\tchanged |= DATA_CHANGED;\n+\t}\n \n \t/* Racily smudged entry? */\n \tif (!ce->ce_size) {\ndiff --git a/t/t2401-checkout-symlink-as-copy.sh b/t/t2401-checkout-symlink-as-copy.sh\nnew file mode 100755\nindex 0000000..922af9d\n--- /dev/null\n+++ b/t/t2401-checkout-symlink-as-copy.sh\n@@ -0,0 +1,142 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2012 Robin Rosenberg\n+\n+test_description='git checkout and reset with symlinks as copy'\n+\n+. ./test-lib.sh\n+\n+fullfilelist=\"a\n+linkd\n+linkd/reald2\n+linkd/reald2/zlink2\n+linkd/reald2/zlink3\n+linkd/realfile\n+linkd/zlink\n+linkfile\n+reald\n+reald/reald2\n+reald/reald2/zlink2\n+reald/reald2/zlink3\n+reald/realfile\n+reald/zlink\n+text\n+z\"\n+\n+fullfilelist_except_linkd=\"a\n+linkfile\n+reald\n+reald/reald2\n+reald/reald2/zlink2\n+reald/reald2/zlink3\n+reald/realfile\n+reald/zlink\n+text\n+z\"\n+\n+fulllsfiles=\"a\n+linkd\n+linkfile\n+reald/reald2/zlink2\n+reald/reald2/zlink3\n+reald/realfile\n+reald/zlink\n+text\n+z\"\n+\n+fulllsfiles_except_linkd=\"a\n+linkfile\n+reald/reald2/zlink2\n+reald/reald2/zlink3\n+reald/realfile\n+reald/zlink\n+text\n+z\"\n+\n+fulllstree=\"100644 blob 1269488f7fb1f4b56a8c0e5eb48cecbfadfa9219\ta\n+120000 blob afb748c1b973ba508f014b969b193f5370060583\tlinkd\n+120000 blob 9a5d4d6a54a108d78fd356d95e15612ace0fc7ed\tlinkfile\n+120000 blob bf219293900231f5f9fdb67fa004f2c83bec1635\treald/reald2/zlink2\n+120000 blob a240f4229e24dde99ca0293d47d62906907e749c\treald/reald2/zlink3\n+100644 blob d95f3ad14dee633a758d2e331151e950dd13e4ed\treald/realfile\n+120000 blob 1856dae911a6b4e1a06f19218f97d94d2b1e5a96\treald/zlink\n+120000 blob ae1910dff1c8f7f779f2154de8f3902702b90e8b\ttext\n+120000 blob 2e65efe2a145dda7ee51d1741299f848e5bf752e\tz\"\n+\n+test_complete_master_state() {\n+\ttest \"$(find -L * -print)\" = \"$fullfilelist\" &&\n+\ttest \"$(git ls-tree -r HEAD)\" = \"$fulllstree\"\n+}\n+\n+reset_master() {\n+\trm -rf * &&\n+\tgit reset --hard &&\n+\ttest_complete_master_state\n+}\n+\n+# setup work with or without real symlink support,\n+# by default we use real symlink support\n+test_expect_success setup '\n+\tgit commit --allow-empty -m \"empty\" &&\n+\tgit branch empty &&\n+\techo >a data &&\n+\tgit add a &&\n+\tgit ln -s a z &&\n+\tmkdir reald &&\n+\tmkdir reald/reald2 &&\n+\techo >../textf external_file &&\n+\tgit ln -s ../textf text &&\n+\techo >reald/realfile content &&\n+\tgit add reald/realfile &&\n+\tgit ln -s reald/realfile linkfile &&\n+\t(cd reald/reald2 && git ln -s ../realfile zlink2) &&\n+\t(cd reald/reald2 && git ln -s ../../z zlink3) &&\n+\t(cd reald && git ln -s ../z zlink) &&\n+\tgit ln -s reald linkd &&\n+\tgit commit -m \"repo with symlinks\" &&\n+\n+\ttest_complete_master_state &&\n+\n+\tgit config core.symlinks copy\n+'\n+\n+test_expect_success 'reset --hard' '\n+\trm -rf * &&\n+\ttest \"$(find * -print)\" = \"\" &&\n+\n+\tgit reset --hard &&\n+\ttest \"$(find * -print )\" = \"$fullfilelist\"\n+'\n+\n+test_expect_success 'checkout -f empty' '\n+\trm -rf * &&\n+\tgit checkout -f empty &&\n+\ttest \"$(find * -print)\" = \"\"\n+'\n+\n+test_expect_success 'checkout -f master' '\n+\trm -rf * &&\n+\tgit checkout -f master &&\n+\ttest_complete_master_state\n+'\n+\n+test_expect_success 'rm --cached, i.e. link only ' '\n+\tgit rm --cached linkd &&\n+\ttest \"$(find * -print)\" = \"$fullfilelist\" &&\n+\ttest \"$(git ls-files)\" = \"$fulllsfiles_except_linkd\"\n+'\n+\n+test_expect_success 'rm copy of linked directory' '\n+\treset_master &&\n+\tgit rm linkd &&\n+\ttest \"$(find * -print)\" = \"$fullfilelist_except_linkd\" &&\n+\ttest \"$(git ls-files)\" = \"$fulllsfiles_except_linkd\"\n+'\n+\n+test_expect_success 'rm without -f for a regular tree should NOT work' '\n+\treset_master &&\n+\t! git rm reald &&\n+\ttest_complete_master_state\n+'\n+\n+test_done\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 6d96366..bb2214d 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -229,6 +229,7 @@ static int check_updates(struct unpack_trees_options *o)\n \t\t\t}\n \t\t}\n \t}\n+\terrs |= checkout_remaining_link_copies();\n \tstop_progress(&progress);\n \tif (o->update)\n \t\tgit_attr_set_direction(GIT_ATTR_CHECKIN, NULL);\n@@ -1226,6 +1227,16 @@ static int verify_uptodate_1(struct cache_entry *ce,\n \t\t */\n \t\tif (S_ISGITLINK(ce->ce_mode))\n \t\t\treturn 0;\n+//\n+//\t\tif (copy_symlinks) {\n+//\t\t\tprintf(\"%s, %0x A=%d, B=%d, c=%d\\n\", ce->name, st.st_mode, S_ISLNK(ce->ce_mode), (ce->ce_flags&CE_VALID), S_ISDIR(st.st_mode));\n+//\t\t\tif (S_ISLNK(ce->ce_mode) /*&& (ce->ce_flags&CE_VALID)*/ && S_ISDIR(st.st_mode))\n+//\t\t\t\treturn 0;\n+//\t\t\tprintf(\"%s, %0x A=%d, B=%d, c=%d\\n\", ce->name, st.st_mode, S_ISLNK(ce->ce_mode), (ce->ce_flags&CE_VALID), S_ISREG(st.st_mode));\n+//\t\t\tif (S_ISLNK(ce->ce_mode) /*&& (ce->ce_flags&CE_VALID)*/ && S_ISREG(st.st_mode))\n+//\t\t\t\treturn 0;\n+//\n+//\t\t}\n \t\terrno = 0;\n \t}\n \tif (errno == ENOENT)\ndiff --git a/wrapper.c b/wrapper.c\nindex 68739aa..2a1a483 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -2,6 +2,7 @@\n  * Various trivial helper wrappers around standard functions\n  */\n #include \"cache.h\"\n+#include \"dir.h\"\n \n static void do_nothing(size_t size)\n {\n@@ -400,7 +401,23 @@ int rmdir_or_warn(const char *file)\n \n int remove_or_warn(unsigned int mode, const char *file)\n {\n-\treturn S_ISGITLINK(mode) ? rmdir_or_warn(file) : unlink_or_warn(file);\n+\tif (S_ISGITLINK(mode))\n+\t\treturn rmdir_or_warn(file);\n+\tif (S_ISLNK(mode) && copy_symlinks) {\n+\t\tstruct stat sb;\n+\t\tif (!stat(file, &sb)) {\n+\t\t\tif (S_IFDIR & sb.st_mode) {\n+\t\t\t\tstruct strbuf dir = STRBUF_INIT;\n+\t\t\t\tstrbuf_add(&dir, file, strlen(file));\n+\t\t\t\treturn remove_dir_recursively(&dir, 0);\n+\t\t\t} else {\n+\t\t\t\treturn unlink_or_warn(file);\n+\t\t\t}\n+\t\t} else {\n+\t\t\tperror(\"oops\");\n+\t\t}\n+\t}\n+\treturn unlink_or_warn(file);\n }\n \n void warn_on_inaccessible(const char *path)\n-- \n1.8.0.msysgit.0.3.gc00b80c.dirty\n"},{"id":"204546","messageId":"7vd2yojbw2.fsf@alter.siamese.dyndns.org","threadId":"32278","inReplyTo":"1354747575-89277-1-git-send-email-robin.rosenberg@dewire.com","subject":"Re: [PATCH] RFC Optionally handle symbolic links as copies","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-12-05T23:51:41Z","receivedAt":"2012-12-05T23:51:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Robin Rosenberg <robin.rosenberg@dewire.com> writes:\n\n> If core.symlinks is set to copy then symbolic links in a git repository\n> will be checked out as copies of the file it points to.\n\nThat all sounds nice on surface when the primary thing you care\nabout is to fetch and check out other people's code and extract it\nto the working tree, but how well would that work on the checkin\nside?  What happens if I check out a symlink that points at a file\n(either in-tree or out-of-tree), make some changes that do not\ninvolve the symlink, and before I make the commit, an unrelated\nchange is made to the file the symlink is pointing at?\n\n> - git status - when do we report a diff. \n> \t- After checkout we should probably not\n> \t- if the \"linked\" files change?\n\nYeah, exactly.\n\n> \t- if a change in the copied directory chsnges\n\nThat, too.\n\n> \t- if a file in the copied diretory is added/removed\n> \t- update, should we update the copied structure automatically\n> \t  when the link target changes\n\nI personally do not think this is worth it.  It would be very useful\non the export/checkout side, so it may make sense to add it to \"git\narchive\", though.\n"},{"id":"204550","messageId":"1622149333.19335600.1354756984435.JavaMail.root@dewire.com","threadId":"32278","inReplyTo":"7vd2yojbw2.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] RFC Optionally handle symbolic links as copies","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2012-12-06T01:23:04Z","receivedAt":"2012-12-06T01:23:04Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"\n\n----- Ursprungligt meddelande -----\n> Robin Rosenberg <robin.rosenberg@dewire.com> writes:\n> \n> > If core.symlinks is set to copy then symbolic links in a git\n> > repository\n> > will be checked out as copies of the file it points to.\n> \n> That all sounds nice on surface when the primary thing you care\n> about is to fetch and check out other people's code and extract it\n> to the working tree, but how well would that work on the checkin\n> side?  What happens if I check out a symlink that points at a file\n> (either in-tree or out-of-tree), make some changes that do not\n> involve the symlink, and before I make the commit, an unrelated\n> change is made to the file the symlink is pointing at?\n> \n> > - git status - when do we report a diff.\n> > \t- After checkout we should probably not\n> > \t- if the \"linked\" files change?\n> \n> Yeah, exactly.\n> \n> > \t- if a change in the copied directory chsnges\n> \n> That, too.\n> \n> > \t- if a file in the copied diretory is added/removed\n> > \t- update, should we update the copied structure automatically\n> > \t  when the link target changes\n\nSome of the questions have proposals in the includes test script. A \nlittle more dangerous than having real symlinks ofcourse, but regardless\nof what one does with or without copied symlinks one can make mistakes\nand I feel letting Git do the copying is way better than having real\ncopies in the git repository. Another crappy scm which the users are\nconverting from does this and it works. A difference to git is that\nit (ok clearcase) makes all files read-only so there are fewer mays\nof making mistakes with the copies.\n\n> I personally do not think this is worth it.  It would be very useful\n> on the export/checkout side, so it may make sense to add it to \"git\n> archive\", though.\n\nIt makes sense, but it does not solve the problem at hand.\n\n-- robin\n"},{"id":"204552","messageId":"50C04255.8050209@viscovery.net","threadId":"32278","inReplyTo":"1354747575-89277-1-git-send-email-robin.rosenberg@dewire.com","subject":"Re: [PATCH] RFC Optionally handle symbolic links as copies","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2012-12-06T06:59:33Z","receivedAt":"2012-12-06T06:59:33Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 12/5/2012 23:46, schrieb Robin Rosenberg:\n> - git status - when do we report a diff. \n> \t- After checkout we should probably not\n\nAre you saying that it should be ignored that the index records a symbolic\nlink, but the worktree contains a regular file and that the regular file\ndoes not even contain the value of the symbolic link (like it would in the\ncore.symlinks=false case)?\n\n-- Hannes\n"},{"id":"204560","messageId":"954603524.19440856.1354794705772.JavaMail.root@dewire.com","threadId":"32278","inReplyTo":"50C04255.8050209@viscovery.net","subject":"Re: [PATCH] RFC Optionally handle symbolic links as copies","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2012-12-06T11:51:45Z","receivedAt":"2012-12-06T11:51:45Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"\n\n----- Ursprungligt meddelande -----\n> Am 12/5/2012 23:46, schrieb Robin Rosenberg:\n> > - git status - when do we report a diff.\n> > \t- After checkout we should probably not\n> \n> Are you saying that it should be ignored that the index records a\n> symbolic\n> link, but the worktree contains a regular file and that the regular\n> file\n> does not even contain the value of the symbolic link (like it would\n> in the\n> core.symlinks=false case)?\n\nWell we should have some notion of clean state. That could be handled\nin more than one way. We could just ignore it, or compare the copy with\nthe content of the link target. The first is good enough for a proof-of\nconcept, but perhaps not as a solution since we don't have good other\nmeans to check that the copy is stale.\n\n-- robin\n"},{"id":"204744","messageId":"50C89822.9050900@drmicha.warpmail.net","threadId":"32278","inReplyTo":"1622149333.19335600.1354756984435.JavaMail.root@dewire.com","subject":"Re: [PATCH] RFC Optionally handle symbolic links as copies","fromName":"Michael J Gruber","fromEmail":"git@drmicha.warpmail.net","sentAt":"2012-12-12T14:43:46Z","receivedAt":"2012-12-12T14:43:46Z","isPatch":true,"sender":{"key":"git@grubix.eu","avatar":"https://avatars.githubusercontent.com/u/233215?v=4"},"body":"Robin Rosenberg venit, vidit, dixit 06.12.2012 02:23:\n> \n> \n> ----- Ursprungligt meddelande -----\n>> Robin Rosenberg <robin.rosenberg@dewire.com> writes:\n>>\n>>> If core.symlinks is set to copy then symbolic links in a git\n>>> repository\n>>> will be checked out as copies of the file it points to.\n>>\n>> That all sounds nice on surface when the primary thing you care\n>> about is to fetch and check out other people's code and extract it\n>> to the working tree, but how well would that work on the checkin\n>> side?  What happens if I check out a symlink that points at a file\n>> (either in-tree or out-of-tree), make some changes that do not\n>> involve the symlink, and before I make the commit, an unrelated\n>> change is made to the file the symlink is pointing at?\n>>\n>>> - git status - when do we report a diff.\n>>> \t- After checkout we should probably not\n>>> \t- if the \"linked\" files change?\n>>\n>> Yeah, exactly.\n>>\n>>> \t- if a change in the copied directory chsnges\n>>\n>> That, too.\n>>\n>>> \t- if a file in the copied diretory is added/removed\n>>> \t- update, should we update the copied structure automatically\n>>> \t  when the link target changes\n> \n> Some of the questions have proposals in the includes test script. A \n> little more dangerous than having real symlinks ofcourse, but regardless\n> of what one does with or without copied symlinks one can make mistakes\n> and I feel letting Git do the copying is way better than having real\n> copies in the git repository. Another crappy scm which the users are\n> converting from does this and it works. A difference to git is that\n> it (ok clearcase) makes all files read-only so there are fewer mays\n> of making mistakes with the copies.\n> \n>> I personally do not think this is worth it.  It would be very useful\n>> on the export/checkout side, so it may make sense to add it to \"git\n>> archive\", though.\n> \n> It makes sense, but it does not solve the problem at hand.\n> \n> -- robin\n> \n\nWell, what is the problem at hand?\n\nYour commit message begins in present tense as if it described the\ncurrent state of git, when in fact it describes what the patch is about\nto achieve. This is confusing enough already,\n\nI don't see any mention of the purpose, other than \"content may be\nused\", which would be served perfectly by a copy-on-link feature on the\nexport side, as mentioned by Junio. Is git-archive|tar an option?\n\nMichael\n"},{"id":"204752","messageId":"1141725649.20938344.1355328914240.JavaMail.root@dewire.com","threadId":"32278","inReplyTo":"50C89822.9050900@drmicha.warpmail.net","subject":"Re: [PATCH] RFC Optionally handle symbolic links as copies","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2012-12-12T16:15:14Z","receivedAt":"2012-12-12T16:15:14Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"\n\n----- Ursprungligt meddelande -----\n> Robin Rosenberg venit, vidit, dixit 06.12.2012 02:23:\n> > \n> > \n> > ----- Ursprungligt meddelande -----\n> >> Robin Rosenberg <robin.rosenberg@dewire.com> writes:\n> >>\n> >>> If core.symlinks is set to copy then symbolic links in a git\n> >>> repository\n> >>> will be checked out as copies of the file it points to.\n> >>\n> >> That all sounds nice on surface when the primary thing you care\n> >> about is to fetch and check out other people's code and extract it\n> >> to the working tree, but how well would that work on the checkin\n> >> side?  What happens if I check out a symlink that points at a file\n> >> (either in-tree or out-of-tree), make some changes that do not\n> >> involve the symlink, and before I make the commit, an unrelated\n> >> change is made to the file the symlink is pointing at?\n> >>\n> >>> - git status - when do we report a diff.\n> >>> \t- After checkout we should probably not\n> >>> \t- if the \"linked\" files change?\n> >>\n> >> Yeah, exactly.\n> >>\n> >>> \t- if a change in the copied directory chsnges\n> >>\n> >> That, too.\n> >>\n> >>> \t- if a file in the copied diretory is added/removed\n> >>> \t- update, should we update the copied structure automatically\n> >>> \t  when the link target changes\n> > \n> > Some of the questions have proposals in the includes test script. A\n> > little more dangerous than having real symlinks ofcourse, but\n> > regardless\n> > of what one does with or without copied symlinks one can make\n> > mistakes\n> > and I feel letting Git do the copying is way better than having\n> > real\n> > copies in the git repository. Another crappy scm which the users\n> > are\n> > converting from does this and it works. A difference to git is that\n> > it (ok clearcase) makes all files read-only so there are fewer mays\n> > of making mistakes with the copies.\n> > \n> >> I personally do not think this is worth it.  It would be very\n> >> useful\n> >> on the export/checkout side, so it may make sense to add it to\n> >> \"git\n> >> archive\", though.\n> > \n> > It makes sense, but it does not solve the problem at hand.\n> > \n> > -- robin\n> > \n> \n> Well, what is the problem at hand?\n\nThe problem is that I'm converting a repo from clearcase to git and\nthere are lots of symbolic links. Symbolic links in clearcase on \nWindows is treated as file copy in snapshot views which means that\nyou get a copy of the linked file when you update the view. If the\nlink target changes you can update your view to refersh your copies.\n\n> Your commit message begins in present tense as if it described the\n> current state of git, when in fact it describes what the patch is\n> about\n> to achieve. This is confusing enough already,\n\nYou're right.\n\n> I don't see any mention of the purpose, other than \"content may be\n> used\", which would be served perfectly by a copy-on-link feature on\n> the\n> export side, as mentioned by Junio. Is git-archive|tar an option?\n\nI want the copy on checkout. The intent is to change things and\nthen commit.\n\nPerhaps I can convince people to let a script copy stuff instead.\n\n-- robin\n"},{"id":"204780","messageId":"7vy5h32f7k.fsf@alter.siamese.dyndns.org","threadId":"32278","inReplyTo":"1141725649.20938344.1355328914240.JavaMail.root@dewire.com","subject":"Re: [PATCH] RFC Optionally handle symbolic links as copies","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-12-12T20:22:23Z","receivedAt":"2012-12-12T20:22:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Robin Rosenberg <robin.rosenberg@dewire.com> writes:\n\n> I want the copy on checkout. The intent is to change things and\n> then commit.\n\nThat largely depends on what purpose each symlink is used for in the\nproject.\n\nSuppose you have a symlink A and another symlink X in the project,\nwhere A points at another path B inside the working tree, and X\npoints at a path outside, say, /etc/motd.  Upon checkout, you make\nregular files A and X that store the contents of the files they\npoint at, and then you edit A and X.\n\nNow, what should happen on the next \"git add A X\"?\n\n * Perhaps it (or any step before \"git add\", or something even\n   outside git) should notice that you modified A that is supposed\n   to represent a copy of B but their contents have drifted.  It\n   should raise a red flag, or take a remedial action, no?\n\n * Perhaps it should copy the updated contents in A to B and run\n   \"git add\" on that one instead, without changing anything else?\n\n * Imagine a project with many template files B, C, ..., where A\n   points at \"the default template\".  You may be designating a\n   different template file as the new default.  On a symlink-capable\n   system you would just do \"rm -f A && ln -s C A\", but because you\n   chose to make a copy of B and store it as a regular file in A, a\n   natural way to update it may be to do \"cp C A\".\n\n   Would you find another file C in the working tree that may be\n   different from B that has the same contents as A, and update the\n   symbolic link A to point at C instead?  Do so only with the\n   contents of A and all the other files in the working tree?  What\n   if there is another template file identical to C?\n\nI didn't even touched the cases where you have to deal with your\nupdates to X.\n\nThis is looking more and more outside the scope of Git to me.\n"}]}