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