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