racy.c 9.04 KB
Newer Older
1
#include "clar_libgit2.h"
2
#include "../checkout/checkout_helpers.h"
3

4
#include "index.h"
5
#include "repository.h"
6 7 8

static git_repository *g_repo;

9
void test_index_racy__initialize(void)
10 11 12 13
{
	cl_git_pass(git_repository_init(&g_repo, "diff_racy", false));
}

14
void test_index_racy__cleanup(void)
15
{
16 17 18 19
	git_repository_free(g_repo);
	g_repo = NULL;

	cl_fixture_cleanup("diff_racy");
20 21
}

22
void test_index_racy__diff(void)
23 24 25
{
	git_index *index;
	git_diff *diff;
26
	git_str path = GIT_STR_INIT;
27

28
	cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "A"));
29 30 31 32 33 34 35 36 37
	cl_git_mkfile(path.ptr, "A");

	/* Put 'A' into the index */
	cl_git_pass(git_repository_index(&index, g_repo));
	cl_git_pass(git_index_add_bypath(index, "A"));
	cl_git_pass(git_index_write(index));

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL));
	cl_assert_equal_i(0, git_diff_num_deltas(diff));
38
	git_diff_free(diff);
39 40 41 42 43 44

	/* Change its contents quickly, so we get the same timestamp */
	cl_git_mkfile(path.ptr, "B");

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL));
	cl_assert_equal_i(1, git_diff_num_deltas(diff));
45 46 47

	git_index_free(index);
	git_diff_free(diff);
48
	git_str_dispose(&path);
49
}
50

51
void test_index_racy__write_index_just_after_file(void)
52 53 54
{
	git_index *index;
	git_diff *diff;
55
	git_str path = GIT_STR_INIT;
56
	struct p_timeval times[2];
57 58 59 60 61

	/* Make sure we do have a timestamp */
	cl_git_pass(git_repository_index(&index, g_repo));
	cl_git_pass(git_index_write(index));

62
	cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "A"));
63
	cl_git_mkfile(path.ptr, "A");
64
	/* Force the file's timestamp to be a second after we wrote the index */
65 66 67 68
	times[0].tv_sec = index->stamp.mtime.tv_sec + 1;
	times[0].tv_usec = index->stamp.mtime.tv_nsec / 1000;
	times[1].tv_sec = index->stamp.mtime.tv_sec + 1;
	times[1].tv_usec = index->stamp.mtime.tv_nsec / 1000;
69
	cl_git_pass(p_utimes(path.ptr, times));
70 71 72 73

	/*
	 * Put 'A' into the index, the size field will be filled,
	 * because the index' on-disk timestamp does not match the
74
	 * file's timestamp.
75 76 77 78 79
	 */
	cl_git_pass(git_index_add_bypath(index, "A"));
	cl_git_pass(git_index_write(index));

	cl_git_mkfile(path.ptr, "B");
80
	/*
Dmitriy Olshevskiy committed
81
	 * Pretend this index' modification happened a second after the
82 83
	 * file update, and rewrite the file in that same second.
	 */
84 85 86 87
	times[0].tv_sec = index->stamp.mtime.tv_sec + 2;
	times[0].tv_usec = index->stamp.mtime.tv_nsec / 1000;
	times[1].tv_sec = index->stamp.mtime.tv_sec + 2;
	times[0].tv_usec = index->stamp.mtime.tv_nsec / 1000;
88 89 90 91 92

	cl_git_pass(p_utimes(git_index_path(index), times));
	cl_git_pass(p_utimes(path.ptr, times));

	cl_git_pass(git_index_read(index, true));
93 94 95

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL));
	cl_assert_equal_i(1, git_diff_num_deltas(diff));
96

97
	git_str_dispose(&path);
98 99
	git_diff_free(diff);
	git_index_free(index);
100
}
101

102 103

static void setup_race(void)
104
{
105
	git_str path = GIT_STR_INIT;
106
	git_index *index;
107 108
	git_index_entry *entry;
	struct stat st;
109 110

	/* Make sure we do have a timestamp */
111
	cl_git_pass(git_repository_index__weakptr(&index, g_repo));
112 113
	cl_git_pass(git_index_write(index));

114
	cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "A"));
115

116 117
	cl_git_mkfile(path.ptr, "A");
	cl_git_pass(git_index_add_bypath(index, "A"));
118

119 120
	cl_git_mkfile(path.ptr, "B");
	cl_git_pass(git_index_write(index));
121

122
	cl_git_mkfile(path.ptr, "");
123

124 125
	cl_git_pass(p_stat(path.ptr, &st));
	cl_assert(entry = (git_index_entry *)git_index_get_bypath(index, "A", 0));
126

127
	/* force a race */
128 129
	entry->mtime.seconds = (int32_t)st.st_mtime;
	entry->mtime.nanoseconds = (int32_t)st.st_mtime_nsec;
130

131
	git_str_dispose(&path);
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
}

void test_index_racy__smudges_index_entry_on_save(void)
{
	git_index *index;
	const git_index_entry *entry;

	setup_race();

	/* write the index, which will smudge anything that had the same timestamp
	 * as the index when the index was loaded.  that way future loads of the
	 * index (with the new timestamp) will know that these files were not
	 * clean.
	 */

	cl_git_pass(git_repository_index__weakptr(&index, g_repo));
	cl_git_pass(git_index_write(index));

	cl_assert(entry = git_index_get_bypath(index, "A", 0));
151
	cl_assert_equal_i(0, entry->file_size);
152 153 154 155 156 157 158 159 160 161
}

void test_index_racy__detects_diff_of_change_in_identical_timestamp(void)
{
	git_index *index;
	git_diff *diff;

	cl_git_pass(git_repository_index__weakptr(&index, g_repo));

	setup_race();
162 163 164

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL));
	cl_assert_equal_i(1, git_diff_num_deltas(diff));
165 166

	git_diff_free(diff);
167
}
168 169 170

static void setup_uptodate_files(void)
{
171
	git_str path = GIT_STR_INIT;
172
	git_index *index;
173
	const git_index_entry *a_entry;
174
	git_index_entry new_entry = {{0}};
175 176 177

	cl_git_pass(git_repository_index(&index, g_repo));

178
	cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "A"));
179 180 181 182 183
	cl_git_mkfile(path.ptr, "A");

	/* Put 'A' into the index */
	cl_git_pass(git_index_add_bypath(index, "A"));

184 185
	cl_assert((a_entry = git_index_get_bypath(index, "A", 0)));

186 187 188
	/* Put 'B' into the index */
	new_entry.path = "B";
	new_entry.mode = GIT_FILEMODE_BLOB;
189
	git_oid_cpy(&new_entry.id, &a_entry->id);
190 191 192 193 194
	cl_git_pass(git_index_add(index, &new_entry));

	/* Put 'C' into the index */
	new_entry.path = "C";
	new_entry.mode = GIT_FILEMODE_BLOB;
195
	cl_git_pass(git_index_add_from_buffer(index, &new_entry, "hello!\n", 7));
196 197

	git_index_free(index);
198
	git_str_dispose(&path);
199 200 201 202 203 204 205 206 207 208 209 210 211
}

void test_index_racy__adding_to_index_is_uptodate(void)
{
	git_index *index;
	const git_index_entry *entry;

	setup_uptodate_files();

	cl_git_pass(git_repository_index(&index, g_repo));

	/* ensure that they're all uptodate */
	cl_assert((entry = git_index_get_bypath(index, "A", 0)));
212
	cl_assert_equal_i(GIT_INDEX_ENTRY_UPTODATE, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE));
213 214

	cl_assert((entry = git_index_get_bypath(index, "B", 0)));
215
	cl_assert_equal_i(GIT_INDEX_ENTRY_UPTODATE, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE));
216 217

	cl_assert((entry = git_index_get_bypath(index, "C", 0)));
218
	cl_assert_equal_i(GIT_INDEX_ENTRY_UPTODATE, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE));
219 220 221 222 223 224 225 226 227 228 229 230

	cl_git_pass(git_index_write(index));

	git_index_free(index);
}

void test_index_racy__reading_clears_uptodate_bit(void)
{
	git_index *index;
	const git_index_entry *entry;

	setup_uptodate_files();
231

232 233 234 235 236 237 238
	cl_git_pass(git_repository_index(&index, g_repo));
	cl_git_pass(git_index_write(index));

	cl_git_pass(git_index_read(index, true));

	/* ensure that no files are uptodate */
	cl_assert((entry = git_index_get_bypath(index, "A", 0)));
239
	cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE));
240 241

	cl_assert((entry = git_index_get_bypath(index, "B", 0)));
242
	cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE));
243 244

	cl_assert((entry = git_index_get_bypath(index, "C", 0)));
245
	cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE));
246 247 248

	git_index_free(index);
}
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265

void test_index_racy__read_tree_clears_uptodate_bit(void)
{
	git_index *index;
	git_tree *tree;
	const git_index_entry *entry;
	git_oid id;

	setup_uptodate_files();

	cl_git_pass(git_repository_index(&index, g_repo));
	cl_git_pass(git_index_write_tree_to(&id, index, g_repo));
	cl_git_pass(git_tree_lookup(&tree, g_repo, &id));
	cl_git_pass(git_index_read_tree(index, tree));

	/* ensure that no files are uptodate */
	cl_assert((entry = git_index_get_bypath(index, "A", 0)));
266
	cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE));
267 268

	cl_assert((entry = git_index_get_bypath(index, "B", 0)));
269
	cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE));
270 271

	cl_assert((entry = git_index_get_bypath(index, "C", 0)));
272
	cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE));
273 274 275 276

	git_tree_free(tree);
	git_index_free(index);
}
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312

void test_index_racy__read_index_smudges(void)
{
	git_index *index, *newindex;
	const git_index_entry *entry;

	/* if we are reading an index into our new index, ensure that any
	 * racy entries in the index that we're reading are smudged so that
	 * we don't propagate their timestamps without further investigation.
	 */
	setup_race();

	cl_git_pass(git_repository_index(&index, g_repo));
	cl_git_pass(git_index_new(&newindex));
	cl_git_pass(git_index_read_index(newindex, index));

	cl_assert(entry = git_index_get_bypath(newindex, "A", 0));
	cl_assert_equal_i(0, entry->file_size);

	git_index_free(index);
	git_index_free(newindex);
}

void test_index_racy__read_index_clears_uptodate_bit(void)
{
	git_index *index, *newindex;
	const git_index_entry *entry;

	setup_uptodate_files();

	cl_git_pass(git_repository_index(&index, g_repo));
	cl_git_pass(git_index_new(&newindex));
	cl_git_pass(git_index_read_index(newindex, index));

	/* ensure that files brought in from the other index are not uptodate */
	cl_assert((entry = git_index_get_bypath(newindex, "A", 0)));
313
	cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE));
314 315

	cl_assert((entry = git_index_get_bypath(newindex, "B", 0)));
316
	cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE));
317 318

	cl_assert((entry = git_index_get_bypath(newindex, "C", 0)));
319
	cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE));
320 321 322 323

	git_index_free(index);
	git_index_free(newindex);
}