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