--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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