1/* 2 * diff_pristine.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 "props.h" 45#include "translate.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 /* Should this diff not compare copied files with their source? */ 93 svn_boolean_t show_copies_as_adds; 94 95 /* Cancel function/baton */ 96 svn_cancel_func_t cancel_func; 97 void *cancel_baton; 98 99 apr_pool_t *pool; 100}; 101 102/* Recursively opens directories on the stack in EB, until LOCAL_ABSPATH 103 is reached. If RECURSIVE_SKIP is TRUE, don't open LOCAL_ABSPATH itself, 104 but create it marked with skip+skip_children. 105 */ 106static svn_error_t * 107ensure_state(struct diff_baton *eb, 108 const char *local_abspath, 109 svn_boolean_t recursive_skip, 110 apr_pool_t *scratch_pool) 111{ 112 struct node_state_t *ns; 113 apr_pool_t *ns_pool; 114 if (!eb->cur) 115 { 116 const char *relpath; 117 118 relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, local_abspath); 119 if (! relpath) 120 return SVN_NO_ERROR; 121 122 /* Don't recurse on the anchor, as that might loop infinately because 123 svn_dirent_dirname("/",...) -> "/" 124 svn_dirent_dirname("C:/",...) -> "C:/" (Windows) */ 125 if (*relpath) 126 SVN_ERR(ensure_state(eb, 127 svn_dirent_dirname(local_abspath,scratch_pool), 128 FALSE, 129 scratch_pool)); 130 } 131 else if (svn_dirent_is_child(eb->cur->local_abspath, local_abspath, NULL)) 132 SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath,scratch_pool), 133 FALSE, 134 scratch_pool)); 135 else 136 return SVN_NO_ERROR; 137 138 if (eb->cur && eb->cur->skip_children) 139 return SVN_NO_ERROR; 140 141 ns_pool = svn_pool_create(eb->cur ? eb->cur->pool : eb->pool); 142 ns = apr_pcalloc(ns_pool, sizeof(*ns)); 143 144 ns->pool = ns_pool; 145 ns->local_abspath = apr_pstrdup(ns_pool, local_abspath); 146 ns->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, ns->local_abspath); 147 ns->parent = eb->cur; 148 eb->cur = ns; 149 150 if (recursive_skip) 151 { 152 ns->skip = TRUE; 153 ns->skip_children = TRUE; 154 return SVN_NO_ERROR; 155 } 156 157 { 158 svn_revnum_t revision; 159 svn_error_t *err; 160 161 err = svn_wc__db_base_get_info(NULL, NULL, &revision, NULL, NULL, NULL, 162 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 163 NULL, NULL, NULL, 164 eb->db, local_abspath, 165 scratch_pool, scratch_pool); 166 167 if (err) 168 { 169 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 170 return svn_error_trace(err); 171 svn_error_clear(err); 172 173 revision = 0; /* Use original revision? */ 174 } 175 ns->left_src = svn_diff__source_create(revision, ns->pool); 176 ns->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, ns->pool); 177 178 SVN_ERR(eb->processor->dir_opened(&ns->baton, &ns->skip, 179 &ns->skip_children, 180 ns->relpath, 181 ns->left_src, 182 ns->right_src, 183 NULL /* copyfrom_source */, 184 ns->parent ? ns->parent->baton : NULL, 185 eb->processor, 186 ns->pool, scratch_pool)); 187 } 188 189 return SVN_NO_ERROR; 190} 191 192/* Implements svn_wc_status_func3_t */ 193static svn_error_t * 194diff_status_callback(void *baton, 195 const char *local_abspath, 196 const svn_wc_status3_t *status, 197 apr_pool_t *scratch_pool) 198{ 199 struct diff_baton *eb = baton; 200 svn_wc__db_t *db = eb->db; 201 202 if (! status->versioned) 203 return SVN_NO_ERROR; /* unversioned (includes dir externals) */ 204 205 if (status->node_status == svn_wc_status_conflicted 206 && status->text_status == svn_wc_status_none 207 && status->prop_status == svn_wc_status_none) 208 { 209 /* Node is an actual only node describing a tree conflict */ 210 return SVN_NO_ERROR; 211 } 212 213 /* Not text/prop modified, not copied. Easy out */ 214 if (status->node_status == svn_wc_status_normal && !status->copied) 215 return SVN_NO_ERROR; 216 217 /* Mark all directories where we are no longer inside as closed */ 218 while (eb->cur 219 && !svn_dirent_is_ancestor(eb->cur->local_abspath, local_abspath)) 220 { 221 struct node_state_t *ns = eb->cur; 222 223 if (!ns->skip) 224 { 225 if (ns->propchanges) 226 SVN_ERR(eb->processor->dir_changed(ns->relpath, 227 ns->left_src, 228 ns->right_src, 229 ns->left_props, 230 ns->right_props, 231 ns->propchanges, 232 ns->baton, 233 eb->processor, 234 ns->pool)); 235 else 236 SVN_ERR(eb->processor->dir_closed(ns->relpath, 237 ns->left_src, 238 ns->right_src, 239 ns->baton, 240 eb->processor, 241 ns->pool)); 242 } 243 eb->cur = ns->parent; 244 svn_pool_clear(ns->pool); 245 } 246 SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool), 247 FALSE, scratch_pool)); 248 249 if (eb->cur && eb->cur->skip_children) 250 return SVN_NO_ERROR; 251 252 /* This code does about the same thing as the inner body of 253 walk_local_nodes_diff() in diff_editor.c, except that 254 it is already filtered by the status walker, doesn't have to 255 account for remote changes (and many tiny other details) */ 256 257 { 258 svn_boolean_t repos_only; 259 svn_boolean_t local_only; 260 svn_wc__db_status_t db_status; 261 svn_boolean_t have_base; 262 svn_node_kind_t base_kind; 263 svn_node_kind_t db_kind = status->kind; 264 svn_depth_t depth_below_here = svn_depth_unknown; 265 266 const char *child_abspath = local_abspath; 267 const char *child_relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, 268 local_abspath); 269 270 271 repos_only = FALSE; 272 local_only = FALSE; 273 274 /* ### optimize away this call using status info. Should 275 be possible in almost every case (except conflict, missing, obst.)*/ 276 SVN_ERR(svn_wc__db_read_info(&db_status, NULL, NULL, NULL, NULL, NULL, 277 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 278 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 279 NULL, NULL, NULL, NULL, 280 &have_base, NULL, NULL, 281 eb->db, local_abspath, 282 scratch_pool, scratch_pool)); 283 if (!have_base) 284 { 285 local_only = TRUE; /* Only report additions */ 286 } 287 else if (db_status == svn_wc__db_status_normal) 288 { 289 /* Simple diff */ 290 base_kind = db_kind; 291 } 292 else if (db_status == svn_wc__db_status_deleted) 293 { 294 svn_wc__db_status_t base_status; 295 repos_only = TRUE; 296 SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, 297 NULL, NULL, NULL, NULL, NULL, 298 NULL, NULL, NULL, NULL, NULL, 299 NULL, NULL, NULL, 300 eb->db, local_abspath, 301 scratch_pool, scratch_pool)); 302 303 if (base_status != svn_wc__db_status_normal) 304 return SVN_NO_ERROR; 305 } 306 else 307 { 308 /* working status is either added or deleted */ 309 svn_wc__db_status_t base_status; 310 311 SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, 312 NULL, NULL, NULL, NULL, NULL, 313 NULL, NULL, NULL, NULL, NULL, 314 NULL, NULL, NULL, 315 eb->db, local_abspath, 316 scratch_pool, scratch_pool)); 317 318 if (base_status != svn_wc__db_status_normal) 319 local_only = TRUE; 320 else if (base_kind != db_kind || !eb->ignore_ancestry) 321 { 322 repos_only = TRUE; 323 local_only = TRUE; 324 } 325 } 326 327 if (repos_only) 328 { 329 /* Report repository form deleted */ 330 if (base_kind == svn_node_file) 331 SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath, 332 child_relpath, 333 SVN_INVALID_REVNUM, 334 eb->processor, 335 eb->cur ? eb->cur->baton : NULL, 336 scratch_pool)); 337 else if (base_kind == svn_node_dir) 338 SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath, 339 child_relpath, 340 SVN_INVALID_REVNUM, 341 depth_below_here, 342 eb->processor, 343 eb->cur ? eb->cur->baton : NULL, 344 eb->cancel_func, 345 eb->cancel_baton, 346 scratch_pool)); 347 } 348 else if (!local_only) 349 { 350 /* Diff base against actual */ 351 if (db_kind == svn_node_file) 352 { 353 SVN_ERR(svn_wc__diff_base_working_diff(db, child_abspath, 354 child_relpath, 355 SVN_INVALID_REVNUM, 356 eb->processor, 357 eb->cur 358 ? eb->cur->baton 359 : NULL, 360 FALSE, 361 eb->cancel_func, 362 eb->cancel_baton, 363 scratch_pool)); 364 } 365 else if (db_kind == svn_node_dir) 366 { 367 SVN_ERR(ensure_state(eb, local_abspath, FALSE, scratch_pool)); 368 369 if (status->prop_status != svn_wc_status_none 370 && status->prop_status != svn_wc_status_normal) 371 { 372 apr_array_header_t *propchanges; 373 SVN_ERR(svn_wc__db_base_get_props(&eb->cur->left_props, 374 eb->db, local_abspath, 375 eb->cur->pool, 376 scratch_pool)); 377 SVN_ERR(svn_wc__db_read_props(&eb->cur->right_props, 378 eb->db, local_abspath, 379 eb->cur->pool, 380 scratch_pool)); 381 382 SVN_ERR(svn_prop_diffs(&propchanges, 383 eb->cur->right_props, 384 eb->cur->left_props, 385 eb->cur->pool)); 386 387 eb->cur->propchanges = propchanges; 388 } 389 } 390 } 391 392 if (local_only && (db_status != svn_wc__db_status_deleted)) 393 { 394 if (db_kind == svn_node_file) 395 SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, 396 child_relpath, 397 eb->processor, 398 eb->cur ? eb->cur->baton : NULL, 399 FALSE, 400 eb->cancel_func, 401 eb->cancel_baton, 402 scratch_pool)); 403 else if (db_kind == svn_node_dir) 404 SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath, 405 child_relpath, depth_below_here, 406 eb->processor, 407 eb->cur ? eb->cur->baton : NULL, 408 FALSE, 409 eb->cancel_func, 410 eb->cancel_baton, 411 scratch_pool)); 412 } 413 414 if (db_kind == svn_node_dir && (local_only || repos_only)) 415 SVN_ERR(ensure_state(eb, local_abspath, TRUE /* skip */, scratch_pool)); 416 } 417 418 return SVN_NO_ERROR; 419} 420 421 422/* Public Interface */ 423svn_error_t * 424svn_wc_diff6(svn_wc_context_t *wc_ctx, 425 const char *local_abspath, 426 const svn_wc_diff_callbacks4_t *callbacks, 427 void *callback_baton, 428 svn_depth_t depth, 429 svn_boolean_t ignore_ancestry, 430 svn_boolean_t show_copies_as_adds, 431 svn_boolean_t use_git_diff_format, 432 const apr_array_header_t *changelist_filter, 433 svn_cancel_func_t cancel_func, 434 void *cancel_baton, 435 apr_pool_t *scratch_pool) 436{ 437 struct diff_baton eb = { 0 }; 438 svn_node_kind_t kind; 439 svn_boolean_t get_all; 440 const svn_diff_tree_processor_t *processor; 441 442 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 443 SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath, 444 FALSE /* allow_missing */, 445 TRUE /* show_deleted */, 446 FALSE /* show_hidden */, 447 scratch_pool)); 448 449 if (kind == svn_node_dir) 450 eb.anchor_abspath = local_abspath; 451 else 452 eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool); 453 454 SVN_ERR(svn_wc__wrap_diff_callbacks(&processor, 455 callbacks, callback_baton, TRUE, 456 scratch_pool, scratch_pool)); 457 458 if (use_git_diff_format) 459 show_copies_as_adds = TRUE; 460 if (show_copies_as_adds) 461 ignore_ancestry = FALSE; 462 463 464 465 /* 466 if (reverse_order) 467 processor = svn_diff__tree_processor_reverse_create(processor, NULL, pool); 468 */ 469 470 if (! show_copies_as_adds && !use_git_diff_format) 471 processor = svn_diff__tree_processor_copy_as_changed_create(processor, 472 scratch_pool); 473 474 /* Apply changelist filtering to the output */ 475 if (changelist_filter && changelist_filter->nelts) 476 { 477 apr_hash_t *changelist_hash; 478 479 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter, 480 scratch_pool)); 481 processor = svn_wc__changelist_filter_tree_processor_create( 482 processor, wc_ctx, local_abspath, 483 changelist_hash, scratch_pool); 484 } 485 486 eb.db = wc_ctx->db; 487 eb.processor = processor; 488 eb.ignore_ancestry = ignore_ancestry; 489 eb.show_copies_as_adds = show_copies_as_adds; 490 eb.pool = scratch_pool; 491 492 if (show_copies_as_adds || use_git_diff_format || !ignore_ancestry) 493 get_all = TRUE; /* We need unmodified descendants of copies */ 494 else 495 get_all = FALSE; 496 497 /* Walk status handles files and directories */ 498 SVN_ERR(svn_wc__internal_walk_status(wc_ctx->db, local_abspath, depth, 499 get_all, 500 TRUE /* no_ignore */, 501 FALSE /* ignore_text_mods */, 502 NULL /* ignore_patterns */, 503 diff_status_callback, &eb, 504 cancel_func, cancel_baton, 505 scratch_pool)); 506 507 /* Close the remaining open directories */ 508 while (eb.cur) 509 { 510 struct node_state_t *ns = eb.cur; 511 512 if (!ns->skip) 513 { 514 if (ns->propchanges) 515 SVN_ERR(processor->dir_changed(ns->relpath, 516 ns->left_src, 517 ns->right_src, 518 ns->left_props, 519 ns->right_props, 520 ns->propchanges, 521 ns->baton, 522 processor, 523 ns->pool)); 524 else 525 SVN_ERR(processor->dir_closed(ns->relpath, 526 ns->left_src, 527 ns->right_src, 528 ns->baton, 529 processor, 530 ns->pool)); 531 } 532 eb.cur = ns->parent; 533 svn_pool_clear(ns->pool); 534 } 535 536 return SVN_NO_ERROR; 537} 538