]> git.proxmox.com Git - libgit2.git/blame - tests/libgit2/checkout/typechange.c
Merge https://salsa.debian.org/debian/libgit2 into proxmox/bullseye
[libgit2.git] / tests / libgit2 / checkout / typechange.c
CommitLineData
0d64bef9 1#include "clar_libgit2.h"
4b3ec53c 2#include "diff_generate.h"
0d64bef9
RB
3#include "git2/checkout.h"
4#include "path.h"
5#include "posix.h"
22a2d3d5 6#include "futils.h"
0d64bef9
RB
7
8static git_repository *g_repo = NULL;
9
70681ff7
JH
10/*
11From the test repo used for this test:
12--------------------------------------
13
14This is a test repo for libgit2 where tree entries have type changes
15
16The key types that could be found in tree entries are:
17
181 - GIT_FILEMODE_NEW = 0000000
192 - GIT_FILEMODE_TREE = 0040000
203 - GIT_FILEMODE_BLOB = 0100644
214 - GIT_FILEMODE_BLOB_EXECUTABLE = 0100755
225 - GIT_FILEMODE_LINK = 0120000
236 - GIT_FILEMODE_COMMIT = 0160000
24
25I will try to have every type of transition somewhere in the history
26of this repo.
27
28Commits
29-------
30Initial commit - a(1) b(1) c(1) d(1) e(1)
31Create content - a(1->2) b(1->3) c(1->4) d(1->5) e(1->6)
32Changes #1 - a(2->3) b(3->4) c(4->5) d(5->6) e(6->2)
33Changes #2 - a(3->5) b(4->6) c(5->2) d(6->3) e(2->4)
34Changes #3 - a(5->3) b(6->4) c(2->5) d(3->6) e(4->2)
35Changes #4 - a(3->2) b(4->3) c(5->4) d(6->5) e(2->6)
36Changes #5 - a(2->1) b(3->1) c(4->1) d(5->1) e(6->1)
37
38*/
39
0d64bef9
RB
40static const char *g_typechange_oids[] = {
41 "79b9f23e85f55ea36a472a902e875bc1121a94cb",
42 "9bdb75b73836a99e3dbeea640a81de81031fdc29",
43 "0e7ed140b514b8cae23254cb8656fe1674403aff",
44 "9d0235c7a7edc0889a18f97a42ee6db9fe688447",
45 "9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a",
46 "1b63caae4a5ca96f78e8dfefc376c6a39a142475",
47 "6eae26c90e8ccc4d16208972119c40635489c6f0",
48 NULL
49};
50
51static bool g_typechange_empty[] = {
52 true, false, false, false, false, false, true, true
53};
54
70681ff7
JH
55static const int g_typechange_expected_conflicts[] = {
56 1, 2, 3, 3, 2, 3, 2
57};
58
59static const int g_typechange_expected_untracked[] = {
60 6, 4, 3, 2, 3, 2, 5
61};
62
0d64bef9
RB
63void test_checkout_typechange__initialize(void)
64{
65 g_repo = cl_git_sandbox_init("typechanges");
66
67 cl_fixture_sandbox("submod2_target");
68 p_rename("submod2_target/.gitted", "submod2_target/.git");
69}
70
71void test_checkout_typechange__cleanup(void)
72{
73 cl_git_sandbox_cleanup();
74 cl_fixture_cleanup("submod2_target");
75}
76
c50c58de
RB
77static void assert_file_exists(const char *path)
78{
e579e0f7 79 cl_assert_(git_fs_path_isfile(path), path);
c50c58de
RB
80}
81
82static void assert_dir_exists(const char *path)
83{
e579e0f7 84 cl_assert_(git_fs_path_isdir(path), path);
c50c58de
RB
85}
86
87static void assert_workdir_matches_tree(
88 git_repository *repo, const git_oid *id, const char *root, bool recurse)
89{
90 git_object *obj;
91 git_tree *tree;
92 size_t i, max_i;
e579e0f7 93 git_str path = GIT_STR_INIT;
c50c58de
RB
94
95 if (!root)
96 root = git_repository_workdir(repo);
97 cl_assert(root);
98
ac3d33df
JK
99 cl_git_pass(git_object_lookup(&obj, repo, id, GIT_OBJECT_ANY));
100 cl_git_pass(git_object_peel((git_object **)&tree, obj, GIT_OBJECT_TREE));
c50c58de
RB
101 git_object_free(obj);
102
103 max_i = git_tree_entrycount(tree);
104
105 for (i = 0; i < max_i; ++i) {
106 const git_tree_entry *te = git_tree_entry_byindex(tree, i);
107 cl_assert(te);
108
e579e0f7 109 cl_git_pass(git_str_joinpath(&path, root, git_tree_entry_name(te)));
c50c58de
RB
110
111 switch (git_tree_entry_type(te)) {
ac3d33df 112 case GIT_OBJECT_COMMIT:
c50c58de
RB
113 assert_dir_exists(path.ptr);
114 break;
ac3d33df 115 case GIT_OBJECT_TREE:
c50c58de
RB
116 assert_dir_exists(path.ptr);
117 if (recurse)
118 assert_workdir_matches_tree(
119 repo, git_tree_entry_id(te), path.ptr, true);
120 break;
ac3d33df 121 case GIT_OBJECT_BLOB:
c50c58de
RB
122 switch (git_tree_entry_filemode(te)) {
123 case GIT_FILEMODE_BLOB:
124 case GIT_FILEMODE_BLOB_EXECUTABLE:
125 assert_file_exists(path.ptr);
126 /* because of cross-platform, don't confirm exec bit yet */
127 break;
128 case GIT_FILEMODE_LINK:
e579e0f7 129 cl_assert_(git_fs_path_exists(path.ptr), path.ptr);
c50c58de
RB
130 /* because of cross-platform, don't confirm link yet */
131 break;
132 default:
133 cl_assert(false); /* really?! */
134 }
135 break;
136 default:
137 cl_assert(false); /* really?!! */
138 }
139 }
140
141 git_tree_free(tree);
e579e0f7 142 git_str_dispose(&path);
c50c58de
RB
143}
144
145void test_checkout_typechange__checkout_typechanges_safe(void)
0d64bef9
RB
146{
147 int i;
148 git_object *obj;
6affd71f 149 git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
0d64bef9 150
0d64bef9
RB
151 for (i = 0; g_typechange_oids[i] != NULL; ++i) {
152 cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i]));
7e5c8a5b 153
70681ff7 154 opts.checkout_strategy = !i ? GIT_CHECKOUT_FORCE : GIT_CHECKOUT_SAFE;
0d64bef9 155
30a46ab1 156 cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
0d64bef9 157
ad9a921b 158 cl_git_pass(
4e498646 159 git_repository_set_head_detached(g_repo, git_object_id(obj)));
ad9a921b 160
c50c58de
RB
161 assert_workdir_matches_tree(g_repo, git_object_id(obj), NULL, true);
162
0d64bef9
RB
163 git_object_free(obj);
164
165 if (!g_typechange_empty[i]) {
e579e0f7
MB
166 cl_assert(git_fs_path_isdir("typechanges"));
167 cl_assert(git_fs_path_exists("typechanges/a"));
168 cl_assert(git_fs_path_exists("typechanges/b"));
169 cl_assert(git_fs_path_exists("typechanges/c"));
170 cl_assert(git_fs_path_exists("typechanges/d"));
171 cl_assert(git_fs_path_exists("typechanges/e"));
0d64bef9 172 } else {
e579e0f7
MB
173 cl_assert(git_fs_path_isdir("typechanges"));
174 cl_assert(!git_fs_path_exists("typechanges/a"));
175 cl_assert(!git_fs_path_exists("typechanges/b"));
176 cl_assert(!git_fs_path_exists("typechanges/c"));
177 cl_assert(!git_fs_path_exists("typechanges/d"));
178 cl_assert(!git_fs_path_exists("typechanges/e"));
0d64bef9
RB
179 }
180 }
181}
c50c58de
RB
182
183typedef struct {
184 int conflicts;
185 int dirty;
186 int updates;
187 int untracked;
188 int ignored;
189} notify_counts;
190
191static int notify_counter(
192 git_checkout_notify_t why,
193 const char *path,
194 const git_diff_file *baseline,
195 const git_diff_file *target,
196 const git_diff_file *workdir,
197 void *payload)
198{
199 notify_counts *cts = payload;
200
201 GIT_UNUSED(path);
202 GIT_UNUSED(baseline);
203 GIT_UNUSED(target);
204 GIT_UNUSED(workdir);
205
206 switch (why) {
207 case GIT_CHECKOUT_NOTIFY_CONFLICT: cts->conflicts++; break;
208 case GIT_CHECKOUT_NOTIFY_DIRTY: cts->dirty++; break;
209 case GIT_CHECKOUT_NOTIFY_UPDATED: cts->updates++; break;
210 case GIT_CHECKOUT_NOTIFY_UNTRACKED: cts->untracked++; break;
211 case GIT_CHECKOUT_NOTIFY_IGNORED: cts->ignored++; break;
212 default: break;
213 }
214
215 return 0;
216}
217
218static void force_create_file(const char *file)
219{
220 int error = git_futils_rmdir_r(file, NULL,
221 GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS);
222 cl_assert(!error || error == GIT_ENOTFOUND);
223 cl_git_pass(git_futils_mkpath2file(file, 0777));
8023b83a 224 cl_git_rewritefile(file, "yowza!!");
c50c58de
RB
225}
226
70681ff7
JH
227static int make_submodule_dirty(git_submodule *sm, const char *name, void *payload)
228{
e579e0f7
MB
229 git_str submodulepath = GIT_STR_INIT;
230 git_str dirtypath = GIT_STR_INIT;
70681ff7
JH
231 git_repository *submodule_repo;
232
bcef008f
ET
233 GIT_UNUSED(name);
234 GIT_UNUSED(payload);
235
70681ff7 236 /* remove submodule directory in preparation for init and repo_init */
e579e0f7 237 cl_git_pass(git_str_joinpath(
70681ff7
JH
238 &submodulepath,
239 git_repository_workdir(g_repo),
240 git_submodule_path(sm)
241 ));
e579e0f7 242 git_futils_rmdir_r(git_str_cstr(&submodulepath), NULL, GIT_RMDIR_REMOVE_FILES);
70681ff7 243
6e0d473b 244 /* initialize submodule's repository */
70681ff7
JH
245 cl_git_pass(git_submodule_repo_init(&submodule_repo, sm, 0));
246
247 /* create a file in the submodule workdir to make it dirty */
248 cl_git_pass(
e579e0f7
MB
249 git_str_joinpath(&dirtypath, git_repository_workdir(submodule_repo), "dirty"));
250 force_create_file(git_str_cstr(&dirtypath));
70681ff7 251
e579e0f7
MB
252 git_str_dispose(&dirtypath);
253 git_str_dispose(&submodulepath);
6e0d473b 254 git_repository_free(submodule_repo);
70681ff7
JH
255
256 return 0;
257}
258
c50c58de
RB
259void test_checkout_typechange__checkout_with_conflicts(void)
260{
261 int i;
262 git_object *obj;
6affd71f 263 git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
c50c58de
RB
264 notify_counts cts = {0};
265
266 opts.notify_flags =
267 GIT_CHECKOUT_NOTIFY_CONFLICT | GIT_CHECKOUT_NOTIFY_UNTRACKED;
268 opts.notify_cb = notify_counter;
269 opts.notify_payload = &cts;
270
271 for (i = 0; g_typechange_oids[i] != NULL; ++i) {
272 cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i]));
273
274 force_create_file("typechanges/a/blocker");
275 force_create_file("typechanges/b");
276 force_create_file("typechanges/c/sub/sub/file");
277 git_futils_rmdir_r("typechanges/d", NULL, GIT_RMDIR_REMOVE_FILES);
278 p_mkdir("typechanges/d", 0777); /* intentionally empty dir */
279 force_create_file("typechanges/untracked");
70681ff7 280 cl_git_pass(git_submodule_foreach(g_repo, make_submodule_dirty, NULL));
c50c58de 281
496b76d4 282 opts.checkout_strategy = GIT_CHECKOUT_SAFE;
c50c58de
RB
283 memset(&cts, 0, sizeof(cts));
284
285 cl_git_fail(git_checkout_tree(g_repo, obj, &opts));
70681ff7
JH
286 cl_assert_equal_i(cts.conflicts, g_typechange_expected_conflicts[i]);
287 cl_assert_equal_i(cts.untracked, g_typechange_expected_untracked[i]);
288 cl_assert_equal_i(cts.dirty, 0);
289 cl_assert_equal_i(cts.updates, 0);
290 cl_assert_equal_i(cts.ignored, 0);
c50c58de
RB
291
292 opts.checkout_strategy =
293 GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED;
294 memset(&cts, 0, sizeof(cts));
295
e579e0f7 296 cl_assert(git_fs_path_exists("typechanges/untracked"));
c50c58de
RB
297
298 cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
299 cl_assert_equal_i(0, cts.conflicts);
300
e579e0f7 301 cl_assert(!git_fs_path_exists("typechanges/untracked"));
c50c58de
RB
302
303 cl_git_pass(
4e498646 304 git_repository_set_head_detached(g_repo, git_object_id(obj)));
c50c58de
RB
305
306 assert_workdir_matches_tree(g_repo, git_object_id(obj), NULL, true);
307
308 git_object_free(obj);
309 }
310}
4b3ec53c
XL
311
312void test_checkout_typechange__status_char(void)
313{
314 size_t i;
315 git_oid oid;
316 git_commit *commit;
317 git_diff *diff;
318 const git_diff_delta *delta;
319 git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
320 char expected[8] = {'M', 'M', 'R', 'T', 'D', 'R', 'A', 'R'};
321
322 git_oid_fromstr(&oid, "9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a");
323 cl_git_pass(git_commit_lookup(&commit, g_repo, &oid));
324 diffopts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
325 cl_git_pass(git_diff__commit(&diff, g_repo, commit, &diffopts));
326 cl_git_pass(git_diff_find_similar(diff, NULL));
327
328 for (i = 0; i < git_diff_num_deltas(diff); i++) {
329 delta = git_diff_get_delta(diff, i);
330 cl_assert_equal_i(expected[i], git_diff_status_char(delta->status));
331 }
332
333 git_diff_free(diff);
334 git_commit_free(commit);
335}