setup: set env $GIT_WORK_TREE when work tree is set, like $GIT_DIR
[gitweb.git] / sequencer.c
index bad5077911667e11e522043ae09875489d4591c4..bbaddcb05af3d720d8efd1d1d7c96ce1f9a35039 100644 (file)
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
 const char sign_off_header[] = "Signed-off-by: ";
+static const char cherry_picked_prefix[] = "(cherry picked from commit ";
+
+static int is_rfc2822_line(const char *buf, int len)
+{
+       int i;
+
+       for (i = 0; i < len; i++) {
+               int ch = buf[i];
+               if (ch == ':')
+                       return 1;
+               if (!isalnum(ch) && ch != '-')
+                       break;
+       }
+
+       return 0;
+}
+
+static int is_cherry_picked_from_line(const char *buf, int len)
+{
+       /*
+        * We only care that it looks roughly like (cherry picked from ...)
+        */
+       return len > strlen(cherry_picked_prefix) + 1 &&
+               starts_with(buf, cherry_picked_prefix) && buf[len - 1] == ')';
+}
+
+/*
+ * Returns 0 for non-conforming footer
+ * Returns 1 for conforming footer
+ * Returns 2 when sob exists within conforming footer
+ * Returns 3 when sob exists within conforming footer as last entry
+ */
+static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,
+       int ignore_footer)
+{
+       char prev;
+       int i, k;
+       int len = sb->len - ignore_footer;
+       const char *buf = sb->buf;
+       int found_sob = 0;
+
+       /* footer must end with newline */
+       if (!len || buf[len - 1] != '\n')
+               return 0;
+
+       prev = '\0';
+       for (i = len - 1; i > 0; i--) {
+               char ch = buf[i];
+               if (prev == '\n' && ch == '\n') /* paragraph break */
+                       break;
+               prev = ch;
+       }
+
+       /* require at least one blank line */
+       if (prev != '\n' || buf[i] != '\n')
+               return 0;
+
+       /* advance to start of last paragraph */
+       while (i < len - 1 && buf[i] == '\n')
+               i++;
+
+       for (; i < len; i = k) {
+               int found_rfc2822;
+
+               for (k = i; k < len && buf[k] != '\n'; k++)
+                       ; /* do nothing */
+               k++;
+
+               found_rfc2822 = is_rfc2822_line(buf + i, k - i - 1);
+               if (found_rfc2822 && sob &&
+                   !strncmp(buf + i, sob->buf, sob->len))
+                       found_sob = k;
+
+               if (!(found_rfc2822 ||
+                     is_cherry_picked_from_line(buf + i, k - i - 1)))
+                       return 0;
+       }
+       if (found_sob == i)
+               return 3;
+       if (found_sob)
+               return 2;
+       return 1;
+}
 
 static void remove_sequencer_state(void)
 {
@@ -33,39 +116,23 @@ static const char *action_name(const struct replay_opts *opts)
        return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick";
 }
 
-static char *get_encoding(const char *message);
-
 struct commit_message {
        char *parent_label;
        const char *label;
        const char *subject;
-       char *reencoded_message;
        const char *message;
 };
 
 static int get_message(struct commit *commit, struct commit_message *out)
 {
-       const char *encoding;
        const char *abbrev, *subject;
        int abbrev_len, subject_len;
        char *q;
 
-       if (!commit->buffer)
-               return -1;
-       encoding = get_encoding(commit->buffer);
-       if (!encoding)
-               encoding = "UTF-8";
        if (!git_commit_encoding)
                git_commit_encoding = "UTF-8";
 
-       out->reencoded_message = NULL;
-       out->message = commit->buffer;
-       if (same_encoding(encoding, git_commit_encoding))
-               out->reencoded_message = reencode_string(commit->buffer,
-                                       git_commit_encoding, encoding);
-       if (out->reencoded_message)
-               out->message = out->reencoded_message;
-
+       out->message = logmsg_reencode(commit, NULL, git_commit_encoding);
        abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
        abbrev_len = strlen(abbrev);
 
@@ -84,29 +151,10 @@ static int get_message(struct commit *commit, struct commit_message *out)
        return 0;
 }
 
-static void free_message(struct commit_message *msg)
+static void free_message(struct commit *commit, struct commit_message *msg)
 {
        free(msg->parent_label);
-       free(msg->reencoded_message);
-}
-
-static char *get_encoding(const char *message)
-{
-       const char *p = message, *eol;
-
-       while (*p && *p != '\n') {
-               for (eol = p + 1; *eol && *eol != '\n'; eol++)
-                       ; /* do nothing */
-               if (!prefixcmp(p, "encoding ")) {
-                       char *result = xmalloc(eol - 8 - p);
-                       strlcpy(result, p + 9, eol - 8 - p);
-                       return result;
-               }
-               p = eol;
-               if (*p == '\n')
-                       p++;
-       }
-       return NULL;
+       unuse_commit_buffer(commit, msg->message);
 }
 
 static void write_cherry_pick_head(struct commit *commit, const char *pseudoref)
@@ -187,15 +235,21 @@ static int error_dirty_index(struct replay_opts *opts)
 }
 
 static int fast_forward_to(const unsigned char *to, const unsigned char *from,
-                          int unborn)
+                       int unborn, struct replay_opts *opts)
 {
        struct ref_lock *ref_lock;
+       struct strbuf sb = STRBUF_INIT;
+       int ret;
 
        read_cache();
        if (checkout_fast_forward(from, to, 1))
                exit(1); /* the callee should have complained already */
-       ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from, 0);
-       return write_ref_sha1(ref_lock, to, "cherry-pick");
+       ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from,
+                                          0, NULL);
+       strbuf_addf(&sb, "%s: fast-forward", action_name(opts));
+       ret = write_ref_sha1(ref_lock, to, sb.buf);
+       strbuf_release(&sb);
+       return ret;
 }
 
 static int do_recursive_merge(struct commit *base, struct commit *next,
@@ -237,13 +291,13 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        rollback_lock_file(&index_lock);
 
        if (opts->signoff)
-               append_signoff(msgbuf, 0);
+               append_signoff(msgbuf, 0, 0);
 
        if (!clean) {
                int i;
                strbuf_addstr(msgbuf, "\nConflicts:\n");
                for (i = 0; i < active_nr;) {
-                       struct cache_entry *ce = active_cache[i++];
+                       const struct cache_entry *ce = active_cache[i++];
                        if (ce_stage(ce)) {
                                strbuf_addch(msgbuf, '\t');
                                strbuf_addstr(msgbuf, ce->name);
@@ -283,8 +337,9 @@ static int is_index_unchanged(void)
                active_cache_tree = cache_tree();
 
        if (!cache_tree_fully_valid(active_cache_tree))
-               if (cache_tree_update(active_cache_tree, active_cache,
-                                 active_nr, 0))
+               if (cache_tree_update(active_cache_tree,
+                                     (const struct cache_entry * const *)active_cache,
+                                     active_nr, 0))
                        return error(_("Unable to update cache tree\n"));
 
        return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1);
@@ -302,11 +357,18 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
 {
        struct argv_array array;
        int rc;
+       char *gpg_sign;
 
        argv_array_init(&array);
        argv_array_push(&array, "commit");
        argv_array_push(&array, "-n");
 
+       if (opts->gpg_sign) {
+               gpg_sign = xmalloc(3 + strlen(opts->gpg_sign));
+               sprintf(gpg_sign, "-S%s", opts->gpg_sign);
+               argv_array_push(&array, gpg_sign);
+               free(gpg_sign);
+       }
        if (opts->signoff)
                argv_array_push(&array, "-s");
        if (!opts->edit) {
@@ -388,10 +450,10 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
        unsigned char head[20];
        struct commit *base, *next, *parent;
        const char *base_label, *next_label;
-       struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
+       struct commit_message msg = { NULL, NULL, NULL, NULL };
        char *defmsg = NULL;
        struct strbuf msgbuf = STRBUF_INIT;
-       int res, unborn = 0;
+       int res, unborn = 0, allow;
 
        if (opts->no_commit) {
                /*
@@ -440,7 +502,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
        if (opts->allow_ff &&
            ((parent && !hashcmp(parent->object.sha1, head)) ||
             (!parent && unborn)))
-            return fast_forward_to(commit->object.sha1, head, unborn);
+               return fast_forward_to(commit->object.sha1, head, unborn, opts);
 
        if (parent && parse_commit(parent) < 0)
                /* TRANSLATORS: The first %s will be "revert" or
@@ -496,7 +558,9 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
                }
 
                if (opts->record_origin) {
-                       strbuf_addstr(&msgbuf, "(cherry picked from commit ");
+                       if (!has_conforming_footer(&msgbuf, NULL, 0))
+                               strbuf_addch(&msgbuf, '\n');
+                       strbuf_addstr(&msgbuf, cherry_picked_prefix);
                        strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
                        strbuf_addstr(&msgbuf, ")\n");
                }
@@ -539,15 +603,19 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
                      msg.subject);
                print_advice(res == 1, opts);
                rerere(opts->allow_rerere_auto);
-       } else {
-               int allow = allow_empty(opts, commit);
-               if (allow < 0)
-                       return allow;
-               if (!opts->no_commit)
-                       res = run_git_commit(defmsg, opts, allow);
+               goto leave;
+       }
+
+       allow = allow_empty(opts, commit);
+       if (allow < 0) {
+               res = allow;
+               goto leave;
        }
+       if (!opts->no_commit)
+               res = run_git_commit(defmsg, opts, allow);
 
-       free_message(&msg);
+leave:
+       free_message(commit, &msg);
        free(defmsg);
 
        return res;
@@ -594,10 +662,12 @@ static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
        int subject_len;
 
        for (cur = todo_list; cur; cur = cur->next) {
+               const char *commit_buffer = get_commit_buffer(cur->item, NULL);
                sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV);
-               subject_len = find_commit_subject(cur->item->buffer, &subject);
+               subject_len = find_commit_subject(commit_buffer, &subject);
                strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev,
                        subject_len, subject);
+               unuse_commit_buffer(cur->item, commit_buffer);
        }
        return 0;
 }
@@ -609,10 +679,10 @@ static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *
        char *end_of_object_name;
        int saved, status, padding;
 
-       if (!prefixcmp(bol, "pick")) {
+       if (starts_with(bol, "pick")) {
                action = REPLAY_PICK;
                bol += strlen("pick");
-       } else if (!prefixcmp(bol, "revert")) {
+       } else if (starts_with(bol, "revert")) {
                action = REPLAY_REVERT;
                bol += strlen("revert");
        } else
@@ -712,6 +782,8 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
                opts->mainline = git_config_int(key, value);
        else if (!strcmp(key, "options.strategy"))
                git_config_string(&opts->strategy, key, value);
+       else if (!strcmp(key, "options.gpg-sign"))
+               git_config_string(&opts->gpg_sign, key, value);
        else if (!strcmp(key, "options.strategy-option")) {
                ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
                opts->xopts[opts->xopts_nr++] = xstrdup(value);
@@ -885,6 +957,8 @@ static void save_opts(struct replay_opts *opts)
        }
        if (opts->strategy)
                git_config_set_in_file(opts_file, "options.strategy", opts->strategy);
+       if (opts->gpg_sign)
+               git_config_set_in_file(opts_file, "options.gpg-sign", opts->gpg_sign);
        if (opts->xopts) {
                int i;
                for (i = 0; i < opts->xopts_nr; i++)
@@ -962,6 +1036,7 @@ int sequencer_pick_revisions(struct replay_opts *opts)
 {
        struct commit_list *todo_list = NULL;
        unsigned char sha1[20];
+       int i;
 
        if (opts->subcommand == REPLAY_NONE)
                assert(opts->revs);
@@ -982,6 +1057,23 @@ int sequencer_pick_revisions(struct replay_opts *opts)
        if (opts->subcommand == REPLAY_CONTINUE)
                return sequencer_continue(opts);
 
+       for (i = 0; i < opts->revs->pending.nr; i++) {
+               unsigned char sha1[20];
+               const char *name = opts->revs->pending.objects[i].name;
+
+               /* This happens when using --stdin. */
+               if (!strlen(name))
+                       continue;
+
+               if (!get_sha1(name, sha1)) {
+                       if (!lookup_commit_reference_gently(sha1, 1)) {
+                               enum object_type type = sha1_object_info(sha1, NULL);
+                               die(_("%s: can't cherry-pick a %s"), name, typename(type));
+                       }
+               } else
+                       die(_("%s: bad revision"), name);
+       }
+
        /*
         * If we were called as "git cherry-pick <commit>", just
         * cherry-pick/revert it, set CHERRY_PICK_HEAD /
@@ -1021,62 +1113,67 @@ int sequencer_pick_revisions(struct replay_opts *opts)
        return pick_commits(todo_list, opts);
 }
 
-static int ends_rfc2822_footer(struct strbuf *sb, int ignore_footer)
-{
-       int ch;
-       int hit = 0;
-       int i, j, k;
-       int len = sb->len - ignore_footer;
-       int first = 1;
-       const char *buf = sb->buf;
-
-       for (i = len - 1; i > 0; i--) {
-               if (hit && buf[i] == '\n')
-                       break;
-               hit = (buf[i] == '\n');
-       }
-
-       while (i < len - 1 && buf[i] == '\n')
-               i++;
-
-       for (; i < len; i = k) {
-               for (k = i; k < len && buf[k] != '\n'; k++)
-                       ; /* do nothing */
-               k++;
-
-               if ((buf[k] == ' ' || buf[k] == '\t') && !first)
-                       continue;
-
-               first = 0;
-
-               for (j = 0; i + j < len; j++) {
-                       ch = buf[i + j];
-                       if (ch == ':')
-                               break;
-                       if (isalnum(ch) ||
-                           (ch == '-'))
-                               continue;
-                       return 0;
-               }
-       }
-       return 1;
-}
-
-void append_signoff(struct strbuf *msgbuf, int ignore_footer)
+void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
 {
+       unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;
        struct strbuf sob = STRBUF_INIT;
-       int i;
+       int has_footer;
 
        strbuf_addstr(&sob, sign_off_header);
        strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
                                getenv("GIT_COMMITTER_EMAIL")));
        strbuf_addch(&sob, '\n');
-       for (i = msgbuf->len - 1 - ignore_footer; i > 0 && msgbuf->buf[i - 1] != '\n'; i--)
-               ; /* do nothing */
-       if (prefixcmp(msgbuf->buf + i, sob.buf)) {
-               if (!i || !ends_rfc2822_footer(msgbuf, ignore_footer))
-                       strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, "\n", 1);
-               strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, sob.buf, sob.len);
+
+       /*
+        * If the whole message buffer is equal to the sob, pretend that we
+        * found a conforming footer with a matching sob
+        */
+       if (msgbuf->len - ignore_footer == sob.len &&
+           !strncmp(msgbuf->buf, sob.buf, sob.len))
+               has_footer = 3;
+       else
+               has_footer = has_conforming_footer(msgbuf, &sob, ignore_footer);
+
+       if (!has_footer) {
+               const char *append_newlines = NULL;
+               size_t len = msgbuf->len - ignore_footer;
+
+               if (!len) {
+                       /*
+                        * The buffer is completely empty.  Leave foom for
+                        * the title and body to be filled in by the user.
+                        */
+                       append_newlines = "\n\n";
+               } else if (msgbuf->buf[len - 1] != '\n') {
+                       /*
+                        * Incomplete line.  Complete the line and add a
+                        * blank one so that there is an empty line between
+                        * the message body and the sob.
+                        */
+                       append_newlines = "\n\n";
+               } else if (len == 1) {
+                       /*
+                        * Buffer contains a single newline.  Add another
+                        * so that we leave room for the title and body.
+                        */
+                       append_newlines = "\n";
+               } else if (msgbuf->buf[len - 2] != '\n') {
+                       /*
+                        * Buffer ends with a single newline.  Add another
+                        * so that there is an empty line between the message
+                        * body and the sob.
+                        */
+                       append_newlines = "\n";
+               } /* else, the buffer already ends with two newlines. */
+
+               if (append_newlines)
+                       strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0,
+                               append_newlines, strlen(append_newlines));
        }
+
+       if (has_footer != 3 && (!no_dup_sob || has_footer != 2))
+               strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0,
+                               sob.buf, sob.len);
+
        strbuf_release(&sob);
 }