{"thread":{"id":"26706","subject":"[PATCH] rerere: make sure it works even in a workdir attached to a young repository","startedAt":"2011-03-11T00:46:40Z","lastAt":"2011-03-11T00:46:40Z","messageCount":1,"participants":["Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"163194","messageId":"7vr5ae1o27.fsf@alter.siamese.dyndns.org","threadId":"26706","inReplyTo":null,"subject":"[PATCH] rerere: make sure it works even in a workdir attached to a young repository","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-03-11T00:46:40Z","receivedAt":"2011-03-11T00:46:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The git-new-workdir script in contrib/ makes a new work tree by sharing\nmany subdirectories of the .git directory with the original repository.\nWhen rerere.enabled is set in the original repository, but the user has\nnot encountered any conflicts yet, the original repository may not yet\nhave .git/rr-cache directory.\n\nWhen rerere wants to run in a new work tree created from such a young\noriginal repository, it fails to mkdir(2) .git/rr-cache that is a symlink\nto a yet-to-be-created directory.\n\nThere are three possible approaches to this:\n\n - A naive solution is not to create a symlink in the git-new-workdir\n   script to a directory the original does not have (yet).  This is not a\n   solution, as we tend to lazily create subdirectories of .git/, and\n   having rerere.enabled configuration set is a strong indication that the\n   user _wants_ to have this lazy creation to happen;\n\n - We could always create .git/rr-cache upon repository creation.  This is\n   tempting but will not help people with existing repositories.\n\n - Detect this case by seeing that mkdir(2) failed with EEXIST, checking\n   that the path is a symlink, and try running mkdir(2) on the link\n   target.\n\nThis patch solves the issue by doing the third one.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n\n---\n\n * As the third test in the new test t1021 says, this is incomplete in\n   that it does not attempt to handle relative symbolic link that points\n   into the original repository.  It is good enough for a workdir the\n   contrib script creates, but I wouldn't be surprised if there are\n   scripts that creates a similar thin workdir using relative symbolic\n   links, and this _will_ fail in such a setting.\n\n cache.h                      |    1 +\n rerere.c                     |    3 +-\n sha1_file.c                  |   29 +++++++++++++++++++++++\n t/t1021-rerere-in-workdir.sh |   52 ++++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 83 insertions(+), 2 deletions(-)\n create mode 100755 t/t1021-rerere-in-workdir.sh\n\ndiff --git a/cache.h b/cache.h\nindex 6e54993..2eddee7 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -674,6 +674,7 @@ int set_shared_perm(const char *path, int mode);\n #define adjust_shared_perm(path) set_shared_perm((path), 0)\n int safe_create_leading_directories(char *path);\n int safe_create_leading_directories_const(const char *path);\n+int mkdir_in_gitdir(const char *path);\n extern char *expand_user_path(const char *path);\n char *enter_repo(char *path, int strict);\n static inline int is_absolute_path(const char *path)\ndiff --git a/rerere.c b/rerere.c\nindex a59f74f..2d8de61 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -522,8 +522,7 @@ static int is_rerere_enabled(void)\n \tif (rerere_enabled < 0)\n \t\treturn rr_cache_exists;\n \n-\tif (!rr_cache_exists &&\n-\t    (mkdir(rr_cache, 0777) || adjust_shared_perm(rr_cache)))\n+\tif (!rr_cache_exists && mkdir_in_gitdir(rr_cache))\n \t\tdie(\"Could not create directory %s\", rr_cache);\n \treturn 1;\n }\ndiff --git a/sha1_file.c b/sha1_file.c\nindex c23cc5e..0926643 100644\n--- a/sha1_file.c\n+++ b/sha1_file.c\n@@ -42,6 +42,35 @@ static inline int offset_1st_component(const char *path)\n \treturn *path == '/';\n }\n \n+int mkdir_in_gitdir(const char *path)\n+{\n+\tif (mkdir(path, 0777)) {\n+\t\tint saved_errno = errno;\n+\t\tstruct stat st;\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\tif (errno != EEXIST)\n+\t\t\treturn -1;\n+\t\t/*\n+\t\t * Are we looking at a path in a symlinked worktree\n+\t\t * whose original repository does not yet have it?\n+\t\t * e.g. .git/rr-cache pointing at its original\n+\t\t * repository in which the user hasn't performed any\n+\t\t * conflict resolution yet?\n+\t\t */\n+\t\tif (lstat(path, &st) || !S_ISLNK(st.st_mode) ||\n+\t\t    strbuf_readlink(&sb, path, st.st_size) ||\n+\t\t    !is_absolute_path(sb.buf) ||\n+\t\t    mkdir(sb.buf, 0777)) {\n+\t\t\tstrbuf_release(&sb);\n+\t\t\terrno = saved_errno;\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstrbuf_release(&sb);\n+\t}\n+\treturn adjust_shared_perm(path);\n+}\n+\n int safe_create_leading_directories(char *path)\n {\n \tchar *pos = path + offset_1st_component(path);\ndiff --git a/t/t1021-rerere-in-workdir.sh b/t/t1021-rerere-in-workdir.sh\nnew file mode 100755\nindex 0000000..ec8621c\n--- /dev/null\n+++ b/t/t1021-rerere-in-workdir.sh\n@@ -0,0 +1,52 @@\n+#!/bin/sh\n+\n+test_description='rerere run in a workdir'\n+. ./test-lib.sh\n+\n+test_expect_success SYMLINKS setup '\n+\tgit config rerere.enabled true &&\n+\t>world &&\n+\tgit add world &&\n+\ttest_tick &&\n+\tgit commit -m initial &&\n+\n+\techo hello >world &&\n+\ttest_tick &&\n+\tgit commit -a -m hello &&\n+\n+\tgit checkout -b side HEAD^ &&\n+\techo goodbye >world &&\n+\ttest_tick &&\n+\tgit commit -a -m goodbye &&\n+\n+\tgit checkout master\n+'\n+\n+test_expect_success SYMLINKS 'rerere in workdir' '\n+\trm -rf .git/rr-cache &&\n+\t\"$SHELL_PATH\" \"$TEST_DIRECTORY/../contrib/workdir/git-new-workdir\" . work &&\n+\t(\n+\t\tcd work &&\n+\t\ttest_must_fail git merge side &&\n+\t\tgit rerere status >actual &&\n+\t\techo world >expect &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# This fails because we don't resolve relative symlink in mkdir_in_gitdir()\n+test_expect_failure SYMLINKS 'rerere in workdir (relative)' '\n+\trm -rf .git/rr-cache &&\n+\t\"$SHELL_PATH\" \"$TEST_DIRECTORY/../contrib/workdir/git-new-workdir\" . krow &&\n+\t(\n+\t\tcd krow &&\n+\t\trm -f .git/rr-cache &&\n+\t\tln -s ../.git/rr-cache .git/rr-cache &&\n+\t\ttest_must_fail git merge side &&\n+\t\tgit rerere status >actual &&\n+\t\techo world >expect &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_done\n-- \n1.7.4.1.373.g37629\n"}]}