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