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 */
54 struct 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 */
77 struct 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 */
103 static svn_error_t *
ensure_state(struct diff_baton * eb,const char * local_abspath,svn_boolean_t recursive_skip,apr_pool_t * scratch_pool)104 ensure_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 */
190 static svn_error_t *
diff_status_callback(void * baton,const char * local_abspath,const svn_wc_status3_t * status,apr_pool_t * scratch_pool)191 diff_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 || db_status == svn_wc__db_status_incomplete)
286 {
287 /* Simple diff */
288 base_kind = db_kind;
289 }
290 else if (db_status == svn_wc__db_status_deleted)
291 {
292 svn_wc__db_status_t base_status;
293 repos_only = TRUE;
294 SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
295 NULL, NULL, NULL, NULL, NULL,
296 NULL, NULL, NULL, NULL, NULL,
297 NULL, NULL, NULL,
298 eb->db, local_abspath,
299 scratch_pool, scratch_pool));
300
301 if (base_status != svn_wc__db_status_normal
302 && base_status != svn_wc__db_status_incomplete)
303 return SVN_NO_ERROR;
304 }
305 else
306 {
307 /* working status is either added or deleted */
308 svn_wc__db_status_t base_status;
309
310 SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
311 NULL, NULL, NULL, NULL, NULL,
312 NULL, NULL, NULL, NULL, NULL,
313 NULL, NULL, NULL,
314 eb->db, local_abspath,
315 scratch_pool, scratch_pool));
316
317 if (base_status != svn_wc__db_status_normal
318 && base_status != svn_wc__db_status_incomplete)
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 /* Moved from. Relative from diff anchor*/
395 const char *moved_from_relpath = NULL;
396
397 if (status->moved_from_abspath)
398 {
399 moved_from_relpath = svn_dirent_skip_ancestor(
400 eb->anchor_abspath,
401 status->moved_from_abspath);
402 }
403
404 if (db_kind == svn_node_file)
405 SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath,
406 child_relpath,
407 moved_from_relpath,
408 eb->processor,
409 eb->cur ? eb->cur->baton : NULL,
410 FALSE,
411 eb->cancel_func,
412 eb->cancel_baton,
413 scratch_pool));
414 else if (db_kind == svn_node_dir)
415 SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath,
416 child_relpath, depth_below_here,
417 moved_from_relpath,
418 eb->processor,
419 eb->cur ? eb->cur->baton : NULL,
420 FALSE,
421 eb->cancel_func,
422 eb->cancel_baton,
423 scratch_pool));
424 }
425
426 if (db_kind == svn_node_dir && (local_only || repos_only))
427 SVN_ERR(ensure_state(eb, local_abspath, TRUE /* skip */, scratch_pool));
428 }
429
430 return SVN_NO_ERROR;
431 }
432
433
434 /* Public Interface */
435 svn_error_t *
svn_wc__diff7(svn_boolean_t anchor_at_given_paths,svn_wc_context_t * wc_ctx,const char * local_abspath,svn_depth_t depth,svn_boolean_t ignore_ancestry,const apr_array_header_t * changelist_filter,const svn_diff_tree_processor_t * diff_processor,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)436 svn_wc__diff7(svn_boolean_t anchor_at_given_paths,
437 svn_wc_context_t *wc_ctx,
438 const char *local_abspath,
439 svn_depth_t depth,
440 svn_boolean_t ignore_ancestry,
441 const apr_array_header_t *changelist_filter,
442 const svn_diff_tree_processor_t *diff_processor,
443 svn_cancel_func_t cancel_func,
444 void *cancel_baton,
445 apr_pool_t *result_pool,
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
452 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
453 SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath,
454 FALSE /* allow_missing */,
455 TRUE /* show_deleted */,
456 FALSE /* show_hidden */,
457 scratch_pool));
458
459 eb.anchor_abspath = local_abspath;
460
461 if (anchor_at_given_paths)
462 {
463 /* Anchor the underlying diff processor at the parent of
464 LOCAL_ABSPATH (if possible), and adjust so the outgoing
465 DIFF_PROCESSOR is always anchored at LOCAL_ABSPATH. */
466 /* ### Why anchor the underlying diff processor at the parent? */
467 svn_boolean_t is_wcroot;
468
469 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot,
470 wc_ctx->db, local_abspath, scratch_pool));
471
472 if (!is_wcroot)
473 {
474 const char *relpath;
475
476 eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
477 relpath = svn_dirent_basename(local_abspath, NULL);
478 diff_processor = svn_diff__tree_processor_filter_create(
479 diff_processor, relpath, scratch_pool);
480 }
481 }
482 else if (kind != svn_node_dir)
483 eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
484
485 /* Apply changelist filtering to the output */
486 if (changelist_filter && changelist_filter->nelts)
487 {
488 apr_hash_t *changelist_hash;
489
490 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
491 result_pool));
492 diff_processor = svn_wc__changelist_filter_tree_processor_create(
493 diff_processor, wc_ctx, eb.anchor_abspath,
494 changelist_hash, result_pool);
495 }
496
497 eb.db = wc_ctx->db;
498 eb.processor = diff_processor;
499 eb.ignore_ancestry = ignore_ancestry;
500 eb.pool = scratch_pool;
501
502 if (ignore_ancestry)
503 get_all = TRUE; /* We need unmodified descendants of copies */
504 else
505 get_all = FALSE;
506
507 /* Walk status handles files and directories */
508 SVN_ERR(svn_wc__internal_walk_status(wc_ctx->db, local_abspath, depth,
509 get_all,
510 TRUE /* no_ignore */,
511 FALSE /* ignore_text_mods */,
512 NULL /* ignore_patterns */,
513 diff_status_callback, &eb,
514 cancel_func, cancel_baton,
515 scratch_pool));
516
517 /* Close the remaining open directories */
518 while (eb.cur)
519 {
520 struct node_state_t *ns = eb.cur;
521
522 if (!ns->skip)
523 {
524 if (ns->propchanges)
525 SVN_ERR(diff_processor->dir_changed(ns->relpath,
526 ns->left_src,
527 ns->right_src,
528 ns->left_props,
529 ns->right_props,
530 ns->propchanges,
531 ns->baton,
532 diff_processor,
533 ns->pool));
534 else
535 SVN_ERR(diff_processor->dir_closed(ns->relpath,
536 ns->left_src,
537 ns->right_src,
538 ns->baton,
539 diff_processor,
540 ns->pool));
541 }
542 eb.cur = ns->parent;
543 svn_pool_clear(ns->pool);
544 }
545
546 return SVN_NO_ERROR;
547 }
548
549 svn_error_t *
svn_wc_diff6(svn_wc_context_t * wc_ctx,const char * local_abspath,const svn_wc_diff_callbacks4_t * callbacks,void * callback_baton,svn_depth_t depth,svn_boolean_t ignore_ancestry,svn_boolean_t show_copies_as_adds,svn_boolean_t use_git_diff_format,const apr_array_header_t * changelist_filter,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)550 svn_wc_diff6(svn_wc_context_t *wc_ctx,
551 const char *local_abspath,
552 const svn_wc_diff_callbacks4_t *callbacks,
553 void *callback_baton,
554 svn_depth_t depth,
555 svn_boolean_t ignore_ancestry,
556 svn_boolean_t show_copies_as_adds,
557 svn_boolean_t use_git_diff_format,
558 const apr_array_header_t *changelist_filter,
559 svn_cancel_func_t cancel_func,
560 void *cancel_baton,
561 apr_pool_t *scratch_pool)
562 {
563 const svn_diff_tree_processor_t *processor;
564
565 SVN_ERR(svn_wc__wrap_diff_callbacks(&processor,
566 callbacks, callback_baton, TRUE,
567 scratch_pool, scratch_pool));
568
569 if (use_git_diff_format)
570 show_copies_as_adds = TRUE;
571 if (show_copies_as_adds)
572 ignore_ancestry = FALSE;
573
574 if (! show_copies_as_adds && !use_git_diff_format)
575 processor = svn_diff__tree_processor_copy_as_changed_create(processor,
576 scratch_pool);
577
578 return svn_error_trace(svn_wc__diff7(FALSE,
579 wc_ctx, local_abspath,
580 depth,
581 ignore_ancestry,
582 changelist_filter,
583 processor,
584 cancel_func, cancel_baton,
585 scratch_pool, scratch_pool));
586 }
587
588