2 * diff_local.c: comparing local trees with each other
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
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 /* ==================================================================== */
30 #include <apr_strings.h>
31 #include <apr_pools.h>
34 #include "svn_types.h"
37 #include "svn_client.h"
38 #include "svn_string.h"
39 #include "svn_error.h"
40 #include "svn_dirent_uri.h"
42 #include "svn_pools.h"
43 #include "svn_props.h"
44 #include "svn_sorts.h"
45 #include "svn_subst.h"
48 #include "private/svn_sorts_private.h"
49 #include "private/svn_wc_private.h"
50 #include "private/svn_diff_tree.h"
52 #include "svn_private_config.h"
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. */
59 get_props(apr_hash_t
**props
,
60 const char *local_abspath
,
61 svn_wc_context_t
*wc_ctx
,
62 apr_pool_t
*result_pool
,
63 apr_pool_t
*scratch_pool
)
67 err
= svn_wc_prop_list2(props
, wc_ctx
, local_abspath
, result_pool
,
71 if (err
->apr_err
== SVN_ERR_WC_PATH_NOT_FOUND
||
72 err
->apr_err
== SVN_ERR_WC_NOT_WORKING_COPY
)
75 *props
= apr_hash_make(result_pool
);
77 /* ### Apply autoprops, like 'svn add' would? */
80 return svn_error_trace(err
);
86 /* Forward declaration */
88 do_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
,
95 const svn_diff_tree_processor_t
*diff_processor
,
96 svn_client_ctx_t
*ctx
,
97 apr_pool_t
*scratch_pool
);
99 /* Forward declaration */
101 do_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
,
110 const svn_diff_tree_processor_t
*diff_processor
,
111 svn_client_ctx_t
*ctx
,
112 apr_pool_t
*scratch_pool
);
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.
118 * Report paths as relative from LEFT_ROOT_ABSPATH/RIGHT_ROOT_ABSPATH.
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).
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).
128 * Walk the sources according to depth, and report with parent baton
131 inner_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
,
140 const svn_diff_tree_processor_t
*diff_processor
,
141 svn_client_ctx_t
*ctx
,
142 apr_pool_t
*scratch_pool
)
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
;
149 svn_depth_t depth_below_here
;
152 SVN_ERR_ASSERT(depth
>= svn_depth_files
&& depth
<= svn_depth_infinity
);
156 err
= svn_io_get_dirents3(&left_dirents
, left_abspath
, FALSE
,
157 scratch_pool
, iterpool
);
159 if (err
&& (APR_STATUS_IS_ENOENT(err
->apr_err
)
160 || SVN__APR_STATUS_IS_ENOTDIR(err
->apr_err
)))
162 svn_error_clear(err
);
163 left_dirents
= apr_hash_make(scratch_pool
);
170 left_dirents
= apr_hash_make(scratch_pool
);
174 err
= svn_io_get_dirents3(&right_dirents
, right_abspath
, FALSE
,
175 scratch_pool
, iterpool
);
177 if (err
&& (APR_STATUS_IS_ENOENT(err
->apr_err
)
178 || SVN__APR_STATUS_IS_ENOTDIR(err
->apr_err
)))
180 svn_error_clear(err
);
181 right_dirents
= apr_hash_make(scratch_pool
);
188 right_dirents
= apr_hash_make(scratch_pool
);
190 if (left_only
&& right_only
)
191 return SVN_NO_ERROR
; /* Somebody deleted the directory?? */
193 if (depth
!= svn_depth_infinity
)
194 depth_below_here
= svn_depth_empty
;
196 depth_below_here
= svn_depth_infinity
;
198 sorted_dirents
= svn_sort__hash(apr_hash_merge(iterpool
, left_dirents
,
199 right_dirents
, NULL
, NULL
),
200 svn_sort_compare_items_as_paths
,
203 for (i
= 0; i
< sorted_dirents
->nelts
; i
++)
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
;
211 svn_pool_clear(iterpool
);
213 if (ctx
->cancel_func
)
214 SVN_ERR(ctx
->cancel_func(ctx
->cancel_baton
));
216 if (svn_wc_is_adm_dir(elt
->key
, iterpool
))
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
);
222 child_left_abspath
= svn_dirent_join(left_abspath
, elt
->key
, iterpool
);
223 child_right_abspath
= svn_dirent_join(right_abspath
, elt
->key
, iterpool
);
225 if (((left_dirent
== NULL
) != (right_dirent
== NULL
))
226 || (left_dirent
->kind
!= right_dirent
->kind
))
228 /* Report delete and/or add */
229 if (left_dirent
&& left_before_right
)
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
));
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
));
259 if (left_dirent
&& !left_before_right
)
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
));
274 else if (left_dirent
->kind
== svn_node_file
)
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
));
282 else if (depth
>= svn_depth_immediates
)
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
));
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 */
301 translate_if_necessary(const char **local_abspath
,
303 svn_cancel_func_t cancel_func
,
305 apr_pool_t
*result_pool
,
306 apr_pool_t
*scratch_pool
)
308 const svn_string_t
*eol_style_val
;
309 const svn_string_t
*keywords_val
;
310 svn_subst_eol_style_t eol_style
;
312 apr_hash_t
*keywords
;
313 svn_stream_t
*contents
;
316 /* if (svn_hash_gets(props, SVN_PROP_SPECIAL))
317 ### TODO: Implement */
319 eol_style_val
= svn_hash_gets(props
, SVN_PROP_EOL_STYLE
);
320 keywords_val
= svn_hash_gets(props
, SVN_PROP_KEYWORDS
);
323 svn_subst_eol_style_from_value(&eol_style
, &eol
, eol_style_val
->data
);
327 eol_style
= svn_subst_eol_style_none
;
331 SVN_ERR(svn_subst_build_keywords3(&keywords
, keywords_val
->data
,
332 APR_STRINGIFY(SVN_INVALID_REVNUM
),
333 "", "", 0, "", scratch_pool
));
337 if (!svn_subst_translation_required(eol_style
, eol
, keywords
, FALSE
, FALSE
))
340 SVN_ERR(svn_stream_open_readonly(&contents
, *local_abspath
,
341 scratch_pool
, 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
));
347 dst
= svn_subst_stream_translated(dst
, eol
, TRUE
/* repair */,
348 keywords
, FALSE
/* expand */,
351 SVN_ERR(svn_stream_copy3(contents
, dst
, cancel_func
, cancel_baton
,
357 /* Handles reporting of a file for inner_dir_diff */
359 do_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
,
366 const svn_diff_tree_processor_t
*diff_processor
,
367 svn_client_ctx_t
*ctx
,
368 apr_pool_t
*scratch_pool
)
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
;
378 relpath
= svn_dirent_skip_ancestor(left_root_abspath
, left_abspath
);
381 left_source
= svn_diff__source_create(SVN_INVALID_REVNUM
, scratch_pool
);
386 right_source
= svn_diff__source_create(SVN_INVALID_REVNUM
, scratch_pool
);
390 SVN_ERR(diff_processor
->file_opened(&file_baton
, &skip
,
394 NULL
/* copyfrom_source */,
405 SVN_ERR(get_props(&left_props
, left_abspath
, ctx
->wc_ctx
,
406 scratch_pool
, scratch_pool
));
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
))
412 const char *mime_type
;
414 /* ### Use libmagic magic? */
415 SVN_ERR(svn_io_detect_mimetype2(&mime_type
, left_abspath
,
416 ctx
->mimetypes_map
, scratch_pool
));
419 svn_hash_sets(left_props
, SVN_PROP_MIME_TYPE
,
420 svn_string_create(mime_type
, scratch_pool
));
423 SVN_ERR(translate_if_necessary(&left_abspath
, left_props
,
424 ctx
->cancel_func
, ctx
->cancel_baton
,
425 scratch_pool
, scratch_pool
));
432 SVN_ERR(get_props(&right_props
, right_abspath
, ctx
->wc_ctx
,
433 scratch_pool
, scratch_pool
));
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
))
439 const char *mime_type
;
441 /* ### Use libmagic magic? */
442 SVN_ERR(svn_io_detect_mimetype2(&mime_type
, right_abspath
,
443 ctx
->mimetypes_map
, scratch_pool
));
446 svn_hash_sets(right_props
, SVN_PROP_MIME_TYPE
,
447 svn_string_create(mime_type
, scratch_pool
));
450 SVN_ERR(translate_if_necessary(&right_abspath
, right_props
,
451 ctx
->cancel_func
, ctx
->cancel_baton
,
452 scratch_pool
, scratch_pool
));
460 SVN_ERR(diff_processor
->file_deleted(relpath
,
470 SVN_ERR(diff_processor
->file_added(relpath
,
471 NULL
/* copyfrom_source */,
473 NULL
/* copyfrom_file */,
475 NULL
/* copyfrom_props */,
483 /* ### Perform diff -> close/changed */
485 apr_array_header_t
*prop_changes
;
487 SVN_ERR(svn_io_files_contents_same_p(&same
, left_abspath
, right_abspath
,
490 SVN_ERR(svn_prop_diffs(&prop_changes
, right_props
, left_props
,
493 if (!same
|| prop_changes
->nelts
> 0)
495 SVN_ERR(diff_processor
->file_changed(relpath
,
498 same
? NULL
: left_abspath
,
499 same
? NULL
: right_abspath
,
510 SVN_ERR(diff_processor
->file_closed(relpath
,
522 /* Handles reporting of a directory and its children for inner_dir_diff */
524 do_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
,
533 const svn_diff_tree_processor_t
*diff_processor
,
534 svn_client_ctx_t
*ctx
,
535 apr_pool_t
*scratch_pool
)
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
;
543 apr_hash_t
*left_props
;
544 apr_hash_t
*right_props
;
546 relpath
= svn_dirent_skip_ancestor(left_root_abspath
, left_abspath
);
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
));
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
));
572 SVN_ERR(diff_processor
->dir_opened(&dir_baton
, &skip
, &skip_children
,
576 NULL
/* copyfrom_source */,
579 scratch_pool
, scratch_pool
));
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
,
589 diff_processor
, ctx
, scratch_pool
));
594 if (left_props
&& right_props
)
596 apr_array_header_t
*prop_diffs
;
598 SVN_ERR(svn_prop_diffs(&prop_diffs
, right_props
, left_props
,
601 if (prop_diffs
->nelts
)
603 SVN_ERR(diff_processor
->dir_changed(relpath
,
616 if (left_source
&& right_source
)
618 SVN_ERR(diff_processor
->dir_closed(relpath
,
625 else if (left_source
)
627 SVN_ERR(diff_processor
->dir_deleted(relpath
,
636 SVN_ERR(diff_processor
->dir_added(relpath
,
637 NULL
/* copyfrom_source */,
639 NULL
/* copyfrom_props */,
650 svn_client__arbitrary_nodes_diff(const char *left_abspath
,
651 const char *right_abspath
,
653 const svn_diff_tree_processor_t
*diff_processor
,
654 svn_client_ctx_t
*ctx
,
655 apr_pool_t
*scratch_pool
)
657 svn_node_kind_t left_kind
;
658 svn_node_kind_t right_kind
;
659 const char *left_root_abspath
= left_abspath
;
660 const char *right_root_abspath
= right_abspath
;
661 svn_boolean_t left_before_right
= TRUE
; /* Future argument? */
663 if (depth
== svn_depth_unknown
)
664 depth
= svn_depth_infinity
;
666 SVN_ERR(svn_io_check_resolved_path(left_abspath
, &left_kind
, scratch_pool
));
667 SVN_ERR(svn_io_check_resolved_path(right_abspath
, &right_kind
, scratch_pool
));
669 if (left_kind
== svn_node_dir
&& right_kind
== svn_node_dir
)
671 SVN_ERR(do_dir_diff(left_abspath
, right_abspath
,
672 left_root_abspath
, right_root_abspath
,
673 FALSE
, FALSE
, left_before_right
,
674 depth
, NULL
/* parent_baton */,
675 diff_processor
, ctx
, scratch_pool
));
677 else if (left_kind
== svn_node_file
&& right_kind
== svn_node_file
)
679 SVN_ERR(do_file_diff(left_abspath
, right_abspath
,
680 left_root_abspath
, right_root_abspath
,
682 NULL
/* parent_baton */,
683 diff_processor
, ctx
, scratch_pool
));
685 else if (left_kind
== svn_node_file
|| left_kind
== svn_node_dir
686 || right_kind
== svn_node_file
|| right_kind
== svn_node_dir
)
688 /* The root is added/deleted/replaced. Report delete and/or add. */
689 if (left_before_right
)
691 if (left_kind
== svn_node_file
)
692 SVN_ERR(do_file_diff(left_abspath
, right_abspath
,
693 left_root_abspath
, right_root_abspath
,
694 TRUE
, FALSE
, NULL
/* parent_baton */,
695 diff_processor
, ctx
, scratch_pool
));
696 else if (left_kind
== svn_node_dir
)
697 SVN_ERR(do_dir_diff(left_abspath
, right_abspath
,
698 left_root_abspath
, right_root_abspath
,
699 TRUE
, FALSE
, left_before_right
,
700 depth
, NULL
/* parent_baton */,
701 diff_processor
, ctx
, scratch_pool
));
704 if (right_kind
== svn_node_file
)
705 SVN_ERR(do_file_diff(left_abspath
, right_abspath
,
706 left_root_abspath
, right_root_abspath
,
707 FALSE
, TRUE
, NULL
/* parent_baton */,
708 diff_processor
, ctx
, scratch_pool
));
709 else if (right_kind
== svn_node_dir
)
710 SVN_ERR(do_dir_diff(left_abspath
, right_abspath
,
711 left_root_abspath
, right_root_abspath
,
712 FALSE
, TRUE
, left_before_right
,
713 depth
, NULL
/* parent_baton */,
714 diff_processor
, ctx
, scratch_pool
));
716 if (! left_before_right
)
718 if (left_kind
== svn_node_file
)
719 SVN_ERR(do_file_diff(left_abspath
, right_abspath
,
720 left_root_abspath
, right_root_abspath
,
721 TRUE
, FALSE
, NULL
/* parent_baton */,
722 diff_processor
, ctx
, scratch_pool
));
723 else if (left_kind
== svn_node_dir
)
724 SVN_ERR(do_dir_diff(left_abspath
, right_abspath
,
725 left_root_abspath
, right_root_abspath
,
726 TRUE
, FALSE
, left_before_right
,
727 depth
, NULL
/* parent_baton */,
728 diff_processor
, ctx
, scratch_pool
));
732 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND
, NULL
,
733 _("'%s' is not a file or directory"),
734 svn_dirent_local_style(
735 (left_kind
== svn_node_none
)