From: Bruno Haible Date: Tue, 5 Aug 2025 21:16:15 +0000 (+0200) Subject: git-merge-changelog: Remove module. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=19b6bc71b09d8b1a342a9d529ee6ab117b04dd7a;p=thirdparty%2Fgnulib.git 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. --- diff --git a/ChangeLog b/ChangeLog index ab88105b47..df3ab2ed6b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2025-08-05 Bruno Haible + + 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 nlcanon tests: Fix last commit. diff --git a/NEWS b/NEWS index 08981f26e9..664007395d 100644 --- 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 index d7703bc6d9..0000000000 --- a/lib/git-merge-changelog.c +++ /dev/null @@ -1,1879 +0,0 @@ -/* git-merge-changelog - git "merge" driver for GNU style ChangeLog files. - Copyright (C) 2008-2025 Bruno Haible - - 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 . */ - -/* 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 - - - - 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 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 - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#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 /* */ entries_list; - /* The entries, as a list in opposite direction. */ - gl_list_t /* */ 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 /* */ 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 .\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 \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 /* */ result_entries; - gl_list_t /* */ 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..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 index d9c2167852..0000000000 --- a/modules/git-merge-changelog +++ /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