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