X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fgit-merge-changelog.c;h=a199f32d5957308e95663207e3aaf37bb8555d32;hb=8def010e77c2bace5391a1cda45e158d9e0a3f7d;hp=14893a4777df17ae9c6a882b9fb572edba682987;hpb=5f193761e59c468f5ae75a377be7beec58eb6c19;p=gnulib.git diff --git a/lib/git-merge-changelog.c b/lib/git-merge-changelog.c index 14893a477..a199f32d5 100644 --- a/lib/git-merge-changelog.c +++ b/lib/git-merge-changelog.c @@ -1,5 +1,5 @@ /* git-merge-changelog - git "merge" driver for GNU style ChangeLog files. - Copyright (C) 2008 Bruno Haible + Copyright (C) 2008-2009 Bruno Haible This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,7 +21,7 @@ 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 - effects to look at the individual line changes inside a ChangeLog entry. + 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 @@ -133,6 +133,7 @@ #include "gl_list.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" @@ -159,8 +160,24 @@ struct entry { char *string; size_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, size_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) @@ -169,29 +186,37 @@ entry_equals (const void *elt1, const void *elt2) 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) { - const struct entry *entry = (const struct entry *) elt; - /* See http://www.haible.de/bruno/hashfunc.html. */ - const char *s; - size_t n; - size_t h = 0; + struct entry *entry = (struct entry *) elt; + if (!entry->hashcode_cached) + { + /* See http://www.haible.de/bruno/hashfunc.html. */ + const char *s; + size_t n; + size_t h = 0; - for (s = entry->string, n = entry->length; n > 0; s++, n--) - h = (unsigned char) *s + ((h << 9) | (h >> (sizeof (size_t) * CHAR_BIT - 9))); + for (s = entry->string, n = entry->length; n > 0; s++, n--) + h = (unsigned char) *s + ((h << 9) | (h >> (sizeof (size_t) * CHAR_BIT - 9))); - return h; + 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. */ + 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) +entry_fstrcmp (const struct entry *entry1, const struct entry *entry2, + double lower_bound) { /* fstrcmp works only on NUL terminated strings. */ char *memory; @@ -211,7 +236,8 @@ entry_fstrcmp (const struct entry *entry1, const struct entry *entry2) p += entry2->length; *p++ = '\0'; } - similarity = fstrcmp (memory, memory + entry1->length + 1); + similarity = + fstrcmp_bounded (memory, memory + entry1->length + 1, lower_bound); freea (memory); return similarity; } @@ -248,7 +274,7 @@ read_changelog_file (const char *filename, struct changelog_file *result) gl_list_create_empty (GL_LINKEDHASH_LIST, entry_equals, entry_hashcode, NULL, true); result->entries_reversed = - gl_list_create_empty (GL_LINKEDHASH_LIST, entry_equals, entry_hashcode, + 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 @@ -281,9 +307,7 @@ read_changelog_file (const char *filename, struct changelog_file *result) } } - curr = XMALLOC (struct entry); - curr->string = start; - curr->length = ptr - start; + curr = entry_create (start, ptr - start); gl_list_add_last (result->entries_list, curr); gl_list_add_first (result->entries_reversed, curr); @@ -305,18 +329,159 @@ read_changelog_file (const char *filename, struct changelog_file *result) } } +/* 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. */ + ssize_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. */ + ssize_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 ssize_t +entries_mapping_get (struct entries_mapping *mapping, ssize_t i) +{ + if (mapping->index_mapping[i] < -1) + { + struct changelog_file *file1 = mapping->file1; + struct changelog_file *file2 = mapping->file2; + size_t n1 = file1->num_entries; + size_t n2 = file2->num_entries; + struct entry *entry_i = file1->entries[i]; + ssize_t j; + + /* Search whether it approximately occurs in file2. */ + ssize_t best_j = -1; + double best_j_similarity = 0.0; + for (j = n2 - 1; 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. */ + ssize_t best_i = -1; + double best_i_similarity = 0.0; + ssize_t ii; + for (ii = n1 - 1; 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 ssize_t +entries_mapping_reverse_get (struct entries_mapping *mapping, ssize_t j) +{ + if (mapping->index_mapping_reverse[j] < -1) + { + struct changelog_file *file1 = mapping->file1; + struct changelog_file *file2 = mapping->file2; + size_t n1 = file1->num_entries; + size_t n2 = file2->num_entries; + struct entry *entry_j = file2->entries[j]; + ssize_t i; + + /* Search whether it approximately occurs in file1. */ + ssize_t best_i = -1; + double best_i_similarity = 0.0; + for (i = n1 - 1; 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. */ + ssize_t best_j = -1; + double best_j_similarity = 0.0; + ssize_t jj; + for (jj = n2 - 1; 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. - Return a set of two arrays: - - An array mapping FILE1 indices to FILE2 indices (or -1 when the entry - from FILE1 is not found in FILE2). - - An array mapping FILE2 indices to FILE1 indices (or -1 when the entry - from FILE2 is not found in FILE1). The correspondence also takes into account small modifications; i.e. the indicated relation is not equality of entries but best-match similarity - of entries. */ + 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, - ssize_t *result[2]) + bool full, + struct entries_mapping *result) { /* Mapping from indices in file1 to indices in file2. */ ssize_t *index_mapping; @@ -328,15 +493,15 @@ compute_mapping (struct changelog_file *file1, struct changelog_file *file2, index_mapping = XNMALLOC (n1, ssize_t); for (i = 0; i < n1; i++) - index_mapping[i] = -1; + index_mapping[i] = -2; index_mapping_reverse = XNMALLOC (n2, ssize_t); for (j = 0; j < n2; j++) - index_mapping_reverse[j] = -1; + index_mapping_reverse[j] = -2; for (i = n1 - 1; i >= 0; i--) /* Take an entry from file1. */ - if (index_mapping[i] < 0) + if (index_mapping[i] < -1) { struct entry *entry = file1->entries[i]; /* Search whether it occurs in file2. */ @@ -345,87 +510,55 @@ compute_mapping (struct changelog_file *file1, struct changelog_file *file2, { j = n2 - 1 - j; /* Found an exact correspondence. */ - ASSERT (index_mapping_reverse[j] < 0); - index_mapping[i] = j; - index_mapping_reverse[j] = i; - /* Look for more occurrences of the same entry. */ - { - ssize_t curr_i = i; - ssize_t curr_j = j; - - for (;;) + /* 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. */ { - ssize_t next_i; - ssize_t next_j; - - next_i = - gl_list_indexof_from (file1->entries_reversed, n1 - curr_i, - entry); - if (next_i < 0) - break; - next_j = - gl_list_indexof_from (file2->entries_reversed, n2 - curr_j, - entry); - if (next_j < 0) - 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; - } - } - } - } + ssize_t curr_i = i; + ssize_t curr_j = j; - for (i = n1 - 1; i >= 0; i--) - /* Take an entry from file1. */ - if (index_mapping[i] < 0) - { - struct entry *entry_i = file1->entries[i]; - /* Search whether it approximately occurs in file2. */ - ssize_t best_j = -1; - double best_j_similarity = 0.0; - for (j = n2 - 1; j >= 0; j--) - if (index_mapping_reverse[j] < 0) - { - double similarity = entry_fstrcmp (entry_i, file2->entries[j]); - 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. */ - ssize_t best_i = -1; - double best_i_similarity = 0.0; - ssize_t ii; - for (ii = n1 - 1; ii >= 0; ii--) - if (index_mapping[ii] < 0) - { - double similarity = - entry_fstrcmp (file1->entries[ii], entry_j); - if (similarity > best_i_similarity) + for (;;) { - best_i = i; - best_i_similarity = similarity; + ssize_t next_i; + ssize_t next_j; + + next_i = + gl_list_indexof_from (file1->entries_reversed, + n1 - curr_i, entry); + if (next_i < 0) + break; + next_j = + gl_list_indexof_from (file2->entries_reversed, + n2 - curr_j, entry); + if (next_j < 0) + 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; } } - if (best_i_similarity >= FSTRCMP_THRESHOLD && best_i == i) - { - index_mapping[i] = best_j; - index_mapping_reverse[best_j] = i; } } } - result[0] = index_mapping; - result[1] = index_mapping_reverse; + result->file1 = file1; + result->file2 = file2; + result->index_mapping = index_mapping; + result->index_mapping_reverse = index_mapping_reverse; + + if (full) + for (i = n1 - 1; i >= 0; i--) + entries_mapping_get (result, i); } /* An "edit" is a textual modification performed by the user, that needs to @@ -709,7 +842,8 @@ try_split_merged_entry (const struct entry *old_entry, new_body.string = new_entry->string + split_offset; new_body.length = new_entry->length - split_offset; - similarity = entry_fstrcmp (&old_body, &new_body); + similarity = + entry_fstrcmp (&old_body, &new_body, best_similarity); if (similarity > best_similarity) { best_split_offset = split_offset; @@ -734,19 +868,15 @@ try_split_merged_entry (const struct entry *old_entry, if (best_similarity < FSTRCMP_STRICTER_THRESHOLD) return false; - new_split[0] = XMALLOC (struct entry); - new_split[0]->string = new_entry->string; - new_split[0]->length = best_split_offset + 1; + new_split[0] = entry_create (new_entry->string, best_split_offset + 1); - new_split[1] = XMALLOC (struct entry); { size_t len1 = new_title_len; size_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]->string = combined; - new_split[1]->length = len1 + len2; + new_split[1] = entry_create (combined, len1 + len2); } return true; @@ -792,7 +922,7 @@ conflict_write (FILE *fp, struct conflict *c) /* Long options. */ static const struct option long_options[] = -{ +{ { "help", no_argument, NULL, 'h' }, { "split-merged-entry", no_argument, NULL, CHAR_MAX + 1 }, { "version", no_argument, NULL, 'V' }, @@ -906,9 +1036,7 @@ There is NO WARRANTY, to the extent permitted by law.\n\ struct changelog_file mainstream_file; struct changelog_file modified_file; /* Mapping from indices in ancestor_file to indices in mainstream_file. */ - ssize_t *index_mapping; - /* Mapping from indices in mainstream_file to indices in ancestor_file. */ - ssize_t *index_mapping_reverse; + 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; @@ -959,7 +1087,8 @@ There is NO WARRANTY, to the extent permitted by law.\n\ How to distinguish these situation? There are several hints: - During a "git stash apply", GIT_REFLOG_ACTION is not set. During a "git pull", it is set to 'pull '. During a "git pull --rebase", - it is set to 'pull --rebase'. + it is set to 'pull --rebase'. 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'. */ { @@ -986,7 +1115,8 @@ There is NO WARRANTY, to the extent permitted by law.\n\ downstream = true; else { - /* "git stash apply", "git rebase" and similar. */ + /* "git stash apply", "git rebase", "git cherry-pick" and + similar. */ downstream = false; } } @@ -1025,12 +1155,8 @@ There is NO WARRANTY, to the extent permitted by law.\n\ /* Compute correspondence between the entries of ancestor_file and of mainstream_file. */ - { - ssize_t *result[2]; - compute_mapping (&ancestor_file, &mainstream_file, result); - index_mapping = result[0]; - index_mapping_reverse = result[1]; - } + 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. */ @@ -1086,10 +1212,10 @@ There is NO WARRANTY, to the extent permitted by law.\n\ ancestor_file.entries[i_after]. See whether these two entries still exist in mainstream_file and are still consecutive. */ - k_before = index_mapping[i_before]; + k_before = entries_mapping_get (&mapping, i_before); k_after = (i_after == ancestor_file.num_entries ? mainstream_file.num_entries - : index_mapping[i_after]); + : 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 @@ -1139,7 +1265,7 @@ There is NO WARRANTY, to the extent permitted by law.\n\ for (i = edit->i1; i <= edit->i2; i++) { struct entry *removed_entry = ancestor_file.entries[i]; - ssize_t k = index_mapping[i]; + ssize_t k = entries_mapping_get (&mapping, i); if (k >= 0 && entry_equals (removed_entry, mainstream_file.entries[k])) @@ -1201,7 +1327,8 @@ There is NO WARRANTY, to the extent permitted by law.\n\ size_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]) + modified_file.entries[i + edit->j2 - edit->i2], + FSTRCMP_THRESHOLD) < FSTRCMP_THRESHOLD) { simple_merged = false; @@ -1232,7 +1359,7 @@ There is NO WARRANTY, to the extent permitted by law.\n\ ? split[1] : modified_file.entries[j]); size_t i = j + edit->i2 - edit->j2; - ssize_t k = index_mapping[i]; + ssize_t k = entries_mapping_get (&mapping, i); if (k >= 0 && entry_equals (ancestor_file.entries[i], mainstream_file.entries[k])) @@ -1241,7 +1368,8 @@ There is NO WARRANTY, to the extent permitted by law.\n\ result_entries_pointers[k], changed_entry); } - else + else if (!entry_equals (ancestor_file.entries[i], + changed_entry)) { struct conflict *c = XMALLOC (struct conflict); c->num_old_entries = 1; @@ -1281,7 +1409,8 @@ There is NO WARRANTY, to the extent permitted by law.\n\ 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]) + modified_file.entries[i + edit->j2 - edit->i2], + FSTRCMP_THRESHOLD) < FSTRCMP_THRESHOLD) { simple = false; @@ -1310,7 +1439,7 @@ There is NO WARRANTY, to the extent permitted by law.\n\ { struct entry *changed_entry = modified_file.entries[j]; size_t i = j + edit->i2 - edit->j2; - ssize_t k = index_mapping[i]; + ssize_t k = entries_mapping_get (&mapping, i); if (k >= 0 && entry_equals (ancestor_file.entries[i], mainstream_file.entries[k])) @@ -1321,7 +1450,10 @@ There is NO WARRANTY, to the extent permitted by law.\n\ } else { - struct conflict *c = XMALLOC (struct conflict); + 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 *); @@ -1346,13 +1478,13 @@ There is NO WARRANTY, to the extent permitted by law.\n\ See whether this entry and the following num_changed entries still exist in mainstream_file and are still consecutive. */ - k_before = index_mapping[i_before]; + k_before = entries_mapping_get (&mapping, i_before); linear = (k_before >= 0); if (linear) { size_t i; for (i = i_before + 1; i <= i_before + num_changed; i++) - if (index_mapping[i] != k_before + (i - i_before)) + if (entries_mapping_get (&mapping, i) != k_before + (i - i_before)) { linear = false; break; @@ -1372,7 +1504,7 @@ There is NO WARRANTY, to the extent permitted by law.\n\ { struct entry *changed_entry = modified_file.entries[j]; size_t i = j + edit->i2 - edit->j2; - ssize_t k = index_mapping[i]; + ssize_t k = entries_mapping_get (&mapping, i); ASSERT (k >= 0); if (entry_equals (ancestor_file.entries[i], mainstream_file.entries[k])) @@ -1383,7 +1515,10 @@ There is NO WARRANTY, to the extent permitted by law.\n\ } else { - struct conflict *c = XMALLOC (struct conflict); + 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 *); @@ -1409,7 +1544,7 @@ There is NO WARRANTY, to the extent permitted by law.\n\ ssize_t k_first; bool linear_unchanged; i_first = edit->i1; - k_first = index_mapping[i_first]; + k_first = entries_mapping_get (&mapping, i_first); linear_unchanged = (k_first >= 0 && entry_equals (ancestor_file.entries[i_first], @@ -1418,9 +1553,9 @@ There is NO WARRANTY, to the extent permitted by law.\n\ { size_t i; for (i = i_first + 1; i <= edit->i2; i++) - if (!(index_mapping[i] == k_first + (i - i_first) + if (!(entries_mapping_get (&mapping, i) == k_first + (i - i_first) && entry_equals (ancestor_file.entries[i], - mainstream_file.entries[index_mapping[i]]))) + mainstream_file.entries[entries_mapping_get (&mapping, i)]))) { linear_unchanged = false; break; @@ -1439,7 +1574,7 @@ There is NO WARRANTY, to the extent permitted by law.\n\ } for (i = edit->i1; i <= edit->i2; i++) { - ssize_t k = index_mapping[i]; + ssize_t k = entries_mapping_get (&mapping, i); ASSERT (k >= 0); ASSERT (entry_equals (ancestor_file.entries[i], mainstream_file.entries[k])); @@ -1489,11 +1624,14 @@ There is NO WARRANTY, to the extent permitted by law.\n\ 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. */ { - size_t n = gl_list_size (result_entries); - size_t i; - for (i = 0; i < n; i++) - entry_write (fp, (struct entry *) gl_list_get_at (result_entries, i)); + 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))