1/* 2 * diff_local.c -- A simple diff walker which compares local files against 3 * their pristine versions. 4 * 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 13 * 14 * http://www.apache.org/licenses/LICENSE-2.0 15 * 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 21 * under the License. 22 * ==================================================================== 23 * 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. 28 * 29 * You can look at this as another form of the status walker. 30 */ 31 32#include <apr_hash.h> 33 34#include "svn_error.h" 35#include "svn_pools.h" 36#include "svn_dirent_uri.h" 37#include "svn_path.h" 38#include "svn_hash.h" 39 40#include "private/svn_wc_private.h" 41#include "private/svn_diff_tree.h" 42 43#include "wc.h" 44#include "wc_db.h" 45#include "props.h" 46#include "diff.h" 47 48#include "svn_private_config.h" 49 50/*-------------------------------------------------------------------------*/ 51 52/* Baton containing the state of a directory 53 reported open via a diff processor */ 54struct node_state_t 55{ 56 struct node_state_t *parent; 57 58 apr_pool_t *pool; 59 60 const char *local_abspath; 61 const char *relpath; 62 void *baton; 63 64 svn_diff_source_t *left_src; 65 svn_diff_source_t *right_src; 66 svn_diff_source_t *copy_src; 67 68 svn_boolean_t skip; 69 svn_boolean_t skip_children; 70 71 apr_hash_t *left_props; 72 apr_hash_t *right_props; 73 const apr_array_header_t *propchanges; 74}; 75 76/* The diff baton */ 77struct diff_baton 78{ 79 /* A wc db. */ 80 svn_wc__db_t *db; 81 82 /* Report editor paths relative from this directory */ 83 const char *anchor_abspath; 84 85 struct node_state_t *cur; 86 87 const svn_diff_tree_processor_t *processor; 88 89 /* Should this diff ignore node ancestry? */ 90 svn_boolean_t ignore_ancestry; 91 92 /* Cancel function/baton */ 93 svn_cancel_func_t cancel_func; 94 void *cancel_baton; 95 96 apr_pool_t *pool; 97}; 98 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. 102 */ 103static svn_error_t * 104ensure_state(struct diff_baton *eb, 105 const char *local_abspath, 106 svn_boolean_t recursive_skip, 107 apr_pool_t *scratch_pool) 108{ 109 struct node_state_t *ns; 110 apr_pool_t *ns_pool; 111 if (!eb->cur) 112 { 113 const char *relpath; 114 115 relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, local_abspath); 116 if (! relpath) 117 return SVN_NO_ERROR; 118 119 /* Don't recurse on the anchor, as that might loop infinitely because 120 svn_dirent_dirname("/",...) -> "/" 121 svn_dirent_dirname("C:/",...) -> "C:/" (Windows) */ 122 if (*relpath) 123 SVN_ERR(ensure_state(eb, 124 svn_dirent_dirname(local_abspath, scratch_pool), 125 FALSE, 126 scratch_pool)); 127 } 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), 130 FALSE, 131 scratch_pool)); 132 else 133 return SVN_NO_ERROR; 134 135 if (eb->cur && eb->cur->skip_children) 136 return SVN_NO_ERROR; 137 138 ns_pool = svn_pool_create(eb->cur ? eb->cur->pool : eb->pool); 139 ns = apr_pcalloc(ns_pool, sizeof(*ns)); 140 141 ns->pool = ns_pool; 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; 145 eb->cur = ns; 146 147 if (recursive_skip) 148 { 149 ns->skip = TRUE; 150 ns->skip_children = TRUE; 151 return SVN_NO_ERROR; 152 } 153 154 { 155 svn_revnum_t revision; 156 svn_error_t *err; 157 158 err = svn_wc__db_base_get_info(NULL, NULL, &revision, NULL, NULL, NULL, 159 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 160 NULL, NULL, NULL, 161 eb->db, local_abspath, 162 scratch_pool, scratch_pool); 163 164 if (err) 165 { 166 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 167 return svn_error_trace(err); 168 svn_error_clear(err); 169 170 revision = 0; /* Use original revision? */ 171 } 172 ns->left_src = svn_diff__source_create(revision, ns->pool); 173 ns->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, ns->pool); 174 175 SVN_ERR(eb->processor->dir_opened(&ns->baton, &ns->skip, 176 &ns->skip_children, 177 ns->relpath, 178 ns->left_src, 179 ns->right_src, 180 NULL /* copyfrom_source */, 181 ns->parent ? ns->parent->baton : NULL, 182 eb->processor, 183 ns->pool, scratch_pool)); 184 } 185 186 return SVN_NO_ERROR; 187} 188 189/* Implements svn_wc_status_func3_t */ 190static svn_error_t * 191diff_status_callback(void *baton, 192 const char *local_abspath, 193 const svn_wc_status3_t *status, 194 apr_pool_t *scratch_pool) 195{ 196 struct diff_baton *eb = baton; 197 svn_wc__db_t *db = eb->db; 198 199 if (! status->versioned) 200 return SVN_NO_ERROR; /* unversioned (includes dir externals) */ 201 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) 205 { 206 /* Node is an actual only node describing a tree conflict */ 207 return SVN_NO_ERROR; 208 } 209 210 /* Not text/prop modified, not copied. Easy out */ 211 if (status->node_status == svn_wc_status_normal && !status->copied) 212 return SVN_NO_ERROR; 213 214 /* Mark all directories where we are no longer inside as closed */ 215 while (eb->cur 216 && !svn_dirent_is_ancestor(eb->cur->local_abspath, local_abspath)) 217 { 218 struct node_state_t *ns = eb->cur; 219 220 if (!ns->skip) 221 { 222 if (ns->propchanges) 223 SVN_ERR(eb->processor->dir_changed(ns->relpath, 224 ns->left_src, 225 ns->right_src, 226 ns->left_props, 227 ns->right_props, 228 ns->propchanges, 229 ns->baton, 230 eb->processor, 231 ns->pool)); 232 else 233 SVN_ERR(eb->processor->dir_closed(ns->relpath, 234 ns->left_src, 235 ns->right_src, 236 ns->baton, 237 eb->processor, 238 ns->pool)); 239 } 240 eb->cur = ns->parent; 241 svn_pool_clear(ns->pool); 242 } 243 SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool), 244 FALSE, scratch_pool)); 245 246 if (eb->cur && eb->cur->skip_children) 247 return SVN_NO_ERROR; 248 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) */ 253 254 { 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; 262 263 const char *child_abspath = local_abspath; 264 const char *child_relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, 265 local_abspath); 266 267 268 repos_only = FALSE; 269 local_only = FALSE; 270 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)); 280 if (!have_base) 281 { 282 local_only = TRUE; /* Only report additions */ 283 } 284 else if (db_status == svn_wc__db_status_normal) 285 { 286 /* Simple diff */ 287 base_kind = db_kind; 288 } 289 else if (db_status == svn_wc__db_status_deleted) 290 { 291 svn_wc__db_status_t base_status; 292 repos_only = TRUE; 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, 296 NULL, NULL, NULL, 297 eb->db, local_abspath, 298 scratch_pool, scratch_pool)); 299 300 if (base_status != svn_wc__db_status_normal) 301 return SVN_NO_ERROR; 302 } 303 else 304 { 305 /* working status is either added or deleted */ 306 svn_wc__db_status_t base_status; 307 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, 311 NULL, NULL, NULL, 312 eb->db, local_abspath, 313 scratch_pool, scratch_pool)); 314 315 if (base_status != svn_wc__db_status_normal) 316 local_only = TRUE; 317 else if (base_kind != db_kind || !eb->ignore_ancestry) 318 { 319 repos_only = TRUE; 320 local_only = TRUE; 321 } 322 } 323 324 if (repos_only) 325 { 326 /* Report repository form deleted */ 327 if (base_kind == svn_node_file) 328 SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath, 329 child_relpath, 330 SVN_INVALID_REVNUM, 331 eb->processor, 332 eb->cur ? eb->cur->baton : NULL, 333 scratch_pool)); 334 else if (base_kind == svn_node_dir) 335 SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath, 336 child_relpath, 337 SVN_INVALID_REVNUM, 338 depth_below_here, 339 eb->processor, 340 eb->cur ? eb->cur->baton : NULL, 341 eb->cancel_func, 342 eb->cancel_baton, 343 scratch_pool)); 344 } 345 else if (!local_only) 346 { 347 /* Diff base against actual */ 348 if (db_kind == svn_node_file) 349 { 350 SVN_ERR(svn_wc__diff_base_working_diff(db, child_abspath, 351 child_relpath, 352 SVN_INVALID_REVNUM, 353 eb->processor, 354 eb->cur 355 ? eb->cur->baton 356 : NULL, 357 FALSE, 358 eb->cancel_func, 359 eb->cancel_baton, 360 scratch_pool)); 361 } 362 else if (db_kind == svn_node_dir) 363 { 364 SVN_ERR(ensure_state(eb, local_abspath, FALSE, scratch_pool)); 365 366 if (status->prop_status != svn_wc_status_none 367 && status->prop_status != svn_wc_status_normal) 368 { 369 apr_array_header_t *propchanges; 370 SVN_ERR(svn_wc__db_base_get_props(&eb->cur->left_props, 371 eb->db, local_abspath, 372 eb->cur->pool, 373 scratch_pool)); 374 SVN_ERR(svn_wc__db_read_props(&eb->cur->right_props, 375 eb->db, local_abspath, 376 eb->cur->pool, 377 scratch_pool)); 378 379 SVN_ERR(svn_prop_diffs(&propchanges, 380 eb->cur->right_props, 381 eb->cur->left_props, 382 eb->cur->pool)); 383 384 eb->cur->propchanges = propchanges; 385 } 386 } 387 } 388 389 if (local_only && (db_status != svn_wc__db_status_deleted)) 390 { 391 if (db_kind == svn_node_file) 392 SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, 393 child_relpath, 394 eb->processor, 395 eb->cur ? eb->cur->baton : NULL, 396 FALSE, 397 eb->cancel_func, 398 eb->cancel_baton, 399 scratch_pool)); 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, 403 eb->processor, 404 eb->cur ? eb->cur->baton : NULL, 405 FALSE, 406 eb->cancel_func, 407 eb->cancel_baton, 408 scratch_pool)); 409 } 410 411 if (db_kind == svn_node_dir && (local_only || repos_only)) 412 SVN_ERR(ensure_state(eb, local_abspath, TRUE /* skip */, scratch_pool)); 413 } 414 415 return SVN_NO_ERROR; 416} 417 418 419/* Public Interface */ 420svn_error_t * 421svn_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, 425 svn_depth_t depth, 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, 430 void *cancel_baton, 431 apr_pool_t *result_pool, 432 apr_pool_t *scratch_pool) 433{ 434 struct diff_baton eb = { 0 }; 435 svn_node_kind_t kind; 436 svn_boolean_t get_all; 437 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 */, 443 scratch_pool)); 444 445 eb.anchor_abspath = local_abspath; 446 447 if (root_relpath) 448 { 449 svn_boolean_t is_wcroot; 450 451 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, 452 wc_ctx->db, local_abspath, scratch_pool)); 453 454 if (!is_wcroot) 455 eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool); 456 } 457 else if (kind != svn_node_dir) 458 eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool); 459 460 if (root_relpath) 461 *root_relpath = apr_pstrdup(result_pool, 462 svn_dirent_skip_ancestor(eb.anchor_abspath, 463 local_abspath)); 464 if (root_is_dir) 465 *root_is_dir = (kind == svn_node_dir); 466 467 /* Apply changelist filtering to the output */ 468 if (changelist_filter && changelist_filter->nelts) 469 { 470 apr_hash_t *changelist_hash; 471 472 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter, 473 result_pool)); 474 diff_processor = svn_wc__changelist_filter_tree_processor_create( 475 diff_processor, wc_ctx, local_abspath, 476 changelist_hash, result_pool); 477 } 478 479 eb.db = wc_ctx->db; 480 eb.processor = diff_processor; 481 eb.ignore_ancestry = ignore_ancestry; 482 eb.pool = scratch_pool; 483 484 if (ignore_ancestry) 485 get_all = TRUE; /* We need unmodified descendants of copies */ 486 else 487 get_all = FALSE; 488 489 /* Walk status handles files and directories */ 490 SVN_ERR(svn_wc__internal_walk_status(wc_ctx->db, local_abspath, depth, 491 get_all, 492 TRUE /* no_ignore */, 493 FALSE /* ignore_text_mods */, 494 NULL /* ignore_patterns */, 495 diff_status_callback, &eb, 496 cancel_func, cancel_baton, 497 scratch_pool)); 498 499 /* Close the remaining open directories */ 500 while (eb.cur) 501 { 502 struct node_state_t *ns = eb.cur; 503 504 if (!ns->skip) 505 { 506 if (ns->propchanges) 507 SVN_ERR(diff_processor->dir_changed(ns->relpath, 508 ns->left_src, 509 ns->right_src, 510 ns->left_props, 511 ns->right_props, 512 ns->propchanges, 513 ns->baton, 514 diff_processor, 515 ns->pool)); 516 else 517 SVN_ERR(diff_processor->dir_closed(ns->relpath, 518 ns->left_src, 519 ns->right_src, 520 ns->baton, 521 diff_processor, 522 ns->pool)); 523 } 524 eb.cur = ns->parent; 525 svn_pool_clear(ns->pool); 526 } 527 528 return SVN_NO_ERROR; 529} 530 531svn_error_t * 532svn_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, 536 svn_depth_t depth, 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, 542 void *cancel_baton, 543 apr_pool_t *scratch_pool) 544{ 545 const svn_diff_tree_processor_t *processor; 546 547 SVN_ERR(svn_wc__wrap_diff_callbacks(&processor, 548 callbacks, callback_baton, TRUE, 549 scratch_pool, scratch_pool)); 550 551 if (use_git_diff_format) 552 show_copies_as_adds = TRUE; 553 if (show_copies_as_adds) 554 ignore_ancestry = FALSE; 555 556 if (! show_copies_as_adds && !use_git_diff_format) 557 processor = svn_diff__tree_processor_copy_as_changed_create(processor, 558 scratch_pool); 559 560 return svn_error_trace(svn_wc__diff7(NULL, NULL, 561 wc_ctx, local_abspath, 562 depth, 563 ignore_ancestry, 564 changelist_filter, 565 processor, 566 cancel_func, cancel_baton, 567 scratch_pool, scratch_pool)); 568} 569 570