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