]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'mp/rebase-label-length-limit' into maint-2.42
authorJunio C Hamano <gitster@pobox.com>
Thu, 2 Nov 2023 07:53:14 +0000 (16:53 +0900)
committerJunio C Hamano <gitster@pobox.com>
Thu, 2 Nov 2023 07:53:14 +0000 (16:53 +0900)
Overly long label names used in the sequencer machinery are now
chopped to fit under filesystem limitation.

* mp/rebase-label-length-limit:
  rebase: allow overriding the maximal length of the generated labels
  sequencer: truncate labels to accommodate loose refs

1  2 
git-compat-util.h
sequencer.c

diff --combined git-compat-util.h
index d32aa754ae14f518d286a8b2205fd0e1d23b5804,e1f7e15d9e6679ef4d171fed0dffd08cb0fff126..3e7a59b5ff108dddce111cb372b6c846c093b4be
@@@ -422,6 -422,10 +422,10 @@@ char *gitdirname(char *)
  #define PATH_MAX 4096
  #endif
  
+ #ifndef NAME_MAX
+ #define NAME_MAX 255
+ #endif
  typedef uintmax_t timestamp_t;
  #define PRItime PRIuMAX
  #define parse_timestamp strtoumax
  #endif
  
  #ifndef platform_core_config
 +struct config_context;
  static inline int noop_core_config(const char *var UNUSED,
                                   const char *value UNUSED,
 +                                 const struct config_context *ctx UNUSED,
                                   void *cb UNUSED)
  {
        return 0;
@@@ -627,7 -629,9 +631,7 @@@ static inline int git_has_dir_sep(cons
  
  #include "compat/bswap.h"
  
 -#include "wildmatch.h"
 -
 -struct strbuf;
 +#include "wrapper.h"
  
  /* General helper functions */
  NORETURN void usage(const char *err);
@@@ -679,6 -683,9 +683,6 @@@ void set_warn_routine(report_fn routine
  report_fn get_warn_routine(void);
  void set_die_is_recursing_routine(int (*routine)(void));
  
 -int starts_with(const char *str, const char *prefix);
 -int istarts_with(const char *str, const char *prefix);
 -
  /*
   * If the string "str" begins with the string found in "prefix", return 1.
   * The "out" parameter is set to "str + strlen(prefix)" (i.e., to the point in
@@@ -707,6 -714,29 +711,6 @@@ static inline int skip_prefix(const cha
        return 0;
  }
  
 -/*
 - * If the string "str" is the same as the string in "prefix", then the "arg"
 - * parameter is set to the "def" parameter and 1 is returned.
 - * If the string "str" begins with the string found in "prefix" and then a
 - * "=" sign, then the "arg" parameter is set to "str + strlen(prefix) + 1"
 - * (i.e., to the point in the string right after the prefix and the "=" sign),
 - * and 1 is returned.
 - *
 - * Otherwise, return 0 and leave "arg" untouched.
 - *
 - * When we accept both a "--key" and a "--key=<val>" option, this function
 - * can be used instead of !strcmp(arg, "--key") and then
 - * skip_prefix(arg, "--key=", &arg) to parse such an option.
 - */
 -int skip_to_optional_arg_default(const char *str, const char *prefix,
 -                               const char **arg, const char *def);
 -
 -static inline int skip_to_optional_arg(const char *str, const char *prefix,
 -                                     const char **arg)
 -{
 -      return skip_to_optional_arg_default(str, prefix, arg, "");
 -}
 -
  /*
   * Like skip_prefix, but promises never to read past "len" bytes of the input
   * buffer, and returns the remaining number of bytes in "out" via "outlen".
@@@ -751,6 -781,12 +755,6 @@@ static inline int strip_suffix(const ch
        return strip_suffix_mem(str, len, suffix);
  }
  
 -static inline int ends_with(const char *str, const char *suffix)
 -{
 -      size_t len;
 -      return strip_suffix(str, suffix, &len);
 -}
 -
  #define SWAP(a, b) do {                                               \
        void *_swap_a_ptr = &(a);                               \
        void *_swap_b_ptr = &(b);                               \
@@@ -1047,6 -1083,36 +1051,6 @@@ static inline int cast_size_t_to_int(si
  # define xalloca(size)      (xmalloc(size))
  # define xalloca_free(p)    (free(p))
  #endif
 -char *xstrdup(const char *str);
 -void *xmalloc(size_t size);
 -void *xmallocz(size_t size);
 -void *xmallocz_gently(size_t size);
 -void *xmemdupz(const void *data, size_t len);
 -char *xstrndup(const char *str, size_t len);
 -void *xrealloc(void *ptr, size_t size);
 -void *xcalloc(size_t nmemb, size_t size);
 -void xsetenv(const char *name, const char *value, int overwrite);
 -void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
 -const char *mmap_os_err(void);
 -void *xmmap_gently(void *start, size_t length, int prot, int flags, int fd, off_t offset);
 -int xopen(const char *path, int flags, ...);
 -ssize_t xread(int fd, void *buf, size_t len);
 -ssize_t xwrite(int fd, const void *buf, size_t len);
 -ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
 -int xdup(int fd);
 -FILE *xfopen(const char *path, const char *mode);
 -FILE *xfdopen(int fd, const char *mode);
 -int xmkstemp(char *temp_filename);
 -int xmkstemp_mode(char *temp_filename, int mode);
 -char *xgetcwd(void);
 -FILE *fopen_for_writing(const char *path);
 -FILE *fopen_or_warn(const char *path, const char *mode);
 -
 -/*
 - * Like strncmp, but only return zero if s is NUL-terminated and exactly len
 - * characters long.  If it is not, consider it greater than t.
 - */
 -int xstrncmpz(const char *s, const char *t, size_t len);
  
  /*
   * FREE_AND_NULL(ptr) is like free(ptr) followed by ptr = NULL. Note
@@@ -1136,81 -1202,6 +1140,81 @@@ static inline void move_array(void *dst
  #define FLEXPTR_ALLOC_STR(x, ptrname, str) \
        FLEXPTR_ALLOC_MEM((x), ptrname, (str), strlen(str))
  
 +#define alloc_nr(x) (((x)+16)*3/2)
 +
 +/**
 + * Dynamically growing an array using realloc() is error prone and boring.
 + *
 + * Define your array with:
 + *
 + * - a pointer (`item`) that points at the array, initialized to `NULL`
 + *   (although please name the variable based on its contents, not on its
 + *   type);
 + *
 + * - an integer variable (`alloc`) that keeps track of how big the current
 + *   allocation is, initialized to `0`;
 + *
 + * - another integer variable (`nr`) to keep track of how many elements the
 + *   array currently has, initialized to `0`.
 + *
 + * Then before adding `n`th element to the item, call `ALLOC_GROW(item, n,
 + * alloc)`.  This ensures that the array can hold at least `n` elements by
 + * calling `realloc(3)` and adjusting `alloc` variable.
 + *
 + * ------------
 + * sometype *item;
 + * size_t nr;
 + * size_t alloc
 + *
 + * for (i = 0; i < nr; i++)
 + *    if (we like item[i] already)
 + *            return;
 + *
 + * // we did not like any existing one, so add one
 + * ALLOC_GROW(item, nr + 1, alloc);
 + * item[nr++] = value you like;
 + * ------------
 + *
 + * You are responsible for updating the `nr` variable.
 + *
 + * If you need to specify the number of elements to allocate explicitly
 + * then use the macro `REALLOC_ARRAY(item, alloc)` instead of `ALLOC_GROW`.
 + *
 + * Consider using ALLOC_GROW_BY instead of ALLOC_GROW as it has some
 + * added niceties.
 + *
 + * DO NOT USE any expression with side-effect for 'x', 'nr', or 'alloc'.
 + */
 +#define ALLOC_GROW(x, nr, alloc) \
 +      do { \
 +              if ((nr) > alloc) { \
 +                      if (alloc_nr(alloc) < (nr)) \
 +                              alloc = (nr); \
 +                      else \
 +                              alloc = alloc_nr(alloc); \
 +                      REALLOC_ARRAY(x, alloc); \
 +              } \
 +      } while (0)
 +
 +/*
 + * Similar to ALLOC_GROW but handles updating of the nr value and
 + * zeroing the bytes of the newly-grown array elements.
 + *
 + * DO NOT USE any expression with side-effect for any of the
 + * arguments.
 + */
 +#define ALLOC_GROW_BY(x, nr, increase, alloc) \
 +      do { \
 +              if (increase) { \
 +                      size_t new_nr = nr + (increase); \
 +                      if (new_nr < nr) \
 +                              BUG("negative growth in ALLOC_GROW_BY"); \
 +                      ALLOC_GROW(x, new_nr, alloc); \
 +                      memset((x) + nr, 0, sizeof(*(x)) * (increase)); \
 +                      nr = new_nr; \
 +              } \
 +      } while (0)
 +
  static inline char *xstrdup_or_null(const char *str)
  {
        return str ? xstrdup(str) : NULL;
@@@ -1223,11 -1214,79 +1227,11 @@@ static inline size_t xsize_t(off_t len
        return (size_t) len;
  }
  
 -__attribute__((format (printf, 3, 4)))
 -int xsnprintf(char *dst, size_t max, const char *fmt, ...);
 -
  #ifndef HOST_NAME_MAX
  #define HOST_NAME_MAX 256
  #endif
  
 -int xgethostname(char *buf, size_t len);
 -
 -/* in ctype.c, for kwset users */
 -extern const unsigned char tolower_trans_tbl[256];
 -
 -/* Sane ctype - no locale, and works with signed chars */
 -#undef isascii
 -#undef isspace
 -#undef isdigit
 -#undef isalpha
 -#undef isalnum
 -#undef isprint
 -#undef islower
 -#undef isupper
 -#undef tolower
 -#undef toupper
 -#undef iscntrl
 -#undef ispunct
 -#undef isxdigit
 -
 -extern const unsigned char sane_ctype[256];
 -extern const signed char hexval_table[256];
 -#define GIT_SPACE 0x01
 -#define GIT_DIGIT 0x02
 -#define GIT_ALPHA 0x04
 -#define GIT_GLOB_SPECIAL 0x08
 -#define GIT_REGEX_SPECIAL 0x10
 -#define GIT_PATHSPEC_MAGIC 0x20
 -#define GIT_CNTRL 0x40
 -#define GIT_PUNCT 0x80
 -#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)
 -#define isascii(x) (((x) & ~0x7f) == 0)
 -#define isspace(x) sane_istest(x,GIT_SPACE)
 -#define isdigit(x) sane_istest(x,GIT_DIGIT)
 -#define isalpha(x) sane_istest(x,GIT_ALPHA)
 -#define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)
 -#define isprint(x) ((x) >= 0x20 && (x) <= 0x7e)
 -#define islower(x) sane_iscase(x, 1)
 -#define isupper(x) sane_iscase(x, 0)
 -#define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL)
 -#define is_regex_special(x) sane_istest(x,GIT_GLOB_SPECIAL | GIT_REGEX_SPECIAL)
 -#define iscntrl(x) (sane_istest(x,GIT_CNTRL))
 -#define ispunct(x) sane_istest(x, GIT_PUNCT | GIT_REGEX_SPECIAL | \
 -              GIT_GLOB_SPECIAL | GIT_PATHSPEC_MAGIC)
 -#define isxdigit(x) (hexval_table[(unsigned char)(x)] != -1)
 -#define tolower(x) sane_case((unsigned char)(x), 0x20)
 -#define toupper(x) sane_case((unsigned char)(x), 0)
 -#define is_pathspec_magic(x) sane_istest(x,GIT_PATHSPEC_MAGIC)
 -
 -static inline int sane_case(int x, int high)
 -{
 -      if (sane_istest(x, GIT_ALPHA))
 -              x = (x & ~0x20) | high;
 -      return x;
 -}
 -
 -static inline int sane_iscase(int x, int is_lower)
 -{
 -      if (!sane_istest(x, GIT_ALPHA))
 -              return 0;
 -
 -      if (is_lower)
 -              return (x & 0x20) != 0;
 -      else
 -              return (x & 0x20) == 0;
 -}
 +#include "sane-ctype.h"
  
  /*
   * Like skip_prefix, but compare case-insensitively. Note that the comparison
@@@ -1404,6 -1463,72 +1408,6 @@@ void bug_fl(const char *file, int line
  #endif
  #endif
  
 -enum fsync_action {
 -      FSYNC_WRITEOUT_ONLY,
 -      FSYNC_HARDWARE_FLUSH
 -};
 -
 -/*
 - * Issues an fsync against the specified file according to the specified mode.
 - *
 - * FSYNC_WRITEOUT_ONLY attempts to use interfaces available on some operating
 - * systems to flush the OS cache without issuing a flush command to the storage
 - * controller. If those interfaces are unavailable, the function fails with
 - * ENOSYS.
 - *
 - * FSYNC_HARDWARE_FLUSH does an OS writeout and hardware flush to ensure that
 - * changes are durable. It is not expected to fail.
 - */
 -int git_fsync(int fd, enum fsync_action action);
 -
 -/*
 - * Writes out trace statistics for fsync using the trace2 API.
 - */
 -void trace_git_fsync_stats(void);
 -
 -/*
 - * Preserves errno, prints a message, but gives no warning for ENOENT.
 - * Returns 0 on success, which includes trying to unlink an object that does
 - * not exist.
 - */
 -int unlink_or_warn(const char *path);
 - /*
 -  * Tries to unlink file.  Returns 0 if unlink succeeded
 -  * or the file already didn't exist.  Returns -1 and
 -  * appends a message to err suitable for
 -  * 'error("%s", err->buf)' on error.
 -  */
 -int unlink_or_msg(const char *file, struct strbuf *err);
 -/*
 - * Preserves errno, prints a message, but gives no warning for ENOENT.
 - * Returns 0 on success, which includes trying to remove a directory that does
 - * not exist.
 - */
 -int rmdir_or_warn(const char *path);
 -/*
 - * Calls the correct function out of {unlink,rmdir}_or_warn based on
 - * the supplied file mode.
 - */
 -int remove_or_warn(unsigned int mode, const char *path);
 -
 -/*
 - * Call access(2), but warn for any error except "missing file"
 - * (ENOENT or ENOTDIR).
 - */
 -#define ACCESS_EACCES_OK (1U << 0)
 -int access_or_warn(const char *path, int mode, unsigned flag);
 -int access_or_die(const char *path, int mode, unsigned flag);
 -
 -/* Warn on an inaccessible file if errno indicates this is an error */
 -int warn_on_fopen_errors(const char *path);
 -
 -/*
 - * Open with O_NOFOLLOW, or equivalent. Note that the fallback equivalent
 - * may be racy. Do not use this as protection against an attacker who can
 - * simultaneously create paths.
 - */
 -int open_nofollow(const char *path, int flags);
 -
  #ifndef SHELL_PATH
  # define SHELL_PATH "/bin/sh"
  #endif
@@@ -1543,4 -1668,13 +1547,4 @@@ static inline void *container_of_or_nul
        ((uintptr_t)&(ptr)->member - (uintptr_t)(ptr))
  #endif /* !__GNUC__ */
  
 -void sleep_millisec(int millisec);
 -
 -/*
 - * Generate len bytes from the system cryptographically secure PRNG.
 - * Returns 0 on success and -1 on error, setting errno.  The inability to
 - * satisfy the full request is an error.
 - */
 -int csprng_bytes(void *buf, size_t len);
 -
  #endif
diff --combined sequencer.c
index 5e0c15a16b73b3bf04fc76afc2bc2bd1c15f677d,5b9b81ea877bf1dacfa4266b71a58b66701984f9..828efd589deff91a67804951176b689d63a4cf18
@@@ -1,6 -1,7 +1,6 @@@
 -#include "cache.h"
 +#include "git-compat-util.h"
  #include "abspath.h"
  #include "advice.h"
 -#include "alloc.h"
  #include "config.h"
  #include "copy.h"
  #include "environment.h"
@@@ -10,7 -11,7 +10,7 @@@
  #include "dir.h"
  #include "object-file.h"
  #include "object-name.h"
 -#include "object-store.h"
 +#include "object-store-ll.h"
  #include "object.h"
  #include "pager.h"
  #include "commit.h"
  #include "utf8.h"
  #include "cache-tree.h"
  #include "diff.h"
 +#include "path.h"
  #include "revision.h"
  #include "rerere.h"
 +#include "merge.h"
  #include "merge-ort.h"
  #include "merge-ort-wrappers.h"
  #include "refs.h"
 +#include "sparse-index.h"
  #include "strvec.h"
  #include "quote.h"
  #include "trailer.h"
  #include "rebase-interactive.h"
  #include "reset.h"
  #include "branch.h"
 -#include "wrapper.h"
  
  #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
  
+ /*
+  * 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.
+  * Add some margin to stay clear from reaching `NAME_MAX`.
+  */
+ #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 ";
  
@@@ -220,8 -228,7 +229,8 @@@ static struct update_ref_record *init_u
        return rec;
  }
  
 -static int git_sequencer_config(const char *k, const char *v, void *cb)
 +static int git_sequencer_config(const char *k, const char *v,
 +                              const struct config_context *ctx, void *cb)
  {
        struct replay_opts *opts = cb;
        int status;
        if (opts->action == REPLAY_REVERT && !strcmp(k, "revert.reference"))
                opts->commit_use_reference = git_config_bool(k, v);
  
 -      return git_diff_basic_config(k, v, NULL);
 +      return git_diff_basic_config(k, v, ctx, NULL);
  }
  
  void sequencer_init_config(struct replay_opts *opts)
@@@ -662,12 -669,11 +671,12 @@@ void append_conflicts_hint(struct index
        }
  
        strbuf_addch(msgbuf, '\n');
 -      strbuf_commented_addf(msgbuf, "Conflicts:\n");
 +      strbuf_commented_addf(msgbuf, comment_line_char, "Conflicts:\n");
        for (i = 0; i < istate->cache_nr;) {
                const struct cache_entry *ce = istate->cache[i++];
                if (ce_stage(ce)) {
 -                      strbuf_commented_addf(msgbuf, "\t%s\n", ce->name);
 +                      strbuf_commented_addf(msgbuf, comment_line_char,
 +                                            "\t%s\n", ce->name);
                        while (i < istate->cache_nr &&
                               !strcmp(ce->name, istate->cache[i]->name))
                                i++;
@@@ -1146,8 -1152,7 +1155,8 @@@ void cleanup_message(struct strbuf *msg
            cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
                strbuf_setlen(msgbuf, wt_status_locate_end(msgbuf->buf, msgbuf->len));
        if (cleanup_mode != COMMIT_MSG_CLEANUP_NONE)
 -              strbuf_stripspace(msgbuf, cleanup_mode == COMMIT_MSG_CLEANUP_ALL);
 +              strbuf_stripspace(msgbuf,
 +                cleanup_mode == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
  }
  
  /*
@@@ -1178,8 -1183,7 +1187,8 @@@ int template_untouched(const struct str
        if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
                return 0;
  
 -      strbuf_stripspace(&tmpl, cleanup_mode == COMMIT_MSG_CLEANUP_ALL);
 +      strbuf_stripspace(&tmpl,
 +        cleanup_mode == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
        if (!skip_prefix(sb->buf, tmpl.buf, &start))
                start = sb->buf;
        strbuf_release(&tmpl);
@@@ -1551,8 -1555,7 +1560,8 @@@ static int try_to_commit(struct reposit
                cleanup = opts->default_msg_cleanup;
  
        if (cleanup != COMMIT_MSG_CLEANUP_NONE)
 -              strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL);
 +              strbuf_stripspace(msg,
 +                cleanup == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
        if ((flags & EDIT_MSG) && message_is_empty(msg, cleanup)) {
                res = 1; /* run 'git commit' to display error message */
                goto out;
@@@ -1846,7 -1849,7 +1855,7 @@@ static void add_commented_lines(struct 
                s += count;
                len -= count;
        }
 -      strbuf_add_commented_lines(buf, s, len);
 +      strbuf_add_commented_lines(buf, s, len, comment_line_char);
  }
  
  /* Does the current fixup chain contain a squash command? */
@@@ -1945,7 -1948,7 +1954,7 @@@ static int append_squash_message(struc
        strbuf_addf(buf, _(nth_commit_msg_fmt),
                    ++opts->current_fixup_count + 1);
        strbuf_addstr(buf, "\n\n");
 -      strbuf_add_commented_lines(buf, body, commented_len);
 +      strbuf_add_commented_lines(buf, body, commented_len, comment_line_char);
        /* buf->buf may be reallocated so store an offset into the buffer */
        fixup_off = buf->len;
        strbuf_addstr(buf, body + commented_len);
@@@ -2035,8 -2038,7 +2044,8 @@@ static int update_squash_messages(struc
                              _(first_commit_msg_str));
                strbuf_addstr(&buf, "\n\n");
                if (is_fixup_flag(command, flag))
 -                      strbuf_add_commented_lines(&buf, body, strlen(body));
 +                      strbuf_add_commented_lines(&buf, body, strlen(body),
 +                                                 comment_line_char);
                else
                        strbuf_addstr(&buf, body);
  
                strbuf_addf(&buf, _(skip_nth_commit_msg_fmt),
                            ++opts->current_fixup_count + 1);
                strbuf_addstr(&buf, "\n\n");
 -              strbuf_add_commented_lines(&buf, body, strlen(body));
 +              strbuf_add_commented_lines(&buf, body, strlen(body),
 +                                         comment_line_char);
        } else
                return error(_("unknown command: %d"), command);
        repo_unuse_commit_buffer(r, commit, message);
@@@ -2702,7 -2703,7 +2711,7 @@@ int todo_list_parse_insn_buffer(struct 
                if (fixup_okay)
                        ; /* do nothing */
                else if (is_fixup(item->command))
 -                      return error(_("cannot '%s' without a previous commit"),
 +                      res = error(_("cannot '%s' without a previous commit"),
                                command_to_string(item->command));
                else if (!is_noop(item->command))
                        fixup_okay = 1;
@@@ -2889,9 -2890,7 +2898,9 @@@ static int git_config_string_dup(char *
        return 0;
  }
  
 -static int populate_opts_cb(const char *key, const char *value, void *data)
 +static int populate_opts_cb(const char *key, const char *value,
 +                          const struct config_context *ctx,
 +                          void *data)
  {
        struct replay_opts *opts = data;
        int error_flag = 1;
        if (!value)
                error_flag = 0;
        else if (!strcmp(key, "options.no-commit"))
 -              opts->no_commit = git_config_bool_or_int(key, value, &error_flag);
 +              opts->no_commit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.edit"))
 -              opts->edit = git_config_bool_or_int(key, value, &error_flag);
 +              opts->edit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.allow-empty"))
                opts->allow_empty =
 -                      git_config_bool_or_int(key, value, &error_flag);
 +                      git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.allow-empty-message"))
                opts->allow_empty_message =
 -                      git_config_bool_or_int(key, value, &error_flag);
 +                      git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.keep-redundant-commits"))
                opts->keep_redundant_commits =
 -                      git_config_bool_or_int(key, value, &error_flag);
 +                      git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.signoff"))
 -              opts->signoff = git_config_bool_or_int(key, value, &error_flag);
 +              opts->signoff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.record-origin"))
 -              opts->record_origin = git_config_bool_or_int(key, value, &error_flag);
 +              opts->record_origin = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.allow-ff"))
 -              opts->allow_ff = git_config_bool_or_int(key, value, &error_flag);
 +              opts->allow_ff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.mainline"))
 -              opts->mainline = git_config_int(key, value);
 +              opts->mainline = git_config_int(key, value, ctx->kvi);
        else if (!strcmp(key, "options.strategy"))
                git_config_string_dup(&opts->strategy, key, value);
        else if (!strcmp(key, "options.gpg-sign"))
                strvec_push(&opts->xopts, value);
        } else if (!strcmp(key, "options.allow-rerere-auto"))
                opts->allow_rerere_auto =
 -                      git_config_bool_or_int(key, value, &error_flag) ?
 +                      git_config_bool_or_int(key, value, ctx->kvi, &error_flag) ?
                                RERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE;
        else if (!strcmp(key, "options.default-msg-cleanup")) {
                opts->explicit_cleanup = 1;
@@@ -5048,31 -5047,19 +5057,31 @@@ static int commit_staged_changes(struc
                                 * We need to update the squash message to skip
                                 * the latest commit message.
                                 */
 +                              int res = 0;
                                struct commit *commit;
 +                              const char *msg;
                                const char *path = rebase_path_squash_msg();
                                const char *encoding = get_commit_output_encoding();
  
 -                              if (parse_head(r, &commit) ||
 -                                  !(p = repo_logmsg_reencode(r, commit, NULL, encoding)) ||
 -                                  write_message(p, strlen(p), path, 0)) {
 -                                      repo_unuse_commit_buffer(r, commit, p);
 -                                      return error(_("could not write file: "
 +                              if (parse_head(r, &commit))
 +                                      return error(_("could not parse HEAD"));
 +
 +                              p = repo_logmsg_reencode(r, commit, NULL, encoding);
 +                              if (!p)  {
 +                                      res = error(_("could not parse commit %s"),
 +                                                  oid_to_hex(&commit->object.oid));
 +                                      goto unuse_commit_buffer;
 +                              }
 +                              find_commit_subject(p, &msg);
 +                              if (write_message(msg, strlen(msg), path, 0)) {
 +                                      res = error(_("could not write file: "
                                                       "'%s'"), path);
 +                                      goto unuse_commit_buffer;
                                }
 -                              repo_unuse_commit_buffer(r,
 -                                                       commit, p);
 +                      unuse_commit_buffer:
 +                              repo_unuse_commit_buffer(r, commit, p);
 +                              if (res)
 +                                      return res;
                        }
                }
  
@@@ -5352,6 -5339,7 +5361,7 @@@ struct label_state 
        struct oidmap commit2label;
        struct hashmap labels;
        struct strbuf buf;
+       int max_label_length;
  };
  
  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 + state->max_label_length;
  
                /*
                 * Sanitize labels by replacing non-alpha-numeric characters
                 *
                 * 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) {
+                                       if (buf->len + (p - label) > max_len)
+                                               break;
+                                       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-");
@@@ -5485,7 -5495,8 +5517,8 @@@ static int make_script_with_merges(stru
        struct string_entry *entry;
        struct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,
                shown = OIDSET_INIT;
-       struct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };
+       struct label_state state =
+               { OIDMAP_INIT, { NULL }, STRBUF_INIT, GIT_MAX_LABEL_LENGTH };
  
        int abbr = flags & TODO_LIST_ABBREVIATE_CMDS;
        const char *cmd_pick = abbr ? "p" : "pick",
                *cmd_reset = abbr ? "t" : "reset",
                *cmd_merge = abbr ? "m" : "merge";
  
+       git_config_get_int("rebase.maxlabellength", &state.max_label_length);
        oidmap_init(&commit2todo, 0);
        oidmap_init(&state.commit2label, 0);
        hashmap_init(&state.labels, labels_cmp, NULL, 0);