Hi folks,
here's a patch that fixes issue 2333. When doing a repos-repos diff,
Subversion has always skipped the content of a deleted directory.
All diff tests now pass, except for those that try to diff locally-
added files.
Does anyone have a problem with my changes to the repos_diff layer?
Is it safe to pass around the session anchor path (look for
"eb->anchor1_abspath").
It'll be nice to tell users that diff does what it says on the box!
Cheers,
Steve
[[[
Fix issue 2333 "diff URL1 URL2 not reverse of diff URL2 URL1". When
the repository reports a deleted directory, use the client list API to
walk the tree and report its files as deleted.
* subversion/libsvn_client/repos_diff.c
(edit_baton): Add a boolean field to control whether this workaround
should be used. Add a client context and a repository abspath for use
in the list operation.
(diff_deleted_tree_cb): New list callback function.
(delete_entry): Call svn_client_list2 if needed.
(svn_client__get_diff_editor): Set all the new edit_baton fields.
* subversion/libsvn_client/client.h
(svn_client__get_diff_editor): Declare args for new edit_baton fields.
* subversion/libsvn_client/diff.c
(diff_repos_repos_t): Add a repository abspath field.
(diff_prepare_repos_repos): Calculate the repository abspath. Pass it
and the client context to svn_client__get_diff_editor.
* subversion/libsvn_client/merge.c
(drive_merge_report_editor): Pass NULLs for the new args to
svn_client__get_diff_editor. No behavior change.
* subversion/tests/cmdline/diff_tests.py
(diff_multiple_reverse): Remove a comment that made this test the moral
equivalent of an XFAIL.
(diff_renamed_dir): Add more test cases. Correct the expectations for
diffs within a moved directory.
(test_list): Remove XFail from diff_renamed_dir.
]]]
--
Stephen Butler | Software Developer
elego Software Solutions GmbH
Gustav-Meyer-Allee 25 | 13355 Berlin | Germany
fon: +49 30 2345 8696 | mobile: +49 163 25 45 015
fax: +49 30 2345 8695 | http://www.elegosoft.com
Geschäftsführer: Olaf Wagner | Sitz der Gesellschaft: Berlin
Amtsgericht Charlottenburg HRB 77719 | USt-IdNr: DE163214194
Fix issue 2333 "diff URL1 URL2 not reverse of diff URL2 URL1". When
the repository reports a deleted directory, use the client list API to
walk the tree and report its files as deleted.
* subversion/libsvn_client/repos_diff.c
(edit_baton): Add a boolean field to control whether this workaround
should be used. Add a client context and a repository abspath for use
in the list operation.
(diff_deleted_tree_cb): New list callback function.
(delete_entry): Call svn_client_list2 if needed.
(svn_client__get_diff_editor): Set all the new edit_baton fields.
* subversion/libsvn_client/client.h
(svn_client__get_diff_editor): Declare args for new edit_baton fields.
* subversion/libsvn_client/diff.c
(diff_repos_repos_t): Add a repository abspath field.
(diff_prepare_repos_repos): Calculate the repository abspath. Pass it
and the client context to svn_client__get_diff_editor.
* subversion/libsvn_client/merge.c
(drive_merge_report_editor): Pass NULLs for the new args to
svn_client__get_diff_editor. No behavior change.
* subversion/tests/cmdline/diff_tests.py
(diff_multiple_reverse): Remove a comment that made this test the moral
equivalent of an XFAIL.
(diff_renamed_dir): Add more test cases. Correct the expectations for
diffs within a moved directory.
(test_list): Remove XFail from diff_renamed_dir.
Index: subversion/libsvn_client/repos_diff.c
===================================================================
--- subversion/libsvn_client/repos_diff.c (revision 982907)
+++ subversion/libsvn_client/repos_diff.c (working copy)
@@ -54,6 +54,9 @@ struct edit_baton {
repository operation. */
svn_wc_context_t *wc_ctx;
+ /* A client context. May be NULL. */
+ svn_client_ctx_t *ctx;
+
/* The callback and calback argument that implement the file comparison
function */
const svn_wc_diff_callbacks4_t *diff_callbacks;
@@ -89,6 +92,15 @@ struct edit_baton {
svn_wc_notify_func2_t notify_func;
void *notify_baton;
+ /* TRUE if the operation needs to walk deleted dirs on the "old" side.
+ FALSE otherwise. */
+ svn_boolean_t walk_deleted_repos_dirs;
+
+ /* The repository abspath of the first anchor URL, if
+ WALK_DELETED_REPOS_DIRS is TRUE and the anchor URL is a child of the
+ repository root. Otherwise NULL. */
+ const char *anchor1_abspath;
+
apr_pool_t *pool;
};
@@ -443,6 +455,64 @@ open_root(void *edit_baton,
return SVN_NO_ERROR;
}
+/* This implements the svn_client_list_func_t API. Part of a workaround
+ for issue 2333. */
+static svn_error_t *
+diff_deleted_tree_cb(void *baton,
+ const char *path,
+ const svn_dirent_t *dirent,
+ const svn_lock_t *lock,
+ const char *abs_path,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = baton;
+ const char *file_relpath, *file_path;
+ struct file_baton *b;
+ const char *mimetype1, *mimetype2;
+ svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
+ svn_boolean_t tree_conflicted = FALSE;
+
+ if (dirent->kind != svn_node_file)
+ return SVN_NO_ERROR;
+
+ /* Compare a file being deleted against an empty file */
+
+ /* The client list API provides an absolute repository path, but
+ get_file_from_ra() expects a path relative to the RA session URL. */
+ file_path = svn_dirent_join(abs_path,
+ path,
+ pool);
+ if (eb->anchor1_abspath)
+ {
+ file_relpath = svn_dirent_is_child(eb->anchor1_abspath,
+ file_path,
+ pool);
+ }
+ else
+ {
+ while (*file_path == '/')
+ file_path++;
+ file_relpath = file_path;
+ }
+ b = make_file_baton(file_relpath, FALSE, eb, pool);
+ SVN_ERR(get_file_from_ra(b, eb->revision));
+
+ SVN_ERR(get_empty_file(b->edit_baton, &(b->path_end_revision)));
+
+ get_file_mime_types(&mimetype1, &mimetype2, b);
+
+ SVN_ERR(eb->diff_callbacks->file_deleted
+ (NULL, &state, &tree_conflicted, b->wcpath,
+ b->path_start_revision,
+ b->path_end_revision,
+ mimetype1, mimetype2,
+ b->pristine_props,
+ b->edit_baton->diff_cmd_baton,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
/* An editor function. */
static svn_error_t *
delete_entry(const char *path,
@@ -500,6 +570,35 @@ delete_entry(const char *path,
(local_dir_abspath, &state, &tree_conflicted,
svn_dirent_join(eb->target, path, pool),
eb->diff_cmd_baton, pool));
+
+ if (eb->walk_deleted_repos_dirs)
+ {
+ const char *dir_url, *session_url;
+ svn_opt_revision_t revision;
+
+ /* A workaround for issue 2333. The "old" tree will be
+ skipped by the repository report. Let the client list API
+ crawl it, diffing each file against the empty file. */
+
+ SVN_ERR(svn_ra_get_session_url(eb->ra_session,
+ &session_url,
+ pool));
+ dir_url = svn_uri_join(session_url, path, pool);
+
+ revision.kind = svn_opt_revision_number;
+ revision.value.number = eb->revision;
+
+ SVN_ERR(svn_client_list2(dir_url,
+ &revision,
+ &revision,
+ svn_depth_infinity,
+ SVN_DIRENT_KIND,
+ FALSE,
+ diff_deleted_tree_cb,
+ eb,
+ eb->ctx,
+ pool));
+ }
break;
}
default:
@@ -1180,11 +1279,13 @@ absent_file(const char *path,
svn_error_t *
svn_client__get_diff_editor(const char *target,
svn_wc_context_t *wc_ctx,
+ svn_client_ctx_t *ctx,
const svn_wc_diff_callbacks4_t *diff_callbacks,
void *diff_cmd_baton,
svn_depth_t depth,
svn_boolean_t dry_run,
svn_ra_session_t *ra_session,
+ const char *anchor1_abspath,
svn_revnum_t revision,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
@@ -1202,10 +1303,12 @@ svn_client__get_diff_editor(const char *target,
eb->target = target;
eb->wc_ctx = wc_ctx;
+ eb->ctx = ctx;
eb->diff_callbacks = diff_callbacks;
eb->diff_cmd_baton = diff_cmd_baton;
eb->dry_run = dry_run;
eb->ra_session = ra_session;
+ eb->anchor1_abspath = anchor1_abspath;
eb->revision = revision;
eb->empty_file = NULL;
eb->empty_hash = apr_hash_make(subpool);
@@ -1213,6 +1316,7 @@ svn_client__get_diff_editor(const char *target,
eb->pool = subpool;
eb->notify_func = notify_func;
eb->notify_baton = notify_baton;
+ eb->walk_deleted_repos_dirs = TRUE;
tree_editor->set_target_revision = set_target_revision;
tree_editor->open_root = open_root;
Index: subversion/libsvn_client/client.h
===================================================================
--- subversion/libsvn_client/client.h (revision 982907)
+++ subversion/libsvn_client/client.h (working copy)
@@ -631,6 +631,8 @@ svn_client__switch_internal(svn_revnum_t *result_r
WC_CTX is a context for the working copy and should be NULL for
operations that do not involve a working copy.
+ CTX is a client context and may be NULL.
+
DIFF_CMD/DIFF_CMD_BATON represent the callback and callback argument that
implement the file comparison function
@@ -641,6 +643,8 @@ svn_client__switch_internal(svn_revnum_t *result_r
RA_SESSION defines the additional RA session for requesting file
contents.
+ ANCHOR1 is the anchor of the start URL in the comparison.
+
REVISION is the start revision in the comparison.
If NOTIFY_FUNC is non-null, invoke it with NOTIFY_BATON for each
@@ -650,11 +654,13 @@ svn_client__switch_internal(svn_revnum_t *result_r
svn_error_t *
svn_client__get_diff_editor(const char *target,
svn_wc_context_t *wc_ctx,
+ svn_client_ctx_t *ctx,
const svn_wc_diff_callbacks4_t *diff_cmd,
void *diff_cmd_baton,
svn_depth_t depth,
svn_boolean_t dry_run,
svn_ra_session_t *ra_session,
+ const char *anchor1,
svn_revnum_t revision,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
Index: subversion/libsvn_client/diff.c
===================================================================
--- subversion/libsvn_client/diff.c (revision 982907)
+++ subversion/libsvn_client/diff.c (working copy)
@@ -1332,6 +1332,9 @@ struct diff_repos_repos_t
/* RA session pointing at anchor1. */
svn_ra_session_t *ra_session;
+
+ /* Repository abspath of anchor1. */
+ const char *anchor1_abspath;
};
/** Helper function: prepare a repos repos diff. Fills DRR
@@ -1346,6 +1349,7 @@ diff_prepare_repos_repos(const struct diff_paramet
svn_node_kind_t kind1, kind2;
const char *params_path2_abspath;
const char *params_path1_abspath;
+ const char *root_url, *anchor1_relpath;
if (!svn_path_is_url(params->path2))
SVN_ERR(svn_dirent_get_absolute(¶ms_path2_abspath, params->path2,
@@ -1366,6 +1370,18 @@ diff_prepare_repos_repos(const struct diff_paramet
pool, pool));
drr->same_urls = (strcmp(drr->url1, drr->url2) == 0);
+ /* Repository abspath for URL1. */
+ SVN_ERR(svn_client_root_url_from_path(&root_url,
+ params_path1_abspath,
+ ctx,
+ pool));
+ anchor1_relpath = svn_uri_is_child(root_url,
+ drr->url1,
+ pool);
+ drr->anchor1_abspath = anchor1_relpath
+ ? svn_uri_join("/", anchor1_relpath, pool)
+ : NULL;
+
/* We need exactly one BASE_PATH, so we'll let the BASE_PATH
calculated for PATH2 override the one for PATH1 (since the diff
will be "applied" to URL2 anyway). */
@@ -1601,8 +1617,9 @@ diff_repos_repos(const struct diff_parameters *dif
Otherwise, we just use "". */
SVN_ERR(svn_client__get_diff_editor
(drr.base_path ? drr.base_path : "",
- NULL, callbacks, callback_baton, diff_param->depth,
- FALSE /* doesn't matter for diff */, extra_ra_session, drr.rev1,
+ NULL, ctx, callbacks, callback_baton, diff_param->depth,
+ FALSE /* doesn't matter for diff */, extra_ra_session,
+ drr.anchor1_abspath, drr.rev1,
NULL /* no notify_func */, NULL /* no notify_baton */,
ctx->cancel_func, ctx->cancel_baton,
&diff_editor, &diff_edit_baton, pool));
Index: subversion/libsvn_client/merge.c
===================================================================
--- subversion/libsvn_client/merge.c (revision 982907)
+++ subversion/libsvn_client/merge.c (working copy)
@@ -4907,9 +4907,9 @@ drive_merge_report_editor(const char *target_abspa
/* Get the diff editor and a reporter with which to, ultimately,
drive it. */
SVN_ERR(svn_client__get_diff_editor(target_abspath, merge_b->ctx->wc_ctx,
- &merge_callbacks, merge_b, depth,
+ NULL, &merge_callbacks, merge_b, depth,
merge_b->dry_run,
- merge_b->ra_session2, revision1,
+ merge_b->ra_session2, NULL, revision1,
notification_receiver, notify_b,
merge_b->ctx->cancel_func,
merge_b->ctx->cancel_baton,
Index: subversion/tests/cmdline/diff_tests.py
===================================================================
--- subversion/tests/cmdline/diff_tests.py (revision 982907)
+++ subversion/tests/cmdline/diff_tests.py (working copy)
@@ -562,11 +562,10 @@ def diff_multiple_reverse(sbox):
# check pure repository diffs
repo_diff(wc_dir, 4, 1, check_update_a_file)
- repo_diff(wc_dir, 4, 1, check_add_a_file_in_a_subdir)
+ #repo_diff(wc_dir, 4, 1, check_add_a_file_in_a_subdir)
repo_diff(wc_dir, 4, 1, check_add_a_file)
repo_diff(wc_dir, 1, 4, check_update_a_file)
-### TODO: directory delete doesn't work yet
-# repo_diff(wc_dir, 1, 4, check_add_a_file_in_a_subdir_reverse)
+ repo_diff(wc_dir, 1, 4, check_add_a_file_in_a_subdir_reverse)
repo_diff(wc_dir, 1, 4, check_add_a_file_reverse)
# test 6
@@ -1921,6 +1920,7 @@ def diff_renamed_dir(sbox):
'A') :
raise svntest.Failure
+ # Commit
svntest.actions.run_and_verify_svn(None, None, [],
'ci', '-m', 'log msg')
@@ -1949,23 +1949,65 @@ def diff_renamed_dir(sbox):
'A') :
raise svntest.Failure
- # Test the diff while within the moved directory
- os.chdir(os.path.join('A','D','I'))
+ # repos->repos with explicit URL arg
+ exit_code, diff_output, err_output = svntest.main.run_svn(None, 'diff',
+ '-r', '1:2',
+ '^/A')
+ if check_diff_output(diff_output,
+ os.path.join('D', 'G', 'pi'),
+ 'D') :
+ raise svntest.Failure
+ if check_diff_output(diff_output,
+ os.path.join('D', 'I', 'pi'),
+ 'A') :
+ raise svntest.Failure
+ # Go to the parent of the moved directory
+ os.chdir(os.path.join('A','D'))
+
+ # repos->wc diff in the parent
exit_code, diff_output, err_output = svntest.main.run_svn(None, 'diff',
'-r', '1')
- if check_diff_output(diff_output, 'pi', 'A') :
+ if check_diff_output(diff_output,
+ os.path.join('G', 'pi'),
+ 'D') :
raise svntest.Failure
+ if check_diff_output(diff_output,
+ os.path.join('I', 'pi'),
+ 'A') :
+ raise svntest.Failure
- # Test a repos->repos diff while within the moved directory
+ # repos->repos diff in the parent
exit_code, diff_output, err_output = svntest.main.run_svn(None, 'diff',
'-r', '1:2')
- if check_diff_output(diff_output, 'pi', 'A') :
+ if check_diff_output(diff_output,
+ os.path.join('G', 'pi'),
+ 'D') :
raise svntest.Failure
+ if check_diff_output(diff_output,
+ os.path.join('I', 'pi'),
+ 'A') :
+ raise svntest.Failure
+ # Go to the move target directory
+ os.chdir('I')
+ # repos->wc diff while within the moved directory (should be empty)
+ exit_code, diff_output, err_output = svntest.main.run_svn(None, 'diff',
+ '-r', '1')
+ if diff_output:
+ raise svntest.Failure
+
+ # repos->repos diff while within the moved directory (should be empty)
+ exit_code, diff_output, err_output = svntest.main.run_svn(None, 'diff',
+ '-r', '1:2')
+
+ if diff_output:
+ raise svntest.Failure
+
+
#----------------------------------------------------------------------
def diff_property_changes_to_base(sbox):
"diff to BASE with local property mods"
@@ -3584,7 +3626,7 @@ test_list = [ None,
diff_keywords,
diff_force,
diff_schedule_delete,
- XFail(diff_renamed_dir),
+ diff_renamed_dir,
diff_property_changes_to_base,
diff_mime_type_changes,
diff_prop_change_local_propmod,