@@ -0,0 +1,992 @@
+#include "builtin.h"
+#include "cache.h"
+#include "parse-options.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "commit.h"
+#include "run-command.h"
+#include "dir.h"
+#include "refs.h"
+#include "quote.h"
+#include "log-tree.h"
+
+static char const * const rebase_usage[] = {
+ "git rebase [-i | --interactive] [options] [--onto <newbase>]\n"
+ " <upstream> [<branch>]",
+ "git rebase [-i | --interactive] [options] --onto <newbase>\n"
+ " --root [<branch>]",
+ "git rebase --continue | --skip | --abort",
+ NULL
+};
+
+static const char *resolve_msg =
+ "When you have resolved this problem, run \"git rebase --continue\".\n"
+ "If you would prefer to skip this patch, instead run \"git rebase --skip\".\n"
+ "To restore the original branch and stop rebasing run \"git rebase --abort\".\n";
+
+#define REBASE_ABORT 0x0001
+#define REBASE_CONTINUE 0x0002
+#define REBASE_FORCE 0x0004
+#define REBASE_IGNORE_DATE 0x0008
+#define REBASE_INTERACTIVE 0x0010
+#define REBASE_MERGE 0x0020
+#define REBASE_STAT 0x0040
+#define REBASE_NO_VERIFY 0x0080
+#define REBASE_PRESERVE_MERGES 0x0100
+#define REBASE_ROOT 0x0200
+#define REBASE_SKIP 0x0400
+#define REBASE_VERBOSE 0x0800
+
+struct rebase_opt {
+ int flags;
+ int context;
+ const char *onto;
+ const char *upstream;
+ const char *branch;
+ const char *strategy;
+ const char *whitespace;
+};
+
+static int rebase_config(const char *var, const char *value, void *data)
+{
+ if (!strcmp(var, "rebase.stat")) {
+ if (git_config_bool(var, value))
+ ((struct rebase_opt*)data)->flags |= REBASE_STAT;
+ return 0;
+ }
+ return 0;
+}
+
+/* utility functions */
+
+static char *load_string(char *git_pathname)
+{
+ struct strbuf sb = STRBUF_INIT;
+ const char *path = git_path(git_pathname);
+
+ if (strbuf_read_file(&sb, path, 41) == -1)
+ die("Could not read %s", path);
+
+ while (sb.len && sb.buf[sb.len-1] == '\n') {
+ sb.len--;
+ sb.buf[sb.len] = '\0';
+ }
+
+ return sb.buf;
+}
+
+static void save_string(char *git_pathname, const char *str)
+{
+ const char *path = git_path(git_pathname);
+ int fd = open(path, O_CREAT | O_WRONLY, 0600);
+ if (fd < 0)
+ die("Could not open %s for writing", path);
+ write_in_full(fd, str, strlen(str));
+ write_in_full(fd, "\n", 1);
+ close(fd);
+}
+
+static void load_sha1(char *git_pathname, unsigned char *sha1)
+{
+ char *buf = load_string(git_pathname);
+ if (get_sha1_hex(buf, sha1) == -1)
+ die("Invalid SHA-1 in %s", git_path(git_pathname));
+ free(buf);
+}
+
+static void save_sha1(char *git_pathname, const unsigned char *sha1)
+{
+ save_string(git_pathname, sha1_to_hex(sha1));
+}
+
+static int load_int(char *git_pathname)
+{
+ char *buf = load_string(git_pathname);
+ int ret;
+
+ if (sscanf(buf, "%d", &ret) != 1)
+ die("Failed to read number from %s", git_path(git_pathname));
+ free(buf);
+ return ret;
+}
+
+static void save_int(char *git_pathname, int number)
+{
+ const char *path = git_path(git_pathname);
+ FILE *fp = fopen(path, "w");
+ if (!fp)
+ die("Could not open %s for writing", path);
+ fprintf(fp, "%d\n", number);
+ fclose(fp);
+}
+
+static void get_commit(const char *name, unsigned char *sha1, struct commit **cmt)
+{
+ if (get_sha1(name, sha1))
+ die("Failed to resolve SHA-1 from %s", name);
+ *cmt = lookup_commit(sha1);
+ if (!*cmt)
+ die("%s is not a commitish", name);
+}
+
+static int continue_merge(int msgnum, unsigned char *prev_head)
+{
+ const char *argv_diff[] = { "--quiet", "--ignore-submodules", "HEAD", "--", NULL };
+ struct strbuf result_line = STRBUF_INIT;
+ unsigned char cmt[20];
+ struct rev_info rev;
+ struct stat st;
+ int i, result;
+
+ if (stat(git_path("rebase-merge"), &st) || !S_ISDIR(st.st_mode))
+ die("%s directory does not exist", git_path("rebase-merge"));
+
+ /* git-am or git-merge-* may have been run, can no longer trust the old cache */
+ discard_cache();
+ if (read_cache() < 0)
+ die("Could not read the index");
+
+ for (i = 0;i < active_nr;i++)
+ if (ce_stage(active_cache[i]))
+ die("You still have unmerged paths in your index\n"
+ "did you forget to use git add?\n"
+ "%s",
+ resolve_msg);
+
+ init_revisions(&rev, NULL);
+ rev.abbrev = 0;
+ setup_revisions(4, argv_diff, &rev, NULL);
+ result = run_diff_index(&rev, 0);
+ load_sha1("rebase-merge/current", cmt);
+
+ if (DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES)) {
+ struct child_process cp;
+ const char *argv[5] = {"commit", "--no-verify", "-C", sha1_to_hex(cmt), NULL };
+
+ memset(&cp, 0, sizeof(cp));
+ cp.git_cmd = 1;
+ cp.argv = argv;
+ if (run_command(&cp))
+ die("Commit failed, please do not call \"git commit\"\n"
+ "directly, but instead do one of the following:\n"
+ "%s", resolve_msg);
+ strbuf_addf(&result_line, "Committed: %04d ", msgnum);
+ }
+ else
+ strbuf_addf(&result_line, "Already applied: %04d ", msgnum);
+
+ parse_commit(lookup_commit(cmt));
+ format_commit_message(lookup_commit(cmt), "%f", &result_line, 0);
+ puts(result_line.buf);
+ strbuf_release(&result_line);
+
+ if (get_sha1("HEAD^0", prev_head))
+ die("Could not resolve HEAD commit");
+ save_sha1("rebase-merge/prev_head", prev_head);
+ msgnum++;
+ save_int("rebase-merge/msgnum", msgnum);
+ return msgnum;
+}
+
+static void call_merge(const char *strategy, int msgnum)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct strbuf sb2 = STRBUF_INIT;
+ struct strbuf sb_env1 = STRBUF_INIT;
+ struct strbuf sb_env2 = STRBUF_INIT;
+ unsigned char cmt[20];
+ unsigned char head[20];
+ struct child_process cp;
+ const char *full_ref;
+ int end, result, type;
+ const char *argv[5];
+ char *onto_name, *head_hex, *cmt_hex;
+
+ strbuf_addf(&sb, "rebase-merge/cmt.%d", msgnum);
+ load_sha1(sb.buf, cmt);
+ save_sha1("rebase-merge/current", cmt);
+
+ full_ref = resolve_ref("HEAD", head, 1, &type);
+ if (!lookup_commit(head))
+ die("HEAD does not point to a commit");
+ msgnum = load_int("rebase-merge/msgnum");
+ end = load_int("rebase-merge/end");
+
+ strbuf_setlen(&sb, 0);
+ strbuf_addf(&sb, "%s~%d",
+ !strncmp(full_ref, "refs/heads/", 11) ? full_ref + 11 : full_ref,
+ end - msgnum);
+ strbuf_addf(&sb_env1, "GITHEAD_%s", sha1_to_hex(cmt));
+ setenv(sb_env1.buf, sb.buf, 1);
+
+ onto_name = load_string("rebase-merge/onto_name");
+ strbuf_setlen(&sb, 0);
+ strbuf_addf(&sb_env2, "GITHEAD_%s", sha1_to_hex(head));
+ setenv(sb_env2.buf, onto_name, 1);
+ free(onto_name);
+
+ strbuf_setlen(&sb, 0);
+ strbuf_addf(&sb, "merge-%s", strategy);
+ strbuf_addf(&sb2, "%s^", sha1_to_hex(cmt));
+ head_hex = xstrdup(sha1_to_hex(head));
+ cmt_hex = xstrdup(sha1_to_hex(cmt));
+ argv[0] = sb.buf;
+ argv[1] = sb2.buf;
+ argv[2] = "--";
+ argv[3] = head_hex;
+ argv[4] = cmt_hex;
+ argv[5] = NULL;
+ memset(&cp, 0, sizeof(cp));
+ cp.git_cmd = 1;
+ cp.argv = argv;
+ result = run_command(&cp);
+ free(head_hex);
+ free(cmt_hex);
+ strbuf_release(&sb);
+ strbuf_release(&sb2);
+ switch (result) {
+ case 0:
+ unsetenv(sb_env1.buf);
+ strbuf_release(&sb_env1);
+ unsetenv(sb_env2.buf);
+ strbuf_release(&sb_env2);
+ break;
+ case -1:
+ argv[0] = "rerere";
+ argv[1] = NULL;
+ run_command(&cp);
+ die(resolve_msg);
+
+ case -2:
+ fprintf(stderr, "Strategy: %d %s failed, try another", result, strategy);
+ die(resolve_msg);
+
+ default:
+ cmt_hex = xstrdup(sha1_to_hex(cmt));
+ die("Unknown exit code (%d) from command: git-merge-%s %s^ -- HEAD %s",
+ result, strategy, cmt_hex, cmt_hex);
+ }
+}
+
+static void move_to_original_branch(const unsigned char *onto,
+ const unsigned char *orig_head,
+ const char *head_name)
+{
+ struct strbuf sb = STRBUF_INIT;
+ unsigned char head[20];
+
+ if (strncmp(head_name, "refs/", 5))
+ return;
+
+ strbuf_addf(&sb, "rebase finished: %s onto %s", head_name, sha1_to_hex(onto));
+ if (get_sha1("HEAD", head))
+ die("Could not resolve HEAD");
+ update_ref(sb.buf, head_name, head, orig_head, 0, DIE_ON_ERR);
+ create_symref("HEAD", head_name, NULL);
+ strbuf_release(&sb);
+}
+
+static int finish_rebase_merge(const unsigned char *onto,
+ const unsigned char *orig_head,
+ const char *head_name)
+{
+ struct strbuf sb = STRBUF_INIT;
+ move_to_original_branch(onto, orig_head, head_name);
+ strbuf_addstr(&sb, git_path("rebase-merge"));
+ remove_dir_recursively(&sb, 0);
+ strbuf_release(&sb);
+ puts("All done.");
+ return 0;
+}
+
+static void check_rebase_in_progress()
+{
+ struct stat st;
+ if ((!stat(git_path("rebase-merge"), &st) && S_ISDIR(st.st_mode)) ||
+ (!stat(git_path("rebase-apply"), &st) && S_ISDIR(st.st_mode)))
+ return;
+ die("No rebase in progress?");
+}
+
+static int cmd_continue(const struct rebase_opt *opt)
+{
+ struct stat st;
+ struct rev_info rev;
+ struct child_process cp;
+ const char *argv_am[] = { "am", "--resolved", "--3way", "--resolvemsg", resolve_msg, NULL };
+ char *head_name;
+ unsigned char onto[20], orig_head[20];
+ int ret;
+
+ check_rebase_in_progress();
+
+ if (read_cache() < 0)
+ die("Could not read the index");
+
+ init_revisions(&rev, NULL);
+ rev.abbrev = 0;
+ DIFF_OPT_SET(&rev.diffopt, QUIET);
+ DIFF_OPT_SET(&rev.diffopt, IGNORE_SUBMODULES);
+ run_diff_files(&rev, DIFF_SILENT_ON_REMOVED);
+ if (DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES)) {
+ printf("You must edit all merge conflicts and then\n"
+ "mark them as resolved using git add\n");
+ return 1;
+ }
+
+ if (!stat(git_path("rebase-merge"), &st) && S_ISDIR(st.st_mode)) {
+ unsigned char prev_head[20], onto[20], orig_head[20];
+ char *head_name;
+ int msgnum, end;
+
+ load_sha1("rebase-merge/prev_head", prev_head);
+ load_sha1("rebase-merge/onto", onto);
+ end = load_int("rebase-merge/end");
+ msgnum = load_int("rebase-merge/msgnum");
+
+ msgnum = continue_merge(msgnum, prev_head);
+ while (msgnum <= end) {
+ call_merge(opt->strategy, msgnum);
+ msgnum = continue_merge(msgnum, prev_head);
+ }
+
+ load_sha1("rebase-merge/orig-head", orig_head);
+ head_name = load_string("rebase-merge/head-name");
+
+ ret = finish_rebase_merge(onto, orig_head, head_name);
+ free(head_name);
+ return ret;
+ }
+
+ head_name = load_string("rebase-apply/head-name");
+ load_sha1("rebase-apply/onto", onto);
+ load_sha1("rebase-apply/orig-head", orig_head);
+
+ memset(&cp, 0, sizeof(cp));
+ cp.git_cmd = 1;
+ cp.argv = argv_am;
+ ret = run_command(&cp);
+ if (IS_RUN_COMMAND_ERR(ret))
+ die("Could not run git am");
+ if (!ret)
+ move_to_original_branch(onto, orig_head, head_name);
+ free(head_name);
+ return ret < 0 ? -ret : 0;
+}
+
+static int cmd_abort()
+{
+ const char *argv_rerere[] = { "rerere", "clear", NULL };
+ const char *argv_reset[] = { "reset", "--hard", NULL /* placeholder */, NULL };
+ struct child_process cp;
+ struct stat st;
+ unsigned char onto[20], orig_head[20];
+ char *head_name;
+ struct strbuf sb = STRBUF_INIT;
+
+ check_rebase_in_progress();
+ memset(&cp, 0, sizeof(cp));
+ cp.git_cmd = 1;
+ cp.argv = argv_rerere;
+ run_command(&cp);
+
+ if (!stat(git_path("rebase-merge"), &st) && S_ISDIR(st.st_mode)) {
+ strbuf_addstr(&sb, git_path("rebase-merge"));
+ head_name = load_string("rebase-merge/head-name");
+ load_sha1("rebase-merge/onto", onto);
+ load_sha1("rebase-merge/orig-head", orig_head);
+ move_to_original_branch(onto, orig_head, head_name);
+ }
+ else {
+ strbuf_addstr(&sb, git_path("rebase-apply"));
+ head_name = load_string("rebase-apply/head-name");
+ load_sha1("rebase-apply/onto", onto);
+ load_sha1("rebase-apply/orig-head", orig_head);
+ move_to_original_branch(onto, orig_head, head_name);
+ }
+ free(head_name);
+
+ cp.argv = argv_reset;
+ argv_reset[2] = sha1_to_hex(orig_head);
+ if (run_command(&cp))
+ die("Failed running git reset");
+ remove_dir_recursively(&sb, 0);
+ strbuf_release(&sb);
+ return 0;
+}
+
+static int cmd_skip(const struct rebase_opt *opt)
+{
+ const char *argv_reset[] = { "reset", "--hard", "HEAD", NULL };
+ const char *argv_am[] = { "am", "-3", "--skip", "--resolvemsg", resolve_msg, NULL };
+ struct child_process cp;
+ char *head_name;
+ unsigned char orig_head[20], onto[20];
+ struct stat st;
+ int ret;
+
+ check_rebase_in_progress();
+ memset(&cp, 0, sizeof(cp));
+ cp.git_cmd = 1;
+ cp.argv = argv_reset;
+ ret = run_command(&cp);
+ if (IS_RUN_COMMAND_ERR(ret))
+ die("Failed to run git reset");
+ if (ret)
+ return -ret;
+ if (!stat(git_path("rebase-merge"), &st) && S_ISDIR(st.st_mode)) {
+ const char *argv_rerere[] = { "rerere", "clear", NULL };
+ unsigned char prev_head[20], onto[20], orig_head[20];
+ int msgnum, end;
+ char *head_name;
+
+ cp.argv = argv_rerere;
+ run_command(&cp);
+ load_sha1("rebase-merge/prev_head", prev_head);
+ end = load_int("rebase-merge/end");
+ msgnum = load_int("rebase-merge/msgnum") + 1;
+ load_sha1("rebase-merge/onto", onto);
+ while (msgnum <= end) {
+ call_merge(opt->strategy, msgnum);
+ msgnum = continue_merge(msgnum, prev_head);
+ }
+
+ load_sha1("rebase-merge/orig-head", orig_head);
+ head_name = load_string("rebase-merge/head-name");
+
+ ret = finish_rebase_merge(onto, orig_head, head_name);
+ free(head_name);
+ return ret;
+ }
+
+ head_name = load_string("rebase-apply/head-name");
+ load_sha1("rebase-apply/onto", onto);
+ load_sha1("rebase-apply/orig-head", orig_head);
+
+ cp.argv = argv_am;
+ ret = run_command(&cp);
+ if (IS_RUN_COMMAND_ERR(ret))
+ die("Failed to run git am");
+ if (ret)
+ return -ret;
+
+ move_to_original_branch(onto, orig_head, head_name);
+ free(head_name);
+ return ret < 0 ? -ret : 0;
+}
+
+static void run_interactive(int argc, const char **argv)
+{
+ int ret, i;
+ struct child_process cp;
+ for (i = 1;i < argc;i++) {
+ if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--interactive"))
+ break;
+ if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--preserve-merges")) {
+ setenv("GIT_EDITOR", ":", 1);
+ break;
+ }
+ }
+
+ if (i == argc) {
+ struct stat st;
+ if (stat(git_path("rebase-merge/interactive"), &st) ||
+ !S_ISREG(st.st_mode))
+ return;
+ }
+
+ memset(&cp, 0, sizeof(cp));
+ cp.git_cmd = 1;
+ cp.argv = argv;
+ argv[0] = "rebase--interactive";
+ ret = run_command(&cp);
+ if (IS_RUN_COMMAND_ERR(ret))
+ die("Failed to run git rebase--interactive");
+ exit(ret);
+}
+
+/* from diff.c */
+static void strip_prefix(int prefix_length, const char **namep, const char **otherp)
+{
+ /* Strip the prefix but do not molest /dev/null and absolute paths */
+ if (*namep && **namep != '/')
+ *namep += prefix_length;
+ if (*otherp && **otherp != '/')
+ *otherp += prefix_length;
+}
+
+static void die_dirty_cache(struct diff_queue_struct *q,
+ struct diff_options *opt,
+ void *data)
+{
+ int i;
+
+ if (!q->nr)
+ return;
+
+ fprintf(stderr,"cannot rebase: your index contains uncommitted changes\n");
+
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+
+ fprintf(stderr, "%c ",p->status);
+
+ if (p->status == DIFF_STATUS_COPIED ||
+ p->status == DIFF_STATUS_RENAMED) {
+ const char *name_a, *name_b;
+ name_a = p->one->path;
+ name_b = p->two->path;
+ strip_prefix(opt->prefix_length, &name_a, &name_b);
+ write_name_quoted(name_a, stderr, '\t');
+ write_name_quoted(name_b, stderr, '\n');
+ } else {
+ const char *name_a, *name_b;
+ name_a = p->one->mode ? p->one->path : p->two->path;
+ name_b = NULL;
+ strip_prefix(opt->prefix_length, &name_a, &name_b);
+ write_name_quoted(name_a, stderr, '\n');
+ }
+ }
+ exit(1);
+}
+
+static void abort_continue_skip(const struct rebase_opt *opt)
+{
+ int count = 0;
+ if (opt->flags & REBASE_CONTINUE) count++;
+ if (opt->flags & REBASE_SKIP) count++;
+ if (opt->flags & REBASE_ABORT) count++;
+ if (count > 1)
+ die("--continue, --skip and --abort are mutually exclusive");
+ if (!count)
+ return;
+
+ /* REBASE_STAT may be set by git config */
+ if (opt->flags & ~(REBASE_CONTINUE | REBASE_SKIP | REBASE_ABORT | REBASE_STAT))
+ die("--continue, --skip and --abort do not take any other argument");
+
+ if (opt->flags & REBASE_CONTINUE) exit(cmd_continue(opt));
+ if (opt->flags & REBASE_ABORT) exit(cmd_abort());
+ if (opt->flags & REBASE_SKIP) exit(cmd_skip(opt));
+
+ die("Unexpected operation");
+}
+
+static int cmd_apply_rebase(const struct rebase_opt *opt,
+ struct object *onto,
+ struct object *upstream,
+ struct object *orig_head,
+ const char *onto_name,
+ const char *head_name)
+{
+ struct strbuf sb = STRBUF_INIT;
+ const char *argv_fp[] = { "format-patch", "-k", "--stdout", "--full-index",
+ "--ignore-if-in-upstream", NULL /* placeholder */, NULL };
+ const char *argv_am[] = { "am", "--rebasing", "--resolvemsg", resolve_msg,
+ NULL, NULL, /* --whitespace */
+ NULL, /* -Cn */
+ NULL, /* --ignore-date */
+ NULL };
+ const char **argp;
+ struct child_process cp_fp, cp_am;
+ int fd[2], ret;
+ char context_buf[10];
+
+ strbuf_addstr(&sb, sha1_to_hex(upstream->sha1));
+ strbuf_addstr(&sb, "..");
+ strbuf_addstr(&sb, sha1_to_hex(orig_head->sha1));
+
+ memset(&cp_fp, 0, sizeof(cp_fp));
+ memset(&cp_am, 0, sizeof(cp_am));
+
+ if (pipe(fd))
+ die("Failed to make pipe");
+
+ cp_fp.git_cmd = 1;
+ cp_fp.argv = argv_fp;
+ cp_fp.out = fd[1];
+ argv_fp[5] = sb.buf;
+
+ argp = argv_am+4;
+ if (opt->whitespace) {
+ *argp++ = "--whitespace";
+ *argp++ = opt->whitespace;
+ }
+ if (opt->flags & REBASE_IGNORE_DATE)
+ *argp++ = "--ignore-date";
+ if (opt->context >= 0) {
+ snprintf(context_buf, 10, "-C%d", opt->context);
+ *argp++ = context_buf;
+ }
+ cp_am.git_cmd = 1;
+ cp_am.argv = argv_am;
+ cp_am.in = fd[0];
+
+ if (start_command(&cp_fp))
+ die("Could not run git format-patch");
+ if (start_command(&cp_am))
+ die("Could not run git am");
+
+ ret = finish_command(&cp_am);
+ strbuf_release(&sb);
+ close(fd[0]);
+ close(fd[1]);
+
+ if (IS_RUN_COMMAND_ERR(ret))
+ die("Failed to run 'git format-patch|git am'");
+ if (!ret)
+ move_to_original_branch(onto->sha1, orig_head->sha1, head_name);
+ else {
+ struct stat st;
+
+ if (!stat(git_path("rebase-apply"), &st) && S_ISDIR(st.st_mode)) {
+ save_string("rebase-apply/head-name", head_name);
+ save_sha1("rebase-apply/onto", onto->sha1);
+ save_sha1("rebase-apply/orig-head", orig_head->sha1);
+ }
+ }
+ return ret;
+}
+
+static int cmd_merge_rebase(const struct rebase_opt *opt,
+ struct object *onto,
+ struct object *upstream,
+ struct object *orig_head,
+ const char *onto_name,
+ const char *head_name)
+{
+ int msgnum, end = 0;
+ struct rev_info revs;
+ struct commit *cmt;
+ struct strbuf sb = STRBUF_INIT;
+ unsigned char prev_head[20];
+
+ /* git rev-list --reverse --no-merges $onto..$orig_head */
+ init_revisions(&revs, NULL);
+ revs.abbrev = 0;
+ revs.reverse = 1;
+ revs.no_merges = 1;
+ upstream->flags |= UNINTERESTING;
+ add_pending_object(&revs, upstream, NULL);
+ orig_head->flags &= ~UNINTERESTING;
+ add_pending_object(&revs, orig_head, NULL);
+
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+
+ memcpy(prev_head, orig_head->sha1, 20);
+
+ mkdir(git_path("rebase-merge"), 0700);
+ save_sha1 ("rebase-merge/onto", onto->sha1);
+ save_string("rebase-merge/onto_name", onto_name);
+ save_sha1 ("rebase-merge/prev_head", prev_head);
+ save_sha1 ("rebase-merge/orig-head", orig_head->sha1);
+ save_string("rebase-merge/head-name", head_name);
+
+ strbuf_addstr(&sb, "rebase-merge/cmt.");
+ while ((cmt = get_revision(&revs)) != NULL) {
+ end++;
+ strbuf_addf(&sb, "%d", end);
+ save_sha1(sb.buf, cmt->object.sha1);
+ strbuf_setlen(&sb, 17);
+ }
+ strbuf_release(&sb);
+
+ msgnum = 1;
+
+ save_int("rebase-merge/msgnum", msgnum);
+ save_int("rebase-merge/end", end);
+
+ while (msgnum <= end) {
+ call_merge(opt->strategy, msgnum);
+ msgnum = continue_merge(msgnum, prev_head);
+ }
+
+ return finish_rebase_merge(onto->sha1, orig_head->sha1, head_name);
+}
+
+int cmd_rebase(int argc, const char **argv, const char *prefix)
+{
+ struct rebase_opt opt;
+ struct option options[] = {
+ OPT_STRING(0,"onto", &opt.onto, "newbase", "starting point at which to create new commits"),
+ OPT_STRING('s',"strategy", &opt.strategy, "strategy", "merge strategy to be used"),
+ OPT_STRING(0,"whitespace", &opt.whitespace, "action", "to be passed to \"git apply\""),
+ OPT_INTEGER('C', NULL, &opt.context, "surrounding context lines to be matched"),
+ OPT_BIT ('i', "interactive", &opt.flags, "interactive mode", REBASE_INTERACTIVE),
+ OPT_BIT ('v', "verbose", &opt.flags, "be verbose", REBASE_VERBOSE | REBASE_STAT),
+ OPT_BIT ('f', "force-rebase",&opt.flags, "force rebase", REBASE_FORCE),
+ OPT_BIT ('p', "preserve-merges",&opt.flags, "preserve merges during rebase", REBASE_PRESERVE_MERGES),
+ OPT_BIT ('m', "merge", &opt.flags, "use merge strategies to rebase", REBASE_MERGE),
+ OPT_BIT (0, "no-verify", &opt.flags, "bypass pre-rebase hook", REBASE_NO_VERIFY),
+ OPT_NEGBIT('n', "no-stat", &opt.flags, "do not show diffstat as part of rebase process", REBASE_STAT),
+ OPT_BIT (0, "committer-date-is-author-date", &opt.flags, "to be passed to \"git am\"", REBASE_IGNORE_DATE),
+ OPT_BIT (0, "ignore-date", &opt.flags, "to be passed to \"git am\"", REBASE_IGNORE_DATE),
+ OPT_BIT (0, "stat", &opt.flags, "show diffstat as part of rebase process", REBASE_STAT),
+ OPT_BIT (0, "root", &opt.flags, "rebase all reachable commits from <branch>", REBASE_ROOT),
+ OPT_BIT (0, "continue", &opt.flags, "restart rebase after having resolved a merge conflict", REBASE_CONTINUE),
+ OPT_BIT (0, "skip", &opt.flags, "restart rebase by skipping the current patch", REBASE_SKIP),
+ OPT_BIT (0, "abort", &opt.flags, "restore original branch and abort rebase", REBASE_ABORT),
+ OPT_END()
+ };
+
+ unsigned char upstream_sha1[20], branch_sha1[20], onto_sha1[20], head_sha1[20];
+ struct commit *upstream_cmt, *branch_cmt, *onto_cmt, *head_cmt;
+ const char *argv_checkout_onto[] = { "checkout", "-q", NULL /* placeholder */, NULL };
+ int (*rebase_func)(const struct rebase_opt *, struct object *,
+ struct object *, struct object *,
+ const char *, const char *);
+ const char *switch_to = NULL;
+ struct commit_list *mb_cmts;
+ struct child_process cp;
+ struct rev_info revs;
+ struct stat st;
+ char *head_name;
+ char *buf;
+ int do_merge;
+
+ if (!stat(git_path("rebase-apply/applying"), &st) && S_ISREG(st.st_mode))
+ die("It looks like git-am is in progress. Cannot rebase.");
+
+ setenv("GIT_REFLOG_ACTION", "rebase", 1);
+ memset(&opt, 0, sizeof(opt));
+ opt.context = -1;
+ git_config(rebase_config, &opt);
+
+ run_interactive(argc, argv);
+
+ if (argc == 1) {
+ int rebase_merge_exists = !stat(git_path("rebase-merge"), &st) && S_ISDIR(st.st_mode);
+ int rebase_apply_exists = !stat(git_path("rebase-apply"), &st) && S_ISDIR(st.st_mode);
+ if (rebase_merge_exists || rebase_apply_exists) {
+ if (rebase_merge_exists ||
+ (!stat(git_path("rebase-apply/rebasing"), &st) && S_ISREG(st.st_mode)))
+ die("A rebase is in progress, try --continue, --skip or --abort.");
+ die("No arguments given and %s already exists.", git_path("rebase-apply"));
+ }
+ else
+ usage_with_options(rebase_usage, options);
+ }
+
+ argc = parse_options(argc, argv, options, rebase_usage, 0);
+
+ do_merge = (opt.flags & REBASE_MERGE) || opt.strategy;
+ if (!opt.strategy)
+ opt.strategy = "recursive";
+
+ abort_continue_skip(&opt);
+
+ if (opt.whitespace &&
+ (!strcmp(opt.whitespace, "fix") ||
+ !strcmp(opt.whitespace, "strip")))
+ opt.flags |= REBASE_FORCE;
+
+ if (opt.flags & REBASE_ROOT) {
+ if (argc > 1)
+ die("Invalid argument");
+ if (argc)
+ opt.branch = *argv;
+ if (!opt.onto)
+ die("--root must be used with --onto");
+ }
+ else {
+ /* without --root only takes either 1 or 2 arguments */
+ if (argc == 0 || argc > 2 ||
+ (argc == 2 && (opt.flags & REBASE_ROOT)))
+ die("Invalid argument");
+ if (argc) {
+ opt.upstream = *argv++;
+ if (argc)
+ opt.branch = *argv;
+ }
+ }
+
+ /* No rebase ongoing */
+ if (do_merge) {
+ if (!stat(git_path("rebase-merge"), &st) && S_ISREG(st.st_mode))
+ die("previous rebase directory rebase-merge still exists."
+ "Try git rebase (--continue | --abort | --skip)");
+ }
+ else {
+ if (!mkdir(git_path("rebase-apply"), 0700))
+ rmdir(git_path("rebase-apply"));
+ else
+ die("It seems that I cannot create a rebase-apply directory, and\n"
+ "I wonder if you are in the middle of the patch application or another\n"
+ "rebase. If that is not the case, please\n"
+ "\trm -fr \"%s\"/rebase_apply\n"
+ "and run me again. I am stopping in case you still have something\n"
+ "valuable there.",
+ get_git_dir());
+ }
+
+ /* worktree clean */
+ if (read_cache() < 0)
+ die("Could not read the index");
+ if (refresh_cache(REFRESH_IGNORE_SUBMODULES))
+ die("cannot rebase: you have unstaged changes");
+
+ /* cache clean */
+ init_revisions(&revs, NULL);
+ revs.abbrev = 0;
+ revs.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+ revs.diffopt.format_callback = die_dirty_cache;
+ DIFF_OPT_SET(&revs.diffopt, IGNORE_SUBMODULES);
+ DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
+ if (get_sha1("HEAD", head_sha1))
+ die("Could not resolve HEAD");
+ head_cmt = lookup_commit(head_sha1);
+ add_pending_object(&revs, &head_cmt->object, NULL);
+ run_diff_index(&revs, 1);
+
+ if (!(opt.flags & REBASE_NO_VERIFY) &&
+ run_hook(NULL, "pre-rebase", opt.flags & REBASE_ROOT ? "--root" : opt.upstream, opt.branch, NULL)) {
+ die("The pre-rebase hook refused to rebase.");
+ }
+
+ /* commit validation */
+ if (!(opt.flags & REBASE_ROOT))
+ get_commit(opt.upstream, upstream_sha1, &upstream_cmt);
+ get_commit("HEAD", head_sha1, &head_cmt);
+ if (opt.onto)
+ get_commit(opt.onto, onto_sha1, &onto_cmt);
+ else {
+ opt.onto = opt.upstream;
+ memcpy(onto_sha1, upstream_sha1, 20);
+ onto_cmt = upstream_cmt;
+ }
+
+ if (opt.branch) {
+ struct strbuf sb = STRBUF_INIT;
+ const char *full_ref;
+ int type;
+
+ switch_to = opt.branch;
+ strbuf_addf(&sb, "refs/heads/%s", opt.branch);
+ full_ref = resolve_ref(sb.buf, branch_sha1, 1, &type);
+ if (full_ref)
+ head_name = xstrdup(full_ref);
+ else {
+ if (!get_sha1(opt.branch, branch_sha1))
+ head_name = xstrdup("detached HEAD");
+ else
+ die("Failed to parse %s", opt.branch);
+ }
+ strbuf_release(&sb);
+ }
+ else {
+ const char *full_ref;
+ int type;
+
+ opt.branch = "HEAD";
+ full_ref = resolve_ref(opt.branch, branch_sha1, 1, &type);
+ if (full_ref) {
+ head_name = xstrdup(full_ref);
+ if (!strncmp(full_ref, "refs/heads/", 11))
+ opt.branch = head_name+11;
+ }
+ else {
+ head_name = xstrdup("detached HEAD");
+ if (get_sha1(opt.branch, branch_sha1))
+ die("Could not resolve HEAD");
+ }
+ }
+
+ branch_cmt = lookup_commit(branch_sha1);
+ if (!branch_cmt)
+ die("Failed to lookup commit for %s", branch_sha1);
+
+ memset(&cp, 0, sizeof(cp));
+ cp.git_cmd = 1;
+
+ /*
+ * Now we are rebasing commits upstream_sha1..branch_sha1
+ * (or with --root, everything leading up to branch_sha1)
+ * on top of onto_sha1
+ */
+
+ /*
+ * Check if we are already based on onto_sha1 with linear history,
+ * but this should be done only when upstream and onto are the same.
+ */
+ if (!(opt.flags & REBASE_ROOT) && !memcmp(upstream_sha1, onto_sha1, 20)) {
+ struct commit *cmt = branch_cmt;
+ while (1) {
+ if (parse_commit(cmt))
+ die("Could not parse commit");
+
+ if (!cmt->parents || cmt->parents->next)
+ break;
+
+ if (cmt->parents->item != onto_cmt) {
+ cmt = cmt->parents->item;
+ continue;
+ }
+
+ if (opt.flags & REBASE_FORCE) {
+ printf("Current branch %s is up to date, rebase forced.\n", opt.branch);
+ break;
+ }
+ if (switch_to) {
+ const char *argv_checkout[] = { "checkout", switch_to, NULL };
+ cp.argv = argv_checkout;
+ if (run_command(&cp))
+ die("Failed to switch to branch %s", switch_to);
+ }
+ fprintf(stderr, "Current branch %s is up to date.\n", opt.branch);
+ return 0;
+ }
+ }
+
+ /* Detach HEAD and reset the tree */
+ printf("First, rewinding head to replay your work on top of it...\n");
+ buf = xmalloc(43);
+ memcpy(buf, sha1_to_hex(onto_sha1), 40);
+ buf[40] = '^';
+ buf[41] = '0';
+ buf[42] = '\0';
+ argv_checkout_onto[2] = buf;
+ cp.argv = argv_checkout_onto;
+ if (run_command(&cp))
+ die("could not detach HEAD");
+ update_ref(NULL, "ORIG_HEAD", branch_sha1, NULL, REF_NODEREF, DIE_ON_ERR);
+
+ /* diffstat */
+ mb_cmts = get_merge_bases(onto_cmt, branch_cmt, 0);
+ if (mb_cmts && (opt.flags & REBASE_STAT)) {
+ struct rev_info diffrev;
+ struct commit *mb_cmt = mb_cmts->item;
+
+ if (opt.flags & REBASE_VERBOSE)
+ printf("Changes from %s to %s:\n", sha1_to_hex(mb_cmt->object.sha1), opt.onto);
+ init_revisions(&diffrev, NULL);
+ diffrev.abbrev = 0;
+ DIFF_OPT_SET(&diffrev.diffopt, RECURSIVE);
+ diffrev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY;
+ if (parse_commit(mb_cmt) || parse_commit(onto_cmt))
+ die("Could not parse commits");
+ diff_tree_sha1(mb_cmt->tree->object.sha1, onto_cmt->tree->object.sha1, "", &diffrev.diffopt);
+ log_tree_diff_flush(&diffrev);
+ }
+
+ /*
+ * If the opt.onto is a proper descendant of the tip of the branch,
+ * then we just fast forward it.
+ */
+ if (mb_cmts && mb_cmts->item == branch_cmt) {
+ fprintf(stderr, "Fast-forwarded %s to %s", opt.branch, opt.onto);
+ move_to_original_branch(onto_sha1, branch_sha1, head_name);
+ return 0;
+ }
+
+ /*
+ * if --root, revision range will be onto..orig_head
+ * otherwise upstream..orig_head
+ */
+
+ rebase_func = do_merge ? cmd_merge_rebase : cmd_apply_rebase;
+ return rebase_func(&opt, &onto_cmt->object,
+ opt.flags & REBASE_ROOT ? &onto_cmt->object : &upstream_cmt->object,
+ &branch_cmt->object, opt.onto, head_name);
+}