git-merge-changelog does its I/O in text mode. This is explained in the sources for the input direction:
/* Read a ChangeLog file into memory. Return the contents in *RESULT. */ static void read_changelog_file (const char *filename, struct changelog_file *result) { /* Read the file in text mode, otherwise it's hard to recognize empty lines. */ But no similar explanation is provided for the output. The result is that, when the program runs on MS-Windows, the merged ChangeLog file is always output with the DOS-style CR-LF EOLs. This gets in the way when git's core.autocrlf config option is set to false, because almost all projects have ChangeLog files in Unix LF-only EOL format, and the merged file will have all of its lines modified. Oops! The changes below make git-merge-changelog on Windows attempt to preserve the EOL format of the original file. 2014-01-18 Eli Zaretskii <e...@gnu.org> Preserve the EOL format of the original ChangeLog file. * git-merge-changelog.c (detect_eol): New function. (main): Use it to detect the EOL format of the destination file (%A). If %A does not have DOS CR-LF EOL format, write the output in binary mode. --- gllib/git-merge-changelog.c~ 2013-01-24 07:30:17.000000000 +0200 +++ gllib/git-merge-changelog.c 2014-01-18 11:32:16.140625000 +0200 @@ -279,6 +279,48 @@ return similarity; } +/* Read the initial block of a ChangeLog file, and return true if it + uses the DOS/Windows CR-LF end-of-line format, false otherwise. */ +static bool +detect_eol (const char *destfn, const char *ancestorfn) +{ +#ifdef __MINGW32__ + /* ChangeLog files have relatively short lines, so we don't need to + read too much stuff, or worry about too long lines. */ + char buf[256]; + FILE *f = fopen (destfn, "rb"); + bool result = false; /* default to Unix-style EOLs */ + size_t nread; + + /* If the destination doesn't exist, inherit the EOL format from the + ancestor. */ + if (!f) + f = fopen (ancestorfn, "rb"); + if (f && (nread = fread (buf, 1, sizeof (buf) - 1, f)) > 0) + { + char *p; + + buf[nread] = '\0'; + /* We assume consistent EOL format throughout the file, so it's + enough to examine the first end-of-line. */ + p = strchr (buf, '\n'); + + if (p > buf) + { + if (p[-1] == '\r') + result = true; + } + } + + if (f) + fclose (f); + + return result; +#else /* !__MINGW32__ */ + return false; +#endif +} + /* This structure represents an entire ChangeLog file, after it was read into memory. */ struct changelog_file @@ -1079,6 +1121,7 @@ gl_list_node_t *result_entries_pointers; /* array of pointers into result_entries */ gl_list_t /* <struct entry *> */ result_entries; gl_list_t /* <struct conflict *> */ result_conflicts; + bool crlf; /* if true, A-FILE-NAME has DOS CR-LF EOL format */ ancestor_file_name = argv[optind]; destination_file_name = argv[optind + 1]; @@ -1186,6 +1229,10 @@ modified_file_name = other_file_name; } + /* We want to preserve the EOL format of the destination file, + which is the ChangeLog file in the current branch. */ + crlf = detect_eol (destination_file_name, ancestor_file_name); + /* Read the three files into memory. */ read_changelog_file (ancestor_file_name, &ancestor_file); read_changelog_file (mainstream_file_name, &mainstream_file); @@ -1648,7 +1695,7 @@ /* Output the result. */ { - FILE *fp = fopen (destination_file_name, "w"); + FILE *fp = fopen (destination_file_name, crlf ? "w" : "wb"); if (fp == NULL) { fprintf (stderr, "could not write file '%s'\n", destination_file_name);