1 #include "clar_libgit2.h"
2 #include "posix.h"
3 #include "path.h"
4 #include "submodule_helpers.h"
5 #include "futils.h"
6 #include "iterator.h"
7 
8 static git_repository *g_repo = NULL;
9 
test_submodule_status__initialize(void)10 void test_submodule_status__initialize(void)
11 {
12 	g_repo = setup_fixture_submod2();
13 }
14 
test_submodule_status__cleanup(void)15 void test_submodule_status__cleanup(void)
16 {
17 }
18 
test_submodule_status__unchanged(void)19 void test_submodule_status__unchanged(void)
20 {
21 	unsigned int status = get_submodule_status(g_repo, "sm_unchanged");
22 	unsigned int expected =
23 		GIT_SUBMODULE_STATUS_IN_HEAD |
24 		GIT_SUBMODULE_STATUS_IN_INDEX |
25 		GIT_SUBMODULE_STATUS_IN_CONFIG |
26 		GIT_SUBMODULE_STATUS_IN_WD;
27 
28 	cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
29 	cl_assert(expected == status);
30 }
31 
rm_submodule(const char * name)32 static void rm_submodule(const char *name)
33 {
34 	git_buf path = GIT_BUF_INIT;
35 	cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), name));
36 	cl_git_pass(git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES));
37 	git_buf_dispose(&path);
38 }
39 
add_submodule_to_index(const char * name)40 static void add_submodule_to_index(const char *name)
41 {
42 	git_submodule *sm;
43 	cl_git_pass(git_submodule_lookup(&sm, g_repo, name));
44 	cl_git_pass(git_submodule_add_to_index(sm, true));
45 	git_submodule_free(sm);
46 }
47 
rm_submodule_from_index(const char * name)48 static void rm_submodule_from_index(const char *name)
49 {
50 	git_index *index;
51 	size_t pos;
52 
53 	cl_git_pass(git_repository_index(&index, g_repo));
54 	cl_assert(!git_index_find(&pos, index, name));
55 	cl_git_pass(git_index_remove(index, name, 0));
56 	cl_git_pass(git_index_write(index));
57 	git_index_free(index);
58 }
59 
60 /* 4 values of GIT_SUBMODULE_IGNORE to check */
61 
test_submodule_status__ignore_none(void)62 void test_submodule_status__ignore_none(void)
63 {
64 	unsigned int status;
65 
66 	rm_submodule("sm_unchanged");
67 
68 	refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND);
69 	refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS);
70 	refute_submodule_exists(g_repo, "not", GIT_EEXISTS);
71 
72 	status = get_submodule_status(g_repo, "sm_changed_index");
73 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) != 0);
74 
75 	status = get_submodule_status(g_repo, "sm_changed_head");
76 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
77 
78 	status = get_submodule_status(g_repo, "sm_changed_file");
79 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) != 0);
80 
81 	status = get_submodule_status(g_repo, "sm_changed_untracked_file");
82 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNTRACKED) != 0);
83 
84 	status = get_submodule_status(g_repo, "sm_missing_commits");
85 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
86 
87 	status = get_submodule_status(g_repo, "sm_added_and_uncommited");
88 	cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0);
89 
90 	/* removed sm_unchanged for deleted workdir */
91 	status = get_submodule_status(g_repo, "sm_unchanged");
92 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0);
93 
94 	/* now mkdir sm_unchanged to test uninitialized */
95 	cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL));
96 	status = get_submodule_status(g_repo, "sm_unchanged");
97 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0);
98 
99 	/* update sm_changed_head in index */
100 	add_submodule_to_index("sm_changed_head");
101 	status = get_submodule_status(g_repo, "sm_changed_head");
102 	cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0);
103 
104 	/* remove sm_changed_head from index */
105 	rm_submodule_from_index("sm_changed_head");
106 	status = get_submodule_status(g_repo, "sm_changed_head");
107 	cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_DELETED) != 0);
108 }
109 
test_submodule_status__ignore_untracked(void)110 void test_submodule_status__ignore_untracked(void)
111 {
112 	unsigned int status;
113 	git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_UNTRACKED;
114 
115 	rm_submodule("sm_unchanged");
116 
117 	refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND);
118 	refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS);
119 	refute_submodule_exists(g_repo, "not", GIT_EEXISTS);
120 
121 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_index", ign));
122 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) != 0);
123 
124 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign));
125 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
126 
127 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_file", ign));
128 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) != 0);
129 
130 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_untracked_file", ign));
131 	cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
132 
133 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_missing_commits", ign));
134 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
135 
136 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_added_and_uncommited", ign));
137 	cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0);
138 
139 	/* removed sm_unchanged for deleted workdir */
140 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign));
141 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0);
142 
143 	/* now mkdir sm_unchanged to test uninitialized */
144 	cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL));
145 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign));
146 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0);
147 
148 	/* update sm_changed_head in index */
149 	add_submodule_to_index("sm_changed_head");
150 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign));
151 	cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0);
152 }
153 
test_submodule_status__ignore_dirty(void)154 void test_submodule_status__ignore_dirty(void)
155 {
156 	unsigned int status;
157 	git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_DIRTY;
158 
159 	rm_submodule("sm_unchanged");
160 
161 	refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND);
162 	refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS);
163 	refute_submodule_exists(g_repo, "not", GIT_EEXISTS);
164 
165 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_index", ign));
166 	cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
167 
168 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign));
169 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
170 
171 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_file", ign));
172 	cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
173 
174 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_untracked_file", ign));
175 	cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
176 
177 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_missing_commits", ign));
178 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
179 
180 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_added_and_uncommited", ign));
181 	cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0);
182 
183 	/* removed sm_unchanged for deleted workdir */
184 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign));
185 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0);
186 
187 	/* now mkdir sm_unchanged to test uninitialized */
188 	cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL));
189 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign));
190 	cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0);
191 
192 	/* update sm_changed_head in index */
193 	add_submodule_to_index("sm_changed_head");
194 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign));
195 	cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0);
196 }
197 
test_submodule_status__ignore_all(void)198 void test_submodule_status__ignore_all(void)
199 {
200 	unsigned int status;
201 	git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_ALL;
202 
203 	rm_submodule("sm_unchanged");
204 
205 	refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND);
206 	refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS);
207 	refute_submodule_exists(g_repo, "not", GIT_EEXISTS);
208 
209 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_index", ign));
210 	cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
211 
212 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign));
213 	cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
214 
215 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_file", ign));
216 	cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
217 
218 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_untracked_file", ign));
219 	cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
220 
221 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_missing_commits", ign));
222 	cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
223 
224 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_added_and_uncommited", ign));
225 	cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
226 
227 	/* removed sm_unchanged for deleted workdir */
228 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign));
229 	cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
230 
231 	/* now mkdir sm_unchanged to test uninitialized */
232 	cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL));
233 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign));
234 	cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
235 
236 	/* update sm_changed_head in index */
237 	add_submodule_to_index("sm_changed_head");
238 	cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign));
239 	cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
240 }
241 
242 typedef struct {
243 	size_t counter;
244 	const char **paths;
245 	int *statuses;
246 } submodule_expectations;
247 
confirm_submodule_status(const char * path,unsigned int status_flags,void * payload)248 static int confirm_submodule_status(
249 	const char *path, unsigned int status_flags, void *payload)
250 {
251 	submodule_expectations *exp = payload;
252 
253 	while (exp->statuses[exp->counter] < 0)
254 		exp->counter++;
255 
256 	cl_assert_equal_i(exp->statuses[exp->counter], (int)status_flags);
257 	cl_assert_equal_s(exp->paths[exp->counter++], path);
258 
259 	GIT_UNUSED(status_flags);
260 
261 	return 0;
262 }
263 
test_submodule_status__iterator(void)264 void test_submodule_status__iterator(void)
265 {
266 	git_iterator *iter;
267 	git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
268 	const git_index_entry *entry;
269 	size_t i;
270 	static const char *expected[] = {
271 		".gitmodules",
272 		"just_a_dir/",
273 		"just_a_dir/contents",
274 		"just_a_file",
275 		"not-submodule/",
276 		"not-submodule/README.txt",
277 		"not/",
278 		"not/README.txt",
279 		"README.txt",
280 		"sm_added_and_uncommited",
281 		"sm_changed_file",
282 		"sm_changed_head",
283 		"sm_changed_index",
284 		"sm_changed_untracked_file",
285 		"sm_missing_commits",
286 		"sm_unchanged",
287 		NULL
288 	};
289 	static int expected_flags[] = {
290 		GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED, /* ".gitmodules" */
291 		-1,					    /* "just_a_dir/" will be skipped */
292 		GIT_STATUS_CURRENT,     /* "just_a_dir/contents" */
293 		GIT_STATUS_CURRENT,	    /* "just_a_file" */
294 		GIT_STATUS_WT_NEW,      /* "not-submodule/" untracked item */
295 		-1,                     /* "not-submodule/README.txt" */
296 		GIT_STATUS_WT_NEW,      /* "not/" untracked item */
297 		-1,                     /* "not/README.txt" */
298 		GIT_STATUS_CURRENT,     /* "README.txt */
299 		GIT_STATUS_INDEX_NEW,   /* "sm_added_and_uncommited" */
300 		GIT_STATUS_WT_MODIFIED, /* "sm_changed_file" */
301 		GIT_STATUS_WT_MODIFIED, /* "sm_changed_head" */
302 		GIT_STATUS_WT_MODIFIED, /* "sm_changed_index" */
303 		GIT_STATUS_WT_MODIFIED, /* "sm_changed_untracked_file" */
304 		GIT_STATUS_WT_MODIFIED, /* "sm_missing_commits" */
305 		GIT_STATUS_CURRENT,     /* "sm_unchanged" */
306 		0
307 	};
308 	submodule_expectations exp = { 0, expected, expected_flags };
309 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
310 	git_index *index;
311 
312 	iter_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
313 
314 	cl_git_pass(git_repository_index(&index, g_repo));
315 	cl_git_pass(git_iterator_for_workdir(&iter, g_repo, index, NULL, &iter_opts));
316 
317 	for (i = 0; !git_iterator_advance(&entry, iter); ++i)
318 		cl_assert_equal_s(expected[i], entry->path);
319 
320 	git_iterator_free(iter);
321 	git_index_free(index);
322 
323 	opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
324 		GIT_STATUS_OPT_INCLUDE_UNMODIFIED |
325 		GIT_STATUS_OPT_INCLUDE_IGNORED |
326 		GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS |
327 		GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY;
328 
329 	cl_git_pass(git_status_foreach_ext(
330 		g_repo, &opts, confirm_submodule_status, &exp));
331 }
332 
test_submodule_status__untracked_dirs_containing_ignored_files(void)333 void test_submodule_status__untracked_dirs_containing_ignored_files(void)
334 {
335 	unsigned int status, expected;
336 
337 	cl_git_append2file(
338 		"submod2/.git/modules/sm_unchanged/info/exclude", "\n*.ignored\n");
339 
340 	cl_git_pass(
341 		git_futils_mkdir_relative("sm_unchanged/directory", "submod2", 0755, 0, NULL));
342 	cl_git_mkfile(
343 		"submod2/sm_unchanged/directory/i_am.ignored",
344 		"ignore this file, please\n");
345 
346 	status = get_submodule_status(g_repo, "sm_unchanged");
347 	cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
348 
349 	expected = GIT_SUBMODULE_STATUS_IN_HEAD |
350 		GIT_SUBMODULE_STATUS_IN_INDEX |
351 		GIT_SUBMODULE_STATUS_IN_CONFIG |
352 		GIT_SUBMODULE_STATUS_IN_WD;
353 	cl_assert(status == expected);
354 }
355