| /* |
| * diff.c: comparing |
| * |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| /* ==================================================================== */ |
| |
| /* We define this here to remove any further warnings about the usage of |
| experimental functions in this file. */ |
| #define SVN_EXPERIMENTAL |
| |
| |
| /*** Includes. ***/ |
| |
| #include <apr_strings.h> |
| #include <apr_pools.h> |
| #include <apr_hash.h> |
| #include "svn_types.h" |
| #include "svn_hash.h" |
| #include "svn_wc.h" |
| #include "svn_diff.h" |
| #include "svn_mergeinfo.h" |
| #include "svn_client.h" |
| #include "svn_string.h" |
| #include "svn_error.h" |
| #include "svn_dirent_uri.h" |
| #include "svn_path.h" |
| #include "svn_io.h" |
| #include "svn_utf.h" |
| #include "svn_pools.h" |
| #include "svn_config.h" |
| #include "svn_props.h" |
| #include "svn_subst.h" |
| #include "client.h" |
| |
| #include "private/svn_client_shelf.h" |
| #include "private/svn_wc_private.h" |
| #include "private/svn_diff_private.h" |
| #include "private/svn_subr_private.h" |
| #include "private/svn_io_private.h" |
| #include "private/svn_ra_private.h" |
| |
| #include "svn_private_config.h" |
| |
| |
| /* Utilities */ |
| |
| #define DIFF_REVNUM_NONEXISTENT ((svn_revnum_t) -100) |
| |
| #define MAKE_ERR_BAD_RELATIVE_PATH(path, relative_to_dir) \ |
| svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH, NULL, \ |
| _("Path '%s' must be an immediate child of " \ |
| "the directory '%s'"), path, relative_to_dir) |
| |
| /* State provided by the diff drivers; used by the diff writer */ |
| typedef struct diff_driver_info_t |
| { |
| /* The anchor to prefix before wc paths */ |
| const char *anchor; |
| |
| /* Relative path of ra session from repos_root_url. |
| |
| Used only in printing git diff headers. The repository-root-relative |
| path of ... ### what user-visible property of the diff? */ |
| const char *session_relpath; |
| |
| /* Used only in printing git diff headers. Used to find the |
| repository-root-relative path of a WC path. */ |
| svn_wc_context_t *wc_ctx; |
| |
| /* The original targets passed to the diff command. We may need |
| these to construct distinctive diff labels when comparing the |
| same relative path in the same revision, under different anchors |
| (for example, when comparing a trunk against a branch). */ |
| const char *orig_path_1; |
| const char *orig_path_2; |
| } diff_driver_info_t; |
| |
| |
| /* Calculate the repository relative path of DIFF_RELPATH, using |
| * SESSION_RELPATH and WC_CTX, and return the result in *REPOS_RELPATH. |
| * ORIG_TARGET is the related original target passed to the diff command, |
| * and may be used to derive leading path components missing from PATH. |
| * ANCHOR is the local path where the diff editor is anchored. |
| * Do all allocations in POOL. */ |
| static svn_error_t * |
| make_repos_relpath(const char **repos_relpath, |
| const char *diff_relpath, |
| const char *orig_target, |
| const char *session_relpath, |
| svn_wc_context_t *wc_ctx, |
| const char *anchor, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const char *local_abspath; |
| |
| if (! session_relpath |
| || (anchor && !svn_path_is_url(orig_target))) |
| { |
| svn_error_t *err; |
| /* We're doing a WC-WC diff, so we can retrieve all information we |
| * need from the working copy. */ |
| SVN_ERR(svn_dirent_get_absolute(&local_abspath, |
| svn_dirent_join(anchor, diff_relpath, |
| scratch_pool), |
| scratch_pool)); |
| |
| err = svn_wc__node_get_repos_info(NULL, repos_relpath, NULL, NULL, |
| wc_ctx, local_abspath, |
| result_pool, scratch_pool); |
| |
| if (!session_relpath |
| || ! err |
| || (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)) |
| { |
| return svn_error_trace(err); |
| } |
| |
| /* The path represents a local working copy path, but does not |
| exist. Fall through to calculate an in-repository location |
| based on the ra session */ |
| |
| /* ### Maybe we should use the nearest existing ancestor instead? */ |
| svn_error_clear(err); |
| } |
| |
| *repos_relpath = svn_relpath_join(session_relpath, diff_relpath, |
| result_pool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Adjust paths to handle the case when we're dealing with different anchors. |
| * |
| * Set *INDEX_PATH to the new relative path. Set *LABEL_PATH1 and |
| * *LABEL_PATH2 to that path annotated with the unique parts of ORIG_PATH_1 |
| * and ORIG_PATH_2 respectively, like this: |
| * |
| * INDEX_PATH: "path" |
| * LABEL_PATH1: "path\t(.../branches/branch1)" |
| * LABEL_PATH2: "path\t(.../trunk)" |
| * |
| * Make the output paths relative to RELATIVE_TO_DIR (if not null) by |
| * removing it from the beginning of (ANCHOR + RELPATH). |
| * |
| * ANCHOR (if not null) is the local path where the diff editor is anchored. |
| * RELPATH is the path to the changed node within the diff editor, so |
| * relative to ANCHOR. |
| * |
| * RELATIVE_TO_DIR and ANCHOR are of the same form -- either absolute local |
| * paths or relative paths relative to the same base. |
| * |
| * ORIG_PATH_1 and ORIG_PATH_2 represent the two original target paths or |
| * URLs passed to the diff command. |
| * |
| * Allocate results in RESULT_POOL (or as a pointer to RELPATH) and |
| * temporary data in SCRATCH_POOL. |
| */ |
| static svn_error_t * |
| adjust_paths_for_diff_labels(const char **index_path, |
| const char **label_path1, |
| const char **label_path2, |
| const char *relative_to_dir, |
| const char *anchor, |
| const char *relpath, |
| const char *orig_path_1, |
| const char *orig_path_2, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const char *new_path = relpath; |
| const char *new_path1 = orig_path_1; |
| const char *new_path2 = orig_path_2; |
| |
| if (anchor) |
| new_path = svn_dirent_join(anchor, new_path, result_pool); |
| |
| if (relative_to_dir) |
| { |
| /* Possibly adjust the paths shown in the output (see issue #2723). */ |
| const char *child_path = svn_dirent_is_child(relative_to_dir, new_path, |
| result_pool); |
| |
| if (child_path) |
| new_path = child_path; |
| else if (! strcmp(relative_to_dir, new_path)) |
| new_path = "."; |
| else |
| return MAKE_ERR_BAD_RELATIVE_PATH(new_path, relative_to_dir); |
| } |
| |
| { |
| apr_size_t len; |
| svn_boolean_t is_url1; |
| svn_boolean_t is_url2; |
| /* ### Holy cow. Due to anchor/target weirdness, we can't |
| simply join dwi->orig_path_1 with path, ditto for |
| orig_path_2. That will work when they're directory URLs, but |
| not for file URLs. Nor can we just use anchor1 and anchor2 |
| from do_diff(), at least not without some more logic here. |
| What a nightmare. |
| |
| For now, to distinguish the two paths, we'll just put the |
| unique portions of the original targets in parentheses after |
| the received path, with ellipses for handwaving. This makes |
| the labels a bit clumsy, but at least distinctive. Better |
| solutions are possible, they'll just take more thought. */ |
| |
| /* ### BH: We can now just construct the repos_relpath, etc. as the |
| anchor is available. See also make_repos_relpath() */ |
| |
| /* Remove the common prefix of NEW_PATH1 and NEW_PATH2. */ |
| is_url1 = svn_path_is_url(new_path1); |
| is_url2 = svn_path_is_url(new_path2); |
| |
| if (is_url1 && is_url2) |
| len = strlen(svn_uri_get_longest_ancestor(new_path1, new_path2, |
| scratch_pool)); |
| else if (!is_url1 && !is_url2) |
| len = strlen(svn_dirent_get_longest_ancestor(new_path1, new_path2, |
| scratch_pool)); |
| else |
| len = 0; /* Path and URL */ |
| |
| new_path1 += len; |
| new_path2 += len; |
| } |
| |
| /* ### Should diff labels print paths in local style? Is there |
| already a standard for this? In any case, this code depends on |
| a particular style, so not calling svn_dirent_local_style() on the |
| paths below.*/ |
| |
| if (new_path[0] == '\0') |
| new_path = "."; |
| |
| if (new_path1[0] == '\0') |
| new_path1 = new_path; |
| else if (svn_path_is_url(new_path1)) |
| new_path1 = apr_psprintf(result_pool, "%s\t(%s)", new_path, new_path1); |
| else if (new_path1[0] == '/') |
| new_path1 = apr_psprintf(result_pool, "%s\t(...%s)", new_path, new_path1); |
| else |
| new_path1 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path1); |
| |
| if (new_path2[0] == '\0') |
| new_path2 = new_path; |
| else if (svn_path_is_url(new_path2)) |
| new_path2 = apr_psprintf(result_pool, "%s\t(%s)", new_path, new_path2); |
| else if (new_path2[0] == '/') |
| new_path2 = apr_psprintf(result_pool, "%s\t(...%s)", new_path, new_path2); |
| else |
| new_path2 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path2); |
| |
| *index_path = new_path; |
| *label_path1 = new_path1; |
| *label_path2 = new_path2; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Generate a label for the diff output for file PATH at revision REVNUM. |
| If REVNUM is invalid then it is assumed to be the current working |
| copy. Assumes the paths are already in the desired style (local |
| vs internal). Allocate the label in RESULT-POOL. */ |
| static const char * |
| diff_label(const char *path, |
| svn_revnum_t revnum, |
| apr_pool_t *result_pool) |
| { |
| const char *label; |
| if (revnum >= 0) |
| label = apr_psprintf(result_pool, _("%s\t(revision %ld)"), path, revnum); |
| else if (revnum == DIFF_REVNUM_NONEXISTENT) |
| label = apr_psprintf(result_pool, _("%s\t(nonexistent)"), path); |
| else /* SVN_INVALID_REVNUM */ |
| label = apr_psprintf(result_pool, _("%s\t(working copy)"), path); |
| |
| return label; |
| } |
| |
| /* Standard modes produced in git style diffs */ |
| static const int exec_mode = 0755; |
| static const int noexec_mode = 0644; |
| static const int kind_file_mode = 0100000; |
| /*static const kind_dir_mode = 0040000;*/ |
| static const int kind_symlink_mode = 0120000; |
| |
| /* Print a git diff header for an addition within a diff between PATH1 and |
| * PATH2 to the stream OS using HEADER_ENCODING. */ |
| static svn_error_t * |
| print_git_diff_header_added(svn_stream_t *os, const char *header_encoding, |
| const char *path1, const char *path2, |
| svn_boolean_t exec_bit, |
| svn_boolean_t symlink_bit, |
| apr_pool_t *scratch_pool) |
| { |
| int new_mode = (exec_bit ? exec_mode : noexec_mode) |
| | (symlink_bit ? kind_symlink_mode : kind_file_mode); |
| |
| SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, |
| "diff --git a/%s b/%s%s", |
| path1, path2, APR_EOL_STR)); |
| SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, |
| "new file mode %06o" APR_EOL_STR, |
| new_mode)); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Print a git diff header for a deletion within a diff between PATH1 and |
| * PATH2 to the stream OS using HEADER_ENCODING. */ |
| static svn_error_t * |
| print_git_diff_header_deleted(svn_stream_t *os, const char *header_encoding, |
| const char *path1, const char *path2, |
| svn_boolean_t exec_bit, |
| svn_boolean_t symlink_bit, |
| apr_pool_t *scratch_pool) |
| { |
| int old_mode = (exec_bit ? exec_mode : noexec_mode) |
| | (symlink_bit ? kind_symlink_mode : kind_file_mode); |
| SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, |
| "diff --git a/%s b/%s%s", |
| path1, path2, APR_EOL_STR)); |
| SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, |
| "deleted file mode %06o" APR_EOL_STR, |
| old_mode)); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Print a git diff header for a copy from COPYFROM_PATH to PATH to the stream |
| * OS using HEADER_ENCODING. */ |
| static svn_error_t * |
| print_git_diff_header_copied(svn_stream_t *os, const char *header_encoding, |
| const char *copyfrom_path, |
| svn_revnum_t copyfrom_rev, |
| const char *path, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, |
| "diff --git a/%s b/%s%s", |
| copyfrom_path, path, APR_EOL_STR)); |
| if (copyfrom_rev != SVN_INVALID_REVNUM) |
| SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, |
| "copy from %s@%ld%s", copyfrom_path, |
| copyfrom_rev, APR_EOL_STR)); |
| else |
| SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, |
| "copy from %s%s", copyfrom_path, |
| APR_EOL_STR)); |
| SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, |
| "copy to %s%s", path, APR_EOL_STR)); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Print a git diff header for a rename from COPYFROM_PATH to PATH to the |
| * stream OS using HEADER_ENCODING. */ |
| static svn_error_t * |
| print_git_diff_header_renamed(svn_stream_t *os, const char *header_encoding, |
| const char *copyfrom_path, const char *path, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, |
| "diff --git a/%s b/%s%s", |
| copyfrom_path, path, APR_EOL_STR)); |
| SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, |
| "rename from %s%s", copyfrom_path, |
| APR_EOL_STR)); |
| SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, |
| "rename to %s%s", path, APR_EOL_STR)); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Print a git diff header for a modification within a diff between PATH1 and |
| * PATH2 to the stream OS using HEADER_ENCODING. */ |
| static svn_error_t * |
| print_git_diff_header_modified(svn_stream_t *os, const char *header_encoding, |
| const char *path1, const char *path2, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, |
| "diff --git a/%s b/%s%s", |
| path1, path2, APR_EOL_STR)); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Helper function for print_git_diff_header */ |
| static svn_error_t * |
| maybe_print_mode_change(svn_stream_t *os, |
| const char *header_encoding, |
| svn_boolean_t exec_bit1, |
| svn_boolean_t exec_bit2, |
| svn_boolean_t symlink_bit1, |
| svn_boolean_t symlink_bit2, |
| const char *git_index_shas, |
| apr_pool_t *scratch_pool) |
| { |
| int old_mode = (exec_bit1 ? exec_mode : noexec_mode) |
| | (symlink_bit1 ? kind_symlink_mode : kind_file_mode); |
| int new_mode = (exec_bit2 ? exec_mode : noexec_mode) |
| | (symlink_bit2 ? kind_symlink_mode : kind_file_mode); |
| if (old_mode == new_mode) |
| { |
| if (git_index_shas) |
| SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, |
| "index %s %06o" APR_EOL_STR, |
| git_index_shas, old_mode)); |
| return SVN_NO_ERROR; |
| } |
| |
| SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, |
| "old mode %06o" APR_EOL_STR, old_mode)); |
| SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, |
| "new mode %06o" APR_EOL_STR, new_mode)); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Print a git diff header showing the OPERATION to the stream OS using |
| * HEADER_ENCODING. |
| * |
| * Return suitable diff labels for the git diff in *LABEL1 and *LABEL2. |
| * |
| * REV1 and REV2 are the revisions being diffed. |
| * COPYFROM_PATH and COPYFROM_REV indicate where the |
| * diffed item was copied from. |
| * Use SCRATCH_POOL for temporary allocations. */ |
| static svn_error_t * |
| print_git_diff_header(svn_stream_t *os, |
| const char **label1, const char **label2, |
| svn_diff_operation_kind_t operation, |
| svn_revnum_t rev1, |
| svn_revnum_t rev2, |
| const char *diff_relpath, |
| const char *copyfrom_path, |
| svn_revnum_t copyfrom_rev, |
| apr_hash_t *left_props, |
| apr_hash_t *right_props, |
| const char *git_index_shas, |
| const char *header_encoding, |
| const diff_driver_info_t *ddi, |
| apr_pool_t *scratch_pool) |
| { |
| const char *repos_relpath1; |
| const char *repos_relpath2; |
| const char *copyfrom_repos_relpath = NULL; |
| svn_boolean_t exec_bit1 = (svn_prop_get_value(left_props, |
| SVN_PROP_EXECUTABLE) != NULL); |
| svn_boolean_t exec_bit2 = (svn_prop_get_value(right_props, |
| SVN_PROP_EXECUTABLE) != NULL); |
| svn_boolean_t symlink_bit1 = (svn_prop_get_value(left_props, |
| SVN_PROP_SPECIAL) != NULL); |
| svn_boolean_t symlink_bit2 = (svn_prop_get_value(right_props, |
| SVN_PROP_SPECIAL) != NULL); |
| |
| SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, |
| ddi->orig_path_1, |
| ddi->session_relpath, |
| ddi->wc_ctx, |
| ddi->anchor, |
| scratch_pool, scratch_pool)); |
| SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, |
| ddi->orig_path_2, |
| ddi->session_relpath, |
| ddi->wc_ctx, |
| ddi->anchor, |
| scratch_pool, scratch_pool)); |
| if (copyfrom_path) |
| SVN_ERR(make_repos_relpath(©from_repos_relpath, copyfrom_path, |
| ddi->orig_path_2, |
| ddi->session_relpath, |
| ddi->wc_ctx, |
| ddi->anchor, |
| scratch_pool, scratch_pool)); |
| |
| if (operation == svn_diff_op_deleted) |
| { |
| SVN_ERR(print_git_diff_header_deleted(os, header_encoding, |
| repos_relpath1, repos_relpath2, |
| exec_bit1, symlink_bit1, |
| scratch_pool)); |
| *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1), |
| rev1, scratch_pool); |
| *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2), |
| rev2, scratch_pool); |
| |
| } |
| else if (operation == svn_diff_op_copied) |
| { |
| SVN_ERR(print_git_diff_header_copied(os, header_encoding, |
| copyfrom_path, copyfrom_rev, |
| repos_relpath2, |
| scratch_pool)); |
| *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path), |
| rev1, scratch_pool); |
| *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2), |
| rev2, scratch_pool); |
| SVN_ERR(maybe_print_mode_change(os, header_encoding, |
| exec_bit1, exec_bit2, |
| symlink_bit1, symlink_bit2, |
| git_index_shas, |
| scratch_pool)); |
| } |
| else if (operation == svn_diff_op_added) |
| { |
| SVN_ERR(print_git_diff_header_added(os, header_encoding, |
| repos_relpath1, repos_relpath2, |
| exec_bit2, symlink_bit2, |
| scratch_pool)); |
| *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1), |
| rev1, scratch_pool); |
| *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2), |
| rev2, scratch_pool); |
| } |
| else if (operation == svn_diff_op_modified) |
| { |
| SVN_ERR(print_git_diff_header_modified(os, header_encoding, |
| repos_relpath1, repos_relpath2, |
| scratch_pool)); |
| *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1), |
| rev1, scratch_pool); |
| *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2), |
| rev2, scratch_pool); |
| SVN_ERR(maybe_print_mode_change(os, header_encoding, |
| exec_bit1, exec_bit2, |
| symlink_bit1, symlink_bit2, |
| git_index_shas, |
| scratch_pool)); |
| } |
| else if (operation == svn_diff_op_moved) |
| { |
| SVN_ERR(print_git_diff_header_renamed(os, header_encoding, |
| copyfrom_path, repos_relpath2, |
| scratch_pool)); |
| *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path), |
| rev1, scratch_pool); |
| *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2), |
| rev2, scratch_pool); |
| SVN_ERR(maybe_print_mode_change(os, header_encoding, |
| exec_bit1, exec_bit2, |
| symlink_bit1, symlink_bit2, |
| git_index_shas, |
| scratch_pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Print the "Index:" and "=====" lines. |
| * Show the paths in platform-independent format ('/' separators) |
| */ |
| static svn_error_t * |
| print_diff_index_header(svn_stream_t *outstream, |
| const char *header_encoding, |
| const char *index_path, |
| const char *suffix, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR(svn_stream_printf_from_utf8(outstream, |
| header_encoding, scratch_pool, |
| "Index: %s%s" APR_EOL_STR |
| SVN_DIFF__EQUAL_STRING APR_EOL_STR, |
| index_path, suffix)); |
| return SVN_NO_ERROR; |
| } |
| |
| /* A helper func that writes out verbal descriptions of property diffs |
| to OUTSTREAM. Of course, OUTSTREAM will probably be whatever was |
| passed to svn_client_diff7(), which is probably stdout. |
| |
| ### FIXME needs proper docstring |
| |
| If USE_GIT_DIFF_FORMAT is TRUE, print git diff headers, which always |
| show paths relative to the repository root. DDI->session_relpath and |
| DDI->wc_ctx are needed to normalize paths relative the repository root, |
| and are ignored if USE_GIT_DIFF_FORMAT is FALSE. |
| |
| If @a pretty_print_mergeinfo is true, then describe 'svn:mergeinfo' |
| property changes in a human-readable form that says what changes were |
| merged or reverse merged; otherwise (or if the mergeinfo property values |
| don't parse correctly) display them just like any other property. |
| */ |
| static svn_error_t * |
| display_prop_diffs(const apr_array_header_t *propchanges, |
| apr_hash_t *left_props, |
| apr_hash_t *right_props, |
| const char *diff_relpath, |
| svn_revnum_t rev1, |
| svn_revnum_t rev2, |
| const char *encoding, |
| svn_stream_t *outstream, |
| const char *relative_to_dir, |
| svn_boolean_t show_diff_header, |
| svn_boolean_t use_git_diff_format, |
| svn_boolean_t pretty_print_mergeinfo, |
| const diff_driver_info_t *ddi, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *scratch_pool) |
| { |
| const char *repos_relpath1 = NULL; |
| const char *index_path; |
| const char *label_path1, *label_path2; |
| |
| if (use_git_diff_format) |
| { |
| SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, ddi->orig_path_1, |
| ddi->session_relpath, ddi->wc_ctx, ddi->anchor, |
| scratch_pool, scratch_pool)); |
| } |
| |
| /* If we're creating a diff on the wc root, path would be empty. */ |
| SVN_ERR(adjust_paths_for_diff_labels(&index_path, |
| &label_path1, &label_path2, |
| relative_to_dir, ddi->anchor, |
| diff_relpath, |
| ddi->orig_path_1, ddi->orig_path_2, |
| scratch_pool, scratch_pool)); |
| |
| if (show_diff_header) |
| { |
| const char *label1; |
| const char *label2; |
| |
| label1 = diff_label(label_path1, rev1, scratch_pool); |
| label2 = diff_label(label_path2, rev2, scratch_pool); |
| |
| SVN_ERR(print_diff_index_header(outstream, encoding, |
| index_path, "", scratch_pool)); |
| |
| if (use_git_diff_format) |
| SVN_ERR(print_git_diff_header(outstream, &label1, &label2, |
| svn_diff_op_modified, |
| rev1, rev2, |
| diff_relpath, |
| NULL, SVN_INVALID_REVNUM, |
| left_props, right_props, |
| NULL, |
| encoding, ddi, scratch_pool)); |
| |
| /* --- label1 |
| * +++ label2 */ |
| SVN_ERR(svn_diff__unidiff_write_header( |
| outstream, encoding, label1, label2, scratch_pool)); |
| } |
| |
| SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool, |
| APR_EOL_STR |
| "Property changes on: %s" |
| APR_EOL_STR, |
| use_git_diff_format |
| ? repos_relpath1 |
| : index_path)); |
| |
| SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool, |
| SVN_DIFF__UNDER_STRING APR_EOL_STR)); |
| |
| SVN_ERR(svn_diff__display_prop_diffs( |
| outstream, encoding, propchanges, left_props, |
| pretty_print_mergeinfo, |
| -1 /* context_size */, |
| cancel_func, cancel_baton, scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /*-----------------------------------------------------------------*/ |
| |
| /*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/ |
| |
| /* Diff writer state */ |
| typedef struct diff_writer_info_t |
| { |
| /* If non-null, the external diff command to invoke. */ |
| const char *diff_cmd; |
| |
| /* This is allocated in this struct's pool or a higher-up pool. */ |
| union { |
| /* If 'diff_cmd' is null, then this is the parsed options to |
| pass to the internal libsvn_diff implementation. */ |
| svn_diff_file_options_t *for_internal; |
| /* Else if 'diff_cmd' is non-null, then... */ |
| struct { |
| /* ...this is an argument array for the external command, and */ |
| const char **argv; |
| /* ...this is the length of argv. */ |
| int argc; |
| } for_external; |
| } options; |
| |
| apr_pool_t *pool; |
| svn_stream_t *outstream; |
| svn_stream_t *errstream; |
| |
| const char *header_encoding; |
| |
| /* Set this if you want diff output even for binary files. */ |
| svn_boolean_t force_binary; |
| |
| /* The directory that diff target paths should be considered as |
| relative to for output generation (see issue #2723). */ |
| const char *relative_to_dir; |
| |
| /* Whether property differences are ignored. */ |
| svn_boolean_t ignore_properties; |
| |
| /* Whether to show only property changes. */ |
| svn_boolean_t properties_only; |
| |
| /* Whether we're producing a git-style diff. */ |
| svn_boolean_t use_git_diff_format; |
| |
| /* Whether addition of a file is summarized versus showing a full diff. */ |
| svn_boolean_t no_diff_added; |
| |
| /* Whether deletion of a file is summarized versus showing a full diff. */ |
| svn_boolean_t no_diff_deleted; |
| |
| /* Whether to ignore copyfrom information when showing adds */ |
| svn_boolean_t show_copies_as_adds; |
| |
| /* Whether to show mergeinfo prop changes in human-readable form */ |
| svn_boolean_t pretty_print_mergeinfo; |
| |
| /* Empty files for creating diffs or NULL if not used yet */ |
| const char *empty_file; |
| |
| svn_cancel_func_t cancel_func; |
| void *cancel_baton; |
| |
| struct diff_driver_info_t ddi; |
| } diff_writer_info_t; |
| |
| /* An helper for diff_dir_props_changed, diff_file_changed and diff_file_added |
| */ |
| static svn_error_t * |
| diff_props_changed(const char *diff_relpath, |
| svn_revnum_t rev1, |
| svn_revnum_t rev2, |
| const apr_array_header_t *propchanges, |
| apr_hash_t *left_props, |
| apr_hash_t *right_props, |
| svn_boolean_t show_diff_header, |
| diff_writer_info_t *dwi, |
| apr_pool_t *scratch_pool) |
| { |
| apr_array_header_t *props; |
| |
| /* If property differences are ignored, there's nothing to do. */ |
| if (dwi->ignore_properties) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, |
| scratch_pool)); |
| |
| if (props->nelts > 0) |
| { |
| /* We're using the revnums from the dwi since there's |
| * no revision argument to the svn_wc_diff_callback_t |
| * dir_props_changed(). */ |
| SVN_ERR(display_prop_diffs(props, left_props, right_props, |
| diff_relpath, |
| rev1, |
| rev2, |
| dwi->header_encoding, |
| dwi->outstream, |
| dwi->relative_to_dir, |
| show_diff_header, |
| dwi->use_git_diff_format, |
| dwi->pretty_print_mergeinfo, |
| &dwi->ddi, |
| dwi->cancel_func, |
| dwi->cancel_baton, |
| scratch_pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Given a file ORIG_TMPFILE, return a path to a temporary file that lives at |
| * least as long as RESULT_POOL, containing the git-like represention of |
| * ORIG_TMPFILE */ |
| static svn_error_t * |
| transform_link_to_git(const char **new_tmpfile, |
| const char **git_sha1, |
| const char *orig_tmpfile, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| apr_file_t *orig; |
| apr_file_t *gitlike; |
| svn_stringbuf_t *line; |
| |
| *git_sha1 = NULL; |
| |
| SVN_ERR(svn_io_file_open(&orig, orig_tmpfile, APR_READ, APR_OS_DEFAULT, |
| scratch_pool)); |
| SVN_ERR(svn_io_open_unique_file3(&gitlike, new_tmpfile, NULL, |
| svn_io_file_del_on_pool_cleanup, |
| result_pool, scratch_pool)); |
| |
| SVN_ERR(svn_io_file_readline(orig, &line, NULL, NULL, 2 * APR_PATH_MAX + 2, |
| scratch_pool, scratch_pool)); |
| |
| if (line->len > 5 && !strncmp(line->data, "link ", 5)) |
| { |
| const char *sz_str; |
| svn_checksum_t *checksum; |
| |
| svn_stringbuf_remove(line, 0, 5); |
| |
| SVN_ERR(svn_io_file_write_full(gitlike, line->data, line->len, |
| NULL, scratch_pool)); |
| |
| /* git calculates the sha over "blob X\0" + the actual data, |
| where X is the decimal size of the blob. */ |
| sz_str = apr_psprintf(scratch_pool, "blob %u", (unsigned int)line->len); |
| svn_stringbuf_insert(line, 0, sz_str, strlen(sz_str) + 1); |
| |
| SVN_ERR(svn_checksum(&checksum, svn_checksum_sha1, |
| line->data, line->len, scratch_pool)); |
| |
| *git_sha1 = svn_checksum_to_cstring(checksum, result_pool); |
| } |
| else |
| { |
| /* Not a link... so can't convert */ |
| *new_tmpfile = apr_pstrdup(result_pool, orig_tmpfile); |
| } |
| |
| SVN_ERR(svn_io_file_close(orig, scratch_pool)); |
| SVN_ERR(svn_io_file_close(gitlike, scratch_pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Show differences between TMPFILE1 and TMPFILE2. DIFF_RELPATH, REV1, and |
| REV2 are used in the headers to indicate the file and revisions. |
| |
| If either side has an svn:mime-type property that indicates 'binary' |
| content, then if DWI->force_binary is set, attempt to produce the |
| diff in the usual way, otherwise produce a 'GIT binary diff' in git mode |
| or print a warning message in non-git mode. |
| |
| If FORCE_DIFF is TRUE, always write a diff, even for empty diffs. |
| |
| Set *WROTE_HEADER to TRUE if a diff header was written */ |
| static svn_error_t * |
| diff_content_changed(svn_boolean_t *wrote_header, |
| const char *diff_relpath, |
| const char *tmpfile1, |
| const char *tmpfile2, |
| svn_revnum_t rev1, |
| svn_revnum_t rev2, |
| apr_hash_t *left_props, |
| apr_hash_t *right_props, |
| svn_diff_operation_kind_t operation, |
| svn_boolean_t force_diff, |
| const char *copyfrom_path, |
| svn_revnum_t copyfrom_rev, |
| diff_writer_info_t *dwi, |
| apr_pool_t *scratch_pool) |
| { |
| const char *rel_to_dir = dwi->relative_to_dir; |
| svn_stream_t *outstream = dwi->outstream; |
| const char *label1, *label2; |
| svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE; |
| const char *index_path; |
| const char *label_path1, *label_path2; |
| const char *mimetype1 = svn_prop_get_value(left_props, SVN_PROP_MIME_TYPE); |
| const char *mimetype2 = svn_prop_get_value(right_props, SVN_PROP_MIME_TYPE); |
| const char *index_shas = NULL; |
| |
| /* If only property differences are shown, there's nothing to do. */ |
| if (dwi->properties_only) |
| return SVN_NO_ERROR; |
| |
| /* Generate the diff headers. */ |
| SVN_ERR(adjust_paths_for_diff_labels(&index_path, |
| &label_path1, &label_path2, |
| rel_to_dir, dwi->ddi.anchor, |
| diff_relpath, |
| dwi->ddi.orig_path_1, dwi->ddi.orig_path_2, |
| scratch_pool, scratch_pool)); |
| |
| label1 = diff_label(label_path1, rev1, scratch_pool); |
| label2 = diff_label(label_path2, rev2, scratch_pool); |
| |
| /* Possible easy-out: if either mime-type is binary and force was not |
| specified, don't attempt to generate a viewable diff at all. |
| Print a warning and exit. */ |
| if (mimetype1) |
| mt1_binary = svn_mime_type_is_binary(mimetype1); |
| if (mimetype2) |
| mt2_binary = svn_mime_type_is_binary(mimetype2); |
| |
| if (dwi->use_git_diff_format) |
| { |
| const char *l_hash = NULL; |
| const char *r_hash = NULL; |
| |
| /* Change symlinks to their 'git like' plain format */ |
| if (svn_prop_get_value(left_props, SVN_PROP_SPECIAL)) |
| SVN_ERR(transform_link_to_git(&tmpfile1, &l_hash, tmpfile1, |
| scratch_pool, scratch_pool)); |
| if (svn_prop_get_value(right_props, SVN_PROP_SPECIAL)) |
| SVN_ERR(transform_link_to_git(&tmpfile2, &r_hash, tmpfile2, |
| scratch_pool, scratch_pool)); |
| |
| if (l_hash && r_hash) |
| { |
| /* The symlink has changed. But we can't tell the user of the |
| diff whether we are writing git diffs or svn diffs of the |
| symlink... except when we add a git-like index line */ |
| |
| l_hash = apr_pstrndup(scratch_pool, l_hash, 8); |
| r_hash = apr_pstrndup(scratch_pool, r_hash, 8); |
| |
| index_shas = apr_psprintf(scratch_pool, "%8s..%8s", |
| l_hash, r_hash); |
| } |
| } |
| |
| if (! dwi->force_binary && (mt1_binary || mt2_binary)) |
| { |
| /* Print out the diff header. */ |
| SVN_ERR(print_diff_index_header(outstream, dwi->header_encoding, |
| index_path, "", scratch_pool)); |
| *wrote_header = TRUE; |
| |
| /* ### Print git diff headers. */ |
| |
| if (dwi->use_git_diff_format) |
| { |
| svn_stream_t *left_stream; |
| svn_stream_t *right_stream; |
| |
| SVN_ERR(print_git_diff_header(outstream, |
| &label1, &label2, |
| operation, |
| rev1, rev2, |
| diff_relpath, |
| copyfrom_path, copyfrom_rev, |
| left_props, right_props, |
| index_shas, |
| dwi->header_encoding, |
| &dwi->ddi, scratch_pool)); |
| |
| SVN_ERR(svn_stream_open_readonly(&left_stream, tmpfile1, |
| scratch_pool, scratch_pool)); |
| SVN_ERR(svn_stream_open_readonly(&right_stream, tmpfile2, |
| scratch_pool, scratch_pool)); |
| SVN_ERR(svn_diff_output_binary(outstream, |
| left_stream, right_stream, |
| dwi->cancel_func, dwi->cancel_baton, |
| scratch_pool)); |
| } |
| else |
| { |
| SVN_ERR(svn_stream_printf_from_utf8(outstream, |
| dwi->header_encoding, scratch_pool, |
| _("Cannot display: file marked as a binary type.%s"), |
| APR_EOL_STR)); |
| |
| if (mt1_binary && !mt2_binary) |
| SVN_ERR(svn_stream_printf_from_utf8(outstream, |
| dwi->header_encoding, scratch_pool, |
| "svn:mime-type = %s" APR_EOL_STR, mimetype1)); |
| else if (mt2_binary && !mt1_binary) |
| SVN_ERR(svn_stream_printf_from_utf8(outstream, |
| dwi->header_encoding, scratch_pool, |
| "svn:mime-type = %s" APR_EOL_STR, mimetype2)); |
| else if (mt1_binary && mt2_binary) |
| { |
| if (strcmp(mimetype1, mimetype2) == 0) |
| SVN_ERR(svn_stream_printf_from_utf8(outstream, |
| dwi->header_encoding, scratch_pool, |
| "svn:mime-type = %s" APR_EOL_STR, |
| mimetype1)); |
| else |
| SVN_ERR(svn_stream_printf_from_utf8(outstream, |
| dwi->header_encoding, scratch_pool, |
| "svn:mime-type = (%s, %s)" APR_EOL_STR, |
| mimetype1, mimetype2)); |
| } |
| } |
| |
| /* Exit early. */ |
| return SVN_NO_ERROR; |
| } |
| |
| |
| if (dwi->diff_cmd) |
| { |
| svn_stream_t *errstream = dwi->errstream; |
| apr_file_t *outfile; |
| apr_file_t *errfile; |
| const char *outfilename; |
| const char *errfilename; |
| svn_stream_t *stream; |
| int exitcode; |
| |
| /* Print out the diff header. */ |
| SVN_ERR(print_diff_index_header(outstream, dwi->header_encoding, |
| index_path, "", scratch_pool)); |
| *wrote_header = TRUE; |
| |
| /* ### Do we want to add git diff headers here too? I'd say no. The |
| * ### 'Index' and '===' line is something subversion has added. The rest |
| * ### is up to the external diff application. We may be dealing with |
| * ### a non-git compatible diff application.*/ |
| |
| /* We deal in streams, but svn_io_run_diff2() deals in file handles, |
| so we may need to make temporary files and then copy the contents |
| to our stream. */ |
| outfile = svn_stream__aprfile(outstream); |
| if (outfile) |
| outfilename = NULL; |
| else |
| SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL, |
| svn_io_file_del_on_pool_cleanup, |
| scratch_pool, scratch_pool)); |
| |
| errfile = svn_stream__aprfile(errstream); |
| if (errfile) |
| errfilename = NULL; |
| else |
| SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL, |
| svn_io_file_del_on_pool_cleanup, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(svn_io_run_diff2(".", |
| dwi->options.for_external.argv, |
| dwi->options.for_external.argc, |
| label1, label2, |
| tmpfile1, tmpfile2, |
| &exitcode, outfile, errfile, |
| dwi->diff_cmd, scratch_pool)); |
| |
| /* Now, open and copy our files to our output streams. */ |
| if (outfilename) |
| { |
| SVN_ERR(svn_io_file_close(outfile, scratch_pool)); |
| SVN_ERR(svn_stream_open_readonly(&stream, outfilename, |
| scratch_pool, scratch_pool)); |
| SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(outstream, |
| scratch_pool), |
| NULL, NULL, scratch_pool)); |
| } |
| if (errfilename) |
| { |
| SVN_ERR(svn_io_file_close(errfile, scratch_pool)); |
| SVN_ERR(svn_stream_open_readonly(&stream, errfilename, |
| scratch_pool, scratch_pool)); |
| SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(errstream, |
| scratch_pool), |
| NULL, NULL, scratch_pool)); |
| } |
| } |
| else /* use libsvn_diff to generate the diff */ |
| { |
| svn_diff_t *diff; |
| |
| SVN_ERR(svn_diff_file_diff_2(&diff, tmpfile1, tmpfile2, |
| dwi->options.for_internal, |
| scratch_pool)); |
| |
| if (force_diff |
| || dwi->use_git_diff_format |
| || svn_diff_contains_diffs(diff)) |
| { |
| /* Print out the diff header. */ |
| SVN_ERR(print_diff_index_header(outstream, dwi->header_encoding, |
| index_path, "", scratch_pool)); |
| *wrote_header = TRUE; |
| |
| if (dwi->use_git_diff_format) |
| { |
| SVN_ERR(print_git_diff_header(outstream, |
| &label1, &label2, |
| operation, |
| rev1, rev2, |
| diff_relpath, |
| copyfrom_path, copyfrom_rev, |
| left_props, right_props, |
| index_shas, |
| dwi->header_encoding, |
| &dwi->ddi, scratch_pool)); |
| } |
| |
| /* Output the actual diff */ |
| if (force_diff || svn_diff_contains_diffs(diff)) |
| SVN_ERR(svn_diff_file_output_unified4(outstream, diff, |
| tmpfile1, tmpfile2, label1, label2, |
| dwi->header_encoding, rel_to_dir, |
| dwi->options.for_internal->show_c_function, |
| dwi->options.for_internal->context_size, |
| dwi->cancel_func, dwi->cancel_baton, |
| scratch_pool)); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* An svn_diff_tree_processor_t callback. */ |
| static svn_error_t * |
| diff_file_changed(const char *relpath, |
| const svn_diff_source_t *left_source, |
| const svn_diff_source_t *right_source, |
| const char *left_file, |
| const char *right_file, |
| /*const*/ apr_hash_t *left_props, |
| /*const*/ apr_hash_t *right_props, |
| svn_boolean_t file_modified, |
| const apr_array_header_t *prop_changes, |
| void *file_baton, |
| const struct svn_diff_tree_processor_t *processor, |
| apr_pool_t *scratch_pool) |
| { |
| diff_writer_info_t *dwi = processor->baton; |
| svn_boolean_t wrote_header = FALSE; |
| |
| if (file_modified) |
| SVN_ERR(diff_content_changed(&wrote_header, relpath, |
| left_file, right_file, |
| left_source->revision, |
| right_source->revision, |
| left_props, right_props, |
| svn_diff_op_modified, FALSE, |
| NULL, |
| SVN_INVALID_REVNUM, dwi, |
| scratch_pool)); |
| if (prop_changes->nelts > 0) |
| SVN_ERR(diff_props_changed(relpath, |
| left_source->revision, |
| right_source->revision, prop_changes, |
| left_props, right_props, !wrote_header, |
| dwi, scratch_pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Because the repos-diff editor passes at least one empty file to |
| each of these next two functions, they can be dumb wrappers around |
| the main workhorse routine. */ |
| |
| /* An svn_diff_tree_processor_t callback. */ |
| static svn_error_t * |
| diff_file_added(const char *relpath, |
| const svn_diff_source_t *copyfrom_source, |
| const svn_diff_source_t *right_source, |
| const char *copyfrom_file, |
| const char *right_file, |
| /*const*/ apr_hash_t *copyfrom_props, |
| /*const*/ apr_hash_t *right_props, |
| void *file_baton, |
| const struct svn_diff_tree_processor_t *processor, |
| apr_pool_t *scratch_pool) |
| { |
| diff_writer_info_t *dwi = processor->baton; |
| svn_boolean_t wrote_header = FALSE; |
| const char *left_file; |
| apr_hash_t *left_props; |
| apr_array_header_t *prop_changes; |
| |
| if (dwi->no_diff_added) |
| { |
| const char *index_path = relpath; |
| |
| if (dwi->ddi.anchor) |
| index_path = svn_dirent_join(dwi->ddi.anchor, relpath, |
| scratch_pool); |
| |
| SVN_ERR(print_diff_index_header(dwi->outstream, dwi->header_encoding, |
| index_path, " (added)", |
| scratch_pool)); |
| wrote_header = TRUE; |
| return SVN_NO_ERROR; |
| } |
| |
| /* During repos->wc diff of a copy revision numbers obtained |
| * from the working copy are always SVN_INVALID_REVNUM. */ |
| if (copyfrom_source && !dwi->show_copies_as_adds) |
| { |
| left_file = copyfrom_file; |
| left_props = copyfrom_props ? copyfrom_props : apr_hash_make(scratch_pool); |
| } |
| else |
| { |
| if (!dwi->empty_file) |
| SVN_ERR(svn_io_open_unique_file3(NULL, &dwi->empty_file, |
| NULL, svn_io_file_del_on_pool_cleanup, |
| dwi->pool, scratch_pool)); |
| |
| left_file = dwi->empty_file; |
| left_props = apr_hash_make(scratch_pool); |
| |
| copyfrom_source = NULL; |
| copyfrom_file = NULL; |
| } |
| |
| SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool)); |
| |
| if (copyfrom_source && right_file) |
| SVN_ERR(diff_content_changed(&wrote_header, relpath, |
| left_file, right_file, |
| copyfrom_source->revision, |
| right_source->revision, |
| left_props, right_props, |
| copyfrom_source->moved_from_relpath |
| ? svn_diff_op_moved |
| : svn_diff_op_copied, |
| TRUE /* force diff output */, |
| copyfrom_source->moved_from_relpath |
| ? copyfrom_source->moved_from_relpath |
| : copyfrom_source->repos_relpath, |
| copyfrom_source->revision, |
| dwi, scratch_pool)); |
| else if (right_file) |
| SVN_ERR(diff_content_changed(&wrote_header, relpath, |
| left_file, right_file, |
| DIFF_REVNUM_NONEXISTENT, |
| right_source->revision, |
| left_props, right_props, |
| svn_diff_op_added, |
| TRUE /* force diff output */, |
| NULL, SVN_INVALID_REVNUM, |
| dwi, scratch_pool)); |
| |
| if (prop_changes->nelts > 0) |
| SVN_ERR(diff_props_changed(relpath, |
| copyfrom_source ? copyfrom_source->revision |
| : DIFF_REVNUM_NONEXISTENT, |
| right_source->revision, |
| prop_changes, |
| left_props, right_props, |
| ! wrote_header, dwi, scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* An svn_diff_tree_processor_t callback. */ |
| static svn_error_t * |
| diff_file_deleted(const char *relpath, |
| const svn_diff_source_t *left_source, |
| const char *left_file, |
| /*const*/ apr_hash_t *left_props, |
| void *file_baton, |
| const struct svn_diff_tree_processor_t *processor, |
| apr_pool_t *scratch_pool) |
| { |
| diff_writer_info_t *dwi = processor->baton; |
| |
| if (dwi->no_diff_deleted) |
| { |
| const char *index_path = relpath; |
| |
| if (dwi->ddi.anchor) |
| index_path = svn_dirent_join(dwi->ddi.anchor, relpath, |
| scratch_pool); |
| |
| SVN_ERR(print_diff_index_header(dwi->outstream, dwi->header_encoding, |
| index_path, " (deleted)", |
| scratch_pool)); |
| } |
| else |
| { |
| svn_boolean_t wrote_header = FALSE; |
| |
| if (!dwi->empty_file) |
| SVN_ERR(svn_io_open_unique_file3(NULL, &dwi->empty_file, |
| NULL, svn_io_file_del_on_pool_cleanup, |
| dwi->pool, scratch_pool)); |
| |
| if (left_file) |
| SVN_ERR(diff_content_changed(&wrote_header, relpath, |
| left_file, dwi->empty_file, |
| left_source->revision, |
| DIFF_REVNUM_NONEXISTENT, |
| left_props, |
| NULL, |
| svn_diff_op_deleted, FALSE, |
| NULL, SVN_INVALID_REVNUM, |
| dwi, |
| scratch_pool)); |
| |
| if (left_props && apr_hash_count(left_props)) |
| { |
| apr_array_header_t *prop_changes; |
| |
| SVN_ERR(svn_prop_diffs(&prop_changes, apr_hash_make(scratch_pool), |
| left_props, scratch_pool)); |
| |
| SVN_ERR(diff_props_changed(relpath, |
| left_source->revision, |
| DIFF_REVNUM_NONEXISTENT, |
| prop_changes, |
| left_props, NULL, |
| ! wrote_header, dwi, scratch_pool)); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* An svn_wc_diff_callbacks4_t function. */ |
| static svn_error_t * |
| diff_dir_changed(const char *relpath, |
| const svn_diff_source_t *left_source, |
| const svn_diff_source_t *right_source, |
| /*const*/ apr_hash_t *left_props, |
| /*const*/ apr_hash_t *right_props, |
| const apr_array_header_t *prop_changes, |
| void *dir_baton, |
| const struct svn_diff_tree_processor_t *processor, |
| apr_pool_t *scratch_pool) |
| { |
| diff_writer_info_t *dwi = processor->baton; |
| |
| SVN_ERR(diff_props_changed(relpath, |
| left_source->revision, |
| right_source->revision, |
| prop_changes, |
| left_props, right_props, |
| TRUE /* show_diff_header */, |
| dwi, |
| scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* An svn_diff_tree_processor_t callback. */ |
| static svn_error_t * |
| diff_dir_added(const char *relpath, |
| const svn_diff_source_t *copyfrom_source, |
| const svn_diff_source_t *right_source, |
| /*const*/ apr_hash_t *copyfrom_props, |
| /*const*/ apr_hash_t *right_props, |
| void *dir_baton, |
| const struct svn_diff_tree_processor_t *processor, |
| apr_pool_t *scratch_pool) |
| { |
| diff_writer_info_t *dwi = processor->baton; |
| apr_hash_t *left_props; |
| apr_array_header_t *prop_changes; |
| |
| if (dwi->no_diff_added) |
| return SVN_NO_ERROR; |
| |
| if (copyfrom_source && !dwi->show_copies_as_adds) |
| { |
| left_props = copyfrom_props ? copyfrom_props |
| : apr_hash_make(scratch_pool); |
| } |
| else |
| { |
| left_props = apr_hash_make(scratch_pool); |
| copyfrom_source = NULL; |
| } |
| |
| SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, |
| scratch_pool)); |
| |
| return svn_error_trace(diff_props_changed(relpath, |
| copyfrom_source ? copyfrom_source->revision |
| : DIFF_REVNUM_NONEXISTENT, |
| right_source->revision, |
| prop_changes, |
| left_props, right_props, |
| TRUE /* show_diff_header */, |
| dwi, |
| scratch_pool)); |
| } |
| |
| /* An svn_diff_tree_processor_t callback. */ |
| static svn_error_t * |
| diff_dir_deleted(const char *relpath, |
| const svn_diff_source_t *left_source, |
| /*const*/ apr_hash_t *left_props, |
| void *dir_baton, |
| const struct svn_diff_tree_processor_t *processor, |
| apr_pool_t *scratch_pool) |
| { |
| diff_writer_info_t *dwi = processor->baton; |
| apr_array_header_t *prop_changes; |
| apr_hash_t *right_props; |
| |
| if (dwi->no_diff_deleted) |
| return SVN_NO_ERROR; |
| |
| right_props = apr_hash_make(scratch_pool); |
| SVN_ERR(svn_prop_diffs(&prop_changes, right_props, |
| left_props, scratch_pool)); |
| |
| SVN_ERR(diff_props_changed(relpath, |
| left_source->revision, |
| DIFF_REVNUM_NONEXISTENT, |
| prop_changes, |
| left_props, right_props, |
| TRUE /* show_diff_header */, |
| dwi, |
| scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /*-----------------------------------------------------------------*/ |
| |
| /** The logic behind 'svn diff' and 'svn merge'. */ |
| |
| |
| /* Hi! This is a comment left behind by Karl, and Ben is too afraid |
| to erase it at this time, because he's not fully confident that all |
| this knowledge has been grokked yet. |
| |
| There are five cases: |
| 1. path is not a URL and start_revision != end_revision |
| 2. path is not a URL and start_revision == end_revision |
| 3. path is a URL and start_revision != end_revision |
| 4. path is a URL and start_revision == end_revision |
| 5. path is not a URL and no revisions given |
| |
| With only one distinct revision the working copy provides the |
| other. When path is a URL there is no working copy. Thus |
| |
| 1: compare repository versions for URL corresponding to working copy |
| 2: compare working copy against repository version |
| 3: compare repository versions for URL |
| 4: nothing to do. |
| 5: compare working copy against text-base |
| |
| Case 4 is not as stupid as it looks, for example it may occur if |
| the user specifies two dates that resolve to the same revision. */ |
| |
| |
| /** Check if paths PATH_OR_URL1 and PATH_OR_URL2 are urls and if the |
| * revisions REVISION1 and REVISION2 are local. If PEG_REVISION is not |
| * unspecified, ensure that at least one of the two revisions is not |
| * BASE or WORKING. |
| * If PATH_OR_URL1 can only be found in the repository, set *IS_REPOS1 |
| * to TRUE. If PATH_OR_URL2 can only be found in the repository, set |
| * *IS_REPOS2 to TRUE. */ |
| static svn_error_t * |
| check_paths(svn_boolean_t *is_repos1, |
| svn_boolean_t *is_repos2, |
| const char *path_or_url1, |
| const char *path_or_url2, |
| const svn_opt_revision_t *revision1, |
| const svn_opt_revision_t *revision2, |
| const svn_opt_revision_t *peg_revision) |
| { |
| svn_boolean_t is_local_rev1, is_local_rev2; |
| |
| /* Verify our revision arguments in light of the paths. */ |
| if ((revision1->kind == svn_opt_revision_unspecified) |
| || (revision2->kind == svn_opt_revision_unspecified)) |
| return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, |
| _("Not all required revisions are specified")); |
| |
| /* Revisions can be said to be local or remote. |
| * BASE and WORKING are local revisions. */ |
| is_local_rev1 = |
| ((revision1->kind == svn_opt_revision_base) |
| || (revision1->kind == svn_opt_revision_working)); |
| is_local_rev2 = |
| ((revision2->kind == svn_opt_revision_base) |
| || (revision2->kind == svn_opt_revision_working)); |
| |
| if (peg_revision->kind != svn_opt_revision_unspecified && |
| is_local_rev1 && is_local_rev2) |
| return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, |
| _("At least one revision must be something other " |
| "than BASE or WORKING when diffing a URL")); |
| |
| /* Working copy paths with non-local revisions get turned into |
| URLs. We don't do that here, though. We simply record that it |
| needs to be done, which is information that helps us choose our |
| diff helper function. */ |
| *is_repos1 = ! is_local_rev1 || svn_path_is_url(path_or_url1); |
| *is_repos2 = ! is_local_rev2 || svn_path_is_url(path_or_url2); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Raise an error if the diff target URL does not exist at REVISION. |
| * If REVISION does not equal OTHER_REVISION, mention both revisions in |
| * the error message. Use RA_SESSION to contact the repository. |
| * Use POOL for temporary allocations. */ |
| static svn_error_t * |
| check_diff_target_exists(const char *url, |
| svn_revnum_t revision, |
| svn_revnum_t other_revision, |
| svn_ra_session_t *ra_session, |
| apr_pool_t *pool) |
| { |
| svn_node_kind_t kind; |
| const char *session_url; |
| |
| SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool)); |
| |
| if (strcmp(url, session_url) != 0) |
| SVN_ERR(svn_ra_reparent(ra_session, url, pool)); |
| |
| SVN_ERR(svn_ra_check_path(ra_session, "", revision, &kind, pool)); |
| if (kind == svn_node_none) |
| { |
| if (revision == other_revision) |
| return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, |
| _("Diff target '%s' was not found in the " |
| "repository at revision '%ld'"), |
| url, revision); |
| else |
| return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, |
| _("Diff target '%s' was not found in the " |
| "repository at revision '%ld' or '%ld'"), |
| url, revision, other_revision); |
| } |
| |
| if (strcmp(url, session_url) != 0) |
| SVN_ERR(svn_ra_reparent(ra_session, session_url, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /** Prepare a repos repos diff between PATH_OR_URL1 and |
| * PATH_OR_URL2@PEG_REVISION, in the revision range REVISION1:REVISION2. |
| * |
| * Return the resolved URL and peg revision pairs in *URL1, *REV1 and in |
| * *URL2, *REV2. |
| * |
| * Return suitable anchor URL and target pairs in *ANCHOR1, *TARGET1 and |
| * in *ANCHOR2, *TARGET2, corresponding to *URL1 and *URL2. |
| * |
| * (The choice of anchor URLs here appears to be: start with *URL1, *URL2; |
| * then take the parent dir on both sides, unless either of *URL1 or *URL2 |
| * is the repository root or the parent dir of *URL1 is unreadable.) |
| * |
| * Set *KIND1 and *KIND2 to the node kinds of *URL1 and *URL2, and verify |
| * that at least one of the diff targets exists. |
| * |
| * Set *RA_SESSION to an RA session parented at the URL *ANCHOR1. |
| * |
| * Use client context CTX. Do all allocations in POOL. */ |
| static svn_error_t * |
| diff_prepare_repos_repos(const char **url1, |
| const char **url2, |
| svn_revnum_t *rev1, |
| svn_revnum_t *rev2, |
| const char **anchor1, |
| const char **anchor2, |
| const char **target1, |
| const char **target2, |
| svn_node_kind_t *kind1, |
| svn_node_kind_t *kind2, |
| svn_ra_session_t **ra_session, |
| svn_client_ctx_t *ctx, |
| const char *path_or_url1, |
| const char *path_or_url2, |
| const svn_opt_revision_t *revision1, |
| const svn_opt_revision_t *revision2, |
| const svn_opt_revision_t *peg_revision, |
| apr_pool_t *pool) |
| { |
| const char *local_abspath1 = NULL; |
| const char *local_abspath2 = NULL; |
| const char *repos_root_url; |
| const char *wri_abspath = NULL; |
| svn_client__pathrev_t *resolved1; |
| svn_client__pathrev_t *resolved2 = NULL; |
| enum svn_opt_revision_kind peg_kind = peg_revision->kind; |
| |
| if (!svn_path_is_url(path_or_url2)) |
| { |
| SVN_ERR(svn_dirent_get_absolute(&local_abspath2, path_or_url2, pool)); |
| SVN_ERR(svn_wc__node_get_url(url2, ctx->wc_ctx, local_abspath2, |
| pool, pool)); |
| wri_abspath = local_abspath2; |
| } |
| else |
| *url2 = apr_pstrdup(pool, path_or_url2); |
| |
| if (!svn_path_is_url(path_or_url1)) |
| { |
| SVN_ERR(svn_dirent_get_absolute(&local_abspath1, path_or_url1, pool)); |
| wri_abspath = local_abspath1; |
| } |
| |
| SVN_ERR(svn_client_open_ra_session2(ra_session, *url2, wri_abspath, |
| ctx, pool, pool)); |
| |
| /* If we are performing a pegged diff, we need to find out what our |
| actual URLs will be. */ |
| if (peg_kind != svn_opt_revision_unspecified |
| || path_or_url1 == path_or_url2 |
| || local_abspath2) |
| { |
| svn_error_t *err; |
| |
| err = svn_client__resolve_rev_and_url(&resolved2, |
| *ra_session, path_or_url2, |
| peg_revision, revision2, |
| ctx, pool); |
| if (err) |
| { |
| if (err->apr_err != SVN_ERR_CLIENT_UNRELATED_RESOURCES |
| && err->apr_err != SVN_ERR_FS_NOT_FOUND) |
| return svn_error_trace(err); |
| |
| svn_error_clear(err); |
| resolved2 = NULL; |
| } |
| } |
| else |
| resolved2 = NULL; |
| |
| if (peg_kind != svn_opt_revision_unspecified |
| || path_or_url1 == path_or_url2 |
| || local_abspath1) |
| { |
| svn_error_t *err; |
| |
| err = svn_client__resolve_rev_and_url(&resolved1, |
| *ra_session, path_or_url1, |
| peg_revision, revision1, |
| ctx, pool); |
| if (err) |
| { |
| if (err->apr_err != SVN_ERR_CLIENT_UNRELATED_RESOURCES |
| && err->apr_err != SVN_ERR_FS_NOT_FOUND) |
| return svn_error_trace(err); |
| |
| svn_error_clear(err); |
| resolved1 = NULL; |
| } |
| } |
| else |
| resolved1 = NULL; |
| |
| if (resolved1) |
| { |
| *url1 = resolved1->url; |
| *rev1 = resolved1->rev; |
| } |
| else |
| { |
| /* It would be nice if we could just return an error when resolving a |
| location fails... But in many such cases we prefer diffing against |
| a non-existent location to show adds or removes (see issue #4153) */ |
| |
| if (resolved2 |
| && (peg_kind != svn_opt_revision_unspecified |
| || path_or_url1 == path_or_url2)) |
| *url1 = resolved2->url; |
| else if (! local_abspath1) |
| *url1 = path_or_url1; |
| else |
| SVN_ERR(svn_wc__node_get_url(url1, ctx->wc_ctx, local_abspath1, |
| pool, pool)); |
| |
| SVN_ERR(svn_client__get_revision_number(rev1, NULL, ctx->wc_ctx, |
| local_abspath1 /* may be NULL */, |
| *ra_session, revision1, pool)); |
| } |
| |
| if (resolved2) |
| { |
| *url2 = resolved2->url; |
| *rev2 = resolved2->rev; |
| } |
| else |
| { |
| /* It would be nice if we could just return an error when resolving a |
| location fails... But in many such cases we prefer diffing against |
| a non-existent location to show adds or removes (see issue #4153) */ |
| |
| if (resolved1 |
| && (peg_kind != svn_opt_revision_unspecified |
| || path_or_url1 == path_or_url2)) |
| *url2 = resolved1->url; |
| /* else keep url2 */ |
| |
| SVN_ERR(svn_client__get_revision_number(rev2, NULL, ctx->wc_ctx, |
| local_abspath2 /* may be NULL */, |
| *ra_session, revision2, pool)); |
| } |
| |
| /* Resolve revision and get path kind for the second target. */ |
| SVN_ERR(svn_ra_reparent(*ra_session, *url2, pool)); |
| SVN_ERR(svn_ra_check_path(*ra_session, "", *rev2, kind2, pool)); |
| |
| /* Do the same for the first target. */ |
| SVN_ERR(svn_ra_reparent(*ra_session, *url1, pool)); |
| SVN_ERR(svn_ra_check_path(*ra_session, "", *rev1, kind1, pool)); |
| |
| /* Either both URLs must exist at their respective revisions, |
| * or one of them may be missing from one side of the diff. */ |
| if (*kind1 == svn_node_none && *kind2 == svn_node_none) |
| { |
| if (strcmp(*url1, *url2) == 0) |
| return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, |
| _("Diff target '%s' was not found in the " |
| "repository at revisions '%ld' and '%ld'"), |
| *url1, *rev1, *rev2); |
| else |
| return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, |
| _("Diff targets '%s' and '%s' were not found " |
| "in the repository at revisions '%ld' and " |
| "'%ld'"), |
| *url1, *url2, *rev1, *rev2); |
| } |
| else if (*kind1 == svn_node_none) |
| SVN_ERR(check_diff_target_exists(*url1, *rev2, *rev1, *ra_session, pool)); |
| else if (*kind2 == svn_node_none) |
| SVN_ERR(check_diff_target_exists(*url2, *rev1, *rev2, *ra_session, pool)); |
| |
| SVN_ERR(svn_ra_get_repos_root2(*ra_session, &repos_root_url, pool)); |
| |
| /* Choose useful anchors and targets for our two URLs. */ |
| *anchor1 = *url1; |
| *anchor2 = *url2; |
| *target1 = ""; |
| *target2 = ""; |
| |
| /* If none of the targets is the repository root open the parent directory |
| to allow describing replacement of the target itself */ |
| if (strcmp(*url1, repos_root_url) != 0 |
| && strcmp(*url2, repos_root_url) != 0) |
| { |
| svn_node_kind_t ignored_kind; |
| svn_error_t *err; |
| |
| svn_uri_split(anchor1, target1, *url1, pool); |
| svn_uri_split(anchor2, target2, *url2, pool); |
| |
| SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool)); |
| |
| /* We might not have the necessary rights to read the root now. |
| (It is ok to pass a revision here where the node doesn't exist) */ |
| err = svn_ra_check_path(*ra_session, "", *rev1, &ignored_kind, pool); |
| |
| if (err && (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN |
| || err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED)) |
| { |
| svn_error_clear(err); |
| |
| /* Ok, lets undo the reparent... |
| |
| We can't report replacements this way, but at least we can |
| report changes on the descendants */ |
| |
| *anchor1 = svn_path_url_add_component2(*anchor1, *target1, pool); |
| *anchor2 = svn_path_url_add_component2(*anchor2, *target2, pool); |
| *target1 = ""; |
| *target2 = ""; |
| |
| SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool)); |
| } |
| else |
| SVN_ERR(err); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* A Theoretical Note From Ben, regarding do_diff(). |
| |
| This function is really svn_client_diff7(). If you read the public |
| API description for svn_client_diff7(), it sounds quite Grand. It |
| sounds really generalized and abstract and beautiful: that it will |
| diff any two paths, be they working-copy paths or URLs, at any two |
| revisions. |
| |
| Now, the *reality* is that we have exactly three 'tools' for doing |
| diffing, and thus this routine is built around the use of the three |
| tools. Here they are, for clarity: |
| |
| - svn_wc_diff: assumes both paths are the same wcpath. |
| compares wcpath@BASE vs. wcpath@WORKING |
| |
| - svn_wc_get_diff_editor: compares some URL@REV vs. wcpath@WORKING |
| |
| - svn_client__get_diff_editor: compares some URL1@REV1 vs. URL2@REV2 |
| |
| Since Subversion 1.8 we also have a variant of svn_wc_diff called |
| svn_client__arbitrary_nodes_diff, that allows handling WORKING-WORKING |
| comparisons between nodes in the working copy. |
| |
| So the truth of the matter is, if the caller's arguments can't be |
| pigeonholed into one of these use-cases, we currently bail with a |
| friendly apology. |
| |
| Perhaps someday a brave soul will truly make svn_client_diff7() |
| perfectly general. For now, we live with the 90% case. Certainly, |
| the commandline client only calls this function in legal ways. |
| When there are other users of svn_client.h, maybe this will become |
| a more pressing issue. |
| */ |
| |
| /* Return a "you can't do that" error, optionally wrapping another |
| error CHILD_ERR. */ |
| static svn_error_t * |
| unsupported_diff_error(svn_error_t *child_err) |
| { |
| return svn_error_create(SVN_ERR_INCORRECT_PARAMS, child_err, |
| _("Sorry, svn_client_diff7 was called in a way " |
| "that is not yet supported")); |
| } |
| |
| /* Perform a diff between two working-copy paths. |
| |
| PATH1 and PATH2 are both working copy paths. REVISION1 and |
| REVISION2 are their respective revisions. |
| |
| For now, require PATH1=PATH2, REVISION1='base', REVISION2='working', |
| otherwise return an error. |
| |
| Anchor DIFF_PROCESSOR at the requested diff targets. |
| |
| All other options are the same as those passed to svn_client_diff7(). */ |
| static svn_error_t * |
| diff_wc_wc(const char *path1, |
| const svn_opt_revision_t *revision1, |
| const char *path2, |
| const svn_opt_revision_t *revision2, |
| svn_depth_t depth, |
| svn_boolean_t ignore_ancestry, |
| const apr_array_header_t *changelists, |
| const svn_diff_tree_processor_t *diff_processor, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const char *abspath1; |
| |
| SVN_ERR_ASSERT(! svn_path_is_url(path1)); |
| SVN_ERR_ASSERT(! svn_path_is_url(path2)); |
| |
| SVN_ERR(svn_dirent_get_absolute(&abspath1, path1, scratch_pool)); |
| |
| /* Currently we support only the case where path1 and path2 are the |
| same path. */ |
| if ((strcmp(path1, path2) != 0) |
| || (! ((revision1->kind == svn_opt_revision_base) |
| && (revision2->kind == svn_opt_revision_working)))) |
| return unsupported_diff_error( |
| svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, |
| _("A non-URL diff at this time must be either from " |
| "a path's base to the same path's working version " |
| "or between the working versions of two paths" |
| ))); |
| |
| SVN_ERR(svn_wc__diff7(TRUE, |
| ctx->wc_ctx, abspath1, depth, |
| ignore_ancestry, changelists, |
| diff_processor, |
| ctx->cancel_func, ctx->cancel_baton, |
| result_pool, scratch_pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Perform a diff between two repository paths. |
| |
| PATH_OR_URL1 and PATH_OR_URL2 may be either URLs or the working copy paths. |
| REVISION1 and REVISION2 are their respective revisions. |
| If PEG_REVISION is specified, PATH_OR_URL2 is the path at the peg revision, |
| and the actual two paths compared are determined by following copy |
| history from PATH_OR_URL2. |
| |
| If DDI is null, anchor the DIFF_PROCESSOR at the requested diff |
| targets. (This case is used by diff-summarize.) |
| |
| If DDI is non-null: Set DDI->orig_path_* to the two diff target URLs as |
| resolved at the given revisions; set DDI->anchor to an anchor WC path |
| if either of PATH_OR_URL* is given as a WC path, else to null; set |
| DDI->session_relpath to the repository-relpath of the anchor URL for |
| DDI->orig_path_1. Anchor the DIFF_PROCESSOR at the anchor chosen |
| for the underlying diff implementation if the target on either side |
| is a file, else at the actual requested targets. |
| |
| (The choice of WC anchor implemented here for DDI->anchor appears to |
| be: choose PATH_OR_URL2 (if it's a WC path) or else PATH_OR_URL1 (if |
| it's a WC path); then take its parent dir unless both resolved URLs |
| refer to directories.) |
| |
| (For the choice of URL anchor for DDI->session_relpath, see |
| diff_prepare_repos_repos().) |
| |
| ### Bizarre anchoring. TODO: always anchor DIFF_PROCESSOR at the |
| requested targets. |
| |
| All other options are the same as those passed to svn_client_diff7(). */ |
| static svn_error_t * |
| diff_repos_repos(struct diff_driver_info_t *ddi, |
| const char *path_or_url1, |
| const char *path_or_url2, |
| const svn_opt_revision_t *revision1, |
| const svn_opt_revision_t *revision2, |
| const svn_opt_revision_t *peg_revision, |
| svn_depth_t depth, |
| svn_boolean_t ignore_ancestry, |
| svn_boolean_t text_deltas, |
| const svn_diff_tree_processor_t *diff_processor, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_ra_session_t *extra_ra_session; |
| |
| const svn_ra_reporter3_t *reporter; |
| void *reporter_baton; |
| |
| const svn_delta_editor_t *diff_editor; |
| void *diff_edit_baton; |
| |
| const char *url1; |
| const char *url2; |
| svn_revnum_t rev1; |
| svn_revnum_t rev2; |
| svn_node_kind_t kind1; |
| svn_node_kind_t kind2; |
| const char *anchor1; |
| const char *anchor2; |
| const char *target1; |
| const char *target2; |
| svn_ra_session_t *ra_session; |
| |
| /* Prepare info for the repos repos diff. */ |
| SVN_ERR(diff_prepare_repos_repos(&url1, &url2, &rev1, &rev2, |
| &anchor1, &anchor2, &target1, &target2, |
| &kind1, &kind2, &ra_session, |
| ctx, path_or_url1, path_or_url2, |
| revision1, revision2, peg_revision, |
| scratch_pool)); |
| |
| /* Set up the repos_diff editor on BASE_PATH, if available. |
| Otherwise, we just use "". */ |
| |
| if (ddi) |
| { |
| /* Get actual URLs. */ |
| ddi->orig_path_1 = url1; |
| ddi->orig_path_2 = url2; |
| |
| /* This should be moved to the diff writer |
| - path_or_url are provided by the caller |
| - target1 is available as *root_relpath |
| - (kind1 != svn_node_dir || kind2 != svn_node_dir) = !*root_is_dir */ |
| |
| if (!svn_path_is_url(path_or_url2)) |
| ddi->anchor = path_or_url2; |
| else if (!svn_path_is_url(path_or_url1)) |
| ddi->anchor = path_or_url1; |
| else |
| ddi->anchor = NULL; |
| |
| if (*target1 && ddi->anchor |
| && (kind1 != svn_node_dir || kind2 != svn_node_dir)) |
| ddi->anchor = svn_dirent_dirname(ddi->anchor, result_pool); |
| } |
| |
| /* The repository can bring in a new working copy, but not delete |
| everything. Luckily our new diff handler can just be reversed. */ |
| if (kind2 == svn_node_none) |
| { |
| const char *str_tmp; |
| svn_revnum_t rev_tmp; |
| |
| str_tmp = url2; |
| url2 = url1; |
| url1 = str_tmp; |
| |
| rev_tmp = rev2; |
| rev2 = rev1; |
| rev1 = rev_tmp; |
| |
| str_tmp = anchor2; |
| anchor2 = anchor1; |
| anchor1 = str_tmp; |
| |
| str_tmp = target2; |
| target2 = target1; |
| target1 = str_tmp; |
| |
| diff_processor = svn_diff__tree_processor_reverse_create(diff_processor, |
| scratch_pool); |
| } |
| |
| /* Filter the first path component using a filter processor, until we fixed |
| the diff processing to handle this directly */ |
| if (!ddi) |
| { |
| diff_processor = svn_diff__tree_processor_filter_create( |
| diff_processor, target1, scratch_pool); |
| } |
| else if ((kind1 != svn_node_file && kind2 != svn_node_file) |
| && target1[0] != '\0') |
| { |
| diff_processor = svn_diff__tree_processor_filter_create( |
| diff_processor, target1, scratch_pool); |
| } |
| |
| /* Now, we open an extra RA session to the correct anchor |
| location for URL1. This is used during the editor calls to fetch file |
| contents. */ |
| SVN_ERR(svn_ra__dup_session(&extra_ra_session, ra_session, anchor1, |
| scratch_pool, scratch_pool)); |
| |
| if (ddi) |
| { |
| const char *repos_root_url; |
| const char *session_url; |
| |
| SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, |
| scratch_pool)); |
| SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, |
| scratch_pool)); |
| |
| ddi->session_relpath = svn_uri_skip_ancestor(repos_root_url, |
| session_url, |
| result_pool); |
| } |
| |
| SVN_ERR(svn_client__get_diff_editor2( |
| &diff_editor, &diff_edit_baton, |
| extra_ra_session, depth, |
| rev1, |
| text_deltas, |
| diff_processor, |
| ctx->cancel_func, ctx->cancel_baton, |
| scratch_pool)); |
| |
| /* We want to switch our txn into URL2 */ |
| SVN_ERR(svn_ra_do_diff3(ra_session, &reporter, &reporter_baton, |
| rev2, target1, |
| depth, ignore_ancestry, text_deltas, |
| url2, diff_editor, diff_edit_baton, scratch_pool)); |
| |
| /* Drive the reporter; do the diff. */ |
| SVN_ERR(reporter->set_path(reporter_baton, "", rev1, |
| svn_depth_infinity, |
| FALSE, NULL, |
| scratch_pool)); |
| |
| return svn_error_trace( |
| reporter->finish_report(reporter_baton, scratch_pool)); |
| } |
| |
| /* Perform a diff between a repository path and a working-copy path. |
| |
| PATH_OR_URL1 may be either a URL or a working copy path. PATH2 is a |
| working copy path. REVISION1 is the revision of URL1. If PEG_REVISION1 |
| is specified, then PATH_OR_URL1 is the path in the peg revision, and the |
| actual repository path to be compared is determined by following copy |
| history. |
| |
| REVISION_KIND2 specifies which revision should be reported from the |
| working copy (BASE or WORKING) |
| |
| If REVERSE is TRUE, the diff will be reported in reverse. |
| |
| If DDI is null, anchor the DIFF_PROCESSOR at the requested diff |
| targets. (This case is used by diff-summarize.) |
| |
| If DDI is non-null: Set DDI->orig_path_* to the URLs of the two diff |
| targets as resolved at the given revisions; set DDI->anchor to a WC path |
| anchor for PATH2; set DDI->session_relpath to the repository-relpath of |
| the URL of that same anchor WC path. |
| |
| All other options are the same as those passed to svn_client_diff7(). */ |
| static svn_error_t * |
| diff_repos_wc(struct diff_driver_info_t *ddi, |
| const char *path_or_url1, |
| const svn_opt_revision_t *revision1, |
| const svn_opt_revision_t *peg_revision1, |
| const char *path2, |
| enum svn_opt_revision_kind revision2_kind, |
| svn_boolean_t reverse, |
| svn_depth_t depth, |
| svn_boolean_t ignore_ancestry, |
| const apr_array_header_t *changelists, |
| const svn_diff_tree_processor_t *diff_processor, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const char *anchor, *anchor_url, *target; |
| svn_ra_session_t *ra_session; |
| svn_depth_t diff_depth; |
| const svn_ra_reporter3_t *reporter; |
| void *reporter_baton; |
| const svn_delta_editor_t *diff_editor; |
| void *diff_edit_baton; |
| svn_boolean_t rev2_is_base = (revision2_kind == svn_opt_revision_base); |
| svn_boolean_t server_supports_depth; |
| const char *abspath_or_url1; |
| const char *abspath2; |
| const char *anchor_abspath; |
| svn_boolean_t is_copy; |
| svn_revnum_t cf_revision; |
| const char *cf_repos_relpath; |
| const char *cf_repos_root_url; |
| svn_depth_t cf_depth; |
| const char *copy_root_abspath; |
| const char *target_url; |
| svn_client__pathrev_t *loc1; |
| |
| SVN_ERR_ASSERT(! svn_path_is_url(path2)); |
| |
| if (!svn_path_is_url(path_or_url1)) |
| { |
| SVN_ERR(svn_dirent_get_absolute(&abspath_or_url1, path_or_url1, |
| scratch_pool)); |
| } |
| else |
| { |
| abspath_or_url1 = path_or_url1; |
| } |
| |
| SVN_ERR(svn_dirent_get_absolute(&abspath2, path2, scratch_pool)); |
| |
| /* Check if our diff target is a copied node. */ |
| SVN_ERR(svn_wc__node_get_origin(&is_copy, |
| &cf_revision, |
| &cf_repos_relpath, |
| &cf_repos_root_url, |
| NULL, &cf_depth, |
| ©_root_abspath, |
| ctx->wc_ctx, abspath2, |
| FALSE, scratch_pool, scratch_pool)); |
| |
| SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc1, |
| path_or_url1, abspath2, |
| peg_revision1, revision1, |
| ctx, scratch_pool)); |
| |
| if (revision2_kind == svn_opt_revision_base || !is_copy) |
| { |
| /* Convert path_or_url1 to a URL to feed to do_diff. */ |
| SVN_ERR(svn_wc_get_actual_target2(&anchor, &target, ctx->wc_ctx, path2, |
| scratch_pool, scratch_pool)); |
| |
| /* Handle the ugly case where target is ".." */ |
| if (*target && !svn_path_is_single_path_component(target)) |
| { |
| anchor = svn_dirent_join(anchor, target, scratch_pool); |
| target = ""; |
| } |
| |
| /* Fetch the URL of the anchor directory. */ |
| SVN_ERR(svn_dirent_get_absolute(&anchor_abspath, anchor, scratch_pool)); |
| SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath, |
| scratch_pool, scratch_pool)); |
| SVN_ERR_ASSERT(anchor_url != NULL); |
| |
| target_url = NULL; |
| } |
| else /* is_copy && revision2->kind != svn_opt_revision_base */ |
| { |
| #if 0 |
| svn_node_kind_t kind; |
| #endif |
| /* ### Ugly hack ahead ### |
| * |
| * We're diffing a locally copied/moved node. |
| * Describe the copy source to the reporter instead of the copy itself. |
| * Doing the latter would generate a single add_directory() call to the |
| * diff editor which results in an unexpected diff (the copy would |
| * be shown as deleted). |
| * |
| * ### But if we will receive any real changes from the repositor we |
| * will most likely fail to apply them as the wc diff editor assumes |
| * that we have the data to which the change applies in BASE... |
| */ |
| |
| target_url = svn_path_url_add_component2(cf_repos_root_url, |
| cf_repos_relpath, |
| scratch_pool); |
| |
| #if 0 |
| /*SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath2, FALSE, FALSE, |
| scratch_pool)); |
| |
| if (kind != svn_node_dir |
| || strcmp(copy_root_abspath, abspath2) != 0) */ |
| #endif |
| { |
| /* We are looking at a subdirectory of the repository, |
| We can describe the parent directory as the anchor.. |
| |
| ### This 'appears to work', but that is really dumb luck |
| ### for the simple cases in the test suite */ |
| anchor_abspath = svn_dirent_dirname(abspath2, scratch_pool); |
| anchor_url = svn_path_url_add_component2(cf_repos_root_url, |
| svn_relpath_dirname( |
| cf_repos_relpath, |
| scratch_pool), |
| scratch_pool); |
| target = svn_dirent_basename(abspath2, NULL); |
| anchor = svn_dirent_dirname(path2, scratch_pool); |
| } |
| #if 0 |
| else |
| { |
| /* This code, while ok can't be enabled without causing test |
| * failures. The repository will send some changes against |
| * BASE for nodes that don't have BASE... |
| */ |
| anchor_abspath = abspath2; |
| anchor_url = svn_path_url_add_component2(cf_repos_root_url, |
| cf_repos_relpath, |
| scratch_pool); |
| anchor = path2; |
| target = ""; |
| } |
| #endif |
| } |
| |
| SVN_ERR(svn_ra_reparent(ra_session, anchor_url, scratch_pool)); |
| |
| if (ddi) |
| { |
| const char *repos_root_url; |
| |
| ddi->anchor = anchor; |
| |
| if (!reverse) |
| { |
| ddi->orig_path_1 = apr_pstrdup(result_pool, loc1->url); |
| ddi->orig_path_2 = |
| svn_path_url_add_component2(anchor_url, target, result_pool); |
| } |
| else |
| { |
| ddi->orig_path_1 = |
| svn_path_url_add_component2(anchor_url, target, result_pool); |
| ddi->orig_path_2 = apr_pstrdup(result_pool, loc1->url); |
| } |
| |
| SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, |
| scratch_pool)); |
| |
| ddi->session_relpath = svn_uri_skip_ancestor(repos_root_url, |
| anchor_url, |
| result_pool); |
| } |
| else |
| { |
| diff_processor = svn_diff__tree_processor_filter_create( |
| diff_processor, target, scratch_pool); |
| } |
| |
| if (reverse) |
| diff_processor = svn_diff__tree_processor_reverse_create(diff_processor, scratch_pool); |
| |
| /* Use the diff editor to generate the diff. */ |
| SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, |
| SVN_RA_CAPABILITY_DEPTH, scratch_pool)); |
| SVN_ERR(svn_wc__get_diff_editor(&diff_editor, &diff_edit_baton, |
| ctx->wc_ctx, |
| anchor_abspath, |
| target, |
| depth, |
| ignore_ancestry, |
| rev2_is_base, |
| reverse, |
| server_supports_depth, |
| changelists, |
| diff_processor, |
| ctx->cancel_func, ctx->cancel_baton, |
| scratch_pool, scratch_pool)); |
| |
| if (depth != svn_depth_infinity) |
| diff_depth = depth; |
| else |
| diff_depth = svn_depth_unknown; |
| |
| /* Tell the RA layer we want a delta to change our txn to URL1 */ |
| SVN_ERR(svn_ra_do_diff3(ra_session, |
| &reporter, &reporter_baton, |
| loc1->rev, |
| target, |
| diff_depth, |
| ignore_ancestry, |
| TRUE, /* text_deltas */ |
| loc1->url, |
| diff_editor, diff_edit_baton, |
| scratch_pool)); |
| |
| if (is_copy && revision2_kind != svn_opt_revision_base) |
| { |
| /* Report the copy source. */ |
| if (cf_depth == svn_depth_unknown) |
| cf_depth = svn_depth_infinity; |
| |
| /* Reporting the in-wc revision as r0, makes the repository send |
| everything as added, which avoids using BASE for pristine information, |
| which is not there (or unrelated) for a copy */ |
| |
| SVN_ERR(reporter->set_path(reporter_baton, "", |
| ignore_ancestry ? 0 : cf_revision, |
| cf_depth, FALSE, NULL, scratch_pool)); |
| |
| if (*target) |
| SVN_ERR(reporter->link_path(reporter_baton, target, |
| target_url, |
| ignore_ancestry ? 0 : cf_revision, |
| cf_depth, FALSE, NULL, scratch_pool)); |
| |
| /* Finish the report to generate the diff. */ |
| SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool)); |
| } |
| else |
| { |
| /* Create a txn mirror of path2; the diff editor will print |
| diffs in reverse. :-) */ |
| SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, abspath2, |
| reporter, reporter_baton, |
| FALSE, depth, TRUE, |
| (! server_supports_depth), |
| FALSE, |
| ctx->cancel_func, ctx->cancel_baton, |
| NULL, NULL, /* notification is N/A */ |
| scratch_pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Run diff on shelf SHELF_NAME, if it exists. |
| */ |
| static svn_error_t * |
| diff_shelf(const char *shelf_name, |
| const char *target_abspath, |
| svn_depth_t depth, |
| svn_boolean_t ignore_ancestry, |
| const svn_diff_tree_processor_t *diff_processor, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *scratch_pool) |
| { |
| svn_error_t *err; |
| svn_client__shelf_t *shelf; |
| svn_client__shelf_version_t *shelf_version; |
| const char *wc_relpath; |
| |
| err = svn_client__shelf_open_existing(&shelf, |
| shelf_name, target_abspath, |
| ctx, scratch_pool); |
| if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET) |
| { |
| svn_error_clear(err); |
| return SVN_NO_ERROR; |
| } |
| else |
| SVN_ERR(err); |
| |
| SVN_ERR(svn_client__shelf_version_open(&shelf_version, |
| shelf, shelf->max_version, |
| scratch_pool, scratch_pool)); |
| wc_relpath = svn_dirent_skip_ancestor(shelf->wc_root_abspath, target_abspath); |
| SVN_ERR(svn_client__shelf_diff(shelf_version, wc_relpath, |
| depth, ignore_ancestry, |
| diff_processor, scratch_pool)); |
| SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Run diff on all shelves named in CHANGELISTS by a changelist name |
| * of the form "svn:shelf:SHELF_NAME", if they exist. |
| */ |
| static svn_error_t * |
| diff_shelves(const apr_array_header_t *changelists, |
| const char *target_abspath, |
| svn_depth_t depth, |
| svn_boolean_t ignore_ancestry, |
| const svn_diff_tree_processor_t *diff_processor, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *scratch_pool) |
| { |
| static const char PREFIX[] = "svn:shelf:"; |
| static const int PREFIX_LEN = 10; |
| int i; |
| |
| if (! changelists) |
| return SVN_NO_ERROR; |
| for (i = 0; i < changelists->nelts; i++) |
| { |
| const char *cl = APR_ARRAY_IDX(changelists, i, const char *); |
| |
| if (strncmp(cl, PREFIX, PREFIX_LEN) == 0) |
| { |
| const char *shelf_name = cl + PREFIX_LEN; |
| |
| SVN_ERR(diff_shelf(shelf_name, target_abspath, |
| depth, ignore_ancestry, |
| diff_processor, ctx, scratch_pool)); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* This is basically just the guts of svn_client_diff[_summarize][_peg]6(). */ |
| static svn_error_t * |
| do_diff(diff_driver_info_t *ddi, |
| const char *path_or_url1, |
| const char *path_or_url2, |
| const svn_opt_revision_t *revision1, |
| const svn_opt_revision_t *revision2, |
| const svn_opt_revision_t *peg_revision, |
| svn_boolean_t no_peg_revision, |
| svn_depth_t depth, |
| svn_boolean_t ignore_ancestry, |
| const apr_array_header_t *changelists, |
| svn_boolean_t text_deltas, |
| const svn_diff_tree_processor_t *diff_processor, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_boolean_t is_repos1; |
| svn_boolean_t is_repos2; |
| |
| /* Check if paths/revisions are urls/local. */ |
| SVN_ERR(check_paths(&is_repos1, &is_repos2, path_or_url1, path_or_url2, |
| revision1, revision2, peg_revision)); |
| |
| if (is_repos1) |
| { |
| if (is_repos2) |
| { |
| /* Ignores changelists. */ |
| SVN_ERR(diff_repos_repos(ddi, |
| path_or_url1, path_or_url2, |
| revision1, revision2, |
| peg_revision, depth, ignore_ancestry, |
| text_deltas, |
| diff_processor, ctx, |
| result_pool, scratch_pool)); |
| } |
| else /* path_or_url2 is a working copy path */ |
| { |
| SVN_ERR(diff_repos_wc(ddi, |
| path_or_url1, revision1, |
| no_peg_revision ? revision1 |
| : peg_revision, |
| path_or_url2, revision2->kind, |
| FALSE, depth, |
| ignore_ancestry, changelists, |
| diff_processor, ctx, |
| result_pool, scratch_pool)); |
| } |
| } |
| else /* path_or_url1 is a working copy path */ |
| { |
| if (is_repos2) |
| { |
| SVN_ERR(diff_repos_wc(ddi, |
| path_or_url2, revision2, |
| no_peg_revision ? revision2 |
| : peg_revision, |
| path_or_url1, |
| revision1->kind, |
| TRUE, depth, |
| ignore_ancestry, changelists, |
| diff_processor, ctx, |
| result_pool, scratch_pool)); |
| } |
| else /* path_or_url2 is a working copy path */ |
| { |
| if (revision1->kind == svn_opt_revision_working |
| && revision2->kind == svn_opt_revision_working) |
| { |
| const char *abspath1; |
| const char *abspath2; |
| |
| SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1, |
| scratch_pool)); |
| SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2, |
| scratch_pool)); |
| |
| if (ddi) |
| { |
| svn_node_kind_t kind1, kind2; |
| |
| SVN_ERR(svn_io_check_resolved_path(abspath1, &kind1, |
| scratch_pool)); |
| SVN_ERR(svn_io_check_resolved_path(abspath2, &kind2, |
| scratch_pool)); |
| if (kind1 == svn_node_dir && kind2 == svn_node_dir) |
| { |
| ddi->anchor = ""; |
| } |
| else |
| { |
| ddi->anchor = svn_dirent_basename(abspath1, NULL); |
| } |
| ddi->orig_path_1 = path_or_url1; |
| ddi->orig_path_2 = path_or_url2; |
| } |
| |
| /* Ignores changelists, ignore_ancestry */ |
| SVN_ERR(svn_client__arbitrary_nodes_diff(abspath1, abspath2, |
| depth, |
| diff_processor, |
| ctx, scratch_pool)); |
| } |
| else |
| { |
| if (ddi) |
| { |
| ddi->anchor = path_or_url1; |
| ddi->orig_path_1 = path_or_url1; |
| ddi->orig_path_2 = path_or_url2; |
| } |
| |
| { |
| const char *abspath1; |
| |
| SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1, |
| scratch_pool)); |
| SVN_ERR(diff_shelves(changelists, abspath1, |
| depth, ignore_ancestry, |
| diff_processor, ctx, scratch_pool)); |
| } |
| SVN_ERR(diff_wc_wc(path_or_url1, revision1, |
| path_or_url2, revision2, |
| depth, ignore_ancestry, changelists, |
| diff_processor, ctx, |
| result_pool, scratch_pool)); |
| } |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Initialize DWI.diff_cmd and DWI.options, |
| * according to OPTIONS and CONFIG. CONFIG and OPTIONS may be null. |
| * Allocate the fields in RESULT_POOL, which should be at least as long-lived |
| * as the pool DWI itself is allocated in. |
| */ |
| static svn_error_t * |
| create_diff_writer_info(diff_writer_info_t *dwi, |
| const apr_array_header_t *options, |
| apr_hash_t *config, apr_pool_t *result_pool) |
| { |
| const char *diff_cmd = NULL; |
| |
| /* See if there is a diff command and/or diff arguments. */ |
| if (config) |
| { |
| svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG); |
| svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS, |
| SVN_CONFIG_OPTION_DIFF_CMD, NULL); |
| if (options == NULL) |
| { |
| const char *diff_extensions; |
| svn_config_get(cfg, &diff_extensions, SVN_CONFIG_SECTION_HELPERS, |
| SVN_CONFIG_OPTION_DIFF_EXTENSIONS, NULL); |
| if (diff_extensions) |
| options = svn_cstring_split(diff_extensions, " \t\n\r", TRUE, |
| result_pool); |
| } |
| } |
| |
| if (options == NULL) |
| options = apr_array_make(result_pool, 0, sizeof(const char *)); |
| |
| if (diff_cmd) |
| SVN_ERR(svn_path_cstring_to_utf8(&dwi->diff_cmd, diff_cmd, |
| result_pool)); |
| else |
| dwi->diff_cmd = NULL; |
| |
| /* If there was a command, arrange options to pass to it. */ |
| if (dwi->diff_cmd) |
| { |
| const char **argv = NULL; |
| int argc = options->nelts; |
| if (argc) |
| { |
| int i; |
| argv = apr_palloc(result_pool, argc * sizeof(char *)); |
| for (i = 0; i < argc; i++) |
| SVN_ERR(svn_utf_cstring_to_utf8(&argv[i], |
| APR_ARRAY_IDX(options, i, const char *), result_pool)); |
| } |
| dwi->options.for_external.argv = argv; |
| dwi->options.for_external.argc = argc; |
| } |
| else /* No command, so arrange options for internal invocation instead. */ |
| { |
| dwi->options.for_internal = svn_diff_file_options_create(result_pool); |
| SVN_ERR(svn_diff_file_options_parse(dwi->options.for_internal, |
| options, result_pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Set up *DIFF_PROCESSOR and *DDI for normal and git-style diffs (but not |
| * summary diffs). |
| */ |
| static svn_error_t * |
| get_diff_processor(svn_diff_tree_processor_t **diff_processor, |
| struct diff_driver_info_t **ddi, |
| const apr_array_header_t *options, |
| const char *relative_to_dir, |
| svn_boolean_t no_diff_added, |
| svn_boolean_t no_diff_deleted, |
| svn_boolean_t show_copies_as_adds, |
| svn_boolean_t ignore_content_type, |
| svn_boolean_t ignore_properties, |
| svn_boolean_t properties_only, |
| svn_boolean_t use_git_diff_format, |
| svn_boolean_t pretty_print_mergeinfo, |
| const char *header_encoding, |
| svn_stream_t *outstream, |
| svn_stream_t *errstream, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| diff_writer_info_t *dwi = apr_pcalloc(pool, sizeof(*dwi)); |
| svn_diff_tree_processor_t *processor; |
| |
| /* setup callback and baton */ |
| |
| SVN_ERR(create_diff_writer_info(dwi, options, |
| ctx->config, pool)); |
| dwi->pool = pool; |
| dwi->outstream = outstream; |
| dwi->errstream = errstream; |
| dwi->header_encoding = header_encoding; |
| |
| dwi->force_binary = ignore_content_type; |
| dwi->ignore_properties = ignore_properties; |
| dwi->properties_only = properties_only; |
| dwi->relative_to_dir = relative_to_dir; |
| dwi->use_git_diff_format = use_git_diff_format; |
| dwi->no_diff_added = no_diff_added; |
| dwi->no_diff_deleted = no_diff_deleted; |
| dwi->show_copies_as_adds = show_copies_as_adds; |
| dwi->pretty_print_mergeinfo = pretty_print_mergeinfo; |
| |
| dwi->cancel_func = ctx->cancel_func; |
| dwi->cancel_baton = ctx->cancel_baton; |
| |
| dwi->ddi.wc_ctx = ctx->wc_ctx; |
| dwi->ddi.session_relpath = NULL; |
| dwi->ddi.anchor = NULL; |
| |
| processor = svn_diff__tree_processor_create(dwi, pool); |
| |
| processor->dir_added = diff_dir_added; |
| processor->dir_changed = diff_dir_changed; |
| processor->dir_deleted = diff_dir_deleted; |
| |
| processor->file_added = diff_file_added; |
| processor->file_changed = diff_file_changed; |
| processor->file_deleted = diff_file_deleted; |
| |
| *diff_processor = processor; |
| *ddi = &dwi->ddi; |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_client__get_diff_writer_svn( |
| svn_diff_tree_processor_t **diff_processor, |
| const char *anchor, |
| const char *orig_path_1, |
| const char *orig_path_2, |
| const apr_array_header_t *options, |
| const char *relative_to_dir, |
| svn_boolean_t no_diff_added, |
| svn_boolean_t no_diff_deleted, |
| svn_boolean_t show_copies_as_adds, |
| svn_boolean_t ignore_content_type, |
| svn_boolean_t ignore_properties, |
| svn_boolean_t properties_only, |
| svn_boolean_t pretty_print_mergeinfo, |
| const char *header_encoding, |
| svn_stream_t *outstream, |
| svn_stream_t *errstream, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| struct diff_driver_info_t *ddi; |
| |
| SVN_ERR(get_diff_processor(diff_processor, &ddi, |
| options, |
| relative_to_dir, |
| no_diff_added, |
| no_diff_deleted, |
| show_copies_as_adds, |
| ignore_content_type, |
| ignore_properties, |
| properties_only, |
| FALSE /*use_git_diff_format*/, |
| pretty_print_mergeinfo, |
| header_encoding, |
| outstream, errstream, |
| ctx, pool)); |
| ddi->anchor = anchor; |
| ddi->orig_path_1 = orig_path_1; |
| ddi->orig_path_2 = orig_path_2; |
| return SVN_NO_ERROR; |
| } |
| |
| /*----------------------------------------------------------------------- */ |
| |
| /*** Public Interfaces. ***/ |
| |
| /* Display context diffs between two PATH/REVISION pairs. Each of |
| these inputs will be one of the following: |
| |
| - a repository URL at a given revision. |
| - a working copy path, ignoring local mods. |
| - a working copy path, including local mods. |
| |
| We can establish a matrix that shows the nine possible types of |
| diffs we expect to support. |
| |
| |
| ` . DST || URL:rev | WC:base | WC:working | |
| ` . || | | | |
| SRC ` . || | | | |
| ============++============+============+============+ |
| URL:rev || (*) | (*) | (*) | |
| || | | | |
| || | | | |
| || | | | |
| ------------++------------+------------+------------+ |
| WC:base || (*) | | |
| || | New svn_wc_diff which | |
| || | is smart enough to | |
| || | handle two WC paths | |
| ------------++------------+ and their related + |
| WC:working || (*) | text-bases and working | |
| || | files. This operation | |
| || | is entirely local. | |
| || | | |
| ------------++------------+------------+------------+ |
| * These cases require server communication. |
| */ |
| svn_error_t * |
| svn_client_diff7(const apr_array_header_t *options, |
| const char *path_or_url1, |
| const svn_opt_revision_t *revision1, |
| const char *path_or_url2, |
| const svn_opt_revision_t *revision2, |
| const char *relative_to_dir, |
| svn_depth_t depth, |
| svn_boolean_t ignore_ancestry, |
| svn_boolean_t no_diff_added, |
| svn_boolean_t no_diff_deleted, |
| svn_boolean_t show_copies_as_adds, |
| svn_boolean_t ignore_content_type, |
| svn_boolean_t ignore_properties, |
| svn_boolean_t properties_only, |
| svn_boolean_t use_git_diff_format, |
| svn_boolean_t pretty_print_mergeinfo, |
| const char *header_encoding, |
| svn_stream_t *outstream, |
| svn_stream_t *errstream, |
| const apr_array_header_t *changelists, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| svn_opt_revision_t peg_revision; |
| svn_diff_tree_processor_t *diff_processor; |
| struct diff_driver_info_t *ddi; |
| |
| if (ignore_properties && properties_only) |
| return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, |
| _("Cannot ignore properties and show only " |
| "properties at the same time")); |
| |
| /* We will never do a pegged diff from here. */ |
| peg_revision.kind = svn_opt_revision_unspecified; |
| |
| /* --show-copies-as-adds and --git imply --notice-ancestry */ |
| if (show_copies_as_adds || use_git_diff_format) |
| ignore_ancestry = FALSE; |
| |
| SVN_ERR(get_diff_processor(&diff_processor, &ddi, |
| options, |
| relative_to_dir, |
| no_diff_added, |
| no_diff_deleted, |
| show_copies_as_adds, |
| ignore_content_type, |
| ignore_properties, |
| properties_only, |
| use_git_diff_format, |
| pretty_print_mergeinfo, |
| header_encoding, |
| outstream, errstream, |
| ctx, pool)); |
| |
| return svn_error_trace(do_diff(ddi, |
| path_or_url1, path_or_url2, |
| revision1, revision2, |
| &peg_revision, TRUE /* no_peg_revision */, |
| depth, ignore_ancestry, changelists, |
| TRUE /* text_deltas */, |
| diff_processor, ctx, pool, pool)); |
| } |
| |
| svn_error_t * |
| svn_client_diff_peg7(const apr_array_header_t *options, |
| const char *path_or_url, |
| const svn_opt_revision_t *peg_revision, |
| const svn_opt_revision_t *start_revision, |
| const svn_opt_revision_t *end_revision, |
| const char *relative_to_dir, |
| svn_depth_t depth, |
| svn_boolean_t ignore_ancestry, |
| svn_boolean_t no_diff_added, |
| svn_boolean_t no_diff_deleted, |
| svn_boolean_t show_copies_as_adds, |
| svn_boolean_t ignore_content_type, |
| svn_boolean_t ignore_properties, |
| svn_boolean_t properties_only, |
| svn_boolean_t use_git_diff_format, |
| svn_boolean_t pretty_print_mergeinfo, |
| const char *header_encoding, |
| svn_stream_t *outstream, |
| svn_stream_t *errstream, |
| const apr_array_header_t *changelists, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| svn_diff_tree_processor_t *diff_processor; |
| struct diff_driver_info_t *ddi; |
| |
| if (ignore_properties && properties_only) |
| return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, |
| _("Cannot ignore properties and show only " |
| "properties at the same time")); |
| |
| /* --show-copies-as-adds and --git imply --notice-ancestry */ |
| if (show_copies_as_adds || use_git_diff_format) |
| ignore_ancestry = FALSE; |
| |
| SVN_ERR(get_diff_processor(&diff_processor, &ddi, |
| options, |
| relative_to_dir, |
| no_diff_added, |
| no_diff_deleted, |
| show_copies_as_adds, |
| ignore_content_type, |
| ignore_properties, |
| properties_only, |
| use_git_diff_format, |
| pretty_print_mergeinfo, |
| header_encoding, |
| outstream, errstream, |
| ctx, pool)); |
| |
| return svn_error_trace(do_diff(ddi, |
| path_or_url, path_or_url, |
| start_revision, end_revision, |
| peg_revision, FALSE /* no_peg_revision */, |
| depth, ignore_ancestry, changelists, |
| TRUE /* text_deltas */, |
| diff_processor, ctx, pool, pool)); |
| } |
| |
| svn_error_t * |
| svn_client_diff_summarize2(const char *path_or_url1, |
| const svn_opt_revision_t *revision1, |
| const char *path_or_url2, |
| const svn_opt_revision_t *revision2, |
| svn_depth_t depth, |
| svn_boolean_t ignore_ancestry, |
| const apr_array_header_t *changelists, |
| svn_client_diff_summarize_func_t summarize_func, |
| void *summarize_baton, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| svn_diff_tree_processor_t *diff_processor; |
| svn_opt_revision_t peg_revision; |
| |
| /* We will never do a pegged diff from here. */ |
| peg_revision.kind = svn_opt_revision_unspecified; |
| |
| SVN_ERR(svn_client__get_diff_summarize_callbacks(&diff_processor, |
| summarize_func, summarize_baton, |
| pool, pool)); |
| |
| return svn_error_trace(do_diff(NULL, |
| path_or_url1, path_or_url2, |
| revision1, revision2, |
| &peg_revision, TRUE /* no_peg_revision */, |
| depth, ignore_ancestry, changelists, |
| FALSE /* text_deltas */, |
| diff_processor, ctx, pool, pool)); |
| } |
| |
| svn_error_t * |
| svn_client_diff_summarize_peg2(const char *path_or_url, |
| const svn_opt_revision_t *peg_revision, |
| const svn_opt_revision_t *start_revision, |
| const svn_opt_revision_t *end_revision, |
| svn_depth_t depth, |
| svn_boolean_t ignore_ancestry, |
| const apr_array_header_t *changelists, |
| svn_client_diff_summarize_func_t summarize_func, |
| void *summarize_baton, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| svn_diff_tree_processor_t *diff_processor; |
| |
| SVN_ERR(svn_client__get_diff_summarize_callbacks(&diff_processor, |
| summarize_func, summarize_baton, |
| pool, pool)); |
| |
| return svn_error_trace(do_diff(NULL, |
| path_or_url, path_or_url, |
| start_revision, end_revision, |
| peg_revision, FALSE /* no_peg_revision */, |
| depth, ignore_ancestry, changelists, |
| FALSE /* text_deltas */, |
| diff_processor, ctx, pool, pool)); |
| } |
| |