]> git.proxmox.com Git - libgit2.git/blame - src/stash.c
reflog: write to the reflog following git's rules
[libgit2.git] / src / stash.c
CommitLineData
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"
11#include "tree.h"
12#include "reflog.h"
13#include "git2/diff.h"
14#include "git2/stash.h"
15#include "git2/status.h"
16#include "git2/checkout.h"
114f5a6c 17#include "git2/index.h"
4ec197f3 18#include "signature.h"
590fb68b 19
20static int create_error(int error, const char *msg)
21{
22 giterr_set(GITERR_STASH, "Cannot stash changes - %s", msg);
23 return error;
24}
25
590fb68b 26static int retrieve_head(git_reference **out, git_repository *repo)
27{
28 int error = git_repository_head(out, repo);
29
605da51a 30 if (error == GIT_EUNBORNBRANCH)
590fb68b 31 return create_error(error, "You do not have the initial commit yet.");
32
33 return error;
34}
35
36static int append_abbreviated_oid(git_buf *out, const git_oid *b_commit)
37{
38 char *formatted_oid;
39
40 formatted_oid = git_oid_allocfmt(b_commit);
41 GITERR_CHECK_ALLOC(formatted_oid);
42
43 git_buf_put(out, formatted_oid, 7);
44 git__free(formatted_oid);
45
46 return git_buf_oom(out) ? -1 : 0;
47}
48
49static int append_commit_description(git_buf *out, git_commit* commit)
50{
51 const char *message;
a8122b5d 52 size_t pos = 0, len;
590fb68b 53
54 if (append_abbreviated_oid(out, git_commit_id(commit)) < 0)
55 return -1;
56
57 message = git_commit_message(commit);
58 len = strlen(message);
59
60 /* TODO: Replace with proper commit short message
61 * when git_commit_message_short() is implemented.
62 */
63 while (pos < len && message[pos] != '\n')
64 pos++;
65
66 git_buf_putc(out, ' ');
67 git_buf_put(out, message, pos);
68 git_buf_putc(out, '\n');
69
70 return git_buf_oom(out) ? -1 : 0;
71}
72
73static int retrieve_base_commit_and_message(
74 git_commit **b_commit,
75 git_buf *stash_message,
76 git_repository *repo)
77{
78 git_reference *head = NULL;
79 int error;
80
81 if ((error = retrieve_head(&head, repo)) < 0)
82 return error;
83
590fb68b 84 if (strcmp("HEAD", git_reference_name(head)) == 0)
a6a82e1a 85 error = git_buf_puts(stash_message, "(no branch): ");
590fb68b 86 else
a6a82e1a 87 error = git_buf_printf(
590fb68b 88 stash_message,
89 "%s: ",
90 git_reference_name(head) + strlen(GIT_REFS_HEADS_DIR));
a6a82e1a 91 if (error < 0)
590fb68b 92 goto cleanup;
93
a6a82e1a
RB
94 if ((error = git_commit_lookup(
95 b_commit, repo, git_reference_target(head))) < 0)
590fb68b 96 goto cleanup;
97
a6a82e1a
RB
98 if ((error = append_commit_description(stash_message, *b_commit)) < 0)
99 goto cleanup;
590fb68b 100
101cleanup:
102 git_reference_free(head);
103 return error;
104}
105
106static int build_tree_from_index(git_tree **out, git_index *index)
107{
a6a82e1a 108 int error;
590fb68b 109 git_oid i_tree_oid;
110
a6a82e1a 111 if ((error = git_index_write_tree(&i_tree_oid, index)) < 0)
590fb68b 112 return -1;
113
114 return git_tree_lookup(out, git_index_owner(index), &i_tree_oid);
115}
116
117static int commit_index(
118 git_commit **i_commit,
119 git_index *index,
2274993b 120 const git_signature *stasher,
590fb68b 121 const char *message,
122 const git_commit *parent)
123{
124 git_tree *i_tree = NULL;
125 git_oid i_commit_oid;
126 git_buf msg = GIT_BUF_INIT;
a6a82e1a 127 int error;
590fb68b 128
a6a82e1a 129 if ((error = build_tree_from_index(&i_tree, index)) < 0)
590fb68b 130 goto cleanup;
131
a6a82e1a 132 if ((error = git_buf_printf(&msg, "index on %s\n", message)) < 0)
590fb68b 133 goto cleanup;
134
a6a82e1a 135 if ((error = git_commit_create(
590fb68b 136 &i_commit_oid,
137 git_index_owner(index),
138 NULL,
139 stasher,
140 stasher,
141 NULL,
142 git_buf_cstr(&msg),
143 i_tree,
144 1,
a6a82e1a
RB
145 &parent)) < 0)
146 goto cleanup;
590fb68b 147
148 error = git_commit_lookup(i_commit, git_index_owner(index), &i_commit_oid);
149
150cleanup:
151 git_tree_free(i_tree);
152 git_buf_free(&msg);
153 return error;
154}
155
10672e3e 156struct stash_update_rules {
590fb68b 157 bool include_changed;
158 bool include_untracked;
159 bool include_ignored;
160};
161
10672e3e
RB
162static int stash_update_index_from_diff(
163 git_index *index,
164 const git_diff *diff,
165 struct stash_update_rules *data)
590fb68b 166{
10672e3e
RB
167 int error = 0;
168 size_t d, max_d = git_diff_num_deltas(diff);
169
170 for (d = 0; !error && d < max_d; ++d) {
171 const char *add_path = NULL;
172 const git_diff_delta *delta = git_diff_get_delta(diff, d);
173
174 switch (delta->status) {
175 case GIT_DELTA_IGNORED:
176 if (data->include_ignored)
177 add_path = delta->new_file.path;
178 break;
179
180 case GIT_DELTA_UNTRACKED:
181 if (data->include_untracked)
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,
201 "Cannot update index. Unimplemented status (%d)",
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
213static 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) {
234 opts.flags |= GIT_DIFF_INCLUDE_IGNORED;
235 data.include_ignored = true;
236 }
237
a6a82e1a 238 if ((error = git_commit_tree(&i_tree, i_commit)) < 0)
590fb68b 239 goto cleanup;
240
a6a82e1a
RB
241 if ((error = git_diff_tree_to_workdir(
242 &diff, git_index_owner(index), i_tree, &opts)) < 0)
590fb68b 243 goto cleanup;
244
10672e3e 245 if ((error = stash_update_index_from_diff(index, diff, &data)) < 0)
590fb68b 246 goto cleanup;
247
a6a82e1a 248 error = build_tree_from_index(tree_out, index);
590fb68b 249
250cleanup:
3ff1d123 251 git_diff_free(diff);
590fb68b 252 git_tree_free(i_tree);
253 return error;
254}
255
256static int commit_untracked(
257 git_commit **u_commit,
258 git_index *index,
2274993b 259 const git_signature *stasher,
590fb68b 260 const char *message,
261 git_commit *i_commit,
262 uint32_t flags)
263{
264 git_tree *u_tree = NULL;
265 git_oid u_commit_oid;
266 git_buf msg = GIT_BUF_INIT;
a6a82e1a 267 int error;
590fb68b 268
a6a82e1a 269 if ((error = build_untracked_tree(&u_tree, index, i_commit, flags)) < 0)
590fb68b 270 goto cleanup;
271
a6a82e1a 272 if ((error = git_buf_printf(&msg, "untracked files on %s\n", message)) < 0)
590fb68b 273 goto cleanup;
274
a6a82e1a 275 if ((error = git_commit_create(
590fb68b 276 &u_commit_oid,
277 git_index_owner(index),
278 NULL,
279 stasher,
280 stasher,
281 NULL,
282 git_buf_cstr(&msg),
283 u_tree,
284 0,
a6a82e1a
RB
285 NULL)) < 0)
286 goto cleanup;
590fb68b 287
288 error = git_commit_lookup(u_commit, git_index_owner(index), &u_commit_oid);
289
290cleanup:
291 git_tree_free(u_tree);
292 git_buf_free(&msg);
293 return error;
294}
295
296static int build_workdir_tree(
297 git_tree **tree_out,
298 git_index *index,
299 git_commit *b_commit)
300{
5735bf5e 301 git_repository *repo = git_index_owner(index);
590fb68b 302 git_tree *b_tree = NULL;
10672e3e 303 git_diff *diff = NULL;
2f8d30be 304 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
10672e3e 305 struct stash_update_rules data = {0};
a6a82e1a 306 int error;
590fb68b 307
4fe0b0b3
JSS
308 opts.flags = GIT_DIFF_IGNORE_SUBMODULES;
309
a6a82e1a 310 if ((error = git_commit_tree(&b_tree, b_commit)) < 0)
590fb68b 311 goto cleanup;
312
10672e3e
RB
313 if ((error = git_diff_tree_to_workdir_with_index(
314 &diff, repo, b_tree, &opts)) < 0)
590fb68b 315 goto cleanup;
316
590fb68b 317 data.include_changed = true;
318
10672e3e 319 if ((error = stash_update_index_from_diff(index, diff, &data)) < 0)
590fb68b 320 goto cleanup;
321
10672e3e 322 error = build_tree_from_index(tree_out, index);
590fb68b 323
324cleanup:
3ff1d123 325 git_diff_free(diff);
590fb68b 326 git_tree_free(b_tree);
a6a82e1a 327
590fb68b 328 return error;
329}
330
331static int commit_worktree(
332 git_oid *w_commit_oid,
333 git_index *index,
2274993b 334 const git_signature *stasher,
590fb68b 335 const char *message,
336 git_commit *i_commit,
337 git_commit *b_commit,
338 git_commit *u_commit)
339{
a6a82e1a 340 int error = 0;
590fb68b 341 git_tree *w_tree = NULL, *i_tree = NULL;
590fb68b 342 const git_commit *parents[] = { NULL, NULL, NULL };
343
344 parents[0] = b_commit;
345 parents[1] = i_commit;
346 parents[2] = u_commit;
347
a6a82e1a
RB
348 if ((error = git_commit_tree(&i_tree, i_commit)) < 0)
349 goto cleanup;
590fb68b 350
a6a82e1a 351 if ((error = git_index_read_tree(index, i_tree)) < 0)
590fb68b 352 goto cleanup;
353
a6a82e1a 354 if ((error = build_workdir_tree(&w_tree, index, b_commit)) < 0)
590fb68b 355 goto cleanup;
356
a6a82e1a 357 error = git_commit_create(
590fb68b 358 w_commit_oid,
359 git_index_owner(index),
360 NULL,
361 stasher,
362 stasher,
363 NULL,
364 message,
365 w_tree,
a6a82e1a
RB
366 u_commit ? 3 : 2,
367 parents);
590fb68b 368
369cleanup:
370 git_tree_free(i_tree);
371 git_tree_free(w_tree);
372 return error;
373}
374
375static int prepare_worktree_commit_message(
376 git_buf* msg,
377 const char *user_message)
378{
379 git_buf buf = GIT_BUF_INIT;
a6a82e1a
RB
380 int error;
381
6f58332f
RB
382 if ((error = git_buf_set(&buf, git_buf_cstr(msg), git_buf_len(msg))) < 0)
383 return error;
590fb68b 384
590fb68b 385 git_buf_clear(msg);
386
387 if (!user_message)
388 git_buf_printf(msg, "WIP on %s", git_buf_cstr(&buf));
389 else {
390 const char *colon;
391
392 if ((colon = strchr(git_buf_cstr(&buf), ':')) == NULL)
393 goto cleanup;
394
395 git_buf_puts(msg, "On ");
396 git_buf_put(msg, git_buf_cstr(&buf), colon - buf.ptr);
397 git_buf_printf(msg, ": %s\n", user_message);
398 }
399
6f58332f 400 error = (git_buf_oom(msg) || git_buf_oom(&buf)) ? -1 : 0;
590fb68b 401
402cleanup:
403 git_buf_free(&buf);
a6a82e1a 404
590fb68b 405 return error;
406}
407
408static int update_reflog(
409 git_oid *w_commit_oid,
410 git_repository *repo,
2274993b 411 const git_signature *stasher,
590fb68b 412 const char *message)
413{
b976f3c2 414 git_reference *stash;
590fb68b 415 int error;
416
a57dd3b7 417 error = git_reference_create_with_log(&stash, repo, GIT_REFS_STASH_FILE, w_commit_oid, 1, stasher, message);
590fb68b 418
b976f3c2
CMN
419 git_reference_free(stash);
420
590fb68b 421 return error;
422}
423
424static 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
430 return 1;
431}
432
433static 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 |
590fb68b 446 GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
447
448 if (include_ignored_files)
4fe0b0b3 449 opts.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED;
590fb68b 450
451 error = git_status_foreach_ext(repo, &opts, is_dirty_cb, NULL);
452
453 if (error == GIT_EUSER)
454 return 0;
455
456 if (!error)
457 return create_error(GIT_ENOTFOUND, "There is nothing to stash.");
458
459 return error;
460}
461
462static int reset_index_and_workdir(
463 git_repository *repo,
464 git_commit *commit,
465 bool remove_untracked)
466{
b81aa2f1 467 git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
590fb68b 468
cf208031 469 opts.checkout_strategy = GIT_CHECKOUT_FORCE;
590fb68b 470
471 if (remove_untracked)
472 opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED;
473
474 return git_checkout_tree(repo, (git_object *)commit, &opts);
475}
476
477int git_stash_save(
478 git_oid *out,
479 git_repository *repo,
2274993b 480 const git_signature *stasher,
590fb68b 481 const char *message,
482 uint32_t flags)
483{
484 git_index *index = NULL;
485 git_commit *b_commit = NULL, *i_commit = NULL, *u_commit = NULL;
486 git_buf msg = GIT_BUF_INIT;
487 int error;
488
489 assert(out && repo && stasher);
490
a6a82e1a 491 if ((error = git_repository__ensure_not_bare(repo, "stash save")) < 0)
590fb68b 492 return error;
493
494 if ((error = retrieve_base_commit_and_message(&b_commit, &msg, repo)) < 0)
495 goto cleanup;
496
497 if ((error = ensure_there_are_changes_to_stash(
498 repo,
a6a82e1a
RB
499 (flags & GIT_STASH_INCLUDE_UNTRACKED) != 0,
500 (flags & GIT_STASH_INCLUDE_IGNORED) != 0)) < 0)
590fb68b 501 goto cleanup;
502
a6a82e1a 503 if ((error = git_repository_index(&index, repo)) < 0)
590fb68b 504 goto cleanup;
505
a6a82e1a
RB
506 if ((error = commit_index(
507 &i_commit, index, stasher, git_buf_cstr(&msg), b_commit)) < 0)
590fb68b 508 goto cleanup;
509
a6a82e1a
RB
510 if ((flags & (GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)) &&
511 (error = commit_untracked(
512 &u_commit, index, stasher, git_buf_cstr(&msg),
513 i_commit, flags)) < 0)
590fb68b 514 goto cleanup;
515
a6a82e1a 516 if ((error = prepare_worktree_commit_message(&msg, message)) < 0)
590fb68b 517 goto cleanup;
518
a6a82e1a
RB
519 if ((error = commit_worktree(
520 out, index, stasher, git_buf_cstr(&msg),
521 i_commit, b_commit, u_commit)) < 0)
590fb68b 522 goto cleanup;
523
524 git_buf_rtrim(&msg);
a6a82e1a
RB
525
526 if ((error = update_reflog(out, repo, stasher, git_buf_cstr(&msg))) < 0)
590fb68b 527 goto cleanup;
528
a6a82e1a 529 if ((error = reset_index_and_workdir(
590fb68b 530 repo,
a6a82e1a
RB
531 ((flags & GIT_STASH_KEEP_INDEX) != 0) ? i_commit : b_commit,
532 (flags & GIT_STASH_INCLUDE_UNTRACKED) != 0)) < 0)
590fb68b 533 goto cleanup;
534
590fb68b 535cleanup:
a6a82e1a 536
590fb68b 537 git_buf_free(&msg);
538 git_commit_free(i_commit);
539 git_commit_free(b_commit);
540 git_commit_free(u_commit);
541 git_index_free(index);
a6a82e1a 542
590fb68b 543 return error;
544}
23388413 545
546int git_stash_foreach(
547 git_repository *repo,
1d8ec670 548 git_stash_cb callback,
23388413 549 void *payload)
550{
551 git_reference *stash;
552 git_reflog *reflog = NULL;
553 int error;
554 size_t i, max;
555 const git_reflog_entry *entry;
556
557 error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE);
52c52737
RB
558 if (error == GIT_ENOTFOUND) {
559 giterr_clear();
23388413 560 return 0;
52c52737 561 }
23388413 562 if (error < 0)
563 goto cleanup;
564
b976f3c2 565 if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0)
23388413 566 goto cleanup;
567
568 max = git_reflog_entrycount(reflog);
569 for (i = 0; i < max; i++) {
b15df1d9 570 entry = git_reflog_entry_byindex(reflog, i);
a6a82e1a 571
23388413 572 if (callback(i,
2508cc66
BS
573 git_reflog_entry_message(entry),
574 git_reflog_entry_id_new(entry),
23388413 575 payload)) {
576 error = GIT_EUSER;
a6a82e1a 577 break;
23388413 578 }
579 }
580
23388413 581cleanup:
582 git_reference_free(stash);
583 git_reflog_free(reflog);
584 return error;
585}
e4c64cf2 586
587int git_stash_drop(
588 git_repository *repo,
589 size_t index)
590{
591 git_reference *stash;
592 git_reflog *reflog = NULL;
593 size_t max;
594 int error;
595
596 if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0)
597 return error;
598
b976f3c2 599 if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0)
e4c64cf2 600 goto cleanup;
601
602 max = git_reflog_entrycount(reflog);
603
604 if (index > max - 1) {
605 error = GIT_ENOTFOUND;
606 giterr_set(GITERR_STASH, "No stashed state at position %" PRIuZ, index);
607 goto cleanup;
608 }
609
b15df1d9 610 if ((error = git_reflog_drop(reflog, index, true)) < 0)
e4c64cf2 611 goto cleanup;
612
613 if ((error = git_reflog_write(reflog)) < 0)
614 goto cleanup;
615
616 if (max == 1) {
617 error = git_reference_delete(stash);
d00d5464 618 git_reference_free(stash);
e4c64cf2 619 stash = NULL;
9ccab8df 620 } else if (index == 0) {
621 const git_reflog_entry *entry;
622
623 entry = git_reflog_entry_byindex(reflog, 0);
52c52737 624
d00d5464 625 git_reference_free(stash);
a57dd3b7
CMN
626 if ((error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, &entry->oid_cur, 1) < 0))
627 goto cleanup;
628
629 /* We need to undo the writing that we just did */
630 error = git_reflog_write(reflog);
e4c64cf2 631 }
632
633cleanup:
634 git_reference_free(stash);
635 git_reflog_free(reflog);
636 return error;
637}