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