@matheusbsbs / matheus-bagliano / commits / 90a6464b4a
Fork 0

rerere: make sure it works even in a workdir attached to a young repository

The git-new-workdir script in contrib/ makes a new work tree by sharing many subdirectories of the .git directory with the original repository. When rerere.enabled is set in the original repository, but the user has not encountered any conflicts yet, the original repository may not yet have .git/rr-cache directory. When rerere wants to run in a new work tree created from such a young original repository, it fails to mkdir(2) .git/rr-cache that is a symlink to a yet-to-be-created directory. There are three possible approaches to this: - A naive solution is not to create a symlink in the git-new-workdir script to a directory the original does not have (yet). This is not a solution, as we tend to lazily create subdirectories of .git/, and having rerere.enabled configuration set is a strong indication that the user _wants_ to have this lazy creation to happen; - We could always create .git/rr-cache upon repository creation. This is tempting but will not help people with existing repositories. - Detect this case by seeing that mkdir(2) failed with EEXIST, checking that the path is a symlink, and try running mkdir(2) on the link target. This patch solves the issue by doing the third one. Strictly speaking, this is incomplete. It does not attempt to handle relative symbolic link that points into the original repository, but this is good enough to help people who use contrib/workdir/git-new-workdir script. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Mar 10, 2011 at 16:02 UTC 90a6464b4ad5887e4d12e660f51dabdbd5c00812
4 files changed +86 -2
cache.h
+1
@@ -674,6 +674,7 @@ int set_shared_perm(const char *path, int mode);
674 #define adjust_shared_perm(path) set_shared_perm((path), 0)
675 int safe_create_leading_directories(char *path);
676 int safe_create_leading_directories_const(const char *path);
677 +int mkdir_in_gitdir(const char *path);
678 extern char *expand_user_path(const char *path);
679 char *enter_repo(char *path, int strict);
680 static inline int is_absolute_path(const char *path)
rerere.c
+1 -2
@@ -522,8 +522,7 @@ static int is_rerere_enabled(void)
522 if (rerere_enabled < 0)
523 return rr_cache_exists;
524
525 - if (!rr_cache_exists &&
526 - (mkdir(rr_cache, 0777) || adjust_shared_perm(rr_cache)))
525 + if (!rr_cache_exists && mkdir_in_gitdir(rr_cache))
526 die("Could not create directory %s", rr_cache);
527 return 1;
528 }
sha1_file.c
+29
@@ -42,6 +42,35 @@ static inline int offset_1st_component(const char *path)
42 return *path == '/';
43 }
44
45 +int mkdir_in_gitdir(const char *path)
46 +{
47 + if (mkdir(path, 0777)) {
48 + int saved_errno = errno;
49 + struct stat st;
50 + struct strbuf sb = STRBUF_INIT;
51 +
52 + if (errno != EEXIST)
53 + return -1;
54 + /*
55 + * Are we looking at a path in a symlinked worktree
56 + * whose original repository does not yet have it?
57 + * e.g. .git/rr-cache pointing at its original
58 + * repository in which the user hasn't performed any
59 + * conflict resolution yet?
60 + */
61 + if (lstat(path, &st) || !S_ISLNK(st.st_mode) ||
62 + strbuf_readlink(&sb, path, st.st_size) ||
63 + !is_absolute_path(sb.buf) ||
64 + mkdir(sb.buf, 0777)) {
65 + strbuf_release(&sb);
66 + errno = saved_errno;
67 + return -1;
68 + }
69 + strbuf_release(&sb);
70 + }
71 + return adjust_shared_perm(path);
72 +}
73 +
74 int safe_create_leading_directories(char *path)
75 {
76 char *pos = path + offset_1st_component(path);
t/t1021-rerere-in-workdir.sh new
+55
@@ -0,0 +1,55 @@
1 +#!/bin/sh
2 +
3 +test_description='rerere run in a workdir'
4 +. ./test-lib.sh
5 +
6 +test_expect_success SYMLINKS setup '
7 + git config rerere.enabled true &&
8 + >world &&
9 + git add world &&
10 + test_tick &&
11 + git commit -m initial &&
12 +
13 + echo hello >world &&
14 + test_tick &&
15 + git commit -a -m hello &&
16 +
17 + git checkout -b side HEAD^ &&
18 + echo goodbye >world &&
19 + test_tick &&
20 + git commit -a -m goodbye &&
21 +
22 + git checkout master
23 +'
24 +
25 +test_expect_success SYMLINKS 'rerere in workdir' '
26 + rm -rf .git/rr-cache &&
27 + "$SHELL_PATH" "$TEST_DIRECTORY/../contrib/workdir/git-new-workdir" . work &&
28 + (
29 + cd work &&
30 + test_must_fail git merge side &&
31 + git rerere status >actual &&
32 + echo world >expect &&
33 + test_cmp expect actual
34 + )
35 +'
36 +
37 +# This fails because we don't resolve relative symlink in mkdir_in_gitdir()
38 +# For the purpose of helping contrib/workdir/git-new-workdir users, we do not
39 +# have to support relative symlinks, but it might be nicer to make this work
40 +# with a relative symbolic link someday.
41 +test_expect_failure SYMLINKS 'rerere in workdir (relative)' '
42 + rm -rf .git/rr-cache &&
43 + "$SHELL_PATH" "$TEST_DIRECTORY/../contrib/workdir/git-new-workdir" . krow &&
44 + (
45 + cd krow &&
46 + rm -f .git/rr-cache &&
47 + ln -s ../.git/rr-cache .git/rr-cache &&
48 + test_must_fail git merge side &&
49 + git rerere status >actual &&
50 + echo world >expect &&
51 + test_cmp expect actual
52 + )
53 +'
54 +
55 +test_done