Deleted Added
full compact
diff_local.c (253734) diff_local.c (289180)
1/*
2 * diff_local.c: comparing local trees with each other
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file

--- 31 unchanged lines hidden (view full) ---

40#include "svn_dirent_uri.h"
41#include "svn_io.h"
42#include "svn_pools.h"
43#include "svn_props.h"
44#include "svn_sorts.h"
45#include "svn_subst.h"
46#include "client.h"
47
1/*
2 * diff_local.c: comparing local trees with each other
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file

--- 31 unchanged lines hidden (view full) ---

40#include "svn_dirent_uri.h"
41#include "svn_io.h"
42#include "svn_pools.h"
43#include "svn_props.h"
44#include "svn_sorts.h"
45#include "svn_subst.h"
46#include "client.h"
47
48#include "private/svn_sorts_private.h"
48#include "private/svn_wc_private.h"
49#include "private/svn_wc_private.h"
50#include "private/svn_diff_tree.h"
49
50#include "svn_private_config.h"
51
52
53/* Try to get properties for LOCAL_ABSPATH and return them in the property
54 * hash *PROPS. If there are no properties because LOCAL_ABSPATH is not
55 * versioned, return an empty property hash. */
56static svn_error_t *

--- 19 unchanged lines hidden (view full) ---

76 }
77 else
78 return svn_error_trace(err);
79 }
80
81 return SVN_NO_ERROR;
82}
83
51
52#include "svn_private_config.h"
53
54
55/* Try to get properties for LOCAL_ABSPATH and return them in the property
56 * hash *PROPS. If there are no properties because LOCAL_ABSPATH is not
57 * versioned, return an empty property hash. */
58static svn_error_t *

--- 19 unchanged lines hidden (view full) ---

78 }
79 else
80 return svn_error_trace(err);
81 }
82
83 return SVN_NO_ERROR;
84}
85
84/* Produce a diff between two arbitrary files at LOCAL_ABSPATH1 and
85 * LOCAL_ABSPATH2, using the diff callbacks from CALLBACKS.
86 * Use PATH as the name passed to diff callbacks.
87 * FILE1_IS_EMPTY and FILE2_IS_EMPTY are used as hints which diff callback
88 * function to use to compare the files (added/deleted/changed).
86/* Forward declaration */
87static svn_error_t *
88do_file_diff(const char *left_abspath,
89 const char *right_abspath,
90 const char *left_root_abspath,
91 const char *right_root_abspath,
92 svn_boolean_t left_only,
93 svn_boolean_t right_only,
94 void *parent_baton,
95 const svn_diff_tree_processor_t *diff_processor,
96 svn_client_ctx_t *ctx,
97 apr_pool_t *scratch_pool);
98
99/* Forward declaration */
100static svn_error_t *
101do_dir_diff(const char *left_abspath,
102 const char *right_abspath,
103 const char *left_root_abspath,
104 const char *right_root_abspath,
105 svn_boolean_t left_only,
106 svn_boolean_t right_only,
107 svn_boolean_t left_before_right,
108 svn_depth_t depth,
109 void *parent_baton,
110 const svn_diff_tree_processor_t *diff_processor,
111 svn_client_ctx_t *ctx,
112 apr_pool_t *scratch_pool);
113
114/* Produce a diff of depth DEPTH between two arbitrary directories at
115 * LEFT_ABSPATH1 and RIGHT_ABSPATH2, using the provided diff callbacks
116 * to show file changes and, for versioned nodes, property changes.
89 *
117 *
90 * If ORIGINAL_PROPS_OVERRIDE is not NULL, use it as original properties
91 * instead of reading properties from LOCAL_ABSPATH1. This is required when
92 * a file replaces a directory, where LOCAL_ABSPATH1 is an empty file that
93 * file content must be diffed against, but properties to diff against come
94 * from the replaced directory. */
118 * Report paths as relative from LEFT_ROOT_ABSPATH/RIGHT_ROOT_ABSPATH.
119 *
120 * If LEFT_ONLY is TRUE, only the left source exists (= everything will
121 * be reported as deleted). If RIGHT_ONLY is TRUE, only the right source
122 * exists (= everything will be reported as added).
123 *
124 * If LEFT_BEFORE_RIGHT is TRUE and left and right are unrelated, left is
125 * reported first. If false, right is reported first. (This is to allow
126 * producing a proper inverse diff).
127 *
128 * Walk the sources according to depth, and report with parent baton
129 * PARENT_BATON. */
95static svn_error_t *
130static svn_error_t *
96do_arbitrary_files_diff(const char *local_abspath1,
97 const char *local_abspath2,
98 const char *path,
99 svn_boolean_t file1_is_empty,
100 svn_boolean_t file2_is_empty,
101 apr_hash_t *original_props_override,
102 const svn_wc_diff_callbacks4_t *callbacks,
103 void *diff_baton,
104 svn_client_ctx_t *ctx,
105 apr_pool_t *scratch_pool)
131inner_dir_diff(const char *left_abspath,
132 const char *right_abspath,
133 const char *left_root_abspath,
134 const char *right_root_abspath,
135 svn_boolean_t left_only,
136 svn_boolean_t right_only,
137 svn_boolean_t left_before_right,
138 svn_depth_t depth,
139 void *parent_baton,
140 const svn_diff_tree_processor_t *diff_processor,
141 svn_client_ctx_t *ctx,
142 apr_pool_t *scratch_pool)
106{
143{
107 apr_hash_t *original_props;
108 apr_hash_t *modified_props;
109 apr_array_header_t *prop_changes;
110 svn_string_t *original_mime_type = NULL;
111 svn_string_t *modified_mime_type = NULL;
144 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
145 apr_hash_t *left_dirents;
146 apr_hash_t *right_dirents;
147 apr_array_header_t *sorted_dirents;
148 svn_error_t *err;
149 svn_depth_t depth_below_here;
150 int i;
112
151
113 if (ctx->cancel_func)
114 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
152 SVN_ERR_ASSERT(depth >= svn_depth_files && depth <= svn_depth_infinity);
115
153
116 /* Try to get properties from either file. It's OK if the files do not
117 * have properties, or if they are unversioned. */
118 if (original_props_override)
119 original_props = original_props_override;
120 else
121 SVN_ERR(get_props(&original_props, local_abspath1, ctx->wc_ctx,
122 scratch_pool, scratch_pool));
123 SVN_ERR(get_props(&modified_props, local_abspath2, ctx->wc_ctx,
124 scratch_pool, scratch_pool));
125
126 SVN_ERR(svn_prop_diffs(&prop_changes, modified_props, original_props,
127 scratch_pool));
128
129 /* Try to determine the mime-type of each file. */
130 original_mime_type = svn_hash_gets(original_props, SVN_PROP_MIME_TYPE);
131 if (!file1_is_empty && !original_mime_type)
154 if (!right_only)
132 {
155 {
133 const char *mime_type;
134 SVN_ERR(svn_io_detect_mimetype2(&mime_type, local_abspath1,
135 ctx->mimetypes_map, scratch_pool));
156 err = svn_io_get_dirents3(&left_dirents, left_abspath, FALSE,
157 scratch_pool, iterpool);
136
158
137 if (mime_type)
138 original_mime_type = svn_string_create(mime_type, scratch_pool);
159 if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
160 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
161 {
162 svn_error_clear(err);
163 left_dirents = apr_hash_make(scratch_pool);
164 right_only = TRUE;
165 }
166 else
167 SVN_ERR(err);
139 }
168 }
169 else
170 left_dirents = apr_hash_make(scratch_pool);
140
171
141 modified_mime_type = svn_hash_gets(modified_props, SVN_PROP_MIME_TYPE);
142 if (!file2_is_empty && !modified_mime_type)
172 if (!left_only)
143 {
173 {
144 const char *mime_type;
145 SVN_ERR(svn_io_detect_mimetype2(&mime_type, local_abspath1,
146 ctx->mimetypes_map, scratch_pool));
174 err = svn_io_get_dirents3(&right_dirents, right_abspath, FALSE,
175 scratch_pool, iterpool);
147
176
148 if (mime_type)
149 modified_mime_type = svn_string_create(mime_type, scratch_pool);
177 if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
178 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
179 {
180 svn_error_clear(err);
181 right_dirents = apr_hash_make(scratch_pool);
182 right_only = TRUE;
183 }
184 else
185 SVN_ERR(err);
150 }
186 }
187 else
188 right_dirents = apr_hash_make(scratch_pool);
151
189
152 /* Produce the diff. */
153 if (file1_is_empty && !file2_is_empty)
154 SVN_ERR(callbacks->file_added(NULL, NULL, NULL, path,
155 local_abspath1, local_abspath2,
156 /* ### TODO get real revision info
157 * for versioned files? */
158 SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
159 original_mime_type ?
160 original_mime_type->data : NULL,
161 modified_mime_type ?
162 modified_mime_type->data : NULL,
163 /* ### TODO get copyfrom? */
164 NULL, SVN_INVALID_REVNUM,
165 prop_changes, original_props,
166 diff_baton, scratch_pool));
167 else if (!file1_is_empty && file2_is_empty)
168 SVN_ERR(callbacks->file_deleted(NULL, NULL, path,
169 local_abspath1, local_abspath2,
170 original_mime_type ?
171 original_mime_type->data : NULL,
172 modified_mime_type ?
173 modified_mime_type->data : NULL,
174 original_props,
175 diff_baton, scratch_pool));
190 if (left_only && right_only)
191 return SVN_NO_ERROR; /* Somebody deleted the directory?? */
192
193 if (depth != svn_depth_infinity)
194 depth_below_here = svn_depth_empty;
176 else
195 else
196 depth_below_here = svn_depth_infinity;
197
198 sorted_dirents = svn_sort__hash(apr_hash_merge(iterpool, left_dirents,
199 right_dirents, NULL, NULL),
200 svn_sort_compare_items_as_paths,
201 scratch_pool);
202
203 for (i = 0; i < sorted_dirents->nelts; i++)
177 {
204 {
178 svn_stream_t *file1;
179 svn_stream_t *file2;
180 svn_boolean_t same;
181 svn_string_t *val;
182 /* We have two files, which may or may not be the same.
205 svn_sort__item_t* elt = &APR_ARRAY_IDX(sorted_dirents, i, svn_sort__item_t);
206 svn_io_dirent2_t *left_dirent;
207 svn_io_dirent2_t *right_dirent;
208 const char *child_left_abspath;
209 const char *child_right_abspath;
183
210
184 ### Our caller assumes that we should ignore symlinks here and
185 handle them as normal paths. Perhaps that should change?
186 */
187 SVN_ERR(svn_stream_open_readonly(&file1, local_abspath1, scratch_pool,
188 scratch_pool));
211 svn_pool_clear(iterpool);
189
212
190 SVN_ERR(svn_stream_open_readonly(&file2, local_abspath2, scratch_pool,
191 scratch_pool));
213 if (ctx->cancel_func)
214 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
192
215
193 /* Wrap with normalization, etc. if necessary */
194 if (original_props)
195 {
196 val = svn_hash_gets(original_props, SVN_PROP_EOL_STYLE);
216 if (svn_wc_is_adm_dir(elt->key, iterpool))
217 continue;
197
218
198 if (val)
199 {
200 svn_subst_eol_style_t style;
201 const char *eol;
202 svn_subst_eol_style_from_value(&style, &eol, val->data);
219 left_dirent = right_only ? NULL : svn_hash_gets(left_dirents, elt->key);
220 right_dirent = left_only ? NULL : svn_hash_gets(right_dirents, elt->key);
203
221
204 /* ### Ignoring keywords */
205 if (eol)
206 file1 = svn_subst_stream_translated(file1, eol, TRUE,
207 NULL, FALSE,
208 scratch_pool);
209 }
210 }
222 child_left_abspath = svn_dirent_join(left_abspath, elt->key, iterpool);
223 child_right_abspath = svn_dirent_join(right_abspath, elt->key, iterpool);
211
224
212 if (modified_props)
225 if (((left_dirent == NULL) != (right_dirent == NULL))
226 || (left_dirent->kind != right_dirent->kind))
213 {
227 {
214 val = svn_hash_gets(modified_props, SVN_PROP_EOL_STYLE);
228 /* Report delete and/or add */
229 if (left_dirent && left_before_right)
230 {
231 if (left_dirent->kind == svn_node_file)
232 SVN_ERR(do_file_diff(child_left_abspath, child_right_abspath,
233 left_root_abspath, right_root_abspath,
234 TRUE, FALSE, parent_baton,
235 diff_processor, ctx, iterpool));
236 else if (depth >= svn_depth_immediates)
237 SVN_ERR(do_dir_diff(child_left_abspath, child_right_abspath,
238 left_root_abspath, right_root_abspath,
239 TRUE, FALSE, left_before_right,
240 depth_below_here, parent_baton,
241 diff_processor, ctx, iterpool));
242 }
215
243
216 if (val)
244 if (right_dirent)
217 {
245 {
218 svn_subst_eol_style_t style;
219 const char *eol;
220 svn_subst_eol_style_from_value(&style, &eol, val->data);
246 if (right_dirent->kind == svn_node_file)
247 SVN_ERR(do_file_diff(child_left_abspath, child_right_abspath,
248 left_root_abspath, right_root_abspath,
249 FALSE, TRUE, parent_baton,
250 diff_processor, ctx, iterpool));
251 else if (depth >= svn_depth_immediates)
252 SVN_ERR(do_dir_diff(child_left_abspath, child_right_abspath,
253 left_root_abspath, right_root_abspath,
254 FALSE, TRUE, left_before_right,
255 depth_below_here, parent_baton,
256 diff_processor, ctx, iterpool));
257 }
221
258
222 /* ### Ignoring keywords */
223 if (eol)
224 file2 = svn_subst_stream_translated(file2, eol, TRUE,
225 NULL, FALSE,
226 scratch_pool);
259 if (left_dirent && !left_before_right)
260 {
261 if (left_dirent->kind == svn_node_file)
262 SVN_ERR(do_file_diff(child_left_abspath, child_right_abspath,
263 left_root_abspath, right_root_abspath,
264 TRUE, FALSE, parent_baton,
265 diff_processor, ctx, iterpool));
266 else if (depth >= svn_depth_immediates)
267 SVN_ERR(do_dir_diff(child_left_abspath, child_right_abspath,
268 left_root_abspath, right_root_abspath,
269 TRUE, FALSE, left_before_right,
270 depth_below_here, parent_baton,
271 diff_processor, ctx, iterpool));
227 }
228 }
272 }
273 }
229
230 SVN_ERR(svn_stream_contents_same2(&same, file1, file2, scratch_pool));
231
232 if (! same || prop_changes->nelts > 0)
274 else if (left_dirent->kind == svn_node_file)
233 {
275 {
234 /* ### We should probably pass the normalized data we created using
235 the subst streams as that is what diff users expect */
236 SVN_ERR(callbacks->file_changed(NULL, NULL, NULL, path,
237 same ? NULL : local_abspath1,
238 same ? NULL : local_abspath2,
239 /* ### TODO get real revision info
240 * for versioned files? */
241 SVN_INVALID_REVNUM /* rev1 */,
242 SVN_INVALID_REVNUM /* rev2 */,
243 original_mime_type ?
244 original_mime_type->data : NULL,
245 modified_mime_type ?
246 modified_mime_type->data : NULL,
247 prop_changes, original_props,
248 diff_baton, scratch_pool));
276 /* Perform file-file diff */
277 SVN_ERR(do_file_diff(child_left_abspath, child_right_abspath,
278 left_root_abspath, right_root_abspath,
279 FALSE, FALSE, parent_baton,
280 diff_processor, ctx, iterpool));
249 }
281 }
282 else if (depth >= svn_depth_immediates)
283 {
284 /* Perform dir-dir diff */
285 SVN_ERR(do_dir_diff(child_left_abspath, child_right_abspath,
286 left_root_abspath, right_root_abspath,
287 FALSE, FALSE, left_before_right,
288 depth_below_here, parent_baton,
289 diff_processor, ctx, iterpool));
290 }
250 }
251
252 return SVN_NO_ERROR;
253}
254
291 }
292
293 return SVN_NO_ERROR;
294}
295
255struct arbitrary_diff_walker_baton {
256 /* The root directories of the trees being compared. */
257 const char *root1_abspath;
258 const char *root2_abspath;
296/* Translates *LEFT_ABSPATH to a temporary file if PROPS specify that the
297 file needs translation. *LEFT_ABSPATH is updated to point to a file that
298 lives at least as long as RESULT_POOL when translation is necessary.
299 Otherwise the value is not updated */
300static svn_error_t *
301translate_if_necessary(const char **local_abspath,
302 apr_hash_t *props,
303 svn_cancel_func_t cancel_func,
304 void *cancel_baton,
305 apr_pool_t *result_pool,
306 apr_pool_t *scratch_pool)
307{
308 const svn_string_t *eol_style_val;
309 const svn_string_t *keywords_val;
310 svn_subst_eol_style_t eol_style;
311 const char *eol;
312 apr_hash_t *keywords;
313 svn_stream_t *contents;
314 svn_stream_t *dst;
259
315
260 /* TRUE if recursing within an added subtree of root2_abspath that
261 * does not exist in root1_abspath. */
262 svn_boolean_t recursing_within_added_subtree;
316 /* if (svn_hash_gets(props, SVN_PROP_SPECIAL))
317 ### TODO: Implement */
263
318
264 /* TRUE if recursing within an administrative (.i.e. .svn) directory. */
265 svn_boolean_t recursing_within_adm_dir;
319 eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
320 keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS);
266
321
267 /* The absolute path of the adm dir if RECURSING_WITHIN_ADM_DIR is TRUE.
268 * Else this is NULL.*/
269 const char *adm_dir_abspath;
322 if (eol_style_val)
323 svn_subst_eol_style_from_value(&eol_style, &eol, eol_style_val->data);
324 else
325 {
326 eol = NULL;
327 eol_style = svn_subst_eol_style_none;
328 }
270
329
271 /* A path to an empty file used for diffs that add/delete files. */
272 const char *empty_file_abspath;
330 if (keywords_val)
331 SVN_ERR(svn_subst_build_keywords3(&keywords, keywords_val->data,
332 APR_STRINGIFY(SVN_INVALID_REVNUM),
333 "", "", 0, "", scratch_pool));
334 else
335 keywords = NULL;
273
336
274 const svn_wc_diff_callbacks4_t *callbacks;
275 void *diff_baton;
276 svn_client_ctx_t *ctx;
277 apr_pool_t *pool;
278} arbitrary_diff_walker_baton;
337 if (!svn_subst_translation_required(eol_style, eol, keywords, FALSE, FALSE))
338 return SVN_NO_ERROR;
279
339
280/* Forward declaration needed because this function has a cyclic
281 * dependency with do_arbitrary_dirs_diff(). */
282static svn_error_t *
283arbitrary_diff_walker(void *baton, const char *local_abspath,
284 const apr_finfo_t *finfo,
285 apr_pool_t *scratch_pool);
340 SVN_ERR(svn_stream_open_readonly(&contents, *local_abspath,
341 scratch_pool, scratch_pool));
286
342
287/* Another forward declaration. */
288static svn_error_t *
289arbitrary_diff_this_dir(struct arbitrary_diff_walker_baton *b,
290 const char *local_abspath,
291 svn_depth_t depth,
292 apr_pool_t *scratch_pool);
343 SVN_ERR(svn_stream_open_unique(&dst, local_abspath, NULL,
344 svn_io_file_del_on_pool_cleanup,
345 result_pool, scratch_pool));
293
346
294/* Produce a diff of depth DEPTH between two arbitrary directories at
295 * LOCAL_ABSPATH1 and LOCAL_ABSPATH2, using the provided diff callbacks
296 * to show file changes and, for versioned nodes, property changes.
297 *
298 * If ROOT_ABSPATH1 and ROOT_ABSPATH2 are not NULL, show paths in diffs
299 * relative to these roots, rather than relative to LOCAL_ABSPATH1 and
300 * LOCAL_ABSPATH2. This is needed when crawling a subtree that exists
301 * only within LOCAL_ABSPATH2. */
302static svn_error_t *
303do_arbitrary_dirs_diff(const char *local_abspath1,
304 const char *local_abspath2,
305 const char *root_abspath1,
306 const char *root_abspath2,
307 svn_depth_t depth,
308 const svn_wc_diff_callbacks4_t *callbacks,
309 void *diff_baton,
310 svn_client_ctx_t *ctx,
311 apr_pool_t *scratch_pool)
312{
313 apr_file_t *empty_file;
314 svn_node_kind_t kind1;
347 dst = svn_subst_stream_translated(dst, eol, TRUE /* repair */,
348 keywords, FALSE /* expand */,
349 scratch_pool);
315
350
316 struct arbitrary_diff_walker_baton b;
351 SVN_ERR(svn_stream_copy3(contents, dst, cancel_func, cancel_baton,
352 scratch_pool));
317
353
318 /* If LOCAL_ABSPATH1 is not a directory, crawl LOCAL_ABSPATH2 instead
319 * and compare it to LOCAL_ABSPATH1, showing only additions.
320 * This case can only happen during recursion from arbitrary_diff_walker(),
321 * because do_arbitrary_nodes_diff() prevents this from happening at
322 * the root of the comparison. */
323 SVN_ERR(svn_io_check_resolved_path(local_abspath1, &kind1, scratch_pool));
324 b.recursing_within_added_subtree = (kind1 != svn_node_dir);
325
326 b.root1_abspath = root_abspath1 ? root_abspath1 : local_abspath1;
327 b.root2_abspath = root_abspath2 ? root_abspath2 : local_abspath2;
328 b.recursing_within_adm_dir = FALSE;
329 b.adm_dir_abspath = NULL;
330 b.callbacks = callbacks;
331 b.diff_baton = diff_baton;
332 b.ctx = ctx;
333 b.pool = scratch_pool;
334
335 SVN_ERR(svn_io_open_unique_file3(&empty_file, &b.empty_file_abspath,
336 NULL, svn_io_file_del_on_pool_cleanup,
337 scratch_pool, scratch_pool));
338
339 if (depth <= svn_depth_immediates)
340 SVN_ERR(arbitrary_diff_this_dir(&b, local_abspath1, depth, scratch_pool));
341 else if (depth == svn_depth_infinity)
342 SVN_ERR(svn_io_dir_walk2(b.recursing_within_added_subtree ? local_abspath2
343 : local_abspath1,
344 0, arbitrary_diff_walker, &b, scratch_pool));
345 return SVN_NO_ERROR;
346}
347
354 return SVN_NO_ERROR;
355}
356
348/* Produce a diff of depth DEPTH for the directory at LOCAL_ABSPATH,
349 * using information from the arbitrary_diff_walker_baton B.
350 * LOCAL_ABSPATH is the path being crawled and can be on either side
351 * of the diff depending on baton->recursing_within_added_subtree. */
357/* Handles reporting of a file for inner_dir_diff */
352static svn_error_t *
358static svn_error_t *
353arbitrary_diff_this_dir(struct arbitrary_diff_walker_baton *b,
354 const char *local_abspath,
355 svn_depth_t depth,
356 apr_pool_t *scratch_pool)
359do_file_diff(const char *left_abspath,
360 const char *right_abspath,
361 const char *left_root_abspath,
362 const char *right_root_abspath,
363 svn_boolean_t left_only,
364 svn_boolean_t right_only,
365 void *parent_baton,
366 const svn_diff_tree_processor_t *diff_processor,
367 svn_client_ctx_t *ctx,
368 apr_pool_t *scratch_pool)
357{
369{
358 const char *local_abspath1;
359 const char *local_abspath2;
360 svn_node_kind_t kind1;
361 svn_node_kind_t kind2;
362 const char *child_relpath;
363 apr_hash_t *dirents1;
364 apr_hash_t *dirents2;
365 apr_hash_t *merged_dirents;
366 apr_array_header_t *sorted_dirents;
367 int i;
368 apr_pool_t *iterpool;
370 const char *relpath;
371 svn_diff_source_t *left_source;
372 svn_diff_source_t *right_source;
373 svn_boolean_t skip = FALSE;
374 apr_hash_t *left_props;
375 apr_hash_t *right_props;
376 void *file_baton;
369
377
370 if (b->recursing_within_adm_dir)
371 {
372 if (svn_dirent_skip_ancestor(b->adm_dir_abspath, local_abspath))
373 return SVN_NO_ERROR;
374 else
375 {
376 b->recursing_within_adm_dir = FALSE;
377 b->adm_dir_abspath = NULL;
378 }
379 }
380 else if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, NULL),
381 scratch_pool))
382 {
383 b->recursing_within_adm_dir = TRUE;
384 b->adm_dir_abspath = apr_pstrdup(b->pool, local_abspath);
385 return SVN_NO_ERROR;
386 }
378 relpath = svn_dirent_skip_ancestor(left_root_abspath, left_abspath);
387
379
388 if (b->recursing_within_added_subtree)
389 child_relpath = svn_dirent_skip_ancestor(b->root2_abspath, local_abspath);
380 if (! right_only)
381 left_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
390 else
382 else
391 child_relpath = svn_dirent_skip_ancestor(b->root1_abspath, local_abspath);
392 if (!child_relpath)
393 return SVN_NO_ERROR;
383 left_source = NULL;
394
384
395 local_abspath1 = svn_dirent_join(b->root1_abspath, child_relpath,
396 scratch_pool);
397 SVN_ERR(svn_io_check_resolved_path(local_abspath1, &kind1, scratch_pool));
385 if (! left_only)
386 right_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
387 else
388 right_source = NULL;
398
389
399 local_abspath2 = svn_dirent_join(b->root2_abspath, child_relpath,
400 scratch_pool);
401 SVN_ERR(svn_io_check_resolved_path(local_abspath2, &kind2, scratch_pool));
390 SVN_ERR(diff_processor->file_opened(&file_baton, &skip,
391 relpath,
392 left_source,
393 right_source,
394 NULL /* copyfrom_source */,
395 parent_baton,
396 diff_processor,
397 scratch_pool,
398 scratch_pool));
402
399
403 if (depth > svn_depth_empty)
404 {
405 if (kind1 == svn_node_dir)
406 SVN_ERR(svn_io_get_dirents3(&dirents1, local_abspath1,
407 TRUE, /* only_check_type */
408 scratch_pool, scratch_pool));
409 else
410 dirents1 = apr_hash_make(scratch_pool);
411 }
400 if (skip)
401 return SVN_NO_ERROR;
412
402
413 if (kind2 == svn_node_dir)
403 if (! right_only)
414 {
404 {
415 apr_hash_t *original_props;
416 apr_hash_t *modified_props;
417 apr_array_header_t *prop_changes;
418
419 /* Show any property changes for this directory. */
420 SVN_ERR(get_props(&original_props, local_abspath1, b->ctx->wc_ctx,
405 SVN_ERR(get_props(&left_props, left_abspath, ctx->wc_ctx,
421 scratch_pool, scratch_pool));
406 scratch_pool, scratch_pool));
422 SVN_ERR(get_props(&modified_props, local_abspath2, b->ctx->wc_ctx,
423 scratch_pool, scratch_pool));
424 SVN_ERR(svn_prop_diffs(&prop_changes, modified_props, original_props,
425 scratch_pool));
426 if (prop_changes->nelts > 0)
427 SVN_ERR(b->callbacks->dir_props_changed(NULL, NULL, child_relpath,
428 FALSE /* was_added */,
429 prop_changes, original_props,
430 b->diff_baton,
431 scratch_pool));
432
407
433 if (depth > svn_depth_empty)
408 /* We perform a mimetype detection to avoid diffing binary files
409 for textual changes.*/
410 if (! svn_hash_gets(left_props, SVN_PROP_MIME_TYPE))
434 {
411 {
435 /* Read directory entries. */
436 SVN_ERR(svn_io_get_dirents3(&dirents2, local_abspath2,
437 TRUE, /* only_check_type */
438 scratch_pool, scratch_pool));
412 const char *mime_type;
413
414 /* ### Use libmagic magic? */
415 SVN_ERR(svn_io_detect_mimetype2(&mime_type, left_abspath,
416 ctx->mimetypes_map, scratch_pool));
417
418 if (mime_type)
419 svn_hash_sets(left_props, SVN_PROP_MIME_TYPE,
420 svn_string_create(mime_type, scratch_pool));
439 }
421 }
422
423 SVN_ERR(translate_if_necessary(&left_abspath, left_props,
424 ctx->cancel_func, ctx->cancel_baton,
425 scratch_pool, scratch_pool));
440 }
426 }
441 else if (depth > svn_depth_empty)
442 dirents2 = apr_hash_make(scratch_pool);
427 else
428 left_props = NULL;
443
429
444 if (depth <= svn_depth_empty)
445 return SVN_NO_ERROR;
446
447 /* Compare dirents1 to dirents2 and show added/deleted/changed files. */
448 merged_dirents = apr_hash_merge(scratch_pool, dirents1, dirents2,
449 NULL, NULL);
450 sorted_dirents = svn_sort__hash(merged_dirents,
451 svn_sort_compare_items_as_paths,
452 scratch_pool);
453 iterpool = svn_pool_create(scratch_pool);
454 for (i = 0; i < sorted_dirents->nelts; i++)
430 if (! left_only)
455 {
431 {
456 svn_sort__item_t elt = APR_ARRAY_IDX(sorted_dirents, i, svn_sort__item_t);
457 const char *name = elt.key;
458 svn_io_dirent2_t *dirent1;
459 svn_io_dirent2_t *dirent2;
460 const char *child1_abspath;
461 const char *child2_abspath;
432 SVN_ERR(get_props(&right_props, right_abspath, ctx->wc_ctx,
433 scratch_pool, scratch_pool));
462
434
463 svn_pool_clear(iterpool);
435 /* We perform a mimetype detection to avoid diffing binary files
436 for textual changes.*/
437 if (! svn_hash_gets(right_props, SVN_PROP_MIME_TYPE))
438 {
439 const char *mime_type;
464
440
465 if (b->ctx->cancel_func)
466 SVN_ERR(b->ctx->cancel_func(b->ctx->cancel_baton));
441 /* ### Use libmagic magic? */
442 SVN_ERR(svn_io_detect_mimetype2(&mime_type, right_abspath,
443 ctx->mimetypes_map, scratch_pool));
467
444
468 if (strcmp(name, SVN_WC_ADM_DIR_NAME) == 0)
469 continue;
470
471 dirent1 = svn_hash_gets(dirents1, name);
472 if (!dirent1)
473 {
474 dirent1 = svn_io_dirent2_create(iterpool);
475 dirent1->kind = svn_node_none;
445 if (mime_type)
446 svn_hash_sets(right_props, SVN_PROP_MIME_TYPE,
447 svn_string_create(mime_type, scratch_pool));
476 }
448 }
477 dirent2 = svn_hash_gets(dirents2, name);
478 if (!dirent2)
479 {
480 dirent2 = svn_io_dirent2_create(iterpool);
481 dirent2->kind = svn_node_none;
482 }
483
449
484 child1_abspath = svn_dirent_join(local_abspath1, name, iterpool);
485 child2_abspath = svn_dirent_join(local_abspath2, name, iterpool);
450 SVN_ERR(translate_if_necessary(&right_abspath, right_props,
451 ctx->cancel_func, ctx->cancel_baton,
452 scratch_pool, scratch_pool));
486
453
487 if (dirent1->special)
488 SVN_ERR(svn_io_check_resolved_path(child1_abspath, &dirent1->kind,
489 iterpool));
490 if (dirent2->special)
491 SVN_ERR(svn_io_check_resolved_path(child1_abspath, &dirent2->kind,
492 iterpool));
454 }
455 else
456 right_props = NULL;
493
457
494 if (dirent1->kind == svn_node_dir &&
495 dirent2->kind == svn_node_dir)
496 {
497 if (depth == svn_depth_immediates)
498 {
499 /* Not using the walker, so show property diffs on these dirs. */
500 SVN_ERR(do_arbitrary_dirs_diff(child1_abspath, child2_abspath,
501 b->root1_abspath, b->root2_abspath,
502 svn_depth_empty,
503 b->callbacks, b->diff_baton,
504 b->ctx, iterpool));
505 }
506 else
507 {
508 /* Either the walker will visit these directories (with
509 * depth=infinity) and they will be processed as 'this dir'
510 * later, or we're showing file children only (depth=files). */
511 continue;
512 }
458 if (left_only)
459 {
460 SVN_ERR(diff_processor->file_deleted(relpath,
461 left_source,
462 left_abspath,
463 left_props,
464 file_baton,
465 diff_processor,
466 scratch_pool));
467 }
468 else if (right_only)
469 {
470 SVN_ERR(diff_processor->file_added(relpath,
471 NULL /* copyfrom_source */,
472 right_source,
473 NULL /* copyfrom_file */,
474 right_abspath,
475 NULL /* copyfrom_props */,
476 right_props,
477 file_baton,
478 diff_processor,
479 scratch_pool));
480 }
481 else
482 {
483 /* ### Perform diff -> close/changed */
484 svn_boolean_t same;
485 apr_array_header_t *prop_changes;
513
486
514 }
487 SVN_ERR(svn_io_files_contents_same_p(&same, left_abspath, right_abspath,
488 scratch_pool));
515
489
516 /* Files that exist only in dirents1. */
517 if (dirent1->kind == svn_node_file &&
518 (dirent2->kind == svn_node_dir || dirent2->kind == svn_node_none))
519 SVN_ERR(do_arbitrary_files_diff(child1_abspath, b->empty_file_abspath,
520 svn_relpath_join(child_relpath, name,
521 iterpool),
522 FALSE, TRUE, NULL,
523 b->callbacks, b->diff_baton,
524 b->ctx, iterpool));
490 SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props,
491 scratch_pool));
525
492
526 /* Files that exist only in dirents2. */
527 if (dirent2->kind == svn_node_file &&
528 (dirent1->kind == svn_node_dir || dirent1->kind == svn_node_none))
493 if (!same || prop_changes->nelts > 0)
529 {
494 {
530 apr_hash_t *original_props;
531
532 SVN_ERR(get_props(&original_props, child1_abspath, b->ctx->wc_ctx,
533 scratch_pool, scratch_pool));
534 SVN_ERR(do_arbitrary_files_diff(b->empty_file_abspath, child2_abspath,
535 svn_relpath_join(child_relpath, name,
536 iterpool),
537 TRUE, FALSE, original_props,
538 b->callbacks, b->diff_baton,
539 b->ctx, iterpool));
495 SVN_ERR(diff_processor->file_changed(relpath,
496 left_source,
497 right_source,
498 same ? NULL : left_abspath,
499 same ? NULL : right_abspath,
500 left_props,
501 right_props,
502 !same,
503 prop_changes,
504 file_baton,
505 diff_processor,
506 scratch_pool));
540 }
507 }
541
542 /* Files that exist in dirents1 and dirents2. */
543 if (dirent1->kind == svn_node_file && dirent2->kind == svn_node_file)
544 SVN_ERR(do_arbitrary_files_diff(child1_abspath, child2_abspath,
545 svn_relpath_join(child_relpath, name,
546 iterpool),
547 FALSE, FALSE, NULL,
548 b->callbacks, b->diff_baton,
549 b->ctx, scratch_pool));
550
551 /* Directories that only exist in dirents2. These aren't crawled
552 * by this walker so we have to crawl them separately. */
553 if (depth > svn_depth_files &&
554 dirent2->kind == svn_node_dir &&
555 (dirent1->kind == svn_node_file || dirent1->kind == svn_node_none))
556 SVN_ERR(do_arbitrary_dirs_diff(child1_abspath, child2_abspath,
557 b->root1_abspath, b->root2_abspath,
558 depth <= svn_depth_immediates
559 ? svn_depth_empty
560 : svn_depth_infinity ,
561 b->callbacks, b->diff_baton,
562 b->ctx, iterpool));
508 else
509 {
510 SVN_ERR(diff_processor->file_closed(relpath,
511 left_source,
512 right_source,
513 file_baton,
514 diff_processor,
515 scratch_pool));
516 }
563 }
517 }
564
565 svn_pool_destroy(iterpool);
566
567 return SVN_NO_ERROR;
568}
569
518 return SVN_NO_ERROR;
519}
520
570/* An implementation of svn_io_walk_func_t.
571 * Note: LOCAL_ABSPATH is the path being crawled and can be on either side
572 * of the diff depending on baton->recursing_within_added_subtree. */
521
522/* Handles reporting of a directory and its children for inner_dir_diff */
573static svn_error_t *
523static svn_error_t *
574arbitrary_diff_walker(void *baton, const char *local_abspath,
575 const apr_finfo_t *finfo,
576 apr_pool_t *scratch_pool)
524do_dir_diff(const char *left_abspath,
525 const char *right_abspath,
526 const char *left_root_abspath,
527 const char *right_root_abspath,
528 svn_boolean_t left_only,
529 svn_boolean_t right_only,
530 svn_boolean_t left_before_right,
531 svn_depth_t depth,
532 void *parent_baton,
533 const svn_diff_tree_processor_t *diff_processor,
534 svn_client_ctx_t *ctx,
535 apr_pool_t *scratch_pool)
577{
536{
578 struct arbitrary_diff_walker_baton *b = baton;
537 const char *relpath;
538 svn_diff_source_t *left_source;
539 svn_diff_source_t *right_source;
540 svn_boolean_t skip = FALSE;
541 svn_boolean_t skip_children = FALSE;
542 void *dir_baton;
543 apr_hash_t *left_props;
544 apr_hash_t *right_props;
579
545
580 if (b->ctx->cancel_func)
581 SVN_ERR(b->ctx->cancel_func(b->ctx->cancel_baton));
546 relpath = svn_dirent_skip_ancestor(left_root_abspath, left_abspath);
582
547
583 if (finfo->filetype != APR_DIR)
548 if (! right_only)
549 {
550 left_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
551 SVN_ERR(get_props(&left_props, left_abspath, ctx->wc_ctx,
552 scratch_pool, scratch_pool));
553 }
554 else
555 {
556 left_source = NULL;
557 left_props = NULL;
558 }
559
560 if (! left_only)
561 {
562 right_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
563 SVN_ERR(get_props(&right_props, right_abspath, ctx->wc_ctx,
564 scratch_pool, scratch_pool));
565 }
566 else
567 {
568 right_source = NULL;
569 right_props = NULL;
570 }
571
572 SVN_ERR(diff_processor->dir_opened(&dir_baton, &skip, &skip_children,
573 relpath,
574 left_source,
575 right_source,
576 NULL /* copyfrom_source */,
577 parent_baton,
578 diff_processor,
579 scratch_pool, scratch_pool));
580
581 if (!skip_children)
582 {
583 if (depth >= svn_depth_files)
584 SVN_ERR(inner_dir_diff(left_abspath, right_abspath,
585 left_root_abspath, right_root_abspath,
586 left_only, right_only,
587 left_before_right, depth,
588 dir_baton,
589 diff_processor, ctx, scratch_pool));
590 }
591 else if (skip)
584 return SVN_NO_ERROR;
585
592 return SVN_NO_ERROR;
593
586 SVN_ERR(arbitrary_diff_this_dir(b, local_abspath, svn_depth_infinity,
587 scratch_pool));
594 if (left_props && right_props)
595 {
596 apr_array_header_t *prop_diffs;
588
597
598 SVN_ERR(svn_prop_diffs(&prop_diffs, right_props, left_props,
599 scratch_pool));
600
601 if (prop_diffs->nelts)
602 {
603 SVN_ERR(diff_processor->dir_changed(relpath,
604 left_source,
605 right_source,
606 left_props,
607 right_props,
608 prop_diffs,
609 dir_baton,
610 diff_processor,
611 scratch_pool));
612 return SVN_NO_ERROR;
613 }
614 }
615
616 if (left_source && right_source)
617 {
618 SVN_ERR(diff_processor->dir_closed(relpath,
619 left_source,
620 right_source,
621 dir_baton,
622 diff_processor,
623 scratch_pool));
624 }
625 else if (left_source)
626 {
627 SVN_ERR(diff_processor->dir_deleted(relpath,
628 left_source,
629 left_props,
630 dir_baton,
631 diff_processor,
632 scratch_pool));
633 }
634 else
635 {
636 SVN_ERR(diff_processor->dir_added(relpath,
637 NULL /* copyfrom_source */,
638 right_source,
639 NULL /* copyfrom_props */,
640 right_props,
641 dir_baton,
642 diff_processor,
643 scratch_pool));
644 }
645
589 return SVN_NO_ERROR;
590}
591
592svn_error_t *
646 return SVN_NO_ERROR;
647}
648
649svn_error_t *
593svn_client__arbitrary_nodes_diff(const char *local_abspath1,
594 const char *local_abspath2,
650svn_client__arbitrary_nodes_diff(const char **root_relpath,
651 svn_boolean_t *root_is_dir,
652 const char *left_abspath,
653 const char *right_abspath,
595 svn_depth_t depth,
654 svn_depth_t depth,
596 const svn_wc_diff_callbacks4_t *callbacks,
597 void *diff_baton,
655 const svn_diff_tree_processor_t *diff_processor,
598 svn_client_ctx_t *ctx,
656 svn_client_ctx_t *ctx,
657 apr_pool_t *result_pool,
599 apr_pool_t *scratch_pool)
600{
658 apr_pool_t *scratch_pool)
659{
601 svn_node_kind_t kind1;
602 svn_node_kind_t kind2;
660 svn_node_kind_t left_kind;
661 svn_node_kind_t right_kind;
662 const char *left_root_abspath;
663 const char *right_root_abspath;
664 svn_boolean_t left_before_right = TRUE; /* Future argument? */
603
665
604 SVN_ERR(svn_io_check_resolved_path(local_abspath1, &kind1, scratch_pool));
605 SVN_ERR(svn_io_check_resolved_path(local_abspath2, &kind2, scratch_pool));
666 if (depth == svn_depth_unknown)
667 depth = svn_depth_infinity;
606
668
607 if (kind1 != kind2)
608 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
609 _("'%s' is not the same node kind as '%s'"),
610 svn_dirent_local_style(local_abspath1,
611 scratch_pool),
612 svn_dirent_local_style(local_abspath2,
613 scratch_pool));
669 SVN_ERR(svn_io_check_resolved_path(left_abspath, &left_kind, scratch_pool));
670 SVN_ERR(svn_io_check_resolved_path(right_abspath, &right_kind, scratch_pool));
614
615 if (depth == svn_depth_unknown)
616 depth = svn_depth_infinity;
617
671
672 if (depth == svn_depth_unknown)
673 depth = svn_depth_infinity;
674
618 if (kind1 == svn_node_file)
619 SVN_ERR(do_arbitrary_files_diff(local_abspath1, local_abspath2,
620 svn_dirent_basename(local_abspath1,
621 scratch_pool),
622 FALSE, FALSE, NULL,
623 callbacks, diff_baton,
624 ctx, scratch_pool));
625 else if (kind1 == svn_node_dir)
626 SVN_ERR(do_arbitrary_dirs_diff(local_abspath1, local_abspath2,
627 NULL, NULL, depth,
628 callbacks, diff_baton,
629 ctx, scratch_pool));
675 if (left_kind == svn_node_dir && right_kind == svn_node_dir)
676 {
677 left_root_abspath = left_abspath;
678 right_root_abspath = right_abspath;
679
680 if (root_relpath)
681 *root_relpath = "";
682 if (root_is_dir)
683 *root_is_dir = TRUE;
684 }
630 else
685 else
686 {
687 svn_dirent_split(&left_root_abspath, root_relpath, left_abspath,
688 scratch_pool);
689 right_root_abspath = svn_dirent_dirname(right_abspath, scratch_pool);
690
691 if (root_relpath)
692 *root_relpath = apr_pstrdup(result_pool, *root_relpath);
693 if (root_is_dir)
694 *root_is_dir = FALSE;
695 }
696
697 if (left_kind == svn_node_dir && right_kind == svn_node_dir)
698 {
699 SVN_ERR(do_dir_diff(left_abspath, right_abspath,
700 left_root_abspath, right_root_abspath,
701 FALSE, FALSE, left_before_right,
702 depth, NULL /* parent_baton */,
703 diff_processor, ctx, scratch_pool));
704 }
705 else if (left_kind == svn_node_file && right_kind == svn_node_file)
706 {
707 SVN_ERR(do_file_diff(left_abspath, right_abspath,
708 left_root_abspath, right_root_abspath,
709 FALSE, FALSE,
710 NULL /* parent_baton */,
711 diff_processor, ctx, scratch_pool));
712 }
713 else if (left_kind == svn_node_file || left_kind == svn_node_dir
714 || right_kind == svn_node_file || right_kind == svn_node_dir)
715 {
716 void *dir_baton;
717 svn_boolean_t skip = FALSE;
718 svn_boolean_t skip_children = FALSE;
719 svn_diff_source_t *left_src;
720 svn_diff_source_t *right_src;
721
722 left_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
723 right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
724
725 /* The root is replaced... */
726 /* Report delete and/or add */
727
728 SVN_ERR(diff_processor->dir_opened(&dir_baton, &skip, &skip_children, "",
729 left_src,
730 right_src,
731 NULL /* copyfrom_src */,
732 NULL,
733 diff_processor,
734 scratch_pool, scratch_pool));
735
736 if (skip)
737 return SVN_NO_ERROR;
738 else if (!skip_children)
739 {
740 if (left_before_right)
741 {
742 if (left_kind == svn_node_file)
743 SVN_ERR(do_file_diff(left_abspath, right_abspath,
744 left_root_abspath, right_root_abspath,
745 TRUE, FALSE, NULL /* parent_baton */,
746 diff_processor, ctx, scratch_pool));
747 else if (left_kind == svn_node_dir)
748 SVN_ERR(do_dir_diff(left_abspath, right_abspath,
749 left_root_abspath, right_root_abspath,
750 TRUE, FALSE, left_before_right,
751 depth, NULL /* parent_baton */,
752 diff_processor, ctx, scratch_pool));
753 }
754
755 if (right_kind == svn_node_file)
756 SVN_ERR(do_file_diff(left_abspath, right_abspath,
757 left_root_abspath, right_root_abspath,
758 FALSE, TRUE, NULL /* parent_baton */,
759 diff_processor, ctx, scratch_pool));
760 else if (right_kind == svn_node_dir)
761 SVN_ERR(do_dir_diff(left_abspath, right_abspath,
762 left_root_abspath, right_root_abspath,
763 FALSE, TRUE, left_before_right,
764 depth, NULL /* parent_baton */,
765 diff_processor, ctx, scratch_pool));
766
767 if (! left_before_right)
768 {
769 if (left_kind == svn_node_file)
770 SVN_ERR(do_file_diff(left_abspath, right_abspath,
771 left_root_abspath, right_root_abspath,
772 TRUE, FALSE, NULL /* parent_baton */,
773 diff_processor, ctx, scratch_pool));
774 else if (left_kind == svn_node_dir)
775 SVN_ERR(do_dir_diff(left_abspath, right_abspath,
776 left_root_abspath, right_root_abspath,
777 TRUE, FALSE, left_before_right,
778 depth, NULL /* parent_baton */,
779 diff_processor, ctx, scratch_pool));
780 }
781 }
782
783 SVN_ERR(diff_processor->dir_closed("",
784 left_src,
785 right_src,
786 dir_baton,
787 diff_processor,
788 scratch_pool));
789 }
790 else
631 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
632 _("'%s' is not a file or directory"),
791 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
792 _("'%s' is not a file or directory"),
633 kind1 == svn_node_none
634 ? svn_dirent_local_style(local_abspath1,
635 scratch_pool)
636 : svn_dirent_local_style(local_abspath2,
637 scratch_pool));
793 svn_dirent_local_style(
794 (left_kind == svn_node_none)
795 ? left_abspath
796 : right_abspath,
797 scratch_pool));
798
638 return SVN_NO_ERROR;
639}
799 return SVN_NO_ERROR;
800}