]> git.ipfire.org Git - thirdparty/gnulib.git/commitdiff
git-merge-changelog: Remove module.
authorBruno Haible <bruno@clisp.org>
Tue, 5 Aug 2025 21:16:15 +0000 (23:16 +0200)
committerBruno Haible <bruno@clisp.org>
Tue, 5 Aug 2025 21:20:41 +0000 (23:20 +0200)
It is now available through
$ git clone https://git.savannah.gnu.org/git/vc-changelog.git
see https://gitweb.git.savannah.gnu.org/gitweb/?p=vc-changelog.git .

* lib/git-merge-changelog.c: Remove file.
* modules/git-merge-changelog: Remove file.
* NEWS: Mention the change.

ChangeLog
NEWS
lib/git-merge-changelog.c [deleted file]
modules/git-merge-changelog [deleted file]

index ab88105b472b44aeecbbba31fe337d39cd92c4b4..df3ab2ed6b29cdbbcaeec6598962c6f73736a83b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2025-08-05  Bruno Haible  <bruno@clisp.org>
+
+       git-merge-changelog: Remove module.
+       It is now available through
+       $ git clone https://git.savannah.gnu.org/git/vc-changelog.git
+       see https://gitweb.git.savannah.gnu.org/gitweb/?p=vc-changelog.git .
+       * lib/git-merge-changelog.c: Remove file.
+       * modules/git-merge-changelog: Remove file.
+       * NEWS: Mention the change.
+
 2025-08-04  Bruno Haible  <bruno@clisp.org>
 
        nlcanon tests: Fix last commit.
diff --git a/NEWS b/NEWS
index 08981f26e96055675d8383b89a7a394e530b3725..664007395de556f5ddcb742d89311383d72bdea9 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -74,6 +74,10 @@ User visible incompatible changes
 
 Date        Modules         Changes
 
+2025-08-05  git-merge-changelog  This module is removed.  Use the package from
+                            https://git.savannah.gnu.org/git/vc-changelog.git
+                            instead.
+
 2025-07-13  safe-alloc      This module is now obsolete.  Also, it no longer
                             defines the REALLOC_N macro.
 
diff --git a/lib/git-merge-changelog.c b/lib/git-merge-changelog.c
deleted file mode 100644 (file)
index d7703bc..0000000
+++ /dev/null
@@ -1,1879 +0,0 @@
-/* git-merge-changelog - git "merge" driver for GNU style ChangeLog files.
-   Copyright (C) 2008-2025 Bruno Haible <bruno@clisp.org>
-
-   This file is free software: you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published
-   by the Free Software Foundation, either version 3 of the License,
-   or (at your option) any later version.
-
-   This file is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
-
-/* README:
-   The default merge driver of 'git' *always* produces conflicts when
-   pulling public modifications into a privately modified ChangeLog file.
-   This is because ChangeLog files are always modified at the top; the
-   default merge driver has no clue how to deal with this. Furthermore
-   the conflicts are presented with more <<<< ==== >>>> markers than
-   necessary; this is because the default merge driver makes pointless
-   efforts to look at the individual line changes inside a ChangeLog entry.
-
-   This program serves as a 'git' merge driver that avoids these problems.
-   1. It produces no conflict when ChangeLog entries have been inserted
-      at the top both in the public and in the private modification. It
-      puts the privately added entries above the publicly added entries.
-   2. It respects the structure of ChangeLog files: entries are not split
-      into lines but kept together.
-   3. It also handles the case of small modifications of past ChangeLog
-      entries, or of removed ChangeLog entries: they are merged as one
-      would expect it.
-   4. Conflicts are presented at the top of the file, rather than where
-      they occurred, so that the user will see them immediately. (Unlike
-      for source code written in some programming language, conflict markers
-      that are located several hundreds lines from the top will not cause
-      any syntax error and therefore would be likely to remain unnoticed.)
- */
-
-/* Installation:
-
-   $ ./gnulib-tool --create-testdir --dir=/tmp/testdir123 git-merge-changelog
-   $ cd /tmp/testdir123
-   $ ./configure
-   $ make
-   $ make install
-
-   Additionally, for git users:
-     - Add to .git/config of the checkout (or to your $HOME/.gitconfig) the
-       lines
-
-          [merge "merge-changelog"]
-                  name = GNU-style ChangeLog merge driver
-                  driver = /usr/local/bin/git-merge-changelog %O %A %B "%Y"
-
-     - Add to the top-level directory of the checkout a file '.gitattributes'
-       with this line:
-
-          ChangeLog    merge=merge-changelog
-
-       (See "man 5 gitattributes" for more info.)
-
-   Additionally, for bzr users:
-     - Install the 'extmerge' bzr plug-in listed at
-         <http://doc.bazaar.canonical.com/plugins/en/index.html>
-         <http://wiki.bazaar.canonical.com/BzrPlugins>
-     - Add to your $HOME/.bazaar/bazaar.conf the line
-
-          external_merge = git-merge-changelog %b %T %o
-
-     - Then, to merge a conflict in a ChangeLog file, use
-
-          $ bzr extmerge ChangeLog
-
-   Additionally, for hg users:
-     - Add to your $HOME/.hgrc the lines
-
-          [merge-patterns]
-          ChangeLog = git-merge-changelog
-
-          [merge-tools]
-          git-merge-changelog.executable = /usr/local/bin/git-merge-changelog
-          git-merge-changelog.args = $base $local $other
-
-       See <https://www.selenic.com/mercurial/hgrc.5.html> section merge-tools
-       for reference.
- */
-
-/* Use as an alternative to 'diff3':
-   git-merge-changelog performs the same role as "diff3 -m", just with
-   reordered arguments:
-     $ git-merge-changelog %O %A %B
-   is comparable to
-     $ diff3 -m %A %O %B
- */
-
-/* Calling convention:
-   A merge driver is called with three filename arguments:
-     1. %O = The common ancestor of %A and %B.
-     2. %A = The file's contents from the "current branch".
-     3. %B = The file's contents from the "other branch"; this is the contents
-        being merged in.
-
-   In case of a "git stash apply" or of an upstream pull (e.g. from a subsystem
-   maintainer to a central maintainer) or of a downstream pull with --rebase:
-     2. %A = The file's newest pulled contents; modified by other committers.
-     3. %B = The user's newest copy of the file; modified by the user.
-   In case of a downstream pull (e.g. from a central repository to the user)
-   or of an upstream pull with --rebase:
-     2. %A = The user's newest copy of the file; modified by the user.
-     3. %B = The file's newest pulled contents; modified by other committers.
-
-   It should write its merged output into file %A. It can also echo some
-   remarks to stdout.  It should exit with return code 0 if the merge could
-   be resolved cleanly, or with non-zero return code if there were conflicts.
- */
-
-/* How it works:
-   The structure of a ChangeLog file: It consists of ChangeLog entries. A
-   ChangeLog entry starts at a line following a blank line and that starts with
-   a non-whitespace character, or at the beginning of a file.
-   The merge driver works as follows: It reads the three files into memory and
-   dissects them into ChangeLog entries. It then finds the differences between
-   %O and %B. They are classified as:
-     - removals (some consecutive entries removed),
-     - changes (some consecutive entries removed, some consecutive entries
-       added),
-     - additions (some consecutive entries added).
-   The driver then attempts to apply the changes to %A.
-   To this effect, it first computes a correspondence between the entries in %O
-   and the entries in %A, using fuzzy string matching to still identify changed
-   entries.
-     - Removals are applied one by one. If the entry is present in %A, at any
-       position, it is removed. If not, the removal is marked as a conflict.
-     - Additions at the top of %B are applied at the top of %A.
-     - Additions between entry x and entry y (y may be the file end) in %B are
-       applied between entry x and entry y in %A (if they still exist and are
-       still consecutive in %A), otherwise the additions are marked as a
-       conflict.
-     - Changes are categorized into "simple changes":
-         entry1 ... entryn
-       are mapped to
-         added_entry ... added_entry modified_entry1 ... modified_entryn,
-       where the correspondence between entry_i and modified_entry_i is still
-       clear; and "big changes": these are all the rest. Simple changes at the
-       top of %B are applied by putting the added entries at the top of %A. The
-       changes in simple changes are applied one by one; possibly leading to
-       single-entry conflicts. Big changes are applied en bloc, possibly
-       leading to conflicts spanning multiple entries.
-     - Conflicts are output at the top of the file and cause an exit status of
-       1.
- */
-
-#include <config.h>
-
-#include <errno.h>
-#include <getopt.h>
-#include <limits.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <error.h>
-#include "idx.h"
-#include "read-file.h"
-#include "spawn-pipe.h"
-#include "wait-process.h"
-#include "gl_xlist.h"
-#include "gl_array_list.h"
-#include "gl_linkedhash_list.h"
-#include "gl_rbtreehash_list.h"
-#include "gl_linked_list.h"
-#include "xalloc.h"
-#include "xmalloca.h"
-#include "xvasprintf.h"
-#include "fstrcmp.h"
-#include "minmax.h"
-#include "c-ctype.h"
-#include "c-strstr.h"
-#include "fwriteerror.h"
-
-/* No internationalization here.  */
-#define _(msgid) msgid
-
-#define ASSERT(expr) \
-  do                                                                         \
-    {                                                                        \
-      if (!(expr))                                                           \
-        abort ();                                                            \
-    }                                                                        \
-  while (0)
-
-#define FSTRCMP_THRESHOLD 0.6
-#define FSTRCMP_STRICTER_THRESHOLD 0.8
-
-/* Representation of a ChangeLog entry.
-   The string may contain NUL bytes; therefore it is represented as a plain
-   opaque memory region.  */
-struct entry
-{
-  char *string;
-  idx_t length;
-  /* Cache for the hash code.  */
-  bool hashcode_cached;
-  size_t hashcode;
-};
-
-/* Create an entry.
-   The memory region passed by the caller must of indefinite extent.  It is
-   *not* copied here.  */
-static struct entry *
-entry_create (char *string, idx_t length)
-{
-  struct entry *result = XMALLOC (struct entry);
-  result->string = string;
-  result->length = length;
-  result->hashcode_cached = false;
-  return result;
-}
-
-/* Compare two entries for equality.  */
-static bool
-entry_equals (const void *elt1, const void *elt2)
-{
-  const struct entry *entry1 = (const struct entry *) elt1;
-  const struct entry *entry2 = (const struct entry *) elt2;
-  return entry1->length == entry2->length
-         && memcmp (entry1->string, entry2->string, entry1->length) == 0;
-}
-
-/* Return a hash code of the contents of a ChangeLog entry.  */
-static size_t
-entry_hashcode (const void *elt)
-{
-  struct entry *entry = (struct entry *) elt;
-  if (!entry->hashcode_cached)
-    {
-      /* See https://www.haible.de/bruno/hashfunc.html.  */
-      const char *s;
-      idx_t n;
-      size_t h = 0;
-
-      for (s = entry->string, n = entry->length; n > 0; s++, n--)
-        h = (unsigned char) *s + ((h << 9) | (h >> (SIZE_WIDTH - 9)));
-
-      entry->hashcode = h;
-      entry->hashcode_cached = true;
-    }
-  return entry->hashcode;
-}
-
-/* Perform a fuzzy comparison of two ChangeLog entries.
-   Return a similarity measure of the two entries, a value between 0 and 1.
-   0 stands for very distinct, 1 for identical.
-   If the result is < LOWER_BOUND, an arbitrary other value < LOWER_BOUND can
-   be returned.  */
-static double
-entry_fstrcmp (const struct entry *entry1, const struct entry *entry2,
-               double lower_bound)
-{
-  /* fstrcmp works only on NUL terminated strings.  */
-  char *memory;
-  double similarity;
-
-  if (memchr (entry1->string, '\0', entry1->length) != NULL)
-    return 0.0;
-  if (memchr (entry2->string, '\0', entry2->length) != NULL)
-    return 0.0;
-  memory = (char *) xmalloca (entry1->length + 1 + entry2->length + 1);
-  {
-    char *p = memory;
-    memcpy (p, entry1->string, entry1->length);
-    p += entry1->length;
-    *p++ = '\0';
-    memcpy (p, entry2->string, entry2->length);
-    p += entry2->length;
-    *p++ = '\0';
-  }
-  similarity =
-    fstrcmp_bounded (memory, memory + entry1->length + 1, lower_bound);
-  freea (memory);
-  return similarity;
-}
-
-/* This structure represents an entire ChangeLog file, after it was read
-   into memory.  */
-struct changelog_file
-{
-  /* The entries, as a list.  */
-  gl_list_t /* <struct entry *> */ entries_list;
-  /* The entries, as a list in opposite direction.  */
-  gl_list_t /* <struct entry *> */ entries_reversed;
-  /* The entries, as an array.  */
-  idx_t num_entries;
-  struct entry **entries;
-};
-
-/* 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.  */
-  size_t length;
-  char *contents = read_file (filename, 0, &length);
-  if (contents == NULL)
-    {
-      fprintf (stderr, "could not read file '%s'\n", filename);
-      exit (EXIT_FAILURE);
-    }
-
-  result->entries_list =
-    gl_list_create_empty (GL_LINKEDHASH_LIST, entry_equals, entry_hashcode,
-                          NULL, true);
-  result->entries_reversed =
-    gl_list_create_empty (GL_RBTREEHASH_LIST, entry_equals, entry_hashcode,
-                          NULL, true);
-  /* A ChangeLog file consists of ChangeLog entries.  A ChangeLog entry starts
-     at a line following a blank line and that starts with a non-whitespace
-     character, or at the beginning of a file.
-     Split the file contents into entries.  */
-  {
-    char *contents_end = contents + length;
-    char *start = contents;
-    while (start < contents_end)
-      {
-        /* Search the end of the current entry.  */
-        char *ptr = start;
-        struct entry *curr;
-
-        while (ptr < contents_end)
-          {
-            ptr = memchr (ptr, '\n', contents_end - ptr);
-            if (ptr == NULL)
-              {
-                ptr = contents_end;
-                break;
-              }
-            ptr++;
-            if (contents_end - ptr >= 2
-                && ptr[0] == '\n'
-                && !(ptr[1] == '\n' || ptr[1] == '\t' || ptr[1] == ' '))
-              {
-                ptr++;
-                break;
-              }
-          }
-
-        curr = entry_create (start, ptr - start);
-        gl_list_add_last (result->entries_list, curr);
-        gl_list_add_first (result->entries_reversed, curr);
-
-        start = ptr;
-      }
-  }
-
-  result->num_entries = gl_list_size (result->entries_list);
-  result->entries = XNMALLOC (result->num_entries, struct entry *);
-  {
-    idx_t index = 0;
-    gl_list_iterator_t iter = gl_list_iterator (result->entries_list);
-    const void *elt;
-    gl_list_node_t node;
-    while (gl_list_iterator_next (&iter, &elt, &node))
-      result->entries[index++] = (struct entry *) elt;
-    gl_list_iterator_free (&iter);
-    ASSERT (index == result->num_entries);
-  }
-}
-
-/* A mapping (correspondence) between entries of FILE1 and of FILE2.  */
-struct entries_mapping
-{
-  struct changelog_file *file1;
-  struct changelog_file *file2;
-  /* Mapping from indices in FILE1 to indices in FILE2.
-     A value -1 means that the entry from FILE1 is not found in FILE2.
-     A value -2 means that it has not yet been computed.  */
-  ptrdiff_t *index_mapping;
-  /* Mapping from indices in FILE2 to indices in FILE1.
-     A value -1 means that the entry from FILE2 is not found in FILE1.
-     A value -2 means that it has not yet been computed.  */
-  ptrdiff_t *index_mapping_reverse;
-};
-
-/* Look up (or lazily compute) the mapping of an entry in FILE1.
-   i is the index in FILE1.
-   Return the index in FILE2, or -1 when the entry is not found in FILE2.  */
-static ptrdiff_t
-entries_mapping_get (struct entries_mapping *mapping, idx_t i)
-{
-  if (mapping->index_mapping[i] < -1)
-    {
-      struct changelog_file *file1 = mapping->file1;
-      struct changelog_file *file2 = mapping->file2;
-      idx_t n1 = file1->num_entries;
-      idx_t n2 = file2->num_entries;
-      struct entry *entry_i = file1->entries[i];
-      idx_t j;
-
-      /* Search whether it approximately occurs in file2.  */
-      ptrdiff_t best_j = -1;
-      double best_j_similarity = 0.0;
-      for (j = n2; j > 0; )
-        {
-          j--;
-          if (mapping->index_mapping_reverse[j] < 0)
-            {
-              double similarity =
-                entry_fstrcmp (entry_i, file2->entries[j], best_j_similarity);
-              if (similarity > best_j_similarity)
-                {
-                  best_j = j;
-                  best_j_similarity = similarity;
-                }
-            }
-        }
-      if (best_j_similarity >= FSTRCMP_THRESHOLD)
-        {
-          /* Found a similar entry in file2.  */
-          struct entry *entry_j = file2->entries[best_j];
-          /* Search whether it approximately occurs in file1 at index i.  */
-          ptrdiff_t best_i = -1;
-          double best_i_similarity = 0.0;
-          idx_t ii;
-          for (ii = n1; ii > 0; )
-            {
-              ii--;
-              if (mapping->index_mapping[ii] < 0)
-                {
-                  double similarity =
-                    entry_fstrcmp (file1->entries[ii], entry_j,
-                                   best_i_similarity);
-                  if (similarity > best_i_similarity)
-                    {
-                      best_i = ii;
-                      best_i_similarity = similarity;
-                    }
-                }
-            }
-          if (best_i_similarity >= FSTRCMP_THRESHOLD && best_i == i)
-            {
-              mapping->index_mapping[i] = best_j;
-              mapping->index_mapping_reverse[best_j] = i;
-            }
-        }
-      if (mapping->index_mapping[i] < -1)
-        /* It does not approximately occur in FILE2.
-           Remember it, for next time.  */
-        mapping->index_mapping[i] = -1;
-    }
-  return mapping->index_mapping[i];
-}
-
-/* Look up (or lazily compute) the mapping of an entry in FILE2.
-   j is the index in FILE2.
-   Return the index in FILE1, or -1 when the entry is not found in FILE1.  */
-static ptrdiff_t
-entries_mapping_reverse_get (struct entries_mapping *mapping, idx_t j)
-{
-  if (mapping->index_mapping_reverse[j] < -1)
-    {
-      struct changelog_file *file1 = mapping->file1;
-      struct changelog_file *file2 = mapping->file2;
-      idx_t n1 = file1->num_entries;
-      idx_t n2 = file2->num_entries;
-      struct entry *entry_j = file2->entries[j];
-      idx_t i;
-
-      /* Search whether it approximately occurs in file1.  */
-      ptrdiff_t best_i = -1;
-      double best_i_similarity = 0.0;
-      for (i = n1; i > 0; )
-        {
-          i--;
-          if (mapping->index_mapping[i] < 0)
-            {
-              double similarity =
-                entry_fstrcmp (file1->entries[i], entry_j, best_i_similarity);
-              if (similarity > best_i_similarity)
-                {
-                  best_i = i;
-                  best_i_similarity = similarity;
-                }
-            }
-        }
-      if (best_i_similarity >= FSTRCMP_THRESHOLD)
-        {
-          /* Found a similar entry in file1.  */
-          struct entry *entry_i = file1->entries[best_i];
-          /* Search whether it approximately occurs in file2 at index j.  */
-          ptrdiff_t best_j = -1;
-          double best_j_similarity = 0.0;
-          idx_t jj;
-          for (jj = n2; jj > 0; )
-            {
-              jj--;
-              if (mapping->index_mapping_reverse[jj] < 0)
-                {
-                  double similarity =
-                    entry_fstrcmp (entry_i, file2->entries[jj],
-                                   best_j_similarity);
-                  if (similarity > best_j_similarity)
-                    {
-                      best_j = jj;
-                      best_j_similarity = similarity;
-                    }
-                }
-            }
-          if (best_j_similarity >= FSTRCMP_THRESHOLD && best_j == j)
-            {
-              mapping->index_mapping_reverse[j] = best_i;
-              mapping->index_mapping[best_i] = j;
-            }
-        }
-      if (mapping->index_mapping_reverse[j] < -1)
-        /* It does not approximately occur in FILE1.
-           Remember it, for next time.  */
-        mapping->index_mapping_reverse[j] = -1;
-    }
-  return mapping->index_mapping_reverse[j];
-}
-
-/* Compute a mapping (correspondence) between entries of FILE1 and of FILE2.
-   The correspondence also takes into account small modifications; i.e. the
-   indicated relation is not equality of entries but best-match similarity
-   of entries.
-   If FULL is true, the maximum of matching is done up-front.  If it is false,
-   it is done in a lazy way through the functions entries_mapping_get and
-   entries_mapping_reverse_get.
-   Return the result in *RESULT.  */
-static void
-compute_mapping (struct changelog_file *file1, struct changelog_file *file2,
-                 bool full,
-                 struct entries_mapping *result)
-{
-  /* Mapping from indices in file1 to indices in file2.  */
-  ptrdiff_t *index_mapping;
-  /* Mapping from indices in file2 to indices in file1.  */
-  ptrdiff_t *index_mapping_reverse;
-  idx_t n1 = file1->num_entries;
-  idx_t n2 = file2->num_entries;
-  idx_t i, j;
-
-  index_mapping = XNMALLOC (n1, ptrdiff_t);
-  for (i = 0; i < n1; i++)
-    index_mapping[i] = -2;
-
-  index_mapping_reverse = XNMALLOC (n2, ptrdiff_t);
-  for (j = 0; j < n2; j++)
-    index_mapping_reverse[j] = -2;
-
-  for (i = n1; i > 0; )
-    {
-      i--;
-      /* Take an entry from file1.  */
-      if (index_mapping[i] < -1)
-        {
-          struct entry *entry = file1->entries[i];
-          /* Search whether it occurs in file2.  */
-          size_t jrev = gl_list_indexof (file2->entries_reversed, entry);
-          if (jrev != (size_t)(-1))
-            {
-              j = n2 - 1 - jrev;
-              /* Found an exact correspondence.  */
-              /* If index_mapping_reverse[j] >= 0, we have already seen other
-                 copies of this entry, and there were more occurrences of it in
-                 file1 than in file2.  In this case, do nothing.  */
-              if (index_mapping_reverse[j] < 0)
-                {
-                  index_mapping[i] = j;
-                  index_mapping_reverse[j] = i;
-                  /* Look for more occurrences of the same entry.  Match them
-                     as long as they pair up.  Unpaired occurrences of the same
-                     entry are left without mapping.  */
-                  {
-                    idx_t curr_i = i;
-                    idx_t curr_j = j;
-
-                    for (;;)
-                      {
-                        size_t next_i =
-                          gl_list_indexof_from (file1->entries_reversed,
-                                                n1 - curr_i, entry);
-                        if (next_i == (size_t)(-1))
-                          break;
-                        size_t next_j =
-                          gl_list_indexof_from (file2->entries_reversed,
-                                                n2 - curr_j, entry);
-                        if (next_j == (size_t)(-1))
-                          break;
-                        curr_i = n1 - 1 - next_i;
-                        curr_j = n2 - 1 - next_j;
-                        ASSERT (index_mapping[curr_i] < 0);
-                        ASSERT (index_mapping_reverse[curr_j] < 0);
-                        index_mapping[curr_i] = curr_j;
-                        index_mapping_reverse[curr_j] = curr_i;
-                      }
-                  }
-                }
-            }
-        }
-    }
-
-  result->file1 = file1;
-  result->file2 = file2;
-  result->index_mapping = index_mapping;
-  result->index_mapping_reverse = index_mapping_reverse;
-
-  if (full)
-    for (i = n1; i > 0; )
-      {
-        i--;
-        entries_mapping_get (result, i);
-      }
-}
-
-/* An "edit" is a textual modification performed by the user, that needs to
-   be applied to the other file.  */
-enum edit_type
-{
-  /* Some consecutive entries were added.  */
-  ADDITION,
-  /* Some consecutive entries were removed; some other consecutive entries
-     were added at the same position.  (Not necessarily the same number of
-     entries.)  */
-  CHANGE,
-  /* Some consecutive entries were removed.  */
-  REMOVAL
-};
-
-/* This structure represents an edit.  */
-struct edit
-{
-  enum edit_type type;
-  /* Range of indices into the entries of FILE1.  */
-  idx_t i1, i2;       /* first, last index; only used for CHANGE, REMOVAL */
-  /* Range of indices into the entries of FILE2.  */
-  idx_t j1, j2;       /* first, last index; only used for ADDITION, CHANGE */
-};
-
-/* This structure represents the differences from one file, FILE1, to another
-   file, FILE2.  */
-struct differences
-{
-  /* An array mapping FILE1 indices to FILE2 indices (or -1 when the entry
-     from FILE1 is not found in FILE2).  */
-  ptrdiff_t *index_mapping;
-  /* An array mapping FILE2 indices to FILE1 indices (or -1 when the entry
-     from FILE2 is not found in FILE1).  */
-  ptrdiff_t *index_mapping_reverse;
-  /* The edits that transform FILE1 into FILE2.  */
-  idx_t num_edits;
-  struct edit **edits;
-};
-
-/* Import the difference detection algorithm from GNU diff.  */
-#define ELEMENT struct entry *
-#define EQUAL entry_equals
-#define OFFSET ptrdiff_t
-#define OFFSET_MAX PTRDIFF_MAX
-#define EXTRA_CONTEXT_FIELDS \
-  ptrdiff_t *index_mapping; \
-  ptrdiff_t *index_mapping_reverse;
-#define NOTE_DELETE(ctxt, xoff) \
-  ctxt->index_mapping[xoff] = -1
-#define NOTE_INSERT(ctxt, yoff) \
-  ctxt->index_mapping_reverse[yoff] = -1
-#include "diffseq.h"
-
-/* Compute the differences between the entries of FILE1 and the entries of
-   FILE2.  */
-static void
-compute_differences (struct changelog_file *file1, struct changelog_file *file2,
-                     struct differences *result)
-{
-  /* Unlike compute_mapping, which mostly ignores the order of the entries and
-     therefore works well when some entries are permuted, here we use the order.
-     I think this is needed in order to distinguish changes from
-     additions+removals; I don't know how to say what is a "change" if the
-     files are considered as unordered sets of entries.  */
-  struct context ctxt;
-  idx_t n1 = file1->num_entries;
-  idx_t n2 = file2->num_entries;
-  idx_t i;
-  idx_t j;
-  gl_list_t /* <struct edit *> */ edits;
-
-  ctxt.xvec = file1->entries;
-  ctxt.yvec = file2->entries;
-  ctxt.index_mapping = XNMALLOC (n1, ptrdiff_t);
-  for (i = 0; i < n1; i++)
-    ctxt.index_mapping[i] = 0;
-  ctxt.index_mapping_reverse = XNMALLOC (n2, ptrdiff_t);
-  for (j = 0; j < n2; j++)
-    ctxt.index_mapping_reverse[j] = 0;
-  ctxt.fdiag = XNMALLOC (2 * (n1 + n2 + 3), ptrdiff_t) + n2 + 1;
-  ctxt.bdiag = ctxt.fdiag + n1 + n2 + 3;
-  ctxt.too_expensive = n1 + n2;
-
-  /* Store in ctxt.index_mapping and ctxt.index_mapping_reverse a -1 for
-     each removed or added entry.  */
-  compareseq (0, n1, 0, n2, 0, &ctxt);
-
-  /* Complete the index_mapping and index_mapping_reverse arrays.  */
-  i = 0;
-  j = 0;
-  while (i < n1 || j < n2)
-    {
-      while (i < n1 && ctxt.index_mapping[i] < 0)
-        i++;
-      while (j < n2 && ctxt.index_mapping_reverse[j] < 0)
-        j++;
-      ASSERT ((i < n1) == (j < n2));
-      if (i == n1 && j == n2)
-        break;
-      ctxt.index_mapping[i] = j;
-      ctxt.index_mapping_reverse[j] = i;
-      i++;
-      j++;
-    }
-
-  /* Create the edits.  */
-  edits = gl_list_create_empty (GL_ARRAY_LIST, NULL, NULL, NULL, true);
-  i = 0;
-  j = 0;
-  while (i < n1 || j < n2)
-    {
-      if (i == n1)
-        {
-          struct edit *e;
-          ASSERT (j < n2);
-          e = XMALLOC (struct edit);
-          e->type = ADDITION;
-          e->j1 = j;
-          e->j2 = n2 - 1;
-          gl_list_add_last (edits, e);
-          break;
-        }
-      if (j == n2)
-        {
-          struct edit *e;
-          ASSERT (i < n1);
-          e = XMALLOC (struct edit);
-          e->type = REMOVAL;
-          e->i1 = i;
-          e->i2 = n1 - 1;
-          gl_list_add_last (edits, e);
-          break;
-        }
-      if (ctxt.index_mapping[i] >= 0)
-        {
-          if (ctxt.index_mapping_reverse[j] >= 0)
-            {
-              ASSERT (ctxt.index_mapping[i] == j);
-              ASSERT (ctxt.index_mapping_reverse[j] == i);
-              i++;
-              j++;
-            }
-          else
-            {
-              struct edit *e;
-              ASSERT (ctxt.index_mapping_reverse[j] < 0);
-              e = XMALLOC (struct edit);
-              e->type = ADDITION;
-              e->j1 = j;
-              do
-                j++;
-              while (j < n2 && ctxt.index_mapping_reverse[j] < 0);
-              e->j2 = j - 1;
-              gl_list_add_last (edits, e);
-            }
-        }
-      else
-        {
-          if (ctxt.index_mapping_reverse[j] >= 0)
-            {
-              struct edit *e;
-              ASSERT (ctxt.index_mapping[i] < 0);
-              e = XMALLOC (struct edit);
-              e->type = REMOVAL;
-              e->i1 = i;
-              do
-                i++;
-              while (i < n1 && ctxt.index_mapping[i] < 0);
-              e->i2 = i - 1;
-              gl_list_add_last (edits, e);
-            }
-          else
-            {
-              struct edit *e;
-              ASSERT (ctxt.index_mapping[i] < 0);
-              ASSERT (ctxt.index_mapping_reverse[j] < 0);
-              e = XMALLOC (struct edit);
-              e->type = CHANGE;
-              e->i1 = i;
-              do
-                i++;
-              while (i < n1 && ctxt.index_mapping[i] < 0);
-              e->i2 = i - 1;
-              e->j1 = j;
-              do
-                j++;
-              while (j < n2 && ctxt.index_mapping_reverse[j] < 0);
-              e->j2 = j - 1;
-              gl_list_add_last (edits, e);
-            }
-        }
-    }
-
-  result->index_mapping = ctxt.index_mapping;
-  result->index_mapping_reverse = ctxt.index_mapping_reverse;
-  result->num_edits = gl_list_size (edits);
-  result->edits = XNMALLOC (result->num_edits, struct edit *);
-  {
-    idx_t index = 0;
-    gl_list_iterator_t iter = gl_list_iterator (edits);
-    const void *elt;
-    gl_list_node_t node;
-    while (gl_list_iterator_next (&iter, &elt, &node))
-      result->edits[index++] = (struct edit *) elt;
-    gl_list_iterator_free (&iter);
-    ASSERT (index == result->num_edits);
-  }
-}
-
-/* An empty entry.  */
-static struct entry empty_entry = { NULL, 0 };
-
-/* Return the end a paragraph.
-   ENTRY is an entry.
-   OFFSET is an offset into the entry, OFFSET <= ENTRY->length.
-   Return the offset of the end of paragraph, as an offset <= ENTRY->length;
-   it is the start of a blank line or the end of the entry.  */
-static idx_t
-find_paragraph_end (const struct entry *entry, idx_t offset)
-{
-  const char *string = entry->string;
-  idx_t length = entry->length;
-
-  for (;;)
-    {
-      const char *nl = memchr (string + offset, '\n', length - offset);
-      if (nl == NULL)
-        return length;
-      offset = (nl - string) + 1;
-      if (offset < length && string[offset] == '\n')
-        return offset;
-    }
-}
-
-/* Split a merged entry.
-   Given an old entry of the form
-       TITLE
-       BODY
-   and a new entry of the form
-       TITLE
-       BODY1
-       BODY'
-   where the two titles are the same and BODY and BODY' are very similar,
-   this computes two new entries
-       TITLE
-       BODY1
-   and
-       TITLE
-       BODY'
-   and returns true.
-   If the entries don't have this form, it returns false.  */
-static bool
-try_split_merged_entry (const struct entry *old_entry,
-                        const struct entry *new_entry,
-                        struct entry *new_split[2])
-{
-  idx_t old_title_len = find_paragraph_end (old_entry, 0);
-  idx_t new_title_len = find_paragraph_end (new_entry, 0);
-  struct entry old_body;
-  struct entry new_body;
-  idx_t best_split_offset;
-  double best_similarity;
-  idx_t split_offset;
-
-  /* Same title? */
-  if (!(old_title_len == new_title_len
-        && memcmp (old_entry->string, new_entry->string, old_title_len) == 0))
-    return false;
-
-  old_body.string = old_entry->string + old_title_len;
-  old_body.length = old_entry->length - old_title_len;
-
-  /* Determine where to split the new entry.
-     This is done by maximizing the similarity between BODY and BODY'.  */
-  best_split_offset = split_offset = new_title_len;
-  best_similarity = 0.0;
-  for (;;)
-    {
-      double similarity;
-
-      new_body.string = new_entry->string + split_offset;
-      new_body.length = new_entry->length - split_offset;
-      similarity =
-        entry_fstrcmp (&old_body, &new_body, best_similarity);
-      if (similarity > best_similarity)
-        {
-          best_split_offset = split_offset;
-          best_similarity = similarity;
-        }
-      if (best_similarity == 1.0)
-        /* It cannot get better.  */
-        break;
-
-      if (split_offset < new_entry->length)
-        split_offset = find_paragraph_end (new_entry, split_offset + 1);
-      else
-        break;
-    }
-
-  /* BODY' should not be empty.  */
-  if (best_split_offset == new_entry->length)
-    return false;
-  ASSERT (new_entry->string[best_split_offset] == '\n');
-
-  /* A certain similarity between BODY and BODY' is required.  */
-  if (best_similarity < FSTRCMP_STRICTER_THRESHOLD)
-    return false;
-
-  new_split[0] = entry_create (new_entry->string, best_split_offset + 1);
-
-  {
-    idx_t len1 = new_title_len;
-    idx_t len2 = new_entry->length - best_split_offset;
-    char *combined = XNMALLOC (len1 + len2, char);
-    memcpy (combined, new_entry->string, len1);
-    memcpy (combined + len1, new_entry->string + best_split_offset, len2);
-    new_split[1] = entry_create (combined, len1 + len2);
-  }
-
-  return true;
-}
-
-/* Write the contents of an entry to the output stream FP.  */
-static void
-entry_write (FILE *fp, struct entry *entry)
-{
-  if (entry->length > 0)
-    fwrite (entry->string, 1, entry->length, fp);
-}
-
-/* This structure represents a conflict.
-   A conflict can occur for various reasons.  */
-struct conflict
-{
-  /* Parts from the ancestor file.  */
-  idx_t num_old_entries;
-  struct entry **old_entries;
-  /* Parts of the modified file.  */
-  idx_t num_modified_entries;
-  struct entry **modified_entries;
-};
-
-/* Write a conflict to the output stream FP, including markers.  */
-static void
-conflict_write (FILE *fp, struct conflict *c)
-{
-  idx_t i;
-
-  /* Use the same syntax as git's default merge driver.
-     The spaces after <<<<<<< and >>>>>>> are for compatibility with
-     git/rerere.c, function 'is_cmarker'.  Usually they would be followed by
-     branch or version names, but this info is not available to us here.
-     Don't indent the contents of the entries (with things like ">" or "-"),
-     otherwise the user needs more textual editing to resolve the conflict.  */
-  fputs ("<<<<<<< \n", fp);
-  for (i = 0; i < c->num_old_entries; i++)
-    entry_write (fp, c->old_entries[i]);
-  fputs ("=======\n", fp);
-  for (i = 0; i < c->num_modified_entries; i++)
-    entry_write (fp, c->modified_entries[i]);
-  fputs (">>>>>>> \n", fp);
-}
-
-/* Executes a program.
-   Returns the first line of its output, as a freshly allocated string, or
-   NULL.  */
-static char *
-execute_and_read_line (const char *progname,
-                       const char *prog_path, const char * const *prog_argv)
-{
-  pid_t child;
-  int fd[1];
-  FILE *fp;
-  char *line;
-  size_t linesize;
-  size_t linelen;
-
-  /* Open a pipe to the program.  */
-  child = create_pipe_in (progname, prog_path, prog_argv, NULL, NULL,
-                          DEV_NULL, false, true, false, fd);
-
-  if (child == -1)
-    return NULL;
-
-  /* Retrieve its result.  */
-  fp = fdopen (fd[0], "r");
-  if (fp == NULL)
-    error (EXIT_FAILURE, errno, _("fdopen() failed"));
-
-  line = NULL; linesize = 0;
-  linelen = getline (&line, &linesize, fp);
-  if (linelen == (size_t)(-1))
-    {
-      error (0, 0, _("%s subprocess I/O error"), progname);
-      fclose (fp);
-      wait_subprocess (child, progname, true, false, true, false, NULL);
-    }
-  else
-    {
-      int exitstatus;
-
-      if (linelen > 0 && line[linelen - 1] == '\n')
-        line[linelen - 1] = '\0';
-
-      /* Read until EOF (otherwise the child process may get a SIGPIPE signal).  */
-      while (getc (fp) != EOF)
-        ;
-
-      fclose (fp);
-
-      /* Remove zombie process from process list, and retrieve exit status.  */
-      exitstatus =
-        wait_subprocess (child, progname, true, false, true, false, NULL);
-      if (exitstatus == 0)
-        return line;
-    }
-  free (line);
-  return NULL;
-}
-
-/* Returns true if the given string consists only of hexadecimal digits.  */
-static bool
-is_all_hex_digits (const char *s)
-{
-  for (; *s != '\0'; s++)
-    if (!c_isxdigit (*s))
-      return false;
-  return true;
-}
-
-/* Print a usage message and exit.  */
-static void
-usage (int status)
-{
-  if (status != EXIT_SUCCESS)
-    fprintf (stderr, "Try '%s --help' for more information.\n",
-             getprogname ());
-  else
-    {
-      printf ("Usage: %s [OPTION] O-FILE-NAME A-FILE-NAME B-FILE-NAME\n",
-              getprogname ());
-      printf ("\n");
-      printf ("Merges independent modifications of a ChangeLog style file.\n");
-      printf ("O-FILE-NAME names the original file, the ancestor of the two others.\n");
-      printf ("A-FILE-NAME names the publicly modified file.\n");
-      printf ("B-FILE-NAME names the user-modified file.\n");
-      printf ("Writes the merged file into A-FILE-NAME.\n");
-      printf ("\n");
-      #if 0 /* --split-merged-entry is now on by default.  */
-      printf ("Operation modifiers:\n");
-      printf ("\
-      --split-merged-entry    Possibly split a merged entry between paragraphs.\n\
-                              Use this if you have the habit to merge unrelated\n\
-                              entries into a single one, separated only by a\n\
-                              newline, just because they happened on the same\n\
-                              date.\n");
-      printf ("\n");
-      #endif
-      printf ("Informative output:\n");
-      printf ("      --debug                 display debugging output\n");
-      printf ("  -h, --help                  display this help and exit\n");
-      printf ("  -V, --version               output version information and exit\n");
-      printf ("\n");
-      fputs ("Report bugs to <bug-gnulib@gnu.org>.\n",
-             stdout);
-    }
-
-  exit (status);
-}
-
-int
-main (int argc, char *argv[])
-{
-  int optchar;
-  bool do_help;
-  bool do_version;
-  bool debug;
-  bool split_merged_entry;
-
-  /* Set default values for variables.  */
-  do_help = false;
-  do_version = false;
-  debug = false;
-  split_merged_entry = true;
-
-  /* Parse command line options.  */
-  static const struct option long_options[] =
-  {
-    { "debug", no_argument, NULL, CHAR_MAX + 2 },
-    { "help", no_argument, NULL, 'h' },
-    { "split-merged-entry", no_argument, NULL, CHAR_MAX + 1 },
-    { "version", no_argument, NULL, 'V' },
-    { NULL, 0, NULL, 0 }
-  };
-  while ((optchar = getopt_long (argc, argv, "hV", long_options, NULL)) != EOF)
-    switch (optchar)
-    {
-    case '\0':          /* Long option with flag != NULL.  */
-      break;
-    case 'h':
-      do_help = true;
-      break;
-    case 'V':
-      do_version = true;
-      break;
-    case CHAR_MAX + 1:  /* --split-merged-entry */
-      break;
-    case CHAR_MAX + 2:  /* --debug */
-      debug = true;
-      break;
-    default:
-      usage (EXIT_FAILURE);
-    }
-
-  if (do_version)
-    {
-      /* Version information is requested.  */
-      printf ("%s\n", getprogname ());
-      printf ("Copyright (C) %s Free Software Foundation, Inc.\n\
-License GPLv2+: GNU GPL version 2 or later <https://gnu.org/licenses/gpl.html>\n\
-This is free software: you are free to change and redistribute it.\n\
-There is NO WARRANTY, to the extent permitted by law.\n\
-",
-              "2020");
-      printf ("Written by %s.\n", "Bruno Haible");
-      exit (EXIT_SUCCESS);
-    }
-
-  if (do_help)
-    {
-      /* Help is requested.  */
-      usage (EXIT_SUCCESS);
-    }
-
-  /* Test argument count.  */
-  if (!(argc >= optind + 3 && argc <= optind + 4))
-    error (EXIT_FAILURE, 0, "expected three arguments");
-
-  {
-    const char *ancestor_file_name; /* O-FILE-NAME */
-    const char *destination_file_name; /* A-FILE-NAME */
-    const char *other_file_name; /* B-FILE-NAME */
-    const char *other_conflict_label; /* %Y */
-    const char *mainstream_file_name;
-    const char *modified_file_name;
-    struct changelog_file ancestor_file;
-    struct changelog_file mainstream_file;
-    struct changelog_file modified_file;
-    /* Mapping from indices in ancestor_file to indices in mainstream_file.  */
-    struct entries_mapping mapping;
-    struct differences diffs;
-    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;
-
-    ancestor_file_name = argv[optind];
-    destination_file_name = argv[optind + 1];
-    other_file_name = argv[optind + 2];
-    other_conflict_label = argv[optind + 3];
-    if (debug)
-      {
-        printf ("\n");
-        printf ("%%Y = |%s|\n", other_conflict_label);
-      }
-
-    /* Heuristic to determine whether it's a pull in downstream direction
-       (e.g. pull from a centralized server) or a pull in upstream direction
-       (e.g. "git stash apply").
-
-       For ChangeLog this distinction is important. The difference between
-       an "upstream" and a "downstream" repository is that more people are
-       looking at the "upstream" repository.  They want to be informed about
-       changes and expect them to be shown at the top of the ChangeLog.
-       When a user pulls downstream, on the other hand, he has two options:
-         a) He gets the change entries from the central repository also at the
-            top of his ChangeLog, and his own changes come after them.
-         b) He gets the change entries from the central repository after those
-            he has collected for his branch.  His own change entries stay at
-            the top of the ChangeLog file.
-       In the case a) he has to reorder the ChangeLog before he can commit.
-       No one does that.  So most people want b).
-       In other words, the order of entries in a ChangeLog should represent
-       the order in which they have flown (or will flow) into the *central*
-       repository.
-
-       But in git this is fundamentally indistinguishable, because when Linus
-       pulls patches from akpm and akpm pulls patches from Linus, it's not
-       clear which of the two is more "upstream".  Also, when you have many
-       branches in a repository and pull from one to another, "git" has no way
-       to know which branch is more "upstream" than the other.  The git-tag(1)
-       manual page also says:
-         "One important aspect of git is it is distributed, and being
-          distributed largely means there is no inherent "upstream" or
-          "downstream" in the system."
-       Therefore anyone who attempts to produce a ChangeLog from the merge
-       history will fail.
-
-       Here we allow the user to specify the pull direction through an
-       environment variable (GIT_UPSTREAM or GIT_DOWNSTREAM).  If these two
-       environment variables are not set, we assume a "simple single user"
-       usage pattern: He manages local changes through stashes and uses
-       "git pull" only to pull downstream.
-
-       How to distinguish these situations? There are several hints:
-         - During a "git stash apply", GIT_REFLOG_ACTION is not set.
-         - During a "git pull --rebase", it is set to 'pull --rebase'.
-         - During a "git pull", it is set to 'pull' or 'pull '.
-           But this pull may include a rebase, depending on the
-           configuration values of 'pull.rebase' and 'branch.<name>.rebase'.
-           To disambiguate this case, we look at the first line of %A and
-           of %B.
-         - During a "git cherry-pick", it is set to 'cherry-pick'.
-         - During a "git stash apply", there is an environment variable of
-           the form GITHEAD_<40_hex_digits>='Stashed changes'.
-         - Looking at the value of %L is not useful: it's usually '7'.
-         - Looking at the value of %P is not useful: it's usually 'ChangeLog'.
-         - Looking at the value of %S is not useful: it's usually
-           "parent of ..." or (during 'git stash apply') "Stash base".
-         - Looking at the value of %X is not useful: it's usually "HEAD"
-           or (during 'git stash apply') "Updated upstream".
-     */
-    int downstream; /* true or false or -1 for ambiguous */
-    {
-      const char *var;
-
-      var = getenv ("GIT_DOWNSTREAM");
-      if (var != NULL && var[0] != '\0')
-        downstream = true;
-      else
-        {
-          var = getenv ("GIT_UPSTREAM");
-          if (var != NULL && var[0] != '\0')
-            downstream = false;
-          else
-            {
-              var = getenv ("GIT_REFLOG_ACTION");
-              if (debug)
-                printf ("GIT_REFLOG_ACTION=|%s|\n", var);
-              if (var == NULL)
-                /* "git am -3", "git stash apply", "git rebase",
-                   "git cherry-pick" and similar.  */
-                downstream = false;
-              else if (str_startswith (var, "merge origin"))
-                downstream = true;
-              else if (str_startswith (var, "pull"))
-                {
-                  if (c_strstr (var, " --rebase") != NULL)
-                    downstream = false;
-                  else
-                    downstream = -1;
-                }
-              else
-                downstream = false;
-            }
-        }
-    }
-
-    if (downstream < 0)
-      {
-        char *user_name;
-        {
-          const char *git_argv[5];
-          git_argv[0] = "git";
-          git_argv[1] = "config";
-          git_argv[2] = "get";
-          git_argv[3] = "user.name";
-          git_argv[4] = NULL;
-          user_name = execute_and_read_line ("git", "git", git_argv);
-        }
-        if (user_name != NULL && user_name[0] != '\0')
-          {
-            user_name = xasprintf ("  %s  ", user_name);
-            /* Heuristic: Look at the first line of %A and %B.  */
-            char *first_line_of_destination_file;
-            char *first_line_of_other_file;
-            {
-              const char *head_argv[5];
-              head_argv[0] = "head";
-              head_argv[1] = "-n";
-              head_argv[2] = "1";
-              head_argv[4] = NULL;
-
-              head_argv[3] = destination_file_name;
-              first_line_of_destination_file =
-                execute_and_read_line ("head", "head", head_argv);
-
-              head_argv[3] = other_file_name;
-              first_line_of_other_file =
-                execute_and_read_line ("head", "head", head_argv);
-            }
-            if (first_line_of_destination_file != NULL
-                && first_line_of_other_file != NULL)
-              {
-                if (c_strstr (first_line_of_destination_file, user_name) != NULL)
-                  {
-                    if (c_strstr (first_line_of_other_file, user_name) == NULL)
-                      /* The user's name is in the first ChangeLog entry of %A
-                         but not in the first ChangeLog entry of %B.  */
-                      downstream = true;
-                  }
-                else
-                  {
-                    if (c_strstr (first_line_of_other_file, user_name) != NULL)
-                      /* The user's name is in the first ChangeLog entry of %B
-                         but not in the first ChangeLog entry of %A.  */
-                      downstream = false;
-                  }
-              }
-            free (first_line_of_other_file);
-            free (first_line_of_destination_file);
-          }
-        free (user_name);
-
-        if (downstream < 0)
-          {
-            /* Disambiguate using the value of %Y:
-               - If it consists of 40 hex-digits, the result will be a merge
-                 made by the 'ort' strategy.  This happens during "git pull"
-                 without rebase, when a long sequence of commits is being
-                 pulled.  In this case, we need downstream = true.
-               - In the other cases â€” "git pull" with rebase, and "git pull"
-                 without rebase but with a short sequence of commits â€” we need
-                 downstream = false.  */
-            if (other_conflict_label != NULL
-                && strcmp (other_conflict_label, "%Y") != 0
-                && strlen (other_conflict_label) >= 40
-                && is_all_hex_digits (other_conflict_label))
-              downstream = true;
-            else
-              downstream = false;
-          }
-      }
-
-    if (debug)
-      {
-        char buf[1000];
-        printf ("First line of %%A:\n"); fflush (stdout);
-        sprintf (buf, "head -n 1 %s", destination_file_name); system (buf);
-        printf ("First line of %%B:\n"); fflush (stdout);
-        sprintf (buf, "head -n 1 %s", other_file_name); system (buf);
-        printf ("Guessing calling convention: %s\n",
-                downstream
-                ? "%A = modified by user, %B = upstream"
-                : "%A = upstream, %B = modified by user");
-      }
-
-    if (downstream)
-      {
-        mainstream_file_name = other_file_name;
-        modified_file_name = destination_file_name;
-      }
-    else
-      {
-        mainstream_file_name = destination_file_name;
-        modified_file_name = other_file_name;
-      }
-
-    /* Read the three files into memory.  */
-    read_changelog_file (ancestor_file_name, &ancestor_file);
-    read_changelog_file (mainstream_file_name, &mainstream_file);
-    read_changelog_file (modified_file_name, &modified_file);
-
-    /* Compute correspondence between the entries of ancestor_file and of
-       mainstream_file.  */
-    compute_mapping (&ancestor_file, &mainstream_file, false, &mapping);
-    (void) entries_mapping_reverse_get; /* avoid gcc "defined but not" warning */
-
-    /* Compute differences between the entries of ancestor_file and of
-       modified_file.  */
-    compute_differences (&ancestor_file, &modified_file, &diffs);
-
-    /* Compute the result.  */
-    result_entries_pointers =
-      XNMALLOC (mainstream_file.num_entries, gl_list_node_t);
-    result_entries =
-      gl_list_create_empty (GL_LINKED_LIST, entry_equals, entry_hashcode,
-                            NULL, true);
-    {
-      idx_t k;
-      for (k = 0; k < mainstream_file.num_entries; k++)
-        result_entries_pointers[k] =
-          gl_list_add_last (result_entries, mainstream_file.entries[k]);
-    }
-    result_conflicts =
-      gl_list_create_empty (GL_ARRAY_LIST, NULL, NULL, NULL, true);
-    {
-      idx_t e;
-      for (e = 0; e < diffs.num_edits; e++)
-        {
-          struct edit *edit = diffs.edits[e];
-          switch (edit->type)
-            {
-            case ADDITION:
-              if (edit->j1 == 0)
-                {
-                  /* An addition to the top of modified_file.
-                     Apply it to the top of mainstream_file.  */
-                  ptrdiff_t j;
-                  for (j = edit->j2; j >= edit->j1; j--)
-                    {
-                      struct entry *added_entry = modified_file.entries[j];
-                      gl_list_add_first (result_entries, added_entry);
-                    }
-                }
-              else
-                {
-                  idx_t i_before;
-                  idx_t i_after;
-                  ptrdiff_t k_before;
-                  ptrdiff_t k_after;
-                  i_before = diffs.index_mapping_reverse[edit->j1 - 1];
-                  ASSERT (i_before >= 0);
-                  i_after = (edit->j2 + 1 == modified_file.num_entries
-                             ? ancestor_file.num_entries
-                             : diffs.index_mapping_reverse[edit->j2 + 1]);
-                  ASSERT (i_after >= 0);
-                  ASSERT (i_after == i_before + 1);
-                  /* An addition between ancestor_file.entries[i_before] and
-                     ancestor_file.entries[i_after].  See whether these two
-                     entries still exist in mainstream_file and are still
-                     consecutive.  */
-                  k_before = entries_mapping_get (&mapping, i_before);
-                  k_after = (i_after == ancestor_file.num_entries
-                             ? mainstream_file.num_entries
-                             : entries_mapping_get (&mapping, i_after));
-                  if (k_before >= 0 && k_after >= 0 && k_after == k_before + 1)
-                    {
-                      /* Yes, the entry before and after are still neighbours
-                         in mainstream_file.  Apply the addition between
-                         them.  */
-                      if (k_after == mainstream_file.num_entries)
-                        {
-                          idx_t j;
-                          for (j = edit->j1; j <= edit->j2; j++)
-                            {
-                              struct entry *added_entry = modified_file.entries[j];
-                              gl_list_add_last (result_entries, added_entry);
-                            }
-                        }
-                      else
-                        {
-                          gl_list_node_t node_k_after = result_entries_pointers[k_after];
-                          idx_t j;
-                          for (j = edit->j1; j <= edit->j2; j++)
-                            {
-                              struct entry *added_entry = modified_file.entries[j];
-                              gl_list_add_before (result_entries, node_k_after, added_entry);
-                            }
-                        }
-                    }
-                  else
-                    {
-                      /* It's not clear where the additions should be applied.
-                         Let the user decide.  */
-                      struct conflict *c = XMALLOC (struct conflict);
-                      idx_t j;
-                      c->num_old_entries = 0;
-                      c->old_entries = NULL;
-                      c->num_modified_entries = edit->j2 - edit->j1 + 1;
-                      c->modified_entries =
-                        XNMALLOC (c->num_modified_entries, struct entry *);
-                      for (j = edit->j1; j <= edit->j2; j++)
-                        c->modified_entries[j - edit->j1] = modified_file.entries[j];
-                      gl_list_add_last (result_conflicts, c);
-                    }
-                }
-              break;
-            case REMOVAL:
-              {
-                /* Apply the removals one by one.  */
-                idx_t i;
-                for (i = edit->i1; i <= edit->i2; i++)
-                  {
-                    struct entry *removed_entry = ancestor_file.entries[i];
-                    ptrdiff_t k = entries_mapping_get (&mapping, i);
-                    if (k >= 0
-                        && entry_equals (removed_entry,
-                                         mainstream_file.entries[k]))
-                      {
-                        /* The entry to be removed still exists in
-                           mainstream_file.  Remove it.  */
-                        gl_list_node_set_value (result_entries,
-                                                result_entries_pointers[k],
-                                                &empty_entry);
-                      }
-                    else
-                      {
-                        /* The entry to be removed was already removed or was
-                           modified.  This is a conflict.  */
-                        struct conflict *c = XMALLOC (struct conflict);
-                        c->num_old_entries = 1;
-                        c->old_entries =
-                          XNMALLOC (c->num_old_entries, struct entry *);
-                        c->old_entries[0] = removed_entry;
-                        c->num_modified_entries = 0;
-                        c->modified_entries = NULL;
-                        gl_list_add_last (result_conflicts, c);
-                      }
-                  }
-              }
-              break;
-            case CHANGE:
-              {
-                bool done = false;
-                /* When the user usually merges entries from the same day,
-                   and this edit is at the top of the file:  */
-                if (split_merged_entry && edit->j1 == 0)
-                  {
-                    /* Test whether the change is "simple merged", i.e. whether
-                       it consists of additions, followed by an augmentation of
-                       the first changed entry, followed by small changes of the
-                       remaining entries:
-                         entry_1
-                         entry_2
-                         ...
-                         entry_n
-                       are mapped to
-                         added_entry
-                         ...
-                         added_entry
-                         augmented_entry_1
-                         modified_entry_2
-                         ...
-                         modified_entry_n.  */
-                    if (edit->i2 - edit->i1 <= edit->j2 - edit->j1)
-                      {
-                        struct entry *split[2];
-                        bool simple_merged =
-                          try_split_merged_entry (ancestor_file.entries[edit->i1],
-                                                  modified_file.entries[edit->i1 + edit->j2 - edit->i2],
-                                                  split);
-                        if (simple_merged)
-                          {
-                            idx_t i;
-                            for (i = edit->i1 + 1; i <= edit->i2; i++)
-                              if (entry_fstrcmp (ancestor_file.entries[i],
-                                                 modified_file.entries[i + edit->j2 - edit->i2],
-                                                 FSTRCMP_THRESHOLD)
-                                  < FSTRCMP_THRESHOLD)
-                                {
-                                  simple_merged = false;
-                                  break;
-                                }
-                          }
-                        if (simple_merged)
-                          {
-                            /* Apply the additions at the top of modified_file.
-                               Apply each of the single-entry changes
-                               separately.  */
-                            idx_t num_changed = edit->i2 - edit->i1 + 1; /* > 0 */
-                            idx_t num_added = (edit->j2 - edit->j1 + 1) - num_changed;
-                            idx_t j;
-                            /* First part of the split modified_file.entries[edit->j2 - edit->i2 + edit->i1]:  */
-                            gl_list_add_first (result_entries, split[0]);
-                            /* The additions.  */
-                            for (j = edit->j1 + num_added; j > edit->j1; )
-                              {
-                                j--;
-                                struct entry *added_entry = modified_file.entries[j];
-                                gl_list_add_first (result_entries, added_entry);
-                              }
-                            /* Now the single-entry changes.  */
-                            for (j = edit->j1 + num_added; j <= edit->j2; j++)
-                              {
-                                struct entry *changed_entry =
-                                  (j == edit->j1 + num_added
-                                   ? split[1]
-                                   : modified_file.entries[j]);
-                                idx_t i = j + edit->i2 - edit->j2;
-                                ptrdiff_t k = entries_mapping_get (&mapping, i);
-                                if (k >= 0
-                                    && entry_equals (ancestor_file.entries[i],
-                                                     mainstream_file.entries[k]))
-                                  {
-                                    gl_list_node_set_value (result_entries,
-                                                            result_entries_pointers[k],
-                                                            changed_entry);
-                                  }
-                                else if (!entry_equals (ancestor_file.entries[i],
-                                                        changed_entry))
-                                  {
-                                    struct conflict *c = XMALLOC (struct conflict);
-                                    c->num_old_entries = 1;
-                                    c->old_entries =
-                                      XNMALLOC (c->num_old_entries, struct entry *);
-                                    c->old_entries[0] = ancestor_file.entries[i];
-                                    c->num_modified_entries = 1;
-                                    c->modified_entries =
-                                      XNMALLOC (c->num_modified_entries, struct entry *);
-                                    c->modified_entries[0] = changed_entry;
-                                    gl_list_add_last (result_conflicts, c);
-                                  }
-                              }
-                            done = true;
-                          }
-                      }
-                  }
-                if (!done)
-                  {
-                    bool simple;
-                    /* Test whether the change is "simple", i.e. whether it
-                       consists of small changes to the old ChangeLog entries
-                       and additions before them:
-                         entry_1
-                         ...
-                         entry_n
-                       are mapped to
-                         added_entry
-                         ...
-                         added_entry
-                         modified_entry_1
-                         ...
-                         modified_entry_n.  */
-                    if (edit->i2 - edit->i1 <= edit->j2 - edit->j1)
-                      {
-                        idx_t i;
-                        simple = true;
-                        for (i = edit->i1; i <= edit->i2; i++)
-                          if (entry_fstrcmp (ancestor_file.entries[i],
-                                             modified_file.entries[i + edit->j2 - edit->i2],
-                                             FSTRCMP_THRESHOLD)
-                              < FSTRCMP_THRESHOLD)
-                            {
-                              simple = false;
-                              break;
-                            }
-                      }
-                    else
-                      simple = false;
-                    if (simple)
-                      {
-                        /* Apply the additions and each of the single-entry
-                           changes separately.  */
-                        idx_t num_changed = edit->i2 - edit->i1 + 1; /* > 0 */
-                        idx_t num_added = (edit->j2 - edit->j1 + 1) - num_changed;
-                        if (edit->j1 == 0)
-                          {
-                            /* A simple change at the top of modified_file.
-                               Apply it to the top of mainstream_file.  */
-                            idx_t j;
-                            for (j = edit->j1 + num_added; j > edit->j1; )
-                              {
-                                j--;
-                                struct entry *added_entry = modified_file.entries[j];
-                                gl_list_add_first (result_entries, added_entry);
-                              }
-                            for (j = edit->j1 + num_added; j <= edit->j2; j++)
-                              {
-                                struct entry *changed_entry = modified_file.entries[j];
-                                idx_t i = j + edit->i2 - edit->j2;
-                                ptrdiff_t k = entries_mapping_get (&mapping, i);
-                                if (k >= 0
-                                    && entry_equals (ancestor_file.entries[i],
-                                                     mainstream_file.entries[k]))
-                                  {
-                                    gl_list_node_set_value (result_entries,
-                                                            result_entries_pointers[k],
-                                                            changed_entry);
-                                  }
-                                else
-                                  {
-                                    struct conflict *c;
-                                    ASSERT (!entry_equals (ancestor_file.entries[i],
-                                                           changed_entry));
-                                    c = XMALLOC (struct conflict);
-                                    c->num_old_entries = 1;
-                                    c->old_entries =
-                                      XNMALLOC (c->num_old_entries, struct entry *);
-                                    c->old_entries[0] = ancestor_file.entries[i];
-                                    c->num_modified_entries = 1;
-                                    c->modified_entries =
-                                      XNMALLOC (c->num_modified_entries, struct entry *);
-                                    c->modified_entries[0] = changed_entry;
-                                    gl_list_add_last (result_conflicts, c);
-                                  }
-                              }
-                            done = true;
-                          }
-                        else
-                          {
-                            idx_t i_before;
-                            ptrdiff_t k_before;
-                            bool linear;
-                            i_before = diffs.index_mapping_reverse[edit->j1 - 1];
-                            ASSERT (i_before >= 0);
-                            /* A simple change after ancestor_file.entries[i_before].
-                               See whether this entry and the following num_changed
-                               entries still exist in mainstream_file and are still
-                               consecutive.  */
-                            k_before = entries_mapping_get (&mapping, i_before);
-                            linear = (k_before >= 0);
-                            if (linear)
-                              {
-                                idx_t i;
-                                for (i = i_before + 1; i <= i_before + num_changed; i++)
-                                  if (entries_mapping_get (&mapping, i) != k_before + (i - i_before))
-                                    {
-                                      linear = false;
-                                      break;
-                                    }
-                              }
-                            if (linear)
-                              {
-                                gl_list_node_t node_for_insert =
-                                  result_entries_pointers[k_before + 1];
-                                idx_t j;
-                                for (j = edit->j1 + num_added; j > edit->j1; )
-                                  {
-                                    j--;
-                                    struct entry *added_entry = modified_file.entries[j];
-                                    gl_list_add_before (result_entries, node_for_insert, added_entry);
-                                  }
-                                for (j = edit->j1 + num_added; j <= edit->j2; j++)
-                                  {
-                                    struct entry *changed_entry = modified_file.entries[j];
-                                    idx_t i = j + edit->i2 - edit->j2;
-                                    idx_t k = entries_mapping_get (&mapping, i);
-                                    ASSERT (k >= 0);
-                                    if (entry_equals (ancestor_file.entries[i],
-                                                      mainstream_file.entries[k]))
-                                      {
-                                        gl_list_node_set_value (result_entries,
-                                                                result_entries_pointers[k],
-                                                                changed_entry);
-                                      }
-                                    else
-                                      {
-                                        struct conflict *c;
-                                        ASSERT (!entry_equals (ancestor_file.entries[i],
-                                                               changed_entry));
-                                        c = XMALLOC (struct conflict);
-                                        c->num_old_entries = 1;
-                                        c->old_entries =
-                                          XNMALLOC (c->num_old_entries, struct entry *);
-                                        c->old_entries[0] = ancestor_file.entries[i];
-                                        c->num_modified_entries = 1;
-                                        c->modified_entries =
-                                          XNMALLOC (c->num_modified_entries, struct entry *);
-                                        c->modified_entries[0] = changed_entry;
-                                        gl_list_add_last (result_conflicts, c);
-                                      }
-                                  }
-                                done = true;
-                              }
-                          }
-                      }
-                    else
-                      {
-                        /* A big change.
-                           See whether the num_changed entries still exist
-                           unchanged in mainstream_file and are still
-                           consecutive.  */
-                        idx_t i_first;
-                        ptrdiff_t k_first;
-                        bool linear_unchanged;
-                        i_first = edit->i1;
-                        k_first = entries_mapping_get (&mapping, i_first);
-                        linear_unchanged =
-                          (k_first >= 0
-                           && entry_equals (ancestor_file.entries[i_first],
-                                            mainstream_file.entries[k_first]));
-                        if (linear_unchanged)
-                          {
-                            idx_t i;
-                            for (i = i_first + 1; i <= edit->i2; i++)
-                              if (!(entries_mapping_get (&mapping, i) == k_first + (i - i_first)
-                                    && entry_equals (ancestor_file.entries[i],
-                                                     mainstream_file.entries[entries_mapping_get (&mapping, i)])))
-                                {
-                                  linear_unchanged = false;
-                                  break;
-                                }
-                          }
-                        if (linear_unchanged)
-                          {
-                            gl_list_node_t node_for_insert =
-                              result_entries_pointers[k_first];
-                            ptrdiff_t j;
-                            idx_t i;
-                            for (j = edit->j2; j >= edit->j1; j--)
-                              {
-                                struct entry *new_entry = modified_file.entries[j];
-                                gl_list_add_before (result_entries, node_for_insert, new_entry);
-                              }
-                            for (i = edit->i1; i <= edit->i2; i++)
-                              {
-                                idx_t k = entries_mapping_get (&mapping, i);
-                                ASSERT (k >= 0);
-                                ASSERT (entry_equals (ancestor_file.entries[i],
-                                                      mainstream_file.entries[k]));
-                                gl_list_node_set_value (result_entries,
-                                                        result_entries_pointers[k],
-                                                        &empty_entry);
-                              }
-                            done = true;
-                          }
-                      }
-                  }
-                if (!done)
-                  {
-                    struct conflict *c = XMALLOC (struct conflict);
-                    idx_t i, j;
-                    c->num_old_entries = edit->i2 - edit->i1 + 1;
-                    c->old_entries =
-                      XNMALLOC (c->num_old_entries, struct entry *);
-                    for (i = edit->i1; i <= edit->i2; i++)
-                      c->old_entries[i - edit->i1] = ancestor_file.entries[i];
-                    c->num_modified_entries = edit->j2 - edit->j1 + 1;
-                    c->modified_entries =
-                      XNMALLOC (c->num_modified_entries, struct entry *);
-                    for (j = edit->j1; j <= edit->j2; j++)
-                      c->modified_entries[j - edit->j1] = modified_file.entries[j];
-                    gl_list_add_last (result_conflicts, c);
-                  }
-              }
-              break;
-            }
-        }
-    }
-
-    /* Output the result.  */
-    {
-      FILE *fp = fopen (destination_file_name, "w");
-      if (fp == NULL)
-        {
-          fprintf (stderr, "could not write file '%s'\n", destination_file_name);
-          exit (EXIT_FAILURE);
-        }
-
-      /* Output the conflicts at the top.  */
-      {
-        idx_t n = gl_list_size (result_conflicts);
-        idx_t i;
-        for (i = 0; i < n; i++)
-          conflict_write (fp, (struct conflict *) gl_list_get_at (result_conflicts, i));
-      }
-      /* Output the modified and unmodified entries, in order.  */
-      {
-        gl_list_iterator_t iter = gl_list_iterator (result_entries);
-        const void *elt;
-        gl_list_node_t node;
-        while (gl_list_iterator_next (&iter, &elt, &node))
-          entry_write (fp, (struct entry *) elt);
-        gl_list_iterator_free (&iter);
-      }
-
-      if (fwriteerror (fp))
-        {
-          fprintf (stderr, "error writing to file '%s'\n", destination_file_name);
-          exit (EXIT_FAILURE);
-        }
-    }
-
-    exit (gl_list_size (result_conflicts) > 0 ? EXIT_FAILURE : EXIT_SUCCESS);
-  }
-}
diff --git a/modules/git-merge-changelog b/modules/git-merge-changelog
deleted file mode 100644 (file)
index d9c2167..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-Description:
-git "merge" driver for GNU style ChangeLog files
-
-Files:
-lib/git-merge-changelog.c
-
-Depends-on:
-c99
-getopt-gnu
-idx
-bool
-stdint-h
-stdlib-h
-error
-read-file
-spawn-pipe
-wait-process
-xlist
-array-list
-linkedhash-list
-linked-list
-rbtreehash-list
-xalloc
-xmalloca
-xvasprintf
-fstrcmp
-minmax
-str_startswith
-c-ctype
-c-strstr
-fwriteerror
-memchr
-memcmp
-getprogname
-
-configure.ac:
-
-Makefile.am:
-bin_PROGRAMS = git-merge-changelog
-git_merge_changelog_LDADD = libgnu.a @LIBINTL@ $(LIBTHREAD)
-
-Include:
-
-License:
-GPL
-
-Maintainer:
-all