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