OSDN Git Service

Add libsvn_diff
authorFrank Li <lznuaa@gmail.com>
Tue, 16 Dec 2008 13:09:22 +0000 (21:09 +0800)
committerFrank Li <lznuaa@gmail.com>
Tue, 16 Dec 2008 13:09:22 +0000 (21:09 +0800)
src/TortoiseMerge/libsvn_diff/deprecated.c [new file with mode: 0644]
src/TortoiseMerge/libsvn_diff/diff.c [new file with mode: 0644]
src/TortoiseMerge/libsvn_diff/diff3.c [new file with mode: 0644]
src/TortoiseMerge/libsvn_diff/diff4.c [new file with mode: 0644]
src/TortoiseMerge/libsvn_diff/diff_file.c [new file with mode: 0644]
src/TortoiseMerge/libsvn_diff/diff_memory.c [new file with mode: 0644]
src/TortoiseMerge/libsvn_diff/lcs.c [new file with mode: 0644]
src/TortoiseMerge/libsvn_diff/token.c [new file with mode: 0644]
src/TortoiseMerge/libsvn_diff/util.c [new file with mode: 0644]

diff --git a/src/TortoiseMerge/libsvn_diff/deprecated.c b/src/TortoiseMerge/libsvn_diff/deprecated.c
new file mode 100644 (file)
index 0000000..a6ad595
--- /dev/null
@@ -0,0 +1,70 @@
+/*\r
+ * deprecated.c:  holding file for all deprecated APIs.\r
+ *                "we can't lose 'em, but we can shun 'em!"\r
+ *\r
+ * ====================================================================\r
+ * Copyright (c) 2000-2008 CollabNet.  All rights reserved.\r
+ *\r
+ * This software is licensed as described in the file COPYING, which\r
+ * you should have received as part of this distribution.  The terms\r
+ * are also available at http://subversion.tigris.org/license-1.html.\r
+ * If newer versions of this license are posted there, you may use a\r
+ * newer version instead, at your option.\r
+ *\r
+ * This software consists of voluntary contributions made by many\r
+ * individuals.  For exact contribution history, see the revision\r
+ * history and logs, available at http://subversion.tigris.org/.\r
+ * ====================================================================\r
+ */\r
+\r
+/* ==================================================================== */\r
+\r
+\r
+\f\r
+/*** Includes. ***/\r
+\r
+/* We define this here to remove any further warnings about the usage of\r
+   deprecated functions in this file. */\r
+#define SVN_DEPRECATED\r
+\r
+#include "svn_diff.h"\r
+#include "svn_utf.h"\r
+\r
+#include "svn_private_config.h"\r
+\r
+\r
+\r
+\f\r
+/*** Code. ***/\r
+\r
+/*** From diff_file.c ***/\r
+svn_error_t *\r
+svn_diff_file_output_unified2(svn_stream_t *output_stream,\r
+                              svn_diff_t *diff,\r
+                              const char *original_path,\r
+                              const char *modified_path,\r
+                              const char *original_header,\r
+                              const char *modified_header,\r
+                              const char *header_encoding,\r
+                              apr_pool_t *pool)\r
+{\r
+  return svn_diff_file_output_unified3(output_stream, diff,\r
+                                       original_path, modified_path,\r
+                                       original_header, modified_header,\r
+                                       header_encoding, NULL, FALSE, pool);\r
+}\r
+\r
+svn_error_t *\r
+svn_diff_file_output_unified(svn_stream_t *output_stream,\r
+                             svn_diff_t *diff,\r
+                             const char *original_path,\r
+                             const char *modified_path,\r
+                             const char *original_header,\r
+                             const char *modified_header,\r
+                             apr_pool_t *pool)\r
+{\r
+  return svn_diff_file_output_unified2(output_stream, diff,\r
+                                       original_path, modified_path,\r
+                                       original_header, modified_header,\r
+                                       SVN_APR_LOCALE_CHARSET, pool);\r
+}\r
diff --git a/src/TortoiseMerge/libsvn_diff/diff.c b/src/TortoiseMerge/libsvn_diff/diff.c
new file mode 100644 (file)
index 0000000..627cec3
--- /dev/null
@@ -0,0 +1,146 @@
+/*\r
+ * diff.c :  routines for doing diffs\r
+ *\r
+ * ====================================================================\r
+ * Copyright (c) 2000-2004 CollabNet.  All rights reserved.\r
+ *\r
+ * This software is licensed as described in the file COPYING, which\r
+ * you should have received as part of this distribution.  The terms\r
+ * are also available at http://subversion.tigris.org/license-1.html.\r
+ * If newer versions of this license are posted there, you may use a\r
+ * newer version instead, at your option.\r
+ *\r
+ * This software consists of voluntary contributions made by many\r
+ * individuals.  For exact contribution history, see the revision\r
+ * history and logs, available at http://subversion.tigris.org/.\r
+ * ====================================================================\r
+ */\r
+\r
+\r
+#include <apr.h>\r
+#include <apr_pools.h>\r
+#include <apr_general.h>\r
+\r
+#include "svn_pools.h"\r
+#include "svn_error.h"\r
+#include "svn_diff.h"\r
+#include "svn_types.h"\r
+\r
+#include "diff.h"\r
+\r
+\r
+svn_diff_t *\r
+svn_diff__diff(svn_diff__lcs_t *lcs,\r
+               apr_off_t original_start, apr_off_t modified_start,\r
+               svn_boolean_t want_common,\r
+               apr_pool_t *pool)\r
+{\r
+  svn_diff_t *diff;\r
+  svn_diff_t **diff_ref = &diff;\r
+\r
+  while (1)\r
+    {\r
+      if (original_start < lcs->position[0]->offset\r
+          || modified_start < lcs->position[1]->offset)\r
+      {\r
+          (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));\r
+\r
+          (*diff_ref)->type = svn_diff__type_diff_modified;\r
+          (*diff_ref)->original_start = original_start - 1;\r
+          (*diff_ref)->original_length =\r
+            lcs->position[0]->offset - original_start;\r
+          (*diff_ref)->modified_start = modified_start - 1;\r
+          (*diff_ref)->modified_length =\r
+            lcs->position[1]->offset - modified_start;\r
+          (*diff_ref)->latest_start = 0;\r
+          (*diff_ref)->latest_length = 0;\r
+\r
+          diff_ref = &(*diff_ref)->next;\r
+      }\r
+\r
+      /* Detect the EOF */\r
+      if (lcs->length == 0)\r
+          break;\r
+\r
+      original_start = lcs->position[0]->offset;\r
+      modified_start = lcs->position[1]->offset;\r
+\r
+      if (want_common)\r
+        {\r
+          (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));\r
+\r
+          (*diff_ref)->type = svn_diff__type_common;\r
+          (*diff_ref)->original_start = original_start - 1;\r
+          (*diff_ref)->original_length = lcs->length;\r
+          (*diff_ref)->modified_start = modified_start - 1;\r
+          (*diff_ref)->modified_length = lcs->length;\r
+          (*diff_ref)->latest_start = 0;\r
+          (*diff_ref)->latest_length = 0;\r
+\r
+          diff_ref = &(*diff_ref)->next;\r
+        }\r
+\r
+      original_start += lcs->length;\r
+      modified_start += lcs->length;\r
+\r
+      lcs = lcs->next;\r
+    }\r
+\r
+  *diff_ref = NULL;\r
+\r
+  return diff;\r
+}\r
+\r
+\r
+svn_error_t *\r
+svn_diff_diff(svn_diff_t **diff,\r
+              void *diff_baton,\r
+              const svn_diff_fns_t *vtable,\r
+              apr_pool_t *pool)\r
+{\r
+  svn_diff__tree_t *tree;\r
+  svn_diff__position_t *position_list[2];\r
+  svn_diff__lcs_t *lcs;\r
+  apr_pool_t *subpool;\r
+  apr_pool_t *treepool;\r
+\r
+  *diff = NULL;\r
+\r
+  subpool = svn_pool_create(pool);\r
+  treepool = svn_pool_create(pool);\r
+\r
+  svn_diff__tree_create(&tree, treepool);\r
+\r
+  /* Insert the data into the tree */\r
+  SVN_ERR(svn_diff__get_tokens(&position_list[0],\r
+                               tree,\r
+                               diff_baton, vtable,\r
+                               svn_diff_datasource_original,\r
+                               subpool));\r
+\r
+  SVN_ERR(svn_diff__get_tokens(&position_list[1],\r
+                               tree,\r
+                               diff_baton, vtable,\r
+                               svn_diff_datasource_modified,\r
+                               subpool));\r
+\r
+  /* The cool part is that we don't need the tokens anymore.\r
+   * Allow the app to clean them up if it wants to.\r
+   */\r
+  if (vtable->token_discard_all != NULL)\r
+    vtable->token_discard_all(diff_baton);\r
+\r
+  /* We don't need the nodes in the tree either anymore, nor the tree itself */\r
+  svn_pool_destroy(treepool);\r
+\r
+  /* Get the lcs */\r
+  lcs = svn_diff__lcs(position_list[0], position_list[1], subpool);\r
+\r
+  /* Produce the diff */\r
+  *diff = svn_diff__diff(lcs, 1, 1, TRUE, pool);\r
+\r
+  /* Get rid of all the data we don't have a use for anymore */\r
+  svn_pool_destroy(subpool);\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
diff --git a/src/TortoiseMerge/libsvn_diff/diff3.c b/src/TortoiseMerge/libsvn_diff/diff3.c
new file mode 100644 (file)
index 0000000..9bf4157
--- /dev/null
@@ -0,0 +1,498 @@
+/*\r
+ * diff3.c :  routines for doing diffs\r
+ *\r
+ * ====================================================================\r
+ * Copyright (c) 2000-2004 CollabNet.  All rights reserved.\r
+ *\r
+ * This software is licensed as described in the file COPYING, which\r
+ * you should have received as part of this distribution.  The terms\r
+ * are also available at http://subversion.tigris.org/license-1.html.\r
+ * If newer versions of this license are posted there, you may use a\r
+ * newer version instead, at your option.\r
+ *\r
+ * This software consists of voluntary contributions made by many\r
+ * individuals.  For exact contribution history, see the revision\r
+ * history and logs, available at http://subversion.tigris.org/.\r
+ * ====================================================================\r
+ */\r
+\r
+\r
+#include <apr.h>\r
+#include <apr_pools.h>\r
+#include <apr_general.h>\r
+\r
+#include "svn_pools.h"\r
+#include "svn_error.h"\r
+#include "svn_diff.h"\r
+#include "svn_types.h"\r
+\r
+#include "diff.h"\r
+\r
+\r
+void\r
+svn_diff__resolve_conflict(svn_diff_t *hunk,\r
+                           svn_diff__position_t **position_list1,\r
+                           svn_diff__position_t **position_list2,\r
+                           apr_pool_t *pool)\r
+{\r
+    apr_off_t modified_start = hunk->modified_start + 1;\r
+    apr_off_t latest_start = hunk->latest_start + 1;\r
+    apr_off_t common_length;\r
+    apr_off_t modified_length = hunk->modified_length;\r
+    apr_off_t latest_length = hunk->latest_length;\r
+    svn_diff__position_t *start_position[2];\r
+    svn_diff__position_t *position[2];\r
+    svn_diff__lcs_t *lcs = NULL;\r
+    svn_diff__lcs_t **lcs_ref = &lcs;\r
+    svn_diff_t **diff_ref = &hunk->resolved_diff;\r
+    apr_pool_t *subpool;\r
+\r
+    /* First find the starting positions for the\r
+     * comparison\r
+     */\r
+\r
+    start_position[0] = *position_list1;\r
+    start_position[1] = *position_list2;\r
+\r
+    while (start_position[0]->offset < modified_start)\r
+      start_position[0] = start_position[0]->next;\r
+\r
+    while (start_position[1]->offset < latest_start)\r
+      start_position[1] = start_position[1]->next;\r
+\r
+    position[0] = start_position[0];\r
+    position[1] = start_position[1];\r
+\r
+    common_length = modified_length < latest_length\r
+                  ? modified_length : latest_length;\r
+\r
+    while (common_length > 0\r
+           && position[0]->node == position[1]->node)\r
+      {\r
+        position[0] = position[0]->next;\r
+        position[1] = position[1]->next;\r
+\r
+        common_length--;\r
+      }\r
+\r
+    if (common_length == 0\r
+        && modified_length == latest_length)\r
+      {\r
+        hunk->type = svn_diff__type_diff_common;\r
+        hunk->resolved_diff = NULL;\r
+\r
+        *position_list1 = position[0];\r
+        *position_list2 = position[1];\r
+\r
+        return;\r
+      }\r
+\r
+    hunk->type = svn_diff__type_conflict;\r
+\r
+    /* ### If we have a conflict we can try to find the\r
+     * ### common parts in it by getting an lcs between\r
+     * ### modified (start to start + length) and\r
+     * ### latest (start to start + length).\r
+     * ### We use this lcs to create a simple diff.  Only\r
+     * ### where there is a diff between the two, we have\r
+     * ### a conflict.\r
+     * ### This raises a problem; several common diffs and\r
+     * ### conflicts can occur within the same original\r
+     * ### block.  This needs some thought.\r
+     * ###\r
+     * ### NB: We can use the node _pointers_ to identify\r
+     * ###     different tokens\r
+     */\r
+\r
+    subpool = svn_pool_create(pool);\r
+\r
+    /* Calculate how much of the two sequences was\r
+     * actually the same.\r
+     */\r
+    common_length = (modified_length < latest_length\r
+                    ? modified_length : latest_length)\r
+                  - common_length;\r
+\r
+    /* If there were matching symbols at the start of\r
+     * both sequences, record that fact.\r
+     */\r
+    if (common_length > 0)\r
+      {\r
+        lcs = apr_palloc(subpool, sizeof(*lcs));\r
+        lcs->next = NULL;\r
+        lcs->position[0] = start_position[0];\r
+        lcs->position[1] = start_position[1];\r
+        lcs->length = common_length;\r
+\r
+        lcs_ref = &lcs->next;\r
+      }\r
+\r
+    modified_length -= common_length;\r
+    latest_length -= common_length;\r
+\r
+    modified_start = start_position[0]->offset;\r
+    latest_start = start_position[1]->offset;\r
+\r
+    start_position[0] = position[0];\r
+    start_position[1] = position[1];\r
+\r
+    /* Create a new ring for svn_diff__lcs to grok.\r
+     * We can safely do this given we don't need the\r
+     * positions we processed anymore.\r
+     */\r
+    if (modified_length == 0)\r
+      {\r
+        *position_list1 = position[0];\r
+        position[0] = NULL;\r
+      }\r
+    else\r
+      {\r
+        while (--modified_length)\r
+          position[0] = position[0]->next;\r
+\r
+        *position_list1 = position[0]->next;\r
+        position[0]->next = start_position[0];\r
+      }\r
+\r
+    if (latest_length == 0)\r
+      {\r
+        *position_list2 = position[1];\r
+        position[1] = NULL;\r
+      }\r
+    else\r
+      {\r
+        while (--latest_length)\r
+          position[1] = position[1]->next;\r
+\r
+        *position_list2 = position[1]->next;\r
+        position[1]->next = start_position[1];\r
+      }\r
+\r
+    *lcs_ref = svn_diff__lcs(position[0], position[1],\r
+                             subpool);\r
+\r
+    /* Fix up the EOF lcs element in case one of\r
+     * the two sequences was NULL.\r
+     */\r
+    if ((*lcs_ref)->position[0]->offset == 1)\r
+      (*lcs_ref)->position[0] = *position_list1;\r
+\r
+    if ((*lcs_ref)->position[1]->offset == 1)\r
+      (*lcs_ref)->position[1] = *position_list2;\r
+\r
+    /* Restore modified_length and latest_length */\r
+    modified_length = hunk->modified_length;\r
+    latest_length = hunk->latest_length;\r
+\r
+    /* Produce the resolved diff */\r
+    while (1)\r
+      {\r
+        if (modified_start < lcs->position[0]->offset\r
+            || latest_start < lcs->position[1]->offset)\r
+          {\r
+            (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));\r
+\r
+            (*diff_ref)->type = svn_diff__type_conflict;\r
+            (*diff_ref)->original_start = hunk->original_start;\r
+            (*diff_ref)->original_length = hunk->original_length;\r
+            (*diff_ref)->modified_start = modified_start - 1;\r
+            (*diff_ref)->modified_length = lcs->position[0]->offset\r
+                                           - modified_start;\r
+            (*diff_ref)->latest_start = latest_start - 1;\r
+            (*diff_ref)->latest_length = lcs->position[1]->offset\r
+                                         - latest_start;\r
+            (*diff_ref)->resolved_diff = NULL;\r
+\r
+            diff_ref = &(*diff_ref)->next;\r
+          }\r
+\r
+        /* Detect the EOF */\r
+        if (lcs->length == 0)\r
+          break;\r
+\r
+        modified_start = lcs->position[0]->offset;\r
+        latest_start = lcs->position[1]->offset;\r
+\r
+        (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));\r
+\r
+        (*diff_ref)->type = svn_diff__type_diff_common;\r
+        (*diff_ref)->original_start = hunk->original_start;\r
+        (*diff_ref)->original_length = hunk->original_length;\r
+        (*diff_ref)->modified_start = modified_start - 1;\r
+        (*diff_ref)->modified_length = lcs->length;\r
+        (*diff_ref)->latest_start = latest_start - 1;\r
+        (*diff_ref)->latest_length = lcs->length;\r
+        (*diff_ref)->resolved_diff = NULL;\r
+\r
+        diff_ref = &(*diff_ref)->next;\r
+\r
+        modified_start += lcs->length;\r
+        latest_start += lcs->length;\r
+\r
+        lcs = lcs->next;\r
+      }\r
+\r
+    *diff_ref = NULL;\r
+\r
+    svn_pool_destroy(subpool);\r
+}\r
+\r
+\r
+svn_error_t *\r
+svn_diff_diff3(svn_diff_t **diff,\r
+               void *diff_baton,\r
+               const svn_diff_fns_t *vtable,\r
+               apr_pool_t *pool)\r
+{\r
+  svn_diff__tree_t *tree;\r
+  svn_diff__position_t *position_list[3];\r
+  svn_diff__lcs_t *lcs_om;\r
+  svn_diff__lcs_t *lcs_ol;\r
+  apr_pool_t *subpool;\r
+  apr_pool_t *treepool;\r
+\r
+  *diff = NULL;\r
+\r
+  subpool = svn_pool_create(pool);\r
+  treepool = svn_pool_create(pool);\r
+\r
+  svn_diff__tree_create(&tree, treepool);\r
+\r
+  SVN_ERR(svn_diff__get_tokens(&position_list[0],\r
+                               tree,\r
+                               diff_baton, vtable,\r
+                               svn_diff_datasource_original,\r
+                               subpool));\r
+\r
+  SVN_ERR(svn_diff__get_tokens(&position_list[1],\r
+                               tree,\r
+                               diff_baton, vtable,\r
+                               svn_diff_datasource_modified,\r
+                               subpool));\r
+\r
+  SVN_ERR(svn_diff__get_tokens(&position_list[2],\r
+                               tree,\r
+                               diff_baton, vtable,\r
+                               svn_diff_datasource_latest,\r
+                               subpool));\r
+\r
+  /* Get rid of the tokens, we don't need them to calc the diff */\r
+  if (vtable->token_discard_all != NULL)\r
+    vtable->token_discard_all(diff_baton);\r
+\r
+  /* We don't need the nodes in the tree either anymore, nor the tree itself */\r
+  svn_pool_destroy(treepool);\r
+\r
+  /* Get the lcs for original-modified and original-latest */\r
+  lcs_om = svn_diff__lcs(position_list[0], position_list[1],\r
+                         subpool);\r
+  lcs_ol = svn_diff__lcs(position_list[0], position_list[2],\r
+                         subpool);\r
+\r
+  /* Produce a merged diff */\r
+  {\r
+    svn_diff_t **diff_ref = diff;\r
+\r
+    apr_off_t original_start = 1;\r
+    apr_off_t modified_start = 1;\r
+    apr_off_t latest_start = 1;\r
+    apr_off_t original_sync;\r
+    apr_off_t modified_sync;\r
+    apr_off_t latest_sync;\r
+    apr_off_t common_length;\r
+    apr_off_t original_length;\r
+    apr_off_t modified_length;\r
+    apr_off_t latest_length;\r
+    svn_boolean_t is_modified;\r
+    svn_boolean_t is_latest;\r
+    svn_diff__position_t sentinel_position[2];\r
+\r
+    /* Point the position lists to the start of the list\r
+     * so that common_diff/conflict detection actually is\r
+     * able to work.\r
+     */\r
+    if (position_list[1])\r
+      {\r
+        sentinel_position[0].next = position_list[1]->next;\r
+        sentinel_position[0].offset = position_list[1]->offset + 1;\r
+        position_list[1]->next = &sentinel_position[0];\r
+        position_list[1] = sentinel_position[0].next;\r
+      }\r
+    else\r
+      {\r
+        sentinel_position[0].offset = 1;\r
+        sentinel_position[0].next = NULL;\r
+        position_list[1] = &sentinel_position[0];\r
+      }\r
+\r
+    if (position_list[2])\r
+      {\r
+        sentinel_position[1].next = position_list[2]->next;\r
+        sentinel_position[1].offset = position_list[2]->offset + 1;\r
+        position_list[2]->next = &sentinel_position[1];\r
+        position_list[2] = sentinel_position[1].next;\r
+      }\r
+    else\r
+      {\r
+        sentinel_position[1].offset = 1;\r
+        sentinel_position[1].next = NULL;\r
+        position_list[2] = &sentinel_position[1];\r
+      }\r
+\r
+    while (1)\r
+      {\r
+        /* Find the sync points */\r
+        while (1)\r
+          {\r
+            if (lcs_om->position[0]->offset > lcs_ol->position[0]->offset)\r
+              {\r
+                original_sync = lcs_om->position[0]->offset;\r
+\r
+                while (lcs_ol->position[0]->offset + lcs_ol->length\r
+                       < original_sync)\r
+                  lcs_ol = lcs_ol->next;\r
+\r
+                /* If the sync point is the EOF, and our current lcs segment\r
+                 * doesn't reach as far as EOF, we need to skip this segment.\r
+                 */\r
+                if (lcs_om->length == 0 && lcs_ol->length > 0\r
+                    && lcs_ol->position[0]->offset + lcs_ol->length\r
+                       == original_sync\r
+                    && lcs_ol->position[1]->offset + lcs_ol->length\r
+                       != lcs_ol->next->position[1]->offset)\r
+                  lcs_ol = lcs_ol->next;\r
+\r
+                if (lcs_ol->position[0]->offset <= original_sync)\r
+                    break;\r
+              }\r
+            else\r
+              {\r
+                original_sync = lcs_ol->position[0]->offset;\r
+\r
+                while (lcs_om->position[0]->offset + lcs_om->length\r
+                       < original_sync)\r
+                  lcs_om = lcs_om->next;\r
+\r
+                /* If the sync point is the EOF, and our current lcs segment\r
+                 * doesn't reach as far as EOF, we need to skip this segment.\r
+                 */\r
+                if (lcs_ol->length == 0 && lcs_om->length > 0\r
+                    && lcs_om->position[0]->offset + lcs_om->length\r
+                       == original_sync\r
+                    && lcs_om->position[1]->offset + lcs_om->length\r
+                       != lcs_om->next->position[1]->offset)\r
+                  lcs_om = lcs_om->next;\r
+\r
+                if (lcs_om->position[0]->offset <= original_sync)\r
+                    break;\r
+              }\r
+          }\r
+\r
+        modified_sync = lcs_om->position[1]->offset\r
+                      + (original_sync - lcs_om->position[0]->offset);\r
+        latest_sync = lcs_ol->position[1]->offset\r
+                    + (original_sync - lcs_ol->position[0]->offset);\r
+\r
+        /* Determine what is modified, if anything */\r
+        is_modified = lcs_om->position[0]->offset - original_start > 0\r
+                      || lcs_om->position[1]->offset - modified_start > 0;\r
+\r
+        is_latest = lcs_ol->position[0]->offset - original_start > 0\r
+                    || lcs_ol->position[1]->offset - latest_start > 0;\r
+\r
+        if (is_modified || is_latest)\r
+          {\r
+            original_length = original_sync - original_start;\r
+            modified_length = modified_sync - modified_start;\r
+            latest_length = latest_sync - latest_start;\r
+\r
+            (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));\r
+\r
+            (*diff_ref)->original_start = original_start - 1;\r
+            (*diff_ref)->original_length = original_sync - original_start;\r
+            (*diff_ref)->modified_start = modified_start - 1;\r
+            (*diff_ref)->modified_length = modified_length;\r
+            (*diff_ref)->latest_start = latest_start - 1;\r
+            (*diff_ref)->latest_length = latest_length;\r
+            (*diff_ref)->resolved_diff = NULL;\r
+\r
+            if (is_modified && is_latest)\r
+              {\r
+                svn_diff__resolve_conflict(*diff_ref,\r
+                                           &position_list[1],\r
+                                           &position_list[2],\r
+                                           pool);\r
+              }\r
+            else if (is_modified)\r
+              {\r
+                (*diff_ref)->type = svn_diff__type_diff_modified;\r
+              }\r
+            else\r
+              {\r
+                (*diff_ref)->type = svn_diff__type_diff_latest;\r
+              }\r
+\r
+            diff_ref = &(*diff_ref)->next;\r
+          }\r
+\r
+        /* Detect EOF */\r
+        if (lcs_om->length == 0 || lcs_ol->length == 0)\r
+            break;\r
+\r
+        modified_length = lcs_om->length\r
+                          - (original_sync - lcs_om->position[0]->offset);\r
+        latest_length = lcs_ol->length\r
+                        - (original_sync - lcs_ol->position[0]->offset);\r
+        common_length = modified_length < latest_length\r
+                        ? modified_length : latest_length;\r
+\r
+        (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));\r
+\r
+        (*diff_ref)->type = svn_diff__type_common;\r
+        (*diff_ref)->original_start = original_sync - 1;\r
+        (*diff_ref)->original_length = common_length;\r
+        (*diff_ref)->modified_start = modified_sync - 1;\r
+        (*diff_ref)->modified_length = common_length;\r
+        (*diff_ref)->latest_start = latest_sync - 1;\r
+        (*diff_ref)->latest_length = common_length;\r
+        (*diff_ref)->resolved_diff = NULL;\r
+\r
+        diff_ref = &(*diff_ref)->next;\r
+\r
+        /* Set the new offsets */\r
+        original_start = original_sync + common_length;\r
+        modified_start = modified_sync + common_length;\r
+        latest_start = latest_sync + common_length;\r
+\r
+        /* Make it easier for diff_common/conflict detection\r
+           by recording last lcs start positions\r
+         */\r
+        if (position_list[1]->offset < lcs_om->position[1]->offset)\r
+          position_list[1] = lcs_om->position[1];\r
+\r
+        if (position_list[2]->offset < lcs_ol->position[1]->offset)\r
+          position_list[2] = lcs_ol->position[1];\r
+\r
+        /* Make sure we are pointing to lcs entries beyond\r
+         * the range we just processed\r
+         */\r
+        while (original_start >= lcs_om->position[0]->offset + lcs_om->length\r
+               && lcs_om->length > 0)\r
+          {\r
+            lcs_om = lcs_om->next;\r
+          }\r
+\r
+        while (original_start >= lcs_ol->position[0]->offset + lcs_ol->length\r
+               && lcs_ol->length > 0)\r
+          {\r
+            lcs_ol = lcs_ol->next;\r
+          }\r
+      }\r
+\r
+    *diff_ref = NULL;\r
+  }\r
+\r
+  svn_pool_destroy(subpool);\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
diff --git a/src/TortoiseMerge/libsvn_diff/diff4.c b/src/TortoiseMerge/libsvn_diff/diff4.c
new file mode 100644 (file)
index 0000000..3698cc8
--- /dev/null
@@ -0,0 +1,274 @@
+/*\r
+ * diff.c :  routines for doing diffs\r
+ *\r
+ * ====================================================================\r
+ * Copyright (c) 2000-2004 CollabNet.  All rights reserved.\r
+ *\r
+ * This software is licensed as described in the file COPYING, which\r
+ * you should have received as part of this distribution.  The terms\r
+ * are also available at http://subversion.tigris.org/license-1.html.\r
+ * If newer versions of this license are posted there, you may use a\r
+ * newer version instead, at your option.\r
+ *\r
+ * This software consists of voluntary contributions made by many\r
+ * individuals.  For exact contribution history, see the revision\r
+ * history and logs, available at http://subversion.tigris.org/.\r
+ * ====================================================================\r
+ */\r
+\r
+\r
+#include <apr.h>\r
+#include <apr_pools.h>\r
+#include <apr_general.h>\r
+\r
+#include "svn_pools.h"\r
+#include "svn_error.h"\r
+#include "svn_diff.h"\r
+#include "svn_types.h"\r
+\r
+#include "diff.h"\r
+\r
+/*\r
+ * Variance adjustment rules:\r
+ *\r
+ * http://subversion.tigris.org/variance-adjusted-patching.html\r
+ *\r
+ * ###: Expand this comment to contain the full set of adjustment\r
+ * ###: rules instead of pointing to a webpage.\r
+ */\r
+\r
+/*\r
+ * In the text below consider the following:\r
+ *\r
+ * O     = Original\r
+ * M     = Modified\r
+ * L     = Latest\r
+ * A     = Ancestor\r
+ * X:Y   = diff between X and Y\r
+ * X:Y:Z = 3-way diff between X, Y and Z\r
+ * P     = O:L, possibly adjusted\r
+ *\r
+ * diff4 -- Variance adjusted diff algorithm\r
+ *\r
+ * 1. Create a diff O:L and call that P.\r
+ *\r
+ * 2. Morph P into a 3-way diff by performing the following\r
+ *    transformation: O:L -> O:O:L.\r
+ *\r
+ * 3. Create a diff A:O.\r
+ *\r
+ * 4. Using A:O...\r
+ *\r
+ * #. Using M:A...\r
+ *\r
+ * #. Resolve conflicts...\r
+ *\r
+\r
+   1. Out-range added line: decrement the line numbers in every hunk in P\r
+      that comes after the addition. This undoes the effect of the add, since\r
+      the add never happened in D.\r
+\r
+   2. Out-range deleted line: increment the line numbers in every hunk in P\r
+      that comes after the deletion. This undoes the effect of the deletion,\r
+      since the deletion never happened in D.\r
+\r
+   3. Out-range edited line: do nothing. Out-range edits are irrelevant to P.\r
+\r
+   4. Added line in context range in P: remove the corresponding line from\r
+      the context, optionally replacing it with new context based on that\r
+      region in M, and adjust line numbers and mappings appropriately.\r
+\r
+   5. Added line in affected text range in P: this is a dependency problem\r
+      -- part of the change T:18-T:19 depends on changes introduced to T after\r
+      B branched. There are several possible behaviors, depending on what the\r
+      user wants. One is to generate an informative error, stating that\r
+      T:18-T:19 depends on some other change (T:N-T:M, where N>=8, M<=18,\r
+      and M-N == 1); the exact revisions can be discovered automatically using\r
+      the same process as "cvs annotate", though it may take some time to do\r
+      so. Another option is to include the change in P, as an insertion of the\r
+      "after" version of the text, and adjust line numbers and mappings\r
+      accordingly. (And if all this isn't sounding a lot like a directory\r
+      merge algorithm, try drinking more of the Kool-Aid.) A third option is\r
+      to include it as an insertion, but with metadata (such as CVS-style\r
+      conflict markers) indicating that the line attempting to be patched\r
+      does not exist in B.\r
+\r
+   6. Deleted line that is in-range in P: request another universe -- this\r
+      situation can't happen in ours.\r
+\r
+   7. In-range edited line: reverse that edit in the "before" version of the\r
+      corresponding line in the appropriate hunk in P, to obtain the version of\r
+      the line that will be found in B when P is applied.\r
+*/\r
+\r
+\r
+static void\r
+adjust_diff(svn_diff_t *diff, svn_diff_t *adjust)\r
+{\r
+  svn_diff_t *hunk;\r
+  apr_off_t range_start;\r
+  apr_off_t range_end;\r
+  apr_off_t adjustment;\r
+\r
+  for (; adjust; adjust = adjust->next)\r
+    {\r
+      range_start = adjust->modified_start;\r
+      range_end = range_start + adjust->modified_length;\r
+      adjustment = adjust->original_length - adjust->modified_length;\r
+\r
+      /* No change in line count, so no modifications. [3, 7] */\r
+      if (adjustment == 0)\r
+        continue;\r
+\r
+      for (hunk = diff; hunk; hunk = hunk->next)\r
+        {\r
+          /* Changes are in the range before this hunk.  Adjust the start\r
+           * of the hunk. [1, 2]\r
+           */\r
+          if (hunk->modified_start >= range_end)\r
+            {\r
+              hunk->modified_start += adjustment;\r
+              continue;\r
+            }\r
+\r
+          /* Changes are in the range beyond this hunk.  No adjustments\r
+           * needed. [1, 2]\r
+           */\r
+          if (hunk->modified_start + hunk->modified_length <= range_start)\r
+            continue;\r
+\r
+          /* From here on changes are in the range of this hunk. */\r
+\r
+          /* This is a context hunk.  Adjust the length. [4]\r
+           */\r
+          if (hunk->type == svn_diff__type_diff_modified)\r
+            {\r
+              hunk->modified_length += adjustment;\r
+              continue;\r
+            }\r
+\r
+          /* Mark as conflicted. This happens in the reverse case when a line\r
+           * is added in range and in the forward case when a line is deleted\r
+           * in range. [5 (reverse), 6 (forward)]\r
+           */\r
+          if (adjustment < 0)\r
+              hunk->type = svn_diff__type_conflict;\r
+\r
+          /* Adjust the length of this hunk (reverse the change). [5, 6] */\r
+          hunk->modified_length -= adjustment;\r
+        }\r
+    }\r
+}\r
+\r
+svn_error_t *\r
+svn_diff_diff4(svn_diff_t **diff,\r
+               void *diff_baton,\r
+               const svn_diff_fns_t *vtable,\r
+               apr_pool_t *pool)\r
+{\r
+  svn_diff__tree_t *tree;\r
+  svn_diff__position_t *position_list[4];\r
+  svn_diff__lcs_t *lcs_ol;\r
+  svn_diff__lcs_t *lcs_adjust;\r
+  svn_diff_t *diff_ol;\r
+  svn_diff_t *diff_adjust;\r
+  svn_diff_t *hunk;\r
+  apr_pool_t *subpool;\r
+  apr_pool_t *subpool2;\r
+  apr_pool_t *subpool3;\r
+\r
+  *diff = NULL;\r
+\r
+  subpool = svn_pool_create(pool);\r
+  subpool2 = svn_pool_create(subpool);\r
+  subpool3 = svn_pool_create(subpool2);\r
+\r
+  svn_diff__tree_create(&tree, subpool3);\r
+\r
+  SVN_ERR(svn_diff__get_tokens(&position_list[0],\r
+                               tree,\r
+                               diff_baton, vtable,\r
+                               svn_diff_datasource_original,\r
+                               subpool2));\r
+\r
+  SVN_ERR(svn_diff__get_tokens(&position_list[1],\r
+                               tree,\r
+                               diff_baton, vtable,\r
+                               svn_diff_datasource_modified,\r
+                               subpool));\r
+\r
+  SVN_ERR(svn_diff__get_tokens(&position_list[2],\r
+                               tree,\r
+                               diff_baton, vtable,\r
+                               svn_diff_datasource_latest,\r
+                               subpool));\r
+\r
+  SVN_ERR(svn_diff__get_tokens(&position_list[3],\r
+                               tree,\r
+                               diff_baton, vtable,\r
+                               svn_diff_datasource_ancestor,\r
+                               subpool2));\r
+\r
+  /* Get rid of the tokens, we don't need them to calc the diff */\r
+  if (vtable->token_discard_all != NULL)\r
+    vtable->token_discard_all(diff_baton);\r
+\r
+  /* We don't need the nodes in the tree either anymore, nor the tree itself */\r
+  svn_pool_clear(subpool3);\r
+\r
+  /* Get the lcs for original - latest */\r
+  lcs_ol = svn_diff__lcs(position_list[0], position_list[2], subpool3);\r
+  diff_ol = svn_diff__diff(lcs_ol, 1, 1, TRUE, pool);\r
+\r
+  svn_pool_clear(subpool3);\r
+\r
+  for (hunk = diff_ol; hunk; hunk = hunk->next)\r
+    {\r
+      hunk->latest_start = hunk->modified_start;\r
+      hunk->latest_length = hunk->modified_length;\r
+      hunk->modified_start = hunk->original_start;\r
+      hunk->modified_length = hunk->original_length;\r
+\r
+      if (hunk->type == svn_diff__type_diff_modified)\r
+          hunk->type = svn_diff__type_diff_latest;\r
+      else\r
+          hunk->type = svn_diff__type_diff_modified;\r
+    }\r
+\r
+  /* Get the lcs for common ancestor - original\r
+   * Do reverse adjustements\r
+   */\r
+  lcs_adjust = svn_diff__lcs(position_list[3], position_list[2], subpool3);\r
+  diff_adjust = svn_diff__diff(lcs_adjust, 1, 1, FALSE, subpool3);\r
+  adjust_diff(diff_ol, diff_adjust);\r
+\r
+  svn_pool_clear(subpool3);\r
+\r
+  /* Get the lcs for modified - common ancestor\r
+   * Do forward adjustments\r
+   */\r
+  lcs_adjust = svn_diff__lcs(position_list[1], position_list[3], subpool3);\r
+  diff_adjust = svn_diff__diff(lcs_adjust, 1, 1, FALSE, subpool3);\r
+  adjust_diff(diff_ol, diff_adjust);\r
+\r
+  /* Get rid of the position lists for original and ancestor, and delete\r
+   * our scratchpool.\r
+   */\r
+  svn_pool_destroy(subpool2);\r
+\r
+  /* Now we try and resolve the conflicts we encountered */\r
+  for (hunk = diff_ol; hunk; hunk = hunk->next)\r
+    {\r
+      if (hunk->type == svn_diff__type_conflict)\r
+        {\r
+          svn_diff__resolve_conflict(hunk, &position_list[1],\r
+                                     &position_list[2], pool);\r
+        }\r
+    }\r
+\r
+  svn_pool_destroy(subpool);\r
+\r
+  *diff = diff_ol;\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
diff --git a/src/TortoiseMerge/libsvn_diff/diff_file.c b/src/TortoiseMerge/libsvn_diff/diff_file.c
new file mode 100644 (file)
index 0000000..88f6a1d
--- /dev/null
@@ -0,0 +1,1814 @@
+/*\r
+ * diff_file.c :  routines for doing diffs on files\r
+ *\r
+ * ====================================================================\r
+ * Copyright (c) 2000-2006 CollabNet.  All rights reserved.\r
+ *\r
+ * This software is licensed as described in the file COPYING, which\r
+ * you should have received as part of this distribution.  The terms\r
+ * are also available at http://subversion.tigris.org/license-1.html.\r
+ * If newer versions of this license are posted there, you may use a\r
+ * newer version instead, at your option.\r
+ *\r
+ * This software consists of voluntary contributions made by many\r
+ * individuals.  For exact contribution history, see the revision\r
+ * history and logs, available at http://subversion.tigris.org/.\r
+ * ====================================================================\r
+ */\r
+\r
+\r
+#include <apr.h>\r
+#include <apr_pools.h>\r
+#include <apr_general.h>\r
+#include <apr_file_io.h>\r
+#include <apr_file_info.h>\r
+#include <apr_time.h>\r
+#include <apr_mmap.h>\r
+#include <apr_getopt.h>\r
+\r
+#include "svn_error.h"\r
+#include "svn_diff.h"\r
+#include "svn_types.h"\r
+#include "svn_string.h"\r
+#include "svn_io.h"\r
+#include "svn_utf.h"\r
+#include "svn_pools.h"\r
+#include "diff.h"\r
+#include "svn_private_config.h"\r
+#include "svn_path.h"\r
+#include "svn_ctype.h"\r
+\r
+/* A token, i.e. a line read from a file. */\r
+typedef struct svn_diff__file_token_t\r
+{\r
+  /* Next token in free list. */\r
+  struct svn_diff__file_token_t *next;\r
+  svn_diff_datasource_e datasource;\r
+  /* Offset in the datasource. */\r
+  apr_off_t offset;\r
+  /* Offset of the normalized token (may skip leading whitespace) */\r
+  apr_off_t norm_offset;\r
+  /* Total length - before normalization. */\r
+  apr_off_t raw_length;\r
+  /* Total length - after normalization. */\r
+  apr_off_t length;\r
+} svn_diff__file_token_t;\r
+\r
+\r
+typedef struct svn_diff__file_baton_t\r
+{\r
+  const svn_diff_file_options_t *options;\r
+  const char *path[4];\r
+\r
+  apr_file_t *file[4];\r
+  apr_off_t size[4];\r
+\r
+  int chunk[4];\r
+  char *buffer[4];\r
+  char *curp[4];\r
+  char *endp[4];\r
+\r
+  /* List of free tokens that may be reused. */\r
+  svn_diff__file_token_t *tokens;\r
+\r
+  svn_diff__normalize_state_t normalize_state[4];\r
+\r
+  apr_pool_t *pool;\r
+} svn_diff__file_baton_t;\r
+\r
+\r
+/* Look for the start of an end-of-line sequence (i.e. CR or LF)\r
+ * in the array pointed to by BUF, of length LEN.\r
+ * If such a byte is found, return the pointer to it, else return NULL.\r
+ */\r
+static char *\r
+find_eol_start(char *buf, apr_size_t len)\r
+{\r
+  for (; len > 0; ++buf, --len)\r
+    {\r
+      if (*buf == '\n' || *buf == '\r')\r
+        return buf;\r
+    }\r
+  return NULL;\r
+}\r
+\r
+static int\r
+datasource_to_index(svn_diff_datasource_e datasource)\r
+{\r
+  switch (datasource)\r
+    {\r
+    case svn_diff_datasource_original:\r
+      return 0;\r
+\r
+    case svn_diff_datasource_modified:\r
+      return 1;\r
+\r
+    case svn_diff_datasource_latest:\r
+      return 2;\r
+\r
+    case svn_diff_datasource_ancestor:\r
+      return 3;\r
+    }\r
+\r
+  return -1;\r
+}\r
+\r
+/* Files are read in chunks of 128k.  There is no support for this number\r
+ * whatsoever.  If there is a number someone comes up with that has some\r
+ * argumentation, let's use that.\r
+ */\r
+#define CHUNK_SHIFT 17\r
+#define CHUNK_SIZE (1 << CHUNK_SHIFT)\r
+\r
+#define chunk_to_offset(chunk) ((chunk) << CHUNK_SHIFT)\r
+#define offset_to_chunk(offset) ((offset) >> CHUNK_SHIFT)\r
+#define offset_in_chunk(offset) ((offset) & (CHUNK_SIZE - 1))\r
+\r
+\r
+/* Read a chunk from a FILE into BUFFER, starting from OFFSET, going for\r
+ * *LENGTH.  The actual bytes read are stored in *LENGTH on return.\r
+ */\r
+static APR_INLINE svn_error_t *\r
+read_chunk(apr_file_t *file, const char *path,\r
+           char *buffer, apr_size_t length,\r
+           apr_off_t offset, apr_pool_t *pool)\r
+{\r
+  /* XXX: The final offset may not be the one we asked for.\r
+   * XXX: Check.\r
+   */\r
+  SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, pool));\r
+  return svn_io_file_read_full(file, buffer, length, NULL, pool);\r
+}\r
+\r
+\r
+/* Map or read a file at PATH. *BUFFER will point to the file\r
+ * contents; if the file was mapped, *FILE and *MM will contain the\r
+ * mmap context; otherwise they will be NULL.  SIZE will contain the\r
+ * file size.  Allocate from POOL.\r
+ */\r
+#if APR_HAS_MMAP\r
+#define MMAP_T_PARAM(NAME) apr_mmap_t **NAME,\r
+#define MMAP_T_ARG(NAME)   &(NAME),\r
+#else\r
+#define MMAP_T_PARAM(NAME)\r
+#define MMAP_T_ARG(NAME)\r
+#endif\r
+\r
+static svn_error_t *\r
+map_or_read_file(apr_file_t **file,\r
+                 MMAP_T_PARAM(mm)\r
+                 char **buffer, apr_off_t *size,\r
+                 const char *path, apr_pool_t *pool)\r
+{\r
+  apr_finfo_t finfo;\r
+  apr_status_t rv;\r
+\r
+  *buffer = NULL;\r
+\r
+  SVN_ERR(svn_io_file_open(file, path, APR_READ, APR_OS_DEFAULT, pool));\r
+  SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE, *file, pool));\r
+\r
+#if APR_HAS_MMAP\r
+  if (finfo.size > APR_MMAP_THRESHOLD)\r
+    {\r
+      rv = apr_mmap_create(mm, *file, 0, finfo.size, APR_MMAP_READ, pool);\r
+      if (rv == APR_SUCCESS)\r
+        {\r
+          *buffer = (*mm)->mm;\r
+        }\r
+\r
+      /* On failure we just fall through and try reading the file into\r
+       * memory instead.\r
+       */\r
+    }\r
+#endif /* APR_HAS_MMAP */\r
+\r
+   if (*buffer == NULL && finfo.size > 0)\r
+    {\r
+      *buffer = apr_palloc(pool, finfo.size);\r
+\r
+      SVN_ERR(svn_io_file_read_full(*file, *buffer, finfo.size, NULL, pool));\r
+\r
+      /* Since we have the entire contents of the file we can\r
+       * close it now.\r
+       */\r
+      SVN_ERR(svn_io_file_close(*file, pool));\r
+\r
+      *file = NULL;\r
+    }\r
+\r
+  *size = finfo.size;\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\r
+/* Implements svn_diff_fns_t::datasource_open */\r
+static svn_error_t *\r
+datasource_open(void *baton, svn_diff_datasource_e datasource)\r
+{\r
+  svn_diff__file_baton_t *file_baton = baton;\r
+  int idx;\r
+  apr_finfo_t finfo;\r
+  apr_size_t length;\r
+  char *curp;\r
+  char *endp;\r
+\r
+  idx = datasource_to_index(datasource);\r
+\r
+  SVN_ERR(svn_io_file_open(&file_baton->file[idx], file_baton->path[idx],\r
+                           APR_READ, APR_OS_DEFAULT, file_baton->pool));\r
+\r
+  SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE,\r
+                               file_baton->file[idx], file_baton->pool));\r
+\r
+  file_baton->size[idx] = finfo.size;\r
+  length = finfo.size > CHUNK_SIZE ? CHUNK_SIZE : finfo.size;\r
+\r
+  if (length == 0)\r
+    return SVN_NO_ERROR;\r
+\r
+  endp = curp = apr_palloc(file_baton->pool, length);\r
+  endp += length;\r
+\r
+  file_baton->buffer[idx] = file_baton->curp[idx] = curp;\r
+  file_baton->endp[idx] = endp;\r
+\r
+  return read_chunk(file_baton->file[idx], file_baton->path[idx],\r
+                    curp, length, 0, file_baton->pool);\r
+}\r
+\r
+\r
+/* Implements svn_diff_fns_t::datasource_close */\r
+static svn_error_t *\r
+datasource_close(void *baton, svn_diff_datasource_e datasource)\r
+{\r
+  /* Do nothing.  The compare_token function needs previous datasources\r
+   * to stay available until all datasources are processed.\r
+   */\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+/* Implements svn_diff_fns_t::datasource_get_next_token */\r
+static svn_error_t *\r
+datasource_get_next_token(apr_uint32_t *hash, void **token, void *baton,\r
+                          svn_diff_datasource_e datasource)\r
+{\r
+  svn_diff__file_baton_t *file_baton = baton;\r
+  svn_diff__file_token_t *file_token;\r
+  int idx;\r
+  char *endp;\r
+  char *curp;\r
+  char *eol;\r
+  int last_chunk;\r
+  apr_off_t length;\r
+  apr_uint32_t h = 0;\r
+  /* Did the last chunk end in a CR character? */\r
+  svn_boolean_t had_cr = FALSE;\r
+\r
+  *token = NULL;\r
+\r
+  idx = datasource_to_index(datasource);\r
+\r
+  curp = file_baton->curp[idx];\r
+  endp = file_baton->endp[idx];\r
+\r
+  last_chunk = offset_to_chunk(file_baton->size[idx]);\r
+\r
+  if (curp == endp\r
+      && last_chunk == file_baton->chunk[idx])\r
+    {\r
+      return SVN_NO_ERROR;\r
+    }\r
+\r
+  /* Get a new token */\r
+  file_token = file_baton->tokens;\r
+  if (file_token)\r
+    {\r
+      file_baton->tokens = file_token->next;\r
+    }\r
+  else\r
+    {\r
+      file_token = apr_palloc(file_baton->pool, sizeof(*file_token));\r
+    }\r
+\r
+  file_token->datasource = datasource;\r
+  file_token->offset = chunk_to_offset(file_baton->chunk[idx])\r
+                       + (curp - file_baton->buffer[idx]);\r
+  file_token->raw_length = 0;\r
+  file_token->length = 0;\r
+\r
+  while (1)\r
+    {\r
+      eol = find_eol_start(curp, endp - curp);\r
+      if (eol)\r
+        {\r
+          had_cr = (*eol == '\r');\r
+          eol++;\r
+          /* If we have the whole eol sequence in the chunk... */\r
+          if (!had_cr || eol != endp)\r
+            {\r
+              if (had_cr && *eol == '\n')\r
+                ++eol;\r
+              break;\r
+            }\r
+        }\r
+\r
+      if (file_baton->chunk[idx] == last_chunk)\r
+        {\r
+          eol = endp;\r
+          break;\r
+        }\r
+\r
+      length = endp - curp;\r
+      file_token->raw_length += length;\r
+      svn_diff__normalize_buffer(&curp, &length,\r
+                                 &file_baton->normalize_state[idx],\r
+                                 curp, file_baton->options);\r
+      file_token->length += length;\r
+      h = svn_diff__adler32(h, curp, length);\r
+\r
+      curp = endp = file_baton->buffer[idx];\r
+      file_baton->chunk[idx]++;\r
+      length = file_baton->chunk[idx] == last_chunk ?\r
+        offset_in_chunk(file_baton->size[idx]) : CHUNK_SIZE;\r
+      endp += length;\r
+      file_baton->endp[idx] = endp;\r
+\r
+      SVN_ERR(read_chunk(file_baton->file[idx], file_baton->path[idx],\r
+                         curp, length,\r
+                         chunk_to_offset(file_baton->chunk[idx]),\r
+                         file_baton->pool));\r
+\r
+      /* If the last chunk ended in a CR, we're done. */\r
+      if (had_cr)\r
+        {\r
+          eol = curp;\r
+          if (*curp == '\n')\r
+            ++eol;\r
+          break;\r
+        }\r
+    }\r
+\r
+  length = eol - curp;\r
+  file_token->raw_length += length;\r
+  file_baton->curp[idx] = eol;\r
+\r
+  /* If the file length is exactly a multiple of CHUNK_SIZE, we will end up\r
+   * with a spurious empty token.  Avoid returning it.\r
+   * Note that we use the unnormalized length; we don't want a line containing\r
+   * only spaces (and no trailing newline) to appear like a non-existent\r
+   * line. */\r
+  if (file_token->raw_length > 0)\r
+    {\r
+      char *c = curp;\r
+      svn_diff__normalize_buffer(&c, &length,\r
+                                 &file_baton->normalize_state[idx],\r
+                                 curp, file_baton->options);\r
+\r
+      file_token->norm_offset = file_token->offset + (c - curp);\r
+      file_token->length += length;\r
+\r
+      *hash = svn_diff__adler32(h, c, length);\r
+      *token = file_token;\r
+    }\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+#define COMPARE_CHUNK_SIZE 4096\r
+\r
+/* Implements svn_diff_fns_t::token_compare */\r
+static svn_error_t *\r
+token_compare(void *baton, void *token1, void *token2, int *compare)\r
+{\r
+  svn_diff__file_baton_t *file_baton = baton;\r
+  svn_diff__file_token_t *file_token[2];\r
+  char buffer[2][COMPARE_CHUNK_SIZE];\r
+  char *bufp[2];\r
+  apr_off_t offset[2];\r
+  int idx[2];\r
+  apr_off_t length[2];\r
+  apr_off_t total_length;\r
+  /* How much is left to read of each token from the file. */\r
+  apr_off_t raw_length[2];\r
+  int i;\r
+  int chunk[2];\r
+  svn_diff__normalize_state_t state[2];\r
+\r
+  file_token[0] = token1;\r
+  file_token[1] = token2;\r
+  if (file_token[0]->length < file_token[1]->length)\r
+    {\r
+      *compare = -1;\r
+      return SVN_NO_ERROR;\r
+    }\r
+\r
+  if (file_token[0]->length > file_token[1]->length)\r
+    {\r
+      *compare = 1;\r
+      return SVN_NO_ERROR;\r
+    }\r
+\r
+  total_length = file_token[0]->length;\r
+  if (total_length == 0)\r
+    {\r
+      *compare = 0;\r
+      return SVN_NO_ERROR;\r
+    }\r
+\r
+  for (i = 0; i < 2; ++i)\r
+    {\r
+      idx[i] = datasource_to_index(file_token[i]->datasource);\r
+      offset[i] = file_token[i]->norm_offset;\r
+      chunk[i] = file_baton->chunk[idx[i]];\r
+      state[i] = svn_diff__normalize_state_normal;\r
+\r
+      if (offset_to_chunk(offset[i]) == chunk[i])\r
+        {\r
+          /* If the start of the token is in memory, the entire token is\r
+           * in memory.\r
+           */\r
+          bufp[i] = file_baton->buffer[idx[i]];\r
+          bufp[i] += offset_in_chunk(offset[i]);\r
+\r
+          length[i] = total_length;\r
+          raw_length[i] = 0;\r
+        }\r
+      else\r
+        {\r
+          length[i] = 0;\r
+          raw_length[i] = file_token[i]->raw_length;\r
+        }\r
+    }\r
+\r
+  do\r
+    {\r
+      apr_off_t len;\r
+      for (i = 0; i < 2; i++)\r
+        {\r
+          if (length[i] == 0)\r
+            {\r
+              /* Error if raw_length is 0, that's an unexpected change\r
+               * of the file that can happen when ingoring whitespace\r
+               * and that can lead to an infinite loop. */\r
+              if (raw_length[i] == 0)\r
+                return svn_error_createf(SVN_ERR_DIFF_DATASOURCE_MODIFIED,\r
+                                         NULL,\r
+                                         _("The file '%s' changed unexpectedly"\r
+                                           " during diff"),\r
+                                         file_baton->path[idx[i]]);\r
+\r
+              /* Read a chunk from disk into a buffer */\r
+              bufp[i] = buffer[i];\r
+              length[i] = raw_length[i] > COMPARE_CHUNK_SIZE ?\r
+                COMPARE_CHUNK_SIZE : raw_length[i];\r
+\r
+              SVN_ERR(read_chunk(file_baton->file[idx[i]],\r
+                                 file_baton->path[idx[i]],\r
+                                 bufp[i], length[i], offset[i],\r
+                                 file_baton->pool));\r
+              offset[i] += length[i];\r
+              raw_length[i] -= length[i];\r
+              /* bufp[i] gets reset to buffer[i] before reading each chunk,\r
+                 so, overwriting it isn't a problem */\r
+              svn_diff__normalize_buffer(&bufp[i], &length[i], &state[i],\r
+                                         bufp[i], file_baton->options);\r
+            }\r
+        }\r
+\r
+      len = length[0] > length[1] ? length[1] : length[0];\r
+\r
+      /* Compare two chunks (that could be entire tokens if they both reside\r
+       * in memory).\r
+       */\r
+      *compare = memcmp(bufp[0], bufp[1], len);\r
+      if (*compare != 0)\r
+        return SVN_NO_ERROR;\r
+\r
+      total_length -= len;\r
+      length[0] -= len;\r
+      length[1] -= len;\r
+      bufp[0] += len;\r
+      bufp[1] += len;\r
+    }\r
+  while(total_length > 0);\r
+\r
+  *compare = 0;\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\r
+/* Implements svn_diff_fns_t::token_discard */\r
+static void\r
+token_discard(void *baton, void *token)\r
+{\r
+  svn_diff__file_baton_t *file_baton = baton;\r
+  svn_diff__file_token_t *file_token = token;\r
+\r
+  file_token->next = file_baton->tokens;\r
+  file_baton->tokens = file_token;\r
+}\r
+\r
+\r
+/* Implements svn_diff_fns_t::token_discard_all */\r
+static void\r
+token_discard_all(void *baton)\r
+{\r
+  svn_diff__file_baton_t *file_baton = baton;\r
+\r
+  /* Discard all memory in use by the tokens, and close all open files. */\r
+  svn_pool_clear(file_baton->pool);\r
+}\r
+\r
+\r
+static const svn_diff_fns_t svn_diff__file_vtable =\r
+{\r
+  datasource_open,\r
+  datasource_close,\r
+  datasource_get_next_token,\r
+  token_compare,\r
+  token_discard,\r
+  token_discard_all\r
+};\r
+\r
+/* Id for the --ignore-eol-style option, which doesn't have a short name. */\r
+#define SVN_DIFF__OPT_IGNORE_EOL_STYLE 256\r
+\r
+/* Options supported by svn_diff_file_options_parse(). */\r
+static const apr_getopt_option_t diff_options[] =\r
+{\r
+  { "ignore-space-change", 'b', 0, NULL },\r
+  { "ignore-all-space", 'w', 0, NULL },\r
+  { "ignore-eol-style", SVN_DIFF__OPT_IGNORE_EOL_STYLE, 0, NULL },\r
+  { "show-c-function", 'p', 0, NULL },\r
+  /* ### For compatibility; we don't support the argument to -u, because\r
+   * ### we don't have optional argument support. */\r
+  { "unified", 'u', 0, NULL },\r
+  { NULL, 0, 0, NULL }\r
+};\r
+\r
+svn_diff_file_options_t *\r
+svn_diff_file_options_create(apr_pool_t *pool)\r
+{\r
+  return apr_pcalloc(pool, sizeof(svn_diff_file_options_t));\r
+}\r
+\r
+svn_error_t *\r
+svn_diff_file_options_parse(svn_diff_file_options_t *options,\r
+                            const apr_array_header_t *args,\r
+                            apr_pool_t *pool)\r
+{\r
+  apr_getopt_t *os;\r
+  /* Make room for each option (starting at index 1) plus trailing NULL. */\r
+  const char **argv = apr_palloc(pool, sizeof(char*) * (args->nelts + 2));\r
+\r
+  argv[0] = "";\r
+  memcpy((void *) (argv + 1), args->elts, sizeof(char*) * args->nelts);\r
+  argv[args->nelts + 1] = NULL;\r
+\r
+  apr_getopt_init(&os, pool, args->nelts + 1, argv);\r
+  /* No printing of error messages, please! */\r
+  os->errfn = NULL;\r
+  while (1)\r
+    {\r
+      const char *opt_arg;\r
+      int opt_id;\r
+      apr_status_t err = apr_getopt_long(os, diff_options, &opt_id, &opt_arg);\r
+\r
+      if (APR_STATUS_IS_EOF(err))\r
+        break;\r
+      if (err)\r
+        return svn_error_wrap_apr(err, _("Error parsing diff options"));\r
+\r
+      switch (opt_id)\r
+        {\r
+        case 'b':\r
+          /* -w takes precedence over -b. */\r
+          if (! options->ignore_space)\r
+            options->ignore_space = svn_diff_file_ignore_space_change;\r
+          break;\r
+        case 'w':\r
+          options->ignore_space = svn_diff_file_ignore_space_all;\r
+          break;\r
+        case SVN_DIFF__OPT_IGNORE_EOL_STYLE:\r
+          options->ignore_eol_style = TRUE;\r
+          break;\r
+        case 'p':\r
+          options->show_c_function = TRUE;\r
+          break;\r
+        default:\r
+          break;\r
+        }\r
+    }\r
+\r
+  /* Check for spurious arguments. */\r
+  if (os->ind < os->argc)\r
+    return svn_error_createf(SVN_ERR_INVALID_DIFF_OPTION, NULL,\r
+                             _("Invalid argument '%s' in diff options"),\r
+                             os->argv[os->ind]);\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+svn_error_t *\r
+svn_diff_file_diff_2(svn_diff_t **diff,\r
+                     const char *original,\r
+                     const char *modified,\r
+                     const svn_diff_file_options_t *options,\r
+                     apr_pool_t *pool)\r
+{\r
+  svn_diff__file_baton_t baton;\r
+\r
+  memset(&baton, 0, sizeof(baton));\r
+  baton.options = options;\r
+  baton.path[0] = original;\r
+  baton.path[1] = modified;\r
+  baton.pool = svn_pool_create(pool);\r
+\r
+  SVN_ERR(svn_diff_diff(diff, &baton, &svn_diff__file_vtable, pool));\r
+\r
+  svn_pool_destroy(baton.pool);\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+svn_error_t *\r
+svn_diff_file_diff(svn_diff_t **diff,\r
+                   const char *original,\r
+                   const char *modified,\r
+                   apr_pool_t *pool)\r
+{\r
+  return svn_diff_file_diff_2(diff, original, modified,\r
+                              svn_diff_file_options_create(pool), pool);\r
+}\r
+\r
+svn_error_t *\r
+svn_diff_file_diff3_2(svn_diff_t **diff,\r
+                      const char *original,\r
+                      const char *modified,\r
+                      const char *latest,\r
+                      const svn_diff_file_options_t *options,\r
+                      apr_pool_t *pool)\r
+{\r
+  svn_diff__file_baton_t baton;\r
+\r
+  memset(&baton, 0, sizeof(baton));\r
+  baton.options = options;\r
+  baton.path[0] = original;\r
+  baton.path[1] = modified;\r
+  baton.path[2] = latest;\r
+  baton.pool = svn_pool_create(pool);\r
+\r
+  SVN_ERR(svn_diff_diff3(diff, &baton, &svn_diff__file_vtable, pool));\r
+\r
+  svn_pool_destroy(baton.pool);\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+svn_error_t *\r
+svn_diff_file_diff3(svn_diff_t **diff,\r
+                    const char *original,\r
+                    const char *modified,\r
+                    const char *latest,\r
+                    apr_pool_t *pool)\r
+{\r
+  return svn_diff_file_diff3_2(diff, original, modified, latest,\r
+                               svn_diff_file_options_create(pool), pool);\r
+}\r
+\r
+svn_error_t *\r
+svn_diff_file_diff4_2(svn_diff_t **diff,\r
+                      const char *original,\r
+                      const char *modified,\r
+                      const char *latest,\r
+                      const char *ancestor,\r
+                      const svn_diff_file_options_t *options,\r
+                      apr_pool_t *pool)\r
+{\r
+  svn_diff__file_baton_t baton;\r
+\r
+  memset(&baton, 0, sizeof(baton));\r
+  baton.options = options;\r
+  baton.path[0] = original;\r
+  baton.path[1] = modified;\r
+  baton.path[2] = latest;\r
+  baton.path[3] = ancestor;\r
+  baton.pool = svn_pool_create(pool);\r
+\r
+  SVN_ERR(svn_diff_diff4(diff, &baton, &svn_diff__file_vtable, pool));\r
+\r
+  svn_pool_destroy(baton.pool);\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+svn_error_t *\r
+svn_diff_file_diff4(svn_diff_t **diff,\r
+                    const char *original,\r
+                    const char *modified,\r
+                    const char *latest,\r
+                    const char *ancestor,\r
+                    apr_pool_t *pool)\r
+{\r
+  return svn_diff_file_diff4_2(diff, original, modified, latest, ancestor,\r
+                               svn_diff_file_options_create(pool), pool);\r
+}\r
+\f\r
+/** Display unified context diffs **/\r
+\r
+/* Maximum length of the extra context to show when show_c_function is set.\r
+ * GNU diff uses 40, let's be brave and use 50 instead. */\r
+#define SVN_DIFF__EXTRA_CONTEXT_LENGTH 50\r
+typedef struct svn_diff__file_output_baton_t\r
+{\r
+  svn_stream_t *output_stream;\r
+  const char *header_encoding;\r
+\r
+  /* Cached markers, in header_encoding. */\r
+  const char *context_str;\r
+  const char *delete_str;\r
+  const char *insert_str;\r
+\r
+  const char *path[2];\r
+  apr_file_t *file[2];\r
+\r
+  apr_off_t   current_line[2];\r
+\r
+  char        buffer[2][4096];\r
+  apr_size_t  length[2];\r
+  char       *curp[2];\r
+\r
+  apr_off_t   hunk_start[2];\r
+  apr_off_t   hunk_length[2];\r
+  svn_stringbuf_t *hunk;\r
+\r
+  /* Should we emit C functions in the unified diff header */\r
+  svn_boolean_t show_c_function;\r
+  /* Extra strings to skip over if we match. */\r
+  apr_array_header_t *extra_skip_match;\r
+  /* "Context" to append to the @@ line when the show_c_function option\r
+   * is set. */\r
+  svn_stringbuf_t *extra_context;\r
+  /* Extra context for the current hunk. */\r
+  char hunk_extra_context[SVN_DIFF__EXTRA_CONTEXT_LENGTH + 1];\r
+\r
+  apr_pool_t *pool;\r
+} svn_diff__file_output_baton_t;\r
+\r
+typedef enum svn_diff__file_output_unified_type_e\r
+{\r
+  svn_diff__file_output_unified_skip,\r
+  svn_diff__file_output_unified_context,\r
+  svn_diff__file_output_unified_delete,\r
+  svn_diff__file_output_unified_insert\r
+} svn_diff__file_output_unified_type_e;\r
+\r
+\r
+static svn_error_t *\r
+output_unified_line(svn_diff__file_output_baton_t *baton,\r
+                    svn_diff__file_output_unified_type_e type, int idx)\r
+{\r
+  char *curp;\r
+  char *eol;\r
+  apr_size_t length;\r
+  svn_error_t *err;\r
+  svn_boolean_t bytes_processed = FALSE;\r
+  svn_boolean_t had_cr = FALSE;\r
+  /* Are we collecting extra context? */\r
+  svn_boolean_t collect_extra = FALSE;\r
+\r
+  length = baton->length[idx];\r
+  curp = baton->curp[idx];\r
+\r
+  /* Lazily update the current line even if we're at EOF.\r
+   * This way we fake output of context at EOF\r
+   */\r
+  baton->current_line[idx]++;\r
+\r
+  if (length == 0 && apr_file_eof(baton->file[idx]))\r
+    {\r
+      return SVN_NO_ERROR;\r
+    }\r
+\r
+  do\r
+    {\r
+      if (length > 0)\r
+        {\r
+          if (!bytes_processed)\r
+            {\r
+              switch (type)\r
+                {\r
+                case svn_diff__file_output_unified_context:\r
+                  svn_stringbuf_appendcstr(baton->hunk, baton->context_str);\r
+                  baton->hunk_length[0]++;\r
+                  baton->hunk_length[1]++;\r
+                  break;\r
+                case svn_diff__file_output_unified_delete:\r
+                  svn_stringbuf_appendcstr(baton->hunk, baton->delete_str);\r
+                  baton->hunk_length[0]++;\r
+                  break;\r
+                case svn_diff__file_output_unified_insert:\r
+                  svn_stringbuf_appendcstr(baton->hunk, baton->insert_str);\r
+                  baton->hunk_length[1]++;\r
+                  break;\r
+                default:\r
+                  break;\r
+                }\r
+\r
+              if (baton->show_c_function\r
+                  && (type == svn_diff__file_output_unified_skip\r
+                      || type == svn_diff__file_output_unified_context)\r
+                  && (svn_ctype_isalpha(*curp) || *curp == '$' || *curp == '_')\r
+                  && !svn_cstring_match_glob_list(curp,\r
+                                                  baton->extra_skip_match))\r
+                {\r
+                  svn_stringbuf_setempty(baton->extra_context);\r
+                  collect_extra = TRUE;\r
+                }\r
+            }\r
+\r
+          eol = find_eol_start(curp, length);\r
+\r
+          if (eol != NULL)\r
+            {\r
+              apr_size_t len;\r
+\r
+              had_cr = (*eol == '\r');\r
+              eol++;\r
+              len = (apr_size_t)(eol - curp);\r
+\r
+              if (! had_cr || len < length)\r
+                {\r
+                  if (had_cr && *eol == '\n')\r
+                    {\r
+                      ++eol;\r
+                      ++len;\r
+                    }\r
+\r
+                  length -= len;\r
+\r
+                  if (type != svn_diff__file_output_unified_skip)\r
+                    {\r
+                      svn_stringbuf_appendbytes(baton->hunk, curp, len);\r
+                    }\r
+                  if (collect_extra)\r
+                    {\r
+                      svn_stringbuf_appendbytes(baton->extra_context,\r
+                                                curp, len);\r
+                    }\r
+\r
+                  baton->curp[idx] = eol;\r
+                  baton->length[idx] = length;\r
+\r
+                  err = SVN_NO_ERROR;\r
+\r
+                  break;\r
+                }\r
+            }\r
+\r
+          if (type != svn_diff__file_output_unified_skip)\r
+            {\r
+              svn_stringbuf_appendbytes(baton->hunk, curp, length);\r
+            }\r
+\r
+          if (collect_extra)\r
+            {\r
+              svn_stringbuf_appendbytes(baton->extra_context, curp, length);\r
+            }\r
+\r
+          bytes_processed = TRUE;\r
+        }\r
+\r
+      curp = baton->buffer[idx];\r
+      length = sizeof(baton->buffer[idx]);\r
+\r
+      err = svn_io_file_read(baton->file[idx], curp, &length, baton->pool);\r
+\r
+      /* If the last chunk ended with a CR, we look for an LF at the start\r
+         of this chunk. */\r
+      if (had_cr)\r
+        {\r
+          if (! err && length > 0 && *curp == '\n')\r
+            {\r
+              if (type != svn_diff__file_output_unified_skip)\r
+                {\r
+                  svn_stringbuf_appendbytes(baton->hunk, curp, 1);\r
+                }\r
+              /* We don't append the LF to extra_context, since it would\r
+               * just be stripped anyway. */\r
+              ++curp;\r
+              --length;\r
+            }\r
+\r
+          baton->curp[idx] = curp;\r
+          baton->length[idx] = length;\r
+\r
+          break;\r
+        }\r
+    }\r
+  while (! err);\r
+\r
+  if (err && ! APR_STATUS_IS_EOF(err->apr_err))\r
+    return err;\r
+\r
+  if (err && APR_STATUS_IS_EOF(err->apr_err))\r
+    {\r
+      svn_error_clear(err);\r
+      /* Special case if we reach the end of file AND the last line is in the\r
+         changed range AND the file doesn't end with a newline */\r
+      if (bytes_processed && (type != svn_diff__file_output_unified_skip)\r
+          && ! had_cr)\r
+        {\r
+          const char *out_str;\r
+          SVN_ERR(svn_utf_cstring_from_utf8_ex2\r
+                  (&out_str,\r
+                   /* The string below is intentionally not marked for\r
+                      translation: it's vital to correct operation of\r
+                      the diff(1)/patch(1) program pair. */\r
+                   APR_EOL_STR "\\ No newline at end of file" APR_EOL_STR,\r
+                   baton->header_encoding, baton->pool));\r
+          svn_stringbuf_appendcstr(baton->hunk, out_str);\r
+        }\r
+\r
+      baton->length[idx] = 0;\r
+    }\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+static svn_error_t *\r
+output_unified_flush_hunk(svn_diff__file_output_baton_t *baton)\r
+{\r
+  apr_off_t target_line;\r
+  apr_size_t hunk_len;\r
+  int i;\r
+\r
+  if (svn_stringbuf_isempty(baton->hunk))\r
+    {\r
+      /* Nothing to flush */\r
+      return SVN_NO_ERROR;\r
+    }\r
+\r
+  target_line = baton->hunk_start[0] + baton->hunk_length[0]\r
+                + SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
+\r
+  /* Add trailing context to the hunk */\r
+  while (baton->current_line[0] < target_line)\r
+    {\r
+      SVN_ERR(output_unified_line\r
+              (baton, svn_diff__file_output_unified_context, 0));\r
+    }\r
+\r
+  /* If the file is non-empty, convert the line indexes from\r
+     zero based to one based */\r
+  for (i = 0; i < 2; i++)\r
+    {\r
+      if (baton->hunk_length[i] > 0)\r
+        baton->hunk_start[i]++;\r
+    }\r
+\r
+  /* Output the hunk header.  If the hunk length is 1, the file is a one line\r
+     file.  In this case, surpress the number of lines in the hunk (it is\r
+     1 implicitly)\r
+   */\r
+  SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,\r
+                                      baton->header_encoding,\r
+                                      baton->pool,\r
+                                      "@@ -%" APR_OFF_T_FMT,\r
+                                      baton->hunk_start[0]));\r
+  if (baton->hunk_length[0] != 1)\r
+    {\r
+      SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,\r
+                                          baton->header_encoding,\r
+                                          baton->pool, ",%" APR_OFF_T_FMT,\r
+                                          baton->hunk_length[0]));\r
+    }\r
+\r
+  SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,\r
+                                      baton->header_encoding,\r
+                                      baton->pool, " +%" APR_OFF_T_FMT,\r
+                                      baton->hunk_start[1]));\r
+  if (baton->hunk_length[1] != 1)\r
+    {\r
+      SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,\r
+                                          baton->header_encoding,\r
+                                          baton->pool, ",%" APR_OFF_T_FMT,\r
+                                          baton->hunk_length[1]));\r
+    }\r
+\r
+  SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,\r
+                                      baton->header_encoding,\r
+                                      baton->pool, " @@%s%s" APR_EOL_STR,\r
+                                      baton->hunk_extra_context[0]\r
+                                      ? " " : "",\r
+                                      baton->hunk_extra_context));\r
+\r
+  /* Output the hunk content */\r
+  hunk_len = baton->hunk->len;\r
+  SVN_ERR(svn_stream_write(baton->output_stream, baton->hunk->data,\r
+                           &hunk_len));\r
+\r
+  /* Prepare for the next hunk */\r
+  baton->hunk_length[0] = 0;\r
+  baton->hunk_length[1] = 0;\r
+  svn_stringbuf_setempty(baton->hunk);\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+static svn_error_t *\r
+output_unified_diff_modified(void *baton,\r
+  apr_off_t original_start, apr_off_t original_length,\r
+  apr_off_t modified_start, apr_off_t modified_length,\r
+  apr_off_t latest_start, apr_off_t latest_length)\r
+{\r
+  svn_diff__file_output_baton_t *output_baton = baton;\r
+  apr_off_t target_line[2];\r
+  int i;\r
+\r
+  target_line[0] = original_start >= SVN_DIFF__UNIFIED_CONTEXT_SIZE\r
+                   ? original_start - SVN_DIFF__UNIFIED_CONTEXT_SIZE : 0;\r
+  target_line[1] = modified_start;\r
+\r
+  /* If the changed ranges are far enough apart (no overlapping or connecting\r
+     context), flush the current hunk, initialize the next hunk and skip the\r
+     lines not in context.  Also do this when this is the first hunk.\r
+   */\r
+  if (output_baton->current_line[0] < target_line[0]\r
+      && (output_baton->hunk_start[0] + output_baton->hunk_length[0]\r
+          + SVN_DIFF__UNIFIED_CONTEXT_SIZE < target_line[0]\r
+          || output_baton->hunk_length[0] == 0))\r
+    {\r
+      SVN_ERR(output_unified_flush_hunk(output_baton));\r
+\r
+      output_baton->hunk_start[0] = target_line[0];\r
+      output_baton->hunk_start[1] = target_line[1] + target_line[0]\r
+                                    - original_start;\r
+\r
+      /* Skip lines until we are at the beginning of the context we want to\r
+         display */\r
+      while (output_baton->current_line[0] < target_line[0])\r
+        {\r
+          SVN_ERR(output_unified_line(output_baton,\r
+                                      svn_diff__file_output_unified_skip, 0));\r
+        }\r
+\r
+      if (output_baton->show_c_function)\r
+        {\r
+          int p;\r
+\r
+          /* Save the extra context for later use.\r
+           * Note that the last byte of the hunk_extra_context array is never\r
+           * touched after it is zero-initialized, so the array is always\r
+           * 0-terminated. */\r
+          strncpy(output_baton->hunk_extra_context,\r
+                  output_baton->extra_context->data,\r
+                  SVN_DIFF__EXTRA_CONTEXT_LENGTH);\r
+          /* Trim whitespace at the end, most notably to get rid of any\r
+           * newline characters. */\r
+          p = strlen(output_baton->hunk_extra_context);\r
+          while (p > 0\r
+                 && svn_ctype_isspace(output_baton->hunk_extra_context[p - 1]))\r
+            {\r
+              output_baton->hunk_extra_context[--p] = '\0';\r
+            }\r
+        }\r
+    }\r
+\r
+  /* Skip lines until we are at the start of the changed range */\r
+  while (output_baton->current_line[1] < target_line[1])\r
+    {\r
+      SVN_ERR(output_unified_line(output_baton,\r
+                                  svn_diff__file_output_unified_skip, 1));\r
+    }\r
+\r
+  /* Output the context preceding the changed range */\r
+  while (output_baton->current_line[0] < original_start)\r
+    {\r
+      SVN_ERR(output_unified_line(output_baton,\r
+                                  svn_diff__file_output_unified_context, 0));\r
+    }\r
+\r
+  target_line[0] = original_start + original_length;\r
+  target_line[1] = modified_start + modified_length;\r
+\r
+  /* Output the changed range */\r
+  for (i = 0; i < 2; i++)\r
+    {\r
+      while (output_baton->current_line[i] < target_line[i])\r
+        {\r
+          SVN_ERR(output_unified_line\r
+                          (output_baton,\r
+                           i == 0 ? svn_diff__file_output_unified_delete\r
+                                  : svn_diff__file_output_unified_insert, i));\r
+        }\r
+    }\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+/* Set *HEADER to a new string consisting of PATH, a tab, and PATH's mtime. */\r
+static svn_error_t *\r
+output_unified_default_hdr(const char **header, const char *path,\r
+                           apr_pool_t *pool)\r
+{\r
+  apr_finfo_t file_info;\r
+  apr_time_exp_t exploded_time;\r
+  char time_buffer[64];\r
+  apr_size_t time_len;\r
+\r
+  SVN_ERR(svn_io_stat(&file_info, path, APR_FINFO_MTIME, pool));\r
+  apr_time_exp_lt(&exploded_time, file_info.mtime);\r
+\r
+  apr_strftime(time_buffer, &time_len, sizeof(time_buffer) - 1,\r
+               "%a %b %e %H:%M:%S %Y", &exploded_time);\r
+\r
+  *header = apr_psprintf(pool, "%s\t%s", path, time_buffer);\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+static const svn_diff_output_fns_t svn_diff__file_output_unified_vtable =\r
+{\r
+  NULL, /* output_common */\r
+  output_unified_diff_modified,\r
+  NULL, /* output_diff_latest */\r
+  NULL, /* output_diff_common */\r
+  NULL  /* output_conflict */\r
+};\r
+\r
+svn_error_t *\r
+svn_diff_file_output_unified3(svn_stream_t *output_stream,\r
+                              svn_diff_t *diff,\r
+                              const char *original_path,\r
+                              const char *modified_path,\r
+                              const char *original_header,\r
+                              const char *modified_header,\r
+                              const char *header_encoding,\r
+                              const char *relative_to_dir,\r
+                              svn_boolean_t show_c_function,\r
+                              apr_pool_t *pool)\r
+{\r
+  svn_diff__file_output_baton_t baton;\r
+  int i;\r
+\r
+  if (svn_diff_contains_diffs(diff))\r
+    {\r
+      const char **c;\r
+\r
+      memset(&baton, 0, sizeof(baton));\r
+      baton.output_stream = output_stream;\r
+      baton.pool = pool;\r
+      baton.header_encoding = header_encoding;\r
+      baton.path[0] = original_path;\r
+      baton.path[1] = modified_path;\r
+      baton.hunk = svn_stringbuf_create("", pool);\r
+      baton.show_c_function = show_c_function;\r
+      baton.extra_context = svn_stringbuf_create("", pool);\r
+      baton.extra_skip_match = apr_array_make(pool, 3, sizeof(char **));\r
+\r
+      c = apr_array_push(baton.extra_skip_match);\r
+      *c = "public:*";\r
+      c = apr_array_push(baton.extra_skip_match);\r
+      *c = "private:*";\r
+      c = apr_array_push(baton.extra_skip_match);\r
+      *c = "protected:*";\r
+\r
+      SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton.context_str, " ",\r
+                                            header_encoding, pool));\r
+      SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton.delete_str, "-",\r
+                                            header_encoding, pool));\r
+      SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton.insert_str, "+",\r
+                                            header_encoding, pool));\r
+\r
+  if (relative_to_dir)\r
+    {\r
+      /* Possibly adjust the "original" and "modified" paths shown in\r
+         the output (see issue #2723). */\r
+      const char *child_path;\r
+\r
+      if (! original_header)\r
+        {\r
+          child_path = svn_path_is_child(relative_to_dir,\r
+                                         original_path, pool);\r
+          if (child_path)\r
+            original_path = child_path;\r
+          else\r
+            return svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH, NULL,\r
+                                     _("Path '%s' must be an immediate child of "\r
+                                       "the directory '%s'"),\r
+                                     original_path, relative_to_dir);\r
+        }\r
+\r
+      if (! modified_header)\r
+        {\r
+          child_path = svn_path_is_child(relative_to_dir, modified_path, pool);\r
+          if (child_path)\r
+            modified_path = child_path;\r
+          else\r
+            return svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH, NULL,\r
+                                     _("Path '%s' must be an immediate child of "\r
+                                       "the directory '%s'"),\r
+                                     modified_path, relative_to_dir);\r
+        }\r
+    }\r
+\r
+      for (i = 0; i < 2; i++)\r
+        {\r
+          SVN_ERR(svn_io_file_open(&baton.file[i], baton.path[i],\r
+                                   APR_READ, APR_OS_DEFAULT, pool));\r
+        }\r
+\r
+      if (original_header == NULL)\r
+        {\r
+          SVN_ERR(output_unified_default_hdr\r
+                  (&original_header, original_path, pool));\r
+        }\r
+\r
+      if (modified_header == NULL)\r
+        {\r
+          SVN_ERR(output_unified_default_hdr\r
+                  (&modified_header, modified_path, pool));\r
+        }\r
+\r
+      SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding, pool,\r
+                                          "--- %s" APR_EOL_STR\r
+                                          "+++ %s" APR_EOL_STR,\r
+                                          original_header, modified_header));\r
+\r
+      SVN_ERR(svn_diff_output(diff, &baton,\r
+                              &svn_diff__file_output_unified_vtable));\r
+      SVN_ERR(output_unified_flush_hunk(&baton));\r
+\r
+      for (i = 0; i < 2; i++)\r
+        {\r
+          SVN_ERR(svn_io_file_close(baton.file[i], pool));\r
+        }\r
+    }\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\f\r
+/** Display diff3 **/\r
+\r
+/* A stream to remember *leading* context.  Note that this stream does\r
+   *not* copy the data that it is remembering; it just saves\r
+   *pointers! */\r
+typedef struct {\r
+  svn_stream_t *stream;\r
+  const char *data[SVN_DIFF__UNIFIED_CONTEXT_SIZE];\r
+  apr_size_t len[SVN_DIFF__UNIFIED_CONTEXT_SIZE];\r
+  apr_size_t next_slot;\r
+  apr_size_t total_written;\r
+} context_saver_t;\r
+\r
+\r
+static svn_error_t *\r
+context_saver_stream_write(void *baton,\r
+                           const char *data,\r
+                           apr_size_t *len)\r
+{\r
+  context_saver_t *cs = baton;\r
+  cs->data[cs->next_slot] = data;\r
+  cs->len[cs->next_slot] = *len;\r
+  cs->next_slot = (cs->next_slot + 1) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
+  cs->total_written++;\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+typedef struct svn_diff3__file_output_baton_t\r
+{\r
+  svn_stream_t *output_stream;\r
+\r
+  const char *path[3];\r
+\r
+  apr_off_t   current_line[3];\r
+\r
+  char       *buffer[3];\r
+  char       *endp[3];\r
+  char       *curp[3];\r
+\r
+  /* The following four members are in the encoding used for the output. */\r
+  const char *conflict_modified;\r
+  const char *conflict_original;\r
+  const char *conflict_separator;\r
+  const char *conflict_latest;\r
+\r
+  const char *marker_eol;\r
+\r
+  svn_diff_conflict_display_style_t conflict_style;\r
+\r
+  /* The rest of the fields are for\r
+     svn_diff_conflict_display_only_conflicts only.  Note that for\r
+     these batons, OUTPUT_STREAM is either CONTEXT_SAVER->STREAM or\r
+     (soon after a conflict) a "trailing context stream", never the\r
+     actual output stream.*/\r
+  /* The actual output stream. */\r
+  svn_stream_t *real_output_stream;\r
+  context_saver_t *context_saver;\r
+  /* Used to allocate context_saver and trailing context streams, and\r
+     for some printfs. */\r
+  apr_pool_t *pool;\r
+} svn_diff3__file_output_baton_t;\r
+\r
+static svn_error_t *\r
+flush_context_saver(context_saver_t *cs,\r
+                    svn_stream_t *output_stream)\r
+{\r
+  int i;\r
+  for (i = 0; i < SVN_DIFF__UNIFIED_CONTEXT_SIZE; i++)\r
+    {\r
+      int slot = (i + cs->next_slot) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
+      if (cs->data[slot])\r
+        {\r
+          apr_size_t len = cs->len[slot];\r
+          SVN_ERR(svn_stream_write(output_stream, cs->data[slot], &len));\r
+        }\r
+    }\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+static void\r
+make_context_saver(svn_diff3__file_output_baton_t *fob)\r
+{\r
+  context_saver_t *cs;\r
+\r
+  svn_pool_clear(fob->pool);\r
+  cs = apr_pcalloc(fob->pool, sizeof(*cs));\r
+  cs->stream = svn_stream_empty(fob->pool);\r
+  svn_stream_set_baton(cs->stream, cs);\r
+  svn_stream_set_write(cs->stream, context_saver_stream_write);\r
+  fob->context_saver = cs;\r
+  fob->output_stream = cs->stream;\r
+}\r
+\r
+\r
+/* A stream which prints SVN_DIFF__UNIFIED_CONTEXT_SIZE lines to\r
+   BATON->REAL_OUTPUT_STREAM, and then changes BATON->OUTPUT_STREAM to\r
+   a context_saver; used for *trailing* context. */\r
+\r
+struct trailing_context_printer {\r
+  apr_size_t lines_to_print;\r
+  svn_diff3__file_output_baton_t *fob;\r
+};\r
+\r
+\r
+\r
+static svn_error_t *\r
+trailing_context_printer_write(void *baton,\r
+                               const char *data,\r
+                               apr_size_t *len)\r
+{\r
+  struct trailing_context_printer *tcp = baton;\r
+  SVN_ERR_ASSERT(tcp->lines_to_print > 0);\r
+  SVN_ERR(svn_stream_write(tcp->fob->real_output_stream, data, len));\r
+  tcp->lines_to_print--;\r
+  if (tcp->lines_to_print == 0)\r
+    make_context_saver(tcp->fob);\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\r
+static void\r
+make_trailing_context_printer(svn_diff3__file_output_baton_t *btn)\r
+{\r
+  struct trailing_context_printer *tcp;\r
+  svn_stream_t *s;\r
+\r
+  svn_pool_clear(btn->pool);\r
+\r
+  tcp = apr_pcalloc(btn->pool, sizeof(*tcp));\r
+  tcp->lines_to_print = SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
+  tcp->fob = btn;\r
+  s = svn_stream_empty(btn->pool);\r
+  svn_stream_set_baton(s, tcp);\r
+  svn_stream_set_write(s, trailing_context_printer_write);\r
+  btn->output_stream = s;\r
+}\r
+\r
+\r
+\r
+typedef enum svn_diff3__file_output_type_e\r
+{\r
+  svn_diff3__file_output_skip,\r
+  svn_diff3__file_output_normal\r
+} svn_diff3__file_output_type_e;\r
+\r
+\r
+static svn_error_t *\r
+output_line(svn_diff3__file_output_baton_t *baton,\r
+            svn_diff3__file_output_type_e type, int idx)\r
+{\r
+  char *curp;\r
+  char *endp;\r
+  char *eol;\r
+  apr_size_t len;\r
+\r
+  curp = baton->curp[idx];\r
+  endp = baton->endp[idx];\r
+\r
+  /* Lazily update the current line even if we're at EOF.\r
+   */\r
+  baton->current_line[idx]++;\r
+\r
+  if (curp == endp)\r
+    return SVN_NO_ERROR;\r
+\r
+  eol = find_eol_start(curp, endp - curp);\r
+  if (!eol)\r
+    eol = endp;\r
+  else\r
+    {\r
+      svn_boolean_t had_cr = (*eol == '\r');\r
+      eol++;\r
+      if (had_cr && eol != endp && *eol == '\n')\r
+        eol++;\r
+    }\r
+\r
+  if (type != svn_diff3__file_output_skip)\r
+    {\r
+      len = eol - curp;\r
+      /* Note that the trailing context printer assumes that\r
+         svn_stream_write is called exactly once per line. */\r
+      SVN_ERR(svn_stream_write(baton->output_stream, curp, &len));\r
+    }\r
+\r
+  baton->curp[idx] = eol;\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+static svn_error_t *\r
+output_marker_eol(svn_diff3__file_output_baton_t *btn)\r
+{\r
+  apr_size_t len = strlen(btn->marker_eol);\r
+  return svn_stream_write(btn->output_stream, btn->marker_eol, &len);\r
+}\r
+\r
+static svn_error_t *\r
+output_hunk(void *baton, int idx, apr_off_t target_line,\r
+            apr_off_t target_length)\r
+{\r
+  svn_diff3__file_output_baton_t *output_baton = baton;\r
+\r
+  /* Skip lines until we are at the start of the changed range */\r
+  while (output_baton->current_line[idx] < target_line)\r
+    {\r
+      SVN_ERR(output_line(output_baton, svn_diff3__file_output_skip, idx));\r
+    }\r
+\r
+  target_line += target_length;\r
+\r
+  while (output_baton->current_line[idx] < target_line)\r
+    {\r
+      SVN_ERR(output_line(output_baton, svn_diff3__file_output_normal, idx));\r
+    }\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+static svn_error_t *\r
+output_common(void *baton, apr_off_t original_start, apr_off_t original_length,\r
+              apr_off_t modified_start, apr_off_t modified_length,\r
+              apr_off_t latest_start, apr_off_t latest_length)\r
+{\r
+  return output_hunk(baton, 1, modified_start, modified_length);\r
+}\r
+\r
+static svn_error_t *\r
+output_diff_modified(void *baton,\r
+                     apr_off_t original_start, apr_off_t original_length,\r
+                     apr_off_t modified_start, apr_off_t modified_length,\r
+                     apr_off_t latest_start, apr_off_t latest_length)\r
+{\r
+  return output_hunk(baton, 1, modified_start, modified_length);\r
+}\r
+\r
+static svn_error_t *\r
+output_diff_latest(void *baton,\r
+                   apr_off_t original_start, apr_off_t original_length,\r
+                   apr_off_t modified_start, apr_off_t modified_length,\r
+                   apr_off_t latest_start, apr_off_t latest_length)\r
+{\r
+  return output_hunk(baton, 2, latest_start, latest_length);\r
+}\r
+\r
+static svn_error_t *\r
+output_conflict(void *baton,\r
+                apr_off_t original_start, apr_off_t original_length,\r
+                apr_off_t modified_start, apr_off_t modified_length,\r
+                apr_off_t latest_start, apr_off_t latest_length,\r
+                svn_diff_t *diff);\r
+\r
+static const svn_diff_output_fns_t svn_diff3__file_output_vtable =\r
+{\r
+  output_common,\r
+  output_diff_modified,\r
+  output_diff_latest,\r
+  output_diff_modified, /* output_diff_common */\r
+  output_conflict\r
+};\r
+\r
+\r
+\r
+static svn_error_t *\r
+output_conflict_with_context(svn_diff3__file_output_baton_t *btn,\r
+                             apr_off_t original_start,\r
+                             apr_off_t original_length,\r
+                             apr_off_t modified_start,\r
+                             apr_off_t modified_length,\r
+                             apr_off_t latest_start,\r
+                             apr_off_t latest_length)\r
+{\r
+  /* Are we currently saving starting context (as opposed to printing\r
+     trailing context)?  If so, flush it. */\r
+  if (btn->output_stream == btn->context_saver->stream)\r
+    {\r
+      if (btn->context_saver->total_written > SVN_DIFF__UNIFIED_CONTEXT_SIZE)\r
+        SVN_ERR(svn_stream_printf(btn->real_output_stream, btn->pool, "@@\n"));\r
+      SVN_ERR(flush_context_saver(btn->context_saver, btn->real_output_stream));\r
+    }\r
+\r
+  /* Print to the real output stream. */\r
+  btn->output_stream = btn->real_output_stream;\r
+\r
+  /* Output the conflict itself. */\r
+  SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,\r
+                            (modified_length == 1\r
+                             ? "%s (%" APR_OFF_T_FMT ")"\r
+                             : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),\r
+                            btn->conflict_modified,\r
+                            modified_start + 1, modified_length));\r
+  SVN_ERR(output_marker_eol(btn));\r
+  SVN_ERR(output_hunk(btn, 1/*modified*/, modified_start, modified_length));\r
+\r
+  SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,\r
+                            (original_length == 1\r
+                             ? "%s (%" APR_OFF_T_FMT ")"\r
+                             : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),\r
+                            btn->conflict_original,\r
+                            original_start + 1, original_length));\r
+  SVN_ERR(output_marker_eol(btn));\r
+  SVN_ERR(output_hunk(btn, 0/*original*/, original_start, original_length));\r
+\r
+  SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,\r
+                            "%s%s", btn->conflict_separator, btn->marker_eol));\r
+  SVN_ERR(output_hunk(btn, 2/*latest*/, latest_start, latest_length));\r
+  SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,\r
+                            (latest_length == 1\r
+                             ? "%s (%" APR_OFF_T_FMT ")"\r
+                             : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),\r
+                            btn->conflict_latest,\r
+                            latest_start + 1, latest_length));\r
+  SVN_ERR(output_marker_eol(btn));\r
+\r
+  /* Go into print-trailing-context mode instead. */\r
+  make_trailing_context_printer(btn);\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\r
+static svn_error_t *\r
+output_conflict(void *baton,\r
+                apr_off_t original_start, apr_off_t original_length,\r
+                apr_off_t modified_start, apr_off_t modified_length,\r
+                apr_off_t latest_start, apr_off_t latest_length,\r
+                svn_diff_t *diff)\r
+{\r
+  svn_diff3__file_output_baton_t *file_baton = baton;\r
+  apr_size_t len;\r
+\r
+  svn_diff_conflict_display_style_t style = file_baton->conflict_style;\r
+\r
+  if (style == svn_diff_conflict_display_only_conflicts)\r
+    return output_conflict_with_context(file_baton,\r
+                                        original_start, original_length,\r
+                                        modified_start, modified_length,\r
+                                        latest_start, latest_length);\r
+\r
+  if (style == svn_diff_conflict_display_resolved_modified_latest)\r
+    {\r
+      if (diff)\r
+        return svn_diff_output(diff, baton,\r
+                               &svn_diff3__file_output_vtable);\r
+      else\r
+        style = svn_diff_conflict_display_modified_latest;\r
+    }\r
+\r
+  if (style == svn_diff_conflict_display_modified_latest ||\r
+      style == svn_diff_conflict_display_modified_original_latest)\r
+    {\r
+      len = strlen(file_baton->conflict_modified);\r
+      SVN_ERR(svn_stream_write(file_baton->output_stream,\r
+                               file_baton->conflict_modified,\r
+                               &len));\r
+      SVN_ERR(output_marker_eol(file_baton));\r
+\r
+      SVN_ERR(output_hunk(baton, 1, modified_start, modified_length));\r
+\r
+      if (style == svn_diff_conflict_display_modified_original_latest)\r
+        {\r
+          len = strlen(file_baton->conflict_original);\r
+          SVN_ERR(svn_stream_write(file_baton->output_stream,\r
+                                   file_baton->conflict_original, &len));\r
+          SVN_ERR(output_marker_eol(file_baton));\r
+          SVN_ERR(output_hunk(baton, 0, original_start, original_length));\r
+        }\r
+\r
+      len = strlen(file_baton->conflict_separator);\r
+      SVN_ERR(svn_stream_write(file_baton->output_stream,\r
+                               file_baton->conflict_separator, &len));\r
+      SVN_ERR(output_marker_eol(file_baton));\r
+\r
+      SVN_ERR(output_hunk(baton, 2, latest_start, latest_length));\r
+\r
+      len = strlen(file_baton->conflict_latest);\r
+      SVN_ERR(svn_stream_write(file_baton->output_stream,\r
+                               file_baton->conflict_latest, &len));\r
+      SVN_ERR(output_marker_eol(file_baton));\r
+    }\r
+  else if (style == svn_diff_conflict_display_modified)\r
+    SVN_ERR(output_hunk(baton, 1, modified_start, modified_length));\r
+  else if (style == svn_diff_conflict_display_latest)\r
+    SVN_ERR(output_hunk(baton, 2, latest_start, latest_length));\r
+  else /* unknown style */\r
+    SVN_ERR_MALFUNCTION();\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\r
+/* Return the first eol marker found in [BUF, ENDP) as a\r
+ * NUL-terminated string, or NULL if no eol marker is found.\r
+ *\r
+ * If the last valid character of BUF is the first byte of a\r
+ * potentially two-byte eol sequence, just return "\r", that is,\r
+ * assume BUF represents a CR-only file.  This is correct for callers\r
+ * that pass an entire file at once, and is no more likely to be\r
+ * incorrect than correct for any caller that doesn't.\r
+ */\r
+static const char *\r
+detect_eol(char *buf, char *endp)\r
+{\r
+  const char *eol = find_eol_start(buf, endp - buf);\r
+  if (eol)\r
+    {\r
+      if (*eol == '\n')\r
+        return "\n";\r
+\r
+      /* We found a CR. */\r
+      ++eol;\r
+      if (eol == endp || *eol != '\n')\r
+        return "\r";\r
+      return "\r\n";\r
+    }\r
+\r
+  return NULL;\r
+}\r
+\r
+svn_error_t *\r
+svn_diff_file_output_merge2(svn_stream_t *output_stream,\r
+                            svn_diff_t *diff,\r
+                            const char *original_path,\r
+                            const char *modified_path,\r
+                            const char *latest_path,\r
+                            const char *conflict_original,\r
+                            const char *conflict_modified,\r
+                            const char *conflict_latest,\r
+                            const char *conflict_separator,\r
+                            svn_diff_conflict_display_style_t style,\r
+                            apr_pool_t *pool)\r
+{\r
+  svn_diff3__file_output_baton_t baton;\r
+  apr_file_t *file[3];\r
+  apr_off_t size;\r
+  int idx;\r
+#if APR_HAS_MMAP\r
+  apr_mmap_t *mm[3] = { 0 };\r
+#endif /* APR_HAS_MMAP */\r
+  const char *eol;\r
+  svn_boolean_t conflicts_only =\r
+    (style == svn_diff_conflict_display_only_conflicts);\r
+\r
+  memset(&baton, 0, sizeof(baton));\r
+  if (conflicts_only)\r
+    {\r
+      baton.pool = svn_pool_create(pool);\r
+      make_context_saver(&baton);\r
+      baton.real_output_stream = output_stream;\r
+    }\r
+  else\r
+    baton.output_stream = output_stream;\r
+  baton.path[0] = original_path;\r
+  baton.path[1] = modified_path;\r
+  baton.path[2] = latest_path;\r
+  SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_modified,\r
+                                    conflict_modified ? conflict_modified\r
+                                    : apr_psprintf(pool, "<<<<<<< %s",\r
+                                                   modified_path),\r
+                                    pool));\r
+  SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_original,\r
+                                    conflict_original ? conflict_original\r
+                                    : apr_psprintf(pool, "||||||| %s",\r
+                                                   original_path),\r
+                                    pool));\r
+  SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_separator,\r
+                                    conflict_separator ? conflict_separator\r
+                                    : "=======", pool));\r
+  SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_latest,\r
+                                    conflict_latest ? conflict_latest\r
+                                    : apr_psprintf(pool, ">>>>>>> %s",\r
+                                                   latest_path),\r
+                                    pool));\r
+\r
+  baton.conflict_style = style;\r
+\r
+  for (idx = 0; idx < 3; idx++)\r
+    {\r
+      SVN_ERR(map_or_read_file(&file[idx],\r
+                               MMAP_T_ARG(mm[idx])\r
+                               &baton.buffer[idx], &size,\r
+                               baton.path[idx], pool));\r
+\r
+      baton.curp[idx] = baton.buffer[idx];\r
+      baton.endp[idx] = baton.buffer[idx];\r
+\r
+      if (baton.endp[idx])\r
+        baton.endp[idx] += size;\r
+    }\r
+\r
+  /* Check what eol marker we should use for conflict markers.\r
+     We use the eol marker of the modified file and fall back on the\r
+     platform's eol marker if that file doesn't contain any newlines. */\r
+  eol = detect_eol(baton.buffer[1], baton.endp[1]);\r
+  if (! eol)\r
+    eol = APR_EOL_STR;\r
+  baton.marker_eol = eol;\r
+\r
+  SVN_ERR(svn_diff_output(diff, &baton,\r
+                          &svn_diff3__file_output_vtable));\r
+\r
+  for (idx = 0; idx < 3; idx++)\r
+    {\r
+#if APR_HAS_MMAP\r
+      if (mm[idx])\r
+        {\r
+          apr_status_t rv = apr_mmap_delete(mm[idx]);\r
+          if (rv != APR_SUCCESS)\r
+            {\r
+              return svn_error_wrap_apr(rv, _("Failed to delete mmap '%s'"),\r
+                                        baton.path[idx]);\r
+            }\r
+        }\r
+#endif /* APR_HAS_MMAP */\r
+\r
+      if (file[idx])\r
+        {\r
+          SVN_ERR(svn_io_file_close(file[idx], pool));\r
+        }\r
+    }\r
+\r
+  if (conflicts_only)\r
+    svn_pool_destroy(baton.pool);\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\r
+svn_error_t *\r
+svn_diff_file_output_merge(svn_stream_t *output_stream,\r
+                           svn_diff_t *diff,\r
+                           const char *original_path,\r
+                           const char *modified_path,\r
+                           const char *latest_path,\r
+                           const char *conflict_original,\r
+                           const char *conflict_modified,\r
+                           const char *conflict_latest,\r
+                           const char *conflict_separator,\r
+                           svn_boolean_t display_original_in_conflict,\r
+                           svn_boolean_t display_resolved_conflicts,\r
+                           apr_pool_t *pool)\r
+{\r
+  svn_diff_conflict_display_style_t style =\r
+    svn_diff_conflict_display_modified_latest;\r
+\r
+  if (display_resolved_conflicts)\r
+    style = svn_diff_conflict_display_resolved_modified_latest;\r
+\r
+  if (display_original_in_conflict)\r
+    style = svn_diff_conflict_display_modified_original_latest;\r
+\r
+  return svn_diff_file_output_merge2(output_stream,\r
+                                     diff,\r
+                                     original_path,\r
+                                     modified_path,\r
+                                     latest_path,\r
+                                     conflict_original,\r
+                                     conflict_modified,\r
+                                     conflict_latest,\r
+                                     conflict_separator,\r
+                                     style,\r
+                                     pool);\r
+}\r
diff --git a/src/TortoiseMerge/libsvn_diff/diff_memory.c b/src/TortoiseMerge/libsvn_diff/diff_memory.c
new file mode 100644 (file)
index 0000000..be2b1cf
--- /dev/null
@@ -0,0 +1,1048 @@
+/*\r
+ * diff_memory.c :  routines for doing diffs on in-memory data\r
+ *\r
+ * ====================================================================\r
+ * Copyright (c) 2007 CollabNet.  All rights reserved.\r
+ *\r
+ * This software is licensed as described in the file COPYING, which\r
+ * you should have received as part of this distribution.  The terms\r
+ * are also available at http://subversion.tigris.org/license-1.html.\r
+ * If newer versions of this license are posted there, you may use a\r
+ * newer version instead, at your option.\r
+ *\r
+ * This software consists of voluntary contributions made by many\r
+ * individuals.  For exact contribution history, see the revision\r
+ * history and logs, available at http://subversion.tigris.org/.\r
+ * ====================================================================\r
+ */\r
+\r
+#define WANT_MEMFUNC\r
+#define WANT_STRFUNC\r
+#include <apr.h>\r
+#include <apr_want.h>\r
+#include <apr_tables.h>\r
+\r
+#include "svn_diff.h"\r
+#include "svn_pools.h"\r
+#include "svn_types.h"\r
+#include "svn_string.h"\r
+#include "svn_utf.h"\r
+#include "diff.h"\r
+#include "svn_private_config.h"\r
+\r
+typedef struct source_tokens_t\r
+{\r
+  /* A token simply is an svn_string_t pointing to\r
+     the data of the in-memory data source, containing\r
+     the raw token text, with length stored in the string */\r
+  apr_array_header_t *tokens;\r
+\r
+  /* Next token to be consumed */\r
+  apr_size_t next_token;\r
+\r
+  /* The source, containing the in-memory data to be diffed */\r
+  svn_string_t *source;\r
+\r
+  /* The last token ends with a newline character (sequence) */\r
+  svn_boolean_t ends_without_eol;\r
+} source_tokens_t;\r
+\r
+typedef struct diff_mem_baton_t\r
+{\r
+  /* The tokens for each of the sources */\r
+  source_tokens_t sources[4];\r
+\r
+  /* Normalization buffer; we only ever compare 2 tokens at the same time */\r
+  char *normalization_buf[2];\r
+\r
+  /* Options for normalized comparison of the data sources */\r
+  const svn_diff_file_options_t *normalization_options;\r
+} diff_mem_baton_t;\r
+\r
+\r
+static int\r
+datasource_to_index(svn_diff_datasource_e datasource)\r
+{\r
+  switch (datasource)\r
+    {\r
+    case svn_diff_datasource_original:\r
+      return 0;\r
+\r
+    case svn_diff_datasource_modified:\r
+      return 1;\r
+\r
+    case svn_diff_datasource_latest:\r
+      return 2;\r
+\r
+    case svn_diff_datasource_ancestor:\r
+      return 3;\r
+    }\r
+\r
+  return -1;\r
+}\r
+\r
+\r
+/* Implements svn_diff_fns_t::datasource_open */\r
+static svn_error_t *\r
+datasource_open(void *baton, svn_diff_datasource_e datasource)\r
+{\r
+  /* Do nothing: everything is already there and initialized to 0 */\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\r
+/* Implements svn_diff_fns_t::datasource_close */\r
+static svn_error_t *\r
+datasource_close(void *baton, svn_diff_datasource_e datasource)\r
+{\r
+  /* Do nothing.  The compare_token function needs previous datasources\r
+   * to stay available until all datasources are processed.\r
+   */\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\r
+/* Implements svn_diff_fns_t::datasource_get_next_token */\r
+static svn_error_t *\r
+datasource_get_next_token(apr_uint32_t *hash, void **token, void *baton,\r
+                          svn_diff_datasource_e datasource)\r
+{\r
+  diff_mem_baton_t *mem_baton = baton;\r
+  source_tokens_t *src = &(mem_baton->sources[datasource_to_index(datasource)]);\r
+\r
+  if (src->tokens->nelts > src->next_token)\r
+    {\r
+      /* There are actually tokens to be returned */\r
+      char *buf = mem_baton->normalization_buf[0];\r
+      svn_string_t *tok = (*token)\r
+        = APR_ARRAY_IDX(src->tokens, src->next_token, svn_string_t *);\r
+      apr_off_t len = tok->len;\r
+      svn_diff__normalize_state_t state\r
+        = svn_diff__normalize_state_normal;\r
+\r
+      svn_diff__normalize_buffer(&buf, &len, &state, tok->data,\r
+                                 mem_baton->normalization_options);\r
+      *hash = svn_diff__adler32(0, buf, len);\r
+      src->next_token++;\r
+    }\r
+  else\r
+    *token = NULL;\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+/* Implements svn_diff_fns_t::token_compare */\r
+static svn_error_t *\r
+token_compare(void *baton, void *token1, void *token2, int *result)\r
+{\r
+  /* Implement the same behaviour as diff_file.c:token_compare(),\r
+     but be simpler, because we know we'll have all data in memory */\r
+  diff_mem_baton_t *btn = baton;\r
+  svn_string_t *t1 = token1;\r
+  svn_string_t *t2 = token2;\r
+  char *buf1 = btn->normalization_buf[0];\r
+  char *buf2 = btn->normalization_buf[1];\r
+  apr_off_t len1 = t1->len;\r
+  apr_off_t len2 = t2->len;\r
+  svn_diff__normalize_state_t state = svn_diff__normalize_state_normal;\r
+\r
+  svn_diff__normalize_buffer(&buf1, &len1, &state, t1->data,\r
+                             btn->normalization_options);\r
+  state = svn_diff__normalize_state_normal;\r
+  svn_diff__normalize_buffer(&buf2, &len2, &state, t2->data,\r
+                             btn->normalization_options);\r
+\r
+  if (len1 != len2)\r
+    *result = (len1 < len2) ? -1 : 1;\r
+  else\r
+    *result = (len1 == 0) ? 0 : memcmp(buf1, buf2, len1);\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+/* Implements svn_diff_fns_t::token_discard */\r
+static void\r
+token_discard(void *baton, void *token)\r
+{\r
+  /* No-op, we have no use for discarded tokens... */\r
+}\r
+\r
+\r
+/* Implements svn_diff_fns_t::token_discard_all */\r
+static void\r
+token_discard_all(void *baton)\r
+{\r
+  /* Do nothing.\r
+     Note that in the file case, this function discards all\r
+     tokens allocated, but we're geared toward small in-memory diffs.\r
+     Meaning that there's no special pool to clear.\r
+  */\r
+}\r
+\r
+\r
+static const svn_diff_fns_t svn_diff__mem_vtable =\r
+{\r
+  datasource_open,\r
+  datasource_close,\r
+  datasource_get_next_token,\r
+  token_compare,\r
+  token_discard,\r
+  token_discard_all\r
+};\r
+\r
+/* Fill SRC with the diff tokens (e.g. lines).\r
+\r
+   TEXT is assumed to live long enough for the tokens to\r
+   stay valid during their lifetime: no data is copied,\r
+   instead, svn_string_t's are allocated pointing straight\r
+   into TEXT.\r
+*/\r
+static void\r
+fill_source_tokens(source_tokens_t *src,\r
+                   const svn_string_t *text,\r
+                   apr_pool_t *pool)\r
+{\r
+  const char *curp;\r
+  const char *endp;\r
+  const char *startp;\r
+\r
+  src->tokens = apr_array_make(pool, 0, sizeof(svn_string_t *));\r
+  src->next_token = 0;\r
+  src->source = (svn_string_t *)text;\r
+\r
+  for (startp = curp = text->data, endp = curp + text->len;\r
+       curp != endp; curp++)\r
+    {\r
+      if (curp != endp && *curp == '\r' && *(curp + 1) == '\n')\r
+        curp++;\r
+\r
+      if (*curp == '\r' || *curp == '\n')\r
+        {\r
+          APR_ARRAY_PUSH(src->tokens, svn_string_t *) =\r
+            svn_string_ncreate(startp, curp - startp + 1, pool);\r
+\r
+          startp = curp + 1;\r
+        }\r
+    }\r
+\r
+  /* If there's anything remaining (ie last line doesn't have a newline) */\r
+  if (startp != endp)\r
+    {\r
+      APR_ARRAY_PUSH(src->tokens, svn_string_t *) =\r
+        svn_string_ncreate(startp, endp - startp, pool);\r
+      src->ends_without_eol = TRUE;\r
+    }\r
+  else\r
+    src->ends_without_eol = FALSE;\r
+}\r
+\r
+\r
+static void\r
+alloc_normalization_bufs(diff_mem_baton_t *btn,\r
+                         int sources,\r
+                         apr_pool_t *pool)\r
+{\r
+  apr_size_t max_len = 0;\r
+  apr_off_t idx;\r
+  int i;\r
+\r
+  for (i = 0; i < sources; i++)\r
+    {\r
+      apr_array_header_t *tokens = btn->sources[i].tokens;\r
+      if (tokens->nelts > 0)\r
+        for (idx = 0; idx < tokens->nelts; idx++)\r
+          {\r
+            apr_size_t token_len\r
+              = APR_ARRAY_IDX(tokens, idx, svn_string_t *)->len;\r
+            max_len = (max_len < token_len) ? token_len : max_len;\r
+          }\r
+    }\r
+\r
+  btn->normalization_buf[0] = apr_palloc(pool, max_len);\r
+  btn->normalization_buf[1] = apr_palloc(pool, max_len);\r
+}\r
+\r
+svn_error_t *\r
+svn_diff_mem_string_diff(svn_diff_t **diff,\r
+                         const svn_string_t *original,\r
+                         const svn_string_t *modified,\r
+                         const svn_diff_file_options_t *options,\r
+                         apr_pool_t *pool)\r
+{\r
+  diff_mem_baton_t baton;\r
+\r
+  fill_source_tokens(&(baton.sources[0]), original, pool);\r
+  fill_source_tokens(&(baton.sources[1]), modified, pool);\r
+  alloc_normalization_bufs(&baton, 2, pool);\r
+\r
+  baton.normalization_options = options;\r
+\r
+  return svn_diff_diff(diff, &baton, &svn_diff__mem_vtable, pool);\r
+}\r
+\r
+svn_error_t *\r
+svn_diff_mem_string_diff3(svn_diff_t **diff,\r
+                          const svn_string_t *original,\r
+                          const svn_string_t *modified,\r
+                          const svn_string_t *latest,\r
+                          const svn_diff_file_options_t *options,\r
+                          apr_pool_t *pool)\r
+{\r
+  diff_mem_baton_t baton;\r
+\r
+  fill_source_tokens(&(baton.sources[0]), original, pool);\r
+  fill_source_tokens(&(baton.sources[1]), modified, pool);\r
+  fill_source_tokens(&(baton.sources[2]), latest, pool);\r
+  alloc_normalization_bufs(&baton, 3, pool);\r
+\r
+  baton.normalization_options = options;\r
+\r
+  return svn_diff_diff3(diff, &baton, &svn_diff__mem_vtable, pool);\r
+}\r
+\r
+\r
+svn_error_t *\r
+svn_diff_mem_string_diff4(svn_diff_t **diff,\r
+                          const svn_string_t *original,\r
+                          const svn_string_t *modified,\r
+                          const svn_string_t *latest,\r
+                          const svn_string_t *ancestor,\r
+                          const svn_diff_file_options_t *options,\r
+                          apr_pool_t *pool)\r
+{\r
+  diff_mem_baton_t baton;\r
+\r
+  fill_source_tokens(&(baton.sources[0]), original, pool);\r
+  fill_source_tokens(&(baton.sources[1]), modified, pool);\r
+  fill_source_tokens(&(baton.sources[2]), latest, pool);\r
+  fill_source_tokens(&(baton.sources[3]), ancestor, pool);\r
+  alloc_normalization_bufs(&baton, 4, pool);\r
+\r
+  baton.normalization_options = options;\r
+\r
+  return svn_diff_diff4(diff, &baton, &svn_diff__mem_vtable, pool);\r
+}\r
+\r
+\r
+typedef enum unified_output_e\r
+{\r
+  unified_output_context = 0,\r
+  unified_output_delete,\r
+  unified_output_insert\r
+} unified_output_e;\r
+\r
+/* Baton for generating unified diffs */\r
+typedef struct unified_output_baton_t\r
+{\r
+  svn_stream_t *output_stream;\r
+  const char *header_encoding;\r
+  source_tokens_t sources[2]; /* 0 == original; 1 == modified */\r
+  apr_size_t next_token; /* next token in original source */\r
+\r
+  /* Cached markers, in header_encoding,\r
+     indexed using unified_output_e */\r
+  const char *prefix_str[3];\r
+\r
+  svn_stringbuf_t *hunk;    /* in-progress hunk data */\r
+  apr_off_t hunk_length[2]; /* 0 == original; 1 == modified */\r
+  apr_off_t hunk_start[2];  /* 0 == original; 1 == modified */\r
+\r
+  /* Pool for allocation of temporary memory in the callbacks\r
+     Should be cleared on entry of each iteration of a callback */\r
+  apr_pool_t *pool;\r
+} output_baton_t;\r
+\r
+\r
+/* Append tokens (lines) FIRST up to PAST_LAST\r
+   from token-source index TOKENS with change-type TYPE\r
+   to the current hunk.\r
+*/\r
+static svn_error_t *\r
+output_unified_token_range(output_baton_t *btn,\r
+                           int tokens,\r
+                           unified_output_e type,\r
+                           apr_off_t first,\r
+                           apr_off_t past_last)\r
+{\r
+  source_tokens_t *source = &btn->sources[tokens];\r
+  apr_off_t idx;\r
+\r
+  past_last = (past_last > source->tokens->nelts)\r
+    ? source->tokens->nelts : past_last;\r
+\r
+  if (tokens == 0)\r
+    /* We get context from the original source, don't expect\r
+       to be asked to output a block which starts before\r
+       what we already have written. */\r
+    first = (first < btn->next_token) ? btn->next_token : first;\r
+\r
+  if (first >= past_last)\r
+    return SVN_NO_ERROR;\r
+\r
+  /* Do the loop with prefix and token */\r
+  for (idx = first; idx < past_last; idx++)\r
+    {\r
+      svn_string_t *token\r
+        = APR_ARRAY_IDX(source->tokens, idx, svn_string_t *);\r
+      svn_stringbuf_appendcstr(btn->hunk, btn->prefix_str[type]);\r
+      svn_stringbuf_appendbytes(btn->hunk, token->data, token->len);\r
+\r
+      if (type == unified_output_context)\r
+        {\r
+          btn->hunk_length[0]++;\r
+          btn->hunk_length[1]++;\r
+        }\r
+      else if (type == unified_output_delete)\r
+        btn->hunk_length[0]++;\r
+      else\r
+        btn->hunk_length[1]++;\r
+\r
+    }\r
+  if (past_last == source->tokens->nelts && source->ends_without_eol)\r
+    {\r
+      const char *out_str;\r
+      SVN_ERR(svn_utf_cstring_from_utf8_ex2\r
+              (&out_str,\r
+               /* The string below is intentionally not marked for translation:\r
+                  it's vital to correct operation of the diff(1)/patch(1)\r
+                  program pair. */\r
+               APR_EOL_STR "\\ No newline at end of file" APR_EOL_STR,\r
+               btn->header_encoding, btn->pool));\r
+      svn_stringbuf_appendcstr(btn->hunk, out_str);\r
+    }\r
+\r
+  if (tokens == 0)\r
+    btn->next_token = past_last;\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+/* Flush the hunk currently built up in BATON\r
+   into the baton's output_stream */\r
+static svn_error_t *\r
+output_unified_flush_hunk(output_baton_t *baton)\r
+{\r
+  apr_off_t target_token;\r
+  apr_size_t hunk_len;\r
+\r
+  if (svn_stringbuf_isempty(baton->hunk))\r
+    return SVN_NO_ERROR;\r
+\r
+  svn_pool_clear(baton->pool);\r
+\r
+  /* Write the trailing context */\r
+  target_token = baton->hunk_start[0] + baton->hunk_length[0]\r
+    + SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
+  SVN_ERR(output_unified_token_range(baton, 0 /*original*/,\r
+                                     unified_output_context,\r
+                                     baton->next_token, target_token));\r
+\r
+  /* Write the hunk header */\r
+  if (baton->hunk_length[0] > 0)\r
+    /* Convert our 0-based line numbers into unidiff 1-based numbers */\r
+    baton->hunk_start[0]++;\r
+  SVN_ERR(svn_stream_printf_from_utf8\r
+          (baton->output_stream, baton->header_encoding,\r
+           baton->pool,\r
+           /* Hunk length 1 is implied, don't show the\r
+              length field if we have a hunk that long */\r
+           (baton->hunk_length[0] == 1)\r
+           ? ("@@ -%" APR_OFF_T_FMT)\r
+           : ("@@ -%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT),\r
+           baton->hunk_start[0], baton->hunk_length[0]));\r
+\r
+  if (baton->hunk_length[1] > 0)\r
+    /* Convert our 0-based line numbers into unidiff 1-based numbers */\r
+    baton->hunk_start[1]++;\r
+  SVN_ERR(svn_stream_printf_from_utf8\r
+          (baton->output_stream, baton->header_encoding,\r
+           baton->pool,\r
+           /* Hunk length 1 is implied, don't show the\r
+              length field if we have a hunk that long */\r
+           (baton->hunk_length[1] == 1)\r
+           ? (" +%" APR_OFF_T_FMT " @@" APR_EOL_STR)\r
+           : (" +%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT " @@" APR_EOL_STR),\r
+           baton->hunk_start[1], baton->hunk_length[1]));\r
+\r
+  hunk_len = baton->hunk->len;\r
+  SVN_ERR(svn_stream_write(baton->output_stream,\r
+                           baton->hunk->data, &hunk_len));\r
+\r
+  baton->hunk_length[0] = baton->hunk_length[1] = 0;\r
+  svn_stringbuf_setempty(baton->hunk);\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+/* Implements svn_diff_output_fns_t::output_diff_modified */\r
+static svn_error_t *\r
+output_unified_diff_modified(void *baton,\r
+                             apr_off_t original_start,\r
+                             apr_off_t original_length,\r
+                             apr_off_t modified_start,\r
+                             apr_off_t modified_length,\r
+                             apr_off_t latest_start,\r
+                             apr_off_t latest_length)\r
+{\r
+  output_baton_t *btn = baton;\r
+  apr_off_t targ_orig, targ_mod;\r
+\r
+  targ_orig = original_start - SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
+  targ_orig = (targ_orig < 0) ? 0 : targ_orig;\r
+  targ_mod = modified_start;\r
+\r
+  if (btn->next_token + SVN_DIFF__UNIFIED_CONTEXT_SIZE < targ_orig)\r
+    SVN_ERR(output_unified_flush_hunk(btn));\r
+\r
+  if (btn->hunk_length[0] == 0\r
+      && btn->hunk_length[1] == 0)\r
+    {\r
+      btn->hunk_start[0] = targ_orig;\r
+      btn->hunk_start[1] = targ_mod + targ_orig - original_start;\r
+    }\r
+\r
+  SVN_ERR(output_unified_token_range(btn, 0/*original*/,\r
+                                     unified_output_context,\r
+                                     targ_orig, original_start));\r
+  SVN_ERR(output_unified_token_range(btn, 0/*original*/,\r
+                                     unified_output_delete,\r
+                                     original_start,\r
+                                     original_start + original_length));\r
+  return output_unified_token_range(btn, 1/*modified*/, unified_output_insert,\r
+                                    modified_start,\r
+                                    modified_start + modified_length);\r
+}\r
+\r
+static const svn_diff_output_fns_t mem_output_unified_vtable =\r
+{\r
+  NULL, /* output_common */\r
+  output_unified_diff_modified,\r
+  NULL, /* output_diff_latest */\r
+  NULL, /* output_diff_common */\r
+  NULL  /* output_conflict */\r
+};\r
+\r
+\r
+svn_error_t *\r
+svn_diff_mem_string_output_unified(svn_stream_t *output_stream,\r
+                                   svn_diff_t *diff,\r
+                                   const char *original_header,\r
+                                   const char *modified_header,\r
+                                   const char *header_encoding,\r
+                                   const svn_string_t *original,\r
+                                   const svn_string_t *modified,\r
+                                   apr_pool_t *pool)\r
+{\r
+\r
+  if (svn_diff_contains_diffs(diff))\r
+    {\r
+      output_baton_t baton;\r
+\r
+      memset(&baton, 0, sizeof(baton));\r
+      baton.output_stream = output_stream;\r
+      baton.pool = svn_pool_create(pool);\r
+      baton.header_encoding = header_encoding;\r
+      baton.hunk = svn_stringbuf_create("", pool);\r
+\r
+      SVN_ERR(svn_utf_cstring_from_utf8_ex2\r
+              (&(baton.prefix_str[unified_output_context]), " ",\r
+               header_encoding, pool));\r
+      SVN_ERR(svn_utf_cstring_from_utf8_ex2\r
+              (&(baton.prefix_str[unified_output_delete]), "-",\r
+               header_encoding, pool));\r
+      SVN_ERR(svn_utf_cstring_from_utf8_ex2\r
+              (&(baton.prefix_str[unified_output_insert]), "+",\r
+               header_encoding, pool));\r
+\r
+      fill_source_tokens(&baton.sources[0], original, pool);\r
+      fill_source_tokens(&baton.sources[1], modified, pool);\r
+\r
+      SVN_ERR(svn_stream_printf_from_utf8\r
+              (output_stream, header_encoding, pool,\r
+               "--- %s" APR_EOL_STR\r
+               "+++ %s" APR_EOL_STR,\r
+               original_header, modified_header));\r
+\r
+      SVN_ERR(svn_diff_output(diff, &baton,\r
+                              &mem_output_unified_vtable));\r
+      SVN_ERR(output_unified_flush_hunk(&baton));\r
+\r
+      svn_pool_destroy(baton.pool);\r
+    }\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\r
+\f\r
+/* diff3 merge output */\r
+\r
+/* A stream to remember *leading* context.  Note that this stream does\r
+   *not* copy the data that it is remembering; it just saves\r
+   *pointers! */\r
+typedef struct {\r
+  svn_stream_t *stream;\r
+  const char *data[SVN_DIFF__UNIFIED_CONTEXT_SIZE];\r
+  apr_size_t len[SVN_DIFF__UNIFIED_CONTEXT_SIZE];\r
+  apr_size_t next_slot;\r
+  apr_size_t total_written;\r
+} context_saver_t;\r
+\r
+\r
+static svn_error_t *\r
+context_saver_stream_write(void *baton,\r
+                           const char *data,\r
+                           apr_size_t *len)\r
+{\r
+  context_saver_t *cs = baton;\r
+  cs->data[cs->next_slot] = data;\r
+  cs->len[cs->next_slot] = *len;\r
+  cs->next_slot = (cs->next_slot + 1) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
+  cs->total_written++;\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\r
+typedef struct merge_output_baton_t\r
+{\r
+  svn_stream_t *output_stream;\r
+\r
+  /* Tokenized source text */\r
+  source_tokens_t sources[3];\r
+  apr_off_t next_token;\r
+\r
+  /* Markers for marking conflicted sections */\r
+  const char *markers[4]; /* 0 = original, 1 = modified,\r
+                             2 = separator, 3 = latest (end) */\r
+  const char *marker_eol;\r
+\r
+  svn_diff_conflict_display_style_t conflict_style;\r
+\r
+  /* The rest of the fields are for\r
+     svn_diff_conflict_display_only_conflicts only.  Note that for\r
+     these batons, OUTPUT_STREAM is either CONTEXT_SAVER->STREAM or\r
+     (soon after a conflict) a "trailing context stream", never the\r
+     actual output stream.*/\r
+  /* The actual output stream. */\r
+  svn_stream_t *real_output_stream;\r
+  context_saver_t *context_saver;\r
+  /* Used to allocate context_saver and trailing context streams, and\r
+     for some printfs. */\r
+  apr_pool_t *pool;\r
+} merge_output_baton_t;\r
+\r
+\r
+static svn_error_t *\r
+flush_context_saver(context_saver_t *cs,\r
+                    svn_stream_t *output_stream)\r
+{\r
+  int i;\r
+  for (i = 0; i < SVN_DIFF__UNIFIED_CONTEXT_SIZE; i++)\r
+    {\r
+      int slot = (i + cs->next_slot) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
+      if (cs->data[slot])\r
+        {\r
+          apr_size_t len = cs->len[slot];\r
+          SVN_ERR(svn_stream_write(output_stream, cs->data[slot], &len));\r
+        }\r
+    }\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\r
+static void\r
+make_context_saver(merge_output_baton_t *mob)\r
+{\r
+  context_saver_t *cs;\r
+\r
+  svn_pool_clear(mob->pool);\r
+  cs = apr_pcalloc(mob->pool, sizeof(*cs));\r
+  cs->stream = svn_stream_empty(mob->pool);\r
+  svn_stream_set_baton(cs->stream, cs);\r
+  svn_stream_set_write(cs->stream, context_saver_stream_write);\r
+  mob->context_saver = cs;\r
+  mob->output_stream = cs->stream;\r
+}\r
+\r
+\r
+/* A stream which prints SVN_DIFF__UNIFIED_CONTEXT_SIZE lines to\r
+   BATON->REAL_OUTPUT_STREAM, and then changes BATON->OUTPUT_STREAM to\r
+   a context_saver; used for *trailing* context. */\r
+\r
+struct trailing_context_printer {\r
+  apr_size_t lines_to_print;\r
+  merge_output_baton_t *mob;\r
+};\r
+\r
+\r
+static svn_error_t *\r
+trailing_context_printer_write(void *baton,\r
+                               const char *data,\r
+                               apr_size_t *len)\r
+{\r
+  struct trailing_context_printer *tcp = baton;\r
+  SVN_ERR_ASSERT(tcp->lines_to_print > 0);\r
+  SVN_ERR(svn_stream_write(tcp->mob->real_output_stream, data, len));\r
+  tcp->lines_to_print--;\r
+  if (tcp->lines_to_print == 0)\r
+    make_context_saver(tcp->mob);\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\r
+static void\r
+make_trailing_context_printer(merge_output_baton_t *btn)\r
+{\r
+  struct trailing_context_printer *tcp;\r
+  svn_stream_t *s;\r
+\r
+  svn_pool_clear(btn->pool);\r
+\r
+  tcp = apr_pcalloc(btn->pool, sizeof(*tcp));\r
+  tcp->lines_to_print = SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
+  tcp->mob = btn;\r
+  s = svn_stream_empty(btn->pool);\r
+  svn_stream_set_baton(s, tcp);\r
+  svn_stream_set_write(s, trailing_context_printer_write);\r
+  btn->output_stream = s;\r
+}\r
+\r
+\r
+static svn_error_t *\r
+output_merge_token_range(apr_size_t *lines_printed_p,\r
+                         merge_output_baton_t *btn,\r
+                         int idx, apr_off_t first,\r
+                         apr_off_t length)\r
+{\r
+  apr_array_header_t *tokens = btn->sources[idx].tokens;\r
+  apr_size_t lines_printed = 0;\r
+\r
+  for (; length > 0 && first < tokens->nelts; length--, first++)\r
+    {\r
+      svn_string_t *token = APR_ARRAY_IDX(tokens, first, svn_string_t *);\r
+      apr_size_t len = token->len;\r
+\r
+      /* Note that the trailing context printer assumes that\r
+         svn_stream_write is called exactly once per line. */\r
+      SVN_ERR(svn_stream_write(btn->output_stream, token->data, &len));\r
+      lines_printed++;\r
+    }\r
+\r
+  if (lines_printed_p)\r
+    *lines_printed_p = lines_printed;\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+static svn_error_t *\r
+output_marker_eol(merge_output_baton_t *btn)\r
+{\r
+  apr_size_t len = strlen(btn->marker_eol);\r
+  return svn_stream_write(btn->output_stream, btn->marker_eol, &len);\r
+}\r
+\r
+static svn_error_t *\r
+output_merge_marker(merge_output_baton_t *btn, int idx)\r
+{\r
+  apr_size_t len = strlen(btn->markers[idx]);\r
+  SVN_ERR(svn_stream_write(btn->output_stream, btn->markers[idx], &len));\r
+  return output_marker_eol(btn);\r
+}\r
+\r
+static svn_error_t *\r
+output_common_modified(void *baton,\r
+                       apr_off_t original_start, apr_off_t original_length,\r
+                       apr_off_t modified_start, apr_off_t modified_length,\r
+                       apr_off_t latest_start, apr_off_t latest_length)\r
+{\r
+  return output_merge_token_range(NULL, baton, 1/*modified*/,\r
+                                  modified_start, modified_length);\r
+}\r
+\r
+static svn_error_t *\r
+output_latest(void *baton,\r
+              apr_off_t original_start, apr_off_t original_length,\r
+              apr_off_t modified_start, apr_off_t modified_length,\r
+              apr_off_t latest_start, apr_off_t latest_length)\r
+{\r
+  return output_merge_token_range(NULL, baton, 2/*latest*/,\r
+                                  latest_start, latest_length);\r
+}\r
+\r
+static svn_error_t *\r
+output_conflict(void *baton,\r
+                apr_off_t original_start, apr_off_t original_length,\r
+                apr_off_t modified_start, apr_off_t modified_length,\r
+                apr_off_t latest_start, apr_off_t latest_length,\r
+                svn_diff_t *diff);\r
+\r
+static const svn_diff_output_fns_t merge_output_vtable =\r
+{\r
+  output_common_modified, /* common */\r
+  output_common_modified, /* modified */\r
+  output_latest,\r
+  output_common_modified, /* output_diff_common */\r
+  output_conflict\r
+};\r
+\r
+static svn_error_t *\r
+output_conflict(void *baton,\r
+                apr_off_t original_start, apr_off_t original_length,\r
+                apr_off_t modified_start, apr_off_t modified_length,\r
+                apr_off_t latest_start, apr_off_t latest_length,\r
+                svn_diff_t *diff)\r
+{\r
+  merge_output_baton_t *btn = baton;\r
+\r
+  svn_diff_conflict_display_style_t style = btn->conflict_style;\r
+\r
+  if (style == svn_diff_conflict_display_resolved_modified_latest)\r
+    {\r
+      if (diff)\r
+        return svn_diff_output(diff, baton, &merge_output_vtable);\r
+      else\r
+        style = svn_diff_conflict_display_modified_latest;\r
+    }\r
+\r
+  if (style == svn_diff_conflict_display_modified_latest ||\r
+      style == svn_diff_conflict_display_modified_original_latest)\r
+    {\r
+      SVN_ERR(output_merge_marker(btn, 1/*modified*/));\r
+      SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,\r
+                                       modified_start, modified_length));\r
+\r
+      if (style == svn_diff_conflict_display_modified_original_latest)\r
+        {\r
+          SVN_ERR(output_merge_marker(btn, 0/*original*/));\r
+          SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/,\r
+                                           original_start, original_length));\r
+        }\r
+\r
+      SVN_ERR(output_merge_marker(btn, 2/*separator*/));\r
+      SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,\r
+                                       latest_start, latest_length));\r
+      SVN_ERR(output_merge_marker(btn, 3/*latest (end)*/));\r
+    }\r
+  else if (style == svn_diff_conflict_display_modified)\r
+      SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,\r
+                                       modified_start, modified_length));\r
+  else if (style == svn_diff_conflict_display_latest)\r
+      SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,\r
+                                       latest_start, latest_length));\r
+  else /* unknown style */\r
+    SVN_ERR_MALFUNCTION();\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\r
+static svn_error_t *\r
+output_conflict_with_context(void *baton,\r
+                             apr_off_t original_start,\r
+                             apr_off_t original_length,\r
+                             apr_off_t modified_start,\r
+                             apr_off_t modified_length,\r
+                             apr_off_t latest_start,\r
+                             apr_off_t latest_length,\r
+                             svn_diff_t *diff)\r
+{\r
+  merge_output_baton_t *btn = baton;\r
+\r
+  /* Are we currently saving starting context (as opposed to printing\r
+     trailing context)?  If so, flush it. */\r
+  if (btn->output_stream == btn->context_saver->stream)\r
+    {\r
+      if (btn->context_saver->total_written > SVN_DIFF__UNIFIED_CONTEXT_SIZE)\r
+        SVN_ERR(svn_stream_printf(btn->real_output_stream, btn->pool, "@@\n"));\r
+      SVN_ERR(flush_context_saver(btn->context_saver, btn->real_output_stream));\r
+    }\r
+\r
+  /* Print to the real output stream. */\r
+  btn->output_stream = btn->real_output_stream;\r
+\r
+  /* Output the conflict itself. */\r
+  SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,\r
+                            (modified_length == 1\r
+                             ? "%s (%" APR_OFF_T_FMT ")"\r
+                             : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),\r
+                            btn->markers[1],\r
+                            modified_start + 1, modified_length));\r
+  SVN_ERR(output_marker_eol(btn));\r
+  SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,\r
+                                   modified_start, modified_length));\r
+\r
+  SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,\r
+                            (original_length == 1\r
+                             ? "%s (%" APR_OFF_T_FMT ")"\r
+                             : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),\r
+                            btn->markers[0],\r
+                            original_start + 1, original_length));\r
+  SVN_ERR(output_marker_eol(btn));\r
+  SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/,\r
+                                   original_start, original_length));\r
+\r
+  SVN_ERR(output_merge_marker(btn, 2/*separator*/));\r
+  SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,\r
+                                   latest_start, latest_length));\r
+  SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,\r
+                            (latest_length == 1\r
+                             ? "%s (%" APR_OFF_T_FMT ")"\r
+                             : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),\r
+                            btn->markers[3],\r
+                            latest_start + 1, latest_length));\r
+  SVN_ERR(output_marker_eol(btn));\r
+\r
+  /* Go into print-trailing-context mode instead. */\r
+  make_trailing_context_printer(btn);\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\r
+static const svn_diff_output_fns_t merge_only_conflicts_output_vtable =\r
+{\r
+  output_common_modified,\r
+  output_common_modified,\r
+  output_latest,\r
+  output_common_modified,\r
+  output_conflict_with_context\r
+};\r
+\r
+\r
+/* TOKEN is the first token in the modified file.\r
+   Return its line-ending, if any. */\r
+static const char *\r
+detect_eol(svn_string_t *token)\r
+{\r
+  const char *curp;\r
+\r
+  if (token->len == 0)\r
+    return NULL;\r
+\r
+  curp = token->data + token->len - 1;\r
+  if (*curp == '\r')\r
+    return "\r";\r
+  else if (*curp != '\n')\r
+    return NULL;\r
+  else\r
+    {\r
+      if (token->len == 1\r
+          || *(--curp) != '\r')\r
+        return "\n";\r
+      else\r
+        return "\r\n";\r
+    }\r
+}\r
+\r
+svn_error_t *\r
+svn_diff_mem_string_output_merge2(svn_stream_t *output_stream,\r
+                                  svn_diff_t *diff,\r
+                                  const svn_string_t *original,\r
+                                  const svn_string_t *modified,\r
+                                  const svn_string_t *latest,\r
+                                  const char *conflict_original,\r
+                                  const char *conflict_modified,\r
+                                  const char *conflict_latest,\r
+                                  const char *conflict_separator,\r
+                                  svn_diff_conflict_display_style_t style,\r
+                                  apr_pool_t *pool)\r
+{\r
+  merge_output_baton_t btn;\r
+  const char *eol;\r
+  svn_boolean_t conflicts_only =\r
+    (style == svn_diff_conflict_display_only_conflicts);\r
+  const svn_diff_output_fns_t *vtable = conflicts_only\r
+     ? &merge_only_conflicts_output_vtable : &merge_output_vtable;\r
+\r
+  memset(&btn, 0, sizeof(btn));\r
+\r
+  if (conflicts_only)\r
+    {\r
+      btn.pool = svn_pool_create(pool);\r
+      make_context_saver(&btn);\r
+      btn.real_output_stream = output_stream;\r
+    }\r
+  else\r
+    btn.output_stream = output_stream;\r
+\r
+  fill_source_tokens(&(btn.sources[0]), original, pool);\r
+  fill_source_tokens(&(btn.sources[1]), modified, pool);\r
+  fill_source_tokens(&(btn.sources[2]), latest, pool);\r
+\r
+  btn.conflict_style = style;\r
+\r
+  if (btn.sources[1].tokens->nelts > 0)\r
+    {\r
+      eol = detect_eol(APR_ARRAY_IDX(btn.sources[1].tokens, 0, svn_string_t *));\r
+      if (!eol)\r
+        eol = APR_EOL_STR;  /* use the platform default */\r
+    }\r
+  else\r
+    eol = APR_EOL_STR;  /* use the platform default */\r
+\r
+  btn.marker_eol = eol;\r
+\r
+  SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[1],\r
+                                    conflict_modified\r
+                                    ? conflict_modified\r
+                                    : "<<<<<<< (modified)",\r
+                                    pool));\r
+  SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[0],\r
+                                    conflict_original\r
+                                    ? conflict_original\r
+                                    : "||||||| (original)",\r
+                                    pool));\r
+  SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[2],\r
+                                    conflict_separator\r
+                                    ? conflict_separator\r
+                                    : "=======",\r
+                                    pool));\r
+  SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[3],\r
+                                    conflict_latest\r
+                                    ? conflict_latest\r
+                                    : ">>>>>>> (latest)",\r
+                                    pool));\r
+\r
+  SVN_ERR(svn_diff_output(diff, &btn, vtable));\r
+  if (conflicts_only)\r
+    svn_pool_destroy(btn.pool);\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+svn_error_t *\r
+svn_diff_mem_string_output_merge(svn_stream_t *output_stream,\r
+                                 svn_diff_t *diff,\r
+                                 const svn_string_t *original,\r
+                                 const svn_string_t *modified,\r
+                                 const svn_string_t *latest,\r
+                                 const char *conflict_original,\r
+                                 const char *conflict_modified,\r
+                                 const char *conflict_latest,\r
+                                 const char *conflict_separator,\r
+                                 svn_boolean_t display_original_in_conflict,\r
+                                 svn_boolean_t display_resolved_conflicts,\r
+                                 apr_pool_t *pool)\r
+{\r
+  svn_diff_conflict_display_style_t style =\r
+    svn_diff_conflict_display_modified_latest;\r
+\r
+  if (display_resolved_conflicts)\r
+    style = svn_diff_conflict_display_resolved_modified_latest;\r
+\r
+  if (display_original_in_conflict)\r
+    style = svn_diff_conflict_display_modified_original_latest;\r
+\r
+  return svn_diff_mem_string_output_merge2(output_stream,\r
+                                           diff,\r
+                                           original,\r
+                                           modified,\r
+                                           latest,\r
+                                           conflict_original,\r
+                                           conflict_modified,\r
+                                           conflict_latest,\r
+                                           conflict_separator,\r
+                                           style,\r
+                                           pool);\r
+}\r
diff --git a/src/TortoiseMerge/libsvn_diff/lcs.c b/src/TortoiseMerge/libsvn_diff/lcs.c
new file mode 100644 (file)
index 0000000..4154b15
--- /dev/null
@@ -0,0 +1,248 @@
+/*\r
+ * lcs.c :  routines for creating an lcs\r
+ *\r
+ * ====================================================================\r
+ * Copyright (c) 2000-2004 CollabNet.  All rights reserved.\r
+ *\r
+ * This software is licensed as described in the file COPYING, which\r
+ * you should have received as part of this distribution.  The terms\r
+ * are also available at http://subversion.tigris.org/license-1.html.\r
+ * If newer versions of this license are posted there, you may use a\r
+ * newer version instead, at your option.\r
+ *\r
+ * This software consists of voluntary contributions made by many\r
+ * individuals.  For exact contribution history, see the revision\r
+ * history and logs, available at http://subversion.tigris.org/.\r
+ * ====================================================================\r
+ */\r
+\r
+\r
+#include <apr.h>\r
+#include <apr_pools.h>\r
+#include <apr_general.h>\r
+\r
+#include "diff.h"\r
+\r
+\r
+/*\r
+ * Calculate the Longest Common Subsequence between two datasources.\r
+ * This function is what makes the diff code tick.\r
+ *\r
+ * The LCS algorithm implemented here is described by Sun Wu,\r
+ * Udi Manber and Gene Meyers in "An O(NP) Sequence Comparison Algorithm"\r
+ *\r
+ */\r
+\r
+typedef struct svn_diff__snake_t svn_diff__snake_t;\r
+\r
+struct svn_diff__snake_t\r
+{\r
+    apr_off_t             y;\r
+    svn_diff__lcs_t      *lcs;\r
+    svn_diff__position_t *position[2];\r
+};\r
+\r
+static APR_INLINE void\r
+svn_diff__snake(apr_off_t k,\r
+                svn_diff__snake_t *fp,\r
+                int idx,\r
+                svn_diff__lcs_t **freelist,\r
+                apr_pool_t *pool)\r
+{\r
+  svn_diff__position_t *start_position[2];\r
+  svn_diff__position_t *position[2];\r
+  svn_diff__lcs_t *lcs;\r
+  svn_diff__lcs_t *previous_lcs;\r
+\r
+  /* The previous entry at fp[k] is going to be replaced.  See if we\r
+   * can mark that lcs node for reuse, because the sequence up to this\r
+   * point was a dead end.\r
+   */\r
+  lcs = fp[k].lcs;\r
+  while (lcs)\r
+    {\r
+      lcs->refcount--;\r
+      if (lcs->refcount)\r
+        break;\r
+\r
+      previous_lcs = lcs->next;\r
+      lcs->next = *freelist;\r
+      *freelist = lcs;\r
+      lcs = previous_lcs;\r
+    }\r
+\r
+  if (fp[k - 1].y + 1 > fp[k + 1].y)\r
+    {\r
+      start_position[0] = fp[k - 1].position[0];\r
+      start_position[1] = fp[k - 1].position[1]->next;\r
+\r
+      previous_lcs = fp[k - 1].lcs;\r
+    }\r
+  else\r
+    {\r
+      start_position[0] = fp[k + 1].position[0]->next;\r
+      start_position[1] = fp[k + 1].position[1];\r
+\r
+      previous_lcs = fp[k + 1].lcs;\r
+    }\r
+\r
+\r
+  /* ### Optimization, skip all positions that don't have matchpoints\r
+   * ### anyway. Beware of the sentinel, don't skip it!\r
+   */\r
+\r
+  position[0] = start_position[0];\r
+  position[1] = start_position[1];\r
+\r
+  while (position[0]->node == position[1]->node)\r
+    {\r
+      position[0] = position[0]->next;\r
+      position[1] = position[1]->next;\r
+    }\r
+\r
+  if (position[1] != start_position[1])\r
+    {\r
+      lcs = *freelist;\r
+      if (lcs)\r
+        {\r
+          *freelist = lcs->next;\r
+        }\r
+      else\r
+        {\r
+          lcs = apr_palloc(pool, sizeof(*lcs));\r
+        }\r
+\r
+      lcs->position[idx] = start_position[0];\r
+      lcs->position[abs(1 - idx)] = start_position[1];\r
+      lcs->length = position[1]->offset - start_position[1]->offset;\r
+      lcs->next = previous_lcs;\r
+      lcs->refcount = 1;\r
+      fp[k].lcs = lcs;\r
+    }\r
+  else\r
+    {\r
+      fp[k].lcs = previous_lcs;\r
+    }\r
+\r
+  if (previous_lcs)\r
+    {\r
+      previous_lcs->refcount++;\r
+    }\r
+\r
+  fp[k].position[0] = position[0];\r
+  fp[k].position[1] = position[1];\r
+\r
+  fp[k].y = position[1]->offset;\r
+}\r
+\r
+\r
+static svn_diff__lcs_t *\r
+svn_diff__lcs_reverse(svn_diff__lcs_t *lcs)\r
+{\r
+  svn_diff__lcs_t *next;\r
+  svn_diff__lcs_t *prev;\r
+\r
+  next = NULL;\r
+  while (lcs != NULL)\r
+    {\r
+      prev = lcs->next;\r
+      lcs->next = next;\r
+      next = lcs;\r
+      lcs = prev;\r
+    }\r
+\r
+  return next;\r
+}\r
+\r
+\r
+svn_diff__lcs_t *\r
+svn_diff__lcs(svn_diff__position_t *position_list1, /* pointer to tail (ring) */\r
+              svn_diff__position_t *position_list2, /* pointer to tail (ring) */\r
+              apr_pool_t *pool)\r
+{\r
+  int idx;\r
+  apr_off_t length[2];\r
+  svn_diff__snake_t *fp;\r
+  apr_off_t d;\r
+  apr_off_t k;\r
+  apr_off_t p = 0;\r
+  svn_diff__lcs_t *lcs, *lcs_freelist = NULL;\r
+\r
+  svn_diff__position_t sentinel_position[2];\r
+\r
+  /* Since EOF is always a sync point we tack on an EOF link\r
+   * with sentinel positions\r
+   */\r
+  lcs = apr_palloc(pool, sizeof(*lcs));\r
+  lcs->position[0] = apr_pcalloc(pool, sizeof(*lcs->position[0]));\r
+  lcs->position[0]->offset = position_list1 ? position_list1->offset + 1 : 1;\r
+  lcs->position[1] = apr_pcalloc(pool, sizeof(*lcs->position[1]));\r
+  lcs->position[1]->offset = position_list2 ? position_list2->offset + 1 : 1;\r
+  lcs->length = 0;\r
+  lcs->refcount = 1;\r
+  lcs->next = NULL;\r
+\r
+  if (position_list1 == NULL || position_list2 == NULL)\r
+    return lcs;\r
+\r
+  /* Calculate length of both sequences to be compared */\r
+  length[0] = position_list1->offset - position_list1->next->offset + 1;\r
+  length[1] = position_list2->offset - position_list2->next->offset + 1;\r
+  idx = length[0] > length[1] ? 1 : 0;\r
+\r
+  /* strikerXXX: here we allocate the furthest point array, which is\r
+   * strikerXXX: sized M + N + 3 (!)\r
+   */\r
+  fp = apr_pcalloc(pool,\r
+                   sizeof(*fp) * (apr_size_t)(length[0] + length[1] + 3));\r
+  fp += length[idx] + 1;\r
+\r
+  sentinel_position[idx].next = position_list1->next;\r
+  position_list1->next = &sentinel_position[idx];\r
+  sentinel_position[idx].offset = position_list1->offset + 1;\r
+\r
+  sentinel_position[abs(1 - idx)].next = position_list2->next;\r
+  position_list2->next = &sentinel_position[abs(1 - idx)];\r
+  sentinel_position[abs(1 - idx)].offset = position_list2->offset + 1;\r
+\r
+  /* These are never dereferenced, only compared by value, so we\r
+   * can safely fake these up and the void* cast is OK.\r
+   */\r
+  sentinel_position[0].node = (void*)&sentinel_position[0];\r
+  sentinel_position[1].node = (void*)&sentinel_position[1];\r
+\r
+  d = length[abs(1 - idx)] - length[idx];\r
+\r
+  /* k = -1 will be the first to be used to get previous\r
+   * position information from, make sure it holds sane\r
+   * data\r
+   */\r
+  fp[-1].position[0] = sentinel_position[0].next;\r
+  fp[-1].position[1] = &sentinel_position[1];\r
+\r
+  p = 0;\r
+  do\r
+    {\r
+      /* Forward */\r
+      for (k = -p; k < d; k++)\r
+        {\r
+          svn_diff__snake(k, fp, idx, &lcs_freelist, pool);\r
+        }\r
+\r
+      for (k = d + p; k >= d; k--)\r
+        {\r
+          svn_diff__snake(k, fp, idx, &lcs_freelist, pool);\r
+        }\r
+\r
+      p++;\r
+    }\r
+  while (fp[d].position[1] != &sentinel_position[1]);\r
+\r
+  lcs->next = fp[d].lcs;\r
+  lcs = svn_diff__lcs_reverse(lcs);\r
+\r
+  position_list1->next = sentinel_position[idx].next;\r
+  position_list2->next = sentinel_position[abs(1 - idx)].next;\r
+\r
+  return lcs;\r
+}\r
diff --git a/src/TortoiseMerge/libsvn_diff/token.c b/src/TortoiseMerge/libsvn_diff/token.c
new file mode 100644 (file)
index 0000000..5bc612c
--- /dev/null
@@ -0,0 +1,184 @@
+/*\r
+ * token.c :  routines for doing diffs\r
+ *\r
+ * ====================================================================\r
+ * Copyright (c) 2000-2004 CollabNet.  All rights reserved.\r
+ *\r
+ * This software is licensed as described in the file COPYING, which\r
+ * you should have received as part of this distribution.  The terms\r
+ * are also available at http://subversion.tigris.org/license-1.html.\r
+ * If newer versions of this license are posted there, you may use a\r
+ * newer version instead, at your option.\r
+ *\r
+ * This software consists of voluntary contributions made by many\r
+ * individuals.  For exact contribution history, see the revision\r
+ * history and logs, available at http://subversion.tigris.org/.\r
+ * ====================================================================\r
+ */\r
+\r
+\r
+#include <apr.h>\r
+#include <apr_pools.h>\r
+#include <apr_general.h>\r
+\r
+#include "svn_error.h"\r
+#include "svn_diff.h"\r
+#include "svn_types.h"\r
+\r
+#include "diff.h"\r
+\r
+\r
+/*\r
+ * Prime number to use as the size of the hash table.  This number was\r
+ * not selected by testing of any kind and may need tweaking.\r
+ */\r
+#define SVN_DIFF__HASH_SIZE 127\r
+\r
+struct svn_diff__node_t\r
+{\r
+  svn_diff__node_t     *parent;\r
+  svn_diff__node_t     *left;\r
+  svn_diff__node_t     *right;\r
+\r
+  apr_uint32_t          hash;\r
+  void                 *token;\r
+};\r
+\r
+struct svn_diff__tree_t\r
+{\r
+  svn_diff__node_t     *root[SVN_DIFF__HASH_SIZE];\r
+  apr_pool_t           *pool;\r
+};\r
+\r
+\r
+/*\r
+ * Support functions to build a tree of token positions\r
+ */\r
+\r
+void\r
+svn_diff__tree_create(svn_diff__tree_t **tree, apr_pool_t *pool)\r
+{\r
+  *tree = apr_pcalloc(pool, sizeof(**tree));\r
+  (*tree)->pool = pool;\r
+}\r
+\r
+\r
+static svn_error_t *\r
+svn_diff__tree_insert_token(svn_diff__node_t **node, svn_diff__tree_t *tree,\r
+                            void *diff_baton,\r
+                            const svn_diff_fns_t *vtable,\r
+                            apr_uint32_t hash, void *token)\r
+{\r
+  svn_diff__node_t *new_node;\r
+  svn_diff__node_t **node_ref;\r
+  svn_diff__node_t *parent;\r
+  int rv;\r
+\r
+  SVN_ERR_ASSERT(token);\r
+\r
+  parent = NULL;\r
+  node_ref = &tree->root[hash % SVN_DIFF__HASH_SIZE];\r
+\r
+  while (*node_ref != NULL)\r
+    {\r
+      parent = *node_ref;\r
+\r
+      rv = hash - parent->hash;\r
+      if (!rv)\r
+        SVN_ERR(vtable->token_compare(diff_baton, parent->token, token, &rv));\r
+\r
+      if (rv == 0)\r
+        {\r
+          /* Discard the previous token.  This helps in cases where\r
+           * only recently read tokens are still in memory.\r
+           */\r
+          if (vtable->token_discard != NULL)\r
+            vtable->token_discard(diff_baton, parent->token);\r
+\r
+          parent->token = token;\r
+          *node = parent;\r
+\r
+          return SVN_NO_ERROR;\r
+        }\r
+      else if (rv > 0)\r
+        {\r
+          node_ref = &parent->left;\r
+        }\r
+      else\r
+        {\r
+          node_ref = &parent->right;\r
+        }\r
+    }\r
+\r
+  /* Create a new node */\r
+  new_node = apr_palloc(tree->pool, sizeof(*new_node));\r
+  new_node->parent = parent;\r
+  new_node->left = NULL;\r
+  new_node->right = NULL;\r
+  new_node->hash = hash;\r
+  new_node->token = token;\r
+\r
+  *node = *node_ref = new_node;\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\r
+/*\r
+ * Get all tokens from a datasource.  Return the\r
+ * last item in the (circular) list.\r
+ */\r
+svn_error_t *\r
+svn_diff__get_tokens(svn_diff__position_t **position_list,\r
+                     svn_diff__tree_t *tree,\r
+                     void *diff_baton,\r
+                     const svn_diff_fns_t *vtable,\r
+                     svn_diff_datasource_e datasource,\r
+                     apr_pool_t *pool)\r
+{\r
+  svn_diff__position_t *start_position;\r
+  svn_diff__position_t *position = NULL;\r
+  svn_diff__position_t **position_ref;\r
+  svn_diff__node_t *node;\r
+  void *token;\r
+  apr_off_t offset;\r
+  apr_uint32_t hash;\r
+\r
+  *position_list = NULL;\r
+\r
+\r
+  SVN_ERR(vtable->datasource_open(diff_baton, datasource));\r
+\r
+  position_ref = &start_position;\r
+  offset = 0;\r
+  hash = 0; /* The callback fn doesn't need to touch it per se */\r
+  while (1)\r
+    {\r
+      SVN_ERR(vtable->datasource_get_next_token(&hash, &token,\r
+                                                diff_baton, datasource));\r
+      if (token == NULL)\r
+        break;\r
+\r
+      offset++;\r
+      SVN_ERR(svn_diff__tree_insert_token(&node, tree,\r
+                                          diff_baton, vtable,\r
+                                          hash, token));\r
+\r
+      /* Create a new position */\r
+      position = apr_palloc(pool, sizeof(*position));\r
+      position->next = NULL;\r
+      position->node = node;\r
+      position->offset = offset;\r
+\r
+      *position_ref = position;\r
+      position_ref = &position->next;\r
+    }\r
+\r
+  *position_ref = start_position;\r
+\r
+  SVN_ERR(vtable->datasource_close(diff_baton, datasource));\r
+\r
+  *position_list = position;\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
diff --git a/src/TortoiseMerge/libsvn_diff/util.c b/src/TortoiseMerge/libsvn_diff/util.c
new file mode 100644 (file)
index 0000000..12b4790
--- /dev/null
@@ -0,0 +1,396 @@
+/*\r
+ * util.c :  routines for doing diffs\r
+ *\r
+ * ====================================================================\r
+ * Copyright (c) 2000-2004 CollabNet.  All rights reserved.\r
+ *\r
+ * This software is licensed as described in the file COPYING, which\r
+ * you should have received as part of this distribution.  The terms\r
+ * are also available at http://subversion.tigris.org/license-1.html.\r
+ * If newer versions of this license are posted there, you may use a\r
+ * newer version instead, at your option.\r
+ *\r
+ * This software consists of voluntary contributions made by many\r
+ * individuals.  For exact contribution history, see the revision\r
+ * history and logs, available at http://subversion.tigris.org/.\r
+ * ====================================================================\r
+ */\r
+\r
+\r
+#include <apr.h>\r
+#include <apr_general.h>\r
+\r
+#include "svn_error.h"\r
+#include "svn_diff.h"\r
+#include "svn_types.h"\r
+#include "svn_ctype.h"\r
+\r
+#include "diff.h"\r
+\r
+/**\r
+ * An Adler-32 implementation per RFC1950.\r
+ *\r
+ * "The Adler-32 algorithm is much faster than the CRC32 algorithm yet\r
+ * still provides an extremely low probability of undetected errors"\r
+ */\r
+\r
+/*\r
+ * 65521 is the largest prime less than 65536.\r
+ * "That 65521 is prime is important to avoid a possible large class of\r
+ *  two-byte errors that leave the check unchanged."\r
+ */\r
+#define ADLER_MOD_BASE 65521\r
+\r
+/*\r
+ * "The modulo on unsigned long accumulators can be delayed for 5552 bytes,\r
+ *  so the modulo operation time is negligible."\r
+ */\r
+#define ADLER_MOD_BLOCK_SIZE 5552\r
+\r
+\r
+/*\r
+ * Start with CHECKSUM and update the checksum by processing a chunk\r
+ * of DATA sized LEN.\r
+ */\r
+apr_uint32_t\r
+svn_diff__adler32(apr_uint32_t checksum, const char *data, apr_size_t len)\r
+{\r
+  const unsigned char *input = (const unsigned char *)data;\r
+  apr_uint32_t s1 = checksum & 0xFFFF;\r
+  apr_uint32_t s2 = checksum >> 16;\r
+  apr_uint32_t b;\r
+  apr_size_t blocks = len / ADLER_MOD_BLOCK_SIZE;\r
+\r
+  len %= ADLER_MOD_BLOCK_SIZE;\r
+\r
+  while (blocks--)\r
+    {\r
+      int count = ADLER_MOD_BLOCK_SIZE;\r
+      while (count--)\r
+        {\r
+          b = *input++;\r
+          s1 += b;\r
+          s2 += s1;\r
+        }\r
+\r
+      s1 %= ADLER_MOD_BASE;\r
+      s2 %= ADLER_MOD_BASE;\r
+    }\r
+\r
+  while (len--)\r
+    {\r
+      b = *input++;\r
+      s1 += b;\r
+      s2 += s1;\r
+    }\r
+\r
+  return ((s2 % ADLER_MOD_BASE) << 16) | (s1 % ADLER_MOD_BASE);\r
+}\r
+\r
+\r
+svn_boolean_t\r
+svn_diff_contains_conflicts(svn_diff_t *diff)\r
+{\r
+  while (diff != NULL)\r
+    {\r
+      if (diff->type == svn_diff__type_conflict)\r
+        {\r
+          return TRUE;\r
+        }\r
+\r
+      diff = diff->next;\r
+    }\r
+\r
+  return FALSE;\r
+}\r
+\r
+svn_boolean_t\r
+svn_diff_contains_diffs(svn_diff_t *diff)\r
+{\r
+  while (diff != NULL)\r
+    {\r
+      if (diff->type != svn_diff__type_common)\r
+        {\r
+          return TRUE;\r
+        }\r
+\r
+      diff = diff->next;\r
+    }\r
+\r
+  return FALSE;\r
+}\r
+\r
+svn_error_t *\r
+svn_diff_output(svn_diff_t *diff,\r
+                void *output_baton,\r
+                const svn_diff_output_fns_t *vtable)\r
+{\r
+  svn_error_t *(*output_fn)(void *,\r
+                            apr_off_t, apr_off_t,\r
+                            apr_off_t, apr_off_t,\r
+                            apr_off_t, apr_off_t);\r
+\r
+  while (diff != NULL)\r
+    {\r
+      switch (diff->type)\r
+        {\r
+        case svn_diff__type_common:\r
+          output_fn = vtable->output_common;\r
+          break;\r
+\r
+        case svn_diff__type_diff_common:\r
+          output_fn = vtable->output_diff_common;\r
+          break;\r
+\r
+        case svn_diff__type_diff_modified:\r
+          output_fn = vtable->output_diff_modified;\r
+          break;\r
+\r
+        case svn_diff__type_diff_latest:\r
+          output_fn = vtable->output_diff_latest;\r
+          break;\r
+\r
+        case svn_diff__type_conflict:\r
+          output_fn = NULL;\r
+          if (vtable->output_conflict != NULL)\r
+            {\r
+              SVN_ERR(vtable->output_conflict(output_baton,\r
+                               diff->original_start, diff->original_length,\r
+                               diff->modified_start, diff->modified_length,\r
+                               diff->latest_start, diff->latest_length,\r
+                               diff->resolved_diff));\r
+            }\r
+          break;\r
+\r
+        default:\r
+          output_fn = NULL;\r
+          break;\r
+        }\r
+\r
+      if (output_fn != NULL)\r
+        {\r
+          SVN_ERR(output_fn(output_baton,\r
+                            diff->original_start, diff->original_length,\r
+                            diff->modified_start, diff->modified_length,\r
+                            diff->latest_start, diff->latest_length));\r
+        }\r
+\r
+      diff = diff->next;\r
+    }\r
+\r
+  return SVN_NO_ERROR;\r
+}\r
+\r
+\r
+void\r
+svn_diff__normalize_buffer(char **tgt,\r
+                           apr_off_t *lengthp,\r
+                           svn_diff__normalize_state_t *statep,\r
+                           const char *buf,\r
+                           const svn_diff_file_options_t *opts)\r
+{\r
+  /* Variables for looping through BUF */\r
+  const char *curp, *endp;\r
+\r
+  /* Variable to record normalizing state */\r
+  svn_diff__normalize_state_t state = *statep;\r
+\r
+  /* Variables to track what needs copying into the target buffer */\r
+  const char *start = buf;\r
+  apr_size_t include_len = 0;\r
+  svn_boolean_t last_skipped = FALSE; /* makes sure we set 'start' */\r
+\r
+  /* Variable to record the state of the target buffer */\r
+  char *tgt_newend = *tgt;\r
+\r
+  /* If this is a noop, then just get out of here. */\r
+  if (! opts->ignore_space && ! opts->ignore_eol_style)\r
+    {\r
+      *tgt = (char *)buf;\r
+      return;\r
+    }\r
+\r
+\r
+  /* It only took me forever to get this routine right,\r
+     so here my thoughts go:\r
+\r
+    Below, we loop through the data, doing 2 things:\r
+\r
+     - Normalizing\r
+     - Copying other data\r
+\r
+     The routine tries its hardest *not* to copy data, but instead\r
+     returning a pointer into already normalized existing data.\r
+\r
+     To this end, a block 'other data' shouldn't be copied when found,\r
+     but only as soon as it can't be returned in-place.\r
+\r
+     On a character level, there are 3 possible operations:\r
+\r
+     - Skip the character (don't include in the normalized data)\r
+     - Include the character (do include in the normalizad data)\r
+     - Include as another character\r
+       This is essentially the same as skipping the current character\r
+       and inserting a given character in the output data.\r
+\r
+    The macros below (SKIP, INCLUDE and INCLUDE_AS) are defined to\r
+    handle the character based operations.  The macros themselves\r
+    collect character level data into blocks.\r
+\r
+    At all times designate the START, INCLUDED_LEN and CURP pointers\r
+    an included and and skipped block like this:\r
+\r
+      [ start, start + included_len ) [ start + included_len, curp )\r
+             INCLUDED                        EXCLUDED\r
+\r
+    When the routine flips from skipping to including, the last\r
+    included block has to be flushed to the output buffer.\r
+  */\r
+\r
+  /* Going from including to skipping; only schedules the current\r
+     included section for flushing.\r
+     Also, simply chop off the character if it's the first in the buffer,\r
+     so we can possibly just return the remainder of the buffer */\r
+#define SKIP             \\r
+  do {                   \\r
+    if (start == curp)   \\r
+       ++start;          \\r
+    last_skipped = TRUE; \\r
+  } while (0)\r
+\r
+#define INCLUDE                \\r
+  do {                         \\r
+    if (last_skipped)          \\r
+      COPY_INCLUDED_SECTION;   \\r
+    ++include_len;             \\r
+    last_skipped = FALSE;      \\r
+  } while (0)\r
+\r
+#define COPY_INCLUDED_SECTION                     \\r
+  do {                                            \\r
+    if (include_len > 0)                          \\r
+      {                                           \\r
+         memmove(tgt_newend, start, include_len); \\r
+         tgt_newend += include_len;               \\r
+         include_len = 0;                         \\r
+      }                                           \\r
+    start = curp;                                 \\r
+  } while (0)\r
+\r
+  /* Include the current character as character X.\r
+     If the current character already *is* X, add it to the\r
+     currently included region, increasing chances for consecutive\r
+     fully normalized blocks. */\r
+#define INCLUDE_AS(x)          \\r
+  do {                         \\r
+    if (*curp == (x))          \\r
+      INCLUDE;                 \\r
+    else                       \\r
+      {                        \\r
+        INSERT((x));           \\r
+        SKIP;                  \\r
+      }                        \\r
+  } while (0)\r
+\r
+  /* Insert character X in the output buffer */\r
+#define INSERT(x)              \\r
+  do {                         \\r
+    COPY_INCLUDED_SECTION;     \\r
+    *tgt_newend++ = (x);       \\r
+  } while (0)\r
+\r
+  for (curp = buf, endp = buf + *lengthp; curp != endp; ++curp)\r
+    {\r
+      switch (*curp)\r
+        {\r
+        case '\r':\r
+          if (opts->ignore_eol_style)\r
+            INCLUDE_AS('\n');\r
+          else\r
+            INCLUDE;\r
+          state = svn_diff__normalize_state_cr;\r
+          break;\r
+\r
+        case '\n':\r
+          if (state == svn_diff__normalize_state_cr\r
+              && opts->ignore_eol_style)\r
+            SKIP;\r
+          else\r
+            INCLUDE;\r
+          state = svn_diff__normalize_state_normal;\r
+          break;\r
+\r
+        default:\r
+          if (svn_ctype_isspace(*curp)\r
+              && opts->ignore_space != svn_diff_file_ignore_space_none)\r
+            {\r
+              /* Whitespace but not '\r' or '\n' */\r
+              if (state != svn_diff__normalize_state_whitespace\r
+                  && opts->ignore_space\r
+                     == svn_diff_file_ignore_space_change)\r
+                /*### If we can postpone insertion of the space\r
+                  until the next non-whitespace character,\r
+                  we have a potential of reducing the number of copies:\r
+                  If this space is followed by more spaces,\r
+                  this will cause a block-copy.\r
+                  If the next non-space block is considered normalized\r
+                  *and* preceded by a space, we can take advantage of that. */\r
+                /* Note, the above optimization applies to 90% of the source\r
+                   lines in our own code, since it (generally) doesn't use\r
+                   more than one space per blank section, except for the\r
+                   beginning of a line. */\r
+                INCLUDE_AS(' ');\r
+              else\r
+                SKIP;\r
+              state = svn_diff__normalize_state_whitespace;\r
+            }\r
+          else\r
+            {\r
+              /* Non-whitespace character, or whitespace character in\r
+                 svn_diff_file_ignore_space_none mode. */\r
+              INCLUDE;\r
+              state = svn_diff__normalize_state_normal;\r
+            }\r
+        }\r
+    }\r
+\r
+  /* If we're not in whitespace, flush the last chunk of data.\r
+   * Note that this will work correctly when this is the last chunk of the\r
+   * file:\r
+   * * If there is an eol, it will either have been output when we entered\r
+   *   the state_cr, or it will be output now.\r
+   * * If there is no eol and we're not in whitespace, then we just output\r
+   *   everything below.\r
+   * * If there's no eol and we are in whitespace, we want to ignore\r
+   *   whitespace unconditionally. */\r
+\r
+  if (*tgt == tgt_newend)\r
+    {\r
+      /* we haven't copied any data in to *tgt and our chunk consists\r
+         only of one block of (already normalized) data.\r
+         Just return the block. */\r
+      *tgt = (char *)start;\r
+      *lengthp = include_len;\r
+    }\r
+  else\r
+    {\r
+      COPY_INCLUDED_SECTION;\r
+      *lengthp = tgt_newend - *tgt;\r
+    }\r
+\r
+  *statep = state;\r
+\r
+#undef SKIP\r
+#undef INCLUDE\r
+#undef INCLUDE_AS\r
+#undef INSERT\r
+#undef COPY_INCLUDED_SECTION\r
+}\r
+\r
+\r
+/* Return the library version number. */\r
+const svn_version_t *\r
+svn_diff_version(void)\r
+{\r
+  SVN_VERSION_BODY;\r
+}\r