1/*2* "git reset" builtin command3*4* Copyright (c) 2007 Carlos Rica5*6* Based on git-reset.sh, which is7*8* Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano9*/10#include "cache.h"11#include "tag.h"12#include "object.h"13#include "commit.h"14#include "run-command.h"15#include "refs.h"16#include "diff.h"17#include "diffcore.h"18#include "tree.h"1920static const char builtin_reset_usage[] =21"git-reset [--mixed | --soft | --hard] [-q] [<commit-ish>] [ [--] <paths>...]";2223static char *args_to_str(const char **argv)24{25char *buf = NULL;26unsigned long len, space = 0, nr = 0;2728for (; *argv; argv++) {29len = strlen(*argv);30ALLOC_GROW(buf, nr + 1 + len, space);31if (nr)32buf[nr++] = ' ';33memcpy(buf + nr, *argv, len);34nr += len;35}36ALLOC_GROW(buf, nr + 1, space);37buf[nr] = '\0';3839return buf;40}4142static inline int is_merge(void)43{44return !access(git_path("MERGE_HEAD"), F_OK);45}4647static int reset_index_file(const unsigned char *sha1, int is_hard_reset)48{49int i = 0;50const char *args[6];5152args[i++] = "read-tree";53args[i++] = "-v";54args[i++] = "--reset";55if (is_hard_reset)56args[i++] = "-u";57args[i++] = sha1_to_hex(sha1);58args[i] = NULL;5960return run_command_v_opt(args, RUN_GIT_CMD);61}6263static void print_new_head_line(struct commit *commit)64{65const char *hex, *dots = "...", *body;6667hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);68if (!hex) {69hex = sha1_to_hex(commit->object.sha1);70dots = "";71}72printf("HEAD is now at %s%s", hex, dots);73body = strstr(commit->buffer, "\n\n");74if (body) {75const char *eol;76size_t len;77body += 2;78eol = strchr(body, '\n');79len = eol ? eol - body : strlen(body);80printf(" %.*s\n", (int) len, body);81}82else83printf("\n");84}8586static int update_index_refresh(int fd, struct lock_file *index_lock)87{88int result;8990if (!index_lock) {91index_lock = xcalloc(1, sizeof(struct lock_file));92fd = hold_locked_index(index_lock, 1);93}9495if (read_cache() < 0)96return error("Could not read index");97result = refresh_cache(0) ? 1 : 0;98if (write_cache(fd, active_cache, active_nr) ||99commit_locked_index(index_lock))100return error ("Could not refresh index");101return result;102}103104static void update_index_from_diff(struct diff_queue_struct *q,105struct diff_options *opt, void *data)106{107int i;108int *discard_flag = data;109110/* do_diff_cache() mangled the index */111discard_cache();112*discard_flag = 1;113read_cache();114115for (i = 0; i < q->nr; i++) {116struct diff_filespec *one = q->queue[i]->one;117if (one->mode) {118struct cache_entry *ce;119ce = make_cache_entry(one->mode, one->sha1, one->path,1200, 0);121add_cache_entry(ce, ADD_CACHE_OK_TO_ADD |122ADD_CACHE_OK_TO_REPLACE);123} else124remove_file_from_cache(one->path);125}126}127128static int read_from_tree(const char *prefix, const char **argv,129unsigned char *tree_sha1)130{131struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));132int index_fd, index_was_discarded = 0;133struct diff_options opt;134135memset(&opt, 0, sizeof(opt));136diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);137opt.output_format = DIFF_FORMAT_CALLBACK;138opt.format_callback = update_index_from_diff;139opt.format_callback_data = &index_was_discarded;140141index_fd = hold_locked_index(lock, 1);142index_was_discarded = 0;143read_cache();144if (do_diff_cache(tree_sha1, &opt))145return 1;146diffcore_std(&opt);147diff_flush(&opt);148diff_tree_release_paths(&opt);149150if (!index_was_discarded)151/* The index is still clobbered from do_diff_cache() */152discard_cache();153return update_index_refresh(index_fd, lock);154}155156static void prepend_reflog_action(const char *action, char *buf, size_t size)157{158const char *sep = ": ";159const char *rla = getenv("GIT_REFLOG_ACTION");160if (!rla)161rla = sep = "";162if (snprintf(buf, size, "%s%s%s", rla, sep, action) >= size)163warning("Reflog action message too long: %.*s...", 50, buf);164}165166enum reset_type { MIXED, SOFT, HARD, NONE };167static const char *reset_type_names[] = { "mixed", "soft", "hard", NULL };168169int cmd_reset(int argc, const char **argv, const char *prefix)170{171int i = 1, reset_type = NONE, update_ref_status = 0, quiet = 0;172const char *rev = "HEAD";173unsigned char sha1[20], *orig = NULL, sha1_orig[20],174*old_orig = NULL, sha1_old_orig[20];175struct commit *commit;176char *reflog_action, msg[1024];177178git_config(git_default_config);179180reflog_action = args_to_str(argv);181setenv("GIT_REFLOG_ACTION", reflog_action, 0);182183while (i < argc) {184if (!strcmp(argv[i], "--mixed")) {185reset_type = MIXED;186i++;187}188else if (!strcmp(argv[i], "--soft")) {189reset_type = SOFT;190i++;191}192else if (!strcmp(argv[i], "--hard")) {193reset_type = HARD;194i++;195}196else if (!strcmp(argv[i], "-q")) {197quiet = 1;198i++;199}200else201break;202}203204if (i < argc && argv[i][0] != '-')205rev = argv[i++];206207if (get_sha1(rev, sha1))208die("Failed to resolve '%s' as a valid ref.", rev);209210commit = lookup_commit_reference(sha1);211if (!commit)212die("Could not parse object '%s'.", rev);213hashcpy(sha1, commit->object.sha1);214215if (i < argc && !strcmp(argv[i], "--"))216i++;217else if (i < argc && argv[i][0] == '-')218usage(builtin_reset_usage);219220/* git reset tree [--] paths... can be used to221* load chosen paths from the tree into the index without222* affecting the working tree nor HEAD. */223if (i < argc) {224if (reset_type == MIXED)225warning("--mixed option is deprecated with paths.");226else if (reset_type != NONE)227die("Cannot do %s reset with paths.",228reset_type_names[reset_type]);229return read_from_tree(prefix, argv + i, sha1);230}231if (reset_type == NONE)232reset_type = MIXED; /* by default */233234if (reset_type == HARD && is_bare_repository())235die("hard reset makes no sense in a bare repository");236237/* Soft reset does not touch the index file nor the working tree238* at all, but requires them in a good order. Other resets reset239* the index file to the tree object we are switching to. */240if (reset_type == SOFT) {241if (is_merge() || read_cache() < 0 || unmerged_cache())242die("Cannot do a soft reset in the middle of a merge.");243}244else if (reset_index_file(sha1, (reset_type == HARD)))245die("Could not reset index file to revision '%s'.", rev);246247/* Any resets update HEAD to the head being switched to,248* saving the previous head in ORIG_HEAD before. */249if (!get_sha1("ORIG_HEAD", sha1_old_orig))250old_orig = sha1_old_orig;251if (!get_sha1("HEAD", sha1_orig)) {252orig = sha1_orig;253prepend_reflog_action("updating ORIG_HEAD", msg, sizeof(msg));254update_ref(msg, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);255}256else if (old_orig)257delete_ref("ORIG_HEAD", old_orig);258prepend_reflog_action("updating HEAD", msg, sizeof(msg));259update_ref_status = update_ref(msg, "HEAD", sha1, orig, 0, MSG_ON_ERR);260261switch (reset_type) {262case HARD:263if (!update_ref_status && !quiet)264print_new_head_line(commit);265break;266case SOFT: /* Nothing else to do. */267break;268case MIXED: /* Report what has not been updated. */269update_index_refresh(0, NULL);270break;271}272273unlink(git_path("MERGE_HEAD"));274unlink(git_path("rr-cache/MERGE_RR"));275unlink(git_path("MERGE_MSG"));276unlink(git_path("SQUASH_MSG"));277278free(reflog_action);279280return update_ref_status;281}