2 * diff_local.c -- A simple diff walker which compares local files against
3 * their pristine versions.
5 * ====================================================================
6 * Licensed to the Apache Software Foundation (ASF) under one
7 * or more contributor license agreements. See the NOTICE file
8 * distributed with this work for additional information
9 * regarding copyright ownership. The ASF licenses this file
10 * to you under the Apache License, Version 2.0 (the
11 * "License"); you may not use this file except in compliance
12 * with the License. You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * Unless required by applicable law or agreed to in writing,
17 * software distributed under the License is distributed on an
18 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19 * KIND, either express or implied. See the License for the
20 * specific language governing permissions and limitations
22 * ====================================================================
24 * This is the simple working copy diff algorithm which is used when you
25 * just use 'svn diff PATH'. It shows what is modified in your working copy
26 * since a node was checked out or copied but doesn't show most kinds of
27 * restructuring operations.
29 * You can look at this as another form of the status walker.
34 #include "svn_error.h"
35 #include "svn_pools.h"
36 #include "svn_dirent_uri.h"
40 #include "private/svn_wc_private.h"
41 #include "private/svn_diff_tree.h"
48 #include "svn_private_config.h"
50 /*-------------------------------------------------------------------------*/
52 /* Baton containing the state of a directory
53 reported open via a diff processor */
56 struct node_state_t *parent;
60 const char *local_abspath;
64 svn_diff_source_t *left_src;
65 svn_diff_source_t *right_src;
66 svn_diff_source_t *copy_src;
69 svn_boolean_t skip_children;
71 apr_hash_t *left_props;
72 apr_hash_t *right_props;
73 const apr_array_header_t *propchanges;
82 /* Report editor paths relative from this directory */
83 const char *anchor_abspath;
85 struct node_state_t *cur;
87 const svn_diff_tree_processor_t *processor;
89 /* Should this diff ignore node ancestry? */
90 svn_boolean_t ignore_ancestry;
92 /* Cancel function/baton */
93 svn_cancel_func_t cancel_func;
99 /* Recursively opens directories on the stack in EB, until LOCAL_ABSPATH
100 is reached. If RECURSIVE_SKIP is TRUE, don't open LOCAL_ABSPATH itself,
101 but create it marked with skip+skip_children.
104 ensure_state(struct diff_baton *eb,
105 const char *local_abspath,
106 svn_boolean_t recursive_skip,
107 apr_pool_t *scratch_pool)
109 struct node_state_t *ns;
115 relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, local_abspath);
119 /* Don't recurse on the anchor, as that might loop infinitely because
120 svn_dirent_dirname("/",...) -> "/"
121 svn_dirent_dirname("C:/",...) -> "C:/" (Windows) */
123 SVN_ERR(ensure_state(eb,
124 svn_dirent_dirname(local_abspath, scratch_pool),
128 else if (svn_dirent_is_child(eb->cur->local_abspath, local_abspath, NULL))
129 SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool),
135 if (eb->cur && eb->cur->skip_children)
138 ns_pool = svn_pool_create(eb->cur ? eb->cur->pool : eb->pool);
139 ns = apr_pcalloc(ns_pool, sizeof(*ns));
142 ns->local_abspath = apr_pstrdup(ns_pool, local_abspath);
143 ns->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, ns->local_abspath);
144 ns->parent = eb->cur;
150 ns->skip_children = TRUE;
155 svn_revnum_t revision;
158 err = svn_wc__db_base_get_info(NULL, NULL, &revision, NULL, NULL, NULL,
159 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
161 eb->db, local_abspath,
162 scratch_pool, scratch_pool);
166 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
167 return svn_error_trace(err);
168 svn_error_clear(err);
170 revision = 0; /* Use original revision? */
172 ns->left_src = svn_diff__source_create(revision, ns->pool);
173 ns->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, ns->pool);
175 SVN_ERR(eb->processor->dir_opened(&ns->baton, &ns->skip,
180 NULL /* copyfrom_source */,
181 ns->parent ? ns->parent->baton : NULL,
183 ns->pool, scratch_pool));
189 /* Implements svn_wc_status_func3_t */
191 diff_status_callback(void *baton,
192 const char *local_abspath,
193 const svn_wc_status3_t *status,
194 apr_pool_t *scratch_pool)
196 struct diff_baton *eb = baton;
197 svn_wc__db_t *db = eb->db;
199 if (! status->versioned)
200 return SVN_NO_ERROR; /* unversioned (includes dir externals) */
202 if (status->node_status == svn_wc_status_conflicted
203 && status->text_status == svn_wc_status_none
204 && status->prop_status == svn_wc_status_none)
206 /* Node is an actual only node describing a tree conflict */
210 /* Not text/prop modified, not copied. Easy out */
211 if (status->node_status == svn_wc_status_normal && !status->copied)
214 /* Mark all directories where we are no longer inside as closed */
216 && !svn_dirent_is_ancestor(eb->cur->local_abspath, local_abspath))
218 struct node_state_t *ns = eb->cur;
223 SVN_ERR(eb->processor->dir_changed(ns->relpath,
233 SVN_ERR(eb->processor->dir_closed(ns->relpath,
240 eb->cur = ns->parent;
241 svn_pool_clear(ns->pool);
243 SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool),
244 FALSE, scratch_pool));
246 if (eb->cur && eb->cur->skip_children)
249 /* This code does about the same thing as the inner body of
250 walk_local_nodes_diff() in diff_editor.c, except that
251 it is already filtered by the status walker, doesn't have to
252 account for remote changes (and many tiny other details) */
255 svn_boolean_t repos_only;
256 svn_boolean_t local_only;
257 svn_wc__db_status_t db_status;
258 svn_boolean_t have_base;
259 svn_node_kind_t base_kind;
260 svn_node_kind_t db_kind = status->kind;
261 svn_depth_t depth_below_here = svn_depth_unknown;
263 const char *child_abspath = local_abspath;
264 const char *child_relpath = svn_dirent_skip_ancestor(eb->anchor_abspath,
271 /* ### optimize away this call using status info. Should
272 be possible in almost every case (except conflict, missing, obst.)*/
273 SVN_ERR(svn_wc__db_read_info(&db_status, NULL, NULL, NULL, NULL, NULL,
274 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
275 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
276 NULL, NULL, NULL, NULL,
277 &have_base, NULL, NULL,
278 eb->db, local_abspath,
279 scratch_pool, scratch_pool));
282 local_only = TRUE; /* Only report additions */
284 else if (db_status == svn_wc__db_status_normal)
289 else if (db_status == svn_wc__db_status_deleted)
291 svn_wc__db_status_t base_status;
293 SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
294 NULL, NULL, NULL, NULL, NULL,
295 NULL, NULL, NULL, NULL, NULL,
297 eb->db, local_abspath,
298 scratch_pool, scratch_pool));
300 if (base_status != svn_wc__db_status_normal)
305 /* working status is either added or deleted */
306 svn_wc__db_status_t base_status;
308 SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
309 NULL, NULL, NULL, NULL, NULL,
310 NULL, NULL, NULL, NULL, NULL,
312 eb->db, local_abspath,
313 scratch_pool, scratch_pool));
315 if (base_status != svn_wc__db_status_normal)
317 else if (base_kind != db_kind || !eb->ignore_ancestry)
326 /* Report repository form deleted */
327 if (base_kind == svn_node_file)
328 SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath,
332 eb->cur ? eb->cur->baton : NULL,
334 else if (base_kind == svn_node_dir)
335 SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath,
340 eb->cur ? eb->cur->baton : NULL,
345 else if (!local_only)
347 /* Diff base against actual */
348 if (db_kind == svn_node_file)
350 SVN_ERR(svn_wc__diff_base_working_diff(db, child_abspath,
362 else if (db_kind == svn_node_dir)
364 SVN_ERR(ensure_state(eb, local_abspath, FALSE, scratch_pool));
366 if (status->prop_status != svn_wc_status_none
367 && status->prop_status != svn_wc_status_normal)
369 apr_array_header_t *propchanges;
370 SVN_ERR(svn_wc__db_base_get_props(&eb->cur->left_props,
371 eb->db, local_abspath,
374 SVN_ERR(svn_wc__db_read_props(&eb->cur->right_props,
375 eb->db, local_abspath,
379 SVN_ERR(svn_prop_diffs(&propchanges,
380 eb->cur->right_props,
384 eb->cur->propchanges = propchanges;
389 if (local_only && (db_status != svn_wc__db_status_deleted))
391 if (db_kind == svn_node_file)
392 SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath,
395 eb->cur ? eb->cur->baton : NULL,
400 else if (db_kind == svn_node_dir)
401 SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath,
402 child_relpath, depth_below_here,
404 eb->cur ? eb->cur->baton : NULL,
411 if (db_kind == svn_node_dir && (local_only || repos_only))
412 SVN_ERR(ensure_state(eb, local_abspath, TRUE /* skip */, scratch_pool));
419 /* Public Interface */
421 svn_wc__diff7(const char **root_relpath,
422 svn_boolean_t *root_is_dir,
423 svn_wc_context_t *wc_ctx,
424 const char *local_abspath,
426 svn_boolean_t ignore_ancestry,
427 const apr_array_header_t *changelist_filter,
428 const svn_diff_tree_processor_t *diff_processor,
429 svn_cancel_func_t cancel_func,
431 apr_pool_t *result_pool,
432 apr_pool_t *scratch_pool)
434 struct diff_baton eb = { 0 };
435 svn_node_kind_t kind;
436 svn_boolean_t get_all;
438 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
439 SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath,
440 FALSE /* allow_missing */,
441 TRUE /* show_deleted */,
442 FALSE /* show_hidden */,
445 eb.anchor_abspath = local_abspath;
449 svn_boolean_t is_wcroot;
451 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot,
452 wc_ctx->db, local_abspath, scratch_pool));
455 eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
457 else if (kind != svn_node_dir)
458 eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
461 *root_relpath = apr_pstrdup(result_pool,
462 svn_dirent_skip_ancestor(eb.anchor_abspath,
465 *root_is_dir = (kind == svn_node_dir);
467 /* Apply changelist filtering to the output */
468 if (changelist_filter && changelist_filter->nelts)
470 apr_hash_t *changelist_hash;
472 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
474 diff_processor = svn_wc__changelist_filter_tree_processor_create(
475 diff_processor, wc_ctx, local_abspath,
476 changelist_hash, result_pool);
480 eb.processor = diff_processor;
481 eb.ignore_ancestry = ignore_ancestry;
482 eb.pool = scratch_pool;
485 get_all = TRUE; /* We need unmodified descendants of copies */
489 /* Walk status handles files and directories */
490 SVN_ERR(svn_wc__internal_walk_status(wc_ctx->db, local_abspath, depth,
492 TRUE /* no_ignore */,
493 FALSE /* ignore_text_mods */,
494 NULL /* ignore_patterns */,
495 diff_status_callback, &eb,
496 cancel_func, cancel_baton,
499 /* Close the remaining open directories */
502 struct node_state_t *ns = eb.cur;
507 SVN_ERR(diff_processor->dir_changed(ns->relpath,
517 SVN_ERR(diff_processor->dir_closed(ns->relpath,
525 svn_pool_clear(ns->pool);
532 svn_wc_diff6(svn_wc_context_t *wc_ctx,
533 const char *local_abspath,
534 const svn_wc_diff_callbacks4_t *callbacks,
535 void *callback_baton,
537 svn_boolean_t ignore_ancestry,
538 svn_boolean_t show_copies_as_adds,
539 svn_boolean_t use_git_diff_format,
540 const apr_array_header_t *changelist_filter,
541 svn_cancel_func_t cancel_func,
543 apr_pool_t *scratch_pool)
545 const svn_diff_tree_processor_t *processor;
547 SVN_ERR(svn_wc__wrap_diff_callbacks(&processor,
548 callbacks, callback_baton, TRUE,
549 scratch_pool, scratch_pool));
551 if (use_git_diff_format)
552 show_copies_as_adds = TRUE;
553 if (show_copies_as_adds)
554 ignore_ancestry = FALSE;
556 if (! show_copies_as_adds && !use_git_diff_format)
557 processor = svn_diff__tree_processor_copy_as_changed_create(processor,
560 return svn_error_trace(svn_wc__diff7(NULL, NULL,
561 wc_ctx, local_abspath,
566 cancel_func, cancel_baton,
567 scratch_pool, scratch_pool));