diff --git a/sequencer.c b/sequencer.c index 863f4cb86cc15d..3fcc92f1cdc267 100644 --- a/sequencer.c +++ b/sequencer.c @@ -51,9 +51,13 @@ #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" -#define GIT_MAX_LABEL_LENGTH ((NAME_MAX) - (LOCK_SUFFIX_LEN)) - -#define GIT_MIN(a, b) ((a) < (b) ? (a) : (b)) +/* + * To accommodate common filesystem limitations, where the loose refs' file + * names must not exceed `NAME_MAX`, the labels generated by `git rebase + * --rebase-merges` need to be truncated if the corresponding commit subjects + * are too long. + */ +#define GIT_MAX_LABEL_LENGTH ((NAME_MAX) - (LOCK_SUFFIX_LEN) - 16) static const char sign_off_header[] = "Signed-off-by: "; static const char cherry_picked_prefix[] = "(cherry picked from commit "; @@ -3705,12 +3709,11 @@ static int do_label(struct repository *r, const char *name, int len) struct strbuf msg = STRBUF_INIT; int ret = 0; struct object_id head_oid; - int len_trunc = GIT_MIN(len, GIT_MAX_LABEL_LENGTH); if (len == 1 && *name == '#') return error(_("illegal label name: '%.*s'"), len, name); - strbuf_addf(&ref_name, "refs/rewritten/%.*s", len_trunc, name); + strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name); strbuf_addf(&msg, "rebase (label) '%.*s'", len, name); transaction = ref_store_transaction_begin(refs, &err); @@ -3774,12 +3777,11 @@ static const char *reflog_message(struct replay_opts *opts, static struct commit *lookup_label(struct repository *r, const char *label, int len, struct strbuf *buf) { - int len_trunc = GIT_MIN(len, GIT_MAX_LABEL_LENGTH); struct commit *commit; struct object_id oid; strbuf_reset(buf); - strbuf_addf(buf, "refs/rewritten/%.*s", len_trunc, label); + strbuf_addf(buf, "refs/rewritten/%.*s", len, label); if (!read_ref(buf->buf, &oid)) { commit = lookup_commit_object(r, &oid); } else { @@ -5402,6 +5404,8 @@ static const char *label_oid(struct object_id *oid, const char *label, } } else { struct strbuf *buf = &state->buf; + int label_is_utf8 = 1; /* start with this assumption */ + size_t max_len = buf->len + GIT_MAX_LABEL_LENGTH; /* * Sanitize labels by replacing non-alpha-numeric characters @@ -5410,14 +5414,32 @@ static const char *label_oid(struct object_id *oid, const char *label, * * Note that we retain non-ASCII UTF-8 characters (identified * via the most significant bit). They should be all acceptable - * in file names. We do not validate the UTF-8 here, that's not - * the job of this function. + * in file names. + * + * As we will use the labels as names of (loose) refs, it is + * vital that the name not be longer than the maximum component + * size of the file system (`NAME_MAX`). We are careful to + * truncate the label accordingly, allowing for the `.lock` + * suffix and for the label to be UTF-8 encoded (i.e. we avoid + * truncating in the middle of a character). */ - for (; *label; label++) - if ((*label & 0x80) || isalnum(*label)) + for (; *label && buf->len + 1 < max_len; label++) + if (isalnum(*label) || + (!label_is_utf8 && (*label & 0x80))) strbuf_addch(buf, *label); + else if (*label & 0x80) { + const char *p = label; + + utf8_width(&p, NULL); + if (p) { + strbuf_add(buf, label, p - label); + label = p - 1; + } else { + label_is_utf8 = 0; + strbuf_addch(buf, *label); + } /* avoid leading dash and double-dashes */ - else if (buf->len && buf->buf[buf->len - 1] != '-') + } else if (buf->len && buf->buf[buf->len - 1] != '-') strbuf_addch(buf, '-'); if (!buf->len) { strbuf_addstr(buf, "rev-");