]> git.ipfire.org Git - thirdparty/git.git/blame - builtin/stash--helper.c
stash: convert branch to builtin
[thirdparty/git.git] / builtin / stash--helper.c
CommitLineData
8a0fc8d1
JT
1#include "builtin.h"
2#include "config.h"
3#include "parse-options.h"
4#include "refs.h"
5#include "lockfile.h"
6#include "cache-tree.h"
7#include "unpack-trees.h"
8#include "merge-recursive.h"
9#include "argv-array.h"
10#include "run-command.h"
11#include "dir.h"
12#include "rerere.h"
13
14static const char * const git_stash_helper_usage[] = {
4e2dd393 15 N_("git stash--helper drop [-q|--quiet] [<stash>]"),
8a0fc8d1 16 N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"),
577c1995 17 N_("git stash--helper branch <branchname> [<stash>]"),
4e2dd393
JT
18 N_("git stash--helper clear"),
19 NULL
20};
21
22static const char * const git_stash_helper_drop_usage[] = {
23 N_("git stash--helper drop [-q|--quiet] [<stash>]"),
8a0fc8d1
JT
24 NULL
25};
26
27static const char * const git_stash_helper_apply_usage[] = {
28 N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"),
29 NULL
30};
31
577c1995
JT
32static const char * const git_stash_helper_branch_usage[] = {
33 N_("git stash--helper branch <branchname> [<stash>]"),
34 NULL
35};
36
4e2dd393
JT
37static const char * const git_stash_helper_clear_usage[] = {
38 N_("git stash--helper clear"),
39 NULL
40};
41
8a0fc8d1
JT
42static const char *ref_stash = "refs/stash";
43static struct strbuf stash_index_path = STRBUF_INIT;
44
45/*
46 * w_commit is set to the commit containing the working tree
47 * b_commit is set to the base commit
48 * i_commit is set to the commit containing the index tree
49 * u_commit is set to the commit containing the untracked files tree
50 * w_tree is set to the working tree
51 * b_tree is set to the base tree
52 * i_tree is set to the index tree
53 * u_tree is set to the untracked files tree
54 */
55struct stash_info {
56 struct object_id w_commit;
57 struct object_id b_commit;
58 struct object_id i_commit;
59 struct object_id u_commit;
60 struct object_id w_tree;
61 struct object_id b_tree;
62 struct object_id i_tree;
63 struct object_id u_tree;
64 struct strbuf revision;
65 int is_stash_ref;
66 int has_u;
67};
68
69static void free_stash_info(struct stash_info *info)
70{
71 strbuf_release(&info->revision);
72}
73
74static void assert_stash_like(struct stash_info *info, const char *revision)
75{
76 if (get_oidf(&info->b_commit, "%s^1", revision) ||
77 get_oidf(&info->w_tree, "%s:", revision) ||
78 get_oidf(&info->b_tree, "%s^1:", revision) ||
79 get_oidf(&info->i_tree, "%s^2:", revision))
80 die(_("'%s' is not a stash-like commit"), revision);
81}
82
83static int get_stash_info(struct stash_info *info, int argc, const char **argv)
84{
85 int ret;
86 char *end_of_rev;
87 char *expanded_ref;
88 const char *revision;
89 const char *commit = NULL;
90 struct object_id dummy;
91 struct strbuf symbolic = STRBUF_INIT;
92
93 if (argc > 1) {
94 int i;
95 struct strbuf refs_msg = STRBUF_INIT;
96
97 for (i = 0; i < argc; i++)
98 strbuf_addf(&refs_msg, " '%s'", argv[i]);
99
100 fprintf_ln(stderr, _("Too many revisions specified:%s"),
101 refs_msg.buf);
102 strbuf_release(&refs_msg);
103
104 return -1;
105 }
106
107 if (argc == 1)
108 commit = argv[0];
109
110 strbuf_init(&info->revision, 0);
111 if (!commit) {
112 if (!ref_exists(ref_stash)) {
113 free_stash_info(info);
114 fprintf_ln(stderr, _("No stash entries found."));
115 return -1;
116 }
117
118 strbuf_addf(&info->revision, "%s@{0}", ref_stash);
119 } else if (strspn(commit, "0123456789") == strlen(commit)) {
120 strbuf_addf(&info->revision, "%s@{%s}", ref_stash, commit);
121 } else {
122 strbuf_addstr(&info->revision, commit);
123 }
124
125 revision = info->revision.buf;
126
127 if (get_oid(revision, &info->w_commit)) {
128 error(_("%s is not a valid reference"), revision);
129 free_stash_info(info);
130 return -1;
131 }
132
133 assert_stash_like(info, revision);
134
135 info->has_u = !get_oidf(&info->u_tree, "%s^3:", revision);
136
137 end_of_rev = strchrnul(revision, '@');
138 strbuf_add(&symbolic, revision, end_of_rev - revision);
139
140 ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref);
141 strbuf_release(&symbolic);
142 switch (ret) {
143 case 0: /* Not found, but valid ref */
144 info->is_stash_ref = 0;
145 break;
146 case 1:
147 info->is_stash_ref = !strcmp(expanded_ref, ref_stash);
148 break;
149 default: /* Invalid or ambiguous */
150 free_stash_info(info);
151 }
152
153 free(expanded_ref);
154 return !(ret == 0 || ret == 1);
155}
156
4e2dd393
JT
157static int do_clear_stash(void)
158{
159 struct object_id obj;
160 if (get_oid(ref_stash, &obj))
161 return 0;
162
163 return delete_ref(NULL, ref_stash, &obj, 0);
164}
165
166static int clear_stash(int argc, const char **argv, const char *prefix)
167{
168 struct option options[] = {
169 OPT_END()
170 };
171
172 argc = parse_options(argc, argv, prefix, options,
173 git_stash_helper_clear_usage,
174 PARSE_OPT_STOP_AT_NON_OPTION);
175
176 if (argc)
177 return error(_("git stash clear with parameters is "
178 "unimplemented"));
179
180 return do_clear_stash();
181}
182
8a0fc8d1
JT
183static int reset_tree(struct object_id *i_tree, int update, int reset)
184{
185 int nr_trees = 1;
186 struct unpack_trees_options opts;
187 struct tree_desc t[MAX_UNPACK_TREES];
188 struct tree *tree;
189 struct lock_file lock_file = LOCK_INIT;
190
191 read_cache_preload(NULL);
192 if (refresh_cache(REFRESH_QUIET))
193 return -1;
194
195 hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
196
197 memset(&opts, 0, sizeof(opts));
198
199 tree = parse_tree_indirect(i_tree);
200 if (parse_tree(tree))
201 return -1;
202
203 init_tree_desc(t, tree->buffer, tree->size);
204
205 opts.head_idx = 1;
206 opts.src_index = &the_index;
207 opts.dst_index = &the_index;
208 opts.merge = 1;
209 opts.reset = reset;
210 opts.update = update;
211 opts.fn = oneway_merge;
212
213 if (unpack_trees(nr_trees, t, &opts))
214 return -1;
215
216 if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
217 return error(_("unable to write new index file"));
218
219 return 0;
220}
221
222static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit)
223{
224 struct child_process cp = CHILD_PROCESS_INIT;
225 const char *w_commit_hex = oid_to_hex(w_commit);
226
227 /*
228 * Diff-tree would not be very hard to replace with a native function,
229 * however it should be done together with apply_cached.
230 */
231 cp.git_cmd = 1;
232 argv_array_pushl(&cp.args, "diff-tree", "--binary", NULL);
233 argv_array_pushf(&cp.args, "%s^2^..%s^2", w_commit_hex, w_commit_hex);
234
235 return pipe_command(&cp, NULL, 0, out, 0, NULL, 0);
236}
237
238static int apply_cached(struct strbuf *out)
239{
240 struct child_process cp = CHILD_PROCESS_INIT;
241
242 /*
243 * Apply currently only reads either from stdin or a file, thus
244 * apply_all_patches would have to be updated to optionally take a
245 * buffer.
246 */
247 cp.git_cmd = 1;
248 argv_array_pushl(&cp.args, "apply", "--cached", NULL);
249 return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0);
250}
251
252static int reset_head(void)
253{
254 struct child_process cp = CHILD_PROCESS_INIT;
255
256 /*
257 * Reset is overall quite simple, however there is no current public
258 * API for resetting.
259 */
260 cp.git_cmd = 1;
261 argv_array_push(&cp.args, "reset");
262
263 return run_command(&cp);
264}
265
266static int get_newly_staged(struct strbuf *out, struct object_id *c_tree)
267{
268 struct child_process cp = CHILD_PROCESS_INIT;
269 const char *c_tree_hex = oid_to_hex(c_tree);
270
271 /*
272 * diff-index is very similar to diff-tree above, and should be
273 * converted together with update_index.
274 */
275 cp.git_cmd = 1;
276 argv_array_pushl(&cp.args, "diff-index", "--cached", "--name-only",
277 "--diff-filter=A", NULL);
278 argv_array_push(&cp.args, c_tree_hex);
279 return pipe_command(&cp, NULL, 0, out, 0, NULL, 0);
280}
281
282static int update_index(struct strbuf *out)
283{
284 struct child_process cp = CHILD_PROCESS_INIT;
285
286 /*
287 * Update-index is very complicated and may need to have a public
288 * function exposed in order to remove this forking.
289 */
290 cp.git_cmd = 1;
291 argv_array_pushl(&cp.args, "update-index", "--add", "--stdin", NULL);
292 return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0);
293}
294
295static int restore_untracked(struct object_id *u_tree)
296{
297 int res;
298 struct child_process cp = CHILD_PROCESS_INIT;
299
300 /*
301 * We need to run restore files from a given index, but without
302 * affecting the current index, so we use GIT_INDEX_FILE with
303 * run_command to fork processes that will not interfere.
304 */
305 cp.git_cmd = 1;
306 argv_array_push(&cp.args, "read-tree");
307 argv_array_push(&cp.args, oid_to_hex(u_tree));
308 argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s",
309 stash_index_path.buf);
310 if (run_command(&cp)) {
311 remove_path(stash_index_path.buf);
312 return -1;
313 }
314
315 child_process_init(&cp);
316 cp.git_cmd = 1;
317 argv_array_pushl(&cp.args, "checkout-index", "--all", NULL);
318 argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s",
319 stash_index_path.buf);
320
321 res = run_command(&cp);
322 remove_path(stash_index_path.buf);
323 return res;
324}
325
326static int do_apply_stash(const char *prefix, struct stash_info *info,
327 int index, int quiet)
328{
329 int ret;
330 int has_index = index;
331 struct merge_options o;
332 struct object_id c_tree;
333 struct object_id index_tree;
334 struct commit *result;
335 const struct object_id *bases[1];
336
337 read_cache_preload(NULL);
338 if (refresh_cache(REFRESH_QUIET))
339 return -1;
340
341 if (write_cache_as_tree(&c_tree, 0, NULL))
342 return error(_("cannot apply a stash in the middle of a merge"));
343
344 if (index) {
345 if (oideq(&info->b_tree, &info->i_tree) ||
346 oideq(&c_tree, &info->i_tree)) {
347 has_index = 0;
348 } else {
349 struct strbuf out = STRBUF_INIT;
350
351 if (diff_tree_binary(&out, &info->w_commit)) {
352 strbuf_release(&out);
353 return error(_("could not generate diff %s^!."),
354 oid_to_hex(&info->w_commit));
355 }
356
357 ret = apply_cached(&out);
358 strbuf_release(&out);
359 if (ret)
360 return error(_("conflicts in index."
361 "Try without --index."));
362
363 discard_cache();
364 read_cache();
365 if (write_cache_as_tree(&index_tree, 0, NULL))
366 return error(_("could not save index tree"));
367
368 reset_head();
369 }
370 }
371
372 if (info->has_u && restore_untracked(&info->u_tree))
373 return error(_("could not restore untracked files from stash"));
374
375 init_merge_options(&o);
376
377 o.branch1 = "Updated upstream";
378 o.branch2 = "Stashed changes";
379
380 if (oideq(&info->b_tree, &c_tree))
381 o.branch1 = "Version stash was based on";
382
383 if (quiet)
384 o.verbosity = 0;
385
386 if (o.verbosity >= 3)
387 printf_ln(_("Merging %s with %s"), o.branch1, o.branch2);
388
389 bases[0] = &info->b_tree;
390
391 ret = merge_recursive_generic(&o, &c_tree, &info->w_tree, 1, bases,
392 &result);
393 if (ret) {
394 rerere(0);
395
396 if (index)
397 fprintf_ln(stderr, _("Index was not unstashed."));
398
399 return ret;
400 }
401
402 if (has_index) {
403 if (reset_tree(&index_tree, 0, 0))
404 return -1;
405 } else {
406 struct strbuf out = STRBUF_INIT;
407
408 if (get_newly_staged(&out, &c_tree)) {
409 strbuf_release(&out);
410 return -1;
411 }
412
413 if (reset_tree(&c_tree, 0, 1)) {
414 strbuf_release(&out);
415 return -1;
416 }
417
418 ret = update_index(&out);
419 strbuf_release(&out);
420 if (ret)
421 return -1;
422
423 discard_cache();
424 }
425
426 if (quiet) {
427 if (refresh_cache(REFRESH_QUIET))
428 warning("could not refresh index");
429 } else {
430 struct child_process cp = CHILD_PROCESS_INIT;
431
432 /*
433 * Status is quite simple and could be replaced with calls to
434 * wt_status in the future, but it adds complexities which may
435 * require more tests.
436 */
437 cp.git_cmd = 1;
438 cp.dir = prefix;
439 argv_array_push(&cp.args, "status");
440 run_command(&cp);
441 }
442
443 return 0;
444}
445
446static int apply_stash(int argc, const char **argv, const char *prefix)
447{
448 int ret;
449 int quiet = 0;
450 int index = 0;
451 struct stash_info info;
452 struct option options[] = {
453 OPT__QUIET(&quiet, N_("be quiet, only report errors")),
454 OPT_BOOL(0, "index", &index,
455 N_("attempt to recreate the index")),
456 OPT_END()
457 };
458
459 argc = parse_options(argc, argv, prefix, options,
460 git_stash_helper_apply_usage, 0);
461
462 if (get_stash_info(&info, argc, argv))
463 return -1;
464
465 ret = do_apply_stash(prefix, &info, index, quiet);
466 free_stash_info(&info);
467 return ret;
468}
469
4e2dd393
JT
470static int do_drop_stash(const char *prefix, struct stash_info *info, int quiet)
471{
472 int ret;
473 struct child_process cp_reflog = CHILD_PROCESS_INIT;
474 struct child_process cp = CHILD_PROCESS_INIT;
475
476 /*
477 * reflog does not provide a simple function for deleting refs. One will
478 * need to be added to avoid implementing too much reflog code here
479 */
480
481 cp_reflog.git_cmd = 1;
482 argv_array_pushl(&cp_reflog.args, "reflog", "delete", "--updateref",
483 "--rewrite", NULL);
484 argv_array_push(&cp_reflog.args, info->revision.buf);
485 ret = run_command(&cp_reflog);
486 if (!ret) {
487 if (!quiet)
488 printf_ln(_("Dropped %s (%s)"), info->revision.buf,
489 oid_to_hex(&info->w_commit));
490 } else {
491 return error(_("%s: Could not drop stash entry"),
492 info->revision.buf);
493 }
494
495 /*
496 * This could easily be replaced by get_oid, but currently it will throw
497 * a fatal error when a reflog is empty, which we can not recover from.
498 */
499 cp.git_cmd = 1;
500 /* Even though --quiet is specified, rev-parse still outputs the hash */
501 cp.no_stdout = 1;
502 argv_array_pushl(&cp.args, "rev-parse", "--verify", "--quiet", NULL);
503 argv_array_pushf(&cp.args, "%s@{0}", ref_stash);
504 ret = run_command(&cp);
505
506 /* do_clear_stash if we just dropped the last stash entry */
507 if (ret)
508 do_clear_stash();
509
510 return 0;
511}
512
513static void assert_stash_ref(struct stash_info *info)
514{
515 if (!info->is_stash_ref) {
516 error(_("'%s' is not a stash reference"), info->revision.buf);
517 free_stash_info(info);
518 exit(1);
519 }
520}
521
522static int drop_stash(int argc, const char **argv, const char *prefix)
523{
524 int ret;
525 int quiet = 0;
526 struct stash_info info;
527 struct option options[] = {
528 OPT__QUIET(&quiet, N_("be quiet, only report errors")),
529 OPT_END()
530 };
531
532 argc = parse_options(argc, argv, prefix, options,
533 git_stash_helper_drop_usage, 0);
534
535 if (get_stash_info(&info, argc, argv))
536 return -1;
537
538 assert_stash_ref(&info);
539
540 ret = do_drop_stash(prefix, &info, quiet);
541 free_stash_info(&info);
542 return ret;
543}
544
577c1995
JT
545static int branch_stash(int argc, const char **argv, const char *prefix)
546{
547 int ret;
548 const char *branch = NULL;
549 struct stash_info info;
550 struct child_process cp = CHILD_PROCESS_INIT;
551 struct option options[] = {
552 OPT_END()
553 };
554
555 argc = parse_options(argc, argv, prefix, options,
556 git_stash_helper_branch_usage, 0);
557
558 if (!argc) {
559 fprintf_ln(stderr, _("No branch name specified"));
560 return -1;
561 }
562
563 branch = argv[0];
564
565 if (get_stash_info(&info, argc - 1, argv + 1))
566 return -1;
567
568 cp.git_cmd = 1;
569 argv_array_pushl(&cp.args, "checkout", "-b", NULL);
570 argv_array_push(&cp.args, branch);
571 argv_array_push(&cp.args, oid_to_hex(&info.b_commit));
572 ret = run_command(&cp);
573 if (!ret)
574 ret = do_apply_stash(prefix, &info, 1, 0);
575 if (!ret && info.is_stash_ref)
576 ret = do_drop_stash(prefix, &info, 0);
577
578 free_stash_info(&info);
579
580 return ret;
581}
582
8a0fc8d1
JT
583int cmd_stash__helper(int argc, const char **argv, const char *prefix)
584{
585 pid_t pid = getpid();
586 const char *index_file;
587
588 struct option options[] = {
589 OPT_END()
590 };
591
592 git_config(git_default_config, NULL);
593
594 argc = parse_options(argc, argv, prefix, options, git_stash_helper_usage,
595 PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
596
597 index_file = get_index_file();
598 strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file,
599 (uintmax_t)pid);
600
601 if (argc < 1)
602 usage_with_options(git_stash_helper_usage, options);
603 if (!strcmp(argv[0], "apply"))
604 return !!apply_stash(argc, argv, prefix);
4e2dd393
JT
605 else if (!strcmp(argv[0], "clear"))
606 return !!clear_stash(argc, argv, prefix);
607 else if (!strcmp(argv[0], "drop"))
608 return !!drop_stash(argc, argv, prefix);
577c1995
JT
609 else if (!strcmp(argv[0], "branch"))
610 return !!branch_stash(argc, argv, prefix);
8a0fc8d1
JT
611
612 usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
613 git_stash_helper_usage, options);
614}