]>
Commit | Line | Data |
---|---|---|
14741d62 | 1 | /* |
359fc2d2 | 2 | * Copyright (C) the libgit2 contributors. All rights reserved. |
14741d62 BS |
3 | * |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with | |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
6 | */ | |
7 | ||
8 | #include <assert.h> | |
9 | ||
cf208031 RB |
10 | #include "checkout.h" |
11 | ||
14741d62 BS |
12 | #include "git2/repository.h" |
13 | #include "git2/refs.h" | |
14 | #include "git2/tree.h" | |
24b0d3d5 | 15 | #include "git2/blob.h" |
8651c10f | 16 | #include "git2/config.h" |
3aa443a9 | 17 | #include "git2/diff.h" |
e0548c0e | 18 | #include "git2/submodule.h" |
e1807113 | 19 | #include "git2/sys/index.h" |
14741d62 | 20 | |
14741d62 | 21 | #include "refs.h" |
24b0d3d5 | 22 | #include "repository.h" |
114f5a6c | 23 | #include "index.h" |
0e874b12 | 24 | #include "filter.h" |
41ad70d0 | 25 | #include "blob.h" |
32def5af | 26 | #include "diff.h" |
ad9a921b | 27 | #include "pathspec.h" |
3658e81e | 28 | #include "buf_text.h" |
14741d62 | 29 | |
77cffa31 | 30 | /* See docs/checkout-internals.md for more information */ |
cf208031 RB |
31 | |
32 | enum { | |
33 | CHECKOUT_ACTION__NONE = 0, | |
34 | CHECKOUT_ACTION__REMOVE = 1, | |
35 | CHECKOUT_ACTION__UPDATE_BLOB = 2, | |
36 | CHECKOUT_ACTION__UPDATE_SUBMODULE = 4, | |
37 | CHECKOUT_ACTION__CONFLICT = 8, | |
38 | CHECKOUT_ACTION__MAX = 8, | |
7e5c8a5b RB |
39 | CHECKOUT_ACTION__DEFER_REMOVE = 16, |
40 | CHECKOUT_ACTION__REMOVE_AND_UPDATE = | |
41 | (CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE), | |
cf208031 RB |
42 | }; |
43 | ||
ad9a921b RB |
44 | typedef struct { |
45 | git_repository *repo; | |
46 | git_diff_list *diff; | |
7e5c8a5b RB |
47 | git_checkout_opts opts; |
48 | bool opts_free_baseline; | |
49 | char *pfx; | |
5cf9875a | 50 | git_index *index; |
7e5c8a5b RB |
51 | git_pool pool; |
52 | git_vector removes; | |
53 | git_buf path; | |
9ac8b113 | 54 | size_t workdir_len; |
7e5c8a5b RB |
55 | unsigned int strategy; |
56 | int can_symlink; | |
e0548c0e | 57 | bool reload_submodules; |
9c05c17b BS |
58 | size_t total_steps; |
59 | size_t completed_steps; | |
7e5c8a5b | 60 | } checkout_data; |
3aa443a9 | 61 | |
cf208031 | 62 | static int checkout_notify( |
7e5c8a5b | 63 | checkout_data *data, |
cf208031 RB |
64 | git_checkout_notify_t why, |
65 | const git_diff_delta *delta, | |
16a666d3 | 66 | const git_index_entry *wditem) |
cf208031 | 67 | { |
16a666d3 | 68 | git_diff_file wdfile; |
7e5c8a5b | 69 | const git_diff_file *baseline = NULL, *target = NULL, *workdir = NULL; |
56543a60 | 70 | const char *path = NULL; |
7e5c8a5b RB |
71 | |
72 | if (!data->opts.notify_cb) | |
73 | return 0; | |
74 | ||
75 | if ((why & data->opts.notify_flags) == 0) | |
76 | return 0; | |
77 | ||
16a666d3 RB |
78 | if (wditem) { |
79 | memset(&wdfile, 0, sizeof(wdfile)); | |
7e5c8a5b | 80 | |
16a666d3 RB |
81 | git_oid_cpy(&wdfile.oid, &wditem->oid); |
82 | wdfile.path = wditem->path; | |
83 | wdfile.size = wditem->file_size; | |
71a3d27e | 84 | wdfile.flags = GIT_DIFF_FLAG_VALID_OID; |
16a666d3 | 85 | wdfile.mode = wditem->mode; |
7e5c8a5b | 86 | |
16a666d3 | 87 | workdir = &wdfile; |
56543a60 RB |
88 | |
89 | path = wditem->path; | |
7e5c8a5b RB |
90 | } |
91 | ||
16a666d3 | 92 | if (delta) { |
7e5c8a5b RB |
93 | switch (delta->status) { |
94 | case GIT_DELTA_UNMODIFIED: | |
95 | case GIT_DELTA_MODIFIED: | |
96 | case GIT_DELTA_TYPECHANGE: | |
97 | default: | |
16a666d3 RB |
98 | baseline = &delta->old_file; |
99 | target = &delta->new_file; | |
7e5c8a5b RB |
100 | break; |
101 | case GIT_DELTA_ADDED: | |
102 | case GIT_DELTA_IGNORED: | |
103 | case GIT_DELTA_UNTRACKED: | |
16a666d3 | 104 | target = &delta->new_file; |
7e5c8a5b RB |
105 | break; |
106 | case GIT_DELTA_DELETED: | |
16a666d3 | 107 | baseline = &delta->old_file; |
7e5c8a5b RB |
108 | break; |
109 | } | |
56543a60 RB |
110 | |
111 | path = delta->old_file.path; | |
7e5c8a5b RB |
112 | } |
113 | ||
114 | return data->opts.notify_cb( | |
56543a60 | 115 | why, path, baseline, target, workdir, data->opts.notify_payload); |
cf208031 RB |
116 | } |
117 | ||
118 | static bool checkout_is_workdir_modified( | |
7e5c8a5b | 119 | checkout_data *data, |
5cf9875a RB |
120 | const git_diff_file *baseitem, |
121 | const git_index_entry *wditem) | |
cf208031 RB |
122 | { |
123 | git_oid oid; | |
0da62c5c | 124 | const git_index_entry *ie; |
cf208031 | 125 | |
e0548c0e RB |
126 | /* handle "modified" submodule */ |
127 | if (wditem->mode == GIT_FILEMODE_COMMIT) { | |
128 | git_submodule *sm; | |
129 | unsigned int sm_status = 0; | |
130 | const git_oid *sm_oid = NULL; | |
131 | ||
132 | if (git_submodule_lookup(&sm, data->repo, wditem->path) < 0 || | |
133 | git_submodule_status(&sm_status, sm) < 0) | |
134 | return true; | |
135 | ||
136 | if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status)) | |
137 | return true; | |
138 | ||
139 | sm_oid = git_submodule_wd_id(sm); | |
140 | if (!sm_oid) | |
141 | return false; | |
142 | ||
b7f167da | 143 | return (git_oid__cmp(&baseitem->oid, sm_oid) != 0); |
e0548c0e RB |
144 | } |
145 | ||
0da62c5c ET |
146 | /* Look at the cache to decide if the workdir is modified. If not, |
147 | * we can simply compare the oid in the cache to the baseitem instead | |
148 | * of hashing the file. | |
149 | */ | |
150 | if ((ie = git_index_get_bypath(data->index, wditem->path, 0)) != NULL) { | |
151 | if (wditem->mtime.seconds == ie->mtime.seconds && | |
152 | wditem->mtime.nanoseconds == ie->mtime.nanoseconds && | |
153 | wditem->file_size == ie->file_size) | |
b7f167da | 154 | return (git_oid__cmp(&baseitem->oid, &ie->oid) != 0); |
0da62c5c ET |
155 | } |
156 | ||
5cf9875a RB |
157 | /* depending on where base is coming from, we may or may not know |
158 | * the actual size of the data, so we can't rely on this shortcut. | |
159 | */ | |
160 | if (baseitem->size && wditem->file_size != baseitem->size) | |
cf208031 RB |
161 | return true; |
162 | ||
163 | if (git_diff__oid_for_file( | |
5cf9875a RB |
164 | data->repo, wditem->path, wditem->mode, |
165 | wditem->file_size, &oid) < 0) | |
cf208031 RB |
166 | return false; |
167 | ||
b7f167da | 168 | return (git_oid__cmp(&baseitem->oid, &oid) != 0); |
7e5c8a5b RB |
169 | } |
170 | ||
171 | #define CHECKOUT_ACTION_IF(FLAG,YES,NO) \ | |
172 | ((data->strategy & GIT_CHECKOUT_##FLAG) ? CHECKOUT_ACTION__##YES : CHECKOUT_ACTION__##NO) | |
173 | ||
7e5c8a5b RB |
174 | static int checkout_action_common( |
175 | checkout_data *data, | |
176 | int action, | |
cf208031 | 177 | const git_diff_delta *delta, |
7e5c8a5b RB |
178 | const git_index_entry *wd) |
179 | { | |
180 | git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE; | |
181 | ||
182 | if (action <= 0) | |
183 | return action; | |
184 | ||
185 | if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) | |
186 | action = (action & ~CHECKOUT_ACTION__REMOVE); | |
187 | ||
188 | if ((action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) { | |
189 | if (S_ISGITLINK(delta->new_file.mode)) | |
190 | action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB) | | |
191 | CHECKOUT_ACTION__UPDATE_SUBMODULE; | |
192 | ||
55d3a390 RB |
193 | /* to "update" a symlink, we must remove the old one first */ |
194 | if (delta->new_file.mode == GIT_FILEMODE_LINK && wd != NULL) | |
195 | action |= CHECKOUT_ACTION__REMOVE; | |
196 | ||
7e5c8a5b RB |
197 | notify = GIT_CHECKOUT_NOTIFY_UPDATED; |
198 | } | |
199 | ||
200 | if ((action & CHECKOUT_ACTION__CONFLICT) != 0) | |
201 | notify = GIT_CHECKOUT_NOTIFY_CONFLICT; | |
202 | ||
203 | if (notify != GIT_CHECKOUT_NOTIFY_NONE && | |
204 | checkout_notify(data, notify, delta, wd) != 0) | |
205 | return GIT_EUSER; | |
206 | ||
207 | return action; | |
208 | } | |
209 | ||
210 | static int checkout_action_no_wd( | |
211 | checkout_data *data, | |
212 | const git_diff_delta *delta) | |
cf208031 RB |
213 | { |
214 | int action = CHECKOUT_ACTION__NONE; | |
cf208031 | 215 | |
7e5c8a5b RB |
216 | switch (delta->status) { |
217 | case GIT_DELTA_UNMODIFIED: /* case 12 */ | |
218 | if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL)) | |
219 | return GIT_EUSER; | |
220 | action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, NONE); | |
221 | break; | |
222 | case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */ | |
7e5c8a5b RB |
223 | action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); |
224 | break; | |
36fd9e30 RB |
225 | case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */ |
226 | action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, CONFLICT); | |
227 | break; | |
7e5c8a5b RB |
228 | case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/ |
229 | if (delta->new_file.mode == GIT_FILEMODE_TREE) | |
230 | action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); | |
231 | break; | |
232 | case GIT_DELTA_DELETED: /* case 8 or 25 */ | |
233 | default: /* impossible */ | |
234 | break; | |
cf208031 RB |
235 | } |
236 | ||
7e5c8a5b RB |
237 | return checkout_action_common(data, action, delta, NULL); |
238 | } | |
239 | ||
240 | static int checkout_action_wd_only( | |
241 | checkout_data *data, | |
242 | git_iterator *workdir, | |
243 | const git_index_entry *wd, | |
244 | git_vector *pathspec) | |
245 | { | |
5cf9875a | 246 | bool remove = false; |
7e5c8a5b RB |
247 | git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE; |
248 | ||
d2ce27dd | 249 | if (!git_pathspec__match( |
40342bd2 RB |
250 | pathspec, wd->path, |
251 | (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, | |
d2ce27dd | 252 | git_iterator_ignore_case(workdir), NULL, NULL)) |
7e5c8a5b RB |
253 | return 0; |
254 | ||
5cf9875a | 255 | /* check if item is tracked in the index but not in the checkout diff */ |
817d6251 RB |
256 | if (data->index != NULL) { |
257 | if (wd->mode != GIT_FILEMODE_TREE) { | |
54a1a042 ET |
258 | int error; |
259 | ||
260 | if ((error = git_index_find(NULL, data->index, wd->path)) == 0) { | |
817d6251 RB |
261 | notify = GIT_CHECKOUT_NOTIFY_DIRTY; |
262 | remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0); | |
54a1a042 ET |
263 | } else if (error != GIT_ENOTFOUND) |
264 | return error; | |
817d6251 RB |
265 | } else { |
266 | /* for tree entries, we have to see if there are any index | |
267 | * entries that are contained inside that tree | |
268 | */ | |
269 | size_t pos = git_index__prefix_position(data->index, wd->path); | |
270 | const git_index_entry *e = git_index_get_byindex(data->index, pos); | |
271 | ||
272 | if (e != NULL && data->diff->pfxcomp(e->path, wd->path) == 0) { | |
273 | notify = GIT_CHECKOUT_NOTIFY_DIRTY; | |
274 | remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0); | |
275 | } | |
276 | } | |
5cf9875a | 277 | } |
817d6251 RB |
278 | |
279 | if (notify != GIT_CHECKOUT_NOTIFY_NONE) | |
280 | /* found in index */; | |
5cf9875a | 281 | else if (git_iterator_current_is_ignored(workdir)) { |
7e5c8a5b RB |
282 | notify = GIT_CHECKOUT_NOTIFY_IGNORED; |
283 | remove = ((data->strategy & GIT_CHECKOUT_REMOVE_IGNORED) != 0); | |
5cf9875a RB |
284 | } |
285 | else { | |
7e5c8a5b RB |
286 | notify = GIT_CHECKOUT_NOTIFY_UNTRACKED; |
287 | remove = ((data->strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0); | |
cf208031 RB |
288 | } |
289 | ||
7e5c8a5b RB |
290 | if (checkout_notify(data, notify, NULL, wd)) |
291 | return GIT_EUSER; | |
cf208031 | 292 | |
7e5c8a5b RB |
293 | if (remove) { |
294 | char *path = git_pool_strdup(&data->pool, wd->path); | |
295 | GITERR_CHECK_ALLOC(path); | |
296 | ||
297 | if (git_vector_insert(&data->removes, path) < 0) | |
298 | return -1; | |
299 | } | |
300 | ||
301 | return 0; | |
302 | } | |
303 | ||
e0548c0e RB |
304 | static bool submodule_is_config_only( |
305 | checkout_data *data, | |
306 | const char *path) | |
307 | { | |
308 | git_submodule *sm = NULL; | |
309 | unsigned int sm_loc = 0; | |
310 | ||
311 | if (git_submodule_lookup(&sm, data->repo, path) < 0 || | |
312 | git_submodule_location(&sm_loc, sm) < 0 || | |
313 | sm_loc == GIT_SUBMODULE_STATUS_IN_CONFIG) | |
314 | return true; | |
315 | ||
316 | return false; | |
317 | } | |
318 | ||
7e5c8a5b RB |
319 | static int checkout_action_with_wd( |
320 | checkout_data *data, | |
321 | const git_diff_delta *delta, | |
322 | const git_index_entry *wd) | |
323 | { | |
324 | int action = CHECKOUT_ACTION__NONE; | |
325 | ||
326 | switch (delta->status) { | |
327 | case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */ | |
e0548c0e | 328 | if (checkout_is_workdir_modified(data, &delta->old_file, wd)) { |
7e5c8a5b RB |
329 | if (checkout_notify( |
330 | data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd)) | |
331 | return GIT_EUSER; | |
332 | action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, NONE); | |
cf208031 | 333 | } |
7e5c8a5b RB |
334 | break; |
335 | case GIT_DELTA_ADDED: /* case 3, 4 or 6 */ | |
336 | action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT); | |
337 | break; | |
338 | case GIT_DELTA_DELETED: /* case 9 or 10 (or 26 but not really) */ | |
339 | if (checkout_is_workdir_modified(data, &delta->old_file, wd)) | |
340 | action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); | |
341 | else | |
342 | action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE); | |
343 | break; | |
344 | case GIT_DELTA_MODIFIED: /* case 16, 17, 18 (or 36 but not really) */ | |
345 | if (checkout_is_workdir_modified(data, &delta->old_file, wd)) | |
346 | action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT); | |
347 | else | |
348 | action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); | |
349 | break; | |
350 | case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */ | |
e0548c0e RB |
351 | if (delta->old_file.mode == GIT_FILEMODE_TREE) { |
352 | if (wd->mode == GIT_FILEMODE_TREE) | |
353 | /* either deleting items in old tree will delete the wd dir, | |
354 | * or we'll get a conflict when we attempt blob update... | |
355 | */ | |
356 | action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); | |
357 | else if (wd->mode == GIT_FILEMODE_COMMIT) { | |
358 | /* workdir is possibly a "phantom" submodule - treat as a | |
359 | * tree if the only submodule info came from the config | |
360 | */ | |
361 | if (submodule_is_config_only(data, wd->path)) | |
362 | action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); | |
363 | else | |
364 | action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); | |
365 | } else | |
366 | action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); | |
367 | } | |
7e5c8a5b RB |
368 | else if (checkout_is_workdir_modified(data, &delta->old_file, wd)) |
369 | action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); | |
370 | else | |
371 | action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE); | |
e0548c0e RB |
372 | |
373 | /* don't update if the typechange is to a tree */ | |
374 | if (delta->new_file.mode == GIT_FILEMODE_TREE) | |
375 | action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB); | |
7e5c8a5b RB |
376 | break; |
377 | default: /* impossible */ | |
378 | break; | |
cf208031 RB |
379 | } |
380 | ||
7e5c8a5b RB |
381 | return checkout_action_common(data, action, delta, wd); |
382 | } | |
cf208031 | 383 | |
7e5c8a5b RB |
384 | static int checkout_action_with_wd_blocker( |
385 | checkout_data *data, | |
386 | const git_diff_delta *delta, | |
387 | const git_index_entry *wd) | |
388 | { | |
389 | int action = CHECKOUT_ACTION__NONE; | |
cf208031 | 390 | |
7e5c8a5b RB |
391 | switch (delta->status) { |
392 | case GIT_DELTA_UNMODIFIED: | |
393 | /* should show delta as dirty / deleted */ | |
394 | if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd)) | |
cf208031 | 395 | return GIT_EUSER; |
7e5c8a5b RB |
396 | action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE); |
397 | break; | |
398 | case GIT_DELTA_ADDED: | |
399 | case GIT_DELTA_MODIFIED: | |
400 | action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); | |
401 | break; | |
402 | case GIT_DELTA_DELETED: | |
403 | action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); | |
404 | break; | |
405 | case GIT_DELTA_TYPECHANGE: | |
406 | /* not 100% certain about this... */ | |
407 | action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); | |
408 | break; | |
409 | default: /* impossible */ | |
410 | break; | |
cf208031 RB |
411 | } |
412 | ||
7e5c8a5b RB |
413 | return checkout_action_common(data, action, delta, wd); |
414 | } | |
415 | ||
416 | static int checkout_action_with_wd_dir( | |
417 | checkout_data *data, | |
418 | const git_diff_delta *delta, | |
419 | const git_index_entry *wd) | |
420 | { | |
421 | int action = CHECKOUT_ACTION__NONE; | |
422 | ||
423 | switch (delta->status) { | |
424 | case GIT_DELTA_UNMODIFIED: /* case 19 or 24 (or 34 but not really) */ | |
425 | if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL) || | |
426 | checkout_notify( | |
427 | data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd)) | |
cf208031 | 428 | return GIT_EUSER; |
7e5c8a5b RB |
429 | break; |
430 | case GIT_DELTA_ADDED:/* case 4 (and 7 for dir) */ | |
431 | case GIT_DELTA_MODIFIED: /* case 20 (or 37 but not really) */ | |
e0548c0e RB |
432 | if (delta->old_file.mode == GIT_FILEMODE_COMMIT) |
433 | /* expected submodule (and maybe found one) */; | |
434 | else if (delta->new_file.mode != GIT_FILEMODE_TREE) | |
7e5c8a5b RB |
435 | action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); |
436 | break; | |
437 | case GIT_DELTA_DELETED: /* case 11 (and 27 for dir) */ | |
438 | if (delta->old_file.mode != GIT_FILEMODE_TREE && | |
439 | checkout_notify( | |
440 | data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd)) | |
441 | return GIT_EUSER; | |
442 | break; | |
443 | case GIT_DELTA_TYPECHANGE: /* case 24 or 31 */ | |
7e5c8a5b | 444 | if (delta->old_file.mode == GIT_FILEMODE_TREE) { |
e0548c0e RB |
445 | /* For typechange from dir, remove dir and add blob, but it is |
446 | * not safe to remove dir if it contains modified files. | |
447 | * However, safely removing child files will remove the parent | |
448 | * directory if is it left empty, so we can defer removing the | |
449 | * dir and it will succeed if no children are left. | |
450 | */ | |
7e5c8a5b RB |
451 | action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); |
452 | if (action != CHECKOUT_ACTION__NONE) | |
453 | action |= CHECKOUT_ACTION__DEFER_REMOVE; | |
454 | } | |
e0548c0e RB |
455 | else if (delta->new_file.mode != GIT_FILEMODE_TREE) |
456 | /* For typechange to dir, dir is already created so no action */ | |
457 | action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); | |
7e5c8a5b RB |
458 | break; |
459 | default: /* impossible */ | |
460 | break; | |
cf208031 RB |
461 | } |
462 | ||
7e5c8a5b | 463 | return checkout_action_common(data, action, delta, wd); |
cf208031 RB |
464 | } |
465 | ||
7e5c8a5b RB |
466 | static int checkout_action( |
467 | checkout_data *data, | |
cf208031 | 468 | git_diff_delta *delta, |
7e5c8a5b RB |
469 | git_iterator *workdir, |
470 | const git_index_entry **wditem_ptr, | |
cf208031 RB |
471 | git_vector *pathspec) |
472 | { | |
7e5c8a5b RB |
473 | const git_index_entry *wd = *wditem_ptr; |
474 | int cmp = -1, act; | |
475 | int (*strcomp)(const char *, const char *) = data->diff->strcomp; | |
476 | int (*pfxcomp)(const char *str, const char *pfx) = data->diff->pfxcomp; | |
0cc7d8df | 477 | int error; |
7e5c8a5b RB |
478 | |
479 | /* move workdir iterator to follow along with deltas */ | |
480 | ||
481 | while (1) { | |
482 | if (!wd) | |
483 | return checkout_action_no_wd(data, delta); | |
169dc616 | 484 | |
7e5c8a5b RB |
485 | cmp = strcomp(wd->path, delta->old_file.path); |
486 | ||
487 | /* 1. wd before delta ("a/a" before "a/b") | |
488 | * 2. wd prefixes delta & should expand ("a/" before "a/b") | |
489 | * 3. wd prefixes delta & cannot expand ("a/b" before "a/b/c") | |
490 | * 4. wd equals delta ("a/b" and "a/b") | |
491 | * 5. wd after delta & delta prefixes wd ("a/b/c" after "a/b/" or "a/b") | |
492 | * 6. wd after delta ("a/c" after "a/b") | |
493 | */ | |
494 | ||
495 | if (cmp < 0) { | |
496 | cmp = pfxcomp(delta->old_file.path, wd->path); | |
497 | ||
d8889d2b | 498 | if (cmp == 0) { |
817d6251 RB |
499 | if (wd->mode == GIT_FILEMODE_TREE) { |
500 | /* case 2 - entry prefixed by workdir tree */ | |
cee695ae RB |
501 | error = git_iterator_advance_into_or_over(&wd, workdir); |
502 | if (error && error != GIT_ITEROVER) | |
503 | goto fail; | |
395509ff | 504 | *wditem_ptr = wd; |
817d6251 RB |
505 | continue; |
506 | } | |
507 | ||
508 | /* case 3 maybe - wd contains non-dir where dir expected */ | |
509 | if (delta->old_file.path[strlen(wd->path)] == '/') { | |
510 | act = checkout_action_with_wd_blocker(data, delta, wd); | |
511 | *wditem_ptr = | |
169dc616 | 512 | git_iterator_advance(&wd, workdir) ? NULL : wd; |
817d6251 RB |
513 | return act; |
514 | } | |
7e5c8a5b | 515 | } |
cf208031 | 516 | |
7e5c8a5b | 517 | /* case 1 - handle wd item (if it matches pathspec) */ |
cee695ae RB |
518 | if (checkout_action_wd_only(data, workdir, wd, pathspec) < 0) |
519 | goto fail; | |
520 | if ((error = git_iterator_advance(&wd, workdir)) < 0 && | |
521 | error != GIT_ITEROVER) | |
7e5c8a5b | 522 | goto fail; |
cf208031 | 523 | |
7e5c8a5b RB |
524 | *wditem_ptr = wd; |
525 | continue; | |
526 | } | |
cf208031 | 527 | |
7e5c8a5b RB |
528 | if (cmp == 0) { |
529 | /* case 4 */ | |
530 | act = checkout_action_with_wd(data, delta, wd); | |
169dc616 | 531 | *wditem_ptr = git_iterator_advance(&wd, workdir) ? NULL : wd; |
7e5c8a5b RB |
532 | return act; |
533 | } | |
cf208031 | 534 | |
7e5c8a5b | 535 | cmp = pfxcomp(wd->path, delta->old_file.path); |
cf208031 | 536 | |
7e5c8a5b | 537 | if (cmp == 0) { /* case 5 */ |
817d6251 | 538 | if (wd->path[strlen(delta->old_file.path)] != '/') |
e0548c0e RB |
539 | return checkout_action_no_wd(data, delta); |
540 | ||
541 | if (delta->status == GIT_DELTA_TYPECHANGE) { | |
542 | if (delta->old_file.mode == GIT_FILEMODE_TREE) { | |
543 | act = checkout_action_with_wd(data, delta, wd); | |
cee695ae RB |
544 | if ((error = git_iterator_advance_into(&wd, workdir)) < 0 && |
545 | error != GIT_ENOTFOUND) | |
546 | goto fail; | |
e0548c0e RB |
547 | *wditem_ptr = wd; |
548 | return act; | |
549 | } | |
550 | ||
551 | if (delta->new_file.mode == GIT_FILEMODE_TREE || | |
552 | delta->new_file.mode == GIT_FILEMODE_COMMIT || | |
553 | delta->old_file.mode == GIT_FILEMODE_COMMIT) | |
554 | { | |
555 | act = checkout_action_with_wd(data, delta, wd); | |
cee695ae RB |
556 | if ((error = git_iterator_advance(&wd, workdir)) < 0 && |
557 | error != GIT_ITEROVER) | |
558 | goto fail; | |
e0548c0e RB |
559 | *wditem_ptr = wd; |
560 | return act; | |
561 | } | |
cf208031 | 562 | } |
cf208031 | 563 | |
7e5c8a5b RB |
564 | return checkout_action_with_wd_dir(data, delta, wd); |
565 | } | |
cf208031 | 566 | |
7e5c8a5b RB |
567 | /* case 6 - wd is after delta */ |
568 | return checkout_action_no_wd(data, delta); | |
cf208031 RB |
569 | } |
570 | ||
7e5c8a5b RB |
571 | fail: |
572 | *wditem_ptr = NULL; | |
573 | return -1; | |
cf208031 RB |
574 | } |
575 | ||
d8889d2b RB |
576 | static int checkout_remaining_wd_items( |
577 | checkout_data *data, | |
578 | git_iterator *workdir, | |
579 | const git_index_entry *wd, | |
580 | git_vector *spec) | |
581 | { | |
582 | int error = 0; | |
d8889d2b RB |
583 | |
584 | while (wd && !error) { | |
817d6251 | 585 | if (!(error = checkout_action_wd_only(data, workdir, wd, spec))) |
169dc616 | 586 | error = git_iterator_advance(&wd, workdir); |
d8889d2b RB |
587 | } |
588 | ||
cee695ae RB |
589 | if (error == GIT_ITEROVER) |
590 | error = 0; | |
591 | ||
d8889d2b RB |
592 | return error; |
593 | } | |
594 | ||
cf208031 RB |
595 | static int checkout_get_actions( |
596 | uint32_t **actions_ptr, | |
597 | size_t **counts_ptr, | |
7e5c8a5b RB |
598 | checkout_data *data, |
599 | git_iterator *workdir) | |
cf208031 RB |
600 | { |
601 | int error = 0; | |
cf208031 RB |
602 | const git_index_entry *wditem; |
603 | git_vector pathspec = GIT_VECTOR_INIT, *deltas; | |
604 | git_pool pathpool = GIT_POOL_INIT_STRINGPOOL; | |
605 | git_diff_delta *delta; | |
606 | size_t i, *counts = NULL; | |
607 | uint32_t *actions = NULL; | |
cf208031 | 608 | |
7e5c8a5b | 609 | if (data->opts.paths.count > 0 && |
d2ce27dd | 610 | git_pathspec__vinit(&pathspec, &data->opts.paths, &pathpool) < 0) |
cf208031 RB |
611 | return -1; |
612 | ||
cee695ae RB |
613 | if ((error = git_iterator_current(&wditem, workdir)) < 0 && |
614 | error != GIT_ITEROVER) | |
cf208031 RB |
615 | goto fail; |
616 | ||
617 | deltas = &data->diff->deltas; | |
618 | ||
619 | *counts_ptr = counts = git__calloc(CHECKOUT_ACTION__MAX+1, sizeof(size_t)); | |
620 | *actions_ptr = actions = git__calloc( | |
621 | deltas->length ? deltas->length : 1, sizeof(uint32_t)); | |
622 | if (!counts || !actions) { | |
623 | error = -1; | |
624 | goto fail; | |
625 | } | |
626 | ||
627 | git_vector_foreach(deltas, i, delta) { | |
7e5c8a5b | 628 | int act = checkout_action(data, delta, workdir, &wditem, &pathspec); |
cf208031 | 629 | |
cf208031 RB |
630 | if (act < 0) { |
631 | error = act; | |
632 | goto fail; | |
633 | } | |
634 | ||
cf208031 RB |
635 | actions[i] = act; |
636 | ||
637 | if (act & CHECKOUT_ACTION__REMOVE) | |
638 | counts[CHECKOUT_ACTION__REMOVE]++; | |
639 | if (act & CHECKOUT_ACTION__UPDATE_BLOB) | |
640 | counts[CHECKOUT_ACTION__UPDATE_BLOB]++; | |
641 | if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE) | |
642 | counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]++; | |
643 | if (act & CHECKOUT_ACTION__CONFLICT) | |
644 | counts[CHECKOUT_ACTION__CONFLICT]++; | |
645 | } | |
d85296ab | 646 | |
d8889d2b RB |
647 | error = checkout_remaining_wd_items(data, workdir, wditem, &pathspec); |
648 | if (error < 0) | |
649 | goto fail; | |
16a666d3 | 650 | |
7e5c8a5b RB |
651 | counts[CHECKOUT_ACTION__REMOVE] += data->removes.length; |
652 | ||
653 | if (counts[CHECKOUT_ACTION__CONFLICT] > 0 && | |
654 | (data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0) | |
655 | { | |
cf208031 RB |
656 | giterr_set(GITERR_CHECKOUT, "%d conflicts prevent checkout", |
657 | (int)counts[CHECKOUT_ACTION__CONFLICT]); | |
4a0ac175 | 658 | error = GIT_EMERGECONFLICT; |
cf208031 RB |
659 | goto fail; |
660 | } | |
661 | ||
d2ce27dd | 662 | git_pathspec__vfree(&pathspec); |
cf208031 RB |
663 | git_pool_clear(&pathpool); |
664 | ||
665 | return 0; | |
666 | ||
667 | fail: | |
668 | *counts_ptr = NULL; | |
669 | git__free(counts); | |
670 | *actions_ptr = NULL; | |
671 | git__free(actions); | |
672 | ||
d2ce27dd | 673 | git_pathspec__vfree(&pathspec); |
cf208031 RB |
674 | git_pool_clear(&pathpool); |
675 | ||
676 | return error; | |
677 | } | |
678 | ||
3aa443a9 | 679 | static int buffer_to_file( |
5cf9875a | 680 | struct stat *st, |
a9f51e43 | 681 | git_buf *buf, |
3aa443a9 | 682 | const char *path, |
28abf3db | 683 | mode_t dir_mode, |
3aa443a9 | 684 | int file_open_flags, |
685 | mode_t file_mode) | |
686 | { | |
4742148d | 687 | int error; |
ec532d5e | 688 | |
0d64bef9 RB |
689 | if ((error = git_futils_mkpath2file(path, dir_mode)) < 0) |
690 | return error; | |
ec532d5e | 691 | |
4742148d | 692 | if ((error = git_futils_writebuffer( |
a9f51e43 | 693 | buf, path, file_open_flags, file_mode)) < 0) |
4742148d | 694 | return error; |
d828f118 | 695 | |
f240acce RB |
696 | if (st != NULL && (error = p_stat(path, st)) < 0) |
697 | giterr_set(GITERR_OS, "Error statting '%s'", path); | |
0d64bef9 | 698 | |
a7fcc44d | 699 | else if (GIT_PERMS_IS_EXEC(file_mode) && |
f240acce | 700 | (error = p_chmod(path, file_mode)) < 0) |
0d64bef9 RB |
701 | giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path); |
702 | ||
f240acce | 703 | return error; |
1d68fcd0 BS |
704 | } |
705 | ||
3aa443a9 | 706 | static int blob_content_to_file( |
5cf9875a | 707 | struct stat *st, |
3aa443a9 | 708 | git_blob *blob, |
709 | const char *path, | |
28abf3db | 710 | mode_t entry_filemode, |
3aa443a9 | 711 | git_checkout_opts *opts) |
dc1b0909 | 712 | { |
85d54812 RB |
713 | int error = 0; |
714 | mode_t file_mode = opts->file_mode ? opts->file_mode : entry_filemode; | |
a9f51e43 | 715 | git_buf out = GIT_BUF_INIT; |
85d54812 | 716 | git_filter_list *fl = NULL; |
6eb240b0 | 717 | |
0e32635f | 718 | if (!opts->disable_filters) |
85d54812 | 719 | error = git_filter_list_load( |
4b11f25a | 720 | &fl, git_blob_owner(blob), blob, path, GIT_FILTER_TO_WORKTREE); |
5e4cb4f4 | 721 | |
2a7d224f RB |
722 | if (!error) |
723 | error = git_filter_list_apply_to_blob(&out, fl, blob); | |
3aa443a9 | 724 | |
2a7d224f | 725 | git_filter_list_free(fl); |
5e4cb4f4 | 726 | |
2a7d224f RB |
727 | if (!error) { |
728 | error = buffer_to_file( | |
729 | st, &out, path, opts->dir_mode, opts->file_open_flags, file_mode); | |
095ccc01 | 730 | |
5cf9875a | 731 | st->st_mode = entry_filemode; |
095ccc01 | 732 | |
a9f51e43 | 733 | git_buf_free(&out); |
2a7d224f | 734 | } |
5e4cb4f4 | 735 | |
736 | return error; | |
3aa443a9 | 737 | } |
738 | ||
ad9a921b | 739 | static int blob_content_to_link( |
55d3a390 RB |
740 | struct stat *st, |
741 | git_blob *blob, | |
742 | const char *path, | |
743 | mode_t dir_mode, | |
744 | int can_symlink) | |
3aa443a9 | 745 | { |
746 | git_buf linktarget = GIT_BUF_INIT; | |
747 | int error; | |
6eb240b0 | 748 | |
fbcab44b | 749 | if ((error = git_futils_mkpath2file(path, dir_mode)) < 0) |
0cb16fe9 | 750 | return error; |
55d3a390 | 751 | |
fade21db RB |
752 | if ((error = git_blob__getbuf(&linktarget, blob)) < 0) |
753 | return error; | |
3aa443a9 | 754 | |
5cf9875a RB |
755 | if (can_symlink) { |
756 | if ((error = p_symlink(git_buf_cstr(&linktarget), path)) < 0) | |
55d3a390 | 757 | giterr_set(GITERR_OS, "Could not create symlink %s\n", path); |
5cf9875a | 758 | } else { |
3aa443a9 | 759 | error = git_futils_fake_symlink(git_buf_cstr(&linktarget), path); |
5cf9875a RB |
760 | } |
761 | ||
762 | if (!error) { | |
763 | if ((error = p_lstat(path, st)) < 0) | |
764 | giterr_set(GITERR_CHECKOUT, "Could not stat symlink %s", path); | |
765 | ||
766 | st->st_mode = GIT_FILEMODE_LINK; | |
767 | } | |
4a26ee4f | 768 | |
3aa443a9 | 769 | git_buf_free(&linktarget); |
770 | ||
771 | return error; | |
24b0d3d5 BS |
772 | } |
773 | ||
5cf9875a RB |
774 | static int checkout_update_index( |
775 | checkout_data *data, | |
776 | const git_diff_file *file, | |
777 | struct stat *st) | |
778 | { | |
779 | git_index_entry entry; | |
780 | ||
781 | if (!data->index) | |
782 | return 0; | |
783 | ||
784 | memset(&entry, 0, sizeof(entry)); | |
785 | entry.path = (char *)file->path; /* cast to prevent warning */ | |
14997dc5 RB |
786 | git_index_entry__init_from_stat( |
787 | &entry, st, !(git_index_caps(data->index) & GIT_INDEXCAP_NO_FILEMODE)); | |
5cf9875a RB |
788 | git_oid_cpy(&entry.oid, &file->oid); |
789 | ||
790 | return git_index_add(data->index, &entry); | |
791 | } | |
792 | ||
dcb0f7c0 RB |
793 | static int checkout_submodule_update_index( |
794 | checkout_data *data, | |
795 | const git_diff_file *file) | |
796 | { | |
797 | struct stat st; | |
798 | ||
799 | /* update the index unless prevented */ | |
800 | if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0) | |
801 | return 0; | |
802 | ||
803 | git_buf_truncate(&data->path, data->workdir_len); | |
804 | if (git_buf_puts(&data->path, file->path) < 0) | |
805 | return -1; | |
806 | ||
807 | if (p_stat(git_buf_cstr(&data->path), &st) < 0) { | |
808 | giterr_set( | |
809 | GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path); | |
810 | return GIT_ENOTFOUND; | |
811 | } | |
812 | ||
813 | st.st_mode = GIT_FILEMODE_COMMIT; | |
814 | ||
815 | return checkout_update_index(data, file, &st); | |
816 | } | |
817 | ||
0d64bef9 | 818 | static int checkout_submodule( |
7e5c8a5b | 819 | checkout_data *data, |
0d64bef9 RB |
820 | const git_diff_file *file) |
821 | { | |
5cf9875a | 822 | int error = 0; |
e0548c0e | 823 | git_submodule *sm; |
5cf9875a | 824 | |
ad9a921b | 825 | /* Until submodules are supported, UPDATE_ONLY means do nothing here */ |
7e5c8a5b | 826 | if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) |
ad9a921b RB |
827 | return 0; |
828 | ||
5cf9875a | 829 | if ((error = git_futils_mkdir( |
9094ae5a | 830 | file->path, data->opts.target_directory, |
5cf9875a RB |
831 | data->opts.dir_mode, GIT_MKDIR_PATH)) < 0) |
832 | return error; | |
0d64bef9 | 833 | |
dcb0f7c0 RB |
834 | if ((error = git_submodule_lookup(&sm, data->repo, file->path)) < 0) { |
835 | /* I've observed repos with submodules in the tree that do not | |
836 | * have a .gitmodules - core Git just makes an empty directory | |
837 | */ | |
838 | if (error == GIT_ENOTFOUND) { | |
839 | giterr_clear(); | |
840 | return checkout_submodule_update_index(data, file); | |
841 | } | |
842 | ||
e0548c0e | 843 | return error; |
dcb0f7c0 | 844 | } |
e0548c0e | 845 | |
ad9a921b | 846 | /* TODO: Support checkout_strategy options. Two circumstances: |
0d64bef9 RB |
847 | * 1 - submodule already checked out, but we need to move the HEAD |
848 | * to the new OID, or | |
849 | * 2 - submodule not checked out and we should recursively check it out | |
850 | * | |
ad9a921b RB |
851 | * Checkout will not execute a pull on the submodule, but a clone |
852 | * command should probably be able to. Do we need a submodule callback? | |
0d64bef9 RB |
853 | */ |
854 | ||
dcb0f7c0 | 855 | return checkout_submodule_update_index(data, file); |
0d64bef9 RB |
856 | } |
857 | ||
45b60d7b | 858 | static void report_progress( |
7e5c8a5b | 859 | checkout_data *data, |
32def5af | 860 | const char *path) |
45b60d7b | 861 | { |
7e5c8a5b RB |
862 | if (data->opts.progress_cb) |
863 | data->opts.progress_cb( | |
ad9a921b | 864 | path, data->completed_steps, data->total_steps, |
7e5c8a5b | 865 | data->opts.progress_payload); |
45b60d7b BS |
866 | } |
867 | ||
0d70f650 RB |
868 | static int checkout_safe_for_update_only(const char *path, mode_t expected_mode) |
869 | { | |
870 | struct stat st; | |
871 | ||
872 | if (p_lstat(path, &st) < 0) { | |
873 | /* if doesn't exist, then no error and no update */ | |
874 | if (errno == ENOENT || errno == ENOTDIR) | |
875 | return 0; | |
876 | ||
877 | /* otherwise, stat error and no update */ | |
878 | giterr_set(GITERR_OS, "Failed to stat file '%s'", path); | |
879 | return -1; | |
880 | } | |
881 | ||
882 | /* only safe for update if this is the same type of file */ | |
883 | if ((st.st_mode & ~0777) == (expected_mode & ~0777)) | |
884 | return 1; | |
885 | ||
886 | return 0; | |
887 | } | |
888 | ||
3aa443a9 | 889 | static int checkout_blob( |
7e5c8a5b | 890 | checkout_data *data, |
9c05c17b | 891 | const git_diff_file *file) |
ec532d5e | 892 | { |
32def5af | 893 | int error = 0; |
ad9a921b | 894 | git_blob *blob; |
5cf9875a | 895 | struct stat st; |
1d68fcd0 | 896 | |
7e5c8a5b RB |
897 | git_buf_truncate(&data->path, data->workdir_len); |
898 | if (git_buf_puts(&data->path, file->path) < 0) | |
0d64bef9 | 899 | return -1; |
3aa443a9 | 900 | |
0d70f650 RB |
901 | if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) { |
902 | int rval = checkout_safe_for_update_only( | |
903 | git_buf_cstr(&data->path), file->mode); | |
904 | if (rval <= 0) | |
905 | return rval; | |
906 | } | |
907 | ||
ad9a921b | 908 | if ((error = git_blob_lookup(&blob, data->repo, &file->oid)) < 0) |
0d64bef9 RB |
909 | return error; |
910 | ||
911 | if (S_ISLNK(file->mode)) | |
912 | error = blob_content_to_link( | |
fbcab44b | 913 | &st, blob, git_buf_cstr(&data->path), data->opts.dir_mode, data->can_symlink); |
3aa443a9 | 914 | else |
0d64bef9 | 915 | error = blob_content_to_file( |
5cf9875a | 916 | &st, blob, git_buf_cstr(&data->path), file->mode, &data->opts); |
3aa443a9 | 917 | |
918 | git_blob_free(blob); | |
919 | ||
7e5c8a5b RB |
920 | /* if we try to create the blob and an existing directory blocks it from |
921 | * being written, then there must have been a typechange conflict in a | |
922 | * parent directory - suppress the error and try to continue. | |
923 | */ | |
924 | if ((data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) != 0 && | |
925 | (error == GIT_ENOTFOUND || error == GIT_EEXISTS)) | |
926 | { | |
927 | giterr_clear(); | |
928 | error = 0; | |
929 | } | |
930 | ||
5cf9875a RB |
931 | /* update the index unless prevented */ |
932 | if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) | |
933 | error = checkout_update_index(data, file, &st); | |
934 | ||
e0548c0e RB |
935 | /* update the submodule data if this was a new .gitmodules file */ |
936 | if (!error && strcmp(file->path, ".gitmodules") == 0) | |
937 | data->reload_submodules = true; | |
938 | ||
3aa443a9 | 939 | return error; |
940 | } | |
941 | ||
32def5af | 942 | static int checkout_remove_the_old( |
32def5af | 943 | unsigned int *actions, |
7e5c8a5b | 944 | checkout_data *data) |
32def5af | 945 | { |
cf208031 | 946 | int error = 0; |
32def5af | 947 | git_diff_delta *delta; |
7e5c8a5b | 948 | const char *str; |
32def5af | 949 | size_t i; |
7e5c8a5b RB |
950 | const char *workdir = git_buf_cstr(&data->path); |
951 | uint32_t flg = GIT_RMDIR_EMPTY_PARENTS | | |
952 | GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS; | |
32def5af | 953 | |
e09d18ee ET |
954 | if (data->opts.checkout_strategy & GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES) |
955 | flg |= GIT_RMDIR_SKIP_NONEMPTY; | |
956 | ||
7e5c8a5b | 957 | git_buf_truncate(&data->path, data->workdir_len); |
ad9a921b | 958 | |
cf208031 | 959 | git_vector_foreach(&data->diff->deltas, i, delta) { |
32def5af | 960 | if (actions[i] & CHECKOUT_ACTION__REMOVE) { |
cf208031 | 961 | error = git_futils_rmdir_r(delta->old_file.path, workdir, flg); |
7e5c8a5b | 962 | if (error < 0) |
32def5af RB |
963 | return error; |
964 | ||
965 | data->completed_steps++; | |
cf208031 | 966 | report_progress(data, delta->old_file.path); |
5cf9875a RB |
967 | |
968 | if ((actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) == 0 && | |
969 | (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 && | |
970 | data->index != NULL) | |
971 | { | |
972 | (void)git_index_remove(data->index, delta->old_file.path, 0); | |
973 | } | |
32def5af RB |
974 | } |
975 | } | |
976 | ||
7e5c8a5b RB |
977 | git_vector_foreach(&data->removes, i, str) { |
978 | error = git_futils_rmdir_r(str, workdir, flg); | |
979 | if (error < 0) | |
980 | return error; | |
981 | ||
982 | data->completed_steps++; | |
983 | report_progress(data, str); | |
5cf9875a RB |
984 | |
985 | if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 && | |
986 | data->index != NULL) | |
987 | { | |
817d6251 RB |
988 | if (str[strlen(str) - 1] == '/') |
989 | (void)git_index_remove_directory(data->index, str, 0); | |
990 | else | |
991 | (void)git_index_remove(data->index, str, 0); | |
5cf9875a | 992 | } |
7e5c8a5b RB |
993 | } |
994 | ||
995 | return 0; | |
996 | } | |
997 | ||
998 | static int checkout_deferred_remove(git_repository *repo, const char *path) | |
999 | { | |
1000 | #if 0 | |
1001 | int error = git_futils_rmdir_r( | |
9094ae5a | 1002 | path, data->opts.target_directory, GIT_RMDIR_EMPTY_PARENTS); |
7e5c8a5b RB |
1003 | |
1004 | if (error == GIT_ENOTFOUND) { | |
1005 | error = 0; | |
1006 | giterr_clear(); | |
1007 | } | |
1008 | ||
1009 | return error; | |
1010 | #else | |
1011 | GIT_UNUSED(repo); | |
1012 | GIT_UNUSED(path); | |
5cf9875a | 1013 | assert(false); |
32def5af | 1014 | return 0; |
7e5c8a5b | 1015 | #endif |
32def5af RB |
1016 | } |
1017 | ||
1018 | static int checkout_create_the_new( | |
32def5af | 1019 | unsigned int *actions, |
7e5c8a5b | 1020 | checkout_data *data) |
32def5af | 1021 | { |
7e5c8a5b | 1022 | int error = 0; |
32def5af RB |
1023 | git_diff_delta *delta; |
1024 | size_t i; | |
1025 | ||
cf208031 | 1026 | git_vector_foreach(&data->diff->deltas, i, delta) { |
7e5c8a5b RB |
1027 | if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) { |
1028 | /* this had a blocker directory that should only be removed iff | |
1029 | * all of the contents of the directory were safely removed | |
1030 | */ | |
1031 | if ((error = checkout_deferred_remove( | |
1032 | data->repo, delta->old_file.path)) < 0) | |
1033 | return error; | |
1034 | } | |
1035 | ||
ad9a921b | 1036 | if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) { |
7e5c8a5b | 1037 | error = checkout_blob(data, &delta->new_file); |
ad9a921b | 1038 | if (error < 0) |
32def5af RB |
1039 | return error; |
1040 | ||
1041 | data->completed_steps++; | |
cf208031 | 1042 | report_progress(data, delta->new_file.path); |
32def5af | 1043 | } |
32def5af RB |
1044 | } |
1045 | ||
1046 | return 0; | |
1047 | } | |
1048 | ||
1049 | static int checkout_create_submodules( | |
32def5af | 1050 | unsigned int *actions, |
7e5c8a5b | 1051 | checkout_data *data) |
32def5af | 1052 | { |
7e5c8a5b | 1053 | int error = 0; |
32def5af RB |
1054 | git_diff_delta *delta; |
1055 | size_t i; | |
1056 | ||
e0548c0e RB |
1057 | /* initial reload of submodules if .gitmodules was changed */ |
1058 | if (data->reload_submodules && | |
1059 | (error = git_submodule_reload_all(data->repo)) < 0) | |
1060 | return error; | |
1061 | ||
cf208031 | 1062 | git_vector_foreach(&data->diff->deltas, i, delta) { |
7e5c8a5b RB |
1063 | if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) { |
1064 | /* this has a blocker directory that should only be removed iff | |
1065 | * all of the contents of the directory were safely removed | |
1066 | */ | |
1067 | if ((error = checkout_deferred_remove( | |
1068 | data->repo, delta->old_file.path)) < 0) | |
1069 | return error; | |
1070 | } | |
1071 | ||
ad9a921b | 1072 | if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) { |
cf208031 | 1073 | int error = checkout_submodule(data, &delta->new_file); |
32def5af RB |
1074 | if (error < 0) |
1075 | return error; | |
1076 | ||
1077 | data->completed_steps++; | |
cf208031 | 1078 | report_progress(data, delta->new_file.path); |
32def5af RB |
1079 | } |
1080 | } | |
1081 | ||
e0548c0e RB |
1082 | /* final reload once submodules have been updated */ |
1083 | return git_submodule_reload_all(data->repo); | |
32def5af RB |
1084 | } |
1085 | ||
7e5c8a5b RB |
1086 | static int checkout_lookup_head_tree(git_tree **out, git_repository *repo) |
1087 | { | |
1088 | int error = 0; | |
1089 | git_reference *ref = NULL; | |
1090 | git_object *head; | |
1091 | ||
1092 | if (!(error = git_repository_head(&ref, repo)) && | |
1093 | !(error = git_reference_peel(&head, ref, GIT_OBJ_TREE))) | |
1094 | *out = (git_tree *)head; | |
1095 | ||
1096 | git_reference_free(ref); | |
1097 | ||
1098 | return error; | |
1099 | } | |
1100 | ||
1101 | static void checkout_data_clear(checkout_data *data) | |
1102 | { | |
1103 | if (data->opts_free_baseline) { | |
1104 | git_tree_free(data->opts.baseline); | |
1105 | data->opts.baseline = NULL; | |
1106 | } | |
1107 | ||
1108 | git_vector_free(&data->removes); | |
1109 | git_pool_clear(&data->pool); | |
1110 | ||
1111 | git__free(data->pfx); | |
1112 | data->pfx = NULL; | |
1113 | ||
1114 | git_buf_free(&data->path); | |
5cf9875a RB |
1115 | |
1116 | git_index_free(data->index); | |
1117 | data->index = NULL; | |
7e5c8a5b RB |
1118 | } |
1119 | ||
1120 | static int checkout_data_init( | |
1121 | checkout_data *data, | |
5cf9875a | 1122 | git_iterator *target, |
eec1c1fe | 1123 | const git_checkout_opts *proposed) |
3aa443a9 | 1124 | { |
7e5c8a5b | 1125 | int error = 0; |
5cf9875a | 1126 | git_repository *repo = git_iterator_owner(target); |
cf208031 | 1127 | |
7e5c8a5b RB |
1128 | memset(data, 0, sizeof(*data)); |
1129 | ||
1130 | if (!repo) { | |
1131 | giterr_set(GITERR_CHECKOUT, "Cannot checkout nothing"); | |
cf208031 | 1132 | return -1; |
7e5c8a5b | 1133 | } |
cf208031 | 1134 | |
9094ae5a RB |
1135 | if ((!proposed || !proposed->target_directory) && |
1136 | (error = git_repository__ensure_not_bare(repo, "checkout")) < 0) | |
7e5c8a5b | 1137 | return error; |
cf208031 | 1138 | |
7e5c8a5b RB |
1139 | data->repo = repo; |
1140 | ||
1141 | GITERR_CHECK_VERSION( | |
1142 | proposed, GIT_CHECKOUT_OPTS_VERSION, "git_checkout_opts"); | |
1143 | ||
1144 | if (!proposed) | |
1145 | GIT_INIT_STRUCTURE(&data->opts, GIT_CHECKOUT_OPTS_VERSION); | |
1146 | else | |
1147 | memmove(&data->opts, proposed, sizeof(git_checkout_opts)); | |
1148 | ||
9094ae5a RB |
1149 | if (!data->opts.target_directory) |
1150 | data->opts.target_directory = git_repository_workdir(repo); | |
1151 | else if (!git_path_isdir(data->opts.target_directory) && | |
1152 | (error = git_futils_mkdir(data->opts.target_directory, NULL, | |
1153 | GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0) | |
1154 | goto cleanup; | |
1155 | ||
5cf9875a RB |
1156 | /* refresh config and index content unless NO_REFRESH is given */ |
1157 | if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) { | |
eac76c23 RB |
1158 | git_config *cfg; |
1159 | ||
1160 | if ((error = git_repository_config__weakptr(&cfg, repo)) < 0 || | |
1161 | (error = git_config_refresh(cfg)) < 0) | |
5cf9875a RB |
1162 | goto cleanup; |
1163 | ||
169dc616 RB |
1164 | /* if we are checking out the index, don't reload, |
1165 | * otherwise get index and force reload | |
1166 | */ | |
1167 | if ((data->index = git_iterator_get_index(target)) != NULL) { | |
5cf9875a RB |
1168 | GIT_REFCOUNT_INC(data->index); |
1169 | } else { | |
1170 | /* otherwise, grab and reload the index */ | |
1171 | if ((error = git_repository_index(&data->index, data->repo)) < 0 || | |
1172 | (error = git_index_read(data->index)) < 0) | |
1173 | goto cleanup; | |
169dc616 | 1174 | |
5bddabcc ET |
1175 | /* clear the REUC when doing a tree or commit checkout */ |
1176 | git_index_reuc_clear(data->index); | |
5cf9875a RB |
1177 | } |
1178 | } | |
7e5c8a5b | 1179 | |
5cf9875a | 1180 | /* if you are forcing, definitely allow safe updates */ |
7e5c8a5b RB |
1181 | if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) != 0) |
1182 | data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE_CREATE; | |
1183 | if ((data->opts.checkout_strategy & GIT_CHECKOUT_SAFE_CREATE) != 0) | |
1184 | data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE; | |
1185 | ||
1186 | data->strategy = data->opts.checkout_strategy; | |
1187 | ||
1188 | /* opts->disable_filters is false by default */ | |
1189 | ||
1190 | if (!data->opts.dir_mode) | |
1191 | data->opts.dir_mode = GIT_DIR_MODE; | |
1192 | ||
1193 | if (!data->opts.file_open_flags) | |
1194 | data->opts.file_open_flags = O_CREAT | O_TRUNC | O_WRONLY; | |
1195 | ||
1196 | data->pfx = git_pathspec_prefix(&data->opts.paths); | |
1197 | ||
eac76c23 RB |
1198 | if ((error = git_repository__cvar( |
1199 | &data->can_symlink, repo, GIT_CVAR_SYMLINKS)) < 0) | |
1200 | goto cleanup; | |
cf208031 | 1201 | |
7e5c8a5b RB |
1202 | if (!data->opts.baseline) { |
1203 | data->opts_free_baseline = true; | |
eac76c23 | 1204 | |
2a3b3e03 | 1205 | error = checkout_lookup_head_tree(&data->opts.baseline, repo); |
1206 | ||
605da51a | 1207 | if (error == GIT_EUNBORNBRANCH) { |
2a3b3e03 | 1208 | error = 0; |
1209 | giterr_clear(); | |
1210 | } | |
1211 | ||
1212 | if (error < 0) | |
7e5c8a5b RB |
1213 | goto cleanup; |
1214 | } | |
1215 | ||
1216 | if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 || | |
1217 | (error = git_pool_init(&data->pool, 1, 0)) < 0 || | |
9094ae5a RB |
1218 | (error = git_buf_puts(&data->path, data->opts.target_directory)) < 0 || |
1219 | (error = git_path_to_dir(&data->path)) < 0) | |
7e5c8a5b RB |
1220 | goto cleanup; |
1221 | ||
1222 | data->workdir_len = git_buf_len(&data->path); | |
1223 | ||
1224 | cleanup: | |
1225 | if (error < 0) | |
1226 | checkout_data_clear(data); | |
cf208031 RB |
1227 | |
1228 | return error; | |
1229 | } | |
1230 | ||
7e5c8a5b RB |
1231 | int git_checkout_iterator( |
1232 | git_iterator *target, | |
eec1c1fe | 1233 | const git_checkout_opts *opts) |
cf208031 RB |
1234 | { |
1235 | int error = 0; | |
7e5c8a5b RB |
1236 | git_iterator *baseline = NULL, *workdir = NULL; |
1237 | checkout_data data = {0}; | |
cf208031 | 1238 | git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; |
32def5af RB |
1239 | uint32_t *actions = NULL; |
1240 | size_t *counts = NULL; | |
134d8c91 | 1241 | git_iterator_flag_t iterflags = 0; |
3aa443a9 | 1242 | |
7e5c8a5b | 1243 | /* initialize structures and options */ |
5cf9875a | 1244 | error = checkout_data_init(&data, target, opts); |
7e5c8a5b RB |
1245 | if (error < 0) |
1246 | return error; | |
c7231c45 | 1247 | |
7e5c8a5b RB |
1248 | diff_opts.flags = |
1249 | GIT_DIFF_INCLUDE_UNMODIFIED | | |
1250 | GIT_DIFF_INCLUDE_UNTRACKED | | |
1251 | GIT_DIFF_RECURSE_UNTRACKED_DIRS | /* needed to match baseline */ | |
1252 | GIT_DIFF_INCLUDE_IGNORED | | |
1253 | GIT_DIFF_INCLUDE_TYPECHANGE | | |
1254 | GIT_DIFF_INCLUDE_TYPECHANGE_TREES | | |
1255 | GIT_DIFF_SKIP_BINARY_CHECK; | |
40342bd2 RB |
1256 | if (data.opts.checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) |
1257 | diff_opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; | |
7e5c8a5b RB |
1258 | if (data.opts.paths.count > 0) |
1259 | diff_opts.pathspec = data.opts.paths; | |
1260 | ||
1261 | /* set up iterators */ | |
134d8c91 RB |
1262 | |
1263 | iterflags = git_iterator_ignore_case(target) ? | |
1264 | GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; | |
1265 | ||
7e5c8a5b | 1266 | if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 || |
9094ae5a RB |
1267 | (error = git_iterator_for_workdir_ext( |
1268 | &workdir, data.repo, data.opts.target_directory, | |
1269 | iterflags | GIT_ITERATOR_DONT_AUTOEXPAND, | |
9bea03ce | 1270 | data.pfx, data.pfx)) < 0 || |
169dc616 | 1271 | (error = git_iterator_for_tree( |
9094ae5a RB |
1272 | &baseline, data.opts.baseline, |
1273 | iterflags, data.pfx, data.pfx)) < 0) | |
7e5c8a5b RB |
1274 | goto cleanup; |
1275 | ||
cc216a01 RB |
1276 | /* Should not have case insensitivity mismatch */ |
1277 | assert(git_iterator_ignore_case(workdir) == git_iterator_ignore_case(baseline)); | |
3aa443a9 | 1278 | |
77cffa31 RB |
1279 | /* Generate baseline-to-target diff which will include an entry for |
1280 | * every possible update that might need to be made. | |
cf208031 RB |
1281 | */ |
1282 | if ((error = git_diff__from_iterators( | |
7e5c8a5b | 1283 | &data.diff, data.repo, baseline, target, &diff_opts)) < 0) |
3aa443a9 | 1284 | goto cleanup; |
1285 | ||
77cffa31 RB |
1286 | /* Loop through diff (and working directory iterator) building a list of |
1287 | * actions to be taken, plus look for conflicts and send notifications. | |
32def5af | 1288 | */ |
7e5c8a5b | 1289 | if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) < 0) |
ad9a921b RB |
1290 | goto cleanup; |
1291 | ||
32def5af | 1292 | data.total_steps = counts[CHECKOUT_ACTION__REMOVE] + |
ad9a921b RB |
1293 | counts[CHECKOUT_ACTION__UPDATE_BLOB] + |
1294 | counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]; | |
3aa443a9 | 1295 | |
ad9a921b | 1296 | report_progress(&data, NULL); /* establish 0 baseline */ |
45b60d7b | 1297 | |
77cffa31 RB |
1298 | /* To deal with some order dependencies, perform remaining checkout |
1299 | * in three passes: removes, then update blobs, then update submodules. | |
1300 | */ | |
32def5af | 1301 | if (counts[CHECKOUT_ACTION__REMOVE] > 0 && |
cf208031 | 1302 | (error = checkout_remove_the_old(actions, &data)) < 0) |
32def5af RB |
1303 | goto cleanup; |
1304 | ||
ad9a921b | 1305 | if (counts[CHECKOUT_ACTION__UPDATE_BLOB] > 0 && |
cf208031 | 1306 | (error = checkout_create_the_new(actions, &data)) < 0) |
32def5af RB |
1307 | goto cleanup; |
1308 | ||
ad9a921b | 1309 | if (counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] > 0 && |
cf208031 | 1310 | (error = checkout_create_submodules(actions, &data)) < 0) |
32def5af RB |
1311 | goto cleanup; |
1312 | ||
1313 | assert(data.completed_steps == data.total_steps); | |
3aa443a9 | 1314 | |
0d64bef9 | 1315 | cleanup: |
fade21db | 1316 | if (error == GIT_EUSER) |
32def5af | 1317 | giterr_clear(); |
fade21db | 1318 | |
5cf9875a RB |
1319 | if (!error && data.index != NULL && |
1320 | (data.strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) | |
1321 | error = git_index_write(data.index); | |
1322 | ||
cf208031 | 1323 | git_diff_list_free(data.diff); |
7e5c8a5b | 1324 | git_iterator_free(workdir); |
dde7602a | 1325 | git_iterator_free(baseline); |
32def5af RB |
1326 | git__free(actions); |
1327 | git__free(counts); | |
7e5c8a5b | 1328 | checkout_data_clear(&data); |
cf208031 RB |
1329 | |
1330 | return error; | |
1331 | } | |
1332 | ||
cf208031 RB |
1333 | int git_checkout_index( |
1334 | git_repository *repo, | |
1335 | git_index *index, | |
1336 | git_checkout_opts *opts) | |
1337 | { | |
1338 | int error; | |
7e5c8a5b | 1339 | git_iterator *index_i; |
cf208031 | 1340 | |
6a15e8d2 RB |
1341 | if (!index && !repo) { |
1342 | giterr_set(GITERR_CHECKOUT, | |
1343 | "Must provide either repository or index to checkout"); | |
1344 | return -1; | |
1345 | } | |
1346 | if (index && repo && git_index_owner(index) != repo) { | |
1347 | giterr_set(GITERR_CHECKOUT, | |
1348 | "Index to checkout does not match repository"); | |
1349 | return -1; | |
1350 | } | |
1351 | ||
1352 | if (!repo) | |
1353 | repo = git_index_owner(index); | |
cf208031 RB |
1354 | |
1355 | if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) | |
1356 | return error; | |
7e5c8a5b | 1357 | GIT_REFCOUNT_INC(index); |
cf208031 | 1358 | |
169dc616 | 1359 | if (!(error = git_iterator_for_index(&index_i, index, 0, NULL, NULL))) |
7e5c8a5b | 1360 | error = git_checkout_iterator(index_i, opts); |
cf208031 | 1361 | |
cf208031 | 1362 | git_iterator_free(index_i); |
7e5c8a5b | 1363 | git_index_free(index); |
0d64bef9 | 1364 | |
e93af304 | 1365 | return error; |
1366 | } | |
1367 | ||
1368 | int git_checkout_tree( | |
1369 | git_repository *repo, | |
cfbe4be3 | 1370 | const git_object *treeish, |
80642656 | 1371 | git_checkout_opts *opts) |
e93af304 | 1372 | { |
cf208031 | 1373 | int error; |
7e5c8a5b RB |
1374 | git_tree *tree = NULL; |
1375 | git_iterator *tree_i = NULL; | |
cf208031 | 1376 | |
6a15e8d2 RB |
1377 | if (!treeish && !repo) { |
1378 | giterr_set(GITERR_CHECKOUT, | |
1379 | "Must provide either repository or tree to checkout"); | |
1380 | return -1; | |
1381 | } | |
1382 | if (treeish && repo && git_object_owner(treeish) != repo) { | |
1383 | giterr_set(GITERR_CHECKOUT, | |
1384 | "Object to checkout does not match repository"); | |
1385 | return -1; | |
1386 | } | |
1387 | ||
1388 | if (!repo) | |
1389 | repo = git_object_owner(treeish); | |
e93af304 | 1390 | |
1391 | if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) { | |
ad9a921b RB |
1392 | giterr_set( |
1393 | GITERR_CHECKOUT, "Provided object cannot be peeled to a tree"); | |
1394 | return -1; | |
e93af304 | 1395 | } |
1396 | ||
169dc616 | 1397 | if (!(error = git_iterator_for_tree(&tree_i, tree, 0, NULL, NULL))) |
7e5c8a5b | 1398 | error = git_checkout_iterator(tree_i, opts); |
e93af304 | 1399 | |
cf208031 | 1400 | git_iterator_free(tree_i); |
3aa443a9 | 1401 | git_tree_free(tree); |
ad9a921b | 1402 | |
3aa443a9 | 1403 | return error; |
14741d62 BS |
1404 | } |
1405 | ||
3aa443a9 | 1406 | int git_checkout_head( |
1407 | git_repository *repo, | |
eec1c1fe | 1408 | const git_checkout_opts *opts) |
3aa443a9 | 1409 | { |
1410 | int error; | |
7e5c8a5b RB |
1411 | git_tree *head = NULL; |
1412 | git_iterator *head_i = NULL; | |
3aa443a9 | 1413 | |
6a15e8d2 | 1414 | assert(repo); |
cf208031 | 1415 | |
7e5c8a5b | 1416 | if (!(error = checkout_lookup_head_tree(&head, repo)) && |
169dc616 | 1417 | !(error = git_iterator_for_tree(&head_i, head, 0, NULL, NULL))) |
7e5c8a5b | 1418 | error = git_checkout_iterator(head_i, opts); |
cf208031 | 1419 | |
7e5c8a5b | 1420 | git_iterator_free(head_i); |
cf208031 | 1421 | git_tree_free(head); |
14741d62 | 1422 | |
3aa443a9 | 1423 | return error; |
1424 | } |