1251881Speter/* 2251881Speter * diff_pristine.c -- A simple diff walker which compares local files against 3251881Speter * their pristine versions. 4251881Speter * 5251881Speter * ==================================================================== 6251881Speter * Licensed to the Apache Software Foundation (ASF) under one 7251881Speter * or more contributor license agreements. See the NOTICE file 8251881Speter * distributed with this work for additional information 9251881Speter * regarding copyright ownership. The ASF licenses this file 10251881Speter * to you under the Apache License, Version 2.0 (the 11251881Speter * "License"); you may not use this file except in compliance 12251881Speter * with the License. You may obtain a copy of the License at 13251881Speter * 14251881Speter * http://www.apache.org/licenses/LICENSE-2.0 15251881Speter * 16251881Speter * Unless required by applicable law or agreed to in writing, 17251881Speter * software distributed under the License is distributed on an 18251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19251881Speter * KIND, either express or implied. See the License for the 20251881Speter * specific language governing permissions and limitations 21251881Speter * under the License. 22251881Speter * ==================================================================== 23251881Speter * 24251881Speter * This is the simple working copy diff algorithm which is used when you 25251881Speter * just use 'svn diff PATH'. It shows what is modified in your working copy 26251881Speter * since a node was checked out or copied but doesn't show most kinds of 27251881Speter * restructuring operations. 28251881Speter * 29251881Speter * You can look at this as another form of the status walker. 30251881Speter */ 31251881Speter 32251881Speter#include <apr_hash.h> 33251881Speter 34251881Speter#include "svn_error.h" 35251881Speter#include "svn_pools.h" 36251881Speter#include "svn_dirent_uri.h" 37251881Speter#include "svn_path.h" 38251881Speter#include "svn_hash.h" 39251881Speter 40251881Speter#include "private/svn_wc_private.h" 41251881Speter#include "private/svn_diff_tree.h" 42251881Speter 43251881Speter#include "wc.h" 44251881Speter#include "props.h" 45251881Speter#include "translate.h" 46251881Speter#include "diff.h" 47251881Speter 48251881Speter#include "svn_private_config.h" 49251881Speter 50251881Speter/*-------------------------------------------------------------------------*/ 51251881Speter 52251881Speter/* Baton containing the state of a directory 53251881Speter reported open via a diff processor */ 54251881Speterstruct node_state_t 55251881Speter{ 56251881Speter struct node_state_t *parent; 57251881Speter 58251881Speter apr_pool_t *pool; 59251881Speter 60251881Speter const char *local_abspath; 61251881Speter const char *relpath; 62251881Speter void *baton; 63251881Speter 64251881Speter svn_diff_source_t *left_src; 65251881Speter svn_diff_source_t *right_src; 66251881Speter svn_diff_source_t *copy_src; 67251881Speter 68251881Speter svn_boolean_t skip; 69251881Speter svn_boolean_t skip_children; 70251881Speter 71251881Speter apr_hash_t *left_props; 72251881Speter apr_hash_t *right_props; 73251881Speter const apr_array_header_t *propchanges; 74251881Speter}; 75251881Speter 76251881Speter/* The diff baton */ 77251881Speterstruct diff_baton 78251881Speter{ 79251881Speter /* A wc db. */ 80251881Speter svn_wc__db_t *db; 81251881Speter 82251881Speter /* Report editor paths relative from this directory */ 83251881Speter const char *anchor_abspath; 84251881Speter 85251881Speter struct node_state_t *cur; 86251881Speter 87251881Speter const svn_diff_tree_processor_t *processor; 88251881Speter 89251881Speter /* Should this diff ignore node ancestry? */ 90251881Speter svn_boolean_t ignore_ancestry; 91251881Speter 92251881Speter /* Should this diff not compare copied files with their source? */ 93251881Speter svn_boolean_t show_copies_as_adds; 94251881Speter 95251881Speter /* Cancel function/baton */ 96251881Speter svn_cancel_func_t cancel_func; 97251881Speter void *cancel_baton; 98251881Speter 99251881Speter apr_pool_t *pool; 100251881Speter}; 101251881Speter 102251881Speter/* Recursively opens directories on the stack in EB, until LOCAL_ABSPATH 103251881Speter is reached. If RECURSIVE_SKIP is TRUE, don't open LOCAL_ABSPATH itself, 104251881Speter but create it marked with skip+skip_children. 105251881Speter */ 106251881Speterstatic svn_error_t * 107251881Speterensure_state(struct diff_baton *eb, 108251881Speter const char *local_abspath, 109251881Speter svn_boolean_t recursive_skip, 110251881Speter apr_pool_t *scratch_pool) 111251881Speter{ 112251881Speter struct node_state_t *ns; 113251881Speter apr_pool_t *ns_pool; 114251881Speter if (!eb->cur) 115251881Speter { 116262253Speter const char *relpath; 117262253Speter 118262253Speter relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, local_abspath); 119262253Speter if (! relpath) 120251881Speter return SVN_NO_ERROR; 121251881Speter 122262253Speter /* Don't recurse on the anchor, as that might loop infinately because 123262253Speter svn_dirent_dirname("/",...) -> "/" 124262253Speter svn_dirent_dirname("C:/",...) -> "C:/" (Windows) */ 125262253Speter if (*relpath) 126262253Speter SVN_ERR(ensure_state(eb, 127262253Speter svn_dirent_dirname(local_abspath,scratch_pool), 128262253Speter FALSE, 129262253Speter scratch_pool)); 130251881Speter } 131251881Speter else if (svn_dirent_is_child(eb->cur->local_abspath, local_abspath, NULL)) 132251881Speter SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath,scratch_pool), 133251881Speter FALSE, 134251881Speter scratch_pool)); 135251881Speter else 136251881Speter return SVN_NO_ERROR; 137251881Speter 138251881Speter if (eb->cur && eb->cur->skip_children) 139251881Speter return SVN_NO_ERROR; 140251881Speter 141251881Speter ns_pool = svn_pool_create(eb->cur ? eb->cur->pool : eb->pool); 142251881Speter ns = apr_pcalloc(ns_pool, sizeof(*ns)); 143251881Speter 144251881Speter ns->pool = ns_pool; 145251881Speter ns->local_abspath = apr_pstrdup(ns_pool, local_abspath); 146251881Speter ns->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, ns->local_abspath); 147251881Speter ns->parent = eb->cur; 148251881Speter eb->cur = ns; 149251881Speter 150251881Speter if (recursive_skip) 151251881Speter { 152251881Speter ns->skip = TRUE; 153251881Speter ns->skip_children = TRUE; 154251881Speter return SVN_NO_ERROR; 155251881Speter } 156251881Speter 157251881Speter { 158251881Speter svn_revnum_t revision; 159251881Speter svn_error_t *err; 160251881Speter 161251881Speter err = svn_wc__db_base_get_info(NULL, NULL, &revision, NULL, NULL, NULL, 162251881Speter NULL, NULL, NULL, NULL, NULL, NULL, NULL, 163251881Speter NULL, NULL, NULL, 164251881Speter eb->db, local_abspath, 165251881Speter scratch_pool, scratch_pool); 166251881Speter 167251881Speter if (err) 168251881Speter { 169251881Speter if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 170251881Speter return svn_error_trace(err); 171251881Speter svn_error_clear(err); 172251881Speter 173251881Speter revision = 0; /* Use original revision? */ 174251881Speter } 175251881Speter ns->left_src = svn_diff__source_create(revision, ns->pool); 176251881Speter ns->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, ns->pool); 177251881Speter 178251881Speter SVN_ERR(eb->processor->dir_opened(&ns->baton, &ns->skip, 179251881Speter &ns->skip_children, 180251881Speter ns->relpath, 181251881Speter ns->left_src, 182251881Speter ns->right_src, 183251881Speter NULL /* copyfrom_source */, 184251881Speter ns->parent ? ns->parent->baton : NULL, 185251881Speter eb->processor, 186251881Speter ns->pool, scratch_pool)); 187251881Speter } 188251881Speter 189251881Speter return SVN_NO_ERROR; 190251881Speter} 191251881Speter 192251881Speter/* Implements svn_wc_status_func3_t */ 193251881Speterstatic svn_error_t * 194251881Speterdiff_status_callback(void *baton, 195251881Speter const char *local_abspath, 196251881Speter const svn_wc_status3_t *status, 197251881Speter apr_pool_t *scratch_pool) 198251881Speter{ 199251881Speter struct diff_baton *eb = baton; 200251881Speter svn_wc__db_t *db = eb->db; 201251881Speter 202262253Speter if (! status->versioned) 203262253Speter return SVN_NO_ERROR; /* unversioned (includes dir externals) */ 204262253Speter 205262253Speter if (status->node_status == svn_wc_status_conflicted 206262253Speter && status->text_status == svn_wc_status_none 207262253Speter && status->prop_status == svn_wc_status_none) 208251881Speter { 209262253Speter /* Node is an actual only node describing a tree conflict */ 210262253Speter return SVN_NO_ERROR; 211251881Speter } 212251881Speter 213251881Speter /* Not text/prop modified, not copied. Easy out */ 214251881Speter if (status->node_status == svn_wc_status_normal && !status->copied) 215251881Speter return SVN_NO_ERROR; 216251881Speter 217251881Speter /* Mark all directories where we are no longer inside as closed */ 218251881Speter while (eb->cur 219251881Speter && !svn_dirent_is_ancestor(eb->cur->local_abspath, local_abspath)) 220251881Speter { 221251881Speter struct node_state_t *ns = eb->cur; 222251881Speter 223251881Speter if (!ns->skip) 224251881Speter { 225251881Speter if (ns->propchanges) 226251881Speter SVN_ERR(eb->processor->dir_changed(ns->relpath, 227251881Speter ns->left_src, 228251881Speter ns->right_src, 229251881Speter ns->left_props, 230251881Speter ns->right_props, 231251881Speter ns->propchanges, 232251881Speter ns->baton, 233251881Speter eb->processor, 234251881Speter ns->pool)); 235251881Speter else 236251881Speter SVN_ERR(eb->processor->dir_closed(ns->relpath, 237251881Speter ns->left_src, 238251881Speter ns->right_src, 239251881Speter ns->baton, 240251881Speter eb->processor, 241251881Speter ns->pool)); 242251881Speter } 243251881Speter eb->cur = ns->parent; 244251881Speter svn_pool_clear(ns->pool); 245251881Speter } 246251881Speter SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool), 247251881Speter FALSE, scratch_pool)); 248251881Speter 249251881Speter if (eb->cur && eb->cur->skip_children) 250251881Speter return SVN_NO_ERROR; 251251881Speter 252251881Speter /* This code does about the same thing as the inner body of 253251881Speter walk_local_nodes_diff() in diff_editor.c, except that 254251881Speter it is already filtered by the status walker, doesn't have to 255251881Speter account for remote changes (and many tiny other details) */ 256251881Speter 257251881Speter { 258251881Speter svn_boolean_t repos_only; 259251881Speter svn_boolean_t local_only; 260251881Speter svn_wc__db_status_t db_status; 261251881Speter svn_boolean_t have_base; 262251881Speter svn_node_kind_t base_kind; 263251881Speter svn_node_kind_t db_kind = status->kind; 264251881Speter svn_depth_t depth_below_here = svn_depth_unknown; 265251881Speter 266251881Speter const char *child_abspath = local_abspath; 267251881Speter const char *child_relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, 268251881Speter local_abspath); 269251881Speter 270251881Speter 271251881Speter repos_only = FALSE; 272251881Speter local_only = FALSE; 273251881Speter 274251881Speter /* ### optimize away this call using status info. Should 275251881Speter be possible in almost every case (except conflict, missing, obst.)*/ 276251881Speter SVN_ERR(svn_wc__db_read_info(&db_status, NULL, NULL, NULL, NULL, NULL, 277251881Speter NULL, NULL, NULL, NULL, NULL, NULL, NULL, 278251881Speter NULL, NULL, NULL, NULL, NULL, NULL, NULL, 279251881Speter NULL, NULL, NULL, NULL, 280251881Speter &have_base, NULL, NULL, 281251881Speter eb->db, local_abspath, 282251881Speter scratch_pool, scratch_pool)); 283251881Speter if (!have_base) 284251881Speter { 285251881Speter local_only = TRUE; /* Only report additions */ 286251881Speter } 287251881Speter else if (db_status == svn_wc__db_status_normal) 288251881Speter { 289251881Speter /* Simple diff */ 290251881Speter base_kind = db_kind; 291251881Speter } 292251881Speter else if (db_status == svn_wc__db_status_deleted) 293251881Speter { 294251881Speter svn_wc__db_status_t base_status; 295251881Speter repos_only = TRUE; 296251881Speter SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, 297251881Speter NULL, NULL, NULL, NULL, NULL, 298251881Speter NULL, NULL, NULL, NULL, NULL, 299251881Speter NULL, NULL, NULL, 300251881Speter eb->db, local_abspath, 301251881Speter scratch_pool, scratch_pool)); 302251881Speter 303251881Speter if (base_status != svn_wc__db_status_normal) 304251881Speter return SVN_NO_ERROR; 305251881Speter } 306251881Speter else 307251881Speter { 308251881Speter /* working status is either added or deleted */ 309251881Speter svn_wc__db_status_t base_status; 310251881Speter 311251881Speter SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, 312251881Speter NULL, NULL, NULL, NULL, NULL, 313251881Speter NULL, NULL, NULL, NULL, NULL, 314251881Speter NULL, NULL, NULL, 315251881Speter eb->db, local_abspath, 316251881Speter scratch_pool, scratch_pool)); 317251881Speter 318251881Speter if (base_status != svn_wc__db_status_normal) 319251881Speter local_only = TRUE; 320251881Speter else if (base_kind != db_kind || !eb->ignore_ancestry) 321251881Speter { 322251881Speter repos_only = TRUE; 323251881Speter local_only = TRUE; 324251881Speter } 325251881Speter } 326251881Speter 327251881Speter if (repos_only) 328251881Speter { 329251881Speter /* Report repository form deleted */ 330251881Speter if (base_kind == svn_node_file) 331251881Speter SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath, 332251881Speter child_relpath, 333251881Speter SVN_INVALID_REVNUM, 334251881Speter eb->processor, 335251881Speter eb->cur ? eb->cur->baton : NULL, 336251881Speter scratch_pool)); 337251881Speter else if (base_kind == svn_node_dir) 338251881Speter SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath, 339251881Speter child_relpath, 340251881Speter SVN_INVALID_REVNUM, 341251881Speter depth_below_here, 342251881Speter eb->processor, 343251881Speter eb->cur ? eb->cur->baton : NULL, 344251881Speter eb->cancel_func, 345251881Speter eb->cancel_baton, 346251881Speter scratch_pool)); 347251881Speter } 348251881Speter else if (!local_only) 349251881Speter { 350251881Speter /* Diff base against actual */ 351251881Speter if (db_kind == svn_node_file) 352251881Speter { 353251881Speter SVN_ERR(svn_wc__diff_base_working_diff(db, child_abspath, 354251881Speter child_relpath, 355251881Speter SVN_INVALID_REVNUM, 356251881Speter eb->processor, 357251881Speter eb->cur 358251881Speter ? eb->cur->baton 359251881Speter : NULL, 360251881Speter FALSE, 361251881Speter eb->cancel_func, 362251881Speter eb->cancel_baton, 363251881Speter scratch_pool)); 364251881Speter } 365251881Speter else if (db_kind == svn_node_dir) 366251881Speter { 367251881Speter SVN_ERR(ensure_state(eb, local_abspath, FALSE, scratch_pool)); 368251881Speter 369251881Speter if (status->prop_status != svn_wc_status_none 370251881Speter && status->prop_status != svn_wc_status_normal) 371251881Speter { 372251881Speter apr_array_header_t *propchanges; 373251881Speter SVN_ERR(svn_wc__db_base_get_props(&eb->cur->left_props, 374251881Speter eb->db, local_abspath, 375251881Speter eb->cur->pool, 376251881Speter scratch_pool)); 377251881Speter SVN_ERR(svn_wc__db_read_props(&eb->cur->right_props, 378251881Speter eb->db, local_abspath, 379251881Speter eb->cur->pool, 380251881Speter scratch_pool)); 381251881Speter 382251881Speter SVN_ERR(svn_prop_diffs(&propchanges, 383251881Speter eb->cur->right_props, 384251881Speter eb->cur->left_props, 385251881Speter eb->cur->pool)); 386251881Speter 387251881Speter eb->cur->propchanges = propchanges; 388251881Speter } 389251881Speter } 390251881Speter } 391251881Speter 392262253Speter if (local_only && (db_status != svn_wc__db_status_deleted)) 393251881Speter { 394251881Speter if (db_kind == svn_node_file) 395251881Speter SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, 396251881Speter child_relpath, 397251881Speter eb->processor, 398251881Speter eb->cur ? eb->cur->baton : NULL, 399251881Speter FALSE, 400251881Speter eb->cancel_func, 401251881Speter eb->cancel_baton, 402251881Speter scratch_pool)); 403251881Speter else if (db_kind == svn_node_dir) 404251881Speter SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath, 405251881Speter child_relpath, depth_below_here, 406251881Speter eb->processor, 407251881Speter eb->cur ? eb->cur->baton : NULL, 408251881Speter FALSE, 409251881Speter eb->cancel_func, 410251881Speter eb->cancel_baton, 411251881Speter scratch_pool)); 412251881Speter } 413251881Speter 414251881Speter if (db_kind == svn_node_dir && (local_only || repos_only)) 415251881Speter SVN_ERR(ensure_state(eb, local_abspath, TRUE /* skip */, scratch_pool)); 416251881Speter } 417251881Speter 418251881Speter return SVN_NO_ERROR; 419251881Speter} 420251881Speter 421251881Speter 422251881Speter/* Public Interface */ 423251881Spetersvn_error_t * 424251881Spetersvn_wc_diff6(svn_wc_context_t *wc_ctx, 425251881Speter const char *local_abspath, 426251881Speter const svn_wc_diff_callbacks4_t *callbacks, 427251881Speter void *callback_baton, 428251881Speter svn_depth_t depth, 429251881Speter svn_boolean_t ignore_ancestry, 430251881Speter svn_boolean_t show_copies_as_adds, 431251881Speter svn_boolean_t use_git_diff_format, 432251881Speter const apr_array_header_t *changelist_filter, 433251881Speter svn_cancel_func_t cancel_func, 434251881Speter void *cancel_baton, 435251881Speter apr_pool_t *scratch_pool) 436251881Speter{ 437251881Speter struct diff_baton eb = { 0 }; 438251881Speter svn_node_kind_t kind; 439251881Speter svn_boolean_t get_all; 440251881Speter const svn_diff_tree_processor_t *processor; 441251881Speter 442251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 443251881Speter SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath, 444251881Speter FALSE /* allow_missing */, 445251881Speter TRUE /* show_deleted */, 446251881Speter FALSE /* show_hidden */, 447251881Speter scratch_pool)); 448251881Speter 449251881Speter if (kind == svn_node_dir) 450251881Speter eb.anchor_abspath = local_abspath; 451251881Speter else 452251881Speter eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool); 453251881Speter 454251881Speter SVN_ERR(svn_wc__wrap_diff_callbacks(&processor, 455251881Speter callbacks, callback_baton, TRUE, 456251881Speter scratch_pool, scratch_pool)); 457251881Speter 458251881Speter if (use_git_diff_format) 459251881Speter show_copies_as_adds = TRUE; 460251881Speter if (show_copies_as_adds) 461251881Speter ignore_ancestry = FALSE; 462251881Speter 463251881Speter 464251881Speter 465251881Speter /* 466251881Speter if (reverse_order) 467251881Speter processor = svn_diff__tree_processor_reverse_create(processor, NULL, pool); 468251881Speter */ 469251881Speter 470251881Speter if (! show_copies_as_adds && !use_git_diff_format) 471251881Speter processor = svn_diff__tree_processor_copy_as_changed_create(processor, 472251881Speter scratch_pool); 473251881Speter 474289166Speter /* Apply changelist filtering to the output */ 475289166Speter if (changelist_filter && changelist_filter->nelts) 476289166Speter { 477289166Speter apr_hash_t *changelist_hash; 478289166Speter 479289166Speter SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter, 480289166Speter scratch_pool)); 481289166Speter processor = svn_wc__changelist_filter_tree_processor_create( 482289166Speter processor, wc_ctx, local_abspath, 483289166Speter changelist_hash, scratch_pool); 484289166Speter } 485289166Speter 486251881Speter eb.db = wc_ctx->db; 487251881Speter eb.processor = processor; 488251881Speter eb.ignore_ancestry = ignore_ancestry; 489251881Speter eb.show_copies_as_adds = show_copies_as_adds; 490251881Speter eb.pool = scratch_pool; 491251881Speter 492251881Speter if (show_copies_as_adds || use_git_diff_format || !ignore_ancestry) 493251881Speter get_all = TRUE; /* We need unmodified descendants of copies */ 494251881Speter else 495251881Speter get_all = FALSE; 496251881Speter 497251881Speter /* Walk status handles files and directories */ 498251881Speter SVN_ERR(svn_wc__internal_walk_status(wc_ctx->db, local_abspath, depth, 499251881Speter get_all, 500251881Speter TRUE /* no_ignore */, 501251881Speter FALSE /* ignore_text_mods */, 502251881Speter NULL /* ignore_patterns */, 503251881Speter diff_status_callback, &eb, 504251881Speter cancel_func, cancel_baton, 505251881Speter scratch_pool)); 506251881Speter 507251881Speter /* Close the remaining open directories */ 508251881Speter while (eb.cur) 509251881Speter { 510251881Speter struct node_state_t *ns = eb.cur; 511251881Speter 512251881Speter if (!ns->skip) 513251881Speter { 514251881Speter if (ns->propchanges) 515251881Speter SVN_ERR(processor->dir_changed(ns->relpath, 516251881Speter ns->left_src, 517251881Speter ns->right_src, 518251881Speter ns->left_props, 519251881Speter ns->right_props, 520251881Speter ns->propchanges, 521251881Speter ns->baton, 522251881Speter processor, 523251881Speter ns->pool)); 524251881Speter else 525251881Speter SVN_ERR(processor->dir_closed(ns->relpath, 526251881Speter ns->left_src, 527251881Speter ns->right_src, 528251881Speter ns->baton, 529251881Speter processor, 530251881Speter ns->pool)); 531251881Speter } 532251881Speter eb.cur = ns->parent; 533251881Speter svn_pool_clear(ns->pool); 534251881Speter } 535251881Speter 536251881Speter return SVN_NO_ERROR; 537251881Speter} 538