Merge branch 'nd/status-auto-comment-char'
authorJunio C Hamano <gitster@pobox.com>
Fri, 6 Jun 2014 18:36:10 +0000 (11:36 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 6 Jun 2014 18:36:10 +0000 (11:36 -0700)
* nd/status-auto-comment-char:
commit: allow core.commentChar=auto for character auto selection
config: be strict on core.commentChar

1  2 
Documentation/config.txt
builtin/commit.c
cache.h
config.c
environment.c
t/t7502-commit.sh
diff --combined Documentation/config.txt
index a33c087cb4c4487817c51df70b7c03d4bc4d2254,9f3ce06c8734d2fca2752b28192aea9f8a19cf58..2e3c6655f0ab45dbab82e9440179b5a52bd73117
@@@ -489,7 -489,7 +489,7 @@@ core.deltaBaseCacheLimit:
        to avoid unpacking and decompressing frequently used base
        objects multiple times.
  +
 -Default is 16 MiB on all platforms.  This should be reasonable
 +Default is 96 MiB on all platforms.  This should be reasonable
  for all users/operating systems, except on the largest projects.
  You probably do not need to adjust this value.
  +
@@@ -544,6 -544,9 +544,9 @@@ core.commentchar:
        messages consider a line that begins with this character
        commented, and removes them after the editor returns
        (default '#').
+ +
+ If set to "auto", `git-commit` would select a character that is not
+ the beginning character of any line in existing commit messages.
  
  sequence.editor::
        Text editor used by `git rebase -i` for editing the rebase instruction file.
@@@ -558,19 -561,14 +561,19 @@@ core.pager:
        configuration, then `$PAGER`, and then the default chosen at
        compile time (usually 'less').
  +
 -When the `LESS` environment variable is unset, Git sets it to `FRSX`
 +When the `LESS` environment variable is unset, Git sets it to `FRX`
  (if `LESS` environment variable is set, Git does not change it at
  all).  If you want to selectively override Git's default setting
 -for `LESS`, you can set `core.pager` to e.g. `less -+S`.  This will
 +for `LESS`, you can set `core.pager` to e.g. `less -S`.  This will
  be passed to the shell by Git, which will translate the final
 -command to `LESS=FRSX less -+S`. The environment tells the command
 -to set the `S` option to chop long lines but the command line
 -resets it to the default to fold long lines.
 +command to `LESS=FRX less -S`. The environment does not set the
 +`S` option but the command line does, instructing less to truncate
 +long lines. Similarly, setting `core.pager` to `less -+F` will
 +deactivate the `F` option specified by the environment from the
 +command-line, deactivating the "quit if one screen" behavior of
 +`less`.  One can specifically activate some flags for particular
 +commands: for example, setting `pager.blame` to `less -S` enables
 +line truncation only for `git blame`.
  +
  Likewise, when the `LV` environment variable is unset, Git sets it
  to `-c`.  You can override this setting by exporting `LV` with
diff --combined builtin/commit.c
index d28505a857dad9e897896473a88e05f32f56bfb2,515c4c4c0553c00c0ad6d577af192a96cf1bc60a..99c20446352edb477d19122fae2b2821e10d689f
@@@ -526,29 -526,10 +526,29 @@@ static int sane_ident_split(struct iden
        return 1;
  }
  
 +static int parse_force_date(const char *in, char *out, int len)
 +{
 +      if (len < 1)
 +              return -1;
 +      *out++ = '@';
 +      len--;
 +
 +      if (parse_date(in, out, len) < 0) {
 +              int errors = 0;
 +              unsigned long t = approxidate_careful(in, &errors);
 +              if (errors)
 +                      return -1;
 +              snprintf(out, len, "%lu", t);
 +      }
 +
 +      return 0;
 +}
 +
  static void determine_author_info(struct strbuf *author_ident)
  {
        char *name, *email, *date;
        struct ident_split author;
 +      char date_buf[64];
  
        name = getenv("GIT_AUTHOR_NAME");
        email = getenv("GIT_AUTHOR_EMAIL");
                email = xstrndup(lb + 2, rb - (lb + 2));
        }
  
 -      if (force_date)
 -              date = force_date;
 +      if (force_date) {
 +              if (parse_force_date(force_date, date_buf, sizeof(date_buf)))
 +                      die(_("invalid date format: %s"), force_date);
 +              date = date_buf;
 +      }
 +
        strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT));
        if (!split_ident_line(&author, author_ident->buf, author_ident->len) &&
            sane_ident_split(&author)) {
        }
  }
  
 -static char *cut_ident_timestamp_part(char *string)
 +static void split_ident_or_die(struct ident_split *id, const struct strbuf *buf)
 +{
 +      if (split_ident_line(id, buf->buf, buf->len) ||
 +          !sane_ident_split(id))
 +              die(_("Malformed ident string: '%s'"), buf->buf);
 +}
 +
 +static int author_date_is_interesting(void)
  {
 -      char *ket = strrchr(string, '>');
 -      if (!ket || ket[1] != ' ')
 -              die(_("Malformed ident string: '%s'"), string);
 -      *++ket = '\0';
 -      return ket;
 +      return author_message || force_date;
  }
  
+ static void adjust_comment_line_char(const struct strbuf *sb)
+ {
+       char candidates[] = "#;@!$%^&|:";
+       char *candidate;
+       const char *p;
+       comment_line_char = candidates[0];
+       if (!memchr(sb->buf, comment_line_char, sb->len))
+               return;
+       p = sb->buf;
+       candidate = strchr(candidates, *p);
+       if (candidate)
+               *candidate = ' ';
+       for (p = sb->buf; *p; p++) {
+               if ((p[0] == '\n' || p[0] == '\r') && p[1]) {
+                       candidate = strchr(candidates, p[1]);
+                       if (candidate)
+                               *candidate = ' ';
+               }
+       }
+       for (p = candidates; *p == ' '; p++)
+               ;
+       if (!*p)
+               die(_("unable to select a comment character that is not used\n"
+                     "in the current commit message"));
+       comment_line_char = *p;
+ }
  static int prepare_to_commit(const char *index_file, const char *prefix,
                             struct commit *current_head,
                             struct wt_status *s,
        } else if (use_message) {
                char *buffer;
                buffer = strstr(use_message_buffer, "\n\n");
 -              if (!use_editor && (!buffer || buffer[2] == '\0'))
 -                      die(_("commit has empty message"));
 -              strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
 +              if (buffer)
 +                      strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
                hook_arg1 = "commit";
                hook_arg2 = use_message;
        } else if (fixup_message) {
        if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
                die_errno(_("could not write commit template"));
  
+       if (auto_comment_line_char)
+               adjust_comment_line_char(&sb);
        strbuf_release(&sb);
  
        /* This checks if committer ident is explicitly given */
        if (use_editor && include_status) {
                int ident_shown = 0;
                int saved_color_setting;
 -              char *ai_tmp, *ci_tmp;
 +              struct ident_split ci, ai;
 +
                if (whence != FROM_COMMIT) {
                        if (cleanup_mode == CLEANUP_SCISSORS)
                                wt_status_add_cut_line(s->fp);
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                                        "%s", only_include_assumed);
  
 -              ai_tmp = cut_ident_timestamp_part(author_ident->buf);
 -              ci_tmp = cut_ident_timestamp_part(committer_ident.buf);
 -              if (strcmp(author_ident->buf, committer_ident.buf))
 +              split_ident_or_die(&ai, author_ident);
 +              split_ident_or_die(&ci, &committer_ident);
 +
 +              if (ident_cmp(&ai, &ci))
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                                _("%s"
 -                              "Author:    %s"),
 +                              "Author:    %.*s <%.*s>"),
                                ident_shown++ ? "" : "\n",
 -                              author_ident->buf);
 +                              (int)(ai.name_end - ai.name_begin), ai.name_begin,
 +                              (int)(ai.mail_end - ai.mail_begin), ai.mail_begin);
 +
 +              if (author_date_is_interesting())
 +                      status_printf_ln(s, GIT_COLOR_NORMAL,
 +                              _("%s"
 +                              "Date:      %s"),
 +                              ident_shown++ ? "" : "\n",
 +                              show_ident_date(&ai, DATE_NORMAL));
  
                if (!committer_ident_sufficiently_given())
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                                _("%s"
 -                              "Committer: %s"),
 +                              "Committer: %.*s <%.*s>"),
                                ident_shown++ ? "" : "\n",
 -                              committer_ident.buf);
 +                              (int)(ci.name_end - ci.name_begin), ci.name_begin,
 +                              (int)(ci.mail_end - ci.mail_begin), ci.mail_begin);
  
                if (ident_shown)
 -                      status_printf_ln(s, GIT_COLOR_NORMAL, "");
 +                      status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
  
                saved_color_setting = s->use_color;
                s->use_color = 0;
                commitable = run_status(s->fp, index_file, prefix, 1, s);
                s->use_color = saved_color_setting;
 -
 -              *ai_tmp = ' ';
 -              *ci_tmp = ' ';
        } else {
                unsigned char sha1[20];
                const char *parent = "HEAD";
@@@ -1389,13 -1388,6 +1421,13 @@@ static void print_summary(const char *p
                strbuf_addstr(&format, "\n Author: ");
                strbuf_addbuf_percentquote(&format, &author_ident);
        }
 +      if (author_date_is_interesting()) {
 +              struct strbuf date = STRBUF_INIT;
 +              format_commit_message(commit, "%ad", &date, &pctx);
 +              strbuf_addstr(&format, "\n Date: ");
 +              strbuf_addbuf_percentquote(&format, &date);
 +              strbuf_release(&date);
 +      }
        if (!committer_ident_sufficiently_given()) {
                strbuf_addstr(&format, "\n Committer: ");
                strbuf_addbuf_percentquote(&format, &committer_ident);
@@@ -1712,10 -1704,6 +1744,10 @@@ int cmd_commit(int argc, const char **a
                                           ? NULL
                                           : current_head->object.sha1,
                                           0, NULL);
 +      if (!ref_lock) {
 +              rollback_index_files();
 +              die(_("cannot lock HEAD ref"));
 +      }
  
        nl = strchr(sb.buf, '\n');
        if (nl)
        strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
        strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
  
 -      if (!ref_lock) {
 -              rollback_index_files();
 -              die(_("cannot lock HEAD ref"));
 -      }
        if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) {
                rollback_index_files();
                die(_("cannot update HEAD ref"));
diff --combined cache.h
index 8c6cdc29deca2eb60d5f76f9b44d6cfa1bd2d8ae,646fb810bb38f5a942fab7bb091e1d965443d1b8..1e4b4f06e1ee013db0cb696115ead61a76087f4e
+++ b/cache.h
@@@ -74,21 -74,6 +74,21 @@@ unsigned long git_deflate_bound(git_zst
  #define S_IFGITLINK   0160000
  #define S_ISGITLINK(m)        (((m) & S_IFMT) == S_IFGITLINK)
  
 +/*
 + * Some mode bits are also used internally for computations.
 + *
 + * They *must* not overlap with any valid modes, and they *must* not be emitted
 + * to outside world - i.e. appear on disk or network. In other words, it's just
 + * temporary fields, which we internally use, but they have to stay in-house.
 + *
 + * ( such approach is valid, as standard S_IF* fits into 16 bits, and in Git
 + *   codebase mode is `unsigned int` which is assumed to be at least 32 bits )
 + */
 +
 +/* used internally in tree-diff */
 +#define S_DIFFTREE_IFXMIN_NEQ 0x80000000
 +
 +
  /*
   * Intensive research over the course of many years has shown that
   * port 9418 is totally unused by anything else. Or
@@@ -294,7 -279,6 +294,7 @@@ struct index_state 
                 initialized : 1;
        struct hashmap name_hash;
        struct hashmap dir_hash;
 +      unsigned char sha1[20];
  };
  
  extern struct index_state the_index;
@@@ -618,6 -602,7 +618,7 @@@ extern int precomposed_unicode
   * that is subject to stripspace.
   */
  extern char comment_line_char;
+ extern int auto_comment_line_char;
  
  enum branch_track {
        BRANCH_TRACK_UNSPECIFIED = -1,
@@@ -1061,13 -1046,6 +1062,13 @@@ struct ident_split 
   */
  extern int split_ident_line(struct ident_split *, const char *, int);
  
 +/*
 + * Like show_date, but pull the timestamp and tz parameters from
 + * the ident_split. It will also sanity-check the values and produce
 + * a well-known sentinel date if they appear bogus.
 + */
 +const char *show_ident_date(const struct ident_split *id, enum date_mode mode);
 +
  /*
   * Compare split idents for equality or strict ordering. Note that we
   * compare only the ident part of the line, ignoring any timestamp.
@@@ -1294,8 -1272,8 +1295,8 @@@ extern int check_repository_format_vers
  extern int git_env_bool(const char *, int);
  extern int git_config_system(void);
  extern int config_error_nonbool(const char *);
 -#if defined(__GNUC__) && ! defined(__clang__)
 -#define config_error_nonbool(s) (config_error_nonbool(s), -1)
 +#if defined(__GNUC__)
 +#define config_error_nonbool(s) (config_error_nonbool(s), const_error())
  #endif
  extern const char *get_log_output_encoding(void);
  extern const char *get_commit_output_encoding(void);
@@@ -1345,8 -1323,6 +1346,8 @@@ extern void fsync_or_die(int fd, const 
  
  extern ssize_t read_in_full(int fd, void *buf, size_t count);
  extern ssize_t write_in_full(int fd, const void *buf, size_t count);
 +extern ssize_t pread_in_full(int fd, void *buf, size_t count, off_t offset);
 +
  static inline ssize_t write_str_in_full(int fd, const char *str)
  {
        return write_in_full(fd, str, strlen(str));
diff --combined config.c
index c227aa85177724354b3c9740ec62ea2ce521ac62,d29c733419829295961898f754271871f807a505..3c3e31448e5be385f9302ccce9c6993667014e5b
+++ b/config.c
@@@ -826,9 -826,16 +826,16 @@@ static int git_default_core_config(cons
        if (!strcmp(var, "core.commentchar")) {
                const char *comment;
                int ret = git_config_string(&comment, var, value);
-               if (!ret)
+               if (ret)
+                       return ret;
+               else if (!strcasecmp(comment, "auto"))
+                       auto_comment_line_char = 1;
+               else if (comment[0] && !comment[1]) {
                        comment_line_char = comment[0];
-               return ret;
+                       auto_comment_line_char = 0;
+               } else
+                       return error("core.commentChar should only be one character");
+               return 0;
        }
  
        if (!strcmp(var, "core.askpass"))
@@@ -1636,13 -1643,6 +1643,13 @@@ int git_config_set_multivar_in_file(con
                        MAP_PRIVATE, in_fd, 0);
                close(in_fd);
  
 +              if (fchmod(fd, st.st_mode & 07777) < 0) {
 +                      error("fchmod on %s failed: %s",
 +                              lock->filename, strerror(errno));
 +                      ret = CONFIG_NO_WRITE;
 +                      goto out_free;
 +              }
 +
                if (store.seen == 0)
                        store.seen = 1;
  
@@@ -1791,7 -1791,6 +1798,7 @@@ int git_config_rename_section_in_file(c
        int out_fd;
        char buf[1024];
        FILE *config_file;
 +      struct stat st;
  
        if (new_name && !section_name_is_ok(new_name)) {
                ret = error("invalid section name: %s", new_name);
                goto unlock_and_out;
        }
  
 +      fstat(fileno(config_file), &st);
 +
 +      if (fchmod(out_fd, st.st_mode & 07777) < 0) {
 +              ret = error("fchmod on %s failed: %s",
 +                              lock->filename, strerror(errno));
 +              goto out;
 +      }
 +
        while (fgets(buf, sizeof(buf), config_file)) {
                int i;
                int length;
diff --combined environment.c
index 37354c8d056efc3f82e2a98f19d0bdda7f79be51,f2de1ee9ada0c1211fe7e31da52011ec0825758d..c648ac3d3af1f86889ccc34e7f794683bd1951ea
@@@ -37,7 -37,7 +37,7 @@@ int core_compression_seen
  int fsync_object_files;
  size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;
  size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT;
 -size_t delta_base_cache_limit = 16 * 1024 * 1024;
 +size_t delta_base_cache_limit = 96 * 1024 * 1024;
  unsigned long big_file_threshold = 512 * 1024 * 1024;
  const char *pager_program;
  int pager_use_color = 1;
@@@ -69,6 -69,7 +69,7 @@@ unsigned long pack_size_limit_cfg
   * that is subject to stripspace.
   */
  char comment_line_char = '#';
+ int auto_comment_line_char;
  
  /* Parallel index stat data preload? */
  int core_preload_index = 0;
diff --combined t/t7502-commit.sh
index 6465cd59afb6dcd4c2e554947da4755a1beb883e,30e46884f29722c38b8fd664339b8440a2b042e4..051489ea334c9693c2d19823ad23947703f66d90
@@@ -344,13 -344,6 +344,13 @@@ test_expect_success 'message shows auth
          .git/COMMIT_EDITMSG
  '
  
 +test_expect_success 'message shows date when it is explicitly set' '
 +      git commit --allow-empty -e -m foo --date="2010-01-02T03:04:05" &&
 +      test_i18ngrep \
 +        "^# Date: *Sat Jan 2 03:04:05 2010 +0000" \
 +        .git/COMMIT_EDITMSG
 +'
 +
  test_expect_success AUTOIDENT 'message shows committer when it is automatic' '
  
        echo >>negative &&
@@@ -570,4 -563,30 +570,30 @@@ test_expect_success 'commit --status wi
        test_i18ngrep "^; Changes to be committed:" .git/COMMIT_EDITMSG
  '
  
+ test_expect_success 'switch core.commentchar' '
+       test_commit "#foo" foo &&
+       GIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend &&
+       test_i18ngrep "^; Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+ test_expect_success 'switch core.commentchar but out of options' '
+       cat >text <<\EOF &&
+ # 1
+ ; 2
+ @ 3
+ ! 4
+ $ 5
+ % 6
+ ^ 7
+ & 8
+ | 9
+ : 10
+ EOF
+       git commit --amend -F text &&
+       (
+               test_set_editor .git/FAKE_EDITOR &&
+               test_must_fail git -c core.commentChar=auto commit --amend
+       )
+ '
  test_done