]>
Commit | Line | Data |
---|---|---|
590fb68b | 1 | /* |
359fc2d2 | 2 | * Copyright (C) the libgit2 contributors. All rights reserved. |
590fb68b | 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 "common.h" | |
eae0bfdc | 9 | |
590fb68b | 10 | #include "repository.h" |
11 | #include "commit.h" | |
7f26b1b9 | 12 | #include "message.h" |
590fb68b | 13 | #include "tree.h" |
14 | #include "reflog.h" | |
15 | #include "git2/diff.h" | |
16 | #include "git2/stash.h" | |
17 | #include "git2/status.h" | |
18 | #include "git2/checkout.h" | |
114f5a6c | 19 | #include "git2/index.h" |
f99ca523 | 20 | #include "git2/transaction.h" |
bf8dd3f5 | 21 | #include "git2/merge.h" |
f0957589 | 22 | #include "index.h" |
4ec197f3 | 23 | #include "signature.h" |
f0957589 ET |
24 | #include "iterator.h" |
25 | #include "merge.h" | |
90177111 | 26 | #include "diff.h" |
9be638ec | 27 | #include "diff_generate.h" |
590fb68b | 28 | |
29 | static int create_error(int error, const char *msg) | |
30 | { | |
909d5494 | 31 | giterr_set(GITERR_STASH, "cannot stash changes - %s", msg); |
590fb68b | 32 | return error; |
33 | } | |
34 | ||
590fb68b | 35 | static int retrieve_head(git_reference **out, git_repository *repo) |
36 | { | |
37 | int error = git_repository_head(out, repo); | |
38 | ||
605da51a | 39 | if (error == GIT_EUNBORNBRANCH) |
909d5494 | 40 | return create_error(error, "you do not have the initial commit yet."); |
590fb68b | 41 | |
42 | return error; | |
43 | } | |
44 | ||
45 | static int append_abbreviated_oid(git_buf *out, const git_oid *b_commit) | |
46 | { | |
47 | char *formatted_oid; | |
48 | ||
49 | formatted_oid = git_oid_allocfmt(b_commit); | |
50 | GITERR_CHECK_ALLOC(formatted_oid); | |
51 | ||
52 | git_buf_put(out, formatted_oid, 7); | |
53 | git__free(formatted_oid); | |
54 | ||
55 | return git_buf_oom(out) ? -1 : 0; | |
56 | } | |
57 | ||
58 | static int append_commit_description(git_buf *out, git_commit* commit) | |
59 | { | |
7f26b1b9 ET |
60 | const char *summary = git_commit_summary(commit); |
61 | GITERR_CHECK_ALLOC(summary); | |
590fb68b | 62 | |
63 | if (append_abbreviated_oid(out, git_commit_id(commit)) < 0) | |
64 | return -1; | |
65 | ||
590fb68b | 66 | git_buf_putc(out, ' '); |
7f26b1b9 | 67 | git_buf_puts(out, summary); |
590fb68b | 68 | git_buf_putc(out, '\n'); |
69 | ||
70 | return git_buf_oom(out) ? -1 : 0; | |
71 | } | |
72 | ||
73 | static int retrieve_base_commit_and_message( | |
74 | git_commit **b_commit, | |
75 | git_buf *stash_message, | |
76 | git_repository *repo) | |
77 | { | |
78 | git_reference *head = NULL; | |
79 | int error; | |
80 | ||
81 | if ((error = retrieve_head(&head, repo)) < 0) | |
82 | return error; | |
83 | ||
590fb68b | 84 | if (strcmp("HEAD", git_reference_name(head)) == 0) |
a6a82e1a | 85 | error = git_buf_puts(stash_message, "(no branch): "); |
590fb68b | 86 | else |
a6a82e1a | 87 | error = git_buf_printf( |
590fb68b | 88 | stash_message, |
89 | "%s: ", | |
90 | git_reference_name(head) + strlen(GIT_REFS_HEADS_DIR)); | |
a6a82e1a | 91 | if (error < 0) |
590fb68b | 92 | goto cleanup; |
93 | ||
a6a82e1a RB |
94 | if ((error = git_commit_lookup( |
95 | b_commit, repo, git_reference_target(head))) < 0) | |
590fb68b | 96 | goto cleanup; |
97 | ||
a6a82e1a RB |
98 | if ((error = append_commit_description(stash_message, *b_commit)) < 0) |
99 | goto cleanup; | |
590fb68b | 100 | |
101 | cleanup: | |
102 | git_reference_free(head); | |
103 | return error; | |
104 | } | |
105 | ||
106 | static int build_tree_from_index(git_tree **out, git_index *index) | |
107 | { | |
a6a82e1a | 108 | int error; |
590fb68b | 109 | git_oid i_tree_oid; |
110 | ||
a6a82e1a | 111 | if ((error = git_index_write_tree(&i_tree_oid, index)) < 0) |
44972873 | 112 | return error; |
590fb68b | 113 | |
114 | return git_tree_lookup(out, git_index_owner(index), &i_tree_oid); | |
115 | } | |
116 | ||
117 | static int commit_index( | |
118 | git_commit **i_commit, | |
119 | git_index *index, | |
2274993b | 120 | const git_signature *stasher, |
590fb68b | 121 | const char *message, |
122 | const git_commit *parent) | |
123 | { | |
124 | git_tree *i_tree = NULL; | |
125 | git_oid i_commit_oid; | |
126 | git_buf msg = GIT_BUF_INIT; | |
a6a82e1a | 127 | int error; |
590fb68b | 128 | |
a6a82e1a | 129 | if ((error = build_tree_from_index(&i_tree, index)) < 0) |
590fb68b | 130 | goto cleanup; |
131 | ||
a6a82e1a | 132 | if ((error = git_buf_printf(&msg, "index on %s\n", message)) < 0) |
590fb68b | 133 | goto cleanup; |
134 | ||
a6a82e1a | 135 | if ((error = git_commit_create( |
590fb68b | 136 | &i_commit_oid, |
137 | git_index_owner(index), | |
138 | NULL, | |
139 | stasher, | |
140 | stasher, | |
141 | NULL, | |
142 | git_buf_cstr(&msg), | |
143 | i_tree, | |
144 | 1, | |
a6a82e1a RB |
145 | &parent)) < 0) |
146 | goto cleanup; | |
590fb68b | 147 | |
148 | error = git_commit_lookup(i_commit, git_index_owner(index), &i_commit_oid); | |
149 | ||
150 | cleanup: | |
151 | git_tree_free(i_tree); | |
152 | git_buf_free(&msg); | |
153 | return error; | |
154 | } | |
155 | ||
10672e3e | 156 | struct stash_update_rules { |
590fb68b | 157 | bool include_changed; |
158 | bool include_untracked; | |
159 | bool include_ignored; | |
160 | }; | |
161 | ||
10672e3e RB |
162 | static int stash_update_index_from_diff( |
163 | git_index *index, | |
164 | const git_diff *diff, | |
165 | struct stash_update_rules *data) | |
590fb68b | 166 | { |
10672e3e RB |
167 | int error = 0; |
168 | size_t d, max_d = git_diff_num_deltas(diff); | |
169 | ||
170 | for (d = 0; !error && d < max_d; ++d) { | |
171 | const char *add_path = NULL; | |
172 | const git_diff_delta *delta = git_diff_get_delta(diff, d); | |
173 | ||
174 | switch (delta->status) { | |
175 | case GIT_DELTA_IGNORED: | |
176 | if (data->include_ignored) | |
177 | add_path = delta->new_file.path; | |
178 | break; | |
179 | ||
180 | case GIT_DELTA_UNTRACKED: | |
24d17de2 RB |
181 | if (data->include_untracked && |
182 | delta->new_file.mode != GIT_FILEMODE_TREE) | |
10672e3e | 183 | add_path = delta->new_file.path; |
590fb68b | 184 | break; |
590fb68b | 185 | |
10672e3e RB |
186 | case GIT_DELTA_ADDED: |
187 | case GIT_DELTA_MODIFIED: | |
188 | if (data->include_changed) | |
189 | add_path = delta->new_file.path; | |
190 | break; | |
a6a82e1a | 191 | |
10672e3e RB |
192 | case GIT_DELTA_DELETED: |
193 | if (data->include_changed && | |
194 | !git_index_find(NULL, index, delta->old_file.path)) | |
195 | error = git_index_remove(index, delta->old_file.path, 0); | |
196 | break; | |
197 | ||
198 | default: | |
199 | /* Unimplemented */ | |
200 | giterr_set( | |
201 | GITERR_INVALID, | |
909d5494 | 202 | "cannot update index. Unimplemented status (%d)", |
10672e3e RB |
203 | delta->status); |
204 | return -1; | |
205 | } | |
206 | ||
207 | if (add_path != NULL) | |
208 | error = git_index_add_bypath(index, add_path); | |
209 | } | |
210 | ||
211 | return error; | |
590fb68b | 212 | } |
213 | ||
214 | static int build_untracked_tree( | |
215 | git_tree **tree_out, | |
216 | git_index *index, | |
217 | git_commit *i_commit, | |
218 | uint32_t flags) | |
219 | { | |
220 | git_tree *i_tree = NULL; | |
3ff1d123 | 221 | git_diff *diff = NULL; |
2f8d30be | 222 | git_diff_options opts = GIT_DIFF_OPTIONS_INIT; |
10672e3e | 223 | struct stash_update_rules data = {0}; |
a6a82e1a | 224 | int error; |
590fb68b | 225 | |
226 | git_index_clear(index); | |
227 | ||
590fb68b | 228 | if (flags & GIT_STASH_INCLUDE_UNTRACKED) { |
a6a82e1a RB |
229 | opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | |
230 | GIT_DIFF_RECURSE_UNTRACKED_DIRS; | |
590fb68b | 231 | data.include_untracked = true; |
232 | } | |
233 | ||
234 | if (flags & GIT_STASH_INCLUDE_IGNORED) { | |
7b7aa75f JG |
235 | opts.flags |= GIT_DIFF_INCLUDE_IGNORED | |
236 | GIT_DIFF_RECURSE_IGNORED_DIRS; | |
590fb68b | 237 | data.include_ignored = true; |
238 | } | |
239 | ||
a6a82e1a | 240 | if ((error = git_commit_tree(&i_tree, i_commit)) < 0) |
590fb68b | 241 | goto cleanup; |
242 | ||
a6a82e1a RB |
243 | if ((error = git_diff_tree_to_workdir( |
244 | &diff, git_index_owner(index), i_tree, &opts)) < 0) | |
590fb68b | 245 | goto cleanup; |
246 | ||
10672e3e | 247 | if ((error = stash_update_index_from_diff(index, diff, &data)) < 0) |
590fb68b | 248 | goto cleanup; |
249 | ||
a6a82e1a | 250 | error = build_tree_from_index(tree_out, index); |
590fb68b | 251 | |
252 | cleanup: | |
3ff1d123 | 253 | git_diff_free(diff); |
590fb68b | 254 | git_tree_free(i_tree); |
255 | return error; | |
256 | } | |
257 | ||
258 | static int commit_untracked( | |
259 | git_commit **u_commit, | |
260 | git_index *index, | |
2274993b | 261 | const git_signature *stasher, |
590fb68b | 262 | const char *message, |
263 | git_commit *i_commit, | |
264 | uint32_t flags) | |
265 | { | |
266 | git_tree *u_tree = NULL; | |
267 | git_oid u_commit_oid; | |
268 | git_buf msg = GIT_BUF_INIT; | |
a6a82e1a | 269 | int error; |
590fb68b | 270 | |
a6a82e1a | 271 | if ((error = build_untracked_tree(&u_tree, index, i_commit, flags)) < 0) |
590fb68b | 272 | goto cleanup; |
273 | ||
a6a82e1a | 274 | if ((error = git_buf_printf(&msg, "untracked files on %s\n", message)) < 0) |
590fb68b | 275 | goto cleanup; |
276 | ||
a6a82e1a | 277 | if ((error = git_commit_create( |
590fb68b | 278 | &u_commit_oid, |
279 | git_index_owner(index), | |
280 | NULL, | |
281 | stasher, | |
282 | stasher, | |
283 | NULL, | |
284 | git_buf_cstr(&msg), | |
285 | u_tree, | |
286 | 0, | |
a6a82e1a RB |
287 | NULL)) < 0) |
288 | goto cleanup; | |
590fb68b | 289 | |
290 | error = git_commit_lookup(u_commit, git_index_owner(index), &u_commit_oid); | |
291 | ||
292 | cleanup: | |
293 | git_tree_free(u_tree); | |
294 | git_buf_free(&msg); | |
295 | return error; | |
296 | } | |
297 | ||
90177111 ET |
298 | static git_diff_delta *stash_delta_merge( |
299 | const git_diff_delta *a, | |
300 | const git_diff_delta *b, | |
301 | git_pool *pool) | |
302 | { | |
303 | /* Special case for stash: if a file is deleted in the index, but exists | |
304 | * in the working tree, we need to stash the workdir copy for the workdir. | |
305 | */ | |
306 | if (a->status == GIT_DELTA_DELETED && b->status == GIT_DELTA_UNTRACKED) { | |
307 | git_diff_delta *dup = git_diff__delta_dup(b, pool); | |
308 | ||
309 | if (dup) | |
310 | dup->status = GIT_DELTA_MODIFIED; | |
311 | return dup; | |
312 | } | |
313 | ||
314 | return git_diff__merge_like_cgit(a, b, pool); | |
315 | } | |
316 | ||
590fb68b | 317 | static int build_workdir_tree( |
318 | git_tree **tree_out, | |
319 | git_index *index, | |
320 | git_commit *b_commit) | |
321 | { | |
5735bf5e | 322 | git_repository *repo = git_index_owner(index); |
590fb68b | 323 | git_tree *b_tree = NULL; |
90177111 | 324 | git_diff *diff = NULL, *idx_to_wd = NULL; |
2f8d30be | 325 | git_diff_options opts = GIT_DIFF_OPTIONS_INIT; |
10672e3e | 326 | struct stash_update_rules data = {0}; |
a6a82e1a | 327 | int error; |
590fb68b | 328 | |
90177111 | 329 | opts.flags = GIT_DIFF_IGNORE_SUBMODULES | GIT_DIFF_INCLUDE_UNTRACKED; |
4fe0b0b3 | 330 | |
a6a82e1a | 331 | if ((error = git_commit_tree(&b_tree, b_commit)) < 0) |
590fb68b | 332 | goto cleanup; |
333 | ||
90177111 ET |
334 | if ((error = git_diff_tree_to_index(&diff, repo, b_tree, index, &opts)) < 0 || |
335 | (error = git_diff_index_to_workdir(&idx_to_wd, repo, index, &opts)) < 0 || | |
336 | (error = git_diff__merge(diff, idx_to_wd, stash_delta_merge)) < 0) | |
590fb68b | 337 | goto cleanup; |
338 | ||
590fb68b | 339 | data.include_changed = true; |
340 | ||
10672e3e | 341 | if ((error = stash_update_index_from_diff(index, diff, &data)) < 0) |
590fb68b | 342 | goto cleanup; |
343 | ||
10672e3e | 344 | error = build_tree_from_index(tree_out, index); |
590fb68b | 345 | |
346 | cleanup: | |
90177111 | 347 | git_diff_free(idx_to_wd); |
3ff1d123 | 348 | git_diff_free(diff); |
590fb68b | 349 | git_tree_free(b_tree); |
a6a82e1a | 350 | |
590fb68b | 351 | return error; |
352 | } | |
353 | ||
354 | static int commit_worktree( | |
355 | git_oid *w_commit_oid, | |
356 | git_index *index, | |
2274993b | 357 | const git_signature *stasher, |
590fb68b | 358 | const char *message, |
359 | git_commit *i_commit, | |
360 | git_commit *b_commit, | |
361 | git_commit *u_commit) | |
362 | { | |
a6a82e1a | 363 | int error = 0; |
590fb68b | 364 | git_tree *w_tree = NULL, *i_tree = NULL; |
590fb68b | 365 | const git_commit *parents[] = { NULL, NULL, NULL }; |
366 | ||
367 | parents[0] = b_commit; | |
368 | parents[1] = i_commit; | |
369 | parents[2] = u_commit; | |
370 | ||
a6a82e1a RB |
371 | if ((error = git_commit_tree(&i_tree, i_commit)) < 0) |
372 | goto cleanup; | |
590fb68b | 373 | |
a6a82e1a | 374 | if ((error = git_index_read_tree(index, i_tree)) < 0) |
590fb68b | 375 | goto cleanup; |
376 | ||
a6a82e1a | 377 | if ((error = build_workdir_tree(&w_tree, index, b_commit)) < 0) |
590fb68b | 378 | goto cleanup; |
379 | ||
a6a82e1a | 380 | error = git_commit_create( |
590fb68b | 381 | w_commit_oid, |
382 | git_index_owner(index), | |
383 | NULL, | |
384 | stasher, | |
385 | stasher, | |
386 | NULL, | |
387 | message, | |
388 | w_tree, | |
a6a82e1a RB |
389 | u_commit ? 3 : 2, |
390 | parents); | |
590fb68b | 391 | |
392 | cleanup: | |
393 | git_tree_free(i_tree); | |
394 | git_tree_free(w_tree); | |
395 | return error; | |
396 | } | |
397 | ||
398 | static int prepare_worktree_commit_message( | |
399 | git_buf* msg, | |
400 | const char *user_message) | |
401 | { | |
402 | git_buf buf = GIT_BUF_INIT; | |
a6a82e1a RB |
403 | int error; |
404 | ||
6f58332f RB |
405 | if ((error = git_buf_set(&buf, git_buf_cstr(msg), git_buf_len(msg))) < 0) |
406 | return error; | |
590fb68b | 407 | |
590fb68b | 408 | git_buf_clear(msg); |
409 | ||
410 | if (!user_message) | |
411 | git_buf_printf(msg, "WIP on %s", git_buf_cstr(&buf)); | |
412 | else { | |
413 | const char *colon; | |
414 | ||
415 | if ((colon = strchr(git_buf_cstr(&buf), ':')) == NULL) | |
416 | goto cleanup; | |
417 | ||
418 | git_buf_puts(msg, "On "); | |
419 | git_buf_put(msg, git_buf_cstr(&buf), colon - buf.ptr); | |
420 | git_buf_printf(msg, ": %s\n", user_message); | |
421 | } | |
422 | ||
6f58332f | 423 | error = (git_buf_oom(msg) || git_buf_oom(&buf)) ? -1 : 0; |
590fb68b | 424 | |
425 | cleanup: | |
426 | git_buf_free(&buf); | |
a6a82e1a | 427 | |
590fb68b | 428 | return error; |
429 | } | |
430 | ||
431 | static int update_reflog( | |
432 | git_oid *w_commit_oid, | |
433 | git_repository *repo, | |
590fb68b | 434 | const char *message) |
435 | { | |
b976f3c2 | 436 | git_reference *stash; |
590fb68b | 437 | int error; |
438 | ||
8d5ec910 CMN |
439 | if ((error = git_reference_ensure_log(repo, GIT_REFS_STASH_FILE)) < 0) |
440 | return error; | |
590fb68b | 441 | |
659cf202 | 442 | error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, w_commit_oid, 1, message); |
590fb68b | 443 | |
b976f3c2 | 444 | git_reference_free(stash); |
590fb68b | 445 | |
590fb68b | 446 | return error; |
447 | } | |
448 | ||
449 | static int is_dirty_cb(const char *path, unsigned int status, void *payload) | |
450 | { | |
451 | GIT_UNUSED(path); | |
452 | GIT_UNUSED(status); | |
453 | GIT_UNUSED(payload); | |
454 | ||
25e0b157 | 455 | return GIT_PASSTHROUGH; |
590fb68b | 456 | } |
457 | ||
458 | static int ensure_there_are_changes_to_stash( | |
459 | git_repository *repo, | |
460 | bool include_untracked_files, | |
461 | bool include_ignored_files) | |
462 | { | |
463 | int error; | |
79cfa20d | 464 | git_status_options opts = GIT_STATUS_OPTIONS_INIT; |
590fb68b | 465 | |
590fb68b | 466 | opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; |
4fe0b0b3 JSS |
467 | opts.flags = GIT_STATUS_OPT_EXCLUDE_SUBMODULES; |
468 | ||
590fb68b | 469 | if (include_untracked_files) |
4fe0b0b3 | 470 | opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | |
7b7aa75f | 471 | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; |
590fb68b | 472 | |
473 | if (include_ignored_files) | |
7b7aa75f JG |
474 | opts.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED | |
475 | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; | |
590fb68b | 476 | |
477 | error = git_status_foreach_ext(repo, &opts, is_dirty_cb, NULL); | |
478 | ||
25e0b157 | 479 | if (error == GIT_PASSTHROUGH) |
590fb68b | 480 | return 0; |
481 | ||
482 | if (!error) | |
909d5494 | 483 | return create_error(GIT_ENOTFOUND, "there is nothing to stash."); |
590fb68b | 484 | |
485 | return error; | |
486 | } | |
487 | ||
488 | static int reset_index_and_workdir( | |
489 | git_repository *repo, | |
490 | git_commit *commit, | |
4636ca93 JG |
491 | bool remove_untracked, |
492 | bool remove_ignored) | |
590fb68b | 493 | { |
6affd71f | 494 | git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; |
590fb68b | 495 | |
cf208031 | 496 | opts.checkout_strategy = GIT_CHECKOUT_FORCE; |
590fb68b | 497 | |
498 | if (remove_untracked) | |
499 | opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED; | |
500 | ||
4636ca93 JG |
501 | if (remove_ignored) |
502 | opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_IGNORED; | |
503 | ||
590fb68b | 504 | return git_checkout_tree(repo, (git_object *)commit, &opts); |
505 | } | |
506 | ||
507 | int git_stash_save( | |
508 | git_oid *out, | |
509 | git_repository *repo, | |
2274993b | 510 | const git_signature *stasher, |
590fb68b | 511 | const char *message, |
512 | uint32_t flags) | |
513 | { | |
514 | git_index *index = NULL; | |
515 | git_commit *b_commit = NULL, *i_commit = NULL, *u_commit = NULL; | |
516 | git_buf msg = GIT_BUF_INIT; | |
517 | int error; | |
518 | ||
519 | assert(out && repo && stasher); | |
520 | ||
a6a82e1a | 521 | if ((error = git_repository__ensure_not_bare(repo, "stash save")) < 0) |
590fb68b | 522 | return error; |
523 | ||
524 | if ((error = retrieve_base_commit_and_message(&b_commit, &msg, repo)) < 0) | |
525 | goto cleanup; | |
526 | ||
527 | if ((error = ensure_there_are_changes_to_stash( | |
528 | repo, | |
a6a82e1a RB |
529 | (flags & GIT_STASH_INCLUDE_UNTRACKED) != 0, |
530 | (flags & GIT_STASH_INCLUDE_IGNORED) != 0)) < 0) | |
590fb68b | 531 | goto cleanup; |
532 | ||
a6a82e1a | 533 | if ((error = git_repository_index(&index, repo)) < 0) |
590fb68b | 534 | goto cleanup; |
535 | ||
a6a82e1a RB |
536 | if ((error = commit_index( |
537 | &i_commit, index, stasher, git_buf_cstr(&msg), b_commit)) < 0) | |
590fb68b | 538 | goto cleanup; |
539 | ||
a6a82e1a RB |
540 | if ((flags & (GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)) && |
541 | (error = commit_untracked( | |
542 | &u_commit, index, stasher, git_buf_cstr(&msg), | |
543 | i_commit, flags)) < 0) | |
590fb68b | 544 | goto cleanup; |
545 | ||
a6a82e1a | 546 | if ((error = prepare_worktree_commit_message(&msg, message)) < 0) |
590fb68b | 547 | goto cleanup; |
548 | ||
a6a82e1a RB |
549 | if ((error = commit_worktree( |
550 | out, index, stasher, git_buf_cstr(&msg), | |
551 | i_commit, b_commit, u_commit)) < 0) | |
590fb68b | 552 | goto cleanup; |
553 | ||
554 | git_buf_rtrim(&msg); | |
a6a82e1a | 555 | |
659cf202 | 556 | if ((error = update_reflog(out, repo, git_buf_cstr(&msg))) < 0) |
590fb68b | 557 | goto cleanup; |
558 | ||
a6a82e1a | 559 | if ((error = reset_index_and_workdir( |
590fb68b | 560 | repo, |
a6a82e1a | 561 | ((flags & GIT_STASH_KEEP_INDEX) != 0) ? i_commit : b_commit, |
4636ca93 JG |
562 | (flags & GIT_STASH_INCLUDE_UNTRACKED) != 0, |
563 | (flags & GIT_STASH_INCLUDE_IGNORED) != 0)) < 0) | |
590fb68b | 564 | goto cleanup; |
565 | ||
590fb68b | 566 | cleanup: |
a6a82e1a | 567 | |
590fb68b | 568 | git_buf_free(&msg); |
569 | git_commit_free(i_commit); | |
570 | git_commit_free(b_commit); | |
571 | git_commit_free(u_commit); | |
572 | git_index_free(index); | |
a6a82e1a | 573 | |
590fb68b | 574 | return error; |
575 | } | |
23388413 | 576 | |
bf8dd3f5 POL |
577 | static int retrieve_stash_commit( |
578 | git_commit **commit, | |
579 | git_repository *repo, | |
580 | size_t index) | |
581 | { | |
582 | git_reference *stash = NULL; | |
583 | git_reflog *reflog = NULL; | |
584 | int error; | |
585 | size_t max; | |
586 | const git_reflog_entry *entry; | |
587 | ||
588 | if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) | |
589 | goto cleanup; | |
590 | ||
591 | if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) | |
592 | goto cleanup; | |
593 | ||
594 | max = git_reflog_entrycount(reflog); | |
90f8408d | 595 | if (!max || index > max - 1) { |
bf8dd3f5 | 596 | error = GIT_ENOTFOUND; |
909d5494 | 597 | giterr_set(GITERR_STASH, "no stashed state at position %" PRIuZ, index); |
bf8dd3f5 POL |
598 | goto cleanup; |
599 | } | |
600 | ||
601 | entry = git_reflog_entry_byindex(reflog, index); | |
602 | if ((error = git_commit_lookup(commit, repo, git_reflog_entry_id_new(entry))) < 0) | |
603 | goto cleanup; | |
604 | ||
605 | cleanup: | |
606 | git_reference_free(stash); | |
607 | git_reflog_free(reflog); | |
608 | return error; | |
609 | } | |
610 | ||
611 | static int retrieve_stash_trees( | |
612 | git_tree **out_stash_tree, | |
613 | git_tree **out_base_tree, | |
614 | git_tree **out_index_tree, | |
615 | git_tree **out_index_parent_tree, | |
616 | git_tree **out_untracked_tree, | |
617 | git_commit *stash_commit) | |
618 | { | |
619 | git_tree *stash_tree = NULL; | |
620 | git_commit *base_commit = NULL; | |
621 | git_tree *base_tree = NULL; | |
622 | git_commit *index_commit = NULL; | |
623 | git_tree *index_tree = NULL; | |
624 | git_commit *index_parent_commit = NULL; | |
625 | git_tree *index_parent_tree = NULL; | |
626 | git_commit *untracked_commit = NULL; | |
627 | git_tree *untracked_tree = NULL; | |
628 | int error; | |
629 | ||
630 | if ((error = git_commit_tree(&stash_tree, stash_commit)) < 0) | |
631 | goto cleanup; | |
632 | ||
633 | if ((error = git_commit_parent(&base_commit, stash_commit, 0)) < 0) | |
634 | goto cleanup; | |
635 | if ((error = git_commit_tree(&base_tree, base_commit)) < 0) | |
636 | goto cleanup; | |
637 | ||
638 | if ((error = git_commit_parent(&index_commit, stash_commit, 1)) < 0) | |
639 | goto cleanup; | |
640 | if ((error = git_commit_tree(&index_tree, index_commit)) < 0) | |
641 | goto cleanup; | |
642 | ||
643 | if ((error = git_commit_parent(&index_parent_commit, index_commit, 0)) < 0) | |
644 | goto cleanup; | |
645 | if ((error = git_commit_tree(&index_parent_tree, index_parent_commit)) < 0) | |
646 | goto cleanup; | |
647 | ||
648 | if (git_commit_parentcount(stash_commit) == 3) { | |
649 | if ((error = git_commit_parent(&untracked_commit, stash_commit, 2)) < 0) | |
650 | goto cleanup; | |
651 | if ((error = git_commit_tree(&untracked_tree, untracked_commit)) < 0) | |
652 | goto cleanup; | |
653 | } | |
654 | ||
655 | *out_stash_tree = stash_tree; | |
656 | *out_base_tree = base_tree; | |
657 | *out_index_tree = index_tree; | |
658 | *out_index_parent_tree = index_parent_tree; | |
659 | *out_untracked_tree = untracked_tree; | |
660 | ||
661 | cleanup: | |
662 | git_commit_free(untracked_commit); | |
663 | git_commit_free(index_parent_commit); | |
664 | git_commit_free(index_commit); | |
665 | git_commit_free(base_commit); | |
666 | if (error < 0) { | |
667 | git_tree_free(stash_tree); | |
668 | git_tree_free(base_tree); | |
669 | git_tree_free(index_tree); | |
670 | git_tree_free(index_parent_tree); | |
671 | git_tree_free(untracked_tree); | |
672 | } | |
673 | return error; | |
674 | } | |
675 | ||
b7f5cb8d ET |
676 | static int merge_indexes( |
677 | git_index **out, | |
678 | git_repository *repo, | |
679 | git_tree *ancestor_tree, | |
680 | git_index *ours_index, | |
681 | git_index *theirs_index) | |
682 | { | |
683 | git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL; | |
ed1c6446 | 684 | git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; |
b7f5cb8d ET |
685 | int error; |
686 | ||
ed1c6446 ET |
687 | iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; |
688 | ||
689 | if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, &iter_opts)) < 0 || | |
3679ebae AS |
690 | (error = git_iterator_for_index(&ours, repo, ours_index, &iter_opts)) < 0 || |
691 | (error = git_iterator_for_index(&theirs, repo, theirs_index, &iter_opts)) < 0) | |
b7f5cb8d ET |
692 | goto done; |
693 | ||
694 | error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL); | |
695 | ||
696 | done: | |
697 | git_iterator_free(ancestor); | |
698 | git_iterator_free(ours); | |
699 | git_iterator_free(theirs); | |
700 | return error; | |
701 | } | |
702 | ||
f0957589 ET |
703 | static int merge_index_and_tree( |
704 | git_index **out, | |
bf8dd3f5 | 705 | git_repository *repo, |
f0957589 ET |
706 | git_tree *ancestor_tree, |
707 | git_index *ours_index, | |
708 | git_tree *theirs_tree) | |
bf8dd3f5 | 709 | { |
f0957589 | 710 | git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL; |
ed1c6446 | 711 | git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; |
bf8dd3f5 | 712 | int error; |
bf8dd3f5 | 713 | |
ed1c6446 ET |
714 | iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; |
715 | ||
716 | if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, &iter_opts)) < 0 || | |
3679ebae | 717 | (error = git_iterator_for_index(&ours, repo, ours_index, &iter_opts)) < 0 || |
ed1c6446 | 718 | (error = git_iterator_for_tree(&theirs, theirs_tree, &iter_opts)) < 0) |
f0957589 | 719 | goto done; |
bf8dd3f5 | 720 | |
f0957589 | 721 | error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL); |
bf8dd3f5 | 722 | |
f0957589 ET |
723 | done: |
724 | git_iterator_free(ancestor); | |
725 | git_iterator_free(ours); | |
726 | git_iterator_free(theirs); | |
bf8dd3f5 POL |
727 | return error; |
728 | } | |
729 | ||
19c80a6f ET |
730 | static void normalize_apply_options( |
731 | git_stash_apply_options *opts, | |
732 | const git_stash_apply_options *given_apply_opts) | |
3679ebae | 733 | { |
19c80a6f ET |
734 | if (given_apply_opts != NULL) { |
735 | memcpy(opts, given_apply_opts, sizeof(git_stash_apply_options)); | |
bf8dd3f5 | 736 | } else { |
19c80a6f ET |
737 | git_stash_apply_options default_apply_opts = GIT_STASH_APPLY_OPTIONS_INIT; |
738 | memcpy(opts, &default_apply_opts, sizeof(git_stash_apply_options)); | |
bf8dd3f5 POL |
739 | } |
740 | ||
19c80a6f ET |
741 | if ((opts->checkout_options.checkout_strategy & (GIT_CHECKOUT_SAFE | GIT_CHECKOUT_FORCE)) == 0) |
742 | opts->checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE; | |
12149a20 | 743 | |
19c80a6f ET |
744 | if (!opts->checkout_options.our_label) |
745 | opts->checkout_options.our_label = "Updated upstream"; | |
f0957589 | 746 | |
19c80a6f ET |
747 | if (!opts->checkout_options.their_label) |
748 | opts->checkout_options.their_label = "Stashed changes"; | |
749 | } | |
750 | ||
751 | int git_stash_apply_init_options(git_stash_apply_options *opts, unsigned int version) | |
752 | { | |
753 | GIT_INIT_STRUCTURE_FROM_TEMPLATE( | |
754 | opts, version, git_stash_apply_options, GIT_STASH_APPLY_OPTIONS_INIT); | |
755 | return 0; | |
bf8dd3f5 POL |
756 | } |
757 | ||
3e529e9d CMN |
758 | #define NOTIFY_PROGRESS(opts, progress_type) \ |
759 | do { \ | |
760 | if ((opts).progress_cb && \ | |
761 | (error = (opts).progress_cb((progress_type), (opts).progress_payload))) { \ | |
762 | error = (error < 0) ? error : -1; \ | |
763 | goto cleanup; \ | |
764 | } \ | |
765 | } while(false); | |
4ea3eebf | 766 | |
82b1c93d ET |
767 | static int ensure_clean_index(git_repository *repo, git_index *index) |
768 | { | |
769 | git_tree *head_tree = NULL; | |
770 | git_diff *index_diff = NULL; | |
771 | int error = 0; | |
772 | ||
773 | if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || | |
774 | (error = git_diff_tree_to_index( | |
775 | &index_diff, repo, head_tree, index, NULL)) < 0) | |
776 | goto done; | |
777 | ||
778 | if (git_diff_num_deltas(index_diff) > 0) { | |
768f8be3 | 779 | giterr_set(GITERR_STASH, "%" PRIuZ " uncommitted changes exist in the index", |
82b1c93d ET |
780 | git_diff_num_deltas(index_diff)); |
781 | error = GIT_EUNCOMMITTED; | |
782 | } | |
783 | ||
784 | done: | |
785 | git_diff_free(index_diff); | |
786 | git_tree_free(head_tree); | |
787 | return error; | |
788 | } | |
789 | ||
b7f5cb8d ET |
790 | static int stage_new_file(const git_index_entry **entries, void *data) |
791 | { | |
792 | git_index *index = data; | |
793 | ||
794 | if(entries[0] == NULL) | |
795 | return git_index_add(index, entries[1]); | |
796 | else | |
797 | return git_index_add(index, entries[0]); | |
798 | } | |
799 | ||
800 | static int stage_new_files( | |
801 | git_index **out, | |
b7f5cb8d ET |
802 | git_tree *parent_tree, |
803 | git_tree *tree) | |
804 | { | |
805 | git_iterator *iterators[2] = { NULL, NULL }; | |
ed1c6446 | 806 | git_iterator_options iterator_options = GIT_ITERATOR_OPTIONS_INIT; |
b7f5cb8d ET |
807 | git_index *index = NULL; |
808 | int error; | |
809 | ||
810 | if ((error = git_index_new(&index)) < 0 || | |
ed1c6446 ET |
811 | (error = git_iterator_for_tree( |
812 | &iterators[0], parent_tree, &iterator_options)) < 0 || | |
813 | (error = git_iterator_for_tree( | |
814 | &iterators[1], tree, &iterator_options)) < 0) | |
b7f5cb8d ET |
815 | goto done; |
816 | ||
817 | error = git_iterator_walk(iterators, 2, stage_new_file, index); | |
818 | ||
819 | done: | |
820 | if (error < 0) | |
821 | git_index_free(index); | |
822 | else | |
823 | *out = index; | |
824 | ||
825 | git_iterator_free(iterators[0]); | |
826 | git_iterator_free(iterators[1]); | |
827 | ||
828 | return error; | |
829 | } | |
830 | ||
bf8dd3f5 POL |
831 | int git_stash_apply( |
832 | git_repository *repo, | |
833 | size_t index, | |
19c80a6f | 834 | const git_stash_apply_options *given_opts) |
bf8dd3f5 | 835 | { |
19c80a6f | 836 | git_stash_apply_options opts; |
f0957589 | 837 | unsigned int checkout_strategy; |
bf8dd3f5 POL |
838 | git_commit *stash_commit = NULL; |
839 | git_tree *stash_tree = NULL; | |
f0957589 | 840 | git_tree *stash_parent_tree = NULL; |
bf8dd3f5 POL |
841 | git_tree *index_tree = NULL; |
842 | git_tree *index_parent_tree = NULL; | |
843 | git_tree *untracked_tree = NULL; | |
b7f5cb8d | 844 | git_index *stash_adds = NULL; |
bf8dd3f5 | 845 | git_index *repo_index = NULL; |
f0957589 ET |
846 | git_index *unstashed_index = NULL; |
847 | git_index *modified_index = NULL; | |
848 | git_index *untracked_index = NULL; | |
bf8dd3f5 POL |
849 | int error; |
850 | ||
19c80a6f ET |
851 | GITERR_CHECK_VERSION(given_opts, GIT_STASH_APPLY_OPTIONS_VERSION, "git_stash_apply_options"); |
852 | ||
853 | normalize_apply_options(&opts, given_opts); | |
854 | checkout_strategy = opts.checkout_options.checkout_strategy; | |
f0957589 | 855 | |
4ea3eebf ET |
856 | NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_LOADING_STASH); |
857 | ||
bf8dd3f5 POL |
858 | /* Retrieve commit corresponding to the given stash */ |
859 | if ((error = retrieve_stash_commit(&stash_commit, repo, index)) < 0) | |
860 | goto cleanup; | |
861 | ||
862 | /* Retrieve all trees in the stash */ | |
863 | if ((error = retrieve_stash_trees( | |
f0957589 | 864 | &stash_tree, &stash_parent_tree, &index_tree, |
bf8dd3f5 POL |
865 | &index_parent_tree, &untracked_tree, stash_commit)) < 0) |
866 | goto cleanup; | |
867 | ||
868 | /* Load repo index */ | |
869 | if ((error = git_repository_index(&repo_index, repo)) < 0) | |
870 | goto cleanup; | |
871 | ||
4ea3eebf ET |
872 | NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX); |
873 | ||
82b1c93d ET |
874 | if ((error = ensure_clean_index(repo, repo_index)) < 0) |
875 | goto cleanup; | |
876 | ||
bf8dd3f5 | 877 | /* Restore index if required */ |
19c80a6f | 878 | if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) && |
f0957589 | 879 | git_oid_cmp(git_tree_id(stash_parent_tree), git_tree_id(index_tree))) { |
bf8dd3f5 | 880 | |
f0957589 ET |
881 | if ((error = merge_index_and_tree( |
882 | &unstashed_index, repo, index_parent_tree, repo_index, index_tree)) < 0) | |
bf8dd3f5 | 883 | goto cleanup; |
bf8dd3f5 | 884 | |
f0957589 | 885 | if (git_index_has_conflicts(unstashed_index)) { |
885b94aa | 886 | error = GIT_ECONFLICT; |
bf8dd3f5 | 887 | goto cleanup; |
f0957589 | 888 | } |
b7f5cb8d ET |
889 | |
890 | /* Otherwise, stage any new files in the stash tree. (Note: their | |
891 | * previously unstaged contents are staged, not the previously staged.) | |
892 | */ | |
893 | } else if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) == 0) { | |
894 | if ((error = stage_new_files( | |
149d5d8a | 895 | &stash_adds, stash_parent_tree, stash_tree)) < 0 || |
b7f5cb8d ET |
896 | (error = merge_indexes( |
897 | &unstashed_index, repo, stash_parent_tree, repo_index, stash_adds)) < 0) | |
898 | goto cleanup; | |
bf8dd3f5 POL |
899 | } |
900 | ||
4ea3eebf ET |
901 | NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED); |
902 | ||
bf8dd3f5 | 903 | /* Restore modified files in workdir */ |
f0957589 ET |
904 | if ((error = merge_index_and_tree( |
905 | &modified_index, repo, stash_parent_tree, repo_index, stash_tree)) < 0) | |
bf8dd3f5 POL |
906 | goto cleanup; |
907 | ||
f0957589 | 908 | /* If applicable, restore untracked / ignored files in workdir */ |
4ea3eebf ET |
909 | if (untracked_tree) { |
910 | NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED); | |
911 | ||
912 | if ((error = merge_index_and_tree(&untracked_index, repo, NULL, repo_index, untracked_tree)) < 0) | |
913 | goto cleanup; | |
914 | } | |
bf8dd3f5 | 915 | |
f0957589 | 916 | if (untracked_index) { |
19c80a6f | 917 | opts.checkout_options.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX; |
f0957589 | 918 | |
4ea3eebf ET |
919 | NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED); |
920 | ||
19c80a6f | 921 | if ((error = git_checkout_index(repo, untracked_index, &opts.checkout_options)) < 0) |
f0957589 ET |
922 | goto cleanup; |
923 | ||
19c80a6f | 924 | opts.checkout_options.checkout_strategy = checkout_strategy; |
f0957589 ET |
925 | } |
926 | ||
927 | ||
928 | /* If there are conflicts in the modified index, then we need to actually | |
929 | * check that out as the repo's index. Otherwise, we don't update the | |
930 | * index. | |
931 | */ | |
932 | ||
933 | if (!git_index_has_conflicts(modified_index)) | |
19c80a6f | 934 | opts.checkout_options.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX; |
f0957589 ET |
935 | |
936 | /* Check out the modified index using the existing repo index as baseline, | |
937 | * so that existing modifications in the index can be rewritten even when | |
938 | * checking out safely. | |
939 | */ | |
19c80a6f | 940 | opts.checkout_options.baseline_index = repo_index; |
f0957589 | 941 | |
4ea3eebf ET |
942 | NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED); |
943 | ||
19c80a6f | 944 | if ((error = git_checkout_index(repo, modified_index, &opts.checkout_options)) < 0) |
bf8dd3f5 POL |
945 | goto cleanup; |
946 | ||
f0957589 ET |
947 | if (unstashed_index && !git_index_has_conflicts(modified_index)) { |
948 | if ((error = git_index_read_index(repo_index, unstashed_index)) < 0) | |
949 | goto cleanup; | |
950 | } | |
951 | ||
4ea3eebf ET |
952 | NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_DONE); |
953 | ||
e35b947b POL |
954 | error = git_index_write(repo_index); |
955 | ||
bf8dd3f5 | 956 | cleanup: |
f0957589 ET |
957 | git_index_free(untracked_index); |
958 | git_index_free(modified_index); | |
959 | git_index_free(unstashed_index); | |
b7f5cb8d | 960 | git_index_free(stash_adds); |
bf8dd3f5 POL |
961 | git_index_free(repo_index); |
962 | git_tree_free(untracked_tree); | |
963 | git_tree_free(index_parent_tree); | |
964 | git_tree_free(index_tree); | |
f0957589 | 965 | git_tree_free(stash_parent_tree); |
bf8dd3f5 POL |
966 | git_tree_free(stash_tree); |
967 | git_commit_free(stash_commit); | |
968 | return error; | |
969 | } | |
970 | ||
23388413 | 971 | int git_stash_foreach( |
972 | git_repository *repo, | |
1d8ec670 | 973 | git_stash_cb callback, |
23388413 | 974 | void *payload) |
975 | { | |
976 | git_reference *stash; | |
977 | git_reflog *reflog = NULL; | |
978 | int error; | |
979 | size_t i, max; | |
980 | const git_reflog_entry *entry; | |
981 | ||
982 | error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE); | |
52c52737 RB |
983 | if (error == GIT_ENOTFOUND) { |
984 | giterr_clear(); | |
23388413 | 985 | return 0; |
52c52737 | 986 | } |
23388413 | 987 | if (error < 0) |
988 | goto cleanup; | |
989 | ||
b976f3c2 | 990 | if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) |
23388413 | 991 | goto cleanup; |
992 | ||
993 | max = git_reflog_entrycount(reflog); | |
994 | for (i = 0; i < max; i++) { | |
b15df1d9 | 995 | entry = git_reflog_entry_byindex(reflog, i); |
a6a82e1a | 996 | |
c7b3e1b3 RB |
997 | error = callback(i, |
998 | git_reflog_entry_message(entry), | |
999 | git_reflog_entry_id_new(entry), | |
1000 | payload); | |
1001 | ||
1002 | if (error) { | |
26c1cb91 | 1003 | giterr_set_after_callback(error); |
dab89f9b | 1004 | break; |
c7b3e1b3 | 1005 | } |
23388413 | 1006 | } |
1007 | ||
23388413 | 1008 | cleanup: |
1009 | git_reference_free(stash); | |
1010 | git_reflog_free(reflog); | |
1011 | return error; | |
1012 | } | |
e4c64cf2 | 1013 | |
1014 | int git_stash_drop( | |
1015 | git_repository *repo, | |
1016 | size_t index) | |
1017 | { | |
f99ca523 CMN |
1018 | git_transaction *tx; |
1019 | git_reference *stash = NULL; | |
e4c64cf2 | 1020 | git_reflog *reflog = NULL; |
1021 | size_t max; | |
1022 | int error; | |
1023 | ||
f99ca523 | 1024 | if ((error = git_transaction_new(&tx, repo)) < 0) |
e4c64cf2 | 1025 | return error; |
1026 | ||
c327d5db | 1027 | if ((error = git_transaction_lock_ref(tx, GIT_REFS_STASH_FILE)) < 0) |
f99ca523 CMN |
1028 | goto cleanup; |
1029 | ||
1030 | if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) | |
1031 | goto cleanup; | |
1032 | ||
b976f3c2 | 1033 | if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) |
e4c64cf2 | 1034 | goto cleanup; |
1035 | ||
1036 | max = git_reflog_entrycount(reflog); | |
1037 | ||
90f8408d | 1038 | if (!max || index > max - 1) { |
e4c64cf2 | 1039 | error = GIT_ENOTFOUND; |
909d5494 | 1040 | giterr_set(GITERR_STASH, "no stashed state at position %" PRIuZ, index); |
e4c64cf2 | 1041 | goto cleanup; |
1042 | } | |
1043 | ||
b15df1d9 | 1044 | if ((error = git_reflog_drop(reflog, index, true)) < 0) |
e4c64cf2 | 1045 | goto cleanup; |
1046 | ||
f99ca523 | 1047 | if ((error = git_transaction_set_reflog(tx, GIT_REFS_STASH_FILE, reflog)) < 0) |
e4c64cf2 | 1048 | goto cleanup; |
1049 | ||
1050 | if (max == 1) { | |
f99ca523 CMN |
1051 | if ((error = git_transaction_remove(tx, GIT_REFS_STASH_FILE)) < 0) |
1052 | goto cleanup; | |
9ccab8df | 1053 | } else if (index == 0) { |
1054 | const git_reflog_entry *entry; | |
1055 | ||
1056 | entry = git_reflog_entry_byindex(reflog, 0); | |
f99ca523 | 1057 | if ((error = git_transaction_set_target(tx, GIT_REFS_STASH_FILE, &entry->oid_cur, NULL, NULL)) < 0) |
a57dd3b7 | 1058 | goto cleanup; |
e4c64cf2 | 1059 | } |
1060 | ||
f99ca523 CMN |
1061 | error = git_transaction_commit(tx); |
1062 | ||
e4c64cf2 | 1063 | cleanup: |
1064 | git_reference_free(stash); | |
f99ca523 | 1065 | git_transaction_free(tx); |
e4c64cf2 | 1066 | git_reflog_free(reflog); |
1067 | return error; | |
1068 | } | |
bf8dd3f5 POL |
1069 | |
1070 | int git_stash_pop( | |
1071 | git_repository *repo, | |
1072 | size_t index, | |
19c80a6f | 1073 | const git_stash_apply_options *options) |
bf8dd3f5 POL |
1074 | { |
1075 | int error; | |
1076 | ||
19c80a6f | 1077 | if ((error = git_stash_apply(repo, index, options)) < 0) |
bf8dd3f5 POL |
1078 | return error; |
1079 | ||
1080 | return git_stash_drop(repo, index); | |
1081 | } |