Code

Merge branch 'jc/maint-blank-at-eof' into maint
authorJunio C Hamano <gitster@pobox.com>
Mon, 16 Nov 2009 07:06:34 +0000 (23:06 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 16 Nov 2009 07:06:34 +0000 (23:06 -0800)
* jc/maint-blank-at-eof:
  diff -B: colour whitespace errors
  diff.c: emit_add_line() takes only the rest of the line
  diff.c: split emit_line() from the first char and the rest of the line
  diff.c: shuffling code around
  diff --whitespace: fix blank lines at end
  core.whitespace: split trailing-space into blank-at-{eol,eof}
  diff --color: color blank-at-eof
  diff --whitespace=warn/error: fix blank-at-eof check
  diff --whitespace=warn/error: obey blank-at-eof
  diff.c: the builtin_diff() deals with only two-file comparison
  apply --whitespace: warn blank but not necessarily empty lines at EOF
  apply --whitespace=warn/error: diagnose blank at EOF
  apply.c: split check_whitespace() into two
  apply --whitespace=fix: detect new blank lines at eof correctly
  apply --whitespace=fix: fix handling of blank lines at the eof

1  2 
Documentation/config.txt
builtin-apply.c
cache.h
diff.c

diff --combined Documentation/config.txt
index ba6ed1080f6a33f532a1cdd931e09b59ff1358e1,273bbde704dfbdf0c72be78f27f91dc53f838a37..d1e2120e15b37a14eec5b3d1de9752da8f6ad7af
@@@ -113,21 -113,6 +113,21 @@@ For command-specific variables, you wil
  in the appropriate manual page. You will find a description of non-core
  porcelain configuration variables in the respective porcelain documentation.
  
 +advice.*::
 +      When set to 'true', display the given optional help message.
 +      When set to 'false', do not display. The configuration variables
 +      are:
 ++
 +--
 +      pushNonFastForward::
 +              Advice shown when linkgit:git-push[1] refuses
 +              non-fast-forward refs. Default: true.
 +      statusHints::
 +              Directions on how to stage/unstage/add shown in the
 +              output of linkgit:git-status[1] and the template shown
 +              when writing commit messages. Default: true.
 +--
 +
  core.fileMode::
        If false, the executable bit differences between the index and
        the working copy are ignored; useful on broken filesystems like FAT.
@@@ -416,13 -401,17 +416,17 @@@ core.whitespace:
        consider them as errors.  You can prefix `-` to disable
        any of them (e.g. `-trailing-space`):
  +
- * `trailing-space` treats trailing whitespaces at the end of the line
+ * `blank-at-eol` treats trailing whitespaces at the end of the line
    as an error (enabled by default).
  * `space-before-tab` treats a space character that appears immediately
    before a tab character in the initial indent part of the line as an
    error (enabled by default).
  * `indent-with-non-tab` treats a line that is indented with 8 or more
    space characters as an error (not enabled by default).
+ * `blank-at-eof` treats blank lines added at the end of file as an error
+   (enabled by default).
+ * `trailing-space` is a short-hand to cover both `blank-at-eol` and
+   `blank-at-eof`.
  * `cr-at-eol` treats a carriage-return at the end of line as
    part of the line terminator, i.e. with it, `trailing-space`
    does not trigger if the character before such a carriage-return
@@@ -476,14 -465,6 +480,14 @@@ it will be treated as a shell command
  executed from the top-level directory of a repository, which may
  not necessarily be the current directory.
  
 +apply.ignorewhitespace::
 +      When set to 'change', tells 'git-apply' to ignore changes in
 +      whitespace, in the same way as the '--ignore-space-change'
 +      option.
 +      When set to one of: no, none, never, false tells 'git-apply' to
 +      respect all whitespace differences.
 +      See linkgit:git-apply[1].
 +
  apply.whitespace::
        Tells 'git-apply' how to handle whitespaces, in the same way
        as the '--whitespace' option. See linkgit:git-apply[1].
@@@ -539,7 -520,7 +543,7 @@@ branch.<name>.merge:
  
  branch.<name>.mergeoptions::
        Sets default options for merging into branch <name>. The syntax and
 -      supported options are equal to that of linkgit:git-merge[1], but
 +      supported options are the same as those of linkgit:git-merge[1], but
        option values containing whitespace characters are currently not
        supported.
  
@@@ -1320,11 -1301,6 +1324,11 @@@ rebase.stat:
        Whether to show a diffstat of what changed upstream since the last
        rebase. False by default.
  
 +receive.autogc::
 +      By default, git-receive-pack will run "git-gc --auto" after
 +      receiving data from git-push and updating refs.  You can stop
 +      it by setting this variable to false.
 +
  receive.fsckObjects::
        If it is set to true, git-receive-pack will check all received
        objects. It will abort in the case of a malformed object or a
@@@ -1360,10 -1336,6 +1364,10 @@@ receive.denyNonFastForwards:
        even if that push is forced. This configuration variable is
        set when initializing a shared repository.
  
 +receive.updateserverinfo::
 +      If set to true, git-receive-pack will run git-update-server-info
 +      after receiving data from git-push and updating refs.
 +
  remote.<name>.url::
        The URL of a remote repository.  See linkgit:git-fetch[1] or
        linkgit:git-push[1].
@@@ -1524,19 -1496,6 +1528,19 @@@ url.<base>.insteadOf:
        never-before-seen repository on the site.  When more than one
        insteadOf strings match a given URL, the longest match is used.
  
 +url.<base>.pushInsteadOf::
 +      Any URL that starts with this value will not be pushed to;
 +      instead, it will be rewritten to start with <base>, and the
 +      resulting URL will be pushed to. In cases where some site serves
 +      a large number of repositories, and serves them with multiple
 +      access methods, some of which do not allow push, this feature
 +      allows people to specify a pull-only URL and have git
 +      automatically use an appropriate URL to push, even for a
 +      never-before-seen repository on the site.  When more than one
 +      pushInsteadOf strings match a given URL, the longest match is
 +      used.  If a remote has an explicit pushurl, git will ignore this
 +      setting for that remote.
 +
  user.email::
        Your email address to be recorded in any newly created commits.
        Can be overridden by the 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL', and
diff --combined builtin-apply.c
index c8372a0a8051c331e671aee588b8ea0632ad6430,82415d93c95095006beeea73f26955cf121cb495..f667368d161609ea3c93fc6ebe2452b9cd0e557d
@@@ -61,13 -61,6 +61,13 @@@ static enum ws_error_action 
  static int whitespace_error;
  static int squelch_whitespace_errors = 5;
  static int applied_after_fixing_ws;
 +
 +static enum ws_ignore {
 +      ignore_ws_none,
 +      ignore_ws_change,
 +} ws_ignore_action = ignore_ws_none;
 +
 +
  static const char *patch_input_file;
  static const char *root;
  static int root_len;
@@@ -104,21 -97,6 +104,21 @@@ static void parse_whitespace_option(con
        die("unrecognized whitespace option '%s'", option);
  }
  
 +static void parse_ignorewhitespace_option(const char *option)
 +{
 +      if (!option || !strcmp(option, "no") ||
 +          !strcmp(option, "false") || !strcmp(option, "never") ||
 +          !strcmp(option, "none")) {
 +              ws_ignore_action = ignore_ws_none;
 +              return;
 +      }
 +      if (!strcmp(option, "change")) {
 +              ws_ignore_action = ignore_ws_change;
 +              return;
 +      }
 +      die("unrecognized whitespace ignore option '%s'", option);
 +}
 +
  static void set_default_whitespace_mode(const char *whitespace_option)
  {
        if (!whitespace_option && !apply_default_whitespace)
@@@ -153,6 -131,7 +153,7 @@@ struct fragment 
        const char *patch;
        int size;
        int rejected;
+       int linenr;
        struct fragment *next;
  };
  
@@@ -236,62 -215,6 +237,62 @@@ static uint32_t hash_line(const char *c
        return h;
  }
  
 +/*
 + * Compare lines s1 of length n1 and s2 of length n2, ignoring
 + * whitespace difference. Returns 1 if they match, 0 otherwise
 + */
 +static int fuzzy_matchlines(const char *s1, size_t n1,
 +                          const char *s2, size_t n2)
 +{
 +      const char *last1 = s1 + n1 - 1;
 +      const char *last2 = s2 + n2 - 1;
 +      int result = 0;
 +
 +      if (n1 < 0 || n2 < 0)
 +              return 0;
 +
 +      /* ignore line endings */
 +      while ((*last1 == '\r') || (*last1 == '\n'))
 +              last1--;
 +      while ((*last2 == '\r') || (*last2 == '\n'))
 +              last2--;
 +
 +      /* skip leading whitespace */
 +      while (isspace(*s1) && (s1 <= last1))
 +              s1++;
 +      while (isspace(*s2) && (s2 <= last2))
 +              s2++;
 +      /* early return if both lines are empty */
 +      if ((s1 > last1) && (s2 > last2))
 +              return 1;
 +      while (!result) {
 +              result = *s1++ - *s2++;
 +              /*
 +               * Skip whitespace inside. We check for whitespace on
 +               * both buffers because we don't want "a b" to match
 +               * "ab"
 +               */
 +              if (isspace(*s1) && isspace(*s2)) {
 +                      while (isspace(*s1) && s1 <= last1)
 +                              s1++;
 +                      while (isspace(*s2) && s2 <= last2)
 +                              s2++;
 +              }
 +              /*
 +               * If we reached the end on one side only,
 +               * lines don't match
 +               */
 +              if (
 +                  ((s2 > last2) && (s1 <= last1)) ||
 +                  ((s1 > last1) && (s2 <= last2)))
 +                      return 0;
 +              if ((s1 > last1) && (s2 > last2))
 +                      break;
 +      }
 +
 +      return !result;
 +}
 +
  static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag)
  {
        ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc);
@@@ -1227,23 -1150,29 +1228,29 @@@ static int find_header(char *line, unsi
        return -1;
  }
  
- static void check_whitespace(const char *line, int len, unsigned ws_rule)
+ static void record_ws_error(unsigned result, const char *line, int len, int linenr)
  {
        char *err;
-       unsigned result = ws_check(line + 1, len - 1, ws_rule);
        if (!result)
                return;
  
        whitespace_error++;
        if (squelch_whitespace_errors &&
            squelch_whitespace_errors < whitespace_error)
-               ;
-       else {
-               err = whitespace_error_string(result);
-               fprintf(stderr, "%s:%d: %s.\n%.*s\n",
-                       patch_input_file, linenr, err, len - 2, line + 1);
-               free(err);
-       }
+               return;
+       err = whitespace_error_string(result);
+       fprintf(stderr, "%s:%d: %s.\n%.*s\n",
+               patch_input_file, linenr, err, len, line);
+       free(err);
+ }
+ static void check_whitespace(const char *line, int len, unsigned ws_rule)
+ {
+       unsigned result = ws_check(line + 1, len - 1, ws_rule);
+       record_ws_error(result, line + 1, len - 2, linenr);
  }
  
  /*
@@@ -1359,6 -1288,7 +1366,7 @@@ static int parse_single_patch(char *lin
                int len;
  
                fragment = xcalloc(1, sizeof(*fragment));
+               fragment->linenr = linenr;
                len = parse_fragment(line, size, patch, fragment);
                if (len <= 0)
                        die("corrupt patch at line %d", linenr);
@@@ -1750,17 -1680,10 +1758,17 @@@ static int read_old_data(struct stat *s
        }
  }
  
 +/*
 + * Update the preimage, and the common lines in postimage,
 + * from buffer buf of length len. If postlen is 0 the postimage
 + * is updated in place, otherwise it's updated on a new buffer
 + * of length postlen
 + */
 +
  static void update_pre_post_images(struct image *preimage,
                                   struct image *postimage,
                                   char *buf,
 -                                 size_t len)
 +                                 size_t len, size_t postlen)
  {
        int i, ctx;
        char *new, *old, *fixed;
        *preimage = fixed_preimage;
  
        /*
 -       * Adjust the common context lines in postimage, in place.
 -       * This is possible because whitespace fixing does not make
 -       * the string grow.
 +       * Adjust the common context lines in postimage. This can be
 +       * done in-place when we are just doing whitespace fixing,
 +       * which does not make the string grow, but needs a new buffer
 +       * when ignoring whitespace causes the update, since in this case
 +       * we could have e.g. tabs converted to multiple spaces.
 +       * We trust the caller to tell us if the update can be done
 +       * in place (postlen==0) or not.
         */
 -      new = old = postimage->buf;
 +      old = postimage->buf;
 +      if (postlen)
 +              new = postimage->buf = xmalloc(postlen);
 +      else
 +              new = old;
        fixed = preimage->buf;
        for (i = ctx = 0; i < postimage->nr; i++) {
                size_t len = postimage->line[i].len;
@@@ -1866,56 -1781,12 +1874,56 @@@ static int match_fragment(struct image 
            !memcmp(img->buf + try, preimage->buf, preimage->len))
                return 1;
  
 +      /*
 +       * No exact match. If we are ignoring whitespace, run a line-by-line
 +       * fuzzy matching. We collect all the line length information because
 +       * we need it to adjust whitespace if we match.
 +       */
 +      if (ws_ignore_action == ignore_ws_change) {
 +              size_t imgoff = 0;
 +              size_t preoff = 0;
 +              size_t postlen = postimage->len;
 +              for (i = 0; i < preimage->nr; i++) {
 +                      size_t prelen = preimage->line[i].len;
 +                      size_t imglen = img->line[try_lno+i].len;
 +
 +                      if (!fuzzy_matchlines(img->buf + try + imgoff, imglen,
 +                                            preimage->buf + preoff, prelen))
 +                              return 0;
 +                      if (preimage->line[i].flag & LINE_COMMON)
 +                              postlen += imglen - prelen;
 +                      imgoff += imglen;
 +                      preoff += prelen;
 +              }
 +
 +              /*
 +               * Ok, the preimage matches with whitespace fuzz. Update it and
 +               * the common postimage lines to use the same whitespace as the
 +               * target. imgoff now holds the true length of the target that
 +               * matches the preimage, and we need to update the line lengths
 +               * of the preimage to match the target ones.
 +               */
 +              fixed_buf = xmalloc(imgoff);
 +              memcpy(fixed_buf, img->buf + try, imgoff);
 +              for (i = 0; i < preimage->nr; i++)
 +                      preimage->line[i].len = img->line[try_lno+i].len;
 +
 +              /*
 +               * Update the preimage buffer and the postimage context lines.
 +               */
 +              update_pre_post_images(preimage, postimage,
 +                              fixed_buf, imgoff, postlen);
 +              return 1;
 +      }
 +
        if (ws_error_action != correct_ws_error)
                return 0;
  
        /*
         * The hunk does not apply byte-by-byte, but the hash says
 -       * it might with whitespace fuzz.
 +       * it might with whitespace fuzz. We haven't been asked to
 +       * ignore whitespace, we were asked to correct whitespace
 +       * errors, so let's try matching after whitespace correction.
         */
        fixed_buf = xmalloc(preimage->len + 1);
        buf = fixed_buf;
         * hunk match.  Update the context lines in the postimage.
         */
        update_pre_post_images(preimage, postimage,
 -                             fixed_buf, buf - fixed_buf);
 +                             fixed_buf, buf - fixed_buf, 0);
        return 1;
  
   unmatch_exit:
@@@ -2142,6 -2013,7 +2150,7 @@@ static int apply_one_fragment(struct im
                int len = linelen(patch, size);
                int plen, added;
                int added_blank_line = 0;
+               int is_blank_context = 0;
  
                if (!len)
                        break;
                        *new++ = '\n';
                        add_line_info(&preimage, "\n", 1, LINE_COMMON);
                        add_line_info(&postimage, "\n", 1, LINE_COMMON);
+                       is_blank_context = 1;
                        break;
                case ' ':
+                       if (plen && (ws_rule & WS_BLANK_AT_EOF) &&
+                           ws_blank_line(patch + 1, plen, ws_rule))
+                               is_blank_context = 1;
                case '-':
                        memcpy(old, patch + 1, plen);
                        add_line_info(&preimage, old, plen,
                                      (first == '+' ? 0 : LINE_COMMON));
                        new += added;
                        if (first == '+' &&
-                           added == 1 && new[-1] == '\n')
+                           (ws_rule & WS_BLANK_AT_EOF) &&
+                           ws_blank_line(patch + 1, plen, ws_rule))
                                added_blank_line = 1;
                        break;
                case '@': case '\\':
                }
                if (added_blank_line)
                        new_blank_lines_at_end++;
+               else if (is_blank_context)
+                       ;
                else
                        new_blank_lines_at_end = 0;
                patch += len;
        }
  
        if (applied_pos >= 0) {
-               if (ws_error_action == correct_ws_error &&
-                   new_blank_lines_at_end &&
-                   postimage.nr + applied_pos == img->nr) {
+               if (new_blank_lines_at_end &&
+                   preimage.nr + applied_pos == img->nr &&
+                   (ws_rule & WS_BLANK_AT_EOF) &&
+                   ws_error_action != nowarn_ws_error) {
+                       record_ws_error(WS_BLANK_AT_EOF, "+", 1, frag->linenr);
+                       if (ws_error_action == correct_ws_error) {
+                               while (new_blank_lines_at_end--)
+                                       remove_last_line(&postimage);
+                       }
                        /*
-                        * If the patch application adds blank lines
-                        * at the end, and if the patch applies at the
-                        * end of the image, remove those added blank
-                        * lines.
+                        * We would want to prevent write_out_results()
+                        * from taking place in apply_patch() that follows
+                        * the callchain led us here, which is:
+                        * apply_patch->check_patch_list->check_patch->
+                        * apply_data->apply_fragments->apply_one_fragment
                         */
-                       while (new_blank_lines_at_end--)
-                               remove_last_line(&postimage);
+                       if (ws_error_action == die_on_ws_error)
+                               apply = 0;
                }
  
                /*
@@@ -3409,8 -3295,6 +3432,8 @@@ static int git_apply_config(const char 
  {
        if (!strcmp(var, "apply.whitespace"))
                return git_config_string(&apply_default_whitespace, var, value);
 +      else if (!strcmp(var, "apply.ignorewhitespace"))
 +              return git_config_string(&apply_default_ignorewhitespace, var, value);
        return git_default_config(var, value, cb);
  }
  
@@@ -3447,16 -3331,6 +3470,16 @@@ static int option_parse_z(const struct 
        return 0;
  }
  
 +static int option_parse_space_change(const struct option *opt,
 +                        const char *arg, int unset)
 +{
 +      if (unset)
 +              ws_ignore_action = ignore_ws_none;
 +      else
 +              ws_ignore_action = ignore_ws_change;
 +      return 0;
 +}
 +
  static int option_parse_whitespace(const struct option *opt,
                                   const char *arg, int unset)
  {
@@@ -3533,12 -3407,6 +3556,12 @@@ int cmd_apply(int argc, const char **ar
                { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action",
                        "detect new or modified lines that have whitespace errors",
                        0, option_parse_whitespace },
 +              { OPTION_CALLBACK, 0, "ignore-space-change", NULL, NULL,
 +                      "ignore changes in whitespace when finding context",
 +                      PARSE_OPT_NOARG, option_parse_space_change },
 +              { OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL,
 +                      "ignore changes in whitespace when finding context",
 +                      PARSE_OPT_NOARG, option_parse_space_change },
                OPT_BOOLEAN('R', "reverse", &apply_in_reverse,
                        "apply the patch in reverse"),
                OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero,
        git_config(git_apply_config, NULL);
        if (apply_default_whitespace)
                parse_whitespace_option(apply_default_whitespace);
 +      if (apply_default_ignorewhitespace)
 +              parse_ignorewhitespace_option(apply_default_ignorewhitespace);
  
        argc = parse_options(argc, argv, prefix, builtin_apply_options,
                        apply_usage, 0);
diff --combined cache.h
index a5eeead1e275523fbc7bc192a836193720370991,3c1061b4d7fc5eabafa303b4636922fdf25ec6a1..96840c7af78aac3c760586dd8018652ec9ddefed
+++ b/cache.h
@@@ -4,7 -4,6 +4,7 @@@
  #include "git-compat-util.h"
  #include "strbuf.h"
  #include "hash.h"
 +#include "advice.h"
  
  #include SHA1_HEADER
  #ifndef git_SHA_CTX
@@@ -331,7 -330,7 +331,7 @@@ static inline void remove_name_hash(str
  #define remove_file_from_cache(path) remove_file_from_index(&the_index, (path))
  #define add_to_cache(path, st, flags) add_to_index(&the_index, (path), (st), (flags))
  #define add_file_to_cache(path, flags) add_file_to_index(&the_index, (path), (flags))
 -#define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL)
 +#define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL, NULL)
  #define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options))
  #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
  #define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase))
@@@ -470,15 -469,15 +470,15 @@@ extern int index_path(unsigned char *sh
  extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
  
  /* "careful lstat()" */
 -extern int check_path(const char *path, int len, struct stat *st);
 +extern int check_path(const char *path, int len, struct stat *st, int skiplen);
  
  #define REFRESH_REALLY                0x0001  /* ignore_valid */
  #define REFRESH_UNMERGED      0x0002  /* allow unmerged */
  #define REFRESH_QUIET         0x0004  /* be quiet about it */
  #define REFRESH_IGNORE_MISSING        0x0008  /* ignore non-existent */
  #define REFRESH_IGNORE_SUBMODULES     0x0010  /* ignore submodules */
 -#define REFRESH_SAY_CHANGED   0x0020  /* say "changed" not "needs update" */
 -extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen);
 +#define REFRESH_IN_PORCELAIN  0x0020  /* user friendly output, not "needs update" */
 +extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, char *header_msg);
  
  struct lock_file {
        struct lock_file *next;
  };
  #define LOCK_DIE_ON_ERROR 1
  #define LOCK_NODEREF 2
 +extern int unable_to_lock_error(const char *path, int err);
  extern NORETURN void unable_to_lock_index_die(const char *path, int err);
  extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
  extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
@@@ -514,7 -512,6 +514,7 @@@ extern int log_all_ref_updates
  extern int warn_ambiguous_refs;
  extern int shared_repository;
  extern const char *apply_default_whitespace;
 +extern const char *apply_default_ignorewhitespace;
  extern int zlib_compression_level;
  extern int core_compression_level;
  extern int core_compression_seen;
@@@ -522,7 -519,6 +522,7 @@@ extern size_t packed_git_window_size
  extern size_t packed_git_limit;
  extern size_t delta_base_cache_limit;
  extern int auto_crlf;
 +extern int read_replace_refs;
  extern int fsync_object_files;
  extern int core_preload_index;
  
@@@ -659,11 -655,7 +659,11 @@@ char *strip_path_suffix(const char *pat
  
  /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
  extern int sha1_object_info(const unsigned char *, unsigned long *);
 -extern void * read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size);
 +extern void *read_sha1_file_repl(const unsigned char *sha1, enum object_type *type, unsigned long *size, const unsigned char **replacement);
 +static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
 +{
 +      return read_sha1_file_repl(sha1, type, size, NULL);
 +}
  extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1);
  extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
  extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
@@@ -733,14 -725,9 +733,14 @@@ enum date_mode 
  };
  
  const char *show_date(unsigned long time, int timezone, enum date_mode mode);
 +const char *show_date_relative(unsigned long time, int tz,
 +                             const struct timeval *now,
 +                             char *timebuf,
 +                             size_t timebuf_size);
  int parse_date(const char *date, char *buf, int bufsize);
  void datestamp(char *buf, int bufsize);
  unsigned long approxidate(const char *);
 +unsigned long approxidate_relative(const char *date, const struct timeval *now);
  enum date_mode parse_date_format(const char *format);
  
  #define IDENT_WARN_ON_NO_NAME  1
@@@ -925,19 -912,13 +925,19 @@@ extern const char *git_mailmap_file
  extern void maybe_flush_or_die(FILE *, const char *);
  extern int copy_fd(int ifd, int ofd);
  extern int copy_file(const char *dst, const char *src, int mode);
 -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 int copy_file_with_time(const char *dst, const char *src, int mode);
  extern void write_or_die(int fd, const void *buf, size_t count);
  extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);
  extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg);
  extern void fsync_or_die(int fd, const char *);
  
 +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);
 +static inline ssize_t write_str_in_full(int fd, const char *str)
 +{
 +      return write_in_full(fd, str, strlen(str));
 +}
 +
  /* pager.c */
  extern void setup_pager(void);
  extern const char *pager_program;
@@@ -986,10 -967,12 +986,12 @@@ void shift_tree(const unsigned char *, 
   * whitespace rules.
   * used by both diff and apply
   */
- #define WS_TRAILING_SPACE     01
+ #define WS_BLANK_AT_EOL         01
  #define WS_SPACE_BEFORE_TAB   02
  #define WS_INDENT_WITH_NON_TAB        04
  #define WS_CR_AT_EOL           010
+ #define WS_BLANK_AT_EOF        020
+ #define WS_TRAILING_SPACE      (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF)
  #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB)
  extern unsigned whitespace_rule_cfg;
  extern unsigned whitespace_rule(const char *);
diff --combined diff.c
index e1be189742f3239de028393ceabf7c6539bb0440,ce2b758a8d68cccb51e9dcea8892c506630d7505..b88c7d11b100ddf3a63b1039f2f18bd6cdc7fe39
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -174,6 -174,175 +174,175 @@@ static struct diff_tempfile 
        char tmp_path[PATH_MAX];
  } diff_temp[2];
  
+ typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
+ struct emit_callback {
+       int color_diff;
+       unsigned ws_rule;
+       int blank_at_eof_in_preimage;
+       int blank_at_eof_in_postimage;
+       int lno_in_preimage;
+       int lno_in_postimage;
+       sane_truncate_fn truncate;
+       const char **label_path;
+       struct diff_words_data *diff_words;
+       int *found_changesp;
+       FILE *file;
+ };
+ static int count_lines(const char *data, int size)
+ {
+       int count, ch, completely_empty = 1, nl_just_seen = 0;
+       count = 0;
+       while (0 < size--) {
+               ch = *data++;
+               if (ch == '\n') {
+                       count++;
+                       nl_just_seen = 1;
+                       completely_empty = 0;
+               }
+               else {
+                       nl_just_seen = 0;
+                       completely_empty = 0;
+               }
+       }
+       if (completely_empty)
+               return 0;
+       if (!nl_just_seen)
+               count++; /* no trailing newline */
+       return count;
+ }
+ static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
+ {
+       if (!DIFF_FILE_VALID(one)) {
+               mf->ptr = (char *)""; /* does not matter */
+               mf->size = 0;
+               return 0;
+       }
+       else if (diff_populate_filespec(one, 0))
+               return -1;
+       mf->ptr = one->data;
+       mf->size = one->size;
+       return 0;
+ }
+ static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule)
+ {
+       char *ptr = mf->ptr;
+       long size = mf->size;
+       int cnt = 0;
+       if (!size)
+               return cnt;
+       ptr += size - 1; /* pointing at the very end */
+       if (*ptr != '\n')
+               ; /* incomplete line */
+       else
+               ptr--; /* skip the last LF */
+       while (mf->ptr < ptr) {
+               char *prev_eol;
+               for (prev_eol = ptr; mf->ptr <= prev_eol; prev_eol--)
+                       if (*prev_eol == '\n')
+                               break;
+               if (!ws_blank_line(prev_eol + 1, ptr - prev_eol, ws_rule))
+                       break;
+               cnt++;
+               ptr = prev_eol - 1;
+       }
+       return cnt;
+ }
+ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,
+                              struct emit_callback *ecbdata)
+ {
+       int l1, l2, at;
+       unsigned ws_rule = ecbdata->ws_rule;
+       l1 = count_trailing_blank(mf1, ws_rule);
+       l2 = count_trailing_blank(mf2, ws_rule);
+       if (l2 <= l1) {
+               ecbdata->blank_at_eof_in_preimage = 0;
+               ecbdata->blank_at_eof_in_postimage = 0;
+               return;
+       }
+       at = count_lines(mf1->ptr, mf1->size);
+       ecbdata->blank_at_eof_in_preimage = (at - l1) + 1;
+       at = count_lines(mf2->ptr, mf2->size);
+       ecbdata->blank_at_eof_in_postimage = (at - l2) + 1;
+ }
+ static void emit_line_0(FILE *file, const char *set, const char *reset,
+                       int first, const char *line, int len)
+ {
+       int has_trailing_newline, has_trailing_carriage_return;
+       int nofirst;
+       if (len == 0) {
+               has_trailing_newline = (first == '\n');
+               has_trailing_carriage_return = (!has_trailing_newline &&
+                                               (first == '\r'));
+               nofirst = has_trailing_newline || has_trailing_carriage_return;
+       } else {
+               has_trailing_newline = (len > 0 && line[len-1] == '\n');
+               if (has_trailing_newline)
+                       len--;
+               has_trailing_carriage_return = (len > 0 && line[len-1] == '\r');
+               if (has_trailing_carriage_return)
+                       len--;
+               nofirst = 0;
+       }
+       fputs(set, file);
+       if (!nofirst)
+               fputc(first, file);
+       fwrite(line, len, 1, file);
+       fputs(reset, file);
+       if (has_trailing_carriage_return)
+               fputc('\r', file);
+       if (has_trailing_newline)
+               fputc('\n', file);
+ }
+ static void emit_line(FILE *file, const char *set, const char *reset,
+                     const char *line, int len)
+ {
+       emit_line_0(file, set, reset, line[0], line+1, len-1);
+ }
+ static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len)
+ {
+       if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) &&
+             ecbdata->blank_at_eof_in_preimage &&
+             ecbdata->blank_at_eof_in_postimage &&
+             ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage &&
+             ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage))
+               return 0;
+       return ws_blank_line(line, len, ecbdata->ws_rule);
+ }
+ static void emit_add_line(const char *reset,
+                         struct emit_callback *ecbdata,
+                         const char *line, int len)
+ {
+       const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
+       const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
+       if (!*ws)
+               emit_line_0(ecbdata->file, set, reset, '+', line, len);
+       else if (new_blank_line_at_eof(ecbdata, line, len))
+               /* Blank line at EOF - paint '+' as well */
+               emit_line_0(ecbdata->file, ws, reset, '+', line, len);
+       else {
+               /* Emit just the prefix, then the rest. */
+               emit_line_0(ecbdata->file, set, reset, '+', "", 0);
+               ws_check_emit(line, len, ecbdata->ws_rule,
+                             ecbdata->file, set, reset, ws);
+       }
+ }
  static struct diff_tempfile *claim_diff_tempfile(void) {
        int i;
        for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
@@@ -201,29 -370,6 +370,6 @@@ static void remove_tempfile_on_signal(i
        raise(signo);
  }
  
- static int count_lines(const char *data, int size)
- {
-       int count, ch, completely_empty = 1, nl_just_seen = 0;
-       count = 0;
-       while (0 < size--) {
-               ch = *data++;
-               if (ch == '\n') {
-                       count++;
-                       nl_just_seen = 1;
-                       completely_empty = 0;
-               }
-               else {
-                       nl_just_seen = 0;
-                       completely_empty = 0;
-               }
-       }
-       if (completely_empty)
-               return 0;
-       if (!nl_just_seen)
-               count++; /* no trailing newline */
-       return count;
- }
  static void print_line_count(FILE *file, int count)
  {
        switch (count) {
        }
  }
  
- static void copy_file_with_prefix(FILE *file,
-                                 int prefix, const char *data, int size,
-                                 const char *set, const char *reset)
+ static void emit_rewrite_lines(struct emit_callback *ecb,
+                              int prefix, const char *data, int size)
  {
-       int ch, nl_just_seen = 1;
-       while (0 < size--) {
-               ch = *data++;
-               if (nl_just_seen) {
-                       fputs(set, file);
-                       putc(prefix, file);
+       const char *endp = NULL;
+       static const char *nneof = " No newline at end of file\n";
+       const char *old = diff_get_color(ecb->color_diff, DIFF_FILE_OLD);
+       const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET);
+       while (0 < size) {
+               int len;
+               endp = memchr(data, '\n', size);
+               len = endp ? (endp - data + 1) : size;
+               if (prefix != '+') {
+                       ecb->lno_in_preimage++;
+                       emit_line_0(ecb->file, old, reset, '-',
+                                   data, len);
+               } else {
+                       ecb->lno_in_postimage++;
+                       emit_add_line(reset, ecb, data, len);
                }
-               if (ch == '\n') {
-                       nl_just_seen = 1;
-                       fputs(reset, file);
-               } else
-                       nl_just_seen = 0;
-               putc(ch, file);
+               size -= len;
+               data += len;
+       }
+       if (!endp) {
+               const char *plain = diff_get_color(ecb->color_diff,
+                                                  DIFF_PLAIN);
+               emit_line_0(ecb->file, plain, reset, '\\',
+                           nneof, strlen(nneof));
        }
-       if (!nl_just_seen)
-               fprintf(file, "%s\n\\ No newline at end of file\n", reset);
  }
  
  static void emit_rewrite_diff(const char *name_a,
        const char *name_a_tab, *name_b_tab;
        const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO);
        const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO);
-       const char *old = diff_get_color(color_diff, DIFF_FILE_OLD);
-       const char *new = diff_get_color(color_diff, DIFF_FILE_NEW);
        const char *reset = diff_get_color(color_diff, DIFF_RESET);
        static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
        const char *a_prefix, *b_prefix;
        const char *data_one, *data_two;
        size_t size_one, size_two;
+       struct emit_callback ecbdata;
  
        if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) {
                a_prefix = o->b_prefix;
                size_two = two->size;
        }
  
+       memset(&ecbdata, 0, sizeof(ecbdata));
+       ecbdata.color_diff = color_diff;
+       ecbdata.found_changesp = &o->found_changes;
+       ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
+       ecbdata.file = o->file;
+       if (ecbdata.ws_rule & WS_BLANK_AT_EOF) {
+               mmfile_t mf1, mf2;
+               mf1.ptr = (char *)data_one;
+               mf2.ptr = (char *)data_two;
+               mf1.size = size_one;
+               mf2.size = size_two;
+               check_blank_at_eof(&mf1, &mf2, &ecbdata);
+       }
+       ecbdata.lno_in_preimage = 1;
+       ecbdata.lno_in_postimage = 1;
        lc_a = count_lines(data_one, size_one);
        lc_b = count_lines(data_two, size_two);
        fprintf(o->file,
        print_line_count(o->file, lc_b);
        fprintf(o->file, " @@%s\n", reset);
        if (lc_a)
-               copy_file_with_prefix(o->file, '-', data_one, size_one, old, reset);
+               emit_rewrite_lines(&ecbdata, '-', data_one, size_one);
        if (lc_b)
-               copy_file_with_prefix(o->file, '+', data_two, size_two, new, reset);
- }
- static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
- {
-       if (!DIFF_FILE_VALID(one)) {
-               mf->ptr = (char *)""; /* does not matter */
-               mf->size = 0;
-               return 0;
-       }
-       else if (diff_populate_filespec(one, 0))
-               return -1;
-       mf->ptr = one->data;
-       mf->size = one->size;
-       return 0;
+               emit_rewrite_lines(&ecbdata, '+', data_two, size_two);
  }
  
  struct diff_words_buffer {
@@@ -529,18 -685,6 +685,6 @@@ static void diff_words_show(struct diff
        diff_words->minus.text.size = diff_words->plus.text.size = 0;
  }
  
- typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
- struct emit_callback {
-       int nparents, color_diff;
-       unsigned ws_rule;
-       sane_truncate_fn truncate;
-       const char **label_path;
-       struct diff_words_data *diff_words;
-       int *found_changesp;
-       FILE *file;
- };
  static void free_diff_words_data(struct emit_callback *ecbdata)
  {
        if (ecbdata->diff_words) {
@@@ -566,42 -710,6 +710,6 @@@ const char *diff_get_color(int diff_use
        return "";
  }
  
- static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len)
- {
-       int has_trailing_newline, has_trailing_carriage_return;
-       has_trailing_newline = (len > 0 && line[len-1] == '\n');
-       if (has_trailing_newline)
-               len--;
-       has_trailing_carriage_return = (len > 0 && line[len-1] == '\r');
-       if (has_trailing_carriage_return)
-               len--;
-       fputs(set, file);
-       fwrite(line, len, 1, file);
-       fputs(reset, file);
-       if (has_trailing_carriage_return)
-               fputc('\r', file);
-       if (has_trailing_newline)
-               fputc('\n', file);
- }
- static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len)
- {
-       const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
-       const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
-       if (!*ws)
-               emit_line(ecbdata->file, set, reset, line, len);
-       else {
-               /* Emit just the prefix, then the rest. */
-               emit_line(ecbdata->file, set, reset, line, ecbdata->nparents);
-               ws_check_emit(line + ecbdata->nparents,
-                             len - ecbdata->nparents, ecbdata->ws_rule,
-                             ecbdata->file, set, reset, ws);
-       }
- }
  static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len)
  {
        const char *cp;
        return allot - l;
  }
  
+ static void find_lno(const char *line, struct emit_callback *ecbdata)
+ {
+       const char *p;
+       ecbdata->lno_in_preimage = 0;
+       ecbdata->lno_in_postimage = 0;
+       p = strchr(line, '-');
+       if (!p)
+               return; /* cannot happen */
+       ecbdata->lno_in_preimage = strtol(p + 1, NULL, 10);
+       p = strchr(p, '+');
+       if (!p)
+               return; /* cannot happen */
+       ecbdata->lno_in_postimage = strtol(p + 1, NULL, 10);
+ }
  static void fn_out_consume(void *priv, char *line, unsigned long len)
  {
-       int i;
-       int color;
        struct emit_callback *ecbdata = priv;
        const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
        const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
                len = 1;
        }
  
-       /* This is not really necessary for now because
-        * this codepath only deals with two-way diffs.
-        */
-       for (i = 0; i < len && line[i] == '@'; i++)
-               ;
-       if (2 <= i && i < len && line[i] == ' ') {
-               ecbdata->nparents = i - 1;
+       if (line[0] == '@') {
                len = sane_truncate_line(ecbdata, line, len);
+               find_lno(line, ecbdata);
                emit_line(ecbdata->file,
                          diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO),
                          reset, line, len);
                return;
        }
  
-       if (len < ecbdata->nparents) {
+       if (len < 1) {
                emit_line(ecbdata->file, reset, reset, line, len);
                return;
        }
  
-       color = DIFF_PLAIN;
-       if (ecbdata->diff_words && ecbdata->nparents != 1)
-               /* fall back to normal diff */
-               free_diff_words_data(ecbdata);
        if (ecbdata->diff_words) {
                if (line[0] == '-') {
                        diff_words_append(line, len,
                emit_line(ecbdata->file, plain, reset, line, len);
                return;
        }
-       for (i = 0; i < ecbdata->nparents && len; i++) {
-               if (line[i] == '-')
-                       color = DIFF_FILE_OLD;
-               else if (line[i] == '+')
-                       color = DIFF_FILE_NEW;
-       }
  
-       if (color != DIFF_FILE_NEW) {
-               emit_line(ecbdata->file,
-                         diff_get_color(ecbdata->color_diff, color),
-                         reset, line, len);
-               return;
+       if (line[0] != '+') {
+               const char *color =
+                       diff_get_color(ecbdata->color_diff,
+                                      line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN);
+               ecbdata->lno_in_preimage++;
+               if (line[0] == ' ')
+                       ecbdata->lno_in_postimage++;
+               emit_line(ecbdata->file, color, reset, line, len);
+       } else {
+               ecbdata->lno_in_postimage++;
+               emit_add_line(reset, ecbdata, line + 1, len - 1);
        }
-       emit_add_line(reset, ecbdata, line, len);
  }
  
  static char *pprint_rename(const char *a, const char *b)
@@@ -1211,7 -1322,6 +1322,6 @@@ struct checkdiff_t 
        struct diff_options *o;
        unsigned ws_rule;
        unsigned status;
-       int trailing_blanks_start;
  };
  
  static int is_conflict_marker(const char *line, unsigned long len)
@@@ -1255,10 -1365,6 +1365,6 @@@ static void checkdiff_consume(void *pri
        if (line[0] == '+') {
                unsigned bad;
                data->lineno++;
-               if (!ws_blank_line(line + 1, len - 1, data->ws_rule))
-                       data->trailing_blanks_start = 0;
-               else if (!data->trailing_blanks_start)
-                       data->trailing_blanks_start = data->lineno;
                if (is_conflict_marker(line + 1, len - 1)) {
                        data->status |= 1;
                        fprintf(data->o->file,
                              data->o->file, set, reset, ws);
        } else if (line[0] == ' ') {
                data->lineno++;
-               data->trailing_blanks_start = 0;
        } else if (line[0] == '@') {
                char *plus = strchr(line, '+');
                if (plus)
                        data->lineno = strtol(plus, NULL, 10) - 1;
                else
                        die("invalid diff");
-               data->trailing_blanks_start = 0;
        }
  }
  
@@@ -1562,6 -1666,8 +1666,8 @@@ static void builtin_diff(const char *na
                ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
                ecbdata.found_changesp = &o->found_changes;
                ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
+               if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
+                       check_blank_at_eof(&mf1, &mf2, &ecbdata);
                ecbdata.file = o->file;
                xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xecfg.ctxlen = o->context;
@@@ -1704,11 -1810,22 +1810,22 @@@ static void builtin_checkdiff(const cha
                xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
                              &xpp, &xecfg, &ecb);
  
-               if ((data.ws_rule & WS_TRAILING_SPACE) &&
-                   data.trailing_blanks_start) {
-                       fprintf(o->file, "%s:%d: ends with blank lines.\n",
-                               data.filename, data.trailing_blanks_start);
-                       data.status = 1; /* report errors */
+               if (data.ws_rule & WS_BLANK_AT_EOF) {
+                       struct emit_callback ecbdata;
+                       int blank_at_eof;
+                       ecbdata.ws_rule = data.ws_rule;
+                       check_blank_at_eof(&mf1, &mf2, &ecbdata);
+                       blank_at_eof = ecbdata.blank_at_eof_in_preimage;
+                       if (blank_at_eof) {
+                               static char *err;
+                               if (!err)
+                                       err = whitespace_error_string(WS_BLANK_AT_EOF);
+                               fprintf(o->file, "%s:%d: %s.\n",
+                                       data.filename, blank_at_eof, err);
+                               data.status = 1; /* report errors */
+                       }
                }
        }
   free_and_return:
@@@ -2691,7 -2808,7 +2808,7 @@@ static int parse_num(const char **cp_p
        num = 0;
        scale = 1;
        dot = 0;
 -      for(;;) {
 +      for (;;) {
                ch = *cp;
                if ( !dot && ch == '.' ) {
                        scale = 1;