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