update-index: test the system before enabling untracked cache

Helped-by: Eric Sunshine <sunshine@sunshineco.com> Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Nguyễn Thái Ngọc Duy committed Mar 8, 2015 at 17:12 UTC f64cb88d3521b64b2db9353d14148328063745dc
2 files changed +174
Documentation/git-update-index.txt
+6
@@ -178,6 +178,12 @@ may not support it yet.
178 system must change `st_mtime` field of a directory if files
179 are added or deleted in that directory.
180
181 +--force-untracked-cache::
182 + For safety, `--untracked-cache` performs tests on the working
183 + directory to make sure untracked cache can be used. These
184 + tests can take a few seconds. `--force-untracked-cache` can be
185 + used to skip the tests.
186 +
187 \--::
188 Do not interpret any more arguments as options.
189
builtin/update-index.c
+168
@@ -33,6 +33,7 @@ static int mark_valid_only;
33 static int mark_skip_worktree_only;
34 #define MARK_FLAG 1
35 #define UNMARK_FLAG 2
36 +static struct strbuf mtime_dir = STRBUF_INIT;
37
38 __attribute__((format (printf, 1, 2)))
39 static void report(const char *fmt, ...)
@@ -48,6 +49,166 @@ static void report(const char *fmt, ...)
49 va_end(vp);
50 }
51
52 +static void remove_test_directory(void)
53 +{
54 + if (mtime_dir.len)
55 + remove_dir_recursively(&mtime_dir, 0);
56 +}
57 +
58 +static const char *get_mtime_path(const char *path)
59 +{
60 + static struct strbuf sb = STRBUF_INIT;
61 + strbuf_reset(&sb);
62 + strbuf_addf(&sb, "%s/%s", mtime_dir.buf, path);
63 + return sb.buf;
64 +}
65 +
66 +static void xmkdir(const char *path)
67 +{
68 + path = get_mtime_path(path);
69 + if (mkdir(path, 0700))
70 + die_errno(_("failed to create directory %s"), path);
71 +}
72 +
73 +static int xstat_mtime_dir(struct stat *st)
74 +{
75 + if (stat(mtime_dir.buf, st))
76 + die_errno(_("failed to stat %s"), mtime_dir.buf);
77 + return 0;
78 +}
79 +
80 +static int create_file(const char *path)
81 +{
82 + int fd;
83 + path = get_mtime_path(path);
84 + fd = open(path, O_CREAT | O_RDWR, 0644);
85 + if (fd < 0)
86 + die_errno(_("failed to create file %s"), path);
87 + return fd;
88 +}
89 +
90 +static void xunlink(const char *path)
91 +{
92 + path = get_mtime_path(path);
93 + if (unlink(path))
94 + die_errno(_("failed to delete file %s"), path);
95 +}
96 +
97 +static void xrmdir(const char *path)
98 +{
99 + path = get_mtime_path(path);
100 + if (rmdir(path))
101 + die_errno(_("failed to delete directory %s"), path);
102 +}
103 +
104 +static void avoid_racy(void)
105 +{
106 + /*
107 + * not use if we could usleep(10) if USE_NSEC is defined. The
108 + * field nsec could be there, but the OS could choose to
109 + * ignore it?
110 + */
111 + sleep(1);
112 +}
113 +
114 +static int test_if_untracked_cache_is_supported(void)
115 +{
116 + struct stat st;
117 + struct stat_data base;
118 + int fd, ret = 0;
119 +
120 + strbuf_addstr(&mtime_dir, "mtime-test-XXXXXX");
121 + if (!mkdtemp(mtime_dir.buf))
122 + die_errno("Could not make temporary directory");
123 +
124 + fprintf(stderr, _("Testing "));
125 + atexit(remove_test_directory);
126 + xstat_mtime_dir(&st);
127 + fill_stat_data(&base, &st);
128 + fputc('.', stderr);
129 +
130 + avoid_racy();
131 + fd = create_file("newfile");
132 + xstat_mtime_dir(&st);
133 + if (!match_stat_data(&base, &st)) {
134 + close(fd);
135 + fputc('\n', stderr);
136 + fprintf_ln(stderr,_("directory stat info does not "
137 + "change after adding a new file"));
138 + goto done;
139 + }
140 + fill_stat_data(&base, &st);
141 + fputc('.', stderr);
142 +
143 + avoid_racy();
144 + xmkdir("new-dir");
145 + xstat_mtime_dir(&st);
146 + if (!match_stat_data(&base, &st)) {
147 + close(fd);
148 + fputc('\n', stderr);
149 + fprintf_ln(stderr, _("directory stat info does not change "
150 + "after adding a new directory"));
151 + goto done;
152 + }
153 + fill_stat_data(&base, &st);
154 + fputc('.', stderr);
155 +
156 + avoid_racy();
157 + write_or_die(fd, "data", 4);
158 + close(fd);
159 + xstat_mtime_dir(&st);
160 + if (match_stat_data(&base, &st)) {
161 + fputc('\n', stderr);
162 + fprintf_ln(stderr, _("directory stat info changes "
163 + "after updating a file"));
164 + goto done;
165 + }
166 + fputc('.', stderr);
167 +
168 + avoid_racy();
169 + close(create_file("new-dir/new"));
170 + xstat_mtime_dir(&st);
171 + if (match_stat_data(&base, &st)) {
172 + fputc('\n', stderr);
173 + fprintf_ln(stderr, _("directory stat info changes after "
174 + "adding a file inside subdirectory"));
175 + goto done;
176 + }
177 + fputc('.', stderr);
178 +
179 + avoid_racy();
180 + xunlink("newfile");
181 + xstat_mtime_dir(&st);
182 + if (!match_stat_data(&base, &st)) {
183 + fputc('\n', stderr);
184 + fprintf_ln(stderr, _("directory stat info does not "
185 + "change after deleting a file"));
186 + goto done;
187 + }
188 + fill_stat_data(&base, &st);
189 + fputc('.', stderr);
190 +
191 + avoid_racy();
192 + xunlink("new-dir/new");
193 + xrmdir("new-dir");
194 + xstat_mtime_dir(&st);
195 + if (!match_stat_data(&base, &st)) {
196 + fputc('\n', stderr);
197 + fprintf_ln(stderr, _("directory stat info does not "
198 + "change after deleting a directory"));
199 + goto done;
200 + }
201 +
202 + if (rmdir(mtime_dir.buf))
203 + die_errno(_("failed to delete directory %s"), mtime_dir.buf);
204 + fprintf_ln(stderr, _(" OK"));
205 + ret = 1;
206 +
207 +done:
208 + strbuf_release(&mtime_dir);
209 + return ret;
210 +}
211 +
212 static int mark_ce_flags(const char *path, int flag, int mark)
213 {
214 int namelen = strlen(path);
@@ -835,6 +996,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
996 N_("enable or disable split index")),
997 OPT_BOOL(0, "untracked-cache", &untracked_cache,
998 N_("enable/disable untracked cache")),
999 + OPT_SET_INT(0, "force-untracked-cache", &untracked_cache,
1000 + N_("enable untracked cache without testing the filesystem"), 2),
1001 OPT_END()
1002 };
1003
@@ -944,6 +1107,11 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
1107 if (untracked_cache > 0 && !the_index.untracked) {
1108 struct untracked_cache *uc;
1109
1110 + if (untracked_cache < 2) {
1111 + setup_work_tree();
1112 + if (!test_if_untracked_cache_is_supported())
1113 + return 1;
1114 + }
1115 uc = xcalloc(1, sizeof(*uc));
1116 uc->exclude_per_dir = ".gitignore";
1117 /* should be the same flags used by git-status */